From 9e14ba7a4e326eba1b8a1fbccc90133887e53e55 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Tue, 14 Jul 2026 04:21:31 -0700 Subject: [PATCH] Add WebSocket CDP network reporting (iOS) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: **Context** This stack implements WebSocket event debugging for the Network panel in React Native DevTools. React Native DevTools' Network support launched with fetch, XHR, and Image traffic, but WebSocket connections are invisible today. Discussed with Expo earlier this year, we want upstream parity before Expo adopts the 1P Network panel. This is a comparatively small surface: 6 CDP events, no CDP methods. **Minimum goal**: Parity with Expo's current WS event coverage. We improve on this with Request Initiator support in D111561995. **This diff** Adds first-party reporting for six of the seven WebSocket CDP events (`webSocketCreated`, `webSocketWillSendHandshakeRequest`, `webSocketHandshakeResponseReceived`, `webSocketFrameSent`, `webSocketFrameReceived`, `webSocketClosed`), mirroring the layering of the existing HTTP pipeline: - **C++ core**: WebSocket CDP types and reporting APIs through `CdpNetwork` → `NetworkHandler` → `NetworkReporter`. Compiled to no-ops in production builds; inert unless the Network domain is enabled. - **iOS**: a new `RCTInspectorWebSocketReporter` bridges `RCTWebSocketModule` to `NetworkReporter`, reporting connection lifecycle, real handshake headers, and sent/received messages. Android follows separately. - **Gating**: a new `fuseboxWebSocketEventsEnabled` feature flag (experimentation, default off), checked alongside `enableNetworkEventReporting`. **Omitted from the spec, and why** - [`Network.webSocketFrameError`](https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-webSocketFrameError) — the one unimplemented event. Neither SocketRocket nor OkHttp exposes a frame-scoped error; connection failures are terminal and already reported via `webSocketClosed`. Cheap to bolt on once a genuine per-message error source exists (e.g. OkHttp `send()` returning false). - Ping/pong and close frames (opcodes 8–10) — not surfaced by the native socket APIs (OkHttp has no ping/pong callbacks). Chrome renders these as extra Messages rows; the practical loss is that close code/reason isn't visible, since `webSocketClosed` itself has no fields for them. - Optional [`WebSocketResponse`](https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-WebSocketResponse) fields (`headersText`, `requestHeaders`, `requestHeadersText`) — we report structured headers only; raw handshake text isn't available from OkHttp on Android. - No `performance-timeline` or tracing integration — WebSocket events report to CDP only; unlike HTTP, there is no `PerformanceResourceTiming` counterpart to populate. **Rollout plan** (New `enableNetworkEventReporting` flag) - 0.88 - Canary channel - 0.89 - Stable channel Changelog: [Internal] Differential Revision: D111561998 --- .../RCTInspectorWebSocketReporter.h | 76 +++++++++ .../RCTInspectorWebSocketReporter.mm | 156 ++++++++++++++++++ .../React/CoreModules/RCTWebSocketModule.mm | 39 ++++- .../CoreModules/React-CoreModules.podspec | 1 + .../featureflags/ReactNativeFeatureFlags.kt | 8 +- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 +- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 +- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 +- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +- .../jsinspector-modern/network/CdpNetwork.cpp | 81 +++++++++ .../jsinspector-modern/network/CdpNetwork.h | 93 +++++++++++ .../network/NetworkHandler.cpp | 135 +++++++++++++++ .../network/NetworkHandler.h | 38 +++++ .../tests/NetworkReporterTest.cpp | 120 ++++++++++++++ .../featureflags/ReactNativeFeatureFlags.cpp | 6 +- .../featureflags/ReactNativeFeatureFlags.h | 7 +- .../ReactNativeFeatureFlagsAccessor.cpp | 72 +++++--- .../ReactNativeFeatureFlagsAccessor.h | 6 +- .../ReactNativeFeatureFlagsDefaults.h | 6 +- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 +- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 +- .../NativeReactNativeFeatureFlags.h | 4 +- .../react/networking/NetworkReporter.cpp | 62 +++++++ .../react/networking/NetworkReporter.h | 60 +++++++ .../ReactNativeFeatureFlags.config.js | 11 ++ .../featureflags/ReactNativeFeatureFlags.js | 7 +- .../specs/NativeReactNativeFeatureFlags.js | 3 +- .../api-snapshots/ReactAndroidDebugCxx.api | 62 +++++++ .../api-snapshots/ReactAndroidNewarchCxx.api | 62 +++++++ .../api-snapshots/ReactAndroidReleaseCxx.api | 62 +++++++ .../api-snapshots/ReactAppleDebugCxx.api | 71 ++++++++ .../api-snapshots/ReactAppleNewarchCxx.api | 71 ++++++++ .../api-snapshots/ReactAppleReleaseCxx.api | 71 ++++++++ .../api-snapshots/ReactCommonDebugCxx.api | 62 +++++++ .../api-snapshots/ReactCommonNewarchCxx.api | 62 +++++++ .../api-snapshots/ReactCommonReleaseCxx.api | 62 +++++++ 40 files changed, 1607 insertions(+), 48 deletions(-) create mode 100644 packages/react-native/React/CoreModules/RCTInspectorWebSocketReporter.h create mode 100644 packages/react-native/React/CoreModules/RCTInspectorWebSocketReporter.mm diff --git a/packages/react-native/React/CoreModules/RCTInspectorWebSocketReporter.h b/packages/react-native/React/CoreModules/RCTInspectorWebSocketReporter.h new file mode 100644 index 000000000000..474085f859bb --- /dev/null +++ b/packages/react-native/React/CoreModules/RCTInspectorWebSocketReporter.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * [Experimental] An interface for reporting WebSocket events to the modern + * debugger server. + * + * In a production (non dev or profiling) build, CDP reporting is disabled + * and all methods are a no-op. + * + * This is a helper class wrapping `facebook::react::NetworkReporter`. + */ +@interface RCTInspectorWebSocketReporter : NSObject + +/** + * Report that a WebSocket connection is about to be created. + * + * Corresponds to `Network.webSocketCreated` in CDP. + */ ++ (void)reportWebSocketCreated:(nullable NSString *)requestId url:(NSURL *)url; + +/** + * Report that a WebSocket handshake (HTTP upgrade) request is about to be + * sent, along with its final request headers. + * + * Corresponds to `Network.webSocketWillSendHandshakeRequest` in CDP. + */ ++ (void)reportWillSendHandshakeRequest:(nullable NSString *)requestId request:(NSURLRequest *)request; + +/** + * Report that the WebSocket handshake response was received and the + * connection is established. `httpMessage` is the raw handshake response, + * e.g. `SRWebSocket.receivedHTTPHeaders`. If NULL or incomplete, a minimal + * `101 Switching Protocols` response is reported instead. + * + * Corresponds to `Network.webSocketHandshakeResponseReceived` in CDP. + */ ++ (void)reportHandshakeResponseReceived:(nullable NSString *)requestId + httpMessage:(nullable CFHTTPMessageRef)httpMessage; + +/** + * Report a WebSocket message sent over an open connection. `message` must be + * an `NSString` (text message) or `NSData` (binary message). + * + * Corresponds to `Network.webSocketFrameSent` in CDP. + */ ++ (void)reportMessageSent:(nullable NSString *)requestId message:(nullable id)message; + +/** + * Report a WebSocket message received over an open connection. `message` + * must be an `NSString` (text message) or `NSData` (binary message). + * + * Corresponds to `Network.webSocketFrameReceived` in CDP. + */ ++ (void)reportMessageReceived:(nullable NSString *)requestId message:(nullable id)message; + +/** + * Report that a WebSocket connection was closed, whether cleanly or due to + * an error. + * + * Corresponds to `Network.webSocketClosed` in CDP. + */ ++ (void)reportWebSocketClosed:(nullable NSString *)requestId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/CoreModules/RCTInspectorWebSocketReporter.mm b/packages/react-native/React/CoreModules/RCTInspectorWebSocketReporter.mm new file mode 100644 index 000000000000..511032ab25fb --- /dev/null +++ b/packages/react-native/React/CoreModules/RCTInspectorWebSocketReporter.mm @@ -0,0 +1,156 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTInspectorWebSocketReporter.h" + +#import +#import + +using facebook::react::Headers; +using facebook::react::NetworkReporter; +using facebook::react::ReactNativeFeatureFlags; + +namespace { + +/** + * Convert an `NSString` to a `std::string`, mapping `nil` (and any string whose + * UTF-8 representation is unavailable) to an empty string. + */ +std::string toStdString(NSString *string) +{ + const char *utf8 = string.UTF8String; + return utf8 != nullptr ? std::string(utf8) : std::string(); +} + +/** + * Returns whether WebSocket events should be reported for the given request + * ID. Reporting requires the `enableNetworkEventReporting` and + * `fuseboxWebSocketEventsEnabled` feature flags, and a connected CDP debugger + * with the Network domain enabled (dev or profiling builds only). + */ +BOOL isReportingEnabled(NSString *requestId) +{ + return requestId != nil && ReactNativeFeatureFlags::enableNetworkEventReporting() && + ReactNativeFeatureFlags::fuseboxWebSocketEventsEnabled() && NetworkReporter::getInstance().isDebuggingEnabled(); +} + +Headers convertNSDictionaryToHeaders(const NSDictionary *headers) +{ + Headers result; + for (NSString *key in headers) { + result[toStdString(key)] = toStdString(headers[key]); + } + return result; +} + +/** + * Convert an `NSString` (text) or `NSData` (binary) WebSocket message to a + * CDP `payloadData` string — UTF-8 for text messages, base64 for binary. + * \returns Whether the message was a supported type, writing to the out + * params on success. + */ +bool convertMessageToPayloadData(id message, std::string &payloadData, bool &isBinary) +{ + if ([message isKindOfClass:[NSString class]]) { + payloadData = toStdString((NSString *)message); + isBinary = false; + return true; + } + + if ([message isKindOfClass:[NSData class]]) { + payloadData = toStdString([(NSData *)message base64EncodedStringWithOptions:0]); + isBinary = true; + return true; + } + + return false; +} + +} // namespace + +@implementation RCTInspectorWebSocketReporter + ++ (void)reportWebSocketCreated:(NSString *)requestId url:(NSURL *)url +{ + if (!isReportingEnabled(requestId)) { + return; + } + + NetworkReporter::getInstance().reportWebSocketCreated(toStdString(requestId), toStdString(url.absoluteString)); +} + ++ (void)reportWillSendHandshakeRequest:(NSString *)requestId request:(NSURLRequest *)request +{ + if (!isReportingEnabled(requestId)) { + return; + } + + NetworkReporter::getInstance().reportWebSocketWillSendHandshakeRequest( + toStdString(requestId), convertNSDictionaryToHeaders(request.allHTTPHeaderFields)); +} + ++ (void)reportHandshakeResponseReceived:(NSString *)requestId httpMessage:(nullable CFHTTPMessageRef)httpMessage +{ + if (!isReportingEnabled(requestId)) { + return; + } + + // Fall back to a minimal `101 Switching Protocols` response (guaranteed by + // RFC 6455 for an open connection) if the handshake response is unavailable. + uint16_t statusCode = 101; + Headers headers; + + if (httpMessage != NULL && CFHTTPMessageIsHeaderComplete(httpMessage) != 0) { + statusCode = (uint16_t)CFHTTPMessageGetResponseStatusCode(httpMessage); + NSDictionary *responseHeaders = + (NSDictionary *)CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(httpMessage)); + headers = convertNSDictionaryToHeaders(responseHeaders); + } + + NetworkReporter::getInstance().reportWebSocketHandshakeResponseReceived(toStdString(requestId), statusCode, headers); +} + ++ (void)reportMessageSent:(NSString *)requestId message:(id)message +{ + if (!isReportingEnabled(requestId)) { + return; + } + + std::string payloadData; + bool isBinary = false; + if (!convertMessageToPayloadData(message, payloadData, isBinary)) { + return; + } + + NetworkReporter::getInstance().reportWebSocketMessageSent(toStdString(requestId), payloadData, isBinary); +} + ++ (void)reportMessageReceived:(NSString *)requestId message:(id)message +{ + if (!isReportingEnabled(requestId)) { + return; + } + + std::string payloadData; + bool isBinary = false; + if (!convertMessageToPayloadData(message, payloadData, isBinary)) { + return; + } + + NetworkReporter::getInstance().reportWebSocketMessageReceived(toStdString(requestId), payloadData, isBinary); +} + ++ (void)reportWebSocketClosed:(NSString *)requestId +{ + if (!isReportingEnabled(requestId)) { + return; + } + + NetworkReporter::getInstance().reportWebSocketClosed(toStdString(requestId)); +} + +@end diff --git a/packages/react-native/React/CoreModules/RCTWebSocketModule.mm b/packages/react-native/React/CoreModules/RCTWebSocketModule.mm index bd95c32accfc..175a5b2eb2b8 100644 --- a/packages/react-native/React/CoreModules/RCTWebSocketModule.mm +++ b/packages/react-native/React/CoreModules/RCTWebSocketModule.mm @@ -16,6 +16,17 @@ #import #import "CoreModulesPlugins.h" +#import "RCTInspectorWebSocketReporter.h" + +@interface SRWebSocket (React) + +/** + * The CDP request ID used to report this connection's events to the modern + * debugger server, via `RCTInspectorWebSocketReporter`. + */ +@property (nonatomic, copy) NSString *inspectorRequestId; + +@end @implementation SRWebSocket (React) @@ -29,6 +40,16 @@ - (void)setReactTag:(NSNumber *)reactTag objc_setAssociatedObject(self, @selector(reactTag), reactTag, OBJC_ASSOCIATION_COPY_NONATOMIC); } +- (NSString *)inspectorRequestId +{ + return objc_getAssociatedObject(self, _cmd); +} + +- (void)setInspectorRequestId:(NSString *)inspectorRequestId +{ + objc_setAssociatedObject(self, @selector(inspectorRequestId), inspectorRequestId, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + @end @interface RCTWebSocketModule () @@ -131,16 +152,23 @@ - (void)invalidate [webSocket setDelegateDispatchQueue:[self methodQueue]]; webSocket.delegate = self; webSocket.reactTag = @(socketID); + webSocket.inspectorRequestId = [[NSUUID UUID] UUIDString]; if (!_sockets) { _sockets = [NSMutableDictionary new]; } _sockets[@(socketID)] = webSocket; + + [RCTInspectorWebSocketReporter reportWebSocketCreated:webSocket.inspectorRequestId url:URL]; + [RCTInspectorWebSocketReporter reportWillSendHandshakeRequest:webSocket.inspectorRequestId request:request]; + [webSocket open]; } RCT_EXPORT_METHOD(send : (NSString *)message forSocketID : (double)socketID) { - [_sockets[@(socketID)] sendString:message error:nil]; + SRWebSocket *webSocket = _sockets[@(socketID)]; + [RCTInspectorWebSocketReporter reportMessageSent:webSocket.inspectorRequestId message:message]; + [webSocket sendString:message error:nil]; } RCT_EXPORT_METHOD(sendBinary : (NSString *)base64String forSocketID : (double)socketID) @@ -150,7 +178,9 @@ - (void)invalidate - (void)sendData:(NSData *)data forSocketID:(NSNumber *__nonnull)socketID { - [_sockets[socketID] sendData:data error:nil]; + SRWebSocket *webSocket = _sockets[socketID]; + [RCTInspectorWebSocketReporter reportMessageSent:webSocket.inspectorRequestId message:data]; + [webSocket sendData:data error:nil]; } RCT_EXPORT_METHOD(ping : (double)socketID) @@ -182,6 +212,7 @@ - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message if (!socketID) { return; } + [RCTInspectorWebSocketReporter reportMessageReceived:webSocket.inspectorRequestId message:message]; id contentHandler = _contentHandlers[socketID]; if (contentHandler) { message = [contentHandler processWebsocketMessage:message forSocketID:socketID withType:&type]; @@ -203,6 +234,8 @@ - (void)webSocketDidOpen:(SRWebSocket *)webSocket if (!socketID) { return; } + [RCTInspectorWebSocketReporter reportHandshakeResponseReceived:webSocket.inspectorRequestId + httpMessage:webSocket.receivedHTTPHeaders]; [self sendEventWithName:@"websocketOpen" body:@{@"id" : socketID, @"protocol" : webSocket.protocol ? webSocket.protocol : @""}]; } @@ -213,6 +246,7 @@ - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error if (!socketID) { return; } + [RCTInspectorWebSocketReporter reportWebSocketClosed:webSocket.inspectorRequestId]; _contentHandlers[socketID] = nil; _sockets[socketID] = nil; NSDictionary *body = @@ -229,6 +263,7 @@ - (void)webSocket:(SRWebSocket *)webSocket if (!socketID) { return; } + [RCTInspectorWebSocketReporter reportWebSocketClosed:webSocket.inspectorRequestId]; _contentHandlers[socketID] = nil; _sockets[socketID] = nil; [self sendEventWithName:@"websocketClosed" diff --git a/packages/react-native/React/CoreModules/React-CoreModules.podspec b/packages/react-native/React/CoreModules/React-CoreModules.podspec index f98c195770c6..2d8b9aa21d12 100644 --- a/packages/react-native/React/CoreModules/React-CoreModules.podspec +++ b/packages/react-native/React/CoreModules/React-CoreModules.podspec @@ -58,6 +58,7 @@ Pod::Spec.new do |s| add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern') add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp') add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing') + add_dependency(s, "React-networking", :framework_name => 'React_networking') add_dependency(s, "React-RCTFBReactNativeSpec") add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"]) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index 04d0b80b3c64..f028b7e4f7da 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<854bb2a573f2c69d495aa1721f903ab0>> + * @generated SignedSource<> */ /** @@ -390,6 +390,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun fuseboxScreenshotCaptureEnabled(): Boolean = accessor.fuseboxScreenshotCaptureEnabled() + /** + * Enable reporting of WebSocket network events (`Network.webSocket*` CDP events) to the React Native DevTools CDP backend. Requires `fuseboxNetworkInspectionEnabled`. + */ + @JvmStatic + public fun fuseboxWebSocketEventsEnabled(): Boolean = accessor.fuseboxWebSocketEventsEnabled() + /** * When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. On iOS, this passes AnimatedProps directly through the delegate chain and applies them via cloneProps, avoiding the folly::dynamic round-trip. */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index 624e509c5034..0e359509c73c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<5ae6b1a0bfa9d680aa61685dbacd69d2>> + * @generated SignedSource<> */ /** @@ -80,6 +80,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var fuseboxFrameRecordingEnabledCache: Boolean? = null private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var fuseboxScreenshotCaptureEnabledCache: Boolean? = null + private var fuseboxWebSocketEventsEnabledCache: Boolean? = null private var optimizedAnimatedPropUpdatesCache: Boolean? = null private var overrideBySynchronousMountPropsAtMountingAndroidCache: Boolean? = null private var perfIssuesEnabledCache: Boolean? = null @@ -647,6 +648,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun fuseboxWebSocketEventsEnabled(): Boolean { + var cached = fuseboxWebSocketEventsEnabledCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.fuseboxWebSocketEventsEnabled() + fuseboxWebSocketEventsEnabledCache = cached + } + return cached + } + override fun optimizedAnimatedPropUpdates(): Boolean { var cached = optimizedAnimatedPropUpdatesCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index 2f1a0cfc504d..dd081be14a65 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<8b1c3206f7e63790e411f57c2dd64076>> + * @generated SignedSource<<87b76f5c2af82462dc66c7721cd66ac4>> */ /** @@ -148,6 +148,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun fuseboxScreenshotCaptureEnabled(): Boolean + @DoNotStrip @JvmStatic public external fun fuseboxWebSocketEventsEnabled(): Boolean + @DoNotStrip @JvmStatic public external fun optimizedAnimatedPropUpdates(): Boolean @DoNotStrip @JvmStatic public external fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 91f9ac37c7ec..7b9062eb3ca9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<9b295106ef57d7fa85846c57bb74a090>> + * @generated SignedSource<> */ /** @@ -143,6 +143,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun fuseboxScreenshotCaptureEnabled(): Boolean = false + override fun fuseboxWebSocketEventsEnabled(): Boolean = false + override fun optimizedAnimatedPropUpdates(): Boolean = false override fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean = true diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index b274311cd9d2..c5786d397193 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<98a6085638c86aaa012c8bd972627ad4>> + * @generated SignedSource<<9a2806b3934d130010954a92a7b48e0f>> */ /** @@ -84,6 +84,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var fuseboxFrameRecordingEnabledCache: Boolean? = null private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var fuseboxScreenshotCaptureEnabledCache: Boolean? = null + private var fuseboxWebSocketEventsEnabledCache: Boolean? = null private var optimizedAnimatedPropUpdatesCache: Boolean? = null private var overrideBySynchronousMountPropsAtMountingAndroidCache: Boolean? = null private var perfIssuesEnabledCache: Boolean? = null @@ -711,6 +712,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun fuseboxWebSocketEventsEnabled(): Boolean { + var cached = fuseboxWebSocketEventsEnabledCache + if (cached == null) { + cached = currentProvider.fuseboxWebSocketEventsEnabled() + accessedFeatureFlags.add("fuseboxWebSocketEventsEnabled") + fuseboxWebSocketEventsEnabledCache = cached + } + return cached + } + override fun optimizedAnimatedPropUpdates(): Boolean { var cached = optimizedAnimatedPropUpdatesCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index c611198dac44..2a6da8129f50 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<8b5a9f8966d9f096b7fa4f7020db1f34>> + * @generated SignedSource<<49c76b8072c1bd29135086c9e0a5da2f>> */ /** @@ -143,6 +143,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun fuseboxScreenshotCaptureEnabled(): Boolean + @DoNotStrip public fun fuseboxWebSocketEventsEnabled(): Boolean + @DoNotStrip public fun optimizedAnimatedPropUpdates(): Boolean @DoNotStrip public fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index 09be85cdc306..349c5ad6a541 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<42f1901c8b7cb568cc761a791991a333>> */ /** @@ -399,6 +399,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool fuseboxWebSocketEventsEnabled() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("fuseboxWebSocketEventsEnabled"); + return method(javaProvider_); + } + bool optimizedAnimatedPropUpdates() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("optimizedAnimatedPropUpdates"); @@ -859,6 +865,11 @@ bool JReactNativeFeatureFlagsCxxInterop::fuseboxScreenshotCaptureEnabled( return ReactNativeFeatureFlags::fuseboxScreenshotCaptureEnabled(); } +bool JReactNativeFeatureFlagsCxxInterop::fuseboxWebSocketEventsEnabled( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::fuseboxWebSocketEventsEnabled(); +} + bool JReactNativeFeatureFlagsCxxInterop::optimizedAnimatedPropUpdates( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::optimizedAnimatedPropUpdates(); @@ -1200,6 +1211,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "fuseboxScreenshotCaptureEnabled", JReactNativeFeatureFlagsCxxInterop::fuseboxScreenshotCaptureEnabled), + makeNativeMethod( + "fuseboxWebSocketEventsEnabled", + JReactNativeFeatureFlagsCxxInterop::fuseboxWebSocketEventsEnabled), makeNativeMethod( "optimizedAnimatedPropUpdates", JReactNativeFeatureFlagsCxxInterop::optimizedAnimatedPropUpdates), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index 2de55550ee3a..6ffc3146e658 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<9967d87b794ba46797ed78a845683dc7>> */ /** @@ -210,6 +210,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool fuseboxScreenshotCaptureEnabled( facebook::jni::alias_ref); + static bool fuseboxWebSocketEventsEnabled( + facebook::jni::alias_ref); + static bool optimizedAnimatedPropUpdates( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.cpp b/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.cpp index 916103327b3a..77dbe081848c 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.cpp @@ -141,6 +141,87 @@ folly::dynamic LoadingFinishedParams::toDynamic() const { return params; } +/* static */ WebSocketResponse WebSocketResponse::fromInputParams( + uint16_t status, + const Headers& headers) { + return { + .status = status, + .statusText = httpReasonPhrase(status), + .headers = headers, + }; +} + +folly::dynamic WebSocketResponse::toDynamic() const { + folly::dynamic result = folly::dynamic::object; + + result["status"] = status; + result["statusText"] = statusText; + result["headers"] = headersToDynamic(headers); + + return result; +} + +folly::dynamic WebSocketFrame::toDynamic() const { + folly::dynamic result = folly::dynamic::object; + + result["opcode"] = opcode; + result["mask"] = mask; + result["payloadData"] = payloadData; + + return result; +} + +folly::dynamic WebSocketCreatedParams::toDynamic() const { + folly::dynamic params = folly::dynamic::object; + + params["requestId"] = requestId; + params["url"] = url; + params["initiator"] = initiator; + + return params; +} + +folly::dynamic WebSocketWillSendHandshakeRequestParams::toDynamic() const { + folly::dynamic params = folly::dynamic::object; + + params["requestId"] = requestId; + params["timestamp"] = timestamp; + params["wallTime"] = wallTime; + params["request"] = + folly::dynamic::object("headers", headersToDynamic(headers)); + + return params; +} + +folly::dynamic WebSocketHandshakeResponseReceivedParams::toDynamic() const { + folly::dynamic params = folly::dynamic::object; + + params["requestId"] = requestId; + params["timestamp"] = timestamp; + params["response"] = response.toDynamic(); + + return params; +} + +folly::dynamic WebSocketFrameParams::toDynamic() const { + folly::dynamic params = folly::dynamic::object; + + params["requestId"] = requestId; + params["timestamp"] = timestamp; + params["response"] = response.toDynamic(); + + return params; +} + +folly::dynamic WebSocketClosedParams::toDynamic() const { + folly::dynamic params = folly::dynamic::object; + + params["requestId"] = requestId; + params["timestamp"] = timestamp; + + return params; +} + std::string resourceTypeFromMimeType(const std::string& mimeType) { if (mimeType.find("image/") == 0) { return "Image"; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.h b/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.h index 2246057ac46a..e017d3a10d6f 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/CdpNetwork.h @@ -136,6 +136,99 @@ struct LoadingFinishedParams { folly::dynamic toDynamic() const; }; +/** + * HTTP response data for a WebSocket handshake (upgrade) request. + * + * https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-WebSocketResponse + */ +struct WebSocketResponse { + uint16_t status; + std::string statusText; + Headers headers; + + /** + * Convenience function to construct a `WebSocketResponse` from generic + * input params, deriving the status text. + */ + static WebSocketResponse fromInputParams(uint16_t status, const Headers &headers); + + folly::dynamic toDynamic() const; +}; + +/** + * A WebSocket message payload. NOTE: Despite the CDP type name, this + * represents a complete WebSocket message, not a wire-level frame. + * + * https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-WebSocketFrame + */ +struct WebSocketFrame { + int opcode; + bool mask; + std::string payloadData; + + folly::dynamic toDynamic() const; +}; + +/** + * https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-webSocketCreated + */ +struct WebSocketCreatedParams { + std::string requestId; + std::string url; + folly::dynamic initiator; + + folly::dynamic toDynamic() const; +}; + +/** + * https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-webSocketWillSendHandshakeRequest + */ +struct WebSocketWillSendHandshakeRequestParams { + std::string requestId; + double timestamp; + double wallTime; + /** Serialized as the `headers` field of the CDP `WebSocketRequest` type. */ + Headers headers; + + folly::dynamic toDynamic() const; +}; + +/** + * https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-webSocketHandshakeResponseReceived + */ +struct WebSocketHandshakeResponseReceivedParams { + std::string requestId; + double timestamp; + WebSocketResponse response; + + folly::dynamic toDynamic() const; +}; + +/** + * Shared params type for the `Network.webSocketFrameSent` and + * `Network.webSocketFrameReceived` events. + * + * https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-webSocketFrameSent + * https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-webSocketFrameReceived + */ +struct WebSocketFrameParams { + std::string requestId; + double timestamp; + WebSocketFrame response; + + folly::dynamic toDynamic() const; +}; + +/** + * https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-webSocketClosed + */ +struct WebSocketClosedParams { + std::string requestId; + double timestamp; + + folly::dynamic toDynamic() const; +}; + /** * Get the CDP `ResourceType` for a given MIME type. * diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.cpp b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.cpp index 9dbe9bfbaa8a..632a8ad25c01 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.cpp @@ -16,6 +16,12 @@ namespace facebook::react::jsinspector_modern { namespace { +/** + * WebSocket frame opcodes for data messages (RFC 6455). + */ +constexpr int kWebSocketOpcodeText = 1; +constexpr int kWebSocketOpcodeBinary = 2; + /** * Get the current Unix timestamp in seconds (µs precision, CDP format). */ @@ -200,6 +206,135 @@ void NetworkHandler::onLoadingFailed( } } +void NetworkHandler::onWebSocketCreated( + const std::string& requestId, + const std::string& url) { + if (!isEnabledNoSync()) { + return; + } + + std::optional initiator = + consumeStoredRequestInitiator(requestId); + auto params = cdp::network::WebSocketCreatedParams{ + .requestId = requestId, + .url = url, + .initiator = initiator.has_value() + ? std::move(initiator.value()) + : folly::dynamic::object("type", "script"), + }; + + sendToAllAgents( + cdp::jsonNotification("Network.webSocketCreated", params.toDynamic())); +} + +void NetworkHandler::onWebSocketWillSendHandshakeRequest( + const std::string& requestId, + const Headers& headers) { + if (!isEnabledNoSync()) { + return; + } + + double timestamp = getCurrentUnixTimestampSeconds(); + auto params = cdp::network::WebSocketWillSendHandshakeRequestParams{ + .requestId = requestId, + // NOTE: Both timestamp and wallTime use the same unit, however wallTime + // is relative to an "arbitrary epoch". In our implementation, use the + // Unix epoch for both. + .timestamp = timestamp, + .wallTime = timestamp, + .headers = headers, + }; + + sendToAllAgents( + cdp::jsonNotification( + "Network.webSocketWillSendHandshakeRequest", params.toDynamic())); +} + +void NetworkHandler::onWebSocketHandshakeResponseReceived( + const std::string& requestId, + uint16_t status, + const Headers& headers) { + if (!isEnabledNoSync()) { + return; + } + + auto params = cdp::network::WebSocketHandshakeResponseReceivedParams{ + .requestId = requestId, + .timestamp = getCurrentUnixTimestampSeconds(), + .response = + cdp::network::WebSocketResponse::fromInputParams(status, headers), + }; + + sendToAllAgents( + cdp::jsonNotification( + "Network.webSocketHandshakeResponseReceived", params.toDynamic())); +} + +void NetworkHandler::onWebSocketFrameSent( + const std::string& requestId, + const std::string& payloadData, + bool isBinary) { + if (!isEnabledNoSync()) { + return; + } + + auto params = cdp::network::WebSocketFrameParams{ + .requestId = requestId, + .timestamp = getCurrentUnixTimestampSeconds(), + .response = + { + .opcode = + isBinary ? kWebSocketOpcodeBinary : kWebSocketOpcodeText, + // Client-to-server messages are always masked (RFC 6455). + .mask = true, + .payloadData = payloadData, + }, + }; + + sendToAllAgents( + cdp::jsonNotification("Network.webSocketFrameSent", params.toDynamic())); +} + +void NetworkHandler::onWebSocketFrameReceived( + const std::string& requestId, + const std::string& payloadData, + bool isBinary) { + if (!isEnabledNoSync()) { + return; + } + + auto params = cdp::network::WebSocketFrameParams{ + .requestId = requestId, + .timestamp = getCurrentUnixTimestampSeconds(), + .response = + { + .opcode = + isBinary ? kWebSocketOpcodeBinary : kWebSocketOpcodeText, + // Server-to-client messages are never masked (RFC 6455). + .mask = false, + .payloadData = payloadData, + }, + }; + + sendToAllAgents( + cdp::jsonNotification( + "Network.webSocketFrameReceived", params.toDynamic())); +} + +void NetworkHandler::onWebSocketClosed(const std::string& requestId) { + if (!isEnabledNoSync()) { + return; + } + + auto params = cdp::network::WebSocketClosedParams{ + .requestId = requestId, + .timestamp = getCurrentUnixTimestampSeconds(), + }; + + sendToAllAgents( + cdp::jsonNotification("Network.webSocketClosed", params.toDynamic())); +} + void NetworkHandler::storeResponseBody( const std::string& requestId, std::string_view body, diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.h b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.h index aaa43aad2b47..b71890f495fc 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkHandler.h @@ -90,6 +90,44 @@ class NetworkHandler { */ void onLoadingFailed(const std::string &requestId, bool cancelled); + /** + * @cdp Network.webSocketCreated + */ + void onWebSocketCreated(const std::string &requestId, const std::string &url); + + /** + * @cdp Network.webSocketWillSendHandshakeRequest + */ + void onWebSocketWillSendHandshakeRequest(const std::string &requestId, const Headers &headers); + + /** + * @cdp Network.webSocketHandshakeResponseReceived + */ + void onWebSocketHandshakeResponseReceived(const std::string &requestId, uint16_t status, const Headers &headers); + + /** + * @cdp Network.webSocketFrameSent + * + * \param payloadData The message payload. A UTF-8 string for text messages, + * or a base64-encoded string for binary messages. + * \param isBinary Whether this is a binary message. + */ + void onWebSocketFrameSent(const std::string &requestId, const std::string &payloadData, bool isBinary); + + /** + * @cdp Network.webSocketFrameReceived + * + * \param payloadData The message payload. A UTF-8 string for text messages, + * or a base64-encoded string for binary messages. + * \param isBinary Whether this is a binary message. + */ + void onWebSocketFrameReceived(const std::string &requestId, const std::string &payloadData, bool isBinary); + + /** + * @cdp Network.webSocketClosed + */ + void onWebSocketClosed(const std::string &requestId); + /** * Store the fetched response body for a text or image network response. * diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tests/NetworkReporterTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tests/NetworkReporterTest.cpp index 678a8c164533..f18935dba169 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tests/NetworkReporterTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tests/NetworkReporterTest.cpp @@ -464,6 +464,114 @@ TEST_P(NetworkReporterTest, testGetResponseBodyWithBase64) { })"); } +TEST_P(NetworkReporterTest, testCompleteWebSocketFlow) { + InSequence s; + this->expectMessageFromPage(JsonEq(R"({ + "id": 1, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 1, + "method": "Network.enable" + })"); + + const std::string requestId = "websocket-flow-request"; + + // Step 1: WebSocket created + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.webSocketCreated"), + AtJsonPtr("/params/requestId", requestId), + AtJsonPtr("/params/url", "wss://echo.example.com/socket"), + AtJsonPtr("/params/initiator/type", "script")))); + + NetworkReporter::getInstance().reportWebSocketCreated( + requestId, "wss://echo.example.com/socket"); + + // Step 2: Handshake request will be sent + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.webSocketWillSendHandshakeRequest"), + AtJsonPtr("/params/requestId", requestId), + AtJsonPtr("/params/timestamp", Gt(0)), + AtJsonPtr("/params/wallTime", Gt(0)), + AtJsonPtr("/params/request/headers/Origin", "https://example.com"), + AtJsonPtr( + "/params/request/headers/Sec-WebSocket-Protocol", "graphql-ws")))); + + NetworkReporter::getInstance().reportWebSocketWillSendHandshakeRequest( + requestId, + Headers{ + {"Origin", "https://example.com"}, + {"Sec-WebSocket-Protocol", "graphql-ws"}}); + + // Step 3: Handshake response received + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.webSocketHandshakeResponseReceived"), + AtJsonPtr("/params/requestId", requestId), + AtJsonPtr("/params/timestamp", Gt(0)), + AtJsonPtr("/params/response/status", 101), + AtJsonPtr("/params/response/statusText", "Switching Protocols"), + AtJsonPtr("/params/response/headers/Upgrade", "websocket"), + AtJsonPtr("/params/response/headers/Connection", "Upgrade")))); + + NetworkReporter::getInstance().reportWebSocketHandshakeResponseReceived( + requestId, + 101, + Headers{{"Upgrade", "websocket"}, {"Connection", "Upgrade"}}); + + // Step 4: Text message sent + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.webSocketFrameSent"), + AtJsonPtr("/params/requestId", requestId), + AtJsonPtr("/params/timestamp", Gt(0)), + AtJsonPtr("/params/response/opcode", 1), + AtJsonPtr("/params/response/mask", true), + AtJsonPtr("/params/response/payloadData", R"({"type": "ping"})")))); + + NetworkReporter::getInstance().reportWebSocketMessageSent( + requestId, R"({"type": "ping"})", false); + + // Step 5: Binary message sent (payload is base64-encoded) + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.webSocketFrameSent"), + AtJsonPtr("/params/requestId", requestId), + AtJsonPtr("/params/timestamp", Gt(0)), + AtJsonPtr("/params/response/opcode", 2), + AtJsonPtr("/params/response/mask", true), + AtJsonPtr("/params/response/payloadData", "aGVsbG8=")))); + + NetworkReporter::getInstance().reportWebSocketMessageSent( + requestId, "aGVsbG8=", true); + + // Step 6: Text message received + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.webSocketFrameReceived"), + AtJsonPtr("/params/requestId", requestId), + AtJsonPtr("/params/timestamp", Gt(0)), + AtJsonPtr("/params/response/opcode", 1), + AtJsonPtr("/params/response/mask", false), + AtJsonPtr("/params/response/payloadData", R"({"type": "pong"})")))); + + NetworkReporter::getInstance().reportWebSocketMessageReceived( + requestId, R"({"type": "pong"})", false); + + // Step 7: WebSocket closed + this->expectMessageFromPage(JsonParsed(AllOf( + AtJsonPtr("/method", "Network.webSocketClosed"), + AtJsonPtr("/params/requestId", requestId), + AtJsonPtr("/params/timestamp", Gt(0))))); + + NetworkReporter::getInstance().reportWebSocketClosed(requestId); + + this->expectMessageFromPage(JsonEq(R"({ + "id": 2, + "result": {} + })")); + this->toPage_->sendMessage(R"({ + "id": 2, + "method": "Network.disable" + })"); +} + TEST_P(NetworkReporterTest, testNetworkEventsWhenDisabled) { EXPECT_FALSE(NetworkReporter::getInstance().isDebuggingEnabled()); @@ -488,6 +596,18 @@ TEST_P(NetworkReporterTest, testNetworkEventsWhenDisabled) { "disabled-request", 512, 512); NetworkReporter::getInstance().reportResponseEnd("disabled-request", 1024); NetworkReporter::getInstance().reportRequestFailed("disabled-request", false); + + NetworkReporter::getInstance().reportWebSocketCreated( + "disabled-websocket", "wss://example.com/disabled"); + NetworkReporter::getInstance().reportWebSocketWillSendHandshakeRequest( + "disabled-websocket", {}); + NetworkReporter::getInstance().reportWebSocketHandshakeResponseReceived( + "disabled-websocket", 101, {}); + NetworkReporter::getInstance().reportWebSocketMessageSent( + "disabled-websocket", "hello", false); + NetworkReporter::getInstance().reportWebSocketMessageReceived( + "disabled-websocket", "world", false); + NetworkReporter::getInstance().reportWebSocketClosed("disabled-websocket"); } TEST_P(NetworkReporterTest, testRequestWillBeSentWithInitiator) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 8e65a9654b44..31d61b5cb247 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<6f2362e9844cab731ca01e4af563fde9>> */ /** @@ -266,6 +266,10 @@ bool ReactNativeFeatureFlags::fuseboxScreenshotCaptureEnabled() { return getAccessor().fuseboxScreenshotCaptureEnabled(); } +bool ReactNativeFeatureFlags::fuseboxWebSocketEventsEnabled() { + return getAccessor().fuseboxWebSocketEventsEnabled(); +} + bool ReactNativeFeatureFlags::optimizedAnimatedPropUpdates() { return getAccessor().optimizedAnimatedPropUpdates(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index a2ca86c275a7..344997745dac 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<4276ae19d55acb679c9e635122e813b3>> + * @generated SignedSource<> */ /** @@ -339,6 +339,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool fuseboxScreenshotCaptureEnabled(); + /** + * Enable reporting of WebSocket network events (`Network.webSocket*` CDP events) to the React Native DevTools CDP backend. Requires `fuseboxNetworkInspectionEnabled`. + */ + RN_EXPORT static bool fuseboxWebSocketEventsEnabled(); + /** * When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. On iOS, this passes AnimatedProps directly through the delegate chain and applies them via cloneProps, avoiding the folly::dynamic round-trip. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 041bc351eac9..eef175da896e 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<2aee1d5b6266189201bb6f5f6c0ceb4c>> + * @generated SignedSource<<4c6a965958d151f22574bc85a6714c11>> */ /** @@ -1109,6 +1109,24 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxScreenshotCaptureEnabled() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::fuseboxWebSocketEventsEnabled() { + auto flagValue = fuseboxWebSocketEventsEnabled_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(60, "fuseboxWebSocketEventsEnabled"); + + flagValue = currentProvider_->fuseboxWebSocketEventsEnabled(); + fuseboxWebSocketEventsEnabled_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::optimizedAnimatedPropUpdates() { auto flagValue = optimizedAnimatedPropUpdates_.load(); @@ -1118,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::optimizedAnimatedPropUpdates() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "optimizedAnimatedPropUpdates"); + markFlagAsAccessed(61, "optimizedAnimatedPropUpdates"); flagValue = currentProvider_->optimizedAnimatedPropUpdates(); optimizedAnimatedPropUpdates_ = flagValue; @@ -1136,7 +1154,7 @@ bool ReactNativeFeatureFlagsAccessor::overrideBySynchronousMountPropsAtMountingA // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(62, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -1154,7 +1172,7 @@ bool ReactNativeFeatureFlagsAccessor::perfIssuesEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(62, "perfIssuesEnabled"); + markFlagAsAccessed(63, "perfIssuesEnabled"); flagValue = currentProvider_->perfIssuesEnabled(); perfIssuesEnabled_ = flagValue; @@ -1172,7 +1190,7 @@ bool ReactNativeFeatureFlagsAccessor::perfMonitorV2Enabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(63, "perfMonitorV2Enabled"); + markFlagAsAccessed(64, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1190,7 +1208,7 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(64, "preparedTextCacheSize"); + markFlagAsAccessed(65, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1208,7 +1226,7 @@ bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(65, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(66, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1226,7 +1244,7 @@ bool ReactNativeFeatureFlagsAccessor::redBoxV2Android() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(66, "redBoxV2Android"); + markFlagAsAccessed(67, "redBoxV2Android"); flagValue = currentProvider_->redBoxV2Android(); redBoxV2Android_ = flagValue; @@ -1244,7 +1262,7 @@ bool ReactNativeFeatureFlagsAccessor::redBoxV2IOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "redBoxV2IOS"); + markFlagAsAccessed(68, "redBoxV2IOS"); flagValue = currentProvider_->redBoxV2IOS(); redBoxV2IOS_ = flagValue; @@ -1262,7 +1280,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldPressibilityUseW3CPointerEventsForHo // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(68, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(69, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1280,7 +1298,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldTriggerResponderTransferOnScrollAndr // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(69, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(70, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1298,7 +1316,7 @@ bool ReactNativeFeatureFlagsAccessor::skipActivityIdentityAssertionOnHostPause() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(70, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(71, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1316,7 +1334,7 @@ bool ReactNativeFeatureFlagsAccessor::syncAndroidClipBoundsWithOverflow() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(71, "syncAndroidClipBoundsWithOverflow"); + markFlagAsAccessed(72, "syncAndroidClipBoundsWithOverflow"); flagValue = currentProvider_->syncAndroidClipBoundsWithOverflow(); syncAndroidClipBoundsWithOverflow_ = flagValue; @@ -1334,7 +1352,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(72, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(73, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1352,7 +1370,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(73, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(74, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1370,7 +1388,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommitT // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(74, "updateRuntimeShadowNodeReferencesOnCommitThread"); + markFlagAsAccessed(75, "updateRuntimeShadowNodeReferencesOnCommitThread"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommitThread(); updateRuntimeShadowNodeReferencesOnCommitThread_ = flagValue; @@ -1388,7 +1406,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(75, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(76, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1406,7 +1424,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(76, "useFabricInterop"); + markFlagAsAccessed(77, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1424,7 +1442,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(77, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(78, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1442,7 +1460,7 @@ bool ReactNativeFeatureFlagsAccessor::useNestedScrollViewAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(78, "useNestedScrollViewAndroid"); + markFlagAsAccessed(79, "useNestedScrollViewAndroid"); flagValue = currentProvider_->useNestedScrollViewAndroid(); useNestedScrollViewAndroid_ = flagValue; @@ -1460,7 +1478,7 @@ bool ReactNativeFeatureFlagsAccessor::useSharedAnimatedBackend() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(79, "useSharedAnimatedBackend"); + markFlagAsAccessed(80, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1478,7 +1496,7 @@ bool ReactNativeFeatureFlagsAccessor::useTraitHiddenOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(80, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(81, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1496,7 +1514,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(81, "useTurboModuleInterop"); + markFlagAsAccessed(82, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1514,7 +1532,7 @@ double ReactNativeFeatureFlagsAccessor::viewCullingOutsetRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(82, "viewCullingOutsetRatio"); + markFlagAsAccessed(83, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1532,7 +1550,7 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(83, "viewTransitionEnabled"); + markFlagAsAccessed(84, "viewTransitionEnabled"); flagValue = currentProvider_->viewTransitionEnabled(); viewTransitionEnabled_ = flagValue; @@ -1550,7 +1568,7 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionUseHardwareBitmapAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(84, "viewTransitionUseHardwareBitmapAndroid"); + markFlagAsAccessed(85, "viewTransitionUseHardwareBitmapAndroid"); flagValue = currentProvider_->viewTransitionUseHardwareBitmapAndroid(); viewTransitionUseHardwareBitmapAndroid_ = flagValue; @@ -1568,7 +1586,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(85, "virtualViewPrerenderRatio"); + markFlagAsAccessed(86, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index cf3369c9aa0f..6a67566e46cc 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -92,6 +92,7 @@ class ReactNativeFeatureFlagsAccessor { bool fuseboxFrameRecordingEnabled(); bool fuseboxNetworkInspectionEnabled(); bool fuseboxScreenshotCaptureEnabled(); + bool fuseboxWebSocketEventsEnabled(); bool optimizedAnimatedPropUpdates(); bool overrideBySynchronousMountPropsAtMountingAndroid(); bool perfIssuesEnabled(); @@ -129,7 +130,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 86> accessedFeatureFlags_; + std::array, 87> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -191,6 +192,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> fuseboxFrameRecordingEnabled_; std::atomic> fuseboxNetworkInspectionEnabled_; std::atomic> fuseboxScreenshotCaptureEnabled_; + std::atomic> fuseboxWebSocketEventsEnabled_; std::atomic> optimizedAnimatedPropUpdates_; std::atomic> overrideBySynchronousMountPropsAtMountingAndroid_; std::atomic> perfIssuesEnabled_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index ca9ab82ce1ec..aa36d5cfa7d5 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<2c09412cdf8edaf4f7d6d90a446ce6fa>> + * @generated SignedSource<<2e19fd4b007d0ae7397babcc605b5783>> */ /** @@ -267,6 +267,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool fuseboxWebSocketEventsEnabled() override { + return false; + } + bool optimizedAnimatedPropUpdates() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 3fb33d362dac..940811f0174c 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -585,6 +585,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::fuseboxScreenshotCaptureEnabled(); } + bool fuseboxWebSocketEventsEnabled() override { + auto value = values_["fuseboxWebSocketEventsEnabled"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::fuseboxWebSocketEventsEnabled(); + } + bool optimizedAnimatedPropUpdates() override { auto value = values_["optimizedAnimatedPropUpdates"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 93d5f232ac49..d80700290554 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<14cafde581fa0fb72ffebb5e192040e8>> + * @generated SignedSource<<2d4757d38664169286dacd779b0e267a>> */ /** @@ -85,6 +85,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool fuseboxFrameRecordingEnabled() = 0; virtual bool fuseboxNetworkInspectionEnabled() = 0; virtual bool fuseboxScreenshotCaptureEnabled() = 0; + virtual bool fuseboxWebSocketEventsEnabled() = 0; virtual bool optimizedAnimatedPropUpdates() = 0; virtual bool overrideBySynchronousMountPropsAtMountingAndroid() = 0; virtual bool perfIssuesEnabled() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 8e627b8ace38..2490fdbd5be5 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<1e26f8dc1f474475c0a4d6be79aedb24>> + * @generated SignedSource<<2a0a030bafa14c6f20691f029703a642>> */ /** @@ -344,6 +344,11 @@ bool NativeReactNativeFeatureFlags::fuseboxScreenshotCaptureEnabled( return ReactNativeFeatureFlags::fuseboxScreenshotCaptureEnabled(); } +bool NativeReactNativeFeatureFlags::fuseboxWebSocketEventsEnabled( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::fuseboxWebSocketEventsEnabled(); +} + bool NativeReactNativeFeatureFlags::optimizedAnimatedPropUpdates( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::optimizedAnimatedPropUpdates(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index d0acb696863d..7d07e49ad1ba 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<86a4b76371a4005f7083c3806d53cc87>> + * @generated SignedSource<<206f5586c0ea22387a48a484d6d96f54>> */ /** @@ -156,6 +156,8 @@ class NativeReactNativeFeatureFlags bool fuseboxScreenshotCaptureEnabled(jsi::Runtime& runtime); + bool fuseboxWebSocketEventsEnabled(jsi::Runtime& runtime); + bool optimizedAnimatedPropUpdates(jsi::Runtime& runtime); bool overrideBySynchronousMountPropsAtMountingAndroid(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/networking/NetworkReporter.cpp b/packages/react-native/ReactCommon/react/networking/NetworkReporter.cpp index 458afb1fa6f7..d74c2e1e291a 100644 --- a/packages/react-native/ReactCommon/react/networking/NetworkReporter.cpp +++ b/packages/react-native/ReactCommon/react/networking/NetworkReporter.cpp @@ -241,6 +241,68 @@ void NetworkReporter::reportRequestFailed( #endif } +void NetworkReporter::reportWebSocketCreated( + const std::string& requestId, + const std::string& url) const { +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debugger enabled: CDP event handling + jsinspector_modern::NetworkHandler::getInstance().onWebSocketCreated( + requestId, url); +#endif +} + +void NetworkReporter::reportWebSocketWillSendHandshakeRequest( + const std::string& requestId, + const Headers& headers) const { +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debugger enabled: CDP event handling + jsinspector_modern::NetworkHandler::getInstance() + .onWebSocketWillSendHandshakeRequest(requestId, headers); +#endif +} + +void NetworkReporter::reportWebSocketHandshakeResponseReceived( + const std::string& requestId, + uint16_t statusCode, + const Headers& headers) const { +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debugger enabled: CDP event handling + jsinspector_modern::NetworkHandler::getInstance() + .onWebSocketHandshakeResponseReceived(requestId, statusCode, headers); +#endif +} + +void NetworkReporter::reportWebSocketMessageSent( + const std::string& requestId, + const std::string& payloadData, + bool isBinary) const { +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debugger enabled: CDP event handling + jsinspector_modern::NetworkHandler::getInstance().onWebSocketFrameSent( + requestId, payloadData, isBinary); +#endif +} + +void NetworkReporter::reportWebSocketMessageReceived( + const std::string& requestId, + const std::string& payloadData, + bool isBinary) const { +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debugger enabled: CDP event handling + jsinspector_modern::NetworkHandler::getInstance().onWebSocketFrameReceived( + requestId, payloadData, isBinary); +#endif +} + +void NetworkReporter::reportWebSocketClosed( + const std::string& requestId) const { +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debugger enabled: CDP event handling + jsinspector_modern::NetworkHandler::getInstance().onWebSocketClosed( + requestId); +#endif +} + void NetworkReporter::storeResponseBody( const std::string& requestId, std::string_view body, diff --git a/packages/react-native/ReactCommon/react/networking/NetworkReporter.h b/packages/react-native/ReactCommon/react/networking/NetworkReporter.h index e76adbf6afe6..841a592c1c6d 100644 --- a/packages/react-native/ReactCommon/react/networking/NetworkReporter.h +++ b/packages/react-native/ReactCommon/react/networking/NetworkReporter.h @@ -122,6 +122,66 @@ class NetworkReporter { */ void reportRequestFailed(const std::string &requestId, bool cancelled) const; + /** + * Report that a WebSocket connection is about to be created. + * + * Corresponds to `Network.webSocketCreated` in CDP. WebSocket events are + * reported to CDP only and have no Web Performance counterpart — in a + * production (non dev or profiling) build, this method is a no-op. + */ + void reportWebSocketCreated(const std::string &requestId, const std::string &url) const; + + /** + * Report that a WebSocket handshake (HTTP upgrade) request is about to be + * sent, along with its final request headers. + * + * Corresponds to `Network.webSocketWillSendHandshakeRequest` in CDP. In a + * production (non dev or profiling) build, this method is a no-op. + */ + void reportWebSocketWillSendHandshakeRequest(const std::string &requestId, const Headers &headers) const; + + /** + * Report that a WebSocket handshake response was received and the + * connection is established. + * + * Corresponds to `Network.webSocketHandshakeResponseReceived` in CDP. In a + * production (non dev or profiling) build, this method is a no-op. + */ + void reportWebSocketHandshakeResponseReceived( + const std::string &requestId, + uint16_t statusCode, + const Headers &headers) const; + + /** + * Report a WebSocket message sent over an open connection. `payloadData` + * must be a UTF-8 string for text messages, or a base64-encoded string for + * binary messages (`isBinary`). + * + * Corresponds to `Network.webSocketFrameSent` in CDP. In a production + * (non dev or profiling) build, this method is a no-op. + */ + void reportWebSocketMessageSent(const std::string &requestId, const std::string &payloadData, bool isBinary) const; + + /** + * Report a WebSocket message received over an open connection. + * `payloadData` must be a UTF-8 string for text messages, or a + * base64-encoded string for binary messages (`isBinary`). + * + * Corresponds to `Network.webSocketFrameReceived` in CDP. In a production + * (non dev or profiling) build, this method is a no-op. + */ + void reportWebSocketMessageReceived(const std::string &requestId, const std::string &payloadData, bool isBinary) + const; + + /** + * Report that a WebSocket connection was closed, whether cleanly or due to + * an error. + * + * Corresponds to `Network.webSocketClosed` in CDP. In a production (non dev + * or profiling) build, this method is a no-op. + */ + void reportWebSocketClosed(const std::string &requestId) const; + /** * Store the fetched response body for a text or image network response. * These may be retrieved by CDP clients to to render a response preview via diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 54ffc48eefc9..1f903b9c290d 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -690,6 +690,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'experimental', }, + fuseboxWebSocketEventsEnabled: { + defaultValue: false, + metadata: { + dateAdded: '2026-07-11', + description: + 'Enable reporting of WebSocket network events (`Network.webSocket*` CDP events) to the React Native DevTools CDP backend. Requires `fuseboxNetworkInspectionEnabled`.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, optimizedAnimatedPropUpdates: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index a9c690414348..b9d7e539d61b 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<71e307d568421d49e3943f58c4fcb163>> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -109,6 +109,7 @@ export type ReactNativeFeatureFlags = Readonly<{ fuseboxFrameRecordingEnabled: Getter, fuseboxNetworkInspectionEnabled: Getter, fuseboxScreenshotCaptureEnabled: Getter, + fuseboxWebSocketEventsEnabled: Getter, optimizedAnimatedPropUpdates: Getter, overrideBySynchronousMountPropsAtMountingAndroid: Getter, perfIssuesEnabled: Getter, @@ -451,6 +452,10 @@ export const fuseboxNetworkInspectionEnabled: Getter = createNativeFlag * Enable Page.captureScreenshot CDP method support in the React Native DevTools CDP backend. This flag is global and should not be changed across React Host lifetimes. */ export const fuseboxScreenshotCaptureEnabled: Getter = createNativeFlagGetter('fuseboxScreenshotCaptureEnabled', false); +/** + * Enable reporting of WebSocket network events (`Network.webSocket*` CDP events) to the React Native DevTools CDP backend. Requires `fuseboxNetworkInspectionEnabled`. + */ +export const fuseboxWebSocketEventsEnabled: Getter = createNativeFlagGetter('fuseboxWebSocketEventsEnabled', false); /** * When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. On iOS, this passes AnimatedProps directly through the delegate chain and applies them via cloneProps, avoiding the folly::dynamic round-trip. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index a4145d59c8c2..67ae1cb7d8a1 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<68ccb8d73a8f29fbf88f15a183bd910b>> + * @generated SignedSource<<4dd4bbc51723d64c94a8e7ba1bf67bf1>> * @flow strict * @noformat */ @@ -85,6 +85,7 @@ export interface Spec extends TurboModule { readonly fuseboxFrameRecordingEnabled?: () => boolean; readonly fuseboxNetworkInspectionEnabled?: () => boolean; readonly fuseboxScreenshotCaptureEnabled?: () => boolean; + readonly fuseboxWebSocketEventsEnabled?: () => boolean; readonly optimizedAnimatedPropUpdates?: () => boolean; readonly overrideBySynchronousMountPropsAtMountingAndroid?: () => boolean; readonly perfIssuesEnabled?: () => boolean; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 0aaf2efc5caa..a11e7ec3c52f 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -3936,6 +3936,12 @@ class facebook::react::NetworkReporter { public void reportRequestStart(const std::string& requestId, const facebook::react::RequestInfo& requestInfo, int encodedDataLength, const std::optional& redirectResponse); public void reportResponseEnd(const std::string& requestId, int encodedDataLength); public void reportResponseStart(const std::string& requestId, const facebook::react::ResponseInfo& responseInfo, int encodedDataLength); + public void reportWebSocketClosed(const std::string& requestId) const; + public void reportWebSocketCreated(const std::string& requestId, const std::string& url) const; + public void reportWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t statusCode, const facebook::react::Headers& headers) const; + public void reportWebSocketMessageReceived(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketMessageSent(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::Headers& headers) const; public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -10819,6 +10825,12 @@ class facebook::react::jsinspector_modern::NetworkHandler { public void onRequestWillBeSent(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Request& request, const std::optional& redirectResponse); public void onRequestWillBeSentExtraInfo(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void onResponseReceived(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Response& response); + public void onWebSocketClosed(const std::string& requestId); + public void onWebSocketCreated(const std::string& requestId, const std::string& url); + public void onWebSocketFrameReceived(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketFrameSent(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t status, const facebook::react::jsinspector_modern::Headers& headers); + public void onWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void recordRequestInitiatorStack(const std::string& requestId, folly::dynamic stackTrace); public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -11538,6 +11550,56 @@ struct facebook::react::jsinspector_modern::cdp::network::ResponseReceivedParams public std::string type; } +struct facebook::react::jsinspector_modern::cdp::network::WebSocketClosedParams { + public double timestamp; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketCreatedParams { + public folly::dynamic initiator; + public folly::dynamic toDynamic() const; + public std::string requestId; + public std::string url; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrame { + public bool mask; + public folly::dynamic toDynamic() const; + public int opcode; + public std::string payloadData; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrameParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketFrame response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketHandshakeResponseReceivedParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketResponse response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketResponse { + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public static facebook::react::jsinspector_modern::cdp::network::WebSocketResponse fromInputParams(uint16_t status, const facebook::react::jsinspector_modern::cdp::network::Headers& headers); + public std::string statusText; + public uint16_t status; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketWillSendHandshakeRequestParams { + public double timestamp; + public double wallTime; + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINED_BY; constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINS; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index be2958293dad..592a14fc9275 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -3790,6 +3790,12 @@ class facebook::react::NetworkReporter { public void reportRequestStart(const std::string& requestId, const facebook::react::RequestInfo& requestInfo, int encodedDataLength, const std::optional& redirectResponse); public void reportResponseEnd(const std::string& requestId, int encodedDataLength); public void reportResponseStart(const std::string& requestId, const facebook::react::ResponseInfo& responseInfo, int encodedDataLength); + public void reportWebSocketClosed(const std::string& requestId) const; + public void reportWebSocketCreated(const std::string& requestId, const std::string& url) const; + public void reportWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t statusCode, const facebook::react::Headers& headers) const; + public void reportWebSocketMessageReceived(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketMessageSent(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::Headers& headers) const; public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -10442,6 +10448,12 @@ class facebook::react::jsinspector_modern::NetworkHandler { public void onRequestWillBeSent(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Request& request, const std::optional& redirectResponse); public void onRequestWillBeSentExtraInfo(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void onResponseReceived(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Response& response); + public void onWebSocketClosed(const std::string& requestId); + public void onWebSocketCreated(const std::string& requestId, const std::string& url); + public void onWebSocketFrameReceived(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketFrameSent(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t status, const facebook::react::jsinspector_modern::Headers& headers); + public void onWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void recordRequestInitiatorStack(const std::string& requestId, folly::dynamic stackTrace); public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -11161,6 +11173,56 @@ struct facebook::react::jsinspector_modern::cdp::network::ResponseReceivedParams public std::string type; } +struct facebook::react::jsinspector_modern::cdp::network::WebSocketClosedParams { + public double timestamp; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketCreatedParams { + public folly::dynamic initiator; + public folly::dynamic toDynamic() const; + public std::string requestId; + public std::string url; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrame { + public bool mask; + public folly::dynamic toDynamic() const; + public int opcode; + public std::string payloadData; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrameParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketFrame response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketHandshakeResponseReceivedParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketResponse response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketResponse { + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public static facebook::react::jsinspector_modern::cdp::network::WebSocketResponse fromInputParams(uint16_t status, const facebook::react::jsinspector_modern::cdp::network::Headers& headers); + public std::string statusText; + public uint16_t status; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketWillSendHandshakeRequestParams { + public double timestamp; + public double wallTime; + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINED_BY; constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINS; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index faf7b0b6db37..33e10869b39d 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -3933,6 +3933,12 @@ class facebook::react::NetworkReporter { public void reportRequestStart(const std::string& requestId, const facebook::react::RequestInfo& requestInfo, int encodedDataLength, const std::optional& redirectResponse); public void reportResponseEnd(const std::string& requestId, int encodedDataLength); public void reportResponseStart(const std::string& requestId, const facebook::react::ResponseInfo& responseInfo, int encodedDataLength); + public void reportWebSocketClosed(const std::string& requestId) const; + public void reportWebSocketCreated(const std::string& requestId, const std::string& url) const; + public void reportWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t statusCode, const facebook::react::Headers& headers) const; + public void reportWebSocketMessageReceived(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketMessageSent(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::Headers& headers) const; public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -10672,6 +10678,12 @@ class facebook::react::jsinspector_modern::NetworkHandler { public void onRequestWillBeSent(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Request& request, const std::optional& redirectResponse); public void onRequestWillBeSentExtraInfo(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void onResponseReceived(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Response& response); + public void onWebSocketClosed(const std::string& requestId); + public void onWebSocketCreated(const std::string& requestId, const std::string& url); + public void onWebSocketFrameReceived(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketFrameSent(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t status, const facebook::react::jsinspector_modern::Headers& headers); + public void onWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void recordRequestInitiatorStack(const std::string& requestId, folly::dynamic stackTrace); public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -11391,6 +11403,56 @@ struct facebook::react::jsinspector_modern::cdp::network::ResponseReceivedParams public std::string type; } +struct facebook::react::jsinspector_modern::cdp::network::WebSocketClosedParams { + public double timestamp; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketCreatedParams { + public folly::dynamic initiator; + public folly::dynamic toDynamic() const; + public std::string requestId; + public std::string url; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrame { + public bool mask; + public folly::dynamic toDynamic() const; + public int opcode; + public std::string payloadData; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrameParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketFrame response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketHandshakeResponseReceivedParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketResponse response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketResponse { + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public static facebook::react::jsinspector_modern::cdp::network::WebSocketResponse fromInputParams(uint16_t status, const facebook::react::jsinspector_modern::cdp::network::Headers& headers); + public std::string statusText; + public uint16_t status; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketWillSendHandshakeRequestParams { + public double timestamp; + public double wallTime; + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINED_BY; constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINS; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 2ec052291919..de18e199e774 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -1254,6 +1254,15 @@ interface RCTInspectorUtils : public NSObject { public virtual static CommonHostMetadata* getHostMetadata(); } +interface RCTInspectorWebSocketReporter : public NSObject { + public virtual static void reportHandshakeResponseReceived:httpMessage:(_Nullable NSString* requestId, _Nullable CFHTTPMessageRef httpMessage); + public virtual static void reportMessageReceived:message:(_Nullable NSString* requestId, _Nullable id message); + public virtual static void reportMessageSent:message:(_Nullable NSString* requestId, _Nullable id message); + public virtual static void reportWebSocketClosed:(_Nullable NSString* requestId); + public virtual static void reportWebSocketCreated:url:(_Nullable NSString* requestId, NSURL* url); + public virtual static void reportWillSendHandshakeRequest:request:(_Nullable NSString* requestId, NSURLRequest* request); +} + interface RCTInstance : public NSObject { public @property (strong, readonly) RCTSurfacePresenter* surfacePresenter; public virtual instancetype initWithDelegate:jsRuntimeFactory:bundleManager:turboModuleManagerDelegate:moduleRegistry:parentInspectorTarget:launchOptions:(id delegate, std::shared_ptr jsRuntimeFactory, RCTBundleManager* bundleManager, id turboModuleManagerDelegate, RCTModuleRegistry* moduleRegistry, facebook::react::jsinspector_modern::HostTarget* parentInspectorTarget, _Nullable NSDictionary* launchOptions); @@ -6111,6 +6120,12 @@ class facebook::react::NetworkReporter { public void reportRequestStart(const std::string& requestId, const facebook::react::RequestInfo& requestInfo, int encodedDataLength, const std::optional& redirectResponse); public void reportResponseEnd(const std::string& requestId, int encodedDataLength); public void reportResponseStart(const std::string& requestId, const facebook::react::ResponseInfo& responseInfo, int encodedDataLength); + public void reportWebSocketClosed(const std::string& requestId) const; + public void reportWebSocketCreated(const std::string& requestId, const std::string& url) const; + public void reportWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t statusCode, const facebook::react::Headers& headers) const; + public void reportWebSocketMessageReceived(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketMessageSent(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::Headers& headers) const; public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -12666,6 +12681,12 @@ class facebook::react::jsinspector_modern::NetworkHandler { public void onRequestWillBeSent(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Request& request, const std::optional& redirectResponse); public void onRequestWillBeSentExtraInfo(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void onResponseReceived(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Response& response); + public void onWebSocketClosed(const std::string& requestId); + public void onWebSocketCreated(const std::string& requestId, const std::string& url); + public void onWebSocketFrameReceived(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketFrameSent(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t status, const facebook::react::jsinspector_modern::Headers& headers); + public void onWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void recordRequestInitiatorStack(const std::string& requestId, folly::dynamic stackTrace); public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -13372,6 +13393,56 @@ struct facebook::react::jsinspector_modern::cdp::network::ResponseReceivedParams public std::string type; } +struct facebook::react::jsinspector_modern::cdp::network::WebSocketClosedParams { + public double timestamp; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketCreatedParams { + public folly::dynamic initiator; + public folly::dynamic toDynamic() const; + public std::string requestId; + public std::string url; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrame { + public bool mask; + public folly::dynamic toDynamic() const; + public int opcode; + public std::string payloadData; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrameParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketFrame response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketHandshakeResponseReceivedParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketResponse response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketResponse { + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public static facebook::react::jsinspector_modern::cdp::network::WebSocketResponse fromInputParams(uint16_t status, const facebook::react::jsinspector_modern::cdp::network::Headers& headers); + public std::string statusText; + public uint16_t status; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketWillSendHandshakeRequestParams { + public double timestamp; + public double wallTime; + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINED_BY; constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINS; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 319fba1e0d52..3d2428683ae3 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -1252,6 +1252,15 @@ interface RCTInspectorUtils : public NSObject { public virtual static CommonHostMetadata* getHostMetadata(); } +interface RCTInspectorWebSocketReporter : public NSObject { + public virtual static void reportHandshakeResponseReceived:httpMessage:(_Nullable NSString* requestId, _Nullable CFHTTPMessageRef httpMessage); + public virtual static void reportMessageReceived:message:(_Nullable NSString* requestId, _Nullable id message); + public virtual static void reportMessageSent:message:(_Nullable NSString* requestId, _Nullable id message); + public virtual static void reportWebSocketClosed:(_Nullable NSString* requestId); + public virtual static void reportWebSocketCreated:url:(_Nullable NSString* requestId, NSURL* url); + public virtual static void reportWillSendHandshakeRequest:request:(_Nullable NSString* requestId, NSURLRequest* request); +} + interface RCTInstance : public NSObject { public @property (strong, readonly) RCTSurfacePresenter* surfacePresenter; public virtual instancetype initWithDelegate:jsRuntimeFactory:bundleManager:turboModuleManagerDelegate:moduleRegistry:parentInspectorTarget:launchOptions:(id delegate, std::shared_ptr jsRuntimeFactory, RCTBundleManager* bundleManager, id turboModuleManagerDelegate, RCTModuleRegistry* moduleRegistry, facebook::react::jsinspector_modern::HostTarget* parentInspectorTarget, _Nullable NSDictionary* launchOptions); @@ -5993,6 +6002,12 @@ class facebook::react::NetworkReporter { public void reportRequestStart(const std::string& requestId, const facebook::react::RequestInfo& requestInfo, int encodedDataLength, const std::optional& redirectResponse); public void reportResponseEnd(const std::string& requestId, int encodedDataLength); public void reportResponseStart(const std::string& requestId, const facebook::react::ResponseInfo& responseInfo, int encodedDataLength); + public void reportWebSocketClosed(const std::string& requestId) const; + public void reportWebSocketCreated(const std::string& requestId, const std::string& url) const; + public void reportWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t statusCode, const facebook::react::Headers& headers) const; + public void reportWebSocketMessageReceived(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketMessageSent(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::Headers& headers) const; public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -12351,6 +12366,12 @@ class facebook::react::jsinspector_modern::NetworkHandler { public void onRequestWillBeSent(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Request& request, const std::optional& redirectResponse); public void onRequestWillBeSentExtraInfo(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void onResponseReceived(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Response& response); + public void onWebSocketClosed(const std::string& requestId); + public void onWebSocketCreated(const std::string& requestId, const std::string& url); + public void onWebSocketFrameReceived(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketFrameSent(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t status, const facebook::react::jsinspector_modern::Headers& headers); + public void onWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void recordRequestInitiatorStack(const std::string& requestId, folly::dynamic stackTrace); public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -13057,6 +13078,56 @@ struct facebook::react::jsinspector_modern::cdp::network::ResponseReceivedParams public std::string type; } +struct facebook::react::jsinspector_modern::cdp::network::WebSocketClosedParams { + public double timestamp; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketCreatedParams { + public folly::dynamic initiator; + public folly::dynamic toDynamic() const; + public std::string requestId; + public std::string url; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrame { + public bool mask; + public folly::dynamic toDynamic() const; + public int opcode; + public std::string payloadData; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrameParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketFrame response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketHandshakeResponseReceivedParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketResponse response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketResponse { + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public static facebook::react::jsinspector_modern::cdp::network::WebSocketResponse fromInputParams(uint16_t status, const facebook::react::jsinspector_modern::cdp::network::Headers& headers); + public std::string statusText; + public uint16_t status; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketWillSendHandshakeRequestParams { + public double timestamp; + public double wallTime; + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINED_BY; constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINS; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 3ec823148f22..a24cb26ab17a 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -1254,6 +1254,15 @@ interface RCTInspectorUtils : public NSObject { public virtual static CommonHostMetadata* getHostMetadata(); } +interface RCTInspectorWebSocketReporter : public NSObject { + public virtual static void reportHandshakeResponseReceived:httpMessage:(_Nullable NSString* requestId, _Nullable CFHTTPMessageRef httpMessage); + public virtual static void reportMessageReceived:message:(_Nullable NSString* requestId, _Nullable id message); + public virtual static void reportMessageSent:message:(_Nullable NSString* requestId, _Nullable id message); + public virtual static void reportWebSocketClosed:(_Nullable NSString* requestId); + public virtual static void reportWebSocketCreated:url:(_Nullable NSString* requestId, NSURL* url); + public virtual static void reportWillSendHandshakeRequest:request:(_Nullable NSString* requestId, NSURLRequest* request); +} + interface RCTInstance : public NSObject { public @property (strong, readonly) RCTSurfacePresenter* surfacePresenter; public virtual instancetype initWithDelegate:jsRuntimeFactory:bundleManager:turboModuleManagerDelegate:moduleRegistry:parentInspectorTarget:launchOptions:(id delegate, std::shared_ptr jsRuntimeFactory, RCTBundleManager* bundleManager, id turboModuleManagerDelegate, RCTModuleRegistry* moduleRegistry, facebook::react::jsinspector_modern::HostTarget* parentInspectorTarget, _Nullable NSDictionary* launchOptions); @@ -6108,6 +6117,12 @@ class facebook::react::NetworkReporter { public void reportRequestStart(const std::string& requestId, const facebook::react::RequestInfo& requestInfo, int encodedDataLength, const std::optional& redirectResponse); public void reportResponseEnd(const std::string& requestId, int encodedDataLength); public void reportResponseStart(const std::string& requestId, const facebook::react::ResponseInfo& responseInfo, int encodedDataLength); + public void reportWebSocketClosed(const std::string& requestId) const; + public void reportWebSocketCreated(const std::string& requestId, const std::string& url) const; + public void reportWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t statusCode, const facebook::react::Headers& headers) const; + public void reportWebSocketMessageReceived(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketMessageSent(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::Headers& headers) const; public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -12529,6 +12544,12 @@ class facebook::react::jsinspector_modern::NetworkHandler { public void onRequestWillBeSent(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Request& request, const std::optional& redirectResponse); public void onRequestWillBeSentExtraInfo(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void onResponseReceived(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Response& response); + public void onWebSocketClosed(const std::string& requestId); + public void onWebSocketCreated(const std::string& requestId, const std::string& url); + public void onWebSocketFrameReceived(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketFrameSent(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t status, const facebook::react::jsinspector_modern::Headers& headers); + public void onWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void recordRequestInitiatorStack(const std::string& requestId, folly::dynamic stackTrace); public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -13235,6 +13256,56 @@ struct facebook::react::jsinspector_modern::cdp::network::ResponseReceivedParams public std::string type; } +struct facebook::react::jsinspector_modern::cdp::network::WebSocketClosedParams { + public double timestamp; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketCreatedParams { + public folly::dynamic initiator; + public folly::dynamic toDynamic() const; + public std::string requestId; + public std::string url; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrame { + public bool mask; + public folly::dynamic toDynamic() const; + public int opcode; + public std::string payloadData; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrameParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketFrame response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketHandshakeResponseReceivedParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketResponse response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketResponse { + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public static facebook::react::jsinspector_modern::cdp::network::WebSocketResponse fromInputParams(uint16_t status, const facebook::react::jsinspector_modern::cdp::network::Headers& headers); + public std::string statusText; + public uint16_t status; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketWillSendHandshakeRequestParams { + public double timestamp; + public double wallTime; + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINED_BY; constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINS; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 64301436eeda..56d20f0b002e 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -2573,6 +2573,12 @@ class facebook::react::NetworkReporter { public void reportRequestStart(const std::string& requestId, const facebook::react::RequestInfo& requestInfo, int encodedDataLength, const std::optional& redirectResponse); public void reportResponseEnd(const std::string& requestId, int encodedDataLength); public void reportResponseStart(const std::string& requestId, const facebook::react::ResponseInfo& responseInfo, int encodedDataLength); + public void reportWebSocketClosed(const std::string& requestId) const; + public void reportWebSocketCreated(const std::string& requestId, const std::string& url) const; + public void reportWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t statusCode, const facebook::react::Headers& headers) const; + public void reportWebSocketMessageReceived(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketMessageSent(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::Headers& headers) const; public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -7830,6 +7836,12 @@ class facebook::react::jsinspector_modern::NetworkHandler { public void onRequestWillBeSent(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Request& request, const std::optional& redirectResponse); public void onRequestWillBeSentExtraInfo(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void onResponseReceived(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Response& response); + public void onWebSocketClosed(const std::string& requestId); + public void onWebSocketCreated(const std::string& requestId, const std::string& url); + public void onWebSocketFrameReceived(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketFrameSent(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t status, const facebook::react::jsinspector_modern::Headers& headers); + public void onWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void recordRequestInitiatorStack(const std::string& requestId, folly::dynamic stackTrace); public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -8536,6 +8548,56 @@ struct facebook::react::jsinspector_modern::cdp::network::ResponseReceivedParams public std::string type; } +struct facebook::react::jsinspector_modern::cdp::network::WebSocketClosedParams { + public double timestamp; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketCreatedParams { + public folly::dynamic initiator; + public folly::dynamic toDynamic() const; + public std::string requestId; + public std::string url; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrame { + public bool mask; + public folly::dynamic toDynamic() const; + public int opcode; + public std::string payloadData; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrameParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketFrame response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketHandshakeResponseReceivedParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketResponse response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketResponse { + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public static facebook::react::jsinspector_modern::cdp::network::WebSocketResponse fromInputParams(uint16_t status, const facebook::react::jsinspector_modern::cdp::network::Headers& headers); + public std::string statusText; + public uint16_t status; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketWillSendHandshakeRequestParams { + public double timestamp; + public double wallTime; + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINED_BY; constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINS; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index eda51abd3696..d898e370e8fa 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -2467,6 +2467,12 @@ class facebook::react::NetworkReporter { public void reportRequestStart(const std::string& requestId, const facebook::react::RequestInfo& requestInfo, int encodedDataLength, const std::optional& redirectResponse); public void reportResponseEnd(const std::string& requestId, int encodedDataLength); public void reportResponseStart(const std::string& requestId, const facebook::react::ResponseInfo& responseInfo, int encodedDataLength); + public void reportWebSocketClosed(const std::string& requestId) const; + public void reportWebSocketCreated(const std::string& requestId, const std::string& url) const; + public void reportWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t statusCode, const facebook::react::Headers& headers) const; + public void reportWebSocketMessageReceived(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketMessageSent(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::Headers& headers) const; public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -7655,6 +7661,12 @@ class facebook::react::jsinspector_modern::NetworkHandler { public void onRequestWillBeSent(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Request& request, const std::optional& redirectResponse); public void onRequestWillBeSentExtraInfo(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void onResponseReceived(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Response& response); + public void onWebSocketClosed(const std::string& requestId); + public void onWebSocketCreated(const std::string& requestId, const std::string& url); + public void onWebSocketFrameReceived(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketFrameSent(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t status, const facebook::react::jsinspector_modern::Headers& headers); + public void onWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void recordRequestInitiatorStack(const std::string& requestId, folly::dynamic stackTrace); public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -8361,6 +8373,56 @@ struct facebook::react::jsinspector_modern::cdp::network::ResponseReceivedParams public std::string type; } +struct facebook::react::jsinspector_modern::cdp::network::WebSocketClosedParams { + public double timestamp; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketCreatedParams { + public folly::dynamic initiator; + public folly::dynamic toDynamic() const; + public std::string requestId; + public std::string url; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrame { + public bool mask; + public folly::dynamic toDynamic() const; + public int opcode; + public std::string payloadData; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrameParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketFrame response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketHandshakeResponseReceivedParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketResponse response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketResponse { + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public static facebook::react::jsinspector_modern::cdp::network::WebSocketResponse fromInputParams(uint16_t status, const facebook::react::jsinspector_modern::cdp::network::Headers& headers); + public std::string statusText; + public uint16_t status; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketWillSendHandshakeRequestParams { + public double timestamp; + public double wallTime; + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINED_BY; constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINS; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 3c9c5e8ab249..d2eb086c932c 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -2570,6 +2570,12 @@ class facebook::react::NetworkReporter { public void reportRequestStart(const std::string& requestId, const facebook::react::RequestInfo& requestInfo, int encodedDataLength, const std::optional& redirectResponse); public void reportResponseEnd(const std::string& requestId, int encodedDataLength); public void reportResponseStart(const std::string& requestId, const facebook::react::ResponseInfo& responseInfo, int encodedDataLength); + public void reportWebSocketClosed(const std::string& requestId) const; + public void reportWebSocketCreated(const std::string& requestId, const std::string& url) const; + public void reportWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t statusCode, const facebook::react::Headers& headers) const; + public void reportWebSocketMessageReceived(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketMessageSent(const std::string& requestId, const std::string& payloadData, bool isBinary) const; + public void reportWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::Headers& headers) const; public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -7821,6 +7827,12 @@ class facebook::react::jsinspector_modern::NetworkHandler { public void onRequestWillBeSent(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Request& request, const std::optional& redirectResponse); public void onRequestWillBeSentExtraInfo(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void onResponseReceived(const std::string& requestId, const facebook::react::jsinspector_modern::cdp::network::Response& response); + public void onWebSocketClosed(const std::string& requestId); + public void onWebSocketCreated(const std::string& requestId, const std::string& url); + public void onWebSocketFrameReceived(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketFrameSent(const std::string& requestId, const std::string& payloadData, bool isBinary); + public void onWebSocketHandshakeResponseReceived(const std::string& requestId, uint16_t status, const facebook::react::jsinspector_modern::Headers& headers); + public void onWebSocketWillSendHandshakeRequest(const std::string& requestId, const facebook::react::jsinspector_modern::Headers& headers); public void recordRequestInitiatorStack(const std::string& requestId, folly::dynamic stackTrace); public void storeResponseBody(const std::string& requestId, std::string_view body, bool base64Encoded); } @@ -8527,6 +8539,56 @@ struct facebook::react::jsinspector_modern::cdp::network::ResponseReceivedParams public std::string type; } +struct facebook::react::jsinspector_modern::cdp::network::WebSocketClosedParams { + public double timestamp; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketCreatedParams { + public folly::dynamic initiator; + public folly::dynamic toDynamic() const; + public std::string requestId; + public std::string url; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrame { + public bool mask; + public folly::dynamic toDynamic() const; + public int opcode; + public std::string payloadData; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketFrameParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketFrame response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketHandshakeResponseReceivedParams { + public double timestamp; + public facebook::react::jsinspector_modern::cdp::network::WebSocketResponse response; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketResponse { + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public static facebook::react::jsinspector_modern::cdp::network::WebSocketResponse fromInputParams(uint16_t status, const facebook::react::jsinspector_modern::cdp::network::Headers& headers); + public std::string statusText; + public uint16_t status; +} + +struct facebook::react::jsinspector_modern::cdp::network::WebSocketWillSendHandshakeRequestParams { + public double timestamp; + public double wallTime; + public facebook::react::jsinspector_modern::cdp::network::Headers headers; + public folly::dynamic toDynamic() const; + public std::string requestId; +} + constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINED_BY; constexpr uint_fast16_t facebook::react::dom::DOCUMENT_POSITION_CONTAINS;