feat: expose more apis

This commit is contained in:
2026-04-29 23:39:09 +02:00
parent fa2cca6fa4
commit 3f92e74350
14 changed files with 297 additions and 23 deletions

View File

@@ -26,11 +26,15 @@ import { UsersActions } from '../../../../store/users/users.actions';
import { selectAllUsers, selectCurrentUser } from '../../../../store/users/users.selectors';
import type {
PluginApiAvatarUpdate,
PluginApiActionContext,
PluginApiActionSource,
PluginApiChannelRequest,
PluginApiCustomStreamRequest,
PluginApiMessageAsPluginUserRequest,
PluginApiServerSettingsUpdate,
TojuClientPluginApi
PluginApiTypingEvent,
TojuClientPluginApi,
TojuPluginDisposable
} from '../../domain/models/plugin-api.models';
import { PluginCapabilityService } from './plugin-capability.service';
import { PluginLoggerService } from './plugin-logger.service';
@@ -122,6 +126,9 @@ export class PluginClientApiService {
info: (message, data) => this.logger.info(pluginId, message, data),
warn: (message, data) => this.logger.warn(pluginId, message, data)
},
context: {
getCurrent: () => this.createActionContext('manual')
},
clientData: {
read: async (key) => {
requireCapability('storage.local');
@@ -183,6 +190,14 @@ export class PluginClientApiService {
requireCapability('messages.send');
this.receivePluginUserMessage(pluginId, request);
},
setTyping: (isTyping, channelId) => {
requireCapability('messages.send');
this.setTyping(pluginId, isTyping, channelId);
},
subscribeTyping: (handler) => {
requireCapability('messages.read');
return this.subscribeTyping(pluginId, handler);
},
sync: (messages) => {
requireCapability('messages.sync');
this.store.dispatch(MessagesActions.syncMessages({ messages }));
@@ -402,6 +417,24 @@ export class PluginClientApiService {
});
}
createActionContext(source: PluginApiActionSource): PluginApiActionContext {
const user = this.currentUser() ?? null;
const server = this.currentRoom();
const channels = this.currentRoomChannels();
const activeChannelId = this.activeChannelId() ?? 'general';
const voiceChannelId = user?.voiceState?.roomId ?? null;
return {
server,
source,
textChannel: channels.find((channel) => channel.type === 'text' && channel.id === activeChannelId) ?? null,
user,
voiceChannel: voiceChannelId
? channels.find((channel) => channel.type === 'voice' && channel.id === voiceChannelId) ?? null
: null
};
}
private assertDeclaredEvent(manifest: TojuPluginManifest, eventName: string): void {
const declared = manifest.events?.some((event) => event.eventName === eventName) ?? false;
@@ -544,6 +577,71 @@ export class PluginClientApiService {
return message;
}
private setTyping(pluginId: string, isTyping: boolean, channelId?: string): void {
const roomId = this.requireRoomId();
try {
this.realtime.sendRawMessage({
type: 'typing',
serverId: roomId,
channelId: channelId ?? this.activeChannelId() ?? 'general',
isTyping
});
} catch (error: unknown) {
this.logger.warn(pluginId, 'Failed to publish typing state', error);
}
}
private subscribeTyping(pluginId: string, handler: (event: PluginApiTypingEvent) => void): TojuPluginDisposable {
const subscription = new Subscription();
subscription.add(this.realtime.onSignalingMessage.subscribe((message) => {
const record = message as Record<string, unknown>;
if (record['type'] !== 'user_typing') {
return;
}
const serverId = typeof record['serverId'] === 'string' ? record['serverId'] : '';
const currentServer = this.currentRoom();
if (!serverId || serverId !== currentServer?.id) {
return;
}
const userId = typeof record['oderId'] === 'string' ? record['oderId'] : '';
const displayName = typeof record['displayName'] === 'string' ? record['displayName'] : userId;
const channelId = typeof record['channelId'] === 'string' && record['channelId'].trim()
? record['channelId'].trim()
: 'general';
const user = this.users().find((entry) => entry.oderId === userId || entry.id === userId) ?? null;
const channels = this.currentRoomChannels();
handler({
channelId,
displayName,
isTyping: record['isTyping'] !== false,
server: currentServer,
serverId,
textChannel: channels.find((channel) => channel.type === 'text' && channel.id === channelId) ?? null,
user,
userId,
voiceChannel: user?.voiceState?.roomId
? channels.find((channel) => channel.type === 'voice' && channel.id === user.voiceState?.roomId) ?? null
: null
});
}));
this.logger.info(pluginId, 'Subscribed to typing events');
return {
dispose: () => {
subscription.unsubscribe();
this.logger.info(pluginId, 'Unsubscribed from typing events');
}
};
}
private persistPluginMessage(pluginId: string, message: Message): void {
void this.db.saveMessage(message).catch((error: unknown) => {
this.logger.warn(pluginId, 'Failed to persist plugin message', error);