first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

108
node_modules/react-native/Libraries/Network/FormData.js generated vendored Normal file
View File

@@ -0,0 +1,108 @@
/**
* 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.
*
* @flow strict
* @format
*/
'use strict';
type FormDataValue = string | {name?: string, type?: string, uri: string};
type FormDataNameValuePair = [string, FormDataValue];
type Headers = {[name: string]: string, ...};
type FormDataPart =
| {
string: string,
headers: Headers,
...
}
| {
uri: string,
headers: Headers,
name?: string,
type?: string,
...
};
/**
* Encode a FormData filename compliant with RFC 2183
*
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#directives
*/
function encodeFilename(filename: string): string {
return encodeURIComponent(filename.replace(/\//g, '_'));
}
/**
* Polyfill for XMLHttpRequest2 FormData API, allowing multipart POST requests
* with mixed data (string, native files) to be submitted via XMLHttpRequest.
*
* Example:
*
* var photo = {
* uri: uriFromCameraRoll,
* type: 'image/jpeg',
* name: 'photo.jpg',
* };
*
* var body = new FormData();
* body.append('authToken', 'secret');
* body.append('photo', photo);
* body.append('title', 'A beautiful photo!');
*
* xhr.open('POST', serverURL);
* xhr.send(body);
*/
class FormData {
_parts: Array<FormDataNameValuePair>;
constructor() {
this._parts = [];
}
append(key: string, value: FormDataValue) {
// The XMLHttpRequest spec doesn't specify if duplicate keys are allowed.
// MDN says that any new values should be appended to existing values.
// In any case, major browsers allow duplicate keys, so that's what we'll do
// too. They'll simply get appended as additional form data parts in the
// request body, leaving the server to deal with them.
this._parts.push([key, value]);
}
getAll(key: string): Array<FormDataValue> {
return this._parts
.filter(([name]) => name === key)
.map(([, value]) => value);
}
getParts(): Array<FormDataPart> {
return this._parts.map(([name, value]) => {
const contentDisposition = 'form-data; name="' + name + '"';
const headers: Headers = {'content-disposition': contentDisposition};
// The body part is a "blob", which in React Native just means
// an object with a `uri` attribute. Optionally, it can also
// have a `name` and `type` attribute to specify filename and
// content type (cf. web Blob interface.)
if (typeof value === 'object' && !Array.isArray(value) && value) {
if (typeof value.name === 'string') {
headers['content-disposition'] +=
`; filename="${encodeFilename(value.name)}"`;
}
if (typeof value.type === 'string') {
headers['content-type'] = value.type;
}
return {...value, headers, fieldName: name};
}
// Convert non-object values to strings as per FormData.append() spec
return {string: String(value), headers, fieldName: name};
});
}
}
export default FormData;

View File

@@ -0,0 +1,14 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
export * from '../../src/private/specs_DEPRECATED/modules/NativeNetworkingAndroid';
import NativeNetworkingAndroid from '../../src/private/specs_DEPRECATED/modules/NativeNetworkingAndroid';
export default NativeNetworkingAndroid;

View File

@@ -0,0 +1,14 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
export * from '../../src/private/specs_DEPRECATED/modules/NativeNetworkingIOS';
import NativeNetworkingIOS from '../../src/private/specs_DEPRECATED/modules/NativeNetworkingIOS';
export default NativeNetworkingIOS;

View File

@@ -0,0 +1,16 @@
/*
* 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 <React/RCTInvalidating.h>
#import <React/RCTURLRequestHandler.h>
/**
* This is the default RCTURLRequestHandler implementation for data URL requests.
*/
@interface RCTDataRequestHandler : NSObject <RCTURLRequestHandler, RCTInvalidating>
@end

View File

@@ -0,0 +1,104 @@
/*
* 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 <React/RCTDataRequestHandler.h>
#import <ReactCommon/RCTTurboModule.h>
#import <mutex>
#import "RCTNetworkPlugins.h"
@interface RCTDataRequestHandler () <RCTTurboModule>
@end
@implementation RCTDataRequestHandler {
NSOperationQueue *_queue;
std::mutex _operationHandlerMutexLock;
}
RCT_EXPORT_MODULE()
- (void)invalidate
{
std::lock_guard<std::mutex> lock(_operationHandlerMutexLock);
if (_queue != nullptr) {
for (NSOperation *operation in _queue.operations) {
if (!operation.isCancelled && !operation.isFinished) {
[operation cancel];
}
}
_queue = nil;
}
}
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return [request.URL.scheme caseInsensitiveCompare:@"data"] == NSOrderedSame;
}
- (NSOperation *)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
{
std::lock_guard<std::mutex> lock(_operationHandlerMutexLock);
// Lazy setup
if (_queue == nullptr) {
_queue = [NSOperationQueue new];
_queue.maxConcurrentOperationCount = 2;
}
NSBlockOperation *op = [NSBlockOperation new];
__weak NSBlockOperation *weakOp = op;
[op addExecutionBlock:^{
NSBlockOperation *strongOp = weakOp; // Strong reference to avoid deallocation during execution
if (strongOp == nil || [strongOp isCancelled]) {
return;
}
// Get mime type
NSRange firstSemicolon = [request.URL.resourceSpecifier rangeOfString:@";"];
NSString *mimeType =
(firstSemicolon.length != 0u) ? [request.URL.resourceSpecifier substringToIndex:firstSemicolon.location] : nil;
// Send response
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:mimeType
expectedContentLength:-1
textEncodingName:nil];
[delegate URLRequest:strongOp didReceiveResponse:response];
// Load data
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:request.URL options:NSDataReadingMappedIfSafe error:&error];
if (data != nullptr) {
[delegate URLRequest:strongOp didReceiveData:data];
}
[delegate URLRequest:strongOp didCompleteWithError:error];
}];
[_queue addOperation:op];
return op;
}
- (void)cancelRequest:(NSOperation *)op
{
std::lock_guard<std::mutex> lock(_operationHandlerMutexLock);
if (!op.isCancelled && !op.isFinished) {
[op cancel];
}
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return nullptr;
}
@end
Class RCTDataRequestHandlerCls(void)
{
return RCTDataRequestHandler.class;
}

View File

@@ -0,0 +1,16 @@
/*
* 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 <React/RCTInvalidating.h>
#import <React/RCTURLRequestHandler.h>
/**
* This is the default RCTURLRequestHandler implementation for file requests.
*/
@interface RCTFileRequestHandler : NSObject <RCTURLRequestHandler, RCTInvalidating>
@end

View File

@@ -0,0 +1,118 @@
/*
* 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 <React/RCTFileRequestHandler.h>
#import <mutex>
#import <MobileCoreServices/MobileCoreServices.h>
#import <React/RCTUtils.h>
#import <ReactCommon/RCTTurboModule.h>
#import "RCTNetworkPlugins.h"
@interface RCTFileRequestHandler () <RCTTurboModule>
@end
@implementation RCTFileRequestHandler {
NSOperationQueue *_fileQueue;
std::mutex _operationHandlerMutexLock;
}
RCT_EXPORT_MODULE()
- (void)invalidate
{
std::lock_guard<std::mutex> lock(_operationHandlerMutexLock);
if (_fileQueue) {
for (NSOperation *operation in _fileQueue.operations) {
if (!operation.isCancelled && !operation.isFinished) {
[operation cancel];
}
}
_fileQueue = nil;
}
}
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return [request.URL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame && !RCTIsBundleAssetURL(request.URL);
}
- (NSOperation *)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
{
std::lock_guard<std::mutex> lock(_operationHandlerMutexLock);
// Lazy setup
if (!_fileQueue) {
_fileQueue = [NSOperationQueue new];
_fileQueue.maxConcurrentOperationCount = 4;
}
NSBlockOperation *op = [NSBlockOperation new];
__weak NSBlockOperation *weakOp = op;
[op addExecutionBlock:^{
NSBlockOperation *strongOp = weakOp; // Strong reference to avoid deallocation during execution
if (strongOp == nil || [strongOp isCancelled]) {
return;
}
// Get content length
NSError *error = nil;
NSFileManager *fileManager = [NSFileManager new];
NSDictionary<NSString *, id> *fileAttributes = [fileManager attributesOfItemAtPath:request.URL.path error:&error];
if (!fileAttributes) {
[delegate URLRequest:strongOp didCompleteWithError:error];
return;
}
// Get mime type
NSString *fileExtension = [request.URL pathExtension];
NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
NSString *contentType =
(__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
// Send response
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:contentType
expectedContentLength:[fileAttributes[NSFileSize] ?: @-1 integerValue]
textEncodingName:nil];
[delegate URLRequest:strongOp didReceiveResponse:response];
// Load data
NSData *data = [NSData dataWithContentsOfURL:request.URL options:NSDataReadingMappedIfSafe error:&error];
if (data) {
[delegate URLRequest:strongOp didReceiveData:data];
}
[delegate URLRequest:strongOp didCompleteWithError:error];
}];
[_fileQueue addOperation:op];
return op;
}
- (void)cancelRequest:(NSOperation *)op
{
std::lock_guard<std::mutex> lock(_operationHandlerMutexLock);
if (!op.isCancelled && !op.isFinished) {
[op cancel];
}
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return nullptr;
}
@end
Class RCTFileRequestHandlerCls(void)
{
return RCTFileRequestHandler.class;
}

View File

@@ -0,0 +1,22 @@
/*
* 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 <React/RCTInvalidating.h>
#import <React/RCTURLRequestHandler.h>
typedef NSURLSessionConfiguration * (^NSURLSessionConfigurationProvider)(void);
/**
* The block provided via this function will provide the NSURLSessionConfiguration for all HTTP requests made by the
* app.
*/
RCT_EXTERN void RCTSetCustomNSURLSessionConfigurationProvider(NSURLSessionConfigurationProvider /*provider*/);
/**
* This is the default RCTURLRequestHandler implementation for HTTP requests.
*/
@interface RCTHTTPRequestHandler : NSObject <RCTURLRequestHandler, RCTInvalidating>
@end

View File

@@ -0,0 +1,194 @@
/*
* 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 <React/RCTHTTPRequestHandler.h>
#import <mutex>
#import <React/RCTNetworking.h>
#import <ReactCommon/RCTTurboModule.h>
#import "RCTNetworkPlugins.h"
@interface RCTHTTPRequestHandler () <NSURLSessionDataDelegate, RCTTurboModule>
@end
static NSURLSessionConfigurationProvider urlSessionConfigurationProvider;
void RCTSetCustomNSURLSessionConfigurationProvider(NSURLSessionConfigurationProvider provider)
{
urlSessionConfigurationProvider = provider;
}
@implementation RCTHTTPRequestHandler {
NSMapTable *_delegates;
NSURLSession *_session;
std::mutex _mutex;
}
@synthesize moduleRegistry = _moduleRegistry;
RCT_EXPORT_MODULE()
- (void)invalidate
{
std::lock_guard<std::mutex> lock(_mutex);
[self->_session invalidateAndCancel];
self->_session = nil;
}
// Needs to lock before call this method.
- (BOOL)isValid
{
// if session == nil and delegates != nil, we've been invalidated
return (_session != nullptr) || (_delegates == nullptr);
}
#pragma mark - NSURLRequestHandler
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
static NSSet<NSString *> *schemes = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// technically, RCTHTTPRequestHandler can handle file:// as well,
// but it's less efficient than using RCTFileRequestHandler
schemes = [[NSSet alloc] initWithObjects:@"http", @"https", nil];
});
return [schemes containsObject:request.URL.scheme.lowercaseString];
}
- (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
{
std::lock_guard<std::mutex> lock(_mutex);
// Lazy setup
if ((_session == nullptr) && [self isValid]) {
// You can override default NSURLSession instance property allowsCellularAccess (default value YES)
// by providing the following key to your RN project (edit ios/project/Info.plist file in Xcode):
// <key>ReactNetworkForceWifiOnly</key> <true/>
// This will set allowsCellularAccess to NO and force Wifi only for all network calls on iOS
// If you do not want to override default behavior, do nothing or set key with value false
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSNumber *useWifiOnly = [infoDictionary objectForKey:@"ReactNetworkForceWifiOnly"];
NSOperationQueue *callbackQueue = [NSOperationQueue new];
callbackQueue.maxConcurrentOperationCount = 1;
callbackQueue.underlyingQueue = [[_moduleRegistry moduleForName:"Networking"] methodQueue];
NSURLSessionConfiguration *configuration;
if (urlSessionConfigurationProvider != nullptr) {
configuration = urlSessionConfigurationProvider();
} else {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Set allowsCellularAccess to NO ONLY if key ReactNetworkForceWifiOnly exists AND its value is YES
if (useWifiOnly != nullptr) {
configuration.allowsCellularAccess = ![useWifiOnly boolValue];
}
[configuration setHTTPShouldSetCookies:YES];
[configuration setHTTPCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
[configuration setHTTPCookieStorage:[NSHTTPCookieStorage sharedHTTPCookieStorage]];
}
assert(configuration != nil);
_session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:callbackQueue];
_delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
valueOptions:NSPointerFunctionsStrongMemory
capacity:0];
}
NSURLSessionDataTask *task = [_session dataTaskWithRequest:request];
[_delegates setObject:delegate forKey:task];
[task resume];
return task;
}
- (void)cancelRequest:(NSURLSessionDataTask *)task
{
{
std::lock_guard<std::mutex> lock(_mutex);
[_delegates removeObjectForKey:task];
}
[task cancel];
}
#pragma mark - NSURLSession delegate
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
id<RCTURLRequestDelegate> delegate;
{
std::lock_guard<std::mutex> lock(_mutex);
delegate = [_delegates objectForKey:task];
}
[delegate URLRequest:task didSendDataWithProgress:totalBytesSent];
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
// Reset the cookies on redirect.
// This is necessary because we're not letting iOS handle cookies by itself
NSMutableURLRequest *nextRequest = [request mutableCopy];
NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL];
nextRequest.allHTTPHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
completionHandler(nextRequest);
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)task
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
id<RCTURLRequestDelegate> delegate;
{
std::lock_guard<std::mutex> lock(_mutex);
delegate = [_delegates objectForKey:task];
}
[delegate URLRequest:task didReceiveResponse:response];
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task didReceiveData:(NSData *)data
{
id<RCTURLRequestDelegate> delegate;
{
std::lock_guard<std::mutex> lock(_mutex);
delegate = [_delegates objectForKey:task];
}
[delegate URLRequest:task didReceiveData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
id<RCTURLRequestDelegate> delegate;
{
std::lock_guard<std::mutex> lock(_mutex);
delegate = [_delegates objectForKey:task];
[_delegates removeObjectForKey:task];
}
[delegate URLRequest:task didCompleteWithError:error];
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return nullptr;
}
@end
Class RCTHTTPRequestHandlerCls(void)
{
return RCTHTTPRequestHandler.class;
}

View File

@@ -0,0 +1,96 @@
/*
* 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.
*/
#pragma once
#import <Foundation/Foundation.h>
/**
* [Experimental] An interface for reporting network events to the modern
* debugger server and Web Performance APIs.
*
* In a production (non dev or profiling) build, CDP reporting is disabled.
*
* This is a helper class wrapping
* `facebook::react::jsinspector_modern::NetworkReporter`.
*/
@interface RCTInspectorNetworkReporter : NSObject
/**
* Report a network request that is about to be sent.
*
* - Corresponds to `Network.requestWillBeSent` in CDP.
* - Corresponds to `PerformanceResourceTiming.requestStart` (specifically,
* marking when the native request was initiated).
*/
+ (void)reportRequestStart:(NSString *)requestId
request:(NSURLRequest *)request
encodedDataLength:(int)encodedDataLength;
/**
* Report timestamp for sending the network request, and (in a debug build)
* provide final headers to be reported via CDP.
*
* - Corresponds to `Network.requestWillBeSentExtraInfo` in CDP.
* - Corresponds to `PerformanceResourceTiming.domainLookupStart`,
* `PerformanceResourceTiming.connectStart`. Defined as "immediately before
* the browser starts to establish the connection to the server".
*/
+ (void)reportConnectionTiming:(NSString *)requestId request:(NSURLRequest *)request;
/**
* Report when HTTP response headers have been received, corresponding to
* when the first byte of the response is available.
*
* - Corresponds to `Network.responseReceived` in CDP.
* - Corresponds to `PerformanceResourceTiming.responseStart`.
*/
+ (void)reportResponseStart:(NSString *)requestId
response:(NSURLResponse *)response
statusCode:(int)statusCode
headers:(NSDictionary<NSString *, NSString *> *)headers;
/**
* Report when additional chunks of the response body have been received.
*
* Corresponds to `Network.dataReceived` in CDP.
*/
+ (void)reportDataReceived:(NSString *)requestId data:(NSData *)data;
/**
* Report when a network request is complete and we are no longer receiving
* response data.
*
* - Corresponds to `Network.loadingFinished` in CDP.
* - Corresponds to `PerformanceResourceTiming.responseEnd`.
*/
+ (void)reportResponseEnd:(NSString *)requestId encodedDataLength:(int)encodedDataLength;
/**
* Report when a network request has failed.
*
* - Corresponds to `Network.loadingFailed` in CDP.
*/
+ (void)reportRequestFailed:(NSString *)requestId cancelled:(BOOL)cancelled;
/**
* Store response body preview. This is an optional reporting method, and is a
* no-op if CDP debugging is disabled.
*/
+ (void)maybeStoreResponseBody:(NSString *)requestId data:(NSData *)data base64Encoded:(bool)base64Encoded;
/**
* Incrementally store a response body preview, when a string response is
* received in chunks. Buffered contents will be flushed to `NetworkReporter`
* with `reportResponseEnd`.
*
* As with `maybeStoreResponseBody`, calling this method is optional and a
* no-op if CDP debugging is disabled.
*/
+ (void)maybeStoreResponseBodyIncremental:(NSString *)requestId data:(NSString *)data;
@end

View File

@@ -0,0 +1,191 @@
/*
* 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 "RCTInspectorNetworkReporter.h"
#import "RCTNetworkConversions.h"
#import <React/RCTLog.h>
#import <react/networking/NetworkReporter.h>
using namespace facebook::react;
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
namespace {
Headers convertNSDictionaryToHeaders(const NSDictionary<NSString *, NSString *> *headers)
{
Headers responseHeaders;
for (NSString *key in headers) {
responseHeaders[[key UTF8String]] = [headers[key] UTF8String];
}
return responseHeaders;
}
std::string convertRequestBodyToStringTruncated(NSURLRequest *request)
{
const NSUInteger maxBodySize = 1024 * 1024; // 1MB
auto bodyLength = request.HTTPBody.length;
auto bytesToRead = std::min(bodyLength, maxBodySize);
auto body = std::string((const char *)request.HTTPBody.bytes, bytesToRead);
if (bytesToRead < bodyLength) {
body +=
"\n... [truncated, showing " + std::to_string(bytesToRead) + " of " + std::to_string(bodyLength) + " bytes]";
}
return body;
}
} // namespace
#endif
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
// Dictionary to buffer incremental response bodies (CDP debugging active only)
static const NSMutableDictionary<NSString *, NSMutableString *> *responseBuffers = nil;
#endif
@implementation RCTInspectorNetworkReporter {
}
+ (void)reportRequestStart:(NSString *)requestId
request:(NSURLRequest *)request
encodedDataLength:(int)encodedDataLength
{
RequestInfo requestInfo;
requestInfo.url = [request.URL absoluteString].UTF8String;
requestInfo.httpMethod = [request.HTTPMethod UTF8String];
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
// Debug build: Process additional request info for CDP reporting
requestInfo.headers = convertNSDictionaryToHeaders(request.allHTTPHeaderFields);
requestInfo.httpBody = convertRequestBodyToStringTruncated(request);
#endif
NetworkReporter::getInstance().reportRequestStart(requestId.UTF8String, requestInfo, encodedDataLength, std::nullopt);
}
+ (void)reportConnectionTiming:(NSString *)requestId request:(NSURLRequest *)request
{
Headers headersMap;
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
// Debug build: Process additional request info for CDP reporting
headersMap = convertNSDictionaryToHeaders(request.allHTTPHeaderFields);
#endif
NetworkReporter::getInstance().reportConnectionTiming(requestId.UTF8String, headersMap);
}
+ (void)reportResponseStart:(NSString *)requestId
response:(NSURLResponse *)response
statusCode:(int)statusCode
headers:(NSDictionary<NSString *, NSString *> *)headers
{
ResponseInfo responseInfo;
responseInfo.url = response.URL.absoluteString.UTF8String;
responseInfo.statusCode = statusCode;
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
// Debug build: Process additional request info for CDP reporting
responseInfo.headers = convertNSDictionaryToHeaders(headers);
#endif
NetworkReporter::getInstance().reportResponseStart(
requestId.UTF8String, responseInfo, response.expectedContentLength);
}
+ (void)reportDataReceived:(NSString *)requestId data:(NSData *)data
{
NetworkReporter::getInstance().reportDataReceived(requestId.UTF8String, (int)data.length, std::nullopt);
}
+ (void)reportResponseEnd:(NSString *)requestId encodedDataLength:(int)encodedDataLength
{
NetworkReporter::getInstance().reportResponseEnd(requestId.UTF8String, encodedDataLength);
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
// Debug build: Check for buffered response body and flush to NetworkReporter
if (responseBuffers != nullptr) {
NSMutableString *buffer = responseBuffers[requestId];
if (buffer != nullptr) {
if (buffer.length > 0) {
NetworkReporter::getInstance().storeResponseBody(
requestId.UTF8String, RCTStringViewFromNSString(buffer), false);
}
[responseBuffers removeObjectForKey:requestId];
}
}
#endif
}
+ (void)reportRequestFailed:(NSString *)requestId cancelled:(bool)cancelled
{
NetworkReporter::getInstance().reportRequestFailed(requestId.UTF8String, cancelled);
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
// Debug build: Clear buffer for request
if (responseBuffers != nullptr) {
[responseBuffers removeObjectForKey:requestId];
}
#endif
}
+ (void)maybeStoreResponseBody:(NSString *)requestId data:(id)data base64Encoded:(bool)base64Encoded
{
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
// Debug build: Process response body and report to NetworkReporter
auto &networkReporter = NetworkReporter::getInstance();
if (!networkReporter.isDebuggingEnabled()) {
return;
}
if ([data isKindOfClass:[NSData class]] && [(NSData *)data length] > 0) {
@try {
NSString *encodedString = [(NSData *)data base64EncodedStringWithOptions:0];
if (encodedString != nullptr) {
networkReporter.storeResponseBody(
requestId.UTF8String, RCTStringViewFromNSString(encodedString), base64Encoded);
} else {
RCTLogWarn(@"Failed to encode response data for request %@", requestId);
}
} @catch (NSException *exception) {
RCTLogWarn(@"Exception while encoding response data: %@", exception.reason);
}
} else if ([data isKindOfClass:[NSString class]] && [(NSString *)data length] > 0) {
networkReporter.storeResponseBody(requestId.UTF8String, RCTStringViewFromNSString((NSString *)data), base64Encoded);
}
#endif
}
+ (void)maybeStoreResponseBodyIncremental:(NSString *)requestId data:(NSString *)data
{
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
// Debug build: Buffer incremental response body contents
auto &networkReporter = NetworkReporter::getInstance();
if (!networkReporter.isDebuggingEnabled()) {
return;
}
if (responseBuffers == nullptr) {
responseBuffers = [NSMutableDictionary dictionary];
}
// Get or create buffer for this requestId
NSMutableString *buffer = responseBuffers[requestId];
if (buffer == nullptr) {
buffer = [NSMutableString string];
responseBuffers[requestId] = buffer;
}
[buffer appendString:data];
#endif
}
@end

View File

@@ -0,0 +1,23 @@
/*
* 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 <UIKit/UIKit.h>
#ifdef __cplusplus
#import <string>
NS_ASSUME_NONNULL_BEGIN
inline std::string_view RCTStringViewFromNSString(NSString *string)
{
return std::string_view{string.UTF8String, string.length};
}
NS_ASSUME_NONNULL_END
#endif

View File

@@ -0,0 +1,43 @@
/**
* 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.
*
* @generated by an internal plugin build system
*/
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
// FB Internal: FBRCTNetworkPlugins.h is autogenerated by the build system.
#import <React/FBRCTNetworkPlugins.h>
#else
// OSS-compatibility layer
#import <Foundation/Foundation.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
#ifdef __cplusplus
extern "C" {
#endif
// RCTTurboModuleManagerDelegate should call this to resolve module classes.
Class RCTNetworkClassProvider(const char *name);
// Lookup functions
Class RCTDataRequestHandlerCls(void) __attribute__((used));
Class RCTFileRequestHandlerCls(void) __attribute__((used));
Class RCTHTTPRequestHandlerCls(void) __attribute__((used));
Class RCTNetworkingCls(void) __attribute__((used));
#ifdef __cplusplus
}
#endif
#pragma GCC diagnostic pop
#endif // RN_DISABLE_OSS_PLUGIN_HEADER

View File

@@ -0,0 +1,36 @@
/**
* 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.
*
* @generated by an internal plugin build system
*/
#ifndef RN_DISABLE_OSS_PLUGIN_HEADER
// OSS-compatibility layer
#import "RCTNetworkPlugins.h"
#import <string>
#import <unordered_map>
Class RCTNetworkClassProvider(const char *name) {
// Intentionally leak to avoid crashing after static destructors are run.
static const auto sCoreModuleClassMap = new const std::unordered_map<std::string, Class (*)(void)>{
{"DataRequestHandler", RCTDataRequestHandlerCls},
{"FileRequestHandler", RCTFileRequestHandlerCls},
{"HTTPRequestHandler", RCTHTTPRequestHandlerCls},
{"Networking", RCTNetworkingCls},
};
auto p = sCoreModuleClassMap->find(name);
if (p != sCoreModuleClassMap->end()) {
auto classFunc = p->second;
return classFunc();
}
return nil;
}
#endif // RN_DISABLE_OSS_PLUGIN_HEADER

View File

@@ -0,0 +1,51 @@
/*
* 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 <Foundation/Foundation.h>
#import <React/RCTURLRequestDelegate.h>
#import <React/RCTURLRequestHandler.h>
typedef void (^RCTURLRequestCompletionBlock)(NSURLResponse *response, NSData *data, NSError *error);
typedef void (^RCTURLRequestCancellationBlock)(void);
typedef void (^RCTURLRequestIncrementalDataBlock)(NSData *data, int64_t progress, int64_t total);
typedef void (^RCTURLRequestProgressBlock)(int64_t progress, int64_t total);
typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response);
typedef NS_ENUM(NSInteger, RCTNetworkTaskStatus) {
RCTNetworkTaskPending = 0,
RCTNetworkTaskInProgress,
RCTNetworkTaskFinished,
};
@interface RCTNetworkTask : NSObject <RCTURLRequestDelegate>
@property (nonatomic, readonly) NSURLRequest *request;
@property (nonatomic, readonly) NSNumber *requestID;
@property (nonatomic, readonly, weak) id requestToken;
@property (nonatomic, readonly) NSURLResponse *response;
@property (nonatomic, readonly) NSString *devToolsRequestId;
@property (nonatomic, copy) RCTURLRequestCompletionBlock completionBlock;
@property (nonatomic, copy) RCTURLRequestProgressBlock downloadProgressBlock;
@property (nonatomic, copy) RCTURLRequestIncrementalDataBlock incrementalDataBlock;
@property (nonatomic, copy) RCTURLRequestResponseBlock responseBlock;
@property (nonatomic, copy) RCTURLRequestProgressBlock uploadProgressBlock;
@property (atomic, readonly) RCTNetworkTaskStatus status;
- (instancetype)initWithRequest:(NSURLRequest *)request
handler:(id<RCTURLRequestHandler>)handler
callbackQueue:(dispatch_queue_t)callbackQueue NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithDevToolsRequestId:(NSString *)devToolsRequestId
request:(NSURLRequest *)request
handler:(id<RCTURLRequestHandler>)handler
callbackQueue:(dispatch_queue_t)callbackQueue;
- (void)start;
- (void)cancel;
@end

View File

@@ -0,0 +1,241 @@
/*
* 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 <atomic>
#import <mutex>
#import <React/RCTLog.h>
#import <React/RCTNetworkTask.h>
#import <React/RCTUtils.h>
@implementation RCTNetworkTask {
NSMutableData *_data;
id<RCTURLRequestHandler> _handler;
dispatch_queue_t _callbackQueue;
std::mutex _mutex;
std::atomic<RCTNetworkTaskStatus> _atomicStatus;
RCTNetworkTask *_selfReference;
}
static auto currentRequestId = std::atomic<NSUInteger>(0);
- (instancetype)initWithRequest:(NSURLRequest *)request
handler:(id<RCTURLRequestHandler>)handler
callbackQueue:(dispatch_queue_t)callbackQueue
{
self = [self initWithDevToolsRequestId:nil request:request handler:handler callbackQueue:callbackQueue];
return self;
}
- (instancetype)initWithDevToolsRequestId:(NSString *)devToolsRequestId
request:(NSURLRequest *)request
handler:(id<RCTURLRequestHandler>)handler
callbackQueue:(dispatch_queue_t)callbackQueue
{
RCTAssertParam(request);
RCTAssertParam(handler);
RCTAssertParam(callbackQueue);
if ((self = [super init])) {
_requestID = @(currentRequestId++);
_devToolsRequestId = devToolsRequestId;
if (_devToolsRequestId == nil) {
_devToolsRequestId = [[NSUUID UUID] UUIDString];
}
_request = request;
_handler = handler;
_callbackQueue = callbackQueue;
_atomicStatus = RCTNetworkTaskPending;
dispatch_queue_set_specific(callbackQueue, (__bridge void *)self, (__bridge void *)self, NULL);
}
return self;
}
RCT_NOT_IMPLEMENTED(-(instancetype)init)
- (RCTNetworkTaskStatus)status
{
return _atomicStatus;
}
- (void)invalidate
{
_selfReference = nil;
_completionBlock = nil;
_downloadProgressBlock = nil;
_incrementalDataBlock = nil;
_responseBlock = nil;
_uploadProgressBlock = nil;
_requestToken = nil;
}
- (void)dispatchCallback:(dispatch_block_t)callback
{
if (dispatch_get_specific((__bridge void *)self) == (__bridge void *)self) {
callback();
} else {
dispatch_async(_callbackQueue, callback);
}
}
- (void)start
{
if (_atomicStatus != RCTNetworkTaskPending) {
RCTLogError(@"RCTNetworkTask was already started or completed");
return;
}
if (_requestToken == nil) {
id token = [_handler sendRequest:_request withDelegate:self];
if ([self validateRequestToken:token]) {
_selfReference = self;
_atomicStatus = RCTNetworkTaskInProgress;
}
}
}
- (void)cancel
{
if (_atomicStatus.exchange(RCTNetworkTaskFinished) == RCTNetworkTaskFinished) {
return;
}
id token = _requestToken;
if (token && [_handler respondsToSelector:@selector(cancelRequest:)]) {
[_handler cancelRequest:token];
}
[self invalidate];
}
- (BOOL)validateRequestToken:(id)requestToken
{
BOOL valid = YES;
if (_requestToken == nil) {
_requestToken = requestToken;
} else if (![requestToken isEqual:_requestToken]) {
if (RCT_DEBUG) {
RCTLogError(@"Unrecognized request token: %@ expected: %@", requestToken, _requestToken);
}
valid = NO;
}
if (!valid) {
_atomicStatus = RCTNetworkTaskFinished;
if (_completionBlock) {
RCTURLRequestCompletionBlock completionBlock = _completionBlock;
[self dispatchCallback:^{
completionBlock(self->_response, nil, RCTErrorWithMessage(@"Invalid request token."));
}];
}
[self invalidate];
}
return valid;
}
- (void)URLRequest:(id)requestToken didSendDataWithProgress:(int64_t)bytesSent
{
if (![self validateRequestToken:requestToken]) {
return;
}
if (_uploadProgressBlock) {
RCTURLRequestProgressBlock uploadProgressBlock = _uploadProgressBlock;
int64_t length = _request.HTTPBody.length;
[self dispatchCallback:^{
uploadProgressBlock(bytesSent, length);
}];
}
}
- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response
{
if (![self validateRequestToken:requestToken]) {
return;
}
_response = response;
if (_responseBlock) {
RCTURLRequestResponseBlock responseBlock = _responseBlock;
[self dispatchCallback:^{
responseBlock(response);
}];
}
}
- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data
{
if (![self validateRequestToken:requestToken]) {
return;
}
int64_t length = 0;
{
// NSData is not thread-safe and this method could be called from different threads as
// RCTURLRequestHandlers does not provide any guarantee of which thread we are called on.
std::lock_guard<std::mutex> lock(_mutex);
if (!_data) {
_data = [NSMutableData new];
}
@try {
[_data appendData:data];
} @catch (NSException *exception) {
_atomicStatus = RCTNetworkTaskFinished;
if (_completionBlock) {
RCTURLRequestCompletionBlock completionBlock = _completionBlock;
[self dispatchCallback:^{
completionBlock(
self->_response, nil, RCTErrorWithMessage(exception.reason ?: @"Request's received data too long."));
}];
}
[self invalidate];
return;
}
length = _data.length;
}
int64_t total = _response.expectedContentLength;
if (_incrementalDataBlock) {
RCTURLRequestIncrementalDataBlock incrementalDataBlock = _incrementalDataBlock;
[self dispatchCallback:^{
incrementalDataBlock(data, length, total);
}];
}
if (_downloadProgressBlock) {
RCTURLRequestProgressBlock downloadProgressBlock = _downloadProgressBlock;
[self dispatchCallback:^{
downloadProgressBlock(length, total);
}];
}
}
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error
{
if (![self validateRequestToken:requestToken]) {
return;
}
_atomicStatus = RCTNetworkTaskFinished;
if (_completionBlock) {
RCTURLRequestCompletionBlock completionBlock = _completionBlock;
NSData *dataCopy = nil;
{
std::lock_guard<std::mutex> lock(self->_mutex);
dataCopy = _data;
_data = nil;
}
[self dispatchCallback:^{
completionBlock(self->_response, dataCopy, error);
}];
}
[self invalidate];
}
@end

View File

@@ -0,0 +1,21 @@
/*
* 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 "RCTNetworking.h"
@protocol RCTNetworkingTextResponseHandler <NSObject>
- (BOOL)canHandleNetworkingTextResponseForRequest:(NSURLRequest *)request;
- (void)handleNetworkingResponseText:(NSString *)responseText request:(NSURLRequest *)request;
@end
@interface RCTNetworking (Internal)
- (void)addTextResponseHandler:(id<RCTNetworkingTextResponseHandler>)handler;
@end

View File

@@ -0,0 +1,105 @@
/**
* 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.
*
* @flow
* @format
*/
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
import type {RequestBody} from './convertRequestBody';
import type {RCTNetworkingEventDefinitions} from './RCTNetworkingEventDefinitions.flow';
import type {NativeResponseType} from './XMLHttpRequest';
// Do not require the native RCTNetworking module directly! Use this wrapper module instead.
// It will add the necessary requestId, so that you don't have to generate it yourself.
import NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
import Platform from '../Utilities/Platform';
import convertRequestBody from './convertRequestBody';
import NativeNetworkingAndroid from './NativeNetworkingAndroid';
type Header = [string, string];
// Convert FormData headers to arrays, which are easier to consume in
// native on Android.
function convertHeadersMapToArray(headers: Object): Array<Header> {
const headerArray: Array<Header> = [];
for (const name in headers) {
headerArray.push([name, headers[name]]);
}
return headerArray;
}
let _requestId = 1;
function generateRequestId(): number {
return _requestId++;
}
const emitter = new NativeEventEmitter<$FlowFixMe>(
// T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior
// If you want to use the native module on other platforms, please remove this condition and test its behavior
Platform.OS !== 'ios' ? null : NativeNetworkingAndroid,
);
/**
* This object is a wrapper around the native RCTNetworking module. It adds a necessary unique
* requestId to each network request that can be used to abort that request later on.
*/
const RCTNetworking = {
addListener<K: $Keys<RCTNetworkingEventDefinitions>>(
eventType: K,
listener: (...RCTNetworkingEventDefinitions[K]) => mixed,
context?: mixed,
): EventSubscription {
// $FlowFixMe[incompatible-type]
return emitter.addListener(eventType, listener, context);
},
sendRequest(
method: string,
trackingName: string | void,
url: string,
headers: Object,
data: RequestBody,
responseType: NativeResponseType,
incrementalUpdates: boolean,
timeout: number,
callback: (requestId: number) => mixed,
withCredentials: boolean,
) {
const body = convertRequestBody(data);
if (body && body.formData) {
body.formData = body.formData.map(part => ({
...part,
headers: convertHeadersMapToArray(part.headers),
}));
}
const requestId = generateRequestId();
const devToolsRequestId =
global.__NETWORK_REPORTER__?.createDevToolsRequestId();
NativeNetworkingAndroid.sendRequest(
method,
url,
requestId,
convertHeadersMapToArray(headers),
{...body, trackingName, devToolsRequestId},
responseType,
incrementalUpdates,
timeout,
withCredentials,
);
callback(requestId);
},
abortRequest(requestId: number) {
NativeNetworkingAndroid.abortRequest(requestId);
},
clearCookies(callback: (result: boolean) => void) {
NativeNetworkingAndroid.clearCookies(callback);
},
};
export default RCTNetworking;

View File

@@ -0,0 +1,85 @@
/*
* 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 <React/RCTBridgeProxy.h>
#import <React/RCTEventEmitter.h>
#import <React/RCTNetworkTask.h>
#import <React/RCTURLRequestHandler.h>
@protocol RCTNetworkingRequestHandler <NSObject>
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
- (BOOL)canHandleNetworkingRequest:(NSDictionary *)data;
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
- (NSDictionary *)handleNetworkingRequest:(NSDictionary *)data;
@end
@protocol RCTNetworkingResponseHandler <NSObject>
- (BOOL)canHandleNetworkingResponse:(NSString *)responseType;
- (id)handleNetworkingResponse:(NSURLResponse *)response data:(NSData *)data;
@end
@interface RCTNetworking : RCTEventEmitter
- (instancetype)init NS_DESIGNATED_INITIALIZER;
/**
* Allows RCTNetworking instances to be initialized with handlers.
* The handlers will be requested via the bridge's moduleForName method when required.
*/
- (instancetype)initWithHandlersProvider:(NSArray<id<RCTURLRequestHandler>> * (^)(RCTModuleRegistry *))getHandlers;
/**
* Does a handler exist for the specified request?
*/
- (BOOL)canHandleRequest:(NSURLRequest *)request;
/**
* Return an RCTNetworkTask for the specified request. This is useful for
* invoking the React Native networking stack from within native code.
*/
- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request
completionBlock:(RCTURLRequestCompletionBlock)completionBlock;
- (RCTNetworkTask *)networkTaskWithDevToolsRequestId:(NSString *)devToolsRequestId
request:(NSURLRequest *)request
completionBlock:(RCTURLRequestCompletionBlock)completionBlock;
- (void)addRequestHandler:(id<RCTNetworkingRequestHandler>)handler;
- (void)addResponseHandler:(id<RCTNetworkingResponseHandler>)handler;
- (void)removeRequestHandler:(id<RCTNetworkingRequestHandler>)handler;
- (void)removeResponseHandler:(id<RCTNetworkingResponseHandler>)handler;
@end
@interface RCTBridge (RCTNetworking)
@property (nonatomic, readonly) RCTNetworking *networking;
@end
@interface RCTBridgeProxy (RCTNetworking)
@property (nonatomic, readonly) RCTNetworking *networking;
@end
// HACK: When uploading images/video from PHAssetLibrary, we change the URL scheme to be
// ph-upload://. This is to ensure that we upload a full video when given a ph-upload:// URL,
// instead of just the thumbnail. Consider the following problem:
// The user has a video in their camera roll with URL ph://1B3E2DDB-0AD3-4E33-A7A1-9F4AA9A762AA/L0/001
// 1. We want to display that video in an <Image> and show the thumbnail
// 2. We later want to upload that video.
// At this point, if we use the same URL for both uses, there is no way to distinguish the intent
// and we will either upload the thumbnail (bad!) or try to show the video in an <Image> (bad!).
// Our solution is to change the URL scheme in the uploader.
extern NSString *const RCTNetworkingPHUploadHackScheme;

View File

@@ -0,0 +1,70 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
'use strict';
import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter';
import {type EventSubscription} from '../vendor/emitter/EventEmitter';
import convertRequestBody, {type RequestBody} from './convertRequestBody';
import NativeNetworkingIOS from './NativeNetworkingIOS';
import {type RCTNetworkingEventDefinitions} from './RCTNetworkingEventDefinitions.flow';
import {type NativeResponseType} from './XMLHttpRequest';
const RCTNetworking = {
addListener<K: $Keys<RCTNetworkingEventDefinitions>>(
eventType: K,
listener: (...RCTNetworkingEventDefinitions[K]) => mixed,
context?: mixed,
): EventSubscription {
// $FlowFixMe[incompatible-type]
return RCTDeviceEventEmitter.addListener(eventType, listener, context);
},
sendRequest(
method: string,
trackingName: string | void,
url: string,
headers: {...},
data: RequestBody,
responseType: NativeResponseType,
incrementalUpdates: boolean,
timeout: number,
callback: (requestId: number) => void,
withCredentials: boolean,
) {
const body = convertRequestBody(data);
const devToolsRequestId =
global.__NETWORK_REPORTER__?.createDevToolsRequestId();
NativeNetworkingIOS.sendRequest(
{
method,
url,
data: {...body, trackingName},
headers,
responseType,
incrementalUpdates,
timeout,
withCredentials,
unstable_devToolsRequestId: devToolsRequestId,
},
callback,
);
},
abortRequest(requestId: number) {
NativeNetworkingIOS.abortRequest(requestId);
},
clearCookies(callback: (result: boolean) => void) {
NativeNetworkingIOS.clearCookies(callback);
},
};
export default RCTNetworking;

View File

@@ -0,0 +1,17 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
// NOTE: This file supports backwards compatibility of subpath (deep) imports
// from 'react-native' with platform-specific extensions. It can be deleted
// once we remove the "./*" mapping from package.json "exports".
import RCTNetworking from './RCTNetworking';
export default RCTNetworking;

View File

@@ -0,0 +1,44 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
'use strict';
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
import type {RequestBody} from './convertRequestBody';
import type {RCTNetworkingEventDefinitions} from './RCTNetworkingEventDefinitions.flow';
import type {NativeResponseType} from './XMLHttpRequest';
declare const RCTNetworking: interface {
addListener<K: $Keys<RCTNetworkingEventDefinitions>>(
eventType: K,
// $FlowFixMe[invalid-computed-prop]
listener: (...RCTNetworkingEventDefinitions[K]) => mixed,
context?: mixed,
): EventSubscription,
sendRequest(
method: string,
trackingName: string | void,
url: string,
headers: {...},
data: RequestBody,
responseType: NativeResponseType,
incrementalUpdates: boolean,
timeout: number,
callback: (requestId: number) => void,
withCredentials: boolean,
): void,
abortRequest(requestId: number): void,
clearCookies(callback: (result: boolean) => void): void,
};
export default RCTNetworking;

View File

@@ -0,0 +1,896 @@
/*
* 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 <mutex>
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTAssert.h>
#import <React/RCTConvert.h>
#import <React/RCTLog.h>
#import <React/RCTNetworkTask.h>
#import <React/RCTUtils.h>
#import <React/RCTHTTPRequestHandler.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
#import "RCTInspectorNetworkReporter.h"
#import "RCTNetworkPlugins.h"
#import "RCTNetworking+Internal.h"
#import "RCTNetworking.h"
typedef RCTURLRequestCancellationBlock (^RCTHTTPQueryResult)(NSError *error, NSDictionary<NSString *, id> *result);
NSString *const RCTNetworkingPHUploadHackScheme = @"ph-upload";
@interface RCTNetworking () <NativeNetworkingIOSSpec>
- (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(NSDictionary<NSString *, id> *)data
devToolsRequestId:(NSString *)devToolsRequestId
callback:(RCTHTTPQueryResult)callback;
@end
/**
* Helper to convert FormData payloads into multipart/formdata requests.
*/
@interface RCTHTTPFormDataHelper : NSObject
@property (nonatomic, weak) RCTNetworking *networker;
@end
@implementation RCTHTTPFormDataHelper {
NSMutableArray<NSDictionary<NSString *, id> *> *_parts;
NSMutableData *_multipartBody;
RCTHTTPQueryResult _callback;
NSString *_boundary;
}
static NSString *RCTGenerateFormBoundary()
{
const size_t boundaryLength = 70;
const char *boundaryChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.";
char *bytes = (char *)malloc(boundaryLength);
if (!bytes) {
// CWE - 391 : Unchecked error condition
// https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html
// https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c
abort();
}
size_t charCount = strlen(boundaryChars);
for (int i = 0; i < boundaryLength; i++) {
bytes[i] = boundaryChars[arc4random_uniform((u_int32_t)charCount)];
}
return [[NSString alloc] initWithBytesNoCopy:bytes
length:boundaryLength
encoding:NSUTF8StringEncoding
freeWhenDone:YES];
}
- (RCTURLRequestCancellationBlock)process:(NSArray<NSDictionary *> *)formData
devToolsRequestId:(NSString *)devToolsRequestId
callback:(RCTHTTPQueryResult)callback
{
RCTAssertThread(_networker.methodQueue, @"process: must be called on request queue");
if (formData.count == 0) {
return callback(nil, nil);
}
_parts = [formData mutableCopy];
_callback = callback;
_multipartBody = [NSMutableData new];
_boundary = RCTGenerateFormBoundary();
for (NSUInteger i = 0; i < _parts.count; i++) {
NSString *uri = _parts[i][@"uri"];
if (uri && [[uri substringToIndex:@"ph:".length] caseInsensitiveCompare:@"ph:"] == NSOrderedSame) {
uri = [RCTNetworkingPHUploadHackScheme stringByAppendingString:[uri substringFromIndex:@"ph".length]];
NSMutableDictionary *mutableDict = [_parts[i] mutableCopy];
mutableDict[@"uri"] = uri;
_parts[i] = mutableDict;
}
}
return [_networker processDataForHTTPQuery:_parts[0]
devToolsRequestId:devToolsRequestId
callback:^(NSError *error, NSDictionary<NSString *, id> *result) {
return [self handleResult:result error:error devToolsRequestId:devToolsRequestId];
}];
}
- (RCTURLRequestCancellationBlock)handleResult:(NSDictionary<NSString *, id> *)result
error:(NSError *)error
devToolsRequestId:(NSString *)devToolsRequestId
{
RCTAssertThread(_networker.methodQueue, @"handleResult: must be called on request queue");
if (error) {
return _callback(error, nil);
}
// Start with boundary.
[_multipartBody
appendData:[[NSString stringWithFormat:@"--%@\r\n", _boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// Print headers.
NSMutableDictionary<NSString *, NSString *> *headers = [_parts[0][@"headers"] mutableCopy];
NSString *partContentType = result[@"contentType"];
if (partContentType != nil && ![partContentType isEqual:[NSNull null]]) {
headers[@"content-type"] = partContentType;
}
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
[self->_multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue]
dataUsingEncoding:NSUTF8StringEncoding]];
}];
// Add the body.
[_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[_multipartBody appendData:result[@"body"]];
[_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[_parts removeObjectAtIndex:0];
if (_parts.count) {
return [_networker processDataForHTTPQuery:_parts[0]
devToolsRequestId:devToolsRequestId
callback:^(NSError *err, NSDictionary<NSString *, id> *res) {
return [self handleResult:res error:err devToolsRequestId:devToolsRequestId];
}];
}
// We've processed the last item. Finish and return.
[_multipartBody
appendData:[[NSString stringWithFormat:@"--%@--\r\n", _boundary] dataUsingEncoding:NSUTF8StringEncoding]];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", _boundary];
return _callback(nil, @{@"body" : _multipartBody, @"contentType" : contentType});
}
@end
/**
* Bridge module that provides the JS interface to the network stack.
*/
@implementation RCTNetworking {
NSMutableDictionary<NSNumber *, RCTNetworkTask *> *_tasksByRequestID;
std::mutex _handlersLock;
NSArray<id<RCTURLRequestHandler>> *_handlers;
NSArray<id<RCTURLRequestHandler>> * (^_handlersProvider)(RCTModuleRegistry *);
NSMutableArray<id<RCTNetworkingRequestHandler>> *_requestHandlers;
NSMutableArray<id<RCTNetworkingResponseHandler>> *_responseHandlers;
NSMutableArray<id<RCTNetworkingTextResponseHandler>> *_textResponseHandlers;
dispatch_queue_t _requestQueue;
}
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
- (instancetype)init
{
return [super initWithDisabledObservation];
}
- (instancetype)initWithHandlersProvider:
(NSArray<id<RCTURLRequestHandler>> * (^)(RCTModuleRegistry *moduleRegistry))getHandlers
{
if (self = [self init]) {
_handlersProvider = getHandlers;
}
return self;
}
- (void)invalidate
{
[super invalidate];
std::lock_guard<std::mutex> lock(_handlersLock);
for (NSNumber *requestID in _tasksByRequestID) {
[_tasksByRequestID[requestID] cancel];
}
[_tasksByRequestID removeAllObjects];
for (id<RCTURLRequestHandler> handler in _handlers) {
if ([handler conformsToProtocol:@protocol(RCTInvalidating)]) {
[(id<RCTInvalidating>)handler invalidate];
}
}
_handlers = nil;
_requestHandlers = nil;
_responseHandlers = nil;
}
- (NSArray<NSString *> *)supportedEvents
{
return @[
@"didCompleteNetworkResponse",
@"didReceiveNetworkResponse",
@"didSendNetworkData",
@"didReceiveNetworkIncrementalData",
@"didReceiveNetworkDataProgress",
@"didReceiveNetworkData"
];
}
- (id<RCTURLRequestHandler>)handlerForRequest:(NSURLRequest *)request
{
if (!request.URL) {
return nil;
}
NSArray<id<RCTURLRequestHandler>> *handlers = [self prioritizedHandlers];
if (RCT_DEBUG) {
// Check for handler conflicts
float previousPriority = 0;
id<RCTURLRequestHandler> previousHandler = nil;
for (id<RCTURLRequestHandler> handler in handlers) {
float priority = [handler respondsToSelector:@selector(handlerPriority)] ? [handler handlerPriority] : 0;
if (previousHandler && priority < previousPriority) {
return previousHandler;
}
if ([handler canHandleRequest:request]) {
if (previousHandler) {
if (priority == previousPriority) {
RCTLogError(
@"The RCTURLRequestHandlers %@ and %@ both reported that"
" they can handle the request %@, and have equal priority"
" (%g). This could result in non-deterministic behavior.",
handler,
previousHandler,
request,
priority);
}
} else {
previousHandler = handler;
previousPriority = priority;
}
}
}
return previousHandler;
}
// Normal code path
for (id<RCTURLRequestHandler> handler in handlers) {
if ([handler canHandleRequest:request]) {
return handler;
}
}
return nil;
}
- (NSArray<id<RCTURLRequestHandler>> *)prioritizedHandlers
{
std::lock_guard<std::mutex> lock(_handlersLock);
if (_handlers) {
return _handlers;
}
NSArray<id<RCTURLRequestHandler>> *newHandlers = _handlersProvider
? _handlersProvider(self.moduleRegistry)
: [self.bridge modulesConformingToProtocol:@protocol(RCTURLRequestHandler)];
// Get handlers, sorted in reverse priority order (highest priority first)
newHandlers = [newHandlers
sortedArrayUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0;
float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0;
if (priorityA > priorityB) {
return NSOrderedAscending;
} else if (priorityA < priorityB) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}];
_handlers = newHandlers;
return newHandlers;
}
- (NSDictionary<NSString *, id> *)stripNullsInRequestHeaders:(NSDictionary<NSString *, id> *)headers
{
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:headers.count];
for (NSString *key in headers.allKeys) {
id val = headers[key];
if (val != [NSNull null]) {
result[key] = val;
}
}
return result;
}
- (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary<NSString *, id> *)query
devToolsRequestId:(NSString *)devToolsRequestId
completionBlock:(void (^)(NSURLRequest *request))block
{
RCTAssertThread(_methodQueue, @"buildRequest: must be called on request queue");
NSURL *URL = [RCTConvert NSURL:query[@"url"]]; // this is marked as nullable in JS, but should not be null
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
request.HTTPMethod = [RCTConvert NSString:RCTNilIfNull(query[@"method"])].uppercaseString ?: @"GET";
request.HTTPShouldHandleCookies = [RCTConvert BOOL:query[@"withCredentials"]];
if (request.HTTPShouldHandleCookies == YES) {
// Load and set the cookie header.
NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:URL];
request.allHTTPHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
}
// Set supplied headers.
NSDictionary *headers = [RCTConvert NSDictionary:query[@"headers"]];
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
if (value) {
[request addValue:[RCTConvert NSString:value] forHTTPHeaderField:key];
}
}];
request.timeoutInterval = [RCTConvert NSTimeInterval:query[@"timeout"]];
NSDictionary<NSString *, id> *data = [RCTConvert NSDictionary:RCTNilIfNull(query[@"data"])];
NSString *trackingName = data[@"trackingName"];
if (trackingName) {
[NSURLProtocol setProperty:trackingName forKey:@"trackingName" inRequest:request];
}
return [self processDataForHTTPQuery:data
devToolsRequestId:devToolsRequestId
callback:^(NSError *error, NSDictionary<NSString *, id> *result) {
if (error) {
RCTLogError(@"Error processing request body: %@", error);
// Ideally we'd circle back to JS here and notify an error/abort on the request.
return (RCTURLRequestCancellationBlock)nil;
}
request.HTTPBody = result[@"body"];
NSString *dataContentType = result[@"contentType"];
NSString *requestContentType = [request valueForHTTPHeaderField:@"Content-Type"];
BOOL isMultipart = ![dataContentType isEqual:[NSNull null]] &&
[dataContentType hasPrefix:@"multipart"];
// For multipart requests we need to override caller-specified content type with one
// from the data object, because it contains the boundary string
if (dataContentType && ([requestContentType length] == 0 || isMultipart)) {
[request setValue:dataContentType forHTTPHeaderField:@"Content-Type"];
}
// Gzip the request body
if ([request.allHTTPHeaderFields[@"Content-Encoding"] isEqualToString:@"gzip"]) {
request.HTTPBody = RCTGzipData(request.HTTPBody, -1 /* default */);
[request setValue:(@(request.HTTPBody.length)).description
forHTTPHeaderField:@"Content-Length"];
}
// NSRequest default cache policy violate on `If-None-Match`, should allow the request
// to get 304 from server.
if (request.allHTTPHeaderFields[@"If-None-Match"]) {
request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
}
dispatch_async(self->_methodQueue, ^{
block(request);
});
return (RCTURLRequestCancellationBlock)nil;
}];
}
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return [self handlerForRequest:request] != nil;
}
/**
* Process the 'data' part of an HTTP query.
*
* 'data' can be a JSON value of the following forms:
*
* - {"string": "..."}: a simple JS string that will be UTF-8 encoded and sent as the body
*
* - {"uri": "some-uri://..."}: reference to a system resource, e.g. an image in the asset library
*
* - {"formData": [...]}: list of data payloads that will be combined into a multipart/form-data request
*
* - {"blob": {...}}: an object representing a blob
*
* If successful, the callback be called with a result dictionary containing the following (optional) keys:
*
* - @"body" (NSData): the body of the request
*
* - @"contentType" (NSString): the content type header of the request
*
*/
- (RCTURLRequestCancellationBlock)
processDataForHTTPQuery:(nullable NSDictionary<NSString *, id> *)query
devToolsRequestId:(NSString *)devToolsRequestId
callback:(RCTURLRequestCancellationBlock (^)(NSError *error, NSDictionary<NSString *, id> *result))
callback
{
RCTAssertThread(_methodQueue, @"processDataForHTTPQuery: must be called on request queue");
if (!query) {
return callback(nil, nil);
}
for (id<RCTNetworkingRequestHandler> handler in _requestHandlers) {
if ([handler canHandleNetworkingRequest:query]) {
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
NSDictionary *body = [handler handleNetworkingRequest:query];
if (body) {
return callback(nil, body);
}
}
}
NSData *body = [RCTConvert NSData:query[@"string"]];
if (body) {
return callback(nil, @{@"body" : body});
}
NSString *base64String = [RCTConvert NSString:query[@"base64"]];
if (base64String) {
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64String options:0];
return callback(nil, @{@"body" : data});
}
NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]];
if (request) {
__block RCTURLRequestCancellationBlock cancellationBlock = nil;
RCTNetworkTask *task = [self
networkTaskWithDevToolsRequestId:devToolsRequestId
request:request
completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
dispatch_async(self->_methodQueue, ^{
cancellationBlock = callback(
error,
data ? @{@"body" : data, @"contentType" : RCTNullIfNil(response.MIMEType)} : nil);
});
}];
[task start];
__weak RCTNetworkTask *weakTask = task;
return ^{
[weakTask cancel];
if (facebook::react::ReactNativeFeatureFlags::enableNetworkEventReporting()) {
[RCTInspectorNetworkReporter reportRequestFailed:devToolsRequestId cancelled:YES];
}
if (cancellationBlock) {
cancellationBlock();
}
};
}
NSArray<NSDictionary *> *formData = [RCTConvert NSDictionaryArray:query[@"formData"]];
if (formData) {
RCTHTTPFormDataHelper *formDataHelper = [RCTHTTPFormDataHelper new];
formDataHelper.networker = self;
return [formDataHelper process:formData devToolsRequestId:devToolsRequestId callback:callback];
}
// Nothing in the data payload, at least nothing we could understand anyway.
// Ignore and treat it as if it were null.
return callback(nil, nil);
}
+ (NSString *)decodeTextData:(NSData *)data
fromResponse:(NSURLResponse *)response
withCarryData:(NSMutableData *)inputCarryData
{
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
NSMutableData *currentCarryData = inputCarryData ?: [NSMutableData new];
[currentCarryData appendData:data];
// Attempt to decode text
NSString *encodedResponse = [[NSString alloc] initWithData:currentCarryData encoding:encoding];
if (!encodedResponse && data.length > 0) {
if (encoding == NSUTF8StringEncoding && inputCarryData) {
// If decode failed, we attempt to trim broken character bytes from the data.
// At this time, only UTF-8 support is enabled. Multibyte encodings, such as UTF-16 and UTF-32, require a lot of
// additional work to determine wether BOM was included in the first data packet. If so, save it, and attach it to
// each new data packet. If not, an encoding has to be selected with a suitable byte order (for ARM iOS, it would
// be little endianness).
CFStringEncoding cfEncoding = CFStringConvertNSStringEncodingToEncoding(encoding);
// Taking a single unichar is not good enough, due to Unicode combining character sequences or characters outside
// the BMP. See https://www.objc.io/issues/9-strings/unicode/#common-pitfalls We'll attempt with a sequence of two
// characters, the most common combining character sequence and characters outside the BMP (emojis).
CFIndex maxCharLength = CFStringGetMaximumSizeForEncoding(2, cfEncoding);
NSUInteger removedBytes = 1;
while (removedBytes < maxCharLength) {
encodedResponse = [[NSString alloc]
initWithData:[currentCarryData subdataWithRange:NSMakeRange(0, currentCarryData.length - removedBytes)]
encoding:encoding];
if (encodedResponse != nil) {
break;
}
removedBytes += 1;
}
} else {
// We don't have an encoding, or the encoding is incorrect, so now we try to guess
[NSString stringEncodingForData:data
encodingOptions:@{NSStringEncodingDetectionSuggestedEncodingsKey : @[ @(encoding) ]}
convertedString:&encodedResponse
usedLossyConversion:NULL];
}
}
if (inputCarryData) {
NSUInteger encodedResponseLength = [encodedResponse dataUsingEncoding:encoding].length;
// Ensure a valid subrange exists within currentCarryData
if (currentCarryData.length >= encodedResponseLength) {
NSData *newCarryData = [currentCarryData
subdataWithRange:NSMakeRange(encodedResponseLength, currentCarryData.length - encodedResponseLength)];
[inputCarryData setData:newCarryData];
} else {
[inputCarryData setLength:0];
}
}
return encodedResponse;
}
- (void)sendData:(NSData *)data
responseType:(NSString *)responseType
response:(NSURLResponse *)response
forTask:(RCTNetworkTask *)task
{
RCTAssertThread(_methodQueue, @"sendData: must be called on request queue");
id responseData = nil;
for (id<RCTNetworkingResponseHandler> handler in _responseHandlers) {
if ([handler canHandleNetworkingResponse:responseType]) {
responseData = [handler handleNetworkingResponse:response data:data];
break;
}
}
if (!responseData) {
if (data.length == 0) {
return;
}
if ([responseType isEqualToString:@"text"]) {
// No carry storage is required here because the entire data has been loaded.
responseData = [RCTNetworking decodeTextData:data fromResponse:task.response withCarryData:nil];
if (!responseData) {
RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
return;
}
} else if ([responseType isEqualToString:@"base64"]) {
responseData = [data base64EncodedStringWithOptions:0];
} else {
RCTLogWarn(@"Invalid responseType: %@", responseType);
return;
}
}
if (facebook::react::ReactNativeFeatureFlags::enableNetworkEventReporting()) {
id responseDataForPreview;
if ([responseType isEqualToString:@"blob"]) {
responseDataForPreview = data;
} else if ([responseData isKindOfClass:[NSString class]]) {
responseDataForPreview = responseData;
}
bool base64Encoded = [responseType isEqualToString:@"base64"] || [responseType isEqualToString:@"blob"];
[RCTInspectorNetworkReporter maybeStoreResponseBody:task.devToolsRequestId
data:responseDataForPreview
base64Encoded:base64Encoded];
}
[self sendEventWithName:@"didReceiveNetworkData" body:@[ task.requestID, responseData ]];
}
- (void)sendRequest:(NSURLRequest *)request
responseType:(NSString *)responseType
incrementalUpdates:(BOOL)incrementalUpdates
responseSender:(RCTResponseSenderBlock)responseSender
devToolsRequestId:(NSString *)devToolsRequestId
{
RCTAssertThread(_methodQueue, @"sendRequest: must be called on request queue");
__weak __typeof(self) weakSelf = self;
__block RCTNetworkTask *task;
RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) {
NSArray *responseJSON = @[ task.requestID, @((double)progress), @((double)total) ];
[weakSelf sendEventWithName:@"didSendNetworkData" body:responseJSON];
};
RCTURLRequestResponseBlock responseBlock = ^(NSURLResponse *response) {
NSDictionary<NSString *, NSString *> *headers;
NSInteger status;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
headers = httpResponse.allHeaderFields ?: @{};
status = httpResponse.statusCode;
} else {
// Other HTTP-like request
headers = response.MIMEType ? @{@"Content-Type" : response.MIMEType} : @{};
status = 200;
}
id responseURL = response.URL ? response.URL.absoluteString : [NSNull null];
NSArray<id> *responseJSON = @[ task.requestID, @(status), headers, responseURL ];
if (facebook::react::ReactNativeFeatureFlags::enableNetworkEventReporting()) {
[RCTInspectorNetworkReporter reportResponseStart:task.devToolsRequestId
response:response
statusCode:status
headers:headers];
}
[weakSelf sendEventWithName:@"didReceiveNetworkResponse" body:responseJSON];
};
// XHR does not allow you to peek at xhr.response before the response is
// finished. Only when xhr.responseType is set to ''/'text', consumers may
// peek at xhr.responseText. So unless the requested responseType is 'text',
// we only send progress updates and not incremental data updates to JS here.
RCTURLRequestIncrementalDataBlock incrementalDataBlock = nil;
RCTURLRequestProgressBlock downloadProgressBlock = nil;
if (incrementalUpdates) {
if ([responseType isEqualToString:@"text"]) {
// We need this to carry over bytes, which could not be decoded into text (such as broken UTF-8 characters).
// The incremental data block holds the ownership of this object, and will be released upon release of the block.
NSMutableData *incrementalDataCarry = [NSMutableData new];
incrementalDataBlock = ^(NSData *data, int64_t progress, int64_t total) {
NSUInteger initialCarryLength = incrementalDataCarry.length;
id responseString = [RCTNetworking decodeTextData:data
fromResponse:task.response
withCarryData:incrementalDataCarry];
if (!responseString) {
RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
return;
}
// Update progress to include the previous carry length and reduce the current carry length.
NSArray<id> *responseJSON = @[
task.requestID,
responseString,
@(progress + initialCarryLength - incrementalDataCarry.length),
@(total)
];
if (facebook::react::ReactNativeFeatureFlags::enableNetworkEventReporting()) {
[RCTInspectorNetworkReporter reportDataReceived:task.devToolsRequestId data:data];
[RCTInspectorNetworkReporter maybeStoreResponseBodyIncremental:task.devToolsRequestId data:responseString];
}
[weakSelf sendEventWithName:@"didReceiveNetworkIncrementalData" body:responseJSON];
};
} else {
downloadProgressBlock = ^(int64_t progress, int64_t total) {
NSArray<id> *responseJSON = @[ task.requestID, @(progress), @(total) ];
[weakSelf sendEventWithName:@"didReceiveNetworkDataProgress" body:responseJSON];
};
}
}
RCTURLRequestCompletionBlock completionBlock = ^(NSURLResponse *response, NSData *data, NSError *error) {
__typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
// Unless we were sending incremental (text) chunks to JS, all along, now
// is the time to send the request body to JS.
if (!(incrementalUpdates && [responseType isEqualToString:@"text"])) {
[strongSelf sendData:data responseType:responseType response:response forTask:task];
}
if ([responseType isEqualToString:@"text"]) {
for (id<RCTNetworkingTextResponseHandler> handler in strongSelf->_textResponseHandlers) {
if ([handler canHandleNetworkingTextResponseForRequest:task.request]) {
NSString *responseString = [RCTNetworking decodeTextData:data
fromResponse:task.response
withCarryData:[NSMutableData new]];
[handler handleNetworkingResponseText:responseString request:task.request];
}
}
}
NSArray *responseJSON =
@[ task.requestID, RCTNullIfNil(error.localizedDescription), error.code == kCFURLErrorTimedOut ? @YES : @NO ];
if (facebook::react::ReactNativeFeatureFlags::enableNetworkEventReporting()) {
if (error != nullptr) {
[RCTInspectorNetworkReporter reportRequestFailed:task.devToolsRequestId cancelled:NO];
} else {
[RCTInspectorNetworkReporter reportResponseEnd:task.devToolsRequestId encodedDataLength:data.length];
}
}
[strongSelf sendEventWithName:@"didCompleteNetworkResponse" body:responseJSON];
[strongSelf->_tasksByRequestID removeObjectForKey:task.requestID];
};
task = [self networkTaskWithDevToolsRequestId:devToolsRequestId request:request completionBlock:completionBlock];
task.downloadProgressBlock = downloadProgressBlock;
task.incrementalDataBlock = incrementalDataBlock;
task.responseBlock = responseBlock;
task.uploadProgressBlock = uploadProgressBlock;
if (task.requestID) {
if (!_tasksByRequestID) {
_tasksByRequestID = [NSMutableDictionary new];
}
_tasksByRequestID[task.requestID] = task;
responseSender(@[ task.requestID ]);
if (facebook::react::ReactNativeFeatureFlags::enableNetworkEventReporting()) {
[RCTInspectorNetworkReporter reportRequestStart:task.devToolsRequestId
request:request
encodedDataLength:task.response.expectedContentLength];
[RCTInspectorNetworkReporter reportConnectionTiming:task.devToolsRequestId request:task.request];
}
}
[task start];
}
#pragma mark - Public API
- (void)addRequestHandler:(id<RCTNetworkingRequestHandler>)handler
{
if (!_requestHandlers) {
_requestHandlers = [NSMutableArray new];
}
[_requestHandlers addObject:handler];
}
- (void)addResponseHandler:(id<RCTNetworkingResponseHandler>)handler
{
if (!_responseHandlers) {
_responseHandlers = [NSMutableArray new];
}
[_responseHandlers addObject:handler];
}
- (void)addTextResponseHandler:(id<RCTNetworkingTextResponseHandler>)handler
{
if (!_textResponseHandlers) {
_textResponseHandlers = [NSMutableArray new];
}
[_textResponseHandlers addObject:handler];
}
- (void)removeRequestHandler:(id<RCTNetworkingRequestHandler>)handler
{
[_requestHandlers removeObject:handler];
}
- (void)removeResponseHandler:(id<RCTNetworkingResponseHandler>)handler
{
[_responseHandlers removeObject:handler];
}
- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request
completionBlock:(RCTURLRequestCompletionBlock)completionBlock
{
return [self networkTaskWithDevToolsRequestId:nil request:request completionBlock:completionBlock];
}
- (RCTNetworkTask *)networkTaskWithDevToolsRequestId:(NSString *)devToolsRequestId
request:(NSURLRequest *)request
completionBlock:(RCTURLRequestCompletionBlock)completionBlock
{
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
if (!handler) {
RCTLogError(@"No suitable URL request handler found for %@", request.URL);
return nil;
}
RCTNetworkTask *task = [[RCTNetworkTask alloc] initWithDevToolsRequestId:devToolsRequestId
request:request
handler:handler
callbackQueue:_methodQueue];
task.completionBlock = completionBlock;
return task;
}
#pragma mark - JS API
RCT_EXPORT_METHOD(
sendRequest : (JS::NativeNetworkingIOS::SpecSendRequestQuery &)query callback : (RCTResponseSenderBlock)
responseSender)
{
NSString *method = query.method();
NSString *url = query.url();
id<NSObject> data = query.data();
id<NSObject> headers = query.headers();
NSString *queryResponseType = query.responseType();
bool queryIncrementalUpdates = query.incrementalUpdates();
double timeout = query.timeout();
bool withCredentials = query.withCredentials();
NSString *devToolsRequestId = query.unstable_devToolsRequestId();
dispatch_async(_methodQueue, ^{
NSDictionary *queryDict = @{
@"method" : method,
@"url" : url,
@"data" : data,
@"headers" : headers,
@"responseType" : queryResponseType,
@"incrementalUpdates" : @(queryIncrementalUpdates),
@"timeout" : @(timeout),
@"withCredentials" : @(withCredentials),
};
// TODO: buildRequest returns a cancellation block, but there's currently
// no way to invoke it, if, for example the request is cancelled while
// loading a large file to build the request body
[self buildRequest:queryDict
devToolsRequestId:devToolsRequestId
completionBlock:^(NSURLRequest *request) {
NSString *responseType = [RCTConvert NSString:queryDict[@"responseType"]];
BOOL incrementalUpdates = [RCTConvert BOOL:queryDict[@"incrementalUpdates"]];
[self sendRequest:request
responseType:responseType
incrementalUpdates:incrementalUpdates
responseSender:responseSender
devToolsRequestId:devToolsRequestId];
}];
});
}
RCT_EXPORT_METHOD(abortRequest : (double)requestID)
{
dispatch_async(_methodQueue, ^{
[self->_tasksByRequestID[[NSNumber numberWithDouble:requestID]] cancel];
[self->_tasksByRequestID removeObjectForKey:[NSNumber numberWithDouble:requestID]];
});
}
RCT_EXPORT_METHOD(clearCookies : (RCTResponseSenderBlock)responseSender)
{
dispatch_async(_methodQueue, ^{
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
if (!storage.cookies.count) {
responseSender(@[ @NO ]);
return;
}
for (NSHTTPCookie *cookie in storage.cookies) {
[storage deleteCookie:cookie];
}
responseSender(@[ @YES ]);
});
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeNetworkingIOSSpecJSI>(params);
}
@end
@implementation RCTBridge (RCTNetworking)
- (RCTNetworking *)networking
{
return [self moduleForClass:[RCTNetworking class]];
}
@end
@implementation RCTBridgeProxy (RCTNetworking)
- (RCTNetworking *)networking
{
return [self moduleForClass:[RCTNetworking class]];
}
@end
Class RCTNetworkingCls(void)
{
return RCTNetworking.class;
}

