noibu-react-native 0.2.18 → 0.2.20

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 (27) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/com/noibu/sessionreplay/reactnative/NoibuSessionReplayModule.kt +15 -77
  4. package/dist/api/ClientConfig.js +1 -1
  5. package/dist/api/MetroplexSocket.d.ts +1 -1
  6. package/dist/api/MetroplexSocket.js +2 -6
  7. package/dist/api/StoredMetrics.js +2 -2
  8. package/dist/constants.js +1 -1
  9. package/dist/entry/init.js +1 -1
  10. package/dist/mobileTransformer/mobile-replay/index.js +16 -2
  11. package/dist/mobileTransformer/mobile-replay/mobile.types.d.ts +7 -0
  12. package/dist/mobileTransformer/mobile-replay/schema/mobile/rr-mobile-schema.json.js +12 -0
  13. package/dist/mobileTransformer/mobile-replay/transformer/screen-chrome.js +1 -1
  14. package/dist/mobileTransformer/mobile-replay/transformer/transformers.d.ts +1 -0
  15. package/dist/mobileTransformer/mobile-replay/transformer/transformers.js +18 -8
  16. package/dist/mobileTransformer/mobile-replay/transformer/webview-transformer.d.ts +9 -0
  17. package/dist/mobileTransformer/mobile-replay/transformer/webview-transformer.js +124 -0
  18. package/dist/monitors/ErrorMonitor.js +1 -1
  19. package/dist/monitors/KeyboardInputMonitor.js +1 -1
  20. package/dist/pageVisit/HttpEventManager.js +2 -2
  21. package/dist/pageVisit/pageVisitEventError.js +1 -1
  22. package/dist/sessionRecorder/SessionRecorder.d.ts +0 -4
  23. package/dist/sessionRecorder/SessionRecorder.js +4 -28
  24. package/dist/sessionRecorder/nativeSessionRecorderSubscription.d.ts +1 -22
  25. package/dist/sessionRecorder/nativeSessionRecorderSubscription.js +17 -18
  26. package/dist/sessionRecorder/types.d.ts +1 -88
  27. package/package.json +3 -5
package/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
3
3
  All notable changes of the noibu-react-native SDK release series are documented in this file using
4
4
  the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
5
5
 
6
+ ## 0.2.20
7
+
8
+ ### Added
9
+ - ** ANDROID: WebView replay support (mobile transformer)**
10
+ - Integrates a WebView-specific transformer to convert rrweb full/incremental snapshots from WebViews into web events.
11
+ - Snapshots generated during transformation are now included at the top level of the transformed output, emitted immediately after the event that produced them.
12
+ - **Live subscription emits multiple events when applicable**
13
+ - The native subscription path now routes events through `transformToWeb`, allowing multiple transformed events (e.g., WebView snapshots) to be emitted per native event.
14
+
15
+ ### Changed
16
+ - **`transformToWeb`** now drains a small internal buffer of WebView snapshots produced during transformation and appends them to the resulting array in order.
17
+ - **`nativeSessionRecorderSubscription`** (Android and iOS) now uses `transformToWeb([event])` and forwards each resulting event via the provided callback.
18
+
19
+
6
20
  ## 0.2.13
7
21
 
8
22
  ### Alpha support for iOS Session Replay in React Native
@@ -67,7 +67,7 @@ def kotlin_version = getExtOrDefault("kotlinVersion")
67
67
  dependencies {
68
68
  implementation "com.facebook.react:react-native:+"
69
69
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
70
- implementation "com.noibu:sessionreplay-recorder:0.2.2"
70
+ implementation "com.noibu:sessionreplay-recorder:1.0.1"
71
71
  }
72
72
 
73
73
  if (isNewArchitectureEnabled()) {
@@ -76,4 +76,4 @@ if (isNewArchitectureEnabled()) {
76
76
  libraryName = "noibusessionreplay"
77
77
  codegenJavaPackageName = "com.noibu.sessionreplay.reactnative"
78
78
  }
79
- }
79
+ }
@@ -6,11 +6,9 @@ import android.util.Log
6
6
  import com.facebook.react.bridge.*
7
7
  import com.facebook.react.modules.core.DeviceEventManagerModule
8
8
 
9
- import com.noibu.sessionreplay.SessionReplayConfig
10
- import com.noibu.sessionreplay.models.ApplicationFramework
11
- import com.noibu.sessionreplay.models.LogLevel
12
-
13
- import com.noibu.sessionreplay.SessionReplay
9
+ // Update the import statements to point to the KMP shared module's package
10
+ import com.noibu.mobile.android.sessionreplay.Noibu
11
+ import com.noibu.mobile.android.sessionreplay.NoibuConfig
14
12
 
15
13
  class NoibuSessionReplayModule(reactContext: ReactApplicationContext) :
16
14
  ReactContextBaseJavaModule(reactContext) {
@@ -28,79 +26,19 @@ class NoibuSessionReplayModule(reactContext: ReactApplicationContext) :
28
26
  }
29
27
 
30
28
  @ReactMethod
