@xuda.io/runtime-bundle
Version:
The Xuda Runtime Bundle refers to a collection of scripts and libraries packaged together to provide the necessary runtime environment for executing plugins or components in the Xuda platform.
340 lines (279 loc) • 11.4 kB
JavaScript
export const run_javascript = async function (SESSION_ID, jobNoP, dsSession, script, evaluate, prog_id, params, evt, $container) {
const _session = SESSION_OBJ[SESSION_ID];
const should_delete_job_after_script = function (source) {
return !String(source || '').includes('func.api.callback');
};
const get_script_base_url = function () {
return `https://${_session.domain}/studio-drive/${APP_OBJ[_session.app_id].app_replicate || _session.app_id}/scripts/${prog_id}/`;
};
const get_browser_api_utils = async function () {
return await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession,
jobNoP,
});
};
const normalize_rows_array = function (rows, source) {
if (!Array.isArray(rows)) {
return rows;
}
Object.defineProperties(rows, {
rows: { value: rows, enumerable: false, configurable: true },
code: { value: source?.code, enumerable: false, configurable: true },
data: { value: source?.data, enumerable: false, configurable: true },
total_rows: { value: source?.data?.total_rows ?? source?.total_rows ?? rows.length, enumerable: false, configurable: true },
response: { value: source, enumerable: false, configurable: true },
});
return rows;
};
const normalize_db_rows_value = function (value) {
if (Array.isArray(value)) {
return normalize_rows_array(value, value.response || value);
}
if (!value || typeof value !== 'object') {
return value;
}
if (Array.isArray(value?.data?.rows)) {
return normalize_rows_array(value.data.rows, value);
}
if (Array.isArray(value?.rows)) {
return normalize_rows_array(value.rows, value);
}
return value;
};
const normalize_javascript_params = function (value) {
const normalized_value = normalize_db_rows_value(value);
if (normalized_value !== value) {
return normalized_value;
}
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return value || {};
}
const params_obj = { ...value };
for (const [key, val] of Object.entries(params_obj)) {
params_obj[key] = normalize_db_rows_value(val);
}
const keys = Object.keys(params_obj);
if (keys.length === 1 && keys[0] === 'rows' && Array.isArray(params_obj.rows)) {
return params_obj.rows;
}
return params_obj;
};
const script_params = normalize_javascript_params(params);
const get_static_imports = function (source) {
return Array.from(String(source || '').matchAll(/(^|\n)([ \t]*import\s+(?:(?:[\s\S]*?)\s+from\s+)?['"][^'"]+['"]\s*(?:(?:assert|with)\s*\{[\s\S]*?\})?\s*;?)[ \t]*(?=\n|$)/g));
};
const has_module_syntax = function (source) {
return /^\s*(import|export)\s/m.test(source || '');
};
const rewrite_import_specifier = function (import_source) {
const base_url = get_script_base_url();
const to_runtime_url = function (specifier) {
if (/^[a-z][a-z0-9+.-]*:/i.test(specifier)) return specifier;
if (specifier.startsWith('.') || specifier.startsWith('/')) return new URL(specifier, base_url).href;
return specifier;
};
return import_source
.replace(/(\bfrom\s*)(['"])([^'"]+)\2/, function (_, prefix, quote, specifier) {
return `${prefix}${quote}${to_runtime_url(specifier)}${quote}`;
})
.replace(/^(\s*import\s*)(['"])([^'"]+)\2/, function (_, prefix, quote, specifier) {
return `${prefix}${quote}${to_runtime_url(specifier)}${quote}`;
});
};
const build_source_module = function (source) {
const imports = [];
const source_without_imports = source.replace(/(^|\n)([ \t]*import\s+(?:(?:[\s\S]*?)\s+from\s+)?['"][^'"]+['"]\s*(?:(?:assert|with)\s*\{[\s\S]*?\})?\s*;?)[ \t]*(?=\n|$)/g, function (match, prefix, import_source) {
imports.push(rewrite_import_specifier(import_source));
return prefix;
});
if (/^\s*export\s/m.test(source_without_imports)) {
return `${imports.join('\n')}
${source_without_imports}`;
}
return `${imports.join('\n')}
export default async function(params, xu, SESSION_ID, SESSION_OBJ, jobNoP, $container, func, glb, APP_OBJ, evt) {
${source_without_imports}
}`;
};
const create_module_url = function (source) {
if (typeof window !== 'undefined' && typeof Blob !== 'undefined' && typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function') {
const url = URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
return {
url,
revoke: function () {
URL.revokeObjectURL(url);
},
};
}
return {
url: `data:text/javascript;charset=utf-8,${encodeURIComponent(source)}`,
revoke: function () {},
};
};
const run_source_javascript_module_in_browser = async function (source, xu) {
const module_source = build_source_module(source);
const module_url = create_module_url(`${module_source}\n//# sourceURL=xuda-source-program-${prog_id || 'inline'}.mjs`);
try {
const source_module = await import(module_url.url);
const code = func.utils.replace_studio_drive_url(SESSION_ID, source_module.default);
if (typeof code === 'function') {
return await code(script_params, xu, SESSION_ID, SESSION_OBJ, jobNoP, $container, func, glb, APP_OBJ, evt);
}
return code;
} finally {
module_url.revoke();
}
};
const run_source_javascript_program_in_browser = async function (import_error) {
if (!script) {
throw import_error;
}
if (import_error) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_JS_010',
source: 'run_javascript',
message: 'Compiled JavaScript module unavailable, running source script',
type: 'W',
err: import_error,
details: {
prog_id,
},
});
}
const source = func.utils.replace_studio_drive_url(SESSION_ID, script);
const xu = await get_browser_api_utils();
let ret;
if (has_module_syntax(source) || get_static_imports(source).length) {
ret = await run_source_javascript_module_in_browser(source, xu);
} else {
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
const runner = new AsyncFunction('params', 'xu', 'SESSION_ID', 'SESSION_OBJ', 'jobNoP', '$container', 'func', 'glb', 'APP_OBJ', 'evt', source);
ret = await runner(script_params, xu, SESSION_ID, SESSION_OBJ, jobNoP, $container, func, glb, APP_OBJ, evt);
}
if (should_delete_job_after_script(source)) {
func.events.delete_job(SESSION_ID, jobNoP);
}
return ret;
};
const run_native_javascript_program_in_browser = async function () {
// run in browser
///// insert the css
const path_to_dist = `${get_script_base_url()}dist/`;
const native_script_cache_bust = _session.opt?.app_build_id || _session.opt?.last_changed_ts || 'dev';
const with_native_cache_bust = (url) => `${url}${url.includes('?') ? '&' : '?'}xu_native_ts=${native_script_cache_bust}`;
let index_mjs;
try {
const native_script_url = with_native_cache_bust(`${path_to_dist}index.mjs`);
index_mjs = await import(native_script_url);
} catch (import_error) {
return await run_source_javascript_program_in_browser(import_error);
}
if (index_mjs.css) {
const css_url = `${path_to_dist}index.css`;
func.utils.load_css_on_demand(css_url);
}
const code = func.utils.replace_studio_drive_url(SESSION_ID, index_mjs.default);
const xu = await get_browser_api_utils();
const ret = await code(script_params, xu, SESSION_ID, SESSION_OBJ, jobNoP, $container);
if (should_delete_job_after_script(code)) {
func.events.delete_job(SESSION_ID, jobNoP);
}
return ret;
};
const run_native_javascript_program_in_server = async function () {
return new Promise(async (resolve, reject) => {
const studio_drive_script_folder = path.join(_conf.studio_drive_path, app_id, 'scripts');
const studio_drive_script_program_folder = path.join(studio_drive_script_folder, prog_id);
const dist = path.join(studio_drive_script_program_folder, 'dist');
const index_mjs = path.join(dist, 'index.mjs');
process.on('uncaughtException', (err) => {
console.error('Asynchronous error caught.', err);
});
const resolve_local = function (e) {
// resolve({ code: 1, data: e });
if (should_delete_job_after_script(script)) {
func.events.delete_job(SESSION_ID, jobNoP);
}
resolve(e);
};
const reject_local = function (e) {
// reject({ code: -1, data: e });
func.events.delete_job(SESSION_ID, jobNoP);
reject(e);
// reject(e.message);
};
try {
const script = new VMScript(
`
const index_mjs = await import(${index_mjs});
try{index_mjs.default(req,params,setup_doc,resolve,reject);}catch(e){ console.error(e); }`,
{ filename: dist, dirname: dist },
);
let vm = new NodeVM({
require: {
external: true,
},
sandbox: {
resolve: resolve_local,
reject: reject_local,
},
timeout: 60000,
});
return await vm.run(script, {
filename: dist,
dirname: dist,
});
} catch (err) {
console.error('Failed to execute script.', err);
}
});
};
if (evaluate) {
var script_ret = (await func.expression.get(SESSION_ID, script, dsSession, 'javascript', null, null, null, null, null, null, null, () => {}, jobNoP)).result;
} else {
if (prog_id) {
if (typeof IS_API_SERVER !== 'undefined' || typeof IS_DOCKER !== 'undefined' || typeof IS_PROCESS_SERVER !== 'undefined') {
return await run_native_javascript_program_in_server();
} else {
return await run_native_javascript_program_in_browser();
}
} else {
await func.expression.secure_eval(SESSION_ID, 'javascript', func.utils.replace_studio_drive_url(SESSION_ID, script), jobNoP, dsSession, () => {}, evt);
}
}
if (should_delete_job_after_script(script)) {
func.events.delete_job(SESSION_ID, jobNoP);
return;
}
};
export const call_javascript = async function (SESSION_ID, jobNoP, refIdP, dsSession, evaluate, $container) {
const error_log = async function (msg) {
let _session = SESSION_OBJ[SESSION_ID];
func.events.delete_job(SESSION_ID, jobNoP);
if (typeof IS_API_SERVER !== 'undefined') {
return __.rpi.write_log(_session.app_id, 'error', 'api', msg);
}
if (typeof IS_DOCKER !== 'undefined') {
return __.rpi.write_log(_session.app_id, 'error', 'worker', msg);
}
// client
await func.common.db(SESSION_ID, 'write_log', {
msg,
log_type: 'error',
source: 'runtime',
});
};
let view_ret = await func.utils.VIEWS_OBJ.get(SESSION_ID, refIdP.prog);
if (!view_ret) {
return error_log('program not found');
}
if (!view_ret?.scriptData?.value) {
return error_log('script is empty');
}
return await run_javascript(SESSION_ID, jobNoP, dsSession, view_ret.scriptData.value, evaluate, refIdP.prog, refIdP?.parameters, null, $container);
};