soundcloud-api-ts 1.13.0 → 1.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -131,6 +131,11 @@ try {
131
131
  8. **Deduplication** — concurrent identical GETs share a single in-flight promise (`dedupe: true` by default). Prevents redundant fetches in SSR or concurrent component trees.
132
132
  9. **Cache** — pass a `SoundCloudCache` implementation in the constructor to cache GET responses. Base package defines the interface only; bring your own backend. `cacheTtlMs` defaults to 60000ms.
133
133
  10. **Retry hook** — pass `onRetry` to receive `RetryInfo` on each retry: `{ attempt, delayMs, reason, status?, url }`.
134
+ 11. **`sc.tracks.getTracks(ids[])`** — batch fetch multiple tracks by ID array in a single request. Returns `SoundCloudTrack[]` (may be shorter than input if some tracks are unavailable).
135
+ 12. **`sc.me.getConnections()`** — list linked social accounts. Requires user token. May require elevated API access.
136
+ 13. **TokenProvider / TokenStore interfaces** — in `src/auth/token-provider.ts`. Implement to integrate with NextAuth, Clerk, Redis, or any session framework. See `docs/auth-guide.md`.
137
+ 14. **Auth guide** — `docs/auth-guide.md` covers: client creds vs user tokens, full PKCE flow, auto-refresh, NextAuth/Clerk bridge patterns, 401 troubleshooting table (invalid_client / insufficient_scope / invalid_token / unauthorized_client).
138
+ 15. **OpenAPI tooling** — `pnpm openapi:sync` fetches the spec (if available), `pnpm openapi:coverage` reports implemented vs total. `src/client/registry.ts` is the source of truth — update it when adding new endpoints.
134
139
  6. **No env vars** — the package reads no environment variables. Pass `clientId`, `clientSecret`, and `redirectUri` directly to the constructor.
135
140
  7. **IDs can be numbers or strings** — all ID parameters accept `string | number`.
136
141
  8. **Search pagination** — search uses zero-based `pageNumber` (10 results per page), not cursor-based pagination.
@@ -141,9 +146,14 @@ try {
141
146
  src/
142
147
  index.ts — All public exports (source of truth)
143
148
  client/SoundCloudClient.ts — Main client class with all namespaced methods
144
- client/http.ts — scFetch, scFetchUrl (HTTP layer with retry)
149
+ client/http.ts — scFetch, scFetchUrl (HTTP layer with retry + RetryInfo hook)
145
150
  client/paginate.ts — paginate, paginateItems, fetchAll helpers
151
+ client/raw.ts — RawClient (sc.raw escape hatch)
152
+ client/dedupe.ts — InFlightDeduper (GET coalescing)
153
+ client/cache.ts — SoundCloudCache interface
154
+ client/registry.ts — IMPLEMENTED_OPERATIONS (OpenAPI coverage tracking)
146
155
  auth/ — Standalone auth functions + PKCE
156
+ auth/token-provider.ts — TokenProvider + TokenStore interfaces
147
157
  users/ — Standalone user functions (getMe, getUser, etc.)
148
158
  tracks/ — Standalone track functions
149
159
  playlists/ — Standalone playlist functions
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)]()
12
12
  [![docs](https://img.shields.io/badge/docs-TypeDoc-blue.svg)](https://twin-paws.github.io/soundcloud-api-ts/)
13
13
  [![GitHub stars](https://img.shields.io/github/stars/twin-paws/soundcloud-api-ts)](https://github.com/twin-paws/soundcloud-api-ts)
14
- [![OpenAPI Coverage](https://img.shields.io/badge/OpenAPI%20coverage-tracked-blue)](tools/coverage-baseline.json)
14
+ [![Endpoints](https://img.shields.io/badge/endpoints-51%20wrapped-blue)](https://github.com/twin-paws/soundcloud-api-ts/blob/main/src/client/registry.ts)
15
15
 
16
16
  The TypeScript SoundCloud API client built on the official API. Zero runtime dependencies, native `fetch`, OAuth 2.1 + PKCE, production-grade retry and deduplication, pluggable cache, raw escape hatch, and an interactive CLI.
17
17
 
@@ -589,6 +589,7 @@ See [CHANGELOG.md](CHANGELOG.md) for release history.
589
589
  ## Related Packages
590
590
 
591
591
  - **[soundcloud-api-ts-next](https://github.com/twin-paws/soundcloud-api-ts-next)** — React hooks + Next.js API route handlers built on this package. Use it when building Next.js apps that need SoundCloud data with secrets kept server-side.
592
+ - **[soundcloud-widget-react](https://github.com/twin-paws/soundcloud-widget-react)** — React component for the SoundCloud HTML5 Widget API. Embed SoundCloud players and control playback programmatically. Complements this package for full SoundCloud integration.
592
593
 
593
594
  ## Contributing
594
595
 
@@ -353,6 +353,14 @@ var RawClient = class {
353
353
  }
354
354
  };
355
355
 
356
+ // src/utils/base64.ts
357
+ var toBase64 = (value) => {
358
+ if (typeof Buffer !== "undefined") {
359
+ return Buffer.from(value).toString("base64");
360
+ }
361
+ return btoa(value);
362
+ };
363
+
356
364
  // src/client/SoundCloudClient.ts
357
365
  function resolveToken(tokenGetter, explicit) {
358
366
  const t = explicit ?? tokenGetter();
@@ -559,9 +567,7 @@ var SoundCloudClient = class _SoundCloudClient {
559
567
  * @see https://developers.soundcloud.com/docs/api/explorer/open-api#/oauth2/post_oauth2_token
560
568
  */
561
569
  async getClientToken() {
562
- const basicAuth = Buffer.from(
563
- `${this.config.clientId}:${this.config.clientSecret}`
564
- ).toString("base64");
570
+ const basicAuth = toBase64(`${this.config.clientId}:${this.config.clientSecret}`);
565
571
  return this.fetch({
566
572
  path: "/oauth/token",
567
573
  method: "POST",
@@ -620,13 +626,15 @@ var SoundCloudClient = class _SoundCloudClient {
620
626
  * @see https://developers.soundcloud.com/docs/api/explorer/open-api#/oauth2/post_oauth2_token
621
627
  */
622
628
  async refreshUserToken(refreshToken) {
629
+ const basicAuth = toBase64(`${this.config.clientId}:${this.config.clientSecret}`);
623
630
  return this.fetch({
624
631
  path: "/oauth/token",
625
632
  method: "POST",
633
+ headers: {
634
+ Authorization: `Basic ${basicAuth}`
635
+ },
626
636
  body: new URLSearchParams({
627
637
  grant_type: "refresh_token",
628
- client_id: this.config.clientId,
629
- client_secret: this.config.clientSecret,
630
638
  redirect_uri: this.config.redirectUri,
631
639
  refresh_token: refreshToken
632
640
  })
@@ -1071,6 +1079,11 @@ var SoundCloudClient = class _SoundCloudClient {
1071
1079
  * @param options - Optional token override
1072
1080
  * @returns Array of track objects (may be shorter than `ids` if some tracks are unavailable)
1073
1081
  * @throws {SoundCloudError} When the API returns an error
1082
+ * @throws {Error} When more than 200 IDs are provided
1083
+ *
1084
+ * @remarks
1085
+ * SoundCloud's API likely caps at ~200 IDs per request. Passing more than 200 IDs
1086
+ * will throw immediately without making a network request.
1074
1087
  *
1075
1088
  * @example
1076
1089
  * ```ts
@@ -1081,6 +1094,9 @@ var SoundCloudClient = class _SoundCloudClient {
1081
1094
  * @see https://developers.soundcloud.com/docs/api/explorer/open-api#/tracks/get_tracks
1082
1095
  */
1083
1096
  async getTracks(ids, options) {
1097
+ if (ids.length > 200) {
1098
+ throw new Error("getTracks: SoundCloud API supports a maximum of 200 IDs per request");
1099
+ }
1084
1100
  const t = resolveToken(this.getToken, options?.token);
1085
1101
  return this.fetch({ path: `/tracks?ids=${ids.join(",")}`, method: "GET", token: t });
1086
1102
  }
@@ -1725,7 +1741,7 @@ var IMPLEMENTED_OPERATIONS = [
1725
1741
 
1726
1742
  // src/auth/getClientToken.ts
1727
1743
  var getClientToken = (clientId, clientSecret) => {
1728
- const basicAuth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
1744
+ const basicAuth = toBase64(`${clientId}:${clientSecret}`);
1729
1745
  return scFetch({
1730
1746
  path: "/oauth/token",
1731
1747
  method: "POST",
@@ -1757,13 +1773,15 @@ var getUserToken = (clientId, clientSecret, redirectUri, code, codeVerifier) =>
1757
1773
 
1758
1774
  // src/auth/refreshUserToken.ts
1759
1775
  var refreshUserToken = (clientId, clientSecret, redirectUri, refreshToken) => {
1776
+ const basicAuth = toBase64(`${clientId}:${clientSecret}`);
1760
1777
  return scFetch({
1761
1778
  path: "/oauth/token",
1762
1779
  method: "POST",
1780
+ headers: {
1781
+ Authorization: `Basic ${basicAuth}`
1782
+ },
1763
1783
  body: new URLSearchParams({
1764
1784
  grant_type: "refresh_token",
1765
- client_id: clientId,
1766
- client_secret: clientSecret,
1767
1785
  redirect_uri: redirectUri,
1768
1786
  refresh_token: refreshToken
1769
1787
  })
@@ -1843,11 +1861,16 @@ var getUserWebProfiles = (token, userId) => scFetch({ path: `/users/${userId}/we
1843
1861
  var getTrack = (token, trackId) => scFetch({ path: `/tracks/${trackId}`, method: "GET", token });
1844
1862
 
1845
1863
  // src/tracks/getTracks.ts
1846
- var getTracks = (token, ids) => scFetch({
1847
- path: `/tracks?ids=${ids.join(",")}`,
1848
- method: "GET",
1849
- token
1850
- });
1864
+ var getTracks = (token, ids) => {
1865
+ if (ids.length > 200) {
1866
+ throw new Error("getTracks: SoundCloud API supports a maximum of 200 IDs per request");
1867
+ }
1868
+ return scFetch({
1869
+ path: `/tracks?ids=${ids.join(",")}`,
1870
+ method: "GET",
1871
+ token
1872
+ });
1873
+ };
1851
1874
 
1852
1875
  // src/tracks/getComments.ts
1853
1876
  var getTrackComments = (token, trackId, limit) => scFetch({ path: `/tracks/${trackId}/comments?threaded=1&filter_replies=0${limit ? `&limit=${limit}` : ""}&linked_partitioning=true`, method: "GET", token });
@@ -2026,5 +2049,5 @@ var unrepostPlaylist = async (token, playlistId) => {
2026
2049
  var getSoundCloudWidgetUrl = (trackId) => `https%3A//api.soundcloud.com/tracks/${trackId}&show_teaser=false&color=%2300a99d&inverse=false&show_user=false&sharing=false&buying=false&liking=false&show_artwork=false&show_name=false`;
2027
2050
 
2028
2051
  export { IMPLEMENTED_OPERATIONS, InFlightDeduper, RawClient, SoundCloudClient, createPlaylist, createTrackComment, deletePlaylist, deleteTrack, fetchAll, followUser, generateCodeChallenge, generateCodeVerifier, getAuthorizationUrl, getClientToken, getFollowers, getFollowings, getMe, getMeActivities, getMeActivitiesOwn, getMeActivitiesTracks, getMeConnections, getMeFollowers, getMeFollowings, getMeFollowingsTracks, getMeLikesPlaylists, getMeLikesTracks, getMePlaylists, getMeTracks, getPlaylist, getPlaylistReposts, getPlaylistTracks, getRelatedTracks, getSoundCloudWidgetUrl, getTrack, getTrackComments, getTrackLikes, getTrackReposts, getTrackStreams, getTracks, getUser, getUserLikesPlaylists, getUserLikesTracks, getUserPlaylists, getUserToken, getUserTracks, getUserWebProfiles, likePlaylist, likeTrack, paginate, paginateItems, refreshUserToken, repostPlaylist, repostTrack, resolveUrl, scFetch, scFetchUrl, searchPlaylists, searchTracks, searchUsers, signOut, unfollowUser, unlikePlaylist, unlikeTrack, unrepostPlaylist, unrepostTrack, updatePlaylist, updateTrack };
2029
- //# sourceMappingURL=chunk-JH7TLL2C.mjs.map
2030
- //# sourceMappingURL=chunk-JH7TLL2C.mjs.map
2052
+ //# sourceMappingURL=chunk-CCHK5S6S.mjs.map
2053
+ //# sourceMappingURL=chunk-CCHK5S6S.mjs.map