rage-framework-rpc/src/index.js

368 lines
12 KiB
JavaScript
Raw Normal View History

2018-11-02 05:10:24 +00:00
import * as util from './util.js';
2018-11-01 13:31:53 +00:00
2018-11-01 19:05:48 +00:00
const environment = util.getEnvironment();
if(!environment) throw 'Unknown RAGE environment';
2018-11-02 04:36:03 +00:00
const ERR_NOT_FOUND = 'PROCEDURE_NOT_FOUND';
2018-11-01 19:05:48 +00:00
const PROCESS_EVENT = '__rpc:process';
2018-11-01 22:47:57 +00:00
const PROCEDURE_EXISTS = '__rpc:exists';
2018-11-01 19:05:48 +00:00
2018-11-01 13:31:53 +00:00
const listeners = {};
2018-11-01 19:05:48 +00:00
const pending = {};
2018-11-01 13:31:53 +00:00
2018-11-01 22:47:57 +00:00
let passEventToBrowser, passEventToBrowsers;
2018-11-01 20:54:32 +00:00
if(environment === "client"){
2018-11-02 04:36:03 +00:00
passEventToBrowser = (browser, data) => {
const raw = util.stringifyData(data);
browser.execute(`var process = window["${PROCESS_EVENT}"]; if(process){ process('${raw}'); }else{ mp.trigger("${PROCESS_EVENT}", '{"ret":1,"id":"${data.id}","err":"${ERR_NOT_FOUND}"}'); }`);
2018-11-01 22:47:57 +00:00
};
2018-11-02 04:36:03 +00:00
passEventToBrowsers = (data) => {
mp.browsers.forEach(browser => passEventToBrowser(browser, data));
2018-11-01 20:54:32 +00:00
};
}
2018-11-01 22:47:57 +00:00
async function callProcedure(name, args, info){
2018-11-02 04:36:03 +00:00
if(!listeners[name]) throw ERR_NOT_FOUND;
2018-11-01 22:47:57 +00:00
return listeners[name](args, info);
}
2018-11-01 19:05:48 +00:00
const processEvent = (...args) => {
2018-11-01 20:40:00 +00:00
let rawData = args[0];
if(environment === "server") rawData = args[1];
const data = util.parseData(rawData);
2018-11-01 19:05:48 +00:00
if(data.req){ // someone is trying to remotely call a procedure
const info = {
id: data.id,
2018-11-02 00:48:56 +00:00
environment: data.fenv || data.env
2018-11-01 19:05:48 +00:00
};
if(environment === "server") info.player = args[0];
const promise = callProcedure(data.name, data.args, info);
2018-11-01 22:47:57 +00:00
const part = {
ret: 1,
id: data.id
};
2018-11-01 19:05:48 +00:00
switch(environment){
case "server": {
promise.then(res => {
info.player.call(PROCESS_EVENT, [util.stringifyData({
2018-11-01 20:40:00 +00:00
...part,
2018-11-01 19:05:48 +00:00
res
})]);
}).catch(err => {
info.player.call(PROCESS_EVENT, [util.stringifyData({
2018-11-01 20:40:00 +00:00
...part,
2018-11-01 19:05:48 +00:00
err
})]);
});
break;
}
case "client": {
2018-11-01 20:54:32 +00:00
if(data.env === "server"){
promise.then(res => {
mp.events.callRemote(PROCESS_EVENT, util.stringifyData({
...part,
res
}));
}).catch(err => {
mp.events.callRemote(PROCESS_EVENT, util.stringifyData({
...part,
err
}));
});
}else if(data.env === "cef"){
promise.then(res => {
2018-11-02 04:36:03 +00:00
passEventToBrowsers({
2018-11-01 20:54:32 +00:00
...part,
res
2018-11-02 04:36:03 +00:00
});
2018-11-01 20:54:32 +00:00
}).catch(err => {
2018-11-02 04:36:03 +00:00
passEventToBrowsers({
2018-11-01 20:54:32 +00:00
...part,
err
2018-11-02 04:36:03 +00:00
});
2018-11-01 20:54:32 +00:00
});
}
2018-11-01 19:05:48 +00:00
break;
}
2018-11-01 22:47:57 +00:00
case "cef": {
promise.then(res => {
mp.trigger(PROCESS_EVENT, util.stringifyData({
...part,
res
}));
}).catch(err => {
mp.trigger(PROCESS_EVENT, util.stringifyData({
...part,
err
}));
});
}
2018-11-01 19:05:48 +00:00
}
}else if(data.ret){ // a previously called remote procedure has returned
const info = pending[data.id];
if(info){
if(data.err) info.reject(data.err);
else info.resolve(data.res);
pending[data.id] = undefined;
}
}
};
2018-11-01 13:31:53 +00:00
2018-11-01 20:40:00 +00:00
if(environment === "cef"){
window[PROCESS_EVENT] = processEvent;
2018-11-01 22:47:57 +00:00
window[PROCEDURE_EXISTS] = name => !!listeners[name];
2018-11-01 20:40:00 +00:00
}else{
mp.events.add(PROCESS_EVENT, processEvent);
}
2018-11-01 13:31:53 +00:00
/**
2018-11-01 19:05:48 +00:00
* Register a procedure.
* @param {string} name - The name of the procedure.
* @param {function} cb - The procedure's callback. The return value will be sent back to the caller.
2018-11-01 13:31:53 +00:00
*/
2018-11-02 05:10:24 +00:00
export function register(name, cb){
2018-11-02 04:36:03 +00:00
if(arguments.length !== 2) throw 'register expects 2 arguments: "name" and "cb"';
2018-11-01 19:05:48 +00:00
listeners[name] = cb;
2018-11-02 05:10:24 +00:00
}
2018-11-01 13:31:53 +00:00
/**
2018-11-01 19:05:48 +00:00
* Unregister a procedure.
* @param {string} name - The name of the procedure.
2018-11-01 13:31:53 +00:00
*/
2018-11-02 05:10:24 +00:00
export function unregister(name){
2018-11-02 04:36:03 +00:00
if(arguments.length !== 1) throw 'unregister expects 1 argument: "name"';
2018-11-01 19:05:48 +00:00
listeners[name] = undefined;
2018-11-02 05:10:24 +00:00
}
2018-11-01 13:31:53 +00:00
/**
2018-11-02 04:36:03 +00:00
* Calls a local procedure. Only procedures registered in the same context will be resolved.
*
* Can be called from any environment.
*
2018-11-01 19:05:48 +00:00
* @param {string} name - The name of the locally registered procedure.
* @param args - Any parameters for the procedure.
* @returns {Promise} - The result from the procedure.
2018-11-01 13:31:53 +00:00
*/
2018-11-02 05:10:24 +00:00
export function call(name, args){
2018-11-02 04:36:03 +00:00
if(arguments.length !== 1 && arguments.length !== 2) return Promise.reject('call expects 1 or 2 arguments: "name" and optional "args"');
return callProcedure(name, args, { environment });
2018-11-02 05:10:24 +00:00
}
2018-11-01 13:31:53 +00:00
2018-11-02 05:10:24 +00:00
function _callServer(name, args, extraData){
2018-11-01 19:05:48 +00:00
switch(environment){
case "server": {
2018-11-02 05:10:24 +00:00
return call(name, args);
2018-11-01 19:05:48 +00:00
}
case "client": {
const id = util.uid();
return new Promise((resolve, reject) => {
pending[id] = {
resolve,
reject
};
mp.events.callRemote(PROCESS_EVENT, util.stringifyData({
req: 1,
id,
name,
env: environment,
2018-11-02 00:48:56 +00:00
args,
...extraData
2018-11-01 19:05:48 +00:00
}));
});
}
2018-11-01 20:40:00 +00:00
case "cef": {
2018-11-02 05:10:24 +00:00
return callClient('__rpc:callServer', [name, args]);
2018-11-01 20:40:00 +00:00
}
2018-11-01 19:05:48 +00:00
}
2018-11-02 00:48:56 +00:00
}
/**
* Calls a remote procedure registered on the server.
2018-11-02 04:36:03 +00:00
*
* Can be called from any environment.
*
2018-11-02 00:48:56 +00:00
* @param {string} name - The name of the registered procedure.
* @param args - Any parameters for the procedure.
* @returns {Promise} - The result from the procedure.
*/
2018-11-02 05:10:24 +00:00
export function callServer(name, args){
2018-11-02 04:36:03 +00:00
if(arguments.length !== 1 && arguments.length !== 2) return Promise.reject('callServer expects 1 or 2 arguments: "name" and optional "args"');
2018-11-02 05:10:24 +00:00
return _callServer(name, args, {});
}
2018-11-01 13:31:53 +00:00
/**
2018-11-01 19:05:48 +00:00
* Calls a remote procedure registered on the client.
2018-11-02 04:36:03 +00:00
*
* Can be called from any environment.
*
* @param player - The player to call the procedure on.
2018-11-01 19:05:48 +00:00
* @param {string} name - The name of the registered procedure.
* @param args - Any parameters for the procedure.
* @returns {Promise} - The result from the procedure.
2018-11-01 13:31:53 +00:00
*/
2018-11-01 22:59:45 +00:00
// serverside
// callClient(player, name, args)
//
// clientside or cef
// callClient(name, args)
2018-11-02 05:10:24 +00:00
export function callClient(player, name, args){
2018-11-01 19:05:48 +00:00
switch(environment){
case "client": {
2018-11-02 04:36:03 +00:00
args = name;
name = player;
if((arguments.length !== 1 && arguments.length !== 2) || typeof name !== "string") return Promise.reject('callClient from the client expects 1 or 2 arguments: "name" and optional "args"');
2018-11-02 05:10:24 +00:00
return call(name, args);
2018-11-01 19:05:48 +00:00
}
case "server": {
2018-11-02 04:36:03 +00:00
if((arguments.length !== 2 && arguments.length !== 3) || typeof player !== "object") return Promise.reject('callClient from the server expects 2 or 3 arguments: "player", "name", and optional "args"');
2018-11-01 19:05:48 +00:00
const id = util.uid();
return new Promise((resolve, reject) => {
pending[id] = {
resolve,
reject
};
player.call(PROCESS_EVENT, [util.stringifyData({
req: 1,
id,
name,
env: environment,
args
})]);
});
}
2018-11-01 20:54:32 +00:00
case "cef": {
2018-11-02 04:36:03 +00:00
args = name;
name = player;
if((arguments.length !== 1 && arguments.length !== 2) || typeof name !== "string") return Promise.reject('callClient from the browser expects 1 or 2 arguments: "name" and optional "args"');
2018-11-01 20:54:32 +00:00
const id = util.uid();
return new Promise((resolve, reject) => {
pending[id] = {
resolve,
reject
};
mp.trigger(PROCESS_EVENT, util.stringifyData({
req: 1,
id,
name,
env: environment,
args
}));
});
}
2018-11-01 19:05:48 +00:00
}
2018-11-02 05:10:24 +00:00
}
2018-11-01 13:31:53 +00:00
2018-11-02 05:10:24 +00:00
function _callBrowser(id, browser, name, args, extraData){
2018-11-02 02:01:11 +00:00
return new Promise((resolve, reject) => {
pending[id] = {
resolve,
reject
};
2018-11-02 04:36:03 +00:00
passEventToBrowser(browser, {
2018-11-02 02:01:11 +00:00
req: 1,
id,
name,
env: environment,
args,
...extraData
2018-11-02 04:36:03 +00:00
});
2018-11-02 02:01:11 +00:00
});
}
2018-11-02 05:10:24 +00:00
async function _callBrowsers(player, name, args, extraData){
2018-11-02 02:01:11 +00:00
switch(environment){
case "client": {
args = name;
name = player;
const id = util.uid();
const numBrowsers = mp.browsers.length;
let browser;
for(let i = 0; i < numBrowsers; i++){
const b = mp.browsers.at(i);
await new Promise(resolve => {
const existsHandler = str => {
const parts = str.split(':');
if(parts[0] === id){
if(+parts[1]){
browser = b;
}
}
mp.events.remove(PROCEDURE_EXISTS, existsHandler);
resolve();
};
mp.events.add(PROCEDURE_EXISTS, existsHandler);
b.execute(`var f = window["${PROCEDURE_EXISTS}"]; mp.trigger("${PROCEDURE_EXISTS}", "${id}:"+((f && f("${name}")) ? 1 : 0));`);
});
if(browser) break;
}
2018-11-02 05:10:24 +00:00
if(browser) return _callBrowser(id, browser, name, args, extraData);
2018-11-02 04:36:03 +00:00
throw ERR_NOT_FOUND;
2018-11-02 02:01:11 +00:00
}
case "server": {
2018-11-02 05:10:24 +00:00
return callClient(player, '__rpc:callBrowsers', [name, args]);
2018-11-02 02:01:11 +00:00
}
case "cef": {
args = name;
name = player;
2018-11-02 05:10:24 +00:00
return callClient('__rpc:callBrowsers', [name, args]);
2018-11-02 02:01:11 +00:00
}
}
}
2018-11-01 22:59:45 +00:00
/**
* Calls a remote procedure registered in any browser context.
2018-11-02 02:01:11 +00:00
*
* Can be called from any environment.
*
* @param {object} [player] - The player to call the procedure on.
2018-11-01 22:59:45 +00:00
* @param {string} name - The name of the registered procedure.
* @param args - Any parameters for the procedure.
* @returns {Promise} - The result from the procedure.
*/
2018-11-02 05:10:24 +00:00
export function callBrowsers(player, name, args){
2018-11-02 02:01:11 +00:00
switch(environment){
case "client":
2018-11-02 04:36:03 +00:00
if(arguments.length !== 1 && arguments.length !== 2) return Promise.reject('callBrowsers from the client expects 1 or 2 arguments: "name" and optional "args"');
2018-11-02 02:01:11 +00:00
break;
case "server":
2018-11-02 04:36:03 +00:00
if(arguments.length !== 2 && arguments.length !== 3) return Promise.reject('callBrowsers from the server expects 2 or 3 arguments: "player", "name", and optional "args"');
2018-11-02 02:01:11 +00:00
break;
case "cef":
2018-11-02 04:36:03 +00:00
if(arguments.length !== 1 && arguments.length !== 2) return Promise.reject('callBrowsers from the browser expects 1 or 2 arguments: "name" and optional "args"');
2018-11-02 02:01:11 +00:00
break;
2018-11-01 22:47:57 +00:00
}
2018-11-02 05:10:24 +00:00
return _callBrowsers(player, name, args, {});
}
2018-11-02 02:01:11 +00:00
/**
* Calls a remote procedure registered in a specific browser instance.
*
* Client-side environment only.
*
* @param {object} browser - The browser instance.
* @param {string} name - The name of the registered procedure.
* @param args - Any parameters for the procedure.
* @returns {Promise} - The result from the procedure.
*/
2018-11-02 05:10:24 +00:00
export function callBrowser(browser, name, args){
2018-11-02 04:36:03 +00:00
if(environment !== "client") return Promise.reject('callBrowser can only be used in the client environment');
if(arguments.length !== 2 && arguments.length !== 3) return Promise.reject('callBrowser expects 2 or 3 arguments: "browser", "name", and optional "args"');
2018-11-02 02:01:11 +00:00
const id = util.uid();
2018-11-02 05:10:24 +00:00
return _callBrowser(id, browser, name, args, {});
}
2018-11-01 22:47:57 +00:00
2018-11-02 00:48:56 +00:00
// set up internal pass-through events
if(environment === "client"){
2018-11-02 05:10:24 +00:00
register('__rpc:callServer', ([name, args], info) => {
return _callServer(name, args, {
2018-11-02 00:48:56 +00:00
fenv: info.environment
});
});
2018-11-02 05:10:24 +00:00
register('__rpc:callBrowsers', ([name, args], info) => {
return _callBrowsers(name, args, null, {
2018-11-02 02:01:11 +00:00
fenv: info.environment
});
});
2018-11-02 05:10:24 +00:00
}