@serwist/broadcast-update 8.4.3 → 9.0.0-preview.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.
@@ -68,3 +68,4 @@ declare class BroadcastCacheUpdate {
68
68
  notifyIfUpdated(options: CacheDidUpdateCallbackParam): Promise<void>;
69
69
  }
70
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"}
@@ -25,3 +25,4 @@ declare class BroadcastUpdatePlugin implements SerwistPlugin {
25
25
  cacheDidUpdate: SerwistPlugin["cacheDidUpdate"];
26
26
  }
27
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 CHANGED
@@ -7,3 +7,4 @@ import { responsesAreSame } from "./responsesAreSame.js";
7
7
  */
8
8
  export { BroadcastCacheUpdate, BroadcastUpdatePlugin, responsesAreSame };
9
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"}
@@ -9,3 +9,4 @@
9
9
  */
10
10
  declare const responsesAreSame: (firstResponse: Response, secondResponse: Response, headersToCheck: string[]) => boolean;
11
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"}
@@ -2,3 +2,4 @@ export declare const CACHE_UPDATED_MESSAGE_TYPE = "CACHE_UPDATED";
2
2
  export declare const CACHE_UPDATED_MESSAGE_META = "serwist-broadcast-update";
3
3
  export declare const NOTIFY_ALL_CLIENTS = true;
