import { createHash } from 'node:crypto'; import { hostname, platform } from 'node:os'; import { join } from 'node:path'; import { request as httpRequest, validateHeaderName, validateHeaderValue } from 'node:http'; import { setTimeout as sleep } from 'node:timers/promises'; import { promisify } from 'node:util'; import { gzip } from 'node:zlib'; import { createKimiDeviceId, FileTokenStorage, KIMI_CODE_PROVIDER_NAME, resolveKimiTokenStorageName, } from '@moonshot-ai/kimi-code-oauth'; import { WebSocket, type RawData } from 'ws'; import { acquireRemoteControlLock } from './lock'; export const REMOTE_CONTROL_RELAY_ORIGIN = 'https://code-rc.kimi.com'; export const REMOTE_CONTROL_RELAY_URL_ENV = 'KIMI_CODE_REMOTE_CONTROL_RELAY_URL'; export function resolveRemoteControlRelayOrigin( env: Readonly> = process.env, ): string { const value = env[REMOTE_CONTROL_RELAY_URL_ENV]?.trim(); return value === undefined || value.length === 0 ? REMOTE_CONTROL_RELAY_ORIGIN : value; } const MAX_HTTP_HEADER_BYTES = 64 * 1024; const MAX_HTTP_REQUEST_BYTES = 10 * 1024 * 1024; const HTTP_REQUEST_TIMEOUT_MS = 30_000; const REGISTER_TIMEOUT_MS = 10_000; const MAX_RECONNECT_DELAY_MS = 30_000; const RELAY_PING_INTERVAL_MS = 30_000; const RELAY_SILENCE_TIMEOUT_MS = 300_000; const BLOCKED_REQUEST_HEADERS = new Set([ 'authorization', 'cookie', 'host', 'origin', 'proxy-authorization', 'proxy-authenticate', 'accept-encoding', 'connection', 'keep-alive', 'proxy-connection', 'te', 'trailer', 'transfer-encoding', 'upgrade', ]); const BLOCKED_RESPONSE_HEADERS = new Set([ 'connection', 'content-length', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'proxy-connection', 'te', 'trailer', 'transfer-encoding', 'upgrade', ]); const GZIP_MIN_BODY_BYTES = 1024; const GZIP_COMPRESSIBLE_TYPES = new Set([ 'application/javascript', 'application/json', 'application/xml', 'image/svg+xml', ]); const gzipAsync = promisify(gzip); interface RelayMessage { readonly type: string; readonly payload?: Record; } interface PendingHttpRequest { readonly chunks: Buffer[]; size: number; } export interface ParsedRawHttpRequest { readonly method: string; readonly path: string; readonly headers: readonly [string, string][]; readonly body: Buffer; } export type RemoteControlStatus = | 'relay_connected' | 'relay_disconnected' | 'device_connected' | 'device_disconnected'; export interface RemoteControlOptions { readonly homeDir: string; readonly localOrigin: string; readonly localServerToken: string | (() => string); readonly clientVersion: string; readonly relayOrigin?: string; readonly stderr?: Pick; readonly onStatus?: (status: RemoteControlStatus) => void; readonly pingIntervalMs?: number; readonly silenceTimeoutMs?: number; } export interface RemoteControlHandle { readonly deviceId: string; readonly deviceName: string; readonly url: string; readonly closed: Promise; close(): Promise; } interface ActiveStream { readonly local: WebSocket; readonly tunnel: WebSocket; } class RegistrationError extends Error {} export function buildRemoteControlUrl( deviceId: string, sessionId?: string, relayOrigin = resolveRemoteControlRelayOrigin(), ): string { const url = new URL(relayOrigin); const relayPath = url.pathname.replace(/\/+$/, ''); const devicePath = `${relayPath}/devices/${encodeURIComponent(deviceId)}`; url.pathname = sessionId === undefined ? `${devicePath}/` : `${devicePath}/sessions/${encodeURIComponent(sessionId)}`; url.search = new URLSearchParams({ rc: '1', from: 'kimi_code_cli' }).toString(); url.hash = ''; return url.toString(); } export function parseRawHttpRequest(raw: Buffer): ParsedRawHttpRequest { const separator = raw.indexOf('\r\n\r\n'); if (separator < 0 || separator > MAX_HTTP_HEADER_BYTES) { throw new SyntaxError('invalid HTTP request headers'); } const head = raw.subarray(0, separator).toString('latin1'); const lines = head.split('\r\n'); const requestLine = lines.shift(); const match = requestLine?.match( /^([!#$%&'*+.^_`|~0-9A-Za-z-]+) (\/[^\u0000-\u0020]*) HTTP\/1\.[01]$/, ); if (match === null || match === undefined || match[2]!.startsWith('//')) { throw new SyntaxError('invalid HTTP request line'); } const headers: [string, string][] = []; for (const line of lines) { const colon = line.indexOf(':'); if (colon <= 0) throw new SyntaxError('invalid HTTP request header'); const name = line.slice(0, colon).trim(); const value = line.slice(colon + 1).trim(); try { validateHeaderName(name); validateHeaderValue(name, value); } catch { throw new SyntaxError('invalid HTTP request header'); } headers.push([name, value]); } return { method: match[1]!, path: match[2]!, headers, body: raw.subarray(separator + 4), }; } export function filterForwardRequestHeaders( headers: readonly [string, string][], serverToken: string, ): string[] { const connectionHeaders = new Set(); for (const [name, value] of headers) { if (name.toLowerCase() === 'connection') { for (const token of value.split(',')) connectionHeaders.add(token.trim().toLowerCase()); } } const result: string[] = []; for (const [name, value] of headers) { const lower = name.toLowerCase(); if (BLOCKED_REQUEST_HEADERS.has(lower) || connectionHeaders.has(lower)) continue; result.push(name, value); } result.push('Authorization', `Bearer ${serverToken}`); return result; } export function rewriteRemoteControlResponse( contentType: string, body: Buffer, publicPrefix: string, ): Buffer { const normalizedPrefix = publicPrefix.replace(/\/+$/, ''); if (contentType.toLowerCase().includes('text/html')) { const prefixLiteral = JSON.stringify(normalizedPrefix); const injected = ``; let text = body.toString('utf8'); const headMatch = /]*)?>/i.exec(text); text = headMatch === null ? injected + text : text.slice(0, headMatch.index + headMatch[0].length) + injected + text.slice(headMatch.index + headMatch[0].length); text = text.replaceAll(/\bsrc="\//g, `src="${normalizedPrefix}/`); text = text.replaceAll(/\bhref="\//g, `href="${normalizedPrefix}/`); return Buffer.from(text); } const lower = contentType.toLowerCase(); if (lower.includes('javascript') || lower.includes('text/css')) { let text = body.toString('utf8'); text = text.replaceAll('"/assets/', `"${normalizedPrefix}/assets/`); text = text.replaceAll("'/assets/", `'${normalizedPrefix}/assets/`); text = text.replaceAll('(/assets/', `(${normalizedPrefix}/assets/`); text = text.replaceAll('"/sessions/"', `"${normalizedPrefix}/sessions/"`); text = text.replaceAll('return"/"+', `return"${normalizedPrefix}/"+`); return Buffer.from(text); } return body; } function acceptsGzipEncoding(headers: readonly [string, string][]): boolean { let wildcard = false; for (const [name, value] of headers) { if (name.toLowerCase() !== 'accept-encoding') continue; for (const token of value.split(',')) { const [encoding, ...params] = token.trim().toLowerCase().split(';'); if (encoding !== 'gzip' && encoding !== '*') continue; const quality = params.map((param) => param.trim()).find((param) => param.startsWith('q=')); const acceptable = quality === undefined || Number(quality.slice(2)) > 0; if (encoding === 'gzip') return acceptable; wildcard = wildcard || acceptable; } } return wildcard; } function isGzipCompressibleType(contentType: string): boolean { const mime = contentType.split(';', 1)[0]!.trim().toLowerCase(); return mime.startsWith('text/') || GZIP_COMPRESSIBLE_TYPES.has(mime); } function rewrittenResponseETag(body: Buffer): string { return `W/"${createHash('sha256').update(body).digest('hex')}"`; } function requestMatchesETag( headers: readonly [string, string][], etag: string, ): boolean { const candidates = [etag, etag.replace(/^W\//, '')]; for (const [name, value] of headers) { if (name.toLowerCase() !== 'if-none-match') continue; for (const token of value.split(',')) { const candidate = token.trim(); if (candidate === '*') return true; if (candidates.includes(candidate)) return true; } } return false; } export async function startRemoteControl( options: RemoteControlOptions, ): Promise { const localServerToken = typeof options.localServerToken === 'function' ? options.localServerToken : () => options.localServerToken as string; if (localServerToken().length === 0) { throw new Error('Remote Control requires local server authentication.'); } const storage = new FileTokenStorage(join(options.homeDir, 'credentials')); const token = await storage.load( resolveKimiTokenStorageName({ providerName: KIMI_CODE_PROVIDER_NAME }), ); if (token?.refreshToken === undefined || token.refreshToken.length === 0) { throw new Error('Remote Control requires a Kimi login. Run `kimi login` first.'); } const relayOrigin = options.relayOrigin ?? resolveRemoteControlRelayOrigin(); const deviceId = createKimiDeviceId(options.homeDir); const deviceName = hostname(); const url = buildRemoteControlUrl(deviceId, undefined, relayOrigin); const lock = await acquireRemoteControlLock(options.homeDir, { localOrigin: options.localOrigin.replace(/\/+$/, ''), deviceId, url, }); const client = new RemoteControlClient({ ...options, localServerToken, relayOrigin, deviceId, refreshToken: token.refreshToken, }); try { await client.start(); } catch (error) { await lock.release(); throw error; } const closed = client.closed.then(async () => { await lock.release(); }); return { deviceId, deviceName, url, closed, close: async () => { await client.close(); await closed; }, }; } class RemoteControlClient { private readonly localOrigin: string; private readonly localServerToken: () => string; private readonly clientVersion: string; private readonly relayOrigin: string; private readonly deviceId: string; private readonly refreshToken: string; private readonly stderr: Pick; private readonly onStatus: (status: RemoteControlStatus) => void; private readonly streams = new Map(); private readonly pendingHttpRequests = new Map(); private management: WebSocket | undefined; private reconnectAbort: AbortController | undefined; private http: WebSocket | undefined; private pendingHttpBytes = 0; private reconnectAttempt = 0; private reconnectImmediately = false; private readonly pingIntervalMs: number; private readonly silenceTimeoutMs: number; private stopped = false; private connected = false; private relayOnline = false; private runPromise: Promise | undefined; private initialResolve: (() => void) | undefined; private initialReject: ((error: unknown) => void) | undefined; constructor( options: Omit & { readonly localServerToken: () => string; readonly relayOrigin: string; readonly deviceId: string; readonly refreshToken: string; }, ) { this.localOrigin = options.localOrigin.replace(/\/+$/, ''); this.localServerToken = options.localServerToken; this.clientVersion = options.clientVersion; this.relayOrigin = options.relayOrigin; this.deviceId = options.deviceId; this.refreshToken = options.refreshToken; this.stderr = options.stderr ?? process.stderr; this.onStatus = options.onStatus ?? (() => {}); this.pingIntervalMs = options.pingIntervalMs ?? RELAY_PING_INTERVAL_MS; this.silenceTimeoutMs = options.silenceTimeoutMs ?? RELAY_SILENCE_TIMEOUT_MS; } async start(): Promise { const initial = new Promise((resolve, reject) => { this.initialResolve = resolve; this.initialReject = reject; }); this.runPromise = this.run(); await initial; } get closed(): Promise { return this.runPromise ?? Promise.resolve(); } async close(): Promise { if (this.stopped) { await this.runPromise; return; } this.stopped = true; if (!this.connected) this.rejectInitial(new Error('Remote Control closed before ready.')); if (this.management?.readyState === WebSocket.OPEN) { this.management.send( JSON.stringify({ type: 'disconnect', payload: { reason: 'local_server_stopped' } }), ); } this.closeCycle(); this.reconnectAbort?.abort(); await this.runPromise; } private async run(): Promise { while (!this.stopped) { try { await this.serveCycle(); } catch (error) { if (error instanceof RegistrationError) { if (!this.connected) { this.rejectInitial(error); this.stopped = true; return; } this.stderr.write(`${error.message}\n`); } else if (!this.stopped && !this.reconnectImmediately) { this.stderr.write(`Remote Control disconnected: ${errorMessage(error)}\n`); } } finally { this.closeCycle(); } if (this.stopped) { if (!this.connected) this.rejectInitial(new Error('Remote Control stopped before ready.')); return; } if (this.reconnectImmediately) { this.reconnectImmediately = false; continue; } this.reconnectAttempt += 1; const delay = Math.min( MAX_RECONNECT_DELAY_MS, 1000 * 2 ** Math.min(this.reconnectAttempt - 1, 5), ); await this.waitForReconnect(delay); } } private async serveCycle(): Promise { const management = await this.connectRelay('/v1/remote/create'); this.management = management; this.watchSocket(management, 'management'); management.send( JSON.stringify({ type: 'register', payload: { device_id: this.deviceId, alias: hostname(), platform: platform(), client_version: this.clientVersion, local_base_url: this.localOrigin, }, }), ); const registration = await waitForRelayMessage(management, REGISTER_TIMEOUT_MS); if (registration.type === 'register_nak') { const code = stringField(registration.payload, 'error_code') ?? 'REGISTRATION_REJECTED'; const message = stringField(registration.payload, 'error_message') ?? 'registration rejected'; throw new RegistrationError(`Remote Control registration failed (${code}): ${message}`); } if (registration.type !== 'register_ack') { throw new Error(`Remote Control expected register_ack, received ${registration.type}`); } const managementEnd = waitForSocketEnd(management); const http = await this.connectRelay( `/v1/remote/http?device_id=${encodeURIComponent(this.deviceId)}`, ); this.http = http; this.watchSocket(http, 'http'); if (management.readyState !== WebSocket.OPEN) { throw new Error('management connection closed'); } management.on('message', (data) => this.handleManagementMessage(data)); http.on('message', (data) => this.handleHttpMessage(data)); this.reconnectAttempt = 0; this.relayOnline = true; this.onStatus('relay_connected'); if (!this.connected) { this.connected = true; this.initialResolve?.(); this.initialResolve = undefined; this.initialReject = undefined; } await Promise.race([managementEnd, waitForSocketEnd(http)]); if (!this.stopped) throw new Error('relay connection closed'); } private connectRelay(path: string): Promise { return connectWebSocket(relayWebSocketUrl(this.relayOrigin, path), this.refreshToken); } private watchSocket(socket: WebSocket, label: string): void { const pingTimer = setInterval(() => { if (socket.readyState === WebSocket.OPEN) socket.ping(); }, this.pingIntervalMs); pingTimer.unref(); let silenceTimer: NodeJS.Timeout | undefined; const armSilenceTimer = (): void => { if (silenceTimer !== undefined) clearTimeout(silenceTimer); silenceTimer = setTimeout(() => { this.stderr.write( `Remote Control ${label} connection silent for ${Math.round(this.silenceTimeoutMs / 1000)}s; reconnecting…\n`, ); socket.terminate(); }, this.silenceTimeoutMs); silenceTimer.unref(); }; armSilenceTimer(); socket.on('message', armSilenceTimer); socket.on('ping', armSilenceTimer); socket.on('pong', armSilenceTimer); socket.once('close', () => { clearInterval(pingTimer); if (silenceTimer !== undefined) clearTimeout(silenceTimer); }); } private rejectInitial(error: Error): void { this.initialReject?.(error); this.initialReject = undefined; this.initialResolve = undefined; } private handleManagementMessage(data: RawData): void { let message: RelayMessage; try { message = parseRelayMessage(data); } catch (error) { this.stderr.write(`Remote Control message error: ${errorMessage(error)}\n`); return; } if (message.type === 'open_ws') { void this.openStream(message.payload ?? {}); return; } if (message.type === 'close_ws') { const streamId = stringField(message.payload, 'stream_id'); if (streamId !== undefined) this.closeStream(streamId); return; } if (message.type === 'disconnect') { const reason = stringField(message.payload, 'reason'); if (reason === 'user_requested') this.stopped = true; if (reason === 'server_shutting_down') this.reconnectImmediately = true; this.closeCycle(); } } private handleHttpMessage(data: RawData): void { const text = rawDataText(data).trim(); if (text.length === 0) return; let requestId: string | undefined; try { const parsed = JSON.parse(text) as Record; if (parsed['type'] !== 'request') return; requestId = typeof parsed['request_id'] === 'string' ? parsed['request_id'] : undefined; if ( requestId === undefined || typeof parsed['body_base64'] !== 'string' || typeof parsed['is_last'] !== 'boolean' ) { throw new SyntaxError('invalid HTTP tunnel request message'); } const bodyBase64 = parsed['body_base64']; const minDecodedBytes = Math.floor(bodyBase64.length / 4) * 3 - 2; if (this.pendingHttpBytes + minDecodedBytes > MAX_HTTP_REQUEST_BYTES) { throw new SyntaxError('HTTP tunnel request exceeds 10 MiB'); } const chunk = decodeBase64(bodyBase64); const pending = this.pendingHttpRequests.get(requestId) ?? { chunks: [], size: 0 }; if (this.pendingHttpBytes + chunk.length > MAX_HTTP_REQUEST_BYTES) { throw new SyntaxError('HTTP tunnel request exceeds 10 MiB'); } pending.chunks.push(chunk); pending.size += chunk.length; this.pendingHttpBytes += chunk.length; this.pendingHttpRequests.set(requestId, pending); if (!parsed['is_last']) return; const rawRequest = Buffer.concat(pending.chunks, pending.size); this.clearPendingHttpRequest(requestId); void this.forwardHttpRequest(requestId, rawRequest); } catch (error) { if (requestId !== undefined) { this.clearPendingHttpRequest(requestId); const status = error instanceof SyntaxError ? 400 : 502; this.sendHttpResponse(requestId, buildErrorResponse(status)); } this.stderr.write(`Remote Control HTTP message error: ${errorMessage(error)}\n`); } } private async forwardHttpRequest(requestId: string, rawRequest: Buffer): Promise { try { const parsed = parseRawHttpRequest(rawRequest); const response = await requestLocalHttp( this.localOrigin, parsed, this.localServerToken(), this.publicPrefix(), ); this.sendHttpResponse(requestId, response); } catch (error) { const status = error instanceof SyntaxError ? 400 : 502; this.sendHttpResponse(requestId, buildErrorResponse(status)); this.stderr.write(`Remote Control HTTP forwarding failed: ${errorMessage(error)}\n`); } } private sendHttpResponse(requestId: string, response: Buffer): void { if (this.http?.readyState !== WebSocket.OPEN) return; this.http.send( JSON.stringify({ request_id: requestId, type: 'response', is_last: true, body_base64: response.toString('base64'), }), ); } private async openStream(payload: Record): Promise { const streamId = stringField(payload, 'stream_id'); const path = stringField(payload, 'path'); if (streamId === undefined || path === undefined || !path.startsWith('/') || path.startsWith('//')) { if (streamId !== undefined) { this.sendOpenStreamResult(streamId, false, 'LOCAL_WS_FAILED', 'invalid local WebSocket path'); } return; } let local: WebSocket | undefined; let tunnel: WebSocket | undefined; const earlyLocalFrames: [RawData, boolean][] = []; try { local = await connectWebSocket( localWebSocketUrl(this.localOrigin, path), this.localServerToken(), relayHeaders(payload['headers']), earlyLocalFrames, ); tunnel = await this.connectRelay(`/v1/remote/stream/${encodeURIComponent(streamId)}`); if (this.stopped || this.management?.readyState !== WebSocket.OPEN) { throw new Error('management connection closed'); } this.streams.set(streamId, { local, tunnel }); this.onStatus('device_connected'); bridgeSockets( local, tunnel, () => { if (this.streams.get(streamId)?.local === local) { this.streams.delete(streamId); this.onStatus('device_disconnected'); } }, earlyLocalFrames, ); this.sendOpenStreamResult(streamId, true); } catch (error) { local?.close(); tunnel?.close(); this.sendOpenStreamResult( streamId, false, local === undefined ? 'LOCAL_WS_FAILED' : 'TUNNEL_STREAM_FAILED', errorMessage(error), ); } } private sendOpenStreamResult( streamId: string, success: boolean, errorCode?: string, error?: string, ): void { if (this.management?.readyState !== WebSocket.OPEN) return; this.management.send( JSON.stringify({ type: 'open_ws_result', payload: { stream_id: streamId, success, error_code: errorCode, error_message: error, }, }), ); } private closeStream(streamId: string): void { const stream = this.streams.get(streamId); if (stream === undefined) return; this.streams.delete(streamId); this.onStatus('device_disconnected'); stream.local.close(); stream.tunnel.close(); } private clearPendingHttpRequest(requestId: string): void { const pending = this.pendingHttpRequests.get(requestId); if (pending === undefined) return; this.pendingHttpRequests.delete(requestId); this.pendingHttpBytes -= pending.size; } private closeCycle(): void { for (const streamId of this.streams.keys()) this.closeStream(streamId); this.pendingHttpRequests.clear(); this.pendingHttpBytes = 0; this.management?.close(); this.http?.close(); if (this.relayOnline) { this.relayOnline = false; this.onStatus('relay_disconnected'); } this.management = undefined; this.http = undefined; } private publicPrefix(): string { const relayPath = new URL(this.relayOrigin).pathname.replace(/\/+$/, ''); return `${relayPath}/devices/${encodeURIComponent(this.deviceId)}`; } private async waitForReconnect(ms: number): Promise { if (this.stopped) return; const controller = new AbortController(); this.reconnectAbort = controller; try { await sleep(ms, undefined, { signal: controller.signal }); } catch (error) { if (!(error instanceof Error) || error.name !== 'AbortError') throw error; } finally { if (this.reconnectAbort === controller) this.reconnectAbort = undefined; } } } async function connectWebSocket( url: string, token: string, headers: Record = {}, earlyFrames?: [RawData, boolean][], ): Promise { const protocol = `kimi-code.bearer.${token}`; if (isWebSocketProtocolToken(protocol)) { try { return await connectWebSocketAttempt(url, [protocol], headers, earlyFrames); } catch {} } return connectWebSocketAttempt( url, undefined, { ...headers, Authorization: `Bearer ${token}`, }, earlyFrames, ); } function connectWebSocketAttempt( url: string, protocols: string[] | undefined, headers: Record, earlyFrames?: [RawData, boolean][], ): Promise { return new Promise((resolve, reject) => { const socket = new WebSocket(url, protocols, { headers, handshakeTimeout: REGISTER_TIMEOUT_MS, }); if (earlyFrames !== undefined) { socket.on('message', (data, isBinary) => { earlyFrames.push([data, isBinary]); }); } let settled = false; const cleanup = (): void => { socket.off('open', onOpen); socket.off('error', onError); socket.off('close', onClose); }; const finish = (error?: Error): void => { if (settled) return; settled = true; cleanup(); if (error === undefined) resolve(socket); else reject(error); }; const onOpen = (): void => finish(); const onError = (error: Error) => finish(error); const onClose = (code: number, reason: Buffer) => { finish(new Error(`WebSocket closed during handshake (${code} ${reason.toString()})`)); }; socket.once('open', onOpen); socket.once('error', onError); socket.once('close', onClose); }); } function isWebSocketProtocolToken(value: string): boolean { return /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(value); } function waitForRelayMessage(socket: WebSocket, timeoutMs: number): Promise { return new Promise((resolve, reject) => { const timer = setTimeout(() => finish(new Error('Remote Control registration timed out')), timeoutMs); const onMessage = (data: RawData): void => { try { finish(undefined, parseRelayMessage(data)); } catch (error) { finish(error); } }; const onClose = (code: number, reason: Buffer): void => { finish(new Error(`Remote Control registration closed (${code} ${reason.toString()})`)); }; const onError = (error: Error): void => finish(error); const finish = (error?: unknown, message?: RelayMessage): void => { clearTimeout(timer); socket.off('message', onMessage); socket.off('close', onClose); socket.off('error', onError); if (error !== undefined) reject(error); else resolve(message!); }; socket.once('message', onMessage); socket.once('close', onClose); socket.once('error', onError); }); } function waitForSocketEnd(socket: WebSocket): Promise { return new Promise((resolve) => { socket.once('close', () => resolve()); socket.once('error', () => resolve()); }); } function parseRelayMessage(data: RawData): RelayMessage { const parsed = JSON.parse(rawDataText(data)) as Record; if (typeof parsed['type'] !== 'string') throw new Error('relay message has no type'); const payload = isRecord(parsed['payload']) ? parsed['payload'] : undefined; return { type: parsed['type'], payload }; } function requestLocalHttp( localOrigin: string, parsed: ParsedRawHttpRequest, serverToken: string, publicPrefix: string, ): Promise { const origin = new URL(localOrigin); return new Promise((resolve, reject) => { const request = httpRequest( { protocol: origin.protocol, hostname: origin.hostname, port: origin.port, method: parsed.method, path: parsed.path, headers: [ ...filterForwardRequestHeaders(parsed.headers, serverToken), 'Host', origin.host, ], timeout: HTTP_REQUEST_TIMEOUT_MS, }, (response) => { const chunks: Buffer[] = []; response.on('data', (chunk: Buffer | string) => chunks.push(Buffer.from(chunk))); response.once('error', reject); response.once('end', () => { void (async (): Promise => { const contentType = response.headers['content-type'] ?? ''; const receivedBody = Buffer.concat(chunks); let body = response.headers['content-encoding'] === undefined ? rewriteRemoteControlResponse(contentType, receivedBody, publicPrefix) : receivedBody; const rewritten = body !== receivedBody; const headers = filterResponseHeaders(response.rawHeaders, rewritten); if (rewritten) { const etag = rewrittenResponseETag(body); headers.push('Cache-Control', 'no-cache', 'ETag', etag); const statusCode = response.statusCode ?? 502; const revalidatable = (parsed.method === 'GET' || parsed.method === 'HEAD') && statusCode >= 200 && statusCode < 300; if (revalidatable && requestMatchesETag(parsed.headers, etag)) { return Buffer.from(`HTTP/1.1 304 Not Modified\r\n${headerLines(headers)}\r\n\r\n`); } } const negotiated = response.headers['content-encoding'] === undefined && response.statusCode !== 206 && body.length >= GZIP_MIN_BODY_BYTES && isGzipCompressibleType(contentType); if (negotiated) { let varyCovers = false; for (let index = 0; index < headers.length; index += 2) { if (headers[index]!.toLowerCase() !== 'vary') continue; const tokens = headers[index + 1]! .toLowerCase() .split(',') .map((token) => token.trim()); if (tokens.includes('*') || tokens.includes('accept-encoding')) varyCovers = true; } if (!varyCovers) headers.push('Vary', 'Accept-Encoding'); } if (negotiated && acceptsGzipEncoding(parsed.headers)) { body = await gzipAsync(body); headers.push('Content-Encoding', 'gzip'); } headers.push('Content-Length', String(body.length)); const statusCode = response.statusCode ?? 502; const statusMessage = response.statusMessage ?? 'Bad Gateway'; return Buffer.concat([ Buffer.from(`HTTP/1.1 ${statusCode} ${statusMessage}\r\n${headerLines(headers)}\r\n\r\n`), body, ]); })().then(resolve, reject); }); }, ); request.once('timeout', () => request.destroy(new Error('local HTTP request timed out'))); request.once('error', reject); request.end(parsed.body); }); } function filterResponseHeaders(rawHeaders: readonly string[], blockCacheValidators = false): string[] { const connectionHeaders = new Set(); for (let index = 0; index < rawHeaders.length; index += 2) { if (rawHeaders[index]!.toLowerCase() === 'connection') { for (const token of rawHeaders[index + 1]!.split(',')) { connectionHeaders.add(token.trim().toLowerCase()); } } } const result: string[] = []; for (let index = 0; index < rawHeaders.length; index += 2) { const name = rawHeaders[index]!; const lower = name.toLowerCase(); if (BLOCKED_RESPONSE_HEADERS.has(lower) || connectionHeaders.has(lower)) { continue; } if ( blockCacheValidators && (lower === 'cache-control' || lower === 'etag' || lower === 'last-modified') ) { continue; } result.push(name, rawHeaders[index + 1]!); } return result; } function relayHeaders(value: unknown): Record { if (!isRecord(value)) return {}; const entries: [string, string][] = []; for (const [name, raw] of Object.entries(value)) { if (typeof raw !== 'string') continue; const lower = name.toLowerCase(); if (BLOCKED_REQUEST_HEADERS.has(lower)) continue; try { validateHeaderName(name); validateHeaderValue(name, raw); entries.push([name, raw]); } catch {} } return Object.fromEntries(entries); } function bridgeSockets( left: WebSocket, right: WebSocket, onClose: () => void, earlyLeftFrames?: [RawData, boolean][], ): void { let closed = false; const closeBoth = (code = 1000, reason = Buffer.alloc(0)): void => { if (closed) return; closed = true; onClose(); const safeCode = isValidCloseCode(code) ? code : 1000; if (left.readyState === WebSocket.OPEN) left.close(safeCode, reason); if (right.readyState === WebSocket.OPEN) right.close(safeCode, reason); }; if (earlyLeftFrames !== undefined) { left.removeAllListeners('message'); for (const [data, isBinary] of earlyLeftFrames) { if (right.readyState === WebSocket.OPEN) right.send(data, { binary: isBinary }); } } left.on('message', (data, isBinary) => { if (right.readyState === WebSocket.OPEN) right.send(data, { binary: isBinary }); }); right.on('message', (data, isBinary) => { if (left.readyState === WebSocket.OPEN) left.send(data, { binary: isBinary }); }); left.once('close', closeBoth); right.once('close', closeBoth); left.once('error', () => closeBoth(1011)); right.once('error', () => closeBoth(1011)); } function isValidCloseCode(code: number): boolean { return ( code === 1000 || code === 1001 || code === 1002 || code === 1003 || (code >= 1007 && code <= 1014) || (code >= 3000 && code <= 4999) ); } function relayWebSocketUrl(origin: string, path: string): string { const url = new URL(origin); url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'; const relayPath = url.pathname.replace(/\/+$/, ''); const [pathname, query] = path.split('?', 2); url.pathname = `${relayPath}${pathname}`; url.search = query === undefined ? '' : query; url.hash = ''; return url.toString(); } function localWebSocketUrl(origin: string, path: string): string { const url = new URL(origin); url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'; url.pathname = path.split('?', 1)[0]!; const query = path.includes('?') ? path.slice(path.indexOf('?') + 1) : ''; url.search = query; url.hash = ''; return url.toString(); } function headerLines(headers: readonly string[]): string { let result = ''; for (let index = 0; index < headers.length; index += 2) { result += `${headers[index]}: ${headers[index + 1]}\r\n`; } return result.replace(/\r\n$/, ''); } function buildErrorResponse(status: number): Buffer { const reason = status === 400 ? 'Bad Request' : 'Bad Gateway'; return Buffer.from(`HTTP/1.1 ${status} ${reason}\r\nContent-Length: 0\r\n\r\n`); } function stringField( value: Record | undefined, key: string, ): string | undefined { const field = value?.[key]; return typeof field === 'string' ? field : undefined; } function decodeBase64(value: string): Buffer { if (value.length % 4 !== 0) { throw new SyntaxError('invalid HTTP tunnel request base64'); } let paddingStart = -1; for (let i = 0; i < value.length; i++) { const c = value.codePointAt(i)!; if (c === 0x3d) { if (paddingStart === -1) paddingStart = i; continue; } if (paddingStart !== -1) { throw new SyntaxError('invalid HTTP tunnel request base64'); } const ok = (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) || (c >= 0x30 && c <= 0x39) || c === 0x2b || c === 0x2f; if (!ok) { throw new SyntaxError('invalid HTTP tunnel request base64'); } } if (paddingStart !== -1 && value.length - paddingStart > 2) { throw new SyntaxError('invalid HTTP tunnel request base64'); } return Buffer.from(value, 'base64'); } function rawDataText(data: RawData): string { if (Array.isArray(data)) return Buffer.concat(data).toString('utf8'); return Buffer.from(data as ArrayBuffer).toString('utf8'); } function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } function errorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error); }