mirror of
https://github.com/readest/readest.git
synced 2026-07-09 16:00:16 +00:00
fix(updater): never throw from an auto update check (#5028)
checkForAppUpdates runs fire-and-forget on reader/library mount, but it threw on any network failure: the desktop check() propagated "error sending request for url (...latest.json)" (READEST-J) and the Android branch re-threw "Failed to fetch Android update info" (READEST-22). Both surfaced as unhandled rejections. Wrap the check so a failure is swallowed (returns false) for an auto-check; manual checks (About window) still surface the error. Adds regression tests. Fixes READEST-J Fixes READEST-22 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fdd13a5a6a
commit
15cca23773
2 changed files with 71 additions and 39 deletions
|
|
@ -256,6 +256,24 @@ describe('updater', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('auto-check swallows Android update failure (READEST-22)', async () => {
|
||||||
|
mockOsType.mockReturnValue('android');
|
||||||
|
mockTauriFetch.mockRejectedValue(new Error('Network error'));
|
||||||
|
|
||||||
|
// Auto-check runs fire-and-forget on mount; a network failure must resolve
|
||||||
|
// false, not reject (which would become an unhandled rejection).
|
||||||
|
await expect(checkForAppUpdates(dummyTranslate, true)).resolves.toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('auto-check swallows desktop update failure (READEST-J)', async () => {
|
||||||
|
mockOsType.mockReturnValue('macos');
|
||||||
|
mockCheck.mockRejectedValue(
|
||||||
|
new Error('error sending request for url (releases/latest/download/latest.json)'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(checkForAppUpdates(dummyTranslate, true)).resolves.toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
test('returns false for unsupported OS types', async () => {
|
test('returns false for unsupported OS types', async () => {
|
||||||
mockOsType.mockReturnValue('ios');
|
mockOsType.mockReturnValue('ios');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,48 +149,62 @@ export const checkForAppUpdates = async (
|
||||||
console.log('Checking for updates', { updateChannel });
|
console.log('Checking for updates', { updateChannel });
|
||||||
const OS_TYPE = osType();
|
const OS_TYPE = osType();
|
||||||
|
|
||||||
if (updateChannel === 'nightly') {
|
try {
|
||||||
const platformKey = getNightlyPlatformKey(
|
if (updateChannel === 'nightly') {
|
||||||
OS_TYPE,
|
const platformKey = getNightlyPlatformKey(
|
||||||
osArch(),
|
OS_TYPE,
|
||||||
Boolean(process.env['NEXT_PUBLIC_PORTABLE_APP']),
|
osArch(),
|
||||||
Boolean((window as { __READEST_IS_APPIMAGE?: boolean }).__READEST_IS_APPIMAGE),
|
Boolean(process.env['NEXT_PUBLIC_PORTABLE_APP']),
|
||||||
);
|
Boolean((window as { __READEST_IS_APPIMAGE?: boolean }).__READEST_IS_APPIMAGE),
|
||||||
if (!platformKey) return false;
|
);
|
||||||
const resolved = await resolveNightlyUpdate(getAppVersion(), platformKey, fetch);
|
if (!platformKey) return false;
|
||||||
if (resolved) {
|
const resolved = await resolveNightlyUpdate(getAppVersion(), platformKey, fetch);
|
||||||
setUpdaterWindowVisible(true, resolved.version, getAppVersion(), true, resolved);
|
if (resolved) {
|
||||||
return true;
|
setUpdaterWindowVisible(true, resolved.version, getAppVersion(), true, resolved);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (['macos', 'windows', 'linux'].includes(OS_TYPE)) {
|
||||||
|
const update = await check();
|
||||||
|
if (update) {
|
||||||
|
// Enum ScrollBarStyle is exported as type by tauri, so it cannot be used directly.
|
||||||
|
const scrollBarStyle = (OS_TYPE === 'windows'
|
||||||
|
? 'fluentOverlay'
|
||||||
|
: 'default') as unknown as ScrollBarStyle;
|
||||||
|
showUpdateWindow(update.version, scrollBarStyle);
|
||||||
|
}
|
||||||
|
return !!update;
|
||||||
|
} else if (OS_TYPE === 'android') {
|
||||||
|
try {
|
||||||
|
const response = await fetch(READEST_UPDATER_FILE, { connectTimeout: 5000 });
|
||||||
|
const data = await response.json();
|
||||||
|
const isNewer = semver.gt(data.version, getAppVersion());
|
||||||
|
if (
|
||||||
|
isNewer &&
|
||||||
|
('android-arm64' in data.platforms || 'android-universal' in data.platforms)
|
||||||
|
) {
|
||||||
|
setUpdaterWindowVisible(true, data.version!, getAppVersion());
|
||||||
|
}
|
||||||
|
return isNewer;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to fetch Android update info', err);
|
||||||
|
throw new Error('Failed to fetch Android update info');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
// Update checks are best-effort: they fail routinely when offline or when
|
||||||
|
// the release host is unreachable. An auto-check runs fire-and-forget on
|
||||||
|
// mount, so throwing here becomes an unhandled rejection (READEST-J desktop
|
||||||
|
// latest.json, READEST-22 Android update info). Only surface the failure for
|
||||||
|
// a manual check (About window).
|
||||||
|
console.warn('Update check failed', err);
|
||||||
|
if (!isAutoCheck) throw err;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['macos', 'windows', 'linux'].includes(OS_TYPE)) {
|
|
||||||
const update = await check();
|
|
||||||
if (update) {
|
|
||||||
// Enum ScrollBarStyle is exported as type by tauri, so it cannot be used directly.
|
|
||||||
const scrollBarStyle = (OS_TYPE === 'windows'
|
|
||||||
? 'fluentOverlay'
|
|
||||||
: 'default') as unknown as ScrollBarStyle;
|
|
||||||
showUpdateWindow(update.version, scrollBarStyle);
|
|
||||||
}
|
|
||||||
return !!update;
|
|
||||||
} else if (OS_TYPE === 'android') {
|
|
||||||
try {
|
|
||||||
const response = await fetch(READEST_UPDATER_FILE, { connectTimeout: 5000 });
|
|
||||||
const data = await response.json();
|
|
||||||
const isNewer = semver.gt(data.version, getAppVersion());
|
|
||||||
if (isNewer && ('android-arm64' in data.platforms || 'android-universal' in data.platforms)) {
|
|
||||||
setUpdaterWindowVisible(true, data.version!, getAppVersion());
|
|
||||||
}
|
|
||||||
return isNewer;
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('Failed to fetch Android update info', err);
|
|
||||||
throw new Error('Failed to fetch Android update info');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const LAST_SHOWN_RELEASE_NOTES_KEY = 'lastShownReleaseNotesVersion';
|
const LAST_SHOWN_RELEASE_NOTES_KEY = 'lastShownReleaseNotesVersion';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue