@repliqo/sdk-react-native 0.3.7 → 0.3.8

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.
@@ -57,6 +57,10 @@ public class MultiWindowCapture {
57
57
  int screenHeight = decorView.getHeight();
58
58
 
59
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);
60
64
  return null;
61
65
  }
62
66
 
@@ -77,38 +81,41 @@ public class MultiWindowCapture {
77
81
  return captureDecorView(decorView, outWidth, outHeight, scale);
78
82
  }
79
83
 
80
- // Create the composite bitmap
84
+ // Create the composite bitmap. Recycled in finally so it never
85
+ // leaks if compression (bitmapToBase64) or anything else throws.
81
86
  Bitmap composite = Bitmap.createBitmap(outWidth, outHeight,
82
87
  Bitmap.Config.ARGB_8888);
83
- Canvas canvas = new Canvas(composite);
84
- canvas.scale(scale, scale);
88
+ try {
89
+ Canvas canvas = new Canvas(composite);
90
+ canvas.scale(scale, scale);
85
91
 
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;
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;
90
96
 
91
- canvas.save();
97
+ canvas.save();
92
98
 
93
- // Apply the window's position offset
94
- canvas.translate(info.left, info.top);
99
+ // Apply the window's position offset
100
+ canvas.translate(info.left, info.top);
95
101
 
96
- try {
97
- info.view.draw(canvas);
98
- } catch (Exception e) {
99
- Log.w(TAG, "Failed to draw view: " + e.getMessage());
100
- }
102
+ try {
103
+ info.view.draw(canvas);
104
+ } catch (Exception e) {
105
+ Log.w(TAG, "Failed to draw view: " + e.getMessage());
106
+ }
101
107
 
102
- canvas.restore();
103
- }
104
-
105
- // Compress to JPEG
106
- String base64 = bitmapToBase64(composite);
107
- composite.recycle();
108
+ canvas.restore();
109
+ }
108
110
 
109
- if (base64 == null) return null;
111
+ // Compress to JPEG
112
+ String base64 = bitmapToBase64(composite);
113
+ if (base64 == null) return null;
110
114
 
111
- return new CaptureResult(base64, outWidth, outHeight);
115
+ return new CaptureResult(base64, outWidth, outHeight);
116
+ } finally {
117
+ composite.recycle();
118
+ }
112
119
 
113
120
  } catch (Exception e) {
114
121
  Log.w(TAG, "MultiWindowCapture failed: " + e.getMessage());
@@ -121,21 +128,23 @@ public class MultiWindowCapture {
121
128
  */
122
129
  private static CaptureResult captureDecorView(
123
130
  View decorView, int outWidth, int outHeight, float scale) {
131
+ Bitmap bitmap = null;
124
132
  try {
125
- Bitmap bitmap = Bitmap.createBitmap(outWidth, outHeight,
133
+ bitmap = Bitmap.createBitmap(outWidth, outHeight,
126
134
  Bitmap.Config.ARGB_8888);
127
135
  Canvas canvas = new Canvas(bitmap);
128
136
  canvas.scale(scale, scale);
129
137
  decorView.draw(canvas);
130
138
 
131
139
  String base64 = bitmapToBase64(bitmap);
132
- bitmap.recycle();
133
-
134
140
  if (base64 == null) return null;
135
141
  return new CaptureResult(base64, outWidth, outHeight);
136
142
  } catch (Exception e) {
137
143
  Log.w(TAG, "DecorView capture failed: " + e.getMessage());
138
144
  return null;
145
+ } finally {
146
+ // Always recycle, even if bitmapToBase64 / draw threw.
147
+ if (bitmap != null) bitmap.recycle();
139
148
  }
140
149
  }
141
150
 
@@ -197,7 +206,10 @@ public class MultiWindowCapture {
197
206
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
198
207
  bitmap.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, baos);
199
208
  return Base64.encodeToString(baos.toByteArray(), Base64.NO_WRAP);
200
- } catch (Exception e) {
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());
201
213
  return null;
202
214
  }
203
215
  }
@@ -12,7 +12,6 @@ import com.facebook.react.bridge.Promise;
12
12
  import com.facebook.react.bridge.ReactApplicationContext;
