@spotify/web-api-ts-sdk
Version:
A typescript SDK for the Spotify Web API
238 lines (206 loc) • 11.7 kB
text/typescript
import AlbumsEndpoints from "./endpoints/AlbumsEndpoints.js";
import ArtistsEndpoints from "./endpoints/ArtistsEndpoints.js";
import AudiobooksEndpoints from "./endpoints/AudiobooksEndpoints.js";
import BrowseEndpoints from "./endpoints/BrowseEndpoints.js";
import ChaptersEndpoints from "./endpoints/ChaptersEndpoints.js";
import EpisodesEndpoints from "./endpoints/EpisodesEndpoints.js";
import RecommendationsEndpoints from "./endpoints/RecommendationsEndpoints.js";
import MarketsEndpoints from "./endpoints/MarketsEndpoints.js";
import PlayerEndpoints from "./endpoints/PlayerEndpoints.js";
import PlaylistsEndpoints from "./endpoints/PlaylistsEndpoints.js";
import SearchEndpoints, { SearchExecutionFunction } from "./endpoints/SearchEndpoints.js";
import ShowsEndpoints from "./endpoints/ShowsEndpoints.js";
import TracksEndpoints from "./endpoints/TracksEndpoints.js";
import IAuthStrategy, { isEmptyAccessToken } from "./auth/IAuthStrategy.js";
import UsersEndpoints from "./endpoints/UsersEndpoints.js";
import CurrentUserEndpoints from "./endpoints/CurrentUserEndpoints.js";
import ClientCredentialsStrategy from "./auth/ClientCredentialsStrategy.js";
import ImplicitGrantStrategy from "./auth/ImplicitGrantStrategy.js";
import AuthorizationCodeWithPKCEStrategy from "./auth/AuthorizationCodeWithPKCEStrategy.js";
import DefaultResponseDeserializer from "./serialization/DefaultResponseDeserializer.js";
import DefaultResponseValidator from "./responsevalidation/DefaultResponseValidator.js";
import NoOpErrorHandler from "./errorhandling/NoOpErrorHandler.js";
import DocumentLocationRedirectionStrategy from "./redirection/DocumentLocationRedirectionStrategy.js";
import LocalStorageCachingStrategy from "./caching/LocalStorageCachingStrategy.js";
import InMemoryCachingStrategy from "./caching/InMemoryCachingStrategy.js";
import ProvidedAccessTokenStrategy from "./auth/ProvidedAccessTokenStrategy.js";
import type { AccessToken, SdkConfiguration, SdkOptions, AuthenticationResponse } from "./types.js";
export class SpotifyApi {
private sdkConfig: SdkConfiguration;
private static rootUrl: string = "https://api.spotify.com/v1/";
private authenticationStrategy: IAuthStrategy;
public albums: AlbumsEndpoints;
public artists: ArtistsEndpoints;
public audiobooks: AudiobooksEndpoints;
public browse: BrowseEndpoints;
public chapters: ChaptersEndpoints;
public episodes: EpisodesEndpoints;
public recommendations: RecommendationsEndpoints;
public markets: MarketsEndpoints;
public player: PlayerEndpoints;
public playlists: PlaylistsEndpoints;
public shows: ShowsEndpoints;
public tracks: TracksEndpoints;
public users: UsersEndpoints;
public search: SearchExecutionFunction;
public currentUser: CurrentUserEndpoints;
public constructor(authentication: IAuthStrategy, config?: SdkOptions) {
this.sdkConfig = this.initializeSdk(config);
this.albums = new AlbumsEndpoints(this);
this.artists = new ArtistsEndpoints(this);
this.audiobooks = new AudiobooksEndpoints(this);
this.browse = new BrowseEndpoints(this);
this.chapters = new ChaptersEndpoints(this);
this.episodes = new EpisodesEndpoints(this);
this.recommendations = new RecommendationsEndpoints(this);
this.markets = new MarketsEndpoints(this);
this.player = new PlayerEndpoints(this);
this.playlists = new PlaylistsEndpoints(this);
this.shows = new ShowsEndpoints(this);
this.tracks = new TracksEndpoints(this);
this.users = new UsersEndpoints(this);
this.currentUser = new CurrentUserEndpoints(this);
const search = new SearchEndpoints(this);
this.search = search.execute.bind(search);
this.authenticationStrategy = authentication;
this.authenticationStrategy.setConfiguration(this.sdkConfig);
}
public async makeRequest<TReturnType>(method: "GET" | "POST" | "PUT" | "DELETE", url: string, body: any = undefined, contentType: string | undefined = undefined): Promise<TReturnType> {
try {
const accessToken = await this.authenticationStrategy.getOrCreateAccessToken();
if (isEmptyAccessToken(accessToken)) {
console.warn("No access token found, authenticating now.");
return null as TReturnType;
}
const token = accessToken?.access_token;
const fullUrl = SpotifyApi.rootUrl + url;
const opts: RequestInit = {
method: method,
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": contentType ?? "application/json"
},
body: body ? typeof body === "string" ? body : JSON.stringify(body) : undefined
};
this.sdkConfig.beforeRequest(fullUrl, opts);
const result = await this.sdkConfig.fetch(fullUrl, opts);
this.sdkConfig.afterRequest(fullUrl, opts, result);
if (result.status === 204) {
return null as TReturnType;
}
await this.sdkConfig.responseValidator.validateResponse(result);
return this.sdkConfig.deserializer.deserialize<TReturnType>(result);
} catch (error) {
const handled = await this.sdkConfig.errorHandler.handleErrors(error);
if (!handled) {
throw error;
}
return null as TReturnType;
}
}
private initializeSdk(config: SdkOptions | undefined): SdkConfiguration {
const isBrowser = typeof window !== 'undefined';
const defaultConfig: SdkConfiguration = {
fetch: (req: RequestInfo | URL, init: RequestInit | undefined) => fetch(req, init),
beforeRequest: (_: string, __: RequestInit) => { },
afterRequest: (_: string, __: RequestInit, ___: Response) => { },
deserializer: new DefaultResponseDeserializer(),
responseValidator: new DefaultResponseValidator(),
errorHandler: new NoOpErrorHandler(),
redirectionStrategy: new DocumentLocationRedirectionStrategy(),
cachingStrategy: isBrowser
? new LocalStorageCachingStrategy()
: new InMemoryCachingStrategy()
};
return { ...defaultConfig, ...config };
}
public switchAuthenticationStrategy(authentication: IAuthStrategy) {
this.authenticationStrategy = authentication;
this.authenticationStrategy.setConfiguration(this.sdkConfig);
this.authenticationStrategy.getOrCreateAccessToken(); // trigger any redirects
}
/**
* Use this when you're running in a browser and you want to control when first authentication+redirect happens.
*/
public async authenticate(): Promise<AuthenticationResponse> {
const response = await this.authenticationStrategy.getOrCreateAccessToken(); // trigger any redirects
return {
authenticated: response.expires! > Date.now() && !isEmptyAccessToken(response),
accessToken: response
};
}
/**
* @returns the current access token. null implies the SpotifyApi is not yet authenticated.
*/
public async getAccessToken(): Promise<AccessToken | null> {
return this.authenticationStrategy.getAccessToken();
}
/**
* Removes the access token if it exists.
*/
public logOut(): void {
this.authenticationStrategy.removeAccessToken();
}
public static withUserAuthorization(clientId: string, redirectUri: string, scopes: string[] = [], config?: SdkOptions): SpotifyApi {
const strategy = new AuthorizationCodeWithPKCEStrategy(clientId, redirectUri, scopes);
return new SpotifyApi(strategy, config);
}
public static withClientCredentials(clientId: string, clientSecret: string, scopes: string[] = [], config?: SdkOptions): SpotifyApi {
const strategy = new ClientCredentialsStrategy(clientId, clientSecret, scopes);
return new SpotifyApi(strategy, config);
}
public static withImplicitGrant(clientId: string, redirectUri: string, scopes: string[] = [], config?: SdkOptions): SpotifyApi {
const strategy = new ImplicitGrantStrategy(clientId, redirectUri, scopes);
return new SpotifyApi(strategy, config);
}
/**
* Use this when you're running in a Node environment, and accepting the access token from a client-side `performUserAuthorization` call.
* You can also use this method if you already have an access token and don't want to use the built-in authentication strategies.
*/
public static withAccessToken(clientId: string, token: AccessToken, config?: SdkOptions): SpotifyApi {
const strategy = new ProvidedAccessTokenStrategy(clientId, token);
return new SpotifyApi(strategy, config);
}
/**
* Use this when you're running in the browser, and want to perform the user authorization flow to post back to your server with the access token.
* @param clientId Your Spotify client ID
* @param redirectUri The URI to redirect to after the user has authorized your app
* @param scopes The scopes to request
* @param postbackUrl The URL to post the access token to
* @param config Optional configuration
*/
public static async performUserAuthorization(clientId: string, redirectUri: string, scopes: string[], postbackUrl: string, config?: SdkOptions): Promise<AuthenticationResponse>;
/**
* Use this when you're running in the browser, and want to perform the user authorization flow to post back to your server with the access token.
* This overload is provided for you to perform the postback yourself, if you want to do something other than a simple HTTP POST to a URL - for example, if you want to use a WebSocket, or provide custom authentication.
* @param clientId Your Spotify client ID
* @param redirectUri The URI to redirect to after the user has authorized your app
* @param scopes The scopes to request
* @param onAuthorization A function to call with the access token where YOU perform the server-side postback
* @param config Optional configuration
*/
public static async performUserAuthorization(clientId: string, redirectUri: string, scopes: string[], onAuthorization: (token: AccessToken) => Promise<void>, config?: SdkOptions): Promise<AuthenticationResponse>;
public static async performUserAuthorization(clientId: string, redirectUri: string, scopes: string[], onAuthorizationOrUrl: ((token: AccessToken) => Promise<void>) | string, config?: SdkOptions): Promise<AuthenticationResponse> {
const strategy = new AuthorizationCodeWithPKCEStrategy(clientId, redirectUri, scopes);
const client = new SpotifyApi(strategy, config);
const accessToken = await client.authenticationStrategy.getOrCreateAccessToken();
if (!isEmptyAccessToken(accessToken)) {
if (typeof onAuthorizationOrUrl === "string") {
console.log("Posting access token to postback URL.");
await fetch(onAuthorizationOrUrl, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(accessToken)
});
} else {
await onAuthorizationOrUrl(accessToken);
}
}
return {
authenticated: accessToken.expires! > Date.now() && !isEmptyAccessToken(accessToken),
accessToken
};
}
}