refactor(qconnect): extract runtime helpers from page\n\ncx

This commit is contained in:
vicrodh committed 2026-03-12 17:21:50 -06:00
1 parent c026fce2ca
commit 674fff996b
5 files changed
+488 -176

No files matched your search

+136
View File
@@ -0,0 +1,136 @@
import { describe, expect, it } from 'vitest';
import {
evaluateQconnectPlaybackReportSkip,
evaluateQconnectSessionPersistence,
isQconnectRemoteModeActive,
type QconnectConnectionStatus
} from './qconnectRuntime';
import type { QconnectQueueSnapshot, QconnectRendererSnapshot } from './qconnectRemoteQueue';
function buildQueueSnapshot(trackIds: number[]): QconnectQueueSnapshot {
return {
version: { major: 2, minor: 1 },
queue_items: trackIds.map((trackId, index) => ({
track_id: trackId,
queue_item_id: index + 1,
track_context_uuid: `ctx-${index + 1}`
})),
shuffle_mode: false,
autoplay_mode: false,
autoplay_items: []
};
}
describe('evaluateQconnectPlaybackReportSkip', () => {
it('does not skip when the current track exists in the remote queue', () => {
const result = evaluateQconnectPlaybackReportSkip({
currentTrackId: 23943863,
queueSnapshot: buildQueueSnapshot([193849747, 23943863, 218534]),
rendererSnapshot: null,
lastSkipSignature: ''
});
expect(result).toEqual({
shouldSkip: false,
nextSkipSignature: '',
diagnosticPayload: null
});
});
it('skips and emits a diagnostic when the local track is outside the remote queue and renderer has no current track', () => {
const result = evaluateQconnectPlaybackReportSkip({
currentTrackId: 46848340,
queueSnapshot: buildQueueSnapshot([193849747, 23943863, 218534]),
rendererSnapshot: {
current_track: null,
next_track: { track_id: 23943863, queue_item_id: 2 }
},
lastSkipSignature: ''
});
expect(result.shouldSkip).toBe(true);
expect(result.nextSkipSignature).toBe('46848340:2.1');
expect(result.diagnosticPayload).toEqual({
reason: 'local_track_not_in_remote_queue',
current_track_id: 46848340,
remote_queue_preview: buildQueueSnapshot([193849747, 23943863, 218534]).queue_items,
renderer_current: null,
renderer_next: { track_id: 23943863, queue_item_id: 2 }
});
});
it('suppresses duplicate diagnostics for the same skip signature', () => {
const queueSnapshot = buildQueueSnapshot([193849747, 23943863, 218534]);
const rendererSnapshot: QconnectRendererSnapshot = {
current_track: null,
next_track: { track_id: 23943863, queue_item_id: 2 }
};
const result = evaluateQconnectPlaybackReportSkip({
currentTrackId: 46848340,
queueSnapshot,
rendererSnapshot,
lastSkipSignature: '46848340:2.1'
});
expect(result).toEqual({
shouldSkip: true,
nextSkipSignature: '46848340:2.1',
diagnosticPayload: null
});
});
it('does not skip when renderer already has a current remote track', () => {
const result = evaluateQconnectPlaybackReportSkip({
currentTrackId: 46848340,
queueSnapshot: buildQueueSnapshot([193849747, 23943863, 218534]),
rendererSnapshot: {
current_track: { track_id: 193849747, queue_item_id: 1 },
next_track: { track_id: 23943863, queue_item_id: 2 }
},
lastSkipSignature: ''
});
expect(result).toEqual({
shouldSkip: false,
nextSkipSignature: '',
diagnosticPayload: null
});
});
});
describe('QConnect runtime state helpers', () => {
it('treats transport connectivity as remote mode even if the local flag is stale', () => {
const status: QconnectConnectionStatus = {
running: true,
transport_connected: true,
endpoint_url: 'ws://127.0.0.1:12345',
last_error: null
};
expect(isQconnectRemoteModeActive(false, status)).toBe(true);
});
it('resets the session persistence skip flag once remote mode ends', () => {
expect(evaluateQconnectSessionPersistence(false, true)).toEqual({
shouldPersist: true,
nextSkipLogged: false,
shouldLogSkip: false
});
});
it('logs the skip only once while remote mode stays active', () => {
expect(evaluateQconnectSessionPersistence(true, false)).toEqual({
shouldPersist: false,
nextSkipLogged: true,
shouldLogSkip: true
});
expect(evaluateQconnectSessionPersistence(true, true)).toEqual({
shouldPersist: false,
nextSkipLogged: true,
shouldLogSkip: false
});
});
});
+258
View File
@@ -0,0 +1,258 @@
import { invoke } from '@tauri-apps/api/core';
import type {
QconnectQueueSnapshot,
QconnectRendererSnapshot
} from '$lib/services/qconnectRemoteQueue';
export type QconnectConnectionStatus = {
running: boolean;
transport_connected: boolean;
endpoint_url?: string | null;
last_error?: string | null;
};
export type QconnectRendererInfo = {
renderer_id: number;
friendly_name?: string | null;
brand?: string | null;
model?: string | null;
device_type?: number | null;
};
export type QconnectSessionSnapshot = {
session_uuid?: string | null;
active_renderer_id?: number | null;
renderers: QconnectRendererInfo[];
};
export type QconnectAdmissionBlockedEvent = {
command_type: string;
origin: string;
reason: string;
handoff_intent: 'continue_locally' | 'send_to_connect';
};
export type QconnectDiagnosticsEntry = {
ts: number;
level: 'info' | 'warn' | 'error';
channel: string;
message: string;
};
export type QconnectPlaybackReportSkipResult = {
shouldSkip: boolean;
nextSkipSignature: string;
diagnosticPayload: Record<string, unknown> | null;
};
export type QconnectSessionPersistenceDecision = {
shouldPersist: boolean;
nextSkipLogged: boolean;
shouldLogSkip: boolean;
};
export type QconnectRuntimeStateSnapshot = {
status: QconnectConnectionStatus;
connected: boolean;
queueSnapshot: QconnectQueueSnapshot | null;
rendererSnapshot: QconnectRendererSnapshot | null;
sessionSnapshot: QconnectSessionSnapshot | null;
snapshotError: unknown | null;
};
export const DEFAULT_QCONNECT_CONNECTION_STATUS: QconnectConnectionStatus = {
running: false,
transport_connected: false,
endpoint_url: null,
last_error: null
};
export const SHOW_QCONNECT_DEV_DIAGNOSTICS = import.meta.env.DEV;
export const QCONNECT_DIAGNOSTIC_LOG_LIMIT = 200;
export function qconnectAdmissionReasonKey(reason: string): string {
if (reason === 'local_library_tracks_never_enter_remote_qconnect_queue') {
return 'qconnect.admissionBlockedLocalLibrary';
}
if (reason === 'plex_tracks_never_enter_remote_qconnect_queue') {
return 'qconnect.admissionBlockedPlex';
}
return 'qconnect.admissionBlockedUnknown';
}
function normalizeQconnectDiagnosticPayload(payload: unknown): string {
if (typeof payload === 'string') {
return payload;
}
try {
return JSON.stringify(payload);
} catch {
return String(payload);
}
}
export function appendQconnectDiagnosticEntry(
logs: QconnectDiagnosticsEntry[],
channel: string,
level: QconnectDiagnosticsEntry['level'],
payload: unknown,
logLimit: number = QCONNECT_DIAGNOSTIC_LOG_LIMIT
): QconnectDiagnosticsEntry[] {
return [
{
ts: Date.now(),
level,
channel,
message: normalizeQconnectDiagnosticPayload(payload)
},
...logs
].slice(0, logLimit);
}
export function logQconnectPlaybackReport(
logs: QconnectDiagnosticsEntry[],
source: 'interval' | 'player_transition',
payload: Record<string, unknown>
): QconnectDiagnosticsEntry[] {
return appendQconnectDiagnosticEntry(logs, `qconnect:report_playback_state:${source}`, 'info', payload);
}
export function evaluateQconnectPlaybackReportSkip(params: {
currentTrackId: number | null | undefined;
queueSnapshot: QconnectQueueSnapshot | null;
rendererSnapshot: QconnectRendererSnapshot | null;
lastSkipSignature: string;
}): QconnectPlaybackReportSkipResult {
const {
currentTrackId,
queueSnapshot,
rendererSnapshot,
lastSkipSignature
} = params;
if (currentTrackId == null || !queueSnapshot) {
return {
shouldSkip: false,
nextSkipSignature: '',
diagnosticPayload: null
};
}
const remoteQueueContainsTrack =
queueSnapshot.queue_items.some((item) => item.track_id === currentTrackId) ||
queueSnapshot.autoplay_items.some((item) => item.track_id === currentTrackId);
if (remoteQueueContainsTrack || rendererSnapshot?.current_track?.track_id != null) {
return {
shouldSkip: false,
nextSkipSignature: '',
diagnosticPayload: null
};
}
const skipSignature =
`${currentTrackId}:${queueSnapshot.version?.major ?? 0}.${queueSnapshot.version?.minor ?? 0}`;
return {
shouldSkip: true,
nextSkipSignature: skipSignature,
diagnosticPayload: lastSkipSignature === skipSignature
? null
: {
reason: 'local_track_not_in_remote_queue',
current_track_id: currentTrackId,
remote_queue_preview: queueSnapshot.queue_items.slice(0, 6),
renderer_current: rendererSnapshot?.current_track ?? null,
renderer_next: rendererSnapshot?.next_track ?? null
}
};
}
export function isQconnectRemoteModeActive(
connected: boolean,
status: QconnectConnectionStatus
): boolean {
return Boolean(connected || status.transport_connected);
}
export function evaluateQconnectSessionPersistence(
remoteModeActive: boolean,
skipLogged: boolean
): QconnectSessionPersistenceDecision {
if (!remoteModeActive) {
return {
shouldPersist: true,
nextSkipLogged: false,
shouldLogSkip: false
};
}
return {
shouldPersist: false,
nextSkipLogged: true,
shouldLogSkip: !skipLogged
};
}
export async function fetchQconnectRuntimeState(): Promise<QconnectRuntimeStateSnapshot> {
try {
const status = await invoke<QconnectConnectionStatus>('v2_qconnect_status');
const connected = Boolean(status.transport_connected);
if (!connected) {
return {
status,
connected,
queueSnapshot: null,
rendererSnapshot: null,
sessionSnapshot: null,
snapshotError: null
};
}
try {
const [queueSnapshot, rendererSnapshot, sessionSnapshot] = await Promise.all([
invoke<QconnectQueueSnapshot>('v2_qconnect_queue_snapshot'),
invoke<QconnectRendererSnapshot>('v2_qconnect_renderer_snapshot'),
invoke<QconnectSessionSnapshot>('v2_qconnect_session_snapshot')
]);
return {
status,
connected,
queueSnapshot,
rendererSnapshot,
sessionSnapshot,
snapshotError: null
};
} catch (snapshotError) {
return {
status,
connected,
queueSnapshot: null,
rendererSnapshot: null,
sessionSnapshot: null,
snapshotError
};
}
} catch {
return {
status: DEFAULT_QCONNECT_CONNECTION_STATUS,
connected: false,
queueSnapshot: null,
rendererSnapshot: null,
sessionSnapshot: null,
snapshotError: null
};
}
}
export async function toggleQconnectConnection(connected: boolean): Promise<void> {
if (connected) {
await invoke('v2_qconnect_disconnect');
return;
}
await invoke('v2_qconnect_connect', { options: null });
}
+5 -15
View File
@@ -19,6 +19,10 @@ import { openPlaylistModal } from '$lib/stores/uiStore';
import { showToast as storeShowToast, type ToastType } from '$lib/stores/toastStore';
import { t } from '$lib/i18n';
import type { QobuzTrack, Track, PlaylistTrack, LocalLibraryTrack, DisplayTrack } from '$lib/types';
import {
qconnectAdmissionReasonKey
} from '$lib/services/qconnectRuntime';
import type { QconnectConnectionStatus } from '$lib/services/qconnectRuntime';
import {
resolveQconnectPlayNextInsertAfter,
type QconnectDiagnosticsPayload,
@@ -36,10 +40,6 @@ function translate(key: string): string {
return get(t)(key);
}
type QconnectStatus = {
transport_connected: boolean;
};
type QconnectTrackOrigin =
| 'qobuz_online'
| 'qobuz_offline_cache'
@@ -60,16 +60,6 @@ type QueueTrackActionOptions = {
const QCONNECT_DIAGNOSTIC_EVENT = 'qconnect:diagnostic';
function qconnectAdmissionReasonKey(reason: string): string {
if (reason === 'local_library_tracks_never_enter_remote_qconnect_queue') {
return 'qconnect.admissionBlockedLocalLibrary';
}
if (reason === 'plex_tracks_never_enter_remote_qconnect_queue') {
return 'qconnect.admissionBlockedPlex';
}
return 'qconnect.admissionBlockedUnknown';
}
function resolveQconnectTrackOrigin(queueTrack: BackendQueueTrack, isLocal: boolean): QconnectTrackOrigin {
const source = (queueTrack.source ?? '').toLowerCase();
if (source === 'plex') return 'plex';
@@ -81,7 +71,7 @@ function resolveQconnectTrackOrigin(queueTrack: BackendQueueTrack, isLocal: bool
async function isQconnectConnected(): Promise<boolean> {
try {
const status = await invoke<QconnectStatus>('v2_qconnect_status');
const status = await invoke<QconnectConnectionStatus>('v2_qconnect_status');
return Boolean(status.transport_connected);
} catch (err) {
console.warn('[QConnect] isQconnectConnected failed:', err);