react-native-nitro-fetch 1.3.2 → 1.4.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.
- package/NitroFetch.podspec +1 -3
- package/README.md +39 -11
- package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt +6 -2
- package/android/src/main/java/com/margelo/nitro/nitrofetch/DevToolsReporterImpl.kt +27 -36
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +45 -0
- package/ios/NitroAutoPrefetcher.swift +4 -0
- package/ios/NitroDevToolsReporter.mm +37 -31
- package/ios/NitroFetchClient.swift +56 -0
- package/lib/module/CurlGenerator.js.map +1 -2
- package/lib/module/Headers.js.map +2 -1
- package/lib/module/HermesProfiler.js.map +2 -1
- package/lib/module/NetworkInspector.js +1 -5
- package/lib/module/NetworkInspector.js.map +2 -1
- package/lib/module/NitroCronet.nitro.js.map +2 -1
- package/lib/module/NitroFetch.nitro.js.map +2 -1
- package/lib/module/NitroInstances.js.map +2 -1
- package/lib/module/Request.js.map +1 -2
- package/lib/module/Response.js.map +2 -2
- package/lib/module/fetch.js +147 -1
- package/lib/module/fetch.js.map +1 -1
- package/lib/module/index.web.js +1 -0
- package/lib/module/index.web.js.map +1 -2
- package/lib/module/tokenRefresh.js.map +1 -2
- package/lib/module/utf8.js.map +1 -2
- package/lib/typescript/src/fetch.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/CurlGenerator.js +26 -23
- package/src/Headers.js +116 -108
- package/src/HermesProfiler.js +18 -16
- package/src/NetworkInspector.js +179 -171
- package/src/NitroInstances.js +1 -2
- package/src/Request.js +164 -167
- package/src/Response.js +242 -244
- package/src/fetch.js +842 -706
- package/src/fetch.ts +170 -1
- package/src/index.js +2 -17
- package/src/index.web.js +67 -69
- package/src/tokenRefresh.js +77 -75
- package/src/utf8.js +27 -28
package/src/fetch.ts
CHANGED
|
@@ -438,6 +438,169 @@ async function resolveBlobBody(
|
|
|
438
438
|
return init;
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
+
// http(s) -> native client; anything else is a local resource (hot path).
|
|
442
|
+
function isHttpUrl(url: string): boolean {
|
|
443
|
+
if (url.startsWith('http://') || url.startsWith('https://')) return true;
|
|
444
|
+
const c = url.charCodeAt(0);
|
|
445
|
+
if (c !== 104 && c !== 72) return false; // not 'h'/'H'
|
|
446
|
+
return /^https?:/i.test(url);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function getUrlString(input: RequestInfo | URL): string {
|
|
450
|
+
if (typeof input === 'string') return input;
|
|
451
|
+
if (input instanceof URL) return input.toString();
|
|
452
|
+
const u = (input as { url?: unknown } | null)?.url;
|
|
453
|
+
return typeof u === 'string' ? u : String(input);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function base64ToBytes(b64: string): Uint8Array {
|
|
457
|
+
const decode = (globalThis as { atob?: (s: string) => string }).atob;
|
|
458
|
+
if (typeof decode === 'function') {
|
|
459
|
+
const bin = decode(b64);
|
|
460
|
+
const out = new Uint8Array(bin.length);
|
|
461
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
462
|
+
return out;
|
|
463
|
+
}
|
|
464
|
+
// base64 fallback for runtimes without a global atob.
|
|
465
|
+
/* eslint-disable no-bitwise */
|
|
466
|
+
const chars =
|
|
467
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
468
|
+
const clean = b64.replace(/[^A-Za-z0-9+/]/g, '');
|
|
469
|
+
const out = new Uint8Array(Math.floor((clean.length * 3) / 4));
|
|
470
|
+
let p = 0;
|
|
471
|
+
let buf = 0;
|
|
472
|
+
let bits = 0;
|
|
473
|
+
for (let i = 0; i < clean.length; i++) {
|
|
474
|
+
buf = (buf << 6) | chars.indexOf(clean[i]!);
|
|
475
|
+
bits += 6;
|
|
476
|
+
if (bits >= 8) {
|
|
477
|
+
bits -= 8;
|
|
478
|
+
out[p++] = (buf >> bits) & 0xff;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return out;
|
|
482
|
+
/* eslint-enable no-bitwise */
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
type MinimalTextDecoder = {
|
|
486
|
+
decode(input: ArrayBufferView): string;
|
|
487
|
+
};
|
|
488
|
+
type TextDecoderCtor = new (
|
|
489
|
+
label?: string,
|
|
490
|
+
options?: { fatal?: boolean }
|
|
491
|
+
) => MinimalTextDecoder;
|
|
492
|
+
|
|
493
|
+
// Cached: our nitro-text-decoder if the app bundles it (aliased require keeps it optional, not a dep), else a global TextDecoder.
|
|
494
|
+
let _decoder: MinimalTextDecoder | null | undefined;
|
|
495
|
+
function resolveTextDecoder(): MinimalTextDecoder | null {
|
|
496
|
+
if (_decoder !== undefined) return _decoder;
|
|
497
|
+
try {
|
|
498
|
+
const dynamicRequire = require;
|
|
499
|
+
const mod = dynamicRequire('react-native-nitro-text-decoder') as {
|
|
500
|
+
TextDecoder?: TextDecoderCtor;
|
|
501
|
+
};
|
|
502
|
+
if (mod && typeof mod.TextDecoder === 'function') {
|
|
503
|
+
_decoder = new mod.TextDecoder('utf-8', { fatal: true });
|
|
504
|
+
return _decoder;
|
|
505
|
+
}
|
|
506
|
+
} catch {
|
|
507
|
+
// optional, not bundled
|
|
508
|
+
}
|
|
509
|
+
const GlobalTextDecoder = (globalThis as { TextDecoder?: TextDecoderCtor })
|
|
510
|
+
.TextDecoder;
|
|
511
|
+
if (typeof GlobalTextDecoder === 'function') {
|
|
512
|
+
_decoder = new GlobalTextDecoder('utf-8', { fatal: true });
|
|
513
|
+
return _decoder;
|
|
514
|
+
}
|
|
515
|
+
_decoder = null;
|
|
516
|
+
return _decoder;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// data: text via a TextDecoder; null (bytes-only) + a one-time warn if none.
|
|
520
|
+
let _warnedNoTextDecoder = false;
|
|
521
|
+
function decodeUtf8(bytes: Uint8Array): string | null {
|
|
522
|
+
const decoder = resolveTextDecoder();
|
|
523
|
+
if (decoder) {
|
|
524
|
+
try {
|
|
525
|
+
return decoder.decode(bytes);
|
|
526
|
+
} catch {
|
|
527
|
+
return null; // invalid UTF-8 -> keep bytes only
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (!_warnedNoTextDecoder) {
|
|
531
|
+
_warnedNoTextDecoder = true;
|
|
532
|
+
console.warn(
|
|
533
|
+
'[nitro-fetch] Reading a data: URL as text needs a TextDecoder. Install ' +
|
|
534
|
+
'react-native-nitro-text-decoder or expose a global TextDecoder. The ' +
|
|
535
|
+
'body is still available via response.arrayBuffer()/bytes().'
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Decode a data: URL into a synthetic 200 response, entirely in JS.
|
|
542
|
+
function decodeDataUrl(url: string): NitroResponseNative {
|
|
543
|
+
const comma = url.indexOf(',');
|
|
544
|
+
if (comma < 0) throw new TypeError('Failed to fetch: invalid data: URL');
|
|
545
|
+
const meta = url.slice(5, comma); // strip leading "data:"
|
|
546
|
+
const rawData = url.slice(comma + 1);
|
|
547
|
+
const isBase64 = /;base64\s*$/i.test(meta);
|
|
548
|
+
const mediaType =
|
|
549
|
+
(isBase64 ? meta.replace(/;base64\s*$/i, '') : meta).trim() ||
|
|
550
|
+
'text/plain;charset=US-ASCII';
|
|
551
|
+
|
|
552
|
+
let bodyString: string | undefined;
|
|
553
|
+
let bodyBytes: ArrayBuffer | undefined;
|
|
554
|
+
let length: number;
|
|
555
|
+
if (isBase64) {
|
|
556
|
+
const bytes = base64ToBytes(rawData);
|
|
557
|
+
length = bytes.byteLength;
|
|
558
|
+
// bytes for arrayBuffer/bytes; string for text/json when a decoder exists.
|
|
559
|
+
bodyBytes = bytes.buffer as ArrayBuffer;
|
|
560
|
+
const decoded = decodeUtf8(bytes);
|
|
561
|
+
if (decoded != null) bodyString = decoded;
|
|
562
|
+
} else {
|
|
563
|
+
bodyString = decodeURIComponent(rawData);
|
|
564
|
+
length =
|
|
565
|
+
typeof TextEncoder !== 'undefined'
|
|
566
|
+
? new TextEncoder().encode(bodyString).length
|
|
567
|
+
: bodyString.length;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
url,
|
|
572
|
+
status: 200,
|
|
573
|
+
statusText: 'OK',
|
|
574
|
+
ok: true,
|
|
575
|
+
redirected: false,
|
|
576
|
+
headers: [
|
|
577
|
+
{ key: 'Content-Type', value: mediaType },
|
|
578
|
+
{ key: 'Content-Length', value: String(length) },
|
|
579
|
+
],
|
|
580
|
+
bodyString,
|
|
581
|
+
bodyBytes,
|
|
582
|
+
} as NitroResponseNative;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Non-http(s): decode data: in JS, reject blob:, read file/content/path natively.
|
|
586
|
+
async function fetchLocalResource(
|
|
587
|
+
req: NitroRequestNative
|
|
588
|
+
): Promise<NitroResponseNative> {
|
|
589
|
+
const url = req.url;
|
|
590
|
+
if (url.startsWith('data:')) return decodeDataUrl(url);
|
|
591
|
+
if (url.startsWith('blob:')) {
|
|
592
|
+
throw new TypeError(
|
|
593
|
+
'nitro-fetch cannot read blob: URLs (the React Native blob registry is not ' +
|
|
594
|
+
'reachable from native). Read blobs with the platform fetch/FileReader instead.'
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
ensureClient();
|
|
598
|
+
if (!client || typeof client.request !== 'function') {
|
|
599
|
+
throw new Error('NitroFetch client not available');
|
|
600
|
+
}
|
|
601
|
+
return client.request(req);
|
|
602
|
+
}
|
|
603
|
+
|
|
441
604
|
async function nitroFetchRaw(
|
|
442
605
|
input: RequestInfo | URL,
|
|
443
606
|
init?: RequestInit
|
|
@@ -478,6 +641,11 @@ async function nitroFetchRaw(
|
|
|
478
641
|
|
|
479
642
|
const req = buildNitroRequest(input, init);
|
|
480
643
|
|
|
644
|
+
// Route non-http(s) (data:/file://content://scheme-less) off the HTTP client.
|
|
645
|
+
if (!isHttpUrl(req.url)) {
|
|
646
|
+
return fetchLocalResource(req);
|
|
647
|
+
}
|
|
648
|
+
|
|
481
649
|
// Inspector: record start (zero cost when disabled — single boolean check)
|
|
482
650
|
let inspectorId: string | undefined;
|
|
483
651
|
if (NetworkInspector.isEnabled()) {
|
|
@@ -696,7 +864,8 @@ export async function nitroFetch(
|
|
|
696
864
|
} as any;
|
|
697
865
|
}
|
|
698
866
|
|
|
699
|
-
|
|
867
|
+
// Streaming is http(s)-only; local URLs fall through to nitroFetchRaw (check runs only when streaming).
|
|
868
|
+
if ((init as any)?.stream === true && isHttpUrl(getUrlString(input))) {
|
|
700
869
|
return nitroStreamFetch(input, init);
|
|
701
870
|
}
|
|
702
871
|
|
package/src/index.js
CHANGED
|
@@ -1,24 +1,9 @@
|
|
|
1
|
-
export {
|
|
2
|
-
nitroFetch as fetch,
|
|
3
|
-
nitroFetchOnWorklet,
|
|
4
|
-
prefetch,
|
|
5
|
-
prefetchOnAppStart,
|
|
6
|
-
removeFromAutoPrefetch,
|
|
7
|
-
removeAllFromAutoprefetch,
|
|
8
|
-
__readAutoPrefetchQueue,
|
|
9
|
-
} from './fetch';
|
|
1
|
+
export { nitroFetch as fetch, nitroFetchOnWorklet, prefetch, prefetchOnAppStart, removeFromAutoPrefetch, removeAllFromAutoprefetch, __readAutoPrefetchQueue, } from './fetch';
|
|
10
2
|
export { NitroHeaders as Headers } from './Headers';
|
|
11
3
|
export { NitroResponse as Response } from './Response';
|
|
12
4
|
export { NitroRequest as Request } from './Request';
|
|
13
5
|
export { NitroFetch } from './NitroInstances';
|
|
14
|
-
export {
|
|
15
|
-
registerTokenRefresh,
|
|
16
|
-
clearTokenRefresh,
|
|
17
|
-
callRefreshEndpoint,
|
|
18
|
-
getStoredTokenRefreshConfig,
|
|
19
|
-
getNestedField,
|
|
20
|
-
applyTemplate,
|
|
21
|
-
} from './tokenRefresh';
|
|
6
|
+
export { registerTokenRefresh, clearTokenRefresh, callRefreshEndpoint, getStoredTokenRefreshConfig, getNestedField, applyTemplate, } from './tokenRefresh';
|
|
22
7
|
export { NetworkInspector } from './NetworkInspector';
|
|
23
8
|
export { generateCurl } from './CurlGenerator';
|
|
24
9
|
export { profileFetch } from './HermesProfiler';
|
package/src/index.web.js
CHANGED
|
@@ -9,98 +9,96 @@ export { NetworkInspector } from './NetworkInspector';
|
|
|
9
9
|
export { generateCurl } from './CurlGenerator';
|
|
10
10
|
export { profileFetch } from './HermesProfiler';
|
|
11
11
|
export async function fetch(input, init) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
let resolvedInput = input;
|
|
13
|
+
let resolvedInit = init;
|
|
14
|
+
if (input instanceof NitroRequestClass) {
|
|
15
|
+
const method = (init?.method ?? input.method).toUpperCase();
|
|
16
|
+
const hasBodyMethod = method !== 'GET' && method !== 'HEAD';
|
|
17
|
+
let body = init?.body;
|
|
18
|
+
if (body === undefined && hasBodyMethod) {
|
|
19
|
+
const bytes = await input.arrayBuffer().catch(() => undefined);
|
|
20
|
+
body = bytes && bytes.byteLength > 0 ? bytes : null;
|
|
21
|
+
}
|
|
22
|
+
resolvedInput = input.url;
|
|
23
|
+
resolvedInit = {
|
|
24
|
+
...init,
|
|
25
|
+
method,
|
|
26
|
+
headers: (init?.headers ??
|
|
27
|
+
input.headers),
|
|
28
|
+
body,
|
|
29
|
+
redirect: init?.redirect ?? input.redirect,
|
|
30
|
+
cache: init?.cache ?? input.cache,
|
|
31
|
+
signal: init?.signal ?? input.signal,
|
|
32
|
+
};
|
|
21
33
|
}
|
|
22
|
-
resolvedInput
|
|
23
|
-
resolvedInit = {
|
|
24
|
-
...init,
|
|
25
|
-
method,
|
|
26
|
-
headers: init?.headers ?? input.headers,
|
|
27
|
-
body,
|
|
28
|
-
redirect: init?.redirect ?? input.redirect,
|
|
29
|
-
cache: init?.cache ?? input.cache,
|
|
30
|
-
signal: init?.signal ?? input.signal,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
return globalThis.fetch(resolvedInput, resolvedInit);
|
|
34
|
+
return globalThis.fetch(resolvedInput, resolvedInit);
|
|
34
35
|
}
|
|
35
36
|
export async function nitroFetchOnWorklet(input, init, mapWorklet, _options) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
});
|
|
37
|
+
console.warn('nitroFetchOnWorklet: worklets are not available on web; running on the JS thread');
|
|
38
|
+
const res = await globalThis.fetch(input, init);
|
|
39
|
+
const bodyBytes = await res.clone().arrayBuffer();
|
|
40
|
+
let bodyString;
|
|
41
|
+
try {
|
|
42
|
+
bodyString = await res.clone().text();
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
bodyString = undefined;
|
|
46
|
+
}
|
|
47
|
+
const headers = [];
|
|
48
|
+
res.headers.forEach((v, k) => headers.push({ key: k, value: v }));
|
|
49
|
+
return mapWorklet({
|
|
50
|
+
url: res.url,
|
|
51
|
+
status: res.status,
|
|
52
|
+
statusText: res.statusText,
|
|
53
|
+
ok: res.ok,
|
|
54
|
+
redirected: res.redirected ?? false,
|
|
55
|
+
headers,
|
|
56
|
+
bodyBytes,
|
|
57
|
+
bodyString,
|
|
58
|
+
});
|
|
59
59
|
}
|
|
60
60
|
export async function prefetch(_input, _init) {
|
|
61
|
-
|
|
61
|
+
console.warn('prefetch is not available on web');
|
|
62
62
|
}
|
|
63
63
|
export async function prefetchOnAppStart(_input, _init) {
|
|
64
|
-
|
|
64
|
+
console.warn('prefetchOnAppStart is not available on web');
|
|
65
65
|
}
|
|
66
66
|
export async function removeFromAutoPrefetch(_prefetchKey) {
|
|
67
|
-
|
|
67
|
+
console.warn('removeFromAutoPrefetch is not available on web');
|
|
68
68
|
}
|
|
69
69
|
export async function removeAllFromAutoprefetch() {
|
|
70
|
-
|
|
70
|
+
console.warn('removeAllFromAutoprefetch is not available on web');
|
|
71
71
|
}
|
|
72
|
-
export const NitroFetch = new Proxy(
|
|
73
|
-
{},
|
|
74
|
-
{
|
|
72
|
+
export const NitroFetch = new Proxy({}, {
|
|
75
73
|
get(_target, prop) {
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
console.warn(`NitroFetch.${String(prop)} is not available on web`);
|
|
75
|
+
return undefined;
|
|
78
76
|
},
|
|
79
|
-
|
|
80
|
-
);
|
|
77
|
+
});
|
|
81
78
|
export function registerTokenRefresh(_options) {
|
|
82
|
-
|
|
79
|
+
console.warn('registerTokenRefresh is not available on web');
|
|
83
80
|
}
|
|
84
81
|
export function clearTokenRefresh(_target) {
|
|
85
|
-
|
|
82
|
+
console.warn('clearTokenRefresh is not available on web');
|
|
86
83
|
}
|
|
87
84
|
export async function callRefreshEndpoint(_config) {
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
console.warn('callRefreshEndpoint is not available on web');
|
|
86
|
+
return {};
|
|
90
87
|
}
|
|
91
88
|
export function getStoredTokenRefreshConfig(_target) {
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
console.warn('getStoredTokenRefreshConfig is not available on web');
|
|
90
|
+
return null;
|
|
94
91
|
}
|
|
95
92
|
export function getNestedField(obj, dotPath) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
93
|
+
const parts = dotPath.split('.');
|
|
94
|
+
let current = obj;
|
|
95
|
+
for (const part of parts) {
|
|
96
|
+
if (current == null || typeof current !== 'object')
|
|
97
|
+
return undefined;
|
|
98
|
+
current = current[part];
|
|
99
|
+
}
|
|
100
|
+
return current != null ? String(current) : undefined;
|
|
103
101
|
}
|
|
104
102
|
export function applyTemplate(template, value) {
|
|
105
|
-
|
|
103
|
+
return template.replace(/\{\{value\}\}/g, value);
|
|
106
104
|
}
|
package/src/tokenRefresh.js
CHANGED
|
@@ -9,94 +9,96 @@ const KEY_FETCH_CACHE = 'nitro_token_refresh_fetch_cache';
|
|
|
9
9
|
* Resolve a dot-notation path inside a parsed JSON object.
|
|
10
10
|
*/
|
|
11
11
|
export function getNestedField(obj, dotPath) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
const parts = dotPath.split('.');
|
|
13
|
+
let current = obj;
|
|
14
|
+
for (const part of parts) {
|
|
15
|
+
if (current == null || typeof current !== 'object')
|
|
16
|
+
return undefined;
|
|
17
|
+
current = current[part];
|
|
18
|
+
}
|
|
19
|
+
return current != null ? String(current) : undefined;
|
|
19
20
|
}
|
|
20
21
|
export function applyTemplate(template, value) {
|
|
21
|
-
|
|
22
|
+
return template.replace(/\{\{value\}\}/g, value);
|
|
22
23
|
}
|
|
23
24
|
function applyCompositeTemplate(template, values) {
|
|
24
|
-
|
|
25
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => values[key] ?? '');
|
|
25
26
|
}
|
|
26
27
|
export async function callRefreshEndpoint(config) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
`Token refresh failed: ${response.status} ${response.statusText}`
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
const headers = {};
|
|
39
|
-
if (config.responseType === 'text') {
|
|
40
|
-
const text = await response.text();
|
|
41
|
-
if (config.textHeader) {
|
|
42
|
-
headers[config.textHeader] = config.textTemplate
|
|
43
|
-
? applyTemplate(config.textTemplate, text)
|
|
44
|
-
: text;
|
|
28
|
+
const method = config.method ?? 'POST';
|
|
29
|
+
const response = await fetch(config.url, {
|
|
30
|
+
method,
|
|
31
|
+
headers: config.headers,
|
|
32
|
+
body: config.body,
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`Token refresh failed: ${response.status} ${response.statusText}`);
|
|
45
36
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
headers
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
37
|
+
const headers = {};
|
|
38
|
+
if (config.responseType === 'text') {
|
|
39
|
+
const text = await response.text();
|
|
40
|
+
if (config.textHeader) {
|
|
41
|
+
headers[config.textHeader] = config.textTemplate
|
|
42
|
+
? applyTemplate(config.textTemplate, text)
|
|
43
|
+
: text;
|
|
44
|
+
}
|
|
45
|
+
return headers;
|
|
46
|
+
}
|
|
47
|
+
// Default: json
|
|
48
|
+
const json = await response.json();
|
|
49
|
+
if (config.mappings) {
|
|
50
|
+
for (const mapping of config.mappings) {
|
|
51
|
+
const value = getNestedField(json, mapping.jsonPath);
|
|
52
|
+
if (value != null) {
|
|
53
|
+
headers[mapping.header] = mapping.valueTemplate
|
|
54
|
+
? applyTemplate(mapping.valueTemplate, value)
|
|
55
|
+
: value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
if (config.compositeHeaders) {
|
|
60
|
+
for (const comp of config.compositeHeaders) {
|
|
61
|
+
const values = {};
|
|
62
|
+
for (const [placeholder, jsonPath] of Object.entries(comp.paths)) {
|
|
63
|
+
const val = getNestedField(json, jsonPath);
|
|
64
|
+
if (val != null)
|
|
65
|
+
values[placeholder] = val;
|
|
66
|
+
}
|
|
67
|
+
headers[comp.header] = applyCompositeTemplate(comp.template, values);
|
|
68
|
+
}
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
-
return headers;
|
|
70
|
+
return headers;
|
|
71
71
|
}
|
|
72
72
|
export function registerTokenRefresh(options) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
const { target, ...config } = options;
|
|
74
|
+
const raw = JSON.stringify(config);
|
|
75
|
+
if (target === 'websocket' || target === 'all') {
|
|
76
|
+
NativeStorageSingleton.setSecureString(KEY_WS, raw);
|
|
77
|
+
}
|
|
78
|
+
if (target === 'fetch' || target === 'all') {
|
|
79
|
+
NativeStorageSingleton.setSecureString(KEY_FETCH, raw);
|
|
80
|
+
}
|
|
81
81
|
}
|
|
82
82
|
export function clearTokenRefresh(target) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
83
|
+
const t = target ?? 'all';
|
|
84
|
+
if (t === 'websocket' || t === 'all') {
|
|
85
|
+
NativeStorageSingleton.removeSecureString(KEY_WS);
|
|
86
|
+
NativeStorageSingleton.removeSecureString(KEY_WS_CACHE);
|
|
87
|
+
}
|
|
88
|
+
if (t === 'fetch' || t === 'all') {
|
|
89
|
+
NativeStorageSingleton.removeSecureString(KEY_FETCH);
|
|
90
|
+
NativeStorageSingleton.removeSecureString(KEY_FETCH_CACHE);
|
|
91
|
+
}
|
|
92
92
|
}
|
|
93
93
|
export function getStoredTokenRefreshConfig(target) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
const key = target === 'websocket' ? KEY_WS : KEY_FETCH;
|
|
95
|
+
try {
|
|
96
|
+
const raw = NativeStorageSingleton.getSecureString(key);
|
|
97
|
+
if (!raw)
|
|
98
|
+
return null;
|
|
99
|
+
return JSON.parse(raw);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
102
104
|
}
|
package/src/utf8.js
CHANGED
|
@@ -2,40 +2,39 @@ let _TextEncoder;
|
|
|
2
2
|
let _TextDecoder;
|
|
3
3
|
const NITRO_TEXT_DECODER_PKG = 'react-native-nitro-text-decoder';
|
|
4
4
|
function loadOptionalTextCodec() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
try {
|
|
6
|
+
// Hide require from the bundler so the package stays truly optional.
|
|
7
|
+
// eslint-disable-next-line no-new-func
|
|
8
|
+
const dynamicRequire = new Function('mod', 'return require(mod);');
|
|
9
|
+
return dynamicRequire(NITRO_TEXT_DECODER_PKG);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
13
14
|
}
|
|
14
15
|
if (typeof TextEncoder !== 'undefined') {
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
16
|
+
_TextEncoder = TextEncoder;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
_TextEncoder = loadOptionalTextCodec().TextEncoder;
|
|
18
20
|
}
|
|
19
21
|
if (typeof TextDecoder !== 'undefined') {
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
22
|
+
_TextDecoder = TextDecoder;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
_TextDecoder = loadOptionalTextCodec().TextDecoder;
|
|
23
26
|
}
|
|
24
27
|
export function stringToUTF8(str) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return new
|
|
30
|
-
}
|
|
31
|
-
return new _TextEncoder().encode(str);
|
|
28
|
+
if (!_TextEncoder) {
|
|
29
|
+
console.warn('stringToUTF8: TextEncoder not available. Install react-native-nitro-text-decoder.');
|
|
30
|
+
return new Uint8Array(0);
|
|
31
|
+
}
|
|
32
|
+
return new _TextEncoder().encode(str);
|
|
32
33
|
}
|
|
33
34
|
export function utf8ToString(bytes) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return
|
|
39
|
-
}
|
|
40
|
-
return new _TextDecoder().decode(bytes);
|
|
35
|
+
if (!_TextDecoder) {
|
|
36
|
+
console.warn('utf8ToString: TextDecoder not available. Install react-native-nitro-text-decoder.');
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
return new _TextDecoder().decode(bytes);
|
|
41
40
|
}
|