react-native-worklets 0.7.2 → 0.8.0-bundle-mode-preview-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp +32 -28
- package/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.h +13 -5
- package/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.cpp +7 -5
- package/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.h +5 -4
- package/Common/cpp/worklets/Resources/SynchronizableUnpacker.cpp +5 -5
- package/Common/cpp/worklets/RunLoop/AsyncQueueImpl.cpp +42 -19
- package/Common/cpp/worklets/RunLoop/AsyncQueueImpl.h +2 -0
- package/Common/cpp/worklets/Tools/Defs.h +2 -2
- package/Common/cpp/worklets/Tools/ScriptBuffer.h +34 -0
- package/Common/cpp/worklets/WorkletRuntime/RuntimeBindings.h +24 -0
- package/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp +11 -6
- package/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.cpp +82 -0
- package/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.h +12 -0
- package/RNWorklets.podspec +15 -14
- package/android/CMakeLists.txt +8 -2
- package/android/build.gradle +92 -56
- package/android/src/main/cpp/worklets/android/JScriptBufferWrapper.cpp +67 -0
- package/android/src/main/cpp/worklets/android/JScriptBufferWrapper.h +48 -0
- package/android/src/main/cpp/worklets/android/JWorkletRuntimeWrapper.cpp +52 -0
- package/android/src/main/cpp/worklets/android/JWorkletRuntimeWrapper.h +43 -0
- package/android/src/main/cpp/worklets/android/WorkletsModule.cpp +115 -19
- package/android/src/main/cpp/worklets/android/WorkletsModule.h +11 -13
- package/android/src/main/cpp/worklets/android/WorkletsOnLoad.cpp +6 -0
- package/android/src/main/java/com/swmansion/worklets/ScriptBufferWrapper.java +88 -0
- package/android/src/networking/com/swmansion/worklets/WorkletRuntimeWrapper.kt +23 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsHeaderUtil.kt +30 -0
- package/android/src/{legacyBundling → networking}/com/swmansion/worklets/WorkletsModule.java +52 -2
- package/android/src/networking/com/swmansion/worklets/WorkletsNetworkEventUtil.kt +268 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsNetworking.kt +1084 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsOkHttpCallUtil.kt +37 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsProgressListener.kt +9 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsProgressRequestBody.kt +98 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsProgressResponseBody.kt +57 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsProgressiveStringDecoder.kt +82 -0
- package/android/src/networking/com/swmansion/worklets/WorkletsRequestBodyUtil.kt +177 -0
- package/android/src/{experimentalBundling → no-networking}/com/swmansion/worklets/WorkletsModule.java +10 -15
- package/apple/worklets/apple/Networking/WorkletsNetworking.h +22 -0
- package/apple/worklets/apple/Networking/WorkletsNetworking.mm +706 -0
- package/apple/worklets/apple/WorkletsModule.mm +56 -17
- package/bundleMode/index.js +2 -6
- package/compatibility.json +4 -1
- package/lib/module/WorkletsModule/NativeWorklets.native.js +8 -2
- package/lib/module/WorkletsModule/NativeWorklets.native.js.map +1 -1
- package/lib/module/bundleMode/metroOverrides.native.js +115 -0
- package/lib/module/bundleMode/metroOverrides.native.js.map +1 -0
- package/lib/module/bundleMode/network.native.js +41 -0
- package/lib/module/bundleMode/network.native.js.map +1 -0
- package/lib/module/debug/jsVersion.js +1 -1
- package/lib/module/debug/jsVersion.js.map +1 -1
- package/lib/module/featureFlags/staticFlags.json +2 -0
- package/lib/module/featureFlags/types.js +3 -1
- package/lib/module/featureFlags/types.js.map +1 -1
- package/lib/module/index.js +4 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/initializers/initializers.native.js +24 -50
- package/lib/module/initializers/initializers.native.js.map +1 -1
- package/lib/module/initializers/workletRuntimeEntry.native.js +3 -3
- package/lib/module/initializers/workletRuntimeEntry.native.js.map +1 -1
- package/lib/module/memory/bundleUnpacker.native.js +2 -2
- package/lib/module/memory/bundleUnpacker.native.js.map +1 -1
- package/lib/module/memory/serializable.native.js +3 -3
- package/lib/module/memory/serializable.native.js.map +1 -1
- package/lib/module/memory/synchronizableUnpacker.native.js +3 -3
- package/lib/module/memory/synchronizableUnpacker.native.js.map +1 -1
- package/lib/module/platformChecker.js +2 -2
- package/lib/module/platformChecker.js.map +1 -1
- package/lib/module/runtimeKind.js +51 -0
- package/lib/module/runtimeKind.js.map +1 -1
- package/lib/module/runtimes.js +3 -0
- package/lib/module/runtimes.js.map +1 -1
- package/lib/module/runtimes.native.js +34 -3
- package/lib/module/runtimes.native.js.map +1 -1
- package/lib/module/threads.native.js +2 -2
- package/lib/module/threads.native.js.map +1 -1
- package/lib/typescript/WorkletsModule/NativeWorklets.native.d.ts.map +1 -1
- package/lib/typescript/WorkletsModule/workletsModuleProxy.d.ts +2 -1
- package/lib/typescript/WorkletsModule/workletsModuleProxy.d.ts.map +1 -1
- package/lib/typescript/bundleMode/metroOverrides.native.d.ts +28 -0
- package/lib/typescript/bundleMode/metroOverrides.native.d.ts.map +1 -0
- package/lib/typescript/bundleMode/network.native.d.ts +7 -0
- package/lib/typescript/bundleMode/network.native.d.ts.map +1 -0
- package/lib/typescript/debug/jsVersion.d.ts +1 -1
- package/lib/typescript/debug/jsVersion.d.ts.map +1 -1
- package/lib/typescript/featureFlags/types.d.ts +3 -1
- package/lib/typescript/featureFlags/types.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/initializers/initializers.native.d.ts +1 -0
- package/lib/typescript/initializers/initializers.native.d.ts.map +1 -1
- package/lib/typescript/initializers/workletRuntimeEntry.native.d.ts +1 -1
- package/lib/typescript/memory/bundleUnpacker.native.d.ts.map +1 -1
- package/lib/typescript/memory/synchronizableUnpacker.native.d.ts.map +1 -1
- package/lib/typescript/platformChecker.d.ts.map +1 -1
- package/lib/typescript/runtimeKind.d.ts +31 -0
- package/lib/typescript/runtimeKind.d.ts.map +1 -1
- package/lib/typescript/runtimes.d.ts +1 -0
- package/lib/typescript/runtimes.d.ts.map +1 -1
- package/lib/typescript/runtimes.native.d.ts +20 -2
- package/lib/typescript/runtimes.native.d.ts.map +1 -1
- package/lib/typescript/threads.native.d.ts +1 -1
- package/package.json +8 -6
- package/plugin/index.d.ts +109 -0
- package/plugin/index.js +59 -9
- package/scripts/worklets_utils.rb +21 -5
- package/src/WorkletsModule/NativeWorklets.native.ts +14 -4
- package/src/WorkletsModule/workletsModuleProxy.ts +6 -3
- package/src/bundleMode/metroOverrides.native.ts +151 -0
- package/src/bundleMode/network.native.ts +59 -0
- package/src/debug/jsVersion.ts +1 -1
- package/src/featureFlags/staticFlags.json +2 -0
- package/src/featureFlags/types.ts +3 -1
- package/src/index.ts +10 -1
- package/src/initializers/initializers.native.ts +29 -70
- package/src/initializers/workletRuntimeEntry.native.ts +3 -3
- package/src/memory/bundleUnpacker.native.ts +2 -4
- package/src/memory/serializable.native.ts +3 -3
- package/src/memory/synchronizableUnpacker.native.ts +6 -12
- package/src/platformChecker.ts +3 -2
- package/src/privateGlobals.d.ts +7 -2
- package/src/runtimeKind.ts +47 -0
- package/src/runtimes.native.ts +43 -2
- package/src/runtimes.ts +10 -0
- package/src/threads.native.ts +2 -2
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
#if defined(WORKLETS_BUNDLE_MODE_ENABLED) && defined(WORKLETS_FETCH_PREVIEW_ENABLED)
|
|
2
|
+
/*
|
|
3
|
+
* This file is based on RCTNetworking.mm from React Native.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
#import <mutex>
|
|
7
|
+
|
|
8
|
+
#import <FBReactNativeSpec/FBReactNativeSpec.h>
|
|
9
|
+
#import <React-Core/React/RCTFollyConvert.h>
|
|
10
|
+
#import <React-jsi/jsi/JSIDynamic.h>
|
|
11
|
+
#import <React/RCTAssert.h>
|
|
12
|
+
#import <React/RCTConvert.h>
|
|
13
|
+
#import <React/RCTHTTPRequestHandler.h>
|
|
14
|
+
#import <React/RCTInspectorNetworkReporter.h>
|
|
15
|
+
#import <React/RCTLog.h>
|
|
16
|
+
#import <React/RCTNetworkPlugins.h>
|
|
17
|
+
#import <React/RCTNetworkTask.h>
|
|
18
|
+
#import <React/RCTNetworking.h>
|
|
19
|
+
#import <React/RCTUtils.h>
|
|
20
|
+
#import <react/featureflags/ReactNativeFeatureFlags.h>
|
|
21
|
+
|
|
22
|
+
#import <worklets/WorkletRuntime/WorkletRuntime.h>
|
|
23
|
+
#import <worklets/apple/Networking/WorkletsNetworking.h>
|
|
24
|
+
|
|
25
|
+
using namespace worklets;
|
|
26
|
+
|
|
27
|
+
// TODO: Document thread switching because it's a mess right now...
|
|
28
|
+
|
|
29
|
+
typedef RCTURLRequestCancellationBlock (^WorkletsHTTPQueryResult)(NSError *error, NSDictionary<NSString *, id> *result);
|
|
30
|
+
|
|
31
|
+
@interface WorkletsNetworking ()
|
|
32
|
+
|
|
33
|
+
- (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(NSDictionary<NSString *, id> *)data
|
|
34
|
+
workletRuntime:(std::weak_ptr<worklets::WorkletRuntime>)workletRuntime
|
|
35
|
+
callback:(WorkletsHTTPQueryResult)callback;
|
|
36
|
+
@end
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Helper to convert FormData payloads into multipart/formdata requests.
|
|
40
|
+
*/
|
|
41
|
+
@interface WorkletsHTTPFormDataHelper : NSObject
|
|
42
|
+
|
|
43
|
+
@property (nonatomic, weak) WorkletsNetworking *networker;
|
|
44
|
+
|
|
45
|
+
@end
|
|
46
|
+
|
|
47
|
+
@implementation WorkletsHTTPFormDataHelper {
|
|
48
|
+
NSMutableArray<NSDictionary<NSString *, id> *> *_parts;
|
|
49
|
+
NSMutableData *_multipartBody;
|
|
50
|
+
WorkletsHTTPQueryResult _callback;
|
|
51
|
+
NSString *_boundary;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static NSString *WorkletsGenerateFormBoundary()
|
|
55
|
+
{
|
|
56
|
+
const size_t boundaryLength = 70;
|
|
57
|
+
const char *boundaryChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.";
|
|
58
|
+
|
|
59
|
+
char *bytes = (char *)malloc(boundaryLength);
|
|
60
|
+
if (!bytes) {
|
|
61
|
+
// CWE - 391 : Unchecked error condition
|
|
62
|
+
// https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html
|
|
63
|
+
// https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c
|
|
64
|
+
abort();
|
|
65
|
+
}
|
|
66
|
+
size_t charCount = strlen(boundaryChars);
|
|
67
|
+
for (int i = 0; i < boundaryLength; i++) {
|
|
68
|
+
bytes[i] = boundaryChars[arc4random_uniform((u_int32_t)charCount)];
|
|
69
|
+
}
|
|
70
|
+
return [[NSString alloc] initWithBytesNoCopy:bytes
|
|
71
|
+
length:boundaryLength
|
|
72
|
+
encoding:NSUTF8StringEncoding
|
|
73
|
+
freeWhenDone:YES];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
- (RCTURLRequestCancellationBlock)process:(NSArray<NSDictionary *> *)formData
|
|
77
|
+
workletRuntime:(std::weak_ptr<worklets::WorkletRuntime>)workletRuntime
|
|
78
|
+
callback:(WorkletsHTTPQueryResult)callback
|
|
79
|
+
{
|
|
80
|
+
if (formData.count == 0) {
|
|
81
|
+
return callback(nil, nil);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
_parts = [formData mutableCopy];
|
|
85
|
+
_callback = callback;
|
|
86
|
+
_multipartBody = [NSMutableData new];
|
|
87
|
+
_boundary = WorkletsGenerateFormBoundary();
|
|
88
|
+
|
|
89
|
+
for (NSUInteger i = 0; i < _parts.count; i++) {
|
|
90
|
+
NSString *uri = _parts[i][@"uri"];
|
|
91
|
+
if (uri && [[uri substringToIndex:@"ph:".length] caseInsensitiveCompare:@"ph:"] == NSOrderedSame) {
|
|
92
|
+
uri = [RCTNetworkingPHUploadHackScheme stringByAppendingString:[uri substringFromIndex:@"ph".length]];
|
|
93
|
+
NSMutableDictionary *mutableDict = [_parts[i] mutableCopy];
|
|
94
|
+
mutableDict[@"uri"] = uri;
|
|
95
|
+
_parts[i] = mutableDict;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return [_networker processDataForHTTPQuery:_parts[0]
|
|
100
|
+
workletRuntime:workletRuntime
|
|
101
|
+
callback:^(NSError *error, NSDictionary<NSString *, id> *result) {
|
|
102
|
+
return [self handleResult:result workletRuntime:workletRuntime error:error];
|
|
103
|
+
}];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
- (RCTURLRequestCancellationBlock)handleResult:(NSDictionary<NSString *, id> *)result
|
|
107
|
+
workletRuntime:(std::weak_ptr<worklets::WorkletRuntime>)workletRuntime
|
|
108
|
+
error:(NSError *)error
|
|
109
|
+
{
|
|
110
|
+
if (error) {
|
|
111
|
+
return _callback(error, nil);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Start with boundary.
|
|
115
|
+
[_multipartBody
|
|
116
|
+
appendData:[[NSString stringWithFormat:@"--%@\r\n", _boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
|
117
|
+
|
|
118
|
+
// Print headers.
|
|
119
|
+
NSMutableDictionary<NSString *, NSString *> *headers = [_parts[0][@"headers"] mutableCopy];
|
|
120
|
+
NSString *partContentType = result[@"contentType"];
|
|
121
|
+
if (partContentType != nil && ![partContentType isEqual:[NSNull null]]) {
|
|
122
|
+
headers[@"content-type"] = partContentType;
|
|
123
|
+
}
|
|
124
|
+
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
|
|
125
|
+
[self->_multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue]
|
|
126
|
+
dataUsingEncoding:NSUTF8StringEncoding]];
|
|
127
|
+
}];
|
|
128
|
+
|
|
129
|
+
// Add the body.
|
|
130
|
+
[_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
|
131
|
+
[_multipartBody appendData:result[@"body"]];
|
|
132
|
+
[_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
|
133
|
+
|
|
134
|
+
[_parts removeObjectAtIndex:0];
|
|
135
|
+
if (_parts.count) {
|
|
136
|
+
return [_networker processDataForHTTPQuery:_parts[0]
|
|
137
|
+
workletRuntime:workletRuntime
|
|
138
|
+
callback:^(NSError *err, NSDictionary<NSString *, id> *res) {
|
|
139
|
+
return [self handleResult:res workletRuntime:workletRuntime error:err];
|
|
140
|
+
}];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// We've processed the last item. Finish and return.
|
|
144
|
+
[_multipartBody
|
|
145
|
+
appendData:[[NSString stringWithFormat:@"--%@--\r\n", _boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
|
146
|
+
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", _boundary];
|
|
147
|
+
return _callback(nil, @{@"body" : _multipartBody, @"contentType" : contentType});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@end
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Bridge module that provides the JS interface to the network stack.
|
|
154
|
+
*/
|
|
155
|
+
@implementation WorkletsNetworking {
|
|
156
|
+
NSMutableDictionary<NSNumber *, RCTNetworkTask *> *_tasksByRequestID;
|
|
157
|
+
NSLock *_tasksLock;
|
|
158
|
+
std::mutex _handlersLock;
|
|
159
|
+
NSArray<id<RCTURLRequestHandler>> *_handlers;
|
|
160
|
+
// NSArray<id<RCTURLRequestHandler>> * (^_handlersProvider)(RCTModuleRegistry *);
|
|
161
|
+
// NSMutableArray<id<RCTNetworkingRequestHandler>> *_requestHandlers;
|
|
162
|
+
// NSMutableArray<id<RCTNetworkingResponseHandler>> *_responseHandlers;
|
|
163
|
+
RCTNetworking *rctNetworking_;
|
|
164
|
+
// dispatch_queue_t _requestQueue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#pragma mark - JS API
|
|
168
|
+
|
|
169
|
+
- (void)jsiSendRequest:(jsi::Runtime &)rt
|
|
170
|
+
jquery:(const facebook::jsi::Value &)jquery
|
|
171
|
+
responseSender:(jsi::Function &&)responseSender
|
|
172
|
+
{
|
|
173
|
+
auto originRuntime = WorkletRuntime::getWeakRuntimeFromJSIRuntime(rt).lock();
|
|
174
|
+
if (!originRuntime) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
id query = facebook::react::TurboModuleConvertUtils::convertJSIValueToObjCObject(rt, jquery, nullptr);
|
|
179
|
+
|
|
180
|
+
NSString *method = query[@"method"];
|
|
181
|
+
NSString *url = query[@"url"];
|
|
182
|
+
id data = query[@"data"];
|
|
183
|
+
id headers = query[@"headers"];
|
|
184
|
+
NSString *queryResponseType = query[@"responseType"];
|
|
185
|
+
bool queryIncrementalUpdates = [RCTConvert BOOL:query[@"incrementalUpdates"]];
|
|
186
|
+
double timeout = [RCTConvert double:query[@"timeout"]];
|
|
187
|
+
bool withCredentials = [RCTConvert BOOL:query[@"withCredentials"]];
|
|
188
|
+
|
|
189
|
+
NSDictionary *queryDict = @{
|
|
190
|
+
@"method" : method,
|
|
191
|
+
@"url" : url,
|
|
192
|
+
@"data" : data,
|
|
193
|
+
@"headers" : headers,
|
|
194
|
+
@"responseType" : queryResponseType,
|
|
195
|
+
@"incrementalUpdates" : @(queryIncrementalUpdates),
|
|
196
|
+
@"timeout" : @(timeout),
|
|
197
|
+
@"withCredentials" : @(withCredentials),
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
auto sharedResponseSender = std::make_shared<jsi::Function>(std::move(responseSender));
|
|
201
|
+
|
|
202
|
+
// TODO: buildRequest returns a cancellation block, but there's currently
|
|
203
|
+
// no way to invoke it, if, for example the request is cancelled while
|
|
204
|
+
// loading a large file to build the request body
|
|
205
|
+
[self buildRequest:queryDict
|
|
206
|
+
workletRuntime:originRuntime
|
|
207
|
+
completionBlock:^(NSURLRequest *request, jsi::Runtime &rt) {
|
|
208
|
+
NSString *responseType = [RCTConvert NSString:queryDict[@"responseType"]];
|
|
209
|
+
BOOL incrementalUpdates = [RCTConvert BOOL:queryDict[@"incrementalUpdates"]];
|
|
210
|
+
jsi::Function responseSender = std::move(*sharedResponseSender);
|
|
211
|
+
[self sendRequest:request
|
|
212
|
+
responseType:responseType
|
|
213
|
+
incrementalUpdates:incrementalUpdates
|
|
214
|
+
rt:rt
|
|
215
|
+
responseSender:std::move(responseSender)];
|
|
216
|
+
}];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
- (void)jsiAbortRequest:(double)requestID
|
|
220
|
+
{
|
|
221
|
+
[_tasksLock lock];
|
|
222
|
+
[_tasksByRequestID[[NSNumber numberWithDouble:requestID]] cancel];
|
|
223
|
+
[_tasksByRequestID removeObjectForKey:[NSNumber numberWithDouble:requestID]];
|
|
224
|
+
[_tasksLock unlock];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
- (void)jsiClearCookies:(facebook::jsi::Runtime &)rt responseSender:(jsi::Function &&)responseSender
|
|
228
|
+
{
|
|
229
|
+
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
|
|
230
|
+
if (!storage.cookies.count) {
|
|
231
|
+
responseSender.call(rt, jsi::Value(rt, false));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
for (NSHTTPCookie *cookie in storage.cookies) {
|
|
236
|
+
[storage deleteCookie:cookie];
|
|
237
|
+
}
|
|
238
|
+
responseSender.call(rt, jsi::Value(rt, true));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#pragma mark - internals
|
|
242
|
+
|
|
243
|
+
- (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary<NSString *, id> *)query
|
|
244
|
+
workletRuntime:(std::weak_ptr<worklets::WorkletRuntime>)workletRuntime
|
|
245
|
+
completionBlock:(void (^)(NSURLRequest *request, jsi::Runtime &rt))completionBlock
|
|
246
|
+
{
|
|
247
|
+
NSURL *URL = [RCTConvert NSURL:query[@"url"]]; // this is marked as nullable in JS, but should not be null
|
|
248
|
+
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
|
|
249
|
+
request.HTTPMethod = [RCTConvert NSString:RCTNilIfNull(query[@"method"])].uppercaseString ?: @"GET";
|
|
250
|
+
request.HTTPShouldHandleCookies = [RCTConvert BOOL:query[@"withCredentials"]];
|
|
251
|
+
|
|
252
|
+
if (request.HTTPShouldHandleCookies == YES) {
|
|
253
|
+
// Load and set the cookie header.
|
|
254
|
+
NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:URL];
|
|
255
|
+
request.allHTTPHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Set supplied headers.
|
|
259
|
+
NSDictionary *headers = [RCTConvert NSDictionary:query[@"headers"]];
|
|
260
|
+
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
|
|
261
|
+
if (value) {
|
|
262
|
+
[request addValue:[RCTConvert NSString:value] forHTTPHeaderField:key];
|
|
263
|
+
}
|
|
264
|
+
}];
|
|
265
|
+
|
|
266
|
+
request.timeoutInterval = [RCTConvert NSTimeInterval:query[@"timeout"]];
|
|
267
|
+
NSDictionary<NSString *, id> *data = [RCTConvert NSDictionary:RCTNilIfNull(query[@"data"])];
|
|
268
|
+
NSString *trackingName = data[@"trackingName"];
|
|
269
|
+
if (trackingName) {
|
|
270
|
+
[NSURLProtocol setProperty:trackingName forKey:@"trackingName" inRequest:request];
|
|
271
|
+
}
|
|
272
|
+
return [self processDataForHTTPQuery:data
|
|
273
|
+
workletRuntime:workletRuntime
|
|
274
|
+
callback:^(NSError *error, NSDictionary<NSString *, id> *result) {
|
|
275
|
+
if (error) {
|
|
276
|
+
RCTLogError(@"Error processing request body: %@", error);
|
|
277
|
+
// Ideally we'd circle back to JS here and notify an error/abort on the request.
|
|
278
|
+
return (RCTURLRequestCancellationBlock)nil;
|
|
279
|
+
}
|
|
280
|
+
request.HTTPBody = result[@"body"];
|
|
281
|
+
NSString *dataContentType = result[@"contentType"];
|
|
282
|
+
NSString *requestContentType = [request valueForHTTPHeaderField:@"Content-Type"];
|
|
283
|
+
BOOL isMultipart = ![dataContentType isEqual:[NSNull null]] &&
|
|
284
|
+
[dataContentType hasPrefix:@"multipart"];
|
|
285
|
+
|
|
286
|
+
// For multipart requests we need to override caller-specified content type with one
|
|
287
|
+
// from the data object, because it contains the boundary string
|
|
288
|
+
if (dataContentType && ([requestContentType length] == 0 || isMultipart)) {
|
|
289
|
+
[request setValue:dataContentType forHTTPHeaderField:@"Content-Type"];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Gzip the request body
|
|
293
|
+
if ([request.allHTTPHeaderFields[@"Content-Encoding"] isEqualToString:@"gzip"]) {
|
|
294
|
+
request.HTTPBody = RCTGzipData(request.HTTPBody, -1 /* default */);
|
|
295
|
+
[request setValue:(@(request.HTTPBody.length)).description
|
|
296
|
+
forHTTPHeaderField:@"Content-Length"];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// NSRequest default cache policy violate on `If-None-Match`, should allow the request
|
|
300
|
+
// to get 304 from server.
|
|
301
|
+
if (request.allHTTPHeaderFields[@"If-None-Match"]) {
|
|
302
|
+
request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// dispatch_async(self->_methodQueue, ^{
|
|
306
|
+
// block(request);
|
|
307
|
+
// });
|
|
308
|
+
// self->uiScheduler_->scheduleOnUI([block, request](){
|
|
309
|
+
auto strongWorkletRuntime = workletRuntime.lock();
|
|
310
|
+
strongWorkletRuntime->schedule(
|
|
311
|
+
[completionBlock, request](jsi::Runtime &rt) { completionBlock(request, rt); });
|
|
312
|
+
|
|
313
|
+
return (RCTURLRequestCancellationBlock)nil;
|
|
314
|
+
}];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
- (instancetype)init:(RCTNetworking *)rctNetworking
|
|
318
|
+
{
|
|
319
|
+
self = [super init];
|
|
320
|
+
if (self) {
|
|
321
|
+
rctNetworking_ = rctNetworking;
|
|
322
|
+
_tasksLock = [[NSLock alloc] init];
|
|
323
|
+
}
|
|
324
|
+
return self;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// TODO: Is it needed?
|
|
328
|
+
- (void)invalidate
|
|
329
|
+
{
|
|
330
|
+
std::lock_guard<std::mutex> lock(_handlersLock);
|
|
331
|
+
[_tasksLock lock];
|
|
332
|
+
|
|
333
|
+
for (NSNumber *requestID in _tasksByRequestID) {
|
|
334
|
+
[_tasksByRequestID[requestID] cancel];
|
|
335
|
+
}
|
|
336
|
+
[_tasksByRequestID removeAllObjects];
|
|
337
|
+
for (id<RCTURLRequestHandler> handler in _handlers) {
|
|
338
|
+
if ([handler conformsToProtocol:@protocol(RCTInvalidating)]) {
|
|
339
|
+
[(id<RCTInvalidating>)handler invalidate];
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
[_tasksLock unlock];
|
|
343
|
+
// _handlers = nil;
|
|
344
|
+
// _requestHandlers = nil;
|
|
345
|
+
// _responseHandlers = nil;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
- (NSArray<NSString *> *)supportedEvents
|
|
349
|
+
{
|
|
350
|
+
return @[
|
|
351
|
+
@"didCompleteNetworkResponse",
|
|
352
|
+
@"didReceiveNetworkResponse",
|
|
353
|
+
@"didSendNetworkData",
|
|
354
|
+
@"didReceiveNetworkIncrementalData",
|
|
355
|
+
@"didReceiveNetworkDataProgress",
|
|
356
|
+
@"didReceiveNetworkData"
|
|
357
|
+
];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
- (NSDictionary<NSString *, id> *)stripNullsInRequestHeaders:(NSDictionary<NSString *, id> *)headers
|
|
361
|
+
{
|
|
362
|
+
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:headers.count];
|
|
363
|
+
for (NSString *key in headers.allKeys) {
|
|
364
|
+
id val = headers[key];
|
|
365
|
+
if (val != [NSNull null]) {
|
|
366
|
+
result[key] = val;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Process the 'data' part of an HTTP query.
|
|
375
|
+
*
|
|
376
|
+
* 'data' can be a JSON value of the following forms:
|
|
377
|
+
*
|
|
378
|
+
* - {"string": "..."}: a simple JS string that will be UTF-8 encoded and sent as the body
|
|
379
|
+
*
|
|
380
|
+
* - {"uri": "some-uri://..."}: reference to a system resource, e.g. an image in the asset library
|
|
381
|
+
*
|
|
382
|
+
* - {"formData": [...]}: list of data payloads that will be combined into a multipart/form-data request
|
|
383
|
+
*
|
|
384
|
+
* - {"blob": {...}}: an object representing a blob
|
|
385
|
+
*
|
|
386
|
+
* If successful, the callback be called with a result dictionary containing the following (optional) keys:
|
|
387
|
+
*
|
|
388
|
+
* - @"body" (NSData): the body of the request
|
|
389
|
+
*
|
|
390
|
+
* - @"contentType" (NSString): the content type header of the request
|
|
391
|
+
*
|
|
392
|
+
*/
|
|
393
|
+
- (RCTURLRequestCancellationBlock)
|
|
394
|
+
processDataForHTTPQuery:(nullable NSDictionary<NSString *, id> *)query
|
|
395
|
+
workletRuntime:(std::weak_ptr<worklets::WorkletRuntime>)workletRuntime
|
|
396
|
+
callback:(RCTURLRequestCancellationBlock (^)(NSError *error, NSDictionary<NSString *, id> *result))
|
|
397
|
+
callback
|
|
398
|
+
{
|
|
399
|
+
if (!query) {
|
|
400
|
+
return callback(nil, nil);
|
|
401
|
+
}
|
|
402
|
+
// for (id<RCTNetworkingRequestHandler> handler in _requestHandlers) {
|
|
403
|
+
// if ([handler canHandleNetworkingRequest:query]) {
|
|
404
|
+
// NSDictionary *body = [handler handleNetworkingRequest:query];
|
|
405
|
+
// if (body) {
|
|
406
|
+
// return callback(nil, body);
|
|
407
|
+
// }
|
|
408
|
+
// }
|
|
409
|
+
// }
|
|
410
|
+
NSData *body = [RCTConvert NSData:query[@"string"]];
|
|
411
|
+
if (body) {
|
|
412
|
+
return callback(nil, @{@"body" : body});
|
|
413
|
+
}
|
|
414
|
+
NSString *base64String = [RCTConvert NSString:query[@"base64"]];
|
|
415
|
+
if (base64String) {
|
|
416
|
+
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64String options:0];
|
|
417
|
+
return callback(nil, @{@"body" : data});
|
|
418
|
+
}
|
|
419
|
+
NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]];
|
|
420
|
+
if (request) {
|
|
421
|
+
__block RCTURLRequestCancellationBlock cancellationBlock = nil;
|
|
422
|
+
RCTNetworkTask *task = [self->rctNetworking_
|
|
423
|
+
networkTaskWithRequest:request
|
|
424
|
+
completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
|
|
425
|
+
// TODO: Fix
|
|
426
|
+
// dispatch_async(self->_methodQueue, ^{
|
|
427
|
+
// dispatch_async(dispatch_get_main_queue(), ^{
|
|
428
|
+
auto strongWorkletRuntime = workletRuntime.lock();
|
|
429
|
+
if (!strongWorkletRuntime) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
strongWorkletRuntime->schedule(^{
|
|
434
|
+
cancellationBlock = callback(
|
|
435
|
+
error, data ? @{@"body" : data, @"contentType" : RCTNullIfNil(response.MIMEType)} : nil);
|
|
436
|
+
});
|
|
437
|
+
}];
|
|
438
|
+
|
|
439
|
+
[task start];
|
|
440
|
+
|
|
441
|
+
__weak RCTNetworkTask *weakTask = task;
|
|
442
|
+
return ^{
|
|
443
|
+
[weakTask cancel];
|
|
444
|
+
if (cancellationBlock) {
|
|
445
|
+
cancellationBlock();
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
NSArray<NSDictionary *> *formData = [RCTConvert NSDictionaryArray:query[@"formData"]];
|
|
450
|
+
if (formData) {
|
|
451
|
+
// TODO: FIX
|
|
452
|
+
WorkletsHTTPFormDataHelper *formDataHelper = [WorkletsHTTPFormDataHelper new];
|
|
453
|
+
formDataHelper.networker = self;
|
|
454
|
+
return [formDataHelper process:formData workletRuntime:workletRuntime callback:callback];
|
|
455
|
+
}
|
|
456
|
+
// Nothing in the data payload, at least nothing we could understand anyway.
|
|
457
|
+
// Ignore and treat it as if it were null.
|
|
458
|
+
return callback(nil, nil);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
+ (NSString *)decodeTextData:(NSData *)data
|
|
462
|
+
fromResponse:(NSURLResponse *)response
|
|
463
|
+
withCarryData:(NSMutableData *)inputCarryData
|
|
464
|
+
{
|
|
465
|
+
NSStringEncoding encoding = NSUTF8StringEncoding;
|
|
466
|
+
if (response.textEncodingName) {
|
|
467
|
+
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
|
468
|
+
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
NSMutableData *currentCarryData = inputCarryData ?: [NSMutableData new];
|
|
472
|
+
[currentCarryData appendData:data];
|
|
473
|
+
|
|
474
|
+
// Attempt to decode text
|
|
475
|
+
NSString *encodedResponse = [[NSString alloc] initWithData:currentCarryData encoding:encoding];
|
|
476
|
+
|
|
477
|
+
if (!encodedResponse && data.length > 0) {
|
|
478
|
+
if (encoding == NSUTF8StringEncoding && inputCarryData) {
|
|
479
|
+
// If decode failed, we attempt to trim broken character bytes from the data.
|
|
480
|
+
// At this time, only UTF-8 support is enabled. Multibyte encodings, such as UTF-16 and UTF-32, require a lot of
|
|
481
|
+
// additional work to determine wether BOM was included in the first data packet. If so, save it, and attach it to
|
|
482
|
+
// each new data packet. If not, an encoding has to be selected with a suitable byte order (for ARM iOS, it would
|
|
483
|
+
// be little endianness).
|
|
484
|
+
|
|
485
|
+
CFStringEncoding cfEncoding = CFStringConvertNSStringEncodingToEncoding(encoding);
|
|
486
|
+
// Taking a single unichar is not good enough, due to Unicode combining character sequences or characters outside
|
|
487
|
+
// the BMP. See https://www.objc.io/issues/9-strings/unicode/#common-pitfalls We'll attempt with a sequence of two
|
|
488
|
+
// characters, the most common combining character sequence and characters outside the BMP (emojis).
|
|
489
|
+
CFIndex maxCharLength = CFStringGetMaximumSizeForEncoding(2, cfEncoding);
|
|
490
|
+
|
|
491
|
+
NSUInteger removedBytes = 1;
|
|
492
|
+
|
|
493
|
+
while (removedBytes < maxCharLength) {
|
|
494
|
+
encodedResponse = [[NSString alloc]
|
|
495
|
+
initWithData:[currentCarryData subdataWithRange:NSMakeRange(0, currentCarryData.length - removedBytes)]
|
|
496
|
+
encoding:encoding];
|
|
497
|
+
|
|
498
|
+
if (encodedResponse != nil) {
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
removedBytes += 1;
|
|
503
|
+
}
|
|
504
|
+
} else {
|
|
505
|
+
// We don't have an encoding, or the encoding is incorrect, so now we try to guess
|
|
506
|
+
[NSString stringEncodingForData:data
|
|
507
|
+
encodingOptions:@{NSStringEncodingDetectionSuggestedEncodingsKey : @[ @(encoding) ]}
|
|
508
|
+
convertedString:&encodedResponse
|
|
509
|
+
usedLossyConversion:NULL];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (inputCarryData) {
|
|
514
|
+
NSUInteger encodedResponseLength = [encodedResponse dataUsingEncoding:encoding].length;
|
|
515
|
+
|
|
516
|
+
// Ensure a valid subrange exists within currentCarryData
|
|
517
|
+
if (currentCarryData.length >= encodedResponseLength) {
|
|
518
|
+
NSData *newCarryData = [currentCarryData
|
|
519
|
+
subdataWithRange:NSMakeRange(encodedResponseLength, currentCarryData.length - encodedResponseLength)];
|
|
520
|
+
[inputCarryData setData:newCarryData];
|
|
521
|
+
} else {
|
|
522
|
+
[inputCarryData setLength:0];
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return encodedResponse;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
- (void)sendData:(NSData *)data
|
|
530
|
+
responseType:(NSString *)responseType
|
|
531
|
+
response:(NSURLResponse *)response
|
|
532
|
+
forTask:(RCTNetworkTask *)task
|
|
533
|
+
rt:(facebook::jsi::Runtime &)rt
|
|
534
|
+
{
|
|
535
|
+
id responseData = nil;
|
|
536
|
+
// TODO: ???
|
|
537
|
+
// for (id<RCTNetworkingResponseHandler> handler in _responseHandlers) {
|
|
538
|
+
// if ([handler canHandleNetworkingResponse:responseType]) {
|
|
539
|
+
// responseData = [handler handleNetworkingResponse:response data:data];
|
|
540
|
+
// break;
|
|
541
|
+
// }
|
|
542
|
+
// }
|
|
543
|
+
|
|
544
|
+
if (!responseData) {
|
|
545
|
+
if (data.length == 0) {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if ([responseType isEqualToString:@"text"]) {
|
|
550
|
+
// No carry storage is required here because the entire data has been loaded.
|
|
551
|
+
responseData = [WorkletsNetworking decodeTextData:data fromResponse:task.response withCarryData:nil];
|
|
552
|
+
if (!responseData) {
|
|
553
|
+
RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
} else if ([responseType isEqualToString:@"base64"]) {
|
|
557
|
+
responseData = [data base64EncodedStringWithOptions:0];
|
|
558
|
+
} else {
|
|
559
|
+
RCTLogWarn(@"Invalid responseType: %@", responseType);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
[self emitDeviceEvent:@"didReceiveNetworkData" argFactory:@[ task.requestID, responseData ] rt:rt];
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
- (void)sendRequest:(NSURLRequest *)request
|
|
568
|
+
responseType:(NSString *)responseType
|
|
569
|
+
incrementalUpdates:(BOOL)incrementalUpdates
|
|
570
|
+
rt:(facebook::jsi::Runtime &)rt
|
|
571
|
+
responseSender:(jsi::Function &&)responseSender
|
|
572
|
+
{
|
|
573
|
+
__weak __typeof(self) weakSelf = self;
|
|
574
|
+
__block RCTNetworkTask *task;
|
|
575
|
+
RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) {
|
|
576
|
+
NSArray *responseJSON = @[ task.requestID, @((double)progress), @((double)total) ];
|
|
577
|
+
[weakSelf emitDeviceEvent:@"didSendNetworkData" argFactory:responseJSON rt:rt];
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
RCTURLRequestResponseBlock responseBlock = ^(NSURLResponse *response) {
|
|
581
|
+
NSDictionary<NSString *, NSString *> *headers;
|
|
582
|
+
NSInteger status;
|
|
583
|
+
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
|
584
|
+
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
|
585
|
+
headers = httpResponse.allHeaderFields ?: @{};
|
|
586
|
+
status = httpResponse.statusCode;
|
|
587
|
+
} else {
|
|
588
|
+
// Other HTTP-like request
|
|
589
|
+
headers = response.MIMEType ? @{@"Content-Type" : response.MIMEType} : @{};
|
|
590
|
+
status = 200;
|
|
591
|
+
}
|
|
592
|
+
id responseURL = response.URL ? response.URL.absoluteString : [NSNull null];
|
|
593
|
+
NSArray<id> *responseJSON = @[ task.requestID, @(status), headers, responseURL ];
|
|
594
|
+
|
|
595
|
+
[weakSelf emitDeviceEvent:@"didReceiveNetworkResponse" argFactory:responseJSON rt:rt];
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// XHR does not allow you to peek at xhr.response before the response is
|
|
599
|
+
// finished. Only when xhr.responseType is set to ''/'text', consumers may
|
|
600
|
+
// peek at xhr.responseText. So unless the requested responseType is 'text',
|
|
601
|
+
// we only send progress updates and not incremental data updates to JS here.
|
|
602
|
+
RCTURLRequestIncrementalDataBlock incrementalDataBlock = nil;
|
|
603
|
+
RCTURLRequestProgressBlock downloadProgressBlock = nil;
|
|
604
|
+
if (incrementalUpdates) {
|
|
605
|
+
if ([responseType isEqualToString:@"text"]) {
|
|
606
|
+
// We need this to carry over bytes, which could not be decoded into text (such as broken UTF-8 characters).
|
|
607
|
+
// The incremental data block holds the ownership of this object, and will be released upon release of the block.
|
|
608
|
+
NSMutableData *incrementalDataCarry = [NSMutableData new];
|
|
609
|
+
|
|
610
|
+
incrementalDataBlock = ^(NSData *data, int64_t progress, int64_t total) {
|
|
611
|
+
NSUInteger initialCarryLength = incrementalDataCarry.length;
|
|
612
|
+
|
|
613
|
+
NSString *responseString = [WorkletsNetworking decodeTextData:data
|
|
614
|
+
fromResponse:task.response
|
|
615
|
+
withCarryData:incrementalDataCarry];
|
|
616
|
+
if (!responseString) {
|
|
617
|
+
RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Update progress to include the previous carry length and reduce the current carry length.
|
|
622
|
+
NSArray<id> *responseJSON = @[
|
|
623
|
+
task.requestID,
|
|
624
|
+
responseString,
|
|
625
|
+
@(progress + initialCarryLength - incrementalDataCarry.length),
|
|
626
|
+
@(total)
|
|
627
|
+
];
|
|
628
|
+
|
|
629
|
+
[weakSelf emitDeviceEvent:@"didReceiveNetworkIncrementalData" argFactory:responseJSON rt:rt];
|
|
630
|
+
};
|
|
631
|
+
} else {
|
|
632
|
+
downloadProgressBlock = ^(int64_t progress, int64_t total) {
|
|
633
|
+
NSArray<id> *responseJSON = @[ task.requestID, @(progress), @(total) ];
|
|
634
|
+
[weakSelf emitDeviceEvent:@"didReceiveNetworkDataProgress" argFactory:responseJSON rt:rt];
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
RCTURLRequestCompletionBlock completionBlock = ^(NSURLResponse *response, NSData *data, NSError *error) {
|
|
640
|
+
__typeof(self) strongSelf = weakSelf;
|
|
641
|
+
if (!strongSelf) {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Unless we were sending incremental (text) chunks to JS, all along, now
|
|
646
|
+
// is the time to send the request body to JS.
|
|
647
|
+
if (!(incrementalUpdates && [responseType isEqualToString:@"text"])) {
|
|
648
|
+
[strongSelf sendData:data responseType:responseType response:response forTask:task rt:rt];
|
|
649
|
+
}
|
|
650
|
+
NSArray *responseJSON =
|
|
651
|
+
@[ task.requestID, RCTNullIfNil(error.localizedDescription), error.code == kCFURLErrorTimedOut ? @YES : @NO ];
|
|
652
|
+
|
|
653
|
+
[strongSelf emitDeviceEvent:@"didCompleteNetworkResponse" argFactory:responseJSON rt:rt];
|
|
654
|
+
|
|
655
|
+
[strongSelf->_tasksLock lock];
|
|
656
|
+
[strongSelf->_tasksByRequestID removeObjectForKey:task.requestID];
|
|
657
|
+
[strongSelf->_tasksLock unlock];
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
task = [self->rctNetworking_ networkTaskWithRequest:request completionBlock:completionBlock];
|
|
661
|
+
task.downloadProgressBlock = downloadProgressBlock;
|
|
662
|
+
task.incrementalDataBlock = incrementalDataBlock;
|
|
663
|
+
task.responseBlock = responseBlock;
|
|
664
|
+
task.uploadProgressBlock = uploadProgressBlock;
|
|
665
|
+
|
|
666
|
+
if (task.requestID) {
|
|
667
|
+
[_tasksLock lock];
|
|
668
|
+
if (!_tasksByRequestID) {
|
|
669
|
+
_tasksByRequestID = [NSMutableDictionary new];
|
|
670
|
+
}
|
|
671
|
+
_tasksByRequestID[task.requestID] = task;
|
|
672
|
+
[_tasksLock unlock];
|
|
673
|
+
auto workletRuntime = WorkletRuntime::getWeakRuntimeFromJSIRuntime(rt).lock();
|
|
674
|
+
auto value = task.requestID.doubleValue;
|
|
675
|
+
|
|
676
|
+
responseSender.call(rt, jsi::Value(rt, value));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
[task start];
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
using ArgFactory = std::function<void(facebook::jsi::Runtime &runtime, std::vector<facebook::jsi::Value> &args)>;
|
|
683
|
+
|
|
684
|
+
- (void)emitDeviceEvent:(NSString *)eventName argFactory:(id)argFactory rt:(facebook::jsi::Runtime &)rt
|
|
685
|
+
|
|
686
|
+
{
|
|
687
|
+
facebook::jsi::Value emitter = rt.global().getProperty(rt, "__rctDeviceEventEmitter");
|
|
688
|
+
if (!emitter.isUndefined()) {
|
|
689
|
+
facebook::jsi::Object emitterObject = emitter.asObject(rt);
|
|
690
|
+
// TODO: consider caching these
|
|
691
|
+
facebook::jsi::Function emitFunction = emitterObject.getPropertyAsFunction(rt, "emit");
|
|
692
|
+
std::vector<facebook::jsi::Value> args;
|
|
693
|
+
args.emplace_back(facebook::jsi::String::createFromAscii(rt, eventName.UTF8String));
|
|
694
|
+
if (argFactory) {
|
|
695
|
+
auto fly = facebook::react::convertIdToFollyDynamic(argFactory);
|
|
696
|
+
auto event = facebook::jsi::valueFromDynamic(rt, fly);
|
|
697
|
+
|
|
698
|
+
args.emplace_back(std::move(event));
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
emitFunction.callWithThis(rt, emitterObject, static_cast<const jsi::Value *>(args.data()), args.size());
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
@end
|
|
706
|
+
#endif // defined(WORKLETS_BUNDLE_MODE_ENABLED) && defined(WORKLETS_FETCH_PREVIEW_ENABLED)
|