@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 +9 -0
- package/DISCOVERY.md +55 -0
- package/README.md +162 -0
- package/SUPPORT.md +19 -0
- package/dist/chunk-4BR5AGTV.js +912 -0
- package/dist/chunk-4BR5AGTV.js.map +7 -0
- package/dist/chunk-ESH3YW7D.js +4464 -0
- package/dist/chunk-ESH3YW7D.js.map +7 -0
- package/dist/cli.js +19 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +1174 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +7 -0
- package/dist/server.d.ts +1543 -0
- package/dist/server.js +12 -0
- package/dist/server.js.map +7 -0
- package/openapi.yaml +737 -0
- package/package.json +121 -0
package/CHANGELOG.md
ADDED
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 | ✅ | |
|