@truewatchtech/react-native-mobile 0.4.1 → 0.4.3-alpha.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.
Files changed (130) hide show
  1. package/FTMobileReactNativeSDK.podspec +7 -5
  2. package/README.md +34 -0
  3. package/android/build.gradle +9 -2
  4. package/android/src/main/java/com/ft/sdk/reactnative/FTMobileImpl.java +45 -14
  5. package/android/src/main/java/com/ft/sdk/reactnative/FTRUMImpl.java +85 -2
  6. package/android/src/main/java/com/ft/sdk/reactnative/FTTraceImpl.java +5 -1
  7. package/android/src/main/java/com/ft/sdk/reactnative/JsLongTaskMonitor.java +281 -0
  8. package/android/src/main/java/com/ft/sdk/reactnative/utils/ReactNativeUtils.java +5 -12
  9. package/android/src/newarch/java/com/ft/sdk/reactnative/FTMobileModule.java +9 -5
  10. package/android/src/newarch/java/com/ft/sdk/reactnative/FTRUMModule.java +26 -1
  11. package/android/src/newarch/java/com/ft/sdk/reactnative/FTTraceModule.java +14 -0
  12. package/android/src/oldarch/java/com/ft/sdk/reactnative/FTMobileModule.java +2 -2
  13. package/android/src/oldarch/java/com/ft/sdk/reactnative/FTRUMModule.java +29 -1
  14. package/android/src/oldarch/java/com/ft/sdk/reactnative/FTTraceModule.java +12 -0
  15. package/android/src/rnpost70/java/com/ft/sdk/reactnative/ReactJsThreadExecutor.java +21 -0
  16. package/android/src/rnpre70/java/com/ft/sdk/reactnative/ReactJsThreadExecutor.java +37 -0
  17. package/android/src/test/java/com/ft/sdk/reactnative/FTRUMModuleTest.java +117 -0
  18. package/android/src/test/java/com/ft/sdk/reactnative/JsLongTaskMonitorTest.java +446 -0
  19. package/android/src/test/java/com/ft/sdk/reactnative/utils/ReactNativeUtilsTest.java +33 -0
  20. package/ios/FTJSLongTaskMonitor.h +51 -0
  21. package/ios/FTJSLongTaskMonitor.m +250 -0
  22. package/ios/FTMobileReactNative.mm +23 -16
  23. package/ios/FTReactNativeLog.mm +3 -4
  24. package/ios/FTReactNativeRUM.mm +103 -28
  25. package/ios/FTReactNativeResource.h +4 -0
  26. package/ios/FTReactNativeResource.m +58 -0
  27. package/ios/FTReactNativeTrace.mm +56 -13
  28. package/ios/FTReactNativeUtils.m +10 -15
  29. package/ios/FTReactNativeWebSocket.h +10 -0
  30. package/ios/FTReactNativeWebSocket.mm +201 -0
  31. package/ios/FTWebSocketMetadataStore.h +25 -0
  32. package/ios/FTWebSocketMetadataStore.m +622 -0
  33. package/ios/FTWebSocketResourceData.h +16 -0
  34. package/ios/FTWebSocketResourceData.m +75 -0
  35. package/lib/commonjs/ft_mobile_agent.js +36 -3
  36. package/lib/commonjs/ft_mobile_agent.js.map +1 -1
  37. package/lib/commonjs/ft_rum.js +46 -2
  38. package/lib/commonjs/ft_rum.js.map +1 -1
  39. package/lib/commonjs/ft_tracing.js +12 -3
  40. package/lib/commonjs/ft_tracing.js.map +1 -1
  41. package/lib/commonjs/index.js +14 -0
  42. package/lib/commonjs/index.js.map +1 -1
  43. package/lib/commonjs/rum/FTBabelInteractionTracking.js +72 -0
  44. package/lib/commonjs/rum/FTBabelInteractionTracking.js.map +1 -0
  45. package/lib/commonjs/rum/FTBabelUtils.js +87 -0
  46. package/lib/commonjs/rum/FTBabelUtils.js.map +1 -0
  47. package/lib/commonjs/rum/FTRumActionTracking.js +10 -39
  48. package/lib/commonjs/rum/FTRumActionTracking.js.map +1 -1
  49. package/lib/commonjs/rum/FTRumActionTrackingContext.js +50 -0
  50. package/lib/commonjs/rum/FTRumActionTrackingContext.js.map +1 -0
  51. package/lib/commonjs/rum/FTRumWebSocketNative.js +73 -0
  52. package/lib/commonjs/rum/FTRumWebSocketNative.js.map +1 -0
  53. package/lib/commonjs/rum/FTRumWebSocketTiming.js +55 -0
  54. package/lib/commonjs/rum/FTRumWebSocketTiming.js.map +1 -0
  55. package/lib/commonjs/rum/FTRumWebSocketTracking.js +510 -0
  56. package/lib/commonjs/rum/FTRumWebSocketTracking.js.map +1 -0
  57. package/lib/commonjs/rum/longTasksUtils.js +25 -0
  58. package/lib/commonjs/rum/longTasksUtils.js.map +1 -0
  59. package/lib/commonjs/specs/NativeFTMobileReactNative.js.map +1 -1
  60. package/lib/commonjs/specs/NativeFTReactNativeRUM.js.map +1 -1
  61. package/lib/commonjs/specs/NativeFTReactNativeTrace.js.map +1 -1
  62. package/lib/commonjs/specs/NativeFTReactNativeWebSocket.js +11 -0
  63. package/lib/commonjs/specs/NativeFTReactNativeWebSocket.js.map +1 -0
  64. package/lib/commonjs/version.js +1 -1
  65. package/lib/commonjs/version.js.map +1 -1
  66. package/lib/module/ft_mobile_agent.js +36 -3
  67. package/lib/module/ft_mobile_agent.js.map +1 -1
  68. package/lib/module/ft_rum.js +46 -2
  69. package/lib/module/ft_rum.js.map +1 -1
  70. package/lib/module/ft_tracing.js +12 -1
  71. package/lib/module/ft_tracing.js.map +1 -1
  72. package/lib/module/index.js +3 -1
  73. package/lib/module/index.js.map +1 -1
  74. package/lib/module/rum/FTBabelInteractionTracking.js +66 -0
  75. package/lib/module/rum/FTBabelInteractionTracking.js.map +1 -0
  76. package/lib/module/rum/FTBabelUtils.js +80 -0
  77. package/lib/module/rum/FTBabelUtils.js.map +1 -0
  78. package/lib/module/rum/FTRumActionTracking.js +10 -39
  79. package/lib/module/rum/FTRumActionTracking.js.map +1 -1
  80. package/lib/module/rum/FTRumActionTrackingContext.js +43 -0
  81. package/lib/module/rum/FTRumActionTrackingContext.js.map +1 -0
  82. package/lib/module/rum/FTRumWebSocketNative.js +62 -0
  83. package/lib/module/rum/FTRumWebSocketNative.js.map +1 -0
  84. package/lib/module/rum/FTRumWebSocketTiming.js +49 -0
  85. package/lib/module/rum/FTRumWebSocketTiming.js.map +1 -0
  86. package/lib/module/rum/FTRumWebSocketTracking.js +503 -0
  87. package/lib/module/rum/FTRumWebSocketTracking.js.map +1 -0
  88. package/lib/module/rum/longTasksUtils.js +18 -0
  89. package/lib/module/rum/longTasksUtils.js.map +1 -0
  90. package/lib/module/specs/NativeFTMobileReactNative.js.map +1 -1
  91. package/lib/module/specs/NativeFTReactNativeRUM.js.map +1 -1
  92. package/lib/module/specs/NativeFTReactNativeTrace.js.map +1 -1
  93. package/lib/module/specs/NativeFTReactNativeWebSocket.js +8 -0
  94. package/lib/module/specs/NativeFTReactNativeWebSocket.js.map +1 -0
  95. package/lib/module/version.js +1 -1
  96. package/lib/module/version.js.map +1 -1
  97. package/lib/typescript/ft_mobile_agent.d.ts +7 -0
  98. package/lib/typescript/ft_rum.d.ts +9 -0
  99. package/lib/typescript/index.d.ts +3 -1
  100. package/lib/typescript/rum/FTBabelInteractionTracking.d.ts +30 -0
  101. package/lib/typescript/rum/FTBabelUtils.d.ts +1 -0
  102. package/lib/typescript/rum/FTRumActionTracking.d.ts +0 -2
  103. package/lib/typescript/rum/FTRumActionTrackingContext.d.ts +11 -0
  104. package/lib/typescript/rum/FTRumWebSocketNative.d.ts +23 -0
  105. package/lib/typescript/rum/FTRumWebSocketTiming.d.ts +2 -0
  106. package/lib/typescript/rum/FTRumWebSocketTracking.d.ts +56 -0
  107. package/lib/typescript/rum/longTasksUtils.d.ts +8 -0
  108. package/lib/typescript/specs/NativeFTMobileReactNative.d.ts +14 -0
  109. package/lib/typescript/specs/NativeFTReactNativeRUM.d.ts +4 -0
  110. package/lib/typescript/specs/NativeFTReactNativeTrace.d.ts +12 -0
  111. package/lib/typescript/specs/NativeFTReactNativeWebSocket.d.ts +13 -0
  112. package/lib/typescript/version.d.ts +1 -1
  113. package/package.json +7 -6
  114. package/src/ft_mobile_agent.tsx +49 -6
  115. package/src/ft_rum.tsx +61 -2
  116. package/src/ft_tracing.tsx +15 -1
  117. package/src/index.tsx +4 -0
  118. package/src/rum/FTBabelInteractionTracking.ts +124 -0
  119. package/src/rum/FTBabelUtils.tsx +99 -0
  120. package/src/rum/FTRumActionTracking.tsx +14 -43
  121. package/src/rum/FTRumActionTrackingContext.ts +68 -0
  122. package/src/rum/FTRumWebSocketNative.ts +106 -0
  123. package/src/rum/FTRumWebSocketTiming.ts +50 -0
  124. package/src/rum/FTRumWebSocketTracking.ts +727 -0
  125. package/src/rum/longTasksUtils.ts +17 -0
  126. package/src/specs/NativeFTMobileReactNative.ts +17 -0
  127. package/src/specs/NativeFTReactNativeRUM.ts +4 -0
  128. package/src/specs/NativeFTReactNativeTrace.ts +12 -0
  129. package/src/specs/NativeFTReactNativeWebSocket.ts +33 -0
  130. package/src/version.ts +1 -1
