(
request: IncomingMessage,
response: ServerResponse,
rpc: ReturnType<typeof createWorkspaceServer>,
upstreamSlot: { ws: WebSocket | undefined },
)
| 333 | } |
| 334 | |
| 335 | async function handleConnect( |
| 336 | request: IncomingMessage, |
| 337 | response: ServerResponse, |
| 338 | rpc: ReturnType<typeof createWorkspaceServer>, |
| 339 | upstreamSlot: { ws: WebSocket | undefined }, |
| 340 | ): Promise<void> { |
| 341 | let body: ConnectBody; |
| 342 | try { |
| 343 | body = await readJson<ConnectBody>(request); |
| 344 | } catch (error) { |
| 345 | send(response, 400, `invalid JSON body: ${(error as Error).message}\n`, { |
| 346 | "content-type": "text/plain; charset=utf-8", |
| 347 | }); |
| 348 | return; |
| 349 | } |
| 350 | |
| 351 | if (typeof body.url !== "string" || body.url.length === 0) { |
| 352 | send(response, 400, "missing 'url' in body\n", { |
| 353 | "content-type": "text/plain; charset=utf-8", |
| 354 | }); |
| 355 | return; |
| 356 | } |
| 357 | const baseUrl = body.url.replace(/\/+$/, ""); |
| 358 | const healthTimeoutMs = |
| 359 | typeof body.healthTimeoutMs === "number" && body.healthTimeoutMs > 0 |
| 360 | ? body.healthTimeoutMs |
| 361 | : 30_000; |
| 362 | |
| 363 | try { |
| 364 | await waitForHealth(baseUrl, healthTimeoutMs); |
| 365 | } catch (error) { |
| 366 | send(response, 502, `upstream /health unreachable: ${(error as Error).message}\n`, { |
| 367 | "content-type": "text/plain; charset=utf-8", |
| 368 | }); |
| 369 | return; |
| 370 | } |
| 371 | |
| 372 | // Close any prior outbound session before opening a new one. A DO |
| 373 | // restart / hibernate hands the new incarnation a fresh /connect; |
| 374 | // without this, the previous WebSocket leaks for the life of the |
| 375 | // container. |
| 376 | const previous = upstreamSlot.ws; |
| 377 | if (previous !== undefined) { |
| 378 | upstreamSlot.ws = undefined; |
| 379 | try { |
| 380 | previous.close(1000, "replaced by new /connect"); |
| 381 | } catch { |
| 382 | // already closed; idempotent |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | const wsUrl = `${toWebSocketUrl(baseUrl)}/ws`; |
| 387 | const ws = new WebSocket(wsUrl); |
| 388 | upstreamSlot.ws = ws; |
| 389 | ws.once("open", () => { |
| 390 | console.log(`/connect: attached RPC session to ${wsUrl}`); |
| 391 | acceptWebSocketSession(ws, rpc); |
| 392 | }); |
no test coverage detected