@cloudpss/ubrpc
Version:
Rpc server/client build on websocket and ubjson.
148 lines • 4.89 kB
JavaScript
import { waitAuth } from './auth.js';
import { WebSocketAppCode } from './codes.js';
import { logger } from './logger.js';
import { RpcSocket } from './socket.js';
import { send } from './utils/messaging.js';
import { serializeError } from './utils/serialize.js';
import { VERSION } from './version.js';
const kOnClose = Symbol('kOnClose');
const kMetadata = Symbol('kMetadata');
const kReplaceSocket = Symbol('kReplaceSocket');
let tempIdGen = 0;
/** 由 WS Server 建立的 RPC 连接组 */
export class RpcServer {
authenticator;
constructor(
/** 服务端 RPC 实现 */
local,
/** 客户端认证方法 */
authenticator = () => ({})) {
this.authenticator = authenticator;
if (local == null) {
this.local = undefined;
}
else if (typeof local == 'function') {
this.local = local;
}
else {
this.local = () => local;
}
}
/** 用于响应调用的本地对象 */
local;
/** 已建立连接的客户端 */
_sockets = new Map();
/** 已建立连接的客户端 */
get sockets() {
return this._sockets;
}
/** WebSocket 连接后调用此方法建立 RPC 连接 */
async connect(socket) {
const tempId = `#${++tempIdGen}`;
logger('[%s] incoming connection', tempId);
socket.binaryType = 'arraybuffer';
const s = socket;
const [id, metadata] = await this.authSocket(socket, tempId);
s[kMetadata] = metadata;
let client = this._sockets.get(id);
if (!client) {
logger('[%s] new connection created', id);
client = new RpcServerSocket(id, this);
this._sockets.set(id, client);
}
else {
logger('[%s] connection resumed', id);
const tid = this.disconnectingSockets.get(id);
if (tid != null) {
this.disconnectingSockets.delete(id);
clearTimeout(tid);
}
}
await client[kReplaceSocket](s);
return client;
}
/** 认证客户端 */
async authSocket(socket, tempId) {
let seq, id, remoteMetadata;
try {
logger('[%s] authenticating...', tempId);
[seq, id, remoteMetadata] = await waitAuth(socket);
logger('[%s -> %s] got server auth. remoteMeta=%o', tempId, id, remoteMetadata);
const localMetadata = await this.authenticator(remoteMetadata);
logger('[%s] server auth success. localMeta=%o', id, localMetadata);
send(socket, 'auth', {
seq,
id,
version: VERSION,
metadata: localMetadata,
});
return [id, remoteMetadata];
}
catch (ex) {
logger('[%s] server auth failed. remoteMeta=%o, error=%o', id ?? tempId, remoteMetadata, ex);
if (seq != null && id != null) {
send(socket, 'auth', {
seq,
id,
version: VERSION,
metadata: {},
error: serializeError(ex),
});
}
else {
send(socket, 'error', {
seq: 1,
error: serializeError(ex),
});
}
socket.close(WebSocketAppCode.AUTH_ERROR);
throw ex;
}
}
disconnectingSockets = new Map();
/** WebSocket 断开时调用 */
[kOnClose](socket) {
const delay = socket.destroyed ? 0 : 5000;
logger('[%s] socket closed, drop connection in %dms', socket.id, delay);
const oldTid = this.disconnectingSockets.get(socket.id);
if (oldTid != null) {
clearTimeout(oldTid);
}
const tid = setTimeout(() => {
logger('[%s] connection dropped', socket.id);
socket.destroy();
this._sockets.delete(socket.id);
this.disconnectingSockets.delete(socket.id);
}, delay);
this.disconnectingSockets.set(socket.id, tid);
}
}
/** 由 WS Server 建立的 RPC 连接 */
export class RpcServerSocket extends RpcSocket {
server;
constructor(id, server) {
super(id);
this.server = server;
this.seq = 1;
}
/** @inheritdoc */
// eslint-disable-next-line @typescript-eslint/promise-function-async
authSocket() {
return Promise.resolve(this.socket[kMetadata]);
}
/** @inheritdoc */
get local() {
return this.server.local?.(this);
}
/** @inheritdoc */
onClose(ev) {
super.onClose(ev);
this.server[kOnClose](this);
}
/** 替换 socket */
async [kReplaceSocket](newSocket) {
this.socket = newSocket;
return this.ready;
}
}
//# sourceMappingURL=server.js.map