13
13
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
14
14
  import com.facebook.react.bridge.ReactMethod;
15
- import com.facebook.react.bridge.WritableArray;
16
15
  import com.facebook.react.bridge.WritableMap;
17
16
 
18
17
  /**
@@ -92,79 +91,4 @@ public class ScreenCaptureModule extends ReactContextBaseJavaModule {
92
91
  }
93
92
  });
94
93
  }
95
-
96
- /**
97
- * Capture the FULL content of the primary ScrollView on screen,
98
- * including content that is scrolled off-screen.
99
- *
100
- * Used for heatmap backgrounds. Falls back to a viewport capture
101
- * if no ScrollView is found.
102
- *
103
- * Returns: { image: base64, width: int, height: int, format: "jpeg" }
104
- */
105
- @ReactMethod
106
- public void captureFullContent(Promise promise) {
107
- Activity activity = getCurrentActivity();
108
- if (activity == null) {
109
- promise.resolve(null);
110
- return;
111
- }
112
-
113
- mainHandler.post(() -> {
114
- try {
115
- FullContentCapture.FullCaptureResult result =
116
- FullContentCapture.capture(activity);
117
-
118
- if (result == null) {
119
- promise.resolve(null);
120
- return;
121
- }
122
-
123
- WritableMap map = Arguments.createMap();
124
- map.putString("image", result.image);
125
- map.putInt("width", result.width);
126
- map.putInt("height", result.height);
127
- map.putString("format", "jpeg");
128
- map.putBoolean("isFullContent", result.isFullContent);
129
- promise.resolve(map);
130
- } catch (Exception e) {
131
- Log.w(TAG, "captureFullContent failed: " + e.getMessage());
132
- promise.resolve(null);
133
- }
134
- });
135
- }
136
-
137
- /**
138
- * Perform a fast programmatic scroll-scan of the main ScrollView.
139
- * Captures the entire content as viewport tiles in ~300-500ms.
140
- * Invisible to the user (scroll is restored to original position).
141
- *
142
- * Returns: Array<{ image, width, height, scrollOffsetY, viewportHeight,
143
- * contentHeight, format }>
144
- */
145
- @ReactMethod
146
- public void scanFullContent(Promise promise) {
147
- Activity activity = getCurrentActivity();
148
- if (activity == null) {
149
- promise.resolve(null);
150
- return;
151
- }
152
-
153
- mainHandler.post(() -> {
154
- try {
155
- // Try PixelCopy (API 26+, captures from GPU framebuffer)
156
- WritableArray tiles = PixelCopyScan.scan(activity);
157
-
158
- // Fallback to View.draw scan
159
- if (tiles == null || tiles.size() == 0) {
160
- tiles = ScrollScanCapture.scan(activity);
161
- }
162
-
163
- promise.resolve(tiles);
164
- } catch (Exception e) {
165
- Log.w(TAG, "scanFullContent failed: " + e.getMessage());
166
- promise.resolve(null);
167
- }
168
- });
169
- }
170
94
  }
@@ -95,11 +95,6 @@ export declare class AppAnalytics {
95
95
  * scale computation during backend cropping.
96
96
  */
97
97
  captureScrollTile(scrollOffsetY: number, viewportHeight: number, contentHeight: number, viewportTop?: number, windowHeight?: number): void;
98
- /**
99
- * Programmatically scroll-scan the current ScrollView and upload all
100
- * tiles to the backend. Invisible to the user (~300-500ms).
101
- */
102
- private autoScanScreen;
103
98
  onScreenEnter(screenName: string): void;
104
99
  onScreenExit(screenName: string): void;
105
100
  flush(): Promise<void>;
@@ -364,30 +364,6 @@ class AppAnalytics {
364
364
  }
365
365
  })();
366
366
  }
