@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
|
@@ -1,230 +1,242 @@
|
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// Get
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (lp
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
this.
|
|
226
|
-
this.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
+
// Happens during layout transitions / before first layout.
|
|
61
|
+
// Log so a persistent capture failure is diagnosable.
|
|
62
|
+
Log.w(TAG, "capture skipped: invalid decor dimensions "
|
|
63
|
+
+ screenWidth + "x" + screenHeight);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Calculate output dimensions (scale to TARGET_WIDTH)
|
|
68
|
+
float ratio = (float) screenHeight / screenWidth;
|
|
69
|
+
int outWidth = Math.min(TARGET_WIDTH, screenWidth);
|
|
70
|
+
int outHeight = Math.round(outWidth * ratio);
|
|
71
|
+
outWidth = outWidth & ~1; // Ensure even
|
|
72
|
+
outHeight = outHeight & ~1;
|
|
73
|
+
|
|
74
|
+
float scale = (float) outWidth / screenWidth;
|
|
75
|
+
|
|
76
|
+
// Get all root views via reflection
|
|
77
|
+
List<ViewInfo> views = getRootViews();
|
|
78
|
+
|
|
79
|
+
if (views.isEmpty()) {
|
|
80
|
+
// Fallback: just capture the decor view
|
|
81
|
+
return captureDecorView(decorView, outWidth, outHeight, scale);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Create the composite bitmap. Recycled in finally so it never
|
|
85
|
+
// leaks if compression (bitmapToBase64) or anything else throws.
|
|
86
|
+
Bitmap composite = Bitmap.createBitmap(outWidth, outHeight,
|
|
87
|
+
Bitmap.Config.ARGB_8888);
|
|
88
|
+
try {
|
|
89
|
+
Canvas canvas = new Canvas(composite);
|
|
90
|
+
canvas.scale(scale, scale);
|
|
91
|
+
|
|
92
|
+
// Draw each visible root view in order (bottom to top)
|
|
93
|
+
for (ViewInfo info : views) {
|
|
94
|
+
if (info.view.getVisibility() != View.VISIBLE) continue;
|
|
95
|
+
if (info.view.getWidth() <= 0 || info.view.getHeight() <= 0) continue;
|
|
96
|
+
|
|
97
|
+
canvas.save();
|
|
98
|
+
|
|
99
|
+
// Apply the window's position offset
|
|
100
|
+
canvas.translate(info.left, info.top);
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
info.view.draw(canvas);
|
|
104
|
+
} catch (Exception e) {
|
|
105
|
+
Log.w(TAG, "Failed to draw view: " + e.getMessage());
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
canvas.restore();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Compress to JPEG
|
|
112
|
+
String base64 = bitmapToBase64(composite);
|
|
113
|
+
if (base64 == null) return null;
|
|
114
|
+
|
|
115
|
+
return new CaptureResult(base64, outWidth, outHeight);
|
|
116
|
+
} finally {
|
|
117
|
+
composite.recycle();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
} catch (Exception e) {
|
|
121
|
+
Log.w(TAG, "MultiWindowCapture failed: " + e.getMessage());
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Fallback: capture just the activity's decor view.
|
|
128
|
+
*/
|
|
129
|
+
private static CaptureResult captureDecorView(
|
|
130
|
+
View decorView, int outWidth, int outHeight, float scale) {
|
|
131
|
+
Bitmap bitmap = null;
|
|
132
|
+
try {
|
|
133
|
+
bitmap = Bitmap.createBitmap(outWidth, outHeight,
|
|
134
|
+
Bitmap.Config.ARGB_8888);
|
|
135
|
+
Canvas canvas = new Canvas(bitmap);
|
|
136
|
+
canvas.scale(scale, scale);
|
|
137
|
+
decorView.draw(canvas);
|
|
138
|
+
|
|
139
|
+
String base64 = bitmapToBase64(bitmap);
|
|
140
|
+
if (base64 == null) return null;
|
|
141
|
+
return new CaptureResult(base64, outWidth, outHeight);
|
|
142
|
+
} catch (Exception e) {
|
|
143
|
+
Log.w(TAG, "DecorView capture failed: " + e.getMessage());
|
|
144
|
+
return null;
|
|
145
|
+
} finally {
|
|
146
|
+
// Always recycle, even if bitmapToBase64 / draw threw.
|
|
147
|
+
if (bitmap != null) bitmap.recycle();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get all root views in the current process via WindowManagerGlobal.
|
|
153
|
+
* Uses reflection because this is an internal API.
|
|
154
|
+
*/
|
|
155
|
+
@SuppressWarnings("unchecked")
|
|
156
|
+
private static List<ViewInfo> getRootViews() {
|
|
157
|
+
List<ViewInfo> result = new ArrayList<>();
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
// Get WindowManagerGlobal.getInstance()
|
|
161
|
+
Class<?> wmgClass = Class.forName("android.view.WindowManagerGlobal");
|
|
162
|
+
Object wmg = wmgClass.getMethod("getInstance").invoke(null);
|
|
163
|
+
|
|
164
|
+
// Get mViews (ArrayList<View>)
|
|
165
|
+
Field viewsField = wmgClass.getDeclaredField("mViews");
|
|
166
|
+
viewsField.setAccessible(true);
|
|
167
|
+
ArrayList<View> views = (ArrayList<View>) viewsField.get(wmg);
|
|
168
|
+
|
|
169
|
+
// Get mParams (ArrayList<WindowManager.LayoutParams>)
|
|
170
|
+
Field paramsField = wmgClass.getDeclaredField("mParams");
|
|
171
|
+
paramsField.setAccessible(true);
|
|
172
|
+
ArrayList<WindowManager.LayoutParams> params =
|
|
173
|
+
(ArrayList<WindowManager.LayoutParams>) paramsField.get(wmg);
|
|
174
|
+
|
|
175
|
+
if (views == null || params == null) return result;
|
|
176
|
+
|
|
177
|
+
int count = Math.min(views.size(), params.size());
|
|
178
|
+
for (int i = 0; i < count; i++) {
|
|
179
|
+
View view = views.get(i);
|
|
180
|
+
WindowManager.LayoutParams lp = params.get(i);
|
|
181
|
+
|
|
182
|
+
if (view == null || lp == null) continue;
|
|
183
|
+
|
|
184
|
+
int left = lp.x;
|
|
185
|
+
int top = lp.y;
|
|
186
|
+
|
|
187
|
+
// For MATCH_PARENT windows (most modals), position is 0,0
|
|
188
|
+
if (lp.width == WindowManager.LayoutParams.MATCH_PARENT) {
|
|
189
|
+
left = 0;
|
|
190
|
+
}
|
|
191
|
+
if (lp.height == WindowManager.LayoutParams.MATCH_PARENT) {
|
|
192
|
+
top = 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
result.add(new ViewInfo(view, left, top));
|
|
196
|
+
}
|
|
197
|
+
} catch (Exception e) {
|
|
198
|
+
Log.w(TAG, "getRootViews reflection failed: " + e.getMessage());
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private static String bitmapToBase64(Bitmap bitmap) {
|
|
205
|
+
try {
|
|
206
|
+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
207
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, baos);
|
|
208
|
+
return Base64.encodeToString(baos.toByteArray(), Base64.NO_WRAP);
|
|
209
|
+
} catch (Throwable e) {
|
|
210
|
+
// Catch Throwable so OutOfMemoryError (not an Exception) is also
|
|
211
|
+
// surfaced rather than silently producing a null capture.
|
|
212
|
+
Log.w(TAG, "JPEG compression failed: " + e.getMessage());
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Container for a root view and its screen position. */
|
|
218
|
+
private static class ViewInfo {
|
|
219
|
+
final View view;
|
|
220
|
+
final int left;
|
|
221
|
+
final int top;
|
|
222
|
+
|
|
223
|
+
ViewInfo(View view, int left, int top) {
|
|
224
|
+
this.view = view;
|
|
225
|
+
this.left = left;
|
|
226
|
+
this.top = top;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Result of a capture: base64 JPEG + dimensions. */
|
|
231
|
+
public static class CaptureResult {
|
|
232
|
+
public final String image;
|
|
233
|
+
public final int width;
|
|
234
|
+
public final int height;
|
|
235
|
+
|
|
236
|
+
CaptureResult(String image, int width, int height) {
|
|
237
|
+
this.image = image;
|
|
238
|
+
this.width = width;
|
|
239
|
+
this.height = height;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|