d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 1 | export const ENGINE_CHANNEL = 'malice-engine'; |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 2 | |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 3 | export type HelloAction = 'ignore' | 'yield' | 'announce'; |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 4 | |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 5 | interface BuildStamp { |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 6 | seconds: number; |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 7 | tag: string; |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 8 | } |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 9 | |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 10 | function parseBuild(build: string): BuildStamp | null { |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 11 | const match = /^(\d+)-(.+)$/.exec(build); |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 12 | return match ? { seconds: Number(match[1]), tag: match[2] } : null; |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 13 | } |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 14 | |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 15 | /** Positive when `a` is newer than `b`, null when either is not a build id. */ |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 16 | export function compareBuilds(a: string, b: string): number | null { |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 17 | const left = parseBuild(a); |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 18 | const right = parseBuild(b); |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 19 | if (!left || !right) return null; |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 20 | if (left.seconds !== right.seconds) return left.seconds - right.seconds; |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 21 | return left.tag < right.tag ? -1 : left.tag > right.tag ? 1 : 0; |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 22 | } |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 23 | |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 24 | /** |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 25 | * What a worker of build `own` does on hearing a hello from build `other`. |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 26 | * A newer peer wins the store lock, so the worker yields. An older peer is |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 27 | * told to yield by repeating our own hello, which it will judge the same way. |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 28 | */ |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 29 | export function helloAction(own: string, other: string): HelloAction { |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 30 | const order = compareBuilds(other, own); |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 31 | if (order === null || order === 0) return 'ignore'; |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 32 | return order > 0 ? 'yield' : 'announce'; |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 33 | } |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 34 | |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 35 | export const RELOAD_GUARD_MS = 60_000; |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 36 | |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 37 | /** A superseded tab reloads itself once, so a shell stuck on the old build cannot loop. */ |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 38 | export function mayAutoReload(now: number, lastReload: number | null): boolean { |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 39 | return lastReload === null || now - lastReload >= RELOAD_GUARD_MS; |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 40 | } |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 41 | |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 42 | export function retryDelay(attempt: number): number { |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 43 | return Math.min(1000 * 2 ** attempt, 8000); |
d9be72fdb393 | atagen | 2026-10-26 09:23 UTC | 44 | } |