Files
cinny/src/app/utils/updateErrors.ts
T
Lotus CIandClaude Opus 5.5 6acdd439d5
CI / Build & Quality Checks (pull_request) Successful in 1m44s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 6s
CI / Playwright smoke (e2e) (pull_request) Successful in 10m38s
fix(desktop updates): pacman command downloads first, installs the local file
`sudo pacman -U <url>` also fetches `<url>.sig` and failed (404) on
CachyOS: remote packages fall under RemoteFileSigLevel (signature
required) and we don't sign the package. A downloaded file installs under
LocalFileSigLevel (optional): `curl -LO <url> && sudo pacman -U ./…`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-30 19:57:12 -04:00

93 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** 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 <url>` also
// fetches `<url>.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);