1 | var __rest = (this && this.__rest) || function (s, e) {
|
2 | var t = {};
|
3 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
4 | t[p] = s[p];
|
5 | if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
6 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
7 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
8 | t[p[i]] = s[p[i]];
|
9 | }
|
10 | return t;
|
11 | };
|
12 | import fetch, { Request } from "node-fetch";
|
13 | import CachePolicy from "http-cache-semantics";
|
14 | import { promises as fsp } from "fs";
|
15 | import { CACHE_DIR } from "./build";
|
16 | import { join } from "path";
|
17 | import { createHash } from "crypto";
|
18 | import cache from "cacache";
|
19 | export async function getCacheEntry(method, fileName) {
|
20 | const cacheKey = `${method}:${fileName}`;
|
21 | const { data = null, meta: { prefetch = null, hash = null } = {}, } = await cache.get(CACHE_DIR, cacheKey).catch(() => ({}));
|
22 | return { data, meta: { prefetch, hash } };
|
23 | }
|
24 | export function createPrefetch(previousKey) {
|
25 | return async function prefetch(info, init = {}) {
|
26 |
|
27 | if (typeof info === "string" && !info.startsWith("http")) {
|
28 | const key = await createFileKey(info);
|
29 | return Buffer.from(JSON.stringify({ type: "fs", key })).toString("base64");
|
30 | }
|
31 |
|
32 | let req = new Request(info, Object.assign(Object.assign({}, init), { method: "HEAD" }));
|
33 | let previousPolicy = null;
|
34 | let previousRes = null;
|
35 | if (previousKey) {
|
36 | const { policy, response } = JSON.parse(Buffer.from(previousKey, "base64").toString("utf8"));
|
37 | previousPolicy = CachePolicy.fromObject(policy);
|
38 | previousRes = response;
|
39 | }
|
40 | if (previousPolicy && previousPolicy.satisfiesWithoutRevalidation(req)) {
|
41 | previousRes.headers = previousPolicy.responseHeaders();
|
42 | const response = !previousPolicy.storable()
|
43 | ? null
|
44 | : serializeRes(previousRes);
|
45 | return Buffer.from(JSON.stringify({
|
46 | type: "network",
|
47 | policy: previousPolicy.toObject(),
|
48 | response,
|
49 | })).toString("base64");
|
50 | }
|
51 | if (previousPolicy) {
|
52 | req = new Request(info, Object.assign(Object.assign({}, init), { method: "HEAD", headers: Object.assign(Object.assign({}, req.headers), previousPolicy.revalidationHeaders(serializeReq(req))) }));
|
53 | const res = await fetch(req);
|
54 | const { policy, modified } = previousPolicy.revalidatedPolicy(req, res);
|
55 | const response = !policy.storable() ? null : serializeRes(res);
|
56 | if (modified) {
|
57 | return Buffer.from(JSON.stringify({
|
58 | type: "network",
|
59 | policy: policy.toObject(),
|
60 | response,
|
61 | })).toString("base64");
|
62 | }
|
63 | return Buffer.from(JSON.stringify({
|
64 | type: "network",
|
65 | policy: previousPolicy.toObject(),
|
66 | response,
|
67 | })).toString("base64");
|
68 | }
|
69 | const res = await fetch(req);
|
70 | const policy = createPolicy(req, res);
|
71 | const response = !policy.storable() ? null : serializeRes(res);
|
72 | return Buffer.from(JSON.stringify({ policy: policy.toObject(), response })).toString("base64");
|
73 | };
|
74 | }
|
75 | export const isKeyValid = (previousKey, key) => {
|
76 | if (!previousKey || !key)
|
77 | return false;
|
78 | const { type } = JSON.parse(Buffer.from(previousKey, "base64").toString("utf8"));
|
79 | if (type === "fs")
|
80 | return previousKey === key;
|
81 | const _a = JSON.parse(Buffer.from(key, "base64").toString("utf8")), { type: _type } = _a, current = __rest(_a, ["type"]);
|
82 | return current.response.status === 200 || current.response.status === 304;
|
83 | };
|
84 | const serializeReq = (req) => ({
|
85 | url: req.url,
|
86 | method: req.method,
|
87 | headers: iterableToObject(req.headers),
|
88 | });
|
89 | const serializeRes = (res) => ({
|
90 | status: res.status,
|
91 | headers: iterableToObject(res.headers),
|
92 | });
|
93 | const iterableToObject = (iter) => {
|
94 | if (typeof iter.keys !== "function")
|
95 | return iter;
|
96 | let obj = {};
|
97 | for (const key of iter.keys()) {
|
98 | obj[key] = iter.get(key);
|
99 | }
|
100 | return obj;
|
101 | };
|
102 | const createPolicy = (req, res) => new CachePolicy(serializeReq(req), serializeRes(res), { shared: false });
|
103 | async function createFileKey(p) {
|
104 | const hash = createHash("sha1");
|
105 | const stat = await fsp.stat(p);
|
106 | if (stat.isDirectory()) {
|
107 | for (const ent of await addDir(p, { mode: "dir" })) {
|
108 | hash.update(ent);
|
109 | }
|
110 | }
|
111 | else {
|
112 | hash.update(await addFile(p, { mode: "file" }));
|
113 | }
|
114 | return hash.digest("base64");
|
115 | }
|
116 | const addDir = async (p, { mode }) => {
|
117 | const ents = await fsp.readdir(p, { withFileTypes: true });
|
118 | const results = await Promise.all(ents.map((ent) => ent.isDirectory()
|
119 | ? addDir(join(p, ent.name), { mode })
|
120 | : addFile(join(p, ent.name), { mode })));
|
121 | return [].concat(...results);
|
122 | };
|
123 | const addFile = async (p, { mode }) => {
|
124 | if (mode === "dir") {
|
125 | return p;
|
126 | }
|
127 | else if (mode === "file") {
|
128 | const content = await fsp.readFile(p);
|
129 | return content;
|
130 | }
|
131 | };
|