31
- fun initialize(
32
- projectId: String,
33
- userId: String?,
34
- logLevel: String,
35
- allowMeteredNetworkUsage: Boolean,
36
- enableWebViewCapture: Boolean,
37
- allowedDomains: ReadableArray,
38
- disableOnLowEndDevices: Boolean,
39
- enableDailyNetworkUsageLimit: Boolean,
40
- maximumDailyNetworkUsageInMB: Double,
41
- promise: Promise
42
- ) {
43
- val allowedActivities = listOf<String>(); // not supported
44
- val disallowedActivities = listOf<String>(); // not supported
45
-
46
- // We use two parameters because the react method parameters do not accept nullable primitive types.
47
- // Moreover, the Long data type is not supported. Js numbers are translated into doubles.
48
- val maximumDailyNetworkUsageInMBLong =
49
- if (enableDailyNetworkUsageLimit) maximumDailyNetworkUsageInMB.toLong() else null
50
-
51
- val config = SessionReplayConfig(
52
- projectId,
53
- userId,
54
- LogLevel.valueOf(logLevel),
55
- allowMeteredNetworkUsage,
56
- enableWebViewCapture,
57
- readableArrayToList(allowedDomains),
58
- ApplicationFramework.ReactNative,
59
- allowedActivities,
60
- disallowedActivities,
61
- disableOnLowEndDevices,
62
- maximumDailyNetworkUsageInMBLong
29
+ fun initialize( promise: Promise ) {
30
+ val context = currentActivity?.applicationContext ?: reactContext?.applicationContext ?: return
31
+ val config = NoibuConfig(
32
+ sessionReplayEnabled = true,
33
+ maskAllTextInputs = false,
63
34
  )
64
-
65
- Handler(Looper.getMainLooper()).post {
66
- val context = currentActivity?.applicationContext ?: reactContext?.applicationContext ?: run {
67
- promise.reject("ACTIVITY_NOT_AVAILABLE", "Current activity is not available.")
68
- return@post
69
- }
70
- promise.resolve(
71
- SessionReplay.initialize(
72
- context,
73
- config,
74
- currentActivity
75
- ) { param ->
76
- Handler(Looper.getMainLooper()).post {
77
- val params = Arguments.createMap()
78
- params.putString("message", param)
79
- sendEvent("noibuRecordingEvent", params)
80
- }
81
- }
82
- )
35
+ Noibu.setup(context, config){ param ->
36
+ val params = Arguments.createMap()
37
+ params.putString("message", param)
38
+ sendEvent("noibuRecordingEvent", params)
39
+ true
83
40
  }
84
- }
85
-
86
- @ReactMethod
87
- fun setCustomUserId(customUserId: String, promise: Promise) {
88
- promise.resolve(SessionReplay.setCustomUserId(customUserId))
89
- }
90
-
91
- @ReactMethod
92
- fun setCustomSessionId(customSessionId: String, promise: Promise) {
93
- promise.resolve(SessionReplay.setCustomSessionId(customSessionId))
94
- }
95
-
96
- private fun readableArrayToList(arr: ReadableArray): List<String> {
97
- val ret = mutableListOf<String>()
98
-
99
- for (i in 0 until arr.size()) {
100
- ret.add(arr.getString(i))
101
- }
102
-
103
- return ret
41
+ promise.resolve(true)
104
42
  }
105
43
 
106
44
  @ReactMethod
@@ -117,4 +55,4 @@ class NoibuSessionReplayModule(reactContext: ReactApplicationContext) :
117
55
  const val NAME = "NativeSessionRecorder"
118
56
  private var reactContext: ReactApplicationContext? = null
119
57
  }
120
- }
58
+ }
@@ -1,7 +1,7 @@
1
1
  import { __awaiter } from 'tslib';
2
2
  import uuid from 'react-native-uuid';
3
3
  import { Severity } from 'noibu-metroplex-ts-bindings';
4
- import { MAX_METROPLEX_SOCKET_INNACTIVE_TIME, GET_SCRIPT_ID, GET_DEVICE_ENV } from '../constants.js';
4
+ import { MAX_METROPLEX_SOCKET_INNACTIVE_TIME, GET_DEVICE_ENV, GET_SCRIPT_ID } from '../constants.js';
5
5
  import { stringifyJSON, getMaxSubstringAllowed, getUserAgent, postRequest } from '../utils/function.js';
6
6
  import Storage from '../storage/Storage.js';
7
7
  import { noibuLog } from '../utils/log.js';
@@ -4,7 +4,7 @@ import { NoSeqNumSlidingMessage, RetryQueueWSMessage } from '../types/Metroplex'
4
4
  /**
5
5
  * Grab the video recorder type based on the device we run the app on.
6
6
  */
7
- export declare function getVideoRecorderType(): Promise<VideoRecorder.RRWeb | VideoRecorder.AndroidNative>;
7
+ export declare function getVideoRecorderType(): Promise<VideoRecorder.RRWeb>;
8
8
  /**
9
9
  * Implements rolling window of specified size,
10
10
  * but only makes a cut once array length exceeds 150%.
@@ -1,10 +1,9 @@
1
1
  import { __awaiter } from 'tslib';
2
2
  import uuid from 'react-native-uuid';
3
- import { Platform } from 'react-native';
4
- import { MetroplexRoute, MetroplexMessageType, WorkRequestMessageType, EventType, Severity, InboundMessageType } from 'noibu-metroplex-ts-bindings';
3
+ import { MetroplexRoute, WorkRequestMessageType, MetroplexMessageType, EventType, Severity, InboundMessageType } from 'noibu-metroplex-ts-bindings';
5
4
  import { getUserAgent, stringifyJSON } from '../utils/function.js';
6
5
  import { addSafeEventListener } from '../utils/eventlistener.js';
7
- import { GET_METROPLEX_BASE_SOCKET_URL, GET_METROPLEX_POST_URL, GET_MAX_METROPLEX_RECONNECTION_NUMBER, GET_METROPLEX_CONSECUTIVE_CONNECTION_DELAY, MAX_BEACON_PAYLOAD_SIZE, CURRENT_NOIBUJS_VERSION, GET_SCRIPT_ID } from '../constants.js';
6
+ import { GET_METROPLEX_BASE_SOCKET_URL, GET_METROPLEX_POST_URL, GET_MAX_METROPLEX_RECONNECTION_NUMBER, GET_METROPLEX_CONSECUTIVE_CONNECTION_DELAY, MAX_BEACON_PAYLOAD_SIZE, GET_SCRIPT_ID, CURRENT_NOIBUJS_VERSION } from '../constants.js';
8
7
  import ClientConfig from './ClientConfig.js';
9
8
  import StoredMetrics from './StoredMetrics.js';
10
9
  import StoredPageVisit from './StoredPageVisit.js';
@@ -19,9 +18,6 @@ import { Singleton } from '../monitors/BaseMonitor.js';
19
18
  */
