@repliqo/sdk-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.
Files changed (58) hide show
  1. package/INTEGRATION_GUIDE.md +1312 -0
  2. package/android/build.gradle +24 -0
  3. package/android/src/main/AndroidManifest.xml +4 -0
  4. package/android/src/main/java/com/repliqo/screencapture/MultiWindowCapture.java +230 -0
  5. package/android/src/main/java/com/repliqo/screencapture/ScreenCaptureModule.java +94 -0
  6. package/android/src/main/java/com/repliqo/screencapture/ScreenCapturePackage.java +38 -0
  7. package/dist/components/ScreenCapturePermission.d.ts +55 -0
  8. package/dist/components/ScreenCapturePermission.js +112 -0
  9. package/dist/core/client.d.ts +41 -0
  10. package/dist/core/client.js +309 -0
  11. package/dist/core/config.d.ts +4 -0
  12. package/dist/core/config.js +26 -0
  13. package/dist/core/logger.d.ts +8 -0
  14. package/dist/core/logger.js +25 -0
  15. package/dist/index.d.ts +15 -0
  16. package/dist/index.js +28 -0
  17. package/dist/snapshot/NativeScreenCapture.d.ts +17 -0
  18. package/dist/snapshot/NativeScreenCapture.js +38 -0
  19. package/dist/snapshot/capture.d.ts +36 -0
  20. package/dist/snapshot/capture.js +109 -0
  21. package/dist/snapshot/captureScreenshot.d.ts +26 -0
  22. package/dist/snapshot/captureScreenshot.js +89 -0
  23. package/dist/trackers/error.tracker.d.ts +22 -0
  24. package/dist/trackers/error.tracker.js +123 -0
  25. package/dist/trackers/navigation.tracker.d.ts +6 -0
  26. package/dist/trackers/navigation.tracker.js +78 -0
  27. package/dist/trackers/screen.tracker.d.ts +2 -0
  28. package/dist/trackers/screen.tracker.js +23 -0
  29. package/dist/trackers/touch.tracker.d.ts +8 -0
  30. package/dist/trackers/touch.tracker.js +30 -0
  31. package/dist/transport/api.client.d.ts +29 -0
  32. package/dist/transport/api.client.js +156 -0
  33. package/dist/transport/batch-queue.d.ts +18 -0
  34. package/dist/transport/batch-queue.js +80 -0
  35. package/dist/types/crash.d.ts +10 -0
  36. package/dist/types/crash.js +2 -0
  37. package/dist/types/events.d.ts +53 -0
  38. package/dist/types/events.js +2 -0
  39. package/dist/types/snapshot.d.ts +21 -0
  40. package/dist/types/snapshot.js +9 -0
  41. package/package.json +64 -0
  42. package/src/components/ScreenCapturePermission.tsx +160 -0
  43. package/src/core/client.ts +425 -0
  44. package/src/core/config.ts +27 -0
  45. package/src/core/logger.ts +27 -0
  46. package/src/index.ts +47 -0
  47. package/src/snapshot/NativeScreenCapture.ts +54 -0
  48. package/src/snapshot/capture.ts +142 -0
  49. package/src/snapshot/captureScreenshot.ts +109 -0
  50. package/src/trackers/error.tracker.ts +136 -0
  51. package/src/trackers/navigation.tracker.ts +98 -0
  52. package/src/trackers/screen.tracker.ts +19 -0
  53. package/src/trackers/touch.tracker.tsx +44 -0
  54. package/src/transport/api.client.ts +225 -0
  55. package/src/transport/batch-queue.ts +95 -0
  56. package/src/types/crash.ts +10 -0
  57. package/src/types/events.ts +59 -0
  58. package/src/types/snapshot.ts +23 -0
