@canister-software/consensus-cli 0.1.0-beta.3 → 0.1.0-beta.5
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/README.md +275 -40
- package/assets/dark-logo.png +0 -0
- package/assets/logo-dark.svg +73 -0
- package/assets/logo-light.svg +73 -0
- package/bin/consensus.js +188 -151
- package/dist/index.js +2 -0
- package/dist/proxy-client.js +466 -0
- package/dist/socket-client.js +440 -0
- package/index.d.ts +271 -0
- package/package.json +23 -3
- package/assets/setup.gif +0 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2
|
+
const DEFAULT_SERVER_URL = process.env.CONSENSUS_SERVER_URL || "https://consensus.canister.software";
|
|
3
|
+
const USD_SCALE = 1_000_000;
|
|
4
|
+
const PROXY_PAID_REQUEST_COST_USD = 0.0001;
|
|
5
|
+
class ProxyClientError extends Error {
|
|
6
|
+
/** HTTP status from proxy response when available. */
|
|
7
|
+
status;
|
|
8
|
+
/** Parsed proxy error payload when available. */
|
|
9
|
+
data;
|
|
10
|
+
}
|
|
11
|
+
const proxyFetchContext = new AsyncLocalStorage();
|
|
12
|
+
let interceptorInstalled = false;
|
|
13
|
+
let passthroughFetch = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : null;
|
|
14
|
+
function trimTrailingSlash(value) {
|
|
15
|
+
return String(value || "").replace(/\/+$/, "");
|
|
16
|
+
}
|
|
17
|
+
function normalizePath(value) {
|
|
18
|
+
const path = String(value || "/").split("?")[0] || "/";
|
|
19
|
+
if (path === "/")
|
|
20
|
+
return "/";
|
|
21
|
+
const normalized = path.replace(/\/+$/, "");
|
|
22
|
+
return normalized || "/";
|
|
23
|
+
}
|
|
24
|
+
function normalizeHeaders(headers) {
|
|
25
|
+
if (!headers)
|
|
26
|
+
return {};
|
|
27
|
+
if (typeof Headers !== "undefined" && headers instanceof Headers) {
|
|
28
|
+
return Object.fromEntries(headers.entries());
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(headers)) {
|
|
31
|
+
return Object.fromEntries(headers.map(([key, value]) => [String(key), String(value)]));
|
|
32
|
+
}
|
|
33
|
+
const result = {};
|
|
34
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
35
|
+
if (typeof value === "undefined" || value === null)
|
|
36
|
+
continue;
|
|
37
|
+
result[key] = String(value);
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
function pathMatches(pathname, route, matchSubroutes = false) {
|
|
42
|
+
const requestPath = normalizePath(pathname);
|
|
43
|
+
const configuredRoute = normalizePath(route);
|
|
44
|
+
if (requestPath === configuredRoute)
|
|
45
|
+
return true;
|
|
46
|
+
if (!matchSubroutes)
|
|
47
|
+
return false;
|
|
48
|
+
if (configuredRoute === "/")
|
|
49
|
+
return true;
|
|
50
|
+
return requestPath.startsWith(`${configuredRoute}/`);
|
|
51
|
+
}
|
|
52
|
+
function shouldProxyPath(pathname, options) {
|
|
53
|
+
const mode = options.mode === "exclusive" ? "exclusive" : "inclusive";
|
|
54
|
+
const routes = Array.isArray(options.routes) ? options.routes : [];
|
|
55
|
+
const matchSubroutes = Boolean(options.matchSubroutes);
|
|
56
|
+
const matched = routes.some((route) => pathMatches(pathname, route, matchSubroutes));
|
|
57
|
+
return mode === "exclusive" ? matched : !matched;
|
|
58
|
+
}
|
|
59
|
+
function controlHeadersFromOptions(options) {
|
|
60
|
+
const headers = {};
|
|
61
|
+
if (typeof options.cache_ttl !== "undefined" && options.cache_ttl !== null) {
|
|
62
|
+
headers["x-cache-ttl"] = String(options.cache_ttl);
|
|
63
|
+
}
|
|
64
|
+
if (options.verbose === true) {
|
|
65
|
+
headers["x-verbose"] = "true";
|
|
66
|
+
}
|
|
67
|
+
if (typeof options.node_region === "string" && options.node_region.trim()) {
|
|
68
|
+
headers["x-node-region"] = options.node_region.trim();
|
|
69
|
+
}
|
|
70
|
+
if (typeof options.node_domain === "string" && options.node_domain.trim()) {
|
|
71
|
+
headers["x-node-domain"] = options.node_domain.trim();
|
|
72
|
+
}
|
|
73
|
+
if (typeof options.node_exclude === "string" && options.node_exclude.trim()) {
|
|
74
|
+
headers["x-node-exclude"] = options.node_exclude.trim();
|
|
75
|
+
}
|
|
76
|
+
return headers;
|
|
77
|
+
}
|
|
78
|
+
function parseMaybeJson(text) {
|
|
79
|
+
if (!text)
|
|
80
|
+
return null;
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(text);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return text;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function parseUsdToMicros(value, fieldName) {
|
|
89
|
+
if (typeof value === "undefined" || value === null)
|
|
90
|
+
return null;
|
|
91
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
92
|
+
throw new TypeError(`${fieldName} must be a non-negative number`);
|
|
93
|
+
}
|
|
94
|
+
const micros = Math.round(value * USD_SCALE);
|
|
95
|
+
const normalized = micros / USD_SCALE;
|
|
96
|
+
if (Math.abs(normalized - value) > 1e-9) {
|
|
97
|
+
throw new TypeError(`${fieldName} supports at most 6 decimal places`);
|
|
98
|
+
}
|
|
99
|
+
return micros;
|
|
100
|
+
}
|
|
101
|
+
function microsToUsd(micros) {
|
|
102
|
+
return Number((micros / USD_SCALE).toFixed(6));
|
|
103
|
+
}
|
|
104
|
+
function normalizeBody(body, headers) {
|
|
105
|
+
if (typeof body === "undefined" || body === null)
|
|
106
|
+
return undefined;
|
|
107
|
+
if (typeof body === "string")
|
|
108
|
+
return body;
|
|
109
|
+
if (typeof body === "number" || typeof body === "boolean")
|
|
110
|
+
return body;
|
|
111
|
+
if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) {
|
|
112
|
+
if (!headers["content-type"] && !headers["Content-Type"]) {
|
|
113
|
+
headers["content-type"] = "application/x-www-form-urlencoded;charset=UTF-8";
|
|
114
|
+
}
|
|
115
|
+
return body.toString();
|
|
116
|
+
}
|
|
117
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(body)) {
|
|
118
|
+
return body.toString("utf8");
|
|
119
|
+
}
|
|
120
|
+
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
|
|
121
|
+
const bytes = body instanceof ArrayBuffer
|
|
122
|
+
? new Uint8Array(body)
|
|
123
|
+
: new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
|
|
124
|
+
if (typeof Buffer !== "undefined")
|
|
125
|
+
return Buffer.from(bytes).toString("utf8");
|
|
126
|
+
return new TextDecoder().decode(bytes);
|
|
127
|
+
}
|
|
128
|
+
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
129
|
+
throw new Error("FormData request bodies are not supported by ProxyClient");
|
|
130
|
+
}
|
|
131
|
+
if (typeof body === "object") {
|
|
132
|
+
if (!headers["content-type"] && !headers["Content-Type"]) {
|
|
133
|
+
headers["content-type"] = "application/json";
|
|
134
|
+
}
|
|
135
|
+
return body;
|
|
136
|
+
}
|
|
137
|
+
throw new Error(`Unsupported request body type: ${typeof body}`);
|
|
138
|
+
}
|
|
139
|
+
function bodyToInit(body, headers) {
|
|
140
|
+
const normalized = normalizeBody(body, headers);
|
|
141
|
+
if (typeof normalized === "undefined")
|
|
142
|
+
return undefined;
|
|
143
|
+
if (typeof normalized === "string")
|
|
144
|
+
return normalized;
|
|
145
|
+
if (typeof normalized === "object" &&
|
|
146
|
+
normalized !== null &&
|
|
147
|
+
!(normalized instanceof ArrayBuffer) &&
|
|
148
|
+
!ArrayBuffer.isView(normalized)) {
|
|
149
|
+
return JSON.stringify(normalized);
|
|
150
|
+
}
|
|
151
|
+
return normalized;
|
|
152
|
+
}
|
|
153
|
+
async function buildProxyPayload(input, init = {}, controlHeaders) {
|
|
154
|
+
let targetUrl;
|
|
155
|
+
let method = "GET";
|
|
156
|
+
let headers = {};
|
|
157
|
+
let body;
|
|
158
|
+
if (typeof Request !== "undefined" && input instanceof Request) {
|
|
159
|
+
targetUrl = input.url;
|
|
160
|
+
method = input.method || method;
|
|
161
|
+
headers = normalizeHeaders(input.headers);
|
|
162
|
+
if (!("body" in init) && method !== "GET" && method !== "HEAD") {
|
|
163
|
+
const raw = await input.clone().text();
|
|
164
|
+
if (raw.length > 0)
|
|
165
|
+
body = raw;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (typeof input === "string" || input instanceof URL) {
|
|
169
|
+
targetUrl = String(input);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
throw new Error("ProxyClient fetch input must be URL string, URL, or Request");
|
|
173
|
+
}
|
|
174
|
+
method = String(init.method || method || "GET").toUpperCase();
|
|
175
|
+
headers = {
|
|
176
|
+
...controlHeaders,
|
|
177
|
+
...headers,
|
|
178
|
+
...normalizeHeaders(init.headers),
|
|
179
|
+
};
|
|
180
|
+
if ("body" in init) {
|
|
181
|
+
body = init.body;
|
|
182
|
+
}
|
|
183
|
+
const normalizedBody = normalizeBody(body, headers);
|
|
184
|
+
return {
|
|
185
|
+
target_url: targetUrl,
|
|
186
|
+
method,
|
|
187
|
+
headers,
|
|
188
|
+
...(typeof normalizedBody !== "undefined" ? { body: normalizedBody } : {}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function toProxyResult(response, data) {
|
|
192
|
+
if (data && typeof data === "object" && "status" in data && "data" in data) {
|
|
193
|
+
const maybe = data;
|
|
194
|
+
return {
|
|
195
|
+
status: Number(maybe.status) || response.status || 200,
|
|
196
|
+
statusText: maybe.statusText || response.statusText || "",
|
|
197
|
+
headers: maybe.headers || {},
|
|
198
|
+
data: maybe.data,
|
|
199
|
+
meta: maybe.meta ?? null,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
status: response.status || 500,
|
|
204
|
+
statusText: response.statusText || "",
|
|
205
|
+
headers: {},
|
|
206
|
+
data,
|
|
207
|
+
meta: null,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function toFetchResponse(proxyResult, requestUrl) {
|
|
211
|
+
const headers = new Headers(proxyResult.headers || {});
|
|
212
|
+
const payload = proxyResult.data;
|
|
213
|
+
const body = payload === null || typeof payload === "undefined"
|
|
214
|
+
? null
|
|
215
|
+
: typeof payload === "string"
|
|
216
|
+
? payload
|
|
217
|
+
: JSON.stringify(payload);
|
|
218
|
+
if (payload !== null && typeof payload === "object" && !headers.has("content-type")) {
|
|
219
|
+
headers.set("content-type", "application/json");
|
|
220
|
+
}
|
|
221
|
+
const response = new Response(body, {
|
|
222
|
+
status: proxyResult.status,
|
|
223
|
+
statusText: proxyResult.statusText || "",
|
|
224
|
+
headers,
|
|
225
|
+
});
|
|
226
|
+
Object.defineProperty(response, "consensus", {
|
|
227
|
+
value: {
|
|
228
|
+
request_url: requestUrl,
|
|
229
|
+
meta: proxyResult.meta || null,
|
|
230
|
+
},
|
|
231
|
+
enumerable: false,
|
|
232
|
+
configurable: false,
|
|
233
|
+
writable: false,
|
|
234
|
+
});
|
|
235
|
+
return response;
|
|
236
|
+
}
|
|
237
|
+
function responseHeadersToRecord(headers) {
|
|
238
|
+
const record = {};
|
|
239
|
+
headers.forEach((value, key) => {
|
|
240
|
+
record[key] = value;
|
|
241
|
+
});
|
|
242
|
+
return record;
|
|
243
|
+
}
|
|
244
|
+
function isLikelyPaidProxyResponse(proxyResult) {
|
|
245
|
+
const meta = proxyResult.meta;
|
|
246
|
+
if (meta && typeof meta === "object" && "cached" in meta) {
|
|
247
|
+
return meta.cached !== true;
|
|
248
|
+
}
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
function ensureInterceptorInstalled() {
|
|
252
|
+
if (interceptorInstalled)
|
|
253
|
+
return;
|
|
254
|
+
if (typeof globalThis.fetch === "function") {
|
|
255
|
+
passthroughFetch = globalThis.fetch.bind(globalThis);
|
|
256
|
+
}
|
|
257
|
+
if (!passthroughFetch) {
|
|
258
|
+
throw new Error("Global fetch is unavailable; use strategy: 'manual' or polyfill fetch.");
|
|
259
|
+
}
|
|
260
|
+
globalThis.fetch = ((input, init) => {
|
|
261
|
+
const state = proxyFetchContext.getStore();
|
|
262
|
+
if (state?.proxyFetch)
|
|
263
|
+
return state.proxyFetch(input, init);
|
|
264
|
+
return passthroughFetch(input, init);
|
|
265
|
+
});
|
|
266
|
+
interceptorInstalled = true;
|
|
267
|
+
}
|
|
268
|
+
function currentPassthroughFetch() {
|
|
269
|
+
if (passthroughFetch)
|
|
270
|
+
return passthroughFetch;
|
|
271
|
+
if (typeof globalThis.fetch === "function") {
|
|
272
|
+
passthroughFetch = globalThis.fetch.bind(globalThis);
|
|
273
|
+
}
|
|
274
|
+
return passthroughFetch;
|
|
275
|
+
}
|
|
276
|
+
export function ProxyClient(fetchWithPayment, options = {}) {
|
|
277
|
+
if (typeof fetchWithPayment !== "function") {
|
|
278
|
+
throw new TypeError("ProxyClient requires fetchWithPayment as the first argument");
|
|
279
|
+
}
|
|
280
|
+
const strategy = options.strategy === "manual" ? "manual" : "auto";
|
|
281
|
+
const serverUrl = trimTrailingSlash(DEFAULT_SERVER_URL);
|
|
282
|
+
const proxyEndpoint = `${serverUrl}/proxy`;
|
|
283
|
+
const baseControlHeaders = controlHeadersFromOptions(options);
|
|
284
|
+
const limitMicros = parseUsdToMicros(options.limit_usd, "limit_usd");
|
|
285
|
+
const requestCostMicros = parseUsdToMicros(PROXY_PAID_REQUEST_COST_USD, "proxy_request_cost_usd") ?? 0;
|
|
286
|
+
let spentMicros = 0;
|
|
287
|
+
let limitCallbackFired = false;
|
|
288
|
+
function computeStandDownState() {
|
|
289
|
+
if (limitMicros === null)
|
|
290
|
+
return false;
|
|
291
|
+
if (spentMicros >= limitMicros)
|
|
292
|
+
return true;
|
|
293
|
+
if (requestCostMicros <= 0)
|
|
294
|
+
return false;
|
|
295
|
+
return spentMicros + requestCostMicros > limitMicros;
|
|
296
|
+
}
|
|
297
|
+
function getBudget() {
|
|
298
|
+
const remainingMicros = limitMicros === null ? null : Math.max(0, limitMicros - spentMicros);
|
|
299
|
+
return {
|
|
300
|
+
limit_usd: limitMicros === null ? null : microsToUsd(limitMicros),
|
|
301
|
+
request_cost_usd: microsToUsd(requestCostMicros),
|
|
302
|
+
spent_usd: microsToUsd(spentMicros),
|
|
303
|
+
remaining_usd: remainingMicros === null ? null : microsToUsd(remainingMicros),
|
|
304
|
+
exhausted: computeStandDownState(),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function isStandDown() {
|
|
308
|
+
const exhausted = computeStandDownState();
|
|
309
|
+
if (exhausted &&
|
|
310
|
+
!limitCallbackFired &&
|
|
311
|
+
typeof options.on_limit_reached === "function") {
|
|
312
|
+
limitCallbackFired = true;
|
|
313
|
+
options.on_limit_reached(getBudget());
|
|
314
|
+
}
|
|
315
|
+
return exhausted;
|
|
316
|
+
}
|
|
317
|
+
function incrementSpend(proxyResult) {
|
|
318
|
+
if (requestCostMicros <= 0)
|
|
319
|
+
return;
|
|
320
|
+
if (!isLikelyPaidProxyResponse(proxyResult))
|
|
321
|
+
return;
|
|
322
|
+
spentMicros += requestCostMicros;
|
|
323
|
+
if (limitMicros !== null && spentMicros > limitMicros)
|
|
324
|
+
spentMicros = limitMicros;
|
|
325
|
+
isStandDown();
|
|
326
|
+
}
|
|
327
|
+
function resetBudget() {
|
|
328
|
+
spentMicros = 0;
|
|
329
|
+
limitCallbackFired = false;
|
|
330
|
+
}
|
|
331
|
+
async function passthroughFetchOrThrow(input, init) {
|
|
332
|
+
const directFetch = currentPassthroughFetch();
|
|
333
|
+
if (!directFetch) {
|
|
334
|
+
throw new ProxyClientError("Global fetch is unavailable; cannot bypass proxy while in stand-down mode.");
|
|
335
|
+
}
|
|
336
|
+
return directFetch(input, init);
|
|
337
|
+
}
|
|
338
|
+
async function requestProxy(payload) {
|
|
339
|
+
const response = await fetchWithPayment(proxyEndpoint, {
|
|
340
|
+
method: "POST",
|
|
341
|
+
headers: { "content-type": "application/json" },
|
|
342
|
+
body: JSON.stringify(payload),
|
|
343
|
+
});
|
|
344
|
+
const raw = await response.text();
|
|
345
|
+
const parsed = parseMaybeJson(raw);
|
|
346
|
+
if (!response.ok && !(parsed && typeof parsed === "object" && "status" in parsed)) {
|
|
347
|
+
const message = parsed?.message ||
|
|
348
|
+
parsed?.error ||
|
|
349
|
+
`Proxy request failed (${response.status})`;
|
|
350
|
+
const error = new ProxyClientError(message);
|
|
351
|
+
error.status = response.status;
|
|
352
|
+
error.data = parsed;
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
return toProxyResult(response, parsed);
|
|
356
|
+
}
|
|
357
|
+
async function requestDirectFromPayload(payload, reason) {
|
|
358
|
+
const targetUrl = String(payload.target_url || "").trim();
|
|
359
|
+
if (!targetUrl) {
|
|
360
|
+
throw new ProxyClientError("target_url is required when proxy is in stand-down mode");
|
|
361
|
+
}
|
|
362
|
+
const method = String(payload.method || "GET").toUpperCase();
|
|
363
|
+
const headers = normalizeHeaders(payload.headers);
|
|
364
|
+
const init = {
|
|
365
|
+
method,
|
|
366
|
+
headers,
|
|
367
|
+
};
|
|
368
|
+
if (!["GET", "HEAD"].includes(method) && typeof payload.body !== "undefined") {
|
|
369
|
+
const convertedBody = bodyToInit(payload.body, headers);
|
|
370
|
+
if (typeof convertedBody !== "undefined")
|
|
371
|
+
init.body = convertedBody;
|
|
372
|
+
}
|
|
373
|
+
const response = await passthroughFetchOrThrow(targetUrl, init);
|
|
374
|
+
const raw = await response.text();
|
|
375
|
+
const parsed = parseMaybeJson(raw);
|
|
376
|
+
return {
|
|
377
|
+
status: response.status,
|
|
378
|
+
statusText: response.statusText || "",
|
|
379
|
+
headers: responseHeadersToRecord(response.headers),
|
|
380
|
+
data: parsed,
|
|
381
|
+
meta: { bypassed: true, reason },
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
async function proxiedFetch(input, init = {}, perRequestOptions = {}) {
|
|
385
|
+
if (isStandDown()) {
|
|
386
|
+
return passthroughFetchOrThrow(input, init);
|
|
387
|
+
}
|
|
388
|
+
const controlHeaders = {
|
|
389
|
+
...baseControlHeaders,
|
|
390
|
+
...controlHeadersFromOptions(perRequestOptions),
|
|
391
|
+
};
|
|
392
|
+
const payload = await buildProxyPayload(input, init, controlHeaders);
|
|
393
|
+
const proxyResult = await requestProxy(payload);
|
|
394
|
+
incrementSpend(proxyResult);
|
|
395
|
+
const requestUrl = typeof Request !== "undefined" && input instanceof Request ? input.url : String(input);
|
|
396
|
+
return toFetchResponse(proxyResult, requestUrl);
|
|
397
|
+
}
|
|
398
|
+
async function proxiedRequest(payload = {}, perRequestOptions = {}) {
|
|
399
|
+
if (isStandDown()) {
|
|
400
|
+
return requestDirectFromPayload(payload, "limit_reached");
|
|
401
|
+
}
|
|
402
|
+
const controlHeaders = {
|
|
403
|
+
...baseControlHeaders,
|
|
404
|
+
...controlHeadersFromOptions(perRequestOptions),
|
|
405
|
+
...normalizeHeaders(payload.headers),
|
|
406
|
+
};
|
|
407
|
+
const proxyResult = await requestProxy({
|
|
408
|
+
target_url: String(payload.target_url || ""),
|
|
409
|
+
method: String(payload.method || "GET").toUpperCase(),
|
|
410
|
+
headers: controlHeaders,
|
|
411
|
+
...(typeof payload.body !== "undefined" ? { body: payload.body } : {}),
|
|
412
|
+
});
|
|
413
|
+
incrementSpend(proxyResult);
|
|
414
|
+
return proxyResult;
|
|
415
|
+
}
|
|
416
|
+
function createFetch(pathname = "/") {
|
|
417
|
+
return (input, init) => {
|
|
418
|
+
if (!shouldProxyPath(pathname, options)) {
|
|
419
|
+
return passthroughFetchOrThrow(input, init);
|
|
420
|
+
}
|
|
421
|
+
return proxiedFetch(input, init);
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
async function runWithPath(pathname, run) {
|
|
425
|
+
if (typeof run !== "function") {
|
|
426
|
+
throw new TypeError("runWithPath requires a callback function");
|
|
427
|
+
}
|
|
428
|
+
ensureInterceptorInstalled();
|
|
429
|
+
const shouldProxy = shouldProxyPath(pathname, options);
|
|
430
|
+
return new Promise((resolve, reject) => {
|
|
431
|
+
proxyFetchContext.run({ proxyFetch: shouldProxy ? proxiedFetch : null }, () => {
|
|
432
|
+
Promise.resolve()
|
|
433
|
+
.then(run)
|
|
434
|
+
.then(resolve, reject);
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
const middleware = ((req, _res, next) => {
|
|
439
|
+
const routePath = req?.path || req?.url || "/";
|
|
440
|
+
const shouldProxy = shouldProxyPath(routePath, options) && !isStandDown();
|
|
441
|
+
req.consensus = {
|
|
442
|
+
strategy,
|
|
443
|
+
shouldProxy,
|
|
444
|
+
fetch: proxiedFetch,
|
|
445
|
+
request: proxiedRequest,
|
|
446
|
+
passthroughFetch: currentPassthroughFetch(),
|
|
447
|
+
createFetch,
|
|
448
|
+
getBudget,
|
|
449
|
+
isStandDown,
|
|
450
|
+
};
|
|
451
|
+
if (strategy !== "auto") {
|
|
452
|
+
next();
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
ensureInterceptorInstalled();
|
|
456
|
+
proxyFetchContext.run({ proxyFetch: shouldProxy ? proxiedFetch : null }, () => next());
|
|
457
|
+
});
|
|
458
|
+
middleware.fetch = proxiedFetch;
|
|
459
|
+
middleware.request = proxiedRequest;
|
|
460
|
+
middleware.runWithPath = runWithPath;
|
|
461
|
+
middleware.createFetch = createFetch;
|
|
462
|
+
middleware.getBudget = getBudget;
|
|
463
|
+
middleware.resetBudget = resetBudget;
|
|
464
|
+
middleware.isStandDown = isStandDown;
|
|
465
|
+
return middleware;
|
|
466
|
+
}
|