提交学习笔记专用
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.

223 lines
6.4 KiB

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