serwist 10.0.0-preview.7 → 10.0.0-preview.9

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.
Files changed (261) hide show
  1. package/dist/chunks/waitUntil.js +129 -129
  2. package/dist/index.d.ts +19 -21
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.internal.d.ts +2 -2
  5. package/dist/index.internal.d.ts.map +1 -1
  6. package/dist/index.internal.js +3 -3
  7. package/dist/index.js +2026 -1917
  8. package/dist/lib/{backgroundSync → background-sync}/BackgroundSyncPlugin.d.ts +1 -1
  9. package/dist/lib/background-sync/BackgroundSyncPlugin.d.ts.map +1 -0
  10. package/dist/lib/background-sync/BackgroundSyncQueue.d.ts.map +1 -0
  11. package/dist/lib/background-sync/BackgroundSyncQueueDb.d.ts.map +1 -0
  12. package/dist/lib/background-sync/BackgroundSyncQueueStore.d.ts.map +1 -0
  13. package/dist/lib/{backgroundSync → background-sync}/StorableRequest.d.ts +1 -1
  14. package/dist/lib/background-sync/StorableRequest.d.ts.map +1 -0
  15. package/dist/lib/background-sync/index.d.ts.map +1 -0
  16. package/dist/lib/{broadcastUpdate → broadcast-update}/BroadcastCacheUpdate.d.ts +1 -1
  17. package/dist/lib/broadcast-update/BroadcastCacheUpdate.d.ts.map +1 -0
  18. package/dist/lib/{broadcastUpdate → broadcast-update}/BroadcastUpdatePlugin.d.ts +1 -1
  19. package/dist/lib/broadcast-update/BroadcastUpdatePlugin.d.ts.map +1 -0
  20. package/dist/lib/broadcast-update/constants.d.ts.map +1 -0
  21. package/dist/lib/broadcast-update/index.d.ts.map +1 -0
  22. package/dist/lib/broadcast-update/responsesAreSame.d.ts.map +1 -0
  23. package/dist/lib/{broadcastUpdate → broadcast-update}/types.d.ts +1 -1
  24. package/dist/lib/broadcast-update/types.d.ts.map +1 -0
  25. package/dist/{cacheNames.d.ts → lib/cache-name.d.ts} +1 -1
  26. package/dist/lib/cache-name.d.ts.map +1 -0
  27. package/dist/lib/cacheable-response/CacheableResponse.d.ts.map +1 -0
  28. package/dist/lib/{cacheableResponse → cacheable-response}/CacheableResponsePlugin.d.ts +1 -1
  29. package/dist/lib/cacheable-response/CacheableResponsePlugin.d.ts.map +1 -0
  30. package/dist/lib/{cacheableResponse → cacheable-response}/index.d.ts +1 -1
  31. package/dist/lib/cacheable-response/index.d.ts.map +1 -0
  32. package/dist/{constants.d.ts → lib/constants.d.ts} +1 -1
  33. package/dist/lib/constants.d.ts.map +1 -0
  34. package/dist/lib/core.d.ts +62 -0
  35. package/dist/lib/core.d.ts.map +1 -0
  36. package/dist/lib/expiration/ExpirationPlugin.d.ts +1 -1
  37. package/dist/lib/expiration/ExpirationPlugin.d.ts.map +1 -1
  38. package/dist/lib/extension.d.ts +30 -0
  39. package/dist/lib/extension.d.ts.map +1 -0
  40. package/dist/lib/extensions/google-analytics/constants.d.ts.map +1 -0
  41. package/dist/lib/extensions/google-analytics/extension.d.ts +79 -0
  42. package/dist/lib/extensions/google-analytics/extension.d.ts.map +1 -0
  43. package/dist/lib/extensions/google-analytics/index.d.ts +2 -0
  44. package/dist/lib/extensions/google-analytics/index.d.ts.map +1 -0
  45. package/dist/lib/extensions/google-analytics/initialize.d.ts +12 -0
  46. package/dist/lib/extensions/google-analytics/initialize.d.ts.map +1 -0
  47. package/dist/lib/extensions/index.d.ts +14 -0
  48. package/dist/lib/extensions/index.d.ts.map +1 -0
  49. package/dist/lib/{controllers/PrecacheController/PrecacheController.d.ts → extensions/precache/extension.d.ts} +11 -34
  50. package/dist/lib/extensions/precache/extension.d.ts.map +1 -0
  51. package/dist/lib/extensions/precache/options.d.ts +26 -0
  52. package/dist/lib/extensions/precache/options.d.ts.map +1 -0
  53. package/dist/lib/{controllers/PrecacheController/PrecacheCacheKeyPlugin.d.ts → extensions/precache/plugin-cache-key.d.ts} +4 -4
  54. package/dist/lib/extensions/precache/plugin-cache-key.d.ts.map +1 -0
  55. package/dist/lib/{precaching/PrecacheFallbackPlugin.d.ts → extensions/precache/plugin-fallback.d.ts} +7 -12
  56. package/dist/lib/extensions/precache/plugin-fallback.d.ts.map +1 -0
  57. package/dist/lib/{controllers/PrecacheController/PrecacheInstallReportPlugin.d.ts → extensions/precache/plugin-install-report.d.ts} +2 -2
  58. package/dist/lib/extensions/precache/plugin-install-report.d.ts.map +1 -0
  59. package/dist/lib/extensions/precache/route.d.ts +42 -0
  60. package/dist/lib/extensions/precache/route.d.ts.map +1 -0
  61. package/dist/lib/{controllers/PrecacheController/PrecacheStrategy.d.ts → extensions/precache/strategy.d.ts} +2 -2
  62. package/dist/lib/extensions/precache/strategy.d.ts.map +1 -0
  63. package/dist/lib/{controllers/RuntimeCacheController.d.ts → extensions/runtime-cache.d.ts} +9 -8
  64. package/dist/lib/extensions/runtime-cache.d.ts.map +1 -0
  65. package/dist/lib/functions/handlers.d.ts +60 -0
  66. package/dist/lib/functions/handlers.d.ts.map +1 -0
  67. package/dist/lib/functions/router.d.ts +60 -0
  68. package/dist/lib/functions/router.d.ts.map +1 -0
  69. package/dist/{navigationPreload.d.ts → lib/navigation-preload.d.ts} +1 -1
  70. package/dist/lib/navigation-preload.d.ts.map +1 -0
  71. package/dist/lib/{rangeRequests → range-requests}/RangeRequestsPlugin.d.ts +1 -1
  72. package/dist/lib/range-requests/RangeRequestsPlugin.d.ts.map +1 -0
  73. package/dist/lib/range-requests/createPartialResponse.d.ts.map +1 -0
  74. package/dist/lib/{rangeRequests → range-requests}/index.d.ts +1 -1
  75. package/dist/lib/range-requests/index.d.ts.map +1 -0
  76. package/dist/lib/range-requests/utils/calculateEffectiveBoundaries.d.ts.map +1 -0
  77. package/dist/lib/range-requests/utils/parseRangeHeader.d.ts.map +1 -0
  78. package/dist/lib/route.d.ts +106 -0
  79. package/dist/lib/route.d.ts.map +1 -0
  80. package/dist/{Serwist.d.ts → lib/serwist.d.ts} +27 -40
  81. package/dist/lib/serwist.d.ts.map +1 -0
  82. package/dist/lib/strategies/NetworkFirst.d.ts.map +1 -1
  83. package/dist/lib/strategies/StaleWhileRevalidate.d.ts.map +1 -1
  84. package/dist/lib/strategies/Strategy.d.ts +1 -1
  85. package/dist/lib/strategies/Strategy.d.ts.map +1 -1
  86. package/dist/lib/strategies/StrategyHandler.d.ts +1 -1
  87. package/dist/lib/strategies/StrategyHandler.d.ts.map +1 -1
  88. package/dist/lib/strategies/plugins/cacheOkAndOpaquePlugin.d.ts +1 -1
  89. package/dist/lib/strategies/plugins/cacheOkAndOpaquePlugin.d.ts.map +1 -1
  90. package/dist/{types.d.ts → lib/types.d.ts} +15 -43
  91. package/dist/lib/types.d.ts.map +1 -0
  92. package/dist/{copyResponse.d.ts → lib/utils.d.ts} +22 -1
  93. package/dist/lib/utils.d.ts.map +1 -0
  94. package/dist/models/messages/messageGenerator.d.ts +1 -1
  95. package/dist/models/messages/messageGenerator.d.ts.map +1 -1
  96. package/dist/utils/SerwistError.d.ts +1 -1
  97. package/dist/utils/SerwistError.d.ts.map +1 -1
  98. package/dist/utils/assert.d.ts +1 -1
  99. package/dist/utils/assert.d.ts.map +1 -1
  100. package/dist/utils/cacheNames.d.ts.map +1 -1
  101. package/dist/utils/createCacheKey.d.ts +1 -1
  102. package/dist/utils/createCacheKey.d.ts.map +1 -1
  103. package/dist/utils/generateURLVariations.d.ts +1 -1
  104. package/dist/utils/generateURLVariations.d.ts.map +1 -1
  105. package/dist/utils/logger.d.ts +1 -1
  106. package/dist/utils/logger.d.ts.map +1 -1
  107. package/dist/utils/normalizeHandler.d.ts +1 -1
  108. package/dist/utils/normalizeHandler.d.ts.map +1 -1
  109. package/dist/utils/parseRoute.d.ts +3 -3
  110. package/dist/utils/parseRoute.d.ts.map +1 -1
  111. package/dist/utils/pluginUtils.d.ts +1 -1
  112. package/dist/utils/pluginUtils.d.ts.map +1 -1
  113. package/package.json +5 -5
  114. package/src/index.internal.ts +2 -2
  115. package/src/index.ts +51 -33
  116. package/src/lib/{backgroundSync → background-sync}/BackgroundSyncPlugin.ts +1 -1
  117. package/src/lib/{backgroundSync → background-sync}/BackgroundSyncQueue.ts +4 -4
  118. package/src/lib/{backgroundSync → background-sync}/BackgroundSyncQueueDb.ts +1 -1
  119. package/src/lib/{backgroundSync → background-sync}/BackgroundSyncQueueStore.ts +0 -2
  120. package/src/lib/{backgroundSync → background-sync}/StorableRequest.ts +1 -1
  121. package/src/lib/{broadcastUpdate → broadcast-update}/BroadcastCacheUpdate.ts +1 -1
  122. package/src/lib/{broadcastUpdate → broadcast-update}/BroadcastUpdatePlugin.ts +1 -1
  123. package/src/lib/{broadcastUpdate → broadcast-update}/types.ts +1 -1
  124. package/src/lib/{cacheableResponse → cacheable-response}/CacheableResponsePlugin.ts +1 -1
  125. package/src/lib/core.ts +128 -0
  126. package/src/lib/expiration/CacheExpiration.ts +1 -1
  127. package/src/lib/expiration/ExpirationPlugin.ts +4 -5
  128. package/src/lib/extension.ts +37 -0
  129. package/src/lib/{googleAnalytics/initializeGoogleAnalytics.ts → extensions/google-analytics/extension.ts} +40 -46
  130. package/src/lib/extensions/google-analytics/index.ts +0 -0
  131. package/src/lib/extensions/google-analytics/initialize.ts +48 -0
  132. package/src/lib/extensions/index.ts +13 -0
  133. package/src/lib/{controllers/PrecacheController/PrecacheController.ts → extensions/precache/extension.ts} +23 -45
  134. package/src/lib/{controllers/PrecacheController/parsePrecacheOptions.ts → extensions/precache/options.ts} +11 -8
  135. package/src/lib/{controllers/PrecacheController/PrecacheCacheKeyPlugin.ts → extensions/precache/plugin-cache-key.ts} +4 -4
  136. package/src/lib/{precaching/PrecacheFallbackPlugin.ts → extensions/precache/plugin-fallback.ts} +22 -21
  137. package/src/lib/{controllers/PrecacheController/PrecacheInstallReportPlugin.ts → extensions/precache/plugin-install-report.ts} +1 -1
  138. package/src/lib/extensions/precache/route.ts +72 -0
  139. package/src/lib/{controllers/PrecacheController/PrecacheStrategy.ts → extensions/precache/strategy.ts} +5 -5
  140. package/src/lib/{controllers/RuntimeCacheController.ts → extensions/runtime-cache.ts} +21 -14
  141. package/src/lib/functions/handlers.ts +149 -0
  142. package/src/lib/functions/router.ts +314 -0
  143. package/src/lib/{rangeRequests → range-requests}/RangeRequestsPlugin.ts +1 -1
  144. package/src/lib/route.ts +234 -0
  145. package/src/lib/serwist.ts +443 -0
  146. package/src/lib/strategies/CacheFirst.ts +2 -2
  147. package/src/lib/strategies/CacheOnly.ts +1 -1
  148. package/src/lib/strategies/NetworkFirst.ts +4 -4
  149. package/src/lib/strategies/NetworkOnly.ts +2 -2
  150. package/src/lib/strategies/StaleWhileRevalidate.ts +3 -3
  151. package/src/lib/strategies/Strategy.ts +6 -6
  152. package/src/lib/strategies/StrategyHandler.ts +5 -5
  153. package/src/lib/strategies/plugins/cacheOkAndOpaquePlugin.ts +1 -1
  154. package/src/{types.ts → lib/types.ts} +17 -57
  155. package/src/{copyResponse.ts → lib/utils.ts} +81 -2
  156. package/src/models/messages/messageGenerator.ts +1 -1
  157. package/src/utils/SerwistError.ts +1 -1
  158. package/src/utils/assert.ts +1 -2
  159. package/src/utils/cacheNames.ts +0 -2
  160. package/src/utils/canConstructReadableStream.ts +1 -1
  161. package/src/utils/canConstructResponseFromBodyStream.ts +1 -1
  162. package/src/utils/createCacheKey.ts +1 -2
  163. package/src/utils/generateURLVariations.ts +1 -1
  164. package/src/utils/normalizeHandler.ts +1 -1
  165. package/src/utils/parseRoute.ts +4 -5
  166. package/src/utils/pluginUtils.ts +1 -1
  167. package/src/utils/resultingClientExists.ts +1 -1
  168. package/dist/NavigationRoute.d.ts +0 -56
  169. package/dist/NavigationRoute.d.ts.map +0 -1
  170. package/dist/RegExpRoute.d.ts +0 -24
  171. package/dist/RegExpRoute.d.ts.map +0 -1
  172. package/dist/Route.d.ts +0 -33
  173. package/dist/Route.d.ts.map +0 -1
  174. package/dist/Serwist.d.ts.map +0 -1
  175. package/dist/cacheNames.d.ts.map +0 -1
  176. package/dist/constants.d.ts.map +0 -1
  177. package/dist/copyResponse.d.ts.map +0 -1
  178. package/dist/disableDevLogs.d.ts +0 -7
  179. package/dist/disableDevLogs.d.ts.map +0 -1
  180. package/dist/lib/backgroundSync/BackgroundSyncPlugin.d.ts.map +0 -1
  181. package/dist/lib/backgroundSync/BackgroundSyncQueue.d.ts.map +0 -1
  182. package/dist/lib/backgroundSync/BackgroundSyncQueueDb.d.ts.map +0 -1
  183. package/dist/lib/backgroundSync/BackgroundSyncQueueStore.d.ts.map +0 -1
  184. package/dist/lib/backgroundSync/StorableRequest.d.ts.map +0 -1
  185. package/dist/lib/backgroundSync/index.d.ts.map +0 -1
  186. package/dist/lib/broadcastUpdate/BroadcastCacheUpdate.d.ts.map +0 -1
  187. package/dist/lib/broadcastUpdate/BroadcastUpdatePlugin.d.ts.map +0 -1
  188. package/dist/lib/broadcastUpdate/constants.d.ts.map +0 -1
  189. package/dist/lib/broadcastUpdate/index.d.ts.map +0 -1
  190. package/dist/lib/broadcastUpdate/responsesAreSame.d.ts.map +0 -1
  191. package/dist/lib/broadcastUpdate/types.d.ts.map +0 -1
  192. package/dist/lib/cacheableResponse/CacheableResponse.d.ts.map +0 -1
  193. package/dist/lib/cacheableResponse/CacheableResponsePlugin.d.ts.map +0 -1
  194. package/dist/lib/cacheableResponse/index.d.ts.map +0 -1
  195. package/dist/lib/controllers/PrecacheController/PrecacheCacheKeyPlugin.d.ts.map +0 -1
  196. package/dist/lib/controllers/PrecacheController/PrecacheController.d.ts.map +0 -1
  197. package/dist/lib/controllers/PrecacheController/PrecacheInstallReportPlugin.d.ts.map +0 -1
  198. package/dist/lib/controllers/PrecacheController/PrecacheRoute.d.ts +0 -15
  199. package/dist/lib/controllers/PrecacheController/PrecacheRoute.d.ts.map +0 -1
  200. package/dist/lib/controllers/PrecacheController/PrecacheStrategy.d.ts.map +0 -1
  201. package/dist/lib/controllers/PrecacheController/parsePrecacheOptions.d.ts +0 -25
  202. package/dist/lib/controllers/PrecacheController/parsePrecacheOptions.d.ts.map +0 -1
  203. package/dist/lib/controllers/RuntimeCacheController.d.ts.map +0 -1
  204. package/dist/lib/controllers/index.d.ts +0 -4
  205. package/dist/lib/controllers/index.d.ts.map +0 -1
  206. package/dist/lib/googleAnalytics/constants.d.ts.map +0 -1
  207. package/dist/lib/googleAnalytics/index.d.ts +0 -3
  208. package/dist/lib/googleAnalytics/index.d.ts.map +0 -1
  209. package/dist/lib/googleAnalytics/initializeGoogleAnalytics.d.ts +0 -30
  210. package/dist/lib/googleAnalytics/initializeGoogleAnalytics.d.ts.map +0 -1
  211. package/dist/lib/precaching/PrecacheFallbackPlugin.d.ts.map +0 -1
  212. package/dist/lib/precaching/index.d.ts +0 -3
  213. package/dist/lib/precaching/index.d.ts.map +0 -1
  214. package/dist/lib/rangeRequests/RangeRequestsPlugin.d.ts.map +0 -1
  215. package/dist/lib/rangeRequests/createPartialResponse.d.ts.map +0 -1
  216. package/dist/lib/rangeRequests/index.d.ts.map +0 -1
  217. package/dist/lib/rangeRequests/utils/calculateEffectiveBoundaries.d.ts.map +0 -1
  218. package/dist/lib/rangeRequests/utils/parseRangeHeader.d.ts.map +0 -1
  219. package/dist/navigationPreload.d.ts.map +0 -1
  220. package/dist/registerQuotaErrorCallback.d.ts +0 -8
  221. package/dist/registerQuotaErrorCallback.d.ts.map +0 -1
  222. package/dist/setCacheNameDetails.d.ts +0 -9
  223. package/dist/setCacheNameDetails.d.ts.map +0 -1
  224. package/dist/types.d.ts.map +0 -1
  225. package/src/NavigationRoute.ts +0 -118
  226. package/src/RegExpRoute.ts +0 -74
  227. package/src/Route.ts +0 -67
  228. package/src/Serwist.ts +0 -766
  229. package/src/disableDevLogs.ts +0 -10
  230. package/src/lib/controllers/PrecacheController/PrecacheRoute.ts +0 -44
  231. package/src/lib/controllers/index.ts +0 -3
  232. package/src/lib/googleAnalytics/index.ts +0 -2
  233. package/src/lib/precaching/index.ts +0 -2
  234. package/src/registerQuotaErrorCallback.ts +0 -34
  235. package/src/setCacheNameDetails.ts +0 -53
  236. package/dist/lib/{backgroundSync → background-sync}/BackgroundSyncQueue.d.ts +0 -0
  237. package/dist/lib/{backgroundSync → background-sync}/BackgroundSyncQueueDb.d.ts +0 -0
  238. package/dist/lib/{backgroundSync → background-sync}/BackgroundSyncQueueStore.d.ts +0 -0
  239. package/dist/lib/{backgroundSync → background-sync}/index.d.ts +0 -0
  240. package/dist/lib/{broadcastUpdate → broadcast-update}/constants.d.ts +0 -0
  241. package/dist/lib/{broadcastUpdate → broadcast-update}/index.d.ts +0 -0
  242. package/dist/lib/{broadcastUpdate → broadcast-update}/responsesAreSame.d.ts +0 -0
  243. package/dist/lib/{cacheableResponse → cacheable-response}/CacheableResponse.d.ts +0 -0
  244. package/dist/lib/{googleAnalytics → extensions/google-analytics}/constants.d.ts +0 -0
  245. package/dist/lib/{rangeRequests → range-requests}/createPartialResponse.d.ts +0 -0
  246. package/dist/lib/{rangeRequests → range-requests}/utils/calculateEffectiveBoundaries.d.ts +0 -0
  247. package/dist/lib/{rangeRequests → range-requests}/utils/parseRangeHeader.d.ts +0 -0
  248. package/src/lib/{backgroundSync → background-sync}/index.ts +0 -0
  249. package/src/lib/{broadcastUpdate → broadcast-update}/constants.ts +0 -0
  250. package/src/lib/{broadcastUpdate → broadcast-update}/index.ts +0 -0
  251. package/src/lib/{broadcastUpdate → broadcast-update}/responsesAreSame.ts +1 -1
  252. package/src/{cacheNames.ts → lib/cache-name.ts} +0 -0
  253. package/src/lib/{cacheableResponse → cacheable-response}/CacheableResponse.ts +1 -1
  254. package/src/lib/{cacheableResponse → cacheable-response}/index.ts +1 -1
  255. package/src/{constants.ts → lib/constants.ts} +0 -0
  256. package/src/lib/{googleAnalytics → extensions/google-analytics}/constants.ts +0 -0
  257. package/src/{navigationPreload.ts → lib/navigation-preload.ts} +0 -0
  258. package/src/lib/{rangeRequests → range-requests}/createPartialResponse.ts +1 -1
  259. package/src/lib/{rangeRequests → range-requests}/index.ts +1 -1
  260. package/src/lib/{rangeRequests → range-requests}/utils/calculateEffectiveBoundaries.ts +1 -1
  261. package/src/lib/{rangeRequests → range-requests}/utils/parseRangeHeader.ts +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,61 @@
1
- import { f as finalAssertExports, l as logger, S as SerwistError, g as getFriendlyURL, c as canConstructResponseFromBodyStream, D as Deferred, t as timeout, a as cacheMatchIgnoreParams, e as executeQuotaErrorCallbacks, b as cacheNames$1, d as clientsClaim, w as waitUntil, q as quotaErrorCallbacks, r as resultingClientExists } from './chunks/waitUntil.js';
2
- import { parallel } from '@serwist/utils';
1
+ import { c as cacheNames$1, l as logger, f as finalAssertExports, S as SerwistError, g as getFriendlyURL, D as Deferred, t as timeout, a as cacheMatchIgnoreParams, e as executeQuotaErrorCallbacks, b as canConstructResponseFromBodyStream, q as quotaErrorCallbacks, w as waitUntil, d as clientsClaim, r as resultingClientExists } from './chunks/waitUntil.js';
3
2
  import { openDB, deleteDB } from 'idb';
3
+ import { parallel } from '@serwist/utils';
4
+
5
+ const cacheNames = {
6
+ get googleAnalytics () {
7
+ return cacheNames$1.getGoogleAnalyticsName();
8
+ },
9
+ get precache () {
10
+ return cacheNames$1.getPrecacheName();
11
+ },
12
+ get prefix () {
13
+ return cacheNames$1.getPrefix();
14
+ },
15
+ get runtime () {
16
+ return cacheNames$1.getRuntimeName();
17
+ },
18
+ get suffix () {
19
+ return cacheNames$1.getSuffix();
20
+ }
21
+ };
22
+
23
+ const isNavigationPreloadSupported = ()=>{
24
+ return Boolean(self.registration?.navigationPreload);
25
+ };
26
+ const enableNavigationPreload = (headerValue)=>{
27
+ if (isNavigationPreloadSupported()) {
28
+ self.addEventListener("activate", (event)=>{
29
+ event.waitUntil(self.registration.navigationPreload.enable().then(()=>{
30
+ if (headerValue) {
31
+ void self.registration.navigationPreload.setHeaderValue(headerValue);
32
+ }
33
+ if (process.env.NODE_ENV !== "production") {
34
+ logger.log("Navigation preloading is enabled.");
35
+ }
36
+ }));
37
+ });
38
+ } else {
39
+ if (process.env.NODE_ENV !== "production") {
40
+ logger.log("Navigation preloading is not supported in this browser.");
41
+ }
42
+ }
43
+ };
44
+ const disableNavigationPreload = ()=>{
45
+ if (isNavigationPreloadSupported()) {
46
+ self.addEventListener("activate", (event)=>{
47
+ event.waitUntil(self.registration.navigationPreload.disable().then(()=>{
48
+ if (process.env.NODE_ENV !== "production") {
49
+ logger.log("Navigation preloading is disabled.");
50
+ }
51
+ }));
52
+ });
53
+ } else {
54
+ if (process.env.NODE_ENV !== "production") {
55
+ logger.log("Navigation preloading is not supported in this browser.");
56
+ }
57
+ }
58
+ };
4
59
 
