@truewatchtech/react-native-mobile 0.4.2 → 0.4.3
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/android/build.gradle +10 -3
- package/android/src/main/java/com/ft/sdk/reactnative/FTLogImpl.java +10 -2
- package/android/src/main/java/com/ft/sdk/reactnative/FTMobileImpl.java +45 -10
- package/android/src/main/java/com/ft/sdk/reactnative/FTRUMImpl.java +131 -6
- package/android/src/main/java/com/ft/sdk/reactnative/FTTraceImpl.java +10 -3
- package/android/src/main/java/com/ft/sdk/reactnative/JsLongTaskMonitor.java +281 -0
- package/android/src/main/java/com/ft/sdk/reactnative/ViewTiming.java +17 -0
- package/android/src/main/java/com/ft/sdk/reactnative/VisitorIdConfigMapper.java +50 -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 +14 -5
- package/android/src/newarch/java/com/ft/sdk/reactnative/FTRUMModule.java +44 -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 +7 -2
- package/android/src/oldarch/java/com/ft/sdk/reactnative/FTRUMModule.java +39 -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/FTMobileImplTest.java +53 -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 +54 -18
- package/ios/FTReactNativeLog.mm +9 -5
- package/ios/FTReactNativeRUM.mm +196 -31
- package/ios/FTReactNativeResource.h +4 -0
- package/ios/FTReactNativeResource.m +64 -0
- package/ios/FTReactNativeTrace.mm +59 -14
- package/ios/FTReactNativeUtils.m +10 -15
- package/ios/FTReactNativeViewTiming.h +18 -0
- 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 +20 -0
- package/ios/FTWebSocketResourceData.m +106 -0
- package/lib/commonjs/ft_logger.js +3 -1
- package/lib/commonjs/ft_logger.js.map +1 -1
- package/lib/commonjs/ft_mobile_agent.js +47 -4
- package/lib/commonjs/ft_mobile_agent.js.map +1 -1
- package/lib/commonjs/ft_rum.js +65 -5
- package/lib/commonjs/ft_rum.js.map +1 -1
- package/lib/commonjs/ft_tracing.js +14 -4
- package/lib/commonjs/ft_tracing.js.map +1 -1
- package/lib/commonjs/index.js +21 -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/FTRumViewTracking.js +147 -0
- package/lib/commonjs/rum/FTRumViewTracking.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 +73 -0
- package/lib/commonjs/rum/FTRumWebSocketTiming.js.map +1 -0
- package/lib/commonjs/rum/FTRumWebSocketTracking.js +506 -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/commonjs/visitorIdConfig.js +37 -0
- package/lib/commonjs/visitorIdConfig.js.map +1 -0
- package/lib/module/ft_logger.js +3 -1
- package/lib/module/ft_logger.js.map +1 -1
- package/lib/module/ft_mobile_agent.js +47 -4
- package/lib/module/ft_mobile_agent.js.map +1 -1
- package/lib/module/ft_rum.js +65 -5
- package/lib/module/ft_rum.js.map +1 -1
- package/lib/module/ft_tracing.js +14 -2
- package/lib/module/ft_tracing.js.map +1 -1
- package/lib/module/index.js +4 -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/FTRumViewTracking.js +140 -0
- package/lib/module/rum/FTRumViewTracking.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 +67 -0
- package/lib/module/rum/FTRumWebSocketTiming.js.map +1 -0
- package/lib/module/rum/FTRumWebSocketTracking.js +499 -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/module/visitorIdConfig.js +31 -0
- package/lib/module/visitorIdConfig.js.map +1 -0
- package/lib/typescript/ft_logger.d.ts +11 -1
- package/lib/typescript/ft_mobile_agent.d.ts +28 -0
- package/lib/typescript/ft_rum.d.ts +42 -5
- package/lib/typescript/ft_tracing.d.ts +9 -1
- package/lib/typescript/index.d.ts +5 -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/FTRumViewTracking.d.ts +32 -0
- package/lib/typescript/rum/FTRumWebSocketNative.d.ts +22 -0
- package/lib/typescript/rum/FTRumWebSocketTiming.d.ts +7 -0
- package/lib/typescript/rum/FTRumWebSocketTracking.d.ts +54 -0
- package/lib/typescript/rum/longTasksUtils.d.ts +8 -0
- package/lib/typescript/specs/NativeFTMobileReactNative.d.ts +15 -0
- package/lib/typescript/specs/NativeFTReactNativeRUM.d.ts +11 -1
- 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/lib/typescript/visitorIdConfig.d.ts +11 -0
- package/package.json +6 -4
- package/src/ft_logger.tsx +11 -1
- package/src/ft_mobile_agent.tsx +79 -7
- package/src/ft_rum.tsx +111 -7
- package/src/ft_tracing.tsx +24 -2
- package/src/index.tsx +8 -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/FTRumViewTracking.ts +179 -0
- package/src/rum/FTRumWebSocketNative.ts +107 -0
- package/src/rum/FTRumWebSocketTiming.ts +82 -0
- package/src/rum/FTRumWebSocketTracking.ts +722 -0
- package/src/rum/longTasksUtils.ts +17 -0
- package/src/specs/NativeFTMobileReactNative.ts +18 -0
- package/src/specs/NativeFTReactNativeRUM.ts +19 -1
- package/src/specs/NativeFTReactNativeTrace.ts +12 -0
- package/src/specs/NativeFTReactNativeWebSocket.ts +33 -0
- package/src/version.ts +1 -1
- package/src/visitorIdConfig.ts +123 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
/* global WebSocket */
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
import NativeFTReactNativeTrace from '../specs/NativeFTReactNativeTrace';
|
|
4
|
+
import { startWebSocketTiming } from './FTRumWebSocketTiming';
|
|
5
|
+
import type { WebSocketTimingMetrics } from './FTRumWebSocketTiming';
|
|
6
|
+
import {
|
|
7
|
+
clearNativeWebSocketSessions,
|
|
8
|
+
startNativeWebSocketSession,
|
|
9
|
+
webSocketNativeID,
|
|
10
|
+
} from './FTRumWebSocketNative';
|
|
11
|
+
import type {
|
|
12
|
+
NativeWebSocketSession,
|
|
13
|
+
NativeWebSocketResource,
|
|
14
|
+
} from './FTRumWebSocketNative';
|
|
15
|
+
|
|
16
|
+
type WebSocketConstructor = typeof WebSocket;
|
|
17
|
+
type WebSocketHandshakeState = 'success' | 'rejected' | 'failed';
|
|
18
|
+
|
|
19
|
+
interface ReactNativeWebSocketOptions {
|
|
20
|
+
headers?: Record<string, unknown>;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface FTRUMWebSocketResource {
|
|
25
|
+
url: string;
|
|
26
|
+
httpMethod: string;
|
|
27
|
+
requestHeader: Record<string, unknown>;
|
|
28
|
+
resourceStatus: number;
|
|
29
|
+
responseHeader?: Record<string, string>;
|
|
30
|
+
resourceType: 'websocket';
|
|
31
|
+
webSocketHandshake: true;
|
|
32
|
+
webSocketHandshakeState: WebSocketHandshakeState;
|
|
33
|
+
errorMessage?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface WebSocketHandshakeError {
|
|
37
|
+
message?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface FTRumWebSocketResourceReporter {
|
|
41
|
+
startResource(key: string): Promise<void>;
|
|
42
|
+
stopResource(key: string): Promise<void>;
|
|
43
|
+
addResource(
|
|
44
|
+
key: string,
|
|
45
|
+
resource: FTRUMWebSocketResource,
|
|
46
|
+
metrics?: WebSocketTimingMetrics
|
|
47
|
+
): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type RuntimeGlobal = typeof globalThis & {
|
|
51
|
+
WebSocket: WebSocketConstructor;
|
|
52
|
+
__DEV__?: boolean;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const REACT_NATIVE_DEV_WEBSOCKET =
|
|
56
|
+
/^wss?:\/\/(?:(?:10|172|192)\.[0-9]+\.[0-9]+\.[0-9]+|localhost|127\.0\.0\.1|\[::1\]):808[0-9]\/(?:hot|symbolicate|message|inspector|status|assets|logs|debugger-proxy)(?:[/?#].*)?$/i;
|
|
57
|
+
|
|
58
|
+
// SocketRocket embeds the actual response status in this description, which
|
|
59
|
+
// RCTWebSocketModule forwards to JS. Do not match proxy errors or bare numbers.
|
|
60
|
+
const SOCKET_ROCKET_HTTP_ERROR =
|
|
61
|
+
/^Received\s+bad\s+response\s+code\s+from\s+server\s*:\s*([1-9]\d{2})\s*\.?$/i;
|
|
62
|
+
const MAX_HTTP_ERROR_PARSE_LENGTH = 512;
|
|
63
|
+
|
|
64
|
+
export class FTRumWebSocketTracking {
|
|
65
|
+
private static enabled = false;
|
|
66
|
+
private static installed = false;
|
|
67
|
+
private static originalWebSocket: WebSocketConstructor | null = null;
|
|
68
|
+
private static instrumentedWebSocket: WebSocketConstructor | null = null;
|
|
69
|
+
private static resourceReporter: FTRumWebSocketResourceReporter | null = null;
|
|
70
|
+
private static nativeAutoTraceEnabled = false;
|
|
71
|
+
private static warnedFailures = new Set<string>();
|
|
72
|
+
private static lifecycleVersion = 0;
|
|
73
|
+
private static nativeSession: NativeWebSocketSession | undefined;
|
|
74
|
+
private static pendingHandshakes = new Set<() => void>();
|
|
75
|
+
|
|
76
|
+
/** Identifies configuration and reporting work started before SDK shutdown. */
|
|
77
|
+
static getLifecycleVersion(): number {
|
|
78
|
+
return FTRumWebSocketTracking.lifecycleVersion;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static shutDown(): void {
|
|
82
|
+
FTRumWebSocketTracking.lifecycleVersion += 1;
|
|
83
|
+
clearNativeWebSocketSessions();
|
|
84
|
+
FTRumWebSocketTracking.setNativeAutoTraceEnabled(false);
|
|
85
|
+
FTRumWebSocketTracking.stopTracking();
|
|
86
|
+
FTRumWebSocketTracking.pendingHandshakes.forEach((removeListeners) =>
|
|
87
|
+
removeListeners()
|
|
88
|
+
);
|
|
89
|
+
FTRumWebSocketTracking.pendingHandshakes.clear();
|
|
90
|
+
FTRumWebSocketTracking.warnedFailures.clear();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static setNativeAutoTraceEnabled(enabled: boolean): void {
|
|
94
|
+
FTRumWebSocketTracking.nativeAutoTraceEnabled = enabled;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private static stopNativeSession(): void {
|
|
98
|
+
FTRumWebSocketTracking.nativeSession?.stop();
|
|
99
|
+
FTRumWebSocketTracking.nativeSession = undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static startTracking(resourceReporter: FTRumWebSocketResourceReporter): void {
|
|
103
|
+
if (Platform.OS !== 'ios') {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
FTRumWebSocketTracking.enabled = true;
|
|
108
|
+
FTRumWebSocketTracking.resourceReporter = resourceReporter;
|
|
109
|
+
const runtimeGlobal = globalThis as RuntimeGlobal;
|
|
110
|
+
const originalWebSocket = runtimeGlobal.WebSocket;
|
|
111
|
+
if (originalWebSocket === FTRumWebSocketTracking.instrumentedWebSocket) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (typeof originalWebSocket !== 'function') {
|
|
115
|
+
FTRumWebSocketTracking.stopNativeSession();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!FTRumWebSocketTracking.nativeSession) {
|
|
120
|
+
FTRumWebSocketTracking.nativeSession = startNativeWebSocketSession(() =>
|
|
121
|
+
FTRumWebSocketTracking.createResourceKey()
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const InstrumentedWebSocket = function (
|
|
126
|
+
this: WebSocket,
|
|
127
|
+
...args: unknown[]
|
|
128
|
+
): WebSocket {
|
|
129
|
+
if (!new.target) {
|
|
130
|
+
return Reflect.apply(originalWebSocket, this, args);
|
|
131
|
+
}
|
|
132
|
+
// A constructor retained before stop/reinitialization must stay passive.
|
|
133
|
+
if (
|
|
134
|
+
FTRumWebSocketTracking.instrumentedWebSocket !== InstrumentedWebSocket
|
|
135
|
+
) {
|
|
136
|
+
return Reflect.construct(originalWebSocket, args, new.target);
|
|
137
|
+
}
|
|
138
|
+
return FTRumWebSocketTracking.createWebSocket(
|
|
139
|
+
originalWebSocket,
|
|
140
|
+
args,
|
|
141
|
+
new.target as unknown as WebSocketConstructor
|
|
142
|
+
);
|
|
143
|
+
} as unknown as WebSocketConstructor;
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
InstrumentedWebSocket.prototype = originalWebSocket.prototype;
|
|
147
|
+
Object.setPrototypeOf(InstrumentedWebSocket, originalWebSocket);
|
|
148
|
+
runtimeGlobal.WebSocket = InstrumentedWebSocket;
|
|
149
|
+
if (runtimeGlobal.WebSocket !== InstrumentedWebSocket) {
|
|
150
|
+
FTRumWebSocketTracking.warnOnce(
|
|
151
|
+
'Unable to install WebSocket tracking.'
|
|
152
|
+
);
|
|
153
|
+
FTRumWebSocketTracking.stopNativeSession();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
FTRumWebSocketTracking.originalWebSocket = originalWebSocket;
|
|
157
|
+
FTRumWebSocketTracking.instrumentedWebSocket = InstrumentedWebSocket;
|
|
158
|
+
FTRumWebSocketTracking.installed = true;
|
|
159
|
+
} catch {
|
|
160
|
+
FTRumWebSocketTracking.stopNativeSession();
|
|
161
|
+
FTRumWebSocketTracking.warnOnce('Unable to install WebSocket tracking.');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
static stopTracking(): void {
|
|
166
|
+
FTRumWebSocketTracking.stopNativeSession();
|
|
167
|
+
FTRumWebSocketTracking.enabled = false;
|
|
168
|
+
FTRumWebSocketTracking.resourceReporter = null;
|
|
169
|
+
if (!FTRumWebSocketTracking.installed) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const runtimeGlobal = globalThis as RuntimeGlobal;
|
|
174
|
+
if (
|
|
175
|
+
runtimeGlobal.WebSocket ===
|
|
176
|
+
FTRumWebSocketTracking.instrumentedWebSocket &&
|
|
177
|
+
FTRumWebSocketTracking.originalWebSocket
|
|
178
|
+
) {
|
|
179
|
+
try {
|
|
180
|
+
runtimeGlobal.WebSocket = FTRumWebSocketTracking.originalWebSocket;
|
|
181
|
+
if (
|
|
182
|
+
runtimeGlobal.WebSocket !== FTRumWebSocketTracking.originalWebSocket
|
|
183
|
+
) {
|
|
184
|
+
FTRumWebSocketTracking.warnOnce(
|
|
185
|
+
'Unable to restore WebSocket constructor.'
|
|
186
|
+
);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
FTRumWebSocketTracking.installed = false;
|
|
190
|
+
FTRumWebSocketTracking.originalWebSocket = null;
|
|
191
|
+
FTRumWebSocketTracking.instrumentedWebSocket = null;
|
|
192
|
+
} catch {
|
|
193
|
+
// The disabled wrapper remains a passthrough if another library froze it.
|
|
194
|
+
FTRumWebSocketTracking.warnOnce(
|
|
195
|
+
'Unable to restore WebSocket constructor.'
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private static createWebSocket(
|
|
202
|
+
OriginalWebSocket: WebSocketConstructor,
|
|
203
|
+
originalArguments: unknown[],
|
|
204
|
+
newTarget: WebSocketConstructor
|
|
205
|
+
): WebSocket {
|
|
206
|
+
if (!FTRumWebSocketTracking.enabled) {
|
|
207
|
+
return Reflect.construct(OriginalWebSocket, originalArguments, newTarget);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const resourceReporter = FTRumWebSocketTracking.resourceReporter;
|
|
211
|
+
if (!resourceReporter) {
|
|
212
|
+
return Reflect.construct(OriginalWebSocket, originalArguments, newTarget);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Leave coercion and validation of nonstandard arguments to the constructor.
|
|
216
|
+
const url = originalArguments[0];
|
|
217
|
+
if (typeof url !== 'string') {
|
|
218
|
+
return Reflect.construct(OriginalWebSocket, originalArguments, newTarget);
|
|
219
|
+
}
|
|
220
|
+
const runtimeGlobal = globalThis as RuntimeGlobal;
|
|
221
|
+
if (
|
|
222
|
+
runtimeGlobal.__DEV__ === true &&
|
|
223
|
+
REACT_NATIVE_DEV_WEBSOCKET.test(url)
|
|
224
|
+
) {
|
|
225
|
+
return Reflect.construct(OriginalWebSocket, originalArguments, newTarget);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const options = originalArguments[2];
|
|
229
|
+
if (
|
|
230
|
+
options !== undefined &&
|
|
231
|
+
(!options || typeof options !== 'object' || Array.isArray(options))
|
|
232
|
+
) {
|
|
233
|
+
return Reflect.construct(OriginalWebSocket, originalArguments, newTarget);
|
|
234
|
+
}
|
|
235
|
+
const lifecycleVersion = FTRumWebSocketTracking.lifecycleVersion;
|
|
236
|
+
let resourceKey: string;
|
|
237
|
+
let traceKey: string | undefined;
|
|
238
|
+
let argumentsWithHeaders: unknown[];
|
|
239
|
+
let requestHeaders: Record<string, unknown>;
|
|
240
|
+
try {
|
|
241
|
+
resourceKey = FTRumWebSocketTracking.createResourceKey();
|
|
242
|
+
// Generation may cache correlation before the bridge returns or throws.
|
|
243
|
+
if (FTRumWebSocketTracking.nativeAutoTraceEnabled) traceKey = resourceKey;
|
|
244
|
+
const traceHeaders = traceKey
|
|
245
|
+
? FTRumWebSocketTracking.getTraceHeaders(url, traceKey)
|
|
246
|
+
: {};
|
|
247
|
+
({ argumentsWithHeaders, requestHeaders } =
|
|
248
|
+
FTRumWebSocketTracking.mergeTraceHeaders(
|
|
249
|
+
originalArguments,
|
|
250
|
+
traceHeaders
|
|
251
|
+
));
|
|
252
|
+
} catch {
|
|
253
|
+
FTRumWebSocketTracking.cancelTrace(traceKey);
|
|
254
|
+
FTRumWebSocketTracking.warnOnce('Unable to prepare WebSocket tracking.');
|
|
255
|
+
return Reflect.construct(OriginalWebSocket, originalArguments, newTarget);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const stopTiming = startWebSocketTiming();
|
|
259
|
+
let socket: WebSocket;
|
|
260
|
+
let completed = false;
|
|
261
|
+
let cancelled = false;
|
|
262
|
+
let pendingCloseError: WebSocketHandshakeError | undefined;
|
|
263
|
+
let startPromise: Promise<boolean> | undefined;
|
|
264
|
+
// Decide once before reporting. Never switch routes after an ambiguous
|
|
265
|
+
// native result: start/add may already have reached the SDK.
|
|
266
|
+
const nativeSession = FTRumWebSocketTracking.nativeSession;
|
|
267
|
+
let nativeResourceStarted = false;
|
|
268
|
+
const removeListeners = (keepFailureClose = false) => {
|
|
269
|
+
completed = true;
|
|
270
|
+
if (!keepFailureClose) {
|
|
271
|
+
pendingCloseError = undefined;
|
|
272
|
+
FTRumWebSocketTracking.pendingHandshakes.delete(cancelHandshake);
|
|
273
|
+
}
|
|
274
|
+
// Cleanup failures must neither interrupt business dispatch nor skip the
|
|
275
|
+
// other listeners. Completed guards also protect callbacks already queued.
|
|
276
|
+
const removers = keepFailureClose
|
|
277
|
+
? listenerRemovers.slice(0, 2)
|
|
278
|
+
: listenerRemovers;
|
|
279
|
+
for (const remove of removers) {
|
|
280
|
+
try {
|
|
281
|
+
remove();
|
|
282
|
+
} catch {
|
|
283
|
+
FTRumWebSocketTracking.warnOnce(
|
|
284
|
+
'Unable to remove WebSocket listener.'
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
const releaseNativeResource = () => {
|
|
290
|
+
if (nativeResourceStarted) {
|
|
291
|
+
nativeResourceStarted = false;
|
|
292
|
+
nativeSession?.release(resourceKey);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
const cancelHandshake = () => {
|
|
296
|
+
cancelled = true;
|
|
297
|
+
removeListeners();
|
|
298
|
+
releaseNativeResource();
|
|
299
|
+
};
|
|
300
|
+
const beginResource = () => {
|
|
301
|
+
if (!startPromise) {
|
|
302
|
+
nativeResourceStarted = nativeSession !== undefined;
|
|
303
|
+
const start = () =>
|
|
304
|
+
nativeSession
|
|
305
|
+
? nativeSession.startResource(
|
|
306
|
+
resourceKey,
|
|
307
|
+
webSocketNativeID(socket),
|
|
308
|
+
url
|
|
309
|
+
)
|
|
310
|
+
: resourceReporter.startResource(resourceKey);
|
|
311
|
+
startPromise = FTRumWebSocketTracking.startResource(start).catch(() => {
|
|
312
|
+
cancelHandshake();
|
|
313
|
+
if (lifecycleVersion === FTRumWebSocketTracking.lifecycleVersion) {
|
|
314
|
+
FTRumWebSocketTracking.warnOnce(
|
|
315
|
+
'Unable to start WebSocket resource.'
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
return false;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return startPromise;
|
|
322
|
+
};
|
|
323
|
+
const complete = (
|
|
324
|
+
resourceStatus: number,
|
|
325
|
+
state: WebSocketHandshakeState,
|
|
326
|
+
getError?: () => WebSocketHandshakeError,
|
|
327
|
+
event: 'open' | 'error' | 'close' = 'error',
|
|
328
|
+
supplementFailureClose = false
|
|
329
|
+
) => {
|
|
330
|
+
if (completed) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
completed = true;
|
|
334
|
+
const timing = stopTiming();
|
|
335
|
+
// Freeze the observed boundary before inspecting or parsing error data.
|
|
336
|
+
const error = getError?.();
|
|
337
|
+
if (
|
|
338
|
+
cancelled ||
|
|
339
|
+
lifecycleVersion !== FTRumWebSocketTracking.lifecycleVersion
|
|
340
|
+
) {
|
|
341
|
+
removeListeners();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
let errorReady: Promise<void> | undefined;
|
|
345
|
+
if (supplementFailureClose && error && !error.message) {
|
|
346
|
+
// RN's built-in EventTarget emits error without message, immediately
|
|
347
|
+
// followed by close(1006, nativeErrorMessage) in the same native-event
|
|
348
|
+
// callback. This also covers RN 0.79's opt-in implementation without
|
|
349
|
+
// guessing behavior from a version number. Freeze time and stop now;
|
|
350
|
+
// allow only that synchronous close to supplement the error text.
|
|
351
|
+
pendingCloseError = error;
|
|
352
|
+
removeListeners(true);
|
|
353
|
+
errorReady = Promise.resolve().then(() => removeListeners());
|
|
354
|
+
} else {
|
|
355
|
+
removeListeners();
|
|
356
|
+
}
|
|
357
|
+
const started = beginResource();
|
|
358
|
+
if (nativeSession) {
|
|
359
|
+
// Invoke stop at the event boundary, without waiting for the start
|
|
360
|
+
// promise or business callbacks. The native module processes in order.
|
|
361
|
+
void FTRumWebSocketTracking.completeNativeResource(
|
|
362
|
+
nativeSession,
|
|
363
|
+
lifecycleVersion,
|
|
364
|
+
resourceKey,
|
|
365
|
+
started,
|
|
366
|
+
{
|
|
367
|
+
url,
|
|
368
|
+
httpMethod: 'GET',
|
|
369
|
+
requestHeader: requestHeaders,
|
|
370
|
+
webSocketEvent: event,
|
|
371
|
+
},
|
|
372
|
+
timing,
|
|
373
|
+
error,
|
|
374
|
+
errorReady
|
|
375
|
+
);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
void FTRumWebSocketTracking.completeResource(
|
|
379
|
+
resourceReporter,
|
|
380
|
+
lifecycleVersion,
|
|
381
|
+
resourceKey,
|
|
382
|
+
started,
|
|
383
|
+
url,
|
|
384
|
+
requestHeaders,
|
|
385
|
+
resourceStatus,
|
|
386
|
+
state,
|
|
387
|
+
timing,
|
|
388
|
+
error,
|
|
389
|
+
errorReady
|
|
390
|
+
);
|
|
391
|
+
};
|
|
392
|
+
const onOpen = () => complete(0, 'success', undefined, 'open');
|
|
393
|
+
const onError = (event: Event) => {
|
|
394
|
+
complete(
|
|
395
|
+
0,
|
|
396
|
+
'failed',
|
|
397
|
+
() => FTRumWebSocketTracking.handshakeErrorFromEvent(event),
|
|
398
|
+
'error',
|
|
399
|
+
true
|
|
400
|
+
);
|
|
401
|
+
};
|
|
402
|
+
const onClose = (event: Event) => {
|
|
403
|
+
if (pendingCloseError) {
|
|
404
|
+
const error = pendingCloseError;
|
|
405
|
+
const message = FTRumWebSocketTracking.failureMessageFromClose(event);
|
|
406
|
+
// Accessors can reenter shutdown. Do not restore a cancelled error.
|
|
407
|
+
if (pendingCloseError === error) error.message = message;
|
|
408
|
+
removeListeners();
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
// A standalone close is only a terminal signal. Never interpret its
|
|
412
|
+
// business code/reason as a network error or as an HTTP response.
|
|
413
|
+
complete(0, 'failed', undefined, 'close');
|
|
414
|
+
};
|
|
415
|
+
const listenerRemovers = [
|
|
416
|
+
() => socket?.removeEventListener('open', onOpen),
|
|
417
|
+
() => socket?.removeEventListener('error', onError),
|
|
418
|
+
() => socket?.removeEventListener('close', onClose),
|
|
419
|
+
];
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
socket = Reflect.construct(
|
|
423
|
+
OriginalWebSocket,
|
|
424
|
+
argumentsWithHeaders,
|
|
425
|
+
newTarget
|
|
426
|
+
);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
complete(0, 'failed', () =>
|
|
429
|
+
FTRumWebSocketTracking.toHandshakeError(error)
|
|
430
|
+
);
|
|
431
|
+
throw error;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
socket.addEventListener('open', onOpen);
|
|
436
|
+
socket.addEventListener('error', onError);
|
|
437
|
+
socket.addEventListener('close', onClose);
|
|
438
|
+
if (!completed) {
|
|
439
|
+
FTRumWebSocketTracking.pendingHandshakes.add(cancelHandshake);
|
|
440
|
+
// Do not leave a native Resource behind if listener installation fails.
|
|
441
|
+
beginResource();
|
|
442
|
+
}
|
|
443
|
+
} catch {
|
|
444
|
+
cancelHandshake();
|
|
445
|
+
// A reentrant terminal may already have started normal Resource reporting.
|
|
446
|
+
if (!startPromise) FTRumWebSocketTracking.cancelTrace(traceKey);
|
|
447
|
+
FTRumWebSocketTracking.warnOnce('Unable to observe WebSocket events.');
|
|
448
|
+
}
|
|
449
|
+
return socket;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private static createResourceKey(): string {
|
|
453
|
+
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, (token) => {
|
|
454
|
+
const randomNibble = Math.floor(Math.random() * 16);
|
|
455
|
+
const uuidNibble = token === 'x' ? randomNibble : (randomNibble % 4) + 8;
|
|
456
|
+
return uuidNibble.toString(16);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private static cancelTrace(key: string | undefined): void {
|
|
461
|
+
if (!key) return;
|
|
462
|
+
try {
|
|
463
|
+
// Older native binaries do not expose this optional cancellation bridge.
|
|
464
|
+
if (
|
|
465
|
+
typeof NativeFTReactNativeTrace?.cancelWebSocketTrace === 'function'
|
|
466
|
+
) {
|
|
467
|
+
void Promise.resolve(
|
|
468
|
+
NativeFTReactNativeTrace.cancelWebSocketTrace(key)
|
|
469
|
+
).catch(() => {});
|
|
470
|
+
}
|
|
471
|
+
} catch {
|
|
472
|
+
// Cleanup must never interrupt the business constructor or its exception.
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private static getTraceHeaders(
|
|
477
|
+
url: string,
|
|
478
|
+
resourceKey: string
|
|
479
|
+
): Record<string, unknown> {
|
|
480
|
+
try {
|
|
481
|
+
const headers = NativeFTReactNativeTrace?.getTraceHeaderFieldsSync(
|
|
482
|
+
url,
|
|
483
|
+
resourceKey
|
|
484
|
+
);
|
|
485
|
+
return FTRumWebSocketTracking.toHeaderRecord(headers);
|
|
486
|
+
} catch {
|
|
487
|
+
FTRumWebSocketTracking.warnOnce(
|
|
488
|
+
'Unable to inject WebSocket trace headers.'
|
|
489
|
+
);
|
|
490
|
+
return {};
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private static mergeTraceHeaders(
|
|
495
|
+
originalArguments: unknown[],
|
|
496
|
+
traceHeaders: Record<string, unknown>
|
|
497
|
+
): {
|
|
498
|
+
argumentsWithHeaders: unknown[];
|
|
499
|
+
requestHeaders: Record<string, unknown>;
|
|
500
|
+
} {
|
|
501
|
+
const options = FTRumWebSocketTracking.toOptions(originalArguments[2]);
|
|
502
|
+
const requestHeaders = {
|
|
503
|
+
...FTRumWebSocketTracking.toHeaderRecord(options.headers),
|
|
504
|
+
};
|
|
505
|
+
const traceHeaderEntries = Object.entries(traceHeaders);
|
|
506
|
+
|
|
507
|
+
if (traceHeaderEntries.length === 0) {
|
|
508
|
+
return {
|
|
509
|
+
argumentsWithHeaders: originalArguments,
|
|
510
|
+
requestHeaders,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const traceHeaderNames = new Set(
|
|
515
|
+
traceHeaderEntries.map(([key]) => key.toLowerCase())
|
|
516
|
+
);
|
|
517
|
+
Object.keys(requestHeaders).forEach((key) => {
|
|
518
|
+
if (traceHeaderNames.has(key.toLowerCase())) {
|
|
519
|
+
delete requestHeaders[key];
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
traceHeaderEntries.forEach(([key, value]) => {
|
|
523
|
+
requestHeaders[key] = value;
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
const argumentsWithHeaders = [...originalArguments];
|
|
527
|
+
// Read a headers accessor only once. Spreading options here would read it
|
|
528
|
+
// again before replacing it with the already captured header values.
|
|
529
|
+
const optionDescriptors = Object.getOwnPropertyDescriptors(options);
|
|
530
|
+
optionDescriptors.headers = {
|
|
531
|
+
configurable: true,
|
|
532
|
+
enumerable: true,
|
|
533
|
+
writable: true,
|
|
534
|
+
value: requestHeaders,
|
|
535
|
+
};
|
|
536
|
+
argumentsWithHeaders[2] = Object.defineProperties({}, optionDescriptors);
|
|
537
|
+
return { argumentsWithHeaders, requestHeaders };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
private static toOptions(value: unknown): ReactNativeWebSocketOptions {
|
|
541
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
542
|
+
return value as ReactNativeWebSocketOptions;
|
|
543
|
+
}
|
|
544
|
+
return {};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private static toHeaderRecord(value: unknown): Record<string, unknown> {
|
|
548
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
549
|
+
return value as Record<string, unknown>;
|
|
550
|
+
}
|
|
551
|
+
return {};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private static eventString(
|
|
555
|
+
event: unknown,
|
|
556
|
+
property: string
|
|
557
|
+
): string | undefined {
|
|
558
|
+
if (!event || typeof event !== 'object') {
|
|
559
|
+
return undefined;
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
const value = (event as Record<string, unknown>)[property];
|
|
563
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
564
|
+
} catch {
|
|
565
|
+
return undefined;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private static handshakeErrorFromEvent(
|
|
570
|
+
event: Event
|
|
571
|
+
): WebSocketHandshakeError {
|
|
572
|
+
return { message: FTRumWebSocketTracking.eventString(event, 'message') };
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private static failureMessageFromClose(event: Event): string | undefined {
|
|
576
|
+
try {
|
|
577
|
+
if ((event as Event & { code?: unknown })?.code === 1006) {
|
|
578
|
+
return FTRumWebSocketTracking.eventString(event, 'reason');
|
|
579
|
+
}
|
|
580
|
+
} catch {
|
|
581
|
+
// Optional event details must never interrupt business event dispatch.
|
|
582
|
+
}
|
|
583
|
+
return undefined;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
private static statusFromError(
|
|
587
|
+
message: string | undefined
|
|
588
|
+
): number | undefined {
|
|
589
|
+
const match =
|
|
590
|
+
message && message.length <= MAX_HTTP_ERROR_PARSE_LENGTH
|
|
591
|
+
? SOCKET_ROCKET_HTTP_ERROR.exec(message.trim())
|
|
592
|
+
: null;
|
|
593
|
+
return match ? Number(match[1]) : undefined;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
private static toHandshakeError(error: unknown): WebSocketHandshakeError {
|
|
597
|
+
try {
|
|
598
|
+
return {
|
|
599
|
+
message:
|
|
600
|
+
error instanceof Error ? error.message || error.name : String(error),
|
|
601
|
+
};
|
|
602
|
+
} catch {
|
|
603
|
+
return {};
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
private static startResource(start: () => Promise<void>): Promise<boolean> {
|
|
608
|
+
try {
|
|
609
|
+
return Promise.resolve(start()).then(() => true);
|
|
610
|
+
} catch (error) {
|
|
611
|
+
return Promise.reject(error);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private static async completeResource(
|
|
616
|
+
resourceReporter: FTRumWebSocketResourceReporter,
|
|
617
|
+
lifecycleVersion: number,
|
|
618
|
+
resourceKey: string,
|
|
619
|
+
startPromise: Promise<boolean>,
|
|
620
|
+
url: string,
|
|
621
|
+
requestHeader: Record<string, unknown>,
|
|
622
|
+
resourceStatus: number,
|
|
623
|
+
webSocketHandshakeState: WebSocketHandshakeState,
|
|
624
|
+
timing: WebSocketTimingMetrics | undefined,
|
|
625
|
+
error?: WebSocketHandshakeError,
|
|
626
|
+
errorReady?: Promise<void>
|
|
627
|
+
): Promise<void> {
|
|
628
|
+
const isCurrentLifecycle = () =>
|
|
629
|
+
lifecycleVersion === FTRumWebSocketTracking.lifecycleVersion;
|
|
630
|
+
try {
|
|
631
|
+
if (!(await startPromise) || !isCurrentLifecycle()) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
try {
|
|
635
|
+
await resourceReporter.stopResource(resourceKey);
|
|
636
|
+
} catch {
|
|
637
|
+
// addResource completes the native Resource. Attempt it even if stop
|
|
638
|
+
// failed, but never retry addResource and risk duplicate completion.
|
|
639
|
+
if (isCurrentLifecycle()) {
|
|
640
|
+
FTRumWebSocketTracking.warnOnce('Unable to stop WebSocket resource.');
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (errorReady) await errorReady;
|
|
644
|
+
// Only old native binaries classify in JS. Resolve supplemental text
|
|
645
|
+
// before normalization; duration was frozen at the first terminal event.
|
|
646
|
+
const httpStatus = FTRumWebSocketTracking.statusFromError(error?.message);
|
|
647
|
+
if (httpStatus !== undefined && webSocketHandshakeState !== 'success') {
|
|
648
|
+
resourceStatus = httpStatus;
|
|
649
|
+
webSocketHandshakeState = httpStatus !== 101 ? 'rejected' : 'failed';
|
|
650
|
+
}
|
|
651
|
+
const resource: FTRUMWebSocketResource = {
|
|
652
|
+
url,
|
|
653
|
+
httpMethod: 'GET',
|
|
654
|
+
requestHeader,
|
|
655
|
+
resourceStatus,
|
|
656
|
+
resourceType: 'websocket',
|
|
657
|
+
webSocketHandshake: true,
|
|
658
|
+
webSocketHandshakeState,
|
|
659
|
+
};
|
|
660
|
+
if (error?.message) resource.errorMessage = error.message;
|
|
661
|
+
if (isCurrentLifecycle()) {
|
|
662
|
+
await resourceReporter.addResource(resourceKey, resource, timing);
|
|
663
|
+
}
|
|
664
|
+
} catch {
|
|
665
|
+
if (isCurrentLifecycle()) {
|
|
666
|
+
FTRumWebSocketTracking.warnOnce('Unable to report WebSocket resource.');
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
private static async completeNativeResource(
|
|
672
|
+
session: NativeWebSocketSession,
|
|
673
|
+
lifecycleVersion: number,
|
|
674
|
+
key: string,
|
|
675
|
+
started: Promise<boolean>,
|
|
676
|
+
content: NativeWebSocketResource,
|
|
677
|
+
timing: WebSocketTimingMetrics | undefined,
|
|
678
|
+
error?: WebSocketHandshakeError,
|
|
679
|
+
errorReady?: Promise<void>
|
|
680
|
+
): Promise<void> {
|
|
681
|
+
// Attach rejection handling immediately, including when start never settles.
|
|
682
|
+
let stopped: Promise<void>;
|
|
683
|
+
try {
|
|
684
|
+
stopped = Promise.resolve(
|
|
685
|
+
session.stopResource(key, content.webSocketEvent)
|
|
686
|
+
).catch(() => {
|
|
687
|
+
if (lifecycleVersion === FTRumWebSocketTracking.lifecycleVersion)
|
|
688
|
+
FTRumWebSocketTracking.warnOnce('Unable to stop WebSocket resource.');
|
|
689
|
+
});
|
|
690
|
+
} catch {
|
|
691
|
+
stopped = Promise.resolve();
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
if (!(await started)) return;
|
|
695
|
+
await stopped;
|
|
696
|
+
if (errorReady) await errorReady;
|
|
697
|
+
if (lifecycleVersion !== FTRumWebSocketTracking.lifecycleVersion) return;
|
|
698
|
+
await session.addResource(
|
|
699
|
+
key,
|
|
700
|
+
error?.message ? { ...content, errorMessage: error.message } : content,
|
|
701
|
+
timing ?? {}
|
|
702
|
+
);
|
|
703
|
+
} catch {
|
|
704
|
+
session.release(key);
|
|
705
|
+
if (lifecycleVersion === FTRumWebSocketTracking.lifecycleVersion)
|
|
706
|
+
FTRumWebSocketTracking.warnOnce('Unable to report WebSocket resource.');
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
private static warnOnce(message: string): void {
|
|
711
|
+
if (FTRumWebSocketTracking.warnedFailures.has(message)) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
FTRumWebSocketTracking.warnedFailures.add(message);
|
|
715
|
+
try {
|
|
716
|
+
console.warn(`[FT-SDK] ${message}`);
|
|
717
|
+
} catch {
|
|
718
|
+
// A host-provided logger must not turn instrumentation failure into one
|
|
719
|
+
// on the application's constructor, event dispatch or promise chain.
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|