@repliqo/sdk-react-native 0.3.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INTEGRATION_GUIDE.md +1384 -1312
- package/android/src/main/java/com/repliqo/screencapture/MultiWindowCapture.java +242 -230
- package/android/src/main/java/com/repliqo/screencapture/ScreenCaptureModule.java +94 -170
- package/dist/components/RepliqoFlatList.d.ts +5 -0
- package/dist/components/RepliqoFlatList.js +39 -0
- package/dist/components/RepliqoScrollView.d.ts +3 -0
- package/dist/components/RepliqoScrollView.js +16 -265
- package/dist/components/useRepliqoScrollTracking.d.ts +33 -0
- package/dist/components/useRepliqoScrollTracking.js +304 -0
- package/dist/core/client.d.ts +52 -5
- package/dist/core/client.js +310 -48
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.js +5 -1
- package/dist/core/storage.d.ts +29 -0
- package/dist/core/storage.js +33 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/snapshot/NativeScreenCapture.d.ts +6 -44
- package/dist/snapshot/NativeScreenCapture.js +6 -64
- package/dist/snapshot/capture.d.ts +7 -0
- package/dist/snapshot/capture.js +17 -5
- package/dist/trackers/error.tracker.d.ts +25 -0
- package/dist/trackers/error.tracker.js +114 -37
- package/dist/trackers/navigation.tracker.js +11 -0
- package/dist/transport/api.client.d.ts +34 -1
- package/dist/transport/api.client.js +102 -19
- package/dist/transport/batch-queue.d.ts +53 -1
- package/dist/transport/batch-queue.js +126 -19
- package/dist/types/events.d.ts +12 -0
- package/package.json +68 -64
- package/src/components/RepliqoFlatList.tsx +64 -0
- package/src/components/RepliqoScrollView.tsx +53 -302
- package/src/components/useRepliqoScrollTracking.ts +366 -0
- package/src/core/client.ts +953 -672
- package/src/core/config.ts +38 -30
- package/src/core/storage.ts +51 -0
- package/src/index.ts +54 -52
- package/src/snapshot/NativeScreenCapture.ts +62 -157
- package/src/snapshot/capture.ts +154 -142
- package/src/trackers/error.tracker.ts +211 -136
- package/src/trackers/navigation.tracker.ts +107 -98
- package/src/transport/api.client.ts +430 -327
- package/src/transport/batch-queue.ts +212 -95
- package/src/types/events.ts +72 -60
- package/android/src/main/java/com/repliqo/screencapture/FullContentCapture.java +0 -273
- package/android/src/main/java/com/repliqo/screencapture/PixelCopyScan.java +0 -279
- package/android/src/main/java/com/repliqo/screencapture/ScrollScanCapture.java +0 -248
- package/src/snapshot/ScreenCaptureManager.ts +0 -156
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isNativeScreenCaptureAvailable = isNativeScreenCaptureAvailable;
|
|
4
4
|
exports.captureNativeFrame = captureNativeFrame;
|
|
5
|
-
exports.captureFullContent = captureFullContent;
|
|
6
|
-
exports.scanFullContent = scanFullContent;
|
|
7
5
|
const react_native_1 = require("react-native");
|
|
8
6
|
const NativeModule = react_native_1.Platform.OS === 'android'
|
|
9
7
|
? react_native_1.NativeModules.ScreenCaptureModule ?? null
|
|
@@ -18,6 +16,12 @@ function isNativeScreenCaptureAvailable() {
|
|
|
18
16
|
*
|
|
19
17
|
* No permissions required. No dialog. No foreground service.
|
|
20
18
|
*
|
|
19
|
+
* This is the ONLY native capture path. Full-content capture of
|
|
20
|
+
* off-screen ScrollView content is NOT possible natively (React Native
|
|
21
|
+
* doesn't render off-screen content to the GPU/view tree) — full-content
|
|
22
|
+
* heatmap backgrounds are instead built collaboratively from viewport
|
|
23
|
+
* tiles captured as the user naturally scrolls (see RepliqoScrollView).
|
|
24
|
+
*
|
|
21
25
|
* @returns NativeFrameResult or null if capture failed / not available
|
|
22
26
|
*/
|
|
23
27
|
async function captureNativeFrame() {
|
|
@@ -38,65 +42,3 @@ async function captureNativeFrame() {
|
|
|
38
42
|
return null;
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Capture the FULL content of the primary ScrollView on screen,
|
|
43
|
-
* including content scrolled off-screen. Used for heatmap backgrounds.
|
|
44
|
-
*
|
|
45
|
-
* Falls back to a viewport capture if no ScrollView is found.
|
|
46
|
-
* Max height: 5000px. JPEG quality: 60%. Width: ~500px.
|
|
47
|
-
*
|
|
48
|
-
* Known limitation: FlatList/SectionList use RecyclerView which only
|
|
49
|
-
* renders visible items — full-content capture falls back to viewport.
|
|
50
|
-
*
|
|
51
|
-
* @returns FullContentResult with isFullContent flag, or null
|
|
52
|
-
*/
|
|
53
|
-
async function captureFullContent() {
|
|
54
|
-
if (!NativeModule)
|
|
55
|
-
return null;
|
|
56
|
-
try {
|
|
57
|
-
const result = await NativeModule.captureFullContent();
|
|
58
|
-
if (!result || !result.image)
|
|
59
|
-
return null;
|
|
60
|
-
return {
|
|
61
|
-
image: result.image,
|
|
62
|
-
width: result.width,
|
|
63
|
-
height: result.height,
|
|
64
|
-
format: 'jpeg',
|
|
65
|
-
isFullContent: !!result.isFullContent,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Perform a fast programmatic scroll-scan of the main ScrollView.
|
|
74
|
-
* Captures the entire content as viewport tiles in ~300-500ms.
|
|
75
|
-
*
|
|
76
|
-
* The scan is invisible to the user — scroll position is saved and
|
|
77
|
-
* restored. Returns an array of tiles covering the full content.
|
|
78
|
-
*
|
|
79
|
-
* @returns Array of tiles, or null if no ScrollView or not available
|
|
80
|
-
*/
|
|
81
|
-
async function scanFullContent() {
|
|
82
|
-
if (!NativeModule)
|
|
83
|
-
return null;
|
|
84
|
-
try {
|
|
85
|
-
const tiles = await NativeModule.scanFullContent();
|
|
86
|
-
if (!tiles || !Array.isArray(tiles) || tiles.length === 0)
|
|
87
|
-
return null;
|
|
88
|
-
return tiles
|
|
89
|
-
.filter((t) => t && t.image)
|
|
90
|
-
.map((t) => ({
|
|
91
|
-
image: t.image,
|
|
92
|
-
width: t.width,
|
|
93
|
-
height: t.height,
|
|
94
|
-
scrollOffsetY: t.scrollOffsetY,
|
|
95
|
-
viewportHeight: t.viewportHeight,
|
|
96
|
-
contentHeight: t.contentHeight,
|
|
97
|
-
}));
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
@@ -22,6 +22,13 @@ export declare class SnapshotCapture {
|
|
|
22
22
|
private log;
|
|
23
23
|
private warn;
|
|
24
24
|
constructor(config: SnapshotCaptureConfig, onSnapshot: (snapshot: SnapshotPayload) => void, provider?: ScreenshotProvider, log?: LogFn, warn?: LogFn);
|
|
25
|
+
/**
|
|
26
|
+
* Generation counter. Incremented on every start()/stop() so that timer
|
|
27
|
+
* chains from a PREVIOUS generation cannot reschedule themselves. Without
|
|
28
|
+
* it, a fast stop()→start() while a tick's capture is still in flight
|
|
29
|
+
* leaves two parallel timer chains alive (double capture rate).
|
|
30
|
+
*/
|
|
31
|
+
private epoch;
|
|
25
32
|
start(): void;
|
|
26
33
|
stop(): void;
|
|
27
34
|
captureNow(): Promise<void>;
|
package/dist/snapshot/capture.js
CHANGED
|
@@ -14,6 +14,13 @@ class SnapshotCapture {
|
|
|
14
14
|
this.currentScreen = 'unknown';
|
|
15
15
|
this.capturing = false;
|
|
16
16
|
this.running = false;
|
|
17
|
+
/**
|
|
18
|
+
* Generation counter. Incremented on every start()/stop() so that timer
|
|
19
|
+
* chains from a PREVIOUS generation cannot reschedule themselves. Without
|
|
20
|
+
* it, a fast stop()→start() while a tick's capture is still in flight
|
|
21
|
+
* leaves two parallel timer chains alive (double capture rate).
|
|
22
|
+
*/
|
|
23
|
+
this.epoch = 0;
|
|
17
24
|
this.config = config;
|
|
18
25
|
this.onSnapshot = onSnapshot;
|
|
19
26
|
this.provider = provider;
|
|
@@ -24,11 +31,13 @@ class SnapshotCapture {
|
|
|
24
31
|
if (this.running)
|
|
25
32
|
return;
|
|
26
33
|
this.running = true;
|
|
34
|
+
this.epoch++;
|
|
27
35
|
this.log('Snapshot capture started (interval:', this.config.captureInterval, 'ms)');
|
|
28
|
-
this.scheduleNext();
|
|
36
|
+
this.scheduleNext(this.epoch);
|
|
29
37
|
}
|
|
30
38
|
stop() {
|
|
31
39
|
this.running = false;
|
|
40
|
+
this.epoch++;
|
|
32
41
|
if (this.timeoutId !== null) {
|
|
33
42
|
clearTimeout(this.timeoutId);
|
|
34
43
|
this.timeoutId = null;
|
|
@@ -38,13 +47,16 @@ class SnapshotCapture {
|
|
|
38
47
|
async captureNow() {
|
|
39
48
|
await this.tick();
|
|
40
49
|
}
|
|
41
|
-
scheduleNext() {
|
|
42
|
-
if (!this.running)
|
|
50
|
+
scheduleNext(epoch) {
|
|
51
|
+
if (!this.running || epoch !== this.epoch)
|
|
43
52
|
return;
|
|
44
53
|
this.timeoutId = setTimeout(() => {
|
|
54
|
+
// Orphan timer from a previous generation — do nothing.
|
|
55
|
+
if (epoch !== this.epoch)
|
|
56
|
+
return;
|
|
45
57
|
Promise.resolve(this.tick()).finally(() => {
|
|
46
|
-
if (this.running) {
|
|
47
|
-
this.scheduleNext();
|
|
58
|
+
if (this.running && epoch === this.epoch) {
|
|
59
|
+
this.scheduleNext(epoch);
|
|
48
60
|
}
|
|
49
61
|
});
|
|
50
62
|
}, this.config.captureInterval);
|
|
@@ -5,6 +5,7 @@ export declare class ErrorTracker {
|
|
|
5
5
|
private sessionId;
|
|
6
6
|
private currentScreen;
|
|
7
7
|
private originalHandler;
|
|
8
|
+
private wrapperHandler;
|
|
8
9
|
private isActive;
|
|
9
10
|
constructor(onCrash: CrashCallback);
|
|
10
11
|
setSessionId(id: string | null): void;
|
|
@@ -16,7 +17,31 @@ export declare class ErrorTracker {
|
|
|
16
17
|
*/
|
|
17
18
|
reportError(error: Error, metadata?: Record<string, any>): void;
|
|
18
19
|
private setupGlobalErrorHandler;
|
|
20
|
+
/**
|
|
21
|
+
* Handle an unhandled promise rejection. Guarded on isActive because the
|
|
22
|
+
* underlying trackers can't always be unregistered.
|
|
23
|
+
*/
|
|
24
|
+
private handleUnhandledRejection;
|
|
25
|
+
/**
|
|
26
|
+
* Register with the mechanisms React Native ACTUALLY uses to surface
|
|
27
|
+
* unhandled promise rejections:
|
|
28
|
+
*
|
|
29
|
+
* 1. Hermes' native promise rejection tracker (Hermes engine).
|
|
30
|
+
* 2. The `promise` polyfill's rejection-tracking module (JSC and older
|
|
31
|
+
* RN versions).
|
|
32
|
+
*
|
|
33
|
+
* A bare global callback (the old approach) is never invoked by RN — it
|
|
34
|
+
* silently captured nothing.
|
|
35
|
+
*
|
|
36
|
+
* KNOWN LIMITATION: these trackers hold a single handler — registering
|
|
37
|
+
* REPLACES whatever was installed before (RN's dev LogBox warning, or
|
|
38
|
+
* another SDK like Sentry if it initialized first), and there is no
|
|
39
|
+
* unregister API, so after stop() an inert handler remains installed.
|
|
40
|
+
* If you run multiple error-tracking SDKs, initialize Repliqo FIRST so
|
|
41
|
+
* the other SDK's handler wins, or disable enableCrashTracking.
|
|
42
|
+
*/
|
|
19
43
|
private setupUnhandledRejectionHandler;
|
|
44
|
+
private teardownUnhandledRejectionHandler;
|
|
20
45
|
private restoreGlobalErrorHandler;
|
|
21
46
|
}
|
|
22
47
|
export {};
|
|
@@ -6,7 +6,32 @@ class ErrorTracker {
|
|
|
6
6
|
this.sessionId = null;
|
|
7
7
|
this.currentScreen = null;
|
|
8
8
|
this.originalHandler = null;
|
|
9
|
+
this.wrapperHandler = null;
|
|
9
10
|
this.isActive = false;
|
|
11
|
+
/**
|
|
12
|
+
* Handle an unhandled promise rejection. Guarded on isActive because the
|
|
13
|
+
* underlying trackers can't always be unregistered.
|
|
14
|
+
*/
|
|
15
|
+
this.handleUnhandledRejection = (_id, error) => {
|
|
16
|
+
if (!this.isActive)
|
|
17
|
+
return;
|
|
18
|
+
try {
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
21
|
+
const crash = {
|
|
22
|
+
sessionId: this.sessionId || undefined,
|
|
23
|
+
type: 'unhandled_rejection',
|
|
24
|
+
message,
|
|
25
|
+
stackTrace: stack || undefined,
|
|
26
|
+
screenName: this.currentScreen || undefined,
|
|
27
|
+
timestamp: new Date().toISOString(),
|
|
28
|
+
};
|
|
29
|
+
this.onCrash(crash);
|
|
30
|
+
}
|
|
31
|
+
catch (_e) {
|
|
32
|
+
// Never crash the app due to error reporting
|
|
33
|
+
}
|
|
34
|
+
};
|
|
10
35
|
this.onCrash = onCrash;
|
|
11
36
|
}
|
|
12
37
|
setSessionId(id) {
|
|
@@ -25,8 +50,11 @@ class ErrorTracker {
|
|
|
25
50
|
stop() {
|
|
26
51
|
if (!this.isActive)
|
|
27
52
|
return;
|
|
53
|
+
// Flip the flag FIRST — the rejection trackers can't always be
|
|
54
|
+
// unregistered (Hermes), so their callbacks guard on isActive.
|
|
28
55
|
this.isActive = false;
|
|
29
56
|
this.restoreGlobalErrorHandler();
|
|
57
|
+
this.teardownUnhandledRejectionHandler();
|
|
30
58
|
}
|
|
31
59
|
/**
|
|
32
60
|
* Manual error reporting - allows consumers to report caught errors.
|
|
@@ -53,66 +81,115 @@ class ErrorTracker {
|
|
|
53
81
|
const ErrorUtils = global.ErrorUtils;
|
|
54
82
|
if (ErrorUtils) {
|
|
55
83
|
this.originalHandler = ErrorUtils.getGlobalHandler();
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
84
|
+
this.wrapperHandler = (error, isFatal) => {
|
|
85
|
+
if (this.isActive) {
|
|
86
|
+
try {
|
|
87
|
+
const crash = {
|
|
88
|
+
sessionId: this.sessionId || undefined,
|
|
89
|
+
type: isFatal ? 'native_crash' : 'js_error',
|
|
90
|
+
message: error.message,
|
|
91
|
+
stackTrace: error.stack || undefined,
|
|
92
|
+
screenName: this.currentScreen || undefined,
|
|
93
|
+
metadata: { isFatal },
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
};
|
|
96
|
+
this.onCrash(crash);
|
|
97
|
+
}
|
|
98
|
+
catch (_e) {
|
|
99
|
+
// Never crash the app due to error reporting
|
|
100
|
+
}
|
|
71
101
|
}
|
|
72
102
|
// Always call original handler so RN can handle the error
|
|
73
103
|
if (this.originalHandler) {
|
|
74
104
|
this.originalHandler(error, isFatal);
|
|
75
105
|
}
|
|
76
|
-
}
|
|
106
|
+
};
|
|
107
|
+
ErrorUtils.setGlobalHandler(this.wrapperHandler);
|
|
77
108
|
}
|
|
78
109
|
}
|
|
79
110
|
catch (_e) {
|
|
80
111
|
// Never crash the app due to error handler setup
|
|
81
112
|
}
|
|
82
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Register with the mechanisms React Native ACTUALLY uses to surface
|
|
116
|
+
* unhandled promise rejections:
|
|
117
|
+
*
|
|
118
|
+
* 1. Hermes' native promise rejection tracker (Hermes engine).
|
|
119
|
+
* 2. The `promise` polyfill's rejection-tracking module (JSC and older
|
|
120
|
+
* RN versions).
|
|
121
|
+
*
|
|
122
|
+
* A bare global callback (the old approach) is never invoked by RN — it
|
|
123
|
+
* silently captured nothing.
|
|
124
|
+
*
|
|
125
|
+
* KNOWN LIMITATION: these trackers hold a single handler — registering
|
|
126
|
+
* REPLACES whatever was installed before (RN's dev LogBox warning, or
|
|
127
|
+
* another SDK like Sentry if it initialized first), and there is no
|
|
128
|
+
* unregister API, so after stop() an inert handler remains installed.
|
|
129
|
+
* If you run multiple error-tracking SDKs, initialize Repliqo FIRST so
|
|
130
|
+
* the other SDK's handler wins, or disable enableCrashTracking.
|
|
131
|
+
*/
|
|
83
132
|
setupUnhandledRejectionHandler() {
|
|
133
|
+
// Kept for manual invocation / test hooks.
|
|
84
134
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// Never crash the app due to error reporting
|
|
102
|
-
}
|
|
103
|
-
};
|
|
135
|
+
global.__analyticsUnhandledRejection =
|
|
136
|
+
this.handleUnhandledRejection;
|
|
137
|
+
}
|
|
138
|
+
catch (_e) {
|
|
139
|
+
/* ignore */
|
|
140
|
+
}
|
|
141
|
+
// Hermes: native promise rejection tracking.
|
|
142
|
+
try {
|
|
143
|
+
const hermes = global.HermesInternal;
|
|
144
|
+
if (hermes?.hasPromise?.() && hermes?.enablePromiseRejectionTracker) {
|
|
145
|
+
hermes.enablePromiseRejectionTracker({
|
|
146
|
+
allRejections: true,
|
|
147
|
+
onUnhandled: this.handleUnhandledRejection,
|
|
148
|
+
onHandled: () => { },
|
|
149
|
+
});
|
|
150
|
+
return;
|
|
104
151
|
}
|
|
105
152
|
}
|
|
106
153
|
catch (_e) {
|
|
107
|
-
|
|
154
|
+
/* ignore — fall through to the polyfill */
|
|
155
|
+
}
|
|
156
|
+
// JSC / promise polyfill path.
|
|
157
|
+
try {
|
|
158
|
+
const rejectionTracking = require('promise/setimmediate/rejection-tracking');
|
|
159
|
+
rejectionTracking.enable({
|
|
160
|
+
allRejections: true,
|
|
161
|
+
onUnhandled: this.handleUnhandledRejection,
|
|
162
|
+
onHandled: () => { },
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch (_e) {
|
|
166
|
+
// Module unavailable (e.g. jest without the polyfill) — the global
|
|
167
|
+
// hook above still allows manual reporting.
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
teardownUnhandledRejectionHandler() {
|
|
171
|
+
try {
|
|
172
|
+
delete global.__analyticsUnhandledRejection;
|
|
173
|
+
}
|
|
174
|
+
catch (_e) {
|
|
175
|
+
/* ignore */
|
|
108
176
|
}
|
|
177
|
+
// Hermes offers no unregister API and disabling the polyfill tracker
|
|
178
|
+
// could clobber another library's handler — the isActive guard in
|
|
179
|
+
// handleUnhandledRejection makes lingering registrations inert.
|
|
109
180
|
}
|
|
110
181
|
restoreGlobalErrorHandler() {
|
|
111
182
|
try {
|
|
112
183
|
const ErrorUtils = global.ErrorUtils;
|
|
113
184
|
if (ErrorUtils && this.originalHandler) {
|
|
114
|
-
|
|
185
|
+
// Only restore if WE are still the installed handler — another
|
|
186
|
+
// library may have wrapped us after start(), and restoring would
|
|
187
|
+
// clobber its handler.
|
|
188
|
+
if (ErrorUtils.getGlobalHandler() === this.wrapperHandler) {
|
|
189
|
+
ErrorUtils.setGlobalHandler(this.originalHandler);
|
|
190
|
+
}
|
|
115
191
|
this.originalHandler = null;
|
|
192
|
+
this.wrapperHandler = null;
|
|
116
193
|
}
|
|
117
194
|
}
|
|
118
195
|
catch (_e) {
|
|
@@ -57,6 +57,17 @@ function useNavigationTracker() {
|
|
|
57
57
|
// AppAnalytics not initialized, silently ignore
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
else if (currentRouteName && !previousRouteName) {
|
|
61
|
+
// First route seen without onReady having fired (some apps only wire
|
|
62
|
+
// onStateChange). Without this, currentScreen stays null until the
|
|
63
|
+
// SECOND navigation and early touches/visits go unattributed.
|
|
64
|
+
try {
|
|
65
|
+
client_1.AppAnalytics.getInstance().onScreenEnter(currentRouteName);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// AppAnalytics not initialized, silently ignore
|
|
69
|
+
}
|
|
70
|
+
}
|
|
60
71
|
routeNameRef.current = currentRouteName;
|
|
61
72
|
}, []);
|
|
62
73
|
return {
|
|
@@ -11,10 +11,39 @@ export declare class ApiClient {
|
|
|
11
11
|
private logger;
|
|
12
12
|
constructor(baseUrl: string, apiKey: string, logger: Logger);
|
|
13
13
|
private getHeaders;
|
|
14
|
+
/**
|
|
15
|
+
* fetch with a hard timeout. Without it, a single stalled request keeps
|
|
16
|
+
* the BatchQueue's in-flight lock held indefinitely — no flush can run,
|
|
17
|
+
* the buffer fills up and starts evicting events (silent data loss).
|
|
18
|
+
*/
|
|
19
|
+
private fetchWithTimeout;
|
|
20
|
+
/**
|
|
21
|
+
* Classify a non-ok batch response. Permanent PAYLOAD errors (the batch
|
|
22
|
+
* itself will never be accepted: 400 malformed, 413 too large, 422
|
|
23
|
+
* unprocessable, 404 gone) surface as NonRetryableFlushError so the
|
|
24
|
+
* queue drops the poison batch instead of retrying it forever.
|
|
25
|
+
*
|
|
26
|
+
* 401/403 are NOT the payload's fault — they mean the API key is wrong
|
|
27
|
+
* or was rotated. Treat them as transient (retry with backoff) so a key
|
|
28
|
+
* rotation doesn't silently destroy every live SDK's buffered data.
|
|
29
|
+
* 408/429 are transient by definition.
|
|
30
|
+
*/
|
|
31
|
+
private batchError;
|
|
32
|
+
/**
|
|
33
|
+
* Parse a 2xx response body, tolerating malformed JSON. The server
|
|
34
|
+
* already accepted the data — throwing here would make the queue retry
|
|
35
|
+
* an already-persisted batch and create duplicates.
|
|
36
|
+
*/
|
|
37
|
+
private safeJson;
|
|
14
38
|
startSession(deviceId: string, deviceInfo?: DeviceInfo): Promise<{
|
|
15
39
|
id: string;
|
|
16
40
|
}>;
|
|
17
41
|
endSession(sessionId: string): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Attach the host app's user identity to a session.
|
|
44
|
+
* Fire-and-forget: errors are logged but never thrown.
|
|
45
|
+
*/
|
|
46
|
+
identifySession(sessionId: string, userId: string, traits?: Record<string, unknown>): Promise<void>;
|
|
18
47
|
sendEventsBatch(sessionId: string, events: AnalyticsEvent[]): Promise<{
|
|
19
48
|
count: number;
|
|
20
49
|
}>;
|
|
@@ -22,7 +51,11 @@ export declare class ApiClient {
|
|
|
22
51
|
sendSnapshotsBatch(snapshots: SnapshotPayload[]): Promise<{
|
|
23
52
|
count: number;
|
|
24
53
|
}>;
|
|
25
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Send a single crash report. Never throws; returns true when the
|
|
56
|
+
* server accepted it (used to clear the persisted pending copy).
|
|
57
|
+
*/
|
|
58
|
+
sendCrashReport(crash: CrashReport): Promise<boolean>;
|
|
26
59
|
sendCrashesBatch(crashes: CrashReport[]): Promise<{
|
|
27
60
|
count: number;
|
|
28
61
|
}>;
|