Skip to content

Commit

Permalink
Implement NetworkRequestDelegate on iOS
Browse files Browse the repository at this point in the history
Summary:
Implement `jsinspector_modern:: NetworkRequestDelegate` for iOS (bridge and bridgeless) to satisfy CDP `Network.loadNetworkResource` (etc) requests.

Changelog:
[iOS][Added] Debugger: Implement CDP methods for loading network resources through the debug target.

Differential Revision: D54496969
  • Loading branch information
robhogan authored and facebook-github-bot committed Jun 9, 2024
1 parent 0ddb8a0 commit 7fcbc8e
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 2 deletions.
11 changes: 10 additions & 1 deletion packages/react-native/React/Base/RCTBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#import "RCTConvert.h"
#if RCT_ENABLE_INSPECTOR
#import "RCTInspectorDevServerHelper.h"
#import "RCTInspectorNetworkHelper.h"
#endif
#import <jsinspector-modern/InspectorFlags.h>
#import <jsinspector-modern/InspectorInterfaces.h>
Expand Down Expand Up @@ -193,7 +194,9 @@ void RCTUIManagerSetDispatchAccessibilityManagerInitOntoMain(BOOL enabled)
class RCTBridgeHostTargetDelegate : public facebook::react::jsinspector_modern::HostTargetDelegate {
public:
RCTBridgeHostTargetDelegate(RCTBridge *bridge)
: bridge_(bridge), pauseOverlayController_([[RCTPausedInDebuggerOverlayController alloc] init])
: bridge_(bridge),
pauseOverlayController_([[RCTPausedInDebuggerOverlayController alloc] init]),
networkHelper_([[RCTInspectorNetworkHelper alloc] init])
{
}

Expand Down Expand Up @@ -226,9 +229,15 @@ void onSetPausedInDebuggerMessage(const OverlaySetPausedInDebuggerMessageRequest
}
}

void networkRequest(const std::string &url, RCTInspectorNetworkListener listener) override
{
[networkHelper_ networkRequestWithUrl:url listener:listener];
}

private:
__weak RCTBridge *bridge_;
RCTPausedInDebuggerOverlayController *pauseOverlayController_;
RCTInspectorNetworkHelper *networkHelper_;
};

@interface RCTBridge () <RCTReloadListener>
Expand Down
20 changes: 20 additions & 0 deletions packages/react-native/React/DevSupport/RCTInspectorNetworkHelper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 <jsinspector-modern/ReactCdp.h>

#import <Foundation/Foundation.h>

typedef std::shared_ptr<facebook::react::jsinspector_modern::NetworkRequestListener> RCTInspectorNetworkListener;

/**
* A helper class that wraps around NSURLSession to make network requests.
*/
@interface RCTInspectorNetworkHelper : NSObject
- (instancetype)init;
- (void)networkRequestWithUrl:(const std::string &)url listener:(RCTInspectorNetworkListener)listener;
@end
134 changes: 134 additions & 0 deletions packages/react-native/React/DevSupport/RCTInspectorNetworkHelper.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* 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 "RCTInspectorNetworkHelper.h"

// Wraps RCTInspectorNetworkListener (a C++ shared_ptr) in an NSObject,
// maintaining a ref while making it id-compatible for an NSDictionary.
@interface RCTInspectorNetworkListenerWrapper : NSObject
@property (nonatomic, readonly) RCTInspectorNetworkListener listener;
- (instancetype)initWithListener:(RCTInspectorNetworkListener)listener;
@end

@interface RCTInspectorNetworkHelper () <NSURLSessionDataDelegate>
@property (nonatomic, strong) NSURLSession *session;
@property (nonatomic, strong)
NSMutableDictionary<NSNumber *, RCTInspectorNetworkListenerWrapper *> *listenerWrappersByTaskId;
@end

@implementation RCTInspectorNetworkHelper

- (instancetype)init
{
self = [super init];
if (self) {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
self.listenerWrappersByTaskId = [NSMutableDictionary new];
}
return self;
}

