26 包 monorepo(TS 全源码): - snowcat-core: NT 内核 API 封装 + listener(含 9.9.37 六回调适配)+ Packet 发包 - snowcat-onebot: OneBot v11 全动作 + MCP Streamable HTTP 适配器 + 稳定线 - snowcat-webui-frontend/backend: 雪猫控制台(React+HeroUI) - snowcat-shell: 单文件 bundle 出口(vite inlineDynamicImports) - snowcat-native: napi2native / MoeHoo / ffmpeg / pty 运行时 - 56 项 SnowLuma 独门动作移植(qzone 全家桶/群相册/装扮/闪照/收藏) - 工具件: 解包器/启动器/便携制作器/doctor GitHub 关联已全量指向 DandanLLab/SnowCatQQ
578 lines
15 KiB
TypeScript
578 lines
15 KiB
TypeScript
import {
|
|
type RpcRequest,
|
|
type RpcResponse,
|
|
type RpcServerOptions,
|
|
type SerializedValue,
|
|
RpcOperationType,
|
|
SerializedValueType,
|
|
} from './types.js';
|
|
import { serialize, deserialize, SimpleCallbackRegistry } from './serializer.js';
|
|
|
|
/**
|
|
* 生成唯一引用 ID
|
|
*/
|
|
function generateRefId (): string {
|
|
return `ref_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
}
|
|
|
|
/**
|
|
* 默认的代理判断函数
|
|
* 判断返回值是否应该保持代理引用(而非完全序列化)
|
|
* 策略:class 实例和有方法的对象保持代理,普通对象直接序列化
|
|
*/
|
|
function defaultShouldProxyResult (value: unknown): boolean {
|
|
if (value === null || value === undefined) {
|
|
return false;
|
|
}
|
|
if (typeof value !== 'object' && typeof value !== 'function') {
|
|
return false;
|
|
}
|
|
// 函数保持代理
|
|
if (typeof value === 'function') {
|
|
return true;
|
|
}
|
|
// 可安全序列化的内置类型不代理
|
|
if (value instanceof Date || value instanceof RegExp || value instanceof Error) {
|
|
return false;
|
|
}
|
|
if (value instanceof Map || value instanceof Set) {
|
|
return false;
|
|
}
|
|
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
|
|
return false;
|
|
}
|
|
// 数组不代理
|
|
if (Array.isArray(value)) {
|
|
return false;
|
|
}
|
|
// 检查对象原型是否为 Object.prototype(普通对象)
|
|
const proto = Object.getPrototypeOf(value);
|
|
if (proto === Object.prototype || proto === null) {
|
|
// 普通对象检查是否有方法
|
|
const hasMethod = Object.values(value as object).some(v => typeof v === 'function');
|
|
return hasMethod;
|
|
}
|
|
// 非普通对象(class 实例)- 保持代理
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* RPC 服务端
|
|
*
|
|
* 处理来自客户端的 RPC 请求,在目标对象上执行操作
|
|
*/
|
|
export class RpcServer {
|
|
private target: unknown;
|
|
private callbackInvoker?: (callbackId: string, args: unknown[]) => Promise<unknown>;
|
|
private localCallbacks = new SimpleCallbackRegistry();
|
|
/** 对象引用存储 */
|
|
private objectRefs = new Map<string, unknown>();
|
|
/** 代理判断函数 */
|
|
private shouldProxyResult: (value: unknown) => boolean;
|
|
|
|
constructor (options: RpcServerOptions) {
|
|
this.target = options.target;
|
|
this.callbackInvoker = options.callbackInvoker;
|
|
this.shouldProxyResult = options.shouldProxyResult ?? defaultShouldProxyResult;
|
|
}
|
|
|
|
/**
|
|
* 处理 RPC 请求
|
|
*/
|
|
async handleRequest (request: RpcRequest): Promise<RpcResponse> {
|
|
try {
|
|
switch (request.type) {
|
|
case RpcOperationType.GET:
|
|
return this.handleGet(request);
|
|
|
|
case RpcOperationType.SET:
|
|
return this.handleSet(request);
|
|
|
|
case RpcOperationType.APPLY:
|
|
return await this.handleApply(request);
|
|
|
|
case RpcOperationType.CONSTRUCT:
|
|
return await this.handleConstruct(request);
|
|
|
|
case RpcOperationType.HAS:
|
|
return this.handleHas(request);
|
|
|
|
case RpcOperationType.OWNKEYS:
|
|
return this.handleOwnKeys(request);
|
|
|
|
case RpcOperationType.DELETE:
|
|
return this.handleDelete(request);
|
|
|
|
case RpcOperationType.GET_DESCRIPTOR:
|
|
return this.handleGetDescriptor(request);
|
|
|
|
case RpcOperationType.GET_PROTOTYPE:
|
|
return this.handleGetPrototype(request);
|
|
|
|
case RpcOperationType.RELEASE:
|
|
return this.handleRelease(request);
|
|
|
|
default:
|
|
return {
|
|
id: request.id,
|
|
success: false,
|
|
error: `Unknown operation type: ${request.type}`,
|
|
};
|
|
}
|
|
} catch (error) {
|
|
return this.createErrorResponse(request.id, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 解析路径获取目标值,支持 refId
|
|
*/
|
|
private resolvePath (path: PropertyKey[], refId?: string): { parent: unknown; key: PropertyKey | undefined; value: unknown; } {
|
|
// 如果有 refId,从引用存储中获取根对象
|
|
let current = refId ? this.objectRefs.get(refId) : this.target;
|
|
|
|
if (refId && current === undefined) {
|
|
throw new Error(`Object reference not found: ${refId}`);
|
|
}
|
|
|
|
let parent: unknown = null;
|
|
let key: PropertyKey | undefined;
|
|
|
|
for (let i = 0; i < path.length; i++) {
|
|
parent = current;
|
|
key = path[i];
|
|
if (key === undefined) {
|
|
throw new Error('Path contains undefined key');
|
|
}
|
|
if (current === null || current === undefined) {
|
|
throw new Error(`Cannot access property '${String(key)}' of ${current}`);
|
|
}
|
|
current = (current as Record<PropertyKey, unknown>)[key];
|
|
}
|
|
|
|
return { parent, key, value: current };
|
|
}
|
|
|
|
/**
|
|
* 存储对象引用并返回序列化的引用
|
|
* 同时序列化可序列化的属性值,避免属性访问需要额外 RPC
|
|
*/
|
|
private storeObjectRef (value: unknown): SerializedValue {
|
|
const refId = generateRefId();
|
|
this.objectRefs.set(refId, value);
|
|
const className = value?.constructor?.name;
|
|
|
|
// 序列化非函数属性
|
|
const cachedProps: Record<string, SerializedValue> = {};
|
|
if (value && typeof value === 'object') {
|
|
for (const key of Object.keys(value)) {
|
|
const propValue = (value as Record<string, unknown>)[key];
|
|
// 跳过函数(方法需要远程调用)
|
|
if (typeof propValue === 'function') {
|
|
continue;
|
|
}
|
|
// 序列化属性值
|
|
try {
|
|
cachedProps[key] = serialize(propValue, { callbackRegistry: this.localCallbacks });
|
|
} catch {
|
|
// 序列化失败的属性跳过,让客户端通过 RPC 获取
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
type: SerializedValueType.OBJECT_REF,
|
|
refId,
|
|
className: className !== 'Object' ? className : undefined,
|
|
cachedProps: Object.keys(cachedProps).length > 0 ? cachedProps : undefined,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 序列化结果值,如果需要代理则存储引用
|
|
*/
|
|
private serializeResult (value: unknown): { result: SerializedValue; isProxyable: boolean; refId?: string; } {
|
|
const shouldProxy = this.shouldProxyResult(value);
|
|
|
|
if (shouldProxy) {
|
|
const ref = this.storeObjectRef(value);
|
|
return {
|
|
result: ref,
|
|
isProxyable: true,
|
|
refId: ref.refId,
|
|
};
|
|
}
|
|
|
|
return {
|
|
result: serialize(value, { callbackRegistry: this.localCallbacks }),
|
|
isProxyable: false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 处理 GET 操作
|
|
*/
|
|
private handleGet (request: RpcRequest): RpcResponse {
|
|
const { value } = this.resolvePath(request.path, request.refId);
|
|
const { result, isProxyable, refId } = this.serializeResult(value);
|
|
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result,
|
|
isProxyable,
|
|
refId,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 处理 SET 操作
|
|
*/
|
|
private handleSet (request: RpcRequest): RpcResponse {
|
|
const path = request.path;
|
|
if (path.length === 0 && !request.refId) {
|
|
throw new Error('Cannot set root object');
|
|
}
|
|
|
|
const parentPath = path.slice(0, -1);
|
|
const key = path[path.length - 1]!;
|
|
const { value: parent } = this.resolvePath(parentPath, request.refId);
|
|
|
|
if (parent === null || parent === undefined) {
|
|
throw new Error(`Cannot set property '${String(key)}' of ${parent}`);
|
|
}
|
|
|
|
const newValue = request.args?.[0]
|
|
? deserialize(request.args[0], {
|
|
callbackResolver: this.createCallbackResolver(request),
|
|
refResolver: (refId) => this.objectRefs.get(refId),
|
|
})
|
|
: undefined;
|
|
|
|
(parent as Record<PropertyKey, unknown>)[key] = newValue;
|
|
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 处理 APPLY 操作
|
|
*/
|
|
private async handleApply (request: RpcRequest): Promise<RpcResponse> {
|
|
const path = request.path;
|
|
|
|
// 如果有 refId 且 path 为空,说明引用对象本身是函数
|
|
if (path.length === 0 && request.refId) {
|
|
const func = this.objectRefs.get(request.refId);
|
|
if (typeof func !== 'function') {
|
|
throw new Error('Referenced object is not callable');
|
|
}
|
|
|
|
const args = (request.args ?? []).map(arg =>
|
|
deserialize(arg, {
|
|
callbackResolver: this.createCallbackResolver(request),
|
|
refResolver: (refId) => this.objectRefs.get(refId),
|
|
})
|
|
);
|
|
|
|
let result = func(...args);
|
|
if (result instanceof Promise) {
|
|
result = await result;
|
|
}
|
|
|
|
const { result: serializedResult, isProxyable, refId } = this.serializeResult(result);
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result: serializedResult,
|
|
isProxyable,
|
|
refId,
|
|
};
|
|
}
|
|
|
|
if (path.length === 0) {
|
|
throw new Error('Cannot call root object');
|
|
}
|
|
|
|
const methodPath = path.slice(0, -1);
|
|
const methodName = path[path.length - 1]!;
|
|
const { value: parent } = this.resolvePath(methodPath, request.refId);
|
|
|
|
if (parent === null || parent === undefined) {
|
|
throw new Error(`Cannot call method on ${parent}`);
|
|
}
|
|
|
|
const method = (parent as Record<PropertyKey, unknown>)[methodName];
|
|
if (typeof method !== 'function') {
|
|
throw new Error(`${String(methodName)} is not a function`);
|
|
}
|
|
|
|
const args = (request.args ?? []).map(arg =>
|
|
deserialize(arg, {
|
|
callbackResolver: this.createCallbackResolver(request),
|
|
refResolver: (refId) => this.objectRefs.get(refId),
|
|
})
|
|
);
|
|
|
|
let result = method.call(parent, ...args);
|
|
|
|
// 处理 Promise
|
|
if (result instanceof Promise) {
|
|
result = await result;
|
|
}
|
|
|
|
const { result: serializedResult, isProxyable, refId } = this.serializeResult(result);
|
|
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result: serializedResult,
|
|
isProxyable,
|
|
refId,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 处理 CONSTRUCT 操作
|
|
*/
|
|
private async handleConstruct (request: RpcRequest): Promise<RpcResponse> {
|
|
const { value: Constructor } = this.resolvePath(request.path, request.refId);
|
|
|
|
if (typeof Constructor !== 'function') {
|
|
throw new Error('Target is not a constructor');
|
|
}
|
|
|
|
const args = (request.args ?? []).map(arg =>
|
|
deserialize(arg, {
|
|
callbackResolver: this.createCallbackResolver(request),
|
|
refResolver: (refId) => this.objectRefs.get(refId),
|
|
})
|
|
);
|
|
|
|
const instance = new (Constructor as new (...args: unknown[]) => unknown)(...args);
|
|
const { result, isProxyable, refId } = this.serializeResult(instance);
|
|
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result,
|
|
isProxyable,
|
|
refId,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 处理 HAS 操作
|
|
*/
|
|
private handleHas (request: RpcRequest): RpcResponse {
|
|
const path = request.path;
|
|
if (path.length === 0) {
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result: serialize(true),
|
|
};
|
|
}
|
|
|
|
const parentPath = path.slice(0, -1);
|
|
const key = path[path.length - 1]!;
|
|
const { value: parent } = this.resolvePath(parentPath, request.refId);
|
|
|
|
const has = parent !== null && parent !== undefined && key in (parent as object);
|
|
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result: serialize(has),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 处理 OWNKEYS 操作
|
|
*/
|
|
private handleOwnKeys (request: RpcRequest): RpcResponse {
|
|
const { value } = this.resolvePath(request.path, request.refId);
|
|
|
|
if (value === null || value === undefined) {
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result: serialize([]),
|
|
};
|
|
}
|
|
|
|
const keys = Reflect.ownKeys(value as object);
|
|
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result: serialize(keys.map(k => (typeof k === 'symbol' ? k.description ?? '' : String(k)))),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 处理 DELETE 操作
|
|
*/
|
|
private handleDelete (request: RpcRequest): RpcResponse {
|
|
const path = request.path;
|
|
if (path.length === 0 && !request.refId) {
|
|
throw new Error('Cannot delete root object');
|
|
}
|
|
|
|
const parentPath = path.slice(0, -1);
|
|
const key = path[path.length - 1]!;
|
|
const { value: parent } = this.resolvePath(parentPath, request.refId);
|
|
|
|
if (parent === null || parent === undefined) {
|
|
throw new Error(`Cannot delete property from ${parent}`);
|
|
}
|
|
|
|
const deleted = delete (parent as Record<PropertyKey, unknown>)[key];
|
|
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result: serialize(deleted),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 处理 GET_DESCRIPTOR 操作
|
|
*/
|
|
private handleGetDescriptor (request: RpcRequest): RpcResponse {
|
|
const path = request.path;
|
|
if (path.length === 0) {
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result: serialize(undefined),
|
|
};
|
|
}
|
|
|
|
const parentPath = path.slice(0, -1);
|
|
const key = path[path.length - 1]!;
|
|
const { value: parent } = this.resolvePath(parentPath, request.refId);
|
|
|
|
if (parent === null || parent === undefined) {
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result: serialize(undefined),
|
|
};
|
|
}
|
|
|
|
const descriptor = Object.getOwnPropertyDescriptor(parent as object, key);
|
|
|
|
if (!descriptor) {
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result: serialize(undefined),
|
|
};
|
|
}
|
|
|
|
// 序列化描述符(排除 value 和 get/set 函数)
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result: serialize({
|
|
configurable: descriptor.configurable,
|
|
enumerable: descriptor.enumerable,
|
|
writable: descriptor.writable,
|
|
}),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 处理 GET_PROTOTYPE 操作
|
|
*/
|
|
private handleGetPrototype (request: RpcRequest): RpcResponse {
|
|
const { value } = this.resolvePath(request.path, request.refId);
|
|
|
|
if (value === null || value === undefined) {
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result: serialize(null),
|
|
};
|
|
}
|
|
|
|
const proto = Object.getPrototypeOf(value);
|
|
const name = proto?.constructor?.name ?? 'Object';
|
|
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
result: serialize({ name }),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 处理 RELEASE 操作
|
|
*/
|
|
private handleRelease (request: RpcRequest): RpcResponse {
|
|
// 如果有 refId,释放该引用
|
|
if (request.refId) {
|
|
this.objectRefs.delete(request.refId);
|
|
}
|
|
return {
|
|
id: request.id,
|
|
success: true,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 创建回调解析器
|
|
*/
|
|
private createCallbackResolver (_request: RpcRequest): (id: string) => Function {
|
|
return (callbackId: string) => {
|
|
// 创建一个代理函数,调用时会通过 callbackInvoker 发送回客户端
|
|
return async (...args: unknown[]) => {
|
|
if (!this.callbackInvoker) {
|
|
throw new Error('Callback invoker not configured');
|
|
}
|
|
return this.callbackInvoker(callbackId, args);
|
|
};
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 创建错误响应
|
|
*/
|
|
private createErrorResponse (requestId: string, error: unknown): RpcResponse {
|
|
if (error instanceof Error) {
|
|
return {
|
|
id: requestId,
|
|
success: false,
|
|
error: error.message,
|
|
stack: error.stack,
|
|
};
|
|
}
|
|
return {
|
|
id: requestId,
|
|
success: false,
|
|
error: String(error),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 调用客户端回调
|
|
*/
|
|
async invokeCallback (callbackId: string, args: unknown[]): Promise<SerializedValue> {
|
|
if (!this.callbackInvoker) {
|
|
throw new Error('Callback invoker not configured');
|
|
}
|
|
const result = await this.callbackInvoker(callbackId, args);
|
|
return serialize(result, { callbackRegistry: this.localCallbacks });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 创建 RPC 服务端
|
|
*/
|
|
export function createRpcServer (options: RpcServerOptions): RpcServer {
|
|
return new RpcServer(options);
|
|
}
|