workbox-broadcast-update
Advanced tools
Comparing version 5.0.0-alpha.2 to 5.0.0-beta.0
// @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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 5 instances in 1 package
180900
3
80
5
33
914
1
Updatedworkbox-core@^5.0.0-beta.0