@ar.io/sdk 3.11.0-beta.1 → 3.11.0
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 +111 -114
- package/lib/cjs/common/ant-versions.js +3 -2
- package/lib/cjs/common/index.js +0 -2
- package/lib/cjs/common/io.js +13 -9
- package/lib/cjs/common/turbo.js +1 -1
- package/lib/cjs/types/index.js +0 -1
- package/lib/cjs/utils/ao.js +2 -1
- package/lib/cjs/version.js +1 -1
- package/lib/esm/common/ant-versions.js +3 -2
- package/lib/esm/common/index.js +0 -2
- package/lib/esm/common/io.js +13 -9
- package/lib/esm/common/turbo.js +1 -1
- package/lib/esm/types/index.js +0 -1
- package/lib/esm/utils/ao.js +2 -1
- package/lib/esm/version.js +1 -1
- package/lib/types/common/ant-versions.d.ts +3 -5
- package/lib/types/common/index.d.ts +0 -1
- package/lib/types/types/index.d.ts +0 -1
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
- package/lib/cjs/common/wayfinder/gateways/network.js +0 -48
- package/lib/cjs/common/wayfinder/gateways/simple-cache.js +0 -35
- package/lib/cjs/common/wayfinder/gateways/static.js +0 -13
- package/lib/cjs/common/wayfinder/index.js +0 -47
- package/lib/cjs/common/wayfinder/routing/strategies/ping.js +0 -72
- package/lib/cjs/common/wayfinder/routing/strategies/ping.test.js +0 -156
- package/lib/cjs/common/wayfinder/routing/strategies/random.js +0 -13
- package/lib/cjs/common/wayfinder/routing/strategies/random.test.js +0 -68
- package/lib/cjs/common/wayfinder/routing/strategies/round-robin.js +0 -42
- package/lib/cjs/common/wayfinder/routing/strategies/round-robin.test.js +0 -78
- package/lib/cjs/common/wayfinder/routing/strategies/static.js +0 -29
- package/lib/cjs/common/wayfinder/routing/strategies/static.test.js +0 -40
- package/lib/cjs/common/wayfinder/verification/strategies/data-root-verifier.js +0 -139
- package/lib/cjs/common/wayfinder/verification/strategies/hash-verifier.js +0 -50
- package/lib/cjs/common/wayfinder/verification/trusted.js +0 -106
- package/lib/cjs/common/wayfinder/wayfinder.js +0 -736
- package/lib/cjs/common/wayfinder/wayfinder.test.js +0 -519
- package/lib/cjs/types/wayfinder.js +0 -3
- package/lib/esm/common/wayfinder/gateways/network.js +0 -44
- package/lib/esm/common/wayfinder/gateways/simple-cache.js +0 -31
- package/lib/esm/common/wayfinder/gateways/static.js +0 -9
- package/lib/esm/common/wayfinder/index.js +0 -31
- package/lib/esm/common/wayfinder/routing/strategies/ping.js +0 -68
- package/lib/esm/common/wayfinder/routing/strategies/ping.test.js +0 -151
- package/lib/esm/common/wayfinder/routing/strategies/random.js +0 -9
- package/lib/esm/common/wayfinder/routing/strategies/random.test.js +0 -63
- package/lib/esm/common/wayfinder/routing/strategies/round-robin.js +0 -38
- package/lib/esm/common/wayfinder/routing/strategies/round-robin.test.js +0 -73
- package/lib/esm/common/wayfinder/routing/strategies/static.js +0 -25
- package/lib/esm/common/wayfinder/routing/strategies/static.test.js +0 -35
- package/lib/esm/common/wayfinder/verification/strategies/data-root-verifier.js +0 -130
- package/lib/esm/common/wayfinder/verification/strategies/hash-verifier.js +0 -46
- package/lib/esm/common/wayfinder/verification/trusted.js +0 -102
- package/lib/esm/common/wayfinder/wayfinder.js +0 -724
- package/lib/esm/common/wayfinder/wayfinder.test.js +0 -514
- package/lib/esm/types/wayfinder.js +0 -2
- package/lib/types/common/wayfinder/gateways/network.d.ts +0 -33
- package/lib/types/common/wayfinder/gateways/simple-cache.d.ts +0 -31
- package/lib/types/common/wayfinder/gateways/static.d.ts +0 -23
- package/lib/types/common/wayfinder/index.d.ts +0 -26
- package/lib/types/common/wayfinder/routing/strategies/ping.d.ts +0 -27
- package/lib/types/common/wayfinder/routing/strategies/ping.test.d.ts +0 -1
- package/lib/types/common/wayfinder/routing/strategies/random.d.ts +0 -21
- package/lib/types/common/wayfinder/routing/strategies/random.test.d.ts +0 -1
- package/lib/types/common/wayfinder/routing/strategies/round-robin.d.ts +0 -29
- package/lib/types/common/wayfinder/routing/strategies/round-robin.test.d.ts +0 -1
- package/lib/types/common/wayfinder/routing/strategies/static.d.ts +0 -29
- package/lib/types/common/wayfinder/routing/strategies/static.test.d.ts +0 -1
- package/lib/types/common/wayfinder/verification/strategies/data-root-verifier.d.ts +0 -31
- package/lib/types/common/wayfinder/verification/strategies/hash-verifier.d.ts +0 -27
- package/lib/types/common/wayfinder/verification/trusted.d.ts +0 -51
- package/lib/types/common/wayfinder/wayfinder.d.ts +0 -299
- package/lib/types/common/wayfinder/wayfinder.test.d.ts +0 -1
- package/lib/types/types/wayfinder.d.ts +0 -66
|
@@ -1,736 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
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");
|
|
26
|
-
const io_js_1 = require("../io.js");
|
|
27
|
-
const logger_js_1 = require("../logger.js");
|
|
28
|
-
const network_js_1 = require("./gateways/network.js");
|
|
29
|
-
const simple_cache_js_1 = require("./gateways/simple-cache.js");
|
|
30
|
-
const static_js_1 = require("./gateways/static.js");
|
|
31
|
-
const ping_js_1 = require("./routing/strategies/ping.js");
|
|
32
|
-
const hash_verifier_js_1 = require("./verification/strategies/hash-verifier.js");
|
|
33
|
-
const trusted_js_1 = require("./verification/trusted.js");
|
|
34
|
-
// known regexes for wayfinder urls
|
|
35
|
-
exports.arnsRegex = /^[a-z0-9_-]{1,51}$/;
|
|
36
|
-
exports.txIdRegex = /^[A-Za-z0-9_-]{43}$/;
|
|
37
|
-
/**
|
|
38
|
-
* Core function that converts a wayfinder url to the proper ar-io gateway URL
|
|
39
|
-
* @param originalUrl - the wayfinder url to resolve
|
|
40
|
-
* @param selectedGateway - the target gateway to resolve the url against
|
|
41
|
-
* @returns the resolved url that can be used to make a request
|
|
42
|
-
*/
|
|
43
|
-
const resolveWayfinderUrl = ({ originalUrl, selectedGateway, logger, }) => {
|
|
44
|
-
if (originalUrl.toString().startsWith('ar://')) {
|
|
45
|
-
logger?.debug(`Applying wayfinder routing protocol to ${originalUrl}`, {
|
|
46
|
-
originalUrl,
|
|
47
|
-
});
|
|
48
|
-
const [, path] = originalUrl.toString().split('ar://');
|
|
49
|
-
// e.g. ar:///info should route to the info endpoint of the target gateway
|
|
50
|
-
if (path.startsWith('/')) {
|
|
51
|
-
logger?.debug(`Routing to ${path.slice(1)} on ${selectedGateway}`, {
|
|
52
|
-
originalUrl,
|
|
53
|
-
selectedGateway,
|
|
54
|
-
});
|
|
55
|
-
return new URL(path.slice(1), selectedGateway);
|
|
56
|
-
}
|
|
57
|
-
// TODO: this breaks 43 character named arns names - we should check a a local name cache list before resolving raw transaction ids
|
|
58
|
-
if (exports.txIdRegex.test(path)) {
|
|
59
|
-
const [txId, ...rest] = path.split('/');
|
|
60
|
-
return new URL(`${txId}${rest.join('/')}`, selectedGateway);
|
|
61
|
-
}
|
|
62
|
-
if (exports.arnsRegex.test(path)) {
|
|
63
|
-
// TODO: tests to ensure arns names support query params and paths
|
|
64
|
-
const [name, ...rest] = path.split('/');
|
|
65
|
-
const arnsUrl = `${selectedGateway.protocol}//${name}.${selectedGateway.hostname}${selectedGateway.port ? `:${selectedGateway.port}` : ''}`;
|
|
66
|
-
logger?.debug(`Routing to ${path} on ${arnsUrl}`, {
|
|
67
|
-
originalUrl,
|
|
68
|
-
selectedGateway,
|
|
69
|
-
});
|
|
70
|
-
return new URL(rest.join('/'), arnsUrl);
|
|
71
|
-
}
|
|
72
|
-
// TODO: support .eth addresses
|
|
73
|
-
// TODO: "gasless" routing via DNS TXT records (e.g. ar://gatewaypie.com -> TXT record lookup for TX ID and redirect to that gateway)
|
|
74
|
-
}
|
|
75
|
-
logger?.debug('No wayfinder routing protocol applied', {
|
|
76
|
-
originalUrl,
|
|
77
|
-
});
|
|
78
|
-
// return the original url if it's not a wayfinder url (allows you to use the wayfinder client with non-wayfinder urls)
|
|
79
|
-
return new URL(originalUrl);
|
|
80
|
-
};
|
|
81
|
-
exports.resolveWayfinderUrl = resolveWayfinderUrl;
|
|
82
|
-
class WayfinderEmitter extends node_events_1.default {
|
|
83
|
-
constructor({ onVerificationPassed, onVerificationFailed, onVerificationProgress,
|
|
84
|
-
// TODO: continue this pattern for all events
|
|
85
|
-
} = {}) {
|
|
86
|
-
super();
|
|
87
|
-
if (onVerificationPassed) {
|
|
88
|
-
this.on('verification-succeeded', onVerificationPassed);
|
|
89
|
-
}
|
|
90
|
-
if (onVerificationFailed) {
|
|
91
|
-
this.on('verification-failed', onVerificationFailed);
|
|
92
|
-
}
|
|
93
|
-
if (onVerificationProgress) {
|
|
94
|
-
this.on('verification-progress', onVerificationProgress);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
emit(event, payload) {
|
|
98
|
-
return super.emit(event, payload);
|
|
99
|
-
}
|
|
100
|
-
on(event, listener) {
|
|
101
|
-
return super.on(event, listener);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
exports.WayfinderEmitter = WayfinderEmitter;
|
|
105
|
-
function tapAndVerifyStream({ originalStream, contentLength, verifyData, txId, emitter, strict = false, }) {
|
|
106
|
-
// taps node streams
|
|
107
|
-
if (originalStream instanceof node_stream_1.Readable &&
|
|
108
|
-
typeof originalStream.pipe === 'function') {
|
|
109
|
-
const tappedClientStream = new node_stream_1.PassThrough();
|
|
110
|
-
const streamToVerify = new node_stream_1.PassThrough();
|
|
111
|
-
// kick off the verification promise, this will be awaited when the original stream ends
|
|
112
|
-
const verificationPromise = verifyData({
|
|
113
|
-
data: streamToVerify,
|
|
114
|
-
txId,
|
|
115
|
-
});
|
|
116
|
-
let bytesProcessed = 0;
|
|
117
|
-
// pipe the original stream to the verifier and the client stream
|
|
118
|
-
originalStream.on('data', (chunk) => {
|
|
119
|
-
streamToVerify.write(chunk);
|
|
120
|
-
tappedClientStream.write(chunk);
|
|
121
|
-
bytesProcessed += chunk.length;
|
|
122
|
-
// only emit if contentLength is not 0
|
|
123
|
-
if (contentLength !== 0) {
|
|
124
|
-
emitter?.emit('verification-progress', {
|
|
125
|
-
txId,
|
|
126
|
-
totalBytes: contentLength,
|
|
127
|
-
processedBytes: bytesProcessed,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
originalStream.on('end', async () => {
|
|
132
|
-
streamToVerify.end(); // triggers verifier completion and completes the verification promise
|
|
133
|
-
if (strict) {
|
|
134
|
-
// in strict mode, we wait for verification to complete before ending the client stream
|
|
135
|
-
try {
|
|
136
|
-
await verificationPromise;
|
|
137
|
-
emitter?.emit('verification-succeeded', { txId });
|
|
138
|
-
tappedClientStream.end();
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
emitter?.emit('verification-failed', { error, txId });
|
|
142
|
-
// In strict mode, destroy the client stream with the error
|
|
143
|
-
tappedClientStream.destroy(new Error('Verification failed', { cause: error }));
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
// in non-strict mode, we end the client stream immediately and handle verification asynchronously
|
|
148
|
-
tappedClientStream.end();
|
|
149
|
-
// trigger the verification promise and emit events for the result
|
|
150
|
-
verificationPromise
|
|
151
|
-
.then(() => {
|
|
152
|
-
emitter?.emit('verification-succeeded', { txId });
|
|
153
|
-
})
|
|
154
|
-
.catch((error) => {
|
|
155
|
-
emitter?.emit('verification-failed', { error, txId });
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
originalStream.on('error', (err) => {
|
|
160
|
-
// emit the verification failed event
|
|
161
|
-
emitter?.emit('verification-failed', {
|
|
162
|
-
error: err,
|
|
163
|
-
txId,
|
|
164
|
-
});
|
|
165
|
-
// destroy both streams and propagate the original stream error
|
|
166
|
-
streamToVerify.destroy(err);
|
|
167
|
-
tappedClientStream.destroy(err);
|
|
168
|
-
});
|
|
169
|
-
// send the stream to the verify function and if it errors end the client stream
|
|
170
|
-
return tappedClientStream;
|
|
171
|
-
}
|
|
172
|
-
// taps web readable streams
|
|
173
|
-
if (originalStream instanceof ReadableStream &&
|
|
174
|
-
typeof originalStream.tee === 'function') {
|
|
175
|
-
const [verifyBranch, clientBranch] = originalStream.tee();
|
|
176
|
-
// setup our promise to verify the data
|
|
177
|
-
const verificationPromise = verifyData({
|
|
178
|
-
data: verifyBranch,
|
|
179
|
-
txId,
|
|
180
|
-
});
|
|
181
|
-
let bytesProcessed = 0;
|
|
182
|
-
const reader = clientBranch.getReader();
|
|
183
|
-
const clientStreamWithVerification = new ReadableStream({
|
|
184
|
-
async pull(controller) {
|
|
185
|
-
const { done, value } = await reader.read();
|
|
186
|
-
if (done) {
|
|
187
|
-
if (strict) {
|
|
188
|
-
// in strict mode, we wait for verification to complete before closing the controller
|
|
189
|
-
try {
|
|
190
|
-
await verificationPromise;
|
|
191
|
-
emitter?.emit('verification-succeeded', { txId });
|
|
192
|
-
controller.close();
|
|
193
|
-
}
|
|
194
|
-
catch (err) {
|
|
195
|
-
emitter?.emit('verification-failed', {
|
|
196
|
-
txId,
|
|
197
|
-
error: err,
|
|
198
|
-
});
|
|
199
|
-
// In strict mode, we report the error to the client stream
|
|
200
|
-
controller.error(new Error('Verification failed', { cause: err }));
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
// in non-strict mode, we close the controller immediately and handle verification asynchronously
|
|
205
|
-
controller.close();
|
|
206
|
-
// trigger the verification promise and emit events for the result
|
|
207
|
-
verificationPromise
|
|
208
|
-
.then(() => {
|
|
209
|
-
emitter?.emit('verification-succeeded', { txId });
|
|
210
|
-
})
|
|
211
|
-
.catch((err) => {
|
|
212
|
-
emitter?.emit('verification-failed', {
|
|
213
|
-
txId,
|
|
214
|
-
error: err,
|
|
215
|
-
});
|
|
216
|
-
// we don't call controller.error() to avoid breaking the client stream
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
bytesProcessed += value.length;
|
|
222
|
-
emitter?.emit('verification-progress', {
|
|
223
|
-
txId,
|
|
224
|
-
totalBytes: contentLength,
|
|
225
|
-
processedBytes: bytesProcessed,
|
|
226
|
-
});
|
|
227
|
-
controller.enqueue(value);
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
cancel(reason) {
|
|
231
|
-
// cancel the reader regardless of verification status
|
|
232
|
-
reader.cancel(reason);
|
|
233
|
-
// emit the verification cancellation event
|
|
234
|
-
emitter?.emit('verification-failed', {
|
|
235
|
-
txId,
|
|
236
|
-
error: new Error('Verification cancelled', {
|
|
237
|
-
cause: {
|
|
238
|
-
reason,
|
|
239
|
-
},
|
|
240
|
-
}),
|
|
241
|
-
});
|
|
242
|
-
// note: we don't block or throw errors here even in strict mode
|
|
243
|
-
// since the stream is already being cancelled by the client
|
|
244
|
-
},
|
|
245
|
-
});
|
|
246
|
-
return clientStreamWithVerification;
|
|
247
|
-
}
|
|
248
|
-
throw new Error('Unsupported body type for cloning');
|
|
249
|
-
}
|
|
250
|
-
function wrapVerifiedResponse(original, newBody, txId) {
|
|
251
|
-
// Clone headers (Header objects aren't serializable)
|
|
252
|
-
const headers = new Headers();
|
|
253
|
-
original.headers.forEach((value, key) => headers.set(key, value));
|
|
254
|
-
// Create a new Response with the new body and cloned headers
|
|
255
|
-
const wrapped = new Response(newBody, {
|
|
256
|
-
status: original.status,
|
|
257
|
-
statusText: original.statusText,
|
|
258
|
-
headers,
|
|
259
|
-
});
|
|
260
|
-
// Attach txId for downstream tracking
|
|
261
|
-
wrapped.txId = txId;
|
|
262
|
-
wrapped.redirectedFrom = original.url;
|
|
263
|
-
return wrapped;
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Creates a wrapped http client that supports ar:// protocol
|
|
267
|
-
*
|
|
268
|
-
* This function leverages a Proxy to intercept calls to the http client
|
|
269
|
-
* and redirects them to the target gateway using the resolveUrl function url.
|
|
270
|
-
* It also supports the http client methods like get(), post(), put(), delete(), etc.
|
|
271
|
-
*
|
|
272
|
-
* Any URLs provided that are not wayfinder urls will be returned as is.
|
|
273
|
-
*
|
|
274
|
-
* @param httpClient - the http client to wrap (e.g. axios, fetch, got, etc.)
|
|
275
|
-
* @param resolveUrl - the function to construct the redirect url for ar:// requests
|
|
276
|
-
* @returns a wrapped http client that supports ar:// protocol
|
|
277
|
-
*/
|
|
278
|
-
const createWayfinderClient = ({ httpClient, resolveUrl, verifyData, selectGateway, emitter = new WayfinderEmitter(), logger, strict = false, }) => {
|
|
279
|
-
const wayfinderRedirect = async (fn, rawArgs) => {
|
|
280
|
-
// TODO: handle if first arg is not a string (i.e. just return the result of the function call)
|
|
281
|
-
const [originalUrl, ...rest] = rawArgs;
|
|
282
|
-
if (typeof originalUrl !== 'string') {
|
|
283
|
-
logger?.debug('Original URL is not a string, skipping routing', {
|
|
284
|
-
originalUrl,
|
|
285
|
-
});
|
|
286
|
-
emitter?.emit('routing-skipped', {
|
|
287
|
-
originalUrl: JSON.stringify(originalUrl),
|
|
288
|
-
});
|
|
289
|
-
return fn(...rawArgs);
|
|
290
|
-
}
|
|
291
|
-
emitter?.emit('routing-started', {
|
|
292
|
-
originalUrl: originalUrl.toString(),
|
|
293
|
-
});
|
|
294
|
-
// TODO: by default we will retry 3 times but this should be configurable and moved to a routing strategy
|
|
295
|
-
const maxRetries = 3;
|
|
296
|
-
const retryDelay = 1000;
|
|
297
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
298
|
-
try {
|
|
299
|
-
// select the target gateway
|
|
300
|
-
const selectedGateway = await selectGateway();
|
|
301
|
-
logger?.debug('Selected gateway', {
|
|
302
|
-
originalUrl,
|
|
303
|
-
selectedGateway: selectedGateway.toString(),
|
|
304
|
-
});
|
|
305
|
-
// route the request to the target gateway
|
|
306
|
-
const redirectUrl = resolveUrl({
|
|
307
|
-
originalUrl,
|
|
308
|
-
selectedGateway,
|
|
309
|
-
logger,
|
|
310
|
-
});
|
|
311
|
-
emitter?.emit('routing-succeeded', {
|
|
312
|
-
originalUrl,
|
|
313
|
-
selectedGateway: selectedGateway.toString(),
|
|
314
|
-
redirectUrl: redirectUrl.toString(),
|
|
315
|
-
});
|
|
316
|
-
logger?.debug(`Redirecting request`, {
|
|
317
|
-
originalUrl,
|
|
318
|
-
redirectUrl: redirectUrl.toString(),
|
|
319
|
-
});
|
|
320
|
-
// make the request to the target gateway using the redirect url and http client
|
|
321
|
-
const response = await fn(redirectUrl.toString(), ...rest);
|
|
322
|
-
// TODO: trigger a routing event with the raw response object?
|
|
323
|
-
logger?.debug(`Successfully routed request to gateway`, {
|
|
324
|
-
redirectUrl: redirectUrl.toString(),
|
|
325
|
-
originalUrl: originalUrl.toString(),
|
|
326
|
-
});
|
|
327
|
-
// only verify data if the redirect url is different from the original url
|
|
328
|
-
if (response && redirectUrl.toString() !== originalUrl.toString()) {
|
|
329
|
-
if (verifyData) {
|
|
330
|
-
// if the headers do not have .get on them, we need to parse the headers manually
|
|
331
|
-
const headers = new Headers();
|
|
332
|
-
const headersObject = response.headers ?? {};
|
|
333
|
-
if (headersObject instanceof Map) {
|
|
334
|
-
for (const [key, value] of headersObject.entries()) {
|
|
335
|
-
headers.set(key, value);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
else if (headersObject instanceof Headers) {
|
|
339
|
-
for (const [key, value] of headersObject.entries()) {
|
|
340
|
-
headers.set(key, value);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
else if (headersObject !== undefined &&
|
|
344
|
-
typeof headersObject === 'object') {
|
|
345
|
-
for (const [key, value] of Object.entries(headersObject)) {
|
|
346
|
-
headers.set(key, value);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
throw new Error('Gateway did not return headers needed for verification', {
|
|
351
|
-
cause: {
|
|
352
|
-
redirectUrl: redirectUrl.toString(),
|
|
353
|
-
originalUrl: originalUrl.toString(),
|
|
354
|
-
},
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
// transaction id is either in the response headers or the path of the request as the first parameter
|
|
358
|
-
// TODO: we may want to move this parsing to be returned by the resolveUrl function depending on the redirect URL we've constructed
|
|
359
|
-
const txId = headers.get('x-arns-resolved-id') ??
|
|
360
|
-
redirectUrl.pathname.split('/')[1];
|
|
361
|
-
// TODO: validate nodes return content length for all responses
|
|
362
|
-
const contentLength = +(headers.get('content-length') ?? 0);
|
|
363
|
-
if (!exports.txIdRegex.test(txId)) {
|
|
364
|
-
// no transaction id found, skip verification
|
|
365
|
-
logger?.debug('No transaction id found, skipping verification', {
|
|
366
|
-
redirectUrl: redirectUrl.toString(),
|
|
367
|
-
originalUrl,
|
|
368
|
-
});
|
|
369
|
-
emitter?.emit('verification-skipped', {
|
|
370
|
-
originalUrl,
|
|
371
|
-
});
|
|
372
|
-
return response;
|
|
373
|
-
}
|
|
374
|
-
emitter?.emit('identified-transaction-id', {
|
|
375
|
-
originalUrl,
|
|
376
|
-
selectedGateway: redirectUrl.toString(),
|
|
377
|
-
txId,
|
|
378
|
-
});
|
|
379
|
-
// parse out the key that contains the response body, we'll use it later when updating the response object
|
|
380
|
-
const responseDataKey = response.body
|
|
381
|
-
? 'body'
|
|
382
|
-
: response.data
|
|
383
|
-
? 'data'
|
|
384
|
-
: undefined;
|
|
385
|
-
if (responseDataKey === undefined) {
|
|
386
|
-
throw new Error('No data body or data provided, skipping verification', {
|
|
387
|
-
cause: {
|
|
388
|
-
redirectUrl: redirectUrl.toString(),
|
|
389
|
-
originalUrl: originalUrl.toString(),
|
|
390
|
-
},
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
const responseBody = response[responseDataKey];
|
|
394
|
-
// TODO: determine if it is data item or L1 transaction, and tell the verifier accordingly, just drop in hit to graphql now
|
|
395
|
-
if (txId === undefined) {
|
|
396
|
-
throw new Error('Failed to parse data hash from response headers', {
|
|
397
|
-
cause: {
|
|
398
|
-
redirectUrl: redirectUrl.toString(),
|
|
399
|
-
originalUrl: originalUrl.toString(),
|
|
400
|
-
txId,
|
|
401
|
-
},
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
else if (responseBody === undefined) {
|
|
405
|
-
throw new Error('No data body provided, skipping verification', {
|
|
406
|
-
cause: {
|
|
407
|
-
redirectUrl: redirectUrl.toString(),
|
|
408
|
-
originalUrl: originalUrl.toString(),
|
|
409
|
-
txId,
|
|
410
|
-
},
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
logger?.debug('Verifying data hash for txId', {
|
|
415
|
-
redirectUrl: redirectUrl.toString(),
|
|
416
|
-
originalUrl: originalUrl.toString(),
|
|
417
|
-
txId,
|
|
418
|
-
});
|
|
419
|
-
if (responseBody instanceof ReadableStream ||
|
|
420
|
-
responseBody instanceof node_stream_1.Readable) {
|
|
421
|
-
const newClientStream = tapAndVerifyStream({
|
|
422
|
-
originalStream: responseBody,
|
|
423
|
-
contentLength,
|
|
424
|
-
verifyData,
|
|
425
|
-
txId,
|
|
426
|
-
emitter,
|
|
427
|
-
strict,
|
|
428
|
-
});
|
|
429
|
-
if (response instanceof Response) {
|
|
430
|
-
// specific to fetch
|
|
431
|
-
return wrapVerifiedResponse(response, newClientStream, txId);
|
|
432
|
-
}
|
|
433
|
-
else {
|
|
434
|
-
// overwrite the response body with the new client stream
|
|
435
|
-
response.txId = txId;
|
|
436
|
-
response.body = newClientStream;
|
|
437
|
-
return response;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
else {
|
|
441
|
-
// TODO: content-application/json and it's smaller than 10mb
|
|
442
|
-
// TODO: add tests and verify this works for all non-Readable/streamed responses
|
|
443
|
-
if (strict) {
|
|
444
|
-
// In strict mode, wait for verification before returning response
|
|
445
|
-
try {
|
|
446
|
-
await verifyData({
|
|
447
|
-
data: responseBody,
|
|
448
|
-
txId,
|
|
449
|
-
});
|
|
450
|
-
emitter?.emit('verification-succeeded', { txId });
|
|
451
|
-
return response;
|
|
452
|
-
}
|
|
453
|
-
catch (error) {
|
|
454
|
-
logger?.debug('Failed to verify data hash', {
|
|
455
|
-
error,
|
|
456
|
-
txId,
|
|
457
|
-
});
|
|
458
|
-
emitter?.emit('verification-failed', { txId, error });
|
|
459
|
-
throw new Error('Verification failed', { cause: error });
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
else {
|
|
463
|
-
// In non-strict mode, perform verification in the background
|
|
464
|
-
verifyData({
|
|
465
|
-
data: responseBody,
|
|
466
|
-
txId,
|
|
467
|
-
})
|
|
468
|
-
.then(() => {
|
|
469
|
-
emitter?.emit('verification-succeeded', { txId });
|
|
470
|
-
})
|
|
471
|
-
.catch((error) => {
|
|
472
|
-
logger?.debug('Failed to verify data hash', {
|
|
473
|
-
error,
|
|
474
|
-
txId,
|
|
475
|
-
});
|
|
476
|
-
emitter?.emit('verification-failed', { txId, error });
|
|
477
|
-
});
|
|
478
|
-
return response;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
// TODO: if strict - wait for verification to finish and succeed before returning the response
|
|
485
|
-
return response;
|
|
486
|
-
}
|
|
487
|
-
catch (error) {
|
|
488
|
-
logger?.debug('Failed to route request', {
|
|
489
|
-
error: error.message,
|
|
490
|
-
stack: error.stack,
|
|
491
|
-
originalUrl,
|
|
492
|
-
attempt: i + 1,
|
|
493
|
-
maxRetries,
|
|
494
|
-
});
|
|
495
|
-
if (i < maxRetries - 1) {
|
|
496
|
-
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
throw new Error('Failed to route request after max retries', {
|
|
501
|
-
cause: {
|
|
502
|
-
originalUrl,
|
|
503
|
-
maxRetries,
|
|
504
|
-
},
|
|
505
|
-
});
|
|
506
|
-
};
|
|
507
|
-
return new Proxy(httpClient, {
|
|
508
|
-
// support direct calls: fetch('ar://…', options)
|
|
509
|
-
// axios() or got()
|
|
510
|
-
apply: (_target, _thisArg, argArray) => wayfinderRedirect(httpClient, argArray),
|
|
511
|
-
// support http clients that use functions like `got.get`, `got.post`, `axios.get`, etc. while still using the wayfinder redirect function
|
|
512
|
-
get: (target, prop, receiver) => {
|
|
513
|
-
const value = Reflect.get(target, prop, receiver);
|
|
514
|
-
if (typeof value === 'function') {
|
|
515
|
-
return (...inner) => wayfinderRedirect(value.bind(target), inner);
|
|
516
|
-
}
|
|
517
|
-
return value; // numbers, objects, symbols pass through untouched
|
|
518
|
-
},
|
|
519
|
-
});
|
|
520
|
-
};
|
|
521
|
-
exports.createWayfinderClient = createWayfinderClient;
|
|
522
|
-
/**
|
|
523
|
-
* The main class for the wayfinder
|
|
524
|
-
* @param router - the router to use for requests
|
|
525
|
-
* @param httpClient - the http client to use for requests
|
|
526
|
-
* @param blocklist - the blocklist of gateways to avoid
|
|
527
|
-
*/
|
|
528
|
-
class Wayfinder {
|
|
529
|
-
/**
|
|
530
|
-
* The native http client used by wayfinder. By default, the native fetch api is used.
|
|
531
|
-
*
|
|
532
|
-
* @example
|
|
533
|
-
* const wayfinder = new Wayfinder({
|
|
534
|
-
* httpClient: axios,
|
|
535
|
-
* });
|
|
536
|
-
*
|
|
537
|
-
*/
|
|
538
|
-
httpClient;
|
|
539
|
-
/**
|
|
540
|
-
* The gateways provider is responsible for providing the list of gateways to use for routing requests.
|
|
541
|
-
*
|
|
542
|
-
* @example
|
|
543
|
-
* const wayfinder = new Wayfinder({
|
|
544
|
-
* gatewaysProvider: new SimpleCacheGatewaysProvider({
|
|
545
|
-
* gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
|
|
546
|
-
* ttlSeconds: 60 * 60 * 24, // 1 day
|
|
547
|
-
* }),
|
|
548
|
-
* });
|
|
549
|
-
*/
|
|
550
|
-
gatewaysProvider;
|
|
551
|
-
/**
|
|
552
|
-
* The routing strategy to use when routing requests.
|
|
553
|
-
*
|
|
554
|
-
* @example
|
|
555
|
-
* const wayfinder = new Wayfinder({
|
|
556
|
-
* strategy: new FastestPingStrategy({
|
|
557
|
-
* timeoutMs: 1000,
|
|
558
|
-
* }),
|
|
559
|
-
* });
|
|
560
|
-
*/
|
|
561
|
-
routingStrategy;
|
|
562
|
-
/**
|
|
563
|
-
* A helper function that resolves the redirect url for ar:// requests to a target gateway.
|
|
564
|
-
*
|
|
565
|
-
* Note: no verification is done when resolving an ar://<path> url to a wayfinder route.
|
|
566
|
-
* In order to verify the data, you must use the `request` function or request the data and
|
|
567
|
-
* verify it yourself via the `verifyData` function.
|
|
568
|
-
*
|
|
569
|
-
* @example
|
|
570
|
-
* const { resolveUrl } = new Wayfinder();
|
|
571
|
-
*
|
|
572
|
-
* // returns the redirected URL based on the routing strategy and the original url
|
|
573
|
-
* const redirectUrl = await resolveUrl({ originalUrl: 'ar://example' });
|
|
574
|
-
*
|
|
575
|
-
* window.open(redirectUrl.toString(), '_blank');
|
|
576
|
-
*/
|
|
577
|
-
resolveUrl;
|
|
578
|
-
/**
|
|
579
|
-
*
|
|
580
|
-
* A wrapped http client that supports ar:// protocol. If a verification strategy is provided,
|
|
581
|
-
* the request will be verified and events will be emitted as the request is processed.
|
|
582
|
-
*
|
|
583
|
-
* @example
|
|
584
|
-
* const wayfinder = new Wayfinder({
|
|
585
|
-
* verificationStrategy: new HashVerificationStrategy({
|
|
586
|
-
* trustedHashProvider: new TrustedGatewaysHashProvider({
|
|
587
|
-
* gatewaysProvider: new StaticGatewaysProvider({
|
|
588
|
-
* gateways: ['https://permagate.io'],
|
|
589
|
-
* }),
|
|
590
|
-
* }),
|
|
591
|
-
* }),
|
|
592
|
-
* })
|
|
593
|
-
*
|
|
594
|
-
* // request an arns name
|
|
595
|
-
* const response = await wayfinder.request('ar://ardrive')
|
|
596
|
-
*
|
|
597
|
-
* // request a transaction id
|
|
598
|
-
* const response = await wayfinder.request('ar://1234567890')
|
|
599
|
-
*
|
|
600
|
-
* // request a transaction id with a custom http client
|
|
601
|
-
* const response = await wayfinder.request('ar://1234567890')
|
|
602
|
-
*
|
|
603
|
-
* // Set strict mode to true to make verification blocking
|
|
604
|
-
* const wayfinder = new Wayfinder({
|
|
605
|
-
* strict: true,
|
|
606
|
-
* });
|
|
607
|
-
*
|
|
608
|
-
* // This will throw an error if verification fails
|
|
609
|
-
* try {
|
|
610
|
-
* const response = await wayfinder.request('ar://1234567890');
|
|
611
|
-
* } catch (error) {
|
|
612
|
-
* console.error('Verification failed', error);
|
|
613
|
-
* }
|
|
614
|
-
*/
|
|
615
|
-
request;
|
|
616
|
-
/**
|
|
617
|
-
* The function that verifies the data hash for a given transaction id.
|
|
618
|
-
*
|
|
619
|
-
* @example
|
|
620
|
-
* const wayfinder = new Wayfinder({
|
|
621
|
-
* verifyData: (data, txId) => {
|
|
622
|
-
* // some custom verification logic
|
|
623
|
-
* return true;
|
|
624
|
-
* },
|
|
625
|
-
* });
|
|
626
|
-
*/
|
|
627
|
-
verifyData;
|
|
628
|
-
/**
|
|
629
|
-
* Whether verification should be strict (blocking) or not.
|
|
630
|
-
* If true, verification failures will cause requests to fail.
|
|
631
|
-
* If false, verification will be performed asynchronously and failures will only emit events.
|
|
632
|
-
*/
|
|
633
|
-
strict;
|
|
634
|
-
/**
|
|
635
|
-
* The event emitter for wayfinder that emits verification events.
|
|
636
|
-
*
|
|
637
|
-
* const wayfinder = new Wayfinder()
|
|
638
|
-
*
|
|
639
|
-
* wayfinder.emitter.on('verification-succeeded', (event) => {
|
|
640
|
-
* console.log('Verification passed!', event);
|
|
641
|
-
* })
|
|
642
|
-
*
|
|
643
|
-
* wayfinder.emitter.on('verification-failed', (event) => {
|
|
644
|
-
* console.log('Verification failed!', event);
|
|
645
|
-
* })
|
|
646
|
-
*
|
|
647
|
-
* or implement the events interface and pass it in, using callback functions
|
|
648
|
-
*
|
|
649
|
-
* const wayfinder = new Wayfinder({
|
|
650
|
-
* events: {
|
|
651
|
-
* onVerificationPassed: (event) => {
|
|
652
|
-
* console.log('Verification passed!', event);
|
|
653
|
-
* },
|
|
654
|
-
* onVerificationFailed: (event) => {
|
|
655
|
-
* console.log('Verification failed!', event);
|
|
656
|
-
* },
|
|
657
|
-
* onVerificationProgress: (event) => {
|
|
658
|
-
* console.log('Verification progress!', event);
|
|
659
|
-
* },
|
|
660
|
-
* }
|
|
661
|
-
* })
|
|
662
|
-
*
|
|
663
|
-
* const response = await wayfind('ar://example');
|
|
664
|
-
*/
|
|
665
|
-
emitter;
|
|
666
|
-
/**
|
|
667
|
-
* The constructor for the wayfinder
|
|
668
|
-
* @param httpClient - the http client to use for requests
|
|
669
|
-
* @param routingStrategy - the routing strategy to use for requests
|
|
670
|
-
* @param verificationStrategy - the verification strategy to use for requests
|
|
671
|
-
* @param gatewaysProvider - the gateways provider to use for routing requests
|
|
672
|
-
* @param logger - the logger to use for logging
|
|
673
|
-
* @param strict - if true, verification will be blocking and will fail requests if verification fails; if false, verification will be non-blocking
|
|
674
|
-
*/
|
|
675
|
-
constructor({ httpClient = fetch, logger = logger_js_1.Logger.default, gatewaysProvider = new simple_cache_js_1.SimpleCacheGatewaysProvider({
|
|
676
|
-
gatewaysProvider: new network_js_1.NetworkGatewaysProvider({
|
|
677
|
-
ario: io_js_1.ARIO.mainnet(),
|
|
678
|
-
}),
|
|
679
|
-
ttlSeconds: 60 * 60, // 1 hour
|
|
680
|
-
}), routingStrategy = new ping_js_1.FastestPingRoutingStrategy({
|
|
681
|
-
timeoutMs: 1000,
|
|
682
|
-
}), verificationStrategy = new hash_verifier_js_1.HashVerificationStrategy({
|
|
683
|
-
trustedHashProvider: new trusted_js_1.TrustedGatewaysHashProvider({
|
|
684
|
-
gatewaysProvider: new static_js_1.StaticGatewaysProvider({
|
|
685
|
-
gateways: ['https://permagate.io'],
|
|
686
|
-
}),
|
|
687
|
-
}),
|
|
688
|
-
}), events = {
|
|
689
|
-
onVerificationPassed: (event) => {
|
|
690
|
-
logger.debug('Verification passed!', event);
|
|
691
|
-
},
|
|
692
|
-
onVerificationFailed: (event) => {
|
|
693
|
-
logger.error('Verification failed!', event);
|
|
694
|
-
},
|
|
695
|
-
onVerificationProgress: (event) => {
|
|
696
|
-
logger.debug('Verification progress!', event);
|
|
697
|
-
},
|
|
698
|
-
}, strict = false,
|
|
699
|
-
// TODO: stats provider
|
|
700
|
-
}) {
|
|
701
|
-
this.routingStrategy = routingStrategy;
|
|
702
|
-
this.gatewaysProvider = gatewaysProvider;
|
|
703
|
-
this.httpClient = httpClient;
|
|
704
|
-
this.emitter = new WayfinderEmitter(events);
|
|
705
|
-
this.verifyData =
|
|
706
|
-
verificationStrategy.verifyData.bind(verificationStrategy);
|
|
707
|
-
this.strict = strict;
|
|
708
|
-
// top level function to easily resolve wayfinder urls using the routing strategy and gateways provider
|
|
709
|
-
this.resolveUrl = async ({ originalUrl, logger }) => {
|
|
710
|
-
const selectedGateway = await this.routingStrategy.selectGateway({
|
|
711
|
-
gateways: await this.gatewaysProvider.getGateways(),
|
|
712
|
-
});
|
|
713
|
-
return (0, exports.resolveWayfinderUrl)({
|
|
714
|
-
originalUrl,
|
|
715
|
-
selectedGateway,
|
|
716
|
-
logger,
|
|
717
|
-
});
|
|
718
|
-
};
|
|
719
|
-
// create a wayfinder client with the routing strategy and gateways provider
|
|
720
|
-
this.request = (0, exports.createWayfinderClient)({
|
|
721
|
-
httpClient,
|
|
722
|
-
selectGateway: async () => {
|
|
723
|
-
return this.routingStrategy.selectGateway({
|
|
724
|
-
gateways: await this.gatewaysProvider.getGateways(),
|
|
725
|
-
});
|
|
726
|
-
},
|
|
727
|
-
resolveUrl: exports.resolveWayfinderUrl,
|
|
728
|
-
verifyData: this.verifyData,
|
|
729
|
-
emitter: this.emitter,
|
|
730
|
-
logger,
|
|
731
|
-
strict,
|
|
732
|
-
});
|
|
733
|
-
logger?.debug(`Wayfinder initialized with ${routingStrategy.constructor.name} routing strategy`);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
exports.Wayfinder = Wayfinder;
|