4
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 CHANGED
@@ -1,45 +1,48 @@
1
1
  {
2
2
  "name": "@serwist/broadcast-update",
3
- "version": "8.4.3",
3
+ "version": "9.0.0-preview.0",
4
4
  "type": "module",
5
5
  "description": "A service worker helper library that uses the Broadcast Channel API to announce when a cached response has updated",
6
6
  "files": [
7
- "dist",
8
- "!dist/**/dts"
7
+ "src",
8
+ "dist"
9
9
  ],
10
10
  "keywords": [
11
11
  "serwist",
12
12
  "serwistjs",
13
13
  "service worker",
14
14
  "sw",
15
- "serwist-plugin"
15
+ "serwist-plugin",
16
+ "broadcastchannel"
16
17
  ],
17
18
  "author": "Google's Web DevRel Team, Serwist's Team",
18
19
  "license": "MIT",
19
20
  "repository": "serwist/serwist",
20
21
  "bugs": "https://github.com/serwist/serwist/issues",
21
22
  "homepage": "https://serwist.pages.dev",
22
- "module": "./dist/index.js",
23
- "main": "./dist/index.cjs",
23
+ "main": "./dist/index.js",
24
24
  "types": "./dist/index.d.ts",
25
25
  "exports": {
26
26
  ".": {
27
- "import": {
28
- "types": "./dist/index.d.ts",
29
- "default": "./dist/index.js"
30
- },
31
- "require": {
32
- "types": "./dist/index.d.cts",
33
- "default": "./dist/index.cjs"
34
- }
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
35
29
  }
36
30
  },
37
31
  "dependencies": {
38
- "@serwist/core": "8.4.3"
32
+ "@serwist/core": "9.0.0-preview.0"
39
33
  },
40
34
  "devDependencies": {
41
- "rollup": "4.9.1",
42
- "@serwist/constants": "8.4.3"
35
+ "rollup": "4.9.6",
36
+ "typescript": "5.4.0-dev.20240203",
37
+ "@serwist/constants": "9.0.0-preview.0"
38
+ },
39
+ "peerDependencies": {
40
+ "typescript": ">=5.0.0"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "typescript": {
44
+ "optional": true
45
+ }
43
46
  },
44
47
  "scripts": {
45
48
  "build": "rimraf dist && cross-env NODE_ENV=production rollup --config rollup.config.js",
@@ -0,0 +1,188 @@
1
+ /*
2
+ Copyright 2018 Google LLC
3
+
4
+ Use of this source code is governed by an MIT-style
5
+ license that can be found in the LICENSE file or at
6
+ https://opensource.org/licenses/MIT.
7
+ */
8
+
9
+ import type { CacheDidUpdateCallbackParam } from "@serwist/core";
10
+ import { assert, logger, resultingClientExists, timeout } from "@serwist/core/internal";
11
+
12
+ import { responsesAreSame } from "./responsesAreSame.js";
13
+ import { CACHE_UPDATED_MESSAGE_META, CACHE_UPDATED_MESSAGE_TYPE, DEFAULT_HEADERS_TO_CHECK, NOTIFY_ALL_CLIENTS } from "./utils/constants.js";
14
+
15
+ // UA-sniff Safari: https://stackoverflow.com/questions/7944460/detect-safari-browser
16
+ // TODO(philipwalton): remove once this Safari bug fix has been released.
17
+ // https://bugs.webkit.org/show_bug.cgi?id=201169
18
+ const isSafari = typeof navigator !== "undefined" && /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
19
+
20
+ // Give TypeScript the correct global.
21
+ declare let self: ServiceWorkerGlobalScope;
22
+
23
+ export interface BroadcastCacheUpdateOptions {
24
+ /**
25
+ * A list of headers that will be used to determine whether the responses
26
+ * differ.
27
+ *
28
+ * @default ['content-length', 'etag', 'last-modified']
29
+ */
30
+ headersToCheck?: string[];
31
+ /**
32
+ * A function whose return value
33
+ * will be used as the `payload` field in any cache update messages sent
34
+ * to the window clients.
35
+ * @param options
36
+ * @returns
37
+ */
38
+ generatePayload?: (options: CacheDidUpdateCallbackParam) => Record<string, any>;
39
+ /**
40
+ * If true (the default) then all open clients will receive a message. If false,
41
+ * then only the client that make the original request will be notified of the update.
42
+ *
43
+ * @default true
44
+ */
45
+ notifyAllClients?: boolean;
46
+ }
47
+
48
+ /**
49
+ * Generates the default payload used in update messages. By default the
50
+ * payload includes the `cacheName` and `updatedURL` fields.
51
+ *
52
+ * @returns
53
+ * @private
54
+ */
55
+ function defaultPayloadGenerator(data: CacheDidUpdateCallbackParam): Record<string, any> {
56
+ return {
57
+ cacheName: data.cacheName,
58
+ updatedURL: data.request.url,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Uses the `postMessage()` API to inform any open windows/tabs when a cached
64
+ * response has been updated.
65
+ *
66
+ * For efficiency's sake, the underlying response bodies are not compared;
67
+ * only specific response headers are checked.
68
+ */
69
+ class BroadcastCacheUpdate {
70
+ private readonly _headersToCheck: string[];
71
+ private readonly _generatePayload: (options: CacheDidUpdateCallbackParam) => Record<string, any>;
72
+ private readonly _notifyAllClients: boolean;
73
+
74
+ /**
75
+ * Construct a BroadcastCacheUpdate instance with a specific `channelName` to
76
+ * broadcast messages on
77
+ *
78
+ * @param options
79
+ */
80
+ constructor({ generatePayload, headersToCheck, notifyAllClients }: BroadcastCacheUpdateOptions = {}) {
81
+ this._headersToCheck = headersToCheck || DEFAULT_HEADERS_TO_CHECK;
82
+ this._generatePayload = generatePayload || defaultPayloadGenerator;
83
+ this._notifyAllClients = notifyAllClients ?? NOTIFY_ALL_CLIENTS;
84
+ }
85
+
86
+ /**
87
+ * Compares two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
88
+ * and sends a message (via `postMessage()`) to all window clients if the
89
+ * responses differ. Neither of the Responses can be
90
+ * [opaque](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses).
91
+ *
92
+ * The message that's posted has the following format (where `payload` can
93
+ * be customized via the `generatePayload` option the instance is created
94
+ * with):
95
+ *
96
+ * ```
97
+ * {
98
+ * type: 'CACHE_UPDATED',
99
+ * meta: 'workbox-broadcast-update',
100
+ * payload: {
101
+ * cacheName: 'the-cache-name',
102
+ * updatedURL: 'https://example.com/'
103
+ * }
104
+ * }
105
+ * ```
106
+ *
107
+ * @param options
108
+ * @returns Resolves once the update is sent.
109
+ */
110
+ async notifyIfUpdated(options: CacheDidUpdateCallbackParam): Promise<void> {
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
+
132
+ // Without two responses there is nothing to compare.
133
+ if (!options.oldResponse) {
134
+ return;
135
+ }
136
+
137
+ if (!responsesAreSame(options.oldResponse, options.newResponse, this._headersToCheck)) {
138
+ if (process.env.NODE_ENV !== "production") {
139
+ logger.log("Newer response found (and cached) for:", options.request.url);
140
+ }
141
+
142
+ const messageData = {
143
+ type: CACHE_UPDATED_MESSAGE_TYPE,
144
+ meta: CACHE_UPDATED_MESSAGE_META,
145
+ payload: this._generatePayload(options),
146
+ };
147
+
148
+ // For navigation requests, wait until the new window client exists
149
+ // before sending the message
150
+ if (options.request.mode === "navigate") {
151
+ let resultingClientId: string | undefined;
152
+ if (options.event instanceof FetchEvent) {
153
+ resultingClientId = options.event.resultingClientId;
154
+ }
155
+
156
+ const resultingWin = await resultingClientExists(resultingClientId);
157
+
158
+ // Safari does not currently implement postMessage buffering and
159
+ // there's no good way to feature detect that, so to increase the
160
+ // chances of the message being delivered in Safari, we add a timeout.
161
+ // We also do this if `resultingClientExists()` didn't return a client,
162
+ // which means it timed out, so it's worth waiting a bit longer.
163
+ if (!resultingWin || isSafari) {
164
+ // 3500 is chosen because (according to CrUX data) 80% of mobile
165
+ // websites hit the DOMContentLoaded event in less than 3.5 seconds.
166
+ // And presumably sites implementing service worker are on the
167
+ // higher end of the performance spectrum.
168
+ await timeout(3500);
169
+ }
170
+ }
171
+
172
+ if (this._notifyAllClients) {
173
+ const windows = await self.clients.matchAll({ type: "window" });
174
+ for (const win of windows) {
175
+ win.postMessage(messageData);
176
+ }
177
+ } else {
178
+ // See https://github.com/GoogleChrome/workbox/issues/2895
179
+ if (options.event instanceof FetchEvent) {
180
+ const client = await self.clients.get(options.event.clientId);
181
+ client?.postMessage(messageData);
182
+ }
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ export { BroadcastCacheUpdate };
@@ -0,0 +1,46 @@
1
+ /*
2
+ Copyright 2018 Google LLC
3
+
4
+ Use of this source code is governed by an MIT-style
5
+ license that can be found in the LICENSE file or at
6
+ https://opensource.org/licenses/MIT.
7
+ */
8
+
9
+ import type { SerwistPlugin } from "@serwist/core";
10
+ import { dontWaitFor } from "@serwist/core/internal";
11
+
12
+ import type { BroadcastCacheUpdateOptions } from "./BroadcastCacheUpdate.js";
13
+ import { BroadcastCacheUpdate } from "./BroadcastCacheUpdate.js";
14
+
15
+ /**
16
+ * This plugin will automatically broadcast a message whenever a cached response
17
+ * is updated.
18
+ */
19
+ class BroadcastUpdatePlugin implements SerwistPlugin {
20
+ private readonly _broadcastUpdate: BroadcastCacheUpdate;
21
+
22
+ /**
23
+ * Construct a `@serwist/broadcast-update.BroadcastCacheUpdate` instance with
24
+ * the passed options and calls its `notifyIfUpdated` method whenever the
25
+ * plugin's `cacheDidUpdate` callback is invoked.
26
+ *
27
+ * @param options
28
+ */
29
+ constructor(options?: BroadcastCacheUpdateOptions) {
30
+ this._broadcastUpdate = new BroadcastCacheUpdate(options);
31
+ }
32
+
33
+ /**
34
+ * A "lifecycle" callback that will be triggered automatically by
35
+ * `@serwist/build.RuntimeCaching` handlers when an entry is
36
+ * added to a cache.
37
+ *
38
+ * @private
39
+ * @param options The input object to this function.
40
+ */
41
+ cacheDidUpdate: SerwistPlugin["cacheDidUpdate"] = async (options) => {
42
+ dontWaitFor(this._broadcastUpdate.notifyIfUpdated(options));
43
+ };
44
+ }
45
+
46
+ export { BroadcastUpdatePlugin };
@@ -1,9 +1,20 @@
1
+ /*
2
+ Copyright 2018 Google LLC
3
+
4
+ Use of this source code is governed by an MIT-style
5
+ license that can be found in the LICENSE file or at
6
+ https://opensource.org/licenses/MIT.
7
+ */
8
+
1
9
  import type { BroadcastCacheUpdateOptions } from "./BroadcastCacheUpdate.js";
2
10
  import { BroadcastCacheUpdate } from "./BroadcastCacheUpdate.js";
3
11
  import { BroadcastUpdatePlugin } from "./BroadcastUpdatePlugin.js";
4
12
  import { responsesAreSame } from "./responsesAreSame.js";
13
+
5
14
  /**
6
15
  * @module workbox-broadcast-update
7
16
  */
17
+
8
18
  export { BroadcastCacheUpdate, BroadcastUpdatePlugin, responsesAreSame };
19
+
9
20
  export type { BroadcastCacheUpdateOptions };
@@ -0,0 +1,50 @@
1
+ /*
2
+ Copyright 2018 Google LLC
3
+
4
+ Use of this source code is governed by an MIT-style
5
+ license that can be found in the LICENSE file or at
6
+ https://opensource.org/licenses/MIT.
7
+ */
8
+
9
+ import { SerwistError, logger } from "@serwist/core/internal";
10
+
11
+ /**
12
+ * Given two `Response's`, compares several header values to see if they are
13
+ * the same or not.
14
+ *
15
+ * @param firstResponse
16
+ * @param secondResponse
17
+ * @param headersToCheck
18
+ * @returns
19
+ */
20
+ const responsesAreSame = (firstResponse: Response, secondResponse: Response, headersToCheck: string[]): boolean => {
21
+ if (process.env.NODE_ENV !== "production") {
22
+ if (!(firstResponse instanceof Response && secondResponse instanceof Response)) {
23
+ throw new SerwistError("invalid-responses-are-same-args");
24
+ }
25
+ }
26
+
27
+ const atLeastOneHeaderAvailable = headersToCheck.some((header) => {
28
+ return firstResponse.headers.has(header) && secondResponse.headers.has(header);
29
+ });
30
+
31
+ if (!atLeastOneHeaderAvailable) {
32
+ if (process.env.NODE_ENV !== "production") {
33
+ logger.warn("Unable to determine where the response has been updated " + "because none of the headers that would be checked are present.");
34
+ logger.debug("Attempting to compare the following: ", firstResponse, secondResponse, headersToCheck);
35
+ }
36
+
37
+ // Just return true, indicating the that responses are the same, since we
38
+ // can't determine otherwise.
39
+ return true;
40
+ }
41
+
42
+ return headersToCheck.every((header) => {
43
+ const headerStateComparison = firstResponse.headers.has(header) === secondResponse.headers.has(header);
44
+ const headerValueComparison = firstResponse.headers.get(header) === secondResponse.headers.get(header);
45
+
46
+ return headerStateComparison && headerValueComparison;
47
+ });
48
+ };
49
+
50
+ export { responsesAreSame };
@@ -0,0 +1,12 @@
1
+ /*
2
+ Copyright 2018 Google LLC
3
+
4
+ Use of this source code is governed by an MIT-style
5
+ license that can be found in the LICENSE file or at
6
+ https://opensource.org/licenses/MIT.
7
+ */
8
+
9
+ export const CACHE_UPDATED_MESSAGE_TYPE = "CACHE_UPDATED";
10
+ export const CACHE_UPDATED_MESSAGE_META = "serwist-broadcast-update";
11
+ export const NOTIFY_ALL_CLIENTS = true;
12
+ export const DEFAULT_HEADERS_TO_CHECK: string[] = ["content-length", "etag", "last-modified"];
package/dist/index.cjs DELETED
@@ -1,213 +0,0 @@
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;