@cxpinsight/react-native 0.18.0 → 0.18.1
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/CHANGELOG.md +28 -0
- package/CXPinsightReactNative.podspec +24 -0
- package/README.md +10 -5
- package/android/build.gradle +33 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/cxpinsight/reactnative/CXPScreenshotModule.java +134 -0
- package/android/src/main/java/com/cxpinsight/reactnative/CXPinsightPackage.java +25 -0
- package/ios/CXPScreenshot.m +99 -0
- package/lib/CXPinsight.d.ts +16 -0
- package/lib/CXPinsight.d.ts.map +1 -1
- package/lib/CXPinsight.js +73 -8
- package/lib/CXPinsight.js.map +1 -1
- package/lib/CXPinsightProvider.d.ts.map +1 -1
- package/lib/CXPinsightProvider.js +4 -0
- package/lib/CXPinsightProvider.js.map +1 -1
- package/lib/core/nativeScreenshot.d.ts +9 -0
- package/lib/core/nativeScreenshot.d.ts.map +1 -0
- package/lib/core/nativeScreenshot.js +76 -0
- package/lib/core/nativeScreenshot.js.map +1 -0
- package/lib/core/networkCapture.d.ts +1 -0
- package/lib/core/networkCapture.d.ts.map +1 -1
- package/lib/core/networkCapture.js +18 -1
- package/lib/core/networkCapture.js.map +1 -1
- package/lib/types.d.ts +1 -2
- package/lib/types.d.ts.map +1 -1
- package/lib/version.d.ts +9 -0
- package/lib/version.d.ts.map +1 -0
- package/lib/version.js +12 -0
- package/lib/version.js.map +1 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.1
|
|
4
|
+
|
|
5
|
+
Needs one native rebuild after upgrading: the package now ships native code.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Screenshots without host code.** The SDK carries its own native module
|
|
10
|
+
(`CXPScreenshot`, iOS and Android) and captures replay screenshots itself.
|
|
11
|
+
Before, screenshots existed only when the app passed `captureScreen`; an app
|
|
12
|
+
that did not recorded timelines with no pictures while the dashboard said
|
|
13
|
+
screenshots were on. Order: your `captureScreen` → the SDK's module →
|
|
14
|
+
`react-native-view-shot` if installed → none. Frames are JPEG, 720 px wide,
|
|
15
|
+
with text inputs masked while `recordMaskInputs` is on. Android windows with
|
|
16
|
+
`FLAG_SECURE` are skipped. The dashboard's Screenshots switch turns all of it off.
|
|
17
|
+
- **`session_start` says which build and wiring produced it**: `sdk_version`,
|
|
18
|
+
`screenshot_source`, `provider_mounted`, `navigation_source`, `tap_capture`.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **A session that starts by rollover knows its screen.** Coming back after the
|
|
23
|
+
inactivity timeout started a new session with no `page_view`, so every event
|
|
24
|
+
in it carried the previous session's screen and funnels never saw an entry.
|
|
25
|
+
The current screen is now re-announced (`session_resumed: true`) without
|
|
26
|
+
firing screen-view survey triggers. That `session_start` also carries
|
|
27
|
+
`is_first_visit` and the diagnostics above, like the one at launch.
|
|
28
|
+
- **Connectivity probes are not api_call events.** NetInfo's reachability check
|
|
29
|
+
(`generate_204`) and captive-portal checks are dropped from network capture.
|
|
30
|
+
|
|
3
31
|
## 0.18.0
|
|
4
32
|
|
|
5
33
|
Requires `@cxpinsight/survey-spec@^0.9.0` — it calls resolveBranding,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
+
|
|
5
|
+
# The SDK's own native code: one module, CXPScreenshot, so replay screenshots
|
|
6
|
+
# work from installing the package alone — no captureScreen callback and no
|
|
7
|
+
# second native dependency in the host app.
|
|
8
|
+
Pod::Spec.new do |s|
|
|
9
|
+
s.name = 'CXPinsightReactNative'
|
|
10
|
+
s.version = package['version']
|
|
11
|
+
s.summary = package['description']
|
|
12
|
+
s.license = package['license']
|
|
13
|
+
s.authors = 'CXPinsight'
|
|
14
|
+
s.homepage = package['homepage']
|
|
15
|
+
s.platforms = { :ios => '13.4' }
|
|
16
|
+
s.source = { :git => 'https://github.com/SaifDalli2/CX_Platform.git', :tag => "react-native-v#{s.version}" }
|
|
17
|
+
s.source_files = 'ios/**/*.{h,m,mm}'
|
|
18
|
+
|
|
19
|
+
if respond_to?(:install_modules_dependencies, true)
|
|
20
|
+
install_modules_dependencies(s)
|
|
21
|
+
else
|
|
22
|
+
s.dependency 'React-Core'
|
|
23
|
+
end
|
|
24
|
+
end
|
package/README.md
CHANGED
|
@@ -34,7 +34,6 @@ Taps on everything else — `TouchableHighlight`, `Button`, and any touchable in
|
|
|
34
34
|
|
|
35
35
|
```js
|
|
36
36
|
import { CXPinsight } from '@cxpinsight/react-native';
|
|
37
|
-
import { captureScreen } from 'react-native-view-shot';
|
|
38
37
|
|
|
39
38
|
CXPinsight.init({
|
|
40
39
|
applicationId: 'YOUR_APPLICATION_ID',
|
|
@@ -42,12 +41,18 @@ CXPinsight.init({
|
|
|
42
41
|
apiBase: 'https://your-gateway.example.com',
|
|
43
42
|
|
|
44
43
|
record: true, // session replay: screens, taps, scroll, inputs
|
|
45
|
-
recordMaskInputs: true, // never store typed text
|
|
46
|
-
captureScreen: () => // optional screenshots in the replay timeline
|
|
47
|
-
captureScreen({ format: 'jpg', quality: 0.3, result: 'base64' }),
|
|
44
|
+
recordMaskInputs: true, // never store typed text; also masks inputs in screenshots
|
|
48
45
|
});
|
|
49
46
|
```
|
|
50
47
|
|
|
48
|
+
Screenshots in the replay are taken by the SDK's own native module — no extra
|
|
49
|
+
package and no capture code. It links automatically; rebuild the app once after
|
|
50
|
+
installing (`npx expo prebuild` / `pod install`). Text inputs are covered in
|
|
51
|
+
every frame while `recordMaskInputs` is on, and an Android screen with
|
|
52
|
+
`FLAG_SECURE` is never captured. Turn screenshots off from the dashboard
|
|
53
|
+
(Recording → Screenshots). To use your own capturer instead, pass
|
|
54
|
+
`captureScreen: () => Promise<base64 jpeg>`; it always takes precedence.
|
|
55
|
+
|
|
51
56
|
On a physical device `apiBase` must be reachable from the device — use your machine's LAN IP, not `localhost`. The SDK checks this at startup and logs a specific message if it looks wrong.
|
|
52
57
|
|
|
53
58
|
### 3. Report screen changes
|
|
@@ -215,7 +220,7 @@ as partial rather than absent.
|
|
|
215
220
|
| `apiBase` | — | Your CXPinsight gateway URL. |
|
|
216
221
|
| `record` | dashboard | Session replay. Omit to follow the dashboard toggle. |
|
|
217
222
|
| `recordMaskInputs` | `true` | Never persist typed text. |
|
|
218
|
-
| `captureScreen` |
|
|
223
|
+
| `captureScreen` | built in | Your own screenshot provider. Omit it: the SDK captures screenshots itself. |
|
|
219
224
|
| `captureNetwork` | `true` | Sanitised `api_call` events — method, host, path, status. Never bodies or headers. |
|
|
220
225
|
| `requireConsent` | `false` | Hold all capture until `optIn()`. |
|
|
221
226
|
| `autoCaptureTaps` | `true` | Capture taps on touch components the babel plugin cannot reach, including inside UI libraries. |
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// The SDK's own native code: one module, CXPScreenshot (see
|
|
2
|
+
// src/main/java/com/cxpinsight/reactnative/CXPScreenshotModule.java).
|
|
3
|
+
|
|
4
|
+
def safeExtGet(prop, fallback) {
|
|
5
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
apply plugin: 'com.android.library'
|
|
9
|
+
|
|
10
|
+
android {
|
|
11
|
+
namespace "com.cxpinsight.reactnative"
|
|
12
|
+
compileSdkVersion safeExtGet('compileSdkVersion', 35)
|
|
13
|
+
|
|
14
|
+
defaultConfig {
|
|
15
|
+
minSdkVersion safeExtGet('minSdkVersion', 24)
|
|
16
|
+
targetSdkVersion safeExtGet('targetSdkVersion', 35)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
compileOptions {
|
|
20
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
21
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
repositories {
|
|
26
|
+
google()
|
|
27
|
+
mavenCentral()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
dependencies {
|
|
31
|
+
// Resolved to the app's React Native version by the React Native Gradle plugin.
|
|
32
|
+
implementation "com.facebook.react:react-android"
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
package com.cxpinsight.reactnative;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.graphics.Bitmap;
|
|
5
|
+
import android.graphics.Canvas;
|
|
6
|
+
import android.graphics.Paint;
|
|
7
|
+
import android.util.Base64;
|
|
8
|
+
import android.view.View;
|
|
9
|
+
import android.view.ViewGroup;
|
|
10
|
+
import android.view.WindowManager;
|
|
11
|
+
import android.widget.EditText;
|
|
12
|
+
|
|
13
|
+
import androidx.annotation.NonNull;
|
|
14
|
+
|
|
15
|
+
import com.facebook.react.bridge.Promise;
|
|
16
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
17
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
18
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
19
|
+
|
|
20
|
+
import java.io.ByteArrayOutputStream;
|
|
21
|
+
import java.util.concurrent.ExecutorService;
|
|
22
|
+
import java.util.concurrent.Executors;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Replay screenshots taken by the SDK itself — the Android half of
|
|
26
|
+
* ios/CXPScreenshot.m. capture(quality, maxWidth, maskInputs) resolves a base64
|
|
27
|
+
* JPEG of the current activity's window, or null. Null means "no frame this
|
|
28
|
+
* time", never an error: a recording must not fail over a frame.
|
|
29
|
+
*/
|
|
30
|
+
public class CXPScreenshotModule extends ReactContextBaseJavaModule {
|
|
31
|
+
static final String NAME = "CXPScreenshot";
|
|
32
|
+
|
|
33
|
+
private final ExecutorService encoder = Executors.newSingleThreadExecutor();
|
|
34
|
+
|
|
35
|
+
CXPScreenshotModule(ReactApplicationContext context) {
|
|
36
|
+
super(context);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@NonNull
|
|
40
|
+
@Override
|
|
41
|
+
public String getName() {
|
|
42
|
+
return NAME;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@ReactMethod
|
|
46
|
+
public void capture(final double quality, final double maxWidth, final boolean maskInputs, final Promise promise) {
|
|
47
|
+
final Activity activity = getReactApplicationContext().getCurrentActivity();
|
|
48
|
+
if (activity == null) {
|
|
49
|
+
promise.resolve(null);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// FLAG_SECURE is the app saying "never capture this screen". The OS already
|
|
53
|
+
// blanks it from screenshots and recents; the SDK does the same.
|
|
54
|
+
if ((activity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
|
|
55
|
+
promise.resolve(null);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
activity.runOnUiThread(new Runnable() {
|
|
59
|
+
@Override
|
|
60
|
+
public void run() {
|
|
61
|
+
final Bitmap bitmap;
|
|
62
|
+
try {
|
|
63
|
+
bitmap = draw(activity.getWindow().getDecorView().getRootView(), maxWidth, maskInputs);
|
|
64
|
+
} catch (Throwable t) {
|
|
65
|
+
promise.resolve(null);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (bitmap == null) {
|
|
69
|
+
promise.resolve(null);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
encoder.execute(new Runnable() {
|
|
73
|
+
@Override
|
|
74
|
+
public void run() {
|
|
75
|
+
try {
|
|
76
|
+
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
77
|
+
int q = (int) Math.round((quality > 0 && quality <= 1 ? quality : 0.5) * 100);
|
|
78
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, q, out);
|
|
79
|
+
promise.resolve(Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP));
|
|
80
|
+
} catch (Throwable t) {
|
|
81
|
+
promise.resolve(null);
|
|
82
|
+
} finally {
|
|
83
|
+
bitmap.recycle();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Draws straight at the output width rather than full resolution and scaling
|
|
92
|
+
// after: fewer pixels, drawn once.
|
|
93
|
+
private static Bitmap draw(View root, double maxWidth, boolean maskInputs) {
|
|
94
|
+
int w = root.getWidth();
|
|
95
|
+
int h = root.getHeight();
|
|
96
|
+
if (w <= 0 || h <= 0) return null;
|
|
97
|
+
float scale = maxWidth > 0 ? Math.min(1f, (float) (maxWidth / w)) : 1f;
|
|
98
|
+
int ow = Math.max(1, Math.round(w * scale));
|
|
99
|
+
int oh = Math.max(1, Math.round(h * scale));
|
|
100
|
+
|
|
101
|
+
Bitmap bitmap = Bitmap.createBitmap(ow, oh, Bitmap.Config.ARGB_8888);
|
|
102
|
+
Canvas canvas = new Canvas(bitmap);
|
|
103
|
+
canvas.scale(scale, scale);
|
|
104
|
+
root.draw(canvas);
|
|
105
|
+
|
|
106
|
+
// Text inputs are covered, not photographed: what someone types is theirs.
|
|
107
|
+
if (maskInputs) {
|
|
108
|
+
Paint paint = new Paint();
|
|
109
|
+
paint.setColor(0xFF8C8C8C);
|
|
110
|
+
int[] rootAt = new int[2];
|
|
111
|
+
root.getLocationInWindow(rootAt);
|
|
112
|
+
maskInputs(root, canvas, paint, rootAt);
|
|
113
|
+
}
|
|
114
|
+
return bitmap;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private static void maskInputs(View view, Canvas canvas, Paint paint, int[] rootAt) {
|
|
118
|
+
if (view.getVisibility() != View.VISIBLE) return;
|
|
119
|
+
if (view instanceof EditText) {
|
|
120
|
+
int[] at = new int[2];
|
|
121
|
+
view.getLocationInWindow(at);
|
|
122
|
+
float x = at[0] - rootAt[0];
|
|
123
|
+
float y = at[1] - rootAt[1];
|
|
124
|
+
canvas.drawRect(x, y, x + view.getWidth(), y + view.getHeight(), paint);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (view instanceof ViewGroup) {
|
|
128
|
+
ViewGroup group = (ViewGroup) view;
|
|
129
|
+
for (int i = 0; i < group.getChildCount(); i++) {
|
|
130
|
+
maskInputs(group.getChildAt(i), canvas, paint, rootAt);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package com.cxpinsight.reactnative;
|
|
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.Collections;
|
|
11
|
+
import java.util.List;
|
|
12
|
+
|
|
13
|
+
public class CXPinsightPackage implements ReactPackage {
|
|
14
|
+
@NonNull
|
|
15
|
+
@Override
|
|
16
|
+
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
|
|
17
|
+
return Collections.<NativeModule>singletonList(new CXPScreenshotModule(reactContext));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@NonNull
|
|
21
|
+
@Override
|
|
22
|
+
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
|
|
23
|
+
return Collections.emptyList();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Replay screenshots taken by the SDK itself.
|
|
2
|
+
//
|
|
3
|
+
// Screenshots used to exist only when the host app passed a captureScreen
|
|
4
|
+
// callback backed by a second native package. An app that followed the setup
|
|
5
|
+
// guide without it — or with it installed but not wired — recorded timelines
|
|
6
|
+
// with no pictures, while the dashboard said screenshots were on.
|
|
7
|
+
//
|
|
8
|
+
// So the SDK carries its own. One method, capture(quality, maxWidth,
|
|
9
|
+
// maskInputs), resolving a base64 JPEG of the key window or null. Null means
|
|
10
|
+
// "no frame this time", never an error: a recording must not fail over a frame.
|
|
11
|
+
|
|
12
|
+
#import <React/RCTBridgeModule.h>
|
|
13
|
+
#import <UIKit/UIKit.h>
|
|
14
|
+
|
|
15
|
+
@interface CXPScreenshot : NSObject <RCTBridgeModule>
|
|
16
|
+
@end
|
|
17
|
+
|
|
18
|
+
@implementation CXPScreenshot
|
|
19
|
+
|
|
20
|
+
RCT_EXPORT_MODULE(CXPScreenshot)
|
|
21
|
+
|
|
22
|
+
+ (BOOL)requiresMainQueueSetup { return NO; }
|
|
23
|
+
|
|
24
|
+
// UIKit drawing belongs on the main queue. Encoding does not, and is moved off it below.
|
|
25
|
+
- (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); }
|
|
26
|
+
|
|
27
|
+
static UIWindow *CXPKeyWindow(void)
|
|
28
|
+
{
|
|
29
|
+
for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) {
|
|
30
|
+
if (scene.activationState != UISceneActivationStateForegroundActive) continue;
|
|
31
|
+
if (![scene isKindOfClass:[UIWindowScene class]]) continue;
|
|
32
|
+
for (UIWindow *w in ((UIWindowScene *)scene).windows) {
|
|
33
|
+
if (w.isKeyWindow) return w;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return nil;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Text inputs are covered, not photographed: what someone types is theirs, and
|
|
40
|
+
// a masked recording is the SDK's default everywhere else.
|
|
41
|
+
static void CXPCollectInputs(UIView *view, UIWindow *window, NSMutableArray<NSValue *> *out)
|
|
42
|
+
{
|
|
43
|
+
if (view.hidden || view.alpha < 0.01) return;
|
|
44
|
+
BOOL isInput = [view isKindOfClass:[UITextField class]] ||
|
|
45
|
+
([view isKindOfClass:[UITextView class]] && ((UITextView *)view).isEditable);
|
|
46
|
+
if (isInput) {
|
|
47
|
+
[out addObject:[NSValue valueWithCGRect:[view convertRect:view.bounds toView:window]]];
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
for (UIView *child in view.subviews) CXPCollectInputs(child, window, out);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
RCT_EXPORT_METHOD(capture:(double)quality
|
|
54
|
+
maxWidth:(double)maxWidth
|
|
55
|
+
maskInputs:(BOOL)maskInputs
|
|
56
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
57
|
+
reject:(__unused RCTPromiseRejectBlock)reject)
|
|
58
|
+
{
|
|
59
|
+
// Backgrounded: iOS shows a snapshot, not the app, and drawing now is wasted.
|
|
60
|
+
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) {
|
|
61
|
+
resolve([NSNull null]);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
UIWindow *window = CXPKeyWindow();
|
|
65
|
+
CGSize size = window.bounds.size;
|
|
66
|
+
if (!window || size.width < 1 || size.height < 1) {
|
|
67
|
+
resolve([NSNull null]);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Render straight at the output width rather than at the screen's 3x and
|
|
72
|
+
// scaling down after: a third of the pixels, drawn once.
|
|
73
|
+
CGFloat scale = window.screen.scale;
|
|
74
|
+
if (maxWidth > 0) scale = MIN(scale, maxWidth / size.width);
|
|
75
|
+
UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat preferredFormat];
|
|
76
|
+
format.scale = scale;
|
|
77
|
+
format.opaque = YES;
|
|
78
|
+
|
|
79
|
+
NSMutableArray<NSValue *> *masks = [NSMutableArray new];
|
|
80
|
+
if (maskInputs) CXPCollectInputs(window, window, masks);
|
|
81
|
+
|
|
82
|
+
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:format];
|
|
83
|
+
UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *ctx) {
|
|
84
|
+
[window drawViewHierarchyInRect:window.bounds afterScreenUpdates:NO];
|
|
85
|
+
if (masks.count) {
|
|
86
|
+
[[UIColor colorWithWhite:0.55 alpha:1] setFill];
|
|
87
|
+
for (NSValue *r in masks) UIRectFill(r.CGRectValue);
|
|
88
|
+
}
|
|
89
|
+
}];
|
|
90
|
+
|
|
91
|
+
double q = quality > 0 && quality <= 1 ? quality : 0.5;
|
|
92
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
|
93
|
+
NSData *jpeg = UIImageJPEGRepresentation(image, q);
|
|
94
|
+
if (!jpeg) { resolve([NSNull null]); return; }
|
|
95
|
+
resolve([jpeg base64EncodedStringWithOptions:0]);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@end
|
package/lib/CXPinsight.d.ts
CHANGED
|
@@ -16,6 +16,9 @@ declare class CXPinsightSDK {
|
|
|
16
16
|
/** The field currently focused, so blur can report dwell + changed. */
|
|
17
17
|
private fieldVisit;
|
|
18
18
|
private currentScreen;
|
|
19
|
+
private screenshotsOff;
|
|
20
|
+
private screenshotSource;
|
|
21
|
+
private integration;
|
|
19
22
|
private previousScreen;
|
|
20
23
|
private engagementStartTs;
|
|
21
24
|
private engagementAccumMs;
|
|
@@ -62,6 +65,13 @@ declare class CXPinsightSDK {
|
|
|
62
65
|
private applyServerSdkConfig;
|
|
63
66
|
/** Roll the recording + engagement clock onto a freshly-started session. */
|
|
64
67
|
private onSessionRollover;
|
|
68
|
+
/** Which build, and how it is wired — so a gap in a session can be read, not guessed. */
|
|
69
|
+
private sessionDiagnostics;
|
|
70
|
+
/** @internal Called by CXPinsightProvider so session_start can report how screens are tracked. */
|
|
71
|
+
_noteIntegration(i: Partial<{
|
|
72
|
+
provider: boolean;
|
|
73
|
+
navigation: 'ref' | 'screen_prop' | 'none';
|
|
74
|
+
}>): void;
|
|
65
75
|
/** Emit everything buffered before init() resolved, oldest first. */
|
|
66
76
|
private flushPending;
|
|
67
77
|
/** Wire native signals → typed analytics events (shared taxonomy). */
|
|
@@ -76,6 +86,12 @@ declare class CXPinsightSDK {
|
|
|
76
86
|
track(eventName: string, data?: Record<string, unknown>, eventType?: string, teeToRecording?: boolean,
|
|
77
87
|
/** Internal: preserves the original time of a call buffered before init(). */
|
|
78
88
|
eventTimestamp?: string): void;
|
|
89
|
+
/**
|
|
90
|
+
* The screenshot capturer for a recording that is starting now: the host's
|
|
91
|
+
* captureScreen, else the SDK's own (nativeScreenshot.ts), unless the
|
|
92
|
+
* dashboard turned screenshots off.
|
|
93
|
+
*/
|
|
94
|
+
private screenshotProvider;
|
|
79
95
|
/** Track a screen view (triggers remote page_view rules) */
|
|
80
96
|
trackScreen(screenName: string, data?: Record<string, unknown>): void;
|
|
81
97
|
/** Identify the current user */
|
package/lib/CXPinsight.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CXPinsight.d.ts","sourceRoot":"","sources":["../src/CXPinsight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EAGZ,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"CXPinsight.d.ts","sourceRoot":"","sources":["../src/CXPinsight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EAGZ,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAyBjB,OAAO,EAGe,KAAK,UAAU,EAAE,KAAK,eAAe,EAC1D,MAAM,gBAAgB,CAAC;AAiBxB,KAAK,eAAe,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;AAClD,KAAK,cAAc,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;AAE9C,cAAM,aAAa;IACjB,OAAO,CAAC,WAAW,CAAS;IAC5B,2EAA2E;IAC3E,OAAO,CAAC,aAAa,CAAqG;IAC1H,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,MAAM,CAAc;IAK5B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,cAAc,CAA+B;IAErD,OAAO,CAAC,cAAc,CAA6B;IACnD,uEAAuE;IACvE,OAAO,CAAC,UAAU,CAAiD;IAEnE,OAAO,CAAC,aAAa,CAAuB;IAE5C,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,gBAAgB,CAA4B;IAEpD,OAAO,CAAC,WAAW,CAA8G;IACjI,OAAO,CAAC,cAAc,CAAuB;IAG7C,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,oBAAoB;IAC5B,OAAO,CAAC,oBAAoB;IAC5B,OAAO,CAAC,qBAAqB;IAC7B,OAAO,CAAC,eAAe;IAEvB,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAS;IAY7C,OAAO,CAAC,OAAO,CAAwG;IACvH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAM;IACzC,OAAO,CAAC,cAAc,CAAK;IAE3B,oDAAoD;IAC9C,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4PnF;IAED;;;;;;OAMG;IAEH;;;;;;;OAOG;IACH;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;IAoBtB,qEAAqE;IACrE,UAAU,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAA0C;IAExF,OAAO,CAAC,sBAAsB;YAqDhB,oBAAoB;IA4FlC,4EAA4E;YAC9D,iBAAiB;IA8C/B,yFAAyF;IACzF,OAAO,CAAC,kBAAkB;IAU1B,kGAAkG;IAClG,gBAAgB,CAAC,CAAC,EAAE,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,KAAK,GAAG,aAAa,GAAG,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI,CAEpG;IAED,qEAAqE;IACrE,OAAO,CAAC,YAAY;IAgBpB,sEAAsE;IACtE,OAAO,CAAC,eAAe;IAmNvB,2BAA2B;IAC3B;;;;OAIG;YACW,kBAAkB;IAiChC,KAAK,CACH,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,CAAC,EAAE,MAAM,EAClB,cAAc,UAAO;IACrB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,GACtB,IAAI,CAmFN;IAED;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAO1B,4DAA4D;IAC5D,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CA+BpE;IAED,gCAAgC;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBhD;IAED,0BAA0B;IAC1B,eAAe,CACb,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,IAAI,CAqCN;IAED,qCAAqC;IACrC,eAAe,CACb,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,IAAI,CAmCN;IAED,+BAA+B;IAC/B;;;;;;;;;;OAUG;IACH,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAErF;IAED;;;;;;;OAOG;IACH,cAAc,IAAI;QAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAErG;IAED,yEAAyE;IACzE,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAE3E;IAED,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAkDpE;IAID,uDAAuD;IACvD,cAAc,IAAI,IAAI,CAUrB;IAED,oCAAoC;IAC9B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAKnC;IAED,+CAA+C;IAC/C,aAAa,IAAI,OAAO,CAEvB;IAED,2CAA2C;IAC3C,aAAa,+DAEZ;IAED,8DAA8D;IACxD,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAEnC;IAED,qCAAqC;IACrC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAMjC;IAED;;;;OAIG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAiBpC;IAED,+DAA+D;IACzD,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnC;IAED,2CAA2C;IAC3C;;;;;;;OAOG;IACH,KAAK,IAAI,IAAI,CAAoB;IACjC,MAAM,IAAI,IAAI,CAAqB;IAEnC;;;;;;OAMG;IACH,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAMpD;IAED;;;;;;;OAOG;IACH,UAAU,IAAI,UAAU,GAAG,IAAI,CAAyB;IAExD,sEAAsE;IACtE,iBAAiB,IAAI,SAAS,eAAe,EAAE,CAA+B;IAE9E,8CAA8C;IAC9C,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAA6B;IACvE,iBAAiB,IAAI,OAAO,CAA8B;IAE1D,qDAAqD;YACvC,eAAe;IAmCvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAgC3B;IAED,+BAA+B;IACzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;IAED,sEAAsE;IACtE,wBAAwB,CAAC,SAAS,EAAE,eAAe,GAAG,IAAI,CAEzD;IAED,gDAAgD;IAChD,0BAA0B,IAAI,IAAI,CAEjC;IAED,qEAAqE;IACrE,uBAAuB,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,CAEvD;IAED,+CAA+C;IAC/C,yBAAyB,IAAI,IAAI,CAEhC;IAED,8BAA8B;IAC9B,aAAa,IAAI,mBAAmB,CAEnC;IAED,2BAA2B;IAC3B,UAAU,IAAI,MAAM,CAEnB;IAED;;;;wEAIoE;IACpE,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAEjD;IAED,2BAA2B;IAC3B;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAI3C;IAED,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,oBAAoB,CAAS;IAErC,8EAA8E;IAC9E,iBAAiB,IAAI,IAAI,CAGxB;IAED,UAAU,IAAI,gBAAgB,GAAG,IAAI,CAEpC;IAED;;mFAE+E;IAC/E,uBAAuB,IAAI;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,IAAI,CAOlG;IAED,qCAAqC;IACrC,cAAc,IAAI,OAAO,CAExB;IAED,OAAO,CAAC,GAAG;CAKZ;AAED,6BAA6B;AAC7B,eAAO,MAAM,UAAU,eAAsB,CAAC"}
|
package/lib/CXPinsight.js
CHANGED
|
@@ -53,6 +53,8 @@ const interactionObserver_1 = require("./instrumented/interactionObserver");
|
|
|
53
53
|
const lifecycle_1 = require("./core/lifecycle");
|
|
54
54
|
const networkCapture_1 = require("./core/networkCapture");
|
|
55
55
|
const scrollCapture_1 = require("./core/scrollCapture");
|
|
56
|
+
const version_1 = require("./version");
|
|
57
|
+
const nativeScreenshot_1 = require("./core/nativeScreenshot");
|
|
56
58
|
const autoTapCapture_1 = require("./core/autoTapCapture");
|
|
57
59
|
const consent_1 = require("./core/consent");
|
|
58
60
|
// Canonical eventType per event name — keeps mobile events aligned with the shared
|
|
@@ -90,6 +92,12 @@ class CXPinsightSDK {
|
|
|
90
92
|
this.fieldVisit = null;
|
|
91
93
|
// Current + previous screen, so click/scroll/screen_view events carry screenName.
|
|
92
94
|
this.currentScreen = null;
|
|
95
|
+
// Set by the dashboard's recording.screenshots === false; read by screenshotProvider().
|
|
96
|
+
this.screenshotsOff = false;
|
|
97
|
+
// Where replay screenshots come from this run — reported on session_start.
|
|
98
|
+
this.screenshotSource = 'none';
|
|
99
|
+
// How the host wired the SDK, as far as the SDK can see — reported on session_start.
|
|
100
|
+
this.integration = { provider: false, navigation: 'none' };
|
|
93
101
|
this.previousScreen = null;
|
|
94
102
|
// Engagement clock — active foreground time this session (paused while backgrounded),
|
|
95
103
|
// attached to every event as engagementTimeMs (parity with web's time-on-page).
|
|
@@ -130,9 +138,10 @@ class CXPinsightSDK {
|
|
|
130
138
|
if (!config.applicationId) {
|
|
131
139
|
throw new Error('[CXPinsight] init requires config.applicationId');
|
|
132
140
|
}
|
|
133
|
-
|
|
134
|
-
//
|
|
135
|
-
//
|
|
141
|
+
this.screenshotsOff = false;
|
|
142
|
+
// Screenshots need no flag: the SDK takes them itself (nativeScreenshot.ts)
|
|
143
|
+
// unless the host passed its own captureScreen or the dashboard turned
|
|
144
|
+
// them off.
|
|
136
145
|
//
|
|
137
146
|
// captureEverything says WHAT to capture within a session. It deliberately
|
|
138
147
|
// does not touch recording.sampleRate, which says HOW MANY sessions to
|
|
@@ -273,7 +282,7 @@ class CXPinsightSDK {
|
|
|
273
282
|
applicationId: config.applicationId,
|
|
274
283
|
sampleRate: config.recordingSampleRate,
|
|
275
284
|
maskInputs: config.recordMaskInputs,
|
|
276
|
-
captureScreen:
|
|
285
|
+
captureScreen: this.screenshotProvider(),
|
|
277
286
|
// The screen the user is ON when this recording begins. Without it a
|
|
278
287
|
// recording that starts mid-session opens with screen null and stays
|
|
279
288
|
// nameless until the next navigation — which may never come.
|
|
@@ -333,6 +342,7 @@ class CXPinsightSDK {
|
|
|
333
342
|
this.track('session_start', {
|
|
334
343
|
sessionId: (0, session_1.getSessionId)(),
|
|
335
344
|
is_first_visit: isFirstVisit,
|
|
345
|
+
...this.sessionDiagnostics(),
|
|
336
346
|
}, 'navigation', false);
|
|
337
347
|
}
|
|
338
348
|
// Replay anything the app tracked while init() was still resolving. After
|
|
@@ -495,8 +505,12 @@ class CXPinsightSDK {
|
|
|
495
505
|
// controlled nothing, because screenshots depended solely on whether the
|
|
496
506
|
// app passed a captureScreen callback. Turning it off now actually stops
|
|
497
507
|
// them, without the app having to ship a new build.
|
|
508
|
+
//
|
|
509
|
+
// Off also has to stop the SDK's OWN capture (nativeScreenshot.ts), which
|
|
510
|
+
// needs no callback — clearing captureScreen alone would leave it running.
|
|
498
511
|
if (s.recording?.screenshots === false) {
|
|
499
512
|
config.captureScreen = undefined;
|
|
513
|
+
this.screenshotsOff = true;
|
|
500
514
|
}
|
|
501
515
|
// Bodies ride WITH network capture rather than needing a second switch: an
|
|
502
516
|
// api_call that says a request happened but not what it carried answers
|
|
@@ -536,7 +550,7 @@ class CXPinsightSDK {
|
|
|
536
550
|
applicationId: this.config.applicationId,
|
|
537
551
|
sampleRate: this.config.recordingSampleRate,
|
|
538
552
|
maskInputs: this.config.recordMaskInputs,
|
|
539
|
-
captureScreen: this.
|
|
553
|
+
captureScreen: this.screenshotProvider(),
|
|
540
554
|
screen: this.currentScreen,
|
|
541
555
|
});
|
|
542
556
|
}
|
|
@@ -545,7 +559,44 @@ class CXPinsightSDK {
|
|
|
545
559
|
}
|
|
546
560
|
}
|
|
547
561
|
this.startEngagementClock(); // reset engagement time for the new session
|
|
548
|
-
this.track('session_start', {
|
|
562
|
+
this.track('session_start', {
|
|
563
|
+
sessionId: newSessionId,
|
|
564
|
+
is_first_visit: false,
|
|
565
|
+
...this.sessionDiagnostics(),
|
|
566
|
+
}, 'navigation', false);
|
|
567
|
+
// THE SCREEN THE USER IS STILL ON BELONGS TO THIS SESSION TOO.
|
|
568
|
+
//
|
|
569
|
+
// A rollover happens when the app comes back after the inactivity timeout,
|
|
570
|
+
// usually on the screen it was left on. trackScreen() is idempotent for the
|
|
571
|
+
// current screen, so nothing re-announced it: the new session had no
|
|
572
|
+
// page_view at all, every later event inherited a screen name from the
|
|
573
|
+
// previous session, and funnels starting there never saw an entry. Seen in
|
|
574
|
+
// production as sessions with pageViews 0 and every event on one screen.
|
|
575
|
+
//
|
|
576
|
+
// Only the event, not the screen-view trigger hook: re-entering a screen by waiting is
|
|
577
|
+
// not a navigation, and a screen-view survey trigger must not fire on it.
|
|
578
|
+
this.previousScreen = null;
|
|
579
|
+
if (this.currentScreen) {
|
|
580
|
+
this.track('page_view', {
|
|
581
|
+
page: this.currentScreen,
|
|
582
|
+
screen_name: this.currentScreen,
|
|
583
|
+
session_resumed: true,
|
|
584
|
+
}, 'page_view', false);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/** Which build, and how it is wired — so a gap in a session can be read, not guessed. */
|
|
588
|
+
sessionDiagnostics() {
|
|
589
|
+
return {
|
|
590
|
+
sdk_version: version_1.SDK_VERSION,
|
|
591
|
+
screenshot_source: this.screenshotSource,
|
|
592
|
+
provider_mounted: this.integration.provider,
|
|
593
|
+
navigation_source: this.integration.navigation,
|
|
594
|
+
tap_capture: (0, autoTapCapture_1.isAutoTapCaptureActive)(),
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
/** @internal Called by CXPinsightProvider so session_start can report how screens are tracked. */
|
|
598
|
+
_noteIntegration(i) {
|
|
599
|
+
Object.assign(this.integration, i);
|
|
549
600
|
}
|
|
550
601
|
/** Emit everything buffered before init() resolved, oldest first. */
|
|
551
602
|
flushPending() {
|
|
@@ -888,6 +939,20 @@ class CXPinsightSDK {
|
|
|
888
939
|
};
|
|
889
940
|
(0, triggerEngine_1.onEventTracked)(eventName, targetingContext);
|
|
890
941
|
}
|
|
942
|
+
/**
|
|
943
|
+
* The screenshot capturer for a recording that is starting now: the host's
|
|
944
|
+
* captureScreen, else the SDK's own (nativeScreenshot.ts), unless the
|
|
945
|
+
* dashboard turned screenshots off.
|
|
946
|
+
*/
|
|
947
|
+
screenshotProvider() {
|
|
948
|
+
if (!this.config || this.screenshotsOff) {
|
|
949
|
+
this.screenshotSource = 'none';
|
|
950
|
+
return undefined;
|
|
951
|
+
}
|
|
952
|
+
const r = (0, nativeScreenshot_1.resolveScreenshotProvider)(this.config.captureScreen, { maskInputs: this.config.recordMaskInputs });
|
|
953
|
+
this.screenshotSource = r.source;
|
|
954
|
+
return r.provider;
|
|
955
|
+
}
|
|
891
956
|
/** Track a screen view (triggers remote page_view rules) */
|
|
892
957
|
trackScreen(screenName, data) {
|
|
893
958
|
// IDEMPOTENT FOR THE SCREEN ALREADY SHOWING.
|
|
@@ -1152,7 +1217,7 @@ class CXPinsightSDK {
|
|
|
1152
1217
|
applicationId: this.config.applicationId,
|
|
1153
1218
|
sampleRate: this.config.recordingSampleRate,
|
|
1154
1219
|
maskInputs: this.config.recordMaskInputs,
|
|
1155
|
-
captureScreen: this.
|
|
1220
|
+
captureScreen: this.screenshotProvider(),
|
|
1156
1221
|
// The screen the user is ON when this recording begins. Without it a
|
|
1157
1222
|
// recording that starts mid-session opens with screen null and stays
|
|
1158
1223
|
// nameless until the next navigation — which may never come.
|
|
@@ -1216,7 +1281,7 @@ class CXPinsightSDK {
|
|
|
1216
1281
|
applicationId: this.config.applicationId,
|
|
1217
1282
|
sampleRate: this.config.recordingSampleRate,
|
|
1218
1283
|
maskInputs: this.config.recordMaskInputs,
|
|
1219
|
-
captureScreen: this.
|
|
1284
|
+
captureScreen: this.screenshotProvider(),
|
|
1220
1285
|
// The screen the user is ON when this recording begins. Without it a
|
|
1221
1286
|
// recording that starts mid-session opens with screen null and stays
|
|
1222
1287
|
// nameless until the next navigation — which may never come.
|