index.mjs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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. const rpcPromiseMap = /* @__PURE__ */ new Map();
  24. let _promise;
  25. let closed = false;
  26. const rpc = new Proxy({}, {
  27. get(_, method) {
  28. if (method === "$functions")
  29. return functions;
  30. if (method === "$close")
  31. return close;
  32. if (method === "$closed")
  33. return closed;
  34. if (method === "then" && !eventNames.includes("then") && !("then" in functions))
  35. return undefined;
  36. const sendEvent = (...args) => {
  37. post(serialize({ m: method, a: args, t: TYPE_REQUEST }));
  38. };
  39. if (eventNames.includes(method)) {
  40. sendEvent.asEvent = sendEvent;
  41. return sendEvent;
  42. }
  43. const sendCall = async (...args) => {
  44. if (closed)
  45. throw new Error(`[birpc] rpc is closed, cannot call "${method}"`);
  46. if (_promise) {
  47. try {
  48. await _promise;
  49. } finally {
  50. _promise = undefined;
  51. }
  52. }
  53. return new Promise((resolve, reject) => {
  54. const id = nanoid();
  55. let timeoutId;
  56. if (timeout >= 0) {
  57. timeoutId = setTimeout(() => {
  58. try {
  59. const handleResult = options.onTimeoutError?.(method, args);
  60. if (handleResult !== true)
  61. throw new Error(`[birpc] timeout on calling "${method}"`);
  62. } catch (e) {
  63. reject(e);
  64. }
  65. rpcPromiseMap.delete(id);
  66. }, timeout);
  67. if (typeof timeoutId === "object")
  68. timeoutId = timeoutId.unref?.();
  69. }
  70. rpcPromiseMap.set(id, { resolve, reject, timeoutId, method });
  71. post(serialize({ m: method, a: args, i: id, t: "q" }));
  72. });
  73. };
  74. sendCall.asEvent = sendEvent;
  75. return sendCall;
  76. }
  77. });
  78. function close(error) {
  79. closed = true;
  80. rpcPromiseMap.forEach(({ reject, method }) => {
  81. reject(error || new Error(`[birpc] rpc is closed, cannot call "${method}"`));
  82. });
  83. rpcPromiseMap.clear();
  84. off(onMessage);
  85. }
  86. async function onMessage(data, ...extra) {
  87. let msg;
  88. try {
  89. msg = deserialize(data);
  90. } catch (e) {
  91. if (options.onGeneralError?.(e) !== true)
  92. throw e;
  93. return;
  94. }
  95. if (msg.t === TYPE_REQUEST) {
  96. const { m: method, a: args } = msg;
  97. let result, error;
  98. const fn = resolver ? resolver(method, functions[method]) : functions[method];
  99. if (!fn) {
  100. error = new Error(`[birpc] function "${method}" not found`);
  101. } else {
  102. try {
  103. result = await fn.apply(bind === "rpc" ? rpc : functions, args);
  104. } catch (e) {
  105. error = e;
  106. }
  107. }
  108. if (msg.i) {
  109. if (error && options.onError)
  110. options.onError(error, method, args);
  111. if (error && options.onFunctionError) {
  112. if (options.onFunctionError(error, method, args) === true)
  113. return;
  114. }
  115. if (!error) {
  116. try {
  117. post(serialize({ t: TYPE_RESPONSE, i: msg.i, r: result }), ...extra);
  118. return;
  119. } catch (e) {
  120. error = e;
  121. if (options.onGeneralError?.(e, method, args) !== true)
  122. throw e;
  123. }
  124. }
  125. try {
  126. post(serialize({ t: TYPE_RESPONSE, i: msg.i, e: error }), ...extra);
  127. } catch (e) {
  128. if (options.onGeneralError?.(e, method, args) !== true)
  129. throw e;
  130. }
  131. }
  132. } else {
  133. const { i: ack, r: result, e: error } = msg;
  134. const promise = rpcPromiseMap.get(ack);
  135. if (promise) {
  136. clearTimeout(promise.timeoutId);
  137. if (error)
  138. promise.reject(error);
  139. else
  140. promise.resolve(result);
  141. }
  142. rpcPromiseMap.delete(ack);
  143. }
  144. }
  145. _promise = on(onMessage);
  146. return rpc;
  147. }
  148. const cacheMap = /* @__PURE__ */ new WeakMap();
  149. function cachedMap(items, fn) {
  150. return items.map((i) => {
  151. let r = cacheMap.get(i);
  152. if (!r) {
  153. r = fn(i);
  154. cacheMap.set(i, r);
  155. }
  156. return r;
  157. });
  158. }
  159. function createBirpcGroup(functions, channels, options = {}) {
  160. const getChannels = () => typeof channels === "function" ? channels() : channels;
  161. const getClients = (channels2 = getChannels()) => cachedMap(channels2, (s) => createBirpc(functions, { ...options, ...s }));
  162. const broadcastProxy = new Proxy({}, {
  163. get(_, method) {
  164. const client = getClients();
  165. const callbacks = client.map((c) => c[method]);
  166. const sendCall = (...args) => {
  167. return Promise.all(callbacks.map((i) => i(...args)));
  168. };
  169. sendCall.asEvent = (...args) => {
  170. callbacks.map((i) => i.asEvent(...args));
  171. };
  172. return sendCall;
  173. }
  174. });
  175. function updateChannels(fn) {
  176. const channels2 = getChannels();
  177. fn?.(channels2);
  178. return getClients(channels2);
  179. }
  180. getClients();
  181. return {
  182. get clients() {
  183. return getClients();
  184. },
  185. functions,
  186. updateChannels,
  187. broadcast: broadcastProxy,
  188. /**
  189. * @deprecated use `broadcast`
  190. */
  191. // @ts-expect-error deprecated
  192. boardcast: broadcastProxy
  193. };
  194. }
  195. const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
  196. function nanoid(size = 21) {
  197. let id = "";
  198. let i = size;
  199. while (i--)
  200. id += urlAlphabet[random() * 64 | 0];
  201. return id;
  202. }
  203. export { DEFAULT_TIMEOUT, cachedMap, createBirpc, createBirpcGroup };