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 +226 -108
- package/dist/index.cjs +328 -3
- package/dist/index.d.cts +94 -2
- package/dist/index.d.ts +94 -2
- package/dist/index.mjs +421 -96
- package/dist/server.cjs +200 -9
- package/dist/server.d.cts +2 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.mjs +196 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,38 +1,44 @@
|
|
|
1
1
|
# soundcloud-api-ts-next
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/soundcloud-api-ts-next)
|
|
4
|
+
[](https://www.npmjs.com/package/soundcloud-api-ts-next)
|
|
5
|
+
[](https://github.com/twin-paws/soundcloud-api-ts-next/blob/main/LICENSE)
|
|
4
6
|
|
|
5
|
-
|
|
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
|
-
##
|
|
14
|
-
|
|
15
|
-
### 1. Server Routes
|
|
17
|
+
## Quick Start
|
|
16
18
|
|
|
17
|
-
Create
|
|
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
|
-
|
|
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
|
-
|
|
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 |
|
|
69
|
-
|
|
70
|
-
| `useTrack(
|
|
71
|
-
| `useTrackSearch(query
|
|
72
|
-
| `useTrackComments(
|
|
73
|
-
| `useTrackLikes(
|
|
74
|
-
| `useRelatedTracks(
|
|
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 |
|
|
79
|
-
|
|
80
|
-
| `useUser(
|
|
81
|
-
| `useUserSearch(query)` |
|
|
82
|
-
| `useUserTracks(
|
|
83
|
-
| `useUserPlaylists(
|
|
84
|
-
| `useUserLikes(
|
|
85
|
-
| `useUserFollowers(
|
|
86
|
-
| `useUserFollowings(
|
|
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 |
|
|
91
|
-
|
|
92
|
-
| `usePlaylist(
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
117
|
+
| Hook | Description |
|
|
118
|
+
|------|-------------|
|
|
119
|
+
| `usePlaylist(id)` | Single playlist |
|
|
120
|
+
| `usePlaylistSearch(query)` | Search playlists |
|
|
121
|
+
| `usePlaylistTracks(id)` | Playlist tracks |
|
|
95
122
|
|
|
96
|
-
|
|
123
|
+
---
|
|
97
124
|
|
|
98
|
-
|
|
99
|
-
|------|-----------|-------------|
|
|
100
|
-
| `usePlayer(streamUrl)` | `string \| undefined` | Audio player with play/pause/seek |
|
|
125
|
+
## Infinite Scroll
|
|
101
126
|
|
|
102
|
-
|
|
127
|
+
Cursor-based pagination with `loadMore()` and `reset()`. All return `InfiniteResult<T>`:
|
|
103
128
|
|
|
104
|
-
|
|
129
|
+
```ts
|
|
130
|
+
{ data: T[], loading, error, hasMore, loadMore, reset }
|
|
131
|
+
```
|
|
105
132
|
|
|
106
|
-
|
|
133
|
+
```tsx
|
|
134
|
+
import { useInfiniteTrackSearch } from "soundcloud-api-ts-next";
|
|
107
135
|
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
|
117
|
-
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
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
|
-
|
|
161
|
+
---
|
|
125
162
|
|
|
126
|
-
|
|
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
|
-
|
|
165
|
+
Full OAuth 2.1 with PKCE. No secrets on the client.
|
|
136
166
|
|
|
137
|
-
|
|
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
|
-
|
|
169
|
+
```tsx
|
|
170
|
+
import { useSCAuth } from "soundcloud-api-ts-next";
|
|
143
171
|
|
|
144
|
-
|
|
172
|
+
function LoginButton() {
|
|
173
|
+
const { isAuthenticated, user, login, logout } = useSCAuth();
|
|
145
174
|
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
150
|
-
|
|
184
|
+
return <button onClick={login}>Login with SoundCloud</button>;
|
|
185
|
+
}
|
|
186
|
+
```
|
|
151
187
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
###
|
|
214
|
+
### Authenticated Hooks
|
|
215
|
+
|
|
216
|
+
Available after login. Automatically pass the user's access token.
|
|
166
217
|
|
|
167
218
|
| Hook | Description |
|
|
168
|
-
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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
|