20
19
  function getVideoRecorderType() {
21
20
  return __awaiter(this, void 0, void 0, function* () {
22
- if (Platform.OS === 'android') {
23
- return 'AndroidNative';
24
- }
25
21
  return 'RRWeb'; // should never happen
26
22
  });
27
23
  }
@@ -1,7 +1,7 @@
1
1
  import { __awaiter } from 'tslib';
2
- import { CURRENT_NOIBUJS_VERSION, GET_METROPLEX_METRICS_URL } from '../constants.js';
2
+ import { GET_METROPLEX_METRICS_URL, CURRENT_NOIBUJS_VERSION } from '../constants.js';
3
3
  import ClientConfig from './ClientConfig.js';
4
- import { getUserAgent, stringifyJSON } from '../utils/function.js';
4
+ import { stringifyJSON, getUserAgent } from '../utils/function.js';
5
5
  import { addSafeEventListener } from '../utils/eventlistener.js';
6
6
  import { unwrapNoibuWrapped } from '../utils/object.js';
7
7
  import { Singleton } from '../monitors/BaseMonitor.js';
package/dist/constants.js CHANGED
@@ -24,7 +24,7 @@ const CONTENT_TYPE = 'content-type';
24
24
  * Gets the script id from the cookie object, returns default if cannot be found
25
25
  */
26
26
  function GET_SCRIPT_ID() {
27
- return "1.0.104-rn-sdk-0.2.18" ;
27
+ return "1.0.104-rn-sdk-0.2.20" ;
28
28
  }
29
29
  /**
30
30
  * Gets the max metro recon number
@@ -3,7 +3,7 @@ import uuid from 'react-native-uuid';
3
3
  import { MetroplexRoute, Severity } from 'noibu-metroplex-ts-bindings';
4
4
  import { KeyboardInputMonitor } from '../monitors/KeyboardInputMonitor.js';
5
5
  import { PageMonitor } from '../monitors/PageMonitor.js';
6
- import { GET_METROPLEX_BASE_SOCKET_URL, GET_METROPLEX_BASE_HTTP_URL } from '../constants.js';
6
+ import { GET_METROPLEX_BASE_HTTP_URL, GET_METROPLEX_BASE_SOCKET_URL } from '../constants.js';
7
7
  import ClientConfig from '../api/ClientConfig.js';
8
8
  import { isNoibuJSAlreadyLoaded } from '../utils/function.js';
9
9
  import { PageVisitManager } from '../pageVisit/PageVisitManager.js';
@@ -1,7 +1,7 @@
1
1
  import Ajv from 'ajv';
2
2
  import mobileSchema from './schema/mobile/rr-mobile-schema.json.js';
3
3
  import webSchema from './schema/web/rr-web-schema.json.js';
4
- import { makeFullEvent, makeIncrementalEvent, makeMetaEvent, makeCustomEvent } from './transformer/transformers.js';
4
+ import { _drainWebViewSnapshots, makeCustomEvent, makeMetaEvent, makeIncrementalEvent, makeFullEvent } from './transformer/transformers.js';
5
5
 
6
6
  //import { captureException, captureMessage } from '@sentry/react'
7
7
  //@ts-ignore
@@ -41,6 +41,20 @@ function transformEventToWeb(event, validateTransformation) {
41
41
  }
42
42
  return event;
43
43
  }
44
+ function transformToWeb(mobileData) {
45
+ return mobileData.reduce((acc, event) => {
46
+ const transformed = transformEventToWeb(event);
47
+ const finalEvent = transformed ? transformed : event;
48
+ acc.push(finalEvent);
49
+ // Append any snapshots generated during this event's transformation
50
+ const snapshots = _drainWebViewSnapshots();
51
+ if (snapshots.length > 0) {
52
+ // Ensure snapshots have required fields; push as-is to preserve timestamps/order
53
+ snapshots.forEach((s) => acc.push(s));
54
+ }
55
+ return acc;
56
+ }, []);
57
+ }
44
58
  function validateAgainstWebSchema(data) {
45
59
  const validationResult = webSchemaValidator(data);
46
60
  if (!validationResult) {
@@ -54,4 +68,4 @@ function validateAgainstWebSchema(data) {
54
68
  return validationResult;
55
69
  }
56
70
 
57
- export { transformEventToWeb, validateAgainstWebSchema };
71
+ export { transformEventToWeb, transformToWeb, validateAgainstWebSchema };
@@ -216,6 +216,13 @@ export type wireframeRectangle = wireframeBase & {
216
216
  export type wireframeWebView = wireframeBase & {
217
217
  type: 'web_view';
218
218
  url?: string;
219
+ /**
220
+ * @description Contains the rrweb meta and full snapshot events for the webview content, as a JSON string.
221
+ * The string is an array of two events: [metaEvent, fullSnapshotEvent].
222
+ */
223
+ rrwebFullSnapshot: {
224
+ node: any[];
225
+ };
219
226
  };
