index.cjs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. 'use strict';
  2. const TYPE_REQUEST = "q";
  3. const TYPE_RESPONSE = "s";
  4. const DEFAULT_TIMEOUT = 6e4;
  5. function defaultSerialize(i) {
  6. return i;
  7. }
  8. const defaultDeserialize = defaultSerialize;
  9. const { clearTimeout, setTimeout } = globalThis;
  10. const random = Math.random.bind(Math);
  11. function createBirpc($functions, options) {
  12. const {
  13. post,
  14. on,
  15. off = () => {
  16. },
  17. eventNames = [],
  18. serialize = defaultSerialize,
  19. deserialize = defaultDeserialize,
  20. resolver,
  21. bind = "rpc",
  22. timeout = DEFAULT_TIMEOUT
  23. } = options;
  24. let $closed = false;
  25. const _rpcPromiseMap = /* @__PURE__ */ new Map();
  26. let _promiseInit;
  27. let rpc;
  28. async function _call(method, args, event, optional) {
  29. if ($closed)
  30. throw new Error(`[birpc] rpc is closed, cannot call "${method}"`);
  31. const req = { m: method, a: args, t: TYPE_REQUEST };
  32. if (optional)
  33. req.o = true;
  34. const send = async (_req) => post(serialize(_req));
  35. if (event) {
  36. await send(req);
  37. return;
  38. }
  39. if (_promiseInit) {
  40. try {
  41. await _promiseInit;
  42. } finally {
  43. _promiseInit = void 0;
  44. }
  45. }
  46. let { promise, resolve, reject } = createPromiseWithResolvers();
  47. const id = nanoid();
  48. req.i = id;
  49. let timeoutId;
  50. async function handler(newReq = req) {
  51. if (timeout >= 0) {
  52. timeoutId = setTimeout(() => {
  53. try {
  54. const handleResult = options.onTimeoutError?.call(rpc, method, args);
  55. if (handleResult !== true)
  56. throw new Error(`[birpc] timeout on calling "${method}"`);
  57. } catch (e) {
  58. reject(e);
  59. }
  60. _rpcPromiseMap.delete(id);
  61. }, timeout);
  62. if (typeof timeoutId === "object")
  63. timeoutId = timeoutId.unref?.();
  64. }
  65. _rpcPromiseMap.set(id, { resolve, reject, timeoutId, method });
  66. await send(newReq);
  67. return promise;
  68. }
  69. try {
  70. if (options.onRequest)
  71. await options.onRequest.call(rpc, req, handler, resolve);
  72. else
  73. await handler();
  74. } catch (e) {
  75. if (options.onGeneralError?.call(rpc, e) !== true)
  76. throw e;
  77. return;
  78. } finally {
  79. clearTimeout(timeoutId);
  80. _rpcPromiseMap.delete(id);
  81. }
  82. return promise;
  83. }
  84. const $call = (method, ...args) => _call(method, args, false);
  85. const $callOptional = (method, ...args) => _call(method, args, false, true);
  86. const $callEvent = (method, ...args) => _call(method, args, true);
  87. const $callRaw = (options2) => _call(options2.method, options2.args, options2.event, options2.optional);
  88. const builtinMethods = {
  89. $call,
  90. $callOptional,
  91. $callEvent,
  92. $callRaw,
  93. $rejectPendingCalls,
  94. get $closed() {
  95. return $closed;
  96. },
  97. get $meta() {
  98. return options.meta;
  99. },
  100. $close,
  101. $functions
  102. };
  103. rpc = new Proxy({}, {
  104. get(_, method) {
  105. if (Object.prototype.hasOwnProperty.call(builtinMethods, method))
  106. return builtinMethods[method];
  107. if (method === "then" && !eventNames.includes("then") && !("then" in $functions))
  108. return void 0;
  109. const sendEvent = (...args) => _call(method, args, true);
  110. if (eventNames.includes(method)) {
  111. sendEvent.asEvent = sendEvent;
  112. return sendEvent;
  113. }
  114. const sendCall = (...args) => _call(method, args, false);
  115. sendCall.asEvent = sendEvent;
  116. return sendCall;
  117. }
  118. });
  119. function $close(customError) {
  120. $closed = true;
  121. _rpcPromiseMap.forEach(({ reject, method }) => {
  122. const error = new Error(`[birpc] rpc is closed, cannot call "${method}"`);
  123. if (customError) {
  124. customError.cause ??= error;
  125. return reject(customError);
  126. }
  127. reject(error);
  128. });
  129. _rpcPromiseMap.clear();
  130. off(onMessage);
  131. }
  132. function $rejectPendingCalls(handler) {
  133. const entries = Array.from(_rpcPromiseMap.values());
  134. const handlerResults = entries.map(({ method, reject }) => {
  135. if (!handler) {
  136. return reject(new Error(`[birpc]: rejected pending call "${method}".`));
  137. }
  138. return handler({ method, reject });
  139. });
  140. _rpcPromiseMap.clear();
  141. return handlerResults;
  142. }
  143. async function onMessage(data, ...extra) {
  144. let msg;
  145. try {
  146. msg = deserialize(data);
  147. } catch (e) {
  148. if (options.onGeneralError?.call(rpc, e) !== true)
  149. throw e;
  150. return;
  151. }
  152. if (msg.t === TYPE_REQUEST) {
  153. const { m: method, a: args, o: optional } = msg;
  154. let result, error;
  155. let fn = await (resolver ? resolver.call(rpc, method, $functions[method]) : $functions[method]);
  156. if (optional)
  157. fn ||= () => void 0;
  158. if (!fn) {
  159. error = new Error(`[birpc] function "${method}" not found`);
  160. } else {
  161. try {
  162. result = await fn.apply(bind === "rpc" ? rpc : $functions, args);
  163. } catch (e) {
  164. error = e;
  165. }
  166. }
  167. if (msg.i) {
  168. if (error && options.onError)
  169. options.onError.call(rpc, error, method, args);
  170. if (error && options.onFunctionError) {
  171. if (options.onFunctionError.call(rpc, error, method, args) === true)
  172. return;
  173. }
  174. if (!error) {
  175. try {
  176. await post(serialize({ t: TYPE_RESPONSE, i: msg.i, r: result }), ...extra);
  177. return;
  178. } catch (e) {
  179. error = e;
  180. if (options.onGeneralError?.call(rpc, e, method, args) !== true)
  181. throw e;
  182. }
  183. }
  184. try {
  185. await post(serialize({ t: TYPE_RESPONSE, i: msg.i, e: error }), ...extra);
  186. } catch (e) {
  187. if (options.onGeneralError?.call(rpc, e, method, args) !== true)
  188. throw e;
  189. }
  190. }
  191. } else {
  192. const { i: ack, r: result, e: error } = msg;
  193. const promise = _rpcPromiseMap.get(ack);
  194. if (promise) {
  195. clearTimeout(promise.timeoutId);
  196. if (error)
  197. promise.reject(error);
  198. else
  199. promise.resolve(result);
  200. }
  201. _rpcPromiseMap.delete(ack);
  202. }
  203. }
  204. _promiseInit = on(onMessage);
  205. return rpc;
  206. }
  207. const cacheMap = /* @__PURE__ */ new WeakMap();
  208. function cachedMap(items, fn) {
  209. return items.map((i) => {
  210. let r = cacheMap.get(i);
  211. if (!r) {
  212. r = fn(i);
  213. cacheMap.set(i, r);
  214. }
  215. return r;
  216. });
  217. }
  218. function createBirpcGroup(functions, channels, options = {}) {
  219. const getChannels = () => typeof channels === "function" ? channels() : channels;
  220. const getClients = (channels2 = getChannels()) => cachedMap(channels2, (s) => createBirpc(functions, { ...options, ...s }));
  221. function _boardcast(method, args, event, optional) {
  222. const clients = getClients();
  223. return Promise.all(clients.map((c) => c.$callRaw({ method, args, event, optional })));
  224. }
  225. function $call(method, ...args) {
  226. return _boardcast(method, args, false);
  227. }
  228. function $callOptional(method, ...args) {
  229. return _boardcast(method, args, false, true);
  230. }
  231. function $callEvent(method, ...args) {
  232. return _boardcast(method, args, true);
  233. }
  234. const broadcastBuiltin = {
  235. $call,
  236. $callOptional,
  237. $callEvent
  238. };
  239. const broadcastProxy = new Proxy({}, {
  240. get(_, method) {
  241. if (Object.prototype.hasOwnProperty.call(broadcastBuiltin, method))
  242. return broadcastBuiltin[method];
  243. const client = getClients();
  244. const callbacks = client.map((c) => c[method]);
  245. const sendCall = (...args) => {
  246. return Promise.all(callbacks.map((i) => i(...args)));
  247. };
  248. sendCall.asEvent = async (...args) => {
  249. await Promise.all(callbacks.map((i) => i.asEvent(...args)));
  250. };
  251. return sendCall;
  252. }
  253. });
  254. function updateChannels(fn) {
  255. const channels2 = getChannels();
  256. fn?.(channels2);
  257. return getClients(channels2);
  258. }
  259. getClients();
  260. return {
  261. get clients() {
  262. return getClients();
  263. },
  264. functions,
  265. updateChannels,
  266. broadcast: broadcastProxy,
  267. /**
  268. * @deprecated use `broadcast`
  269. */
  270. // @ts-expect-error deprecated
  271. boardcast: broadcastProxy
  272. };
  273. }
  274. function createPromiseWithResolvers() {
  275. let resolve;
  276. let reject;
  277. const promise = new Promise((res, rej) => {
  278. resolve = res;
  279. reject = rej;
  280. });
  281. return { promise, resolve, reject };
  282. }
  283. const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
  284. function nanoid(size = 21) {
  285. let id = "";
  286. let i = size;
  287. while (i--)
  288. id += urlAlphabet[random() * 64 | 0];
  289. return id;
  290. }
  291. exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
  292. exports.cachedMap = cachedMap;
  293. exports.createBirpc = createBirpc;
  294. exports.createBirpcGroup = createBirpcGroup;