mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-27 06:05:54 +08:00
110 lines
2.8 KiB
TypeScript
110 lines
2.8 KiB
TypeScript
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);
|
|
});
|
|
}
|