@oneuptime/react-native-replay 13.0.4
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/OneUptimeReactNativeReplay.podspec +20 -0
- package/README.md +264 -0
- package/android/build.gradle +36 -0
- package/android/consumer-rules.pro +26 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/oneuptime/replay/OneUptimeReplayPackage.java +29 -0
- package/android/src/main/java/com/oneuptime/replay/OneUptimeReplayViewTreeModule.java +541 -0
- package/dist/index.cjs +4111 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +4088 -0
- package/dist/index.js.map +7 -0
- package/dist/types/ChunkBuffer.d.ts +47 -0
- package/dist/types/ChunkBuffer.d.ts.map +1 -0
- package/dist/types/Config.d.ts +59 -0
- package/dist/types/Config.d.ts.map +1 -0
- package/dist/types/Contract.d.ts +176 -0
- package/dist/types/Contract.d.ts.map +1 -0
- package/dist/types/Events.d.ts +14 -0
- package/dist/types/Events.d.ts.map +1 -0
- package/dist/types/MobileReplayRecorder.d.ts +176 -0
- package/dist/types/MobileReplayRecorder.d.ts.map +1 -0
- package/dist/types/NativeViewTree.d.ts +49 -0
- package/dist/types/NativeViewTree.d.ts.map +1 -0
- package/dist/types/Outbox.d.ts +29 -0
- package/dist/types/Outbox.d.ts.map +1 -0
- package/dist/types/ReplayProvider.d.ts +28 -0
- package/dist/types/ReplayProvider.d.ts.map +1 -0
- package/dist/types/Sanitize.d.ts +29 -0
- package/dist/types/Sanitize.d.ts.map +1 -0
- package/dist/types/Storage.d.ts +30 -0
- package/dist/types/Storage.d.ts.map +1 -0
- package/dist/types/TouchPrivacy.d.ts +15 -0
- package/dist/types/TouchPrivacy.d.ts.map +1 -0
- package/dist/types/Transport.d.ts +36 -0
- package/dist/types/Transport.d.ts.map +1 -0
- package/dist/types/ViewTreeSerializer.d.ts +29 -0
- package/dist/types/ViewTreeSerializer.d.ts.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -0
- package/ios/OneUptimeReplayViewTreeModule.m +17 -0
- package/ios/OneUptimeReplayViewTreeModule.swift +239 -0
- package/package.json +66 -0
- package/react-native.config.js +15 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4088 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/ReplayProvider.tsx
|
|
6
|
+
import {
|
|
7
|
+
createContext,
|
|
8
|
+
useContext,
|
|
9
|
+
useEffect,
|
|
10
|
+
useRef
|
|
11
|
+
} from "react";
|
|
12
|
+
import {
|
|
13
|
+
findNodeHandle,
|
|
14
|
+
View
|
|
15
|
+
} from "react-native";
|
|
16
|
+
|
|
17
|
+
// src/MobileReplayRecorder.ts
|
|
18
|
+
import { AppState } from "react-native";
|
|
19
|
+
|
|
20
|
+
// src/Contract.ts
|
|
21
|
+
var SESSION_REPLAY_WIRE_VERSION = 1;
|
|
22
|
+
var SESSION_REPLAY_SCHEMA_VERSION = 1;
|
|
23
|
+
var SESSION_REPLAY_CONTENT_TYPE = "application/vnd.oneuptime.session-replay.v1";
|
|
24
|
+
var SESSION_REPLAY_APP_IDENTIFIER_HEADER = "x-oneuptime-app-identifier";
|
|
25
|
+
var SESSION_REPLAY_RECORDER_KIND_HEADER = "x-oneuptime-replay-recorder-kind";
|
|
26
|
+
var SESSION_REPLAY_MOBILE_APP_IDENTIFIER_HEADER = "x-oneuptime-mobile-app-identifier";
|
|
27
|
+
var SESSION_REPLAY_USER_REF_HEADER = "x-oneuptime-user-ref";
|
|
28
|
+
var SESSION_REPLAY_MAX_USER_REF_LENGTH = 512;
|
|
29
|
+
var AUTH_TOKEN_HEADER = "x-oneuptime-token";
|
|
30
|
+
var CONFIG_PATH = "/telemetry/session-replay/v1/config";
|
|
31
|
+
var CHUNK_PATH = "/telemetry/session-replay/v1/chunk";
|
|
32
|
+
var SESSION_REPLAY_FLUSH_INTERVAL_MS = 15e3;
|
|
33
|
+
var SESSION_REPLAY_FLUSH_BYTES = 256 * 1024;
|
|
34
|
+
var SESSION_REPLAY_CHECKOUT_INTERVAL_MS = 6e4;
|
|
35
|
+
var SESSION_REPLAY_ROLLING_BUFFER_MS = 6e4;
|
|
36
|
+
var SESSION_REPLAY_ROLLING_BUFFER_BYTES = 2 * 1024 * 1024;
|
|
37
|
+
var SESSION_REPLAY_IDLE_ROLLOVER_MS = 30 * 6e4;
|
|
38
|
+
var SESSION_REPLAY_MAX_SESSION_MS = 4 * 60 * 6e4;
|
|
39
|
+
var SESSION_REPLAY_MAX_DECOMPRESSED_FRAME_BYTES = 8 * 1024 * 1024;
|
|
40
|
+
var MAX_SESSION_REPLAY_CHUNK_BYTES = 2 * 1024 * 1024;
|
|
41
|
+
var MAX_SESSION_REPLAY_CHUNKS_PER_SESSION = 480;
|
|
42
|
+
var MOBILE_CAPTURE_INTERVAL_MS = 500;
|
|
43
|
+
var MOBILE_RECORDER_KIND = "rn-view-tree";
|
|
44
|
+
var SYNTHETIC_RRWEB_VERSION = "synthetic-1";
|
|
45
|
+
var RECORDER_VERSION = true ? "13.0.4" : "0.0.0-dev";
|
|
46
|
+
var SESSION_REPLAY_MOBILE_RECORDER_CAPABILITIES = [
|
|
47
|
+
"mobile-view-tree",
|
|
48
|
+
"mobile-touch-events",
|
|
49
|
+
"custom-events",
|
|
50
|
+
"traits",
|
|
51
|
+
"tags",
|
|
52
|
+
"visibility",
|
|
53
|
+
"visitor-id",
|
|
54
|
+
"route-events",
|
|
55
|
+
"js-errors"
|
|
56
|
+
];
|
|
57
|
+
var SessionReplayMaskingMode = /* @__PURE__ */ ((SessionReplayMaskingMode2) => {
|
|
58
|
+
SessionReplayMaskingMode2["MaskSensitiveInputsOnly"] = "MaskSensitiveInputsOnly";
|
|
59
|
+
SessionReplayMaskingMode2["MaskInputsOnly"] = "MaskInputsOnly";
|
|
60
|
+
SessionReplayMaskingMode2["MaskAllText"] = "MaskAllText";
|
|
61
|
+
return SessionReplayMaskingMode2;
|
|
62
|
+
})(SessionReplayMaskingMode || {});
|
|
63
|
+
var SessionReplayConsentMode = /* @__PURE__ */ ((SessionReplayConsentMode2) => {
|
|
64
|
+
SessionReplayConsentMode2["RequireExplicit"] = "RequireExplicit";
|
|
65
|
+
SessionReplayConsentMode2["NotRequired"] = "NotRequired";
|
|
66
|
+
return SessionReplayConsentMode2;
|
|
67
|
+
})(SessionReplayConsentMode || {});
|
|
68
|
+
var SessionReplayCaptureTrigger = /* @__PURE__ */ ((SessionReplayCaptureTrigger2) => {
|
|
69
|
+
SessionReplayCaptureTrigger2["Always"] = "Always";
|
|
70
|
+
SessionReplayCaptureTrigger2["OnErrorOrFrustration"] = "OnErrorOrFrustration";
|
|
71
|
+
return SessionReplayCaptureTrigger2;
|
|
72
|
+
})(SessionReplayCaptureTrigger || {});
|
|
73
|
+
var SessionReplayTriggerReason = /* @__PURE__ */ ((SessionReplayTriggerReason2) => {
|
|
74
|
+
SessionReplayTriggerReason2["Error"] = "error";
|
|
75
|
+
SessionReplayTriggerReason2["Frustration"] = "frustration";
|
|
76
|
+
SessionReplayTriggerReason2["Sampled"] = "sampled";
|
|
77
|
+
SessionReplayTriggerReason2["Manual"] = "manual";
|
|
78
|
+
SessionReplayTriggerReason2["Performance"] = "performance";
|
|
79
|
+
return SessionReplayTriggerReason2;
|
|
80
|
+
})(SessionReplayTriggerReason || {});
|
|
81
|
+
var SessionReplayFidelityNotice = /* @__PURE__ */ ((SessionReplayFidelityNotice2) => {
|
|
82
|
+
SessionReplayFidelityNotice2["MobileImagesOpaque"] = "mobile-images-opaque";
|
|
83
|
+
SessionReplayFidelityNotice2["MobileWebViewOpaque"] = "mobile-webview-opaque";
|
|
84
|
+
SessionReplayFidelityNotice2["MobileCanvasOpaque"] = "mobile-canvas-opaque";
|
|
85
|
+
SessionReplayFidelityNotice2["MobileAnimationSampled"] = "mobile-animation-sampled";
|
|
86
|
+
SessionReplayFidelityNotice2["BufferOverflow"] = "buffer-overflow";
|
|
87
|
+
SessionReplayFidelityNotice2["SnapshotTooLarge"] = "snapshot-too-large";
|
|
88
|
+
return SessionReplayFidelityNotice2;
|
|
89
|
+
})(SessionReplayFidelityNotice || {});
|
|
90
|
+
var ROUTE_CUSTOM_EVENT_TAG = "oneuptime.route";
|
|
91
|
+
var ERROR_CUSTOM_EVENT_TAG = "oneuptime.error";
|
|
92
|
+
var CUSTOM_EVENT_TAG = "oneuptime.custom";
|
|
93
|
+
var IDENTIFY_CUSTOM_EVENT_TAG = "oneuptime.identify";
|
|
94
|
+
var TAGS_CUSTOM_EVENT_TAG = "oneuptime.tags";
|
|
95
|
+
var VISIBILITY_CUSTOM_EVENT_TAG = "oneuptime.visibility";
|
|
96
|
+
var TOUCH_CUSTOM_EVENT_TAG = "oneuptime.touch";
|
|
97
|
+
|
|
98
|
+
// src/Sanitize.ts
|
|
99
|
+
var MAX_TRAIT_KEYS = 20;
|
|
100
|
+
var MAX_TRAIT_KEY_LENGTH = 40;
|
|
101
|
+
var MAX_TRAIT_VALUE_LENGTH = 200;
|
|
102
|
+
var MAX_TAG_KEYS = 20;
|
|
103
|
+
var MAX_TAG_KEY_LENGTH = 32;
|
|
104
|
+
var MAX_TAG_VALUE_LENGTH = 128;
|
|
105
|
+
var MAX_CUSTOM_EVENT_NAME_LENGTH = 64;
|
|
106
|
+
var MAX_CUSTOM_EVENT_PROPERTY_KEYS = 20;
|
|
107
|
+
var MAX_CAPTURE_REASON_LENGTH = 80;
|
|
108
|
+
var MAX_USER_REFERENCE_LENGTH = 200;
|
|
109
|
+
var BLOCKED_KEYS = /* @__PURE__ */ new Set([
|
|
110
|
+
"__proto__",
|
|
111
|
+
"constructor",
|
|
112
|
+
"prototype"
|
|
113
|
+
]);
|
|
114
|
+
var UUID_SEGMENT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
115
|
+
var OBJECT_ID_SEGMENT = /^[0-9a-f]{24}$/iu;
|
|
116
|
+
var EMAIL_SEGMENT = /^[^@\s/]+@[^@\s/]+\.[^@\s/]+$/u;
|
|
117
|
+
var LONG_DIGIT_SEGMENT = /^\d{9,}$/u;
|
|
118
|
+
var OPAQUE_TOKEN_SEGMENT = /^[A-Za-z0-9_-]{32,}$/u;
|
|
119
|
+
var ABSOLUTE_URL_PATTERN = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//;
|
|
120
|
+
var CONTROL_CHARACTER_PATTERN = /[\u0000-\u001F\u007F]/gu;
|
|
121
|
+
function truncate(value, length) {
|
|
122
|
+
return value.slice(0, Math.max(0, length));
|
|
123
|
+
}
|
|
124
|
+
function stringifyPrimitive(value) {
|
|
125
|
+
switch (typeof value) {
|
|
126
|
+
case "string":
|
|
127
|
+
return value;
|
|
128
|
+
case "number":
|
|
129
|
+
return Number.isFinite(value) ? String(value) : null;
|
|
130
|
+
case "boolean":
|
|
131
|
+
return value ? "true" : "false";
|
|
132
|
+
default:
|
|
133
|
+
return value === null ? "null" : null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function sanitizeStringMap(value, limits) {
|
|
137
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
138
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
for (const [rawKey, rawValue] of Object.entries(
|
|
142
|
+
value
|
|
143
|
+
)) {
|
|
144
|
+
if (Object.keys(result).length >= limits.keys) {
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
const key = truncate(rawKey.trim(), limits.keyLength);
|
|
148
|
+
const stringValue = stringifyPrimitive(rawValue);
|
|
149
|
+
if (!key || BLOCKED_KEYS.has(key) || stringValue === null) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
result[key] = truncate(stringValue, limits.valueLength);
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
function sanitizeTraits(value) {
|
|
157
|
+
return sanitizeStringMap(value, {
|
|
158
|
+
keys: MAX_TRAIT_KEYS,
|
|
159
|
+
keyLength: MAX_TRAIT_KEY_LENGTH,
|
|
160
|
+
valueLength: MAX_TRAIT_VALUE_LENGTH
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function sanitizeTags(value) {
|
|
164
|
+
return sanitizeStringMap(value, {
|
|
165
|
+
keys: MAX_TAG_KEYS,
|
|
166
|
+
keyLength: MAX_TAG_KEY_LENGTH,
|
|
167
|
+
valueLength: MAX_TAG_VALUE_LENGTH
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function sanitizeEventProperties(value) {
|
|
171
|
+
return sanitizeStringMap(value, {
|
|
172
|
+
keys: MAX_CUSTOM_EVENT_PROPERTY_KEYS,
|
|
173
|
+
keyLength: MAX_TRAIT_KEY_LENGTH,
|
|
174
|
+
valueLength: MAX_TRAIT_VALUE_LENGTH
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function maskTextValue(value) {
|
|
178
|
+
if (!value || value.trim().length === 0) {
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
const length = value.trim().length;
|
|
182
|
+
const maskedLength = length <= 8 ? 4 : length <= 32 ? 16 : 40;
|
|
183
|
+
return "\u2022".repeat(maskedLength);
|
|
184
|
+
}
|
|
185
|
+
function maskStringMapValues(values) {
|
|
186
|
+
const masked = /* @__PURE__ */ Object.create(null);
|
|
187
|
+
for (const [key, value] of Object.entries(values)) {
|
|
188
|
+
masked[key] = maskTextValue(value);
|
|
189
|
+
}
|
|
190
|
+
return masked;
|
|
191
|
+
}
|
|
192
|
+
function sanitizeRoute(route) {
|
|
193
|
+
if (typeof route !== "string") {
|
|
194
|
+
return "/";
|
|
195
|
+
}
|
|
196
|
+
let path = route.trim();
|
|
197
|
+
if (!path) {
|
|
198
|
+
return "/";
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
if (ABSOLUTE_URL_PATTERN.test(path)) {
|
|
202
|
+
path = new URL(path).pathname;
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
205
|
+
path = "/";
|
|
206
|
+
}
|
|
207
|
+
path = path.split(/[?#]/u, 1)[0] || "/";
|
|
208
|
+
if (!path.startsWith("/")) {
|
|
209
|
+
path = `/${path}`;
|
|
210
|
+
}
|
|
211
|
+
path = path.split("/").map((segment) => {
|
|
212
|
+
let decoded = segment;
|
|
213
|
+
try {
|
|
214
|
+
decoded = decodeURIComponent(segment);
|
|
215
|
+
} catch {
|
|
216
|
+
decoded = segment;
|
|
217
|
+
}
|
|
218
|
+
return UUID_SEGMENT.test(decoded) || OBJECT_ID_SEGMENT.test(decoded) || EMAIL_SEGMENT.test(decoded) || LONG_DIGIT_SEGMENT.test(decoded) || OPAQUE_TOKEN_SEGMENT.test(decoded) ? "[redacted]" : segment;
|
|
219
|
+
}).join("/");
|
|
220
|
+
return truncate(path.replace(CONTROL_CHARACTER_PATTERN, ""), 2048);
|
|
221
|
+
}
|
|
222
|
+
function toAppUrl(mobileAppIdentifier, route) {
|
|
223
|
+
return `app://${mobileAppIdentifier}${sanitizeRoute(route)}`;
|
|
224
|
+
}
|
|
225
|
+
function emptySignalCounts() {
|
|
226
|
+
return {
|
|
227
|
+
errorCount: 0,
|
|
228
|
+
rageClickCount: 0,
|
|
229
|
+
deadClickCount: 0,
|
|
230
|
+
errorClickCount: 0,
|
|
231
|
+
refreshRageCount: 0,
|
|
232
|
+
routeCount: 0,
|
|
233
|
+
clickCount: 0,
|
|
234
|
+
customEventCount: 0
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function byteLength(value) {
|
|
238
|
+
if (typeof TextEncoder !== "undefined") {
|
|
239
|
+
return new TextEncoder().encode(value).byteLength;
|
|
240
|
+
}
|
|
241
|
+
return unescape(encodeURIComponent(value)).length;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/ChunkBuffer.ts
|
|
245
|
+
var ReplayChunkBuffer = class {
|
|
246
|
+
constructor() {
|
|
247
|
+
__publicField(this, "events", []);
|
|
248
|
+
__publicField(this, "approximateBytes", 2);
|
|
249
|
+
__publicField(this, "startUnixMs", 0);
|
|
250
|
+
__publicField(this, "endUnixMs", 0);
|
|
251
|
+
__publicField(this, "hasFullSnapshot", false);
|
|
252
|
+
__publicField(this, "routes", []);
|
|
253
|
+
__publicField(this, "fidelity", /* @__PURE__ */ new Set());
|
|
254
|
+
__publicField(this, "signals", emptySignalCounts());
|
|
255
|
+
__publicField(this, "droppedEvents", 0);
|
|
256
|
+
}
|
|
257
|
+
append(event, options) {
|
|
258
|
+
let serialized;
|
|
259
|
+
try {
|
|
260
|
+
serialized = JSON.stringify(event);
|
|
261
|
+
} catch {
|
|
262
|
+
this.droppedEvents += 1;
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
const eventBytes = byteLength(serialized) + 1;
|
|
266
|
+
if (eventBytes > SESSION_REPLAY_MAX_DECOMPRESSED_FRAME_BYTES) {
|
|
267
|
+
this.droppedEvents += 1;
|
|
268
|
+
this.fidelity.add("snapshot-too-large" /* SnapshotTooLarge */);
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
this.events.push(event);
|
|
272
|
+
this.approximateBytes += eventBytes;
|
|
273
|
+
this.startUnixMs = this.startUnixMs === 0 ? event.timestamp : this.startUnixMs;
|
|
274
|
+
this.endUnixMs = Math.max(this.endUnixMs, event.timestamp);
|
|
275
|
+
if (event.type === 2 /* FullSnapshot */) {
|
|
276
|
+
this.hasFullSnapshot = true;
|
|
277
|
+
}
|
|
278
|
+
if (options?.route && !this.routes.includes(options.route)) {
|
|
279
|
+
this.routes.push(options.route);
|
|
280
|
+
}
|
|
281
|
+
for (const notice of options?.fidelityNotices ?? []) {
|
|
282
|
+
this.fidelity.add(notice);
|
|
283
|
+
}
|
|
284
|
+
if (options?.signal) {
|
|
285
|
+
const current = this.signals[options.signal] ?? 0;
|
|
286
|
+
this.signals[options.signal] = current + 1;
|
|
287
|
+
}
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
appendMany(events, options) {
|
|
291
|
+
let accepted = 0;
|
|
292
|
+
for (const event of events) {
|
|
293
|
+
if (this.append(event, options)) {
|
|
294
|
+
accepted += 1;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return accepted;
|
|
298
|
+
}
|
|
299
|
+
shouldFlush() {
|
|
300
|
+
return this.approximateBytes >= SESSION_REPLAY_FLUSH_BYTES;
|
|
301
|
+
}
|
|
302
|
+
isEmpty() {
|
|
303
|
+
return this.events.length === 0;
|
|
304
|
+
}
|
|
305
|
+
take() {
|
|
306
|
+
if (this.events.length === 0) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
const payload = JSON.stringify(this.events);
|
|
310
|
+
const segment = {
|
|
311
|
+
payload,
|
|
312
|
+
eventCount: this.events.length,
|
|
313
|
+
startUnixMs: this.startUnixMs,
|
|
314
|
+
endUnixMs: this.endUnixMs,
|
|
315
|
+
hasFullSnapshot: this.hasFullSnapshot,
|
|
316
|
+
routes: this.routes.slice(),
|
|
317
|
+
signals: { ...this.signals },
|
|
318
|
+
fidelityNotices: Array.from(this.fidelity),
|
|
319
|
+
droppedEvents: this.droppedEvents
|
|
320
|
+
};
|
|
321
|
+
this.reset();
|
|
322
|
+
return segment;
|
|
323
|
+
}
|
|
324
|
+
clear() {
|
|
325
|
+
this.reset();
|
|
326
|
+
}
|
|
327
|
+
reset() {
|
|
328
|
+
this.events = [];
|
|
329
|
+
this.approximateBytes = 2;
|
|
330
|
+
this.startUnixMs = 0;
|
|
331
|
+
this.endUnixMs = 0;
|
|
332
|
+
this.hasFullSnapshot = false;
|
|
333
|
+
this.routes = [];
|
|
334
|
+
this.fidelity.clear();
|
|
335
|
+
this.signals = emptySignalCounts();
|
|
336
|
+
this.droppedEvents = 0;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
var RollingReplayBuffer = class {
|
|
340
|
+
constructor() {
|
|
341
|
+
__publicField(this, "segments", []);
|
|
342
|
+
__publicField(this, "bytes", 0);
|
|
343
|
+
__publicField(this, "overflowed", false);
|
|
344
|
+
}
|
|
345
|
+
push(segment, nowUnixMs) {
|
|
346
|
+
this.segments.push(segment);
|
|
347
|
+
this.bytes += byteLength(segment.payload);
|
|
348
|
+
const cutoff = nowUnixMs - SESSION_REPLAY_ROLLING_BUFFER_MS;
|
|
349
|
+
while (this.segments.length > 0 && (this.bytes > SESSION_REPLAY_ROLLING_BUFFER_BYTES || (this.segments[0]?.endUnixMs ?? nowUnixMs) < cutoff)) {
|
|
350
|
+
const removed = this.segments.shift();
|
|
351
|
+
if (removed) {
|
|
352
|
+
this.bytes -= byteLength(removed.payload);
|
|
353
|
+
this.overflowed = true;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
drain() {
|
|
358
|
+
const segments = this.segments;
|
|
359
|
+
if (this.overflowed && segments[0]) {
|
|
360
|
+
const notices = new Set(
|
|
361
|
+
segments[0].fidelityNotices
|
|
362
|
+
);
|
|
363
|
+
notices.add("buffer-overflow" /* BufferOverflow */);
|
|
364
|
+
segments[0] = {
|
|
365
|
+
...segments[0],
|
|
366
|
+
fidelityNotices: Array.from(notices)
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
this.clear();
|
|
370
|
+
return segments;
|
|
371
|
+
}
|
|
372
|
+
clear() {
|
|
373
|
+
this.segments = [];
|
|
374
|
+
this.bytes = 0;
|
|
375
|
+
this.overflowed = false;
|
|
376
|
+
}
|
|
377
|
+
get length() {
|
|
378
|
+
return this.segments.length;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// src/Config.ts
|
|
383
|
+
var CONFIG_FETCH_TIMEOUT_MS = 5e3;
|
|
384
|
+
var MOBILE_APP_IDENTIFIER_LABEL_PATTERN = /^[a-z0-9_-]+$/u;
|
|
385
|
+
var WHITESPACE_PATTERN = /\s/u;
|
|
386
|
+
var CONTROL_CHARACTER_PATTERN2 = /[\u0000-\u001F\u007F]/u;
|
|
387
|
+
function normalizeMobileAppIdentifier(value) {
|
|
388
|
+
if (typeof value !== "string") {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
const identifier = value.trim().toLowerCase();
|
|
392
|
+
if (!identifier || identifier.length > 255 || WHITESPACE_PATTERN.test(identifier)) {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
const labels = identifier.split(".");
|
|
396
|
+
if (labels.length < 2) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
for (const label of labels) {
|
|
400
|
+
if (!label || !MOBILE_APP_IDENTIFIER_LABEL_PATTERN.test(label) || label.startsWith("-") || label.endsWith("-") || label.startsWith("_") || label.endsWith("_")) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return identifier;
|
|
405
|
+
}
|
|
406
|
+
function normalizeIngestHost(value) {
|
|
407
|
+
if (typeof value !== "string") {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
const parsed = new URL(value.trim());
|
|
412
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:" || parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
return parsed.origin;
|
|
416
|
+
} catch {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function normalizeRumAppIdentifier(value) {
|
|
421
|
+
if (typeof value !== "string") {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
const identifier = value.trim();
|
|
425
|
+
if (!identifier || identifier.length > 255 || CONTROL_CHARACTER_PATTERN2.test(identifier)) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
return identifier;
|
|
429
|
+
}
|
|
430
|
+
function defaultFetch(input, init) {
|
|
431
|
+
const fetchRef = globalThis.fetch;
|
|
432
|
+
if (!fetchRef) {
|
|
433
|
+
return Promise.reject(new Error("fetch-unavailable"));
|
|
434
|
+
}
|
|
435
|
+
return fetchRef(input, init);
|
|
436
|
+
}
|
|
437
|
+
function validateStartOptions(options) {
|
|
438
|
+
if (!options || typeof options !== "object") {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
const host = normalizeIngestHost(options.host);
|
|
442
|
+
const token = typeof options.token === "string" ? options.token.trim() : "";
|
|
443
|
+
const appIdentifier = normalizeRumAppIdentifier(
|
|
444
|
+
options.appIdentifier
|
|
445
|
+
);
|
|
446
|
+
const mobileAppIdentifier = normalizeMobileAppIdentifier(
|
|
447
|
+
options.mobileAppIdentifier
|
|
448
|
+
);
|
|
449
|
+
const rawUserRef = typeof options.userRef === "string" ? options.userRef.trim() : "";
|
|
450
|
+
const userRef = rawUserRef ? rawUserRef.slice(0, SESSION_REPLAY_MAX_USER_REF_LENGTH) : void 0;
|
|
451
|
+
if (!host || !token || !appIdentifier || !mobileAppIdentifier) {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
const validated = {
|
|
455
|
+
...options,
|
|
456
|
+
host,
|
|
457
|
+
token,
|
|
458
|
+
appIdentifier,
|
|
459
|
+
mobileAppIdentifier,
|
|
460
|
+
fetch: options.fetch ?? defaultFetch
|
|
461
|
+
};
|
|
462
|
+
if (userRef) {
|
|
463
|
+
validated.userRef = userRef;
|
|
464
|
+
} else {
|
|
465
|
+
delete validated.userRef;
|
|
466
|
+
}
|
|
467
|
+
return validated;
|
|
468
|
+
}
|
|
469
|
+
function mobileRequestHeaders(options, includeUserRef = false) {
|
|
470
|
+
const headers = {
|
|
471
|
+
[AUTH_TOKEN_HEADER]: options.token,
|
|
472
|
+
[SESSION_REPLAY_APP_IDENTIFIER_HEADER]: options.appIdentifier,
|
|
473
|
+
[SESSION_REPLAY_RECORDER_KIND_HEADER]: MOBILE_RECORDER_KIND,
|
|
474
|
+
[SESSION_REPLAY_MOBILE_APP_IDENTIFIER_HEADER]: options.mobileAppIdentifier
|
|
475
|
+
};
|
|
476
|
+
if (includeUserRef && options.userRef) {
|
|
477
|
+
try {
|
|
478
|
+
headers[SESSION_REPLAY_USER_REF_HEADER] = encodeURIComponent(
|
|
479
|
+
options.userRef.slice(0, SESSION_REPLAY_MAX_USER_REF_LENGTH)
|
|
480
|
+
);
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return headers;
|
|
485
|
+
}
|
|
486
|
+
function failClosed(reason) {
|
|
487
|
+
return {
|
|
488
|
+
enabled: false,
|
|
489
|
+
captureTrigger: "OnErrorOrFrustration" /* OnErrorOrFrustration */,
|
|
490
|
+
consentMode: "RequireExplicit" /* RequireExplicit */,
|
|
491
|
+
maskingMode: "MaskAllText" /* MaskAllText */,
|
|
492
|
+
samplePercentage: 0,
|
|
493
|
+
captureUserIdentity: false,
|
|
494
|
+
configEpoch: 0,
|
|
495
|
+
isTargeted: false,
|
|
496
|
+
disabledReason: reason
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function normalizeReplayConfig(value) {
|
|
500
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
501
|
+
return failClosed("invalid-config");
|
|
502
|
+
}
|
|
503
|
+
const config = value;
|
|
504
|
+
const captureTrigger = config.captureTrigger === "Always" /* Always */ ? "Always" /* Always */ : "OnErrorOrFrustration" /* OnErrorOrFrustration */;
|
|
505
|
+
const consentMode = config.consentMode === "NotRequired" /* NotRequired */ ? "NotRequired" /* NotRequired */ : "RequireExplicit" /* RequireExplicit */;
|
|
506
|
+
const samplePercentage = typeof config.samplePercentage === "number" && Number.isFinite(config.samplePercentage) && config.samplePercentage >= 0 && config.samplePercentage <= 100 ? config.samplePercentage : 0;
|
|
507
|
+
const enabled = config.enabled === true && config.directive === "continue";
|
|
508
|
+
const resolved = {
|
|
509
|
+
enabled,
|
|
510
|
+
captureTrigger,
|
|
511
|
+
consentMode,
|
|
512
|
+
/* Mobile never relaxes text masking, even if web policy is looser. */
|
|
513
|
+
maskingMode: "MaskAllText" /* MaskAllText */,
|
|
514
|
+
samplePercentage,
|
|
515
|
+
captureUserIdentity: config.captureUserIdentity === true,
|
|
516
|
+
configEpoch: typeof config.configEpoch === "number" && Number.isSafeInteger(config.configEpoch) && config.configEpoch >= 0 ? config.configEpoch : 0,
|
|
517
|
+
isTargeted: config.isTargeted === true
|
|
518
|
+
};
|
|
519
|
+
if (typeof config.disabledReason === "string") {
|
|
520
|
+
resolved.disabledReason = config.disabledReason.slice(0, 100);
|
|
521
|
+
} else if (!enabled) {
|
|
522
|
+
resolved.disabledReason = "disabled";
|
|
523
|
+
}
|
|
524
|
+
return resolved;
|
|
525
|
+
}
|
|
526
|
+
async function fetchReplayConfig(options, includeUserRef = false, revalidate = false) {
|
|
527
|
+
const controller = typeof AbortController === "undefined" ? null : new AbortController();
|
|
528
|
+
let timeout = null;
|
|
529
|
+
try {
|
|
530
|
+
const request = (async () => {
|
|
531
|
+
const headers = {
|
|
532
|
+
...mobileRequestHeaders(options, includeUserRef),
|
|
533
|
+
Accept: "application/json"
|
|
534
|
+
};
|
|
535
|
+
if (revalidate) {
|
|
536
|
+
headers["Cache-Control"] = "no-cache";
|
|
537
|
+
headers["Pragma"] = "no-cache";
|
|
538
|
+
}
|
|
539
|
+
const requestInit = {
|
|
540
|
+
method: "GET",
|
|
541
|
+
headers
|
|
542
|
+
};
|
|
543
|
+
if (controller) {
|
|
544
|
+
requestInit.signal = controller.signal;
|
|
545
|
+
}
|
|
546
|
+
const response = await options.fetch(
|
|
547
|
+
`${options.host}${CONFIG_PATH}`,
|
|
548
|
+
requestInit
|
|
549
|
+
);
|
|
550
|
+
if (!response.ok) {
|
|
551
|
+
return failClosed(`config-http-${response.status}`);
|
|
552
|
+
}
|
|
553
|
+
return normalizeReplayConfig(await response.json());
|
|
554
|
+
})();
|
|
555
|
+
const deadline = new Promise(
|
|
556
|
+
(_resolve, reject) => {
|
|
557
|
+
timeout = setTimeout(() => {
|
|
558
|
+
controller?.abort();
|
|
559
|
+
reject(new Error("config-fetch-timeout"));
|
|
560
|
+
}, CONFIG_FETCH_TIMEOUT_MS);
|
|
561
|
+
}
|
|
562
|
+
);
|
|
563
|
+
return await Promise.race([request, deadline]);
|
|
564
|
+
} catch {
|
|
565
|
+
return failClosed("config-fetch-failed");
|
|
566
|
+
} finally {
|
|
567
|
+
if (timeout) {
|
|
568
|
+
clearTimeout(timeout);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/ViewTreeSerializer.ts
|
|
574
|
+
var RRWEB_DOCUMENT_NODE_ID = 1;
|
|
575
|
+
var RRWEB_HTML_NODE_ID = 3;
|
|
576
|
+
var RRWEB_HEAD_NODE_ID = 4;
|
|
577
|
+
var RRWEB_BODY_NODE_ID = 5;
|
|
578
|
+
var FIRST_VIEW_NODE_ID = 10;
|
|
579
|
+
var MAX_VIEW_TREE_DEPTH = 64;
|
|
580
|
+
var MAX_VIEW_TREE_NODES = 5e3;
|
|
581
|
+
var MAX_GEOMETRY_VALUE = 1e5;
|
|
582
|
+
var SAFE_KINDS = /* @__PURE__ */ new Set([
|
|
583
|
+
"view",
|
|
584
|
+
"scroll",
|
|
585
|
+
"button",
|
|
586
|
+
"text",
|
|
587
|
+
"input",
|
|
588
|
+
"image",
|
|
589
|
+
"webview",
|
|
590
|
+
"canvas",
|
|
591
|
+
"masked",
|
|
592
|
+
"unknown"
|
|
593
|
+
]);
|
|
594
|
+
function finiteGeometry(value) {
|
|
595
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
596
|
+
return 0;
|
|
597
|
+
}
|
|
598
|
+
return Math.min(MAX_GEOMETRY_VALUE, Math.max(-MAX_GEOMETRY_VALUE, value));
|
|
599
|
+
}
|
|
600
|
+
function safeKind(value) {
|
|
601
|
+
if (typeof value === "string" && SAFE_KINDS.has(value)) {
|
|
602
|
+
return value;
|
|
603
|
+
}
|
|
604
|
+
return "unknown";
|
|
605
|
+
}
|
|
606
|
+
function geometryStyle(node) {
|
|
607
|
+
const x2 = finiteGeometry(node.x);
|
|
608
|
+
const y = finiteGeometry(node.y);
|
|
609
|
+
const width = Math.max(0, finiteGeometry(node.width));
|
|
610
|
+
const height = Math.max(0, finiteGeometry(node.height));
|
|
611
|
+
const style = [
|
|
612
|
+
"position:absolute",
|
|
613
|
+
`left:${x2}px`,
|
|
614
|
+
`top:${y}px`,
|
|
615
|
+
`width:${width}px`,
|
|
616
|
+
`height:${height}px`,
|
|
617
|
+
"box-sizing:border-box",
|
|
618
|
+
"overflow:hidden",
|
|
619
|
+
"outline:1px solid rgba(148,163,184,0.18)"
|
|
620
|
+
];
|
|
621
|
+
const backgroundColor = safeColor(node.backgroundColor);
|
|
622
|
+
const borderColor = safeColor(node.borderColor);
|
|
623
|
+
const borderWidth = clampedStyleNumber(node.borderWidth, 0, 100, 0);
|
|
624
|
+
const borderRadius = clampedStyleNumber(
|
|
625
|
+
node.borderRadius,
|
|
626
|
+
0,
|
|
627
|
+
MAX_GEOMETRY_VALUE,
|
|
628
|
+
0
|
|
629
|
+
);
|
|
630
|
+
const opacity = clampedStyleNumber(node.opacity, 0, 1, 1);
|
|
631
|
+
const zIndex = clampedStyleNumber(node.zIndex, -1e4, 1e4, 0);
|
|
632
|
+
if (backgroundColor) {
|
|
633
|
+
style.push(`background-color:${backgroundColor}`);
|
|
634
|
+
}
|
|
635
|
+
if (borderColor && borderWidth > 0) {
|
|
636
|
+
style.push(`border:${borderWidth}px solid ${borderColor}`);
|
|
637
|
+
}
|
|
638
|
+
if (borderRadius > 0) {
|
|
639
|
+
style.push(`border-radius:${borderRadius}px`);
|
|
640
|
+
}
|
|
641
|
+
if (opacity < 1) {
|
|
642
|
+
style.push(`opacity:${opacity}`);
|
|
643
|
+
}
|
|
644
|
+
if (zIndex !== 0) {
|
|
645
|
+
style.push(`z-index:${zIndex}`);
|
|
646
|
+
}
|
|
647
|
+
return style.join(";");
|
|
648
|
+
}
|
|
649
|
+
var HEX_COLOR_PATTERN = /^#[0-9a-f]{6}([0-9a-f]{2})?$/u;
|
|
650
|
+
function safeColor(value) {
|
|
651
|
+
if (typeof value !== "string") {
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
const normalized = value.trim().toLowerCase();
|
|
655
|
+
return HEX_COLOR_PATTERN.test(normalized) ? normalized : null;
|
|
656
|
+
}
|
|
657
|
+
function clampedStyleNumber(value, minimum, maximum, fallback) {
|
|
658
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
659
|
+
return fallback;
|
|
660
|
+
}
|
|
661
|
+
return Math.max(minimum, Math.min(maximum, value));
|
|
662
|
+
}
|
|
663
|
+
function cloneNode(node) {
|
|
664
|
+
return {
|
|
665
|
+
...node,
|
|
666
|
+
attributes: { ...node.attributes },
|
|
667
|
+
childNodes: node.childNodes.map(cloneNode)
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
function sameAttributes(left, right) {
|
|
671
|
+
const leftKeys = Object.keys(left);
|
|
672
|
+
const rightKeys = Object.keys(right);
|
|
673
|
+
return leftKeys.length === rightKeys.length && leftKeys.every((key) => {
|
|
674
|
+
return left[key] === right[key];
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
var ViewTreeSerializer = class {
|
|
678
|
+
constructor() {
|
|
679
|
+
__publicField(this, "nodeIds", /* @__PURE__ */ new Map());
|
|
680
|
+
__publicField(this, "nextNodeId", FIRST_VIEW_NODE_ID);
|
|
681
|
+
__publicField(this, "previous", null);
|
|
682
|
+
}
|
|
683
|
+
capture(input, options) {
|
|
684
|
+
const shouldTakeFullSnapshot = !this.previous || options.forceFullSnapshot === true;
|
|
685
|
+
if (options.forceFullSnapshot === true && this.previous) {
|
|
686
|
+
this.previous = null;
|
|
687
|
+
this.nodeIds.clear();
|
|
688
|
+
this.nextNodeId = FIRST_VIEW_NODE_ID;
|
|
689
|
+
}
|
|
690
|
+
const current = this.build(input);
|
|
691
|
+
const events = shouldTakeFullSnapshot ? this.fullSnapshot(current, options) : this.mutations(
|
|
692
|
+
this.previous,
|
|
693
|
+
current,
|
|
694
|
+
options.timestamp
|
|
695
|
+
);
|
|
696
|
+
this.previous = current;
|
|
697
|
+
for (const key of this.nodeIds.keys()) {
|
|
698
|
+
if (!current.records.has(key)) {
|
|
699
|
+
this.nodeIds.delete(key);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return {
|
|
703
|
+
events,
|
|
704
|
+
hasFullSnapshot: shouldTakeFullSnapshot,
|
|
705
|
+
fidelityNotices: Array.from(current.fidelity),
|
|
706
|
+
droppedNodes: current.droppedNodes
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
reset() {
|
|
710
|
+
this.previous = null;
|
|
711
|
+
this.nodeIds.clear();
|
|
712
|
+
this.nextNodeId = FIRST_VIEW_NODE_ID;
|
|
713
|
+
}
|
|
714
|
+
build(input) {
|
|
715
|
+
const records = /* @__PURE__ */ new Map();
|
|
716
|
+
const byId = /* @__PURE__ */ new Map();
|
|
717
|
+
const fidelity = /* @__PURE__ */ new Set([
|
|
718
|
+
"mobile-animation-sampled" /* MobileAnimationSampled */
|
|
719
|
+
]);
|
|
720
|
+
let visited = 0;
|
|
721
|
+
let droppedNodes = typeof input.truncatedNodes === "number" && Number.isSafeInteger(input.truncatedNodes) && input.truncatedNodes > 0 ? Math.min(input.truncatedNodes, 1e6) : 0;
|
|
722
|
+
const visit = (candidate, parentId, path, depth) => {
|
|
723
|
+
if (depth > MAX_VIEW_TREE_DEPTH || visited >= MAX_VIEW_TREE_NODES || !candidate || typeof candidate !== "object") {
|
|
724
|
+
droppedNodes += 1;
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
visited += 1;
|
|
728
|
+
const kind = candidate.masked ? "masked" : safeKind(candidate.kind);
|
|
729
|
+
const key = `${typeof candidate.nativeId}:${String(
|
|
730
|
+
candidate.nativeId
|
|
731
|
+
)}:${path}`;
|
|
732
|
+
let id = this.nodeIds.get(key);
|
|
733
|
+
if (id === void 0) {
|
|
734
|
+
id = this.nextNodeId;
|
|
735
|
+
this.nextNodeId += 1;
|
|
736
|
+
this.nodeIds.set(key, id);
|
|
737
|
+
}
|
|
738
|
+
const attributes = {
|
|
739
|
+
style: geometryStyle(candidate),
|
|
740
|
+
"data-oneuptime-mobile-view": kind
|
|
741
|
+
};
|
|
742
|
+
const isOpaque = candidate.opaque === true || kind === "image" || kind === "webview" || kind === "canvas" || kind === "masked";
|
|
743
|
+
if (isOpaque) {
|
|
744
|
+
attributes["data-oneuptime-replay-placeholder"] = kind;
|
|
745
|
+
if (!safeColor(candidate.backgroundColor)) {
|
|
746
|
+
attributes["style"] += ";background-color:#94a3b833";
|
|
747
|
+
}
|
|
748
|
+
} else if (kind === "text" || kind === "input") {
|
|
749
|
+
attributes["style"] += ";background-color:#94a3b826";
|
|
750
|
+
}
|
|
751
|
+
switch (kind) {
|
|
752
|
+
case "image":
|
|
753
|
+
fidelity.add("mobile-images-opaque" /* MobileImagesOpaque */);
|
|
754
|
+
break;
|
|
755
|
+
case "webview":
|
|
756
|
+
fidelity.add("mobile-webview-opaque" /* MobileWebViewOpaque */);
|
|
757
|
+
break;
|
|
758
|
+
case "canvas":
|
|
759
|
+
fidelity.add("mobile-canvas-opaque" /* MobileCanvasOpaque */);
|
|
760
|
+
break;
|
|
761
|
+
default:
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
764
|
+
const element = {
|
|
765
|
+
type: 2,
|
|
766
|
+
tagName: "div",
|
|
767
|
+
attributes,
|
|
768
|
+
childNodes: [],
|
|
769
|
+
id
|
|
770
|
+
};
|
|
771
|
+
const record = {
|
|
772
|
+
key,
|
|
773
|
+
id,
|
|
774
|
+
parentId,
|
|
775
|
+
attributes,
|
|
776
|
+
node: element
|
|
777
|
+
};
|
|
778
|
+
records.set(key, record);
|
|
779
|
+
byId.set(id, record);
|
|
780
|
+
if (!isOpaque && Array.isArray(candidate.children)) {
|
|
781
|
+
candidate.children.forEach(
|
|
782
|
+
(child, index) => {
|
|
783
|
+
const serialized = visit(
|
|
784
|
+
child,
|
|
785
|
+
id,
|
|
786
|
+
`${path}.${index}`,
|
|
787
|
+
depth + 1
|
|
788
|
+
);
|
|
789
|
+
if (serialized) {
|
|
790
|
+
element.childNodes.push(serialized);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
return element;
|
|
796
|
+
};
|
|
797
|
+
const root = visit(
|
|
798
|
+
input,
|
|
799
|
+
RRWEB_BODY_NODE_ID,
|
|
800
|
+
"0",
|
|
801
|
+
0
|
|
802
|
+
) ?? {
|
|
803
|
+
type: 2,
|
|
804
|
+
tagName: "div",
|
|
805
|
+
attributes: {
|
|
806
|
+
style: "position:absolute;left:0px;top:0px;width:0px;height:0px;box-sizing:border-box;overflow:hidden",
|
|
807
|
+
"data-oneuptime-mobile-view": "unknown"
|
|
808
|
+
},
|
|
809
|
+
childNodes: [],
|
|
810
|
+
id: this.nextNodeId++
|
|
811
|
+
};
|
|
812
|
+
if (droppedNodes > 0) {
|
|
813
|
+
fidelity.add("buffer-overflow" /* BufferOverflow */);
|
|
814
|
+
}
|
|
815
|
+
return { root, records, byId, fidelity, droppedNodes };
|
|
816
|
+
}
|
|
817
|
+
fullSnapshot(current, options) {
|
|
818
|
+
const width = Math.max(0, finiteGeometry(options.viewportWidth));
|
|
819
|
+
const height = Math.max(0, finiteGeometry(options.viewportHeight));
|
|
820
|
+
return [
|
|
821
|
+
{
|
|
822
|
+
type: 4 /* Meta */,
|
|
823
|
+
data: {
|
|
824
|
+
href: options.url,
|
|
825
|
+
width,
|
|
826
|
+
height
|
|
827
|
+
},
|
|
828
|
+
timestamp: options.timestamp
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
type: 2 /* FullSnapshot */,
|
|
832
|
+
data: {
|
|
833
|
+
node: {
|
|
834
|
+
type: 0,
|
|
835
|
+
childNodes: [
|
|
836
|
+
{
|
|
837
|
+
type: 1,
|
|
838
|
+
name: "html",
|
|
839
|
+
publicId: "",
|
|
840
|
+
systemId: "",
|
|
841
|
+
id: 2
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
type: 2,
|
|
845
|
+
tagName: "html",
|
|
846
|
+
attributes: {},
|
|
847
|
+
childNodes: [
|
|
848
|
+
{
|
|
849
|
+
type: 2,
|
|
850
|
+
tagName: "head",
|
|
851
|
+
attributes: {},
|
|
852
|
+
childNodes: [],
|
|
853
|
+
id: RRWEB_HEAD_NODE_ID
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
type: 2,
|
|
857
|
+
tagName: "body",
|
|
858
|
+
attributes: {
|
|
859
|
+
style: `margin:0;position:relative;width:${width}px;height:${height}px;overflow:hidden`,
|
|
860
|
+
"data-oneuptime-recorder-kind": "rn-view-tree"
|
|
861
|
+
},
|
|
862
|
+
childNodes: [cloneNode(current.root)],
|
|
863
|
+
id: RRWEB_BODY_NODE_ID
|
|
864
|
+
}
|
|
865
|
+
],
|
|
866
|
+
id: RRWEB_HTML_NODE_ID
|
|
867
|
+
}
|
|
868
|
+
],
|
|
869
|
+
id: RRWEB_DOCUMENT_NODE_ID
|
|
870
|
+
},
|
|
871
|
+
initialOffset: { top: 0, left: 0 }
|
|
872
|
+
},
|
|
873
|
+
timestamp: options.timestamp
|
|
874
|
+
}
|
|
875
|
+
];
|
|
876
|
+
}
|
|
877
|
+
mutations(previous, current, timestamp) {
|
|
878
|
+
const additions = [];
|
|
879
|
+
const removals = [];
|
|
880
|
+
const attributes = [];
|
|
881
|
+
for (const record of current.records.values()) {
|
|
882
|
+
const before = previous.records.get(
|
|
883
|
+
record.key
|
|
884
|
+
);
|
|
885
|
+
if (!before) {
|
|
886
|
+
const parentWasAdded = record.parentId !== RRWEB_BODY_NODE_ID && !previous.byId.has(record.parentId);
|
|
887
|
+
if (!parentWasAdded) {
|
|
888
|
+
additions.push({
|
|
889
|
+
parentId: record.parentId,
|
|
890
|
+
nextId: null,
|
|
891
|
+
node: cloneNode(record.node)
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
} else if (!sameAttributes(before.attributes, record.attributes)) {
|
|
895
|
+
attributes.push({
|
|
896
|
+
id: record.id,
|
|
897
|
+
attributes: { ...record.attributes }
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
for (const record of previous.records.values()) {
|
|
902
|
+
if (!current.records.has(record.key)) {
|
|
903
|
+
const parentWasRemoved = record.parentId !== RRWEB_BODY_NODE_ID && !current.byId.has(record.parentId);
|
|
904
|
+
if (!parentWasRemoved) {
|
|
905
|
+
removals.push({ parentId: record.parentId, id: record.id });
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (additions.length === 0 && removals.length === 0 && attributes.length === 0) {
|
|
910
|
+
return [];
|
|
911
|
+
}
|
|
912
|
+
return [
|
|
913
|
+
{
|
|
914
|
+
type: 3 /* IncrementalSnapshot */,
|
|
915
|
+
data: {
|
|
916
|
+
source: 0 /* Mutation */,
|
|
917
|
+
texts: [],
|
|
918
|
+
attributes,
|
|
919
|
+
removes: removals,
|
|
920
|
+
adds: additions
|
|
921
|
+
},
|
|
922
|
+
timestamp
|
|
923
|
+
}
|
|
924
|
+
];
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
// src/Events.ts
|
|
929
|
+
var MAX_TOUCH_CLOCK_SKEW_MS = 6e4;
|
|
930
|
+
function coordinate(value) {
|
|
931
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(-1e5, Math.min(1e5, value)) : 0;
|
|
932
|
+
}
|
|
933
|
+
function sanitizeRecordedTouch(value, nowUnixMs) {
|
|
934
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
const raw = value;
|
|
938
|
+
if (raw.phase !== "start" && raw.phase !== "move" && raw.phase !== "end") {
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
const safeNowUnixMs = Number.isFinite(nowUnixMs) ? nowUnixMs : Date.now();
|
|
942
|
+
const candidateTimestamp = raw.timestamp;
|
|
943
|
+
const timestamp = typeof candidateTimestamp === "number" && Number.isFinite(candidateTimestamp) && candidateTimestamp >= safeNowUnixMs - MAX_TOUCH_CLOCK_SKEW_MS && candidateTimestamp <= safeNowUnixMs + MAX_TOUCH_CLOCK_SKEW_MS ? candidateTimestamp : safeNowUnixMs;
|
|
944
|
+
const touch = {
|
|
945
|
+
phase: raw.phase,
|
|
946
|
+
x: coordinate(raw.x),
|
|
947
|
+
y: coordinate(raw.y),
|
|
948
|
+
timestamp
|
|
949
|
+
};
|
|
950
|
+
if (typeof raw.targetTag === "number" && Number.isSafeInteger(raw.targetTag) && raw.targetTag > 0) {
|
|
951
|
+
touch.targetTag = raw.targetTag;
|
|
952
|
+
}
|
|
953
|
+
if (typeof raw.pointerId === "number" && Number.isSafeInteger(raw.pointerId) || typeof raw.pointerId === "string" && raw.pointerId.length <= 100) {
|
|
954
|
+
touch.pointerId = raw.pointerId;
|
|
955
|
+
}
|
|
956
|
+
return touch;
|
|
957
|
+
}
|
|
958
|
+
function createTouchEvent(touch) {
|
|
959
|
+
const x2 = coordinate(touch.x);
|
|
960
|
+
const y = coordinate(touch.y);
|
|
961
|
+
if (touch.phase === "move") {
|
|
962
|
+
return {
|
|
963
|
+
type: 3 /* IncrementalSnapshot */,
|
|
964
|
+
data: {
|
|
965
|
+
source: 6 /* TouchMove */,
|
|
966
|
+
positions: [
|
|
967
|
+
{
|
|
968
|
+
x: x2,
|
|
969
|
+
y,
|
|
970
|
+
id: RRWEB_BODY_NODE_ID,
|
|
971
|
+
timeOffset: 0
|
|
972
|
+
}
|
|
973
|
+
]
|
|
974
|
+
},
|
|
975
|
+
timestamp: touch.timestamp
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
return {
|
|
979
|
+
type: 3 /* IncrementalSnapshot */,
|
|
980
|
+
data: {
|
|
981
|
+
source: 2 /* MouseInteraction */,
|
|
982
|
+
type: touch.phase === "start" ? 7 /* TouchStart */ : 9 /* TouchEnd */,
|
|
983
|
+
id: RRWEB_BODY_NODE_ID,
|
|
984
|
+
x: x2,
|
|
985
|
+
y
|
|
986
|
+
},
|
|
987
|
+
timestamp: touch.timestamp
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
function createCustomEvent(tag, payload, timestamp) {
|
|
991
|
+
return {
|
|
992
|
+
type: 5 /* Custom */,
|
|
993
|
+
data: { tag, payload },
|
|
994
|
+
timestamp
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// src/NativeViewTree.ts
|
|
999
|
+
import { Dimensions, NativeModules, Platform } from "react-native";
|
|
1000
|
+
function viewport() {
|
|
1001
|
+
const dimensions = Dimensions.get(
|
|
1002
|
+
"window"
|
|
1003
|
+
);
|
|
1004
|
+
return {
|
|
1005
|
+
width: typeof dimensions.width === "number" && Number.isFinite(dimensions.width) ? dimensions.width : 0,
|
|
1006
|
+
height: typeof dimensions.height === "number" && Number.isFinite(dimensions.height) ? dimensions.height : 0
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
var ReactNativeViewTreeAdapter = class {
|
|
1010
|
+
isAvailable() {
|
|
1011
|
+
const nativeModule = NativeModules["OneUptimeReplayViewTree"];
|
|
1012
|
+
return typeof nativeModule?.captureViewTree === "function";
|
|
1013
|
+
}
|
|
1014
|
+
async captureViewTree(rootTag) {
|
|
1015
|
+
const nativeModule = NativeModules["OneUptimeReplayViewTree"];
|
|
1016
|
+
if (nativeModule?.captureViewTree) {
|
|
1017
|
+
return await nativeModule.captureViewTree(rootTag);
|
|
1018
|
+
}
|
|
1019
|
+
throw new Error(
|
|
1020
|
+
"OneUptime replay native module is unavailable. Expo Go is not supported; use an autolinked native or Expo development build."
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
async getAppMetadata() {
|
|
1024
|
+
const nativeModule = NativeModules["OneUptimeReplayViewTree"];
|
|
1025
|
+
let metadata = {};
|
|
1026
|
+
try {
|
|
1027
|
+
metadata = await nativeModule?.getAppMetadata?.() ?? {};
|
|
1028
|
+
} catch {
|
|
1029
|
+
metadata = {};
|
|
1030
|
+
}
|
|
1031
|
+
return {
|
|
1032
|
+
appName: typeof metadata.appName === "string" ? metadata.appName : "React Native",
|
|
1033
|
+
appVersion: typeof metadata.appVersion === "string" ? metadata.appVersion : "unknown",
|
|
1034
|
+
osName: typeof metadata.osName === "string" ? metadata.osName : Platform.OS,
|
|
1035
|
+
osVersion: typeof metadata.osVersion === "string" ? metadata.osVersion : String(Platform.Version)
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
async isTouchTargetPrivate(targetTag, replayRootTag) {
|
|
1039
|
+
const nativeModule = NativeModules["OneUptimeReplayViewTree"];
|
|
1040
|
+
if (typeof nativeModule?.isTouchTargetPrivate !== "function") {
|
|
1041
|
+
return true;
|
|
1042
|
+
}
|
|
1043
|
+
try {
|
|
1044
|
+
return await nativeModule.isTouchTargetPrivate(targetTag, replayRootTag) !== false;
|
|
1045
|
+
} catch {
|
|
1046
|
+
return true;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
var defaultNativeViewTreeAdapter = new ReactNativeViewTreeAdapter();
|
|
1051
|
+
function getViewport() {
|
|
1052
|
+
return viewport();
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// src/Outbox.ts
|
|
1056
|
+
var MAX_OUTBOX_ENTRIES = 64;
|
|
1057
|
+
var MAX_OUTBOX_BYTES = 8 * 1024 * 1024;
|
|
1058
|
+
function isFrame(value) {
|
|
1059
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
const frame = value;
|
|
1063
|
+
return typeof frame.id === "string" && typeof frame.payload === "string" && typeof frame.attempts === "number" && Number.isSafeInteger(frame.attempts) && Boolean(frame.envelope) && typeof frame.envelope === "object" && typeof frame.envelope.sessionId === "string" && typeof frame.envelope.tabId === "string" && typeof frame.envelope.chunkIndex === "number";
|
|
1064
|
+
}
|
|
1065
|
+
function frameId(envelope) {
|
|
1066
|
+
return `${envelope.sessionId}:${envelope.tabId}:${envelope.chunkIndex}`;
|
|
1067
|
+
}
|
|
1068
|
+
var ReplayOutbox = class {
|
|
1069
|
+
constructor(storage, namespace) {
|
|
1070
|
+
__publicField(this, "storage");
|
|
1071
|
+
__publicField(this, "key");
|
|
1072
|
+
__publicField(this, "mutationQueue", Promise.resolve());
|
|
1073
|
+
this.storage = storage;
|
|
1074
|
+
this.key = `@oneuptime/replay/${namespace}/outbox`;
|
|
1075
|
+
}
|
|
1076
|
+
async list() {
|
|
1077
|
+
return await this.withLock(
|
|
1078
|
+
async () => {
|
|
1079
|
+
return this.readUnlocked();
|
|
1080
|
+
}
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
async readUnlocked() {
|
|
1084
|
+
try {
|
|
1085
|
+
const raw = await this.storage.getItem(this.key);
|
|
1086
|
+
if (!raw) {
|
|
1087
|
+
return [];
|
|
1088
|
+
}
|
|
1089
|
+
const parsed = JSON.parse(raw);
|
|
1090
|
+
if (!Array.isArray(parsed) || !parsed.every(isFrame)) {
|
|
1091
|
+
await this.removeUnlocked();
|
|
1092
|
+
return [];
|
|
1093
|
+
}
|
|
1094
|
+
return parsed.slice().sort(
|
|
1095
|
+
(left, right) => {
|
|
1096
|
+
return left.createdAtUnixMs - right.createdAtUnixMs;
|
|
1097
|
+
}
|
|
1098
|
+
);
|
|
1099
|
+
} catch {
|
|
1100
|
+
await this.removeUnlocked();
|
|
1101
|
+
return [];
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
async enqueue(frame) {
|
|
1105
|
+
return await this.withLock(async () => {
|
|
1106
|
+
const frames = await this.readUnlocked();
|
|
1107
|
+
if (frames.some((candidate) => {
|
|
1108
|
+
return candidate.id === frame.id;
|
|
1109
|
+
})) {
|
|
1110
|
+
return { dropped: 0 };
|
|
1111
|
+
}
|
|
1112
|
+
frames.push(frame);
|
|
1113
|
+
let dropped = 0;
|
|
1114
|
+
while (frames.length > MAX_OUTBOX_ENTRIES || byteLength(JSON.stringify(frames)) > MAX_OUTBOX_BYTES) {
|
|
1115
|
+
frames.shift();
|
|
1116
|
+
dropped += 1;
|
|
1117
|
+
}
|
|
1118
|
+
await this.writeUnlocked(frames);
|
|
1119
|
+
return { dropped };
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
async remove(id) {
|
|
1123
|
+
await this.withLock(async () => {
|
|
1124
|
+
const frames = (await this.readUnlocked()).filter((frame) => {
|
|
1125
|
+
return frame.id !== id;
|
|
1126
|
+
});
|
|
1127
|
+
await this.writeUnlocked(frames);
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
async incrementAttempts(id) {
|
|
1131
|
+
return await this.withLock(async () => {
|
|
1132
|
+
const frames = await this.readUnlocked();
|
|
1133
|
+
let attempts = 0;
|
|
1134
|
+
for (const frame of frames) {
|
|
1135
|
+
if (frame.id === id) {
|
|
1136
|
+
frame.attempts += 1;
|
|
1137
|
+
attempts = frame.attempts;
|
|
1138
|
+
break;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
await this.writeUnlocked(frames);
|
|
1142
|
+
return attempts;
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
async clear() {
|
|
1146
|
+
await this.withLock(async () => {
|
|
1147
|
+
return this.removeUnlocked();
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
async removeUnlocked() {
|
|
1151
|
+
try {
|
|
1152
|
+
await this.storage.removeItem(this.key);
|
|
1153
|
+
} catch {
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
async writeUnlocked(frames) {
|
|
1157
|
+
try {
|
|
1158
|
+
if (frames.length === 0) {
|
|
1159
|
+
await this.storage.removeItem(this.key);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
await this.storage.setItem(this.key, JSON.stringify(frames));
|
|
1163
|
+
} catch {
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
async withLock(operation) {
|
|
1167
|
+
const result = this.mutationQueue.then(operation, operation);
|
|
1168
|
+
this.mutationQueue = result.then(
|
|
1169
|
+
() => {
|
|
1170
|
+
return void 0;
|
|
1171
|
+
},
|
|
1172
|
+
() => {
|
|
1173
|
+
return void 0;
|
|
1174
|
+
}
|
|
1175
|
+
);
|
|
1176
|
+
return await result;
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
// src/Storage.ts
|
|
1181
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
1182
|
+
var REPLAY_ID_PATTERN = /^[0-9a-f]{32}$/u;
|
|
1183
|
+
var defaultReplayStorage = AsyncStorage;
|
|
1184
|
+
function generateReplayId() {
|
|
1185
|
+
const bytes = new Uint8Array(16);
|
|
1186
|
+
const cryptoRef = globalThis.crypto;
|
|
1187
|
+
if (cryptoRef && typeof cryptoRef.getRandomValues === "function") {
|
|
1188
|
+
cryptoRef.getRandomValues(bytes);
|
|
1189
|
+
} else {
|
|
1190
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
1191
|
+
bytes[index] = Math.floor(Math.random() * 256);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
return Array.from(bytes).map((byte) => {
|
|
1195
|
+
return byte.toString(16).padStart(2, "0");
|
|
1196
|
+
}).join("");
|
|
1197
|
+
}
|
|
1198
|
+
function isReplayId(value) {
|
|
1199
|
+
return typeof value === "string" && REPLAY_ID_PATTERN.test(value);
|
|
1200
|
+
}
|
|
1201
|
+
function parseStoredSession(value) {
|
|
1202
|
+
if (!value) {
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
try {
|
|
1206
|
+
const parsed = JSON.parse(value);
|
|
1207
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1210
|
+
const candidate = parsed;
|
|
1211
|
+
if (!isReplayId(candidate.sessionId) || !Number.isSafeInteger(candidate.sessionStartUnixMs) || !Number.isSafeInteger(candidate.lastActivityUnixMs) || !Number.isSafeInteger(candidate.nextChunkIndex) || (candidate.nextChunkIndex ?? -1) < 0) {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
return candidate;
|
|
1215
|
+
} catch {
|
|
1216
|
+
return null;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
var ReplaySessionStore = class {
|
|
1220
|
+
constructor(storage, namespace) {
|
|
1221
|
+
__publicField(this, "storage");
|
|
1222
|
+
__publicField(this, "sessionKey");
|
|
1223
|
+
__publicField(this, "visitorKey");
|
|
1224
|
+
__publicField(this, "identity", null);
|
|
1225
|
+
this.storage = storage;
|
|
1226
|
+
this.sessionKey = `@oneuptime/replay/${namespace}/session`;
|
|
1227
|
+
this.visitorKey = `@oneuptime/replay/${namespace}/visitor`;
|
|
1228
|
+
}
|
|
1229
|
+
async resolve(nowUnixMs) {
|
|
1230
|
+
const stored = parseStoredSession(
|
|
1231
|
+
await this.safeGet(this.sessionKey)
|
|
1232
|
+
);
|
|
1233
|
+
const shouldRotate = !stored || stored.nextChunkIndex >= MAX_SESSION_REPLAY_CHUNKS_PER_SESSION || nowUnixMs - stored.lastActivityUnixMs >= SESSION_REPLAY_IDLE_ROLLOVER_MS || nowUnixMs - stored.sessionStartUnixMs >= SESSION_REPLAY_MAX_SESSION_MS;
|
|
1234
|
+
let session;
|
|
1235
|
+
if (shouldRotate || !stored) {
|
|
1236
|
+
session = {
|
|
1237
|
+
sessionId: generateReplayId(),
|
|
1238
|
+
sessionStartUnixMs: nowUnixMs,
|
|
1239
|
+
lastActivityUnixMs: nowUnixMs,
|
|
1240
|
+
nextChunkIndex: 0
|
|
1241
|
+
};
|
|
1242
|
+
} else {
|
|
1243
|
+
session = {
|
|
1244
|
+
sessionId: stored.sessionId,
|
|
1245
|
+
sessionStartUnixMs: stored.sessionStartUnixMs,
|
|
1246
|
+
lastActivityUnixMs: nowUnixMs,
|
|
1247
|
+
nextChunkIndex: stored.nextChunkIndex
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
let visitorId = await this.safeGet(this.visitorKey);
|
|
1251
|
+
if (!isReplayId(visitorId)) {
|
|
1252
|
+
visitorId = generateReplayId();
|
|
1253
|
+
await this.safeSet(this.visitorKey, visitorId);
|
|
1254
|
+
}
|
|
1255
|
+
await this.safeSet(this.sessionKey, JSON.stringify(session));
|
|
1256
|
+
this.identity = {
|
|
1257
|
+
sessionId: session.sessionId,
|
|
1258
|
+
tabId: generateReplayId(),
|
|
1259
|
+
visitorId,
|
|
1260
|
+
sessionStartUnixMs: session.sessionStartUnixMs,
|
|
1261
|
+
chunkIndex: session.nextChunkIndex
|
|
1262
|
+
};
|
|
1263
|
+
return { ...this.identity };
|
|
1264
|
+
}
|
|
1265
|
+
async takeChunkIndex(nowUnixMs) {
|
|
1266
|
+
if (!this.identity) {
|
|
1267
|
+
await this.resolve(nowUnixMs);
|
|
1268
|
+
}
|
|
1269
|
+
const identity = this.identity;
|
|
1270
|
+
const index = identity.chunkIndex;
|
|
1271
|
+
identity.chunkIndex += 1;
|
|
1272
|
+
const state = {
|
|
1273
|
+
sessionId: identity.sessionId,
|
|
1274
|
+
sessionStartUnixMs: identity.sessionStartUnixMs,
|
|
1275
|
+
lastActivityUnixMs: nowUnixMs,
|
|
1276
|
+
nextChunkIndex: identity.chunkIndex
|
|
1277
|
+
};
|
|
1278
|
+
await this.safeSet(this.sessionKey, JSON.stringify(state));
|
|
1279
|
+
return index;
|
|
1280
|
+
}
|
|
1281
|
+
getNextChunkIndex() {
|
|
1282
|
+
return this.identity?.chunkIndex ?? null;
|
|
1283
|
+
}
|
|
1284
|
+
async rotate(nowUnixMs) {
|
|
1285
|
+
let visitorId = await this.safeGet(this.visitorKey);
|
|
1286
|
+
if (!isReplayId(visitorId)) {
|
|
1287
|
+
visitorId = generateReplayId();
|
|
1288
|
+
await this.safeSet(this.visitorKey, visitorId);
|
|
1289
|
+
}
|
|
1290
|
+
const session = {
|
|
1291
|
+
sessionId: generateReplayId(),
|
|
1292
|
+
sessionStartUnixMs: nowUnixMs,
|
|
1293
|
+
lastActivityUnixMs: nowUnixMs,
|
|
1294
|
+
nextChunkIndex: 0
|
|
1295
|
+
};
|
|
1296
|
+
await this.safeSet(this.sessionKey, JSON.stringify(session));
|
|
1297
|
+
this.identity = {
|
|
1298
|
+
sessionId: session.sessionId,
|
|
1299
|
+
tabId: generateReplayId(),
|
|
1300
|
+
visitorId,
|
|
1301
|
+
sessionStartUnixMs: nowUnixMs,
|
|
1302
|
+
chunkIndex: 0
|
|
1303
|
+
};
|
|
1304
|
+
return { ...this.identity };
|
|
1305
|
+
}
|
|
1306
|
+
async clear() {
|
|
1307
|
+
this.identity = null;
|
|
1308
|
+
await Promise.all([
|
|
1309
|
+
this.safeRemove(this.sessionKey),
|
|
1310
|
+
this.safeRemove(this.visitorKey)
|
|
1311
|
+
]);
|
|
1312
|
+
}
|
|
1313
|
+
async safeGet(key) {
|
|
1314
|
+
try {
|
|
1315
|
+
return await this.storage.getItem(key);
|
|
1316
|
+
} catch {
|
|
1317
|
+
return null;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
async safeSet(key, value) {
|
|
1321
|
+
try {
|
|
1322
|
+
await this.storage.setItem(key, value);
|
|
1323
|
+
} catch {
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
async safeRemove(key) {
|
|
1327
|
+
try {
|
|
1328
|
+
await this.storage.removeItem(key);
|
|
1329
|
+
} catch {
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
// node_modules/fflate/esm/browser.js
|
|
1335
|
+
var u8 = Uint8Array;
|
|
1336
|
+
var u16 = Uint16Array;
|
|
1337
|
+
var i32 = Int32Array;
|
|
1338
|
+
var fleb = new u8([
|
|
1339
|
+
0,
|
|
1340
|
+
0,
|
|
1341
|
+
0,
|
|
1342
|
+
0,
|
|
1343
|
+
0,
|
|
1344
|
+
0,
|
|
1345
|
+
0,
|
|
1346
|
+
0,
|
|
1347
|
+
1,
|
|
1348
|
+
1,
|
|
1349
|
+
1,
|
|
1350
|
+
1,
|
|
1351
|
+
2,
|
|
1352
|
+
2,
|
|
1353
|
+
2,
|
|
1354
|
+
2,
|
|
1355
|
+
3,
|
|
1356
|
+
3,
|
|
1357
|
+
3,
|
|
1358
|
+
3,
|
|
1359
|
+
4,
|
|
1360
|
+
4,
|
|
1361
|
+
4,
|
|
1362
|
+
4,
|
|
1363
|
+
5,
|
|
1364
|
+
5,
|
|
1365
|
+
5,
|
|
1366
|
+
5,
|
|
1367
|
+
0,
|
|
1368
|
+
/* unused */
|
|
1369
|
+
0,
|
|
1370
|
+
0,
|
|
1371
|
+
/* impossible */
|
|
1372
|
+
0
|
|
1373
|
+
]);
|
|
1374
|
+
var fdeb = new u8([
|
|
1375
|
+
0,
|
|
1376
|
+
0,
|
|
1377
|
+
0,
|
|
1378
|
+
0,
|
|
1379
|
+
1,
|
|
1380
|
+
1,
|
|
1381
|
+
2,
|
|
1382
|
+
2,
|
|
1383
|
+
3,
|
|
1384
|
+
3,
|
|
1385
|
+
4,
|
|
1386
|
+
4,
|
|
1387
|
+
5,
|
|
1388
|
+
5,
|
|
1389
|
+
6,
|
|
1390
|
+
6,
|
|
1391
|
+
7,
|
|
1392
|
+
7,
|
|
1393
|
+
8,
|
|
1394
|
+
8,
|
|
1395
|
+
9,
|
|
1396
|
+
9,
|
|
1397
|
+
10,
|
|
1398
|
+
10,
|
|
1399
|
+
11,
|
|
1400
|
+
11,
|
|
1401
|
+
12,
|
|
1402
|
+
12,
|
|
1403
|
+
13,
|
|
1404
|
+
13,
|
|
1405
|
+
/* unused */
|
|
1406
|
+
0,
|
|
1407
|
+
0
|
|
1408
|
+
]);
|
|
1409
|
+
var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
|
|
1410
|
+
var freb = function(eb, start) {
|
|
1411
|
+
var b = new u16(31);
|
|
1412
|
+
for (var i2 = 0; i2 < 31; ++i2) {
|
|
1413
|
+
b[i2] = start += 1 << eb[i2 - 1];
|
|
1414
|
+
}
|
|
1415
|
+
var r = new i32(b[30]);
|
|
1416
|
+
for (var i2 = 1; i2 < 30; ++i2) {
|
|
1417
|
+
for (var j = b[i2]; j < b[i2 + 1]; ++j) {
|
|
1418
|
+
r[j] = j - b[i2] << 5 | i2;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
return { b, r };
|
|
1422
|
+
};
|
|
1423
|
+
var _a = freb(fleb, 2);
|
|
1424
|
+
var fl = _a.b;
|
|
1425
|
+
var revfl = _a.r;
|
|
1426
|
+
fl[28] = 258, revfl[258] = 28;
|
|
1427
|
+
var _b = freb(fdeb, 0);
|
|
1428
|
+
var fd = _b.b;
|
|
1429
|
+
var revfd = _b.r;
|
|
1430
|
+
var rev = new u16(32768);
|
|
1431
|
+
for (i = 0; i < 32768; ++i) {
|
|
1432
|
+
x = (i & 43690) >> 1 | (i & 21845) << 1;
|
|
1433
|
+
x = (x & 52428) >> 2 | (x & 13107) << 2;
|
|
1434
|
+
x = (x & 61680) >> 4 | (x & 3855) << 4;
|
|
1435
|
+
rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
|
|
1436
|
+
}
|
|
1437
|
+
var x;
|
|
1438
|
+
var i;
|
|
1439
|
+
var hMap = (function(cd, mb, r) {
|
|
1440
|
+
var s = cd.length;
|
|
1441
|
+
var i2 = 0;
|
|
1442
|
+
var l = new u16(mb);
|
|
1443
|
+
for (; i2 < s; ++i2) {
|
|
1444
|
+
if (cd[i2])
|
|
1445
|
+
++l[cd[i2] - 1];
|
|
1446
|
+
}
|
|
1447
|
+
var le = new u16(mb);
|
|
1448
|
+
for (i2 = 1; i2 < mb; ++i2) {
|
|
1449
|
+
le[i2] = le[i2 - 1] + l[i2 - 1] << 1;
|
|
1450
|
+
}
|
|
1451
|
+
var co;
|
|
1452
|
+
if (r) {
|
|
1453
|
+
co = new u16(1 << mb);
|
|
1454
|
+
var rvb = 15 - mb;
|
|
1455
|
+
for (i2 = 0; i2 < s; ++i2) {
|
|
1456
|
+
if (cd[i2]) {
|
|
1457
|
+
var sv = i2 << 4 | cd[i2];
|
|
1458
|
+
var r_1 = mb - cd[i2];
|
|
1459
|
+
var v = le[cd[i2] - 1]++ << r_1;
|
|
1460
|
+
for (var m = v | (1 << r_1) - 1; v <= m; ++v) {
|
|
1461
|
+
co[rev[v] >> rvb] = sv;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
} else {
|
|
1466
|
+
co = new u16(s);
|
|
1467
|
+
for (i2 = 0; i2 < s; ++i2) {
|
|
1468
|
+
if (cd[i2]) {
|
|
1469
|
+
co[i2] = rev[le[cd[i2] - 1]++] >> 15 - cd[i2];
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
return co;
|
|
1474
|
+
});
|
|
1475
|
+
var flt = new u8(288);
|
|
1476
|
+
for (i = 0; i < 144; ++i)
|
|
1477
|
+
flt[i] = 8;
|
|
1478
|
+
var i;
|
|
1479
|
+
for (i = 144; i < 256; ++i)
|
|
1480
|
+
flt[i] = 9;
|
|
1481
|
+
var i;
|
|
1482
|
+
for (i = 256; i < 280; ++i)
|
|
1483
|
+
flt[i] = 7;
|
|
1484
|
+
var i;
|
|
1485
|
+
for (i = 280; i < 288; ++i)
|
|
1486
|
+
flt[i] = 8;
|
|
1487
|
+
var i;
|
|
1488
|
+
var fdt = new u8(32);
|
|
1489
|
+
for (i = 0; i < 32; ++i)
|
|
1490
|
+
fdt[i] = 5;
|
|
1491
|
+
var i;
|
|
1492
|
+
var flm = /* @__PURE__ */ hMap(flt, 9, 0);
|
|
1493
|
+
var fdm = /* @__PURE__ */ hMap(fdt, 5, 0);
|
|
1494
|
+
var shft = function(p) {
|
|
1495
|
+
return (p + 7) / 8 | 0;
|
|
1496
|
+
};
|
|
1497
|
+
var slc = function(v, s, e) {
|
|
1498
|
+
if (s == null || s < 0)
|
|
1499
|
+
s = 0;
|
|
1500
|
+
if (e == null || e > v.length)
|
|
1501
|
+
e = v.length;
|
|
1502
|
+
return new u8(v.subarray(s, e));
|
|
1503
|
+
};
|
|
1504
|
+
var wbits = function(d, p, v) {
|
|
1505
|
+
v <<= p & 7;
|
|
1506
|
+
var o = p / 8 | 0;
|
|
1507
|
+
d[o] |= v;
|
|
1508
|
+
d[o + 1] |= v >> 8;
|
|
1509
|
+
};
|
|
1510
|
+
var wbits16 = function(d, p, v) {
|
|
1511
|
+
v <<= p & 7;
|
|
1512
|
+
var o = p / 8 | 0;
|
|
1513
|
+
d[o] |= v;
|
|
1514
|
+
d[o + 1] |= v >> 8;
|
|
1515
|
+
d[o + 2] |= v >> 16;
|
|
1516
|
+
};
|
|
1517
|
+
var hTree = function(d, mb) {
|
|
1518
|
+
var t = [];
|
|
1519
|
+
for (var i2 = 0; i2 < d.length; ++i2) {
|
|
1520
|
+
if (d[i2])
|
|
1521
|
+
t.push({ s: i2, f: d[i2] });
|
|
1522
|
+
}
|
|
1523
|
+
var s = t.length;
|
|
1524
|
+
var t2 = t.slice();
|
|
1525
|
+
if (!s)
|
|
1526
|
+
return { t: et, l: 0 };
|
|
1527
|
+
if (s == 1) {
|
|
1528
|
+
var v = new u8(t[0].s + 1);
|
|
1529
|
+
v[t[0].s] = 1;
|
|
1530
|
+
return { t: v, l: 1 };
|
|
1531
|
+
}
|
|
1532
|
+
t.sort(function(a, b) {
|
|
1533
|
+
return a.f - b.f;
|
|
1534
|
+
});
|
|
1535
|
+
t.push({ s: -1, f: 25001 });
|
|
1536
|
+
var l = t[0], r = t[1], i0 = 0, i1 = 1, i22 = 2;
|
|
1537
|
+
t[0] = { s: -1, f: l.f + r.f, l, r };
|
|
1538
|
+
while (i1 != s - 1) {
|
|
1539
|
+
l = t[t[i0].f < t[i22].f ? i0++ : i22++];
|
|
1540
|
+
r = t[i0 != i1 && t[i0].f < t[i22].f ? i0++ : i22++];
|
|
1541
|
+
t[i1++] = { s: -1, f: l.f + r.f, l, r };
|
|
1542
|
+
}
|
|
1543
|
+
var maxSym = t2[0].s;
|
|
1544
|
+
for (var i2 = 1; i2 < s; ++i2) {
|
|
1545
|
+
if (t2[i2].s > maxSym)
|
|
1546
|
+
maxSym = t2[i2].s;
|
|
1547
|
+
}
|
|
1548
|
+
var tr = new u16(maxSym + 1);
|
|
1549
|
+
var mbt = ln(t[i1 - 1], tr, 0);
|
|
1550
|
+
if (mbt > mb) {
|
|
1551
|
+
var i2 = 0, dt = 0;
|
|
1552
|
+
var lft = mbt - mb, cst = 1 << lft;
|
|
1553
|
+
t2.sort(function(a, b) {
|
|
1554
|
+
return tr[b.s] - tr[a.s] || a.f - b.f;
|
|
1555
|
+
});
|
|
1556
|
+
for (; i2 < s; ++i2) {
|
|
1557
|
+
var i2_1 = t2[i2].s;
|
|
1558
|
+
if (tr[i2_1] > mb) {
|
|
1559
|
+
dt += cst - (1 << mbt - tr[i2_1]);
|
|
1560
|
+
tr[i2_1] = mb;
|
|
1561
|
+
} else
|
|
1562
|
+
break;
|
|
1563
|
+
}
|
|
1564
|
+
dt >>= lft;
|
|
1565
|
+
while (dt > 0) {
|
|
1566
|
+
var i2_2 = t2[i2].s;
|
|
1567
|
+
if (tr[i2_2] < mb)
|
|
1568
|
+
dt -= 1 << mb - tr[i2_2]++ - 1;
|
|
1569
|
+
else
|
|
1570
|
+
++i2;
|
|
1571
|
+
}
|
|
1572
|
+
for (; i2 >= 0 && dt; --i2) {
|
|
1573
|
+
var i2_3 = t2[i2].s;
|
|
1574
|
+
if (tr[i2_3] == mb) {
|
|
1575
|
+
--tr[i2_3];
|
|
1576
|
+
++dt;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
mbt = mb;
|
|
1580
|
+
}
|
|
1581
|
+
return { t: new u8(tr), l: mbt };
|
|
1582
|
+
};
|
|
1583
|
+
var ln = function(n, l, d) {
|
|
1584
|
+
return n.s == -1 ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) : l[n.s] = d;
|
|
1585
|
+
};
|
|
1586
|
+
var lc = function(c) {
|
|
1587
|
+
var s = c.length;
|
|
1588
|
+
while (s && !c[--s])
|
|
1589
|
+
;
|
|
1590
|
+
var cl = new u16(++s);
|
|
1591
|
+
var cli = 0, cln = c[0], cls = 1;
|
|
1592
|
+
var w = function(v) {
|
|
1593
|
+
cl[cli++] = v;
|
|
1594
|
+
};
|
|
1595
|
+
for (var i2 = 1; i2 <= s; ++i2) {
|
|
1596
|
+
if (c[i2] == cln && i2 != s)
|
|
1597
|
+
++cls;
|
|
1598
|
+
else {
|
|
1599
|
+
if (!cln && cls > 2) {
|
|
1600
|
+
for (; cls > 138; cls -= 138)
|
|
1601
|
+
w(32754);
|
|
1602
|
+
if (cls > 2) {
|
|
1603
|
+
w(cls > 10 ? cls - 11 << 5 | 28690 : cls - 3 << 5 | 12305);
|
|
1604
|
+
cls = 0;
|
|
1605
|
+
}
|
|
1606
|
+
} else if (cls > 3) {
|
|
1607
|
+
w(cln), --cls;
|
|
1608
|
+
for (; cls > 6; cls -= 6)
|
|
1609
|
+
w(8304);
|
|
1610
|
+
if (cls > 2)
|
|
1611
|
+
w(cls - 3 << 5 | 8208), cls = 0;
|
|
1612
|
+
}
|
|
1613
|
+
while (cls--)
|
|
1614
|
+
w(cln);
|
|
1615
|
+
cls = 1;
|
|
1616
|
+
cln = c[i2];
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
return { c: cl.subarray(0, cli), n: s };
|
|
1620
|
+
};
|
|
1621
|
+
var clen = function(cf, cl) {
|
|
1622
|
+
var l = 0;
|
|
1623
|
+
for (var i2 = 0; i2 < cl.length; ++i2)
|
|
1624
|
+
l += cf[i2] * cl[i2];
|
|
1625
|
+
return l;
|
|
1626
|
+
};
|
|
1627
|
+
var wfblk = function(out, pos, dat) {
|
|
1628
|
+
var s = dat.length;
|
|
1629
|
+
var o = shft(pos + 2);
|
|
1630
|
+
out[o] = s & 255;
|
|
1631
|
+
out[o + 1] = s >> 8;
|
|
1632
|
+
out[o + 2] = out[o] ^ 255;
|
|
1633
|
+
out[o + 3] = out[o + 1] ^ 255;
|
|
1634
|
+
for (var i2 = 0; i2 < s; ++i2)
|
|
1635
|
+
out[o + i2 + 4] = dat[i2];
|
|
1636
|
+
return (o + 4 + s) * 8;
|
|
1637
|
+
};
|
|
1638
|
+
var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
|
|
1639
|
+
wbits(out, p++, final);
|
|
1640
|
+
++lf[256];
|
|
1641
|
+
var _a2 = hTree(lf, 15), dlt = _a2.t, mlb = _a2.l;
|
|
1642
|
+
var _b2 = hTree(df, 15), ddt = _b2.t, mdb = _b2.l;
|
|
1643
|
+
var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
|
|
1644
|
+
var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
|
|
1645
|
+
var lcfreq = new u16(19);
|
|
1646
|
+
for (var i2 = 0; i2 < lclt.length; ++i2)
|
|
1647
|
+
++lcfreq[lclt[i2] & 31];
|
|
1648
|
+
for (var i2 = 0; i2 < lcdt.length; ++i2)
|
|
1649
|
+
++lcfreq[lcdt[i2] & 31];
|
|
1650
|
+
var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
|
|
1651
|
+
var nlcc = 19;
|
|
1652
|
+
for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
|
|
1653
|
+
;
|
|
1654
|
+
var flen = bl + 5 << 3;
|
|
1655
|
+
var ftlen = clen(lf, flt) + clen(df, fdt) + eb;
|
|
1656
|
+
var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18];
|
|
1657
|
+
if (bs >= 0 && flen <= ftlen && flen <= dtlen)
|
|
1658
|
+
return wfblk(out, p, dat.subarray(bs, bs + bl));
|
|
1659
|
+
var lm, ll, dm, dl;
|
|
1660
|
+
wbits(out, p, 1 + (dtlen < ftlen)), p += 2;
|
|
1661
|
+
if (dtlen < ftlen) {
|
|
1662
|
+
lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;
|
|
1663
|
+
var llm = hMap(lct, mlcb, 0);
|
|
1664
|
+
wbits(out, p, nlc - 257);
|
|
1665
|
+
wbits(out, p + 5, ndc - 1);
|
|
1666
|
+
wbits(out, p + 10, nlcc - 4);
|
|
1667
|
+
p += 14;
|
|
1668
|
+
for (var i2 = 0; i2 < nlcc; ++i2)
|
|
1669
|
+
wbits(out, p + 3 * i2, lct[clim[i2]]);
|
|
1670
|
+
p += 3 * nlcc;
|
|
1671
|
+
var lcts = [lclt, lcdt];
|
|
1672
|
+
for (var it = 0; it < 2; ++it) {
|
|
1673
|
+
var clct = lcts[it];
|
|
1674
|
+
for (var i2 = 0; i2 < clct.length; ++i2) {
|
|
1675
|
+
var len = clct[i2] & 31;
|
|
1676
|
+
wbits(out, p, llm[len]), p += lct[len];
|
|
1677
|
+
if (len > 15)
|
|
1678
|
+
wbits(out, p, clct[i2] >> 5 & 127), p += clct[i2] >> 12;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
} else {
|
|
1682
|
+
lm = flm, ll = flt, dm = fdm, dl = fdt;
|
|
1683
|
+
}
|
|
1684
|
+
for (var i2 = 0; i2 < li; ++i2) {
|
|
1685
|
+
var sym = syms[i2];
|
|
1686
|
+
if (sym > 255) {
|
|
1687
|
+
var len = sym >> 18 & 31;
|
|
1688
|
+
wbits16(out, p, lm[len + 257]), p += ll[len + 257];
|
|
1689
|
+
if (len > 7)
|
|
1690
|
+
wbits(out, p, sym >> 23 & 31), p += fleb[len];
|
|
1691
|
+
var dst = sym & 31;
|
|
1692
|
+
wbits16(out, p, dm[dst]), p += dl[dst];
|
|
1693
|
+
if (dst > 3)
|
|
1694
|
+
wbits16(out, p, sym >> 5 & 8191), p += fdeb[dst];
|
|
1695
|
+
} else {
|
|
1696
|
+
wbits16(out, p, lm[sym]), p += ll[sym];
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
wbits16(out, p, lm[256]);
|
|
1700
|
+
return p + ll[256];
|
|
1701
|
+
};
|
|
1702
|
+
var deo = /* @__PURE__ */ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);
|
|
1703
|
+
var et = /* @__PURE__ */ new u8(0);
|
|
1704
|
+
var dflt = function(dat, lvl, plvl, pre, post, st) {
|
|
1705
|
+
var s = st.z || dat.length;
|
|
1706
|
+
var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7e3)) + post);
|
|
1707
|
+
var w = o.subarray(pre, o.length - post);
|
|
1708
|
+
var lst = st.l;
|
|
1709
|
+
var pos = (st.r || 0) & 7;
|
|
1710
|
+
if (lvl) {
|
|
1711
|
+
if (pos)
|
|
1712
|
+
w[0] = st.r >> 3;
|
|
1713
|
+
var opt = deo[lvl - 1];
|
|
1714
|
+
var n = opt >> 13, c = opt & 8191;
|
|
1715
|
+
var msk_1 = (1 << plvl) - 1;
|
|
1716
|
+
var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
|
|
1717
|
+
var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
|
|
1718
|
+
var hsh = function(i3) {
|
|
1719
|
+
return (dat[i3] ^ dat[i3 + 1] << bs1_1 ^ dat[i3 + 2] << bs2_1) & msk_1;
|
|
1720
|
+
};
|
|
1721
|
+
var syms = new i32(25e3);
|
|
1722
|
+
var lf = new u16(288), df = new u16(32);
|
|
1723
|
+
var lc_1 = 0, eb = 0, i2 = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
|
|
1724
|
+
for (; i2 + 2 < s; ++i2) {
|
|
1725
|
+
var hv = hsh(i2);
|
|
1726
|
+
var imod = i2 & 32767, pimod = head[hv];
|
|
1727
|
+
prev[imod] = pimod;
|
|
1728
|
+
head[hv] = imod;
|
|
1729
|
+
if (wi <= i2) {
|
|
1730
|
+
var rem = s - i2;
|
|
1731
|
+
if ((lc_1 > 7e3 || li > 24576) && (rem > 423 || !lst)) {
|
|
1732
|
+
pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i2 - bs, pos);
|
|
1733
|
+
li = lc_1 = eb = 0, bs = i2;
|
|
1734
|
+
for (var j = 0; j < 286; ++j)
|
|
1735
|
+
lf[j] = 0;
|
|
1736
|
+
for (var j = 0; j < 30; ++j)
|
|
1737
|
+
df[j] = 0;
|
|
1738
|
+
}
|
|
1739
|
+
var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
|
|
1740
|
+
if (rem > 2 && hv == hsh(i2 - dif)) {
|
|
1741
|
+
var maxn = Math.min(n, rem) - 1;
|
|
1742
|
+
var maxd = Math.min(32767, i2);
|
|
1743
|
+
var ml = Math.min(258, rem);
|
|
1744
|
+
while (dif <= maxd && --ch_1 && imod != pimod) {
|
|
1745
|
+
if (dat[i2 + l] == dat[i2 + l - dif]) {
|
|
1746
|
+
var nl = 0;
|
|
1747
|
+
for (; nl < ml && dat[i2 + nl] == dat[i2 + nl - dif]; ++nl)
|
|
1748
|
+
;
|
|
1749
|
+
if (nl > l) {
|
|
1750
|
+
l = nl, d = dif;
|
|
1751
|
+
if (nl > maxn)
|
|
1752
|
+
break;
|
|
1753
|
+
var mmd = Math.min(dif, nl - 2);
|
|
1754
|
+
var md = 0;
|
|
1755
|
+
for (var j = 0; j < mmd; ++j) {
|
|
1756
|
+
var ti = i2 - dif + j & 32767;
|
|
1757
|
+
var pti = prev[ti];
|
|
1758
|
+
var cd = ti - pti & 32767;
|
|
1759
|
+
if (cd > md)
|
|
1760
|
+
md = cd, pimod = ti;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
imod = pimod, pimod = prev[imod];
|
|
1765
|
+
dif += imod - pimod & 32767;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
if (d) {
|
|
1769
|
+
syms[li++] = 268435456 | revfl[l] << 18 | revfd[d];
|
|
1770
|
+
var lin = revfl[l] & 31, din = revfd[d] & 31;
|
|
1771
|
+
eb += fleb[lin] + fdeb[din];
|
|
1772
|
+
++lf[257 + lin];
|
|
1773
|
+
++df[din];
|
|
1774
|
+
wi = i2 + l;
|
|
1775
|
+
++lc_1;
|
|
1776
|
+
} else {
|
|
1777
|
+
syms[li++] = dat[i2];
|
|
1778
|
+
++lf[dat[i2]];
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
for (i2 = Math.max(i2, wi); i2 < s; ++i2) {
|
|
1783
|
+
syms[li++] = dat[i2];
|
|
1784
|
+
++lf[dat[i2]];
|
|
1785
|
+
}
|
|
1786
|
+
pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i2 - bs, pos);
|
|
1787
|
+
if (!lst) {
|
|
1788
|
+
st.r = pos & 7 | w[pos / 8 | 0] << 3;
|
|
1789
|
+
pos -= 7;
|
|
1790
|
+
st.h = head, st.p = prev, st.i = i2, st.w = wi;
|
|
1791
|
+
}
|
|
1792
|
+
} else {
|
|
1793
|
+
for (var i2 = st.w || 0; i2 < s + lst; i2 += 65535) {
|
|
1794
|
+
var e = i2 + 65535;
|
|
1795
|
+
if (e >= s) {
|
|
1796
|
+
w[pos / 8 | 0] = lst;
|
|
1797
|
+
e = s;
|
|
1798
|
+
}
|
|
1799
|
+
pos = wfblk(w, pos + 1, dat.subarray(i2, e));
|
|
1800
|
+
}
|
|
1801
|
+
st.i = s;
|
|
1802
|
+
}
|
|
1803
|
+
return slc(o, 0, pre + shft(pos) + post);
|
|
1804
|
+
};
|
|
1805
|
+
var crct = /* @__PURE__ */ (function() {
|
|
1806
|
+
var t = new Int32Array(256);
|
|
1807
|
+
for (var i2 = 0; i2 < 256; ++i2) {
|
|
1808
|
+
var c = i2, k = 9;
|
|
1809
|
+
while (--k)
|
|
1810
|
+
c = (c & 1 && -306674912) ^ c >>> 1;
|
|
1811
|
+
t[i2] = c;
|
|
1812
|
+
}
|
|
1813
|
+
return t;
|
|
1814
|
+
})();
|
|
1815
|
+
var crc = function() {
|
|
1816
|
+
var c = -1;
|
|
1817
|
+
return {
|
|
1818
|
+
p: function(d) {
|
|
1819
|
+
var cr = c;
|
|
1820
|
+
for (var i2 = 0; i2 < d.length; ++i2)
|
|
1821
|
+
cr = crct[cr & 255 ^ d[i2]] ^ cr >>> 8;
|
|
1822
|
+
c = cr;
|
|
1823
|
+
},
|
|
1824
|
+
d: function() {
|
|
1825
|
+
return ~c;
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
};
|
|
1829
|
+
var dopt = function(dat, opt, pre, post, st) {
|
|
1830
|
+
if (!st) {
|
|
1831
|
+
st = { l: 1 };
|
|
1832
|
+
if (opt.dictionary) {
|
|
1833
|
+
var dict = opt.dictionary.subarray(-32768);
|
|
1834
|
+
var newDat = new u8(dict.length + dat.length);
|
|
1835
|
+
newDat.set(dict);
|
|
1836
|
+
newDat.set(dat, dict.length);
|
|
1837
|
+
dat = newDat;
|
|
1838
|
+
st.w = dict.length;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? st.l ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : 20 : 12 + opt.mem, pre, post, st);
|
|
1842
|
+
};
|
|
1843
|
+
var wbytes = function(d, b, v) {
|
|
1844
|
+
for (; v; ++b)
|
|
1845
|
+
d[b] = v, v >>>= 8;
|
|
1846
|
+
};
|
|
1847
|
+
var gzh = function(c, o) {
|
|
1848
|
+
var fn = o.filename;
|
|
1849
|
+
c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3;
|
|
1850
|
+
if (o.mtime != 0)
|
|
1851
|
+
wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1e3));
|
|
1852
|
+
if (fn) {
|
|
1853
|
+
c[3] = 8;
|
|
1854
|
+
for (var i2 = 0; i2 <= fn.length; ++i2)
|
|
1855
|
+
c[i2 + 10] = fn.charCodeAt(i2);
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
var gzhl = function(o) {
|
|
1859
|
+
return 10 + (o.filename ? o.filename.length + 1 : 0);
|
|
1860
|
+
};
|
|
1861
|
+
function gzipSync(data, opts) {
|
|
1862
|
+
if (!opts)
|
|
1863
|
+
opts = {};
|
|
1864
|
+
var c = crc(), l = data.length;
|
|
1865
|
+
c.p(data);
|
|
1866
|
+
var d = dopt(data, opts, gzhl(opts), 8), s = d.length;
|
|
1867
|
+
return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d;
|
|
1868
|
+
}
|
|
1869
|
+
var te = typeof TextEncoder != "undefined" && /* @__PURE__ */ new TextEncoder();
|
|
1870
|
+
var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder();
|
|
1871
|
+
var tds = 0;
|
|
1872
|
+
try {
|
|
1873
|
+
td.decode(et, { stream: true });
|
|
1874
|
+
tds = 1;
|
|
1875
|
+
} catch (e) {
|
|
1876
|
+
}
|
|
1877
|
+
function strToU8(str, latin1) {
|
|
1878
|
+
if (latin1) {
|
|
1879
|
+
var ar_1 = new u8(str.length);
|
|
1880
|
+
for (var i2 = 0; i2 < str.length; ++i2)
|
|
1881
|
+
ar_1[i2] = str.charCodeAt(i2);
|
|
1882
|
+
return ar_1;
|
|
1883
|
+
}
|
|
1884
|
+
if (te)
|
|
1885
|
+
return te.encode(str);
|
|
1886
|
+
var l = str.length;
|
|
1887
|
+
var ar = new u8(str.length + (str.length >> 1));
|
|
1888
|
+
var ai = 0;
|
|
1889
|
+
var w = function(v) {
|
|
1890
|
+
ar[ai++] = v;
|
|
1891
|
+
};
|
|
1892
|
+
for (var i2 = 0; i2 < l; ++i2) {
|
|
1893
|
+
if (ai + 5 > ar.length) {
|
|
1894
|
+
var n = new u8(ai + 8 + (l - i2 << 1));
|
|
1895
|
+
n.set(ar);
|
|
1896
|
+
ar = n;
|
|
1897
|
+
}
|
|
1898
|
+
var c = str.charCodeAt(i2);
|
|
1899
|
+
if (c < 128 || latin1)
|
|
1900
|
+
w(c);
|
|
1901
|
+
else if (c < 2048)
|
|
1902
|
+
w(192 | c >> 6), w(128 | c & 63);
|
|
1903
|
+
else if (c > 55295 && c < 57344)
|
|
1904
|
+
c = 65536 + (c & 1023 << 10) | str.charCodeAt(++i2) & 1023, w(240 | c >> 18), w(128 | c >> 12 & 63), w(128 | c >> 6 & 63), w(128 | c & 63);
|
|
1905
|
+
else
|
|
1906
|
+
w(224 | c >> 12), w(128 | c >> 6 & 63), w(128 | c & 63);
|
|
1907
|
+
}
|
|
1908
|
+
return slc(ar, 0, ai);
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
// src/Transport.ts
|
|
1912
|
+
var MAX_ATTEMPTS_PER_FRAME = 3;
|
|
1913
|
+
var DEFAULT_RETRY_MS = 15e3;
|
|
1914
|
+
var MAX_RETRY_MS = 5 * 6e4;
|
|
1915
|
+
var CHUNK_POST_TIMEOUT_MS = 1e4;
|
|
1916
|
+
var DIRECTIVE_REASON_PATTERN = /^[A-Za-z0-9_.:-]{1,100}$/u;
|
|
1917
|
+
function encodeReplayFrame(originalEnvelope, payload) {
|
|
1918
|
+
const compressed = gzipSync(strToU8(payload));
|
|
1919
|
+
const envelope = {
|
|
1920
|
+
...originalEnvelope,
|
|
1921
|
+
payloadEncoding: "gzip",
|
|
1922
|
+
payloadBytes: compressed.byteLength,
|
|
1923
|
+
clientSendUnixMs: Date.now()
|
|
1924
|
+
};
|
|
1925
|
+
const prefix = strToU8(`${JSON.stringify(envelope)}
|
|
1926
|
+
`);
|
|
1927
|
+
const body = new Uint8Array(
|
|
1928
|
+
prefix.byteLength + compressed.byteLength
|
|
1929
|
+
);
|
|
1930
|
+
body.set(prefix, 0);
|
|
1931
|
+
body.set(compressed, prefix.byteLength);
|
|
1932
|
+
return { envelope, body };
|
|
1933
|
+
}
|
|
1934
|
+
function safeReason(value) {
|
|
1935
|
+
return typeof value === "string" && DIRECTIVE_REASON_PATTERN.test(value) ? value : null;
|
|
1936
|
+
}
|
|
1937
|
+
function parseDirectiveValue(value) {
|
|
1938
|
+
return value === "continue" || value === "stop" || value === "throttle" ? value : null;
|
|
1939
|
+
}
|
|
1940
|
+
async function readDirective(response) {
|
|
1941
|
+
if (response.status === 204) {
|
|
1942
|
+
return {
|
|
1943
|
+
directive: parseDirectiveValue(
|
|
1944
|
+
response.headers?.get("x-oneuptime-replay-directive")
|
|
1945
|
+
),
|
|
1946
|
+
reason: safeReason(response.headers?.get("x-oneuptime-replay-reason")),
|
|
1947
|
+
retryAfterSeconds: parseRetryAfter(
|
|
1948
|
+
response.headers?.get("retry-after") ?? null
|
|
1949
|
+
)
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
try {
|
|
1953
|
+
const value = await response.json();
|
|
1954
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1955
|
+
return { directive: null, reason: null, retryAfterSeconds: null };
|
|
1956
|
+
}
|
|
1957
|
+
const body = value;
|
|
1958
|
+
return {
|
|
1959
|
+
directive: parseDirectiveValue(body.directive),
|
|
1960
|
+
reason: safeReason(body.reason) ?? safeReason(body.error),
|
|
1961
|
+
retryAfterSeconds: typeof body.retryAfterSeconds === "number" && Number.isFinite(body.retryAfterSeconds) ? Math.max(0, Math.min(300, body.retryAfterSeconds)) : parseRetryAfter(response.headers?.get("retry-after") ?? null)
|
|
1962
|
+
};
|
|
1963
|
+
} catch {
|
|
1964
|
+
return {
|
|
1965
|
+
directive: null,
|
|
1966
|
+
reason: null,
|
|
1967
|
+
retryAfterSeconds: parseRetryAfter(
|
|
1968
|
+
response.headers?.get("retry-after") ?? null
|
|
1969
|
+
)
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
function parseRetryAfter(value) {
|
|
1974
|
+
if (!value) {
|
|
1975
|
+
return null;
|
|
1976
|
+
}
|
|
1977
|
+
const seconds = Number(value);
|
|
1978
|
+
return Number.isFinite(seconds) && seconds >= 0 ? Math.min(300, seconds) : null;
|
|
1979
|
+
}
|
|
1980
|
+
var ReplayTransport = class {
|
|
1981
|
+
constructor(options) {
|
|
1982
|
+
__publicField(this, "options");
|
|
1983
|
+
__publicField(this, "fetch");
|
|
1984
|
+
__publicField(this, "drainPromise", null);
|
|
1985
|
+
__publicField(this, "retryTimer", null);
|
|
1986
|
+
__publicField(this, "stopped", false);
|
|
1987
|
+
__publicField(this, "flushFailures", 0);
|
|
1988
|
+
__publicField(this, "retryAfterDrainMs", null);
|
|
1989
|
+
__publicField(this, "activeAbortController", null);
|
|
1990
|
+
__publicField(this, "abortCurrentRequest", null);
|
|
1991
|
+
this.options = options;
|
|
1992
|
+
this.fetch = options.startOptions.fetch;
|
|
1993
|
+
}
|
|
1994
|
+
async enqueue(envelope, payload) {
|
|
1995
|
+
if (this.stopped) {
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
const mutation = await this.options.outbox.enqueue({
|
|
1999
|
+
id: frameId(envelope),
|
|
2000
|
+
envelope,
|
|
2001
|
+
payload,
|
|
2002
|
+
attempts: 0,
|
|
2003
|
+
createdAtUnixMs: Date.now()
|
|
2004
|
+
});
|
|
2005
|
+
if (mutation.dropped > 0) {
|
|
2006
|
+
this.options.onDiagnostic("outbox-overflow", {
|
|
2007
|
+
dropped: mutation.dropped
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
await this.drain();
|
|
2011
|
+
}
|
|
2012
|
+
async restore() {
|
|
2013
|
+
await this.drain();
|
|
2014
|
+
}
|
|
2015
|
+
async drain() {
|
|
2016
|
+
if (this.stopped) {
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
if (this.drainPromise) {
|
|
2020
|
+
return await this.drainPromise;
|
|
2021
|
+
}
|
|
2022
|
+
this.drainPromise = this.drainLoop().finally(() => {
|
|
2023
|
+
this.drainPromise = null;
|
|
2024
|
+
if (this.retryAfterDrainMs !== null) {
|
|
2025
|
+
const delayMs = this.retryAfterDrainMs;
|
|
2026
|
+
this.retryAfterDrainMs = null;
|
|
2027
|
+
this.scheduleRetry(delayMs);
|
|
2028
|
+
}
|
|
2029
|
+
});
|
|
2030
|
+
return await this.drainPromise;
|
|
2031
|
+
}
|
|
2032
|
+
destroy() {
|
|
2033
|
+
this.stopped = true;
|
|
2034
|
+
this.activeAbortController?.abort();
|
|
2035
|
+
this.abortCurrentRequest?.();
|
|
2036
|
+
if (this.retryTimer) {
|
|
2037
|
+
clearTimeout(this.retryTimer);
|
|
2038
|
+
this.retryTimer = null;
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
getFlushFailures() {
|
|
2042
|
+
return this.flushFailures;
|
|
2043
|
+
}
|
|
2044
|
+
async drainLoop() {
|
|
2045
|
+
const frames = await this.options.outbox.list();
|
|
2046
|
+
for (const frame of frames) {
|
|
2047
|
+
if (this.stopped) {
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
const outcome = await this.post(frame);
|
|
2051
|
+
if (outcome === "remove") {
|
|
2052
|
+
await this.options.outbox.remove(frame.id);
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
if (outcome === "remove-and-halt") {
|
|
2056
|
+
await this.options.outbox.remove(frame.id);
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
if (outcome === "stop") {
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
const attempts = await this.options.outbox.incrementAttempts(
|
|
2063
|
+
frame.id
|
|
2064
|
+
);
|
|
2065
|
+
this.flushFailures += 1;
|
|
2066
|
+
if (attempts >= MAX_ATTEMPTS_PER_FRAME) {
|
|
2067
|
+
await this.options.outbox.remove(frame.id);
|
|
2068
|
+
this.options.onDiagnostic("chunk-retry-exhausted", {
|
|
2069
|
+
chunkIndex: frame.envelope.chunkIndex
|
|
2070
|
+
});
|
|
2071
|
+
continue;
|
|
2072
|
+
}
|
|
2073
|
+
if (this.retryAfterDrainMs === null) {
|
|
2074
|
+
this.retryAfterDrainMs = DEFAULT_RETRY_MS;
|
|
2075
|
+
}
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
async post(frame) {
|
|
2080
|
+
const encoded = encodeReplayFrame(
|
|
2081
|
+
frame.envelope,
|
|
2082
|
+
frame.payload
|
|
2083
|
+
);
|
|
2084
|
+
if (encoded.body.byteLength > MAX_SESSION_REPLAY_CHUNK_BYTES) {
|
|
2085
|
+
this.options.onDiagnostic("chunk-too-large", {
|
|
2086
|
+
chunkIndex: frame.envelope.chunkIndex,
|
|
2087
|
+
bytes: encoded.body.byteLength
|
|
2088
|
+
});
|
|
2089
|
+
return "remove";
|
|
2090
|
+
}
|
|
2091
|
+
let response;
|
|
2092
|
+
let said;
|
|
2093
|
+
const controller = typeof AbortController === "undefined" ? null : new AbortController();
|
|
2094
|
+
this.activeAbortController = controller;
|
|
2095
|
+
let timedOut = false;
|
|
2096
|
+
let timeout = null;
|
|
2097
|
+
let abortDeadline = null;
|
|
2098
|
+
try {
|
|
2099
|
+
const request = (async () => {
|
|
2100
|
+
const requestInit = {
|
|
2101
|
+
method: "POST",
|
|
2102
|
+
headers: {
|
|
2103
|
+
...mobileRequestHeaders(this.options.startOptions),
|
|
2104
|
+
"Content-Type": SESSION_REPLAY_CONTENT_TYPE
|
|
2105
|
+
},
|
|
2106
|
+
body: encoded.body
|
|
2107
|
+
};
|
|
2108
|
+
if (controller) {
|
|
2109
|
+
requestInit.signal = controller.signal;
|
|
2110
|
+
}
|
|
2111
|
+
const fetched = await this.fetch(
|
|
2112
|
+
`${this.options.startOptions.host}${CHUNK_PATH}`,
|
|
2113
|
+
requestInit
|
|
2114
|
+
);
|
|
2115
|
+
return { response: fetched, said: await readDirective(fetched) };
|
|
2116
|
+
})();
|
|
2117
|
+
const deadline = new Promise(
|
|
2118
|
+
(_resolve, reject) => {
|
|
2119
|
+
abortDeadline = () => {
|
|
2120
|
+
controller?.abort();
|
|
2121
|
+
reject(new Error("chunk-post-aborted"));
|
|
2122
|
+
};
|
|
2123
|
+
this.abortCurrentRequest = abortDeadline;
|
|
2124
|
+
timeout = setTimeout(() => {
|
|
2125
|
+
timedOut = true;
|
|
2126
|
+
abortDeadline?.();
|
|
2127
|
+
}, CHUNK_POST_TIMEOUT_MS);
|
|
2128
|
+
}
|
|
2129
|
+
);
|
|
2130
|
+
({ response, said } = await Promise.race([request, deadline]));
|
|
2131
|
+
} catch {
|
|
2132
|
+
if (this.stopped) {
|
|
2133
|
+
return "stop";
|
|
2134
|
+
}
|
|
2135
|
+
this.options.onDiagnostic(
|
|
2136
|
+
timedOut ? "chunk-post-timeout" : "chunk-network-failure",
|
|
2137
|
+
{
|
|
2138
|
+
chunkIndex: frame.envelope.chunkIndex
|
|
2139
|
+
}
|
|
2140
|
+
);
|
|
2141
|
+
return "retry";
|
|
2142
|
+
} finally {
|
|
2143
|
+
if (timeout) {
|
|
2144
|
+
clearTimeout(timeout);
|
|
2145
|
+
}
|
|
2146
|
+
if (this.activeAbortController === controller) {
|
|
2147
|
+
this.activeAbortController = null;
|
|
2148
|
+
}
|
|
2149
|
+
if (this.abortCurrentRequest === abortDeadline) {
|
|
2150
|
+
this.abortCurrentRequest = null;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
if (said.directive) {
|
|
2154
|
+
this.options.onDirective(said.directive, said.reason);
|
|
2155
|
+
}
|
|
2156
|
+
if (said.directive === "stop") {
|
|
2157
|
+
this.stopped = true;
|
|
2158
|
+
await this.options.outbox.clear();
|
|
2159
|
+
return "stop";
|
|
2160
|
+
}
|
|
2161
|
+
if (response.status >= 200 && response.status < 300) {
|
|
2162
|
+
if (said.directive === "throttle") {
|
|
2163
|
+
this.retryAfterDrainMs = Math.min(
|
|
2164
|
+
MAX_RETRY_MS,
|
|
2165
|
+
(said.retryAfterSeconds ?? DEFAULT_RETRY_MS / 1e3) * 1e3
|
|
2166
|
+
);
|
|
2167
|
+
return "remove-and-halt";
|
|
2168
|
+
}
|
|
2169
|
+
return "remove";
|
|
2170
|
+
}
|
|
2171
|
+
if (response.status === 400 || response.status === 413 || response.status === 422) {
|
|
2172
|
+
this.options.onDiagnostic("chunk-refused", {
|
|
2173
|
+
status: response.status,
|
|
2174
|
+
chunkIndex: frame.envelope.chunkIndex,
|
|
2175
|
+
reason: said.reason
|
|
2176
|
+
});
|
|
2177
|
+
return "remove";
|
|
2178
|
+
}
|
|
2179
|
+
if (response.status === 401 || response.status === 403 || response.status === 404) {
|
|
2180
|
+
this.options.onDiagnostic("transport-disabled", {
|
|
2181
|
+
status: response.status,
|
|
2182
|
+
reason: said.reason
|
|
2183
|
+
});
|
|
2184
|
+
this.options.onDirective("stop", said.reason ?? "transport-disabled");
|
|
2185
|
+
this.stopped = true;
|
|
2186
|
+
await this.options.outbox.clear();
|
|
2187
|
+
return "stop";
|
|
2188
|
+
}
|
|
2189
|
+
const retryMs = Math.min(
|
|
2190
|
+
MAX_RETRY_MS,
|
|
2191
|
+
(said.retryAfterSeconds ?? DEFAULT_RETRY_MS / 1e3) * 1e3
|
|
2192
|
+
);
|
|
2193
|
+
this.retryAfterDrainMs = retryMs;
|
|
2194
|
+
return "retry";
|
|
2195
|
+
}
|
|
2196
|
+
scheduleRetry(delayMs) {
|
|
2197
|
+
if (this.retryTimer || this.stopped) {
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
this.retryTimer = setTimeout(
|
|
2201
|
+
() => {
|
|
2202
|
+
this.retryTimer = null;
|
|
2203
|
+
void this.drain();
|
|
2204
|
+
},
|
|
2205
|
+
Math.max(0, delayMs)
|
|
2206
|
+
);
|
|
2207
|
+
}
|
|
2208
|
+
};
|
|
2209
|
+
|
|
2210
|
+
// src/TouchPrivacy.ts
|
|
2211
|
+
var MAX_PRIVACY_TREE_NODES = 5e3;
|
|
2212
|
+
var OPAQUE_KINDS = /* @__PURE__ */ new Set([
|
|
2213
|
+
"image",
|
|
2214
|
+
"webview",
|
|
2215
|
+
"canvas",
|
|
2216
|
+
"masked"
|
|
2217
|
+
]);
|
|
2218
|
+
var CONSERVATIVE_TOUCH_PRIVACY_MAP = {
|
|
2219
|
+
regions: [],
|
|
2220
|
+
suppressAll: true
|
|
2221
|
+
};
|
|
2222
|
+
function finite(value) {
|
|
2223
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
2224
|
+
}
|
|
2225
|
+
function deriveTouchPrivacyMap(root) {
|
|
2226
|
+
const regions = [];
|
|
2227
|
+
const hasRootOrigin = finite(root.touchOriginX) && finite(root.touchOriginY);
|
|
2228
|
+
const rootX = hasRootOrigin ? root.touchOriginX : 0;
|
|
2229
|
+
const rootY = hasRootOrigin ? root.touchOriginY : 0;
|
|
2230
|
+
let suppressAll = finite(root.truncatedNodes) && root.truncatedNodes > 0;
|
|
2231
|
+
let visited = 0;
|
|
2232
|
+
const visit = (node, parentX, parentY, alignedPath, isRoot) => {
|
|
2233
|
+
if (visited >= MAX_PRIVACY_TREE_NODES) {
|
|
2234
|
+
suppressAll = true;
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
visited += 1;
|
|
2238
|
+
const hasGeometry = finite(node.x) && finite(node.y) && finite(node.width) && finite(node.height);
|
|
2239
|
+
const left = isRoot ? parentX : parentX + (finite(node.x) ? node.x : 0);
|
|
2240
|
+
const top = isRoot ? parentY : parentY + (finite(node.y) ? node.y : 0);
|
|
2241
|
+
const pathIsAligned = alignedPath && hasGeometry;
|
|
2242
|
+
const opaque = node.masked === true || node.opaque === true || OPAQUE_KINDS.has(typeof node.kind === "string" ? node.kind : "");
|
|
2243
|
+
if (opaque) {
|
|
2244
|
+
if (!pathIsAligned || !hasRootOrigin) {
|
|
2245
|
+
suppressAll = true;
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
if (node.width > 0 && node.height > 0) {
|
|
2249
|
+
regions.push({
|
|
2250
|
+
left,
|
|
2251
|
+
top,
|
|
2252
|
+
right: left + node.width,
|
|
2253
|
+
bottom: top + node.height
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
if (Array.isArray(node.children)) {
|
|
2259
|
+
for (const child of node.children) {
|
|
2260
|
+
if (child && typeof child === "object") {
|
|
2261
|
+
visit(child, left, top, pathIsAligned, false);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
};
|
|
2266
|
+
visit(root, rootX, rootY, hasRootOrigin, true);
|
|
2267
|
+
return { regions, suppressAll };
|
|
2268
|
+
}
|
|
2269
|
+
function isTouchPrivate(privacyMap, x2, y) {
|
|
2270
|
+
if (privacyMap.suppressAll || !finite(x2) || !finite(y)) {
|
|
2271
|
+
return true;
|
|
2272
|
+
}
|
|
2273
|
+
return privacyMap.regions.some((region) => {
|
|
2274
|
+
return x2 >= region.left && x2 <= region.right && y >= region.top && y <= region.bottom;
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
// src/MobileReplayRecorder.ts
|
|
2279
|
+
var MAX_DIAGNOSTIC_EVENTS = 100;
|
|
2280
|
+
var TOUCH_MOVE_SAMPLE_MS = 50;
|
|
2281
|
+
var MOBILE_POLICY_REFRESH_INTERVAL_MS = 5 * 6e4;
|
|
2282
|
+
var SAFE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
2283
|
+
"AggregateError",
|
|
2284
|
+
"Error",
|
|
2285
|
+
"EvalError",
|
|
2286
|
+
"RangeError",
|
|
2287
|
+
"ReferenceError",
|
|
2288
|
+
"SyntaxError",
|
|
2289
|
+
"TypeError",
|
|
2290
|
+
"URIError"
|
|
2291
|
+
]);
|
|
2292
|
+
function defaultNow() {
|
|
2293
|
+
return Date.now();
|
|
2294
|
+
}
|
|
2295
|
+
function deterministicSample(sessionId, percentage) {
|
|
2296
|
+
if (percentage >= 100) {
|
|
2297
|
+
return true;
|
|
2298
|
+
}
|
|
2299
|
+
if (percentage <= 0) {
|
|
2300
|
+
return false;
|
|
2301
|
+
}
|
|
2302
|
+
let hash = 2166136261;
|
|
2303
|
+
for (let index = 0; index < sessionId.length; index += 1) {
|
|
2304
|
+
hash ^= sessionId.charCodeAt(index);
|
|
2305
|
+
hash = Math.imul(hash, 16777619);
|
|
2306
|
+
}
|
|
2307
|
+
return (hash >>> 0) % 1e4 < Math.round(percentage * 100);
|
|
2308
|
+
}
|
|
2309
|
+
function hashNamespaceSecret(value) {
|
|
2310
|
+
const seeds = [
|
|
2311
|
+
2166136261,
|
|
2312
|
+
2654435761,
|
|
2313
|
+
1013904223,
|
|
2314
|
+
3747611933
|
|
2315
|
+
];
|
|
2316
|
+
return seeds.map((seed) => {
|
|
2317
|
+
let hash = seed;
|
|
2318
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2319
|
+
hash ^= value.charCodeAt(index);
|
|
2320
|
+
hash = Math.imul(hash, 16777619);
|
|
2321
|
+
}
|
|
2322
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
2323
|
+
}).join("");
|
|
2324
|
+
}
|
|
2325
|
+
function getReplayStorageNamespace(options) {
|
|
2326
|
+
const scope = [
|
|
2327
|
+
options.host,
|
|
2328
|
+
options.appIdentifier,
|
|
2329
|
+
options.mobileAppIdentifier,
|
|
2330
|
+
hashNamespaceSecret(options.token)
|
|
2331
|
+
].join("\0");
|
|
2332
|
+
return `v1-${hashNamespaceSecret(scope)}`;
|
|
2333
|
+
}
|
|
2334
|
+
function safeMetadataValue(value, fallback, maximumLength = 100) {
|
|
2335
|
+
return typeof value === "string" && value.trim() ? truncate(value.trim(), maximumLength) : fallback;
|
|
2336
|
+
}
|
|
2337
|
+
function isRetryablePolicyFailure(config) {
|
|
2338
|
+
if (config.disabledReason === "config-fetch-failed") {
|
|
2339
|
+
return true;
|
|
2340
|
+
}
|
|
2341
|
+
const match = config.disabledReason?.match(/^config-http-(\d{3})$/u) ?? null;
|
|
2342
|
+
if (!match) {
|
|
2343
|
+
return false;
|
|
2344
|
+
}
|
|
2345
|
+
const status = Number(match[1]);
|
|
2346
|
+
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
2347
|
+
}
|
|
2348
|
+
var MobileReplayRecorder = class {
|
|
2349
|
+
constructor(dependencies = {}) {
|
|
2350
|
+
__publicField(this, "storage");
|
|
2351
|
+
__publicField(this, "nativeViewTree");
|
|
2352
|
+
__publicField(this, "appState");
|
|
2353
|
+
__publicField(this, "now");
|
|
2354
|
+
__publicField(this, "serializer", new ViewTreeSerializer());
|
|
2355
|
+
__publicField(this, "chunkBuffer", new ReplayChunkBuffer());
|
|
2356
|
+
__publicField(this, "rollingBuffer", new RollingReplayBuffer());
|
|
2357
|
+
__publicField(this, "diagnosticsStatus", "idle");
|
|
2358
|
+
__publicField(this, "diagnosticsEvents", []);
|
|
2359
|
+
__publicField(this, "options", null);
|
|
2360
|
+
__publicField(this, "config", null);
|
|
2361
|
+
__publicField(this, "sessionStore", null);
|
|
2362
|
+
__publicField(this, "identity", null);
|
|
2363
|
+
__publicField(this, "outbox", null);
|
|
2364
|
+
__publicField(this, "transport", null);
|
|
2365
|
+
__publicField(this, "metadata", {
|
|
2366
|
+
appName: "React Native",
|
|
2367
|
+
appVersion: "unknown",
|
|
2368
|
+
osName: "unknown",
|
|
2369
|
+
osVersion: "unknown"
|
|
2370
|
+
});
|
|
2371
|
+
__publicField(this, "rootTag", null);
|
|
2372
|
+
__publicField(this, "route", "/");
|
|
2373
|
+
__publicField(this, "entryUrl", "");
|
|
2374
|
+
__publicField(this, "captureTimer", null);
|
|
2375
|
+
__publicField(this, "flushTimer", null);
|
|
2376
|
+
__publicField(this, "appStateSubscription", null);
|
|
2377
|
+
__publicField(this, "captureGeneration", null);
|
|
2378
|
+
__publicField(this, "running", false);
|
|
2379
|
+
__publicField(this, "foreground", true);
|
|
2380
|
+
__publicField(this, "forceFullSnapshot", true);
|
|
2381
|
+
__publicField(this, "lastFullSnapshotAtUnixMs", 0);
|
|
2382
|
+
__publicField(this, "sampled", false);
|
|
2383
|
+
__publicField(this, "triggered", false);
|
|
2384
|
+
__publicField(this, "uploadActive", false);
|
|
2385
|
+
__publicField(this, "consentGranted", false);
|
|
2386
|
+
__publicField(this, "triggerReason", "sampled" /* Sampled */);
|
|
2387
|
+
__publicField(this, "userRef", null);
|
|
2388
|
+
__publicField(this, "traits", {});
|
|
2389
|
+
__publicField(this, "tags", {});
|
|
2390
|
+
__publicField(this, "metaDirty", true);
|
|
2391
|
+
__publicField(this, "droppedEvents", 0);
|
|
2392
|
+
__publicField(this, "closeQueue", Promise.resolve());
|
|
2393
|
+
__publicField(this, "lastTouchMoveAtUnixMs", 0);
|
|
2394
|
+
__publicField(this, "previousGlobalErrorHandler", null);
|
|
2395
|
+
__publicField(this, "installedGlobalErrorHandler", null);
|
|
2396
|
+
__publicField(this, "globalErrorUtils", null);
|
|
2397
|
+
__publicField(this, "lifecycleGeneration", 0);
|
|
2398
|
+
__publicField(this, "lifecycleCancellationEpoch", 0);
|
|
2399
|
+
__publicField(this, "lifecycleQueue", Promise.resolve());
|
|
2400
|
+
__publicField(this, "pendingStart", null);
|
|
2401
|
+
__publicField(this, "pendingStartCancellation", null);
|
|
2402
|
+
__publicField(this, "identityPersisted", false);
|
|
2403
|
+
__publicField(this, "sessionLastActivityUnixMs", 0);
|
|
2404
|
+
__publicField(this, "rotatingSession", false);
|
|
2405
|
+
__publicField(this, "rotationPromise", null);
|
|
2406
|
+
__publicField(this, "appStateQueue", Promise.resolve());
|
|
2407
|
+
__publicField(this, "acceptingEvents", false);
|
|
2408
|
+
__publicField(this, "lastPolicyRefreshAtUnixMs", 0);
|
|
2409
|
+
__publicField(this, "policyRefreshPromise", null);
|
|
2410
|
+
__publicField(this, "identityQueue", Promise.resolve());
|
|
2411
|
+
__publicField(this, "identityCancellationEpoch", 0);
|
|
2412
|
+
__publicField(this, "pendingIdentityOperations", 0);
|
|
2413
|
+
__publicField(this, "sessionChangeListeners", /* @__PURE__ */ new Set());
|
|
2414
|
+
__publicField(this, "lastNotifiedSessionId", null);
|
|
2415
|
+
__publicField(this, "touchPrivacyMap", CONSERVATIVE_TOUCH_PRIVACY_MAP);
|
|
2416
|
+
__publicField(this, "touchQueue", Promise.resolve());
|
|
2417
|
+
__publicField(this, "suppressedTouchPointers", /* @__PURE__ */ new Set());
|
|
2418
|
+
__publicField(this, "unidentifiedTouchCount", 0);
|
|
2419
|
+
__publicField(this, "unidentifiedTouchPrivate", false);
|
|
2420
|
+
this.storage = dependencies.storage ?? defaultReplayStorage;
|
|
2421
|
+
this.nativeViewTree = dependencies.nativeViewTree ?? defaultNativeViewTreeAdapter;
|
|
2422
|
+
this.appState = dependencies.appState ?? AppState;
|
|
2423
|
+
this.now = dependencies.now ?? defaultNow;
|
|
2424
|
+
}
|
|
2425
|
+
start(options) {
|
|
2426
|
+
if (this.pendingStart && this.pendingStart.generation === this.lifecycleCancellationEpoch) {
|
|
2427
|
+
return this.pendingStart.promise;
|
|
2428
|
+
}
|
|
2429
|
+
const requestGeneration = this.lifecycleCancellationEpoch;
|
|
2430
|
+
const identityBarrier = this.identityQueue;
|
|
2431
|
+
const promise = this.enqueueLifecycleOperation(
|
|
2432
|
+
async () => {
|
|
2433
|
+
await identityBarrier;
|
|
2434
|
+
if (requestGeneration !== this.lifecycleCancellationEpoch) {
|
|
2435
|
+
return false;
|
|
2436
|
+
}
|
|
2437
|
+
return await this.startNow(options);
|
|
2438
|
+
}
|
|
2439
|
+
);
|
|
2440
|
+
this.pendingStart = { generation: requestGeneration, promise };
|
|
2441
|
+
void promise.then(
|
|
2442
|
+
() => {
|
|
2443
|
+
if (this.pendingStart?.promise === promise) {
|
|
2444
|
+
this.pendingStart = null;
|
|
2445
|
+
}
|
|
2446
|
+
},
|
|
2447
|
+
() => {
|
|
2448
|
+
if (this.pendingStart?.promise === promise) {
|
|
2449
|
+
this.pendingStart = null;
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
);
|
|
2453
|
+
return promise;
|
|
2454
|
+
}
|
|
2455
|
+
async startNow(options) {
|
|
2456
|
+
if (this.running) {
|
|
2457
|
+
return true;
|
|
2458
|
+
}
|
|
2459
|
+
const generation = this.lifecycleGeneration + 1;
|
|
2460
|
+
this.lifecycleGeneration = generation;
|
|
2461
|
+
let cancelStart = () => {
|
|
2462
|
+
return void 0;
|
|
2463
|
+
};
|
|
2464
|
+
const cancellation = new Promise(
|
|
2465
|
+
(resolve) => {
|
|
2466
|
+
cancelStart = () => {
|
|
2467
|
+
resolve(false);
|
|
2468
|
+
};
|
|
2469
|
+
}
|
|
2470
|
+
);
|
|
2471
|
+
this.pendingStartCancellation = { generation, cancel: cancelStart };
|
|
2472
|
+
const work = this.startInternal(options, generation);
|
|
2473
|
+
try {
|
|
2474
|
+
return await Promise.race([work, cancellation]);
|
|
2475
|
+
} finally {
|
|
2476
|
+
if (this.pendingStartCancellation?.generation === generation) {
|
|
2477
|
+
this.pendingStartCancellation = null;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
async startInternal(options, generation) {
|
|
2482
|
+
this.acceptingEvents = false;
|
|
2483
|
+
this.diagnosticsStatus = "starting";
|
|
2484
|
+
this.serializer.reset();
|
|
2485
|
+
this.chunkBuffer.clear();
|
|
2486
|
+
this.rollingBuffer.clear();
|
|
2487
|
+
this.closeQueue = Promise.resolve();
|
|
2488
|
+
this.appStateQueue = Promise.resolve();
|
|
2489
|
+
this.rotationPromise = null;
|
|
2490
|
+
this.rotatingSession = false;
|
|
2491
|
+
this.policyRefreshPromise = null;
|
|
2492
|
+
this.lastPolicyRefreshAtUnixMs = 0;
|
|
2493
|
+
this.forceFullSnapshot = true;
|
|
2494
|
+
this.lastFullSnapshotAtUnixMs = 0;
|
|
2495
|
+
this.touchPrivacyMap = CONSERVATIVE_TOUCH_PRIVACY_MAP;
|
|
2496
|
+
this.touchQueue = Promise.resolve();
|
|
2497
|
+
this.resetTouchSequences();
|
|
2498
|
+
const validated = validateStartOptions(options);
|
|
2499
|
+
if (!validated) {
|
|
2500
|
+
this.disable("invalid-start-options");
|
|
2501
|
+
return false;
|
|
2502
|
+
}
|
|
2503
|
+
if (validated.userRef) {
|
|
2504
|
+
this.userRef = truncate(validated.userRef, MAX_USER_REFERENCE_LENGTH);
|
|
2505
|
+
}
|
|
2506
|
+
this.options = this.userRef ? { ...validated, userRef: this.userRef } : validated;
|
|
2507
|
+
if (this.nativeViewTree.isAvailable?.() === false) {
|
|
2508
|
+
this.disable("native-module-unavailable", {
|
|
2509
|
+
expoGoUnsupported: true
|
|
2510
|
+
});
|
|
2511
|
+
return false;
|
|
2512
|
+
}
|
|
2513
|
+
let config = await fetchReplayConfig(this.options);
|
|
2514
|
+
if (!this.isCurrentGeneration(generation)) {
|
|
2515
|
+
return false;
|
|
2516
|
+
}
|
|
2517
|
+
if (config.enabled && this.userRef && (config.consentMode === "NotRequired" /* NotRequired */ || this.consentGranted)) {
|
|
2518
|
+
config = await this.refreshTargetingConfig(config);
|
|
2519
|
+
if (!this.isCurrentGeneration(generation)) {
|
|
2520
|
+
return false;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
this.config = config;
|
|
2524
|
+
if (!config.enabled) {
|
|
2525
|
+
this.disable("config-disabled", {
|
|
2526
|
+
reason: config.disabledReason ?? "not-reported"
|
|
2527
|
+
});
|
|
2528
|
+
return false;
|
|
2529
|
+
}
|
|
2530
|
+
this.lastPolicyRefreshAtUnixMs = this.now();
|
|
2531
|
+
const namespace = getReplayStorageNamespace(validated);
|
|
2532
|
+
const sessionStore = new ReplaySessionStore(
|
|
2533
|
+
this.storage,
|
|
2534
|
+
namespace
|
|
2535
|
+
);
|
|
2536
|
+
this.sessionStore = sessionStore;
|
|
2537
|
+
const nowUnixMs = this.now();
|
|
2538
|
+
const awaitingConsent = config.consentMode === "RequireExplicit" /* RequireExplicit */ && !this.consentGranted;
|
|
2539
|
+
if (awaitingConsent) {
|
|
2540
|
+
this.identity = this.ephemeralIdentity(nowUnixMs);
|
|
2541
|
+
this.identityPersisted = false;
|
|
2542
|
+
this.sampled = false;
|
|
2543
|
+
} else {
|
|
2544
|
+
const identity = await sessionStore.resolve(nowUnixMs);
|
|
2545
|
+
if (!this.isCurrentGeneration(generation) || this.sessionStore !== sessionStore) {
|
|
2546
|
+
return false;
|
|
2547
|
+
}
|
|
2548
|
+
this.identity = identity;
|
|
2549
|
+
this.identityPersisted = true;
|
|
2550
|
+
this.sampled = deterministicSample(
|
|
2551
|
+
this.identity.sessionId,
|
|
2552
|
+
config.samplePercentage
|
|
2553
|
+
);
|
|
2554
|
+
}
|
|
2555
|
+
this.sessionLastActivityUnixMs = nowUnixMs;
|
|
2556
|
+
if (!awaitingConsent && config.captureTrigger === "Always" /* Always */ && !this.sampled && !config.isTargeted) {
|
|
2557
|
+
this.disable("not-sampled");
|
|
2558
|
+
return false;
|
|
2559
|
+
}
|
|
2560
|
+
this.outbox = new ReplayOutbox(this.storage, namespace);
|
|
2561
|
+
this.transport = this.createTransport(validated, this.outbox);
|
|
2562
|
+
try {
|
|
2563
|
+
const nativeMetadata = await this.nativeViewTree.getAppMetadata();
|
|
2564
|
+
if (!this.isCurrentGeneration(generation)) {
|
|
2565
|
+
return false;
|
|
2566
|
+
}
|
|
2567
|
+
this.metadata = {
|
|
2568
|
+
appName: safeMetadataValue(
|
|
2569
|
+
validated.appName,
|
|
2570
|
+
safeMetadataValue(nativeMetadata?.appName, "React Native")
|
|
2571
|
+
),
|
|
2572
|
+
appVersion: safeMetadataValue(
|
|
2573
|
+
validated.appVersion,
|
|
2574
|
+
safeMetadataValue(nativeMetadata?.appVersion, "unknown")
|
|
2575
|
+
),
|
|
2576
|
+
osName: safeMetadataValue(nativeMetadata?.osName, "unknown"),
|
|
2577
|
+
osVersion: safeMetadataValue(nativeMetadata?.osVersion, "unknown")
|
|
2578
|
+
};
|
|
2579
|
+
} catch {
|
|
2580
|
+
if (!this.isCurrentGeneration(generation)) {
|
|
2581
|
+
return false;
|
|
2582
|
+
}
|
|
2583
|
+
this.diagnostic("native-metadata-unavailable");
|
|
2584
|
+
}
|
|
2585
|
+
this.route = "/";
|
|
2586
|
+
this.entryUrl = toAppUrl(validated.mobileAppIdentifier, this.route);
|
|
2587
|
+
this.running = true;
|
|
2588
|
+
this.acceptingEvents = true;
|
|
2589
|
+
this.triggered = config.isTargeted;
|
|
2590
|
+
this.triggerReason = config.isTargeted ? "manual" /* Manual */ : "sampled" /* Sampled */;
|
|
2591
|
+
const hasConsent = config.consentMode === "NotRequired" /* NotRequired */ || this.consentGranted;
|
|
2592
|
+
this.uploadActive = hasConsent && (this.sampled || config.isTargeted || this.triggered);
|
|
2593
|
+
this.foreground = this.appState.currentState === "active";
|
|
2594
|
+
this.diagnosticsStatus = hasConsent ? this.foreground ? "recording" : "background" : "consent-required";
|
|
2595
|
+
this.notifySessionChange();
|
|
2596
|
+
this.installLifecycle();
|
|
2597
|
+
this.installGlobalErrorHandler();
|
|
2598
|
+
if (this.foreground) {
|
|
2599
|
+
this.startTimers();
|
|
2600
|
+
await this.captureTick(true);
|
|
2601
|
+
}
|
|
2602
|
+
if (!this.running) {
|
|
2603
|
+
return false;
|
|
2604
|
+
}
|
|
2605
|
+
if (this.uploadActive) {
|
|
2606
|
+
await this.transport.restore();
|
|
2607
|
+
}
|
|
2608
|
+
if (!this.isCurrentGeneration(generation) || !this.running) {
|
|
2609
|
+
return false;
|
|
2610
|
+
}
|
|
2611
|
+
this.diagnostic("recorder-started", {
|
|
2612
|
+
sampled: this.sampled,
|
|
2613
|
+
consentState: this.consentState()
|
|
2614
|
+
});
|
|
2615
|
+
return true;
|
|
2616
|
+
}
|
|
2617
|
+
stop() {
|
|
2618
|
+
const identityBarrier = this.identityQueue;
|
|
2619
|
+
this.lifecycleGeneration += 1;
|
|
2620
|
+
this.lifecycleCancellationEpoch += 1;
|
|
2621
|
+
this.pendingStartCancellation?.cancel();
|
|
2622
|
+
this.acceptingEvents = false;
|
|
2623
|
+
this.quiesceCapture();
|
|
2624
|
+
return this.enqueueLifecycleOperation(async () => {
|
|
2625
|
+
await identityBarrier;
|
|
2626
|
+
await this.stopInternal();
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
async stopInternal() {
|
|
2630
|
+
if (!this.running) {
|
|
2631
|
+
this.diagnosticsStatus = "stopped";
|
|
2632
|
+
return;
|
|
2633
|
+
}
|
|
2634
|
+
if (this.rotationPromise) {
|
|
2635
|
+
await this.rotationPromise;
|
|
2636
|
+
}
|
|
2637
|
+
if (!this.running) {
|
|
2638
|
+
this.diagnosticsStatus = "stopped";
|
|
2639
|
+
return;
|
|
2640
|
+
}
|
|
2641
|
+
this.appendCustom(
|
|
2642
|
+
VISIBILITY_CUSTOM_EVENT_TAG,
|
|
2643
|
+
{ state: "stopped" },
|
|
2644
|
+
void 0,
|
|
2645
|
+
true
|
|
2646
|
+
);
|
|
2647
|
+
this.haltCapture();
|
|
2648
|
+
await this.closeCurrent(true);
|
|
2649
|
+
if (this.uploadActive) {
|
|
2650
|
+
await this.transport?.drain();
|
|
2651
|
+
} else {
|
|
2652
|
+
this.rollingBuffer.clear();
|
|
2653
|
+
}
|
|
2654
|
+
this.transport?.destroy();
|
|
2655
|
+
this.diagnosticsStatus = "stopped";
|
|
2656
|
+
this.diagnostic("recorder-stopped");
|
|
2657
|
+
}
|
|
2658
|
+
grantConsent() {
|
|
2659
|
+
const identityBarrier = this.identityQueue;
|
|
2660
|
+
return this.enqueueLifecycleOperation(async () => {
|
|
2661
|
+
await identityBarrier;
|
|
2662
|
+
await this.grantConsentInternal();
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
async grantConsentInternal() {
|
|
2666
|
+
this.consentGranted = true;
|
|
2667
|
+
if (!this.running && this.options) {
|
|
2668
|
+
const options = this.options;
|
|
2669
|
+
await this.startNow(options);
|
|
2670
|
+
return;
|
|
2671
|
+
}
|
|
2672
|
+
if (!this.running || !this.config) {
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
const generation = this.lifecycleGeneration;
|
|
2676
|
+
const refreshed = await this.refreshRuntimePolicy(
|
|
2677
|
+
generation,
|
|
2678
|
+
true,
|
|
2679
|
+
"consent"
|
|
2680
|
+
);
|
|
2681
|
+
if (!refreshed || !this.running || !this.consentGranted || !this.isCurrentGeneration(generation)) {
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
this.diagnosticsStatus = this.foreground ? "recording" : "background";
|
|
2685
|
+
this.diagnostic("consent-granted");
|
|
2686
|
+
}
|
|
2687
|
+
revokeConsent() {
|
|
2688
|
+
this.lifecycleGeneration += 1;
|
|
2689
|
+
this.lifecycleCancellationEpoch += 1;
|
|
2690
|
+
this.identityCancellationEpoch += 1;
|
|
2691
|
+
this.pendingStartCancellation?.cancel();
|
|
2692
|
+
this.consentGranted = false;
|
|
2693
|
+
this.acceptingEvents = false;
|
|
2694
|
+
this.haltCapture();
|
|
2695
|
+
this.transport?.destroy();
|
|
2696
|
+
return this.enqueueLifecycleOperation(async () => {
|
|
2697
|
+
return this.revokeConsentInternal();
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
async revokeConsentInternal() {
|
|
2701
|
+
this.haltCapture();
|
|
2702
|
+
this.transport?.destroy();
|
|
2703
|
+
this.chunkBuffer.clear();
|
|
2704
|
+
this.rollingBuffer.clear();
|
|
2705
|
+
this.serializer.reset();
|
|
2706
|
+
await this.closeQueue.catch(() => {
|
|
2707
|
+
return void 0;
|
|
2708
|
+
});
|
|
2709
|
+
await this.outbox?.clear();
|
|
2710
|
+
await this.sessionStore?.clear();
|
|
2711
|
+
this.identity = null;
|
|
2712
|
+
this.userRef = null;
|
|
2713
|
+
this.traits = {};
|
|
2714
|
+
this.tags = {};
|
|
2715
|
+
this.uploadActive = false;
|
|
2716
|
+
this.triggered = false;
|
|
2717
|
+
this.diagnosticsStatus = "consent-revoked";
|
|
2718
|
+
this.diagnostic("consent-revoked");
|
|
2719
|
+
}
|
|
2720
|
+
identify(userRef, traits) {
|
|
2721
|
+
const trimmedRef = typeof userRef === "string" ? userRef.trim() : "";
|
|
2722
|
+
const ref = trimmedRef ? truncate(trimmedRef, MAX_USER_REFERENCE_LENGTH) : null;
|
|
2723
|
+
const sanitizedTraits = traits === void 0 ? null : maskStringMapValues(sanitizeTraits(traits));
|
|
2724
|
+
const identityEpoch = this.identityCancellationEpoch;
|
|
2725
|
+
const lifecycleBarrier = this.lifecycleQueue;
|
|
2726
|
+
this.pendingIdentityOperations += 1;
|
|
2727
|
+
const operation = this.identityQueue.then(
|
|
2728
|
+
async () => {
|
|
2729
|
+
await lifecycleBarrier;
|
|
2730
|
+
await this.applyIdentityChange(ref, sanitizedTraits, identityEpoch);
|
|
2731
|
+
}
|
|
2732
|
+
);
|
|
2733
|
+
this.identityQueue = operation.then(
|
|
2734
|
+
() => {
|
|
2735
|
+
this.pendingIdentityOperations = Math.max(
|
|
2736
|
+
0,
|
|
2737
|
+
this.pendingIdentityOperations - 1
|
|
2738
|
+
);
|
|
2739
|
+
},
|
|
2740
|
+
() => {
|
|
2741
|
+
this.pendingIdentityOperations = Math.max(
|
|
2742
|
+
0,
|
|
2743
|
+
this.pendingIdentityOperations - 1
|
|
2744
|
+
);
|
|
2745
|
+
this.diagnostic("identity-change-failed");
|
|
2746
|
+
}
|
|
2747
|
+
);
|
|
2748
|
+
}
|
|
2749
|
+
setTags(tags) {
|
|
2750
|
+
const sanitizedTags = sanitizeTags(tags);
|
|
2751
|
+
this.runExternalActivity(this.now(), () => {
|
|
2752
|
+
this.tags = sanitizedTags;
|
|
2753
|
+
this.metaDirty = true;
|
|
2754
|
+
this.appendCustom(TAGS_CUSTOM_EVENT_TAG, { ...this.tags });
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
addTag(key, value) {
|
|
2758
|
+
if (typeof key !== "string") {
|
|
2759
|
+
return;
|
|
2760
|
+
}
|
|
2761
|
+
const safeKey = truncate(key.trim(), MAX_TAG_KEY_LENGTH);
|
|
2762
|
+
if (!safeKey) {
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
const safeValue = truncate(String(value), MAX_TAG_VALUE_LENGTH);
|
|
2766
|
+
this.runExternalActivity(this.now(), () => {
|
|
2767
|
+
this.tags = sanitizeTags({
|
|
2768
|
+
...this.tags,
|
|
2769
|
+
[safeKey]: safeValue
|
|
2770
|
+
});
|
|
2771
|
+
this.metaDirty = true;
|
|
2772
|
+
this.appendCustom(TAGS_CUSTOM_EVENT_TAG, { ...this.tags });
|
|
2773
|
+
});
|
|
2774
|
+
}
|
|
2775
|
+
track(name, properties) {
|
|
2776
|
+
const eventName = typeof name === "string" ? truncate(name.trim(), MAX_CUSTOM_EVENT_NAME_LENGTH) : "";
|
|
2777
|
+
if (!eventName) {
|
|
2778
|
+
return;
|
|
2779
|
+
}
|
|
2780
|
+
const sanitizedProperties = maskStringMapValues(
|
|
2781
|
+
sanitizeEventProperties(properties)
|
|
2782
|
+
);
|
|
2783
|
+
this.runExternalActivity(this.now(), () => {
|
|
2784
|
+
this.appendCustom(
|
|
2785
|
+
CUSTOM_EVENT_TAG,
|
|
2786
|
+
{ name: eventName, properties: sanitizedProperties },
|
|
2787
|
+
"customEventCount"
|
|
2788
|
+
);
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
async setRoute(route) {
|
|
2792
|
+
await this.identityQueue;
|
|
2793
|
+
if (this.running && !this.acceptingEvents) {
|
|
2794
|
+
this.route = sanitizeRoute(route);
|
|
2795
|
+
return;
|
|
2796
|
+
}
|
|
2797
|
+
await this.maybeRotateSession(this.now());
|
|
2798
|
+
if (!this.options) {
|
|
2799
|
+
this.route = sanitizeRoute(route);
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2802
|
+
const previous = this.route;
|
|
2803
|
+
const next = sanitizeRoute(route);
|
|
2804
|
+
if (previous === next) {
|
|
2805
|
+
return;
|
|
2806
|
+
}
|
|
2807
|
+
this.route = next;
|
|
2808
|
+
this.appendCustom(
|
|
2809
|
+
ROUTE_CUSTOM_EVENT_TAG,
|
|
2810
|
+
{
|
|
2811
|
+
from: toAppUrl(this.options.mobileAppIdentifier, previous),
|
|
2812
|
+
to: toAppUrl(this.options.mobileAppIdentifier, next),
|
|
2813
|
+
kind: "manual"
|
|
2814
|
+
},
|
|
2815
|
+
"routeCount"
|
|
2816
|
+
);
|
|
2817
|
+
await this.closeCurrent(false);
|
|
2818
|
+
this.forceFullSnapshot = true;
|
|
2819
|
+
await this.captureTick(true);
|
|
2820
|
+
}
|
|
2821
|
+
async captureSession(reason = "manual") {
|
|
2822
|
+
await this.identityQueue;
|
|
2823
|
+
if (!this.running || !this.acceptingEvents) {
|
|
2824
|
+
return;
|
|
2825
|
+
}
|
|
2826
|
+
await this.maybeRotateSession(this.now());
|
|
2827
|
+
const safeReason2 = truncate(
|
|
2828
|
+
(typeof reason === "string" ? reason.trim() : "") || "manual",
|
|
2829
|
+
MAX_CAPTURE_REASON_LENGTH
|
|
2830
|
+
);
|
|
2831
|
+
this.appendCustom(CUSTOM_EVENT_TAG, {
|
|
2832
|
+
name: "oneuptime.capture",
|
|
2833
|
+
properties: { reason: maskTextValue(safeReason2) }
|
|
2834
|
+
});
|
|
2835
|
+
await this.trigger("manual" /* Manual */);
|
|
2836
|
+
}
|
|
2837
|
+
async captureError(error) {
|
|
2838
|
+
await this.identityQueue;
|
|
2839
|
+
if (!this.running || !this.acceptingEvents) {
|
|
2840
|
+
return;
|
|
2841
|
+
}
|
|
2842
|
+
await this.maybeRotateSession(this.now());
|
|
2843
|
+
const rawName = error instanceof Error && typeof error.name === "string" ? error.name.trim() : "";
|
|
2844
|
+
const name = SAFE_ERROR_NAMES.has(rawName) ? rawName : "Error";
|
|
2845
|
+
this.appendCustom(
|
|
2846
|
+
ERROR_CUSTOM_EVENT_TAG,
|
|
2847
|
+
{ kind: "error", name, message: "[masked]" },
|
|
2848
|
+
"errorCount"
|
|
2849
|
+
);
|
|
2850
|
+
await this.trigger("error" /* Error */);
|
|
2851
|
+
}
|
|
2852
|
+
recordTouch(touch) {
|
|
2853
|
+
const sanitizedTouch = sanitizeRecordedTouch(
|
|
2854
|
+
touch,
|
|
2855
|
+
this.now()
|
|
2856
|
+
);
|
|
2857
|
+
if (!sanitizedTouch) {
|
|
2858
|
+
this.diagnostic("invalid-touch-event");
|
|
2859
|
+
return;
|
|
2860
|
+
}
|
|
2861
|
+
if (!this.running || !this.acceptingEvents || !this.foreground) {
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
const generation = this.lifecycleGeneration;
|
|
2865
|
+
const operation = this.touchQueue.then(
|
|
2866
|
+
async () => {
|
|
2867
|
+
await this.recordTouchAfterPrivacyCheck(sanitizedTouch, generation);
|
|
2868
|
+
}
|
|
2869
|
+
);
|
|
2870
|
+
this.touchQueue = operation.catch(() => {
|
|
2871
|
+
this.diagnostic("touch-privacy-check-failed");
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
async recordTouchAfterPrivacyCheck(touch, generation) {
|
|
2875
|
+
if (!this.running || !this.acceptingEvents || !this.foreground || !this.isCurrentGeneration(generation)) {
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
const pointIsPrivate = await this.isCurrentTouchPrivate(touch);
|
|
2879
|
+
if (!this.running || !this.acceptingEvents || !this.foreground || !this.isCurrentGeneration(generation)) {
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
if (this.updateTouchSuppression(touch, pointIsPrivate)) {
|
|
2883
|
+
this.diagnostic("touch-suppressed-private-region");
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2886
|
+
if (touch.phase === "move" && touch.timestamp - this.lastTouchMoveAtUnixMs < TOUCH_MOVE_SAMPLE_MS) {
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
if (touch.phase === "move") {
|
|
2890
|
+
this.lastTouchMoveAtUnixMs = touch.timestamp;
|
|
2891
|
+
}
|
|
2892
|
+
const nowUnixMs = this.now();
|
|
2893
|
+
if (this.rotatingSession || this.rotationReason(nowUnixMs)) {
|
|
2894
|
+
this.diagnostic("touch-dropped-session-rotation");
|
|
2895
|
+
void this.maybeRotateSession(nowUnixMs);
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
this.appendEvent(
|
|
2899
|
+
createTouchEvent(touch),
|
|
2900
|
+
touch.phase === "start" ? "clickCount" : void 0
|
|
2901
|
+
);
|
|
2902
|
+
if (touch.phase === "start") {
|
|
2903
|
+
this.appendCustom(TOUCH_CUSTOM_EVENT_TAG, {
|
|
2904
|
+
phase: "start",
|
|
2905
|
+
x: touch.x,
|
|
2906
|
+
y: touch.y
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
setRootTag(rootTag) {
|
|
2911
|
+
this.rootTag = typeof rootTag === "number" && Number.isSafeInteger(rootTag) && rootTag > 0 ? rootTag : null;
|
|
2912
|
+
this.forceFullSnapshot = true;
|
|
2913
|
+
this.touchPrivacyMap = CONSERVATIVE_TOUCH_PRIVACY_MAP;
|
|
2914
|
+
this.resetTouchSequences();
|
|
2915
|
+
if (this.rootTag && this.running && this.acceptingEvents && this.foreground) {
|
|
2916
|
+
void this.captureTick(true);
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
getDiagnostics() {
|
|
2920
|
+
return {
|
|
2921
|
+
status: this.diagnosticsStatus,
|
|
2922
|
+
sessionId: this.identity?.sessionId ?? null,
|
|
2923
|
+
recorderKind: MOBILE_RECORDER_KIND,
|
|
2924
|
+
configEpoch: this.config?.configEpoch ?? 0,
|
|
2925
|
+
sampled: this.sampled,
|
|
2926
|
+
triggered: this.triggered,
|
|
2927
|
+
consentState: this.consentState(),
|
|
2928
|
+
pendingEvents: this.rollingBuffer.length + (this.chunkBuffer.isEmpty() ? 0 : 1),
|
|
2929
|
+
events: this.diagnosticsEvents.map(
|
|
2930
|
+
(event) => {
|
|
2931
|
+
const copied = {
|
|
2932
|
+
code: event.code,
|
|
2933
|
+
atUnixMs: event.atUnixMs
|
|
2934
|
+
};
|
|
2935
|
+
if (event.details) {
|
|
2936
|
+
copied.details = { ...event.details };
|
|
2937
|
+
}
|
|
2938
|
+
return copied;
|
|
2939
|
+
}
|
|
2940
|
+
)
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
/** Current consented replay session for host trace/log correlation. */
|
|
2944
|
+
getSessionId() {
|
|
2945
|
+
return this.running && this.identityPersisted ? this.identity?.sessionId ?? null : null;
|
|
2946
|
+
}
|
|
2947
|
+
/** Fires immediately when a session exists and again on rotate/clear. */
|
|
2948
|
+
onSessionChange(listener) {
|
|
2949
|
+
if (typeof listener !== "function") {
|
|
2950
|
+
return () => {
|
|
2951
|
+
return void 0;
|
|
2952
|
+
};
|
|
2953
|
+
}
|
|
2954
|
+
this.sessionChangeListeners.add(listener);
|
|
2955
|
+
const sessionId = this.getSessionId();
|
|
2956
|
+
if (sessionId) {
|
|
2957
|
+
try {
|
|
2958
|
+
listener(sessionId);
|
|
2959
|
+
} catch {
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
return () => {
|
|
2963
|
+
this.sessionChangeListeners.delete(listener);
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
async trigger(reason) {
|
|
2967
|
+
if (!this.running || !this.acceptingEvents || this.triggered || this.uploadActive || this.sampled || this.config?.isTargeted) {
|
|
2968
|
+
return;
|
|
2969
|
+
}
|
|
2970
|
+
this.triggered = true;
|
|
2971
|
+
this.triggerReason = reason;
|
|
2972
|
+
this.diagnostic("capture-triggered", { reason });
|
|
2973
|
+
if (this.consentState() !== "Unknown") {
|
|
2974
|
+
await this.activateUpload(this.lifecycleGeneration);
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
async activateUpload(generation) {
|
|
2978
|
+
if (!this.running || !this.acceptingEvents || this.uploadActive || !this.isCurrentGeneration(generation)) {
|
|
2979
|
+
return;
|
|
2980
|
+
}
|
|
2981
|
+
await this.closeCurrent(false);
|
|
2982
|
+
if (!this.running || !this.acceptingEvents || !this.isCurrentGeneration(generation)) {
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
this.uploadActive = true;
|
|
2986
|
+
const buffered = this.rollingBuffer.drain();
|
|
2987
|
+
for (const segment of buffered) {
|
|
2988
|
+
if (!this.running || !this.acceptingEvents || !this.isCurrentGeneration(generation)) {
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
await this.uploadSegment(segment, false);
|
|
2992
|
+
}
|
|
2993
|
+
if (!this.running || !this.acceptingEvents || !this.isCurrentGeneration(generation)) {
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
await this.transport?.restore();
|
|
2997
|
+
}
|
|
2998
|
+
async captureTick(force = false) {
|
|
2999
|
+
const generation = this.lifecycleGeneration;
|
|
3000
|
+
if (!this.running || !this.acceptingEvents || !this.foreground || !this.rootTag || this.captureGeneration === generation || !this.options) {
|
|
3001
|
+
return;
|
|
3002
|
+
}
|
|
3003
|
+
this.captureGeneration = generation;
|
|
3004
|
+
try {
|
|
3005
|
+
const nowUnixMs = this.now();
|
|
3006
|
+
const policyReady = await this.refreshRuntimePolicy(
|
|
3007
|
+
generation,
|
|
3008
|
+
false,
|
|
3009
|
+
"periodic"
|
|
3010
|
+
);
|
|
3011
|
+
if (!policyReady) {
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
3014
|
+
await this.maybeRotateSession(nowUnixMs);
|
|
3015
|
+
if (!this.running || !this.acceptingEvents || !this.foreground || !this.isCurrentGeneration(generation)) {
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
const capturedSessionId = this.identity?.sessionId;
|
|
3019
|
+
const tree = await this.nativeViewTree.captureViewTree(this.rootTag);
|
|
3020
|
+
if (!this.running || !this.acceptingEvents || !this.foreground || !this.isCurrentGeneration(generation) || this.rotatingSession || !capturedSessionId || this.identity?.sessionId !== capturedSessionId) {
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
this.touchPrivacyMap = deriveTouchPrivacyMap(tree);
|
|
3024
|
+
const size = getViewport();
|
|
3025
|
+
const shouldForce = force || this.forceFullSnapshot || nowUnixMs - this.lastFullSnapshotAtUnixMs >= SESSION_REPLAY_CHECKOUT_INTERVAL_MS;
|
|
3026
|
+
if (shouldForce && !this.chunkBuffer.isEmpty()) {
|
|
3027
|
+
await this.closeCurrent(false);
|
|
3028
|
+
}
|
|
3029
|
+
if (!this.running || !this.acceptingEvents || !this.foreground || !this.isCurrentGeneration(generation) || this.identity?.sessionId !== capturedSessionId) {
|
|
3030
|
+
return;
|
|
3031
|
+
}
|
|
3032
|
+
const capture = this.serializer.capture(tree, {
|
|
3033
|
+
timestamp: nowUnixMs,
|
|
3034
|
+
url: toAppUrl(this.options.mobileAppIdentifier, this.route),
|
|
3035
|
+
viewportWidth: size.width,
|
|
3036
|
+
viewportHeight: size.height,
|
|
3037
|
+
forceFullSnapshot: shouldForce
|
|
3038
|
+
});
|
|
3039
|
+
this.droppedEvents += capture.droppedNodes;
|
|
3040
|
+
this.chunkBuffer.appendMany(capture.events, {
|
|
3041
|
+
route: toAppUrl(this.options.mobileAppIdentifier, this.route),
|
|
3042
|
+
fidelityNotices: capture.fidelityNotices
|
|
3043
|
+
});
|
|
3044
|
+
if (capture.hasFullSnapshot) {
|
|
3045
|
+
this.lastFullSnapshotAtUnixMs = nowUnixMs;
|
|
3046
|
+
this.forceFullSnapshot = false;
|
|
3047
|
+
}
|
|
3048
|
+
if (this.chunkBuffer.shouldFlush()) {
|
|
3049
|
+
await this.closeCurrent(false);
|
|
3050
|
+
}
|
|
3051
|
+
} catch (error) {
|
|
3052
|
+
if (this.running && this.isCurrentGeneration(generation)) {
|
|
3053
|
+
this.disable("native-capture-failed", {
|
|
3054
|
+
name: error instanceof Error ? error.name : "Error"
|
|
3055
|
+
});
|
|
3056
|
+
this.haltCapture();
|
|
3057
|
+
}
|
|
3058
|
+
} finally {
|
|
3059
|
+
if (this.captureGeneration === generation) {
|
|
3060
|
+
this.captureGeneration = null;
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
appendCustom(tag, payload, signal, allowWhenQuiesced = false) {
|
|
3065
|
+
this.appendEvent(
|
|
3066
|
+
createCustomEvent(tag, payload, this.now()),
|
|
3067
|
+
signal,
|
|
3068
|
+
allowWhenQuiesced
|
|
3069
|
+
);
|
|
3070
|
+
}
|
|
3071
|
+
appendEvent(event, signal, allowWhenQuiesced = false) {
|
|
3072
|
+
if (!this.running || !this.acceptingEvents && !allowWhenQuiesced) {
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
if (!Number.isFinite(event.timestamp)) {
|
|
3076
|
+
this.droppedEvents += 1;
|
|
3077
|
+
this.diagnostic("invalid-event-timestamp");
|
|
3078
|
+
return;
|
|
3079
|
+
}
|
|
3080
|
+
this.sessionLastActivityUnixMs = Math.max(
|
|
3081
|
+
this.sessionLastActivityUnixMs,
|
|
3082
|
+
event.timestamp
|
|
3083
|
+
);
|
|
3084
|
+
const appendOptions = {};
|
|
3085
|
+
if (this.options) {
|
|
3086
|
+
appendOptions.route = toAppUrl(
|
|
3087
|
+
this.options.mobileAppIdentifier,
|
|
3088
|
+
this.route
|
|
3089
|
+
);
|
|
3090
|
+
}
|
|
3091
|
+
if (signal) {
|
|
3092
|
+
appendOptions.signal = signal;
|
|
3093
|
+
}
|
|
3094
|
+
const accepted = this.chunkBuffer.append(event, appendOptions);
|
|
3095
|
+
if (!accepted) {
|
|
3096
|
+
this.droppedEvents += 1;
|
|
3097
|
+
}
|
|
3098
|
+
if (this.chunkBuffer.shouldFlush()) {
|
|
3099
|
+
void this.closeCurrent(false);
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
async closeCurrent(isFinal) {
|
|
3103
|
+
const closesFinalChunkSlot = !isFinal && !this.rotatingSession && this.uploadActive && this.sessionStore?.getNextChunkIndex() === MAX_SESSION_REPLAY_CHUNKS_PER_SESSION - 1;
|
|
3104
|
+
if (closesFinalChunkSlot) {
|
|
3105
|
+
this.rotatingSession = true;
|
|
3106
|
+
const rotationOptions = {};
|
|
3107
|
+
if (this.options) {
|
|
3108
|
+
rotationOptions.route = toAppUrl(
|
|
3109
|
+
this.options.mobileAppIdentifier,
|
|
3110
|
+
this.route
|
|
3111
|
+
);
|
|
3112
|
+
}
|
|
3113
|
+
this.chunkBuffer.append(
|
|
3114
|
+
createCustomEvent(
|
|
3115
|
+
"oneuptime.session-rotated",
|
|
3116
|
+
{ reason: "chunk-cap" },
|
|
3117
|
+
this.now()
|
|
3118
|
+
),
|
|
3119
|
+
rotationOptions
|
|
3120
|
+
);
|
|
3121
|
+
}
|
|
3122
|
+
const segment = this.chunkBuffer.take();
|
|
3123
|
+
if (!segment) {
|
|
3124
|
+
if (closesFinalChunkSlot) {
|
|
3125
|
+
this.rotatingSession = false;
|
|
3126
|
+
}
|
|
3127
|
+
return;
|
|
3128
|
+
}
|
|
3129
|
+
const operation = this.closeQueue.then(
|
|
3130
|
+
async () => {
|
|
3131
|
+
return this.processSegment(segment, isFinal || closesFinalChunkSlot);
|
|
3132
|
+
}
|
|
3133
|
+
);
|
|
3134
|
+
if (closesFinalChunkSlot) {
|
|
3135
|
+
const rotation = operation.then(
|
|
3136
|
+
async () => {
|
|
3137
|
+
if (this.running) {
|
|
3138
|
+
await this.completeSessionIdentityRotation(this.now(), "chunk-cap");
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
);
|
|
3142
|
+
this.rotationPromise = rotation;
|
|
3143
|
+
this.closeQueue = rotation.catch(() => {
|
|
3144
|
+
this.diagnostic("chunk-close-failed");
|
|
3145
|
+
});
|
|
3146
|
+
try {
|
|
3147
|
+
await rotation;
|
|
3148
|
+
} finally {
|
|
3149
|
+
if (this.rotationPromise === rotation) {
|
|
3150
|
+
this.rotationPromise = null;
|
|
3151
|
+
}
|
|
3152
|
+
this.rotatingSession = false;
|
|
3153
|
+
}
|
|
3154
|
+
return;
|
|
3155
|
+
}
|
|
3156
|
+
this.closeQueue = operation.catch(() => {
|
|
3157
|
+
this.diagnostic("chunk-close-failed");
|
|
3158
|
+
});
|
|
3159
|
+
return await operation;
|
|
3160
|
+
}
|
|
3161
|
+
async processSegment(segment, isFinal) {
|
|
3162
|
+
if (!this.uploadActive) {
|
|
3163
|
+
this.rollingBuffer.push(segment, this.now());
|
|
3164
|
+
return;
|
|
3165
|
+
}
|
|
3166
|
+
await this.uploadSegment(segment, isFinal);
|
|
3167
|
+
}
|
|
3168
|
+
async uploadSegment(segment, isFinal) {
|
|
3169
|
+
if (!this.options || !this.config || !this.identity || !this.sessionStore || !this.transport || !this.identityPersisted) {
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
const chunkIndex = await this.sessionStore.takeChunkIndex(
|
|
3173
|
+
this.now()
|
|
3174
|
+
);
|
|
3175
|
+
if (chunkIndex >= MAX_SESSION_REPLAY_CHUNKS_PER_SESSION) {
|
|
3176
|
+
this.diagnostic("session-chunk-cap-race", { chunkIndex });
|
|
3177
|
+
if (this.running) {
|
|
3178
|
+
await this.completeSessionIdentityRotation(this.now(), "chunk-cap");
|
|
3179
|
+
await this.uploadSegment(segment, isFinal);
|
|
3180
|
+
}
|
|
3181
|
+
return;
|
|
3182
|
+
}
|
|
3183
|
+
const includeMeta = chunkIndex === 0 || isFinal || this.metaDirty;
|
|
3184
|
+
const envelope = {
|
|
3185
|
+
v: SESSION_REPLAY_WIRE_VERSION,
|
|
3186
|
+
appIdentifier: this.options.appIdentifier,
|
|
3187
|
+
sessionId: this.identity.sessionId,
|
|
3188
|
+
tabId: this.identity.tabId,
|
|
3189
|
+
chunkIndex,
|
|
3190
|
+
sessionStartUnixMs: this.identity.sessionStartUnixMs,
|
|
3191
|
+
clientSendUnixMs: this.now(),
|
|
3192
|
+
chunkStartOffsetMs: Math.max(
|
|
3193
|
+
0,
|
|
3194
|
+
segment.startUnixMs - this.identity.sessionStartUnixMs
|
|
3195
|
+
),
|
|
3196
|
+
chunkEndOffsetMs: Math.max(
|
|
3197
|
+
0,
|
|
3198
|
+
segment.endUnixMs - this.identity.sessionStartUnixMs
|
|
3199
|
+
),
|
|
3200
|
+
eventCount: segment.eventCount,
|
|
3201
|
+
hasFullSnapshot: segment.hasFullSnapshot,
|
|
3202
|
+
isFinal,
|
|
3203
|
+
recorderKind: MOBILE_RECORDER_KIND,
|
|
3204
|
+
schemaVersion: SESSION_REPLAY_SCHEMA_VERSION,
|
|
3205
|
+
rrwebVersion: SYNTHETIC_RRWEB_VERSION,
|
|
3206
|
+
recorderVersion: RECORDER_VERSION,
|
|
3207
|
+
maskingMode: this.config.maskingMode,
|
|
3208
|
+
consentState: this.consentState(),
|
|
3209
|
+
triggerReason: this.triggerReason,
|
|
3210
|
+
payloadEncoding: "gzip",
|
|
3211
|
+
payloadBytes: 0,
|
|
3212
|
+
url: toAppUrl(this.options.mobileAppIdentifier, this.route),
|
|
3213
|
+
routes: segment.routes,
|
|
3214
|
+
signals: segment.signals,
|
|
3215
|
+
fidelityNotices: segment.fidelityNotices,
|
|
3216
|
+
droppedEvents: segment.droppedEvents + this.droppedEvents,
|
|
3217
|
+
flushFailures: this.transport.getFlushFailures()
|
|
3218
|
+
};
|
|
3219
|
+
if (includeMeta) {
|
|
3220
|
+
envelope.meta = this.buildMeta();
|
|
3221
|
+
}
|
|
3222
|
+
if (chunkIndex === 0) {
|
|
3223
|
+
envelope.capabilities = Array.from(
|
|
3224
|
+
SESSION_REPLAY_MOBILE_RECORDER_CAPABILITIES
|
|
3225
|
+
);
|
|
3226
|
+
}
|
|
3227
|
+
this.metaDirty = false;
|
|
3228
|
+
this.droppedEvents = 0;
|
|
3229
|
+
await this.transport.enqueue(envelope, segment.payload);
|
|
3230
|
+
}
|
|
3231
|
+
buildMeta() {
|
|
3232
|
+
const size = getViewport();
|
|
3233
|
+
const captureIdentity = this.config?.captureUserIdentity === true;
|
|
3234
|
+
const meta = {
|
|
3235
|
+
entryUrl: this.entryUrl,
|
|
3236
|
+
browserName: this.metadata.appName,
|
|
3237
|
+
browserVersion: this.metadata.appVersion,
|
|
3238
|
+
osName: `${this.metadata.osName} ${this.metadata.osVersion}`.trim(),
|
|
3239
|
+
deviceType: this.metadata.osName.toLowerCase().includes("ios") ? "ios" : "android",
|
|
3240
|
+
viewportWidth: size.width,
|
|
3241
|
+
viewportHeight: size.height
|
|
3242
|
+
};
|
|
3243
|
+
if (this.identity?.visitorId) {
|
|
3244
|
+
meta.visitorId = this.identity.visitorId;
|
|
3245
|
+
}
|
|
3246
|
+
if (captureIdentity && this.userRef) {
|
|
3247
|
+
meta.identifiedUserRef = this.userRef;
|
|
3248
|
+
}
|
|
3249
|
+
if (captureIdentity && Object.keys(this.traits).length > 0) {
|
|
3250
|
+
meta.identifiedUserTraits = { ...this.traits };
|
|
3251
|
+
}
|
|
3252
|
+
if (Object.keys(this.tags).length > 0) {
|
|
3253
|
+
meta.tags = { ...this.tags };
|
|
3254
|
+
}
|
|
3255
|
+
return meta;
|
|
3256
|
+
}
|
|
3257
|
+
consentState() {
|
|
3258
|
+
if (this.config?.consentMode === "NotRequired" /* NotRequired */) {
|
|
3259
|
+
return "NotRequired";
|
|
3260
|
+
}
|
|
3261
|
+
return this.consentGranted ? "Granted" : "Unknown";
|
|
3262
|
+
}
|
|
3263
|
+
installLifecycle() {
|
|
3264
|
+
this.appStateSubscription?.remove();
|
|
3265
|
+
this.appStateSubscription = this.appState.addEventListener(
|
|
3266
|
+
"change",
|
|
3267
|
+
(state) => {
|
|
3268
|
+
const generation = this.lifecycleGeneration;
|
|
3269
|
+
const operation = this.appStateQueue.then(
|
|
3270
|
+
async () => {
|
|
3271
|
+
await this.onAppStateChange(state, generation);
|
|
3272
|
+
}
|
|
3273
|
+
);
|
|
3274
|
+
this.appStateQueue = operation.catch(() => {
|
|
3275
|
+
this.diagnostic("app-state-transition-failed");
|
|
3276
|
+
});
|
|
3277
|
+
}
|
|
3278
|
+
);
|
|
3279
|
+
}
|
|
3280
|
+
async onAppStateChange(state, generation) {
|
|
3281
|
+
if (!this.running || !this.isCurrentGeneration(generation)) {
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
const isActive = state === "active";
|
|
3285
|
+
if (!isActive && this.foreground) {
|
|
3286
|
+
this.foreground = false;
|
|
3287
|
+
this.clearTimers();
|
|
3288
|
+
this.diagnosticsStatus = "background";
|
|
3289
|
+
if (this.rotationPromise) {
|
|
3290
|
+
await this.rotationPromise;
|
|
3291
|
+
}
|
|
3292
|
+
if (this.policyRefreshPromise) {
|
|
3293
|
+
await this.policyRefreshPromise;
|
|
3294
|
+
}
|
|
3295
|
+
if (!this.running || !this.isCurrentGeneration(generation)) {
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
this.appendCustom(VISIBILITY_CUSTOM_EVENT_TAG, { state: "hidden" });
|
|
3299
|
+
await this.closeCurrent(false);
|
|
3300
|
+
if (this.uploadActive) {
|
|
3301
|
+
await this.transport?.drain();
|
|
3302
|
+
}
|
|
3303
|
+
return;
|
|
3304
|
+
}
|
|
3305
|
+
if (isActive && !this.foreground) {
|
|
3306
|
+
this.foreground = true;
|
|
3307
|
+
const policyReady = await this.refreshRuntimePolicy(
|
|
3308
|
+
generation,
|
|
3309
|
+
true,
|
|
3310
|
+
"foreground"
|
|
3311
|
+
);
|
|
3312
|
+
if (!policyReady) {
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3315
|
+
await this.maybeRotateSession(this.now());
|
|
3316
|
+
if (!this.running || !this.isCurrentGeneration(generation)) {
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
this.appendCustom(VISIBILITY_CUSTOM_EVENT_TAG, { state: "visible" });
|
|
3320
|
+
this.forceFullSnapshot = true;
|
|
3321
|
+
this.startTimers();
|
|
3322
|
+
this.diagnosticsStatus = this.consentState() === "Unknown" ? "consent-required" : "recording";
|
|
3323
|
+
await this.captureTick(true);
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
startTimers() {
|
|
3327
|
+
this.clearTimers();
|
|
3328
|
+
const intervalMs = Math.max(
|
|
3329
|
+
250,
|
|
3330
|
+
Math.min(
|
|
3331
|
+
5e3,
|
|
3332
|
+
typeof this.options?.captureIntervalMs === "number" && Number.isFinite(this.options.captureIntervalMs) ? this.options.captureIntervalMs : MOBILE_CAPTURE_INTERVAL_MS
|
|
3333
|
+
)
|
|
3334
|
+
);
|
|
3335
|
+
this.captureTimer = setInterval(() => {
|
|
3336
|
+
void this.captureTick();
|
|
3337
|
+
}, intervalMs);
|
|
3338
|
+
this.flushTimer = setInterval(() => {
|
|
3339
|
+
if (this.acceptingEvents) {
|
|
3340
|
+
void this.closeCurrent(false);
|
|
3341
|
+
}
|
|
3342
|
+
}, SESSION_REPLAY_FLUSH_INTERVAL_MS);
|
|
3343
|
+
}
|
|
3344
|
+
clearTimers() {
|
|
3345
|
+
if (this.captureTimer) {
|
|
3346
|
+
clearInterval(this.captureTimer);
|
|
3347
|
+
this.captureTimer = null;
|
|
3348
|
+
}
|
|
3349
|
+
if (this.flushTimer) {
|
|
3350
|
+
clearInterval(this.flushTimer);
|
|
3351
|
+
this.flushTimer = null;
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
installGlobalErrorHandler() {
|
|
3355
|
+
const globals = globalThis;
|
|
3356
|
+
const errorUtils = globals["ErrorUtils"];
|
|
3357
|
+
const previous = errorUtils?.getGlobalHandler?.();
|
|
3358
|
+
if (!errorUtils?.setGlobalHandler || typeof previous !== "function") {
|
|
3359
|
+
return;
|
|
3360
|
+
}
|
|
3361
|
+
this.globalErrorUtils = errorUtils;
|
|
3362
|
+
this.previousGlobalErrorHandler = previous;
|
|
3363
|
+
const handlerGeneration = this.lifecycleGeneration;
|
|
3364
|
+
const installedHandler = (error, isFatal) => {
|
|
3365
|
+
if (this.installedGlobalErrorHandler === installedHandler && this.lifecycleGeneration === handlerGeneration) {
|
|
3366
|
+
void this.captureError(error);
|
|
3367
|
+
}
|
|
3368
|
+
try {
|
|
3369
|
+
previous?.(error, isFatal);
|
|
3370
|
+
} catch {
|
|
3371
|
+
}
|
|
3372
|
+
};
|
|
3373
|
+
this.installedGlobalErrorHandler = installedHandler;
|
|
3374
|
+
errorUtils.setGlobalHandler(installedHandler);
|
|
3375
|
+
}
|
|
3376
|
+
restoreGlobalErrorHandler() {
|
|
3377
|
+
if (this.globalErrorUtils?.setGlobalHandler && this.globalErrorUtils.getGlobalHandler?.() === this.installedGlobalErrorHandler && this.previousGlobalErrorHandler) {
|
|
3378
|
+
this.globalErrorUtils.setGlobalHandler(this.previousGlobalErrorHandler);
|
|
3379
|
+
}
|
|
3380
|
+
this.globalErrorUtils = null;
|
|
3381
|
+
this.previousGlobalErrorHandler = null;
|
|
3382
|
+
this.installedGlobalErrorHandler = null;
|
|
3383
|
+
}
|
|
3384
|
+
createTransport(startOptions, outbox) {
|
|
3385
|
+
return new ReplayTransport({
|
|
3386
|
+
startOptions,
|
|
3387
|
+
outbox,
|
|
3388
|
+
onDirective: (directive, reason) => {
|
|
3389
|
+
this.onDirective(directive, reason);
|
|
3390
|
+
},
|
|
3391
|
+
onDiagnostic: (code, details) => {
|
|
3392
|
+
this.diagnostic(code, details);
|
|
3393
|
+
}
|
|
3394
|
+
});
|
|
3395
|
+
}
|
|
3396
|
+
onDirective(directive, reason) {
|
|
3397
|
+
this.diagnostic("server-directive", { directive, reason });
|
|
3398
|
+
if (directive === "stop") {
|
|
3399
|
+
this.disable("server-stopped", { reason });
|
|
3400
|
+
this.haltCapture();
|
|
3401
|
+
this.chunkBuffer.clear();
|
|
3402
|
+
this.rollingBuffer.clear();
|
|
3403
|
+
this.serializer.reset();
|
|
3404
|
+
this.touchPrivacyMap = CONSERVATIVE_TOUCH_PRIVACY_MAP;
|
|
3405
|
+
this.resetTouchSequences();
|
|
3406
|
+
this.uploadActive = false;
|
|
3407
|
+
this.transport?.destroy();
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
haltCapture() {
|
|
3411
|
+
this.running = false;
|
|
3412
|
+
this.acceptingEvents = false;
|
|
3413
|
+
this.quiesceCapture();
|
|
3414
|
+
this.notifySessionChange();
|
|
3415
|
+
}
|
|
3416
|
+
notifySessionChange() {
|
|
3417
|
+
const sessionId = this.getSessionId();
|
|
3418
|
+
if (sessionId === this.lastNotifiedSessionId) {
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
this.lastNotifiedSessionId = sessionId;
|
|
3422
|
+
for (const listener of this.sessionChangeListeners) {
|
|
3423
|
+
try {
|
|
3424
|
+
listener(sessionId);
|
|
3425
|
+
} catch {
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
quiesceCapture() {
|
|
3430
|
+
this.clearTimers();
|
|
3431
|
+
this.appStateSubscription?.remove();
|
|
3432
|
+
this.appStateSubscription = null;
|
|
3433
|
+
this.restoreGlobalErrorHandler();
|
|
3434
|
+
}
|
|
3435
|
+
isCurrentGeneration(generation) {
|
|
3436
|
+
return generation === this.lifecycleGeneration;
|
|
3437
|
+
}
|
|
3438
|
+
enqueueLifecycleOperation(action) {
|
|
3439
|
+
const operation = this.lifecycleQueue.then(action, action);
|
|
3440
|
+
this.lifecycleQueue = operation.then(
|
|
3441
|
+
() => {
|
|
3442
|
+
return void 0;
|
|
3443
|
+
},
|
|
3444
|
+
() => {
|
|
3445
|
+
this.diagnostic("lifecycle-operation-failed");
|
|
3446
|
+
}
|
|
3447
|
+
);
|
|
3448
|
+
return operation;
|
|
3449
|
+
}
|
|
3450
|
+
async isCurrentTouchPrivate(touch) {
|
|
3451
|
+
if (!this.rootTag || !touch.targetTag || typeof this.nativeViewTree.isTouchTargetPrivate !== "function") {
|
|
3452
|
+
return true;
|
|
3453
|
+
}
|
|
3454
|
+
try {
|
|
3455
|
+
const privateAncestry = await this.nativeViewTree.isTouchTargetPrivate(
|
|
3456
|
+
touch.targetTag,
|
|
3457
|
+
this.rootTag
|
|
3458
|
+
);
|
|
3459
|
+
if (privateAncestry) {
|
|
3460
|
+
return true;
|
|
3461
|
+
}
|
|
3462
|
+
return isTouchPrivate(this.touchPrivacyMap, touch.x, touch.y);
|
|
3463
|
+
} catch {
|
|
3464
|
+
return true;
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
updateTouchSuppression(touch, pointIsPrivate) {
|
|
3468
|
+
if (touch.pointerId !== void 0) {
|
|
3469
|
+
const pointerKey = `${typeof touch.pointerId}:${String(
|
|
3470
|
+
touch.pointerId
|
|
3471
|
+
)}`;
|
|
3472
|
+
if (pointIsPrivate) {
|
|
3473
|
+
this.suppressedTouchPointers.add(pointerKey);
|
|
3474
|
+
}
|
|
3475
|
+
const suppressed2 = this.suppressedTouchPointers.has(pointerKey);
|
|
3476
|
+
if (touch.phase === "end") {
|
|
3477
|
+
this.suppressedTouchPointers.delete(pointerKey);
|
|
3478
|
+
}
|
|
3479
|
+
return suppressed2;
|
|
3480
|
+
}
|
|
3481
|
+
if (touch.phase === "start") {
|
|
3482
|
+
this.unidentifiedTouchCount += 1;
|
|
3483
|
+
}
|
|
3484
|
+
if (pointIsPrivate) {
|
|
3485
|
+
this.unidentifiedTouchPrivate = true;
|
|
3486
|
+
}
|
|
3487
|
+
const suppressed = this.unidentifiedTouchPrivate;
|
|
3488
|
+
if (touch.phase === "end") {
|
|
3489
|
+
this.unidentifiedTouchCount = Math.max(
|
|
3490
|
+
0,
|
|
3491
|
+
this.unidentifiedTouchCount - 1
|
|
3492
|
+
);
|
|
3493
|
+
if (this.unidentifiedTouchCount === 0) {
|
|
3494
|
+
this.unidentifiedTouchPrivate = false;
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
return suppressed;
|
|
3498
|
+
}
|
|
3499
|
+
resetTouchSequences() {
|
|
3500
|
+
this.suppressedTouchPointers.clear();
|
|
3501
|
+
this.unidentifiedTouchCount = 0;
|
|
3502
|
+
this.unidentifiedTouchPrivate = false;
|
|
3503
|
+
}
|
|
3504
|
+
async applyIdentityChange(ref, traits, identityEpoch) {
|
|
3505
|
+
if (identityEpoch !== this.identityCancellationEpoch) {
|
|
3506
|
+
return;
|
|
3507
|
+
}
|
|
3508
|
+
if (!this.running) {
|
|
3509
|
+
this.applyIdentityValues(ref, traits, true);
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
if (ref === this.userRef) {
|
|
3513
|
+
this.applyIdentityValues(ref, traits, false);
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
const wasAcceptingEvents = this.acceptingEvents;
|
|
3517
|
+
this.acceptingEvents = false;
|
|
3518
|
+
try {
|
|
3519
|
+
if (this.policyRefreshPromise) {
|
|
3520
|
+
await this.policyRefreshPromise;
|
|
3521
|
+
}
|
|
3522
|
+
if (!this.running || identityEpoch !== this.identityCancellationEpoch) {
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
if (this.rotationPromise) {
|
|
3526
|
+
await this.rotationPromise;
|
|
3527
|
+
} else {
|
|
3528
|
+
this.rotatingSession = true;
|
|
3529
|
+
const rotation = this.performSessionRotation(
|
|
3530
|
+
this.now(),
|
|
3531
|
+
"identity"
|
|
3532
|
+
);
|
|
3533
|
+
this.rotationPromise = rotation;
|
|
3534
|
+
try {
|
|
3535
|
+
await rotation;
|
|
3536
|
+
} finally {
|
|
3537
|
+
if (this.rotationPromise === rotation) {
|
|
3538
|
+
this.rotationPromise = null;
|
|
3539
|
+
}
|
|
3540
|
+
this.rotatingSession = false;
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
if (identityEpoch !== this.identityCancellationEpoch) {
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
3546
|
+
this.acceptingEvents = this.running && wasAcceptingEvents;
|
|
3547
|
+
this.triggered = false;
|
|
3548
|
+
this.triggerReason = "sampled" /* Sampled */;
|
|
3549
|
+
this.uploadActive = this.consentState() !== "Unknown" && this.sampled;
|
|
3550
|
+
this.applyIdentityValues(ref, traits, true);
|
|
3551
|
+
const generation = this.lifecycleGeneration;
|
|
3552
|
+
const policyReady = await this.refreshRuntimePolicy(
|
|
3553
|
+
generation,
|
|
3554
|
+
true,
|
|
3555
|
+
"identity"
|
|
3556
|
+
);
|
|
3557
|
+
if (!policyReady || !this.running || !this.isCurrentGeneration(generation) || identityEpoch !== this.identityCancellationEpoch) {
|
|
3558
|
+
return;
|
|
3559
|
+
}
|
|
3560
|
+
if (this.running && this.foreground) {
|
|
3561
|
+
this.forceFullSnapshot = true;
|
|
3562
|
+
void this.captureTick(true);
|
|
3563
|
+
}
|
|
3564
|
+
} finally {
|
|
3565
|
+
if (this.running && identityEpoch === this.identityCancellationEpoch) {
|
|
3566
|
+
this.acceptingEvents = wasAcceptingEvents;
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
applyIdentityValues(ref, traits, clearTraitsWhenMissing) {
|
|
3571
|
+
const changed = ref !== this.userRef;
|
|
3572
|
+
this.userRef = ref;
|
|
3573
|
+
if (traits) {
|
|
3574
|
+
this.traits = traits;
|
|
3575
|
+
} else if (clearTraitsWhenMissing && changed) {
|
|
3576
|
+
this.traits = {};
|
|
3577
|
+
}
|
|
3578
|
+
if (this.options) {
|
|
3579
|
+
const currentOptions = { ...this.options };
|
|
3580
|
+
if (ref) {
|
|
3581
|
+
currentOptions.userRef = ref;
|
|
3582
|
+
} else {
|
|
3583
|
+
delete currentOptions.userRef;
|
|
3584
|
+
}
|
|
3585
|
+
this.options = currentOptions;
|
|
3586
|
+
}
|
|
3587
|
+
this.metaDirty = true;
|
|
3588
|
+
this.appendCustom(IDENTIFY_CUSTOM_EVENT_TAG, {
|
|
3589
|
+
identified: ref !== null,
|
|
3590
|
+
traitKeys: Object.keys(this.traits)
|
|
3591
|
+
});
|
|
3592
|
+
}
|
|
3593
|
+
async refreshTargetingConfig(baseConfig, forceRefresh = false) {
|
|
3594
|
+
if (!this.options || !forceRefresh && !this.userRef) {
|
|
3595
|
+
return baseConfig;
|
|
3596
|
+
}
|
|
3597
|
+
const currentOptions = { ...this.options };
|
|
3598
|
+
if (this.userRef) {
|
|
3599
|
+
currentOptions.userRef = this.userRef;
|
|
3600
|
+
} else {
|
|
3601
|
+
delete currentOptions.userRef;
|
|
3602
|
+
}
|
|
3603
|
+
const targetedConfig = await fetchReplayConfig(
|
|
3604
|
+
currentOptions,
|
|
3605
|
+
Boolean(this.userRef),
|
|
3606
|
+
forceRefresh
|
|
3607
|
+
);
|
|
3608
|
+
return targetedConfig;
|
|
3609
|
+
}
|
|
3610
|
+
async refreshRuntimePolicy(generation, forceRefresh, reason) {
|
|
3611
|
+
if (!this.running || !this.config || !this.options || !this.isCurrentGeneration(generation)) {
|
|
3612
|
+
return false;
|
|
3613
|
+
}
|
|
3614
|
+
if (this.policyRefreshPromise) {
|
|
3615
|
+
const existingResult = await this.policyRefreshPromise;
|
|
3616
|
+
if (!forceRefresh || !existingResult) {
|
|
3617
|
+
return existingResult;
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
const nowUnixMs = this.now();
|
|
3621
|
+
if (!forceRefresh && nowUnixMs - this.lastPolicyRefreshAtUnixMs < MOBILE_POLICY_REFRESH_INTERVAL_MS) {
|
|
3622
|
+
return true;
|
|
3623
|
+
}
|
|
3624
|
+
const operation = this.performRuntimePolicyRefresh(
|
|
3625
|
+
generation,
|
|
3626
|
+
reason,
|
|
3627
|
+
nowUnixMs
|
|
3628
|
+
);
|
|
3629
|
+
this.policyRefreshPromise = operation;
|
|
3630
|
+
try {
|
|
3631
|
+
return await operation;
|
|
3632
|
+
} finally {
|
|
3633
|
+
if (this.policyRefreshPromise === operation) {
|
|
3634
|
+
this.policyRefreshPromise = null;
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
async performRuntimePolicyRefresh(generation, reason, nowUnixMs) {
|
|
3639
|
+
if (!this.options || !this.config) {
|
|
3640
|
+
return false;
|
|
3641
|
+
}
|
|
3642
|
+
const previousConfig = this.config;
|
|
3643
|
+
const previousConsentState = this.consentState();
|
|
3644
|
+
const wasAcceptingEvents = this.acceptingEvents;
|
|
3645
|
+
this.acceptingEvents = false;
|
|
3646
|
+
try {
|
|
3647
|
+
let currentOptions = this.currentStartOptions();
|
|
3648
|
+
const maySendUserRef = previousConsentState !== "Unknown" && Boolean(currentOptions.userRef);
|
|
3649
|
+
let freshConfig = await fetchReplayConfig(
|
|
3650
|
+
currentOptions,
|
|
3651
|
+
maySendUserRef,
|
|
3652
|
+
true
|
|
3653
|
+
);
|
|
3654
|
+
if (!this.running || !this.isCurrentGeneration(generation)) {
|
|
3655
|
+
return false;
|
|
3656
|
+
}
|
|
3657
|
+
if (freshConfig.enabled && !maySendUserRef && freshConfig.consentMode === "NotRequired" /* NotRequired */ && this.userRef) {
|
|
3658
|
+
currentOptions = this.currentStartOptions();
|
|
3659
|
+
freshConfig = await fetchReplayConfig(currentOptions, true, true);
|
|
3660
|
+
if (!this.running || !this.isCurrentGeneration(generation)) {
|
|
3661
|
+
return false;
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
this.lastPolicyRefreshAtUnixMs = nowUnixMs;
|
|
3665
|
+
if (!freshConfig.enabled) {
|
|
3666
|
+
if (reason !== "consent" && isRetryablePolicyFailure(freshConfig)) {
|
|
3667
|
+
this.diagnostic("policy-refresh-failed-using-last-config", {
|
|
3668
|
+
reason
|
|
3669
|
+
});
|
|
3670
|
+
return true;
|
|
3671
|
+
}
|
|
3672
|
+
this.config = freshConfig;
|
|
3673
|
+
await this.discardForPolicyStop(
|
|
3674
|
+
reason === "consent" ? "config-disabled-after-consent" : "config-disabled-after-refresh",
|
|
3675
|
+
freshConfig.disabledReason ?? "not-reported"
|
|
3676
|
+
);
|
|
3677
|
+
return false;
|
|
3678
|
+
}
|
|
3679
|
+
return await this.applyRuntimePolicy(
|
|
3680
|
+
previousConfig,
|
|
3681
|
+
freshConfig,
|
|
3682
|
+
previousConsentState,
|
|
3683
|
+
wasAcceptingEvents,
|
|
3684
|
+
generation
|
|
3685
|
+
);
|
|
3686
|
+
} catch (error) {
|
|
3687
|
+
if (!this.running || !this.isCurrentGeneration(generation)) {
|
|
3688
|
+
return false;
|
|
3689
|
+
}
|
|
3690
|
+
this.lastPolicyRefreshAtUnixMs = nowUnixMs;
|
|
3691
|
+
if (reason !== "consent") {
|
|
3692
|
+
this.diagnostic("policy-refresh-failed-using-last-config", {
|
|
3693
|
+
reason,
|
|
3694
|
+
name: error instanceof Error ? error.name : "Error"
|
|
3695
|
+
});
|
|
3696
|
+
return true;
|
|
3697
|
+
}
|
|
3698
|
+
await this.discardForPolicyStop(
|
|
3699
|
+
"config-disabled-after-consent",
|
|
3700
|
+
"config-fetch-failed"
|
|
3701
|
+
);
|
|
3702
|
+
return false;
|
|
3703
|
+
} finally {
|
|
3704
|
+
if (this.running && this.isCurrentGeneration(generation)) {
|
|
3705
|
+
this.acceptingEvents = wasAcceptingEvents;
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
async applyRuntimePolicy(_previousConfig, freshConfig, previousConsentState, wasAcceptingEvents, generation) {
|
|
3710
|
+
this.config = freshConfig;
|
|
3711
|
+
this.metaDirty = true;
|
|
3712
|
+
const hasConsent = freshConfig.consentMode === "NotRequired" /* NotRequired */ || this.consentGranted;
|
|
3713
|
+
if (!hasConsent) {
|
|
3714
|
+
const mustWithdrawPersistedState = this.identityPersisted || this.uploadActive || previousConsentState !== "Unknown";
|
|
3715
|
+
if (mustWithdrawPersistedState) {
|
|
3716
|
+
this.transport?.destroy();
|
|
3717
|
+
this.chunkBuffer.clear();
|
|
3718
|
+
this.rollingBuffer.clear();
|
|
3719
|
+
this.serializer.reset();
|
|
3720
|
+
await this.closeQueue.catch(() => {
|
|
3721
|
+
return void 0;
|
|
3722
|
+
});
|
|
3723
|
+
await this.outbox?.clear();
|
|
3724
|
+
await this.sessionStore?.clear();
|
|
3725
|
+
if (!this.running || !this.isCurrentGeneration(generation)) {
|
|
3726
|
+
return false;
|
|
3727
|
+
}
|
|
3728
|
+
const nowUnixMs = this.now();
|
|
3729
|
+
this.identity = this.ephemeralIdentity(nowUnixMs);
|
|
3730
|
+
this.identityPersisted = false;
|
|
3731
|
+
this.notifySessionChange();
|
|
3732
|
+
this.sessionLastActivityUnixMs = nowUnixMs;
|
|
3733
|
+
this.sampled = false;
|
|
3734
|
+
this.uploadActive = false;
|
|
3735
|
+
this.forceFullSnapshot = true;
|
|
3736
|
+
this.touchPrivacyMap = CONSERVATIVE_TOUCH_PRIVACY_MAP;
|
|
3737
|
+
this.resetTouchSequences();
|
|
3738
|
+
this.recreateTransport();
|
|
3739
|
+
}
|
|
3740
|
+
this.triggered = freshConfig.isTargeted;
|
|
3741
|
+
this.triggerReason = freshConfig.isTargeted ? "manual" /* Manual */ : "sampled" /* Sampled */;
|
|
3742
|
+
this.diagnosticsStatus = "consent-required";
|
|
3743
|
+
this.acceptingEvents = wasAcceptingEvents;
|
|
3744
|
+
this.diagnostic("policy-now-requires-consent");
|
|
3745
|
+
return true;
|
|
3746
|
+
}
|
|
3747
|
+
if (!this.identityPersisted && this.sessionStore) {
|
|
3748
|
+
const sessionStore = this.sessionStore;
|
|
3749
|
+
const identity = await sessionStore.resolve(
|
|
3750
|
+
this.now()
|
|
3751
|
+
);
|
|
3752
|
+
if (!this.running || !this.isCurrentGeneration(generation) || this.sessionStore !== sessionStore) {
|
|
3753
|
+
return false;
|
|
3754
|
+
}
|
|
3755
|
+
this.identity = identity;
|
|
3756
|
+
this.identityPersisted = true;
|
|
3757
|
+
this.notifySessionChange();
|
|
3758
|
+
this.sessionLastActivityUnixMs = this.now();
|
|
3759
|
+
}
|
|
3760
|
+
if (!this.identity) {
|
|
3761
|
+
return false;
|
|
3762
|
+
}
|
|
3763
|
+
this.sampled = deterministicSample(
|
|
3764
|
+
this.identity.sessionId,
|
|
3765
|
+
freshConfig.samplePercentage
|
|
3766
|
+
);
|
|
3767
|
+
if (freshConfig.isTargeted) {
|
|
3768
|
+
this.triggered = true;
|
|
3769
|
+
this.triggerReason = "manual" /* Manual */;
|
|
3770
|
+
} else if (!this.triggered) {
|
|
3771
|
+
this.triggerReason = "sampled" /* Sampled */;
|
|
3772
|
+
}
|
|
3773
|
+
if (freshConfig.captureTrigger === "Always" /* Always */ && !this.sampled && !freshConfig.isTargeted) {
|
|
3774
|
+
await this.discardForPolicyStop(
|
|
3775
|
+
"not-sampled-after-config-refresh",
|
|
3776
|
+
"not-sampled"
|
|
3777
|
+
);
|
|
3778
|
+
return false;
|
|
3779
|
+
}
|
|
3780
|
+
const shouldUpload = this.sampled || this.triggered || freshConfig.isTargeted;
|
|
3781
|
+
if (!shouldUpload && this.uploadActive) {
|
|
3782
|
+
this.transport?.destroy();
|
|
3783
|
+
this.chunkBuffer.clear();
|
|
3784
|
+
this.rollingBuffer.clear();
|
|
3785
|
+
await this.closeQueue.catch(() => {
|
|
3786
|
+
return void 0;
|
|
3787
|
+
});
|
|
3788
|
+
await this.outbox?.clear();
|
|
3789
|
+
if (!this.running || !this.isCurrentGeneration(generation)) {
|
|
3790
|
+
return false;
|
|
3791
|
+
}
|
|
3792
|
+
this.uploadActive = false;
|
|
3793
|
+
this.recreateTransport();
|
|
3794
|
+
}
|
|
3795
|
+
this.acceptingEvents = wasAcceptingEvents;
|
|
3796
|
+
if (shouldUpload && !this.uploadActive) {
|
|
3797
|
+
await this.activateUpload(generation);
|
|
3798
|
+
}
|
|
3799
|
+
if (!this.running || !this.isCurrentGeneration(generation)) {
|
|
3800
|
+
return false;
|
|
3801
|
+
}
|
|
3802
|
+
this.diagnosticsStatus = this.foreground ? "recording" : "background";
|
|
3803
|
+
this.diagnostic("policy-refreshed", {
|
|
3804
|
+
configEpoch: freshConfig.configEpoch
|
|
3805
|
+
});
|
|
3806
|
+
return true;
|
|
3807
|
+
}
|
|
3808
|
+
currentStartOptions() {
|
|
3809
|
+
const currentOptions = {
|
|
3810
|
+
...this.options
|
|
3811
|
+
};
|
|
3812
|
+
if (this.userRef) {
|
|
3813
|
+
currentOptions.userRef = this.userRef;
|
|
3814
|
+
} else {
|
|
3815
|
+
delete currentOptions.userRef;
|
|
3816
|
+
}
|
|
3817
|
+
return currentOptions;
|
|
3818
|
+
}
|
|
3819
|
+
recreateTransport() {
|
|
3820
|
+
if (this.options && this.outbox) {
|
|
3821
|
+
this.transport = this.createTransport(this.options, this.outbox);
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
async discardForPolicyStop(code, reason) {
|
|
3825
|
+
this.disable(code, { reason });
|
|
3826
|
+
this.haltCapture();
|
|
3827
|
+
this.transport?.destroy();
|
|
3828
|
+
this.chunkBuffer.clear();
|
|
3829
|
+
this.rollingBuffer.clear();
|
|
3830
|
+
this.serializer.reset();
|
|
3831
|
+
this.touchPrivacyMap = CONSERVATIVE_TOUCH_PRIVACY_MAP;
|
|
3832
|
+
this.resetTouchSequences();
|
|
3833
|
+
this.uploadActive = false;
|
|
3834
|
+
await this.closeQueue.catch(() => {
|
|
3835
|
+
return void 0;
|
|
3836
|
+
});
|
|
3837
|
+
await this.outbox?.clear();
|
|
3838
|
+
}
|
|
3839
|
+
ephemeralIdentity(nowUnixMs) {
|
|
3840
|
+
return {
|
|
3841
|
+
sessionId: generateReplayId(),
|
|
3842
|
+
tabId: generateReplayId(),
|
|
3843
|
+
visitorId: generateReplayId(),
|
|
3844
|
+
sessionStartUnixMs: nowUnixMs,
|
|
3845
|
+
chunkIndex: 0
|
|
3846
|
+
};
|
|
3847
|
+
}
|
|
3848
|
+
rotationReason(nowUnixMs) {
|
|
3849
|
+
if (!this.identity) {
|
|
3850
|
+
return null;
|
|
3851
|
+
}
|
|
3852
|
+
if (nowUnixMs - this.identity.sessionStartUnixMs >= SESSION_REPLAY_MAX_SESSION_MS) {
|
|
3853
|
+
return "duration";
|
|
3854
|
+
}
|
|
3855
|
+
if (nowUnixMs - this.sessionLastActivityUnixMs >= SESSION_REPLAY_IDLE_ROLLOVER_MS) {
|
|
3856
|
+
return "idle";
|
|
3857
|
+
}
|
|
3858
|
+
return null;
|
|
3859
|
+
}
|
|
3860
|
+
runExternalActivity(nowUnixMs, action) {
|
|
3861
|
+
if (this.pendingIdentityOperations > 0) {
|
|
3862
|
+
const identityBarrier = this.identityQueue;
|
|
3863
|
+
const lifecycleEpoch = this.lifecycleCancellationEpoch;
|
|
3864
|
+
const identityEpoch = this.identityCancellationEpoch;
|
|
3865
|
+
const wasRunning = this.running;
|
|
3866
|
+
void identityBarrier.then(() => {
|
|
3867
|
+
if (lifecycleEpoch !== this.lifecycleCancellationEpoch || identityEpoch !== this.identityCancellationEpoch) {
|
|
3868
|
+
return;
|
|
3869
|
+
}
|
|
3870
|
+
if (this.running && this.acceptingEvents || !wasRunning && this.diagnosticsStatus === "idle") {
|
|
3871
|
+
action();
|
|
3872
|
+
}
|
|
3873
|
+
});
|
|
3874
|
+
return;
|
|
3875
|
+
}
|
|
3876
|
+
if (!this.running) {
|
|
3877
|
+
action();
|
|
3878
|
+
return;
|
|
3879
|
+
}
|
|
3880
|
+
const shouldDefer = this.rotatingSession || this.rotationReason(nowUnixMs) !== null;
|
|
3881
|
+
if (!shouldDefer) {
|
|
3882
|
+
action();
|
|
3883
|
+
return;
|
|
3884
|
+
}
|
|
3885
|
+
const generation = this.lifecycleGeneration;
|
|
3886
|
+
void this.maybeRotateSession(nowUnixMs).then(() => {
|
|
3887
|
+
if (this.running && this.isCurrentGeneration(generation)) {
|
|
3888
|
+
action();
|
|
3889
|
+
}
|
|
3890
|
+
});
|
|
3891
|
+
}
|
|
3892
|
+
async maybeRotateSession(nowUnixMs) {
|
|
3893
|
+
if (this.rotationPromise) {
|
|
3894
|
+
await this.rotationPromise;
|
|
3895
|
+
return;
|
|
3896
|
+
}
|
|
3897
|
+
if (!this.running || this.rotatingSession || !this.identity || !this.config || !this.sessionStore || !this.options) {
|
|
3898
|
+
return;
|
|
3899
|
+
}
|
|
3900
|
+
const reason = this.rotationReason(nowUnixMs);
|
|
3901
|
+
if (!reason) {
|
|
3902
|
+
return;
|
|
3903
|
+
}
|
|
3904
|
+
this.rotatingSession = true;
|
|
3905
|
+
const operation = this.performSessionRotation(
|
|
3906
|
+
nowUnixMs,
|
|
3907
|
+
reason
|
|
3908
|
+
);
|
|
3909
|
+
this.rotationPromise = operation;
|
|
3910
|
+
try {
|
|
3911
|
+
await operation;
|
|
3912
|
+
} finally {
|
|
3913
|
+
if (this.rotationPromise === operation) {
|
|
3914
|
+
this.rotationPromise = null;
|
|
3915
|
+
}
|
|
3916
|
+
this.rotatingSession = false;
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
async performSessionRotation(nowUnixMs, reason) {
|
|
3920
|
+
this.appendCustom("oneuptime.session-rotated", { reason }, void 0, true);
|
|
3921
|
+
await this.closeCurrent(true);
|
|
3922
|
+
await this.closeQueue;
|
|
3923
|
+
if (!this.running) {
|
|
3924
|
+
return;
|
|
3925
|
+
}
|
|
3926
|
+
if (this.uploadActive) {
|
|
3927
|
+
await this.transport?.drain();
|
|
3928
|
+
} else {
|
|
3929
|
+
this.rollingBuffer.clear();
|
|
3930
|
+
}
|
|
3931
|
+
if (!this.running) {
|
|
3932
|
+
return;
|
|
3933
|
+
}
|
|
3934
|
+
await this.completeSessionIdentityRotation(nowUnixMs, reason);
|
|
3935
|
+
}
|
|
3936
|
+
async completeSessionIdentityRotation(nowUnixMs, reason) {
|
|
3937
|
+
if (!this.sessionStore || !this.config || !this.options) {
|
|
3938
|
+
return;
|
|
3939
|
+
}
|
|
3940
|
+
this.identity = this.identityPersisted ? await this.sessionStore.rotate(nowUnixMs) : this.ephemeralIdentity(nowUnixMs);
|
|
3941
|
+
this.notifySessionChange();
|
|
3942
|
+
this.sampled = this.identityPersisted ? deterministicSample(
|
|
3943
|
+
this.identity.sessionId,
|
|
3944
|
+
this.config.samplePercentage
|
|
3945
|
+
) : false;
|
|
3946
|
+
this.triggered = this.config.isTargeted;
|
|
3947
|
+
this.triggerReason = this.config.isTargeted ? "manual" /* Manual */ : "sampled" /* Sampled */;
|
|
3948
|
+
this.uploadActive = this.consentState() !== "Unknown" && (this.sampled || this.config.isTargeted);
|
|
3949
|
+
this.serializer.reset();
|
|
3950
|
+
this.chunkBuffer.clear();
|
|
3951
|
+
this.rollingBuffer.clear();
|
|
3952
|
+
this.forceFullSnapshot = true;
|
|
3953
|
+
this.lastFullSnapshotAtUnixMs = 0;
|
|
3954
|
+
this.sessionLastActivityUnixMs = nowUnixMs;
|
|
3955
|
+
this.metaDirty = true;
|
|
3956
|
+
this.entryUrl = toAppUrl(this.options.mobileAppIdentifier, this.route);
|
|
3957
|
+
this.diagnostic("session-rotated", { reason });
|
|
3958
|
+
if (this.config.captureTrigger === "Always" /* Always */ && this.identityPersisted && !this.sampled && !this.config.isTargeted) {
|
|
3959
|
+
this.disable("not-sampled");
|
|
3960
|
+
this.haltCapture();
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
disable(code, details) {
|
|
3964
|
+
this.diagnosticsStatus = "disabled";
|
|
3965
|
+
this.diagnostic(code, details);
|
|
3966
|
+
}
|
|
3967
|
+
diagnostic(code, details) {
|
|
3968
|
+
const event = {
|
|
3969
|
+
code,
|
|
3970
|
+
atUnixMs: this.now()
|
|
3971
|
+
};
|
|
3972
|
+
if (details) {
|
|
3973
|
+
event.details = details;
|
|
3974
|
+
}
|
|
3975
|
+
this.diagnosticsEvents.push(event);
|
|
3976
|
+
if (this.diagnosticsEvents.length > MAX_DIAGNOSTIC_EVENTS) {
|
|
3977
|
+
this.diagnosticsEvents.shift();
|
|
3978
|
+
}
|
|
3979
|
+
if (this.options?.debug) {
|
|
3980
|
+
console.info(`[OneUptime Replay] ${code}`, details ?? {});
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
};
|
|
3984
|
+
|
|
3985
|
+
// src/ReplayProvider.tsx
|
|
3986
|
+
import { jsx } from "react/jsx-runtime";
|
|
3987
|
+
var defaultMobileReplayRecorder = new MobileReplayRecorder();
|
|
3988
|
+
var ReplayContext = createContext(defaultMobileReplayRecorder);
|
|
3989
|
+
function touchPointFromEvent(event) {
|
|
3990
|
+
const nativeEvent = event.nativeEvent;
|
|
3991
|
+
const x2 = typeof nativeEvent.pageX === "number" ? nativeEvent.pageX : typeof nativeEvent.locationX === "number" ? nativeEvent.locationX : 0;
|
|
3992
|
+
const y = typeof nativeEvent.pageY === "number" ? nativeEvent.pageY : typeof nativeEvent.locationY === "number" ? nativeEvent.locationY : 0;
|
|
3993
|
+
return {
|
|
3994
|
+
x: Number.isFinite(x2) ? x2 : 0,
|
|
3995
|
+
y: Number.isFinite(y) ? y : 0
|
|
3996
|
+
};
|
|
3997
|
+
}
|
|
3998
|
+
function OneUptimeReplayProvider(props) {
|
|
3999
|
+
const recorder = props.recorder ?? defaultMobileReplayRecorder;
|
|
4000
|
+
const rootRef = useRef(null);
|
|
4001
|
+
const initialOptions = useRef(props.options);
|
|
4002
|
+
useEffect(() => {
|
|
4003
|
+
const tag = findNodeHandle(rootRef.current);
|
|
4004
|
+
recorder.setRootTag(tag);
|
|
4005
|
+
if (initialOptions.current) {
|
|
4006
|
+
void recorder.start(initialOptions.current);
|
|
4007
|
+
}
|
|
4008
|
+
return () => {
|
|
4009
|
+
recorder.setRootTag(null);
|
|
4010
|
+
if (initialOptions.current && props.stopOnUnmount !== false) {
|
|
4011
|
+
void recorder.stop();
|
|
4012
|
+
}
|
|
4013
|
+
};
|
|
4014
|
+
}, [props.stopOnUnmount, recorder]);
|
|
4015
|
+
const record = (phase, event) => {
|
|
4016
|
+
const point = touchPointFromEvent(event);
|
|
4017
|
+
const targetTag = event.nativeEvent.target;
|
|
4018
|
+
const pointerId = event.nativeEvent["identifier"];
|
|
4019
|
+
const touch = {
|
|
4020
|
+
phase,
|
|
4021
|
+
x: point.x,
|
|
4022
|
+
y: point.y,
|
|
4023
|
+
timestamp: Date.now()
|
|
4024
|
+
};
|
|
4025
|
+
if (typeof targetTag === "number" && Number.isSafeInteger(targetTag)) {
|
|
4026
|
+
touch.targetTag = targetTag;
|
|
4027
|
+
}
|
|
4028
|
+
if (typeof pointerId === "number" && Number.isSafeInteger(pointerId) || typeof pointerId === "string") {
|
|
4029
|
+
touch.pointerId = pointerId;
|
|
4030
|
+
}
|
|
4031
|
+
recorder.recordTouch(touch);
|
|
4032
|
+
};
|
|
4033
|
+
return /* @__PURE__ */ jsx(ReplayContext.Provider, { value: recorder, children: /* @__PURE__ */ jsx(
|
|
4034
|
+
View,
|
|
4035
|
+
{
|
|
4036
|
+
ref: rootRef,
|
|
4037
|
+
collapsable: false,
|
|
4038
|
+
style: [{ flex: 1 }, props.style],
|
|
4039
|
+
onTouchStart: (event) => {
|
|
4040
|
+
record("start", event);
|
|
4041
|
+
},
|
|
4042
|
+
onTouchMove: (event) => {
|
|
4043
|
+
record("move", event);
|
|
4044
|
+
},
|
|
4045
|
+
onTouchEnd: (event) => {
|
|
4046
|
+
record("end", event);
|
|
4047
|
+
},
|
|
4048
|
+
children: props.children
|
|
4049
|
+
}
|
|
4050
|
+
) });
|
|
4051
|
+
}
|
|
4052
|
+
function useOneUptimeReplay() {
|
|
4053
|
+
return useContext(ReplayContext);
|
|
4054
|
+
}
|
|
4055
|
+
function ReplayMask(props) {
|
|
4056
|
+
return /* @__PURE__ */ jsx(
|
|
4057
|
+
View,
|
|
4058
|
+
{
|
|
4059
|
+
collapsable: false,
|
|
4060
|
+
nativeID: "oneuptime-replay-mask",
|
|
4061
|
+
testID: "oneuptime-replay-mask",
|
|
4062
|
+
style: props.style,
|
|
4063
|
+
children: props.children
|
|
4064
|
+
}
|
|
4065
|
+
);
|
|
4066
|
+
}
|
|
4067
|
+
var ReplayProvider = OneUptimeReplayProvider;
|
|
4068
|
+
|
|
4069
|
+
// src/index.ts
|
|
4070
|
+
var OneUptimeReplay = defaultMobileReplayRecorder;
|
|
4071
|
+
var index_default = OneUptimeReplay;
|
|
4072
|
+
export {
|
|
4073
|
+
MOBILE_RECORDER_KIND,
|
|
4074
|
+
MobileReplayRecorder,
|
|
4075
|
+
OneUptimeReplay,
|
|
4076
|
+
OneUptimeReplayProvider,
|
|
4077
|
+
ReplayMask,
|
|
4078
|
+
ReplayProvider,
|
|
4079
|
+
SESSION_REPLAY_MOBILE_RECORDER_CAPABILITIES,
|
|
4080
|
+
SessionReplayCaptureTrigger,
|
|
4081
|
+
SessionReplayConsentMode,
|
|
4082
|
+
SessionReplayFidelityNotice,
|
|
4083
|
+
SessionReplayMaskingMode,
|
|
4084
|
+
SessionReplayTriggerReason,
|
|
4085
|
+
index_default as default,
|
|
4086
|
+
useOneUptimeReplay
|
|
4087
|
+
};
|
|
4088
|
+
//# sourceMappingURL=index.js.map
|