79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
import type { Session, User } from 'lucia'
|
|
import { Buffer } from 'node:buffer'
|
|
import { AccessToken } from 'livekit-server-sdk'
|
|
import { SESSION_EXPIRES_IN } from '../auth/lucia.ts'
|
|
import prisma from '../prisma/client.ts'
|
|
|
|
/**
|
|
* Re-issue the token slightly before it actually expires, so a client that
|
|
* caches the /me response for a while still gets a usable token.
|
|
*/
|
|
const REFRESH_BEFORE_EXPIRY_SECONDS = 60 * 60
|
|
|
|
export async function createLivekitToken(user: User, ttlSeconds: number) {
|
|
const apiKey = process.env.LIVEKIT_API_KEY
|
|
const apiSecret = process.env.LIVEKIT_API_SECRET
|
|
|
|
if (!apiKey || !apiSecret)
|
|
throw new Error('LIVEKIT_API_KEY and LIVEKIT_API_SECRET must be set')
|
|
|
|
const token = new AccessToken(apiKey, apiSecret, {
|
|
identity: user.id,
|
|
name: user.displayName,
|
|
ttl: ttlSeconds,
|
|
})
|
|
|
|
token.addGrant({
|
|
room: process.env.LIVEKIT_ROOM || 'chad',
|
|
roomJoin: true,
|
|
canPublish: true,
|
|
canSubscribe: true,
|
|
canPublishData: true,
|
|
canUpdateOwnMetadata: true,
|
|
})
|
|
|
|
return token.toJwt()
|
|
}
|
|
|
|
/** Mints the token that gets stored on a session at the moment it is created. */
|
|
export function createSessionLivekitToken(user: User) {
|
|
return createLivekitToken(user, SESSION_EXPIRES_IN.seconds())
|
|
}
|
|
|
|
/**
|
|
* Returns the token stored alongside the session, re-issuing and persisting it
|
|
* when it is missing (session predates the column) or about to expire (Lucia
|
|
* extends `expiresAt` on active sessions, outliving the original token).
|
|
*/
|
|
export async function getSessionLivekitToken(user: User, session: Session) {
|
|
if (session.livekitToken && !isExpiringSoon(session.livekitToken))
|
|
return session.livekitToken
|
|
|
|
const ttlSeconds = Math.max(
|
|
Math.ceil((session.expiresAt.getTime() - Date.now()) / 1000),
|
|
REFRESH_BEFORE_EXPIRY_SECONDS,
|
|
)
|
|
const livekitToken = await createLivekitToken(user, ttlSeconds)
|
|
|
|
await prisma.session.update({
|
|
where: { id: session.id },
|
|
data: { livekitToken },
|
|
})
|
|
|
|
session.livekitToken = livekitToken
|
|
|
|
return livekitToken
|
|
}
|
|
|
|
function isExpiringSoon(token: string) {
|
|
try {
|
|
const [, payload] = token.split('.')
|
|
const { exp } = JSON.parse(Buffer.from(payload, 'base64url').toString())
|
|
|
|
return typeof exp !== 'number' || exp - REFRESH_BEFORE_EXPIRY_SECONDS <= Date.now() / 1000
|
|
}
|
|
catch {
|
|
return true
|
|
}
|
|
}
|