{"_id":"@1moby/react-sw-updater","name":"@1moby/react-sw-updater","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@1moby/react-sw-updater","publishConfig":{"access":"public"},"version":"1.0.0","description":"Lightweight React library for detecting app updates via version polling — with cache-busting, offline awareness, chunk error recovery, and a drop-in update banner.","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./vite":{"import":{"types":"./dist/vite.d.ts","default":"./dist/vite.js"},"require":{"types":"./dist/vite.d.cts","default":"./dist/vite.cjs"}},"./nextjs":{"import":{"types":"./dist/nextjs.d.ts","default":"./dist/nextjs.js"},"require":{"types":"./dist/nextjs.d.cts","default":"./dist/nextjs.cjs"}}},"sideEffects":false,"scripts":{"build":"tsup","prepare":"tsup","prepublishOnly":"npm run typecheck && npm run test && tsup","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"engines":{"node":">=20.0.0"},"keywords":["react","service-worker","pwa","update","version-check","cache-busting","chunk-error","hooks","update-banner","hot-reload"],"author":{"name":"1moby"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/1moby/react-sw-updater.git"},"homepage":"https://github.com/1moby/react-sw-updater#readme","bugs":{"url":"https://github.com/1moby/react-sw-updater/issues"},"devDependencies":{"@testing-library/jest-dom":"^6.9.1","@testing-library/react":"^16.3.2","@types/node":"^25.3.3","@types/react":"^19.2.14","@types/react-dom":"^19.2.3","jsdom":"^28.1.0","tsup":"^8.5.1","typescript":"^5.9.3","vitest":"^4.0.18"},"_id":"@1moby/react-sw-updater@1.0.0","gitHead":"4f7c384e10ab9e5fe750d29a324faaff1ac8dd27","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-CoZC3zwR3oNAsaO9IdOpGRrbobHnS5oLqHSL4n7BoxQinsQmbLRs9eAJau0s8bQ1V9BogW07mKKibl/uQIB0Kg==","shasum":"122af221ad22ad49660d8293fe5ccf8fb23080bd","tarball":"https://registry.npmjs.org/@1moby/react-sw-updater/-/react-sw-updater-1.0.0.tgz","fileCount":24,"unpackedSize":110931,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHPmetdu+pyGgEcTdWRZ9WciesUnuWIghCRrLDzTp1DeAiEA+edHQhCxM85WcsHG8p34O6a7P4OgzUxLw6iF5XQfK4Y="}]},"_npmUser":{"name":"mynameisanu","email":"anu@anusoft.biz"},"directories":{},"maintainers":[{"name":"mynameisanu","email":"anu@anusoft.biz"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/react-sw-updater_1.0.0_1776405494384_0.9065627625513881"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-17T05:58:14.273Z","1.0.0":"2026-04-17T05:58:14.508Z","modified":"2026-04-17T05:58:14.738Z"},"maintainers":[{"name":"mynameisanu","email":"anu@anusoft.biz"}],"description":"Lightweight React library for detecting app updates via version polling — with cache-busting, offline awareness, chunk error recovery, and a drop-in update banner.","homepage":"https://github.com/1moby/react-sw-updater#readme","keywords":["react","service-worker","pwa","update","version-check","cache-busting","chunk-error","hooks","update-banner","hot-reload"],"repository":{"type":"git","url":"git+https://github.com/1moby/react-sw-updater.git"},"author":{"name":"1moby"},"bugs":{"url":"https://github.com/1moby/react-sw-updater/issues"},"license":"MIT","readme":"# @1moby/react-sw-updater\n\nLightweight React library for detecting app updates via version polling — with cache-busting, offline awareness, chunk error recovery, and a drop-in update banner.\n\n**Zero runtime dependencies. ~3KB gzipped.**\n\n## How It Works\n\n1. Your build tool generates a `version.json` with a unique build version\n2. The library polls `version.json` with aggressive cache-busting (timestamp param + `no-store` + `no-cache` headers)\n3. When the server version differs from the bundled version, an update prompt appears\n4. On accept: unregisters service workers, clears all caches, hard-reloads with a CDN-busting param\n\n## Install\n\n```bash\nnpm install @1moby/react-sw-updater\n```\n\n## Quick Start\n\n### 1. Add the Vite plugin\n\n```typescript\n// vite.config.ts\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport { updateChecker } from '@1moby/react-sw-updater/vite';\n\nexport default defineConfig({\n  plugins: [react(), updateChecker()],\n});\n```\n\nThe plugin:\n- Generates a deterministic `BUILD_VERSION` (git hash + timestamp)\n- Defines `__BUILD_VERSION__` as a global constant in your bundle\n- Writes `version.json` to your output directory on build\n- Deduplicates React (prevents dual-instance issues)\n\n### 2. Add the provider to your app\n\n```tsx\n// App.tsx\nimport { UpdateProvider, useUpdateContext, UpdateBanner } from '@1moby/react-sw-updater';\n\ndeclare const __BUILD_VERSION__: string;\n\nfunction AppUpdateBanner() {\n  const { updateAvailable, applyUpdate, dismiss } = useUpdateContext();\n  if (!updateAvailable) return null;\n\n  return (\n    <UpdateBanner\n      onAccept={applyUpdate}\n      onDismiss={dismiss}\n    />\n  );\n}\n\nexport default function App() {\n  return (\n    <UpdateProvider currentVersion={__BUILD_VERSION__}>\n      <AppUpdateBanner />\n      {/* your app */}\n    </UpdateProvider>\n  );\n}\n```\n\nThat's it. The library handles polling, cache-busting, offline detection, and reload-loop prevention automatically.\n\n## Next.js Setup\n\n```javascript\n// next.config.js\nconst { withUpdateChecker } = require('@1moby/react-sw-updater/nextjs');\n\nmodule.exports = withUpdateChecker({\n  // your Next.js config\n});\n```\n\nThen use `process.env.NEXT_PUBLIC_BUILD_VERSION` as your `currentVersion`.\n\n## API\n\n### `<UpdateProvider>`\n\nContext provider that handles version polling and update detection.\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `currentVersion` | `string` | *required* | Build-time version baked into the bundle |\n| `versionUrl` | `string` | `'/version.json'` | URL to the version endpoint |\n| `checkInterval` | `number` | `300000` (5 min) | Polling interval in ms |\n| `swUrl` | `string` | — | Optional SW to register for offline caching |\n\n### `useUpdateContext()`\n\nReturns the update state from the nearest `<UpdateProvider>`.\n\n```typescript\ninterface UpdateCheckerResult {\n  updateAvailable: boolean;      // true when server version differs\n  serverVersion: string | null;  // the server's version string\n  applyUpdate: () => void;       // clear caches + hard reload\n  dismiss: () => void;           // hide banner until next page load\n}\n```\n\n### `useUpdateChecker(options)`\n\nStandalone hook (no provider needed) with the same options and return type.\n\n### `<UpdateBanner>`\n\nPre-styled, accessible banner component.\n\n| Prop | Type | Default |\n|------|------|---------|\n| `message` | `string` | `'A new version is available.'` |\n| `acceptLabel` | `string` | `'Update'` |\n| `dismissLabel` | `string` | `'Later'` |\n| `onAccept` | `() => void` | *required* |\n| `onDismiss` | `() => void` | — |\n| `className` | `string` | — |\n| `style` | `CSSProperties` | — |\n\n### Utilities\n\n```typescript\nimport { isChunkLoadError, retryDynamicImport } from '@1moby/react-sw-updater';\n\n// Detect chunk load errors (code-split lazy imports failing after deploy)\nisChunkLoadError(error); // boolean\n\n// Wrap lazy imports with auto-retry on chunk errors\nconst MyPage = lazy(() => retryDynamicImport(() => import('./MyPage')));\n\n// Per-tab state persistence (survives reload, isolated per tab)\nimport { persistState, restoreState } from '@1moby/react-sw-updater';\npersistState('form-data', { name: 'John' });\nconst data = restoreState('form-data'); // null if not found (auto-cleans)\n```\n\n## Reliability Features\n\n- **Fetch timeout** — 10s AbortController timeout prevents hanging on slow networks\n- **Concurrent check dedup** — visibility-change + timer firing simultaneously won't cause duplicate fetches\n- **Offline awareness** — skips polling when `navigator.onLine` is false, checks immediately when connectivity returns\n- **Reload-loop guard** — `applyUpdate()` blocks rapid successive reloads (30s cooldown) in case CDN hasn't purged yet\n- **Per-route chunk retry** — `retryDynamicImport` uses per-pathname keys so a chunk error on `/settings` doesn't block retry on `/dashboard`\n- **Recursive setTimeout** — never uses `setInterval`, preventing call stacking on slow networks\n\n## How `applyUpdate()` Works\n\nWhen the user accepts the update:\n\n1. Unregisters all service workers\n2. Clears all Cache API caches\n3. Navigates with a `_uc=<timestamp>` cache-bust param (cleaned up on next load)\n\nThis \"nuclear\" approach guarantees the browser loads fresh assets regardless of CDN, SW, or HTTP cache state.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-353ffa69278033f0195589d510552da6"}