@floegence/flowersec-core 0.15.0 → 0.16.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/proxy/appWindow.d.ts +22 -0
- package/dist/proxy/appWindow.js +133 -0
- package/dist/proxy/controllerWindow.d.ts +11 -0
- package/dist/proxy/controllerWindow.js +124 -0
- package/dist/proxy/index.d.ts +2 -0
- package/dist/proxy/index.js +2 -0
- package/dist/proxy/portStream.d.ts +2 -0
- package/dist/proxy/portStream.js +120 -0
- package/dist/proxy/runtime.d.ts +11 -1
- package/dist/proxy/runtime.js +65 -63
- package/dist/proxy/serviceWorker.d.ts +2 -0
- package/dist/proxy/serviceWorker.js +40 -9
- package/dist/proxy/windowBridgeProtocol.d.ts +52 -0
- package/dist/proxy/windowBridgeProtocol.js +9 -0
- package/dist/proxy/wsPatch.d.ts +12 -2
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { YamuxStream } from "../yamux/stream.js";
|
|
2
|
+
import type { ProxyRuntimeLimits } from "./runtime.js";
|
|
3
|
+
export type RegisterProxyAppWindowOptions = Readonly<{
|
|
4
|
+
controllerOrigin: string;
|
|
5
|
+
controllerWindow?: Window | null;
|
|
6
|
+
targetWindow?: Window;
|
|
7
|
+
maxWsFrameBytes?: number;
|
|
8
|
+
}>;
|
|
9
|
+
export type ProxyAppWindowHandle = Readonly<{
|
|
10
|
+
runtime: Readonly<{
|
|
11
|
+
limits: Partial<ProxyRuntimeLimits>;
|
|
12
|
+
openWebSocketStream: (path: string, opts?: Readonly<{
|
|
13
|
+
protocols?: readonly string[];
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
}>) => Promise<Readonly<{
|
|
16
|
+
stream: YamuxStream;
|
|
17
|
+
protocol: string;
|
|
18
|
+
}>>;
|
|
19
|
+
}>;
|
|
20
|
+
dispose: () => void;
|
|
21
|
+
}>;
|
|
22
|
+
export declare function registerProxyAppWindow(opts: RegisterProxyAppWindowOptions): ProxyAppWindowHandle;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { createMessagePortBackedStream } from "./portStream.js";
|
|
2
|
+
import { PROXY_WINDOW_FETCH_FORWARD_MSG_TYPE, PROXY_WINDOW_FETCH_MSG_TYPE, PROXY_WINDOW_WS_ERROR_MSG_TYPE, PROXY_WINDOW_WS_OPEN_ACK_MSG_TYPE, PROXY_WINDOW_WS_OPEN_MSG_TYPE, } from "./windowBridgeProtocol.js";
|
|
3
|
+
function resolveTargetWindow(raw) {
|
|
4
|
+
const target = raw ?? globalThis.window;
|
|
5
|
+
if (target == null)
|
|
6
|
+
throw new Error("targetWindow is not available");
|
|
7
|
+
return target;
|
|
8
|
+
}
|
|
9
|
+
function resolveControllerWindow(targetWindow, raw) {
|
|
10
|
+
if (raw != null)
|
|
11
|
+
return raw;
|
|
12
|
+
try {
|
|
13
|
+
if (targetWindow.top && targetWindow.top !== targetWindow)
|
|
14
|
+
return targetWindow.top;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// ignore
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
if (targetWindow.parent && targetWindow.parent !== targetWindow)
|
|
21
|
+
return targetWindow.parent;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// ignore
|
|
25
|
+
}
|
|
26
|
+
throw new Error("controllerWindow is not available");
|
|
27
|
+
}
|
|
28
|
+
function postFetchError(port, message) {
|
|
29
|
+
try {
|
|
30
|
+
port.postMessage({ type: "flowersec-proxy:response_error", status: 502, message });
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Best-effort.
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
port.close();
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Best-effort.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function registerProxyAppWindow(opts) {
|
|
43
|
+
const controllerOrigin = String(opts.controllerOrigin ?? "").trim();
|
|
44
|
+
if (controllerOrigin === "") {
|
|
45
|
+
throw new Error("controllerOrigin is required");
|
|
46
|
+
}
|
|
47
|
+
const targetWindow = resolveTargetWindow(opts.targetWindow);
|
|
48
|
+
const controllerWindow = resolveControllerWindow(targetWindow, opts.controllerWindow);
|
|
49
|
+
const sw = targetWindow.navigator?.serviceWorker;
|
|
50
|
+
const onServiceWorkerMessage = (ev) => {
|
|
51
|
+
const data = ev.data;
|
|
52
|
+
if (data == null || typeof data !== "object")
|
|
53
|
+
return;
|
|
54
|
+
if (data.type !== PROXY_WINDOW_FETCH_FORWARD_MSG_TYPE)
|
|
55
|
+
return;
|
|
56
|
+
const port = ev.ports?.[0];
|
|
57
|
+
if (!port)
|
|
58
|
+
return;
|
|
59
|
+
try {
|
|
60
|
+
controllerWindow.postMessage({ type: PROXY_WINDOW_FETCH_MSG_TYPE, req: data.req }, controllerOrigin, [port]);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
64
|
+
postFetchError(port, message);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
sw?.addEventListener("message", onServiceWorkerMessage);
|
|
68
|
+
const runtime = {
|
|
69
|
+
limits: opts.maxWsFrameBytes === undefined ? {} : { maxWsFrameBytes: opts.maxWsFrameBytes },
|
|
70
|
+
openWebSocketStream: async (path, wsOpts = {}) => {
|
|
71
|
+
const channel = new MessageChannel();
|
|
72
|
+
const port = channel.port1;
|
|
73
|
+
port.start?.();
|
|
74
|
+
return await new Promise((resolve, reject) => {
|
|
75
|
+
let settled = false;
|
|
76
|
+
const finishReject = (error) => {
|
|
77
|
+
if (settled)
|
|
78
|
+
return;
|
|
79
|
+
settled = true;
|
|
80
|
+
try {
|
|
81
|
+
port.close();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Best-effort.
|
|
85
|
+
}
|
|
86
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
87
|
+
};
|
|
88
|
+
const finishResolve = (protocol) => {
|
|
89
|
+
if (settled)
|
|
90
|
+
return;
|
|
91
|
+
settled = true;
|
|
92
|
+
resolve({ stream: createMessagePortBackedStream(port), protocol });
|
|
93
|
+
};
|
|
94
|
+
port.onmessage = (ev) => {
|
|
95
|
+
const data = ev.data;
|
|
96
|
+
if (data == null || typeof data !== "object")
|
|
97
|
+
return;
|
|
98
|
+
const type = typeof data.type === "string" ? data.type : "";
|
|
99
|
+
if (type === PROXY_WINDOW_WS_OPEN_ACK_MSG_TYPE) {
|
|
100
|
+
finishResolve(String(data.protocol ?? ""));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (type === PROXY_WINDOW_WS_ERROR_MSG_TYPE) {
|
|
104
|
+
finishReject(new Error(String(data.message ?? "upstream ws open failed")));
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
if (wsOpts.signal != null) {
|
|
108
|
+
if (wsOpts.signal.aborted) {
|
|
109
|
+
finishReject(wsOpts.signal.reason ?? new Error("aborted"));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
wsOpts.signal.addEventListener("abort", () => finishReject(wsOpts.signal?.reason ?? new Error("aborted")), { once: true });
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
controllerWindow.postMessage({
|
|
116
|
+
type: PROXY_WINDOW_WS_OPEN_MSG_TYPE,
|
|
117
|
+
path,
|
|
118
|
+
...(wsOpts.protocols === undefined ? {} : { protocols: wsOpts.protocols }),
|
|
119
|
+
}, controllerOrigin, [channel.port2]);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
finishReject(error);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
runtime,
|
|
129
|
+
dispose: () => {
|
|
130
|
+
sw?.removeEventListener("message", onServiceWorkerMessage);
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ProxyRuntime } from "./runtime.js";
|
|
2
|
+
export type RegisterProxyControllerWindowOptions = Readonly<{
|
|
3
|
+
runtime: ProxyRuntime;
|
|
4
|
+
allowedOrigins: readonly string[];
|
|
5
|
+
targetWindow?: Window;
|
|
6
|
+
expectedSource?: Window | null;
|
|
7
|
+
}>;
|
|
8
|
+
export type ProxyControllerWindowHandle = Readonly<{
|
|
9
|
+
dispose: () => void;
|
|
10
|
+
}>;
|
|
11
|
+
export declare function registerProxyControllerWindow(opts: RegisterProxyControllerWindowOptions): ProxyControllerWindowHandle;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { PROXY_WINDOW_FETCH_MSG_TYPE, PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE, PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE, PROXY_WINDOW_STREAM_END_MSG_TYPE, PROXY_WINDOW_STREAM_RESET_MSG_TYPE, PROXY_WINDOW_WS_ERROR_MSG_TYPE, PROXY_WINDOW_WS_OPEN_ACK_MSG_TYPE, PROXY_WINDOW_WS_OPEN_MSG_TYPE, } from "./windowBridgeProtocol.js";
|
|
2
|
+
function normalizeOrigins(origins) {
|
|
3
|
+
const out = [];
|
|
4
|
+
const seen = new Set();
|
|
5
|
+
for (const origin of origins) {
|
|
6
|
+
const normalized = String(origin ?? "").trim();
|
|
7
|
+
if (normalized === "" || seen.has(normalized))
|
|
8
|
+
continue;
|
|
9
|
+
seen.add(normalized);
|
|
10
|
+
out.push(normalized);
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
function cloneChunk(chunk) {
|
|
15
|
+
const out = new Uint8Array(chunk.byteLength);
|
|
16
|
+
out.set(chunk);
|
|
17
|
+
return out.buffer;
|
|
18
|
+
}
|
|
19
|
+
function bridgeWebSocket(runtime, msg, port) {
|
|
20
|
+
const ac = new AbortController();
|
|
21
|
+
let streamClosed = false;
|
|
22
|
+
const closePort = () => {
|
|
23
|
+
try {
|
|
24
|
+
port.close();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Best-effort.
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
void (async () => {
|
|
31
|
+
try {
|
|
32
|
+
const wsOpts = {
|
|
33
|
+
signal: ac.signal,
|
|
34
|
+
...(msg.protocols === undefined ? {} : { protocols: msg.protocols }),
|
|
35
|
+
};
|
|
36
|
+
const { stream, protocol } = await runtime.openWebSocketStream(msg.path, wsOpts);
|
|
37
|
+
port.onmessage = (ev) => {
|
|
38
|
+
const data = ev.data;
|
|
39
|
+
if (data == null || typeof data !== "object")
|
|
40
|
+
return;
|
|
41
|
+
const type = typeof data.type === "string" ? data.type : "";
|
|
42
|
+
switch (type) {
|
|
43
|
+
case PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE: {
|
|
44
|
+
const raw = data.data;
|
|
45
|
+
if (!(raw instanceof ArrayBuffer))
|
|
46
|
+
return;
|
|
47
|
+
void stream.write(new Uint8Array(raw));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
case PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE:
|
|
51
|
+
void stream.close();
|
|
52
|
+
return;
|
|
53
|
+
case PROXY_WINDOW_STREAM_RESET_MSG_TYPE: {
|
|
54
|
+
const message = String(data.message ?? "stream reset");
|
|
55
|
+
stream.reset(new Error(message));
|
|
56
|
+
streamClosed = true;
|
|
57
|
+
ac.abort(message);
|
|
58
|
+
closePort();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
default:
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
port.start?.();
|
|
66
|
+
port.postMessage({ type: PROXY_WINDOW_WS_OPEN_ACK_MSG_TYPE, protocol });
|
|
67
|
+
while (!streamClosed) {
|
|
68
|
+
const chunk = await stream.read();
|
|
69
|
+
if (chunk == null) {
|
|
70
|
+
streamClosed = true;
|
|
71
|
+
port.postMessage({ type: PROXY_WINDOW_STREAM_END_MSG_TYPE });
|
|
72
|
+
closePort();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const ab = cloneChunk(chunk);
|
|
76
|
+
port.postMessage({ type: PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE, data: ab }, [ab]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
+
port.postMessage({ type: PROXY_WINDOW_WS_ERROR_MSG_TYPE, message });
|
|
82
|
+
closePort();
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
}
|
|
86
|
+
export function registerProxyControllerWindow(opts) {
|
|
87
|
+
const allowedOrigins = normalizeOrigins(opts.allowedOrigins);
|
|
88
|
+
if (allowedOrigins.length === 0) {
|
|
89
|
+
throw new Error("allowedOrigins is required");
|
|
90
|
+
}
|
|
91
|
+
const targetWindow = opts.targetWindow ?? globalThis.window;
|
|
92
|
+
if (targetWindow == null) {
|
|
93
|
+
throw new Error("targetWindow is not available");
|
|
94
|
+
}
|
|
95
|
+
const onMessage = (ev) => {
|
|
96
|
+
if (!allowedOrigins.includes(String(ev.origin ?? "").trim()))
|
|
97
|
+
return;
|
|
98
|
+
if (opts.expectedSource != null && ev.source !== opts.expectedSource)
|
|
99
|
+
return;
|
|
100
|
+
const data = ev.data;
|
|
101
|
+
if (data == null || typeof data !== "object")
|
|
102
|
+
return;
|
|
103
|
+
const type = typeof data.type === "string" ? data.type : "";
|
|
104
|
+
const port = ev.ports?.[0];
|
|
105
|
+
if (!port)
|
|
106
|
+
return;
|
|
107
|
+
switch (type) {
|
|
108
|
+
case PROXY_WINDOW_FETCH_MSG_TYPE:
|
|
109
|
+
opts.runtime.dispatchFetch(data.req, port);
|
|
110
|
+
return;
|
|
111
|
+
case PROXY_WINDOW_WS_OPEN_MSG_TYPE:
|
|
112
|
+
bridgeWebSocket(opts.runtime, data, port);
|
|
113
|
+
return;
|
|
114
|
+
default:
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
targetWindow.addEventListener("message", onMessage);
|
|
119
|
+
return {
|
|
120
|
+
dispose: () => {
|
|
121
|
+
targetWindow.removeEventListener("message", onMessage);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
package/dist/proxy/index.d.ts
CHANGED
|
@@ -8,5 +8,7 @@ export { registerServiceWorkerAndEnsureControl } from "./registerServiceWorker.j
|
|
|
8
8
|
export * from "./integration.js";
|
|
9
9
|
export * from "./controllerGuard.js";
|
|
10
10
|
export * from "./bootstrap.js";
|
|
11
|
+
export * from "./controllerWindow.js";
|
|
12
|
+
export * from "./appWindow.js";
|
|
11
13
|
export * from "./wsPatch.js";
|
|
12
14
|
export * from "./disableUpstreamServiceWorkerRegister.js";
|
package/dist/proxy/index.js
CHANGED
|
@@ -8,5 +8,7 @@ export { registerServiceWorkerAndEnsureControl } from "./registerServiceWorker.j
|
|
|
8
8
|
export * from "./integration.js";
|
|
9
9
|
export * from "./controllerGuard.js";
|
|
10
10
|
export * from "./bootstrap.js";
|
|
11
|
+
export * from "./controllerWindow.js";
|
|
12
|
+
export * from "./appWindow.js";
|
|
11
13
|
export * from "./wsPatch.js";
|
|
12
14
|
export * from "./disableUpstreamServiceWorkerRegister.js";
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE, PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE, PROXY_WINDOW_STREAM_END_MSG_TYPE, PROXY_WINDOW_STREAM_RESET_MSG_TYPE, } from "./windowBridgeProtocol.js";
|
|
2
|
+
function cloneChunk(chunk) {
|
|
3
|
+
const out = new Uint8Array(chunk.byteLength);
|
|
4
|
+
out.set(chunk);
|
|
5
|
+
return out.buffer;
|
|
6
|
+
}
|
|
7
|
+
export function createMessagePortBackedStream(port) {
|
|
8
|
+
let closed = false;
|
|
9
|
+
let error = null;
|
|
10
|
+
const queue = [];
|
|
11
|
+
const waiters = [];
|
|
12
|
+
const resolveWaiter = (value) => {
|
|
13
|
+
const waiter = waiters.shift();
|
|
14
|
+
if (waiter) {
|
|
15
|
+
waiter(value);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
};
|
|
20
|
+
const pushValue = (value) => {
|
|
21
|
+
if (resolveWaiter(value))
|
|
22
|
+
return;
|
|
23
|
+
queue.push(value);
|
|
24
|
+
};
|
|
25
|
+
const fail = (err) => {
|
|
26
|
+
if (error != null)
|
|
27
|
+
return;
|
|
28
|
+
error = err;
|
|
29
|
+
while (resolveWaiter(err)) {
|
|
30
|
+
// Drain waiters.
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
port.onmessage = (ev) => {
|
|
34
|
+
const data = ev.data;
|
|
35
|
+
if (data == null || typeof data !== "object")
|
|
36
|
+
return;
|
|
37
|
+
const type = typeof data.type === "string" ? data.type : "";
|
|
38
|
+
switch (type) {
|
|
39
|
+
case PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE: {
|
|
40
|
+
const raw = data.data;
|
|
41
|
+
if (!(raw instanceof ArrayBuffer))
|
|
42
|
+
return;
|
|
43
|
+
pushValue(new Uint8Array(raw));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
case PROXY_WINDOW_STREAM_END_MSG_TYPE:
|
|
47
|
+
case PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE:
|
|
48
|
+
closed = true;
|
|
49
|
+
pushValue(null);
|
|
50
|
+
return;
|
|
51
|
+
case PROXY_WINDOW_STREAM_RESET_MSG_TYPE: {
|
|
52
|
+
closed = true;
|
|
53
|
+
const message = String(data.message ?? "stream reset");
|
|
54
|
+
fail(new Error(message));
|
|
55
|
+
try {
|
|
56
|
+
port.close();
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Best-effort.
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
default:
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
port.start?.();
|
|
68
|
+
return {
|
|
69
|
+
async read() {
|
|
70
|
+
if (error != null)
|
|
71
|
+
throw error;
|
|
72
|
+
const next = queue.shift();
|
|
73
|
+
if (next !== undefined)
|
|
74
|
+
return next;
|
|
75
|
+
if (closed)
|
|
76
|
+
return null;
|
|
77
|
+
const value = await new Promise((resolve) => {
|
|
78
|
+
waiters.push(resolve);
|
|
79
|
+
});
|
|
80
|
+
if (value instanceof Error)
|
|
81
|
+
throw value;
|
|
82
|
+
return value;
|
|
83
|
+
},
|
|
84
|
+
async write(chunk) {
|
|
85
|
+
if (error != null)
|
|
86
|
+
throw error;
|
|
87
|
+
if (closed)
|
|
88
|
+
throw new Error("stream is closed");
|
|
89
|
+
const ab = cloneChunk(chunk);
|
|
90
|
+
port.postMessage({ type: PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE, data: ab }, [ab]);
|
|
91
|
+
},
|
|
92
|
+
async close() {
|
|
93
|
+
if (closed)
|
|
94
|
+
return;
|
|
95
|
+
closed = true;
|
|
96
|
+
try {
|
|
97
|
+
port.postMessage({ type: PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE });
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
pushValue(null);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
reset(err) {
|
|
104
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
105
|
+
closed = true;
|
|
106
|
+
fail(err instanceof Error ? err : new Error(message));
|
|
107
|
+
try {
|
|
108
|
+
port.postMessage({ type: PROXY_WINDOW_STREAM_RESET_MSG_TYPE, message });
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
try {
|
|
112
|
+
port.close();
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Best-effort.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
package/dist/proxy/runtime.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { Client } from "../client.js";
|
|
2
2
|
import type { YamuxStream } from "../yamux/stream.js";
|
|
3
3
|
import { CookieJar } from "./cookieJar.js";
|
|
4
|
+
import type { Header } from "./types.js";
|
|
5
|
+
type ProxyFetchReq = Readonly<{
|
|
6
|
+
id: string;
|
|
7
|
+
method: string;
|
|
8
|
+
path: string;
|
|
9
|
+
headers: readonly Header[];
|
|
10
|
+
body?: ArrayBuffer;
|
|
11
|
+
}>;
|
|
4
12
|
export type ProxyRuntimeLimits = Readonly<{
|
|
5
13
|
maxJsonFrameBytes: number;
|
|
6
14
|
maxChunkBytes: number;
|
|
@@ -8,9 +16,9 @@ export type ProxyRuntimeLimits = Readonly<{
|
|
|
8
16
|
maxWsFrameBytes: number;
|
|
9
17
|
}>;
|
|
10
18
|
export type ProxyRuntime = Readonly<{
|
|
11
|
-
cookieJar: CookieJar;
|
|
12
19
|
limits: ProxyRuntimeLimits;
|
|
13
20
|
dispose: () => void;
|
|
21
|
+
dispatchFetch: (req: ProxyFetchReq, port: MessagePort) => void;
|
|
14
22
|
openWebSocketStream: (path: string, opts?: Readonly<{
|
|
15
23
|
protocols?: readonly string[];
|
|
16
24
|
signal?: AbortSignal;
|
|
@@ -29,5 +37,7 @@ export type ProxyRuntimeOptions = Readonly<{
|
|
|
29
37
|
extraRequestHeaders?: readonly string[];
|
|
30
38
|
extraResponseHeaders?: readonly string[];
|
|
31
39
|
extraWsHeaders?: readonly string[];
|
|
40
|
+
cookieJar?: CookieJar;
|
|
32
41
|
}>;
|
|
33
42
|
export declare function createProxyRuntime(opts: ProxyRuntimeOptions): ProxyRuntime;
|
|
43
|
+
export {};
|
package/dist/proxy/runtime.js
CHANGED
|
@@ -87,7 +87,7 @@ async function readChunkFrames(reader, maxChunkBytes, maxBodyBytes) {
|
|
|
87
87
|
}
|
|
88
88
|
export function createProxyRuntime(opts) {
|
|
89
89
|
const client = opts.client;
|
|
90
|
-
const cookieJar = new CookieJar();
|
|
90
|
+
const cookieJar = opts.cookieJar ?? new CookieJar();
|
|
91
91
|
const maxJsonFrameBytes = normalizeMaxBytes("maxJsonFrameBytes", opts.maxJsonFrameBytes, DEFAULT_MAX_JSON_FRAME_BYTES);
|
|
92
92
|
const maxChunkBytes = normalizeMaxBytes("maxChunkBytes", opts.maxChunkBytes, DEFAULT_MAX_CHUNK_BYTES);
|
|
93
93
|
const maxBodyBytes = normalizeMaxBytes("maxBodyBytes", opts.maxBodyBytes, DEFAULT_MAX_BODY_BYTES);
|
|
@@ -115,13 +115,13 @@ export function createProxyRuntime(opts) {
|
|
|
115
115
|
const port = ev.ports?.[0];
|
|
116
116
|
if (!port)
|
|
117
117
|
return;
|
|
118
|
-
|
|
118
|
+
dispatchFetch(msg.req, port);
|
|
119
119
|
};
|
|
120
120
|
const sw = globalThis.navigator?.serviceWorker;
|
|
121
121
|
sw?.addEventListener("message", onMessage);
|
|
122
122
|
sw?.addEventListener("controllerchange", registerRuntime);
|
|
123
123
|
registerRuntime();
|
|
124
|
-
|
|
124
|
+
const dispatchFetch = (req, port) => {
|
|
125
125
|
const ac = new AbortController();
|
|
126
126
|
let stream = null;
|
|
127
127
|
port.onmessage = (ev) => {
|
|
@@ -130,74 +130,76 @@ export function createProxyRuntime(opts) {
|
|
|
130
130
|
ac.abort("aborted");
|
|
131
131
|
}
|
|
132
132
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
133
|
+
void (async () => {
|
|
134
|
+
try {
|
|
135
|
+
const path = pathOnly(req.path);
|
|
136
|
+
const requestID = req.id.trim() !== "" ? req.id : randomB64u(18);
|
|
137
|
+
stream = await client.openStream(PROXY_KIND_HTTP1, { signal: ac.signal });
|
|
138
|
+
const reader = createByteReader(stream, { signal: ac.signal });
|
|
139
|
+
const filteredReqHeaders = filterRequestHeaders(req.headers, { extraAllowed: extraRequestHeaders });
|
|
140
|
+
const cookieHeader = cookieJar.getCookieHeader(cookiePathFromRequestPath(path));
|
|
141
|
+
const reqHeaders = cookieHeader === "" ? filteredReqHeaders : [...filteredReqHeaders, { name: "cookie", value: cookieHeader }];
|
|
142
|
+
await writeJsonFrame(stream, {
|
|
143
|
+
v: PROXY_PROTOCOL_VERSION,
|
|
144
|
+
request_id: requestID,
|
|
145
|
+
method: req.method,
|
|
146
|
+
path,
|
|
147
|
+
headers: reqHeaders,
|
|
148
|
+
timeout_ms: timeoutMs
|
|
149
|
+
});
|
|
150
|
+
const body = req.body != null ? new Uint8Array(req.body) : new Uint8Array();
|
|
151
|
+
await writeChunkFrames(stream, body, Math.min(64 * 1024, maxChunkBytes), maxBodyBytes);
|
|
152
|
+
const respMeta = (await readJsonFrame(reader, maxJsonFrameBytes));
|
|
153
|
+
if (respMeta.v !== PROXY_PROTOCOL_VERSION || respMeta.request_id !== requestID) {
|
|
154
|
+
throw new Error("invalid upstream response meta");
|
|
155
|
+
}
|
|
156
|
+
if (!respMeta.ok) {
|
|
157
|
+
const msg = respMeta.error?.message ?? "upstream error";
|
|
158
|
+
port.postMessage({ type: "flowersec-proxy:response_error", status: 502, message: msg });
|
|
159
|
+
try {
|
|
160
|
+
stream.reset(new Error(msg));
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Best-effort.
|
|
164
|
+
}
|
|
165
|
+
stream = null;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const status = Math.max(0, Math.floor(respMeta.status ?? 502));
|
|
169
|
+
const rawHeaders = Array.isArray(respMeta.headers) ? respMeta.headers : [];
|
|
170
|
+
const { passthrough, setCookie } = filterResponseHeaders(rawHeaders, { extraAllowed: extraResponseHeaders });
|
|
171
|
+
cookieJar.updateFromSetCookieHeaders(setCookie);
|
|
172
|
+
port.postMessage({ type: "flowersec-proxy:response_meta", status, headers: passthrough });
|
|
173
|
+
const chunks = await readChunkFrames(reader, maxChunkBytes, maxBodyBytes);
|
|
174
|
+
for await (const chunk of chunks) {
|
|
175
|
+
// Always transfer an ArrayBuffer (SharedArrayBuffer is not transferable).
|
|
176
|
+
const ab = chunk.slice().buffer;
|
|
177
|
+
port.postMessage({ type: "flowersec-proxy:response_chunk", data: ab }, [ab]);
|
|
178
|
+
}
|
|
179
|
+
port.postMessage({ type: "flowersec-proxy:response_end" });
|
|
180
|
+
await stream.close();
|
|
181
|
+
stream = null;
|
|
154
182
|
}
|
|
155
|
-
|
|
156
|
-
const msg =
|
|
183
|
+
catch (e) {
|
|
184
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
157
185
|
port.postMessage({ type: "flowersec-proxy:response_error", status: 502, message: msg });
|
|
158
186
|
try {
|
|
159
|
-
stream
|
|
187
|
+
stream?.reset(new Error(msg));
|
|
160
188
|
}
|
|
161
189
|
catch {
|
|
162
190
|
// Best-effort.
|
|
163
191
|
}
|
|
164
|
-
stream = null;
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
const status = Math.max(0, Math.floor(respMeta.status ?? 502));
|
|
168
|
-
const rawHeaders = Array.isArray(respMeta.headers) ? respMeta.headers : [];
|
|
169
|
-
const { passthrough, setCookie } = filterResponseHeaders(rawHeaders, { extraAllowed: extraResponseHeaders });
|
|
170
|
-
cookieJar.updateFromSetCookieHeaders(setCookie);
|
|
171
|
-
port.postMessage({ type: "flowersec-proxy:response_meta", status, headers: passthrough });
|
|
172
|
-
const chunks = await readChunkFrames(reader, maxChunkBytes, maxBodyBytes);
|
|
173
|
-
for await (const chunk of chunks) {
|
|
174
|
-
// Always transfer an ArrayBuffer (SharedArrayBuffer is not transferable).
|
|
175
|
-
const ab = chunk.slice().buffer;
|
|
176
|
-
port.postMessage({ type: "flowersec-proxy:response_chunk", data: ab }, [ab]);
|
|
177
|
-
}
|
|
178
|
-
port.postMessage({ type: "flowersec-proxy:response_end" });
|
|
179
|
-
await stream.close();
|
|
180
|
-
stream = null;
|
|
181
|
-
}
|
|
182
|
-
catch (e) {
|
|
183
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
184
|
-
port.postMessage({ type: "flowersec-proxy:response_error", status: 502, message: msg });
|
|
185
|
-
try {
|
|
186
|
-
stream?.reset(new Error(msg));
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
// Best-effort.
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
finally {
|
|
193
|
-
try {
|
|
194
|
-
port.close();
|
|
195
192
|
}
|
|
196
|
-
|
|
197
|
-
|
|
193
|
+
finally {
|
|
194
|
+
try {
|
|
195
|
+
port.close();
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Best-effort.
|
|
199
|
+
}
|
|
198
200
|
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
+
})();
|
|
202
|
+
};
|
|
201
203
|
async function openWebSocketStream(pathRaw, wsOpts = {}) {
|
|
202
204
|
const path = pathOnly(pathRaw);
|
|
203
205
|
const openOpts = wsOpts.signal ? { signal: wsOpts.signal } : undefined;
|
|
@@ -226,8 +228,8 @@ export function createProxyRuntime(opts) {
|
|
|
226
228
|
return { stream, protocol: resp.protocol ?? "" };
|
|
227
229
|
}
|
|
228
230
|
return {
|
|
229
|
-
cookieJar,
|
|
230
231
|
limits: { maxJsonFrameBytes, maxChunkBytes, maxBodyBytes, maxWsFrameBytes },
|
|
232
|
+
dispatchFetch,
|
|
231
233
|
openWebSocketStream,
|
|
232
234
|
dispose: () => {
|
|
233
235
|
sw?.removeEventListener("message", onMessage);
|
|
@@ -31,6 +31,8 @@ export type ProxyServiceWorkerScriptOptions = Readonly<{
|
|
|
31
31
|
stripProxyPathPrefix?: boolean;
|
|
32
32
|
injectHTML?: ProxyServiceWorkerInjectHTMLOptions;
|
|
33
33
|
forwardFetchMessageTypes?: readonly string[];
|
|
34
|
+
windowTarget?: "registered_runtime" | "request_client";
|
|
35
|
+
windowClientMessageType?: string;
|
|
34
36
|
conflictHints?: Readonly<{
|
|
35
37
|
keepScriptPathSuffixes?: readonly string[];
|
|
36
38
|
}>;
|
|
@@ -74,6 +74,18 @@ export function createProxyServiceWorkerScript(opts = {}) {
|
|
|
74
74
|
const passthroughPrefixes = normalizePathList("passthrough.prefixes", opts.passthrough?.prefixes);
|
|
75
75
|
const forwardFetchMessageTypes = normalizeMessageTypeList("forwardFetchMessageTypes", opts.forwardFetchMessageTypes);
|
|
76
76
|
const keepScriptPathSuffixes = normalizePathList("conflictHints.keepScriptPathSuffixes", opts.conflictHints?.keepScriptPathSuffixes);
|
|
77
|
+
const windowTarget = opts.windowTarget ?? "registered_runtime";
|
|
78
|
+
if (windowTarget !== "registered_runtime" && windowTarget !== "request_client") {
|
|
79
|
+
throw new Error('windowTarget must be either "registered_runtime" or "request_client"');
|
|
80
|
+
}
|
|
81
|
+
const windowClientMessageType = (() => {
|
|
82
|
+
const normalized = typeof opts.windowClientMessageType === "string" ? opts.windowClientMessageType.trim() : "flowersec-proxy:fetch";
|
|
83
|
+
if (normalized === "")
|
|
84
|
+
throw new Error("windowClientMessageType must be non-empty");
|
|
85
|
+
if (/[\r\n]/.test(normalized))
|
|
86
|
+
throw new Error("windowClientMessageType must not contain newline");
|
|
87
|
+
return normalized;
|
|
88
|
+
})();
|
|
77
89
|
const injectHTML = opts.injectHTML ?? null;
|
|
78
90
|
// Injection mode defaults to inline_module when injectHTML is provided.
|
|
79
91
|
const injectMode = injectHTML?.mode ?? "inline_module";
|
|
@@ -133,6 +145,8 @@ const INJECT_SET_NO_STORE = ${JSON.stringify(setNoStore)};
|
|
|
133
145
|
const MAX_REQUEST_BODY_BYTES = ${JSON.stringify(maxRequestBodyBytes)};
|
|
134
146
|
const MAX_INJECT_HTML_BYTES = ${JSON.stringify(maxInjectHTMLBytes)};
|
|
135
147
|
const FORWARD_FETCH_MESSAGE_TYPES = new Set(${JSON.stringify(forwardFetchMessageTypes)});
|
|
148
|
+
const WINDOW_TARGET = ${JSON.stringify(windowTarget)};
|
|
149
|
+
const WINDOW_CLIENT_MESSAGE_TYPE = ${JSON.stringify(windowClientMessageType)};
|
|
136
150
|
const CONFLICT_HINT_KEEP_SCRIPT_SUFFIXES = ${JSON.stringify(keepScriptPathSuffixes)};
|
|
137
151
|
|
|
138
152
|
const INJECT_STRIP_HEADER_NAMES = new Set(["content-length", "etag", "last-modified", "content-md5"]);
|
|
@@ -162,15 +176,19 @@ self.addEventListener("message", (event) => {
|
|
|
162
176
|
if (!port) return;
|
|
163
177
|
|
|
164
178
|
event.waitUntil((async () => {
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
179
|
+
const preferredClientId =
|
|
180
|
+
WINDOW_TARGET === "request_client" && event.source && typeof event.source.id === "string"
|
|
181
|
+
? event.source.id
|
|
182
|
+
: "";
|
|
183
|
+
const target = await getWindowClient(preferredClientId);
|
|
184
|
+
if (!target) {
|
|
185
|
+
try { port.postMessage({ type: "flowersec-proxy:response_error", status: 503, message: "flowersec-proxy target window not available" }); } catch {}
|
|
168
186
|
try { port.close(); } catch {}
|
|
169
187
|
return;
|
|
170
188
|
}
|
|
171
189
|
|
|
172
190
|
try {
|
|
173
|
-
|
|
191
|
+
target.postMessage({ type: WINDOW_CLIENT_MESSAGE_TYPE, req: data.req }, [port]);
|
|
174
192
|
} catch (e) {
|
|
175
193
|
const msg = e instanceof Error ? e.message : String(e);
|
|
176
194
|
try { port.postMessage({ type: "flowersec-proxy:response_error", status: 502, message: msg }); } catch {}
|
|
@@ -179,7 +197,16 @@ self.addEventListener("message", (event) => {
|
|
|
179
197
|
})());
|
|
180
198
|
});
|
|
181
199
|
|
|
182
|
-
async function
|
|
200
|
+
async function getWindowClient(preferredClientId) {
|
|
201
|
+
if (WINDOW_TARGET === "request_client") {
|
|
202
|
+
if (preferredClientId) {
|
|
203
|
+
const c = await self.clients.get(preferredClientId);
|
|
204
|
+
if (c) return c;
|
|
205
|
+
}
|
|
206
|
+
const cs = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
|
|
207
|
+
return cs.length > 0 ? cs[0] : null;
|
|
208
|
+
}
|
|
209
|
+
|
|
183
210
|
if (runtimeClientId) {
|
|
184
211
|
const c = await self.clients.get(runtimeClientId);
|
|
185
212
|
if (c) return c;
|
|
@@ -330,8 +357,12 @@ async function handleFetch(event) {
|
|
|
330
357
|
let lastErrorMessage = "proxy error";
|
|
331
358
|
|
|
332
359
|
try {
|
|
333
|
-
const
|
|
334
|
-
|
|
360
|
+
const preferredClientId =
|
|
361
|
+
WINDOW_TARGET === "request_client"
|
|
362
|
+
? String(event.clientId || event.resultingClientId || "")
|
|
363
|
+
: "";
|
|
364
|
+
const target = await getWindowClient(preferredClientId);
|
|
365
|
+
if (!target) return new Response("flowersec-proxy target window not available", { status: 503 });
|
|
335
366
|
|
|
336
367
|
const req = event.request;
|
|
337
368
|
const url = new URL(req.url);
|
|
@@ -455,8 +486,8 @@ async function handleFetch(event) {
|
|
|
455
486
|
path = "/" + rest + url.search;
|
|
456
487
|
}
|
|
457
488
|
|
|
458
|
-
|
|
459
|
-
type:
|
|
489
|
+
target.postMessage({
|
|
490
|
+
type: WINDOW_CLIENT_MESSAGE_TYPE,
|
|
460
491
|
req: { id, method: req.method, path, headers: headersToPairs(req.headers), body }
|
|
461
492
|
}, [port2]);
|
|
462
493
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Header } from "./types.js";
|
|
2
|
+
export declare const PROXY_WINDOW_FETCH_FORWARD_MSG_TYPE = "flowersec-proxy:window_fetch";
|
|
3
|
+
export declare const PROXY_WINDOW_FETCH_MSG_TYPE = "flowersec-proxy:fetch";
|
|
4
|
+
export declare const PROXY_WINDOW_WS_OPEN_MSG_TYPE = "flowersec-proxy:ws_open";
|
|
5
|
+
export declare const PROXY_WINDOW_WS_OPEN_ACK_MSG_TYPE = "flowersec-proxy:ws_open_ack";
|
|
6
|
+
export declare const PROXY_WINDOW_WS_ERROR_MSG_TYPE = "flowersec-proxy:ws_error";
|
|
7
|
+
export declare const PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE = "flowersec-proxy:stream_chunk";
|
|
8
|
+
export declare const PROXY_WINDOW_STREAM_END_MSG_TYPE = "flowersec-proxy:stream_end";
|
|
9
|
+
export declare const PROXY_WINDOW_STREAM_RESET_MSG_TYPE = "flowersec-proxy:stream_reset";
|
|
10
|
+
export declare const PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE = "flowersec-proxy:stream_close";
|
|
11
|
+
export type ProxyWindowFetchRequest = Readonly<{
|
|
12
|
+
id: string;
|
|
13
|
+
method: string;
|
|
14
|
+
path: string;
|
|
15
|
+
headers: readonly Header[];
|
|
16
|
+
body?: ArrayBuffer;
|
|
17
|
+
}>;
|
|
18
|
+
export type ProxyWindowFetchForwardMsg = Readonly<{
|
|
19
|
+
type: typeof PROXY_WINDOW_FETCH_FORWARD_MSG_TYPE;
|
|
20
|
+
req: ProxyWindowFetchRequest;
|
|
21
|
+
}>;
|
|
22
|
+
export type ProxyWindowFetchMsg = Readonly<{
|
|
23
|
+
type: typeof PROXY_WINDOW_FETCH_MSG_TYPE;
|
|
24
|
+
req: ProxyWindowFetchRequest;
|
|
25
|
+
}>;
|
|
26
|
+
export type ProxyWindowWsOpenMsg = Readonly<{
|
|
27
|
+
type: typeof PROXY_WINDOW_WS_OPEN_MSG_TYPE;
|
|
28
|
+
path: string;
|
|
29
|
+
protocols?: readonly string[];
|
|
30
|
+
}>;
|
|
31
|
+
export type ProxyWindowWsOpenAckMsg = Readonly<{
|
|
32
|
+
type: typeof PROXY_WINDOW_WS_OPEN_ACK_MSG_TYPE;
|
|
33
|
+
protocol: string;
|
|
34
|
+
}>;
|
|
35
|
+
export type ProxyWindowWsErrorMsg = Readonly<{
|
|
36
|
+
type: typeof PROXY_WINDOW_WS_ERROR_MSG_TYPE;
|
|
37
|
+
message: string;
|
|
38
|
+
}>;
|
|
39
|
+
export type ProxyWindowStreamChunkMsg = Readonly<{
|
|
40
|
+
type: typeof PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE;
|
|
41
|
+
data: ArrayBuffer;
|
|
42
|
+
}>;
|
|
43
|
+
export type ProxyWindowStreamEndMsg = Readonly<{
|
|
44
|
+
type: typeof PROXY_WINDOW_STREAM_END_MSG_TYPE;
|
|
45
|
+
}>;
|
|
46
|
+
export type ProxyWindowStreamResetMsg = Readonly<{
|
|
47
|
+
type: typeof PROXY_WINDOW_STREAM_RESET_MSG_TYPE;
|
|
48
|
+
message: string;
|
|
49
|
+
}>;
|
|
50
|
+
export type ProxyWindowStreamCloseMsg = Readonly<{
|
|
51
|
+
type: typeof PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE;
|
|
52
|
+
}>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const PROXY_WINDOW_FETCH_FORWARD_MSG_TYPE = "flowersec-proxy:window_fetch";
|
|
2
|
+
export const PROXY_WINDOW_FETCH_MSG_TYPE = "flowersec-proxy:fetch";
|
|
3
|
+
export const PROXY_WINDOW_WS_OPEN_MSG_TYPE = "flowersec-proxy:ws_open";
|
|
4
|
+
export const PROXY_WINDOW_WS_OPEN_ACK_MSG_TYPE = "flowersec-proxy:ws_open_ack";
|
|
5
|
+
export const PROXY_WINDOW_WS_ERROR_MSG_TYPE = "flowersec-proxy:ws_error";
|
|
6
|
+
export const PROXY_WINDOW_STREAM_CHUNK_MSG_TYPE = "flowersec-proxy:stream_chunk";
|
|
7
|
+
export const PROXY_WINDOW_STREAM_END_MSG_TYPE = "flowersec-proxy:stream_end";
|
|
8
|
+
export const PROXY_WINDOW_STREAM_RESET_MSG_TYPE = "flowersec-proxy:stream_reset";
|
|
9
|
+
export const PROXY_WINDOW_STREAM_CLOSE_MSG_TYPE = "flowersec-proxy:stream_close";
|
package/dist/proxy/wsPatch.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { YamuxStream } from "../yamux/stream.js";
|
|
2
|
+
import type { ProxyRuntimeLimits } from "./runtime.js";
|
|
2
3
|
export type WebSocketPatchOptions = Readonly<{
|
|
3
|
-
runtime:
|
|
4
|
+
runtime: Readonly<{
|
|
5
|
+
limits: Partial<ProxyRuntimeLimits>;
|
|
6
|
+
openWebSocketStream: (path: string, opts?: Readonly<{
|
|
7
|
+
protocols?: readonly string[];
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
}>) => Promise<Readonly<{
|
|
10
|
+
stream: YamuxStream;
|
|
11
|
+
protocol: string;
|
|
12
|
+
}>>;
|
|
13
|
+
}>;
|
|
4
14
|
shouldProxy?: (url: URL) => boolean;
|
|
5
15
|
maxWsFrameBytes?: number;
|
|
6
16
|
}>;
|