@ar.io/sdk 3.11.0-alpha.1 → 3.11.0-alpha.11

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 (64) hide show
  1. package/README.md +52 -0
  2. package/bundles/web.bundle.min.js +119 -116
  3. package/lib/cjs/cli/cli.js +137 -122
  4. package/lib/cjs/cli/commands/readCommands.js +6 -0
  5. package/lib/cjs/cli/utils.js +4 -1
  6. package/lib/cjs/common/ant.js +5 -5
  7. package/lib/cjs/common/io.js +49 -3
  8. package/lib/cjs/common/wayfinder/gateways/trusted-gateways.js +106 -0
  9. package/lib/cjs/common/wayfinder/gateways.js +19 -8
  10. package/lib/cjs/common/wayfinder/index.js +9 -1
  11. package/lib/cjs/common/wayfinder/routers/priority.js +11 -20
  12. package/lib/cjs/common/wayfinder/routers/priority.test.js +5 -5
  13. package/lib/cjs/common/wayfinder/routers/random.js +2 -2
  14. package/lib/cjs/common/wayfinder/routers/random.test.js +5 -84
  15. package/lib/cjs/common/wayfinder/routers/{fixed.js → static.js} +5 -5
  16. package/lib/cjs/common/wayfinder/routers/{fixed.test.js → static.test.js} +4 -4
  17. package/lib/cjs/common/wayfinder/verification/data-root-verifier.js +139 -0
  18. package/lib/cjs/common/wayfinder/verification/hash-verifier.js +50 -0
  19. package/lib/cjs/common/wayfinder/wayfinder.js +459 -30
  20. package/lib/cjs/common/wayfinder/wayfinder.test.js +296 -49
  21. package/lib/cjs/types/wayfinder.js +1 -0
  22. package/lib/cjs/utils/ao.js +37 -0
  23. package/lib/cjs/utils/arweave.js +1 -1
  24. package/lib/cjs/utils/hash.js +56 -0
  25. package/lib/cjs/version.js +1 -1
  26. package/lib/esm/cli/cli.js +138 -123
  27. package/lib/esm/cli/commands/readCommands.js +5 -0
  28. package/lib/esm/cli/utils.js +4 -1
  29. package/lib/esm/common/ant.js +5 -5
  30. package/lib/esm/common/io.js +49 -3
  31. package/lib/esm/common/wayfinder/gateways/trusted-gateways.js +102 -0
  32. package/lib/esm/common/wayfinder/gateways.js +17 -6
  33. package/lib/esm/common/wayfinder/index.js +9 -1
  34. package/lib/esm/common/wayfinder/routers/priority.js +11 -20
  35. package/lib/esm/common/wayfinder/routers/priority.test.js +5 -5
  36. package/lib/esm/common/wayfinder/routers/random.js +2 -2
  37. package/lib/esm/common/wayfinder/routers/random.test.js +5 -84
  38. package/lib/esm/common/wayfinder/routers/{fixed.js → static.js} +3 -3
  39. package/lib/esm/common/wayfinder/routers/{fixed.test.js → static.test.js} +4 -4
  40. package/lib/esm/common/wayfinder/verification/data-root-verifier.js +130 -0
  41. package/lib/esm/common/wayfinder/verification/hash-verifier.js +46 -0
  42. package/lib/esm/common/wayfinder/wayfinder.js +453 -30
  43. package/lib/esm/common/wayfinder/wayfinder.test.js +297 -50
  44. package/lib/esm/types/wayfinder.js +1 -0
  45. package/lib/esm/utils/ao.js +37 -0
  46. package/lib/esm/utils/arweave.js +1 -1
  47. package/lib/esm/utils/hash.js +50 -0
  48. package/lib/esm/version.js +1 -1
  49. package/lib/types/cli/commands/readCommands.d.ts +1 -0
  50. package/lib/types/common/io.d.ts +7 -8
  51. package/lib/types/common/wayfinder/gateways/trusted-gateways.d.ts +51 -0
  52. package/lib/types/common/wayfinder/gateways.d.ts +16 -7
  53. package/lib/types/common/wayfinder/index.d.ts +5 -1
  54. package/lib/types/common/wayfinder/routers/priority.d.ts +8 -12
  55. package/lib/types/common/wayfinder/routers/{fixed.d.ts → static.d.ts} +3 -3
  56. package/lib/types/common/wayfinder/verification/data-root-verifier.d.ts +31 -0
  57. package/lib/types/common/wayfinder/verification/hash-verifier.d.ts +27 -0
  58. package/lib/types/common/wayfinder/wayfinder.d.ts +169 -17
  59. package/lib/types/types/io.d.ts +16 -1
  60. package/lib/types/types/wayfinder.d.ts +43 -0
  61. package/lib/types/utils/hash.d.ts +4 -0
  62. package/lib/types/version.d.ts +1 -1
  63. package/package.json +1 -1
  64. /package/lib/types/common/wayfinder/routers/{fixed.test.d.ts → static.test.d.ts} +0 -0
