@statewalker/webrun-http-proxy 0.2.0 → 0.2.2
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/index.js +19 -8
- package/dist/proxy.d.ts.map +1 -1
- package/package.json +7 -6
- package/src/proxy.ts +59 -8
package/dist/index.js
CHANGED
|
@@ -19,15 +19,26 @@ function urlUpstream(init) {
|
|
|
19
19
|
if (init.via != null) headers.set("via", init.via);
|
|
20
20
|
for (const [name, value] of Object.entries(init.headers ?? {})) headers.set(name, value);
|
|
21
21
|
for (const [name, value] of Object.entries(init.credential?.() ?? {})) headers.set(name, value);
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
22
|
+
const bodyless = request.method === "GET" || request.method === "HEAD";
|
|
23
|
+
let body;
|
|
24
|
+
let streaming = false;
|
|
25
|
+
if (!bodyless && request.body != null) {
|
|
26
|
+
body = request.body;
|
|
27
|
+
streaming = true;
|
|
28
|
+
}
|
|
30
29
|
try {
|
|
30
|
+
if (!bodyless && !streaming) {
|
|
31
|
+
const buffered = await request.arrayBuffer();
|
|
32
|
+
if (buffered.byteLength > 0) body = buffered;
|
|
33
|
+
}
|
|
34
|
+
const outbound = new Request(target, {
|
|
35
|
+
method: request.method,
|
|
36
|
+
headers,
|
|
37
|
+
body,
|
|
38
|
+
...streaming ? { duplex: "half" } : {},
|
|
39
|
+
signal: request.signal,
|
|
40
|
+
redirect: "manual"
|
|
41
|
+
});
|
|
31
42
|
const upstream = await doFetch(outbound);
|
|
32
43
|
if (upstream.type === "opaqueredirect" || upstream.status === 0) return new Response("upstream redirected; the proxy does not follow redirects", {
|
|
33
44
|
status: 502,
|
package/dist/proxy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEnE,0HAA0H;AAC1H,eAAO,MAAM,MAAM,mBAAmB,CAAC;AAEvC;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,YAAY,CAAC;AAEpC,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAExC,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,+FAA+F;IAC/F,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACtD,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,QAAQ,
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEnE,0HAA0H;AAC1H,eAAO,MAAM,MAAM,mBAAmB,CAAC;AAEvC;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,YAAY,CAAC;AAEpC,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAExC,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,+FAA+F;IAC/F,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACtD,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,QAAQ,CAkH3D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statewalker/webrun-http-proxy",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Re-issue an HTTP request to an outside origin, safely: credentials consumed, hop-by-hop headers dropped, body streamed.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
|
+
"source": "./src/index.ts",
|
|
16
17
|
"types": "./dist/index.d.ts",
|
|
17
18
|
"import": "./dist/index.js"
|
|
18
19
|
}
|
|
@@ -24,13 +25,13 @@
|
|
|
24
25
|
"sideEffects": false,
|
|
25
26
|
"dependencies": {},
|
|
26
27
|
"devDependencies": {
|
|
27
|
-
"@biomejs/biome": "^2.5.
|
|
28
|
-
"@types/node": "^26.
|
|
29
|
-
"hono": "^4.13.
|
|
28
|
+
"@biomejs/biome": "^2.5.15",
|
|
29
|
+
"@types/node": "^26.6.4",
|
|
30
|
+
"hono": "^4.13.12",
|
|
30
31
|
"rimraf": "^6.1.3",
|
|
31
|
-
"rolldown": "^1.2.
|
|
32
|
+
"rolldown": "^1.2.12",
|
|
32
33
|
"typescript": "^7.0.2",
|
|
33
|
-
"vitest": "^
|
|
34
|
+
"vitest": "^5.0.3"
|
|
34
35
|
},
|
|
35
36
|
"publishConfig": {
|
|
36
37
|
"access": "public"
|
package/src/proxy.ts
CHANGED
|
@@ -99,16 +99,67 @@ export function urlUpstream(init: UrlUpstreamInit): Upstream {
|
|
|
99
99
|
for (const [name, value] of Object.entries(init.headers ?? {})) headers.set(name, value);
|
|
100
100
|
for (const [name, value] of Object.entries(init.credential?.() ?? {})) headers.set(name, value);
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
// FIREFOX HAS NO `Request.prototype.body` (checked against 155):
|
|
103
|
+
// `request.body` reads as `undefined` there even for a genuine payload, so
|
|
104
|
+
// `body: request.body` silently builds a bodyless outbound request in that
|
|
105
|
+
// engine while working in Chromium, where the property is a stream. This
|
|
106
|
+
// defect has shipped from this exact pattern four times already
|
|
107
|
+
// (statewalker/httpeers: edge-dispatch.ts, two sites in core/router.ts, a
|
|
108
|
+
// demo page) — a POST through a session origin arrived as 0 bytes sent, in
|
|
109
|
+
// Firefox only, everywhere else silent.
|
|
110
|
+
//
|
|
111
|
+
// GET/HEAD is decided FIRST and never touches the body at all -- the
|
|
112
|
+
// Fetch spec forbids giving either one a body (`new Request(url, {
|
|
113
|
+
// method: "GET", body })` throws), so deciding this before anything else
|
|
114
|
+
// removes the await for the common case and removes any reliance on a
|
|
115
|
+
// byte count being the only thing standing between a GET and that throw.
|
|
116
|
+
// Matches `bodyOf` in httpeers-core, which the other four fixed sites use.
|
|
117
|
+
const bodyless = request.method === "GET" || request.method === "HEAD";
|
|
118
|
+
|
|
119
|
+
// `duplex` is a plain boolean decided in the branch that already knows,
|
|
120
|
+
// not `body instanceof ReadableStream` checked later: a stream built in a
|
|
121
|
+
// different realm (crossing a worker or iframe boundary) fails that
|
|
122
|
+
// `instanceof`, `duplex` would then be silently omitted, and `new
|
|
123
|
+
// Request` throws "duplex option is required" for a stream body.
|
|
124
|
+
let body: ReadableStream<Uint8Array> | ArrayBuffer | undefined;
|
|
125
|
+
let streaming = false;
|
|
126
|
+
if (!bodyless && request.body != null) {
|
|
127
|
+
// The streaming path stays first and unconditional: a runtime that has
|
|
128
|
+
// request streams never reaches the buffering fallback below, and
|
|
129
|
+
// never buffers an upload.
|
|
130
|
+
body = request.body;
|
|
131
|
+
streaming = true;
|
|
132
|
+
}
|
|
110
133
|
|
|
111
134
|
try {
|
|
135
|
+
if (!bodyless && !streaming) {
|
|
136
|
+
// Firefox. Without `request.body` there is nothing to stream from, so
|
|
137
|
+
// the only way to see the payload at all is to buffer the whole of
|
|
138
|
+
// it. This runs INSIDE the try: Firefox hides `body` for EVERY
|
|
139
|
+
// request, so a request whose body was already consumed elsewhere
|
|
140
|
+
// throws `TypeError: Body is unusable` here too, and uncaught that
|
|
141
|
+
// would turn a proxied call into a rejected promise instead of this
|
|
142
|
+
// package's own `502 upstream-unreachable` -- a new failure mode, in
|
|
143
|
+
// exactly the engine this buffering exists to fix. Caught here, it is
|
|
144
|
+
// reported the same as any other unreachable upstream.
|
|
145
|
+
//
|
|
146
|
+
// A zero-length result is indistinguishable from an absent body
|
|
147
|
+
// (Chromium's own `new Request(url, { method: "POST", body: "" })`
|
|
148
|
+
// yields a non-null *empty* stream, handled by the branch above), so
|
|
149
|
+
// an empty buffer here is treated as absent too.
|
|
150
|
+
const buffered = await request.arrayBuffer();
|
|
151
|
+
if (buffered.byteLength > 0) body = buffered;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const outbound = new Request(target, {
|
|
155
|
+
method: request.method,
|
|
156
|
+
headers,
|
|
157
|
+
body,
|
|
158
|
+
...(streaming ? { duplex: "half" as const } : {}),
|
|
159
|
+
signal: request.signal,
|
|
160
|
+
redirect: "manual",
|
|
161
|
+
});
|
|
162
|
+
|
|
112
163
|
const upstream = await doFetch(outbound);
|
|
113
164
|
// AN OPAQUE REDIRECT HAS STATUS 0, and `new Response(body, {status: 0})`
|
|
114
165
|
// throws — which is how the shipping proxy reports a redirecting
|