mira-app-server
Version:
Mira Server - standalone server application using mira-app-core
249 lines • 10.1 kB
JavaScript
"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