UNPKG

mira-app-server

Version:

Mira Server - standalone server application using mira-app-core

249 lines 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MiraWebsocketServer = void 0; const ws_1 = require("ws"); const mira_app_core_1 = require("mira-app-core"); const WebSocketRouter_1 = require("./routes/WebSocketRouter"); const permission_1 = require("./middleware/permission"); class MiraWebsocketServer { constructor(backend) { this.libraryClients = {}; this.backend = backend; } async start(port) { this.port = port; this.wss = new ws_1.WebSocketServer({ port: this.port }); this.wss.on('connection', async (ws, request) => { const urlString = request.url ?? ''; const url = new URL(urlString, `ws://${request.headers.host}`); const clientId = url.searchParams.get('clientId'); const libraryId = url.searchParams.get('libraryId'); const token = url.searchParams.get('token'); if (clientId == null || libraryId == null) { console.warn('[WebSocketServer] Missing clientId or libraryId, closing connection:', request.url); ws.close(); return; } // 认证检查 const settings = this.backend.settingsManager.getSettings(); let user; if (settings.authRequired) { if (!token) { console.warn(`[WebSocketServer] No token provided, closing: clientId=${clientId}`); ws.close(4001, 'Authentication required'); return; } const authService = this.backend.httpServer?.authRouter.getAuthService(); if (!authService) { ws.close(1011, 'Server error'); return; } const validated = await authService.validateToken(token); if (!validated) { console.warn(`[WebSocketServer] Invalid token, closing: clientId=${clientId}`); ws.close(4001, 'Authentication failed'); return; } user = { id: validated.id, username: validated.username, role: validated.role }; // 库权限检查 const libConfig = this.backend.libraries?.getLibraryConfig(libraryId); if (!(0, permission_1.canAccessLibrary)(libConfig, user.role)) { console.warn(`[WebSocketServer] Access denied: user=${user.username} role=${user.role} library=${libraryId}`); ws.close(4003, 'Access denied to library'); return; } } console.log(`WebSocket connection established: clientId=${clientId}, libraryId=${libraryId}${user ? `, user=${user.username}(${user.role})` : ''}`); this.registerClient(ws, clientId, libraryId, { url: request.url, headers: request.headers, remoteAddress: request.socket.remoteAddress }, user); this.handleConnection(ws); }); } broadcastToClients(eventName, eventData) { const obj = this.backend.libraries.getLibrary(eventData.libraryId); if (!obj) return; const eventManager = obj.eventManager; if (!eventManager) return; eventManager.broadcast(eventName, new mira_app_core_1.EventArgs(eventName, eventData)); } getWsClientById(libraryId, clientId) { const clients = this.libraryClients[libraryId]; if (!clients) return undefined; return clients.find((client) => client.clientId === clientId); } setClientFields(libraryId, clientId, fields) { const client = this.getWsClientById(libraryId, clientId); if (!client) return; client.fields = client.fields || {}; for (const [key, value] of Object.entries(fields)) { if (value === null || value === undefined) { delete client.fields[key]; } else { client.fields[key] = value; } } } getClientFields(libraryId, clientId) { if (!clientId) return undefined; const client = this.getWsClientById(libraryId, clientId); return client?.fields; } showDialogToWeboscket(ws, data) { this.sendToWebsocket(ws, { eventName: 'dialog', data: Object.assign({ title: '提示', message: '', url: '' }, data) }); } sendToWebsocket(ws, data) { // console.log('Sending WebSocket message:', data); ws.send(JSON.stringify(data)); } broadcastPluginEvent(eventName, data) { const libraryId = data?.libraryId ?? data?.message?.libraryId; const obj = this.backend.libraries.getLibrary(libraryId); if (!obj) return Promise.resolve(false); const eventManager = obj.eventManager; if (!eventManager) return Promise.resolve(false); return eventManager.broadcast(eventName, new mira_app_core_1.EventArgs(eventName, data)); } broadcastLibraryEvent(libraryId, eventName, data) { const message = JSON.stringify({ eventName, data }); const clients = this.libraryClients[libraryId] || []; clients.forEach(client => { if (client.readyState === ws_1.WebSocket.OPEN) { client.send(message); } }); } async stop() { this.backend.libraries.clear(); this.wss?.close(); console.log('WebSocket server stopped'); } handleConnection(ws) { ws.on('message', async (message) => { try { ws.lastActivity = new Date().toISOString(); const data = JSON.parse(message); await this.handleMessage(ws, data); } catch (e) { this.sendToWebsocket(ws, { error: 'Invalid message format', details: e instanceof Error ? e.message : String(e) }); } }); ws.on('close', () => { this.unregisterClient(ws); }); } async handleMessage(ws, row) { const client = ws; // 心跳响应:ping 直接回 pong,不走业务逻辑 if (row.eventName === 'ping') { this.sendToWebsocket(ws, { eventName: 'pong' }); return; } const payload = row.payload || {}; const action = row.action; const requestId = row.requestId; const libraryId = row.libraryId; const data = payload.data || {}; const recordType = payload.type; // 库权限检查:如果消息目标库和用户角色不匹配 if (client.user && libraryId) { const libConfig = this.backend.libraries?.getLibraryConfig(libraryId); if (!(0, permission_1.canAccessLibrary)(libConfig, client.user.role)) { this.sendToWebsocket(ws, { status: 'error', message: 'Access denied to library', requestId }); return; } } const exists = this.backend.libraries.libraryExists(libraryId); if (!exists) { this.sendToWebsocket(ws, { status: 'error', msg: 'Library not found!' }); return; } const obj = this.backend.libraries.getLibrary(libraryId); if (!obj) { this.sendToWebsocket(ws, { status: 'error', msg: 'Library service not found' }); return; } const handler = await WebSocketRouter_1.WebSocketRouter.route(this, obj.libraryService, ws, { ...row, ...payload }); if (handler) { await handler.handle(); return; } this.sendToWebsocket(ws, { status: 'error', message: `Unsupported action: ${action} and record type: ${recordType}`, requestId }); } registerClient(ws, clientId, libraryId, requestInfo, user) { const now = new Date().toISOString(); Object.assign(ws, { clientId, libraryId, user, connectionTime: now, lastActivity: now, requestInfo }); this.libraryClients[libraryId] = this.libraryClients[libraryId] || []; const duplicateIndex = this.libraryClients[libraryId].findIndex(client => client.clientId === clientId); if (duplicateIndex !== -1) { this.libraryClients[libraryId].splice(duplicateIndex, 1); } this.libraryClients[libraryId].push(ws); console.log(`[WebSocketServer] Registered client ${clientId} for library ${libraryId}. ` + `libraryConnections=${this.libraryClients[libraryId].length}, totalConnections=${this.getTotalConnectionCount()}`); } unregisterClient(ws) { Object.keys(this.libraryClients).forEach(libraryId => { const index = this.libraryClients[libraryId].findIndex(client => client === ws); if (index === -1) return; const clientId = ws.clientId || 'unknown'; this.libraryClients[libraryId].splice(index, 1); if (this.libraryClients[libraryId].length === 0) { delete this.libraryClients[libraryId]; } console.log(`[WebSocketServer] Unregistered client ${clientId} from library ${libraryId}. ` + `totalConnections=${this.getTotalConnectionCount()}`); }); } getTotalConnectionCount() { return Object.values(this.libraryClients).reduce((sum, clients) => sum + clients.length, 0); } } exports.MiraWebsocketServer = MiraWebsocketServer; //# sourceMappingURL=WebSocketServer.js.map