@pagepocket/lib 0.4.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/content-type.d.ts +2 -0
- package/dist/content-type.js +36 -0
- package/dist/css-rewrite.d.ts +9 -0
- package/dist/css-rewrite.js +76 -0
- package/dist/download-resources.d.ts +25 -0
- package/dist/download-resources.js +163 -0
- package/dist/hack-html.d.ts +9 -0
- package/dist/hack-html.js +32 -0
- package/dist/hackers/index.d.ts +3 -0
- package/dist/hackers/index.js +22 -0
- package/dist/hackers/preload-fetch.d.ts +2 -0
- package/dist/hackers/preload-fetch.js +56 -0
- package/dist/hackers/preload-xhr.d.ts +2 -0
- package/dist/hackers/preload-xhr.js +59 -0
- package/dist/hackers/replay-beacon.d.ts +2 -0
- package/dist/hackers/replay-beacon.js +21 -0
- package/dist/hackers/replay-dom-rewrite.d.ts +2 -0
- package/dist/hackers/replay-dom-rewrite.js +295 -0
- package/dist/hackers/replay-eventsource.d.ts +2 -0
- package/dist/hackers/replay-eventsource.js +25 -0
- package/dist/hackers/replay-fetch.d.ts +2 -0
- package/dist/hackers/replay-fetch.js +33 -0
- package/dist/hackers/replay-svg-image.d.ts +2 -0
- package/dist/hackers/replay-svg-image.js +89 -0
- package/dist/hackers/replay-websocket.d.ts +2 -0
- package/dist/hackers/replay-websocket.js +26 -0
- package/dist/hackers/replay-xhr.d.ts +2 -0
- package/dist/hackers/replay-xhr.js +91 -0
- package/dist/hackers/types.d.ts +10 -0
- package/dist/hackers/types.js +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +13 -0
- package/dist/network-records.d.ts +4 -0
- package/dist/network-records.js +83 -0
- package/dist/pagepocket.d.ts +18 -0
- package/dist/pagepocket.js +73 -0
- package/dist/preload.d.ts +1 -0
- package/dist/preload.js +60 -0
- package/dist/replay-script.d.ts +1 -0
- package/dist/replay-script.js +347 -0
- package/dist/resources.d.ts +16 -0
- package/dist/resources.js +82 -0
- package/dist/rewrite-links.d.ts +15 -0
- package/dist/rewrite-links.js +263 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.js +2 -0
- package/package.json +29 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isTextResponse = exports.extensionFromContentType = void 0;
|
|
4
|
+
const extensionFromContentType = (contentType) => {
|
|
5
|
+
if (!contentType) {
|
|
6
|
+
return "";
|
|
7
|
+
}
|
|
8
|
+
if (contentType.includes("text/css"))
|
|
9
|
+
return ".css";
|
|
10
|
+
if (contentType.includes("javascript"))
|
|
11
|
+
return ".js";
|
|
12
|
+
if (contentType.includes("image/png"))
|
|
13
|
+
return ".png";
|
|
14
|
+
if (contentType.includes("image/jpeg"))
|
|
15
|
+
return ".jpg";
|
|
16
|
+
if (contentType.includes("image/gif"))
|
|
17
|
+
return ".gif";
|
|
18
|
+
if (contentType.includes("image/svg"))
|
|
19
|
+
return ".svg";
|
|
20
|
+
if (contentType.includes("font/woff2"))
|
|
21
|
+
return ".woff2";
|
|
22
|
+
if (contentType.includes("font/woff"))
|
|
23
|
+
return ".woff";
|
|
24
|
+
return "";
|
|
25
|
+
};
|
|
26
|
+
exports.extensionFromContentType = extensionFromContentType;
|
|
27
|
+
const isTextResponse = (contentType) => {
|
|
28
|
+
const lowered = contentType.toLowerCase();
|
|
29
|
+
return (lowered.startsWith("text/") ||
|
|
30
|
+
lowered.includes("json") ||
|
|
31
|
+
lowered.includes("javascript") ||
|
|
32
|
+
lowered.includes("xml") ||
|
|
33
|
+
lowered.includes("svg") ||
|
|
34
|
+
lowered.includes("html"));
|
|
35
|
+
};
|
|
36
|
+
exports.isTextResponse = isTextResponse;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type CssUrlResolver = (absoluteUrl: string) => Promise<string | null>;
|
|
2
|
+
type RewriteCssInput = {
|
|
3
|
+
filename: string;
|
|
4
|
+
extension: string;
|
|
5
|
+
cssUrl: string;
|
|
6
|
+
resolveUrl: CssUrlResolver;
|
|
7
|
+
};
|
|
8
|
+
export declare const rewriteCssUrls: (input: RewriteCssInput) => Promise<boolean>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.rewriteCssUrls = void 0;
|
|
37
|
+
const URL_PATTERN = /url\(\s*(['"]?)([^'")]+)\1\s*\)/g;
|
|
38
|
+
const rewriteCssUrls = async (input) => {
|
|
39
|
+
const { readText, write } = await Promise.resolve().then(() => __importStar(require("uni-fs")));
|
|
40
|
+
const original = await readText(input.filename, input.extension);
|
|
41
|
+
let updated = "";
|
|
42
|
+
let lastIndex = 0;
|
|
43
|
+
let changed = false;
|
|
44
|
+
for (const match of original.matchAll(URL_PATTERN)) {
|
|
45
|
+
const index = match.index ?? 0;
|
|
46
|
+
updated += original.slice(lastIndex, index);
|
|
47
|
+
const quote = match[1] || "";
|
|
48
|
+
const rawUrl = String(match[2] || "").trim();
|
|
49
|
+
let replacement = match[0];
|
|
50
|
+
if (rawUrl && !rawUrl.startsWith("data:") && !rawUrl.startsWith("blob:")) {
|
|
51
|
+
const absolute = (() => {
|
|
52
|
+
try {
|
|
53
|
+
return new URL(rawUrl, input.cssUrl).toString();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
59
|
+
if (absolute) {
|
|
60
|
+
const resolved = await input.resolveUrl(absolute);
|
|
61
|
+
if (resolved) {
|
|
62
|
+
replacement = `url(${quote}${resolved}${quote})`;
|
|
63
|
+
changed = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
updated += replacement;
|
|
68
|
+
lastIndex = index + match[0].length;
|
|
69
|
+
}
|
|
70
|
+
updated += original.slice(lastIndex);
|
|
71
|
+
if (changed) {
|
|
72
|
+
await write(input.filename, input.extension, updated);
|
|
73
|
+
}
|
|
74
|
+
return changed;
|
|
75
|
+
};
|
|
76
|
+
exports.rewriteCssUrls = rewriteCssUrls;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ResourceReference, type SrcsetReference } from "./resources";
|
|
2
|
+
import type { SnapshotData } from "./types";
|
|
3
|
+
export type DownloadedResource = {
|
|
4
|
+
url: string;
|
|
5
|
+
filename: string;
|
|
6
|
+
extension: string;
|
|
7
|
+
localPath: string;
|
|
8
|
+
contentType?: string | null;
|
|
9
|
+
size?: number;
|
|
10
|
+
};
|
|
11
|
+
type DownloadResourcesInput = {
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
assetsDirName: string;
|
|
14
|
+
resourceUrls: ResourceReference[];
|
|
15
|
+
srcsetItems: SrcsetReference[];
|
|
16
|
+
referer?: string;
|
|
17
|
+
};
|
|
18
|
+
type DownloadResourcesResult = {
|
|
19
|
+
resourceMap: Map<string, DownloadedResource>;
|
|
20
|
+
resourceMeta: SnapshotData["resources"];
|
|
21
|
+
downloadedCount: number;
|
|
22
|
+
failedCount: number;
|
|
23
|
+
};
|
|
24
|
+
export declare const downloadResources: (input: DownloadResourcesInput) => Promise<DownloadResourcesResult>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.downloadResources = void 0;
|
|
37
|
+
const content_type_1 = require("./content-type");
|
|
38
|
+
const resources_1 = require("./resources");
|
|
39
|
+
const FNV_OFFSET = 0x811c9dc5;
|
|
40
|
+
const FNV_PRIME = 0x01000193;
|
|
41
|
+
const shouldSkipUrl = (value) => {
|
|
42
|
+
const trimmed = value.trim();
|
|
43
|
+
return (!trimmed ||
|
|
44
|
+
trimmed.startsWith("data:") ||
|
|
45
|
+
trimmed.startsWith("blob:") ||
|
|
46
|
+
trimmed.startsWith("mailto:") ||
|
|
47
|
+
trimmed.startsWith("tel:") ||
|
|
48
|
+
trimmed.startsWith("javascript:") ||
|
|
49
|
+
trimmed.startsWith("#"));
|
|
50
|
+
};
|
|
51
|
+
const hashUrl = (value) => {
|
|
52
|
+
let hash = FNV_OFFSET;
|
|
53
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
54
|
+
hash ^= value.charCodeAt(i);
|
|
55
|
+
hash = (hash * FNV_PRIME) >>> 0;
|
|
56
|
+
}
|
|
57
|
+
return hash.toString(16).padStart(8, "0");
|
|
58
|
+
};
|
|
59
|
+
const extensionFromUrl = (url) => {
|
|
60
|
+
try {
|
|
61
|
+
const pathname = new URL(url).pathname;
|
|
62
|
+
const lastDot = pathname.lastIndexOf(".");
|
|
63
|
+
if (lastDot === -1) {
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
return pathname.slice(lastDot);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const normalizeExtension = (extension) => {
|
|
73
|
+
if (!extension) {
|
|
74
|
+
return "";
|
|
75
|
+
}
|
|
76
|
+
return extension.startsWith(".") ? extension.slice(1) : extension;
|
|
77
|
+
};
|
|
78
|
+
const buildLocalPath = (assetsDirName, filename, extension) => {
|
|
79
|
+
const normalizedExtension = normalizeExtension(extension);
|
|
80
|
+
if (!normalizedExtension) {
|
|
81
|
+
return `${assetsDirName}/${filename}`;
|
|
82
|
+
}
|
|
83
|
+
return `${assetsDirName}/${filename}.${normalizedExtension}`;
|
|
84
|
+
};
|
|
85
|
+
const collectSrcsetUrls = (items, baseUrl) => {
|
|
86
|
+
const urls = [];
|
|
87
|
+
for (const item of items) {
|
|
88
|
+
const parts = item.value.split(",").map((part) => part.trim());
|
|
89
|
+
for (const part of parts) {
|
|
90
|
+
const [rawUrl] = part.split(/\s+/, 2);
|
|
91
|
+
if (!rawUrl) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const absolute = (0, resources_1.toAbsoluteUrl)(baseUrl, rawUrl);
|
|
95
|
+
urls.push(absolute);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return urls;
|
|
99
|
+
};
|
|
100
|
+
const downloadResources = async (input) => {
|
|
101
|
+
const { write, exists } = await Promise.resolve().then(() => __importStar(require("uni-fs")));
|
|
102
|
+
const resourceMap = new Map();
|
|
103
|
+
const resourceMeta = [];
|
|
104
|
+
let downloadedCount = 0;
|
|
105
|
+
let failedCount = 0;
|
|
106
|
+
const srcsetUrls = collectSrcsetUrls(input.srcsetItems, input.baseUrl);
|
|
107
|
+
const candidateUrls = [
|
|
108
|
+
...input.resourceUrls.map((resource) => resource.url),
|
|
109
|
+
...srcsetUrls
|
|
110
|
+
];
|
|
111
|
+
for (const candidate of candidateUrls) {
|
|
112
|
+
if (shouldSkipUrl(candidate)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const url = (0, resources_1.toAbsoluteUrl)(input.baseUrl, candidate);
|
|
116
|
+
if (!url || resourceMap.has(url)) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const headers = {};
|
|
121
|
+
if (input.referer) {
|
|
122
|
+
headers.referer = input.referer;
|
|
123
|
+
}
|
|
124
|
+
const response = await fetch(url, { redirect: "follow", headers });
|
|
125
|
+
const contentType = response.headers.get("content-type");
|
|
126
|
+
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
127
|
+
const extFromUrl = extensionFromUrl(url);
|
|
128
|
+
const extFromType = (0, content_type_1.extensionFromContentType)(contentType);
|
|
129
|
+
const extension = normalizeExtension(extFromUrl || extFromType);
|
|
130
|
+
const filename = hashUrl(url);
|
|
131
|
+
const localPath = buildLocalPath(input.assetsDirName, filename, extension);
|
|
132
|
+
if (!(await exists(input.assetsDirName + "/" + filename, extension))) {
|
|
133
|
+
await write(input.assetsDirName + "/" + filename, extension, buffer);
|
|
134
|
+
}
|
|
135
|
+
const entry = {
|
|
136
|
+
url,
|
|
137
|
+
filename,
|
|
138
|
+
extension,
|
|
139
|
+
localPath,
|
|
140
|
+
contentType,
|
|
141
|
+
size: buffer.byteLength
|
|
142
|
+
};
|
|
143
|
+
resourceMap.set(url, entry);
|
|
144
|
+
resourceMeta.push({
|
|
145
|
+
url,
|
|
146
|
+
localPath,
|
|
147
|
+
contentType,
|
|
148
|
+
size: buffer.byteLength
|
|
149
|
+
});
|
|
150
|
+
downloadedCount += 1;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
failedCount += 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
resourceMap,
|
|
158
|
+
resourceMeta,
|
|
159
|
+
downloadedCount,
|
|
160
|
+
failedCount
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
exports.downloadResources = downloadResources;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hackHtml = void 0;
|
|
4
|
+
const preload_1 = require("./preload");
|
|
5
|
+
const replay_script_1 = require("./replay-script");
|
|
6
|
+
const hackHtml = (input) => {
|
|
7
|
+
const replayScript = (0, replay_script_1.buildReplayScript)(input.requestsPath, input.baseUrl);
|
|
8
|
+
const preloadScript = `<script>${(0, preload_1.buildPreloadScript)()}</script>`;
|
|
9
|
+
const head = input.$("head");
|
|
10
|
+
const root = input.$.root();
|
|
11
|
+
if (head.length) {
|
|
12
|
+
head.prepend(replayScript);
|
|
13
|
+
head.prepend(preloadScript);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
root.prepend(replayScript);
|
|
17
|
+
root.prepend(preloadScript);
|
|
18
|
+
}
|
|
19
|
+
if (input.faviconDataUrl) {
|
|
20
|
+
const existingIcon = input.$('link[rel="icon"]');
|
|
21
|
+
if (existingIcon.length) {
|
|
22
|
+
existingIcon.attr("href", input.faviconDataUrl);
|
|
23
|
+
}
|
|
24
|
+
else if (head.length) {
|
|
25
|
+
head.append(`<link rel="icon" href="${input.faviconDataUrl}" />`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
root.append(`<link rel="icon" href="${input.faviconDataUrl}" />`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
exports.hackHtml = hackHtml;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.replayHackers = exports.preloadHackers = void 0;
|
|
4
|
+
const preload_fetch_1 = require("./preload-fetch");
|
|
5
|
+
const preload_xhr_1 = require("./preload-xhr");
|
|
6
|
+
const replay_beacon_1 = require("./replay-beacon");
|
|
7
|
+
const replay_dom_rewrite_1 = require("./replay-dom-rewrite");
|
|
8
|
+
const replay_eventsource_1 = require("./replay-eventsource");
|
|
9
|
+
const replay_fetch_1 = require("./replay-fetch");
|
|
10
|
+
const replay_svg_image_1 = require("./replay-svg-image");
|
|
11
|
+
const replay_websocket_1 = require("./replay-websocket");
|
|
12
|
+
const replay_xhr_1 = require("./replay-xhr");
|
|
13
|
+
exports.preloadHackers = [preload_fetch_1.preloadFetchRecorder, preload_xhr_1.preloadXhrRecorder];
|
|
14
|
+
exports.replayHackers = [
|
|
15
|
+
replay_fetch_1.replayFetchResponder,
|
|
16
|
+
replay_xhr_1.replayXhrResponder,
|
|
17
|
+
replay_dom_rewrite_1.replayDomRewriter,
|
|
18
|
+
replay_svg_image_1.replaySvgImageRewriter,
|
|
19
|
+
replay_beacon_1.replayBeaconStub,
|
|
20
|
+
replay_websocket_1.replayWebSocketStub,
|
|
21
|
+
replay_eventsource_1.replayEventSourceStub
|
|
22
|
+
];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preloadFetchRecorder = void 0;
|
|
4
|
+
exports.preloadFetchRecorder = {
|
|
5
|
+
id: "preload-fetch-recorder",
|
|
6
|
+
stage: "preload",
|
|
7
|
+
build: () => `
|
|
8
|
+
// Record fetch calls and responses for replay.
|
|
9
|
+
const originalFetch = window.fetch.bind(window);
|
|
10
|
+
const recordFetch = async (input, init) => {
|
|
11
|
+
trackPendingStart();
|
|
12
|
+
const url = typeof input === "string" ? toAbsoluteUrl(input) : toAbsoluteUrl(input.url);
|
|
13
|
+
const method = init && init.method ? init.method : (typeof input === "string" ? "GET" : input.method || "GET");
|
|
14
|
+
const requestBody = normalizeBody(init && init.body ? init.body : (typeof input === "string" ? undefined : input.body));
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const response = await originalFetch(input, init);
|
|
18
|
+
const clone = response.clone();
|
|
19
|
+
const responseBody = await clone.text();
|
|
20
|
+
const headers = {};
|
|
21
|
+
response.headers.forEach((value, key) => {
|
|
22
|
+
headers[key] = value;
|
|
23
|
+
});
|
|
24
|
+
records.push({
|
|
25
|
+
kind: "fetch",
|
|
26
|
+
url,
|
|
27
|
+
method,
|
|
28
|
+
requestBody,
|
|
29
|
+
status: response.status,
|
|
30
|
+
statusText: response.statusText,
|
|
31
|
+
responseHeaders: headers,
|
|
32
|
+
responseBody,
|
|
33
|
+
timestamp: Date.now()
|
|
34
|
+
});
|
|
35
|
+
return response;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
records.push({
|
|
38
|
+
kind: "fetch",
|
|
39
|
+
url,
|
|
40
|
+
method,
|
|
41
|
+
requestBody,
|
|
42
|
+
error: String(error),
|
|
43
|
+
timestamp: Date.now()
|
|
44
|
+
});
|
|
45
|
+
throw error;
|
|
46
|
+
} finally {
|
|
47
|
+
trackPendingEnd();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
window.fetch = (input, init) => {
|
|
52
|
+
return recordFetch(input, init);
|
|
53
|
+
};
|
|
54
|
+
window.fetch.__pagepocketOriginal = originalFetch;
|
|
55
|
+
`
|
|
56
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preloadXhrRecorder = void 0;
|
|
4
|
+
exports.preloadXhrRecorder = {
|
|
5
|
+
id: "preload-xhr-recorder",
|
|
6
|
+
stage: "preload",
|
|
7
|
+
build: () => `
|
|
8
|
+
// Record XHR calls and responses for replay.
|
|
9
|
+
const originalOpen = XMLHttpRequest.prototype.open;
|
|
10
|
+
const originalSend = XMLHttpRequest.prototype.send;
|
|
11
|
+
|
|
12
|
+
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
|
|
13
|
+
this.__pagepocketMethod = method;
|
|
14
|
+
this.__pagepocketUrl = toAbsoluteUrl(url);
|
|
15
|
+
return originalOpen.call(this, method, url, ...rest);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
XMLHttpRequest.prototype.send = function(body) {
|
|
19
|
+
const xhr = this;
|
|
20
|
+
const requestBody = normalizeBody(body);
|
|
21
|
+
|
|
22
|
+
const onLoadEnd = () => {
|
|
23
|
+
const responseHeadersRaw = xhr.getAllResponseHeaders();
|
|
24
|
+
const headers = {};
|
|
25
|
+
responseHeadersRaw
|
|
26
|
+
.trim()
|
|
27
|
+
.split(/\\r?\\n/)
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.forEach((line) => {
|
|
30
|
+
const index = line.indexOf(":");
|
|
31
|
+
if (index > -1) {
|
|
32
|
+
const key = line.slice(0, index).trim().toLowerCase();
|
|
33
|
+
const value = line.slice(index + 1).trim();
|
|
34
|
+
headers[key] = value;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
records.push({
|
|
39
|
+
kind: "xhr",
|
|
40
|
+
url: xhr.__pagepocketUrl || "",
|
|
41
|
+
method: xhr.__pagepocketMethod || "GET",
|
|
42
|
+
requestBody,
|
|
43
|
+
status: xhr.status,
|
|
44
|
+
statusText: xhr.statusText,
|
|
45
|
+
responseHeaders: headers,
|
|
46
|
+
responseBody: xhr.responseText || "",
|
|
47
|
+
timestamp: Date.now()
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
xhr.removeEventListener("loadend", onLoadEnd);
|
|
51
|
+
trackPendingEnd();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
trackPendingStart();
|
|
55
|
+
xhr.addEventListener("loadend", onLoadEnd);
|
|
56
|
+
return originalSend.call(xhr, body);
|
|
57
|
+
};
|
|
58
|
+
`
|
|
59
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.replayBeaconStub = void 0;
|
|
4
|
+
exports.replayBeaconStub = {
|
|
5
|
+
id: "replay-beacon-stub",
|
|
6
|
+
stage: "replay",
|
|
7
|
+
build: () => `
|
|
8
|
+
// Stub beacon calls so analytics doesn't leak outside the snapshot.
|
|
9
|
+
if (navigator.sendBeacon) {
|
|
10
|
+
const originalBeacon = navigator.sendBeacon.bind(navigator);
|
|
11
|
+
navigator.sendBeacon = (url, data) => {
|
|
12
|
+
const record = findRecord("POST", url, data);
|
|
13
|
+
if (record) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
};
|
|
18
|
+
navigator.sendBeacon.__pagepocketOriginal = originalBeacon;
|
|
19
|
+
}
|
|
20
|
+
`
|
|
21
|
+
};
|