Tuesday, April 16, 2024

from "Headcrash"

"I mean." Uberman cleared his throat, adjusted his necktie, and began delivering his morning whine, which is clearly what he'd been intending to do all along. "This is, what? The third network outage this year?"

I stopped. "We're having some problems porting your database to our server, sir." I edged one step closer to the exit.

"I mean," Uberman scowled, "if I can't depend on your network, I'm screwed. Just totally screwed, you know?"

Then how come you're not smiling? is what I thought, but "We'll have it back up as soon as possible," is what I said.

"I mean," Uberman whacked his PC with his newspaper again, "we never had problems like this before MDE acquired us. Dammit, our old Applied Photonics network never crashed! Not once!"

"So I've heard." And heard, and heard, and heard! And if you gave me just sixteen users in a one-floor office, I could make this network look pretty good, too.

Bruce Bethke, "Headcrash"
"Headcrash" is kind of a no-account cyberpunk-y book from the mid-90s... the technobabble is pretty clumsy, but for some reason this passage has stuck with me for 20 years so I thought I'd post it - from time to time, its reminder that little toy systems can get away with things that projects you want to scale can't is useful.

Thursday, April 4, 2024

ux ideas!

 Some UX humor ideation from Sorn Iverson ...
Some of my favorites:





easy drag-and-drop sorting/reordering with no build system vanilla js using SortableJS

I've already linked to a defense of making webapps without a build system - it really is great for long term sustainability and updates, and PHP + Vanilla JS are actually really powerful tools these days. You can even build dynamic page parts in a declarative style, using JSX looking string templates: 

const list = document.getElementById("myList");
list.innerHTML = items.map((item)=>`<li>${item}</li>`).join("");

It's all right there in the browser these days!

One thing I didn't know how to do is add drag-and-drop reordering - at least not in a way that was mobile-friendly. (The browser's Drag and Drop API is one place where the usual abstraction between computers with mice or touchpads and mobile devices with touch screen breaks down.)

I knew of SortableJS but the README didn't make it clear that it works without a build system. ChatGPT straightened me out - using SortableJS is just a <script> tag away:

<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.14.0/Sortable.min.js"></script>

