// Entwicklung ohne Handy: startet die gebaute Oberfläche (www/) in einem Fenster in Handygröße // und das Node-Backend im Electron-Hauptprozess, verbunden über eine nachgebaute Capacitor-Bridge. // cd mobile && node build.js && ../node_modules/.bin/electron dev/electron-dev.js [--user-data-dir=…] const { app, BrowserWindow, ipcMain, clipboard } = require('electron'); const path = require('path'); const Module = require('module'); const EventEmitter = require('events'); const crypto = require('crypto'); const WWW = path.join(__dirname, '..', 'www'); const toWeb = new EventEmitter(); const fromWeb = new EventEmitter(); let win; // require('bridge') im Backend auf diese Nachbildung umleiten const bridge = { channel: { send: (name, ...args) => toWeb.emit('msg', name, args), addListener: (name, fn) => fromWeb.on(name, fn) }, getDataPath: () => path.join(app.getPath('userData'), 'mobile-data'), }; const resolve = Module._resolveFilename; Module._resolveFilename = function (req, ...rest) { return req === 'bridge' ? 'bridge' : resolve.call(this, req, ...rest); }; require.cache.bridge = { id: 'bridge', filename: 'bridge', loaded: true, exports: bridge }; toWeb.on('msg', (name, args) => { if (win && !win.isDestroyed()) win.webContents.send('dev:node', name, args); }); 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 }); 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 }; if (method === 'bioAvailable') return { available: true, enrolled: false }; if (method === 'bioEnroll' || method === 'bioUnlock') return { secret: Buffer.alloc(32, 1).toString('base64') }; if (method === 'copy') return clipboard.writeText(arg.text); if (method === 'paste') return { text: clipboard.readText() }; return {}; }); app.whenReady().then(() => { win = new BrowserWindow({ width: 412, height: 870, backgroundColor: '#14161d', webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true }, }); win.loadFile(path.join(WWW, 'index.html')); if (process.env.MRTERM_SHOT) { const steps = JSON.parse(require('fs').readFileSync(process.env.MRTERM_SHOT, 'utf8')); win.webContents.on('console-message', (e) => console.log('[r]', e.message)); win.webContents.once('did-finish-load', async () => { await new Promise((r) => setTimeout(r, 2500)); for (const s of steps) { if (s.js) await win.webContents.executeJavaScript(`(async()=>{${s.js}})();0`).catch((e) => console.log('js', e.message)); await new Promise((r) => setTimeout(r, s.wait || 900)); if (s.shot) require('fs').writeFileSync(path.join(process.env.MRTERM_SHOT_OUT, s.shot), (await win.webContents.capturePage()).toPNG()); } app.exit(0); }); } }); app.on('window-all-closed', () => app.exit(0));