diff --git a/.yarn/patches/react-native-npm-0.82.0-228ad0ffa7.patch b/.yarn/patches/react-native-npm-0.82.0-228ad0ffa7.patch
index 5786993e420f..d65d72970d00 100644
--- a/.yarn/patches/react-native-npm-0.82.0-228ad0ffa7.patch
+++ b/.yarn/patches/react-native-npm-0.82.0-228ad0ffa7.patch
@@ -749,9 +749,18 @@ index d6552a8a978..ca4e72ac488 100644
void callFunctionOnModule(
diff --git a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h
-index 9ef16f4cc5c..5c0622ad990 100644
+index 9ef16f4..94afa4e 100644
--- a/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h
+++ b/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h
+@@ -61,7 +61,7 @@ class JSI_EXPORT TurboModule : public jsi::HostObject {
+ // If we have a JS wrapper, cache the result of this lookup
+ // We don't cache misses, to allow for methodMap_ to dynamically be
+ // extended
+- if (jsRepresentation_ && !prop.isUndefined()) {
++ if (jsRepresentation_ && !prop.isUndefined() && &runtime == representationRuntime_) {
+ jsRepresentation_->lock(runtime).asObject(runtime).setProperty(
+ runtime, propName, prop);
+ }
@@ -150,6 +150,7 @@ class JSI_EXPORT TurboModule : public jsi::HostObject {
private:
friend class TurboModuleBinding;
diff --git a/apps/common-app/package.json b/apps/common-app/package.json
index 8b2eba4dfcdc..e066096c5265 100644
--- a/apps/common-app/package.json
+++ b/apps/common-app/package.json
@@ -28,6 +28,7 @@
"@react-navigation/native-stack": "7.3.27",
"@react-navigation/stack": "7.4.9",
"@shopify/flash-list": "2.1.0",
+ "axios": "1.10.0",
"d3-shape": "3.2.0",
"fuse.js": "patch:fuse.js@npm%3A7.1.0#~/.yarn/patches/fuse.js-npm-7.1.0-5dcae892a6.patch",
"react": "19.1.1",
diff --git a/apps/common-app/src/apps/reanimated/examples/EmptyExample.tsx b/apps/common-app/src/apps/reanimated/examples/EmptyExample.tsx
index 7f3a1ac26ce1..abc8c43a5e9a 100644
--- a/apps/common-app/src/apps/reanimated/examples/EmptyExample.tsx
+++ b/apps/common-app/src/apps/reanimated/examples/EmptyExample.tsx
@@ -1,10 +1,163 @@
import React from 'react';
-import { StyleSheet, Text, View } from 'react-native';
+import { StyleSheet, View, Button } from 'react-native';
+import {
+ createWorkletRuntime,
+ scheduleOnRuntime,
+ type WorkletRuntime,
+} from 'react-native-worklets';
+import axios from 'axios';
-export default function EmptyExample() {
+const mydloRuntime = createWorkletRuntime({
+ name: 'mydlo',
+});
+
+const widloRuntime = createWorkletRuntime({
+ name: 'widlo',
+});
+
+const powidloRuntime = createWorkletRuntime({
+ name: 'powidlo',
+});
+
+function testXHR(
+ readystateHandler: boolean,
+ progressHandler: boolean,
+ arraybuffer: boolean,
+ chunked: boolean
+) {
+ 'worklet';
+ const xhr = new globalThis.XMLHttpRequest();
+
+ const state = {
+ readystateHandler,
+ progressHandler,
+ arraybuffer,
+ chunked,
+ downloading: false,
+ contentLength: 1,
+ responseLength: 0,
+ progressTotal: 1,
+ progressLoaded: 0,
+ cancelled: false,
+ };
+
+ const onreadystatechange = () => {
+ if (xhr.readyState === xhr.HEADERS_RECEIVED) {
+ const contentLength = parseInt(
+ xhr.getResponseHeader('Content-Length')!,
+ 10
+ );
+ state.contentLength = contentLength;
+ state.responseLength = 0;
+ } else if (xhr.readyState === xhr.LOADING && xhr.response) {
+ state.responseLength = xhr.response.length;
+ }
+ };
+ const onprogress = (event: ProgressEvent) => {
+ state.progressTotal = event.total;
+ state.progressLoaded = event.loaded;
+ };
+ const onerror = (event: ProgressEvent) => {
+ state.downloading = false;
+ throw new Error(
+ `XHR error: ${event.type} - ${xhr.status} - ${xhr.responseText} - ${event.toString()}`
+ );
+ };
+
+ if (state.readystateHandler) {
+ xhr.onreadystatechange = onreadystatechange;
+ }
+ if (state.progressHandler) {
+ xhr.onprogress = onprogress;
+ }
+ if (state.arraybuffer) {
+ xhr.responseType = 'arraybuffer';
+ }
+ xhr.onerror = onerror;
+ xhr.onload = () => {
+ // this.setState({ downloading: false });
+ state.downloading = false;
+ if (state.cancelled) {
+ state.cancelled = false;
+ return;
+ }
+ if (xhr.status === 200) {
+ let responseType = `Response is a string, ${xhr.response.length} characters long.`;
+ if (xhr.response instanceof ArrayBuffer) {
+ responseType = `Response is an ArrayBuffer, ${xhr.response.byteLength} bytes long.`;
+ }
+ console.log('Download complete!', responseType);
+ } else if (xhr.status !== 0) {
+ console.error(
+ `Server returned HTTP status of ${xhr.status}: ${xhr.responseText}`
+ );
+ } else {
+ console.error(xhr.responseText);
+ }
+ };
+ if (state.chunked) {
+ xhr.open(
+ 'GET',
+ 'https://filesamples.com/samples/ebook/azw3/Around%20the%20World%20in%2028%20Languages.azw3'
+ );
+ } else {
+ xhr.open('GET', 'https://filesamples.com/samples/document/txt/sample3.txt');
+ // Avoid gzip so we can actually show progress
+ xhr.setRequestHeader('Accept-Encoding', '');
+ }
+ xhr.send();
+
+ state.downloading = true;
+}
+
+function callback(runtime: WorkletRuntime, count: number) {
+ 'worklet';
+ axios({
+ method: 'get',
+ url: 'https://tomekzaw.pl',
+ })
+ .then((response) => {
+ console.log(`Received ${count} response on ${runtime.name}`);
+ console.log(response.data);
+ })
+ .catch((error) => {
+ console.error('Axios error:', error);
+ });
+ if (count > 32) {
+ return;
+ }
+
+ const nextRuntime =
+ runtime.name === mydloRuntime.name
+ ? widloRuntime
+ : runtime.name === widloRuntime.name
+ ? powidloRuntime
+ : mydloRuntime;
+
+ setTimeout(() => {
+ scheduleOnRuntime(nextRuntime, callback, nextRuntime, count + 1);
+ }, 100);
+}
+
+export default function App() {
return (
- Hello world!
+
);
}
diff --git a/apps/fabric-example/babel.config.js b/apps/fabric-example/babel.config.js
index 259ed1ad84b1..101f51198759 100644
--- a/apps/fabric-example/babel.config.js
+++ b/apps/fabric-example/babel.config.js
@@ -7,6 +7,7 @@ module.exports = {
{
// Uncomment the next line to enable bundle mode.
// bundleMode: true,
+ // workletizableModules: ['react-native/Libraries/Core/setUpXHR', 'axios'],
},
],
[
diff --git a/packages/react-native-worklets/Common/cpp/worklets/AnimationFrameQueue/AnimationFrameBatchinator.cpp b/packages/react-native-worklets/Common/cpp/worklets/AnimationFrameQueue/AnimationFrameBatchinator.cpp
index 17da80028853..d8259365db5f 100644
--- a/packages/react-native-worklets/Common/cpp/worklets/AnimationFrameQueue/AnimationFrameBatchinator.cpp
+++ b/packages/react-native-worklets/Common/cpp/worklets/AnimationFrameQueue/AnimationFrameBatchinator.cpp
@@ -1,6 +1,7 @@
#include
#include
#include
+#include
#include
#include
@@ -63,9 +64,7 @@ AnimationFrameBatchinator::pullCallbacks() {
AnimationFrameBatchinator::AnimationFrameBatchinator(
facebook::jsi::Runtime &uiRuntime,
- std::function)>
- &&forwardedRequestAnimationFrame)
- : uiRuntime_(&uiRuntime),
- requestAnimationFrame_(std::move(forwardedRequestAnimationFrame)) {}
+ RuntimeBindings::RequestAnimationFrame requestAnimationFrame)
+ : uiRuntime_(&uiRuntime), requestAnimationFrame_(requestAnimationFrame) {}
} // namespace worklets
diff --git a/packages/react-native-worklets/Common/cpp/worklets/AnimationFrameQueue/AnimationFrameBatchinator.h b/packages/react-native-worklets/Common/cpp/worklets/AnimationFrameQueue/AnimationFrameBatchinator.h
index c6455b0c4a18..e9e32fecbed9 100644
--- a/packages/react-native-worklets/Common/cpp/worklets/AnimationFrameQueue/AnimationFrameBatchinator.h
+++ b/packages/react-native-worklets/Common/cpp/worklets/AnimationFrameQueue/AnimationFrameBatchinator.h
@@ -2,6 +2,8 @@
#include
#include
+#include
+
#include
#include
#include
@@ -21,8 +23,7 @@ class AnimationFrameBatchinator
AnimationFrameBatchinator(
facebook::jsi::Runtime &uiRuntime,
- std::function)>
- &&forwardedRequestAnimationFrame);
+ RuntimeBindings::RequestAnimationFrame requestAnimationFrame);
private:
void flush();
diff --git a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp
index 15f5ad2a5aeb..db23e38a0e8a 100644
--- a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp
+++ b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp
@@ -135,7 +135,8 @@ JSIWorkletsModuleProxy::JSIWorkletsModuleProxy(
const std::shared_ptr &jsScheduler,
const std::shared_ptr &uiScheduler,
const std::shared_ptr &runtimeManager,
- const std::weak_ptr &uiWorkletRuntime)
+ const std::weak_ptr &uiWorkletRuntime,
+ RuntimeBindings runtimeBindings)
: jsi::HostObject(),
isDevBundle_(isDevBundle),
script_(script),
@@ -144,7 +145,8 @@ JSIWorkletsModuleProxy::JSIWorkletsModuleProxy(
jsScheduler_(jsScheduler),
uiScheduler_(uiScheduler),
runtimeManager_(runtimeManager),
- uiWorkletRuntime_(uiWorkletRuntime) {}
+ uiWorkletRuntime_(uiWorkletRuntime),
+ runtimeBindings_(runtimeBindings) {}
JSIWorkletsModuleProxy::JSIWorkletsModuleProxy(
const JSIWorkletsModuleProxy &other)
@@ -156,7 +158,8 @@ JSIWorkletsModuleProxy::JSIWorkletsModuleProxy(
jsScheduler_(other.jsScheduler_),
uiScheduler_(other.uiScheduler_),
runtimeManager_(other.runtimeManager_),
- uiWorkletRuntime_(other.uiWorkletRuntime_) {}
+ uiWorkletRuntime_(other.uiWorkletRuntime_),
+ runtimeBindings_(other.runtimeBindings_) {}
JSIWorkletsModuleProxy::~JSIWorkletsModuleProxy() = default;
diff --git a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.h b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.h
index 755bdb3a6736..ff9ece59e87d 100644
--- a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.h
+++ b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.h
@@ -7,6 +7,7 @@
#include
#include
+#include
#include
#include
@@ -36,7 +37,8 @@ class JSIWorkletsModuleProxy : public jsi::HostObject {
const std::shared_ptr &jsScheduler,
const std::shared_ptr &uiScheduler,
const std::shared_ptr &runtimeManager,
- const std::weak_ptr &uiWorkletRuntime);
+ const std::weak_ptr &uiWorkletRuntime,
+ RuntimeBindings runtimeBindings);
JSIWorkletsModuleProxy(const JSIWorkletsModuleProxy &other);
@@ -74,6 +76,10 @@ class JSIWorkletsModuleProxy : public jsi::HostObject {
return runtimeManager_;
}
+ [[nodiscard]] inline RuntimeBindings getRuntimeBindings() const {
+ return runtimeBindings_;
+ }
+
private:
const bool isDevBundle_;
const std::shared_ptr script_;
@@ -83,6 +89,7 @@ class JSIWorkletsModuleProxy : public jsi::HostObject {
const std::shared_ptr uiScheduler_;
const std::shared_ptr runtimeManager_;
const std::weak_ptr uiWorkletRuntime_;
+ const RuntimeBindings runtimeBindings_;
};
} // namespace worklets
diff --git a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.cpp b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.cpp
index 4e72c838ca17..7772e98129c8 100644
--- a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.cpp
+++ b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.cpp
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
#ifdef __ANDROID__
@@ -26,8 +27,8 @@ WorkletsModuleProxy::WorkletsModuleProxy(
const std::shared_ptr &jsCallInvoker,
const std::shared_ptr &uiScheduler,
std::function &&isJavaScriptThread,
- std::function)>
- &&forwardedRequestAnimationFrame,
+ RuntimeBindings runtimeBindings,
+ const std::shared_ptr &runtimeManager,
const std::shared_ptr &script,
const std::string &sourceUrl)
: isDevBundle_(isDevBundleFromRNRuntime(rnRuntime)),
@@ -38,9 +39,10 @@ WorkletsModuleProxy::WorkletsModuleProxy(
std::move(isJavaScriptThread))),
uiScheduler_(uiScheduler),
jsLogger_(std::make_shared(jsScheduler_)),
+ runtimeBindings_(runtimeBindings),
+ runtimeManager_(runtimeManager),
script_(script),
sourceUrl_(sourceUrl),
- runtimeManager_(std::make_shared()),
uiWorkletRuntime_(runtimeManager_->createUninitializedUIRuntime(
jsQueue_,
std::make_shared(uiScheduler_))) {
@@ -52,7 +54,7 @@ WorkletsModuleProxy::WorkletsModuleProxy(
animationFrameBatchinator_ = std::make_shared(
uiWorkletRuntime_->getJSIRuntime(),
- std::move(forwardedRequestAnimationFrame));
+ runtimeBindings_.requestAnimationFrame);
UIRuntimeDecorator::decorate(
uiWorkletRuntime_->getJSIRuntime(),
@@ -72,7 +74,8 @@ WorkletsModuleProxy::createJSIWorkletsModuleProxy() const {
jsScheduler_,
uiScheduler_,
runtimeManager_,
- uiWorkletRuntime_);
+ uiWorkletRuntime_,
+ runtimeBindings_);
}
WorkletsModuleProxy::~WorkletsModuleProxy() {
diff --git a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.h b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.h
index a576e1af0438..3fadc4aed757 100644
--- a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.h
+++ b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.h
@@ -26,8 +26,8 @@ class WorkletsModuleProxy
const std::shared_ptr &jsCallInvoker,
const std::shared_ptr &uiScheduler,
std::function &&isJavaScriptQueue,
- std::function)>
- &&forwardedRequestAnimationFrame,
+ RuntimeBindings runtimeBindings,
+ const std::shared_ptr &runtimeManager,
const std::shared_ptr &script,
const std::string &sourceUrl);
@@ -54,6 +54,11 @@ class WorkletsModuleProxy
return uiWorkletRuntime_;
}
+ [[nodiscard]] inline std::shared_ptr getRuntimeManager()
+ const {
+ return runtimeManager_;
+ }
+
[[nodiscard]] std::shared_ptr
createJSIWorkletsModuleProxy() const;
@@ -67,9 +72,10 @@ class WorkletsModuleProxy
const std::shared_ptr jsScheduler_;
const std::shared_ptr uiScheduler_;
const std::shared_ptr jsLogger_;
+ const RuntimeBindings runtimeBindings_;
+ const std::shared_ptr runtimeManager_;
const std::shared_ptr script_;
const std::string sourceUrl_;
- const std::shared_ptr runtimeManager_;
std::shared_ptr uiWorkletRuntime_;
std::shared_ptr animationFrameBatchinator_;
#ifndef NDEBUG
diff --git a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RuntimeBindings.h b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RuntimeBindings.h
new file mode 100644
index 000000000000..61c051308ca5
--- /dev/null
+++ b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RuntimeBindings.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include
+
+#include
+
+using namespace facebook;
+
+namespace worklets {
+
+struct RuntimeBindings {
+#if defined(__APPLE__) && defined(WORKLETS_BUNDLE_MODE)
+ using AbortRequest = std::function;
+ using ClearCookies =
+ std::function;
+ using SendRequest = std::function;
+
+ const AbortRequest abortRequest;
+ const ClearCookies clearCookies;
+ const SendRequest sendRequest;
+#endif // defined(__APPLE__) && defined(WORKLETS_BUNDLE_MODE)
+
+ using RequestAnimationFrame =
+ std::function)>;
+
+ const RequestAnimationFrame requestAnimationFrame;
+};
+
+} // namespace worklets
diff --git a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp
index 3ff260e6a680..5674d2c6d54c 100644
--- a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp
+++ b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp
@@ -92,9 +92,11 @@ void WorkletRuntime::init(
jsi::Runtime &rt = *runtime_;
const auto jsScheduler = jsiWorkletsModuleProxy->getJSScheduler();
const auto isDevBundle = jsiWorkletsModuleProxy->isDevBundle();
+
#ifdef WORKLETS_BUNDLE_MODE
auto script = jsiWorkletsModuleProxy->getScript();
const auto &sourceUrl = jsiWorkletsModuleProxy->getSourceUrl();
+ auto runtimeBindings = jsiWorkletsModuleProxy->getRuntimeBindings();
#endif // WORKLETS_BUNDLE_MODE
auto optimizedJsiWorkletsModuleProxy =
@@ -128,8 +130,12 @@ void WorkletRuntime::init(
.stack = stack,
.name = "WorkletsError",
.jsEngine = "Worklets"});
+ return;
}
}
+
+ WorkletRuntimeDecorator::postEvaluateScript(rt, runtimeBindings);
+
#else
// Legacy behavior
auto valueUnpackerBuffer =
@@ -158,33 +164,50 @@ void WorkletRuntime::runAsyncGuarded(
});
}
+void WorkletRuntime::runOnQueue(std::function &&job) {
+ queue_->push(std::move(job));
+}
+
+void WorkletRuntime::runOnQueue(std::function &&job) {
+ queue_->push([job = std::move(job), weakThis = weak_from_this()]() {
+ auto strongThis = weakThis.lock();
+ if (!strongThis) {
+ return;
+ }
+ auto lock =
+ std::unique_lock(*strongThis->runtimeMutex_);
+ jsi::Runtime &rt = strongThis->getJSIRuntime();
+ job(rt);
+ });
+}
+
jsi::Value WorkletRuntime::executeSync(
- jsi::Runtime &rt,
+ jsi::Runtime &callerRuntime,
const jsi::Value &worklet) const {
auto serializableWorklet = extractSerializableOrThrow(
- rt,
+ callerRuntime,
worklet,
"[Worklets] Only worklets can be executed synchronously on UI runtime.");
auto lock = std::unique_lock(*runtimeMutex_);
- jsi::Runtime &uiRuntime = getJSIRuntime();
+ jsi::Runtime &targetRuntime = getJSIRuntime();
auto result = runGuarded(serializableWorklet);
- auto serializableResult = extractSerializableOrThrow(uiRuntime, result);
+ auto serializableResult = extractSerializableOrThrow(targetRuntime, result);
lock.unlock();
- return serializableResult->toJSValue(rt);
+ return serializableResult->toJSValue(callerRuntime);
}
jsi::Value WorkletRuntime::executeSync(
std::function &&job) const {
auto lock = std::unique_lock(*runtimeMutex_);
- jsi::Runtime &uiRuntime = getJSIRuntime();
- return job(uiRuntime);
+ jsi::Runtime &rt = getJSIRuntime();
+ return job(rt);
}
jsi::Value WorkletRuntime::executeSync(
const std::function &job) const {
auto lock = std::unique_lock(*runtimeMutex_);
- jsi::Runtime &uiRuntime = getJSIRuntime();
- return job(uiRuntime);
+ jsi::Runtime &rt = getJSIRuntime();
+ return job(rt);
}
jsi::Value WorkletRuntime::get(
diff --git a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h
index 3d3d3724e7e2..da2c35aa68fc 100644
--- a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h
+++ b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h
@@ -54,6 +54,10 @@ class WorkletRuntime : public jsi::HostObject,
void runAsyncGuarded(const std::shared_ptr &worklet);
+ void runOnQueue(std::function &&job);
+
+ void runOnQueue(std::function &&job);
+
jsi::Value executeSync(jsi::Runtime &rt, const jsi::Value &worklet) const;
jsi::Value executeSync(std::function &&job) const;
@@ -77,7 +81,7 @@ class WorkletRuntime : public jsi::HostObject,
return name_;
}
- private:
+ public:
const uint64_t runtimeId_;
const std::shared_ptr runtimeMutex_;
const std::shared_ptr runtime_;
diff --git a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.cpp b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.cpp
index 32df7d573af5..cbe85cbad3d2 100644
--- a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.cpp
+++ b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.cpp
@@ -305,4 +305,73 @@ void WorkletRuntimeDecorator::decorate(
});
}
+#ifdef WORKLETS_BUNDLE_MODE
+void WorkletRuntimeDecorator::postEvaluateScript(
+ jsi::Runtime &rt,
+ RuntimeBindings runtimeBindings) {
+ installNetworking(rt, runtimeBindings);
+}
+
+void WorkletRuntimeDecorator::installNetworking(
+ jsi::Runtime &rt,
+ const RuntimeBindings runtimeBindings) {
+ auto TurboModules = rt.global().getPropertyAsObject(rt, "TurboModules");
+
+ auto Networking = TurboModules.getPropertyAsFunction(rt, "get").callWithThis(
+ rt, TurboModules, "Networking");
+
+ auto jsiSendRequest = jsi::Function::createFromHostFunction(
+ rt,
+ jsi::PropNameID::forAscii(rt, "sendRequest"),
+ 2,
+ [sendRequest = runtimeBindings.sendRequest](
+ jsi::Runtime &rt,
+ const jsi::Value &thisValue,
+ const jsi::Value *args,
+ size_t count) {
+ auto &query = args[0];
+ auto responseSender = args[1].asObject(rt).asFunction(rt);
+ sendRequest(rt, query, std::move(responseSender));
+ return jsi::Value::undefined();
+ });
+
+ Networking.asObject(rt).setProperty(
+ rt, "sendRequest", std::move(jsiSendRequest));
+
+ auto jsiAbortRequest = jsi::Function::createFromHostFunction(
+ rt,
+ jsi::PropNameID::forAscii(rt, "abortRequest"),
+ 1,
+ [abortRequest = runtimeBindings.abortRequest](
+ jsi::Runtime &rt,
+ const jsi::Value &thisValue,
+ const jsi::Value *args,
+ size_t count) {
+ auto requestID = args[0].asNumber();
+ abortRequest(rt, requestID);
+ return jsi::Value::undefined();
+ });
+
+ Networking.asObject(rt).setProperty(
+ rt, "abortRequest", std::move(jsiAbortRequest));
+
+ auto jsiClearCookies = jsi::Function::createFromHostFunction(
+ rt,
+ jsi::PropNameID::forAscii(rt, "clearCookies"),
+ 1,
+ [clearCookies = runtimeBindings.clearCookies](
+ jsi::Runtime &rt,
+ const jsi::Value &thisValue,
+ const jsi::Value *args,
+ size_t count) {
+ auto responseSender = args[0].asObject(rt).asFunction(rt);
+ clearCookies(rt, std::move(responseSender));
+ return jsi::Value::undefined();
+ });
+
+ Networking.asObject(rt).setProperty(
+ rt, "clearCookies", std::move(jsiClearCookies));
+}
+#endif // WORKLETS_BUNDLE_MODE
+
} // namespace worklets
diff --git a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.h b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.h
index 614cc35197ca..496643e1209c 100644
--- a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.h
+++ b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntimeDecorator.h
@@ -2,6 +2,9 @@
#include
#include
+#ifdef WORKLETS_BUNDLE_MODE
+#include
+#endif // WORKLETS_BUNDLE_MODE
#include
@@ -21,6 +24,17 @@ class WorkletRuntimeDecorator {
const bool isDevBundle,
jsi::Object &&jsiWorkletsModuleProxy,
const std::shared_ptr &eventLoop);
+
+#ifdef WORKLETS_BUNDLE_MODE
+ static void postEvaluateScript(
+ jsi::Runtime &rt,
+ RuntimeBindings runtimeBindings);
+
+ private:
+ static void installNetworking(
+ jsi::Runtime &rt,
+ RuntimeBindings runtimeBindings);
+#endif // WORKLETS_BUNDLE_MODE
};
} // namespace worklets
diff --git a/packages/react-native-worklets/android/src/main/cpp/worklets/android/WorkletsModule.cpp b/packages/react-native-worklets/android/src/main/cpp/worklets/android/WorkletsModule.cpp
index 9f8f67af2347..8662a96a983b 100644
--- a/packages/react-native-worklets/android/src/main/cpp/worklets/android/WorkletsModule.cpp
+++ b/packages/react-native-worklets/android/src/main/cpp/worklets/android/WorkletsModule.cpp
@@ -28,7 +28,7 @@ WorkletsModule::WorkletsModule(
jsCallInvoker,
uiScheduler,
getIsOnJSQueueThread(),
- getForwardedRequestAnimationFrame(),
+ RuntimeBindings{.requestAnimationFrame = getRequestAnimationFrame()},
script,
sourceURL)) {
auto jsiWorkletsModuleProxy =
@@ -77,8 +77,8 @@ jni::local_ref WorkletsModule::initHybrid(
sourceURL);
}
-std::function)>
-WorkletsModule::getForwardedRequestAnimationFrame() {
+RuntimeBindings::RequestAnimationFrame
+WorkletsModule::getRequestAnimationFrame() {
return [javaPart =
javaPart_](std::function &&callback) -> void {
static const auto jRequestAnimationFrame =
diff --git a/packages/react-native-worklets/android/src/main/cpp/worklets/android/WorkletsModule.h b/packages/react-native-worklets/android/src/main/cpp/worklets/android/WorkletsModule.h
index 3770363697f4..a54a2b369b8c 100644
--- a/packages/react-native-worklets/android/src/main/cpp/worklets/android/WorkletsModule.h
+++ b/packages/react-native-worklets/android/src/main/cpp/worklets/android/WorkletsModule.h
@@ -10,6 +10,7 @@
#endif // WORKLETS_BUNDLE_MODE
#include
+#include
#include
#include
@@ -63,8 +64,7 @@ class WorkletsModule : public jni::HybridClass {
return javaPart_->getClass()->getMethod(methodName.c_str());
}
- std::function)>
- getForwardedRequestAnimationFrame();
+ RuntimeBindings::RequestAnimationFrame getRequestAnimationFrame();
std::function getIsOnJSQueueThread();
diff --git a/packages/react-native-worklets/apple/worklets/apple/Networking/WorkletsNetworking.h b/packages/react-native-worklets/apple/worklets/apple/Networking/WorkletsNetworking.h
new file mode 100644
index 000000000000..88e39f082649
--- /dev/null
+++ b/packages/react-native-worklets/apple/worklets/apple/Networking/WorkletsNetworking.h
@@ -0,0 +1,24 @@
+#ifdef WORKLETS_BUNDLE_MODE
+/*
+ * This file is based on RCTNetworking.h from React Native.
+ */
+
+#import
+
+#import
+
+@interface WorkletsNetworking : NSObject
+
+- (instancetype)init:(std::shared_ptr)runtimeManager
+ rctNetworking:(RCTNetworking *)rctNetworking;
+
+- (void)jsiSendRequest:(jsi::Runtime &)rt
+ jquery:(const jsi::Value &)jquery
+ responseSender:(jsi::Function &&)responseSender;
+
+- (void)jsiAbortRequest:(double)requestID;
+
+- (void)jsiClearCookies:(jsi::Runtime &)rt responseSender:(jsi::Function &&)responseSender;
+
+@end
+#endif // WORKLETS_BUNDLE_MODE
diff --git a/packages/react-native-worklets/apple/worklets/apple/Networking/WorkletsNetworking.mm b/packages/react-native-worklets/apple/worklets/apple/Networking/WorkletsNetworking.mm
new file mode 100644
index 000000000000..4c46a6f34af6
--- /dev/null
+++ b/packages/react-native-worklets/apple/worklets/apple/Networking/WorkletsNetworking.mm
@@ -0,0 +1,723 @@
+#ifdef WORKLETS_BUNDLE_MODE
+/*
+ * This file is based on RCTNetworking.mm from React Native.
+ */
+
+#import
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+#import
+#import
+
+#import
+#import
+
+#import
+#import
+
+// TODO: Document thread switching because it's a mess right now...
+
+typedef RCTURLRequestCancellationBlock (^WorkletsHTTPQueryResult)(NSError *error, NSDictionary *result);
+
+@interface WorkletsNetworking ()
+
+- (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(NSDictionary *)data
+ workletRuntime:(std::weak_ptr)workletRuntime
+ callback:(WorkletsHTTPQueryResult)callback;
+@end
+
+/**
+ * Helper to convert FormData payloads into multipart/formdata requests.
+ */
+@interface WorkletsHTTPFormDataHelper : NSObject
+
+@property (nonatomic, weak) WorkletsNetworking *networker;
+
+@end
+
+@implementation WorkletsHTTPFormDataHelper {
+ NSMutableArray *> *_parts;
+ NSMutableData *_multipartBody;
+ WorkletsHTTPQueryResult _callback;
+ NSString *_boundary;
+}
+
+static NSString *WorkletsGenerateFormBoundary()
+{
+ 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 *)formData
+ workletRuntime:(std::weak_ptr)workletRuntime
+ callback:(WorkletsHTTPQueryResult)callback
+{
+ if (formData.count == 0) {
+ return callback(nil, nil);
+ }
+
+ _parts = [formData mutableCopy];
+ _callback = callback;
+ _multipartBody = [NSMutableData new];
+ _boundary = WorkletsGenerateFormBoundary();
+
+ 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]
+ workletRuntime:workletRuntime
+ callback:^(NSError *error, NSDictionary *result) {
+ return [self handleResult:result workletRuntime:workletRuntime error:error];
+ }];
+}
+
+- (RCTURLRequestCancellationBlock)handleResult:(NSDictionary *)result
+ workletRuntime:(std::weak_ptr)workletRuntime
+ error:(NSError *)error
+{
+ if (error) {
+ return _callback(error, nil);
+ }
+
+ // Start with boundary.
+ [_multipartBody
+ appendData:[[NSString stringWithFormat:@"--%@\r\n", _boundary] dataUsingEncoding:NSUTF8StringEncoding]];
+
+ // Print headers.
+ NSMutableDictionary *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]
+ workletRuntime:workletRuntime
+ callback:^(NSError *err, NSDictionary *res) {
+ return [self handleResult:res workletRuntime:workletRuntime error:err];
+ }];
+ }
+
+ // 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 WorkletsNetworking {
+ NSMutableDictionary *_tasksByRequestID;
+ NSLock *_tasksLock;
+ std::mutex _handlersLock;
+ NSArray> *_handlers;
+ // NSArray> * (^_handlersProvider)(RCTModuleRegistry *);
+ // NSMutableArray> *_requestHandlers;
+ // NSMutableArray> *_responseHandlers;
+ std::shared_ptr runtimeManager_;
+ RCTNetworking *rctNetworking_;
+ // dispatch_queue_t _requestQueue;
+}
+
+#pragma mark - JS API
+
+- (void)jsiSendRequest:(jsi::Runtime &)rt
+ jquery:(const facebook::jsi::Value &)jquery
+ responseSender:(jsi::Function &&)responseSender
+{
+ auto originRuntime = runtimeManager_->getRuntime(&rt);
+ if (!originRuntime) {
+ return;
+ }
+
+ id query = facebook::react::TurboModuleConvertUtils::convertJSIValueToObjCObject(rt, jquery, nullptr);
+
+ NSString *method = query[@"method"];
+ NSString *url = query[@"url"];
+ id data = query[@"data"];
+ id headers = query[@"headers"];
+ NSString *queryResponseType = query[@"responseType"];
+ bool queryIncrementalUpdates = [RCTConvert BOOL:query[@"incrementalUpdates"]];
+ double timeout = [RCTConvert double:query[@"timeout"]];
+ bool withCredentials = [RCTConvert BOOL:query[@"withCredentials"]];
+
+ NSDictionary *queryDict = @{
+ @"method" : method,
+ @"url" : url,
+ @"data" : data,
+ @"headers" : headers,
+ @"responseType" : queryResponseType,
+ @"incrementalUpdates" : @(queryIncrementalUpdates),
+ @"timeout" : @(timeout),
+ @"withCredentials" : @(withCredentials),
+ };
+
+ auto sharedResponseSender = std::make_shared(std::move(responseSender));
+
+ // 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
+ workletRuntime:originRuntime
+ completionBlock:^(NSURLRequest *request, jsi::Runtime &rt) {
+ NSString *responseType = [RCTConvert NSString:queryDict[@"responseType"]];
+ BOOL incrementalUpdates = [RCTConvert BOOL:queryDict[@"incrementalUpdates"]];
+ jsi::Function responseSender = std::move(*sharedResponseSender);
+ [self sendRequest:request
+ responseType:responseType
+ incrementalUpdates:incrementalUpdates
+ rt:rt
+ responseSender:std::move(responseSender)];
+ }];
+}
+
+- (void)jsiAbortRequest:(double)requestID
+{
+ [_tasksLock lock];
+ [_tasksByRequestID[[NSNumber numberWithDouble:requestID]] cancel];
+ [_tasksByRequestID removeObjectForKey:[NSNumber numberWithDouble:requestID]];
+ [_tasksLock unlock];
+}
+
+- (void)jsiClearCookies:(facebook::jsi::Runtime &)rt responseSender:(jsi::Function &&)responseSender
+{
+ NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ if (!storage.cookies.count) {
+ responseSender.call(rt, jsi::Value(rt, false));
+ return;
+ }
+
+ for (NSHTTPCookie *cookie in storage.cookies) {
+ [storage deleteCookie:cookie];
+ }
+ responseSender.call(rt, jsi::Value(rt, true));
+}
+
+#pragma mark - internals
+
+- (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)query
+ workletRuntime:(std::weak_ptr)workletRuntime
+ completionBlock:(void (^)(NSURLRequest *request, jsi::Runtime &rt))completionBlock
+{
+ 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 *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 *data = [RCTConvert NSDictionary:RCTNilIfNull(query[@"data"])];
+ NSString *trackingName = data[@"trackingName"];
+ if (trackingName) {
+ [NSURLProtocol setProperty:trackingName forKey:@"trackingName" inRequest:request];
+ }
+ return [self processDataForHTTPQuery:data
+ workletRuntime:workletRuntime
+ callback:^(NSError *error, NSDictionary *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);
+ // });
+ // self->uiScheduler_->scheduleOnUI([block, request](){
+ auto strongWorkletRuntime = workletRuntime.lock();
+ strongWorkletRuntime->runOnQueue(
+ [completionBlock, request](jsi::Runtime &rt) { completionBlock(request, rt); });
+
+ return (RCTURLRequestCancellationBlock)nil;
+ }];
+}
+
+- (instancetype)init:(std::shared_ptr)runtimeManager
+ rctNetworking:(RCTNetworking *)rctNetworking
+{
+ self = [super init];
+ if (self) {
+ runtimeManager_ = runtimeManager;
+ rctNetworking_ = rctNetworking;
+ _tasksLock = [[NSLock alloc] init];
+ }
+ return self;
+}
+
+// TODO: Is it needed?
+- (void)invalidate
+{
+ std::lock_guard lock(_handlersLock);
+ [_tasksLock lock];
+
+ for (NSNumber *requestID in _tasksByRequestID) {
+ [_tasksByRequestID[requestID] cancel];
+ }
+ [_tasksByRequestID removeAllObjects];
+ for (id handler in _handlers) {
+ if ([handler conformsToProtocol:@protocol(RCTInvalidating)]) {
+ [(id)handler invalidate];
+ }
+ }
+ [_tasksLock unlock];
+ // _handlers = nil;
+ // _requestHandlers = nil;
+ // _responseHandlers = nil;
+}
+
+- (NSArray *)supportedEvents
+{
+ return @[
+ @"didCompleteNetworkResponse",
+ @"didReceiveNetworkResponse",
+ @"didSendNetworkData",
+ @"didReceiveNetworkIncrementalData",
+ @"didReceiveNetworkDataProgress",
+ @"didReceiveNetworkData"
+ ];
+}
+
+- (NSDictionary *)stripNullsInRequestHeaders:(NSDictionary *)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;
+}
+
+/**
+ * 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 *)query
+ workletRuntime:(std::weak_ptr)workletRuntime
+ callback:(RCTURLRequestCancellationBlock (^)(NSError *error, NSDictionary *result))
+ callback
+{
+ if (!query) {
+ return callback(nil, nil);
+ }
+ // for (id handler in _requestHandlers) {
+ // if ([handler canHandleNetworkingRequest:query]) {
+ // 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->rctNetworking_
+ networkTaskWithRequest:request
+ completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
+ // TODO: Fix
+ // dispatch_async(self->_methodQueue, ^{
+ // dispatch_async(dispatch_get_main_queue(), ^{
+ auto strongWorkletRuntime = workletRuntime.lock();
+ if (!strongWorkletRuntime) {
+ return;
+ }
+
+ strongWorkletRuntime->runOnQueue(^{
+ cancellationBlock = callback(
+ error, data ? @{@"body" : data, @"contentType" : RCTNullIfNil(response.MIMEType)} : nil);
+ });
+ }];
+
+ [task start];
+
+ __weak RCTNetworkTask *weakTask = task;
+ return ^{
+ [weakTask cancel];
+ if (cancellationBlock) {
+ cancellationBlock();
+ }
+ };
+ }
+ NSArray *formData = [RCTConvert NSDictionaryArray:query[@"formData"]];
+ if (formData) {
+ // TODO: FIX
+ WorkletsHTTPFormDataHelper *formDataHelper = [WorkletsHTTPFormDataHelper new];
+ formDataHelper.networker = self;
+ return [formDataHelper process:formData workletRuntime:workletRuntime 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
+ rt:(facebook::jsi::Runtime &)rt
+{
+ id responseData = nil;
+ // TODO: ???
+ // for (id 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 = [WorkletsNetworking 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;
+ }
+ }
+
+ [self emitDeviceEvent:@"didReceiveNetworkData" argFactory:@[ task.requestID, responseData ] rt:rt];
+}
+
+- (void)sendRequest:(NSURLRequest *)request
+ responseType:(NSString *)responseType
+ incrementalUpdates:(BOOL)incrementalUpdates
+ rt:(facebook::jsi::Runtime &)rt
+ responseSender:(jsi::Function &&)responseSender
+{
+ __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 emitDeviceEvent:@"didSendNetworkData" argFactory:responseJSON rt:rt];
+ };
+
+ RCTURLRequestResponseBlock responseBlock = ^(NSURLResponse *response) {
+ NSDictionary *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 *responseJSON = @[ task.requestID, @(status), headers, responseURL ];
+
+ if (facebook::react::ReactNativeFeatureFlags::enableNetworkEventReporting()) {
+ [RCTInspectorNetworkReporter reportResponseStart:task.requestID
+ response:response
+ statusCode:(int)status
+ headers:headers];
+ }
+ [weakSelf emitDeviceEvent:@"didReceiveNetworkResponse" argFactory:responseJSON rt:rt];
+ };
+
+ // 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;
+
+ NSString *responseString = [WorkletsNetworking 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 *responseJSON = @[
+ task.requestID,
+ responseString,
+ @(progress + initialCarryLength - incrementalDataCarry.length),
+ @(total)
+ ];
+
+ [weakSelf emitDeviceEvent:@"didReceiveNetworkIncrementalData" argFactory:responseJSON rt:rt];
+ };
+ } else {
+ downloadProgressBlock = ^(int64_t progress, int64_t total) {
+ NSArray *responseJSON = @[ task.requestID, @(progress), @(total) ];
+ [weakSelf emitDeviceEvent:@"didReceiveNetworkDataProgress" argFactory:responseJSON rt:rt];
+ };
+ }
+ }
+
+ 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 rt:rt];
+ }
+ NSArray *responseJSON =
+ @[ task.requestID, RCTNullIfNil(error.localizedDescription), error.code == kCFURLErrorTimedOut ? @YES : @NO ];
+
+ if (facebook::react::ReactNativeFeatureFlags::enableNetworkEventReporting()) {
+ [RCTInspectorNetworkReporter reportResponseEnd:task.requestID encodedDataLength:(int)data.length];
+ }
+ [strongSelf emitDeviceEvent:@"didCompleteNetworkResponse" argFactory:responseJSON rt:rt];
+
+ [strongSelf->_tasksLock lock];
+ [strongSelf->_tasksByRequestID removeObjectForKey:task.requestID];
+ [strongSelf->_tasksLock unlock];
+ };
+
+ task = [self->rctNetworking_ networkTaskWithRequest:request completionBlock:completionBlock];
+ task.downloadProgressBlock = downloadProgressBlock;
+ task.incrementalDataBlock = incrementalDataBlock;
+ task.responseBlock = responseBlock;
+ task.uploadProgressBlock = uploadProgressBlock;
+
+ if (task.requestID) {
+ [_tasksLock lock];
+ if (!_tasksByRequestID) {
+ _tasksByRequestID = [NSMutableDictionary new];
+ }
+ _tasksByRequestID[task.requestID] = task;
+ [_tasksLock unlock];
+ auto workletRuntime = runtimeManager_->getRuntime(&rt);
+ auto value = task.requestID.doubleValue;
+
+ responseSender.call(rt, jsi::Value(rt, value));
+
+ if (facebook::react::ReactNativeFeatureFlags::enableNetworkEventReporting()) {
+ [RCTInspectorNetworkReporter reportRequestStart:task.requestID
+ request:request
+ encodedDataLength:(int)task.response.expectedContentLength];
+ }
+ }
+
+ [task start];
+}
+
+using ArgFactory = std::function &args)>;
+
+- (void)emitDeviceEvent:(NSString *)eventName argFactory:(id)argFactory rt:(facebook::jsi::Runtime &)rt
+
+{
+ facebook::jsi::Value emitter = rt.global().getProperty(rt, "__rctDeviceEventEmitter");
+ if (!emitter.isUndefined()) {
+ facebook::jsi::Object emitterObject = emitter.asObject(rt);
+ // TODO: consider caching these
+ facebook::jsi::Function emitFunction = emitterObject.getPropertyAsFunction(rt, "emit");
+ std::vector args;
+ args.emplace_back(facebook::jsi::String::createFromAscii(rt, eventName.UTF8String));
+ if (argFactory) {
+ auto fly = facebook::react::convertIdToFollyDynamic(argFactory);
+ auto event = facebook::jsi::valueFromDynamic(rt, fly);
+
+ args.emplace_back(std::move(event));
+ }
+
+ emitFunction.callWithThis(rt, emitterObject, static_cast(args.data()), args.size());
+ }
+}
+
+@end
+#endif // WORKLETS_BUNDLE_MODE
diff --git a/packages/react-native-worklets/apple/worklets/apple/WorkletsModule.mm b/packages/react-native-worklets/apple/worklets/apple/WorkletsModule.mm
index d27547abf110..d904f053cb12 100644
--- a/packages/react-native-worklets/apple/worklets/apple/WorkletsModule.mm
+++ b/packages/react-native-worklets/apple/worklets/apple/WorkletsModule.mm
@@ -12,6 +12,14 @@
#import
#import
+#ifdef WORKLETS_BUNDLE_MODE
+#import
+#import
+#import
+
+#import
+#endif // WORKLETS_BUNDLE_MODE
+
#if __has_include()
// Bundle mode
#import
@@ -27,6 +35,10 @@ - (void *)runtime;
@implementation WorkletsModule {
AnimationFrameQueue *animationFrameQueue_;
std::shared_ptr workletsModuleProxy_;
+#ifdef WORKLETS_BUNDLE_MODE
+ RCTNetworking *networkingModule_;
+ WorkletsNetworking *workletsNetworking_;
+#endif // WORKLETS_BUNDLE_MODE
#ifndef NDEBUG
worklets::SingleInstanceChecker singleInstanceChecker_;
#endif // NDEBUG
@@ -49,7 +61,8 @@ - (void)checkBridgeless
react_native_assert(isBridgeless && "[Worklets] react-native-worklets only supports bridgeless mode");
}
-@synthesize callInvoker = _callInvoker;
+@synthesize callInvoker = callInvoker_;
+@synthesize moduleRegistry = moduleRegistry_;
RCT_EXPORT_MODULE(WorkletsModule);
@@ -69,28 +82,24 @@ - (void)checkBridgeless
std::string sourceURL = "";
std::shared_ptr script = nullptr;
+
+ auto jsCallInvoker = callInvoker_.callInvoker;
+ auto uiScheduler = std::make_shared();
+ auto runtimeManager = std::make_shared();
+ auto isJavaScriptQueue = []() -> bool { return IsJavaScriptQueue(); };
+ animationFrameQueue_ = [AnimationFrameQueue new];
#ifdef WORKLETS_BUNDLE_MODE
script = [bundleProvider_ getBundle];
sourceURL = [[bundleProvider_ getSourceURL] UTF8String];
+ networkingModule_ = [moduleRegistry_ moduleForClass:RCTNetworking.class];
+ workletsNetworking_ = [[WorkletsNetworking alloc] init:runtimeManager rctNetworking:networkingModule_];
#endif // WORKLETS_BUNDLE_MODE
+ auto runtimeBindings = [self getRuntimeBindings];
- auto jsCallInvoker = _callInvoker.callInvoker;
- auto uiScheduler = std::make_shared();
- auto isJavaScriptQueue = []() -> bool { return IsJavaScriptQueue(); };
- animationFrameQueue_ = [AnimationFrameQueue new];
- auto forwardedRequestAnimationFrame = std::function)>(
- [animationFrameQueue = animationFrameQueue_](std::function callback) {
- [animationFrameQueue requestAnimationFrame:callback];
- });
workletsModuleProxy_ = std::make_shared(
- rnRuntime,
- jsQueue,
- jsCallInvoker,
- uiScheduler,
- std::move(isJavaScriptQueue),
- std::move(forwardedRequestAnimationFrame),
- script,
- sourceURL);
+ rnRuntime, jsQueue, jsCallInvoker, uiScheduler, std::move(isJavaScriptQueue), runtimeBindings, runtimeManager, script, sourceURL
+ );
+
auto jsiWorkletsModuleProxy = workletsModuleProxy_->createJSIWorkletsModuleProxy();
auto optimizedJsiWorkletsModuleProxy = worklets::jsi_utils::optimizedFromHostObject(
rnRuntime, std::static_pointer_cast(std::move(jsiWorkletsModuleProxy)));
@@ -122,4 +131,33 @@ - (void)invalidate
return std::make_shared(params);
}
+- (worklets::RuntimeBindings)getRuntimeBindings
+{
+ return {
+ .requestAnimationFrame = [animationFrameQueue =
+ animationFrameQueue_](std::function &&callback) -> void {
+ [animationFrameQueue requestAnimationFrame:callback];
+ }
+ #ifdef WORKLETS_BUNDLE_MODE
+ ,
+ .abortRequest =
+ [workletsNetworking = workletsNetworking_](jsi::Runtime &rt, const jsi::Value &requestID) {
+ [workletsNetworking jsiAbortRequest:requestID.asNumber()];
+ return jsi::Value::undefined();
+ },
+ .clearCookies =
+ [workletsNetworking = workletsNetworking_](jsi::Runtime &rt, jsi::Function &&responseSender) {
+ [workletsNetworking jsiClearCookies:rt responseSender:(std::move(responseSender))];
+ return jsi::Value::undefined();
+ },
+ .sendRequest =
+ [workletsNetworking = workletsNetworking_](
+ jsi::Runtime &rt, const jsi::Value &query, jsi::Function &&responseSender) {
+ [workletsNetworking jsiSendRequest:rt jquery:query responseSender:(std::move(responseSender))];
+ return jsi::Value::undefined();
+ }
+ #endif // WORKLETS_BUNDLE_MODE
+ };
+}
+
@end
diff --git a/packages/react-native-worklets/plugin/index.js b/packages/react-native-worklets/plugin/index.js
index 0044ac6e922d..ca6ec5105a4a 100644
--- a/packages/react-native-worklets/plugin/index.js
+++ b/packages/react-native-worklets/plugin/index.js
@@ -387,6 +387,8 @@ var require_globals = __commonJS({
"performance",
"arguments",
"require",
+ "fetch",
+ "XMLHttpRequest",
"queueMicrotask",
"requestAnimationFrame",
"cancelAnimationFrame",
diff --git a/packages/react-native-worklets/plugin/src/globals.ts b/packages/react-native-worklets/plugin/src/globals.ts
index 5495da9ab7e2..6d6f7940f23c 100644
--- a/packages/react-native-worklets/plugin/src/globals.ts
+++ b/packages/react-native-worklets/plugin/src/globals.ts
@@ -111,6 +111,8 @@ const notCapturedIdentifiers = [
'performance',
'arguments', // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
'require',
+ 'fetch',
+ 'XMLHttpRequest',
// Run loop
'queueMicrotask',
diff --git a/packages/react-native-worklets/src/initializers.ts b/packages/react-native-worklets/src/initializers.ts
index fd180700c777..6b1e3ee3a12b 100644
--- a/packages/react-native-worklets/src/initializers.ts
+++ b/packages/react-native-worklets/src/initializers.ts
@@ -3,6 +3,7 @@
import { bundleValueUnpacker } from './bundleUnpacker';
import { setupCallGuard } from './callGuard';
import { registerReportFatalRemoteError } from './errors';
+import { initializeNetworking } from './network';
import { IS_JEST, SHOULD_BE_USE_WEB } from './PlatformChecker';
import { setupSetImmediate } from './runLoop/common/setImmediatePolyfill';
import { setupSetInterval } from './runLoop/common/setIntervalPolyfill';
@@ -172,7 +173,9 @@ function initializeWorkletRuntime() {
{
get: function get(_target, prop) {
globalThis.console.warn(
- `You tried to import '${String(prop)}' from 'react-native' module on a Worklet Runtime. Using 'react-native' module on a Worklet Runtime is not allowed.`
+ `You tried to import '${String(prop)}' from 'react-native' module on a Worklet Runtime. Using 'react-native' module on a Worklet Runtime is not allowed.`,
+ // eslint-disable-next-line reanimated/use-worklets-error
+ new Error().stack
);
return {
get() {
@@ -197,6 +200,95 @@ function initializeWorkletRuntime() {
};
modules.set(ReactNativeModuleId, mod);
+
+ // @ts-expect-error type not exposed by Metro
+ const PolyfillFunctionsId = require.resolveWeak(
+ 'react-native/Libraries/Utilities/PolyfillFunctions'
+ );
+
+ const polyfillFactory = function (
+ _global: unknown,
+ _$$_REQUIRE: unknown,
+ _$$_IMPORT_DEFAULT: unknown,
+ _$$_IMPORT_ALL: unknown,
+ module: Record>,
+ _exports: unknown,
+ _dependencyMap: unknown
+ ) {
+ module.exports.polyfillGlobal = (
+ name: string,
+ getValue: () => unknown
+ ) => {
+ // globalThis._log('polyfillGlobal ' + name + ' ' + getValue);
+ (globalThis as Record)[name] = getValue();
+ };
+ };
+
+ const polyfillMod = {
+ dependencyMap: [],
+ factory: polyfillFactory,
+ hasError: false,
+ importedAll: {},
+ importedDefault: {},
+ isInitialized: false,
+ publicModule: {
+ exports: {},
+ },
+ };
+
+ modules.set(PolyfillFunctionsId, polyfillMod);
+
+ // @ts-expect-error type not exposed by Metro
+ const TurboModuleRegistryId = require.resolveWeak(
+ 'react-native/Libraries/TurboModule/TurboModuleRegistry'
+ );
+
+ const TurboModules = new Map();
+
+ // globalThis.TurboModules = new Map();
+ // @ts-expect-error type not exposed by Metro
+ globalThis.TurboModules = TurboModules;
+
+ TurboModules.set('Networking', {});
+
+ const faactory = function (
+ _global: unknown,
+ _$$_REQUIRE: unknown,
+ _$$_IMPORT_DEFAULT: unknown,
+ _$$_IMPORT_ALL: unknown,
+ module: Record,
+ _exports: unknown,
+ _dependencyMap: unknown
+ ) {
+ function get(name: string) {
+ globalThis._log('TurboModuleRegistry get ' + name);
+ // @ts-expect-error type not exposed by Metro
+ return globalThis.TurboModules.get(name);
+ }
+ function getEnforcing(name: string) {
+ globalThis._log('TurboModuleRegistry getEnforcing ' + name);
+ // @ts-expect-error type not exposed by Metro
+ return globalThis.TurboModules.get(name);
+ }
+ // @ts-expect-error type not exposed by Metro
+ module.exports.get = get;
+ // @ts-expect-error type not exposed by Metro
+ module.exports.getEnforcing = getEnforcing;
+ };
+
+ const mood = {
+ dependencyMap: [],
+ factory: faactory,
+ hasError: false,
+ importedAll: {},
+ importedDefault: {},
+ isInitialized: false,
+ publicModule: {
+ exports: {},
+ },
+ };
+
+ modules.set(TurboModuleRegistryId, mood);
}
}
}
@@ -259,4 +351,8 @@ function installRNBindingsOnUIRuntime() {
setupSetImmediate();
setupSetInterval();
})();
+
+ if (globalThis._WORKLETS_BUNDLE_MODE) {
+ executeOnUIRuntimeSync(initializeNetworking)();
+ }
}
diff --git a/packages/react-native-worklets/src/network/index.ts b/packages/react-native-worklets/src/network/index.ts
new file mode 100644
index 000000000000..aeecc9a46f14
--- /dev/null
+++ b/packages/react-native-worklets/src/network/index.ts
@@ -0,0 +1,75 @@
+'use strict';
+
+import { TurboModuleRegistry } from 'react-native';
+
+import { WorkletsError } from '../WorkletsError';
+
+// const FileReaderModule = getHostObjectFromTurboModule(
+// TurboModuleRegistry.getEnforcing('FileReaderModule')
+// );
+// const PlatformConstans = getHostObjectFromTurboModule(
+// TurboModuleRegistry.getEnforcing('PlatformConstants')
+// );
+// const WebSocketModule = getHostObjectFromTurboModule(
+// TurboModuleRegistry.getEnforcing('WebSocketModule')
+// );
+const BlobModule = getHostObjectFromTurboModule(
+ TurboModuleRegistry.getEnforcing('BlobModule')
+);
+
+export function initializeNetworking() {
+ 'worklet';
+
+ const errorProxyFactory = (moduleName: string) => {
+ return new Proxy(
+ {},
+ {
+ get: (__, propName) => {
+ throw new WorkletsError(
+ `${propName as string} not available in ${moduleName}`
+ );
+ },
+ }
+ );
+ };
+
+ const BlobProxy = new Proxy(BlobModule, {
+ get: (target, propName) => {
+ if (propName === 'addNetworkingHandler') {
+ return () => {};
+ }
+ if (propName === 'getConstants') {
+ return target.getConstants.bind(BlobModule);
+ } else {
+ return undefined;
+ }
+ },
+ });
+
+ const TurboModules = (globalThis as Record)
+ .TurboModules as Map;
+
+ try {
+ TurboModules.set('FileReaderModule', errorProxyFactory('FileReaderModule'));
+ TurboModules.set(
+ 'PlatformConstants',
+ errorProxyFactory('PlatformConstants')
+ );
+ TurboModules.set('WebSocketModule', errorProxyFactory('WebSocketModule'));
+ TurboModules.set('BlobModule', BlobProxy);
+ } catch (e) {
+ console.error('Error initializing networking:', e);
+ }
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
+ require('react-native/Libraries/Core/setUpXHR');
+}
+
+function getHostObjectFromTurboModule(turboModule: object) {
+ const hostObject = Object.getPrototypeOf(turboModule);
+ if (!('mmmmmmagic' in hostObject)) {
+ throw new WorkletsError(
+ 'Host object is not available. Make sure you are using the correct TurboModule.'
+ );
+ }
+ return hostObject;
+}
diff --git a/packages/react-native-worklets/src/runtimes.ts b/packages/react-native-worklets/src/runtimes.ts
index fafc128647a5..e5f1362747a9 100644
--- a/packages/react-native-worklets/src/runtimes.ts
+++ b/packages/react-native-worklets/src/runtimes.ts
@@ -2,6 +2,7 @@
import { setupCallGuard } from './callGuard';
import { getMemorySafeCapturableConsole, setupConsole } from './initializers';
+import { initializeNetworking } from './network';
import { SHOULD_BE_USE_WEB } from './PlatformChecker';
import { setupRunLoop } from './runLoop/workletRuntime';
import { RuntimeKind } from './runtimeKind';
@@ -90,6 +91,9 @@ export function createWorkletRuntime(
if (enableEventLoop) {
setupRunLoop(animationQueuePollingRate);
}
+ if (globalThis._WORKLETS_BUNDLE_MODE) {
+ initializeNetworking();
+ }
initializerFn?.();
}),
useDefaultQueue,
diff --git a/yarn.lock b/yarn.lock
index 1061bd666f53..7ac3d22635f9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10215,6 +10215,17 @@ __metadata:
languageName: node
linkType: hard
+"axios@npm:1.10.0":
+ version: 1.10.0
+ resolution: "axios@npm:1.10.0"
+ dependencies:
+ follow-redirects: "npm:^1.15.6"
+ form-data: "npm:^4.0.0"
+ proxy-from-env: "npm:^1.1.0"
+ checksum: 10/d43c80316a45611fd395743e15d16ea69a95f2b7f7095f2bb12cb78f9ca0a905194a02e52a3bf4e0db9f85fd1186d6c690410644c10ecd8bb0a468e57c2040e4
+ languageName: node
+ linkType: hard
+
"axios@npm:^1.12.1":
version: 1.12.2
resolution: "axios@npm:1.12.2"
@@ -12449,6 +12460,7 @@ __metadata:
"@types/jest": "npm:30.0.0"
"@types/react": "npm:19.2.2"
"@types/react-test-renderer": "npm:19.1.0"
+ axios: "npm:1.10.0"
d3-shape: "npm:3.2.0"
eslint: "npm:9.37.0"
fuse.js: "patch:fuse.js@npm%3A7.1.0#~/.yarn/patches/fuse.js-npm-7.1.0-5dcae892a6.patch"
@@ -16168,7 +16180,7 @@ __metadata:
languageName: node
linkType: hard
-"form-data@npm:^4.0.4, form-data@npm:~4.0.4":
+"form-data@npm:^4.0.0, form-data@npm:^4.0.4, form-data@npm:~4.0.4":
version: 4.0.4
resolution: "form-data@npm:4.0.4"
dependencies: