/** * \file * \defgroup tfrpc Tilde Friends RPC. * Tilde Friends RPC. * @{ */ /** Whether this module is being run in a web browser. */ const k_is_browser = get_is_browser(); /** Registered methods. */ let g_api = {}; /** The next method identifier. */ let g_next_id = 1; /** Identifiers of pending calls. */ let g_calls = {}; /** * TODOC * @returns */ function get_is_browser() { try { return window !== undefined && console !== undefined; } catch { return false; } } /** \cond */ if (k_is_browser) { print = console.log; } if (k_is_browser) { window.addEventListener('message', function (event) { call_rpc(event.data); }); } else { core.register('message', function (message) { call_rpc(message?.message); }); } export let rpc = new Proxy({}, {get: make_rpc}); /** \endcond */ /** * Make a function to invoke a remote procedure. * @param target The target. * @param prop The name of the function. * @param receiver The receiver. * @return A function. */ function make_rpc(target, prop, receiver) { return function () { let id = g_next_id++; while (!id || g_calls[id] !== undefined) { id = g_next_id++; } let promise = new Promise(function (resolve, reject) { g_calls[id] = {resolve: resolve, reject: reject}; }); if (k_is_browser) { window.parent.postMessage( {message: 'tfrpc', method: prop, params: [...arguments], id: id}, '*' ); return promise; } else { return app .postMessage({ message: 'tfrpc', method: prop, params: [...arguments], id: id, }) .then((x) => promise); } }; } /** * Send a response. * @param response The response. */ function send(response) { if (k_is_browser) { window.parent.postMessage(response, '*'); } else { app.postMessage(response); } } /** * Invoke a remote procedure. * @param message An object describing the call. */ function call_rpc(message) { if (message && message.message === 'tfrpc') { let id = message.id; if (message.method) { let method = g_api[message.method]; if (method) { try { Promise.resolve(method(...message.params)) .then(function (result) { send({message: 'tfrpc', id: id, result: result}); }) .catch(function (error) { send({message: 'tfrpc', id: id, error: error}); }); } catch (error) { send({message: 'tfrpc', id: id, error: error}); } } else { send({ message: 'tfrpc', id: id, error: `Method '${message.method}' not found.`, }); } } else if (message.error !== undefined) { if (g_calls[id]) { g_calls[id].reject(message.error); delete g_calls[id]; } else { throw new Error(id + ' not found to reply.'); } } else { if (g_calls[id]) { g_calls[id].resolve(message.result); delete g_calls[id]; } else { throw new Error(id + ' not found to reply.'); } } } } /** * Register a function that to be called remotely. * @param method The method. */ export function register(method) { g_api[method.name] = method; } /** @} */