@@ -0,0 +1,24 @@
1
+ def safeExtGet(prop, fallback) {
2
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
3
+ }
4
+
5
+ apply plugin: 'com.android.library'
6
+
7
+ android {
8
+ namespace 'com.repliqo.screencapture'
9
+ compileSdkVersion safeExtGet('compileSdkVersion', 34)
10
+
11
+ defaultConfig {
12
+ minSdkVersion safeExtGet('minSdkVersion', 21)
13
+ targetSdkVersion safeExtGet('targetSdkVersion', 34)
14
+ }
15
+
16
+ compileOptions {
17
+ sourceCompatibility JavaVersion.VERSION_1_8
18
+ targetCompatibility JavaVersion.VERSION_1_8
19
+ }
20
+ }
21
+
22
+ dependencies {
23
+ compileOnly 'com.facebook.react:react-android:+'
24
+ }
@@ -0,0 +1,4 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <!-- No permissions needed. Multi-window capture uses View.draw()
3
+ to composite all app windows. No MediaProjection, no service. -->
4
+ </manifest>
@@ -0,0 +1,230 @@
1
+ package com.repliqo.screencapture;
2
+
3
+ import android.app.Activity;
4
+ import android.graphics.Bitmap;
5
+ import android.graphics.Canvas;
6
+ import android.graphics.Rect;
7
+ import android.os.Build;
8
+ import android.util.Base64;
9
+ import android.util.Log;
10
+ import android.view.View;
11
+ import android.view.WindowManager;
12
+
13
+ import java.io.ByteArrayOutputStream;
14
+ import java.lang.reflect.Field;
15
+ import java.util.ArrayList;
16
+ import java.util.List;
17
+
18
+ /**
19
+ * Captures ALL visible windows of the app (main view + modals + alerts +
20
+ * dialogs + toasts) by accessing Android's internal WindowManagerGlobal
21
+ * via reflection.
22
+ *
23
+ * How it works:
24
+ * 1. WindowManagerGlobal keeps a list of all root views (mViews) and
25
+ * their layout params (mParams) for the current process.
26
+ * 2. We iterate over all root views, filter for visible ones, and
27
+ * draw each one to a Bitmap using View.draw().
28
+ * 3. We composite the bitmaps in z-order onto a single canvas.
29
+ * 4. The result is a complete screenshot including modals, alerts,
30
+ * system dialogs, etc. — anything that's a Window in the app.
31
+ *
32
+ * NO permissions required. NO MediaProjection. NO foreground service.
33
+ * This is the same technique used by Facebook's screenshot-tests library.
34
+ */
35
+ public class MultiWindowCapture {
36
+ private static final String TAG = "RepliqoCapture";
37
+ private static final int TARGET_WIDTH = 390;
38
+ private static final int JPEG_QUALITY = 40;
39
+
40
+ /**
41
+ * Capture all visible windows composited into a single JPEG.
42
+ *
43
+ * Must be called on the UI thread.
44
+ *
45
+ * @param activity The current Activity (used for screen dimensions)
46
+ * @return base64-encoded JPEG, or null on failure
47
+ */
48
+ public static CaptureResult capture(Activity activity) {
49
+ if (activity == null || activity.isFinishing()) {
50
+ return null;
51
+ }
52
+
53
+ try {
54
+ // Get screen dimensions from the activity's window
55
+ View decorView = activity.getWindow().getDecorView();
56
+ int screenWidth = decorView.getWidth();
57
+ int screenHeight = decorView.getHeight();
58
+
59
+ if (screenWidth <= 0 || screenHeight <= 0) {
60
+ return null;
61
+ }
62
+
63
+ // Calculate output dimensions (scale to TARGET_WIDTH)
64
+ float ratio = (float) screenHeight / screenWidth;
65
+ int outWidth = Math.min(TARGET_WIDTH, screenWidth);
66
+ int outHeight = Math.round(outWidth * ratio);
67
+ outWidth = outWidth & ~1; // Ensure even
68
+ outHeight = outHeight & ~1;
69
+
70
+ float scale = (float) outWidth / screenWidth;
71
+
72
+ // Get all root views via reflection
73
+ List<ViewInfo> views = getRootViews();
74
+
75
+ if (views.isEmpty()) {
76
+ // Fallback: just capture the decor view
77
+ return captureDecorView(decorView, outWidth, outHeight, scale);
78
+ }
79
+
80
+ // Create the composite bitmap
81
+ Bitmap composite = Bitmap.createBitmap(outWidth, outHeight,
82
+ Bitmap.Config.ARGB_8888);
83
+ Canvas canvas = new Canvas(composite);
84
+ canvas.scale(scale, scale);
85
+
86
+ // Draw each visible root view in order (bottom to top)
87
+ for (ViewInfo info : views) {
88
+ if (info.view.getVisibility() != View.VISIBLE) continue;
89
+ if (info.view.getWidth() <= 0 || info.view.getHeight() <= 0) continue;
90
+
91
+ canvas.save();
92
+
93
+ // Apply the window's position offset
94
+ canvas.translate(info.left, info.top);
95
+
96
+ try {
97
+ info.view.draw(canvas);
98
+ } catch (Exception e) {
99
+ Log.w(TAG, "Failed to draw view: " + e.getMessage());
100
+ }
101
+
102
+ canvas.restore();
103
+ }
104
+
105
+ // Compress to JPEG
106
+ String base64 = bitmapToBase64(composite);
107
+ composite.recycle();
108
+
109
+ if (base64 == null) return null;
110
+
111
+ return new CaptureResult(base64, outWidth, outHeight);
112
+
113
+ } catch (Exception e) {
114
+ Log.w(TAG, "MultiWindowCapture failed: " + e.getMessage());
115
+ return null;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Fallback: capture just the activity's decor view.
121
+ */
122
+ private static CaptureResult captureDecorView(
123
+ View decorView, int outWidth, int outHeight, float scale) {
124
+ try {
125
+ Bitmap bitmap = Bitmap.createBitmap(outWidth, outHeight,
126
+ Bitmap.Config.ARGB_8888);
127
+ Canvas canvas = new Canvas(bitmap);
128
+ canvas.scale(scale, scale);
129
+ decorView.draw(canvas);
130
+
131
+ String base64 = bitmapToBase64(bitmap);
132
+ bitmap.recycle();
133
+
134
+ if (base64 == null) return null;
135
+ return new CaptureResult(base64, outWidth, outHeight);
136
+ } catch (Exception e) {
137
+ Log.w(TAG, "DecorView capture failed: " + e.getMessage());
138
+ return null;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Get all root views in the current process via WindowManagerGlobal.
144
+ * Uses reflection because this is an internal API.
145
+ */
146
+ @SuppressWarnings("unchecked")
147
+ private static List<ViewInfo> getRootViews() {
148
+ List<ViewInfo> result = new ArrayList<>();
149
+
150
+ try {
151
+ // Get WindowManagerGlobal.getInstance()
152
+ Class<?> wmgClass = Class.forName("android.view.WindowManagerGlobal");
153
+ Object wmg = wmgClass.getMethod("getInstance").invoke(null);
154
+
155
+ // Get mViews (ArrayList<View>)
156
+ Field viewsField = wmgClass.getDeclaredField("mViews");
157
+ viewsField.setAccessible(true);
158
+ ArrayList<View> views = (ArrayList<View>) viewsField.get(wmg);
159
+
160
+ // Get mParams (ArrayList<WindowManager.LayoutParams>)
161
+ Field paramsField = wmgClass.getDeclaredField("mParams");
162
+ paramsField.setAccessible(true);
163
+ ArrayList<WindowManager.LayoutParams> params =
164
+ (ArrayList<WindowManager.LayoutParams>) paramsField.get(wmg);
165
+
166
+ if (views == null || params == null) return result;
167
+
168
+ int count = Math.min(views.size(), params.size());
169
+ for (int i = 0; i < count; i++) {
170
+ View view = views.get(i);
171
+ WindowManager.LayoutParams lp = params.get(i);
172
+
173
+ if (view == null || lp == null) continue;
174
+
175
+ int left = lp.x;
176
+ int top = lp.y;
177
+
178
+ // For MATCH_PARENT windows (most modals), position is 0,0
179
+ if (lp.width == WindowManager.LayoutParams.MATCH_PARENT) {
180
+ left = 0;
181
+ }
182
+ if (lp.height == WindowManager.LayoutParams.MATCH_PARENT) {
183
+ top = 0;
184
+ }
185
+
186
+ result.add(new ViewInfo(view, left, top));
187
+ }
188
+ } catch (Exception e) {
189
+ Log.w(TAG, "getRootViews reflection failed: " + e.getMessage());
190
+ }
191
+
192
+ return result;
193
+ }
194
+
195
+ private static String bitmapToBase64(Bitmap bitmap) {
196
+ try {
197
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
198
+ bitmap.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, baos);
199
+ return Base64.encodeToString(baos.toByteArray(), Base64.NO_WRAP);
200
+ } catch (Exception e) {
201
+ return null;
202
+ }
203
+ }
204
+
205
+ /** Container for a root view and its screen position. */
206
+ private static class ViewInfo {
207
+ final View view;
208
+ final int left;
209
+ final int top;
210
+
211
+ ViewInfo(View view, int left, int top) {
212
+ this.view = view;
213
+ this.left = left;
214
+ this.top = top;
215
+ }
216
+ }
217
+
218
+ /** Result of a capture: base64 JPEG + dimensions. */
219
+ public static class CaptureResult {
220
+ public final String image;
221
+ public final int width;
222
+ public final int height;
223
+
224
+ CaptureResult(String image, int width, int height) {
225
+ this.image = image;
226
+ this.width = width;
227
+ this.height = height;
228
+ }
229
+ }
230
+ }
@@ -0,0 +1,94 @@
1
+ package com.repliqo.screencapture;
2
+
3
+ import android.app.Activity;
4
+ import android.os.Handler;
5
+ import android.os.Looper;
6
+ import android.util.Log;
7
+
8
+ import androidx.annotation.NonNull;
9
+
10
+ import com.facebook.react.bridge.Arguments;
11
+ import com.facebook.react.bridge.Promise;
12
+ import com.facebook.react.bridge.ReactApplicationContext;
13
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
14
+ import com.facebook.react.bridge.ReactMethod;
15
+ import com.facebook.react.bridge.WritableMap;
16
+
17
+ /**
18
+ * React Native native module for full-screen capture INCLUDING modals,
19
+ * alerts, and system dialogs.
20
+ *
21
+ * Uses MultiWindowCapture which draws all app windows via View.draw()
22
+ * and composites them. NO permissions required. NO MediaProjection.
23
+ * NO foreground service.
24
+ *
25
+ * JS API:
26
+ * NativeModules.ScreenCaptureModule.captureFrame() → Promise<{image,width,height,format}>
27
+ *
28
+ * The TS wrapper (NativeScreenCapture.ts) detects availability via
29
+ * NativeModules.ScreenCaptureModule != null, so isAvailable() is kept
30
+ * for explicit checks but not required.
31
+ */
32
+ public class ScreenCaptureModule extends ReactContextBaseJavaModule {
33
+ private static final String TAG = "RepliqoCapture";
34
+ private static final String MODULE_NAME = "ScreenCaptureModule";
35
+
36
+ private final Handler mainHandler = new Handler(Looper.getMainLooper());
37
+
38
+ public ScreenCaptureModule(ReactApplicationContext reactContext) {
39
+ super(reactContext);
40
+ }
41
+
42
+ @NonNull
43
+ @Override
44
+ public String getName() {
45
+ return MODULE_NAME;
46
+ }
47
+
48
+ /**
49
+ * Check if the native capture module is available.
50
+ * Always true on Android (no permissions needed).
51
+ */
52
+ @ReactMethod
53
+ public void isAvailable(Promise promise) {
54
+ promise.resolve(true);
55
+ }
56
+
57
+ /**
58
+ * Capture a single frame including all visible windows.
59
+ * Must run View.draw() on the UI thread, so we post to the main handler.
60
+ *
61
+ * Returns: { image: base64, width: int, height: int, format: "jpeg" }
62
+ */
63
+ @ReactMethod
64
+ public void captureFrame(Promise promise) {
65
+ Activity activity = getCurrentActivity();
66
+ if (activity == null) {
67
+ promise.resolve(null);
68
+ return;
69
+ }
70
+
71
+ // View.draw() must run on the UI thread
72
+ mainHandler.post(() -> {
73
+ try {
74
+ MultiWindowCapture.CaptureResult result =
75
+ MultiWindowCapture.capture(activity);
76
+
77
+ if (result == null) {
78
+ promise.resolve(null);
79
+ return;
80
+ }
81
+
82
+ WritableMap map = Arguments.createMap();
83
+ map.putString("image", result.image);
84
+ map.putInt("width", result.width);
85
+ map.putInt("height", result.height);
86
+ map.putString("format", "jpeg");
87
+ promise.resolve(map);
88
+ } catch (Exception e) {
89
+ Log.w(TAG, "captureFrame failed: " + e.getMessage());
90
+ promise.resolve(null);
91
+ }
92
+ });
93
+ }
94
+ }
@@ -0,0 +1,38 @@
1
+ package com.repliqo.screencapture;
2
+
3
+ import androidx.annotation.NonNull;
4
+
5
+ import com.facebook.react.ReactPackage;
6
+ import com.facebook.react.bridge.NativeModule;
7
+ import com.facebook.react.bridge.ReactApplicationContext;
8
+ import com.facebook.react.uimanager.ViewManager;
9
+
10
+ import java.util.ArrayList;
11
+ import java.util.Collections;
12
+ import java.util.List;
13
+
14
+ /**
15
+ * React Native package that registers the ScreenCaptureModule.
16
+ *
17
+ * The host app must add this package to its getPackages() list
18
+ * in MainApplication.java. With autolinking (RN 0.60+), this
19
+ * should be handled automatically.
20
+ */
21
+ public class ScreenCapturePackage implements ReactPackage {
22
+
23
+ @NonNull
24
+ @Override
25
+ public List<NativeModule> createNativeModules(
26
+ @NonNull ReactApplicationContext reactContext) {
27
+ List<NativeModule> modules = new ArrayList<>();
28
+ modules.add(new ScreenCaptureModule(reactContext));
29
+ return modules;
30
+ }
31
+
32
+ @NonNull
33
+ @Override
34
+ public List<ViewManager> createViewManagers(
35
+ @NonNull ReactApplicationContext reactContext) {
36
+ return Collections.emptyList();
37
+ }
38
+ }
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import { ViewStyle, TextStyle } from 'react-native';
3
+ interface ScreenCapturePermissionProps {
4
+ /**
5
+ * Called when the user taps "Allow" — the host app should then call
6
+ * `AppAnalytics.getInstance().startSession(...)` which triggers the
7
+ * actual system permission dialog.
8
+ */
9
+ onAccept: () => void;
10
+ /**
11
+ * Called when the user taps "Not now" — the host app can proceed
12
+ * without screen capture (view-shot fallback will be used).
13
+ */
14
+ onDecline: () => void;
15
+ /** Optional custom title. */
16
+ title?: string;
17
+ /** Optional custom description. */
18
+ description?: string;
19
+ /** Override container (overlay) style. */
20
+ containerStyle?: ViewStyle;
21
+ /** Override card style. */
22
+ cardStyle?: ViewStyle;
23
+ /** Override title text style. */
24
+ titleStyle?: TextStyle;
25
+ /** Override description text style. */
26
+ descriptionStyle?: TextStyle;
27
+ /** Override accept button style. */
28
+ acceptButtonStyle?: ViewStyle;
29
+ /** Override decline button style. */
30
+ declineButtonStyle?: ViewStyle;
31
+ }
32
+ /**
33
+ * Pre-permission explanation screen for session replay.
34
+ *
35
+ * Android requires a system-level permission dialog for screen capture
36
+ * (MediaProjection), which can be confusing without context. Show this
37
+ * component BEFORE the system dialog to explain why the permission is
38
+ * needed.
39
+ *
40
+ * Usage in the host app:
41
+ *
42
+ * ```tsx
43
+ * import { ScreenCapturePermission } from '@repliqo/sdk-react-native';
44
+ *
45
+ * <ScreenCapturePermission
46
+ * onAccept={() => initRepliqo()}
47
+ * onDecline={() => initRepliqoWithoutCapture()}
48
+ * />
49
+ * ```
50
+ *
51
+ * This component is OPTIONAL. If not shown, the SDK still works — the
52
+ * system dialog appears directly, and denial falls back to view-shot.
53
+ */
54
+ export declare function ScreenCapturePermission({ onAccept, onDecline, title, description, containerStyle, cardStyle, titleStyle, descriptionStyle, acceptButtonStyle, declineButtonStyle, }: ScreenCapturePermissionProps): React.JSX.Element | null;
55
+ export {};
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ScreenCapturePermission = ScreenCapturePermission;
7
+ const react_1 = __importDefault(require("react"));
8
+ const react_native_1 = require("react-native");
9
+ /**
10
+ * Pre-permission explanation screen for session replay.
11
+ *
12
+ * Android requires a system-level permission dialog for screen capture
13
+ * (MediaProjection), which can be confusing without context. Show this
14
+ * component BEFORE the system dialog to explain why the permission is
15
+ * needed.
16
+ *
17
+ * Usage in the host app:
18
+ *
19
+ * ```tsx
20
+ * import { ScreenCapturePermission } from '@repliqo/sdk-react-native';
21
+ *
22
+ * <ScreenCapturePermission
23
+ * onAccept={() => initRepliqo()}
24
+ * onDecline={() => initRepliqoWithoutCapture()}
25
+ * />
26
+ * ```
27
+ *
28
+ * This component is OPTIONAL. If not shown, the SDK still works — the
29
+ * system dialog appears directly, and denial falls back to view-shot.
30
+ */
31
+ function ScreenCapturePermission({ onAccept, onDecline, title, description, containerStyle, cardStyle, titleStyle, descriptionStyle, acceptButtonStyle, declineButtonStyle, }) {
32
+ if (react_native_1.Platform.OS !== 'android') {
33
+ return null;
34
+ }
35
+ return (<react_native_1.View style={[styles.container, containerStyle]}>
36
+ <react_native_1.View style={[styles.card, cardStyle]}>
37
+ <react_native_1.Text style={styles.icon}>📹</react_native_1.Text>
38
+ <react_native_1.Text style={[styles.title, titleStyle]}>
39
+ {title || 'Session Recording'}
40
+ </react_native_1.Text>
41
+ <react_native_1.Text style={[styles.description, descriptionStyle]}>
42
+ {description ||
43
+ 'To capture your full session including dialogs and menus, ' +
44
+ 'we need screen recording permission. This helps us improve ' +
45
+ 'your experience by understanding how you use the app.\n\n' +
46
+ 'You can stop recording at any time from the notification bar.'}
47
+ </react_native_1.Text>
48
+ <react_native_1.TouchableOpacity style={[styles.acceptButton, acceptButtonStyle]} onPress={onAccept}>
49
+ <react_native_1.Text style={styles.acceptText}>Allow Recording</react_native_1.Text>
50
+ </react_native_1.TouchableOpacity>
51
+ <react_native_1.TouchableOpacity style={[styles.declineButton, declineButtonStyle]} onPress={onDecline}>
52
+ <react_native_1.Text style={styles.declineText}>Not Now</react_native_1.Text>
53
+ </react_native_1.TouchableOpacity>
54
+ </react_native_1.View>
55
+ </react_native_1.View>);
56
+ }
57
+ const styles = react_native_1.StyleSheet.create({
58
+ container: {
59
+ flex: 1,
60
+ justifyContent: 'center',
61
+ alignItems: 'center',
62
+ backgroundColor: 'rgba(0,0,0,0.5)',
63
+ padding: 24,
64
+ },
65
+ card: {
66
+ backgroundColor: '#1a1a2e',
67
+ borderRadius: 16,
68
+ padding: 24,
69
+ width: '100%',
70
+ maxWidth: 340,
71
+ alignItems: 'center',
72
+ },
73
+ icon: {
74
+ fontSize: 48,
75
+ marginBottom: 16,
76
+ },
77
+ title: {
78
+ fontSize: 20,
79
+ fontWeight: '700',
80
+ color: '#ffffff',
81
+ marginBottom: 12,
82
+ textAlign: 'center',
83
+ },
84
+ description: {
85
+ fontSize: 14,
86
+ color: '#a0a0b8',
87
+ lineHeight: 22,
88
+ textAlign: 'center',
89
+ marginBottom: 24,
90
+ },
91
+ acceptButton: {
92
+ backgroundColor: '#6366f1',
93
+ borderRadius: 12,
94
+ paddingVertical: 14,
95
+ paddingHorizontal: 32,
96
+ width: '100%',
97
+ alignItems: 'center',
98
+ marginBottom: 12,
99
+ },
100
+ acceptText: {
101
+ color: '#ffffff',
102
+ fontSize: 16,
103
+ fontWeight: '600',
104
+ },
105
+ declineButton: {
106
+ paddingVertical: 10,
107
+ },
108
+ declineText: {
109
+ color: '#6b7280',
110
+ fontSize: 14,
111
+ },
112
+ });
@@ -0,0 +1,41 @@
1
+ import { DeviceInfo, SDKConfig } from '../types/events';
2
+ export declare class AppAnalytics {
3
+ private static instance;
4
+ private config;
5
+ private apiClient;
6
+ private eventQueue;
7
+ private screenVisitQueue;
8
+ private snapshotQueue;
9
+ private snapshotCapture;
10
+ private errorTracker;
11
+ private sessionId;
12
+ private currentScreen;
13
+ private currentScreenEnteredAt;
14
+ private logger;
15
+ private appStateSubscription;
16
+ private constructor();
17
+ static init(config: SDKConfig): AppAnalytics;
18
+ static getInstance(): AppAnalytics;
19
+ static destroy(): void;
20
+ startSession(deviceId: string, deviceInfo?: DeviceInfo): Promise<string>;
21
+ endSession(): Promise<void>;
22
+ trackTouch(x: number, y: number, screenName?: string, extra?: Record<string, any>): void;
23
+ trackNavigation(fromScreen: string, toScreen: string): void;
24
+ trackCustomEvent(eventName: string, data?: Record<string, any>): void;
25
+ reportError(error: Error, metadata?: Record<string, any>): void;
26
+ onScreenEnter(screenName: string): void;
27
+ onScreenExit(screenName: string): void;
28
+ flush(): Promise<void>;
29
+ getSessionId(): string | null;
30
+ getCurrentScreen(): string | null;
31
+ /**
32
+ * Force an immediate screenshot capture, outside the periodic schedule.
33
+ * Useful for capturing specific moments (e.g. right after a critical
34
+ * user action). Respects the in-flight lock and session cap.
35
+ */
36
+ captureSnapshot(screenName?: string): Promise<void>;
37
+ startSnapshotCapture(): void;
38
+ stopSnapshotCapture(): void;
39
+ isInitialized(): boolean;
40
+ private setupAppStateListener;
41
+ }