@ar.io/sdk 3.13.0-alpha.1 → 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 (53) hide show
  1. package/bundles/web.bundle.min.js +139 -136
  2. package/lib/cjs/common/index.js +2 -0
  3. package/lib/cjs/common/wayfinder/gateways/network.js +48 -0
  4. package/lib/cjs/common/wayfinder/gateways/simple-cache.js +35 -0
  5. package/lib/cjs/common/wayfinder/gateways/static.js +13 -0
  6. package/lib/cjs/common/wayfinder/index.js +48 -0
  7. package/lib/cjs/common/wayfinder/routing/strategies/ping.js +72 -0
  8. package/lib/cjs/common/wayfinder/routing/strategies/preferred-with-fallback.js +50 -0
  9. package/lib/cjs/common/wayfinder/routing/strategies/random.js +13 -0
  10. package/lib/cjs/common/wayfinder/routing/strategies/round-robin.js +42 -0
  11. package/lib/cjs/common/wayfinder/routing/strategies/static.js +29 -0
  12. package/lib/cjs/common/wayfinder/verification/strategies/data-root-verifier.js +110 -0
  13. package/lib/cjs/common/wayfinder/verification/strategies/hash-verifier.js +27 -0
  14. package/lib/cjs/common/wayfinder/verification/trusted.js +125 -0
  15. package/lib/cjs/common/wayfinder/wayfinder.js +508 -0
  16. package/lib/cjs/types/wayfinder.js +3 -0
  17. package/lib/cjs/utils/hash.js +83 -31
  18. package/lib/cjs/version.js +1 -1
  19. package/lib/esm/common/index.js +2 -0
  20. package/lib/esm/common/wayfinder/gateways/network.js +44 -0
  21. package/lib/esm/common/wayfinder/gateways/simple-cache.js +31 -0
  22. package/lib/esm/common/wayfinder/gateways/static.js +9 -0
  23. package/lib/esm/common/wayfinder/index.js +32 -0
  24. package/lib/esm/common/wayfinder/routing/strategies/ping.js +68 -0
  25. package/lib/esm/common/wayfinder/routing/strategies/preferred-with-fallback.js +46 -0
  26. package/lib/esm/common/wayfinder/routing/strategies/random.js +9 -0
  27. package/lib/esm/common/wayfinder/routing/strategies/round-robin.js +38 -0
  28. package/lib/esm/common/wayfinder/routing/strategies/static.js +25 -0
  29. package/lib/esm/common/wayfinder/verification/strategies/data-root-verifier.js +102 -0
  30. package/lib/esm/common/wayfinder/verification/strategies/hash-verifier.js +23 -0
  31. package/lib/esm/common/wayfinder/verification/trusted.js +121 -0
  32. package/lib/esm/common/wayfinder/wayfinder.js +499 -0
  33. package/lib/esm/types/wayfinder.js +2 -0
  34. package/lib/esm/utils/hash.js +74 -27
  35. package/lib/esm/version.js +1 -1
  36. package/lib/types/common/index.d.ts +1 -0
  37. package/lib/types/common/wayfinder/gateways/network.d.ts +33 -0
  38. package/lib/types/common/wayfinder/gateways/simple-cache.d.ts +31 -0
  39. package/lib/types/common/wayfinder/gateways/static.d.ts +23 -0
  40. package/lib/types/common/wayfinder/index.d.ts +27 -0
  41. package/lib/types/common/wayfinder/routing/strategies/ping.d.ts +27 -0
  42. package/lib/types/common/wayfinder/routing/strategies/preferred-with-fallback.d.ts +31 -0
  43. package/lib/types/common/wayfinder/routing/strategies/random.d.ts +21 -0
  44. package/lib/types/common/wayfinder/routing/strategies/round-robin.d.ts +29 -0
  45. package/lib/types/common/wayfinder/routing/strategies/static.d.ts +29 -0
  46. package/lib/types/common/wayfinder/verification/strategies/data-root-verifier.d.ts +27 -0
  47. package/lib/types/common/wayfinder/verification/strategies/hash-verifier.d.ts +26 -0
  48. package/lib/types/common/wayfinder/verification/trusted.d.ts +51 -0
  49. package/lib/types/common/wayfinder/wayfinder.d.ts +257 -0
  50. package/lib/types/types/wayfinder.d.ts +66 -0
  51. package/lib/types/utils/hash.d.ts +8 -4
  52. package/lib/types/version.d.ts +1 -1
  53. package/package.json +2 -1
@@ -0,0 +1,508 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Wayfinder = exports.createWayfinderClient = exports.WayfinderEmitter = exports.resolveWayfinderUrl = exports.txIdRegex = exports.arnsRegex = void 0;
4
+ exports.tapAndVerifyReadableStream = tapAndVerifyReadableStream;
5
+ exports.sandboxFromId = sandboxFromId;
6
+ /**
7
+ * Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
8
+ *
9
+ * Licensed under the Apache License, Version 2.0 (the "License");
10
+ * you may not use this file except in compliance with the License.
11
+ * You may obtain a copy of the License at
12
+ *
13
+ * http://www.apache.org/licenses/LICENSE-2.0
14
+ *
15
+ * Unless required by applicable law or agreed to in writing, software
16
+ * distributed under the License is distributed on an "AS IS" BASIS,
17
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ * See the License for the specific language governing permissions and
19
+ * limitations under the License.
20
+ */
21
+ const eventemitter3_1 = require("eventemitter3");
22
+ const rfc4648_1 = require("rfc4648");
23
+ const io_js_1 = require("../io.js");
24
+ const logger_js_1 = require("../logger.js");
25
+ const network_js_1 = require("./gateways/network.js");
26
+ const simple_cache_js_1 = require("./gateways/simple-cache.js");
27
+ const static_js_1 = require("./gateways/static.js");
28
+ const ping_js_1 = require("./routing/strategies/ping.js");
29
+ const hash_verifier_js_1 = require("./verification/strategies/hash-verifier.js");
30
+ const trusted_js_1 = require("./verification/trusted.js");
31
+ // known regexes for wayfinder urls
32
+ exports.arnsRegex = /^[a-z0-9_-]{1,51}$/;
33
+ exports.txIdRegex = /^[A-Za-z0-9_-]{43}$/;
34
+ /**
35
+ * Core function that converts a wayfinder url to the proper ar-io gateway URL
36
+ * @param originalUrl - the wayfinder url to resolve
37
+ * @param selectedGateway - the target gateway to resolve the url against
38
+ * @returns the resolved url that can be used to make a request
39
+ */
40
+ const resolveWayfinderUrl = ({ originalUrl, selectedGateway, logger, }) => {
41
+ if (originalUrl.toString().startsWith('ar://')) {
42
+ logger?.debug(`Applying wayfinder routing protocol to ${originalUrl}`, {
43
+ originalUrl,
44
+ });
45
+ const [, path] = originalUrl.toString().split('ar://');
46
+ // e.g. ar:///info should route to the info endpoint of the target gateway
47
+ if (path.startsWith('/')) {
48
+ logger?.debug(`Routing to ${path.slice(1)} on ${selectedGateway}`, {
49
+ originalUrl,
50
+ selectedGateway,
51
+ });
52
+ return new URL(path.slice(1), selectedGateway);
53
+ }
54
+ // Split path to get the first part (name/txId) and remaining path components
55
+ const [firstPart, ...rest] = path.split('/');
56
+ // TODO: this breaks 43 character named arns names - we should check a a local name cache list before resolving raw transaction ids
57
+ if (exports.txIdRegex.test(firstPart)) {
58
+ const sandbox = sandboxFromId(firstPart);
59
+ return new URL(`${firstPart}${rest.length > 0 ? '/' + rest.join('/') : ''}`, `${selectedGateway.protocol}//${sandbox}.${selectedGateway.hostname}`);
60
+ }
61
+ if (exports.arnsRegex.test(firstPart)) {
62
+ // TODO: tests to ensure arns names support query params and paths
63
+ const arnsUrl = `${selectedGateway.protocol}//${firstPart}.${selectedGateway.hostname}${selectedGateway.port ? `:${selectedGateway.port}` : ''}`;
64
+ logger?.debug(`Routing to ${path} on ${arnsUrl}`, {
65
+ originalUrl,
66
+ selectedGateway,
67
+ });
68
+ return new URL(rest.length > 0 ? rest.join('/') : '', arnsUrl);
69
+ }
70
+ // TODO: support .eth addresses
71
+ // TODO: "gasless" routing via DNS TXT records
72
+ }
73
+ logger?.debug('No wayfinder routing protocol applied', {
74
+ originalUrl,
75
+ });
76
+ // return the original url if it's not a wayfinder url
77
+ return new URL(originalUrl);
78
+ };
79
+ exports.resolveWayfinderUrl = resolveWayfinderUrl;
80
+ class WayfinderEmitter extends eventemitter3_1.EventEmitter {
81
+ constructor({ onVerificationSucceeded, onVerificationFailed, onVerificationProgress, } = {}) {
82
+ super();
83
+ if (onVerificationSucceeded) {
84
+ this.on('verification-succeeded', onVerificationSucceeded);
85
+ }
86
+ if (onVerificationFailed) {
87
+ this.on('verification-failed', onVerificationFailed);
88
+ }
89
+ if (onVerificationProgress) {
90
+ this.on('verification-progress', onVerificationProgress);
91
+ }
92
+ }
93
+ }
94
+ exports.WayfinderEmitter = WayfinderEmitter;
95
+ function tapAndVerifyReadableStream({ originalStream, contentLength, verifyData, txId, emitter, strict = false, }) {
96
+ if (originalStream instanceof ReadableStream &&
97
+ typeof originalStream.tee === 'function') {
98
+ /**
99
+ * NOTE: tee requires the streams both streams to be consumed, so we need to make sure we consume the client branch
100
+ * by the caller. This means when `request` is called, the client stream must be consumed by the caller via await request.text()
101
+ * for verification to complete.
102
+ *
103
+ * It is feasible to make the verification stream not to depend on the client branch being consumed, should the DX not be obvious.
104
+ */
105
+ const [verifyBranch, clientBranch] = originalStream.tee();
106
+ // setup our promise to verify the data
107
+ const verificationPromise = verifyData({
108
+ data: verifyBranch,
109
+ txId,
110
+ });
111
+ let bytesProcessed = 0;
112
+ const reader = clientBranch.getReader();
113
+ const clientStreamWithVerification = new ReadableStream({
114
+ async pull(controller) {
115
+ const { done, value } = await reader.read();
116
+ if (done) {
117
+ if (strict) {
118
+ // in strict mode, we wait for verification to complete before closing the controller
119
+ try {
120
+ await verificationPromise;
121
+ emitter?.emit('verification-succeeded', { txId });
122
+ controller.close();
123
+ }
124
+ catch (err) {
125
+ // emit the verification failed event
126
+ emitter?.emit('verification-failed', err);
127
+ // In strict mode, we report the error to the client stream
128
+ controller.error(new Error('Verification failed', { cause: err }));
129
+ }
130
+ }
131
+ else {
132
+ // trigger the verification promise and emit events for the result
133
+ verificationPromise
134
+ .then(() => {
135
+ emitter?.emit('verification-succeeded', { txId });
136
+ })
137
+ .catch((error) => {
138
+ emitter?.emit('verification-failed', error);
139
+ });
140
+ // in non-strict mode, we close the controller immediately and handle verification asynchronously
141
+ controller.close();
142
+ }
143
+ }
144
+ else {
145
+ bytesProcessed += value.length;
146
+ emitter?.emit('verification-progress', {
147
+ txId,
148
+ totalBytes: contentLength,
149
+ processedBytes: bytesProcessed,
150
+ });
151
+ controller.enqueue(value);
152
+ }
153
+ },
154
+ cancel(reason) {
155
+ // cancel the reader regardless of verification status
156
+ reader.cancel(reason);
157
+ // emit the verification cancellation event
158
+ emitter?.emit('verification-failed', {
159
+ txId,
160
+ error: new Error('Verification cancelled', {
161
+ cause: {
162
+ reason,
163
+ },
164
+ }),
165
+ });
166
+ },
167
+ });
168
+ return clientStreamWithVerification;
169
+ }
170
+ throw new Error('Unsupported body type for cloning');
171
+ }
172
+ /**
173
+ * Gets the sandbox hash for a given transaction id
174
+ */
175
+ function sandboxFromId(id) {
176
+ return rfc4648_1.base32
177
+ .stringify(Buffer.from(id, 'base64'), { pad: false })
178
+ .toLowerCase();
179
+ }
180
+ /**
181
+ * Creates a wrapped fetch function that supports ar:// protocol
182
+ *
183
+ * This function leverages a Proxy to intercept calls to fetch
184
+ * and redirects them to the target gateway using the resolveUrl function.
185
+ *
186
+ * Any URLs provided that are not wayfinder urls will be passed directly to fetch.
187
+ *
188
+ * @param resolveUrl - the function to construct the redirect url for ar:// requests
189
+ * @returns a wrapped fetch function that supports ar:// protocol and always returns Response
190
+ */
191
+ const createWayfinderClient = ({ resolveUrl, verifyData, selectGateway, emitter = new WayfinderEmitter(), logger, strict = false, }) => {
192
+ return async (url, init) => {
193
+ if (typeof url !== 'string') {
194
+ logger?.debug('URL is not a string, skipping routing', {
195
+ url,
196
+ });
197
+ emitter?.emit('routing-skipped', {
198
+ originalUrl: JSON.stringify(url),
199
+ });
200
+ return fetch(url, init);
201
+ }
202
+ emitter?.emit('routing-started', {
203
+ originalUrl: url.toString(),
204
+ });
205
+ const maxRetries = 3;
206
+ const retryDelay = 1000;
207
+ for (let i = 0; i < maxRetries; i++) {
208
+ try {
209
+ // select the target gateway
210
+ const selectedGateway = await selectGateway();
211
+ logger?.debug('Selected gateway', {
212
+ originalUrl: url,
213
+ selectedGateway: selectedGateway.toString(),
214
+ });
215
+ // route the request to the target gateway
216
+ const redirectUrl = resolveUrl({
217
+ originalUrl: url,
218
+ selectedGateway,
219
+ logger,
220
+ });
221
+ emitter?.emit('routing-succeeded', {
222
+ originalUrl: url,
223
+ selectedGateway: selectedGateway.toString(),
224
+ redirectUrl: redirectUrl.toString(),
225
+ });
226
+ logger?.debug(`Redirecting request`, {
227
+ originalUrl: url,
228
+ redirectUrl: redirectUrl.toString(),
229
+ });
230
+ // make the request to the target gateway using the redirect url
231
+ const response = await fetch(redirectUrl.toString(), {
232
+ // enforce CORS given we're likely going to a different origin, but always allow the client to override
233
+ redirect: 'follow',
234
+ mode: 'cors',
235
+ ...init,
236
+ });
237
+ logger?.debug(`Successfully routed request to gateway`, {
238
+ redirectUrl: redirectUrl.toString(),
239
+ originalUrl: url.toString(),
240
+ });
241
+ // only verify data if the redirect url is different from the original url
242
+ if (redirectUrl.toString() !== url.toString()) {
243
+ if (verifyData) {
244
+ const headers = response.headers;
245
+ // transaction id is either in the response headers or the path of the request as the first parameter
246
+ const txId = headers.get('x-arns-resolved-id') ??
247
+ redirectUrl.pathname.split('/')[1];
248
+ const contentLength = +(headers.get('content-length') ?? 0);
249
+ if (!exports.txIdRegex.test(txId)) {
250
+ // no transaction id found, skip verification
251
+ logger?.debug('No transaction id found, skipping verification', {
252
+ redirectUrl: redirectUrl.toString(),
253
+ originalUrl: url,
254
+ });
255
+ emitter?.emit('verification-skipped', {
256
+ originalUrl: url,
257
+ });
258
+ return response;
259
+ }
260
+ emitter?.emit('identified-transaction-id', {
261
+ originalUrl: url,
262
+ selectedGateway: redirectUrl.toString(),
263
+ txId,
264
+ });
265
+ // Check if the response has a body
266
+ if (response.body) {
267
+ const newClientStream = tapAndVerifyReadableStream({
268
+ originalStream: response.body,
269
+ contentLength,
270
+ verifyData,
271
+ txId,
272
+ emitter,
273
+ strict,
274
+ });
275
+ return new Response(newClientStream, {
276
+ status: response.status,
277
+ statusText: response.statusText,
278
+ headers: response.headers,
279
+ });
280
+ }
281
+ else {
282
+ // No response body to verify, skip verification
283
+ logger?.debug('No response body to verify', {
284
+ redirectUrl: redirectUrl.toString(),
285
+ originalUrl: url,
286
+ txId,
287
+ });
288
+ return response;
289
+ }
290
+ }
291
+ }
292
+ return response;
293
+ }
294
+ catch (error) {
295
+ logger?.debug('Failed to route request', {
296
+ error: error.message,
297
+ stack: error.stack,
298
+ originalUrl: url,
299
+ attempt: i + 1,
300
+ maxRetries,
301
+ });
302
+ if (i < maxRetries - 1) {
303
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
304
+ }
305
+ }
306
+ }
307
+ throw new Error('Failed to route request after max retries', {
308
+ cause: {
309
+ originalUrl: url,
310
+ maxRetries,
311
+ },
312
+ });
313
+ };
314
+ };
315
+ exports.createWayfinderClient = createWayfinderClient;
316
+ /**
317
+ * The main class for the wayfinder
318
+ */
319
+ class Wayfinder {
320
+ /**
321
+ * The gateways provider is responsible for providing the list of gateways to use for routing requests.
322
+ *
323
+ * @example
324
+ * const wayfinder = new Wayfinder({
325
+ * gatewaysProvider: new SimpleCacheGatewaysProvider({
326
+ * gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
327
+ * ttlSeconds: 60 * 60 * 24, // 1 day
328
+ * }),
329
+ * });
330
+ */
331
+ gatewaysProvider;
332
+ /**
333
+ * The routing strategy to use when routing requests.
334
+ *
335
+ * @example
336
+ * const wayfinder = new Wayfinder({
337
+ * strategy: new FastestPingStrategy({
338
+ * timeoutMs: 1000,
339
+ * }),
340
+ * });
341
+ */
342
+ routingStrategy;
343
+ /**
344
+ * A helper function that resolves the redirect url for ar:// requests to a target gateway.
345
+ *
346
+ * Note: no verification is done when resolving an ar://<path> url to a wayfinder route.
347
+ * In order to verify the data, you must use the `request` function or request the data and
348
+ * verify it yourself via the `verifyData` function.
349
+ *
350
+ * @example
351
+ * const { resolveUrl } = new Wayfinder();
352
+ *
353
+ * // returns the redirected URL based on the routing strategy and the original url
354
+ * const redirectUrl = await resolveUrl({ originalUrl: 'ar://example' });
355
+ *
356
+ * window.open(redirectUrl.toString(), '_blank');
357
+ */
358
+ resolveUrl;
359
+ /**
360
+ * A wrapped fetch function that supports ar:// protocol. If a verification strategy is provided,
361
+ * the request will be verified and events will be emitted as the request is processed.
362
+ *
363
+ * @example
364
+ * const wayfinder = new Wayfinder({
365
+ * verificationStrategy: new HashVerificationStrategy({
366
+ * trustedHashProvider: new TrustedGatewaysHashProvider({
367
+ * gatewaysProvider: new StaticGatewaysProvider({
368
+ * gateways: ['https://permagate.io'],
369
+ * }),
370
+ * }),
371
+ * }),
372
+ * })
373
+ *
374
+ * // request an arns name
375
+ * const response = await wayfinder.request('ar://ardrive')
376
+ *
377
+ * // request a transaction id
378
+ * const response = await wayfinder.request('ar://1234567890')
379
+ *
380
+ * // Set strict mode to true to make verification blocking
381
+ * const wayfinder = new Wayfinder({
382
+ * strict: true,
383
+ * });
384
+ *
385
+ * // This will throw an error if verification fails
386
+ * try {
387
+ * const response = await wayfinder.request('ar://1234567890');
388
+ * } catch (error) {
389
+ * console.error('Verification failed', error);
390
+ * }
391
+ */
392
+ request;
393
+ /**
394
+ * The function that verifies the data hash for a given transaction id.
395
+ *
396
+ * @example
397
+ * const wayfinder = new Wayfinder({
398
+ * verifyData: (data, txId) => {
399
+ * // some custom verification logic
400
+ * return true;
401
+ * },
402
+ * });
403
+ */
404
+ verifyData;
405
+ /**
406
+ * Whether verification should be strict (blocking) or not.
407
+ * If true, verification failures will cause requests to fail.
408
+ * If false, verification will be performed asynchronously and failures will only emit events.
409
+ */
410
+ strict;
411
+ /**
412
+ * The event emitter for wayfinder that emits verification events.
413
+ *
414
+ * const wayfinder = new Wayfinder()
415
+ *
416
+ * wayfinder.emitter.on('verification-succeeded', (event) => {
417
+ * console.log('Verification passed!', event);
418
+ * })
419
+ *
420
+ * wayfinder.emitter.on('verification-failed', (event) => {
421
+ * console.log('Verification failed!', event);
422
+ * })
423
+ *
424
+ * or implement the events interface and pass it in, using callback functions
425
+ *
426
+ * const wayfinder = new Wayfinder({
427
+ * events: {
428
+ * onVerificationPassed: (event) => {
429
+ * console.log('Verification passed!', event);
430
+ * },
431
+ * onVerificationFailed: (event) => {
432
+ * console.log('Verification failed!', event);
433
+ * },
434
+ * onVerificationProgress: (event) => {
435
+ * console.log('Verification progress!', event);
436
+ * },
437
+ * }
438
+ * })
439
+ *
440
+ * const response = await wayfind('ar://example');
441
+ */
442
+ emitter;
443
+ /**
444
+ * The constructor for the wayfinder
445
+ * @param routingStrategy - the routing strategy to use for requests
446
+ * @param verificationStrategy - the verification strategy to use for requests
447
+ * @param gatewaysProvider - the gateways provider to use for routing requests
448
+ * @param logger - the logger to use for logging
449
+ * @param strict - if true, verification will be blocking and will fail requests if verification fails; if false, verification will be non-blocking
450
+ */
451
+ constructor({ logger = logger_js_1.Logger.default, gatewaysProvider = new simple_cache_js_1.SimpleCacheGatewaysProvider({
452
+ gatewaysProvider: new network_js_1.NetworkGatewaysProvider({
453
+ ario: io_js_1.ARIO.mainnet(),
454
+ }),
455
+ ttlSeconds: 60 * 60, // 1 hour
456
+ }), routingStrategy = new ping_js_1.FastestPingRoutingStrategy({
457
+ timeoutMs: 1000,
458
+ }), verificationStrategy = new hash_verifier_js_1.HashVerificationStrategy({
459
+ trustedHashProvider: new trusted_js_1.TrustedGatewaysHashProvider({
460
+ gatewaysProvider: new static_js_1.StaticGatewaysProvider({
461
+ gateways: ['https://permagate.io'],
462
+ }),
463
+ }),
464
+ }), events = {
465
+ onVerificationSucceeded: (event) => {
466
+ logger.debug('Verification passed!', event);
467
+ },
468
+ onVerificationFailed: (event) => {
469
+ logger.error('Verification failed!', event);
470
+ },
471
+ onVerificationProgress: (event) => {
472
+ logger.debug('Verification progress!', event);
473
+ },
474
+ }, strict = false, } = {}) {
475
+ this.routingStrategy = routingStrategy;
476
+ this.gatewaysProvider = gatewaysProvider;
477
+ this.emitter = new WayfinderEmitter(events);
478
+ this.verifyData =
479
+ verificationStrategy.verifyData.bind(verificationStrategy);
480
+ this.strict = strict;
481
+ // top level function to easily resolve wayfinder urls using the routing strategy and gateways provider
482
+ this.resolveUrl = async ({ originalUrl, logger }) => {
483
+ const selectedGateway = await this.routingStrategy.selectGateway({
484
+ gateways: await this.gatewaysProvider.getGateways(),
485
+ });
486
+ return (0, exports.resolveWayfinderUrl)({
487
+ originalUrl,
488
+ selectedGateway,
489
+ logger,
490
+ });
491
+ };
492
+ // create a wayfinder client with the routing strategy and gateways provider
493
+ this.request = (0, exports.createWayfinderClient)({
494
+ selectGateway: async () => {
495
+ return this.routingStrategy.selectGateway({
496
+ gateways: await this.gatewaysProvider.getGateways(),
497
+ });
498
+ },
499
+ resolveUrl: exports.resolveWayfinderUrl,
500
+ verifyData: this.verifyData,
501
+ emitter: this.emitter,
502
+ logger,
503
+ strict,
504
+ });
505
+ logger?.debug(`Wayfinder initialized with ${routingStrategy.constructor.name} routing strategy`);
506
+ }
507
+ }
508
+ exports.Wayfinder = Wayfinder;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // TODO: add an offset provider that returns offsets for data items so we can use them to verify the signatures of a data item within a bundle
@@ -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.13.0-alpha.1';
20
+ exports.version = '3.13.0-beta.1';
@@ -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';