From 261c8b347a6469acfb2939d454b578a4785b3a65 Mon Sep 17 00:00:00 2001 From: riteshshukla04 Date: Mon, 17 Nov 2025 23:50:25 +0530 Subject: [PATCH 1/2] fix: worklets --- bun.lock | 5 +- example/babel.config.js | 1 + .../xcschemes/NitroFetchExample.xcscheme | 2 +- example/package.json | 2 +- example/src/App.tsx | 33 ++++++-- .../nitro/nitrofetch/NitroFetchClient.kt | 78 ++++++++++++------- package/ios/NitroFetchClient.swift | 25 ++++++ .../c++/JHybridNitroFetchClientSpec.cpp | 5 ++ .../c++/JHybridNitroFetchClientSpec.hpp | 1 + .../nitrofetch/HybridNitroFetchClientSpec.kt | 4 + .../ios/NitroFetch-Swift-Cxx-Bridge.hpp | 9 +++ .../c++/HybridNitroFetchClientSpecSwift.hpp | 8 ++ .../swift/HybridNitroFetchClientSpec.swift | 1 + .../HybridNitroFetchClientSpec_cxx.swift | 12 +++ .../shared/c++/HybridNitroFetchClientSpec.cpp | 1 + .../shared/c++/HybridNitroFetchClientSpec.hpp | 1 + package/src/NitroFetch.nitro.ts | 3 + package/src/NitroInstances.ts | 2 + package/src/fetch.ts | 69 ++++++---------- 19 files changed, 178 insertions(+), 84 deletions(-) diff --git a/bun.lock b/bun.lock index 48814d8..588dc93 100644 --- a/bun.lock +++ b/bun.lock @@ -40,10 +40,11 @@ "react-native": "0.81.0", "react-native-nitro-modules": "^0.29.2", "react-native-safe-area-context": "^5.5.2", - "react-native-worklets-core": "^1.6.0", + "react-native-worklets-core": "^1.6.2", }, "devDependencies": { "@babel/core": "^7.25.2", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", "@babel/preset-env": "^7.25.3", "@babel/runtime": "^7.25.0", "@react-native-community/cli": "20.0.0", @@ -166,6 +167,8 @@ "@babel/plugin-proposal-export-default-from": ["@babel/plugin-proposal-export-default-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw=="], + "@babel/plugin-proposal-nullish-coalescing-operator": ["@babel/plugin-proposal-nullish-coalescing-operator@7.18.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA=="], + "@babel/plugin-proposal-private-property-in-object": ["@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w=="], "@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="], diff --git a/example/babel.config.js b/example/babel.config.js index 85b81e7..00dd58b 100644 --- a/example/babel.config.js +++ b/example/babel.config.js @@ -7,6 +7,7 @@ const root = path.resolve(__dirname, '..', 'package'); module.exports = getConfig( { presets: ['module:@react-native/babel-preset'], + plugins: ["react-native-worklets-core/plugin"], }, { root, pkg } ); diff --git a/example/ios/NitroFetchExample.xcodeproj/xcshareddata/xcschemes/NitroFetchExample.xcscheme b/example/ios/NitroFetchExample.xcodeproj/xcshareddata/xcschemes/NitroFetchExample.xcscheme index 554704b..2b61c7f 100644 --- a/example/ios/NitroFetchExample.xcodeproj/xcshareddata/xcschemes/NitroFetchExample.xcscheme +++ b/example/ios/NitroFetchExample.xcodeproj/xcshareddata/xcschemes/NitroFetchExample.xcscheme @@ -41,7 +41,7 @@ ; - const arr = Object.entries(json).map(([id, v]) => ({ id, usd: v.usd })); - arr.sort((a, b) => a.id.localeCompare(b.id)); + const entries = Object.entries(json); + const arr = []; + for (let i = 0; i < entries.length; ++i) { + const entry = entries[i]; + arr.push({ id: entry[0], usd: entry[1].usd }); + } + // Manual sort (localeCompare not available in worklets, use plain compare) + for (let i = 0; i < arr.length - 1; ++i) { + for (let j = i + 1; j < arr.length; ++j) { + if (arr[i].id > arr[j].id) { + const tmp = arr[i] as { id: string; usd: number }; + arr[i] = arr[j]; + arr[j] = tmp; + } + } + } return arr; }; console.log('Loading crypto prices from coingecko'); - const data = await nitroFetchOnWorklet(url, undefined, mapper, { - preferBytes: false, - }); - console.log('Loaded crypto prices:', data); - setPrices(data); + try { + const data = await nitroFetchOnWorklet(url, undefined, mapper, { + preferBytes: false, + }); + console.log('Loaded crypto prices:', data); + setPrices(data); + } catch (e: any) { + console.error('Loading crypto prices error', e); + } }, []); const run = React.useCallback(async () => { diff --git a/package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt b/package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt index bbff569..842ba69 100644 --- a/package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +++ b/package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt @@ -155,6 +155,56 @@ class NitroFetchClient(private val engine: CronetEngine, private val executor: E } } + // Helper function to add prefetch header to response (reused by both sync/async) + private fun withPrefetchedHeader(res: NitroResponse): NitroResponse { + val newHeaders = (res.headers?.toMutableList() ?: mutableListOf()) + newHeaders.add(NitroHeader("nitroPrefetched", "true")) + return NitroResponse( + url = res.url, + status = res.status, + statusText = res.statusText, + ok = res.ok, + redirected = res.redirected, + headers = newHeaders.toTypedArray(), + bodyString = res.bodyString, + bodyBytes = res.bodyBytes + ) + } + + override fun requestSync(req: NitroRequest): NitroResponse { + val key = findPrefetchKey(req) + if (key != null) { + FetchCache.getPending(key)?.let { fut -> + return try { + withPrefetchedHeader(fut.get()) // blocks until complete + } catch (e: Exception) { + throw e.cause ?: e + } + } + FetchCache.getResultIfFresh(key, 5_000L)?.let { cached -> + return withPrefetchedHeader(cached) + } + } + val latch = java.util.concurrent.CountDownLatch(1) + var result: NitroResponse? = null + var error: Throwable? = null + + fetch( + req, + onSuccess = { + result = it + latch.countDown() + }, + onFail = { + error = it + latch.countDown() + } + ) + latch.await() + error?.let { throw it } + return result!! + } + override fun request(req: NitroRequest): Promise { val promise = Promise() // Try to serve from prefetch cache/pending first @@ -162,20 +212,6 @@ class NitroFetchClient(private val engine: CronetEngine, private val executor: E if (key != null) { // If a prefetch is currently pending, wait for it FetchCache.getPending(key)?.let { fut -> - fun withPrefetchedHeader(res: NitroResponse): NitroResponse { - val newHeaders = (res.headers?.toMutableList() ?: mutableListOf()) - newHeaders.add(NitroHeader("nitroPrefetched", "true")) - return NitroResponse( - url = res.url, - status = res.status, - statusText = res.statusText, - ok = res.ok, - redirected = res.redirected, - headers = newHeaders.toTypedArray(), - bodyString = res.bodyString, - bodyBytes = res.bodyBytes - ) - } fut.whenComplete { res, err -> if (err != null) { promise.reject(err) @@ -189,19 +225,7 @@ class NitroFetchClient(private val engine: CronetEngine, private val executor: E } // If a fresh prefetched result exists (<=5s old), return it immediately FetchCache.getResultIfFresh(key, 5_000L)?.let { cached -> - val newHeaders = (cached.headers?.toMutableList() ?: mutableListOf()) - newHeaders.add(NitroHeader("nitroPrefetched", "true")) - val wrapped = NitroResponse( - url = cached.url, - status = cached.status, - statusText = cached.statusText, - ok = cached.ok, - redirected = cached.redirected, - headers = newHeaders.toTypedArray(), - bodyString = cached.bodyString, - bodyBytes = cached.bodyBytes - ) - promise.resolve(wrapped) + promise.resolve(withPrefetchedHeader(cached)) return promise } } diff --git a/package/ios/NitroFetchClient.swift b/package/ios/NitroFetchClient.swift index 91c731e..b8cd558 100644 --- a/package/ios/NitroFetchClient.swift +++ b/package/ios/NitroFetchClient.swift @@ -2,6 +2,31 @@ import Foundation import NitroModules final class NitroFetchClient: HybridNitroFetchClientSpec { + func requestSync(req: NitroRequest) throws -> NitroResponse { + let semaphore = DispatchSemaphore(value: 0) + var result: Result? + + Task { + do { + let response = try await NitroFetchClient.requestStatic(req) + result = .success(response) + } catch { + result = .failure(error) + } + semaphore.signal() + } + + semaphore.wait() + + switch result! { + case .success(let response): + return response + case .failure(let error): + throw error + } + } + + // Async version - returns Promise func request(req: NitroRequest) throws -> Promise { let promise = Promise.init() Task { diff --git a/package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp b/package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp index 3351207..23ef43b 100644 --- a/package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp +++ b/package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.cpp @@ -87,5 +87,10 @@ namespace margelo::nitro::nitrofetch { return __promise; }(); } + NitroResponse JHybridNitroFetchClientSpec::requestSync(const NitroRequest& req) { + static const auto method = javaClassStatic()->getMethod(jni::alias_ref /* req */)>("requestSync"); + auto __result = method(_javaPart, JNitroRequest::fromCpp(req)); + return __result->toCpp(); + } } // namespace margelo::nitro::nitrofetch diff --git a/package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.hpp b/package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.hpp index b06ff39..d6e3677 100644 --- a/package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.hpp +++ b/package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.hpp @@ -55,6 +55,7 @@ namespace margelo::nitro::nitrofetch { // Methods std::shared_ptr> request(const NitroRequest& req) override; std::shared_ptr> prefetch(const NitroRequest& req) override; + NitroResponse requestSync(const NitroRequest& req) override; private: friend HybridBase; diff --git a/package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec.kt b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec.kt index c6a8cee..285f036 100644 --- a/package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec.kt +++ b/package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec.kt @@ -47,6 +47,10 @@ abstract class HybridNitroFetchClientSpec: HybridObject() { @DoNotStrip @Keep abstract fun prefetch(req: NitroRequest): Promise + + @DoNotStrip + @Keep + abstract fun requestSync(req: NitroRequest): NitroResponse private external fun initHybrid(): HybridData diff --git a/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp b/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp index 69c358a..9a3cfb6 100644 --- a/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp +++ b/package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp @@ -258,6 +258,15 @@ namespace margelo::nitro::nitrofetch::bridge::swift { return Result>>::withError(error); } + // pragma MARK: Result + using Result_NitroResponse_ = Result; + inline Result_NitroResponse_ create_Result_NitroResponse_(const NitroResponse& value) noexcept { + return Result::withValue(value); + } + inline Result_NitroResponse_ create_Result_NitroResponse_(const std::exception_ptr& error) noexcept { + return Result::withError(error); + } + // pragma MARK: std::shared_ptr /** * Specialized version of `std::shared_ptr`. diff --git a/package/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.hpp b/package/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.hpp index 7e0882b..26e48fc 100644 --- a/package/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.hpp +++ b/package/nitrogen/generated/ios/c++/HybridNitroFetchClientSpecSwift.hpp @@ -87,6 +87,14 @@ namespace margelo::nitro::nitrofetch { auto __value = std::move(__result.value()); return __value; } + inline NitroResponse requestSync(const NitroRequest& req) override { + auto __result = _swiftPart.requestSync(req); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + auto __value = std::move(__result.value()); + return __value; + } private: NitroFetch::HybridNitroFetchClientSpec_cxx _swiftPart; diff --git a/package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec.swift b/package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec.swift index 1548ec5..57e4e57 100644 --- a/package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec.swift +++ b/package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec.swift @@ -16,6 +16,7 @@ public protocol HybridNitroFetchClientSpec_protocol: HybridObject { // Methods func request(req: NitroRequest) throws -> Promise func prefetch(req: NitroRequest) throws -> Promise + func requestSync(req: NitroRequest) throws -> NitroResponse } /// See ``HybridNitroFetchClientSpec`` diff --git a/package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec_cxx.swift b/package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec_cxx.swift index 2cc0866..ee3acec 100644 --- a/package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec_cxx.swift +++ b/package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec_cxx.swift @@ -146,4 +146,16 @@ open class HybridNitroFetchClientSpec_cxx { return bridge.create_Result_std__shared_ptr_Promise_void___(__exceptionPtr) } } + + @inline(__always) + public final func requestSync(req: NitroRequest) -> bridge.Result_NitroResponse_ { + do { + let __result = try self.__implementation.requestSync(req: req) + let __resultCpp = __result + return bridge.create_Result_NitroResponse_(__resultCpp) + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_NitroResponse_(__exceptionPtr) + } + } } diff --git a/package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp b/package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp index 1540766..7a17999 100644 --- a/package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp +++ b/package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp @@ -16,6 +16,7 @@ namespace margelo::nitro::nitrofetch { registerHybrids(this, [](Prototype& prototype) { prototype.registerHybridMethod("request", &HybridNitroFetchClientSpec::request); prototype.registerHybridMethod("prefetch", &HybridNitroFetchClientSpec::prefetch); + prototype.registerHybridMethod("requestSync", &HybridNitroFetchClientSpec::requestSync); }); } diff --git a/package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.hpp b/package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.hpp index 936fff5..4f3f49d 100644 --- a/package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.hpp +++ b/package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.hpp @@ -55,6 +55,7 @@ namespace margelo::nitro::nitrofetch { // Methods virtual std::shared_ptr> request(const NitroRequest& req) = 0; virtual std::shared_ptr> prefetch(const NitroRequest& req) = 0; + virtual NitroResponse requestSync(const NitroRequest& req) = 0; protected: // Hybrid Setup diff --git a/package/src/NitroFetch.nitro.ts b/package/src/NitroFetch.nitro.ts index 3511d03..4dd3a4f 100644 --- a/package/src/NitroFetch.nitro.ts +++ b/package/src/NitroFetch.nitro.ts @@ -45,6 +45,9 @@ export interface NitroFetchClient request(req: NitroRequest): Promise; // Start a prefetch for a given request; expects a header `prefetchKey`. prefetch(req: NitroRequest): Promise; + + // Synchronous version of request for worklets + requestSync(req: NitroRequest): NitroResponse; } export interface NitroFetch diff --git a/package/src/NitroInstances.ts b/package/src/NitroInstances.ts index d5debbb..b8e66fe 100644 --- a/package/src/NitroInstances.ts +++ b/package/src/NitroInstances.ts @@ -10,3 +10,5 @@ export const NitroFetch: NitroFetchType = export const NativeStorage: NativeStorageType = NitroModules.createHybridObject('NativeStorage'); + +export const boxedNitroFetch = NitroModules.box(NitroFetch); diff --git a/package/src/fetch.ts b/package/src/fetch.ts index 153d47c..245f1b4 100644 --- a/package/src/fetch.ts +++ b/package/src/fetch.ts @@ -4,7 +4,10 @@ import type { NitroRequest, NitroResponse, } from './NitroFetch.nitro'; -import { NitroFetch as NitroFetchSingleton } from './NitroInstances'; +import { + boxedNitroFetch, + NitroFetch as NitroFetchSingleton, +} from './NitroInstances'; import { NativeStorage as NativeStorageSingleton } from './NitroInstances'; // No base64: pass strings/ArrayBuffers directly @@ -121,7 +124,6 @@ async function nitroFetchRaw( input: RequestInfo | URL, init?: RequestInit ): Promise { - 'worklet'; const hasNative = typeof (NitroFetchHybrid as any)?.createClient === 'function'; if (!hasNative) { @@ -193,8 +195,6 @@ export async function nitroFetch( input: RequestInfo | URL, init?: RequestInit ): Promise { - 'worklet'; - const res = await nitroFetchRaw(input, init); const headersObj = new NitroHeaders(res.headers); @@ -360,7 +360,7 @@ function ensureWorkletRuntime(name = 'nitro-fetch'): any | undefined { console.log('ensuring worklet runtime'); try { const { Worklets } = require('react-native-worklets-core'); - nitroRuntime = nitroRuntime ?? Worklets.createRuntime(name); + nitroRuntime = nitroRuntime ?? Worklets.createContext(name); console.log('nitroRuntime:', !!nitroRuntime); return nitroRuntime; } catch { @@ -403,7 +403,7 @@ export async function nitroFetchOnWorklet( } // Fallback: if runtime is not available, do the work on JS - if (!rt || !Worklets || typeof rt.run !== 'function') { + if (!rt || !Worklets || typeof rt.runAsync !== 'function') { console.warn('nitroFetchOnWorklet: no runtime, mapping on JS thread'); const res = await nitroFetchRaw(input, init); const payload = { @@ -418,47 +418,24 @@ export async function nitroFetchOnWorklet( } as const; return mapWorklet(payload as any); } + console.log('nitroFetchOnWorklet: running on worklet thread'); + return await rt.runAsync(() => { + 'worklet'; + const unboxedNitroFetch = boxedNitroFetch.unbox(); + const unboxedClient = unboxedNitroFetch.createClient(); + const res = unboxedClient.requestSync(buildNitroRequest(input, init)); + const payload = { + url: res.url, + status: res.status, + statusText: res.statusText, + ok: res.ok, + redirected: res.redirected, + headers: res.headers, + bodyBytes: preferBytes ? res.bodyBytes : undefined, + bodyString: preferBytes ? undefined : res.bodyString, + } as const; - return await new Promise((resolve, reject) => { - try { - console.log('nitroFetchOnWorklet: about to call rt.run'); - rt.run(async (map: NitroWorkletMapper) => { - 'worklet'; - try { - console.log('nitroFetchOnWorklet: running fetch on worklet thread'); - const res = await nitroFetchRaw(input, init); - console.log('nitroFetchOnWorklet: fetch completed'); - const url = res.url; - const status = res.status; - const statusText = res.statusText; - const ok = res.ok; - const redirected = res.redirected; - const headersPairs: NitroHeader[] = res.headers; - const bodyBytes: ArrayBuffer | undefined = undefined; // preferBytes ? res.bodyBytes : undefined; - const bodyString: string | undefined = preferBytes - ? undefined - : res.bodyString; - const payload = { - url, - status, - statusText, - ok, - redirected, - headers: headersPairs, - bodyBytes, - bodyString, - }; - const out = map(payload); - // Resolve back on JS thread - Worklets.runOnJS(resolve)(out as any); - } catch (e) { - Worklets.runOnJS(reject)(e as any); - } - }, mapWorklet as any); - } catch (e) { - console.error('nitroFetchOnWorklet: rt.run failed', e); - reject(e); - } + return mapWorklet(payload as any); }); } From 2b66d038c82233ebfa97ed37359bc21768dc9aa7 Mon Sep 17 00:00:00 2001 From: riteshshukla04 Date: Mon, 17 Nov 2025 23:53:38 +0530 Subject: [PATCH 2/2] fix: worklets --- example/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/example/package.json b/example/package.json index a7383fd..972f7db 100644 --- a/example/package.json +++ b/example/package.json @@ -19,6 +19,7 @@ }, "devDependencies": { "@babel/core": "^7.25.2", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", "@babel/preset-env": "^7.25.3", "@babel/runtime": "^7.25.0", "@react-native-community/cli": "20.0.0",