@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,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
- 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
- }
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
+ }