// Node-Backend der Android-App. Die Kanäle kommen aus src/core/backend.js (gemeinsam mit der Web-Version); // hier nur, was Android betrifft: Bridge-Anbindung, Entsperren per Fingerabdruck, Pause/Fortsetzen. // Protokoll: Oberfläche → 'call' [reqId, Kanal, Argumente] bzw. 'send' [Kanal, Argumente]; // Backend → 'reply' [reqId, ok, Wert|Fehler] und 'evt' [Kanal, Argumente]. const { channel } = require('bridge'); const crypto = require('crypto'); const { Store } = require('../../src/main/store'); const { createBackend } = require('../../src/core/backend'); const i18n = require('../../src/i18n'); const send = (ch, ...args) => channel.send('evt', ch, args); process.on('uncaughtException', (e) => { console.error(e); send('toast', e?.message || String(e), 'error'); }); process.on('unhandledRejection', (e) => console.error('Unhandled rejection:', e)); const store = new Store(); const applyLanguage = (lang = store.get().settings.language) => i18n.setLanguage(lang, process.env.MRTERM_LOCALE || 'en'); const be = createBackend({ store, send, platform: 'android', version: process.env.MRTERM_VERSION || '0.0.0', withSync: true, onLanguage: applyLanguage }); channel.addListener('call', async (reqId, ch, args = []) => { try { channel.send('reply', reqId, true, await be.call(ch, args)); } catch (e) { channel.send('reply', reqId, false, e?.message || String(e)); } }); channel.addListener('send', (ch, args = []) => be.notify(ch, args)); // ---------- Fingerabdruck (Geheimnis aus dem Android Keystore, nur zusätzlich zum Passwort) ---------- const bioKek = (b64) => Buffer.from(crypto.hkdfSync('sha256', Buffer.from(b64, 'base64'), Buffer.alloc(0), 'mrterm-bio-kek', 32)); be.handle('lock:unlockBio', (secret) => { if (!store.lock?.bio) throw new Error(i18n.t('Fingerprint unlock is not set up.')); try { store.unlockWith(bioKek(secret), store.lock.bio.wrap); } catch { throw new Error(i18n.t('Fingerprint unlock failed. Use your password.')); } applyLanguage(); be.sync.start().catch(() => {}); return true; }); be.handle('lock:setBio', (secret) => { be.requireUnlocked(); if (!store.lock?.password) throw new Error(i18n.t('Set a password first.')); store.lock.bio = secret ? { wrap: store.wrapDek(bioKek(secret)) } : null; store.save(); return be.lockStatus(); }); // App im Hintergrund: Sync-Sockets schließen, im Vordergrund wieder öffnen be.handle('app:pause', () => be.sync.stop()); be.handle('app:resume', () => be.sync.start().catch(() => {})); store.load(); applyLanguage(); be.sync.start().catch(() => {}); channel.send('ready');