diff --git a/.github/workflows/dev_on_dispatch_service_worker_test.yml b/.github/workflows/dev_on_dispatch_service_worker_test.yml deleted file mode 100644 index fdc531bc550..00000000000 --- a/.github/workflows/dev_on_dispatch_service_worker_test.yml +++ /dev/null @@ -1,130 +0,0 @@ -name: '1 [on_dispatch] Service Worker Test' - -on: - workflow_dispatch: - inputs: - versionFrom: - description: "Nom de l'ancienne release (ex: 1.199.0)" - required: true - type: string - versionTo: - description: 'Nom de la nouvelle release (ex: 1.200.0)' - required: true - type: string - repository_dispatch: - types: service-worker-test - -jobs: - testServiceWorker: - name: Test service worker - runs-on: ubuntu-22.04 - env: - PORT: 5000 - container: - image: ghcr.io/pass-culture/puppeteer:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - options: --user root - steps: - # Docker entrypoint is otherwise ignored in GitHub Action - - name: Docker entrypoint - shell: bash - run: /entrypoint.sh - - # Useful to get the latest service worker test script - - name: Checkout registry - uses: actions/checkout@v4 - - # To initialize the latest node version - - name: Setup Node.JS - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - - # Necessary for the latest service worker test script - - name: Install dependencies - shell: bash - run: | - yarn install --immutable - yarn add puppeteer - - # Clone the version from in its own directory - - name: Checkout ${{ github.event.inputs.versionFrom }}${{ github.event.client_payload.versionFrom }} - uses: actions/checkout@v4 - with: - ref: ${{ github.event.inputs.versionFrom }}${{ github.event.client_payload.versionFrom }} - path: versionFrom - - # Build version from in versionFrom/build - - name: Building Webapp ${{ github.event.inputs.versionFrom }}${{ github.event.client_payload.versionFrom }} - shell: bash - run: | - yarn install --cwd versionFrom --immutable - NODE_OPTIONS='--openssl-legacy-provider --max-old-space-size=4096' yarn --cwd versionFrom build:testing - - # Clone the version to in its own directory - - name: Checkout ${{ github.event.inputs.versionTo }}${{ github.event.client_payload.versionTo }} - uses: actions/checkout@v4 - with: - ref: ${{ github.event.inputs.versionTo }}${{ github.event.client_payload.versionTo }} - path: versionTo - - # Build version to in versionTo/build - ## Important: This build will be used during the last step by the JS script - ## There is an alternative way to improve this, described here: https://github.com/pass-culture/pass-culture-app-native/pull/3452 - - name: Building Webapp ${{ github.event.inputs.versionTo }}${{ github.event.client_payload.versionTo }} - shell: bash - run: | - yarn install --cwd versionTo --immutable - NODE_OPTIONS='--openssl-legacy-provider --max-old-space-size=4096' yarn --cwd versionTo build:testing - - # Create build directory for serve - - name: Create a build directory to be served - run: mkdir build - - # Run HTTP server - - name: Serve Files - uses: Eun/http-server-action@v1 - with: - directory: build - port: ${{ env.PORT }} - no-cache: false - content-types: | - { - "appcache": "text/cache-manifest", - "css": "text/css", - "gif": "image/gif", - "html": "text/html", - "ico": "image/x-icon", - "jpeg": "image/jpeg", - "jpg": "image/jpeg", - "js": "text/javascript", - "json": "application/json", - "png": "image/png", - "txt": "text/plain", - "xml": "text/xml" - } - - # Copy versionFrom to build directory - - name: Copy versionFrom to server directory - run: yes | cp -rf versionFrom/build/* build/ - - # Run puppeteer - - run: yarn test:sw ${{ github.event.inputs.versionFrom }}${{ github.event.client_payload.versionFrom }} ${{ github.event.inputs.versionTo }}${{ github.event.client_payload.versionTo }} - - # Send failure otherwise success message to slack - - name: Send failure message to slack - shell: bash - if: failure() - env: - SLACK_WEBHOOK_SERVICE_WORKER_TEST: ${{ secrets.SLACK_WEBHOOK_SERVICE_WORKER_TEST }} - run: | - curl -X POST -H 'Content-type: application/json' --data '{"text":"[App Web] Test service-worker failed to upgrade from ${{ github.event.inputs.versionFrom }}${{ github.event.client_payload.versionFrom }} to ${{ github.event.inputs.versionTo }}${{ github.event.client_payload.versionTo }} :collision:"}' "$SLACK_WEBHOOK_SERVICE_WORKER_TEST" - - name: Send success message to slack - shell: bash - if: success() - env: - SLACK_WEBHOOK_SERVICE_WORKER_TEST: ${{ secrets.SLACK_WEBHOOK_SERVICE_WORKER_TEST }} - run: | - curl -X POST -H 'Content-type: application/json' --data '{"text":"[App Web] Test service-worker succeeded to upgrade from ${{ github.event.inputs.versionFrom }}${{ github.event.client_payload.versionFrom }} to ${{ github.event.inputs.versionTo }}${{ github.event.client_payload.versionTo }} :rocket:️️"}' "$SLACK_WEBHOOK_SERVICE_WORKER_TEST" diff --git a/doc/development/general-info-web.md b/doc/development/general-info-web.md index ec9f93ac464..48e6411a702 100644 --- a/doc/development/general-info-web.md +++ b/doc/development/general-info-web.md @@ -127,7 +127,6 @@ There are certain number of optimizations/improvements and things that were done MUST HAVE: -- Remove service workers (caused cache update issues) and manifest if we decide to not use PWA (because it requires maintenance). - Test older browsers - If needed, increase compatibility with `@vitejs/plugin-legacy` (you will need to install `terser` for the plugin to work) @@ -142,7 +141,6 @@ COULD HAVE: - Using vite's chunking technology (or any other chunking technology) to reduce the initial loading time of the web app - Chunk protection (check there isn't any bundle issues and protect against future issues) - Depending on the compatibility with older browsers, update `src/web/SupportedBrowsersGate.web.test.tsx` and `package.json.browserList` -- PWA (it seems that vite generates a manifest but we should make sure the service worker is functioning) COULD BE NICE TO HAVE: diff --git a/package.json b/package.json index c8180a79f80..fd9ef3fdc65 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,6 @@ "test:lint": "eslint . --ext .js,.ts,.tsx,.mjs --cache", "test:perf": "NODE_ENV=performance NODE_OPTIONS='--no-experimental-fetch' TEST_RUNNER_ARGS='--runInBand --testMatch \"/**/*.perf.test.tsx\"' yarn reassure", "test:perf:update": "yarn test:perf --baseline", - "test:sw": "node scripts/service-worker-test.mjs", "test:storybook:accessibility": "TZ=UTC axe-storybook --storybook-address http://localhost:6006", "test:types": "tsc", "test:types:noUncheckedIndexedAccess": "grep --recursive --line-number ./src --regexp '// @ts-expect-error: because of noUncheckedIndexedAccess' | awk -F: '{print $1\":\"$2}' | sort", @@ -349,19 +348,7 @@ "typescript": "5.0.4", "url-loader": "^4.1.1", "vite": "^5.3.1", - "vite-plugin-html": "^3.2.2", - "workbox-background-sync": "^6.1.5", - "workbox-broadcast-update": "^6.1.5", - "workbox-cacheable-response": "^6.1.5", - "workbox-core": "^6.1.5", - "workbox-expiration": "^6.1.5", - "workbox-google-analytics": "^6.1.5", - "workbox-navigation-preload": "^7.1.0", - "workbox-precaching": "^6.1.5", - "workbox-range-requests": "^6.1.5", - "workbox-routing": "^6.1.5", - "workbox-strategies": "^6.1.5", - "workbox-streams": "^6.1.5" + "vite-plugin-html": "^3.2.2" }, "resolutions": { "@sentry/cli": "^2.33.1", diff --git a/scripts/service-worker-test.mjs b/scripts/service-worker-test.mjs deleted file mode 100644 index 311e0548ab2..00000000000 --- a/scripts/service-worker-test.mjs +++ /dev/null @@ -1,127 +0,0 @@ -/* eslint-disable no-console */ -import { cpSync } from 'fs' -import { join } from 'path' - -// We do not want to install puppeteer on the whole project -// as the JS dependencies caused build issues after building with the CI -// https://github.com/pass-culture/pass-culture-app-native/pull/3528 -// eslint-disable-next-line import/no-unresolved -import puppeteer from 'puppeteer' - -const versionFrom = process.argv[2] -const versionTo = process.argv[3] - -const PORT = Number(process.env.PORT) || 3000 -const APP_PUBLIC_URL = `http://localhost:${PORT}/index.html` - -function evaluateMetaContentVersion() { - return document.querySelector('meta[name="version"]').getAttribute('content') -} - -async function waitFor(ms) { - return new Promise((r) => setTimeout(r, ms)) -} - -function waitForServiceWorkers(page) { - return page.evaluate(() => - navigator.serviceWorker.getRegistrations().then((registrations) => { - return Promise.all( - registrations.map( - (reg) => - new Promise((resolve) => { - if (reg.active) { - resolve() - } else { - reg.onupdatefound = () => { - setTimeout(resolve, 10) - } - } - }) - ) - ) - }) - ) -} - -;(async () => { - let browser - let success = false - try { - if (!versionFrom || !versionTo) { - throw new Error( - `You must set two versions to run the test, ex: yarn test:sw v1.200.3 v1.201.1` - ) - } - console.log('Running service-worker test:', { - versionFrom, - versionTo, - }) - - console.log(`Launching puppeteer at ${APP_PUBLIC_URL}`) - browser = await puppeteer.launch({ - args: ['--disable-setuid-sandbox', '--no-sandbox', '--enable-features=NetworkService'], - executablePath: 'google-chrome-stable', - headless: false, - ignoreHTTPSErrors: true, - env: { - DISPLAY: ':99.0', - }, - }) - const page = await browser.newPage() - - await page.goto(APP_PUBLIC_URL, { waitUntil: 'networkidle0', timeout: 0 }) - await waitForServiceWorkers(page) - - let version = await page.evaluate(evaluateMetaContentVersion) - console.log(`meta version is v${String(version)} while it should be ${versionFrom}`) - - if (!versionFrom.includes(version)) { - throw new Error( - `Version v${String(version)} differ from build version ${versionFrom}, aborting...` - ) - } - - cpSync(join(process.cwd(), 'versionTo/build'), join(process.cwd(), 'build'), { - recursive: true, - }) - - console.log('It will now reload the page') - await page.reload({ waitUntil: 'networkidle0', timeout: 0 }) - - await waitForServiceWorkers(page) - await page.evaluate(() => { - const btn = document.querySelector('button[type="button"]') - btn?.click() - }) - - await waitFor(15000) - version = await page.evaluate(evaluateMetaContentVersion) - console.log(`meta version is v${String(version)} while it should be ${versionTo}`) - - if (!versionTo.includes(version)) { - throw new Error( - `Version v${String(version)} differ from build version ${versionTo}, aborting...` - ) - } - - success = true - } catch (error) { - console.error(error) - } finally { - try { - await browser.close() - console.log('Closed puppeteer ✅') - } catch (err) { - console.log('Failed to close puppeteer ❌') - console.error(err) - } - - if (success) { - console.log('Service worker update test ✅') - process.exit(0) - } else { - console.log('Service worker update test ❌') - process.exit(1) - } - } -})() diff --git a/src/App.web.tsx b/src/App.web.tsx index ccb9684df82..4a6cc9547cf 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -34,7 +34,6 @@ import { theme } from 'theme' import { LoadingPage } from 'ui/components/LoadingPage' import { SnackBarProvider } from 'ui/components/snackBar/SnackBarContext' import { SupportedBrowsersGate } from 'web/SupportedBrowsersGate' -import { ServiceWorkerProvider } from 'web/useServiceWorker' import 'reset-css' globalThisShim() @@ -48,50 +47,57 @@ export function App() { initAlgoliaAnalytics() }, []) + // Unregister service workers (to make sure all sw cache is removed) + useEffect(() => { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.getRegistrations().then((registrations) => { + registrations.forEach((registration) => registration.unregister()) + }) + } + }, []) + return ( - - - - - - - - - - - - - - - - - - - - - }> - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + }> + + + + + + + + + + + + + + + + + + + + ) } diff --git a/src/service-worker.ts b/src/service-worker.ts deleted file mode 100644 index e590de667b2..00000000000 --- a/src/service-worker.ts +++ /dev/null @@ -1,83 +0,0 @@ -/// -/* eslint-disable no-restricted-globals */ - -// This service worker can be customized! -// See https://developers.google.com/web/tools/workbox/modules -// for the list of available Workbox modules, or add any other -// code you'd like. -// You can also remove this file if you'd prefer not to use a -// service worker, and the Workbox build step will be skipped. - -import { clientsClaim } from 'workbox-core' -import { ExpirationPlugin } from 'workbox-expiration' -import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching' -import { registerRoute } from 'workbox-routing' -import { StaleWhileRevalidate } from 'workbox-strategies' - -declare const self: ServiceWorkerGlobalScope - -clientsClaim() - -// Precache all of the assets generated by your build process. -// Their URLs are injected into the manifest variable below. -// This variable must be present somewhere in your service worker file, -// even if you decide not to use precaching. See https://cra.link/PWA -precacheAndRoute(self.__WB_MANIFEST) - -// Set up App Shell-style routing, so that all navigation requests -// are fulfilled with your index.html shell. Learn more at -// https://developers.google.com/web/fundamentals/architecture/app-shell -const fileExtensionRegexp = new RegExp(/[^/?]+\\.\w{1,5}$/) -registerRoute( - // Return false to exempt requests from being fulfilled by index.html. - ({ request, url }: { request: Request; url: URL }) => { - // If this isn't a navigation, skip. - if (request.mode !== 'navigate') { - return false - } - - // If this is a URL that starts with /_, skip. - if (url.pathname.startsWith('/_')) { - return false - } - - // If this looks like a URL for a resource, because it contains - // a file extension, skip. - if (url.pathname.match(fileExtensionRegexp)) { - return false - } - - // Return true to signal that we want to use the handler. - return true - }, - createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') -) - -// An example runtime caching route for requests that aren't handled by the -// precache, in this case same-origin .png requests like those from in public/ -registerRoute( - // Add in any other file extensions or routing criteria as needed. - ({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), - // Customize this strategy as needed, e.g., by changing to CacheFirst. - new StaleWhileRevalidate({ - cacheName: 'images', - plugins: [ - // Ensure that once this runtime cache reaches a maximum size the - // least-recently used images are removed. - new ExpirationPlugin({ maxEntries: 50 }), - ], - }) -) - -// This allows the web app to trigger skipWaiting via -// registration.waiting.postMessage({type: 'SKIP_WAITING'}) -self.addEventListener('message', (event) => { - if (event.data && event.data.type === 'SKIP_WAITING') { - self.skipWaiting() - } -}) - -// This allow the web app to trigger skipWaiting when a new SW version is available -self.addEventListener('install', () => { - self.skipWaiting() -}) diff --git a/src/web/serviceWorkerRegistration.ts b/src/web/serviceWorkerRegistration.ts deleted file mode 100644 index 951cdbbb3ad..00000000000 --- a/src/web/serviceWorkerRegistration.ts +++ /dev/null @@ -1,165 +0,0 @@ -// This optional code is used to register a service worker. -// register() is not called by default. - -// This lets the app load faster on subsequent visits in production, and gives -// it offline capabilities. However, it also means that developers (and users) -// will only see deployed updates on subsequent visits to a page, after all the -// existing tabs open on the page have been closed, since previously cached -// resources are updated in the background. - -// To learn more about the benefits of this model and instructions on how to -// opt-in, read https://cra.link/PWA - -const isLocalhost = Boolean( - window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.0/8 are considered localhost for IPv4. - window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/) -) - -type Config = { - registrationOptions?: RegistrationOptions - ready?: (registration: ServiceWorkerRegistration) => void - registered?: (registration: ServiceWorkerRegistration) => void - cached?: (registration: ServiceWorkerRegistration) => void - updatefound?: (registration: ServiceWorkerRegistration) => void - updated?: (registration: ServiceWorkerRegistration) => void - offline?: () => void - error?: (error: Error) => void -} - -type Emit = (emitKey: EmitKey, registration?: Error | ServiceWorkerRegistration) => void - -type EmitKey = 'ready' | 'registered' | 'cached' | 'updatefound' | 'updated' | 'offline' | 'error' - -export function register(swUrl: string, config: Config = {}) { - const { registrationOptions = {} } = config - delete config.registrationOptions - - const emit = (emitKey: EmitKey, payload?: Error | ServiceWorkerRegistration): void => { - const callback = config[emitKey] - if (callback) { - callback(payload as never) - } - } - - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { - // The URL constructor is available in all browsers that support SW. - // @ts-ignore process.env.PUBLIC_URL is always defined in this context - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href) - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return - } - - window.addEventListener('load', () => { - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, emit, registrationOptions) - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, emit, registrationOptions) - } - navigator.serviceWorker.ready - .then((registration) => { - emit('ready', registration) - }) - .catch((error) => handleError(emit, error)) - }) - } -} - -function handleError(emit: Emit, error: Error) { - if (!navigator.onLine) { - emit('offline') - } - emit('error', error) -} - -function registerValidSW( - swUrl: string | URL, - emit: Emit, - registrationOptions: RegistrationOptions -) { - navigator.serviceWorker - .register(swUrl, registrationOptions) - .then((registration) => { - emit('registered', registration) - if (registration.waiting) { - emit('updated', registration) - return - } - registration.onupdatefound = () => { - emit('updatefound', registration) - const installingWorker = registration.installing - if (installingWorker == null) { - return - } - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the old content will have been purged and - // the fresh content will have been added to the cache. - // It's the perfect time to display a "New content is - // available; please refresh." message in your web app. - emit('updated', registration) - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - emit('cached', registration) - } - } - } - } - }) - .catch((error) => handleError(emit, error)) -} - -function checkValidServiceWorker( - swUrl: string, - emit: Emit, - registrationOptions: RegistrationOptions -) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl, { - headers: { 'Service-Worker': 'script' }, - }) - .then((response) => { - const contentType = response.headers.get('content-type') - - // Ensure service worker exists, and that we really are getting a JS file. - if (response.status === 404) { - // No service worker found. - emit('error', new Error(`Service worker not found at ${swUrl}`)) - unregister(emit) - } else if (contentType != null && !contentType.includes('javascript')) { - emit( - 'error', - new Error( - `Expected ${swUrl} to have javascript content-type, but received ${contentType}` - ) - ) - unregister(emit) - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, emit, registrationOptions) - } - }) - .catch((error) => handleError(emit, error)) -} - -export function unregister(emit?: Emit) { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready - .then((registration) => { - registration.unregister() - }) - .catch((error) => { - emit && handleError(emit, error) - }) - } -} diff --git a/src/web/useServiceWorker.tsx b/src/web/useServiceWorker.tsx deleted file mode 100644 index 7d3d1c087fb..00000000000 --- a/src/web/useServiceWorker.tsx +++ /dev/null @@ -1,214 +0,0 @@ -import React, { useEffect, useReducer, useContext } from 'react' - -import { register, unregister } from './serviceWorkerRegistration' - -const SERVICE_WORKER_READY = 'SERVICE_WORKER_READY' -const SERVICE_WORKER_REGISTERED = 'SERVICE_WORKER_REGISTERED' -const SERVICE_WORKER_CACHED = 'SERVICE_WORKER_CACHED' -const SERVICE_WORKER_UPDATE_FOUND = 'SERVICE_WORKER_UPDATE_FOUND' -const SERVICE_WORKER_OFFLINE = 'SERVICE_WORKER_OFFLINE' -const SERVICE_WORKER_UPDATE_READY = 'SERVICE_WORKER_UPDATE_READY' -const SERVICE_WORKER_ERROR = 'SERVICE_WORKER_ERROR' - -interface ServiceWorkerReady { - type: typeof SERVICE_WORKER_READY - payload: ServiceWorker -} - -interface ServiceWorkerRegistered { - type: typeof SERVICE_WORKER_REGISTERED - payload: ServiceWorker -} - -interface ServiceWorkerCached { - type: typeof SERVICE_WORKER_CACHED - payload: ServiceWorker -} - -interface ServiceWorkerUpdateFound { - type: typeof SERVICE_WORKER_UPDATE_FOUND - payload: ServiceWorker -} - -interface ServiceWorkerOffline { - type: typeof SERVICE_WORKER_OFFLINE - payload: ServiceWorker -} - -interface ServiceWorkerUpdateReady { - type: typeof SERVICE_WORKER_UPDATE_READY - payload: ServiceWorker -} - -interface ServiceWorkerError { - type: typeof SERVICE_WORKER_ERROR - payload: ServiceWorker -} - -type ServiceWorkerActionTypes = - | ServiceWorkerReady - | ServiceWorkerRegistered - | ServiceWorkerCached - | ServiceWorkerUpdateFound - | ServiceWorkerOffline - | ServiceWorkerUpdateReady - | ServiceWorkerError - -type ServiceWorkerStatus = - | 'offline' - | 'registered' - | 'register' - | 'ready' - | 'cached' - | 'updates' - | 'updated' - | 'error' - -interface ServiceWorker { - serviceWorkerStatus: ServiceWorkerStatus - registration?: null | ServiceWorkerRegistration - error?: Error -} - -interface ServiceWorkerState { - serviceWorkerStatus: ServiceWorkerStatus - registration?: null | ServiceWorkerRegistration - error?: Error -} - -const useServiceWorkerReducer = ( - state: ServiceWorkerState, - action: ServiceWorkerActionTypes -): ServiceWorkerState => { - switch (action.type) { - case 'SERVICE_WORKER_READY': - return { - ...state, - serviceWorkerStatus: action.payload.serviceWorkerStatus, - registration: action.payload.registration, - } - case 'SERVICE_WORKER_REGISTERED': - return { - ...state, - serviceWorkerStatus: action.payload.serviceWorkerStatus, - registration: action.payload.registration, - } - case 'SERVICE_WORKER_CACHED': - return { - ...state, - serviceWorkerStatus: action.payload.serviceWorkerStatus, - registration: action.payload.registration, - } - case 'SERVICE_WORKER_UPDATE_FOUND': - return { - ...state, - serviceWorkerStatus: action.payload.serviceWorkerStatus, - registration: action.payload.registration, - } - case 'SERVICE_WORKER_UPDATE_READY': - globalThis.window.pcupdate = true - return { - ...state, - serviceWorkerStatus: action.payload.serviceWorkerStatus, - registration: action.payload.registration, - } - case 'SERVICE_WORKER_OFFLINE': - return { - ...state, - serviceWorkerStatus: action.payload.serviceWorkerStatus, - } - case 'SERVICE_WORKER_ERROR': - return { - ...state, - serviceWorkerStatus: action.payload.serviceWorkerStatus, - } - - default: - return state - } -} - -const initialState: ServiceWorkerState = { - registration: null, - serviceWorkerStatus: 'register', -} -const serviceWorkerContext = React.createContext(initialState) - -export function ServiceWorkerProvider({ - children, - fileName, - registrationOptions, -}: { - children: React.ReactNode - fileName: string - registrationOptions?: RegistrationOptions -}) { - const serviceWorker = useProvideServiceWorker(fileName, registrationOptions) - return ( - {children} - ) -} - -export const useServiceWorker = () => { - return useContext(serviceWorkerContext) -} - -const useProvideServiceWorker = ( - file = 'service-worker.js', - registrationOptions: RegistrationOptions = {} -) => { - const [swState, dispatch] = useReducer(useServiceWorkerReducer, initialState) - useEffect(() => { - register(file, { - registrationOptions, - ready(registration: ServiceWorkerRegistration) { - dispatch({ - type: 'SERVICE_WORKER_READY', - payload: { serviceWorkerStatus: 'ready', registration }, - }) - }, - registered(registration: ServiceWorkerRegistration) { - dispatch({ - type: 'SERVICE_WORKER_REGISTERED', - payload: { serviceWorkerStatus: 'registered', registration }, - }) - }, - cached(registration: ServiceWorkerRegistration) { - dispatch({ - type: 'SERVICE_WORKER_REGISTERED', - payload: { serviceWorkerStatus: 'cached', registration }, - }) - }, - updatefound(registration: ServiceWorkerRegistration) { - dispatch({ - type: 'SERVICE_WORKER_UPDATE_FOUND', - payload: { serviceWorkerStatus: 'updates', registration }, - }) - }, - updated(registration: ServiceWorkerRegistration) { - dispatch({ - type: 'SERVICE_WORKER_UPDATE_READY', - payload: { serviceWorkerStatus: 'updated', registration }, - }) - }, - offline() { - dispatch({ - type: 'SERVICE_WORKER_OFFLINE', - payload: { serviceWorkerStatus: 'offline' }, - }) - }, - error(error: Error) { - dispatch({ - type: 'SERVICE_WORKER_OFFLINE', - payload: { serviceWorkerStatus: 'error', error }, - }) - }, - }) - return () => { - unregister() - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - return swState -} diff --git a/yarn.lock b/yarn.lock index 996c31d80f1..7ba992d3c77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15711,11 +15711,6 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -idb@^6.1.4: - version "6.1.5" - resolved "https://registry.yarnpkg.com/idb/-/idb-6.1.5.tgz#dbc53e7adf1ac7c59f9b2bf56e00b4ea4fce8c7b" - integrity sha512-IJtugpKkiVXQn5Y+LteyBCNk1N8xpGV3wWZk9EVtZWH8DYkjBn0bX1XnGP9RkyZF0sAcywa6unHqSWKe7q4LGw== - ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -15753,11 +15748,6 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= -immer@^9.0.6: - version "9.0.7" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.7.tgz#b6156bd7db55db7abc73fd2fdadf4e579a701075" - integrity sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA== - immutable@^4.0.0-rc.12: version "4.0.0" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23" @@ -25131,101 +25121,6 @@ wordwrap@1.0.0, wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= -workbox-background-sync@6.4.2, workbox-background-sync@^6.1.5: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.4.2.tgz#bb31b95928d376abcb9bde0de3a0cef9bae46cf7" - integrity sha512-P7c8uG5X2k+DMICH9xeSA9eUlCOjHHYoB42Rq+RtUpuwBxUOflAXR1zdsMWj81LopE4gjKXlTw7BFd1BDAHo7g== - dependencies: - idb "^6.1.4" - workbox-core "6.4.2" - -workbox-broadcast-update@^6.1.5: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.4.2.tgz#5094c4767dfb590532ac03ee07e9e82b2ac206bc" - integrity sha512-qnBwQyE0+PWFFc/n4ISXINE49m44gbEreJUYt2ldGH3+CNrLmJ1egJOOyUqqu9R4Eb7QrXcmB34ClXG7S37LbA== - dependencies: - workbox-core "6.4.2" - -workbox-cacheable-response@^6.1.5: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.4.2.tgz#ebcabb3667019da232e986a9927af97871e37ccb" - integrity sha512-9FE1W/cKffk1AJzImxgEN0ceWpyz1tqNjZVtA3/LAvYL3AC5SbIkhc7ZCO82WmO9IjTfu8Vut2X/C7ViMSF7TA== - dependencies: - workbox-core "6.4.2" - -workbox-core@6.4.2, workbox-core@^6.1.5: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.4.2.tgz#f99fd36a211cc01dce90aa7d5f2c255e8fe9d6bc" - integrity sha512-1U6cdEYPcajRXiboSlpJx6U7TvhIKbxRRerfepAJu2hniKwJ3DHILjpU/zx3yvzSBCWcNJDoFalf7Vgd7ey/rw== - -workbox-core@7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-7.1.0.tgz#1867576f994f20d9991b71a7d0b2581af22db170" - integrity sha512-5KB4KOY8rtL31nEF7BfvU7FMzKT4B5TkbYa2tzkS+Peqj0gayMT9SytSFtNzlrvMaWgv6y/yvP9C0IbpFjV30Q== - -workbox-expiration@^6.1.5: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.4.2.tgz#61613459fd6ddd1362730767618d444c6b9c9139" - integrity sha512-0hbpBj0tDnW+DZOUmwZqntB/8xrXOgO34i7s00Si/VlFJvvpRKg1leXdHHU8ykoSBd6+F2KDcMP3swoCi5guLw== - dependencies: - idb "^6.1.4" - workbox-core "6.4.2" - -workbox-google-analytics@^6.1.5: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.4.2.tgz#eea7d511b3078665a726dc2ee9f11c6b7a897530" - integrity sha512-u+gxs3jXovPb1oul4CTBOb+T9fS1oZG+ZE6AzS7l40vnyfJV79DaLBvlpEZfXGv3CjMdV1sT/ltdOrKzo7HcGw== - dependencies: - workbox-background-sync "6.4.2" - workbox-core "6.4.2" - workbox-routing "6.4.2" - workbox-strategies "6.4.2" - -workbox-navigation-preload@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-7.1.0.tgz#2610674d412a1774b5d9f03c9644c9964407b8b6" - integrity sha512-4wyAbo0vNI/X0uWNJhCMKxnPanNyhybsReMGN9QUpaePLTiDpKxPqFxl4oUmBNddPwIXug01eTSLVIFXimRG/A== - dependencies: - workbox-core "7.1.0" - -workbox-precaching@^6.1.5: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.4.2.tgz#8d87c05d54f32ac140f549faebf3b4d42d63621e" - integrity sha512-CZ6uwFN/2wb4noHVlALL7UqPFbLfez/9S2GAzGAb0Sk876ul9ukRKPJJ6gtsxfE2HSTwqwuyNVa6xWyeyJ1XSA== - dependencies: - workbox-core "6.4.2" - workbox-routing "6.4.2" - workbox-strategies "6.4.2" - -workbox-range-requests@^6.1.5: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.4.2.tgz#050f0dfbb61cd1231e609ed91298b6c2442ae41b" - integrity sha512-SowF3z69hr3Po/w7+xarWfzxJX/3Fo0uSG72Zg4g5FWWnHpq2zPvgbWerBZIa81zpJVUdYpMa3akJJsv+LaO1Q== - dependencies: - workbox-core "6.4.2" - -workbox-routing@6.4.2, workbox-routing@^6.1.5: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.4.2.tgz#65b1c61e8ca79bb9152f93263c26b1f248d09dcc" - integrity sha512-0ss/n9PAcHjTy4Ad7l2puuod4WtsnRYu9BrmHcu6Dk4PgWeJo1t5VnGufPxNtcuyPGQ3OdnMdlmhMJ57sSrrSw== - dependencies: - workbox-core "6.4.2" - -workbox-strategies@6.4.2, workbox-strategies@^6.1.5: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.4.2.tgz#50c02bf2d116918e1a8052df5f2c1e4103c62d5d" - integrity sha512-YXh9E9dZGEO1EiPC3jPe2CbztO5WT8Ruj8wiYZM56XqEJp5YlGTtqRjghV+JovWOqkWdR+amJpV31KPWQUvn1Q== - dependencies: - workbox-core "6.4.2" - -workbox-streams@^6.1.5: - version "6.4.2" - resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.4.2.tgz#3bc615cccebfd62dedf28315afb7d9ee177912a5" - integrity sha512-ROEGlZHGVEgpa5bOZefiJEVsi5PsFjJG9Xd+wnDbApsCO9xq9rYFopF+IRq9tChyYzhBnyk2hJxbQVWphz3sog== - dependencies: - workbox-core "6.4.2" - workbox-routing "6.4.2" - worker-farm@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"