anvilsign in

collin/browser-terminal-extension

1// The `devport` convention, reimplemented so the panel can apply it offline.
2//
3// devport(1) is a shell script that hands a local project a stable block of ten
4// ports: `BASE + (cksum(name) % BLOCKS) * SLOT`, where the name is the git
5// repo's directory name. Nothing registers anything — the hash *is* the
6// registry — which is what makes it reproducible here.
7//
8// The panel wants the question the other way round: a browser tab is sitting on
9// http://localhost:12345, and it wants to know which tmux session that port
10// belongs to. devport answers that with `-r`, by walking ~/Code and hashing
11// every directory in it. We do not need the walk: the daemon already tells us
12// each session's working directory, so hashing those few names forward and
13// comparing blocks gives the same answer without a subprocess, a filesystem, or
14// devport being installed at all.
15//
16// The one thing that must not drift is the hash. `portof()` in devport is
17// `printf '%s' "$name" | cksum`, and POSIX cksum is CRC-32/CKSUM: the ordinary
18// CRC-32 polynomial, unreflected, with the message length appended to the
19// message. Any faster-looking CRC-32 (zlib's, reflected, no length) computes a
20// different number and would point at the wrong project.
21
22/** First assignable port. Below this is the crowd of framework defaults. */
23const DEVPORT_BASE = 10240;
24/** How many blocks exist: 2100 * 10 covers 10240-31239. */
25const DEVPORT_BLOCKS = 2100;
26/** Ports per project — app, db, cache, debugger, whatever. */
27const DEVPORT_SLOT = 10;
28
29/** Lazily built, because this runs on every tab activation. @type {number[] | null} */
30let table = null;
31
32/** The CRC-32/CKSUM byte table, poly 0x04C11DB7, unreflected. */
33function crcTable() {
34 if (table) return table;
35 const t = new Array(256);
36 for (let i = 0; i < 256; i++) {
37 let c = i << 24;
38 for (let k = 0; k < 8; k++) c = c & 0x80000000 ? (c << 1) ^ 0x04c11db7 : c << 1;
39 t[i] = c >>> 0;
40 }
41 table = t;
42 return t;
43}
44
45/**
46 * POSIX `cksum`, as a number.
47 *
48 * The trailing length loop is not decoration: cksum feeds the byte count in
49 * after the data, low byte first, dropping the high zero bytes. Without it the
50 * checksums differ from the shell's for every input.
51 *
52 * @param {string} s
53 * @returns {number}
54 */
55function cksum(s) {
56 const t = crcTable();
57 const bytes = new TextEncoder().encode(s);
58 let crc = 0;
59 for (const b of bytes) crc = ((crc << 8) ^ t[((crc >>> 24) ^ b) & 0xff]) >>> 0;
60 for (let n = bytes.length; n !== 0; n = Math.floor(n / 256)) {
61 crc = ((crc << 8) ^ t[((crc >>> 24) ^ (n & 0xff)) & 0xff]) >>> 0;
62 }
63 return (crc ^ 0xffffffff) >>> 0;
64}
65
66/**
67 * The first port of the block a project name owns — what plain `devport` prints
68 * in a directory of that name.
69 *
70 * @param {string} name
71 * @returns {number}
72 */
73function portOf(name) {
74 return DEVPORT_BASE + (cksum(name) % DEVPORT_BLOCKS) * DEVPORT_SLOT;
75}
76
77/**
78 * The block a port falls in, or null when it is outside the assignable range —
79 * 3000, 5173, 8080 and every other hand-picked port land here, and they carry
80 * no project in them to find.
81 *
82 * @param {number} port
83 * @returns {number | null}
84 */
85function blockOf(port) {
86 if (!Number.isInteger(port)) return null;
87 if (port < DEVPORT_BASE || port >= DEVPORT_BASE + DEVPORT_BLOCKS * DEVPORT_SLOT) return null;
88 return port - ((port - DEVPORT_BASE) % DEVPORT_SLOT);
89}
90
91/**
92 * Whether `port` is one of the ten this name owns.
93 *
94 * A block is ten ports wide and there are only 2100 blocks, so a match is
95 * evidence and not proof — two projects in ~/Code can share one. That is why
96 * this only ever *suggests* a pin rather than making one.
97 *
98 * @param {number} port
99 * @param {string} name
100 * @returns {boolean}
101 */
102function owns(port, name) {
103 if (!name) return false;
104 const block = blockOf(port);
105 return block !== null && block === portOf(name);
106}
107
108const Devport = { BASE: DEVPORT_BASE, BLOCKS: DEVPORT_BLOCKS, SLOT: DEVPORT_SLOT, cksum, portOf, blockOf, owns };
109
110if (typeof module !== "undefined" && module.exports) {
111 module.exports = Devport;
112}