clintplay-react-native 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +222 -0
- package/android/build.gradle +50 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/digital/clint/clintplay/ClintplayRNModule.kt +362 -0
- package/android/src/main/java/digital/clint/clintplay/ClintplayRNPackage.kt +49 -0
- package/app.plugin.js +29 -0
- package/clintplay-react-native.podspec +40 -0
- package/dist/index.d.mts +280 -0
- package/dist/index.d.ts +280 -0
- package/dist/index.js +1480 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1461 -0
- package/dist/index.mjs.map +1 -0
- package/ios/ClintplayRN.h +25 -0
- package/ios/ClintplayRN.mm +361 -0
- package/ios/PrivacyInfo.xcprivacy +52 -0
- package/package.json +67 -0
- package/react-native.config.js +22 -0
- package/src/spec/NativeClintplayRN.ts +25 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1461 @@
|
|
|
1
|
+
import { View, TextInput, Image, Dimensions, AppState, Platform, TurboModuleRegistry, NativeModules } from 'react-native';
|
|
2
|
+
import React5, { useCallback, useRef, useEffect } from 'react';
|
|
3
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
|
|
7
|
+
// src/buffer.ts
|
|
8
|
+
var EventBuffer = class {
|
|
9
|
+
constructor(maxSize) {
|
|
10
|
+
this.maxSize = maxSize;
|
|
11
|
+
this.events = [];
|
|
12
|
+
}
|
|
13
|
+
push(event) {
|
|
14
|
+
this.events.push(event);
|
|
15
|
+
const excess = this.events.length - this.maxSize;
|
|
16
|
+
if (excess > 0) {
|
|
17
|
+
this.events.splice(0, excess);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
size() {
|
|
21
|
+
return this.events.length;
|
|
22
|
+
}
|
|
23
|
+
drain() {
|
|
24
|
+
const out = this.events;
|
|
25
|
+
this.events = [];
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
peek() {
|
|
29
|
+
return this.events;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/session.ts
|
|
34
|
+
function uuid() {
|
|
35
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
36
|
+
return crypto.randomUUID();
|
|
37
|
+
}
|
|
38
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
39
|
+
const r = Math.random() * 16 | 0;
|
|
40
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
41
|
+
return v.toString(16);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
var IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
45
|
+
var SessionManager = class {
|
|
46
|
+
constructor(now = Date.now) {
|
|
47
|
+
this.now = now;
|
|
48
|
+
this.state = this.create();
|
|
49
|
+
}
|
|
50
|
+
create() {
|
|
51
|
+
const ts = this.now();
|
|
52
|
+
return { sessionId: uuid(), startedAt: ts, lastActivity: ts, userId: null };
|
|
53
|
+
}
|
|
54
|
+
current() {
|
|
55
|
+
return this.state;
|
|
56
|
+
}
|
|
57
|
+
touch() {
|
|
58
|
+
this.state.lastActivity = this.now();
|
|
59
|
+
}
|
|
60
|
+
setUser(userId) {
|
|
61
|
+
const prev = this.state.userId;
|
|
62
|
+
if (prev !== null && (userId === null || prev !== userId)) {
|
|
63
|
+
const next = this.create();
|
|
64
|
+
next.userId = userId;
|
|
65
|
+
this.state = next;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
this.state.userId = userId;
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
isStale() {
|
|
72
|
+
return this.now() - this.state.lastActivity > IDLE_TIMEOUT_MS;
|
|
73
|
+
}
|
|
74
|
+
// Rotates the session id if the gap since last activity exceeds the
|
|
75
|
+
// idle window. Caller should emit a fresh meta event under the new
|
|
76
|
+
// id when this returns true.
|
|
77
|
+
rotateIfStale() {
|
|
78
|
+
if (this.isStale()) {
|
|
79
|
+
const prevUser = this.state.userId;
|
|
80
|
+
this.state = this.create();
|
|
81
|
+
this.state.userId = prevUser;
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
reset() {
|
|
87
|
+
this.state = this.create();
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// src/transport.ts
|
|
92
|
+
var DEFAULT_BACKOFF = (attempt) => 500 * (attempt + 1);
|
|
93
|
+
var MAX_RETRIES_CAP = 10;
|
|
94
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
95
|
+
var Transport = class {
|
|
96
|
+
constructor(cfg) {
|
|
97
|
+
this.cfg = cfg;
|
|
98
|
+
}
|
|
99
|
+
// Returns true on a 2xx. Drops the batch on 4xx (caller-side error,
|
|
100
|
+
// no point retrying), retries on 5xx up to maxRetries, and returns
|
|
101
|
+
// false on network errors / timeout after exhausting retries.
|
|
102
|
+
async sendDetailed(batch) {
|
|
103
|
+
const maxRetries = Math.min(this.cfg.maxRetries ?? 2, MAX_RETRIES_CAP);
|
|
104
|
+
const timeoutMs = this.cfg.timeoutMs ?? 8e3;
|
|
105
|
+
const rawBackoff = this.cfg.backoffMs ?? DEFAULT_BACKOFF;
|
|
106
|
+
const backoff = (attempt) => Math.min(rawBackoff(attempt), MAX_BACKOFF_MS);
|
|
107
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
108
|
+
try {
|
|
109
|
+
const controller = new AbortController();
|
|
110
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
111
|
+
let res;
|
|
112
|
+
try {
|
|
113
|
+
res = await fetch(this.cfg.apiUrl, {
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: this.headers(),
|
|
116
|
+
body: JSON.stringify(batch),
|
|
117
|
+
signal: controller.signal
|
|
118
|
+
});
|
|
119
|
+
} finally {
|
|
120
|
+
clearTimeout(timer);
|
|
121
|
+
}
|
|
122
|
+
if (res.ok) return { ok: true, status: res.status };
|
|
123
|
+
if (res.status >= 400 && res.status < 500) {
|
|
124
|
+
return { ok: false, status: res.status, retryable: false };
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
if (attempt < maxRetries) {
|
|
129
|
+
await new Promise((r) => setTimeout(() => r(), backoff(attempt)));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { ok: false, retryable: true };
|
|
133
|
+
}
|
|
134
|
+
async send(batch) {
|
|
135
|
+
const res = await this.sendDetailed(batch);
|
|
136
|
+
return res.ok;
|
|
137
|
+
}
|
|
138
|
+
headers() {
|
|
139
|
+
const h = { "content-type": "application/json" };
|
|
140
|
+
if (this.cfg.apiKey) {
|
|
141
|
+
h.authorization = `Bearer ${this.cfg.apiKey}`;
|
|
142
|
+
h["x-api-key"] = this.cfg.apiKey;
|
|
143
|
+
}
|
|
144
|
+
if (this.cfg.projectId) {
|
|
145
|
+
h["x-project-id"] = this.cfg.projectId;
|
|
146
|
+
}
|
|
147
|
+
return h;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
function mapAppState(state2) {
|
|
151
|
+
if (state2 === "active") return "active";
|
|
152
|
+
if (state2 === "background") return "background";
|
|
153
|
+
return "inactive";
|
|
154
|
+
}
|
|
155
|
+
function orientationFor(width, height) {
|
|
156
|
+
return width > height ? "landscape" : "portrait";
|
|
157
|
+
}
|
|
158
|
+
function createRecorder(deps) {
|
|
159
|
+
const cleanups = [];
|
|
160
|
+
let started = false;
|
|
161
|
+
let wireframeTimer = null;
|
|
162
|
+
function takeWireframeSnapshot() {
|
|
163
|
+
if (!deps.captureViewHierarchy) return;
|
|
164
|
+
try {
|
|
165
|
+
const raw = deps.captureViewHierarchy();
|
|
166
|
+
if (!raw || typeof raw !== "string") return;
|
|
167
|
+
const payload = JSON.parse(raw);
|
|
168
|
+
if (payload && Array.isArray(payload.nodes)) {
|
|
169
|
+
deps.onEvent({
|
|
170
|
+
type: "wireframe",
|
|
171
|
+
ts: deps.now(),
|
|
172
|
+
version: typeof payload.version === "number" ? payload.version : 1,
|
|
173
|
+
nodes: payload.nodes
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function startWireframeTimer() {
|
|
180
|
+
if (!deps.captureWireframe || !deps.captureViewHierarchy) return;
|
|
181
|
+
if (wireframeTimer) return;
|
|
182
|
+
takeWireframeSnapshot();
|
|
183
|
+
const rawInterval = deps.wireframeIntervalMs;
|
|
184
|
+
const intervalMs = typeof rawInterval === "number" && Number.isFinite(rawInterval) && rawInterval > 0 ? Math.max(500, rawInterval) : 1e3;
|
|
185
|
+
wireframeTimer = setInterval(() => {
|
|
186
|
+
takeWireframeSnapshot();
|
|
187
|
+
}, intervalMs);
|
|
188
|
+
}
|
|
189
|
+
function stopWireframeTimer() {
|
|
190
|
+
if (wireframeTimer) {
|
|
191
|
+
clearInterval(wireframeTimer);
|
|
192
|
+
wireframeTimer = null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
takeWireframeSnapshot,
|
|
197
|
+
start() {
|
|
198
|
+
if (started) return;
|
|
199
|
+
started = true;
|
|
200
|
+
const { width, height, scale } = Dimensions.get("window");
|
|
201
|
+
deps.onEvent({
|
|
202
|
+
type: "screen",
|
|
203
|
+
ts: deps.now(),
|
|
204
|
+
width,
|
|
205
|
+
height,
|
|
206
|
+
scale,
|
|
207
|
+
orientation: orientationFor(width, height)
|
|
208
|
+
});
|
|
209
|
+
const appStateSub = AppState.addEventListener("change", (state2) => {
|
|
210
|
+
const mapped = mapAppState(state2);
|
|
211
|
+
deps.onEvent({
|
|
212
|
+
type: "lifecycle",
|
|
213
|
+
ts: deps.now(),
|
|
214
|
+
state: mapped
|
|
215
|
+
});
|
|
216
|
+
if (deps.captureWireframe && deps.captureViewHierarchy) {
|
|
217
|
+
if (mapped === "active") {
|
|
218
|
+
startWireframeTimer();
|
|
219
|
+
} else {
|
|
220
|
+
stopWireframeTimer();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
cleanups.push(() => appStateSub.remove());
|
|
225
|
+
const dimSub = Dimensions.addEventListener("change", ({ window }) => {
|
|
226
|
+
deps.onEvent({
|
|
227
|
+
type: "screen",
|
|
228
|
+
ts: deps.now(),
|
|
229
|
+
width: window.width,
|
|
230
|
+
height: window.height,
|
|
231
|
+
scale: window.scale,
|
|
232
|
+
orientation: orientationFor(window.width, window.height)
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
cleanups.push(() => dimSub.remove());
|
|
236
|
+
if (deps.installErrorHandler) {
|
|
237
|
+
const g = globalThis;
|
|
238
|
+
const eu = g.ErrorUtils;
|
|
239
|
+
if (eu) {
|
|
240
|
+
let handlerActive = true;
|
|
241
|
+
const original = eu.getGlobalHandler();
|
|
242
|
+
const ourHandler = (error, isFatal) => {
|
|
243
|
+
if (handlerActive) {
|
|
244
|
+
try {
|
|
245
|
+
deps.onEvent({
|
|
246
|
+
type: "error",
|
|
247
|
+
ts: deps.now(),
|
|
248
|
+
message: error?.message ?? String(error),
|
|
249
|
+
stack: error?.stack,
|
|
250
|
+
name: error?.name,
|
|
251
|
+
fatal: !!isFatal
|
|
252
|
+
});
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (original) original(error, isFatal);
|
|
257
|
+
};
|
|
258
|
+
eu.setGlobalHandler(ourHandler);
|
|
259
|
+
cleanups.push(() => {
|
|
260
|
+
handlerActive = false;
|
|
261
|
+
if (eu.getGlobalHandler() === ourHandler) {
|
|
262
|
+
eu.setGlobalHandler(original);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (deps.captureWireframe && deps.captureViewHierarchy) {
|
|
268
|
+
if (AppState.currentState === "active" || !AppState.currentState) {
|
|
269
|
+
startWireframeTimer();
|
|
270
|
+
}
|
|
271
|
+
cleanups.push(() => stopWireframeTimer());
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
stop() {
|
|
275
|
+
if (!started) return;
|
|
276
|
+
started = false;
|
|
277
|
+
stopWireframeTimer();
|
|
278
|
+
for (const fn of cleanups.splice(0)) {
|
|
279
|
+
try {
|
|
280
|
+
fn();
|
|
281
|
+
} catch {
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// src/modules/network-interceptor.ts
|
|
289
|
+
var MAX_URL_LENGTH = 2048;
|
|
290
|
+
var stripQuery = (raw) => {
|
|
291
|
+
if (typeof raw !== "string") return "";
|
|
292
|
+
if (raw.startsWith("data:")) {
|
|
293
|
+
const commaIdx = raw.indexOf(",");
|
|
294
|
+
if (commaIdx !== -1) {
|
|
295
|
+
return `${raw.slice(0, commaIdx + 1)}[data-url]`;
|
|
296
|
+
}
|
|
297
|
+
return "data:[data-url]";
|
|
298
|
+
}
|
|
299
|
+
const strippedCredentials = raw.replace(
|
|
300
|
+
/^((?:[a-zA-Z][a-zA-Z\d+.-]*:)?\/\/)[^/?#]+@/,
|
|
301
|
+
"$1"
|
|
302
|
+
);
|
|
303
|
+
const cutIdx = strippedCredentials.search(/[?#]/);
|
|
304
|
+
const cleaned = cutIdx === -1 ? strippedCredentials : strippedCredentials.substring(0, cutIdx);
|
|
305
|
+
return cleaned.length > MAX_URL_LENGTH ? cleaned.slice(0, MAX_URL_LENGTH) + "\u2026" : cleaned;
|
|
306
|
+
};
|
|
307
|
+
var xhrMetadataMap = /* @__PURE__ */ new WeakMap();
|
|
308
|
+
var NetworkInterceptor = class {
|
|
309
|
+
constructor(deps) {
|
|
310
|
+
this.deps = deps;
|
|
311
|
+
this.originalFetch = null;
|
|
312
|
+
this.wrappedFetch = null;
|
|
313
|
+
this.originalXHROpen = null;
|
|
314
|
+
this.wrappedXHROpen = null;
|
|
315
|
+
this.originalXHRSend = null;
|
|
316
|
+
this.wrappedXHRSend = null;
|
|
317
|
+
this.started = false;
|
|
318
|
+
this.isCallingFetchSync = false;
|
|
319
|
+
this.ingestOrigin = null;
|
|
320
|
+
this.ingestPath = null;
|
|
321
|
+
this.sanitize = deps.sanitizeUrl ?? stripQuery;
|
|
322
|
+
if (deps.apiUrl) {
|
|
323
|
+
try {
|
|
324
|
+
const parsed = typeof URL !== "undefined" ? new URL(deps.apiUrl) : null;
|
|
325
|
+
if (parsed) {
|
|
326
|
+
this.ingestOrigin = parsed.origin;
|
|
327
|
+
const cleanPath = parsed.pathname.replace(/\/+$/, "");
|
|
328
|
+
this.ingestPath = cleanPath || "/";
|
|
329
|
+
}
|
|
330
|
+
} catch {
|
|
331
|
+
this.ingestOrigin = null;
|
|
332
|
+
this.ingestPath = null;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
isSelfIngestUrl(url) {
|
|
337
|
+
if (!url) return false;
|
|
338
|
+
try {
|
|
339
|
+
const parsed = typeof URL !== "undefined" ? new URL(url, "http://localhost") : null;
|
|
340
|
+
if (parsed) {
|
|
341
|
+
if (this.ingestOrigin && parsed.origin === this.ingestOrigin) {
|
|
342
|
+
if (this.ingestPath === "/" || !this.ingestPath) {
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
const reqPath = parsed.pathname.replace(/\/+$/, "") || "/";
|
|
346
|
+
if (reqPath === this.ingestPath || reqPath.startsWith(this.ingestPath + "/")) {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
if (/(^|\.)clintplay\./i.test(parsed.hostname || parsed.host || "")) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
} catch {
|
|
357
|
+
}
|
|
358
|
+
return /(^|\.)clintplay\./i.test(url);
|
|
359
|
+
}
|
|
360
|
+
start() {
|
|
361
|
+
if (this.started) return;
|
|
362
|
+
this.started = true;
|
|
363
|
+
if (typeof globalThis.fetch === "function") {
|
|
364
|
+
if (globalThis.fetch !== this.wrappedFetch) {
|
|
365
|
+
this.originalFetch = globalThis.fetch;
|
|
366
|
+
}
|
|
367
|
+
const self = this;
|
|
368
|
+
const wrapped = async (input, init2) => {
|
|
369
|
+
if (!self.started || !self.originalFetch) {
|
|
370
|
+
return (self.originalFetch ?? globalThis.fetch)(input, init2);
|
|
371
|
+
}
|
|
372
|
+
const startedAt = self.deps.now();
|
|
373
|
+
const url = self.extractUrl(input);
|
|
374
|
+
const method = self.extractMethod(input, init2);
|
|
375
|
+
const skipTelemetry = !self.started || self.isSelfIngestUrl(url);
|
|
376
|
+
self.isCallingFetchSync = true;
|
|
377
|
+
let promise;
|
|
378
|
+
try {
|
|
379
|
+
promise = self.originalFetch(input, init2);
|
|
380
|
+
} finally {
|
|
381
|
+
self.isCallingFetchSync = false;
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
const res = await promise;
|
|
385
|
+
if (!skipTelemetry && self.started) {
|
|
386
|
+
try {
|
|
387
|
+
self.deps.onEvent({
|
|
388
|
+
type: "network",
|
|
389
|
+
ts: startedAt,
|
|
390
|
+
method,
|
|
391
|
+
url: self.sanitize(url),
|
|
392
|
+
status: res.status,
|
|
393
|
+
durationMs: Math.max(0, self.deps.now() - startedAt)
|
|
394
|
+
});
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return res;
|
|
399
|
+
} catch (err) {
|
|
400
|
+
if (!skipTelemetry && self.started) {
|
|
401
|
+
try {
|
|
402
|
+
self.deps.onEvent({
|
|
403
|
+
type: "network",
|
|
404
|
+
ts: startedAt,
|
|
405
|
+
method,
|
|
406
|
+
url: self.sanitize(url),
|
|
407
|
+
durationMs: Math.max(0, self.deps.now() - startedAt),
|
|
408
|
+
error: err instanceof Error ? err.message : String(err)
|
|
409
|
+
});
|
|
410
|
+
} catch {
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
throw err;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
this.wrappedFetch = wrapped;
|
|
417
|
+
globalThis.fetch = this.wrappedFetch;
|
|
418
|
+
}
|
|
419
|
+
const g = globalThis;
|
|
420
|
+
if (g.XMLHttpRequest && g.XMLHttpRequest.prototype) {
|
|
421
|
+
const xhrProto = g.XMLHttpRequest.prototype;
|
|
422
|
+
if (xhrProto.open !== this.wrappedXHROpen) {
|
|
423
|
+
this.originalXHROpen = xhrProto.open;
|
|
424
|
+
}
|
|
425
|
+
if (xhrProto.send !== this.wrappedXHRSend) {
|
|
426
|
+
this.originalXHRSend = xhrProto.send;
|
|
427
|
+
}
|
|
428
|
+
const self = this;
|
|
429
|
+
const wrappedOpen = function(method, url, ...rest) {
|
|
430
|
+
if (self.started) {
|
|
431
|
+
try {
|
|
432
|
+
const urlStr = self.extractUrl(url);
|
|
433
|
+
const normalizedMethod = typeof method === "string" ? method.toUpperCase() : "GET";
|
|
434
|
+
const isFromFetch = self.isCallingFetchSync;
|
|
435
|
+
const meta = {
|
|
436
|
+
method: normalizedMethod,
|
|
437
|
+
url: self.sanitize(urlStr),
|
|
438
|
+
rawUrl: urlStr,
|
|
439
|
+
startTime: 0,
|
|
440
|
+
isFromFetch
|
|
441
|
+
};
|
|
442
|
+
if (this && typeof this === "object") {
|
|
443
|
+
xhrMetadataMap.set(this, meta);
|
|
444
|
+
}
|
|
445
|
+
} catch {
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (self.originalXHROpen) {
|
|
449
|
+
return self.originalXHROpen.apply(this, [method, url, ...rest]);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
const wrappedSend = function(body) {
|
|
453
|
+
if (self.started && this && typeof this === "object") {
|
|
454
|
+
try {
|
|
455
|
+
const meta = xhrMetadataMap.get(this);
|
|
456
|
+
if (meta && !meta.isFromFetch && !self.isSelfIngestUrl(meta.rawUrl || meta.url)) {
|
|
457
|
+
meta.startTime = self.deps.now();
|
|
458
|
+
let emitted = false;
|
|
459
|
+
const cleanupListeners = () => {
|
|
460
|
+
if (typeof this.removeEventListener === "function") {
|
|
461
|
+
this.removeEventListener("load", handleLoad);
|
|
462
|
+
this.removeEventListener("error", handleError);
|
|
463
|
+
this.removeEventListener("abort", handleAbort);
|
|
464
|
+
this.removeEventListener("timeout", handleTimeout);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
const emitOnce = (event) => {
|
|
468
|
+
cleanupListeners();
|
|
469
|
+
if (this && typeof this === "object") {
|
|
470
|
+
xhrMetadataMap.delete(this);
|
|
471
|
+
}
|
|
472
|
+
if (emitted || !self.started) return;
|
|
473
|
+
emitted = true;
|
|
474
|
+
try {
|
|
475
|
+
self.deps.onEvent(event);
|
|
476
|
+
} catch {
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
const handleLoad = () => {
|
|
480
|
+
emitOnce({
|
|
481
|
+
type: "network",
|
|
482
|
+
ts: meta.startTime,
|
|
483
|
+
method: meta.method,
|
|
484
|
+
url: meta.url,
|
|
485
|
+
status: typeof this.status === "number" ? this.status : 200,
|
|
486
|
+
durationMs: Math.max(0, self.deps.now() - meta.startTime)
|
|
487
|
+
});
|
|
488
|
+
};
|
|
489
|
+
const handleError = (e) => {
|
|
490
|
+
emitOnce({
|
|
491
|
+
type: "network",
|
|
492
|
+
ts: meta.startTime,
|
|
493
|
+
method: meta.method,
|
|
494
|
+
url: meta.url,
|
|
495
|
+
durationMs: Math.max(0, self.deps.now() - meta.startTime),
|
|
496
|
+
error: e && e.message || "Network request failed"
|
|
497
|
+
});
|
|
498
|
+
};
|
|
499
|
+
const handleAbort = () => {
|
|
500
|
+
emitOnce({
|
|
501
|
+
type: "network",
|
|
502
|
+
ts: meta.startTime,
|
|
503
|
+
method: meta.method,
|
|
504
|
+
url: meta.url,
|
|
505
|
+
durationMs: Math.max(0, self.deps.now() - meta.startTime),
|
|
506
|
+
error: "Request aborted"
|
|
507
|
+
});
|
|
508
|
+
};
|
|
509
|
+
const handleTimeout = () => {
|
|
510
|
+
emitOnce({
|
|
511
|
+
type: "network",
|
|
512
|
+
ts: meta.startTime,
|
|
513
|
+
method: meta.method,
|
|
514
|
+
url: meta.url,
|
|
515
|
+
durationMs: Math.max(0, self.deps.now() - meta.startTime),
|
|
516
|
+
error: "Request timed out"
|
|
517
|
+
});
|
|
518
|
+
};
|
|
519
|
+
if (typeof this.addEventListener === "function") {
|
|
520
|
+
this.addEventListener("load", handleLoad);
|
|
521
|
+
this.addEventListener("error", handleError);
|
|
522
|
+
this.addEventListener("abort", handleAbort);
|
|
523
|
+
this.addEventListener("timeout", handleTimeout);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (self.originalXHRSend) {
|
|
530
|
+
return self.originalXHRSend.apply(this, [body]);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
this.wrappedXHROpen = wrappedOpen;
|
|
534
|
+
this.wrappedXHRSend = wrappedSend;
|
|
535
|
+
xhrProto.open = wrappedOpen;
|
|
536
|
+
xhrProto.send = wrappedSend;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
stop() {
|
|
540
|
+
if (!this.started) return;
|
|
541
|
+
this.started = false;
|
|
542
|
+
if (this.originalFetch && this.wrappedFetch) {
|
|
543
|
+
if (globalThis.fetch === this.wrappedFetch) {
|
|
544
|
+
globalThis.fetch = this.originalFetch;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const g = globalThis;
|
|
548
|
+
if (g.XMLHttpRequest && g.XMLHttpRequest.prototype) {
|
|
549
|
+
const xhrProto = g.XMLHttpRequest.prototype;
|
|
550
|
+
if (this.originalXHROpen && xhrProto.open === this.wrappedXHROpen) {
|
|
551
|
+
xhrProto.open = this.originalXHROpen;
|
|
552
|
+
}
|
|
553
|
+
if (this.originalXHRSend && xhrProto.send === this.wrappedXHRSend) {
|
|
554
|
+
xhrProto.send = this.originalXHRSend;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
extractUrl(input) {
|
|
559
|
+
if (typeof input === "string") return input;
|
|
560
|
+
if (typeof URL !== "undefined" && input instanceof URL) return input.toString();
|
|
561
|
+
if (input && typeof input === "object" && "url" in input && typeof input.url === "string") {
|
|
562
|
+
return input.url;
|
|
563
|
+
}
|
|
564
|
+
return String(input ?? "");
|
|
565
|
+
}
|
|
566
|
+
extractMethod(input, init2) {
|
|
567
|
+
const fromInit = init2?.method;
|
|
568
|
+
if (fromInit) return fromInit.toUpperCase();
|
|
569
|
+
if (input && typeof input === "object" && "method" in input && typeof input.method === "string") {
|
|
570
|
+
return input.method.toUpperCase();
|
|
571
|
+
}
|
|
572
|
+
return "GET";
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// src/modules/console-interceptor.ts
|
|
577
|
+
var DEFAULT_MAX_LENGTH = 1e3;
|
|
578
|
+
var ConsoleInterceptor = class {
|
|
579
|
+
constructor(deps) {
|
|
580
|
+
this.deps = deps;
|
|
581
|
+
this.originals = /* @__PURE__ */ new Map();
|
|
582
|
+
this.wrapped = /* @__PURE__ */ new Map();
|
|
583
|
+
this.started = false;
|
|
584
|
+
this.isEmitting = false;
|
|
585
|
+
this.maxLength = deps.maxMessageLength ?? DEFAULT_MAX_LENGTH;
|
|
586
|
+
}
|
|
587
|
+
start() {
|
|
588
|
+
if (this.started) return;
|
|
589
|
+
if (typeof console === "undefined") return;
|
|
590
|
+
this.started = true;
|
|
591
|
+
const methods = this.deps.captureConsoleLog ? ["log", "warn", "error"] : ["warn", "error"];
|
|
592
|
+
for (const method of methods) {
|
|
593
|
+
this.wrapMethod(method);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
wrapMethod(method) {
|
|
597
|
+
const c = console;
|
|
598
|
+
const original = c[method];
|
|
599
|
+
if (typeof original !== "function") return;
|
|
600
|
+
this.originals.set(method, original);
|
|
601
|
+
const wrappedFn = (...args) => {
|
|
602
|
+
if (this.started && !this.isEmitting) {
|
|
603
|
+
this.isEmitting = true;
|
|
604
|
+
try {
|
|
605
|
+
this.deps.onEvent({
|
|
606
|
+
type: "log",
|
|
607
|
+
ts: this.deps.now(),
|
|
608
|
+
level: method === "log" ? "info" : method,
|
|
609
|
+
message: this.formatMessage(args)
|
|
610
|
+
});
|
|
611
|
+
} catch {
|
|
612
|
+
} finally {
|
|
613
|
+
this.isEmitting = false;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
original.apply(console, args);
|
|
617
|
+
};
|
|
618
|
+
this.wrapped.set(method, wrappedFn);
|
|
619
|
+
c[method] = wrappedFn;
|
|
620
|
+
}
|
|
621
|
+
stop() {
|
|
622
|
+
if (!this.started) return;
|
|
623
|
+
this.started = false;
|
|
624
|
+
this.isEmitting = false;
|
|
625
|
+
const c = console;
|
|
626
|
+
for (const [method, original] of this.originals) {
|
|
627
|
+
if (c[method] === this.wrapped.get(method)) {
|
|
628
|
+
c[method] = original;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
this.originals.clear();
|
|
632
|
+
this.wrapped.clear();
|
|
633
|
+
}
|
|
634
|
+
formatMessage(args) {
|
|
635
|
+
const text = args.map((a) => {
|
|
636
|
+
if (a === void 0) return "undefined";
|
|
637
|
+
if (typeof a === "string") return a;
|
|
638
|
+
if (typeof a === "number") return String(a);
|
|
639
|
+
if (typeof a === "boolean") return String(a);
|
|
640
|
+
if (typeof a === "symbol") return a.toString();
|
|
641
|
+
if (typeof a === "function") {
|
|
642
|
+
return a.name ? `[Function: ${a.name}]` : "[Function]";
|
|
643
|
+
}
|
|
644
|
+
if (a instanceof Error) return a.stack || `${a.name}: ${a.message}`;
|
|
645
|
+
try {
|
|
646
|
+
const json = JSON.stringify(a);
|
|
647
|
+
return json !== void 0 ? json : String(a);
|
|
648
|
+
} catch {
|
|
649
|
+
return String(a);
|
|
650
|
+
}
|
|
651
|
+
}).join(" ");
|
|
652
|
+
return text.length > this.maxLength ? text.slice(0, this.maxLength) + "\u2026" : text;
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
// src/modules/offline-queue.ts
|
|
657
|
+
var DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
|
|
658
|
+
var DEFAULT_PREFIX = "clintplay:batch:";
|
|
659
|
+
var OfflineQueue = class {
|
|
660
|
+
constructor(deps) {
|
|
661
|
+
this.deps = deps;
|
|
662
|
+
this.currentBytes = 0;
|
|
663
|
+
// Monotonic sequence number appended to the storage key. Two batches
|
|
664
|
+
// enqueued in the same millisecond would otherwise tie on the
|
|
665
|
+
// timestamp prefix and sort non-deterministically. The padded seq
|
|
666
|
+
// resolves ties while preserving lex order = enqueue order.
|
|
667
|
+
this.seq = 0;
|
|
668
|
+
this.hydrationPromise = null;
|
|
669
|
+
this.prefix = deps.keyPrefix ?? DEFAULT_PREFIX;
|
|
670
|
+
this.maxBytes = deps.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
671
|
+
}
|
|
672
|
+
// Compute the on-disk footprint from existing keys. Cheap on first
|
|
673
|
+
// boot, marginal on subsequent boots — called once from `init()`.
|
|
674
|
+
// Per-key getItem errors are isolated so a single flaky entry can't
|
|
675
|
+
// wedge accounting for the entire queue. `getAllKeys` failure is
|
|
676
|
+
// the only hard stop (no keys → no work).
|
|
677
|
+
hydrate(force = false) {
|
|
678
|
+
if (force) {
|
|
679
|
+
this.hydrationPromise = null;
|
|
680
|
+
}
|
|
681
|
+
if (!this.hydrationPromise) {
|
|
682
|
+
this.hydrationPromise = (async () => {
|
|
683
|
+
let keys;
|
|
684
|
+
try {
|
|
685
|
+
keys = (await this.deps.storage.getAllKeys()).filter(
|
|
686
|
+
(k) => k.startsWith(this.prefix)
|
|
687
|
+
);
|
|
688
|
+
} catch (err) {
|
|
689
|
+
this.deps.debug?.(
|
|
690
|
+
`offline-queue hydrate getAllKeys failed: ${String(err)}`
|
|
691
|
+
);
|
|
692
|
+
this.hydrationPromise = null;
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
let total = 0;
|
|
696
|
+
for (const k of keys) {
|
|
697
|
+
try {
|
|
698
|
+
const raw = await this.deps.storage.getItem(k);
|
|
699
|
+
if (raw) total += raw.length;
|
|
700
|
+
} catch (err) {
|
|
701
|
+
this.deps.debug?.(
|
|
702
|
+
`offline-queue hydrate getItem ${k} failed: ${String(err)}`
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
this.currentBytes = total;
|
|
707
|
+
})().catch((err) => {
|
|
708
|
+
this.hydrationPromise = null;
|
|
709
|
+
throw err;
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
return this.hydrationPromise;
|
|
713
|
+
}
|
|
714
|
+
// Persist a single batch. Caller invokes after a live send fails.
|
|
715
|
+
// Silently drops batches that exceed the per-batch ceiling — those
|
|
716
|
+
// are unrecoverable even with retries, and keeping them around would
|
|
717
|
+
// wedge the queue.
|
|
718
|
+
async persist(batch) {
|
|
719
|
+
if (this.hydrationPromise) {
|
|
720
|
+
await this.hydrationPromise;
|
|
721
|
+
}
|
|
722
|
+
const json = JSON.stringify(batch);
|
|
723
|
+
const bytes = json.length;
|
|
724
|
+
if (bytes > this.maxBytes) {
|
|
725
|
+
this.deps.debug?.(
|
|
726
|
+
`offline-queue batch too large (${bytes} > ${this.maxBytes})`
|
|
727
|
+
);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
await this.evictUntilFits(bytes);
|
|
731
|
+
const ts = Date.now().toString().padStart(13, "0");
|
|
732
|
+
const seqStr = (this.seq++).toString().padStart(6, "0");
|
|
733
|
+
const key = `${this.prefix}${ts}-${seqStr}`;
|
|
734
|
+
try {
|
|
735
|
+
await this.deps.storage.setItem(key, json);
|
|
736
|
+
this.currentBytes += bytes;
|
|
737
|
+
} catch (err) {
|
|
738
|
+
this.deps.debug?.(`offline-queue setItem failed: ${String(err)}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
// Replay all persisted batches in enqueue order. Stops at the first
|
|
742
|
+
// failed send — a network outage that caused the queue to fill up is
|
|
743
|
+
// typically still ongoing on the next boot; pushing more batches at
|
|
744
|
+
// a dead endpoint just burns retries and drops events on the floor.
|
|
745
|
+
async drain() {
|
|
746
|
+
if (this.hydrationPromise) {
|
|
747
|
+
await this.hydrationPromise;
|
|
748
|
+
}
|
|
749
|
+
let sent = 0;
|
|
750
|
+
try {
|
|
751
|
+
const keys = (await this.deps.storage.getAllKeys()).filter((k) => k.startsWith(this.prefix)).sort();
|
|
752
|
+
for (const key of keys) {
|
|
753
|
+
const raw = await this.deps.storage.getItem(key);
|
|
754
|
+
if (!raw) {
|
|
755
|
+
await this.deps.storage.removeItem(key);
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
try {
|
|
759
|
+
const batch = JSON.parse(raw);
|
|
760
|
+
const result = await this.deps.send(batch);
|
|
761
|
+
const ok = typeof result === "boolean" ? result : !!result?.ok;
|
|
762
|
+
const isNonRetryable = typeof result === "object" && result !== null && (typeof result.status === "number" && result.status >= 400 && result.status < 500 || result.retryable === false);
|
|
763
|
+
if (!ok) {
|
|
764
|
+
if (isNonRetryable) {
|
|
765
|
+
this.deps.debug?.(
|
|
766
|
+
`offline-queue drop 4xx non-retryable batch for key: ${key}`
|
|
767
|
+
);
|
|
768
|
+
await this.deps.storage.removeItem(key);
|
|
769
|
+
this.currentBytes = Math.max(0, this.currentBytes - raw.length);
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
break;
|
|
773
|
+
}
|
|
774
|
+
await this.deps.storage.removeItem(key);
|
|
775
|
+
this.currentBytes = Math.max(0, this.currentBytes - raw.length);
|
|
776
|
+
sent++;
|
|
777
|
+
} catch (err) {
|
|
778
|
+
this.deps.debug?.(`offline-queue drop bad batch: ${String(err)}`);
|
|
779
|
+
await this.deps.storage.removeItem(key);
|
|
780
|
+
this.currentBytes = Math.max(0, this.currentBytes - raw.length);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
} catch (err) {
|
|
784
|
+
this.deps.debug?.(`offline-queue drain failed: ${String(err)}`);
|
|
785
|
+
}
|
|
786
|
+
return sent;
|
|
787
|
+
}
|
|
788
|
+
async clear() {
|
|
789
|
+
try {
|
|
790
|
+
const keys = (await this.deps.storage.getAllKeys()).filter(
|
|
791
|
+
(k) => k.startsWith(this.prefix)
|
|
792
|
+
);
|
|
793
|
+
if (keys.length > 0) {
|
|
794
|
+
await this.deps.storage.multiRemove(keys);
|
|
795
|
+
}
|
|
796
|
+
this.currentBytes = 0;
|
|
797
|
+
this.hydrationPromise = null;
|
|
798
|
+
} catch (err) {
|
|
799
|
+
this.deps.debug?.(`offline-queue clear failed: ${String(err)}`);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
async evictUntilFits(bytesNeeded) {
|
|
803
|
+
if (this.currentBytes + bytesNeeded <= this.maxBytes) return;
|
|
804
|
+
const toFree = this.currentBytes + bytesNeeded - this.maxBytes;
|
|
805
|
+
let keys;
|
|
806
|
+
try {
|
|
807
|
+
keys = (await this.deps.storage.getAllKeys()).filter((k) => k.startsWith(this.prefix)).sort();
|
|
808
|
+
} catch (err) {
|
|
809
|
+
this.deps.debug?.(`evict getAllKeys failed: ${String(err)}`);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
let freed = 0;
|
|
813
|
+
const toRemove = [];
|
|
814
|
+
for (const key of keys) {
|
|
815
|
+
if (freed >= toFree) break;
|
|
816
|
+
let raw;
|
|
817
|
+
try {
|
|
818
|
+
raw = await this.deps.storage.getItem(key);
|
|
819
|
+
} catch (err) {
|
|
820
|
+
this.deps.debug?.(`evict getItem ${key} failed: ${String(err)}`);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
if (raw != null) freed += raw.length;
|
|
824
|
+
toRemove.push(key);
|
|
825
|
+
}
|
|
826
|
+
if (toRemove.length === 0) return;
|
|
827
|
+
try {
|
|
828
|
+
await this.deps.storage.multiRemove(toRemove);
|
|
829
|
+
this.currentBytes = Math.max(0, this.currentBytes - freed);
|
|
830
|
+
} catch (err) {
|
|
831
|
+
this.deps.debug?.(`evict multiRemove failed: ${String(err)}`);
|
|
832
|
+
await this.hydrate(true);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
// src/runtime-bridge.ts
|
|
838
|
+
var sink = null;
|
|
839
|
+
function _setSink(next) {
|
|
840
|
+
sink = next;
|
|
841
|
+
}
|
|
842
|
+
function emit(event) {
|
|
843
|
+
if (sink) {
|
|
844
|
+
try {
|
|
845
|
+
sink(event);
|
|
846
|
+
} catch {
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
var cachedModule = null;
|
|
851
|
+
function resolveNativeModule() {
|
|
852
|
+
if (cachedModule) return cachedModule;
|
|
853
|
+
try {
|
|
854
|
+
const turbo = TurboModuleRegistry.get("ClintplayRN");
|
|
855
|
+
if (turbo) {
|
|
856
|
+
cachedModule = turbo;
|
|
857
|
+
return cachedModule;
|
|
858
|
+
}
|
|
859
|
+
} catch {
|
|
860
|
+
}
|
|
861
|
+
try {
|
|
862
|
+
const paper = NativeModules.ClintplayRN ?? null;
|
|
863
|
+
if (paper) {
|
|
864
|
+
cachedModule = paper;
|
|
865
|
+
return cachedModule;
|
|
866
|
+
}
|
|
867
|
+
} catch {
|
|
868
|
+
}
|
|
869
|
+
return null;
|
|
870
|
+
}
|
|
871
|
+
function captureViewHierarchy() {
|
|
872
|
+
const n = resolveNativeModule();
|
|
873
|
+
if (!n) return null;
|
|
874
|
+
try {
|
|
875
|
+
return n.captureViewHierarchy();
|
|
876
|
+
} catch {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// package.json
|
|
882
|
+
var version = "0.1.0";
|
|
883
|
+
var CLINTPLAY_MASK_ID = "clintplay-mask";
|
|
884
|
+
var ClintplayMaskView = React5.forwardRef(function ClintplayMaskView2({ children, testID, ...rest }, ref) {
|
|
885
|
+
return /* @__PURE__ */ jsx(
|
|
886
|
+
View,
|
|
887
|
+
{
|
|
888
|
+
ref,
|
|
889
|
+
testID: testID ?? CLINTPLAY_MASK_ID,
|
|
890
|
+
...rest,
|
|
891
|
+
collapsable: false,
|
|
892
|
+
nativeID: CLINTPLAY_MASK_ID,
|
|
893
|
+
children
|
|
894
|
+
}
|
|
895
|
+
);
|
|
896
|
+
});
|
|
897
|
+
var ClintplayMaskTextInput = React5.forwardRef(function ClintplayMaskTextInput2({ testID, accessibilityLabel, ...props }, ref) {
|
|
898
|
+
return /* @__PURE__ */ jsx(
|
|
899
|
+
TextInput,
|
|
900
|
+
{
|
|
901
|
+
ref,
|
|
902
|
+
testID: testID ?? CLINTPLAY_MASK_ID,
|
|
903
|
+
accessibilityLabel,
|
|
904
|
+
...props,
|
|
905
|
+
nativeID: CLINTPLAY_MASK_ID
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
});
|
|
909
|
+
var ClintplayMaskImage = React5.forwardRef(function ClintplayMaskImage2({ testID, accessibilityLabel, ...props }, ref) {
|
|
910
|
+
return /* @__PURE__ */ jsx(
|
|
911
|
+
Image,
|
|
912
|
+
{
|
|
913
|
+
ref,
|
|
914
|
+
testID: testID ?? CLINTPLAY_MASK_ID,
|
|
915
|
+
accessibilityLabel,
|
|
916
|
+
...props,
|
|
917
|
+
nativeID: CLINTPLAY_MASK_ID
|
|
918
|
+
}
|
|
919
|
+
);
|
|
920
|
+
});
|
|
921
|
+
function ClintplayCaptureProvider({
|
|
922
|
+
children,
|
|
923
|
+
style,
|
|
924
|
+
enabled = true,
|
|
925
|
+
onTouchStart,
|
|
926
|
+
onTouchEnd,
|
|
927
|
+
onTouchCancel,
|
|
928
|
+
...rest
|
|
929
|
+
}) {
|
|
930
|
+
const onStart = useCallback(
|
|
931
|
+
(e) => {
|
|
932
|
+
const nativeEvent = e?.nativeEvent;
|
|
933
|
+
if (!nativeEvent) return;
|
|
934
|
+
const { locationX = 0, locationY = 0, pageX, pageY } = nativeEvent;
|
|
935
|
+
emit({
|
|
936
|
+
type: "touch",
|
|
937
|
+
ts: Date.now(),
|
|
938
|
+
x: locationX,
|
|
939
|
+
y: locationY,
|
|
940
|
+
screenX: pageX,
|
|
941
|
+
screenY: pageY,
|
|
942
|
+
kind: "start"
|
|
943
|
+
});
|
|
944
|
+
onTouchStart?.(e);
|
|
945
|
+
},
|
|
946
|
+
[onTouchStart]
|
|
947
|
+
);
|
|
948
|
+
const onEnd = useCallback(
|
|
949
|
+
(e) => {
|
|
950
|
+
const nativeEvent = e?.nativeEvent;
|
|
951
|
+
if (!nativeEvent) return;
|
|
952
|
+
const { locationX = 0, locationY = 0, pageX, pageY } = nativeEvent;
|
|
953
|
+
emit({
|
|
954
|
+
type: "touch",
|
|
955
|
+
ts: Date.now(),
|
|
956
|
+
x: locationX,
|
|
957
|
+
y: locationY,
|
|
958
|
+
screenX: pageX,
|
|
959
|
+
screenY: pageY,
|
|
960
|
+
kind: "end"
|
|
961
|
+
});
|
|
962
|
+
onTouchEnd?.(e);
|
|
963
|
+
},
|
|
964
|
+
[onTouchEnd]
|
|
965
|
+
);
|
|
966
|
+
const onCancel = useCallback(
|
|
967
|
+
(e) => {
|
|
968
|
+
const nativeEvent = e?.nativeEvent;
|
|
969
|
+
if (!nativeEvent) return;
|
|
970
|
+
const { locationX = 0, locationY = 0, pageX, pageY } = nativeEvent;
|
|
971
|
+
emit({
|
|
972
|
+
type: "touch",
|
|
973
|
+
ts: Date.now(),
|
|
974
|
+
x: locationX,
|
|
975
|
+
y: locationY,
|
|
976
|
+
screenX: pageX,
|
|
977
|
+
screenY: pageY,
|
|
978
|
+
kind: "end"
|
|
979
|
+
});
|
|
980
|
+
onTouchCancel?.(e);
|
|
981
|
+
},
|
|
982
|
+
[onTouchCancel]
|
|
983
|
+
);
|
|
984
|
+
if (!enabled) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
985
|
+
return /* @__PURE__ */ jsx(
|
|
986
|
+
View,
|
|
987
|
+
{
|
|
988
|
+
...rest,
|
|
989
|
+
style: [{ flex: 1 }, style],
|
|
990
|
+
onTouchStart: onStart,
|
|
991
|
+
onTouchEnd: onEnd,
|
|
992
|
+
onTouchCancel: onCancel,
|
|
993
|
+
children
|
|
994
|
+
}
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
var sharedLastTrackedRoute = void 0;
|
|
998
|
+
function _resetLastTrackedRoute() {
|
|
999
|
+
sharedLastTrackedRoute = void 0;
|
|
1000
|
+
}
|
|
1001
|
+
function useClintplayNavigationTracking(routeNameOrPath, options) {
|
|
1002
|
+
const optionsRef = useRef(options);
|
|
1003
|
+
optionsRef.current = options;
|
|
1004
|
+
const trackScreen2 = useCallback((to, from) => {
|
|
1005
|
+
const cleanTo = sanitizeRoute(to);
|
|
1006
|
+
if (!cleanTo || cleanTo === sharedLastTrackedRoute) return;
|
|
1007
|
+
const cleanFrom = sanitizeRoute(from);
|
|
1008
|
+
const initialFrom = sanitizeRoute(optionsRef.current?.from);
|
|
1009
|
+
const previousRoute = cleanFrom ?? sharedLastTrackedRoute ?? (initialFrom && initialFrom !== cleanTo ? initialFrom : void 0);
|
|
1010
|
+
sharedLastTrackedRoute = cleanTo;
|
|
1011
|
+
emit({
|
|
1012
|
+
type: "navigation",
|
|
1013
|
+
ts: Date.now(),
|
|
1014
|
+
to: cleanTo,
|
|
1015
|
+
...previousRoute ? { from: previousRoute } : {}
|
|
1016
|
+
});
|
|
1017
|
+
try {
|
|
1018
|
+
optionsRef.current?.onRouteChanged?.(cleanTo, previousRoute);
|
|
1019
|
+
} catch {
|
|
1020
|
+
}
|
|
1021
|
+
}, []);
|
|
1022
|
+
useEffect(() => {
|
|
1023
|
+
const clean = sanitizeRoute(routeNameOrPath);
|
|
1024
|
+
if (clean && clean !== sharedLastTrackedRoute) {
|
|
1025
|
+
trackScreen2(clean);
|
|
1026
|
+
}
|
|
1027
|
+
}, [routeNameOrPath, trackScreen2]);
|
|
1028
|
+
const handleStateChange = useCallback(
|
|
1029
|
+
(state2) => {
|
|
1030
|
+
const nextRoute = getActiveRouteName(state2);
|
|
1031
|
+
if (nextRoute) {
|
|
1032
|
+
trackScreen2(nextRoute);
|
|
1033
|
+
}
|
|
1034
|
+
},
|
|
1035
|
+
[trackScreen2]
|
|
1036
|
+
);
|
|
1037
|
+
const result = Object.assign(handleStateChange, {
|
|
1038
|
+
onStateChange: handleStateChange,
|
|
1039
|
+
trackScreen: trackScreen2
|
|
1040
|
+
});
|
|
1041
|
+
return result;
|
|
1042
|
+
}
|
|
1043
|
+
function sanitizeRoute(route) {
|
|
1044
|
+
if (!route || typeof route !== "string") return void 0;
|
|
1045
|
+
const clean = route.split(/[?#]/)[0];
|
|
1046
|
+
return clean || void 0;
|
|
1047
|
+
}
|
|
1048
|
+
function getActiveRouteName(state2, depth = 0) {
|
|
1049
|
+
if (depth > 20) return void 0;
|
|
1050
|
+
if (!state2 || typeof state2 !== "object") {
|
|
1051
|
+
return void 0;
|
|
1052
|
+
}
|
|
1053
|
+
const s = state2;
|
|
1054
|
+
if (!Array.isArray(s.routes) || s.routes.length === 0) {
|
|
1055
|
+
if (typeof s.name === "string") {
|
|
1056
|
+
return s.name;
|
|
1057
|
+
}
|
|
1058
|
+
return void 0;
|
|
1059
|
+
}
|
|
1060
|
+
const index = typeof s.index === "number" && s.index >= 0 && s.index < s.routes.length ? s.index : s.routes.length - 1;
|
|
1061
|
+
const currentRoute = s.routes[index];
|
|
1062
|
+
if (!currentRoute) return void 0;
|
|
1063
|
+
if (currentRoute.state) {
|
|
1064
|
+
if (depth >= 20) return void 0;
|
|
1065
|
+
const childRoute = getActiveRouteName(currentRoute.state, depth + 1);
|
|
1066
|
+
if (childRoute !== void 0) {
|
|
1067
|
+
return childRoute;
|
|
1068
|
+
}
|
|
1069
|
+
const childState = currentRoute.state;
|
|
1070
|
+
if (childState && typeof childState === "object" && (!Array.isArray(childState.routes) || childState.routes.length === 0)) {
|
|
1071
|
+
return currentRoute.name;
|
|
1072
|
+
}
|
|
1073
|
+
return void 0;
|
|
1074
|
+
}
|
|
1075
|
+
return currentRoute.name;
|
|
1076
|
+
}
|
|
1077
|
+
function createClintplayNavigationStateListener(options) {
|
|
1078
|
+
const opts = typeof options === "function" ? { onRouteChanged: options } : options ?? {};
|
|
1079
|
+
let currentRoute = void 0;
|
|
1080
|
+
const initialRoute = sanitizeRoute(opts.initialRouteName);
|
|
1081
|
+
let isFirst = true;
|
|
1082
|
+
return function onStateChange(state2) {
|
|
1083
|
+
const rawRoute = getActiveRouteName(state2);
|
|
1084
|
+
const nextRoute = sanitizeRoute(rawRoute);
|
|
1085
|
+
if (!nextRoute) return;
|
|
1086
|
+
if (isFirst) {
|
|
1087
|
+
isFirst = false;
|
|
1088
|
+
const from = initialRoute && initialRoute !== nextRoute ? initialRoute : void 0;
|
|
1089
|
+
currentRoute = nextRoute;
|
|
1090
|
+
emit({
|
|
1091
|
+
type: "navigation",
|
|
1092
|
+
ts: Date.now(),
|
|
1093
|
+
to: nextRoute,
|
|
1094
|
+
...from ? { from } : {}
|
|
1095
|
+
});
|
|
1096
|
+
try {
|
|
1097
|
+
opts.onRouteChanged?.(nextRoute, from);
|
|
1098
|
+
} catch {
|
|
1099
|
+
}
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (nextRoute === currentRoute) return;
|
|
1103
|
+
const previousRoute = currentRoute;
|
|
1104
|
+
currentRoute = nextRoute;
|
|
1105
|
+
emit({
|
|
1106
|
+
type: "navigation",
|
|
1107
|
+
ts: Date.now(),
|
|
1108
|
+
to: nextRoute,
|
|
1109
|
+
...previousRoute ? { from: previousRoute } : {}
|
|
1110
|
+
});
|
|
1111
|
+
try {
|
|
1112
|
+
opts.onRouteChanged?.(nextRoute, previousRoute);
|
|
1113
|
+
} catch {
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
function ClintplayNavigationTracker({
|
|
1118
|
+
children,
|
|
1119
|
+
pathname,
|
|
1120
|
+
onStateChange,
|
|
1121
|
+
initialRouteName,
|
|
1122
|
+
onRouteChanged
|
|
1123
|
+
}) {
|
|
1124
|
+
const tracking = useClintplayNavigationTracking(pathname, {
|
|
1125
|
+
from: initialRouteName,
|
|
1126
|
+
onRouteChanged
|
|
1127
|
+
});
|
|
1128
|
+
const handleStateChange = useCallback(
|
|
1129
|
+
(state2) => {
|
|
1130
|
+
tracking.onStateChange(state2);
|
|
1131
|
+
try {
|
|
1132
|
+
onStateChange?.(state2);
|
|
1133
|
+
} catch {
|
|
1134
|
+
}
|
|
1135
|
+
},
|
|
1136
|
+
[tracking, onStateChange]
|
|
1137
|
+
);
|
|
1138
|
+
const childOnStateChangeRef = useRef(void 0);
|
|
1139
|
+
const mergedOnStateChange = useCallback(
|
|
1140
|
+
(state2) => {
|
|
1141
|
+
handleStateChange(state2);
|
|
1142
|
+
try {
|
|
1143
|
+
childOnStateChangeRef.current?.(state2);
|
|
1144
|
+
} catch {
|
|
1145
|
+
}
|
|
1146
|
+
},
|
|
1147
|
+
[handleStateChange]
|
|
1148
|
+
);
|
|
1149
|
+
if (typeof children === "function") {
|
|
1150
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children({ onStateChange: handleStateChange }) });
|
|
1151
|
+
}
|
|
1152
|
+
if (React5.isValidElement(children)) {
|
|
1153
|
+
childOnStateChangeRef.current = children.props?.onStateChange;
|
|
1154
|
+
return React5.cloneElement(children, {
|
|
1155
|
+
onStateChange: mergedOnStateChange
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
if (Array.isArray(children) || children && React5.Children.count(children) > 1) {
|
|
1159
|
+
let targetIndex = -1;
|
|
1160
|
+
let firstValidIndex = -1;
|
|
1161
|
+
let scanIndex = 0;
|
|
1162
|
+
React5.Children.forEach(children, (child) => {
|
|
1163
|
+
if (React5.isValidElement(child)) {
|
|
1164
|
+
if (firstValidIndex === -1) {
|
|
1165
|
+
firstValidIndex = scanIndex;
|
|
1166
|
+
}
|
|
1167
|
+
if (typeof child.props?.onStateChange === "function" && targetIndex === -1) {
|
|
1168
|
+
targetIndex = scanIndex;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
scanIndex++;
|
|
1172
|
+
});
|
|
1173
|
+
const chosenIndex = targetIndex !== -1 ? targetIndex : firstValidIndex;
|
|
1174
|
+
let mapIndex = 0;
|
|
1175
|
+
const clonedChildren = React5.Children.map(children, (child) => {
|
|
1176
|
+
const currentIdx = mapIndex++;
|
|
1177
|
+
if (!React5.isValidElement(child)) return child;
|
|
1178
|
+
if (currentIdx === chosenIndex) {
|
|
1179
|
+
childOnStateChangeRef.current = child.props?.onStateChange;
|
|
1180
|
+
return React5.cloneElement(child, {
|
|
1181
|
+
onStateChange: mergedOnStateChange
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
return child;
|
|
1185
|
+
});
|
|
1186
|
+
return /* @__PURE__ */ jsx(Fragment, { children: clonedChildren });
|
|
1187
|
+
}
|
|
1188
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children ?? null });
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// src/index.ts
|
|
1192
|
+
var SDK_VERSION = version;
|
|
1193
|
+
var state = null;
|
|
1194
|
+
function deviceInfo() {
|
|
1195
|
+
const os = Platform.OS;
|
|
1196
|
+
const platform = os === "ios" ? "ios" : os === "android" ? "android" : "web";
|
|
1197
|
+
return { platform, osVersion: String(Platform.Version ?? "") };
|
|
1198
|
+
}
|
|
1199
|
+
function debugLog(cfg, ...args) {
|
|
1200
|
+
if (!cfg.debug) return;
|
|
1201
|
+
if (typeof console !== "undefined" && console.log) {
|
|
1202
|
+
console.log("[clintplay-rn]", ...args);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
function formatEventForBatch(event, defaultSessionId) {
|
|
1206
|
+
const ev = event;
|
|
1207
|
+
const timestamp = typeof ev.timestamp === "number" ? ev.timestamp : typeof ev.ts === "number" ? ev.ts : Date.now();
|
|
1208
|
+
const sessionId = ev.sessionId || defaultSessionId;
|
|
1209
|
+
const type = ev.type;
|
|
1210
|
+
if (ev.data !== void 0) {
|
|
1211
|
+
return {
|
|
1212
|
+
type,
|
|
1213
|
+
timestamp,
|
|
1214
|
+
sessionId,
|
|
1215
|
+
data: ev.data
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
const { type: _t, ts: _ts, timestamp: _tms, sessionId: _s, ...rest } = ev;
|
|
1219
|
+
return {
|
|
1220
|
+
type,
|
|
1221
|
+
timestamp,
|
|
1222
|
+
sessionId,
|
|
1223
|
+
data: rest
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
async function flushOnce(s) {
|
|
1227
|
+
try {
|
|
1228
|
+
if (s.buffer.size() === 0) return;
|
|
1229
|
+
const currentSessionId = s.session.current().sessionId;
|
|
1230
|
+
const rawEvents = s.buffer.drain();
|
|
1231
|
+
const events = rawEvents.map(
|
|
1232
|
+
(e) => formatEventForBatch(e, currentSessionId)
|
|
1233
|
+
);
|
|
1234
|
+
const batch = {
|
|
1235
|
+
projectId: s.config.projectId,
|
|
1236
|
+
sessionId: currentSessionId,
|
|
1237
|
+
sdkVersion: SDK_VERSION,
|
|
1238
|
+
events,
|
|
1239
|
+
sentAt: Date.now(),
|
|
1240
|
+
...s.config.anonId ? { anonId: s.config.anonId } : {}
|
|
1241
|
+
};
|
|
1242
|
+
const ok = await s.transport.send(batch);
|
|
1243
|
+
if (!ok && s.offlineQueue) {
|
|
1244
|
+
await s.offlineQueue.persist(batch);
|
|
1245
|
+
}
|
|
1246
|
+
debugLog(s.config, ok ? "flushed" : "flush failed", events.length);
|
|
1247
|
+
} catch (err) {
|
|
1248
|
+
debugLog(s.config, "flush failed with error", String(err));
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
function init(rawConfig) {
|
|
1252
|
+
if (state?.active) return;
|
|
1253
|
+
if (!rawConfig.apiUrl) return;
|
|
1254
|
+
if (rawConfig.enabled === false) return;
|
|
1255
|
+
const sampleRate = rawConfig.sampleRate ?? 1;
|
|
1256
|
+
if (sampleRate < 1 && Math.random() > sampleRate) return;
|
|
1257
|
+
const config = {
|
|
1258
|
+
apiUrl: rawConfig.apiUrl,
|
|
1259
|
+
apiKey: rawConfig.apiKey,
|
|
1260
|
+
projectId: rawConfig.projectId ?? rawConfig.apiKey ?? "",
|
|
1261
|
+
anonId: rawConfig.anonId,
|
|
1262
|
+
flushIntervalMs: rawConfig.flushIntervalMs ?? 5e3,
|
|
1263
|
+
flushSize: rawConfig.flushSize ?? 50,
|
|
1264
|
+
maxBufferSize: rawConfig.maxBufferSize ?? 1e3,
|
|
1265
|
+
debug: rawConfig.debug ?? false,
|
|
1266
|
+
captureWireframe: rawConfig.captureWireframe ?? false
|
|
1267
|
+
};
|
|
1268
|
+
const buffer = new EventBuffer(config.maxBufferSize);
|
|
1269
|
+
const session = new SessionManager();
|
|
1270
|
+
const transport = new Transport({
|
|
1271
|
+
apiUrl: config.apiUrl,
|
|
1272
|
+
apiKey: config.apiKey,
|
|
1273
|
+
projectId: config.projectId
|
|
1274
|
+
});
|
|
1275
|
+
const excluded = new Set(rawConfig.excludeEventTypes ?? []);
|
|
1276
|
+
const local = {
|
|
1277
|
+
config,
|
|
1278
|
+
buffer,
|
|
1279
|
+
session,
|
|
1280
|
+
transport,
|
|
1281
|
+
recorder: { start: () => {
|
|
1282
|
+
}, stop: () => {
|
|
1283
|
+
}, takeWireframeSnapshot: () => {
|
|
1284
|
+
} },
|
|
1285
|
+
networkInterceptor: null,
|
|
1286
|
+
consoleInterceptor: null,
|
|
1287
|
+
offlineQueue: rawConfig.asyncStorage ? new OfflineQueue({
|
|
1288
|
+
storage: rawConfig.asyncStorage,
|
|
1289
|
+
send: (b) => transport.sendDetailed(b),
|
|
1290
|
+
debug: (msg) => debugLog(config, msg)
|
|
1291
|
+
}) : null,
|
|
1292
|
+
flushTimer: null,
|
|
1293
|
+
excluded,
|
|
1294
|
+
active: true
|
|
1295
|
+
};
|
|
1296
|
+
state = local;
|
|
1297
|
+
const onEvent = (event) => {
|
|
1298
|
+
if (excluded.has(event.type)) return;
|
|
1299
|
+
buffer.push(event);
|
|
1300
|
+
session.touch();
|
|
1301
|
+
if (buffer.size() >= config.flushSize) {
|
|
1302
|
+
void flushOnce(local);
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
const recorder = createRecorder({
|
|
1306
|
+
onEvent,
|
|
1307
|
+
now: Date.now,
|
|
1308
|
+
installErrorHandler: true,
|
|
1309
|
+
captureWireframe: rawConfig.captureWireframe,
|
|
1310
|
+
wireframeIntervalMs: rawConfig.wireframeIntervalMs,
|
|
1311
|
+
captureViewHierarchy
|
|
1312
|
+
});
|
|
1313
|
+
local.recorder = recorder;
|
|
1314
|
+
if (rawConfig.captureNetwork !== false) {
|
|
1315
|
+
local.networkInterceptor = new NetworkInterceptor({
|
|
1316
|
+
onEvent,
|
|
1317
|
+
now: Date.now,
|
|
1318
|
+
sanitizeUrl: rawConfig.sanitizeUrl,
|
|
1319
|
+
apiUrl: config.apiUrl
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
if (rawConfig.captureConsole === true) {
|
|
1323
|
+
local.consoleInterceptor = new ConsoleInterceptor({
|
|
1324
|
+
onEvent,
|
|
1325
|
+
now: Date.now,
|
|
1326
|
+
captureConsoleLog: rawConfig.captureConsoleLog
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
buffer.push({
|
|
1330
|
+
type: "meta",
|
|
1331
|
+
ts: Date.now(),
|
|
1332
|
+
sessionId: session.current().sessionId,
|
|
1333
|
+
sdkVersion: SDK_VERSION,
|
|
1334
|
+
device: deviceInfo()
|
|
1335
|
+
});
|
|
1336
|
+
recorder.start();
|
|
1337
|
+
local.networkInterceptor?.start();
|
|
1338
|
+
local.consoleInterceptor?.start();
|
|
1339
|
+
_setSink(onEvent);
|
|
1340
|
+
if (local.offlineQueue) {
|
|
1341
|
+
const q = local.offlineQueue;
|
|
1342
|
+
void q.hydrate().then(() => q.drain()).catch(
|
|
1343
|
+
(err) => debugLog(config, "offline queue init failed", String(err))
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
local.flushTimer = setInterval(async () => {
|
|
1347
|
+
if (local.session.isStale()) {
|
|
1348
|
+
await flushOnce(local);
|
|
1349
|
+
if (local.session.rotateIfStale()) {
|
|
1350
|
+
local.buffer.push({
|
|
1351
|
+
type: "meta",
|
|
1352
|
+
ts: Date.now(),
|
|
1353
|
+
sessionId: local.session.current().sessionId,
|
|
1354
|
+
sdkVersion: SDK_VERSION,
|
|
1355
|
+
device: deviceInfo()
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
void flushOnce(local);
|
|
1360
|
+
}, config.flushIntervalMs);
|
|
1361
|
+
debugLog(config, "init", { sessionId: session.current().sessionId });
|
|
1362
|
+
}
|
|
1363
|
+
function identify(userId, traits) {
|
|
1364
|
+
if (!state?.active) return;
|
|
1365
|
+
const prevUser = state.session.current().userId;
|
|
1366
|
+
const isUserChange = prevUser !== null && (userId === null || prevUser !== userId);
|
|
1367
|
+
if (isUserChange) {
|
|
1368
|
+
void flushOnce(state);
|
|
1369
|
+
}
|
|
1370
|
+
const rotated = state.session.setUser(userId);
|
|
1371
|
+
if (rotated) {
|
|
1372
|
+
state.buffer.push({
|
|
1373
|
+
type: "meta",
|
|
1374
|
+
ts: Date.now(),
|
|
1375
|
+
sessionId: state.session.current().sessionId,
|
|
1376
|
+
sdkVersion: SDK_VERSION,
|
|
1377
|
+
device: deviceInfo()
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
state.buffer.push({
|
|
1381
|
+
type: "identify",
|
|
1382
|
+
ts: Date.now(),
|
|
1383
|
+
userId,
|
|
1384
|
+
traits
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
function reset(options) {
|
|
1388
|
+
if (!state?.active) return;
|
|
1389
|
+
void flushOnce(state);
|
|
1390
|
+
state.session.reset();
|
|
1391
|
+
if (options?.clearOfflineQueue && state.offlineQueue) {
|
|
1392
|
+
void state.offlineQueue.clear();
|
|
1393
|
+
}
|
|
1394
|
+
state.buffer.push({
|
|
1395
|
+
type: "meta",
|
|
1396
|
+
ts: Date.now(),
|
|
1397
|
+
sessionId: state.session.current().sessionId,
|
|
1398
|
+
sdkVersion: SDK_VERSION,
|
|
1399
|
+
device: deviceInfo()
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
function trackScreen(path, from) {
|
|
1403
|
+
if (!state?.active) return;
|
|
1404
|
+
state.buffer.push({
|
|
1405
|
+
type: "navigation",
|
|
1406
|
+
ts: Date.now(),
|
|
1407
|
+
to: path,
|
|
1408
|
+
...from ? { from } : {}
|
|
1409
|
+
});
|
|
1410
|
+
state.session.touch();
|
|
1411
|
+
if (state.config.captureWireframe) {
|
|
1412
|
+
state.recorder.takeWireframeSnapshot();
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
function reportError(err) {
|
|
1416
|
+
if (!state?.active) return;
|
|
1417
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1418
|
+
state.buffer.push({
|
|
1419
|
+
type: "error",
|
|
1420
|
+
ts: Date.now(),
|
|
1421
|
+
message: error.message,
|
|
1422
|
+
stack: error.stack,
|
|
1423
|
+
name: error.name,
|
|
1424
|
+
fatal: false
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
async function flush() {
|
|
1428
|
+
if (!state?.active) return;
|
|
1429
|
+
await flushOnce(state);
|
|
1430
|
+
}
|
|
1431
|
+
async function stop() {
|
|
1432
|
+
if (!state?.active) return;
|
|
1433
|
+
const s = state;
|
|
1434
|
+
s.active = false;
|
|
1435
|
+
state = null;
|
|
1436
|
+
_setSink(null);
|
|
1437
|
+
if (s.flushTimer) clearInterval(s.flushTimer);
|
|
1438
|
+
s.consoleInterceptor?.stop();
|
|
1439
|
+
s.networkInterceptor?.stop();
|
|
1440
|
+
s.recorder.stop();
|
|
1441
|
+
await flushOnce(s);
|
|
1442
|
+
}
|
|
1443
|
+
function current() {
|
|
1444
|
+
if (!state?.active) return null;
|
|
1445
|
+
const s = state.session.current();
|
|
1446
|
+
return { sessionId: s.sessionId, userId: s.userId };
|
|
1447
|
+
}
|
|
1448
|
+
var Clintplay = {
|
|
1449
|
+
init,
|
|
1450
|
+
identify,
|
|
1451
|
+
reset,
|
|
1452
|
+
trackScreen,
|
|
1453
|
+
reportError,
|
|
1454
|
+
flush,
|
|
1455
|
+
stop,
|
|
1456
|
+
current
|
|
1457
|
+
};
|
|
1458
|
+
|
|
1459
|
+
export { CLINTPLAY_MASK_ID, Clintplay, ClintplayCaptureProvider, ClintplayMaskImage, ClintplayMaskTextInput, ClintplayMaskView, ClintplayNavigationTracker, SDK_VERSION, _resetLastTrackedRoute, createClintplayNavigationStateListener, formatEventForBatch, getActiveRouteName, sanitizeRoute, useClintplayNavigationTracking };
|
|
1460
|
+
//# sourceMappingURL=index.mjs.map
|
|
1461
|
+
//# sourceMappingURL=index.mjs.map
|