react-native-nitro-fetch 0.1.2 → 0.1.4
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/android/build.gradle +1 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt +8 -27
- package/android/src/main/java/com/margelo/nitro/nitrofetch/FetchCache.kt +11 -1
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NativeStorage.kt +102 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +3 -3
- package/ios/NativeStorage.swift +61 -0
- package/ios/NitroAutoPrefetcher.swift +5 -41
- package/lib/module/NitroFetch.nitro.js +0 -3
- package/lib/module/NitroFetch.nitro.js.map +1 -1
- package/lib/module/NitroInstances.js +1 -0
- package/lib/module/NitroInstances.js.map +1 -1
- package/lib/module/fetch.js +63 -81
- package/lib/module/fetch.js.map +1 -1
- package/lib/typescript/src/NitroFetch.nitro.d.ts +8 -0
- package/lib/typescript/src/NitroFetch.nitro.d.ts.map +1 -1
- package/lib/typescript/src/NitroInstances.d.ts +2 -1
- package/lib/typescript/src/NitroInstances.d.ts.map +1 -1
- package/lib/typescript/src/fetch.d.ts.map +1 -1
- package/nitro.json +4 -0
- package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.cpp +54 -0
- package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.hpp +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNativeStorageSpec.kt +60 -0
- package/nitrogen/generated/android/nitrofetch+autolinking.cmake +9 -4
- package/nitrogen/generated/android/nitrofetchOnLoad.cpp +10 -0
- package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp +35 -0
- package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp +5 -0
- package/nitrogen/generated/ios/NitroFetchAutolinking.mm +8 -0
- package/nitrogen/generated/ios/NitroFetchAutolinking.swift +15 -0
- package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.hpp +85 -0
- package/nitrogen/generated/ios/swift/HybridNativeStorageSpec.swift +51 -0
- package/nitrogen/generated/ios/swift/HybridNativeStorageSpec_cxx.swift +145 -0
- package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.cpp +23 -0
- package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.hpp +64 -0
- package/package.json +8 -24
- package/src/NitroFetch.nitro.ts +12 -5
- package/src/NitroInstances.ts +6 -2
- package/src/fetch.ts +151 -89
- package/LICENSE +0 -20
- package/README.md +0 -144
- package/src/NitroFetch.nitro.js +0 -2
- package/src/NitroInstances.js +0 -3
- package/src/fetch.js +0 -377
- package/src/index.js +0 -8
package/src/fetch.js
DELETED
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
import { NitroFetch as NitroFetchSingleton } from './NitroInstances';
|
|
2
|
-
// No base64: pass strings/ArrayBuffers directly
|
|
3
|
-
function headersToPairs(headers) {
|
|
4
|
-
'worklet';
|
|
5
|
-
if (!headers)
|
|
6
|
-
return undefined;
|
|
7
|
-
const pairs = [];
|
|
8
|
-
if (headers instanceof Headers) {
|
|
9
|
-
headers.forEach((v, k) => pairs.push({ key: k, value: v }));
|
|
10
|
-
return pairs;
|
|
11
|
-
}
|
|
12
|
-
if (Array.isArray(headers)) {
|
|
13
|
-
// Convert tuple pairs to objects if needed
|
|
14
|
-
for (const entry of headers) {
|
|
15
|
-
if (Array.isArray(entry) && entry.length >= 2) {
|
|
16
|
-
pairs.push({ key: String(entry[0]), value: String(entry[1]) });
|
|
17
|
-
}
|
|
18
|
-
else if (entry && typeof entry === 'object' && 'key' in entry && 'value' in entry) {
|
|
19
|
-
pairs.push(entry);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return pairs;
|
|
23
|
-
}
|
|
24
|
-
// Record<string, string>
|
|
25
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
26
|
-
pairs.push({ key: k, value: String(v) });
|
|
27
|
-
}
|
|
28
|
-
return pairs;
|
|
29
|
-
}
|
|
30
|
-
function normalizeBody(body) {
|
|
31
|
-
'worklet';
|
|
32
|
-
if (body == null)
|
|
33
|
-
return undefined;
|
|
34
|
-
if (typeof body === 'string')
|
|
35
|
-
return { bodyString: body };
|
|
36
|
-
if (body instanceof URLSearchParams)
|
|
37
|
-
return { bodyString: body.toString() };
|
|
38
|
-
if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer)
|
|
39
|
-
return { bodyBytes: body };
|
|
40
|
-
if (ArrayBuffer.isView(body)) {
|
|
41
|
-
const view = body;
|
|
42
|
-
// Pass a copy/slice of the underlying bytes without base64
|
|
43
|
-
//@ts-ignore
|
|
44
|
-
return { bodyBytes: view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength) };
|
|
45
|
-
}
|
|
46
|
-
// TODO: Blob/FormData support can be added later
|
|
47
|
-
throw new Error('Unsupported body type for nitro fetch');
|
|
48
|
-
}
|
|
49
|
-
// @ts-ignore
|
|
50
|
-
function pairsToHeaders(pairs) {
|
|
51
|
-
'worklet';
|
|
52
|
-
const h = new Headers();
|
|
53
|
-
for (const { key, value } of pairs)
|
|
54
|
-
h.append(key, value);
|
|
55
|
-
return h;
|
|
56
|
-
}
|
|
57
|
-
const NitroFetchHybrid = NitroFetchSingleton;
|
|
58
|
-
let client;
|
|
59
|
-
function ensureClient() {
|
|
60
|
-
if (client)
|
|
61
|
-
return client;
|
|
62
|
-
try {
|
|
63
|
-
client = NitroFetchHybrid.createClient();
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
console.error('Failed to create NitroFetch client', err);
|
|
67
|
-
// native not ready; keep undefined
|
|
68
|
-
}
|
|
69
|
-
return client;
|
|
70
|
-
}
|
|
71
|
-
function buildNitroRequest(input, init) {
|
|
72
|
-
'worklet';
|
|
73
|
-
let url;
|
|
74
|
-
let method;
|
|
75
|
-
let headersInit;
|
|
76
|
-
let body;
|
|
77
|
-
if (typeof input === 'string' || input instanceof URL) {
|
|
78
|
-
url = String(input);
|
|
79
|
-
method = init?.method;
|
|
80
|
-
headersInit = init?.headers;
|
|
81
|
-
body = init?.body ?? null;
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
// Request object
|
|
85
|
-
url = input.url;
|
|
86
|
-
method = input.method;
|
|
87
|
-
headersInit = input.headers;
|
|
88
|
-
// Clone body if needed – Request objects in RN typically allow direct access
|
|
89
|
-
body = init?.body ?? null;
|
|
90
|
-
}
|
|
91
|
-
const headers = headersToPairs(headersInit);
|
|
92
|
-
const normalized = normalizeBody(body);
|
|
93
|
-
return {
|
|
94
|
-
url,
|
|
95
|
-
method: method?.toUpperCase() ?? 'GET',
|
|
96
|
-
headers,
|
|
97
|
-
bodyString: normalized?.bodyString,
|
|
98
|
-
bodyBytes: "", //normalized?.bodyBytes,
|
|
99
|
-
followRedirects: true,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
async function nitroFetchRaw(input, init) {
|
|
103
|
-
'worklet';
|
|
104
|
-
const hasNative = typeof NitroFetchHybrid?.createClient === 'function';
|
|
105
|
-
if (!hasNative) {
|
|
106
|
-
// Fallback path not supported for raw; use global fetch and synthesize minimal shape
|
|
107
|
-
// @ts-ignore: global fetch exists in RN
|
|
108
|
-
const res = await fetch(input, init);
|
|
109
|
-
const url = res.url ?? String(input);
|
|
110
|
-
const bytes = await res.arrayBuffer();
|
|
111
|
-
const headers = [];
|
|
112
|
-
res.headers.forEach((v, k) => headers.push({ key: k, value: v }));
|
|
113
|
-
return {
|
|
114
|
-
url,
|
|
115
|
-
status: res.status,
|
|
116
|
-
statusText: res.statusText,
|
|
117
|
-
ok: res.ok,
|
|
118
|
-
redirected: res.redirected ?? false,
|
|
119
|
-
headers,
|
|
120
|
-
bodyBytes: bytes,
|
|
121
|
-
bodyString: undefined,
|
|
122
|
-
}; // bleee
|
|
123
|
-
}
|
|
124
|
-
const req = buildNitroRequest(input, init);
|
|
125
|
-
ensureClient();
|
|
126
|
-
if (!client || typeof client.request !== 'function')
|
|
127
|
-
throw new Error('NitroFetch client not available');
|
|
128
|
-
const res = await client.request(req);
|
|
129
|
-
return res;
|
|
130
|
-
}
|
|
131
|
-
export async function nitroFetch(input, init) {
|
|
132
|
-
'worklet';
|
|
133
|
-
// If native implementation is not present yet, fallback to global fetch
|
|
134
|
-
const hasNative = typeof NitroFetchHybrid?.createClient === 'function';
|
|
135
|
-
if (!hasNative) {
|
|
136
|
-
// @ts-ignore: global fetch exists in RN
|
|
137
|
-
return fetch(input, init);
|
|
138
|
-
}
|
|
139
|
-
const res = await nitroFetchRaw(input, init);
|
|
140
|
-
// Fallback lightweight Response-like object (minimal methods)
|
|
141
|
-
const headersObj = res.headers.reduce((acc, { key, value }) => {
|
|
142
|
-
acc[key] = value;
|
|
143
|
-
return acc;
|
|
144
|
-
}, {});
|
|
145
|
-
const light = {
|
|
146
|
-
url: res.url,
|
|
147
|
-
ok: res.ok,
|
|
148
|
-
status: res.status,
|
|
149
|
-
statusText: res.statusText,
|
|
150
|
-
redirected: res.redirected,
|
|
151
|
-
headers: headersObj,
|
|
152
|
-
arrayBuffer: async () => res.bodyBytes,
|
|
153
|
-
text: async () => res.bodyString,
|
|
154
|
-
json: async () => JSON.parse(res.bodyString ?? '{}'),
|
|
155
|
-
};
|
|
156
|
-
return light;
|
|
157
|
-
}
|
|
158
|
-
// Start a native prefetch. Requires a `prefetchKey` header on the request.
|
|
159
|
-
export async function prefetch(input, init) {
|
|
160
|
-
// If native implementation is not present yet, do nothing
|
|
161
|
-
const hasNative = typeof NitroFetchHybrid?.createClient === 'function';
|
|
162
|
-
if (!hasNative)
|
|
163
|
-
return;
|
|
164
|
-
// Build NitroRequest and ensure prefetchKey header exists
|
|
165
|
-
const req = buildNitroRequest(input, init);
|
|
166
|
-
const hasKey = req.headers?.some(h => h.key.toLowerCase() === 'prefetchkey') ?? false;
|
|
167
|
-
// Also support passing prefetchKey via non-standard field on init
|
|
168
|
-
const fromInit = init?.prefetchKey;
|
|
169
|
-
if (!hasKey && fromInit) {
|
|
170
|
-
req.headers = (req.headers ?? []).concat([{ key: 'prefetchKey', value: fromInit }]);
|
|
171
|
-
}
|
|
172
|
-
const finalHasKey = req.headers?.some(h => h.key.toLowerCase() === 'prefetchkey');
|
|
173
|
-
if (!finalHasKey) {
|
|
174
|
-
throw new Error('prefetch requires a \"prefetchKey\" header');
|
|
175
|
-
}
|
|
176
|
-
// Ensure client and call native prefetch
|
|
177
|
-
ensureClient();
|
|
178
|
-
if (!client || typeof client.prefetch !== 'function')
|
|
179
|
-
return;
|
|
180
|
-
await client.prefetch(req);
|
|
181
|
-
}
|
|
182
|
-
// Persist a request to MMKV so native can prefetch it on app start.
|
|
183
|
-
// Stores an array of entries under the same key Android reads: "nitrofetch_autoprefetch_queue".
|
|
184
|
-
export async function prefetchOnAppStart(input, init) {
|
|
185
|
-
// Resolve request and prefetchKey
|
|
186
|
-
const req = buildNitroRequest(input, init);
|
|
187
|
-
const fromHeader = req.headers?.find(h => h.key.toLowerCase() === 'prefetchkey')?.value;
|
|
188
|
-
const fromInit = init?.prefetchKey;
|
|
189
|
-
const prefetchKey = fromHeader ?? fromInit;
|
|
190
|
-
if (!prefetchKey) {
|
|
191
|
-
throw new Error('prefetchOnAppStart requires a "prefetchKey" (header or init.prefetchKey)');
|
|
192
|
-
}
|
|
193
|
-
// Convert headers to a plain object for storage
|
|
194
|
-
const headersObj = (req.headers ?? []).reduce((acc, { key, value }) => {
|
|
195
|
-
acc[String(key)] = String(value);
|
|
196
|
-
return acc;
|
|
197
|
-
}, {});
|
|
198
|
-
const entry = {
|
|
199
|
-
url: req.url,
|
|
200
|
-
prefetchKey,
|
|
201
|
-
headers: headersObj,
|
|
202
|
-
};
|
|
203
|
-
// Write or append to MMKV queue
|
|
204
|
-
try {
|
|
205
|
-
// Dynamically require to keep it optional for consumers
|
|
206
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
207
|
-
const { MMKV } = require('react-native-mmkv');
|
|
208
|
-
const storage = new MMKV(); // default instance matches Android's defaultMMKV
|
|
209
|
-
const KEY = 'nitrofetch_autoprefetch_queue';
|
|
210
|
-
let arr = [];
|
|
211
|
-
try {
|
|
212
|
-
const raw = storage.getString(KEY);
|
|
213
|
-
if (raw)
|
|
214
|
-
arr = JSON.parse(raw);
|
|
215
|
-
if (!Array.isArray(arr))
|
|
216
|
-
arr = [];
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
arr = [];
|
|
220
|
-
}
|
|
221
|
-
arr.push(entry);
|
|
222
|
-
storage.set(KEY, JSON.stringify(arr));
|
|
223
|
-
}
|
|
224
|
-
catch (e) {
|
|
225
|
-
console.warn('react-native-mmkv not available; prefetchOnAppStart is a no-op', e);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
// Remove one entry (by prefetchKey) from the auto-prefetch queue in MMKV.
|
|
229
|
-
export async function removeFromAutoPrefetch(prefetchKey) {
|
|
230
|
-
// No-op on iOS
|
|
231
|
-
try {
|
|
232
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
233
|
-
const { MMKV } = require('react-native-mmkv');
|
|
234
|
-
const storage = new MMKV();
|
|
235
|
-
const KEY = 'nitrofetch_autoprefetch_queue';
|
|
236
|
-
let arr = [];
|
|
237
|
-
try {
|
|
238
|
-
const raw = storage.getString(KEY);
|
|
239
|
-
if (raw)
|
|
240
|
-
arr = JSON.parse(raw);
|
|
241
|
-
if (!Array.isArray(arr))
|
|
242
|
-
arr = [];
|
|
243
|
-
}
|
|
244
|
-
catch {
|
|
245
|
-
arr = [];
|
|
246
|
-
}
|
|
247
|
-
const next = arr.filter((e) => e && e.prefetchKey !== prefetchKey);
|
|
248
|
-
if (next.length === 0) {
|
|
249
|
-
if (typeof storage.delete === 'function') {
|
|
250
|
-
storage.delete(KEY);
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
storage.set(KEY, JSON.stringify([]));
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
else if (next.length !== arr.length) {
|
|
257
|
-
storage.set(KEY, JSON.stringify(next));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
catch (e) {
|
|
261
|
-
console.warn('react-native-mmkv not available; removeFromAutoPrefetch is a no-op', e);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
// Remove all entries from the auto-prefetch queue in MMKV.
|
|
265
|
-
export async function removeAllFromAutoprefetch() {
|
|
266
|
-
try {
|
|
267
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
268
|
-
const { MMKV } = require('react-native-mmkv');
|
|
269
|
-
const storage = new MMKV();
|
|
270
|
-
const KEY = 'nitrofetch_autoprefetch_queue';
|
|
271
|
-
if (typeof storage.delete === 'function') {
|
|
272
|
-
storage.delete(KEY);
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
storage.set(KEY, JSON.stringify([]));
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
catch (e) {
|
|
279
|
-
console.warn('react-native-mmkv not available; removeAllFromAutoprefetch is a no-op', e);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
let nitroRuntime;
|
|
283
|
-
let WorkletsRef;
|
|
284
|
-
function ensureWorkletRuntime(name = 'nitro-fetch') {
|
|
285
|
-
console.log('ensuring worklet runtime');
|
|
286
|
-
try {
|
|
287
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
288
|
-
const { Worklets } = require('react-native-worklets-core');
|
|
289
|
-
nitroRuntime = nitroRuntime ?? Worklets.createRuntime(name);
|
|
290
|
-
console.log('nitroRuntime:', !!nitroRuntime);
|
|
291
|
-
return nitroRuntime;
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
console.warn('react-native-worklets-core not available');
|
|
295
|
-
return undefined;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
function getWorklets() {
|
|
299
|
-
try {
|
|
300
|
-
if (WorkletsRef)
|
|
301
|
-
return WorkletsRef;
|
|
302
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
303
|
-
const { Worklets } = require('react-native-worklets-core');
|
|
304
|
-
WorkletsRef = Worklets;
|
|
305
|
-
return WorkletsRef;
|
|
306
|
-
}
|
|
307
|
-
catch {
|
|
308
|
-
console.warn('react-native-worklets-core not available');
|
|
309
|
-
return undefined;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
export async function nitroFetchOnWorklet(input, init, mapWorklet, options) {
|
|
313
|
-
console.log('nitroFetchOnWorklet: starting');
|
|
314
|
-
const preferBytes = options?.preferBytes === true; // default true
|
|
315
|
-
console.log('nitroFetchOnWorklet: preferBytes:', preferBytes);
|
|
316
|
-
let rt;
|
|
317
|
-
let Worklets;
|
|
318
|
-
try {
|
|
319
|
-
rt = ensureWorkletRuntime(options?.runtimeName);
|
|
320
|
-
console.log('nitroFetchOnWorklet: runtime created?', !!rt);
|
|
321
|
-
Worklets = getWorklets();
|
|
322
|
-
console.log('nitroFetchOnWorklet: Worklets available?', !!Worklets);
|
|
323
|
-
}
|
|
324
|
-
catch (e) {
|
|
325
|
-
console.error('nitroFetchOnWorklet: setup failed', e);
|
|
326
|
-
}
|
|
327
|
-
// Fallback: if runtime is not available, do the work on JS
|
|
328
|
-
if (!rt || !Worklets || typeof rt.run !== 'function') {
|
|
329
|
-
console.warn('nitroFetchOnWorklet: no runtime, mapping on JS thread');
|
|
330
|
-
const res = await nitroFetchRaw(input, init);
|
|
331
|
-
const payload = {
|
|
332
|
-
url: res.url,
|
|
333
|
-
status: res.status,
|
|
334
|
-
statusText: res.statusText,
|
|
335
|
-
ok: res.ok,
|
|
336
|
-
redirected: res.redirected,
|
|
337
|
-
headers: res.headers,
|
|
338
|
-
bodyBytes: preferBytes ? res.bodyBytes : undefined,
|
|
339
|
-
bodyString: preferBytes ? undefined : res.bodyString,
|
|
340
|
-
};
|
|
341
|
-
return mapWorklet(payload);
|
|
342
|
-
}
|
|
343
|
-
return await new Promise((resolve, reject) => {
|
|
344
|
-
try {
|
|
345
|
-
console.log('nitroFetchOnWorklet: about to call rt.run');
|
|
346
|
-
rt.run(async (map) => {
|
|
347
|
-
'worklet';
|
|
348
|
-
try {
|
|
349
|
-
console.log('nitroFetchOnWorklet: running fetch on worklet thread');
|
|
350
|
-
const res = await nitroFetchRaw(input, init);
|
|
351
|
-
console.log('nitroFetchOnWorklet: fetch completed');
|
|
352
|
-
const url = res.url;
|
|
353
|
-
const status = res.status;
|
|
354
|
-
const statusText = res.statusText;
|
|
355
|
-
const ok = res.ok;
|
|
356
|
-
const redirected = res.redirected;
|
|
357
|
-
const headersPairs = res.headers;
|
|
358
|
-
const bodyBytes = undefined; // preferBytes ? res.bodyBytes : undefined;
|
|
359
|
-
const bodyString = preferBytes ? undefined : res.bodyString;
|
|
360
|
-
const payload = { url, status, statusText, ok, redirected, headers: headersPairs, bodyBytes, bodyString };
|
|
361
|
-
const out = map(payload);
|
|
362
|
-
// Resolve back on JS thread
|
|
363
|
-
Worklets.runOnJS(resolve)(out);
|
|
364
|
-
}
|
|
365
|
-
catch (e) {
|
|
366
|
-
Worklets.runOnJS(reject)(e);
|
|
367
|
-
}
|
|
368
|
-
}, mapWorklet);
|
|
369
|
-
}
|
|
370
|
-
catch (e) {
|
|
371
|
-
console.error('nitroFetchOnWorklet: rt.run failed', e);
|
|
372
|
-
reject(e);
|
|
373
|
-
}
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
export const x = ensureWorkletRuntime();
|
|
377
|
-
export const y = getWorklets();
|
package/src/index.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export { nitroFetch as fetch, nitroFetchOnWorklet, prefetch, prefetchOnAppStart, removeFromAutoPrefetch, removeAllFromAutoprefetch, } from './fetch';
|
|
2
|
-
export { NitroFetch } from './NitroInstances';
|
|
3
|
-
import './fetch';
|
|
4
|
-
// Keep legacy export to avoid breaking any local tests/usages during scaffolding.
|
|
5
|
-
// Will be removed once native Cronet path is ready.
|
|
6
|
-
export function multiply(a, b) {
|
|
7
|
-
return a * b;
|
|
8
|
-
}
|