soundcloud-api-ts-next 1.1.0 → 1.3.0

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
@@ -24,10 +24,13 @@ import { createSoundCloudRoutes } from "soundcloud-api-ts-next/server";
24
24
  const sc = createSoundCloudRoutes({
25
25
  clientId: process.env.SOUNDCLOUD_CLIENT_ID!,
26
26
  clientSecret: process.env.SOUNDCLOUD_CLIENT_SECRET!,
27
+ redirectUri: process.env.SOUNDCLOUD_REDIRECT_URI, // Required for OAuth
27
28
  });
28
29
 
29
30
  const handler = sc.handler();
30
31
  export const GET = handler;
32
+ export const POST = handler;
33
+ export const DELETE = handler;
31
34
  ```
32
35
 
33
36
  **Pages Router** (`pages/api/soundcloud/[...route].ts`):
@@ -38,6 +41,7 @@ import { createSoundCloudRoutes } from "soundcloud-api-ts-next/server";
38
41
  const sc = createSoundCloudRoutes({
39
42
  clientId: process.env.SOUNDCLOUD_CLIENT_ID!,
40
43
  clientSecret: process.env.SOUNDCLOUD_CLIENT_SECRET!,
44
+ redirectUri: process.env.SOUNDCLOUD_REDIRECT_URI,
41
45
  });
42
46
 
43
47
  export default sc.pagesHandler();
@@ -59,6 +63,131 @@ export default function Layout({ children }) {
59
63
  }
60
64
  ```
61
65
 
66
+ ## Authentication (OAuth + PKCE)
67
+
68
+ soundcloud-api-ts-next includes a complete OAuth flow with PKCE for secure user authentication.
69
+
70
+ ### Setup
71
+
72
+ 1. Add `redirectUri` to your server config (see above)
73
+ 2. Set your SoundCloud app's redirect URI to match (e.g., `http://localhost:3000/callback`)
74
+
75
+ ### Login Flow
76
+
77
+ ```tsx
78
+ import { useSCAuth } from "soundcloud-api-ts-next";
79
+
80
+ function LoginButton() {
81
+ const { isAuthenticated, user, login, logout, loading } = useSCAuth();
82
+
83
+ if (loading) return <p>Loading...</p>;
84
+
85
+ if (isAuthenticated) {
86
+ return (
87
+ <div>
88
+ <p>Logged in as {user?.username}</p>
89
+ <button onClick={logout}>Logout</button>
90
+ </div>
91
+ );
92
+ }
93
+
94
+ return <button onClick={login}>Login with SoundCloud</button>;
95
+ }
96
+ ```
97
+
98
+ ### Callback Page
99
+
100
+ Create a callback page that handles the OAuth redirect:
101
+
102
+ ```tsx
103
+ // app/callback/page.tsx
104
+ "use client";
105
+
106
+ import { useEffect } from "react";
107
+ import { useSearchParams, useRouter } from "next/navigation";
108
+ import { useSCAuth } from "soundcloud-api-ts-next";
109
+
110
+ export default function CallbackPage() {
111
+ const searchParams = useSearchParams();
112
+ const router = useRouter();
113
+ const { handleCallback } = useSCAuth();
114
+
115
+ useEffect(() => {
116
+ const code = searchParams.get("code");
117
+ const state = searchParams.get("state");
118
+ if (code && state) {
119
+ handleCallback(code, state).then(() => {
120
+ router.push("/");
121
+ });
122
+ }
123
+ }, [searchParams]);
124
+
125
+ return <p>Authenticating...</p>;
126
+ }
127
+ ```
128
+
129
+ ### Authenticated User Hooks
130
+
131
+ These hooks fetch data for the currently logged-in user. They require authentication and automatically pass the access token.
132
+
133
+ | Hook | Returns | Description |
134
+ |------|---------|-------------|
135
+ | `useMe()` | `HookResult<SoundCloudUser>` | Current user's profile |
136
+ | `useMeTracks()` | `HookResult<SoundCloudTrack[]>` | Current user's tracks |
137
+ | `useMeLikes()` | `HookResult<SoundCloudTrack[]>` | Current user's liked tracks |
138
+ | `useMePlaylists()` | `HookResult<SoundCloudPlaylist[]>` | Current user's playlists |
139
+ | `useMeFollowings()` | `HookResult<SoundCloudUser[]>` | Who current user follows |
140
+ | `useMeFollowers()` | `HookResult<SoundCloudUser[]>` | Current user's followers |
141
+
142
+ ```tsx
143
+ import { useMe, useMeTracks, useMeLikes } from "soundcloud-api-ts-next";
144
+
145
+ function MyProfile() {
146
+ const { data: me } = useMe();
147
+ const { data: tracks } = useMeTracks();
148
+ const { data: likes } = useMeLikes();
149
+
150
+ if (!me) return null;
151
+
152
+ return (
153
+ <div>
154
+ <h1>{me.username}</h1>
155
+ <p>{tracks?.length} tracks, {likes?.length} likes</p>
156
+ </div>
157
+ );
158
+ }
159
+ ```
160
+
161
+ ### Action Hooks
162
+
163
+ Mutation hooks for user actions. All require authentication.
164
+
165
+ | Hook | Methods | Description |
166
+ |------|---------|-------------|
167
+ | `useFollow()` | `follow(userId)`, `unfollow(userId)` | Follow/unfollow a user |
168
+ | `useLike()` | `likeTrack(trackId)`, `unlikeTrack(trackId)` | Like/unlike a track |
169
+ | `useRepost()` | `repostTrack(trackId)`, `unrepostTrack(trackId)` | Repost/unrepost a track |
170
+
171
+ ```tsx
172
+ import { useLike, useFollow } from "soundcloud-api-ts-next";
173
+
174
+ function TrackActions({ trackId, artistId }) {
175
+ const { likeTrack, unlikeTrack, loading: likeLoading } = useLike();
176
+ const { follow, loading: followLoading } = useFollow();
177
+
178
+ return (
179
+ <div>
180
+ <button onClick={() => likeTrack(trackId)} disabled={likeLoading}>
181
+ ❤️ Like
182
+ </button>
183
+ <button onClick={() => follow(artistId)} disabled={followLoading}>
184
+ ➕ Follow Artist
185
+ </button>
186
+ </div>
187
+ );
188
+ }
189
+ ```
190
+
62
191
  ## Hooks
