@floegence/flowersec-core 0.26.0 → 2.0.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.
Files changed (270) hide show
  1. package/README.md +91 -22
  2. package/THIRD_PARTY_NOTICES.md +141 -0
  3. package/dist/browser/connectSession.d.ts +13 -0
  4. package/dist/browser/connectSession.js +40 -0
  5. package/dist/browser/index.d.ts +3 -10
  6. package/dist/browser/index.js +2 -5
  7. package/dist/browser/runtimeCapability.d.ts +13 -0
  8. package/dist/browser/runtimeCapability.js +27 -0
  9. package/dist/browser/sessionRuntime.d.ts +2 -0
  10. package/dist/browser/sessionRuntime.js +21 -0
  11. package/dist/browser/webTransportClient.d.ts +8 -0
  12. package/dist/browser/webTransportClient.js +55 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +126 -0
  15. package/dist/cliSpendMarker.d.ts +1 -0
  16. package/dist/cliSpendMarker.js +22 -0
  17. package/dist/connectionController.d.ts +54 -0
  18. package/dist/connectionController.js +438 -0
  19. package/dist/connector/adapters/webSocketCandidate.d.ts +7 -0
  20. package/dist/connector/adapters/webSocketCandidate.js +116 -0
  21. package/dist/connector/adapters/webTransportCandidate.d.ts +5 -0
  22. package/dist/connector/adapters/webTransportCandidate.js +73 -0
  23. package/dist/connector/admissionCommit.d.ts +31 -0
  24. package/dist/connector/admissionCommit.js +239 -0
  25. package/dist/connector/sessionAcceptor.d.ts +16 -0
  26. package/dist/connector/sessionAcceptor.js +114 -0
  27. package/dist/connector/sessionConfig.d.ts +3 -0
  28. package/dist/connector/sessionConfig.js +30 -0
  29. package/dist/connector/sessionConnector.d.ts +45 -0
  30. package/dist/connector/sessionConnector.js +603 -0
  31. package/dist/defaults.d.ts +7 -7
  32. package/dist/defaults.js +7 -7
  33. package/dist/facade.d.ts +12 -30
  34. package/dist/facade.js +6 -30
  35. package/dist/node/connectSession.d.ts +21 -0
  36. package/dist/node/connectSession.js +62 -0
  37. package/dist/node/index.d.ts +3 -11
  38. package/dist/node/index.js +2 -8
  39. package/dist/node/runtimeCapability.d.ts +7 -0
  40. package/dist/node/runtimeCapability.js +10 -0
  41. package/dist/node/sessionRuntime.d.ts +2 -0
  42. package/dist/node/sessionRuntime.js +13 -0
  43. package/dist/node/webTransportClient.d.ts +9 -0
  44. package/dist/node/webTransportClient.js +56 -0
  45. package/dist/node/webTransportServer.d.ts +22 -0
  46. package/dist/node/webTransportServer.js +100 -0
  47. package/dist/node/wsFactory.d.ts +3 -1
  48. package/dist/node/wsFactory.js +4 -3
  49. package/dist/proxy/controllerGuard.d.ts +4 -4
  50. package/dist/proxy/controllerGuard.js +83 -177
  51. package/dist/proxy/disableUpstreamServiceWorkerRegister.d.ts +3 -1
  52. package/dist/proxy/disableUpstreamServiceWorkerRegister.js +19 -17
  53. package/dist/proxy/index.d.ts +16 -15
  54. package/dist/proxy/index.js +8 -14
  55. package/dist/proxy/integration.d.ts +23 -71
  56. package/dist/proxy/integration.js +37 -340
  57. package/dist/proxy/registerServiceWorker.d.ts +1 -10
  58. package/dist/proxy/registerServiceWorker.js +43 -105
  59. package/dist/proxy/runtime.d.ts +3 -63
  60. package/dist/proxy/runtime.js +349 -462
  61. package/dist/proxy/scope.d.ts +6 -0
  62. package/dist/proxy/scope.js +131 -0
  63. package/dist/proxy/serviceWorker.d.ts +5 -16
  64. package/dist/proxy/serviceWorker.js +230 -617
  65. package/dist/proxy/stream.d.ts +9 -0
  66. package/dist/proxy/stream.js +40 -0
  67. package/dist/proxy/types.d.ts +78 -30
  68. package/dist/proxy/windowBridge.d.ts +61 -0
  69. package/dist/proxy/windowBridge.js +344 -0
  70. package/dist/proxy/wsPatch.d.ts +4 -14
  71. package/dist/proxy/wsPatch.js +173 -268
  72. package/dist/public/artifact.d.ts +11 -0
  73. package/dist/public/artifact.js +44 -0
  74. package/dist/public/artifactLease.d.ts +10 -0
  75. package/dist/public/artifactLease.js +48 -0
  76. package/dist/public/connectError.d.ts +6 -0
  77. package/dist/public/connectError.js +9 -0
  78. package/dist/public/contract.d.ts +83 -0
  79. package/dist/public/contract.js +17 -0
  80. package/dist/public/streamMetadata.d.ts +10 -0
  81. package/dist/public/streamMetadata.js +40 -0
  82. package/dist/rpc/caller.d.ts +1 -1
  83. package/dist/rpc/client.d.ts +1 -4
  84. package/dist/rpc/client.js +0 -21
  85. package/dist/rpc/rpcProxy.d.ts +1 -1
  86. package/dist/rpc/server.d.ts +1 -1
  87. package/dist/rpc/validate.d.ts +1 -1
  88. package/dist/rpc/wire.d.ts +15 -0
  89. package/dist/runtime/errors.d.ts +6 -0
  90. package/dist/runtime/errors.js +10 -0
  91. package/dist/transport/webSocketAdapter.d.ts +24 -0
  92. package/dist/transport/webSocketAdapter.js +234 -0
  93. package/dist/transport/webTransportAdapter.d.ts +27 -0
  94. package/dist/transport/webTransportAdapter.js +519 -0
  95. package/dist/utils/errors.d.ts +2 -20
  96. package/dist/utils/errors.js +73 -20
  97. package/dist/v2/admissionError.d.ts +4 -0
  98. package/dist/v2/admissionError.js +8 -0
  99. package/dist/v2/artifact.d.ts +128 -0
  100. package/dist/v2/artifact.js +1126 -0
  101. package/dist/v2/artifactLease.d.ts +1 -0
  102. package/dist/v2/artifactLease.js +7 -0
  103. package/dist/v2/capability.d.ts +29 -0
  104. package/dist/v2/capability.js +190 -0
  105. package/dist/v2/carrier.d.ts +79 -0
  106. package/dist/v2/carrier.js +405 -0
  107. package/dist/v2/contract.d.ts +37 -0
  108. package/dist/v2/contract.js +1 -0
  109. package/dist/v2/handshake.d.ts +80 -0
  110. package/dist/v2/handshake.js +470 -0
  111. package/dist/v2/opaqueArtifact.d.ts +1 -0
  112. package/dist/v2/opaqueArtifact.js +2 -0
  113. package/dist/v2/protocol.d.ts +99 -0
  114. package/dist/v2/protocol.js +739 -0
  115. package/dist/v2/publicSession.d.ts +1 -0
  116. package/dist/v2/publicSession.js +179 -0
  117. package/dist/v2/retryDisposition.d.ts +16 -0
  118. package/dist/v2/retryDisposition.js +30 -0
  119. package/dist/v2/session.d.ts +277 -0
  120. package/dist/v2/session.js +1879 -0
  121. package/dist/v2/streamLifetimeLedger.d.ts +31 -0
  122. package/dist/v2/streamLifetimeLedger.js +131 -0
  123. package/dist/v2/streamMetadata.d.ts +1 -0
  124. package/dist/v2/streamMetadata.js +7 -0
  125. package/dist/v2/unreliableMessage.d.ts +43 -0
  126. package/dist/v2/unreliableMessage.js +252 -0
  127. package/dist/vendor/tr46.d.ts +10 -0
  128. package/dist/vendor/tr46.js +501 -0
  129. package/dist/ws-client/binaryTransport.d.ts +0 -3
  130. package/dist/ws-client/binaryTransport.js +15 -39
  131. package/dist/yamux/errors.d.ts +0 -4
  132. package/dist/yamux/errors.js +0 -9
  133. package/dist/yamux/session.d.ts +0 -5
  134. package/dist/yamux/session.js +4 -70
  135. package/dist/yamux/stream.d.ts +1 -0
  136. package/dist/yamux/stream.js +4 -0
  137. package/package.json +29 -83
  138. package/sbom/cyclonedx.json +2764 -0
  139. package/sbom/spdx.json +1947 -0
  140. package/YAMUX_ALIGNMENT.md +0 -127
  141. package/dist/_examples/flowersec/demo/v1.facade.gen.d.ts +0 -12
  142. package/dist/_examples/flowersec/demo/v1.facade.gen.js +0 -15
  143. package/dist/_examples/flowersec/demo/v1.gen.d.ts +0 -16
  144. package/dist/_examples/flowersec/demo/v1.gen.js +0 -86
  145. package/dist/_examples/flowersec/demo/v1.rpc.gen.d.ts +0 -11
  146. package/dist/_examples/flowersec/demo/v1.rpc.gen.js +0 -22
  147. package/dist/browser/connect.d.ts +0 -12
  148. package/dist/browser/connect.js +0 -31
  149. package/dist/browser/controlplane.d.ts +0 -15
  150. package/dist/browser/controlplane.js +0 -64
  151. package/dist/browser/reconnectConfig.d.ts +0 -15
  152. package/dist/browser/reconnectConfig.js +0 -20
  153. package/dist/client-connect/common.d.ts +0 -26
  154. package/dist/client-connect/common.js +0 -167
  155. package/dist/client-connect/connectCore.d.ts +0 -61
  156. package/dist/client-connect/connectCore.js +0 -572
  157. package/dist/client-connect/contract.d.ts +0 -8
  158. package/dist/client-connect/contract.js +0 -51
  159. package/dist/client-connect/termination.d.ts +0 -6
  160. package/dist/client-connect/termination.js +0 -7
  161. package/dist/client-connect/transportSecurity.d.ts +0 -27
  162. package/dist/client-connect/transportSecurity.js +0 -199
  163. package/dist/client-connect/tunnelAttachCloseReason.d.ts +0 -3
  164. package/dist/client-connect/tunnelAttachCloseReason.js +0 -23
  165. package/dist/client.d.ts +0 -23
  166. package/dist/connect/artifact.d.ts +0 -37
  167. package/dist/connect/artifact.js +0 -217
  168. package/dist/connect/internalNormalize.d.ts +0 -22
  169. package/dist/connect/internalNormalize.js +0 -173
  170. package/dist/controlplane/channelInit.d.ts +0 -27
  171. package/dist/controlplane/channelInit.js +0 -116
  172. package/dist/controlplane/http.d.ts +0 -16
  173. package/dist/controlplane/http.js +0 -59
  174. package/dist/controlplane/index.d.ts +0 -6
  175. package/dist/controlplane/index.js +0 -5
  176. package/dist/controlplane/issuer.d.ts +0 -18
  177. package/dist/controlplane/issuer.js +0 -113
  178. package/dist/controlplane/request.d.ts +0 -51
  179. package/dist/controlplane/request.js +0 -273
  180. package/dist/controlplane/token.d.ts +0 -33
  181. package/dist/controlplane/token.js +0 -119
  182. package/dist/direct-client/connect.d.ts +0 -9
  183. package/dist/direct-client/connect.js +0 -90
  184. package/dist/direct-client/index.d.ts +0 -1
  185. package/dist/direct-client/index.js +0 -1
  186. package/dist/e2ee/constants.d.ts +0 -9
  187. package/dist/e2ee/constants.js +0 -18
  188. package/dist/e2ee/errors.d.ts +0 -5
  189. package/dist/e2ee/errors.js +0 -8
  190. package/dist/e2ee/framing.d.ts +0 -12
  191. package/dist/e2ee/framing.js +0 -57
  192. package/dist/e2ee/handshake.d.ts +0 -88
  193. package/dist/e2ee/handshake.js +0 -353
  194. package/dist/e2ee/index.d.ts +0 -7
  195. package/dist/e2ee/index.js +0 -7
  196. package/dist/e2ee/kdf.d.ts +0 -15
  197. package/dist/e2ee/kdf.js +0 -39
  198. package/dist/e2ee/record.d.ts +0 -11
  199. package/dist/e2ee/record.js +0 -76
  200. package/dist/e2ee/secureChannel.d.ts +0 -92
  201. package/dist/e2ee/secureChannel.js +0 -328
  202. package/dist/e2ee/transcript.d.ts +0 -23
  203. package/dist/e2ee/transcript.js +0 -31
  204. package/dist/endpoint/index.d.ts +0 -84
  205. package/dist/endpoint/index.js +0 -557
  206. package/dist/endpoint/node.d.ts +0 -9
  207. package/dist/endpoint/node.js +0 -51
  208. package/dist/gen/flowersec/controlplane/v1.gen.d.ts +0 -36
  209. package/dist/gen/flowersec/controlplane/v1.gen.js +0 -135
  210. package/dist/gen/flowersec/direct/v1.gen.d.ts +0 -21
  211. package/dist/gen/flowersec/direct/v1.gen.js +0 -101
  212. package/dist/gen/flowersec/e2ee/v1.gen.d.ts +0 -68
  213. package/dist/gen/flowersec/e2ee/v1.gen.js +0 -194
  214. package/dist/gen/flowersec/rpc/v1.gen.d.ts +0 -30
  215. package/dist/gen/flowersec/rpc/v1.gen.js +0 -107
  216. package/dist/gen/flowersec/tunnel/v1.gen.d.ts +0 -23
  217. package/dist/gen/flowersec/tunnel/v1.gen.js +0 -104
  218. package/dist/index.d.ts +0 -22
  219. package/dist/index.js +0 -22
  220. package/dist/node/connect.d.ts +0 -12
  221. package/dist/node/connect.js +0 -29
  222. package/dist/node/reconnectConfig.d.ts +0 -16
  223. package/dist/node/reconnectConfig.js +0 -20
  224. package/dist/observability/index.d.ts +0 -1
  225. package/dist/observability/index.js +0 -1
  226. package/dist/observability/observer.d.ts +0 -52
  227. package/dist/observability/observer.js +0 -314
  228. package/dist/proxy/appWindow.d.ts +0 -37
  229. package/dist/proxy/appWindow.js +0 -273
  230. package/dist/proxy/bootstrap.d.ts +0 -35
  231. package/dist/proxy/bootstrap.js +0 -119
  232. package/dist/proxy/constants.d.ts +0 -7
  233. package/dist/proxy/constants.js +0 -8
  234. package/dist/proxy/controllerWindow.d.ts +0 -12
  235. package/dist/proxy/controllerWindow.js +0 -384
  236. package/dist/proxy/cookieJar.d.ts +0 -7
  237. package/dist/proxy/cookieJar.js +0 -136
  238. package/dist/proxy/headerPolicy.d.ts +0 -14
  239. package/dist/proxy/headerPolicy.js +0 -134
  240. package/dist/proxy/portStream.d.ts +0 -5
  241. package/dist/proxy/portStream.js +0 -256
  242. package/dist/proxy/preset.d.ts +0 -29
  243. package/dist/proxy/preset.js +0 -111
  244. package/dist/proxy/runtimeScope.d.ts +0 -57
  245. package/dist/proxy/runtimeScope.js +0 -218
  246. package/dist/proxy/server.d.ts +0 -28
  247. package/dist/proxy/server.js +0 -833
  248. package/dist/proxy/windowBridgeProtocol.d.ts +0 -65
  249. package/dist/proxy/windowBridgeProtocol.js +0 -11
  250. package/dist/reconnect/artifactControlplane.d.ts +0 -16
  251. package/dist/reconnect/artifactControlplane.js +0 -35
  252. package/dist/reconnect/index.d.ts +0 -36
  253. package/dist/reconnect/index.js +0 -388
  254. package/dist/rpc-proxy/index.d.ts +0 -1
  255. package/dist/rpc-proxy/index.js +0 -1
  256. package/dist/rpc-proxy/rpcProxy.d.ts +0 -1
  257. package/dist/rpc-proxy/rpcProxy.js +0 -1
  258. package/dist/streamhello/index.d.ts +0 -1
  259. package/dist/streamhello/index.js +0 -1
  260. package/dist/streamhello/streamHello.d.ts +0 -3
  261. package/dist/streamhello/streamHello.js +0 -13
  262. package/dist/streamio/index.d.ts +0 -32
  263. package/dist/streamio/index.js +0 -87
  264. package/dist/tunnel-client/connect.d.ts +0 -9
  265. package/dist/tunnel-client/connect.js +0 -157
  266. package/dist/tunnel-client/index.d.ts +0 -1
  267. package/dist/tunnel-client/index.js +0 -1
  268. package/dist/ws/index.d.ts +0 -1
  269. package/dist/ws/index.js +0 -1
  270. /package/dist/{client.js → rpc/wire.js} +0 -0
