playlist-data-engine 1.6.0 → 1.7.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/dist/analysis.js +1 -1
- package/dist/analysis.mjs +2 -2
- package/dist/arweaveGatewayManager-C9SIMDTd.cjs +1 -0
- package/dist/{arweaveGatewayManager-BKUZ1Klm.js → arweaveGatewayManager-DDSO5sLJ.js} +381 -338
- package/dist/core/parser/MetadataExtractor.d.ts +57 -3
- package/dist/core/parser/MetadataExtractor.d.ts.map +1 -1
- package/dist/core/parser/PlaylistParser.d.ts.map +1 -1
- package/dist/core/parser/TrackExtras.d.ts +23 -0
- package/dist/core/parser/TrackExtras.d.ts.map +1 -1
- package/dist/core/types/Playlist.d.ts +4 -1
- package/dist/core/types/Playlist.d.ts.map +1 -1
- package/dist/gateway-CtmUPK4J.cjs +1 -0
- package/dist/gateway-PjLtANkX.js +662 -0
- package/dist/gateway.d.ts +2 -0
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +1 -1
- package/dist/gateway.mjs +18 -315
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/playlist-data-engine.js +40 -40
- package/dist/playlist-data-engine.mjs +1742 -1973
- package/dist/{unifyBeatMap-sIbIw5OX.cjs → unifyBeatMap-DIsNlck4.cjs} +1 -1
- package/dist/{unifyBeatMap-BA5EG991.js → unifyBeatMap-k6vw_JPQ.js} +1 -1
- package/dist/utils/arweaveGatewayManager.d.ts +18 -3
- package/dist/utils/arweaveGatewayManager.d.ts.map +1 -1
- package/dist/utils/arweaveUtils.d.ts +29 -0
- package/dist/utils/arweaveUtils.d.ts.map +1 -1
- package/dist/utils/playlistUtils.d.ts.map +1 -1
- package/package.json +34 -31
- package/dist/arweaveGatewayManager-CeW9SP2P.cjs +0 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
var
|
|
2
|
-
const
|
|
1
|
+
var x = /* @__PURE__ */ ((y) => (y[y.DEBUG = 0] = "DEBUG", y[y.INFO = 1] = "INFO", y[y.WARN = 2] = "WARN", y[y.ERROR = 3] = "ERROR", y[y.NONE = 4] = "NONE", y))(x || {});
|
|
2
|
+
const $ = {
|
|
3
3
|
0: "DEBUG",
|
|
4
4
|
1: "INFO",
|
|
5
5
|
2: "WARN",
|
|
6
6
|
3: "ERROR",
|
|
7
7
|
4: "NONE"
|
|
8
8
|
};
|
|
9
|
-
let
|
|
10
|
-
class
|
|
9
|
+
let k = 1, W = !0, _ = !0, E = null, D = !1, T = !1;
|
|
10
|
+
class P {
|
|
11
11
|
/**
|
|
12
12
|
* Create a logger instance with a specific context (usually the class/module name)
|
|
13
13
|
* @param context - The context name to prefix log messages (e.g., 'WeatherAPIClient')
|
|
@@ -21,28 +21,28 @@ class D {
|
|
|
21
21
|
* @returns A Logger instance configured with the given context
|
|
22
22
|
*/
|
|
23
23
|
static for(e) {
|
|
24
|
-
return new
|
|
24
|
+
return new P(e);
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Enable diagnostic mode
|
|
28
28
|
* Sets log level to DEBUG for maximum verbosity
|
|
29
29
|
*/
|
|
30
30
|
static enableDiagnosticMode() {
|
|
31
|
-
|
|
31
|
+
D = !0, k = 0;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Disable diagnostic mode
|
|
35
35
|
* Resets log level to INFO
|
|
36
36
|
*/
|
|
37
37
|
static disableDiagnosticMode() {
|
|
38
|
-
|
|
38
|
+
D = !1, k = 1;
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* Check if diagnostic mode is enabled
|
|
42
42
|
* @returns True if diagnostic mode is active
|
|
43
43
|
*/
|
|
44
44
|
static isDiagnosticMode() {
|
|
45
|
-
return
|
|
45
|
+
return D;
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* Enable verbose logging mode
|
|
@@ -53,7 +53,7 @@ class D {
|
|
|
53
53
|
* // All debug messages will now be logged
|
|
54
54
|
*/
|
|
55
55
|
static enableVerbose() {
|
|
56
|
-
|
|
56
|
+
T = !0, k = 0;
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
59
|
* Disable verbose logging mode
|
|
@@ -63,14 +63,14 @@ class D {
|
|
|
63
63
|
* // Only INFO and above will be logged
|
|
64
64
|
*/
|
|
65
65
|
static disableVerbose() {
|
|
66
|
-
|
|
66
|
+
T = !1, k = 1;
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
69
|
* Check if verbose mode is enabled
|
|
70
70
|
* @returns True if verbose mode is active
|
|
71
71
|
*/
|
|
72
72
|
static isVerbose() {
|
|
73
|
-
return
|
|
73
|
+
return T;
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
76
76
|
* Set verbose mode on or off
|
|
@@ -80,34 +80,34 @@ class D {
|
|
|
80
80
|
* Logger.setVerbose(false); // Disable verbose logging
|
|
81
81
|
*/
|
|
82
82
|
static setVerbose(e) {
|
|
83
|
-
|
|
83
|
+
T = e, k = e ? 0 : 1;
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
86
|
* Set the global log level
|
|
87
87
|
* @param level - The minimum log level to display
|
|
88
88
|
*/
|
|
89
89
|
static setLevel(e) {
|
|
90
|
-
|
|
90
|
+
k = e;
|
|
91
91
|
}
|
|
92
92
|
/**
|
|
93
93
|
* Get the current global log level
|
|
94
94
|
* @returns The current log level
|
|
95
95
|
*/
|
|
96
96
|
static getLevel() {
|
|
97
|
-
return
|
|
97
|
+
return k;
|
|
98
98
|
}
|
|
99
99
|
/**
|
|
100
100
|
* Configure the logger globally
|
|
101
101
|
* @param config - Logger configuration options
|
|
102
102
|
*/
|
|
103
103
|
static configure(e) {
|
|
104
|
-
e.level !== void 0 && (
|
|
104
|
+
e.level !== void 0 && (k = e.level), e.includeTimestamp !== void 0 && (W = e.includeTimestamp), e.includeContext !== void 0 && (_ = e.includeContext), e.customHandler !== void 0 && (E = e.customHandler);
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
107
|
* Reset logger to default configuration
|
|
108
108
|
*/
|
|
109
109
|
static reset() {
|
|
110
|
-
|
|
110
|
+
k = 1, W = !0, _ = !0, E = null, D = !1, T = !1;
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
113
113
|
* Log a debug message (most verbose)
|
|
@@ -145,7 +145,7 @@ class D {
|
|
|
145
145
|
* Internal logging method
|
|
146
146
|
*/
|
|
147
147
|
log(e, t, s) {
|
|
148
|
-
if (e <
|
|
148
|
+
if (e < k)
|
|
149
149
|
return;
|
|
150
150
|
const a = {
|
|
151
151
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -154,8 +154,8 @@ class D {
|
|
|
154
154
|
message: t,
|
|
155
155
|
data: s
|
|
156
156
|
};
|
|
157
|
-
if (
|
|
158
|
-
|
|
157
|
+
if (E) {
|
|
158
|
+
E(a);
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
const r = this.formatMessage(a);
|
|
@@ -179,7 +179,7 @@ class D {
|
|
|
179
179
|
*/
|
|
180
180
|
formatMessage(e) {
|
|
181
181
|
const t = [];
|
|
182
|
-
return
|
|
182
|
+
return W && t.push(this.formatTimestamp(e.timestamp)), t.push(`[${$[e.level]}]`), _ && e.context && t.push(`[${e.context}]`), t.push(e.message), t.join(" ");
|
|
183
183
|
}
|
|
184
184
|
/**
|
|
185
185
|
* Format a timestamp for log output
|
|
@@ -189,14 +189,14 @@ class D {
|
|
|
189
189
|
return `${t}:${s}:${a}.${r}`;
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
-
function
|
|
193
|
-
return
|
|
192
|
+
function q(y) {
|
|
193
|
+
return P.for(y);
|
|
194
194
|
}
|
|
195
|
-
const
|
|
195
|
+
const I = [
|
|
196
196
|
{ host: "arweave.net", protocol: "https", priority: 3 },
|
|
197
197
|
{ host: "ardrive.net", protocol: "https", priority: 2 },
|
|
198
198
|
{ host: "turbo-gateway.com", protocol: "https", priority: 1 }
|
|
199
|
-
],
|
|
199
|
+
], F = [
|
|
200
200
|
"arweave.net",
|
|
201
201
|
"arweave.dev",
|
|
202
202
|
"www.arweave.net",
|
|
@@ -207,137 +207,175 @@ const O = [
|
|
|
207
207
|
"ar-io.net",
|
|
208
208
|
"arweave.rocks",
|
|
209
209
|
"g8way.io"
|
|
210
|
+
], z = [
|
|
211
|
+
"irys.xyz"
|
|
210
212
|
];
|
|
211
|
-
function
|
|
212
|
-
if (!
|
|
213
|
+
function Y(y) {
|
|
214
|
+
if (!y) return !1;
|
|
215
|
+
const e = y.toLowerCase();
|
|
216
|
+
return z.some((t) => e === t || e.endsWith("." + t));
|
|
217
|
+
}
|
|
218
|
+
const M = /^ar:\/\//i;
|
|
219
|
+
function C(y) {
|
|
220
|
+
if (!y || typeof y != "string")
|
|
213
221
|
return !1;
|
|
214
|
-
if (
|
|
222
|
+
if (M.test(y))
|
|
215
223
|
return !0;
|
|
216
|
-
const e =
|
|
224
|
+
const e = y.toLowerCase();
|
|
217
225
|
try {
|
|
218
|
-
const t = new URL(
|
|
219
|
-
return
|
|
226
|
+
const t = new URL(y).hostname;
|
|
227
|
+
return F.some(
|
|
220
228
|
(s) => t === s || t.endsWith("." + s)
|
|
221
229
|
);
|
|
222
230
|
} catch {
|
|
223
|
-
return
|
|
231
|
+
return F.some((t) => e.includes(t));
|
|
224
232
|
}
|
|
225
233
|
}
|
|
226
|
-
function
|
|
227
|
-
if (!
|
|
234
|
+
function A(y) {
|
|
235
|
+
if (!y || typeof y != "string" || !C(y))
|
|
228
236
|
return null;
|
|
229
237
|
let e = null, t = "";
|
|
230
|
-
if (
|
|
231
|
-
const l =
|
|
232
|
-
return
|
|
238
|
+
if (M.test(y)) {
|
|
239
|
+
const l = y.replace(M, ""), o = l.search(/[/?#]/);
|
|
240
|
+
return o !== -1 ? (e = l.slice(0, o), t = l.slice(o)) : e = l, e.length === 43 && /^[A-Za-z0-9_-]+$/.test(e) ? { txId: e, originalUrl: y, pathSuffix: t } : null;
|
|
233
241
|
}
|
|
234
|
-
let s =
|
|
242
|
+
let s = y;
|
|
235
243
|
try {
|
|
236
|
-
const l = new URL(
|
|
244
|
+
const l = new URL(y);
|
|
237
245
|
s = l.pathname + l.search;
|
|
238
246
|
} catch {
|
|
239
247
|
}
|
|
240
248
|
const a = /[A-Za-z0-9_-]{43}/g, r = s.match(a);
|
|
241
249
|
if (r && r.length > 0) {
|
|
242
250
|
e = r[0];
|
|
243
|
-
const l =
|
|
251
|
+
const l = y.indexOf(e);
|
|
244
252
|
if (l !== -1) {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
253
|
+
const o = y.slice(l + 43), c = Math.min(
|
|
254
|
+
o.indexOf("?") === -1 ? 1 / 0 : o.indexOf("?"),
|
|
255
|
+
o.indexOf("#") === -1 ? 1 / 0 : o.indexOf("#")
|
|
248
256
|
);
|
|
249
|
-
c === 1 / 0 ? t =
|
|
257
|
+
c === 1 / 0 ? t = o : t = o.slice(0, c);
|
|
250
258
|
}
|
|
251
|
-
return { txId: e, originalUrl:
|
|
259
|
+
return { txId: e, originalUrl: y, pathSuffix: t };
|
|
252
260
|
}
|
|
253
261
|
return null;
|
|
254
262
|
}
|
|
255
|
-
function
|
|
256
|
-
return `${e.protocol || "https"}://${e.host}/${
|
|
263
|
+
function R(y, e, t = "") {
|
|
264
|
+
return `${e.protocol || "https"}://${e.host}/${y}${t}`;
|
|
265
|
+
}
|
|
266
|
+
function Z(y, e = I, t = "") {
|
|
267
|
+
return e.sort((s, a) => s.priority - a.priority).map((s) => R(y, s, t));
|
|
257
268
|
}
|
|
258
|
-
function
|
|
259
|
-
|
|
269
|
+
function B(y) {
|
|
270
|
+
if (!y.startsWith("www.")) return y;
|
|
271
|
+
const e = y.slice(4);
|
|
272
|
+
return F.includes(e) ? e : y;
|
|
260
273
|
}
|
|
261
|
-
async function
|
|
274
|
+
async function K() {
|
|
262
275
|
try {
|
|
263
276
|
return await import("@ar.io/wayfinder-core");
|
|
264
277
|
} catch {
|
|
265
278
|
return null;
|
|
266
279
|
}
|
|
267
280
|
}
|
|
268
|
-
async function
|
|
281
|
+
async function V() {
|
|
269
282
|
try {
|
|
270
283
|
return await import("@ar.io/sdk");
|
|
271
284
|
} catch {
|
|
272
285
|
return null;
|
|
273
286
|
}
|
|
274
287
|
}
|
|
275
|
-
const
|
|
276
|
-
class
|
|
288
|
+
const N = "https://solana-rpc.publicnode.com", j = 5e3, X = 864e5, H = "arweave_active_gateway", L = "arweave_ranked_gateways";
|
|
289
|
+
class J {
|
|
277
290
|
constructor(e) {
|
|
278
|
-
this.cache = /* @__PURE__ */ new Map(), this.logger =
|
|
291
|
+
this.cache = /* @__PURE__ */ new Map(), this.logger = P.for("ArweaveGateway"), this.hitCount = 0, this.missCount = 0, this.healthData = /* @__PURE__ */ new Map(), this.originalPriorities = /* @__PURE__ */ new Map(), this.MAX_HEALTH_RECORDS = 50, this.inflightResolves = /* @__PURE__ */ new Map(), this.inflightControllers = /* @__PURE__ */ new Map(), this.wayfinder = null, this.wayfinderGatewaysProvider = null, this.rankedGatewaysCache = null, this.rankedGatewaysCacheTimestamp = 0, this.RANKED_GATEWAYS_TTL_MS = 1440 * 60 * 1e3, this.activeGateway = null, this.lastFetchTiming = 0, this.consecutiveSlowResponses = 0, this.gateways = (e?.gateways ?? I).map((s) => ({ ...s })), this.timeout = e?.timeout ?? j, this.cacheTTL = e?.cacheTTL ?? X, this.slowResponseThreshold = e?.slowResponseThreshold ?? 8e3, this.maxSlowResponses = e?.maxSlowResponses ?? 3, this.solanaRpcUrl = e?.solanaRpcUrl ?? N, this.hasCustomSolanaRpc = typeof e?.solanaRpcUrl == "string" && e.solanaRpcUrl.length > 0, this.wayfinderSortBy = e?.wayfinderSortBy ?? "weights.compositeWeight", this.wayfinderPrimaryLimit = e?.wayfinderPrimaryLimit ?? 50, this.wayfinderFallbackLimit = e?.wayfinderFallbackLimit ?? 100, this.wayfinderStrategy = e?.wayfinderStrategy ?? "composite-ping-random", this.gateways.sort((s, a) => s.priority - a.priority), this.gateways.forEach((s) => {
|
|
279
292
|
this.originalPriorities.set(s.host, s.priority);
|
|
280
293
|
}), this.activeGateway = this.loadPersistedGateway();
|
|
281
294
|
const t = this.loadPersistedRankedGateways();
|
|
282
|
-
t && (this.rankedGatewaysCache = t),
|
|
283
|
-
|
|
295
|
+
t && (this.rankedGatewaysCache = t), this.initWayfinder(), this.logger.info("Gateway manager initialized", {
|
|
296
|
+
gateways: this.gateways.map((s) => s.host),
|
|
297
|
+
timeout: this.timeout,
|
|
298
|
+
cacheTTL: this.cacheTTL,
|
|
299
|
+
activeGateway: this.activeGateway?.host ?? null
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Build the Wayfinder client + ARIO providers on `this.solanaRpcUrl`.
|
|
304
|
+
* Runs async/fire-and-forget — `this.wayfinder` is null until it lands,
|
|
305
|
+
* and resolves fall back to the static gateway list meanwhile. Called
|
|
306
|
+
* from the constructor and again by `setSolanaRpcUrl()` after a RPC swap.
|
|
307
|
+
*/
|
|
308
|
+
initWayfinder() {
|
|
309
|
+
Promise.all([K(), V(), import("./index.browser-CVv08iVS.js")]).then(([e, t, s]) => {
|
|
310
|
+
if (e && t)
|
|
284
311
|
try {
|
|
285
312
|
const {
|
|
286
|
-
FastestPingRoutingStrategy:
|
|
287
|
-
RandomRoutingStrategy:
|
|
288
|
-
CompositeRoutingStrategy:
|
|
289
|
-
RoundRobinRoutingStrategy:
|
|
290
|
-
NetworkGatewaysProvider:
|
|
291
|
-
} =
|
|
292
|
-
new
|
|
293
|
-
ario:
|
|
313
|
+
FastestPingRoutingStrategy: a,
|
|
314
|
+
RandomRoutingStrategy: r,
|
|
315
|
+
CompositeRoutingStrategy: l,
|
|
316
|
+
RoundRobinRoutingStrategy: o,
|
|
317
|
+
NetworkGatewaysProvider: c
|
|
318
|
+
} = e, n = t.ARIO, h = s.createSolanaRpc(this.solanaRpcUrl), w = n.init({ rpc: h }), i = this.wrapProviderWithCache(
|
|
319
|
+
new c({
|
|
320
|
+
ario: w,
|
|
294
321
|
sortBy: this.wayfinderSortBy,
|
|
295
322
|
limit: this.wayfinderPrimaryLimit
|
|
296
323
|
})
|
|
297
324
|
), u = this.wrapProviderWithCache(
|
|
298
|
-
new
|
|
299
|
-
ario:
|
|
325
|
+
new c({
|
|
326
|
+
ario: w,
|
|
300
327
|
sortBy: this.wayfinderSortBy,
|
|
301
328
|
limit: this.wayfinderFallbackLimit
|
|
302
329
|
})
|
|
303
330
|
);
|
|
304
|
-
this.wayfinderGatewaysProvider =
|
|
331
|
+
this.wayfinderGatewaysProvider = i;
|
|
305
332
|
const d = (() => {
|
|
306
333
|
switch (this.wayfinderStrategy) {
|
|
307
334
|
case "random-only":
|
|
308
|
-
return new
|
|
335
|
+
return new r({ gatewaysProvider: u });
|
|
309
336
|
case "ping-only":
|
|
310
|
-
return new
|
|
337
|
+
return new a({ timeoutMs: 3e3, gatewaysProvider: i });
|
|
311
338
|
case "round-robin":
|
|
312
|
-
return new
|
|
339
|
+
return new o({ gateways: [], gatewaysProvider: i });
|
|
313
340
|
default:
|
|
314
|
-
return new
|
|
341
|
+
return new l({
|
|
315
342
|
strategies: [
|
|
316
|
-
new
|
|
317
|
-
new
|
|
343
|
+
new a({ timeoutMs: 3e3, gatewaysProvider: i }),
|
|
344
|
+
new r({ gatewaysProvider: u })
|
|
318
345
|
]
|
|
319
346
|
});
|
|
320
347
|
}
|
|
321
348
|
})();
|
|
322
|
-
this.wayfinder =
|
|
349
|
+
this.wayfinder = e.createWayfinderClient({ routingStrategy: d }), this.logger.info("Wayfinder client initialized", {
|
|
323
350
|
strategy: this.wayfinderStrategy,
|
|
324
351
|
sortBy: this.wayfinderSortBy,
|
|
325
352
|
primaryLimit: this.wayfinderPrimaryLimit,
|
|
326
353
|
fallbackLimit: this.wayfinderFallbackLimit
|
|
327
354
|
});
|
|
328
|
-
} catch (
|
|
329
|
-
this.logger.warn("Failed to configure Wayfinder routing strategy, using defaults", { error:
|
|
355
|
+
} catch (a) {
|
|
356
|
+
this.logger.warn("Failed to configure Wayfinder routing strategy, using defaults", { error: a }), this.wayfinder = e.createWayfinderClient(), this.logger.info("Wayfinder client initialized with default routing");
|
|
330
357
|
}
|
|
331
|
-
else
|
|
358
|
+
else e && (this.wayfinder = e.createWayfinderClient(), this.logger.info("Wayfinder client initialized with default routing (SDK unavailable)"));
|
|
332
359
|
}).catch(() => {
|
|
333
360
|
this.logger.debug("Wayfinder client initialization failed, using static gateways");
|
|
334
|
-
}), this.logger.info("Gateway manager initialized", {
|
|
335
|
-
gateways: this.gateways.map((s) => s.host),
|
|
336
|
-
timeout: this.timeout,
|
|
337
|
-
cacheTTL: this.cacheTTL,
|
|
338
|
-
activeGateway: this.activeGateway?.host ?? null
|
|
339
361
|
});
|
|
340
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Swap the Solana RPC the ARIO gateway registry is read from. Invalidates
|
|
365
|
+
* the ranked-gateway caches (memory + localStorage — a fresh 24h list
|
|
366
|
+
* would otherwise keep serving rankings fetched via the old RPC) and
|
|
367
|
+
* rebuilds the Wayfinder client on the new RPC. Resolves already in
|
|
368
|
+
* flight finish on the old providers.
|
|
369
|
+
*/
|
|
370
|
+
setSolanaRpcUrl(e) {
|
|
371
|
+
const t = (e ?? "").trim();
|
|
372
|
+
this.solanaRpcUrl = t || N, this.hasCustomSolanaRpc = t.length > 0, this.rankedGatewaysCache = null, this.rankedGatewaysCacheTimestamp = 0, this.clearPersistedRankedGateways();
|
|
373
|
+
try {
|
|
374
|
+
this.logger.info("Solana RPC updated", { host: new URL(this.solanaRpcUrl).host });
|
|
375
|
+
} catch {
|
|
376
|
+
}
|
|
377
|
+
this.initWayfinder();
|
|
378
|
+
}
|
|
341
379
|
/**
|
|
342
380
|
* Wrap a promise with a timeout to prevent hanging indefinitely.
|
|
343
381
|
* Useful for third-party calls (e.g., Wayfinder) that have no built-in timeout.
|
|
@@ -346,11 +384,11 @@ class K {
|
|
|
346
384
|
return new Promise((a, r) => {
|
|
347
385
|
const l = setTimeout(() => r(new Error(s)), t);
|
|
348
386
|
e.then(
|
|
349
|
-
(
|
|
350
|
-
clearTimeout(l), a(
|
|
387
|
+
(o) => {
|
|
388
|
+
clearTimeout(l), a(o);
|
|
351
389
|
},
|
|
352
|
-
(
|
|
353
|
-
clearTimeout(l), r(
|
|
390
|
+
(o) => {
|
|
391
|
+
clearTimeout(l), r(o);
|
|
354
392
|
}
|
|
355
393
|
);
|
|
356
394
|
});
|
|
@@ -374,8 +412,8 @@ class K {
|
|
|
374
412
|
*/
|
|
375
413
|
wrapProviderWithCache(e) {
|
|
376
414
|
let t = null, s = 0, a = null;
|
|
377
|
-
const r = () => t !== null && t.length > 0 && Date.now() < s,
|
|
378
|
-
return
|
|
415
|
+
const r = () => t !== null && t.length > 0 && Date.now() < s, o = this.loadPersistedRankedGateways();
|
|
416
|
+
return o && o.length > 0 && (t = o, s = (this.rankedGatewaysCacheTimestamp || Date.now()) + this.RANKED_GATEWAYS_TTL_MS), {
|
|
379
417
|
getGateways: async () => r() ? t : a || (a = (async () => {
|
|
380
418
|
try {
|
|
381
419
|
const c = await e.getGateways();
|
|
@@ -400,15 +438,15 @@ class K {
|
|
|
400
438
|
* @returns A gateway URL constructed from the active gateway, or the original URL if not Arweave
|
|
401
439
|
*/
|
|
402
440
|
resolveUrlSimple(e) {
|
|
403
|
-
if (!
|
|
404
|
-
const t =
|
|
441
|
+
if (!C(e)) return e;
|
|
442
|
+
const t = A(e);
|
|
405
443
|
if (!t) return e;
|
|
406
444
|
const s = this.activeGateway ?? this.gateways[0];
|
|
407
445
|
return this.logger.debug("[gateway] resolveUrlSimple", {
|
|
408
446
|
txId: t.txId,
|
|
409
447
|
gateway: s.host,
|
|
410
448
|
pathSuffix: t.pathSuffix || void 0
|
|
411
|
-
}),
|
|
449
|
+
}), R(t.txId, s, t.pathSuffix);
|
|
412
450
|
}
|
|
413
451
|
/**
|
|
414
452
|
* Set a preferred gateway by host name.
|
|
@@ -444,7 +482,7 @@ class K {
|
|
|
444
482
|
*/
|
|
445
483
|
getDiagnostics() {
|
|
446
484
|
const e = this.gateways.map((t) => {
|
|
447
|
-
const s = this.healthData.get(t.host) ?? [], a = s.length, r = a > 0 ? Math.round(s.reduce((
|
|
485
|
+
const s = this.healthData.get(t.host) ?? [], a = s.length, r = a > 0 ? Math.round(s.reduce((o, c) => o + c.responseTime, 0) / a) : 0, l = a > 0 ? s.filter((o) => o.success).length / a : 1;
|
|
448
486
|
return { host: t.host, avgMs: r, successRate: Math.round(l * 100) / 100, checks: a };
|
|
449
487
|
});
|
|
450
488
|
return {
|
|
@@ -471,16 +509,16 @@ class K {
|
|
|
471
509
|
const s = t?.signal;
|
|
472
510
|
if (s?.aborted)
|
|
473
511
|
return this.logger.debug("resolveUrl called with already-aborted signal, returning original URL"), e;
|
|
474
|
-
if (!
|
|
512
|
+
if (!C(e))
|
|
475
513
|
return e;
|
|
476
|
-
const a =
|
|
514
|
+
const a = A(e);
|
|
477
515
|
if (!a)
|
|
478
516
|
return this.logger.warn("Failed to parse Arweave URL, returning original", { url: e }), e;
|
|
479
|
-
const { txId: r, pathSuffix: l } = a,
|
|
480
|
-
if (
|
|
517
|
+
const { txId: r, pathSuffix: l } = a, o = this.getCachedGateway(r);
|
|
518
|
+
if (o) {
|
|
481
519
|
this.hitCount++;
|
|
482
|
-
const
|
|
483
|
-
return this.logger.debug("Cache hit for txId", { txId: r, gateway:
|
|
520
|
+
const i = R(r, o, l);
|
|
521
|
+
return this.logger.debug("Cache hit for txId", { txId: r, gateway: o.host, pathSuffix: l }), i;
|
|
484
522
|
}
|
|
485
523
|
const c = this.activeGateway?.host ?? "none";
|
|
486
524
|
this.consecutiveSlowResponses >= this.maxSlowResponses && (this.logger.warn("Proactive gateway rotation triggered", {
|
|
@@ -491,12 +529,12 @@ class K {
|
|
|
491
529
|
const n = this.inflightResolves.get(r);
|
|
492
530
|
if (n)
|
|
493
531
|
return this.logger.debug("Reusing in-flight resolve for txId", { txId: r }), n;
|
|
494
|
-
const
|
|
495
|
-
s && s.addEventListener("abort", () =>
|
|
496
|
-
const w = this.resolveGatewayChain(e, r, l,
|
|
532
|
+
const h = new AbortController();
|
|
533
|
+
s && s.addEventListener("abort", () => h.abort(), { once: !0 });
|
|
534
|
+
const w = this.resolveGatewayChain(e, r, l, h.signal, t).finally(() => {
|
|
497
535
|
this.inflightResolves.delete(r), this.inflightControllers.delete(r);
|
|
498
536
|
});
|
|
499
|
-
return this.inflightResolves.set(r, w), this.inflightControllers.set(r,
|
|
537
|
+
return this.inflightResolves.set(r, w), this.inflightControllers.set(r, h), w;
|
|
500
538
|
}
|
|
501
539
|
/**
|
|
502
540
|
* The actual gateway fallback chain. Extracted from resolveUrl() for
|
|
@@ -504,57 +542,62 @@ class K {
|
|
|
504
542
|
* one instance of this chain.
|
|
505
543
|
*/
|
|
506
544
|
async resolveGatewayChain(e, t, s, a, r) {
|
|
507
|
-
const l = Date.now(),
|
|
545
|
+
const l = Date.now(), o = r?.bypassArweaveNet ?? !1;
|
|
508
546
|
let c = null, n = null;
|
|
509
547
|
if (e.startsWith("https://") || e.startsWith("http://"))
|
|
510
548
|
try {
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
549
|
+
const g = new URL(e), f = B(g.host);
|
|
550
|
+
if (Y(f))
|
|
551
|
+
this.logger.info("[gateway] Step 0 (original): skipping legacy redirect host, re-resolving via chain", { host: g.host }), c = f;
|
|
552
|
+
else {
|
|
553
|
+
const m = {
|
|
554
|
+
host: f,
|
|
555
|
+
// includes port if present; strips www. for known gateways
|
|
556
|
+
protocol: g.protocol.replace(":", ""),
|
|
557
|
+
priority: 0
|
|
558
|
+
}, v = Date.now(), G = await this.checkAndSetGateway(e, t, s, m, a), S = Date.now() - v;
|
|
559
|
+
if (G) {
|
|
560
|
+
if (this.logger.info(`[gateway] Step 0 (original): ${G.kind}`, { host: m.host, ms: S, totalMs: Date.now() - l }), G.kind === "verified") return G.url;
|
|
561
|
+
n ??= G.url;
|
|
562
|
+
} else
|
|
563
|
+
this.logger.info("[gateway] Step 0 (original): failed", { host: m.host, ms: S });
|
|
564
|
+
c = m.host;
|
|
565
|
+
}
|
|
523
566
|
} catch {
|
|
524
567
|
}
|
|
525
568
|
if (this.activeGateway && this.activeGateway.host !== c) {
|
|
526
|
-
const
|
|
527
|
-
if (
|
|
528
|
-
if (this.logger.info(`[gateway] Step 1 (persisted): ${
|
|
529
|
-
n ??=
|
|
569
|
+
const g = Date.now(), f = await this.checkAndSetGateway(e, t, s, this.activeGateway, a), m = Date.now() - g;
|
|
570
|
+
if (f) {
|
|
571
|
+
if (this.logger.info(`[gateway] Step 1 (persisted): ${f.kind}`, { host: this.activeGateway.host, ms: m, totalMs: Date.now() - l }), f.kind === "verified") return f.url;
|
|
572
|
+
n ??= f.url;
|
|
530
573
|
} else
|
|
531
574
|
this.logger.info("[gateway] Step 1 (persisted): failed, clearing", { ms: m }), this.activeGateway = null, this.clearPersistedGateway();
|
|
532
575
|
}
|
|
533
|
-
if (!
|
|
534
|
-
const
|
|
535
|
-
if (
|
|
536
|
-
const
|
|
576
|
+
if (!o && c !== "arweave.net") {
|
|
577
|
+
const g = this.gateways.find((f) => f.host === "arweave.net");
|
|
578
|
+
if (g) {
|
|
579
|
+
const f = Date.now(), m = await this.checkAndSetGateway(e, t, s, g, a), v = Date.now() - f;
|
|
537
580
|
if (m) {
|
|
538
|
-
if (this.logger.info(`[gateway] Step 2 (arweave.net): ${m.kind}`, { ms:
|
|
581
|
+
if (this.logger.info(`[gateway] Step 2 (arweave.net): ${m.kind}`, { ms: v, totalMs: Date.now() - l }), m.kind === "verified") return m.url;
|
|
539
582
|
n ??= m.url;
|
|
540
583
|
} else
|
|
541
|
-
this.logger.info("[gateway] Step 2 (arweave.net): failed", { ms:
|
|
584
|
+
this.logger.info("[gateway] Step 2 (arweave.net): failed", { ms: v });
|
|
542
585
|
}
|
|
543
586
|
}
|
|
544
|
-
const
|
|
587
|
+
const h = Date.now(), w = await this.tryFallbackGateways(e, t, s, a, c), i = Date.now() - h;
|
|
545
588
|
if (w) {
|
|
546
|
-
if (this.logger.info(`[gateway] Step 3 (fallbacks): ${w.kind}`, { ms:
|
|
589
|
+
if (this.logger.info(`[gateway] Step 3 (fallbacks): ${w.kind}`, { ms: i, totalMs: Date.now() - l }), w.kind === "verified") return w.url;
|
|
547
590
|
n ??= w.url;
|
|
548
591
|
} else
|
|
549
|
-
this.logger.info("[gateway] Step 3 (fallbacks): failed", { ms:
|
|
550
|
-
const
|
|
551
|
-
if (this.wayfinder && !
|
|
552
|
-
const
|
|
592
|
+
this.logger.info("[gateway] Step 3 (fallbacks): failed", { ms: i });
|
|
593
|
+
const u = !this.hasCustomSolanaRpc, d = r?.bypassWayfinder ?? u;
|
|
594
|
+
if (this.wayfinder && !d) {
|
|
595
|
+
const g = Date.now();
|
|
553
596
|
this.logger.info("[gateway] Step 4 (wayfinder): trying");
|
|
554
|
-
const
|
|
555
|
-
if (
|
|
556
|
-
if (this.logger.info(`[gateway] Step 4 (wayfinder): ${
|
|
557
|
-
n ??=
|
|
597
|
+
const f = await this.tryWayfinder(t, s, a, c), m = Date.now() - g;
|
|
598
|
+
if (f) {
|
|
599
|
+
if (this.logger.info(`[gateway] Step 4 (wayfinder): ${f.kind}`, { ms: m, totalMs: Date.now() - l }), f.kind === "verified") return f.url;
|
|
600
|
+
n ??= f.url;
|
|
558
601
|
} else
|
|
559
602
|
this.logger.info("[gateway] Step 4 (wayfinder): failed", { ms: m });
|
|
560
603
|
}
|
|
@@ -580,29 +623,29 @@ class K {
|
|
|
580
623
|
return this.logger.debug("reportGatewayFailure called with already-aborted signal, returning original URL"), e;
|
|
581
624
|
if (a === "user-cancel-fast")
|
|
582
625
|
return this.logger.debug("User cancelled quickly, keeping current gateway"), e;
|
|
583
|
-
const l =
|
|
626
|
+
const l = A(e);
|
|
584
627
|
if (!l) return e;
|
|
585
|
-
const { txId:
|
|
628
|
+
const { txId: o, pathSuffix: c } = l;
|
|
586
629
|
this.logger.warn("Gateway failure reported, finding new gateway", {
|
|
587
630
|
failedHost: this.activeGateway?.host,
|
|
588
|
-
txId:
|
|
631
|
+
txId: o,
|
|
589
632
|
reason: a ?? "load-error"
|
|
590
633
|
});
|
|
591
634
|
const n = this.activeGateway?.host ?? r;
|
|
592
|
-
this.activeGateway = null, this.cache.delete(
|
|
593
|
-
const
|
|
594
|
-
if (
|
|
595
|
-
const
|
|
596
|
-
if (
|
|
635
|
+
this.activeGateway = null, this.cache.delete(o), this.clearPersistedGateway();
|
|
636
|
+
const h = this.gateways.find((u) => u.host === "arweave.net" && u.host !== n);
|
|
637
|
+
if (h) {
|
|
638
|
+
const u = await this.checkAndSetGateway(e, o, c, h, s);
|
|
639
|
+
if (u) return u.url;
|
|
597
640
|
}
|
|
598
641
|
if (this.wayfinder) {
|
|
599
|
-
const
|
|
600
|
-
if (
|
|
642
|
+
const u = await this.tryWayfinder(o, c, s);
|
|
643
|
+
if (u) return u.url;
|
|
601
644
|
}
|
|
602
645
|
const w = this.gateways;
|
|
603
|
-
n && (this.gateways = this.gateways.filter((
|
|
604
|
-
const
|
|
605
|
-
return this.gateways = w,
|
|
646
|
+
n && (this.gateways = this.gateways.filter((u) => u.host !== n));
|
|
647
|
+
const i = await this.tryFallbackGateways(e, o, c, s);
|
|
648
|
+
return this.gateways = w, i ? i.url : e;
|
|
606
649
|
}
|
|
607
650
|
/**
|
|
608
651
|
* Report that a fetch succeeded with timing data.
|
|
@@ -649,22 +692,22 @@ class K {
|
|
|
649
692
|
const l = await this.checkGateway(t, a, s, r);
|
|
650
693
|
if (l === !0) {
|
|
651
694
|
this.setActiveGateway(a, t);
|
|
652
|
-
const
|
|
695
|
+
const o = R(t, a, s);
|
|
653
696
|
return this.logger.info("Gateway resolved", {
|
|
654
697
|
txId: t,
|
|
655
698
|
gateway: a.host,
|
|
656
699
|
pathSuffix: s,
|
|
657
|
-
workingUrl:
|
|
658
|
-
}), { kind: "verified", url:
|
|
700
|
+
workingUrl: o
|
|
701
|
+
}), { kind: "verified", url: o };
|
|
659
702
|
}
|
|
660
703
|
if (l === "maybe") {
|
|
661
|
-
const
|
|
704
|
+
const o = R(t, a, s);
|
|
662
705
|
return this.logger.info("Gateway maybe resolved (no-cors)", {
|
|
663
706
|
txId: t,
|
|
664
707
|
gateway: a.host,
|
|
665
708
|
pathSuffix: s,
|
|
666
|
-
workingUrl:
|
|
667
|
-
}), { kind: "maybe", url:
|
|
709
|
+
workingUrl: o
|
|
710
|
+
}), { kind: "maybe", url: o };
|
|
668
711
|
}
|
|
669
712
|
return null;
|
|
670
713
|
}
|
|
@@ -674,55 +717,55 @@ class K {
|
|
|
674
717
|
*/
|
|
675
718
|
async tryFallbackGateways(e, t, s, a, r) {
|
|
676
719
|
this.missCount++;
|
|
677
|
-
const l = 0.7,
|
|
678
|
-
const { rate:
|
|
679
|
-
return !(
|
|
680
|
-
}),
|
|
681
|
-
if (a?.aborted ||
|
|
682
|
-
const w = async (
|
|
683
|
-
if (a?.aborted ||
|
|
684
|
-
const
|
|
685
|
-
a?.addEventListener("abort",
|
|
686
|
-
let m = null,
|
|
720
|
+
const l = 0.7, o = 3, c = this.gateways.filter((d) => d.host !== "arweave.net" && d.host !== r), n = c.filter((d) => {
|
|
721
|
+
const { rate: g, totalChecks: f } = this.getFailureRate(d.host);
|
|
722
|
+
return !(f >= o && g > l);
|
|
723
|
+
}), h = n.length > 0 ? n : c;
|
|
724
|
+
if (a?.aborted || h.length === 0) return null;
|
|
725
|
+
const w = async (d) => {
|
|
726
|
+
if (a?.aborted || d.length === 0) return null;
|
|
727
|
+
const g = new AbortController(), f = () => g.abort();
|
|
728
|
+
a?.addEventListener("abort", f, { once: !0 });
|
|
729
|
+
let m = null, v = null, G = !1;
|
|
687
730
|
try {
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
const b = await this.checkGateway(t,
|
|
691
|
-
if (!b) throw new Error(`Gateway ${
|
|
692
|
-
const
|
|
693
|
-
return
|
|
731
|
+
const S = await Promise.allSettled(
|
|
732
|
+
d.map(async (p) => {
|
|
733
|
+
const b = await this.checkGateway(t, p, s, g.signal);
|
|
734
|
+
if (!b) throw new Error(`Gateway ${p.host} failed check`);
|
|
735
|
+
const U = R(t, p, s), O = b === !0 ? "verified" : "maybe";
|
|
736
|
+
return G || (G = !0, O === "verified" ? (this.setActiveGateway(p, t), this.logger.info("Gateway resolved via fallback", {
|
|
694
737
|
txId: t,
|
|
695
|
-
gateway:
|
|
738
|
+
gateway: p.host,
|
|
696
739
|
pathSuffix: s,
|
|
697
|
-
workingUrl:
|
|
740
|
+
workingUrl: U
|
|
698
741
|
})) : this.logger.info("Gateway maybe resolved via fallback (no-cors)", {
|
|
699
742
|
txId: t,
|
|
700
|
-
gateway:
|
|
743
|
+
gateway: p.host,
|
|
701
744
|
pathSuffix: s,
|
|
702
|
-
workingUrl:
|
|
703
|
-
})), { kind:
|
|
745
|
+
workingUrl: U
|
|
746
|
+
})), { kind: O, url: U };
|
|
704
747
|
})
|
|
705
748
|
);
|
|
706
|
-
for (const
|
|
707
|
-
if (
|
|
708
|
-
const b =
|
|
709
|
-
if (b.kind === "verified" && !m ? m = { kind: "verified", url: b.url } : b.kind === "maybe" && !
|
|
749
|
+
for (const p of S) {
|
|
750
|
+
if (p.status !== "fulfilled") continue;
|
|
751
|
+
const b = p.value;
|
|
752
|
+
if (b.kind === "verified" && !m ? m = { kind: "verified", url: b.url } : b.kind === "maybe" && !v && (v = { kind: "maybe", url: b.url }), m) break;
|
|
710
753
|
}
|
|
711
|
-
return m ??
|
|
754
|
+
return m ?? v;
|
|
712
755
|
} finally {
|
|
713
|
-
a?.removeEventListener("abort",
|
|
756
|
+
a?.removeEventListener("abort", f);
|
|
714
757
|
}
|
|
715
|
-
},
|
|
716
|
-
if (
|
|
717
|
-
let
|
|
758
|
+
}, i = await w(h);
|
|
759
|
+
if (i && i.kind === "verified") return i;
|
|
760
|
+
let u = i;
|
|
718
761
|
if (n.length < c.length && !a?.aborted) {
|
|
719
|
-
const
|
|
720
|
-
this.logger.debug("Healthy gateways exhausted, retrying with previously unhealthy", { excludedHosts:
|
|
721
|
-
const
|
|
722
|
-
if (
|
|
723
|
-
return
|
|
762
|
+
const d = c.filter((f) => !n.some((m) => m.host === f.host)).map((f) => f.host);
|
|
763
|
+
this.logger.debug("Healthy gateways exhausted, retrying with previously unhealthy", { excludedHosts: d });
|
|
764
|
+
const g = await w(c);
|
|
765
|
+
if (g && (g.kind === "verified" || !u))
|
|
766
|
+
return g;
|
|
724
767
|
}
|
|
725
|
-
return
|
|
768
|
+
return u;
|
|
726
769
|
}
|
|
727
770
|
/**
|
|
728
771
|
* Return the ranked gateway list. The provider itself is already wrapped in
|
|
@@ -747,8 +790,8 @@ class K {
|
|
|
747
790
|
async tryWayfinder(e, t, s, a) {
|
|
748
791
|
if (!this.wayfinder) return null;
|
|
749
792
|
let r = null;
|
|
750
|
-
const l = 10,
|
|
751
|
-
a &&
|
|
793
|
+
const l = 10, o = /* @__PURE__ */ new Set();
|
|
794
|
+
a && o.add(a);
|
|
752
795
|
const c = `https://arweave.net/${e}${t}`;
|
|
753
796
|
try {
|
|
754
797
|
this.logger.debug("Resolving via Wayfinder (first pick)", { txId: e, normalizedUrl: c });
|
|
@@ -758,32 +801,32 @@ class K {
|
|
|
758
801
|
"Wayfinder resolution timed out"
|
|
759
802
|
);
|
|
760
803
|
if (s?.aborted) return null;
|
|
761
|
-
const
|
|
762
|
-
if (
|
|
763
|
-
this.logger.debug("Wayfinder first pick was already excluded, skipping to walk", { host:
|
|
804
|
+
const i = w.hostname;
|
|
805
|
+
if (o.has(i))
|
|
806
|
+
this.logger.debug("Wayfinder first pick was already excluded, skipping to walk", { host: i });
|
|
764
807
|
else {
|
|
765
|
-
const
|
|
766
|
-
host:
|
|
767
|
-
protocol:
|
|
808
|
+
const u = w.protocol.replace(":", ""), d = {
|
|
809
|
+
host: i,
|
|
810
|
+
protocol: u,
|
|
768
811
|
priority: 0
|
|
769
|
-
},
|
|
770
|
-
if (
|
|
771
|
-
const
|
|
772
|
-
if (m === "verified" ? (this.setActiveGateway(
|
|
812
|
+
}, g = await this.checkGateway(e, d, t, s);
|
|
813
|
+
if (g) {
|
|
814
|
+
const f = R(e, d, t), m = g === !0 ? "verified" : "maybe";
|
|
815
|
+
if (m === "verified" ? (this.setActiveGateway(d, e), this.logger.info("Gateway resolved via Wayfinder (first pick)", {
|
|
773
816
|
txId: e,
|
|
774
|
-
gateway:
|
|
817
|
+
gateway: i,
|
|
775
818
|
pathSuffix: t,
|
|
776
|
-
workingUrl:
|
|
819
|
+
workingUrl: f
|
|
777
820
|
})) : this.logger.info("Gateway maybe resolved via Wayfinder (first pick, no-cors)", {
|
|
778
821
|
txId: e,
|
|
779
|
-
gateway:
|
|
822
|
+
gateway: i,
|
|
780
823
|
pathSuffix: t,
|
|
781
|
-
workingUrl:
|
|
782
|
-
}), m === "verified") return { kind: m, url:
|
|
783
|
-
r ??= { kind: m, url:
|
|
824
|
+
workingUrl: f
|
|
825
|
+
}), m === "verified") return { kind: m, url: f };
|
|
826
|
+
r ??= { kind: m, url: f };
|
|
784
827
|
} else
|
|
785
|
-
this.logger.debug("Wayfinder first pick failed verify, will walk ranked list", { host:
|
|
786
|
-
|
|
828
|
+
this.logger.debug("Wayfinder first pick failed verify, will walk ranked list", { host: i });
|
|
829
|
+
o.add(i);
|
|
787
830
|
}
|
|
788
831
|
} catch (w) {
|
|
789
832
|
this.logger.debug("Wayfinder first pick threw, will walk ranked list", { error: w });
|
|
@@ -791,44 +834,44 @@ class K {
|
|
|
791
834
|
if (s?.aborted) return r;
|
|
792
835
|
const n = await this.getRankedGatewaysCached(s);
|
|
793
836
|
if (!n || n.length === 0 || s?.aborted) return r;
|
|
794
|
-
const
|
|
837
|
+
const h = n.filter((w) => !o.has(w.hostname)).slice(0, l);
|
|
795
838
|
this.logger.debug("Walking Wayfinder ranked list", {
|
|
796
839
|
txId: e,
|
|
797
840
|
poolSize: n.length,
|
|
798
|
-
walkSize:
|
|
841
|
+
walkSize: h.length
|
|
799
842
|
});
|
|
800
|
-
for (const w of
|
|
843
|
+
for (const w of h) {
|
|
801
844
|
if (s?.aborted) return r;
|
|
802
|
-
const
|
|
845
|
+
const i = {
|
|
803
846
|
host: w.hostname,
|
|
804
847
|
protocol: w.protocol.replace(":", ""),
|
|
805
848
|
priority: 0
|
|
806
|
-
},
|
|
807
|
-
if (
|
|
808
|
-
const
|
|
809
|
-
if (
|
|
810
|
-
return this.setActiveGateway(
|
|
849
|
+
}, u = await this.checkGateway(e, i, t, s);
|
|
850
|
+
if (u) {
|
|
851
|
+
const d = R(e, i, t), g = u === !0 ? "verified" : "maybe";
|
|
852
|
+
if (g === "verified")
|
|
853
|
+
return this.setActiveGateway(i, e), this.logger.info("Gateway resolved via Wayfinder ranked walk", {
|
|
811
854
|
txId: e,
|
|
812
|
-
gateway:
|
|
855
|
+
gateway: i.host,
|
|
813
856
|
pathSuffix: t,
|
|
814
|
-
workingUrl:
|
|
815
|
-
}), { kind:
|
|
857
|
+
workingUrl: d
|
|
858
|
+
}), { kind: g, url: d };
|
|
816
859
|
this.logger.info("Gateway maybe resolved via Wayfinder ranked walk (no-cors)", {
|
|
817
860
|
txId: e,
|
|
818
|
-
gateway:
|
|
861
|
+
gateway: i.host,
|
|
819
862
|
pathSuffix: t,
|
|
820
|
-
workingUrl:
|
|
821
|
-
}), r ??= { kind:
|
|
863
|
+
workingUrl: d
|
|
864
|
+
}), r ??= { kind: g, url: d };
|
|
822
865
|
}
|
|
823
|
-
|
|
866
|
+
o.add(i.host);
|
|
824
867
|
}
|
|
825
868
|
return r ? this.logger.debug("Wayfinder returning best maybe-result (no verified match)", {
|
|
826
869
|
txId: e,
|
|
827
|
-
checkedHosts: Array.from(
|
|
870
|
+
checkedHosts: Array.from(o),
|
|
828
871
|
maybeUrl: r.url
|
|
829
872
|
}) : this.logger.debug("Wayfinder ranked walk exhausted with no match", {
|
|
830
873
|
txId: e,
|
|
831
|
-
checkedHosts: Array.from(
|
|
874
|
+
checkedHosts: Array.from(o)
|
|
832
875
|
}), r;
|
|
833
876
|
}
|
|
834
877
|
/**
|
|
@@ -941,51 +984,51 @@ class K {
|
|
|
941
984
|
* @returns true if the gateway can serve the transaction
|
|
942
985
|
*/
|
|
943
986
|
async checkGateway(e, t, s = "", a) {
|
|
944
|
-
const r =
|
|
987
|
+
const r = R(e, t, s), l = Date.now(), o = new AbortController(), c = setTimeout(() => o.abort(), this.timeout), n = () => o.abort();
|
|
945
988
|
a?.addEventListener("abort", n, { once: !0 });
|
|
946
989
|
try {
|
|
947
|
-
const
|
|
990
|
+
const h = await fetch(r, {
|
|
948
991
|
method: "HEAD",
|
|
949
992
|
mode: "cors",
|
|
950
|
-
signal:
|
|
993
|
+
signal: o.signal
|
|
951
994
|
});
|
|
952
995
|
clearTimeout(c), a?.removeEventListener("abort", n);
|
|
953
|
-
const w = Date.now() - l,
|
|
954
|
-
return this.recordGatewayResponse(t.host, w,
|
|
955
|
-
} catch (
|
|
996
|
+
const w = Date.now() - l, i = h.ok || h.status >= 200 && h.status < 400;
|
|
997
|
+
return this.recordGatewayResponse(t.host, w, i), i;
|
|
998
|
+
} catch (h) {
|
|
956
999
|
clearTimeout(c), a?.removeEventListener("abort", n);
|
|
957
1000
|
const w = Date.now() - l;
|
|
958
|
-
if (this.recordGatewayResponse(t.host, w, !1),
|
|
959
|
-
const
|
|
960
|
-
return this.logger.debug(
|
|
1001
|
+
if (this.recordGatewayResponse(t.host, w, !1), h instanceof Error && h.name === "AbortError") {
|
|
1002
|
+
const i = a?.aborted;
|
|
1003
|
+
return this.logger.debug(i ? "Gateway check aborted by external signal" : "Gateway check timed out", {
|
|
961
1004
|
txId: e,
|
|
962
1005
|
gateway: t.host,
|
|
963
1006
|
timeout: this.timeout
|
|
964
1007
|
}), !1;
|
|
965
1008
|
}
|
|
966
|
-
if (
|
|
1009
|
+
if (h instanceof TypeError) {
|
|
967
1010
|
this.logger.debug("HEAD failed (likely CORS), retrying with no-cors", {
|
|
968
1011
|
txId: e,
|
|
969
1012
|
gateway: t.host,
|
|
970
|
-
error:
|
|
1013
|
+
error: h.message
|
|
971
1014
|
});
|
|
972
1015
|
try {
|
|
973
1016
|
await fetch(r, {
|
|
974
1017
|
method: "GET",
|
|
975
1018
|
mode: "no-cors",
|
|
976
|
-
signal:
|
|
1019
|
+
signal: o.signal
|
|
977
1020
|
});
|
|
978
|
-
const
|
|
979
|
-
return this.recordGatewayResponse(t.host,
|
|
1021
|
+
const i = Date.now() - l;
|
|
1022
|
+
return this.recordGatewayResponse(t.host, i, !0), "maybe";
|
|
980
1023
|
} catch {
|
|
981
|
-
const
|
|
982
|
-
return this.recordGatewayResponse(t.host,
|
|
1024
|
+
const i = Date.now() - l;
|
|
1025
|
+
return this.recordGatewayResponse(t.host, i, !1), !1;
|
|
983
1026
|
}
|
|
984
1027
|
}
|
|
985
1028
|
return this.logger.debug("Gateway HEAD failed", {
|
|
986
1029
|
txId: e,
|
|
987
1030
|
gateway: t.host,
|
|
988
|
-
error:
|
|
1031
|
+
error: h instanceof Error ? h.message : String(h)
|
|
989
1032
|
}), !1;
|
|
990
1033
|
}
|
|
991
1034
|
}
|
|
@@ -1027,8 +1070,8 @@ class K {
|
|
|
1027
1070
|
* runs the full fallback chain instead of short-circuiting.
|
|
1028
1071
|
*/
|
|
1029
1072
|
invalidateCacheForUrl(e) {
|
|
1030
|
-
if (!
|
|
1031
|
-
const t =
|
|
1073
|
+
if (!C(e)) return;
|
|
1074
|
+
const t = A(e);
|
|
1032
1075
|
t && (this.cache.delete(t.txId), this.activeGateway = null, this.clearPersistedGateway());
|
|
1033
1076
|
}
|
|
1034
1077
|
/**
|
|
@@ -1052,73 +1095,73 @@ class K {
|
|
|
1052
1095
|
*/
|
|
1053
1096
|
async prefetchUrls(e, t) {
|
|
1054
1097
|
const s = t?.concurrency ?? 5, a = t?.continueOnError ?? !0, r = [];
|
|
1055
|
-
let l = 0,
|
|
1098
|
+
let l = 0, o = 0, c = 0;
|
|
1056
1099
|
for (let n = 0; n < e.length; n += s) {
|
|
1057
|
-
const
|
|
1058
|
-
|
|
1059
|
-
if (!
|
|
1100
|
+
const h = e.slice(n, n + s), w = await Promise.allSettled(
|
|
1101
|
+
h.map(async (i) => {
|
|
1102
|
+
if (!C(i))
|
|
1060
1103
|
return {
|
|
1061
|
-
url:
|
|
1104
|
+
url: i,
|
|
1062
1105
|
txId: "",
|
|
1063
1106
|
success: !1,
|
|
1064
1107
|
error: "Not an Arweave URL"
|
|
1065
1108
|
};
|
|
1066
|
-
const
|
|
1067
|
-
if (!
|
|
1109
|
+
const u = A(i);
|
|
1110
|
+
if (!u)
|
|
1068
1111
|
return {
|
|
1069
|
-
url:
|
|
1112
|
+
url: i,
|
|
1070
1113
|
txId: "",
|
|
1071
1114
|
success: !1,
|
|
1072
1115
|
error: "Failed to parse Arweave URL"
|
|
1073
1116
|
};
|
|
1074
|
-
const
|
|
1075
|
-
if (
|
|
1117
|
+
const d = this.getCachedGateway(u.txId);
|
|
1118
|
+
if (d)
|
|
1076
1119
|
return {
|
|
1077
|
-
url:
|
|
1078
|
-
txId:
|
|
1120
|
+
url: i,
|
|
1121
|
+
txId: u.txId,
|
|
1079
1122
|
success: !0,
|
|
1080
|
-
gateway:
|
|
1123
|
+
gateway: d
|
|
1081
1124
|
};
|
|
1082
|
-
await this.resolveUrl(
|
|
1083
|
-
const
|
|
1084
|
-
return
|
|
1085
|
-
url:
|
|
1086
|
-
txId:
|
|
1125
|
+
await this.resolveUrl(i);
|
|
1126
|
+
const g = this.getCachedGateway(u.txId);
|
|
1127
|
+
return g ? {
|
|
1128
|
+
url: i,
|
|
1129
|
+
txId: u.txId,
|
|
1087
1130
|
success: !0,
|
|
1088
|
-
gateway:
|
|
1131
|
+
gateway: g
|
|
1089
1132
|
} : {
|
|
1090
|
-
url:
|
|
1091
|
-
txId:
|
|
1133
|
+
url: i,
|
|
1134
|
+
txId: u.txId,
|
|
1092
1135
|
success: !1,
|
|
1093
1136
|
error: "All gateways failed"
|
|
1094
1137
|
};
|
|
1095
1138
|
})
|
|
1096
1139
|
);
|
|
1097
|
-
for (const
|
|
1098
|
-
if (
|
|
1099
|
-
const
|
|
1100
|
-
r.push(
|
|
1140
|
+
for (const i of w)
|
|
1141
|
+
if (i.status === "fulfilled") {
|
|
1142
|
+
const u = i.value;
|
|
1143
|
+
r.push(u), u.error === "Not an Arweave URL" ? c++ : u.success ? l++ : o++;
|
|
1101
1144
|
} else
|
|
1102
|
-
|
|
1145
|
+
o++, r.push({
|
|
1103
1146
|
url: "unknown",
|
|
1104
1147
|
txId: "",
|
|
1105
1148
|
success: !1,
|
|
1106
|
-
error:
|
|
1149
|
+
error: i.reason?.message ?? "Unknown error"
|
|
1107
1150
|
});
|
|
1108
|
-
if (!a &&
|
|
1109
|
-
this.logger.warn("Prefetch stopped due to failures", { failed:
|
|
1151
|
+
if (!a && o > 0) {
|
|
1152
|
+
this.logger.warn("Prefetch stopped due to failures", { failed: o, processed: r.length });
|
|
1110
1153
|
break;
|
|
1111
1154
|
}
|
|
1112
1155
|
}
|
|
1113
1156
|
return this.logger.info("Prefetch complete", {
|
|
1114
1157
|
total: e.length,
|
|
1115
1158
|
succeeded: l,
|
|
1116
|
-
failed:
|
|
1159
|
+
failed: o,
|
|
1117
1160
|
skipped: c
|
|
1118
1161
|
}), {
|
|
1119
1162
|
total: e.length,
|
|
1120
1163
|
succeeded: l,
|
|
1121
|
-
failed:
|
|
1164
|
+
failed: o,
|
|
1122
1165
|
skipped: c,
|
|
1123
1166
|
entries: r
|
|
1124
1167
|
};
|
|
@@ -1220,7 +1263,7 @@ class K {
|
|
|
1220
1263
|
* @returns Health statistics for the gateway, or undefined if not tracked
|
|
1221
1264
|
*/
|
|
1222
1265
|
getGatewayHealth(e) {
|
|
1223
|
-
const t = this.gateways.find((
|
|
1266
|
+
const t = this.gateways.find((i) => i.host === e);
|
|
1224
1267
|
if (!t)
|
|
1225
1268
|
return;
|
|
1226
1269
|
const s = this.healthData.get(e) || [], a = this.originalPriorities.get(e) ?? t.priority;
|
|
@@ -1239,9 +1282,9 @@ class K {
|
|
|
1239
1282
|
isHealthy: !0
|
|
1240
1283
|
// Assume healthy until we have data
|
|
1241
1284
|
};
|
|
1242
|
-
const r = s.filter((
|
|
1243
|
-
s.reduce((
|
|
1244
|
-
), n = s.filter((
|
|
1285
|
+
const r = s.filter((i) => i.success).length, l = s.filter((i) => !i.success).length, o = r / s.length, c = Math.round(
|
|
1286
|
+
s.reduce((i, u) => i + u.responseTime, 0) / s.length
|
|
1287
|
+
), n = s.filter((i) => i.success).pop()?.timestamp ?? null, h = s.filter((i) => !i.success).pop()?.timestamp ?? null, w = o >= 0.5;
|
|
1245
1288
|
return {
|
|
1246
1289
|
host: e,
|
|
1247
1290
|
priority: t.priority,
|
|
@@ -1249,10 +1292,10 @@ class K {
|
|
|
1249
1292
|
successCount: r,
|
|
1250
1293
|
failureCount: l,
|
|
1251
1294
|
totalChecks: s.length,
|
|
1252
|
-
successRate:
|
|
1295
|
+
successRate: o,
|
|
1253
1296
|
averageResponseTime: c,
|
|
1254
1297
|
lastSuccess: n,
|
|
1255
|
-
lastFailure:
|
|
1298
|
+
lastFailure: h,
|
|
1256
1299
|
isHealthy: w
|
|
1257
1300
|
};
|
|
1258
1301
|
}
|
|
@@ -1276,33 +1319,33 @@ class K {
|
|
|
1276
1319
|
* @returns The new gateway order after adjustment
|
|
1277
1320
|
*/
|
|
1278
1321
|
adjustGatewayPriorities(e) {
|
|
1279
|
-
const t = e?.minChecks ?? 3, s = e?.healthyThreshold ?? 0.5, r = this.getAllGatewayHealth().reduce((n,
|
|
1322
|
+
const t = e?.minChecks ?? 3, s = e?.healthyThreshold ?? 0.5, r = this.getAllGatewayHealth().reduce((n, h) => n + h.totalChecks, 0);
|
|
1280
1323
|
if (r < t)
|
|
1281
1324
|
return this.logger.debug("Not enough health data to adjust priorities", {
|
|
1282
1325
|
totalChecks: r,
|
|
1283
1326
|
required: t
|
|
1284
1327
|
}), [...this.gateways];
|
|
1285
|
-
const l = this.gateways.map((n,
|
|
1286
|
-
l.sort((n,
|
|
1287
|
-
const w = this.getGatewayHealth(n.gateway.host),
|
|
1288
|
-
if (
|
|
1289
|
-
if (!
|
|
1290
|
-
if (w.totalChecks > 0 &&
|
|
1291
|
-
const
|
|
1292
|
-
if (Math.abs(
|
|
1293
|
-
return
|
|
1328
|
+
const l = this.gateways.map((n, h) => ({ gateway: n, originalIndex: h }));
|
|
1329
|
+
l.sort((n, h) => {
|
|
1330
|
+
const w = this.getGatewayHealth(n.gateway.host), i = this.getGatewayHealth(h.gateway.host), u = w.successRate >= s || w.totalChecks === 0, d = i.successRate >= s || i.totalChecks === 0;
|
|
1331
|
+
if (u && !d) return -1;
|
|
1332
|
+
if (!u && d) return 1;
|
|
1333
|
+
if (w.totalChecks > 0 && i.totalChecks > 0) {
|
|
1334
|
+
const g = w.averageResponseTime - i.averageResponseTime;
|
|
1335
|
+
if (Math.abs(g) > 100)
|
|
1336
|
+
return g;
|
|
1294
1337
|
}
|
|
1295
|
-
if (w.totalChecks > 0 &&
|
|
1296
|
-
const
|
|
1297
|
-
if (Math.abs(
|
|
1298
|
-
return
|
|
1338
|
+
if (w.totalChecks > 0 && i.totalChecks > 0) {
|
|
1339
|
+
const g = i.successRate - w.successRate;
|
|
1340
|
+
if (Math.abs(g) > 0.1)
|
|
1341
|
+
return g;
|
|
1299
1342
|
}
|
|
1300
|
-
return n.originalIndex -
|
|
1343
|
+
return n.originalIndex - h.originalIndex;
|
|
1301
1344
|
});
|
|
1302
|
-
const
|
|
1303
|
-
return
|
|
1304
|
-
n.priority = c +
|
|
1305
|
-
}), this.gateways =
|
|
1345
|
+
const o = l.map((n) => n.gateway), c = Math.min(...Array.from(this.originalPriorities.values()));
|
|
1346
|
+
return o.forEach((n, h) => {
|
|
1347
|
+
n.priority = c + h;
|
|
1348
|
+
}), this.gateways = o, this.logger.info("Gateway priorities adjusted", {
|
|
1306
1349
|
newOrder: this.gateways.map((n) => ({
|
|
1307
1350
|
host: n.host,
|
|
1308
1351
|
priority: n.priority,
|
|
@@ -1332,51 +1375,51 @@ class K {
|
|
|
1332
1375
|
const t = Date.now(), s = e?.txId ?? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk012345", a = e?.healthyThreshold ?? 0.5;
|
|
1333
1376
|
e?.fastThreshold, this.logger.info("Starting gateway health check", {
|
|
1334
1377
|
txId: s,
|
|
1335
|
-
gateways: this.gateways.map((
|
|
1378
|
+
gateways: this.gateways.map((d) => d.host)
|
|
1336
1379
|
});
|
|
1337
|
-
const r = this.gateways.map(async (
|
|
1338
|
-
const
|
|
1339
|
-
let
|
|
1380
|
+
const r = this.gateways.map(async (d) => {
|
|
1381
|
+
const g = Date.now();
|
|
1382
|
+
let f = !1, m = this.timeout;
|
|
1340
1383
|
try {
|
|
1341
|
-
const
|
|
1384
|
+
const v = R(s, d), G = new AbortController(), S = setTimeout(() => G.abort(), this.timeout), p = await fetch(v, {
|
|
1342
1385
|
method: "HEAD",
|
|
1343
1386
|
mode: "cors",
|
|
1344
|
-
signal:
|
|
1387
|
+
signal: G.signal
|
|
1345
1388
|
});
|
|
1346
|
-
clearTimeout(
|
|
1389
|
+
clearTimeout(S), m = Date.now() - g, f = p.ok || p.status >= 200 && p.status < 400;
|
|
1347
1390
|
} catch {
|
|
1348
|
-
m = Date.now() -
|
|
1391
|
+
m = Date.now() - g, f = !1;
|
|
1349
1392
|
}
|
|
1350
|
-
return this.recordGatewayResponse(
|
|
1351
|
-
host:
|
|
1352
|
-
success:
|
|
1393
|
+
return this.recordGatewayResponse(d.host, m, f), {
|
|
1394
|
+
host: d.host,
|
|
1395
|
+
success: f,
|
|
1353
1396
|
responseTime: m
|
|
1354
1397
|
};
|
|
1355
|
-
}), l = await Promise.all(r),
|
|
1356
|
-
let
|
|
1357
|
-
for (const
|
|
1358
|
-
const
|
|
1359
|
-
|
|
1398
|
+
}), l = await Promise.all(r), o = this.getAllGatewayHealth(), c = [], n = [];
|
|
1399
|
+
let h = null, w = 1 / 0;
|
|
1400
|
+
for (const d of l) {
|
|
1401
|
+
const g = o.find((f) => f.host === d.host);
|
|
1402
|
+
d.success && d.responseTime < w && (w = d.responseTime, h = d.host), (g.successRate >= a || g.totalChecks === 1) && d.success ? c.push(d.host) : n.push(d.host);
|
|
1360
1403
|
}
|
|
1361
|
-
const
|
|
1404
|
+
const i = Date.now() - t;
|
|
1362
1405
|
e?.adjustPriorities && this.adjustGatewayPriorities({
|
|
1363
1406
|
minChecks: e.minChecksForAdjustment ?? 1,
|
|
1364
1407
|
healthyThreshold: a
|
|
1365
1408
|
});
|
|
1366
|
-
const
|
|
1409
|
+
const u = {
|
|
1367
1410
|
timestamp: t,
|
|
1368
|
-
gateways:
|
|
1411
|
+
gateways: o,
|
|
1369
1412
|
healthyGateways: c,
|
|
1370
1413
|
unhealthyGateways: n,
|
|
1371
|
-
fastestGateway:
|
|
1372
|
-
totalTime:
|
|
1414
|
+
fastestGateway: h,
|
|
1415
|
+
totalTime: i
|
|
1373
1416
|
};
|
|
1374
1417
|
return this.logger.info("Health check complete", {
|
|
1375
1418
|
healthyGateways: c,
|
|
1376
1419
|
unhealthyGateways: n,
|
|
1377
|
-
fastestGateway:
|
|
1378
|
-
totalTime:
|
|
1379
|
-
}),
|
|
1420
|
+
fastestGateway: h,
|
|
1421
|
+
totalTime: i
|
|
1422
|
+
}), u;
|
|
1380
1423
|
}
|
|
1381
1424
|
/**
|
|
1382
1425
|
* Reset gateway priorities to their original values
|
|
@@ -1399,17 +1442,17 @@ class K {
|
|
|
1399
1442
|
this.healthData.clear(), this.logger.info("Health data cleared");
|
|
1400
1443
|
}
|
|
1401
1444
|
}
|
|
1402
|
-
const
|
|
1445
|
+
const Q = new J();
|
|
1403
1446
|
export {
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1447
|
+
J as A,
|
|
1448
|
+
I as D,
|
|
1449
|
+
F as K,
|
|
1450
|
+
P as L,
|
|
1451
|
+
Q as a,
|
|
1452
|
+
x as b,
|
|
1453
|
+
R as c,
|
|
1454
|
+
q as d,
|
|
1455
|
+
Z as g,
|
|
1456
|
+
C as i,
|
|
1457
|
+
A as p
|
|
1415
1458
|
};
|