5
60
  const normalizeHandler = (handler)=>{
6
61
  if (handler && typeof handler === "object") {
@@ -64,7 +119,32 @@ class Route {
64
119
  this.catchHandler = normalizeHandler(handler);
65
120
  }
66
121
  }
67
-
122
+ class RegExpRoute extends Route {
123
+ constructor(regExp, handler, method){
124
+ if (process.env.NODE_ENV !== "production") {
125
+ finalAssertExports.isInstance(regExp, RegExp, {
126
+ moduleName: "serwist",
127
+ className: "RegExpRoute",
128
+ funcName: "constructor",
129
+ paramName: "pattern"
130
+ });
131
+ }
132
+ const match = ({ url })=>{
133
+ const result = regExp.exec(url.href);
134
+ if (!result) {
135
+ return;
136
+ }
137
+ if (url.origin !== location.origin && result.index !== 0) {
138
+ if (process.env.NODE_ENV !== "production") {
139
+ logger.debug(`The regular expression '${regExp.toString()}' only partially matched against the cross-origin URL '${url.toString()}'. RegExpRoute's will only handle cross-origin requests if they match the entire URL.`);
140
+ }
141
+ return;
142
+ }
143
+ return result.slice(1);
144
+ };
145
+ super(match, handler, method);
146
+ }
147
+ }
68
148
  class NavigationRoute extends Route {
69
149
  _allowlist;
70
150
  _denylist;
@@ -115,1977 +195,2086 @@ class NavigationRoute extends Route {
115
195
  }
116
196
  }
117
197
 
118
- class RegExpRoute extends Route {
119
- constructor(regExp, handler, method){
120
- if (process.env.NODE_ENV !== "production") {
121
- finalAssertExports.isInstance(regExp, RegExp, {
122
- moduleName: "serwist",
123
- className: "RegExpRoute",
124
- funcName: "constructor",
125
- paramName: "pattern"
126
- });
127
- }
128
- const match = ({ url })=>{
129
- const result = regExp.exec(url.href);
130
- if (!result) {
131
- return;
132
- }
133
- if (url.origin !== location.origin && result.index !== 0) {
134
- if (process.env.NODE_ENV !== "production") {
135
- logger.debug(`The regular expression '${regExp.toString()}' only partially matched against the cross-origin URL '${url.toString()}'. RegExpRoute's will only handle cross-origin requests if they match the entire URL.`);
136
- }
137
- return;
138
- }
139
- return result.slice(1);
140
- };
141
- super(match, handler, method);
142
- }
143
- }
144
-
145
- const REVISION_SEARCH_PARAM = "__WB_REVISION__";
146
- const createCacheKey = (entry)=>{
147
- if (!entry) {
148
- throw new SerwistError("add-to-cache-list-unexpected-type", {
149
- entry
198
+ const BACKGROUND_SYNC_DB_VERSION = 3;
199
+ const BACKGROUND_SYNC_DB_NAME = "serwist-background-sync";
200
+ const REQUEST_OBJECT_STORE_NAME = "requests";
201
+ const QUEUE_NAME_INDEX = "queueName";
202
+ class BackgroundSyncQueueDb {
203
+ _db = null;
204
+ async addEntry(entry) {
205
+ const db = await this.getDb();
206
+ const tx = db.transaction(REQUEST_OBJECT_STORE_NAME, "readwrite", {
207
+ durability: "relaxed"
150
208
  });
209
+ await tx.store.add(entry);
210
+ await tx.done;
151
211
  }
152
- if (typeof entry === "string") {
153
- const urlObject = new URL(entry, location.href);
154
- return {
155
- cacheKey: urlObject.href,
156
- url: urlObject.href
157
- };
212
+ async getFirstEntryId() {
213
+ const db = await this.getDb();
214
+ const cursor = await db.transaction(REQUEST_OBJECT_STORE_NAME).store.openCursor();
215
+ return cursor?.value.id;
158
216
  }
159
- const { revision, url } = entry;
160
- if (!url) {
161
- throw new SerwistError("add-to-cache-list-unexpected-type", {
162
- entry
163
- });
217
+ async getAllEntriesByQueueName(queueName) {
218
+ const db = await this.getDb();
219
+ const results = await db.getAllFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
220
+ return results ? results : [];
164
221
  }
165
- if (!revision) {
166
- const urlObject = new URL(url, location.href);
167
- return {
168
- cacheKey: urlObject.href,
169
- url: urlObject.href
170
- };
222
+ async getEntryCountByQueueName(queueName) {
223
+ const db = await this.getDb();
224
+ return db.countFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
171
225
  }
172
- const cacheKeyURL = new URL(url, location.href);
173
- const originalURL = new URL(url, location.href);
174
- cacheKeyURL.searchParams.set(REVISION_SEARCH_PARAM, revision);
175
- return {
176
- cacheKey: cacheKeyURL.href,
177
- url: originalURL.href
178
- };
179
- };
180
-
181
- const logGroup = (groupTitle, deletedURLs)=>{
182
- logger.groupCollapsed(groupTitle);
183
- for (const url of deletedURLs){
184
- logger.log(url);
226
+ async deleteEntry(id) {
227
+ const db = await this.getDb();
228
+ await db.delete(REQUEST_OBJECT_STORE_NAME, id);
185
229
  }
186
- logger.groupEnd();
187
- };
188
- const printCleanupDetails = (deletedURLs)=>{
189
- const deletionCount = deletedURLs.length;
190
- if (deletionCount > 0) {
191
- logger.groupCollapsed(`During precaching cleanup, ${deletionCount} cached request${deletionCount === 1 ? " was" : "s were"} deleted.`);
192
- logGroup("Deleted Cache Requests", deletedURLs);
193
- logger.groupEnd();
230
+ async getFirstEntryByQueueName(queueName) {
231
+ return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), "next");
194
232
  }
195
- };
196
-
197
- function _nestedGroup(groupTitle, urls) {
198
- if (urls.length === 0) {
199
- return;
233
+ async getLastEntryByQueueName(queueName) {
234
+ return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), "prev");
200
235
  }
201
- logger.groupCollapsed(groupTitle);
202
- for (const url of urls){
203
- logger.log(url);
236
+ async getEndEntryFromIndex(query, direction) {
237
+ const db = await this.getDb();
238
+ const cursor = await db.transaction(REQUEST_OBJECT_STORE_NAME).store.index(QUEUE_NAME_INDEX).openCursor(query, direction);
239
+ return cursor?.value;
204
240
  }
205
- logger.groupEnd();
206
- }
207
- const printInstallDetails = (urlsToPrecache, urlsAlreadyPrecached)=>{
208
- const precachedCount = urlsToPrecache.length;
209
- const alreadyPrecachedCount = urlsAlreadyPrecached.length;
210
- if (precachedCount || alreadyPrecachedCount) {
211
- let message = `Precaching ${precachedCount} file${precachedCount === 1 ? "" : "s"}.`;
212
- if (alreadyPrecachedCount > 0) {
213
- message += ` ${alreadyPrecachedCount} ` + `file${alreadyPrecachedCount === 1 ? " is" : "s are"} already cached.`;
241
+ async getDb() {
242
+ if (!this._db) {
243
+ this._db = await openDB(BACKGROUND_SYNC_DB_NAME, BACKGROUND_SYNC_DB_VERSION, {
244
+ upgrade: this._upgradeDb
245
+ });
214
246
  }
215
- logger.groupCollapsed(message);
216
- _nestedGroup("View newly precached URLs.", urlsToPrecache);
217
- _nestedGroup("View previously precached URLs.", urlsAlreadyPrecached);
218
- logger.groupEnd();
247
+ return this._db;
219
248
  }
220
- };
221
-
222
- class PrecacheInstallReportPlugin {
223
- updatedURLs = [];
224
- notUpdatedURLs = [];
225
- handlerWillStart = async ({ request, state })=>{
226
- if (state) {
227
- state.originalRequest = request;
228
- }
229
- };
230
- cachedResponseWillBeUsed = async ({ event, state, cachedResponse })=>{
231
- if (event.type === "install") {
232
- if (state?.originalRequest && state.originalRequest instanceof Request) {
233
- const url = state.originalRequest.url;
234
- if (cachedResponse) {
235
- this.notUpdatedURLs.push(url);
236
- } else {
237
- this.updatedURLs.push(url);
238
- }
249
+ _upgradeDb(db, oldVersion) {
250
+ if (oldVersion > 0 && oldVersion < BACKGROUND_SYNC_DB_VERSION) {
251
+ if (db.objectStoreNames.contains(REQUEST_OBJECT_STORE_NAME)) {
252
+ db.deleteObjectStore(REQUEST_OBJECT_STORE_NAME);
239
253
  }
240
254
  }
241
- return cachedResponse;
242
- };
255
+ const objStore = db.createObjectStore(REQUEST_OBJECT_STORE_NAME, {
256
+ autoIncrement: true,
257
+ keyPath: "id"
258
+ });
259
+ objStore.createIndex(QUEUE_NAME_INDEX, QUEUE_NAME_INDEX, {
260
+ unique: false
261
+ });
262
+ }
243
263
  }
244
264
 
245
- const removeIgnoredSearchParams = (urlObject, ignoreURLParametersMatching = [])=>{
246
- for (const paramName of [
247
- ...urlObject.searchParams.keys()
248
- ]){
249
- if (ignoreURLParametersMatching.some((regExp)=>regExp.test(paramName))) {
250
- urlObject.searchParams.delete(paramName);
251
- }
252
- }
253
- return urlObject;
254
- };
255
-
256
- function* generateURLVariations(url, { directoryIndex = "index.html", ignoreURLParametersMatching = [
257
- /^utm_/,
258
- /^fbclid$/
259
- ], cleanURLs = true, urlManipulation } = {}) {
260
- const urlObject = new URL(url, location.href);
261
- urlObject.hash = "";
262
- yield urlObject.href;
263
- const urlWithoutIgnoredParams = removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching);
264
- yield urlWithoutIgnoredParams.href;
265
- if (directoryIndex && urlWithoutIgnoredParams.pathname.endsWith("/")) {
266
- const directoryURL = new URL(urlWithoutIgnoredParams.href);
267
- directoryURL.pathname += directoryIndex;
268
- yield directoryURL.href;
265
+ class BackgroundSyncQueueStore {
266
+ _queueName;
267
+ _queueDb;
268
+ constructor(queueName){
269
+ this._queueName = queueName;
270
+ this._queueDb = new BackgroundSyncQueueDb();
269
271
  }
270
- if (cleanURLs) {
271
- const cleanURL = new URL(urlWithoutIgnoredParams.href);
272
- cleanURL.pathname += ".html";
273
- yield cleanURL.href;
272
+ async pushEntry(entry) {
273
+ if (process.env.NODE_ENV !== "production") {
274
+ finalAssertExports.isType(entry, "object", {
275
+ moduleName: "serwist",
276
+ className: "BackgroundSyncQueueStore",
277
+ funcName: "pushEntry",
278
+ paramName: "entry"
279
+ });
280
+ finalAssertExports.isType(entry.requestData, "object", {
281
+ moduleName: "serwist",
282
+ className: "BackgroundSyncQueueStore",
283
+ funcName: "pushEntry",
284
+ paramName: "entry.requestData"
285
+ });
286
+ }
287
+ delete entry.id;
288
+ entry.queueName = this._queueName;
289
+ await this._queueDb.addEntry(entry);
274
290
  }
275
- if (urlManipulation) {
276
- const additionalURLs = urlManipulation({
277
- url: urlObject
278
- });
279
- for (const urlToAttempt of additionalURLs){
280
- yield urlToAttempt.href;
291
+ async unshiftEntry(entry) {
292
+ if (process.env.NODE_ENV !== "production") {
293
+ finalAssertExports.isType(entry, "object", {
294
+ moduleName: "serwist",
295
+ className: "BackgroundSyncQueueStore",
296
+ funcName: "unshiftEntry",
297
+ paramName: "entry"
298
+ });
299
+ finalAssertExports.isType(entry.requestData, "object", {
300
+ moduleName: "serwist",
301
+ className: "BackgroundSyncQueueStore",
302
+ funcName: "unshiftEntry",
303
+ paramName: "entry.requestData"
304
+ });
305
+ }
306
+ const firstId = await this._queueDb.getFirstEntryId();
307
+ if (firstId) {
308
+ entry.id = firstId - 1;
309
+ } else {
310
+ delete entry.id;
281
311
  }
312
+ entry.queueName = this._queueName;
313
+ await this._queueDb.addEntry(entry);
282
314
  }
283
- }
284
-
285
- class PrecacheRoute extends Route {
286
- constructor(controller, options){
287
- const match = ({ request })=>{
288
- const urlsToCacheKeys = controller.getUrlsToPrecacheKeys();
289
- for (const possibleURL of generateURLVariations(request.url, options)){
290
- const cacheKey = urlsToCacheKeys.get(possibleURL);
291
- if (cacheKey) {
292
- const integrity = controller.getIntegrityForPrecacheKey(cacheKey);
293
- return {
294
- cacheKey,
295
- integrity
296
- };
297
- }
298
- }
299
- if (process.env.NODE_ENV !== "production") {
300
- logger.debug(`Precaching did not find a match for ${getFriendlyURL(request.url)}.`);
301
- }
302
- return;
303
- };
304
- super(match, controller.strategy);
315
+ async popEntry() {
316
+ return this._removeEntry(await this._queueDb.getLastEntryByQueueName(this._queueName));
317
+ }
318
+ async shiftEntry() {
319
+ return this._removeEntry(await this._queueDb.getFirstEntryByQueueName(this._queueName));
320
+ }
321
+ async getAll() {
322
+ return await this._queueDb.getAllEntriesByQueueName(this._queueName);
323
+ }
324
+ async size() {
325
+ return await this._queueDb.getEntryCountByQueueName(this._queueName);
326
+ }
327
+ async deleteEntry(id) {
328
+ await this._queueDb.deleteEntry(id);
329
+ }
330
+ async _removeEntry(entry) {
331
+ if (entry) {
332
+ await this.deleteEntry(entry.id);
333
+ }
334
+ return entry;
305
335
  }
306
336
  }
307
337
 
308
- const copyResponse = async (response, modifier)=>{
309
- let origin = null;
310
- if (response.url) {
311
- const responseURL = new URL(response.url);
312
- origin = responseURL.origin;
313
- }
314
- if (origin !== self.location.origin) {
315
- throw new SerwistError("cross-origin-copy-response", {
316
- origin
338
+ const serializableProperties = [
339
+ "method",
340
+ "referrer",
341
+ "referrerPolicy",
342
+ "mode",
343
+ "credentials",
344
+ "cache",
345
+ "redirect",
346
+ "integrity",
347
+ "keepalive"
348
+ ];
349
+ class StorableRequest {
350
+ _requestData;
351
+ static async fromRequest(request) {
352
+ const requestData = {
353
+ url: request.url,
354
+ headers: {}
355
+ };
356
+ if (request.method !== "GET") {
357
+ requestData.body = await request.clone().arrayBuffer();
358
+ }
359
+ request.headers.forEach((value, key)=>{
360
+ requestData.headers[key] = value;
317
361
  });
362
+ for (const prop of serializableProperties){
363
+ if (request[prop] !== undefined) {
364
+ requestData[prop] = request[prop];
365
+ }
366
+ }
367
+ return new StorableRequest(requestData);
318
368
  }
319
- const clonedResponse = response.clone();
320
- const responseInit = {
321
- headers: new Headers(clonedResponse.headers),
322
- status: clonedResponse.status,
323
- statusText: clonedResponse.statusText
324
- };
325
- const modifiedResponseInit = modifier ? modifier(responseInit) : responseInit;
326
- const body = canConstructResponseFromBodyStream() ? clonedResponse.body : await clonedResponse.blob();
327
- return new Response(body, modifiedResponseInit);
328
- };
329
-
330
- function toRequest(input) {
331
- return typeof input === "string" ? new Request(input) : input;
332
- }
333
- class StrategyHandler {
334
- event;
335
- request;
336
- url;
337
- params;
338
- _cacheKeys = {};
339
- _strategy;
340
- _handlerDeferred;
341
- _extendLifetimePromises;
342
- _plugins;
343
- _pluginStateMap;
344
- constructor(strategy, options){
369
+ constructor(requestData){
345
370
  if (process.env.NODE_ENV !== "production") {
346
- finalAssertExports.isInstance(options.event, ExtendableEvent, {
371
+ finalAssertExports.isType(requestData, "object", {
347
372
  moduleName: "serwist",
348
- className: "StrategyHandler",
373
+ className: "StorableRequest",
349
374
  funcName: "constructor",
350
- paramName: "options.event"
375
+ paramName: "requestData"
351
376
  });
352
- finalAssertExports.isInstance(options.request, Request, {
377
+ finalAssertExports.isType(requestData.url, "string", {
353
378
  moduleName: "serwist",
354
- className: "StrategyHandler",
379
+ className: "StorableRequest",
355
380
  funcName: "constructor",
356
- paramName: "options.request"
381
+ paramName: "requestData.url"
357
382
  });
358
383
  }
359
- this.event = options.event;
360
- this.request = options.request;
361
- if (options.url) {
362
- this.url = options.url;
363
- this.params = options.params;
384
+ if (requestData.mode === "navigate") {
385
+ requestData.mode = "same-origin";
364
386
  }
365
- this._strategy = strategy;
366
- this._handlerDeferred = new Deferred();
367
- this._extendLifetimePromises = [];
368
- this._plugins = [
369
- ...strategy.plugins
370
- ];
371
- this._pluginStateMap = new Map();
372
- for (const plugin of this._plugins){
373
- this._pluginStateMap.set(plugin, {});
387
+ this._requestData = requestData;
388
+ }
389
+ toObject() {
390
+ const requestData = Object.assign({}, this._requestData);
391
+ requestData.headers = Object.assign({}, this._requestData.headers);
392
+ if (requestData.body) {
393
+ requestData.body = requestData.body.slice(0);
374
394
  }
375
- this.event.waitUntil(this._handlerDeferred.promise);
395
+ return requestData;
376
396
  }
377
- async fetch(input) {
378
- const { event } = this;
379
- let request = toRequest(input);
380
- const preloadResponse = await this.getPreloadResponse();
381
- if (preloadResponse) {
382
- return preloadResponse;
383
- }
384
- const originalRequest = this.hasCallback("fetchDidFail") ? request.clone() : null;
385
- try {
386
- for (const cb of this.iterateCallbacks("requestWillFetch")){
387
- request = await cb({
388
- request: request.clone(),
389
- event
390
- });
391
- }
392
- } catch (err) {
393
- if (err instanceof Error) {
394
- throw new SerwistError("plugin-error-request-will-fetch", {
395
- thrownErrorMessage: err.message
396
- });
397
- }
398
- }
399
- const pluginFilteredRequest = request.clone();
400
- try {
401
- let fetchResponse;
402
- fetchResponse = await fetch(request, request.mode === "navigate" ? undefined : this._strategy.fetchOptions);
403
- if (process.env.NODE_ENV !== "production") {
404
- logger.debug(`Network request for '${getFriendlyURL(request.url)}' returned a response with status '${fetchResponse.status}'.`);
405
- }
406
- for (const callback of this.iterateCallbacks("fetchDidSucceed")){
407
- fetchResponse = await callback({
408
- event,
409
- request: pluginFilteredRequest,
410
- response: fetchResponse
411
- });
412
- }
413
- return fetchResponse;
414
- } catch (error) {
415
- if (process.env.NODE_ENV !== "production") {
416
- logger.log(`Network request for '${getFriendlyURL(request.url)}' threw an error.`, error);
417
- }
418
- if (originalRequest) {
419
- await this.runCallbacks("fetchDidFail", {
420
- error: error,
421
- event,
422
- originalRequest: originalRequest.clone(),
423
- request: pluginFilteredRequest.clone()
424
- });
425
- }
426
- throw error;
397
+ toRequest() {
398
+ return new Request(this._requestData.url, this._requestData);
399
+ }
400
+ clone() {
401
+ return new StorableRequest(this.toObject());
402
+ }
403
+ }
404
+
405
+ const TAG_PREFIX = "serwist-background-sync";
406
+ const MAX_RETENTION_TIME$1 = 60 * 24 * 7;
407
+ const queueNames = new Set();
408
+ const convertEntry = (queueStoreEntry)=>{
409
+ const queueEntry = {
410
+ request: new StorableRequest(queueStoreEntry.requestData).toRequest(),
411
+ timestamp: queueStoreEntry.timestamp
412
+ };
413
+ if (queueStoreEntry.metadata) {
414
+ queueEntry.metadata = queueStoreEntry.metadata;
415
+ }
416
+ return queueEntry;
417
+ };
418
+ class BackgroundSyncQueue {
419
+ _name;
420
+ _onSync;
421
+ _maxRetentionTime;
422
+ _queueStore;
423
+ _forceSyncFallback;
424
+ _syncInProgress = false;
425
+ _requestsAddedDuringSync = false;
426
+ constructor(name, { forceSyncFallback, onSync, maxRetentionTime } = {}){
427
+ if (queueNames.has(name)) {
428
+ throw new SerwistError("duplicate-queue-name", {
429
+ name
430
+ });
427
431
  }
432
+ queueNames.add(name);
433
+ this._name = name;
434
+ this._onSync = onSync || this.replayRequests;
435
+ this._maxRetentionTime = maxRetentionTime || MAX_RETENTION_TIME$1;
436
+ this._forceSyncFallback = Boolean(forceSyncFallback);
437
+ this._queueStore = new BackgroundSyncQueueStore(this._name);
438
+ this._addSyncListener();
428
439
  }
429
- async fetchAndCachePut(input) {
430
- const response = await this.fetch(input);
431
- const responseClone = response.clone();
432
- void this.waitUntil(this.cachePut(input, responseClone));
433
- return response;
440
+ get name() {
441
+ return this._name;
434
442
  }
435
- async cacheMatch(key) {
436
- const request = toRequest(key);
437
- let cachedResponse;
438
- const { cacheName, matchOptions } = this._strategy;
439
- const effectiveRequest = await this.getCacheKey(request, "read");
440
- const multiMatchOptions = {
441
- ...matchOptions,
442
- ...{
443
- cacheName
444
- }
445
- };
446
- cachedResponse = await caches.match(effectiveRequest, multiMatchOptions);
443
+ async pushRequest(entry) {
447
444
  if (process.env.NODE_ENV !== "production") {
448
- if (cachedResponse) {
449
- logger.debug(`Found a cached response in '${cacheName}'.`);
450
- } else {
451
- logger.debug(`No cached response found in '${cacheName}'.`);
452
- }
453
- }
454
- for (const callback of this.iterateCallbacks("cachedResponseWillBeUsed")){
455
- cachedResponse = await callback({
456
- cacheName,
457
- matchOptions,
458
- cachedResponse,
459
- request: effectiveRequest,
460
- event: this.event
461
- }) || undefined;
445
+ finalAssertExports.isType(entry, "object", {
446
+ moduleName: "serwist",
447
+ className: "BackgroundSyncQueue",
448
+ funcName: "pushRequest",
449
+ paramName: "entry"
450
+ });
451
+ finalAssertExports.isInstance(entry.request, Request, {
452
+ moduleName: "serwist",
453
+ className: "BackgroundSyncQueue",
454
+ funcName: "pushRequest",
455
+ paramName: "entry.request"
456
+ });
462
457
  }
463
- return cachedResponse;
458
+ await this._addRequest(entry, "push");
464
459
  }
465
- async cachePut(key, response) {
466
- const request = toRequest(key);
467
- await timeout(0);
468
- const effectiveRequest = await this.getCacheKey(request, "write");
460
+ async unshiftRequest(entry) {
469
461
  if (process.env.NODE_ENV !== "production") {
470
- if (effectiveRequest.method && effectiveRequest.method !== "GET") {
471
- throw new SerwistError("attempt-to-cache-non-get-request", {
472
- url: getFriendlyURL(effectiveRequest.url),
473
- method: effectiveRequest.method
474
- });
475
- }
476
- }
477
- if (!response) {
478
- if (process.env.NODE_ENV !== "production") {
479
- logger.error(`Cannot cache non-existent response for '${getFriendlyURL(effectiveRequest.url)}'.`);
480
- }
481
- throw new SerwistError("cache-put-with-no-response", {
482
- url: getFriendlyURL(effectiveRequest.url)
462
+ finalAssertExports.isType(entry, "object", {
463
+ moduleName: "serwist",
464
+ className: "BackgroundSyncQueue",
465
+ funcName: "unshiftRequest",
466
+ paramName: "entry"
467
+ });
468
+ finalAssertExports.isInstance(entry.request, Request, {
469
+ moduleName: "serwist",
470
+ className: "BackgroundSyncQueue",
471
+ funcName: "unshiftRequest",
472
+ paramName: "entry.request"
483
473
  });
484
474
  }
485
- const responseToCache = await this._ensureResponseSafeToCache(response);
486
- if (!responseToCache) {
487
- if (process.env.NODE_ENV !== "production") {
488
- logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' will not be cached.`, responseToCache);
475
+ await this._addRequest(entry, "unshift");
476
+ }
477
+ async popRequest() {
478
+ return this._removeRequest("pop");
479
+ }
480
+ async shiftRequest() {
481
+ return this._removeRequest("shift");
482
+ }
483
+ async getAll() {
484
+ const allEntries = await this._queueStore.getAll();
485
+ const now = Date.now();
486
+ const unexpiredEntries = [];
487
+ for (const entry of allEntries){
488
+ const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
489
+ if (now - entry.timestamp > maxRetentionTimeInMs) {
490
+ await this._queueStore.deleteEntry(entry.id);
491
+ } else {
492
+ unexpiredEntries.push(convertEntry(entry));
489
493
  }
490
- return false;
491
494
  }
492
- const { cacheName, matchOptions } = this._strategy;
493
- const cache = await self.caches.open(cacheName);
494
- if (process.env.NODE_ENV !== "production") {
495
- const vary = response.headers.get("Vary");
496
- if (vary && matchOptions?.ignoreVary !== true) {
497
- logger.debug(`The response for ${getFriendlyURL(effectiveRequest.url)} has a 'Vary: ${vary}' header. Consider setting the {ignoreVary: true} option on your strategy to ensure cache matching and deletion works as expected.`);
498
- }
495
+ return unexpiredEntries;
496
+ }
497
+ async size() {
498
+ return await this._queueStore.size();
499
+ }
500
+ async _addRequest({ request, metadata, timestamp = Date.now() }, operation) {
501
+ const storableRequest = await StorableRequest.fromRequest(request.clone());
502
+ const entry = {
503
+ requestData: storableRequest.toObject(),
504
+ timestamp
505
+ };
506
+ if (metadata) {
507
+ entry.metadata = metadata;
499
508
  }
500
- const hasCacheUpdateCallback = this.hasCallback("cacheDidUpdate");
501
- const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams(cache, effectiveRequest.clone(), [
502
- "__WB_REVISION__"
503
- ], matchOptions) : null;
504
- if (process.env.NODE_ENV !== "production") {
505
- logger.debug(`Updating the '${cacheName}' cache with a new Response for ${getFriendlyURL(effectiveRequest.url)}.`);
509
+ switch(operation){
510
+ case "push":
511
+ await this._queueStore.pushEntry(entry);
512
+ break;
513
+ case "unshift":
514
+ await this._queueStore.unshiftEntry(entry);
515
+ break;
506
516
  }
507
- try {
508
- await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache);
509
- } catch (error) {
510
- if (error instanceof Error) {
511
- if (error.name === "QuotaExceededError") {
512
- await executeQuotaErrorCallbacks();
513
- }
514
- throw error;
515
- }
517
+ if (process.env.NODE_ENV !== "production") {
518
+ logger.log(`Request for '${getFriendlyURL(request.url)}' has ` + `been added to background sync queue '${this._name}'.`);
516
519
  }
517
- for (const callback of this.iterateCallbacks("cacheDidUpdate")){
518
- await callback({
519
- cacheName,
520
- oldResponse,
521
- newResponse: responseToCache.clone(),
522
- request: effectiveRequest,
523
- event: this.event
524
- });
520
+ if (this._syncInProgress) {
521
+ this._requestsAddedDuringSync = true;
522
+ } else {
523
+ await this.registerSync();
525
524
  }
526
- return true;
527
525
  }
528
- async getCacheKey(request, mode) {
529
- const key = `${request.url} | ${mode}`;
530
- if (!this._cacheKeys[key]) {
531
- let effectiveRequest = request;
532
- for (const callback of this.iterateCallbacks("cacheKeyWillBeUsed")){
533
- effectiveRequest = toRequest(await callback({
534
- mode,
535
- request: effectiveRequest,
536
- event: this.event,
537
- params: this.params
538
- }));
526
+ async _removeRequest(operation) {
527
+ const now = Date.now();
528
+ let entry;
529
+ switch(operation){
530
+ case "pop":
531
+ entry = await this._queueStore.popEntry();
532
+ break;
533
+ case "shift":
534
+ entry = await this._queueStore.shiftEntry();
535
+ break;
536
+ }
537
+ if (entry) {
538
+ const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
539
+ if (now - entry.timestamp > maxRetentionTimeInMs) {
540
+ return this._removeRequest(operation);
539
541
  }
540
- this._cacheKeys[key] = effectiveRequest;
542
+ return convertEntry(entry);
541
543
  }
542
- return this._cacheKeys[key];
544
+ return undefined;
543
545
  }
544
- hasCallback(name) {
545
- for (const plugin of this._strategy.plugins){
546
- if (name in plugin) {
547
- return true;
546
+ async replayRequests() {
547
+ let entry;
548
+ while(entry = await this.shiftRequest()){
549
+ try {
550
+ await fetch(entry.request.clone());
551
+ if (process.env.NODE_ENV !== "production") {
552
+ logger.log(`Request for '${getFriendlyURL(entry.request.url)}' ` + `has been replayed in queue '${this._name}'`);
553
+ }
554
+ } catch {
555
+ await this.unshiftRequest(entry);
556
+ if (process.env.NODE_ENV !== "production") {
557
+ logger.log(`Request for '${getFriendlyURL(entry.request.url)}' ` + `failed to replay, putting it back in queue '${this._name}'`);
558
+ }
559
+ throw new SerwistError("queue-replay-failed", {
560
+ name: this._name
561
+ });
548
562
  }
549
563
  }
550
- return false;
564
+ if (process.env.NODE_ENV !== "production") {
565
+ logger.log(`All requests in queue '${this.name}' have successfully replayed; the queue is now empty!`);
566
+ }
551
567
  }
552
- async runCallbacks(name, param) {
553
- for (const callback of this.iterateCallbacks(name)){
554
- await callback(param);
555
- }
556
- }
557
- *iterateCallbacks(name) {
558
- for (const plugin of this._strategy.plugins){
559
- if (typeof plugin[name] === "function") {
560
- const state = this._pluginStateMap.get(plugin);
561
- const statefulCallback = (param)=>{
562
- const statefulParam = {
563
- ...param,
564
- state
565
- };
566
- return plugin[name](statefulParam);
567
- };
568
- yield statefulCallback;
569
- }
570
- }
571
- }
572
- waitUntil(promise) {
573
- this._extendLifetimePromises.push(promise);
574
- return promise;
575
- }
576
- async doneWaiting() {
577
- let promise = undefined;
578
- while(promise = this._extendLifetimePromises.shift()){
579
- await promise;
580
- }
581
- }
582
- destroy() {
583
- this._handlerDeferred.resolve(null);
584
- }
585
- async getPreloadResponse() {
586
- if (this.event instanceof FetchEvent && this.event.request.mode === "navigate" && "preloadResponse" in this.event) {
568
+ async registerSync() {
569
+ if ("sync" in self.registration && !this._forceSyncFallback) {
587
570
  try {
588
- const possiblePreloadResponse = await this.event.preloadResponse;
589
- if (possiblePreloadResponse) {
590
- if (process.env.NODE_ENV !== "production") {
591
- logger.log(`Using a preloaded navigation response for '${getFriendlyURL(this.event.request.url)}'`);
592
- }
593
- return possiblePreloadResponse;
594
- }
595
- } catch (error) {
571
+ await self.registration.sync.register(`${TAG_PREFIX}:${this._name}`);
572
+ } catch (err) {
596
573
  if (process.env.NODE_ENV !== "production") {
597
- logger.error(error);
574
+ logger.warn(`Unable to register sync event for '${this._name}'.`, err);
598
575
  }
599
- return undefined;
600
576
  }
601
577
  }
602
- return undefined;
603
578
  }
604
- async _ensureResponseSafeToCache(response) {
605
- let responseToCache = response;
606
- let pluginsUsed = false;
607
- for (const callback of this.iterateCallbacks("cacheWillUpdate")){
608
- responseToCache = await callback({
609
- request: this.request,
610
- response: responseToCache,
611
- event: this.event
612
- }) || undefined;
613
- pluginsUsed = true;
614
- if (!responseToCache) {
615
- break;
616
- }
617
- }
618
- if (!pluginsUsed) {
619
- if (responseToCache && responseToCache.status !== 200) {
620
- if (process.env.NODE_ENV !== "production") {
621
- if (responseToCache.status === 0) {
622
- logger.warn(`The response for '${this.request.url}' is an opaque response. The caching strategy that you're using will not cache opaque responses by default.`);
623
- } else {
624
- logger.debug(`The response for '${this.request.url}' returned a status code of '${response.status}' and won't be cached as a result.`);
579
+ _addSyncListener() {
580
+ if ("sync" in self.registration && !this._forceSyncFallback) {
581
+ self.addEventListener("sync", (event)=>{
582
+ if (event.tag === `${TAG_PREFIX}:${this._name}`) {
583
+ if (process.env.NODE_ENV !== "production") {
584
+ logger.log(`Background sync for tag '${event.tag}' has been received`);
625
585
  }
586
+ const syncComplete = async ()=>{
587
+ this._syncInProgress = true;
588
+ let syncError;
589
+ try {
590
+ await this._onSync({
591
+ queue: this
592
+ });
593
+ } catch (error) {
594
+ if (error instanceof Error) {
595
+ syncError = error;
596
+ throw syncError;
597
+ }
598
+ } finally{
599
+ if (this._requestsAddedDuringSync && !(syncError && !event.lastChance)) {
600
+ await this.registerSync();
601
+ }
602
+ this._syncInProgress = false;
603
+ this._requestsAddedDuringSync = false;
604
+ }
605
+ };
606
+ event.waitUntil(syncComplete());
626
607
  }
627
- responseToCache = undefined;
608
+ });
609
+ } else {
610
+ if (process.env.NODE_ENV !== "production") {
611
+ logger.log("Background sync replaying without background sync event");
628
612
  }
613
+ void this._onSync({
614
+ queue: this
615
+ });
629
616
  }
630
- return responseToCache;
617
+ }
618
+ static get _queueNames() {
619
+ return queueNames;
631
620
  }
632
621
  }
633
622
 
634
- class Strategy {
635
- cacheName;
636
- plugins;
637
- fetchOptions;
638
- matchOptions;
639
- constructor(options = {}){
640
- this.cacheName = cacheNames$1.getRuntimeName(options.cacheName);
641
- this.plugins = options.plugins || [];
642
- this.fetchOptions = options.fetchOptions;
643
- this.matchOptions = options.matchOptions;
644
- }
645
- handle(options) {
646
- const [responseDone] = this.handleAll(options);
647
- return responseDone;
623
+ class BackgroundSyncPlugin {
624
+ _queue;
625
+ constructor(name, options){
626
+ this._queue = new BackgroundSyncQueue(name, options);
648
627
  }
649
- handleAll(options) {
650
- if (options instanceof FetchEvent) {
651
- options = {
652
- event: options,
653
- request: options.request
654
- };
655
- }
656
- const event = options.event;
657
- const request = typeof options.request === "string" ? new Request(options.request) : options.request;
658
- const handler = new StrategyHandler(this, options.url ? {
659
- event,
660
- request,
661
- url: options.url,
662
- params: options.params
663
- } : {
664
- event,
628
+ async fetchDidFail({ request }) {
629
+ await this._queue.pushRequest({
665
630
  request
666
631
  });
667
- const responseDone = this._getResponse(handler, request, event);
668
- const handlerDone = this._awaitComplete(responseDone, handler, request, event);
669
- return [
670
- responseDone,
671
- handlerDone
672
- ];
673
632
  }
674
- async _getResponse(handler, request, event) {
675
- await handler.runCallbacks("handlerWillStart", {
676
- event,
677
- request
678
- });
679
- let response = undefined;
680
- try {
681
- response = await this._handle(request, handler);
682
- if (response === undefined || response.type === "error") {
683
- throw new SerwistError("no-response", {
684
- url: request.url
633
+ }
634
+
635
+ const parseRoute = (capture, handler, method)=>{
636
+ if (typeof capture === "string") {
637
+ const captureUrl = new URL(capture, location.href);
638
+ if (process.env.NODE_ENV !== "production") {
639
+ if (!(capture.startsWith("/") || capture.startsWith("http"))) {
640
+ throw new SerwistError("invalid-string", {
641
+ moduleName: "serwist",
642
+ funcName: "parseRoute",
643
+ paramName: "capture"
685
644
  });
686
645
  }
687
- } catch (error) {
688
- if (error instanceof Error) {
689
- for (const callback of handler.iterateCallbacks("handlerDidError")){
690
- response = await callback({
691
- error,
692
- event,
693
- request
694
- });
695
- if (response !== undefined) {
696
- break;
697
- }
698
- }
699
- }
700
- if (!response) {
701
- throw error;
646
+ const valueToCheck = capture.startsWith("http") ? captureUrl.pathname : capture;
647
+ const wildcards = "[*:?+]";
648
+ if (new RegExp(`${wildcards}`).exec(valueToCheck)) {
649
+ logger.debug(`The '$capture' parameter contains an Express-style wildcard character (${wildcards}). Strings are now always interpreted as exact matches; use a RegExp for partial or wildcard matches.`);
702
650
  }
651
+ }
652
+ const matchCallback = ({ url })=>{
703
653
  if (process.env.NODE_ENV !== "production") {
704
- throw logger.log(`While responding to '${getFriendlyURL(request.url)}', an ${error instanceof Error ? error.toString() : ""} error occurred. Using a fallback response provided by a handlerDidError plugin.`);
654
+ if (url.pathname === captureUrl.pathname && url.origin !== captureUrl.origin) {
655
+ logger.debug(`${capture} only partially matches the cross-origin URL ${url.toString()}. This route will only handle cross-origin requests if they match the entire URL.`);
656
+ }
705
657
  }
706
- }
707
- for (const callback of handler.iterateCallbacks("handlerWillRespond")){
708
- response = await callback({
709
- event,
710
- request,
711
- response
712
- });
713
- }
714
- return response;
658
+ return url.href === captureUrl.href;
659
+ };
660
+ return new Route(matchCallback, handler, method);
715
661
  }
716
- async _awaitComplete(responseDone, handler, request, event) {
717
- let response = undefined;
718
- let error = undefined;
719
- try {
720
- response = await responseDone;
721
- } catch (error) {}
722
- try {
723
- await handler.runCallbacks("handlerDidRespond", {
724
- event,
725
- request,
726
- response
727
- });
728
- await handler.doneWaiting();
729
- } catch (waitUntilError) {
730
- if (waitUntilError instanceof Error) {
731
- error = waitUntilError;
732
- }
733
- }
734
- await handler.runCallbacks("handlerDidComplete", {
735
- event,
736
- request,
737
- response,
738
- error
739
- });
740
- handler.destroy();
741
- if (error) {
742
- throw error;
743
- }
662
+ if (capture instanceof RegExp) {
663
+ return new RegExpRoute(capture, handler, method);
744
664
  }
745
- }
665
+ if (typeof capture === "function") {
666
+ return new Route(capture, handler, method);
667
+ }
668
+ if (capture instanceof Route) {
669
+ return capture;
670
+ }
671
+ throw new SerwistError("unsupported-route-type", {
672
+ moduleName: "serwist",
673
+ funcName: "parseRoute",
674
+ paramName: "capture"
675
+ });
676
+ };
746
677
 
747
- class PrecacheStrategy extends Strategy {
748
- _fallbackToNetwork;
749
- static defaultPrecacheCacheabilityPlugin = {
750
- async cacheWillUpdate ({ response }) {
751
- if (!response || response.status >= 400) {
752
- return null;
753
- }
754
- return response;
755
- }
756
- };
757
- static copyRedirectedCacheableResponsesPlugin = {
758
- async cacheWillUpdate ({ response }) {
759
- return response.redirected ? await copyResponse(response) : response;
760
- }
761
- };
762
- constructor(options = {}){
763
- options.cacheName = cacheNames$1.getPrecacheName(options.cacheName);
764
- super(options);
765
- this._fallbackToNetwork = options.fallbackToNetwork !== false;
766
- this.plugins.push(PrecacheStrategy.copyRedirectedCacheableResponsesPlugin);
678
+ const registerCapture = (state, capture, handler, method)=>{
679
+ const route = parseRoute(capture, handler, method);
680
+ registerRoute(state, route);
681
+ return route;
682
+ };
683
+ const registerRoute = (state, route)=>{
684
+ if (process.env.NODE_ENV !== "production") {
685
+ finalAssertExports.isType(route, "object", {
686
+ moduleName: "serwist",
687
+ className: "Serwist",
688
+ funcName: "registerRoute",
689
+ paramName: "route"
690
+ });
691
+ finalAssertExports.hasMethod(route, "match", {
692
+ moduleName: "serwist",
693
+ className: "Serwist",
694
+ funcName: "registerRoute",
695
+ paramName: "route"
696
+ });
697
+ finalAssertExports.isType(route.handler, "object", {
698
+ moduleName: "serwist",
699
+ className: "Serwist",
700
+ funcName: "registerRoute",
701
+ paramName: "route"
702
+ });
703
+ finalAssertExports.hasMethod(route.handler, "handle", {
704
+ moduleName: "serwist",
705
+ className: "Serwist",
706
+ funcName: "registerRoute",
707
+ paramName: "route.handler"
708
+ });
709
+ finalAssertExports.isType(route.method, "string", {
710
+ moduleName: "serwist",
711
+ className: "Serwist",
712
+ funcName: "registerRoute",
713
+ paramName: "route.method"
714
+ });
767
715
  }
768
- async _handle(request, handler) {
769
- const preloadResponse = await handler.getPreloadResponse();
770
- if (preloadResponse) {
771
- return preloadResponse;
772
- }
773
- const response = await handler.cacheMatch(request);
774
- if (response) {
775
- return response;
776
- }
777
- if (handler.event && handler.event.type === "install") {
778
- return await this._handleInstall(request, handler);
716
+ if (!state.routes.has(route.method)) {
717
+ state.routes.set(route.method, []);
718
+ }
719
+ state.routes.get(route.method).push(route);
720
+ };
721
+ const unregisterRoute = (state, route)=>{
722
+ if (!state.routes.has(route.method)) {
723
+ throw new SerwistError("unregister-route-but-not-found-with-method", {
724
+ method: route.method
725
+ });
726
+ }
727
+ const routeIndex = state.routes.get(route.method).indexOf(route);
728
+ if (routeIndex > -1) {
729
+ state.routes.get(route.method).splice(routeIndex, 1);
730
+ } else {
731
+ throw new SerwistError("unregister-route-route-not-registered");
732
+ }
733
+ };
734
+ const handleRequest = (state, { request, event })=>{
735
+ if (process.env.NODE_ENV !== "production") {
736
+ finalAssertExports.isInstance(request, Request, {
737
+ moduleName: "serwist",
738
+ className: "Serwist",
739
+ funcName: "handleRequest",
740
+ paramName: "options.request"
741
+ });
742
+ }
743
+ const url = new URL(request.url, location.href);
744
+ if (!url.protocol.startsWith("http")) {
745
+ if (process.env.NODE_ENV !== "production") {
746
+ logger.debug("Router only supports URLs that start with 'http'.");
779
747
  }
780
- return await this._handleFetch(request, handler);
748
+ return;
781
749
  }
782
- async _handleFetch(request, handler) {
783
- let response = undefined;
784
- const params = handler.params || {};
785
- if (this._fallbackToNetwork) {
786
- if (process.env.NODE_ENV !== "production") {
787
- logger.warn(`The precached response for ${getFriendlyURL(request.url)} in ${this.cacheName} was not found. Falling back to the network.`);
788
- }
789
- const integrityInManifest = params.integrity;
790
- const integrityInRequest = request.integrity;
791
- const noIntegrityConflict = !integrityInRequest || integrityInRequest === integrityInManifest;
792
- response = await handler.fetch(new Request(request, {
793
- integrity: request.mode !== "no-cors" ? integrityInRequest || integrityInManifest : undefined
794
- }));
795
- if (integrityInManifest && noIntegrityConflict && request.mode !== "no-cors") {
796
- this._useDefaultCacheabilityPluginIfNeeded();
797
- const wasCached = await handler.cachePut(request, response.clone());
798
- if (process.env.NODE_ENV !== "production") {
799
- if (wasCached) {
800
- logger.log(`A response for ${getFriendlyURL(request.url)} was used to "repair" the precache.`);
801
- }
802
- }
750
+ const sameOrigin = url.origin === location.origin;
751
+ const { params, route } = findMatchingRoute(state, {
752
+ event,
753
+ request,
754
+ sameOrigin,
755
+ url
756
+ });
757
+ let handler = route?.handler;
758
+ const debugMessages = [];
759
+ if (process.env.NODE_ENV !== "production") {
760
+ if (handler) {
761
+ debugMessages.push([
762
+ "Found a route to handle this request:",
763
+ route
764
+ ]);
765
+ if (params) {
766
+ debugMessages.push([
767
+ `Passing the following params to the route's handler:`,
768
+ params
769
+ ]);
803
770
  }
804
- } else {
805
- throw new SerwistError("missing-precache-entry", {
806
- cacheName: this.cacheName,
807
- url: request.url
808
- });
809
771
  }
772
+ }
773
+ const method = request.method;
774
+ if (!handler && state.defaultHandlerMap.has(method)) {
810
775
  if (process.env.NODE_ENV !== "production") {
811
- const cacheKey = params.cacheKey || await handler.getCacheKey(request, "read");
812
- logger.groupCollapsed(`Precaching is responding to: ${getFriendlyURL(request.url)}`);
813
- logger.log(`Serving the precached url: ${getFriendlyURL(cacheKey instanceof Request ? cacheKey.url : cacheKey)}`);
814
- logger.groupCollapsed("View request details here.");
815
- logger.log(request);
816
- logger.groupEnd();
817
- logger.groupCollapsed("View response details here.");
818
- logger.log(response);
819
- logger.groupEnd();
820
- logger.groupEnd();
776
+ debugMessages.push(`Failed to find a matching route. Falling back to the default handler for ${method}.`);
821
777
  }
822
- return response;
778
+ handler = state.defaultHandlerMap.get(method);
823
779
  }
824
- async _handleInstall(request, handler) {
825
- this._useDefaultCacheabilityPluginIfNeeded();
826
- const response = await handler.fetch(request);
827
- const wasCached = await handler.cachePut(request, response.clone());
828
- if (!wasCached) {
829
- throw new SerwistError("bad-precaching-response", {
830
- url: request.url,
831
- status: response.status
832
- });
780
+ if (!handler) {
781
+ if (process.env.NODE_ENV !== "production") {
782
+ logger.debug(`No route found for: ${getFriendlyURL(url)}`);
833
783
  }
834
- return response;
784
+ return;
835
785
  }
836
- _useDefaultCacheabilityPluginIfNeeded() {
837
- let defaultPluginIndex = null;
838
- let cacheWillUpdatePluginCount = 0;
839
- for (const [index, plugin] of this.plugins.entries()){
840
- if (plugin === PrecacheStrategy.copyRedirectedCacheableResponsesPlugin) {
841
- continue;
842
- }
843
- if (plugin === PrecacheStrategy.defaultPrecacheCacheabilityPlugin) {
844
- defaultPluginIndex = index;
845
- }
846
- if (plugin.cacheWillUpdate) {
847
- cacheWillUpdatePluginCount++;
786
+ if (process.env.NODE_ENV !== "production") {
787
+ logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`);
788
+ for (const msg of debugMessages){
789
+ if (Array.isArray(msg)) {
790
+ logger.log(...msg);
791
+ } else {
792
+ logger.log(msg);
848
793
  }
849
794
  }
850
- if (cacheWillUpdatePluginCount === 0) {
851
- this.plugins.push(PrecacheStrategy.defaultPrecacheCacheabilityPlugin);
852
- } else if (cacheWillUpdatePluginCount > 1 && defaultPluginIndex !== null) {
853
- this.plugins.splice(defaultPluginIndex, 1);
854
- }
855
- }
856
- }
857
-
858
- class PrecacheCacheKeyPlugin {
859
- _precacheController;
860
- constructor({ precacheController }){
861
- this._precacheController = precacheController;
862
- }
863
- cacheKeyWillBeUsed = async ({ request, params })=>{
864
- const cacheKey = params?.cacheKey || this._precacheController.getPrecacheKeyForUrl(request.url);
865
- return cacheKey ? new Request(cacheKey, {
866
- headers: request.headers
867
- }) : request;
868
- };
869
- }
870
-
871
- const parsePrecacheOptions = (controller, { cacheName, plugins, fetchOptions, matchOptions, fallbackToNetwork, directoryIndex, ignoreURLParametersMatching, cleanURLs, urlManipulation, cleanupOutdatedCaches, concurrency, navigateFallback, navigateFallbackAllowlist, navigateFallbackDenylist } = {})=>({
872
- strategyOptions: {
873
- cacheName: cacheNames$1.getPrecacheName(cacheName),
874
- plugins: [
875
- ...plugins ?? [],
876
- new PrecacheCacheKeyPlugin({
877
- precacheController: controller
878
- })
879
- ],
880
- fetchOptions,
881
- matchOptions,
882
- fallbackToNetwork
883
- },
884
- routeOptions: {
885
- directoryIndex,
886
- ignoreURLParametersMatching,
887
- cleanURLs,
888
- urlManipulation
889
- },
890
- controllerOptions: {
891
- cleanupOutdatedCaches,
892
- concurrency: concurrency ?? 10,
893
- navigateFallback,
894
- navigateFallbackAllowlist,
895
- navigateFallbackDenylist
896
- }
897
- });
898
-
899
- class PrecacheController {
900
- _urlsToCacheKeys = new Map();
901
- _urlsToCacheModes = new Map();
902
- _cacheKeysToIntegrities = new Map();
903
- _strategy;
904
- _options;
905
- _routeOptions;
906
- constructor(entries, precacheOptions){
907
- const { strategyOptions, routeOptions, controllerOptions } = parsePrecacheOptions(this, precacheOptions);
908
- this.addToCacheList(entries);
909
- this._strategy = new PrecacheStrategy(strategyOptions);
910
- this._options = controllerOptions;
911
- this._routeOptions = routeOptions;
795
+ logger.groupEnd();
912
796
  }
913
- get strategy() {
914
- return this._strategy;
797
+ let responsePromise;
798
+ try {
799
+ responsePromise = handler.handle({
800
+ url,
801
+ request,
802
+ event,
803
+ params
804
+ });
805
+ } catch (err) {
806
+ responsePromise = Promise.reject(err);
915
807
  }
916
- addToCacheList(entries) {
917
- if (process.env.NODE_ENV !== "production") {
918
- finalAssertExports.isArray(entries, {
919
- moduleName: "serwist",
920
- className: "PrecacheController",
921
- funcName: "addEntries",
922
- paramName: "entries"
923
- });
924
- }
925
- const urlsToWarnAbout = [];
926
- for (const entry of entries){
927
- if (typeof entry === "string") {
928
- urlsToWarnAbout.push(entry);
929
- } else if (entry && !entry.integrity && entry.revision === undefined) {
930
- urlsToWarnAbout.push(entry.url);
808
+ const catchHandler = route?.catchHandler;
809
+ if (responsePromise instanceof Promise && (state.catchHandler || catchHandler)) {
810
+ responsePromise = responsePromise.catch(async (err)=>{
811
+ if (catchHandler) {
812
+ if (process.env.NODE_ENV !== "production") {
813
+ logger.groupCollapsed(`Error thrown when responding to: ${getFriendlyURL(url)}. Falling back to route's Catch Handler.`);
814
+ logger.error("Error thrown by:", route);
815
+ logger.error(err);
816
+ logger.groupEnd();
817
+ }
818
+ try {
819
+ return await catchHandler.handle({
820
+ url,
821
+ request,
822
+ event,
823
+ params
824
+ });
825
+ } catch (catchErr) {
826
+ if (catchErr instanceof Error) {
827
+ err = catchErr;
828
+ }
829
+ }
931
830
  }
932
- const { cacheKey, url } = createCacheKey(entry);
933
- const cacheMode = typeof entry !== "string" && entry.revision ? "reload" : "default";
934
- if (this._urlsToCacheKeys.has(url) && this._urlsToCacheKeys.get(url) !== cacheKey) {
935
- throw new SerwistError("add-to-cache-list-conflicting-entries", {
936
- firstEntry: this._urlsToCacheKeys.get(url),
937
- secondEntry: cacheKey
831
+ if (state.catchHandler) {
832
+ if (process.env.NODE_ENV !== "production") {
833
+ logger.groupCollapsed(`Error thrown when responding to: ${getFriendlyURL(url)}. Falling back to global Catch Handler.`);
834
+ logger.error("Error thrown by:", route);
835
+ logger.error(err);
836
+ logger.groupEnd();
837
+ }
838
+ return state.catchHandler.handle({
839
+ url,
840
+ request,
841
+ event
938
842
  });
939
843
  }
940
- if (typeof entry !== "string" && entry.integrity) {
941
- if (this._cacheKeysToIntegrities.has(cacheKey) && this._cacheKeysToIntegrities.get(cacheKey) !== entry.integrity) {
942
- throw new SerwistError("add-to-cache-list-conflicting-integrities", {
943
- url
944
- });
844
+ throw err;
845
+ });
846
+ }
847
+ return responsePromise;
848
+ };
849
+ const findMatchingRoute = (state, { url, sameOrigin, request, event })=>{
850
+ const routes = state.routes.get(request.method) || [];
851
+ for (const route of routes){
852
+ let params;
853
+ const matchResult = route.match({
854
+ url,
855
+ sameOrigin,
856
+ request,
857
+ event
858
+ });
859
+ if (matchResult) {
860
+ if (process.env.NODE_ENV !== "production") {
861
+ if (matchResult instanceof Promise) {
862
+ logger.warn(`While routing ${getFriendlyURL(url)}, an async matchCallback function was used. Please convert the following route to use a synchronous matchCallback function:`, route);
945
863
  }
946
- this._cacheKeysToIntegrities.set(cacheKey, entry.integrity);
947
864
  }
948
- this._urlsToCacheKeys.set(url, cacheKey);
949
- this._urlsToCacheModes.set(url, cacheMode);
950
- if (urlsToWarnAbout.length > 0) {
951
- const warningMessage = `Serwist is precaching URLs without revision info: ${urlsToWarnAbout.join(", ")}\nThis is generally NOT safe, as you risk serving outdated assets.`;
952
- if (process.env.NODE_ENV === "production") {
953
- console.warn(warningMessage);
954
- } else {
955
- logger.warn(warningMessage);
956
- }
865
+ params = matchResult;
866
+ if (Array.isArray(params) && params.length === 0) {
867
+ params = undefined;
868
+ } else if (matchResult.constructor === Object && Object.keys(matchResult).length === 0) {
869
+ params = undefined;
870
+ } else if (typeof matchResult === "boolean") {
871
+ params = undefined;
957
872
  }
873
+ return {
874
+ route,
875
+ params
876
+ };
958
877
  }
959
878
  }
960
- init({ serwist }) {
961
- serwist.registerRoute(new PrecacheRoute(this, this._routeOptions));
962
- if (this._options.navigateFallback) {
963
- serwist.registerRoute(new NavigationRoute(this.createHandlerBoundToUrl(this._options.navigateFallback), {
964
- allowlist: this._options.navigateFallbackAllowlist,
965
- denylist: this._options.navigateFallbackDenylist
966
- }));
879
+ return {};
880
+ };
881
+
882
+ const cacheOkAndOpaquePlugin = {
883
+ cacheWillUpdate: async ({ response })=>{
884
+ if (response.status === 200 || response.status === 0) {
885
+ return response;
967
886
  }
887
+ return null;
968
888
  }
969
- async install({ event }) {
970
- const installReportPlugin = new PrecacheInstallReportPlugin();
971
- this._strategy.plugins.push(installReportPlugin);
972
- await parallel(this._options.concurrency, Array.from(this._urlsToCacheKeys.entries()), async ([url, cacheKey])=>{
973
- const integrity = this._cacheKeysToIntegrities.get(cacheKey);
974
- const cacheMode = this._urlsToCacheModes.get(url);
975
- const request = new Request(url, {
976
- integrity,
977
- cache: cacheMode,
978
- credentials: "same-origin"
979
- });
980
- await Promise.all(this._strategy.handleAll({
981
- event,
982
- request,
983
- url: new URL(request.url),
984
- params: {
985
- cacheKey
986
- }
987
- }));
988
- });
989
- const { updatedURLs, notUpdatedURLs } = installReportPlugin;
889
+ };
890
+
891
+ function toRequest(input) {
892
+ return typeof input === "string" ? new Request(input) : input;
893
+ }
894
+ class StrategyHandler {
895
+ event;
896
+ request;
897
+ url;
898
+ params;
899
+ _cacheKeys = {};
900
+ _strategy;
901
+ _handlerDeferred;
902
+ _extendLifetimePromises;
903
+ _plugins;
904
+ _pluginStateMap;
905
+ constructor(strategy, options){
990
906
  if (process.env.NODE_ENV !== "production") {
991
- printInstallDetails(updatedURLs, notUpdatedURLs);
992
- }
993
- }
994
- async activate() {
995
- const cache = await self.caches.open(this._strategy.cacheName);
996
- const currentlyCachedRequests = await cache.keys();
997
- const expectedCacheKeys = new Set(this._urlsToCacheKeys.values());
998
- const deletedCacheRequests = [];
999
- for (const request of currentlyCachedRequests){
1000
- if (!expectedCacheKeys.has(request.url)) {
1001
- await cache.delete(request);
1002
- deletedCacheRequests.push(request.url);
1003
- }
907
+ finalAssertExports.isInstance(options.event, ExtendableEvent, {
908
+ moduleName: "serwist",
909
+ className: "StrategyHandler",
910
+ funcName: "constructor",
911
+ paramName: "options.event"
912
+ });
913
+ finalAssertExports.isInstance(options.request, Request, {
914
+ moduleName: "serwist",
915
+ className: "StrategyHandler",
916
+ funcName: "constructor",
917
+ paramName: "options.request"
918
+ });
1004
919
  }
1005
- if (process.env.NODE_ENV !== "production") {
1006
- printCleanupDetails(deletedCacheRequests);
920
+ this.event = options.event;
921
+ this.request = options.request;
922
+ if (options.url) {
923
+ this.url = options.url;
924
+ this.params = options.params;
1007
925
  }
1008
- }
1009
- getUrlsToPrecacheKeys() {
1010
- return this._urlsToCacheKeys;
1011
- }
1012
- getPrecachedUrls() {
1013
- return [
1014
- ...this._urlsToCacheKeys.keys()
926
+ this._strategy = strategy;
927
+ this._handlerDeferred = new Deferred();
928
+ this._extendLifetimePromises = [];
929
+ this._plugins = [
930
+ ...strategy.plugins
1015
931
  ];
1016
- }
1017
- getPrecacheKeyForUrl(url) {
1018
- const urlObject = new URL(url, location.href);
1019
- return this._urlsToCacheKeys.get(urlObject.href);
1020
- }
1021
- getIntegrityForPrecacheKey(cacheKey) {
1022
- return this._cacheKeysToIntegrities.get(cacheKey);
1023
- }
1024
- async matchPrecache(request) {
1025
- const url = request instanceof Request ? request.url : request;
1026
- const cacheKey = this.getPrecacheKeyForUrl(url);
1027
- if (cacheKey) {
1028
- const cache = await self.caches.open(this._strategy.cacheName);
1029
- return cache.match(cacheKey);
932
+ this._pluginStateMap = new Map();
933
+ for (const plugin of this._plugins){
934
+ this._pluginStateMap.set(plugin, {});
1030
935
  }
1031
- return undefined;
936
+ this.event.waitUntil(this._handlerDeferred.promise);
1032
937
  }
1033
- createHandlerBoundToUrl(url) {
1034
- const cacheKey = this.getPrecacheKeyForUrl(url);
1035
- if (!cacheKey) {
1036
- throw new SerwistError("non-precached-url", {
1037
- url
1038
- });
938
+ async fetch(input) {
939
+ const { event } = this;
940
+ let request = toRequest(input);
941
+ const preloadResponse = await this.getPreloadResponse();
942
+ if (preloadResponse) {
943
+ return preloadResponse;
1039
944
  }
1040
- return (options)=>{
1041
- options.request = new Request(url);
1042
- options.params = {
1043
- cacheKey,
1044
- ...options.params
1045
- };
1046
- return this._strategy.handle(options);
1047
- };
1048
- }
1049
- }
1050
-
1051
- class PrecacheFallbackPlugin {
1052
- _fallbackUrls;
1053
- _precacheController;
1054
- constructor({ fallbackUrls, precacheController, serwist }){
1055
- this._fallbackUrls = fallbackUrls;
1056
- if (!serwist) {
1057
- this._precacheController = precacheController;
1058
- } else {
1059
- this._precacheController = serwist.precache;
945
+ const originalRequest = this.hasCallback("fetchDidFail") ? request.clone() : null;
946
+ try {
947
+ for (const cb of this.iterateCallbacks("requestWillFetch")){
948
+ request = await cb({
949
+ request: request.clone(),
950
+ event
951
+ });
952
+ }
953
+ } catch (err) {
954
+ if (err instanceof Error) {
955
+ throw new SerwistError("plugin-error-request-will-fetch", {
956
+ thrownErrorMessage: err.message
957
+ });
958
+ }
1060
959
  }
1061
- }
1062
- async handlerDidError(param) {
1063
- for (const fallback of this._fallbackUrls){
1064
- if (typeof fallback === "string") {
1065
- const fallbackResponse = await this._precacheController.matchPrecache(fallback);
1066
- if (fallbackResponse !== undefined) {
1067
- return fallbackResponse;
1068
- }
1069
- } else if (fallback.matcher(param)) {
1070
- const fallbackResponse = await this._precacheController.matchPrecache(fallback.url);
1071
- if (fallbackResponse !== undefined) {
1072
- return fallbackResponse;
1073
- }
960
+ const pluginFilteredRequest = request.clone();
961
+ try {
962
+ let fetchResponse;
963
+ fetchResponse = await fetch(request, request.mode === "navigate" ? undefined : this._strategy.fetchOptions);
964
+ if (process.env.NODE_ENV !== "production") {
965
+ logger.debug(`Network request for '${getFriendlyURL(request.url)}' returned a response with status '${fetchResponse.status}'.`);
966
+ }
967
+ for (const callback of this.iterateCallbacks("fetchDidSucceed")){
968
+ fetchResponse = await callback({
969
+ event,
970
+ request: pluginFilteredRequest,
971
+ response: fetchResponse
972
+ });
1074
973
  }
974
+ return fetchResponse;
975
+ } catch (error) {
976
+ if (process.env.NODE_ENV !== "production") {
977
+ logger.log(`Network request for '${getFriendlyURL(request.url)}' threw an error.`, error);
978
+ }
979
+ if (originalRequest) {
980
+ await this.runCallbacks("fetchDidFail", {
981
+ error: error,
982
+ event,
983
+ originalRequest: originalRequest.clone(),
984
+ request: pluginFilteredRequest.clone()
985
+ });
986
+ }
987
+ throw error;
1075
988
  }
1076
- return undefined;
1077
989
  }
1078
- }
1079
-
1080
- class RuntimeCacheController {
1081
- _entries;
1082
- _options;
1083
- constructor(entries, options = {}){
1084
- this._entries = entries;
1085
- this._options = options;
1086
- this.init = this.init.bind(this);
1087
- this.install = this.install.bind(this);
990
+ async fetchAndCachePut(input) {
991
+ const response = await this.fetch(input);
992
+ const responseClone = response.clone();
993
+ void this.waitUntil(this.cachePut(input, responseClone));
994
+ return response;
1088
995
  }
1089
- init({ serwist }) {
1090
- if (this._options.fallbacks !== undefined) {
1091
- const fallbackPlugin = new PrecacheFallbackPlugin({
1092
- fallbackUrls: this._options.fallbacks.entries,
1093
- precacheController: serwist.precache
1094
- });
1095
- this._entries.forEach((cacheEntry)=>{
1096
- if (cacheEntry.handler instanceof Strategy && !cacheEntry.handler.plugins.some((plugin)=>"handlerDidError" in plugin)) {
1097
- cacheEntry.handler.plugins.push(fallbackPlugin);
1098
- }
1099
- });
996
+ async cacheMatch(key) {
997
+ const request = toRequest(key);
998
+ let cachedResponse;
999
+ const { cacheName, matchOptions } = this._strategy;
1000
+ const effectiveRequest = await this.getCacheKey(request, "read");
1001
+ const multiMatchOptions = {
1002
+ ...matchOptions,
1003
+ ...{
1004
+ cacheName
1005
+ }
1006
+ };
1007
+ cachedResponse = await caches.match(effectiveRequest, multiMatchOptions);
1008
+ if (process.env.NODE_ENV !== "production") {
1009
+ if (cachedResponse) {
1010
+ logger.debug(`Found a cached response in '${cacheName}'.`);
1011
+ } else {
1012
+ logger.debug(`No cached response found in '${cacheName}'.`);
1013
+ }
1100
1014
  }
1101
- for (const entry of this._entries){
1102
- serwist.registerCapture(entry.matcher, entry.handler, entry.method);
1015
+ for (const callback of this.iterateCallbacks("cachedResponseWillBeUsed")){
1016
+ cachedResponse = await callback({
1017
+ cacheName,
1018
+ matchOptions,
1019
+ cachedResponse,
1020
+ request: effectiveRequest,
1021
+ event: this.event
1022
+ }) || undefined;
1103
1023
  }
1024
+ return cachedResponse;
1104
1025
  }
1105
- async install({ event, serwist }) {
1106
- const concurrency = this._options.warmOptions?.concurrency ?? 10;
1107
- if (this._options.warmEntries) {
1108
- await parallel(concurrency, this._options.warmEntries, async (entry)=>{
1109
- const request = entry instanceof Request ? entry : new Request(typeof entry === "string" ? entry : entry.url, {
1110
- integrity: typeof entry !== "string" ? entry.integrity : undefined,
1111
- credentials: "same-origin"
1112
- });
1113
- await serwist.handleRequest({
1114
- request,
1115
- event
1026
+ async cachePut(key, response) {
1027
+ const request = toRequest(key);
1028
+ await timeout(0);
1029
+ const effectiveRequest = await this.getCacheKey(request, "write");
1030
+ if (process.env.NODE_ENV !== "production") {
1031
+ if (effectiveRequest.method && effectiveRequest.method !== "GET") {
1032
+ throw new SerwistError("attempt-to-cache-non-get-request", {
1033
+ url: getFriendlyURL(effectiveRequest.url),
1034
+ method: effectiveRequest.method
1116
1035
  });
1117
- });
1036
+ }
1118
1037
  }
1119
- }
1120
- warmRuntimeCache(entries) {
1121
- if (!this._options.warmEntries) this._options.warmEntries = [];
1122
- this._options.warmEntries.push(...entries);
1123
- }
1124
- }
1125
-
1126
- const BACKGROUND_SYNC_DB_VERSION = 3;
1127
- const BACKGROUND_SYNC_DB_NAME = "serwist-background-sync";
1128
- const REQUEST_OBJECT_STORE_NAME = "requests";
1129
- const QUEUE_NAME_INDEX = "queueName";
1130
- class BackgroundSyncQueueDb {
1131
- _db = null;
1132
- async addEntry(entry) {
1133
- const db = await this.getDb();
1134
- const tx = db.transaction(REQUEST_OBJECT_STORE_NAME, "readwrite", {
1135
- durability: "relaxed"
1136
- });
1137
- await tx.store.add(entry);
1138
- await tx.done;
1139
- }
1140
- async getFirstEntryId() {
1141
- const db = await this.getDb();
1142
- const cursor = await db.transaction(REQUEST_OBJECT_STORE_NAME).store.openCursor();
1143
- return cursor?.value.id;
1144
- }
1145
- async getAllEntriesByQueueName(queueName) {
1146
- const db = await this.getDb();
1147
- const results = await db.getAllFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
1148
- return results ? results : new Array();
1149
- }
1150
- async getEntryCountByQueueName(queueName) {
1151
- const db = await this.getDb();
1152
- return db.countFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
1153
- }
1154
- async deleteEntry(id) {
1155
- const db = await this.getDb();
1156
- await db.delete(REQUEST_OBJECT_STORE_NAME, id);
1157
- }
1158
- async getFirstEntryByQueueName(queueName) {
1159
- return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), "next");
1160
- }
1161
- async getLastEntryByQueueName(queueName) {
1162
- return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), "prev");
1163
- }
1164
- async getEndEntryFromIndex(query, direction) {
1165
- const db = await this.getDb();
1166
- const cursor = await db.transaction(REQUEST_OBJECT_STORE_NAME).store.index(QUEUE_NAME_INDEX).openCursor(query, direction);
1167
- return cursor?.value;
1168
- }
1169
- async getDb() {
1170
- if (!this._db) {
1171
- this._db = await openDB(BACKGROUND_SYNC_DB_NAME, BACKGROUND_SYNC_DB_VERSION, {
1172
- upgrade: this._upgradeDb
1038
+ if (!response) {
1039
+ if (process.env.NODE_ENV !== "production") {
1040
+ logger.error(`Cannot cache non-existent response for '${getFriendlyURL(effectiveRequest.url)}'.`);
1041
+ }
1042
+ throw new SerwistError("cache-put-with-no-response", {
1043
+ url: getFriendlyURL(effectiveRequest.url)
1173
1044
  });
1174
1045
  }
1175
- return this._db;
1176
- }
1177
- _upgradeDb(db, oldVersion) {
1178
- if (oldVersion > 0 && oldVersion < BACKGROUND_SYNC_DB_VERSION) {
1179
- if (db.objectStoreNames.contains(REQUEST_OBJECT_STORE_NAME)) {
1180
- db.deleteObjectStore(REQUEST_OBJECT_STORE_NAME);
1046
+ const responseToCache = await this._ensureResponseSafeToCache(response);
1047
+ if (!responseToCache) {
1048
+ if (process.env.NODE_ENV !== "production") {
1049
+ logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' will not be cached.`, responseToCache);
1181
1050
  }
1051
+ return false;
1182
1052
  }
1183
- const objStore = db.createObjectStore(REQUEST_OBJECT_STORE_NAME, {
1184
- autoIncrement: true,
1185
- keyPath: "id"
1186
- });
1187
- objStore.createIndex(QUEUE_NAME_INDEX, QUEUE_NAME_INDEX, {
1188
- unique: false
1189
- });
1190
- }
1191
- }
1192
-
1193
- class BackgroundSyncQueueStore {
1194
- _queueName;
1195
- _queueDb;
1196
- constructor(queueName){
1197
- this._queueName = queueName;
1198
- this._queueDb = new BackgroundSyncQueueDb();
1199
- }
1200
- async pushEntry(entry) {
1053
+ const { cacheName, matchOptions } = this._strategy;
1054
+ const cache = await self.caches.open(cacheName);
1201
1055
  if (process.env.NODE_ENV !== "production") {
1202
- finalAssertExports.isType(entry, "object", {
1203
- moduleName: "serwist",
1204
- className: "BackgroundSyncQueueStore",
1205
- funcName: "pushEntry",
1206
- paramName: "entry"
1207
- });
1208
- finalAssertExports.isType(entry.requestData, "object", {
1209
- moduleName: "serwist",
1210
- className: "BackgroundSyncQueueStore",
1211
- funcName: "pushEntry",
1212
- paramName: "entry.requestData"
1056
+ const vary = response.headers.get("Vary");
1057
+ if (vary && matchOptions?.ignoreVary !== true) {
1058
+ logger.debug(`The response for ${getFriendlyURL(effectiveRequest.url)} has a 'Vary: ${vary}' header. Consider setting the {ignoreVary: true} option on your strategy to ensure cache matching and deletion works as expected.`);
1059
+ }
1060
+ }
1061
+ const hasCacheUpdateCallback = this.hasCallback("cacheDidUpdate");
1062
+ const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams(cache, effectiveRequest.clone(), [
1063
+ "__WB_REVISION__"
1064
+ ], matchOptions) : null;
1065
+ if (process.env.NODE_ENV !== "production") {
1066
+ logger.debug(`Updating the '${cacheName}' cache with a new Response for ${getFriendlyURL(effectiveRequest.url)}.`);
1067
+ }
1068
+ try {
1069
+ await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache);
1070
+ } catch (error) {
1071
+ if (error instanceof Error) {
1072
+ if (error.name === "QuotaExceededError") {
1073
+ await executeQuotaErrorCallbacks();
1074
+ }
1075
+ throw error;
1076
+ }
1077
+ }
1078
+ for (const callback of this.iterateCallbacks("cacheDidUpdate")){
1079
+ await callback({
1080
+ cacheName,
1081
+ oldResponse,
1082
+ newResponse: responseToCache.clone(),
1083
+ request: effectiveRequest,
1084
+ event: this.event
1213
1085
  });
1214
1086
  }
1215
- delete entry.id;
1216
- entry.queueName = this._queueName;
1217
- await this._queueDb.addEntry(entry);
1087
+ return true;
1218
1088
  }
1219
- async unshiftEntry(entry) {
1220
- if (process.env.NODE_ENV !== "production") {
1221
- finalAssertExports.isType(entry, "object", {
1222
- moduleName: "serwist",
1223
- className: "BackgroundSyncQueueStore",
1224
- funcName: "unshiftEntry",
1225
- paramName: "entry"
1226
- });
1227
- finalAssertExports.isType(entry.requestData, "object", {
1228
- moduleName: "serwist",
1229
- className: "BackgroundSyncQueueStore",
1230
- funcName: "unshiftEntry",
1231
- paramName: "entry.requestData"
1232
- });
1089
+ async getCacheKey(request, mode) {
1090
+ const key = `${request.url} | ${mode}`;
1091
+ if (!this._cacheKeys[key]) {
1092
+ let effectiveRequest = request;
1093
+ for (const callback of this.iterateCallbacks("cacheKeyWillBeUsed")){
1094
+ effectiveRequest = toRequest(await callback({
1095
+ mode,
1096
+ request: effectiveRequest,
1097
+ event: this.event,
1098
+ params: this.params
1099
+ }));
1100
+ }
1101
+ this._cacheKeys[key] = effectiveRequest;
1233
1102
  }
1234
- const firstId = await this._queueDb.getFirstEntryId();
1235
- if (firstId) {
1236
- entry.id = firstId - 1;
1237
- } else {
1238
- delete entry.id;
1103
+ return this._cacheKeys[key];
1104
+ }
1105
+ hasCallback(name) {
1106
+ for (const plugin of this._strategy.plugins){
1107
+ if (name in plugin) {
1108
+ return true;
1109
+ }
1239
1110
  }
1240
- entry.queueName = this._queueName;
1241
- await this._queueDb.addEntry(entry);
1111
+ return false;
1242
1112
  }
1243
- async popEntry() {
1244
- return this._removeEntry(await this._queueDb.getLastEntryByQueueName(this._queueName));
1113
+ async runCallbacks(name, param) {
1114
+ for (const callback of this.iterateCallbacks(name)){
1115
+ await callback(param);
1116
+ }
1245
1117
  }
1246
- async shiftEntry() {
1247
- return this._removeEntry(await this._queueDb.getFirstEntryByQueueName(this._queueName));
1118
+ *iterateCallbacks(name) {
1119
+ for (const plugin of this._strategy.plugins){
1120
+ if (typeof plugin[name] === "function") {
1121
+ const state = this._pluginStateMap.get(plugin);
1122
+ const statefulCallback = (param)=>{
1123
+ const statefulParam = {
1124
+ ...param,
1125
+ state
1126
+ };
1127
+ return plugin[name](statefulParam);
1128
+ };
1129
+ yield statefulCallback;
1130
+ }
1131
+ }
1248
1132
  }
1249
- async getAll() {
1250
- return await this._queueDb.getAllEntriesByQueueName(this._queueName);
1133
+ waitUntil(promise) {
1134
+ this._extendLifetimePromises.push(promise);
1135
+ return promise;
1251
1136
  }
1252
- async size() {
1253
- return await this._queueDb.getEntryCountByQueueName(this._queueName);
1137
+ async doneWaiting() {
1138
+ let promise;
1139
+ while(promise = this._extendLifetimePromises.shift()){
1140
+ await promise;
1141
+ }
1254
1142
  }
1255
- async deleteEntry(id) {
1256
- await this._queueDb.deleteEntry(id);
1143
+ destroy() {
1144
+ this._handlerDeferred.resolve(null);
1257
1145
  }
1258
- async _removeEntry(entry) {
1259
- if (entry) {
1260
- await this.deleteEntry(entry.id);
1146
+ async getPreloadResponse() {
1147
+ if (this.event instanceof FetchEvent && this.event.request.mode === "navigate" && "preloadResponse" in this.event) {
1148
+ try {
1149
+ const possiblePreloadResponse = await this.event.preloadResponse;
1150
+ if (possiblePreloadResponse) {
1151
+ if (process.env.NODE_ENV !== "production") {
1152
+ logger.log(`Using a preloaded navigation response for '${getFriendlyURL(this.event.request.url)}'`);
1153
+ }
1154
+ return possiblePreloadResponse;
1155
+ }
1156
+ } catch (error) {
1157
+ if (process.env.NODE_ENV !== "production") {
1158
+ logger.error(error);
1159
+ }
1160
+ return undefined;
1161
+ }
1261
1162
  }
1262
- return entry;
1163
+ return undefined;
1164
+ }
1165
+ async _ensureResponseSafeToCache(response) {
1166
+ let responseToCache = response;
1167
+ let pluginsUsed = false;
1168
+ for (const callback of this.iterateCallbacks("cacheWillUpdate")){
1169
+ responseToCache = await callback({
1170
+ request: this.request,
1171
+ response: responseToCache,
1172
+ event: this.event
1173
+ }) || undefined;
1174
+ pluginsUsed = true;
1175
+ if (!responseToCache) {
1176
+ break;
1177
+ }
1178
+ }
1179
+ if (!pluginsUsed) {
1180
+ if (responseToCache && responseToCache.status !== 200) {
1181
+ if (process.env.NODE_ENV !== "production") {
1182
+ if (responseToCache.status === 0) {
1183
+ logger.warn(`The response for '${this.request.url}' is an opaque response. The caching strategy that you're using will not cache opaque responses by default.`);
1184
+ } else {
1185
+ logger.debug(`The response for '${this.request.url}' returned a status code of '${response.status}' and won't be cached as a result.`);
1186
+ }
1187
+ }
1188
+ responseToCache = undefined;
1189
+ }
1190
+ }
1191
+ return responseToCache;
1263
1192
  }
1264
1193
  }
1265
1194
 
1266
- const serializableProperties = [
1267
- "method",
1268
- "referrer",
1269
- "referrerPolicy",
1270
- "mode",
1271
- "credentials",
1272
- "cache",
1273
- "redirect",
1274
- "integrity",
1275
- "keepalive"
1276
- ];
1277
- class StorableRequest {
1278
- _requestData;
1279
- static async fromRequest(request) {
1280
- const requestData = {
1281
- url: request.url,
1282
- headers: {}
1283
- };
1284
- if (request.method !== "GET") {
1285
- requestData.body = await request.clone().arrayBuffer();
1195
+ class Strategy {
1196
+ cacheName;
1197
+ plugins;
1198
+ fetchOptions;
1199
+ matchOptions;
1200
+ constructor(options = {}){
1201
+ this.cacheName = cacheNames$1.getRuntimeName(options.cacheName);
1202
+ this.plugins = options.plugins || [];
1203
+ this.fetchOptions = options.fetchOptions;
1204
+ this.matchOptions = options.matchOptions;
1205
+ }
1206
+ handle(options) {
1207
+ const [responseDone] = this.handleAll(options);
1208
+ return responseDone;
1209
+ }
1210
+ handleAll(options) {
1211
+ if (options instanceof FetchEvent) {
1212
+ options = {
1213
+ event: options,
1214
+ request: options.request
1215
+ };
1286
1216
  }
1287
- request.headers.forEach((value, key)=>{
1288
- requestData.headers[key] = value;
1217
+ const event = options.event;
1218
+ const request = typeof options.request === "string" ? new Request(options.request) : options.request;
1219
+ const handler = new StrategyHandler(this, options.url ? {
1220
+ event,
1221
+ request,
1222
+ url: options.url,
1223
+ params: options.params
1224
+ } : {
1225
+ event,
1226
+ request
1289
1227
  });
1290
- for (const prop of serializableProperties){
1291
- if (request[prop] !== undefined) {
1292
- requestData[prop] = request[prop];
1293
- }
1294
- }
1295
- return new StorableRequest(requestData);
1228
+ const responseDone = this._getResponse(handler, request, event);
1229
+ const handlerDone = this._awaitComplete(responseDone, handler, request, event);
1230
+ return [
1231
+ responseDone,
1232
+ handlerDone
1233
+ ];
1296
1234
  }
1297
- constructor(requestData){
1298
- if (process.env.NODE_ENV !== "production") {
1299
- finalAssertExports.isType(requestData, "object", {
1300
- moduleName: "serwist",
1301
- className: "StorableRequest",
1302
- funcName: "constructor",
1303
- paramName: "requestData"
1304
- });
1305
- finalAssertExports.isType(requestData.url, "string", {
1306
- moduleName: "serwist",
1307
- className: "StorableRequest",
1308
- funcName: "constructor",
1309
- paramName: "requestData.url"
1310
- });
1235
+ async _getResponse(handler, request, event) {
1236
+ await handler.runCallbacks("handlerWillStart", {
1237
+ event,
1238
+ request
1239
+ });
1240
+ let response;
1241
+ try {
1242
+ response = await this._handle(request, handler);
1243
+ if (response === undefined || response.type === "error") {
1244
+ throw new SerwistError("no-response", {
1245
+ url: request.url
1246
+ });
1247
+ }
1248
+ } catch (error) {
1249
+ if (error instanceof Error) {
1250
+ for (const callback of handler.iterateCallbacks("handlerDidError")){
1251
+ response = await callback({
1252
+ error,
1253
+ event,
1254
+ request
1255
+ });
1256
+ if (response !== undefined) {
1257
+ break;
1258
+ }
1259
+ }
1260
+ }
1261
+ if (!response) {
1262
+ throw error;
1263
+ }
1264
+ if (process.env.NODE_ENV !== "production") {
1265
+ throw logger.log(`While responding to '${getFriendlyURL(request.url)}', an ${error instanceof Error ? error.toString() : ""} error occurred. Using a fallback response provided by a handlerDidError plugin.`);
1266
+ }
1311
1267
  }
1312
- if (requestData.mode === "navigate") {
1313
- requestData.mode = "same-origin";
1268
+ for (const callback of handler.iterateCallbacks("handlerWillRespond")){
1269
+ response = await callback({
1270
+ event,
1271
+ request,
1272
+ response
1273
+ });
1314
1274
  }
1315
- this._requestData = requestData;
1275
+ return response;
1316
1276
  }
1317
- toObject() {
1318
- const requestData = Object.assign({}, this._requestData);
1319
- requestData.headers = Object.assign({}, this._requestData.headers);
1320
- if (requestData.body) {
1321
- requestData.body = requestData.body.slice(0);
1277
+ async _awaitComplete(responseDone, handler, request, event) {
1278
+ let response;
1279
+ let error;
1280
+ try {
1281
+ response = await responseDone;
1282
+ } catch {}
1283
+ try {
1284
+ await handler.runCallbacks("handlerDidRespond", {
1285
+ event,
1286
+ request,
1287
+ response
1288
+ });
1289
+ await handler.doneWaiting();
1290
+ } catch (waitUntilError) {
1291
+ if (waitUntilError instanceof Error) {
1292
+ error = waitUntilError;
1293
+ }
1294
+ }
1295
+ await handler.runCallbacks("handlerDidComplete", {
1296
+ event,
1297
+ request,
1298
+ response,
1299
+ error
1300
+ });
1301
+ handler.destroy();
1302
+ if (error) {
1303
+ throw error;
1322
1304
  }
1323
- return requestData;
1324
- }
1325
- toRequest() {
1326
- return new Request(this._requestData.url, this._requestData);
1327
- }
1328
- clone() {
1329
- return new StorableRequest(this.toObject());
1330
1305
  }
1331
1306
  }
1332
1307
 
1333
- const TAG_PREFIX = "serwist-background-sync";
1334
- const MAX_RETENTION_TIME$1 = 60 * 24 * 7;
1335
- const queueNames = new Set();
1336
- const convertEntry = (queueStoreEntry)=>{
1337
- const queueEntry = {
1338
- request: new StorableRequest(queueStoreEntry.requestData).toRequest(),
1339
- timestamp: queueStoreEntry.timestamp
1340
- };
1341
- if (queueStoreEntry.metadata) {
1342
- queueEntry.metadata = queueStoreEntry.metadata;
1308
+ const messages = {
1309
+ strategyStart: (strategyName, request)=>`Using ${strategyName} to respond to '${getFriendlyURL(request.url)}'`,
1310
+ printFinalResponse: (response)=>{
1311
+ if (response) {
1312
+ logger.groupCollapsed("View the final response here.");
1313
+ logger.log(response || "[No response returned]");
1314
+ logger.groupEnd();
1315
+ }
1343
1316
  }
1344
- return queueEntry;
1345
1317
  };
1346
- class BackgroundSyncQueue {
1347
- _name;
1348
- _onSync;
1349
- _maxRetentionTime;
1350
- _queueStore;
1351
- _forceSyncFallback;
1352
- _syncInProgress = false;
1353
- _requestsAddedDuringSync = false;
1354
- constructor(name, { forceSyncFallback, onSync, maxRetentionTime } = {}){
1355
- if (queueNames.has(name)) {
1356
- throw new SerwistError("duplicate-queue-name", {
1357
- name
1358
- });
1318
+
1319
+ class NetworkFirst extends Strategy {
1320
+ _networkTimeoutSeconds;
1321
+ constructor(options = {}){
1322
+ super(options);
1323
+ if (!this.plugins.some((p)=>"cacheWillUpdate" in p)) {
1324
+ this.plugins.unshift(cacheOkAndOpaquePlugin);
1325
+ }
1326
+ this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
1327
+ if (process.env.NODE_ENV !== "production") {
1328
+ if (this._networkTimeoutSeconds) {
1329
+ finalAssertExports.isType(this._networkTimeoutSeconds, "number", {
1330
+ moduleName: "serwist",
1331
+ className: this.constructor.name,
1332
+ funcName: "constructor",
1333
+ paramName: "networkTimeoutSeconds"
1334
+ });
1335
+ }
1359
1336
  }
1360
- queueNames.add(name);
1361
- this._name = name;
1362
- this._onSync = onSync || this.replayRequests;
1363
- this._maxRetentionTime = maxRetentionTime || MAX_RETENTION_TIME$1;
1364
- this._forceSyncFallback = Boolean(forceSyncFallback);
1365
- this._queueStore = new BackgroundSyncQueueStore(this._name);
1366
- this._addSyncListener();
1367
- }
1368
- get name() {
1369
- return this._name;
1370
1337
  }
1371
- async pushRequest(entry) {
1338
+ async _handle(request, handler) {
1339
+ const logs = [];
1372
1340
  if (process.env.NODE_ENV !== "production") {
1373
- finalAssertExports.isType(entry, "object", {
1341
+ finalAssertExports.isInstance(request, Request, {
1374
1342
  moduleName: "serwist",
1375
- className: "BackgroundSyncQueue",
1376
- funcName: "pushRequest",
1377
- paramName: "entry"
1343
+ className: this.constructor.name,
1344
+ funcName: "handle",
1345
+ paramName: "makeRequest"
1378
1346
  });
1379
- finalAssertExports.isInstance(entry.request, Request, {
1380
- moduleName: "serwist",
1381
- className: "BackgroundSyncQueue",
1382
- funcName: "pushRequest",
1383
- paramName: "entry.request"
1347
+ }
1348
+ const promises = [];
1349
+ let timeoutId;
1350
+ if (this._networkTimeoutSeconds) {
1351
+ const { id, promise } = this._getTimeoutPromise({
1352
+ request,
1353
+ logs,
1354
+ handler
1384
1355
  });
1356
+ timeoutId = id;
1357
+ promises.push(promise);
1385
1358
  }
1386
- await this._addRequest(entry, "push");
1387
- }
1388
- async unshiftRequest(entry) {
1359
+ const networkPromise = this._getNetworkPromise({
1360
+ timeoutId,
1361
+ request,
1362
+ logs,
1363
+ handler
1364
+ });
1365
+ promises.push(networkPromise);
1366
+ const response = await handler.waitUntil((async ()=>{
1367
+ return await handler.waitUntil(Promise.race(promises)) || await networkPromise;
1368
+ })());
1389
1369
  if (process.env.NODE_ENV !== "production") {
1390
- finalAssertExports.isType(entry, "object", {
1391
- moduleName: "serwist",
1392
- className: "BackgroundSyncQueue",
1393
- funcName: "unshiftRequest",
1394
- paramName: "entry"
1395
- });
1396
- finalAssertExports.isInstance(entry.request, Request, {
1397
- moduleName: "serwist",
1398
- className: "BackgroundSyncQueue",
1399
- funcName: "unshiftRequest",
1400
- paramName: "entry.request"
1370
+ logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
1371
+ for (const log of logs){
1372
+ logger.log(log);
1373
+ }
1374
+ messages.printFinalResponse(response);
1375
+ logger.groupEnd();
1376
+ }
1377
+ if (!response) {
1378
+ throw new SerwistError("no-response", {
1379
+ url: request.url
1401
1380
  });
1402
1381
  }
1403
- await this._addRequest(entry, "unshift");
1404
- }
1405
- async popRequest() {
1406
- return this._removeRequest("pop");
1382
+ return response;
1407
1383
  }
1408
- async shiftRequest() {
1409
- return this._removeRequest("shift");
1384
+ _getTimeoutPromise({ request, logs, handler }) {
1385
+ let timeoutId;
1386
+ const timeoutPromise = new Promise((resolve)=>{
1387
+ const onNetworkTimeout = async ()=>{
1388
+ if (process.env.NODE_ENV !== "production") {
1389
+ logs.push(`Timing out the network response at ${this._networkTimeoutSeconds} seconds.`);
1390
+ }
1391
+ resolve(await handler.cacheMatch(request));
1392
+ };
1393
+ timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1000);
1394
+ });
1395
+ return {
1396
+ promise: timeoutPromise,
1397
+ id: timeoutId
1398
+ };
1410
1399
  }
1411
- async getAll() {
1412
- const allEntries = await this._queueStore.getAll();
1413
- const now = Date.now();
1414
- const unexpiredEntries = [];
1415
- for (const entry of allEntries){
1416
- const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
1417
- if (now - entry.timestamp > maxRetentionTimeInMs) {
1418
- await this._queueStore.deleteEntry(entry.id);
1400
+ async _getNetworkPromise({ timeoutId, request, logs, handler }) {
1401
+ let error;
1402
+ let response;
1403
+ try {
1404
+ response = await handler.fetchAndCachePut(request);
1405
+ } catch (fetchError) {
1406
+ if (fetchError instanceof Error) {
1407
+ error = fetchError;
1408
+ }
1409
+ }
1410
+ if (timeoutId) {
1411
+ clearTimeout(timeoutId);
1412
+ }
1413
+ if (process.env.NODE_ENV !== "production") {
1414
+ if (response) {
1415
+ logs.push("Got response from network.");
1419
1416
  } else {
1420
- unexpiredEntries.push(convertEntry(entry));
1417
+ logs.push("Unable to get a response from the network. Will respond " + "with a cached response.");
1421
1418
  }
1422
1419
  }
1423
- return unexpiredEntries;
1420
+ if (error || !response) {
1421
+ response = await handler.cacheMatch(request);
1422
+ if (process.env.NODE_ENV !== "production") {
1423
+ if (response) {
1424
+ logs.push(`Found a cached response in the '${this.cacheName}' cache.`);
1425
+ } else {
1426
+ logs.push(`No response found in the '${this.cacheName}' cache.`);
1427
+ }
1428
+ }
1429
+ }
1430
+ return response;
1424
1431
  }
1425
- async size() {
1426
- return await this._queueStore.size();
1432
+ }
1433
+
1434
+ class NetworkOnly extends Strategy {
1435
+ _networkTimeoutSeconds;
1436
+ constructor(options = {}){
1437
+ super(options);
1438
+ this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
1427
1439
  }
1428
- async _addRequest({ request, metadata, timestamp = Date.now() }, operation) {
1429
- const storableRequest = await StorableRequest.fromRequest(request.clone());
1430
- const entry = {
1431
- requestData: storableRequest.toObject(),
1432
- timestamp
1433
- };
1434
- if (metadata) {
1435
- entry.metadata = metadata;
1440
+ async _handle(request, handler) {
1441
+ if (process.env.NODE_ENV !== "production") {
1442
+ finalAssertExports.isInstance(request, Request, {
1443
+ moduleName: "serwist",
1444
+ className: this.constructor.name,
1445
+ funcName: "_handle",
1446
+ paramName: "request"
1447
+ });
1436
1448
  }
1437
- switch(operation){
1438
- case "push":
1439
- await this._queueStore.pushEntry(entry);
1440
- break;
1441
- case "unshift":
1442
- await this._queueStore.unshiftEntry(entry);
1443
- break;
1449
+ let error;
1450
+ let response;
1451
+ try {
1452
+ const promises = [
1453
+ handler.fetch(request)
1454
+ ];
1455
+ if (this._networkTimeoutSeconds) {
1456
+ const timeoutPromise = timeout(this._networkTimeoutSeconds * 1000);
1457
+ promises.push(timeoutPromise);
1458
+ }
1459
+ response = await Promise.race(promises);
1460
+ if (!response) {
1461
+ throw new Error(`Timed out the network response after ${this._networkTimeoutSeconds} seconds.`);
1462
+ }
1463
+ } catch (err) {
1464
+ if (err instanceof Error) {
1465
+ error = err;
1466
+ }
1444
1467
  }
1445
1468
  if (process.env.NODE_ENV !== "production") {
1446
- logger.log(`Request for '${getFriendlyURL(request.url)}' has ` + `been added to background sync queue '${this._name}'.`);
1469
+ logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
1470
+ if (response) {
1471
+ logger.log("Got response from network.");
1472
+ } else {
1473
+ logger.log("Unable to get a response from the network.");
1474
+ }
1475
+ messages.printFinalResponse(response);
1476
+ logger.groupEnd();
1447
1477
  }
1448
- if (this._syncInProgress) {
1449
- this._requestsAddedDuringSync = true;
1450
- } else {
1451
- await this.registerSync();
1478
+ if (!response) {
1479
+ throw new SerwistError("no-response", {
1480
+ url: request.url,
1481
+ error
1482
+ });
1452
1483
  }
1484
+ return response;
1453
1485
  }
1454
- async _removeRequest(operation) {
1455
- const now = Date.now();
1486
+ }
1487
+
1488
+ const QUEUE_NAME = "serwist-google-analytics";
1489
+ const MAX_RETENTION_TIME = 60 * 48;
1490
+ const GOOGLE_ANALYTICS_HOST = "www.google-analytics.com";
1491
+ const GTM_HOST = "www.googletagmanager.com";
1492
+ const ANALYTICS_JS_PATH = "/analytics.js";
1493
+ const GTAG_JS_PATH = "/gtag/js";
1494
+ const GTM_JS_PATH = "/gtm.js";
1495
+ const COLLECT_PATHS_REGEX = /^\/(\w+\/)?collect/;
1496
+
1497
+ const createOnSyncCallback = (config)=>{
1498
+ return async ({ queue })=>{
1456
1499
  let entry;
1457
- switch(operation){
1458
- case "pop":
1459
- entry = await this._queueStore.popEntry();
1460
- break;
1461
- case "shift":
1462
- entry = await this._queueStore.shiftEntry();
1463
- break;
1464
- }
1465
- if (entry) {
1466
- const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
1467
- if (now - entry.timestamp > maxRetentionTimeInMs) {
1468
- return this._removeRequest(operation);
1469
- }
1470
- return convertEntry(entry);
1471
- }
1472
- return undefined;
1473
- }
1474
- async replayRequests() {
1475
- let entry = undefined;
1476
- while(entry = await this.shiftRequest()){
1500
+ while(entry = await queue.shiftRequest()){
1501
+ const { request, timestamp } = entry;
1502
+ const url = new URL(request.url);
1477
1503
  try {
1478
- await fetch(entry.request.clone());
1504
+ const params = request.method === "POST" ? new URLSearchParams(await request.clone().text()) : url.searchParams;
1505
+ const originalHitTime = timestamp - (Number(params.get("qt")) || 0);
1506
+ const queueTime = Date.now() - originalHitTime;
1507
+ params.set("qt", String(queueTime));
1508
+ if (config.parameterOverrides) {
1509
+ for (const param of Object.keys(config.parameterOverrides)){
1510
+ const value = config.parameterOverrides[param];
1511
+ params.set(param, value);
1512
+ }
1513
+ }
1514
+ if (typeof config.hitFilter === "function") {
1515
+ config.hitFilter.call(null, params);
1516
+ }
1517
+ await fetch(new Request(url.origin + url.pathname, {
1518
+ body: params.toString(),
1519
+ method: "POST",
1520
+ mode: "cors",
1521
+ credentials: "omit",
1522
+ headers: {
1523
+ "Content-Type": "text/plain"
1524
+ }
1525
+ }));
1479
1526
  if (process.env.NODE_ENV !== "production") {
1480
- logger.log(`Request for '${getFriendlyURL(entry.request.url)}' ` + `has been replayed in queue '${this._name}'`);
1527
+ logger.log(`Request for '${getFriendlyURL(url.href)}' has been replayed`);
1481
1528
  }
1482
- } catch (error) {
1483
- await this.unshiftRequest(entry);
1529
+ } catch (err) {
1530
+ await queue.unshiftRequest(entry);
1484
1531
  if (process.env.NODE_ENV !== "production") {
1485
- logger.log(`Request for '${getFriendlyURL(entry.request.url)}' ` + `failed to replay, putting it back in queue '${this._name}'`);
1532
+ logger.log(`Request for '${getFriendlyURL(url.href)}' failed to replay, putting it back in the queue.`);
1486
1533
  }
1487
- throw new SerwistError("queue-replay-failed", {
1488
- name: this._name
1489
- });
1534
+ throw err;
1490
1535
  }
1491
1536
  }
1492
1537
  if (process.env.NODE_ENV !== "production") {
1493
- logger.log(`All requests in queue '${this.name}' have successfully replayed; the queue is now empty!`);
1538
+ logger.log("All Google Analytics request successfully replayed; " + "the queue is now empty!");
1494
1539
  }
1540
+ };
1541
+ };
1542
+ const createCollectRoutes = (bgSyncPlugin)=>{
1543
+ const match = ({ url })=>url.hostname === GOOGLE_ANALYTICS_HOST && COLLECT_PATHS_REGEX.test(url.pathname);
1544
+ const handler = new NetworkOnly({
1545
+ plugins: [
1546
+ bgSyncPlugin
1547
+ ]
1548
+ });
1549
+ return [
1550
+ new Route(match, handler, "GET"),
1551
+ new Route(match, handler, "POST")
1552
+ ];
1553
+ };
1554
+ const createAnalyticsJsRoute = (cacheName)=>{
1555
+ const match = ({ url })=>url.hostname === GOOGLE_ANALYTICS_HOST && url.pathname === ANALYTICS_JS_PATH;
1556
+ const handler = new NetworkFirst({
1557
+ cacheName
1558
+ });
1559
+ return new Route(match, handler, "GET");
1560
+ };
1561
+ const createGtagJsRoute = (cacheName)=>{
1562
+ const match = ({ url })=>url.hostname === GTM_HOST && url.pathname === GTAG_JS_PATH;
1563
+ const handler = new NetworkFirst({
1564
+ cacheName
1565
+ });
1566
+ return new Route(match, handler, "GET");
1567
+ };
1568
+ const createGtmJsRoute = (cacheName)=>{
1569
+ const match = ({ url })=>url.hostname === GTM_HOST && url.pathname === GTM_JS_PATH;
1570
+ const handler = new NetworkFirst({
1571
+ cacheName
1572
+ });
1573
+ return new Route(match, handler, "GET");
1574
+ };
1575
+ class GoogleAnalytics {
1576
+ _options;
1577
+ constructor(options = {}){
1578
+ this._options = options;
1495
1579
  }
1496
- async registerSync() {
1497
- if ("sync" in self.registration && !this._forceSyncFallback) {
1498
- try {
1499
- await self.registration.sync.register(`${TAG_PREFIX}:${this._name}`);
1500
- } catch (err) {
1501
- if (process.env.NODE_ENV !== "production") {
1502
- logger.warn(`Unable to register sync event for '${this._name}'.`, err);
1503
- }
1504
- }
1580
+ init({ serwist }) {
1581
+ const resolvedCacheName = cacheNames$1.getGoogleAnalyticsName(this._options.cacheName);
1582
+ const bgSyncPlugin = new BackgroundSyncPlugin(QUEUE_NAME, {
1583
+ maxRetentionTime: MAX_RETENTION_TIME,
1584
+ onSync: createOnSyncCallback(this._options)
1585
+ });
1586
+ const routes = [
1587
+ createGtmJsRoute(resolvedCacheName),
1588
+ createAnalyticsJsRoute(resolvedCacheName),
1589
+ createGtagJsRoute(resolvedCacheName),
1590
+ ...createCollectRoutes(bgSyncPlugin)
1591
+ ];
1592
+ for (const route of routes){
1593
+ registerRoute(serwist, route);
1505
1594
  }
1506
1595
  }
1507
- _addSyncListener() {
1508
- if ("sync" in self.registration && !this._forceSyncFallback) {
1509
- self.addEventListener("sync", (event)=>{
1510
- if (event.tag === `${TAG_PREFIX}:${this._name}`) {
1511
- if (process.env.NODE_ENV !== "production") {
1512
- logger.log(`Background sync for tag '${event.tag}' has been received`);
1513
- }
1514
- const syncComplete = async ()=>{
1515
- this._syncInProgress = true;
1516
- let syncError = undefined;
1517
- try {
1518
- await this._onSync({
1519
- queue: this
1520
- });
1521
- } catch (error) {
1522
- if (error instanceof Error) {
1523
- syncError = error;
1524
- throw syncError;
1525
- }
1526
- } finally{
1527
- if (this._requestsAddedDuringSync && !(syncError && !event.lastChance)) {
1528
- await this.registerSync();
1529
- }
1530
- this._syncInProgress = false;
1531
- this._requestsAddedDuringSync = false;
1532
- }
1533
- };
1534
- event.waitUntil(syncComplete());
1535
- }
1536
- });
1537
- } else {
1538
- if (process.env.NODE_ENV !== "production") {
1539
- logger.log("Background sync replaying without background sync event");
1540
- }
1541
- void this._onSync({
1542
- queue: this
1543
- });
1544
- }
1596
+ }
1597
+
1598
+ const initializeGoogleAnalytics = ({ serwist, cacheName, ...options })=>{
1599
+ const resolvedCacheName = cacheNames$1.getGoogleAnalyticsName(cacheName);
1600
+ const bgSyncPlugin = new BackgroundSyncPlugin(QUEUE_NAME, {
1601
+ maxRetentionTime: MAX_RETENTION_TIME,
1602
+ onSync: createOnSyncCallback(options)
1603
+ });
1604
+ const routes = [
1605
+ createGtmJsRoute(resolvedCacheName),
1606
+ createAnalyticsJsRoute(resolvedCacheName),
1607
+ createGtagJsRoute(resolvedCacheName),
1608
+ ...createCollectRoutes(bgSyncPlugin)
1609
+ ];
1610
+ for (const route of routes){
1611
+ serwist.registerRoute(route);
1545
1612
  }
1546
- static get _queueNames() {
1547
- return queueNames;
1613
+ };
1614
+
1615
+ const REVISION_SEARCH_PARAM = "__WB_REVISION__";
1616
+ const createCacheKey = (entry)=>{
1617
+ if (!entry) {
1618
+ throw new SerwistError("add-to-cache-list-unexpected-type", {
1619
+ entry
1620
+ });
1621
+ }
1622
+ if (typeof entry === "string") {
1623
+ const urlObject = new URL(entry, location.href);
1624
+ return {
1625
+ cacheKey: urlObject.href,
1626
+ url: urlObject.href
1627
+ };
1628
+ }
1629
+ const { revision, url } = entry;
1630
+ if (!url) {
1631
+ throw new SerwistError("add-to-cache-list-unexpected-type", {
1632
+ entry
1633
+ });
1634
+ }
1635
+ if (!revision) {
1636
+ const urlObject = new URL(url, location.href);
1637
+ return {
1638
+ cacheKey: urlObject.href,
1639
+ url: urlObject.href
1640
+ };
1641
+ }
1642
+ const cacheKeyURL = new URL(url, location.href);
1643
+ const originalURL = new URL(url, location.href);
1644
+ cacheKeyURL.searchParams.set(REVISION_SEARCH_PARAM, revision);
1645
+ return {
1646
+ cacheKey: cacheKeyURL.href,
1647
+ url: originalURL.href
1648
+ };
1649
+ };
1650
+
1651
+ const logGroup = (groupTitle, deletedURLs)=>{
1652
+ logger.groupCollapsed(groupTitle);
1653
+ for (const url of deletedURLs){
1654
+ logger.log(url);
1655
+ }
1656
+ logger.groupEnd();
1657
+ };
1658
+ const printCleanupDetails = (deletedURLs)=>{
1659
+ const deletionCount = deletedURLs.length;
1660
+ if (deletionCount > 0) {
1661
+ logger.groupCollapsed(`During precaching cleanup, ${deletionCount} cached request${deletionCount === 1 ? " was" : "s were"} deleted.`);
1662
+ logGroup("Deleted Cache Requests", deletedURLs);
1663
+ logger.groupEnd();
1548
1664
  }
1549
- }
1665
+ };
1550
1666
 
1551
- class BackgroundSyncPlugin {
1552
- _queue;
1553
- constructor(name, options){
1554
- this._queue = new BackgroundSyncQueue(name, options);
1667
+ function _nestedGroup(groupTitle, urls) {
1668
+ if (urls.length === 0) {
1669
+ return;
1555
1670
  }
1556
- async fetchDidFail({ request }) {
1557
- await this._queue.pushRequest({
1558
- request
1559
- });
1671
+ logger.groupCollapsed(groupTitle);
1672
+ for (const url of urls){
1673
+ logger.log(url);
1560
1674
  }
1675
+ logger.groupEnd();
1561
1676
  }
1562
-
1563
- const cacheOkAndOpaquePlugin = {
1564
- cacheWillUpdate: async ({ response })=>{
1565
- if (response.status === 200 || response.status === 0) {
1566
- return response;
1677
+ const printInstallDetails = (urlsToPrecache, urlsAlreadyPrecached)=>{
1678
+ const precachedCount = urlsToPrecache.length;
1679
+ const alreadyPrecachedCount = urlsAlreadyPrecached.length;
1680
+ if (precachedCount || alreadyPrecachedCount) {
1681
+ let message = `Precaching ${precachedCount} file${precachedCount === 1 ? "" : "s"}.`;
1682
+ if (alreadyPrecachedCount > 0) {
1683
+ message += ` ${alreadyPrecachedCount} ` + `file${alreadyPrecachedCount === 1 ? " is" : "s are"} already cached.`;
1567
1684
  }
1568
- return null;
1685
+ logger.groupCollapsed(message);
1686
+ _nestedGroup("View newly precached URLs.", urlsToPrecache);
1687
+ _nestedGroup("View previously precached URLs.", urlsAlreadyPrecached);
1688
+ logger.groupEnd();
1569
1689
  }
1570
1690
  };
1571
1691
 
1572
- const messages = {
1573
- strategyStart: (strategyName, request)=>`Using ${strategyName} to respond to '${getFriendlyURL(request.url)}'`,
1574
- printFinalResponse: (response)=>{
1575
- if (response) {
1576
- logger.groupCollapsed("View the final response here.");
1577
- logger.log(response || "[No response returned]");
1578
- logger.groupEnd();
1692
+ class PrecacheCacheKeyPlugin {
1693
+ _precacheController;
1694
+ constructor({ precacheController }){
1695
+ this._precacheController = precacheController;
1696
+ }
1697
+ cacheKeyWillBeUsed = async ({ request, params })=>{
1698
+ const cacheKey = params?.cacheKey || this._precacheController.getPrecacheKeyForUrl(request.url);
1699
+ return cacheKey ? new Request(cacheKey, {
1700
+ headers: request.headers
1701
+ }) : request;
1702
+ };
1703
+ }
1704
+
1705
+ const parsePrecacheOptions = (controller, { entries, cacheName, plugins, fetchOptions, matchOptions, fallbackToNetwork, directoryIndex, ignoreURLParametersMatching, cleanURLs, urlManipulation, cleanupOutdatedCaches, concurrency, navigateFallback, navigateFallbackAllowlist, navigateFallbackDenylist })=>({
1706
+ entries: entries ?? [],
1707
+ strategyOptions: {
1708
+ cacheName: cacheNames$1.getPrecacheName(cacheName),
1709
+ plugins: [
1710
+ ...plugins ?? [],
1711
+ new PrecacheCacheKeyPlugin({
1712
+ precacheController: controller
1713
+ })
1714
+ ],
1715
+ fetchOptions,
1716
+ matchOptions,
1717
+ fallbackToNetwork
1718
+ },
1719
+ routeOptions: {
1720
+ directoryIndex,
1721
+ ignoreURLParametersMatching,
1722
+ cleanURLs,
1723
+ urlManipulation
1724
+ },
1725
+ extensionOptions: {
1726
+ cleanupOutdatedCaches,
1727
+ concurrency: concurrency ?? 10,
1728
+ navigateFallback,
1729
+ navigateFallbackAllowlist,
1730
+ navigateFallbackDenylist
1731
+ }
1732
+ });
1733
+
1734
+ class PrecacheInstallReportPlugin {
1735
+ updatedURLs = [];
1736
+ notUpdatedURLs = [];
1737
+ handlerWillStart = async ({ request, state })=>{
1738
+ if (state) {
1739
+ state.originalRequest = request;
1740
+ }
1741
+ };
1742
+ cachedResponseWillBeUsed = async ({ event, state, cachedResponse })=>{
1743
+ if (event.type === "install") {
1744
+ if (state?.originalRequest && state.originalRequest instanceof Request) {
1745
+ const url = state.originalRequest.url;
1746
+ if (cachedResponse) {
1747
+ this.notUpdatedURLs.push(url);
1748
+ } else {
1749
+ this.updatedURLs.push(url);
1750
+ }
1751
+ }
1752
+ }
1753
+ return cachedResponse;
1754
+ };
1755
+ }
1756
+
1757
+ const removeIgnoredSearchParams = (urlObject, ignoreURLParametersMatching = [])=>{
1758
+ for (const paramName of [
1759
+ ...urlObject.searchParams.keys()
1760
+ ]){
1761
+ if (ignoreURLParametersMatching.some((regExp)=>regExp.test(paramName))) {
1762
+ urlObject.searchParams.delete(paramName);
1579
1763
  }
1580
1764
  }
1765
+ return urlObject;
1581
1766
  };
1582
1767
 
1583
- class NetworkFirst extends Strategy {
1584
- _networkTimeoutSeconds;
1585
- constructor(options = {}){
1586
- super(options);
1587
- if (!this.plugins.some((p)=>"cacheWillUpdate" in p)) {
1588
- this.plugins.unshift(cacheOkAndOpaquePlugin);
1768
+ function* generateURLVariations(url, { directoryIndex = "index.html", ignoreURLParametersMatching = [
1769
+ /^utm_/,
1770
+ /^fbclid$/
1771
+ ], cleanURLs = true, urlManipulation } = {}) {
1772
+ const urlObject = new URL(url, location.href);
1773
+ urlObject.hash = "";
1774
+ yield urlObject.href;
1775
+ const urlWithoutIgnoredParams = removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching);
1776
+ yield urlWithoutIgnoredParams.href;
1777
+ if (directoryIndex && urlWithoutIgnoredParams.pathname.endsWith("/")) {
1778
+ const directoryURL = new URL(urlWithoutIgnoredParams.href);
1779
+ directoryURL.pathname += directoryIndex;
1780
+ yield directoryURL.href;
1781
+ }
1782
+ if (cleanURLs) {
1783
+ const cleanURL = new URL(urlWithoutIgnoredParams.href);
1784
+ cleanURL.pathname += ".html";
1785
+ yield cleanURL.href;
1786
+ }
1787
+ if (urlManipulation) {
1788
+ const additionalURLs = urlManipulation({
1789
+ url: urlObject
1790
+ });
1791
+ for (const urlToAttempt of additionalURLs){
1792
+ yield urlToAttempt.href;
1589
1793
  }
1590
- this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
1591
- if (process.env.NODE_ENV !== "production") {
1592
- if (this._networkTimeoutSeconds) {
1593
- finalAssertExports.isType(this._networkTimeoutSeconds, "number", {
1594
- moduleName: "serwist",
1595
- className: this.constructor.name,
1596
- funcName: "constructor",
1597
- paramName: "networkTimeoutSeconds"
1598
- });
1794
+ }
1795
+ }
1796
+
1797
+ class PrecacheRoute extends Route {
1798
+ constructor(precache, options){
1799
+ const match = ({ request })=>{
1800
+ const urlsToCacheKeys = precache.getUrlsToPrecacheKeys();
1801
+ for (const possibleURL of generateURLVariations(request.url, options)){
1802
+ const cacheKey = urlsToCacheKeys.get(possibleURL);
1803
+ if (cacheKey) {
1804
+ const integrity = precache.getIntegrityForPrecacheKey(cacheKey);
1805
+ return {
1806
+ cacheKey,
1807
+ integrity
1808
+ };
1809
+ }
1599
1810
  }
1600
- }
1811
+ if (process.env.NODE_ENV !== "production") {
1812
+ logger.debug(`Precaching did not find a match for ${getFriendlyURL(request.url)}.`);
1813
+ }
1814
+ return;
1815
+ };
1816
+ super(match, precache.strategy);
1601
1817
  }
1602
- async _handle(request, handler) {
1603
- const logs = [];
1604
- if (process.env.NODE_ENV !== "production") {
1605
- finalAssertExports.isInstance(request, Request, {
1606
- moduleName: "serwist",
1607
- className: this.constructor.name,
1608
- funcName: "handle",
1609
- paramName: "makeRequest"
1818
+ }
1819
+
1820
+ const disableDevLogs = ()=>{
1821
+ self.__WB_DISABLE_DEV_LOGS = true;
1822
+ };
1823
+ const setCacheNameDetails = (details)=>{
1824
+ if (process.env.NODE_ENV !== "production") {
1825
+ for (const key of Object.keys(details)){
1826
+ finalAssertExports.isType(details[key], "string", {
1827
+ moduleName: "@serwist/core",
1828
+ funcName: "setCacheNameDetails",
1829
+ paramName: `details.${key}`
1610
1830
  });
1611
1831
  }
1612
- const promises = [];
1613
- let timeoutId;
1614
- if (this._networkTimeoutSeconds) {
1615
- const { id, promise } = this._getTimeoutPromise({
1616
- request,
1617
- logs,
1618
- handler
1832
+ if (details.precache?.length === 0) {
1833
+ throw new SerwistError("invalid-cache-name", {
1834
+ cacheNameId: "precache",
1835
+ value: details.precache
1619
1836
  });
1620
- timeoutId = id;
1621
- promises.push(promise);
1622
1837
  }
1623
- const networkPromise = this._getNetworkPromise({
1624
- timeoutId,
1625
- request,
1626
- logs,
1627
- handler
1628
- });
1629
- promises.push(networkPromise);
1630
- const response = await handler.waitUntil((async ()=>{
1631
- return await handler.waitUntil(Promise.race(promises)) || await networkPromise;
1632
- })());
1633
- if (process.env.NODE_ENV !== "production") {
1634
- logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
1635
- for (const log of logs){
1636
- logger.log(log);
1637
- }
1638
- messages.printFinalResponse(response);
1639
- logger.groupEnd();
1838
+ if (details.runtime?.length === 0) {
1839
+ throw new SerwistError("invalid-cache-name", {
1840
+ cacheNameId: "runtime",
1841
+ value: details.runtime
1842
+ });
1640
1843
  }
1641
- if (!response) {
1642
- throw new SerwistError("no-response", {
1643
- url: request.url
1844
+ if (details.googleAnalytics?.length === 0) {
1845
+ throw new SerwistError("invalid-cache-name", {
1846
+ cacheNameId: "googleAnalytics",
1847
+ value: details.googleAnalytics
1644
1848
  });
1645
1849
  }
1646
- return response;
1647
1850
  }
1648
- _getTimeoutPromise({ request, logs, handler }) {
1649
- let timeoutId;
1650
- const timeoutPromise = new Promise((resolve)=>{
1651
- const onNetworkTimeout = async ()=>{
1652
- if (process.env.NODE_ENV !== "production") {
1653
- logs.push(`Timing out the network response at ${this._networkTimeoutSeconds} seconds.`);
1654
- }
1655
- resolve(await handler.cacheMatch(request));
1656
- };
1657
- timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1000);
1851
+ cacheNames$1.updateDetails(details);
1852
+ };
1853
+ const registerQuotaErrorCallback = (callback)=>{
1854
+ if (process.env.NODE_ENV !== "production") {
1855
+ finalAssertExports.isType(callback, "function", {
1856
+ moduleName: "@serwist/core",
1857
+ funcName: "register",
1858
+ paramName: "callback"
1859
+ });
1860
+ }
1861
+ quotaErrorCallbacks.add(callback);
1862
+ if (process.env.NODE_ENV !== "production") {
1863
+ logger.log("Registered a callback to respond to quota errors.", callback);
1864
+ }
1865
+ };
1866
+ const copyResponse = async (response, modifier)=>{
1867
+ let origin = null;
1868
+ if (response.url) {
1869
+ const responseURL = new URL(response.url);
1870
+ origin = responseURL.origin;
1871
+ }
1872
+ if (origin !== self.location.origin) {
1873
+ throw new SerwistError("cross-origin-copy-response", {
1874
+ origin
1658
1875
  });
1659
- return {
1660
- promise: timeoutPromise,
1661
- id: timeoutId
1662
- };
1663
1876
  }
1664
- async _getNetworkPromise({ timeoutId, request, logs, handler }) {
1665
- let error = undefined;
1666
- let response = undefined;
1667
- try {
1668
- response = await handler.fetchAndCachePut(request);
1669
- } catch (fetchError) {
1670
- if (fetchError instanceof Error) {
1671
- error = fetchError;
1672
- }
1673
- }
1674
- if (timeoutId) {
1675
- clearTimeout(timeoutId);
1676
- }
1677
- if (process.env.NODE_ENV !== "production") {
1678
- if (response) {
1679
- logs.push("Got response from network.");
1680
- } else {
1681
- logs.push("Unable to get a response from the network. Will respond " + "with a cached response.");
1877
+ const clonedResponse = response.clone();
1878
+ const responseInit = {
1879
+ headers: new Headers(clonedResponse.headers),
1880
+ status: clonedResponse.status,
1881
+ statusText: clonedResponse.statusText
1882
+ };
1883
+ const modifiedResponseInit = modifier ? modifier(responseInit) : responseInit;
1884
+ const body = canConstructResponseFromBodyStream() ? clonedResponse.body : await clonedResponse.blob();
1885
+ return new Response(body, modifiedResponseInit);
1886
+ };
1887
+
1888
+ class PrecacheStrategy extends Strategy {
1889
+ _fallbackToNetwork;
1890
+ static defaultPrecacheCacheabilityPlugin = {
1891
+ async cacheWillUpdate ({ response }) {
1892
+ if (!response || response.status >= 400) {
1893
+ return null;
1682
1894
  }
1895
+ return response;
1683
1896
  }
1684
- if (error || !response) {
1685
- response = await handler.cacheMatch(request);
1686
- if (process.env.NODE_ENV !== "production") {
1687
- if (response) {
1688
- logs.push(`Found a cached response in the '${this.cacheName}' cache.`);
1689
- } else {
1690
- logs.push(`No response found in the '${this.cacheName}' cache.`);
1691
- }
1692
- }
1897
+ };
1898
+ static copyRedirectedCacheableResponsesPlugin = {
1899
+ async cacheWillUpdate ({ response }) {
1900
+ return response.redirected ? await copyResponse(response) : response;
1693
1901
  }
1694
- return response;
1695
- }
1696
- }
1697
-
1698
- class NetworkOnly extends Strategy {
1699
- _networkTimeoutSeconds;
1902
+ };
1700
1903
  constructor(options = {}){
1904
+ options.cacheName = cacheNames$1.getPrecacheName(options.cacheName);
1701
1905
  super(options);
1702
- this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
1906
+ this._fallbackToNetwork = options.fallbackToNetwork !== false;
1907
+ this.plugins.push(PrecacheStrategy.copyRedirectedCacheableResponsesPlugin);
1703
1908
  }
1704
1909
  async _handle(request, handler) {
1705
- if (process.env.NODE_ENV !== "production") {
1706
- finalAssertExports.isInstance(request, Request, {
1707
- moduleName: "serwist",
1708
- className: this.constructor.name,
1709
- funcName: "_handle",
1710
- paramName: "request"
1711
- });
1712
- }
1713
- let error = undefined;
1714
- let response;
1715
- try {
1716
- const promises = [
1717
- handler.fetch(request)
1718
- ];
1719
- if (this._networkTimeoutSeconds) {
1720
- const timeoutPromise = timeout(this._networkTimeoutSeconds * 1000);
1721
- promises.push(timeoutPromise);
1722
- }
1723
- response = await Promise.race(promises);
1724
- if (!response) {
1725
- throw new Error(`Timed out the network response after ${this._networkTimeoutSeconds} seconds.`);
1726
- }
1727
- } catch (err) {
1728
- if (err instanceof Error) {
1729
- error = err;
1730
- }
1910
+ const preloadResponse = await handler.getPreloadResponse();
1911
+ if (preloadResponse) {
1912
+ return preloadResponse;
1731
1913
  }
1732
- if (process.env.NODE_ENV !== "production") {
1733
- logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
1734
- if (response) {
1735
- logger.log("Got response from network.");
1736
- } else {
1737
- logger.log("Unable to get a response from the network.");
1738
- }
1739
- messages.printFinalResponse(response);
1740
- logger.groupEnd();
1914
+ const response = await handler.cacheMatch(request);
1915
+ if (response) {
1916
+ return response;
1741
1917
  }
1742
- if (!response) {
1743
- throw new SerwistError("no-response", {
1744
- url: request.url,
1745
- error
1746
- });
1918
+ if (handler.event && handler.event.type === "install") {
1919
+ return await this._handleInstall(request, handler);
1747
1920
  }
1748
- return response;
1921
+ return await this._handleFetch(request, handler);
1749
1922
  }
1750
- }
1751
-
1752
- const QUEUE_NAME = "serwist-google-analytics";
1753
- const MAX_RETENTION_TIME = 60 * 48;
1754
- const GOOGLE_ANALYTICS_HOST = "www.google-analytics.com";
1755
- const GTM_HOST = "www.googletagmanager.com";
1756
- const ANALYTICS_JS_PATH = "/analytics.js";
1757
- const GTAG_JS_PATH = "/gtag/js";
1758
- const GTM_JS_PATH = "/gtm.js";
1759
- const COLLECT_PATHS_REGEX = /^\/(\w+\/)?collect/;
1760
-
1761
- const createOnSyncCallback = (config)=>{
1762
- return async ({ queue })=>{
1763
- let entry = undefined;
1764
- while(entry = await queue.shiftRequest()){
1765
- const { request, timestamp } = entry;
1766
- const url = new URL(request.url);
1767
- try {
1768
- const params = request.method === "POST" ? new URLSearchParams(await request.clone().text()) : url.searchParams;
1769
- const originalHitTime = timestamp - (Number(params.get("qt")) || 0);
1770
- const queueTime = Date.now() - originalHitTime;
1771
- params.set("qt", String(queueTime));
1772
- if (config.parameterOverrides) {
1773
- for (const param of Object.keys(config.parameterOverrides)){
1774
- const value = config.parameterOverrides[param];
1775
- params.set(param, value);
1776
- }
1777
- }
1778
- if (typeof config.hitFilter === "function") {
1779
- config.hitFilter.call(null, params);
1780
- }
1781
- await fetch(new Request(url.origin + url.pathname, {
1782
- body: params.toString(),
1783
- method: "POST",
1784
- mode: "cors",
1785
- credentials: "omit",
1786
- headers: {
1787
- "Content-Type": "text/plain"
1788
- }
1789
- }));
1790
- if (process.env.NODE_ENV !== "production") {
1791
- logger.log(`Request for '${getFriendlyURL(url.href)}' has been replayed`);
1792
- }
1793
- } catch (err) {
1794
- await queue.unshiftRequest(entry);
1923
+ async _handleFetch(request, handler) {
1924
+ let response;
1925
+ const params = handler.params || {};
1926
+ if (this._fallbackToNetwork) {
1927
+ if (process.env.NODE_ENV !== "production") {
1928
+ logger.warn(`The precached response for ${getFriendlyURL(request.url)} in ${this.cacheName} was not found. Falling back to the network.`);
1929
+ }
1930
+ const integrityInManifest = params.integrity;
1931
+ const integrityInRequest = request.integrity;
1932
+ const noIntegrityConflict = !integrityInRequest || integrityInRequest === integrityInManifest;
1933
+ response = await handler.fetch(new Request(request, {
1934
+ integrity: request.mode !== "no-cors" ? integrityInRequest || integrityInManifest : undefined
1935
+ }));
1936
+ if (integrityInManifest && noIntegrityConflict && request.mode !== "no-cors") {
1937
+ this._useDefaultCacheabilityPluginIfNeeded();
1938
+ const wasCached = await handler.cachePut(request, response.clone());
1795
1939
  if (process.env.NODE_ENV !== "production") {
1796
- logger.log(`Request for '${getFriendlyURL(url.href)}' failed to replay, putting it back in the queue.`);
1940
+ if (wasCached) {
1941
+ logger.log(`A response for ${getFriendlyURL(request.url)} was used to "repair" the precache.`);
1942
+ }
1797
1943
  }
1798
- throw err;
1799
1944
  }
1945
+ } else {
1946
+ throw new SerwistError("missing-precache-entry", {
1947
+ cacheName: this.cacheName,
1948
+ url: request.url
1949
+ });
1800
1950
  }
1801
1951
  if (process.env.NODE_ENV !== "production") {
1802
- logger.log("All Google Analytics request successfully replayed; " + "the queue is now empty!");
1803
- }
1804
- };
1805
- };
1806
- const createCollectRoutes = (bgSyncPlugin)=>{
1807
- const match = ({ url })=>url.hostname === GOOGLE_ANALYTICS_HOST && COLLECT_PATHS_REGEX.test(url.pathname);
1808
- const handler = new NetworkOnly({
1809
- plugins: [
1810
- bgSyncPlugin
1811
- ]
1812
- });
1813
- return [
1814
- new Route(match, handler, "GET"),
1815
- new Route(match, handler, "POST")
1816
- ];
1817
- };
1818
- const createAnalyticsJsRoute = (cacheName)=>{
1819
- const match = ({ url })=>url.hostname === GOOGLE_ANALYTICS_HOST && url.pathname === ANALYTICS_JS_PATH;
1820
- const handler = new NetworkFirst({
1821
- cacheName
1822
- });
1823
- return new Route(match, handler, "GET");
1824
- };
1825
- const createGtagJsRoute = (cacheName)=>{
1826
- const match = ({ url })=>url.hostname === GTM_HOST && url.pathname === GTAG_JS_PATH;
1827
- const handler = new NetworkFirst({
1828
- cacheName
1829
- });
1830
- return new Route(match, handler, "GET");
1831
- };
1832
- const createGtmJsRoute = (cacheName)=>{
1833
- const match = ({ url })=>url.hostname === GTM_HOST && url.pathname === GTM_JS_PATH;
1834
- const handler = new NetworkFirst({
1835
- cacheName
1836
- });
1837
- return new Route(match, handler, "GET");
1838
- };
1839
- const initializeGoogleAnalytics = ({ serwist, cacheName, ...options })=>{
1840
- const resolvedCacheName = cacheNames$1.getGoogleAnalyticsName(cacheName);
1841
- const bgSyncPlugin = new BackgroundSyncPlugin(QUEUE_NAME, {
1842
- maxRetentionTime: MAX_RETENTION_TIME,
1843
- onSync: createOnSyncCallback(options)
1844
- });
1845
- const routes = [
1846
- createGtmJsRoute(resolvedCacheName),
1847
- createAnalyticsJsRoute(resolvedCacheName),
1848
- createGtagJsRoute(resolvedCacheName),
1849
- ...createCollectRoutes(bgSyncPlugin)
1850
- ];
1851
- for (const route of routes){
1852
- serwist.registerRoute(route);
1952
+ const cacheKey = params.cacheKey || await handler.getCacheKey(request, "read");
1953
+ logger.groupCollapsed(`Precaching is responding to: ${getFriendlyURL(request.url)}`);
1954
+ logger.log(`Serving the precached url: ${getFriendlyURL(cacheKey instanceof Request ? cacheKey.url : cacheKey)}`);
1955
+ logger.groupCollapsed("View request details here.");
1956
+ logger.log(request);
1957
+ logger.groupEnd();
1958
+ logger.groupCollapsed("View response details here.");
1959
+ logger.log(response);
1960
+ logger.groupEnd();
1961
+ logger.groupEnd();
1962
+ }
1963
+ return response;
1853
1964
  }
1854
- };
1855
-
1856
- const parseRoute = (capture, handler, method)=>{
1857
- if (typeof capture === "string") {
1858
- const captureUrl = new URL(capture, location.href);
1859
- if (process.env.NODE_ENV !== "production") {
1860
- if (!(capture.startsWith("/") || capture.startsWith("http"))) {
1861
- throw new SerwistError("invalid-string", {
1862
- moduleName: "serwist",
1863
- funcName: "parseRoute",
1864
- paramName: "capture"
1865
- });
1965
+ async _handleInstall(request, handler) {
1966
+ this._useDefaultCacheabilityPluginIfNeeded();
1967
+ const response = await handler.fetch(request);
1968
+ const wasCached = await handler.cachePut(request, response.clone());
1969
+ if (!wasCached) {
1970
+ throw new SerwistError("bad-precaching-response", {
1971
+ url: request.url,
1972
+ status: response.status
1973
+ });
1974
+ }
1975
+ return response;
1976
+ }
1977
+ _useDefaultCacheabilityPluginIfNeeded() {
1978
+ let defaultPluginIndex = null;
1979
+ let cacheWillUpdatePluginCount = 0;
1980
+ for (const [index, plugin] of this.plugins.entries()){
1981
+ if (plugin === PrecacheStrategy.copyRedirectedCacheableResponsesPlugin) {
1982
+ continue;
1866
1983
  }
1867
- const valueToCheck = capture.startsWith("http") ? captureUrl.pathname : capture;
1868
- const wildcards = "[*:?+]";
1869
- if (new RegExp(`${wildcards}`).exec(valueToCheck)) {
1870
- logger.debug(`The '$capture' parameter contains an Express-style wildcard character (${wildcards}). Strings are now always interpreted as exact matches; use a RegExp for partial or wildcard matches.`);
1984
+ if (plugin === PrecacheStrategy.defaultPrecacheCacheabilityPlugin) {
1985
+ defaultPluginIndex = index;
1871
1986
  }
1872
- }
1873
- const matchCallback = ({ url })=>{
1874
- if (process.env.NODE_ENV !== "production") {
1875
- if (url.pathname === captureUrl.pathname && url.origin !== captureUrl.origin) {
1876
- logger.debug(`${capture} only partially matches the cross-origin URL ${url.toString()}. This route will only handle cross-origin requests if they match the entire URL.`);
1877
- }
1987
+ if (plugin.cacheWillUpdate) {
1988
+ cacheWillUpdatePluginCount++;
1878
1989
  }
1879
- return url.href === captureUrl.href;
1880
- };
1881
- return new Route(matchCallback, handler, method);
1882
- }
1883
- if (capture instanceof RegExp) {
1884
- return new RegExpRoute(capture, handler, method);
1990
+ }
1991
+ if (cacheWillUpdatePluginCount === 0) {
1992
+ this.plugins.push(PrecacheStrategy.defaultPrecacheCacheabilityPlugin);
1993
+ } else if (cacheWillUpdatePluginCount > 1 && defaultPluginIndex !== null) {
1994
+ this.plugins.splice(defaultPluginIndex, 1);
1995
+ }
1885
1996
  }
1886
- if (typeof capture === "function") {
1887
- return new Route(capture, handler, method);
1997
+ }
1998
+
1999
+ class Precache {
2000
+ _urlsToCacheKeys = new Map();
2001
+ _urlsToCacheModes = new Map();
2002
+ _cacheKeysToIntegrities = new Map();
2003
+ _strategy;
2004
+ _options;
2005
+ _routeOptions;
2006
+ constructor(precacheOptions){
2007
+ const { entries, strategyOptions, routeOptions, extensionOptions: controllerOptions } = parsePrecacheOptions(this, precacheOptions);
2008
+ this.addToCacheList(entries);
2009
+ this._strategy = new PrecacheStrategy(strategyOptions);
2010
+ this._options = controllerOptions;
2011
+ this._routeOptions = routeOptions;
1888
2012
  }
1889
- if (capture instanceof Route) {
1890
- return capture;
2013
+ get strategy() {
2014
+ return this._strategy;
1891
2015
  }
1892
- throw new SerwistError("unsupported-route-type", {
1893
- moduleName: "serwist",
1894
- funcName: "parseRoute",
1895
- paramName: "capture"
1896
- });
1897
- };
1898
-
1899
- const disableDevLogs = ()=>{
1900
- self.__WB_DISABLE_DEV_LOGS = true;
1901
- };
1902
-
1903
- const isNavigationPreloadSupported = ()=>{
1904
- return Boolean(self.registration?.navigationPreload);
1905
- };
1906
- const enableNavigationPreload = (headerValue)=>{
1907
- if (isNavigationPreloadSupported()) {
1908
- self.addEventListener("activate", (event)=>{
1909
- event.waitUntil(self.registration.navigationPreload.enable().then(()=>{
1910
- if (headerValue) {
1911
- void self.registration.navigationPreload.setHeaderValue(headerValue);
2016
+ addToCacheList(entries) {
2017
+ if (process.env.NODE_ENV !== "production") {
2018
+ finalAssertExports.isArray(entries, {
2019
+ moduleName: "serwist",
2020
+ className: "PrecacheController",
2021
+ funcName: "addEntries",
2022
+ paramName: "entries"
2023
+ });
2024
+ }
2025
+ const urlsToWarnAbout = [];
2026
+ for (const entry of entries){
2027
+ if (typeof entry === "string") {
2028
+ urlsToWarnAbout.push(entry);
2029
+ } else if (entry && !entry.integrity && entry.revision === undefined) {
2030
+ urlsToWarnAbout.push(entry.url);
2031
+ }
2032
+ const { cacheKey, url } = createCacheKey(entry);
2033
+ const cacheMode = typeof entry !== "string" && entry.revision ? "reload" : "default";
2034
+ if (this._urlsToCacheKeys.has(url) && this._urlsToCacheKeys.get(url) !== cacheKey) {
2035
+ throw new SerwistError("add-to-cache-list-conflicting-entries", {
2036
+ firstEntry: this._urlsToCacheKeys.get(url),
2037
+ secondEntry: cacheKey
2038
+ });
2039
+ }
2040
+ if (typeof entry !== "string" && entry.integrity) {
2041
+ if (this._cacheKeysToIntegrities.has(cacheKey) && this._cacheKeysToIntegrities.get(cacheKey) !== entry.integrity) {
2042
+ throw new SerwistError("add-to-cache-list-conflicting-integrities", {
2043
+ url
2044
+ });
1912
2045
  }
1913
- if (process.env.NODE_ENV !== "production") {
1914
- logger.log("Navigation preloading is enabled.");
2046
+ this._cacheKeysToIntegrities.set(cacheKey, entry.integrity);
2047
+ }
2048
+ this._urlsToCacheKeys.set(url, cacheKey);
2049
+ this._urlsToCacheModes.set(url, cacheMode);
2050
+ if (urlsToWarnAbout.length > 0) {
2051
+ const warningMessage = `Serwist is precaching URLs without revision info: ${urlsToWarnAbout.join(", ")}\nThis is generally NOT safe, as you risk serving outdated assets.`;
2052
+ if (process.env.NODE_ENV === "production") {
2053
+ console.warn(warningMessage);
2054
+ } else {
2055
+ logger.warn(warningMessage);
1915
2056
  }
2057
+ }
2058
+ }
2059
+ }
2060
+ init({ serwist }) {
2061
+ registerRoute(serwist, new PrecacheRoute(this, this._routeOptions));
2062
+ if (this._options.navigateFallback) {
2063
+ registerRoute(serwist, new NavigationRoute(this.createHandlerBoundToUrl(this._options.navigateFallback), {
2064
+ allowlist: this._options.navigateFallbackAllowlist,
2065
+ denylist: this._options.navigateFallbackDenylist
1916
2066
  }));
1917
- });
1918
- } else {
1919
- if (process.env.NODE_ENV !== "production") {
1920
- logger.log("Navigation preloading is not supported in this browser.");
1921
2067
  }
1922
2068
  }
1923
- };
1924
- const disableNavigationPreload = ()=>{
1925
- if (isNavigationPreloadSupported()) {
1926
- self.addEventListener("activate", (event)=>{
1927
- event.waitUntil(self.registration.navigationPreload.disable().then(()=>{
1928
- if (process.env.NODE_ENV !== "production") {
1929
- logger.log("Navigation preloading is disabled.");
2069
+ async install({ event }) {
2070
+ const installReportPlugin = new PrecacheInstallReportPlugin();
2071
+ this._strategy.plugins.push(installReportPlugin);
2072
+ await parallel(this._options.concurrency, Array.from(this._urlsToCacheKeys.entries()), async ([url, cacheKey])=>{
2073
+ const integrity = this._cacheKeysToIntegrities.get(cacheKey);
2074
+ const cacheMode = this._urlsToCacheModes.get(url);
2075
+ const request = new Request(url, {
2076
+ integrity,
2077
+ cache: cacheMode,
2078
+ credentials: "same-origin"
2079
+ });
2080
+ await Promise.all(this._strategy.handleAll({
2081
+ event,
2082
+ request,
2083
+ url: new URL(request.url),
2084
+ params: {
2085
+ cacheKey
1930
2086
  }
1931
2087
  }));
1932
2088
  });
1933
- } else {
2089
+ const { updatedURLs, notUpdatedURLs } = installReportPlugin;
1934
2090
  if (process.env.NODE_ENV !== "production") {
1935
- logger.log("Navigation preloading is not supported in this browser.");
2091
+ printInstallDetails(updatedURLs, notUpdatedURLs);
1936
2092
  }
1937
2093
  }
1938
- };
1939
-
1940
- const setCacheNameDetails = (details)=>{
1941
- if (process.env.NODE_ENV !== "production") {
1942
- for (const key of Object.keys(details)){
1943
- finalAssertExports.isType(details[key], "string", {
1944
- moduleName: "@serwist/core",
1945
- funcName: "setCacheNameDetails",
1946
- paramName: `details.${key}`
1947
- });
2094
+ async activate() {
2095
+ const cache = await self.caches.open(this._strategy.cacheName);
2096
+ const currentlyCachedRequests = await cache.keys();
2097
+ const expectedCacheKeys = new Set(this._urlsToCacheKeys.values());
2098
+ const deletedCacheRequests = [];
2099
+ for (const request of currentlyCachedRequests){
2100
+ if (!expectedCacheKeys.has(request.url)) {
2101
+ await cache.delete(request);
2102
+ deletedCacheRequests.push(request.url);
2103
+ }
1948
2104
  }
1949
- if (details.precache?.length === 0) {
1950
- throw new SerwistError("invalid-cache-name", {
1951
- cacheNameId: "precache",
1952
- value: details.precache
1953
- });
2105
+ if (process.env.NODE_ENV !== "production") {
2106
+ printCleanupDetails(deletedCacheRequests);
1954
2107
  }
1955
- if (details.runtime?.length === 0) {
1956
- throw new SerwistError("invalid-cache-name", {
1957
- cacheNameId: "runtime",
1958
- value: details.runtime
1959
- });
2108
+ }
2109
+ getUrlsToPrecacheKeys() {
2110
+ return this._urlsToCacheKeys;
2111
+ }
2112
+ getPrecachedUrls() {
2113
+ return [
2114
+ ...this._urlsToCacheKeys.keys()
2115
+ ];
2116
+ }
2117
+ getPrecacheKeyForUrl(url) {
2118
+ const urlObject = new URL(url, location.href);
2119
+ return this._urlsToCacheKeys.get(urlObject.href);
2120
+ }
2121
+ getIntegrityForPrecacheKey(cacheKey) {
2122
+ return this._cacheKeysToIntegrities.get(cacheKey);
2123
+ }
2124
+ async matchPrecache(request) {
2125
+ const url = request instanceof Request ? request.url : request;
2126
+ const cacheKey = this.getPrecacheKeyForUrl(url);
2127
+ if (cacheKey) {
2128
+ const cache = await self.caches.open(this._strategy.cacheName);
2129
+ return cache.match(cacheKey);
1960
2130
  }
1961
- if (details.googleAnalytics?.length === 0) {
1962
- throw new SerwistError("invalid-cache-name", {
1963
- cacheNameId: "googleAnalytics",
1964
- value: details.googleAnalytics
2131
+ return undefined;
2132
+ }
2133
+ createHandlerBoundToUrl(url) {
2134
+ const cacheKey = this.getPrecacheKeyForUrl(url);
2135
+ if (!cacheKey) {
2136
+ throw new SerwistError("non-precached-url", {
2137
+ url
1965
2138
  });
1966
2139
  }
2140
+ return (options)=>{
2141
+ options.request = new Request(url);
2142
+ options.params = {
2143
+ cacheKey,
2144
+ ...options.params
2145
+ };
2146
+ return this._strategy.handle(options);
2147
+ };
2148
+ }
2149
+ }
2150
+
2151
+ class PrecacheFallbackPlugin {
2152
+ _fallbackUrls;
2153
+ _serwist;
2154
+ constructor({ fallbackUrls, serwist }){
2155
+ this._fallbackUrls = fallbackUrls;
2156
+ this._serwist = serwist instanceof Serwist ? serwist.state : serwist;
2157
+ }
2158
+ async handlerDidError(param) {
2159
+ for (const fallback of this._fallbackUrls){
2160
+ if (typeof fallback === "string") {
2161
+ const fallbackResponse = handleRequest(this._serwist, {
2162
+ request: new Request(fallback, {
2163
+ credentials: "same-origin"
2164
+ }),
2165
+ event: param.event
2166
+ });
2167
+ if (fallbackResponse !== undefined) {
2168
+ return await fallbackResponse;
2169
+ }
2170
+ } else if (fallback.matcher(param)) {
2171
+ const fallbackResponse = handleRequest(this._serwist, {
2172
+ request: new Request(fallback.url, {
2173
+ credentials: "same-origin"
2174
+ }),
2175
+ event: param.event
2176
+ });
2177
+ if (fallbackResponse !== undefined) {
2178
+ return await fallbackResponse;
2179
+ }
2180
+ }
2181
+ }
2182
+ return undefined;
1967
2183
  }
1968
- cacheNames$1.updateDetails(details);
1969
- };
2184
+ }
1970
2185
 
1971
- class Serwist {
1972
- _routes;
1973
- _defaultHandlerMap;
1974
- _precacheController;
1975
- _controllers;
1976
- _catchHandler;
1977
- constructor({ precacheEntries, precacheOptions, controllers = [], skipWaiting = false, importScripts, navigationPreload = false, cacheId, clientsClaim: clientsClaim$1 = false, runtimeCaching, offlineAnalyticsConfig, disableDevLogs: disableDevLogs$1 = false, fallbacks } = {}){
1978
- this._routes = new Map();
1979
- this._defaultHandlerMap = new Map();
1980
- this._controllers = controllers;
1981
- this.handleInstall = this.handleInstall.bind(this);
1982
- this.handleActivate = this.handleActivate.bind(this);
1983
- this.handleFetch = this.handleFetch.bind(this);
1984
- this.handleCache = this.handleCache.bind(this);
1985
- if (!!importScripts && importScripts.length > 0) self.importScripts(...importScripts);
1986
- if (navigationPreload) enableNavigationPreload();
1987
- if (cacheId !== undefined) {
1988
- setCacheNameDetails({
1989
- prefix: cacheId
2186
+ class RuntimeCache {
2187
+ _entries;
2188
+ _options;
2189
+ constructor(entries, options = {}){
2190
+ this._entries = entries;
2191
+ this._options = options;
2192
+ this.init = this.init.bind(this);
2193
+ this.install = this.install.bind(this);
2194
+ }
2195
+ init({ serwist }) {
2196
+ if (this._options.fallbacks !== undefined) {
2197
+ const fallbackPlugin = new PrecacheFallbackPlugin({
2198
+ fallbackUrls: this._options.fallbacks.entries,
2199
+ serwist
1990
2200
  });
1991
- }
1992
- if (skipWaiting) {
1993
- self.skipWaiting();
1994
- } else {
1995
- self.addEventListener("message", (event)=>{
1996
- if (event.data && event.data.type === "SKIP_WAITING") {
1997
- self.skipWaiting();
2201
+ this._entries.forEach((cacheEntry)=>{
2202
+ if (cacheEntry.handler instanceof Strategy && !cacheEntry.handler.plugins.some((plugin)=>"handlerDidError" in plugin)) {
2203
+ cacheEntry.handler.plugins.push(fallbackPlugin);
1998
2204
  }
1999
2205
  });
2000
2206
  }
2001
- if (clientsClaim$1) clientsClaim();
2002
- this._precacheController = new PrecacheController(precacheEntries ?? [], precacheOptions);
2003
- if (runtimeCaching) {
2004
- if (!this._controllers?.some((controller)=>controller instanceof RuntimeCacheController)) {
2005
- this._controllers.unshift(new RuntimeCacheController(runtimeCaching, {
2006
- fallbacks
2007
- }));
2008
- } else if (process.env.NODE_ENV !== "production") {
2009
- logger.warn("You have migrated to the Controller pattern, so setting `runtimeCaching` is a no-op.");
2010
- }
2207
+ for (const entry of this._entries){
2208
+ registerCapture(serwist, entry.matcher, entry.handler, entry.method);
2011
2209
  }
2012
- this._controllers.unshift(this._precacheController);
2013
- if (offlineAnalyticsConfig !== undefined) {
2014
- if (typeof offlineAnalyticsConfig === "boolean") {
2015
- offlineAnalyticsConfig && initializeGoogleAnalytics({
2016
- serwist: this
2210
+ }
2211
+ async install({ event, serwist }) {
2212
+ const concurrency = this._options.warmOptions?.concurrency ?? 10;
2213
+ if (this._options.warmEntries) {
2214
+ await parallel(concurrency, this._options.warmEntries, async (entry)=>{
2215
+ const request = entry instanceof Request ? entry : typeof entry === "string" ? new Request(entry, {
2216
+ credentials: "same-origin"
2217
+ }) : new Request(entry.url, {
2218
+ integrity: entry.integrity,
2219
+ credentials: "same-origin"
2017
2220
  });
2018
- } else {
2019
- initializeGoogleAnalytics({
2020
- ...offlineAnalyticsConfig,
2021
- serwist: this
2221
+ await handleRequest(serwist, {
2222
+ request,
2223
+ event
2022
2224
  });
2023
- }
2024
- }
2025
- for (const callback of this.iterateControllers("init")){
2026
- callback({
2027
- serwist: this
2028
2225
  });
2029
2226
  }
2030
- if (disableDevLogs$1) disableDevLogs();
2031
- }
2032
- *iterateControllers(name) {
2033
- if (!this._controllers) return;
2034
- for (const controller of this._controllers){
2035
- if (typeof controller[name] === "function") {
2036
- const callback = (param)=>{
2037
- controller[name](param);
2038
- };
2039
- yield callback;
2040
- }
2041
- }
2042
- }
2043
- get precache() {
2044
- return this._precacheController;
2045
- }
2046
- get precacheStrategy() {
2047
- return this._precacheController.strategy;
2048
- }
2049
- get routes() {
2050
- return this._routes;
2051
2227
  }
2052
- addEventListeners() {
2053
- self.addEventListener("install", this.handleInstall);
2054
- self.addEventListener("activate", this.handleActivate);
2055
- self.addEventListener("fetch", this.handleFetch);
2056
- self.addEventListener("message", this.handleCache);
2228
+ warmRuntimeCache(entries) {
2229
+ if (!this._options.warmEntries) this._options.warmEntries = [];
2230
+ this._options.warmEntries.push(...entries);
2057
2231
  }
2058
- handleInstall(event) {
2232
+ }
2233
+
2234
+ const addEventListeners = (state)=>{
2235
+ self.addEventListener("install", createInstallHandler(state));
2236
+ self.addEventListener("activate", createActivateHandler(state));
2237
+ self.addEventListener("fetch", createFetchHandler(state));
2238
+ self.addEventListener("message", createCacheHandler(state));
2239
+ };
2240
+ const createInstallHandler = (state)=>{
2241
+ return (event)=>{
2059
2242
  return waitUntil(event, async ()=>{
2060
- for (const callback of this.iterateControllers("install")){
2243
+ for (const callback of iterateExtensions(state, "install")){
2061
2244
  await callback({
2062
2245
  event,
2063
- serwist: this
2246
+ serwist: state
2064
2247
  });
2065
2248
  }
2066
2249
  });
2067
- }
2068
- handleActivate(event) {
2250
+ };
2251
+ };
2252
+ const createActivateHandler = (state)=>{
2253
+ return (event)=>{
2069
2254
  return waitUntil(event, async ()=>{
2070
- for (const callback of this.iterateControllers("activate")){
2255
+ for (const callback of iterateExtensions(state, "activate")){
2071
2256
  await callback({
2072
2257
  event,
2073
- serwist: this
2258
+ serwist: state
2074
2259
  });
2075
2260
  }
2076
2261
  });
2077
- }
2078
- handleFetch(event) {
2262
+ };
2263
+ };
2264
+ const createFetchHandler = (state)=>{
2265
+ return (event)=>{
2079
2266
  const { request } = event;
2080
- const responsePromise = this.handleRequest({
2267
+ const responsePromise = handleRequest(state, {
2081
2268
  request,
2082
2269
  event
2083
2270
  });
2084
2271
  if (responsePromise) {
2085
2272
  event.respondWith(responsePromise);
2086
2273
  }
2087
- }
2088
- handleCache(event) {
2274
+ };
2275
+ };
2276
+ const createCacheHandler = (state)=>{
2277
+ return (event)=>{
2089
2278
  if (event.data && event.data.type === "CACHE_URLS") {
2090
2279
  const { payload } = event.data;
2091
2280
  if (process.env.NODE_ENV !== "production") {
@@ -2098,7 +2287,7 @@ class Serwist {
2098
2287
  } else {
2099
2288
  request = new Request(...entry);
2100
2289
  }
2101
- return this.handleRequest({
2290
+ return handleRequest(state, {
2102
2291
  request,
2103
2292
  event
2104
2293
  });
@@ -2108,218 +2297,170 @@ class Serwist {
2108
2297
  void requestPromises.then(()=>event.ports[0].postMessage(true));
2109
2298
  }
2110
2299
  }
2300
+ };
2301
+ };
2302
+ const setDefaultHandler = (state, handler, method = defaultMethod)=>{
2303
+ state.defaultHandlerMap.set(method, normalizeHandler(handler));
2304
+ };
2305
+ const setCatchHandler = (state, handler)=>{
2306
+ state.catchHandler = normalizeHandler(handler);
2307
+ };
2308
+ function* iterateExtensions(state, name) {
2309
+ if (!state.extensions) return;
2310
+ for (const controller of state.extensions){
2311
+ if (typeof controller[name] === "function") {
2312
+ const callback = (param)=>{
2313
+ controller[name](param);
2314
+ };
2315
+ yield callback;
2316
+ }
2317
+ }
2318
+ }
2319
+
2320
+ const createSerwist = ({ precache, skipWaiting = false, importScripts, navigationPreload = false, cacheId, clientsClaim: clientsClaim$1 = false, disableDevLogs: disableDevLogs$1 = false, extensions = [] } = {})=>{
2321
+ if (importScripts?.length) self.importScripts(...importScripts);
2322
+ if (navigationPreload) enableNavigationPreload();
2323
+ if (cacheId) setCacheNameDetails({
2324
+ prefix: cacheId
2325
+ });
2326
+ if (skipWaiting) {
2327
+ self.skipWaiting();
2328
+ } else {
2329
+ self.addEventListener("message", (event)=>{
2330
+ if (event.data?.type === "SKIP_WAITING") {
2331
+ self.skipWaiting();
2332
+ }
2333
+ });
2334
+ }
2335
+ if (clientsClaim$1) clientsClaim();
2336
+ const precacheExtension = new Precache(precache ?? {
2337
+ entries: []
2338
+ });
2339
+ const routes = new Map();
2340
+ const defaultHandlerMap = new Map();
2341
+ const exts = [
2342
+ precacheExtension,
2343
+ ...extensions
2344
+ ];
2345
+ const state = {
2346
+ get routes () {
2347
+ return routes;
2348
+ },
2349
+ get defaultHandlerMap () {
2350
+ return defaultHandlerMap;
2351
+ },
2352
+ get precache () {
2353
+ return precacheExtension;
2354
+ },
2355
+ get extensions () {
2356
+ return exts;
2357
+ }
2358
+ };
2359
+ for (const callback of iterateExtensions(state, "init")){
2360
+ callback({
2361
+ serwist: state
2362
+ });
2363
+ }
2364
+ if (disableDevLogs$1) disableDevLogs();
2365
+ return state;
2366
+ };
2367
+
2368
+ class Serwist {
2369
+ _state;
2370
+ _installHandler;
2371
+ _activateHandler;
2372
+ _fetchHandler;
2373
+ _cacheHandler;
2374
+ constructor({ precacheEntries, precacheOptions, skipWaiting = false, importScripts, navigationPreload = false, cacheId, clientsClaim = false, runtimeCaching, offlineAnalyticsConfig, disableDevLogs = false, fallbacks, extensions } = {}){
2375
+ this.handleInstall = this.handleInstall.bind(this);
2376
+ this.handleActivate = this.handleActivate.bind(this);
2377
+ this.handleFetch = this.handleFetch.bind(this);
2378
+ this.handleCache = this.handleCache.bind(this);
2379
+ this._state = createSerwist({
2380
+ precache: {
2381
+ entries: precacheEntries ?? [],
2382
+ ...precacheOptions
2383
+ },
2384
+ extensions: [
2385
+ !extensions?.some((ext)=>ext instanceof RuntimeCache) && runtimeCaching !== undefined ? new RuntimeCache(runtimeCaching, {
2386
+ fallbacks
2387
+ }) : undefined,
2388
+ !extensions?.some((ext)=>ext instanceof GoogleAnalytics) && offlineAnalyticsConfig !== undefined ? typeof offlineAnalyticsConfig === "boolean" ? offlineAnalyticsConfig ? new GoogleAnalytics() : undefined : new GoogleAnalytics(offlineAnalyticsConfig) : undefined,
2389
+ ...extensions ?? []
2390
+ ].filter((extension)=>extension !== undefined),
2391
+ skipWaiting,
2392
+ importScripts,
2393
+ navigationPreload,
2394
+ cacheId,
2395
+ clientsClaim,
2396
+ disableDevLogs
2397
+ });
2398
+ this._installHandler = createInstallHandler(this._state);
2399
+ this._activateHandler = createActivateHandler(this._state);
2400
+ this._fetchHandler = createFetchHandler(this._state);
2401
+ this._cacheHandler = createCacheHandler(this._state);
2402
+ }
2403
+ get precache() {
2404
+ return this._state.precache;
2405
+ }
2406
+ get precacheStrategy() {
2407
+ return this._state.precache.strategy;
2408
+ }
2409
+ get routes() {
2410
+ return this._state.routes;
2411
+ }
2412
+ get state() {
2413
+ return this._state;
2414
+ }
2415
+ addEventListeners() {
2416
+ self.addEventListener("install", this._installHandler);
2417
+ self.addEventListener("activate", this._activateHandler);
2418
+ self.addEventListener("fetch", this._fetchHandler);
2419
+ self.addEventListener("message", this._cacheHandler);
2111
2420
  }
2112
- setDefaultHandler(handler, method = defaultMethod) {
2113
- this._defaultHandlerMap.set(method, normalizeHandler(handler));
2421
+ handleInstall(event) {
2422
+ return this._installHandler(event);
2423
+ }
2424
+ handleActivate(event) {
2425
+ return this._activateHandler(event);
2426
+ }
2427
+ handleFetch(event) {
2428
+ return this._fetchHandler(event);
2429
+ }
2430
+ handleCache(event) {
2431
+ return this._cacheHandler(event);
2432
+ }
2433
+ setDefaultHandler(handler, method) {
2434
+ setDefaultHandler(this._state, handler, method);
2114
2435
  }
2115
2436
  setCatchHandler(handler) {
2116
- this._catchHandler = normalizeHandler(handler);
2437
+ setCatchHandler(this._state, handler);
2117
2438
  }
2118
2439
  registerCapture(capture, handler, method) {
2119
- const route = parseRoute(capture, handler, method);
2120
- this.registerRoute(route);
2121
- return route;
2440
+ return registerCapture(this._state, capture, handler, method);
2122
2441
  }
2123
2442
  registerRoute(route) {
2124
- if (process.env.NODE_ENV !== "production") {
2125
- finalAssertExports.isType(route, "object", {
2126
- moduleName: "serwist",
2127
- className: "Serwist",
2128
- funcName: "registerRoute",
2129
- paramName: "route"
2130
- });
2131
- finalAssertExports.hasMethod(route, "match", {
2132
- moduleName: "serwist",
2133
- className: "Serwist",
2134
- funcName: "registerRoute",
2135
- paramName: "route"
2136
- });
2137
- finalAssertExports.isType(route.handler, "object", {
2138
- moduleName: "serwist",
2139
- className: "Serwist",
2140
- funcName: "registerRoute",
2141
- paramName: "route"
2142
- });
2143
- finalAssertExports.hasMethod(route.handler, "handle", {
2144
- moduleName: "serwist",
2145
- className: "Serwist",
2146
- funcName: "registerRoute",
2147
- paramName: "route.handler"
2148
- });
2149
- finalAssertExports.isType(route.method, "string", {
2150
- moduleName: "serwist",
2151
- className: "Serwist",
2152
- funcName: "registerRoute",
2153
- paramName: "route.method"
2154
- });
2155
- }
2156
- if (!this._routes.has(route.method)) {
2157
- this._routes.set(route.method, []);
2158
- }
2159
- this._routes.get(route.method).push(route);
2443
+ registerRoute(this._state, route);
2160
2444
  }
2161
2445
  unregisterRoute(route) {
2162
- if (!this._routes.has(route.method)) {
2163
- throw new SerwistError("unregister-route-but-not-found-with-method", {
2164
- method: route.method
2165
- });
2166
- }
2167
- const routeIndex = this._routes.get(route.method).indexOf(route);
2168
- if (routeIndex > -1) {
2169
- this._routes.get(route.method).splice(routeIndex, 1);
2170
- } else {
2171
- throw new SerwistError("unregister-route-route-not-registered");
2172
- }
2446
+ unregisterRoute(this._state, route);
2173
2447
  }
2174
2448
  handleRequest({ request, event }) {
2175
- if (process.env.NODE_ENV !== "production") {
2176
- finalAssertExports.isInstance(request, Request, {
2177
- moduleName: "serwist",
2178
- className: "Serwist",
2179
- funcName: "handleRequest",
2180
- paramName: "options.request"
2181
- });
2182
- }
2183
- const url = new URL(request.url, location.href);
2184
- if (!url.protocol.startsWith("http")) {
2185
- if (process.env.NODE_ENV !== "production") {
2186
- logger.debug("Router only supports URLs that start with 'http'.");
2187
- }
2188
- return;
2189
- }
2190
- const sameOrigin = url.origin === location.origin;
2191
- const { params, route } = this.findMatchingRoute({
2192
- event,
2449
+ return handleRequest(this._state, {
2193
2450
  request,
2194
- sameOrigin,
2195
- url
2451
+ event
2196
2452
  });
2197
- let handler = route?.handler;
2198
- const debugMessages = [];
2199
- if (process.env.NODE_ENV !== "production") {
2200
- if (handler) {
2201
- debugMessages.push([
2202
- "Found a route to handle this request:",
2203
- route
2204
- ]);
2205
- if (params) {
2206
- debugMessages.push([
2207
- `Passing the following params to the route's handler:`,
2208
- params
2209
- ]);
2210
- }
2211
- }
2212
- }
2213
- const method = request.method;
2214
- if (!handler && this._defaultHandlerMap.has(method)) {
2215
- if (process.env.NODE_ENV !== "production") {
2216
- debugMessages.push(`Failed to find a matching route. Falling back to the default handler for ${method}.`);
2217
- }
2218
- handler = this._defaultHandlerMap.get(method);
2219
- }
2220
- if (!handler) {
2221
- if (process.env.NODE_ENV !== "production") {
2222
- logger.debug(`No route found for: ${getFriendlyURL(url)}`);
2223
- }
2224
- return;
2225
- }
2226
- if (process.env.NODE_ENV !== "production") {
2227
- logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`);
2228
- for (const msg of debugMessages){
2229
- if (Array.isArray(msg)) {
2230
- logger.log(...msg);
2231
- } else {
2232
- logger.log(msg);
2233
- }
2234
- }
2235
- logger.groupEnd();
2236
- }
2237
- let responsePromise;
2238
- try {
2239
- responsePromise = handler.handle({
2240
- url,
2241
- request,
2242
- event,
2243
- params
2244
- });
2245
- } catch (err) {
2246
- responsePromise = Promise.reject(err);
2247
- }
2248
- const catchHandler = route?.catchHandler;
2249
- if (responsePromise instanceof Promise && (this._catchHandler || catchHandler)) {
2250
- responsePromise = responsePromise.catch(async (err)=>{
2251
- if (catchHandler) {
2252
- if (process.env.NODE_ENV !== "production") {
2253
- logger.groupCollapsed(`Error thrown when responding to: ${getFriendlyURL(url)}. Falling back to route's Catch Handler.`);
2254
- logger.error("Error thrown by:", route);
2255
- logger.error(err);
2256
- logger.groupEnd();
2257
- }
2258
- try {
2259
- return await catchHandler.handle({
2260
- url,
2261
- request,
2262
- event,
2263
- params
2264
- });
2265
- } catch (catchErr) {
2266
- if (catchErr instanceof Error) {
2267
- err = catchErr;
2268
- }
2269
- }
2270
- }
2271
- if (this._catchHandler) {
2272
- if (process.env.NODE_ENV !== "production") {
2273
- logger.groupCollapsed(`Error thrown when responding to: ${getFriendlyURL(url)}. Falling back to global Catch Handler.`);
2274
- logger.error("Error thrown by:", route);
2275
- logger.error(err);
2276
- logger.groupEnd();
2277
- }
2278
- return this._catchHandler.handle({
2279
- url,
2280
- request,
2281
- event
2282
- });
2283
- }
2284
- throw err;
2285
- });
2286
- }
2287
- return responsePromise;
2288
2453
  }
2289
2454
  findMatchingRoute({ url, sameOrigin, request, event }) {
2290
- const routes = this._routes.get(request.method) || [];
2291
- for (const route of routes){
2292
- let params;
2293
- const matchResult = route.match({
2294
- url,
2295
- sameOrigin,
2296
- request,
2297
- event
2298
- });
2299
- if (matchResult) {
2300
- if (process.env.NODE_ENV !== "production") {
2301
- if (matchResult instanceof Promise) {
2302
- logger.warn(`While routing ${getFriendlyURL(url)}, an async matchCallback function was used. Please convert the following route to use a synchronous matchCallback function:`, route);
2303
- }
2304
- }
2305
- params = matchResult;
2306
- if (Array.isArray(params) && params.length === 0) {
2307
- params = undefined;
2308
- } else if (matchResult.constructor === Object && Object.keys(matchResult).length === 0) {
2309
- params = undefined;
2310
- } else if (typeof matchResult === "boolean") {
2311
- params = undefined;
2312
- }
2313
- return {
2314
- route,
2315
- params
2316
- };
2317
- }
2318
- }
2319
- return {};
2455
+ return findMatchingRoute(this._state, {
2456
+ url,
2457
+ sameOrigin,
2458
+ request,
2459
+ event
2460
+ });
2320
2461
  }
2321
2462
  addToPrecacheList(entries) {
2322
- this._precacheController.addToCacheList(entries);
2463
+ this.precache.addToCacheList(entries);
2323
2464
  }
2324
2465
  getUrlsToPrecacheKeys() {
2325
2466
  return this.precache.getUrlsToPrecacheKeys();
@@ -2341,38 +2482,6 @@ class Serwist {
2341
2482
  }
2342
2483
  }
2343
2484
 
2344
- const cacheNames = {
2345
- get googleAnalytics () {
2346
- return cacheNames$1.getGoogleAnalyticsName();
2347
- },
2348
- get precache () {
2349
- return cacheNames$1.getPrecacheName();
2350
- },
2351
- get prefix () {
2352
- return cacheNames$1.getPrefix();
2353
- },
2354
- get runtime () {
2355
- return cacheNames$1.getRuntimeName();
2356
- },
2357
- get suffix () {
2358
- return cacheNames$1.getSuffix();
2359
- }
2360
- };
2361
-
2362
- const registerQuotaErrorCallback = (callback)=>{
2363
- if (process.env.NODE_ENV !== "production") {
2364
- finalAssertExports.isType(callback, "function", {
2365
- moduleName: "@serwist/core",
2366
- funcName: "register",
2367
- paramName: "callback"
2368
- });
2369
- }
2370
- quotaErrorCallbacks.add(callback);
2371
- if (process.env.NODE_ENV !== "production") {
2372
- logger.log("Registered a callback to respond to quota errors.", callback);
2373
- }
2374
- };
2375
-
2376
2485
  const BROADCAST_UPDATE_MESSAGE_TYPE = "CACHE_UPDATED";
2377
2486
  const BROADCAST_UPDATE_MESSAGE_META = "serwist-broadcast-update";
2378
2487
  const BROADCAST_UPDATE_DEFAULT_NOTIFY = true;
@@ -2846,7 +2955,7 @@ class ExpirationPlugin {
2846
2955
  })();
2847
2956
  try {
2848
2957
  event.waitUntil(done);
2849
- } catch (error) {
2958
+ } catch {
2850
2959
  if (process.env.NODE_ENV !== "production") {
2851
2960
  if (event instanceof FetchEvent) {
2852
2961
  logger.warn(`Unable to ensure service worker stays alive when updating cache entry for '${getFriendlyURL(event.request.url)}'.`);
@@ -3046,7 +3155,7 @@ class CacheFirst extends Strategy {
3046
3155
  });
3047
3156
  }
3048
3157
  let response = await handler.cacheMatch(request);
3049
- let error = undefined;
3158
+ let error;
3050
3159
  if (!response) {
3051
3160
  if (process.env.NODE_ENV !== "production") {
3052
3161
  logs.push(`No response found in the '${this.cacheName}' cache. Will respond with a network request.`);
@@ -3138,7 +3247,7 @@ class StaleWhileRevalidate extends Strategy {
3138
3247
  const fetchAndCachePromise = handler.fetchAndCachePut(request).catch(()=>{});
3139
3248
  void handler.waitUntil(fetchAndCachePromise);
3140
3249
  let response = await handler.cacheMatch(request);
3141
- let error = undefined;
3250
+ let error;
3142
3251
  if (response) {
3143
3252
  if (process.env.NODE_ENV !== "production") {
3144
3253
  logs.push(`Found a cached response in the '${this.cacheName}' cache. Will update with the network response in the background.`);
@@ -3173,4 +3282,4 @@ class StaleWhileRevalidate extends Strategy {
3173
3282
  }
3174
3283
  }
3175
3284
 
3176
- export { BROADCAST_UPDATE_DEFAULT_HEADERS, BackgroundSyncPlugin, BackgroundSyncQueue, BackgroundSyncQueueStore, BroadcastCacheUpdate, BroadcastUpdatePlugin, CacheExpiration, CacheFirst, CacheOnly, CacheableResponse, CacheableResponsePlugin, ExpirationPlugin, NavigationRoute, NetworkFirst, NetworkOnly, PrecacheFallbackPlugin, PrecacheRoute, PrecacheStrategy, RangeRequestsPlugin, RegExpRoute, Route, RuntimeCacheController, Serwist, StaleWhileRevalidate, StorableRequest, Strategy, StrategyHandler, cacheNames, copyResponse, createPartialResponse, disableDevLogs, disableNavigationPreload, enableNavigationPreload, initializeGoogleAnalytics, isNavigationPreloadSupported, registerQuotaErrorCallback, responsesAreSame, setCacheNameDetails };
3285
+ export { BROADCAST_UPDATE_DEFAULT_HEADERS, BackgroundSyncPlugin, BackgroundSyncQueue, BackgroundSyncQueueStore, BroadcastCacheUpdate, BroadcastUpdatePlugin, CacheExpiration, CacheFirst, CacheOnly, CacheableResponse, CacheableResponsePlugin, ExpirationPlugin, GoogleAnalytics, NavigationRoute, NetworkFirst, NetworkOnly, PrecacheFallbackPlugin, PrecacheRoute, PrecacheStrategy, RangeRequestsPlugin, RegExpRoute, Route, RuntimeCache, Serwist, StaleWhileRevalidate, StorableRequest, Strategy, StrategyHandler, addEventListeners, cacheNames, copyResponse, createActivateHandler, createCacheHandler, createFetchHandler, createInstallHandler, createPartialResponse, createSerwist, disableDevLogs, disableNavigationPreload, enableNavigationPreload, findMatchingRoute, handleRequest, initializeGoogleAnalytics, isNavigationPreloadSupported, iterateExtensions, registerCapture, registerQuotaErrorCallback, registerRoute, responsesAreSame, setCacheNameDetails, setCatchHandler, setDefaultHandler, unregisterRoute };