index.mjs 5.7 KB

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