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

Implementing forced nullification #25

Merged
merged 7 commits into from
Jun 13, 2019
Merged

Implementing forced nullification #25

merged 7 commits into from
Jun 13, 2019

Conversation

prateekbh
Copy link
Member

@prateekbh prateekbh commented Apr 24, 2019

  • implements a kill switch worker method installNoOpServiceWorker which removes the currently installed service worker.
  • claim all clients
  • force refreshes clients once, after installation
  • cleans all known amp-sw caches
  • does not intercept any call

// Initialize AMP_SW namespace
self['AMP_SW'] = {
init,
installNoOpServiceWorker,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can really use some help in deciding a better name

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -23,6 +23,7 @@ export async function buildSW(
documentCachingOptions: {},
},
importFrom: string = 'https://cdn.ampproject.org/amp-sw.js',
eject: boolean = false,
Copy link
Contributor

@kristoferbaxter kristoferbaxter May 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the same name everywhere, called eject here and installNoOpServiceWorker below.

Pick one!

Also, this function could use some documentation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -32,6 +33,11 @@ export async function buildSW(
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config.offlinePageOptions.assets = (config.offlinePageOptions.assets || []).concat(...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious, how is this more readable than what is there today?

} else {
code += `AMP_SW.init(${serializeObject(config || {})})`;
}

return code;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this all to testing directories, since it's purely for test cases.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do this in a separate PR, issue opened #30

code += 'AMP_SW.installNoOpServiceWorker()';
} else {
code += `AMP_SW.init(${serializeObject(config || {})})`;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can just return code + '' instead of conditionals.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

declare global {
interface WorkerGlobalScope {
AMP_SW: {
init: Function;
installNoOpServiceWorker: Function;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add more explicit typing for what the installNoOpServiceWorker function is?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import(/* webpackChunkName: "service-worker-remover" */ '../service-worker-remover/index').then(
async ({ ServiceWorkerRemover }) => {
const swRemover = new ServiceWorkerRemover();
swRemover.installNoOpServiceWorker();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not using the value elsewhere, so you can just...

new ServiceWorkerRemover().installNoOpServiceWorker();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -0,0 +1 @@
export const cacheName: string = 'AMP-PREFETCHED-LINKS';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's capitalize this constant, for consistency.

Also, you might consider calling it something more explicit since it's used outside of the link-prefetch component now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -14,8 +14,8 @@
* limitations under the License.
*/

import { cacheName as documentCache } from '../document-caching/constants';
import { cacheName as assetCache } from '../asset-caching/constants';
import { cacheName as AMP_PUBLISHER_CACHE_NAME } from '../document-caching/constants';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to avoid the as statements during import by naming the cacheName this in the first place. Helps with finding usages.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

const publisherCache = await caches.open(documentCache);
const assetsCache = await caches.open(assetCache);
const publisherCache = await caches.open(AMP_PUBLISHER_CACHE_NAME);
const assetsCache = await caches.open(ASSET_CACHE);
Copy link
Contributor

@kristoferbaxter kristoferbaxter May 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a good place to use Promise.all or similar, including the fetch in the next line.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import { cacheName as AMP_PREFETCHED_LINKS } from '../link-prefetch/constants';

export class ServiceWorkerRemover {
async installNoOpServiceWorker() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's verify the first version of Safari supporting Service Workers, there is a bug in Safari 10.1 that would preclude an async member from working as expected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11.2

export class ServiceWorkerRemover {
async installNoOpServiceWorker() {
// Taking over the document
self.addEventListener('install', function(e: ExtendableEvent) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be onces?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This memory is freed once the service worker is terminated, and post that service worker execution is just event based so i guess this should matter.
Also there is not good place/lifecycle hook to clear these listeners

return clients.claim().then(async () => {
// Cache current document if its AMP.
const windowClients = await clients.matchAll({ type: 'window' });
windowClients.forEach((client: WindowClient) => {
Copy link
Contributor

@kristoferbaxter kristoferbaxter May 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

windowClients.forEach((client: WindowClient) => {
        client.navigate(client.url);
      });
windowClients.forEach((client: WindowClient) => client.navigate(client.url));

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

async cleanCacheStorage() {
return Promise.all([
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we build a test that verifies that an exception thrown in these methods doesn't break the SW?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit tough to do in the current e2e test setup due to the following reasons

  • The CacheStorage API never rejects the promise it just returns true/false
  • The service worker clears the cache and refreshes the window so even if I try to change the prototype to reject the promise, the service worker thread never gets the change.

Instead I can test this with a unit test case instead.

I'll setup a unit test setup and add a unit tests around service worker forced nullification in a separate PR.
It also makes sense to have these tests for forced nullification module because this piece is beyond just configuring workbox


it('should remove currently registered service worker', async () => {
const installedServiceWorker = await driver.executeAsyncScript(async cb => {
executeScript(async cb => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

executeScript(async cb => {
        cb(navigator.serviceWorker.controller.scriptURL);
      }, cb);
executeScript(async cb => cb(navigator.serviceWorker.controller.scriptURL), cb);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

@kristoferbaxter kristoferbaxter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments.

@prateekbh prateekbh requested a review from kristoferbaxter June 6, 2019 19:55
@@ -96,7 +98,16 @@ function init(config: ServiceWorkerConfiguration = {}) {
});
}

function forcedNullifcation() {
import(/* webpackChunkName: "service-worker-remover" */ '../service-worker-remover/index').then(
async ({ ServiceWorkerRemover }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

async ({ ServiceWorkerRemover }) => {
  new ServiceWorkerRemover().installNoOpServiceWorker();
},

Can be rewritten as...

async ({ ServiceWorkerRemover }) => new ServiceWorkerRemover().installNoOpServiceWorker(),

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you aware of a prettier rule for this?
This definitely looks like an optimization that should either be done by prettier or the compiler.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

manually fixing this for now

@prateekbh prateekbh changed the title Implementing a kill switch Implementing forced nullification Jun 13, 2019
@prateekbh prateekbh merged commit b376d0d into master Jun 13, 2019
@prateekbh prateekbh deleted the kill-switch branch June 13, 2019 17:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants