Settings gains an Updates block: it asks the church Gitea for the newest release, compares the tag against the running build, and if there is a newer one shows what changed and offers to download the installer. Deliberately not electron-updater. That wants a static feed published beside the artifacts, and this build is ad-hoc signed with no Team ID, so a silent in-place swap would meet Gatekeeper on a machine nobody is sitting at. And nothing here happens on its own: a background check that announces itself three minutes into a sermon is a hazard, so the operator presses the button, and presses a second one to download. The file lands in Downloads and is revealed; installing is a drag across when the room is empty. Tags are v<version>-<build>, compared numerically field by field so 3.9.0 does not read as newer than 3.23.2 and build 113 beats build 112. Two things only real testing found. Gitea answers with whatever address it was reached on, so the release API returned asset URLs on 10.3.8.90 while the forge is configured by hostname — the same-host guard refused a perfectly good installer. The URL is now rebuilt on the configured origin, which keeps the property that matters (this only ever fetches from the server the operator configured) and survives a forge that knows itself by more than one name. And a private repository answers 404 rather than 401, so 'no releases published yet' told the operator the opposite of the truth; without a token that case now names the remedy instead. The token is this machine's credential: it stays out of PORTABLE_APP, so it does not ride along in an exported settings file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
62 lines
3.5 KiB
JavaScript
62 lines
3.5 KiB
JavaScript
/*
|
|
* preload.js — the only bridge between the renderer and Node.
|
|
* Nothing here takes a path or a command from the page; every call is a fixed
|
|
* verb with no free-form arguments beyond a config patch.
|
|
*/
|
|
'use strict';
|
|
|
|
const { contextBridge, ipcRenderer } = require('electron');
|
|
|
|
contextBridge.exposeInMainWorld('engineHost', {
|
|
getConfig: () => ipcRenderer.invoke('config:get'),
|
|
setConfig: (patch) => ipcRenderer.invoke('config:set', patch),
|
|
micStatus: () => ipcRenderer.invoke('mic:status'),
|
|
askMic: () => ipcRenderer.invoke('mic:ask'),
|
|
openPrivacy: () => ipcRenderer.invoke('app:openPrivacy'),
|
|
cacheInfo: () => ipcRenderer.invoke('app:cacheInfo'),
|
|
revealCache: () => ipcRenderer.invoke('app:revealCache'),
|
|
copyText: (text) => ipcRenderer.invoke('app:copy', text),
|
|
setLive: (live) => ipcRenderer.send('engine:live', !!live),
|
|
logLine: (line) => ipcRenderer.send('log:line', String(line || '')),
|
|
|
|
/* Translation lives in the main process; these are its only two verbs. */
|
|
mtEnsure: () => ipcRenderer.invoke('mt:ensure'),
|
|
updateCheck: () => ipcRenderer.invoke('update:check'),
|
|
updateDownload: (u, n) => ipcRenderer.invoke('update:download', u, n),
|
|
updateReveal: (p) => ipcRenderer.invoke('update:reveal', p),
|
|
onUpdateProgress: (fn) => ipcRenderer.on('update:progress', (_e, p) => fn(p)),
|
|
settingsExport: () => ipcRenderer.invoke('settings:export'),
|
|
settingsImport: () => ipcRenderer.invoke('settings:import'),
|
|
audioDevices: () => ipcRenderer.invoke('audio:devices'),
|
|
modelsCached: (id) => ipcRenderer.invoke('models:cached', id),
|
|
mtTranslate: (req) => ipcRenderer.invoke('mt:translate', req),
|
|
mtTranslateMulti: (req) => ipcRenderer.invoke('mt:translateMulti', req),
|
|
onMtProgress: (fn) => ipcRenderer.on('mt:progress', (_e, p) => fn(p)),
|
|
onMtDown: (fn) => ipcRenderer.on('mt:down', (_e, info) => fn(info)),
|
|
|
|
/* The congregation server runs inside this app; these describe it. */
|
|
localInfo: () => ipcRenderer.invoke('local:info'),
|
|
onServerUp: (fn) => ipcRenderer.on('server:up', (_e, info) => fn(info)),
|
|
onServerDown: (fn) => ipcRenderer.on('server:down', () => fn()),
|
|
|
|
/* Share to the internet: a Cloudflare quick tunnel for livestream viewers. */
|
|
tunnelStart: () => ipcRenderer.invoke('tunnel:start'),
|
|
tunnelStop: () => ipcRenderer.invoke('tunnel:stop'),
|
|
tunnelStatus: () => ipcRenderer.invoke('tunnel:status'),
|
|
onTunnel: (fn) => ipcRenderer.on('tunnel:status', (_e, s) => fn(s)),
|
|
|
|
/* Built-in voices: this Mac speaks, and the phones play what it sends. */
|
|
ttsInfo: () => ipcRenderer.invoke('tts:info'),
|
|
ttsSpeak: (req) => ipcRenderer.invoke('tts:speak', req),
|
|
ttsPreload: (langs) => ipcRenderer.invoke('tts:preload', langs),
|
|
/* Audition one specific voice, and save the per-language choices. */
|
|
ttsAudition: (req) => ipcRenderer.invoke('tts:audition', req),
|
|
ttsSetVoices: (c) => ipcRenderer.invoke('tts:setVoices', c),
|
|
ttsSetListenerVoices: (m) => ipcRenderer.invoke('tts:setListenerVoices', m),
|
|
onTtsReady: (fn) => ipcRenderer.on('tts:ready', (_e, i) => fn(i)),
|
|
onTtsProgress: (fn) => ipcRenderer.on('tts:progress', (_e, p) => fn(p)),
|
|
onTtsCloud: (fn) => ipcRenderer.on('tts:cloud', (_e, s) => fn(s)),
|
|
onLocalVoices: (fn) => ipcRenderer.on('localvoices:status', (_e, s) => fn(s)),
|
|
localVoicesStatus: () => ipcRenderer.invoke('localvoices:status')
|
|
});
|