serwist 10.0.0-preview.1 → 10.0.0-preview.11

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