first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
# `socket`
Wrapper around any `WebSocketLike` (the browser global, `ws` from Node,
or a fake from tests). Exposes a typed frame-level API and a
`Promise<void>` for the open handshake.
## Purpose
Raw WebSocket APIs are awkward — open / message / close / error are
delivered as untyped events, binary frames arrive as `Blob` /
`ArrayBuffer` / typed-array views depending on platform, and there's no
built-in way to await the open handshake. `ElevenLabsSocket` papers over
all of that so adapters that need WebSocket transport (e.g. the
streaming-input TTS endpoint) can `await sock.waitOpen()` and then send
typed `SocketFrame` values without touching the raw event surface.
## Features
- `waitOpen()` — promise that resolves on `open` and rejects on `error`
or caller-signal abort.
- `send(frame: SocketFrame)` — typed text or binary send; binary frames
are copied into a fresh `ArrayBuffer` so the underlying WS impl never
sees a SharedArrayBuffer-backed view by accident.
- `onMessage` / `onClose` / `onError` — listeners returning unsubscribe
functions.
- Accepts `Uint8Array`, `ArrayBuffer`, or any `ArrayBufferView` on the
wire and normalises them to `Uint8Array` before dispatching.
- Sends on a closed socket throw `WebSocketProtocolError` instead of
silently dropping the frame.
- Pluggable `WebSocketConstructor` so tests inject a fake.
## Usage: Basic Example
```ts
import { ElevenLabsClient } from "@suaveplan/third-party-elevenlabs";
const client = new ElevenLabsClient({ apiKey: process.env.KEY! });
const sock = client.openSocket({
path: "/v1/text-to-speech/voice-id/stream-input",
feature: "tts",
operation: "stream_input",
query: { model_id: "eleven_flash_v2" },
});
await sock.waitOpen();
sock.onMessage((f) => process.stdout.write(f.data));
sock.send({ kind: "text", data: JSON.stringify({ text: "hello" }) });
```
## Usage: Advanced Example
```ts
import { ElevenLabsClient } from "@suaveplan/third-party-elevenlabs";
const ctrl = new AbortController();
const client = new ElevenLabsClient({
apiKey: KEY,
webSocketImpl: WS, // 'ws' on Node
});
const sock = client.openSocket({
path: "/v1/text-to-speech/abc/stream-input",
feature: "tts",
operation: "stream_input",
});
const unsubMsg = sock.onMessage((f) => sink.write(f.data));
const unsubClose = sock.onClose((e) =>
log.info("closed", { code: e.code, clean: e.wasClean })
);
sock.onError((err) => log.warn("ws error", { err }));
try {
await sock.waitOpen();
for (const chunk of corpus) {
sock.send({ kind: "text", data: JSON.stringify({ text: chunk }) });
}
} finally {
unsubMsg();
unsubClose();
sock.close(1000, "done");
}
ctrl.abort();
```
## API Reference
- `new ElevenLabsSocket(ws: WebSocketLike, endpoint: string, signal?: AbortSignal)`
- `waitOpen(): Promise<void>`
- `readyState: number` — 0 connecting / 1 open / 2 closing / 3 closed.
- `send(frame: SocketFrame): void` — `{ kind: "text" | "binary", data }`
- `close(code?: number, reason?: string): void`
- `onMessage(listener): () => void`
- `onClose(listener): () => void`
- `onError(listener): () => void`
## Implementation Notes
- The `xi-api-key` is sent as a query parameter (browsers can't set
headers on the WS upgrade) — that's done in `ElevenLabsClient.openSocket`,
not here.
- Binary `send` always copies into a fresh `ArrayBuffer` to defend
against SharedArrayBuffer leakage; the cost is one heap allocation per
outbound binary frame.
- The internal `#wireOpenPromise` removes its `error` listener once the
promise settles, so post-open transport errors flow only into the
user-installed `onError` listeners — they don't reject `waitOpen()`
twice.
- `bytesFrom` rejects unsupported binary payload types with
`WebSocketProtocolError`. The error is dispatched to `onError` rather
than thrown synchronously, since it originates inside an event handler
callback.