diff --git a/README.md b/README.md index 692bede..ea39a43 100644 --- a/README.md +++ b/README.md @@ -23,15 +23,17 @@ The full API of this library can be found in [api.md](api.md). ```js -import Spotify from '@stainless-commons/spotify'; +import { SpotifyClient } from '@stainless-commons/spotify'; -const client = new Spotify(); +const client = new SpotifyClient(); const album = await client.albums.retrieve('4aawyAB9vmqN3uQ7FjRGTy'); console.log(album.id); ``` +If no `auth` option is provided, the client reads the `SPOTIFY_ACCESS_TOKEN` environment variable automatically. + ## Authentication The SDK supports multiple authentication modes via the `SpotifyClient` class. Choose the mode that fits your use case. @@ -42,7 +44,7 @@ Use this when your app needs to access Spotify catalog data without a user conte ```ts -import { SpotifyClient } from '@stainless-commons/spotify/lib/auth'; +import { SpotifyClient } from '@stainless-commons/spotify'; const client = new SpotifyClient({ auth: { @@ -64,11 +66,13 @@ Use this when a user has authorized your app via OAuth and you have an access to ```ts -import { SpotifyClient } from '@stainless-commons/spotify/lib/auth'; +import { SpotifyClient } from '@stainless-commons/spotify'; -const client = new SpotifyClient({ - auth: process.env['SPOTIFY_ACCESS_TOKEN']!, -}); +// Reads SPOTIFY_ACCESS_TOKEN from the environment automatically: +const client = new SpotifyClient(); + +// Or pass the token explicitly: +// const client = new SpotifyClient({ auth: 'my-access-token' }); const me = await client.me.retrieve(); console.log(me.display_name); @@ -83,9 +87,10 @@ This library includes TypeScript definitions for all request params and response ```ts +import { SpotifyClient } from '@stainless-commons/spotify'; import Spotify from '@stainless-commons/spotify'; -const client = new Spotify(); +const client = new SpotifyClient(); const album: Spotify.AlbumRetrieveResponse = await client.albums.retrieve('4aawyAB9vmqN3uQ7FjRGTy'); ``` @@ -135,10 +140,9 @@ You can use the `maxRetries` option to configure or disable this: ```js // Configure the default for all requests: -const client = new Spotify({ +const client = new SpotifyClient({ maxRetries: 0, // default is 2 }); - // Or, configure per-request: await client.albums.retrieve('4aawyAB9vmqN3uQ7FjRGTy', { maxRetries: 5, @@ -152,7 +156,7 @@ Requests time out after 1 minute by default. You can configure this with a `time ```ts // Configure the default for all requests: -const client = new Spotify({ +const client = new SpotifyClient({ timeout: 20 * 1000, // 20 seconds (default is 1 minute) }); @@ -212,7 +216,7 @@ Unlike `.asResponse()` this method consumes the body, returning once it is parse ```ts -const client = new Spotify(); +const client = new SpotifyClient({ auth }); const response = await client.albums.retrieve('4aawyAB9vmqN3uQ7FjRGTy').asResponse(); console.log(response.headers.get('X-My-Header')); @@ -239,9 +243,8 @@ The log level can be configured in two ways: 2. Using the `logLevel` client option (overrides the environment variable if set) ```ts -import Spotify from '@stainless-commons/spotify'; - -const client = new Spotify({ +const client = new SpotifyClient({ + auth: process.env['SPOTIFY_AUTH_CLIENT'], logLevel: 'debug', // Show all log messages }); ``` @@ -272,7 +275,7 @@ import pino from 'pino'; const logger = pino(); -const client = new Spotify({ +const client = new SpotifyClient({ logger: logger.child({ name: 'Spotify' }), logLevel: 'debug', // Send all messages to pino, allowing it to filter }); @@ -339,7 +342,7 @@ Or pass it to the client: import Spotify from '@stainless-commons/spotify'; import fetch from 'my-fetch'; -const client = new Spotify({ fetch }); +const client = new SpotifyClient({ fetch }); ``` ### Fetch options @@ -349,7 +352,7 @@ If you want to set custom `fetch` options without overriding the `fetch` functio ```ts import Spotify from '@stainless-commons/spotify'; -const client = new Spotify({ +const client = new SpotifyClient({ fetchOptions: { // `RequestInit` options }, @@ -364,11 +367,11 @@ options to requests: **Node** [[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)] ```ts -import Spotify from '@stainless-commons/spotify'; +import { SpotifyClient } from '@stainless-commons/spotify'; import * as undici from 'undici'; const proxyAgent = new undici.ProxyAgent('http://localhost:8888'); -const client = new Spotify({ +const client = new SpotifyClient({ fetchOptions: { dispatcher: proxyAgent, }, @@ -378,9 +381,9 @@ const client = new Spotify({ **Bun** [[docs](https://bun.sh/guides/http/proxy)] ```ts -import Spotify from '@stainless-commons/spotify'; +import { SpotifyClient } from '@stainless-commons/spotify'; -const client = new Spotify({ +const client = new SpotifyClient({ fetchOptions: { proxy: 'http://localhost:8888', }, @@ -390,10 +393,10 @@ const client = new Spotify({ **Deno** [[docs](https://docs.deno.com/api/deno/~/Deno.createHttpClient)] ```ts -import Spotify from 'npm:@stainless-commons/spotify'; +import { SpotifyClient } from 'npm:@stainless-commons/spotify'; const httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } }); -const client = new Spotify({ +const client = new SpotifyClient({ fetchOptions: { client: httpClient, }, diff --git a/examples/auth-token.ts b/examples/auth-token.ts index 7941692..9417f8c 100644 --- a/examples/auth-token.ts +++ b/examples/auth-token.ts @@ -1,15 +1,16 @@ -import { SpotifyClient } from '@stainless-commons/spotify/lib/auth'; +import { SpotifyClient } from '@stainless-commons/spotify'; /** * Access Token flow: user-level auth with a pre-obtained token. * Required for user-specific endpoints (/me, saved tracks, user playlists). * - * Required env vars: - * SPOTIFY_ACCESS_TOKEN (user-scoped OAuth token) + * If SPOTIFY_ACCESS_TOKEN is set in the environment, you can omit the auth option: + * const client = new SpotifyClient(); + * + * Or pass it explicitly: + * const client = new SpotifyClient({ auth: process.env['SPOTIFY_ACCESS_TOKEN']! }); */ -const client = new SpotifyClient({ - auth: process.env['SPOTIFY_ACCESS_TOKEN']!, -}); +const client = new SpotifyClient(); async function main() { const me = await client.me.retrieve(); diff --git a/examples/oauth-client-creds.ts b/examples/oauth-client-creds.ts index 8d29854..0c279f5 100644 --- a/examples/oauth-client-creds.ts +++ b/examples/oauth-client-creds.ts @@ -1,4 +1,4 @@ -import { SpotifyClient } from '@stainless-commons/spotify/lib/auth'; +import { SpotifyClient } from '@stainless-commons/spotify'; /** * Client Credentials flow: app-level auth, no user context. diff --git a/src/index.ts b/src/index.ts index f4d6e00..6a1188d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ export { type Uploadable, toFile } from './core/uploads'; export { APIPromise } from './core/api-promise'; export { Spotify, type ClientOptions } from './client'; export { PagePromise } from './core/pagination'; +export { SpotifyClient, type SpotifyClientOptions } from './lib/spotify-client'; export { SpotifyError, APIError, diff --git a/src/lib/spotify-client.ts b/src/lib/spotify-client.ts index 93faabe..3706216 100644 --- a/src/lib/spotify-client.ts +++ b/src/lib/spotify-client.ts @@ -6,20 +6,25 @@ import { TokenManager } from './auth/token-manager'; import type { AuthConfig } from './auth/types'; export interface SpotifyClientOptions extends Omit { - auth: AuthConfig | string; + auth?: AuthConfig | string | undefined; } export class SpotifyClient extends Spotify { private tokenManager: TokenManager; private authConfig: AuthConfig; - constructor(options: SpotifyClientOptions) { - if (!options.auth) { - throw new Error('The `auth` option is required. Pass an access token string or an AuthConfig object.'); + constructor(options: SpotifyClientOptions = {}) { + const resolvedAuth = options.auth ?? process.env['SPOTIFY_ACCESS_TOKEN']; + + if (!resolvedAuth) { + throw new Error( + 'No authentication provided. Pass an `auth` option (access token string or AuthConfig object) ' + + 'or set the SPOTIFY_ACCESS_TOKEN environment variable.', + ); } const authConfig: AuthConfig = - typeof options.auth === 'string' ? { type: 'access_token', accessToken: options.auth } : options.auth; + typeof resolvedAuth === 'string' ? { type: 'access_token', accessToken: resolvedAuth } : resolvedAuth; const { auth: _auth, ...baseOptions } = options; super(baseOptions);