@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.
- package/FTMobileReactNativeSDK.podspec +7 -5
- package/README.md +34 -0
- package/android/build.gradle +9 -2
- package/android/src/main/java/com/ft/sdk/reactnative/FTMobileImpl.java +45 -14
- package/android/src/main/java/com/ft/sdk/reactnative/FTRUMImpl.java +85 -2
- package/android/src/main/java/com/ft/sdk/reactnative/FTTraceImpl.java +5 -1
- package/android/src/main/java/com/ft/sdk/reactnative/JsLongTaskMonitor.java +281 -0
- package/android/src/main/java/com/ft/sdk/reactnative/utils/ReactNativeUtils.java +5 -12
- package/android/src/newarch/java/com/ft/sdk/reactnative/FTMobileModule.java +9 -5
- package/android/src/newarch/java/com/ft/sdk/reactnative/FTRUMModule.java +26 -1
- package/android/src/newarch/java/com/ft/sdk/reactnative/FTTraceModule.java +14 -0
- package/android/src/oldarch/java/com/ft/sdk/reactnative/FTMobileModule.java +2 -2
- package/android/src/oldarch/java/com/ft/sdk/reactnative/FTRUMModule.java +29 -1
- package/android/src/oldarch/java/com/ft/sdk/reactnative/FTTraceModule.java +12 -0
- package/android/src/rnpost70/java/com/ft/sdk/reactnative/ReactJsThreadExecutor.java +21 -0
- package/android/src/rnpre70/java/com/ft/sdk/reactnative/ReactJsThreadExecutor.java +37 -0
- package/android/src/test/java/com/ft/sdk/reactnative/FTRUMModuleTest.java +117 -0
- package/android/src/test/java/com/ft/sdk/reactnative/JsLongTaskMonitorTest.java +446 -0
- package/android/src/test/java/com/ft/sdk/reactnative/utils/ReactNativeUtilsTest.java +33 -0
- package/ios/FTJSLongTaskMonitor.h +51 -0
- package/ios/FTJSLongTaskMonitor.m +250 -0
- package/ios/FTMobileReactNative.mm +23 -16
- package/ios/FTReactNativeLog.mm +3 -4
- package/ios/FTReactNativeRUM.mm +103 -28
- package/ios/FTReactNativeResource.h +4 -0
- package/ios/FTReactNativeResource.m +58 -0
- package/ios/FTReactNativeTrace.mm +56 -13
- package/ios/FTReactNativeUtils.m +10 -15
- package/ios/FTReactNativeWebSocket.h +10 -0
- package/ios/FTReactNativeWebSocket.mm +201 -0
- package/ios/FTWebSocketMetadataStore.h +25 -0
- package/ios/FTWebSocketMetadataStore.m +622 -0
- package/ios/FTWebSocketResourceData.h +16 -0
- package/ios/FTWebSocketResourceData.m +75 -0
- package/lib/commonjs/ft_mobile_agent.js +36 -3
- package/lib/commonjs/ft_mobile_agent.js.map +1 -1
- package/lib/commonjs/ft_rum.js +46 -2
- package/lib/commonjs/ft_rum.js.map +1 -1
- package/lib/commonjs/ft_tracing.js +12 -3
- package/lib/commonjs/ft_tracing.js.map +1 -1
- package/lib/commonjs/index.js +14 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/rum/FTBabelInteractionTracking.js +72 -0
- package/lib/commonjs/rum/FTBabelInteractionTracking.js.map +1 -0
- package/lib/commonjs/rum/FTBabelUtils.js +87 -0
- package/lib/commonjs/rum/FTBabelUtils.js.map +1 -0
- package/lib/commonjs/rum/FTRumActionTracking.js +10 -39
- package/lib/commonjs/rum/FTRumActionTracking.js.map +1 -1
- package/lib/commonjs/rum/FTRumActionTrackingContext.js +50 -0
- package/lib/commonjs/rum/FTRumActionTrackingContext.js.map +1 -0
- package/lib/commonjs/rum/FTRumWebSocketNative.js +73 -0
- package/lib/commonjs/rum/FTRumWebSocketNative.js.map +1 -0
- package/lib/commonjs/rum/FTRumWebSocketTiming.js +55 -0
- package/lib/commonjs/rum/FTRumWebSocketTiming.js.map +1 -0
- package/lib/commonjs/rum/FTRumWebSocketTracking.js +510 -0
- package/lib/commonjs/rum/FTRumWebSocketTracking.js.map +1 -0
- package/lib/commonjs/rum/longTasksUtils.js +25 -0
- package/lib/commonjs/rum/longTasksUtils.js.map +1 -0
- package/lib/commonjs/specs/NativeFTMobileReactNative.js.map +1 -1
- package/lib/commonjs/specs/NativeFTReactNativeRUM.js.map +1 -1
- package/lib/commonjs/specs/NativeFTReactNativeTrace.js.map +1 -1
- package/lib/commonjs/specs/NativeFTReactNativeWebSocket.js +11 -0
- package/lib/commonjs/specs/NativeFTReactNativeWebSocket.js.map +1 -0
- package/lib/commonjs/version.js +1 -1
- package/lib/commonjs/version.js.map +1 -1
- package/lib/module/ft_mobile_agent.js +36 -3
- package/lib/module/ft_mobile_agent.js.map +1 -1
- package/lib/module/ft_rum.js +46 -2
- package/lib/module/ft_rum.js.map +1 -1
- package/lib/module/ft_tracing.js +12 -1
- package/lib/module/ft_tracing.js.map +1 -1
- package/lib/module/index.js +3 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/rum/FTBabelInteractionTracking.js +66 -0
- package/lib/module/rum/FTBabelInteractionTracking.js.map +1 -0
- package/lib/module/rum/FTBabelUtils.js +80 -0
- package/lib/module/rum/FTBabelUtils.js.map +1 -0
- package/lib/module/rum/FTRumActionTracking.js +10 -39
- package/lib/module/rum/FTRumActionTracking.js.map +1 -1
- package/lib/module/rum/FTRumActionTrackingContext.js +43 -0
- package/lib/module/rum/FTRumActionTrackingContext.js.map +1 -0
- package/lib/module/rum/FTRumWebSocketNative.js +62 -0
- package/lib/module/rum/FTRumWebSocketNative.js.map +1 -0
- package/lib/module/rum/FTRumWebSocketTiming.js +49 -0
- package/lib/module/rum/FTRumWebSocketTiming.js.map +1 -0
- package/lib/module/rum/FTRumWebSocketTracking.js +503 -0
- package/lib/module/rum/FTRumWebSocketTracking.js.map +1 -0
- package/lib/module/rum/longTasksUtils.js +18 -0
- package/lib/module/rum/longTasksUtils.js.map +1 -0
- package/lib/module/specs/NativeFTMobileReactNative.js.map +1 -1
- package/lib/module/specs/NativeFTReactNativeRUM.js.map +1 -1
- package/lib/module/specs/NativeFTReactNativeTrace.js.map +1 -1
- package/lib/module/specs/NativeFTReactNativeWebSocket.js +8 -0
- package/lib/module/specs/NativeFTReactNativeWebSocket.js.map +1 -0
- package/lib/module/version.js +1 -1
- package/lib/module/version.js.map +1 -1
- package/lib/typescript/ft_mobile_agent.d.ts +7 -0
- package/lib/typescript/ft_rum.d.ts +9 -0
- package/lib/typescript/index.d.ts +3 -1
- package/lib/typescript/rum/FTBabelInteractionTracking.d.ts +30 -0
- package/lib/typescript/rum/FTBabelUtils.d.ts +1 -0
- package/lib/typescript/rum/FTRumActionTracking.d.ts +0 -2
- package/lib/typescript/rum/FTRumActionTrackingContext.d.ts +11 -0
- package/lib/typescript/rum/FTRumWebSocketNative.d.ts +23 -0
- package/lib/typescript/rum/FTRumWebSocketTiming.d.ts +2 -0
- package/lib/typescript/rum/FTRumWebSocketTracking.d.ts +56 -0
- package/lib/typescript/rum/longTasksUtils.d.ts +8 -0
- package/lib/typescript/specs/NativeFTMobileReactNative.d.ts +14 -0
- package/lib/typescript/specs/NativeFTReactNativeRUM.d.ts +4 -0
- package/lib/typescript/specs/NativeFTReactNativeTrace.d.ts +12 -0
- package/lib/typescript/specs/NativeFTReactNativeWebSocket.d.ts +13 -0
- package/lib/typescript/version.d.ts +1 -1
- package/package.json +7 -6
- package/src/ft_mobile_agent.tsx +49 -6
- package/src/ft_rum.tsx +61 -2
- package/src/ft_tracing.tsx +15 -1
- package/src/index.tsx +4 -0
- package/src/rum/FTBabelInteractionTracking.ts +124 -0
- package/src/rum/FTBabelUtils.tsx +99 -0
- package/src/rum/FTRumActionTracking.tsx +14 -43
- package/src/rum/FTRumActionTrackingContext.ts +68 -0
- package/src/rum/FTRumWebSocketNative.ts +106 -0
- package/src/rum/FTRumWebSocketTiming.ts +50 -0
- package/src/rum/FTRumWebSocketTracking.ts +727 -0
- package/src/rum/longTasksUtils.ts +17 -0
- package/src/specs/NativeFTMobileReactNative.ts +17 -0
- package/src/specs/NativeFTReactNativeRUM.ts +4 -0
- package/src/specs/NativeFTReactNativeTrace.ts +12 -0
- package/src/specs/NativeFTReactNativeWebSocket.ts +33 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#import "FTJSLongTaskMonitor.h"
|
|
2
|
+
|
|
3
|
+
#import <QuartzCore/CADisplayLink.h>
|
|
4
|
+
#import <TrueWatchSDK/FTExternalDataManager.h>
|
|
5
|
+
#import <React/RCTBridge.h>
|
|
6
|
+
#import <React/RCTBridge+Private.h>
|
|
7
|
+
#import <React/RCTJSThread.h>
|
|
8
|
+
|
|
9
|
+
static const double FTNanosecondsPerSecond = 1000000000.0;
|
|
10
|
+
|
|
11
|
+
@interface FTReactBridgeJSQueue : NSObject <FTJSLongTaskQueue>
|
|
12
|
+
@property (nonatomic, weak) RCTBridge *bridge;
|
|
13
|
+
- (instancetype)initWithBridge:(RCTBridge *)bridge;
|
|
14
|
+
@end
|
|
15
|
+
|
|
16
|
+
@implementation FTReactBridgeJSQueue
|
|
17
|
+
- (instancetype)initWithBridge:(RCTBridge *)bridge {
|
|
18
|
+
self = [super init];
|
|
19
|
+
if (self) {
|
|
20
|
+
_bridge = bridge;
|
|
21
|
+
}
|
|
22
|
+
return self;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
- (BOOL)dispatchBlock:(dispatch_block_t)block {
|
|
26
|
+
RCTBridge *bridge = self.bridge;
|
|
27
|
+
if (bridge == nil) {
|
|
28
|
+
return NO;
|
|
29
|
+
}
|
|
30
|
+
@try {
|
|
31
|
+
// RCTBridgeProxy.valid is always NO, including while bridgeless RN is active.
|
|
32
|
+
[bridge dispatchBlock:block queue:RCTJSThread];
|
|
33
|
+
return YES;
|
|
34
|
+
} @catch (NSException *exception) {
|
|
35
|
+
// Bridge teardown may race a start request. Monitoring must not take down the host.
|
|
36
|
+
return NO;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
@end
|
|
40
|
+
|
|
41
|
+
// The display link retains this callback target, not the scheduler that owns it.
|
|
42
|
+
@interface FTJSLongTaskDisplayLinkTarget : NSObject
|
|
43
|
+
@property (nonatomic, copy) FTJSLongTaskFrameCallback callback;
|
|
44
|
+
@end
|
|
45
|
+
|
|
46
|
+
@implementation FTJSLongTaskDisplayLinkTarget
|
|
47
|
+
- (void)displayLinkDidFire:(CADisplayLink *)displayLink {
|
|
48
|
+
self.callback(displayLink.timestamp);
|
|
49
|
+
}
|
|
50
|
+
@end
|
|
51
|
+
|
|
52
|
+
@interface FTCADisplayLinkScheduler : NSObject <FTJSLongTaskFrameScheduler>
|
|
53
|
+
@property (nonatomic, copy) FTJSLongTaskFrameCallback callback;
|
|
54
|
+
@property (nonatomic, strong, nullable) CADisplayLink *displayLink;
|
|
55
|
+
- (instancetype)initWithCallback:(FTJSLongTaskFrameCallback)callback;
|
|
56
|
+
@end
|
|
57
|
+
|
|
58
|
+
@implementation FTCADisplayLinkScheduler
|
|
59
|
+
- (instancetype)initWithCallback:(FTJSLongTaskFrameCallback)callback {
|
|
60
|
+
self = [super init];
|
|
61
|
+
if (self) {
|
|
62
|
+
_callback = [callback copy];
|
|
63
|
+
}
|
|
64
|
+
return self;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
- (void)start {
|
|
68
|
+
[self stop];
|
|
69
|
+
FTJSLongTaskDisplayLinkTarget *target = [FTJSLongTaskDisplayLinkTarget new];
|
|
70
|
+
target.callback = self.callback;
|
|
71
|
+
self.displayLink = [CADisplayLink displayLinkWithTarget:target selector:@selector(displayLinkDidFire:)];
|
|
72
|
+
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
- (void)stop {
|
|
76
|
+
// CADisplayLink.invalidate is thread safe and releases its run loop and target
|
|
77
|
+
// references without requiring a live JS queue.
|
|
78
|
+
[self.displayLink invalidate];
|
|
79
|
+
self.displayLink = nil;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
- (void)dealloc {
|
|
83
|
+
[_displayLink invalidate];
|
|
84
|
+
}
|
|
85
|
+
@end
|
|
86
|
+
|
|
87
|
+
@interface FTCADisplayLinkSchedulerFactory : NSObject <FTJSLongTaskFrameSchedulerFactory>
|
|
88
|
+
@end
|
|
89
|
+
|
|
90
|
+
@implementation FTCADisplayLinkSchedulerFactory
|
|
91
|
+
- (id<FTJSLongTaskFrameScheduler>)createSchedulerWithCallback:(FTJSLongTaskFrameCallback)callback {
|
|
92
|
+
return [[FTCADisplayLinkScheduler alloc] initWithCallback:callback];
|
|
93
|
+
}
|
|
94
|
+
@end
|
|
95
|
+
|
|
96
|
+
@interface FTNativeLongTaskReporter : NSObject <FTJSLongTaskReporter>
|
|
97
|
+
@end
|
|
98
|
+
|
|
99
|
+
@implementation FTNativeLongTaskReporter
|
|
100
|
+
- (void)reportLongTaskWithDurationNanoseconds:(int64_t)durationNanoseconds property:(NSDictionary *)property {
|
|
101
|
+
[[FTExternalDataManager sharedManager]
|
|
102
|
+
addLongTaskWithStack:@""
|
|
103
|
+
duration:@(durationNanoseconds)
|
|
104
|
+
property:property];
|
|
105
|
+
}
|
|
106
|
+
@end
|
|
107
|
+
|
|
108
|
+
@interface FTJSLongTaskMonitor ()
|
|
109
|
+
@property (nonatomic, strong) id<FTJSLongTaskQueue> queue;
|
|
110
|
+
@property (nonatomic, strong) id<FTJSLongTaskFrameSchedulerFactory> schedulerFactory;
|
|
111
|
+
@property (nonatomic, strong) id<FTJSLongTaskReporter> reporter;
|
|
112
|
+
@property (nonatomic, strong) NSObject *stateLock;
|
|
113
|
+
@property (nonatomic, assign) NSTimeInterval thresholdSeconds;
|
|
114
|
+
@property (nonatomic, assign) NSUInteger generation;
|
|
115
|
+
@property (nonatomic, assign) BOOL requestedRunning;
|
|
116
|
+
@property (nonatomic, strong, nullable) id<FTJSLongTaskFrameScheduler> scheduler;
|
|
117
|
+
@property (nonatomic, assign) NSUInteger activeGeneration;
|
|
118
|
+
@property (nonatomic, assign) CFTimeInterval lastFrameTimestamp;
|
|
119
|
+
@property (atomic, copy) NSDictionary *bridgeContext;
|
|
120
|
+
@end
|
|
121
|
+
|
|
122
|
+
@implementation FTJSLongTaskMonitor
|
|
123
|
+
|
|
124
|
+
+ (instancetype)monitorWithBridge:(RCTBridge *)bridge {
|
|
125
|
+
return [[self alloc]
|
|
126
|
+
initWithQueue:[[FTReactBridgeJSQueue alloc] initWithBridge:bridge]
|
|
127
|
+
schedulerFactory:[FTCADisplayLinkSchedulerFactory new]
|
|
128
|
+
reporter:[FTNativeLongTaskReporter new]];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
- (instancetype)initWithQueue:(id<FTJSLongTaskQueue>)queue
|
|
132
|
+
schedulerFactory:(id<FTJSLongTaskFrameSchedulerFactory>)schedulerFactory
|
|
133
|
+
reporter:(id<FTJSLongTaskReporter>)reporter {
|
|
134
|
+
self = [super init];
|
|
135
|
+
if (self) {
|
|
136
|
+
_queue = queue;
|
|
137
|
+
_schedulerFactory = schedulerFactory;
|
|
138
|
+
_reporter = reporter;
|
|
139
|
+
_stateLock = [NSObject new];
|
|
140
|
+
_bridgeContext = @{};
|
|
141
|
+
}
|
|
142
|
+
return self;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
- (void)setThresholdMilliseconds:(double)thresholdMilliseconds {
|
|
146
|
+
@synchronized (self.stateLock) {
|
|
147
|
+
self.thresholdSeconds = thresholdMilliseconds <= 0 ? 0 : thresholdMilliseconds / 1000.0;
|
|
148
|
+
if (thresholdMilliseconds <= 0) {
|
|
149
|
+
[self stopLocked];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
- (void)start {
|
|
155
|
+
NSUInteger startGeneration;
|
|
156
|
+
@synchronized (self.stateLock) {
|
|
157
|
+
if (self.thresholdSeconds == 0) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
self.requestedRunning = YES;
|
|
161
|
+
startGeneration = ++self.generation;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
__weak typeof(self) weakSelf = self;
|
|
165
|
+
BOOL dispatched = [self.queue dispatchBlock:^{
|
|
166
|
+
// A pending block must not keep a destroyed monitor (and its queue) alive.
|
|
167
|
+
typeof(self) strongSelf = weakSelf;
|
|
168
|
+
if (strongSelf == nil) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
@synchronized (strongSelf.stateLock) {
|
|
172
|
+
if (strongSelf.generation != startGeneration || !strongSelf.requestedRunning) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
[strongSelf.scheduler stop];
|
|
176
|
+
strongSelf.scheduler = [strongSelf.schedulerFactory createSchedulerWithCallback:^(CFTimeInterval timestamp) {
|
|
177
|
+
[weakSelf handleFrameTimestamp:timestamp generation:startGeneration];
|
|
178
|
+
}];
|
|
179
|
+
strongSelf.activeGeneration = startGeneration;
|
|
180
|
+
strongSelf.lastFrameTimestamp = 0;
|
|
181
|
+
[strongSelf.scheduler start];
|
|
182
|
+
}
|
|
183
|
+
}];
|
|
184
|
+
if (!dispatched) {
|
|
185
|
+
@synchronized (self.stateLock) {
|
|
186
|
+
if (self.generation == startGeneration) {
|
|
187
|
+
[self stopLocked];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
- (void)stop {
|
|
194
|
+
[self stopWithCompletion:nil];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
- (void)disableWithCompletion:(dispatch_block_t)completion {
|
|
198
|
+
@synchronized (self.stateLock) {
|
|
199
|
+
self.thresholdSeconds = 0;
|
|
200
|
+
[self stopLocked];
|
|
201
|
+
}
|
|
202
|
+
if (completion != nil) {
|
|
203
|
+
completion();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
- (void)stopWithCompletion:(dispatch_block_t)completion {
|
|
208
|
+
@synchronized (self.stateLock) {
|
|
209
|
+
[self stopLocked];
|
|
210
|
+
}
|
|
211
|
+
if (completion != nil) {
|
|
212
|
+
completion();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Caller holds stateLock, also used by scheduler installation and frame callbacks.
|
|
217
|
+
// Cleanup deliberately never dispatches to RN: an invalid proxy may silently drop it.
|
|
218
|
+
- (void)stopLocked {
|
|
219
|
+
self.requestedRunning = NO;
|
|
220
|
+
self.generation++;
|
|
221
|
+
[self.scheduler stop];
|
|
222
|
+
self.scheduler = nil;
|
|
223
|
+
self.activeGeneration = 0;
|
|
224
|
+
self.lastFrameTimestamp = 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
- (void)handleFrameTimestamp:(CFTimeInterval)timestamp generation:(NSUInteger)generation {
|
|
228
|
+
@synchronized (self.stateLock) {
|
|
229
|
+
if (!self.requestedRunning || self.generation != generation ||
|
|
230
|
+
self.activeGeneration != generation || self.scheduler == nil) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (self.lastFrameTimestamp != 0) {
|
|
234
|
+
NSTimeInterval duration = timestamp - self.lastFrameTimestamp;
|
|
235
|
+
if (duration > self.thresholdSeconds) {
|
|
236
|
+
int64_t durationNanoseconds = (int64_t)(duration * FTNanosecondsPerSecond);
|
|
237
|
+
// Capture at detection; later updates must not change an already reported event.
|
|
238
|
+
NSDictionary *property = self.bridgeContext;
|
|
239
|
+
[self.reporter reportLongTaskWithDurationNanoseconds:durationNanoseconds property:property];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
self.lastFrameTimestamp = timestamp;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
- (void)dealloc {
|
|
247
|
+
[_scheduler stop];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@end
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
#import "FTMobileReactNative.h"
|
|
10
10
|
#import <React/RCTConvert.h>
|
|
11
|
-
#import <
|
|
12
|
-
#import <
|
|
13
|
-
#import <
|
|
14
|
-
#import <
|
|
15
|
-
#import <
|
|
16
|
-
#import <
|
|
11
|
+
#import <TrueWatchSDK/FTMobileAgent.h>
|
|
12
|
+
#import <TrueWatchSDK/FTSDKConfig+Private.h>
|
|
13
|
+
#import <TrueWatchSDK/FTThreadDispatchManager.h>
|
|
14
|
+
#import <TrueWatchSDK/FTConstants.h>
|
|
15
|
+
#import <TrueWatchSDK/FTJSONUtil.h>
|
|
16
|
+
#import <TrueWatchSDK/FTRemoteConfigModel+Private.h>
|
|
17
|
+
#import "FTWebSocketMetadataStore.h"
|
|
17
18
|
|
|
18
19
|
static NSString *const FTRemoteConfigCallbackEvent = @"ft_remote_config_callback";
|
|
19
20
|
|
|
@@ -373,14 +374,14 @@ RCT_REMAP_METHOD(clearAllData,
|
|
|
373
374
|
#endif
|
|
374
375
|
- (void)sdkConfig:(NSDictionary *)context resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
375
376
|
[FTThreadDispatchManager performBlockDispatchMainSyncSafe:^{
|
|
376
|
-
|
|
377
|
+
FTSDKConfig *config;
|
|
377
378
|
NSString *datakitUrl = [RCTConvert NSString:context[@"datakitUrl"]];
|
|
378
379
|
NSString *dataWayUrl = [RCTConvert NSString:context[@"datawayUrl"]];
|
|
379
380
|
NSString *clientToken = [RCTConvert NSString:context[@"clientToken"]];
|
|
380
381
|
if(dataWayUrl && dataWayUrl.length>0 && clientToken && clientToken.length>0){
|
|
381
|
-
config = [[
|
|
382
|
+
config = [[FTSDKConfig alloc]initWithDatawayUrl:dataWayUrl clientToken:clientToken];
|
|
382
383
|
}else if(datakitUrl && datakitUrl.length>0){
|
|
383
|
-
config = [[
|
|
384
|
+
config = [[FTSDKConfig alloc]initWithDatakitUrl:datakitUrl];
|
|
384
385
|
}else{
|
|
385
386
|
resolve(nil);
|
|
386
387
|
return;
|
|
@@ -421,6 +422,12 @@ RCT_REMAP_METHOD(clearAllData,
|
|
|
421
422
|
if ([context.allKeys containsObject:@"compressIntakeRequests"]) {
|
|
422
423
|
config.compressIntakeRequests = [RCTConvert BOOL:context[@"compressIntakeRequests"]];
|
|
423
424
|
}
|
|
425
|
+
if ([context.allKeys containsObject:@"enableDataFilter"]) {
|
|
426
|
+
config.enableDataFilter = [RCTConvert BOOL:context[@"enableDataFilter"]];
|
|
427
|
+
}
|
|
428
|
+
if ([context.allKeys containsObject:@"dataFilters"]) {
|
|
429
|
+
config.dataFilters = [RCTConvert NSDictionary:context[@"dataFilters"]];
|
|
430
|
+
}
|
|
424
431
|
if ([context.allKeys containsObject:@"globalContext"]) {
|
|
425
432
|
config.globalContext = [RCTConvert NSDictionary:context[@"globalContext"]];
|
|
426
433
|
}
|
|
@@ -484,6 +491,7 @@ RCT_REMAP_METHOD(clearAllData,
|
|
|
484
491
|
};
|
|
485
492
|
}
|
|
486
493
|
[FTMobileAgent startWithConfigOptions:config];
|
|
494
|
+
[FTWebSocketMetadataStore sdkDidStart];
|
|
487
495
|
resolve(nil);
|
|
488
496
|
}];
|
|
489
497
|
}
|
|
@@ -531,6 +539,7 @@ RCT_REMAP_METHOD(clearAllData,
|
|
|
531
539
|
}
|
|
532
540
|
|
|
533
541
|
- (void)shutDown:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
542
|
+
[FTWebSocketMetadataStore shutDown];
|
|
534
543
|
[FTMobileAgent shutDown];
|
|
535
544
|
resolve(nil);
|
|
536
545
|
}
|
|
@@ -547,9 +556,8 @@ RCT_REMAP_METHOD(clearAllData,
|
|
|
547
556
|
|
|
548
557
|
}];
|
|
549
558
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
rejecter:(RCTPromiseRejectBlock)reject){
|
|
559
|
+
RCT_EXPORT_METHOD(updateRemoteConfig:(RCTPromiseResolveBlock)resolve
|
|
560
|
+
reject:(RCTPromiseRejectBlock)reject){
|
|
553
561
|
if (!_remoteConfigurationEnabled) {
|
|
554
562
|
reject(@"E_REMOTE_CONFIG_DISABLED", @"Remote configuration is not enabled.", nil);
|
|
555
563
|
return;
|
|
@@ -574,11 +582,10 @@ RCT_REMAP_METHOD(updateRemoteConfig,
|
|
|
574
582
|
return nil;
|
|
575
583
|
}];
|
|
576
584
|
}
|
|
577
|
-
|
|
578
|
-
interval:(int)interval
|
|
585
|
+
RCT_EXPORT_METHOD(updateRemoteConfigWithMiniUpdateInterval:(double)interval
|
|
579
586
|
rules:(NSArray *)rules
|
|
580
|
-
|
|
581
|
-
|
|
587
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
588
|
+
reject:(RCTPromiseRejectBlock)reject){
|
|
582
589
|
if (!_remoteConfigurationEnabled) {
|
|
583
590
|
reject(@"E_REMOTE_CONFIG_DISABLED", @"Remote configuration is not enabled.", nil);
|
|
584
591
|
return;
|
package/ios/FTReactNativeLog.mm
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
//
|
|
8
8
|
|
|
9
9
|
#import "FTReactNativeLog.h"
|
|
10
|
-
#import <
|
|
10
|
+
#import <TrueWatchSDK/FTMobileAgent.h>
|
|
11
11
|
#import <React/RCTConvert.h>
|
|
12
|
-
#import <
|
|
12
|
+
#import <TrueWatchSDK/FTLogger+Private.h>
|
|
13
13
|
@implementation FTReactNativeLog
|
|
14
14
|
RCT_EXPORT_MODULE()
|
|
15
15
|
|
|
@@ -43,7 +43,7 @@ RCT_REMAP_METHOD(logWithStatusString,
|
|
|
43
43
|
- (void)logConfig:(NSDictionary *)context resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
44
44
|
FTLoggerConfig *logger = [[FTLoggerConfig alloc]init];
|
|
45
45
|
if ([context.allKeys containsObject:@"sampleRate"]) {
|
|
46
|
-
logger.
|
|
46
|
+
logger.sampleRate = [RCTConvert double:context[@"sampleRate"]] * 100;
|
|
47
47
|
}
|
|
48
48
|
NSArray<NSNumber *>*filters = [RCTConvert NSNumberArray:context[@"logLevelFilters"]];
|
|
49
49
|
if (filters) {
|
|
@@ -76,4 +76,3 @@ RCT_REMAP_METHOD(logWithStatusString,
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
@end
|
|
79
|
-
|
package/ios/FTReactNativeRUM.mm
CHANGED
|
@@ -7,21 +7,48 @@
|
|
|
7
7
|
//
|
|
8
8
|
|
|
9
9
|
#import "FTReactNativeRUM.h"
|
|
10
|
-
#import <
|
|
11
|
-
#import <
|
|
12
|
-
#import
|
|
13
|
-
#import <FTMobileSDK/FTResourceContentModel.h>
|
|
10
|
+
#import <TrueWatchSDK/FTMobileAgent.h>
|
|
11
|
+
#import <TrueWatchSDK/FTExternalDataManager.h>
|
|
12
|
+
#import "FTReactNativeResource.h"
|
|
14
13
|
#import <React/RCTConvert.h>
|
|
14
|
+
#import <React/RCTBridge.h>
|
|
15
|
+
#import <UIKit/UIKit.h>
|
|
15
16
|
#import "FTReactNativeUtils.h"
|
|
17
|
+
#import "FTJSLongTaskMonitor.h"
|
|
18
|
+
|
|
19
|
+
@interface FTReactNativeRUM ()
|
|
20
|
+
@property (atomic, strong, nullable) FTJSLongTaskMonitor *jsLongTaskMonitor;
|
|
21
|
+
- (FTJSLongTaskMonitor *)getOrCreateLongTaskMonitor;
|
|
22
|
+
@end
|
|
23
|
+
|
|
16
24
|
|
|
17
25
|
@implementation FTReactNativeRUM
|
|
26
|
+
@synthesize bridge = _bridge;
|
|
18
27
|
RCT_EXPORT_MODULE()
|
|
28
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(setLongTaskContext:(NSDictionary *)context) {
|
|
29
|
+
[[self getOrCreateLongTaskMonitor] setBridgeContext:context ?: @{}];
|
|
30
|
+
return @YES;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
- (FTJSLongTaskMonitor *)getOrCreateLongTaskMonitor {
|
|
34
|
+
@synchronized (self) {
|
|
35
|
+
if (self.jsLongTaskMonitor == nil) {
|
|
36
|
+
self.jsLongTaskMonitor = [FTJSLongTaskMonitor monitorWithBridge:self.bridge];
|
|
37
|
+
}
|
|
38
|
+
return self.jsLongTaskMonitor;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
19
41
|
RCT_REMAP_METHOD(setConfig,
|
|
20
42
|
context:(NSDictionary *)context
|
|
21
43
|
findEventsWithResolver:(RCTPromiseResolveBlock)resolve
|
|
22
44
|
rejecter:(RCTPromiseRejectBlock)reject){
|
|
23
45
|
[self setConfig:context resolve:resolve reject:reject];
|
|
24
46
|
}
|
|
47
|
+
RCT_REMAP_METHOD(stopLongTaskTracking,
|
|
48
|
+
stopLongTaskTrackingWithResolver:(RCTPromiseResolveBlock)resolve
|
|
49
|
+
rejecter:(RCTPromiseRejectBlock)reject){
|
|
50
|
+
[self stopLongTaskTracking:resolve reject:reject];
|
|
51
|
+
}
|
|
25
52
|
RCT_REMAP_METHOD(startAction,
|
|
26
53
|
actionName:(NSString *)actionName actionType:(NSString *)actionType property:(NSDictionary *)property
|
|
27
54
|
findEventsWithResolver:(RCTPromiseResolveBlock)resolve
|
|
@@ -88,6 +115,33 @@ RCT_REMAP_METHOD(addResource,
|
|
|
88
115
|
}
|
|
89
116
|
#endif
|
|
90
117
|
|
|
118
|
+
- (instancetype)init {
|
|
119
|
+
self = [super init];
|
|
120
|
+
if (self) {
|
|
121
|
+
[[NSNotificationCenter defaultCenter]
|
|
122
|
+
addObserver:self
|
|
123
|
+
selector:@selector(applicationDidBecomeActive:)
|
|
124
|
+
name:UIApplicationDidBecomeActiveNotification
|
|
125
|
+
object:nil];
|
|
126
|
+
[[NSNotificationCenter defaultCenter]
|
|
127
|
+
addObserver:self
|
|
128
|
+
selector:@selector(applicationWillResignActive:)
|
|
129
|
+
name:UIApplicationWillResignActiveNotification
|
|
130
|
+
object:nil];
|
|
131
|
+
}
|
|
132
|
+
return self;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
- (void)invalidate {
|
|
136
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
137
|
+
[self.jsLongTaskMonitor disableWithCompletion:nil];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
- (void)dealloc {
|
|
141
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
142
|
+
[_jsLongTaskMonitor disableWithCompletion:nil];
|
|
143
|
+
}
|
|
144
|
+
|
|
91
145
|
- (void)addAction:(NSString *)actionName actionType:(NSString *)actionType property:(NSDictionary *)property resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
92
146
|
[[FTExternalDataManager sharedManager] addAction:actionName actionType:actionType property:property];
|
|
93
147
|
resolve(nil);
|
|
@@ -104,29 +158,7 @@ RCT_REMAP_METHOD(addResource,
|
|
|
104
158
|
}
|
|
105
159
|
|
|
106
160
|
- (void)addResource:(NSString *)key resource:(NSDictionary *)content metrics:(NSDictionary *)metrics resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
107
|
-
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
FTResourceMetricsModel *metricsModel = nil;
|
|
111
|
-
if (metrics.allKeys.count>0) {
|
|
112
|
-
metricsModel = [[FTResourceMetricsModel alloc]init];
|
|
113
|
-
metricsModel.duration = [RCTConvert NSNumber:metrics[@"duration"]];
|
|
114
|
-
metricsModel.resource_dns = [RCTConvert NSNumber:metrics[@"resource_dns"]];
|
|
115
|
-
metricsModel.resource_tcp = [RCTConvert NSNumber:metrics[@"resource_tcp"]];
|
|
116
|
-
metricsModel.resource_ssl = [RCTConvert NSNumber:metrics[@"resource_ssl"]];
|
|
117
|
-
metricsModel.resource_ttfb = [RCTConvert NSNumber:metrics[@"resource_ttfb"]];
|
|
118
|
-
metricsModel.resource_trans = [RCTConvert NSNumber:metrics[@"resource_trans"]];
|
|
119
|
-
metricsModel.resource_first_byte = [RCTConvert NSNumber:metrics[@"resource_first_byte"]];
|
|
120
|
-
}
|
|
121
|
-
FTResourceContentModel *contentModel = [[FTResourceContentModel alloc]init];
|
|
122
|
-
contentModel.url = [RCTConvert NSURL:content[@"url"]];
|
|
123
|
-
contentModel.httpMethod = [RCTConvert NSString:content[@"httpMethod"]];
|
|
124
|
-
contentModel.requestHeader = [RCTConvert NSDictionary:content[@"requestHeader"]];
|
|
125
|
-
contentModel.responseHeader = [RCTConvert NSDictionary:content[@"responseHeader"]];
|
|
126
|
-
contentModel.responseBody = [RCTConvert NSString:content[@"responseBody"]];
|
|
127
|
-
contentModel.httpStatusCode = [RCTConvert int:content[@"resourceStatus"]];
|
|
128
|
-
|
|
129
|
-
[[FTExternalDataManager sharedManager] addResourceWithKey:key metrics:metricsModel content:contentModel];
|
|
161
|
+
FTAddReactNativeResource(key, content, metrics);
|
|
130
162
|
resolve(nil);
|
|
131
163
|
}
|
|
132
164
|
|
|
@@ -139,7 +171,7 @@ RCT_REMAP_METHOD(addResource,
|
|
|
139
171
|
NSString *rumAppId = [RCTConvert NSString:context[@"iOSAppId"]];
|
|
140
172
|
FTRumConfig *rumConfig = [[FTRumConfig alloc]initWithAppid:rumAppId];
|
|
141
173
|
if ([context.allKeys containsObject:@"sampleRate"]) {
|
|
142
|
-
rumConfig.
|
|
174
|
+
rumConfig.sampleRate = [RCTConvert double:context[@"sampleRate"]] * 100;
|
|
143
175
|
}
|
|
144
176
|
if ([context.allKeys containsObject:@"sessionOnErrorSampleRate"]) {
|
|
145
177
|
rumConfig.sessionOnErrorSampleRate = [RCTConvert double:context[@"sessionOnErrorSampleRate"]]*100;
|
|
@@ -177,6 +209,11 @@ RCT_REMAP_METHOD(addResource,
|
|
|
177
209
|
if ([context.allKeys containsObject:@"nativeFreezeDurationMs"]){
|
|
178
210
|
rumConfig.freezeDurationMs = [RCTConvert double:context[@"nativeFreezeDurationMs"]];
|
|
179
211
|
}
|
|
212
|
+
BOOL enableLongTask = [RCTConvert BOOL:context[@"enableLongTask"]];
|
|
213
|
+
double configuredLongTaskThresholdMs = [context.allKeys containsObject:@"longTaskThresholdMs"]
|
|
214
|
+
? [RCTConvert double:context[@"longTaskThresholdMs"]]
|
|
215
|
+
: 100;
|
|
216
|
+
double longTaskThresholdMs = enableLongTask ? configuredLongTaskThresholdMs : 0;
|
|
180
217
|
if ([context.allKeys containsObject:@"globalContext"]) {
|
|
181
218
|
rumConfig.globalContext = [RCTConvert NSDictionary:context[@"globalContext"]];
|
|
182
219
|
}
|
|
@@ -201,9 +238,47 @@ RCT_REMAP_METHOD(addResource,
|
|
|
201
238
|
};
|
|
202
239
|
#endif
|
|
203
240
|
[[FTMobileAgent sharedInstance] startRumWithConfigOptions:rumConfig];
|
|
241
|
+
[self getOrCreateLongTaskMonitor];
|
|
242
|
+
[self.jsLongTaskMonitor setThresholdMilliseconds:longTaskThresholdMs];
|
|
243
|
+
if (longTaskThresholdMs > 0 && [self applicationIsActive]) {
|
|
244
|
+
[self.jsLongTaskMonitor start];
|
|
245
|
+
} else {
|
|
246
|
+
[self.jsLongTaskMonitor stop];
|
|
247
|
+
}
|
|
204
248
|
resolve(nil);
|
|
205
249
|
}
|
|
206
250
|
|
|
251
|
+
- (void)stopLongTaskTracking:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
252
|
+
if (self.jsLongTaskMonitor == nil) {
|
|
253
|
+
resolve(nil);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
[self.jsLongTaskMonitor disableWithCompletion:^{
|
|
257
|
+
resolve(nil);
|
|
258
|
+
}];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
|
262
|
+
[self.jsLongTaskMonitor start];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
- (void)applicationWillResignActive:(NSNotification *)notification {
|
|
266
|
+
[self.jsLongTaskMonitor stop];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
- (BOOL)applicationIsActive {
|
|
270
|
+
__block BOOL active = NO;
|
|
271
|
+
dispatch_block_t readState = ^{
|
|
272
|
+
active = UIApplication.sharedApplication.applicationState == UIApplicationStateActive;
|
|
273
|
+
};
|
|
274
|
+
if (NSThread.isMainThread) {
|
|
275
|
+
readState();
|
|
276
|
+
} else {
|
|
277
|
+
dispatch_sync(dispatch_get_main_queue(), readState);
|
|
278
|
+
}
|
|
279
|
+
return active;
|
|
280
|
+
}
|
|
281
|
+
|
|
207
282
|
- (void)startAction:(NSString *)actionName actionType:(NSString *)actionType property:(NSDictionary *)property resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
208
283
|
[[FTExternalDataManager sharedManager] startAction:actionName actionType:actionType property:property];
|
|
209
284
|
resolve(nil);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#import "FTReactNativeResource.h"
|
|
2
|
+
#import <TrueWatchSDK/FTExternalDataManager.h>
|
|
3
|
+
#import <TrueWatchSDK/FTResourceMetricsModel.h>
|
|
4
|
+
#import <TrueWatchSDK/FTResourceContentModel.h>
|
|
5
|
+
#import <React/RCTConvert.h>
|
|
6
|
+
|
|
7
|
+
static NSString * const FTReactNativeWebSocketErrorDomain = @"com.truewatch.react-native.websocket";
|
|
8
|
+
|
|
9
|
+
void FTAddReactNativeResource(NSString *key, NSDictionary *content, NSDictionary *metrics) {
|
|
10
|
+
if (key.length==0 || content.allKeys.count == 0) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
FTResourceMetricsModel *metricsModel = nil;
|
|
14
|
+
if (metrics.allKeys.count>0) {
|
|
15
|
+
metricsModel = [[FTResourceMetricsModel alloc]init];
|
|
16
|
+
metricsModel.duration = [RCTConvert NSNumber:metrics[@"duration"]];
|
|
17
|
+
metricsModel.resource_dns = [RCTConvert NSNumber:metrics[@"resource_dns"]];
|
|
18
|
+
metricsModel.resource_tcp = [RCTConvert NSNumber:metrics[@"resource_tcp"]];
|
|
19
|
+
metricsModel.resource_ssl = [RCTConvert NSNumber:metrics[@"resource_ssl"]];
|
|
20
|
+
metricsModel.resource_ttfb = [RCTConvert NSNumber:metrics[@"resource_ttfb"]];
|
|
21
|
+
metricsModel.resource_trans = [RCTConvert NSNumber:metrics[@"resource_trans"]];
|
|
22
|
+
metricsModel.resource_first_byte = [RCTConvert NSNumber:metrics[@"resource_first_byte"]];
|
|
23
|
+
if ([content[@"webSocketHandshake"] boolValue] && [metrics[@"resource_http_protocol"] isKindOfClass:NSString.class]) {
|
|
24
|
+
metricsModel.resourceHttpProtocol = metrics[@"resource_http_protocol"];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
FTResourceContentModel *contentModel = [[FTResourceContentModel alloc]init];
|
|
28
|
+
contentModel.url = [RCTConvert NSURL:content[@"url"]];
|
|
29
|
+
contentModel.httpMethod = [RCTConvert NSString:content[@"httpMethod"]];
|
|
30
|
+
contentModel.requestHeader = [RCTConvert NSDictionary:content[@"requestHeader"]];
|
|
31
|
+
contentModel.responseHeader = [RCTConvert NSDictionary:content[@"responseHeader"]];
|
|
32
|
+
contentModel.responseBody = [RCTConvert NSString:content[@"responseBody"]];
|
|
33
|
+
contentModel.httpStatusCode = [RCTConvert int:content[@"resourceStatus"]];
|
|
34
|
+
if ([content.allKeys containsObject:@"errorMessage"]) {
|
|
35
|
+
NSString *errorMessage = [RCTConvert NSString:content[@"errorMessage"]];
|
|
36
|
+
if (errorMessage.length > 0) {
|
|
37
|
+
NSInteger errorCode = -1;
|
|
38
|
+
if ([content.allKeys containsObject:@"errorCode"]) {
|
|
39
|
+
errorCode = [RCTConvert NSInteger:content[@"errorCode"]];
|
|
40
|
+
}
|
|
41
|
+
contentModel.errorMessage = errorMessage;
|
|
42
|
+
contentModel.error = [NSError errorWithDomain:FTReactNativeWebSocketErrorDomain
|
|
43
|
+
code:errorCode
|
|
44
|
+
userInfo:@{NSLocalizedDescriptionKey: errorMessage}];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if ([content.allKeys containsObject:@"resourceType"]) {
|
|
48
|
+
contentModel.resourceType = [RCTConvert NSString:content[@"resourceType"]];
|
|
49
|
+
}
|
|
50
|
+
if ([content.allKeys containsObject:@"webSocketHandshake"]) {
|
|
51
|
+
contentModel.webSocketHandshake = [RCTConvert BOOL:content[@"webSocketHandshake"]];
|
|
52
|
+
}
|
|
53
|
+
if ([content.allKeys containsObject:@"webSocketHandshakeState"]) {
|
|
54
|
+
contentModel.webSocketHandshakeState = [RCTConvert NSString:content[@"webSocketHandshakeState"]];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
[[FTExternalDataManager sharedManager] addResourceWithKey:key metrics:metricsModel content:contentModel];
|
|
58
|
+
}
|