@ar.io/sdk 3.12.0-beta.5 → 3.12.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundles/web.bundle.min.js +130 -130
- package/lib/cjs/common/wayfinder/verification/strategies/data-root-verifier.js +17 -46
- package/lib/cjs/common/wayfinder/verification/strategies/hash-verifier.js +7 -30
- package/lib/cjs/common/wayfinder/verification/trusted.js +26 -10
- package/lib/cjs/common/wayfinder/wayfinder.js +99 -185
- package/lib/cjs/utils/hash.js +83 -31
- package/lib/cjs/version.js +1 -1
- package/lib/esm/common/wayfinder/verification/strategies/data-root-verifier.js +15 -43
- package/lib/esm/common/wayfinder/verification/strategies/hash-verifier.js +8 -31
- package/lib/esm/common/wayfinder/verification/trusted.js +26 -10
- package/lib/esm/common/wayfinder/wayfinder.js +96 -180
- package/lib/esm/utils/hash.js +74 -27
- package/lib/esm/version.js +1 -1
- package/lib/types/common/wayfinder/verification/strategies/data-root-verifier.d.ts +4 -8
- package/lib/types/common/wayfinder/verification/strategies/hash-verifier.d.ts +2 -3
- package/lib/types/common/wayfinder/wayfinder.d.ts +44 -58
- package/lib/types/types/wayfinder.d.ts +2 -2
- package/lib/types/utils/hash.d.ts +8 -4
- package/lib/types/version.d.ts +1 -1
- package/package.json +2 -1
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import EventEmitter from '
|
|
17
|
-
import {
|
|
16
|
+
import { EventEmitter } from 'eventemitter3';
|
|
17
|
+
import { base32 } from 'rfc4648';
|
|
18
18
|
import { ARIO } from '../io.js';
|
|
19
19
|
import { Logger } from '../logger.js';
|
|
20
20
|
import { NetworkGatewaysProvider } from './gateways/network.js';
|
|
@@ -50,7 +50,8 @@ export const resolveWayfinderUrl = ({ originalUrl, selectedGateway, logger, }) =
|
|
|
50
50
|
const [firstPart, ...rest] = path.split('/');
|
|
51
51
|
// TODO: this breaks 43 character named arns names - we should check a a local name cache list before resolving raw transaction ids
|
|
52
52
|
if (txIdRegex.test(firstPart)) {
|
|
53
|
-
|
|
53
|
+
const sandbox = sandboxFromId(firstPart);
|
|
54
|
+
return new URL(`${firstPart}${rest.length > 0 ? '/' + rest.join('/') : ''}`, `${selectedGateway.protocol}//${sandbox}.${selectedGateway.hostname}`);
|
|
54
55
|
}
|
|
55
56
|
if (arnsRegex.test(firstPart)) {
|
|
56
57
|
// TODO: tests to ensure arns names support query params and paths
|
|
@@ -62,21 +63,19 @@ export const resolveWayfinderUrl = ({ originalUrl, selectedGateway, logger, }) =
|
|
|
62
63
|
return new URL(rest.length > 0 ? rest.join('/') : '', arnsUrl);
|
|
63
64
|
}
|
|
64
65
|
// TODO: support .eth addresses
|
|
65
|
-
// TODO: "gasless" routing via DNS TXT records
|
|
66
|
+
// TODO: "gasless" routing via DNS TXT records
|
|
66
67
|
}
|
|
67
68
|
logger?.debug('No wayfinder routing protocol applied', {
|
|
68
69
|
originalUrl,
|
|
69
70
|
});
|
|
70
|
-
// return the original url if it's not a wayfinder url
|
|
71
|
+
// return the original url if it's not a wayfinder url
|
|
71
72
|
return new URL(originalUrl);
|
|
72
73
|
};
|
|
73
74
|
export class WayfinderEmitter extends EventEmitter {
|
|
74
|
-
constructor({
|
|
75
|
-
// TODO: continue this pattern for all events
|
|
76
|
-
} = {}) {
|
|
75
|
+
constructor({ onVerificationSucceeded, onVerificationFailed, onVerificationProgress, } = {}) {
|
|
77
76
|
super();
|
|
78
|
-
if (
|
|
79
|
-
this.on('verification-succeeded',
|
|
77
|
+
if (onVerificationSucceeded) {
|
|
78
|
+
this.on('verification-succeeded', onVerificationSucceeded);
|
|
80
79
|
}
|
|
81
80
|
if (onVerificationFailed) {
|
|
82
81
|
this.on('verification-failed', onVerificationFailed);
|
|
@@ -85,83 +84,17 @@ export class WayfinderEmitter extends EventEmitter {
|
|
|
85
84
|
this.on('verification-progress', onVerificationProgress);
|
|
86
85
|
}
|
|
87
86
|
}
|
|
88
|
-
emit(event, payload) {
|
|
89
|
-
return super.emit(event, payload);
|
|
90
|
-
}
|
|
91
|
-
on(event, listener) {
|
|
92
|
-
return super.on(event, listener);
|
|
93
|
-
}
|
|
94
87
|
}
|
|
95
|
-
export function
|
|
96
|
-
// taps node streams
|
|
97
|
-
if (originalStream instanceof Readable &&
|
|
98
|
-
typeof originalStream.pipe === 'function') {
|
|
99
|
-
const tappedClientStream = new PassThrough();
|
|
100
|
-
const streamToVerify = new PassThrough();
|
|
101
|
-
// kick off the verification promise, this will be awaited when the original stream ends
|
|
102
|
-
const verificationPromise = verifyData({
|
|
103
|
-
data: streamToVerify,
|
|
104
|
-
txId,
|
|
105
|
-
});
|
|
106
|
-
let bytesProcessed = 0;
|
|
107
|
-
// pipe the original stream to the verifier and the client stream
|
|
108
|
-
originalStream.on('data', (chunk) => {
|
|
109
|
-
streamToVerify.write(chunk);
|
|
110
|
-
tappedClientStream.write(chunk);
|
|
111
|
-
bytesProcessed += chunk.length;
|
|
112
|
-
// only emit if contentLength is not 0
|
|
113
|
-
if (contentLength !== 0) {
|
|
114
|
-
emitter?.emit('verification-progress', {
|
|
115
|
-
txId,
|
|
116
|
-
totalBytes: contentLength,
|
|
117
|
-
processedBytes: bytesProcessed,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
originalStream.on('end', async () => {
|
|
122
|
-
streamToVerify.end(); // triggers verifier completion and completes the verification promise
|
|
123
|
-
if (strict) {
|
|
124
|
-
// in strict mode, we wait for verification to complete before ending the client stream
|
|
125
|
-
try {
|
|
126
|
-
await verificationPromise;
|
|
127
|
-
emitter?.emit('verification-succeeded', { txId });
|
|
128
|
-
tappedClientStream.end();
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
emitter?.emit('verification-failed', { error, txId });
|
|
132
|
-
// In strict mode, destroy the client stream with the error
|
|
133
|
-
tappedClientStream.destroy(new Error('Verification failed', { cause: error }));
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
// in non-strict mode, we end the client stream immediately and handle verification asynchronously
|
|
138
|
-
tappedClientStream.end();
|
|
139
|
-
// trigger the verification promise and emit events for the result
|
|
140
|
-
verificationPromise
|
|
141
|
-
.then(() => {
|
|
142
|
-
emitter?.emit('verification-succeeded', { txId });
|
|
143
|
-
})
|
|
144
|
-
.catch((error) => {
|
|
145
|
-
emitter?.emit('verification-failed', { error, txId });
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
originalStream.on('error', (err) => {
|
|
150
|
-
// emit the verification failed event
|
|
151
|
-
emitter?.emit('verification-failed', {
|
|
152
|
-
error: err,
|
|
153
|
-
txId,
|
|
154
|
-
});
|
|
155
|
-
// destroy both streams and propagate the original stream error
|
|
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
|
|
88
|
+
export function tapAndVerifyReadableStream({ originalStream, contentLength, verifyData, txId, emitter, strict = false, }) {
|
|
163
89
|
if (originalStream instanceof ReadableStream &&
|
|
164
90
|
typeof originalStream.tee === 'function') {
|
|
91
|
+
/**
|
|
92
|
+
* NOTE: tee requires the streams both streams to be consumed, so we need to make sure we consume the client branch
|
|
93
|
+
* by the caller. This means when `request` is called, the client stream must be consumed by the caller via await request.text()
|
|
94
|
+
* for verification to complete.
|
|
95
|
+
*
|
|
96
|
+
* It is feasible to make the verification stream not to depend on the client branch being consumed, should the DX not be obvious.
|
|
97
|
+
*/
|
|
165
98
|
const [verifyBranch, clientBranch] = originalStream.tee();
|
|
166
99
|
// setup our promise to verify the data
|
|
167
100
|
const verificationPromise = verifyData({
|
|
@@ -182,10 +115,8 @@ export function tapAndVerifyStream({ originalStream, contentLength, verifyData,
|
|
|
182
115
|
controller.close();
|
|
183
116
|
}
|
|
184
117
|
catch (err) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
error: err,
|
|
188
|
-
});
|
|
118
|
+
// emit the verification failed event
|
|
119
|
+
emitter?.emit('verification-failed', err);
|
|
189
120
|
// In strict mode, we report the error to the client stream
|
|
190
121
|
controller.error(new Error('Verification failed', { cause: err }));
|
|
191
122
|
}
|
|
@@ -197,10 +128,7 @@ export function tapAndVerifyStream({ originalStream, contentLength, verifyData,
|
|
|
197
128
|
emitter?.emit('verification-succeeded', { txId });
|
|
198
129
|
})
|
|
199
130
|
.catch((error) => {
|
|
200
|
-
emitter?.emit('verification-failed',
|
|
201
|
-
txId,
|
|
202
|
-
error,
|
|
203
|
-
});
|
|
131
|
+
emitter?.emit('verification-failed', error);
|
|
204
132
|
});
|
|
205
133
|
// in non-strict mode, we close the controller immediately and handle verification asynchronously
|
|
206
134
|
controller.close();
|
|
@@ -228,14 +156,20 @@ export function tapAndVerifyStream({ originalStream, contentLength, verifyData,
|
|
|
228
156
|
},
|
|
229
157
|
}),
|
|
230
158
|
});
|
|
231
|
-
// note: we don't block or throw errors here even in strict mode
|
|
232
|
-
// since the stream is already being cancelled by the client
|
|
233
159
|
},
|
|
234
160
|
});
|
|
235
161
|
return clientStreamWithVerification;
|
|
236
162
|
}
|
|
237
163
|
throw new Error('Unsupported body type for cloning');
|
|
238
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Gets the sandbox hash for a given transaction id
|
|
167
|
+
*/
|
|
168
|
+
export function sandboxFromId(id) {
|
|
169
|
+
return base32
|
|
170
|
+
.stringify(Buffer.from(id, 'base64'), { pad: false })
|
|
171
|
+
.toLowerCase();
|
|
172
|
+
}
|
|
239
173
|
/**
|
|
240
174
|
* Creates a wrapped fetch function that supports ar:// protocol
|
|
241
175
|
*
|
|
@@ -248,146 +182,128 @@ export function tapAndVerifyStream({ originalStream, contentLength, verifyData,
|
|
|
248
182
|
* @returns a wrapped fetch function that supports ar:// protocol and always returns Response
|
|
249
183
|
*/
|
|
250
184
|
export const createWayfinderClient = ({ resolveUrl, verifyData, selectGateway, emitter = new WayfinderEmitter(), logger, strict = false, }) => {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
// If it's not an ar:// URL, pass it directly to fetch
|
|
256
|
-
if (!originalUrl.startsWith('ar://')) {
|
|
257
|
-
logger?.debug('Not a wayfinder URL, passing to fetch directly', {
|
|
258
|
-
originalUrl,
|
|
185
|
+
return async (url, init) => {
|
|
186
|
+
if (typeof url !== 'string') {
|
|
187
|
+
logger?.debug('URL is not a string, skipping routing', {
|
|
188
|
+
url,
|
|
259
189
|
});
|
|
260
190
|
emitter?.emit('routing-skipped', {
|
|
261
|
-
originalUrl,
|
|
191
|
+
originalUrl: JSON.stringify(url),
|
|
262
192
|
});
|
|
263
|
-
// we don't do anything special with non-ar:// urls, just pass them through
|
|
264
193
|
return fetch(url, init);
|
|
265
194
|
}
|
|
266
|
-
// Start the routing process
|
|
267
195
|
emitter?.emit('routing-started', {
|
|
268
|
-
originalUrl,
|
|
196
|
+
originalUrl: url.toString(),
|
|
269
197
|
});
|
|
270
|
-
// Retry logic for gateway selection
|
|
271
198
|
const maxRetries = 3;
|
|
272
199
|
const retryDelay = 1000;
|
|
273
200
|
for (let i = 0; i < maxRetries; i++) {
|
|
274
201
|
try {
|
|
275
202
|
// select the target gateway
|
|
276
|
-
// TODO: we may want to provide the `path` to select gateway so the HEAD checks in routers check the existence of the actual path/request
|
|
277
203
|
const selectedGateway = await selectGateway();
|
|
278
204
|
logger?.debug('Selected gateway', {
|
|
279
|
-
originalUrl,
|
|
205
|
+
originalUrl: url,
|
|
280
206
|
selectedGateway: selectedGateway.toString(),
|
|
281
207
|
});
|
|
282
208
|
// route the request to the target gateway
|
|
283
209
|
const redirectUrl = resolveUrl({
|
|
284
|
-
originalUrl,
|
|
210
|
+
originalUrl: url,
|
|
285
211
|
selectedGateway,
|
|
286
212
|
logger,
|
|
287
213
|
});
|
|
288
214
|
emitter?.emit('routing-succeeded', {
|
|
289
|
-
originalUrl,
|
|
215
|
+
originalUrl: url,
|
|
290
216
|
selectedGateway: selectedGateway.toString(),
|
|
291
217
|
redirectUrl: redirectUrl.toString(),
|
|
292
218
|
});
|
|
293
219
|
logger?.debug(`Redirecting request`, {
|
|
294
|
-
originalUrl,
|
|
220
|
+
originalUrl: url,
|
|
295
221
|
redirectUrl: redirectUrl.toString(),
|
|
296
222
|
});
|
|
297
|
-
//
|
|
223
|
+
// make the request to the target gateway using the redirect url
|
|
298
224
|
const response = await fetch(redirectUrl.toString(), {
|
|
299
|
-
//
|
|
225
|
+
// enforce CORS given we're likely going to a different origin, but always allow the client to override
|
|
300
226
|
redirect: 'follow',
|
|
301
227
|
mode: 'cors',
|
|
302
|
-
// allow requestor to override and any additional request configuration
|
|
303
228
|
...init,
|
|
304
229
|
});
|
|
305
|
-
// TODO: update any caching we use for the request and gateway response
|
|
306
230
|
logger?.debug(`Successfully routed request to gateway`, {
|
|
307
231
|
redirectUrl: redirectUrl.toString(),
|
|
308
|
-
originalUrl,
|
|
232
|
+
originalUrl: url.toString(),
|
|
309
233
|
});
|
|
310
|
-
//
|
|
311
|
-
if (redirectUrl.toString()
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
234
|
+
// only verify data if the redirect url is different from the original url
|
|
235
|
+
if (redirectUrl.toString() !== url.toString()) {
|
|
236
|
+
if (verifyData) {
|
|
237
|
+
const headers = response.headers;
|
|
238
|
+
// transaction id is either in the response headers or the path of the request as the first parameter
|
|
239
|
+
const txId = headers.get('x-arns-resolved-id') ??
|
|
240
|
+
redirectUrl.pathname.split('/')[1];
|
|
241
|
+
const contentLength = +(headers.get('content-length') ?? 0);
|
|
242
|
+
if (!txIdRegex.test(txId)) {
|
|
243
|
+
// no transaction id found, skip verification
|
|
244
|
+
logger?.debug('No transaction id found, skipping verification', {
|
|
245
|
+
redirectUrl: redirectUrl.toString(),
|
|
246
|
+
originalUrl: url,
|
|
247
|
+
});
|
|
248
|
+
emitter?.emit('verification-skipped', {
|
|
249
|
+
originalUrl: url,
|
|
250
|
+
});
|
|
251
|
+
return response;
|
|
252
|
+
}
|
|
253
|
+
emitter?.emit('identified-transaction-id', {
|
|
254
|
+
originalUrl: url,
|
|
255
|
+
selectedGateway: redirectUrl.toString(),
|
|
256
|
+
txId,
|
|
257
|
+
});
|
|
258
|
+
// Check if the response has a body
|
|
259
|
+
if (response.body) {
|
|
260
|
+
const newClientStream = tapAndVerifyReadableStream({
|
|
261
|
+
originalStream: response.body,
|
|
262
|
+
contentLength,
|
|
263
|
+
verifyData,
|
|
264
|
+
txId,
|
|
265
|
+
emitter,
|
|
266
|
+
strict,
|
|
267
|
+
});
|
|
268
|
+
return new Response(newClientStream, {
|
|
269
|
+
status: response.status,
|
|
270
|
+
statusText: response.statusText,
|
|
271
|
+
headers: response.headers,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
// No response body to verify, skip verification
|
|
276
|
+
logger?.debug('No response body to verify', {
|
|
277
|
+
redirectUrl: redirectUrl.toString(),
|
|
278
|
+
originalUrl: url,
|
|
279
|
+
txId,
|
|
280
|
+
});
|
|
281
|
+
return response;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
347
284
|
}
|
|
348
|
-
|
|
349
|
-
originalStream: response.body,
|
|
350
|
-
contentLength,
|
|
351
|
-
verifyData,
|
|
352
|
-
txId,
|
|
353
|
-
emitter,
|
|
354
|
-
strict,
|
|
355
|
-
});
|
|
356
|
-
// wrap the response with the verified stream
|
|
357
|
-
return new Response(verifiedStream, {
|
|
358
|
-
status: response.status,
|
|
359
|
-
statusText: response.statusText,
|
|
360
|
-
// TODO: we could add identified transaction id to the headers here, but it would be changing information from the original response
|
|
361
|
-
headers: response.headers,
|
|
362
|
-
});
|
|
285
|
+
return response;
|
|
363
286
|
}
|
|
364
287
|
catch (error) {
|
|
365
288
|
logger?.debug('Failed to route request', {
|
|
366
289
|
error: error.message,
|
|
367
290
|
stack: error.stack,
|
|
368
|
-
originalUrl,
|
|
291
|
+
originalUrl: url,
|
|
369
292
|
attempt: i + 1,
|
|
370
293
|
maxRetries,
|
|
371
294
|
});
|
|
372
295
|
if (i < maxRetries - 1) {
|
|
373
296
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
374
297
|
}
|
|
375
|
-
else {
|
|
376
|
-
emitter?.emit('routing-failed', {
|
|
377
|
-
originalUrl,
|
|
378
|
-
error,
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
298
|
}
|
|
382
299
|
}
|
|
383
300
|
throw new Error('Failed to route request after max retries', {
|
|
384
301
|
cause: {
|
|
385
|
-
originalUrl,
|
|
302
|
+
originalUrl: url,
|
|
386
303
|
maxRetries,
|
|
387
304
|
},
|
|
388
305
|
});
|
|
389
306
|
};
|
|
390
|
-
return wayfinderRedirect;
|
|
391
307
|
};
|
|
392
308
|
/**
|
|
393
309
|
* The main class for the wayfinder
|
|
@@ -538,7 +454,7 @@ export class Wayfinder {
|
|
|
538
454
|
}),
|
|
539
455
|
}),
|
|
540
456
|
}), events = {
|
|
541
|
-
|
|
457
|
+
onVerificationSucceeded: (event) => {
|
|
542
458
|
logger.debug('Verification passed!', event);
|
|
543
459
|
},
|
|
544
460
|
onVerificationFailed: (event) => {
|
|
@@ -547,7 +463,7 @@ export class Wayfinder {
|
|
|
547
463
|
onVerificationProgress: (event) => {
|
|
548
464
|
logger.debug('Verification progress!', event);
|
|
549
465
|
},
|
|
550
|
-
}, strict = false, }) {
|
|
466
|
+
}, strict = false, } = {}) {
|
|
551
467
|
this.routingStrategy = routingStrategy;
|
|
552
468
|
this.gatewaysProvider = gatewaysProvider;
|
|
553
469
|
this.emitter = new WayfinderEmitter(events);
|
package/lib/esm/utils/hash.js
CHANGED
|
@@ -13,38 +13,85 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
+
import Arweave from 'arweave';
|
|
17
|
+
import { MAX_CHUNK_SIZE, MIN_CHUNK_SIZE, buildLayers, generateLeaves, } from 'arweave/node/lib/merkle.js';
|
|
16
18
|
import { createHash } from 'crypto';
|
|
17
19
|
import { toB64Url } from './base64.js';
|
|
18
|
-
export const
|
|
19
|
-
return
|
|
20
|
-
const hash = createHash(algorithm);
|
|
21
|
-
stream.on('data', (chunk) => hash.update(chunk));
|
|
22
|
-
stream.on('end', () => resolve(toB64Url(hash.digest())));
|
|
23
|
-
stream.on('error', (err) => reject(err));
|
|
24
|
-
});
|
|
20
|
+
export const isAsyncIterable = (x) => {
|
|
21
|
+
return x && typeof x[Symbol.asyncIterator] === 'function';
|
|
25
22
|
};
|
|
26
|
-
export const
|
|
27
|
-
return
|
|
28
|
-
|
|
23
|
+
export const isReadableStream = (x) => {
|
|
24
|
+
return x && typeof x.getReader === 'function';
|
|
25
|
+
};
|
|
26
|
+
// convert ReadableStream to async iterable
|
|
27
|
+
export const readableStreamToAsyncIterable = (stream) => ({
|
|
28
|
+
async *[Symbol.asyncIterator]() {
|
|
29
29
|
const reader = stream.getReader();
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
try {
|
|
31
|
+
while (true) {
|
|
32
32
|
const { done, value } = await reader.read();
|
|
33
|
-
if (done)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
hash.update(value);
|
|
38
|
-
read();
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
reject(err);
|
|
33
|
+
if (done)
|
|
34
|
+
break;
|
|
35
|
+
if (value !== undefined)
|
|
36
|
+
yield value;
|
|
43
37
|
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
reader.releaseLock();
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
export const hashDataStreamToB64Url = async (stream, algorithm = 'sha256') => {
|
|
45
|
+
const asyncIterable = isAsyncIterable(stream)
|
|
46
|
+
? stream
|
|
47
|
+
: readableStreamToAsyncIterable(stream);
|
|
48
|
+
const hash = createHash(algorithm);
|
|
49
|
+
for await (const chunk of asyncIterable) {
|
|
50
|
+
hash.update(chunk);
|
|
51
|
+
}
|
|
52
|
+
return toB64Url(hash.digest());
|
|
47
53
|
};
|
|
48
|
-
export const
|
|
49
|
-
|
|
54
|
+
export const convertDataStreamToDataRoot = async ({ dataStream, }) => {
|
|
55
|
+
const chunks = [];
|
|
56
|
+
let leftover = new Uint8Array(0);
|
|
57
|
+
let cursor = 0;
|
|
58
|
+
const asyncIterable = isAsyncIterable(dataStream)
|
|
59
|
+
? dataStream
|
|
60
|
+
: readableStreamToAsyncIterable(dataStream);
|
|
61
|
+
for await (const data of asyncIterable) {
|
|
62
|
+
const inputChunk = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
63
|
+
const combined = new Uint8Array(leftover.length + inputChunk.length);
|
|
64
|
+
combined.set(leftover, 0);
|
|
65
|
+
combined.set(inputChunk, leftover.length);
|
|
66
|
+
let startIndex = 0;
|
|
67
|
+
while (combined.length - startIndex >= MAX_CHUNK_SIZE) {
|
|
68
|
+
let chunkSize = MAX_CHUNK_SIZE;
|
|
69
|
+
const remainderAfterThis = combined.length - startIndex - MAX_CHUNK_SIZE;
|
|
70
|
+
if (remainderAfterThis > 0 && remainderAfterThis < MIN_CHUNK_SIZE) {
|
|
71
|
+
chunkSize = Math.ceil((combined.length - startIndex) / 2);
|
|
72
|
+
}
|
|
73
|
+
const chunkData = combined.slice(startIndex, startIndex + chunkSize);
|
|
74
|
+
const dataHash = await Arweave.crypto.hash(chunkData);
|
|
75
|
+
chunks.push({
|
|
76
|
+
dataHash,
|
|
77
|
+
minByteRange: cursor,
|
|
78
|
+
maxByteRange: cursor + chunkSize,
|
|
79
|
+
});
|
|
80
|
+
cursor += chunkSize;
|
|
81
|
+
startIndex += chunkSize;
|
|
82
|
+
}
|
|
83
|
+
leftover = combined.slice(startIndex);
|
|
84
|
+
}
|
|
85
|
+
if (leftover.length > 0) {
|
|
86
|
+
// TODO: ensure a web friendly crypto hash function is used in web
|
|
87
|
+
const dataHash = await Arweave.crypto.hash(leftover);
|
|
88
|
+
chunks.push({
|
|
89
|
+
dataHash,
|
|
90
|
+
minByteRange: cursor,
|
|
91
|
+
maxByteRange: cursor + leftover.length,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const leaves = await generateLeaves(chunks);
|
|
95
|
+
const root = await buildLayers(leaves);
|
|
96
|
+
return toB64Url(Buffer.from(root.id));
|
|
50
97
|
};
|
package/lib/esm/version.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
buffer: Buffer;
|
|
5
|
-
}): Promise<string>;
|
|
6
|
-
export declare const convertReadableToDataRoot: <T extends AsyncIterable<Uint8Array>>({ iterable, }: {
|
|
7
|
-
iterable: T;
|
|
1
|
+
import { DataRootProvider, DataStream, DataVerificationStrategy } from '../../../../types/wayfinder.js';
|
|
2
|
+
export declare const convertDataStreamToDataRoot: ({ dataStream, }: {
|
|
3
|
+
dataStream: DataStream;
|
|
8
4
|
}) => Promise<string>;
|
|
9
5
|
export declare class DataRootVerificationStrategy implements DataVerificationStrategy {
|
|
10
6
|
private readonly trustedDataRootProvider;
|
|
@@ -12,7 +8,7 @@ export declare class DataRootVerificationStrategy implements DataVerificationStr
|
|
|
12
8
|
trustedDataRootProvider: DataRootProvider;
|
|
13
9
|
});
|
|
14
10
|
verifyData({ data, txId, }: {
|
|
15
|
-
data:
|
|
11
|
+
data: DataStream;
|
|
16
12
|
txId: string;
|
|
17
13
|
}): Promise<void>;
|
|
18
14
|
}
|
|
@@ -13,15 +13,14 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
17
|
-
import { DataHashProvider, DataVerificationStrategy } from '../../../../types/wayfinder.js';
|
|
16
|
+
import { DataHashProvider, DataStream, DataVerificationStrategy } from '../../../../types/wayfinder.js';
|
|
18
17
|
export declare class HashVerificationStrategy implements DataVerificationStrategy {
|
|
19
18
|
private readonly trustedHashProvider;
|
|
20
19
|
constructor({ trustedHashProvider, }: {
|
|
21
20
|
trustedHashProvider: DataHashProvider;
|
|
22
21
|
});
|
|
23
22
|
verifyData({ data, txId, }: {
|
|
24
|
-
data:
|
|
23
|
+
data: DataStream;
|
|
25
24
|
txId: string;
|
|
26
25
|
}): Promise<void>;
|
|
27
26
|
}
|