@ar.io/sdk 3.12.0-beta.3 → 3.12.0-beta.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/bundles/web.bundle.min.js +125 -125
- package/lib/cjs/common/wayfinder/wayfinder.js +90 -233
- package/lib/cjs/version.js +1 -1
- package/lib/esm/common/wayfinder/wayfinder.js +90 -232
- package/lib/esm/version.js +1 -1
- package/lib/types/common/wayfinder/wayfinder.d.ts +13 -41
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.Wayfinder = exports.createWayfinderClient = exports.WayfinderEmitter = exports.resolveWayfinderUrl = exports.txIdRegex = exports.arnsRegex = void 0;
|
|
7
7
|
exports.tapAndVerifyStream = tapAndVerifyStream;
|
|
8
|
-
exports.wrapVerifiedResponse = wrapVerifiedResponse;
|
|
9
8
|
/**
|
|
10
9
|
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
|
|
11
10
|
*
|
|
@@ -246,56 +245,44 @@ function tapAndVerifyStream({ originalStream, contentLength, verifyData, txId, e
|
|
|
246
245
|
}
|
|
247
246
|
throw new Error('Unsupported body type for cloning');
|
|
248
247
|
}
|
|
249
|
-
function wrapVerifiedResponse(original, newBody, txId) {
|
|
250
|
-
// Clone headers (Header objects aren't serializable)
|
|
251
|
-
const headers = new Headers();
|
|
252
|
-
original.headers.forEach((value, key) => headers.set(key, value));
|
|
253
|
-
// Create a new Response with the new body and cloned headers
|
|
254
|
-
const wrapped = new Response(newBody, {
|
|
255
|
-
status: original.status,
|
|
256
|
-
statusText: original.statusText,
|
|
257
|
-
headers,
|
|
258
|
-
});
|
|
259
|
-
// Attach txId for downstream tracking
|
|
260
|
-
wrapped.txId = txId;
|
|
261
|
-
wrapped.redirectedFrom = original.url;
|
|
262
|
-
return wrapped;
|
|
263
|
-
}
|
|
264
248
|
/**
|
|
265
|
-
* Creates a wrapped
|
|
249
|
+
* Creates a wrapped fetch function that supports ar:// protocol
|
|
266
250
|
*
|
|
267
|
-
* This function leverages a Proxy to intercept calls to
|
|
268
|
-
* and redirects them to the target gateway using the resolveUrl function
|
|
269
|
-
* It also supports the http client methods like get(), post(), put(), delete(), etc.
|
|
251
|
+
* This function leverages a Proxy to intercept calls to fetch
|
|
252
|
+
* and redirects them to the target gateway using the resolveUrl function.
|
|
270
253
|
*
|
|
271
|
-
* Any URLs provided that are not wayfinder urls will be
|
|
254
|
+
* Any URLs provided that are not wayfinder urls will be passed directly to fetch.
|
|
272
255
|
*
|
|
273
|
-
* @param httpClient - the http client to wrap (e.g. axios, fetch, got, etc.)
|
|
274
256
|
* @param resolveUrl - the function to construct the redirect url for ar:// requests
|
|
275
|
-
* @returns a wrapped
|
|
257
|
+
* @returns a wrapped fetch function that supports ar:// protocol and always returns Response
|
|
276
258
|
*/
|
|
277
|
-
const createWayfinderClient = ({
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
259
|
+
const createWayfinderClient = ({ resolveUrl, verifyData, selectGateway, emitter = new WayfinderEmitter(), logger, strict = false, }) => {
|
|
260
|
+
// Create a function that will handle the redirection logic for ar:// URLs
|
|
261
|
+
const wayfinderRedirect = async (url, init) => {
|
|
262
|
+
// If the url is not a string or URL (e.g., it's a Request object), extract the URL from it
|
|
263
|
+
const originalUrl = url instanceof Request ? url.url : url.toString();
|
|
264
|
+
// If it's not an ar:// URL, pass it directly to fetch
|
|
265
|
+
if (!originalUrl.startsWith('ar://')) {
|
|
266
|
+
logger?.debug('Not a wayfinder URL, passing to fetch directly', {
|
|
283
267
|
originalUrl,
|
|
284
268
|
});
|
|
285
269
|
emitter?.emit('routing-skipped', {
|
|
286
|
-
originalUrl
|
|
270
|
+
originalUrl,
|
|
287
271
|
});
|
|
288
|
-
|
|
272
|
+
// we don't do anything special with non-ar:// urls, just pass them through
|
|
273
|
+
return fetch(url, init);
|
|
289
274
|
}
|
|
275
|
+
// Start the routing process
|
|
290
276
|
emitter?.emit('routing-started', {
|
|
291
|
-
originalUrl
|
|
277
|
+
originalUrl,
|
|
292
278
|
});
|
|
293
|
-
//
|
|
279
|
+
// Retry logic for gateway selection
|
|
294
280
|
const maxRetries = 3;
|
|
295
281
|
const retryDelay = 1000;
|
|
296
282
|
for (let i = 0; i < maxRetries; i++) {
|
|
297
283
|
try {
|
|
298
284
|
// select the target gateway
|
|
285
|
+
// TODO: we may want to provide the `path` to select gateway so the HEAD checks in routers check the existence of the actual path/request
|
|
299
286
|
const selectedGateway = await selectGateway();
|
|
300
287
|
logger?.debug('Selected gateway', {
|
|
301
288
|
originalUrl,
|
|
@@ -316,172 +303,70 @@ const createWayfinderClient = ({ httpClient, resolveUrl, verifyData, selectGatew
|
|
|
316
303
|
originalUrl,
|
|
317
304
|
redirectUrl: redirectUrl.toString(),
|
|
318
305
|
});
|
|
319
|
-
//
|
|
320
|
-
const response = await
|
|
321
|
-
|
|
306
|
+
// Make the request to the target gateway
|
|
307
|
+
const response = await fetch(redirectUrl.toString(), {
|
|
308
|
+
...init,
|
|
309
|
+
// follow redirects as gateways use sandboxing on /txId requests
|
|
310
|
+
redirect: 'follow',
|
|
311
|
+
});
|
|
312
|
+
// TODO: update any caching we use for the request and gateway response
|
|
322
313
|
logger?.debug(`Successfully routed request to gateway`, {
|
|
323
314
|
redirectUrl: redirectUrl.toString(),
|
|
324
|
-
originalUrl
|
|
315
|
+
originalUrl,
|
|
325
316
|
});
|
|
326
|
-
//
|
|
327
|
-
if (
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
// no transaction id found, skip verification
|
|
364
|
-
logger?.debug('No transaction id found, skipping verification', {
|
|
365
|
-
redirectUrl: redirectUrl.toString(),
|
|
366
|
-
originalUrl,
|
|
367
|
-
});
|
|
368
|
-
emitter?.emit('verification-skipped', {
|
|
369
|
-
originalUrl,
|
|
370
|
-
});
|
|
371
|
-
return response;
|
|
372
|
-
}
|
|
373
|
-
emitter?.emit('identified-transaction-id', {
|
|
374
|
-
originalUrl,
|
|
375
|
-
selectedGateway: redirectUrl.toString(),
|
|
376
|
-
txId,
|
|
377
|
-
});
|
|
378
|
-
// parse out the key that contains the response body, we'll use it later when updating the response object
|
|
379
|
-
const responseDataKey = response.body
|
|
380
|
-
? 'body'
|
|
381
|
-
: response.data
|
|
382
|
-
? 'data'
|
|
383
|
-
: undefined;
|
|
384
|
-
if (responseDataKey === undefined) {
|
|
385
|
-
throw new Error('No data body or data provided, skipping verification', {
|
|
386
|
-
cause: {
|
|
387
|
-
redirectUrl: redirectUrl.toString(),
|
|
388
|
-
originalUrl: originalUrl.toString(),
|
|
389
|
-
},
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
const responseBody = response[responseDataKey];
|
|
393
|
-
// TODO: determine if it is data item or L1 transaction, and tell the verifier accordingly, just drop in hit to graphql now
|
|
394
|
-
if (txId === undefined) {
|
|
395
|
-
throw new Error('Failed to parse data hash from response headers', {
|
|
396
|
-
cause: {
|
|
397
|
-
redirectUrl: redirectUrl.toString(),
|
|
398
|
-
originalUrl: originalUrl.toString(),
|
|
399
|
-
txId,
|
|
400
|
-
},
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
else if (responseBody === undefined) {
|
|
404
|
-
throw new Error('No data body provided, skipping verification', {
|
|
405
|
-
cause: {
|
|
406
|
-
redirectUrl: redirectUrl.toString(),
|
|
407
|
-
originalUrl: originalUrl.toString(),
|
|
408
|
-
txId,
|
|
409
|
-
},
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
else {
|
|
413
|
-
logger?.debug('Verifying data hash for txId', {
|
|
414
|
-
redirectUrl: redirectUrl.toString(),
|
|
415
|
-
originalUrl: originalUrl.toString(),
|
|
416
|
-
txId,
|
|
417
|
-
});
|
|
418
|
-
if (responseBody instanceof ReadableStream ||
|
|
419
|
-
responseBody instanceof node_stream_1.Readable) {
|
|
420
|
-
const newClientStream = tapAndVerifyStream({
|
|
421
|
-
originalStream: responseBody,
|
|
422
|
-
contentLength,
|
|
423
|
-
verifyData,
|
|
424
|
-
txId,
|
|
425
|
-
emitter,
|
|
426
|
-
strict,
|
|
427
|
-
});
|
|
428
|
-
if (response instanceof Response) {
|
|
429
|
-
// specific to fetch
|
|
430
|
-
return wrapVerifiedResponse(response, newClientStream, txId);
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
// overwrite the response body with the new client stream
|
|
434
|
-
response.txId = txId;
|
|
435
|
-
response.body = newClientStream;
|
|
436
|
-
return response;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
// TODO: content-application/json and it's smaller than 10mb
|
|
441
|
-
// TODO: add tests and verify this works for all non-Readable/streamed responses
|
|
442
|
-
if (strict) {
|
|
443
|
-
// In strict mode, wait for verification before returning response
|
|
444
|
-
try {
|
|
445
|
-
await verifyData({
|
|
446
|
-
data: responseBody,
|
|
447
|
-
txId,
|
|
448
|
-
});
|
|
449
|
-
emitter?.emit('verification-succeeded', { txId });
|
|
450
|
-
return response;
|
|
451
|
-
}
|
|
452
|
-
catch (error) {
|
|
453
|
-
logger?.debug('Failed to verify data hash', {
|
|
454
|
-
error,
|
|
455
|
-
txId,
|
|
456
|
-
});
|
|
457
|
-
emitter?.emit('verification-failed', { txId, error });
|
|
458
|
-
throw new Error('Verification failed', { cause: error });
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
else {
|
|
462
|
-
// In non-strict mode, perform verification in the background
|
|
463
|
-
verifyData({
|
|
464
|
-
data: responseBody,
|
|
465
|
-
txId,
|
|
466
|
-
})
|
|
467
|
-
.then(() => {
|
|
468
|
-
emitter?.emit('verification-succeeded', { txId });
|
|
469
|
-
})
|
|
470
|
-
.catch((error) => {
|
|
471
|
-
logger?.debug('Failed to verify data hash', {
|
|
472
|
-
error,
|
|
473
|
-
txId,
|
|
474
|
-
});
|
|
475
|
-
emitter?.emit('verification-failed', { txId, error });
|
|
476
|
-
});
|
|
477
|
-
return response;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
317
|
+
// return the response right away if no redirect was made
|
|
318
|
+
if (redirectUrl.toString() === originalUrl) {
|
|
319
|
+
return response;
|
|
320
|
+
}
|
|
321
|
+
// return the response right away if no verification is needed or if there is no body
|
|
322
|
+
if (!verifyData) {
|
|
323
|
+
return response;
|
|
324
|
+
}
|
|
325
|
+
// the txId is either in the response headers or the path of the request as the first parameter
|
|
326
|
+
const txId = response.headers.get('x-arns-resolved-id') ??
|
|
327
|
+
redirectUrl.pathname.split('/')[1];
|
|
328
|
+
const contentLength = +(response.headers.get('content-length') ?? 0);
|
|
329
|
+
if (!exports.txIdRegex.test(txId)) {
|
|
330
|
+
// No transaction ID found, skip verification
|
|
331
|
+
logger?.debug('No transaction ID found, skipping verification', {
|
|
332
|
+
redirectUrl: redirectUrl.toString(),
|
|
333
|
+
originalUrl,
|
|
334
|
+
});
|
|
335
|
+
emitter?.emit('verification-skipped', {
|
|
336
|
+
originalUrl,
|
|
337
|
+
});
|
|
338
|
+
return response;
|
|
339
|
+
}
|
|
340
|
+
emitter?.emit('identified-transaction-id', {
|
|
341
|
+
originalUrl,
|
|
342
|
+
selectedGateway: redirectUrl.toString(),
|
|
343
|
+
txId,
|
|
344
|
+
});
|
|
345
|
+
if (!response.body) {
|
|
346
|
+
logger?.debug('No body, skipping verification', {
|
|
347
|
+
redirectUrl: redirectUrl.toString(),
|
|
348
|
+
originalUrl,
|
|
349
|
+
});
|
|
350
|
+
emitter?.emit('verification-skipped', {
|
|
351
|
+
originalUrl,
|
|
352
|
+
});
|
|
353
|
+
return response;
|
|
482
354
|
}
|
|
483
|
-
|
|
484
|
-
|
|
355
|
+
const verifiedStream = tapAndVerifyStream({
|
|
356
|
+
originalStream: response.body,
|
|
357
|
+
contentLength,
|
|
358
|
+
verifyData,
|
|
359
|
+
txId,
|
|
360
|
+
emitter,
|
|
361
|
+
strict,
|
|
362
|
+
});
|
|
363
|
+
// wrap the response with the verified stream
|
|
364
|
+
return new Response(verifiedStream, {
|
|
365
|
+
status: response.status,
|
|
366
|
+
statusText: response.statusText,
|
|
367
|
+
// TODO: we could add identified transaction id to the headers here, but it would be changing information from the original response
|
|
368
|
+
headers: response.headers,
|
|
369
|
+
});
|
|
485
370
|
}
|
|
486
371
|
catch (error) {
|
|
487
372
|
logger?.debug('Failed to route request', {
|
|
@@ -494,6 +379,12 @@ const createWayfinderClient = ({ httpClient, resolveUrl, verifyData, selectGatew
|
|
|
494
379
|
if (i < maxRetries - 1) {
|
|
495
380
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
496
381
|
}
|
|
382
|
+
else {
|
|
383
|
+
emitter?.emit('routing-failed', {
|
|
384
|
+
originalUrl,
|
|
385
|
+
error,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
497
388
|
}
|
|
498
389
|
}
|
|
499
390
|
throw new Error('Failed to route request after max retries', {
|
|
@@ -503,38 +394,13 @@ const createWayfinderClient = ({ httpClient, resolveUrl, verifyData, selectGatew
|
|
|
503
394
|
},
|
|
504
395
|
});
|
|
505
396
|
};
|
|
506
|
-
return
|
|
507
|
-
// support direct calls: fetch('ar://…', options)
|
|
508
|
-
// axios() or got()
|
|
509
|
-
apply: (_target, _thisArg, argArray) => wayfinderRedirect(httpClient, argArray),
|
|
510
|
-
// support http clients that use functions like `got.get`, `got.post`, `axios.get`, etc. while still using the wayfinder redirect function
|
|
511
|
-
get: (target, prop, receiver) => {
|
|
512
|
-
const value = Reflect.get(target, prop, receiver);
|
|
513
|
-
if (typeof value === 'function') {
|
|
514
|
-
return (...inner) => wayfinderRedirect(value.bind(target), inner);
|
|
515
|
-
}
|
|
516
|
-
return value; // numbers, objects, symbols pass through untouched
|
|
517
|
-
},
|
|
518
|
-
});
|
|
397
|
+
return wayfinderRedirect;
|
|
519
398
|
};
|
|
520
399
|
exports.createWayfinderClient = createWayfinderClient;
|
|
521
400
|
/**
|
|
522
401
|
* The main class for the wayfinder
|
|
523
|
-
* @param router - the router to use for requests
|
|
524
|
-
* @param httpClient - the http client to use for requests
|
|
525
|
-
* @param blocklist - the blocklist of gateways to avoid
|
|
526
402
|
*/
|
|
527
403
|
class Wayfinder {
|
|
528
|
-
/**
|
|
529
|
-
* The native http client used by wayfinder. By default, the native fetch api is used.
|
|
530
|
-
*
|
|
531
|
-
* @example
|
|
532
|
-
* const wayfinder = new Wayfinder({
|
|
533
|
-
* httpClient: axios,
|
|
534
|
-
* });
|
|
535
|
-
*
|
|
536
|
-
*/
|
|
537
|
-
httpClient;
|
|
538
404
|
/**
|
|
539
405
|
* The gateways provider is responsible for providing the list of gateways to use for routing requests.
|
|
540
406
|
*
|
|
@@ -575,8 +441,7 @@ class Wayfinder {
|
|
|
575
441
|
*/
|
|
576
442
|
resolveUrl;
|
|
577
443
|
/**
|
|
578
|
-
*
|
|
579
|
-
* A wrapped http client that supports ar:// protocol. If a verification strategy is provided,
|
|
444
|
+
* A wrapped fetch function that supports ar:// protocol. If a verification strategy is provided,
|
|
580
445
|
* the request will be verified and events will be emitted as the request is processed.
|
|
581
446
|
*
|
|
582
447
|
* @example
|
|
@@ -596,9 +461,6 @@ class Wayfinder {
|
|
|
596
461
|
* // request a transaction id
|
|
597
462
|
* const response = await wayfinder.request('ar://1234567890')
|
|
598
463
|
*
|
|
599
|
-
* // request a transaction id with a custom http client
|
|
600
|
-
* const response = await wayfinder.request('ar://1234567890')
|
|
601
|
-
*
|
|
602
464
|
* // Set strict mode to true to make verification blocking
|
|
603
465
|
* const wayfinder = new Wayfinder({
|
|
604
466
|
* strict: true,
|
|
@@ -664,14 +526,13 @@ class Wayfinder {
|
|
|
664
526
|
emitter;
|
|
665
527
|
/**
|
|
666
528
|
* The constructor for the wayfinder
|
|
667
|
-
* @param httpClient - the http client to use for requests
|
|
668
529
|
* @param routingStrategy - the routing strategy to use for requests
|
|
669
530
|
* @param verificationStrategy - the verification strategy to use for requests
|
|
670
531
|
* @param gatewaysProvider - the gateways provider to use for routing requests
|
|
671
532
|
* @param logger - the logger to use for logging
|
|
672
533
|
* @param strict - if true, verification will be blocking and will fail requests if verification fails; if false, verification will be non-blocking
|
|
673
534
|
*/
|
|
674
|
-
constructor({
|
|
535
|
+
constructor({ logger = logger_js_1.Logger.default, gatewaysProvider = new simple_cache_js_1.SimpleCacheGatewaysProvider({
|
|
675
536
|
gatewaysProvider: new network_js_1.NetworkGatewaysProvider({
|
|
676
537
|
ario: io_js_1.ARIO.mainnet(),
|
|
677
538
|
}),
|
|
@@ -694,12 +555,9 @@ class Wayfinder {
|
|
|
694
555
|
onVerificationProgress: (event) => {
|
|
695
556
|
logger.debug('Verification progress!', event);
|
|
696
557
|
},
|
|
697
|
-
}, strict = false,
|
|
698
|
-
// TODO: stats provider
|
|
699
|
-
}) {
|
|
558
|
+
}, strict = false, }) {
|
|
700
559
|
this.routingStrategy = routingStrategy;
|
|
701
560
|
this.gatewaysProvider = gatewaysProvider;
|
|
702
|
-
this.httpClient = httpClient;
|
|
703
561
|
this.emitter = new WayfinderEmitter(events);
|
|
704
562
|
this.verifyData =
|
|
705
563
|
verificationStrategy.verifyData.bind(verificationStrategy);
|
|
@@ -717,7 +575,6 @@ class Wayfinder {
|
|
|
717
575
|
};
|
|
718
576
|
// create a wayfinder client with the routing strategy and gateways provider
|
|
719
577
|
this.request = (0, exports.createWayfinderClient)({
|
|
720
|
-
httpClient,
|
|
721
578
|
selectGateway: async () => {
|
|
722
579
|
return this.routingStrategy.selectGateway({
|
|
723
580
|
gateways: await this.gatewaysProvider.getGateways(),
|
package/lib/cjs/version.js
CHANGED