fix(HiFi.ts): ensure only one token is fetched

If multiple calls to the HiFi methods were called at once, you could potentially have ended up with multiple simultaneous token api calls
This commit is contained in:
Daniel 2026-03-20 13:12:40 -05:00
parent a385cb558a
commit 5ac4d23199

View file

@ -20,8 +20,9 @@ export class TidalResponse extends Response {
export class HiFiClient {
private static token: string | null;
private countryCode: string;
private static appTokenExpiry = 0;
private static tokenPromise: Promise<string> | null = null;
private countryCode: string;
private static albumTracksMax = 20;
private static albumTracksActive = 0;
private static albumTracksQueue: Array<() => void> = [];
@ -35,7 +36,7 @@ export class HiFiClient {
return u.toString();
}
private encodeBasic(id: string, secret: string) {
private static encodeBasic(id: string, secret: string) {
if (typeof window !== 'undefined' && typeof window.btoa === 'function') {
return window.btoa(`${id}:${secret}`);
}
@ -43,10 +44,12 @@ export class HiFiClient {
return Buffer.from(`${id}:${secret}`).toString('base64');
}
private async fetchAppToken(signal: AbortSignal = new AbortController().signal): Promise<string> {
private static async fetchAppToken(signal: AbortSignal = new AbortController().signal) {
const now = Date.now();
if (HiFiClient.token && now < HiFiClient.appTokenExpiry) return HiFiClient.token;
return await (HiFiClient.tokenPromise ??= (async () => {
try {
const res = await fetch('https://auth.tidal.com/v1/oauth2/token', {
method: 'POST',
headers: {
@ -72,6 +75,10 @@ export class HiFiClient {
HiFiClient.token = token;
HiFiClient.appTokenExpiry = Date.now() + expires_in * 1000 - 60_000;
return token;
} finally {
HiFiClient.tokenPromise = null;
}
})());
}
constructor(countryCode = 'US') {
@ -81,7 +88,7 @@ export class HiFiClient {
private async fetchJson(url: string, params?: Params, signal: AbortSignal = new AbortController().signal) {
const final = HiFiClient.buildUrl(url, params);
const res = await fetch(final, {
headers: { authorization: `Bearer ${await this.fetchAppToken(signal)}` },
headers: { authorization: `Bearer ${await HiFiClient.fetchAppToken(signal)}` },
signal,
});