KI-AGENT: Matrix-Einbettung stabilisieren
This commit is contained in:
@@ -35,6 +35,11 @@ type MatrixUserSession = {
|
|||||||
validUntilMs: number
|
validUntilMs: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MatrixLoginTokenResponse = {
|
||||||
|
login_token: string
|
||||||
|
expires_in_ms: number
|
||||||
|
}
|
||||||
|
|
||||||
type MatrixTenantRoomOptions = {
|
type MatrixTenantRoomOptions = {
|
||||||
key?: string
|
key?: string
|
||||||
name?: string
|
name?: string
|
||||||
@@ -1146,6 +1151,40 @@ export function matrixService(server: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createElementRoomSession = async (
|
||||||
|
userId: string,
|
||||||
|
tenantId: number | null,
|
||||||
|
options: MatrixTenantRoomOptions = {}
|
||||||
|
) => {
|
||||||
|
const room = await provisionTenantRoom(userId, tenantId, options)
|
||||||
|
const session = await ensureCurrentUserJoinedRoom(userId, tenantId, {
|
||||||
|
roomId: room.roomId,
|
||||||
|
alias: room.alias,
|
||||||
|
})
|
||||||
|
|
||||||
|
const token = await requestMatrixJson<MatrixLoginTokenResponse>(
|
||||||
|
"/_matrix/client/v1/login/get_token",
|
||||||
|
session.accessToken,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
roomId: room.roomId,
|
||||||
|
alias: room.alias,
|
||||||
|
key: room.key,
|
||||||
|
name: room.name,
|
||||||
|
matrixUserId: session.matrixUserId,
|
||||||
|
loginToken: token.login_token,
|
||||||
|
expiresInMs: token.expires_in_ms,
|
||||||
|
homeserverUrl: homeserverUrl(),
|
||||||
|
serverName: serverName(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const listTenantCommunicationUsers = async (tenantId: number | null) => {
|
const listTenantCommunicationUsers = async (tenantId: number | null) => {
|
||||||
const tenant = await getCurrentTenant(tenantId)
|
const tenant = await getCurrentTenant(tenantId)
|
||||||
const rows = await server.db
|
const rows = await server.db
|
||||||
@@ -1302,6 +1341,7 @@ export function matrixService(server: FastifyInstance) {
|
|||||||
getTenantRoomMessages,
|
getTenantRoomMessages,
|
||||||
getTenantRoomMembers,
|
getTenantRoomMembers,
|
||||||
sendTenantRoomMessage,
|
sendTenantRoomMessage,
|
||||||
|
createElementRoomSession,
|
||||||
syncTenantRoomMembers,
|
syncTenantRoomMembers,
|
||||||
getGeneralRoomMessages,
|
getGeneralRoomMessages,
|
||||||
getGeneralRoomMembers,
|
getGeneralRoomMembers,
|
||||||
|
|||||||
@@ -125,6 +125,17 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
server.post("/communication/matrix/rooms/general/session", async (req, reply) => {
|
||||||
|
try {
|
||||||
|
return await matrix.createElementRoomSession(req.user.user_id, req.user.tenant_id, {
|
||||||
|
key: "allgemein",
|
||||||
|
name: "Allgemeiner Chat",
|
||||||
|
})
|
||||||
|
} catch (err: any) {
|
||||||
|
return handleMatrixError(req, reply, err, "Matrix session failed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
server.post("/communication/matrix/rooms/general/members/sync", async (req, reply) => {
|
server.post("/communication/matrix/rooms/general/members/sync", async (req, reply) => {
|
||||||
try {
|
try {
|
||||||
return await matrix.syncTenantRoomMembers(req.user.user_id, req.user.tenant_id, {
|
return await matrix.syncTenantRoomMembers(req.user.user_id, req.user.tenant_id, {
|
||||||
@@ -190,6 +201,18 @@ export default async function communicationRoutes(server: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
server.post("/communication/matrix/rooms/:roomKey/session", async (req, reply) => {
|
||||||
|
try {
|
||||||
|
return await matrix.createElementRoomSession(
|
||||||
|
req.user.user_id,
|
||||||
|
req.user.tenant_id,
|
||||||
|
roomOptionsFromRequest(req)
|
||||||
|
)
|
||||||
|
} catch (err: any) {
|
||||||
|
return handleMatrixError(req, reply, err, "Matrix session failed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
server.post("/communication/matrix/rooms/:roomKey/members/sync", async (req, reply) => {
|
server.post("/communication/matrix/rooms/:roomKey/members/sync", async (req, reply) => {
|
||||||
try {
|
try {
|
||||||
return await matrix.syncTenantRoomMembers(
|
return await matrix.syncTenantRoomMembers(
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const matrixMessagesViewport = ref(null)
|
|||||||
const roomCreateOpen = ref(false)
|
const roomCreateOpen = ref(false)
|
||||||
const matrixCallOpen = ref(false)
|
const matrixCallOpen = ref(false)
|
||||||
const matrixCallMode = ref("video")
|
const matrixCallMode = ref("video")
|
||||||
|
const matrixCallLoading = ref(false)
|
||||||
|
const matrixCallUrl = ref("")
|
||||||
const roomCreateForm = ref({
|
const roomCreateForm = ref({
|
||||||
name: "",
|
name: "",
|
||||||
key: "",
|
key: "",
|
||||||
@@ -73,6 +75,10 @@ const matrixCallTitle = computed(() =>
|
|||||||
matrixCallMode.value === "audio" ? "Audioanruf" : "Videokonferenz"
|
matrixCallMode.value === "audio" ? "Audioanruf" : "Videokonferenz"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const activeMatrixCallUrl = computed(() =>
|
||||||
|
matrixCallUrl.value || activeRoomElementUrl.value
|
||||||
|
)
|
||||||
|
|
||||||
const roomCreateKeyPreview = computed(() =>
|
const roomCreateKeyPreview = computed(() =>
|
||||||
normalizeRoomKey(roomCreateForm.value.key || roomCreateForm.value.name)
|
normalizeRoomKey(roomCreateForm.value.key || roomCreateForm.value.name)
|
||||||
)
|
)
|
||||||
@@ -340,7 +346,18 @@ const syncRoomMembers = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openMatrixCall = (mode = "video") => {
|
const buildElementRoomSessionUrl = (session) => {
|
||||||
|
const roomAddress = session.roomId || session.alias || activeRoomMatrixAddress.value
|
||||||
|
if (!matrixElementUrl.value || !roomAddress) return ""
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
loginToken: session.loginToken
|
||||||
|
})
|
||||||
|
|
||||||
|
return `${matrixElementUrl.value}/?${params.toString()}#/room/${encodeURIComponent(roomAddress)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const openMatrixCall = async (mode = "video") => {
|
||||||
if (!canStartMatrixCall.value) {
|
if (!canStartMatrixCall.value) {
|
||||||
toast.add({
|
toast.add({
|
||||||
title: "Besprechung kann noch nicht gestartet werden",
|
title: "Besprechung kann noch nicht gestartet werden",
|
||||||
@@ -350,7 +367,24 @@ const openMatrixCall = (mode = "video") => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
matrixCallMode.value = mode
|
matrixCallMode.value = mode
|
||||||
|
matrixCallLoading.value = true
|
||||||
matrixCallOpen.value = true
|
matrixCallOpen.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const session = await $api(`${activeRoomEndpoint.value}/session`, {
|
||||||
|
method: "POST"
|
||||||
|
})
|
||||||
|
matrixCallUrl.value = buildElementRoomSessionUrl(session) || activeRoomElementUrl.value
|
||||||
|
} catch (error) {
|
||||||
|
matrixCallUrl.value = activeRoomElementUrl.value
|
||||||
|
toast.add({
|
||||||
|
title: "Matrix-Anmeldung konnte nicht vorbereitet werden",
|
||||||
|
description: "Der Raum wird ohne automatische Anmeldung geöffnet.",
|
||||||
|
color: "warning"
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
matrixCallLoading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadRoomChat = async ({ silent = false, includeMembers = false } = {}) => {
|
const loadRoomChat = async ({ silent = false, includeMembers = false } = {}) => {
|
||||||
@@ -619,6 +653,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
color="neutral"
|
color="neutral"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
aria-label="Audioanruf starten"
|
aria-label="Audioanruf starten"
|
||||||
|
:loading="matrixCallLoading && matrixCallMode === 'audio'"
|
||||||
:disabled="!canStartMatrixCall"
|
:disabled="!canStartMatrixCall"
|
||||||
@click="openMatrixCall('audio')"
|
@click="openMatrixCall('audio')"
|
||||||
/>
|
/>
|
||||||
@@ -627,6 +662,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
color="primary"
|
color="primary"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
aria-label="Videokonferenz starten"
|
aria-label="Videokonferenz starten"
|
||||||
|
:loading="matrixCallLoading && matrixCallMode === 'video'"
|
||||||
:disabled="!canStartMatrixCall"
|
:disabled="!canStartMatrixCall"
|
||||||
@click="openMatrixCall('video')"
|
@click="openMatrixCall('video')"
|
||||||
/>
|
/>
|
||||||
@@ -893,6 +929,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
color="neutral"
|
color="neutral"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
block
|
block
|
||||||
|
:loading="matrixCallLoading && matrixCallMode === 'audio'"
|
||||||
:disabled="!canStartMatrixCall"
|
:disabled="!canStartMatrixCall"
|
||||||
@click="openMatrixCall('audio')"
|
@click="openMatrixCall('audio')"
|
||||||
>
|
>
|
||||||
@@ -903,6 +940,7 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
color="primary"
|
color="primary"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
block
|
block
|
||||||
|
:loading="matrixCallLoading && matrixCallMode === 'video'"
|
||||||
:disabled="!canStartMatrixCall"
|
:disabled="!canStartMatrixCall"
|
||||||
@click="openMatrixCall('video')"
|
@click="openMatrixCall('video')"
|
||||||
>
|
>
|
||||||
@@ -940,8 +978,8 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<UButton
|
<UButton
|
||||||
v-if="activeRoomElementUrl"
|
v-if="activeMatrixCallUrl"
|
||||||
:to="activeRoomElementUrl"
|
:to="activeMatrixCallUrl"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
icon="i-heroicons-arrow-top-right-on-square"
|
icon="i-heroicons-arrow-top-right-on-square"
|
||||||
color="neutral"
|
color="neutral"
|
||||||
@@ -959,10 +997,16 @@ onBeforeUnmount(stopMatrixAutoRefresh)
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="min-h-0 flex-1 bg-muted">
|
<div class="min-h-0 flex-1 bg-muted">
|
||||||
|
<div
|
||||||
|
v-if="matrixCallLoading"
|
||||||
|
class="flex h-full items-center justify-center text-sm text-muted"
|
||||||
|
>
|
||||||
|
Matrix wird geladen...
|
||||||
|
</div>
|
||||||
<iframe
|
<iframe
|
||||||
v-if="canStartMatrixCall"
|
v-else-if="canStartMatrixCall"
|
||||||
:key="`${activeRoomKey}-${matrixCallMode}`"
|
:key="`${activeRoomKey}-${matrixCallMode}`"
|
||||||
:src="activeRoomElementUrl"
|
:src="activeMatrixCallUrl"
|
||||||
class="h-full w-full border-0"
|
class="h-full w-full border-0"
|
||||||
allow="camera; microphone; display-capture; clipboard-read; clipboard-write; fullscreen; autoplay"
|
allow="camera; microphone; display-capture; clipboard-read; clipboard-write; fullscreen; autoplay"
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
|
|||||||
@@ -184,3 +184,14 @@ Das Backend stellt geschützte Matrix-Endpunkte unter `/api/communication/matrix
|
|||||||
Für lokale Provisionierung muss `MATRIX_REGISTRATION_SHARED_SECRET` aus `matrix/dev/synapse/homeserver.yaml` in der Backend-Umgebung gesetzt werden. Die lokale Synapse-Konfiguration ist absichtlich nicht versioniert, weil sie Secrets enthält.
|
Für lokale Provisionierung muss `MATRIX_REGISTRATION_SHARED_SECRET` aus `matrix/dev/synapse/homeserver.yaml` in der Backend-Umgebung gesetzt werden. Die lokale Synapse-Konfiguration ist absichtlich nicht versioniert, weil sie Secrets enthält.
|
||||||
|
|
||||||
In der lokalen Entwicklung liest das Backend dieses Secret als Fallback direkt aus `matrix/dev/synapse/homeserver.yaml`, sofern `NODE_ENV` nicht `production` ist. Auf Servern muss das Secret weiterhin explizit über die Umgebung oder das Secret-Management gesetzt werden.
|
In der lokalen Entwicklung liest das Backend dieses Secret als Fallback direkt aus `matrix/dev/synapse/homeserver.yaml`, sofern `NODE_ENV` nicht `production` ist. Auf Servern muss das Secret weiterhin explizit über die Umgebung oder das Secret-Management gesetzt werden.
|
||||||
|
|
||||||
|
Für den eingebetteten Element-Login in FEDEO muss in der lokalen Synapse-Konfiguration außerdem der kurzlebige Login-Token-Flow aktiv sein:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
login_via_existing_session:
|
||||||
|
enabled: true
|
||||||
|
require_ui_auth: false
|
||||||
|
token_timeout: "5m"
|
||||||
|
```
|
||||||
|
|
||||||
|
Nach einer Änderung an `matrix/dev/synapse/homeserver.yaml` muss `matrix-dev-synapse` neu gestartet werden.
|
||||||
|
|||||||
@@ -5,6 +5,12 @@
|
|||||||
"server_name": "localhost"
|
"server_name": "localhost"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"org.matrix.msc4143.rtc_foci": [
|
||||||
|
{
|
||||||
|
"type": "livekit",
|
||||||
|
"livekit_service_url": "http://localhost:8081"
|
||||||
|
}
|
||||||
|
],
|
||||||
"disable_custom_urls": false,
|
"disable_custom_urls": false,
|
||||||
"disable_guests": true,
|
"disable_guests": true,
|
||||||
"brand": "FEDEO Matrix Dev",
|
"brand": "FEDEO Matrix Dev",
|
||||||
|
|||||||
Reference in New Issue
Block a user