From 817fb743de181fe5f650eefd121c8f837ba68668 Mon Sep 17 00:00:00 2001 From: Waters Church Date: Thu, 15 Oct 2026 13:40:30 -0400 Subject: [PATCH] Show the domain, not the LAN address, once one is set Build 122. The operator page honoured Public address and the main app window did not, so the two disagreed about the one fact they both exist to display: translate.watersmedia.org was configured and working through nginx, while the window, the QR code, the poster and Copy address all still handed out http://10.1.7.39:3100. engine.js had no mention of publicUrl at all. It now reads it from the server config it was already fetching, and prefers it everywhere the congregation is pointed. The operator link deliberately does not follow. /operator returns 404 to anything carrying X-Forwarded-For, so over a proxy the domain is exactly where that page cannot be reached; it stays on the LAN address, which is the whole point of being able to run the desk from a phone. The QR warnings are suppressed when a public address is set. They talk about Wi-Fi being down and leases moving, which are facts about an address nobody is being given any more; the panel now names the public address and reminds the desk that this Mac still has to be reachable from the proxy. The share panel also re-renders when the server config arrives. It is fetched asynchronously and lost a race at startup, so the first paint had no publicUrl to read even once this was wired up. Co-Authored-By: Claude Opus 5 --- app/engine.js | 43 +++++++++++++++++++++++++++++++++++++++++-- app/package.json | 4 ++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/app/engine.js b/app/engine.js index 8bd6f05..6387710 100644 --- a/app/engine.js +++ b/app/engine.js @@ -3508,14 +3508,50 @@ function shareChoice() { }; } -function shareBase() { +/* This Mac on the church network. The operator page lives here and NOWHERE + else: it returns 404 to anything arriving through a proxy, by design. */ +function lanBase() { const c = shareChoice(); if (!c || c.none) return ''; return 'http://' + c.addr.address + ':' + state.local.port; } +/* + * WHAT THE CONGREGATION OPENS. + * + * A church that has put this on a domain has said so in the operator page's + * Public address, and from that moment the LAN address is the wrong thing to + * print: it is not what is on the poster, not what the QR should encode, and + * not what someone off the building's wifi can reach. The app window went on + * showing the IP anyway, because this half of it never read that setting -- + * the operator page honoured it and the main window did not, so the two + * disagreed about the one fact they both exist to display. + */ +function publicBaseConfigured() { + const u = opConfig && typeof opConfig.publicUrl === 'string' ? opConfig.publicUrl.trim() : ''; + return u ? u.replace(/\/+$/, '') : ''; +} + +function shareBase() { + return publicBaseConfigured() || lanBase(); +} + function renderQrWarning() { const el = $('qrWarn'); + /* + * On a domain the warnings below are about an address nobody is being + * given any more. Saying "Wi-Fi is down, phones cannot reach the code" + * while the code points at a proxy would send the desk after the wrong + * thing entirely. + */ + const pub = publicBaseConfigured(); + if (pub) { + el.hidden = false; + el.textContent = 'The code points at ' + pub + ', set as the public address. ' + + 'This Mac still has to be reachable from there; its own address is ' + + (lanBase() || 'unavailable') + '.'; + return; + } const c = shareChoice(); if (!c) { el.hidden = true; return; } @@ -3665,7 +3701,8 @@ function renderBuildTag() { } function renderOperatorUrl() { - const base = shareBase(); + /* Deliberately lanBase, not shareBase: over a proxy /operator is a 404. */ + const base = lanBase(); $('opPageUrl').textContent = base ? base + '/operator' : 'no network'; } @@ -3968,6 +4005,7 @@ async function loadOperator() { try { const j = await opApi('/api/config'); opConfig = j.config; + renderShare(); /* publicUrl may have just arrived */ clearTimeout(opLoadTimer); opLoadTimer = null; // it answered; stop retrying opRetryMs = 0; renderOpLanguages(); @@ -4022,6 +4060,7 @@ async function opSave(patch, okMsg) { try { const j = await opApi('/api/config', { method: 'POST', body: JSON.stringify(patch) }); opConfig = j.config; + renderShare(); /* publicUrl may have just arrived */ if (okMsg) log(okMsg, 'ok'); return j; } catch (e) { log('could not save: ' + e.message, 'err'); throw e; } diff --git a/app/package.json b/app/package.json index cb1a4ca..e2ff77e 100644 --- a/app/package.json +++ b/app/package.json @@ -2,7 +2,7 @@ "name": "church-translate-engine", "productName": "Church Translate", "version": "3.23.2", - "buildNumber": 121, + "buildNumber": 122, "private": true, "description": "Live sermon translation — self-contained: captures audio, transcribes, translates, and serves the congregation page itself", "main": "main.js", @@ -106,7 +106,7 @@ ] }, "asar": false, - "buildVersion": "121", + "buildVersion": "122", "win": { "target": [ {