@tapflowio/relay 0.22.0 → 0.24.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/RelayServer.d.ts +18 -0
- package/dist/RelayServer.d.ts.map +1 -1
- package/dist/RelayServer.js +128 -20
- package/dist/RelayServer.js.map +1 -1
- package/dist/SessionManager.d.ts +4 -1
- package/dist/SessionManager.d.ts.map +1 -1
- package/dist/SessionManager.js +7 -0
- package/dist/SessionManager.js.map +1 -1
- package/dist/api/auth.d.ts +1 -1
- package/dist/api/auth.d.ts.map +1 -1
- package/dist/api/auth.js +13 -4
- package/dist/api/auth.js.map +1 -1
- package/dist/api/builds.d.ts +1 -1
- package/dist/api/builds.d.ts.map +1 -1
- package/dist/api/builds.js +8 -2
- package/dist/api/builds.js.map +1 -1
- package/dist/api/passwordReset.d.ts +6 -2
- package/dist/api/passwordReset.d.ts.map +1 -1
- package/dist/api/passwordReset.js +21 -6
- package/dist/api/passwordReset.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/buildFiles.d.ts +23 -0
- package/dist/lib/buildFiles.d.ts.map +1 -0
- package/dist/lib/buildFiles.js +39 -0
- package/dist/lib/buildFiles.js.map +1 -0
- package/dist/lib/clientAddress.d.ts +15 -0
- package/dist/lib/clientAddress.d.ts.map +1 -1
- package/dist/lib/clientAddress.js +21 -0
- package/dist/lib/clientAddress.js.map +1 -1
- package/dist/lib/config.d.ts +28 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +87 -17
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/dataDir.d.ts +48 -1
- package/dist/lib/dataDir.d.ts.map +1 -1
- package/dist/lib/dataDir.js +108 -12
- package/dist/lib/dataDir.js.map +1 -1
- package/dist/lib/proxyConfig.d.ts +10 -0
- package/dist/lib/proxyConfig.d.ts.map +1 -1
- package/dist/lib/proxyConfig.js +14 -0
- package/dist/lib/proxyConfig.js.map +1 -1
- package/dist/middleware/auth.js +3 -3
- package/dist/middleware/auth.js.map +1 -1
- package/dist/server.js +5 -2
- package/dist/server.js.map +1 -1
- package/package.json +5 -5
- package/public/assets/AppCenter-BHz6vh5p.js +26 -0
- package/public/assets/AppCenter-BHz6vh5p.js.br +0 -0
- package/public/assets/AppCenter-BHz6vh5p.js.gz +0 -0
- package/public/assets/Default-bCqU0Qz9.js +1 -0
- package/public/assets/Default-bCqU0Qz9.js.br +0 -0
- package/public/assets/Default-bCqU0Qz9.js.gz +0 -0
- package/public/assets/Invite-EVMTsuNz.js +1 -0
- package/public/assets/Invite-EVMTsuNz.js.br +0 -0
- package/public/assets/Invite-EVMTsuNz.js.gz +0 -0
- package/public/assets/MacResources-fCn40Tf5.js +1 -0
- package/public/assets/MacResources-fCn40Tf5.js.br +0 -0
- package/public/assets/MacResources-fCn40Tf5.js.gz +0 -0
- package/public/assets/NotFound-wHOnT2Fl.js +1 -0
- package/public/assets/NotFound-wHOnT2Fl.js.br +0 -0
- package/public/assets/NotFound-wHOnT2Fl.js.gz +0 -0
- package/public/assets/QASession-CL-eyFxG.js +150 -0
- package/public/assets/QASession-CL-eyFxG.js.br +0 -0
- package/public/assets/QASession-CL-eyFxG.js.gz +0 -0
- package/public/assets/ResetPassword-D-SFg4ap.js +1 -0
- package/public/assets/ResetPassword-D-SFg4ap.js.br +0 -0
- package/public/assets/ResetPassword-D-SFg4ap.js.gz +0 -0
- package/public/assets/Setup-ULjvIfAi.js +1 -0
- package/public/assets/Setup-ULjvIfAi.js.br +0 -0
- package/public/assets/Setup-ULjvIfAi.js.gz +0 -0
- package/public/assets/Team-DH91GD4w.js +6 -0
- package/public/assets/Team-DH91GD4w.js.br +0 -0
- package/public/assets/Team-DH91GD4w.js.gz +0 -0
- package/public/assets/Tokens-DImLpOVZ.js +1 -0
- package/public/assets/Tokens-DImLpOVZ.js.br +0 -0
- package/public/assets/Tokens-DImLpOVZ.js.gz +0 -0
- package/public/assets/alert-dialog-CwcUyDZQ.js +1 -0
- package/public/assets/alert-dialog-CwcUyDZQ.js.br +0 -0
- package/public/assets/alert-dialog-CwcUyDZQ.js.gz +0 -0
- package/public/assets/badge-B4QAWrzA.js +1 -0
- package/public/assets/badge-B4QAWrzA.js.br +0 -0
- package/public/assets/badge-B4QAWrzA.js.gz +0 -0
- package/public/assets/build-format-CtmRXihN.js +11 -0
- package/public/assets/build-format-CtmRXihN.js.br +0 -0
- package/public/assets/build-format-CtmRXihN.js.gz +0 -0
- package/public/assets/dialog-BAFN_ONP.js +11 -0
- package/public/assets/dialog-BAFN_ONP.js.br +0 -0
- package/public/assets/dialog-BAFN_ONP.js.gz +0 -0
- package/public/assets/index-COOWQXpW.js +218 -0
- package/public/assets/index-COOWQXpW.js.br +0 -0
- package/public/assets/index-COOWQXpW.js.gz +0 -0
- package/public/assets/index-ozCdBYxk.css +1 -0
- package/public/assets/index-ozCdBYxk.css.br +0 -0
- package/public/assets/index-ozCdBYxk.css.gz +0 -0
- package/public/assets/list-view-Rrqp8TkG.js +1 -0
- package/public/assets/list-view-Rrqp8TkG.js.br +0 -0
- package/public/assets/list-view-Rrqp8TkG.js.gz +0 -0
- package/public/assets/pencil-CAHyK7Qf.js +6 -0
- package/public/assets/pencil-CAHyK7Qf.js.br +0 -0
- package/public/assets/pencil-CAHyK7Qf.js.gz +0 -0
- package/public/assets/resource-health-BRpSwarg.js +1 -0
- package/public/assets/resource-health-BRpSwarg.js.br +0 -0
- package/public/assets/resource-health-BRpSwarg.js.gz +0 -0
- package/public/assets/trash-CkO-5TRA.js +11 -0
- package/public/assets/trash-CkO-5TRA.js.br +0 -0
- package/public/assets/trash-CkO-5TRA.js.gz +0 -0
- package/public/assets/useFocusAfterSwap-BY3N9f9v.js +1 -0
- package/public/assets/useFocusAfterSwap-BY3N9f9v.js.br +0 -0
- package/public/assets/useFocusAfterSwap-BY3N9f9v.js.gz +0 -0
- package/public/assets/{vendor-charts-8kVeHLwY.js → vendor-charts-BykzUV2D.js} +1 -1
- package/public/assets/vendor-charts-BykzUV2D.js.br +0 -0
- package/public/assets/vendor-charts-BykzUV2D.js.gz +0 -0
- package/public/assets/vendor-react-BlPSzrEe.js +57 -0
- package/public/assets/vendor-react-BlPSzrEe.js.br +0 -0
- package/public/assets/vendor-react-BlPSzrEe.js.gz +0 -0
- package/public/assets/{vendor-router-BFibuab1.js → vendor-router-B_lVHrKl.js} +3 -3
- package/public/assets/vendor-router-B_lVHrKl.js.br +0 -0
- package/public/assets/vendor-router-B_lVHrKl.js.gz +0 -0
- package/public/index.html +4 -4
- package/public/index.html.br +0 -0
- package/public/index.html.gz +0 -0
- package/public/assets/AppCenter-gbsJscjW.js +0 -21
- package/public/assets/AppCenter-gbsJscjW.js.br +0 -0
- package/public/assets/AppCenter-gbsJscjW.js.gz +0 -0
- package/public/assets/Default-2rFTkbo9.js +0 -1
- package/public/assets/Default-2rFTkbo9.js.br +0 -0
- package/public/assets/Default-2rFTkbo9.js.gz +0 -0
- package/public/assets/Invite-aN4DXJyM.js +0 -1
- package/public/assets/Invite-aN4DXJyM.js.br +0 -0
- package/public/assets/Invite-aN4DXJyM.js.gz +0 -0
- package/public/assets/MacResources-D3IlesXU.js +0 -3
- package/public/assets/MacResources-D3IlesXU.js.br +0 -0
- package/public/assets/MacResources-D3IlesXU.js.gz +0 -0
- package/public/assets/NotFound-BoDDivK5.js +0 -1
- package/public/assets/NotFound-BoDDivK5.js.br +0 -0
- package/public/assets/NotFound-BoDDivK5.js.gz +0 -0
- package/public/assets/QASession-U29t9j0E.js +0 -140
- package/public/assets/QASession-U29t9j0E.js.br +0 -0
- package/public/assets/QASession-U29t9j0E.js.gz +0 -0
- package/public/assets/ResetPassword-DPc5kwQU.js +0 -1
- package/public/assets/ResetPassword-DPc5kwQU.js.br +0 -0
- package/public/assets/ResetPassword-DPc5kwQU.js.gz +0 -0
- package/public/assets/Setup-CwPx_e_O.js +0 -1
- package/public/assets/Setup-CwPx_e_O.js.br +0 -0
- package/public/assets/Setup-CwPx_e_O.js.gz +0 -0
- package/public/assets/Team-DD9li-7M.js +0 -6
- package/public/assets/Team-DD9li-7M.js.br +0 -0
- package/public/assets/Team-DD9li-7M.js.gz +0 -0
- package/public/assets/Tokens-DdMMke73.js +0 -1
- package/public/assets/Tokens-DdMMke73.js.br +0 -0
- package/public/assets/Tokens-DdMMke73.js.gz +0 -0
- package/public/assets/alert-dialog-DADd1jHN.js +0 -1
- package/public/assets/alert-dialog-DADd1jHN.js.br +0 -0
- package/public/assets/alert-dialog-DADd1jHN.js.gz +0 -0
- package/public/assets/badge-CMf8d5RJ.js +0 -1
- package/public/assets/badge-CMf8d5RJ.js.br +0 -0
- package/public/assets/badge-CMf8d5RJ.js.gz +0 -0
- package/public/assets/build-format-lp_8kSvq.js +0 -11
- package/public/assets/build-format-lp_8kSvq.js.br +0 -0
- package/public/assets/build-format-lp_8kSvq.js.gz +0 -0
- package/public/assets/dialog-pS8vHXXv.js +0 -11
- package/public/assets/dialog-pS8vHXXv.js.br +0 -0
- package/public/assets/dialog-pS8vHXXv.js.gz +0 -0
- package/public/assets/index-Bxs7CdEi.css +0 -1
- package/public/assets/index-Bxs7CdEi.css.br +0 -0
- package/public/assets/index-Bxs7CdEi.css.gz +0 -0
- package/public/assets/index-Cajf0T-5.js +0 -198
- package/public/assets/index-Cajf0T-5.js.br +0 -0
- package/public/assets/index-Cajf0T-5.js.gz +0 -0
- package/public/assets/pencil-LD1ga-Kk.js +0 -6
- package/public/assets/pencil-LD1ga-Kk.js.br +0 -0
- package/public/assets/pencil-LD1ga-Kk.js.gz +0 -0
- package/public/assets/resource-health-BScurILF.js +0 -1
- package/public/assets/resource-health-BScurILF.js.br +0 -0
- package/public/assets/resource-health-BScurILF.js.gz +0 -0
- package/public/assets/table-CL9AvoTp.js +0 -1
- package/public/assets/table-CL9AvoTp.js.br +0 -0
- package/public/assets/table-CL9AvoTp.js.gz +0 -0
- package/public/assets/trash-mvVp4nwq.js +0 -11
- package/public/assets/trash-mvVp4nwq.js.br +0 -0
- package/public/assets/trash-mvVp4nwq.js.gz +0 -0
- package/public/assets/vendor-charts-8kVeHLwY.js.br +0 -0
- package/public/assets/vendor-charts-8kVeHLwY.js.gz +0 -0
- package/public/assets/vendor-react-AJpf4YZ0.js +0 -49
- package/public/assets/vendor-react-AJpf4YZ0.js.br +0 -0
- package/public/assets/vendor-react-AJpf4YZ0.js.gz +0 -0
- package/public/assets/vendor-router-BFibuab1.js.br +0 -0
- package/public/assets/vendor-router-BFibuab1.js.gz +0 -0
package/dist/RelayServer.d.ts
CHANGED
|
@@ -30,6 +30,20 @@ export declare function ownerKeyFor(userId: number | string | undefined, claimed
|
|
|
30
30
|
export declare class RelayServer {
|
|
31
31
|
private readonly options;
|
|
32
32
|
private httpServer;
|
|
33
|
+
/**
|
|
34
|
+
* **The port tunnel clients are pointed at, and the only thing that tells their traffic apart.**
|
|
35
|
+
* rathole's client and `tailscale serve` run on this machine and connect from loopback on behalf of
|
|
36
|
+
* someone on the internet or the tailnet; rathole forwards raw TCP, so no header marks it either. On
|
|
37
|
+
* the main port such a socket is indistinguishable from `tapflow start`'s own agent, which is
|
|
38
|
+
* unauthenticated by design — so every tunnel visitor was too. Everything accepted here is remote
|
|
39
|
+
* (`resolveClientAddress`'s `viaTunnel`), whatever its address.
|
|
40
|
+
*
|
|
41
|
+
* Loopback-only, because only a tunnel client on this machine has any business here; a remote client
|
|
42
|
+
* belongs on the main port, where it is classified by address as before. Same handler, same
|
|
43
|
+
* `WebSocketServer`, same TLS material — rathole carried a TLS relay's own handshake through before
|
|
44
|
+
* this existed, and still does.
|
|
45
|
+
*/
|
|
46
|
+
private tunnelServer;
|
|
33
47
|
private wss;
|
|
34
48
|
private sessions;
|
|
35
49
|
private publicDir;
|
|
@@ -114,12 +128,16 @@ export declare class RelayServer {
|
|
|
114
128
|
};
|
|
115
129
|
agentGraceMs?: number;
|
|
116
130
|
tunnel?: TunnelRuntime;
|
|
131
|
+
tunnelPort?: number;
|
|
117
132
|
});
|
|
118
133
|
private registerRoutes;
|
|
119
134
|
pushLog(msg: string): void;
|
|
120
135
|
start(): Promise<void>;
|
|
136
|
+
private listenTunnel;
|
|
121
137
|
stop(): Promise<void>;
|
|
122
138
|
address(): string | import("net").AddressInfo | null;
|
|
139
|
+
/** The tunnel listener's address, or null when this relay was given no `tunnelPort`. */
|
|
140
|
+
tunnelAddress(): string | import("net").AddressInfo | null;
|
|
123
141
|
private runHeartbeat;
|
|
124
142
|
/**
|
|
125
143
|
* Has this socket answered recently enough to be treated as present?
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RelayServer.d.ts","sourceRoot":"","sources":["../src/RelayServer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RelayServer.d.ts","sourceRoot":"","sources":["../src/RelayServer.ts"],"names":[],"mappings":"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"}
|
package/dist/RelayServer.js
CHANGED
|
@@ -10,12 +10,13 @@ import { directionOf, parseInbound } from '@tapflowio/protocol/validate';
|
|
|
10
10
|
import { Router, json } from './router.js';
|
|
11
11
|
import { requireViewAuth, requireAuth, getAuth, verifyPat } from './middleware/auth.js';
|
|
12
12
|
import { classifyConnection } from './lib/connectionAuth.js';
|
|
13
|
-
import { resolveClientAddress } from './lib/clientAddress.js';
|
|
13
|
+
import { isTunnelIngress, markTunnelIngress, resolveClientAddress } from './lib/clientAddress.js';
|
|
14
14
|
import { BuildTicketStore } from './lib/buildTickets.js';
|
|
15
|
+
import { resolveBuildFile } from './lib/buildFiles.js';
|
|
15
16
|
import { resolveCorsHeaders } from './lib/cors.js';
|
|
16
17
|
import { isCsrfBlocked } from './lib/csrf.js';
|
|
17
18
|
import { pickLanAddress, runningInContainer } from './lib/lanAddress.js';
|
|
18
|
-
import { config } from './lib/config.js';
|
|
19
|
+
import { config, getJwtSecret } from './lib/config.js';
|
|
19
20
|
import { forTeammates, resolveAgentRelayUrl, resolvePublicBaseUrl } from './lib/publicUrl.js';
|
|
20
21
|
import { createTrailingRequester, systemTimerScheduler } from './lib/trailingRequester.js';
|
|
21
22
|
import { getDb } from './db.js';
|
|
@@ -156,6 +157,20 @@ function describeRejection(failure, suppressed) {
|
|
|
156
157
|
export class RelayServer {
|
|
157
158
|
options;
|
|
158
159
|
httpServer;
|
|
160
|
+
/**
|
|
161
|
+
* **The port tunnel clients are pointed at, and the only thing that tells their traffic apart.**
|
|
162
|
+
* rathole's client and `tailscale serve` run on this machine and connect from loopback on behalf of
|
|
163
|
+
* someone on the internet or the tailnet; rathole forwards raw TCP, so no header marks it either. On
|
|
164
|
+
* the main port such a socket is indistinguishable from `tapflow start`'s own agent, which is
|
|
165
|
+
* unauthenticated by design — so every tunnel visitor was too. Everything accepted here is remote
|
|
166
|
+
* (`resolveClientAddress`'s `viaTunnel`), whatever its address.
|
|
167
|
+
*
|
|
168
|
+
* Loopback-only, because only a tunnel client on this machine has any business here; a remote client
|
|
169
|
+
* belongs on the main port, where it is classified by address as before. Same handler, same
|
|
170
|
+
* `WebSocketServer`, same TLS material — rathole carried a TLS relay's own handshake through before
|
|
171
|
+
* this existed, and still does.
|
|
172
|
+
*/
|
|
173
|
+
tunnelServer = null;
|
|
159
174
|
wss;
|
|
160
175
|
sessions;
|
|
161
176
|
publicDir;
|
|
@@ -240,6 +255,10 @@ export class RelayServer {
|
|
|
240
255
|
pendingUITrees = new Map();
|
|
241
256
|
constructor(options) {
|
|
242
257
|
this.options = options;
|
|
258
|
+
// 0 on both sides is two ephemeral ports, which is what the tests ask for.
|
|
259
|
+
if (options.tunnelPort !== undefined && options.tunnelPort !== 0 && options.tunnelPort === options.port) {
|
|
260
|
+
throw new Error(`The tunnel port (${options.tunnelPort}) must differ from the relay port. Set TAPFLOW_TUNNEL_PORT to another port.`);
|
|
261
|
+
}
|
|
243
262
|
this.backpressureBytes = options.wsBackpressureBytes ?? DEFAULT_BACKPRESSURE_BYTES;
|
|
244
263
|
this.screenshotTimeoutMs = options.screenshotTimeoutMs ?? 10_000;
|
|
245
264
|
// Longer than the screenshot default: the Android agent's device-side dump
|
|
@@ -280,11 +299,29 @@ export class RelayServer {
|
|
|
280
299
|
this.wss = new WebSocketServer({ server: this.httpServer });
|
|
281
300
|
this.wss.on('connection', (ws, request) => this.handleConnection(ws, request));
|
|
282
301
|
this.wss.on('error', () => { });
|
|
302
|
+
if (options.tunnelPort !== undefined) {
|
|
303
|
+
// Marked before anything reads it: the router hands this same request object to `handleInit`, and
|
|
304
|
+
// `handleUpgrade` passes it on to the `connection` event.
|
|
305
|
+
const tunnelHandler = (req, res) => {
|
|
306
|
+
markTunnelIngress(req);
|
|
307
|
+
return handler(req, res);
|
|
308
|
+
};
|
|
309
|
+
const tunnelServer = options.tls
|
|
310
|
+
? https.createServer({ cert: options.tls.cert, key: options.tls.key }, tunnelHandler)
|
|
311
|
+
: http.createServer(tunnelHandler);
|
|
312
|
+
tunnelServer.on('connection', (socket) => socket.setNoDelay(true));
|
|
313
|
+
// `handleUpgrade` adds the socket to `wss.clients`, so the heartbeat and `stop()` cover it.
|
|
314
|
+
tunnelServer.on('upgrade', (req, socket, head) => {
|
|
315
|
+
markTunnelIngress(req);
|
|
316
|
+
this.wss.handleUpgrade(req, socket, head, (ws) => this.wss.emit('connection', ws, req));
|
|
317
|
+
});
|
|
318
|
+
this.tunnelServer = tunnelServer;
|
|
319
|
+
}
|
|
283
320
|
}
|
|
284
321
|
registerRoutes() {
|
|
285
322
|
const u = this.uploadsDir;
|
|
286
323
|
// auth
|
|
287
|
-
this.router.get('/api/v1/auth/status', handleAuthStatus);
|
|
324
|
+
this.router.get('/api/v1/auth/status', (req, res) => handleAuthStatus(req, res, this.options.trustedProxies ?? []));
|
|
288
325
|
this.router.post('/api/v1/auth/init', (req, res) => handleInit(req, res, this.options.trustedProxies ?? []));
|
|
289
326
|
this.router.get('/api/v1/auth/me', handleMe);
|
|
290
327
|
this.router.post('/api/v1/auth/login', (req, res) => handleLogin(req, res, this.options.trustedProxies ?? []));
|
|
@@ -327,7 +364,7 @@ export class RelayServer {
|
|
|
327
364
|
this.router.post('/api/v1/team/invite', (req, res) => handleInvite(req, res, this.options.tunnel));
|
|
328
365
|
this.router.patch('/api/v1/team/members/:id', handleUpdateMember);
|
|
329
366
|
this.router.delete('/api/v1/team/members/:id', handleDeleteMember);
|
|
330
|
-
this.router.post('/api/v1/team/members/:id/send-reset', handleSendMemberReset);
|
|
367
|
+
this.router.post('/api/v1/team/members/:id/send-reset', (req, res, params) => handleSendMemberReset(req, res, params, this.options.tunnel));
|
|
331
368
|
// tokens
|
|
332
369
|
this.router.get('/api/v1/tokens', handleListTokens);
|
|
333
370
|
this.router.post('/api/v1/tokens', handleCreateToken);
|
|
@@ -380,6 +417,9 @@ export class RelayServer {
|
|
|
380
417
|
logger.info(msg);
|
|
381
418
|
}
|
|
382
419
|
start() {
|
|
420
|
+
// Creates this install's JWT secret if it has none. Here rather than at import, so commands
|
|
421
|
+
// that never run a relay stop leaving one behind — and a write failure is still a boot failure.
|
|
422
|
+
getJwtSecret();
|
|
383
423
|
purgeExpiredRecordings(this.recordingsDir);
|
|
384
424
|
this.purgeRecordingsTimer = setInterval(() => purgeExpiredRecordings(this.recordingsDir), 24 * 60 * 60 * 1000);
|
|
385
425
|
this.purgeRecordingsTimer.unref();
|
|
@@ -389,8 +429,8 @@ export class RelayServer {
|
|
|
389
429
|
purgeOldResources();
|
|
390
430
|
this.purgeOldResourcesTimer = setInterval(purgeOldResources, 24 * 60 * 60 * 1000);
|
|
391
431
|
this.purgeOldResourcesTimer.unref();
|
|
392
|
-
purgeExpiredBuilds(this.recordingsDir);
|
|
393
|
-
this.purgeBuildsTimer = setInterval(() => purgeExpiredBuilds(this.recordingsDir), 24 * 60 * 60 * 1000);
|
|
432
|
+
purgeExpiredBuilds(this.recordingsDir, this.uploadsDir);
|
|
433
|
+
this.purgeBuildsTimer = setInterval(() => purgeExpiredBuilds(this.recordingsDir, this.uploadsDir), 24 * 60 * 60 * 1000);
|
|
394
434
|
this.purgeBuildsTimer.unref();
|
|
395
435
|
this.flushResourcesTimer = setInterval(() => this.flushResourceBuffers(), 60_000);
|
|
396
436
|
this.flushResourcesTimer.unref();
|
|
@@ -407,7 +447,30 @@ export class RelayServer {
|
|
|
407
447
|
});
|
|
408
448
|
// Bind dual-stack (IPv4 + IPv6). A bare listen(port) binds IPv6-only on some
|
|
409
449
|
// macOS/node setups, so LAN agents connecting over IPv4 (ws://<ipv4>:port) time out.
|
|
410
|
-
this.httpServer.listen({ port: this.options.port, host: '::', ipv6Only: false }, resolve);
|
|
450
|
+
this.httpServer.listen({ port: this.options.port, host: '::', ipv6Only: false }, () => resolve());
|
|
451
|
+
}).then(() => this.listenTunnel());
|
|
452
|
+
}
|
|
453
|
+
listenTunnel() {
|
|
454
|
+
const tunnelServer = this.tunnelServer;
|
|
455
|
+
if (!tunnelServer)
|
|
456
|
+
return Promise.resolve();
|
|
457
|
+
const port = this.options.tunnelPort ?? 0;
|
|
458
|
+
return new Promise((resolve, reject) => {
|
|
459
|
+
tunnelServer.once('error', (err) => reject(err.code === 'EADDRINUSE'
|
|
460
|
+
? new Error(`Tunnel port ${port} is already in use. Stop the process holding it, or set TAPFLOW_TUNNEL_PORT to a free port.`)
|
|
461
|
+
: err));
|
|
462
|
+
// 127.0.0.1, not `localhost`: rathole's client dials that address literally, and `tailscale serve
|
|
463
|
+
// <port>` proxies to it.
|
|
464
|
+
tunnelServer.listen({ port, host: '127.0.0.1' }, () => {
|
|
465
|
+
// The main server's later errors reach `wss`, which swallows them; nothing forwards this one's.
|
|
466
|
+
tunnelServer.on('error', (err) => logger.error(`tunnel listener error: ${String(err)}`));
|
|
467
|
+
resolve();
|
|
468
|
+
});
|
|
469
|
+
}).catch(async (err) => {
|
|
470
|
+
// A relay whose tunnel port failed must not keep serving on the main one: the caller treats this
|
|
471
|
+
// as a failed start and moves on, and the port would stay held by a server nobody stops.
|
|
472
|
+
await this.stop().catch(() => { });
|
|
473
|
+
throw err;
|
|
411
474
|
});
|
|
412
475
|
}
|
|
413
476
|
stop() {
|
|
@@ -445,16 +508,27 @@ export class RelayServer {
|
|
|
445
508
|
for (const requester of this.networkStateRequesters.values())
|
|
446
509
|
requester.dispose();
|
|
447
510
|
this.networkStateRequesters.clear();
|
|
448
|
-
|
|
511
|
+
const closeTunnel = new Promise((resolve, reject) => {
|
|
512
|
+
// Not listening means `start()` never reached it, or its listen failed; `close()` would reject.
|
|
513
|
+
if (!this.tunnelServer?.listening)
|
|
514
|
+
return resolve();
|
|
515
|
+
this.tunnelServer.close((err) => (err ? reject(err) : resolve()));
|
|
516
|
+
});
|
|
517
|
+
const closeMain = new Promise((resolve, reject) => {
|
|
449
518
|
this.wss.clients.forEach((ws) => ws.terminate());
|
|
450
519
|
this.wss.close(() => {
|
|
451
520
|
this.httpServer.close((err) => (err ? reject(err) : resolve()));
|
|
452
521
|
});
|
|
453
522
|
});
|
|
523
|
+
return Promise.all([closeMain, closeTunnel]).then(() => undefined);
|
|
454
524
|
}
|
|
455
525
|
address() {
|
|
456
526
|
return this.httpServer.address();
|
|
457
527
|
}
|
|
528
|
+
/** The tunnel listener's address, or null when this relay was given no `tunnelPort`. */
|
|
529
|
+
tunnelAddress() {
|
|
530
|
+
return this.tunnelServer?.address() ?? null;
|
|
531
|
+
}
|
|
458
532
|
// Terminate sockets that missed the previous pong; ping the rest. Covers all roles via wss.clients.
|
|
459
533
|
runHeartbeat(clients = this.wss.clients) {
|
|
460
534
|
for (const ws of clients) {
|
|
@@ -488,8 +562,10 @@ export class RelayServer {
|
|
|
488
562
|
}
|
|
489
563
|
// 갱신된 cert를 재시작 없이 핫스왑한다(https 종단일 때만 의미 있음).
|
|
490
564
|
updateTlsContext(material) {
|
|
491
|
-
|
|
492
|
-
|
|
565
|
+
for (const server of [this.httpServer, this.tunnelServer]) {
|
|
566
|
+
if (server instanceof https.Server) {
|
|
567
|
+
server.setSecureContext({ cert: material.cert, key: material.key });
|
|
568
|
+
}
|
|
493
569
|
}
|
|
494
570
|
}
|
|
495
571
|
/**
|
|
@@ -604,8 +680,9 @@ export class RelayServer {
|
|
|
604
680
|
const a = socketAddr.replace(/^::ffff:/, '');
|
|
605
681
|
if (a === '::1' || a.startsWith('127.')) {
|
|
606
682
|
logger.warn('Received X-Forwarded-For from a loopback connection but TAPFLOW_TRUSTED_PROXIES is unset. ' +
|
|
607
|
-
'If
|
|
608
|
-
'
|
|
683
|
+
'If a same-host reverse proxy or `tailscale serve` forwards to this port, every client it forwards is ' +
|
|
684
|
+
'treated as localhost (unauthenticated). Point it at the tunnel port instead (TAPFLOW_TUNNEL_PORT, ' +
|
|
685
|
+
'default 4001), or set TAPFLOW_TRUSTED_PROXIES so the real client IP is used.');
|
|
609
686
|
this.warnedProxyMisconfig = true;
|
|
610
687
|
}
|
|
611
688
|
}
|
|
@@ -613,11 +690,15 @@ export class RelayServer {
|
|
|
613
690
|
const socketAddr = this.remoteAddressOf(request);
|
|
614
691
|
const xff = request.headers['x-forwarded-for'];
|
|
615
692
|
const forwardedFor = Array.isArray(xff) ? xff[0] : xff;
|
|
616
|
-
|
|
693
|
+
const viaTunnel = isTunnelIngress(request);
|
|
694
|
+
// A proxy in front of the tunnel port adds the header too, and there it changes nothing about auth.
|
|
695
|
+
if (!viaTunnel)
|
|
696
|
+
this.warnProxyMisconfigOnce(socketAddr, forwardedFor);
|
|
617
697
|
const { addr, isLocal } = resolveClientAddress({
|
|
618
698
|
socketAddr,
|
|
619
699
|
forwardedFor,
|
|
620
700
|
trustedProxies: this.options.trustedProxies ?? [],
|
|
701
|
+
viaTunnel,
|
|
621
702
|
});
|
|
622
703
|
const hasCookieAuth = getAuth(request) !== null;
|
|
623
704
|
// DB lookup — only when the connection can't be classified without it (remote, no cookie).
|
|
@@ -964,15 +1045,32 @@ export class RelayServer {
|
|
|
964
1045
|
// two-member union** while `AgentRegister.platform` is `string`, open so that a third-party
|
|
965
1046
|
// platform can register through `AgentRegistry.register()` (root AGENTS.md, OCP). Validating
|
|
966
1047
|
// this would refuse a platform the repo promises to support — and refusing costs more than a
|
|
967
|
-
// dropped frame
|
|
968
|
-
// empties what the re-join replay reads.
|
|
969
|
-
//
|
|
1048
|
+
// dropped frame: skipping `setChromeData` also
|
|
1049
|
+
// empties what the re-join replay reads. It is no longer true that this arrives once per boot,
|
|
1050
|
+
// either — a foldable re-sends it when the screen changes under the session — so the cost of
|
|
1051
|
+
// a refusal is a viewer stuck with the previous screen's bezel rather than one frame.
|
|
1052
|
+
// The relay never looks inside; the viewer does, and it is where the variants are already
|
|
1053
|
+
// told apart.
|
|
970
1054
|
this.sessions.setChromeData(session.id, raw['payload']);
|
|
971
1055
|
if (session.browserSocket?.readyState === WebSocket.OPEN) {
|
|
972
1056
|
session.browserSocket.send(JSON.stringify(raw));
|
|
973
1057
|
}
|
|
974
1058
|
break;
|
|
975
1059
|
}
|
|
1060
|
+
case 'device:postures': {
|
|
1061
|
+
const session = this.sessions.get(msg.sessionId);
|
|
1062
|
+
if (!session)
|
|
1063
|
+
break;
|
|
1064
|
+
// Cached for the same reason `session:chrome` is: a viewer that re-joins (a tab reload, a
|
|
1065
|
+
// socket blip) gets the session's state replayed, and without this the posture control would
|
|
1066
|
+
// simply be missing until the device was rebooted — with the picture still fine, which is
|
|
1067
|
+
// the hardest kind of gap to diagnose.
|
|
1068
|
+
this.sessions.setPostures(session.id, raw['payload']);
|
|
1069
|
+
if (session.browserSocket?.readyState === WebSocket.OPEN) {
|
|
1070
|
+
session.browserSocket.send(JSON.stringify(raw));
|
|
1071
|
+
}
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
976
1074
|
case 'session:deviceInfo': {
|
|
977
1075
|
const session = this.sessions.get(msg.sessionId);
|
|
978
1076
|
if (!session)
|
|
@@ -1228,6 +1326,7 @@ export class RelayServer {
|
|
|
1228
1326
|
case 'input:pinch:start':
|
|
1229
1327
|
case 'input:pinch:move':
|
|
1230
1328
|
case 'input:rotate':
|
|
1329
|
+
case 'input:posture':
|
|
1231
1330
|
case 'input:keyboard:toggle': {
|
|
1232
1331
|
// No ack, so no correlator to check and nothing to answer. An unowned frame is dropped rather
|
|
1233
1332
|
// than refused for the same reason: there is no waiter to tell.
|
|
@@ -1627,6 +1726,9 @@ export class RelayServer {
|
|
|
1627
1726
|
if (session.deviceInfo) {
|
|
1628
1727
|
this.sendTo(ws, { type: 'session:deviceInfo', sessionId: session.id, payload: session.deviceInfo });
|
|
1629
1728
|
}
|
|
1729
|
+
if (session.postures) {
|
|
1730
|
+
this.sendTo(ws, { type: 'device:postures', sessionId: session.id, payload: session.postures });
|
|
1731
|
+
}
|
|
1630
1732
|
// Replay device:ready only if this session actually announced one (browser WS blip reconnect).
|
|
1631
1733
|
// Not `deviceStatus`: that starts from the agent's `simctl list` snapshot, so a session for a
|
|
1632
1734
|
// simulator that was already running would fire this before the agent had done anything —
|
|
@@ -1986,9 +2088,15 @@ export class RelayServer {
|
|
|
1986
2088
|
// A missing file is answered rather than thrown for the same reason the lookup above is: the
|
|
1987
2089
|
// build row outlives its file whenever a bind mount goes away or a purge half-ran, and an
|
|
1988
2090
|
// unanswered install is a spinner that never stops and a caller that times out with no cause.
|
|
2091
|
+
//
|
|
2092
|
+
// Resolved rather than read verbatim: the stored path names wherever the data directory was at
|
|
2093
|
+
// upload time, and a migrated, moved or restored install keeps the file under a different one.
|
|
2094
|
+
const filePath = resolveBuildFile(build.file_path, this.uploadsDir);
|
|
1989
2095
|
let bytes;
|
|
1990
2096
|
try {
|
|
1991
|
-
|
|
2097
|
+
if (filePath === null)
|
|
2098
|
+
throw new Error('not found');
|
|
2099
|
+
bytes = fs.statSync(filePath).size;
|
|
1992
2100
|
}
|
|
1993
2101
|
catch {
|
|
1994
2102
|
return fail('The relay cannot read this build file. It may have been deleted — re-upload the build.');
|
|
@@ -1999,10 +2107,10 @@ export class RelayServer {
|
|
|
1999
2107
|
requestId,
|
|
2000
2108
|
payload: {
|
|
2001
2109
|
// Still sent, and sent first: an agent that predates `build-download` reads only this.
|
|
2002
|
-
filePath
|
|
2110
|
+
filePath,
|
|
2003
2111
|
bundleId: build.bundle_id,
|
|
2004
|
-
buildTicket: this.buildTickets.mint(msg.buildId,
|
|
2005
|
-
buildName: path.basename(
|
|
2112
|
+
buildTicket: this.buildTickets.mint(msg.buildId, filePath),
|
|
2113
|
+
buildName: path.basename(filePath),
|
|
2006
2114
|
buildBytes: bytes,
|
|
2007
2115
|
},
|
|
2008
2116
|
});
|