feat(desktop): update failures say what happened and what to do
CI / Secret scan (gitleaks) (push) Successful in 15s
CI / Build & Quality Checks (push) Canceled after 53s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s
CI / Secret scan (gitleaks) (push) Successful in 15s
CI / Build & Quality Checks (push) Canceled after 53s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s
A friend's update failed ten times in a row ("Update check failed: error
sending request for url (…nsis.zip)") before the 11th went through, and
he didn't know what to do. The label was also wrong: the check had
worked; the download failed.
- Progress: "Downloading update… 28% (14.3 MB of 49.9 MB)", "The update
server didn't respond. Trying again in 3 s (attempt 2 of 4)…", from the
native `lotus-update-progress` events (cinny-desktop retries itself).
- Failures name the step (check / download / install, from the native
error prefix) in plain language, with Try again and a Download
installer button (Windows: the setup .exe; else the release page), and
the raw error under "Details".
- Installing from the update toast now shows a "Downloading update"
toast, and on failure a sticky "Update didn't install" toast that
retries on click and points at Settings → General → App Updates.
Before, the toast vanished and the failure was only visible in Settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
23649f1255
commit
568f218fe9
@@ -0,0 +1,57 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
describeUpdateError,
|
||||
manualDownloadUrl,
|
||||
MANUAL_DOWNLOAD_URL,
|
||||
parseUpdateError,
|
||||
} from './updateErrors';
|
||||
|
||||
test('parseUpdateError reads the native phase prefix', () => {
|
||||
assert.deepEqual(parseUpdateError('download: error sending request for url (x)', 'check'), {
|
||||
phase: 'download',
|
||||
message: 'error sending request for url (x)',
|
||||
});
|
||||
assert.deepEqual(parseUpdateError('install: access denied', 'download'), {
|
||||
phase: 'install',
|
||||
message: 'access denied',
|
||||
});
|
||||
});
|
||||
|
||||
test('parseUpdateError falls back to the caller phase for unprefixed errors', () => {
|
||||
assert.deepEqual(parseUpdateError('error sending request', 'check'), {
|
||||
phase: 'check',
|
||||
message: 'error sending request',
|
||||
});
|
||||
});
|
||||
|
||||
test('the reported download failure reads as a busy server, not a failed check', () => {
|
||||
const { phase, message } = parseUpdateError(
|
||||
'download: error sending request for url (https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/download/latest/LotusChat-x86_64-setup.nsis.zip)',
|
||||
'check',
|
||||
);
|
||||
const text = describeUpdateError(phase, message);
|
||||
assert.match(text, /didn’t finish downloading/);
|
||||
assert.match(text, /download the installer yourself/);
|
||||
});
|
||||
|
||||
test('only signature failures are reported as a failed safety check', () => {
|
||||
assert.match(
|
||||
describeUpdateError('download', 'The signature X could not be decoded'),
|
||||
/safety check/,
|
||||
);
|
||||
assert.doesNotMatch(describeUpdateError('download', 'No space left on device'), /safety/);
|
||||
});
|
||||
|
||||
test('check failures never suggest the app is broken', () => {
|
||||
assert.match(describeUpdateError('check', 'operation timed out'), /Lotus Chat still works/);
|
||||
assert.match(describeUpdateError('check', 'weird'), /Lotus Chat still works/);
|
||||
});
|
||||
|
||||
test('manual download link: Windows gets the installer, others the release page', () => {
|
||||
assert.equal(
|
||||
manualDownloadUrl('Mozilla/5.0 (Windows NT 10.0; Win64; x64) Edg/140'),
|
||||
MANUAL_DOWNLOAD_URL.windows,
|
||||
);
|
||||
assert.equal(manualDownloadUrl('Mozilla/5.0 (X11; Linux x86_64)'), MANUAL_DOWNLOAD_URL.other);
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
/** 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.';
|
||||
};
|
||||
Reference in New Issue
Block a user