soundcloud-api-ts-next 1.2.0 → 1.3.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/README.md CHANGED
@@ -1,38 +1,44 @@
1
1
  # soundcloud-api-ts-next
2
2
 
3
- Next.js integration for [soundcloud-api-ts](https://github.com/twin-paws/soundcloud-api-ts) — React hooks + secure API route handlers.
3
+ [![npm version](https://img.shields.io/npm/v/soundcloud-api-ts-next)](https://www.npmjs.com/package/soundcloud-api-ts-next)
4
+ [![npm downloads](https://img.shields.io/npm/dm/soundcloud-api-ts-next)](https://www.npmjs.com/package/soundcloud-api-ts-next)
5
+ [![license](https://img.shields.io/npm/l/soundcloud-api-ts-next)](https://github.com/twin-paws/soundcloud-api-ts-next/blob/main/LICENSE)
4
6
 
5
- ## Installation
7
+ React hooks and Next.js API route handlers for the SoundCloud API. Client secrets stay on the server.
8
+
9
+ Built on [soundcloud-api-ts](https://github.com/twin-paws/soundcloud-api-ts).
10
+
11
+ ## Install
6
12
 
7
13
  ```bash
8
14
  npm install soundcloud-api-ts-next
9
- # or
10
- pnpm add soundcloud-api-ts-next
11
15
  ```
12
16
 
13
- ## Setup
14
-
15
- ### 1. Server Routes
17
+ ## Quick Start
16
18
 
17
- Create an API route handler that proxies SoundCloud requests (keeps your credentials server-side).
18
-
19
- **App Router** (`app/api/soundcloud/[...route]/route.ts`):
19
+ **1. Create API routes** secrets stay server-side:
20
20
 
21
21
  ```ts
22
+ // app/api/soundcloud/[...route]/route.ts
22
23
  import { createSoundCloudRoutes } from "soundcloud-api-ts-next/server";
23
24
 
24
25
  const sc = createSoundCloudRoutes({
25
26
  clientId: process.env.SOUNDCLOUD_CLIENT_ID!,
26
27
  clientSecret: process.env.SOUNDCLOUD_CLIENT_SECRET!,
28
+ redirectUri: process.env.SOUNDCLOUD_REDIRECT_URI, // for OAuth
27
29
  });
28
30
 
29
31
  const handler = sc.handler();
30
32
  export const GET = handler;
33
+ export const POST = handler;
34
+ export const DELETE = handler;
31
35
  ```
32
36
 
33
- **Pages Router** (`pages/api/soundcloud/[...route].ts`):
37
+ <details>
38
+ <summary>Pages Router setup</summary>
34
39
 
35
40
  ```ts
41
+ // pages/api/soundcloud/[...route].ts
36
42
  import { createSoundCloudRoutes } from "soundcloud-api-ts-next/server";
37
43
 
38
44
  const sc = createSoundCloudRoutes({
@@ -42,12 +48,12 @@ const sc = createSoundCloudRoutes({
42
48
 
43
49
  export default sc.pagesHandler();
44
50
  ```
51
+ </details>
45
52
 
46
- ### 2. Client Provider
47
-
48
- Wrap your app with the `SoundCloudProvider`:
53
+ **2. Add the provider:**
49
54
 
50
55
  ```tsx
56
+ // app/layout.tsx
51
57
  import { SoundCloudProvider } from "soundcloud-api-ts-next";
52
58
 
53
59
  export default function Layout({ children }) {
@@ -59,140 +65,239 @@ export default function Layout({ children }) {
59
65
  }
60
66
  ```
61
67
 
68
+ **3. Use hooks:**
69
+
70
+ ```tsx
71
+ import { useTrackSearch, usePlayer } from "soundcloud-api-ts-next";
72
+
73
+ function SearchPage() {
74
+ const { data: tracks, loading } = useTrackSearch("lofi beats");
75
+
76
+ if (loading) return <p>Searching...</p>;
77
+
78
+ return tracks?.map((track) => (
79
+ <div key={track.id}>
80
+ <p>{track.title} — {track.user.username}</p>
81
+ </div>
82
+ ));
83
+ }
84
+ ```
85
+
86
+ ---
87
+
62
88
  ## Hooks
63
89
 
64
90
  All hooks return `{ data, loading, error }`.
65
91
 
66
92
  ### Tracks
67
93
 
68
- | Hook | Arguments | Description |
69
- |------|-----------|-------------|
70
- | `useTrack(trackId)` | `string \| number \| undefined` | Fetch a single track |
71
- | `useTrackSearch(query, options?)` | `string`, `{ limit? }` | Search tracks |
72
- | `useTrackComments(trackId)` | `string \| number \| undefined` | Get track comments |
73
- | `useTrackLikes(trackId)` | `string \| number \| undefined` | Get users who liked a track |
74
- | `useRelatedTracks(trackId)` | `string \| number \| undefined` | Get related tracks |
94
+ | Hook | Description |
95
+ |------|-------------|
96
+ | `useTrack(id)` | Single track |
97
+ | `useTrackSearch(query)` | Search tracks |
98
+ | `useTrackComments(id)` | Track comments |
99
+ | `useTrackLikes(id)` | Users who liked a track |
100
+ | `useRelatedTracks(id)` | Related tracks |
101
+ | `usePlayer(streamUrl)` | Audio player — `{ playing, progress, duration, play, pause, toggle, seek }` |
75
102
 
76
103
  ### Users
77
104
 
78
- | Hook | Arguments | Description |
79
- |------|-----------|-------------|
80
- | `useUser(userId)` | `string \| number \| undefined` | Fetch a single user |
81
- | `useUserSearch(query)` | `string` | Search users |
82
- | `useUserTracks(userId)` | `string \| number \| undefined` | Get a user's tracks |
83
- | `useUserPlaylists(userId)` | `string \| number \| undefined` | Get a user's playlists |
84
- | `useUserLikes(userId)` | `string \| number \| undefined` | Get a user's liked tracks |
85
- | `useUserFollowers(userId)` | `string \| number \| undefined` | Get a user's followers |
86
- | `useUserFollowings(userId)` | `string \| number \| undefined` | Get a user's followings |
105
+ | Hook | Description |
106
+ |------|-------------|
107
+ | `useUser(id)` | Single user |
108
+ | `useUserSearch(query)` | Search users |
109
+ | `useUserTracks(id)` | User's tracks |
110
+ | `useUserPlaylists(id)` | User's playlists |
111
+ | `useUserLikes(id)` | User's liked tracks |
112
+ | `useUserFollowers(id)` | User's followers |
113
+ | `useUserFollowings(id)` | User's followings |
87
114
 
88
115
  ### Playlists
89
116
 
90
- | Hook | Arguments | Description |
91
- |------|-----------|-------------|
92
- | `usePlaylist(playlistId)` | `string \| number \| undefined` | Fetch a single playlist |
93
- | `usePlaylistTracks(playlistId)` | `string \| number \| undefined` | Get tracks in a playlist |
94
- | `usePlaylistSearch(query)` | `string` | Search playlists |
117
+ | Hook | Description |
118
+ |------|-------------|
119
+ | `usePlaylist(id)` | Single playlist |
120
+ | `usePlaylistSearch(query)` | Search playlists |
121
+ | `usePlaylistTracks(id)` | Playlist tracks |
95
122
 
96
- ### Player
123
+ ---
97
124
 
98
- | Hook | Arguments | Description |
99
- |------|-----------|-------------|
100
- | `usePlayer(streamUrl)` | `string \| undefined` | Audio player with play/pause/seek |
125
+ ## Infinite Scroll
101
126
 
102
- ## Server Routes
127
+ Cursor-based pagination with `loadMore()` and `reset()`. All return `InfiniteResult<T>`:
103
128
 
104
- All routes are available via the catch-all handler and as individual methods on the routes object.
129
+ ```ts
130
+ { data: T[], loading, error, hasMore, loadMore, reset }
131
+ ```
105
132
 
106
- ### Search
133
+ ```tsx
134
+ import { useInfiniteTrackSearch } from "soundcloud-api-ts-next";
107
135
 
108
- | Route | Method | Description |
109
- |-------|--------|-------------|
110
- | `GET /search/tracks?q=...` | `searchTracks(q, page?)` | Search tracks |
111
- | `GET /search/users?q=...` | `searchUsers(q)` | Search users |
112
- | `GET /search/playlists?q=...` | `searchPlaylists(q)` | Search playlists |
136
+ function Feed() {
137
+ const { data, loading, hasMore, loadMore } = useInfiniteTrackSearch("dubstep");
113
138
 
114
- ### Tracks
139
+ return (
140
+ <>
141
+ {data.map((track) => <TrackCard key={track.id} track={track} />)}
142
+ {hasMore && <button onClick={loadMore} disabled={loading}>Load More</button>}
143
+ </>
144
+ );
145
+ }
146
+ ```
115
147
 
116
- | Route | Method | Description |
117
- |-------|--------|-------------|
118
- | `GET /tracks/:id` | `getTrack(id)` | Get track details |
119
- | `GET /tracks/:id/stream` | `getTrackStreams(id)` | Get stream URLs |
120
- | `GET /tracks/:id/comments` | `getTrackComments(id)` | Get track comments |
121
- | `GET /tracks/:id/likes` | `getTrackLikes(id)` | Get track likes |
122
- | `GET /tracks/:id/related` | `getRelatedTracks(id)` | Get related tracks |
148
+ | Hook | Description |
149
+ |------|-------------|
150
+ | `useInfiniteTrackSearch(query)` | Paginated track search |
151
+ | `useInfiniteUserSearch(query)` | Paginated user search |
152
+ | `useInfinitePlaylistSearch(query)` | Paginated playlist search |
153
+ | `useInfiniteUserTracks(id)` | User's tracks |
154
+ | `useInfiniteUserPlaylists(id)` | User's playlists |
155
+ | `useInfiniteUserLikes(id)` | User's liked tracks |
156
+ | `useInfiniteUserFollowers(id)` | User's followers |
157
+ | `useInfiniteUserFollowings(id)` | User's followings |
158
+ | `useInfiniteTrackComments(id)` | Track comments |
159
+ | `useInfinitePlaylistTracks(id)` | Playlist tracks |
123
160
 
124
- ### Users
161
+ ---
125
162
 
126
- | Route | Method | Description |
127
- |-------|--------|-------------|
128
- | `GET /users/:id` | `getUser(id)` | Get user details |
129
- | `GET /users/:id/tracks` | `getUserTracks(id, limit?)` | Get user's tracks |
130
- | `GET /users/:id/playlists` | `getUserPlaylists(id)` | Get user's playlists |
131
- | `GET /users/:id/likes/tracks` | `getUserLikesTracks(id)` | Get user's liked tracks |
132
- | `GET /users/:id/followers` | `getFollowers(id)` | Get user's followers |
133
- | `GET /users/:id/followings` | `getFollowings(id)` | Get user's followings |
163
+ ## Authentication
134
164
 
135
- ### Playlists
165
+ Full OAuth 2.1 with PKCE. No secrets on the client.
136
166
 
137
- | Route | Method | Description |
138
- |-------|--------|-------------|
139
- | `GET /playlists/:id` | `getPlaylist(id)` | Get playlist details |
140
- | `GET /playlists/:id/tracks` | `getPlaylistTracks(id)` | Get playlist tracks |
167
+ ### Login
141
168
 
142
- ## Pagination / Infinite Scroll
169
+ ```tsx
170
+ import { useSCAuth } from "soundcloud-api-ts-next";
143
171
 
144
- All paginated endpoints have `useInfinite*` hooks that handle cursor-based pagination automatically:
172
+ function LoginButton() {
173
+ const { isAuthenticated, user, login, logout } = useSCAuth();
145
174
 
146
- ```tsx
147
- import { useInfiniteTrackSearch } from "soundcloud-api-ts-next";
175
+ if (isAuthenticated) {
176
+ return (
177
+ <div>
178
+ <p>Welcome, {user?.username}</p>
179
+ <button onClick={logout}>Logout</button>
180
+ </div>
181
+ );
182
+ }
148
183
 
149
- function TrackList() {
150
- const { data, loading, error, hasMore, loadMore } = useInfiniteTrackSearch("lofi");
184
+ return <button onClick={login}>Login with SoundCloud</button>;
185
+ }
186
+ ```
151
187
 
152
- return (
153
- <div>
154
- {data.map((track) => (
155
- <div key={track.id}>{track.title}</div>
156
- ))}
157
- {loading && <p>Loading...</p>}
158
- {error && <p>Error: {error.message}</p>}
159
- {hasMore && <button onClick={loadMore}>Load More</button>}
160
- </div>
161
- );
188
+ ### Callback Page
189
+
190
+ ```tsx
191
+ // app/callback/page.tsx
192
+ "use client";
193
+ import { useEffect } from "react";
194
+ import { useSearchParams, useRouter } from "next/navigation";
195
+ import { useSCAuth } from "soundcloud-api-ts-next";
196
+
197
+ export default function Callback() {
198
+ const params = useSearchParams();
199
+ const router = useRouter();
200
+ const { handleCallback } = useSCAuth();
201
+
202
+ useEffect(() => {
203
+ const code = params.get("code");
204
+ const state = params.get("state");
205
+ if (code && state) {
206
+ handleCallback(code, state).then(() => router.push("/"));
207
+ }
208
+ }, [params]);
209
+
210
+ return <p>Authenticating...</p>;
162
211
  }
163
212
  ```
164
213
 
165
- ### Available infinite hooks
214
+ ### Authenticated Hooks
215
+
216
+ Available after login. Automatically pass the user's access token.
166
217
 
167
218
  | Hook | Description |
168
- | ---- | ----------- |
169
- | `useInfiniteTrackSearch(query, options?)` | Paginated track search |
170
- | `useInfiniteUserSearch(query, options?)` | Paginated user search |
171
- | `useInfinitePlaylistSearch(query, options?)` | Paginated playlist search |
172
- | `useInfiniteUserTracks(userId)` | User's tracks |
173
- | `useInfiniteUserPlaylists(userId)` | User's playlists |
174
- | `useInfiniteUserLikes(userId)` | User's liked tracks |
175
- | `useInfiniteUserFollowers(userId)` | User's followers |
176
- | `useInfiniteUserFollowings(userId)` | User's followings |
177
- | `useInfiniteTrackComments(trackId)` | Track comments |
178
- | `useInfinitePlaylistTracks(playlistId)` | Playlist tracks |
179
-
180
- All hooks return `InfiniteResult<T>`:
219
+ |------|-------------|
220
+ | `useMe()` | Current user profile |
221
+ | `useMeTracks()` | Your tracks |
222
+ | `useMeLikes()` | Your liked tracks |
223
+ | `useMePlaylists()` | Your playlists |
224
+ | `useMeFollowings()` | Who you follow |
225
+ | `useMeFollowers()` | Your followers |
181
226
 
182
- ```ts
183
- interface InfiniteResult<T> {
184
- data: T[]; // accumulated items across all pages
185
- loading: boolean;
186
- error: Error | null;
187
- hasMore: boolean; // true if more pages exist
188
- loadMore: () => void;
189
- reset: () => void; // clear and refetch from page 1
227
+ ### Actions
228
+
229
+ Mutation hooks for authenticated users.
230
+
231
+ | Hook | Methods |
232
+ |------|---------|
233
+ | `useLike()` | `likeTrack(id)`, `unlikeTrack(id)` |
234
+ | `useFollow()` | `follow(userId)`, `unfollow(userId)` |
235
+ | `useRepost()` | `repostTrack(id)`, `unrepostTrack(id)` |
236
+
237
+ ```tsx
238
+ import { useLike, useFollow } from "soundcloud-api-ts-next";
239
+
240
+ function TrackActions({ trackId, artistId }) {
241
+ const { likeTrack } = useLike();
242
+ const { follow } = useFollow();
243
+
244
+ return (
245
+ <>
246
+ <button onClick={() => likeTrack(trackId)}>❤️ Like</button>
247
+ <button onClick={() => follow(artistId)}>➕ Follow</button>
248
+ </>
249
+ );
190
250
  }
191
251
  ```
192
252
 
253
+ ---
254
+
255
+ ## Server Routes
256
+
257
+ The catch-all handler exposes these routes automatically:
258
+
259
+ | Route | Method | Description |
260
+ |-------|--------|-------------|
261
+ | `/search/tracks?q=` | GET | Search tracks |
262
+ | `/search/users?q=` | GET | Search users |
263
+ | `/search/playlists?q=` | GET | Search playlists |
264
+ | `/tracks/:id` | GET | Track details |
265
+ | `/tracks/:id/stream` | GET | Stream URLs |
266
+ | `/tracks/:id/comments` | GET | Track comments |
267
+ | `/tracks/:id/likes` | GET | Track likes |
268
+ | `/tracks/:id/related` | GET | Related tracks |
269
+ | `/tracks/:id/like` | POST/DELETE | Like/unlike (auth) |
270
+ | `/tracks/:id/repost` | POST/DELETE | Repost/unrepost (auth) |
271
+ | `/users/:id` | GET | User details |
272
+ | `/users/:id/tracks` | GET | User tracks |
273
+ | `/users/:id/playlists` | GET | User playlists |
274
+ | `/users/:id/likes/tracks` | GET | User likes |
275
+ | `/users/:id/followers` | GET | User followers |
276
+ | `/users/:id/followings` | GET | User followings |
277
+ | `/playlists/:id` | GET | Playlist details |
278
+ | `/playlists/:id/tracks` | GET | Playlist tracks |
279
+ | `/playlists/:id/like` | POST/DELETE | Like/unlike (auth) |
280
+ | `/playlists/:id/repost` | POST/DELETE | Repost/unrepost (auth) |
281
+ | `/me` | GET | Current user (auth) |
282
+ | `/me/tracks` | GET | Your tracks (auth) |
283
+ | `/me/likes` | GET | Your likes (auth) |
284
+ | `/me/playlists` | GET | Your playlists (auth) |
285
+ | `/me/followings` | GET | Your followings (auth) |
286
+ | `/me/followers` | GET | Your followers (auth) |
287
+ | `/me/follow/:userId` | POST/DELETE | Follow/unfollow (auth) |
288
+ | `/auth/login` | GET | OAuth URL (PKCE) |
289
+ | `/auth/callback` | GET | Token exchange |
290
+ | `/auth/refresh` | POST | Refresh token |
291
+ | `/auth/logout` | POST | Sign out |
292
+ | `/next?url=` | GET | Pagination cursor |
293
+
294
+ Routes marked **(auth)** require `Authorization: Bearer <token>` header.
295
+
296
+ ---
297
+
193
298
  ## Types
194
299
 
195
- All SoundCloud types are re-exported from `soundcloud-api-ts`:
300
+ Re-exported from [soundcloud-api-ts](https://github.com/twin-paws/soundcloud-api-ts):
196
301
 
197
302
  ```ts
198
303
  import type {
@@ -201,9 +306,22 @@ import type {
201
306
  SoundCloudPlaylist,
202
307
  SoundCloudComment,
203
308
  SoundCloudStreams,
309
+ SoundCloudToken,
204
310
  } from "soundcloud-api-ts-next";
205
311
  ```
206
312
 
313
+ ---
314
+
315
+ ## Requirements
316
+
317
+ - **Next.js** 13+ (App Router or Pages Router)
318
+ - **React** 18+
319
+ - **soundcloud-api-ts** installed automatically as a dependency
320
+
207
321
  ## License
208
322
 
209
323
  MIT
324
+
325
+ ## Related
326
+
327
+ - [soundcloud-api-ts](https://github.com/twin-paws/soundcloud-api-ts) — The TypeScript-first SoundCloud API client this package is built on