{"_id":"@arnoldgraf/cross-tab-worker","_rev":"8-dda04cab020a1e2a72f1c40484536ef5","name":"@arnoldgraf/cross-tab-worker","dist-tags":{"latest":"0.1.7"},"versions":{"0.1.0":{"name":"@arnoldgraf/cross-tab-worker","version":"0.1.0","_id":"@arnoldgraf/cross-tab-worker@0.1.0","maintainers":[{"name":"arnoldgraf","email":"arnold.graf@gmail.com"}],"homepage":"https://github.com/arnold-graf/cross-tab-worker#readme","bugs":{"url":"https://github.com/arnold-graf/cross-tab-worker/issues"},"dist":{"shasum":"ad5cc5b314e04eac0eb7ef22950232639196a0c0","tarball":"https://registry.npmjs.org/@arnoldgraf/cross-tab-worker/-/cross-tab-worker-0.1.0.tgz","fileCount":31,"integrity":"sha512-9No9fjPzzJVJ4pfifvjPYt3PyphGtLV/no8/em0RLMSWb9IRpQy/h9MayNMdRinEgnpezSBC2SpptZ2pzFOaXw==","signatures":[{"sig":"MEYCIQCYhCkMq0cUGFErhuwQ0FpUbs7Gh/bU0i7FvKidbNpExgIhAJ6NuIr9LzLE28LNlWYHAlxnDmc7vUDH3E4C0ccp1iN5","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arnoldgraf%2fcross-tab-worker@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":71512},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22.12.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"a2c9efe0fef339652be1afc9415b4c42e9a8fded","scripts":{"test":"vitest run tests","build":"tsc -p tsconfig.build.json","test:e2e":"playwright test --project=chromium","typecheck":"tsc --noEmit","test:watch":"vitest tests","prepublishOnly":"npm run build","test:e2e:webkit":"playwright test --project=webkit","test:e2e:webkit:headed":"playwright test --project=webkit --ui"},"_npmUser":{"name":"arnoldgraf","email":"arnold.graf@gmail.com"},"repository":{"url":"git+https://github.com/arnold-graf/cross-tab-worker.git","type":"git"},"_npmVersion":"10.9.0","description":"Drop-in coordination wrapper that keeps exactly one Worker alive across all same-origin tabs.","directories":{},"_nodeVersion":"22.12.0","_hasShrinkwrap":false,"devDependencies":{"vite":"^8.0.10","vitest":"^4.1.5","typescript":"^6.0.3","@playwright/test":"^1.59.1"},"peerDependencies":{"typescript":">=5.0"},"peerDependenciesMeta":{"typescript":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/cross-tab-worker_0.1.0_1777382879995_0.5880868103435941","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@arnoldgraf/cross-tab-worker","version":"0.1.1","_id":"@arnoldgraf/cross-tab-worker@0.1.1","maintainers":[{"name":"arnoldgraf","email":"arnold.graf@gmail.com"}],"homepage":"https://github.com/arnold-graf/cross-tab-worker#readme","bugs":{"url":"https://github.com/arnold-graf/cross-tab-worker/issues"},"dist":{"shasum":"7210066d899bf7b9f1ccf5897b371fce06e61ef4","tarball":"https://registry.npmjs.org/@arnoldgraf/cross-tab-worker/-/cross-tab-worker-0.1.1.tgz","fileCount":31,"integrity":"sha512-0G0/e7Ljxj4EqukRPvLiNqEj6eR+jhA/5OOquuGXDQIChRwJuoq6TTpTfaV3uBfSSCrG6jW5qKX+3o+CvfDqyA==","signatures":[{"sig":"MEQCIDkoLT0D86qf4hiWW4/Bz0YTadCoK/RSXjwavOeWALXOAiBX1Wsd7U6d+QvgMdEa8e+SyUoKOZQsnsBPxVsrayk/uA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arnoldgraf%2fcross-tab-worker@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":71768},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22.12.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"cb55a333df00ce884a3f42bb4c9037fd0e0de705","scripts":{"test":"vitest run tests","build":"tsc -p tsconfig.build.json","test:e2e":"playwright test --project=chromium","typecheck":"tsc --noEmit","test:watch":"vitest tests","prepublishOnly":"npm run build","test:e2e:webkit":"playwright test --project=webkit","test:e2e:webkit:headed":"playwright test --project=webkit --ui"},"_npmUser":{"name":"arnoldgraf","email":"arnold.graf@gmail.com"},"repository":{"url":"git+https://github.com/arnold-graf/cross-tab-worker.git","type":"git"},"_npmVersion":"10.9.0","description":"Drop-in coordination wrapper that keeps exactly one Worker alive across all same-origin tabs.","directories":{},"_nodeVersion":"22.12.0","_hasShrinkwrap":false,"devDependencies":{"vite":"^8.0.10","vitest":"^4.1.5","typescript":"^6.0.3","@playwright/test":"^1.59.1"},"peerDependencies":{"typescript":">=5.0"},"peerDependenciesMeta":{"typescript":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/cross-tab-worker_0.1.1_1777383822880_0.11221958850849023","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@arnoldgraf/cross-tab-worker","version":"0.1.2","_id":"@arnoldgraf/cross-tab-worker@0.1.2","maintainers":[{"name":"arnoldgraf","email":"arnold.graf@gmail.com"}],"homepage":"https://github.com/arnold-graf/cross-tab-worker#readme","bugs":{"url":"https://github.com/arnold-graf/cross-tab-worker/issues"},"dist":{"shasum":"fdbfc2f393a8ffcdf6c1a3d135628dd67659a8b1","tarball":"https://registry.npmjs.org/@arnoldgraf/cross-tab-worker/-/cross-tab-worker-0.1.2.tgz","fileCount":32,"integrity":"sha512-27cU5my0omMSgut1FbyyJ1Ac0IHg8aqnMCLRxOwIwII+RN2rjcQpsKs2T/8jKrlzY099LJqlRghKepLjrLS8zA==","signatures":[{"sig":"MEQCIFiUPo2xqKxBGF+yL3gAeTPMjDWXeSJX2O98HliKrUdAAiAyh0SPJkROPmoFjsKP8FP64gtD52uQxyQeCwLeWERxhA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arnoldgraf%2fcross-tab-worker@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":71803},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22.12.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"1d8e4d5ca0d54c354f72665022f36318adcb4c9e","scripts":{"test":"vitest run tests","build":"tsc -p tsconfig.build.json","test:e2e":"playwright test --project=chromium","typecheck":"tsc --noEmit","test:watch":"vitest tests","prepublishOnly":"npm run build","test:e2e:webkit":"playwright test --project=webkit","test:e2e:webkit:headed":"playwright test --project=webkit --ui"},"_npmUser":{"name":"arnoldgraf","email":"arnold.graf@gmail.com"},"repository":{"url":"git+https://github.com/arnold-graf/cross-tab-worker.git","type":"git"},"_npmVersion":"10.9.0","description":"Drop-in coordination wrapper that keeps exactly one Worker alive across all same-origin tabs.","directories":{},"_nodeVersion":"22.12.0","_hasShrinkwrap":false,"devDependencies":{"vite":"^8.0.10","vitest":"^4.1.5","typescript":"^6.0.3","@playwright/test":"^1.59.1"},"peerDependencies":{"typescript":">=5.0"},"peerDependenciesMeta":{"typescript":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/cross-tab-worker_0.1.2_1777384478787_0.5481311127536361","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@arnoldgraf/cross-tab-worker","version":"0.1.3","_id":"@arnoldgraf/cross-tab-worker@0.1.3","maintainers":[{"name":"arnoldgraf","email":"arnold.graf@gmail.com"}],"homepage":"https://github.com/arnold-graf/cross-tab-worker#readme","bugs":{"url":"https://github.com/arnold-graf/cross-tab-worker/issues"},"dist":{"shasum":"a81510a0274b7a97c4c89b6c97d12a9654008e8e","tarball":"https://registry.npmjs.org/@arnoldgraf/cross-tab-worker/-/cross-tab-worker-0.1.3.tgz","fileCount":32,"integrity":"sha512-ve3q3r5AjLuMqwm47DZcv2N8LMmq5Bsv2A9OTqpODNoZFPS6B40224dhKGCn0XlcR1srrpXe+wMG21Jl7001Tw==","signatures":[{"sig":"MEYCIQCt7WR+SwwY/hh12dQ9+TJFR96+BlO9hBBB/g16E+MjcgIhAN0pH7jr8ioGP9R4Hp+qnjDuKDWau9B0xJnlWvXMyG9w","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arnoldgraf%2fcross-tab-worker@0.1.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":71744},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22.12.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"33c1dc402671636ad80d5ec40fc36e1cd4c0cc3b","scripts":{"test":"vitest run tests","build":"tsc -p tsconfig.build.json","test:e2e":"playwright test --project=chromium","typecheck":"tsc --noEmit","test:watch":"vitest tests","prepublishOnly":"npm run build","test:e2e:webkit":"playwright test --project=webkit","test:e2e:webkit:headed":"playwright test --project=webkit --ui"},"_npmUser":{"name":"arnoldgraf","email":"arnold.graf@gmail.com"},"repository":{"url":"git+https://github.com/arnold-graf/cross-tab-worker.git","type":"git"},"_npmVersion":"10.9.0","description":"Drop-in coordination wrapper that keeps exactly one Worker alive across all same-origin tabs.","directories":{},"_nodeVersion":"22.12.0","_hasShrinkwrap":false,"devDependencies":{"vite":"^8.0.10","vitest":"^4.1.5","typescript":"^6.0.3","@playwright/test":"^1.59.1"},"peerDependencies":{"typescript":">=5.0"},"peerDependenciesMeta":{"typescript":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/cross-tab-worker_0.1.3_1777384761293_0.7024852147432754","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@arnoldgraf/cross-tab-worker","version":"0.1.4","_id":"@arnoldgraf/cross-tab-worker@0.1.4","maintainers":[{"name":"arnoldgraf","email":"arnold.graf@gmail.com"}],"homepage":"https://github.com/arnold-graf/cross-tab-worker#readme","bugs":{"url":"https://github.com/arnold-graf/cross-tab-worker/issues"},"dist":{"shasum":"b76e1aea8644f1a8e6e9b64cc1e637d0d0551198","tarball":"https://registry.npmjs.org/@arnoldgraf/cross-tab-worker/-/cross-tab-worker-0.1.4.tgz","fileCount":32,"integrity":"sha512-nJ70KBRyuT4qa5s6jS3g2X08+sxKzfWNpuJbaYH5oYtF8T1K9I3cmWXQvnlzsUY6WFW6QkKGJXQ+DEdAkU3CtQ==","signatures":[{"sig":"MEQCIFDiuH3vnvvNL+MynxrWn6NOv+EN4B7zBw5hOzh0RwP2AiBjxmCyx9X5j6vYE9G5XENYvqLcusYtErjkXH/K4RkFYA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arnoldgraf%2fcross-tab-worker@0.1.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":71854},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22.12.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"36871e54af299b320a9e72cebbed8629adf2ba89","scripts":{"test":"vitest run tests","build":"tsc -p tsconfig.build.json","test:e2e":"npm run build && playwright test --project=chromium","typecheck":"tsc --noEmit","test:watch":"vitest tests","prepublishOnly":"npm run build","test:e2e:webkit":"npm run build && playwright test --project=webkit","test:e2e:webkit:headed":"npm run build && playwright test --project=webkit --ui"},"_npmUser":{"name":"arnoldgraf","email":"arnold.graf@gmail.com"},"repository":{"url":"git+https://github.com/arnold-graf/cross-tab-worker.git","type":"git"},"_npmVersion":"10.9.0","description":"Drop-in coordination wrapper that keeps exactly one Worker alive across all same-origin tabs.","directories":{},"_nodeVersion":"22.12.0","_hasShrinkwrap":false,"devDependencies":{"vite":"^8.0.10","vitest":"^4.1.5","typescript":"^6.0.3","@playwright/test":"^1.59.1"},"peerDependencies":{"typescript":">=5.0"},"peerDependenciesMeta":{"typescript":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/cross-tab-worker_0.1.4_1777468729219_0.7060629796273006","host":"s3://npm-registry-packages-npm-production"}},"0.1.5":{"name":"@arnoldgraf/cross-tab-worker","version":"0.1.5","_id":"@arnoldgraf/cross-tab-worker@0.1.5","maintainers":[{"name":"arnoldgraf","email":"arnold.graf@gmail.com"}],"homepage":"https://github.com/arnold-graf/cross-tab-worker#readme","bugs":{"url":"https://github.com/arnold-graf/cross-tab-worker/issues"},"dist":{"shasum":"6203129005cf5df6e208c56e2c001a13ccb27e87","tarball":"https://registry.npmjs.org/@arnoldgraf/cross-tab-worker/-/cross-tab-worker-0.1.5.tgz","fileCount":32,"integrity":"sha512-KF8YUhI2H64WcdvE1zZ2QQXEZP8cVBqiUbEodgdb98EOAsgdn1w6dayUev0E3xW9mI/7diz7t8xOaA3zL7NJ1A==","signatures":[{"sig":"MEUCIQCzNigyGwJIF92ujf28ZqdkheHaM6HveJr9QBnDJkCXNgIgDImsbgX3LDsVjWb5ZMfr4eUB+NilTWJhwTPpTJe+N2w=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arnoldgraf%2fcross-tab-worker@0.1.5","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":75392},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22.12.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"b97ce594652a372ba209bef94ef4fb8f72f51e3f","scripts":{"test":"vitest run tests","build":"tsc -p tsconfig.build.json","test:e2e":"npm run build && playwright test --project=chromium","typecheck":"tsc --noEmit","test:watch":"vitest tests","prepublishOnly":"npm run build","test:e2e:webkit":"npm run build && playwright test --project=webkit","test:e2e:webkit:headed":"npm run build && playwright test --project=webkit --ui"},"_npmUser":{"name":"arnoldgraf","email":"arnold.graf@gmail.com"},"repository":{"url":"git+https://github.com/arnold-graf/cross-tab-worker.git","type":"git"},"_npmVersion":"10.9.0","description":"Drop-in coordination wrapper that keeps exactly one Worker alive across all same-origin tabs.","directories":{},"_nodeVersion":"22.12.0","_hasShrinkwrap":false,"devDependencies":{"vite":"^8.0.10","vitest":"^4.1.5","typescript":"^6.0.3","@playwright/test":"^1.59.1"},"peerDependencies":{"typescript":">=5.0"},"peerDependenciesMeta":{"typescript":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/cross-tab-worker_0.1.5_1777469337786_0.5053392034029545","host":"s3://npm-registry-packages-npm-production"}},"0.1.6":{"name":"@arnoldgraf/cross-tab-worker","version":"0.1.6","_id":"@arnoldgraf/cross-tab-worker@0.1.6","maintainers":[{"name":"arnoldgraf","email":"arnold.graf@gmail.com"}],"homepage":"https://github.com/arnold-graf/cross-tab-worker#readme","bugs":{"url":"https://github.com/arnold-graf/cross-tab-worker/issues"},"dist":{"shasum":"09e707c69720aac37fd6d84a745004f64ebebfda","tarball":"https://registry.npmjs.org/@arnoldgraf/cross-tab-worker/-/cross-tab-worker-0.1.6.tgz","fileCount":32,"integrity":"sha512-r8dVKJ+5tclXQ+wZbA6uzKi+Q8GxRadrf1+3b5+TfXChyCQecQ61U1JE0Of5EEK17fhbxaWSAQwdkSQYShpUAQ==","signatures":[{"sig":"MEQCIAcaKgljAM7hpysEfbB0YRKwyfQDTBlP4WZdmqdXrctRAiAiaBP6KbkWQEEdA7fd2ft1HAZektXRl0a5oKyIFi8ygw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arnoldgraf%2fcross-tab-worker@0.1.6","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":79888},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22.12.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"b66eea84c93934e829c353d56c05a29446ffae61","scripts":{"test":"vitest run tests","build":"tsc -p tsconfig.build.json","test:e2e":"npm run build && playwright test --project=chromium","typecheck":"tsc --noEmit","test:watch":"vitest tests","prepublishOnly":"npm run build","test:e2e:webkit":"npm run build && playwright test --project=webkit","test:e2e:webkit:headed":"npm run build && playwright test --project=webkit --ui"},"_npmUser":{"name":"arnoldgraf","email":"arnold.graf@gmail.com"},"repository":{"url":"git+https://github.com/arnold-graf/cross-tab-worker.git","type":"git"},"_npmVersion":"10.9.0","description":"Drop-in coordination wrapper that keeps exactly one Worker alive across all same-origin tabs.","directories":{},"_nodeVersion":"22.12.0","_hasShrinkwrap":false,"devDependencies":{"vite":"^8.0.10","vitest":"^4.1.5","typescript":"^6.0.3","@playwright/test":"^1.59.1"},"peerDependencies":{"typescript":">=5.0"},"peerDependenciesMeta":{"typescript":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/cross-tab-worker_0.1.6_1777474520804_0.18058303173506718","host":"s3://npm-registry-packages-npm-production"}},"0.1.7":{"name":"@arnoldgraf/cross-tab-worker","version":"0.1.7","description":"Drop-in coordination wrapper that keeps exactly one Worker alive across all same-origin tabs.","repository":{"type":"git","url":"git+https://github.com/arnold-graf/cross-tab-worker.git"},"type":"module","main":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"generate:broker-source":"node scripts/generate-broker-source.mjs","build":"npm run generate:broker-source && tsc -p tsconfig.build.json","prepublishOnly":"npm run build","test":"vitest run tests","test:watch":"vitest tests","typecheck":"tsc --noEmit","test:e2e":"npm run build && playwright test --project=chromium","test:e2e:webkit":"npm run build && playwright test --project=webkit","test:e2e:webkit:headed":"npm run build && playwright test --project=webkit --ui"},"peerDependencies":{"typescript":">=5.0"},"peerDependenciesMeta":{"typescript":{"optional":true}},"engines":{"node":">=22.12.0"},"devDependencies":{"@playwright/test":"^1.59.1","typescript":"^6.0.3","vite":"^8.0.10","vitest":"^4.1.5"},"_id":"@arnoldgraf/cross-tab-worker@0.1.7","gitHead":"ecd8109634523a2f5df992e57bb9afcd90e26151","types":"./dist/index.d.ts","bugs":{"url":"https://github.com/arnold-graf/cross-tab-worker/issues"},"homepage":"https://github.com/arnold-graf/cross-tab-worker#readme","_nodeVersion":"22.12.0","_npmVersion":"10.9.0","dist":{"integrity":"sha512-E5lMD5XpvSLBVH7PavSNNYc3hX5Z9rFjxwsxzopvLPpGSSImR54e8c7QZqGobUdKn+XXHPk30ubJFJMvo5d52w==","shasum":"b4ccbe9c96bae07d31131bdb6f7658fbe01fc7e2","tarball":"https://registry.npmjs.org/@arnoldgraf/cross-tab-worker/-/cross-tab-worker-0.1.7.tgz","fileCount":36,"unpackedSize":88200,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arnoldgraf%2fcross-tab-worker@0.1.7","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCotSkDvzGQpZ+A9hIo2q40IUchS2A5lgCaDEZUImibXgIgfH1YiFn2YKTJrPjCWY54IvwESLCABvm6ttQnwEiqLIg="}]},"_npmUser":{"name":"arnoldgraf","email":"arnold.graf@gmail.com"},"directories":{},"maintainers":[{"name":"arnoldgraf","email":"arnold.graf@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cross-tab-worker_0.1.7_1777475998001_0.44649508933903714"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-28T13:27:59.912Z","modified":"2026-04-29T15:19:58.634Z","0.1.0":"2026-04-28T13:28:00.136Z","0.1.1":"2026-04-28T13:43:43.027Z","0.1.2":"2026-04-28T13:54:38.935Z","0.1.3":"2026-04-28T13:59:21.446Z","0.1.4":"2026-04-29T13:18:49.365Z","0.1.5":"2026-04-29T13:28:57.916Z","0.1.6":"2026-04-29T14:55:20.945Z","0.1.7":"2026-04-29T15:19:58.160Z"},"bugs":{"url":"https://github.com/arnold-graf/cross-tab-worker/issues"},"homepage":"https://github.com/arnold-graf/cross-tab-worker#readme","repository":{"type":"git","url":"git+https://github.com/arnold-graf/cross-tab-worker.git"},"description":"Drop-in coordination wrapper that keeps exactly one Worker alive across all same-origin tabs.","maintainers":[{"name":"arnoldgraf","email":"arnold.graf@gmail.com"}],"readme":"# cross-tab-worker\n\nA drop-in coordination wrapper that keeps exactly one Worker alive across all same-origin browser tabs. Useful for OPFS access from Workers when using multiple tabs.\n\nExactly one tab owns the real Worker at a time (the **leader**). All other tabs (**followers**) send messages through a direct `MessagePort` to the leader, which forwards them to the worker. When the leader tab closes, one follower is automatically elected as the new leader. The application sees the same `postMessage` / `onmessage` interface regardless of role.\n\n---\n\n## Why not SharedWorker?\n\n`SharedWorker` doesn't support `FileSystemSyncAccessHandle` — the synchronous OPFS API that high-performance SQLite/WASM VFS implementations require. This library achieves the same multi-tab sharing using a regular dedicated Worker owned by one tab, with coordination through Web Locks and a tiny broker SharedWorker.\n\n---\n\n## Usage\n\n```ts\nimport { CrossTabWorker } from '@arnoldgraf/cross-tab-worker';\n\nconst worker = new CrossTabWorker(\n  'my-db-worker',                              // stable name — used as the lock key\n  () => new Worker(new URL('./db.worker.ts', import.meta.url), { type: 'module' }),\n);\n\nworker.onmessage = (e) => console.log('from worker:', e.data);\n\n// Zero-copy if this tab is the leader; relayed through a direct MessagePort if follower.\nconst buffer = new ArrayBuffer(4096);\nworker.postMessage({ op: 'write', buf: buffer }, [buffer]);\n```\n\n---\n\n## API\n\n```ts\nnew CrossTabWorker(name, factory)\n```\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `name` | `string` | Unique identifier for this worker type. Used as the Web Lock key and SharedWorker name. Must be stable across page loads. |\n| `factory` | `() => Worker` | Called only in the leader tab to instantiate the real Worker. |\n\n### Instance\n\n| Member | Description |\n| --- | --- |\n| `postMessage(message, transfer?)` | Send a message to the worker. Zero-copy in the leader; transferred via a direct `MessagePort` in followers. |\n| `onmessage` | Callback for messages from the worker. |\n| `onmessageerror` | Callback for deserialization errors. |\n| `addEventListener(type, handler)` | `'message'` or `'messageerror'`. |\n| `removeEventListener(type, handler)` | |\n| `destroy()` | Leader: terminates the underlying Worker and releases the lock. Follower: closes ports and cancels the queued lock request. Safe to call in either role. |\n| `isLeader` | `boolean` getter — true if this tab currently owns the Worker. |\n\n---\n\n## Architecture\n\n```text\nFollower tab (B)                               Leader tab (A)\n┌─────────────────────────────┐                 ┌────────────────────────────┐\n│ CrossTabWorker (follower)   │                 │ CrossTabWorker (leader)    │\n│  app.postMessage(msg, xfer) │                 │  ┌──────────────────────┐  │\n│      │                      │                 │  │ Dedicated Worker     │  │\n│      ▼                      │ direct channel  │  │ (real worker owner)  │  │\n│  MessagePort (port1) ───────┼────────────────►│  └──────────────────────┘  │\n└─────────────────────────────┘  relay msg/xfer │            ▲               │\n                                                │            │ worker events │\n                                                └────────────┼───────────────┘\n                                                             │\n                                                             ▼\n                           ┌─────────────────────────────────────────────────────┐\n                           │ port-broker SharedWorker                            │\n                           │ - tab registry                                      │\n                           │ - current leader tracking                           │\n                           │ - forwards handshake ports (port2)                  │\n                           │ - fans out coordination messages                    │\n                           │   (`leader-ready`, `worker-msg`, `worker-msg-error`)│\n                           └─────────────────────────────────────────────────────┘\n```\n\n### Data path (zero-copy)\n\nWhen a follower calls `postMessage(msg, [transfer])`:\n\n1. The message and its transfer list travel through the follower's direct `MessagePort` to the leader.\n2. Transferable objects (`ArrayBuffer`, etc.) are **transferred** (ownership moves) — no copy.\n3. The leader forwards them to the Worker with `worker.postMessage(msg, transfer)` — no copy again.\n\nTwo ownership transfers, zero copies.\n\n### Directed zero-copy responses\n\nFor bulk response data (e.g. query results), the worker can reply zero-copy to the specific tab that sent the request using the reply port attached to every relayed message:\n\n```ts\n// Inside the worker\nself.onmessage = (e) => {\n  const replyPort = e.ports[0]; // present when the message came from a follower tab\n  const result = new ArrayBuffer(1024 * 1024);\n  // ... fill result ...\n\n  if (replyPort) {\n    // Zero-copy: result is transferred directly to the requesting tab.\n    replyPort.postMessage({ payload: { result }, transfer: [result] }, [result]);\n  } else {\n    // Broadcast fallback for leader-local callers (no relay port).\n    self.postMessage({ result });\n  }\n};\n```\n\nThe library unwraps the `{ payload, transfer }` envelope and delivers `payload` directly to the follower's `onmessage` — no structured clone, no broadcast to other tabs.\n\nWhen a message originates from the **leader tab itself**, `e.ports[0]` is absent (the leader posts directly to the worker without a relay). The `self.postMessage(result)` fallback handles that case and broadcasts to all tabs via the broker fan-out path.\n\n### Port handshake\n\nOn startup (or after failover), each follower:\n\n1. Creates a `MessageChannel`, keeps `port1` for sending.\n2. Sends `port2` to the broker, which forwards it to the leader tab.\n3. The leader holds `port2` and listens for relay messages on it.\n\nAll subsequent data flows directly through the `MessageChannel` — the broker is not involved after the handshake.\n\n### No BroadcastChannel, no heartbeat\n\nLeader death detection is handled entirely by the Web Locks API — when a leader tab closes or calls `destroy()`, the browser automatically releases the lock and wakes up the next follower. No periodic heartbeat is needed.\n\nAll other coordination — `leader-ready` and `worker-msg` — travels through the broker SharedWorker, which fans messages out to all registered tab ports. No `BroadcastChannel` is used anywhere.\n\n### Late-joining tabs\n\nA tab that opens after the leader is established receives a `leader-info` message from the broker immediately on registration, so it can connect to the leader right away.\n\n### Failover sequence\n\n1. Leader tab closes → browser releases the Web Lock automatically.\n2. Every follower is already blocking on `navigator.locks.request` — exactly one wakes up and wins.\n3. Winner calls `factory()`, broadcasts `leader-ready`, drains its outbound buffer.\n4. Other followers receive `leader-ready` and re-establish direct ports to the new leader.\n5. Messages in-flight when the old leader closed are lost. Applications that require exactly-once delivery must implement their own sequence numbers.\n\n---\n\n## Requirements\n\n| API | Required |\n| --- | --- |\n| Web Locks (`navigator.locks`) | ✅ |\n| SharedWorker | ✅ |\n| MessageChannel / MessagePort | ✅ |\n\nThrows a clear error on construction if either Web Locks or SharedWorker is unavailable.\n\n---\n\n## Response delivery modes\n\n| Scenario | Path | Copy? |\n| --- | --- | --- |\n| Worker → leader tab | Direct `onmessage` | Zero-copy (no transfer needed) |\n| Worker → all tabs (broadcast) | Broker fan-out via `worker-msg` | Structured clone |\n| Worker → specific follower (directed reply) | `e.ports[0]` reply port | Zero-copy |\n\nUse the directed reply pattern for bulk response payloads. Use `self.postMessage(data)` (broadcast) for notifications or results that every tab needs to receive.\n","readmeFilename":"README.md"}