@emulates/daily 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog — @emulates/daily
2
+
3
+ ## 2.3.1 (2026-10-06)
4
+
5
+ Initial release.
6
+
7
+ ### Dependencies
8
+
9
+ - `@emulates/sqlite`
package/DISCOVERY.md ADDED
@@ -0,0 +1,55 @@
1
+ # @emulates/daily discovery
2
+
3
+ This is the installed-package index for coding agents and tooling. All relative links resolve
4
+ inside `node_modules/@emulates/daily/`; no repository checkout is needed to discover the emulator's
5
+ supported surface or documented behavior.
6
+
7
+ ## Capability and behavior sources
8
+
9
+ | Question | Authoritative file | What it contains |
10
+ | --- | --- | --- |
11
+ | Behaviour and integration | [`README.md`](README.md) | Routes, state transitions, auth, webhooks, controls, presets and deliberate omissions. |
12
+ | Exact capabilities | [`SUPPORT.md`](SUPPORT.md) | Supported, unsupported and parity-covered operations or commands, including reasons for gaps. |
13
+ | Wire contract | [`openapi.yaml`](openapi.yaml) | Machine-readable paths, methods, schemas, responses and parity annotations. |
14
+ | Public API | [`dist/index.d.ts`](dist/index.d.ts) | The installed package's exact TypeScript exports and signatures. |
15
+ | Package metadata | [`package.json`](package.json) | Runtime/entry-point claims, vendor links, parity scope/tier and `emulates.discovery`. |
16
+
17
+ Read these together: the contract/capability matrix says *what* is available, while the README
18
+ defines stateful behavior, lifecycle rules, test controls, and intentional oracle differences.
19
+ If prose and an executable surface disagree, report a parity mismatch instead of adding a
20
+ consumer-side workaround.
21
+
22
+ ## Parity and oracle
23
+
24
+ - Declared parity surface: **Rooms, tokens, and webhooks**.
25
+ - Parity tier: **cold** (the repository controls when live checks run).
26
+ - Oracle: **Live vendor API or sandbox**.
27
+ - Repository command: `bun run parity:service -- daily`.
28
+ - Evidence model: Run from an Emulates checkout; credentials come only from .env.local or GitHub Actions secrets. Missing credentials exit 2.
29
+
30
+ The npm package contains evidence summaries and the exact contract, not credentials or the
31
+ repository-only parity harness. Self-parity/property and acceptance tests run in the Emulates
32
+ repository; live parity is an additional oracle check, not a substitute for the packaged matrix.
33
+
34
+ ## Runtime introspection
35
+
36
+ - `GET /__admin/health`
37
+ - `GET /__admin`
38
+ - `GET /__admin/state`
39
+ - `GET /__admin/requests`
40
+ - `GET /__admin/metrics`
41
+ - `GET /__admin/faults/presets`
42
+ - `GET /__admin/ui`
43
+
44
+ For HTTP services, use `x-emulates-namespace` (or the documented credential/path carrier) so
45
+ parallel tests do not share state. Admin state, journal, metrics and fault-preset endpoints are
46
+ designed for assertions and diagnosis by consuming test suites.
47
+
48
+ ## Report a mismatch or missing capability
49
+
50
+ Follow the [agent reporting contract](https://github.com/crvouga/emulators/blob/main/docs/REPORTING_ISSUES.md). Include package version,
51
+ operation/command, a minimal redacted request, actual emulator result, expected oracle result or vendor
52
+ documentation, and whether the mismatch appears in the matrix. Never include keys, tokens,
53
+ customer data, prompts, PHI, card data, or unredacted recordings.
54
+
55
+ Service key: `daily`.
package/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # @emulates/daily
2
+
3
+ > Part of [Emulates](https://github.com/crvouga/emulators): high-fidelity, in-process emulators for APIs and databases.
4
+
5
+ Stateful emulator of the **Daily.co** REST API for test suites: rooms (create, get, update,
6
+ delete, presence, eject), meeting tokens (mint and validate), verification of the HS256 meeting
7
+ tokens our backend signs itself, and the end-of-call webhooks (`transcription.stopped`,
8
+ `recording.ready-to-download`) with the transcript written to the stack's S3. Every EMR
9
+ booking creates a room and a token; today a broken Daily integration is silent because booking
10
+ swallows the error. Against the emulator it is observable and assertable.
11
+
12
+ - Operation coverage: [SUPPORT.md](https://github.com/crvouga/emulators/blob/main/packages/service/daily/SUPPORT.md)
13
+ - The contract (`openapi.yaml`) is trimmed from Daily's documented REST API to the calls our
14
+ backend and EMR make, with the fields they send.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm install -D @emulates/daily
20
+ ```
21
+
22
+ ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with
23
+ `npx emulates-daily serve`, `createServer` from `./server` (Node), or `createRuntime` with
24
+ any Fetch server.
25
+
26
+ ## Usage
27
+
28
+ Point the apps at the emulator (the G-D1 seams):
29
+
30
+ | App | Env | Value |
31
+ | --- | --- | --- |
32
+ | backend | `DAILY_API_BASE_URL` | `http://127.0.0.1:8800/v1` (needs the http-loopback exception) |
33
+ | backend | `DAILY_API_KEY`, `DAILY_API_DOMAIN_ID` | any key (or `--api-key`), and the same value as `--domain-id` |
34
+ | EMR backend | `DailyService.baseUrl` | `http://127.0.0.1:8800/v1` (hardcoded today) |
35
+ | EMR backend | `DEFAULT_DAILY_BASE_URL` | the same value as `--room-url-base` |
36
+ | EMR backend | `DAILY_WEBHOOK_SECRET` | the same value as `--webhook-secret` |
37
+ | member-app | `EXPO_PUBLIC_DAILY_BASE_URL` | the same value as `--room-url-base` |
38
+
39
+ ```bash
40
+ npx emulates-daily serve --port 8800 \
41
+ --room-url-base https://acme-mock.daily.test/ \
42
+ --domain-id "$DAILY_API_DOMAIN_ID" \
43
+ --webhook-url http://127.0.0.1:4000/v1/webhooks/daily \
44
+ --webhook-secret "$DAILY_WEBHOOK_SECRET" \
45
+ --s3-endpoint http://127.0.0.1:4569 --s3-bucket "$S3_BUCKET_NAME"
46
+ ```
47
+
48
+ ```ts
49
+ import { createRuntime } from "@emulates/daily"
50
+
51
+ const daily = createRuntime({
52
+ settings: { roomUrlBase: "https://acme-mock.daily.test/" },
53
+ webhooks: { url: "http://127.0.0.1:4000/v1/webhooks/daily", secret: "whsec-daily" },
54
+ transcripts: { endpoint: "http://127.0.0.1:4569", bucket: "emr-transcripts" },
55
+ })
56
+ const admin = (path: string, body: unknown) =>
57
+ daily.fetch(
58
+ new Request(`http://daily.test/__admin${path}`, {
59
+ method: "POST",
60
+ headers: { "content-type": "application/json" },
61
+ body: JSON.stringify(body),
62
+ }),
63
+ )
64
+
65
+ // …the EMR books an appointment: POST /v1/rooms + POST /v1/meeting-tokens…
66
+
67
+ // End the call: writes <room>/<session>.json to S3, then fires transcription.stopped.
68
+ await admin("/rooms/<room name>/session", {
69
+ participants: [{ userId: "prac-1" }, { userId: "pat-1" }],
70
+ durationSec: 1200,
71
+ transcript: [{ s: "prac-1", t: "How are you feeling?", ts: 0.5, te: 2.1 }],
72
+ })
73
+ ```
74
+
75
+ ### Routes
76
+
77
+ | Route | Behaviour |
78
+ | --- | --- |
79
+ | `GET /v1/rooms` | `{total_count, data}` in newest-first creation order; `limit` (default 100), `ending_before` and `starting_after` use room IDs. Shares state with room creation, updates and deletion. |
80
+ | `POST /v1/rooms` | `{name?, privacy?, properties?}`; unknown properties, bad types and a duplicate `name` are 400 `{error: "invalid-request-error", info}`. No name → a random 20-character one. Answers `{id, name, api_created, privacy, url: <roomUrlBase><name>, created_at, config}` with `config` echoing the properties. Accepts both the backend's strict body and the EMR's `generateRoomConfig` body. |
81
+ | `GET /v1/rooms/:name` | The room, or 404 `{error: "not-found", info: "room <name> not found"}` (the EMR branches on `message.includes('404')`). |
82
+ | `POST /v1/rooms/:name` | Merge `properties` (and `privacy`) into the room; 404 when missing. |
83
+ | `DELETE /v1/rooms/:name` | `{deleted: true, name}`; 404 when missing (the EMR tolerates it). |
84
+ | `GET /v1/rooms/:name/presence` | `{total_count, data: [{room, id, userId, userName, joinTime, duration}]}` — exactly the backend's strict schema. Participants come from `PUT /__admin/rooms/:name/presence`. |
85
+ | `POST /v1/rooms/:name/eject` | `{user_ids?, ids?}` → `{ejectedIds}` (participant session ids), removing them from presence. |
86
+ | `POST /v1/meeting-tokens` | `{properties}` → `{token}`: an HS256 JWT signed with the caller's API key, claims under Daily's abbreviations (`r`, `d`, `o`, `u`, `ud`, `nbf`, `exp`, `ejt`, `eje`, `er`, `erui`, `sr`, `ast`, `p`, …) plus `iat`. |
87
+ | `GET /v1/meeting-tokens/:token` | Verifies a token (minted by the emulator **or self-signed by our backend**) with the caller's API key and its `nbf`/`exp` on the emulator clock (`?ignoreNbf=true` skips nbf); answers its properties under full names, else 400. |
88
+
89
+ **Auth.** `Authorization: Bearer <DAILY_API_KEY>`; any key unless `apiKeys` is set. Missing →
90
+ 401 `{error: "authentication-error"}`.
91
+
92
+ **Milliseconds.** Some of our callers pass `nbf`/`exp` in milliseconds (e.g. the EMR's
93
+ `generatePatientToken` passes `Date.parse(end)`). The emulator accepts them, interprets values
94
+ ≥ 1e11 as ms in time checks, records each in `GET /__admin/warnings`, and tags the journal entry
95
+ (`ids.warning: "exp in milliseconds"`).
96
+
97
+ ### Webhooks
98
+
99
+ Admin sessions post Daily-shaped events: `{version: "1.0.0", type, event, id, event_ts,
100
+ payload}` (our receiver reads `event`; Daily documents `type`; both are sent).
101
+
102
+ - `transcription.stopped` — `payload: {room_name, session_id, duration, s3_key, instance_id}`.
103
+ - `recording.ready-to-download` — `payload: {type: "cloud", recording_id, room_name, session_id,
104
+ start_ts, status: "finished", max_participants, duration, s3_key}` (our EMR just acks it).
105
+
106
+ **Signature — our scheme, not Daily's.** `x-webhook-signature` = **hex**
107
+ HMAC-SHA256(`DAILY_WEBHOOK_SECRET`, raw body), which is what our EMR verifies (only when the
108
+ secret is set). Daily's documented scheme is different — base64 HMAC-SHA256 over
109
+ `"<X-Webhook-Timestamp>.<body>"` with the base64-decoded secret — so a real Daily webhook would
110
+ fail our check; the emulator signs the way our code checks. `x-webhook-timestamp` is sent too.
111
+ Retries: immediately, 5 s, 5 min, 30 min, 2 h; `GET /__admin/webhooks`, `…/events`,
112
+ `…/replay`, `…/flush` as usual.
113
+
114
+ ### Admin (beyond the standard contract)
115
+
116
+ | Route | Effect |
117
+ | --- | --- |
118
+ | `POST /__admin/rooms/:name/session` | `{participants: [{userId, userName?}], durationSec, transcript?: [{s, t, ts, te}], sessionId?, recording?}` writes the transcript JSON to S3 at `{roomName}/{sessionId}.json` (SigV4 `PutObject` to the `--s3-endpoint` / `transcripts` target; a synthetic transcript alternating between participants when none is given), clears presence, then emits `transcription.stopped` (and `recording.ready-to-download` unless `recording: false`). Answers `{sessionId, s3Key, transcript: "s3://…" \| null, events}`; 502 when S3 refuses. The transcript text is never kept. |
119
+ | `PUT /__admin/rooms/:name/presence` | `{participants: [{userId, userName?, joinedAt?}]}` — who `GET …/presence` reports. |
120
+ | `POST /__admin/tokens/decode` | `{token, apiKey?}` → `{decodable, header, claims, properties, signatureValid, room, joinable, problems, warnings}`: signature (against `apiKey` or `apiKeys`), the backend's strict claim set, `ud` ≤ 36, domain id, ms timestamps, and the token and room `nbf`/`exp` windows on the emulator clock (owners may enter before the room's `nbf`). |
121
+ | `GET /__admin/rooms`, `GET /__admin/rooms/:name` | Rooms with config, presence and session metadata. |
122
+ | `GET /__admin/warnings` | Tolerated oddities (millisecond timestamps, tokens for rooms that do not exist). |
123
+ | `PUT /__admin/settings` | `{apiKeys?, domainId?, roomUrlBase?}` for the calling namespace (`GET` masks keys). |
124
+
125
+ Time rules run on the emulator clock (`POST /__admin/clock`): token/room `nbf` and `exp`, and
126
+ therefore the backend's 13 h room-creation delay and 30-min guardrail window as seen by Daily.
127
+
128
+ Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`): `room_not_found`
129
+ (404 on `/v1/rooms/:name…`), `unauthorized` (401), `rate_limited` (429), `server_error` (500),
130
+ `webhook_duplicate`, `webhook_reorder`, `webhook_drop`.
131
+
132
+ ### Namespaces
133
+
134
+ `x-emulates-namespace`, a `/__admin/ns/<name>` prefix on the base URL, or by API key:
135
+ `PUT /__admin/credentials {"credentials": {"<DAILY_API_KEY>": "<namespace>"}}`.
136
+
137
+ ### Deliberately not modelled
138
+
139
+ - The media plane: SFU, WebRTC, knocking/admission, recording and transcription themselves.
140
+ daily-js loads Daily's CDN bundle; member-app UI tests should inject a fake `DailyCallLike`
141
+ through `useCallProviderLogic(createCallObject)`.
142
+ - The room page at `https://<domain>.daily.co/<room>?t=` (the URL is produced, not served).
143
+ - Daily's own webhook signature scheme and webhook registration API (see above).
144
+ - Recordings/transcripts REST APIs, dial-out, streaming.
145
+
146
+ ## API
147
+
148
+ | Export | Kind | Description |
149
+ | --- | --- | --- |
150
+ | `DailyAPI` | class | The in-process emulator: `fetch(request)`, `reset()`, `inspectToken(token, keys?)`, `setPresence(name, participants)`, `endSession(name, input)`, `rooms()`. Options: `sqlite`, `now`, `namespace`, `settings`, `onWebhook`, `transcripts`. |
151
+ | `createRuntime` | function | The emulator with the full service contract. Options: `webhooks: {url, secret, retryDelaysMs?, fetch?}`, `transcripts: {endpoint, bucket, region?, accessKeyId?, secretAccessKey?, keyPattern?, fetch?}`, `settings`, `clock`, `seed`, `adminKey`, `onLog`. |
152
+ | `DAILY_PRESETS` | object | Every named fault preset. |
153
+ | `DAILY_NAMESPACE` | string | The service name, `"daily"`. |
154
+ | `WEBHOOK_PATH` | string | `"/v1/webhooks/daily"`, our EMR receiver's route. |
155
+ | `dailyWebhookSigner` | function | The `x-webhook-signature` signer (hex HMAC-SHA256 of the raw body). |
156
+ | `signToken`, `decodeToken`, `verifySignature` | functions | HS256 meeting-token helpers. |
157
+ | `claimsToProperties`, `propertiesToClaims`, `CLAIM_NAMES` | values | Daily's abbreviated claim names ↔ full property names. |
158
+ | `synthesizeTranscript` | function | The default transcript for a session with none given. |
159
+ | `document`, `operationIds`, `supportedOperationIds` | values | The vendored OpenAPI contract and its operation ids. |
160
+ | `createServer`, `serveTarget`, `DEFAULT_PORT` (`./server`) | Node | Serve over `node:http`; the `serve` CLI target; port 8800. |
161
+
162
+ Part of [emulators](https://github.com/crvouga/emulators).
package/SUPPORT.md ADDED
@@ -0,0 +1,19 @@
1
+ # Daily.co REST API (Emulates subset) — operation support
2
+
3
+ Generated from `openapi.yaml`; do not edit by hand.
4
+
5
+ - operations in spec: **9**
6
+ - supported by the emulator: **9**
7
+ - parity enabled: **9**
8
+
9
+ | operationId | route | emulator | parity | notes |
10
+ | --- | --- | --- | --- | --- |
11
+ | `ListRooms` | `GET /v1/rooms` | ✅ supported | ✅ | |
12
+ | `CreateRoom` | `POST /v1/rooms` | ✅ supported | ⚠️ unsafe (opt-in) | |
13
+ | `GetRoom` | `GET /v1/rooms/{name}` | ✅ supported | ✅ | |
14
+ | `UpdateRoom` | `POST /v1/rooms/{name}` | ✅ supported | ⚠️ unsafe (opt-in) | |
15
+ | `DeleteRoom` | `DELETE /v1/rooms/{name}` | ✅ supported | ⚠️ unsafe (opt-in) | |
16
+ | `GetRoomPresence` | `GET /v1/rooms/{name}/presence` | ✅ supported | ✅ | |
17
+ | `EjectParticipants` | `POST /v1/rooms/{name}/eject` | ✅ supported | ⚠️ unsafe (opt-in) | |
18
+ | `CreateMeetingToken` | `POST /v1/meeting-tokens` | ✅ supported | ✅ | |
19
+ | `ValidateMeetingToken` | `GET /v1/meeting-tokens/{token}` | ✅ supported | ✅ | |