Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New workbox.precaching.setBaseUrl() method #1347

Closed
josephliccini opened this issue Mar 6, 2018 · 7 comments
Closed

New workbox.precaching.setBaseUrl() method #1347

josephliccini opened this issue Mar 6, 2018 · 7 comments

Comments

@josephliccini
Copy link
Contributor

josephliccini commented Mar 6, 2018

Library Affected:
workbox-sw

Browser & Platform:
All Browsers

Issue or Feature Request Description:
Hi!

We are a large-scale application, and for a variety of reasons, we have different CDN domains depending on what geography the user connects from.

I am interested in obtaining information from the client app to start the 'install' message.

This is where I am at right now:

In the worker

self.addEventListener('install', event => {
    console.log('INSTALL STARTED');
    const installSequence = new Promise((resolve, reject) => {
        self.addEventListener('message', function cdnDomainListener(messageEvent) {
            if (messageEvent.data.type !== 'SET_CDN_DOMAIN') {
                return;
            }
            console.log('MESSAGE RECEIVED');

            self.removeEventListener('message', cdnDomainListener);

            let cdnDomain = messageEvent.data.payload.cdnDomain;
            if (typeof cdnDomain === 'undefined' || cdnDomain === null) {
                cdnDomain = '';
            } else {
                cdnDomain = `https://${messageEvent.data.payload.cdnDomain}`;
            }

            const itemsFromCdnToPrecache = itemsToPrecache.map(item => { // from 'getManifest(...)'
                if (typeof item === 'string') {
                    return `${cdnDomain}${item}`
                } else {
                    item.url = `${cdnDomain}${item.url}`;
                    return item;
                }
            });

            precacheController.addToCacheList(itemsFromCdnToPrecache);

            resolve();
        });
    }).then(() => {
        return precacheController.install();
    }).then(() => {
        console.log('INSTALL FINISHED')
    });

    event.waitUntil(installSequence);
});

And in my main script:

navigator.serviceWorker.register(script, { scope: options.scope }))
            .then(registration => {
                registration.addEventListener('updatefound', function updateListener() {
                    console.log('UPDATE FOUND!!');
                    registration.installing.postMessage({
                        type: 'SET_CDN_DOMAIN',
                        meta: 'workbox-precache-install-from-cdn',
                        payload: {
                            cdnDomain: startupConfigService.config.cdnDomain
                        }
                    } as ServiceWorkerSetCdnDomainMessage);
                });
        });

This technique may want to be documented somewhere. I am not sure if my approach is the right one, but I assume others may have to use some data from the app for the 'install' event at some point.

It seems that from time to time I get stuck in a bad state during refreshes, but this might be from the 'update on reload' flag, and may not be relevant...

Thanks for the great library!

@gauntface
Copy link

This is a pretty cool approach.

I've also thought of doing this via service worker URL params (i.e. register('/sw.js?cdn=http://hello.world/') while easier, is more restrictive.

Out of interest, why is it that the page can access the CDN domain and the service worker can't?

There have been a few requests along these lines (i.e. how can I manipulate the list of items to cache before install) but the reasons why have been generally vague, so documenting possible approaches like this would be good.

@gauntface gauntface added the Documentation Related to our docs. label Mar 6, 2018
@josephliccini
Copy link
Contributor Author

@gauntface I didn't even think of using the query parameters! That might be the easiest approach for now! Thanks for the suggestion!

For our specific implementation, the server will detect the geo the user is connecting to, and then in the Server Rendered page response, the <script> tags will have the correct CDN domain. We are kind of a mix between a SPA and some Server rendered logic for now.. Moving to a PWA model involves some paradigm shifts! 😄

The SW cannot access the DOM or cookies, which are where we can extract the CDN domain from. I suppose I could put it in IndexedDB or something, or in an API call.

@jeffposnick
Copy link
Contributor

jeffposnick commented Mar 6, 2018

I do like the 'sw.js?cdn=https://example.com/' approach, as you also have the advantage of triggering a new service worker registration if you end up swapping in a different URL parameter in your client page. (I'm assuming that's what you'd want in that scenario.)

If we wanted to make this easier, we could theoretically add a method to workbox.precaching that allowed you to override the base URL that was used for evaluating relative/absolute URLs:

const cdnUrl = new URL(location.href).searchParams.get('cdn') || location.origin;

// New config method:
workbox.precaching.setBaseUrl(cdnUrl);

workbox.precaching.precacheAndRoute([...]);

@josephliccini
Copy link
Contributor Author

josephliccini commented Mar 6, 2018

I hooked up the query parameter approach as discussed and it worked like a charm 😃 Thanks @gauntface This might be another 'advanced recipe'.

I even got it working in Workbox 2 without writing a custom 'install' handler / creating my own PrecacheController:

// This is a placeholder that is required by `workbox-build` so that the file manifest can be populated correctly.
// Do not remove or edit.
const itemsToPrecache = []; // written in custom find + replace with result of getFileManifestEntries()

const registrationUrl = new URL(location.href);
const cdnDomainFromQueryParam = registrationUrl.searchParams.get('cdnDomain');
let cdnDomain;
if (cdnDomainFromQueryParam) {
    cdnDomain = `https://${cdnDomainFromQueryParam}`;
} else {
    cdnDomain = location.origin;
}

const itemsFromCdnToPrecache = itemsToPrecache.map(item => {
    if (typeof item === 'string') {
        return `${cdnDomain}${item}`
    } else {
        item.url = `${cdnDomain}${item.url}`;
        return item;
    }
});

workboxSW.precache(itemsFromCdnToPrecache)

@josephliccini
Copy link
Contributor Author

I think it might be worth noting that doing q param passing to the worker does expose a potential XSS vulnerability. Though reading the q param and putting it in a template string should make sure it's always a string, just something to think about..

@gauntface
Copy link

@josephliccini Another option is to import a script that contains this information, avoiding the back and forth from the page (Note importscripts are synchronous).

importScripts('/cdn-details.js');

console.log(self.__cdnDetails.origin);

Then cdn-details.js could be:

self.__cdnDetails = {
 origin: 'https://cdn.example.com/',
};

Downside of this, as @jeffposnick points out, that changing countries could cause some unusual side effects.

@jeffposnick jeffposnick changed the title Document / Advise on Workbox Precache + Install with Data from App New workbox.precaching.setBaseUrl() method Feb 19, 2019
@jeffposnick jeffposnick added Feature Request and removed Documentation Related to our docs. labels Feb 19, 2019
@jeffposnick
Copy link
Contributor

I believe the work @philipwalton is doing in #2459 will enable this without requiring a setBaseUrl() method, so I'm going to close this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants