提交学习笔记专用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

228 lines
6.5 KiB

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