@pagepocket/lib 0.8.6 → 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
|
@@ -1,377 +1,7 @@
|
|
|
1
|
+
import { replayDomRewriteScriptPart1 } from "./replay-dom-rewrite/script-part-1.js";
|
|
2
|
+
import { replayDomRewriteScriptPart2 } from "./replay-dom-rewrite/script-part-2.js";
|
|
1
3
|
export const replayDomRewriter = {
|
|
2
4
|
id: "replay-dom-rewriter",
|
|
3
5
|
stage: "replay",
|
|
4
|
-
build: () => `
|
|
5
|
-
// NOTE: When a resource is not found in the snapshot, we keep the original URL.
|
|
6
|
-
// (User choice) We still preserve a limited placeholder behavior for some tags,
|
|
7
|
-
// but only when we have a recorded response body to convert to a data URL.
|
|
8
|
-
const transparentGif = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
|
|
9
|
-
const emptyScript = "data:text/javascript,/*pagepocket-missing*/";
|
|
10
|
-
const emptyStyle = "data:text/css,/*pagepocket-missing*/";
|
|
11
|
-
|
|
12
|
-
let readyResolved = false;
|
|
13
|
-
if (ready && typeof ready.then === "function") {
|
|
14
|
-
ready.then(() => {
|
|
15
|
-
readyResolved = true;
|
|
16
|
-
});
|
|
17
|
-
} else {
|
|
18
|
-
readyResolved = true;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const onReady = (callback) => {
|
|
22
|
-
if (readyResolved) {
|
|
23
|
-
callback();
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
if (ready && typeof ready.then === "function") {
|
|
27
|
-
ready.then(callback);
|
|
28
|
-
} else {
|
|
29
|
-
callback();
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const rewritten = new WeakMap();
|
|
34
|
-
|
|
35
|
-
const shouldRewriteAttr = (element, attr) => {
|
|
36
|
-
try {
|
|
37
|
-
const tag = String((element && element.tagName) || "").toLowerCase();
|
|
38
|
-
const name = String(attr || "").toLowerCase();
|
|
39
|
-
|
|
40
|
-
if (!tag || !name) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Never rewrite navigation.
|
|
45
|
-
if (tag === "a" && name === "href") {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (name === "src") {
|
|
50
|
-
return (
|
|
51
|
-
tag === "img" ||
|
|
52
|
-
tag === "source" ||
|
|
53
|
-
tag === "video" ||
|
|
54
|
-
tag === "audio" ||
|
|
55
|
-
tag === "script" ||
|
|
56
|
-
tag === "iframe" ||
|
|
57
|
-
tag === "object" ||
|
|
58
|
-
tag === "embed" ||
|
|
59
|
-
tag === "track"
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (name === "href") {
|
|
64
|
-
if (tag !== "link") {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const rel = String((element.getAttribute && element.getAttribute("rel")) || "").toLowerCase();
|
|
69
|
-
// Stylesheets + icons are resources.
|
|
70
|
-
if (rel.includes("stylesheet") || rel.includes("icon")) {
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Preload/prefetch/modulepreload are also resource loads.
|
|
75
|
-
// These are common in modern bundlers (Webpack/Rspack/Vite) and can
|
|
76
|
-
// otherwise bypass replay mapping.
|
|
77
|
-
if (rel.includes("preload") || rel.includes("prefetch") || rel.includes("modulepreload")) {
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (name === "srcset") {
|
|
85
|
-
return tag === "img" || tag === "source";
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return false;
|
|
89
|
-
} catch {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// Rewrite srcset values to local files only (avoid data: URLs in srcset).
|
|
95
|
-
const rewriteSrcset = (value) => {
|
|
96
|
-
if (!value) return value;
|
|
97
|
-
|
|
98
|
-
// Substack-style image/fetch URLs include commas inside the URL token
|
|
99
|
-
// (",w_40,h_40,..."). This makes the srcset invalid and browsers will
|
|
100
|
-
// parse it into garbage candidate URLs. Prefer dropping srcset and relying
|
|
101
|
-
// on the rewritten img[src].
|
|
102
|
-
try {
|
|
103
|
-
const trimmed = String(value || "").trim();
|
|
104
|
-
const hasFetchTransform = trimmed.includes("/image/fetch/");
|
|
105
|
-
const hasEncodedUrlTail = trimmed.includes("https%3A%2F%2F");
|
|
106
|
-
const hasCommaTokens =
|
|
107
|
-
trimmed.includes(",w_") ||
|
|
108
|
-
trimmed.includes(", w_") ||
|
|
109
|
-
trimmed.includes(",h_") ||
|
|
110
|
-
trimmed.includes(", h_") ||
|
|
111
|
-
trimmed.includes(",c_") ||
|
|
112
|
-
trimmed.includes(", c_");
|
|
113
|
-
|
|
114
|
-
if (hasFetchTransform && hasEncodedUrlTail && hasCommaTokens) {
|
|
115
|
-
return "";
|
|
116
|
-
}
|
|
117
|
-
} catch {}
|
|
118
|
-
|
|
119
|
-
return value.split(",").map((part) => {
|
|
120
|
-
const trimmed = part.trim();
|
|
121
|
-
if (!trimmed) return trimmed;
|
|
122
|
-
const pieces = trimmed.split(/\\s+/, 2);
|
|
123
|
-
const url = pieces[0];
|
|
124
|
-
const descriptor = pieces[1];
|
|
125
|
-
if (isLocalResource(url)) return trimmed;
|
|
126
|
-
const localPath = findLocalPath(url);
|
|
127
|
-
if (localPath) {
|
|
128
|
-
return descriptor ? localPath + " " + descriptor : localPath;
|
|
129
|
-
}
|
|
130
|
-
return trimmed;
|
|
131
|
-
}).join(",");
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
// Rewrite element attributes to local files or data URLs.
|
|
135
|
-
const rewriteElement = (element) => {
|
|
136
|
-
if (!element || !element.getAttribute) return;
|
|
137
|
-
if (!readyResolved) {
|
|
138
|
-
onReady(() => rewriteElement(element));
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
const prev = rewritten.get(element);
|
|
142
|
-
const currentSrc = element.getAttribute("src");
|
|
143
|
-
const currentHref = element.getAttribute("href");
|
|
144
|
-
const currentSrcset = element.getAttribute("srcset");
|
|
145
|
-
if (
|
|
146
|
-
prev &&
|
|
147
|
-
prev.src === currentSrc &&
|
|
148
|
-
prev.href === currentHref &&
|
|
149
|
-
prev.srcset === currentSrcset
|
|
150
|
-
) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
const tag = (element.tagName || "").toLowerCase();
|
|
154
|
-
if (tag === "img" || tag === "source" || tag === "video" || tag === "audio" || tag === "script" || tag === "iframe" || tag === "object" || tag === "embed" || tag === "track") {
|
|
155
|
-
const src = currentSrc;
|
|
156
|
-
if (src && !src.startsWith("data:") && !src.startsWith("blob:")) {
|
|
157
|
-
const next = rewriteResourceUrl(src, { kind: "attr", tag, attr: "src" });
|
|
158
|
-
if (next && next !== src) {
|
|
159
|
-
element.setAttribute("src", next);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (tag === "link") {
|
|
166
|
-
const href = currentHref;
|
|
167
|
-
const rel = (element.getAttribute("rel") || "").toLowerCase();
|
|
168
|
-
if (href && !href.startsWith("data:") && !href.startsWith("blob:")) {
|
|
169
|
-
if (rel.includes("stylesheet") || rel.includes("icon") || rel.includes("preload") || rel.includes("prefetch") || rel.includes("modulepreload")) {
|
|
170
|
-
// For <link rel=preload as=style>, use a CSS fallback type.
|
|
171
|
-
const as = (element.getAttribute("as") || "").toLowerCase();
|
|
172
|
-
const fallbackType =
|
|
173
|
-
rel.includes("stylesheet") || (rel.includes("preload") && as === "style")
|
|
174
|
-
? "text/css"
|
|
175
|
-
: undefined;
|
|
176
|
-
const next = rewriteResourceUrl(href, {
|
|
177
|
-
kind: "attr",
|
|
178
|
-
tag,
|
|
179
|
-
attr: "href",
|
|
180
|
-
fallbackType
|
|
181
|
-
});
|
|
182
|
-
if (next && next !== href) {
|
|
183
|
-
element.setAttribute("href", next);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const srcset = currentSrcset;
|
|
191
|
-
if (srcset) {
|
|
192
|
-
element.setAttribute("srcset", rewriteSrcset(srcset));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
rewritten.set(element, {
|
|
196
|
-
src: element.getAttribute("src"),
|
|
197
|
-
href: element.getAttribute("href"),
|
|
198
|
-
srcset: element.getAttribute("srcset")
|
|
199
|
-
});
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
// Intercept DOM attribute writes to keep resources local.
|
|
203
|
-
const originalSetAttribute = Element.prototype.setAttribute;
|
|
204
|
-
Element.prototype.setAttribute = function(name, value) {
|
|
205
|
-
const attr = String(name).toLowerCase();
|
|
206
|
-
if (attr === "src" || attr === "href" || attr === "srcset") {
|
|
207
|
-
if (!readyResolved) {
|
|
208
|
-
const pendingValue = String(value);
|
|
209
|
-
onReady(() => originalSetAttribute.call(this, name, pendingValue));
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
if (attr === "srcset") {
|
|
213
|
-
const rewritten = rewriteSrcset(String(value));
|
|
214
|
-
return originalSetAttribute.call(this, name, rewritten);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (!shouldRewriteAttr(this, attr)) {
|
|
218
|
-
return originalSetAttribute.call(this, name, value);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const tag = (this.tagName || "").toLowerCase();
|
|
222
|
-
const rel = (this.getAttribute && this.getAttribute("rel")) || "";
|
|
223
|
-
const relLower = rel.toLowerCase();
|
|
224
|
-
const as = String((this.getAttribute && this.getAttribute("as")) || "").toLowerCase();
|
|
225
|
-
const fallbackType =
|
|
226
|
-
attr === "href" && (relLower.includes("stylesheet") || (relLower.includes("preload") && as === "style"))
|
|
227
|
-
? "text/css"
|
|
228
|
-
: undefined;
|
|
229
|
-
|
|
230
|
-
const next = rewriteResourceUrl(String(value), {
|
|
231
|
-
kind: "setAttribute",
|
|
232
|
-
tag,
|
|
233
|
-
attr,
|
|
234
|
-
fallbackType
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
return originalSetAttribute.call(this, name, next);
|
|
238
|
-
}
|
|
239
|
-
return originalSetAttribute.call(this, name, value);
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
// Patch property setters (e.g. img.src) so direct assignments are rewritten.
|
|
243
|
-
const patchProperty = (proto, prop, handler) => {
|
|
244
|
-
try {
|
|
245
|
-
const desc = Object.getOwnPropertyDescriptor(proto, prop);
|
|
246
|
-
if (!desc || !desc.set) return;
|
|
247
|
-
Object.defineProperty(proto, prop, {
|
|
248
|
-
configurable: true,
|
|
249
|
-
get: desc.get,
|
|
250
|
-
set: function(value) {
|
|
251
|
-
return handler.call(this, value, desc.set);
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
} catch {}
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
patchProperty(HTMLImageElement.prototype, "src", function(value, setter) {
|
|
258
|
-
const rawValue = String(value);
|
|
259
|
-
if (!readyResolved) {
|
|
260
|
-
onReady(() => {
|
|
261
|
-
const next = rewriteResourceUrl(rawValue, { kind: "setter", tag: "img", attr: "src" });
|
|
262
|
-
setter.call(this, next);
|
|
263
|
-
});
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
const next = rewriteResourceUrl(rawValue, { kind: "setter", tag: "img", attr: "src" });
|
|
267
|
-
setter.call(this, next);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
patchProperty(HTMLScriptElement.prototype, "src", function(value, setter) {
|
|
271
|
-
const rawValue = String(value);
|
|
272
|
-
if (!readyResolved) {
|
|
273
|
-
onReady(() => {
|
|
274
|
-
const next = rewriteResourceUrl(rawValue, { kind: "setter", tag: "script", attr: "src" });
|
|
275
|
-
setter.call(this, next);
|
|
276
|
-
});
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
const next = rewriteResourceUrl(rawValue, { kind: "setter", tag: "script", attr: "src" });
|
|
280
|
-
setter.call(this, next);
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
patchProperty(HTMLLinkElement.prototype, "href", function(value, setter) {
|
|
284
|
-
const rawValue = String(value);
|
|
285
|
-
const rel = (this.getAttribute && this.getAttribute("rel")) || "";
|
|
286
|
-
const relLower = rel.toLowerCase();
|
|
287
|
-
const as = String((this.getAttribute && this.getAttribute("as")) || "").toLowerCase();
|
|
288
|
-
if (!readyResolved) {
|
|
289
|
-
onReady(() => {
|
|
290
|
-
if (
|
|
291
|
-
!relLower.includes("stylesheet") &&
|
|
292
|
-
!relLower.includes("icon") &&
|
|
293
|
-
!relLower.includes("preload") &&
|
|
294
|
-
!relLower.includes("prefetch") &&
|
|
295
|
-
!relLower.includes("modulepreload")
|
|
296
|
-
) {
|
|
297
|
-
setter.call(this, rawValue);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
const fallbackType =
|
|
301
|
-
relLower.includes("stylesheet") || (relLower.includes("preload") && as === "style")
|
|
302
|
-
? "text/css"
|
|
303
|
-
: undefined;
|
|
304
|
-
const next = rewriteResourceUrl(rawValue, {
|
|
305
|
-
kind: "setter",
|
|
306
|
-
tag: "link",
|
|
307
|
-
attr: "href",
|
|
308
|
-
fallbackType
|
|
309
|
-
});
|
|
310
|
-
setter.call(this, next);
|
|
311
|
-
});
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
if (
|
|
315
|
-
!relLower.includes("stylesheet") &&
|
|
316
|
-
!relLower.includes("icon") &&
|
|
317
|
-
!relLower.includes("preload") &&
|
|
318
|
-
!relLower.includes("prefetch") &&
|
|
319
|
-
!relLower.includes("modulepreload")
|
|
320
|
-
) {
|
|
321
|
-
setter.call(this, rawValue);
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
const fallbackType =
|
|
325
|
-
relLower.includes("stylesheet") || (relLower.includes("preload") && as === "style")
|
|
326
|
-
? "text/css"
|
|
327
|
-
: undefined;
|
|
328
|
-
const next = rewriteResourceUrl(rawValue, {
|
|
329
|
-
kind: "setter",
|
|
330
|
-
tag: "link",
|
|
331
|
-
attr: "href",
|
|
332
|
-
fallbackType
|
|
333
|
-
});
|
|
334
|
-
setter.call(this, next);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
patchProperty(HTMLImageElement.prototype, "srcset", function(value, setter) {
|
|
338
|
-
const rawValue = String(value);
|
|
339
|
-
if (!readyResolved) {
|
|
340
|
-
onReady(() => {
|
|
341
|
-
const next = rewriteSrcset(rawValue);
|
|
342
|
-
setter.call(this, next);
|
|
343
|
-
});
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const next = rewriteSrcset(rawValue);
|
|
347
|
-
setter.call(this, next);
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
// Observe DOM mutations and rewrite any new elements or attributes.
|
|
351
|
-
const observer = new MutationObserver((mutations) => {
|
|
352
|
-
for (const mutation of mutations) {
|
|
353
|
-
if (mutation.type === "attributes" && mutation.target) {
|
|
354
|
-
rewriteElement(mutation.target);
|
|
355
|
-
}
|
|
356
|
-
if (mutation.type === "childList") {
|
|
357
|
-
mutation.addedNodes.forEach((node) => {
|
|
358
|
-
if (node && node.nodeType === 1) {
|
|
359
|
-
rewriteElement(node);
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
observer.observe(document.documentElement, {
|
|
367
|
-
attributes: true,
|
|
368
|
-
childList: true,
|
|
369
|
-
subtree: true,
|
|
370
|
-
// Include rel/as so we can rewrite <link> even when href is set before
|
|
371
|
-
// rel/as (common in some loaders).
|
|
372
|
-
attributeFilter: ["src", "href", "srcset", "rel", "as"]
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
document.querySelectorAll("img,source,video,audio,script,link,iframe").forEach((el) => rewriteElement(el));
|
|
376
|
-
`
|
|
6
|
+
build: () => `${replayDomRewriteScriptPart1}${replayDomRewriteScriptPart2}`
|
|
377
7
|
};
|
|
@@ -28,20 +28,41 @@ export const replaySvgImageRewriter = {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
const resolveHref = (value) => {
|
|
31
|
-
if (!value)
|
|
32
|
-
|
|
31
|
+
if (!value) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isLocalResource(value)) {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
const localPath = findLocalPath(value);
|
|
34
|
-
if (localPath)
|
|
40
|
+
if (localPath) {
|
|
41
|
+
return localPath;
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
const record = findByUrl(value);
|
|
36
|
-
if (record)
|
|
45
|
+
if (record) {
|
|
46
|
+
return toDataUrl(record);
|
|
47
|
+
}
|
|
48
|
+
|
|
37
49
|
return transparentGif;
|
|
38
50
|
};
|
|
39
51
|
|
|
40
52
|
const rewriteImage = (el) => {
|
|
41
|
-
if (!el || !el.getAttribute)
|
|
53
|
+
if (!el || !el.getAttribute) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
const href = el.getAttribute("href") || (el.getAttributeNS && el.getAttributeNS(xlinkNs, "href"));
|
|
43
|
-
if (!href)
|
|
44
|
-
|
|
58
|
+
if (!href) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (href.startsWith("data:") || href.startsWith("blob:") || isLocalResource(href)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
45
66
|
if (!readyResolved) {
|
|
46
67
|
try { el.setAttributeNS(xlinkNs, "href", transparentGif); } catch {}
|
|
47
68
|
try { el.setAttribute("href", transparentGif); } catch {}
|
|
@@ -49,7 +70,10 @@ export const replaySvgImageRewriter = {
|
|
|
49
70
|
return;
|
|
50
71
|
}
|
|
51
72
|
const next = resolveHref(href);
|
|
52
|
-
if (!next)
|
|
73
|
+
if (!next) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
53
77
|
try { el.setAttributeNS(xlinkNs, "href", next); } catch {}
|
|
54
78
|
try { el.setAttribute("href", next); } catch {}
|
|
55
79
|
};
|
|
@@ -16,12 +16,12 @@ export const replayXhrResponder = {
|
|
|
16
16
|
this.__pagepocketUrl = url;
|
|
17
17
|
return originalOpen.call(this, method, url, ...rest);
|
|
18
18
|
};
|
|
19
|
-
|
|
19
|
+
XMLHttpRequest.prototype.send = function(body) {
|
|
20
20
|
const method = this.__pagepocketMethod || "GET";
|
|
21
21
|
const url = this.__pagepocketUrl || "";
|
|
22
22
|
const xhr = this;
|
|
23
|
-
|
|
24
|
-
let record
|
|
23
|
+
waitForReady().then(() => {
|
|
24
|
+
let record;
|
|
25
25
|
try {
|
|
26
26
|
record = findRecord(method, url, body);
|
|
27
27
|
} catch (err) {
|
package/dist/path-resolver.js
CHANGED
|
@@ -40,12 +40,12 @@ export const createDefaultPathResolver = () => {
|
|
|
40
40
|
resolvedByUrl.set(input.url, entryPath);
|
|
41
41
|
return entryPath;
|
|
42
42
|
}
|
|
43
|
-
let parsed
|
|
43
|
+
let parsed;
|
|
44
44
|
try {
|
|
45
45
|
parsed = new URL(input.url);
|
|
46
46
|
}
|
|
47
47
|
catch {
|
|
48
|
-
parsed =
|
|
48
|
+
parsed = undefined;
|
|
49
49
|
}
|
|
50
50
|
const pathname = normalizePathname(parsed?.pathname || "/");
|
|
51
51
|
const queryHash = `${parsed?.search || ""}${parsed?.hash || ""}`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AnyCheerio, AnyCheerioApi } from "../cheerio/types.js";
|
|
2
2
|
import type { ReplaceAction } from "../types.js";
|
|
3
|
-
export declare const isHtmlElement: ($:
|
|
4
|
-
export declare const applyReplaceAction: ($:
|
|
3
|
+
export declare const isHtmlElement: ($: AnyCheerioApi, $el: AnyCheerio) => boolean;
|
|
4
|
+
export declare const applyReplaceAction: ($: AnyCheerioApi, $el: AnyCheerio, action: ReplaceAction) => void;
|
|
5
5
|
export declare const tagNameMatches: (el: unknown, expected?: string) => boolean;
|
|
@@ -1,26 +1,42 @@
|
|
|
1
|
+
const describeReplaceAction = (value) => {
|
|
2
|
+
if (!value || typeof value !== "object") {
|
|
3
|
+
return "";
|
|
4
|
+
}
|
|
5
|
+
if (!("type" in value)) {
|
|
6
|
+
return "";
|
|
7
|
+
}
|
|
8
|
+
const typeValue = value.type;
|
|
9
|
+
return typeof typeValue === "string" ? typeValue : String(typeValue ?? "");
|
|
10
|
+
};
|
|
1
11
|
const getNodeName = (el) => {
|
|
2
|
-
if (!el || typeof el !== "object")
|
|
3
|
-
return
|
|
12
|
+
if (!el || typeof el !== "object") {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
4
15
|
const name = el.name;
|
|
5
|
-
return typeof name === "string" ? name :
|
|
16
|
+
return typeof name === "string" ? name : undefined;
|
|
6
17
|
};
|
|
7
18
|
const getNodeType = (el) => {
|
|
8
|
-
if (!el || typeof el !== "object")
|
|
9
|
-
return
|
|
19
|
+
if (!el || typeof el !== "object") {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
10
22
|
const type = el.type;
|
|
11
|
-
return typeof type === "string" ? type :
|
|
23
|
+
return typeof type === "string" ? type : undefined;
|
|
12
24
|
};
|
|
13
25
|
export const isHtmlElement = ($, $el) => {
|
|
14
|
-
if (!$el || !$el.length)
|
|
26
|
+
if (!$el || !$el.length) {
|
|
15
27
|
return false;
|
|
16
|
-
|
|
28
|
+
}
|
|
29
|
+
if (!$el.closest("html").length) {
|
|
17
30
|
return false;
|
|
31
|
+
}
|
|
18
32
|
const el = $el.get(0);
|
|
19
|
-
if (!el)
|
|
33
|
+
if (!el) {
|
|
20
34
|
return false;
|
|
35
|
+
}
|
|
21
36
|
const type = getNodeType(el);
|
|
22
|
-
if (type && type !== "tag")
|
|
37
|
+
if (type && type !== "tag") {
|
|
23
38
|
return false;
|
|
39
|
+
}
|
|
24
40
|
return true;
|
|
25
41
|
};
|
|
26
42
|
export const applyReplaceAction = ($, $el, action) => {
|
|
@@ -55,9 +71,11 @@ export const applyReplaceAction = ($, $el, action) => {
|
|
|
55
71
|
const keepChildren = action.keepChildren !== false;
|
|
56
72
|
const next = $(`<${to}></${to}>`);
|
|
57
73
|
if (keepAttributes) {
|
|
58
|
-
const attrs =
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
const attrs = $el.attr();
|
|
75
|
+
if (attrs) {
|
|
76
|
+
Object.entries(attrs).forEach(([key, value]) => {
|
|
77
|
+
next.attr(key, value);
|
|
78
|
+
});
|
|
61
79
|
}
|
|
62
80
|
}
|
|
63
81
|
if (keepChildren) {
|
|
@@ -72,15 +90,17 @@ export const applyReplaceAction = ($, $el, action) => {
|
|
|
72
90
|
}
|
|
73
91
|
default: {
|
|
74
92
|
const exhaustive = action;
|
|
75
|
-
throw new Error(`Unknown replace action: ${
|
|
93
|
+
throw new Error(`Unknown replace action: ${describeReplaceAction(exhaustive)}`);
|
|
76
94
|
}
|
|
77
95
|
}
|
|
78
96
|
};
|
|
79
97
|
export const tagNameMatches = (el, expected) => {
|
|
80
|
-
if (!expected)
|
|
98
|
+
if (!expected) {
|
|
81
99
|
return true;
|
|
100
|
+
}
|
|
82
101
|
const name = getNodeName(el);
|
|
83
|
-
if (!name)
|
|
102
|
+
if (!name) {
|
|
84
103
|
return false;
|
|
104
|
+
}
|
|
85
105
|
return name.toLowerCase() === expected.toLowerCase();
|
|
86
106
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AnyCheerio, AnyCheerioApi } from "../cheerio/types.js";
|
|
2
2
|
import type { MatchQuery } from "../types.js";
|
|
3
3
|
import { isHtmlElement } from "./actions.js";
|
|
4
|
-
export declare const elementMatchesFilter: ($:
|
|
4
|
+
export declare const elementMatchesFilter: ($: AnyCheerioApi, $el: AnyCheerio, filter: MatchQuery | undefined) => boolean;
|
|
5
5
|
export { isHtmlElement };
|
|
@@ -1,46 +1,57 @@
|
|
|
1
1
|
import { isHtmlElement, tagNameMatches } from "./actions.js";
|
|
2
2
|
const idMatches = (_$, $el, expected) => {
|
|
3
|
-
if (!expected)
|
|
3
|
+
if (!expected) {
|
|
4
4
|
return true;
|
|
5
|
+
}
|
|
5
6
|
return ($el.attr("id") || "") === expected;
|
|
6
7
|
};
|
|
7
8
|
const attrsMatch = ($el, attrs) => {
|
|
8
|
-
if (!attrs)
|
|
9
|
+
if (!attrs) {
|
|
9
10
|
return true;
|
|
11
|
+
}
|
|
10
12
|
for (const [name, expected] of Object.entries(attrs)) {
|
|
11
13
|
const actual = $el.attr(name);
|
|
12
14
|
if (expected === true) {
|
|
13
|
-
if (actual === undefined)
|
|
15
|
+
if (actual === undefined) {
|
|
14
16
|
return false;
|
|
17
|
+
}
|
|
15
18
|
continue;
|
|
16
19
|
}
|
|
17
20
|
if (typeof expected === "string") {
|
|
18
|
-
if (actual !== expected)
|
|
21
|
+
if (actual !== expected) {
|
|
19
22
|
return false;
|
|
23
|
+
}
|
|
20
24
|
continue;
|
|
21
25
|
}
|
|
22
26
|
if (expected instanceof RegExp) {
|
|
23
|
-
if (actual === undefined)
|
|
27
|
+
if (actual === undefined) {
|
|
24
28
|
return false;
|
|
25
|
-
|
|
29
|
+
}
|
|
30
|
+
if (!expected.test(actual)) {
|
|
26
31
|
return false;
|
|
32
|
+
}
|
|
27
33
|
continue;
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
return true;
|
|
31
37
|
};
|
|
32
38
|
export const elementMatchesFilter = ($, $el, filter) => {
|
|
33
|
-
if (!filter || typeof filter === "string")
|
|
39
|
+
if (!filter || typeof filter === "string") {
|
|
34
40
|
return true;
|
|
41
|
+
}
|
|
35
42
|
const el = $el.get(0);
|
|
36
|
-
if (!el)
|
|
43
|
+
if (!el) {
|
|
37
44
|
return false;
|
|
38
|
-
|
|
45
|
+
}
|
|
46
|
+
if (!tagNameMatches(el, filter.tagName)) {
|
|
39
47
|
return false;
|
|
40
|
-
|
|
48
|
+
}
|
|
49
|
+
if (!idMatches($, $el, filter.id)) {
|
|
41
50
|
return false;
|
|
42
|
-
|
|
51
|
+
}
|
|
52
|
+
if (!attrsMatch($el, filter.attrs)) {
|
|
43
53
|
return false;
|
|
54
|
+
}
|
|
44
55
|
return true;
|
|
45
56
|
};
|
|
46
57
|
export { isHtmlElement };
|
|
@@ -14,7 +14,7 @@ type NormalizedItem = {
|
|
|
14
14
|
};
|
|
15
15
|
export declare const normalizeMatchToSelector: (match: MatchQuery) => {
|
|
16
16
|
selector: string;
|
|
17
|
-
filter: MatchQuery |
|
|
17
|
+
filter: MatchQuery | undefined;
|
|
18
18
|
};
|
|
19
19
|
export declare const normalizeReplaceElementsConfig: (replaceElements: ReplaceElementsConfig) => NormalizedItem[];
|
|
20
20
|
export type { NormalizedItem };
|
|
@@ -3,7 +3,9 @@ const defaultApply = {
|
|
|
3
3
|
limit: "all",
|
|
4
4
|
onReplaced: "stop"
|
|
5
5
|
};
|
|
6
|
-
const isPlainObject = (value) =>
|
|
6
|
+
const isPlainObject = (value) => {
|
|
7
|
+
return typeof value === "object" && value !== null;
|
|
8
|
+
};
|
|
7
9
|
const normalizeApply = (apply) => ({
|
|
8
10
|
scope: apply?.scope ?? defaultApply.scope,
|
|
9
11
|
limit: apply?.limit ?? defaultApply.limit,
|
|
@@ -11,7 +13,7 @@ const normalizeApply = (apply) => ({
|
|
|
11
13
|
});
|
|
12
14
|
export const normalizeMatchToSelector = (match) => {
|
|
13
15
|
if (typeof match === "string") {
|
|
14
|
-
return { selector: match, filter:
|
|
16
|
+
return { selector: match, filter: undefined };
|
|
15
17
|
}
|
|
16
18
|
return { selector: match.selector ?? "*", filter: match };
|
|
17
19
|
};
|