| … | … | @@ -1428,7 +1428,57 @@ export function reachSettings (reach) { |
| 1428 | 1428 | '<button>save</button></form>' |
| 1429 | 1429 | } |
| 1430 | 1430 | |
| 1431 | | export function settings (profile, message, handle = null, namespace = null, token = null, theme = null, reach = null) { |
| 1431 | |
| 1432 | // ── app updates ────────────────────────────────────────────────────────────────────────────────── |
| 1433 | // |
| 1434 | // ⛔ RENDERED FROM A FILE THE SHELL WROTE, not from anything this surface knows. favela-surface has no |
| 1435 | // idea whether it is inside an Android worklet or on a desktop, and must not learn: the shell writes |
| 1436 | // `app-update.json`, the page reads it, and neither knows the other's process model. |
| 1437 | // |
| 1438 | // ⛔ AND THE CONTROLS FOLLOW THE SHELL'S CAPABILITY, not our own optimism. `ota` and `pair` are |
| 1439 | // separate flags for a reason recorded in shell-handoff.js: the page arrives by OTA and the shell by |
| 1440 | // APK, so "can service update buttons" is answered by whatever shell is actually running. A page that |
| 1441 | // renders a control the shell beneath it cannot honour is worse than one that renders nothing. |
| 1442 | export function updateSettings (u) { |
| 1443 | if (!u) return '' |
| 1444 | const short = (h) => (h ? esc(String(h).slice(0, 12)) + '…' : '<span class="who">unknown</span>') |
| 1445 | const checked = u.checkedAt ? '<p class="who">Last checked ' + esc(u.checkedAt) + '</p>' : '' |
| 1446 | |
| 1447 | // ⛔ STAGED AND DIFFERENT is the only state that means "an update is waiting". Staged and IDENTICAL |
| 1448 | // is the ordinary case — the node re-verified the bundle it is already running — and calling that |
| 1449 | // "update available" is what makes a restart button lie. |
| 1450 | const waiting = u.staged && u.running && u.staged !== u.running |
| 1451 | const body = waiting |
| 1452 | ? '<p class="warn">An update is downloaded and will apply on the next start.</p>' + |
| 1453 | '<p class="who">running ' + short(u.running) + ' → staged ' + short(u.staged) + '</p>' + |
| 1454 | (u.ota ? '<p><button id="app-restart">restart now</button></p>' : '<p class="who">This app cannot restart itself; close and reopen it.</p>') |
| 1455 | : '<p>Up to date.</p><p class="who">running ' + short(u.running) + '</p>' |
| 1456 | |
| 1457 | return '<h2>App updates</h2>' + body + checked + |
| 1458 | (u.ota ? '<p><button id="app-check">check now</button> <span id="app-msg" class="who"></span></p>' : '') + |
| 1459 | (u.ota ? '<script type="module">\n' + UPDATE_JS + '\n</script>' : '') |
| 1460 | } |
| 1461 | |
| 1462 | // ⛔ THE ONLY THING THIS POSTS IS A VERB. `parseShellMessage` on the other side is an allow-list of |
| 1463 | // three names and nothing else — no op passed through to a handler map, which is how a WebView bridge |
| 1464 | // turns into a remote-call surface for anything that reaches the page. |
| 1465 | const UPDATE_JS = ` |
| 1466 | const post = (op) => { |
| 1467 | try { window.ReactNativeWebView.postMessage(JSON.stringify({ t: 'app-update', op })) } |
| 1468 | catch { const m = document.getElementById('app-msg'); if (m) m.textContent = 'this app cannot do that' } |
| 1469 | } |
| 1470 | const check = document.getElementById('app-check') |
| 1471 | if (check) check.addEventListener('click', (e) => { |
| 1472 | e.preventDefault() |
| 1473 | const m = document.getElementById('app-msg') |
| 1474 | if (m) m.textContent = 'checking…' |
| 1475 | post('check') |
| 1476 | }) |
| 1477 | const restart = document.getElementById('app-restart') |
| 1478 | if (restart) restart.addEventListener('click', (e) => { e.preventDefault(); post('restart') }) |
| 1479 | ` |
| 1480 | |
| 1481 | export function settings (profile, message, handle = null, namespace = null, token = null, theme = null, reach = null, update = null) { |
| 1432 | 1482 | const p = profile || {} |
| 1433 | 1483 | const note = message ? '<p class="warn">' + esc(message) + '</p>' : '' |
| 1434 | 1484 | const shown = p.avatar |
| … | … | @@ -1439,7 +1489,7 @@ export function settings (profile, message, handle = null, namespace = null, tok |
| 1439 | 1489 | const shownBanner = p.banner |
| 1440 | 1490 | ? '<p><img class="banner-lg" src="/avatar/' + esc(p.banner) + '" alt="your banner"></p>' |
| 1441 | 1491 | : '' |
| 1442 | | return layout('settings', note + reachSettings(reach) + |
| 1492 | return layout('settings', note + updateSettings(update) + reachSettings(reach) + |
| 1443 | 1493 | '<h1>Settings</h1>' + shown + |
| 1444 | 1494 | '<form method="post" action="/settings" enctype="multipart/form-data">' + |
| 1445 | 1495 | '<p><label>Display name<br><input type="text" name="name" value="' + esc(p.name || '') + |