@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.
Files changed (48) hide show
  1. package/INTEGRATION_GUIDE.md +1384 -1312
  2. package/android/src/main/java/com/repliqo/screencapture/MultiWindowCapture.java +242 -230
  3. package/android/src/main/java/com/repliqo/screencapture/ScreenCaptureModule.java +94 -170
  4. package/dist/components/RepliqoFlatList.d.ts +5 -0
  5. package/dist/components/RepliqoFlatList.js +39 -0
  6. package/dist/components/RepliqoScrollView.d.ts +3 -0
  7. package/dist/components/RepliqoScrollView.js +16 -265
  8. package/dist/components/useRepliqoScrollTracking.d.ts +33 -0
  9. package/dist/components/useRepliqoScrollTracking.js +304 -0
  10. package/dist/core/client.d.ts +52 -5
  11. package/dist/core/client.js +310 -48
  12. package/dist/core/config.d.ts +1 -1
  13. package/dist/core/config.js +5 -1
  14. package/dist/core/storage.d.ts +29 -0
  15. package/dist/core/storage.js +33 -0
  16. package/dist/index.d.ts +3 -2
  17. package/dist/index.js +3 -2
  18. package/dist/snapshot/NativeScreenCapture.d.ts +6 -44
  19. package/dist/snapshot/NativeScreenCapture.js +6 -64
  20. package/dist/snapshot/capture.d.ts +7 -0
  21. package/dist/snapshot/capture.js +17 -5
  22. package/dist/trackers/error.tracker.d.ts +25 -0
  23. package/dist/trackers/error.tracker.js +114 -37
  24. package/dist/trackers/navigation.tracker.js +11 -0
  25. package/dist/transport/api.client.d.ts +34 -1
  26. package/dist/transport/api.client.js +102 -19
  27. package/dist/transport/batch-queue.d.ts +53 -1
  28. package/dist/transport/batch-queue.js +126 -19
  29. package/dist/types/events.d.ts +12 -0
  30. package/package.json +68 -64
  31. package/src/components/RepliqoFlatList.tsx +64 -0
  32. package/src/components/RepliqoScrollView.tsx +53 -302
  33. package/src/components/useRepliqoScrollTracking.ts +366 -0
  34. package/src/core/client.ts +953 -672
  35. package/src/core/config.ts +38 -30
  36. package/src/core/storage.ts +51 -0
  37. package/src/index.ts +54 -52
  38. package/src/snapshot/NativeScreenCapture.ts +62 -157
  39. package/src/snapshot/capture.ts +154 -142
  40. package/src/trackers/error.tracker.ts +211 -136
  41. package/src/trackers/navigation.tracker.ts +107 -98
  42. package/src/transport/api.client.ts +430 -327
  43. package/src/transport/batch-queue.ts +212 -95
  44. package/src/types/events.ts +72 -60
  45. package/android/src/main/java/com/repliqo/screencapture/FullContentCapture.java +0 -273
  46. package/android/src/main/java/com/repliqo/screencapture/PixelCopyScan.java +0 -279
  47. package/android/src/main/java/com/repliqo/screencapture/ScrollScanCapture.java +0 -248
  48. package/src/snapshot/ScreenCaptureManager.ts +0 -156
@@ -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<Omit<SDKConfig, 'apiKey' | 'appId'>> = {
6
- batchSize: 20,
7
- flushInterval: 30000,
8
- enableTouchTracking: true,
9
- enableNavigationTracking: true,
10
- enableSnapshots: true,
11
- // 0.5 fps. JPEG screenshots at 390px/40% quality are ~15-30 KB each,
12
- // so 2 s gives a good quality/bandwidth tradeoff for MVP session replay.
13
- snapshotInterval: 2000,
14
- // Cap sessions at ~30 minutes of continuous capture (900 frames).
15
- maxSnapshotsPerSession: 900,
16
- // Smaller batches than events - JPEGs are heavier.
17
- snapshotBatchSize: 8,
18
- snapshotFlushInterval: 15000,
19
- // ~32 * 30KB ≈ 1 MB cap on in-memory buffer if the backend is down.
20
- snapshotMaxBufferSize: 32,
21
- enableHeatmapCapture: true,
22
- enableCrashTracking: true,
23
- debug: false,
24
- };
25
-
26
- export type ResolvedConfig = SDKConfig & typeof DEFAULT_CONFIG;
27
-
28
- export function resolveConfig(config: SDKConfig): ResolvedConfig {
29
- return { ...DEFAULT_CONFIG, ...config };
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
- captureFullContent,
23
- } from './snapshot/NativeScreenCapture';
24
- export type { FullContentResult } from './snapshot/NativeScreenCapture';
25
-
26
- // Types
27
- export type {
28
- EventType,
29
- AnalyticsEvent,
30
- TouchEventData,
31
- NavigationEventData,
32
- ScreenVisit,
33
- DeviceInfo,
34
- SDKConfig,
35
- } from './types/events';
36
-
37
- export type {
38
- ScreenshotData,
39
- SnapshotPayload,
40
- } from './types/snapshot';
41
-
42
- export type { CrashReport } from './types/crash';
43
-
44
- export type { SnapshotCaptureConfig } from './snapshot/capture';
45
- export type { ScreenshotResult } from './snapshot/captureScreenshot';
46
-
47
- // Components
48
- export { RepliqoScrollView } from './components/RepliqoScrollView';
49
-
50
- // Config
51
- export { DEFAULT_CONFIG } from './core/config';
52
- export type { ResolvedConfig } from './core/config';
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 NativeFullContentResult extends NativeCaptureResult {
18
- isFullContent: boolean;
19
- }
20
-
21
- interface NativeTile {
22
- image: string;
23
- width: number;
24
- height: number;
25
- scrollOffsetY: number;
26
- viewportHeight: number;
27
- contentHeight: number;
28
- format: string;
29
- }
30
-
31
- interface NativeScreenCaptureModule {
32
- captureFrame(): Promise<NativeCaptureResult | null>;
33
- captureFullContent(): Promise<NativeFullContentResult | null>;
34
- scanFullContent(): Promise<NativeTile[] | null>;
35
- isAvailable(): Promise<boolean>;
36
- }
37
-
38
- const NativeModule: NativeScreenCaptureModule | null =
39
- Platform.OS === 'android'
40
- ? (NativeModules.ScreenCaptureModule as NativeScreenCaptureModule) ?? null
41
- : null;
42
-
43
- /** Whether the native multi-window capture module is available. */
44
- export function isNativeScreenCaptureAvailable(): boolean {
45
- return NativeModule !== null;
46
- }
47
-
48
- /**
49
- * Capture a single frame using native multi-window capture.
50
- * Captures ALL visible windows: main view + modals + alerts + dialogs.
51
- *
52
- * No permissions required. No dialog. No foreground service.
53
- *
54
- * @returns NativeFrameResult or null if capture failed / not available
55
- */
56
- export async function captureNativeFrame(): Promise<NativeFrameResult | null> {
57
- if (!NativeModule) return null;
58
-
59
- try {
60
- const result = await NativeModule.captureFrame();
61
- if (!result || !result.image) return null;
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
+ }