@ar.io/sdk 3.12.0-beta.3 → 3.12.0-beta.5
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 +92 -233
- package/lib/cjs/version.js +1 -1
- package/lib/esm/common/wayfinder/wayfinder.js +92 -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,72 @@ 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
|
+
// follow redirects as gateways use sandboxing on /txId requests
|
|
309
|
+
redirect: 'follow',
|
|
310
|
+
mode: 'cors',
|
|
311
|
+
// allow requestor to override and any additional request configuration
|
|
312
|
+
...init,
|
|
313
|
+
});
|
|
314
|
+
// TODO: update any caching we use for the request and gateway response
|
|
322
315
|
logger?.debug(`Successfully routed request to gateway`, {
|
|
323
316
|
redirectUrl: redirectUrl.toString(),
|
|
324
|
-
originalUrl
|
|
317
|
+
originalUrl,
|
|
325
318
|
});
|
|
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
|
-
}
|
|
319
|
+
// return the response right away if no redirect was made
|
|
320
|
+
if (redirectUrl.toString() === originalUrl) {
|
|
321
|
+
return response;
|
|
322
|
+
}
|
|
323
|
+
// return the response right away if no verification is needed or if there is no body
|
|
324
|
+
if (!verifyData) {
|
|
325
|
+
return response;
|
|
326
|
+
}
|
|
327
|
+
// the txId is either in the response headers or the path of the request as the first parameter
|
|
328
|
+
const txId = response.headers.get('x-arns-resolved-id') ??
|
|
329
|
+
redirectUrl.pathname.split('/')[1];
|
|
330
|
+
const contentLength = +(response.headers.get('content-length') ?? 0);
|
|
331
|
+
if (!exports.txIdRegex.test(txId)) {
|
|
332
|
+
// No transaction ID found, skip verification
|
|
333
|
+
logger?.debug('No transaction ID found, skipping verification', {
|
|
334
|
+
redirectUrl: redirectUrl.toString(),
|
|
335
|
+
originalUrl,
|
|
336
|
+
});
|
|
337
|
+
emitter?.emit('verification-skipped', {
|
|
338
|
+
originalUrl,
|
|
339
|
+
});
|
|
340
|
+
return response;
|
|
341
|
+
}
|
|
342
|
+
emitter?.emit('identified-transaction-id', {
|
|
343
|
+
originalUrl,
|
|
344
|
+
selectedGateway: redirectUrl.toString(),
|
|
345
|
+
txId,
|
|
346
|
+
});
|
|
347
|
+
if (!response.body) {
|
|
348
|
+
logger?.debug('No body, skipping verification', {
|
|
349
|
+
redirectUrl: redirectUrl.toString(),
|
|
350
|
+
originalUrl,
|
|
351
|
+
});
|
|
352
|
+
emitter?.emit('verification-skipped', {
|
|
353
|
+
originalUrl,
|
|
354
|
+
});
|
|
355
|
+
return response;
|
|
482
356
|
}
|
|
483
|
-
|
|
484
|
-
|
|
357
|
+
const verifiedStream = tapAndVerifyStream({
|
|
358
|
+
originalStream: response.body,
|
|
359
|
+
contentLength,
|
|
360
|
+
verifyData,
|
|
361
|
+
txId,
|
|
362
|
+
emitter,
|
|
363
|
+
strict,
|
|
364
|
+
});
|
|
365
|
+
// wrap the response with the verified stream
|
|
366
|
+
return new Response(verifiedStream, {
|
|
367
|
+
status: response.status,
|
|
368
|
+
statusText: response.statusText,
|
|
369
|
+
// TODO: we could add identified transaction id to the headers here, but it would be changing information from the original response
|
|
370
|
+
headers: response.headers,
|
|
371
|
+
});
|
|
485
372
|
}
|
|
486
373
|
catch (error) {
|
|
487
374
|
logger?.debug('Failed to route request', {
|
|
@@ -494,6 +381,12 @@ const createWayfinderClient = ({ httpClient, resolveUrl, verifyData, selectGatew
|
|
|
494
381
|
if (i < maxRetries - 1) {
|
|
495
382
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
496
383
|
}
|
|
384
|
+
else {
|
|
385
|
+
emitter?.emit('routing-failed', {
|
|
386
|
+
originalUrl,
|
|
387
|
+
error,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
497
390
|
}
|
|
498
391
|
}
|
|
499
392
|
throw new Error('Failed to route request after max retries', {
|
|
@@ -503,38 +396,13 @@ const createWayfinderClient = ({ httpClient, resolveUrl, verifyData, selectGatew
|
|
|
503
396
|
},
|
|
504
397
|
});
|
|
505
398
|
};
|
|
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
|
-
});
|
|
399
|
+
return wayfinderRedirect;
|
|
519
400
|
};
|
|
520
401
|
exports.createWayfinderClient = createWayfinderClient;
|
|
521
402
|
/**
|
|
522
403
|
* 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
404
|
*/
|
|
527
405
|
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
406
|
/**
|
|
539
407
|
* The gateways provider is responsible for providing the list of gateways to use for routing requests.
|
|
540
408
|
*
|
|
@@ -575,8 +443,7 @@ class Wayfinder {
|
|
|
575
443
|
*/
|
|
576
444
|
resolveUrl;
|
|
577
445
|
/**
|
|
578
|
-
*
|
|
579
|
-
* A wrapped http client that supports ar:// protocol. If a verification strategy is provided,
|
|
446
|
+
* A wrapped fetch function that supports ar:// protocol. If a verification strategy is provided,
|
|
580
447
|
* the request will be verified and events will be emitted as the request is processed.
|
|
581
448
|
*
|
|
582
449
|
* @example
|
|
@@ -596,9 +463,6 @@ class Wayfinder {
|
|
|
596
463
|
* // request a transaction id
|
|
597
464
|
* const response = await wayfinder.request('ar://1234567890')
|
|
598
465
|
*
|
|
599
|
-
* // request a transaction id with a custom http client
|
|
600
|
-
* const response = await wayfinder.request('ar://1234567890')
|
|
601
|
-
*
|
|
602
466
|
* // Set strict mode to true to make verification blocking
|
|
603
467
|
* const wayfinder = new Wayfinder({
|
|
604
468
|
* strict: true,
|
|
@@ -664,14 +528,13 @@ class Wayfinder {
|
|
|
664
528
|
emitter;
|
|
665
529
|
/**
|
|
666
530
|
* The constructor for the wayfinder
|
|
667
|
-
* @param httpClient - the http client to use for requests
|
|
668
531
|
* @param routingStrategy - the routing strategy to use for requests
|
|
669
532
|
* @param verificationStrategy - the verification strategy to use for requests
|
|
670
533
|
* @param gatewaysProvider - the gateways provider to use for routing requests
|
|
671
534
|
* @param logger - the logger to use for logging
|
|
672
535
|
* @param strict - if true, verification will be blocking and will fail requests if verification fails; if false, verification will be non-blocking
|
|
673
536
|
*/
|
|
674
|
-
constructor({
|
|
537
|
+
constructor({ logger = logger_js_1.Logger.default, gatewaysProvider = new simple_cache_js_1.SimpleCacheGatewaysProvider({
|
|
675
538
|
gatewaysProvider: new network_js_1.NetworkGatewaysProvider({
|
|
676
539
|
ario: io_js_1.ARIO.mainnet(),
|
|
677
540
|
}),
|
|
@@ -694,12 +557,9 @@ class Wayfinder {
|
|
|
694
557
|
onVerificationProgress: (event) => {
|
|
695
558
|
logger.debug('Verification progress!', event);
|
|
696
559
|
},
|
|
697
|
-
}, strict = false,
|
|
698
|
-
// TODO: stats provider
|
|
699
|
-
}) {
|
|
560
|
+
}, strict = false, }) {
|
|
700
561
|
this.routingStrategy = routingStrategy;
|
|
701
562
|
this.gatewaysProvider = gatewaysProvider;
|
|
702
|
-
this.httpClient = httpClient;
|
|
703
563
|
this.emitter = new WayfinderEmitter(events);
|
|
704
564
|
this.verifyData =
|
|
705
565
|
verificationStrategy.verifyData.bind(verificationStrategy);
|
|
@@ -717,7 +577,6 @@ class Wayfinder {
|
|
|
717
577
|
};
|
|
718
578
|
// create a wayfinder client with the routing strategy and gateways provider
|
|
719
579
|
this.request = (0, exports.createWayfinderClient)({
|
|
720
|
-
httpClient,
|
|
721
580
|
selectGateway: async () => {
|
|
722
581
|
return this.routingStrategy.selectGateway({
|
|
723
582
|
gateways: await this.gatewaysProvider.getGateways(),
|
package/lib/cjs/version.js
CHANGED