/** 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 }; export const packageUpdateHelp = (kind: InstallKind): PackageUpdateHelp | undefined => { if (kind === 'pacman') { const url = `${RELEASE_DOWNLOAD}/LotusChat-x86_64.pkg.tar.zst`; // Download first, then install the local file: `pacman -U ` also // fetches `.sig` and fails without it (we don't sign packages), // while a local file falls under LocalFileSigLevel (signature optional). return { command: `curl -LO ${url} && sudo pacman -U ./LotusChat-x86_64.pkg.tar.zst`, url, download: 'Download package', }; } if (kind === 'deb') { const url = `${RELEASE_DOWNLOAD}/LotusChat-x86_64.deb`; return { command: `curl -LO ${url} && sudo apt install ./LotusChat-x86_64.deb`, url, 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);