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.
Files changed (123) hide show
  1. package/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp +32 -28
  2. package/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.h +13 -5
  3. package/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.cpp +7 -5
  4. package/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.h +5 -4
  5. package/Common/cpp/worklets/Resources/SynchronizableUnpacker.cpp +5 -5
  6. package/Common/cpp/worklets/RunLoop/AsyncQueueImpl.cpp +42 -19
  7. package/Common/cpp/worklets/RunLoop/AsyncQueueImpl.h +2 -0
  8. package/Common/cpp/worklets/Tools/Defs.h +2 -2
  9. package/Common/cpp/worklets/Tools/ScriptBuffer.h +34 -0
  10. package/Common/cpp/worklets/WorkletRuntime/RuntimeBindings.h +24 -0
  11. package/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp +11 -6
  12. package/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.cpp +82 -0
  13. package/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.h +12 -0
  14. package/RNWorklets.podspec +15 -14
  15. package/android/CMakeLists.txt +8 -2
  16. package/android/build.gradle +92 -56
  17. package/android/src/main/cpp/worklets/android/JScriptBufferWrapper.cpp +67 -0
  18. package/android/src/main/cpp/worklets/android/JScriptBufferWrapper.h +48 -0
  19. package/android/src/main/cpp/worklets/android/JWorkletRuntimeWrapper.cpp +52 -0
  20. package/android/src/main/cpp/worklets/android/JWorkletRuntimeWrapper.h +43 -0
  21. package/android/src/main/cpp/worklets/android/WorkletsModule.cpp +115 -19
  22. package/android/src/main/cpp/worklets/android/WorkletsModule.h +11 -13
  23. package/android/src/main/cpp/worklets/android/WorkletsOnLoad.cpp +6 -0
  24. package/android/src/main/java/com/swmansion/worklets/ScriptBufferWrapper.java +88 -0
  25. package/android/src/networking/com/swmansion/worklets/WorkletRuntimeWrapper.kt +23 -0
  26. package/android/src/networking/com/swmansion/worklets/WorkletsHeaderUtil.kt +30 -0
  27. package/android/src/{legacyBundling → networking}/com/swmansion/worklets/WorkletsModule.java +52 -2
  28. package/android/src/networking/com/swmansion/worklets/WorkletsNetworkEventUtil.kt +268 -0
  29. package/android/src/networking/com/swmansion/worklets/WorkletsNetworking.kt +1084 -0
  30. package/android/src/networking/com/swmansion/worklets/WorkletsOkHttpCallUtil.kt +37 -0
  31. package/android/src/networking/com/swmansion/worklets/WorkletsProgressListener.kt +9 -0
  32. package/android/src/networking/com/swmansion/worklets/WorkletsProgressRequestBody.kt +98 -0
  33. package/android/src/networking/com/swmansion/worklets/WorkletsProgressResponseBody.kt +57 -0
  34. package/android/src/networking/com/swmansion/worklets/WorkletsProgressiveStringDecoder.kt +82 -0
  35. package/android/src/networking/com/swmansion/worklets/WorkletsRequestBodyUtil.kt +177 -0
  36. package/android/src/{experimentalBundling → no-networking}/com/swmansion/worklets/WorkletsModule.java +10 -15
  37. package/apple/worklets/apple/Networking/WorkletsNetworking.h +22 -0
  38. package/apple/worklets/apple/Networking/WorkletsNetworking.mm +706 -0
  39. package/apple/worklets/apple/WorkletsModule.mm +56 -17
  40. package/bundleMode/index.js +2 -6
  41. package/compatibility.json +4 -1
  42. package/lib/module/WorkletsModule/NativeWorklets.native.js +8 -2
  43. package/lib/module/WorkletsModule/NativeWorklets.native.js.map +1 -1
  44. package/lib/module/bundleMode/metroOverrides.native.js +115 -0
  45. package/lib/module/bundleMode/metroOverrides.native.js.map +1 -0
  46. package/lib/module/bundleMode/network.native.js +41 -0
  47. package/lib/module/bundleMode/network.native.js.map +1 -0
  48. package/lib/module/debug/jsVersion.js +1 -1
  49. package/lib/module/debug/jsVersion.js.map +1 -1
  50. package/lib/module/featureFlags/staticFlags.json +2 -0
  51. package/lib/module/featureFlags/types.js +3 -1
  52. package/lib/module/featureFlags/types.js.map +1 -1
  53. package/lib/module/index.js +4 -2
  54. package/lib/module/index.js.map +1 -1
  55. package/lib/module/initializers/initializers.native.js +24 -50
  56. package/lib/module/initializers/initializers.native.js.map +1 -1
  57. package/lib/module/initializers/workletRuntimeEntry.native.js +3 -3
  58. package/lib/module/initializers/workletRuntimeEntry.native.js.map +1 -1
  59. package/lib/module/memory/bundleUnpacker.native.js +2 -2
  60. package/lib/module/memory/bundleUnpacker.native.js.map +1 -1
  61. package/lib/module/memory/serializable.native.js +3 -3
  62. package/lib/module/memory/serializable.native.js.map +1 -1
  63. package/lib/module/memory/synchronizableUnpacker.native.js +3 -3
  64. package/lib/module/memory/synchronizableUnpacker.native.js.map +1 -1
  65. package/lib/module/platformChecker.js +2 -2
  66. package/lib/module/platformChecker.js.map +1 -1
  67. package/lib/module/runtimeKind.js +51 -0
  68. package/lib/module/runtimeKind.js.map +1 -1
  69. package/lib/module/runtimes.js +3 -0
  70. package/lib/module/runtimes.js.map +1 -1
  71. package/lib/module/runtimes.native.js +34 -3
  72. package/lib/module/runtimes.native.js.map +1 -1
  73. package/lib/module/threads.native.js +2 -2
  74. package/lib/module/threads.native.js.map +1 -1
  75. package/lib/typescript/WorkletsModule/NativeWorklets.native.d.ts.map +1 -1
  76. package/lib/typescript/WorkletsModule/workletsModuleProxy.d.ts +2 -1
  77. package/lib/typescript/WorkletsModule/workletsModuleProxy.d.ts.map +1 -1
  78. package/lib/typescript/bundleMode/metroOverrides.native.d.ts +28 -0
  79. package/lib/typescript/bundleMode/metroOverrides.native.d.ts.map +1 -0
  80. package/lib/typescript/bundleMode/network.native.d.ts +7 -0
  81. package/lib/typescript/bundleMode/network.native.d.ts.map +1 -0
  82. package/lib/typescript/debug/jsVersion.d.ts +1 -1
  83. package/lib/typescript/debug/jsVersion.d.ts.map +1 -1
  84. package/lib/typescript/featureFlags/types.d.ts +3 -1
  85. package/lib/typescript/featureFlags/types.d.ts.map +1 -1
  86. package/lib/typescript/index.d.ts +2 -2
  87. package/lib/typescript/index.d.ts.map +1 -1
  88. package/lib/typescript/initializers/initializers.native.d.ts +1 -0
  89. package/lib/typescript/initializers/initializers.native.d.ts.map +1 -1
  90. package/lib/typescript/initializers/workletRuntimeEntry.native.d.ts +1 -1
  91. package/lib/typescript/memory/bundleUnpacker.native.d.ts.map +1 -1
  92. package/lib/typescript/memory/synchronizableUnpacker.native.d.ts.map +1 -1
  93. package/lib/typescript/platformChecker.d.ts.map +1 -1
  94. package/lib/typescript/runtimeKind.d.ts +31 -0
  95. package/lib/typescript/runtimeKind.d.ts.map +1 -1
  96. package/lib/typescript/runtimes.d.ts +1 -0
  97. package/lib/typescript/runtimes.d.ts.map +1 -1
  98. package/lib/typescript/runtimes.native.d.ts +20 -2
  99. package/lib/typescript/runtimes.native.d.ts.map +1 -1
  100. package/lib/typescript/threads.native.d.ts +1 -1
  101. package/package.json +8 -6
  102. package/plugin/index.d.ts +109 -0
  103. package/plugin/index.js +59 -9
  104. package/scripts/worklets_utils.rb +21 -5
  105. package/src/WorkletsModule/NativeWorklets.native.ts +14 -4
  106. package/src/WorkletsModule/workletsModuleProxy.ts +6 -3
  107. package/src/bundleMode/metroOverrides.native.ts +151 -0
  108. package/src/bundleMode/network.native.ts +59 -0
  109. package/src/debug/jsVersion.ts +1 -1
  110. package/src/featureFlags/staticFlags.json +2 -0
  111. package/src/featureFlags/types.ts +3 -1
  112. package/src/index.ts +10 -1
  113. package/src/initializers/initializers.native.ts +29 -70
  114. package/src/initializers/workletRuntimeEntry.native.ts +3 -3
  115. package/src/memory/bundleUnpacker.native.ts +2 -4
  116. package/src/memory/serializable.native.ts +3 -3
  117. package/src/memory/synchronizableUnpacker.native.ts +6 -12
  118. package/src/platformChecker.ts +3 -2
  119. package/src/privateGlobals.d.ts +7 -2
  120. package/src/runtimeKind.ts +47 -0
  121. package/src/runtimes.native.ts +43 -2
  122. package/src/runtimes.ts +10 -0
  123. 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)