Files
Psychotoxical-psysonic/src/api/subsonicStreamUrl.ts
T
Psychotoxical 7c724a642f chore(eslint): add ESLint toolchain and clean src to strict 0/0 (#1165)
* chore(eslint): add eslint toolchain and configs

* fix(eslint): resolve gradual-config errors (rules-of-hooks, no-empty, prefer-const, …)

* chore(eslint): clear unused vars in config, contexts, app and test

* chore(eslint): clear unused vars in api and music-network

* chore(eslint): clear unused vars in utils

* chore(eslint): clear unused vars in store

* chore(eslint): clear unused vars in cover and hooks

* chore(eslint): clear unused vars in components

* chore(eslint): clear unused vars in pages

* chore(eslint): remove explicit any in src

* chore(eslint): align react-hooks exhaustive-deps

* chore(eslint): zero gradual config on src

* chore(eslint): strict hook rules in store and utils

* chore(eslint): strict hook rules in hooks and cover

* chore(eslint): strict hook rules in components

* chore(eslint): strict hook rules in pages and contexts

* chore(eslint): document scripts ignore in eslint config

* chore(eslint): add lint script and zero strict findings

* chore(eslint): address review round 1 (gradual 0/0, per-site disable reasons)

* chore(eslint): tighten two set-state disable comments (review round 2 LOW)

* chore(nix): sync npmDepsHash with package-lock.json

* docs(changelog): add Under the Hood entry for ESLint setup (PR #1165)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-24 01:33:34 +02:00

123 lines
4.5 KiB
TypeScript

import md5 from 'md5';
import { coverStorageKeyFromRef } from '../cover/storageKeys';
import { coverEntryToRef, resolveAlbumCoverEntry } from '../cover/resolveEntry';
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';
function coverArtQueryParams(username: string, password: string, id: string, size: number): URLSearchParams {
const salt = secureRandomSalt();
const token = md5(password + salt);
return new URLSearchParams({
id,
size: String(size),
u: username,
t: token,
s: salt,
v: '1.16.1',
c: SUBSONIC_CLIENT,
f: 'json',
});
}
function streamUrlFromProfile(
serverUrl: string,
username: string,
password: string,
id: string,
): string {
const baseUrl = restBaseFromUrl(serverUrl);
const salt = secureRandomSalt();
const token = md5(password + salt);
const p = new URLSearchParams({
id,
u: username,
t: token,
s: salt,
v: '1.16.1',
c: SUBSONIC_CLIENT,
f: 'json',
});
return `${baseUrl}/stream.view?${p.toString()}`;
}
export function buildStreamUrlForServer(serverId: string, id: string): string {
const server = findServerByIdOrIndexKey(serverId);
if (!server) return buildStreamUrl(id);
// Dual-address: route the stream through the cached connect endpoint.
return streamUrlFromProfile(connectBaseUrlForServer(server), server.username, server.password, id);
}
export function buildStreamUrl(id: string): string {
const { getBaseUrl, getActiveServer } = useAuthStore.getState();
const server = getActiveServer();
const baseUrl = getBaseUrl();
if (!server || !baseUrl) return streamUrlFromProfile('', '', '', id);
// `getBaseUrl()` already returns the cached connect URL; use it directly
// instead of re-normalizing `server.url`, which would bypass the dual-
// address connect cache.
return streamUrlFromProfile(baseUrl, server.username, server.password, id);
}
/** @deprecated Use `coverStorageKey` from `src/cover/storageKeys` — shim until migration. */
export function coverArtCacheKey(id: string, size = 256): string {
const entry = resolveAlbumCoverEntry(id, id);
const ref = coverEntryToRef(entry ?? { cacheKind: 'album', cacheEntityId: id, fetchCoverArtId: id });
return coverStorageKeyFromRef(ref, size as CoverArtTier);
}
/** @deprecated Use `coverStorageKey` from `src/cover/storageKeys` — shim until migration. */
export function coverArtCacheKeyForServer(serverIdOrKey: string, id: string, size = 256): string {
const server = findServerByIdOrIndexKey(serverIdOrKey);
if (!server) return `${serverIdOrKey}:cover:album:${id}:${size}`;
const entry = resolveAlbumCoverEntry(id, id);
const ref = coverEntryToRef(
entry ?? { cacheKind: 'album', cacheEntityId: id, fetchCoverArtId: id },
{
kind: 'server',
serverId: server.id,
url: server.url,
username: server.username,
password: server.password,
},
);
return coverStorageKeyFromRef(ref, size as CoverArtTier);
}
/** @deprecated Use `buildCoverArtFetchUrl` from `src/cover/fetchUrl` — shim until migration. */
export function buildCoverArtUrl(id: string, size = 256): string {
const { getBaseUrl, getActiveServer } = useAuthStore.getState();
const server = getActiveServer();
const baseUrl = getBaseUrl();
const p = coverArtQueryParams(server?.username ?? '', server?.password ?? '', id, size);
return `${baseUrl}/rest/getCoverArt.view?${p.toString()}`;
}
/** @deprecated Use `buildCoverArtFetchUrl` from `src/cover/fetchUrl` — shim until migration. */
export function buildCoverArtUrlForServer(
serverUrl: string,
username: string,
password: string,
id: string,
size = 256,
): string {
const p = coverArtQueryParams(username, password, id, size);
return `${restBaseFromUrl(serverUrl)}/getCoverArt.view?${p.toString()}`;
}
export function buildDownloadUrl(id: string): string {
const { getBaseUrl, getActiveServer } = useAuthStore.getState();
const server = getActiveServer();
const baseUrl = getBaseUrl();
const salt = secureRandomSalt();
const token = md5((server?.password ?? '') + salt);
const p = new URLSearchParams({
id,
u: server?.username ?? '',
t: token, s: salt, v: '1.16.1', c: SUBSONIC_CLIENT, f: 'json',
});
return `${baseUrl}/rest/download.view?${p.toString()}`;
}