@ar.io/sdk 3.12.2 → 3.13.0-beta.1

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.
Files changed (68) hide show
  1. package/README.md +28 -1
  2. package/bundles/web.bundle.min.js +142 -147
  3. package/lib/cjs/cli/commands/arnsPurchaseCommands.js +10 -0
  4. package/lib/cjs/cli/options.js +5 -0
  5. package/lib/cjs/cli/utils.js +4 -0
  6. package/lib/cjs/common/index.js +2 -0
  7. package/lib/cjs/common/io.js +4 -0
  8. package/lib/cjs/common/turbo.js +2 -1
  9. package/lib/cjs/common/wayfinder/gateways/network.js +48 -0
  10. package/lib/cjs/common/wayfinder/gateways/simple-cache.js +35 -0
  11. package/lib/cjs/common/wayfinder/gateways/static.js +13 -0
  12. package/lib/cjs/common/wayfinder/index.js +48 -0
  13. package/lib/cjs/common/wayfinder/routing/strategies/ping.js +72 -0
  14. package/lib/cjs/common/wayfinder/routing/strategies/preferred-with-fallback.js +50 -0
  15. package/lib/cjs/common/wayfinder/routing/strategies/random.js +13 -0
  16. package/lib/cjs/common/wayfinder/routing/strategies/round-robin.js +42 -0
  17. package/lib/cjs/common/wayfinder/routing/strategies/static.js +29 -0
  18. package/lib/cjs/common/wayfinder/verification/strategies/data-root-verifier.js +110 -0
  19. package/lib/cjs/common/wayfinder/verification/strategies/hash-verifier.js +27 -0
  20. package/lib/cjs/common/wayfinder/verification/trusted.js +125 -0
  21. package/lib/cjs/common/wayfinder/wayfinder.js +508 -0
  22. package/lib/cjs/types/wayfinder.js +3 -0
  23. package/lib/cjs/utils/hash.js +83 -31
  24. package/lib/cjs/version.js +1 -1
  25. package/lib/esm/cli/commands/arnsPurchaseCommands.js +11 -1
  26. package/lib/esm/cli/options.js +5 -0
  27. package/lib/esm/cli/utils.js +3 -0
  28. package/lib/esm/common/index.js +2 -0
  29. package/lib/esm/common/io.js +4 -0
  30. package/lib/esm/common/turbo.js +2 -1
  31. package/lib/esm/common/wayfinder/gateways/network.js +44 -0
  32. package/lib/esm/common/wayfinder/gateways/simple-cache.js +31 -0
  33. package/lib/esm/common/wayfinder/gateways/static.js +9 -0
  34. package/lib/esm/common/wayfinder/index.js +32 -0
  35. package/lib/esm/common/wayfinder/routing/strategies/ping.js +68 -0
  36. package/lib/esm/common/wayfinder/routing/strategies/preferred-with-fallback.js +46 -0
  37. package/lib/esm/common/wayfinder/routing/strategies/random.js +9 -0
  38. package/lib/esm/common/wayfinder/routing/strategies/round-robin.js +38 -0
  39. package/lib/esm/common/wayfinder/routing/strategies/static.js +25 -0
  40. package/lib/esm/common/wayfinder/verification/strategies/data-root-verifier.js +102 -0
  41. package/lib/esm/common/wayfinder/verification/strategies/hash-verifier.js +23 -0
  42. package/lib/esm/common/wayfinder/verification/trusted.js +121 -0
  43. package/lib/esm/common/wayfinder/wayfinder.js +499 -0
  44. package/lib/esm/types/wayfinder.js +2 -0
  45. package/lib/esm/utils/hash.js +74 -27
  46. package/lib/esm/version.js +1 -1
  47. package/lib/types/cli/options.d.ts +4 -0
  48. package/lib/types/cli/utils.d.ts +3 -0
  49. package/lib/types/common/index.d.ts +1 -0
  50. package/lib/types/common/turbo.d.ts +2 -1
  51. package/lib/types/common/wayfinder/gateways/network.d.ts +33 -0
  52. package/lib/types/common/wayfinder/gateways/simple-cache.d.ts +31 -0
  53. package/lib/types/common/wayfinder/gateways/static.d.ts +23 -0
  54. package/lib/types/common/wayfinder/index.d.ts +27 -0
  55. package/lib/types/common/wayfinder/routing/strategies/ping.d.ts +27 -0
  56. package/lib/types/common/wayfinder/routing/strategies/preferred-with-fallback.d.ts +31 -0
  57. package/lib/types/common/wayfinder/routing/strategies/random.d.ts +21 -0
  58. package/lib/types/common/wayfinder/routing/strategies/round-robin.d.ts +29 -0
  59. package/lib/types/common/wayfinder/routing/strategies/static.d.ts +29 -0
  60. package/lib/types/common/wayfinder/verification/strategies/data-root-verifier.d.ts +27 -0
  61. package/lib/types/common/wayfinder/verification/strategies/hash-verifier.d.ts +26 -0
  62. package/lib/types/common/wayfinder/verification/trusted.d.ts +51 -0
  63. package/lib/types/common/wayfinder/wayfinder.d.ts +257 -0
  64. package/lib/types/types/io.d.ts +4 -0
  65. package/lib/types/types/wayfinder.d.ts +66 -0
  66. package/lib/types/utils/hash.d.ts +8 -4
  67. package/lib/types/version.d.ts +1 -1
  68. package/package.json +2 -1
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hashBufferToB64Url = exports.hashReadableStreamToB64Url = exports.hashReadableToB64Url = void 0;
6
+ exports.convertDataStreamToDataRoot = exports.hashDataStreamToB64Url = exports.readableStreamToAsyncIterable = exports.isReadableStream = exports.isAsyncIterable = void 0;
4
7
  /**
5
8
  * Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
6
9
  *
@@ -16,41 +19,90 @@ exports.hashBufferToB64Url = exports.hashReadableStreamToB64Url = exports.hashRe
16
19
  * See the License for the specific language governing permissions and
17
20
  * limitations under the License.
18
21
  */