367
- /**
368
- * Programmatically scroll-scan the current ScrollView and upload all
369
- * tiles to the backend. Invisible to the user (~300-500ms).
370
- */
371
- autoScanScreen(screenName) {
372
- (async () => {
373
- try {
374
- const tiles = await (0, NativeScreenCapture_1.scanFullContent)();
375
- if (!tiles || tiles.length === 0) {
376
- this.logger.log(`Auto-scan: no ScrollView found for "${screenName}"`);
377
- return;
378
- }
379
- this.logger.log(`Auto-scan: "${screenName}" captured ${tiles.length} tiles ` +
380
- `(content=${tiles[0].contentHeight}px)`);
381
- // Upload all tiles in parallel
382
- const sessionId = this.sessionId;
383
- await Promise.all(tiles.map((tile) => this.apiClient.uploadScreenTile(sessionId, screenName, tile.image, tile.width, tile.height, tile.scrollOffsetY, tile.viewportHeight, tile.contentHeight).catch(() => { })));
384
- this.logger.log(`Auto-scan: "${screenName}" tiles uploaded`);
385
- }
386
- catch (err) {
387
- this.logger.warn('Auto-scan failed:', err);
388
- }
389
- })();
390
- }
391
367
  onScreenEnter(screenName) {
392
368
  this.currentScreen = screenName;
393
369
  this.currentScreenEnteredAt = new Date().toISOString();
@@ -396,14 +372,12 @@ class AppAnalytics {
396
372
  this.scrollViewRegistry.clear();
397
373
  this.errorTracker?.setCurrentScreen(screenName);
398
374
  this.snapshotCapture?.setCurrentScreen(screenName);
399
- // NOTE: autoScanScreen (PixelCopy / programmatic scroll) is DISABLED.
400
- // React Native does NOT render off-screen content to the GPU or view
401
- // tree, so programmatic scroll + View.draw()/PixelCopy produces blank
402
- // tiles. Worse, those blank tiles poison the dedup cache and block
403
- // real tiles captured during natural user scroll.
404
- //
405
- // Full-content capture relies entirely on RepliqoScrollView capturing
406
- // viewport tiles as the user naturally scrolls through the content.
375
+ // NOTE: full-content capture is NOT done via programmatic scroll +
376
+ // native capture. React Native does NOT render off-screen content to
377
+ // the GPU or view tree, so any programmatic-scroll approach produces
378
+ // blank tiles. Full-content heatmap backgrounds are built entirely
379
+ // from viewport tiles captured by RepliqoScrollView as the user
380
+ // naturally scrolls through the content.
407
381
  this.logger.log('Screen entered:', screenName);
408
382
  }
409
383
  onScreenExit(screenName) {
package/dist/index.d.ts CHANGED
@@ -5,8 +5,7 @@ export { trackScreenEnter, trackScreenExit, } from './trackers/screen.tracker';
5
5
  export { ErrorTracker } from './trackers/error.tracker';
6
6
  export { SnapshotCapture } from './snapshot/capture';
7
7
  export { captureScreenshot } from './snapshot/captureScreenshot';
8
- export { isNativeScreenCaptureAvailable, captureNativeFrame, captureFullContent, } from './snapshot/NativeScreenCapture';
9
- export type { FullContentResult } from './snapshot/NativeScreenCapture';
8
+ export { isNativeScreenCaptureAvailable, captureNativeFrame, } from './snapshot/NativeScreenCapture';
10
9
  export type { EventType, AnalyticsEvent, TouchEventData, NavigationEventData, ScreenVisit, DeviceInfo, SDKConfig, } from './types/events';
11
10
  export type { ScreenshotData, SnapshotPayload, } from './types/snapshot';
12
11
  export type { CrashReport } from './types/crash';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_CONFIG = exports.RepliqoScrollView = exports.captureFullContent = exports.captureNativeFrame = exports.isNativeScreenCaptureAvailable = exports.captureScreenshot = exports.SnapshotCapture = exports.ErrorTracker = exports.trackScreenExit = exports.trackScreenEnter = exports.trackScreenChange = exports.useNavigationTracker = exports.TouchTracker = exports.AppAnalytics = void 0;
3
+ exports.DEFAULT_CONFIG = exports.RepliqoScrollView = exports.captureNativeFrame = exports.isNativeScreenCaptureAvailable = exports.captureScreenshot = exports.SnapshotCapture = exports.ErrorTracker = exports.trackScreenExit = exports.trackScreenEnter = exports.trackScreenChange = exports.useNavigationTracker = exports.TouchTracker = exports.AppAnalytics = void 0;
4
4
  // Main class
5
5
  var client_1 = require("./core/client");
6
6
  Object.defineProperty(exports, "AppAnalytics", { enumerable: true, get: function () { return client_1.AppAnalytics; } });
@@ -23,7 +23,6 @@ Object.defineProperty(exports, "captureScreenshot", { enumerable: true, get: fun
23
23
  var NativeScreenCapture_1 = require("./snapshot/NativeScreenCapture");
24
24
  Object.defineProperty(exports, "isNativeScreenCaptureAvailable", { enumerable: true, get: function () { return NativeScreenCapture_1.isNativeScreenCaptureAvailable; } });
25
25
  Object.defineProperty(exports, "captureNativeFrame", { enumerable: true, get: function () { return NativeScreenCapture_1.captureNativeFrame; } });
26
- Object.defineProperty(exports, "captureFullContent", { enumerable: true, get: function () { return NativeScreenCapture_1.captureFullContent; } });
27
26
  // Components
28
27
  var RepliqoScrollView_1 = require("./components/RepliqoScrollView");
29
28
  Object.defineProperty(exports, "RepliqoScrollView", { enumerable: true, get: function () { return RepliqoScrollView_1.RepliqoScrollView; } });
@@ -12,50 +12,12 @@ export declare function isNativeScreenCaptureAvailable(): boolean;
12
12
  *
13
13
  * No permissions required. No dialog. No foreground service.
14
14
  *
15
- * @returns NativeFrameResult or null if capture failed / not available
16
- */
17
- export declare function captureNativeFrame(): Promise<NativeFrameResult | null>;
18
- /**
19
- * Capture the FULL content of the primary ScrollView on screen,
20
- * including content scrolled off-screen. Used for heatmap backgrounds.
21
- *
22
- * Falls back to a viewport capture if no ScrollView is found.
23
- * Max height: 5000px. JPEG quality: 60%. Width: ~500px.
15
+ * This is the ONLY native capture path. Full-content capture of
16
+ * off-screen ScrollView content is NOT possible natively (React Native
17
+ * doesn't render off-screen content to the GPU/view tree) full-content
18
+ * heatmap backgrounds are instead built collaboratively from viewport
19
+ * tiles captured as the user naturally scrolls (see RepliqoScrollView).
24
20
  *
25
21
  * @returns NativeFrameResult or null if capture failed / not available
26
22
  */
27
- export interface FullContentResult extends NativeFrameResult {
28
- /** True if the capture includes full scrollable content. False if viewport-only fallback. */
29
- isFullContent: boolean;
30
- }
31
- /**
32
- * Capture the FULL content of the primary ScrollView on screen,
33
- * including content scrolled off-screen. Used for heatmap backgrounds.
34
- *
35
- * Falls back to a viewport capture if no ScrollView is found.
36
- * Max height: 5000px. JPEG quality: 60%. Width: ~500px.
37
- *
38
- * Known limitation: FlatList/SectionList use RecyclerView which only
39
- * renders visible items — full-content capture falls back to viewport.
40
- *
41
- * @returns FullContentResult with isFullContent flag, or null
42
- */
43
- export declare function captureFullContent(): Promise<FullContentResult | null>;
44
- export interface ScanTile {
45
- image: string;
46
- width: number;
47
- height: number;
48
- scrollOffsetY: number;
49
- viewportHeight: number;
50
- contentHeight: number;
51
- }
52
- /**
53
- * Perform a fast programmatic scroll-scan of the main ScrollView.
54
- * Captures the entire content as viewport tiles in ~300-500ms.
55
- *
56
- * The scan is invisible to the user — scroll position is saved and
57
- * restored. Returns an array of tiles covering the full content.
58
- *
59
- * @returns Array of tiles, or null if no ScrollView or not available
60
- */
61
- export declare function scanFullContent(): Promise<ScanTile[] | null>;
23
+ export declare function captureNativeFrame(): Promise<NativeFrameResult | null>;
@@ -2,8 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isNativeScreenCaptureAvailable = isNativeScreenCaptureAvailable;
4
4
  exports.captureNativeFrame = captureNativeFrame;
5
- exports.captureFullContent = captureFullContent;
6
- exports.scanFullContent = scanFullContent;
7
5
  const react_native_1 = require("react-native");
8
6
  const NativeModule = react_native_1.Platform.OS === 'android'
9
7
  ? react_native_1.NativeModules.ScreenCaptureModule ?? null
@@ -18,6 +16,12 @@ function isNativeScreenCaptureAvailable() {
18
16
  *
19
17
  * No permissions required. No dialog. No foreground service.
20
18
  *
19
+ * This is the ONLY native capture path. Full-content capture of
20
+ * off-screen ScrollView content is NOT possible natively (React Native
21
+ * doesn't render off-screen content to the GPU/view tree) — full-content
22
+ * heatmap backgrounds are instead built collaboratively from viewport
23
+ * tiles captured as the user naturally scrolls (see RepliqoScrollView).
24
+ *
21
25
  * @returns NativeFrameResult or null if capture failed / not available
22
26
  */
23
27
  async function captureNativeFrame() {
@@ -38,65 +42,3 @@ async function captureNativeFrame() {
38
42
  return null;
39
43
  }
40
44
  }
41
- /**
42
- * Capture the FULL content of the primary ScrollView on screen,
43
- * including content scrolled off-screen. Used for heatmap backgrounds.
44
- *
45
- * Falls back to a viewport capture if no ScrollView is found.
46
- * Max height: 5000px. JPEG quality: 60%. Width: ~500px.
47
- *
48
- * Known limitation: FlatList/SectionList use RecyclerView which only
49
- * renders visible items — full-content capture falls back to viewport.
50
- *
51
- * @returns FullContentResult with isFullContent flag, or null
52
- */
53
- async function captureFullContent() {
54
- if (!NativeModule)
55
- return null;
56
- try {
57
- const result = await NativeModule.captureFullContent();
58
- if (!result || !result.image)
59
- return null;
60
- return {
61
- image: result.image,
62
- width: result.width,
63
- height: result.height,
64
- format: 'jpeg',
65
- isFullContent: !!result.isFullContent,
66
- };
67
- }
68
- catch {
69
- return null;
70
- }
71
- }
72
- /**
73
- * Perform a fast programmatic scroll-scan of the main ScrollView.
74
- * Captures the entire content as viewport tiles in ~300-500ms.
75
- *
76
- * The scan is invisible to the user — scroll position is saved and
77
- * restored. Returns an array of tiles covering the full content.
78
- *
79
- * @returns Array of tiles, or null if no ScrollView or not available
80
- */
81
- async function scanFullContent() {
82
- if (!NativeModule)
83
- return null;
84
- try {
85
- const tiles = await NativeModule.scanFullContent();
86
- if (!tiles || !Array.isArray(tiles) || tiles.length === 0)
87
- return null;
88
- return tiles
89
- .filter((t) => t && t.image)
90
- .map((t) => ({
91
- image: t.image,
92
- width: t.width,
93
- height: t.height,
94
- scrollOffsetY: t.scrollOffsetY,
95
- viewportHeight: t.viewportHeight,
96
- contentHeight: t.contentHeight,
97
- }));
98
- }
99
- catch {
100
- return null;
101
- }
102
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@repliqo/sdk-react-native",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "Session replay & analytics SDK for React Native. Captures screenshots (including modals/alerts), navigation, screen visits, and custom events.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,7 +12,7 @@ import { Logger } from './logger';
12
12
  import { ApiClient } from '../transport/api.client';
13
13
  import { BatchQueue } from '../transport/batch-queue';
14
14
  import { ErrorTracker } from '../trackers/error.tracker';
15
- import { captureNativeFrame, scanFullContent } from '../snapshot/NativeScreenCapture';
15
+ import { captureNativeFrame } from '../snapshot/NativeScreenCapture';
16
16
 
17
17
  export class AppAnalytics {
18
18
  private static instance: AppAnalytics | null = null;
@@ -499,48 +499,6 @@ export class AppAnalytics {
499
499
  })();
500
500
  }
501
501
 
502
- /**
503
- * Programmatically scroll-scan the current ScrollView and upload all
504
- * tiles to the backend. Invisible to the user (~300-500ms).
505
- */
506
- private autoScanScreen(screenName: string): void {
507
- (async () => {
508
- try {
509
- const tiles = await scanFullContent();
510
- if (!tiles || tiles.length === 0) {
511
- this.logger.log(`Auto-scan: no ScrollView found for "${screenName}"`);
512
- return;
513
- }
514
-
515
- this.logger.log(
516
- `Auto-scan: "${screenName}" captured ${tiles.length} tiles ` +
517
- `(content=${tiles[0].contentHeight}px)`,
518
- );
519
-
520
- // Upload all tiles in parallel
521
- const sessionId = this.sessionId!;
522
- await Promise.all(
523
- tiles.map((tile) =>
524
- this.apiClient.uploadScreenTile(
525
- sessionId,
526
- screenName,
527
- tile.image,
528
- tile.width,
529
- tile.height,
530
- tile.scrollOffsetY,
531
- tile.viewportHeight,
532
- tile.contentHeight,
533
- ).catch(() => {}),
534
- ),
535
- );
536
-
537
- this.logger.log(`Auto-scan: "${screenName}" tiles uploaded`);
538
- } catch (err) {
539
- this.logger.warn('Auto-scan failed:', err);
540
- }
541
- })();
542
- }
543
-
544
502
  onScreenEnter(screenName: string): void {
545
503
  this.currentScreen = screenName;
546
504
  this.currentScreenEnteredAt = new Date().toISOString();
@@ -549,14 +507,12 @@ export class AppAnalytics {
549
507
  this.scrollViewRegistry.clear();
550
508
  this.errorTracker?.setCurrentScreen(screenName);
551
509
  this.snapshotCapture?.setCurrentScreen(screenName);
552
- // NOTE: autoScanScreen (PixelCopy / programmatic scroll) is DISABLED.
553
- // React Native does NOT render off-screen content to the GPU or view
554
- // tree, so programmatic scroll + View.draw()/PixelCopy produces blank
555
- // tiles. Worse, those blank tiles poison the dedup cache and block
556
- // real tiles captured during natural user scroll.
557
- //
558
- // Full-content capture relies entirely on RepliqoScrollView capturing
559
- // viewport tiles as the user naturally scrolls through the content.
510
+ // NOTE: full-content capture is NOT done via programmatic scroll +
511
+ // native capture. React Native does NOT render off-screen content to
512
+ // the GPU or view tree, so any programmatic-scroll approach produces
513
+ // blank tiles. Full-content heatmap backgrounds are built entirely
514
+ // from viewport tiles captured by RepliqoScrollView as the user
515
+ // naturally scrolls through the content.
560
516
 
561
517
  this.logger.log('Screen entered:', screenName);
562
518
  }
package/src/index.ts CHANGED
@@ -19,9 +19,7 @@ export { captureScreenshot } from './snapshot/captureScreenshot';
19
19
  export {
20
20
  isNativeScreenCaptureAvailable,
21
21
  captureNativeFrame,
22
- captureFullContent,
23
22
  } from './snapshot/NativeScreenCapture';
24
- export type { FullContentResult } from './snapshot/NativeScreenCapture';
25
23
 
26
24
  // Types
27
25
  export type {
@@ -14,24 +14,8 @@ interface NativeCaptureResult {
14
14
  format: string;
15
15
  }
16
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
17
  interface NativeScreenCaptureModule {
32
18
  captureFrame(): Promise<NativeCaptureResult | null>;
33
- captureFullContent(): Promise<NativeFullContentResult | null>;
34
- scanFullContent(): Promise<NativeTile[] | null>;
35
19
  isAvailable(): Promise<boolean>;
36
20
  }
37
21
 
@@ -51,6 +35,12 @@ export function isNativeScreenCaptureAvailable(): boolean {
51
35
  *
52
36
  * No permissions required. No dialog. No foreground service.
53
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
+ *
54
44
  * @returns NativeFrameResult or null if capture failed / not available
55
45
  */
56
46
  export async function captureNativeFrame(): Promise<NativeFrameResult | null> {
@@ -70,88 +60,3 @@ export async function captureNativeFrame(): Promise<NativeFrameResult | null> {
70
60
  return null;
71
61
  }
72
62
  }
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
- }