react-native-nitro-fetch 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,7 +25,10 @@ Pod::Spec.new do |s|
25
25
  s.dependency 'React-RCTNetwork'
26
26
 
27
27
  # Expose the DevTools reporter Obj-C facade to Swift via the pod's umbrella module.
28
- s.public_header_files = ["ios/NitroDevToolsReporter.h"]
28
+ s.public_header_files = [
29
+ "ios/NitroDevToolsReporter.h",
30
+ "ios/NitroAutoPrefetcher.h",
31
+ ]
29
32
 
30
33
  load 'nitrogen/generated/ios/NitroFetch+autolinking.rb'
31
34
  add_nitrogen_files(s)
@@ -16,6 +16,77 @@ object AutoPrefetcher {
16
16
  private const val KEY_TOKEN_CACHE = "nitro_token_refresh_fetch_cache"
17
17
  private const val PREFS_NAME = NitroFetchSecureAtRest.PREFS_NAME
18
18
 
19
+ /**
20
+ * Register a URL to prefetch on app start. Call from `Application.onCreate()`
21
+ * BEFORE `prefetchOnStart(this)`. Writes to the same persistent queue used by
22
+ * the JS `prefetchOnAppStart` API; entries are deduped by `prefetchKey`.
23
+ *
24
+ * If called after `prefetchOnStart` already ran (late registration), the
25
+ * entry is also kicked immediately via `NitroFetchClient.fetch` so the
26
+ * current session benefits without waiting for the next cold launch.
27
+ */
28
+ @JvmStatic
29
+ @JvmOverloads
30
+ fun registerPrefetch(
31
+ context: Context,
32
+ url: String,
33
+ prefetchKey: String,
34
+ headers: Map<String, String> = emptyMap()
35
+ ) {
36
+ if (url.isEmpty() || prefetchKey.isEmpty()) return
37
+ try {
38
+ val prefs = context.applicationContext
39
+ .getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
40
+ val raw = prefs.getString(KEY_QUEUE, null) ?: ""
41
+ val arr = if (raw.isEmpty()) JSONArray() else try { JSONArray(raw) } catch (_: Throwable) { JSONArray() }
42
+
43
+ val next = JSONArray()
44
+ for (i in 0 until arr.length()) {
45
+ val o = arr.optJSONObject(i) ?: continue
46
+ if (o.optString("prefetchKey", "") == prefetchKey) continue
47
+ next.put(o)
48
+ }
49
+
50
+ val headersObj = JSONObject()
51
+ headers.forEach { (k, v) -> headersObj.put(k, v) }
52
+ val entry = JSONObject().apply {
53
+ put("url", url)
54
+ put("prefetchKey", prefetchKey)
55
+ put("headers", headersObj)
56
+ }
57
+ next.put(entry)
58
+ prefs.edit().putString(KEY_QUEUE, next.toString()).apply()
59
+ } catch (_: Throwable) {
60
+ // best-effort
61
+ }
62
+
63
+ if (initialized) {
64
+ // late path — kick a single immediate prefetch with cached token headers
65
+ try {
66
+ val prefs = context.applicationContext
67
+ .getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
68
+ val cacheRaw = NitroFetchSecureAtRest.getDecryptedForPrefs(prefs, KEY_TOKEN_CACHE)
69
+ val tokenHeaders: Map<String, String> = if (!cacheRaw.isNullOrEmpty()) {
70
+ try {
71
+ val co = JSONObject(cacheRaw)
72
+ co.keys().asSequence().associateWith { k -> co.optString(k, "") }
73
+ } catch (_: Throwable) { emptyMap() }
74
+ } else emptyMap()
75
+
76
+ val single = JSONArray().apply {
77
+ val headersObj = JSONObject()
78
+ headers.forEach { (k, v) -> headersObj.put(k, v) }
79
+ put(JSONObject().apply {
80
+ put("url", url)
81
+ put("prefetchKey", prefetchKey)
82
+ put("headers", headersObj)
83
+ })
84
+ }
85
+ startPrefetches(single, tokenHeaders)
86
+ } catch (_: Throwable) {}
87
+ }
88
+ }
89
+
19
90
  fun prefetchOnStart(app: Application) {
20
91
  if (initialized) return
21
92
  initialized = true
@@ -155,12 +226,22 @@ object AutoPrefetcher {
155
226
  conn.setRequestProperty(k, reqHeaders.optString(k, ""))
156
227
  }
157
228
 
229
+ NitroCookieSync.attachCookieFromManagerIfMissing(
230
+ urlStr,
231
+ NitroCookieSync.hasCookieHeaderInJson(reqHeaders)
232
+ ) { key, value -> conn.setRequestProperty(key, value) }
233
+
158
234
  if (body != null) {
159
235
  conn.outputStream.use { it.write(body.toByteArray(Charsets.UTF_8)) }
160
236
  }
161
237
 
162
238
  val status = conn.responseCode
163
- if (status !in 200..299) return null
239
+ if (status !in 200..299) {
240
+ android.util.Log.d("NitroFetch", "[TokenRefresh] Refresh endpoint returned HTTP $status")
241
+ return null
242
+ }
243
+
244
+ NitroCookieSync.storeSetCookieFromHttpURLConnection(conn.url.toString(), conn, flush = true)
164
245
 
165
246
  val responseBody = conn.inputStream.use { it.bufferedReader(Charsets.UTF_8).readText() }
166
247
 
@@ -0,0 +1,138 @@
1
+ package com.margelo.nitro.nitrofetch
2
+
3
+ import android.util.Log
4
+ import android.webkit.CookieManager
5
+ import org.json.JSONObject
6
+ import org.chromium.net.UrlResponseInfo
7
+ import java.net.HttpURLConnection
8
+
9
+ /**
10
+ * Shared [CookieManager] bridging for Cronet and [HttpURLConnection] token refresh.
11
+ * - Attaches `Cookie` from the jar when the request has no `Cookie` header.
12
+ * - Persists `Set-Cookie` responses; [flush] is applied only when at least one cookie was stored.
13
+ *
14
+ * **Opt-in:** Cookie sync is disabled by default to avoid changing behaviour for consumers
15
+ * that do not rely on the WebView cookie jar. Call [enableCookieSync] before any requests.
16
+ */
17
+ object NitroCookieSync {
18
+ private const val LOG_TAG = "NitroCookieSync"
19
+
20
+ @Volatile
21
+ private var enabled = false
22
+
23
+ /**
24
+ * Enable cookie synchronisation between Cronet / HttpURLConnection and the system
25
+ * [CookieManager]. Call once (e.g. from `Application.onCreate`) before any fetch or
26
+ * autoprefetch work. Has no effect when called multiple times.
27
+ */
28
+ @JvmStatic
29
+ fun enableCookieSync() {
30
+ enabled = true
31
+ }
32
+
33
+ @JvmStatic
34
+ fun isCookieSyncEnabled(): Boolean = enabled
35
+
36
+ fun hasCookieHeaderInNitroRequest(headers: Array<NitroHeader>?): Boolean {
37
+ return headers?.any { it.key.equals("Cookie", ignoreCase = true) } == true
38
+ }
39
+
40
+ fun hasCookieHeaderInJson(reqHeaders: JSONObject?): Boolean {
41
+ if (reqHeaders == null) return false
42
+ return reqHeaders.keys().asSequence().any { it.equals("Cookie", ignoreCase = true) }
43
+ }
44
+
45
+ /**
46
+ * If [hasCookieHeader] is false, adds `Cookie` from [CookieManager] for [url] via [addHeader].
47
+ * No-op when cookie sync is [disabled][enableCookieSync].
48
+ */
49
+ fun attachCookieFromManagerIfMissing(
50
+ url: String,
51
+ hasCookieHeader: Boolean,
52
+ addHeader: (String, String) -> Unit
53
+ ) {
54
+ if (!enabled) return
55
+ if (hasCookieHeader) return
56
+ try {
57
+ val jar = CookieManager.getInstance()
58
+ val cookieHeader = jar.getCookie(url)
59
+ if (!cookieHeader.isNullOrEmpty()) {
60
+ addHeader("Cookie", cookieHeader)
61
+ }
62
+ } catch (exception: Exception) {
63
+ Log.w(LOG_TAG, "Failed to attach cookie header", exception)
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Applies `Set-Cookie` headers from a Cronet [UrlResponseInfo] into [CookieManager].
69
+ * @param flush If true, [CookieManager.flush] runs only when at least one cookie was applied.
70
+ * Use `flush = false` on redirects so persistence happens once on the final response.
71
+ * @return true if at least one `Set-Cookie` was stored.
72
+ */
73
+ fun storeSetCookieFromUrlResponseInfo(
74
+ responseUrl: String,
75
+ info: UrlResponseInfo,
76
+ flush: Boolean
77
+ ): Boolean {
78
+ if (!enabled) return false
79
+ return try {
80
+ val cookieManager = CookieManager.getInstance()
81
+ val setCookieHeaders = info.allHeadersAsList.filter {
82
+ it.key.equals("Set-Cookie", ignoreCase = true)
83
+ }
84
+ if (setCookieHeaders.isEmpty()) return false
85
+ for (header in setCookieHeaders) {
86
+ cookieManager.setCookie(responseUrl, header.value)
87
+ }
88
+ if (flush) {
89
+ cookieManager.flush()
90
+ }
91
+ true
92
+ } catch (exception: Exception) {
93
+ Log.w(LOG_TAG, "Failed to store response cookies", exception)
94
+ false
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Applies `Set-Cookie` from an [HttpURLConnection] response into [CookieManager].
100
+ * @param flush If true, [CookieManager.flush] runs only when at least one cookie was applied.
101
+ */
102
+ fun storeSetCookieFromHttpURLConnection(
103
+ urlStr: String,
104
+ conn: HttpURLConnection,
105
+ flush: Boolean
106
+ ): Boolean {
107
+ if (!enabled) return false
108
+ return try {
109
+ val cookieManager = CookieManager.getInstance()
110
+ var anySet = false
111
+ conn.headerFields?.forEach { (key, values) ->
112
+ if (key?.equals("Set-Cookie", ignoreCase = true) == true) {
113
+ values.forEach { cookieValue ->
114
+ cookieManager.setCookie(urlStr, cookieValue)
115
+ anySet = true
116
+ }
117
+ }
118
+ }
119
+ if (anySet && flush) {
120
+ cookieManager.flush()
121
+ }
122
+ anySet
123
+ } catch (exception: Exception) {
124
+ Log.w(LOG_TAG, "Failed to store response cookies (HttpURLConnection)", exception)
125
+ false
126
+ }
127
+ }
128
+
129
+ /** Persists in-memory cookie updates to disk (call after a successful request when any `Set-Cookie` was applied). */
130
+ fun flushCookieManager() {
131
+ if (!enabled) return
132
+ try {
133
+ CookieManager.getInstance().flush()
134
+ } catch (exception: Exception) {
135
+ Log.w(LOG_TAG, "Failed to flush CookieManager", exception)
136
+ }
137
+ }
138
+ }
@@ -90,11 +90,17 @@ class NitroFetchClient(private val engine: CronetEngine, private val executor: E
90
90
  private val buffer = ByteBuffer.allocateDirect(16 * 1024)
91
91
  private val out = java.io.ByteArrayOutputStream()
92
92
  private var redirectStopped = false
93
+ /** True if a redirect response applied at least one `Set-Cookie` (in memory, not yet flushed). */
94
+ private var setCookieAppliedOnRedirect = false
93
95
  private var devToolsBytes = 0
94
96
  private var devToolsTextual = false
95
97
 
96
98
  override fun onRedirectReceived(request: UrlRequest, info: UrlResponseInfo, newLocationUrl: String) {
97
99
  if (shouldFollowRedirects) {
100
+ // Apply Set-Cookie in-memory; flush once in onSucceeded (avoid flush per hop).
101
+ if (NitroCookieSync.storeSetCookieFromUrlResponseInfo(info.url, info, flush = false)) {
102
+ setCookieAppliedOnRedirect = true
103
+ }
98
104
  request.followRedirect()
99
105
  } else {
100
106
  // Return the redirect response as-is without following
@@ -162,6 +168,11 @@ class NitroFetchClient(private val engine: CronetEngine, private val executor: E
162
168
  DevToolsReporter.reportResponseEnd(devToolsRequestId, devToolsBytes.toLong())
163
169
  }
164
170
  try {
171
+ val storedOnFinal =
172
+ NitroCookieSync.storeSetCookieFromUrlResponseInfo(info.url, info, flush = false)
173
+ if (storedOnFinal || setCookieAppliedOnRedirect) {
174
+ NitroCookieSync.flushCookieManager()
175
+ }
165
176
  val headersArr: Array<NitroHeader> =
166
177
  info.allHeadersAsList.map { NitroHeader(it.key, it.value) }.toTypedArray()
167
178
  val status = info.httpStatusCode
@@ -221,6 +232,11 @@ class NitroFetchClient(private val engine: CronetEngine, private val executor: E
221
232
  builder.setHttpMethod(method)
222
233
  req.headers?.forEach { (k, v) -> builder.addHeader(k, v) }
223
234
 
235
+ NitroCookieSync.attachCookieFromManagerIfMissing(
236
+ url,
237
+ NitroCookieSync.hasCookieHeaderInNitroRequest(req.headers)
238
+ ) { key, value -> builder.addHeader(key, value) }
239
+
224
240
  val formParts = req.bodyFormData
225
241
  if (formParts != null && formParts.isNotEmpty()) {
226
242
  val (multipartBody, contentType) = buildMultipartBody(formParts)
@@ -0,0 +1,25 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ /**
6
+ * Native-side prefetch registration. Call from `application:didFinishLaunchingWithOptions:`
7
+ * to declare URLs that should be prefetched on the very first cold launch (and every
8
+ * cold launch thereafter). Writes to the same persistent queue used by the JS
9
+ * `prefetchOnAppStart` API; entries are deduped by `prefetchKey`.
10
+ *
11
+ * This header is the Obj-C facade for the Swift `NitroAutoPrefetcher` class so that
12
+ * Swift host apps can `#import <NitroFetch/NitroAutoPrefetcher.h>` from a bridging
13
+ * header without pulling in the pod's C++ surface.
14
+ */
15
+ @interface NitroAutoPrefetcher : NSObject
16
+
17
+ + (void)prefetchOnStart;
18
+
19
+ + (void)registerPrefetchWithUrl:(NSString *)url
20
+ prefetchKey:(NSString *)prefetchKey
21
+ headers:(NSDictionary<NSString *, NSString *> *)headers;
22
+
23
+ @end
24
+
25
+ NS_ASSUME_NONNULL_END
@@ -8,6 +8,70 @@ public final class NitroAutoPrefetcher: NSObject {
8
8
  private static let tokenRefreshKey = "nitro_token_refresh_fetch"
9
9
  private static let tokenCacheKey = "nitro_token_refresh_fetch_cache"
10
10
 
11
+ /// Register a URL to prefetch on app start. Call from
12
+ /// `application(_:didFinishLaunchingWithOptions:)`. Writes to the same
13
+ /// persistent queue used by the JS `prefetchOnAppStart` API; entries are
14
+ /// deduped by `prefetchKey`.
15
+ ///
16
+ /// If called after `prefetchOnStart` already ran (late registration), the
17
+ /// entry is also kicked immediately via `NitroFetchClient.prefetchStatic`.
18
+ @objc
19
+ public static func registerPrefetch(
20
+ url: String,
21
+ prefetchKey: String,
22
+ headers: [String: String]
23
+ ) {
24
+ if url.isEmpty || prefetchKey.isEmpty { return }
25
+ let userDefaults = UserDefaults(suiteName: suiteName) ?? UserDefaults.standard
26
+
27
+ var arr: [[String: Any]] = []
28
+ if let raw = userDefaults.string(forKey: queueKey),
29
+ !raw.isEmpty,
30
+ let data = raw.data(using: .utf8),
31
+ let parsed = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] {
32
+ arr = parsed
33
+ }
34
+ arr.removeAll { ($0["prefetchKey"] as? String) == prefetchKey }
35
+ arr.append([
36
+ "url": url,
37
+ "prefetchKey": prefetchKey,
38
+ "headers": headers,
39
+ ])
40
+ if let data = try? JSONSerialization.data(withJSONObject: arr),
41
+ let str = String(data: data, encoding: .utf8) {
42
+ userDefaults.set(str, forKey: queueKey)
43
+ }
44
+
45
+ if initialized {
46
+ // Late path — apply cached token headers + kick immediate prefetch
47
+ var tokenHeaders: [String: String] = [:]
48
+ if let cacheRaw = NitroFetchSecureAtRest.decryptedString(forKey: tokenCacheKey, defaults: userDefaults),
49
+ !cacheRaw.isEmpty,
50
+ let cacheData = cacheRaw.data(using: .utf8),
51
+ let cacheObj = try? JSONSerialization.jsonObject(with: cacheData) as? [String: String] {
52
+ tokenHeaders = cacheObj
53
+ }
54
+ var merged: [String: String] = headers
55
+ for (k, v) in tokenHeaders { merged[k] = v }
56
+ var hdrs: [NitroHeader] = merged.map { NitroHeader(key: $0.key, value: $0.value) }
57
+ hdrs.append(NitroHeader(key: "prefetchKey", value: prefetchKey))
58
+ let req = NitroRequest(
59
+ url: url,
60
+ method: nil,
61
+ headers: hdrs,
62
+ bodyString: nil,
63
+ bodyBytes: nil,
64
+ bodyFormData: nil,
65
+ timeoutMs: nil,
66
+ followRedirects: true,
67
+ requestId: nil
68
+ )
69
+ Task {
70
+ do { try await NitroFetchClient.prefetchStatic(req) } catch { /* best-effort */ }
71
+ }
72
+ }
73
+ }
74
+
11
75
  @objc
12
76
  public static func prefetchOnStart() {
13
77
  if initialized { return }
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+
3
+ // Web entry for react-native-nitro-fetch.
4
+ // Native (Cronet/URLSession) is unavailable on the web, so we delegate to the
5
+ // browser's built-in fetch and stub native-only APIs with console.warn.
6
+
7
+ import { NitroRequest as NitroRequestClass } from "./Request.js";
8
+ export { NitroHeaders as Headers } from "./Headers.js";
9
+ export { NitroResponse as Response } from "./Response.js";
10
+ export { NitroRequest as Request } from "./Request.js";
11
+ export { NetworkInspector } from "./NetworkInspector.js";
12
+ export { generateCurl } from "./CurlGenerator.js";
13
+ export { profileFetch } from "./HermesProfiler.js";
14
+ export async function fetch(input, init) {
15
+ let resolvedInput = input;
16
+ let resolvedInit = init;
17
+ if (input instanceof NitroRequestClass) {
18
+ const method = (init?.method ?? input.method).toUpperCase();
19
+ const hasBodyMethod = method !== 'GET' && method !== 'HEAD';
20
+ let body = init?.body;
21
+ if (body === undefined && hasBodyMethod) {
22
+ const bytes = await input.arrayBuffer().catch(() => undefined);
23
+ body = bytes && bytes.byteLength > 0 ? bytes : null;
24
+ }
25
+ resolvedInput = input.url;
26
+ resolvedInit = {
27
+ ...init,
28
+ method,
29
+ headers: init?.headers ?? input.headers,
30
+ body,
31
+ redirect: init?.redirect ?? input.redirect,
32
+ cache: init?.cache ?? input.cache,
33
+ signal: init?.signal ?? input.signal
34
+ };
35
+ }
36
+ return globalThis.fetch(resolvedInput, resolvedInit);
37
+ }
38
+ export async function nitroFetchOnWorklet(input, init, mapWorklet, _options) {
39
+ console.warn('nitroFetchOnWorklet: worklets are not available on web; running on the JS thread');
40
+ const res = await globalThis.fetch(input, init);
41
+ const bodyBytes = await res.clone().arrayBuffer();
42
+ let bodyString;
43
+ try {
44
+ bodyString = await res.clone().text();
45
+ } catch {
46
+ bodyString = undefined;
47
+ }
48
+ const headers = [];
49
+ res.headers.forEach((v, k) => headers.push({
50
+ key: k,
51
+ value: v
52
+ }));
53
+ return mapWorklet({
54
+ url: res.url,
55
+ status: res.status,
56
+ statusText: res.statusText,
57
+ ok: res.ok,
58
+ redirected: res.redirected ?? false,
59
+ headers,
60
+ bodyBytes,
61
+ bodyString
62
+ });
63
+ }
64
+ export async function prefetch(_input, _init) {
65
+ console.warn('prefetch is not available on web');
66
+ }
67
+ export async function prefetchOnAppStart(_input, _init) {
68
+ console.warn('prefetchOnAppStart is not available on web');
69
+ }
70
+ export async function removeFromAutoPrefetch(_prefetchKey) {
71
+ console.warn('removeFromAutoPrefetch is not available on web');
72
+ }
73
+ export async function removeAllFromAutoprefetch() {
74
+ console.warn('removeAllFromAutoprefetch is not available on web');
75
+ }
76
+ export const NitroFetch = new Proxy({}, {
77
+ get(_target, prop) {
78
+ console.warn(`NitroFetch.${String(prop)} is not available on web`);
79
+ return undefined;
80
+ }
81
+ });
82
+ export function registerTokenRefresh(_options) {
83
+ console.warn('registerTokenRefresh is not available on web');
84
+ }
85
+ export function clearTokenRefresh(_target) {
86
+ console.warn('clearTokenRefresh is not available on web');
87
+ }
88
+ export async function callRefreshEndpoint(_config) {
89
+ console.warn('callRefreshEndpoint is not available on web');
90
+ return {};
91
+ }
92
+ export function getStoredTokenRefreshConfig(_target) {
93
+ console.warn('getStoredTokenRefreshConfig is not available on web');
94
+ return null;
95
+ }
96
+ export function getNestedField(obj, dotPath) {
97
+ const parts = dotPath.split('.');
98
+ let current = obj;
99
+ for (const part of parts) {
100
+ if (current == null || typeof current !== 'object') return undefined;
101
+ current = current[part];
102
+ }
103
+ return current != null ? String(current) : undefined;
104
+ }
105
+ export function applyTemplate(template, value) {
106
+ return template.replace(/\{\{value\}\}/g, value);
107
+ }
108
+ //# sourceMappingURL=index.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NitroRequest","NitroRequestClass","NitroHeaders","Headers","NitroResponse","Response","Request","NetworkInspector","generateCurl","profileFetch","fetch","input","init","resolvedInput","resolvedInit","method","toUpperCase","hasBodyMethod","body","undefined","bytes","arrayBuffer","catch","byteLength","url","headers","redirect","cache","signal","globalThis","nitroFetchOnWorklet","mapWorklet","_options","console","warn","res","bodyBytes","clone","bodyString","text","forEach","v","k","push","key","value","status","statusText","ok","redirected","prefetch","_input","_init","prefetchOnAppStart","removeFromAutoPrefetch","_prefetchKey","removeAllFromAutoprefetch","NitroFetch","Proxy","get","_target","prop","String","registerTokenRefresh","clearTokenRefresh","callRefreshEndpoint","_config","getStoredTokenRefreshConfig","getNestedField","obj","dotPath","parts","split","current","part","applyTemplate","template","replace"],"sourceRoot":"../../src","sources":["index.web.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,68 @@
1
+ import type { RequestRedirect, RequestCache } from './Request';
2
+ export { NitroHeaders as Headers } from './Headers';
3
+ export { NitroResponse as Response } from './Response';
4
+ export { NitroRequest as Request } from './Request';
5
+ export type { RequestRedirect, RequestCache } from './Request';
6
+ export { NetworkInspector } from './NetworkInspector';
7
+ export type { NetworkEntry, NetworkEntryCallback, WebSocketEntry, WebSocketMessage, InspectorEntry, } from './NetworkInspector';
8
+ export { generateCurl } from './CurlGenerator';
9
+ export type { CurlOptions } from './CurlGenerator';
10
+ export { profileFetch } from './HermesProfiler';
11
+ export type { ProfileResult } from './HermesProfiler';
12
+ export type { NitroFormDataPart } from './NitroFetch.nitro';
13
+ export type { NitroRequest as NitroRequest, NitroResponse as NitroResponse, } from './NitroFetch.nitro';
14
+ export type TokenRefreshConfig = {
15
+ url: string;
16
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH';
17
+ headers?: Record<string, string>;
18
+ body?: string;
19
+ responseType?: 'json' | 'text';
20
+ mappings?: {
21
+ jsonPath: string;
22
+ header: string;
23
+ valueTemplate?: string;
24
+ }[];
25
+ compositeHeaders?: {
26
+ header: string;
27
+ template: string;
28
+ paths: Record<string, string>;
29
+ }[];
30
+ };
31
+ export declare function fetch(input: RequestInfo | URL, init?: RequestInit & {
32
+ stream?: boolean;
33
+ redirect?: RequestRedirect;
34
+ cache?: RequestCache;
35
+ }): Promise<Response>;
36
+ export type NitroWorkletMapper<T> = (payload: {
37
+ url: string;
38
+ status: number;
39
+ statusText: string;
40
+ ok: boolean;
41
+ redirected: boolean;
42
+ headers: {
43
+ key: string;
44
+ value: string;
45
+ }[];
46
+ bodyBytes?: ArrayBuffer;
47
+ bodyString?: string;
48
+ }) => T;
49
+ export declare function nitroFetchOnWorklet<T>(input: RequestInfo | URL, init: RequestInit | undefined, mapWorklet: NitroWorkletMapper<T>, _options?: {
50
+ preferBytes?: boolean;
51
+ runtimeName?: string;
52
+ }): Promise<T>;
53
+ export declare function prefetch(_input: RequestInfo | URL, _init?: RequestInit): Promise<void>;
54
+ export declare function prefetchOnAppStart(_input: RequestInfo | URL, _init?: RequestInit & {
55
+ prefetchKey?: string;
56
+ }): Promise<void>;
57
+ export declare function removeFromAutoPrefetch(_prefetchKey: string): Promise<void>;
58
+ export declare function removeAllFromAutoprefetch(): Promise<void>;
59
+ export declare const NitroFetch: unknown;
60
+ export declare function registerTokenRefresh(_options: {
61
+ target: 'websocket' | 'fetch' | 'all';
62
+ } & TokenRefreshConfig): void;
63
+ export declare function clearTokenRefresh(_target?: 'websocket' | 'fetch' | 'all'): void;
64
+ export declare function callRefreshEndpoint(_config: TokenRefreshConfig): Promise<Record<string, string>>;
65
+ export declare function getStoredTokenRefreshConfig(_target: 'websocket' | 'fetch'): TokenRefreshConfig | null;
66
+ export declare function getNestedField(obj: unknown, dotPath: string): string | undefined;
67
+ export declare function applyTemplate(template: string, value: string): string;
68
+ //# sourceMappingURL=index.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../../../src/index.web.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-fetch",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Awesome Fetch :)",
5
5
  "main": "./lib/module/index.js",
6
6
  "module": "./lib/module/index.js",
@@ -11,6 +11,7 @@
11
11
  ".": {
12
12
  "source": "./src/index.tsx",
13
13
  "types": "./lib/typescript/src/index.d.ts",
14
+ "browser": "./lib/module/index.web.js",
14
15
  "default": "./lib/module/index.js"
15
16
  },
16
17
  "./package.json": "./package.json",
@@ -0,0 +1,200 @@
1
+ // Web entry for react-native-nitro-fetch.
2
+ // Native (Cronet/URLSession) is unavailable on the web, so we delegate to the
3
+ // browser's built-in fetch and stub native-only APIs with console.warn.
4
+
5
+ import { NitroRequest as NitroRequestClass } from './Request';
6
+ import type { RequestRedirect, RequestCache } from './Request';
7
+
8
+ export { NitroHeaders as Headers } from './Headers';
9
+ export { NitroResponse as Response } from './Response';
10
+ export { NitroRequest as Request } from './Request';
11
+ export type { RequestRedirect, RequestCache } from './Request';
12
+
13
+ export { NetworkInspector } from './NetworkInspector';
14
+ export type {
15
+ NetworkEntry,
16
+ NetworkEntryCallback,
17
+ WebSocketEntry,
18
+ WebSocketMessage,
19
+ InspectorEntry,
20
+ } from './NetworkInspector';
21
+ export { generateCurl } from './CurlGenerator';
22
+ export type { CurlOptions } from './CurlGenerator';
23
+ export { profileFetch } from './HermesProfiler';
24
+ export type { ProfileResult } from './HermesProfiler';
25
+
26
+ export type { NitroFormDataPart } from './NitroFetch.nitro';
27
+ export type {
28
+ NitroRequest as NitroRequest,
29
+ NitroResponse as NitroResponse,
30
+ } from './NitroFetch.nitro';
31
+
32
+ export type TokenRefreshConfig = {
33
+ url: string;
34
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH';
35
+ headers?: Record<string, string>;
36
+ body?: string;
37
+ responseType?: 'json' | 'text';
38
+ mappings?: {
39
+ jsonPath: string;
40
+ header: string;
41
+ valueTemplate?: string;
42
+ }[];
43
+ compositeHeaders?: {
44
+ header: string;
45
+ template: string;
46
+ paths: Record<string, string>;
47
+ }[];
48
+ };
49
+
50
+ export async function fetch(
51
+ input: RequestInfo | URL,
52
+ init?: RequestInit & {
53
+ stream?: boolean;
54
+ redirect?: RequestRedirect;
55
+ cache?: RequestCache;
56
+ }
57
+ ): Promise<Response> {
58
+ let resolvedInput: RequestInfo | URL = input;
59
+ let resolvedInit = init;
60
+ if (input instanceof NitroRequestClass) {
61
+ const method = (init?.method ?? input.method).toUpperCase();
62
+ const hasBodyMethod = method !== 'GET' && method !== 'HEAD';
63
+ let body: BodyInit | null | undefined = init?.body;
64
+ if (body === undefined && hasBodyMethod) {
65
+ const bytes = await input.arrayBuffer().catch(() => undefined);
66
+ body = bytes && bytes.byteLength > 0 ? bytes : null;
67
+ }
68
+ resolvedInput = input.url;
69
+ resolvedInit = {
70
+ ...init,
71
+ method,
72
+ headers: (init?.headers ??
73
+ (input.headers as unknown as HeadersInit)) as HeadersInit,
74
+ body,
75
+ redirect: init?.redirect ?? input.redirect,
76
+ cache: init?.cache ?? input.cache,
77
+ signal: init?.signal ?? input.signal,
78
+ };
79
+ }
80
+ return globalThis.fetch(resolvedInput as RequestInfo, resolvedInit);
81
+ }
82
+
83
+ export type NitroWorkletMapper<T> = (payload: {
84
+ url: string;
85
+ status: number;
86
+ statusText: string;
87
+ ok: boolean;
88
+ redirected: boolean;
89
+ headers: { key: string; value: string }[];
90
+ bodyBytes?: ArrayBuffer;
91
+ bodyString?: string;
92
+ }) => T;
93
+
94
+ export async function nitroFetchOnWorklet<T>(
95
+ input: RequestInfo | URL,
96
+ init: RequestInit | undefined,
97
+ mapWorklet: NitroWorkletMapper<T>,
98
+ _options?: { preferBytes?: boolean; runtimeName?: string }
99
+ ): Promise<T> {
100
+ console.warn(
101
+ 'nitroFetchOnWorklet: worklets are not available on web; running on the JS thread'
102
+ );
103
+ const res = await globalThis.fetch(input as RequestInfo, init);
104
+ const bodyBytes = await res.clone().arrayBuffer();
105
+ let bodyString: string | undefined;
106
+ try {
107
+ bodyString = await res.clone().text();
108
+ } catch {
109
+ bodyString = undefined;
110
+ }
111
+ const headers: { key: string; value: string }[] = [];
112
+ res.headers.forEach((v, k) => headers.push({ key: k, value: v }));
113
+ return mapWorklet({
114
+ url: res.url,
115
+ status: res.status,
116
+ statusText: res.statusText,
117
+ ok: res.ok,
118
+ redirected: (res as { redirected?: boolean }).redirected ?? false,
119
+ headers,
120
+ bodyBytes,
121
+ bodyString,
122
+ });
123
+ }
124
+
125
+ export async function prefetch(
126
+ _input: RequestInfo | URL,
127
+ _init?: RequestInit
128
+ ): Promise<void> {
129
+ console.warn('prefetch is not available on web');
130
+ }
131
+
132
+ export async function prefetchOnAppStart(
133
+ _input: RequestInfo | URL,
134
+ _init?: RequestInit & { prefetchKey?: string }
135
+ ): Promise<void> {
136
+ console.warn('prefetchOnAppStart is not available on web');
137
+ }
138
+
139
+ export async function removeFromAutoPrefetch(
140
+ _prefetchKey: string
141
+ ): Promise<void> {
142
+ console.warn('removeFromAutoPrefetch is not available on web');
143
+ }
144
+
145
+ export async function removeAllFromAutoprefetch(): Promise<void> {
146
+ console.warn('removeAllFromAutoprefetch is not available on web');
147
+ }
148
+
149
+ export const NitroFetch: unknown = new Proxy(
150
+ {},
151
+ {
152
+ get(_target, prop) {
153
+ console.warn(`NitroFetch.${String(prop)} is not available on web`);
154
+ return undefined;
155
+ },
156
+ }
157
+ );
158
+
159
+ export function registerTokenRefresh(
160
+ _options: { target: 'websocket' | 'fetch' | 'all' } & TokenRefreshConfig
161
+ ): void {
162
+ console.warn('registerTokenRefresh is not available on web');
163
+ }
164
+
165
+ export function clearTokenRefresh(
166
+ _target?: 'websocket' | 'fetch' | 'all'
167
+ ): void {
168
+ console.warn('clearTokenRefresh is not available on web');
169
+ }
170
+
171
+ export async function callRefreshEndpoint(
172
+ _config: TokenRefreshConfig
173
+ ): Promise<Record<string, string>> {
174
+ console.warn('callRefreshEndpoint is not available on web');
175
+ return {};
176
+ }
177
+
178
+ export function getStoredTokenRefreshConfig(
179
+ _target: 'websocket' | 'fetch'
180
+ ): TokenRefreshConfig | null {
181
+ console.warn('getStoredTokenRefreshConfig is not available on web');
182
+ return null;
183
+ }
184
+
185
+ export function getNestedField(
186
+ obj: unknown,
187
+ dotPath: string
188
+ ): string | undefined {
189
+ const parts = dotPath.split('.');
190
+ let current: unknown = obj;
191
+ for (const part of parts) {
192
+ if (current == null || typeof current !== 'object') return undefined;
193
+ current = (current as Record<string, unknown>)[part];
194
+ }
195
+ return current != null ? String(current) : undefined;
196
+ }
197
+
198
+ export function applyTemplate(template: string, value: string): string {
199
+ return template.replace(/\{\{value\}\}/g, value);
200
+ }