collin/mahjong · f857279a
The tunnel keeps its name: mahjong-table, every run
Collin Richards · 2026-08-21 17:01 UTC · f857279a2dbb8b7527af80d127b27e1d3e991ce1 · parent a0adfbc3 · browse files
modifiedREADME.md+6 −0
| ⋯ 228 unchanged lines | |||
| 229 | 229 | or `rsgrok` is not on the PATH (`RSGROK_BIN` points elsewhere), links fall | |
| 230 | 230 | back to the page's own address and the tunnel is retried on later joins. | |
| 231 | 231 | ||
| 232 | + | The tunnel always asks for the same subdomain — `mahjong-table`, or whatever | |
| 233 | + | `TUNNEL_NAME` says — so restarts land back on the URL people already have | |
| 234 | + | instead of burning a fresh name each run. A name someone else already holds | |
| 235 | + | kills that first attempt before it prints a URL; the retry then goes out | |
| 236 | + | nameless and takes whatever it is given. | |
| 237 | + | ||
| 232 | 238 | ## On a phone | |
| 233 | 239 | ||
| 234 | 240 | The table assumes four people sitting around a screen lying flat, and about | |
| ⋯ 287 unchanged lines | |||
modifiedserver/rooms.ts+15 −1
| ⋯ 73 unchanged lines | |||
| 74 | 74 | * moment the server knows its port, read the https URL off the tunnel-up | |
| 75 | 75 | * line, and hand it to every client on join. A tunnel that dies — network | |
| 76 | 76 | * gone, rsgrok missing — is retried on later joins, at most once per window. | |
| 77 | + | * | |
| 78 | + | * We always ask for the same subdomain (`TUNNEL_NAME`, default `mahjong-table`), so | |
| 79 | + | * a restart lands back on the URL people already have and we spend one name | |
| 80 | + | * rather than a fresh one per run. If that name is taken — a second copy of | |
| 81 | + | * the server, a tunnel the last run left behind — the first attempt dies | |
| 82 | + | * without ever printing a URL, and the retry goes out nameless. | |
| 77 | 83 | */ | |
| 78 | 84 | const TUNNEL_RETRY = 10_000; | |
| 79 | 85 | /** How long a join will wait on a tunnel still shaking hands. */ | |
| 80 | 86 | const TUNNEL_WAIT = 3_000; | |
| 87 | + | /** The subdomain we ask for: {name}.vibe.richardscollin.com. */ | |
| 88 | + | const TUNNEL_NAME = process.env.TUNNEL_NAME ?? 'mahjong-table'; | |
| 81 | 89 | ||
| 82 | 90 | export function attachRooms(server: UpgradeServer, path = '/ws'): void { | |
| 83 | 91 | const rooms = new Map<string, RoomRec>(); | |
| ⋯ 7 unchanged lines | |||
| 91 | 99 | let tunnel: ChildProcess | null = null; | |
| 92 | 100 | let tunnelUrl = ''; | |
| 93 | 101 | let tunnelDiedAt = 0; | |
| 102 | + | /** Cleared once a named attempt dies URL-less: the name is someone else's. */ | |
| 103 | + | let tunnelName = TUNNEL_NAME; | |
| 94 | 104 | let waiters: Array<() => void> = []; | |
| 95 | 105 | const wake = () => { | |
| 96 | 106 | for (const w of waiters) w(); | |
| ⋯ 4 unchanged lines | |||
| 101 | 111 | if (process.env.PUBLIC_URL || tunnel || Date.now() - tunnelDiedAt < TUNNEL_RETRY) return; | |
| 102 | 112 | const port = ownPort(); | |
| 103 | 113 | if (port === null) return; | |
| 114 | + | const named = tunnelName; | |
| 104 | 115 | const child = spawn( | |
| 105 | 116 | process.env.RSGROK_BIN ?? 'rsgrok', | |
| 106 | 117 | // No :4040 inspection API — another agent may already own that port. | |
| 107 | - | ['http', `127.0.0.1:${port}`, '--web-addr', 'false'], | |
| 118 | + | ['http', `127.0.0.1:${port}`, '--web-addr', 'false', ...(named ? ['-n', named] : [])], | |
| 108 | 119 | { stdio: ['ignore', 'pipe', 'inherit'] }, | |
| 109 | 120 | ); | |
| 110 | 121 | tunnel = child; | |
| ⋯ 10 unchanged lines | |||
| 121 | 132 | }); | |
| 122 | 133 | const gone = () => { | |
| 123 | 134 | if (tunnel === child) { | |
| 135 | + | // Died before it ever said a URL, and we had asked for a name: read | |
| 136 | + | // that as the name being spoken for, and go nameless from here. | |
| 137 | + | if (!tunnelUrl && named) tunnelName = ''; | |
| 124 | 138 | tunnel = null; | |
| 125 | 139 | tunnelUrl = ''; | |
| 126 | 140 | tunnelDiedAt = Date.now(); | |
| ⋯ 195 unchanged lines | |||