Use HTTPS for service APIs
This commit is contained in:
parent
30d755f0b1
commit
1f3423f4d1
2 changed files with 73 additions and 69 deletions
|
|
@ -16,24 +16,28 @@ const Constants = {
|
|||
SERVICES: {
|
||||
lastfm: {
|
||||
name: "Last.fm",
|
||||
baseUrl: "http://ws.audioscrobbler.com/2.0",
|
||||
baseUrl: "https://ws.audioscrobbler.com/2.0",
|
||||
requiresApiKey: true,
|
||||
requiresToken: false,
|
||||
},
|
||||
librefm: {
|
||||
name: "Libre.fm",
|
||||
baseUrl: "http://libre.fm/api",
|
||||
baseUrl: "https://libre.fm/api",
|
||||
requiresApiKey: true,
|
||||
requiresToken: false,
|
||||
},
|
||||
listenbrainz: {
|
||||
name: "ListenBrainz",
|
||||
baseUrl: "http://api.listenbrainz.org/1",
|
||||
baseUrl: "https://api.listenbrainz.org/1",
|
||||
requiresApiKey: false,
|
||||
requiresToken: true,
|
||||
},
|
||||
} as Record<ServiceType, ServiceConfig>,
|
||||
|
||||
// Default request headers. Avoid setting a custom User-Agent here to prevent exposing
|
||||
// environment or build details; the runtime should manage safe headers.
|
||||
DEFAULT_HEADERS: {} as Record<string, string>,
|
||||
|
||||
// Last.fm/Libre.fm use these hashes for their generic album covers
|
||||
DEFAULT_COVER_HASHES: [
|
||||
"2a96cbd8b46e442fc41c2b86b821562f",
|
||||
|
|
|
|||
|
|
@ -3,119 +3,119 @@ import Constants from "../constants";
|
|||
import { incrementApiCall, recordServiceError } from "../utils/debug";
|
||||
|
||||
export abstract class BaseService implements ServiceClient {
|
||||
protected retryCount = 0;
|
||||
protected lastError = 0;
|
||||
protected retryCount = 0;
|
||||
protected lastError = 0;
|
||||
|
||||
abstract fetchLatestScrobble(): Promise<Track>;
|
||||
abstract validateCredentials(): Promise<boolean>;
|
||||
abstract getServiceName(): string;
|
||||
|
||||
protected log(...args: any[]): void {
|
||||
console.log(`[${this.getServiceName()}]`, ...args);
|
||||
console.log(`[${this.getServiceName()}]`, ...args);
|
||||
}
|
||||
|
||||
protected logError(...args: any[]): void {
|
||||
console.error(`[${this.getServiceName()}] Error:`, ...args);
|
||||
console.error(`[${this.getServiceName()}] Error:`, ...args);
|
||||
}
|
||||
|
||||
protected logVerbose(...args: any[]): void {
|
||||
// subclasses will check if verbose logging is enabled
|
||||
console.log(`[${this.getServiceName()}] Verbose:`, ...args);
|
||||
// subclasses will check if verbose logging is enabled
|
||||
console.log(`[${this.getServiceName()}] Verbose:`, ...args);
|
||||
}
|
||||
|
||||
protected async handleError(error: any): Promise<never> {
|
||||
this.lastError = error.error || 0;
|
||||
this.lastError = error.error || 0;
|
||||
|
||||
const errorMessage = this.getErrorMessage(error);
|
||||
this.logError(errorMessage);
|
||||
recordServiceError(
|
||||
const errorMessage = this.getErrorMessage(error);
|
||||
this.logError(errorMessage);
|
||||
recordServiceError(
|
||||
this.getServiceName().toLowerCase() as any,
|
||||
errorMessage,
|
||||
);
|
||||
);
|
||||
|
||||
throw new Error(`${this.getServiceName()} API Error: ${errorMessage}`);
|
||||
throw new Error(`${this.getServiceName()} API Error: ${errorMessage}`);
|
||||
}
|
||||
|
||||
protected getErrorMessage(error: any): string {
|
||||
if (error.error && Constants.API_ERROR_CODES[error.error]) {
|
||||
return Constants.API_ERROR_CODES[error.error];
|
||||
}
|
||||
return error.message || error.toString() || "Unknown error";
|
||||
if (error.error && Constants.API_ERROR_CODES[error.error]) {
|
||||
return Constants.API_ERROR_CODES[error.error];
|
||||
}
|
||||
return error.message || error.toString() || "Unknown error";
|
||||
}
|
||||
|
||||
protected async makeRequest(
|
||||
url: string,
|
||||
options: RequestInit = {},
|
||||
url: string,
|
||||
options: RequestInit = {},
|
||||
): Promise<any> {
|
||||
try {
|
||||
this.logVerbose(`Making request to: ${url}`);
|
||||
incrementApiCall();
|
||||
try {
|
||||
this.logVerbose(`Making request to: ${url}`);
|
||||
incrementApiCall();
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
"User-Agent": "Vendetta Multi-Service Scrobbler/3.0.0",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
"User-Agent": "Vendetta Multi-Service Scrobbler/3.0.0",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = new Error(
|
||||
`HTTP ${response.status}: ${response.statusText}`,
|
||||
);
|
||||
recordServiceError(
|
||||
if (!response.ok) {
|
||||
const error = new Error(
|
||||
`HTTP ${response.status}: ${response.statusText}`,
|
||||
);
|
||||
recordServiceError(
|
||||
this.getServiceName().toLowerCase() as any,
|
||||
error.message,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
await this.handleError(data);
|
||||
}
|
||||
if (data.error) {
|
||||
await this.handleError(data);
|
||||
}
|
||||
|
||||
this.retryCount = 0; // worked, reset retry counter
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.retryCount++;
|
||||
if (this.retryCount > Constants.MAX_RETRY_ATTEMPTS) {
|
||||
this.retryCount = 0;
|
||||
recordServiceError(
|
||||
this.retryCount = 0; // worked, reset retry counter
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.retryCount++;
|
||||
if (this.retryCount > Constants.MAX_RETRY_ATTEMPTS) {
|
||||
this.retryCount = 0;
|
||||
recordServiceError(
|
||||
this.getServiceName().toLowerCase() as any,
|
||||
`Max retries exceeded: ${(error as Error).message}`,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.logVerbose(
|
||||
`Request failed, retrying (${this.retryCount}/${Constants.MAX_RETRY_ATTEMPTS})`,
|
||||
);
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, Constants.RETRY_DELAY),
|
||||
);
|
||||
return this.makeRequest(url, options);
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.logVerbose(
|
||||
`Request failed, retrying (${this.retryCount}/${Constants.MAX_RETRY_ATTEMPTS})`,
|
||||
);
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, Constants.RETRY_DELAY),
|
||||
);
|
||||
return this.makeRequest(url, options);
|
||||
}
|
||||
}
|
||||
|
||||
protected isDefaultCover(cover?: string): boolean {
|
||||
if (!cover) return true;
|
||||
return Constants.DEFAULT_COVER_HASHES.some((hash) => cover.includes(hash));
|
||||
if (!cover) return true;
|
||||
return Constants.DEFAULT_COVER_HASHES.some((hash) => cover.includes(hash));
|
||||
}
|
||||
|
||||
protected processAlbumArt(cover?: string): string | null {
|
||||
if (!cover || this.isDefaultCover(cover)) {
|
||||
return null;
|
||||
}
|
||||
return cover;
|
||||
if (!cover || this.isDefaultCover(cover)) {
|
||||
return null;
|
||||
}
|
||||
return cover;
|
||||
}
|
||||
|
||||
public getLastError(): number {
|
||||
return this.lastError;
|
||||
return this.lastError;
|
||||
}
|
||||
|
||||
public resetRetryCount(): void {
|
||||
this.retryCount = 0;
|
||||
this.retryCount = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue