ftown-bridge 0.19.19 → 0.19.21
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 +228 -3
- package/dist/index.js.map +1 -1
- package/dist/solo/contract.d.ts +104 -0
- package/dist/solo/contract.js +248 -0
- package/dist/solo/contract.js.map +1 -0
- package/dist/solo/hub-manager.d.ts +85 -0
- package/dist/solo/hub-manager.js +381 -0
- package/dist/solo/hub-manager.js.map +1 -0
- package/dist/solo/panel-manager.d.ts +129 -0
- package/dist/solo/panel-manager.js +715 -0
- package/dist/solo/panel-manager.js.map +1 -0
- package/dist/solo/solo-auth.d.ts +25 -0
- package/dist/solo/solo-auth.js +83 -0
- package/dist/solo/solo-auth.js.map +1 -0
- package/dist/solo/solo-server.d.ts +105 -0
- package/dist/solo/solo-server.js +399 -0
- package/dist/solo/solo-server.js.map +1 -0
- package/dist/solo/ws-proxy.d.ts +55 -0
- package/dist/solo/ws-proxy.js +228 -0
- package/dist/solo/ws-proxy.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
/**
|
|
4
|
+
* ws-proxy — P1-P5 proxying for solo mode (contract v4).
|
|
5
|
+
*
|
|
6
|
+
* S20 SINGLE-PARSE SEAM — the ONE coherent interpretation of routing-table row
|
|
7
|
+
* "GET /hub/connection/websocket → proxied to hub" + P1 + the hub-config note:
|
|
8
|
+
*
|
|
9
|
+
* 1. The PUBLIC request path is matched against ^/hub/connection/websocket$
|
|
10
|
+
* (case-sensitive, exact — no trailing segments, no percent-encoded
|
|
11
|
+
* slashes, no double slashes, no NUL suffixes; query strings allowed).
|
|
12
|
+
* 2. On match, the FORWARD path sent to the hub is the public path with the
|
|
13
|
+
* single leading '/hub' prefix stripped → '/connection/websocket', which
|
|
14
|
+
* is centrifugo's default path (no hub path options needed).
|
|
15
|
+
*
|
|
16
|
+
* (P1's regex describes the public allowlist; stripping happens only for
|
|
17
|
+
* forwarding. Anything else under /hub is never forwarded — solo-server 404s
|
|
18
|
+
* it; this module exposes only parse + forwarding.)
|
|
19
|
+
*/
|
|
20
|
+
/** Public path allowlist (P1) — exact match required. */
|
|
21
|
+
export const HUB_UPGRADE_PUBLIC_PATH = '/hub/connection/websocket';
|
|
22
|
+
/** Forward path to the hub after stripping the single leading '/hub'. */
|
|
23
|
+
export const HUB_UPSTREAM_WS_PATH = '/connection/websocket';
|
|
24
|
+
/**
|
|
25
|
+
* Inbound hop-by-hop headers stripped per P3 — exact contract list.
|
|
26
|
+
*/
|
|
27
|
+
export const HOP_BY_HOP_HEADERS = [
|
|
28
|
+
'connection',
|
|
29
|
+
'keep-alive',
|
|
30
|
+
'proxy-authenticate',
|
|
31
|
+
'proxy-authorization',
|
|
32
|
+
'te',
|
|
33
|
+
'trailer',
|
|
34
|
+
'transfer-encoding',
|
|
35
|
+
'upgrade',
|
|
36
|
+
];
|
|
37
|
+
const HOP_BY_HOP = new Set(HOP_BY_HOP_HEADERS);
|
|
38
|
+
const BAD_GATEWAY_BODY = '{"error":"upstream unavailable"}';
|
|
39
|
+
/**
|
|
40
|
+
* S20 seam: parse req.url ONCE and decide whether it targets the proxied hub
|
|
41
|
+
* upgrade path. Percent-decoded / differently-cased / extra-segment variants
|
|
42
|
+
* are NOT the allowlist path (goldens pinned in tests).
|
|
43
|
+
*/
|
|
44
|
+
export function parseHubTarget(url) {
|
|
45
|
+
try {
|
|
46
|
+
const { pathname } = new URL(url, 'http://solo.invalid');
|
|
47
|
+
return { isHubUpgradePath: pathname === HUB_UPGRADE_PUBLIC_PATH };
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return { isHubUpgradePath: false };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* P3/P4 inbound header sanitizer shared by both proxy paths:
|
|
55
|
+
* - drops every hop-by-hop header in HOP_BY_HOP_HEADERS,
|
|
56
|
+
* - drops every sec-websocket-* header (upstream handshake recomputes;
|
|
57
|
+
* handleHubUpgrade re-adds a fresh key/version so no deflate survives),
|
|
58
|
+
* - drops every inbound x-forwarded-* header (never relayed — S13),
|
|
59
|
+
* - rewrites Host to 127.0.0.1:<targetPort> (P2),
|
|
60
|
+
* - sets X-Forwarded-Proto from the caller-supplied scheme (P4).
|
|
61
|
+
*/
|
|
62
|
+
function sanitizeRequestHeaders(headers, targetPort, forwardedProto) {
|
|
63
|
+
const out = {};
|
|
64
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
65
|
+
const lower = name.toLowerCase();
|
|
66
|
+
if (HOP_BY_HOP.has(lower))
|
|
67
|
+
continue;
|
|
68
|
+
if (lower.startsWith('sec-websocket-'))
|
|
69
|
+
continue;
|
|
70
|
+
if (lower.startsWith('x-forwarded-'))
|
|
71
|
+
continue;
|
|
72
|
+
out[lower] = value;
|
|
73
|
+
}
|
|
74
|
+
out['host'] = `127.0.0.1:${targetPort}`;
|
|
75
|
+
out['x-forwarded-proto'] = forwardedProto;
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
/** Strip hop-by-hop headers from an upstream response before relaying (P3). */
|
|
79
|
+
function sanitizeResponseHeaders(headers) {
|
|
80
|
+
const out = {};
|
|
81
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
82
|
+
const lower = name.toLowerCase();
|
|
83
|
+
if (HOP_BY_HOP.has(lower))
|
|
84
|
+
continue;
|
|
85
|
+
out[lower] = value;
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
function writeJsonError(socket, statusCode, statusText, error) {
|
|
90
|
+
try {
|
|
91
|
+
socket.end(`HTTP/1.1 ${statusCode} ${statusText}\r\n` +
|
|
92
|
+
'content-type: application/json\r\n' +
|
|
93
|
+
`content-length: ${Buffer.byteLength(error)}\r\n` +
|
|
94
|
+
'connection: close\r\n' +
|
|
95
|
+
'\r\n' +
|
|
96
|
+
error);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Socket already gone — nothing to relay the error to.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* P2/P3/P4 plain-HTTP proxy hop to 127.0.0.1:<targetPort>. Request and response
|
|
104
|
+
* body streams are piped both directions; status/headers propagate minus
|
|
105
|
+
* hop-by-hop. Upstream failure yields {"error":...} with 502 (headers not yet
|
|
106
|
+
* sent) or destroys the response.
|
|
107
|
+
*/
|
|
108
|
+
export function proxyHttpRequest(req, res, targetPort, forwardedProto = 'http') {
|
|
109
|
+
const upstream = http.request({
|
|
110
|
+
host: '127.0.0.1',
|
|
111
|
+
port: targetPort,
|
|
112
|
+
path: req.url ?? '/',
|
|
113
|
+
method: req.method ?? 'GET',
|
|
114
|
+
headers: sanitizeRequestHeaders(req.headers, targetPort, forwardedProto),
|
|
115
|
+
}, (upstreamRes) => {
|
|
116
|
+
res.writeHead(upstreamRes.statusCode ?? 502, sanitizeResponseHeaders(upstreamRes.headers));
|
|
117
|
+
upstreamRes.pipe(res);
|
|
118
|
+
});
|
|
119
|
+
req.pipe(upstream);
|
|
120
|
+
upstream.on('error', () => {
|
|
121
|
+
if (res.headersSent) {
|
|
122
|
+
res.destroy();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
res.writeHead(502, {
|
|
126
|
+
'content-type': 'application/json',
|
|
127
|
+
'content-length': Buffer.byteLength(BAD_GATEWAY_BODY),
|
|
128
|
+
connection: 'close',
|
|
129
|
+
});
|
|
130
|
+
res.end(BAD_GATEWAY_BODY);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* WebSocket upgrade proxy (P1/P2/P3/P5): validates the S20 allowlist first,
|
|
135
|
+
* then opens an HTTP upgrade to 127.0.0.1:<targetPort> at
|
|
136
|
+
* HUB_UPSTREAM_WS_PATH with sanitized headers. All inbound sec-websocket-*
|
|
137
|
+
* are stripped (no extension offer survives — compression stays off), then
|
|
138
|
+
* the handshake identity is re-set: version 13 plus the CLIENT'S OWN
|
|
139
|
+
* Sec-WebSocket-Key (fresh random fallback). Preserving the client key lets
|
|
140
|
+
* the UPSTREAM handshake compute a Sec-WebSocket-Accept that validates
|
|
141
|
+
* end-to-end while this module recomputes nothing locally. On upstream 101
|
|
142
|
+
* the upstream-computed sec-websocket-* headers are relayed verbatim, then
|
|
143
|
+
* both sockets are piped so protocol pings/pongs pass untouched (P5).
|
|
144
|
+
*/
|
|
145
|
+
export function handleHubUpgrade(req, socket, head, targetPort, forwardedProto = 'http') {
|
|
146
|
+
if (!parseHubTarget(req.url ?? '').isHubUpgradePath || (req.headers.upgrade ?? '').toLowerCase() !== 'websocket') {
|
|
147
|
+
writeJsonError(socket, 404, 'Not Found', '{"error":"not found"}');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const headers = sanitizeRequestHeaders(req.headers, targetPort, forwardedProto);
|
|
151
|
+
headers['connection'] = 'Upgrade';
|
|
152
|
+
headers['upgrade'] = 'websocket';
|
|
153
|
+
const clientKey = req.headers['sec-websocket-key'];
|
|
154
|
+
headers['sec-websocket-key'] =
|
|
155
|
+
typeof clientKey === 'string' && clientKey.length > 0
|
|
156
|
+
? clientKey
|
|
157
|
+
: crypto.randomBytes(16).toString('base64');
|
|
158
|
+
headers['sec-websocket-version'] = '13';
|
|
159
|
+
const killBoth = (a, b) => {
|
|
160
|
+
a.destroy();
|
|
161
|
+
b.destroy();
|
|
162
|
+
};
|
|
163
|
+
const upstream = http.request({
|
|
164
|
+
host: '127.0.0.1',
|
|
165
|
+
port: targetPort,
|
|
166
|
+
path: HUB_UPSTREAM_WS_PATH,
|
|
167
|
+
method: 'GET',
|
|
168
|
+
headers,
|
|
169
|
+
});
|
|
170
|
+
upstream.on('upgrade', (upstreamRes, upstreamSocket, upstreamHead) => {
|
|
171
|
+
// Relay upstream's 101 + its OWN sec-websocket-* accept headers (P3).
|
|
172
|
+
const lines = ['HTTP/1.1 101 Switching Protocols', 'connection: Upgrade', 'upgrade: websocket'];
|
|
173
|
+
for (const name of ['sec-websocket-accept', 'sec-websocket-protocol', 'sec-websocket-extensions']) {
|
|
174
|
+
const value = upstreamRes.headers[name];
|
|
175
|
+
if (value !== undefined) {
|
|
176
|
+
lines.push(`${name}: ${Array.isArray(value) ? value.join(', ') : value}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
socket.write(lines.join('\r\n') + '\r\n\r\n');
|
|
181
|
+
if (head.length > 0)
|
|
182
|
+
socket.write(head);
|
|
183
|
+
if (upstreamHead.length > 0)
|
|
184
|
+
socket.write(upstreamHead);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
upstreamSocket.destroy();
|
|
188
|
+
socket.destroy();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
upstreamSocket.pipe(socket);
|
|
192
|
+
socket.pipe(upstreamSocket);
|
|
193
|
+
upstreamSocket.on('error', () => killBoth(upstreamSocket, socket));
|
|
194
|
+
socket.on('error', () => killBoth(upstreamSocket, socket));
|
|
195
|
+
upstreamSocket.on('close', () => socket.destroy());
|
|
196
|
+
socket.on('close', () => upstreamSocket.destroy());
|
|
197
|
+
});
|
|
198
|
+
// Hub answered with a plain HTTP response (e.g. rejected handshake): relay
|
|
199
|
+
// it once, then close.
|
|
200
|
+
upstream.on('response', (upstreamRes) => {
|
|
201
|
+
const chunks = [];
|
|
202
|
+
upstreamRes.on('data', (chunk) => chunks.push(chunk));
|
|
203
|
+
upstreamRes.on('end', () => {
|
|
204
|
+
const body = Buffer.concat(chunks);
|
|
205
|
+
const relayed = [`HTTP/1.1 ${upstreamRes.statusCode} ${upstreamRes.statusMessage ?? ''}`.trimEnd()];
|
|
206
|
+
for (const [name, value] of Object.entries(sanitizeResponseHeaders(upstreamRes.headers))) {
|
|
207
|
+
if (name === 'content-length')
|
|
208
|
+
continue;
|
|
209
|
+
if (value !== undefined)
|
|
210
|
+
relayed.push(`${name}: ${Array.isArray(value) ? value.join(', ') : value}`);
|
|
211
|
+
}
|
|
212
|
+
relayed.push(`content-length: ${body.length}`, 'connection: close', '');
|
|
213
|
+
try {
|
|
214
|
+
socket.end(relayed.join('\r\n') + '\r\n' + (body.length > 0 ? body : ''));
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
socket.destroy();
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
upstreamRes.on('error', () => socket.destroy());
|
|
221
|
+
});
|
|
222
|
+
// Private child down: clean 502-style close instead of a hang/crash.
|
|
223
|
+
upstream.on('error', () => {
|
|
224
|
+
writeJsonError(socket, 502, 'Bad Gateway', BAD_GATEWAY_BODY);
|
|
225
|
+
});
|
|
226
|
+
upstream.end();
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=ws-proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-proxy.js","sourceRoot":"","sources":["../../src/solo/ws-proxy.ts"],"names":[],"mappings":"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"}
|