220
227
  export type wireframePlaceholder = wireframeBase & {
221
228
  type: 'placeholder';
@@ -1539,6 +1539,18 @@ var definitions = {
1539
1539
  },
1540
1540
  y: {
1541
1541
  type: "number"
1542
+ },
1543
+ rrwebFullSnapshot: {
1544
+ description: "Contains the rrweb meta and full snapshot events for the webview content, as a JSON string. The string is an array of two events: [metaEvent, fullSnapshotEvent].",
1545
+ type: "object",
1546
+ properties: {
1547
+ node: {
1548
+ type: "string"
1549
+ }
1550
+ },
1551
+ required: [
1552
+ "node"
1553
+ ]
1542
1554
  }
1543
1555
  },
1544
1556
  required: [
@@ -1,6 +1,6 @@
1
1
  import { NodeType } from '../mobile.types.js';
2
2
  import { isLight } from './colors.js';
3
- import { _isPositiveInteger, makePlaceholderElement, KEYBOARD_ID, NAVIGATION_BAR_ID, STATUS_BAR_ID, BACKGROUND } from './transformers.js';
3
+ import { _isPositiveInteger, makePlaceholderElement, NAVIGATION_BAR_ID, STATUS_BAR_ID, BACKGROUND, KEYBOARD_ID } from './transformers.js';
4
4
  import { asStyleString, makeStylesString } from './wireframeStyle.js';
5
5
 
6
6
  let navigationBackgroundColor = undefined;
@@ -2,6 +2,7 @@ import { customEvent, fullSnapshotEvent, incrementalSnapshotEvent, metaEvent } f
2
2
  import { fullSnapshotEvent as MobileFullSnapshotEvent, keyboardEvent, metaEvent as MobileMetaEvent, MobileIncrementalSnapshotEvent, serializedNodeWithId, wireframe, wireframeDiv, wireframeNavigationBar, wireframeStatusBar } from '../mobile.types';
3
3
  import { ConversionContext, ConversionResult } from './types';
4
4
  export declare const BACKGROUND = "#f3f4ef";
5
+ export declare function _drainWebViewSnapshots(): any[];
5
6
  export declare const NAVIGATION_BAR_ID = 8;
6
7
  export declare const KEYBOARD_ID = 10;
7
8
  export declare const STATUS_BAR_PARENT_ID = 11;
@@ -1,11 +1,20 @@
1
- import { IncrementalSource, EventType } from '../rrweb.js';
1
+ import { EventType, IncrementalSource } from '../rrweb.js';
2
2
  import { isObject } from '../../utils.js';
3
3
  import { NodeType } from '../mobile.types.js';
4
- import { makeOpenKeyboardPlaceholder, makeStatusBar, makeNavigationBar } from './screen-chrome.js';
5
- import { makeHTMLStyles, makeBodyStyles, makeStylesString, asStyleString, makeMinimalStyles, makeDeterminateProgressStyles, makeIndeterminateProgressStyles, makePositionStyles, makeColorStyles } from './wireframeStyle.js';
4
+ import { makeOpenKeyboardPlaceholder, makeNavigationBar, makeStatusBar } from './screen-chrome.js';
5
+ import { makeBodyStyles, makeHTMLStyles, makeStylesString, asStyleString, makeDeterminateProgressStyles, makeIndeterminateProgressStyles, makeMinimalStyles, makePositionStyles, makeColorStyles } from './wireframeStyle.js';
6
+ import { transform } from './webview-transformer.js';
6
7
 
7
8
  const BACKGROUND = '#f3f4ef';
8
9
  const FOREGROUND = '#35373e';
10
+ // Buffer to collect snapshots generated during element transformations (e.g., WebView)
11
+ // These are later drained and appended to the top-level transformed JSON.
12
+ let __pendingWebViewSnapshots = [];
13
+ function _drainWebViewSnapshots() {
14
+ const out = __pendingWebViewSnapshots;
15
+ __pendingWebViewSnapshots = [];
16
+ return out;
17
+ }
9
18
  /**
10
19
  * generates a sequence of ids
11
20
  * from 100 to 9,999,999
@@ -183,11 +192,12 @@ function makeTextElement(wireframe, children, context) {
183
192
  };
184
193
  }
185
194
  function makeWebViewElement(wireframe, children, context) {
186
- const labelledWireframe = Object.assign({}, wireframe);
187
- if ('url' in wireframe) {
188
- labelledWireframe.label = wireframe.url;
195
+ const webViewWireframe = wireframe;
196
+ const { frame, snapshots } = transform(webViewWireframe);
197
+ if (Array.isArray(snapshots) && snapshots.length > 0) {
198
+ __pendingWebViewSnapshots.push(...snapshots);
189
199
  }
190
- return makePlaceholderElement(labelledWireframe, children, context);
200
+ return { result: frame, context };
191
201
  }
192
202
  function makePlaceholderElement(wireframe, children, context) {
193
203
  var _a, _b;
@@ -1151,4 +1161,4 @@ function makeCSSReset(context) {
1151
1161
  };
1152
1162
  }
1153
1163
 
1154
- export { BACKGROUND, KEYBOARD_ID, NAVIGATION_BAR_ID, STATUS_BAR_ID, STATUS_BAR_PARENT_ID, _isPositiveInteger, dataURIOrPNG, makeCustomEvent, makeDivElement, makeFullEvent, makeIncrementalEvent, makeMetaEvent, makePlaceholderElement, stripBarsFromWireframes };
1164
+ export { BACKGROUND, KEYBOARD_ID, NAVIGATION_BAR_ID, STATUS_BAR_ID, STATUS_BAR_PARENT_ID, _drainWebViewSnapshots, _isPositiveInteger, dataURIOrPNG, makeCustomEvent, makeDivElement, makeFullEvent, makeIncrementalEvent, makeMetaEvent, makePlaceholderElement, stripBarsFromWireframes };
@@ -0,0 +1,9 @@
1
+ import { wireframeWebView as WebView } from "../mobile.types";
2
+ type Node = any;
3
+ type Snapshot = any;
4
+ type Result = {
5
+ frame: Node;
6
+ snapshots: Snapshot[];
7
+ };
8
+ export declare function transform(webView: WebView): Result;
9
+ export {};
@@ -0,0 +1,124 @@
1
+ function transform(webView) {
2
+ const frameId = 999999999;
3
+ const rootId = frameId + 1;
4
+ const result = { frame: null, snapshots: [] };
5
+ try {
6
+ const snapshots = webView.rrwebFullSnapshot.node;
7
+ const fullSnapshotIndex = snapshots.findIndex((event) => event.type === 2);
8
+ const fullSnapshot = snapshots[fullSnapshotIndex];
9
+ const incrementalSnapshots = snapshots.filter((snapshot, index) => snapshot.type === 3 && index > fullSnapshotIndex);
10
+ const syntheticSnapshot = createSyntheticSnapshot(fullSnapshot, frameId, rootId);
11
+ incrementalSnapshots.forEach((snapshot) => patchIncrementalSnapshot(snapshot, rootId));
12
+ result.frame = createFrameNode(frameId);
13
+ ;
14
+ result.snapshots.push(syntheticSnapshot, ...incrementalSnapshots);
15
+ }
16
+ catch (error) {
17
+ console.error(error);
18
+ }
19
+ return result;
20
+ }
21
+ function createSyntheticSnapshot(snapshot, frameId, rootId) {
22
+ const childNodes = snapshot.data.node.childNodes;
23
+ const htmlNodeIndex = childNodes.findIndex((node) => node.type === 2 && node.tagName === "html");
24
+ const htmlNode = childNodes[htmlNodeIndex];
25
+ patch(htmlNode);
26
+ return {
27
+ type: 3,
28
+ data: {
29
+ source: 0,
30
+ adds: [
31
+ {
32
+ parentId: frameId,
33
+ nextId: null,
34
+ node: {
35
+ type: 0,
36
+ childNodes: [htmlNode],
37
+ id: rootId,
38
+ }
39
+ }
40
+ ],
41
+ removes: [],
42
+ texts: [],
43
+ attributes: [],
44
+ isAttachIframe: true,
45
+ },
46
+ timestamp: snapshot.timestamp,
47
+ };
48
+ function patch(node) {
49
+ node.rootId = rootId;
50
+ if (node.id) {
51
+ node.id = rootId + node.id;
52
+ }
53
+ if (node.childNodes) {
54
+ node.childNodes.forEach(patch);
55
+ }
56
+ }
57
+ }
58
+ function patchIncrementalSnapshot(snapshot, rootId) {
59
+ const { data } = snapshot;
60
+ const { adds, removes, attributes, positions } = data;
61
+ if (data.id) {
62
+ data.id = rootId + data.id;
63
+ }
64
+ if (adds === null || adds === void 0 ? void 0 : adds.length) {
65
+ adds.forEach(patchAdd);
66
+ }
67
+ if (removes === null || removes === void 0 ? void 0 : removes.length) {
68
+ removes.forEach(patchRemove);
69
+ }
70
+ if (attributes === null || attributes === void 0 ? void 0 : attributes.length) {
71
+ attributes.forEach(patchAttribute);
72
+ }
73
+ if (positions === null || positions === void 0 ? void 0 : positions.length) {
74
+ positions.forEach(patchAttribute);
75
+ }
76
+ function patchNode(node) {
77
+ if (node.id) {
78
+ node.id = rootId + node.id;
79
+ }
80
+ if (node.rootId) {
81
+ node.rootId = rootId + node.rootId;
82
+ }
83
+ if (node.childNodes) {
84
+ node.childNodes.forEach(patchNode);
85
+ }
86
+ }
87
+ function patchAdd(add) {
88
+ if (add.parentId) {
89
+ add.parentId = rootId + add.parentId;
90
+ }
91
+ if (add.nextId) {
92
+ add.nextId = rootId + add.nextId;
93
+ }
94
+ if (add.node) {
95
+ patchNode(add.node);
96
+ }
97
+ }
98
+ function patchRemove(remove) {
99
+ if (remove.parentId) {
100
+ remove.parentId = rootId + remove.parentId;
101
+ }
102
+ if (remove.id) {
103
+ remove.id = rootId + remove.id;
104
+ }
105
+ }
106
+ function patchAttribute(attribute) {
107
+ if (attribute.id) {
108
+ attribute.id = rootId + attribute.id;
109
+ }
110
+ }
111
+ }
112
+ function createFrameNode(id) {
113
+ return {
114
+ type: 2,
115
+ tagName: "iframe",
116
+ attributes: {
117
+ style: "width: 100%; height: 100%;"
118
+ },
119
+ childNodes: [],
120
+ id: id,
121
+ };
122
+ }
123
+
124
+ export { transform };
@@ -1,4 +1,4 @@
1
- import { asString, isStackTrace } from '../utils/function.js';
1
+ import { isStackTrace, asString } from '../utils/function.js';
2
2
  import { saveErrorToPagevisit } from '../pageVisit/pageVisitEventError.js';
3
3
  import { replace } from '../utils/object.js';
4
4
  import { Singleton } from './BaseMonitor.js';
@@ -1,7 +1,7 @@
1
1
  import { EventDebouncer } from '../pageVisit/EventDebouncer.js';
2
2
  import { TextInput } from 'react-native';
3
3
  import { Singleton } from './BaseMonitor.js';
4
- import { EventType, UserStepType } from 'noibu-metroplex-ts-bindings';
4
+ import { UserStepType, EventType } from 'noibu-metroplex-ts-bindings';
5
5
 
6
6
  /** @module KeyboardInputMonitor */
7
7
  /**
@@ -8,9 +8,9 @@ import { MetroplexMessageType, EventType } from 'noibu-metroplex-ts-bindings';
8
8
  /** @module PageVisitEventHTTP */
9
9
  /** http event manager */
10
10
  // maximum number of HTTP data events including errors to collect per page visit
11
- const MAX_HTTP_DATA_IF_ERROR_EVENT_COUNT = 2800;
11
+ const MAX_HTTP_DATA_IF_ERROR_EVENT_COUNT = 5000;
12
12
  // maximum number of HTTP data events to collect per page visit
13
- const MAX_HTTP_DATA_EVENT_COUNT = 2500;
13
+ const MAX_HTTP_DATA_EVENT_COUNT = 5000;
14
14
  /** if no value or it's less than 0, fallback to 0 */
15
15
  const validate = (value) => (!value || value < 0 ? 0 : value);
16
16
  /** Saves the HTTP event to the pageVisit Queue */
@@ -1,5 +1,5 @@
1
1
  import { __awaiter } from 'tslib';
2
- import { isValidURL, getJSStack, stringifyJSON, getMaxSubstringAllowed, asString } from '../utils/function.js';
2
+ import { isValidURL, stringifyJSON, getMaxSubstringAllowed, getJSStack, asString } from '../utils/function.js';
3
3
  import ClientConfig from '../api/ClientConfig.js';
4
4
  import StoredMetrics from '../api/StoredMetrics.js';
5
5
  import { EventDebouncer } from './EventDebouncer.js';
@@ -27,10 +27,6 @@ export default class SessionRecorder extends Singleton {
27
27
  * of the buffer if it exceeds max size
28
28
  */
29
29
  handleRecorderEvent(recorderEvent: RecorderEvent): Promise<void>;
30
- /** Compress event */
31
- private pack;
32
- /** Compresses the snapshot */
33
- private static compress;
34
30
  /** builds a log message with debug info */
35
31
  buildDebugMessage(eventName: string, totalVideoTime: number, sessionLength: number): string;
36
32
  /**
@@ -1,6 +1,5 @@
1
1
  import { __awaiter } from 'tslib';
2
- import { strToU8, zlibSync, strFromU8 } from 'fflate';
3
- import { LogLevel, initialize, subscribeToNativeEvent } from './nativeSessionRecorderSubscription.js';
2
+ import { initialize, subscribeToNativeEvent } from './nativeSessionRecorderSubscription.js';
4
3
  import StoredMetrics from '../api/StoredMetrics.js';
5
4
  import ClientConfig from '../api/ClientConfig.js';
6
5
  import { stringifyJSON } from '../utils/function.js';
@@ -8,7 +7,6 @@ import { MAX_TIME_FOR_UNSENT_DATA_MILLIS } from '../constants.js';
8
7
  import MetroplexSocket from '../api/MetroplexSocket.js';
9
8
  import { addSafeEventListener } from '../utils/eventlistener.js';
10
9
  import { noibuLog } from '../utils/log.js';
11
- import { Platform } from 'react-native';
12
10
  import { Singleton } from '../monitors/BaseMonitor.js';
13
11
  import { Severity, MetroplexMessageType } from 'noibu-metroplex-ts-bindings';
14
12
 
@@ -36,10 +34,7 @@ class SessionRecorder extends Singleton {
36
34
  this.freezingEvents = false;
37
35
  this.setupUnloadHandler();
38
36
  this.setupPostMetricsHandler();
39
- const nativeSessionRecorderConfig = {
40
- logLevel: LogLevel.Verbose,
41
- };
42
- initialize('abc1234', nativeSessionRecorderConfig);
37
+ initialize();
43
38
  addSafeEventListener(window, 'click', () => this.handleFragPost());
44
39
  }
45
40
  /** Sets up the page hide handler to try to push remaining video events */
@@ -147,9 +142,9 @@ class SessionRecorder extends Singleton {
147
142
  ClientConfig.getInstance().postInternalError({ msg: `Detected time rewind. Client has been disabled.` }, true, Severity.ERROR, true);
148
143
  return;
149
144
  }
150
- const packedEvent = yield this.pack(recorderEvent.message);
145
+ //
151
146
  // Buffer the event for sending to metroplex
152
- this.eventBuffer.push(packedEvent);
147
+ this.eventBuffer.push(recorderEvent.message);
153
148
  // Check if the event was a click or a double click. This is true if the root type is
154
149
  // incremental snapshot (3) and the data source is mouse interaction data (2).
155
150
  // Finally, we capture a click (2) or double click (4) event.
@@ -161,25 +156,6 @@ class SessionRecorder extends Singleton {
161
156
  }
162
157
  });
