@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
package/src/snapshot/capture.ts
CHANGED
|
@@ -1,142 +1,154 @@
|
|
|
1
|
-
import { SnapshotPayload, ScreenshotData } from '../types/snapshot';
|
|
2
|
-
import { captureScreenshot } from './captureScreenshot';
|
|
3
|
-
|
|
4
|
-
export interface SnapshotCaptureConfig {
|
|
5
|
-
captureInterval: number;
|
|
6
|
-
maxSnapshotsPerSession: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type ScreenshotProvider = () => Promise<ScreenshotData | null>;
|
|
10
|
-
|
|
11
|
-
type LogFn = (...args: any[]) => void;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Captures JPEG screenshots of the RN view hierarchy at a fixed interval
|
|
15
|
-
* and forwards them to the host (usually the snapshot BatchQueue).
|
|
16
|
-
*/
|
|
17
|
-
export class SnapshotCapture {
|
|
18
|
-
private timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
19
|
-
private sequence: number = 0;
|
|
20
|
-
private config: SnapshotCaptureConfig;
|
|
21
|
-
private onSnapshot: (snapshot: SnapshotPayload) => void;
|
|
22
|
-
private sessionId: string | null = null;
|
|
23
|
-
private currentScreen: string = 'unknown';
|
|
24
|
-
private capturing: boolean = false;
|
|
25
|
-
private running: boolean = false;
|
|
26
|
-
private provider: ScreenshotProvider;
|
|
27
|
-
private log: LogFn;
|
|
28
|
-
private warn: LogFn;
|
|
29
|
-
|
|
30
|
-
constructor(
|
|
31
|
-
config: SnapshotCaptureConfig,
|
|
32
|
-
onSnapshot: (snapshot: SnapshotPayload) => void,
|
|
33
|
-
provider: ScreenshotProvider = captureScreenshot,
|
|
34
|
-
log: LogFn = () => {},
|
|
35
|
-
warn: LogFn = () => {},
|
|
36
|
-
) {
|
|
37
|
-
this.config = config;
|
|
38
|
-
this.onSnapshot = onSnapshot;
|
|
39
|
-
this.provider = provider;
|
|
40
|
-
this.log = log;
|
|
41
|
-
this.warn = warn;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (this.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
this.
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
1
|
+
import { SnapshotPayload, ScreenshotData } from '../types/snapshot';
|
|
2
|
+
import { captureScreenshot } from './captureScreenshot';
|
|
3
|
+
|
|
4
|
+
export interface SnapshotCaptureConfig {
|
|
5
|
+
captureInterval: number;
|
|
6
|
+
maxSnapshotsPerSession: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type ScreenshotProvider = () => Promise<ScreenshotData | null>;
|
|
10
|
+
|
|
11
|
+
type LogFn = (...args: any[]) => void;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Captures JPEG screenshots of the RN view hierarchy at a fixed interval
|
|
15
|
+
* and forwards them to the host (usually the snapshot BatchQueue).
|
|
16
|
+
*/
|
|
17
|
+
export class SnapshotCapture {
|
|
18
|
+
private timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
19
|
+
private sequence: number = 0;
|
|
20
|
+
private config: SnapshotCaptureConfig;
|
|
21
|
+
private onSnapshot: (snapshot: SnapshotPayload) => void;
|
|
22
|
+
private sessionId: string | null = null;
|
|
23
|
+
private currentScreen: string = 'unknown';
|
|
24
|
+
private capturing: boolean = false;
|
|
25
|
+
private running: boolean = false;
|
|
26
|
+
private provider: ScreenshotProvider;
|
|
27
|
+
private log: LogFn;
|
|
28
|
+
private warn: LogFn;
|
|
29
|
+
|
|
30
|
+
constructor(
|
|
31
|
+
config: SnapshotCaptureConfig,
|
|
32
|
+
onSnapshot: (snapshot: SnapshotPayload) => void,
|
|
33
|
+
provider: ScreenshotProvider = captureScreenshot,
|
|
34
|
+
log: LogFn = () => {},
|
|
35
|
+
warn: LogFn = () => {},
|
|
36
|
+
) {
|
|
37
|
+
this.config = config;
|
|
38
|
+
this.onSnapshot = onSnapshot;
|
|
39
|
+
this.provider = provider;
|
|
40
|
+
this.log = log;
|
|
41
|
+
this.warn = warn;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generation counter. Incremented on every start()/stop() so that timer
|
|
46
|
+
* chains from a PREVIOUS generation cannot reschedule themselves. Without
|
|
47
|
+
* it, a fast stop()→start() while a tick's capture is still in flight
|
|
48
|
+
* leaves two parallel timer chains alive (double capture rate).
|
|
49
|
+
*/
|
|
50
|
+
private epoch = 0;
|
|
51
|
+
|
|
52
|
+
start(): void {
|
|
53
|
+
if (this.running) return;
|
|
54
|
+
this.running = true;
|
|
55
|
+
this.epoch++;
|
|
56
|
+
this.log('Snapshot capture started (interval:', this.config.captureInterval, 'ms)');
|
|
57
|
+
this.scheduleNext(this.epoch);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
stop(): void {
|
|
61
|
+
this.running = false;
|
|
62
|
+
this.epoch++;
|
|
63
|
+
if (this.timeoutId !== null) {
|
|
64
|
+
clearTimeout(this.timeoutId);
|
|
65
|
+
this.timeoutId = null;
|
|
66
|
+
}
|
|
67
|
+
this.log('Snapshot capture stopped');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async captureNow(): Promise<void> {
|
|
71
|
+
await this.tick();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private scheduleNext(epoch: number): void {
|
|
75
|
+
if (!this.running || epoch !== this.epoch) return;
|
|
76
|
+
this.timeoutId = setTimeout(() => {
|
|
77
|
+
// Orphan timer from a previous generation — do nothing.
|
|
78
|
+
if (epoch !== this.epoch) return;
|
|
79
|
+
Promise.resolve(this.tick()).finally(() => {
|
|
80
|
+
if (this.running && epoch === this.epoch) {
|
|
81
|
+
this.scheduleNext(epoch);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}, this.config.captureInterval);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private async tick(): Promise<void> {
|
|
88
|
+
if (!this.sessionId) return;
|
|
89
|
+
if (this.capturing) return;
|
|
90
|
+
|
|
91
|
+
if (this.sequence >= this.config.maxSnapshotsPerSession) {
|
|
92
|
+
this.log('Max snapshots reached:', this.sequence);
|
|
93
|
+
this.stop();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.capturing = true;
|
|
98
|
+
let screenshot: ScreenshotData | null = null;
|
|
99
|
+
try {
|
|
100
|
+
screenshot = await this.provider();
|
|
101
|
+
} catch (err) {
|
|
102
|
+
this.warn('Snapshot capture failed:', err);
|
|
103
|
+
screenshot = null;
|
|
104
|
+
} finally {
|
|
105
|
+
this.capturing = false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!screenshot) {
|
|
109
|
+
this.warn('Snapshot returned null (provider unavailable or failed)');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Strip `source` field before sending — the backend DTO only
|
|
114
|
+
// accepts { image, width, height, format } and rejects unknown props.
|
|
115
|
+
const { image, width, height, format } = screenshot;
|
|
116
|
+
|
|
117
|
+
const payload: SnapshotPayload = {
|
|
118
|
+
sessionId: this.sessionId,
|
|
119
|
+
screenName: this.currentScreen,
|
|
120
|
+
timestamp: new Date().toISOString(),
|
|
121
|
+
sequence: this.sequence,
|
|
122
|
+
isDiff: false,
|
|
123
|
+
data: { image, width, height, format },
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
this.sequence++;
|
|
127
|
+
this.log(
|
|
128
|
+
'Snapshot captured: seq=', this.sequence - 1,
|
|
129
|
+
'screen=', this.currentScreen,
|
|
130
|
+
'size=', Math.round(screenshot.image.length / 1024), 'KB',
|
|
131
|
+
);
|
|
132
|
+
this.onSnapshot(payload);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
reset(): void {
|
|
136
|
+
this.sequence = 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setSessionId(sessionId: string | null): void {
|
|
140
|
+
this.sessionId = sessionId;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setCurrentScreen(screenName: string): void {
|
|
144
|
+
this.currentScreen = screenName;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getSequence(): number {
|
|
148
|
+
return this.sequence;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
isRunning(): boolean {
|
|
152
|
+
return this.running;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -1,136 +1,211 @@
|
|
|
1
|
-
import { CrashReport } from '../types/crash';
|
|
2
|
-
|
|
3
|
-
type CrashCallback = (crash: CrashReport) => void;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
private
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
private
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
1
|
+
import { CrashReport } from '../types/crash';
|
|
2
|
+
|
|
3
|
+
type CrashCallback = (crash: CrashReport) => void;
|
|
4
|
+
|
|
5
|
+
/* Dynamic require for React Native's optional promise internals. */
|
|
6
|
+
declare const require: any;
|
|
7
|
+
|
|
8
|
+
export class ErrorTracker {
|
|
9
|
+
private onCrash: CrashCallback;
|
|
10
|
+
private sessionId: string | null = null;
|
|
11
|
+
private currentScreen: string | null = null;
|
|
12
|
+
private originalHandler:
|
|
13
|
+
| ((error: Error, isFatal?: boolean) => void)
|
|
14
|
+
| null = null;
|
|
15
|
+
private wrapperHandler:
|
|
16
|
+
| ((error: Error, isFatal?: boolean) => void)
|
|
17
|
+
| null = null;
|
|
18
|
+
private isActive = false;
|
|
19
|
+
|
|
20
|
+
constructor(onCrash: CrashCallback) {
|
|
21
|
+
this.onCrash = onCrash;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setSessionId(id: string | null): void {
|
|
25
|
+
this.sessionId = id;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setCurrentScreen(screen: string | null): void {
|
|
29
|
+
this.currentScreen = screen;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
start(): void {
|
|
33
|
+
if (this.isActive) return;
|
|
34
|
+
this.isActive = true;
|
|
35
|
+
this.setupGlobalErrorHandler();
|
|
36
|
+
this.setupUnhandledRejectionHandler();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
stop(): void {
|
|
40
|
+
if (!this.isActive) return;
|
|
41
|
+
// Flip the flag FIRST — the rejection trackers can't always be
|
|
42
|
+
// unregistered (Hermes), so their callbacks guard on isActive.
|
|
43
|
+
this.isActive = false;
|
|
44
|
+
this.restoreGlobalErrorHandler();
|
|
45
|
+
this.teardownUnhandledRejectionHandler();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Manual error reporting - allows consumers to report caught errors.
|
|
50
|
+
*/
|
|
51
|
+
reportError(error: Error, metadata?: Record<string, any>): void {
|
|
52
|
+
try {
|
|
53
|
+
const crash: CrashReport = {
|
|
54
|
+
sessionId: this.sessionId || undefined,
|
|
55
|
+
type: 'js_error',
|
|
56
|
+
message: error.message,
|
|
57
|
+
stackTrace: error.stack || undefined,
|
|
58
|
+
screenName: this.currentScreen || undefined,
|
|
59
|
+
metadata,
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
this.onCrash(crash);
|
|
63
|
+
} catch (_e) {
|
|
64
|
+
// Never crash the app due to error reporting
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private setupGlobalErrorHandler(): void {
|
|
69
|
+
try {
|
|
70
|
+
const ErrorUtils = (global as any).ErrorUtils;
|
|
71
|
+
if (ErrorUtils) {
|
|
72
|
+
this.originalHandler = ErrorUtils.getGlobalHandler();
|
|
73
|
+
this.wrapperHandler = (error: Error, isFatal?: boolean) => {
|
|
74
|
+
if (this.isActive) {
|
|
75
|
+
try {
|
|
76
|
+
const crash: CrashReport = {
|
|
77
|
+
sessionId: this.sessionId || undefined,
|
|
78
|
+
type: isFatal ? 'native_crash' : 'js_error',
|
|
79
|
+
message: error.message,
|
|
80
|
+
stackTrace: error.stack || undefined,
|
|
81
|
+
screenName: this.currentScreen || undefined,
|
|
82
|
+
metadata: { isFatal },
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
};
|
|
85
|
+
this.onCrash(crash);
|
|
86
|
+
} catch (_e) {
|
|
87
|
+
// Never crash the app due to error reporting
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Always call original handler so RN can handle the error
|
|
92
|
+
if (this.originalHandler) {
|
|
93
|
+
this.originalHandler(error, isFatal);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
ErrorUtils.setGlobalHandler(this.wrapperHandler);
|
|
97
|
+
}
|
|
98
|
+
} catch (_e) {
|
|
99
|
+
// Never crash the app due to error handler setup
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Handle an unhandled promise rejection. Guarded on isActive because the
|
|
105
|
+
* underlying trackers can't always be unregistered.
|
|
106
|
+
*/
|
|
107
|
+
private handleUnhandledRejection = (_id: number, error: any): void => {
|
|
108
|
+
if (!this.isActive) return;
|
|
109
|
+
try {
|
|
110
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
111
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
112
|
+
|
|
113
|
+
const crash: CrashReport = {
|
|
114
|
+
sessionId: this.sessionId || undefined,
|
|
115
|
+
type: 'unhandled_rejection',
|
|
116
|
+
message,
|
|
117
|
+
stackTrace: stack || undefined,
|
|
118
|
+
screenName: this.currentScreen || undefined,
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
};
|
|
121
|
+
this.onCrash(crash);
|
|
122
|
+
} catch (_e) {
|
|
123
|
+
// Never crash the app due to error reporting
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Register with the mechanisms React Native ACTUALLY uses to surface
|
|
129
|
+
* unhandled promise rejections:
|
|
130
|
+
*
|
|
131
|
+
* 1. Hermes' native promise rejection tracker (Hermes engine).
|
|
132
|
+
* 2. The `promise` polyfill's rejection-tracking module (JSC and older
|
|
133
|
+
* RN versions).
|
|
134
|
+
*
|
|
135
|
+
* A bare global callback (the old approach) is never invoked by RN — it
|
|
136
|
+
* silently captured nothing.
|
|
137
|
+
*
|
|
138
|
+
* KNOWN LIMITATION: these trackers hold a single handler — registering
|
|
139
|
+
* REPLACES whatever was installed before (RN's dev LogBox warning, or
|
|
140
|
+
* another SDK like Sentry if it initialized first), and there is no
|
|
141
|
+
* unregister API, so after stop() an inert handler remains installed.
|
|
142
|
+
* If you run multiple error-tracking SDKs, initialize Repliqo FIRST so
|
|
143
|
+
* the other SDK's handler wins, or disable enableCrashTracking.
|
|
144
|
+
*/
|
|
145
|
+
private setupUnhandledRejectionHandler(): void {
|
|
146
|
+
// Kept for manual invocation / test hooks.
|
|
147
|
+
try {
|
|
148
|
+
(global as any).__analyticsUnhandledRejection =
|
|
149
|
+
this.handleUnhandledRejection;
|
|
150
|
+
} catch (_e) {
|
|
151
|
+
/* ignore */
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Hermes: native promise rejection tracking.
|
|
155
|
+
try {
|
|
156
|
+
const hermes = (global as any).HermesInternal;
|
|
157
|
+
if (hermes?.hasPromise?.() && hermes?.enablePromiseRejectionTracker) {
|
|
158
|
+
hermes.enablePromiseRejectionTracker({
|
|
159
|
+
allRejections: true,
|
|
160
|
+
onUnhandled: this.handleUnhandledRejection,
|
|
161
|
+
onHandled: () => {},
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
} catch (_e) {
|
|
166
|
+
/* ignore — fall through to the polyfill */
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// JSC / promise polyfill path.
|
|
170
|
+
try {
|
|
171
|
+
const rejectionTracking = require('promise/setimmediate/rejection-tracking');
|
|
172
|
+
rejectionTracking.enable({
|
|
173
|
+
allRejections: true,
|
|
174
|
+
onUnhandled: this.handleUnhandledRejection,
|
|
175
|
+
onHandled: () => {},
|
|
176
|
+
});
|
|
177
|
+
} catch (_e) {
|
|
178
|
+
// Module unavailable (e.g. jest without the polyfill) — the global
|
|
179
|
+
// hook above still allows manual reporting.
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private teardownUnhandledRejectionHandler(): void {
|
|
184
|
+
try {
|
|
185
|
+
delete (global as any).__analyticsUnhandledRejection;
|
|
186
|
+
} catch (_e) {
|
|
187
|
+
/* ignore */
|
|
188
|
+
}
|
|
189
|
+
// Hermes offers no unregister API and disabling the polyfill tracker
|
|
190
|
+
// could clobber another library's handler — the isActive guard in
|
|
191
|
+
// handleUnhandledRejection makes lingering registrations inert.
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private restoreGlobalErrorHandler(): void {
|
|
195
|
+
try {
|
|
196
|
+
const ErrorUtils = (global as any).ErrorUtils;
|
|
197
|
+
if (ErrorUtils && this.originalHandler) {
|
|
198
|
+
// Only restore if WE are still the installed handler — another
|
|
199
|
+
// library may have wrapped us after start(), and restoring would
|
|
200
|
+
// clobber its handler.
|
|
201
|
+
if (ErrorUtils.getGlobalHandler() === this.wrapperHandler) {
|
|
202
|
+
ErrorUtils.setGlobalHandler(this.originalHandler);
|
|
203
|
+
}
|
|
204
|
+
this.originalHandler = null;
|
|
205
|
+
this.wrapperHandler = null;
|
|
206
|
+
}
|
|
207
|
+
} catch (_e) {
|
|
208
|
+
// Never crash the app due to handler restoration
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|