noibu-react-native 0.2.28 → 0.2.30
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 +18 -0
- package/README.md +26 -8
- package/android/src/legacy/java/com/noibu/sessionreplay/reactnative/NoibuSessionReplayModule.kt +11 -2
- package/android/src/newarch/java/com/noibu/sessionreplay/reactnative/NoibuSessionRecorderModule.kt +24 -5
- package/dist/constants.js +1 -1
- package/dist/native/NativeNoibuSessionRecorder.d.ts +3 -2
- package/dist/native/NativeNoibuSessionRecorder.js +21 -2
- package/dist/sessionRecorder/nativeSessionRecorderSubscription.js +58 -12
- package/package.json +1 -1
- package/src/native/NativeNoibuSessionRecorder.ts +37 -5
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
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.30
|
|
7
|
+
|
|
8
|
+
- **Improve documentation for SDK configuration**
|
|
9
|
+
|
|
10
|
+
## 0.2.29
|
|
11
|
+
|
|
12
|
+
- **React Native use new architecture using a poll mechanism rather than the old bridge**
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## 0.2.28
|
|
16
|
+
|
|
17
|
+
- **Fix a broken build where SDK would only work with iOS 26+**
|
|
18
|
+
|
|
19
|
+
## 0.2.27
|
|
20
|
+
|
|
21
|
+
- **Recycling UI/library-managed bitmaps caused Glide/Fresco to crash when returning
|
|
22
|
+
recycled bitmaps to pools. Let GC manage lifecycle; still compress+store PNG.**
|
|
23
|
+
|
|
6
24
|
## 0.2.26
|
|
7
25
|
- **iOS: iOS 26 Support**
|
|
8
26
|
|
package/README.md
CHANGED
|
@@ -69,12 +69,18 @@ That's it! First time the module is set up, it runs an init and starts listening
|
|
|
69
69
|
|
|
70
70
|
- `config` which consists of
|
|
71
71
|
- `@property domain {string}` - indicates which Noibu dashboard session recordings should go to ([learn more about domains](https://help.noibu.com/hc/en-us/articles/4846518088845-Domains-Overview))
|
|
72
|
-
- `@property [blockedElements] {string[]}` - lets you specify component ids to be ignored by SDK when collecting error information
|
|
73
|
-
- `@property [enableHttpDataCollection] {boolean}` - indicates whether SDK should collect HTTP information like headers or body from requests
|
|
74
|
-
- `@property [listOfUrlsToCollectHttpDataFrom] {string[]}` - is an
|
|
75
|
-
- `@property [httpPiiBlockingPatterns] {RegExp[]}` - allows you to specify RegEx patterns for what PII information should be removed from JSON request and response data for the value in a key value pair
|
|
76
|
-
- `@property [fuzzyFieldsToRedact] {string[]}` - allows you to specify fuzzy strings for what PII information should be removed from JSON request and response data based on the key in a key value pair
|
|
77
|
-
- `@property [exactFieldsToRedact] {string[]}` - allows you to specify strict strings for what PII information should be removed from JSON request and response data based on the key in a key value pair
|
|
72
|
+
- `@property [blockedElements] {string[]}` - defaults see below; lets you specify component ids to be ignored by SDK when collecting error information
|
|
73
|
+
- `@property [enableHttpDataCollection] {boolean}` - default `false`; indicates whether SDK should collect HTTP information like headers or body from requests
|
|
74
|
+
- `@property [listOfUrlsToCollectHttpDataFrom] {string[]}` - is an allowlist of URLs to allow HTTP data collection from, works best with `enableHttpDataCollection` enabled
|
|
75
|
+
- `@property [httpPiiBlockingPatterns] {RegExp[]}` - defaults see below; allows you to specify RegEx patterns for what PII information should be removed from JSON request and response data for the value in a key value pair
|
|
76
|
+
- `@property [fuzzyFieldsToRedact] {string[]}` - defaults see below; allows you to specify fuzzy strings for what PII information should be removed from JSON request and response data based on the key in a key value pair
|
|
77
|
+
- `@property [exactFieldsToRedact] {string[]}` - defaults see below; allows you to specify strict strings for what PII information should be removed from JSON request and response data based on the key in a key value pair
|
|
78
|
+
|
|
79
|
+
### Note on overriding properties
|
|
80
|
+
|
|
81
|
+
The properties `blockedElements`, `httpPiiBlockingPatterns`, `fuzzyFieldsToRedact`, and `exactFieldsToRedact` have
|
|
82
|
+
default values.
|
|
83
|
+
If you want to override them or add new ones, you must also pass the original values, as seen below.
|
|
78
84
|
|
|
79
85
|
Example:
|
|
80
86
|
|
|
@@ -199,11 +205,23 @@ Adds a custom attribute to the session.
|
|
|
199
205
|
- `@param {any} value` - It's value, should be a JSON.stringify-able type.
|
|
200
206
|
- `@returns {Promise<string>}` - A success message, or validation failure cause.
|
|
201
207
|
|
|
208
|
+
##### Example custom attributes we recommend
|
|
209
|
+
|
|
210
|
+
- `appVersion` The version of your application the user's session is running.
|
|
211
|
+
- `customerId` The ID of the customer the user's session is associated with.
|
|
212
|
+
- `orderId` The ID of the order associated with the user's session.
|
|
213
|
+
- `voC` The Voice of Customer survey ID the user's session is associated with.
|
|
214
|
+
|
|
215
|
+
##### Limitations on attribute name lengths
|
|
216
|
+
|
|
217
|
+
If the character length of the attribute name exceeds 50 characters, the attribute will fail to be added.
|
|
218
|
+
See example below:
|
|
219
|
+
|
|
202
220
|
```js
|
|
203
221
|
import { NoibuJS } from 'noibu-react-native';
|
|
204
222
|
|
|
205
|
-
NoibuJS.addCustomAttribute('
|
|
206
|
-
NoibuJS.addCustomAttribute('
|
|
223
|
+
NoibuJS.addCustomAttribute('stripeApiKey', 'some-api-key'); // Promise<SUCCESS>
|
|
224
|
+
NoibuJS.addCustomAttribute('system_configuration_payment_gateway_stripe_api_key_prod', 'some-api-key'); // Promise<NAME_TOO_LONG>
|
|
207
225
|
```
|
|
208
226
|
|
|
209
227
|
#### `addError(customError: Error) => string`
|
package/android/src/legacy/java/com/noibu/sessionreplay/reactnative/NoibuSessionReplayModule.kt
CHANGED
|
@@ -8,6 +8,7 @@ import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
|
8
8
|
import com.facebook.react.bridge.ReactMethod
|
|
9
9
|
|
|
10
10
|
// Update the import statements to point to the KMP shared module's package
|
|
11
|
+
// Import from published artifact when building outside monorepo; otherwise same package
|
|
11
12
|
import com.noibu.mobile.android.sessionreplay.Noibu
|
|
12
13
|
import com.noibu.mobile.android.sessionreplay.NoibuConfig
|
|
13
14
|
|
|
@@ -23,12 +24,20 @@ class NoibuSessionReplayModule(reactContext: ReactApplicationContext) :
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
private fun sendEvent(eventName: String, params: WritableMap?) {
|
|
26
|
-
|
|
27
|
+
try {
|
|
28
|
+
reactContext?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)?.emit(eventName, params)
|
|
29
|
+
} catch (t: Throwable) {
|
|
30
|
+
Log.e(NAME, "Failed to emit $eventName", t)
|
|
31
|
+
}
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
@ReactMethod
|
|
30
35
|
fun initialize( promise: Promise ) {
|
|
31
|
-
val context = currentActivity?.applicationContext ?: reactContext?.applicationContext
|
|
36
|
+
val context = currentActivity?.applicationContext ?: reactContext?.applicationContext
|
|
37
|
+
if (context == null) {
|
|
38
|
+
promise.reject("E_INIT", IllegalStateException("No application context"))
|
|
39
|
+
return
|
|
40
|
+
}
|
|
32
41
|
val config = NoibuConfig(
|
|
33
42
|
sessionReplayEnabled = true,
|
|
34
43
|
maskAllTextInputs = false,
|
package/android/src/newarch/java/com/noibu/sessionreplay/reactnative/NoibuSessionRecorderModule.kt
CHANGED
|
@@ -7,15 +7,18 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
7
7
|
import com.facebook.react.bridge.ReactMethod
|
|
8
8
|
import com.facebook.react.turbomodule.core.interfaces.TurboModule
|
|
9
9
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
10
|
-
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
11
10
|
import com.noibu.mobile.android.sessionreplay.Noibu
|
|
12
11
|
import com.noibu.mobile.android.sessionreplay.NoibuConfig
|
|
12
|
+
import java.util.concurrent.ConcurrentLinkedQueue
|
|
13
13
|
|
|
14
14
|
class NoibuSessionRecorderModule(val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), TurboModule {
|
|
15
15
|
companion object {
|
|
16
16
|
private const val TAG = "NoibuSessionRecorder"
|
|
17
|
+
private const val MAX_BATCH = 100
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
private val eventQueue: ConcurrentLinkedQueue<String> = ConcurrentLinkedQueue()
|
|
21
|
+
|
|
19
22
|
init {
|
|
20
23
|
Log.i(TAG, "[new-arch] Module constructed")
|
|
21
24
|
}
|
|
@@ -32,10 +35,8 @@ class NoibuSessionRecorderModule(val reactContext: ReactApplicationContext) : Re
|
|
|
32
35
|
maskAllTextInputs = false,
|
|
33
36
|
)
|
|
34
37
|
Noibu.setup(context, config) { param ->
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
38
|
-
.emit("noibuRecordingEvent", map)
|
|
38
|
+
// Enqueue raw mobile event JSON for JS to consume via TurboModule polling
|
|
39
|
+
eventQueue.add(param)
|
|
39
40
|
true
|
|
40
41
|
}
|
|
41
42
|
promise.resolve(true)
|
|
@@ -44,4 +45,22 @@ class NoibuSessionRecorderModule(val reactContext: ReactApplicationContext) : Re
|
|
|
44
45
|
promise.reject("E_INIT", t)
|
|
45
46
|
}
|
|
46
47
|
}
|
|
48
|
+
|
|
49
|
+
@ReactMethod
|
|
50
|
+
fun consumeEvents(promise: Promise) {
|
|
51
|
+
try {
|
|
52
|
+
val array = Arguments.createArray()
|
|
53
|
+
var count = 0
|
|
54
|
+
var item = eventQueue.poll()
|
|
55
|
+
while (item != null && count < MAX_BATCH) {
|
|
56
|
+
array.pushString(item)
|
|
57
|
+
count += 1
|
|
58
|
+
item = eventQueue.poll()
|
|
59
|
+
}
|
|
60
|
+
promise.resolve(array)
|
|
61
|
+
} catch (t: Throwable) {
|
|
62
|
+
Log.e(TAG, "consumeEvents() failed", t)
|
|
63
|
+
promise.reject("E_CONSUME", t)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
47
66
|
}
|
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.
|
|
27
|
+
return "1.0.104-rn-sdk-0.2.30" ;
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Gets the max metro recon number
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TurboModule } from 'react-native';
|
|
2
2
|
export interface Spec extends TurboModule {
|
|
3
3
|
initialize(): Promise<boolean>;
|
|
4
|
+
consumeEvents?(): Promise<string[]>;
|
|
4
5
|
}
|
|
5
|
-
declare
|
|
6
|
-
export default
|
|
6
|
+
declare let moduleRef: Spec | null;
|
|
7
|
+
export default moduleRef;
|
|
@@ -1,6 +1,25 @@
|
|
|
1
|
+
// Ensure the Codegen parser sees Spec usage with TurboModuleRegistry.get<Spec>(..)
|
|
2
|
+
// This block is never executed at runtime but allows codegen to infer the module name.
|
|
3
|
+
// eslint-disable-next-line no-constant-condition, @typescript-eslint/no-unused-expressions
|
|
1
4
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
2
5
|
const { TurboModuleRegistry: TurboModuleRegistryUntyped } = require('react-native');
|
|
3
|
-
|
|
4
|
-
|
|
6
|
+
let moduleRef = null;
|
|
7
|
+
try {
|
|
8
|
+
// Note: We've already destructured the TurboModuleRegistry object into TurboModuleRegistryUntyped
|
|
9
|
+
const tmr = TurboModuleRegistryUntyped;
|
|
10
|
+
// Prefer non-throwing get() if available
|
|
11
|
+
const maybe = (tmr === null || tmr === void 0 ? void 0 : tmr.get) ? tmr.get('NoibuSessionRecorder') : undefined;
|
|
12
|
+
if (maybe) {
|
|
13
|
+
moduleRef = maybe;
|
|
14
|
+
}
|
|
15
|
+
else if (tmr === null || tmr === void 0 ? void 0 : tmr.getEnforcing) {
|
|
16
|
+
// getEnforcing may throw on legacy; catch and fall back to null
|
|
17
|
+
moduleRef = tmr.getEnforcing('NoibuSessionRecorder');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (_e) {
|
|
21
|
+
moduleRef = null;
|
|
22
|
+
}
|
|
23
|
+
var TurboNativeSessionRecorder = moduleRef;
|
|
5
24
|
|
|
6
25
|
export { TurboNativeSessionRecorder as default };
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { __rest } from 'tslib';
|
|
1
|
+
import { __awaiter, __rest } from 'tslib';
|
|
2
2
|
import { NativeModules, Platform, NativeEventEmitter } from 'react-native';
|
|
3
3
|
import TurboNativeSessionRecorder from '../native/NativeNoibuSessionRecorder.js';
|
|
4
4
|
import { noibuLog } from '../utils/log.js';
|
|
5
5
|
import { transformToWeb } from '../mobileTransformer/mobile-replay/index.js';
|
|
6
6
|
|
|
7
|
+
var _a;
|
|
7
8
|
const LINKING_ERROR = `The package 'noibu-session-replay' doesn't seem to be linked. Make sure: \n\n` +
|
|
8
9
|
'- You rebuilt the app after installing the package\n' +
|
|
9
10
|
'- You are not using Expo Go\n';
|
|
10
|
-
const
|
|
11
|
-
const NativeSessionRecorder = TurboNativeSessionRecorder !== null && TurboNativeSessionRecorder !== void 0 ? TurboNativeSessionRecorder :
|
|
11
|
+
const RNModule = (_a = NativeModules.NoibuSessionRecorder) !== null && _a !== void 0 ? _a : NativeModules.NativeSessionRecorder;
|
|
12
|
+
const NativeSessionRecorder = TurboNativeSessionRecorder !== null && TurboNativeSessionRecorder !== void 0 ? TurboNativeSessionRecorder : RNModule;
|
|
13
|
+
// Consider new-arch if the resolved module exposes the polling method
|
|
14
|
+
const isNewArchAndroid = Platform.OS === 'android' && typeof (NativeSessionRecorder === null || NativeSessionRecorder === void 0 ? void 0 : NativeSessionRecorder.consumeEvents) === 'function';
|
|
12
15
|
let nativeModuleEmitter;
|
|
13
16
|
const SupportedPlatforms = ['android', 'ios'];
|
|
14
17
|
/** The level of logging to show in the device logcat stream. */
|
|
@@ -26,20 +29,28 @@ var LogLevel;
|
|
|
26
29
|
* Initializes the Noibu - Session recording SDK if the API level is supported.
|
|
27
30
|
*/
|
|
28
31
|
function initialize() {
|
|
29
|
-
var _a;
|
|
30
32
|
if (Platform.OS === 'ios') {
|
|
31
33
|
return;
|
|
32
34
|
}
|
|
33
|
-
nativeModuleEmitter = new NativeEventEmitter((_a = NativeModules.NativeSessionRecorder) !== null && _a !== void 0 ? _a : NativeSessionRecorder);
|
|
34
35
|
if (!SupportedPlatforms.includes(Platform.OS)) {
|
|
35
36
|
noibuLog(`Noibu - Session recording supports ${SupportedPlatforms.join(', ')} only for now.`);
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
|
-
if (NativeSessionRecorder
|
|
39
|
+
if (!NativeSessionRecorder) {
|
|
39
40
|
noibuLog('Noibu - Session recording did not initialize properly.', LINKING_ERROR);
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
43
|
+
// For legacy Android, set up the event emitter; for new-arch Android, we poll via TurboModule
|
|
44
|
+
if (!isNewArchAndroid) {
|
|
45
|
+
const moduleForEmitter = RNModule !== null && RNModule !== void 0 ? RNModule : NativeSessionRecorder;
|
|
46
|
+
if (!moduleForEmitter) {
|
|
47
|
+
noibuLog('NativeSessionRecorder module missing for legacy Android.');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
nativeModuleEmitter = new NativeEventEmitter(moduleForEmitter);
|
|
51
|
+
}
|
|
42
52
|
NativeSessionRecorder.initialize();
|
|
53
|
+
noibuLog("Session recorder initialized.");
|
|
43
54
|
}
|
|
44
55
|
let isIOSInitialized = false;
|
|
45
56
|
/**
|
|
@@ -58,26 +69,61 @@ let isIOSInitialized = false;
|
|
|
58
69
|
* @throws {Error} If the Noibu Session Recorder is not initialized before calling this function.
|
|
59
70
|
*/
|
|
60
71
|
function subscribeToNativeEvent(callback) {
|
|
72
|
+
var _a;
|
|
73
|
+
noibuLog("will subscribe to NativeEvent");
|
|
61
74
|
if (Platform.OS === 'android') {
|
|
75
|
+
if (isNewArchAndroid) {
|
|
76
|
+
noibuLog("is new architecture");
|
|
77
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
78
|
+
(function pump() {
|
|
79
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
80
|
+
while (true) {
|
|
81
|
+
try {
|
|
82
|
+
const batch = yield NativeSessionRecorder.consumeEvents();
|
|
83
|
+
noibuLog("this is the recording array: ", batch);
|
|
84
|
+
if (Array.isArray(batch) && batch.length > 0) {
|
|
85
|
+
for (const message of batch) {
|
|
86
|
+
try {
|
|
87
|
+
const _a = JSON.parse(message), { data } = _a, rest = __rest(_a, ["data"]);
|
|
88
|
+
const transformedEvents = transformToWeb([Object.assign(Object.assign({}, rest), { data })]);
|
|
89
|
+
for (const e of transformedEvents) {
|
|
90
|
+
callback({ message: JSON.stringify(e) });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
// Skip malformed items
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// Swallow errors and continue polling
|
|
101
|
+
}
|
|
102
|
+
yield sleep(50);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
})();
|
|
106
|
+
// Keep polling indefinitely; do not expose a canceller in new-arch path
|
|
107
|
+
return () => { };
|
|
108
|
+
}
|
|
109
|
+
noibuLog("is NOT new architecture");
|
|
62
110
|
if (!nativeModuleEmitter) {
|
|
63
111
|
throw new Error('You have to initialize Noibu Session Recorder first');
|
|
64
112
|
}
|
|
65
|
-
//
|
|
66
|
-
// const subscription = nativeModuleEmitter.addListener('noibuRecordingEvent', callback);
|
|
113
|
+
// Legacy bridge path with DeviceEventManagerModule
|
|
67
114
|
nativeModuleEmitter.addListener('noibuRecordingEvent', event => {
|
|
68
115
|
const message = event.message;
|
|
69
116
|
const _a = JSON.parse(message), { data } = _a, rest = __rest(_a, ["data"]);
|
|
70
117
|
noibuLog('New noibu recording event', rest);
|
|
71
118
|
const transformedEvents = transformToWeb([Object.assign(Object.assign({}, rest), { data })]);
|
|
72
|
-
noibuLog('after transformation: ', transformedEvents);
|
|
73
119
|
// Emit pre-serialized JSON strings to minimize JS heap during batching
|
|
74
|
-
transformedEvents.forEach(
|
|
120
|
+
transformedEvents.forEach(e => callback({ message: JSON.stringify(e) }));
|
|
75
121
|
});
|
|
76
122
|
// return () => subscription.remove();
|
|
77
123
|
}
|
|
78
124
|
if (Platform.OS === 'ios') {
|
|
79
125
|
if (!nativeModuleEmitter) {
|
|
80
|
-
nativeModuleEmitter = new NativeEventEmitter(
|
|
126
|
+
nativeModuleEmitter = new NativeEventEmitter(RNModule);
|
|
81
127
|
noibuLog('nativeModuleEmitter', nativeModuleEmitter);
|
|
82
128
|
}
|
|
83
129
|
nativeModuleEmitter.addListener('iOSEvent', (event) => {
|
|
@@ -95,7 +141,7 @@ function subscribeToNativeEvent(callback) {
|
|
|
95
141
|
}
|
|
96
142
|
});
|
|
97
143
|
if (!isIOSInitialized) {
|
|
98
|
-
|
|
144
|
+
(_a = RNModule === null || RNModule === void 0 ? void 0 : RNModule.startIOS) === null || _a === void 0 ? void 0 : _a.call(RNModule);
|
|
99
145
|
isIOSInitialized = true;
|
|
100
146
|
}
|
|
101
147
|
// return () => subscription.remove();
|
package/package.json
CHANGED
|
@@ -1,20 +1,52 @@
|
|
|
1
1
|
// @ts-ignore - Using type from newer React Native versions when building locally
|
|
2
2
|
import type { TurboModule } from 'react-native';
|
|
3
|
+
// Provide a type-only ambient declaration so TS doesn't require a real export
|
|
4
|
+
// This is used only for RN Codegen parsing inside a dead code branch below
|
|
5
|
+
declare const TurboModuleRegistry: {
|
|
6
|
+
get?<T>(name: string): T | null | undefined;
|
|
7
|
+
getEnforcing?<T>(name: string): T;
|
|
8
|
+
};
|
|
3
9
|
|
|
4
10
|
export interface Spec extends TurboModule {
|
|
5
11
|
initialize(): Promise<boolean>;
|
|
12
|
+
// Returns an array of raw mobile event JSON strings and clears native queue
|
|
13
|
+
consumeEvents?(): Promise<string[]>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Ensure the Codegen parser sees Spec usage with TurboModuleRegistry.get<Spec>(..)
|
|
17
|
+
// This block is never executed at runtime but allows codegen to infer the module name.
|
|
18
|
+
// eslint-disable-next-line no-constant-condition, @typescript-eslint/no-unused-expressions
|
|
19
|
+
if (false) {
|
|
20
|
+
// The string must match the native module name
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
TurboModuleRegistry.get<Spec>('NoibuSessionRecorder');
|
|
6
23
|
}
|
|
7
24
|
|
|
8
|
-
//
|
|
25
|
+
// Safely access TurboModuleRegistry without throwing on legacy arch
|
|
9
26
|
type TurboModuleRegistryType = {
|
|
10
|
-
|
|
27
|
+
get?<T>(name: string): T | null | undefined;
|
|
28
|
+
getEnforcing?<T>(name: string): T;
|
|
11
29
|
};
|
|
12
30
|
|
|
13
31
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
14
32
|
const { TurboModuleRegistry: TurboModuleRegistryUntyped } = require('react-native') as {
|
|
15
|
-
TurboModuleRegistry
|
|
33
|
+
TurboModuleRegistry?: TurboModuleRegistryType;
|
|
16
34
|
};
|
|
17
35
|
|
|
18
|
-
|
|
36
|
+
let moduleRef: Spec | null = null;
|
|
37
|
+
try {
|
|
38
|
+
// Note: We've already destructured the TurboModuleRegistry object into TurboModuleRegistryUntyped
|
|
39
|
+
const tmr = TurboModuleRegistryUntyped as TurboModuleRegistryType | undefined;
|
|
40
|
+
// Prefer non-throwing get() if available
|
|
41
|
+
const maybe = tmr?.get ? tmr.get<Spec>('NoibuSessionRecorder') : undefined;
|
|
42
|
+
if (maybe) {
|
|
43
|
+
moduleRef = maybe;
|
|
44
|
+
} else if (tmr?.getEnforcing) {
|
|
45
|
+
// getEnforcing may throw on legacy; catch and fall back to null
|
|
46
|
+
moduleRef = tmr.getEnforcing<Spec>('NoibuSessionRecorder');
|
|
47
|
+
}
|
|
48
|
+
} catch (_e) {
|
|
49
|
+
moduleRef = null;
|
|
50
|
+
}
|
|
19
51
|
|
|
20
|
-
export default
|
|
52
|
+
export default moduleRef;
|