22
+ const arweave_1 = __importDefault(require("arweave"));
23
+ const merkle_js_1 = require("arweave/node/lib/merkle.js");
19
24
  const crypto_1 = require("crypto");
20
25
  const base64_js_1 = require("./base64.js");
21
- const hashReadableToB64Url = (stream, algorithm = 'sha256') => {
22
- return new Promise((resolve, reject) => {
23
- const hash = (0, crypto_1.createHash)(algorithm);
24
- stream.on('data', (chunk) => hash.update(chunk));
25
- stream.on('end', () => resolve((0, base64_js_1.toB64Url)(hash.digest())));
26
- stream.on('error', (err) => reject(err));
27
- });
26
+ const isAsyncIterable = (x) => {
27
+ return x && typeof x[Symbol.asyncIterator] === 'function';
28
+ };
29
+ exports.isAsyncIterable = isAsyncIterable;
30
+ const isReadableStream = (x) => {
31
+ return x && typeof x.getReader === 'function';
28
32
  };
29
- exports.hashReadableToB64Url = hashReadableToB64Url;
30
- const hashReadableStreamToB64Url = (stream, algorithm = 'sha256') => {
31
- return new Promise((resolve, reject) => {
32
- const hash = (0, crypto_1.createHash)(algorithm);
33
+ exports.isReadableStream = isReadableStream;
34
+ // convert ReadableStream to async iterable
35
+ const readableStreamToAsyncIterable = (stream) => ({
36
+ async *[Symbol.asyncIterator]() {
33
37
  const reader = stream.getReader();
34
- const read = async () => {
35
- try {
38
+ try {
39
+ while (true) {
36
40
  const { done, value } = await reader.read();
37
- if (done) {
38
- resolve((0, base64_js_1.toB64Url)(hash.digest()));
39
- }
40
- else {
41
- hash.update(value);
42
- read();
43
- }
41
+ if (done)
42
+ break;
43
+ if (value !== undefined)
44
+ yield value;
44
45
  }
45
- catch (err) {
46
- reject(err);
47
- }
48
- };
49
- read().catch(reject);
50
- });
46
+ }
47
+ finally {
48
+ reader.releaseLock();
49
+ }
50
+ },
51
+ });
52
+ exports.readableStreamToAsyncIterable = readableStreamToAsyncIterable;
53
+ const hashDataStreamToB64Url = async (stream, algorithm = 'sha256') => {
54
+ const asyncIterable = (0, exports.isAsyncIterable)(stream)
55
+ ? stream
56
+ : (0, exports.readableStreamToAsyncIterable)(stream);
57
+ const hash = (0, crypto_1.createHash)(algorithm);
58
+ for await (const chunk of asyncIterable) {
59
+ hash.update(chunk);
60
+ }
61
+ return (0, base64_js_1.toB64Url)(hash.digest());
51
62
  };
52
- exports.hashReadableStreamToB64Url = hashReadableStreamToB64Url;
53
- const hashBufferToB64Url = (buffer, algorithm = 'sha256') => {
54
- return (0, base64_js_1.toB64Url)((0, crypto_1.createHash)(algorithm).update(buffer).digest());
63
+ exports.hashDataStreamToB64Url = hashDataStreamToB64Url;
64
+ const convertDataStreamToDataRoot = async ({ dataStream, }) => {
65
+ const chunks = [];
66
+ let leftover = new Uint8Array(0);
67
+ let cursor = 0;
68
+ const asyncIterable = (0, exports.isAsyncIterable)(dataStream)
69
+ ? dataStream
70
+ : (0, exports.readableStreamToAsyncIterable)(dataStream);
71
+ for await (const data of asyncIterable) {
72
+ const inputChunk = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
73
+ const combined = new Uint8Array(leftover.length + inputChunk.length);
74
+ combined.set(leftover, 0);
75
+ combined.set(inputChunk, leftover.length);
76
+ let startIndex = 0;
77
+ while (combined.length - startIndex >= merkle_js_1.MAX_CHUNK_SIZE) {
78
+ let chunkSize = merkle_js_1.MAX_CHUNK_SIZE;
79
+ const remainderAfterThis = combined.length - startIndex - merkle_js_1.MAX_CHUNK_SIZE;
80
+ if (remainderAfterThis > 0 && remainderAfterThis < merkle_js_1.MIN_CHUNK_SIZE) {
81
+ chunkSize = Math.ceil((combined.length - startIndex) / 2);
82
+ }
83
+ const chunkData = combined.slice(startIndex, startIndex + chunkSize);
84
+ const dataHash = await arweave_1.default.crypto.hash(chunkData);
85
+ chunks.push({
86
+ dataHash,
87
+ minByteRange: cursor,
88
+ maxByteRange: cursor + chunkSize,
89
+ });
90
+ cursor += chunkSize;
91
+ startIndex += chunkSize;
92
+ }
93
+ leftover = combined.slice(startIndex);
94
+ }
95
+ if (leftover.length > 0) {
96
+ // TODO: ensure a web friendly crypto hash function is used in web
97
+ const dataHash = await arweave_1.default.crypto.hash(leftover);
98
+ chunks.push({
99
+ dataHash,
100
+ minByteRange: cursor,
101
+ maxByteRange: cursor + leftover.length,
102
+ });
103
+ }
104
+ const leaves = await (0, merkle_js_1.generateLeaves)(chunks);
105
+ const root = await (0, merkle_js_1.buildLayers)(leaves);
106
+ return (0, base64_js_1.toB64Url)(Buffer.from(root.id));
55
107
  };
56
- exports.hashBufferToB64Url = hashBufferToB64Url;
108
+ exports.convertDataStreamToDataRoot = convertDataStreamToDataRoot;
@@ -17,4 +17,4 @@
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.version = void 0;
19
19
  // AUTOMATICALLY GENERATED FILE - DO NOT TOUCH
20
- exports.version = '3.12.2';
20
+ exports.version = '3.13.0-beta.1';
@@ -1,10 +1,11 @@
1
- import { assertConfirmationPrompt, assertEnoughBalanceForArNSPurchase, customTagsFromOptions, fundFromFromOptions, positiveIntegerFromOptions, recordTypeFromOptions, requiredPositiveIntegerFromOptions, requiredStringFromOptions, stringArrayFromOptions, writeARIOFromOptions, } from '../utils.js';
1
+ import { assertConfirmationPrompt, assertEnoughBalanceForArNSPurchase, customTagsFromOptions, fundFromFromOptions, positiveIntegerFromOptions, recordTypeFromOptions, referrerFromOptions, requiredPositiveIntegerFromOptions, requiredStringFromOptions, stringArrayFromOptions, writeARIOFromOptions, } from '../utils.js';
2
2
  export async function buyRecordCLICommand(o) {
3
3
  const { ario, signerAddress } = writeARIOFromOptions(o);
4
4
  const name = requiredStringFromOptions(o, 'name');
5
5
  const type = recordTypeFromOptions(o);
6
6
  const years = positiveIntegerFromOptions(o, 'years');
7
7
  const fundFrom = fundFromFromOptions(o);
8
+ const referrer = referrerFromOptions(o);
8
9
  const processId = o.processId;
9
10
  if (processId === undefined) {
10
11
  // TODO: Spawn ANT process, register it to ANT registry, get process ID
@@ -38,12 +39,14 @@ export async function buyRecordCLICommand(o) {
38
39
  years,
39
40
  fundFrom: fundFromFromOptions(o),
40
41
  paidBy: stringArrayFromOptions(o, 'paidBy'),
42
+ referrer,
41
43
  }, customTagsFromOptions(o));
42
44
  }
43
45
  export async function upgradeRecordCLICommand(o) {
44
46
  const name = requiredStringFromOptions(o, 'name');
45
47
  const { ario, signerAddress } = writeARIOFromOptions(o);
46
48
  const fundFrom = fundFromFromOptions(o);
49
+ const referrer = referrerFromOptions(o);
47
50
  if (!o.skipConfirmation) {
48
51
  const existingRecord = await ario.getArNSRecord({
49
52
  name,
@@ -70,12 +73,14 @@ export async function upgradeRecordCLICommand(o) {
70
73
  name,
71
74
  fundFrom,
72
75
  paidBy: stringArrayFromOptions(o, 'paidBy'),
76
+ referrer,
73
77
  });
74
78
  }
75
79
  export async function extendLeaseCLICommand(o) {
76
80
  const name = requiredStringFromOptions(o, 'name');
77
81
  const years = requiredPositiveIntegerFromOptions(o, 'years');
78
82
  const fundFrom = fundFromFromOptions(o);
83
+ const referrer = referrerFromOptions(o);
79
84
  const { ario, signerAddress } = writeARIOFromOptions(o);
80
85
  if (!o.skipConfirmation) {
81
86
  const existingRecord = await ario.getArNSRecord({
@@ -104,12 +109,14 @@ export async function extendLeaseCLICommand(o) {
104
109
  name,
105
110
  years,
106
111
  paidBy: stringArrayFromOptions(o, 'paidBy'),
112
+ referrer,
107
113
  }, customTagsFromOptions(o));
108
114
  }
109
115
  export async function increaseUndernameLimitCLICommand(o) {
110
116
  const name = requiredStringFromOptions(o, 'name');
111
117
  const increaseCount = requiredPositiveIntegerFromOptions(o, 'increaseCount');
112
118
  const fundFrom = fundFromFromOptions(o);
119
+ const referrer = referrerFromOptions(o);
113
120
  const { ario, signerAddress } = writeARIOFromOptions(o);
114
121
  if (!o.skipConfirmation) {
115
122
  const existingRecord = await ario.getArNSRecord({
@@ -135,11 +142,13 @@ export async function increaseUndernameLimitCLICommand(o) {
135
142
  name,
136
143
  increaseCount,
137
144
  paidBy: stringArrayFromOptions(o, 'paidBy'),
145
+ referrer,
138
146
  }, customTagsFromOptions(o));
139
147
  }
140
148
  export async function requestPrimaryNameCLICommand(o) {
141
149
  const { ario, signerAddress } = writeARIOFromOptions(o);
142
150
  const fundFrom = fundFromFromOptions(o);
151
+ const referrer = referrerFromOptions(o);
143
152
  const name = requiredStringFromOptions(o, 'name');
144
153
  if (!o.skipConfirmation) {
145
154
  // TODO: Assert name requested is not already owned?
@@ -160,5 +169,6 @@ export async function requestPrimaryNameCLICommand(o) {
160
169
  name,
161
170
  fundFrom,
162
171
  paidBy: stringArrayFromOptions(o, 'paidBy'),
172
+ referrer,
163
173
  }, customTagsFromOptions(o));
164
174
  }
@@ -283,6 +283,10 @@ export const optionMap = {
283
283
  description: 'Addresses to pay for the interaction',
284
284
  type: 'array',
285
285
  },
286
+ referrer: {
287
+ alias: '--referrer <referrer>',
288
+ description: 'The referrer for ArNS purchase tracking',
289
+ },
286
290
  };
287
291
  export const walletOptions = [
288
292
  optionMap.walletFile,
@@ -306,6 +310,7 @@ export const arnsPurchaseOptions = [
306
310
  optionMap.fundFrom,
307
311
  optionMap.paidBy,
308
312
  optionMap.paymentUrl,
313
+ optionMap.referrer,
309
314
  ];
310
315
  export const epochOptions = [optionMap.epochIndex, optionMap.timestamp];
311
316
  export const addressAndVaultIdOptions = [optionMap.address, optionMap.vaultId];
@@ -458,6 +458,9 @@ export function fundFromFromOptions(o) {
458
458
  }
459
459
  return o.fundFrom ?? 'balance';
460
460
  }
461
+ export function referrerFromOptions(o) {
462
+ return o.referrer;
463
+ }
461
464
  export function assertLockLengthInRange(lockLengthMs, assertMin = true) {
462
465
  const minLockLengthMs = 1209600000; // 14 days
463
466
  const maxLockLengthMs = 378432000000; // ~12 years
@@ -22,3 +22,5 @@ export * from './faucet.js';
22
22
  // ao
23
23
  export * from './io.js';
24
24
  export * from './contracts/ao-process.js';
25
+ // wayfinder
26
+ export * from './wayfinder/index.js';
@@ -981,6 +981,7 @@ export class ARIOWriteable extends ARIOReadable {
981
981
  { name: 'Process-Id', value: params.processId },
982
982
  { name: 'Purchase-Type', value: params.type || 'lease' },
983
983
  { name: 'Fund-From', value: params.fundFrom },
984
+ { name: 'Referrer', value: params.referrer },
984
985
  ];
985
986
  return this.process.send({
986
987
  signer: this.signer,
@@ -1011,6 +1012,7 @@ export class ARIOWriteable extends ARIOReadable {
1011
1012
  { name: 'Action', value: 'Upgrade-Name' },
1012
1013
  { name: 'Name', value: params.name },
1013
1014
  { name: 'Fund-From', value: params.fundFrom },
1015
+ { name: 'Referrer', value: params.referrer },
1014
1016
  ];
1015
1017
  return this.process.send({
1016
1018
  signer: this.signer,
@@ -1044,6 +1046,7 @@ export class ARIOWriteable extends ARIOReadable {
1044
1046
  { name: 'Name', value: params.name },
1045
1047
  { name: 'Years', value: params.years.toString() },
1046
1048
  { name: 'Fund-From', value: params.fundFrom },
1049
+ { name: 'Referrer', value: params.referrer },
1047
1050
  ];
1048
1051
  return this.process.send({
1049
1052
  signer: this.signer,
@@ -1068,6 +1071,7 @@ export class ARIOWriteable extends ARIOReadable {
1068
1071
  { name: 'Name', value: params.name },
1069
1072
  { name: 'Quantity', value: params.increaseCount.toString() },
1070
1073
  { name: 'Fund-From', value: params.fundFrom },
1074
+ { name: 'Referrer', value: params.referrer },
1071
1075
  ];
1072
1076
  return this.process.send({
1073
1077
  signer: this.signer,
@@ -148,7 +148,7 @@ export class TurboArNSPaymentProviderAuthenticated extends TurboArNSPaymentProvi
148
148
  }
149
149
  this.signer = signer;
150
150
  }
151
- async initiateArNSPurchase({ intent, name, quantity, type, processId, years, paidBy = [], }) {
151
+ async initiateArNSPurchase({ intent, name, quantity, type, processId, years, paidBy = [], referrer, }) {
152
152
  // Signer check is implicitly handled by requiring it in the constructor
153
153
  const url = urlWithSearchParams({
154
154
  baseUrl: `${this.paymentUrl}/v1/arns/purchase/${intent}/${name}`,
@@ -158,6 +158,7 @@ export class TurboArNSPaymentProviderAuthenticated extends TurboArNSPaymentProvi
158
158
  type,
159
159
  years,
160
160
  paidBy,
161
+ referrer,
161
162
  },
162
163
  });
163
164
  const headers = await signedRequestHeadersFromSigner({
@@ -0,0 +1,44 @@
1
+ import { ARIO } from '../../io.js';
2
+ export class NetworkGatewaysProvider {
3
+ ario;
4
+ sortBy;
5
+ sortOrder;
6
+ limit;
7
+ filter;
8
+ constructor({ ario = ARIO.mainnet(), sortBy = 'operatorStake', sortOrder = 'desc', limit = 1000, filter = (g) => g.status === 'joined', }) {
9
+ this.ario = ario;
10
+ this.sortBy = sortBy;
11
+ this.sortOrder = sortOrder;
12
+ this.limit = limit;
13
+ this.filter = filter;
14
+ }
15
+ async getGateways() {
16
+ let cursor;
17
+ let attempts = 0;
18
+ const gateways = [];
19
+ do {
20
+ try {
21
+ const { items: newGateways = [], nextCursor } = await this.ario.getGateways({
22
+ limit: 1000,
23
+ cursor,
24
+ sortBy: this.sortBy,
25
+ sortOrder: this.sortOrder,
26
+ });
27
+ gateways.push(...newGateways);
28
+ cursor = nextCursor;
29
+ attempts = 0; // reset attempts if we get a new cursor
30
+ }
31
+ catch (error) {
32
+ console.error('Error fetching gateways', {
33
+ cursor,
34
+ attempts,
35
+ error,
36
+ });
37
+ attempts++;
38
+ }
39
+ } while (cursor !== undefined && attempts < 3);
40
+ // filter out any gateways that are not joined
41
+ const filteredGateways = gateways.filter(this.filter).slice(0, this.limit);
42
+ return filteredGateways.map((g) => new URL(`${g.settings.protocol}://${g.settings.fqdn}:${g.settings.port}`));
43
+ }
44
+ }
@@ -0,0 +1,31 @@
1
+ import { Logger } from '../../../web/index.js';
2
+ export class SimpleCacheGatewaysProvider {
3
+ gatewaysProvider;
4
+ ttlSeconds;
5
+ lastUpdated;
6
+ gatewaysCache;
7
+ logger;
8
+ constructor({ gatewaysProvider, ttlSeconds = 60 * 60, // 1 hour
9
+ logger = Logger.default, }) {
10
+ this.gatewaysCache = [];
11
+ this.gatewaysProvider = gatewaysProvider;
12
+ this.ttlSeconds = ttlSeconds;
13
+ this.logger = logger;
14
+ }
15
+ async getGateways() {
16
+ const now = Date.now();
17
+ if (this.gatewaysCache.length === 0 ||
18
+ now - this.lastUpdated > this.ttlSeconds * 1000) {
19
+ try {
20
+ // preserve the cache if the fetch fails
21
+ const allGateways = await this.gatewaysProvider.getGateways();
22
+ this.gatewaysCache = allGateways;
23
+ this.lastUpdated = now;
24
+ }
25
+ catch (error) {
26
+ this.logger.error('Error fetching gateways', error);
27
+ }
28
+ }
29
+ return this.gatewaysCache;
30
+ }
31
+ }
@@ -0,0 +1,9 @@
1
+ export class StaticGatewaysProvider {
2
+ gateways;
3
+ constructor({ gateways }) {
4
+ this.gateways = gateways.map((g) => new URL(g));
5
+ }
6
+ async getGateways() {
7
+ return this.gateways;
8
+ }
9
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export * from './wayfinder.js';
17
+ // routing strategies
18
+ export * from './routing/strategies/random.js';
19
+ export * from './routing/strategies/static.js';
20
+ export * from './routing/strategies/ping.js';
21
+ export * from './routing/strategies/round-robin.js';
22
+ export * from './routing/strategies/preferred-with-fallback.js';
23
+ // gateways providers
24
+ export * from './gateways/network.js';
25
+ export * from './gateways/simple-cache.js';
26
+ export * from './gateways/static.js';
27
+ // trusted gateways
28
+ export * from './verification/trusted.js';
29
+ // hash providers
30
+ export * from './verification/strategies/data-root-verifier.js';
31
+ export * from './verification/strategies/hash-verifier.js';
32
+ // TODO: signature verification
@@ -0,0 +1,68 @@
1
+ export class FastestPingRoutingStrategy {
2
+ timeoutMs;
3
+ probePath;
4
+ constructor({ timeoutMs = 500, probePath = '/ar-io/info', // TODO: limit to allowed /ar-io and arweave node endpoints
5
+ } = {}) {
6
+ this.timeoutMs = timeoutMs;
7
+ this.probePath = probePath;
8
+ }
9
+ async selectGateway({ gateways }) {
10
+ if (gateways.length === 0) {
11
+ throw new Error('No gateways provided');
12
+ }
13
+ try {
14
+ const results = await Promise.allSettled(gateways.map(async (gateway) => {
15
+ try {
16
+ const startTime = Date.now();
17
+ const response = await fetch(`${gateway.toString().replace(/\/$/, '')}${this.probePath}`, {
18
+ method: 'HEAD',
19
+ signal: AbortSignal.timeout(this.timeoutMs),
20
+ });
21
+ const endTime = Date.now();
22
+ const durationMs = endTime - startTime;
23
+ return {
24
+ gateway,
25
+ status: response.status,
26
+ durationMs,
27
+ error: null,
28
+ };
29
+ }
30
+ catch (error) {
31
+ // Handle network errors
32
+ return {
33
+ gateway,
34
+ status: 'rejected',
35
+ durationMs: Infinity,
36
+ error,
37
+ };
38
+ }
39
+ }));
40
+ // Process results
41
+ const processedResults = results.map((result, index) => {
42
+ if (result.status === 'fulfilled') {
43
+ return result.value;
44
+ }
45
+ else {
46
+ return {
47
+ gateway: gateways[index],
48
+ status: 'rejected',
49
+ durationMs: Infinity,
50
+ error: result.reason,
51
+ };
52
+ }
53
+ });
54
+ // Filter healthy gateways and sort by latency
55
+ const healthyGateways = processedResults
56
+ .filter((result) => result.status === 200)
57
+ .sort((a, b) => a.durationMs - b.durationMs);
58
+ if (healthyGateways.length > 0) {
59
+ return healthyGateways[0].gateway;
60
+ }
61
+ throw new Error('No healthy gateways found');
62
+ }
63
+ catch (error) {
64
+ throw new Error('Failed to ping gateways: ' +
65
+ (error instanceof Error ? error.message : String(error)));
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,46 @@
1
+ import { Logger } from '../../../logger.js';
2
+ import { FastestPingRoutingStrategy } from './ping.js';
3
+ export class PreferredWithFallbackRoutingStrategy {
4
+ name = 'preferred-with-fallback';
5
+ preferredGateway;
6
+ fallbackStrategy;
7
+ logger;
8
+ constructor({ preferredGateway, fallbackStrategy = new FastestPingRoutingStrategy(), logger = Logger.default, }) {
9
+ try {
10
+ this.preferredGateway = new URL(preferredGateway);
11
+ }
12
+ catch (error) {
13
+ throw new Error(`Invalid URL provided for preferred gateway: ${preferredGateway}`);
14
+ }
15
+ this.fallbackStrategy = fallbackStrategy;
16
+ this.logger = logger;
17
+ }
18
+ async selectGateway({ gateways = [] }) {
19
+ this.logger.debug('Attempting to connect to preferred gateway', {
20
+ preferredGateway: this.preferredGateway.toString(),
21
+ });
22
+ try {
23
+ // Check if the preferred gateway is responsive
24
+ const response = await fetch(this.preferredGateway.toString(), {
25
+ method: 'HEAD',
26
+ signal: AbortSignal.timeout(1000),
27
+ });
28
+ if (response.ok) {
29
+ this.logger.debug('Successfully connected to preferred gateway', {
30
+ preferredGateway: this.preferredGateway.toString(),
31
+ });
32
+ return this.preferredGateway;
33
+ }
34
+ throw new Error(`Preferred gateway responded with status: ${response.status}`);
35
+ }
36
+ catch (error) {
37
+ this.logger.warn('Failed to connect to preferred gateway, falling back to alternative strategy', {
38
+ preferredGateway: this.preferredGateway.toString(),
39
+ error: error instanceof Error ? error.message : String(error),
40
+ fallbackStrategy: this.fallbackStrategy.constructor.name,
41
+ });
42
+ // Fall back to the provided routing strategy
43
+ return this.fallbackStrategy.selectGateway({ gateways });
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,9 @@
1
+ import { randomInt } from '../../../../utils/random.js';
2
+ export class RandomRoutingStrategy {
3
+ async selectGateway({ gateways }) {
4
+ if (gateways.length === 0) {
5
+ throw new Error('No gateways available');
6
+ }
7
+ return gateways[randomInt(0, gateways.length)];
8
+ }
9
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { Logger } from '../../../../common/logger.js';
17
+ export class RoundRobinRoutingStrategy {
18
+ gateways;
19
+ currentIndex;
20
+ logger;
21
+ constructor({ gateways, logger = Logger.default, }) {
22
+ this.gateways = gateways;
23
+ this.currentIndex = 0;
24
+ this.logger = logger;
25
+ }
26
+ // provided gateways are ignored
27
+ async selectGateway({ gateways = [], } = {}) {
28
+ if (gateways.length > 0) {
29
+ this.logger.warn('RoundRobinRoutingStrategy does not accept provided gateways. Ignoring provided gateways...', {
30
+ providedGateways: gateways.length,
31
+ internalGateways: this.gateways,
32
+ });
33
+ }
34
+ const gateway = this.gateways[this.currentIndex];
35
+ this.currentIndex = (this.currentIndex + 1) % this.gateways.length;
36
+ return gateway;
37
+ }
38
+ }
@@ -0,0 +1,25 @@
1
+ import { Logger } from '../../../logger.js';
2
+ export class StaticRoutingStrategy {
3
+ name = 'static';
4
+ gateway;
5
+ logger;
6
+ constructor({ gateway, logger = Logger.default, }) {
7
+ try {
8
+ this.gateway = new URL(gateway);
9
+ }
10
+ catch (error) {
11
+ throw new Error(`Invalid URL provided for static gateway: ${gateway}`);
12
+ }
13
+ this.logger = logger;
14
+ }
15
+ // provided gateways are ignored
16
+ async selectGateway({ gateways = [], } = {}) {
17
+ if (gateways.length > 0) {
18
+ this.logger.warn('StaticRoutingStrategy does not accept provided gateways. Ignoring provided gateways...', {
19
+ providedGateways: gateways.length,
20
+ internalGateway: this.gateway,
21
+ });
22
+ }
23
+ return this.gateway;
24
+ }
25
+ }