@@ -1,833 +0,0 @@
1
- import { createRequire } from "node:module";
2
- import { DEFAULT_MAX_JSON_FRAME_BYTES, readJsonFrame, writeJsonFrame } from "../framing/jsonframe.js";
3
- import { RpcRouter, RpcServer } from "../rpc/server.js";
4
- import { createByteReader } from "../streamio/index.js";
5
- import { readU32be, u32be } from "../utils/bin.js";
6
- import { DEFAULT_MAX_BODY_BYTES, DEFAULT_MAX_CHUNK_BYTES, DEFAULT_MAX_CONCURRENT_STREAMS, DEFAULT_MAX_WS_FRAME_BYTES, PROXY_KIND_HTTP1, PROXY_KIND_WS, PROXY_PROTOCOL_VERSION, } from "./constants.js";
7
- import { filterRequestHeaders, filterResponseHeaders, filterWsOpenHeaders, isSafeHeaderValue, isValidHeaderName, normalizeHeaderName } from "./headerPolicy.js";
8
- export async function serveProxySession(session, options, signal) {
9
- const compiled = compileOptions(options);
10
- const activeProxy = new Set();
11
- const activeRPC = new Set();
12
- try {
13
- while (!signal?.aborted) {
14
- const accepted = await session.acceptStream(signal === undefined ? {} : { signal });
15
- if (accepted.kind === "rpc") {
16
- const task = serveRPCStream(accepted.stream, options.rpcRouter ?? new RpcRouter(), options.rpcServerOptions, signal)
17
- .catch(() => { })
18
- .finally(() => activeRPC.delete(task));
19
- activeRPC.add(task);
20
- continue;
21
- }
22
- if (accepted.kind !== PROXY_KIND_HTTP1 && accepted.kind !== PROXY_KIND_WS) {
23
- await accepted.stream.reset(new Error(`unsupported proxy stream kind ${accepted.kind}`));
24
- continue;
25
- }
26
- if (activeProxy.size >= compiled.maxConcurrentStreams) {
27
- await accepted.stream.reset(new Error("proxy stream concurrency exhausted"));
28
- continue;
29
- }
30
- const task = serveProxyStreamCompiled(accepted.kind, accepted.stream, compiled, signal)
31
- .catch(() => { })
32
- .finally(() => activeProxy.delete(task));
33
- activeProxy.add(task);
34
- }
35
- }
36
- finally {
37
- await Promise.allSettled([...activeProxy, ...activeRPC]);
38
- }
39
- }
40
- async function serveRPCStream(stream, router, options, signal) {
41
- const reader = createByteReader(stream, signal === undefined ? {} : { signal });
42
- const server = new RpcServer({
43
- readExactly: (length) => reader.readExactly(length),
44
- write: (bytes) => stream.write(bytes),
45
- close: (error) => { void stream.reset(asError(error)); },
46
- }, options, router);
47
- await server.serve(signal);
48
- }
49
- export function serveProxyStream(kind, stream, options, signal) {
50
- const compiled = compileOptions(options);
51
- return serveProxyStreamCompiled(kind, stream, compiled, signal);
52
- }
53
- async function serveProxyStreamCompiled(kind, stream, options, signal) {
54
- try {
55
- if (kind === PROXY_KIND_HTTP1)
56
- await serveHTTP(stream, options, signal);
57
- else
58
- await serveWebSocket(stream, options, signal);
59
- }
60
- finally {
61
- try {
62
- await stream.close();
63
- }
64
- catch { /* The peer may already have reset the stream. */ }
65
- }
66
- }
67
- async function serveHTTP(stream, options, signal) {
68
- const reader = createByteReader(stream);
69
- let requestId = "unknown";
70
- let responseStarted = false;
71
- let streamReset = false;
72
- let requestBody;
73
- let responseBodyReader;
74
- let controller;
75
- let timer;
76
- let removeAbortListener;
77
- const resetStream = async (error) => {
78
- if (streamReset)
79
- return;
80
- streamReset = true;
81
- try {
82
- await stream.reset(error);
83
- }
84
- catch { /* The peer may already have reset the stream. */ }
85
- };
86
- try {
87
- const meta = assertHTTPRequestMeta(await readJsonFrame(reader, options.maxJsonFrameBytes));
88
- requestId = meta.request_id;
89
- const path = parsePath(meta.path);
90
- const knownRequestLength = contentLength(meta.headers, "request");
91
- if (knownRequestLength != null && knownRequestLength > options.maxBodyBytes) {
92
- throw new ProxyServerError("request_body_too_large", "request body too large");
93
- }
94
- if (signal?.aborted)
95
- throw new ProxyServerError("canceled", "proxy request canceled");
96
- const headers = requestHeaders(meta.headers, options);
97
- applyExternalOrigin(headers, meta.external_origin);
98
- const target = new URL(options.upstream);
99
- target.pathname = path.pathname;
100
- target.search = path.search;
101
- target.hash = "";
102
- controller = new AbortController();
103
- const abortUpstream = (error) => {
104
- if (!controller.signal.aborted)
105
- controller.abort(error);
106
- requestBody?.abort(error);
107
- if (responseStarted)
108
- void resetStream(error);
109
- };
110
- const onAbort = () => abortUpstream(new ProxyServerError("canceled", "proxy request canceled"));
111
- signal?.addEventListener("abort", onAbort, { once: true });
112
- removeAbortListener = () => signal?.removeEventListener("abort", onAbort);
113
- if (signal?.aborted)
114
- onAbort();
115
- const timeoutMs = resolveTimeout(meta.timeout_ms, options);
116
- timer = timeoutMs > 0
117
- ? setTimeout(() => abortUpstream(new ProxyServerError("timeout", "upstream request timeout")), timeoutMs)
118
- : undefined;
119
- requestBody = createStreamingRequestBody(reader, options.maxChunkBytes, options.maxBodyBytes, abortUpstream);
120
- const method = meta.method.trim().toUpperCase();
121
- const requestInit = {
122
- method,
123
- headers,
124
- redirect: "manual",
125
- signal: controller.signal,
126
- };
127
- if (method === "GET" || method === "HEAD") {
128
- requestBody.drain();
129
- }
130
- else {
131
- requestInit.body = requestBody.stream;
132
- requestInit.duplex = "half";
133
- }
134
- if (controller.signal.aborted)
135
- throw controller.signal.reason;
136
- const response = await options.fetch(target, requestInit);
137
- if (requestBody.error != null)
138
- throw requestBody.error;
139
- requestBody.drain();
140
- const knownResponseLength = contentLength(response.headers, "response");
141
- if (knownResponseLength != null && knownResponseLength > options.maxBodyBytes) {
142
- await response.body?.cancel();
143
- throw new ProxyServerError("response_body_too_large", "response body too large");
144
- }
145
- const responseHeaders = collectResponseHeaders(response.headers, options);
146
- const responseMeta = {
147
- v: PROXY_PROTOCOL_VERSION,
148
- request_id: requestId,
149
- ok: true,
150
- status: response.status,
151
- headers: responseHeaders,
152
- };
153
- await writeJsonFrame(stream, responseMeta);
154
- responseStarted = true;
155
- if (response.body == null) {
156
- await stream.write(u32be(0));
157
- const requestError = await requestBody.completion;
158
- if (requestError != null)
159
- throw requestError;
160
- return;
161
- }
162
- responseBodyReader = response.body.getReader();
163
- let total = 0;
164
- while (true) {
165
- const next = await Promise.race([
166
- responseBodyReader.read(),
167
- requestBody.failure.then((error) => { throw error; }),
168
- ]);
169
- if (next.done)
170
- break;
171
- const bytes = next.value;
172
- const nextTotal = total + bytes.length;
173
- if (!Number.isSafeInteger(nextTotal) || nextTotal > options.maxBodyBytes) {
174
- throw new ProxyServerError("response_body_too_large", "response body too large");
175
- }
176
- total = nextTotal;
177
- for (let offset = 0; offset < bytes.length; offset += options.maxChunkBytes) {
178
- const chunk = bytes.subarray(offset, Math.min(bytes.length, offset + options.maxChunkBytes));
179
- await stream.write(u32be(chunk.length));
180
- await stream.write(chunk);
181
- }
182
- }
183
- await stream.write(u32be(0));
184
- const requestError = await requestBody.completion;
185
- if (requestError != null)
186
- throw requestError;
187
- }
188
- catch (error) {
189
- const effective = requestBody?.error ?? controller?.signal.reason ?? asError(error);
190
- if (controller != null && !controller.signal.aborted)
191
- controller.abort(effective);
192
- if (responseStarted)
193
- await resetStream(effective);
194
- else
195
- await writeHTTPError(stream, requestId, classifyHTTPError(effective));
196
- }
197
- finally {
198
- if (timer != null)
199
- clearTimeout(timer);
200
- removeAbortListener?.();
201
- requestBody?.stop();
202
- try {
203
- await responseBodyReader?.cancel();
204
- }
205
- catch { /* The upstream body may already be closed. */ }
206
- }
207
- }
208
- async function serveWebSocket(stream, options, signal) {
209
- const reader = createByteReader(stream, signal === undefined ? {} : { signal });
210
- let connId = "unknown";
211
- let raw;
212
- let upstreamOpened = false;
213
- let removePostOpenAbortListener;
214
- try {
215
- const meta = assertWSOpenMeta(await readJsonFrame(reader, options.maxJsonFrameBytes));
216
- connId = meta.conn_id;
217
- const path = parsePath(meta.path);
218
- const target = new URL(options.upstream);
219
- target.protocol = target.protocol === "https:" ? "wss:" : "ws:";
220
- target.pathname = path.pathname;
221
- target.search = path.search;
222
- target.hash = "";
223
- const filtered = serverWSHeaders(meta.headers, options);
224
- const protocolsHeader = filtered.find((header) => header.name === "sec-websocket-protocol")?.value ?? "";
225
- const protocols = protocolsHeader.split(",").map((value) => value.trim()).filter((value) => value !== "");
226
- const headers = { Origin: options.upstreamOrigin };
227
- for (const header of filtered) {
228
- if (header.name === "sec-websocket-protocol")
229
- continue;
230
- headers[header.name] = headers[header.name] == null ? header.value : `${headers[header.name]}, ${header.value}`;
231
- }
232
- const require = createRequire(import.meta.url);
233
- const module = require("ws");
234
- const WebSocketCtor = module.WebSocket ?? module;
235
- raw = new WebSocketCtor(target.toString(), protocols, {
236
- headers,
237
- maxPayload: options.maxWsFrameBytes,
238
- perMessageDeflate: false,
239
- handshakeTimeout: resolveTimeout(undefined, options),
240
- });
241
- let writeChain = Promise.resolve();
242
- let queuedWriteBytes = 0;
243
- let terminalSettled = false;
244
- let terminalError;
245
- let terminalResolve;
246
- const terminal = new Promise((resolve) => { terminalResolve = resolve; });
247
- const settleTerminal = (error) => {
248
- if (terminalSettled)
249
- return;
250
- terminalSettled = true;
251
- terminalError = error;
252
- terminalResolve(error);
253
- };
254
- const queueFrame = (op, payload, allowCloseFrame = false) => {
255
- if (terminalSettled)
256
- return Promise.reject(new Error("proxy WebSocket is closed"));
257
- const frameBytes = payload.byteLength + 5;
258
- const nextQueuedWriteBytes = queuedWriteBytes + frameBytes;
259
- if (!Number.isSafeInteger(nextQueuedWriteBytes)
260
- || (!allowCloseFrame && nextQueuedWriteBytes > options.maxWsQueuedBytes)) {
261
- const error = new ProxyServerError("resource_exhausted", "proxy WebSocket write queue exhausted");
262
- settleTerminal(error);
263
- try {
264
- raw.close(1011, "proxy buffer limit exceeded");
265
- }
266
- catch { /* Best effort. */ }
267
- return Promise.reject(error);
268
- }
269
- queuedWriteBytes = nextQueuedWriteBytes;
270
- const write = writeChain
271
- .then(() => {
272
- if (terminalSettled)
273
- throw terminalError ?? new Error("proxy WebSocket is closed");
274
- return writeWSFrame(stream, op, payload, options.maxWsFrameBytes);
275
- })
276
- .finally(() => {
277
- queuedWriteBytes = Math.max(0, queuedWriteBytes - frameBytes);
278
- });
279
- writeChain = write;
280
- void write.catch((error) => settleTerminal(asError(error)));
281
- return write;
282
- };
283
- const queueEventFrame = (op, payload) => {
284
- void queueFrame(op, payload).catch(() => { });
285
- };
286
- raw.on("close", (code, reason) => {
287
- if (terminalSettled)
288
- return;
289
- if (!upstreamOpened) {
290
- settleTerminal(new Error("upstream WebSocket closed before open"));
291
- return;
292
- }
293
- const reasonBytes = toBytes(reason);
294
- const payload = new Uint8Array(2 + reasonBytes.length);
295
- payload[0] = (code >>> 8) & 0xff;
296
- payload[1] = code & 0xff;
297
- payload.set(reasonBytes, 2);
298
- void queueFrame(8, payload, true).then(() => settleTerminal(), (error) => settleTerminal(asError(error)));
299
- });
300
- raw.on("error", (error) => settleTerminal(error));
301
- await waitForUpstreamOpen(raw, signal);
302
- upstreamOpened = true;
303
- const response = {
304
- v: PROXY_PROTOCOL_VERSION,
305
- conn_id: connId,
306
- ok: true,
307
- protocol: String(raw.protocol ?? ""),
308
- };
309
- const openResponseWrite = writeJsonFrame(stream, response);
310
- writeChain = openResponseWrite;
311
- void openResponseWrite.catch((error) => settleTerminal(asError(error)));
312
- raw.on("message", (data, isBinary) => queueEventFrame(isBinary ? 2 : 1, toBytes(data)));
313
- raw.on("ping", (data) => queueEventFrame(9, toBytes(data)));
314
- raw.on("pong", (data) => queueEventFrame(10, toBytes(data)));
315
- const onPostOpenAbort = () => {
316
- const error = asError(signal?.reason ?? new Error("proxy WebSocket aborted"));
317
- settleTerminal(error);
318
- try {
319
- if (typeof raw.terminate === "function")
320
- raw.terminate();
321
- else
322
- raw.close();
323
- }
324
- catch {
325
- // The terminal error is already authoritative.
326
- }
327
- };
328
- if (signal != null) {
329
- signal.addEventListener("abort", onPostOpenAbort, { once: true });
330
- removePostOpenAbortListener = () => signal.removeEventListener("abort", onPostOpenAbort);
331
- if (signal.aborted)
332
- onPostOpenAbort();
333
- }
334
- const openResponseResult = await Promise.race([
335
- openResponseWrite.then(() => undefined),
336
- terminal,
337
- ]);
338
- if (openResponseResult != null)
339
- throw openResponseResult;
340
- try {
341
- if (!terminalSettled && typeof raw.resume === "function")
342
- raw.resume();
343
- }
344
- catch (error) {
345
- settleTerminal(asError(error));
346
- throw error;
347
- }
348
- const inbound = (async () => {
349
- while (true) {
350
- const frame = await readWSFrame(reader, options.maxWsFrameBytes);
351
- switch (frame.op) {
352
- case 1:
353
- await sendUpstreamFrame(raw, 1, frame.payload);
354
- break;
355
- case 2:
356
- await sendUpstreamFrame(raw, 2, frame.payload);
357
- break;
358
- case 8: {
359
- raw.close(frame.payload.length >= 2 ? (frame.payload[0] << 8) | frame.payload[1] : 1000, new TextDecoder().decode(frame.payload.subarray(2)));
360
- const closeError = await terminal;
361
- if (closeError != null)
362
- throw closeError;
363
- return;
364
- }
365
- case 9:
366
- await sendUpstreamFrame(raw, 9, frame.payload);
367
- break;
368
- case 10:
369
- await sendUpstreamFrame(raw, 10, frame.payload);
370
- break;
371
- default: throw new Error("invalid websocket frame operation");
372
- }
373
- }
374
- })();
375
- const result = await Promise.race([inbound.then(() => undefined), terminal]);
376
- if (result != null)
377
- throw result;
378
- await writeChain;
379
- }
380
- catch (error) {
381
- if (!upstreamOpened)
382
- await writeWSOpenError(stream, connId, classifyWSError(error));
383
- }
384
- finally {
385
- removePostOpenAbortListener?.();
386
- try {
387
- raw?.close();
388
- }
389
- catch { /* Best effort. */ }
390
- }
391
- }
392
- function compileOptions(input) {
393
- const upstream = new URL(input.upstream);
394
- if ((upstream.protocol !== "http:" && upstream.protocol !== "https:") || upstream.username !== "" || upstream.password !== "") {
395
- throw new Error("upstream must be an http(s) URL without credentials");
396
- }
397
- const allowedHosts = new Set((input.allowedUpstreamHosts?.length ? input.allowedUpstreamHosts : ["127.0.0.1"]).map((host) => host.trim().toLowerCase()));
398
- if (!allowedHosts.has(upstream.hostname.toLowerCase()))
399
- throw new Error("upstream host is not allowed");
400
- const upstreamOrigin = normalizeOrigin(input.upstreamOrigin ?? upstream.origin);
401
- const maxJsonFrameBytes = positive(input.maxJsonFrameBytes, DEFAULT_MAX_JSON_FRAME_BYTES, "maxJsonFrameBytes");
402
- const maxChunkBytes = positive(input.maxChunkBytes, DEFAULT_MAX_CHUNK_BYTES, "maxChunkBytes");
403
- const maxBodyBytes = positive(input.maxBodyBytes, DEFAULT_MAX_BODY_BYTES, "maxBodyBytes");
404
- const maxWsFrameBytes = positive(input.maxWsFrameBytes, DEFAULT_MAX_WS_FRAME_BYTES, "maxWsFrameBytes");
405
- const defaultMaxWsQueuedBytes = maxWsFrameBytes <= Number.MAX_SAFE_INTEGER - 5
406
- ? maxWsFrameBytes + 5
407
- : maxWsFrameBytes;
408
- const maxWsQueuedBytes = positive(input.maxWsQueuedBytes, defaultMaxWsQueuedBytes, "maxWsQueuedBytes");
409
- if (maxWsQueuedBytes < 5)
410
- throw new Error("maxWsQueuedBytes must be at least 5");
411
- const defaultTimeoutMs = nonNegative(input.defaultTimeoutMs, 30_000, "defaultTimeoutMs");
412
- const maxTimeoutMs = nonNegative(input.maxTimeoutMs, 300_000, "maxTimeoutMs");
413
- if (maxTimeoutMs > 0 && defaultTimeoutMs > maxTimeoutMs)
414
- throw new Error("defaultTimeoutMs exceeds maxTimeoutMs");
415
- return {
416
- upstream,
417
- upstreamOrigin,
418
- maxJsonFrameBytes,
419
- maxChunkBytes,
420
- maxBodyBytes,
421
- maxWsFrameBytes,
422
- maxWsQueuedBytes,
423
- defaultTimeoutMs,
424
- maxTimeoutMs,
425
- maxConcurrentStreams: positive(input.maxConcurrentStreams, DEFAULT_MAX_CONCURRENT_STREAMS, "maxConcurrentStreams"),
426
- extraRequestHeaders: input.extraRequestHeaders ?? [],
427
- extraResponseHeaders: input.extraResponseHeaders ?? [],
428
- blockedResponseHeaders: normalizeNames(input.blockedResponseHeaders),
429
- extraWsHeaders: input.extraWsHeaders ?? [],
430
- forbiddenCookieNames: normalizeNames(input.forbiddenCookieNames),
431
- forbiddenCookieNamePrefixes: [...normalizeNames(input.forbiddenCookieNamePrefixes)],
432
- fetch: input.fetch ?? globalThis.fetch.bind(globalThis),
433
- };
434
- }
435
- function assertHTTPRequestMeta(input) {
436
- if (typeof input !== "object" || input == null || Array.isArray(input))
437
- throw new ProxyServerError("invalid_request_meta", "invalid HTTP request meta");
438
- const meta = input;
439
- if (meta.v !== PROXY_PROTOCOL_VERSION || typeof meta.request_id !== "string" || meta.request_id.trim() === "")
440
- throw new ProxyServerError("invalid_request_meta", "invalid request ID");
441
- if (typeof meta.method !== "string" || meta.method.trim() === "" || typeof meta.path !== "string" || !Array.isArray(meta.headers))
442
- throw new ProxyServerError("invalid_request_meta", "invalid HTTP request meta");
443
- if (meta.timeout_ms !== undefined && (!Number.isSafeInteger(meta.timeout_ms) || meta.timeout_ms < 0))
444
- throw new ProxyServerError("invalid_request_meta", "invalid timeout_ms");
445
- return { ...meta, request_id: meta.request_id.trim(), method: meta.method.trim() };
446
- }
447
- function assertWSOpenMeta(input) {
448
- if (typeof input !== "object" || input == null || Array.isArray(input))
449
- throw new ProxyServerError("invalid_ws_open_meta", "invalid websocket open meta");
450
- const meta = input;
451
- if (meta.v !== PROXY_PROTOCOL_VERSION || typeof meta.conn_id !== "string" || meta.conn_id.trim() === "" || typeof meta.path !== "string" || !Array.isArray(meta.headers)) {
452
- throw new ProxyServerError("invalid_ws_open_meta", "invalid websocket open meta");
453
- }
454
- return { ...meta, conn_id: meta.conn_id.trim() };
455
- }
456
- function parsePath(input) {
457
- const path = input.trim();
458
- if (!path.startsWith("/") || path.startsWith("//") || path.includes("://") || /[\r\n\t ]/.test(path))
459
- throw new ProxyServerError("invalid_request_meta", "invalid path");
460
- return new URL(path, "http://flowersec.invalid");
461
- }
462
- function requestHeaders(input, options) {
463
- const filtered = filterRequestHeaders(input, { extraAllowed: options.extraRequestHeaders });
464
- const headers = new Headers();
465
- for (const header of filtered)
466
- headers.append(header.name, header.value);
467
- for (const header of input) {
468
- if (normalizeHeaderName(header.name) !== "cookie" || !isSafeHeaderValue(header.value))
469
- continue;
470
- const cookie = filterCookie(header.value, options);
471
- if (cookie !== "")
472
- headers.append("cookie", cookie);
473
- }
474
- return headers;
475
- }
476
- function serverWSHeaders(input, options) {
477
- const filtered = filterWsOpenHeaders(input, { extraAllowed: options.extraWsHeaders });
478
- return filtered.flatMap((header) => header.name === "cookie" ? [{ ...header, value: filterCookie(header.value, options) }] : [header]).filter((header) => header.value !== "");
479
- }
480
- function filterCookie(input, options) {
481
- return input.split(";").map((part) => part.trim()).filter((part) => {
482
- const index = part.indexOf("=");
483
- if (index <= 0)
484
- return false;
485
- const name = part.slice(0, index).trim().toLowerCase();
486
- if (options.forbiddenCookieNames.has(name))
487
- return false;
488
- return !options.forbiddenCookieNamePrefixes.some((prefix) => name.startsWith(prefix));
489
- }).join("; ");
490
- }
491
- function collectResponseHeaders(input, options) {
492
- const raw = [];
493
- const setCookies = input.getSetCookie?.() ?? [];
494
- input.forEach((value, name) => {
495
- if (name.toLowerCase() !== "set-cookie")
496
- raw.push({ name, value });
497
- });
498
- for (const value of setCookies)
499
- raw.push({ name: "set-cookie", value });
500
- return filterResponseHeaders(raw, { extraAllowed: options.extraResponseHeaders }).passthrough
501
- .concat(raw.filter((header) => normalizeHeaderName(header.name) === "set-cookie"))
502
- .filter((header) => !options.blockedResponseHeaders.has(normalizeHeaderName(header.name)));
503
- }
504
- function applyExternalOrigin(headers, input) {
505
- if (input == null || input.trim() === "")
506
- return;
507
- const external = normalizeOrigin(input);
508
- const current = headers.get("origin");
509
- if (current != null && normalizeOrigin(current) !== external)
510
- throw new ProxyServerError("invalid_request_meta", "external_origin conflicts with origin header");
511
- headers.set("x-forwarded-proto", new URL(external).protocol.slice(0, -1));
512
- headers.set("host", new URL(external).host);
513
- }
514
- function createStreamingRequestBody(reader, maxChunkBytes, maxBodyBytes, onFailure) {
515
- let total = 0;
516
- let finished = false;
517
- let draining = false;
518
- let readChain = Promise.resolve();
519
- let error;
520
- let streamController;
521
- let resolveFailure;
522
- const failure = new Promise((resolve) => { resolveFailure = resolve; });
523
- let resolveCompletion;
524
- const completion = new Promise((resolve) => { resolveCompletion = resolve; });
525
- const fail = (cause) => {
526
- if (error != null)
527
- return error;
528
- const raw = asError(cause);
529
- error = raw instanceof ProxyServerError
530
- ? raw
531
- : new ProxyServerError("request_body_invalid", raw.message);
532
- finished = true;
533
- try {
534
- streamController?.error(error);
535
- }
536
- catch { /* The body consumer may already be gone. */ }
537
- resolveFailure(error);
538
- resolveCompletion(error);
539
- onFailure(error);
540
- return error;
541
- };
542
- const readChunkUnserialized = async () => {
543
- try {
544
- const length = readU32be(await reader.readExactly(4), 0);
545
- if (length === 0) {
546
- finished = true;
547
- resolveCompletion(undefined);
548
- return null;
549
- }
550
- if (length > maxChunkBytes) {
551
- throw new ProxyServerError("request_body_too_large", "request chunk too large");
552
- }
553
- const nextTotal = total + length;
554
- if (!Number.isSafeInteger(nextTotal) || nextTotal > maxBodyBytes) {
555
- throw new ProxyServerError("request_body_too_large", "request body too large");
556
- }
557
- const chunk = await reader.readExactly(length);
558
- total = nextTotal;
559
- return chunk;
560
- }
561
- catch (cause) {
562
- throw fail(cause);
563
- }
564
- };
565
- const readChunk = () => {
566
- const next = readChain.then(readChunkUnserialized);
567
- readChain = next.then(() => { }, () => { });
568
- return next;
569
- };
570
- const drain = () => {
571
- if (draining || finished)
572
- return;
573
- draining = true;
574
- try {
575
- streamController?.close();
576
- }
577
- catch { /* A pending pull will observe draining below. */ }
578
- void (async () => {
579
- while (!finished)
580
- await readChunk();
581
- })().catch(() => { });
582
- };
583
- const stream = new ReadableStream({
584
- start(controller) { streamController = controller; },
585
- async pull(controller) {
586
- if (finished) {
587
- if (error == null)
588
- controller.close();
589
- return;
590
- }
591
- const chunk = await readChunk();
592
- if (draining)
593
- return;
594
- if (chunk == null)
595
- controller.close();
596
- else
597
- controller.enqueue(chunk);
598
- },
599
- cancel() { drain(); },
600
- }, { highWaterMark: 0 });
601
- return {
602
- stream,
603
- failure,
604
- completion,
605
- get error() { return error; },
606
- drain,
607
- abort: (cause) => { if (!finished)
608
- fail(cause); },
609
- stop: () => {
610
- if (finished)
611
- return;
612
- finished = true;
613
- try {
614
- streamController?.close();
615
- }
616
- catch { /* The fetch implementation may own the stream. */ }
617
- resolveCompletion(undefined);
618
- },
619
- };
620
- }
621
- function contentLength(headers, side) {
622
- const rawValues = headers instanceof Headers
623
- ? (headers.get("content-length") == null ? [] : [headers.get("content-length")])
624
- : headers
625
- .filter((header) => normalizeHeaderName(header.name) === "content-length")
626
- .map((header) => header.value.trim());
627
- if (rawValues.length === 0)
628
- return undefined;
629
- if (rawValues.length !== 1 || !/^(0|[1-9][0-9]*)$/.test(rawValues[0])) {
630
- throw new ProxyServerError(side === "request" ? "invalid_request_meta" : "upstream_request_failed", `invalid ${side} content-length`);
631
- }
632
- const value = Number(rawValues[0]);
633
- if (!Number.isSafeInteger(value)) {
634
- throw new ProxyServerError(side === "request" ? "invalid_request_meta" : "upstream_request_failed", `invalid ${side} content-length`);
635
- }
636
- return value;
637
- }
638
- async function writeHTTPError(stream, requestId, code) {
639
- const meta = {
640
- v: PROXY_PROTOCOL_VERSION,
641
- request_id: requestId.trim() || "unknown",
642
- ok: false,
643
- error: { code, message: publicHTTPErrorMessage(code) },
644
- };
645
- try {
646
- await writeJsonFrame(stream, meta);
647
- await stream.write(u32be(0));
648
- }
649
- catch { /* The peer may be gone. */ }
650
- }
651
- async function readWSFrame(reader, maxBytes) {
652
- const header = await reader.readExactly(5);
653
- const length = readU32be(header, 1);
654
- if (length > maxBytes)
655
- throw new Error("websocket frame too large");
656
- return { op: header[0], payload: await reader.readExactly(length) };
657
- }
658
- async function writeWSFrame(stream, op, payload, maxBytes) {
659
- if (payload.length > maxBytes)
660
- throw new Error("websocket frame too large");
661
- const header = new Uint8Array(5);
662
- header[0] = op;
663
- header.set(u32be(payload.length), 1);
664
- await stream.write(header);
665
- if (payload.length > 0)
666
- await stream.write(payload);
667
- }
668
- async function writeWSOpenError(stream, connId, code) {
669
- const response = {
670
- v: PROXY_PROTOCOL_VERSION,
671
- conn_id: connId.trim() || "unknown",
672
- ok: false,
673
- error: { code, message: publicWSErrorMessage(code) },
674
- };
675
- try {
676
- await writeJsonFrame(stream, response);
677
- }
678
- catch { /* The peer may be gone. */ }
679
- }
680
- function waitForUpstreamOpen(websocket, signal) {
681
- return new Promise((resolve, reject) => {
682
- const cleanup = () => {
683
- websocket.off("open", onOpen);
684
- websocket.off("error", onError);
685
- websocket.off("close", onClose);
686
- signal?.removeEventListener("abort", onAbort);
687
- };
688
- const onOpen = () => {
689
- try {
690
- if (typeof websocket.pause === "function")
691
- websocket.pause();
692
- }
693
- catch (error) {
694
- cleanup();
695
- reject(asError(error));
696
- return;
697
- }
698
- cleanup();
699
- resolve();
700
- };
701
- const onError = (error) => { cleanup(); reject(error); };
702
- const onClose = () => { cleanup(); reject(new Error("upstream WebSocket closed before open")); };
703
- const onAbort = () => { cleanup(); reject(signal?.reason ?? new Error("aborted")); };
704
- websocket.once("open", onOpen);
705
- websocket.once("error", onError);
706
- websocket.once("close", onClose);
707
- signal?.addEventListener("abort", onAbort, { once: true });
708
- if (signal?.aborted)
709
- onAbort();
710
- });
711
- }
712
- function sendUpstreamFrame(websocket, op, payload) {
713
- return new Promise((resolve, reject) => {
714
- const finish = (error) => {
715
- if (error == null)
716
- resolve();
717
- else
718
- reject(asError(error));
719
- };
720
- try {
721
- switch (op) {
722
- case 1:
723
- websocket.send(payload, { binary: false }, finish);
724
- return;
725
- case 2:
726
- websocket.send(payload, { binary: true }, finish);
727
- return;
728
- case 9:
729
- websocket.ping(payload, undefined, finish);
730
- return;
731
- case 10:
732
- websocket.pong(payload, undefined, finish);
733
- return;
734
- default:
735
- reject(new Error("invalid upstream WebSocket operation"));
736
- }
737
- }
738
- catch (error) {
739
- reject(asError(error));
740
- }
741
- });
742
- }
743
- function resolveTimeout(input, options) {
744
- const timeout = input == null || input === 0 ? options.defaultTimeoutMs : input;
745
- return options.maxTimeoutMs > 0 ? Math.min(timeout, options.maxTimeoutMs) : timeout;
746
- }
747
- function normalizeOrigin(input) {
748
- const url = new URL(input);
749
- if ((url.protocol !== "http:" && url.protocol !== "https:") || url.username !== "" || url.password !== "" || (url.pathname !== "" && url.pathname !== "/") || url.search !== "" || url.hash !== "")
750
- throw new Error("invalid origin");
751
- return url.origin;
752
- }
753
- function normalizeNames(input) {
754
- const result = new Set();
755
- for (const item of input ?? []) {
756
- const name = normalizeHeaderName(item);
757
- if (name === "" || !isValidHeaderName(name))
758
- throw new Error("invalid policy name");
759
- result.add(name);
760
- }
761
- return result;
762
- }
763
- function positive(input, fallback, name) {
764
- const value = input ?? fallback;
765
- if (!Number.isSafeInteger(value) || value <= 0)
766
- throw new Error(`${name} must be a positive integer`);
767
- return value;
768
- }
769
- function nonNegative(input, fallback, name) {
770
- const value = input ?? fallback;
771
- if (!Number.isSafeInteger(value) || value < 0)
772
- throw new Error(`${name} must be a non-negative integer`);
773
- return value;
774
- }
775
- function classifyHTTPError(error) {
776
- if (error instanceof ProxyServerError)
777
- return error.code;
778
- if (asError(error).name === "AbortError")
779
- return "timeout";
780
- return "upstream_request_failed";
781
- }
782
- function classifyWSError(error) {
783
- if (error instanceof ProxyServerError)
784
- return error.code;
785
- return "upstream_ws_dial_failed";
786
- }
787
- function publicHTTPErrorMessage(code) {
788
- switch (code) {
789
- case "invalid_request_meta":
790
- return "invalid proxy request";
791
- case "request_body_too_large":
792
- case "response_body_too_large":
793
- return "proxy body limit exceeded";
794
- case "resource_exhausted":
795
- return "proxy resource limit exceeded";
796
- case "timeout":
797
- return "upstream request timed out";
798
- case "canceled":
799
- return "proxy request canceled";
800
- default:
801
- return "upstream request failed";
802
- }
803
- }
804
- function publicWSErrorMessage(code) {
805
- switch (code) {
806
- case "invalid_ws_open_meta":
807
- return "invalid proxy WebSocket request";
808
- case "resource_exhausted":
809
- return "proxy WebSocket resource limit exceeded";
810
- default:
811
- return "upstream WebSocket connection failed";
812
- }
813
- }
814
- function toBytes(input) {
815
- if (input instanceof Uint8Array)
816
- return input;
817
- if (input instanceof ArrayBuffer)
818
- return new Uint8Array(input);
819
- if (ArrayBuffer.isView(input))
820
- return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
821
- return new TextEncoder().encode(String(input ?? ""));
822
- }
823
- function asError(error) {
824
- return error instanceof Error ? error : new Error(String(error));
825
- }
826
- class ProxyServerError extends Error {
827
- code;
828
- constructor(code, message) {
829
- super(message);
830
- this.code = code;
831
- this.name = "ProxyServerError";
832
- }
833
- }