163
158
  }
164
- /** Compress event */
165
- pack(recorderEvent) {
166
- return __awaiter(this, void 0, void 0, function* () {
167
- // return JSON.stringify(recorderEvent);
168
- if (Platform.OS === 'ios') {
169
- return recorderEvent;
170
- }
171
- else {
172
- return SessionRecorder.compress(recorderEvent);
173
- }
174
- });
175
- }
176
- /** Compresses the snapshot */
177
- static compress(snapshot) {
178
- const uncompressedString = stringifyJSON(snapshot);
179
- const uncompressedData = strToU8(uncompressedString);
180
- const compressedData = zlibSync(uncompressedData, { level: 1 });
181
- return strFromU8(compressedData, true);
182
- }
183
159
  /** builds a log message with debug info */
184
160
  buildDebugMessage(eventName, totalVideoTime, sessionLength) {
185
161
  return JSON.stringify({ eventName, totalVideoTime, sessionLength });
@@ -34,29 +34,8 @@ export interface SessionRecorderConfig {
34
34
  }
35
35
  /**
36
36
  * Initializes the Noibu - Session recording SDK if the API level is supported.
37
- * param projectId [REQUIRED] The session recording project id to send data to.
38
- * param config [OPTIONAL] The sessionreplay config, if not provided default values are used.
39
37
  */
40
- export declare function initialize(projectId: string, config?: SessionRecorderConfig): void;
41
- /**
42
- * Sets a custom user id that can be used to identify the user. It has less
43
- * restrictions than the userId parameter. You can pass any string and
44
- * you can filter on it on the dashboard side. If you need the most efficient
45
- * filtering on the dashboard, use the userId parameter if possible.
46
- * <p>
47
- * Note: custom user id cannot be null or empty, or consists only of whitespaces.
48
- * </p>
49
- * @param customUserId The custom user id to set.
50
- */
51
- export declare function setCustomUserId(customUserId: string): void;
52
- /**
53
- * Sets a custom session id that can be used to identify the session.
54
- * <p>
55
- * Note: custom session id cannot be null or empty, or consists only of whitespaces.
56
- * </p>
57
- * @param customSessionId The custom session id to set.
58
- */
59
- export declare function setCustomSessionId(customSessionId: string): void;
38
+ export declare function initialize(): void;
60
39
  export type RecorderEvent = import('./types').RecorderEvent;
61
40
  export type UnsubscribeFn = import('./types').UnsubscribeFn;
62
41
  export declare function subscribeToNativeEvent(callback: (event: RecorderEvent) => void): UnsubscribeFn;
@@ -1,6 +1,7 @@
1
- import { Platform, NativeEventEmitter, NativeModules } from 'react-native';
1
+ import { __rest } from 'tslib';
2
+ import { NativeModules, Platform, NativeEventEmitter } from 'react-native';
2
3
  import { noibuLog } from '../utils/log.js';
3
- import { transformEventToWeb } from '../mobileTransformer/mobile-replay/index.js';
4
+ import { transformToWeb } from '../mobileTransformer/mobile-replay/index.js';
4
5
 
5
6
  const LINKING_ERROR = `The package 'noibu-session-replay' doesn't seem to be linked. Make sure: \n\n` +
6
7
  // Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + TODO: add back when iOS is supported.
@@ -22,19 +23,12 @@ var LogLevel;
22
23
  })(LogLevel || (LogLevel = {}));
