@ar.io/sdk 3.11.0-alpha.6 → 3.11.0-alpha.8
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 +119 -116
- package/lib/cjs/common/io.js +8 -3
- package/lib/cjs/common/wayfinder/gateways/trusted-gateways.js +106 -0
- package/lib/cjs/common/wayfinder/gateways.js +19 -8
- package/lib/cjs/common/wayfinder/index.js +7 -1
- package/lib/cjs/common/wayfinder/routers/priority.js +11 -20
- package/lib/cjs/common/wayfinder/routers/priority.test.js +5 -5
- package/lib/cjs/common/wayfinder/routers/random.js +2 -2
- package/lib/cjs/common/wayfinder/routers/random.test.js +5 -84
- package/lib/cjs/common/wayfinder/routers/{fixed.js → static.js} +5 -5
- package/lib/cjs/common/wayfinder/routers/{fixed.test.js → static.test.js} +4 -4
- package/lib/cjs/common/wayfinder/verification/data-root-verifier.js +139 -0
- package/lib/cjs/common/wayfinder/verification/hash-verifier.js +50 -0
- package/lib/cjs/common/wayfinder/wayfinder.js +408 -19
- package/lib/cjs/common/wayfinder/wayfinder.test.js +296 -49
- package/lib/cjs/types/wayfinder.js +1 -0
- package/lib/cjs/utils/arweave.js +1 -1
- package/lib/cjs/utils/hash.js +56 -0
- package/lib/cjs/version.js +1 -1
- package/lib/esm/common/io.js +8 -3
- package/lib/esm/common/wayfinder/gateways/trusted-gateways.js +102 -0
- package/lib/esm/common/wayfinder/gateways.js +17 -6
- package/lib/esm/common/wayfinder/index.js +7 -1
- package/lib/esm/common/wayfinder/routers/priority.js +11 -20
- package/lib/esm/common/wayfinder/routers/priority.test.js +5 -5
- package/lib/esm/common/wayfinder/routers/random.js +2 -2
- package/lib/esm/common/wayfinder/routers/random.test.js +5 -84
- package/lib/esm/common/wayfinder/routers/{fixed.js → static.js} +3 -3
- package/lib/esm/common/wayfinder/routers/{fixed.test.js → static.test.js} +4 -4
- package/lib/esm/common/wayfinder/verification/data-root-verifier.js +130 -0
- package/lib/esm/common/wayfinder/verification/hash-verifier.js +46 -0
- package/lib/esm/common/wayfinder/wayfinder.js +402 -19
- package/lib/esm/common/wayfinder/wayfinder.test.js +297 -50
- package/lib/esm/types/wayfinder.js +1 -0
- package/lib/esm/utils/arweave.js +1 -1
- package/lib/esm/utils/hash.js +50 -0
- package/lib/esm/version.js +1 -1
- package/lib/types/common/wayfinder/gateways/trusted-gateways.d.ts +51 -0
- package/lib/types/common/wayfinder/gateways.d.ts +16 -7
- package/lib/types/common/wayfinder/index.d.ts +4 -1
- package/lib/types/common/wayfinder/routers/priority.d.ts +8 -12
- package/lib/types/common/wayfinder/routers/{fixed.d.ts → static.d.ts} +3 -3
- package/lib/types/common/wayfinder/verification/data-root-verifier.d.ts +31 -0
- package/lib/types/common/wayfinder/verification/hash-verifier.d.ts +27 -0
- package/lib/types/common/wayfinder/wayfinder.d.ts +148 -10
- package/lib/types/types/wayfinder.d.ts +43 -0
- package/lib/types/utils/hash.d.ts +4 -0
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
- /package/lib/types/common/wayfinder/routers/{fixed.test.d.ts → static.test.d.ts} +0 -0
|
@@ -1,13 +1,37 @@
|
|
|
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");
|
|
5
27
|
const logger_js_1 = require("../logger.js");
|
|
6
28
|
const gateways_js_1 = require("./gateways.js");
|
|
29
|
+
const trusted_gateways_js_1 = require("./gateways/trusted-gateways.js");
|
|
7
30
|
const random_js_1 = require("./routers/random.js");
|
|
31
|
+
const hash_verifier_js_1 = require("./verification/hash-verifier.js");
|
|
8
32
|
// known regexes for wayfinder urls
|
|
9
33
|
exports.arnsRegex = /^[a-z0-9_-]{1,51}$/;
|
|
10
|
-
exports.txIdRegex = /^[
|
|
34
|
+
exports.txIdRegex = /^[A-Za-z0-9_-]{43}$/;
|
|
11
35
|
/**
|
|
12
36
|
* Core function to resolve a wayfinder url against a target gateway
|
|
13
37
|
* @param originalUrl - the wayfinder url to resolve
|
|
@@ -58,6 +82,155 @@ const resolveWayfinderUrl = async ({ originalUrl, targetGateway, logger, }) => {
|
|
|
58
82
|
return new URL(originalUrl);
|
|
59
83
|
};
|
|
60
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
|
+
}
|
|
61
234
|
/**
|
|
62
235
|
* Creates a wrapped http client that supports ar:// protocol
|
|
63
236
|
*
|
|
@@ -71,26 +244,164 @@ exports.resolveWayfinderUrl = resolveWayfinderUrl;
|
|
|
71
244
|
* @param resolveUrl - the function to construct the redirect url for ar:// requests
|
|
72
245
|
* @returns a wrapped http client that supports ar:// protocol
|
|
73
246
|
*/
|
|
74
|
-
const createWayfinderClient = ({ httpClient, resolveUrl, logger, }) => {
|
|
247
|
+
const createWayfinderClient = ({ httpClient, resolveUrl, verifyData, emitter = new WayfinderEmitter(), logger, }) => {
|
|
75
248
|
const wayfinderRedirect = async (fn, rawArgs) => {
|
|
76
249
|
// TODO: handle if first arg is not a string (i.e. just return the result of the function call)
|
|
77
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
|
+
});
|
|
78
260
|
// route the request to the target gateway
|
|
79
261
|
const redirectUrl = await resolveUrl({
|
|
80
262
|
originalUrl,
|
|
81
263
|
logger,
|
|
82
264
|
});
|
|
265
|
+
emitter?.emit('routing-succeeded', {
|
|
266
|
+
originalUrl,
|
|
267
|
+
targetGateway: redirectUrl.toString(),
|
|
268
|
+
});
|
|
83
269
|
logger?.debug(`Redirecting request to ${redirectUrl}`, {
|
|
84
270
|
originalUrl,
|
|
85
271
|
redirectUrl,
|
|
86
272
|
});
|
|
87
273
|
// make the request to the target gateway using the redirect url and http client
|
|
88
274
|
const response = await fn(redirectUrl.toString(), ...rest);
|
|
275
|
+
// TODO: trigger a routing event with the raw response object?
|
|
89
276
|
logger?.debug(`Successfully routed request to ${redirectUrl}`, {
|
|
90
277
|
redirectUrl,
|
|
91
278
|
originalUrl,
|
|
92
279
|
});
|
|
93
|
-
//
|
|
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
|
|
94
405
|
return response;
|
|
95
406
|
};
|
|
96
407
|
return new Proxy(httpClient, {
|
|
@@ -121,21 +432,31 @@ class Wayfinder {
|
|
|
121
432
|
* @example
|
|
122
433
|
* const wayfinder = new Wayfinder({
|
|
123
434
|
* router: new RandomGatewayRouter({
|
|
124
|
-
* gatewaysProvider: new
|
|
435
|
+
* gatewaysProvider: new SimpleCacheGatewaysProvider({
|
|
436
|
+
* gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
|
|
437
|
+
* ttlSeconds: 60 * 60 * 24, // 1 day
|
|
438
|
+
* }),
|
|
125
439
|
* }),
|
|
126
440
|
* });
|
|
441
|
+
*
|
|
442
|
+
* // Returns a target gateway based on the routing strategy
|
|
443
|
+
* const targetGateway = await wayfinder.router.getTargetGateway();
|
|
127
444
|
*/
|
|
128
445
|
router;
|
|
129
446
|
/**
|
|
130
|
-
* The http client
|
|
447
|
+
* The native http client used by wayfinder
|
|
131
448
|
*
|
|
132
449
|
* @example
|
|
133
450
|
* const wayfinder = new Wayfinder({
|
|
134
451
|
* router: new RandomGatewayRouter({
|
|
135
|
-
* gatewaysProvider: new
|
|
452
|
+
* gatewaysProvider: new SimpleCacheGatewaysProvider({
|
|
453
|
+
* gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
|
|
454
|
+
* ttlSeconds: 60 * 60 * 24, // 1 day
|
|
455
|
+
* }),
|
|
136
456
|
* }),
|
|
137
457
|
* httpClient: axios,
|
|
138
458
|
* });
|
|
459
|
+
*
|
|
139
460
|
*/
|
|
140
461
|
httpClient;
|
|
141
462
|
/**
|
|
@@ -144,11 +465,15 @@ class Wayfinder {
|
|
|
144
465
|
* @example
|
|
145
466
|
* const wayfinder = new Wayfinder({
|
|
146
467
|
* router: new RandomGatewayRouter({
|
|
147
|
-
* gatewaysProvider: new
|
|
468
|
+
* gatewaysProvider: new SimpleCacheGatewaysProvider({
|
|
469
|
+
* gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
|
|
470
|
+
* ttlSeconds: 60 * 60 * 24, // 1 day
|
|
471
|
+
* }),
|
|
148
472
|
* }),
|
|
149
473
|
* httpClient: axios,
|
|
150
474
|
* });
|
|
151
475
|
*
|
|
476
|
+
* // returns the redirected URL based on the routing strategy and the original url
|
|
152
477
|
* const redirectUrl = await wayfinder.resolveUrl({ originalUrl: 'ar://example' });
|
|
153
478
|
*/
|
|
154
479
|
resolveUrl;
|
|
@@ -158,7 +483,10 @@ class Wayfinder {
|
|
|
158
483
|
* @example
|
|
159
484
|
* const { request: wayfind } = new Wayfinder({
|
|
160
485
|
* router: new RandomGatewayRouter({
|
|
161
|
-
* gatewaysProvider: new
|
|
486
|
+
* gatewaysProvider: new SimpleCacheGatewaysProvider({
|
|
487
|
+
* gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
|
|
488
|
+
* ttlSeconds: 60 * 60 * 24, // 1 day
|
|
489
|
+
* }),
|
|
162
490
|
* }),
|
|
163
491
|
* httpClient: axios,
|
|
164
492
|
* });;
|
|
@@ -173,21 +501,62 @@ class Wayfinder {
|
|
|
173
501
|
request;
|
|
174
502
|
// TODO: stats provider
|
|
175
503
|
// TODO: metricsProvider for otel/prom support
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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,
|
|
181
539
|
// TODO: consider changing router to routingStrategy or strategy
|
|
182
540
|
router = new random_js_1.RandomGatewayRouter({
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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,
|
|
187
554
|
// TODO: stats provider
|
|
188
555
|
}) {
|
|
189
556
|
this.router = router;
|
|
190
557
|
this.httpClient = httpClient;
|
|
558
|
+
this.emitter = new WayfinderEmitter(events);
|
|
559
|
+
this.verifyData = verifier.verifyData.bind(verifier);
|
|
191
560
|
this.resolveUrl = async ({ originalUrl, logger }) => {
|
|
192
561
|
return (0, exports.resolveWayfinderUrl)({
|
|
193
562
|
originalUrl,
|
|
@@ -198,10 +567,30 @@ class Wayfinder {
|
|
|
198
567
|
this.request = (0, exports.createWayfinderClient)({
|
|
199
568
|
httpClient,
|
|
200
569
|
resolveUrl: this.resolveUrl,
|
|
570
|
+
verifyData: this.verifyData,
|
|
571
|
+
emitter: this.emitter,
|
|
201
572
|
logger,
|
|
202
|
-
// TODO: provide the verifyDataHash function from the verifier to the wayfinder client along with verificationSettings
|
|
203
573
|
});
|
|
204
574
|
logger?.debug(`Wayfinder initialized with ${router.name} routing strategy`);
|
|
205
575
|
}
|
|
206
576
|
}
|
|
207
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
|
+
*/
|