mira-app-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/HttpRouter.d.ts +17 -0
- package/dist/HttpRouter.d.ts.map +1 -0
- package/dist/HttpRouter.js +217 -0
- package/dist/HttpRouter.js.map +1 -0
- package/dist/HttpServer.d.ts +24 -0
- package/dist/HttpServer.d.ts.map +1 -0
- package/dist/HttpServer.js +55 -0
- package/dist/HttpServer.js.map +1 -0
- package/dist/ILibraryServerData.d.ts +63 -0
- package/dist/ILibraryServerData.d.ts.map +1 -0
- package/dist/ILibraryServerData.js +3 -0
- package/dist/ILibraryServerData.js.map +1 -0
- package/dist/LibraryList.d.ts +9 -0
- package/dist/LibraryList.d.ts.map +1 -0
- package/dist/LibraryList.js +29 -0
- package/dist/LibraryList.js.map +1 -0
- package/dist/LibraryServerDataSQLite.d.ts +96 -0
- package/dist/LibraryServerDataSQLite.d.ts.map +1 -0
- package/dist/LibraryServerDataSQLite.js +683 -0
- package/dist/LibraryServerDataSQLite.js.map +1 -0
- package/dist/LibraryStorage.d.ts +14 -0
- package/dist/LibraryStorage.d.ts.map +1 -0
- package/dist/LibraryStorage.js +45 -0
- package/dist/LibraryStorage.js.map +1 -0
- package/dist/MessageHandler.d.ts +16 -0
- package/dist/MessageHandler.d.ts.map +1 -0
- package/dist/MessageHandler.js +35 -0
- package/dist/MessageHandler.js.map +1 -0
- package/dist/ServerExample.d.ts +33 -0
- package/dist/ServerExample.d.ts.map +1 -0
- package/dist/ServerExample.js +87 -0
- package/dist/ServerExample.js.map +1 -0
- package/dist/ServerPlugin.d.ts +20 -0
- package/dist/ServerPlugin.d.ts.map +1 -0
- package/dist/ServerPlugin.js +79 -0
- package/dist/ServerPlugin.js.map +1 -0
- package/dist/ServerPluginManager.d.ts +30 -0
- package/dist/ServerPluginManager.d.ts.map +1 -0
- package/dist/ServerPluginManager.js +112 -0
- package/dist/ServerPluginManager.js.map +1 -0
- package/dist/WebSocketRouter.d.ts +18 -0
- package/dist/WebSocketRouter.d.ts.map +1 -0
- package/dist/WebSocketRouter.js +31 -0
- package/dist/WebSocketRouter.js.map +1 -0
- package/dist/WebSocketServer.d.ts +23 -0
- package/dist/WebSocketServer.d.ts.map +1 -0
- package/dist/WebSocketServer.js +162 -0
- package/dist/WebSocketServer.js.map +1 -0
- package/dist/event-manager.d.ts +85 -0
- package/dist/event-manager.d.ts.map +1 -0
- package/dist/event-manager.js +142 -0
- package/dist/event-manager.js.map +1 -0
- package/dist/handlers/FileHandler.d.ts +10 -0
- package/dist/handlers/FileHandler.d.ts.map +1 -0
- package/dist/handlers/FileHandler.js +55 -0
- package/dist/handlers/FileHandler.js.map +1 -0
- package/dist/handlers/FolderHandler.d.ts +10 -0
- package/dist/handlers/FolderHandler.d.ts.map +1 -0
- package/dist/handlers/FolderHandler.js +59 -0
- package/dist/handlers/FolderHandler.js.map +1 -0
- package/dist/handlers/LibraryHandler.d.ts +10 -0
- package/dist/handlers/LibraryHandler.d.ts.map +1 -0
- package/dist/handlers/LibraryHandler.js +49 -0
- package/dist/handlers/LibraryHandler.js.map +1 -0
- package/dist/handlers/MessageHandler.d.ts +15 -0
- package/dist/handlers/MessageHandler.d.ts.map +1 -0
- package/dist/handlers/MessageHandler.js +32 -0
- package/dist/handlers/MessageHandler.js.map +1 -0
- package/dist/handlers/PluginMessageHandler.d.ts +10 -0
- package/dist/handlers/PluginMessageHandler.d.ts.map +1 -0
- package/dist/handlers/PluginMessageHandler.js +21 -0
- package/dist/handlers/PluginMessageHandler.js.map +1 -0
- package/dist/handlers/TagHandler.d.ts +10 -0
- package/dist/handlers/TagHandler.d.ts.map +1 -0
- package/dist/handlers/TagHandler.js +59 -0
- package/dist/handlers/TagHandler.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/package.json +32 -0
- package/src/HttpRouter.ts +236 -0
- package/src/HttpServer.ts +72 -0
- package/src/ILibraryServerData.ts +70 -0
- package/src/LibraryList.ts +26 -0
- package/src/LibraryServerDataSQLite.ts +778 -0
- package/src/LibraryStorage.ts +55 -0
- package/src/MessageHandler.ts +41 -0
- package/src/ServerExample.ts +72 -0
- package/src/ServerPlugin.ts +56 -0
- package/src/ServerPluginManager.ts +106 -0
- package/src/WebSocketRouter.ts +46 -0
- package/src/WebSocketServer.ts +206 -0
- package/src/event-manager.ts +191 -0
- package/src/handlers/FileHandler.ts +61 -0
- package/src/handlers/FolderHandler.ts +65 -0
- package/src/handlers/LibraryHandler.ts +55 -0
- package/src/handlers/MessageHandler.ts +37 -0
- package/src/handlers/PluginMessageHandler.ts +27 -0
- package/src/handlers/TagHandler.ts +66 -0
- package/src/index.ts +44 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { LibraryServerDataSQLite } from "./LibraryServerDataSQLite";
|
|
2
|
+
import { MiraBackend } from "./ServerExample";
|
|
3
|
+
import { getLibrarysJson } from './LibraryList';
|
|
4
|
+
|
|
5
|
+
export class LibraryStorage {
|
|
6
|
+
libraryServices: LibraryServerDataSQLite[] = [];
|
|
7
|
+
backend: MiraBackend;
|
|
8
|
+
|
|
9
|
+
constructor(backend: MiraBackend) {
|
|
10
|
+
this.backend = backend;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
all(): LibraryServerDataSQLite[] {
|
|
14
|
+
return this.libraryServices;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async load(dbConfig: Record<string, any>): Promise<LibraryServerDataSQLite> {
|
|
18
|
+
const dbServer = new LibraryServerDataSQLite(dbConfig, {webSocketServer: this.backend.webSocketServer, httpServer: this.backend.httpServer} );
|
|
19
|
+
await dbServer.initialize();
|
|
20
|
+
this.libraryServices.push(dbServer);
|
|
21
|
+
return dbServer;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async loadAll(): Promise<number> {
|
|
25
|
+
let success = 0;
|
|
26
|
+
for (const library of await getLibrarysJson(this.backend.dataPath)) {
|
|
27
|
+
try {
|
|
28
|
+
console.log('loading library ', library.name);
|
|
29
|
+
await this.load(library);
|
|
30
|
+
success++;
|
|
31
|
+
} catch(err){
|
|
32
|
+
console.log(err)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return success;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get(libraryId: string): LibraryServerDataSQLite | undefined {
|
|
39
|
+
return this.libraryServices.find(
|
|
40
|
+
(library) => library.getLibraryId() === libraryId
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
exists(libraryId: string): boolean {
|
|
45
|
+
return this.libraryServices.some(
|
|
46
|
+
(library) => library.getLibraryId() === libraryId
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
find(libraryId: string): LibraryServerDataSQLite | undefined {
|
|
51
|
+
return this.libraryServices.find((library) => library.getLibraryId() === libraryId);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { LibraryServerDataSQLite } from './LibraryServerDataSQLite';
|
|
2
|
+
import { WebSocket } from 'ws';
|
|
3
|
+
import { WebSocketMessage } from './WebSocketRouter';
|
|
4
|
+
|
|
5
|
+
export abstract class MessageHandler {
|
|
6
|
+
constructor(
|
|
7
|
+
protected dbService: LibraryServerDataSQLite,
|
|
8
|
+
protected ws: WebSocket,
|
|
9
|
+
protected message: WebSocketMessage
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
abstract handle(): Promise<void>;
|
|
13
|
+
|
|
14
|
+
protected sendResponse(data: Record<string, any>): void {
|
|
15
|
+
this.ws.send(JSON.stringify({
|
|
16
|
+
requestId: this.message.requestId,
|
|
17
|
+
status: 'ok',
|
|
18
|
+
data
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
protected sendError(error: string): void {
|
|
23
|
+
this.ws.send(JSON.stringify({
|
|
24
|
+
requestId: this.message.requestId,
|
|
25
|
+
status: 'error',
|
|
26
|
+
error
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
protected getLibraryId(): string {
|
|
31
|
+
return this.message.libraryId;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
protected getAction(): string {
|
|
35
|
+
return this.message.action;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected getPayload(): Record<string, any> {
|
|
39
|
+
return this.message.payload.data || {};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { MiraWebsocketServer } from './WebSocketServer';
|
|
2
|
+
import { MiraHttpServer } from './HttpServer';
|
|
3
|
+
import { LibraryStorage } from './LibraryStorage';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
|
|
6
|
+
export class MiraBackend {
|
|
7
|
+
webSocketServer: MiraWebsocketServer;
|
|
8
|
+
httpServer: MiraHttpServer;
|
|
9
|
+
libraries: LibraryStorage;
|
|
10
|
+
dataPath: string;
|
|
11
|
+
|
|
12
|
+
constructor(options?: {
|
|
13
|
+
dataPath?: string,
|
|
14
|
+
httpPort?: number,
|
|
15
|
+
wsPort?: number,
|
|
16
|
+
autoLoad?: boolean,
|
|
17
|
+
autoStart?: boolean
|
|
18
|
+
}) {
|
|
19
|
+
this.dataPath = options?.dataPath || process.env.DATA_PATH || path.join(process.cwd(), 'data');
|
|
20
|
+
this.libraries = new LibraryStorage(this);
|
|
21
|
+
|
|
22
|
+
// 只有在明确要求时才自动加载
|
|
23
|
+
if (options?.autoLoad !== false) {
|
|
24
|
+
this.libraries.loadAll().then((loaded) => console.log(`${loaded} Libraries loaded`));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.httpServer = new MiraHttpServer(options?.httpPort || 3000, this);
|
|
28
|
+
this.webSocketServer = new MiraWebsocketServer(options?.wsPort || 8081, this);
|
|
29
|
+
|
|
30
|
+
// 只有在明确要求时才自动启动
|
|
31
|
+
if (options?.autoStart === true) {
|
|
32
|
+
this.start();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 手动启动服务器
|
|
38
|
+
*/
|
|
39
|
+
start(): void {
|
|
40
|
+
this.webSocketServer.start('/ws');
|
|
41
|
+
|
|
42
|
+
// 处理退出
|
|
43
|
+
process.on('SIGINT', async () => {
|
|
44
|
+
console.log('Shutting down servers...');
|
|
45
|
+
await this.stop();
|
|
46
|
+
process.exit();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 停止服务器
|
|
52
|
+
*/
|
|
53
|
+
async stop(): Promise<void> {
|
|
54
|
+
await this.webSocketServer.stop();
|
|
55
|
+
await this.httpServer.stop();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 创建并启动服务器的静态方法 (向后兼容)
|
|
60
|
+
*/
|
|
61
|
+
static createAndStart(options?: {
|
|
62
|
+
dataPath?: string,
|
|
63
|
+
httpPort?: number,
|
|
64
|
+
wsPort?: number
|
|
65
|
+
}): MiraBackend {
|
|
66
|
+
return new MiraBackend({
|
|
67
|
+
...options,
|
|
68
|
+
autoLoad: true,
|
|
69
|
+
autoStart: true
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { ServerPluginManager } from './ServerPluginManager';
|
|
5
|
+
import { ILibraryServerData } from './ILibraryServerData';
|
|
6
|
+
import { MiraHttpServer } from './HttpServer';
|
|
7
|
+
|
|
8
|
+
export abstract class ServerPlugin {
|
|
9
|
+
protected configs: Record<string, any> = {};
|
|
10
|
+
protected readonly eventEmitter: EventEmitter;
|
|
11
|
+
protected readonly pluginDir: string;
|
|
12
|
+
protected readonly pluginDataDir: string;
|
|
13
|
+
|
|
14
|
+
constructor(protected readonly pluginName: string, pluginManager: ServerPluginManager, dbServer: ILibraryServerData, httpServer: MiraHttpServer) {
|
|
15
|
+
this.eventEmitter = dbServer.getEventManager()!;
|
|
16
|
+
this.pluginDir = pluginManager.getPluginDir(pluginName);
|
|
17
|
+
this.pluginDataDir = path.join(this.pluginDir, 'data');
|
|
18
|
+
this.ensureDirExists();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private ensureDirExists() {
|
|
22
|
+
if (!fs.existsSync(this.pluginDataDir)) {
|
|
23
|
+
fs.mkdirSync(this.pluginDataDir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected writeConfig(key: string, value: any) {
|
|
28
|
+
this.configs[key] = value;
|
|
29
|
+
this.saveConfig();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
protected saveConfig() {
|
|
33
|
+
return this.writeJson('config.json', this.configs);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected readConfig(key: string): any {
|
|
37
|
+
return this.configs[key];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
protected loadConfig(defaultConfig: Record<string, any> = {}) {
|
|
41
|
+
this.configs = {...defaultConfig, ...this.readJson('config.json')};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected writeJson(filename: string, data: any) {
|
|
45
|
+
const filePath = path.join(this.pluginDataDir, filename);
|
|
46
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected readJson(filename: string): any {
|
|
50
|
+
const filePath = path.join(this.pluginDataDir, filename);
|
|
51
|
+
if (fs.existsSync(filePath)) {
|
|
52
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
|
|
2
|
+
import { MiraHttpServer } from './HttpServer';
|
|
3
|
+
import { ILibraryServerData } from './ILibraryServerData';
|
|
4
|
+
import { MiraWebsocketServer } from './WebSocketServer';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
|
|
8
|
+
export interface PluginConfig {
|
|
9
|
+
name: string;
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
path: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ServerPluginManager {
|
|
15
|
+
pluginsDir: string;
|
|
16
|
+
private server: MiraWebsocketServer;
|
|
17
|
+
private httpServer: MiraHttpServer;
|
|
18
|
+
private dbService: ILibraryServerData;
|
|
19
|
+
private pluginsConfigPath: string;
|
|
20
|
+
private loadedPlugins: Map<string, any> = new Map();
|
|
21
|
+
fields: Record<string, any>[] = [];
|
|
22
|
+
|
|
23
|
+
constructor({server, dbService, httpServer, pluginsDir}: {server: MiraWebsocketServer, dbService: ILibraryServerData, httpServer: MiraHttpServer, pluginsDir?: string}) {
|
|
24
|
+
this.pluginsDir = pluginsDir || path.join(__dirname, 'plugins');
|
|
25
|
+
this.server = server;
|
|
26
|
+
this.dbService = dbService;
|
|
27
|
+
this.httpServer = httpServer;
|
|
28
|
+
this.pluginsConfigPath = path.join(this.pluginsDir, 'plugins.json');
|
|
29
|
+
|
|
30
|
+
// Ensure plugins directory exists
|
|
31
|
+
if (!fs.existsSync(this.pluginsDir)) {
|
|
32
|
+
fs.mkdirSync(this.pluginsDir, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Initialize plugins.json if it doesn't exist
|
|
36
|
+
if (!fs.existsSync(this.pluginsConfigPath)) {
|
|
37
|
+
fs.writeFileSync(this.pluginsConfigPath, JSON.stringify([], null, 2));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// getPluginDir
|
|
42
|
+
getPluginDir(pluginName: string): string {
|
|
43
|
+
return path.join(this.pluginsDir, pluginName);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async loadPlugins(): Promise<void> {
|
|
47
|
+
const config: PluginConfig[] = JSON.parse(
|
|
48
|
+
fs.readFileSync(this.pluginsConfigPath, 'utf-8')
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
for (const pluginConfig of config) {
|
|
52
|
+
if (pluginConfig.enabled) {
|
|
53
|
+
try {
|
|
54
|
+
const pluginPath = path.join(this.pluginsDir, pluginConfig.path);
|
|
55
|
+
delete require.cache[require.resolve(pluginPath)];
|
|
56
|
+
const pluginModule = require(pluginPath);
|
|
57
|
+
if (typeof pluginModule.init === 'function') {
|
|
58
|
+
await pluginModule.init(
|
|
59
|
+
{pluginManager: this, server: this.server, dbService: this.dbService, httpServer: this.httpServer})
|
|
60
|
+
}
|
|
61
|
+
this.loadedPlugins.set(pluginConfig.name, pluginModule);
|
|
62
|
+
console.log(`Loaded plugin: ${pluginConfig.name}`);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error(`Failed to load plugin ${pluginConfig.name}:`, err);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
registerFields(fields: Record<string, any>[]): void {
|
|
71
|
+
for (const field of fields) {
|
|
72
|
+
this.registerField(field);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
registerField(field: Record<string, any>): void {
|
|
77
|
+
let {action, type, field: fieldName} = field;
|
|
78
|
+
if(!fieldName || !action || !type) {
|
|
79
|
+
throw new Error('Field registration error: action, type, and field are required');
|
|
80
|
+
}
|
|
81
|
+
this.fields.push(field);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getPlugin<T>(name: string): T | undefined {
|
|
85
|
+
return this.loadedPlugins.get(name);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async addPlugin(config: PluginConfig): Promise<void> {
|
|
89
|
+
const currentConfig: PluginConfig[] = JSON.parse(
|
|
90
|
+
fs.readFileSync(this.pluginsConfigPath, 'utf-8')
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const existingIndex = currentConfig.findIndex(p => p.name === config.name);
|
|
94
|
+
if (existingIndex >= 0) {
|
|
95
|
+
currentConfig[existingIndex] = config;
|
|
96
|
+
} else {
|
|
97
|
+
currentConfig.push(config);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fs.writeFileSync(this.pluginsConfigPath, JSON.stringify(currentConfig, null, 2));
|
|
101
|
+
|
|
102
|
+
if (config.enabled) {
|
|
103
|
+
await this.loadPlugins();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { LibraryServerDataSQLite } from './LibraryServerDataSQLite';
|
|
2
|
+
import { WebSocket, WebSocketServer } from 'ws';
|
|
3
|
+
import { MessageHandler } from './handlers/MessageHandler';
|
|
4
|
+
import { FileHandler } from './handlers/FileHandler';
|
|
5
|
+
import { TagHandler } from './handlers/TagHandler';
|
|
6
|
+
import { FolderHandler } from './handlers/FolderHandler';
|
|
7
|
+
import { LibraryHandler } from './handlers/LibraryHandler';
|
|
8
|
+
import { PluginMessageHandler } from './handlers/PluginMessageHandler';
|
|
9
|
+
|
|
10
|
+
export interface WebSocketMessage {
|
|
11
|
+
action: string;
|
|
12
|
+
requestId: string;
|
|
13
|
+
libraryId: string;
|
|
14
|
+
clientId: string;
|
|
15
|
+
payload: {
|
|
16
|
+
type: string;
|
|
17
|
+
data: Record<string, any>;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class WebSocketRouter {
|
|
22
|
+
static async route(
|
|
23
|
+
server: any, // 修改为any类型避免类型冲突
|
|
24
|
+
dbService: LibraryServerDataSQLite,
|
|
25
|
+
ws: WebSocket,
|
|
26
|
+
message: WebSocketMessage
|
|
27
|
+
): Promise<MessageHandler | null> {
|
|
28
|
+
const { payload } = message;
|
|
29
|
+
|
|
30
|
+
// 根据资源类型路由到不同的处理器
|
|
31
|
+
switch (payload.type) {
|
|
32
|
+
case 'plugin':
|
|
33
|
+
return new PluginMessageHandler(server, dbService, ws, message);
|
|
34
|
+
case 'file':
|
|
35
|
+
return new FileHandler(server, dbService, ws, message);
|
|
36
|
+
case 'tag':
|
|
37
|
+
return new TagHandler(server, dbService, ws, message);
|
|
38
|
+
case 'folder':
|
|
39
|
+
return new FolderHandler(server, dbService, ws, message);
|
|
40
|
+
case 'library':
|
|
41
|
+
return new LibraryHandler(server, dbService, ws, message);
|
|
42
|
+
default:
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { WebSocketServer as WSServer, WebSocket } from 'ws';
|
|
2
|
+
import { Server } from 'http';
|
|
3
|
+
import { LibraryServerDataSQLite } from './LibraryServerDataSQLite';
|
|
4
|
+
import { WebSocketRouter } from './WebSocketRouter';
|
|
5
|
+
import { ServerPluginManager } from './ServerPluginManager';
|
|
6
|
+
import { EventArgs } from './event-manager';
|
|
7
|
+
import { LibraryStorage } from './LibraryStorage';
|
|
8
|
+
import { MiraHttpServer } from './HttpServer';
|
|
9
|
+
import { MiraBackend } from './ServerExample';
|
|
10
|
+
|
|
11
|
+
interface LibraryClient {
|
|
12
|
+
[libraryId: string]: WebSocket[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export class MiraWebsocketServer {
|
|
17
|
+
private port: number;
|
|
18
|
+
private libraryClients: LibraryClient = {};
|
|
19
|
+
private wss?: WSServer;
|
|
20
|
+
libraries: LibraryStorage;
|
|
21
|
+
private httpServer: MiraHttpServer;
|
|
22
|
+
backend: MiraBackend;
|
|
23
|
+
|
|
24
|
+
constructor(port: number, backend: MiraBackend) {
|
|
25
|
+
this.port = port;
|
|
26
|
+
this.backend = backend;
|
|
27
|
+
this.httpServer = this.backend.httpServer;
|
|
28
|
+
this.libraries = this.backend.libraries;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async start(basePath: string): Promise<void> {
|
|
32
|
+
this.wss = new WSServer({ port: this.port });
|
|
33
|
+
this.wss.on('connection', (ws: WebSocket, request) => {
|
|
34
|
+
const urlString = request.url ?? '';
|
|
35
|
+
const url = new URL(urlString, `ws://${request.headers.host}`);
|
|
36
|
+
const clientId = url.searchParams.get('clientId');
|
|
37
|
+
const libraryId = url.searchParams.get('libraryId');
|
|
38
|
+
if (clientId == null || libraryId == null) {
|
|
39
|
+
console.error('Missing clientId or libraryId in WebSocket connection');
|
|
40
|
+
return ws.close();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 将请求信息保存到 ws 对象上
|
|
44
|
+
Object.assign(ws, {
|
|
45
|
+
clientId: clientId,
|
|
46
|
+
libraryId: libraryId,
|
|
47
|
+
requestInfo: {
|
|
48
|
+
url: request.url,
|
|
49
|
+
headers: request.headers,
|
|
50
|
+
remoteAddress: request.socket.remoteAddress
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// 保存连接
|
|
55
|
+
if (!this.libraryClients[libraryId]) {
|
|
56
|
+
this.libraryClients[libraryId] = [];
|
|
57
|
+
}
|
|
58
|
+
if (!this.libraryClients[libraryId].includes(ws)) {
|
|
59
|
+
this.libraryClients[libraryId].push(ws);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.handleConnection(ws);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
console.log(`[!]Serving at ws://localhost:${this.port}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
broadcastToClients(eventName: string, eventData: Record<string, any>): void {
|
|
69
|
+
const dbService = this.libraries.all().find(
|
|
70
|
+
(library) => library.getLibraryId() === eventData.libraryId
|
|
71
|
+
);
|
|
72
|
+
if (dbService) {
|
|
73
|
+
const eventManager = dbService.getEventManager();
|
|
74
|
+
eventManager!.broadcast(
|
|
75
|
+
eventName,
|
|
76
|
+
new EventArgs(eventName, eventData)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getWsClientById(libraryId: string, clientId: string): WebSocket | undefined {
|
|
82
|
+
const clients = this.libraryClients[libraryId];
|
|
83
|
+
if (clients) {
|
|
84
|
+
return clients.find((client) => (client as any).clientId === clientId);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
showDialogToWeboscket(ws: WebSocket, data: Record<string, any>): void {
|
|
89
|
+
this.sendToWebsocket(ws, {
|
|
90
|
+
eventName: 'dialog', data: Object.assign({
|
|
91
|
+
title: '提示',
|
|
92
|
+
message: '',
|
|
93
|
+
url: ''
|
|
94
|
+
}, data)
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
sendToWebsocket(ws: WebSocket, data: Record<string, any>): void {
|
|
99
|
+
console.log({ response: data });
|
|
100
|
+
ws.send(JSON.stringify(data));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
broadcastPluginEvent(eventName: string, data: Record<string, any>): Promise<boolean> {
|
|
104
|
+
const libraryId = data?.libraryId ?? data?.message?.libraryId;
|
|
105
|
+
const dbService = this.libraries.all().find(
|
|
106
|
+
(library) => library.getLibraryId() === libraryId
|
|
107
|
+
);
|
|
108
|
+
if (dbService) {
|
|
109
|
+
const eventManager = dbService.getEventManager();
|
|
110
|
+
return eventManager!.broadcast(
|
|
111
|
+
eventName,
|
|
112
|
+
new EventArgs(eventName, data)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return Promise.resolve(false);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private handleConnection(ws: WebSocket): void {
|
|
119
|
+
ws.on('message', async (message: string) => {
|
|
120
|
+
try {
|
|
121
|
+
const data = JSON.parse(message);
|
|
122
|
+
console.log('Incoming message:', data)
|
|
123
|
+
await this.handleMessage(ws, data);
|
|
124
|
+
} catch (e) {
|
|
125
|
+
this.sendToWebsocket(ws, {
|
|
126
|
+
error: 'Invalid message format',
|
|
127
|
+
details: e instanceof Error ? e.message : String(e)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
ws.on('close', () => {
|
|
133
|
+
// Remove from all library client lists
|
|
134
|
+
Object.keys(this.libraryClients).forEach(libraryId => {
|
|
135
|
+
const index = this.libraryClients[libraryId].findIndex(
|
|
136
|
+
client => client === ws
|
|
137
|
+
);
|
|
138
|
+
console.log({ index });
|
|
139
|
+
if (index !== -1) {
|
|
140
|
+
this.libraryClients[libraryId].splice(index, 1);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private async handleMessage(ws: WebSocket, row: Record<string, any>): Promise<void> {
|
|
147
|
+
const payload = row.payload || {};
|
|
148
|
+
const action = row.action;
|
|
149
|
+
const requestId = row.requestId;
|
|
150
|
+
const libraryId = row.libraryId;
|
|
151
|
+
const data = payload.data || {};
|
|
152
|
+
const recordType = payload.type;
|
|
153
|
+
const exists = this.libraries.exists(libraryId);
|
|
154
|
+
if (!exists) {
|
|
155
|
+
this.sendToWebsocket(ws, {
|
|
156
|
+
status: 'error',
|
|
157
|
+
msg: 'Library not found!'
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const dbService = this.libraries.all().find(
|
|
163
|
+
(library) => library.getLibraryId() === libraryId
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (!dbService) {
|
|
167
|
+
this.sendToWebsocket(ws, {
|
|
168
|
+
status: 'error',
|
|
169
|
+
msg: 'Library service not found'
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const handler = await WebSocketRouter.route(this, dbService, ws, {
|
|
175
|
+
...row,
|
|
176
|
+
...payload
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (handler) {
|
|
180
|
+
await handler.handle();
|
|
181
|
+
} else {
|
|
182
|
+
this.sendToWebsocket(ws, {
|
|
183
|
+
status: 'error',
|
|
184
|
+
message: `Unsupported action: ${action} and record type: ${recordType}`,
|
|
185
|
+
requestId
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
broadcastLibraryEvent(libraryId: string, eventName: string, data: Record<string, any>): void {
|
|
191
|
+
const message = JSON.stringify({ eventName: eventName, data: data });
|
|
192
|
+
if (this.libraryClients[libraryId]) {
|
|
193
|
+
this.libraryClients[libraryId].forEach(client => {
|
|
194
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
195
|
+
client.send(message);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async stop(): Promise<void> {
|
|
202
|
+
this.libraries.all().map(dbService => dbService.close());
|
|
203
|
+
this.wss?.close();
|
|
204
|
+
console.log('WebSocket server stopped');
|
|
205
|
+
}
|
|
206
|
+
}
|