Compare commits
2
Commits
1980d3044b
...
b94f18d2e2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b94f18d2e2 | ||
|
|
7cff93419d |
@@ -37,7 +37,16 @@ function copy(from, to) {
|
|||||||
fs.cpSync(from, to, { recursive: true });
|
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() {
|
function buildWeb() {
|
||||||
|
patchPlugin();
|
||||||
fs.rmSync(WWW, { recursive: true, force: true });
|
fs.rmSync(WWW, { recursive: true, force: true });
|
||||||
copy(path.join(__dirname, 'web'), WWW);
|
copy(path.join(__dirname, 'web'), WWW);
|
||||||
const nm = path.join(ROOT, 'node_modules', '@xterm');
|
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) => {
|
ipcMain.handle('dev:start', (_e, env) => {
|
||||||
Object.assign(process.env, env, { DATADIR: bridge.getDataPath() });
|
Object.assign(process.env, env, { DATADIR: bridge.getDataPath() });
|
||||||
require('fs').mkdirSync(process.env.DATADIR, { recursive: true });
|
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) => {
|
ipcMain.handle('dev:native', (_e, method, arg) => {
|
||||||
if (method === 'vaultKey') return { key: crypto.createHash('sha256').update(app.getPath('userData')).digest('base64') };
|
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 === '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', {
|
contextBridge.exposeInMainWorld('DevBridge', {
|
||||||
NodeJS: {
|
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),
|
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) }; },
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const id = ++seq;
|
const id = ++seq;
|
||||||
waits.set(id, { resolve, reject });
|
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); },
|
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] }) => {
|
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' }));
|
const info = await Native.info().catch(() => ({ device: 'Android', locale: navigator.language, version: '0.0.0' }));
|
||||||
S.info = info;
|
S.info = info;
|
||||||
I18N.setLanguage('auto', info.locale);
|
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();
|
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 } });
|
// start() kehrt erst zurück, wenn Node beendet wird – daher nicht darauf warten, sondern auf whenReady()
|
||||||
await Promise.race([ready, new Promise((r) => setTimeout(r, 8000))]);
|
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 {}
|
try { Object.assign(S.bio, await Native.bioAvailable()); } catch {}
|
||||||
Native.multicast({ on: true }).catch(() => {});
|
Native.multicast({ on: true }).catch(() => {});
|
||||||
$('#boot').hidden = true;
|
$('#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)
|
// 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); };
|
window.mrtermIme = (open) => { document.body.classList.toggle('kbd-open', open); setTimeout(fitViewport, 50); };
|
||||||
|
|
||||||
boot().catch((e) => {
|
// whenReady() wartet, bis die Node-Engine Nachrichten annimmt (vorher lehnt send() ab)
|
||||||
$('#boot').innerHTML = `<div style="color:var(--red);padding:24px;text-align:center">${esc(e.message || e)}</div>`;
|
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)));
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mrterm",
|
"name": "mrterm",
|
||||||
"version": "0.11.0",
|
"version": "0.11.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mrterm",
|
"name": "mrterm",
|
||||||
"version": "0.11.0",
|
"version": "0.11.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xterm/addon-fit": "^0.11.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mrterm",
|
"name": "mrterm",
|
||||||
"productName": "MrTerm",
|
"productName": "MrTerm",
|
||||||
"version": "0.11.0",
|
"version": "0.11.1",
|
||||||
"description": "Moderner SSH-, SFTP- und RDP-Client",
|
"description": "Moderner SSH-, SFTP- und RDP-Client",
|
||||||
"main": "src/main/main.js",
|
"main": "src/main/main.js",
|
||||||
"author": "MrBlake",
|
"author": "MrBlake",
|
||||||
|
|||||||
Reference in New Issue
Block a user