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.
178 lines
5.0 KiB
178 lines
5.0 KiB
'use strict'; |
|
|
|
const DEFAULT_TIMEOUT = 6e4; |
|
function defaultSerialize(i) { |
|
return i; |
|
} |
|
const defaultDeserialize = defaultSerialize; |
|
const { clearTimeout, setTimeout } = globalThis; |
|
const random = Math.random.bind(Math); |
|
function createBirpc(functions, options) { |
|
const { |
|
post, |
|
on, |
|
off = () => { |
|
}, |
|
eventNames = [], |
|
serialize = defaultSerialize, |
|
deserialize = defaultDeserialize, |
|
resolver, |
|
bind = "rpc", |
|
timeout = DEFAULT_TIMEOUT |
|
} = options; |
|
const rpcPromiseMap = /* @__PURE__ */ new Map(); |
|
let _promise; |
|
let closed = false; |
|
const rpc = new Proxy({}, { |
|
get(_, method) { |
|
if (method === "$functions") |
|
return functions; |
|
if (method === "$close") |
|
return close; |
|
if (method === "then" && !eventNames.includes("then") && !("then" in functions)) |
|
return void 0; |
|
const sendEvent = (...args) => { |
|
post(serialize({ m: method, a: args, t: "q" })); |
|
}; |
|
if (eventNames.includes(method)) { |
|
sendEvent.asEvent = sendEvent; |
|
return sendEvent; |
|
} |
|
const sendCall = async (...args) => { |
|
if (closed) |
|
throw new Error(`[birpc] rpc is closed, cannot call "${method}"`); |
|
if (_promise) { |
|
try { |
|
await _promise; |
|
} finally { |
|
_promise = void 0; |
|
} |
|
} |
|
return new Promise((resolve, reject) => { |
|
const id = nanoid(); |
|
let timeoutId; |
|
if (timeout >= 0) { |
|
timeoutId = setTimeout(() => { |
|
try { |
|
options.onTimeoutError?.(method, args); |
|
throw new Error(`[birpc] timeout on calling "${method}"`); |
|
} catch (e) { |
|
reject(e); |
|
} |
|
rpcPromiseMap.delete(id); |
|
}, timeout); |
|
if (typeof timeoutId === "object") |
|
timeoutId = timeoutId.unref?.(); |
|
} |
|
rpcPromiseMap.set(id, { resolve, reject, timeoutId, method }); |
|
post(serialize({ m: method, a: args, i: id, t: "q" })); |
|
}); |
|
}; |
|
sendCall.asEvent = sendEvent; |
|
return sendCall; |
|
} |
|
}); |
|
function close() { |
|
closed = true; |
|
rpcPromiseMap.forEach(({ reject, method }) => { |
|
reject(new Error(`[birpc] rpc is closed, cannot call "${method}"`)); |
|
}); |
|
rpcPromiseMap.clear(); |
|
off(onMessage); |
|
} |
|
async function onMessage(data, ...extra) { |
|
const msg = deserialize(data); |
|
if (msg.t === "q") { |
|
const { m: method, a: args } = msg; |
|
let result, error; |
|
const fn = resolver ? resolver(method, functions[method]) : functions[method]; |
|
if (!fn) { |
|
error = new Error(`[birpc] function "${method}" not found`); |
|
} else { |
|
try { |
|
result = await fn.apply(bind === "rpc" ? rpc : functions, args); |
|
} catch (e) { |
|
error = e; |
|
} |
|
} |
|
if (msg.i) { |
|
if (error && options.onError) |
|
options.onError(error, method, args); |
|
post(serialize({ t: "s", i: msg.i, r: result, e: error }), ...extra); |
|
} |
|
} else { |
|
const { i: ack, r: result, e: error } = msg; |
|
const promise = rpcPromiseMap.get(ack); |
|
if (promise) { |
|
clearTimeout(promise.timeoutId); |
|
if (error) |
|
promise.reject(error); |
|
else |
|
promise.resolve(result); |
|
} |
|
rpcPromiseMap.delete(ack); |
|
} |
|
} |
|
_promise = on(onMessage); |
|
return rpc; |
|
} |
|
const cacheMap = /* @__PURE__ */ new WeakMap(); |
|
function cachedMap(items, fn) { |
|
return items.map((i) => { |
|
let r = cacheMap.get(i); |
|
if (!r) { |
|
r = fn(i); |
|
cacheMap.set(i, r); |
|
} |
|
return r; |
|
}); |
|
} |
|
function createBirpcGroup(functions, channels, options = {}) { |
|
const getChannels = () => typeof channels === "function" ? channels() : channels; |
|
const getClients = (channels2 = getChannels()) => cachedMap(channels2, (s) => createBirpc(functions, { ...options, ...s })); |
|
const broadcastProxy = new Proxy({}, { |
|
get(_, method) { |
|
const client = getClients(); |
|
const callbacks = client.map((c) => c[method]); |
|
const sendCall = (...args) => { |
|
return Promise.all(callbacks.map((i) => i(...args))); |
|
}; |
|
sendCall.asEvent = (...args) => { |
|
callbacks.map((i) => i.asEvent(...args)); |
|
}; |
|
return sendCall; |
|
} |
|
}); |
|
function updateChannels(fn) { |
|
const channels2 = getChannels(); |
|
fn?.(channels2); |
|
return getClients(channels2); |
|
} |
|
getClients(); |
|
return { |
|
get clients() { |
|
return getClients(); |
|
}, |
|
functions, |
|
updateChannels, |
|
broadcast: broadcastProxy, |
|
/** |
|
* @deprecated use `broadcast` |
|
*/ |
|
// @ts-expect-error deprecated |
|
boardcast: broadcastProxy |
|
}; |
|
} |
|
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; |
|
function nanoid(size = 21) { |
|
let id = ""; |
|
let i = size; |
|
while (i--) |
|
id += urlAlphabet[random() * 64 | 0]; |
|
return id; |
|
} |
|
|
|
exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT; |
|
exports.cachedMap = cachedMap; |
|
exports.createBirpc = createBirpc; |
|
exports.createBirpcGroup = createBirpcGroup;
|
|
|