Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"format": "prettier --write \"**/*.ts\""
},
"dependencies": {
"@livekit/protocol": "^1.41.0",
"body-parser": "^2.2.0",
"dotenv": "^16.4.5",
"express": "^4.21.1",
"livekit-server-sdk": "^2.8.1"
Expand All @@ -19,6 +21,7 @@
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.13.0",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/body-parser": "^1.19.6",
"@types/express": "^5.0.0",
"@types/node": "^22.8.6",
"@typescript-eslint/eslint-plugin": "^8.12.2",
Expand Down
108 changes: 100 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 44 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,77 @@
import { RoomConfiguration } from '@livekit/protocol';
import bodyParser from 'body-parser';
import dotenv from 'dotenv';
import express from 'express';
import { AccessToken } from 'livekit-server-sdk';

type TokenRequest = {
roomName: string;
participantName: string;
roomName?: string;
participantName?: string;

room_name?: string;
participant_name?: string;
participant_identity?: string;
participant_metadata?: string;
participant_attributes?: Record<string, string>;
room_config?: ReturnType<RoomConfiguration['toJson']>;
};

// Load environment variables from .env.local file
dotenv.config({ path: '.env.local' });

// This route handler creates a token for a given room and participant
async function createToken({ roomName, participantName }: TokenRequest) {
async function createToken(request: TokenRequest) {
const roomName = request.room_name ?? request.roomName!;
const participantName = request.participant_name ?? request.participantName!;

const at = new AccessToken(process.env.LIVEKIT_API_KEY, process.env.LIVEKIT_API_SECRET, {
identity: participantName,
// Token to expire after 10 minutes
ttl: '10m',
});

// Token permissions can be added here based on the
// desired capabilities of the participant
at.addGrant({
roomJoin: true,
room: roomName,
canUpdateOwnMetadata: true,
});

if (request.participant_identity) {
at.identity = request.participant_identity;
}
if (request.participant_metadata) {
at.metadata = request.participant_metadata;
}
if (request.participant_attributes) {
at.attributes = request.participant_attributes;
}
if (request.room_config) {
at.roomConfig = RoomConfiguration.fromJson(request.room_config);
}

return at.toJwt();
}

const app = express();
app.use(bodyParser.json());
const port = 3000;
Comment on lines 58 to 60

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a FYI, there wasn't a body parser loaded in this example, so when I went to test my changes (even with the right Content-Type header), the custom request fields weren't being loaded since req.body was not being set. This should fix that!


app.post('/createToken', async (req, res) => {
const { roomName = 'demo-room', participantName = 'demo-user' } = req.body ?? {};
res.send(await createToken({ roomName, participantName }));
const body = req.body ?? {};
body.roomName = body.roomName ?? 'demo-room';
Comment thread
1egoman marked this conversation as resolved.
Outdated
body.participantName = body.participantName ?? 'demo-user';

try {
res.send({
server_url: process.env.LIVEKIT_URL,
participant_token: await createToken(body),
});
} catch (err) {
console.error('Error generating token:', err);
res.status(500).send({ message: 'Generating token failed' });
}
});

app.listen(port, () => {
Expand Down