workbox-broadcast-update.dev.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. this.workbox = this.workbox || {};
  2. this.workbox.broadcastUpdate = (function (exports, assert_mjs, getFriendlyURL_mjs, logger_mjs, Deferred_mjs, WorkboxError_mjs) {
  3. 'use strict';
  4. try {
  5. self['workbox:broadcast-update:4.3.1'] && _();
  6. } catch (e) {} // eslint-disable-line
  7. /*
  8. Copyright 2018 Google LLC
  9. Use of this source code is governed by an MIT-style
  10. license that can be found in the LICENSE file or at
  11. https://opensource.org/licenses/MIT.
  12. */
  13. /**
  14. * Given two `Response's`, compares several header values to see if they are
  15. * the same or not.
  16. *
  17. * @param {Response} firstResponse
  18. * @param {Response} secondResponse
  19. * @param {Array<string>} headersToCheck
  20. * @return {boolean}
  21. *
  22. * @memberof workbox.broadcastUpdate
  23. * @private
  24. */
  25. const responsesAreSame = (firstResponse, secondResponse, headersToCheck) => {
  26. {
  27. if (!(firstResponse instanceof Response && secondResponse instanceof Response)) {
  28. throw new WorkboxError_mjs.WorkboxError('invalid-responses-are-same-args');
  29. }
  30. }
  31. const atLeastOneHeaderAvailable = headersToCheck.some(header => {
  32. return firstResponse.headers.has(header) && secondResponse.headers.has(header);
  33. });
  34. if (!atLeastOneHeaderAvailable) {
  35. {
  36. logger_mjs.logger.warn(`Unable to determine where the response has been updated ` + `because none of the headers that would be checked are present.`);
  37. logger_mjs.logger.debug(`Attempting to compare the following: `, firstResponse, secondResponse, headersToCheck);
  38. } // Just return true, indicating the that responses are the same, since we
  39. // can't determine otherwise.
  40. return true;
  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. return headerStateComparison && headerValueComparison;
  46. });
  47. };
  48. /*
  49. Copyright 2018 Google LLC
  50. Use of this source code is governed by an MIT-style
  51. license that can be found in the LICENSE file or at
  52. https://opensource.org/licenses/MIT.
  53. */
  54. const CACHE_UPDATED_MESSAGE_TYPE = 'CACHE_UPDATED';
  55. const CACHE_UPDATED_MESSAGE_META = 'workbox-broadcast-update';
  56. const DEFAULT_BROADCAST_CHANNEL_NAME = 'workbox';
  57. const DEFAULT_DEFER_NOTIFICATION_TIMEOUT = 10000;
  58. const DEFAULT_HEADERS_TO_CHECK = ['content-length', 'etag', 'last-modified'];
  59. /*
  60. Copyright 2018 Google LLC
  61. Use of this source code is governed by an MIT-style
  62. license that can be found in the LICENSE file or at
  63. https://opensource.org/licenses/MIT.
  64. */
  65. /**
  66. * You would not normally call this method directly; it's called automatically
  67. * by an instance of the {@link BroadcastCacheUpdate} class. It's exposed here
  68. * for the benefit of developers who would rather not use the full
  69. * `BroadcastCacheUpdate` implementation.
  70. *
  71. * Calling this will dispatch a message on the provided
  72. * {@link https://developers.google.com/web/updates/2016/09/broadcastchannel|Broadcast Channel}
  73. * to notify interested subscribers about a change to a cached resource.
  74. *
  75. * The message that's posted has a formation inspired by the
  76. * [Flux standard action](https://github.com/acdlite/flux-standard-action#introduction)
  77. * format like so:
  78. *
  79. * ```
  80. * {
  81. * type: 'CACHE_UPDATED',
  82. * meta: 'workbox-broadcast-update',
  83. * payload: {
  84. * cacheName: 'the-cache-name',
  85. * updatedURL: 'https://example.com/'
  86. * }
  87. * }
  88. * ```
  89. *
  90. * (Usage of [Flux](https://facebook.github.io/flux/) itself is not at
  91. * all required.)
  92. *
  93. * @param {Object} options
  94. * @param {string} options.cacheName The name of the cache in which the updated
  95. * `Response` was stored.
  96. * @param {string} options.url The URL associated with the updated `Response`.
  97. * @param {BroadcastChannel} [options.channel] The `BroadcastChannel` to use.
  98. * If no channel is set or the browser doesn't support the BroadcastChannel
  99. * api, then an attempt will be made to `postMessage` each window client.
  100. *
  101. * @memberof workbox.broadcastUpdate
  102. */
  103. const broadcastUpdate = async ({
  104. channel,
  105. cacheName,
  106. url
  107. }) => {
  108. {
  109. assert_mjs.assert.isType(cacheName, 'string', {
  110. moduleName: 'workbox-broadcast-update',
  111. className: '~',
  112. funcName: 'broadcastUpdate',
  113. paramName: 'cacheName'
  114. });
  115. assert_mjs.assert.isType(url, 'string', {
  116. moduleName: 'workbox-broadcast-update',
  117. className: '~',
  118. funcName: 'broadcastUpdate',
  119. paramName: 'url'
  120. });
  121. }
  122. const data = {
  123. type: CACHE_UPDATED_MESSAGE_TYPE,
  124. meta: CACHE_UPDATED_MESSAGE_META,
  125. payload: {
  126. cacheName: cacheName,
  127. updatedURL: url
  128. }
  129. };
  130. if (channel) {
  131. channel.postMessage(data);
  132. } else {
  133. const windows = await clients.matchAll({
  134. type: 'window'
  135. });
  136. for (const win of windows) {
  137. win.postMessage(data);
  138. }
  139. }
  140. };
  141. /*
  142. Copyright 2018 Google LLC
  143. Use of this source code is governed by an MIT-style
  144. license that can be found in the LICENSE file or at
  145. https://opensource.org/licenses/MIT.
  146. */
  147. /**
  148. * Uses the [Broadcast Channel API]{@link https://developers.google.com/web/updates/2016/09/broadcastchannel}
  149. * to notify interested parties when a cached response has been updated.
  150. * In browsers that do not support the Broadcast Channel API, the instance
  151. * falls back to sending the update via `postMessage()` to all window clients.
  152. *
  153. * For efficiency's sake, the underlying response bodies are not compared;
  154. * only specific response headers are checked.
  155. *
  156. * @memberof workbox.broadcastUpdate
  157. */
  158. class BroadcastCacheUpdate {
  159. /**
  160. * Construct a BroadcastCacheUpdate instance with a specific `channelName` to
  161. * broadcast messages on
  162. *
  163. * @param {Object} options
  164. * @param {Array<string>}
  165. * [options.headersToCheck=['content-length', 'etag', 'last-modified']]
  166. * A list of headers that will be used to determine whether the responses
  167. * differ.
  168. * @param {string} [options.channelName='workbox'] The name that will be used
  169. *. when creating the `BroadcastChannel`, which defaults to 'workbox' (the
  170. * channel name used by the `workbox-window` package).
  171. * @param {string} [options.deferNoticationTimeout=10000] The amount of time
  172. * to wait for a ready message from the window on navigation requests
  173. * before sending the update.
  174. */
  175. constructor({
  176. headersToCheck,
  177. channelName,
  178. deferNoticationTimeout
  179. } = {}) {
  180. this._headersToCheck = headersToCheck || DEFAULT_HEADERS_TO_CHECK;
  181. this._channelName = channelName || DEFAULT_BROADCAST_CHANNEL_NAME;
  182. this._deferNoticationTimeout = deferNoticationTimeout || DEFAULT_DEFER_NOTIFICATION_TIMEOUT;
  183. {
  184. assert_mjs.assert.isType(this._channelName, 'string', {
  185. moduleName: 'workbox-broadcast-update',
  186. className: 'BroadcastCacheUpdate',
  187. funcName: 'constructor',
  188. paramName: 'channelName'
  189. });
  190. assert_mjs.assert.isArray(this._headersToCheck, {
  191. moduleName: 'workbox-broadcast-update',
  192. className: 'BroadcastCacheUpdate',
  193. funcName: 'constructor',
  194. paramName: 'headersToCheck'
  195. });
  196. }
  197. this._initWindowReadyDeferreds();
  198. }
  199. /**
  200. * Compare two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
  201. * and send a message via the
  202. * {@link https://developers.google.com/web/updates/2016/09/broadcastchannel|Broadcast Channel API}
  203. * if they differ.
  204. *
  205. * Neither of the Responses can be {@link http://stackoverflow.com/questions/39109789|opaque}.
  206. *
  207. * @param {Object} options
  208. * @param {Response} options.oldResponse Cached response to compare.
  209. * @param {Response} options.newResponse Possibly updated response to compare.
  210. * @param {string} options.url The URL of the request.
  211. * @param {string} options.cacheName Name of the cache the responses belong
  212. * to. This is included in the broadcast message.
  213. * @param {Event} [options.event] event An optional event that triggered
  214. * this possible cache update.
  215. * @return {Promise} Resolves once the update is sent.
  216. */
  217. notifyIfUpdated({
  218. oldResponse,
  219. newResponse,
  220. url,
  221. cacheName,
  222. event
  223. }) {
  224. if (!responsesAreSame(oldResponse, newResponse, this._headersToCheck)) {
  225. {
  226. logger_mjs.logger.log(`Newer response found (and cached) for:`, url);
  227. }
  228. const sendUpdate = async () => {
  229. // In the case of a navigation request, the requesting page will likely
  230. // not have loaded its JavaScript in time to recevied the update
  231. // notification, so we defer it until ready (or we timeout waiting).
  232. if (event && event.request && event.request.mode === 'navigate') {
  233. {
  234. logger_mjs.logger.debug(`Original request was a navigation request, ` + `waiting for a ready message from the window`, event.request);
  235. }
  236. await this._windowReadyOrTimeout(event);
  237. }
  238. await this._broadcastUpdate({
  239. channel: this._getChannel(),
  240. cacheName,
  241. url
  242. });
  243. }; // Send the update and ensure the SW stays alive until it's sent.
  244. const done = sendUpdate();
  245. if (event) {
  246. try {
  247. event.waitUntil(done);
  248. } catch (error) {
  249. {
  250. logger_mjs.logger.warn(`Unable to ensure service worker stays alive ` + `when broadcasting cache update for ` + `${getFriendlyURL_mjs.getFriendlyURL(event.request.url)}'.`);
  251. }
  252. }
  253. }
  254. return done;
  255. }
  256. }
  257. /**
  258. * NOTE: this is exposed on the instance primarily so it can be spied on
  259. * in tests.
  260. *
  261. * @param {Object} opts
  262. * @private
  263. */
  264. async _broadcastUpdate(opts) {
  265. await broadcastUpdate(opts);
  266. }
  267. /**
  268. * @return {BroadcastChannel|undefined} The BroadcastChannel instance used for
  269. * broadcasting updates, or undefined if the browser doesn't support the
  270. * Broadcast Channel API.
  271. *
  272. * @private
  273. */
  274. _getChannel() {
  275. if ('BroadcastChannel' in self && !this._channel) {
  276. this._channel = new BroadcastChannel(this._channelName);
  277. }
  278. return this._channel;
  279. }
  280. /**
  281. * Waits for a message from the window indicating that it's capable of
  282. * receiving broadcasts. By default, this will only wait for the amount of
  283. * time specified via the `deferNoticationTimeout` option.
  284. *
  285. * @param {Event} event The navigation fetch event.
  286. * @return {Promise}
  287. * @private
  288. */
  289. _windowReadyOrTimeout(event) {
  290. if (!this._navigationEventsDeferreds.has(event)) {
  291. const deferred = new Deferred_mjs.Deferred(); // Set the deferred on the `_navigationEventsDeferreds` map so it will
  292. // be resolved when the next ready message event comes.
  293. this._navigationEventsDeferreds.set(event, deferred); // But don't wait too long for the message since it may never come.
  294. const timeout = setTimeout(() => {
  295. {
  296. logger_mjs.logger.debug(`Timed out after ${this._deferNoticationTimeout}` + `ms waiting for message from window`);
  297. }
  298. deferred.resolve();
  299. }, this._deferNoticationTimeout); // Ensure the timeout is cleared if the deferred promise is resolved.
  300. deferred.promise.then(() => clearTimeout(timeout));
  301. }
  302. return this._navigationEventsDeferreds.get(event).promise;
  303. }
  304. /**
  305. * Creates a mapping between navigation fetch events and deferreds, and adds
  306. * a listener for message events from the window. When message events arrive,
  307. * all deferreds in the mapping are resolved.
  308. *
  309. * Note: it would be easier if we could only resolve the deferred of
  310. * navigation fetch event whose client ID matched the source ID of the
  311. * message event, but currently client IDs are not exposed on navigation
  312. * fetch events: https://www.chromestatus.com/feature/4846038800138240
  313. *
  314. * @private
  315. */
  316. _initWindowReadyDeferreds() {
  317. // A mapping between navigation events and their deferreds.
  318. this._navigationEventsDeferreds = new Map(); // The message listener needs to be added in the initial run of the
  319. // service worker, but since we don't actually need to be listening for
  320. // messages until the cache updates, we only invoke the callback if set.
  321. self.addEventListener('message', event => {
  322. if (event.data.type === 'WINDOW_READY' && event.data.meta === 'workbox-window' && this._navigationEventsDeferreds.size > 0) {
  323. {
  324. logger_mjs.logger.debug(`Received WINDOW_READY event: `, event);
  325. } // Resolve any pending deferreds.
  326. for (const deferred of this._navigationEventsDeferreds.values()) {
  327. deferred.resolve();
  328. }
  329. this._navigationEventsDeferreds.clear();
  330. }
  331. });
  332. }
  333. }
  334. /*
  335. Copyright 2018 Google LLC
  336. Use of this source code is governed by an MIT-style
  337. license that can be found in the LICENSE file or at
  338. https://opensource.org/licenses/MIT.
  339. */
  340. /**
  341. * This plugin will automatically broadcast a message whenever a cached response
  342. * is updated.
  343. *
  344. * @memberof workbox.broadcastUpdate
  345. */
  346. class Plugin {
  347. /**
  348. * Construct a BroadcastCacheUpdate instance with the passed options and
  349. * calls its `notifyIfUpdated()` method whenever the plugin's
  350. * `cacheDidUpdate` callback is invoked.
  351. *
  352. * @param {Object} options
  353. * @param {Array<string>}
  354. * [options.headersToCheck=['content-length', 'etag', 'last-modified']]
  355. * A list of headers that will be used to determine whether the responses
  356. * differ.
  357. * @param {string} [options.channelName='workbox'] The name that will be used
  358. *. when creating the `BroadcastChannel`, which defaults to 'workbox' (the
  359. * channel name used by the `workbox-window` package).
  360. * @param {string} [options.deferNoticationTimeout=10000] The amount of time
  361. * to wait for a ready message from the window on navigation requests
  362. * before sending the update.
  363. */
  364. constructor(options) {
  365. this._broadcastUpdate = new BroadcastCacheUpdate(options);
  366. }
  367. /**
  368. * A "lifecycle" callback that will be triggered automatically by the
  369. * `workbox-sw` and `workbox-runtime-caching` handlers when an entry is
  370. * added to a cache.
  371. *
  372. * @private
  373. * @param {Object} options The input object to this function.
  374. * @param {string} options.cacheName Name of the cache being updated.
  375. * @param {Response} [options.oldResponse] The previous cached value, if any.
  376. * @param {Response} options.newResponse The new value in the cache.
  377. * @param {Request} options.request The request that triggered the udpate.
  378. * @param {Request} [options.event] The event that triggered the update.
  379. */
  380. cacheDidUpdate({
  381. cacheName,
  382. oldResponse,
  383. newResponse,
  384. request,
  385. event
  386. }) {
  387. {
  388. assert_mjs.assert.isType(cacheName, 'string', {
  389. moduleName: 'workbox-broadcast-update',
  390. className: 'Plugin',
  391. funcName: 'cacheDidUpdate',
  392. paramName: 'cacheName'
  393. });
  394. assert_mjs.assert.isInstance(newResponse, Response, {
  395. moduleName: 'workbox-broadcast-update',
  396. className: 'Plugin',
  397. funcName: 'cacheDidUpdate',
  398. paramName: 'newResponse'
  399. });
  400. assert_mjs.assert.isInstance(request, Request, {
  401. moduleName: 'workbox-broadcast-update',
  402. className: 'Plugin',
  403. funcName: 'cacheDidUpdate',
  404. paramName: 'request'
  405. });
  406. }
  407. if (!oldResponse) {
  408. // Without a two responses there is nothing to compare.
  409. return;
  410. }
  411. this._broadcastUpdate.notifyIfUpdated({
  412. cacheName,
  413. oldResponse,
  414. newResponse,
  415. event,
  416. url: request.url
  417. });
  418. }
  419. }
  420. /*
  421. Copyright 2018 Google LLC
  422. Use of this source code is governed by an MIT-style
  423. license that can be found in the LICENSE file or at
  424. https://opensource.org/licenses/MIT.
  425. */
  426. exports.BroadcastCacheUpdate = BroadcastCacheUpdate;
  427. exports.Plugin = Plugin;
  428. exports.broadcastUpdate = broadcastUpdate;
  429. exports.responsesAreSame = responsesAreSame;
  430. return exports;
  431. }({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
  432. //# sourceMappingURL=workbox-broadcast-update.dev.js.map