diff --git a/nui/include/nui/backend/rpc_hub.hpp b/nui/include/nui/backend/rpc_hub.hpp index 949bbf27..6fee3bb9 100644 --- a/nui/include/nui/backend/rpc_hub.hpp +++ b/nui/include/nui/backend/rpc_hub.hpp @@ -169,6 +169,13 @@ namespace Nui callRemoteImpl(name); } + // alias for callRemote + template + void call(std::string const& name, Args&&... args) const + { + callRemote(name, std::forward(args)...); + } + /** * @brief Enables file dialog functionality */ diff --git a/nui/include/nui/frontend/rpc_client.hpp b/nui/include/nui/frontend/rpc_client.hpp index 076d6673..c6c7fc74 100644 --- a/nui/include/nui/frontend/rpc_client.hpp +++ b/nui/include/nui/frontend/rpc_client.hpp @@ -178,6 +178,34 @@ namespace Nui return RemoteCallable{std::move(name)}; } + /** + * @brief Get a callable remote function and call it immediately. + * + * @param name Name of the function. + * @param args Arguments to pass to the function. + * @return auto The result of the function. + */ + template + static auto call(std::string name, ArgsT&&... args) + { + return getRemoteCallable(std::move(name))(std::forward(args)...); + } + + /** + * @brief Get a callable remote function and call it immediately with a callback. + * + * @param name Name of the function. + * @param cb The callback function. + * @param args Arguments to pass to the function. + * @return auto The result of the function. + */ + template + static auto callWithBackChannel(std::string name, FunctionT&& cb, ArgsT&&... args) + { + return getRemoteCallableWithBackChannel(std::move(name), std::forward(cb))( + std::forward(args)...); + } + /** * @brief Get a callable remote function and register a temporary callable for a response. */ diff --git a/nui/js/rpc.ts b/nui/js/rpc.ts new file mode 100644 index 00000000..3d35e0ed --- /dev/null +++ b/nui/js/rpc.ts @@ -0,0 +1,120 @@ +export type AnyFunction = (...args: any[]) => any; + +class RpcClient { + constructor() { + } + + public static UnresolvedError = class { + private message: string; + public readonly name = "UnresolvedRemoteCallableError"; + + constructor(name: string) { + this.message = `Remote callable with name '${name}' is undefined`; + } + }; + + private static resolve = (name: string) => { + const rpcObject = globalThis.nui_rpc; + if (rpcObject === undefined) + return undefined; + + if (rpcObject.backend === undefined) + return undefined; + + if (!rpcObject.backend.hasOwnProperty(name)) + return undefined; + + return rpcObject.backend[name]; + } + + public static getRemoteCallable(name: string) { + return (...args: any[]) : any => { + let resolved: AnyFunction | undefined = undefined; + const memoize = (): AnyFunction | undefined => { + if (resolved !== undefined) + return resolved; + resolved = RpcClient.resolve(name); + console.log(name, resolved); + return resolved; + }; + + return memoize() ? resolved!(...args) : new RpcClient.UnresolvedError(name); + } + } + + public static getRemoteCallableWithBackChannel(name: string, cb: AnyFunction) + { + return (...args: any[]) : any => { + const tempId = globalThis.nui_rpc.tempId + 1; + globalThis.nui_rpc.tempId = tempId; + + const tempIdString = `temp_${tempId}`; + globalThis.nui_rpc.backend[tempIdString] = (param: any) => { + cb(param); + delete globalThis.nui_rpc.backend[tempIdString]; + }; + + const resolved = RpcClient.resolve(name); + if (resolved === undefined) + return new RpcClient.UnresolvedError(name); + return resolved(tempIdString, ...args); + } + } + + public static call(name: string, ...args: any[]) { + if (args.length > 0 && typeof args[0] === 'function') { + const cb = args[0]; + const restArgs = args.slice(1); + const callable = RpcClient.getRemoteCallableWithBackChannel(name, cb); + if (callable instanceof RpcClient.UnresolvedError) + return callable; + return callable(...restArgs); + } else { + const callable = RpcClient.getRemoteCallable(name); + if (callable instanceof RpcClient.UnresolvedError) + return callable; + return callable(...args); + } + } + + // Only use for functions that respond via callback + public static callAsync(name: string, ...args: any[]): Promise { + return new Promise((resolve, reject) => { + const callback = (result: any) => { + resolve(result); + }; + + const callable = RpcClient.getRemoteCallableWithBackChannel(name, callback); + if (callable instanceof RpcClient.UnresolvedError) { + reject(callable); + } else { + const result = callable(...args); + if (result instanceof RpcClient.UnresolvedError) { + reject(result); + } + } + }); + } + + public static register(name: string, func: AnyFunction) { + globalThis.nui_rpc.frontend[name] = func; + } + + public static unregister(name: string) { + delete globalThis.nui_rpc.frontend[name]; + } + + public static registerMany(funcs: { [key: string]: AnyFunction }) { + for (const key in funcs) { + RpcClient.register(key, funcs[key]); + } + } + + public static unregisterMany(names: string[]) { + for (const name of names) { + RpcClient.unregister(name); + } + } +} + +export default RpcClient; \ No newline at end of file