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.

436 lines (381 loc) • 12.9 kB
const _this = {}; export const init_module = (e) => { _this.func = e.func; _this.glb = e.glb; _this.SESSION_OBJ = e.SESSION_OBJ; _this.APP_OBJ = e.APP_OBJ; _this.IS_DOCKER = e.IS_DOCKER; _this.IS_API_SERVER = e.IS_API_SERVER; _this.IS_PROCESS_SERVER = e.IS_PROCESS_SERVER; }; export const get_doc_from_studio = async function (SESSION_ID, app_id, idP) { return new Promise(function (resolve, reject) { var _session = _this.SESSION_OBJ[SESSION_ID]; if (glb.IS_WORKER) { func.utils.post_back_to_client(SESSION_ID, 'get_doc_from_studio', _session.worker_id, { doc_id: idP }); self.addEventListener('live_preview_get_obj_response_worker_' + idP, (event) => { // console.log("Custom event triggered:", event); resolve(event.detail.data); }); } else { STUDIO_PEER_CONN_SEND_METHOD({ service: 'get_doc_obj', data: { doc_id: idP }, session_id: SESSION_ID, id: STUDIO_PEER.id, source: 'runtime', app_id, }); const _lp_handler = function (data) { resolve(data.data); func.runtime.platform.off('live_preview_get_obj_response_' + idP, _lp_handler); }; func.runtime.platform.on('live_preview_get_obj_response_' + idP, _lp_handler); } }); }; export const get_doc_from_websocket = async function (SESSION_ID, app_id, idP) { var _session = _this.SESSION_OBJ[SESSION_ID]; return new Promise(function (resolve, reject) { let done = false; let cleanup = function () {}; const finish = function (err, data) { if (done) return; done = true; clearTimeout(timeout); cleanup(); if (err) { reject(err); return; } resolve(data); }; const timeout = setTimeout(() => { finish(new Error(`get_doc_from_websocket timeout: ${idP}`)); }, 8000); if (glb.IS_WORKER) { // throw new Error("fail to fetch from ws"); func.utils.post_back_to_client(SESSION_ID, 'get_doc_from_websocket', _session.worker_id, { doc_id: idP }); const worker_event = 'get_doc_obj_from_build_worker_' + idP; const worker_handler = (event) => { finish(null, event.detail.data); }; cleanup = function () { self.removeEventListener?.(worker_event, worker_handler); }; self.addEventListener(worker_event, worker_handler); } else { if (RUNTIME_SERVER_WEBSOCKET && RUNTIME_SERVER_WEBSOCKET_CONNECTED) { const ws_event = 'get_doc_obj_from_build_response_' + idP; const _doc_handler = function (data) { finish(null, data.data); }; cleanup = function () { func.runtime.platform.off(ws_event, _doc_handler); }; func.runtime.platform.on(ws_event, _doc_handler); RUNTIME_SERVER_WEBSOCKET.emit('message', { service: 'get_doc_obj_from_build', data: { doc_id: idP }, }); } else { finish(new Error('fail to fetch from ws websocket inactive')); } } }); }; const should_fallback_doc_http = function (err) { const message = `${err?.message || err || ''}`; return /websocket inactive|get_doc_from_websocket timeout/i.test(message); }; const warn_doc_http_fallback = function (SESSION_ID, app_id, idP, err) { const _session = _this.SESSION_OBJ[SESSION_ID]; _session.doc_http_fallback_count = (_session.doc_http_fallback_count || 0) + 1; if (_session.doc_http_fallback_warned) { return; } _session.doc_http_fallback_warned = true; console.warn('[xuda-runtime] doc_ws_fallback_http', { app_id, doc_id: idP, reason: err?.message || String(err || ''), note: 'subsequent document websocket fallbacks are counted on SESSION_OBJ[SESSION_ID].doc_http_fallback_count', }); }; const get_doc_from_studio_with_timeout = async function (SESSION_ID, app_id, idP, timeout_ms = 5000) { return new Promise((resolve) => { let resolved = false; const timeout = setTimeout(() => { if (resolved) return; resolved = true; resolve(null); }, timeout_ms); get_doc_from_studio(SESSION_ID, app_id, idP) .then((doc) => { if (resolved) return; resolved = true; clearTimeout(timeout); resolve(doc); }) .catch(() => { if (resolved) return; resolved = true; clearTimeout(timeout); resolve(null); }); }); }; const get_doc_from_runtime_http = async function (SESSION_ID, app_id, idP) { const _session = _this.SESSION_OBJ[SESSION_ID]; let url = _this.func.common.get_url(SESSION_ID, 'rpi', ''); if (['master'].includes(_this.APP_OBJ[app_id]?.app_type)) { url += 'get_doc_obj_raw'; } else { url += 'get_doc_obj_from_build'; } const response = await fetch(url, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'xu-gtp-token': _session.gtp_token, 'xu-app-token': _session.app_token, }, body: JSON.stringify({ app_id, doc_id: idP, build: _session.build_info?.instance_build || _session.build_info?.build || _session.opt.app_build_id, app_id_reference: _this.APP_OBJ[app_id]?.app_id_reference, engine_mode: _session.engine_mode, }), }); if (!response.ok) { if (response.status === 400) { return _this.func.utils.request_error(SESSION_ID, 'ajax', response.statusText); } throw new Error(await response.text()); } const json = await response.json(); return json.data; }; const get_doc_from_runtime_cache_or_remote = async function (SESSION_ID, app_id, idP, obj_type = 'DOCS_OBJ') { const _session = _this.SESSION_OBJ[SESSION_ID]; let views_ret = await read_objects_cache(SESSION_ID, idP, obj_type); if (!views_ret) { if (typeof _this.IS_DOCKER === 'undefined' && typeof _this.IS_PROCESS_SERVER === 'undefined') { if (_session.engine_mode === 'miniapp' || _session.engine_mode === 'live_preview') { views_ret = await get_doc_from_runtime_http(SESSION_ID, app_id, idP); } else { try { views_ret = await get_doc_from_websocket(SESSION_ID, app_id, idP); } catch (err) { if (!should_fallback_doc_http(err)) { throw err; } warn_doc_http_fallback(SESSION_ID, app_id, idP, err); views_ret = await get_doc_from_runtime_http(SESSION_ID, app_id, idP); } } } if (typeof _this.IS_DOCKER !== 'undefined' || typeof _this.IS_PROCESS_SERVER !== 'undefined') { const ret = await __.rpi.get_prog(app_id, idP); if (ret.data) { views_ret = ret.data; } } if (views_ret) { if (views_ret.error) { console.error(views_ret); return; } await save_objects_cache(SESSION_ID, idP, obj_type, views_ret); } } return views_ret; }; export const DOCS_OBJ_get = async function (SESSION_ID, idP) { if (!idP) return; if (idP === '_empty_panel_program') { return { _id: '_empty_panel_program', properties: { menuName: '_empty_panel_program', menuTitle: '_empty_panel_program', menuType: 'component', // uiFramework: "@xuda.io/xuda-framework-plugin-tailwind", renderType: 'form', }, progUi: [ { id: 'node-empty_panel_program', tagName: 'xu-single-view', children: [ { id: 'node-empty_panel_program-span', tagName: 'span', children: [], content: '', type: 'element', attributes: {}, }, ], content: '', type: 'element', attributes: {}, }, ], progDataSource: {}, progFields: [], progEvents: [], }; } var _session = _this.SESSION_OBJ[SESSION_ID]; const app_id = _session.app_id; var views_ret; if (_session.engine_mode === 'live_preview') { if (_session.is_draft_runtime) { return await get_doc_from_studio(SESSION_ID, app_id, idP); } if (!_session.live_preview_studio_doc_source_unavailable) { views_ret = await get_doc_from_studio_with_timeout(SESSION_ID, app_id, idP); if (views_ret) { return views_ret; } _session.live_preview_studio_doc_source_unavailable = true; } return await get_doc_from_runtime_cache_or_remote(SESSION_ID, app_id, idP); } if (glb.register_startup_programs) { func.common.db(SESSION_ID, 'register_startup_programs', { prog_id: idP, build_id: _session?.build_info?.build || 0, }); } return await get_doc_from_runtime_cache_or_remote(SESSION_ID, app_id, idP); }; export const save_objects_cache = async function (SESSION_ID, id, obj_type, data) { var _session = _this.SESSION_OBJ[SESSION_ID]; if (typeof _this.IS_DOCKER !== 'undefined' || typeof _this.IS_PROCESS_SERVER !== 'undefined' || _session.worker_type === 'Dev') { return; } var build_id = _session?.build_info?.build || 0; const db = await _this.func.utils.connect_pouchdb(SESSION_ID); var doc = { _id: `cache_${build_id}_${obj_type}_${id}`, id: id, docType: 'cache_objects', build_id: build_id.toString(), obj_type: obj_type, data: data, }; setTimeout(async function () { try { await db.get(doc_id); return; } catch (e) { if (await read_objects_cache(SESSION_ID, id, obj_type)) { return; } if (!doc.data || (doc.data && typeof doc.data === 'string' && doc.data.indexOf('error') > -1)) { return; } try { return await db.put(doc); } catch (err) { return; } } }, 1000); }; export const read_objects_cache = async function (SESSION_ID, id, obj_type) { if (typeof _this.IS_DOCKER !== 'undefined' || typeof _this.IS_PROCESS_SERVER !== 'undefined') { try { const data = fs.readFileSync('/var/xuda/progs/' + id + '.json', 'utf8'); return JSON.parse(data); } catch (err) { console.error('read_objects_cache', err); return; } } var _session = _this.SESSION_OBJ[SESSION_ID]; var build_id = _session?.build_info?.build || 0; const db = await _this.func.utils.connect_pouchdb(SESSION_ID); var ret; try { ret = await db.get(`cache_${build_id}_${obj_type}_${id}`); if (ret.data && typeof ret.data === 'string' && ret.data.indexOf('error') > -1) { return ''; } return ret.data; } catch (err) { return ret; } }; export const delete_objects_cache = async function (SESSION_ID, id, obj_type) { const app_id = _this.SESSION_OBJ[SESSION_ID].app_id; var build_id = _session?.build_info?.build || 0; const db = await _this.func.utils.connect_pouchdb(SESSION_ID); try { var doc = await db.get(`cache_${build_id}_${obj_type}_${id}`); if (!doc) return; var ret = await db.remove(doc); } catch (err) { // return; } return ret; }; export const load_objects_cache = async function (SESSION_ID) { var _session = _this.SESSION_OBJ[SESSION_ID]; if (typeof _this.IS_DOCKER !== 'undefined' || typeof _this.IS_PROCESS_SERVER !== 'undefined') { return; } const app_id = _session.app_id; const db = await _this.func.utils.connect_pouchdb(SESSION_ID); const build_id = _this.APP_OBJ[app_id].app_build_id; let ret = await db.find({ selector: { docType: 'cache_objects', build_id }, }); for (let val of ret.docs) { eval(val.obj_type)[_session.app_id][val.id] = val.data; } }; export const prime_objects_cache = async function (SESSION_ID, obj_type, data_by_id = {}) { var _session = _this.SESSION_OBJ[SESSION_ID]; if (typeof _this.IS_DOCKER !== 'undefined' || typeof _this.IS_PROCESS_SERVER !== 'undefined' || _session.worker_type === 'Dev') { return; } const ids = Object.keys(data_by_id || {}); if (!ids.length) { return; } const build_id = (_session?.build_info?.build || 0).toString(); const db = await _this.func.utils.connect_pouchdb(SESSION_ID); const cache_doc_ids = ids.map((id) => `cache_${build_id}_${obj_type}_${id}`); const existing_docs = await db.allDocs({ keys: cache_doc_ids }); const existing_revs = {}; for (const row of existing_docs.rows || []) { if (row?.value?.rev) { existing_revs[row.id] = row.value.rev; } } const docs = ids.map((id) => { const cache_id = `cache_${build_id}_${obj_type}_${id}`; const doc = { _id: cache_id, id, docType: 'cache_objects', build_id, obj_type, data: data_by_id[id], }; if (existing_revs[cache_id]) { doc._rev = existing_revs[cache_id]; } return doc; }); await db.bulkDocs(docs); for (const id of ids) { eval(obj_type)[_session.app_id][id] = data_by_id[id]; } };