UNPKG

@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
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); };