2024-01-01 19:58:21 +00:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
2024-01-26 22:46:46 +00:00
|
|
|
import { primordials } from "ext:core/mod.js";
|
|
|
|
import {
|
2024-01-10 22:37:25 +00:00
|
|
|
op_cache_delete,
|
|
|
|
op_cache_match,
|
|
|
|
op_cache_put,
|
|
|
|
op_cache_storage_delete,
|
|
|
|
op_cache_storage_has,
|
|
|
|
op_cache_storage_open,
|
2024-01-26 22:46:46 +00:00
|
|
|
} from "ext:core/ops";
|
2023-02-07 19:22:46 +00:00
|
|
|
const {
|
2023-05-01 13:30:02 +00:00
|
|
|
ArrayPrototypePush,
|
|
|
|
ObjectPrototypeIsPrototypeOf,
|
|
|
|
StringPrototypeSplit,
|
|
|
|
StringPrototypeTrim,
|
2023-02-07 19:22:46 +00:00
|
|
|
Symbol,
|
2023-11-19 08:13:38 +00:00
|
|
|
SymbolFor,
|
2023-02-07 19:22:46 +00:00
|
|
|
TypeError,
|
|
|
|
} = primordials;
|
2024-01-10 22:37:25 +00:00
|
|
|
|
|
|
|
import * as webidl from "ext:deno_webidl/00_webidl.js";
|
2023-02-07 19:22:46 +00:00
|
|
|
import {
|
|
|
|
Request,
|
|
|
|
RequestPrototype,
|
|
|
|
toInnerRequest,
|
2023-03-08 11:44:54 +00:00
|
|
|
} from "ext:deno_fetch/23_request.js";
|
|
|
|
import { toInnerResponse } from "ext:deno_fetch/23_response.js";
|
|
|
|
import { URLPrototype } from "ext:deno_url/00_url.js";
|
|
|
|
import { getHeader } from "ext:deno_fetch/20_headers.js";
|
2024-01-15 20:14:54 +00:00
|
|
|
import {
|
|
|
|
getReadableStreamResourceBacking,
|
|
|
|
readableStreamForRid,
|
|
|
|
resourceForReadableStream,
|
|
|
|
} from "ext:deno_web/06_streams.js";
|
2024-01-10 22:37:25 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
class CacheStorage {
|
|
|
|
constructor() {
|
|
|
|
webidl.illegalConstructor();
|
|
|
|
}
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
async open(cacheName) {
|
|
|
|
webidl.assertBranded(this, CacheStoragePrototype);
|
|
|
|
const prefix = "Failed to execute 'open' on 'CacheStorage'";
|
2023-04-12 19:58:57 +00:00
|
|
|
webidl.requiredArguments(arguments.length, 1, prefix);
|
2023-05-01 10:47:13 +00:00
|
|
|
cacheName = webidl.converters["DOMString"](cacheName, prefix, "Argument 1");
|
2023-08-09 17:45:35 +00:00
|
|
|
const cacheId = await op_cache_storage_open(cacheName);
|
2023-02-07 19:22:46 +00:00
|
|
|
const cache = webidl.createBranded(Cache);
|
|
|
|
cache[_id] = cacheId;
|
|
|
|
return cache;
|
|
|
|
}
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
async has(cacheName) {
|
|
|
|
webidl.assertBranded(this, CacheStoragePrototype);
|
|
|
|
const prefix = "Failed to execute 'has' on 'CacheStorage'";
|
2023-04-12 19:58:57 +00:00
|
|
|
webidl.requiredArguments(arguments.length, 1, prefix);
|
2023-05-01 10:47:13 +00:00
|
|
|
cacheName = webidl.converters["DOMString"](cacheName, prefix, "Argument 1");
|
2023-08-09 17:45:35 +00:00
|
|
|
return await op_cache_storage_has(cacheName);
|
2023-02-07 19:22:46 +00:00
|
|
|
}
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
async delete(cacheName) {
|
|
|
|
webidl.assertBranded(this, CacheStoragePrototype);
|
|
|
|
const prefix = "Failed to execute 'delete' on 'CacheStorage'";
|
2023-04-12 19:58:57 +00:00
|
|
|
webidl.requiredArguments(arguments.length, 1, prefix);
|
2023-05-01 10:47:13 +00:00
|
|
|
cacheName = webidl.converters["DOMString"](cacheName, prefix, "Argument 1");
|
2023-08-09 17:45:35 +00:00
|
|
|
return await op_cache_storage_delete(cacheName);
|
2022-09-28 12:11:12 +00:00
|
|
|
}
|
2023-11-19 08:13:38 +00:00
|
|
|
|
|
|
|
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
|
|
|
|
return `${this.constructor.name} ${inspect({}, inspectOptions)}`;
|
|
|
|
}
|
2023-02-07 19:22:46 +00:00
|
|
|
}
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
const _matchAll = Symbol("[[matchAll]]");
|
|
|
|
const _id = Symbol("id");
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
class Cache {
|
|
|
|
/** @type {number} */
|
|
|
|
[_id];
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
constructor() {
|
|
|
|
webidl.illegalConstructor();
|
|
|
|
}
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
/** See https://w3c.github.io/ServiceWorker/#dom-cache-put */
|
|
|
|
async put(request, response) {
|
|
|
|
webidl.assertBranded(this, CachePrototype);
|
|
|
|
const prefix = "Failed to execute 'put' on 'Cache'";
|
2023-04-12 19:58:57 +00:00
|
|
|
webidl.requiredArguments(arguments.length, 2, prefix);
|
2023-05-01 10:47:13 +00:00
|
|
|
request = webidl.converters["RequestInfo_DOMString"](
|
|
|
|
request,
|
2023-02-07 19:22:46 +00:00
|
|
|
prefix,
|
2023-05-01 10:47:13 +00:00
|
|
|
"Argument 1",
|
|
|
|
);
|
|
|
|
response = webidl.converters["Response"](response, prefix, "Argument 2");
|
2023-02-07 19:22:46 +00:00
|
|
|
// Step 1.
|
|
|
|
let innerRequest = null;
|
|
|
|
// Step 2.
|
|
|
|
if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) {
|
|
|
|
innerRequest = toInnerRequest(request);
|
|
|
|
} else {
|
|
|
|
// Step 3.
|
|
|
|
innerRequest = toInnerRequest(new Request(request));
|
|
|
|
}
|
|
|
|
// Step 4.
|
|
|
|
const reqUrl = new URL(innerRequest.url());
|
|
|
|
if (reqUrl.protocol !== "http:" && reqUrl.protocol !== "https:") {
|
|
|
|
throw new TypeError(
|
|
|
|
"Request url protocol must be 'http:' or 'https:'",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (innerRequest.method !== "GET") {
|
|
|
|
throw new TypeError("Request method must be GET");
|
|
|
|
}
|
|
|
|
// Step 5.
|
|
|
|
const innerResponse = toInnerResponse(response);
|
|
|
|
// Step 6.
|
|
|
|
if (innerResponse.status === 206) {
|
|
|
|
throw new TypeError("Response status must not be 206");
|
|
|
|
}
|
|
|
|
// Step 7.
|
|
|
|
const varyHeader = getHeader(innerResponse.headerList, "vary");
|
|
|
|
if (varyHeader) {
|
2023-05-01 13:30:02 +00:00
|
|
|
const fieldValues = StringPrototypeSplit(varyHeader, ",");
|
2023-02-07 19:22:46 +00:00
|
|
|
for (let i = 0; i < fieldValues.length; ++i) {
|
|
|
|
const field = fieldValues[i];
|
2023-05-01 13:30:02 +00:00
|
|
|
if (StringPrototypeTrim(field) === "*") {
|
2023-02-07 19:22:46 +00:00
|
|
|
throw new TypeError("Vary header must not contain '*'");
|
2022-09-28 12:11:12 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-07 19:22:46 +00:00
|
|
|
}
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
// Step 8.
|
|
|
|
if (innerResponse.body !== null && innerResponse.body.unusable()) {
|
|
|
|
throw new TypeError("Response body is already used");
|
|
|
|
}
|
2024-01-15 20:14:54 +00:00
|
|
|
|
|
|
|
const stream = innerResponse.body?.stream;
|
|
|
|
let rid = null;
|
|
|
|
if (stream) {
|
|
|
|
const resourceBacking = getReadableStreamResourceBacking(
|
|
|
|
innerResponse.body?.stream,
|
|
|
|
);
|
|
|
|
if (resourceBacking) {
|
|
|
|
rid = resourceBacking.rid;
|
|
|
|
} else {
|
|
|
|
rid = resourceForReadableStream(stream, innerResponse.body?.length);
|
|
|
|
}
|
|
|
|
}
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
// Remove fragment from request URL before put.
|
|
|
|
reqUrl.hash = "";
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
// Step 9-11.
|
2024-01-15 20:14:54 +00:00
|
|
|
// Step 12-19: TODO(@satyarohith): do the insertion in background.
|
|
|
|
await op_cache_put(
|
2023-02-07 19:22:46 +00:00
|
|
|
{
|
|
|
|
cacheId: this[_id],
|
2023-06-05 19:57:01 +00:00
|
|
|
// deno-lint-ignore prefer-primordials
|
2023-02-07 19:22:46 +00:00
|
|
|
requestUrl: reqUrl.toString(),
|
|
|
|
responseHeaders: innerResponse.headerList,
|
|
|
|
requestHeaders: innerRequest.headerList,
|
|
|
|
responseStatus: innerResponse.status,
|
|
|
|
responseStatusText: innerResponse.statusMessage,
|
2024-01-15 20:14:54 +00:00
|
|
|
responseRid: rid,
|
2023-02-07 19:22:46 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
/** See https://w3c.github.io/ServiceWorker/#cache-match */
|
|
|
|
async match(request, options) {
|
|
|
|
webidl.assertBranded(this, CachePrototype);
|
|
|
|
const prefix = "Failed to execute 'match' on 'Cache'";
|
2023-04-12 19:58:57 +00:00
|
|
|
webidl.requiredArguments(arguments.length, 1, prefix);
|
2023-05-01 10:47:13 +00:00
|
|
|
request = webidl.converters["RequestInfo_DOMString"](
|
|
|
|
request,
|
2023-02-07 19:22:46 +00:00
|
|
|
prefix,
|
2023-05-01 10:47:13 +00:00
|
|
|
"Argument 1",
|
|
|
|
);
|
2023-02-07 19:22:46 +00:00
|
|
|
const p = await this[_matchAll](request, options);
|
|
|
|
if (p.length > 0) {
|
|
|
|
return p[0];
|
|
|
|
} else {
|
|
|
|
return undefined;
|
2022-09-28 12:11:12 +00:00
|
|
|
}
|
2023-02-07 19:22:46 +00:00
|
|
|
}
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
/** See https://w3c.github.io/ServiceWorker/#cache-delete */
|
|
|
|
async delete(request, _options) {
|
|
|
|
webidl.assertBranded(this, CachePrototype);
|
|
|
|
const prefix = "Failed to execute 'delete' on 'Cache'";
|
2023-04-12 19:58:57 +00:00
|
|
|
webidl.requiredArguments(arguments.length, 1, prefix);
|
2023-05-01 10:47:13 +00:00
|
|
|
request = webidl.converters["RequestInfo_DOMString"](
|
|
|
|
request,
|
2023-02-07 19:22:46 +00:00
|
|
|
prefix,
|
2023-05-01 10:47:13 +00:00
|
|
|
"Argument 1",
|
|
|
|
);
|
2023-02-07 19:22:46 +00:00
|
|
|
// Step 1.
|
|
|
|
let r = null;
|
|
|
|
// Step 2.
|
|
|
|
if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) {
|
|
|
|
r = request;
|
|
|
|
if (request.method !== "GET") {
|
|
|
|
return false;
|
2022-09-28 12:11:12 +00:00
|
|
|
}
|
2023-02-07 19:22:46 +00:00
|
|
|
} else if (
|
|
|
|
typeof request === "string" ||
|
|
|
|
ObjectPrototypeIsPrototypeOf(URLPrototype, request)
|
|
|
|
) {
|
|
|
|
r = new Request(request);
|
2022-09-28 12:11:12 +00:00
|
|
|
}
|
2023-08-09 17:45:35 +00:00
|
|
|
return await op_cache_delete({
|
2023-02-07 19:22:46 +00:00
|
|
|
cacheId: this[_id],
|
|
|
|
requestUrl: r.url,
|
|
|
|
});
|
|
|
|
}
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
/** See https://w3c.github.io/ServiceWorker/#cache-matchall
|
|
|
|
*
|
|
|
|
* Note: the function is private as we don't want to expose
|
|
|
|
* this API to the public yet.
|
|
|
|
*
|
|
|
|
* The function will return an array of responses.
|
|
|
|
*/
|
|
|
|
async [_matchAll](request, _options) {
|
|
|
|
// Step 1.
|
|
|
|
let r = null;
|
|
|
|
// Step 2.
|
|
|
|
if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) {
|
|
|
|
r = request;
|
|
|
|
if (request.method !== "GET") {
|
|
|
|
return [];
|
2022-09-28 12:11:12 +00:00
|
|
|
}
|
2023-02-07 19:22:46 +00:00
|
|
|
} else if (
|
|
|
|
typeof request === "string" ||
|
|
|
|
ObjectPrototypeIsPrototypeOf(URLPrototype, request)
|
|
|
|
) {
|
|
|
|
r = new Request(request);
|
|
|
|
}
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
// Step 5.
|
|
|
|
const responses = [];
|
|
|
|
// Step 5.2
|
|
|
|
if (r === null) {
|
|
|
|
// Step 5.3
|
|
|
|
// Note: we have to return all responses in the cache when
|
|
|
|
// the request is null.
|
|
|
|
// We deviate from the spec here and return an empty array
|
|
|
|
// as we don't expose matchAll() API.
|
|
|
|
return responses;
|
|
|
|
} else {
|
|
|
|
// Remove the fragment from the request URL.
|
|
|
|
const url = new URL(r.url);
|
|
|
|
url.hash = "";
|
|
|
|
const innerRequest = toInnerRequest(r);
|
2023-08-09 17:45:35 +00:00
|
|
|
const matchResult = await op_cache_match(
|
2023-02-07 19:22:46 +00:00
|
|
|
{
|
|
|
|
cacheId: this[_id],
|
2023-06-05 19:57:01 +00:00
|
|
|
// deno-lint-ignore prefer-primordials
|
2023-02-07 19:22:46 +00:00
|
|
|
requestUrl: url.toString(),
|
|
|
|
requestHeaders: innerRequest.headerList,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
if (matchResult) {
|
|
|
|
const { 0: meta, 1: responseBodyRid } = matchResult;
|
|
|
|
let body = null;
|
|
|
|
if (responseBodyRid !== null) {
|
|
|
|
body = readableStreamForRid(responseBodyRid);
|
|
|
|
}
|
|
|
|
const response = new Response(
|
|
|
|
body,
|
2022-09-28 12:11:12 +00:00
|
|
|
{
|
2023-02-07 19:22:46 +00:00
|
|
|
headers: meta.responseHeaders,
|
|
|
|
status: meta.responseStatus,
|
|
|
|
statusText: meta.responseStatusText,
|
2022-09-28 12:11:12 +00:00
|
|
|
},
|
|
|
|
);
|
2023-05-01 13:30:02 +00:00
|
|
|
ArrayPrototypePush(responses, response);
|
2022-09-28 12:11:12 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-07 19:22:46 +00:00
|
|
|
// Step 5.4-5.5: don't apply in this context.
|
|
|
|
|
|
|
|
return responses;
|
2022-09-28 12:11:12 +00:00
|
|
|
}
|
2023-11-19 08:13:38 +00:00
|
|
|
|
|
|
|
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
|
|
|
|
return `${this.constructor.name} ${inspect({}, inspectOptions)}`;
|
|
|
|
}
|
2023-02-07 19:22:46 +00:00
|
|
|
}
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-10-10 03:01:01 +00:00
|
|
|
webidl.configureInterface(CacheStorage);
|
|
|
|
webidl.configureInterface(Cache);
|
2023-02-07 19:22:46 +00:00
|
|
|
const CacheStoragePrototype = CacheStorage.prototype;
|
|
|
|
const CachePrototype = Cache.prototype;
|
2022-09-28 12:11:12 +00:00
|
|
|
|
2023-02-07 19:22:46 +00:00
|
|
|
let cacheStorageStorage;
|
|
|
|
function cacheStorage() {
|
|
|
|
if (!cacheStorageStorage) {
|
|
|
|
cacheStorageStorage = webidl.createBranded(CacheStorage);
|
|
|
|
}
|
|
|
|
return cacheStorageStorage;
|
|
|
|
}
|
|
|
|
|
|
|
|
export { Cache, CacheStorage, cacheStorage };
|