summaryrefslogtreecommitdiff
path: root/ext/cache/01_cache.js
diff options
context:
space:
mode:
authorSatya Rohith <me@satyarohith.com>2022-09-28 17:41:12 +0530
committerGitHub <noreply@github.com>2022-09-28 17:41:12 +0530
commitb312279e58e51520a38e51cca317a09cdadd7cb4 (patch)
treea0c6f432042ba25b569c151bbe59f1e721788d0c /ext/cache/01_cache.js
parent1156f726a92d3d3985e591327c7526cd3e2b0473 (diff)
feat: implement Web Cache API (#15829)
Diffstat (limited to 'ext/cache/01_cache.js')
-rw-r--r--ext/cache/01_cache.js287
1 files changed, 287 insertions, 0 deletions
diff --git a/ext/cache/01_cache.js b/ext/cache/01_cache.js
new file mode 100644
index 000000000..9c624b5d7
--- /dev/null
+++ b/ext/cache/01_cache.js
@@ -0,0 +1,287 @@
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+"use strict";
+
+((window) => {
+ const core = window.__bootstrap.core;
+ const webidl = window.__bootstrap.webidl;
+ const {
+ Symbol,
+ TypeError,
+ ObjectPrototypeIsPrototypeOf,
+ } = window.__bootstrap.primordials;
+ const {
+ Request,
+ toInnerResponse,
+ toInnerRequest,
+ } = window.__bootstrap.fetch;
+ const { URLPrototype } = window.__bootstrap.url;
+ const RequestPrototype = Request.prototype;
+ const { getHeader } = window.__bootstrap.headers;
+ const { readableStreamForRid } = window.__bootstrap.streams;
+
+ class CacheStorage {
+ constructor() {
+ webidl.illegalConstructor();
+ }
+
+ async open(cacheName) {
+ webidl.assertBranded(this, CacheStoragePrototype);
+ const prefix = "Failed to execute 'open' on 'CacheStorage'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ cacheName = webidl.converters["DOMString"](cacheName, {
+ prefix,
+ context: "Argument 1",
+ });
+ const cacheId = await core.opAsync("op_cache_storage_open", cacheName);
+ return new Cache(cacheId);
+ }
+
+ async has(cacheName) {
+ webidl.assertBranded(this, CacheStoragePrototype);
+ const prefix = "Failed to execute 'has' on 'CacheStorage'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ cacheName = webidl.converters["DOMString"](cacheName, {
+ prefix,
+ context: "Argument 1",
+ });
+ return await core.opAsync("op_cache_storage_has", cacheName);
+ }
+
+ async delete(cacheName) {
+ webidl.assertBranded(this, CacheStoragePrototype);
+ const prefix = "Failed to execute 'delete' on 'CacheStorage'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ cacheName = webidl.converters["DOMString"](cacheName, {
+ prefix,
+ context: "Argument 1",
+ });
+ return await core.opAsync("op_cache_storage_delete", cacheName);
+ }
+ }
+
+ const _id = Symbol("id");
+
+ class Cache {
+ /** @type {number} */
+ [_id];
+
+ constructor(cacheId) {
+ this[_id] = cacheId;
+ }
+
+ /** See https://w3c.github.io/ServiceWorker/#dom-cache-put */
+ async put(request, response) {
+ const prefix = "Failed to execute 'put' on 'Cache'";
+ webidl.requiredArguments(arguments.length, 2, { prefix });
+ request = webidl.converters["RequestInfo_DOMString"](request, {
+ prefix,
+ context: "Argument 1",
+ });
+ response = webidl.converters["Response"](response, {
+ prefix,
+ context: "Argument 2",
+ });
+ // 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) {
+ const fieldValues = varyHeader.split(",").map((field) => field.trim());
+ for (const fieldValue of fieldValues) {
+ if (
+ fieldValue === "*"
+ ) {
+ throw new TypeError("Vary header must not contain '*'");
+ }
+ }
+ }
+
+ // Step 8.
+ if (innerResponse.body !== null && innerResponse.body.unusable()) {
+ throw new TypeError("Response body must not already used");
+ }
+
+ // Remove fragment from request URL before put.
+ reqUrl.hash = "";
+
+ // Step 9-11.
+ const rid = await core.opAsync(
+ "op_cache_put",
+ {
+ cacheId: this[_id],
+ requestUrl: reqUrl.toString(),
+ responseHeaders: innerResponse.headerList,
+ requestHeaders: innerRequest.headerList,
+ responseHasBody: innerResponse.body !== null,
+ responseStatus: innerResponse.status,
+ responseStatusText: innerResponse.statusMessage,
+ },
+ );
+ if (innerResponse.body) {
+ const reader = innerResponse.body.stream.getReader();
+ while (true) {
+ const { value, done } = await reader.read();
+ if (done) {
+ await core.shutdown(rid);
+ core.close(rid);
+ break;
+ } else {
+ await core.write(rid, value);
+ }
+ }
+ }
+ // Step 12-19: TODO(@satyarohith): do the insertion in background.
+ }
+
+ /** See https://w3c.github.io/ServiceWorker/#cache-match */
+ async match(request, options) {
+ const prefix = "Failed to execute 'match' on 'Cache'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ request = webidl.converters["RequestInfo_DOMString"](request, {
+ prefix,
+ context: "Argument 1",
+ });
+ const p = await this.#matchAll(request, options);
+ if (p.length > 0) {
+ return p[0];
+ } else {
+ return undefined;
+ }
+ }
+
+ /** See https://w3c.github.io/ServiceWorker/#cache-delete */
+ async delete(request, _options) {
+ const prefix = "Failed to execute 'delete' on 'Cache'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ request = webidl.converters["RequestInfo_DOMString"](request, {
+ prefix,
+ context: "Argument 1",
+ });
+ // Step 1.
+ let r = null;
+ // Step 2.
+ if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) {
+ r = request;
+ if (request.method !== "GET") {
+ return false;
+ }
+ } else if (
+ typeof request === "string" ||
+ ObjectPrototypeIsPrototypeOf(URLPrototype, request)
+ ) {
+ r = new Request(request);
+ }
+ return await core.opAsync("op_cache_delete", {
+ cacheId: this[_id],
+ requestUrl: r.url,
+ });
+ }
+
+ /** 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 [];
+ }
+ } else if (
+ typeof request === "string" ||
+ ObjectPrototypeIsPrototypeOf(URLPrototype, request)
+ ) {
+ r = new Request(request);
+ }
+
+ // 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);
+ const matchResult = await core.opAsync(
+ "op_cache_match",
+ {
+ cacheId: this[_id],
+ requestUrl: url.toString(),
+ requestHeaders: innerRequest.headerList,
+ },
+ );
+ if (matchResult) {
+ const [meta, responseBodyRid] = matchResult;
+ let body = null;
+ if (responseBodyRid !== null) {
+ body = readableStreamForRid(responseBodyRid);
+ }
+ const response = new Response(
+ body,
+ {
+ headers: meta.responseHeaders,
+ status: meta.responseStatus,
+ statusText: meta.responseStatusText,
+ },
+ );
+ responses.push(response);
+ }
+ }
+ // Step 5.4-5.5: don't apply in this context.
+
+ return responses;
+ }
+ }
+
+ webidl.configurePrototype(CacheStorage);
+ webidl.configurePrototype(Cache);
+ const CacheStoragePrototype = CacheStorage.prototype;
+
+ let cacheStorage;
+ window.__bootstrap.caches = {
+ CacheStorage,
+ Cache,
+ cacheStorage() {
+ if (!cacheStorage) {
+ cacheStorage = webidl.createBranded(CacheStorage);
+ }
+ return cacheStorage;
+ },
+ };
+})(this);