View File

@@ -0,0 +1,57 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
'use strict';
export type RCTNetworkingEventDefinitions = $ReadOnly<{
didSendNetworkData: [
[
number, // requestId
number, // progress
number, // total
],
],
didReceiveNetworkResponse: [
[
number, // requestId
number, // status
?{[string]: string}, // responseHeaders
?string, // responseURL
],
],
didReceiveNetworkData: [
[
number, // requestId
string, // response
],
],
didReceiveNetworkIncrementalData: [
[
number, // requestId
string, // responseText
number, // progress
number, // total
],
],
didReceiveNetworkDataProgress: [
[
number, // requestId
number, // loaded
number, // total
],
],
didCompleteNetworkResponse: [
[
number, // requestId
string, // error
boolean, // timeOutError
],
],
}>;

View File

@@ -0,0 +1,57 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
header_search_paths = [
"\"${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\"",
]
Pod::Spec.new do |s|
s.name = "React-RCTNetwork"
s.version = version
s.summary = "The networking library of React Native."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.compiler_flags = '-Wno-nullability-completeness'
s.source = source
s.source_files = podspec_sources("*.{m,mm}", "")
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
s.header_dir = "RCTNetwork"
s.pod_target_xcconfig = {
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' ')
}
s.frameworks = "MobileCoreServices"
s.dependency "RCTTypeSafety"
s.dependency "React-jsi"
s.dependency "React-Core/RCTNetworkHeaders"
add_dependency(s, "React-debug")
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
add_dependency(s, "React-featureflags")
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectornetwork", :framework_name => 'jsinspector_modernnetwork')
add_dependency(s, "React-NativeModulesApple", :additional_framework_paths => ["build/generated/ios"])
add_dependency(s, "React-networking", :framework_name => 'React_networking')
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,769 @@
/**
* 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.
*
* @flow
* @format
*/
'use strict';
import type {
EventCallback,
EventListener,
} from '../../src/private/webapis/dom/events/EventTarget';
import type {IPerformanceLogger} from '../Utilities/createPerformanceLogger';
import Event from '../../src/private/webapis/dom/events/Event';
import {
getEventHandlerAttribute,
setEventHandlerAttribute,
} from '../../src/private/webapis/dom/events/EventHandlerAttributes';
import EventTarget from '../../src/private/webapis/dom/events/EventTarget';
import {dispatchTrustedEvent} from '../../src/private/webapis/dom/events/internals/EventTargetInternals';
import ProgressEvent from '../../src/private/webapis/xhr/events/ProgressEvent';
import {type EventSubscription} from '../vendor/emitter/EventEmitter';
const BlobManager = require('../Blob/BlobManager').default;
const GlobalPerformanceLogger =
require('../Utilities/GlobalPerformanceLogger').default;
const RCTNetworking = require('./RCTNetworking').default;
const base64 = require('base64-js');
const invariant = require('invariant');
const DEBUG_NETWORK_SEND_DELAY: false = false; // Set to a number of milliseconds when debugging
export type NativeResponseType = 'base64' | 'blob' | 'text';
export type ResponseType =
| ''
| 'arraybuffer'
| 'blob'
| 'document'
| 'json'
| 'text';
export type Response = ?Object | string;
type XHRInterceptor = interface {
requestSent(id: number, url: string, method: string, headers: Object): void,
responseReceived(
id: number,
url: string,
status: number,
headers: Object,
): void,
dataReceived(id: number, data: string): void,
loadingFinished(id: number, encodedDataLength: number): void,
loadingFailed(id: number, error: string): void,
};
// The native blob module is optional so inject it here if available.
if (BlobManager.isAvailable) {
BlobManager.addNetworkingHandler();
}
const UNSENT = 0;
const OPENED = 1;
const HEADERS_RECEIVED = 2;
const LOADING = 3;
const DONE = 4;
const SUPPORTED_RESPONSE_TYPES = {
arraybuffer: typeof global.ArrayBuffer === 'function',
blob: typeof global.Blob === 'function',
document: false,
json: true,
text: true,
'': true,
};
class XMLHttpRequestEventTarget extends EventTarget {
get onload(): EventCallback | null {
return getEventHandlerAttribute(this, 'load');
}
set onload(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'load', listener);
}
get onloadstart(): EventCallback | null {
return getEventHandlerAttribute(this, 'loadstart');
}
set onloadstart(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'loadstart', listener);
}
get onprogress(): EventCallback | null {
return getEventHandlerAttribute(this, 'progress');
}
set onprogress(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'progress', listener);
}
get ontimeout(): EventCallback | null {
return getEventHandlerAttribute(this, 'timeout');
}
set ontimeout(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'timeout', listener);
}
get onerror(): EventCallback | null {
return getEventHandlerAttribute(this, 'error');
}
set onerror(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'error', listener);
}
get onabort(): EventCallback | null {
return getEventHandlerAttribute(this, 'abort');
}
set onabort(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'abort', listener);
}
get onloadend(): EventCallback | null {
return getEventHandlerAttribute(this, 'loadend');
}
set onloadend(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'loadend', listener);
}
}
/**
* Shared base for platform-specific XMLHttpRequest implementations.
*/
class XMLHttpRequest extends EventTarget {
static UNSENT: number = UNSENT;
static OPENED: number = OPENED;
static HEADERS_RECEIVED: number = HEADERS_RECEIVED;
static LOADING: number = LOADING;
static DONE: number = DONE;
static _interceptor: ?XHRInterceptor = null;
UNSENT: number = UNSENT;
OPENED: number = OPENED;
HEADERS_RECEIVED: number = HEADERS_RECEIVED;
LOADING: number = LOADING;
DONE: number = DONE;
readyState: number = UNSENT;
responseHeaders: ?Object;
status: number = 0;
timeout: number = 0;
responseURL: ?string;
withCredentials: boolean = true;
upload: XMLHttpRequestEventTarget = new XMLHttpRequestEventTarget();
_requestId: ?number;
_subscriptions: Array<EventSubscription>;
_aborted: boolean = false;
_cachedResponse: Response;
_hasError: boolean = false;
_headers: Object;
_lowerCaseResponseHeaders: Object;
_method: ?string = null;
_perfKey: ?string = null;
_responseType: ResponseType;
_response: string = '';
_sent: boolean;
_url: ?string = null;
_timedOut: boolean = false;
_trackingName: ?string;
_incrementalEvents: boolean = false;
_startTime: ?number = null;
_performanceLogger: IPerformanceLogger = GlobalPerformanceLogger;
static __setInterceptor_DO_NOT_USE(interceptor: ?XHRInterceptor) {
XMLHttpRequest._interceptor = interceptor;
}
constructor() {
super();
this._reset();
}
_reset(): void {
this.readyState = this.UNSENT;
this.responseHeaders = undefined;
this.status = 0;
delete this.responseURL;
this._requestId = null;
this._cachedResponse = undefined;
this._hasError = false;
this._headers = {};
this._response = '';
this._responseType = '';
this._sent = false;
this._lowerCaseResponseHeaders = {};
this._clearSubscriptions();
this._timedOut = false;
}
get responseType(): ResponseType {
return this._responseType;
}
set responseType(responseType: ResponseType): void {
if (this._sent) {
throw new Error(
"Failed to set the 'responseType' property on 'XMLHttpRequest': The " +
'response type cannot be set after the request has been sent.',
);
}
if (!SUPPORTED_RESPONSE_TYPES.hasOwnProperty(responseType)) {
console.warn(
`The provided value '${responseType}' is not a valid 'responseType'.`,
);
return;
}
// redboxes early, e.g. for 'arraybuffer' on ios 7
invariant(
SUPPORTED_RESPONSE_TYPES[responseType] || responseType === 'document',
`The provided value '${responseType}' is unsupported in this environment.`,
);
if (responseType === 'blob') {
invariant(
BlobManager.isAvailable,
'Native module BlobModule is required for blob support',
);
}
this._responseType = responseType;
}
get responseText(): string {
if (this._responseType !== '' && this._responseType !== 'text') {
throw new Error(
"The 'responseText' property is only available if 'responseType' " +
`is set to '' or 'text', but it is '${this._responseType}'.`,
);
}
if (this.readyState < LOADING) {
return '';
}
return this._response;
}
get response(): Response {
const {responseType} = this;
if (responseType === '' || responseType === 'text') {
return this.readyState < LOADING || this._hasError ? '' : this._response;
}
if (this.readyState !== DONE) {
return null;
}
if (this._cachedResponse !== undefined) {
return this._cachedResponse;
}
switch (responseType) {
case 'document':
this._cachedResponse = null;
break;
case 'arraybuffer':
this._cachedResponse = base64.toByteArray(this._response).buffer;
break;
case 'blob':
if (typeof this._response === 'object' && this._response) {
this._cachedResponse = BlobManager.createFromOptions(this._response);
} else if (this._response === '') {
this._cachedResponse = BlobManager.createFromParts([]);
} else {
throw new Error(
'Invalid response for blob - expecting object, was ' +
`${typeof this._response}: ${this._response.trim()}`,
);
}
break;
case 'json':
try {
this._cachedResponse = JSON.parse(this._response);
} catch (_) {
this._cachedResponse = null;
}
break;
default:
this._cachedResponse = null;
}
return this._cachedResponse;
}
// exposed for testing
__didCreateRequest(requestId: number): void {
this._requestId = requestId;
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.requestSent(
requestId,
this._url || '',
this._method || 'GET',
this._headers,
);
}
// exposed for testing
__didUploadProgress(
requestId: number,
progress: number,
total: number,
): void {
if (requestId === this._requestId) {
dispatchTrustedEvent(
this.upload,
new ProgressEvent('progress', {
lengthComputable: true,
loaded: progress,
total,
}),
);
}
}
__didReceiveResponse(
requestId: number,
status: number,
responseHeaders: ?Object,
responseURL: ?string,
): void {
if (requestId === this._requestId) {
this._perfKey != null &&
this._performanceLogger.stopTimespan(this._perfKey);
this.status = status;
this.setResponseHeaders(responseHeaders);
this.setReadyState(this.HEADERS_RECEIVED);
if (responseURL || responseURL === '') {
this.responseURL = responseURL;
} else {
delete this.responseURL;
}
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.responseReceived(
requestId,
responseURL || this._url || '',
status,
responseHeaders || {},
);
}
}
__didReceiveData(requestId: number, response: string): void {
if (requestId !== this._requestId) {
return;
}
this._response = response;
this._cachedResponse = undefined; // force lazy recomputation
this.setReadyState(this.LOADING);
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.dataReceived(requestId, response);
}
__didReceiveIncrementalData(
requestId: number,
responseText: string,
progress: number,
total: number,
) {
if (requestId !== this._requestId) {
return;
}
if (!this._response) {
this._response = responseText;
} else {
this._response += responseText;
}
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.dataReceived(requestId, responseText);
this.setReadyState(this.LOADING);
this.__didReceiveDataProgress(requestId, progress, total);
}
__didReceiveDataProgress(
requestId: number,
loaded: number,
total: number,
): void {
if (requestId !== this._requestId) {
return;
}
dispatchTrustedEvent(
this,
new ProgressEvent('progress', {
lengthComputable: total >= 0,
loaded,
total,
}),
);
}
// exposed for testing
__didCompleteResponse(
requestId: number,
error: string,
timeOutError: boolean,
): void {
if (requestId === this._requestId) {
if (error) {
if (this._responseType === '' || this._responseType === 'text') {
this._response = error;
}
this._hasError = true;
if (timeOutError) {
this._timedOut = true;
}
}
this._clearSubscriptions();
this._requestId = null;
this.setReadyState(this.DONE);
if (error) {
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.loadingFailed(requestId, error);
} else {
XMLHttpRequest._interceptor &&
XMLHttpRequest._interceptor.loadingFinished(
requestId,
this._response.length,
);
}
}
}
_clearSubscriptions(): void {
(this._subscriptions || []).forEach(sub => {
if (sub) {
sub.remove();
}
});
this._subscriptions = [];
}
getAllResponseHeaders(): ?string {
if (!this.responseHeaders) {
// according to the spec, return null if no response has been received
return null;
}
// Assign to non-nullable local variable.
const responseHeaders = this.responseHeaders;
const unsortedHeaders: Map<
string,
{lowerHeaderName: string, upperHeaderName: string, headerValue: string},
> = new Map();
for (const rawHeaderName of Object.keys(responseHeaders)) {
const headerValue = responseHeaders[rawHeaderName];
const lowerHeaderName = rawHeaderName.toLowerCase();
const header = unsortedHeaders.get(lowerHeaderName);
if (header) {
header.headerValue += ', ' + headerValue;
unsortedHeaders.set(lowerHeaderName, header);
} else {
unsortedHeaders.set(lowerHeaderName, {
lowerHeaderName,
upperHeaderName: rawHeaderName.toUpperCase(),
headerValue,
});
}
}
// Sort in ascending order, with a being less than b if a's name is legacy-uppercased-byte less than b's name.
const sortedHeaders = [...unsortedHeaders.values()].sort((a, b) => {
if (a.upperHeaderName < b.upperHeaderName) {
return -1;
}
if (a.upperHeaderName > b.upperHeaderName) {
return 1;
}
return 0;
});
// Combine into single text response.
return (
sortedHeaders
.map(header => {
return header.lowerHeaderName + ': ' + header.headerValue;
})
.join('\r\n') + '\r\n'
);
}
getResponseHeader(header: string): ?string {
const value = this._lowerCaseResponseHeaders[header.toLowerCase()];
return value !== undefined ? value : null;
}
setRequestHeader(header: string, value: any): void {
if (this.readyState !== this.OPENED) {
throw new Error('Request has not been opened');
}
this._headers[header.toLowerCase()] = String(value);
}
/**
* Custom extension for tracking origins of request.
*/
setTrackingName(trackingName: ?string): XMLHttpRequest {
this._trackingName = trackingName;
return this;
}
/**
* Custom extension for setting a custom performance logger
*/
setPerformanceLogger(performanceLogger: IPerformanceLogger): XMLHttpRequest {
this._performanceLogger = performanceLogger;
return this;
}
open(method: string, url: string, async: ?boolean): void {
/* Other optional arguments are not supported yet */
if (this.readyState !== this.UNSENT) {
throw new Error('Cannot open, already sending');
}
if (async !== undefined && !async) {
// async is default
throw new Error('Synchronous http requests are not supported');
}
if (!url) {
throw new Error('Cannot load an empty url');
}
this._method = method.toUpperCase();
this._url = url;
this._aborted = false;
this.setReadyState(this.OPENED);
}
send(data: any): void {
if (this.readyState !== this.OPENED) {
throw new Error('Request has not been opened');
}
if (this._sent) {
throw new Error('Request has already been sent');
}
this._sent = true;
const incrementalEvents =
this._incrementalEvents || !!this.onreadystatechange || !!this.onprogress;
this._subscriptions.push(
RCTNetworking.addListener('didSendNetworkData', args =>
this.__didUploadProgress(...args),
),
);
this._subscriptions.push(
RCTNetworking.addListener('didReceiveNetworkResponse', args =>
this.__didReceiveResponse(...args),
),
);
this._subscriptions.push(
RCTNetworking.addListener('didReceiveNetworkData', args =>
this.__didReceiveData(...args),
),
);
this._subscriptions.push(
RCTNetworking.addListener('didReceiveNetworkIncrementalData', args =>
this.__didReceiveIncrementalData(...args),
),
);
this._subscriptions.push(
RCTNetworking.addListener('didReceiveNetworkDataProgress', args =>
this.__didReceiveDataProgress(...args),
),
);
this._subscriptions.push(
RCTNetworking.addListener('didCompleteNetworkResponse', args =>
this.__didCompleteResponse(...args),
),
);
let nativeResponseType: NativeResponseType = 'text';
if (this._responseType === 'arraybuffer') {
nativeResponseType = 'base64';
}
if (this._responseType === 'blob') {
nativeResponseType = 'blob';
}
const doSend = () => {
const friendlyName = this._trackingName ?? this._url;
this._perfKey = 'network_XMLHttpRequest_' + String(friendlyName);
this._performanceLogger.startTimespan(this._perfKey);
this._startTime = performance.now();
invariant(
this._method,
'XMLHttpRequest method needs to be defined (%s).',
friendlyName,
);
invariant(
this._url,
'XMLHttpRequest URL needs to be defined (%s).',
friendlyName,
);
RCTNetworking.sendRequest(
this._method,
this._trackingName ?? undefined,
this._url,
this._headers,
data,
nativeResponseType,
incrementalEvents,
this.timeout,
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
this.__didCreateRequest.bind(this),
this.withCredentials,
);
};
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
if (DEBUG_NETWORK_SEND_DELAY) {
setTimeout(doSend, DEBUG_NETWORK_SEND_DELAY);
} else {
doSend();
}
}
abort(): void {
this._aborted = true;
if (this._requestId) {
RCTNetworking.abortRequest(this._requestId);
}
// only call onreadystatechange if there is something to abort,
// below logic is per spec
if (
!(
this.readyState === this.UNSENT ||
(this.readyState === this.OPENED && !this._sent) ||
this.readyState === this.DONE
)
) {
this._reset();
this.setReadyState(this.DONE);
}
// Reset again after, in case modified in handler
this._reset();
}
setResponseHeaders(responseHeaders: ?Object): void {
this.responseHeaders = responseHeaders || null;
const headers = responseHeaders || {};
this._lowerCaseResponseHeaders = Object.keys(headers).reduce<{
[string]: any,
}>((lcaseHeaders, headerName) => {
// $FlowFixMe[invalid-computed-prop]
lcaseHeaders[headerName.toLowerCase()] = headers[headerName];
return lcaseHeaders;
}, {});
}
setReadyState(newState: number): void {
this.readyState = newState;
dispatchTrustedEvent(this, new Event('readystatechange'));
if (newState === this.DONE) {
if (this._aborted) {
dispatchTrustedEvent(this, new Event('abort'));
} else if (this._hasError) {
if (this._timedOut) {
dispatchTrustedEvent(this, new Event('timeout'));
} else {
dispatchTrustedEvent(this, new Event('error'));
}
} else {
dispatchTrustedEvent(this, new Event('load'));
}
dispatchTrustedEvent(this, new Event('loadend'));
}
}
addEventListener(type: string, listener: EventListener | null): void {
// If we dont' have a 'readystatechange' event handler, we don't
// have to send repeated LOADING events with incremental updates
// to responseText, which will avoid a bunch of native -> JS
// bridge traffic.
if (type === 'readystatechange' || type === 'progress') {
this._incrementalEvents = true;
}
super.addEventListener(type, listener);
}
/*
* `on<event>` event handling (without JS prototype magic).
*/
get onabort(): EventCallback | null {
return getEventHandlerAttribute(this, 'abort');
}
set onabort(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'abort', listener);
}
get onerror(): EventCallback | null {
return getEventHandlerAttribute(this, 'error');
}
set onerror(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'error', listener);
}
get onload(): EventCallback | null {
return getEventHandlerAttribute(this, 'load');
}
set onload(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'load', listener);
}
get onloadstart(): EventCallback | null {
return getEventHandlerAttribute(this, 'loadstart');
}
set onloadstart(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'loadstart', listener);
}
get onprogress(): EventCallback | null {
return getEventHandlerAttribute(this, 'progress');
}
set onprogress(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'progress', listener);
}
get ontimeout(): EventCallback | null {
return getEventHandlerAttribute(this, 'timeout');
}
set ontimeout(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'timeout', listener);
}
get onloadend(): EventCallback | null {
return getEventHandlerAttribute(this, 'loadend');
}
set onloadend(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'loadend', listener);
}
get onreadystatechange(): EventCallback | null {
return getEventHandlerAttribute(this, 'readystatechange');
}
set onreadystatechange(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'readystatechange', listener);
}
}
export default XMLHttpRequest;

