Android: fix app hanging on the loading screen (Node start only returns when Node exits, wait for whenReady instead), build the native bridge with 16 KB page alignment, show an error instead of an endless spinner
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,16 @@ function copy(from, to) {
|
||||
fs.cpSync(from, to, { recursive: true });
|
||||
}
|
||||
|
||||
// capacitor-nodejs baut seine Brückenbibliothek mit 4-KB-Seitenausrichtung; neuere Geräte (z. B. Pixel mit
|
||||
// 16-KB-Speicherseiten) laden sie dann nicht. Linker-Option einmalig ergänzen.
|
||||
function patchPlugin() {
|
||||
const f = path.join(__dirname, 'node_modules', 'capacitor-nodejs', 'android', 'CMakeLists.txt');
|
||||
const s = fs.readFileSync(f, 'utf8');
|
||||
if (!s.includes('max-page-size=16384')) fs.writeFileSync(f, `${s}\ntarget_link_options(native-lib PRIVATE "-Wl,-z,max-page-size=16384")\n`);
|
||||
}
|
||||
|
||||
function buildWeb() {
|
||||
patchPlugin();
|
||||
fs.rmSync(WWW, { recursive: true, force: true });
|
||||
copy(path.join(__dirname, 'web'), WWW);
|
||||
const nm = path.join(ROOT, 'node_modules', '@xterm');
|
||||
|
||||
@@ -26,8 +26,10 @@ ipcMain.on('dev:send', (_e, name, args) => fromWeb.emit(name, ...args));
|
||||
ipcMain.handle('dev:start', (_e, env) => {
|
||||
Object.assign(process.env, env, { DATADIR: bridge.getDataPath() });
|
||||
require('fs').mkdirSync(process.env.DATADIR, { recursive: true });
|
||||
require(path.join(WWW, 'nodejs', 'index.js'));
|
||||
setTimeout(() => { started = true; require(path.join(WWW, 'nodejs', 'index.js')); }, 500);
|
||||
});
|
||||
let started;
|
||||
ipcMain.handle('dev:ready', async () => { while (!started) await new Promise((r) => setTimeout(r, 100)); });
|
||||
ipcMain.handle('dev:native', (_e, method, arg) => {
|
||||
if (method === 'vaultKey') return { key: crypto.createHash('sha256').update(app.getPath('userData')).digest('base64') };
|
||||
if (method === 'info') return { device: 'Dev Phone', locale: process.env.MRTERM_DEV_LOCALE || 'en-US', version: require('../../package.json').version };
|
||||
|
||||
@@ -6,7 +6,9 @@ const native = (m) => (arg) => ipcRenderer.invoke('dev:native', m, arg);
|
||||
|
||||
contextBridge.exposeInMainWorld('DevBridge', {
|
||||
NodeJS: {
|
||||
start: (o) => ipcRenderer.invoke('dev:start', o.env),
|
||||
// Wie auf Android: start() kehrt erst zurück, wenn Node beendet wird
|
||||
start: (o) => { ipcRenderer.invoke('dev:start', o.env); return new Promise(() => {}); },
|
||||
whenReady: () => ipcRenderer.invoke('dev:ready'),
|
||||
send: ({ eventName, args }) => ipcRenderer.send('dev:send', eventName, args),
|
||||
addListener: (name, fn) => { if (!listeners.has(name)) listeners.set(name, new Set()); listeners.get(name).add(fn); return { remove: () => listeners.get(name).delete(fn) }; },
|
||||
},
|
||||
|
||||
+25
-8
@@ -22,10 +22,10 @@ const api = {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = ++seq;
|
||||
waits.set(id, { resolve, reject });
|
||||
NodeJS.send({ eventName: 'call', args: [id, ch, args] });
|
||||
Promise.resolve(NodeJS.send({ eventName: 'call', args: [id, ch, args] })).catch((e) => { waits.delete(id); reject(e); });
|
||||
});
|
||||
},
|
||||
send(ch, ...args) { NodeJS.send({ eventName: 'send', args: [ch, args] }); },
|
||||
send(ch, ...args) { NodeJS.send({ eventName: 'send', args: [ch, args] }).catch?.((e) => console.error(e)); },
|
||||
on(ch, fn) { if (!events.has(ch)) events.set(ch, new Set()); events.get(ch).add(fn); return () => events.get(ch).delete(fn); },
|
||||
};
|
||||
NodeJS.addListener('reply', ({ args: [id, ok, value] }) => {
|
||||
@@ -174,10 +174,15 @@ async function boot() {
|
||||
const info = await Native.info().catch(() => ({ device: 'Android', locale: navigator.language, version: '0.0.0' }));
|
||||
S.info = info;
|
||||
I18N.setLanguage('auto', info.locale);
|
||||
const ready = new Promise((r) => { const off = NodeJS.addListener('ready', () => r()); void off; });
|
||||
const ready = new Promise((r) => NodeJS.addListener('ready', () => r(true)));
|
||||
const { key } = await Native.vaultKey();
|
||||
await NodeJS.start({ env: { MRTERM_VAULT_KEY: key, MRTERM_DEVICE: info.device, MRTERM_LOCALE: info.locale, MRTERM_VERSION: info.version } });
|
||||
await Promise.race([ready, new Promise((r) => setTimeout(r, 8000))]);
|
||||
// start() kehrt erst zurück, wenn Node beendet wird – daher nicht darauf warten, sondern auf whenReady()
|
||||
NodeJS.start({ env: { MRTERM_VAULT_KEY: key, MRTERM_DEVICE: info.device, MRTERM_LOCALE: info.locale, MRTERM_VERSION: info.version } })
|
||||
.catch((e) => { if (!/already been started/.test(e.message)) bootError(`Node.js: ${e.message}`); });
|
||||
const timeout = (ms, what) => new Promise((_, rej) => setTimeout(() => rej(new Error(what)), ms));
|
||||
await Promise.race([whenReady(), timeout(30000, 'The Node.js engine did not start.')]);
|
||||
// Backend meldet sich mit 'ready'; nach einem Neuladen der Oberfläche läuft es schon → Ping genügt
|
||||
await Promise.race([ready, api.call('app:version'), timeout(20000, 'The MrTerm backend did not respond.')]);
|
||||
try { Object.assign(S.bio, await Native.bioAvailable()); } catch {}
|
||||
Native.multicast({ on: true }).catch(() => {});
|
||||
$('#boot').hidden = true;
|
||||
@@ -1015,6 +1020,18 @@ window.mrtermBack = onBack; // wird von MainActivity bei der Zurück-Taste aufge
|
||||
// MainActivity meldet die Bildschirmtastatur (WebView wird dann um ihre Höhe verkleinert)
|
||||
window.mrtermIme = (open) => { document.body.classList.toggle('kbd-open', open); setTimeout(fitViewport, 50); };
|
||||
|
||||
boot().catch((e) => {
|
||||
$('#boot').innerHTML = `<div style="color:var(--red);padding:24px;text-align:center">${esc(e.message || e)}</div>`;
|
||||
});
|
||||
// whenReady() wartet, bis die Node-Engine Nachrichten annimmt (vorher lehnt send() ab)
|
||||
async function whenReady() {
|
||||
for (;;) {
|
||||
try { await NodeJS.whenReady(); return; } catch (e) {
|
||||
if (!/not been started/.test(e.message)) throw e;
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
}
|
||||
}
|
||||
}
|
||||
function bootError(msg) {
|
||||
const b = $('#boot');
|
||||
if (b.hidden) return toast(msg, 'error');
|
||||
b.innerHTML = `<img src="logo.png" alt=""/><div style="color:var(--red);padding:0 24px;text-align:center;max-width:420px">${esc(msg)}</div><div style="color:var(--muted);padding:0 24px;text-align:center;font-size:13px">Android ${esc(navigator.userAgent.match(/Android ([\d.]+)/)?.[1] || '')} · ${esc(S.info?.device || '')} · ${esc(S.info?.version || '')}</div>`;
|
||||
}
|
||||
boot().catch((e) => bootError(e.message || String(e)));
|
||||
|
||||
Reference in New Issue
Block a user