GoogleChrome / workbox

📦 Workbox: JavaScript libraries for Progressive Web Apps
https://developers.google.com/web/tools/workbox/
MIT License
12.27k stars 806 forks source link

Improve examples for using custom onSync callback in Queue class #2842

Closed sofiaps closed 4 months ago

sofiaps commented 3 years ago

Library Affected: workbox-core

Browser & Platform: all browsers

Issue or Feature Request Description: registerRoute does not work after service worker has been activated, although clientsClaim has been called.

My service-worker.ts file:

import { clientsClaim, skipWaiting } from 'workbox-core';
import { NavigationRoute, registerRoute } from 'workbox-routing';

declare const self: ServiceWorkerGlobalScope;

skipWaiting();
clientsClaim();
cleanupOutdatedCaches();

if (process.env.NODE_ENV === 'production') {
    precacheAndRoute(self.__WB_MANIFEST);
    registerRoute(
        /.+\/api\/test/,
        async ({url, request, event, params}) => {
            const response = await fetch(request)
            .then(async response => {
                console.log("success", response);
               return response;
            }).catch(error=>{
                console.log("error", error)
                throw error;
            });
            const responseBody = await response.text();
            return new Response(responseBody);
          },
        "GET"
      );

    const handler = createHandlerBoundToURL('/index.html');
    const navigationRoute = new NavigationRoute(handler, {
        allowlist: [
          new RegExp('/test'),
        ],
      });
    registerRoute(navigationRoute);
}

My main.ts file:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { Workbox } from 'workbox-window';

if (environment.production) {
  enableProdMode();
}

function loadServiceWorker(): void {
  if (environment.production && ('serviceWorker' in navigator)) {
    window.addEventListener('load', () => {
      const wb = new Workbox('service-worker.js');
      wb.addEventListener('installed', (event) => {
        if (!event.isUpdate) {
          console.log('Service worker installed for the first time');
        } else {
          console.log('Service worker installed');
        }
      });

      wb.addEventListener('activated', (event) => {
        if (!event.isUpdate) {
          console.log('Service worker activated for the first time!');
        } else {
          console.log('Service worker activated!');
        }
      });

      wb.register();
    });
  }
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .then(_ => loadServiceWorker())
  .catch(err => console.error(err));

and my package-json:

  "name": "testApp",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "postinstall": "ngcc",
    "sw-prod-webpack": "rimraf ./dist/testApp/service-worker.js && webpack --config ./sw-webpack.config.js --progress --color && workbox injectManifest ./workbox-config.js",
    "build-prod": "ng build --prod",
    "postbuild-prod": "npm run sw-prod-webpack",
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "~11.2.7",
    "@angular/common": "~11.2.7",
    "@angular/compiler": "~11.2.7",
    "@angular/core": "~11.2.7",
    "@angular/forms": "~11.2.7",
    "@angular/platform-browser": "~11.2.7",
    "@angular/platform-browser-dynamic": "~11.2.7",
    "@angular/router": "~11.2.7",
    "@datorama/akita": "^6.1.2",
    "@datorama/akita-ng-entity-service": "^6.0.0",
    "@datorama/akita-ng-router-store": "^6.0.0",
    "dexie": "^3.0.3",
    "rxjs": "~6.6.0",
    "tslib": "^2.0.0",
    "workbox-cacheable-response": "^6.1.5",
    "workbox-core": "^6.1.5",
    "workbox-expiration": "^6.1.5",
    "workbox-precaching": "^6.1.5",
    "workbox-routing": "^6.1.5",
    "workbox-strategies": "^6.1.5",
    "workbox-window": "^6.1.5",
    "zone.js": "~0.11.3"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.1102.6",
    "@angular/cli": "~11.2.6",
    "@angular/compiler-cli": "~11.2.7",
    "@datorama/akita-ngdevtools": "^6.0.0",
    "@types/jasmine": "~3.6.0",
    "@types/node": "^12.11.1",
    "codelyzer": "^6.0.0",
    "jasmine-core": "~3.6.0",
    "jasmine-spec-reporter": "~5.0.0",
    "karma": "~6.1.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "~2.0.3",
    "karma-jasmine": "~4.0.0",
    "karma-jasmine-html-reporter": "^1.5.0",
    "local-web-server": "^4.2.1",
    "protractor": "~7.0.0",
    "rimraf": "^3.0.2",
    "ts-loader": "6.2.1",
    "ts-node": "~8.3.0",
    "tslint": "~6.1.0",
    "typescript": "~4.1.5",
    "webpack": "4.44.1",
    "webpack-cli": "^4.7.0",
    "workbox-build": "^6.1.5",
    "workbox-cli": "^6.1.5",
    "workbox-webpack-plugin": "^6.1.5"
  }
}