@@ -8,12 +8,22 @@
8
8
 
9
9
  #import "FTReactNativeTrace.h"
10
10
  #import "FtMobileAgent.h"
11
- #import <FTMobileSDK/FTMobileAgent.h>
12
- #import <FTMobileSDK/FTExternalDataManager.h>
13
- #import <FTMobileSDK/FTResourceMetricsModel.h>
14
- #import <FTMobileSDK/FTResourceContentModel.h>
11
+ #import <TrueWatchSDK/FTMobileAgent.h>
12
+ #import <TrueWatchSDK/FTExternalDataManager.h>
13
+ #import <TrueWatchSDK/FTExternalDataManager+Private.h>
14
+ #import "FTWebSocketResourceData.h"
15
+ #import <TrueWatchSDK/FTResourceMetricsModel.h>
16
+ #import <TrueWatchSDK/FTResourceContentModel.h>
15
17
  #import <React/RCTConvert.h>
16
- #import <FTMobileSDK/FTTraceManager.h>
18
+ #import <TrueWatchSDK/FTTraceManager.h>
19
+
20
+ @interface FTReactNativeTrace ()
21
+ // Keep no extra owner alive. Cancellation must not obtain a singleton after
22
+ // shutdown or depend on a WebSocket Resource having started.
23
+ @property (nonatomic, weak) id<FTExternalResourceProtocol> traceResourceDelegate;
24
+ - (nullable NSDictionary *)traceHeaderFieldsForURL:(NSString *)url key:(nullable NSString *)key;
25
+ @end
26
+
17
27
  @implementation FTReactNativeTrace
18
28
  RCT_EXPORT_MODULE()
19
29
 
@@ -32,6 +42,21 @@ RCT_REMAP_METHOD(getTraceHeaderFields,
32
42
  [self getTraceHeaderFields:url key:key resolve:resolve reject:reject];
33
43
  }
34
44
 
45
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getTraceHeaderFieldsSync:(NSString *)url
46
+ key:(NSString *)key) {
47
+ return [self traceHeaderFieldsForURL:url key:key];
48
+ }
49
+
50
+ RCT_REMAP_METHOD(cancelWebSocketTrace,
51
+ cancelWebSocketTrace:(NSString *)key
52
+ resolve:(RCTPromiseResolveBlock)resolve
53
+ reject:(RCTPromiseRejectBlock)reject) {
54
+ @synchronized (self) {
55
+ FTWebSocketDiscardTraceResource(self.traceResourceDelegate, key);
56
+ }
57
+ resolve(nil);
58
+ }
59
+
35
60
  #ifdef RCT_NEW_ARCH_ENABLED
36
61
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
37
62
  return std::make_shared<facebook::react::NativeFTReactNativeTraceSpecJSI>(params);
@@ -42,12 +67,7 @@ RCT_REMAP_METHOD(getTraceHeaderFields,
42
67
  }
43
68
 
44
69
  - (void)getTraceHeaderFields:(NSString *)url key:(NSString *)key resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
45
- NSDictionary *traceHeader = nil;
46
- if(key&&key.length>0){
47
- traceHeader = [[FTExternalDataManager sharedManager] getTraceHeaderWithKey:key url:[NSURL URLWithString:url]];
48
- }else{
49
- traceHeader = [[FTExternalDataManager sharedManager] getTraceHeaderWithUrl:[NSURL URLWithString:url]];
50
- }
70
+ NSDictionary *traceHeader = [self traceHeaderFieldsForURL:url key:key];
51
71
  if (traceHeader) {
52
72
  resolve(traceHeader);
53
73
  }else{
@@ -55,10 +75,34 @@ RCT_REMAP_METHOD(getTraceHeaderFields,
55
75
  }
56
76
  }
57
77
 
78
+ - (nullable NSDictionary *)traceHeaderFieldsForURL:(NSString *)url key:(nullable NSString *)key {
79
+ NSURL *requestURL = [NSURL URLWithString:url];
80
+ if (!requestURL) {
81
+ return nil;
82
+ }
83
+ if (key.length > 0) {
84
+ @synchronized (self) {
85
+ // Use the exact delegate that creates the keyed correlation. Record it
86
+ // before generation, including a generation that subsequently throws.
87
+ id<FTExternalResourceProtocol> delegate = [FTExternalDataManager sharedManager].resourceDelegate;
88
+ self.traceResourceDelegate = delegate;
89
+ if ([delegate respondsToSelector:@selector(getTraceHeaderWithKey:url:)]) {
90
+ return [delegate getTraceHeaderWithKey:key url:requestURL];
91
+ }
92
+ return nil;
93
+ }
94
+ }
95
+ return [[FTExternalDataManager sharedManager] getTraceHeaderWithUrl:requestURL];
96
+ }
97
+
98
+ - (void)invalidate {
99
+ @synchronized (self) { self.traceResourceDelegate = nil; }
100
+ }
101
+
58
102
  - (void)setConfig:(NSDictionary *)context resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
