feat: Update how messages load and sync, allow plugins to import messages
All checks were successful
Queue Release Build / prepare (push) Successful in 23s
Deploy Web Apps / deploy (push) Successful in 7m36s
Queue Release Build / build-windows (push) Successful in 28m3s
Queue Release Build / build-linux (push) Successful in 44m14s
Queue Release Build / finalize (push) Successful in 39s

This commit is contained in:
2026-05-18 23:21:09 +02:00
parent 94428ed170
commit 54e8b9a5e4
19 changed files with 542 additions and 86 deletions

View File

@@ -17,6 +17,7 @@ import type {
User
} from '../../../../shared-kernel';
import { MessagesActions } from '../../../../store/messages/messages.actions';
import { CHUNK_SIZE, chunkArray } from '../../../../store/messages/messages.helpers';
import { selectCurrentRoomMessages } from '../../../../store/messages/messages.selectors';
import { RoomsActions } from '../../../../store/rooms/rooms.actions';
import {
@@ -27,6 +28,8 @@ import {
} from '../../../../store/rooms/rooms.selectors';
import { UsersActions } from '../../../../store/users/users.actions';
import { selectAllUsers, selectCurrentUser } from '../../../../store/users/users.selectors';
import { defaultChannels } from '../../../../store/rooms/room-channels.defaults';
import { isChannelNameTaken, normalizeChannelName } from '../../../../store/rooms/room-channels.rules';
import type {
PluginApiAvatarUpdate,
PluginApiActionContext,
@@ -77,11 +80,11 @@ export class PluginClientApiService {
channels: {
addAudioChannel: (request) => {
requireCapability('channels.manage');
this.store.dispatch(RoomsActions.addChannel({ channel: createChannel(request, 'voice') }));
this.addPluginManagedChannel(pluginId, createChannel(request, 'voice'));
},
addTextChannel: (request) => {
requireCapability('channels.manage');
this.store.dispatch(RoomsActions.addChannel({ channel: createChannel(request, 'text') }));
this.addPluginManagedChannel(pluginId, createChannel(request, 'text'));
},
addVideoChannel: (request) => {
requireCapability('channels.manage');
@@ -743,9 +746,86 @@ export class PluginClientApiService {
}
this.store.dispatch(MessagesActions.syncMessages({ messages: normalizedMessages }));
// Broadcast imported history to peers in CHUNK_SIZE batches so they don't
// depend on the inventory-limited background sync to discover bulk imports.
for (const chunk of chunkArray(normalizedMessages, CHUNK_SIZE)) {
this.voice.broadcastMessage({
type: 'chat-sync-batch',
roomId,
messages: chunk
} as unknown as ChatEvent);
}
this.logger.info(pluginId, 'Historical messages imported', { count: normalizedMessages.length });
}
private addPluginManagedChannel(pluginId: string, channel: Channel): void {
const room = this.currentRoom();
const currentUser = this.currentUser();
if (!room || !currentUser) {
return;
}
const isOwner = room.hostId === currentUser.id || room.hostId === currentUser.oderId;
const isServerAdmin = currentUser.role === 'admin' || currentUser.role === 'host';
const canManageChannels = resolveRoomPermission(room, currentUser, 'manageChannels');
if (!isOwner && !isServerAdmin && !canManageChannels) {
this.logger.warn(pluginId, 'Plugin channel creation denied by room permissions', {
channelId: channel.id,
roomId: room.id
});
return;
}
const existingChannels = room.channels ?? defaultChannels();
const normalizedName = normalizeChannelName(channel.name);
const channelExists = existingChannels.some((entry) => entry.id === channel.id) ||
isChannelNameTaken(existingChannels, normalizedName, channel.type);
if (!normalizedName || channelExists) {
return;
}
const channels = [
...existingChannels,
{ ...channel,
name: normalizedName }
];
this.store.dispatch(RoomsActions.updateRoom({ roomId: room.id,
changes: { channels } }));
void this.db.updateRoom(room.id, { channels }).catch((error: unknown) => {
this.logger.warn(pluginId, 'Failed to persist plugin-created channel', error);
});
this.realtime.broadcastMessage({
type: 'channels-update',
roomId: room.id,
channels
});
this.serverDirectory.updateServer(room.id, {
actingRole: isOwner ? 'host' : undefined,
channels,
currentOwnerId: currentUser.id
}, {
sourceId: room.sourceId,
sourceUrl: room.sourceUrl
}).subscribe({
error: () => {}
});
this.logger.info(pluginId, 'Plugin channel created', {
channelId: channel.id,
roomId: room.id
});
}
private persistPluginMessageUpdate(pluginId: string, messageId: string, updates: Partial<Message>): void {
void this.db.updateMessage(messageId, updates).catch((error: unknown) => {
this.logger.warn(pluginId, 'Failed to persist plugin message update', error);