(Hmm, there's no authentication checksum so you might want to grab the file and move it locally - I mean in general eliminating external dependencies is a good practice, tying into that long term sustainability: linkrot is real and ubiquitous, even well meaning CDNs broke projects depending on them as stuff switched from http to https...)

But back to sortable! Here's about the simplest example:
<script src="Sortable.min.js"></script>
<ol id="items-list">
    <!-- List items will be dynamically inserted here -->
</ol>
<script>
document.addEventListener('DOMContentLoaded', () => {
    const items = ["Apple", "Banana", "Cherry", "Date"];

    const listElement = document.getElementById('items-list');
    listElement.innerHTML = items.map(item => `<li>${item}</li>`).join('');

    new Sortable(document.getElementById('items-list'), {
        animation: 150, // Animation speed (milliseconds)
        onEnd: function (evt) {
            items.splice(evt.newIndex, 0, items.splice(evt.oldIndex, 1)[0]);
        }
    });
});
</script>


You can see it in action - everything is right there in the source.

So one slightly wonky aspect is that this kind of reverses the usual flow of declarative programming: Sortable update the DOM and then relies on the onEnd to make your internal state match. Still, a small price to pay for a mobile-friendly reordering solution; so much better than providing little /\ and \/ buttons!

You can go a little further and add some grabbable handles - here's how to make those three lines:
<span class="drag-handle">&#x2630;</span>

Add the reference to the Sortable options object:
handle: ".drag-handle",

and then a little CSS:
  ul {
   list-style-type: none;
   padding-left: 0;
 }
 .drag-handle {
    cursor: grab; /* Changes the cursor to indicate movability */
    padding-right: 10px; /* Spacing between the handle and the text */
    user-select: none; /* Prevents text selection */
  }

  /* Style the drag handle on hover and active states for better feedback */
  .drag-handle:hover, .drag-handle:active {
    cursor: grabbing;
  }


Here's a working example of that.

Finally, if you DO rerender the list, you should probably hold onto the returned Sortable object and call .destroy() and then reapply. That's obviously more tied into whatever app you're actually making, but here is a basic destroy/rerender example as well, which is a bit closer to my usecase.

Wednesday, April 3, 2024

robohorn

As a kid I wondered if you could make a robot trumpet player. The answer is now yes. I wonder how the cyber-embouchure works... (you can google up a robot sax player as well...)

Related Diesel Sweeties comic 10:



dangerous times

A while back I posted I’m harvesting credit card numbers and passwords from your site. Here’s how. a fake (or was it) description of how the overwhelming amount of npm-ish dependencies can make your webapp vulnerable, if a bad actor makes a helpful looking tiny utility (that the framework you like uses, even if you don't see it as worthwhile) and covers their tracks well. 

What we know about the xz Utils backdoor that almost infected the world is along the same lines, except i can't preach the gospel of "use fewer dependencies!"

None of this is new - the seminal Reflections on Trusting Trust - where a trojan could be snuck into a C compiler, covering its tracks all along the way - is 40 years old. But it's scary.

Related: You Are All On The Hobbyists Maintainers' Turf Now. The business world has absolutely embraced the Open Source paradigm - or at least decided to take freely of its fruit, and so the risk of poisoned flowers is there, even as more and more we depend on the good will "doing it for the reputation and to scratch my own itch" work of fewer and fewer - or as XKCD put it:




Monday, April 1, 2024

ObHack: see if I've hit quota

 I know I've mentioned Usenet's alt.hackers and "ObHacks" before... the latest is this:

My main VPS is pretty old (I'm migrating to a new one) and it doesn't do a good job of letting me how close to my disk quota I am - but when I start having problems, the clearest telltale is that if I try writing to a file from terminal, the file is made but is zero bytes.

But on that same server I run my personal start page, so many times during the day I'm going back to that site. So now the script that generates that page tries does this:

// QUOTA PROBLEM CHECK
// Get the current Unix timestamp
$timestamp = time();
// Write it to a file named "timetest.txt"
file_put_contents("timetest.txt", $timestamp);
// Initialize $bodyclass with an empty value
$bodyclass = "";
// Open the file and read the timestamp
$readTimestamp = file_get_contents("timetest.txt");

// Check if we can open the file, if it's not empty, and if the contents match
if ($readTimestamp === FALSE
        || $readTimestamp == ""
        || $readTimestamp != $timestamp) {
    $bodyclass = "alert";
}

and I made the body.alert CSS to be bright red, so I'll see that something is up.


Heh, that piece of PHP reminds me of a thought I had recently; in general I don't adore TypeScript because while I like having JSON arguments described, there are better ways to do that, I find the syntax makes code a bit harder to read, and also there's more of a chance of a false sense of security, since you don't REALLY know what types are going to come back from a given endpoint at runtime.

My observation with that is that 90% of the typing issues I *do* have in JS would go away if "+" always meant addition and not string concatenation. Which is how PHP does it. But then I realized that PHP only gets away with supporting both $string.$concatenation and $object.key lookup syntax because it prefixes its variables with "$", otherwise you'd have to do object["key"] only since object.key would look like a lookup reference.

Monday, March 25, 2024

baby mock json endpoint server and cloning endpoints

My boss thought it strange that we didn't already have a "mock server" so that we could keep doing UI work in case the endpoints went down.

He suggested using json-server but that wasn't made to emulate a rich set of endpoints, just one level deep, either GETing all the entries or just one at a time by id. Luckily that kind of server is the easy part of the assignment.

The first part was to make a file "urls.txt", with just the relevant part of the endpoint...

then this script load-db.js hits each of those entries and writes the content to a file in mocks/

const express = require("express");
const fs = require("fs");
const path = require("path");
const app = express();

// Ensure the mocks directory exists
const mocksDir = path.join(__dirname, "mocks");
if (!fs.existsSync(mocksDir)) {
fs.mkdirSync(mocksDir);
}

// Read the command line argument for the prefix
const prefix = process.argv[2];

// Read the URLs from urls.txt
const urlsFilePath = path.join(__dirname, "urls.txt");
const urls = fs.readFileSync(urlsFilePath, "utf8").split("\n");

// Function to make a safe filename from a URL path and append .json
const makeSafeFilename = (urlPath) => encodeURIComponent(urlPath.replace(/^\//, "").replace(/\//g, "_")) + ".json";

// Fetch content and save it as JSON
urls.forEach(async (urlPath) => {
if (!urlPath.trim()) return; // Skip empty lines
const content = await fetch(`${prefix}${urlPath}`).then((res) => res.json());
const safeFilename = makeSafeFilename(urlPath);
fs.writeFileSync(path.join(mocksDir, safeFilename), JSON.stringify(content, null, 2));
console.log(`Saved content from ${urlPath} to ${safeFilename}`);
});

Then the server.js just looks like this:

const express = require("express");
const fs = require("fs");
const path = require("path");
const cors = require("cors");
const app = express();

app.use(cors());

const mocksDir = path.join(__dirname, "mocks");

app.get("/", (req, res) => {
fs.readdir(mocksDir, (err, files) => {
if (err) {
console.error(err);
return res.status(500).send("Server error");
}

const links = files
.filter((file) => file.endsWith(".geojson") || file.endsWith(".json"))
.map((file) => {
const decodedFilename = file.replace(/\..+$/, "").replace(/_/g, "/");
const encodedPath = decodedFilename
.split("/")
.map((part) => encodeURIComponent(part))
.join("/");
return `<li><a href="/${encodedPath}">${file}</a></li>`;
})
.join("");

res.send(`<ul>${links}</ul>`);
});
});

app.get("*", (req, res) => {
const safeFilename = encodeURIComponent(req.path.replace(/^\//, "").replace(/\//g, "_"));
const filePath = path.join(mocksDir, safeFilename);

if (fs.existsSync(`${filePath}.geojson`) || fs.existsSync(`${filePath}.json`)) {
res.type("application/json");
res.sendFile(fs.existsSync(`${filePath}.geojson`) ? `${filePath}.geojson` : `${filePath}.json`);
console.log(req.path);
} else {
res.status(404).send("Not Found");
console.error(`404 ${req.path}`);
}
});

// Use environment variable for port or a default value
const port = process.env.PORT || 3000;

app.listen(port, () => {
console.log(`Mock server listening at http://localhost:${port}`);
});




package.json is

{
"name": "mock-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.3",
"node-fetch": "^3.3.2"
}
}



(Sigh, I suppose if I was feeling ambitious I should make my a little project out of this and put it on github, but I'm sure someone has, and better.)

UPDATE: this version now lists the endpoint/files it knows about if you go to the root, and listens to process.env.PORT for instructions on port