@guidelytech/sdk-core 0.2.0 → 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/README.md +37 -4
- package/dist/chunk-GJT7CFAC.js +151 -0
- package/dist/chunk-GJT7CFAC.js.map +1 -0
- package/dist/chunk-HLR3W63Z.js +282 -0
- package/dist/chunk-HLR3W63Z.js.map +1 -0
- package/dist/headless.cjs +423 -0
- package/dist/headless.cjs.map +1 -0
- package/dist/headless.d.cts +65 -0
- package/dist/headless.d.ts +65 -0
- package/dist/headless.js +35 -0
- package/dist/headless.js.map +1 -0
- package/dist/index.cjs +731 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -213
- package/dist/index.d.ts +86 -213
- package/dist/index.js +701 -411
- package/dist/index.js.map +1 -1
- package/dist/recorder.cjs +766 -0
- package/dist/recorder.cjs.map +1 -0
- package/dist/recorder.d.cts +155 -0
- package/dist/recorder.d.ts +155 -0
- package/dist/recorder.js +735 -0
- package/dist/recorder.js.map +1 -0
- package/dist/types-BZ9r3Fs9.d.cts +247 -0
- package/dist/types-BZ9r3Fs9.d.ts +247 -0
- package/package.json +21 -8
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21
|
+
|
|
22
|
+
// src/recorder.ts
|
|
23
|
+
var recorder_exports = {};
|
|
24
|
+
__export(recorder_exports, {
|
|
25
|
+
ReplayController: () => ReplayController,
|
|
26
|
+
recorder: () => recorder,
|
|
27
|
+
startRecorder: () => startRecorder
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(recorder_exports);
|
|
30
|
+
var import_record = require("@rrweb/record");
|
|
31
|
+
var import_rrweb_plugin_console_record = require("@rrweb/rrweb-plugin-console-record");
|
|
32
|
+
|
|
33
|
+
// src/network.ts
|
|
34
|
+
var import_runtime = require("@guidelytech/shared/runtime");
|
|
35
|
+
var MAX_BODY_CHARS = 10 * 1024;
|
|
36
|
+
var MAX_REQUESTS_PER_PAGE = 500;
|
|
37
|
+
var BATCH_SIZE = 20;
|
|
38
|
+
var BATCH_MS = 1e3;
|
|
39
|
+
var BODY_TIMEOUT_MS = 2e3;
|
|
40
|
+
var REDACTED = "[redacted]";
|
|
41
|
+
var requestCount = 0;
|
|
42
|
+
var perfCutoff = 0;
|
|
43
|
+
var now = () => Date.now();
|
|
44
|
+
function redactUrl(raw, base = typeof location !== "undefined" ? location.href : "http://localhost/") {
|
|
45
|
+
try {
|
|
46
|
+
const u = new URL(raw, base);
|
|
47
|
+
u.searchParams.forEach((_v, k) => {
|
|
48
|
+
if (import_runtime.SENSITIVE_PARAM_PATTERN.test(k)) u.searchParams.set(k, REDACTED);
|
|
49
|
+
});
|
|
50
|
+
return u.toString().replace(/%5Bredacted%5D/g, REDACTED);
|
|
51
|
+
} catch {
|
|
52
|
+
return String(raw);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function maskJson(value, depth = 0) {
|
|
56
|
+
if (depth > 20 || !value || typeof value !== "object") return value;
|
|
57
|
+
if (Array.isArray(value)) return value.map((v) => maskJson(v, depth + 1));
|
|
58
|
+
const out = {};
|
|
59
|
+
for (const [k, v] of Object.entries(value)) out[k] = import_runtime.SENSITIVE_PARAM_PATTERN.test(k) ? REDACTED : maskJson(v, depth + 1);
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function cap(text, max) {
|
|
63
|
+
return text.length > max ? `${text.slice(0, max)}[truncated]` : text;
|
|
64
|
+
}
|
|
65
|
+
function formatBody(body, contentType) {
|
|
66
|
+
if (body == null || body === "") return void 0;
|
|
67
|
+
const ct = (contentType || "").toLowerCase();
|
|
68
|
+
if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) return formatBody(body.toString(), "application/x-www-form-urlencoded");
|
|
69
|
+
if (typeof body !== "string") return "[binary]";
|
|
70
|
+
if (ct && !/^text\/|json|x-www-form-urlencoded/.test(ct)) return "[binary]";
|
|
71
|
+
if (ct.includes("x-www-form-urlencoded")) {
|
|
72
|
+
const params = new URLSearchParams(body);
|
|
73
|
+
params.forEach((_v, k) => {
|
|
74
|
+
if (import_runtime.SENSITIVE_PARAM_PATTERN.test(k)) params.set(k, REDACTED);
|
|
75
|
+
});
|
|
76
|
+
return cap(params.toString().replace(/%5Bredacted%5D/g, REDACTED), MAX_BODY_CHARS);
|
|
77
|
+
}
|
|
78
|
+
if (ct.includes("json") || !ct && /^\s*[[{]/.test(body)) {
|
|
79
|
+
try {
|
|
80
|
+
return cap(JSON.stringify(maskJson(JSON.parse(body))), MAX_BODY_CHARS);
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return cap(body, MAX_BODY_CHARS);
|
|
85
|
+
}
|
|
86
|
+
function formatHeaders(entries) {
|
|
87
|
+
const out = {};
|
|
88
|
+
for (const [k, v] of entries) {
|
|
89
|
+
const name = k.toLowerCase();
|
|
90
|
+
out[name] = import_runtime.SENSITIVE_HEADER_PATTERN.test(name) ? REDACTED : String(v);
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
function headerEntries(h) {
|
|
95
|
+
if (!h) return [];
|
|
96
|
+
if (Array.isArray(h)) return h.map(([k, v]) => [String(k), String(v)]);
|
|
97
|
+
if (typeof h.forEach === "function") {
|
|
98
|
+
const out = [];
|
|
99
|
+
h.forEach((v, k) => out.push([k, v]));
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
return Object.entries(h).map(([k, v]) => [k, String(v)]);
|
|
103
|
+
}
|
|
104
|
+
function byteSize(data) {
|
|
105
|
+
if (typeof data === "string") return typeof TextEncoder !== "undefined" ? new TextEncoder().encode(data).length : data.length;
|
|
106
|
+
if (data instanceof ArrayBuffer) return data.byteLength;
|
|
107
|
+
if (ArrayBuffer.isView(data)) return data.byteLength;
|
|
108
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) return data.size;
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
function frameData(data, size) {
|
|
112
|
+
if (typeof data !== "string") return `[binary ${size} B]`;
|
|
113
|
+
let text = data;
|
|
114
|
+
try {
|
|
115
|
+
if (/^\s*[[{]/.test(text)) text = JSON.stringify(maskJson(JSON.parse(text)));
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
return cap(text, import_runtime.WS_MAX_MESSAGE_BYTES);
|
|
119
|
+
}
|
|
120
|
+
var RESOURCE_TYPES = { script: "script", link: "stylesheet", css: "stylesheet", img: "image", image: "image", audio: "media", video: "media", track: "media", beacon: "beacon", navigation: "document", iframe: "document", frame: "document" };
|
|
121
|
+
function startNetworkCapture(win, opts, emit) {
|
|
122
|
+
let active = true;
|
|
123
|
+
const startedAt = now();
|
|
124
|
+
let requests = [];
|
|
125
|
+
let messages = [];
|
|
126
|
+
let droppedRequests = 0;
|
|
127
|
+
let droppedMessages = 0;
|
|
128
|
+
let timer = null;
|
|
129
|
+
const recent = [];
|
|
130
|
+
const cleanups = [];
|
|
131
|
+
let apiPrefix = "";
|
|
132
|
+
try {
|
|
133
|
+
if (opts.apiUrl) apiPrefix = `${new URL(opts.apiUrl).origin}/api/v1/sdk/`;
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
const excluded = (url) => /^(data|blob|about|chrome-extension):/i.test(url) || !!apiPrefix && url.replace(/^ws(s?):/i, "http$1:").startsWith(apiPrefix) || /\/guidely-recorder(\.min)?\.js(\?|$)/.test(url);
|
|
137
|
+
const flush = () => {
|
|
138
|
+
if (timer) clearTimeout(timer);
|
|
139
|
+
timer = null;
|
|
140
|
+
if (!requests.length && !messages.length && !droppedRequests && !droppedMessages) return;
|
|
141
|
+
const payload = {};
|
|
142
|
+
if (requests.length) payload.requests = requests;
|
|
143
|
+
if (messages.length) payload.messages = messages;
|
|
144
|
+
if (droppedRequests || droppedMessages) payload.dropped = { ...droppedRequests ? { requests: droppedRequests } : {}, ...droppedMessages ? { messages: droppedMessages } : {} };
|
|
145
|
+
requests = [];
|
|
146
|
+
messages = [];
|
|
147
|
+
droppedRequests = 0;
|
|
148
|
+
droppedMessages = 0;
|
|
149
|
+
try {
|
|
150
|
+
emit(payload);
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const schedule = () => {
|
|
155
|
+
if (requests.length + messages.length >= BATCH_SIZE) flush();
|
|
156
|
+
else timer ?? (timer = setTimeout(flush, BATCH_MS));
|
|
157
|
+
};
|
|
158
|
+
const push = (req, counted = false) => {
|
|
159
|
+
if (!active) return false;
|
|
160
|
+
if (!counted) {
|
|
161
|
+
if (requestCount >= MAX_REQUESTS_PER_PAGE) {
|
|
162
|
+
droppedRequests += 1;
|
|
163
|
+
schedule();
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
requestCount += 1;
|
|
167
|
+
}
|
|
168
|
+
let out = req;
|
|
169
|
+
if (opts.mask) {
|
|
170
|
+
try {
|
|
171
|
+
out = opts.mask({ ...req });
|
|
172
|
+
} catch {
|
|
173
|
+
out = null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!out) return false;
|
|
177
|
+
requests.push(out);
|
|
178
|
+
schedule();
|
|
179
|
+
return true;
|
|
180
|
+
};
|
|
181
|
+
const finish = (req, fields) => {
|
|
182
|
+
Object.assign(req, fields);
|
|
183
|
+
req.endTime = now();
|
|
184
|
+
req.durationMs = req.endTime - req.startTime;
|
|
185
|
+
};
|
|
186
|
+
const remember = (req) => {
|
|
187
|
+
recent.push(req);
|
|
188
|
+
if (recent.length > 100) recent.shift();
|
|
189
|
+
};
|
|
190
|
+
const origFetch = win.fetch;
|
|
191
|
+
if (typeof origFetch === "function") {
|
|
192
|
+
const patched = function(input, init) {
|
|
193
|
+
const p = origFetch.call(this === void 0 || this === patched ? win : this, input, init);
|
|
194
|
+
if (!active) return p;
|
|
195
|
+
try {
|
|
196
|
+
const isReq = typeof Request !== "undefined" && input instanceof Request;
|
|
197
|
+
const rawUrl = isReq ? input.url : String(input instanceof URL ? input.href : input);
|
|
198
|
+
const url = redactUrl(rawUrl);
|
|
199
|
+
if (excluded(url) || excluded(rawUrl)) return p;
|
|
200
|
+
const req = { id: (0, import_runtime.randomId)(12), type: "fetch", method: String(init?.method || (isReq ? input.method : "GET")).toUpperCase(), url, startTime: now() };
|
|
201
|
+
if (opts.payloads) {
|
|
202
|
+
const headers = headerEntries(init?.headers ?? (isReq ? input.headers : void 0));
|
|
203
|
+
req.requestHeaders = formatHeaders(headers);
|
|
204
|
+
const body = formatBody(init?.body, req.requestHeaders["content-type"] ?? (typeof init?.body === "string" ? "text/plain" : void 0));
|
|
205
|
+
if (body !== void 0) req.requestBody = body;
|
|
206
|
+
}
|
|
207
|
+
remember(req);
|
|
208
|
+
p.then(
|
|
209
|
+
(res) => {
|
|
210
|
+
try {
|
|
211
|
+
finish(req, { status: res.status });
|
|
212
|
+
if (!opts.payloads) return void push(req);
|
|
213
|
+
req.responseHeaders = formatHeaders(headerEntries(res.headers));
|
|
214
|
+
const ct = res.headers.get("content-type");
|
|
215
|
+
if (!ct || !/^text\/(?!event-stream)|json|x-www-form-urlencoded/i.test(ct) || !res.body) {
|
|
216
|
+
if (ct && res.body) req.responseBody = "[binary]";
|
|
217
|
+
return void push(req);
|
|
218
|
+
}
|
|
219
|
+
const reader = res.clone().body.getReader();
|
|
220
|
+
const decoder = new TextDecoder();
|
|
221
|
+
let text = "";
|
|
222
|
+
const done = () => {
|
|
223
|
+
req.responseBody = formatBody(text, ct);
|
|
224
|
+
push(req);
|
|
225
|
+
};
|
|
226
|
+
const t = setTimeout(() => void reader.cancel().catch(() => void 0), BODY_TIMEOUT_MS);
|
|
227
|
+
const read = () => reader.read().then(({ done: end, value }) => {
|
|
228
|
+
if (value) text += decoder.decode(value, { stream: true });
|
|
229
|
+
if (end) return;
|
|
230
|
+
if (text.length > MAX_BODY_CHARS * 4) return void reader.cancel().catch(() => void 0);
|
|
231
|
+
return read();
|
|
232
|
+
});
|
|
233
|
+
read().catch(() => void 0).then(() => {
|
|
234
|
+
clearTimeout(t);
|
|
235
|
+
done();
|
|
236
|
+
});
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
(err) => {
|
|
241
|
+
try {
|
|
242
|
+
finish(req, { status: 0, error: err instanceof Error && err.name === "AbortError" ? "aborted" : String(err?.message || err) });
|
|
243
|
+
push(req);
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
return p;
|
|
251
|
+
};
|
|
252
|
+
win.fetch = patched;
|
|
253
|
+
cleanups.push(() => {
|
|
254
|
+
if (win.fetch === patched) win.fetch = origFetch;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
const XHR = win.XMLHttpRequest;
|
|
258
|
+
if (XHR?.prototype) {
|
|
259
|
+
const proto = XHR.prototype;
|
|
260
|
+
const { open, send, setRequestHeader } = proto;
|
|
261
|
+
const state = /* @__PURE__ */ new WeakMap();
|
|
262
|
+
const pOpen = function(method, url, ...rest) {
|
|
263
|
+
try {
|
|
264
|
+
if (active) state.set(this, { method: String(method).toUpperCase(), url: String(url), headers: [] });
|
|
265
|
+
} catch {
|
|
266
|
+
}
|
|
267
|
+
return open.call(this, method, url, ...rest);
|
|
268
|
+
};
|
|
269
|
+
const pSetHeader = function(name, value) {
|
|
270
|
+
try {
|
|
271
|
+
state.get(this)?.headers.push([name, value]);
|
|
272
|
+
} catch {
|
|
273
|
+
}
|
|
274
|
+
return setRequestHeader.call(this, name, value);
|
|
275
|
+
};
|
|
276
|
+
const pSend = function(body) {
|
|
277
|
+
try {
|
|
278
|
+
const s = state.get(this);
|
|
279
|
+
const url = s ? redactUrl(s.url) : "";
|
|
280
|
+
if (active && s && !excluded(url) && !excluded(s.url)) {
|
|
281
|
+
const req = { id: (0, import_runtime.randomId)(12), type: "xhr", method: s.method, url, startTime: now() };
|
|
282
|
+
if (opts.payloads) {
|
|
283
|
+
req.requestHeaders = formatHeaders(s.headers);
|
|
284
|
+
const b = formatBody(body, req.requestHeaders["content-type"] ?? (typeof body === "string" ? "text/plain" : void 0));
|
|
285
|
+
if (b !== void 0) req.requestBody = b;
|
|
286
|
+
}
|
|
287
|
+
remember(req);
|
|
288
|
+
let ended = false;
|
|
289
|
+
const end = (error) => () => {
|
|
290
|
+
if (ended) return;
|
|
291
|
+
ended = true;
|
|
292
|
+
try {
|
|
293
|
+
finish(req, { status: error ? 0 : this.status, ...error ? { error } : {} });
|
|
294
|
+
if (opts.payloads && !error) {
|
|
295
|
+
const raw = this.getAllResponseHeaders() || "";
|
|
296
|
+
req.responseHeaders = formatHeaders(raw.trim().split(/[\r\n]+/).filter(Boolean).map((line) => [line.slice(0, line.indexOf(":")).trim(), line.slice(line.indexOf(":") + 1).trim()]));
|
|
297
|
+
const ct = this.getResponseHeader("content-type");
|
|
298
|
+
const type = this.responseType;
|
|
299
|
+
const text = type === "" || type === "text" ? this.responseText : type === "json" ? JSON.stringify(this.response) : null;
|
|
300
|
+
const formatted = text == null ? this.response ? "[binary]" : void 0 : formatBody(text, ct ?? (type === "json" ? "application/json" : "text/plain"));
|
|
301
|
+
if (formatted !== void 0) req.responseBody = formatted;
|
|
302
|
+
}
|
|
303
|
+
push(req);
|
|
304
|
+
} catch {
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
this.addEventListener("load", end());
|
|
308
|
+
this.addEventListener("error", end("network error"));
|
|
309
|
+
this.addEventListener("abort", end("aborted"));
|
|
310
|
+
this.addEventListener("timeout", end("timeout"));
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
}
|
|
314
|
+
return send.call(this, body);
|
|
315
|
+
};
|
|
316
|
+
proto.open = pOpen;
|
|
317
|
+
proto.setRequestHeader = pSetHeader;
|
|
318
|
+
proto.send = pSend;
|
|
319
|
+
cleanups.push(() => {
|
|
320
|
+
if (proto.open === pOpen) proto.open = open;
|
|
321
|
+
if (proto.setRequestHeader === pSetHeader) proto.setRequestHeader = setRequestHeader;
|
|
322
|
+
if (proto.send === pSend) proto.send = send;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
const WS = win.WebSocket;
|
|
326
|
+
if (typeof WS === "function") {
|
|
327
|
+
const track = (ws, rawUrl) => {
|
|
328
|
+
const url = redactUrl(rawUrl);
|
|
329
|
+
if (excluded(url)) return;
|
|
330
|
+
const req = { id: (0, import_runtime.randomId)(12), type: "websocket", method: "GET", url, startTime: now() };
|
|
331
|
+
const totals = { sent: 0, received: 0, sentBytes: 0, receivedBytes: 0, recorded: 0 };
|
|
332
|
+
let dropped = false;
|
|
333
|
+
let counted = false;
|
|
334
|
+
const emitConn = (fields) => {
|
|
335
|
+
if (dropped || !active) return;
|
|
336
|
+
Object.assign(req, fields, { messages: { ...totals } });
|
|
337
|
+
dropped = !push({ ...req }, counted);
|
|
338
|
+
counted = true;
|
|
339
|
+
};
|
|
340
|
+
const frame = (dir, data) => {
|
|
341
|
+
try {
|
|
342
|
+
const size = byteSize(data);
|
|
343
|
+
if (dir === "send") {
|
|
344
|
+
totals.sent += 1;
|
|
345
|
+
totals.sentBytes += size;
|
|
346
|
+
} else {
|
|
347
|
+
totals.received += 1;
|
|
348
|
+
totals.receivedBytes += size;
|
|
349
|
+
}
|
|
350
|
+
if (dropped || !active) return;
|
|
351
|
+
if (totals.recorded >= import_runtime.WS_MAX_MESSAGES_PER_SOCKET) {
|
|
352
|
+
droppedMessages += 1;
|
|
353
|
+
return schedule();
|
|
354
|
+
}
|
|
355
|
+
totals.recorded += 1;
|
|
356
|
+
const m = { socketId: req.id, dir, time: now(), size };
|
|
357
|
+
if (typeof data !== "string") m.binary = true;
|
|
358
|
+
if (opts.payloads) m.data = frameData(data, size);
|
|
359
|
+
messages.push(m);
|
|
360
|
+
schedule();
|
|
361
|
+
} catch {
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
const nativeSend = ws.send;
|
|
365
|
+
ws.send = function(data) {
|
|
366
|
+
const r = nativeSend.call(this, data);
|
|
367
|
+
frame("send", data);
|
|
368
|
+
return r;
|
|
369
|
+
};
|
|
370
|
+
ws.addEventListener("open", () => emitConn({ protocol: ws.protocol || void 0 }));
|
|
371
|
+
ws.addEventListener("message", (e) => frame("recv", e.data));
|
|
372
|
+
let errored = false;
|
|
373
|
+
ws.addEventListener("error", () => {
|
|
374
|
+
errored = true;
|
|
375
|
+
});
|
|
376
|
+
ws.addEventListener("close", (e) => {
|
|
377
|
+
const endTime = now();
|
|
378
|
+
emitConn({ endTime, durationMs: endTime - req.startTime, closeCode: e.code, closeReason: e.reason || void 0, ...errored || e.code === 1006 ? { error: "connection error" } : {} });
|
|
379
|
+
});
|
|
380
|
+
};
|
|
381
|
+
const Patched = class extends WS {
|
|
382
|
+
constructor(url, protocols) {
|
|
383
|
+
super(url, protocols);
|
|
384
|
+
try {
|
|
385
|
+
if (active) track(this, String(url instanceof URL ? url.href : url));
|
|
386
|
+
} catch {
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
static [Symbol.hasInstance](x) {
|
|
390
|
+
return x instanceof WS;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
Object.defineProperty(Patched, "name", { value: WS.name });
|
|
394
|
+
win.WebSocket = Patched;
|
|
395
|
+
cleanups.push(() => {
|
|
396
|
+
if (win.WebSocket === Patched) win.WebSocket = WS;
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
const PO = win.PerformanceObserver;
|
|
400
|
+
if (typeof PO === "function") {
|
|
401
|
+
const origin = win.performance?.timeOrigin ?? startedAt - (win.performance?.now?.() ?? 0);
|
|
402
|
+
const cutoff = perfCutoff;
|
|
403
|
+
const seen = /* @__PURE__ */ new Set();
|
|
404
|
+
const onEntries = (list) => {
|
|
405
|
+
for (const e of list.getEntries()) {
|
|
406
|
+
try {
|
|
407
|
+
const start = Math.round(origin + e.startTime);
|
|
408
|
+
const key = `${e.entryType}|${e.name}|${e.startTime}`;
|
|
409
|
+
if (start < cutoff || seen.has(key)) continue;
|
|
410
|
+
seen.add(key);
|
|
411
|
+
const url = redactUrl(e.name);
|
|
412
|
+
if (excluded(url) || excluded(e.name)) continue;
|
|
413
|
+
const status = e.responseStatus;
|
|
414
|
+
const timing = { transferSize: e.transferSize, responseSize: e.decodedBodySize, ...status ? { status } : {} };
|
|
415
|
+
if (e.initiatorType === "fetch" || e.initiatorType === "xmlhttprequest") {
|
|
416
|
+
const match = recent.find((r) => r.url === url && Math.abs(r.startTime - start) <= 50);
|
|
417
|
+
if (match) Object.assign(match, { transferSize: timing.transferSize, responseSize: timing.responseSize }, match.status === void 0 && status ? { status } : {});
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
const type = e.entryType === "navigation" ? "document" : /\.(woff2?|ttf|otf|eot)(\?|$)/i.test(e.name) ? "font" : RESOURCE_TYPES[e.initiatorType] ?? "other";
|
|
421
|
+
push({ id: (0, import_runtime.randomId)(12), type, method: "GET", url, startTime: start, endTime: Math.round(origin + e.responseEnd), durationMs: Math.round(e.duration), ...timing });
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
for (const type of ["resource", "navigation"]) {
|
|
427
|
+
try {
|
|
428
|
+
const po = new PO(onEntries);
|
|
429
|
+
po.observe({ type, buffered: true });
|
|
430
|
+
cleanups.push(() => po.disconnect());
|
|
431
|
+
} catch {
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
flush,
|
|
437
|
+
stop: () => {
|
|
438
|
+
if (!active) return;
|
|
439
|
+
flush();
|
|
440
|
+
active = false;
|
|
441
|
+
perfCutoff = now();
|
|
442
|
+
cleanups.splice(0).forEach((fn) => {
|
|
443
|
+
try {
|
|
444
|
+
fn();
|
|
445
|
+
} catch {
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function networkRecordPlugin(opts) {
|
|
452
|
+
let capture = null;
|
|
453
|
+
return {
|
|
454
|
+
plugin: {
|
|
455
|
+
name: import_runtime.NETWORK_PLUGIN,
|
|
456
|
+
options: {},
|
|
457
|
+
observer: (cb, win) => {
|
|
458
|
+
if (capture || win !== (typeof window !== "undefined" ? window : win)) return () => {
|
|
459
|
+
};
|
|
460
|
+
const c = startNetworkCapture(win, opts, cb);
|
|
461
|
+
capture = c;
|
|
462
|
+
return () => {
|
|
463
|
+
c.stop();
|
|
464
|
+
if (capture === c) capture = null;
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
flush: () => capture?.flush()
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/replay.ts
|
|
473
|
+
var import_runtime3 = require("@guidelytech/shared/runtime");
|
|
474
|
+
|
|
475
|
+
// src/http.ts
|
|
476
|
+
function backoffMs(attempt, baseMs = 1e3, maxMs = 3e4) {
|
|
477
|
+
return Math.min(maxMs, baseMs * 2 ** Math.max(0, attempt));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/replay.ts
|
|
481
|
+
var UNLOAD_MAX_BYTES = 60 * 1024;
|
|
482
|
+
async function gzip(text) {
|
|
483
|
+
const stream = new Blob([text]).stream().pipeThrough(new CompressionStream("gzip"));
|
|
484
|
+
return new Response(stream).blob();
|
|
485
|
+
}
|
|
486
|
+
var ReplayController = class {
|
|
487
|
+
constructor(opts) {
|
|
488
|
+
__publicField(this, "opts", opts);
|
|
489
|
+
__publicField(this, "windowId", (0, import_runtime3.randomId)(16));
|
|
490
|
+
__publicField(this, "handle", null);
|
|
491
|
+
__publicField(this, "sessionId", null);
|
|
492
|
+
__publicField(this, "configKey", "");
|
|
493
|
+
__publicField(this, "buffer", null);
|
|
494
|
+
__publicField(this, "queue", []);
|
|
495
|
+
__publicField(this, "queuedBytes", 0);
|
|
496
|
+
__publicField(this, "seqs", /* @__PURE__ */ new Map());
|
|
497
|
+
__publicField(this, "interval", null);
|
|
498
|
+
__publicField(this, "retryTimer", null);
|
|
499
|
+
__publicField(this, "attempt", 0);
|
|
500
|
+
__publicField(this, "uploading", false);
|
|
501
|
+
__publicField(this, "flushing", null);
|
|
502
|
+
__publicField(this, "destroyed", false);
|
|
503
|
+
}
|
|
504
|
+
/** True while rrweb is recording. */
|
|
505
|
+
isRecording() {
|
|
506
|
+
return !!this.handle;
|
|
507
|
+
}
|
|
508
|
+
/** The session currently being recorded, if any. */
|
|
509
|
+
get activeSessionId() {
|
|
510
|
+
return this.sessionId;
|
|
511
|
+
}
|
|
512
|
+
/** Starts (or keeps) recording for `sessionId`. Restarts when the session or settings changed. */
|
|
513
|
+
start(sessionId, config) {
|
|
514
|
+
if (this.destroyed) return;
|
|
515
|
+
const key = JSON.stringify(config);
|
|
516
|
+
if (this.sessionId === sessionId && this.configKey === key && this.handle) return;
|
|
517
|
+
this.stop();
|
|
518
|
+
this.handle = this.opts.startRecorder({ ...config, emit: (e) => this.push(sessionId, e), onError: this.opts.onError });
|
|
519
|
+
if (!this.handle) return;
|
|
520
|
+
this.sessionId = sessionId;
|
|
521
|
+
this.configKey = key;
|
|
522
|
+
this.interval = setInterval(() => void this.flush(), this.opts.flushIntervalMs ?? 5e3);
|
|
523
|
+
}
|
|
524
|
+
/** Stops recording. Buffered events are uploaded unless `discard` (consent withdrawn, authoring). */
|
|
525
|
+
stop(options = {}) {
|
|
526
|
+
const handle = this.handle;
|
|
527
|
+
this.handle = null;
|
|
528
|
+
this.sessionId = null;
|
|
529
|
+
this.configKey = "";
|
|
530
|
+
if (this.interval) clearInterval(this.interval);
|
|
531
|
+
this.interval = null;
|
|
532
|
+
handle?.stop();
|
|
533
|
+
if (options.discard) {
|
|
534
|
+
this.buffer = null;
|
|
535
|
+
this.queue = [];
|
|
536
|
+
this.queuedBytes = 0;
|
|
537
|
+
} else void this.flush();
|
|
538
|
+
}
|
|
539
|
+
/** Adds an rrweb custom event (Guidely event markers) while recording. */
|
|
540
|
+
addCustomEvent(tag, payload) {
|
|
541
|
+
this.handle?.addCustomEvent(tag, payload);
|
|
542
|
+
}
|
|
543
|
+
push(sessionId, event) {
|
|
544
|
+
let json;
|
|
545
|
+
try {
|
|
546
|
+
json = JSON.stringify(event);
|
|
547
|
+
} catch {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (this.buffer && this.buffer.sessionId !== sessionId) this.seal();
|
|
551
|
+
const chunk = this.buffer ?? (this.buffer = { sessionId, meta: this.opts.meta(), events: [], bytes: 0 });
|
|
552
|
+
chunk.events.push(json);
|
|
553
|
+
chunk.bytes += json.length;
|
|
554
|
+
if (chunk.bytes > (this.opts.maxBufferBytes ?? 1e6)) void this.flush();
|
|
555
|
+
}
|
|
556
|
+
/** Moves the buffer to the upload queue, dropping the oldest chunks above the backlog cap. */
|
|
557
|
+
seal() {
|
|
558
|
+
const chunk = this.buffer;
|
|
559
|
+
this.buffer = null;
|
|
560
|
+
if (!chunk || !chunk.events.length) return;
|
|
561
|
+
this.queue.push(chunk);
|
|
562
|
+
this.queuedBytes += chunk.bytes;
|
|
563
|
+
const max = this.opts.maxQueuedBytes ?? 5e6;
|
|
564
|
+
while (this.queuedBytes > max && this.queue.length > 1) {
|
|
565
|
+
const i = this.uploading ? 1 : 0;
|
|
566
|
+
const [dropped] = this.queue.splice(i, 1);
|
|
567
|
+
if (dropped) this.queuedBytes -= dropped.bytes;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
nextSeq(chunk) {
|
|
571
|
+
if (chunk.seq === void 0) {
|
|
572
|
+
const seq = this.seqs.get(chunk.sessionId) ?? 0;
|
|
573
|
+
this.seqs.set(chunk.sessionId, seq + 1);
|
|
574
|
+
chunk.seq = seq;
|
|
575
|
+
}
|
|
576
|
+
return chunk.seq;
|
|
577
|
+
}
|
|
578
|
+
body(chunk) {
|
|
579
|
+
const head = JSON.stringify({ sessionId: chunk.sessionId, windowId: this.windowId, seq: this.nextSeq(chunk), ...chunk.meta });
|
|
580
|
+
return `${head.slice(0, -1)},"events":[${chunk.events.join(",")}]}`;
|
|
581
|
+
}
|
|
582
|
+
remove(chunk) {
|
|
583
|
+
const i = this.queue.indexOf(chunk);
|
|
584
|
+
if (i >= 0) {
|
|
585
|
+
this.queue.splice(i, 1);
|
|
586
|
+
this.queuedBytes -= chunk.bytes;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
/** Uploads the buffer and the backlog, one chunk at a time; retries with backoff on failure. */
|
|
590
|
+
flush() {
|
|
591
|
+
this.seal();
|
|
592
|
+
if (this.retryTimer || !this.queue.length) return Promise.resolve();
|
|
593
|
+
if (this.flushing) return this.flushing.then(() => this.flush());
|
|
594
|
+
return this.flushing = this.drain().finally(() => {
|
|
595
|
+
this.flushing = null;
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
async drain() {
|
|
599
|
+
this.uploading = true;
|
|
600
|
+
try {
|
|
601
|
+
while (this.queue.length) {
|
|
602
|
+
const chunk = this.queue[0];
|
|
603
|
+
const result = await this.send(chunk);
|
|
604
|
+
if (result === "retry") {
|
|
605
|
+
this.attempt += 1;
|
|
606
|
+
if (!this.destroyed && this.attempt < 6) {
|
|
607
|
+
this.retryTimer = setTimeout(() => {
|
|
608
|
+
this.retryTimer = null;
|
|
609
|
+
void this.flush();
|
|
610
|
+
}, backoffMs(this.attempt - 1));
|
|
611
|
+
} else this.attempt = 0;
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
this.attempt = 0;
|
|
615
|
+
this.remove(chunk);
|
|
616
|
+
if (result === "disabled") {
|
|
617
|
+
for (const c of this.queue.slice()) if (c.sessionId === chunk.sessionId) this.remove(c);
|
|
618
|
+
this.opts.onDisabled(chunk.sessionId);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
} finally {
|
|
622
|
+
this.uploading = false;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
async send(chunk) {
|
|
626
|
+
const f = this.opts.fetch();
|
|
627
|
+
if (!f) return "drop";
|
|
628
|
+
try {
|
|
629
|
+
const json = this.body(chunk);
|
|
630
|
+
let body = json;
|
|
631
|
+
let enc;
|
|
632
|
+
if (typeof CompressionStream === "function") {
|
|
633
|
+
try {
|
|
634
|
+
body = await gzip(json);
|
|
635
|
+
enc = "gzip";
|
|
636
|
+
} catch {
|
|
637
|
+
body = json;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
const size = typeof body === "string" ? body.length : body.size;
|
|
641
|
+
const res = await f(this.opts.url({ enc }), { method: "POST", body, headers: { "Content-Type": "text/plain" }, keepalive: size < UNLOAD_MAX_BYTES, credentials: "omit", mode: "cors" });
|
|
642
|
+
if (res.ok) {
|
|
643
|
+
const data = await res.json().catch(() => null);
|
|
644
|
+
return data?.disabled ? "disabled" : "ok";
|
|
645
|
+
}
|
|
646
|
+
return res.status >= 400 && res.status < 500 && res.status !== 429 ? "drop" : "retry";
|
|
647
|
+
} catch {
|
|
648
|
+
return "retry";
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Page is being hidden / unloaded: sends whatever is buffered synchronously as uncompressed JSON
|
|
653
|
+
* (`sendBeacon` up to 60KB, keepalive fetch otherwise), splitting chunks that never went out.
|
|
654
|
+
*/
|
|
655
|
+
flushOnHide() {
|
|
656
|
+
this.handle?.flush?.();
|
|
657
|
+
this.seal();
|
|
658
|
+
const pending = this.queue.slice(this.uploading ? 1 : 0);
|
|
659
|
+
for (const chunk of pending) {
|
|
660
|
+
const parts = chunk.seq === void 0 ? splitChunk(chunk, UNLOAD_MAX_BYTES - 2048) : [chunk];
|
|
661
|
+
let sent = true;
|
|
662
|
+
for (const part of parts) {
|
|
663
|
+
if (!this.sendNow(this.body(part))) {
|
|
664
|
+
sent = false;
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
if (part !== chunk) {
|
|
668
|
+
chunk.events.splice(0, part.events.length);
|
|
669
|
+
chunk.bytes -= part.bytes;
|
|
670
|
+
this.queuedBytes -= part.bytes;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (!sent) break;
|
|
674
|
+
this.remove(chunk);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
sendNow(json) {
|
|
678
|
+
const url = this.opts.url({});
|
|
679
|
+
try {
|
|
680
|
+
if (json.length <= UNLOAD_MAX_BYTES && typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" && navigator.sendBeacon(url, new Blob([json], { type: "text/plain;charset=UTF-8" }))) return true;
|
|
681
|
+
const f = this.opts.fetch();
|
|
682
|
+
if (!f || json.length > 64 * 1024) return false;
|
|
683
|
+
void f(url, { method: "POST", body: json, headers: { "Content-Type": "text/plain" }, keepalive: true, credentials: "omit", mode: "cors" }).catch(() => void 0);
|
|
684
|
+
return true;
|
|
685
|
+
} catch {
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/** Stops recording and uploads what is left; no retries afterwards. */
|
|
690
|
+
destroy() {
|
|
691
|
+
this.stop();
|
|
692
|
+
this.destroyed = true;
|
|
693
|
+
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
694
|
+
this.retryTimer = null;
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
function splitChunk(chunk, max) {
|
|
698
|
+
if (chunk.bytes <= max) return [chunk];
|
|
699
|
+
const parts = [];
|
|
700
|
+
let cur = null;
|
|
701
|
+
for (const e of chunk.events) {
|
|
702
|
+
if (!cur || cur.bytes + e.length > max && cur.events.length) parts.push(cur = { sessionId: chunk.sessionId, meta: chunk.meta, events: [], bytes: 0 });
|
|
703
|
+
cur.events.push(e);
|
|
704
|
+
cur.bytes += e.length;
|
|
705
|
+
}
|
|
706
|
+
return parts;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// src/recorder.ts
|
|
710
|
+
function startRecorder(options) {
|
|
711
|
+
const onError = (err) => {
|
|
712
|
+
try {
|
|
713
|
+
options.onError?.(err);
|
|
714
|
+
} catch {
|
|
715
|
+
}
|
|
716
|
+
return true;
|
|
717
|
+
};
|
|
718
|
+
try {
|
|
719
|
+
const network = options.network ? networkRecordPlugin(options.network) : null;
|
|
720
|
+
const plugins = [];
|
|
721
|
+
if (options.recordConsole) plugins.push((0, import_rrweb_plugin_console_record.getRecordConsolePlugin)({ level: ["error", "warn", "info", "log"], lengthThreshold: 200 }));
|
|
722
|
+
if (network) plugins.push(network.plugin);
|
|
723
|
+
const stop = (0, import_record.record)({
|
|
724
|
+
emit: (event) => options.emit(event),
|
|
725
|
+
maskAllInputs: true,
|
|
726
|
+
maskTextSelector: options.maskTextSelector || void 0,
|
|
727
|
+
blockSelector: options.blockSelector || void 0,
|
|
728
|
+
slimDOMOptions: { script: true, comment: true, headFavicon: true, headWhitespace: true, headMetaSocial: true, headMetaRobots: true, headMetaHttpEquiv: true, headMetaVerification: true, headMetaAuthorship: true, headMetaDescKeywords: true },
|
|
729
|
+
sampling: { mousemove: 50, scroll: 150, input: "last" },
|
|
730
|
+
inlineStylesheet: true,
|
|
731
|
+
recordCanvas: false,
|
|
732
|
+
collectFonts: false,
|
|
733
|
+
inlineImages: false,
|
|
734
|
+
plugins,
|
|
735
|
+
errorHandler: onError
|
|
736
|
+
});
|
|
737
|
+
if (!stop) return null;
|
|
738
|
+
return {
|
|
739
|
+
stop: () => {
|
|
740
|
+
try {
|
|
741
|
+
stop();
|
|
742
|
+
} catch (err) {
|
|
743
|
+
onError(err);
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
addCustomEvent: (tag, payload) => {
|
|
747
|
+
try {
|
|
748
|
+
import_record.record.addCustomEvent(tag, payload);
|
|
749
|
+
} catch {
|
|
750
|
+
}
|
|
751
|
+
},
|
|
752
|
+
flush: () => network?.flush()
|
|
753
|
+
};
|
|
754
|
+
} catch (err) {
|
|
755
|
+
onError(err);
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
var recorder = { startRecorder, createReplay: (options) => new ReplayController({ ...options, startRecorder }) };
|
|
760
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
761
|
+
0 && (module.exports = {
|
|
762
|
+
ReplayController,
|
|
763
|
+
recorder,
|
|
764
|
+
startRecorder
|
|
765
|
+
});
|
|
766
|
+
//# sourceMappingURL=recorder.cjs.map
|