59
103
  FTTraceConfig *trace = [[FTTraceConfig alloc]init];
60
104
  if ([context.allKeys containsObject:@"sampleRate"]) {
61
- trace.samplerate =[RCTConvert double:context[@"sampleRate"]] * 100;
105
+ trace.sampleRate = [RCTConvert double:context[@"sampleRate"]] * 100;
62
106
  }
63
107
  if ([context.allKeys containsObject:@"traceType"]) {
64
108
  int traceType = [RCTConvert int:context[@"traceType"]];
@@ -71,4 +115,3 @@ RCT_REMAP_METHOD(getTraceHeaderFields,
71
115
  }
72
116
 
73
117
  @end
74
-
@@ -9,24 +9,19 @@
9
9
 
10
10
  @implementation FTReactNativeUtils
11
11
  + (BOOL)filterBlackResource:(NSURL *)url{
12
- static NSMutableArray *internalDevResourceBlacklist;
12
+ static NSRegularExpression *internalDevResourceRegex;
13
13
  static dispatch_once_t onceToken;
14
14
  dispatch_once(&onceToken, ^{
15
- internalDevResourceBlacklist = [NSMutableArray new];
16
- NSError *error = nil;
17
- NSString *pattern = @"^http://((10|172|192).[0-9]+.[0-9]+.[0-9]+|localhost|127.0.0.1):808[0-9]/logs$";
18
- NSRegularExpression * regularExpress = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];
19
- [internalDevResourceBlacklist addObject:regularExpress];
20
- NSString *rn = @"^http://localhost:808[0-9]/(hot|symbolicate|message|inspector|status|assets).*$";
21
- NSRegularExpression * rnRegularExpress = [NSRegularExpression regularExpressionWithPattern:rn options:NSRegularExpressionCaseInsensitive error:&error];
22
- [internalDevResourceBlacklist addObject:rnRegularExpress];
15
+ NSString *pattern = @"^(?:https?|wss?)://(?:(?:10|172|192)(?:\\.[0-9]+){3}|localhost|127\\.0\\.0\\.1|\\[::1\\]):808[0-9]/(?:hot|symbolicate|message|inspector|status|assets|logs|debugger-proxy)(?:[/?#].*)?$";
16
+ internalDevResourceRegex = [NSRegularExpression regularExpressionWithPattern:pattern
17
+ options:NSRegularExpressionCaseInsensitive
18
+ error:NULL];
23
19
  });
24
- for (NSRegularExpression *regex in internalDevResourceBlacklist) {
25
- NSTextCheckingResult *firstMatch =[regex firstMatchInString:url.absoluteString options:0 range:NSMakeRange(0, [url.absoluteString length])];
26
- if (firstMatch) {
27
- return YES;
28
- }
20
+ NSString *absoluteString = url.absoluteString;
21
+ if (absoluteString.length == 0) {
22
+ return NO;
29
23
  }
30
- return NO;
24
+ NSRange fullRange = NSMakeRange(0, absoluteString.length);
25
+ return [internalDevResourceRegex firstMatchInString:absoluteString options:0 range:fullRange] != nil;
31
26
  }
32
27
  @end
@@ -0,0 +1,10 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <React/RCTBridgeModule.h>
3
+
4
+ #ifdef RCT_NEW_ARCH_ENABLED
5
+ #import <FTSdkReactNative/FTSdkReactNative.h>
6
+ @interface FTReactNativeWebSocket : NSObject <NativeFTReactNativeWebSocketSpec>
7
+ #else
8
+ @interface FTReactNativeWebSocket : NSObject <RCTBridgeModule>
9
+ #endif
10
+ @end
@@ -0,0 +1,201 @@
1
+ #import "FTReactNativeWebSocket.h"
2
+ #import "FTWebSocketMetadataStore.h"
3
+ #import "FTWebSocketResourceData.h"
4
+ #import "FTReactNativeResource.h"
5
+ #import <TrueWatchSDK/FTExternalDataManager.h>
6
+ #import <TrueWatchSDK/FTURLSessionInterceptor.h>
7
+ #import <TrueWatchSDK/FTURLSessionInterceptorProtocol.h>
8
+ #import <React/RCTBridge.h>
9
+
10
+ @interface FTReactNativeWebSocket ()
11
+ // Retired capture sessions can finish after configuration disables/re-enables
12
+ // collection. Each store keeps its own association namespace; all socket refs
13
+ // still share the process-wide 256 cap and native expiry timer.
14
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, FTWebSocketMetadataStore *> *sessions;
15
+ @property (nonatomic, strong) NSMutableArray<NSString *> *sessionOrder;
16
+ @property (nonatomic, copy) NSString *currentSession;
17
+ // Capture the current native instance weakly. Cancellation must not call
18
+ // +shared after shutdown and accidentally recreate an interceptor singleton.
19
+ @property (nonatomic, weak) id<FTURLSessionInterceptorProtocol> resourceInterceptor;
20
+ @end
21
+
22
+ @implementation FTReactNativeWebSocket
23
+ @synthesize bridge = _bridge;
24
+ RCT_EXPORT_MODULE()
25
+
26
+ + (BOOL)requiresMainQueueSetup { return NO; }
27
+
28
+ - (instancetype)init {
29
+ if ((self = [super init])) {
30
+ _sessions = [NSMutableDictionary new];
31
+ _sessionOrder = [NSMutableArray new];
32
+ }
33
+ return self;
34
+ }
35
+
36
+ - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); }
37
+
38
+ RCT_REMAP_METHOD(startCapture,
39
+ startCapture:(NSString *)session
40
+ resolve:(RCTPromiseResolveBlock)resolve
41
+ reject:(RCTPromiseRejectBlock)reject) {
42
+ @synchronized (self) {
43
+ @try {
44
+ if (self.sessions[session]) {
45
+ // A duplicate setup cannot reactivate a retired session.
46
+ resolve(nil);
47
+ return;
48
+ }
49
+ id owner = nil;
50
+ @try {
51
+ Class moduleClass = NSClassFromString(@"RCTWebSocketModule");
52
+ owner = moduleClass ? [self.bridge moduleForClass:moduleClass] : nil;
53
+ } @catch (__unused NSException *exception) {
54
+ // RN module lookup only affects optional socket enrichment.
55
+ }
56
+ [self.sessions[self.currentSession ?: @""] stopForSession:self.currentSession];
57
+ FTWebSocketMetadataStore *store = [FTWebSocketMetadataStore new];
58
+ if ([store startForOwner:owner session:session]) {
59
+ // Bound bookkeeping for repeated configuration as well as socket refs.
60
+ if (self.sessionOrder.count >= 256) {
61
+ NSString *oldest = self.sessionOrder.firstObject;
62
+ [self.sessions[oldest] clear];
63
+ [self.sessions removeObjectForKey:oldest];
64
+ [self.sessionOrder removeObjectAtIndex:0];
65
+ }
66
+ self.sessions[session] = store;
67
+ [self.sessionOrder addObject:session];
68
+ self.currentSession = session;
69
+ }
70
+ resolve(nil);
71
+ } @catch (__unused NSException *exception) { reject(@"E_WS_CAPTURE", @"Unable to create WebSocket capture session", nil); }
72
+ }
73
+ }
74
+
75
+ RCT_REMAP_METHOD(stopCapture,
76
+ stopCapture:(NSString *)session
77
+ resolve:(RCTPromiseResolveBlock)resolve
78
+ reject:(RCTPromiseRejectBlock)reject) {
79
+ @synchronized (self) {
80
+ [self.sessions[session] stopForSession:session];
81
+ resolve(nil);
82
+ }
83
+ }
84
+
85
+ RCT_REMAP_METHOD(clear,
86
+ clear:(RCTPromiseResolveBlock)resolve
87
+ reject:(RCTPromiseRejectBlock)reject) {
88
+ @synchronized (self) {
89
+ [self clearSessions];
90
+ resolve(nil);
91
+ }
92
+ }
93
+
94
+ RCT_REMAP_METHOD(startResource,
95
+ startResource:(NSString *)key
96
+ socketID:(double)socketID
97
+ url:(NSString *)url
98
+ session:(NSString *)session
99
+ sequence:(double)sequence
100
+ property:(NSDictionary *)property
101
+ resolve:(RCTPromiseResolveBlock)resolve
102
+ reject:(RCTPromiseRejectBlock)reject) {
103
+ @synchronized (self) {
104
+ @try {
105
+ FTWebSocketMetadataStore *store = self.sessions[session];
106
+ [store startResource:key socketID:@(socketID) URL:url session:session sequence:sequence report:^{
107
+ // The store invokes this block under its SDK lifecycle lock. Resolve
108
+ // the singleton here, before shutdown can clear the native instance.
109
+ id<FTURLSessionInterceptorProtocol> interceptor = (id)[FTURLSessionInterceptor shared];
110
+ self.resourceInterceptor = interceptor;
111
+ FTResourceUrlHandler blocked = nil;
112
+ FTIntakeUrl allowed = nil;
113
+ if ([interceptor respondsToSelector:@selector(resourceUrlHandler)]) blocked = interceptor.resourceUrlHandler;
114
+ if ([interceptor respondsToSelector:@selector(intakeUrlHandler)]) allowed = interceptor.intakeUrlHandler;
115
+ if (!FTWebSocketShouldCollectResource([NSURL URLWithString:url], blocked, allowed)) {
116
+ // Cancel also leaves a terminal tombstone, so queued stop/add cannot
117
+ // report a filtered Resource or retain its socket until expiry.
118
+ [store cancelResource:key session:session];
119
+ FTWebSocketDiscardTraceResource(interceptor, key);
120
+ return;
121
+ }
122
+ [[FTExternalDataManager sharedManager] startResourceWithKey:key property:property];
123
+ }];
124
+ resolve(nil);
125
+ } @catch (__unused NSException *exception) {
126
+ if ([self.sessions[session] cancelResource:key session:session]) {
127
+ FTWebSocketDiscardTraceResource(self.resourceInterceptor, key);
128
+ }
129
+ reject(@"E_WS_RESOURCE", @"Unable to start WebSocket resource", nil);
130
+ }
131
+ }
132
+ }
133
+
134
+ RCT_REMAP_METHOD(stopResource,
135
+ stopResource:(NSString *)key
136
+ event:(NSString *)event
137
+ session:(NSString *)session
138
+ property:(NSDictionary *)property
139
+ resolve:(RCTPromiseResolveBlock)resolve
140
+ reject:(RCTPromiseRejectBlock)reject) {
141
+ @synchronized (self) {
142
+ @try {
143
+ [self.sessions[session] stopResource:key event:event session:session report:^{
144
+ [[FTExternalDataManager sharedManager] stopResourceWithKey:key property:property];
145
+ }];
146
+ resolve(nil);
147
+ } @catch (__unused NSException *exception) { reject(@"E_WS_RESOURCE", @"Unable to stop WebSocket resource", nil); }
148
+ }
149
+ }
150
+
151
+ RCT_REMAP_METHOD(addResource,
152
+ addResource:(NSString *)key
153
+ content:(NSDictionary *)content
154
+ metrics:(NSDictionary *)metrics
155
+ session:(NSString *)session
156
+ resolve:(RCTPromiseResolveBlock)resolve
157
+ reject:(RCTPromiseRejectBlock)reject) {
158
+ @synchronized (self) {
159
+ @try {
160
+ [self.sessions[session] addResource:key event:content[@"webSocketEvent"] session:session report:^(NSDictionary *metadata, NSString *event) {
161
+ NSDictionary *data = FTWebSocketResourceData(content, metrics, metadata, event);
162
+ FTAddReactNativeResource(key, data[@"content"], data[@"metrics"]);
163
+ }];
164
+ resolve(nil);
165
+ } @catch (__unused NSException *exception) { reject(@"E_WS_RESOURCE", @"Unable to add WebSocket resource", nil); }
166
+ }
167
+ }
168
+
169
+ RCT_REMAP_METHOD(releaseResource,
170
+ releaseResource:(NSString *)key
171
+ session:(NSString *)session
172
+ resolve:(RCTPromiseResolveBlock)resolve
173
+ reject:(RCTPromiseRejectBlock)reject) {
174
+ @synchronized (self) {
175
+ if ([self.sessions[session] cancelResource:key session:session]) {
176
+ FTWebSocketDiscardTraceResource(self.resourceInterceptor, key);
177
+ }
178
+ resolve(nil);
179
+ }
180
+ }
181
+
182
+ - (void)invalidate { [self clearSessions]; }
183
+ - (void)clearSessions {
184
+ @synchronized (self) {
185
+ for (FTWebSocketMetadataStore *store in self.sessions.allValues) [store clear];
186
+ [self.sessions removeAllObjects];
187
+ [self.sessionOrder removeAllObjects];
188
+ self.currentSession = nil;
189
+ self.resourceInterceptor = nil;
190
+ }
191
+ }
192
+ - (void)dealloc {
193
+ for (FTWebSocketMetadataStore *store in _sessions.allValues) [store clear];
194
+ }
195
+
196
+ #ifdef RCT_NEW_ARCH_ENABLED
197
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
198
+ return std::make_shared<facebook::react::NativeFTReactNativeWebSocketSpecJSI>(params);
199
+ }
200
+ #endif
201
+ @end
@@ -0,0 +1,25 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ /// Bounded handshake-only ownership. Hooks initialization and HTTP response
6
+ /// completion; JS alone controls Resource lifecycle. No delegate events hooked.
7
+ @interface FTWebSocketMetadataStore : NSObject
8
+ // An unavailable RN owner disables enrichment only; Resource reporting remains valid.
9
+ - (BOOL)startForOwner:(nullable id)owner session:(NSString *)session;
10
+ - (void)stopForSession:(NSString *)session;
11
+ - (void)clear;
12
+ - (void)bindResource:(NSString *)key socketID:(NSNumber *)socketID URL:(NSString *)URL session:(NSString *)session sequence:(double)sequence;
13
+ - (void)releaseResource:(NSString *)key session:(NSString *)session;
14
+ - (nullable NSDictionary *)takeResource:(NSString *)key session:(NSString *)session;
15
+ // All callbacks run synchronously under the lifecycle lock on the module's
16
+ // main queue. No callback, bridge promise, or business socket is stored here.
17
+ - (void)startResource:(NSString *)key socketID:(NSNumber *)socketID URL:(NSString *)URL session:(NSString *)session sequence:(double)sequence report:(void (^)(void))report;
18
+ - (void)stopResource:(NSString *)key event:(NSString *)event session:(NSString *)session report:(void (^)(void))report;
19
+ - (void)addResource:(NSString *)key event:(NSString *)event session:(NSString *)session report:(void (^)(NSDictionary * _Nullable metadata, NSString *event))report;
20
+ - (BOOL)cancelResource:(NSString *)key session:(NSString *)session;
21
+ + (void)sdkDidStart;
22
+ + (void)shutDown;
23
+ @end
24
+
25
+ NS_ASSUME_NONNULL_END