Files
redlight/server/jobs/federationSync.js
Michelle 57bb1fb696
Some checks failed
Build & Push Docker Image / build (push) Has been cancelled
Build & Push Docker Image / build (release) Successful in 7m27s
feat(logging): implement centralized logging system and replace console errors with structured logs
feat(federation): add room sync and deletion notification endpoints for federated instances

fix(federation): handle room deletion and update settings during sync process

feat(federation): enhance FederatedRoomCard and FederatedRoomDetail components to display deleted rooms

i18n: add translations for room deletion messages in English and German
2026-03-01 12:20:14 +01:00

155 lines
4.7 KiB
JavaScript

import { getDb } from '../config/database.js';
import { log, fmtDuration } from '../config/logger.js';
import {
isFederationEnabled,
getFederationDomain,
signPayload,
discoverInstance,
parseAddress,
} from '../config/federation.js';
const SYNC_INTERVAL_MS = 60_000; // 1 minute
let syncTimer = null;
/**
* Periodic federation sync job.
* Groups federated rooms by origin domain, then batch-queries each origin
* for current room settings. Updates local records if settings changed or
* if the room was deleted on the origin.
*/
async function runSync() {
if (!isFederationEnabled()) return;
const syncStart = Date.now();
let totalUpdated = 0;
let totalDeleted = 0;
let totalRooms = 0;
try {
const db = getDb();
// Fetch all non-deleted federated rooms
const rooms = await db.all(
'SELECT id, meet_id, from_user, room_name, max_participants, allow_recording FROM federated_rooms WHERE deleted = 0'
);
if (rooms.length === 0) return;
totalRooms = rooms.length;
// Group by origin domain
const byDomain = new Map();
for (const room of rooms) {
if (!room.meet_id) continue; // no room UID, can't sync
const { domain } = parseAddress(room.from_user);
if (!domain) continue;
if (!byDomain.has(domain)) byDomain.set(domain, []);
byDomain.get(domain).push(room);
}
// Query each origin domain
for (const [domain, domainRooms] of byDomain) {
try {
const roomUids = [...new Set(domainRooms.map(r => r.meet_id))];
const payload = {
room_uids: roomUids,
timestamp: new Date().toISOString(),
};
const signature = signPayload(payload);
const { baseUrl: remoteApi } = await discoverInstance(domain);
const response = await fetch(`${remoteApi}/room-sync`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Federation-Signature': signature,
'X-Federation-Origin': getFederationDomain(),
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(15_000),
});
if (!response.ok) {
log.fedSync.warn(`${domain} responded with status ${response.status}`);
continue;
}
const data = await response.json();
const remoteRooms = data.rooms || {};
// Update local records
for (const localRoom of domainRooms) {
const remote = remoteRooms[localRoom.meet_id];
if (!remote) continue; // UID not in response, skip
if (remote.deleted) {
// Room was deleted on origin
await db.run(
'UPDATE federated_rooms SET deleted = 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
[localRoom.id]
);
totalDeleted++;
log.fedSync.info(`Room ${localRoom.meet_id} deleted on origin ${domain}`);
} else {
// Check if settings changed
const changed =
localRoom.room_name !== remote.room_name ||
(localRoom.max_participants ?? 0) !== (remote.max_participants ?? 0) ||
(localRoom.allow_recording ?? 1) !== (remote.allow_recording ?? 1);
if (changed) {
await db.run(
`UPDATE federated_rooms
SET room_name = ?, max_participants = ?, allow_recording = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
[remote.room_name, remote.max_participants ?? 0, remote.allow_recording ?? 1, localRoom.id]
);
totalUpdated++;
log.fedSync.info(`Room ${localRoom.meet_id} settings updated from ${domain}`);
}
}
}
} catch (err) {
log.fedSync.warn(`Sync with ${domain} failed: ${err.message}`);
}
}
// Summary log (only if something happened)
if (totalUpdated > 0 || totalDeleted > 0) {
log.fedSync.info(
`Sync complete: ${totalRooms} rooms, ${totalUpdated} updated, ${totalDeleted} deleted (${fmtDuration(Date.now() - syncStart)})`
);
}
} catch (err) {
log.fedSync.error(`Sync job failed: ${err.message}`);
}
}
/**
* Start the periodic federation sync job.
*/
export function startFederationSync() {
if (!isFederationEnabled()) {
log.fedSync.info('Disabled (federation not configured)');
return;
}
// Run first sync after a short delay to let the server fully start
setTimeout(() => {
runSync();
syncTimer = setInterval(runSync, SYNC_INTERVAL_MS);
log.fedSync.info('Started (interval: 60s)');
}, 5_000);
}
/**
* Stop the periodic federation sync job.
*/
export function stopFederationSync() {
if (syncTimer) {
clearInterval(syncTimer);
syncTimer = null;
}
}