I also tried calling self.clients.claim() in the activated event listener, but that's also not possible: image

Any help would be much appreciated! Thanks!

jeffposnick commented 3 years ago

Hello @sofiaps!

I don't see anything that jumps out to me based on the code you shared or the description of the problem.

Do you have an instance of your service worker and web app deployed at a public URL that I could try out? I'd like to take a closer look and debug further. If you can't share the URL publicly, you can DM @jeffposnick on Twitter.

sofiaps commented 3 years ago

Hello @jeffposnick and thanks for the prompt response!

It seems that the first API response was being received before the service worker registration was through. I am registering the service worker using APP_INITIALIZER, so that I make sure the service worker has been registered before anything else and it seems to be working as expected now.

sofiaps commented 3 years ago

Hi again @jeffposnick ! I have another question that came up while trying to register PUT/POST calls.... Is there any way to get the body sent in the handlerCallbak function, so that I could manage the failed requests on network errors? Thanks in advance! :)

jeffposnick commented 3 years ago

If you'd like to access the body of a failed POST/PUT request in the event of a failure, you need to clone() the request before it's sent to the network. After it's already been sent to the network, the body is considered "consumed" and can't be accessed again.

The easiest way to do this using Workbox would probably be via a custom fetchDidFail plugin, which gives you a convenient way to package up reusable logic that runs when the underlying network request failed.

If you use a fetchDidFail plugin, Workbox will take care of calling clone() on the requests for you, so you should always be able to access the body of what's passed in to the callback:

https://github.com/GoogleChrome/workbox/blob/bbaa0bb5bc7830543be4f170d108842dd1f19e01/packages/workbox-strategies/src/StrategyHandler.ts#L167-L186

sofiaps commented 3 years ago

Thanks again @jeffposnick for your great support! For now, a simple request.clone().json() has worked for me in order to retrieve the body in my registered routes for the POST/PUT calls, but I am about to re-write my service worker using custom strategies and plugins, so they will for sure come in handy. :+1: :slightly_smiling_face:

sofiaps commented 3 years ago

Hello @jeffposnick! May I ask another question? I use the Queue class to store and replay tge failed requests, but I would like to send a message from the service worker to the app, once the replay is done, so that I could do some more actions on the client side, but I haven't found a way to do that.. Is it possible to post a message from the service worker (outside the 'message' event-listener) , without having to receive one from the client first?

jeffposnick commented 3 years ago

Does the answer at https://stackoverflow.com/a/67537862/385997 help?

If so, we can get that example moved into the docs so that it's easier to find.

sofiaps commented 3 years ago

@jeffposnick Unfortunately, I'm not able to use 'self' to get the clients. I've tried it before, since it is mentioned in the docs that we should now use self.skipWaiting() instead of just skipWaiting, but I keep getting an error, since in order for my service worker to properly work, I have it declared as declare const self: ServiceWorkerGlobalScope & ServiceWorker;. I'm not sure how 'self' should be declared, in order to achieve that with Angular/TS.

image

jeffposnick commented 3 years ago

Inside of a service worker script, you should use

declare const self: ServiceWorkerGlobalScope;

(and also make sure the 'webworker' lib is included in your TypeScript compilation).

You should not use & ServiceWorker to combine the types, as self is not an instance of ServiceWorker.

sofiaps commented 3 years ago

@jeffposnick yes, sorry! I changed it recently to try something out, but even when declaring it as ServiceWorkerGlobalScope, I cannot access clients (or skipWaiting() ) this way unfortunately. image

Also, I cannot use 'webworker' in my lib file, since when I do, I get errors on building image

Only when I remove 'webworker' from lib it builds properly

jeffposnick commented 3 years ago

I am not 100% sure how to help you, as we're wading into TypeScript setup issues that are unrelated to Workbox.

But I can tell you that skipWaiting() is defined as part of the ServiceWorkerGlobalScope inside of the webworker TypeScript library, so if you're not using webworker, that's why you can't find skipWaiting().

sofiaps commented 3 years ago

@jeffposnick I tried by including webworker lib in a different tsconfig.json in my service-worker folder, which extends the main one, and also by triple slash reference... It just doesn't want to work :smile: Anyway, thanks for your help, I really appreciate it!

sofiaps commented 3 years ago

Forgot to mention that I found a workaround, in case anyone faces the same problem in Angular. I ended up calling the function mentioned at https://stackoverflow.com/a/67537862/385997 from a JS file (created along with the respective typings)

tomayac commented 4 months ago

Hi there,

Workbox is moving to a new engineering team within Google. As part of this move, we're declaring a partial bug bankruptcy to allow the new team to start fresh. We realize this isn't optimal, but realistically, this is the only way we see it working. For transparency, here're the criteria we applied:

Thanks, and we hope for your understanding! The Workbox team