@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.
- package/INTEGRATION_GUIDE.md +1312 -0
- package/android/build.gradle +24 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/repliqo/screencapture/MultiWindowCapture.java +230 -0
- package/android/src/main/java/com/repliqo/screencapture/ScreenCaptureModule.java +94 -0
- package/android/src/main/java/com/repliqo/screencapture/ScreenCapturePackage.java +38 -0
- package/dist/components/ScreenCapturePermission.d.ts +55 -0
- package/dist/components/ScreenCapturePermission.js +112 -0
- package/dist/core/client.d.ts +41 -0
- package/dist/core/client.js +309 -0
- package/dist/core/config.d.ts +4 -0
- package/dist/core/config.js +26 -0
- package/dist/core/logger.d.ts +8 -0
- package/dist/core/logger.js +25 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +28 -0
- package/dist/snapshot/NativeScreenCapture.d.ts +17 -0
- package/dist/snapshot/NativeScreenCapture.js +38 -0
- package/dist/snapshot/capture.d.ts +36 -0
- package/dist/snapshot/capture.js +109 -0
- package/dist/snapshot/captureScreenshot.d.ts +26 -0
- package/dist/snapshot/captureScreenshot.js +89 -0
- package/dist/trackers/error.tracker.d.ts +22 -0
- package/dist/trackers/error.tracker.js +123 -0
- package/dist/trackers/navigation.tracker.d.ts +6 -0
- package/dist/trackers/navigation.tracker.js +78 -0
- package/dist/trackers/screen.tracker.d.ts +2 -0
- package/dist/trackers/screen.tracker.js +23 -0
- package/dist/trackers/touch.tracker.d.ts +8 -0
- package/dist/trackers/touch.tracker.js +30 -0
- package/dist/transport/api.client.d.ts +29 -0
- package/dist/transport/api.client.js +156 -0
- package/dist/transport/batch-queue.d.ts +18 -0
- package/dist/transport/batch-queue.js +80 -0
- package/dist/types/crash.d.ts +10 -0
- package/dist/types/crash.js +2 -0
- package/dist/types/events.d.ts +53 -0
- package/dist/types/events.js +2 -0
- package/dist/types/snapshot.d.ts +21 -0
- package/dist/types/snapshot.js +9 -0
- package/package.json +64 -0
- package/src/components/ScreenCapturePermission.tsx +160 -0
- package/src/core/client.ts +425 -0
- package/src/core/config.ts +27 -0
- package/src/core/logger.ts +27 -0
- package/src/index.ts +47 -0
- package/src/snapshot/NativeScreenCapture.ts +54 -0
- package/src/snapshot/capture.ts +142 -0
- package/src/snapshot/captureScreenshot.ts +109 -0
- package/src/trackers/error.tracker.ts +136 -0
- package/src/trackers/navigation.tracker.ts +98 -0
- package/src/trackers/screen.tracker.ts +19 -0
- package/src/trackers/touch.tracker.tsx +44 -0
- package/src/transport/api.client.ts +225 -0
- package/src/transport/batch-queue.ts +95 -0
- package/src/types/crash.ts +10 -0
- package/src/types/events.ts +59 -0
- 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,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
|
+
}
|