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

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