From 8ddae4e7c9197beb8ab7ec426aec997b65314b7e Mon Sep 17 00:00:00 2001 From: alaamh Date: Sun, 16 Aug 2026 11:51:16 +0400 Subject: [PATCH] Unify the StandIn calling-lane port on 9442 Change the bridge's default PORT from 8080 to 9442 (code default, Dockerfile EXPOSE and HEALTHCHECK, .env.example, README, examples, and the Starlight docs). Replace the invalid Tailscale Funnel command (--https=8080) with the path-mount form on the real route, /voice/msteams/stream, and make the documented agent voice URL portless accordingly. Bump the package to 0.1.3. --- .env.example | 2 +- Dockerfile | 4 ++-- README.md | 6 +++--- examples/basic-bridge/README.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- src/config.ts | 4 ++-- .../src/content/docs/configuration-reference.md | 2 +- website/src/content/docs/connecting-to-standin.md | 6 +++--- website/src/content/docs/getting-started.md | 15 ++++++++------- 10 files changed, 24 insertions(+), 23 deletions(-) diff --git a/.env.example b/.env.example index 9673099..88ed03f 100644 --- a/.env.example +++ b/.env.example @@ -22,7 +22,7 @@ DEEPGRAM_LANGUAGE=en #DEEPGRAM_GREETING=Hello! You've reached Komaa. How can I help? # --- optional --- -PORT=8080 +PORT=9442 BIND=0.0.0.0 # TLS: with BOTH set the bridge serves wss:// itself. Without them it is plain WS diff --git a/Dockerfile b/Dockerfile index d6696db..6ab75ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,8 +14,8 @@ ENV NODE_ENV=production COPY --from=build /app/node_modules ./node_modules COPY --from=build /app/dist ./dist COPY package.json ./ -EXPOSE 8080 +EXPOSE 9442 # healthcheck against the bridge's /healthz endpoint -HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://127.0.0.1:${PORT:-8080}/healthz || exit 1 +HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://127.0.0.1:${PORT:-9442}/healthz || exit 1 USER node CMD ["node", "dist/cli.js"] diff --git a/README.md b/README.md index e96c6a6..55aa57d 100644 --- a/README.md +++ b/README.md @@ -102,11 +102,11 @@ startServer(loadConfig(), undefined, undefined, { handleSignals: true }); ### 3. Connect it to StandIn -StandIn dials in **from the internet**, so expose port 8080 (tunnel or public host), then register the URL on your identity in the [StandIn dashboard](https://standin.komaa.com/dashboard): +StandIn dials in **from the internet**, so expose port 9442 (tunnel or public host), then register the URL on your identity in the [StandIn dashboard](https://standin.komaa.com/dashboard): ```bash -tailscale funnel --bg --https=8080 8080 -# Agent voice URL: wss://..ts.net:8080/voice/msteams/stream +tailscale funnel --bg --set-path /voice/msteams/stream http://127.0.0.1:9442/voice/msteams/stream +# Agent voice URL: wss://..ts.net/voice/msteams/stream ``` Place a Teams call to your bot (or join the [sandbox](https://standin.komaa.com/sandbox) meeting). StandIn joins, connects to the bridge, and your Deepgram agent answers. diff --git a/examples/basic-bridge/README.md b/examples/basic-bridge/README.md index 5f0a4e6..97507dd 100644 --- a/examples/basic-bridge/README.md +++ b/examples/basic-bridge/README.md @@ -8,7 +8,7 @@ cp ../../.env.example .env # fill in DEEPGRAM_API_KEY and WORKER_SHARED_SECRET npm start ``` -The bridge listens on `PORT` (default 8080). Point your StandIn identity's agent +The bridge listens on `PORT` (default 9442). Point your StandIn identity's agent WebSocket URL at `wss:///voice/msteams/stream` (front it with TLS, or set `TLS_CERT_PATH`/`TLS_KEY_PATH`), call your bot in Microsoft Teams, and talk to the agent. diff --git a/package-lock.json b/package-lock.json index e2a12b8..61818f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@komaa/deepgram-msteams-bridge", - "version": "0.1.2", + "version": "0.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@komaa/deepgram-msteams-bridge", - "version": "0.1.2", + "version": "0.1.3", "license": "MIT", "dependencies": { "ws": "^8.18.0" diff --git a/package.json b/package.json index b776c28..f4d648f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@komaa/deepgram-msteams-bridge", - "version": "0.1.2", + "version": "0.1.3", "description": "Put a Deepgram Voice Agent on Microsoft Teams calls. Terminates the StandIn media bridge wire protocol on one side and the Deepgram Voice Agent WebSocket on the other. No transcoding: PCM 16k verbatim both ways, barge-in, extensible client-side tools, vision on demand, call governors.", "keywords": [ "deepgram", diff --git a/src/config.ts b/src/config.ts index a76790e..ab852e1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -13,7 +13,7 @@ import { logger } from "./log.js"; const log = logger("config"); export interface BridgeConfig { - /** TCP port the bridge listens on for worker WebSocket upgrades. */ + /** TCP port the bridge listens on for worker WebSocket upgrades (default 9442). */ port: number; /** Bind address. */ host: string; @@ -229,7 +229,7 @@ function numFromEnv(name: string, fallback: number): number { export function loadConfig(): BridgeConfig { return { - port: numFromEnv("PORT", 8080), + port: numFromEnv("PORT", 9442), host: process.env.BIND?.trim() || "0.0.0.0", workerSharedSecret: required("WORKER_SHARED_SECRET"), deepgramApiKey: required("DEEPGRAM_API_KEY"), diff --git a/website/src/content/docs/configuration-reference.md b/website/src/content/docs/configuration-reference.md index f2f3461..d4dbe13 100644 --- a/website/src/content/docs/configuration-reference.md +++ b/website/src/content/docs/configuration-reference.md @@ -59,7 +59,7 @@ The Voice Agent API is audio-only, so the bridge answers `look` itself - and onl | Env | Default | Meaning | |---|---|---| -| `PORT` | `8080` | TCP port the bridge listens on. | +| `PORT` | `9442` | TCP port the bridge listens on. | | `BIND` | `0.0.0.0` | Bind address. | | `TLS_CERT_PATH` / `TLS_KEY_PATH` | unset | PEM cert/key for native TLS (`wss://`). Without both, the bridge serves plain WS and MUST be fronted by a TLS terminator. | | `HMAC_FRESHNESS_MS` | `60000` | Allowed clock skew for the HMAC timestamp. | diff --git a/website/src/content/docs/connecting-to-standin.md b/website/src/content/docs/connecting-to-standin.md index 0fad00b..2facd80 100644 --- a/website/src/content/docs/connecting-to-standin.md +++ b/website/src/content/docs/connecting-to-standin.md @@ -7,7 +7,7 @@ This bridge does not join Teams calls itself. That is the job of **StandIn** (th ## The connection model -- **The bridge is a WebSocket server.** It listens on `BIND:PORT` (default `0.0.0.0:8080`) and waits. +- **The bridge is a WebSocket server.** It listens on `BIND:PORT` (default `0.0.0.0:9442`) and waits. - **The StandIn media bridge is the client.** For each Teams call it opens **one WebSocket** to your bridge, appending the call id to the URL path (the call id is the last path segment). - **Authentication is HMAC over a shared secret.** Both sides hold the same secret. On the WebSocket upgrade, StandIn sends two headers whose signature is `HMAC-SHA256(secret, "{timestampMs}.{callId}")`. The bridge verifies it (constant-time, inside a 60 s freshness window, with a single-use replay guard) before accepting the connection. A mismatch is rejected with `401`. @@ -15,7 +15,7 @@ This bridge does not join Teams calls itself. That is the job of **StandIn** (th flowchart LR Teams["Teams call"] StandIn["StandIn media bridge
(hosted)
dials {url}/{callId} with
X-StandIn-Timestamp / -Signature"] - Bridge["your-host:8080
(this bridge)"] + Bridge["your-host:9442
(this bridge)"] DG["Deepgram Voice Agent"] Teams <--> StandIn StandIn -- "HMAC WS" --> Bridge @@ -62,7 +62,7 @@ The value in your env **must equal** the value StandIn holds, or the HMAC handsh In the StandIn dashboard, set your identity's **agent WebSocket URL** to where this bridge listens, for example: ```text -wss://dg-bridge.example.com:8080/voice/msteams/stream +wss://dg-bridge.example.com:9442/voice/msteams/stream ``` StandIn appends `/{callId}` per call. Any base path works - the bridge takes the **last path segment** as the call id and verifies it against the HMAC signature and the `session.start` body. diff --git a/website/src/content/docs/getting-started.md b/website/src/content/docs/getting-started.md index 0e1a0fe..b9a4512 100644 --- a/website/src/content/docs/getting-started.md +++ b/website/src/content/docs/getting-started.md @@ -29,7 +29,7 @@ import { loadConfig, startServer } from "@komaa/deepgram-msteams-bridge"; startServer(loadConfig()); // same env variables as the CLI ``` -Every option is an environment variable; the package ships a fully commented [`.env.example`](https://github.com/komaa-com/deepgram-msteams-bridge/blob/main/.env.example), and the [Configuration Reference](/deepgram-msteams-bridge/configuration-reference/) documents each one. The bridge listens on `0.0.0.0:8080` by default and exposes `GET /healthz` for liveness checks. +Every option is an environment variable; the package ships a fully commented [`.env.example`](https://github.com/komaa-com/deepgram-msteams-bridge/blob/main/.env.example), and the [Configuration Reference](/deepgram-msteams-bridge/configuration-reference/) documents each one. The bridge listens on `0.0.0.0:9442` by default and exposes `GET /healthz` for liveness checks. `WORKER_SHARED_SECRET` comes from StandIn in the next step. @@ -51,33 +51,34 @@ The bridge appends per-call caller context (name, tenant, direction) to your pro StandIn is the hosted service that joins the Teams call and dials into your bridge. Pick a tier at [standin.komaa.com](https://standin.komaa.com) (sandbox for an instant trial), pair, and you get a **shared secret**. 1. Put the secret in `WORKER_SHARED_SECRET` (both sides must match exactly). -2. Point the identity's **agent WebSocket URL** at your bridge, for example `wss://dg-bridge.example.com:8080/voice/msteams/stream`. StandIn appends `/{callId}` per call. +2. Point the identity's **agent WebSocket URL** at your bridge, for example `wss://dg-bridge.example.com:9442/voice/msteams/stream`. StandIn appends `/{callId}` per call. 3. Restart the bridge if you changed the env. -StandIn dials in **from the internet**, so a laptop or private host needs a public URL. A tunnel gives you one and terminates TLS (so you get `wss://` for free). Run one pointing at port `8080`, then use the `wss://…/voice/msteams/stream` form of the printed host: +StandIn dials in **from the internet**, so a laptop or private host needs a public URL. A tunnel gives you one and terminates TLS (so you get `wss://` for free). Run one pointing at port `9442`, then use the `wss://…/voice/msteams/stream` form of the printed host (Tailscale Funnel only serves 443/8443/10000, so mount the bridge path on 443 and use a portless `wss://` URL): Tailscale Funnel: ```bash -tailscale funnel --bg --https=8080 8080 +tailscale funnel --bg --set-path /voice/msteams/stream http://127.0.0.1:9442/voice/msteams/stream +# Agent voice URL: wss://..ts.net/voice/msteams/stream ``` Cloudflare Tunnel: ```bash -cloudflared tunnel --url http://localhost:8080 +cloudflared tunnel --url http://localhost:9442 ``` ngrok: ```bash -ngrok http 8080 +ngrok http 9442 ``` VS Code dev tunnels: ```bash -devtunnel host -p 8080 --allow-anonymous +devtunnel host -p 9442 --allow-anonymous ``` For a fixed production host use an ingress/load balancer, or serve TLS natively with `TLS_CERT_PATH` + `TLS_KEY_PATH`. Never give StandIn a plain `ws://` URL outside local testing.