index.mjs 8.3 KB

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