workbox icon indicating copy to clipboard operation
workbox copied to clipboard

Improve examples for using custom onSync callback in Queue class

Open sofiaps opened this issue 4 years ago • 13 comments
trafficstars

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!

sofiaps avatar May 13 '21 06:05 sofiaps

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.

jeffposnick avatar May 14 '21 19:05 jeffposnick

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 avatar May 15 '21 13:05 sofiaps

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! :)

sofiaps avatar May 15 '21 16:05 sofiaps

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

jeffposnick avatar May 17 '21 18:05 jeffposnick

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 avatar May 18 '21 08:05 sofiaps

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?

sofiaps avatar May 26 '21 06:05 sofiaps

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.

jeffposnick avatar May 26 '21 18:05 jeffposnick

@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

sofiaps avatar May 26 '21 20:05 sofiaps

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.

jeffposnick avatar May 26 '21 21:05 jeffposnick

@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

sofiaps avatar May 26 '21 21:05 sofiaps

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().

jeffposnick avatar May 26 '21 21:05 jeffposnick

@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 avatar May 26 '21 21:05 sofiaps

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)

sofiaps avatar Jun 02 '21 18:06 sofiaps