@strevokit/meeting-sdk 0.6.0 → 0.8.0

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.
@@ -30,6 +30,8 @@ Throws synchronously:
30
30
  | `realtimeUrl` | `string` | yes | Realtime endpoint base URL, `wss://host[:port]`. `ws://` is accepted only for `localhost` and `127.0.0.1`. No path, query, fragment, or credentials. |
31
31
  | `joinTimeoutMs` | `number` | no | How long `join()` waits for admission before rejecting with `join_timeout`. Integer, 1000–60000. Default 15000. |
32
32
  | `logger` | `MeetingLogger` | no | Diagnostics sink. Receives short messages and allow-listed fields only. |
33
+ | `getJoinToken` | `() => Promise<string>` | no | Called when automatic recovery needs a fresh join token and the in-memory token cannot be used. Resolve with a token your backend issued for the same participant; reject to end recovery. Kept in memory only and never logged. |
34
+ | `reconnectWindowMs` | `number` | no | Continuous unsuccessful recovery budget in milliseconds. Default `120000`; values outside `1000`–`600000` are clamped. |
33
35
 
34
36
  ### `MeetingLogger`
35
37
 
@@ -129,6 +131,12 @@ microphone) to `enableCamera()` or `unmute()`. Without one, the browser's defaul
129
131
  | Muted, with another device kept open | `unmute({ deviceId })` | The chosen microphone is captured and announced; the previous one is released. |
130
132
  | Unmuted with another device | `unmute({ deviceId })` | Switches in place, as for the camera. |
131
133
 
134
+ If the camera or an unmuted microphone ends on its own, and another device of that kind is listed, capture continues
135
+ on that device. `local_media_changed` fires with the kind still on and the new stream. The change is not announced as
136
+ stopped or started. When no other device can be opened, the kind turns off and is announced. A muted microphone that
137
+ ends is not replaced and is not unmuted. Calls made while the engine is `reconnecting` reject with `not_joined` and
138
+ do not capture.
139
+
132
140
  If the chosen device is missing, the call rejects with `device_not_found`; if another app is using it, with
133
141
  `device_unavailable`. Either way, the device already in use keeps running. A pending call is shared only by calls for
134
142
  the same state **and** the same device. Device IDs are never logged. Read the device in use from the track:
@@ -137,7 +145,14 @@ the same state **and** the same device. Device IDs are never logged. Read the de
137
145
  ## States: `ConnectionState`
138
146
 
139
147
  ```ts
140
- type ConnectionState = "idle" | "connecting" | "joining" | "joined" | "leaving" | "closed";
148
+ type ConnectionState =
149
+ | "idle"
150
+ | "connecting"
151
+ | "joining"
152
+ | "joined"
153
+ | "reconnecting"
154
+ | "leaving"
155
+ | "closed";
141
156
  ```
142
157
 
143
158
  | State | Meaning |
@@ -146,6 +161,7 @@ type ConnectionState = "idle" | "connecting" | "joining" | "joined" | "leaving"
146
161
  | `connecting` | Connecting and presenting the join token. |
147
162
  | `joining` | The token was accepted; waiting for the participant list. |
148
163
  | `joined` | In the meeting. Media methods are available. |
164
+ | `reconnecting` | Previously joined; automatic recovery of the meeting-service connection is in progress. Success returns to `joined` (same self identity; `joined` is not re-emitted). |
149
165
  | `leaving` | `leave()` was called; waiting for the platform to confirm. |
150
166
  | `closed` | Finished. The engine cannot be reused; create a new one with a new token. |
151
167
 
@@ -272,6 +288,8 @@ it; otherwise `null`.
272
288
  | `interrupted` | The connection dropped after working; it may recover on its own. |
273
289
  | `failed` | The connection could not be set up or did not recover. An `error` event with `media_connection_failed` accompanies it. Rejoining starts a new attempt. |
274
290
 
291
+ While a peer link is alive, the SDK may periodically report aggregated media-health samples (RTT, jitter, loss, bitrates, and path/connectivity indicators) to the realtime service for operator diagnostics. Sampling failures never tear down media.
292
+
275
293
  ### `ParticipantLeftReason`
276
294
 
277
295
  | Reason | Meaning |
