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