| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263 |
- /** Host HTTP bridge for browser-client RPC. */
- import type { Context } from 'cordis'
- import z from 'schemastery'
- // Activates the httpServer Context merge used below.
- import type { WebRoute } from '@deepseek-ai/dsh-host-webserver'
- import { toFetchHandler } from '@deepseek-ai/dsh-host-apiproxy'
- import { API_PATH } from './api-path.ts'
- import { bridge } from './http-bridge.ts'
- import { assertTrustedAuthority, isTrustedApiRequest } from './api-request-trust.ts'
- export { API_PATH } from './api-path.ts'
- /** Stable Cordis plugin name. */
- export const name = 'client-connection'
- /** Services required before mounting the route. */
- export const inject = ['httpServer', 'apiProxy']
- /** Plugin config: the deployment's non-loopback serving authorities. */
- export interface ConnectionConfig {
- /**
- * Authorities this deployment serves beyond loopback: exact `host:port`, or
- * port-less `host` matching any port. The /api trust fence refuses any
- * request whose Host is neither loopback nor listed here, so a
- * non-loopback (`0.0.0.0`) deployment must declare the names it is reached
- * by (the dsh CLI derives the machine's LAN IP literals itself). An entry
- * that is not a bare, canonical authority fails the plugin load.
- */
- trustedHosts?: string[]
- }
- export const Config: z<ConnectionConfig> = z.object({
- trustedHosts: z.array(String).default([]),
- })
- /**
- * Mounts the API gateway under the browser transport prefix. Every request on
- * the prefix passes the browser-trust fence first (DNS-rebinding and
- * cross-site defense — [api-request-trust](./api-request-trust.ts)).
- * @param ctx - Host plugin context.
- * @param config - resolved plugin config (schema defaults applied).
- */
- export function apply(ctx: Context, config?: ConnectionConfig): void {
- // The Loader resolves schema defaults; hand-built test contexts may pass none.
- const trustedHosts = config?.trustedHosts ?? []
- // Config boundary: a malformed entry fails the load loudly here rather than
- // silently authorizing its hostname prefix at request time.
- for (const entry of trustedHosts) assertTrustedAuthority(entry)
- const apiHandler = toFetchHandler(ctx.apiProxy)
- const route: WebRoute = {
- kind: 'prefix',
- path: API_PATH,
- handler: async (req, res) => {
- if (!isTrustedApiRequest(req, trustedHosts)) {
- res.writeHead(403)
- res.end('forbidden')
- return
- }
- await bridge(req, res, apiHandler)
- },
- }
- ctx.effect(() => ctx.httpServer.register(route), 'client-connection: /api route')
- }
|