diff --git a/src/api/subsonicClient.ts b/src/api/subsonicClient.ts index fdfa0c65..90cbf8b7 100644 --- a/src/api/subsonicClient.ts +++ b/src/api/subsonicClient.ts @@ -8,6 +8,15 @@ import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '../utils/s export const SUBSONIC_CLIENT = `psysonic/${version}`; +/** + * Subsonic REST protocol version sent on every request. A server returns + * error 30 ("Incompatible REST protocol version") if the client asks for a + * HIGHER version than it supports — surfaced to the user via the connection + * error toast. OpenSubsonic extensions are negotiated via the `openSubsonic` + * response flag, not this number. + */ +export const SUBSONIC_API_VERSION = '1.16.1'; + export function secureRandomSalt(): string { const buf = new Uint8Array(8); crypto.getRandomValues(buf); @@ -17,7 +26,7 @@ export function secureRandomSalt(): string { export function getAuthParams(username: string, password: string) { const salt = secureRandomSalt(); const token = md5(password + salt); - return { u: username, t: token, s: salt, v: '1.16.1', c: SUBSONIC_CLIENT, f: 'json' }; + return { u: username, t: token, s: salt, v: SUBSONIC_API_VERSION, c: SUBSONIC_CLIENT, f: 'json' }; } export function restBaseFromUrl(serverUrl: string): string { diff --git a/src/api/subsonicStreamUrl.ts b/src/api/subsonicStreamUrl.ts index 63125876..14d6ad4b 100644 --- a/src/api/subsonicStreamUrl.ts +++ b/src/api/subsonicStreamUrl.ts @@ -5,7 +5,7 @@ import type { CoverArtTier } from '../cover/types'; import { useAuthStore } from '../store/authStore'; import { connectBaseUrlForServer } from '../utils/server/serverEndpoint'; import { findServerByIdOrIndexKey } from '../utils/server/serverLookup'; -import { restBaseFromUrl, SUBSONIC_CLIENT, secureRandomSalt } from './subsonicClient'; +import { restBaseFromUrl, SUBSONIC_CLIENT, SUBSONIC_API_VERSION, secureRandomSalt } from './subsonicClient'; function coverArtQueryParams(username: string, password: string, id: string, size: number): URLSearchParams { const salt = secureRandomSalt(); @@ -16,7 +16,7 @@ function coverArtQueryParams(username: string, password: string, id: string, siz u: username, t: token, s: salt, - v: '1.16.1', + v: SUBSONIC_API_VERSION, c: SUBSONIC_CLIENT, f: 'json', }); @@ -36,7 +36,7 @@ function streamUrlFromProfile( u: username, t: token, s: salt, - v: '1.16.1', + v: SUBSONIC_API_VERSION, c: SUBSONIC_CLIENT, f: 'json', }); @@ -116,7 +116,7 @@ export function buildDownloadUrl(id: string): string { const p = new URLSearchParams({ id, u: server?.username ?? '', - t: token, s: salt, v: '1.16.1', c: SUBSONIC_CLIENT, f: 'json', + t: token, s: salt, v: SUBSONIC_API_VERSION, c: SUBSONIC_CLIENT, f: 'json', }); return `${baseUrl}/rest/download.view?${p.toString()}`; } diff --git a/src/utils/server/serverFingerprint.ts b/src/utils/server/serverFingerprint.ts index 40866886..e735fa89 100644 --- a/src/utils/server/serverFingerprint.ts +++ b/src/utils/server/serverFingerprint.ts @@ -15,7 +15,7 @@ */ import md5 from 'md5'; -import { apiWithCredentials, restBaseFromUrl, secureRandomSalt, SUBSONIC_CLIENT } from '../../api/subsonicClient'; +import { apiWithCredentials, restBaseFromUrl, secureRandomSalt, SUBSONIC_CLIENT, SUBSONIC_API_VERSION } from '../../api/subsonicClient'; import type { ServerProfile } from '../../store/authStoreTypes'; import { allNormalizedAddresses } from './serverEndpoint'; @@ -64,7 +64,7 @@ async function fetchPingFingerprint( u: username, t: token, s: salt, - v: '1.16.1', + v: SUBSONIC_API_VERSION, c: SUBSONIC_CLIENT, f: 'json', });