noibu-react-native 0.2.33-rc.1 → 0.2.33-rc.2

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.
@@ -64,15 +64,7 @@ repositories {
64
64
  def kotlin_version = getExtOrDefault("kotlinVersion")
65
65
 
66
66
  dependencies {
67
- implementation "com.facebook.react:react-native:+"
68
- implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
69
- implementation "com.noibu:sessionreplay-recorder:1.0.4"
70
- }
71
-
72
- if (isNewArchitectureEnabled()) {
73
- react {
74
- jsRootDir = file("../src/")
75
- libraryName = "NoibuSessionRecorder"
76
- codegenJavaPackageName = "com.noibu.sessionreplay.reactnative"
77
- }
67
+ implementation "com.facebook.react:react-native:+"
68
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
69
+ implementation "com.noibu:sessionreplay-recorder:1.0.5-rc.1"
78
70
  }
@@ -11,7 +11,7 @@ import com.noibu.mobile.android.sessionreplay.Noibu
11
11
  import com.noibu.mobile.android.sessionreplay.NoibuConfig
12
12
  import java.util.concurrent.ConcurrentLinkedQueue
13
13
 
14
- class NoibuSessionRecorderModule(val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), TurboModule {
14
+ class NoibuSessionRecorderModule(val reactContext: ReactApplicationContext) : NativeSessionRecorderSpec(reactContext) {
15
15
  companion object {
16
16
  private const val TAG = "NoibuSessionRecorder"
17
17
  private const val MAX_BATCH = 100
@@ -25,11 +25,10 @@ class NoibuSessionRecorderModule(val reactContext: ReactApplicationContext) : Re
25
25
 
26
26
  override fun getName(): String = "NoibuSessionRecorder"
27
27
 
28
- @ReactMethod
29
- fun initialize(promise: Promise) {
28
+ override fun initialize(promise: Promise) {
30
29
  Log.i(TAG, "[new-arch] initialize() called")
31
30
  try {
32
- val context = currentActivity?.applicationContext ?: reactApplicationContext.applicationContext
31
+ val context = reactContext.currentActivity?.applicationContext ?: reactApplicationContext.applicationContext
33
32
  val config = NoibuConfig(
34
33
  sessionReplayEnabled = true,
35
34
  maskAllTextInputs = false,
@@ -46,8 +45,7 @@ class NoibuSessionRecorderModule(val reactContext: ReactApplicationContext) : Re
46
45
  }
47
46
  }
48
47
 
49
- @ReactMethod
50
- fun consumeEvents(promise: Promise) {
48
+ override fun consumeEvents(promise: Promise) {
51
49
  try {
52
50
  val array = Arguments.createArray()
53
51
  var count = 0
@@ -1,28 +1,27 @@
1
1
  package com.noibu.sessionreplay.reactnative
2
2
 
3
- import com.facebook.react.TurboReactPackage
3
+ import com.facebook.react.BaseReactPackage
4
4
  import com.facebook.react.bridge.NativeModule
5
5
  import com.facebook.react.bridge.ReactApplicationContext
6
6
  import com.facebook.react.module.model.ReactModuleInfo
7
7
  import com.facebook.react.module.model.ReactModuleInfoProvider
8
8
 
9
- class NoibuSessionReplayPackage : TurboReactPackage() {
9
+ class NoibuSessionReplayPackage : BaseReactPackage() {
10
10
  override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
11
- return if (name == "NoibuSessionRecorder") {
11
+ return if (name == NativeSessionRecorderSpec.NAME) {
12
12
  NoibuSessionRecorderModule(reactContext)
13
13
  } else null
14
14
  }
15
15
 
16
16
  override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider {
17
17
  mapOf(
18
- "NoibuSessionRecorder" to ReactModuleInfo(
19
- /* name */ "NoibuSessionRecorder",
20
- /* className */ "com.noibu.sessionreplay.reactnative.NoibuSessionRecorderModule",
21
- /* canOverrideExistingModule */ false,
22
- /* needsEagerInit */ false,
23
- /* hasConstants */ false,
24
- /* isCxxModule */ false,
25
- /* isTurboModule */ true
18
+ NativeSessionRecorderSpec.NAME to ReactModuleInfo(/* name */
19
+ name = NativeSessionRecorderSpec.NAME,
20
+ className = NativeSessionRecorderSpec.NAME,
21
+ canOverrideExistingModule = false,
22
+ needsEagerInit = false,
23
+ isCxxModule = false,
24
+ isTurboModule = true
26
25
  )
27
26
  )
28
27
  }
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.33-rc.1" ;
27
+ return "1.0.104-rn-sdk-0.2.33-rc.2" ;
28
28
  }
29
29
  /**
30
30
  * Gets the max metro recon number
@@ -11,7 +11,9 @@ const LINKING_ERROR = `The package 'noibu-session-replay' doesn't seem to be lin
11
11
  const RNModule = (_a = NativeModules.NoibuSessionRecorder) !== null && _a !== void 0 ? _a : NativeModules.NativeSessionRecorder;
12
12
  const NativeSessionRecorder = TurboNativeSessionRecorder !== null && TurboNativeSessionRecorder !== void 0 ? TurboNativeSessionRecorder : RNModule;
13
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';
14
+ const isNewArch = typeof (NativeSessionRecorder === null || NativeSessionRecorder === void 0 ? void 0 : NativeSessionRecorder.consumeEvents) === 'function';
15
+ const isNewArchAndroid = Platform.OS === 'android' && isNewArch;
16
+ const isNewArchIOS = Platform.OS === 'ios' && isNewArch;
15
17
  let nativeModuleEmitter;
16
18
  const SupportedPlatforms = ['android', 'ios'];
17
19
  /** The level of logging to show in the device logcat stream. */
@@ -30,6 +32,10 @@ var LogLevel;
30
32
  */
31
33
  function initialize() {
32
34
  if (Platform.OS === 'ios') {
35
+ // In new architecture, initialize native module to start capturing
36
+ if (isNewArchIOS) {
37
+ NativeSessionRecorder.initialize();
38
+ }
33
39
  return;
34
40
  }
35
41
  if (!SupportedPlatforms.includes(Platform.OS)) {
@@ -69,84 +75,95 @@ let isIOSInitialized = false;
69
75
  * @throws {Error} If the Noibu Session Recorder is not initialized before calling this function.
70
76
  */
71
77
  function subscribeToNativeEvent(callback) {
72
- var _a;
73
- noibuLog("will subscribe to NativeEvent");
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
78
+ noibuLog('will subscribe to NativeEvent');
79
+ // Shared new-architecture polling (Android/iOS)
80
+ const startNewArchPump = () => {
81
+ let cancelled = false;
82
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
83
+ (function pump() {
84
+ return __awaiter(this, void 0, void 0, function* () {
85
+ while (!cancelled) {
86
+ try {
87
+ const batch = yield NativeSessionRecorder.consumeEvents();
88
+ if (Array.isArray(batch) && batch.length > 0) {
89
+ noibuLog("Actual batch: ", batch);
90
+ for (const message of batch) {
91
+ try {
92
+ const _a = JSON.parse(message), { data } = _a, rest = __rest(_a, ["data"]);
93
+ const transformedEvents = transformToWeb([Object.assign(Object.assign({}, rest), { data })]);
94
+ for (const e of transformedEvents) {
95
+ callback({ message: JSON.stringify(e) });
95
96
  }
96
97
  }
98
+ catch (_e) {
99
+ // Skip malformed items
100
+ }
97
101
  }
98
102
  }
99
- catch (err) {
100
- // Swallow errors and continue polling
101
- }
102
- yield sleep(50);
103
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");
110
- if (!nativeModuleEmitter) {
111
- throw new Error('You have to initialize Noibu Session Recorder first');
112
- }
113
- // Legacy bridge path with DeviceEventManagerModule
114
- nativeModuleEmitter.addListener('noibuRecordingEvent', event => {
115
- const message = event.message;
116
- const _a = JSON.parse(message), { data } = _a, rest = __rest(_a, ["data"]);
117
- noibuLog('New noibu recording event', rest);
118
- const transformedEvents = transformToWeb([Object.assign(Object.assign({}, rest), { data })]);
119
- // Emit pre-serialized JSON strings to minimize JS heap during batching
120
- transformedEvents.forEach(e => callback({ message: JSON.stringify(e) }));
121
- });
122
- // return () => subscription.remove();
123
- }
124
- if (Platform.OS === 'ios') {
104
+ catch (_err) {
105
+ // Swallow errors and continue polling
106
+ }
107
+ yield sleep(50);
108
+ }
109
+ });
110
+ })();
111
+ return () => {
112
+ cancelled = true;
113
+ };
114
+ };
115
+ // Shared legacy-architecture event listener (Android/iOS)
116
+ const addLegacyListener = () => {
117
+ var _a;
125
118
  if (!nativeModuleEmitter) {
126
- nativeModuleEmitter = new NativeEventEmitter(RNModule);
127
- noibuLog('nativeModuleEmitter', nativeModuleEmitter);
119
+ const moduleForEmitter = RNModule !== null && RNModule !== void 0 ? RNModule : NativeSessionRecorder;
120
+ if (!moduleForEmitter) {
121
+ throw new Error('You have to initialize Noibu Session Recorder first');
122
+ }
123
+ nativeModuleEmitter = new NativeEventEmitter(moduleForEmitter);
128
124
  }
129
- nativeModuleEmitter.addListener('iOSEvent', (event) => {
125
+ const handleLegacyPayload = (event) => {
130
126
  var _a;
127
+ noibuLog('handleLegacyPayload', event);
131
128
  try {
132
- const mobileEvents = event.mobileEvents;
133
- const webEvents = transformToWeb(mobileEvents);
134
- // Emit pre-serialized JSON strings to minimize JS heap during batching
135
- for (const webEvent of webEvents) {
136
- callback({ message: JSON.stringify(webEvent) });
129
+ let mobileEvents = [];
130
+ if (event && Array.isArray(event.mobileEvents)) {
131
+ // iOS legacy format
132
+ mobileEvents = event.mobileEvents;
133
+ }
134
+ else if (event && typeof event.message === 'string') {
135
+ // Android legacy format: single JSON string containing one mobile event
136
+ const _b = JSON.parse(event.message), { data } = _b, rest = __rest(_b, ["data"]);
137
+ mobileEvents = [Object.assign(Object.assign({}, rest), { data })];
138
+ }
139
+ else if (typeof event === 'string') {
140
+ // Fallback: raw string message
141
+ const _c = JSON.parse(event), { data } = _c, rest = __rest(_c, ["data"]);
142
+ mobileEvents = [Object.assign(Object.assign({}, rest), { data })];
143
+ }
144
+ if (mobileEvents.length > 0) {
145
+ const webEvents = transformToWeb(mobileEvents);
146
+ for (const webEvent of webEvents) {
147
+ callback({ message: JSON.stringify(webEvent) });
148
+ }
137
149
  }
138
150
  }
139
151
  catch (err) {
140
- noibuLog(`[Error] iOS transformToWeb failed: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
152
+ noibuLog(`[Error] Legacy transformToWeb failed: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
141
153
  }
142
- });
143
- if (!isIOSInitialized) {
154
+ };
155
+ nativeModuleEmitter.addListener('noibuRecordingEvent', handleLegacyPayload);
156
+ if (Platform.OS === 'ios' && !isIOSInitialized) {
144
157
  (_a = RNModule === null || RNModule === void 0 ? void 0 : RNModule.startIOS) === null || _a === void 0 ? void 0 : _a.call(RNModule);
145
158
  isIOSInitialized = true;
146
159
  }
147
- // return () => subscription.remove();
160
+ // return () => subscription?.remove();
161
+ return () => { };
162
+ };
163
+ if (isNewArch) {
164
+ return startNewArchPump();
148
165
  }
149
- return () => { };
166
+ return addLegacyListener();
150
167
  }
151
168
 
152
169
  export { LogLevel, initialize, subscribeToNativeEvent };
@@ -16,7 +16,7 @@ class NoibuSessionRecorder: RCTEventEmitter {
16
16
  }
17
17
 
18
18
  override func supportedEvents() -> [String]! {
19
- return ["iOSEvent"]
19
+ return ["noibuRecordingEvent"]
20
20
  }
21
21
 
22
22
  override func startObserving() {
@@ -36,7 +36,7 @@ class NoibuSessionRecorder: RCTEventEmitter {
36
36
 
37
37
  config.onReactNativeCallback = { [weak self] mobileEvents in
38
38
  guard let self = self, self.bridge != nil else { return }
39
- self.sendEvent(withName: "iOSEvent", body: ["mobileEvents": mobileEvents])
39
+ self.sendEvent(withName: "noibuRecordingEvent", body: ["mobileEvents": mobileEvents])
40
40
  }
41
41
  NoibuSDKManager.shared.setup(config)
42
42
  }
@@ -0,0 +1,5 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ // Public header must not pull in C++/JSI.
4
+ @interface RCTNoibuSessionRecorder : NSObject <RCTBridgeModule>
5
+ @end
@@ -0,0 +1,112 @@
1
+ // Must be Obj-C++
2
+ #import "RCTNoibuSessionRecorder.h"
3
+
4
+ #import <ReactCommon/RCTTurboModule.h>
5
+ // Import the generated header (TurboModule spec)
6
+ #import <NoibuSessionRecorder/NoibuSessionRecorder.h>
7
+ // Import Noibu SDK Swift interfaces exposed to ObjC
8
+ #if __has_include(<NoibuSDK/NoibuSDK-Swift.h>)
9
+ #import <NoibuSDK/NoibuSDK-Swift.h>
10
+ #elif __has_include("NoibuSDK-Swift.h")
11
+ #import "NoibuSDK-Swift.h"
12
+ #endif
13
+
14
+ @interface RCTNoibuSessionRecorder () <NativeSessionRecorderSpec>
15
+ @end
16
+
17
+ @implementation RCTNoibuSessionRecorder
18
+
19
+ RCT_EXPORT_MODULE(NoibuSessionRecorder)
20
+
21
+ + (BOOL)requiresMainQueueSetup { return NO; }
22
+
23
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
24
+ (const facebook::react::ObjCTurboModule::InitParams &)params
25
+ {
26
+ return std::make_shared<facebook::react::NativeSessionRecorderSpecJSI>(params);
27
+ }
28
+
29
+ static const NSInteger kMaxBatch = 100;
30
+
31
+ // Simple thread-safe queue of JSON strings
32
+ static dispatch_queue_t noibu_queue_lock;
33
+ static NSMutableArray<NSString *> *noibu_queue_storage;
34
+
35
+ static void ensure_queue_initialized(void) {
36
+ static dispatch_once_t onceToken;
37
+ dispatch_once(&onceToken, ^{
38
+ noibu_queue_lock = dispatch_queue_create("com.noibu.sessionreplay.queue", DISPATCH_QUEUE_SERIAL);
39
+ noibu_queue_storage = [NSMutableArray array];
40
+ });
41
+ }
42
+
43
+ static void enqueue_json_string(NSString *item) {
44
+ ensure_queue_initialized();
45
+ if (item.length == 0) { return; }
46
+ dispatch_sync(noibu_queue_lock, ^{
47
+ [noibu_queue_storage addObject:item];
48
+ });
49
+ }
50
+
51
+ static NSArray<NSString *> *drain_queue(NSUInteger maxCount) {
52
+ ensure_queue_initialized();
53
+ __block NSArray<NSString *> *result = @[];
54
+ dispatch_sync(noibu_queue_lock, ^{
55
+ if (noibu_queue_storage.count == 0) { return; }
56
+ NSUInteger count = MIN(MAX(1, maxCount), (NSUInteger)noibu_queue_storage.count);
57
+ result = [noibu_queue_storage subarrayWithRange:NSMakeRange(0, count)];
58
+ [noibu_queue_storage removeObjectsInRange:NSMakeRange(0, count)];
59
+ });
60
+ return result ?: @[];
61
+ }
62
+
63
+ - (void)initialize:(RCTPromiseResolveBlock)resolve
64
+ reject:(RCTPromiseRejectBlock)reject
65
+ {
66
+ @try {
67
+ ensure_queue_initialized();
68
+
69
+ // Construct Noibu SDK configuration (placeholder values)
70
+ NSString *apiKey = @"NOIBU_API_KEY";
71
+ NSString *host = @"https://noibu.com/";
72
+
73
+ #if __has_include(<NoibuSDK/NoibuSDK-Swift.h>) || __has_include("NoibuSDK-Swift.h")
74
+ NoibuConfig *config = [[NoibuConfig alloc] apiKey:apiKey host:host];
75
+ // Enable session replay by default, leave other options as defaults
76
+ // Bridge callback to enqueue raw mobile events for JS polling
77
+
78
+ config.onReactNativeCallback = ^(NSArray<NSDictionary *> *mobileEvents) {
79
+ for (id item in mobileEvents) {
80
+ if ([item isKindOfClass:[NSString class]]) {
81
+ enqueue_json_string((NSString *)item);
82
+ } else if ([NSJSONSerialization isValidJSONObject:item]) {
83
+ NSError *error = nil;
84
+ NSData *data = [NSJSONSerialization dataWithJSONObject:item options:0 error:&error];
85
+ if (data && !error) {
86
+ NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
87
+ if (json.length > 0) { enqueue_json_string(json); }
88
+ }
89
+ }
90
+ }
91
+ };
92
+
93
+ [[NoibuSDKManager shared] setup:config];
94
+ #endif
95
+ resolve(@(YES));
96
+ } @catch (NSException *exception) {
97
+ reject(@"E_INIT", exception.reason ?: @"initialize() failed", nil);
98
+ }
99
+ }
100
+
101
+ - (void)consumeEvents:(RCTPromiseResolveBlock)resolve
102
+ reject:(RCTPromiseRejectBlock)reject
103
+ {
104
+ @try {
105
+ NSArray<NSString *> *batch = drain_queue((NSUInteger)kMaxBatch);
106
+ resolve(batch ?: @[]);
107
+ } @catch (NSException *exception) {
108
+ reject(@"E_CONSUME", exception.reason ?: @"consumeEvents() failed", nil);
109
+ }
110
+ }
111
+
112
+ @end
@@ -15,9 +15,20 @@ Pod::Spec.new do |s|
15
15
  #s.source = { :git => "https://www.github.com/aleguia.git", :tag => "#{s.version}" }
16
16
  s.source = { :path => "." }
17
17
 
18
- # Include source files
19
-
20
- s.source_files = "ios/**/*.{h,m,mm,swift}"
18
+ # Include source files per-architecture (legacy vs new-arch)
19
+ if ENV['RCT_NEW_ARCH_ENABLED'] == '1'
20
+ # New Architecture: TurboModule implementation only (Objective-C++)
21
+ s.source_files = [
22
+ "ios/RCTNoibuSessionRecorder.h",
23
+ "ios/RCTNoibuSessionRecorder.mm",
24
+ ]
25
+ else
26
+ # Legacy Architecture: RCTEventEmitter implementation only
27
+ s.source_files = [
28
+ "ios/IOSPocEmitter.m",
29
+ "ios/IOSPocEmitter.swift",
30
+ ]
31
+ end
21
32
  s.swift_version = '5.0'
22
33
 
23
34
  # Build as static framework to avoid new-arch linking issues
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noibu-react-native",
3
- "version": "0.2.33-rc.1",
3
+ "version": "0.2.33-rc.2",
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",
@@ -8,16 +8,20 @@
8
8
  "codegenConfig": {
9
9
  "name": "NoibuSessionRecorder",
10
10
  "type": "modules",
11
- "jsSrcsDir": "src",
11
+ "jsSrcsDir": "specs",
12
12
  "android": {
13
13
  "javaPackageName": "com.noibu.sessionreplay.reactnative"
14
+ },
15
+ "ios": {
16
+ "modulesProvider": {
17
+ "NoibuSessionRecorder": "RCTNoibuSessionRecorder"
18
+ }
14
19
  }
15
20
  },
16
21
  "files": [
17
22
  "android",
18
- "android/settings.gradle",
19
23
  "dist/*",
20
- "src/native/NativeNoibuSessionRecorder.ts",
24
+ "specs/*",
21
25
  "README.md",
22
26
  "CHANGELOG.md",
23
27
  "ios",
@@ -0,0 +1,10 @@
1
+ import type {TurboModule} from 'react-native';
2
+ import {TurboModuleRegistry} from 'react-native';
3
+
4
+ export interface Spec extends TurboModule {
5
+ initialize(): Promise<boolean>;
6
+ // Returns an array of raw mobile event JSON strings and clears native queue
7
+ consumeEvents?(): Promise<string[]>;
8
+ }
9
+
10
+ export default TurboModuleRegistry.get<Spec>('NoibuSessionRecorder');
@@ -1,52 +0,0 @@
1
- // @ts-ignore - Using type from newer React Native versions when building locally
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
- };
9
-
10
- export interface Spec extends TurboModule {
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');
23
- }
24
-
25
- // Safely access TurboModuleRegistry without throwing on legacy arch
26
- type TurboModuleRegistryType = {
27
- get?<T>(name: string): T | null | undefined;
28
- getEnforcing?<T>(name: string): T;
29
- };
30
-
31
- // eslint-disable-next-line @typescript-eslint/no-var-requires
32
- const { TurboModuleRegistry: TurboModuleRegistryUntyped } = require('react-native') as {
33
- TurboModuleRegistry?: TurboModuleRegistryType;
34
- };
35
-
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
- }
51
-
52
- export default moduleRef;