@dvmkit/sdk 0.14.0 → 0.14.1
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.
|
@@ -6,7 +6,8 @@ function envFlag(value) {
|
|
|
6
6
|
// src/sdk/server/ssrf.ts
|
|
7
7
|
import { lookup } from "dns/promises";
|
|
8
8
|
import { isIPv4, isIPv6 } from "net";
|
|
9
|
-
import {
|
|
9
|
+
import { Readable } from "stream";
|
|
10
|
+
import { Agent, getGlobalDispatcher } from "undici";
|
|
10
11
|
var SSRFError = class extends Error {
|
|
11
12
|
code = "ssrf_blocked";
|
|
12
13
|
reason;
|
|
@@ -44,25 +45,27 @@ async function createPinnedFetch(url, opts) {
|
|
|
44
45
|
const pinnedFetch = async (input, init) => {
|
|
45
46
|
let request = new Request(input, init);
|
|
46
47
|
let redirects = 0;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
request,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
try {
|
|
49
|
+
for (; ; ) {
|
|
50
|
+
const target = await targetFor(request.url);
|
|
51
|
+
const response = await fetchPinnedHop(request, target, baseFetch);
|
|
52
|
+
if (!isRedirect(response.status) || request.redirect === "manual") return response;
|
|
53
|
+
const location = response.headers.get("location");
|
|
54
|
+
if (location === null) return response;
|
|
55
|
+
await discardResponse(response);
|
|
56
|
+
if (request.redirect === "error") {
|
|
57
|
+
throw new TypeError("fetch failed: redirect mode is set to error");
|
|
58
|
+
}
|
|
59
|
+
if (redirects >= MAX_PINNED_REDIRECTS) {
|
|
60
|
+
throw new TypeError("fetch failed: maximum redirect count exceeded");
|
|
61
|
+
}
|
|
62
|
+
request = requestForRedirect(request, new URL(location, request.url), response.status);
|
|
63
|
+
redirects += 1;
|
|
64
|
+
opts?.onRedirectFollowed?.();
|
|
62
65
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
} catch (error) {
|
|
67
|
+
await request.body?.cancel(error).catch(() => void 0);
|
|
68
|
+
throw error;
|
|
66
69
|
}
|
|
67
70
|
};
|
|
68
71
|
return {
|
|
@@ -74,6 +77,64 @@ async function createPinnedFetch(url, opts) {
|
|
|
74
77
|
}
|
|
75
78
|
};
|
|
76
79
|
}
|
|
80
|
+
async function fetchPinnedHop(request, target, baseFetch) {
|
|
81
|
+
if (!request.body) {
|
|
82
|
+
return baseFetch(request, target.agent ? { redirect: "manual", dispatcher: target.agent } : { redirect: "manual" });
|
|
83
|
+
}
|
|
84
|
+
const body = request.body;
|
|
85
|
+
let reader;
|
|
86
|
+
let stopped = false;
|
|
87
|
+
let dispatched = false;
|
|
88
|
+
const upload = Readable.from((async function* () {
|
|
89
|
+
if (stopped) return;
|
|
90
|
+
reader = body.getReader();
|
|
91
|
+
try {
|
|
92
|
+
while (!stopped) {
|
|
93
|
+
const next = await reader.read();
|
|
94
|
+
if (next.done || stopped) return;
|
|
95
|
+
yield next.value;
|
|
96
|
+
}
|
|
97
|
+
} finally {
|
|
98
|
+
reader.releaseLock();
|
|
99
|
+
reader = void 0;
|
|
100
|
+
}
|
|
101
|
+
})(), { objectMode: false, highWaterMark: 64 * 1024 });
|
|
102
|
+
const stop = async (reason) => {
|
|
103
|
+
if (stopped) return;
|
|
104
|
+
stopped = true;
|
|
105
|
+
await (reader ? reader.cancel(reason) : body.cancel(reason)).catch(() => void 0);
|
|
106
|
+
};
|
|
107
|
+
const onAbort = () => {
|
|
108
|
+
void stop(request.signal.reason);
|
|
109
|
+
};
|
|
110
|
+
request.signal.addEventListener("abort", onAbort, { once: true });
|
|
111
|
+
const dispatcher = {
|
|
112
|
+
dispatch(options, handler) {
|
|
113
|
+
if (dispatched) throw new TypeError("fetch failed: cannot safely replay a request body");
|
|
114
|
+
dispatched = true;
|
|
115
|
+
return (target.agent ?? getGlobalDispatcher()).dispatch({
|
|
116
|
+
...options,
|
|
117
|
+
body: upload
|
|
118
|
+
}, handler);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
try {
|
|
122
|
+
return await baseFetch(request, {
|
|
123
|
+
redirect: "manual",
|
|
124
|
+
// A stream also prevents native automatic body replay (e.g. on HTTP 421).
|
|
125
|
+
body: new ReadableStream({ start(controller) {
|
|
126
|
+
controller.close();
|
|
127
|
+
} }),
|
|
128
|
+
duplex: "half",
|
|
129
|
+
// Node's keepalive is a no-op, but rejects streaming request envelopes.
|
|
130
|
+
keepalive: false,
|
|
131
|
+
dispatcher
|
|
132
|
+
});
|
|
133
|
+
} finally {
|
|
134
|
+
request.signal.removeEventListener("abort", onAbort);
|
|
135
|
+
await stop();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
77
138
|
var MAX_PINNED_REDIRECTS = 20;
|
|
78
139
|
function isRedirect(status) {
|
|
79
140
|
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
package/dist/index.js
CHANGED
|
@@ -1461,8 +1461,14 @@ interface JobStatus {
|
|
|
1461
1461
|
}
|
|
1462
1462
|
/**
|
|
1463
1463
|
* What a provider's `201` to a job message carries, when the transport
|
|
1464
|
-
* surfaces it. `seq`
|
|
1465
|
-
*
|
|
1464
|
+
* surfaces it. The successful standard message response's `seq` is the latest
|
|
1465
|
+
* message position observed after dispatch, so it can equal or exceed the
|
|
1466
|
+
* accepted payment's sequence. `drawFundingReceipt` is the signed fact a mid-job
|
|
1467
|
+
* rail payment funded the job's own draw: its optional `payment_seq` fixes the
|
|
1468
|
+
* accepted inbound payment sequence, while `ledger_seq` names the credit draw's
|
|
1469
|
+
* ledger position. Cursor equality is not a funding-validity requirement;
|
|
1470
|
+
* signature verification and all funding bindings still apply. Separately,
|
|
1471
|
+
* `503 job_snapshot_unavailable.seq` identifies the recorded inbound payment.
|
|
1466
1472
|
*/
|
|
1467
1473
|
interface SendMessageResult {
|
|
1468
1474
|
seq: number;
|
package/dist/server/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dvmkit/sdk",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "SDK for building Digital Vending Machines: pay-per-use HTTPS services AI agents can find, pay, and call with no account or API key",
|
|
5
5
|
"author": "DVM Technologies Ltd",
|
|
6
6
|
"keywords": [
|