@@ -1,40 +1,226 @@
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 EventEmitter from 'node:events';
17
+ import { PassThrough, Readable } from 'node:stream';
1
18
  import { ARIO } from '../io.js';
2
- import { ARIOGatewaysProvider } from './gateways.js';
19
+ import { Logger } from '../logger.js';
20
+ import { NetworkGatewaysProvider, SimpleCacheGatewaysProvider, StaticGatewaysProvider, } from './gateways.js';
21
+ import { TrustedGatewaysHashProvider } from './gateways/trusted-gateways.js';
3
22
  import { RandomGatewayRouter } from './routers/random.js';
23
+ import { HashVerifier } from './verification/hash-verifier.js';
4
24
  // known regexes for wayfinder urls
5
25
  export const arnsRegex = /^[a-z0-9_-]{1,51}$/;
6
- export const txIdRegex = /^[a-z0-9]{43}$/;
26
+ export const txIdRegex = /^[A-Za-z0-9_-]{43}$/;
7
27
  /**
8
28
  * Core function to resolve a wayfinder url against a target gateway
9
29
  * @param originalUrl - the wayfinder url to resolve
10
30
  * @param targetGateway - the target gateway to resolve the url against
11
31
  * @returns the resolved url that can be used to make a request
12
32
  */
13
- export const resolveWayfinderUrl = ({ originalUrl, targetGateway, }) => {
33
+ export const resolveWayfinderUrl = async ({ originalUrl, targetGateway, logger, }) => {
14
34
  if (originalUrl.toString().startsWith('ar://')) {
35
+ logger?.debug(`Applying wayfinder routing protocol to ${originalUrl}`, {
36
+ originalUrl,
37
+ });
38
+ const targetGatewayUrl = new URL(await targetGateway());
39
+ logger?.debug(`Selected target gateway: ${targetGatewayUrl}`, {
40
+ originalUrl,
41
+ targetGateway: targetGatewayUrl,
42
+ });
15
43
  const [, path] = originalUrl.toString().split('ar://');
16
44
  // e.g. ar:///info should route to the info endpoint of the target gateway
17
45
  if (path.startsWith('/')) {
18
- return new URL(path.slice(1), targetGateway);
46
+ logger?.debug(`Routing to ${path.slice(1)} on ${targetGatewayUrl}`, {
47
+ originalUrl,
48
+ targetGateway: targetGatewayUrl,
49
+ });
50
+ return new URL(path.slice(1), targetGatewayUrl);
19
51
  }
20
52
  // TODO: this breaks 43 character named arns names - we should check a a local name cache list before resolving raw transaction ids
21
53
  if (txIdRegex.test(path)) {
22
54
  const [txId, ...rest] = path.split('/');
23
- return new URL(`${txId}${rest.join('/')}`, targetGateway);
55
+ return new URL(`${txId}${rest.join('/')}`, targetGatewayUrl);
24
56
  }
25
57
  if (arnsRegex.test(path)) {
26
58
  // TODO: tests to ensure arns names support query params and paths
27
59
  const [name, ...rest] = path.split('/');
28
- const targetGatewayUrl = new URL(targetGateway);
29
60
  const arnsUrl = `${targetGatewayUrl.protocol}//${name}.${targetGatewayUrl.hostname}${targetGatewayUrl.port ? `:${targetGatewayUrl.port}` : ''}`;
61
+ logger?.debug(`Routing to ${path} on ${arnsUrl}`, {
62
+ originalUrl,
63
+ targetGateway: targetGatewayUrl,
64
+ });
30
65
  return new URL(rest.join('/'), arnsUrl);
31
66
  }
32
67
  // TODO: support .eth addresses
33
68
  // TODO: "gasless" routing via DNS TXT records (e.g. ar://gatewaypie.com -> TXT record lookup for TX ID and redirect to that gateway)
34
69
  }
70
+ logger?.debug('No wayfinder routing protocol applied', {
71
+ originalUrl,
72
+ });
35
73
  // return the original url if it's not a wayfinder url (allows you to use the wayfinder client with non-wayfinder urls)
36
74
  return new URL(originalUrl);
37
75
  };
76
+ export class WayfinderEmitter extends EventEmitter {
77
+ constructor({ onVerificationPassed, onVerificationFailed, onVerificationProgress,
78
+ // TODO: continue this pattern for all events
79
+ } = {}) {
80
+ super();
81
+ if (onVerificationPassed) {
82
+ this.on('verification-passed', onVerificationPassed);
83
+ }
84
+ if (onVerificationFailed) {
85
+ this.on('verification-failed', onVerificationFailed);
86
+ }
87
+ if (onVerificationProgress) {
88
+ this.on('verification-progress', onVerificationProgress);
89
+ }
90
+ }
91
+ emit(event, payload) {
92
+ return super.emit(event, payload);
93
+ }
94
+ on(event, listener) {
95
+ return super.on(event, listener);
96
+ }
97
+ }
98
+ export function tapAndVerifyStream({ originalStream, contentLength, verifyData, txId, emitter, }) {
99
+ // taps node streams
100
+ if (originalStream instanceof Readable &&
101
+ typeof originalStream.pipe === 'function') {
102
+ const tappedClientStream = new PassThrough();
103
+ const streamToVerify = new PassThrough();
104
+ // kick off the verification promise, this will be awaited when the original stream ends
105
+ const verificationPromise = verifyData({
106
+ data: streamToVerify,
107
+ txId,
108
+ });
109
+ let bytesProcessed = 0;
110
+ // pipe the original stream to the verifier and the client stream
111
+ originalStream.on('data', (chunk) => {
112
+ streamToVerify.write(chunk);
113
+ tappedClientStream.write(chunk);
114
+ bytesProcessed += chunk.length;
115
+ // only emit if contentLength is not 0
116
+ if (contentLength !== 0) {
117
+ emitter?.emit('verification-progress', {
118
+ txId,
119
+ totalBytes: contentLength,
120
+ processedBytes: bytesProcessed,
121
+ });
122
+ }
123
+ });
124
+ originalStream.on('end', async () => {
125
+ streamToVerify.end(); // triggers verifier completion and completes the verification promise
126
+ try {
127
+ await verificationPromise;
128
+ emitter?.emit('verification-passed', {
129
+ txId,
130
+ });
131
+ tappedClientStream.end();
132
+ }
133
+ catch (error) {
134
+ emitter?.emit('verification-failed', {
135
+ error,
136
+ txId,
137
+ });
138
+ tappedClientStream.destroy(error);
139
+ }
140
+ });
141
+ originalStream.on('error', (err) => {
142
+ emitter?.emit('verification-failed', {
143
+ error: err,
144
+ txId,
145
+ });
146
+ streamToVerify.destroy(err);
147
+ tappedClientStream.destroy(err);
148
+ });
149
+ // send the stream to the verify function and if it errors end the client stream
150
+ return tappedClientStream;
151
+ }
152
+ // taps web readable streams
153
+ if (originalStream instanceof ReadableStream &&
154
+ typeof originalStream.tee === 'function') {
155
+ const [verifyBranch, clientBranch] = originalStream.tee();
156
+ // setup our promise to verify the data
157
+ const verificationPromise = verifyData({
158
+ data: verifyBranch,
159
+ txId,
160
+ });
161
+ let bytesProcessed = 0;
162
+ const reader = clientBranch.getReader();
163
+ const clientStreamWithVerification = new ReadableStream({
164
+ async pull(controller) {
165
+ const { done, value } = await reader.read();
166
+ if (done) {
167
+ try {
168
+ // due to backpressure, if the client does not consume the stream, the verification will not complete (particularly important for fetch, where the response body needs to be awaited for verification to complete)
169
+ await verificationPromise;
170
+ emitter?.emit('verification-passed', {
171
+ txId,
172
+ });
173
+ controller.close();
174
+ }
175
+ catch (err) {
176
+ emitter?.emit('verification-failed', {
177
+ txId,
178
+ error: err,
179
+ });
180
+ controller.error(err);
181
+ }
182
+ }
183
+ else {
184
+ bytesProcessed += value.length;
185
+ emitter?.emit('verification-progress', {
186
+ txId,
187
+ totalBytes: contentLength,
188
+ processedBytes: bytesProcessed,
189
+ });
190
+ controller.enqueue(value);
191
+ }
192
+ },
193
+ cancel(reason) {
194
+ reader.cancel(reason);
195
+ emitter?.emit('verification-failed', {
196
+ txId,
197
+ error: new Error('Verification cancelled', {
198
+ cause: {
199
+ reason,
200
+ },
201
+ }),
202
+ });
203
+ },
204
+ });
205
+ return clientStreamWithVerification;
206
+ }
207
+ throw new Error('Unsupported body type for cloning');
208
+ }
209
+ export function wrapVerifiedResponse(original, newBody, txId) {
210
+ // Clone headers (Header objects aren't serializable)
211
+ const headers = new Headers();
212
+ original.headers.forEach((value, key) => headers.set(key, value));
213
+ // Create a new Response with the new body and cloned headers
214
+ const wrapped = new Response(newBody, {
215
+ status: original.status,
216
+ statusText: original.statusText,
217
+ headers,
218
+ });
219
+ // Attach txId for downstream tracking
220
+ wrapped.txId = txId;
221
+ wrapped.redirectedFrom = original.url;
222
+ return wrapped;
223
+ }
38
224
  /**
39
225
  * Creates a wrapped http client that supports ar:// protocol
40
226
  *
@@ -48,24 +234,171 @@ export const resolveWayfinderUrl = ({ originalUrl, targetGateway, }) => {
48
234
  * @param resolveUrl - the function to construct the redirect url for ar:// requests
49
235
  * @returns a wrapped http client that supports ar:// protocol
50
236
  */
51
- export const createWayfinderClient = ({ httpClient, resolveUrl, }) => {
237
+ export const createWayfinderClient = ({ httpClient, resolveUrl, verifyData, emitter = new WayfinderEmitter(), logger, }) => {
52
238
  const wayfinderRedirect = async (fn, rawArgs) => {
53
239
  // TODO: handle if first arg is not a string (i.e. just return the result of the function call)
54
240
  const [originalUrl, ...rest] = rawArgs;
241
+ if (typeof originalUrl !== 'string') {
242
+ logger?.debug('Original URL is not a string, skipping routing', {
243
+ originalUrl,
244
+ });
245
+ return fn(...rawArgs);
246
+ }
247
+ emitter?.emit('routing-started', {
248
+ originalUrl: originalUrl.toString(),
249
+ });
55
250
  // route the request to the target gateway
56
251
  const redirectUrl = await resolveUrl({
57
252
  originalUrl,
253
+ logger,
254
+ });
255
+ emitter?.emit('routing-succeeded', {
256
+ originalUrl,
257
+ targetGateway: redirectUrl.toString(),
258
+ });
259
+ logger?.debug(`Redirecting request to ${redirectUrl}`, {
260
+ originalUrl,
261
+ redirectUrl,
58
262
  });
59
263
  // make the request to the target gateway using the redirect url and http client
60
264
  const response = await fn(redirectUrl.toString(), ...rest);
61
- // TODO: if verifyDataHash is provided, verify the data hash before returning
265
+ // TODO: trigger a routing event with the raw response object?
266
+ logger?.debug(`Successfully routed request to ${redirectUrl}`, {
267
+ redirectUrl,
268
+ originalUrl,
269
+ });
270
+ // only verify data if the redirect url is different from the original url
271
+ if (response && redirectUrl.toString() !== originalUrl.toString()) {
272
+ if (verifyData) {
273
+ // if the headers do not have .get on them, we need to parse the headers manually
274
+ const headers = new Headers();
275
+ let headersObject = response.headers ?? {};
276
+ if (typeof headersObject.get !== 'function') {
277
+ headersObject = Object.fromEntries(headersObject);
278
+ for (const [key, value] of Object.entries(headersObject)) {
279
+ headers.set(key, value);
280
+ }
281
+ }
282
+ else {
283
+ for (const [key, value] of headersObject.entries()) {
284
+ headers.set(key, value);
285
+ }
286
+ }
287
+ // transaction id is either in the response headers or the path of the request as the first parameter
288
+ // TODO: we may want to move this parsing to be returned by the resolveUrl function depending on the redirect URL we've constructed
289
+ const txId = headers.get('x-arns-resolved-id') ??
290
+ redirectUrl.pathname.split('/')[1];
291
+ // TODO: validate nodes return content length for all responses
292
+ const contentLength = +(headers.get('content-length') ?? 0);
293
+ if (!txIdRegex.test(txId)) {
294
+ // no transaction id found, skip verification
295
+ logger?.debug('No transaction id found, skipping verification', {
296
+ redirectUrl,
297
+ originalUrl,
298
+ });
299
+ emitter?.emit('verification-skipped', {
300
+ originalUrl,
301
+ });
302
+ return response;
303
+ }
304
+ emitter?.emit('identified-transaction-id', {
305
+ originalUrl,
306
+ targetGateway: redirectUrl.toString(),
307
+ txId,
308
+ });
309
+ // parse out the key that contains the response body, we'll use it later when updating the response object
310
+ const responseDataKey = response.body
311
+ ? 'body'
312
+ : response.data
313
+ ? 'data'
314
+ : undefined;
315
+ if (responseDataKey === undefined) {
316
+ throw new Error('No data body or data provided, skipping verification', {
317
+ cause: {
318
+ redirectUrl: redirectUrl.toString(),
319
+ originalUrl: originalUrl.toString(),
320
+ },
321
+ });
322
+ }
323
+ const responseBody = response[responseDataKey];
324
+ // TODO: determine if it is data item or L1 transaction, and tell the verifier accordingly, just drop in hit to graphql now
325
+ if (txId === undefined) {
326
+ throw new Error('Failed to parse data hash from response headers', {
327
+ cause: {
328
+ redirectUrl: redirectUrl.toString(),
329
+ originalUrl: originalUrl.toString(),
330
+ txId,
331
+ },
332
+ });
333
+ }
334
+ else if (responseBody === undefined) {
335
+ throw new Error('No data body provided, skipping verification', {
336
+ cause: {
337
+ redirectUrl: redirectUrl.toString(),
338
+ originalUrl: originalUrl.toString(),
339
+ txId,
340
+ },
341
+ });
342
+ }
343
+ else {
344
+ logger?.debug('Verifying data hash for txId', {
345
+ redirectUrl: redirectUrl.toString(),
346
+ originalUrl: originalUrl.toString(),
347
+ txId,
348
+ });
349
+ const newClientStream = tapAndVerifyStream({
350
+ originalStream: responseBody,
351
+ contentLength,
352
+ verifyData,
353
+ txId,
354
+ emitter,
355
+ });
356
+ if (responseBody instanceof ReadableStream) {
357
+ // specific to fetch
358
+ return wrapVerifiedResponse(response, newClientStream, txId);
359
+ }
360
+ else if (responseBody instanceof Readable) {
361
+ // overwrite the response body with the new client stream
362
+ response.txId = txId;
363
+ response.body = newClientStream;
364
+ return response;
365
+ }
366
+ else {
367
+ // TODO: content-application/json and it's smaller than 10mb
368
+ // TODO: add tests and verify this works for all non-Readable/streamed responses
369
+ try {
370
+ // if strict set to true
371
+ await verifyData({
372
+ data: responseBody,
373
+ txId,
374
+ });
375
+ emitter?.emit('verification-passed', {
376
+ txId,
377
+ });
378
+ }
379
+ catch (error) {
380
+ logger?.debug('Failed to verify data hash', {
381
+ error,
382
+ txId,
383
+ });
384
+ emitter?.emit('verification-failed', {
385
+ txId,
386
+ error,
387
+ });
388
+ }
389
+ return response;
390
+ }
391
+ }
392
+ }
393
+ }
394
+ // TODO: if strict - wait for verification to finish and succeed before returning the response
62
395
  return response;
63
396
  };
64
397
  return new Proxy(httpClient, {
65
398
  // support direct calls: fetch('ar://…', options)
66
399
  // axios() or got()
67
400
  apply: (_target, _thisArg, argArray) => wayfinderRedirect(httpClient, argArray),
68
- // support http clients that use methods like `got.get`, `got.post`, `axios.get`, etc. while still using the wayfinder redirect function
401
+ // support http clients that use functions like `got.get`, `got.post`, `axios.get`, etc. while still using the wayfinder redirect function
69
402
  get: (target, prop, receiver) => {
70
403
  const value = Reflect.get(target, prop, receiver);
71
404
  if (typeof value === 'function') {
@@ -87,18 +420,32 @@ export class Wayfinder {
87
420
  *
88
421
  * @example
89
422
  * const wayfinder = new Wayfinder({
90
- * router: new RandomGatewayRouter({ ario: ARIO.mainnet() }),
423
+ * router: new RandomGatewayRouter({
424
+ * gatewaysProvider: new SimpleCacheGatewaysProvider({
425
+ * gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
426
+ * ttlSeconds: 60 * 60 * 24, // 1 day
427
+ * }),
428
+ * }),
91
429
  * });
430
+ *
431
+ * // Returns a target gateway based on the routing strategy
432
+ * const targetGateway = await wayfinder.router.getTargetGateway();
92
433
  */
93
434
  router;
94
435
  /**
95
- * The http client to use for requests
436
+ * The native http client used by wayfinder
96
437
  *
97
438
  * @example
98
439
  * const wayfinder = new Wayfinder({
99
- * router: new RandomGatewayRouter({ ario: ARIO.mainnet() }),
440
+ * router: new RandomGatewayRouter({
441
+ * gatewaysProvider: new SimpleCacheGatewaysProvider({
442
+ * gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
443
+ * ttlSeconds: 60 * 60 * 24, // 1 day
444
+ * }),
445
+ * }),
100
446
  * httpClient: axios,
101
447
  * });
448
+ *
102
449
  */
103
450
  httpClient;
104
451
  /**
@@ -106,10 +453,16 @@ export class Wayfinder {
106
453
  *
107
454
  * @example
108
455
  * const wayfinder = new Wayfinder({
109
- * router: new RandomGatewayRouter({ ario: ARIO.mainnet() }),
456
+ * router: new RandomGatewayRouter({
457
+ * gatewaysProvider: new SimpleCacheGatewaysProvider({
458
+ * gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
459
+ * ttlSeconds: 60 * 60 * 24, // 1 day
460
+ * }),
461
+ * }),
110
462
  * httpClient: axios,
111
463
  * });
112
464
  *
465
+ * // returns the redirected URL based on the routing strategy and the original url
113
466
  * const redirectUrl = await wayfinder.resolveUrl({ originalUrl: 'ar://example' });
114
467
  */
115
468
  resolveUrl;
@@ -118,12 +471,16 @@ export class Wayfinder {
118
471
  *
119
472
  * @example
120
473
  * const { request: wayfind } = new Wayfinder({
121
- * router: new RandomGatewayRouter({ ario: ARIO.mainnet() }),
474
+ * router: new RandomGatewayRouter({
475
+ * gatewaysProvider: new SimpleCacheGatewaysProvider({
476
+ * gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
477
+ * ttlSeconds: 60 * 60 * 24, // 1 day
478
+ * }),
479
+ * }),
122
480
  * httpClient: axios,
123
481
  * });;
124
482
  *
125
- * TODO: consider a top level function that supports wayfinder routing under the hood
126
- * const response = await wayfind('ar://', {
483
+ * const response = await wayfind('ar://example', {
127
484
  * method: 'POST',
128
485
  * data: {
129
486
  * name: 'John Doe',
@@ -133,29 +490,95 @@ export class Wayfinder {
133
490
  request;
134
491
  // TODO: stats provider
135
492
  // TODO: metricsProvider for otel/prom support
136
- // TODO: private verificationSettings: {
137
- // trustedGateways: URL[];
138
- // method: 'local' | 'remote';
139
- // };
140
- constructor({
493
+ verifyData;
494
+ /**
495
+ * The event emitter for wayfinder that emits verification events.
496
+ *
497
+ * const wayfinder = new Wayfinder()
498
+ *
499
+ * wayfinder.emitter.on('verification-passed', (event) => {
500
+ * console.log('Verification passed!', event);
501
+ * })
502
+ *
503
+ * wayfinder.emitter.on('verification-failed', (event) => {
504
+ * console.log('Verification failed!', event);
505
+ * })
506
+ *
507
+ * or implement the events interface and pass it in, using callback functions
508
+ *
509
+ * const wayfinder = new Wayfinder({
510
+ * events: {
511
+ * onVerificationPassed: (event) => {
512
+ * console.log('Verification passed!', event);
513
+ * },
514
+ * onVerificationFailed: (event) => {
515
+ * console.log('Verification failed!', event);
516
+ * },
517
+ * onVerificationProgress: (event) => {
518
+ * console.log('Verification progress!', event);
519
+ * },
520
+ * }
521
+ * })
522
+ *
523
+ * const response = await wayfind('ar://example');
524
+ */
525
+ // TODO: consider changing this to events or event emitter
526
+ emitter;
527
+ constructor({ httpClient,
141
528
  // TODO: consider changing router to routingStrategy or strategy
142
529
  router = new RandomGatewayRouter({
143
- // optionally use a cache gateways provider to reduce the number of requests to the contract
144
- gatewaysProvider: new ARIOGatewaysProvider({ ario: ARIO.mainnet() }),
145
- }), httpClient,
146
- // TODO: add verifier interface that provides a verifyDataHash function
530
+ gatewaysProvider: new SimpleCacheGatewaysProvider({
531
+ gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
532
+ ttlSeconds: 60 * 60 * 24, // 1 day
533
+ }),
534
+ }), logger = Logger.default,
535
+ // TODO: support disabling verification or create some PassThroughVerifier like thing
536
+ verifier = new HashVerifier({
537
+ trustedHashProvider: new TrustedGatewaysHashProvider({
538
+ gatewaysProvider: new StaticGatewaysProvider({
539
+ gateways: ['https://permagate.io'],
540
+ }),
541
+ }),
542
+ }), events,
147
543
  // TODO: stats provider
148
544
  }) {
149
545
  this.router = router;
150
546
  this.httpClient = httpClient;
151
- this.resolveUrl = async ({ originalUrl }) => resolveWayfinderUrl({
152
- originalUrl,
153
- targetGateway: await this.router.getTargetGateway(),
154
- });
547
+ this.emitter = new WayfinderEmitter(events);
548
+ this.verifyData = verifier.verifyData.bind(verifier);
549
+ this.resolveUrl = async ({ originalUrl, logger }) => {
550
+ return resolveWayfinderUrl({
551
+ originalUrl,
552
+ targetGateway: async () => await this.router.getTargetGateway(),
553
+ logger,
554
+ });
555
+ };
155
556
  this.request = createWayfinderClient({
156
557
  httpClient,
157
558
  resolveUrl: this.resolveUrl,
158
- // TODO: provide the verifyDataHash function from the verifier to the wayfinder client along with verificationSettings
559
+ verifyData: this.verifyData,
560
+ emitter: this.emitter,
561
+ logger,
159
562
  });
563
+ logger?.debug(`Wayfinder initialized with ${router.name} routing strategy`);
160
564
  }
161
565
  }
566
+ // TODO: add a chart for verification strategies and what they do
567
+ // include complexity, performance, and security
568
+ // explain use cases that each strategy is best for
569
+ // e.g.
570
+ /**
571
+ *
572
+ * type | complexity | performance | security
573
+ * ---------|------------|-------------|---------
574
+ * hash | low | high | low
575
+ * ---------|------------|-------------|---------
576
+ * data root | medium | medium | low | only L1
577
+ * ---------|------------|-------------|---------
578
+ * signature | medium | medium | medium
579
+ * ---------|------------|-------------|---------
580
+ * composite | high | low | high
581
+ * ---------|------------|-------------|---------
582
+ *
583
+ *
584
+ */