- (void)networkRequestWithUrl:(const std::string &)rawUrl listener:(RCTInspectorNetworkListener)listener
{
NSString *urlString = [NSString stringWithUTF8String:rawUrl.c_str()];
NSURL *url = [NSURL URLWithString:urlString];
if (url == nil) {
listener->onError([NSString stringWithFormat:@"Not a valid URL: %@", urlString].UTF8String);
return;
}

NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest setHTTPMethod:@"GET"];
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:urlRequest];
__weak NSURLSessionDataTask *weakDataTask = dataTask;
listener->setCancelFunction([weakDataTask]() { [weakDataTask cancel]; });
// Store the listener using the task identifier as the key
RCTInspectorNetworkListenerWrapper *listenerWrapper =
[[RCTInspectorNetworkListenerWrapper alloc] initWithListener:listener];
self.listenerWrappersByTaskId[@(dataTask.taskIdentifier)] = listenerWrapper;
[dataTask resume];
}

- (RCTInspectorNetworkListener)listenerForTask:(NSNumber *)taskIdentifier
{
auto *listenerWrapper = self.listenerWrappersByTaskId[taskIdentifier];
return listenerWrapper ? listenerWrapper.listener : nil;
}

#pragma mark - NSURLSessionDataDelegate

- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
RCTInspectorNetworkListener listener = [self listenerForTask:@(dataTask.taskIdentifier)];
if (!listener) {
[dataTask cancel];
return;
}
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
std::map<std::string, std::string> headersMap;
for (NSString *key in httpResponse.allHeaderFields) {
headersMap[[key UTF8String]] = [[httpResponse.allHeaderFields objectForKey:key] UTF8String];
}
completionHandler(NSURLSessionResponseAllow);
listener->onHeaders(httpResponse.statusCode, headersMap);
} else {
listener->onError("Unsupported response type");
completionHandler(NSURLSessionResponseCancel);
}
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
RCTInspectorNetworkListener listener = [self listenerForTask:@(dataTask.taskIdentifier)];
if (!listener) {
[dataTask cancel];
return;
}
if (data) {
listener->onData(std::string_view(static_cast<const char *>(data.bytes), data.length));
}
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
RCTInspectorNetworkListener listener = [self listenerForTask:@(task.taskIdentifier)];
if (!listener) {
return;
}
if (error != nil) {
listener->onError(error.localizedDescription.UTF8String);
} else {
listener->onEnd();
}
[self.listenerWrappersByTaskId removeObjectForKey:@(task.taskIdentifier)];
}

@end

@implementation RCTInspectorNetworkListenerWrapper {
RCTInspectorNetworkListener _listener;
}

- (instancetype)initWithListener:(RCTInspectorNetworkListener)listener
{
if (self = [super init]) {
_listener = listener;
}
return self;
}

- (RCTInspectorNetworkListener)listener
{
return _listener;
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#import <React/RCTConvert.h>
#import <React/RCTFabricSurface.h>
#import <React/RCTInspectorDevServerHelper.h>
#import <React/RCTInspectorNetworkHelper.h>
#import <React/RCTJSThread.h>
#import <React/RCTLog.h>
#import <React/RCTMockDef.h>
Expand All @@ -36,7 +37,9 @@ @interface RCTHost () <RCTReloadListener, RCTInstanceDelegate>
class RCTHostHostTargetDelegate : public facebook::react::jsinspector_modern::HostTargetDelegate {
public:
RCTHostHostTargetDelegate(RCTHost *host)
: host_(host), pauseOverlayController_([[RCTPausedInDebuggerOverlayController alloc] init])
: host_(host),
pauseOverlayController_([[RCTPausedInDebuggerOverlayController alloc] init]),
networkHelper_([[RCTInspectorNetworkHelper alloc] init])
{
}

Expand Down Expand Up @@ -69,9 +72,15 @@ void onSetPausedInDebuggerMessage(const OverlaySetPausedInDebuggerMessageRequest
}
}

void networkRequest(const std::string &rawUrl, RCTInspectorNetworkListener listener) override
{
[networkHelper_ networkRequestWithUrl:rawUrl listener:listener];
}

private:
__weak RCTHost *host_;
RCTPausedInDebuggerOverlayController *pauseOverlayController_;
RCTInspectorNetworkHelper *networkHelper_;
};

@implementation RCTHost {
Expand Down

0 comments on commit 7fcbc8e

Please sign in to comment.