View File

@@ -0,0 +1,46 @@
/**
* 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.
*
* @flow
* @format
*/
'use strict';
import typeof BlobT from '../Blob/Blob';
import typeof FormDataT from './FormData';
const Blob: BlobT = require('../Blob/Blob').default;
const binaryToBase64 = require('../Utilities/binaryToBase64').default;
const FormData: FormDataT = require('./FormData').default;
export type RequestBody =
| string
| Blob
| FormData
| {uri: string, ...}
| ArrayBuffer
| $ArrayBufferView;
function convertRequestBody(body: RequestBody): Object {
if (typeof body === 'string') {
return {string: body};
}
if (body instanceof Blob) {
return {blob: body.data};
}
if (body instanceof FormData) {
return {formData: body.getParts()};
}
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
/* $FlowFixMe[incompatible-type] : no way to assert that 'body' is indeed
* an ArrayBufferView */
return {base64: binaryToBase64(body)};
}
return body;
}
export default convertRequestBody;

20
node_modules/react-native/Libraries/Network/fetch.js generated vendored Normal file
View File

@@ -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.
*
* @flow strict
* @format
*/
'use strict';
// side-effectful require() to put fetch,
// Headers, Request, Response in global scope
require('whatwg-fetch');
export const fetch = global.fetch;
export const Headers = global.Headers;
export const Request = global.Request;
export const Response = global.Response;