@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/core/config.ts
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
|
-
import { SDKConfig } from '../types/events';
|
|
2
|
-
|
|
3
|
-
export const REPLIQO_API_URL = 'https://api-repliqo.odmservice.site';
|
|
4
|
-
|
|
5
|
-
export const DEFAULT_CONFIG: Required<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
1
|
+
import { SDKConfig } from '../types/events';
|
|
2
|
+
|
|
3
|
+
export const REPLIQO_API_URL = 'https://api-repliqo.odmservice.site';
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_CONFIG: Required<
|
|
6
|
+
Omit<SDKConfig, 'apiKey' | 'appId' | 'storage'>
|
|
7
|
+
> = {
|
|
8
|
+
batchSize: 20,
|
|
9
|
+
flushInterval: 30000,
|
|
10
|
+
enableTouchTracking: true,
|
|
11
|
+
enableNavigationTracking: true,
|
|
12
|
+
enableSnapshots: true,
|
|
13
|
+
// 0.5 fps. JPEG screenshots at 390px/40% quality are ~15-30 KB each,
|
|
14
|
+
// so 2 s gives a good quality/bandwidth tradeoff for MVP session replay.
|
|
15
|
+
snapshotInterval: 2000,
|
|
16
|
+
// Cap sessions at ~30 minutes of continuous capture (900 frames).
|
|
17
|
+
maxSnapshotsPerSession: 900,
|
|
18
|
+
// Smaller batches than events - JPEGs are heavier.
|
|
19
|
+
snapshotBatchSize: 8,
|
|
20
|
+
snapshotFlushInterval: 15000,
|
|
21
|
+
// ~32 * 30KB ≈ 1 MB cap on in-memory buffer if the backend is down.
|
|
22
|
+
snapshotMaxBufferSize: 32,
|
|
23
|
+
enableHeatmapCapture: true,
|
|
24
|
+
enableCrashTracking: true,
|
|
25
|
+
debug: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ResolvedConfig = SDKConfig & typeof DEFAULT_CONFIG;
|
|
29
|
+
|
|
30
|
+
export function resolveConfig(config: SDKConfig): ResolvedConfig {
|
|
31
|
+
// Strip keys whose value is explicitly `undefined` — otherwise
|
|
32
|
+
// `{ enableSnapshots: maybeUndefinedVar }` would silently override the
|
|
33
|
+
// default `true` with `undefined` and disable the feature.
|
|
34
|
+
const cleaned = Object.fromEntries(
|
|
35
|
+
Object.entries(config).filter(([, v]) => v !== undefined),
|
|
36
|
+
) as SDKConfig;
|
|
37
|
+
return { ...DEFAULT_CONFIG, ...cleaned };
|
|
38
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal async key-value storage contract, compatible with
|
|
3
|
+
* @react-native-async-storage/async-storage (and trivially mockable).
|
|
4
|
+
*
|
|
5
|
+
* Used to persist analytics data across app kills:
|
|
6
|
+
* - buffered events that couldn't be flushed before backgrounding
|
|
7
|
+
* - crash reports (a fatal crash kills the app before the report's
|
|
8
|
+
* network request can complete — persistence is the only way those
|
|
9
|
+
* reports survive to the next launch)
|
|
10
|
+
*/
|
|
11
|
+
export interface PersistentStorage {
|
|
12
|
+
getItem(key: string): Promise<string | null>;
|
|
13
|
+
setItem(key: string, value: string): Promise<void>;
|
|
14
|
+
removeItem(key: string): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Dynamic optional require — AsyncStorage is an optional peer. */
|
|
18
|
+
declare const require: any;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Try to auto-load @react-native-async-storage/async-storage. Returns null
|
|
22
|
+
* when the host app doesn't have it installed — persistence is then
|
|
23
|
+
* disabled gracefully (in-memory only, pre-existing behavior).
|
|
24
|
+
*/
|
|
25
|
+
export function tryLoadAsyncStorage(): PersistentStorage | null {
|
|
26
|
+
try {
|
|
27
|
+
const mod = require('@react-native-async-storage/async-storage');
|
|
28
|
+
const storage = mod?.default ?? mod;
|
|
29
|
+
if (
|
|
30
|
+
storage &&
|
|
31
|
+
typeof storage.getItem === 'function' &&
|
|
32
|
+
typeof storage.setItem === 'function' &&
|
|
33
|
+
typeof storage.removeItem === 'function'
|
|
34
|
+
) {
|
|
35
|
+
return storage as PersistentStorage;
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// Not installed — fine.
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Storage keys namespaced to avoid colliding with the host app's data. */
|
|
44
|
+
export const STORAGE_KEYS = {
|
|
45
|
+
pendingEvents: '@repliqo/pending_events',
|
|
46
|
+
pendingCrashes: '@repliqo/pending_crashes',
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
/** Caps so a long-offline device can't grow storage unboundedly. */
|
|
50
|
+
export const MAX_PERSISTED_EVENTS = 500;
|
|
51
|
+
export const MAX_PERSISTED_CRASHES = 20;
|
package/src/index.ts
CHANGED
|
@@ -1,52 +1,54 @@
|
|
|
1
|
-
// Main class
|
|
2
|
-
export { AppAnalytics } from './core/client';
|
|
3
|
-
|
|
4
|
-
// Trackers
|
|
5
|
-
export { TouchTracker } from './trackers/touch.tracker';
|
|
6
|
-
export {
|
|
7
|
-
useNavigationTracker,
|
|
8
|
-
trackScreenChange,
|
|
9
|
-
} from './trackers/navigation.tracker';
|
|
10
|
-
export {
|
|
11
|
-
trackScreenEnter,
|
|
12
|
-
trackScreenExit,
|
|
13
|
-
} from './trackers/screen.tracker';
|
|
14
|
-
export { ErrorTracker } from './trackers/error.tracker';
|
|
15
|
-
|
|
16
|
-
// Snapshot
|
|
17
|
-
export { SnapshotCapture } from './snapshot/capture';
|
|
18
|
-
export { captureScreenshot } from './snapshot/captureScreenshot';
|
|
19
|
-
export {
|
|
20
|
-
isNativeScreenCaptureAvailable,
|
|
21
|
-
captureNativeFrame,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} from './types/
|
|
41
|
-
|
|
42
|
-
export type {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
1
|
+
// Main class
|
|
2
|
+
export { AppAnalytics } from './core/client';
|
|
3
|
+
|
|
4
|
+
// Trackers
|
|
5
|
+
export { TouchTracker } from './trackers/touch.tracker';
|
|
6
|
+
export {
|
|
7
|
+
useNavigationTracker,
|
|
8
|
+
trackScreenChange,
|
|
9
|
+
} from './trackers/navigation.tracker';
|
|
10
|
+
export {
|
|
11
|
+
trackScreenEnter,
|
|
12
|
+
trackScreenExit,
|
|
13
|
+
} from './trackers/screen.tracker';
|
|
14
|
+
export { ErrorTracker } from './trackers/error.tracker';
|
|
15
|
+
|
|
16
|
+
// Snapshot
|
|
17
|
+
export { SnapshotCapture } from './snapshot/capture';
|
|
18
|
+
export { captureScreenshot } from './snapshot/captureScreenshot';
|
|
19
|
+
export {
|
|
20
|
+
isNativeScreenCaptureAvailable,
|
|
21
|
+
captureNativeFrame,
|
|
22
|
+
} from './snapshot/NativeScreenCapture';
|
|
23
|
+
|
|
24
|
+
// Types
|
|
25
|
+
export type {
|
|
26
|
+
EventType,
|
|
27
|
+
AnalyticsEvent,
|
|
28
|
+
TouchEventData,
|
|
29
|
+
NavigationEventData,
|
|
30
|
+
ScreenVisit,
|
|
31
|
+
DeviceInfo,
|
|
32
|
+
SDKConfig,
|
|
33
|
+
} from './types/events';
|
|
34
|
+
|
|
35
|
+
export type {
|
|
36
|
+
ScreenshotData,
|
|
37
|
+
SnapshotPayload,
|
|
38
|
+
} from './types/snapshot';
|
|
39
|
+
|
|
40
|
+
export type { CrashReport } from './types/crash';
|
|
41
|
+
|
|
42
|
+
export type { SnapshotCaptureConfig } from './snapshot/capture';
|
|
43
|
+
export type { ScreenshotResult } from './snapshot/captureScreenshot';
|
|
44
|
+
|
|
45
|
+
// Components
|
|
46
|
+
export { RepliqoScrollView } from './components/RepliqoScrollView';
|
|
47
|
+
export { RepliqoFlatList } from './components/RepliqoFlatList';
|
|
48
|
+
|
|
49
|
+
// Storage (offline persistence contract)
|
|
50
|
+
export type { PersistentStorage } from './core/storage';
|
|
51
|
+
|
|
52
|
+
// Config
|
|
53
|
+
export { DEFAULT_CONFIG } from './core/config';
|
|
54
|
+
export type { ResolvedConfig } from './core/config';
|
|
@@ -1,157 +1,62 @@
|
|
|
1
|
-
import { NativeModules, Platform } from 'react-native';
|
|
2
|
-
|
|
3
|
-
export interface NativeFrameResult {
|
|
4
|
-
image: string;
|
|
5
|
-
width: number;
|
|
6
|
-
height: number;
|
|
7
|
-
format: 'jpeg';
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface NativeCaptureResult {
|
|
11
|
-
image: string;
|
|
12
|
-
width: number;
|
|
13
|
-
height: number;
|
|
14
|
-
format: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
return {
|
|
64
|
-
image: result.image,
|
|
65
|
-
width: result.width,
|
|
66
|
-
height: result.height,
|
|
67
|
-
format: 'jpeg',
|
|
68
|
-
};
|
|
69
|
-
} catch {
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Capture the FULL content of the primary ScrollView on screen,
|
|
76
|
-
* including content scrolled off-screen. Used for heatmap backgrounds.
|
|
77
|
-
*
|
|
78
|
-
* Falls back to a viewport capture if no ScrollView is found.
|
|
79
|
-
* Max height: 5000px. JPEG quality: 60%. Width: ~500px.
|
|
80
|
-
*
|
|
81
|
-
* @returns NativeFrameResult or null if capture failed / not available
|
|
82
|
-
*/
|
|
83
|
-
export interface FullContentResult extends NativeFrameResult {
|
|
84
|
-
/** True if the capture includes full scrollable content. False if viewport-only fallback. */
|
|
85
|
-
isFullContent: boolean;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Capture the FULL content of the primary ScrollView on screen,
|
|
90
|
-
* including content scrolled off-screen. Used for heatmap backgrounds.
|
|
91
|
-
*
|
|
92
|
-
* Falls back to a viewport capture if no ScrollView is found.
|
|
93
|
-
* Max height: 5000px. JPEG quality: 60%. Width: ~500px.
|
|
94
|
-
*
|
|
95
|
-
* Known limitation: FlatList/SectionList use RecyclerView which only
|
|
96
|
-
* renders visible items — full-content capture falls back to viewport.
|
|
97
|
-
*
|
|
98
|
-
* @returns FullContentResult with isFullContent flag, or null
|
|
99
|
-
*/
|
|
100
|
-
export async function captureFullContent(): Promise<FullContentResult | null> {
|
|
101
|
-
if (!NativeModule) return null;
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
const result = await NativeModule.captureFullContent();
|
|
105
|
-
if (!result || !result.image) return null;
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
image: result.image,
|
|
109
|
-
width: result.width,
|
|
110
|
-
height: result.height,
|
|
111
|
-
format: 'jpeg',
|
|
112
|
-
isFullContent: !!result.isFullContent,
|
|
113
|
-
};
|
|
114
|
-
} catch {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export interface ScanTile {
|
|
120
|
-
image: string;
|
|
121
|
-
width: number;
|
|
122
|
-
height: number;
|
|
123
|
-
scrollOffsetY: number;
|
|
124
|
-
viewportHeight: number;
|
|
125
|
-
contentHeight: number;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Perform a fast programmatic scroll-scan of the main ScrollView.
|
|
130
|
-
* Captures the entire content as viewport tiles in ~300-500ms.
|
|
131
|
-
*
|
|
132
|
-
* The scan is invisible to the user — scroll position is saved and
|
|
133
|
-
* restored. Returns an array of tiles covering the full content.
|
|
134
|
-
*
|
|
135
|
-
* @returns Array of tiles, or null if no ScrollView or not available
|
|
136
|
-
*/
|
|
137
|
-
export async function scanFullContent(): Promise<ScanTile[] | null> {
|
|
138
|
-
if (!NativeModule) return null;
|
|
139
|
-
|
|
140
|
-
try {
|
|
141
|
-
const tiles = await NativeModule.scanFullContent();
|
|
142
|
-
if (!tiles || !Array.isArray(tiles) || tiles.length === 0) return null;
|
|
143
|
-
|
|
144
|
-
return tiles
|
|
145
|
-
.filter((t) => t && t.image)
|
|
146
|
-
.map((t) => ({
|
|
147
|
-
image: t.image,
|
|
148
|
-
width: t.width,
|
|
149
|
-
height: t.height,
|
|
150
|
-
scrollOffsetY: t.scrollOffsetY,
|
|
151
|
-
viewportHeight: t.viewportHeight,
|
|
152
|
-
contentHeight: t.contentHeight,
|
|
153
|
-
}));
|
|
154
|
-
} catch {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
1
|
+
import { NativeModules, Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export interface NativeFrameResult {
|
|
4
|
+
image: string;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
format: 'jpeg';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface NativeCaptureResult {
|
|
11
|
+
image: string;
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
format: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface NativeScreenCaptureModule {
|
|
18
|
+
captureFrame(): Promise<NativeCaptureResult | null>;
|
|
19
|
+
isAvailable(): Promise<boolean>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const NativeModule: NativeScreenCaptureModule | null =
|
|
23
|
+
Platform.OS === 'android'
|
|
24
|
+
? (NativeModules.ScreenCaptureModule as NativeScreenCaptureModule) ?? null
|
|
25
|
+
: null;
|
|
26
|
+
|
|
27
|
+
/** Whether the native multi-window capture module is available. */
|
|
28
|
+
export function isNativeScreenCaptureAvailable(): boolean {
|
|
29
|
+
return NativeModule !== null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Capture a single frame using native multi-window capture.
|
|
34
|
+
* Captures ALL visible windows: main view + modals + alerts + dialogs.
|
|
35
|
+
*
|
|
36
|
+
* No permissions required. No dialog. No foreground service.
|
|
37
|
+
*
|
|
38
|
+
* This is the ONLY native capture path. Full-content capture of
|
|
39
|
+
* off-screen ScrollView content is NOT possible natively (React Native
|
|
40
|
+
* doesn't render off-screen content to the GPU/view tree) — full-content
|
|
41
|
+
* heatmap backgrounds are instead built collaboratively from viewport
|
|
42
|
+
* tiles captured as the user naturally scrolls (see RepliqoScrollView).
|
|
43
|
+
*
|
|
44
|
+
* @returns NativeFrameResult or null if capture failed / not available
|
|
45
|
+
*/
|
|
46
|
+
export async function captureNativeFrame(): Promise<NativeFrameResult | null> {
|
|
47
|
+
if (!NativeModule) return null;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const result = await NativeModule.captureFrame();
|
|
51
|
+
if (!result || !result.image) return null;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
image: result.image,
|
|
55
|
+
width: result.width,
|
|
56
|
+
height: result.height,
|
|
57
|
+
format: 'jpeg',
|
|
58
|
+
};
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|