23
24
  /**
24
25
  * Initializes the Noibu - Session recording SDK if the API level is supported.
25
- * param projectId [REQUIRED] The session recording project id to send data to.
26
- * param config [OPTIONAL] The sessionreplay config, if not provided default values are used.
27
26
  */
28
- function initialize(projectId, config) {
27
+ function initialize() {
29
28
  if (Platform.OS === 'ios') {
30
29
  return;
31
30
  }
32
- if (!(typeof config === 'object' || typeof config === 'undefined')) {
33
- throw Error('Invalid session recording initialization arguments. Please check the docs for assitance.');
34
- }
35
31
  nativeModuleEmitter = new NativeEventEmitter(NativeSessionRecorder);
36
- // applying default values
37
- const { userId = null, logLevel = LogLevel.None, allowMeteredNetworkUsage = false, enableWebViewCapture = true, allowedDomains = ['*'], disableOnLowEndDevices = false, maximumDailyNetworkUsageInMB = null, } = config !== null && config !== void 0 ? config : {};
38
32
  if (!SupportedPlatforms.includes(Platform.OS)) {
39
33
  noibuLog(`Noibu - Session recording supports ${SupportedPlatforms.join(', ')} only for now.`);
40
34
  return;
@@ -43,10 +37,7 @@ function initialize(projectId, config) {
43
37
  noibuLog('Noibu - Session recording did not initialize properly.', LINKING_ERROR);
44
38
  return;
45
39
  }
46
- // We use two parameters because the react method parameters do not accept nullable primitive types.
47
- const enableDailyNetworkUsageLimit = maximumDailyNetworkUsageInMB != null;
48
- const refinedMaximumDailyNetworkUsageInMB = maximumDailyNetworkUsageInMB !== null && maximumDailyNetworkUsageInMB !== void 0 ? maximumDailyNetworkUsageInMB : 0;
49
- NativeSessionRecorder.initialize(projectId, userId, logLevel, allowMeteredNetworkUsage, enableWebViewCapture, allowedDomains, disableOnLowEndDevices, enableDailyNetworkUsageLimit, refinedMaximumDailyNetworkUsageInMB);
40
+ NativeSessionRecorder.initialize();
50
41
  }
51
42
  /**
52
43
  * Subscribes to a native event emitted by the Noibu Session Recorder.
@@ -70,7 +61,15 @@ function subscribeToNativeEvent(callback) {
70
61
  throw new Error('You have to initialize Noibu Session Recorder first');
71
62
  }
72
63
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
73
- nativeModuleEmitter.addListener('noibuRecordingEvent', callback);
64
+ // const subscription = nativeModuleEmitter.addListener('noibuRecordingEvent', callback);
65
+ nativeModuleEmitter.addListener('noibuRecordingEvent', event => {
66
+ const message = event.message;
67
+ const _a = JSON.parse(message), { data } = _a, rest = __rest(_a, ["data"]);
68
+ noibuLog('New noibu recording event', rest);
69
+ const transformedEvents = transformToWeb([Object.assign(Object.assign({}, rest), { data })]);
70
+ noibuLog('after transformation: ', transformedEvents);
71
+ transformedEvents.forEach((e) => callback({ message: e }));
72
+ });
74
73
  // return () => subscription.remove();
75
74
  }
76
75
  if (Platform.OS === 'ios') {
@@ -80,11 +79,11 @@ function subscribeToNativeEvent(callback) {
80
79
  }
81
80
  nativeModuleEmitter.addListener('iosPOCRecordingEvent', event => {
82
81
  try {
83
- const transformedEvent = transformEventToWeb(event.message);
84
- callback({ message: transformedEvent });
82
+ const transformedEvents = transformToWeb([event.message]);
83
+ transformedEvents.forEach((e) => callback({ message: e }));
85
84
  }
86
85
  catch (e) {
87
- noibuLog(`[Error] transformEventToWeb failed: ${e.message}`);
86
+ noibuLog(`[Error] transformToWeb failed: ${e.message}`);
88
87
  }
89
88
  });
90
89
  if (!isIOSInitialized) {
@@ -1,91 +1,4 @@
1
- type Color = {
2
- a: number;
3
- b: number;
4
- r: number;
5
- g: number;
6
- };
7
- type Paint = {
8
- strokeJoin: number;
9
- strokeWidth: number;
10
- strokeCap: number;
11
- color: Color;
12
- dither: boolean;
13
- blendMode: number;
14
- style: number;
15
- antiAlias: boolean;
16
- strokeMiter: number;
17
- };
18
- type Rect = {
19
- top: number;
20
- left: number;
21
- bottom: number;
22
- right: number;
23
- };
24
- type Command = {
25
- name?: string;
26
- id?: number;
27
- type: string;
28
- isClipRectSource?: boolean;
29
- rect?: Rect;
30
- paintIndex?: number;
31
- op?: number;
32
- antiAlias?: boolean;
33
- matrix?: number[];
34
- };
35
- type ViewNode = {
36
- viewX: number;
37
- visible: boolean;
38
- processedText$delegate: {
39
- _value: any;
40
- initializer: any;
41
- };
42
- viewY: number;
43
- viewWidth: number;
44
- clickable: boolean;
45
- viewHeight: number;
46
- isMasked: boolean;
47
- type: string;
48
- renderNodeId: number;
49
- isWebView: boolean;
50
- ignoreClicks: boolean;
51
- children: ViewNode[];
52
- width: number;
53
- x: number;
54
- y: number;
55
- id: number;
56
- text: string;
57
- height: number;
58
- backgroundColor?: number;
59
- };
60
- type ViewHierarchy = {
61
- root: ViewNode;
62
- visibleFragments: any[];
63
- timestamp: number;
64
- };
65
- type SubPicture = {
66
- subPictures: any[];
67
- images: any[];
68
- screenWidth: number;
69
- textBlobs: any[];
70
- density: number;
71
- vertices: any[];
72
- screenHeight: number;
73
- activityName: string;
74
- paints: Paint[];
75
- typefaces: any[];
76
- viewHierarchy: ViewHierarchy;
77
- paths: any[];
78
- activityHashCode: number;
79
- commands: Command[];
80
- timestamp: number;
81
- };
82
1
  export type RecorderEvent = {
83
- message: NativeFrames;
2
+ message: string;
84
3
  };
85
- export type NativeFrames = {
86
- p: (number | boolean | SubPicture)[][];
87
- a: (number[] | (number | string | string[])[])[];
88
- e: (string | number)[];
89
- } | string;
90
4
  export type UnsubscribeFn = () => void;
91
- export {};
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "noibu-react-native",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "targetNjsVersion": "1.0.104",
5
5
  "description": "React-Native SDK for NoibuJS to collect errors in React-Native applications",
6
6
  "main": "dist/entry/index.js",
7
7
  "types": "dist/entry/index.d.ts",
8
8
  "files": [
9
9
  "android",
10
+ "android/settings.gradle",
10
11
  "dist/*",
11
12
  "README.md",
12
13
  "CHANGELOG.md",
@@ -41,14 +42,12 @@
41
42
  },
42
43
  "optionalDependencies": {
43
44
  "@react-native-async-storage/async-storage": "^1.19.0",
44
- "fflate": "^0.8.2",
45
45
  "react-native-navigation": ">=2.29.0",
46
46
  "react-native-url-polyfill": "^1.3.0",
47
47
  "react-native-uuid": "^2.0.1"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@react-native-async-storage/async-storage": "^1.19.0",
51
- "fflate": "^0.8.2",
52
51
  "react": ">=16.13.1 <=18",
53
52
  "react-native": ">=0.63.0",
54
53
  "react-native-navigation": ">=2.29.0",
@@ -71,7 +70,7 @@
71
70
  "@tsconfig/react-native": "^3.0.2",
72
71
  "@types/jest": "^29.5.14",
73
72
  "@types/node": "^20.2.3",
74
- "@types/react": "16.14.62",
73
+ "@types/react": "18.2.6",
75
74
  "@types/react-test-renderer": "^18.0.0",
76
75
  "@typescript-eslint/eslint-plugin": "^8.19.1",
77
76
  "@typescript-eslint/parser": "^8.19.1",
@@ -83,7 +82,6 @@
83
82
  "eslint-plugin-jsdoc": "^50.6.1",
84
83
  "eslint-plugin-prettier": "^5.2.1",
85
84
  "eslint-plugin-react": "^7.37.3",
86
- "fflate": "0.8.2",
87
85
  "jest": "^29.7.0",
88
86
  "prettier": "^3.4.2",
89
87
  "react": "16.13.1",