rpc-shooter
Version:
A tool library for handling window && iframe && worker communication based on the JSON RPC specification
318 lines (317 loc) • 10.7 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
const RPCCodes = {
CONNECT_TIMEOUT: {
code: -32300,
message: "Connect timeout"
},
APPLICATION_ERROR: {
code: -32500,
message: "Application error"
},
METHOD_NOT_FOUND: {
code: -32601,
message: `Method not found`
}
};
class RPCMessageEvent {
constructor(options) {
__publicField(this, "_currentEndpoint");
__publicField(this, "_targetEndpoint");
__publicField(this, "_events");
__publicField(this, "_originOnmessage");
__publicField(this, "_receiveMessage");
__publicField(this, "onerror", null);
__publicField(this, "config");
__publicField(this, "sendAdapter");
__publicField(this, "receiveAdapter");
this._events = {};
this._currentEndpoint = options.currentEndpoint;
this._targetEndpoint = options.targetEndpoint;
this._originOnmessage = null;
this.config = options.config;
this.receiveAdapter = options.receiveAdapter;
this.sendAdapter = options.sendAdapter;
const receiveMessage = (event) => {
const receiveData = this.receiveAdapter ? this.receiveAdapter(event) : event.data;
if (receiveData && typeof receiveData.event === "string") {
const eventHandlers = this._events[receiveData.event] || [];
if (eventHandlers.length) {
eventHandlers.forEach((handler) => {
handler(...receiveData.args || []);
});
return;
}
if (this.onerror) {
this.onerror(__spreadProps(__spreadValues({}, RPCCodes.METHOD_NOT_FOUND), {
data: receiveData
}));
}
}
};
if (this._currentEndpoint.addEventListener) {
if ("start" in this._currentEndpoint && this._currentEndpoint.start) {
this._currentEndpoint.start();
}
this._currentEndpoint.addEventListener("message", receiveMessage, false);
this._receiveMessage = receiveMessage;
return;
}
this._originOnmessage = this._currentEndpoint.onmessage;
this._currentEndpoint.onmessage = (event) => {
if (this._originOnmessage) {
this._originOnmessage(event);
}
receiveMessage(event);
};
this._receiveMessage = this._currentEndpoint.onmessage;
}
emit(event, ...args) {
const data = {
event,
args
};
const result = this.sendAdapter ? this.sendAdapter(data, this._targetEndpoint) : { data };
const sendData = result.data || data;
const postMessageConfig = this.config ? typeof this.config === "function" ? this.config(sendData, this._targetEndpoint) || {} : this.config || {} : {};
if (Array.isArray(result.transfer) && result.transfer.length) {
postMessageConfig.transfer = result.transfer;
}
this._targetEndpoint.postMessage(sendData, postMessageConfig);
}
on(event, fn) {
if (!this._events[event]) {
this._events[event] = [];
}
this._events[event].push(fn);
}
off(event, fn) {
if (!this._events[event])
return;
if (!fn) {
this._events[event] = [];
return;
}
const handlers = this._events[event] || [];
this._events[event] = handlers.filter((handler) => handler !== fn);
}
destroy() {
if (this._currentEndpoint.removeEventListener) {
this._currentEndpoint.removeEventListener("message", this._receiveMessage, false);
return;
}
try {
this._currentEndpoint.onmessage = this._originOnmessage;
} catch (error) {
console.warn(error);
}
}
}
const _RPC = class {
constructor(options) {
__publicField(this, "_event");
__publicField(this, "_methods", {});
__publicField(this, "_timeout", 0);
__publicField(this, "_$connect", null);
this._event = options.event;
this._timeout = options.timeout || 0;
if (options.methods) {
Object.entries(options.methods).forEach(([method, handler]) => {
this.registerMethod(method, handler);
});
}
this._event.onerror = (error) => {
const { code, message, data } = error;
if (data.event && Array.isArray(data.args) && data.args.length) {
const synEventData = data.args[0];
const ackEventName = this._getAckEventName(synEventData.method);
const ackEventData = {
jsonrpc: "2.0",
id: synEventData == null ? void 0 : synEventData.id,
error: {
code,
message,
data: synEventData
}
};
this._event.emit(ackEventName, ackEventData);
} else {
console.error(error);
}
};
this.connect();
}
static uuid() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
const r = Math.random() * 16 | 0;
const v = c == "x" ? r : r & 3 | 8;
return v.toString(16);
});
}
_getSynEventName(method) {
return `${_RPC.EVENT.SYN_SIGN}${method}`;
}
_getAckEventName(method) {
return `${_RPC.EVENT.ACK_SIGN}${method}`;
}
connect(timeout) {
if (this._$connect) {
return this._$connect;
}
this._$connect = new Promise((resolve, reject) => {
const connectTimeout = timeout || this._timeout;
let connectTimer;
if (connectTimeout) {
connectTimer = setTimeout(() => {
const error = __spreadProps(__spreadValues({}, RPCCodes.TIMEOUT), {
data: { timeout: connectTimeout }
});
reject(error);
}, connectTimeout);
}
const connectEventName = _RPC.EVENT.CONNECT;
const connectAckEventName = this._getAckEventName(connectEventName);
const connectSynEventName = this._getSynEventName(connectEventName);
const resolveConnectEvent = () => {
clearTimeout(connectTimer);
resolve();
};
this._event.on(connectAckEventName, resolveConnectEvent);
const connectSynEventHandler = () => {
this._event.emit(connectAckEventName);
resolveConnectEvent();
};
this._event.on(connectSynEventName, connectSynEventHandler);
this._event.emit(connectSynEventName);
});
return this._$connect;
}
registerMethod(method, handler) {
if (this._methods[method]) {
throw new Error(`${method} already registered`);
}
this._methods[method] = handler;
const synEventName = this._getSynEventName(method);
const synEventHandler = (synEventData) => {
const ackEventName = this._getAckEventName(method);
if (!synEventData.id) {
handler(...synEventData.params);
return;
}
Promise.resolve(handler(...synEventData.params)).then((result) => {
const ackEventData = {
jsonrpc: "2.0",
result,
id: synEventData.id
};
this._event.emit(ackEventName, ackEventData);
}).catch((error) => {
const ackEventData = {
jsonrpc: "2.0",
id: synEventData.id,
error: {
code: (error == null ? void 0 : error.code) || RPCCodes.APPLICATION_ERROR.code,
message: (error == null ? void 0 : error.message) || RPCCodes.APPLICATION_ERROR.message,
data: null
}
};
this._event.emit(ackEventName, ackEventData);
});
};
this._event.on(synEventName, synEventHandler);
}
removeMethod(method) {
if (!this._methods[method]) {
delete this._methods[method];
}
const synEventName = this._getSynEventName(method);
this._event.off(synEventName);
}
invoke(method, ...args) {
return new Promise((resolve, reject) => {
const lastArg = args[args.length - 1];
const hasInvokeOptions = lastArg && typeof lastArg === "object" && (Reflect.has(lastArg, "isNotify") || Reflect.has(lastArg, "timeout"));
const options = hasInvokeOptions ? lastArg : { isNotify: false, timeout: 0 };
const params = hasInvokeOptions ? args.slice(0, -1) : args;
const synEventName = this._getSynEventName(method);
const synEventId = _RPC.uuid();
const synEventData = {
jsonrpc: "2.0",
method,
params,
id: synEventId
};
this._event.emit(synEventName, synEventData);
if (!options.isNotify) {
const ackEventName = this._getAckEventName(method);
const timeout = options.timeout || this._timeout;
let timer;
if (timeout) {
timer = setTimeout(() => {
const error = __spreadProps(__spreadValues({}, RPCCodes.CONNECT_TIMEOUT), {
data: { timeout }
});
reject(error);
}, timeout);
}
const ackEventHandler = (ackEventData) => {
if (ackEventData.id === synEventId) {
clearTimeout(timer);
this._event.off(ackEventName, ackEventHandler);
if (!ackEventData.error) {
resolve(ackEventData.result);
} else {
reject(ackEventData.error);
}
}
};
this._event.on(ackEventName, ackEventHandler);
} else {
resolve(void 0);
}
});
}
destroy() {
Object.entries(this._methods).forEach(([method]) => {
const synEventName = this._getSynEventName(method);
this._event.off(synEventName);
});
const connectAckEventName = this._getAckEventName(_RPC.EVENT.CONNECT);
const connectSynEventName = this._getSynEventName(_RPC.EVENT.CONNECT);
this._event.off(connectSynEventName);
this._event.off(connectAckEventName);
if (this._event.destroy) {
this._event.destroy();
}
}
};
let RPC = _RPC;
__publicField(RPC, "CODES", RPCCodes);
__publicField(RPC, "EVENT", {
SYN_SIGN: "syn:",
ACK_SIGN: "ack:",
CONNECT: "__rpc_connect_event",
SYNC_METHODS: "__rpc_sync_methods_event"
});
export { RPC, RPCCodes, RPCMessageEvent };