@weft-js/realtime-livekit 0.1.1 → 0.1.4
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/README.md +250 -4
- package/package.json +15 -6
- package/plugin.mjs +4 -1
- package/src/index.ts +27 -7
- package/src/server.ts +29 -5
package/README.md
CHANGED
|
@@ -1,9 +1,255 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://github.com/Ravonus/weft">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/Ravonus/weft/main/site/public/logo.svg" width="88" height="88" alt="Weft">
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
2
6
|
|
|
3
|
-
>
|
|
7
|
+
<h1 align="center">@weft-js/realtime-livekit</h1>
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
<p align="center">
|
|
10
|
+
<strong>Live cursors over a LiveKit room, with the token signed by your Weft server.</strong><br>
|
|
11
|
+
LiveKit proves who sent each frame, and the same room can carry audio and video.
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://www.npmjs.com/package/@weft-js/realtime-livekit"><img src="https://img.shields.io/npm/v/@weft-js/realtime-livekit?color=145c3c&label=npm" alt="npm version"></a>
|
|
16
|
+
<a href="https://github.com/Ravonus/weft/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-145c3c" alt="MIT license"></a>
|
|
17
|
+
<a href="https://github.com/Ravonus/weft/actions"><img src="https://img.shields.io/github/actions/workflow/status/Ravonus/weft/quality.yml?branch=main&label=checks" alt="checks"></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<a href="#quick-start">Quick start</a> ·
|
|
22
|
+
<a href="https://github.com/Ravonus/weft/tree/main/docs">Docs</a> ·
|
|
23
|
+
<a href="https://github.com/Ravonus/weft/tree/main/examples">Examples</a> ·
|
|
24
|
+
<a href="https://github.com/Ravonus/weft/blob/main/CHANGELOG.md">Changelog</a> ·
|
|
25
|
+
<a href="https://github.com/Ravonus/weft">GitHub</a>
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
> [!IMPORTANT]
|
|
29
|
+
> **Early release (0.1.x).** APIs can change between minor versions. Pin the version and read the
|
|
30
|
+
> [changelog](https://github.com/Ravonus/weft/blob/main/CHANGELOG.md) before you upgrade.
|
|
31
|
+
|
|
32
|
+
A cursor transport for [`@weft-js/multiplayer`](https://www.npmjs.com/package/@weft-js/multiplayer) <sup>[source](https://github.com/Ravonus/weft/tree/main/packages/multiplayer)</sup>. By
|
|
33
|
+
default cursors go through your server over the channel's socket. With this package they go through a
|
|
34
|
+
[LiveKit](https://livekit.io) room instead, which takes that traffic off your server. It suits an app that already uses
|
|
35
|
+
LiveKit, or wants audio and video in the same room as its cursors.
|
|
36
|
+
|
|
37
|
+
## Highlights
|
|
38
|
+
|
|
39
|
+
- **Your server signs the token.** `livekitTicket()` signs a LiveKit access token whose identity is the channel
|
|
40
|
+
connection's id. No client can name itself to LiveKit.
|
|
41
|
+
- **The sender is proven.** A frame's sender is the LiveKit participant's identity, and `@weft-js/multiplayer` only
|
|
42
|
+
accepts frames from connections that are in the room.
|
|
43
|
+
- **Data only by default.** The token allows joining, subscribing and publishing data. `media: true` adds publishing
|
|
44
|
+
audio and video.
|
|
45
|
+
- **Rooms that say nothing.** The LiveKit room name is a hash of the channel's params, so LiveKit never sees them.
|
|
46
|
+
- **Falls back on its own.** Until every peer is in the LiveKit room, cursors keep going over the socket.
|
|
47
|
+
- **Lossy on purpose.** Cursor frames are published with `reliable: false` on the topic `weft`: a late cursor is
|
|
48
|
+
replaced by the next one.
|
|
49
|
+
|
|
50
|
+
## Install
|
|
6
51
|
|
|
7
52
|
```bash
|
|
8
|
-
npm
|
|
53
|
+
# npm
|
|
54
|
+
npm install @weft-js/multiplayer @weft-js/realtime-livekit
|
|
55
|
+
|
|
56
|
+
# pnpm
|
|
57
|
+
pnpm add @weft-js/multiplayer @weft-js/realtime-livekit
|
|
58
|
+
|
|
59
|
+
# bun
|
|
60
|
+
bun add @weft-js/multiplayer @weft-js/realtime-livekit
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or install `@weft-js/multiplayer`, then let the `weft` CLI install this package and add its plugin to
|
|
64
|
+
`weft.config.ts`:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm install @weft-js/multiplayer
|
|
68
|
+
npx weft add realtime-livekit
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`@weft-js/multiplayer` is a peer dependency, so add it too. `livekit-client` comes with this package.
|
|
72
|
+
|
|
73
|
+
`weft add` installs the package and puts its plugin in `weft.config.ts`:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// weft.config.ts
|
|
77
|
+
import { defineConfig } from '@weft-js/core/config'
|
|
78
|
+
import realtimeLivekit from '@weft-js/realtime-livekit/plugin'
|
|
79
|
+
|
|
80
|
+
export default defineConfig({
|
|
81
|
+
plugins: [realtimeLivekit()],
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The plugin fills no slot. It records what the app reaches (LiveKit from the browser, and the `LIVEKIT_API_KEY` and
|
|
86
|
+
`LIVEKIT_API_SECRET` variables), and tells agents how the transport is used. The transport itself is passed to
|
|
87
|
+
`presence()` in a page.
|
|
88
|
+
|
|
89
|
+
## Setup
|
|
90
|
+
|
|
91
|
+
You need a LiveKit server: [LiveKit Cloud](https://livekit.io) or your own. The server reads two variables:
|
|
92
|
+
|
|
93
|
+
| Variable | Where | What it is |
|
|
94
|
+
| -------------------- | ----------- | ---------- |
|
|
95
|
+
| `LIVEKIT_API_KEY` | your server | The key `livekitTicket()` puts in the token's `iss`. |
|
|
96
|
+
| `LIVEKIT_API_SECRET` | your server | The secret it signs the token with (HS256). Never send it to a page. |
|
|
97
|
+
|
|
98
|
+
The page needs the LiveKit URL (`wss://…`). It isn't secret: read it on the server and hand it to the page, for
|
|
99
|
+
example from a route's `load`.
|
|
100
|
+
|
|
101
|
+
## Quick start
|
|
102
|
+
|
|
103
|
+
Give `multiplayer()` the ticket:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
// src/server/doc.ts
|
|
107
|
+
import { channel } from '@weft-js/start'
|
|
108
|
+
import { multiplayer } from '@weft-js/multiplayer/server'
|
|
109
|
+
import { livekitTicket } from '@weft-js/realtime-livekit/server'
|
|
110
|
+
import { z } from 'zod'
|
|
111
|
+
|
|
112
|
+
const mp = multiplayer<{ id: string }>({
|
|
113
|
+
user: ({ ctx }) => ({ id: ctx.user!.id, name: ctx.user!.name }),
|
|
114
|
+
ticket: livekitTicket(), // LIVEKIT_API_KEY and LIVEKIT_API_SECRET
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
export const doc = channel({
|
|
118
|
+
params: z.object({ id: z.string().max(64) }),
|
|
119
|
+
authorize: ({ ctx }) => !!ctx.user,
|
|
120
|
+
client: { ...mp.client },
|
|
121
|
+
server: { ...mp.server },
|
|
122
|
+
on: { ...mp.on },
|
|
123
|
+
left: mp.left,
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Pass the transport to `presence()`:
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
// src/routes/room.tsx
|
|
131
|
+
import { cursors, presence } from '@weft-js/multiplayer'
|
|
132
|
+
import { Cursors } from '@weft-js/multiplayer/ui'
|
|
133
|
+
import { livekit } from '@weft-js/realtime-livekit'
|
|
134
|
+
import { doc } from '#/server/doc'
|
|
135
|
+
|
|
136
|
+
export const load = () => ({ livekit: process.env.LIVEKIT_URL ?? '' })
|
|
137
|
+
|
|
138
|
+
export default function Room(props: { data: { livekit: string } }) {
|
|
139
|
+
const chat = doc.connect({ id: 'lobby' })
|
|
140
|
+
const live = presence(chat, {
|
|
141
|
+
transport: livekit({ url: props.data.livekit }),
|
|
142
|
+
})
|
|
143
|
+
const pointers = cursors(live)
|
|
144
|
+
return (
|
|
145
|
+
<main>
|
|
146
|
+
<p>Cursors via {live.via.value}</p>
|
|
147
|
+
<Cursors of={pointers} />
|
|
148
|
+
</main>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
9
151
|
```
|
|
152
|
+
|
|
153
|
+
`live.via.value` is `'socket'` until every other peer is in the LiveKit room, then `'livekit'`.
|
|
154
|
+
|
|
155
|
+
## How it works
|
|
156
|
+
|
|
157
|
+
1. A page joins the channel and sends `mp.hello`. The server calls `livekitTicket()` with the connection's id, the
|
|
158
|
+
peer's `user` and the channel's params, and returns the token with `mp.you`.
|
|
159
|
+
2. `livekit({ url })` connects a `livekit-client` `Room` to `url` with that token. Without a ticket it doesn't
|
|
160
|
+
connect, and cursors stay on the socket.
|
|
161
|
+
3. A cursor is published as data on the topic `weft`, unreliable. A received frame is handed to `presence` with the
|
|
162
|
+
participant's identity as the sender, and checked there like any frame off a transport.
|
|
163
|
+
4. The link is `up` while every peer the channel knows is also a remote participant in the LiveKit room.
|
|
164
|
+
|
|
165
|
+
## API reference
|
|
166
|
+
|
|
167
|
+
### `@weft-js/realtime-livekit`
|
|
168
|
+
|
|
169
|
+
`livekit(options): PeerTransport`: the transport, for `presence(conn, { transport })`. Its `open` returns the
|
|
170
|
+
`PeerLink` with one more field, `room`: the `livekit-client` `Room`, for audio and video.
|
|
171
|
+
|
|
172
|
+
| Option | Type | Default | What it does |
|
|
173
|
+
| ------ | -------- | -------- | ------------ |
|
|
174
|
+
| `url` | `string` | required | The LiveKit server, `wss://…`. |
|
|
175
|
+
|
|
176
|
+
Type: `LivekitOptions`.
|
|
177
|
+
|
|
178
|
+
### `@weft-js/realtime-livekit/server`
|
|
179
|
+
|
|
180
|
+
`livekitTicket(options?)`: returns the function `multiplayer({ ticket })` calls. It refuses to run without a key and
|
|
181
|
+
secret.
|
|
182
|
+
|
|
183
|
+
| Option | Type | Default | What it does |
|
|
184
|
+
| -------- | --------- | ----------------------------- | ------------ |
|
|
185
|
+
| `key` | `string` | `process.env.LIVEKIT_API_KEY` | The API key, the token's `iss`. |
|
|
186
|
+
| `secret` | `string` | `process.env.LIVEKIT_API_SECRET` | The API secret the token is signed with. |
|
|
187
|
+
| `ttl` | `number` | `3600` | Seconds the token is valid. |
|
|
188
|
+
| `media` | `boolean` | `false` | Also allow publishing audio and video (`canPublish`). Data only is what cursors need. |
|
|
189
|
+
|
|
190
|
+
The token's claims: `iss` (the key), `sub` (the connection id), `name` (`user.name`), `nbf`, `exp`, and a `video`
|
|
191
|
+
grant with `room` (`roomId(params)` from `@weft-js/multiplayer/server`), `roomJoin`, `canPublish` (from `media`),
|
|
192
|
+
`canSubscribe` and `canPublishData`. Type: `LivekitTicketOptions`.
|
|
193
|
+
|
|
194
|
+
### `@weft-js/realtime-livekit/plugin`
|
|
195
|
+
|
|
196
|
+
The plugin for `weft.config.ts`, as a default export and as the named export `livekit`. Import the default, or rename
|
|
197
|
+
the named one, so it doesn't clash with the transport of the same name.
|
|
198
|
+
|
|
199
|
+
## Recipes
|
|
200
|
+
|
|
201
|
+
**Audio and video in the same room.** Sign tokens that may publish media:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
const mp = multiplayer({
|
|
205
|
+
user,
|
|
206
|
+
ticket: livekitTicket({ media: true }),
|
|
207
|
+
})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The `room` on the link `livekit()` opens is a `livekit-client` `Room`, so its own API publishes tracks.
|
|
211
|
+
|
|
212
|
+
**Shorter-lived tokens.**
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
livekitTicket({ ttl: 600 })
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Limits
|
|
219
|
+
|
|
220
|
+
- Only cursors ride LiveKit. Presence state, the relay and signalling stay on the channel, where the server checks them.
|
|
221
|
+
- Frames are unreliable. A lost cursor is replaced by the next; nothing is retried.
|
|
222
|
+
- A connection gets its token once, with `mp.you`. A token past `ttl` can't open a new LiveKit connection; a new
|
|
223
|
+
`mp.hello` (a reconnect or a reset) brings a fresh one.
|
|
224
|
+
|
|
225
|
+
## Troubleshooting
|
|
226
|
+
|
|
227
|
+
| Symptom | Cause | Fix |
|
|
228
|
+
| ------- | ----- | --- |
|
|
229
|
+
| `` `livekitTicket` needs `LIVEKIT_API_KEY` and `LIVEKIT_API_SECRET` `` on join | The server has neither variable nor options. | Set both variables, or pass `key` and `secret`. |
|
|
230
|
+
| `via` stays `'socket'` | No ticket reached the page (no `ticket` in `multiplayer()`), the URL is wrong, LiveKit refused the token, or a peer hasn't joined LiveKit yet. Connection errors are swallowed. | Check the URL and the key pair; cursors keep working over the socket meanwhile. |
|
|
231
|
+
| `weft check` stops after `weft add` | A newly added plugin's permissions must be approved. | `weft plugins approve @weft-js/realtime-livekit`. |
|
|
232
|
+
|
|
233
|
+
## Related packages
|
|
234
|
+
|
|
235
|
+
| Package | What it is |
|
|
236
|
+
| ------- | ---------- |
|
|
237
|
+
| [`@weft-js/multiplayer`](https://www.npmjs.com/package/@weft-js/multiplayer) | Presence, cursors and the relay that this transport carries cursors for. |
|
|
238
|
+
| [`@weft-js/realtime-webrtc`](https://www.npmjs.com/package/@weft-js/realtime-webrtc) | Cursors browser to browser, with no service in between. |
|
|
239
|
+
| [`@weft-js/realtime-socketio`](https://www.npmjs.com/package/@weft-js/realtime-socketio) | Cursors through a Socket.IO relay you run. |
|
|
240
|
+
| [`@weft-js/realtime-redis`](https://www.npmjs.com/package/@weft-js/realtime-redis) | A `realtime.bus` over Redis: how server instances reach each other, not a cursor transport. |
|
|
241
|
+
| [`@weft-js/realtime-nats`](https://www.npmjs.com/package/@weft-js/realtime-nats) | A `realtime.bus` over NATS. |
|
|
242
|
+
| [`@weft-js/realtime-durable-object`](https://www.npmjs.com/package/@weft-js/realtime-durable-object) | A `realtime.bus` on Cloudflare Durable Objects. |
|
|
243
|
+
| [`@weft-js/core`](https://www.npmjs.com/package/@weft-js/core) | The compiler and reactive runtime, the Vite plugin, the plugin system and test utilities. |
|
|
244
|
+
| [`@weft-js/start`](https://www.npmjs.com/package/@weft-js/start) | The full-stack runtime: routes, server functions, `channel`, `realtime()` and the realtime hub. |
|
|
245
|
+
|
|
246
|
+
## Contributing
|
|
247
|
+
|
|
248
|
+
See [CONTRIBUTING.md](https://github.com/Ravonus/weft/blob/main/CONTRIBUTING.md). From the repository,
|
|
249
|
+
`pnpm --filter @weft-js/realtime-livekit test` checks the token against LiveKit's rules. The transport against a real
|
|
250
|
+
LiveKit server runs in Chromium through `@weft-js/multiplayer`'s end-to-end test with `--via livekit`, which needs
|
|
251
|
+
`LIVEKIT_URL`, `LIVEKIT_API_KEY` and `LIVEKIT_API_SECRET`.
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
[MIT](https://github.com/Ravonus/weft/blob/main/LICENSE)
|
package/package.json
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weft-js/realtime-livekit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "@weft-js/realtime-livekit: cursors over a LiveKit room, with the token signed by the room",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Ravonus/weft.git",
|
|
9
|
+
"directory": "packages/realtime-livekit"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/Ravonus/weft/tree/main/packages/realtime-livekit#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Ravonus/weft/issues"
|
|
14
|
+
},
|
|
6
15
|
"type": "module",
|
|
7
16
|
"exports": {
|
|
8
17
|
".": "./src/index.ts",
|
|
@@ -13,12 +22,12 @@
|
|
|
13
22
|
"livekit-client": "2.23.0"
|
|
14
23
|
},
|
|
15
24
|
"peerDependencies": {
|
|
16
|
-
"@weft-js/start": "^0.1.
|
|
17
|
-
"@weft-js/core": "^0.1.
|
|
18
|
-
"@weft-js/multiplayer": "^0.1.
|
|
25
|
+
"@weft-js/start": "^0.1.4",
|
|
26
|
+
"@weft-js/core": "^0.1.4",
|
|
27
|
+
"@weft-js/multiplayer": "^0.1.4"
|
|
19
28
|
},
|
|
20
29
|
"engines": {
|
|
21
|
-
"node": ">=
|
|
30
|
+
"node": ">=24"
|
|
22
31
|
},
|
|
23
32
|
"keywords": [
|
|
24
33
|
"weft"
|
|
@@ -33,4 +42,4 @@
|
|
|
33
42
|
"publishConfig": {
|
|
34
43
|
"access": "public"
|
|
35
44
|
}
|
|
36
|
-
}
|
|
45
|
+
}
|
package/plugin.mjs
CHANGED
|
@@ -7,7 +7,10 @@ export const livekit = definePlugin({
|
|
|
7
7
|
weft: '*',
|
|
8
8
|
permissions: { net: ['browser:livekit'], env: ['LIVEKIT_API_KEY', 'LIVEKIT_API_SECRET'] },
|
|
9
9
|
contribute: () => ({
|
|
10
|
-
agents: {
|
|
10
|
+
agents: {
|
|
11
|
+
title: 'Peer transport (LiveKit)',
|
|
12
|
+
text: '`presence(conn, { transport: livekit({ url }) })` sends cursors through LiveKit, and `link.room` is the room for audio and video. Pass `ticket: livekitTicket({ media })` from `@weft-js/realtime-livekit/server` to `multiplayer()`, with `LIVEKIT_API_KEY` and `LIVEKIT_API_SECRET` on the server.',
|
|
13
|
+
},
|
|
11
14
|
}),
|
|
12
15
|
})
|
|
13
16
|
|
package/src/index.ts
CHANGED
|
@@ -18,18 +18,38 @@ export function livekit({ url }: LivekitOptions): PeerTransport {
|
|
|
18
18
|
return {
|
|
19
19
|
name: 'livekit',
|
|
20
20
|
open(room: PeerRoom): PeerLink & { room: Room } {
|
|
21
|
-
return createRoot(dispose => {
|
|
21
|
+
return createRoot((dispose) => {
|
|
22
22
|
const live = new Room()
|
|
23
23
|
const here = state<string[]>([])
|
|
24
|
-
const sync = () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const sync = () => {
|
|
25
|
+
here.value = [...live.remoteParticipants.values()].map((p) => p.identity)
|
|
26
|
+
}
|
|
27
|
+
const decoder = new TextDecoder(),
|
|
28
|
+
encoder = new TextEncoder()
|
|
29
|
+
live.on(RoomEvent.DataReceived, (bytes, participant, _kind, topic) => {
|
|
30
|
+
if (topic === TOPIC && participant)
|
|
31
|
+
room.receive(participant.identity, decoder.decode(bytes))
|
|
32
|
+
})
|
|
33
|
+
for (const e of [
|
|
34
|
+
RoomEvent.ParticipantConnected,
|
|
35
|
+
RoomEvent.ParticipantDisconnected,
|
|
36
|
+
RoomEvent.Connected,
|
|
37
|
+
RoomEvent.Disconnected,
|
|
38
|
+
])
|
|
39
|
+
live.on(e, sync)
|
|
28
40
|
if (room.ticket) void live.connect(url, room.ticket as string).then(sync, () => {})
|
|
29
41
|
return {
|
|
30
42
|
room: live,
|
|
31
|
-
up: derived(() => {
|
|
32
|
-
|
|
43
|
+
up: derived(() => {
|
|
44
|
+
const ids = room.peers.value
|
|
45
|
+
return ids.length > 0 && ids.every((id) => here.value.includes(id))
|
|
46
|
+
}),
|
|
47
|
+
send(text) {
|
|
48
|
+
if (untrack(() => here.value.length))
|
|
49
|
+
void live.localParticipant
|
|
50
|
+
.publishData(encoder.encode(text), { reliable: false, topic: TOPIC })
|
|
51
|
+
.catch(() => {})
|
|
52
|
+
},
|
|
33
53
|
close() {
|
|
34
54
|
void live.disconnect()
|
|
35
55
|
dispose()
|
package/src/server.ts
CHANGED
|
@@ -14,8 +14,32 @@ export interface LivekitTicketOptions {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/** For `multiplayer({ ticket })`: a LiveKit token that names the connection and its room. Refuses to run without a key and secret. */
|
|
17
|
-
export const livekitTicket =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
export const livekitTicket =
|
|
18
|
+
({
|
|
19
|
+
key = process.env.LIVEKIT_API_KEY,
|
|
20
|
+
secret = process.env.LIVEKIT_API_SECRET,
|
|
21
|
+
ttl = 3600,
|
|
22
|
+
media = false,
|
|
23
|
+
}: LivekitTicketOptions = {}) =>
|
|
24
|
+
async ({ id, user, params }: { id: string; user: { name?: string }; params: unknown }) => {
|
|
25
|
+
if (!key || !secret)
|
|
26
|
+
throw new Error('`livekitTicket` needs `LIVEKIT_API_KEY` and `LIVEKIT_API_SECRET`')
|
|
27
|
+
const now = Math.floor(Date.now() / 1000)
|
|
28
|
+
return signTicket(
|
|
29
|
+
{
|
|
30
|
+
iss: key,
|
|
31
|
+
sub: id,
|
|
32
|
+
name: user.name,
|
|
33
|
+
nbf: now,
|
|
34
|
+
exp: now + ttl,
|
|
35
|
+
video: {
|
|
36
|
+
room: await roomId(params),
|
|
37
|
+
roomJoin: true,
|
|
38
|
+
canPublish: media,
|
|
39
|
+
canSubscribe: true,
|
|
40
|
+
canPublishData: true,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
secret,
|
|
44
|
+
)
|
|
45
|
+
}
|