@cloudpss/ubrpc
Version:
325 lines • 11.4 kB
JavaScript
import { connected } from '@cloudpss/fetch';
import { from, Observable } from 'rxjs';
import { send } from './utils/messaging.js';
import { decodePayload, deserializeError, serializeError } from './utils/serialize.js';
import { logger } from './logger.js';
import { ReadyPromise } from './utils/ready.js';
import { shouldReconnectWebSocket, WebSocketAppCode } from './codes.js';
/** RPC 连接 */
export class RpcSocket {
constructor(id) {
this.id = id;
this.ready = new ReadyPromise();
this.#handlers = Object.freeze({
open: (ev) => this.onOpen(ev),
close: (ev) => this.onClose(ev),
error: (ev) => this.onError(ev),
message: (ev) => this.onMessage(ev),
});
this.#localSubscription = new Map();
/** 序列号 */
this.seq = 0;
this.#pendingCalls = new Map();
this.#pendingSubscriptions = new Map();
this.#destroyed = false;
}
/** 本地认证信息 */
get localMetadata() {
return this._localMetadata;
}
/** 远程认证信息 */
get remoteMetadata() {
return this._remoteMetadata;
}
/** 连接是否已认证 */
get authenticated() {
return this._remoteMetadata != null;
}
#socket;
/** 作为底层传输的 WebSocket */
get socket() {
if (this.#destroyed)
throw new Error(`RPC Socket destroyed.`);
if (!this.#socket)
throw new Error(`Socket not initialized`);
return this.#socket;
}
set socket(value) {
if (this.#destroyed)
throw new Error(`RPC Socket destroyed.`);
if (this.#socket === value)
return;
this.resetReady();
const oldValue = this.#socket;
this.#socket = value;
this.ready.settle(this.initSocket(oldValue, value));
}
/** 重置 ready Promise */
resetReady() {
if (!this.ready.settled)
return;
this.ready = new ReadyPromise();
}
#handlers;
/** 初始化 WebSocket */
async initSocket(oldValue, newValue) {
try {
const { open, close, error, message } = this.#handlers;
if (oldValue) {
oldValue.removeEventListener('open', open);
oldValue.removeEventListener('close', close);
oldValue.removeEventListener('error', error);
oldValue.removeEventListener('message', message);
if (oldValue.readyState !== oldValue.CLOSED) {
logger('[%s] close old socket', this.id);
oldValue.close(WebSocketAppCode.REPLACED);
}
}
await connected(newValue);
const info = await this.authSocket();
if (this.#socket === newValue) {
newValue.addEventListener('open', open);
newValue.addEventListener('close', close);
newValue.addEventListener('error', error);
newValue.addEventListener('message', message);
this._remoteMetadata = info ?? {};
}
}
catch (ex) {
logger('[%s] connection initialize failed. error=%o', this.id, ex);
this._remoteMetadata = undefined;
throw ex;
}
}
/** 响应 WebSocket error */
onError(ev) {
logger('[%s] socket error: %o', this.id, ev);
this._remoteMetadata = undefined;
this.resetReady();
}
/** 响应 WebSocket open */
onOpen(_ev) {
//
}
/** 响应 WebSocket close */
onClose(ev) {
const { code } = ev;
logger('[%s] socket closed, code=%d', this.id, code);
this._remoteMetadata = undefined;
if (!shouldReconnectWebSocket(code)) {
this.destroy();
}
else {
this.resetReady();
}
}
/** 响应 WebSocket message */
onMessage(ev) {
const payload = decodePayload(ev.data);
let error;
if (payload) {
const handled = this.onPayload(payload);
if (!handled)
error = [payload.seq, `Unrecognized message, not handled.`];
}
else {
error = [this.nextSeq(), `Invalid message, unknown format.`];
}
if (error) {
void this.sendPayload('error', {
seq: error[0],
error: serializeError(new SyntaxError(error[1])),
});
}
}
/** 响应 Rpc 消息 */
onPayload(payload) {
switch (payload.type) {
case 'call':
case 'notify':
void this.localCall(payload);
return true;
case 'return': {
const pending = this.#pendingCalls.get(payload.seq);
// 即使不存在等待的请求,也认为响应是有效的
if (!pending)
return true;
this.#pendingCalls.delete(payload.seq);
if (payload.error) {
pending[1](deserializeError(payload.error));
}
else {
pending[0](payload.result);
}
return true;
}
case 'subscribe':
void this.localSubscribe(payload);
return true;
case 'unsubscribe': {
const subscription = this.#localSubscription.get(payload.seq);
// 即使不存在对应订阅,也认为响应是有效的
if (!subscription)
return true;
subscription.unsubscribe();
this.#localSubscription.delete(payload.seq);
return true;
}
case 'publish': {
const status = this.#pendingSubscriptions.get(payload.seq);
// 即使不存在对应订阅,也认为响应是有效的
if (!status) {
void this.sendPayload('unsubscribe', { seq: payload.seq });
return true;
}
if (payload.error) {
status[1] = true;
status[0].error(deserializeError(payload.error));
}
else if (payload.complete) {
status[1] = true;
status[0].complete();
}
else {
status[0].next(payload.next);
}
return true;
}
case 'error':
return true;
case 'auth':
default:
return false;
}
}
/** 调用本地方法 */
async localCall(payload) {
const noReturn = payload.type === 'notify';
const method = this.local ? this.local[payload.method] : undefined;
const { seq } = payload;
if (typeof method != 'function') {
if (noReturn)
return;
return this.sendPayload('return', {
seq,
error: serializeError(new TypeError(`${payload.method} is not a function`)),
});
}
try {
const result = await Reflect.apply(method, this.local, payload.args);
if (noReturn)
return;
return this.sendPayload('return', { seq, result });
}
catch (ex) {
if (noReturn)
return;
return this.sendPayload('return', { seq, error: serializeError(ex) });
}
}
#localSubscription;
/** 调用本地方法 */
async localSubscribe(payload) {
const method = this.local ? this.local[payload.method] : undefined;
const { seq } = payload;
if (typeof method != 'function') {
return this.sendPayload('publish', {
seq,
error: serializeError(new TypeError(`${payload.method} is not a function`)),
});
}
try {
const result = from((await Reflect.apply(method, this.local, payload.args)));
const subscription = result.subscribe({
next: (value) => {
void this.sendPayload('publish', { seq, next: value });
},
error: (err) => {
this.#localSubscription.delete(payload.seq);
void this.sendPayload('publish', { seq, error: serializeError(err) });
},
complete: () => {
this.#localSubscription.delete(payload.seq);
void this.sendPayload('publish', { seq, complete: true });
},
});
this.#localSubscription.set(seq, subscription);
}
catch (ex) {
return this.sendPayload('publish', { seq, error: serializeError(ex) });
}
}
/** 发送数据 */
async sendPayload(type, info) {
if (this.#destroyed)
throw new Error(`RPC Socket destroyed.`);
await this.ready.value;
send(this.socket, type, info);
}
/** 获取下一个序列号 */
nextSeq() {
const { seq } = this;
this.seq += 2;
return seq;
}
#pendingCalls;
/** 调用远程方法 */
// eslint-disable-next-line @typescript-eslint/promise-function-async
call(method, ...args) {
return new Promise((resolve, reject) => {
const seq = this.nextSeq();
void this.sendPayload('call', { seq, method, args });
this.#pendingCalls.set(seq, [resolve, reject]);
});
}
/** 调用远程方法,放弃返回值 */
notify(method, ...args) {
const seq = this.nextSeq();
void this.sendPayload('notify', { seq, method, args });
}
#pendingSubscriptions;
/** 调用远程订阅 */
subscribe(method, ...args) {
return new Observable((subscriber) => {
const seq = this.nextSeq();
const status = [subscriber, false];
void this.sendPayload('subscribe', { seq, method, args });
this.#pendingSubscriptions.set(seq, status);
return () => {
this.#pendingSubscriptions.delete(seq);
if (!status[1])
void this.sendPayload('unsubscribe', { seq });
};
});
}
#destroyed;
/** 是否已结束 */
get destroyed() {
return this.#destroyed;
}
/** 结束 */
destroy() {
if (this.#destroyed)
return;
logger('[%s] socket destroyed', this.id);
if (this.#socket) {
this.#socket.close(1000);
this.#socket = undefined;
}
for (const s of this.#localSubscription.values()) {
s.unsubscribe();
}
this.#localSubscription.clear();
for (const [, reject] of this.#pendingCalls.values()) {
reject(new Error(`RPC Socket closed.`));
}
this.#pendingCalls.clear();
for (const s of this.#pendingSubscriptions.values()) {
s[1] = true;
s[0].error(new Error(`RPC Socket closed.`));
}
this.#pendingSubscriptions.clear();
this.#destroyed = true;
}
}
//# sourceMappingURL=socket.js.map