@pagepocket/lib 0.8.5 → 0.9.0
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.
- package/dist/cheerio/types.d.ts +5 -0
- package/dist/cheerio/types.js +1 -0
- package/dist/core/completion.js +2 -1
- package/dist/core/content-store.js +2 -2
- package/dist/css-rewrite.d.ts +1 -1
- package/dist/css-rewrite.js +2 -2
- package/dist/hackers/replay-dom-rewrite/script-part-1.d.ts +1 -0
- package/dist/hackers/replay-dom-rewrite/script-part-1.js +202 -0
- package/dist/hackers/replay-dom-rewrite/script-part-2.d.ts +1 -0
- package/dist/hackers/replay-dom-rewrite/script-part-2.js +173 -0
- package/dist/hackers/replay-dom-rewrite.js +3 -373
- package/dist/hackers/replay-svg-image.js +32 -8
- package/dist/hackers/replay-xhr.js +3 -3
- package/dist/path-resolver.js +2 -2
- package/dist/replace-elements/actions.d.ts +3 -3
- package/dist/replace-elements/actions.js +36 -16
- package/dist/replace-elements/match.d.ts +2 -2
- package/dist/replace-elements/match.js +22 -11
- package/dist/replace-elements/normalize.d.ts +1 -1
- package/dist/replace-elements/normalize.js +4 -2
- package/dist/replay/match-api.js +29 -17
- package/dist/replay/templates/replay-script-template.js +16 -332
- package/dist/replay/templates/replay-script-template.part-1.d.ts +5 -0
- package/dist/replay/templates/replay-script-template.part-1.js +101 -0
- package/dist/replay/templates/replay-script-template.part-2.d.ts +3 -0
- package/dist/replay/templates/replay-script-template.part-2.js +222 -0
- package/dist/replay/templates/replay-script-template.part-3.d.ts +3 -0
- package/dist/replay/templates/replay-script-template.part-3.js +9 -0
- package/dist/resource-proxy/pathname-variants.js +8 -5
- package/dist/resource-proxy.js +10 -10
- package/dist/resources.d.ts +3 -2
- package/dist/resources.js +6 -3
- package/dist/rewrite-links/js-imports.d.ts +1 -1
- package/dist/rewrite-links/link-rel.d.ts +2 -2
- package/dist/rewrite-links/meta-refresh.d.ts +1 -1
- package/dist/rewrite-links/meta-refresh.js +6 -3
- package/dist/rewrite-links/srcset.d.ts +1 -1
- package/dist/rewrite-links/srcset.js +4 -2
- package/dist/rewrite-links/url-resolve.d.ts +2 -2
- package/dist/rewrite-links/url-resolve.js +2 -2
- package/dist/rewrite-links.d.ts +1 -1
- package/dist/rewrite-links.js +12 -6
- package/dist/snapshot-builder/build-snapshot.js +2 -3
- package/dist/snapshot-builder/capture-index/index-capture.js +2 -1
- package/dist/snapshot-builder/emit-document.d.ts +1 -1
- package/dist/snapshot-builder/emit-document.js +1 -1
- package/dist/snapshot-builder/grouping.js +2 -2
- package/dist/snapshot-builder/path-map.d.ts +1 -1
- package/dist/snapshot-builder/path-map.js +1 -1
- package/dist/snapshot-builder/resources-path.js +8 -4
- package/dist/snapshot-builder/rewrite-resource.d.ts +2 -2
- package/dist/snapshot-builder/rewrite-resource.js +2 -2
- package/dist/types.d.ts +3 -3
- package/dist/units/internal/async-queue.d.ts +9 -0
- package/dist/units/internal/async-queue.js +57 -0
- package/dist/units/internal/deferred-tracker.d.ts +5 -0
- package/dist/units/internal/deferred-tracker.js +13 -0
- package/dist/units/internal/runtime.d.ts +37 -0
- package/dist/units/internal/runtime.js +113 -0
- package/dist/units/runner.js +3 -184
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +6 -6
- package/package.json +5 -4
- package/README.md +0 -357
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
export const buildReplayInjectedScriptPart2 = (options) => {
|
|
2
|
+
const { matchAPISource } = options;
|
|
3
|
+
return `
|
|
4
|
+
const matchAPI = ${matchAPISource};
|
|
5
|
+
|
|
6
|
+
const primeLookups = (snapshot) => {
|
|
7
|
+
records = snapshot.records || [];
|
|
8
|
+
byKey.clear();
|
|
9
|
+
for (const record of records) {
|
|
10
|
+
if (!record || !record.url || !record.method) continue;
|
|
11
|
+
const keys = makeVariantKeys(record.method, record.url, record.requestBody || record.requestBodyBase64 || "");
|
|
12
|
+
for (const key of keys) {
|
|
13
|
+
if (!byKey.has(key)) {
|
|
14
|
+
byKey.set(key, record);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const ready = (async () => {
|
|
21
|
+
const [apiSnapshot, resourcesPath] = await Promise.all([loadApiSnapshot(), loadResourcesPath()]);
|
|
22
|
+
resourceIndex = __pagepocketResourceProxy.buildIndex(resourcesPath || {});
|
|
23
|
+
|
|
24
|
+
embeddedByPath = new Map();
|
|
25
|
+
try {
|
|
26
|
+
const items = (resourcesPath && resourcesPath.items) || [];
|
|
27
|
+
for (const item of items) {
|
|
28
|
+
if (!item || !item.path || !item.data || item.dataEncoding !== "base64") {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
embeddedByPath.set(item.path, item);
|
|
32
|
+
}
|
|
33
|
+
} catch {}
|
|
34
|
+
|
|
35
|
+
const snapshot = apiSnapshot || {};
|
|
36
|
+
primeLookups(snapshot);
|
|
37
|
+
return snapshot;
|
|
38
|
+
})();
|
|
39
|
+
|
|
40
|
+
const isLocalResource = (value) => {
|
|
41
|
+
if (!value) return false;
|
|
42
|
+
if (value.startsWith("data:") || value.startsWith("blob:")) return true;
|
|
43
|
+
if (value.startsWith("//")) return false;
|
|
44
|
+
if (value.startsWith("/")) return true;
|
|
45
|
+
return false;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const findRecord = (method, url, body) => {
|
|
49
|
+
return matchAPI({ records, byKey, baseUrl, method, url, body });
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const urlLookupCache = new Map();
|
|
53
|
+
const findByUrl = (url) => {
|
|
54
|
+
if (isLocalResource(url)) return null;
|
|
55
|
+
if (urlLookupCache.has(url)) {
|
|
56
|
+
return urlLookupCache.get(url);
|
|
57
|
+
}
|
|
58
|
+
const record = matchAPI({ records, byKey, baseUrl, method: "GET", url, body: "" });
|
|
59
|
+
urlLookupCache.set(url, record || null);
|
|
60
|
+
return record;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const findLocalPath = (url) => {
|
|
64
|
+
try {
|
|
65
|
+
if (!url || isLocalResource(String(url))) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (!resourceIndex) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const resolvedPath = __pagepocketResourceProxy.resolveToLocalPath(String(url), baseUrl, resourceIndex);
|
|
73
|
+
if (!resolvedPath) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const embedded = embeddedByPath && embeddedByPath.get(resolvedPath);
|
|
78
|
+
if (embedded && embedded.dataEncoding === "base64" && embedded.data) {
|
|
79
|
+
if (blobUrlByPath.has(resolvedPath)) {
|
|
80
|
+
return blobUrlByPath.get(resolvedPath);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const binary = atob(embedded.data);
|
|
84
|
+
const bytes = new Uint8Array(binary.length);
|
|
85
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
86
|
+
bytes[i] = binary.charCodeAt(i);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const mime = embedded.mimeType || "application/octet-stream";
|
|
90
|
+
const blob = new Blob([bytes], { type: mime });
|
|
91
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
92
|
+
blobUrlByPath.set(resolvedPath, blobUrl);
|
|
93
|
+
return blobUrl;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return resolvedPath;
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const rewriteResourceUrl = (value, ctx) => {
|
|
103
|
+
try {
|
|
104
|
+
const raw = String(value || "");
|
|
105
|
+
if (!raw) {
|
|
106
|
+
return raw;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isLocalResource(raw)) {
|
|
110
|
+
return raw;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const localPath = findLocalPath(raw);
|
|
114
|
+
if (localPath) {
|
|
115
|
+
return localPath;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const fallbackType = ctx && ctx.fallbackType ? String(ctx.fallbackType) : undefined;
|
|
119
|
+
const record = findByUrl(raw);
|
|
120
|
+
if (record) {
|
|
121
|
+
const dataUrl = toDataUrl(record, fallbackType);
|
|
122
|
+
if (dataUrl) {
|
|
123
|
+
return dataUrl;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return raw;
|
|
128
|
+
} catch {
|
|
129
|
+
try {
|
|
130
|
+
return String(value || "");
|
|
131
|
+
} catch {
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const defineProp = (obj, key, value) => {
|
|
138
|
+
try {
|
|
139
|
+
Object.defineProperty(obj, key, { value, configurable: true });
|
|
140
|
+
} catch {}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const decodeBase64 = (input) => {
|
|
144
|
+
try {
|
|
145
|
+
const binary = atob(input || "");
|
|
146
|
+
const bytes = new Uint8Array(binary.length);
|
|
147
|
+
Array.from(binary).forEach((char, index) => {
|
|
148
|
+
bytes[index] = char.charCodeAt(0);
|
|
149
|
+
});
|
|
150
|
+
return bytes;
|
|
151
|
+
} catch {
|
|
152
|
+
return new Uint8Array();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const bytesToBase64 = (bytes) => {
|
|
157
|
+
const binary = Array.from(bytes, (value) => String.fromCharCode(value)).join("");
|
|
158
|
+
return btoa(binary);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const textToBase64 = (text) => {
|
|
162
|
+
try {
|
|
163
|
+
const bytes = new TextEncoder().encode(text || "");
|
|
164
|
+
return bytesToBase64(bytes);
|
|
165
|
+
} catch {
|
|
166
|
+
return btoa(text || "");
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const getContentType = (record) => {
|
|
171
|
+
const headers = record.responseHeaders || {};
|
|
172
|
+
for (const key in headers) {
|
|
173
|
+
if (key.toLowerCase() === "content-type") {
|
|
174
|
+
return headers[key] || "application/octet-stream";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return "application/octet-stream";
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const dataUrlCache = new Map();
|
|
181
|
+
const toDataUrl = (record, fallbackType) => {
|
|
182
|
+
if (!record) return "";
|
|
183
|
+
const contentType = getContentType(record) || fallbackType || "application/octet-stream";
|
|
184
|
+
const cacheKey = (record.url || "") + "|" + contentType + "|" + (record.responseEncoding || "") + "|" +
|
|
185
|
+
(record.responseBodyBase64 ? "b64:" + record.responseBodyBase64.length : "txt:" + (record.responseBody ? record.responseBody.length : 0));
|
|
186
|
+
if (dataUrlCache.has(cacheKey)) {
|
|
187
|
+
return dataUrlCache.get(cacheKey);
|
|
188
|
+
}
|
|
189
|
+
if (record.responseEncoding === "base64" && record.responseBodyBase64) {
|
|
190
|
+
const dataUrl = "data:" + contentType + ";base64," + record.responseBodyBase64;
|
|
191
|
+
dataUrlCache.set(cacheKey, dataUrl);
|
|
192
|
+
return dataUrl;
|
|
193
|
+
}
|
|
194
|
+
if (record.responseBody) {
|
|
195
|
+
const dataUrl = "data:" + contentType + ";base64," + textToBase64(record.responseBody);
|
|
196
|
+
dataUrlCache.set(cacheKey, dataUrl);
|
|
197
|
+
return dataUrl;
|
|
198
|
+
}
|
|
199
|
+
const dataUrl = "data:" + (fallbackType || "application/octet-stream") + ",";
|
|
200
|
+
dataUrlCache.set(cacheKey, dataUrl);
|
|
201
|
+
return dataUrl;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const responseFromRecord = (record) => {
|
|
205
|
+
const headers = new Headers(record.responseHeaders || {});
|
|
206
|
+
if (record.responseEncoding === "base64" && record.responseBodyBase64) {
|
|
207
|
+
const bytes = decodeBase64(record.responseBodyBase64);
|
|
208
|
+
return new Response(bytes, {
|
|
209
|
+
status: record.status || 200,
|
|
210
|
+
statusText: record.statusText || "OK",
|
|
211
|
+
headers
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
const bodyText = record.responseBody || "";
|
|
215
|
+
return new Response(bodyText, {
|
|
216
|
+
status: record.status || 200,
|
|
217
|
+
statusText: record.statusText || "OK",
|
|
218
|
+
headers
|
|
219
|
+
});
|
|
220
|
+
};
|
|
221
|
+
`;
|
|
222
|
+
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { stripHash, stripTrailingSlash } from "@pagepocket/shared";
|
|
2
2
|
const isLikelyHostname = (value) => {
|
|
3
|
-
if (!value)
|
|
3
|
+
if (!value) {
|
|
4
4
|
return false;
|
|
5
|
-
|
|
5
|
+
}
|
|
6
|
+
if (value === "localhost") {
|
|
6
7
|
return true;
|
|
8
|
+
}
|
|
7
9
|
return value.includes(".");
|
|
8
10
|
};
|
|
9
11
|
const encodeEmbeddedUrlTailIfPresent = (pathname) => {
|
|
10
12
|
const raw = String(pathname || "");
|
|
11
13
|
if (!raw.includes("/http")) {
|
|
12
|
-
return
|
|
14
|
+
return undefined;
|
|
13
15
|
}
|
|
14
16
|
const parts = raw.split("/");
|
|
15
17
|
for (let i = 0; i < parts.length; i += 1) {
|
|
@@ -28,13 +30,14 @@ const encodeEmbeddedUrlTailIfPresent = (pathname) => {
|
|
|
28
30
|
const rebuilt = nextParts.join("/") || "/";
|
|
29
31
|
return rebuilt.startsWith("/") ? rebuilt : "/" + rebuilt;
|
|
30
32
|
}
|
|
31
|
-
return
|
|
33
|
+
return undefined;
|
|
32
34
|
};
|
|
33
35
|
export const makePathnameVariants = (pathname) => {
|
|
34
36
|
const variants = new Set();
|
|
35
37
|
const push = (value) => {
|
|
36
|
-
if (!value)
|
|
38
|
+
if (!value) {
|
|
37
39
|
return;
|
|
40
|
+
}
|
|
38
41
|
variants.add(value);
|
|
39
42
|
};
|
|
40
43
|
push(pathname);
|
package/dist/resource-proxy.js
CHANGED
|
@@ -11,12 +11,12 @@ const canonicalizeHttpUrlForIndex = (url) => {
|
|
|
11
11
|
}
|
|
12
12
|
return url.toString();
|
|
13
13
|
};
|
|
14
|
-
const
|
|
14
|
+
const toUrlOrUndefined = (value) => {
|
|
15
15
|
try {
|
|
16
16
|
return new URL(value);
|
|
17
17
|
}
|
|
18
18
|
catch {
|
|
19
|
-
return
|
|
19
|
+
return undefined;
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
22
|
export const buildResourceProxyIndex = (snapshot) => {
|
|
@@ -29,7 +29,7 @@ export const buildResourceProxyIndex = (snapshot) => {
|
|
|
29
29
|
if (!item || !item.url || !item.path) {
|
|
30
30
|
continue;
|
|
31
31
|
}
|
|
32
|
-
const parsed =
|
|
32
|
+
const parsed = toUrlOrUndefined(item.url);
|
|
33
33
|
if (!parsed) {
|
|
34
34
|
continue;
|
|
35
35
|
}
|
|
@@ -81,14 +81,14 @@ const uniqByPath = (items) => {
|
|
|
81
81
|
};
|
|
82
82
|
const preferSingle = (items, baseUrl, suffixLength) => {
|
|
83
83
|
if (items.length <= 1) {
|
|
84
|
-
return items[0]
|
|
84
|
+
return items[0];
|
|
85
85
|
}
|
|
86
86
|
const baseParsed = (() => {
|
|
87
87
|
try {
|
|
88
88
|
return new URL(baseUrl);
|
|
89
89
|
}
|
|
90
90
|
catch {
|
|
91
|
-
return
|
|
91
|
+
return undefined;
|
|
92
92
|
}
|
|
93
93
|
})();
|
|
94
94
|
if (baseParsed) {
|
|
@@ -103,14 +103,14 @@ const preferSingle = (items, baseUrl, suffixLength) => {
|
|
|
103
103
|
// If still ambiguous, only accept when the match key is strong.
|
|
104
104
|
// We treat very short suffix matches (or basename-only) as too risky.
|
|
105
105
|
if (suffixLength < 2) {
|
|
106
|
-
return
|
|
106
|
+
return undefined;
|
|
107
107
|
}
|
|
108
|
-
return
|
|
108
|
+
return undefined;
|
|
109
109
|
};
|
|
110
110
|
const tryCandidates = (items, baseUrl, suffixLength) => {
|
|
111
111
|
const unique = uniqByPath(items);
|
|
112
112
|
if (unique.length === 0) {
|
|
113
|
-
return
|
|
113
|
+
return undefined;
|
|
114
114
|
}
|
|
115
115
|
if (unique.length === 1) {
|
|
116
116
|
return unique[0];
|
|
@@ -131,12 +131,12 @@ export const resolveToLocalPath = (options) => {
|
|
|
131
131
|
if (!requestUrl) {
|
|
132
132
|
return undefined;
|
|
133
133
|
}
|
|
134
|
-
let abs
|
|
134
|
+
let abs;
|
|
135
135
|
try {
|
|
136
136
|
abs = new URL(requestUrl, baseUrl);
|
|
137
137
|
}
|
|
138
138
|
catch {
|
|
139
|
-
abs =
|
|
139
|
+
abs = undefined;
|
|
140
140
|
}
|
|
141
141
|
if (!abs) {
|
|
142
142
|
return undefined;
|
package/dist/resources.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as cheerio from "cheerio";
|
|
2
|
+
import type { AnyCheerioElement } from "./cheerio/types.js";
|
|
2
3
|
export type ResourceReference = {
|
|
3
4
|
attr: string;
|
|
4
|
-
element:
|
|
5
|
+
element: AnyCheerioElement;
|
|
5
6
|
url: string;
|
|
6
7
|
};
|
|
7
8
|
export type SrcsetReference = {
|
|
8
|
-
element:
|
|
9
|
+
element: AnyCheerioElement;
|
|
9
10
|
value: string;
|
|
10
11
|
};
|
|
11
12
|
export declare const toAbsoluteUrl: (baseUrl: string, resourceUrl: string) => string;
|
package/dist/resources.js
CHANGED
|
@@ -12,7 +12,8 @@ export const extractResourceUrls = (html, baseUrl) => {
|
|
|
12
12
|
const urls = [];
|
|
13
13
|
const collect = (selector, attr) => {
|
|
14
14
|
$(selector).each((_, element) => {
|
|
15
|
-
const
|
|
15
|
+
const $element = $(element);
|
|
16
|
+
const value = $element.attr(attr);
|
|
16
17
|
if (value) {
|
|
17
18
|
urls.push({ attr, element });
|
|
18
19
|
}
|
|
@@ -27,13 +28,15 @@ export const extractResourceUrls = (html, baseUrl) => {
|
|
|
27
28
|
collect("audio[src]", "src");
|
|
28
29
|
const srcsetItems = [];
|
|
29
30
|
$("img[srcset], source[srcset]").each((_, element) => {
|
|
30
|
-
const
|
|
31
|
+
const $element = $(element);
|
|
32
|
+
const value = $element.attr("srcset");
|
|
31
33
|
if (value) {
|
|
32
34
|
srcsetItems.push({ element, value });
|
|
33
35
|
}
|
|
34
36
|
});
|
|
35
37
|
const resourceUrls = urls.map(({ attr, element }) => {
|
|
36
|
-
const
|
|
38
|
+
const $element = $(element);
|
|
39
|
+
const value = $element.attr(attr) || "";
|
|
37
40
|
return {
|
|
38
41
|
attr,
|
|
39
42
|
element,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
export declare const shouldRewriteLinkHref: ($element:
|
|
1
|
+
import type { AnyCheerio } from "../cheerio/types.js";
|
|
2
|
+
export declare const shouldRewriteLinkHref: ($element: AnyCheerio) => boolean;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { resolveUrlValue } from "./url-resolve.js";
|
|
2
2
|
export const rewriteMetaRefresh = (content, baseUrl, resolve) => {
|
|
3
3
|
const parts = content.split(";");
|
|
4
|
-
if (parts.length < 2)
|
|
4
|
+
if (parts.length < 2) {
|
|
5
5
|
return content;
|
|
6
|
+
}
|
|
6
7
|
const urlPartIndex = parts.findIndex((part) => part.trim().toLowerCase().startsWith("url="));
|
|
7
|
-
if (urlPartIndex === -1)
|
|
8
|
+
if (urlPartIndex === -1) {
|
|
8
9
|
return content;
|
|
10
|
+
}
|
|
9
11
|
const urlPart = parts[urlPartIndex];
|
|
10
12
|
let rawUrl = urlPart.split("=").slice(1).join("=").trim();
|
|
11
13
|
if ((rawUrl.startsWith('"') && rawUrl.endsWith('"')) ||
|
|
@@ -13,8 +15,9 @@ export const rewriteMetaRefresh = (content, baseUrl, resolve) => {
|
|
|
13
15
|
rawUrl = rawUrl.slice(1, -1).trim();
|
|
14
16
|
}
|
|
15
17
|
const resolved = resolveUrlValue(rawUrl, baseUrl, resolve);
|
|
16
|
-
if (!resolved)
|
|
18
|
+
if (!resolved) {
|
|
17
19
|
return content;
|
|
20
|
+
}
|
|
18
21
|
const next = `url=${resolved}`;
|
|
19
22
|
const nextParts = parts.slice();
|
|
20
23
|
nextParts[urlPartIndex] = next;
|
|
@@ -16,8 +16,9 @@ const isUnsafeSrcsetValue = (value) => {
|
|
|
16
16
|
};
|
|
17
17
|
const isDescriptorToken = (token) => {
|
|
18
18
|
const trimmed = token.trim();
|
|
19
|
-
if (!trimmed)
|
|
19
|
+
if (!trimmed) {
|
|
20
20
|
return false;
|
|
21
|
+
}
|
|
21
22
|
return /^\d+(\.\d+)?x$/i.test(trimmed) || /^\d+w$/i.test(trimmed);
|
|
22
23
|
};
|
|
23
24
|
const parseSrcset = (input) => {
|
|
@@ -43,8 +44,9 @@ const stringifySrcset = (candidates) => {
|
|
|
43
44
|
return candidates
|
|
44
45
|
.map((c) => {
|
|
45
46
|
const url = c.url.trim();
|
|
46
|
-
if (!c.descriptor)
|
|
47
|
+
if (!c.descriptor) {
|
|
47
48
|
return url;
|
|
49
|
+
}
|
|
48
50
|
return `${url} ${c.descriptor.trim()}`;
|
|
49
51
|
})
|
|
50
52
|
.filter(Boolean)
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
type UrlResolver = (absoluteUrl: string) => string |
|
|
2
|
-
export declare const resolveUrlValue: (value: string, baseUrl: string, resolve: UrlResolver) => string |
|
|
1
|
+
type UrlResolver = (absoluteUrl: string) => string | undefined;
|
|
2
|
+
export declare const resolveUrlValue: (value: string, baseUrl: string, resolve: UrlResolver) => string | undefined;
|
|
3
3
|
export {};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { shouldSkipValue } from "./skip.js";
|
|
2
2
|
export const resolveUrlValue = (value, baseUrl, resolve) => {
|
|
3
3
|
if (shouldSkipValue(value)) {
|
|
4
|
-
return
|
|
4
|
+
return undefined;
|
|
5
5
|
}
|
|
6
6
|
try {
|
|
7
7
|
const absolute = new URL(value, baseUrl).toString();
|
|
8
8
|
return resolve(absolute);
|
|
9
9
|
}
|
|
10
10
|
catch {
|
|
11
|
-
return
|
|
11
|
+
return undefined;
|
|
12
12
|
}
|
|
13
13
|
};
|
package/dist/rewrite-links.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { rewriteJsText } from "./rewrite-links/js-imports.js";
|
|
2
2
|
import type { ReplaceElementsConfig } from "./types.js";
|
|
3
|
-
type UrlResolver = (absoluteUrl: string) => string |
|
|
3
|
+
type UrlResolver = (absoluteUrl: string) => string | undefined;
|
|
4
4
|
export { rewriteJsText };
|
|
5
5
|
export declare const rewriteEntryHtml: (input: {
|
|
6
6
|
html: string;
|
package/dist/rewrite-links.js
CHANGED
|
@@ -28,8 +28,9 @@ export const rewriteEntryHtml = async (input) => {
|
|
|
28
28
|
const rewriteAttr = (selector, attr) => {
|
|
29
29
|
$(selector).each((_, element) => {
|
|
30
30
|
const value = $(element).attr(attr);
|
|
31
|
-
if (!value)
|
|
31
|
+
if (!value) {
|
|
32
32
|
return;
|
|
33
|
+
}
|
|
33
34
|
const resolved = resolveUrlValue(value, baseUrl, resolve);
|
|
34
35
|
if (resolved) {
|
|
35
36
|
$(element).attr(attr, resolved);
|
|
@@ -53,8 +54,9 @@ export const rewriteEntryHtml = async (input) => {
|
|
|
53
54
|
return;
|
|
54
55
|
}
|
|
55
56
|
const value = el.attr("href");
|
|
56
|
-
if (!value)
|
|
57
|
+
if (!value) {
|
|
57
58
|
return;
|
|
59
|
+
}
|
|
58
60
|
const resolved = resolveUrlValue(value, baseUrl, resolve);
|
|
59
61
|
if (resolved) {
|
|
60
62
|
el.attr("href", resolved);
|
|
@@ -67,25 +69,29 @@ export const rewriteEntryHtml = async (input) => {
|
|
|
67
69
|
rewriteDataAttrs("[data-url]", "data-url");
|
|
68
70
|
$("img[srcset], source[srcset]").each((_, element) => {
|
|
69
71
|
const value = $(element).attr("srcset");
|
|
70
|
-
if (!value)
|
|
72
|
+
if (!value) {
|
|
71
73
|
return;
|
|
74
|
+
}
|
|
72
75
|
const rewritten = rewriteSrcsetValue(value, baseUrl, resolve);
|
|
73
76
|
$(element).attr("srcset", rewritten);
|
|
74
77
|
});
|
|
75
78
|
$("link[imagesrcset]").each((_, element) => {
|
|
76
79
|
const value = $(element).attr("imagesrcset");
|
|
77
|
-
if (!value)
|
|
80
|
+
if (!value) {
|
|
78
81
|
return;
|
|
82
|
+
}
|
|
79
83
|
const rewritten = rewriteSrcsetValue(value, baseUrl, resolve);
|
|
80
84
|
$(element).attr("imagesrcset", rewritten);
|
|
81
85
|
});
|
|
82
86
|
$("meta[http-equiv]").each((_, element) => {
|
|
83
87
|
const httpEquiv = ($(element).attr("http-equiv") || "").toLowerCase();
|
|
84
|
-
if (httpEquiv !== "refresh")
|
|
88
|
+
if (httpEquiv !== "refresh") {
|
|
85
89
|
return;
|
|
90
|
+
}
|
|
86
91
|
const content = $(element).attr("content");
|
|
87
|
-
if (!content)
|
|
92
|
+
if (!content) {
|
|
88
93
|
return;
|
|
94
|
+
}
|
|
89
95
|
const rewritten = rewriteMetaRefresh(content, baseUrl, resolve);
|
|
90
96
|
$(element).attr("content", rewritten);
|
|
91
97
|
});
|
|
@@ -8,7 +8,6 @@ import { responseMimeType } from "./http.js";
|
|
|
8
8
|
import { docDirFromUrl, resolveSnapshotPath } from "./path-map.js";
|
|
9
9
|
import { buildResourcesPathSnapshot } from "./resources-path.js";
|
|
10
10
|
import { maybeRewriteScript, maybeRewriteStylesheet } from "./rewrite-resource.js";
|
|
11
|
-
// NOTE: helpers were extracted into snapshot-builder/* modules.
|
|
12
11
|
export const buildSnapshot = async (input) => {
|
|
13
12
|
const warnings = input.warnings;
|
|
14
13
|
const contentStore = input.capture.contentStore;
|
|
@@ -97,7 +96,7 @@ export const buildSnapshot = async (input) => {
|
|
|
97
96
|
});
|
|
98
97
|
}
|
|
99
98
|
const apiSnapshot = buildApiSnapshot(group.url, input.createdAt, group.apiEntries);
|
|
100
|
-
const apiBytes = new TextEncoder().encode(`${JSON.stringify(apiSnapshot,
|
|
99
|
+
const apiBytes = new TextEncoder().encode(`${JSON.stringify(apiSnapshot, undefined, 2)}\n`);
|
|
101
100
|
const apiRef = await contentStore.put({ kind: "buffer", data: apiBytes }, { url: apiPath, mimeType: "application/json", sizeHint: apiBytes.byteLength });
|
|
102
101
|
files.push({
|
|
103
102
|
path: apiPath,
|
|
@@ -109,7 +108,7 @@ export const buildSnapshot = async (input) => {
|
|
|
109
108
|
}
|
|
110
109
|
{
|
|
111
110
|
const resourcesPath = buildResourcesPathSnapshot(input.createdAt, files);
|
|
112
|
-
const bytes = new TextEncoder().encode(`${JSON.stringify(resourcesPath,
|
|
111
|
+
const bytes = new TextEncoder().encode(`${JSON.stringify(resourcesPath, undefined, 2)}\n`);
|
|
113
112
|
files.push({
|
|
114
113
|
path: "/resources_path.json",
|
|
115
114
|
mimeType: "application/json",
|
|
@@ -9,8 +9,9 @@ const buildByRequestId = (events) => {
|
|
|
9
9
|
const byId = new Map();
|
|
10
10
|
const ensure = (requestId) => {
|
|
11
11
|
const existing = byId.get(requestId);
|
|
12
|
-
if (existing)
|
|
12
|
+
if (existing) {
|
|
13
13
|
return existing;
|
|
14
|
+
}
|
|
14
15
|
const created = {};
|
|
15
16
|
byId.set(requestId, created);
|
|
16
17
|
return created;
|
|
@@ -5,7 +5,7 @@ export declare const emitDocumentFile: (input: {
|
|
|
5
5
|
entryUrl: string;
|
|
6
6
|
groupUrl: string;
|
|
7
7
|
apiPath: string;
|
|
8
|
-
resolve: (absoluteUrl: string) => string |
|
|
8
|
+
resolve: (absoluteUrl: string) => string | undefined;
|
|
9
9
|
rewriteEntry: boolean;
|
|
10
10
|
replaceElements: BuildOptions["replaceElements"];
|
|
11
11
|
contentStore: BuildOptions["capture"]["contentStore"];
|
|
@@ -5,7 +5,7 @@ export const emitDocumentFile = async (input) => {
|
|
|
5
5
|
const stream = await input.contentStore.open(input.resource.contentRef);
|
|
6
6
|
const bytes = await streamToUint8Array(stream);
|
|
7
7
|
const decoded = decodeUtf8(bytes);
|
|
8
|
-
if (decoded ===
|
|
8
|
+
if (typeof decoded === "undefined") {
|
|
9
9
|
return {
|
|
10
10
|
file: {
|
|
11
11
|
path: input.path,
|
|
@@ -52,8 +52,8 @@ export const groupResources = (input) => {
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
const primaryGroup = primaryDoc
|
|
55
|
-
?
|
|
56
|
-
:
|
|
55
|
+
? groups.get(primaryDoc.request.frameId ?? primaryDoc.request.requestId)
|
|
56
|
+
: undefined;
|
|
57
57
|
const groupByUrl = new Map();
|
|
58
58
|
for (const group of groups.values()) {
|
|
59
59
|
groupByUrl.set(group.url, group);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const escapePercentForStaticServers: (value: string) => string;
|
|
2
2
|
export declare const docDirFromUrl: (url: string) => string;
|
|
3
|
-
export declare const resolveSnapshotPath: (urlToPath: Map<string, string>, absoluteUrl: string) => string |
|
|
3
|
+
export declare const resolveSnapshotPath: (urlToPath: Map<string, string>, absoluteUrl: string) => string | undefined;
|
|
@@ -24,7 +24,7 @@ export const docDirFromUrl = (url) => {
|
|
|
24
24
|
export const resolveSnapshotPath = (urlToPath, absoluteUrl) => {
|
|
25
25
|
const resolved = urlToPath.get(absoluteUrl);
|
|
26
26
|
if (!resolved) {
|
|
27
|
-
return
|
|
27
|
+
return undefined;
|
|
28
28
|
}
|
|
29
29
|
// Defensive: resolved should be a path, but avoid breaking any unexpected
|
|
30
30
|
// absolute URLs.
|
|
@@ -29,14 +29,18 @@ export const buildResourcesPathSnapshot = (createdAt, files) => {
|
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
items.sort((left, right) => {
|
|
32
|
-
if (left.path < right.path)
|
|
32
|
+
if (left.path < right.path) {
|
|
33
33
|
return -1;
|
|
34
|
-
|
|
34
|
+
}
|
|
35
|
+
if (left.path > right.path) {
|
|
35
36
|
return 1;
|
|
36
|
-
|
|
37
|
+
}
|
|
38
|
+
if (left.url < right.url) {
|
|
37
39
|
return -1;
|
|
38
|
-
|
|
40
|
+
}
|
|
41
|
+
if (left.url > right.url) {
|
|
39
42
|
return 1;
|
|
43
|
+
}
|
|
40
44
|
return 0;
|
|
41
45
|
});
|
|
42
46
|
return {
|