Merge pull request #364 from DanTheMan827/hifi-token
fix(HiFi): enhance token fetching
This commit is contained in:
commit
98b65509ea
1 changed files with 77 additions and 18 deletions
95
js/HiFi.ts
95
js/HiFi.ts
|
|
@ -1,6 +1,6 @@
|
||||||
const API_VERSION = '2.6';
|
const API_VERSION = '2.6';
|
||||||
const CLIENT_ID = 'txNoH4kkV41MfH25';
|
const BROWSER_CLIENT_ID = 'txNoH4kkV41MfH25';
|
||||||
const CLIENT_SECRET = 'dQjy0MinCEvxi1O4UmxvxWnDjt4cgHBPw8ll6nYBk98=';
|
const BROWSER_CLIENT_SECRET = 'dQjy0MinCEvxi1O4UmxvxWnDjt4cgHBPw8ll6nYBk98=';
|
||||||
|
|
||||||
type Params = Record<string, string | number | undefined | null>;
|
type Params = Record<string, string | number | undefined | null>;
|
||||||
|
|
||||||
|
|
@ -18,14 +18,71 @@ export class TidalResponse extends Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A container for the mock localStorage */
|
||||||
|
|
||||||
export class HiFiClient {
|
export class HiFiClient {
|
||||||
private static token: string | null;
|
|
||||||
private static appTokenExpiry = 0;
|
|
||||||
private static tokenPromise: Promise<string> | null = null;
|
private static tokenPromise: Promise<string> | null = null;
|
||||||
private countryCode: string;
|
|
||||||
private static albumTracksMax = 20;
|
private static albumTracksMax = 20;
|
||||||
private static albumTracksActive = 0;
|
private static albumTracksActive = 0;
|
||||||
private static albumTracksQueue: Array<() => void> = [];
|
private static albumTracksQueue: Array<() => void> = [];
|
||||||
|
private countryCode: string;
|
||||||
|
private clientId: string;
|
||||||
|
private clientSecret: string;
|
||||||
|
private static _localStorage: Record<string, string> = {};
|
||||||
|
private static get localStorage() {
|
||||||
|
return (
|
||||||
|
globalThis?.localStorage ??
|
||||||
|
window?.localStorage ?? {
|
||||||
|
getItem: (key) => HiFiClient._localStorage[key],
|
||||||
|
setItem: (key, value) => {
|
||||||
|
HiFiClient._localStorage[key] = String(value);
|
||||||
|
},
|
||||||
|
removeItem: (key) => {
|
||||||
|
delete HiFiClient._localStorage[key];
|
||||||
|
},
|
||||||
|
get length() {
|
||||||
|
return Object.keys(HiFiClient._localStorage).length;
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
for (const key in HiFiClient._localStorage) {
|
||||||
|
delete HiFiClient._localStorage[key];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key(index) {
|
||||||
|
const keys = Object.keys(HiFiClient._localStorage);
|
||||||
|
return keys[index] || null;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static get token(): string | null {
|
||||||
|
return HiFiClient.localStorage.getItem('hifi_token') || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static set token(value: string | null) {
|
||||||
|
if (value) {
|
||||||
|
HiFiClient.localStorage.setItem('hifi_token', value);
|
||||||
|
} else {
|
||||||
|
HiFiClient.localStorage.removeItem('hifi_token');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static get appTokenExpiry() {
|
||||||
|
return Number(HiFiClient.localStorage.getItem('hifi_token_expiry') || '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static set appTokenExpiry(value: number) {
|
||||||
|
if (value) {
|
||||||
|
HiFiClient.localStorage.setItem('hifi_token_expiry', value.toString());
|
||||||
|
} else {
|
||||||
|
HiFiClient.localStorage.removeItem('hifi_token_expiry');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < Date.now()) {
|
||||||
|
HiFiClient.localStorage.removeItem('hifi_token');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static buildUrl(base: string, params?: Params) {
|
private static buildUrl(base: string, params?: Params) {
|
||||||
if (!params) return base;
|
if (!params) return base;
|
||||||
|
|
@ -44,12 +101,12 @@ export class HiFiClient {
|
||||||
return Buffer.from(`${id}:${secret}`).toString('base64');
|
return Buffer.from(`${id}:${secret}`).toString('base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async fetchAppToken(signal: AbortSignal = new AbortController().signal) {
|
private static async fetchAppToken(
|
||||||
const now = Date.now();
|
signal: AbortSignal = new AbortController().signal,
|
||||||
HiFiClient.token ??= localStorage.getItem('hifi_token') || null;
|
clientId: string,
|
||||||
HiFiClient.appTokenExpiry = Number(localStorage.getItem('hifi_token_expiry') || '0');
|
clientSecret: string
|
||||||
|
) {
|
||||||
if (HiFiClient.token && now < HiFiClient.appTokenExpiry) return HiFiClient.token;
|
if (HiFiClient.token && Date.now() < HiFiClient.appTokenExpiry) return HiFiClient.token;
|
||||||
|
|
||||||
return await (HiFiClient.tokenPromise ??= (async () => {
|
return await (HiFiClient.tokenPromise ??= (async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -57,12 +114,12 @@ export class HiFiClient {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'content-type': 'application/x-www-form-urlencoded',
|
'content-type': 'application/x-www-form-urlencoded',
|
||||||
authorization: `Basic ${this.encodeBasic(CLIENT_ID, CLIENT_SECRET)}`,
|
authorization: `Basic ${this.encodeBasic(clientId, clientSecret)}`,
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
grant_type: 'client_credentials',
|
grant_type: 'client_credentials',
|
||||||
client_id: CLIENT_ID,
|
client_id: clientId,
|
||||||
client_secret: CLIENT_SECRET,
|
client_secret: clientSecret,
|
||||||
}),
|
}),
|
||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
|
|
@ -77,8 +134,6 @@ export class HiFiClient {
|
||||||
const expires_in = json.expires_in ?? 3600;
|
const expires_in = json.expires_in ?? 3600;
|
||||||
HiFiClient.token = token;
|
HiFiClient.token = token;
|
||||||
HiFiClient.appTokenExpiry = Date.now() + expires_in * 1000 - 60_000;
|
HiFiClient.appTokenExpiry = Date.now() + expires_in * 1000 - 60_000;
|
||||||
localStorage.setItem('hifi_token', token);
|
|
||||||
localStorage.setItem('hifi_token_expiry', HiFiClient.appTokenExpiry.toString());
|
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -87,14 +142,18 @@ export class HiFiClient {
|
||||||
})());
|
})());
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(countryCode = 'US') {
|
constructor(clientId = BROWSER_CLIENT_ID, clientSecret = BROWSER_CLIENT_SECRET, countryCode = 'US') {
|
||||||
this.countryCode = countryCode;
|
this.countryCode = countryCode;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchJson(url: string, params?: Params, signal: AbortSignal = new AbortController().signal) {
|
private async fetchJson(url: string, params?: Params, signal: AbortSignal = new AbortController().signal) {
|
||||||
const final = HiFiClient.buildUrl(url, params);
|
const final = HiFiClient.buildUrl(url, params);
|
||||||
const res = await fetch(final, {
|
const res = await fetch(final, {
|
||||||
headers: { authorization: `Bearer ${await HiFiClient.fetchAppToken(signal)}` },
|
headers: {
|
||||||
|
authorization: `Bearer ${await HiFiClient.fetchAppToken(signal, this.clientId, this.clientSecret)}`,
|
||||||
|
},
|
||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue