Implement HostTargetDelegate.networkRequest on iOS (#44846)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/44846

Implement the `networkRequest` method of `jsinspector_modern::HostTargetDelegate` 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

fbshipit-source-id: fdaf445dda69b621617decdba49ba89c42043a92
This commit is contained in:
Rob Hogan
2024-08-07 09:08:28 -07:00
committed by Facebook GitHub Bot
parent 62231ea883
commit c720583f6c
4 changed files with 162 additions and 2 deletions
+11 -1
View File
@@ -20,6 +20,7 @@
#import <jsinspector-modern/ReactCdp.h>
#import <optional>
#import "RCTDevLoadingViewProtocol.h"
#import "RCTInspectorNetworkHelper.h"
#import "RCTInspectorUtils.h"
#import "RCTJSThread.h"
#import "RCTLog.h"
@@ -182,7 +183,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])
{
}
@@ -229,9 +232,16 @@ class RCTBridgeHostTargetDelegate : public facebook::react::jsinspector_modern::
}
}
void loadNetworkResource(const RCTInspectorLoadNetworkResourceRequest &params, RCTInspectorNetworkExecutor executor)
override
{
[networkHelper_ loadNetworkResourceWithParams:params executor:executor];
}
private:
__weak RCTBridge *bridge_;
RCTPausedInDebuggerOverlayController *pauseOverlayController_;
RCTInspectorNetworkHelper *networkHelper_;
};
@interface RCTBridge () <RCTReloadListener>
@@ -0,0 +1,25 @@
/*
* 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 facebook::react::jsinspector_modern::NetworkRequestListener RCTInspectorNetworkListener;
typedef facebook::react::jsinspector_modern::ScopedExecutor<RCTInspectorNetworkListener> RCTInspectorNetworkExecutor;
typedef facebook::react::jsinspector_modern::LoadNetworkResourceRequest RCTInspectorLoadNetworkResourceRequest;
/**
* A helper class that wraps around NSURLSession to make network requests.
*/
@interface RCTInspectorNetworkHelper : NSObject
- (instancetype)init;
- (void)loadNetworkResourceWithParams:(const RCTInspectorLoadNetworkResourceRequest &)params
executor:(RCTInspectorNetworkExecutor)executor;
@end
@@ -0,0 +1,115 @@
/*
* 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"
#import <React/RCTLog.h>
typedef void (^ListenerBlock)(RCTInspectorNetworkListener *);
@interface RCTInspectorNetworkHelper () <NSURLSessionDataDelegate>
@property (nonatomic, strong) NSURLSession *session;
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, void (^)(ListenerBlock)> *executorsByTaskId;
- (void)withListenerForTask:(NSURLSessionTask *)task execute:(ListenerBlock)block;
@end
@implementation RCTInspectorNetworkHelper
- (instancetype)init
{
self = [super init];
if (self) {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
self.executorsByTaskId = [NSMutableDictionary new];
}
return self;
}
- (void)loadNetworkResourceWithParams:(const RCTInspectorLoadNetworkResourceRequest &)params
executor:(RCTInspectorNetworkExecutor)executor
{
NSString *urlString = [NSString stringWithCString:params.url.c_str() encoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:urlString];
auto executorBlock = ^(ListenerBlock func) {
executor([=](RCTInspectorNetworkListener &listener) { func(&listener); });
};
if (url == nil) {
executorBlock(^(RCTInspectorNetworkListener *listener) {
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;
executorBlock(^(RCTInspectorNetworkListener *listener) {
listener->setCancelFunction([weakDataTask]() { [weakDataTask cancel]; });
});
// Store the executor as a block per task.
self.executorsByTaskId[@(dataTask.taskIdentifier)] = executorBlock;
[dataTask resume];
}
- (void)withListenerForTask:(NSURLSessionTask *)task execute:(ListenerBlock)block
{
void (^executor)(ListenerBlock) = self.executorsByTaskId[@(task.taskIdentifier)];
if (executor) {
executor(block);
}
}
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
auto callbackWithHeadersOrError = (^(RCTInspectorNetworkListener *listener) {
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);
}
});
[self withListenerForTask:dataTask execute:callbackWithHeadersOrError];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
auto callbackWithData = ^(RCTInspectorNetworkListener *listener) {
listener->onData(std::string_view(static_cast<const char *>(data.bytes), data.length));
};
[self withListenerForTask:dataTask execute:callbackWithData];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
auto callbackWithCompletionOrError = ^(RCTInspectorNetworkListener *listener) {
if (error != nil) {
listener->onError(error.localizedDescription.UTF8String);
} else {
listener->onCompletion();
}
};
[self withListenerForTask:task execute:callbackWithCompletionOrError];
}
@end
@@ -13,6 +13,7 @@
#import <React/RCTConvert.h>
#import <React/RCTFabricSurface.h>
#import <React/RCTInspectorDevServerHelper.h>
#import <React/RCTInspectorNetworkHelper.h>
#import <React/RCTInspectorUtils.h>
#import <React/RCTJSThread.h>
#import <React/RCTLog.h>
@@ -37,7 +38,9 @@ using namespace facebook::react;
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])
{
}
@@ -84,9 +87,16 @@ class RCTHostHostTargetDelegate : public facebook::react::jsinspector_modern::Ho
}
}
void loadNetworkResource(const RCTInspectorLoadNetworkResourceRequest &params, RCTInspectorNetworkExecutor executor)
override
{
[networkHelper_ loadNetworkResourceWithParams:params executor:executor];
}
private:
__weak RCTHost *host_;
RCTPausedInDebuggerOverlayController *pauseOverlayController_;
RCTInspectorNetworkHelper *networkHelper_;
};
@implementation RCTHost {