Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions example/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
);
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
Expand Down
3 changes: 2 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,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",
Expand Down
33 changes: 25 additions & 8 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ async function measure(
const cached = detectCached(res.headers);
return { ok: true, ms: t1 - t0, cached } as const;
} catch (e: any) {
console.error('measure error', url, e);
const t1 = global.performance ? global.performance.now() : Date.now();
return { ok: false, ms: t1 - t0, error: e?.message ?? String(e) } as const;
}
Expand Down Expand Up @@ -204,16 +203,34 @@ export default function App() {
'worklet';
const txt = payload.bodyString ?? '';
const json = JSON.parse(txt) as Record<string, { usd: number }>;
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 () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,27 +155,63 @@ 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<NitroResponse> {
val promise = Promise<NitroResponse>()
// Try to serve from prefetch cache/pending first
val key = findPrefetchKey(req)
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)
Expand All @@ -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
}
}
Expand Down
25 changes: 25 additions & 0 deletions package/ios/NitroFetchClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<NitroResponse, Error>?

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<NitroResponse>
func request(req: NitroRequest) throws -> Promise<NitroResponse> {
let promise = Promise<NitroResponse>.init()
Task {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package/src/NitroFetch.nitro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export interface NitroFetchClient
request(req: NitroRequest): Promise<NitroResponse>;
// Start a prefetch for a given request; expects a header `prefetchKey`.
prefetch(req: NitroRequest): Promise<void>;

// Synchronous version of request for worklets
requestSync(req: NitroRequest): NitroResponse;
}

export interface NitroFetch
Expand Down
2 changes: 2 additions & 0 deletions package/src/NitroInstances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ export const NitroFetch: NitroFetchType =

export const NativeStorage: NativeStorageType =
NitroModules.createHybridObject<NativeStorageType>('NativeStorage');

export const boxedNitroFetch = NitroModules.box(NitroFetch);
Loading
Loading