/** Which step of the desktop update failed (the native side prefixes its errors). */ export type UpdatePhase = 'check' | 'download' | 'install'; /** Where to get the installer by hand when the in-app update keeps failing. */ export const MANUAL_DOWNLOAD_URL = { windows: 'https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/download/latest/LotusChat-x86_64-setup.exe', other: 'https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/tag/latest', }; export const manualDownloadUrl = (userAgent: string): string => /Windows/i.test(userAgent) ? MANUAL_DOWNLOAD_URL.windows : MANUAL_DOWNLOAD_URL.other; const NETWORK_PATTERN = /error sending request|timed out|timeout|connection|connect|dns|network|reset|refused|unreachable|Could not fetch a valid release/i; export const isNetworkUpdateError = (message: string): boolean => NETWORK_PATTERN.test(message); /** Split `download: error sending request …` into its phase and the raw message. */ export const parseUpdateError = ( raw: string, fallback: UpdatePhase, ): { phase: UpdatePhase; message: string } => { const match = /^(check|download|install): ([\s\S]*)$/.exec(raw); return match ? { phase: match[1] as UpdatePhase, message: match[2] } : { phase: fallback, message: raw }; }; /** * A plain-language sentence for an update failure. The raw error ("error * sending request for url (…nsis.zip)") told users nothing about what to do. */ export const describeUpdateError = (phase: UpdatePhase, message: string): string => { const network = isNetworkUpdateError(message); if (phase === 'check') { return network ? 'Couldn’t reach the update server. It may be busy, or your connection dropped. Lotus Chat still works; try again in a minute.' : 'Couldn’t check for updates. Lotus Chat still works; try again later.'; } if (phase === 'download') { return network ? 'The update didn’t finish downloading after several tries. The update server may be busy. Try again in a minute, or download the installer yourself.' : /signature|minisign|base64/i.test(message) ? 'The downloaded update didn’t pass its safety check, so it wasn’t installed. Try again, or download the installer yourself.' : 'The update couldn’t be downloaded. Try again, or download the installer yourself.'; } return 'The update downloaded but couldn’t be installed. Download the installer yourself and run it; your chats and settings are kept.'; }; /** * How this desktop install gets updated (cinny-desktop `update_install_kind`). * Linux package installs can't be replaced in place by the in-app updater * (it tried to write into /usr/bin: "Permission denied (os error 13)"), so * they get their package manager's command instead of an Install button. */ export type InstallKind = 'in-app' | 'pacman' | 'deb' | 'manual'; const RELEASE_DOWNLOAD = 'https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/download/latest'; export const toInstallKind = (value: unknown): InstallKind => value === 'pacman' || value === 'deb' || value === 'manual' ? value : 'in-app'; export type PackageUpdateHelp = { command?: string; url: string; download: string }; /** The desktop updater's minisign public key (cinny-desktop tauri.conf.json). */ export const RELEASE_MINISIGN_KEY = 'RWRZZdyii05/NUcz337A5SAbiZK+NPVDWuiL2mM5Jk1p/LfRmNfiZ/6l'; /** * [cinny-desktop #36] Download a Linux package and install it only after * checking the release's minisign-signed SHA256SUMS.txt. Runs in a fresh temp * directory so a stale SHA256SUMS.txt can't be picked up. */ const verifiedInstall = (file: string, install: string): string => [ `cd "$(mktemp -d)"`, `curl -fLO ${RELEASE_DOWNLOAD}/${file} -fLO ${RELEASE_DOWNLOAD}/SHA256SUMS.txt -fLO ${RELEASE_DOWNLOAD}/SHA256SUMS.txt.minisig`, `minisign -Vm SHA256SUMS.txt -P ${RELEASE_MINISIGN_KEY}`, `sha256sum -c --ignore-missing SHA256SUMS.txt`, `${install} ./${file}`, ].join(' && '); export const packageUpdateHelp = (kind: InstallKind): PackageUpdateHelp | undefined => { if (kind === 'pacman') { // Install the local file: `pacman -U ` also fetches `.sig` and // fails without it (packages aren't signed for pacman), while a local file // falls under LocalFileSigLevel. Integrity comes from SHA256SUMS.txt. return { command: verifiedInstall('LotusChat-x86_64.pkg.tar.zst', 'sudo pacman -U'), url: `${RELEASE_DOWNLOAD}/LotusChat-x86_64.pkg.tar.zst`, download: 'Download package', }; } if (kind === 'deb') { return { command: verifiedInstall('LotusChat-x86_64.deb', 'sudo apt install'), url: `${RELEASE_DOWNLOAD}/LotusChat-x86_64.deb`, download: 'Download package', }; } if (kind === 'manual') return { url: MANUAL_DOWNLOAD_URL.other, download: 'Open downloads' }; return undefined; }; /** The native side refuses an in-app install on a package install. */ export const isPackageManagedError = (message: string): boolean => /package-managed/.test(message);