UNPKG

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
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 };