diff --git a/backend/src/modules/matrix.service.ts b/backend/src/modules/matrix.service.ts index eb2f332..213f435 100644 --- a/backend/src/modules/matrix.service.ts +++ b/backend/src/modules/matrix.service.ts @@ -35,8 +35,18 @@ type MatrixUserSession = { validUntilMs: number } +type MatrixCachedValue = { + exists: true + cachedUntil: number + value: T +} + const matrixUserSessionCache = new Map() const matrixJoinedRoomCache = new Map() +const matrixProvisionedUserCache = new Map() +const matrixTenantSpaceCache = new Map() +const matrixTenantRoomCache = new Map() +let matrixServiceSessionCache: MatrixUserSession | null = null const trimTrailingSlash = (value: string) => value.replace(/\/+$/, "") const readLocalDevRegistrationSharedSecret = () => { @@ -229,6 +239,10 @@ export function matrixService(server: FastifyInstance) { ) } + if (matrixServiceSessionCache && matrixServiceSessionCache.validUntilMs > Date.now() + 60_000) { + return matrixServiceSessionCache + } + const username = serviceUserLocalpart() const password = serviceUserPassword() @@ -240,7 +254,14 @@ export function matrixService(server: FastifyInstance) { } } - return loginMatrixUser(username, password) + const login = await loginMatrixUser(username, password) + matrixServiceSessionCache = { + accessToken: login.access_token, + matrixUserId: login.user_id, + validUntilMs: Date.now() + 30 * 60 * 1000, + } + + return matrixServiceSessionCache } const getCurrentUserDisplayName = async (userId: string, tenantId: number | null) => { @@ -320,7 +341,7 @@ export function matrixService(server: FastifyInstance) { const login = await requestMatrixJson<{ access_token: string }>( `/_synapse/admin/v1/users/${encodeURIComponent(matrixUserId)}/login`, - serviceLogin.access_token, + serviceLogin.accessToken, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -386,11 +407,25 @@ export function matrixService(server: FastifyInstance) { const username = await matrixLocalpartForUser(userId, tenantId) const matrixUserId = await matrixUserIdForUser(userId, tenantId) - const password = randomBytes(32).toString("base64url") const displayName = await getCurrentUserDisplayName(userId, tenantId) + const cacheKey = `${tenantId || "global"}:${userId}` + const cachedUntil = matrixProvisionedUserCache.get(cacheKey) + + if (cachedUntil && cachedUntil > Date.now()) { + return { + matrixUserId, + localpart: username, + displayName, + created: false, + alreadyExisted: true, + } + } + + const password = randomBytes(32).toString("base64url") try { await registerWithSharedSecret(username, password, false) + matrixProvisionedUserCache.set(cacheKey, Date.now() + 30 * 60 * 1000) return { matrixUserId, @@ -401,6 +436,8 @@ export function matrixService(server: FastifyInstance) { } } catch (err: any) { if (err.errcode === "M_USER_IN_USE") { + matrixProvisionedUserCache.set(cacheKey, Date.now() + 30 * 60 * 1000) + return { matrixUserId, localpart: username, @@ -478,23 +515,37 @@ export function matrixService(server: FastifyInstance) { const provisionCurrentTenantSpace = async (userId: string, tenantId: number | null) => { const tenant = await getCurrentTenant(tenantId) + const cacheKey = String(tenant.id) + const cachedSpace = matrixTenantSpaceCache.get(cacheKey) + + if (cachedSpace?.exists && cachedSpace.cachedUntil > Date.now()) { + return cachedSpace.value + } + const existing = await getTenantSpaceStatus(tenant.id) const userAccount = await provisionCurrentUser(userId, tenant.id) if (existing.exists) { - return { + const value = { ...existing, created: false, alreadyExisted: true, invitedUserId: userAccount.matrixUserId, } + + matrixTenantSpaceCache.set(cacheKey, { + exists: true, + cachedUntil: Date.now() + 30 * 60 * 1000, + value, + }) + return value } const serviceLogin = await ensureServiceAccessToken() const aliasLocalpart = tenantSpaceAliasLocalpart(tenant) const createdRoom = await requestMatrixJson<{ room_id: string }>( "/_matrix/client/v3/createRoom", - serviceLogin.access_token, + serviceLogin.accessToken, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -521,7 +572,7 @@ export function matrixService(server: FastifyInstance) { } ) - return { + const value = { tenantId: tenant.id, tenantName: tenant.name, alias: tenantSpaceAlias(tenant), @@ -530,8 +581,15 @@ export function matrixService(server: FastifyInstance) { alreadyExisted: false, roomId: createdRoom.room_id, invitedUserId: userAccount.matrixUserId, - serviceUserId: serviceLogin.user_id, + serviceUserId: serviceLogin.matrixUserId, } + + matrixTenantSpaceCache.set(cacheKey, { + exists: true, + cachedUntil: Date.now() + 30 * 60 * 1000, + value, + }) + return value } const getTenantRoomStatus = async ( @@ -589,24 +647,38 @@ export function matrixService(server: FastifyInstance) { const key = normalizeMatrixAliasSeed(options.key || options.name || "allgemein") const name = (options.name || "Allgemeiner Chat").trim() || "Allgemeiner Chat" const topic = (options.topic || `Allgemeiner Kommunikationsraum für ${tenant.name}`).trim() + const cacheKey = `${tenant.id}:${key}` + const cachedRoom = matrixTenantRoomCache.get(cacheKey) + + if (cachedRoom?.exists && cachedRoom.cachedUntil > Date.now()) { + return cachedRoom.value + } + const existing = await getTenantRoomStatus(tenant.id, key, name) const userAccount = await provisionCurrentUser(userId, tenant.id) const tenantSpace = await provisionCurrentTenantSpace(userId, tenant.id) if (existing.exists) { - return { + const value = { ...existing, created: false, alreadyExisted: true, parentSpaceRoomId: tenantSpace.roomId, invitedUserId: userAccount.matrixUserId, } + + matrixTenantRoomCache.set(cacheKey, { + exists: true, + cachedUntil: Date.now() + 30 * 60 * 1000, + value, + }) + return value } const serviceLogin = await ensureServiceAccessToken() const createdRoom = await requestMatrixJson<{ room_id: string }>( "/_matrix/client/v3/createRoom", - serviceLogin.access_token, + serviceLogin.accessToken, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -640,7 +712,7 @@ export function matrixService(server: FastifyInstance) { await requestMatrixJson( `/_matrix/client/v3/rooms/${encodeURIComponent(tenantSpace.roomId)}/state/m.space.child/${encodeURIComponent(createdRoom.room_id)}`, - serviceLogin.access_token, + serviceLogin.accessToken, { method: "PUT", headers: { "Content-Type": "application/json" }, @@ -652,7 +724,7 @@ export function matrixService(server: FastifyInstance) { } ) - return { + const value = { tenantId: tenant.id, tenantName: tenant.name, key, @@ -664,8 +736,15 @@ export function matrixService(server: FastifyInstance) { roomId: createdRoom.room_id, parentSpaceRoomId: tenantSpace.roomId, invitedUserId: userAccount.matrixUserId, - serviceUserId: serviceLogin.user_id, + serviceUserId: serviceLogin.matrixUserId, } + + matrixTenantRoomCache.set(cacheKey, { + exists: true, + cachedUntil: Date.now() + 30 * 60 * 1000, + value, + }) + return value } const ensureCurrentUserJoinedRoom = async (