Skip to content

Commit

Permalink
Merge pull request NuiCpp#109 from NuiCpp/feat/js_rpc
Browse files Browse the repository at this point in the history
Added js implementation of nui rpc.
  • Loading branch information
5cript authored May 31, 2024
2 parents a37f28d + f606640 commit 513c50e
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 0 deletions.
7 changes: 7 additions & 0 deletions nui/include/nui/backend/rpc_hub.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@ namespace Nui
callRemoteImpl(name);
}

// alias for callRemote
template <typename... Args>
void call(std::string const& name, Args&&... args) const
{
callRemote(name, std::forward<Args>(args)...);
}

/**
* @brief Enables file dialog functionality
*/
Expand Down
28 changes: 28 additions & 0 deletions nui/include/nui/frontend/rpc_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename... ArgsT>
static auto call(std::string name, ArgsT&&... args)
{
return getRemoteCallable(std::move(name))(std::forward<ArgsT>(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 <typename FunctionT, typename... ArgsT>
static auto callWithBackChannel(std::string name, FunctionT&& cb, ArgsT&&... args)
{
return getRemoteCallableWithBackChannel(std::move(name), std::forward<FunctionT>(cb))(
std::forward<ArgsT>(args)...);
}

/**
* @brief Get a callable remote function and register a temporary callable for a response.
*/
Expand Down
120 changes: 120 additions & 0 deletions nui/js/rpc.ts
Original file line number Diff line number Diff line change
@@ -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<any> {
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;

0 comments on commit 513c50e

Please sign in to comment.