63
192
 
64
193
  All hooks return `{ data, loading, error }`.
@@ -103,6 +232,39 @@ All hooks return `{ data, loading, error }`.
103
232
 
104
233
  All routes are available via the catch-all handler and as individual methods on the routes object.
105
234
 
235
+ ### Auth Routes
236
+
237
+ | Route | Method | Description |
238
+ |-------|--------|-------------|
239
+ | `/auth/login` | GET | Get SoundCloud OAuth URL (PKCE) |
240
+ | `/auth/callback?code=...&state=...` | GET | Exchange auth code for tokens |
241
+ | `/auth/refresh` | POST | Refresh access token |
242
+ | `/auth/logout` | POST | Sign out / revoke token |
243
+
244
+ ### Me Routes (require `Authorization: Bearer <token>` header)
245
+
246
+ | Route | Method | Description |
247
+ |-------|--------|-------------|
248
+ | `/me` | GET | Current user profile |
249
+ | `/me/tracks` | GET | Current user's tracks |
250
+ | `/me/likes` | GET | Current user's liked tracks |
251
+ | `/me/playlists` | GET | Current user's playlists |
252
+ | `/me/followings` | GET | Who current user follows |
253
+ | `/me/followers` | GET | Current user's followers |
254
+
255
+ ### Action Routes (require `Authorization: Bearer <token>` header)
256
+
257
+ | Route | Method | Description |
258
+ |-------|--------|-------------|
259
+ | `/me/follow/:userId` | POST | Follow a user |
260
+ | `/me/follow/:userId` | DELETE | Unfollow a user |
261
+ | `/tracks/:id/like` | POST | Like a track |
262
+ | `/tracks/:id/like` | DELETE | Unlike a track |
263
+ | `/tracks/:id/repost` | POST | Repost a track |
264
+ | `/tracks/:id/repost` | DELETE | Unrepost a track |
265
+ | `/playlists/:id/like` | POST/DELETE | Like/unlike a playlist |
266
+ | `/playlists/:id/repost` | POST/DELETE | Repost/unrepost a playlist |
267
+
106
268
  ### Search
107
269
 
108
270
  | Route | Method | Description |
@@ -139,6 +301,57 @@ All routes are available via the catch-all handler and as individual methods on
139
301
  | `GET /playlists/:id` | `getPlaylist(id)` | Get playlist details |
140
302
  | `GET /playlists/:id/tracks` | `getPlaylistTracks(id)` | Get playlist tracks |
141
303
 
304
+ ## Pagination / Infinite Scroll
305
+
306
+ All paginated endpoints have `useInfinite*` hooks that handle cursor-based pagination automatically:
307
+
308
+ ```tsx
309
+ import { useInfiniteTrackSearch } from "soundcloud-api-ts-next";
310
+
311
+ function TrackList() {
312
+ const { data, loading, error, hasMore, loadMore } = useInfiniteTrackSearch("lofi");
313
+
314
+ return (
315
+ <div>
316
+ {data.map((track) => (
317
+ <div key={track.id}>{track.title}</div>
318
+ ))}
319
+ {loading && <p>Loading...</p>}
320
+ {error && <p>Error: {error.message}</p>}
321
+ {hasMore && <button onClick={loadMore}>Load More</button>}
322
+ </div>
323
+ );
324
+ }
325
+ ```
326
+
327
+ ### Available infinite hooks
328
+
329
+ | Hook | Description |
330
+ | ---- | ----------- |
331
+ | `useInfiniteTrackSearch(query, options?)` | Paginated track search |
332
+ | `useInfiniteUserSearch(query, options?)` | Paginated user search |
333
+ | `useInfinitePlaylistSearch(query, options?)` | Paginated playlist search |
334
+ | `useInfiniteUserTracks(userId)` | User's tracks |
335
+ | `useInfiniteUserPlaylists(userId)` | User's playlists |
336
+ | `useInfiniteUserLikes(userId)` | User's liked tracks |
337
+ | `useInfiniteUserFollowers(userId)` | User's followers |
338
+ | `useInfiniteUserFollowings(userId)` | User's followings |
339
+ | `useInfiniteTrackComments(trackId)` | Track comments |
340
+ | `useInfinitePlaylistTracks(playlistId)` | Playlist tracks |
341
+
342
+ All hooks return `InfiniteResult<T>`:
343
+
344
+ ```ts
345
+ interface InfiniteResult<T> {
346
+ data: T[]; // accumulated items across all pages
347
+ loading: boolean;
348
+ error: Error | null;
349
+ hasMore: boolean; // true if more pages exist
350
+ loadMore: () => void;
351
+ reset: () => void; // clear and refetch from page 1
352
+ }
353
+ ```
354
+
142
355
  ## Types
143
356
 
144
357
  All SoundCloud types are re-exported from `soundcloud-api-ts`:
@@ -150,6 +363,9 @@ import type {
150
363
  SoundCloudPlaylist,
151
364
  SoundCloudComment,
152
365
  SoundCloudStreams,
366
+ SoundCloudToken,
367
+ AuthState,
368
+ MutationResult,
153
369
  } from "soundcloud-api-ts-next";
154
370
  ```
155
371