@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
JavaScript
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];
}
};