This repository was archived by the owner on Feb 14, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 283
Expand file tree
/
Copy pathRpcHandlerManager.ts
More file actions
152 lines (133 loc) · 5.25 KB
/
RpcHandlerManager.ts
File metadata and controls
152 lines (133 loc) · 5.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
* Generic RPC handler manager for session and machine clients
* Manages RPC method registration, encryption/decryption, and handler execution
*/
import { logger as defaultLogger } from '@/ui/logger';
import { decodeBase64, encodeBase64, encrypt, decrypt } from '@/api/encryption';
import {
RpcHandler,
RpcHandlerMap,
RpcRequest,
RpcHandlerConfig,
} from './types';
import { Socket } from 'socket.io-client';
export class RpcHandlerManager {
private handlers: RpcHandlerMap = new Map();
private readonly scopePrefix: string;
private readonly encryptionKey: Uint8Array;
private readonly encryptionVariant: 'legacy' | 'dataKey';
private readonly logger: (message: string, data?: any) => void;
private socket: Socket | null = null;
constructor(config: RpcHandlerConfig) {
this.scopePrefix = config.scopePrefix;
this.encryptionKey = config.encryptionKey;
this.encryptionVariant = config.encryptionVariant;
this.logger = config.logger || ((msg, data) => defaultLogger.debug(msg, data));
}
/**
* Register an RPC handler for a specific method
* @param method - The method name (without prefix)
* @param handler - The handler function
*/
registerHandler<TRequest = any, TResponse = any>(
method: string,
handler: RpcHandler<TRequest, TResponse>
): void {
const prefixedMethod = this.getPrefixedMethod(method);
// Store the handler
this.handlers.set(prefixedMethod, handler);
if (this.socket) {
this.socket.emit('rpc-register', { method: prefixedMethod });
}
}
/**
* Handle an incoming RPC request
* @param request - The RPC request data
* @param callback - The response callback
*/
async handleRequest(
request: RpcRequest,
): Promise<any> {
try {
let handler = this.handlers.get(request.method);
// Compatibility: some clients may send unscoped methods (e.g. "permission")
// instead of the usual scoped `${scopePrefix}:${method}` form.
if (!handler && !request.method.includes(':')) {
const prefixedMethod = this.getPrefixedMethod(request.method);
handler = this.handlers.get(prefixedMethod);
if (handler) {
this.logger('[RPC] Matched unscoped method to scoped handler', {
method: request.method,
prefixedMethod,
});
request = { ...request, method: prefixedMethod };
}
}
if (!handler) {
this.logger('[RPC] [ERROR] Method not found', { method: request.method });
const errorResponse = { error: 'Method not found' };
const encryptedError = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
return encryptedError;
}
// Decrypt the incoming params
const decryptedParams = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(request.params));
// Call the handler
this.logger('[RPC] Calling handler', { method: request.method });
const result = await handler(decryptedParams);
this.logger('[RPC] Handler returned', { method: request.method, hasResult: result !== undefined });
// Encrypt and return the response
const encryptedResponse = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, result));
this.logger('[RPC] Sending encrypted response', { method: request.method, responseLength: encryptedResponse.length });
return encryptedResponse;
} catch (error) {
this.logger('[RPC] [ERROR] Error handling request', { error });
const errorResponse = {
error: error instanceof Error ? error.message : 'Unknown error'
};
return encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
}
}
onSocketConnect(socket: Socket): void {
this.socket = socket;
for (const [prefixedMethod] of this.handlers) {
socket.emit('rpc-register', { method: prefixedMethod });
}
}
onSocketDisconnect(): void {
this.socket = null;
}
/**
* Get the number of registered handlers
*/
getHandlerCount(): number {
return this.handlers.size;
}
/**
* Check if a handler is registered
* @param method - The method name (without prefix)
*/
hasHandler(method: string): boolean {
const prefixedMethod = this.getPrefixedMethod(method);
return this.handlers.has(prefixedMethod);
}
/**
* Clear all handlers
*/
clearHandlers(): void {
this.handlers.clear();
this.logger('Cleared all RPC handlers');
}
/**
* Get the prefixed method name
* @param method - The method name
*/
private getPrefixedMethod(method: string): string {
return `${this.scopePrefix}:${method}`;
}
}
/**
* Factory function to create an RPC handler manager
*/
export function createRpcHandlerManager(config: RpcHandlerConfig): RpcHandlerManager {
return new RpcHandlerManager(config);
}