@serwist/broadcast-update 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/BroadcastCacheUpdate.d.ts +71 -0
- package/dist/BroadcastCacheUpdate.d.ts.map +1 -0
- package/dist/BroadcastUpdatePlugin.d.ts +28 -0
- package/dist/BroadcastUpdatePlugin.d.ts.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +209 -0
- package/dist/index.old.cjs +213 -0
- package/dist/responsesAreSame.d.ts +12 -0
- package/dist/responsesAreSame.d.ts.map +1 -0
- package/dist/utils/constants.d.ts +5 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Google LLC, 2019 ShadowWalker w@weiw.io https://weiw.io, 2023 Serwist
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-broadcast-update
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { CacheDidUpdateCallbackParam } from "@serwist/core";
|
|
2
|
+
export interface BroadcastCacheUpdateOptions {
|
|
3
|
+
/**
|
|
4
|
+
* A list of headers that will be used to determine whether the responses
|
|
5
|
+
* differ.
|
|
6
|
+
*
|
|
7
|
+
* @default ['content-length', 'etag', 'last-modified']
|
|
8
|
+
*/
|
|
9
|
+
headersToCheck?: string[];
|
|
10
|
+
/**
|
|
11
|
+
* A function whose return value
|
|
12
|
+
* will be used as the `payload` field in any cache update messages sent
|
|
13
|
+
* to the window clients.
|
|
14
|
+
* @param options
|
|
15
|
+
* @returns
|
|
16
|
+
*/
|
|
17
|
+
generatePayload?: (options: CacheDidUpdateCallbackParam) => Record<string, any>;
|
|
18
|
+
/**
|
|
19
|
+
* If true (the default) then all open clients will receive a message. If false,
|
|
20
|
+
* then only the client that make the original request will be notified of the update.
|
|
21
|
+
*
|
|
22
|
+
* @default true
|
|
23
|
+
*/
|
|
24
|
+
notifyAllClients?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Uses the `postMessage()` API to inform any open windows/tabs when a cached
|
|
28
|
+
* response has been updated.
|
|
29
|
+
*
|
|
30
|
+
* For efficiency's sake, the underlying response bodies are not compared;
|
|
31
|
+
* only specific response headers are checked.
|
|
32
|
+
*/
|
|
33
|
+
declare class BroadcastCacheUpdate {
|
|
34
|
+
private readonly _headersToCheck;
|
|
35
|
+
private readonly _generatePayload;
|
|
36
|
+
private readonly _notifyAllClients;
|
|
37
|
+
/**
|
|
38
|
+
* Construct a BroadcastCacheUpdate instance with a specific `channelName` to
|
|
39
|
+
* broadcast messages on
|
|
40
|
+
*
|
|
41
|
+
* @param options
|
|
42
|
+
*/
|
|
43
|
+
constructor({ generatePayload, headersToCheck, notifyAllClients }?: BroadcastCacheUpdateOptions);
|
|
44
|
+
/**
|
|
45
|
+
* Compares two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
|
|
46
|
+
* and sends a message (via `postMessage()`) to all window clients if the
|
|
47
|
+
* responses differ. Neither of the Responses can be
|
|
48
|
+
* [opaque](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses).
|
|
49
|
+
*
|
|
50
|
+
* The message that's posted has the following format (where `payload` can
|
|
51
|
+
* be customized via the `generatePayload` option the instance is created
|
|
52
|
+
* with):
|
|
53
|
+
*
|
|
54
|
+
* ```
|
|
55
|
+
* {
|
|
56
|
+
* type: 'CACHE_UPDATED',
|
|
57
|
+
* meta: 'workbox-broadcast-update',
|
|
58
|
+
* payload: {
|
|
59
|
+
* cacheName: 'the-cache-name',
|
|
60
|
+
* updatedURL: 'https://example.com/'
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @param options
|
|
66
|
+
* @returns Resolves once the update is sent.
|
|
67
|
+
*/
|
|
68
|
+
notifyIfUpdated(options: CacheDidUpdateCallbackParam): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
export { BroadcastCacheUpdate };
|
|
71
|
+
//# sourceMappingURL=BroadcastCacheUpdate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BroadcastCacheUpdate.d.ts","sourceRoot":"","sources":["../src/BroadcastCacheUpdate.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAcjE,MAAM,WAAW,2BAA2B;IAC1C;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,2BAA2B,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChF;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAgBD;;;;;;GAMG;AACH,cAAM,oBAAoB;IACxB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAW;IAC3C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAgE;IACjG,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;IAE5C;;;;;OAKG;gBACS,EAAE,eAAe,EAAE,cAAc,EAAE,gBAAgB,EAAE,GAAE,2BAAgC;IAMnG;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACG,eAAe,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;CA4E3E;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SerwistPlugin } from "@serwist/core";
|
|
2
|
+
import type { BroadcastCacheUpdateOptions } from "./BroadcastCacheUpdate.js";
|
|
3
|
+
/**
|
|
4
|
+
* This plugin will automatically broadcast a message whenever a cached response
|
|
5
|
+
* is updated.
|
|
6
|
+
*/
|
|
7
|
+
declare class BroadcastUpdatePlugin implements SerwistPlugin {
|
|
8
|
+
private readonly _broadcastUpdate;
|
|
9
|
+
/**
|
|
10
|
+
* Construct a `@serwist/broadcast-update.BroadcastCacheUpdate` instance with
|
|
11
|
+
* the passed options and calls its `notifyIfUpdated` method whenever the
|
|
12
|
+
* plugin's `cacheDidUpdate` callback is invoked.
|
|
13
|
+
*
|
|
14
|
+
* @param options
|
|
15
|
+
*/
|
|
16
|
+
constructor(options?: BroadcastCacheUpdateOptions);
|
|
17
|
+
/**
|
|
18
|
+
* A "lifecycle" callback that will be triggered automatically by
|
|
19
|
+
* `@serwist/build.RuntimeCaching` handlers when an entry is
|
|
20
|
+
* added to a cache.
|
|
21
|
+
*
|
|
22
|
+
* @private
|
|
23
|
+
* @param options The input object to this function.
|
|
24
|
+
*/
|
|
25
|
+
cacheDidUpdate: SerwistPlugin["cacheDidUpdate"];
|
|
26
|
+
}
|
|
27
|
+
export { BroadcastUpdatePlugin };
|
|
28
|
+
//# sourceMappingURL=BroadcastUpdatePlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BroadcastUpdatePlugin.d.ts","sourceRoot":"","sources":["../src/BroadcastUpdatePlugin.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGnD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAG7E;;;GAGG;AACH,cAAM,qBAAsB,YAAW,aAAa;IAClD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuB;IAExD;;;;;;OAMG;gBACS,OAAO,CAAC,EAAE,2BAA2B;IAIjD;;;;;;;OAOG;IACH,cAAc,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAE7C;CACH;AAED,OAAO,EAAE,qBAAqB,EAAE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BroadcastCacheUpdateOptions } from "./BroadcastCacheUpdate.js";
|
|
2
|
+
import { BroadcastCacheUpdate } from "./BroadcastCacheUpdate.js";
|
|
3
|
+
import { BroadcastUpdatePlugin } from "./BroadcastUpdatePlugin.js";
|
|
4
|
+
import { responsesAreSame } from "./responsesAreSame.js";
|
|
5
|
+
/**
|
|
6
|
+
* @module workbox-broadcast-update
|
|
7
|
+
*/
|
|
8
|
+
export { BroadcastCacheUpdate, BroadcastUpdatePlugin, responsesAreSame };
|
|
9
|
+
export type { BroadcastCacheUpdateOptions };
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD;;GAEG;AAEH,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,CAAC;AAEzE,YAAY,EAAE,2BAA2B,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { SerwistError, logger, assert, resultingClientExists, timeout, dontWaitFor } from '@serwist/core/internal';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Given two `Response's`, compares several header values to see if they are
|
|
5
|
+
* the same or not.
|
|
6
|
+
*
|
|
7
|
+
* @param firstResponse
|
|
8
|
+
* @param secondResponse
|
|
9
|
+
* @param headersToCheck
|
|
10
|
+
* @returns
|
|
11
|
+
*/ const responsesAreSame = (firstResponse, secondResponse, headersToCheck)=>{
|
|
12
|
+
if (process.env.NODE_ENV !== "production") {
|
|
13
|
+
if (!(firstResponse instanceof Response && secondResponse instanceof Response)) {
|
|
14
|
+
throw new SerwistError("invalid-responses-are-same-args");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const atLeastOneHeaderAvailable = headersToCheck.some((header)=>{
|
|
18
|
+
return firstResponse.headers.has(header) && secondResponse.headers.has(header);
|
|
19
|
+
});
|
|
20
|
+
if (!atLeastOneHeaderAvailable) {
|
|
21
|
+
if (process.env.NODE_ENV !== "production") {
|
|
22
|
+
logger.warn(`Unable to determine where the response has been updated ` + `because none of the headers that would be checked are present.`);
|
|
23
|
+
logger.debug(`Attempting to compare the following: `, firstResponse, secondResponse, headersToCheck);
|
|
24
|
+
}
|
|
25
|
+
// Just return true, indicating the that responses are the same, since we
|
|
26
|
+
// can't determine otherwise.
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return headersToCheck.every((header)=>{
|
|
30
|
+
const headerStateComparison = firstResponse.headers.has(header) === secondResponse.headers.has(header);
|
|
31
|
+
const headerValueComparison = firstResponse.headers.get(header) === secondResponse.headers.get(header);
|
|
32
|
+
return headerStateComparison && headerValueComparison;
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
Copyright 2018 Google LLC
|
|
38
|
+
|
|
39
|
+
Use of this source code is governed by an MIT-style
|
|
40
|
+
license that can be found in the LICENSE file or at
|
|
41
|
+
https://opensource.org/licenses/MIT.
|
|
42
|
+
*/ const CACHE_UPDATED_MESSAGE_TYPE = "CACHE_UPDATED";
|
|
43
|
+
const CACHE_UPDATED_MESSAGE_META = "serwist-broadcast-update";
|
|
44
|
+
const NOTIFY_ALL_CLIENTS = true;
|
|
45
|
+
const DEFAULT_HEADERS_TO_CHECK = [
|
|
46
|
+
"content-length",
|
|
47
|
+
"etag",
|
|
48
|
+
"last-modified"
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// UA-sniff Safari: https://stackoverflow.com/questions/7944460/detect-safari-browser
|
|
52
|
+
// TODO(philipwalton): remove once this Safari bug fix has been released.
|
|
53
|
+
// https://bugs.webkit.org/show_bug.cgi?id=201169
|
|
54
|
+
const isSafari = typeof navigator !== "undefined" && /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
55
|
+
/**
|
|
56
|
+
* Generates the default payload used in update messages. By default the
|
|
57
|
+
* payload includes the `cacheName` and `updatedURL` fields.
|
|
58
|
+
*
|
|
59
|
+
* @returns
|
|
60
|
+
* @private
|
|
61
|
+
*/ function defaultPayloadGenerator(data) {
|
|
62
|
+
return {
|
|
63
|
+
cacheName: data.cacheName,
|
|
64
|
+
updatedURL: data.request.url
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Uses the `postMessage()` API to inform any open windows/tabs when a cached
|
|
69
|
+
* response has been updated.
|
|
70
|
+
*
|
|
71
|
+
* For efficiency's sake, the underlying response bodies are not compared;
|
|
72
|
+
* only specific response headers are checked.
|
|
73
|
+
*/ class BroadcastCacheUpdate {
|
|
74
|
+
_headersToCheck;
|
|
75
|
+
_generatePayload;
|
|
76
|
+
_notifyAllClients;
|
|
77
|
+
/**
|
|
78
|
+
* Construct a BroadcastCacheUpdate instance with a specific `channelName` to
|
|
79
|
+
* broadcast messages on
|
|
80
|
+
*
|
|
81
|
+
* @param options
|
|
82
|
+
*/ constructor({ generatePayload, headersToCheck, notifyAllClients } = {}){
|
|
83
|
+
this._headersToCheck = headersToCheck || DEFAULT_HEADERS_TO_CHECK;
|
|
84
|
+
this._generatePayload = generatePayload || defaultPayloadGenerator;
|
|
85
|
+
this._notifyAllClients = notifyAllClients ?? NOTIFY_ALL_CLIENTS;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Compares two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
|
|
89
|
+
* and sends a message (via `postMessage()`) to all window clients if the
|
|
90
|
+
* responses differ. Neither of the Responses can be
|
|
91
|
+
* [opaque](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses).
|
|
92
|
+
*
|
|
93
|
+
* The message that's posted has the following format (where `payload` can
|
|
94
|
+
* be customized via the `generatePayload` option the instance is created
|
|
95
|
+
* with):
|
|
96
|
+
*
|
|
97
|
+
* ```
|
|
98
|
+
* {
|
|
99
|
+
* type: 'CACHE_UPDATED',
|
|
100
|
+
* meta: 'workbox-broadcast-update',
|
|
101
|
+
* payload: {
|
|
102
|
+
* cacheName: 'the-cache-name',
|
|
103
|
+
* updatedURL: 'https://example.com/'
|
|
104
|
+
* }
|
|
105
|
+
* }
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* @param options
|
|
109
|
+
* @returns Resolves once the update is sent.
|
|
110
|
+
*/ async notifyIfUpdated(options) {
|
|
111
|
+
if (process.env.NODE_ENV !== "production") {
|
|
112
|
+
assert.isType(options.cacheName, "string", {
|
|
113
|
+
moduleName: "@serwist/broadcast-update",
|
|
114
|
+
className: "BroadcastCacheUpdate",
|
|
115
|
+
funcName: "notifyIfUpdated",
|
|
116
|
+
paramName: "cacheName"
|
|
117
|
+
});
|
|
118
|
+
assert.isInstance(options.newResponse, Response, {
|
|
119
|
+
moduleName: "@serwist/broadcast-update",
|
|
120
|
+
className: "BroadcastCacheUpdate",
|
|
121
|
+
funcName: "notifyIfUpdated",
|
|
122
|
+
paramName: "newResponse"
|
|
123
|
+
});
|
|
124
|
+
assert.isInstance(options.request, Request, {
|
|
125
|
+
moduleName: "@serwist/broadcast-update",
|
|
126
|
+
className: "BroadcastCacheUpdate",
|
|
127
|
+
funcName: "notifyIfUpdated",
|
|
128
|
+
paramName: "request"
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// Without two responses there is nothing to compare.
|
|
132
|
+
if (!options.oldResponse) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (!responsesAreSame(options.oldResponse, options.newResponse, this._headersToCheck)) {
|
|
136
|
+
if (process.env.NODE_ENV !== "production") {
|
|
137
|
+
logger.log(`Newer response found (and cached) for:`, options.request.url);
|
|
138
|
+
}
|
|
139
|
+
const messageData = {
|
|
140
|
+
type: CACHE_UPDATED_MESSAGE_TYPE,
|
|
141
|
+
meta: CACHE_UPDATED_MESSAGE_META,
|
|
142
|
+
payload: this._generatePayload(options)
|
|
143
|
+
};
|
|
144
|
+
// For navigation requests, wait until the new window client exists
|
|
145
|
+
// before sending the message
|
|
146
|
+
if (options.request.mode === "navigate") {
|
|
147
|
+
let resultingClientId;
|
|
148
|
+
if (options.event instanceof FetchEvent) {
|
|
149
|
+
resultingClientId = options.event.resultingClientId;
|
|
150
|
+
}
|
|
151
|
+
const resultingWin = await resultingClientExists(resultingClientId);
|
|
152
|
+
// Safari does not currently implement postMessage buffering and
|
|
153
|
+
// there's no good way to feature detect that, so to increase the
|
|
154
|
+
// chances of the message being delivered in Safari, we add a timeout.
|
|
155
|
+
// We also do this if `resultingClientExists()` didn't return a client,
|
|
156
|
+
// which means it timed out, so it's worth waiting a bit longer.
|
|
157
|
+
if (!resultingWin || isSafari) {
|
|
158
|
+
// 3500 is chosen because (according to CrUX data) 80% of mobile
|
|
159
|
+
// websites hit the DOMContentLoaded event in less than 3.5 seconds.
|
|
160
|
+
// And presumably sites implementing service worker are on the
|
|
161
|
+
// higher end of the performance spectrum.
|
|
162
|
+
await timeout(3500);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (this._notifyAllClients) {
|
|
166
|
+
const windows = await self.clients.matchAll({
|
|
167
|
+
type: "window"
|
|
168
|
+
});
|
|
169
|
+
for (const win of windows){
|
|
170
|
+
win.postMessage(messageData);
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
// See https://github.com/GoogleChrome/workbox/issues/2895
|
|
174
|
+
if (options.event instanceof FetchEvent) {
|
|
175
|
+
const client = await self.clients.get(options.event.clientId);
|
|
176
|
+
client?.postMessage(messageData);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* This plugin will automatically broadcast a message whenever a cached response
|
|
185
|
+
* is updated.
|
|
186
|
+
*/ class BroadcastUpdatePlugin {
|
|
187
|
+
_broadcastUpdate;
|
|
188
|
+
/**
|
|
189
|
+
* Construct a `@serwist/broadcast-update.BroadcastCacheUpdate` instance with
|
|
190
|
+
* the passed options and calls its `notifyIfUpdated` method whenever the
|
|
191
|
+
* plugin's `cacheDidUpdate` callback is invoked.
|
|
192
|
+
*
|
|
193
|
+
* @param options
|
|
194
|
+
*/ constructor(options){
|
|
195
|
+
this._broadcastUpdate = new BroadcastCacheUpdate(options);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* A "lifecycle" callback that will be triggered automatically by
|
|
199
|
+
* `@serwist/build.RuntimeCaching` handlers when an entry is
|
|
200
|
+
* added to a cache.
|
|
201
|
+
*
|
|
202
|
+
* @private
|
|
203
|
+
* @param options The input object to this function.
|
|
204
|
+
*/ cacheDidUpdate = async (options)=>{
|
|
205
|
+
dontWaitFor(this._broadcastUpdate.notifyIfUpdated(options));
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export { BroadcastCacheUpdate, BroadcastUpdatePlugin, responsesAreSame };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var internal = require('@serwist/core/internal');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Given two `Response's`, compares several header values to see if they are
|
|
7
|
+
* the same or not.
|
|
8
|
+
*
|
|
9
|
+
* @param firstResponse
|
|
10
|
+
* @param secondResponse
|
|
11
|
+
* @param headersToCheck
|
|
12
|
+
* @returns
|
|
13
|
+
*/ const responsesAreSame = (firstResponse, secondResponse, headersToCheck)=>{
|
|
14
|
+
if (process.env.NODE_ENV !== "production") {
|
|
15
|
+
if (!(firstResponse instanceof Response && secondResponse instanceof Response)) {
|
|
16
|
+
throw new internal.SerwistError("invalid-responses-are-same-args");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const atLeastOneHeaderAvailable = headersToCheck.some((header)=>{
|
|
20
|
+
return firstResponse.headers.has(header) && secondResponse.headers.has(header);
|
|
21
|
+
});
|
|
22
|
+
if (!atLeastOneHeaderAvailable) {
|
|
23
|
+
if (process.env.NODE_ENV !== "production") {
|
|
24
|
+
internal.logger.warn(`Unable to determine where the response has been updated ` + `because none of the headers that would be checked are present.`);
|
|
25
|
+
internal.logger.debug(`Attempting to compare the following: `, firstResponse, secondResponse, headersToCheck);
|
|
26
|
+
}
|
|
27
|
+
// Just return true, indicating the that responses are the same, since we
|
|
28
|
+
// can't determine otherwise.
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return headersToCheck.every((header)=>{
|
|
32
|
+
const headerStateComparison = firstResponse.headers.has(header) === secondResponse.headers.has(header);
|
|
33
|
+
const headerValueComparison = firstResponse.headers.get(header) === secondResponse.headers.get(header);
|
|
34
|
+
return headerStateComparison && headerValueComparison;
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
Copyright 2018 Google LLC
|
|
40
|
+
|
|
41
|
+
Use of this source code is governed by an MIT-style
|
|
42
|
+
license that can be found in the LICENSE file or at
|
|
43
|
+
https://opensource.org/licenses/MIT.
|
|
44
|
+
*/ const CACHE_UPDATED_MESSAGE_TYPE = "CACHE_UPDATED";
|
|
45
|
+
const CACHE_UPDATED_MESSAGE_META = "serwist-broadcast-update";
|
|
46
|
+
const NOTIFY_ALL_CLIENTS = true;
|
|
47
|
+
const DEFAULT_HEADERS_TO_CHECK = [
|
|
48
|
+
"content-length",
|
|
49
|
+
"etag",
|
|
50
|
+
"last-modified"
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
// UA-sniff Safari: https://stackoverflow.com/questions/7944460/detect-safari-browser
|
|
54
|
+
// TODO(philipwalton): remove once this Safari bug fix has been released.
|
|
55
|
+
// https://bugs.webkit.org/show_bug.cgi?id=201169
|
|
56
|
+
const isSafari = typeof navigator !== "undefined" && /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
57
|
+
/**
|
|
58
|
+
* Generates the default payload used in update messages. By default the
|
|
59
|
+
* payload includes the `cacheName` and `updatedURL` fields.
|
|
60
|
+
*
|
|
61
|
+
* @returns
|
|
62
|
+
* @private
|
|
63
|
+
*/ function defaultPayloadGenerator(data) {
|
|
64
|
+
return {
|
|
65
|
+
cacheName: data.cacheName,
|
|
66
|
+
updatedURL: data.request.url
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Uses the `postMessage()` API to inform any open windows/tabs when a cached
|
|
71
|
+
* response has been updated.
|
|
72
|
+
*
|
|
73
|
+
* For efficiency's sake, the underlying response bodies are not compared;
|
|
74
|
+
* only specific response headers are checked.
|
|
75
|
+
*/ class BroadcastCacheUpdate {
|
|
76
|
+
_headersToCheck;
|
|
77
|
+
_generatePayload;
|
|
78
|
+
_notifyAllClients;
|
|
79
|
+
/**
|
|
80
|
+
* Construct a BroadcastCacheUpdate instance with a specific `channelName` to
|
|
81
|
+
* broadcast messages on
|
|
82
|
+
*
|
|
83
|
+
* @param options
|
|
84
|
+
*/ constructor({ generatePayload, headersToCheck, notifyAllClients } = {}){
|
|
85
|
+
this._headersToCheck = headersToCheck || DEFAULT_HEADERS_TO_CHECK;
|
|
86
|
+
this._generatePayload = generatePayload || defaultPayloadGenerator;
|
|
87
|
+
this._notifyAllClients = notifyAllClients ?? NOTIFY_ALL_CLIENTS;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Compares two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
|
|
91
|
+
* and sends a message (via `postMessage()`) to all window clients if the
|
|
92
|
+
* responses differ. Neither of the Responses can be
|
|
93
|
+
* [opaque](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses).
|
|
94
|
+
*
|
|
95
|
+
* The message that's posted has the following format (where `payload` can
|
|
96
|
+
* be customized via the `generatePayload` option the instance is created
|
|
97
|
+
* with):
|
|
98
|
+
*
|
|
99
|
+
* ```
|
|
100
|
+
* {
|
|
101
|
+
* type: 'CACHE_UPDATED',
|
|
102
|
+
* meta: 'workbox-broadcast-update',
|
|
103
|
+
* payload: {
|
|
104
|
+
* cacheName: 'the-cache-name',
|
|
105
|
+
* updatedURL: 'https://example.com/'
|
|
106
|
+
* }
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @param options
|
|
111
|
+
* @returns Resolves once the update is sent.
|
|
112
|
+
*/ async notifyIfUpdated(options) {
|
|
113
|
+
if (process.env.NODE_ENV !== "production") {
|
|
114
|
+
internal.assert.isType(options.cacheName, "string", {
|
|
115
|
+
moduleName: "@serwist/broadcast-update",
|
|
116
|
+
className: "BroadcastCacheUpdate",
|
|
117
|
+
funcName: "notifyIfUpdated",
|
|
118
|
+
paramName: "cacheName"
|
|
119
|
+
});
|
|
120
|
+
internal.assert.isInstance(options.newResponse, Response, {
|
|
121
|
+
moduleName: "@serwist/broadcast-update",
|
|
122
|
+
className: "BroadcastCacheUpdate",
|
|
123
|
+
funcName: "notifyIfUpdated",
|
|
124
|
+
paramName: "newResponse"
|
|
125
|
+
});
|
|
126
|
+
internal.assert.isInstance(options.request, Request, {
|
|
127
|
+
moduleName: "@serwist/broadcast-update",
|
|
128
|
+
className: "BroadcastCacheUpdate",
|
|
129
|
+
funcName: "notifyIfUpdated",
|
|
130
|
+
paramName: "request"
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// Without two responses there is nothing to compare.
|
|
134
|
+
if (!options.oldResponse) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (!responsesAreSame(options.oldResponse, options.newResponse, this._headersToCheck)) {
|
|
138
|
+
if (process.env.NODE_ENV !== "production") {
|
|
139
|
+
internal.logger.log(`Newer response found (and cached) for:`, options.request.url);
|
|
140
|
+
}
|
|
141
|
+
const messageData = {
|
|
142
|
+
type: CACHE_UPDATED_MESSAGE_TYPE,
|
|
143
|
+
meta: CACHE_UPDATED_MESSAGE_META,
|
|
144
|
+
payload: this._generatePayload(options)
|
|
145
|
+
};
|
|
146
|
+
// For navigation requests, wait until the new window client exists
|
|
147
|
+
// before sending the message
|
|
148
|
+
if (options.request.mode === "navigate") {
|
|
149
|
+
let resultingClientId;
|
|
150
|
+
if (options.event instanceof FetchEvent) {
|
|
151
|
+
resultingClientId = options.event.resultingClientId;
|
|
152
|
+
}
|
|
153
|
+
const resultingWin = await internal.resultingClientExists(resultingClientId);
|
|
154
|
+
// Safari does not currently implement postMessage buffering and
|
|
155
|
+
// there's no good way to feature detect that, so to increase the
|
|
156
|
+
// chances of the message being delivered in Safari, we add a timeout.
|
|
157
|
+
// We also do this if `resultingClientExists()` didn't return a client,
|
|
158
|
+
// which means it timed out, so it's worth waiting a bit longer.
|
|
159
|
+
if (!resultingWin || isSafari) {
|
|
160
|
+
// 3500 is chosen because (according to CrUX data) 80% of mobile
|
|
161
|
+
// websites hit the DOMContentLoaded event in less than 3.5 seconds.
|
|
162
|
+
// And presumably sites implementing service worker are on the
|
|
163
|
+
// higher end of the performance spectrum.
|
|
164
|
+
await internal.timeout(3500);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (this._notifyAllClients) {
|
|
168
|
+
const windows = await self.clients.matchAll({
|
|
169
|
+
type: "window"
|
|
170
|
+
});
|
|
171
|
+
for (const win of windows){
|
|
172
|
+
win.postMessage(messageData);
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
// See https://github.com/GoogleChrome/workbox/issues/2895
|
|
176
|
+
if (options.event instanceof FetchEvent) {
|
|
177
|
+
const client = await self.clients.get(options.event.clientId);
|
|
178
|
+
client?.postMessage(messageData);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* This plugin will automatically broadcast a message whenever a cached response
|
|
187
|
+
* is updated.
|
|
188
|
+
*/ class BroadcastUpdatePlugin {
|
|
189
|
+
_broadcastUpdate;
|
|
190
|
+
/**
|
|
191
|
+
* Construct a `@serwist/broadcast-update.BroadcastCacheUpdate` instance with
|
|
192
|
+
* the passed options and calls its `notifyIfUpdated` method whenever the
|
|
193
|
+
* plugin's `cacheDidUpdate` callback is invoked.
|
|
194
|
+
*
|
|
195
|
+
* @param options
|
|
196
|
+
*/ constructor(options){
|
|
197
|
+
this._broadcastUpdate = new BroadcastCacheUpdate(options);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* A "lifecycle" callback that will be triggered automatically by
|
|
201
|
+
* `@serwist/build.RuntimeCaching` handlers when an entry is
|
|
202
|
+
* added to a cache.
|
|
203
|
+
*
|
|
204
|
+
* @private
|
|
205
|
+
* @param options The input object to this function.
|
|
206
|
+
*/ cacheDidUpdate = async (options)=>{
|
|
207
|
+
internal.dontWaitFor(this._broadcastUpdate.notifyIfUpdated(options));
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
exports.BroadcastCacheUpdate = BroadcastCacheUpdate;
|
|
212
|
+
exports.BroadcastUpdatePlugin = BroadcastUpdatePlugin;
|
|
213
|
+
exports.responsesAreSame = responsesAreSame;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Given two `Response's`, compares several header values to see if they are
|
|
3
|
+
* the same or not.
|
|
4
|
+
*
|
|
5
|
+
* @param firstResponse
|
|
6
|
+
* @param secondResponse
|
|
7
|
+
* @param headersToCheck
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
declare const responsesAreSame: (firstResponse: Response, secondResponse: Response, headersToCheck: string[]) => boolean;
|
|
11
|
+
export { responsesAreSame };
|
|
12
|
+
//# sourceMappingURL=responsesAreSame.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responsesAreSame.d.ts","sourceRoot":"","sources":["../src/responsesAreSame.ts"],"names":[],"mappings":"AAUA;;;;;;;;GAQG;AACH,QAAA,MAAM,gBAAgB,kBAAmB,QAAQ,kBAAkB,QAAQ,kBAAkB,MAAM,EAAE,KAAG,OA4BvG,CAAC;AAEF,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const CACHE_UPDATED_MESSAGE_TYPE = "CACHE_UPDATED";
|
|
2
|
+
export declare const CACHE_UPDATED_MESSAGE_META = "serwist-broadcast-update";
|
|
3
|
+
export declare const NOTIFY_ALL_CLIENTS = true;
|
|
4
|
+
export declare const DEFAULT_HEADERS_TO_CHECK: string[];
|
|
5
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/utils/constants.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,0BAA0B,kBAAkB,CAAC;AAC1D,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AACrE,eAAO,MAAM,kBAAkB,OAAO,CAAC;AACvC,eAAO,MAAM,wBAAwB,EAAE,MAAM,EAAgD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@serwist/broadcast-update",
|
|
3
|
+
"version": "8.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A service worker helper library that uses the Broadcast Channel API to announce when a cached response has updated",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"!dist/**/dts"
|
|
9
|
+
],
|
|
10
|
+
"keywords": [
|
|
11
|
+
"serwist",
|
|
12
|
+
"serwistjs",
|
|
13
|
+
"service worker",
|
|
14
|
+
"sw",
|
|
15
|
+
"serwist-plugin"
|
|
16
|
+
],
|
|
17
|
+
"author": "Google's Web DevRel Team, Serwist's Team",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": "serwist/serwist",
|
|
20
|
+
"bugs": "https://github.com/serwist/serwist/issues",
|
|
21
|
+
"homepage": "https://ducanh-next-pwa.vercel.app",
|
|
22
|
+
"module": "./dist/index.js",
|
|
23
|
+
"main": "./dist/index.old.cjs",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"import": "./dist/index.js",
|
|
28
|
+
"require": "./dist/index.old.cjs",
|
|
29
|
+
"types": "./dist/index.d.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@serwist/core": "8.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"rollup": "3.28.1",
|
|
37
|
+
"@serwist/constants": "8.0.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "rimraf dist && cross-env NODE_ENV=production rollup --config rollup.config.js",
|
|
41
|
+
"lint": "eslint src --ext ts,tsx,js,jsx,cjs,mjs",
|
|
42
|
+
"typecheck": "tsc"
|
|
43
|
+
}
|
|
44
|
+
}
|