@@ -0,0 +1,370 @@
1
+ # Backend API for meeting clients
2
+
3
+ Your backend prepares each meeting with the Strevokit HTTP API, then mints a short-lived **join token** for the
4
+ browser. The meeting SDK only ever receives that token — never your API credential.
5
+
6
+ Base URL in the examples: `https://api.example.com` (replace with your Strevokit API host).
7
+
8
+ ## Auth
9
+
10
+ Every route below requires:
11
+
12
+ ```http
13
+ Authorization: Bearer <STREVOKIT_API_CREDENTIAL>
14
+ Content-Type: application/json
15
+ ```
16
+
17
+ Keep `STREVOKIT_API_CREDENTIAL` on the server only. Never put it in browser code, `VITE_*` / `NEXT_PUBLIC_*`
18
+ variables, or mobile apps.
19
+
20
+ Meeting and participant identifiers in paths and JSON are opaque `public_id` values (26-character Crockford ULIDs).
21
+ Responses never include numeric internal database ids.
22
+
23
+ ## Route index
24
+
25
+ | Method | Path | Purpose |
26
+ |--------|------|---------|
27
+ | `POST` | `/v1/embed-origins` | Allow-list a browser origin that may join |
28
+ | `GET` | `/v1/embed-origins` | List allow-listed origins |
29
+ | `DELETE` | `/v1/embed-origins/{public_id}` | Remove an allow-listed origin |
30
+ | `POST` | `/v1/meetings` | Create a meeting |
31
+ | `GET` | `/v1/meetings/{public_id}` | Fetch a meeting |
32
+ | `POST` | `/v1/meetings/{public_id}/participants` | Authorize (register) a participant |
33
+ | `POST` | `/v1/meetings/{public_id}/join-token` | Mint a join token for one participant |
34
+ | `POST` | `/v1/meetings/{public_id}/end` | End the meeting for everyone |
35
+
36
+ ## Integration sequence
37
+
38
+ 1. **Allow-list your app origin** (`POST /v1/embed-origins`) once per environment (for example `https://app.example.com`).
39
+ Joins from other origins fail with `origin_not_allowed` at the realtime handshake.
40
+ 2. **Create a meeting** (`POST /v1/meetings`) when your product starts a session. Store `data.public_id`.
41
+ 3. **Authorize each person** (`POST /v1/meetings/{public_id}/participants`) as `host` or `participant`. Store each
42
+ `data.public_id` (the participant public id).
43
+ 4. **Mint a join token** (`POST /v1/meetings/{public_id}/join-token`) when that user opens the meeting page. Return
44
+ only `data.token` to the browser.
45
+ 5. **Join in the browser** with `createMeetingEngine({ token, realtimeUrl })` (see the [README](../README.md)).
46
+
47
+ Optional later: `GET /v1/meetings/{public_id}` to read status; `POST /v1/meetings/{public_id}/end` to end from your
48
+ backend.
49
+
50
+ ---
51
+
52
+ ## POST /v1/embed-origins
53
+
54
+ Register one browser origin that may open meetings.
55
+
56
+ ```http
57
+ POST /v1/embed-origins
58
+ Authorization: Bearer <STREVOKIT_API_CREDENTIAL>
59
+ Content-Type: application/json
60
+ ```
61
+
62
+ ```json
63
+ { "origin": "https://app.example.com" }
64
+ ```
65
+
66
+ `origin` must be a full web origin (`http` or `https`, host, optional non-default port). No path, query, fragment, or
67
+ wildcards.
68
+
69
+ **Success:** `201` when newly inserted, `200` when the same origin already exists.
70
+
71
+ ```json
72
+ {
73
+ "data": {
74
+ "public_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
75
+ "origin": "https://app.example.com",
76
+ "created_at": "2026-09-24T12:00:00.000000Z"
77
+ }
78
+ }
79
+ ```
80
+
81
+ Also available: `GET /v1/embed-origins` (list) and `DELETE /v1/embed-origins/{public_id}` (`204` on success). An empty
82
+ allow-list means joins fail closed until you add at least one origin.
83
+
84
+ ---
85
+
86
+ ## POST /v1/meetings
87
+
88
+ Create a meeting.
89
+
90
+ ```http
91
+ POST /v1/meetings
92
+ Authorization: Bearer <STREVOKIT_API_CREDENTIAL>
93
+ Content-Type: application/json
94
+ Idempotency-Key: <optional opaque string>
95
+ ```
96
+
97
+ ```json
98
+ {
99
+ "type": "one_to_one",
100
+ "max_participants": 2,
101
+ "recording_requested": false,
102
+ "external_reference": "session_9876",
103
+ "empty_timeout_seconds": 300
104
+ }
105
+ ```
106
+
107
+ | Field | Required | Notes |
108
+ |-------|----------|--------|
109
+ | `type` | yes | `one_to_one` or `one_to_many` |
110
+ | `max_participants` | yes | `2` for `one_to_one`; `3`–`50` for `one_to_many` |
111
+ | `recording_requested` | no | Default `false`. Alias `recording` is accepted |
112
+ | `scheduled_start` | no | ISO-8601 with timezone; stored as `scheduled_at` |
113
+ | `metadata` | no | Flat JSON object; keep it small |
114
+ | `external_reference` | no | Your own id, 1–255 characters when present; echoed unchanged |
115
+ | `empty_timeout_seconds` | no | `60`–`86400`; default `300`. Idle empty meetings end after this many seconds |
116
+
117
+ Do not send `tenant_id`, numeric `id`, or a client-chosen media mode — the platform selects delivery.
118
+
119
+ Optional `Idempotency-Key` (≤255 characters): same key + same body replays the first response; same key + different
120
+ body returns `409` `idempotency_conflict`.
121
+
122
+ **Success:** `201 Created` (idempotent replay may return the original status).
123
+
124
+ ```json
125
+ {
126
+ "data": {
127
+ "public_id": "01JB2Q8ABCDEF01234567890AB",
128
+ "external_reference": "session_9876",
129
+ "type": "one_to_one",
130
+ "max_participants": 2,
131
+ "recording_requested": false,
132
+ "recording_allowed": false,
133
+ "status": "created",
134
+ "scheduled_at": null,
135
+ "started_at": null,
136
+ "ended_at": null,
137
+ "end_reason": null,
138
+ "empty_timeout_seconds": 300,
139
+ "metadata": null,
140
+ "created_at": "2026-09-22T12:00:00+00:00",
141
+ "updated_at": "2026-09-22T12:00:00+00:00"
142
+ }
143
+ }
144
+ ```
145
+
146
+ The response may also include platform-selected fields (for example how media is delivered). Store `public_id` for
147
+ later calls.
148
+
149
+ | Case | HTTP | `error.code` |
150
+ |------|------|--------------|
151
+ | Missing or invalid credential | 401 | `unauthorized` |
152
+ | Bad or missing fields | 422 | `validation_failed` |
153
+ | Idempotency key reused with a different body | 409 | `idempotency_conflict` |
154
+ | Rate limited | 429 | `rate_limited` (+ `Retry-After`) |
155
+ | Suspended tenant | 403 | `forbidden` |
156
+
157
+ ---
158
+
159
+ ## GET /v1/meetings/{public_id}
160
+
161
+ Fetch the meeting resource for your tenant (same shape as create). Missing or other-tenant ids return `404`
162
+ `meeting_not_found`.
163
+
164
+ ---
165
+
166
+ ## POST /v1/meetings/{public_id}/participants
167
+
168
+ Authorize (register) a participant on a meeting. Call once per person who may join.
169
+
170
+ ```http
171
+ POST /v1/meetings/{public_id}/participants
172
+ Authorization: Bearer <STREVOKIT_API_CREDENTIAL>
173
+ Content-Type: application/json
174
+ Idempotency-Key: <optional opaque string>
175
+ ```
176
+
177
+ ```json
178
+ {
179
+ "external_user_id": "user_42",
180
+ "display_name": "Alex",
181
+ "role": "host"
182
+ }
183
+ ```
184
+
185
+ | Field | Required | Notes |
186
+ |-------|----------|--------|
187
+ | `external_user_id` | yes | Your user id within this meeting (1–255 chars; not whitespace-only) |
188
+ | `display_name` | yes | Shown name (1–255 chars; not whitespace-only) |
189
+ | `role` | yes | `host` or `participant` |
190
+
191
+ **Upsert:** the same `external_user_id` on the same meeting returns the same participant `public_id`, updates name and
192
+ role, and does not consume an extra capacity slot. A **new** identity is rejected with `422`
193
+ `participant_limit_exceeded` when the meeting already has `max_participants` distinct people.
194
+
195
+ **Display names:** Pass the name users should see in the roster as `display_name` when you authorize. The join-token
196
+ route and the browser (including this SDK) do not accept a name. If no usable name is available at join, the UI shows
197
+ **Guest**. To rename someone, re-authorize with the same `external_user_id` and a new `display_name`, then mint a new
198
+ join token and have them join again.
199
+
200
+ Optional `Idempotency-Key` behaves like create (same key + different body → `409` `idempotency_conflict`).
201
+
202
+ **Success:** `201` on first insert, `200` on upsert of an existing identity (idempotent replay returns the stored
203
+ status and body).
204
+
205
+ ```json
206
+ {
207
+ "data": {
208
+ "public_id": "01JQ4ABCDEF01234567890ABCD",
209
+ "external_user_id": "user_42",
210
+ "display_name": "Alex",
211
+ "role": "host",
212
+ "status": "invited"
213
+ }
214
+ }
215
+ ```
216
+
217
+ | Case | HTTP | `error.code` |
218
+ |------|------|--------------|
219
+ | Missing or invalid credential | 401 | `unauthorized` |
220
+ | Bad or missing fields | 422 | `validation_failed` |
221
+ | Meeting missing or other tenant | 404 | `meeting_not_found` |
222
+ | Meeting already `ended` | 409 | `meeting_ended` |
223
+ | Meeting `cancelled` / `failed` | 403 | `forbidden` |
224
+ | New identity over capacity | 422 | `participant_limit_exceeded` |
225
+ | Idempotency conflict | 409 | `idempotency_conflict` |
226
+ | Rate limited | 429 | `rate_limited` (+ `Retry-After`) |
227
+
228
+ ---
229
+
230
+ ## POST /v1/meetings/{public_id}/join-token
231
+
232
+ Mint a short-lived join token for one authorized participant. Mint a **new** token for every join (including rejoin
233
+ after leave). Do **not** send `Idempotency-Key` — each successful call issues a new token.
234
+
235
+ ```http
236
+ POST /v1/meetings/{public_id}/join-token
237
+ Authorization: Bearer <STREVOKIT_API_CREDENTIAL>
238
+ Content-Type: application/json
239
+ ```
240
+
241
+ ```json
242
+ { "participant_public_id": "01JQ4ABCDEF01234567890ABCD" }
243
+ ```
244
+
245
+ | Field | Required | Notes |
246
+ |-------|----------|--------|
247
+ | `participant_public_id` | yes | From authorize; must belong to this meeting |
248
+
249
+ Do not send `role`, `display_name`, `tenant_id`, or numeric `id`. Role and display name come from the authorized
250
+ participant row.
251
+
252
+ **Success:** `201 Created`
253
+
254
+ ```json
255
+ {
256
+ "data": {
257
+ "token": "<compact JWT>",
258
+ "expires_at": "2026-09-24T15:05:00Z",
259
+ "expires_in": 300
260
+ }
261
+ }
262
+ ```
263
+
264
+ Default TTL is 300 seconds. Treat `token` like a secret: do not log it, put it in URLs, or cache it (`Cache-Control:
265
+ no-store` on your app response). Return only the token string to the browser.
266
+
267
+ | Case | HTTP | `error.code` |
268
+ |------|------|--------------|
269
+ | Missing or invalid credential | 401 | `unauthorized` |
270
+ | Bad or missing `participant_public_id` | 422 | `validation_failed` |
271
+ | Meeting missing or other tenant | 404 | `meeting_not_found` |
272
+ | Participant missing or not on this meeting | 404 | `participant_not_found` |
273
+ | Meeting already `ended` | 409 | `meeting_ended` |
274
+ | Meeting `cancelled` / `failed` | 403 | `forbidden` |
275
+ | Join-token rate limited | 429 | `rate_limited` (+ `Retry-After`) |
276
+
277
+ Realtime handshake failures (after you hand the token to the SDK) include `invalid_join_token`, `join_token_expired`,
278
+ and `origin_not_allowed`.
279
+
280
+ ---
281
+
282
+ ## POST /v1/meetings/{public_id}/end
283
+
284
+ End the meeting for everyone from your backend. Optional `Idempotency-Key`. Success returns the updated meeting
285
+ resource with `status` `ended` and `end_reason` `tenant_api`.
286
+
287
+ ---
288
+
289
+ ## Errors
290
+
291
+ Error bodies use a stable `error.code` string (and a message). On `429`, honor `Retry-After`. Prefer mapping known
292
+ codes in your backend rather than parsing message text.
293
+
294
+ ## End-to-end example (Node.js)
295
+
296
+ ```ts
297
+ const STREVOKIT_API = "https://api.example.com";
298
+ const API_CREDENTIAL = process.env.STREVOKIT_API_CREDENTIAL!;
299
+
300
+ async function strevokit(path: string, init: RequestInit = {}) {
301
+ const response = await fetch(`${STREVOKIT_API}${path}`, {
302
+ ...init,
303
+ headers: {
304
+ Authorization: `Bearer ${API_CREDENTIAL}`,
305
+ "Content-Type": "application/json",
306
+ ...(init.headers ?? {}),
307
+ },
308
+ });
309
+ if (!response.ok) throw new Error(`Strevokit ${path} → HTTP ${response.status}`);
310
+ if (response.status === 204) return null;
311
+ return response.json();
312
+ }
313
+
314
+ // Once per environment:
315
+ await strevokit("/v1/embed-origins", {
316
+ method: "POST",
317
+ body: JSON.stringify({ origin: "https://app.example.com" }),
318
+ });
319
+
320
+ // When your product starts a session:
321
+ const { data: meeting } = await strevokit("/v1/meetings", {
322
+ method: "POST",
323
+ headers: { "Idempotency-Key": crypto.randomUUID() },
324
+ body: JSON.stringify({
325
+ type: "one_to_one",
326
+ max_participants: 2,
327
+ external_reference: "booking_123",
328
+ }),
329
+ });
330
+
331
+ const { data: host } = await strevokit(`/v1/meetings/${meeting.public_id}/participants`, {
332
+ method: "POST",
333
+ body: JSON.stringify({
334
+ external_user_id: "user_host",
335
+ display_name: "Alex",
336
+ role: "host",
337
+ }),
338
+ });
339
+
340
+ const { data: guest } = await strevokit(`/v1/meetings/${meeting.public_id}/participants`, {
341
+ method: "POST",
342
+ body: JSON.stringify({
343
+ external_user_id: "user_guest",
344
+ display_name: "Sam",
345
+ role: "participant",
346
+ }),
347
+ });
348
+
349
+ // When that signed-in user opens the meeting page:
350
+ const { data: issued } = await strevokit(`/v1/meetings/${meeting.public_id}/join-token`, {
351
+ method: "POST",
352
+ body: JSON.stringify({ participant_public_id: host.public_id }),
353
+ });
354
+ // Return issued.token to the browser only.
355
+ ```
356
+
357
+ ## Hand the token to the SDK
358
+
359
+ ```ts
360
+ import { createMeetingEngine } from "@strevokit/meeting-sdk";
361
+
362
+ const engine = createMeetingEngine({
363
+ token, // from your backend
364
+ realtimeUrl: "wss://realtime.example.com",
365
+ });
366
+ await engine.join();
367
+ ```
368
+
369
+ A longer Express-style proxy that creates, authorizes, and mints is in
370
+ [examples/backend-integration.md](./examples/backend-integration.md).
@@ -0,0 +1,103 @@
1
+ # Backend integration (create, authorize, join token)
2
+
3
+ The meeting SDK needs a join token for each participant. Tokens are short-lived and single-use. Only your backend can
4
+ mint them, because minting needs your Strevokit API credential.
5
+
6
+ Before minting, your backend usually **creates** the meeting and **authorizes** participants. The full HTTP reference
7
+ (methods, paths, bodies, errors) is in [Backend API](../backend-api.md).
8
+
9
+ **Keep the API credential on your server.** Never ship it in browser code, environment variables exposed to the
10
+ browser (such as `VITE_*` or `NEXT_PUBLIC_*`), or mobile apps. The browser only ever receives the join token.
11
+
12
+ ## Flow
13
+
14
+ 1. (Once) Allow-list the origin that hosts your meeting page (`POST /v1/embed-origins`).
15
+ 2. When your product starts a session, create a meeting (`POST /v1/meetings`) and authorize each person
16
+ (`POST /v1/meetings/{public_id}/participants`).
17
+ 3. The signed-in user opens a meeting page in your app.
18
+ 4. The page asks your backend for a join token.
19
+ 5. Your backend checks that this user may join, then calls `POST /v1/meetings/{public_id}/join-token`.
20
+ 6. Your backend returns only the token to the page, which passes it to `createMeetingEngine({ token, realtimeUrl })`.
21
+
22
+ ## Example (Node.js with Express)
23
+
24
+ ```ts
25
+ import express from "express";
26
+
27
+ const app = express();
28
+ app.use(express.json());
29
+
30
+ const STREVOKIT_API = "https://api.example.com"; // your Strevokit API base URL
31
+ const API_CREDENTIAL = process.env.STREVOKIT_API_CREDENTIAL; // server-side secret, never sent to browsers
32
+
33
+ const strevokitHeaders = {
34
+ Authorization: `Bearer ${API_CREDENTIAL}`,
35
+ "Content-Type": "application/json",
36
+ };
37
+
38
+ // Once per environment (run from a setup script or admin path):
39
+ // await fetch(`${STREVOKIT_API}/v1/embed-origins`, {
40
+ // method: "POST",
41
+ // headers: strevokitHeaders,
42
+ // body: JSON.stringify({ origin: "https://app.example.com" }),
43
+ // });
44
+
45
+ app.post("/api/meetings", requireSignedInUser, async (req, res) => {
46
+ const response = await fetch(`${STREVOKIT_API}/v1/meetings`, {
47
+ method: "POST",
48
+ headers: { ...strevokitHeaders, "Idempotency-Key": crypto.randomUUID() },
49
+ body: JSON.stringify({
50
+ type: "one_to_one",
51
+ max_participants: 2,
52
+ external_reference: req.body.bookingId,
53
+ }),
54
+ });
55
+ if (!response.ok) return res.sendStatus(502);
56
+ const { data: meeting } = await response.json();
57
+
58
+ const hostResponse = await fetch(`${STREVOKIT_API}/v1/meetings/${meeting.public_id}/participants`, {
59
+ method: "POST",
60
+ headers: strevokitHeaders,
61
+ body: JSON.stringify({
62
+ external_user_id: String(req.user.id),
63
+ display_name: req.user.displayName,
64
+ role: "host",
65
+ }),
66
+ });
67
+ if (!hostResponse.ok) return res.sendStatus(502);
68
+ const { data: host } = await hostResponse.json();
69
+
70
+ // Persist meeting.public_id and host.public_id in your own database, keyed by your booking / room.
71
+ res.status(201).json({ meetingPublicId: meeting.public_id, participantPublicId: host.public_id });
72
+ });
73
+
74
+ app.post("/api/meetings/:meetingId/join-token", requireSignedInUser, async (req, res) => {
75
+ // Your own authorization: may this user join this meeting, and as which Strevokit participant?
76
+ const participantPublicId = await participantFor(req.user, req.params.meetingId);
77
+ if (!participantPublicId) return res.sendStatus(403);
78
+
79
+ const response = await fetch(
80
+ `${STREVOKIT_API}/v1/meetings/${encodeURIComponent(req.params.meetingId)}/join-token`,
81
+ {
82
+ method: "POST",
83
+ headers: strevokitHeaders,
84
+ body: JSON.stringify({ participant_public_id: participantPublicId }),
85
+ },
86
+ );
87
+ if (!response.ok) return res.sendStatus(502);
88
+
89
+ const { data } = await response.json(); // { token, expires_at, expires_in }
90
+ res.set("Cache-Control", "no-store").json({ token: data.token });
91
+ });
92
+ ```
93
+
94
+ `requireSignedInUser` and `participantFor` stand for your own session and authorization code.
95
+
96
+ ## Notes
97
+
98
+ - Create and authorize before the first join-token call; join-token needs an existing participant `public_id`.
99
+ - Pass a real `display_name` on authorize; join-token cannot set or change it. If the UI shows **Guest**, authorize
100
+ used a missing or placeholder name, or that step was skipped.
101
+ - Mint a new token for every join, including rejoining after `leave()` / `dispose()`.
102
+ - Send the join-token response with `Cache-Control: no-store` so the token isn't cached.
103
+ - Don't log the token or put it in a URL. The SDK keeps it in memory only.
@@ -20,7 +20,7 @@
20
20
  // With a bundler, import from "@strevokit/meeting-sdk" instead.
21
21
  import { createMeetingEngine, MeetingError } from "./node_modules/@strevokit/meeting-sdk/dist/index.js";
22
22
 
23
- // Ask your backend for a join token; it calls the Strevokit API with its credential.
23
+ // Ask your backend for a join token (see backend-integration.md and ../backend-api.md).
24
24
  const { token } = await fetch("/my-backend/join-token", { method: "POST" }).then((r) => r.json());
25
25
 
26
26
  const engine = createMeetingEngine({ token, realtimeUrl: "wss://realtime.example.com" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strevokit/meeting-sdk",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Embed Strevokit meetings in your web product.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {