KI-AGENT: LiveKit-Calls nativ in FEDEO integrieren

This commit is contained in:
2026-05-18 19:15:36 +02:00
parent c93ea4284d
commit 248da3412c
7 changed files with 480 additions and 54 deletions

View File

@@ -5,6 +5,7 @@ import { FastifyInstance } from "fastify"
import { authProfiles, authTenantUsers, authUsers, communicationRooms, tenants } from "../../db/schema"
import { and, eq } from "drizzle-orm"
import { secrets } from "../utils/secrets"
import jwt from "jsonwebtoken"
type MatrixErrorResponse = {
errcode?: string
@@ -40,6 +41,13 @@ type MatrixLoginTokenResponse = {
expires_in_ms: number
}
type LiveKitGrant = {
roomJoin: boolean
room: string
canPublish: boolean
canSubscribe: boolean
}
type MatrixTenantRoomOptions = {
key?: string
name?: string
@@ -153,6 +161,16 @@ export function matrixService(server: FastifyInstance) {
? `wss://${rtcHost()}/livekit/sfu`
: `ws://localhost:${process.env.MATRIX_DEV_LIVEKIT_PORT || "7880"}`)
const livekitKey = () =>
process.env.LIVEKIT_KEY ||
secrets.LIVEKIT_KEY ||
(process.env.NODE_ENV === "production" ? "" : "devkey")
const livekitSecret = () =>
process.env.LIVEKIT_SECRET ||
secrets.LIVEKIT_SECRET ||
(process.env.NODE_ENV === "production" ? "" : "devsecret-local-matrix-stack-32-chars")
const serviceUserLocalpart = () =>
process.env.MATRIX_SERVICE_USER_LOCALPART ||
secrets.MATRIX_SERVICE_USER_LOCALPART ||
@@ -1185,6 +1203,62 @@ export function matrixService(server: FastifyInstance) {
}
}
const createLiveKitRoomSession = async (
userId: string,
tenantId: number | null,
options: MatrixTenantRoomOptions = {}
) => {
if (!livekitKey() || !livekitSecret()) {
throw Object.assign(
new Error("LIVEKIT_KEY and LIVEKIT_SECRET are not configured"),
{ statusCode: 503 }
)
}
const room = await provisionTenantRoom(userId, tenantId, options)
const session = await ensureCurrentUserJoinedRoom(userId, tenantId, {
roomId: room.roomId,
alias: room.alias,
})
const displayName = await getCurrentUserDisplayName(userId, tenantId)
const liveKitRoomName = `fedeo-${tenantId || "global"}-${room.key}`.replace(/[^a-zA-Z0-9_-]/g, "_")
const now = Math.floor(Date.now() / 1000)
const expiresInSeconds = 60 * 60
const video: LiveKitGrant = {
roomJoin: true,
room: liveKitRoomName,
canPublish: true,
canSubscribe: true,
}
const token = jwt.sign(
{
sub: session.matrixUserId,
name: displayName,
video,
nbf: now - 10,
exp: now + expiresInSeconds,
},
livekitSecret(),
{
algorithm: "HS256",
issuer: livekitKey(),
}
)
return {
roomId: room.roomId,
alias: room.alias,
key: room.key,
name: room.name,
matrixUserId: session.matrixUserId,
displayName,
liveKitUrl: livekitUrl(),
liveKitRoomName,
liveKitToken: token,
expiresInMs: expiresInSeconds * 1000,
}
}
const listTenantCommunicationUsers = async (tenantId: number | null) => {
const tenant = await getCurrentTenant(tenantId)
const rows = await server.db
@@ -1342,6 +1416,7 @@ export function matrixService(server: FastifyInstance) {
getTenantRoomMembers,
sendTenantRoomMessage,
createElementRoomSession,
createLiveKitRoomSession,
syncTenantRoomMembers,
getGeneralRoomMessages,
getGeneralRoomMembers,

View File

@@ -136,6 +136,17 @@ export default async function communicationRoutes(server: FastifyInstance) {
}
})
server.post("/communication/matrix/rooms/general/call-session", async (req, reply) => {
try {
return await matrix.createLiveKitRoomSession(req.user.user_id, req.user.tenant_id, {
key: "allgemein",
name: "Allgemeiner Chat",
})
} catch (err: any) {
return handleMatrixError(req, reply, err, "Matrix call session failed")
}
})
server.post("/communication/matrix/rooms/general/members/sync", async (req, reply) => {
try {
return await matrix.syncTenantRoomMembers(req.user.user_id, req.user.tenant_id, {
@@ -213,6 +224,18 @@ export default async function communicationRoutes(server: FastifyInstance) {
}
})
server.post("/communication/matrix/rooms/:roomKey/call-session", async (req, reply) => {
try {
return await matrix.createLiveKitRoomSession(
req.user.user_id,
req.user.tenant_id,
roomOptionsFromRequest(req)
)
} catch (err: any) {
return handleMatrixError(req, reply, err, "Matrix call session failed")
}
})
server.post("/communication/matrix/rooms/:roomKey/members/sync", async (req, reply) => {
try {
return await matrix.syncTenantRoomMembers(

View File

@@ -45,6 +45,8 @@ export let secrets = {
MATRIX_LIVEKIT_URL?: string
MATRIX_REGISTRATION_SHARED_SECRET?: string
MATRIX_SERVICE_USER_LOCALPART?: string
LIVEKIT_KEY?: string
LIVEKIT_SECRET?: string
}
const secretKeys = [
@@ -84,6 +86,8 @@ const secretKeys = [
"MATRIX_LIVEKIT_URL",
"MATRIX_REGISTRATION_SHARED_SECRET",
"MATRIX_SERVICE_USER_LOCALPART",
"LIVEKIT_KEY",
"LIVEKIT_SECRET",
] as const
const numberKeys = new Set(["PORT", "MAILER_SMTP_PORT", "DOKUBOX_IMAP_PORT"])