mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 14:55:43 +00:00
fix(updater): hold Windows update notice during WinGet moderation window (#1200)
* fix(updater): hold Windows update notice during WinGet moderation window * docs(changelog): Windows update notice WinGet moderation delay
This commit is contained in:
@@ -351,6 +351,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
* If a screen hit an unexpected rendering error, the whole window could go blank with no way back. The app now shows a small recoverable error card (Try again / Reload app) instead, and playback keeps going.
|
||||
|
||||
### Windows update notice waits out WinGet moderation
|
||||
|
||||
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1200](https://github.com/Psychotoxical/psysonic/pull/1200)**
|
||||
|
||||
* On Windows, the "update available" notice now waits until a release is a couple of days old, so it no longer points to a version that WinGet has not finished publishing yet. macOS and Linux are unaffected.
|
||||
|
||||
## [1.48.1] - 2026-06-15
|
||||
|
||||
## Fixed
|
||||
|
||||
@@ -4,8 +4,8 @@ import { dirname } from '@tauri-apps/api/path';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { version as currentVersion } from '../../package.json';
|
||||
import { IS_LINUX, IS_MACOS } from '../utils/platform';
|
||||
import { SKIP_KEY, isNewer, pickAsset, type ReleaseData, type DlState } from '../utils/componentHelpers/appUpdaterHelpers';
|
||||
import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '../utils/platform';
|
||||
import { SKIP_KEY, isNewer, isWithinModerationWindow, pickAsset, type ReleaseData, type DlState } from '../utils/componentHelpers/appUpdaterHelpers';
|
||||
|
||||
/** All update-modal state, the GitHub release probe and the download/relaunch
|
||||
* handlers. The component owns only the early-return guard and the JSX. */
|
||||
@@ -36,6 +36,10 @@ export function useAppUpdater() {
|
||||
if (!isNewer(version, currentVersion)) return;
|
||||
const skipped = localStorage.getItem(SKIP_KEY);
|
||||
if (skipped === version) return;
|
||||
// Windows updates go through WinGet moderation; hold the notice until
|
||||
// the release clears the moderation window so users aren't pointed at a
|
||||
// version WinGet hasn't published yet. macOS/Linux use other channels.
|
||||
if (IS_WINDOWS && isWithinModerationWindow(data.published_at, Date.now())) return;
|
||||
}
|
||||
setDismissed(false);
|
||||
setDlState('idle');
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { isWithinModerationWindow, WINGET_MODERATION_DELAY_MS } from './appUpdaterHelpers';
|
||||
|
||||
describe('isWithinModerationWindow', () => {
|
||||
const published = '2026-06-27T00:00:00Z';
|
||||
const publishedMs = Date.parse(published);
|
||||
|
||||
it('returns true while the release is younger than the window', () => {
|
||||
const now = publishedMs + 12 * 60 * 60 * 1000; // 12h after release
|
||||
expect(isWithinModerationWindow(published, now)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false once the release is older than the window', () => {
|
||||
const now = publishedMs + WINGET_MODERATION_DELAY_MS + 1;
|
||||
expect(isWithinModerationWindow(published, now)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false exactly at the window boundary', () => {
|
||||
const now = publishedMs + WINGET_MODERATION_DELAY_MS;
|
||||
expect(isWithinModerationWindow(published, now)).toBe(false);
|
||||
});
|
||||
|
||||
it('respects a custom window override', () => {
|
||||
const now = publishedMs + 2 * 60 * 60 * 1000; // 2h after release
|
||||
expect(isWithinModerationWindow(published, now, 1 * 60 * 60 * 1000)).toBe(false);
|
||||
expect(isWithinModerationWindow(published, now, 3 * 60 * 60 * 1000)).toBe(true);
|
||||
});
|
||||
|
||||
it('fails open on a missing date', () => {
|
||||
expect(isWithinModerationWindow(undefined, publishedMs)).toBe(false);
|
||||
});
|
||||
|
||||
it('fails open on an unparseable date', () => {
|
||||
expect(isWithinModerationWindow('not-a-date', publishedMs)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,28 @@ export function isNewer(a: string, b: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Windows updates ship through WinGet, which moderates each new release for a
|
||||
// while after the GitHub release goes live (installer scan + manual review,
|
||||
// longer for freshly signed binaries building SmartScreen reputation). Holding
|
||||
// the update notice back until the release clears this window avoids pointing
|
||||
// Windows users at a version WinGet has not published yet. Conservative start
|
||||
// value — tune down once real winget-pkgs merge times are known.
|
||||
export const WINGET_MODERATION_DELAY_MS = 48 * 60 * 60 * 1000;
|
||||
|
||||
// True while `publishedAt` is younger than `windowMs` relative to `now`.
|
||||
// Missing or unparseable date → false (fail open: show the notice rather than
|
||||
// hide it indefinitely). Platform-agnostic so the time logic stays testable.
|
||||
export function isWithinModerationWindow(
|
||||
publishedAt: string | undefined,
|
||||
now: number,
|
||||
windowMs: number = WINGET_MODERATION_DELAY_MS,
|
||||
): boolean {
|
||||
if (!publishedAt) return false;
|
||||
const published = Date.parse(publishedAt);
|
||||
if (Number.isNaN(published)) return false;
|
||||
return now - published < windowMs;
|
||||
}
|
||||
|
||||
export interface GithubAsset {
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
|
||||
Reference in New Issue
Block a user