fix(connection): ignore spurious navigator.onLine offline hint in Tauri (#1234)

This commit is contained in:
cucadmuh
2026-07-04 20:56:31 +03:00
committed by GitHub
parent bf0e73ce6c
commit 5e075b2c36
12 changed files with 159 additions and 12 deletions
@@ -4,11 +4,12 @@ import {
} from '@/store/devOfflineBrowseStore';
import { useConnectionStatus } from '@/lib/hooks/useConnectionStatus';
import { isActiveServerReachable } from '@/lib/network/activeServerReachability';
import { isNavigatorOfflineHint } from '@/lib/network/navigatorOnlineHint';
/** True when browse/detail pages should use local-bytes-only data sources. */
export function isOfflineBrowseActive(): boolean {
if (isDevOfflineBrowseForced()) return true;
if (typeof navigator !== 'undefined' && !navigator.onLine) return true;
if (isNavigatorOfflineHint()) return true;
return !isActiveServerReachable();
}
@@ -22,6 +23,6 @@ export function useOfflineBrowseActive(): boolean {
useConnectionStatus();
if (import.meta.env.DEV && devForceOffline) return true;
if (typeof navigator !== 'undefined' && !navigator.onLine) return true;
if (isNavigatorOfflineHint()) return true;
return !isActiveServerReachable();
}
@@ -11,6 +11,10 @@ vi.mock('@/lib/api/subsonicStarRating', () => ({
import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { Track } from '@/lib/media/trackTypes';
import {
resetActiveServerConnectionSnapshot,
setActiveServerReachable,
} from '@/lib/network/activeServerReachability';
import { queueSongStar, queueSongRating, _resetPendingStarSyncForTest } from '@/features/playback/store/pendingStarSync';
import {
getCachedTrack,
@@ -26,6 +30,8 @@ const track = (id: string): Track => ({
describe('pendingStarSync', () => {
beforeEach(() => {
vi.useFakeTimers();
resetActiveServerConnectionSnapshot();
setActiveServerReachable(true);
starMock.mockReset().mockResolvedValue(undefined);
unstarMock.mockReset().mockResolvedValue(undefined);
setRatingMock.mockReset().mockResolvedValue(undefined);
@@ -75,6 +81,19 @@ describe('pendingStarSync', () => {
expect(usePlayerStore.getState().starredOverrides.t1).toBe(true); // override survives (no rollback)
});
it('flushes pending stars when the active server becomes reachable', async () => {
starMock.mockRejectedValue(new Error('offline'));
queueSongStar('t1', true);
await vi.advanceTimersByTimeAsync(0);
expect(starMock).toHaveBeenCalledTimes(1);
setActiveServerReachable(false);
setActiveServerReachable(true);
await vi.advanceTimersByTimeAsync(0);
expect(starMock.mock.calls.length).toBeGreaterThan(1);
});
it('passes serverId through to star/unstar for cross-server favorites', async () => {
queueSongStar('t1', true, 'srv-b');
await vi.runAllTimersAsync();
@@ -1,6 +1,7 @@
import { setRating, star, unstar } from '@/lib/api/subsonicStarRating';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { patchCachedTrack } from '@/features/playback/store/queueTrackResolver';
import { onActiveServerBecameReachable } from '@/lib/network/activeServerReachability';
/**
* F4 — pending-sync for **song** star + rating (spec §6.5 / R7-18).
@@ -11,7 +12,8 @@ import { patchCachedTrack } from '@/features/playback/store/queueTrackResolver';
*
* 1. Set the override optimistically (instant UI).
* 2. Retry the Subsonic API (`star` / `unstar` / `setRating`) with exponential
* backoff; flush immediately on `online` / window focus.
* backoff; flush immediately when the active server becomes reachable again
* (`onActiveServerBecameReachable`) or on window focus.
* 3. On **star** success: KEEP the override — list views read it — and patch
* the in-memory `Track`. F3 index patch-on-use runs in the API layer.
* (Ratings clear on success; see `onRatingSuccess`.)
@@ -42,8 +44,8 @@ function armListeners(): void {
const flushAll = () => {
for (const k of pending.keys()) schedule(k, 0);
};
window.addEventListener('online', flushAll);
window.addEventListener('focus', flushAll);
onActiveServerBecameReachable(flushAll);
}
function schedule(k: string, delayMs: number): void {