@dev-anywhere/relay 0.9.1 → 0.9.3
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/assets/web/assets/{fvad-DreiJBJr.js → fvad-okuu-1HD.js} +1 -1
- package/assets/web/assets/{index-Buv7a7PI.js → index-BDBa51VR.js} +184 -184
- package/assets/web/assets/index-BVDmlZok.css +41 -0
- package/assets/web/assets/{jmuxer.min-BMedIzGm.js → jmuxer.min-CqkQ8MXd.js} +1 -1
- package/assets/web/index.html +2 -2
- package/assets/web/sw.js +1 -1
- package/dist/{chunk-JLLMBFAB.js → chunk-PMXR723R.js} +216 -29
- package/dist/chunk-PMXR723R.js.map +1 -0
- package/dist/client-registration-admission.d.ts +14 -0
- package/dist/client-registration-admission.d.ts.map +1 -0
- package/dist/handlers/client.d.ts +1 -1
- package/dist/handlers/client.d.ts.map +1 -1
- package/dist/handlers/proxy.d.ts +1 -1
- package/dist/handlers/proxy.d.ts.map +1 -1
- package/dist/health.d.ts.map +1 -1
- package/dist/index.js +551 -51
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +3 -3
- package/assets/web/assets/index-CTJCtPHc.css +0 -41
- package/dist/chunk-JLLMBFAB.js.map +0 -1
package/assets/web/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const
|
|
1
|
+
if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(s[o])return;let t={};const l=e=>i(e,o),c={module:{uri:o},exports:t,require:l};s[o]=Promise.all(n.map(e=>c[e]||l(e))).then(e=>(r(...e),t))}}define(["./workbox-10a3128e"],function(e){"use strict";importScripts("notification-sw.js"),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"notification-sw.js",revision:"ee0be62f4c1f36625b92680b3060becb"},{url:"index.html",revision:"bb20b4ecbecb601d7f08075e68281105"},{url:"assets/jmuxer.min-CqkQ8MXd.js",revision:null},{url:"assets/index-BVDmlZok.css",revision:null},{url:"assets/index-BDBa51VR.js",revision:null},{url:"assets/fvad-okuu-1HD.js",revision:null},{url:"assets/fvad-DYxbW2my.wasm",revision:null},{url:"assets/__vite-browser-external-D7Ct-6yo.js",revision:null},{url:"brand-icon.svg",revision:"efe8505a7b7ef12cbb3a4bcd286254bd"},{url:"maskable-icon-512x512.png",revision:"e762ff07cdedcd78f8650f59c6881727"},{url:"pwa-192x192.png",revision:"4e4603ce0f22528eded553ba7f2ffbc5"},{url:"pwa-512x512.png",revision:"e762ff07cdedcd78f8650f59c6881727"},{url:"pwa-64x64.png",revision:"d322160ad1dc08ba9e133cb3d296ae63"},{url:"manifest.webmanifest",revision:"4daa295c44052f4ce59a1fcfd6f06ab8"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/proxy(\/|$|\?)/,/^\/client(\/|$|\?)/,/^\/voice(\/|$|\?)/,/^\/fonts\//,/^\/health/,/^\/status/,/^\/api\//]})),e.registerRoute(/^\/fonts\/.*\.(woff2|woff|ttf|otf|css)$/,new e.CacheFirst({cacheName:"dev-anywhere-fonts",plugins:[new e.ExpirationPlugin({maxEntries:40,maxAgeSeconds:15552e3})]}),"GET")});
|
|
@@ -130,7 +130,7 @@ function createSessionIdentitySchema(commonFields) {
|
|
|
130
130
|
kind: z4.literal("terminal"),
|
|
131
131
|
mode: z4.literal("pty"),
|
|
132
132
|
provider: z4.literal("claude"),
|
|
133
|
-
ptyOwner: z4.literal("
|
|
133
|
+
ptyOwner: z4.literal("proxy-hosted")
|
|
134
134
|
}).strict()
|
|
135
135
|
]);
|
|
136
136
|
}
|
|
@@ -489,7 +489,7 @@ var ProxyUpgradeBootstrapResponseSchema = z9.object({
|
|
|
489
489
|
bootstrapVersion: z9.literal(PROXY_UPGRADE_BOOTSTRAP_VERSION),
|
|
490
490
|
relayVersion: StableVersionSchema,
|
|
491
491
|
controlProtocolVersion: z9.number().int().positive()
|
|
492
|
-
})
|
|
492
|
+
});
|
|
493
493
|
|
|
494
494
|
// ../../packages/shared/dist/schemas/web-preview.js
|
|
495
495
|
import { z as z10 } from "zod";
|
|
@@ -1446,7 +1446,7 @@ var SessionCreateSuccessResponseSchema = z13.discriminatedUnion("mode", [
|
|
|
1446
1446
|
kind: z13.literal("terminal"),
|
|
1447
1447
|
mode: z13.literal("pty"),
|
|
1448
1448
|
provider: z13.literal("claude"),
|
|
1449
|
-
ptyOwner: z13.literal("
|
|
1449
|
+
ptyOwner: z13.literal("proxy-hosted")
|
|
1450
1450
|
}).strict()
|
|
1451
1451
|
])
|
|
1452
1452
|
]);
|
|
@@ -2109,6 +2109,29 @@ var RelayCloseCode = {
|
|
|
2109
2109
|
DEVICE_STREAM_BINDING_REJECTED: 4404,
|
|
2110
2110
|
PROXY_PROTOCOL_REJECTED: 4405
|
|
2111
2111
|
};
|
|
2112
|
+
var RelayProtocolRejectReason = {
|
|
2113
|
+
PAGE_OUTDATED: "page_outdated",
|
|
2114
|
+
SERVICE_OUTDATED: "service_outdated",
|
|
2115
|
+
PROTOCOL_MISMATCH: "protocol_mismatch"
|
|
2116
|
+
};
|
|
2117
|
+
var ProxyProtocolAdmissionDirection = {
|
|
2118
|
+
COMPATIBLE: "compatible",
|
|
2119
|
+
PROXY_OUTDATED: "proxy_outdated",
|
|
2120
|
+
RELAY_OUTDATED: "relay_outdated",
|
|
2121
|
+
PROTOCOL_MISMATCH: "protocol_mismatch"
|
|
2122
|
+
};
|
|
2123
|
+
function isPositiveSafeProtocolVersion(value) {
|
|
2124
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
2125
|
+
}
|
|
2126
|
+
function compareProxyRelayProtocolVersions(proxyProtocolVersion, relayProtocolVersion) {
|
|
2127
|
+
if (!isPositiveSafeProtocolVersion(proxyProtocolVersion) || !isPositiveSafeProtocolVersion(relayProtocolVersion)) {
|
|
2128
|
+
return ProxyProtocolAdmissionDirection.PROTOCOL_MISMATCH;
|
|
2129
|
+
}
|
|
2130
|
+
if (proxyProtocolVersion === relayProtocolVersion) {
|
|
2131
|
+
return ProxyProtocolAdmissionDirection.COMPATIBLE;
|
|
2132
|
+
}
|
|
2133
|
+
return proxyProtocolVersion < relayProtocolVersion ? ProxyProtocolAdmissionDirection.PROXY_OUTDATED : ProxyProtocolAdmissionDirection.RELAY_OUTDATED;
|
|
2134
|
+
}
|
|
2112
2135
|
|
|
2113
2136
|
// ../../packages/shared/dist/constants/wire-limits.js
|
|
2114
2137
|
var RELAY_JSON_MESSAGE_MAX_BYTES = 8 * 1024 * 1024;
|
|
@@ -2628,8 +2651,8 @@ var RelayRegistry = class {
|
|
|
2628
2651
|
}
|
|
2629
2652
|
getAllClientWs() {
|
|
2630
2653
|
const clients = [];
|
|
2631
|
-
for (const ws of this.connectedClients
|
|
2632
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
2654
|
+
for (const [ws, metadata] of this.connectedClients) {
|
|
2655
|
+
if (metadata.clientId && ws.readyState === WebSocket.OPEN) {
|
|
2633
2656
|
clients.push(ws);
|
|
2634
2657
|
}
|
|
2635
2658
|
}
|
|
@@ -2718,9 +2741,11 @@ function healthRouter(registry, options = {}) {
|
|
|
2718
2741
|
const proxyTokenRequired = options.proxyTokenRequired ?? false;
|
|
2719
2742
|
const clientTokenRequired = options.clientTokenRequired ?? false;
|
|
2720
2743
|
router.get("/health", (_req, res) => {
|
|
2744
|
+
res.setHeader("Cache-Control", "no-store");
|
|
2721
2745
|
res.json({
|
|
2722
2746
|
status: "ok",
|
|
2723
2747
|
version: RELAY_VERSION,
|
|
2748
|
+
controlProtocolVersion: RELAY_CONTROL_PROTOCOL_VERSION,
|
|
2724
2749
|
uptime: process.uptime(),
|
|
2725
2750
|
auth: {
|
|
2726
2751
|
proxyTokenRequired,
|
|
@@ -3223,6 +3248,8 @@ var PREVIEW_PUSH_TYPES = /* @__PURE__ */ new Set([
|
|
|
3223
3248
|
"device_preview_state_push",
|
|
3224
3249
|
"device_preview_removed_push"
|
|
3225
3250
|
]);
|
|
3251
|
+
var DEFAULT_PROXY_ADMISSION_TIMEOUT_MS = 1e4;
|
|
3252
|
+
var RETRYABLE_PROXY_ADMISSION_TIMEOUT_CLOSE_CODE = 1013;
|
|
3226
3253
|
function isPreviewEventMessage(message) {
|
|
3227
3254
|
return PREVIEW_EVENT_TYPES.has(message.type);
|
|
3228
3255
|
}
|
|
@@ -3303,28 +3330,60 @@ function rejectNotRegistered(ws) {
|
|
|
3303
3330
|
})
|
|
3304
3331
|
);
|
|
3305
3332
|
}
|
|
3306
|
-
function closeRejectedProxyProtocol(ws) {
|
|
3307
|
-
ws.
|
|
3333
|
+
function closeRejectedProxyProtocol(ws, direction = ProxyProtocolAdmissionDirection.PROTOCOL_MISMATCH) {
|
|
3334
|
+
if (ws.admissionPhase === "rejected") return;
|
|
3335
|
+
ws.admissionPhase = "rejected";
|
|
3336
|
+
ws.close(RelayCloseCode.PROXY_PROTOCOL_REJECTED, direction);
|
|
3337
|
+
}
|
|
3338
|
+
function classifyInitialProxyProtocol(raw) {
|
|
3339
|
+
let value;
|
|
3340
|
+
try {
|
|
3341
|
+
value = JSON.parse(raw);
|
|
3342
|
+
} catch {
|
|
3343
|
+
return ProxyProtocolAdmissionDirection.PROTOCOL_MISMATCH;
|
|
3344
|
+
}
|
|
3345
|
+
if (!value || typeof value !== "object" || value.type !== "proxy_register") {
|
|
3346
|
+
return ProxyProtocolAdmissionDirection.PROTOCOL_MISMATCH;
|
|
3347
|
+
}
|
|
3348
|
+
const direction = compareProxyRelayProtocolVersions(
|
|
3349
|
+
value.protocolVersion,
|
|
3350
|
+
RELAY_CONTROL_PROTOCOL_VERSION
|
|
3351
|
+
);
|
|
3352
|
+
return direction === ProxyProtocolAdmissionDirection.COMPATIBLE ? null : direction;
|
|
3308
3353
|
}
|
|
3309
3354
|
function isCurrentProxySocket(ws, registry) {
|
|
3310
3355
|
return ws.proxyId !== void 0 && registry.getProxy(ws.proxyId) === ws;
|
|
3311
3356
|
}
|
|
3312
|
-
function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, sessionHistoryRoutes, webPreviewRoutes, devicePreviewBridge, chaos, remoteFileBridge) {
|
|
3357
|
+
function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, sessionHistoryRoutes, webPreviewRoutes, devicePreviewBridge, chaos, remoteFileBridge, proxyAdmissionTimeoutMs = DEFAULT_PROXY_ADMISSION_TIMEOUT_MS) {
|
|
3313
3358
|
const proxyWs = ws;
|
|
3314
3359
|
proxyWs.isAlive = true;
|
|
3360
|
+
proxyWs.admissionPhase = "awaiting";
|
|
3315
3361
|
let registrationCompleted = false;
|
|
3316
|
-
let upgradeBootstrapStarted = false;
|
|
3317
3362
|
let initialFrameReceived = false;
|
|
3363
|
+
const effectiveAdmissionTimeoutMs = Number.isSafeInteger(proxyAdmissionTimeoutMs) && proxyAdmissionTimeoutMs > 0 ? proxyAdmissionTimeoutMs : DEFAULT_PROXY_ADMISSION_TIMEOUT_MS;
|
|
3364
|
+
let admissionTimeout = setTimeout(() => {
|
|
3365
|
+
admissionTimeout = null;
|
|
3366
|
+
if (proxyWs.admissionPhase !== "awaiting" || proxyWs.readyState !== WebSocket4.OPEN) return;
|
|
3367
|
+
proxyWs.admissionPhase = "rejected";
|
|
3368
|
+
logger.warn({ timeoutMs: effectiveAdmissionTimeoutMs }, "Proxy registration timed out");
|
|
3369
|
+
proxyWs.close(RETRYABLE_PROXY_ADMISSION_TIMEOUT_CLOSE_CODE, "proxy registration timeout");
|
|
3370
|
+
}, effectiveAdmissionTimeoutMs);
|
|
3371
|
+
admissionTimeout.unref?.();
|
|
3372
|
+
const clearAdmissionTimeout = () => {
|
|
3373
|
+
if (!admissionTimeout) return;
|
|
3374
|
+
clearTimeout(admissionTimeout);
|
|
3375
|
+
admissionTimeout = null;
|
|
3376
|
+
};
|
|
3318
3377
|
proxyWs.on("pong", () => {
|
|
3319
3378
|
proxyWs.isAlive = true;
|
|
3320
3379
|
});
|
|
3321
3380
|
proxyWs.on("message", (data, isBinary) => {
|
|
3322
|
-
if (
|
|
3381
|
+
if (proxyWs.admissionPhase === "quarantined" || proxyWs.admissionPhase === "rejected") return;
|
|
3323
3382
|
const isInitialFrame = !initialFrameReceived;
|
|
3324
3383
|
initialFrameReceived = true;
|
|
3325
3384
|
if (isBinary) {
|
|
3326
3385
|
if (!proxyWs.proxyId) {
|
|
3327
|
-
closeRejectedProxyProtocol(proxyWs);
|
|
3386
|
+
closeRejectedProxyProtocol(proxyWs, ProxyProtocolAdmissionDirection.PROTOCOL_MISMATCH);
|
|
3328
3387
|
return;
|
|
3329
3388
|
}
|
|
3330
3389
|
if (!isCurrentProxySocket(proxyWs, registry)) {
|
|
@@ -3366,13 +3425,16 @@ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, sessionH
|
|
|
3366
3425
|
{ size: data.length, proxyId: proxyWs.proxyId },
|
|
3367
3426
|
"JSON message rejected: exceeds max size"
|
|
3368
3427
|
);
|
|
3369
|
-
if (!proxyWs.proxyId)
|
|
3428
|
+
if (!proxyWs.proxyId) {
|
|
3429
|
+
closeRejectedProxyProtocol(proxyWs, ProxyProtocolAdmissionDirection.PROTOCOL_MISMATCH);
|
|
3430
|
+
}
|
|
3370
3431
|
return;
|
|
3371
3432
|
}
|
|
3372
3433
|
const raw = data.toString();
|
|
3373
3434
|
const upgradeBootstrap = isInitialFrame ? parseProxyUpgradeBootstrapRequest(raw, RELAY_VERSION) : null;
|
|
3374
3435
|
if (upgradeBootstrap) {
|
|
3375
|
-
|
|
3436
|
+
proxyWs.admissionPhase = "quarantined";
|
|
3437
|
+
clearAdmissionTimeout();
|
|
3376
3438
|
logger.info(
|
|
3377
3439
|
{
|
|
3378
3440
|
proxyId: upgradeBootstrap.proxyId,
|
|
@@ -3384,6 +3446,14 @@ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, sessionH
|
|
|
3384
3446
|
sendProxyUpgradeBootstrapResponse(proxyWs, RELAY_VERSION);
|
|
3385
3447
|
return;
|
|
3386
3448
|
}
|
|
3449
|
+
if (isInitialFrame) {
|
|
3450
|
+
const rejection = classifyInitialProxyProtocol(raw);
|
|
3451
|
+
if (rejection) {
|
|
3452
|
+
logger.warn({ direction: rejection }, "Proxy control protocol rejected before admission");
|
|
3453
|
+
closeRejectedProxyProtocol(proxyWs, rejection);
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3387
3457
|
const result = parseMessage(raw);
|
|
3388
3458
|
if (result.kind === "control" && result.message.type === "proxy_register") {
|
|
3389
3459
|
if (registrationCompleted) {
|
|
@@ -3391,10 +3461,12 @@ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, sessionH
|
|
|
3391
3461
|
{ proxyId: proxyWs.proxyId, requestedProxyId: result.message.proxyId },
|
|
3392
3462
|
"Repeated Proxy registration on the same socket rejected"
|
|
3393
3463
|
);
|
|
3394
|
-
closeRejectedProxyProtocol(proxyWs);
|
|
3464
|
+
closeRejectedProxyProtocol(proxyWs, ProxyProtocolAdmissionDirection.PROTOCOL_MISMATCH);
|
|
3395
3465
|
return;
|
|
3396
3466
|
}
|
|
3397
3467
|
const { proxyId, name, proxyVersion } = result.message;
|
|
3468
|
+
proxyWs.admissionPhase = "ready";
|
|
3469
|
+
clearAdmissionTimeout();
|
|
3398
3470
|
const status = registry.registerProxy(proxyId, proxyWs, proxyVersion, name);
|
|
3399
3471
|
proxyWs.proxyId = proxyId;
|
|
3400
3472
|
registrationCompleted = true;
|
|
@@ -3421,8 +3493,7 @@ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, sessionH
|
|
|
3421
3493
|
return;
|
|
3422
3494
|
}
|
|
3423
3495
|
if (!proxyWs.proxyId) {
|
|
3424
|
-
|
|
3425
|
-
closeRejectedProxyProtocol(proxyWs);
|
|
3496
|
+
closeRejectedProxyProtocol(proxyWs, ProxyProtocolAdmissionDirection.PROTOCOL_MISMATCH);
|
|
3426
3497
|
return;
|
|
3427
3498
|
}
|
|
3428
3499
|
if (!isCurrentProxySocket(proxyWs, registry)) {
|
|
@@ -3722,6 +3793,7 @@ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, sessionH
|
|
|
3722
3793
|
}
|
|
3723
3794
|
});
|
|
3724
3795
|
proxyWs.on("close", (code, reason) => {
|
|
3796
|
+
clearAdmissionTimeout();
|
|
3725
3797
|
if (!proxyWs.proxyId) return;
|
|
3726
3798
|
const closeMeta = { code, reason: reason.toString() || void 0 };
|
|
3727
3799
|
const current = registry.getProxy(proxyWs.proxyId);
|
|
@@ -4486,15 +4558,45 @@ var DevicePreviewRouteRegistry = class extends PreviewRouteRegistry {
|
|
|
4486
4558
|
}
|
|
4487
4559
|
};
|
|
4488
4560
|
|
|
4489
|
-
// src/
|
|
4490
|
-
function
|
|
4561
|
+
// src/client-registration-admission.ts
|
|
4562
|
+
function inspectClientRegistrationAdmission(raw) {
|
|
4563
|
+
let value;
|
|
4491
4564
|
try {
|
|
4492
|
-
|
|
4493
|
-
return parsed !== null && typeof parsed === "object" && parsed.type === "client_register";
|
|
4565
|
+
value = JSON.parse(raw);
|
|
4494
4566
|
} catch {
|
|
4495
|
-
return
|
|
4567
|
+
return { kind: "not_client_registration" };
|
|
4568
|
+
}
|
|
4569
|
+
if (value === null || typeof value !== "object") {
|
|
4570
|
+
return { kind: "not_client_registration" };
|
|
4571
|
+
}
|
|
4572
|
+
const candidate = value;
|
|
4573
|
+
if (candidate.type !== "client_register") {
|
|
4574
|
+
return { kind: "not_client_registration" };
|
|
4575
|
+
}
|
|
4576
|
+
const clientId = typeof candidate.clientId === "string" ? candidate.clientId : void 0;
|
|
4577
|
+
if (!("protocolVersion" in candidate)) {
|
|
4578
|
+
return {
|
|
4579
|
+
kind: "unversioned_client_registration",
|
|
4580
|
+
...clientId !== void 0 ? { clientId } : {}
|
|
4581
|
+
};
|
|
4582
|
+
}
|
|
4583
|
+
return {
|
|
4584
|
+
kind: "versioned_client_registration",
|
|
4585
|
+
...clientId !== void 0 ? { clientId } : {},
|
|
4586
|
+
protocolVersion: candidate.protocolVersion
|
|
4587
|
+
};
|
|
4588
|
+
}
|
|
4589
|
+
function classifyClientRegistrationProtocol(clientVersion, relayVersion = RELAY_CONTROL_PROTOCOL_VERSION) {
|
|
4590
|
+
if (typeof clientVersion !== "number" || !Number.isSafeInteger(clientVersion) || clientVersion <= 0) {
|
|
4591
|
+
return RelayProtocolRejectReason.PROTOCOL_MISMATCH;
|
|
4496
4592
|
}
|
|
4593
|
+
if (clientVersion === relayVersion) return null;
|
|
4594
|
+
return clientVersion < relayVersion ? RelayProtocolRejectReason.PAGE_OUTDATED : RelayProtocolRejectReason.SERVICE_OUTDATED;
|
|
4497
4595
|
}
|
|
4596
|
+
|
|
4597
|
+
// src/handlers/client.ts
|
|
4598
|
+
var DEFAULT_CLIENT_ADMISSION_TIMEOUT_MS = 1e4;
|
|
4599
|
+
var RETRYABLE_CLIENT_ADMISSION_TIMEOUT_CLOSE_CODE = 1013;
|
|
4498
4600
|
function handleClientRegister(registration, clientWs, registry, logger, webPreviewRoutes, devicePreviewBridge) {
|
|
4499
4601
|
const { clientId } = registration;
|
|
4500
4602
|
if (clientWs.clientId && clientWs.clientId !== clientId) {
|
|
@@ -4849,8 +4951,36 @@ function validatePreviewScope(ws, registry, requestId, scope) {
|
|
|
4849
4951
|
rejectStaleBinding(ws, requestId);
|
|
4850
4952
|
return false;
|
|
4851
4953
|
}
|
|
4852
|
-
function closeRejectedClientProtocol(ws) {
|
|
4853
|
-
ws
|
|
4954
|
+
function closeRejectedClientProtocol(ws, reason = RelayProtocolRejectReason.PROTOCOL_MISMATCH) {
|
|
4955
|
+
if (!beginClientRejection(ws)) return;
|
|
4956
|
+
ws.close(RelayCloseCode.CLIENT_PROTOCOL_REJECTED, reason);
|
|
4957
|
+
}
|
|
4958
|
+
function beginClientRejection(ws) {
|
|
4959
|
+
if (ws.admissionPhase === "rejected") return false;
|
|
4960
|
+
ws.admissionPhase = "rejected";
|
|
4961
|
+
return ws.readyState !== WebSocket7.CLOSING && ws.readyState !== WebSocket7.CLOSED;
|
|
4962
|
+
}
|
|
4963
|
+
function closeUnversionedClientProtocol(ws) {
|
|
4964
|
+
if (!beginClientRejection(ws)) return;
|
|
4965
|
+
ws.send(
|
|
4966
|
+
JSON.stringify({
|
|
4967
|
+
type: "relay_client_kicked",
|
|
4968
|
+
reason: "\u9875\u9762\u7248\u672C\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0"
|
|
4969
|
+
})
|
|
4970
|
+
);
|
|
4971
|
+
ws.close(RelayCloseCode.CLIENT_KICKED, "client refresh required");
|
|
4972
|
+
}
|
|
4973
|
+
function rejectVersionedClientProtocol(ws, clientProtocolVersion, reason, clientId, logger) {
|
|
4974
|
+
logger.warn(
|
|
4975
|
+
{
|
|
4976
|
+
...clientId !== void 0 ? { clientId } : {},
|
|
4977
|
+
clientProtocolVersion,
|
|
4978
|
+
relayProtocolVersion: RELAY_CONTROL_PROTOCOL_VERSION,
|
|
4979
|
+
reason
|
|
4980
|
+
},
|
|
4981
|
+
"Client control protocol rejected at admission"
|
|
4982
|
+
);
|
|
4983
|
+
closeRejectedClientProtocol(ws, reason);
|
|
4854
4984
|
}
|
|
4855
4985
|
function rejectProxySelect(ws, requestId, proxyId) {
|
|
4856
4986
|
ws.send(
|
|
@@ -4903,14 +5033,33 @@ function sendRemoteFileUploadUrlFailure(ws, requestId, sessionId, error, errorCo
|
|
|
4903
5033
|
})
|
|
4904
5034
|
);
|
|
4905
5035
|
}
|
|
4906
|
-
function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, sessionHistoryRoutes, webPreviewRoutes, devicePreviewBridge, chaos, voiceConfigStore, voiceProviders, remoteFileBridge, connectionInfo = {}) {
|
|
5036
|
+
function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, sessionHistoryRoutes, webPreviewRoutes, devicePreviewBridge, chaos, voiceConfigStore, voiceProviders, remoteFileBridge, connectionInfo = {}, clientAdmissionTimeoutMs = DEFAULT_CLIENT_ADMISSION_TIMEOUT_MS) {
|
|
4907
5037
|
const clientWs = ws;
|
|
4908
5038
|
clientWs.isAlive = true;
|
|
5039
|
+
clientWs.admissionPhase = "awaiting";
|
|
4909
5040
|
registry.addClientWs(clientWs, connectionInfo);
|
|
5041
|
+
const effectiveAdmissionTimeoutMs = Number.isSafeInteger(clientAdmissionTimeoutMs) && clientAdmissionTimeoutMs > 0 ? clientAdmissionTimeoutMs : DEFAULT_CLIENT_ADMISSION_TIMEOUT_MS;
|
|
5042
|
+
let admissionTimeout = setTimeout(() => {
|
|
5043
|
+
admissionTimeout = null;
|
|
5044
|
+
if (clientWs.admissionPhase !== "awaiting" || clientWs.readyState !== WebSocket7.OPEN) return;
|
|
5045
|
+
if (!beginClientRejection(clientWs)) return;
|
|
5046
|
+
logger.warn(
|
|
5047
|
+
{ timeoutMs: effectiveAdmissionTimeoutMs, remoteAddress: connectionInfo.remoteAddress },
|
|
5048
|
+
"Client registration timed out"
|
|
5049
|
+
);
|
|
5050
|
+
clientWs.close(RETRYABLE_CLIENT_ADMISSION_TIMEOUT_CLOSE_CODE, "client registration timeout");
|
|
5051
|
+
}, effectiveAdmissionTimeoutMs);
|
|
5052
|
+
admissionTimeout.unref?.();
|
|
5053
|
+
const clearAdmissionTimeout = () => {
|
|
5054
|
+
if (!admissionTimeout) return;
|
|
5055
|
+
clearTimeout(admissionTimeout);
|
|
5056
|
+
admissionTimeout = null;
|
|
5057
|
+
};
|
|
4910
5058
|
clientWs.on("pong", () => {
|
|
4911
5059
|
clientWs.isAlive = true;
|
|
4912
5060
|
});
|
|
4913
5061
|
clientWs.on("message", (data, isBinary) => {
|
|
5062
|
+
if (clientWs.admissionPhase === "rejected") return;
|
|
4914
5063
|
if (isBinary) {
|
|
4915
5064
|
if (!clientWs.clientId) closeRejectedClientProtocol(clientWs);
|
|
4916
5065
|
return;
|
|
@@ -4924,6 +5073,32 @@ function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, session
|
|
|
4924
5073
|
return;
|
|
4925
5074
|
}
|
|
4926
5075
|
const raw = data.toString();
|
|
5076
|
+
const registrationAdmission = inspectClientRegistrationAdmission(raw);
|
|
5077
|
+
if (registrationAdmission.kind === "unversioned_client_registration") {
|
|
5078
|
+
logger.warn(
|
|
5079
|
+
{
|
|
5080
|
+
...registrationAdmission.clientId !== void 0 ? { clientId: registrationAdmission.clientId } : {}
|
|
5081
|
+
},
|
|
5082
|
+
"Unversioned client registration rejected"
|
|
5083
|
+
);
|
|
5084
|
+
closeUnversionedClientProtocol(clientWs);
|
|
5085
|
+
return;
|
|
5086
|
+
}
|
|
5087
|
+
if (registrationAdmission.kind === "versioned_client_registration") {
|
|
5088
|
+
const registrationProtocolIssue = classifyClientRegistrationProtocol(
|
|
5089
|
+
registrationAdmission.protocolVersion
|
|
5090
|
+
);
|
|
5091
|
+
if (registrationProtocolIssue) {
|
|
5092
|
+
rejectVersionedClientProtocol(
|
|
5093
|
+
clientWs,
|
|
5094
|
+
registrationAdmission.protocolVersion,
|
|
5095
|
+
registrationProtocolIssue,
|
|
5096
|
+
registrationAdmission.clientId,
|
|
5097
|
+
logger
|
|
5098
|
+
);
|
|
5099
|
+
return;
|
|
5100
|
+
}
|
|
5101
|
+
}
|
|
4927
5102
|
const result = parseMessage(raw);
|
|
4928
5103
|
if (result.kind === "control") {
|
|
4929
5104
|
const msg = result.message;
|
|
@@ -4940,6 +5115,8 @@ function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, session
|
|
|
4940
5115
|
webPreviewRoutes,
|
|
4941
5116
|
devicePreviewBridge
|
|
4942
5117
|
);
|
|
5118
|
+
clientWs.admissionPhase = "ready";
|
|
5119
|
+
clearAdmissionTimeout();
|
|
4943
5120
|
return;
|
|
4944
5121
|
}
|
|
4945
5122
|
if (!clientWs.clientId) {
|
|
@@ -5457,7 +5634,13 @@ function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, session
|
|
|
5457
5634
|
routeClientMessage(raw, clientWs.boundProxyId, clientWs, registry, logger, chaos);
|
|
5458
5635
|
return;
|
|
5459
5636
|
}
|
|
5460
|
-
|
|
5637
|
+
const isRegistrationAttempt = registrationAdmission.kind !== "not_client_registration";
|
|
5638
|
+
const logContext = {
|
|
5639
|
+
error: result.error,
|
|
5640
|
+
raw: raw.slice(0, 200),
|
|
5641
|
+
...registrationAdmission.kind !== "not_client_registration" && registrationAdmission.clientId !== void 0 ? { clientId: registrationAdmission.clientId } : {}
|
|
5642
|
+
};
|
|
5643
|
+
logger.error(logContext, "Invalid message from client");
|
|
5461
5644
|
clientWs.send(
|
|
5462
5645
|
JSON.stringify({
|
|
5463
5646
|
type: "relay_error",
|
|
@@ -5465,11 +5648,13 @@ function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, session
|
|
|
5465
5648
|
message: `${result.error} | raw: ${raw.slice(0, 200)}`
|
|
5466
5649
|
})
|
|
5467
5650
|
);
|
|
5468
|
-
if (
|
|
5651
|
+
if (isRegistrationAttempt || !clientWs.clientId) {
|
|
5469
5652
|
closeRejectedClientProtocol(clientWs);
|
|
5470
5653
|
}
|
|
5471
5654
|
});
|
|
5472
5655
|
clientWs.on("close", (code, reason) => {
|
|
5656
|
+
clearAdmissionTimeout();
|
|
5657
|
+
clientWs.admissionPhase = "rejected";
|
|
5473
5658
|
ptySnapshotRoutes.abandonSocket(clientWs);
|
|
5474
5659
|
sessionHistoryRoutes.abandonSocket(clientWs);
|
|
5475
5660
|
webPreviewRoutes.abandonSocket(clientWs);
|
|
@@ -8568,7 +8753,8 @@ function createRelayServer(options) {
|
|
|
8568
8753
|
webPreviewRoutes,
|
|
8569
8754
|
devicePreviewBridge,
|
|
8570
8755
|
relayChaos,
|
|
8571
|
-
remoteFileBridge
|
|
8756
|
+
remoteFileBridge,
|
|
8757
|
+
options.proxyAdmissionTimeoutMs
|
|
8572
8758
|
);
|
|
8573
8759
|
});
|
|
8574
8760
|
devicePreviewStreamWss.on("connection", (ws) => {
|
|
@@ -8590,7 +8776,8 @@ function createRelayServer(options) {
|
|
|
8590
8776
|
{
|
|
8591
8777
|
userAgent: firstHeaderValue(request.headers["user-agent"]),
|
|
8592
8778
|
remoteAddress: requestRemoteAddress(request)
|
|
8593
|
-
}
|
|
8779
|
+
},
|
|
8780
|
+
options.clientAdmissionTimeoutMs
|
|
8594
8781
|
);
|
|
8595
8782
|
});
|
|
8596
8783
|
voiceAsrWss.on("connection", (ws) => {
|
|
@@ -8697,4 +8884,4 @@ export {
|
|
|
8697
8884
|
parseRelayChaosFromEnv,
|
|
8698
8885
|
createRelayServer
|
|
8699
8886
|
};
|
|
8700
|
-
//# sourceMappingURL=chunk-
|
|
8887
|
+
//# sourceMappingURL=chunk-PMXR723R.js.map
|