mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +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.
|
* 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
|
## [1.48.1] - 2026-06-15
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { dirname } from '@tauri-apps/api/path';
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { version as currentVersion } from '../../package.json';
|
import { version as currentVersion } from '../../package.json';
|
||||||
import { IS_LINUX, IS_MACOS } from '../utils/platform';
|
import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '../utils/platform';
|
||||||
import { SKIP_KEY, isNewer, pickAsset, type ReleaseData, type DlState } from '../utils/componentHelpers/appUpdaterHelpers';
|
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
|
/** All update-modal state, the GitHub release probe and the download/relaunch
|
||||||
* handlers. The component owns only the early-return guard and the JSX. */
|
* handlers. The component owns only the early-return guard and the JSX. */
|
||||||
@@ -36,6 +36,10 @@ export function useAppUpdater() {
|
|||||||
if (!isNewer(version, currentVersion)) return;
|
if (!isNewer(version, currentVersion)) return;
|
||||||
const skipped = localStorage.getItem(SKIP_KEY);
|
const skipped = localStorage.getItem(SKIP_KEY);
|
||||||
if (skipped === version) return;
|
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);
|
setDismissed(false);
|
||||||
setDlState('idle');
|
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;
|
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 {
|
export interface GithubAsset {
|
||||||
name: string;
|
name: string;
|
||||||
browser_download_url: string;
|
browser_download_url: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user