Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

workbox-broadcast-update

Package Overview
Dependencies
Maintainers
3
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

workbox-broadcast-update - npm Package Compare versions

Comparing version 5.0.0-alpha.2 to 5.0.0-beta.0

BroadcastUpdatePlugin.d.ts

2

_version.js
// @ts-ignore
try {
self['workbox:broadcast-update:5.0.0-alpha.2'] && _();
self['workbox:broadcast-update:5.0.0-beta.0'] && _();
}
catch (e) { }

@@ -1,13 +0,10 @@

import { BroadcastUpdateOptions } from './broadcastUpdate.js';
import { CacheDidUpdateCallbackParam } from 'workbox-core/types.js';
import './_version.js';
export interface BroadcastCacheUpdateOptions {
headersToCheck?: string[];
channelName?: string;
deferNoticationTimeout?: number;
generatePayload?: (options: CacheDidUpdateCallbackParam) => Object;
}
/**
* Uses the [Broadcast Channel API]{@link https://developers.google.com/web/updates/2016/09/broadcastchannel}
* to notify interested parties when a cached response has been updated.
* In browsers that do not support the Broadcast Channel API, the instance
* falls back to sending the update via `postMessage()` to all window clients.
* Uses the `postMessage()` API to inform any open windows/tabs when a cached
* response has been updated.
*

@@ -21,6 +18,3 @@ * For efficiency's sake, the underlying response bodies are not compared;

private _headersToCheck;
private _channelName;
private _deferNoticationTimeout;
private _channel?;
private _navigationEventsDeferreds?;
private _generatePayload;
/**

@@ -31,26 +25,35 @@ * Construct a BroadcastCacheUpdate instance with a specific `channelName` to

* @param {Object} options
* @param {Array<string>}
* [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* A list of headers that will be used to determine whether the responses
* differ.
* @param {string} [options.channelName='workbox'] The name that will be used
*. when creating the `BroadcastChannel`, which defaults to 'workbox' (the
* channel name used by the `workbox-window` package).
* @param {string} [options.deferNoticationTimeout=10000] The amount of time
* to wait for a ready message from the window on navigation requests
* before sending the update.
* @param {string} [options.generatePayload] A function whose return value
* will be used as the `payload` field in any cache update messages sent
* to the window clients.
*/
constructor({ headersToCheck, channelName, deferNoticationTimeout, }?: BroadcastCacheUpdateOptions);
constructor({ headersToCheck, generatePayload, }?: BroadcastCacheUpdateOptions);
/**
* Compare two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* and send a message via the
* {@link https://developers.google.com/web/updates/2016/09/broadcastchannel|Broadcast Channel API}
* if they differ.
* Compares two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* and sends a message (via `postMessage()`) to all window clients if the
* responses differ (note: neither of the Responses can be
* {@link http://stackoverflow.com/questions/39109789|opaque}).
*
* Neither of the Responses can be {@link http://stackoverflow.com/questions/39109789|opaque}.
* The message that's posted has the following format (where `payload` can
* be customized via the `generatePayload` option the instance is created
* with):
*
* ```
* {
* type: 'CACHE_UPDATED',
* meta: 'workbox-broadcast-update',
* payload: {
* cacheName: 'the-cache-name',
* updatedURL: 'https://example.com/'
* }
* }
* ```
*
* @param {Object} options
* @param {Response} options.oldResponse Cached response to compare.
* @param {Response} [options.oldResponse] Cached response to compare.
* @param {Response} options.newResponse Possibly updated response to compare.
* @param {string} options.url The URL of the request.
* @param {Request} options.request The request.
* @param {string} options.cacheName Name of the cache the responses belong

@@ -62,49 +65,4 @@ * to. This is included in the broadcast message.

*/
notifyIfUpdated({ oldResponse, newResponse, url, cacheName, event }: {
oldResponse: Response;
newResponse: Response;
url: string;
cacheName: string;
event?: FetchEvent;
}): Promise<unknown> | void;
/**
* NOTE: this is exposed on the instance primarily so it can be spied on
* in tests.
*
* @param {Object} opts
* @private
*/
_broadcastUpdate(opts: BroadcastUpdateOptions): Promise<void>;
/**
* @return {BroadcastChannel|undefined} The BroadcastChannel instance used for
* broadcasting updates, or undefined if the browser doesn't support the
* Broadcast Channel API.
*
* @private
*/
private _getChannel;
/**
* Waits for a message from the window indicating that it's capable of
* receiving broadcasts. By default, this will only wait for the amount of
* time specified via the `deferNoticationTimeout` option.
*
* @param {Event} event The navigation fetch event.
* @return {Promise}
* @private
*/
private _windowReadyOrTimeout;
/**
* Creates a mapping between navigation fetch events and deferreds, and adds
* a listener for message events from the window. When message events arrive,
* all deferreds in the mapping are resolved.
*
* Note: it would be easier if we could only resolve the deferred of
* navigation fetch event whose client ID matched the source ID of the
* message event, but currently client IDs are not exposed on navigation
* fetch events: https://www.chromestatus.com/feature/4846038800138240
*
* @private
*/
private _initWindowReadyDeferreds;
notifyIfUpdated(options: CacheDidUpdateCallbackParam): Promise<void>;
}
export { BroadcastCacheUpdate };

@@ -9,15 +9,23 @@ /*

import { assert } from 'workbox-core/_private/assert.js';
import { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';
import { logger } from 'workbox-core/_private/logger.js';
import { Deferred } from 'workbox-core/_private/Deferred.js';
import { responsesAreSame } from './responsesAreSame.js';
import { broadcastUpdate } from './broadcastUpdate.js';
import { DEFAULT_HEADERS_TO_CHECK, DEFAULT_BROADCAST_CHANNEL_NAME, DEFAULT_DEFER_NOTIFICATION_TIMEOUT } from './utils/constants.js';
import { CACHE_UPDATED_MESSAGE_TYPE, CACHE_UPDATED_MESSAGE_META, DEFAULT_HEADERS_TO_CHECK } from './utils/constants.js';
import './_version.js';
/**
* Uses the [Broadcast Channel API]{@link https://developers.google.com/web/updates/2016/09/broadcastchannel}
* to notify interested parties when a cached response has been updated.
* In browsers that do not support the Broadcast Channel API, the instance
* falls back to sending the update via `postMessage()` to all window clients.
* Generates the default payload used in update messages. By default the
* payload includes the `cacheName` and `updatedURL` fields.
*
* @return Object
* @private
*/
function defaultPayloadGenerator(data) {
return {
cacheName: data.cacheName,
updatedURL: data.request.url,
};
}
/**
* Uses the `postMessage()` API to inform any open windows/tabs when a cached
* response has been updated.
*
* For efficiency's sake, the underlying response bodies are not compared;

@@ -34,46 +42,38 @@ * only specific response headers are checked.

* @param {Object} options
* @param {Array<string>}
* [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* A list of headers that will be used to determine whether the responses
* differ.
* @param {string} [options.channelName='workbox'] The name that will be used
*. when creating the `BroadcastChannel`, which defaults to 'workbox' (the
* channel name used by the `workbox-window` package).
* @param {string} [options.deferNoticationTimeout=10000] The amount of time
* to wait for a ready message from the window on navigation requests
* before sending the update.
* @param {string} [options.generatePayload] A function whose return value
* will be used as the `payload` field in any cache update messages sent
* to the window clients.
*/
constructor({ headersToCheck, channelName, deferNoticationTimeout, } = {}) {
constructor({ headersToCheck, generatePayload, } = {}) {
this._headersToCheck = headersToCheck || DEFAULT_HEADERS_TO_CHECK;
this._channelName = channelName || DEFAULT_BROADCAST_CHANNEL_NAME;
this._deferNoticationTimeout =
deferNoticationTimeout || DEFAULT_DEFER_NOTIFICATION_TIMEOUT;
if (process.env.NODE_ENV !== 'production') {
assert.isType(this._channelName, 'string', {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'constructor',
paramName: 'channelName',
});
assert.isArray(this._headersToCheck, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'constructor',
paramName: 'headersToCheck',
});
}
this._initWindowReadyDeferreds();
this._generatePayload = generatePayload || defaultPayloadGenerator;
}
/**
* Compare two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* and send a message via the
* {@link https://developers.google.com/web/updates/2016/09/broadcastchannel|Broadcast Channel API}
* if they differ.
* Compares two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* and sends a message (via `postMessage()`) to all window clients if the
* responses differ (note: neither of the Responses can be
* {@link http://stackoverflow.com/questions/39109789|opaque}).
*
* Neither of the Responses can be {@link http://stackoverflow.com/questions/39109789|opaque}.
* The message that's posted has the following format (where `payload` can
* be customized via the `generatePayload` option the instance is created
* with):
*
* ```
* {
* type: 'CACHE_UPDATED',
* meta: 'workbox-broadcast-update',
* payload: {
* cacheName: 'the-cache-name',
* updatedURL: 'https://example.com/'
* }
* }
* ```
*
* @param {Object} options
* @param {Response} options.oldResponse Cached response to compare.
* @param {Response} [options.oldResponse] Cached response to compare.
* @param {Response} options.newResponse Possibly updated response to compare.
* @param {string} options.url The URL of the request.
* @param {Request} options.request The request.
* @param {string} options.cacheName Name of the cache the responses belong

@@ -85,126 +85,43 @@ * to. This is included in the broadcast message.

*/
notifyIfUpdated({ oldResponse, newResponse, url, cacheName, event }) {
if (!responsesAreSame(oldResponse, newResponse, this._headersToCheck)) {
async notifyIfUpdated(options) {
if (process.env.NODE_ENV !== 'production') {
assert.isType(options.cacheName, 'string', {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'cacheName',
});
assert.isInstance(options.newResponse, Response, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'newResponse',
});
assert.isInstance(options.request, Request, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'request',
});
}
// Without two responses there is nothing to compare.
if (!options.oldResponse) {
return;
}
if (!responsesAreSame(options.oldResponse, options.newResponse, this._headersToCheck)) {
if (process.env.NODE_ENV !== 'production') {
logger.log(`Newer response found (and cached) for:`, url);
logger.log(`Newer response found (and cached) for:`, options.request.url);
}
const sendUpdate = async () => {
// In the case of a navigation request, the requesting page will likely
// not have loaded its JavaScript in time to recevied the update
// notification, so we defer it until ready (or we timeout waiting).
if (event && event.request && event.request.mode === 'navigate') {
if (process.env.NODE_ENV !== 'production') {
logger.debug(`Original request was a navigation request, ` +
`waiting for a ready message from the window`, event.request);
}
await this._windowReadyOrTimeout(event);
}
await this._broadcastUpdate({
channel: this._getChannel(),
cacheName,
url,
});
const messageData = {
type: CACHE_UPDATED_MESSAGE_TYPE,
meta: CACHE_UPDATED_MESSAGE_META,
payload: this._generatePayload(options),
};
// Send the update and ensure the SW stays alive until it's sent.
const done = sendUpdate();
if (event) {
try {
event.waitUntil(done);
}
catch (error) {
if (process.env.NODE_ENV !== 'production') {
logger.warn(`Unable to ensure service worker stays alive ` +
`when broadcasting cache update for ` +
`${getFriendlyURL(event.request.url)}'.`);
}
}
const windows = await self.clients.matchAll({ type: 'window' });
for (const win of windows) {
win.postMessage(messageData);
}
return done;
}
}
/**
* NOTE: this is exposed on the instance primarily so it can be spied on
* in tests.
*
* @param {Object} opts
* @private
*/
async _broadcastUpdate(opts) {
await broadcastUpdate(opts);
}
/**
* @return {BroadcastChannel|undefined} The BroadcastChannel instance used for
* broadcasting updates, or undefined if the browser doesn't support the
* Broadcast Channel API.
*
* @private
*/
_getChannel() {
if (('BroadcastChannel' in self) && !this._channel) {
this._channel = new BroadcastChannel(this._channelName);
}
return this._channel;
}
/**
* Waits for a message from the window indicating that it's capable of
* receiving broadcasts. By default, this will only wait for the amount of
* time specified via the `deferNoticationTimeout` option.
*
* @param {Event} event The navigation fetch event.
* @return {Promise}
* @private
*/
_windowReadyOrTimeout(event) {
if (!this._navigationEventsDeferreds.has(event)) {
const deferred = new Deferred();
// Set the deferred on the `_navigationEventsDeferreds` map so it will
// be resolved when the next ready message event comes.
this._navigationEventsDeferreds.set(event, deferred);
// But don't wait too long for the message since it may never come.
const timeout = setTimeout(() => {
if (process.env.NODE_ENV !== 'production') {
logger.debug(`Timed out after ${this._deferNoticationTimeout}` +
`ms waiting for message from window`);
}
deferred.resolve();
}, this._deferNoticationTimeout);
// Ensure the timeout is cleared if the deferred promise is resolved.
deferred.promise.then(() => clearTimeout(timeout));
}
return this._navigationEventsDeferreds.get(event).promise;
}
/**
* Creates a mapping between navigation fetch events and deferreds, and adds
* a listener for message events from the window. When message events arrive,
* all deferreds in the mapping are resolved.
*
* Note: it would be easier if we could only resolve the deferred of
* navigation fetch event whose client ID matched the source ID of the
* message event, but currently client IDs are not exposed on navigation
* fetch events: https://www.chromestatus.com/feature/4846038800138240
*
* @private
*/
_initWindowReadyDeferreds() {
// A mapping between navigation events and their deferreds.
this._navigationEventsDeferreds = new Map();
// The message listener needs to be added in the initial run of the
// service worker, but since we don't actually need to be listening for
// messages until the cache updates, we only invoke the callback if set.
self.addEventListener('message', (event) => {
if (event.data.type === 'WINDOW_READY' &&
event.data.meta === 'workbox-window' &&
this._navigationEventsDeferreds.size > 0) {
if (process.env.NODE_ENV !== 'production') {
logger.debug(`Received WINDOW_READY event: `, event);
}
// Resolve any pending deferreds.
for (const deferred of this._navigationEventsDeferreds.values()) {
deferred.resolve();
}
this._navigationEventsDeferreds.clear();
}
});
}
}
export { BroadcastCacheUpdate };
this.workbox = this.workbox || {};
this.workbox.broadcastUpdate = (function (exports, assert_js, getFriendlyURL_js, logger_js, Deferred_js, WorkboxError_js) {
this.workbox.broadcastUpdate = (function (exports, assert_js, logger_js, WorkboxError_js) {
'use strict';

@@ -7,3 +7,3 @@

try {
self['workbox:broadcast-update:5.0.0-alpha.2'] && _();
self['workbox:broadcast-update:5.0.0-beta.0'] && _();
} catch (e) {}

@@ -69,4 +69,2 @@

const CACHE_UPDATED_MESSAGE_META = 'workbox-broadcast-update';
const DEFAULT_BROADCAST_CHANNEL_NAME = 'workbox';
const DEFAULT_DEFER_NOTIFICATION_TIMEOUT = 10000;
const DEFAULT_HEADERS_TO_CHECK = ['content-length', 'etag', 'last-modified'];

@@ -82,94 +80,18 @@

/**
* You would not normally call this method directly; it's called automatically
* by an instance of the {@link BroadcastCacheUpdate} class. It's exposed here
* for the benefit of developers who would rather not use the full
* `BroadcastCacheUpdate` implementation.
* Generates the default payload used in update messages. By default the
* payload includes the `cacheName` and `updatedURL` fields.
*
* Calling this will dispatch a message on the provided
* {@link https://developers.google.com/web/updates/2016/09/broadcastchannel|Broadcast Channel}
* to notify interested subscribers about a change to a cached resource.
*
* The message that's posted has a formation inspired by the
* [Flux standard action](https://github.com/acdlite/flux-standard-action#introduction)
* format like so:
*
* ```
* {
* type: 'CACHE_UPDATED',
* meta: 'workbox-broadcast-update',
* payload: {
* cacheName: 'the-cache-name',
* updatedURL: 'https://example.com/'
* }
* }
* ```
*
* (Usage of [Flux](https://facebook.github.io/flux/) itself is not at
* all required.)
*
* @param {Object} options
* @param {string} options.cacheName The name of the cache in which the updated
* `Response` was stored.
* @param {string} options.url The URL associated with the updated `Response`.
* @param {BroadcastChannel} [options.channel] The `BroadcastChannel` to use.
* If no channel is set or the browser doesn't support the BroadcastChannel
* api, then an attempt will be made to `postMessage` each window client.
*
* @memberof workbox.broadcastUpdate
* @return Object
* @private
*/
const broadcastUpdate = async ({
channel,
cacheName,
url
}) => {
{
assert_js.assert.isType(cacheName, 'string', {
moduleName: 'workbox-broadcast-update',
className: '~',
funcName: 'broadcastUpdate',
paramName: 'cacheName'
});
assert_js.assert.isType(url, 'string', {
moduleName: 'workbox-broadcast-update',
className: '~',
funcName: 'broadcastUpdate',
paramName: 'url'
});
}
const data = {
type: CACHE_UPDATED_MESSAGE_TYPE,
meta: CACHE_UPDATED_MESSAGE_META,
payload: {
cacheName: cacheName,
updatedURL: url
}
function defaultPayloadGenerator(data) {
return {
cacheName: data.cacheName,
updatedURL: data.request.url
};
if (channel) {
channel.postMessage(data);
} else {
const windows = await self.clients.matchAll({
type: 'window'
});
for (const win of windows) {
win.postMessage(data);
}
}
};
/*
Copyright 2018 Google LLC
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
}
/**
* Uses the [Broadcast Channel API]{@link https://developers.google.com/web/updates/2016/09/broadcastchannel}
* to notify interested parties when a cached response has been updated.
* In browsers that do not support the Broadcast Channel API, the instance
* falls back to sending the update via `postMessage()` to all window clients.
* Uses the `postMessage()` API to inform any open windows/tabs when a cached
* response has been updated.
*

@@ -182,2 +104,3 @@ * For efficiency's sake, the underlying response bodies are not compared;

class BroadcastCacheUpdate {

@@ -189,51 +112,41 @@ /**

* @param {Object} options
* @param {Array<string>}
* [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* A list of headers that will be used to determine whether the responses
* differ.
* @param {string} [options.channelName='workbox'] The name that will be used
*. when creating the `BroadcastChannel`, which defaults to 'workbox' (the
* channel name used by the `workbox-window` package).
* @param {string} [options.deferNoticationTimeout=10000] The amount of time
* to wait for a ready message from the window on navigation requests
* before sending the update.
* @param {string} [options.generatePayload] A function whose return value
* will be used as the `payload` field in any cache update messages sent
* to the window clients.
*/
constructor({
headersToCheck,
channelName,
deferNoticationTimeout
generatePayload
} = {}) {
this._headersToCheck = headersToCheck || DEFAULT_HEADERS_TO_CHECK;
this._channelName = channelName || DEFAULT_BROADCAST_CHANNEL_NAME;
this._deferNoticationTimeout = deferNoticationTimeout || DEFAULT_DEFER_NOTIFICATION_TIMEOUT;
{
assert_js.assert.isType(this._channelName, 'string', {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'constructor',
paramName: 'channelName'
});
assert_js.assert.isArray(this._headersToCheck, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'constructor',
paramName: 'headersToCheck'
});
}
this._initWindowReadyDeferreds();
this._generatePayload = generatePayload || defaultPayloadGenerator;
}
/**
* Compare two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* and send a message via the
* {@link https://developers.google.com/web/updates/2016/09/broadcastchannel|Broadcast Channel API}
* if they differ.
* Compares two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* and sends a message (via `postMessage()`) to all window clients if the
* responses differ (note: neither of the Responses can be
* {@link http://stackoverflow.com/questions/39109789|opaque}).
*
* Neither of the Responses can be {@link http://stackoverflow.com/questions/39109789|opaque}.
* The message that's posted has the following format (where `payload` can
* be customized via the `generatePayload` option the instance is created
* with):
*
* ```
* {
* type: 'CACHE_UPDATED',
* meta: 'workbox-broadcast-update',
* payload: {
* cacheName: 'the-cache-name',
* updatedURL: 'https://example.com/'
* }
* }
* ```
*
* @param {Object} options
* @param {Response} options.oldResponse Cached response to compare.
* @param {Response} [options.oldResponse] Cached response to compare.
* @param {Response} options.newResponse Possibly updated response to compare.
* @param {string} options.url The URL of the request.
* @param {Request} options.request The request.
* @param {string} options.cacheName Name of the cache the responses belong

@@ -247,145 +160,49 @@ * to. This is included in the broadcast message.

notifyIfUpdated({
oldResponse,
newResponse,
url,
cacheName,
event
}) {
if (!responsesAreSame(oldResponse, newResponse, this._headersToCheck)) {
{
logger_js.logger.log(`Newer response found (and cached) for:`, url);
}
async notifyIfUpdated(options) {
{
assert_js.assert.isType(options.cacheName, 'string', {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'cacheName'
});
assert_js.assert.isInstance(options.newResponse, Response, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'newResponse'
});
assert_js.assert.isInstance(options.request, Request, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'request'
});
} // Without two responses there is nothing to compare.
const sendUpdate = async () => {
// In the case of a navigation request, the requesting page will likely
// not have loaded its JavaScript in time to recevied the update
// notification, so we defer it until ready (or we timeout waiting).
if (event && event.request && event.request.mode === 'navigate') {
{
logger_js.logger.debug(`Original request was a navigation request, ` + `waiting for a ready message from the window`, event.request);
}
await this._windowReadyOrTimeout(event);
}
if (!options.oldResponse) {
return;
}
await this._broadcastUpdate({
channel: this._getChannel(),
cacheName,
url
});
}; // Send the update and ensure the SW stays alive until it's sent.
if (!responsesAreSame(options.oldResponse, options.newResponse, this._headersToCheck)) {
{
logger_js.logger.log(`Newer response found (and cached) for:`, options.request.url);
}
const messageData = {
type: CACHE_UPDATED_MESSAGE_TYPE,
meta: CACHE_UPDATED_MESSAGE_META,
payload: this._generatePayload(options)
};
const windows = await self.clients.matchAll({
type: 'window'
});
const done = sendUpdate();
if (event) {
try {
event.waitUntil(done);
} catch (error) {
{
logger_js.logger.warn(`Unable to ensure service worker stays alive ` + `when broadcasting cache update for ` + `${getFriendlyURL_js.getFriendlyURL(event.request.url)}'.`);
}
}
for (const win of windows) {
win.postMessage(messageData);
}
return done;
}
}
/**
* NOTE: this is exposed on the instance primarily so it can be spied on
* in tests.
*
* @param {Object} opts
* @private
*/
async _broadcastUpdate(opts) {
await broadcastUpdate(opts);
}
/**
* @return {BroadcastChannel|undefined} The BroadcastChannel instance used for
* broadcasting updates, or undefined if the browser doesn't support the
* Broadcast Channel API.
*
* @private
*/
_getChannel() {
if ('BroadcastChannel' in self && !this._channel) {
this._channel = new BroadcastChannel(this._channelName);
}
return this._channel;
}
/**
* Waits for a message from the window indicating that it's capable of
* receiving broadcasts. By default, this will only wait for the amount of
* time specified via the `deferNoticationTimeout` option.
*
* @param {Event} event The navigation fetch event.
* @return {Promise}
* @private
*/
_windowReadyOrTimeout(event) {
if (!this._navigationEventsDeferreds.has(event)) {
const deferred = new Deferred_js.Deferred(); // Set the deferred on the `_navigationEventsDeferreds` map so it will
// be resolved when the next ready message event comes.
this._navigationEventsDeferreds.set(event, deferred); // But don't wait too long for the message since it may never come.
const timeout = setTimeout(() => {
{
logger_js.logger.debug(`Timed out after ${this._deferNoticationTimeout}` + `ms waiting for message from window`);
}
deferred.resolve();
}, this._deferNoticationTimeout); // Ensure the timeout is cleared if the deferred promise is resolved.
deferred.promise.then(() => clearTimeout(timeout));
}
return this._navigationEventsDeferreds.get(event).promise;
}
/**
* Creates a mapping between navigation fetch events and deferreds, and adds
* a listener for message events from the window. When message events arrive,
* all deferreds in the mapping are resolved.
*
* Note: it would be easier if we could only resolve the deferred of
* navigation fetch event whose client ID matched the source ID of the
* message event, but currently client IDs are not exposed on navigation
* fetch events: https://www.chromestatus.com/feature/4846038800138240
*
* @private
*/
_initWindowReadyDeferreds() {
// A mapping between navigation events and their deferreds.
this._navigationEventsDeferreds = new Map(); // The message listener needs to be added in the initial run of the
// service worker, but since we don't actually need to be listening for
// messages until the cache updates, we only invoke the callback if set.
self.addEventListener('message', event => {
if (event.data.type === 'WINDOW_READY' && event.data.meta === 'workbox-window' && this._navigationEventsDeferreds.size > 0) {
{
logger_js.logger.debug(`Received WINDOW_READY event: `, event);
} // Resolve any pending deferreds.
for (const deferred of this._navigationEventsDeferreds.values()) {
deferred.resolve();
}
this._navigationEventsDeferreds.clear();
}
});
}
}

@@ -407,19 +224,15 @@

class Plugin {
class BroadcastUpdatePlugin {
/**
* Construct a BroadcastCacheUpdate instance with the passed options and
* calls its `notifyIfUpdated()` method whenever the plugin's
* `cacheDidUpdate` callback is invoked.
* calls its [`notifyIfUpdated()`]{@link workbox.broadcastUpdate.BroadcastCacheUpdate~notifyIfUpdated}
* method whenever the plugin's `cacheDidUpdate` callback is invoked.
*
* @param {Object} options
* @param {Array<string>}
* [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* A list of headers that will be used to determine whether the responses
* differ.
* @param {string} [options.channelName='workbox'] The name that will be used
*. when creating the `BroadcastChannel`, which defaults to 'workbox' (the
* channel name used by the `workbox-window` package).
* @param {string} [options.deferNoticationTimeout=10000] The amount of time
* to wait for a ready message from the window on navigation requests
* before sending the update.
* @param {string} [options.generatePayload] A function whose return value
* will be used as the `payload` field in any cache update messages sent
* to the window clients.
*/

@@ -440,40 +253,4 @@ constructor(options) {

*/
this.cacheDidUpdate = async ({
cacheName,
oldResponse,
newResponse,
request,
event
}) => {
{
assert_js.assert.isType(cacheName, 'string', {
moduleName: 'workbox-broadcast-update',
className: 'Plugin',
funcName: 'cacheDidUpdate',
paramName: 'cacheName'
});
assert_js.assert.isInstance(newResponse, Response, {
moduleName: 'workbox-broadcast-update',
className: 'Plugin',
funcName: 'cacheDidUpdate',
paramName: 'newResponse'
});
assert_js.assert.isInstance(request, Request, {
moduleName: 'workbox-broadcast-update',
className: 'Plugin',
funcName: 'cacheDidUpdate',
paramName: 'request'
});
} // Without a two responses there is nothing to compare.
if (oldResponse) {
this._broadcastUpdate.notifyIfUpdated({
cacheName,
oldResponse,
newResponse,
event,
url: request.url
});
}
this.cacheDidUpdate = async options => {
this._broadcastUpdate.notifyIfUpdated(options);
};

@@ -487,4 +264,3 @@

exports.BroadcastCacheUpdate = BroadcastCacheUpdate;
exports.Plugin = Plugin;
exports.broadcastUpdate = broadcastUpdate;
exports.BroadcastUpdatePlugin = BroadcastUpdatePlugin;
exports.responsesAreSame = responsesAreSame;

@@ -494,3 +270,3 @@

}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
}({}, workbox.core._private, workbox.core._private, workbox.core._private));
//# sourceMappingURL=workbox-broadcast-update.dev.js.map

@@ -1,2 +0,2 @@

this.workbox=this.workbox||{},this.workbox.broadcastUpdate=function(e,t){"use strict";try{self["workbox:broadcast-update:5.0.0-alpha.2"]&&_()}catch(e){}const s=(e,t,s)=>{return!s.some(s=>e.headers.has(s)&&t.headers.has(s))||s.every(s=>{const n=e.headers.has(s)===t.headers.has(s),a=e.headers.get(s)===t.headers.get(s);return n&&a})},n="workbox",a=1e4,i=["content-length","etag","last-modified"],o=async({channel:e,cacheName:t,url:s})=>{const n={type:"CACHE_UPDATED",meta:"workbox-broadcast-update",payload:{cacheName:t,updatedURL:s}};if(e)e.postMessage(n);else{const e=await self.clients.matchAll({type:"window"});for(const t of e)t.postMessage(n)}};class c{constructor({headersToCheck:e,channelName:t,deferNoticationTimeout:s}={}){this.t=e||i,this.s=t||n,this.i=s||a,this.o()}notifyIfUpdated({oldResponse:e,newResponse:t,url:n,cacheName:a,event:i}){if(!s(e,t,this.t)){const e=(async()=>{i&&i.request&&"navigate"===i.request.mode&&await this.h(i),await this.l({channel:this.u(),cacheName:a,url:n})})();if(i)try{i.waitUntil(e)}catch(e){}return e}}async l(e){await o(e)}u(){return"BroadcastChannel"in self&&!this.m&&(this.m=new BroadcastChannel(this.s)),this.m}h(e){if(!this.p.has(e)){const s=new t.Deferred;this.p.set(e,s);const n=setTimeout(()=>{s.resolve()},this.i);s.promise.then(()=>clearTimeout(n))}return this.p.get(e).promise}o(){this.p=new Map,self.addEventListener("message",e=>{if("WINDOW_READY"===e.data.type&&"workbox-window"===e.data.meta&&this.p.size>0){for(const e of this.p.values())e.resolve();this.p.clear()}})}}return e.BroadcastCacheUpdate=c,e.Plugin=class{constructor(e){this.cacheDidUpdate=(async({cacheName:e,oldResponse:t,newResponse:s,request:n,event:a})=>{t&&this.l.notifyIfUpdated({cacheName:e,oldResponse:t,newResponse:s,event:a,url:n.url})}),this.l=new c(e)}},e.broadcastUpdate=o,e.responsesAreSame=s,e}({},workbox.core._private);
this.workbox=this.workbox||{},this.workbox.broadcastUpdate=function(t){"use strict";try{self["workbox:broadcast-update:5.0.0-beta.0"]&&_()}catch(t){}const s=(t,s,e)=>{return!e.some(e=>t.headers.has(e)&&s.headers.has(e))||e.every(e=>{const a=t.headers.has(e)===s.headers.has(e),n=t.headers.get(e)===s.headers.get(e);return a&&n})},e="CACHE_UPDATED",a="workbox-broadcast-update",n=["content-length","etag","last-modified"];function c(t){return{cacheName:t.cacheName,updatedURL:t.request.url}}class o{constructor({headersToCheck:t,generatePayload:s}={}){this.t=t||n,this.s=s||c}async notifyIfUpdated(t){if(t.oldResponse&&!s(t.oldResponse,t.newResponse,this.t)){const s={type:e,meta:a,payload:this.s(t)},n=await self.clients.matchAll({type:"window"});for(const t of n)t.postMessage(s)}}}return t.BroadcastCacheUpdate=o,t.BroadcastUpdatePlugin=class{constructor(t){this.cacheDidUpdate=(async t=>{this.o.notifyIfUpdated(t)}),this.o=new o(t)}},t.responsesAreSame=s,t}({});
//# sourceMappingURL=workbox-broadcast-update.prod.js.map
import { BroadcastCacheUpdate } from './BroadcastCacheUpdate.js';
import { Plugin } from './Plugin.js';
import { broadcastUpdate } from './broadcastUpdate.js';
import { BroadcastUpdatePlugin } from './BroadcastUpdatePlugin.js';
import { responsesAreSame } from './responsesAreSame.js';

@@ -9,2 +8,2 @@ import './_version.js';

*/
export { BroadcastCacheUpdate, Plugin, broadcastUpdate, responsesAreSame, };
export { BroadcastCacheUpdate, BroadcastUpdatePlugin, responsesAreSame, };

@@ -9,4 +9,3 @@ /*

import { BroadcastCacheUpdate } from './BroadcastCacheUpdate.js';
import { Plugin } from './Plugin.js';
import { broadcastUpdate } from './broadcastUpdate.js';
import { BroadcastUpdatePlugin } from './BroadcastUpdatePlugin.js';
import { responsesAreSame } from './responsesAreSame.js';

@@ -17,2 +16,2 @@ import './_version.js';

*/
export { BroadcastCacheUpdate, Plugin, broadcastUpdate, responsesAreSame, };
export { BroadcastCacheUpdate, BroadcastUpdatePlugin, responsesAreSame, };
{
"name": "workbox-broadcast-update",
"version": "5.0.0-alpha.2",
"version": "5.0.0-beta.0",
"license": "MIT",

@@ -30,5 +30,5 @@ "author": "Google's Web DevRel Team",

"dependencies": {
"workbox-core": "^5.0.0-alpha.2"
"workbox-core": "^5.0.0-beta.0"
},
"gitHead": "0cb0029b692c3802545238fe59b6d6179ca32f6c"
"gitHead": "136b38f2c701bd7c04e808d19961310a9ede524b"
}
// @ts-ignore
try{self['workbox:broadcast-update:5.0.0-alpha.2']&&_()}catch(e){}
try{self['workbox:broadcast-update:5.0.0-beta.0']&&_()}catch(e){}

@@ -10,23 +10,36 @@ /*

import {assert} from 'workbox-core/_private/assert.js';
import {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.js';
import {CacheDidUpdateCallbackParam} from 'workbox-core/types.js';
import {logger} from 'workbox-core/_private/logger.js';
import {Deferred} from 'workbox-core/_private/Deferred.js';
import {responsesAreSame} from './responsesAreSame.js';
import {broadcastUpdate, BroadcastUpdateOptions} from './broadcastUpdate.js';
import {DEFAULT_HEADERS_TO_CHECK, DEFAULT_BROADCAST_CHANNEL_NAME, DEFAULT_DEFER_NOTIFICATION_TIMEOUT} from './utils/constants.js';
import {CACHE_UPDATED_MESSAGE_TYPE, CACHE_UPDATED_MESSAGE_META, DEFAULT_HEADERS_TO_CHECK} from './utils/constants.js';
import './_version.js';
// Give TypeScript the correct global.
declare var self: ServiceWorkerGlobalScope;
export interface BroadcastCacheUpdateOptions {
headersToCheck?: string[];
channelName?: string;
deferNoticationTimeout?: number;
generatePayload?: (options: CacheDidUpdateCallbackParam) => Object;
}
/**
* Uses the [Broadcast Channel API]{@link https://developers.google.com/web/updates/2016/09/broadcastchannel}
* to notify interested parties when a cached response has been updated.
* In browsers that do not support the Broadcast Channel API, the instance
* falls back to sending the update via `postMessage()` to all window clients.
* Generates the default payload used in update messages. By default the
* payload includes the `cacheName` and `updatedURL` fields.
*
* @return Object
* @private
*/
function defaultPayloadGenerator(data: CacheDidUpdateCallbackParam): Object {
return {
cacheName: data.cacheName,
updatedURL: data.request.url,
};
}
/**
* Uses the `postMessage()` API to inform any open windows/tabs when a cached
* response has been updated.
*
* For efficiency's sake, the underlying response bodies are not compared;

@@ -39,6 +52,3 @@ * only specific response headers are checked.

private _headersToCheck: string[];
private _channelName: string;
private _deferNoticationTimeout: number;
private _channel?: BroadcastChannel;
private _navigationEventsDeferreds?: Map<Event, Deferred<unknown>>;
private _generatePayload: (options: CacheDidUpdateCallbackParam) => Object;

@@ -50,53 +60,42 @@ /**

* @param {Object} options
* @param {Array<string>}
* [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]
* A list of headers that will be used to determine whether the responses
* differ.
* @param {string} [options.channelName='workbox'] The name that will be used
*. when creating the `BroadcastChannel`, which defaults to 'workbox' (the
* channel name used by the `workbox-window` package).
* @param {string} [options.deferNoticationTimeout=10000] The amount of time
* to wait for a ready message from the window on navigation requests
* before sending the update.
* @param {string} [options.generatePayload] A function whose return value
* will be used as the `payload` field in any cache update messages sent
* to the window clients.
*/
constructor({
headersToCheck,
channelName,
deferNoticationTimeout,
generatePayload,
}: BroadcastCacheUpdateOptions = {}) {
this._headersToCheck = headersToCheck || DEFAULT_HEADERS_TO_CHECK;
this._channelName = channelName || DEFAULT_BROADCAST_CHANNEL_NAME;
this._deferNoticationTimeout =
deferNoticationTimeout || DEFAULT_DEFER_NOTIFICATION_TIMEOUT;
if (process.env.NODE_ENV !== 'production') {
assert!.isType(this._channelName, 'string', {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'constructor',
paramName: 'channelName',
});
assert!.isArray(this._headersToCheck, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'constructor',
paramName: 'headersToCheck',
});
}
this._initWindowReadyDeferreds();
this._generatePayload = generatePayload || defaultPayloadGenerator;
}
/**
* Compare two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* and send a message via the
* {@link https://developers.google.com/web/updates/2016/09/broadcastchannel|Broadcast Channel API}
* if they differ.
* Compares two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
* and sends a message (via `postMessage()`) to all window clients if the
* responses differ (note: neither of the Responses can be
* {@link http://stackoverflow.com/questions/39109789|opaque}).
*
* Neither of the Responses can be {@link http://stackoverflow.com/questions/39109789|opaque}.
* The message that's posted has the following format (where `payload` can
* be customized via the `generatePayload` option the instance is created
* with):
*
* ```
* {
* type: 'CACHE_UPDATED',
* meta: 'workbox-broadcast-update',
* payload: {
* cacheName: 'the-cache-name',
* updatedURL: 'https://example.com/'
* }
* }
* ```
*
* @param {Object} options
* @param {Response} options.oldResponse Cached response to compare.
* @param {Response} [options.oldResponse] Cached response to compare.
* @param {Response} options.newResponse Possibly updated response to compare.
* @param {string} options.url The URL of the request.
* @param {Request} options.request The request.
* @param {string} options.cacheName Name of the cache the responses belong

@@ -108,149 +107,49 @@ * to. This is included in the broadcast message.

*/
notifyIfUpdated({
oldResponse,
newResponse,
url,
cacheName,
event
}: {
oldResponse: Response,
newResponse: Response,
url: string,
cacheName: string,
event?: FetchEvent
}): Promise<unknown> | void {
if (!responsesAreSame(oldResponse, newResponse, this._headersToCheck)) {
async notifyIfUpdated(options: CacheDidUpdateCallbackParam): Promise<void> {
if (process.env.NODE_ENV !== 'production') {
assert!.isType(options.cacheName, 'string', {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'cacheName',
});
assert!.isInstance(options.newResponse, Response, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'newResponse',
});
assert!.isInstance(options.request, Request, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'notifyIfUpdated',
paramName: 'request',
});
}
// Without two responses there is nothing to compare.
if (!options.oldResponse) {
return;
}
if (!responsesAreSame(options.oldResponse!, options.newResponse, this._headersToCheck)) {
if (process.env.NODE_ENV !== 'production') {
logger.log(`Newer response found (and cached) for:`, url);
logger.log(
`Newer response found (and cached) for:`, options.request.url);
}
const sendUpdate = async () => {
// In the case of a navigation request, the requesting page will likely
// not have loaded its JavaScript in time to recevied the update
// notification, so we defer it until ready (or we timeout waiting).
if (event && event.request && event.request.mode === 'navigate') {
if (process.env.NODE_ENV !== 'production') {
logger.debug(`Original request was a navigation request, ` +
`waiting for a ready message from the window`, event.request);
}
await this._windowReadyOrTimeout(event);
}
await this._broadcastUpdate({
channel: this._getChannel(),
cacheName,
url,
});
const messageData = {
type: CACHE_UPDATED_MESSAGE_TYPE,
meta: CACHE_UPDATED_MESSAGE_META,
payload: this._generatePayload(options),
};
// Send the update and ensure the SW stays alive until it's sent.
const done = sendUpdate();
if (event) {
try {
event.waitUntil(done);
} catch (error) {
if (process.env.NODE_ENV !== 'production') {
logger.warn(`Unable to ensure service worker stays alive ` +
`when broadcasting cache update for ` +
`${getFriendlyURL(event.request.url)}'.`);
}
}
const windows = await self.clients.matchAll({type: 'window'});
for (const win of windows) {
win.postMessage(messageData);
}
return done;
}
}
/**
* NOTE: this is exposed on the instance primarily so it can be spied on
* in tests.
*
* @param {Object} opts
* @private
*/
async _broadcastUpdate(opts: BroadcastUpdateOptions) {
await broadcastUpdate(opts);
}
/**
* @return {BroadcastChannel|undefined} The BroadcastChannel instance used for
* broadcasting updates, or undefined if the browser doesn't support the
* Broadcast Channel API.
*
* @private
*/
private _getChannel() {
if (('BroadcastChannel' in self) && !this._channel) {
this._channel = new BroadcastChannel(this._channelName);
}
return this._channel;
}
/**
* Waits for a message from the window indicating that it's capable of
* receiving broadcasts. By default, this will only wait for the amount of
* time specified via the `deferNoticationTimeout` option.
*
* @param {Event} event The navigation fetch event.
* @return {Promise}
* @private
*/
private _windowReadyOrTimeout(event: Event) {
if (!this._navigationEventsDeferreds!.has(event)) {
const deferred = new Deferred();
// Set the deferred on the `_navigationEventsDeferreds` map so it will
// be resolved when the next ready message event comes.
this._navigationEventsDeferreds!.set(event, deferred);
// But don't wait too long for the message since it may never come.
const timeout = setTimeout(() => {
if (process.env.NODE_ENV !== 'production') {
logger.debug(`Timed out after ${this._deferNoticationTimeout}` +
`ms waiting for message from window`);
}
deferred.resolve();
}, this._deferNoticationTimeout);
// Ensure the timeout is cleared if the deferred promise is resolved.
deferred.promise.then(() => clearTimeout(timeout));
}
return this._navigationEventsDeferreds!.get(event)!.promise;
}
/**
* Creates a mapping between navigation fetch events and deferreds, and adds
* a listener for message events from the window. When message events arrive,
* all deferreds in the mapping are resolved.
*
* Note: it would be easier if we could only resolve the deferred of
* navigation fetch event whose client ID matched the source ID of the
* message event, but currently client IDs are not exposed on navigation
* fetch events: https://www.chromestatus.com/feature/4846038800138240
*
* @private
*/
private _initWindowReadyDeferreds() {
// A mapping between navigation events and their deferreds.
this._navigationEventsDeferreds = new Map();
// The message listener needs to be added in the initial run of the
// service worker, but since we don't actually need to be listening for
// messages until the cache updates, we only invoke the callback if set.
self.addEventListener('message', (event: MessageEvent) => {
if (event.data.type === 'WINDOW_READY' &&
event.data.meta === 'workbox-window' &&
this._navigationEventsDeferreds!.size > 0) {
if (process.env.NODE_ENV !== 'production') {
logger.debug(`Received WINDOW_READY event: `, event);
}
// Resolve any pending deferreds.
for (const deferred of this._navigationEventsDeferreds!.values()) {
deferred.resolve();
}
this._navigationEventsDeferreds!.clear();
}
});
}
}
export {BroadcastCacheUpdate};

@@ -10,4 +10,3 @@ /*

import {BroadcastCacheUpdate} from './BroadcastCacheUpdate.js';
import {Plugin} from './Plugin.js';
import {broadcastUpdate} from './broadcastUpdate.js';
import {BroadcastUpdatePlugin} from './BroadcastUpdatePlugin.js';
import {responsesAreSame} from './responsesAreSame.js';

@@ -23,5 +22,4 @@ import './_version.js';

BroadcastCacheUpdate,
Plugin,
broadcastUpdate,
BroadcastUpdatePlugin,
responsesAreSame,
};

@@ -13,4 +13,2 @@ /*

export const CACHE_UPDATED_MESSAGE_META = 'workbox-broadcast-update';
export const DEFAULT_BROADCAST_CHANNEL_NAME = 'workbox';
export const DEFAULT_DEFER_NOTIFICATION_TIMEOUT = 10000;
export const DEFAULT_HEADERS_TO_CHECK: string[] = [

@@ -17,0 +15,0 @@ 'content-length',

import '../_version.js';
export declare const CACHE_UPDATED_MESSAGE_TYPE = "CACHE_UPDATED";
export declare const CACHE_UPDATED_MESSAGE_META = "workbox-broadcast-update";
export declare const DEFAULT_BROADCAST_CHANNEL_NAME = "workbox";
export declare const DEFAULT_DEFER_NOTIFICATION_TIMEOUT = 10000;
export declare const DEFAULT_HEADERS_TO_CHECK: string[];

@@ -11,4 +11,2 @@ /*

export const CACHE_UPDATED_MESSAGE_META = 'workbox-broadcast-update';
export const DEFAULT_BROADCAST_CHANNEL_NAME = 'workbox';
export const DEFAULT_DEFER_NOTIFICATION_TIMEOUT = 10000;
export const DEFAULT_HEADERS_TO_CHECK = [

@@ -15,0 +13,0 @@ 'content-length',

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc