@ar.io/sdk 3.12.0-beta.6 → 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 +134 -134
- 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/wayfinder.js +87 -184
- 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/wayfinder.js +85 -179
- 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 +40 -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 +1 -1
|
@@ -3,8 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.DataRootVerificationStrategy = exports.
|
|
7
|
-
exports.convertBufferToDataRoot = convertBufferToDataRoot;
|
|
6
|
+
exports.DataRootVerificationStrategy = exports.convertDataStreamToDataRoot = void 0;
|
|
8
7
|
/**
|
|
9
8
|
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
|
|
10
9
|
*
|
|
@@ -22,39 +21,16 @@ exports.convertBufferToDataRoot = convertBufferToDataRoot;
|
|
|
22
21
|
*/
|
|
23
22
|
const arweave_1 = __importDefault(require("arweave"));
|
|
24
23
|
const merkle_js_1 = require("arweave/node/lib/merkle.js");
|
|
25
|
-
const node_stream_1 = require("node:stream");
|
|
26
24
|
const base64_js_1 = require("../../../../utils/base64.js");
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let cursor = 0;
|
|
30
|
-
let offset = 0;
|
|
31
|
-
while (offset < buffer.byteLength) {
|
|
32
|
-
let chunkSize = Math.min(merkle_js_1.MAX_CHUNK_SIZE, buffer.byteLength - offset);
|
|
33
|
-
const remainder = buffer.byteLength - offset - chunkSize;
|
|
34
|
-
if (remainder > 0 && remainder < merkle_js_1.MIN_CHUNK_SIZE) {
|
|
35
|
-
chunkSize = Math.ceil((buffer.byteLength - offset) / 2);
|
|
36
|
-
}
|
|
37
|
-
// subarray does not exist on web Buffer type
|
|
38
|
-
const slice = buffer.subarray(offset, offset + chunkSize);
|
|
39
|
-
const hash = await crypto.subtle.digest('SHA-256', slice);
|
|
40
|
-
const hashArray = new Uint8Array(hash);
|
|
41
|
-
chunks.push({
|
|
42
|
-
dataHash: hashArray,
|
|
43
|
-
minByteRange: cursor,
|
|
44
|
-
maxByteRange: cursor + chunkSize,
|
|
45
|
-
});
|
|
46
|
-
cursor += chunkSize;
|
|
47
|
-
offset += chunkSize;
|
|
48
|
-
}
|
|
49
|
-
const leaves = await (0, merkle_js_1.generateLeaves)(chunks);
|
|
50
|
-
const result = await (0, merkle_js_1.buildLayers)(leaves);
|
|
51
|
-
return Buffer.from(result.id).toString('base64url');
|
|
52
|
-
}
|
|
53
|
-
const convertReadableToDataRoot = async ({ iterable, }) => {
|
|
25
|
+
const hash_js_1 = require("../../../../utils/hash.js");
|
|
26
|
+
const convertDataStreamToDataRoot = async ({ dataStream, }) => {
|
|
54
27
|
const chunks = [];
|
|
55
28
|
let leftover = new Uint8Array(0);
|
|
56
29
|
let cursor = 0;
|
|
57
|
-
|
|
30
|
+
const asyncIterable = (0, hash_js_1.isAsyncIterable)(dataStream)
|
|
31
|
+
? dataStream
|
|
32
|
+
: (0, hash_js_1.readableStreamToAsyncIterable)(dataStream);
|
|
33
|
+
for await (const data of asyncIterable) {
|
|
58
34
|
const inputChunk = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
59
35
|
const combined = new Uint8Array(leftover.length + inputChunk.length);
|
|
60
36
|
combined.set(leftover, 0);
|
|
@@ -79,6 +55,7 @@ const convertReadableToDataRoot = async ({ iterable, }) => {
|
|
|
79
55
|
leftover = combined.slice(startIndex);
|
|
80
56
|
}
|
|
81
57
|
if (leftover.length > 0) {
|
|
58
|
+
// TODO: ensure a web friendly crypto hash function is used in web
|
|
82
59
|
const dataHash = await arweave_1.default.crypto.hash(leftover);
|
|
83
60
|
chunks.push({
|
|
84
61
|
dataHash,
|
|
@@ -90,27 +67,21 @@ const convertReadableToDataRoot = async ({ iterable, }) => {
|
|
|
90
67
|
const root = await (0, merkle_js_1.buildLayers)(leaves);
|
|
91
68
|
return (0, base64_js_1.toB64Url)(Buffer.from(root.id));
|
|
92
69
|
};
|
|
93
|
-
exports.
|
|
70
|
+
exports.convertDataStreamToDataRoot = convertDataStreamToDataRoot;
|
|
94
71
|
class DataRootVerificationStrategy {
|
|
95
72
|
trustedDataRootProvider;
|
|
96
73
|
constructor({ trustedDataRootProvider, }) {
|
|
97
74
|
this.trustedDataRootProvider = trustedDataRootProvider;
|
|
98
75
|
}
|
|
99
76
|
async verifyData({ data, txId, }) {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
computedDataRoot = await (0, exports.convertReadableToDataRoot)({ iterable: data });
|
|
109
|
-
}
|
|
110
|
-
if (computedDataRoot === undefined) {
|
|
111
|
-
throw new Error('Data root could not be computed');
|
|
112
|
-
}
|
|
113
|
-
const trustedDataRoot = await trustedDataRootPromise;
|
|
77
|
+
const [computedDataRoot, trustedDataRoot] = await Promise.all([
|
|
78
|
+
(0, exports.convertDataStreamToDataRoot)({
|
|
79
|
+
dataStream: data,
|
|
80
|
+
}),
|
|
81
|
+
this.trustedDataRootProvider.getDataRoot({
|
|
82
|
+
txId,
|
|
83
|
+
}),
|
|
84
|
+
]);
|
|
114
85
|
if (computedDataRoot !== trustedDataRoot) {
|
|
115
86
|
throw new Error('Data root does not match', {
|
|
116
87
|
cause: { computedDataRoot, trustedDataRoot },
|
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HashVerificationStrategy = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
|
|
6
|
-
*
|
|
7
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
-
* you may not use this file except in compliance with the License.
|
|
9
|
-
* You may obtain a copy of the License at
|
|
10
|
-
*
|
|
11
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
-
*
|
|
13
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
* See the License for the specific language governing permissions and
|
|
17
|
-
* limitations under the License.
|
|
18
|
-
*/
|
|
19
|
-
const node_stream_1 = require("node:stream");
|
|
20
4
|
const hash_js_1 = require("../../../../utils/hash.js");
|
|
21
5
|
class HashVerificationStrategy {
|
|
22
6
|
trustedHashProvider;
|
|
@@ -24,25 +8,18 @@ class HashVerificationStrategy {
|
|
|
24
8
|
this.trustedHashProvider = trustedHashProvider;
|
|
25
9
|
}
|
|
26
10
|
async verifyData({ data, txId, }) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
else if (data instanceof node_stream_1.Readable) {
|
|
33
|
-
computedHash = await (0, hash_js_1.hashReadableToB64Url)(data);
|
|
34
|
-
}
|
|
35
|
-
else if (data instanceof ReadableStream) {
|
|
36
|
-
computedHash = await (0, hash_js_1.hashReadableStreamToB64Url)(data);
|
|
37
|
-
}
|
|
11
|
+
// kick off the hash computation, but don't wait for it until we compute our own hash
|
|
12
|
+
const [computedHash, fetchedHash] = await Promise.all([
|
|
13
|
+
(0, hash_js_1.hashDataStreamToB64Url)(data),
|
|
14
|
+
this.trustedHashProvider.getHash({ txId }),
|
|
15
|
+
]);
|
|
38
16
|
// await on the hash promise and compare to get a little concurrency when computing hashes over larger data
|
|
39
|
-
const { hash } = await hashPromise;
|
|
40
17
|
if (computedHash === undefined) {
|
|
41
18
|
throw new Error('Hash could not be computed');
|
|
42
19
|
}
|
|
43
|
-
if (computedHash !== hash) {
|
|
20
|
+
if (computedHash !== fetchedHash.hash) {
|
|
44
21
|
throw new Error('Hash does not match', {
|
|
45
|
-
cause: { computedHash, trustedHash:
|
|
22
|
+
cause: { computedHash, trustedHash: fetchedHash },
|
|
46
23
|
});
|
|
47
24
|
}
|
|
48
25
|
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.Wayfinder = exports.createWayfinderClient = exports.WayfinderEmitter = exports.resolveWayfinderUrl = exports.txIdRegex = exports.arnsRegex = void 0;
|
|
7
|
-
exports.
|
|
4
|
+
exports.tapAndVerifyReadableStream = tapAndVerifyReadableStream;
|
|
8
5
|
exports.sandboxFromId = sandboxFromId;
|
|
9
6
|
/**
|
|
10
7
|
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
|
|
@@ -21,8 +18,7 @@ exports.sandboxFromId = sandboxFromId;
|
|
|
21
18
|
* See the License for the specific language governing permissions and
|
|
22
19
|
* limitations under the License.
|
|
23
20
|
*/
|
|
24
|
-
const
|
|
25
|
-
const node_stream_1 = require("node:stream");
|
|
21
|
+
const eventemitter3_1 = require("eventemitter3");
|
|
26
22
|
const rfc4648_1 = require("rfc4648");
|
|
27
23
|
const io_js_1 = require("../io.js");
|
|
28
24
|
const logger_js_1 = require("../logger.js");
|
|
@@ -72,22 +68,20 @@ const resolveWayfinderUrl = ({ originalUrl, selectedGateway, logger, }) => {
|
|
|
72
68
|
return new URL(rest.length > 0 ? rest.join('/') : '', arnsUrl);
|
|
73
69
|
}
|
|
74
70
|
// TODO: support .eth addresses
|
|
75
|
-
// TODO: "gasless" routing via DNS TXT records
|
|
71
|
+
// TODO: "gasless" routing via DNS TXT records
|
|
76
72
|
}
|
|
77
73
|
logger?.debug('No wayfinder routing protocol applied', {
|
|
78
74
|
originalUrl,
|
|
79
75
|
});
|
|
80
|
-
// return the original url if it's not a wayfinder url
|
|
76
|
+
// return the original url if it's not a wayfinder url
|
|
81
77
|
return new URL(originalUrl);
|
|
82
78
|
};
|
|
83
79
|
exports.resolveWayfinderUrl = resolveWayfinderUrl;
|
|
84
|
-
class WayfinderEmitter extends
|
|
85
|
-
constructor({
|
|
86
|
-
// TODO: continue this pattern for all events
|
|
87
|
-
} = {}) {
|
|
80
|
+
class WayfinderEmitter extends eventemitter3_1.EventEmitter {
|
|
81
|
+
constructor({ onVerificationSucceeded, onVerificationFailed, onVerificationProgress, } = {}) {
|
|
88
82
|
super();
|
|
89
|
-
if (
|
|
90
|
-
this.on('verification-succeeded',
|
|
83
|
+
if (onVerificationSucceeded) {
|
|
84
|
+
this.on('verification-succeeded', onVerificationSucceeded);
|
|
91
85
|
}
|
|
92
86
|
if (onVerificationFailed) {
|
|
93
87
|
this.on('verification-failed', onVerificationFailed);
|
|
@@ -96,84 +90,18 @@ class WayfinderEmitter extends node_events_1.default {
|
|
|
96
90
|
this.on('verification-progress', onVerificationProgress);
|
|
97
91
|
}
|
|
98
92
|
}
|
|
99
|
-
emit(event, payload) {
|
|
100
|
-
return super.emit(event, payload);
|
|
101
|
-
}
|
|
102
|
-
on(event, listener) {
|
|
103
|
-
return super.on(event, listener);
|
|
104
|
-
}
|
|
105
93
|
}
|
|
106
94
|
exports.WayfinderEmitter = WayfinderEmitter;
|
|
107
|
-
function
|
|
108
|
-
// taps node streams
|
|
109
|
-
if (originalStream instanceof node_stream_1.Readable &&
|
|
110
|
-
typeof originalStream.pipe === 'function') {
|
|
111
|
-
const tappedClientStream = new node_stream_1.PassThrough();
|
|
112
|
-
const streamToVerify = new node_stream_1.PassThrough();
|
|
113
|
-
// kick off the verification promise, this will be awaited when the original stream ends
|
|
114
|
-
const verificationPromise = verifyData({
|
|
115
|
-
data: streamToVerify,
|
|
116
|
-
txId,
|
|
117
|
-
});
|
|
118
|
-
let bytesProcessed = 0;
|
|
119
|
-
// pipe the original stream to the verifier and the client stream
|
|
120
|
-
originalStream.on('data', (chunk) => {
|
|
121
|
-
streamToVerify.write(chunk);
|
|
122
|
-
tappedClientStream.write(chunk);
|
|
123
|
-
bytesProcessed += chunk.length;
|
|
124
|
-
// only emit if contentLength is not 0
|
|
125
|
-
if (contentLength !== 0) {
|
|
126
|
-
emitter?.emit('verification-progress', {
|
|
127
|
-
txId,
|
|
128
|
-
totalBytes: contentLength,
|
|
129
|
-
processedBytes: bytesProcessed,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
originalStream.on('end', async () => {
|
|
134
|
-
streamToVerify.end(); // triggers verifier completion and completes the verification promise
|
|
135
|
-
if (strict) {
|
|
136
|
-
// in strict mode, we wait for verification to complete before ending the client stream
|
|
137
|
-
try {
|
|
138
|
-
await verificationPromise;
|
|
139
|
-
emitter?.emit('verification-succeeded', { txId });
|
|
140
|
-
tappedClientStream.end();
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
emitter?.emit('verification-failed', { error, txId });
|
|
144
|
-
// In strict mode, destroy the client stream with the error
|
|
145
|
-
tappedClientStream.destroy(new Error('Verification failed', { cause: error }));
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
// in non-strict mode, we end the client stream immediately and handle verification asynchronously
|
|
150
|
-
tappedClientStream.end();
|
|
151
|
-
// trigger the verification promise and emit events for the result
|
|
152
|
-
verificationPromise
|
|
153
|
-
.then(() => {
|
|
154
|
-
emitter?.emit('verification-succeeded', { txId });
|
|
155
|
-
})
|
|
156
|
-
.catch((error) => {
|
|
157
|
-
emitter?.emit('verification-failed', { error, txId });
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
originalStream.on('error', (err) => {
|
|
162
|
-
// emit the verification failed event
|
|
163
|
-
emitter?.emit('verification-failed', {
|
|
164
|
-
error: err,
|
|
165
|
-
txId,
|
|
166
|
-
});
|
|
167
|
-
// destroy both streams and propagate the original stream error
|
|
168
|
-
streamToVerify.destroy(err);
|
|
169
|
-
tappedClientStream.destroy(err);
|
|
170
|
-
});
|
|
171
|
-
// send the stream to the verify function and if it errors end the client stream
|
|
172
|
-
return tappedClientStream;
|
|
173
|
-
}
|
|
174
|
-
// taps web readable streams
|
|
95
|
+
function tapAndVerifyReadableStream({ originalStream, contentLength, verifyData, txId, emitter, strict = false, }) {
|
|
175
96
|
if (originalStream instanceof ReadableStream &&
|
|
176
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
|
+
*/
|
|
177
105
|
const [verifyBranch, clientBranch] = originalStream.tee();
|
|
178
106
|
// setup our promise to verify the data
|
|
179
107
|
const verificationPromise = verifyData({
|
|
@@ -194,10 +122,8 @@ function tapAndVerifyStream({ originalStream, contentLength, verifyData, txId, e
|
|
|
194
122
|
controller.close();
|
|
195
123
|
}
|
|
196
124
|
catch (err) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
error: err,
|
|
200
|
-
});
|
|
125
|
+
// emit the verification failed event
|
|
126
|
+
emitter?.emit('verification-failed', err);
|
|
201
127
|
// In strict mode, we report the error to the client stream
|
|
202
128
|
controller.error(new Error('Verification failed', { cause: err }));
|
|
203
129
|
}
|
|
@@ -209,10 +135,7 @@ function tapAndVerifyStream({ originalStream, contentLength, verifyData, txId, e
|
|
|
209
135
|
emitter?.emit('verification-succeeded', { txId });
|
|
210
136
|
})
|
|
211
137
|
.catch((error) => {
|
|
212
|
-
emitter?.emit('verification-failed',
|
|
213
|
-
txId,
|
|
214
|
-
error,
|
|
215
|
-
});
|
|
138
|
+
emitter?.emit('verification-failed', error);
|
|
216
139
|
});
|
|
217
140
|
// in non-strict mode, we close the controller immediately and handle verification asynchronously
|
|
218
141
|
controller.close();
|
|
@@ -240,8 +163,6 @@ function tapAndVerifyStream({ originalStream, contentLength, verifyData, txId, e
|
|
|
240
163
|
},
|
|
241
164
|
}),
|
|
242
165
|
});
|
|
243
|
-
// note: we don't block or throw errors here even in strict mode
|
|
244
|
-
// since the stream is already being cancelled by the client
|
|
245
166
|
},
|
|
246
167
|
});
|
|
247
168
|
return clientStreamWithVerification;
|
|
@@ -268,146 +189,128 @@ function sandboxFromId(id) {
|
|
|
268
189
|
* @returns a wrapped fetch function that supports ar:// protocol and always returns Response
|
|
269
190
|
*/
|
|
270
191
|
const createWayfinderClient = ({ resolveUrl, verifyData, selectGateway, emitter = new WayfinderEmitter(), logger, strict = false, }) => {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
// If it's not an ar:// URL, pass it directly to fetch
|
|
276
|
-
if (!originalUrl.startsWith('ar://')) {
|
|
277
|
-
logger?.debug('Not a wayfinder URL, passing to fetch directly', {
|
|
278
|
-
originalUrl,
|
|
192
|
+
return async (url, init) => {
|
|
193
|
+
if (typeof url !== 'string') {
|
|
194
|
+
logger?.debug('URL is not a string, skipping routing', {
|
|
195
|
+
url,
|
|
279
196
|
});
|
|
280
197
|
emitter?.emit('routing-skipped', {
|
|
281
|
-
originalUrl,
|
|
198
|
+
originalUrl: JSON.stringify(url),
|
|
282
199
|
});
|
|
283
|
-
// we don't do anything special with non-ar:// urls, just pass them through
|
|
284
200
|
return fetch(url, init);
|
|
285
201
|
}
|
|
286
|
-
// Start the routing process
|
|
287
202
|
emitter?.emit('routing-started', {
|
|
288
|
-
originalUrl,
|
|
203
|
+
originalUrl: url.toString(),
|
|
289
204
|
});
|
|
290
|
-
// Retry logic for gateway selection
|
|
291
205
|
const maxRetries = 3;
|
|
292
206
|
const retryDelay = 1000;
|
|
293
207
|
for (let i = 0; i < maxRetries; i++) {
|
|
294
208
|
try {
|
|
295
209
|
// select the target gateway
|
|
296
|
-
// 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
|
|
297
210
|
const selectedGateway = await selectGateway();
|
|
298
211
|
logger?.debug('Selected gateway', {
|
|
299
|
-
originalUrl,
|
|
212
|
+
originalUrl: url,
|
|
300
213
|
selectedGateway: selectedGateway.toString(),
|
|
301
214
|
});
|
|
302
215
|
// route the request to the target gateway
|
|
303
216
|
const redirectUrl = resolveUrl({
|
|
304
|
-
originalUrl,
|
|
217
|
+
originalUrl: url,
|
|
305
218
|
selectedGateway,
|
|
306
219
|
logger,
|
|
307
220
|
});
|
|
308
221
|
emitter?.emit('routing-succeeded', {
|
|
309
|
-
originalUrl,
|
|
222
|
+
originalUrl: url,
|
|
310
223
|
selectedGateway: selectedGateway.toString(),
|
|
311
224
|
redirectUrl: redirectUrl.toString(),
|
|
312
225
|
});
|
|
313
226
|
logger?.debug(`Redirecting request`, {
|
|
314
|
-
originalUrl,
|
|
227
|
+
originalUrl: url,
|
|
315
228
|
redirectUrl: redirectUrl.toString(),
|
|
316
229
|
});
|
|
317
|
-
//
|
|
230
|
+
// make the request to the target gateway using the redirect url
|
|
318
231
|
const response = await fetch(redirectUrl.toString(), {
|
|
319
|
-
//
|
|
232
|
+
// enforce CORS given we're likely going to a different origin, but always allow the client to override
|
|
320
233
|
redirect: 'follow',
|
|
321
234
|
mode: 'cors',
|
|
322
|
-
// allow requestor to override and any additional request configuration
|
|
323
235
|
...init,
|
|
324
236
|
});
|
|
325
|
-
// TODO: update any caching we use for the request and gateway response
|
|
326
237
|
logger?.debug(`Successfully routed request to gateway`, {
|
|
327
238
|
redirectUrl: redirectUrl.toString(),
|
|
328
|
-
originalUrl,
|
|
239
|
+
originalUrl: url.toString(),
|
|
329
240
|
});
|
|
330
|
-
//
|
|
331
|
-
if (redirectUrl.toString()
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
+
}
|
|
367
291
|
}
|
|
368
|
-
|
|
369
|
-
originalStream: response.body,
|
|
370
|
-
contentLength,
|
|
371
|
-
verifyData,
|
|
372
|
-
txId,
|
|
373
|
-
emitter,
|
|
374
|
-
strict,
|
|
375
|
-
});
|
|
376
|
-
// wrap the response with the verified stream
|
|
377
|
-
return new Response(verifiedStream, {
|
|
378
|
-
status: response.status,
|
|
379
|
-
statusText: response.statusText,
|
|
380
|
-
// TODO: we could add identified transaction id to the headers here, but it would be changing information from the original response
|
|
381
|
-
headers: response.headers,
|
|
382
|
-
});
|
|
292
|
+
return response;
|
|
383
293
|
}
|
|
384
294
|
catch (error) {
|
|
385
295
|
logger?.debug('Failed to route request', {
|
|
386
296
|
error: error.message,
|
|
387
297
|
stack: error.stack,
|
|
388
|
-
originalUrl,
|
|
298
|
+
originalUrl: url,
|
|
389
299
|
attempt: i + 1,
|
|
390
300
|
maxRetries,
|
|
391
301
|
});
|
|
392
302
|
if (i < maxRetries - 1) {
|
|
393
303
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
394
304
|
}
|
|
395
|
-
else {
|
|
396
|
-
emitter?.emit('routing-failed', {
|
|
397
|
-
originalUrl,
|
|
398
|
-
error,
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
305
|
}
|
|
402
306
|
}
|
|
403
307
|
throw new Error('Failed to route request after max retries', {
|
|
404
308
|
cause: {
|
|
405
|
-
originalUrl,
|
|
309
|
+
originalUrl: url,
|
|
406
310
|
maxRetries,
|
|
407
311
|
},
|
|
408
312
|
});
|
|
409
313
|
};
|
|
410
|
-
return wayfinderRedirect;
|
|
411
314
|
};
|
|
412
315
|
exports.createWayfinderClient = createWayfinderClient;
|
|
413
316
|
/**
|
|
@@ -559,7 +462,7 @@ class Wayfinder {
|
|
|
559
462
|
}),
|
|
560
463
|
}),
|
|
561
464
|
}), events = {
|
|
562
|
-
|
|
465
|
+
onVerificationSucceeded: (event) => {
|
|
563
466
|
logger.debug('Verification passed!', event);
|
|
564
467
|
},
|
|
565
468
|
onVerificationFailed: (event) => {
|
|
@@ -568,7 +471,7 @@ class Wayfinder {
|
|
|
568
471
|
onVerificationProgress: (event) => {
|
|
569
472
|
logger.debug('Verification progress!', event);
|
|
570
473
|
},
|
|
571
|
-
}, strict = false, }) {
|
|
474
|
+
}, strict = false, } = {}) {
|
|
572
475
|
this.routingStrategy = routingStrategy;
|
|
573
476
|
this.gatewaysProvider = gatewaysProvider;
|
|
574
477
|
this.emitter = new WayfinderEmitter(events);
|