workbox-broadcast-update.dev.js
16.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
this.workbox = this.workbox || {};
this.workbox.broadcastUpdate = (function (exports, assert_mjs, getFriendlyURL_mjs, logger_mjs, Deferred_mjs, WorkboxError_mjs) {
'use strict';
try {
self['workbox:broadcast-update:4.3.1'] && _();
} catch (e) {} // eslint-disable-line
/*
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.
*/
/**
* Given two `Response's`, compares several header values to see if they are
* the same or not.
*
* @param {Response} firstResponse
* @param {Response} secondResponse
* @param {Array<string>} headersToCheck
* @return {boolean}
*
* @memberof workbox.broadcastUpdate
* @private
*/
const responsesAreSame = (firstResponse, secondResponse, headersToCheck) => {
{
if (!(firstResponse instanceof Response && secondResponse instanceof Response)) {
throw new WorkboxError_mjs.WorkboxError('invalid-responses-are-same-args');
}
}
const atLeastOneHeaderAvailable = headersToCheck.some(header => {
return firstResponse.headers.has(header) && secondResponse.headers.has(header);
});
if (!atLeastOneHeaderAvailable) {
{
logger_mjs.logger.warn(`Unable to determine where the response has been updated ` + `because none of the headers that would be checked are present.`);
logger_mjs.logger.debug(`Attempting to compare the following: `, firstResponse, secondResponse, headersToCheck);
} // Just return true, indicating the that responses are the same, since we
// can't determine otherwise.
return true;
}
return headersToCheck.every(header => {
const headerStateComparison = firstResponse.headers.has(header) === secondResponse.headers.has(header);
const headerValueComparison = firstResponse.headers.get(header) === secondResponse.headers.get(header);
return headerStateComparison && headerValueComparison;
});
};
/*
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.
*/
const CACHE_UPDATED_MESSAGE_TYPE = 'CACHE_UPDATED';
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'];
/*
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.
*/
/**
* 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.
*
* 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
*/
const broadcastUpdate = async ({
channel,
cacheName,
url
}) => {
{
assert_mjs.assert.isType(cacheName, 'string', {
moduleName: 'workbox-broadcast-update',
className: '~',
funcName: 'broadcastUpdate',
paramName: 'cacheName'
});
assert_mjs.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
}
};
if (channel) {
channel.postMessage(data);
} else {
const windows = await 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.
*
* For efficiency's sake, the underlying response bodies are not compared;
* only specific response headers are checked.
*
* @memberof workbox.broadcastUpdate
*/
class BroadcastCacheUpdate {
/**
* Construct a BroadcastCacheUpdate instance with a specific `channelName` to
* broadcast messages on
*
* @param {Object} options
* @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.
*/
constructor({
headersToCheck,
channelName,
deferNoticationTimeout
} = {}) {
this._headersToCheck = headersToCheck || DEFAULT_HEADERS_TO_CHECK;
this._channelName = channelName || DEFAULT_BROADCAST_CHANNEL_NAME;
this._deferNoticationTimeout = deferNoticationTimeout || DEFAULT_DEFER_NOTIFICATION_TIMEOUT;
{
assert_mjs.assert.isType(this._channelName, 'string', {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'constructor',
paramName: 'channelName'
});
assert_mjs.assert.isArray(this._headersToCheck, {
moduleName: 'workbox-broadcast-update',
className: 'BroadcastCacheUpdate',
funcName: 'constructor',
paramName: 'headersToCheck'
});
}
this._initWindowReadyDeferreds();
}
/**
* 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.
*
* Neither of the Responses can be {@link http://stackoverflow.com/questions/39109789|opaque}.
*
* @param {Object} options
* @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 {string} options.cacheName Name of the cache the responses belong
* to. This is included in the broadcast message.
* @param {Event} [options.event] event An optional event that triggered
* this possible cache update.
* @return {Promise} Resolves once the update is sent.
*/
notifyIfUpdated({
oldResponse,
newResponse,
url,
cacheName,
event
}) {
if (!responsesAreSame(oldResponse, newResponse, this._headersToCheck)) {
{
logger_mjs.logger.log(`Newer response found (and cached) for:`, 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') {
{
logger_mjs.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
});
}; // Send the update and ensure the SW stays alive until it's sent.
const done = sendUpdate();
if (event) {
try {
event.waitUntil(done);
} catch (error) {
{
logger_mjs.logger.warn(`Unable to ensure service worker stays alive ` + `when broadcasting cache update for ` + `${getFriendlyURL_mjs.getFriendlyURL(event.request.url)}'.`);
}
}
}
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_mjs.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_mjs.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_mjs.logger.debug(`Received WINDOW_READY event: `, event);
} // Resolve any pending deferreds.
for (const deferred of this._navigationEventsDeferreds.values()) {
deferred.resolve();
}
this._navigationEventsDeferreds.clear();
}
});
}
}
/*
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.
*/
/**
* This plugin will automatically broadcast a message whenever a cached response
* is updated.
*
* @memberof workbox.broadcastUpdate
*/
class Plugin {
/**
* Construct a BroadcastCacheUpdate instance with the passed options and
* calls its `notifyIfUpdated()` method whenever the plugin's
* `cacheDidUpdate` callback is invoked.
*
* @param {Object} options
* @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.
*/
constructor(options) {
this._broadcastUpdate = new BroadcastCacheUpdate(options);
}
/**
* A "lifecycle" callback that will be triggered automatically by the
* `workbox-sw` and `workbox-runtime-caching` handlers when an entry is
* added to a cache.
*
* @private
* @param {Object} options The input object to this function.
* @param {string} options.cacheName Name of the cache being updated.
* @param {Response} [options.oldResponse] The previous cached value, if any.
* @param {Response} options.newResponse The new value in the cache.
* @param {Request} options.request The request that triggered the udpate.
* @param {Request} [options.event] The event that triggered the update.
*/
cacheDidUpdate({
cacheName,
oldResponse,
newResponse,
request,
event
}) {
{
assert_mjs.assert.isType(cacheName, 'string', {
moduleName: 'workbox-broadcast-update',
className: 'Plugin',
funcName: 'cacheDidUpdate',
paramName: 'cacheName'
});
assert_mjs.assert.isInstance(newResponse, Response, {
moduleName: 'workbox-broadcast-update',
className: 'Plugin',
funcName: 'cacheDidUpdate',
paramName: 'newResponse'
});
assert_mjs.assert.isInstance(request, Request, {
moduleName: 'workbox-broadcast-update',
className: 'Plugin',
funcName: 'cacheDidUpdate',
paramName: 'request'
});
}
if (!oldResponse) {
// Without a two responses there is nothing to compare.
return;
}
this._broadcastUpdate.notifyIfUpdated({
cacheName,
oldResponse,
newResponse,
event,
url: request.url
});
}
}
/*
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.
*/
exports.BroadcastCacheUpdate = BroadcastCacheUpdate;
exports.Plugin = Plugin;
exports.broadcastUpdate = broadcastUpdate;
exports.responsesAreSame = responsesAreSame;
return exports;
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
//# sourceMappingURL=workbox-broadcast-update.dev.js.map