mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-27 06:05:54 +08:00
feat: add desktop notifications and skills updates
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import type { WebSocket } from 'ws';
|
||||
|
||||
import {
|
||||
registerDesktopNotificationClient,
|
||||
unregisterDesktopNotificationClient,
|
||||
} from '@/modules/notifications/services/desktop-notification-clients.service.js';
|
||||
import type { AuthenticatedWebSocketRequest } from '@/shared/types.js';
|
||||
import { parseIncomingJsonObject } from '@/shared/utils.js';
|
||||
|
||||
type DesktopNotificationRegisterMessage = {
|
||||
type?: unknown;
|
||||
kind?: unknown;
|
||||
deviceId?: unknown;
|
||||
label?: unknown;
|
||||
platform?: unknown;
|
||||
appVersion?: unknown;
|
||||
};
|
||||
|
||||
function readRequestUserId(request: AuthenticatedWebSocketRequest): number | null {
|
||||
const user = request.user;
|
||||
const rawUserId = typeof user?.id === 'number' || typeof user?.id === 'string'
|
||||
? user.id
|
||||
: typeof user?.userId === 'number' || typeof user?.userId === 'string'
|
||||
? user.userId
|
||||
: null;
|
||||
const numericUserId = Number(rawUserId);
|
||||
return Number.isInteger(numericUserId) && numericUserId > 0 ? numericUserId : null;
|
||||
}
|
||||
|
||||
function readOptionalString(value: unknown): string | null {
|
||||
if (typeof value !== 'string') return null;
|
||||
const normalized = value.trim();
|
||||
return normalized || null;
|
||||
}
|
||||
|
||||
function sendJson(ws: WebSocket, payload: unknown): void {
|
||||
if (ws.readyState === ws.OPEN) {
|
||||
ws.send(JSON.stringify(payload));
|
||||
}
|
||||
}
|
||||
|
||||
export function handleDesktopNotificationsConnection(
|
||||
ws: WebSocket,
|
||||
request: AuthenticatedWebSocketRequest
|
||||
): void {
|
||||
const userId = readRequestUserId(request);
|
||||
if (!userId) {
|
||||
ws.close(1008, 'Missing authenticated user');
|
||||
return;
|
||||
}
|
||||
|
||||
let registered = false;
|
||||
|
||||
ws.on('message', (rawMessage) => {
|
||||
const data = parseIncomingJsonObject(rawMessage) as DesktopNotificationRegisterMessage | null;
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const type = typeof data.type === 'string' ? data.type : typeof data.kind === 'string' ? data.kind : '';
|
||||
if (type === 'notification_ack') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type !== 'register' || registered) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deviceId = readOptionalString(data.deviceId);
|
||||
if (!deviceId) {
|
||||
sendJson(ws, {
|
||||
type: 'error',
|
||||
code: 'DEVICE_ID_REQUIRED',
|
||||
message: 'Desktop notification registration requires deviceId.',
|
||||
});
|
||||
ws.close(1008, 'Missing deviceId');
|
||||
return;
|
||||
}
|
||||
|
||||
const device = registerDesktopNotificationClient({
|
||||
userId,
|
||||
deviceId,
|
||||
label: readOptionalString(data.label),
|
||||
platform: readOptionalString(data.platform),
|
||||
appVersion: readOptionalString(data.appVersion),
|
||||
ws,
|
||||
});
|
||||
|
||||
if (!device) {
|
||||
ws.close(1011, 'Registration failed');
|
||||
return;
|
||||
}
|
||||
|
||||
registered = true;
|
||||
sendJson(ws, {
|
||||
type: 'registered',
|
||||
deviceId: device.endpoint_id,
|
||||
enabled: Boolean(device.enabled),
|
||||
});
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
unregisterDesktopNotificationClient(ws);
|
||||
});
|
||||
|
||||
ws.on('error', () => {
|
||||
unregisterDesktopNotificationClient(ws);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user