@serwist/strategies
Advanced tools
Comparing version 9.0.0-preview.17 to 9.0.0-preview.18
@@ -1,22 +0,3 @@ | ||
import { CacheFirst } from "./CacheFirst.js"; | ||
import { CacheOnly } from "./CacheOnly.js"; | ||
import type { NetworkFirstOptions } from "./NetworkFirst.js"; | ||
import { NetworkFirst } from "./NetworkFirst.js"; | ||
import type { NetworkOnlyOptions } from "./NetworkOnly.js"; | ||
import { NetworkOnly } from "./NetworkOnly.js"; | ||
import { StaleWhileRevalidate } from "./StaleWhileRevalidate.js"; | ||
import type { StrategyOptions } from "./Strategy.js"; | ||
import { Strategy } from "./Strategy.js"; | ||
import { StrategyHandler } from "./StrategyHandler.js"; | ||
declare global { | ||
interface FetchEvent { | ||
readonly preloadResponse: Promise<any>; | ||
} | ||
} | ||
/** | ||
* There are common caching strategies that most service workers will need | ||
* and use. This module provides simple implementations of these strategies. | ||
*/ | ||
export { CacheFirst, CacheOnly, NetworkFirst, NetworkOnly, StaleWhileRevalidate, Strategy, StrategyHandler }; | ||
export type { NetworkFirstOptions, NetworkOnlyOptions, StrategyOptions }; | ||
export { CacheFirst, CacheOnly, NetworkFirst, NetworkOnly, StaleWhileRevalidate, Strategy, StrategyHandler } from "@serwist/sw/strategies"; | ||
export type { NetworkFirstOptions, NetworkOnlyOptions, StrategyOptions } from "@serwist/sw/strategies"; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -1,738 +0,1 @@ | ||
import { assert, Deferred, logger, getFriendlyURL, SerwistError, timeout, cacheMatchIgnoreParams, executeQuotaErrorCallbacks, privateCacheNames } from '@serwist/core/internal'; | ||
function toRequest(input) { | ||
return typeof input === "string" ? new Request(input) : input; | ||
} | ||
class StrategyHandler { | ||
event; | ||
request; | ||
url; | ||
params; | ||
_cacheKeys = {}; | ||
_strategy; | ||
_handlerDeferred; | ||
_extendLifetimePromises; | ||
_plugins; | ||
_pluginStateMap; | ||
constructor(strategy, options){ | ||
if (process.env.NODE_ENV !== "production") { | ||
assert.isInstance(options.event, ExtendableEvent, { | ||
moduleName: "@serwist/strategies", | ||
className: "StrategyHandler", | ||
funcName: "constructor", | ||
paramName: "options.event" | ||
}); | ||
assert.isInstance(options.request, Request, { | ||
moduleName: "@serwist/strategies", | ||
className: "StrategyHandler", | ||
funcName: "constructor", | ||
paramName: "options.request" | ||
}); | ||
} | ||
this.event = options.event; | ||
this.request = options.request; | ||
if (options.url) { | ||
this.url = options.url; | ||
this.params = options.params; | ||
} | ||
this._strategy = strategy; | ||
this._handlerDeferred = new Deferred(); | ||
this._extendLifetimePromises = []; | ||
this._plugins = [ | ||
...strategy.plugins | ||
]; | ||
this._pluginStateMap = new Map(); | ||
for (const plugin of this._plugins){ | ||
this._pluginStateMap.set(plugin, {}); | ||
} | ||
this.event.waitUntil(this._handlerDeferred.promise); | ||
} | ||
async fetch(input) { | ||
const { event } = this; | ||
let request = toRequest(input); | ||
if (request.mode === "navigate" && event instanceof FetchEvent && event.preloadResponse) { | ||
const possiblePreloadResponse = await event.preloadResponse; | ||
if (possiblePreloadResponse) { | ||
if (process.env.NODE_ENV !== "production") { | ||
logger.log(`Using a preloaded navigation response for '${getFriendlyURL(request.url)}'`); | ||
} | ||
return possiblePreloadResponse; | ||
} | ||
} | ||
const originalRequest = this.hasCallback("fetchDidFail") ? request.clone() : null; | ||
try { | ||
for (const cb of this.iterateCallbacks("requestWillFetch")){ | ||
request = await cb({ | ||
request: request.clone(), | ||
event | ||
}); | ||
} | ||
} catch (err) { | ||
if (err instanceof Error) { | ||
throw new SerwistError("plugin-error-request-will-fetch", { | ||
thrownErrorMessage: err.message | ||
}); | ||
} | ||
} | ||
const pluginFilteredRequest = request.clone(); | ||
try { | ||
let fetchResponse; | ||
fetchResponse = await fetch(request, request.mode === "navigate" ? undefined : this._strategy.fetchOptions); | ||
if (process.env.NODE_ENV !== "production") { | ||
logger.debug(`Network request for '${getFriendlyURL(request.url)}' returned a response with status '${fetchResponse.status}'.`); | ||
} | ||
for (const callback of this.iterateCallbacks("fetchDidSucceed")){ | ||
fetchResponse = await callback({ | ||
event, | ||
request: pluginFilteredRequest, | ||
response: fetchResponse | ||
}); | ||
} | ||
return fetchResponse; | ||
} catch (error) { | ||
if (process.env.NODE_ENV !== "production") { | ||
logger.log(`Network request for '${getFriendlyURL(request.url)}' threw an error.`, error); | ||
} | ||
if (originalRequest) { | ||
await this.runCallbacks("fetchDidFail", { | ||
error: error, | ||
event, | ||
originalRequest: originalRequest.clone(), | ||
request: pluginFilteredRequest.clone() | ||
}); | ||
} | ||
throw error; | ||
} | ||
} | ||
async fetchAndCachePut(input) { | ||
const response = await this.fetch(input); | ||
const responseClone = response.clone(); | ||
void this.waitUntil(this.cachePut(input, responseClone)); | ||
return response; | ||
} | ||
async cacheMatch(key) { | ||
const request = toRequest(key); | ||
let cachedResponse; | ||
const { cacheName, matchOptions } = this._strategy; | ||
const effectiveRequest = await this.getCacheKey(request, "read"); | ||
const multiMatchOptions = { | ||
...matchOptions, | ||
...{ | ||
cacheName | ||
} | ||
}; | ||
cachedResponse = await caches.match(effectiveRequest, multiMatchOptions); | ||
if (process.env.NODE_ENV !== "production") { | ||
if (cachedResponse) { | ||
logger.debug(`Found a cached response in '${cacheName}'.`); | ||
} else { | ||
logger.debug(`No cached response found in '${cacheName}'.`); | ||
} | ||
} | ||
for (const callback of this.iterateCallbacks("cachedResponseWillBeUsed")){ | ||
cachedResponse = await callback({ | ||
cacheName, | ||
matchOptions, | ||
cachedResponse, | ||
request: effectiveRequest, | ||
event: this.event | ||
}) || undefined; | ||
} | ||
return cachedResponse; | ||
} | ||
async cachePut(key, response) { | ||
const request = toRequest(key); | ||
await timeout(0); | ||
const effectiveRequest = await this.getCacheKey(request, "write"); | ||
if (process.env.NODE_ENV !== "production") { | ||
if (effectiveRequest.method && effectiveRequest.method !== "GET") { | ||
throw new SerwistError("attempt-to-cache-non-get-request", { | ||
url: getFriendlyURL(effectiveRequest.url), | ||
method: effectiveRequest.method | ||
}); | ||
} | ||
} | ||
if (!response) { | ||
if (process.env.NODE_ENV !== "production") { | ||
logger.error(`Cannot cache non-existent response for '${getFriendlyURL(effectiveRequest.url)}'.`); | ||
} | ||
throw new SerwistError("cache-put-with-no-response", { | ||
url: getFriendlyURL(effectiveRequest.url) | ||
}); | ||
} | ||
const responseToCache = await this._ensureResponseSafeToCache(response); | ||
if (!responseToCache) { | ||
if (process.env.NODE_ENV !== "production") { | ||
logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' will not be cached.`, responseToCache); | ||
} | ||
return false; | ||
} | ||
const { cacheName, matchOptions } = this._strategy; | ||
const cache = await self.caches.open(cacheName); | ||
if (process.env.NODE_ENV !== "production") { | ||
const vary = response.headers.get("Vary"); | ||
if (vary && matchOptions?.ignoreVary !== true) { | ||
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.`); | ||
} | ||
} | ||
const hasCacheUpdateCallback = this.hasCallback("cacheDidUpdate"); | ||
const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams(cache, effectiveRequest.clone(), [ | ||
"__WB_REVISION__" | ||
], matchOptions) : null; | ||
if (process.env.NODE_ENV !== "production") { | ||
logger.debug(`Updating the '${cacheName}' cache with a new Response for ${getFriendlyURL(effectiveRequest.url)}.`); | ||
} | ||
try { | ||
await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache); | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
if (error.name === "QuotaExceededError") { | ||
await executeQuotaErrorCallbacks(); | ||
} | ||
throw error; | ||
} | ||
} | ||
for (const callback of this.iterateCallbacks("cacheDidUpdate")){ | ||
await callback({ | ||
cacheName, | ||
oldResponse, | ||
newResponse: responseToCache.clone(), | ||
request: effectiveRequest, | ||
event: this.event | ||
}); | ||
} | ||
return true; | ||
} | ||
async getCacheKey(request, mode) { | ||
const key = `${request.url} | ${mode}`; | ||
if (!this._cacheKeys[key]) { | ||
let effectiveRequest = request; | ||
for (const callback of this.iterateCallbacks("cacheKeyWillBeUsed")){ | ||
effectiveRequest = toRequest(await callback({ | ||
mode, | ||
request: effectiveRequest, | ||
event: this.event, | ||
params: this.params | ||
})); | ||
} | ||
this._cacheKeys[key] = effectiveRequest; | ||
} | ||
return this._cacheKeys[key]; | ||
} | ||
hasCallback(name) { | ||
for (const plugin of this._strategy.plugins){ | ||
if (name in plugin) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
async runCallbacks(name, param) { | ||
for (const callback of this.iterateCallbacks(name)){ | ||
await callback(param); | ||
} | ||
} | ||
*iterateCallbacks(name) { | ||
for (const plugin of this._strategy.plugins){ | ||
if (typeof plugin[name] === "function") { | ||
const state = this._pluginStateMap.get(plugin); | ||
const statefulCallback = (param)=>{ | ||
const statefulParam = { | ||
...param, | ||
state | ||
}; | ||
return plugin[name](statefulParam); | ||
}; | ||
yield statefulCallback; | ||
} | ||
} | ||
} | ||
waitUntil(promise) { | ||
this._extendLifetimePromises.push(promise); | ||
return promise; | ||
} | ||
async doneWaiting() { | ||
let promise = undefined; | ||
while(promise = this._extendLifetimePromises.shift()){ | ||
await promise; | ||
} | ||
} | ||
destroy() { | ||
this._handlerDeferred.resolve(null); | ||
} | ||
async _ensureResponseSafeToCache(response) { | ||
let responseToCache = response; | ||
let pluginsUsed = false; | ||
for (const callback of this.iterateCallbacks("cacheWillUpdate")){ | ||
responseToCache = await callback({ | ||
request: this.request, | ||
response: responseToCache, | ||
event: this.event | ||
}) || undefined; | ||
pluginsUsed = true; | ||
if (!responseToCache) { | ||
break; | ||
} | ||
} | ||
if (!pluginsUsed) { | ||
if (responseToCache && responseToCache.status !== 200) { | ||
responseToCache = undefined; | ||
} | ||
if (process.env.NODE_ENV !== "production") { | ||
if (responseToCache) { | ||
if (responseToCache.status !== 200) { | ||
if (responseToCache.status === 0) { | ||
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.`); | ||
} else { | ||
logger.debug(`The response for '${this.request.url}' returned a status code of '${response.status}' and won't be cached as a result.`); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return responseToCache; | ||
} | ||
} | ||
class Strategy { | ||
cacheName; | ||
plugins; | ||
fetchOptions; | ||
matchOptions; | ||
constructor(options = {}){ | ||
this.cacheName = privateCacheNames.getRuntimeName(options.cacheName); | ||
this.plugins = options.plugins || []; | ||
this.fetchOptions = options.fetchOptions; | ||
this.matchOptions = options.matchOptions; | ||
} | ||
handle(options) { | ||
const [responseDone] = this.handleAll(options); | ||
return responseDone; | ||
} | ||
handleAll(options) { | ||
if (options instanceof FetchEvent) { | ||
options = { | ||
event: options, | ||
request: options.request | ||
}; | ||
} | ||
const event = options.event; | ||
const request = typeof options.request === "string" ? new Request(options.request) : options.request; | ||
const handler = new StrategyHandler(this, options.url ? { | ||
event, | ||
request, | ||
url: options.url, | ||
params: options.params | ||
} : { | ||
event, | ||
request | ||
}); | ||
const responseDone = this._getResponse(handler, request, event); | ||
const handlerDone = this._awaitComplete(responseDone, handler, request, event); | ||
return [ | ||
responseDone, | ||
handlerDone | ||
]; | ||
} | ||
async _getResponse(handler, request, event) { | ||
await handler.runCallbacks("handlerWillStart", { | ||
event, | ||
request | ||
}); | ||
let response = undefined; | ||
try { | ||
response = await this._handle(request, handler); | ||
if (response === undefined || response.type === "error") { | ||
throw new SerwistError("no-response", { | ||
url: request.url | ||
}); | ||
} | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
for (const callback of handler.iterateCallbacks("handlerDidError")){ | ||
response = await callback({ | ||
error, | ||
event, | ||
request | ||
}); | ||
if (response !== undefined) { | ||
break; | ||
} | ||
} | ||
} | ||
if (!response) { | ||
throw error; | ||
} | ||
if (process.env.NODE_ENV !== "production") { | ||
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.`); | ||
} | ||
} | ||
for (const callback of handler.iterateCallbacks("handlerWillRespond")){ | ||
response = await callback({ | ||
event, | ||
request, | ||
response | ||
}); | ||
} | ||
return response; | ||
} | ||
async _awaitComplete(responseDone, handler, request, event) { | ||
let response = undefined; | ||
let error = undefined; | ||
try { | ||
response = await responseDone; | ||
} catch (error) {} | ||
try { | ||
await handler.runCallbacks("handlerDidRespond", { | ||
event, | ||
request, | ||
response | ||
}); | ||
await handler.doneWaiting(); | ||
} catch (waitUntilError) { | ||
if (waitUntilError instanceof Error) { | ||
error = waitUntilError; | ||
} | ||
} | ||
await handler.runCallbacks("handlerDidComplete", { | ||
event, | ||
request, | ||
response, | ||
error | ||
}); | ||
handler.destroy(); | ||
if (error) { | ||
throw error; | ||
} | ||
} | ||
} | ||
const messages = { | ||
strategyStart: (strategyName, request)=>`Using ${strategyName} to respond to '${getFriendlyURL(request.url)}'`, | ||
printFinalResponse: (response)=>{ | ||
if (response) { | ||
logger.groupCollapsed("View the final response here."); | ||
logger.log(response || "[No response returned]"); | ||
logger.groupEnd(); | ||
} | ||
} | ||
}; | ||
class CacheFirst extends Strategy { | ||
async _handle(request, handler) { | ||
const logs = []; | ||
if (process.env.NODE_ENV !== "production") { | ||
assert.isInstance(request, Request, { | ||
moduleName: "@serwist/strategies", | ||
className: this.constructor.name, | ||
funcName: "makeRequest", | ||
paramName: "request" | ||
}); | ||
} | ||
let response = await handler.cacheMatch(request); | ||
let error = undefined; | ||
if (!response) { | ||
if (process.env.NODE_ENV !== "production") { | ||
logs.push(`No response found in the '${this.cacheName}' cache. Will respond with a network request.`); | ||
} | ||
try { | ||
response = await handler.fetchAndCachePut(request); | ||
} catch (err) { | ||
if (err instanceof Error) { | ||
error = err; | ||
} | ||
} | ||
if (process.env.NODE_ENV !== "production") { | ||
if (response) { | ||
logs.push("Got response from network."); | ||
} else { | ||
logs.push("Unable to get a response from the network."); | ||
} | ||
} | ||
} else { | ||
if (process.env.NODE_ENV !== "production") { | ||
logs.push(`Found a cached response in the '${this.cacheName}' cache.`); | ||
} | ||
} | ||
if (process.env.NODE_ENV !== "production") { | ||
logger.groupCollapsed(messages.strategyStart(this.constructor.name, request)); | ||
for (const log of logs){ | ||
logger.log(log); | ||
} | ||
messages.printFinalResponse(response); | ||
logger.groupEnd(); | ||
} | ||
if (!response) { | ||
throw new SerwistError("no-response", { | ||
url: request.url, | ||
error | ||
}); | ||
} | ||
return response; | ||
} | ||
} | ||
class CacheOnly extends Strategy { | ||
async _handle(request, handler) { | ||
if (process.env.NODE_ENV !== "production") { | ||
assert.isInstance(request, Request, { | ||
moduleName: "@serwist/strategies", | ||
className: this.constructor.name, | ||
funcName: "makeRequest", | ||
paramName: "request" | ||
}); | ||
} | ||
const response = await handler.cacheMatch(request); | ||
if (process.env.NODE_ENV !== "production") { | ||
logger.groupCollapsed(messages.strategyStart(this.constructor.name, request)); | ||
if (response) { | ||
logger.log(`Found a cached response in the '${this.cacheName}' cache.`); | ||
messages.printFinalResponse(response); | ||
} else { | ||
logger.log(`No response found in the '${this.cacheName}' cache.`); | ||
} | ||
logger.groupEnd(); | ||
} | ||
if (!response) { | ||
throw new SerwistError("no-response", { | ||
url: request.url | ||
}); | ||
} | ||
return response; | ||
} | ||
} | ||
const cacheOkAndOpaquePlugin = { | ||
cacheWillUpdate: async ({ response })=>{ | ||
if (response.status === 200 || response.status === 0) { | ||
return response; | ||
} | ||
return null; | ||
} | ||
}; | ||
class NetworkFirst extends Strategy { | ||
_networkTimeoutSeconds; | ||
constructor(options = {}){ | ||
super(options); | ||
if (!this.plugins.some((p)=>"cacheWillUpdate" in p)) { | ||
this.plugins.unshift(cacheOkAndOpaquePlugin); | ||
} | ||
this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0; | ||
if (process.env.NODE_ENV !== "production") { | ||
if (this._networkTimeoutSeconds) { | ||
assert.isType(this._networkTimeoutSeconds, "number", { | ||
moduleName: "@serwist/strategies", | ||
className: this.constructor.name, | ||
funcName: "constructor", | ||
paramName: "networkTimeoutSeconds" | ||
}); | ||
} | ||
} | ||
} | ||
async _handle(request, handler) { | ||
const logs = []; | ||
if (process.env.NODE_ENV !== "production") { | ||
assert.isInstance(request, Request, { | ||
moduleName: "@serwist/strategies", | ||
className: this.constructor.name, | ||
funcName: "handle", | ||
paramName: "makeRequest" | ||
}); | ||
} | ||
const promises = []; | ||
let timeoutId; | ||
if (this._networkTimeoutSeconds) { | ||
const { id, promise } = this._getTimeoutPromise({ | ||
request, | ||
logs, | ||
handler | ||
}); | ||
timeoutId = id; | ||
promises.push(promise); | ||
} | ||
const networkPromise = this._getNetworkPromise({ | ||
timeoutId, | ||
request, | ||
logs, | ||
handler | ||
}); | ||
promises.push(networkPromise); | ||
const response = await handler.waitUntil((async ()=>{ | ||
return await handler.waitUntil(Promise.race(promises)) || await networkPromise; | ||
})()); | ||
if (process.env.NODE_ENV !== "production") { | ||
logger.groupCollapsed(messages.strategyStart(this.constructor.name, request)); | ||
for (const log of logs){ | ||
logger.log(log); | ||
} | ||
messages.printFinalResponse(response); | ||
logger.groupEnd(); | ||
} | ||
if (!response) { | ||
throw new SerwistError("no-response", { | ||
url: request.url | ||
}); | ||
} | ||
return response; | ||
} | ||
_getTimeoutPromise({ request, logs, handler }) { | ||
let timeoutId; | ||
const timeoutPromise = new Promise((resolve)=>{ | ||
const onNetworkTimeout = async ()=>{ | ||
if (process.env.NODE_ENV !== "production") { | ||
logs.push(`Timing out the network response at ${this._networkTimeoutSeconds} seconds.`); | ||
} | ||
resolve(await handler.cacheMatch(request)); | ||
}; | ||
timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1000); | ||
}); | ||
return { | ||
promise: timeoutPromise, | ||
id: timeoutId | ||
}; | ||
} | ||
async _getNetworkPromise({ timeoutId, request, logs, handler }) { | ||
let error = undefined; | ||
let response = undefined; | ||
try { | ||
response = await handler.fetchAndCachePut(request); | ||
} catch (fetchError) { | ||
if (fetchError instanceof Error) { | ||
error = fetchError; | ||
} | ||
} | ||
if (timeoutId) { | ||
clearTimeout(timeoutId); | ||
} | ||
if (process.env.NODE_ENV !== "production") { | ||
if (response) { | ||
logs.push("Got response from network."); | ||
} else { | ||
logs.push("Unable to get a response from the network. Will respond " + "with a cached response."); | ||
} | ||
} | ||
if (error || !response) { | ||
response = await handler.cacheMatch(request); | ||
if (process.env.NODE_ENV !== "production") { | ||
if (response) { | ||
logs.push(`Found a cached response in the '${this.cacheName}' cache.`); | ||
} else { | ||
logs.push(`No response found in the '${this.cacheName}' cache.`); | ||
} | ||
} | ||
} | ||
return response; | ||
} | ||
} | ||
class NetworkOnly extends Strategy { | ||
_networkTimeoutSeconds; | ||
constructor(options = {}){ | ||
super(options); | ||
this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0; | ||
} | ||
async _handle(request, handler) { | ||
if (process.env.NODE_ENV !== "production") { | ||
assert.isInstance(request, Request, { | ||
moduleName: "@serwist/strategies", | ||
className: this.constructor.name, | ||
funcName: "_handle", | ||
paramName: "request" | ||
}); | ||
} | ||
let error = undefined; | ||
let response; | ||
try { | ||
const promises = [ | ||
handler.fetch(request) | ||
]; | ||
if (this._networkTimeoutSeconds) { | ||
const timeoutPromise = timeout(this._networkTimeoutSeconds * 1000); | ||
promises.push(timeoutPromise); | ||
} | ||
response = await Promise.race(promises); | ||
if (!response) { | ||
throw new Error(`Timed out the network response after ${this._networkTimeoutSeconds} seconds.`); | ||
} | ||
} catch (err) { | ||
if (err instanceof Error) { | ||
error = err; | ||
} | ||
} | ||
if (process.env.NODE_ENV !== "production") { | ||
logger.groupCollapsed(messages.strategyStart(this.constructor.name, request)); | ||
if (response) { | ||
logger.log("Got response from network."); | ||
} else { | ||
logger.log("Unable to get a response from the network."); | ||
} | ||
messages.printFinalResponse(response); | ||
logger.groupEnd(); | ||
} | ||
if (!response) { | ||
throw new SerwistError("no-response", { | ||
url: request.url, | ||
error | ||
}); | ||
} | ||
return response; | ||
} | ||
} | ||
class StaleWhileRevalidate extends Strategy { | ||
constructor(options = {}){ | ||
super(options); | ||
if (!this.plugins.some((p)=>"cacheWillUpdate" in p)) { | ||
this.plugins.unshift(cacheOkAndOpaquePlugin); | ||
} | ||
} | ||
async _handle(request, handler) { | ||
const logs = []; | ||
if (process.env.NODE_ENV !== "production") { | ||
assert.isInstance(request, Request, { | ||
moduleName: "@serwist/strategies", | ||
className: this.constructor.name, | ||
funcName: "handle", | ||
paramName: "request" | ||
}); | ||
} | ||
const fetchAndCachePromise = handler.fetchAndCachePut(request).catch(()=>{}); | ||
void handler.waitUntil(fetchAndCachePromise); | ||
let response = await handler.cacheMatch(request); | ||
let error = undefined; | ||
if (response) { | ||
if (process.env.NODE_ENV !== "production") { | ||
logs.push(`Found a cached response in the '${this.cacheName}' cache. Will update with the network response in the background.`); | ||
} | ||
} else { | ||
if (process.env.NODE_ENV !== "production") { | ||
logs.push(`No response found in the '${this.cacheName}' cache. Will wait for the network response.`); | ||
} | ||
try { | ||
response = await fetchAndCachePromise; | ||
} catch (err) { | ||
if (err instanceof Error) { | ||
error = err; | ||
} | ||
} | ||
} | ||
if (process.env.NODE_ENV !== "production") { | ||
logger.groupCollapsed(messages.strategyStart(this.constructor.name, request)); | ||
for (const log of logs){ | ||
logger.log(log); | ||
} | ||
messages.printFinalResponse(response); | ||
logger.groupEnd(); | ||
} | ||
if (!response) { | ||
throw new SerwistError("no-response", { | ||
url: request.url, | ||
error | ||
}); | ||
} | ||
return response; | ||
} | ||
} | ||
export { CacheFirst, CacheOnly, NetworkFirst, NetworkOnly, StaleWhileRevalidate, Strategy, StrategyHandler }; | ||
export { CacheFirst, CacheOnly, NetworkFirst, NetworkOnly, StaleWhileRevalidate, Strategy, StrategyHandler } from '@serwist/sw/strategies'; |
{ | ||
"name": "@serwist/strategies", | ||
"version": "9.0.0-preview.17", | ||
"version": "9.0.0-preview.18", | ||
"type": "module", | ||
@@ -33,3 +33,3 @@ "description": "A service worker helper library implementing common caching strategies.", | ||
"dependencies": { | ||
"@serwist/core": "9.0.0-preview.17" | ||
"@serwist/sw": "9.0.0-preview.18" | ||
}, | ||
@@ -39,3 +39,3 @@ "devDependencies": { | ||
"typescript": "5.5.0-dev.20240323", | ||
"@serwist/constants": "9.0.0-preview.17" | ||
"@serwist/constants": "9.0.0-preview.18" | ||
}, | ||
@@ -42,0 +42,0 @@ "peerDependencies": { |
@@ -1,34 +0,2 @@ | ||
/* | ||
Copyright 2018 Google LLC | ||
Use of this source code is governed by an MIT-style | ||
license that can be found in the LICENSE file or at | ||
https://opensource.org/licenses/MIT. | ||
*/ | ||
import { CacheFirst } from "./CacheFirst.js"; | ||
import { CacheOnly } from "./CacheOnly.js"; | ||
import type { NetworkFirstOptions } from "./NetworkFirst.js"; | ||
import { NetworkFirst } from "./NetworkFirst.js"; | ||
import type { NetworkOnlyOptions } from "./NetworkOnly.js"; | ||
import { NetworkOnly } from "./NetworkOnly.js"; | ||
import { StaleWhileRevalidate } from "./StaleWhileRevalidate.js"; | ||
import type { StrategyOptions } from "./Strategy.js"; | ||
import { Strategy } from "./Strategy.js"; | ||
import { StrategyHandler } from "./StrategyHandler.js"; | ||
// See https://github.com/GoogleChrome/workbox/issues/2946 | ||
declare global { | ||
interface FetchEvent { | ||
// See https://github.com/GoogleChrome/workbox/issues/2974 | ||
readonly preloadResponse: Promise<any>; | ||
} | ||
} | ||
/** | ||
* There are common caching strategies that most service workers will need | ||
* and use. This module provides simple implementations of these strategies. | ||
*/ | ||
export { CacheFirst, CacheOnly, NetworkFirst, NetworkOnly, StaleWhileRevalidate, Strategy, StrategyHandler }; | ||
export type { NetworkFirstOptions, NetworkOnlyOptions, StrategyOptions }; | ||
export { CacheFirst, CacheOnly, NetworkFirst, NetworkOnly, StaleWhileRevalidate, Strategy, StrategyHandler } from "@serwist/sw/strategies"; | ||
export type { NetworkFirstOptions, NetworkOnlyOptions, StrategyOptions } from "@serwist/sw/strategies"; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Trivial Package
Supply chain riskPackages less than 10 lines of code are easily copied into your own project and may not warrant the additional supply chain risk of an external dependency.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 9 instances in 1 package
0
100
1
3496
7
5
2
+ Added@serwist/sw@9.0.0-preview.18
+ Added@serwist/core@9.0.0-preview.18(transitive)
+ Added@serwist/navigation-preload@9.0.0-preview.18(transitive)
+ Added@serwist/sw@9.0.0-preview.18(transitive)
+ Addedidb@8.0.0(transitive)
- Removed@serwist/core@9.0.0-preview.17
- Removed@serwist/core@9.0.0-preview.17(transitive)