soundcloud-api-ts 1.10.1 → 1.11.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/AGENTS.md +4 -3
- package/README.md +29 -0
- package/dist/{chunk-RETVFKZM.mjs → chunk-5FCXAR2S.mjs} +83 -22
- package/dist/chunk-5FCXAR2S.mjs.map +1 -0
- package/dist/{chunk-YKKLNC4A.js → chunk-7IRQD552.js} +83 -22
- package/dist/chunk-7IRQD552.js.map +1 -0
- package/dist/cli.js +6 -6
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +26 -3
- package/dist/index.d.ts +26 -3
- package/dist/index.js +63 -63
- package/dist/index.mjs +1 -1
- package/llms.txt +11 -1
- package/package.json +1 -1
- package/dist/chunk-RETVFKZM.mjs.map +0 -1
- package/dist/chunk-YKKLNC4A.js.map +0 -1
|
@@ -30,16 +30,32 @@ async function parseErrorBody(response) {
|
|
|
30
30
|
return void 0;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
async function scFetch(options, refreshCtx) {
|
|
33
|
+
async function scFetch(options, refreshCtx, onRequest) {
|
|
34
34
|
const retryConfig = refreshCtx?.retry ?? DEFAULT_RETRY;
|
|
35
|
+
const telemetryCallback = onRequest ?? refreshCtx?.onRequest;
|
|
36
|
+
const startTime = Date.now();
|
|
37
|
+
let retryCount = 0;
|
|
38
|
+
let finalStatus = 0;
|
|
39
|
+
const emitTelemetry = (error) => {
|
|
40
|
+
if (!telemetryCallback) return;
|
|
41
|
+
telemetryCallback({
|
|
42
|
+
method: options.method,
|
|
43
|
+
path: options.path,
|
|
44
|
+
durationMs: Date.now() - startTime,
|
|
45
|
+
status: finalStatus,
|
|
46
|
+
retryCount,
|
|
47
|
+
...error ? { error } : {}
|
|
48
|
+
});
|
|
49
|
+
};
|
|
35
50
|
const execute = async (tokenOverride) => {
|
|
36
51
|
const isAuthPath = options.path.startsWith("/oauth");
|
|
37
52
|
const url = `${isAuthPath ? AUTH_BASE_URL : BASE_URL}${options.path}`;
|
|
38
53
|
const headers = {
|
|
39
|
-
Accept: "application/json"
|
|
54
|
+
Accept: "application/json",
|
|
55
|
+
...options.headers
|
|
40
56
|
};
|
|
41
57
|
const token = tokenOverride ?? options.token;
|
|
42
|
-
if (token) {
|
|
58
|
+
if (token && !headers["Authorization"]) {
|
|
43
59
|
headers["Authorization"] = `OAuth ${token}`;
|
|
44
60
|
}
|
|
45
61
|
let fetchBody;
|
|
@@ -64,22 +80,32 @@ async function scFetch(options, refreshCtx) {
|
|
|
64
80
|
body: fetchBody,
|
|
65
81
|
redirect: "manual"
|
|
66
82
|
});
|
|
83
|
+
finalStatus = response.status;
|
|
67
84
|
if (response.status === 302) {
|
|
68
85
|
const location = response.headers.get("location");
|
|
69
|
-
if (location)
|
|
86
|
+
if (location) {
|
|
87
|
+
emitTelemetry();
|
|
88
|
+
return location;
|
|
89
|
+
}
|
|
70
90
|
}
|
|
71
91
|
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
92
|
+
emitTelemetry();
|
|
72
93
|
return void 0;
|
|
73
94
|
}
|
|
74
95
|
if (response.ok) {
|
|
75
|
-
|
|
96
|
+
const result = response.json();
|
|
97
|
+
emitTelemetry();
|
|
98
|
+
return result;
|
|
76
99
|
}
|
|
77
100
|
if (!isRetryable(response.status)) {
|
|
78
101
|
const body2 = await parseErrorBody(response);
|
|
79
|
-
|
|
102
|
+
const err2 = new chunkMLVA534Z_js.SoundCloudError(response.status, response.statusText, body2);
|
|
103
|
+
emitTelemetry(err2.message);
|
|
104
|
+
throw err2;
|
|
80
105
|
}
|
|
81
106
|
lastResponse = response;
|
|
82
107
|
if (attempt < retryConfig.maxRetries) {
|
|
108
|
+
retryCount = attempt + 1;
|
|
83
109
|
const delayMs = getRetryDelay(response, attempt, retryConfig);
|
|
84
110
|
retryConfig.onDebug?.(
|
|
85
111
|
`Retry ${attempt + 1}/${retryConfig.maxRetries} after ${Math.round(delayMs)}ms (status ${response.status})`
|
|
@@ -88,7 +114,9 @@ async function scFetch(options, refreshCtx) {
|
|
|
88
114
|
}
|
|
89
115
|
}
|
|
90
116
|
const body = await parseErrorBody(lastResponse);
|
|
91
|
-
|
|
117
|
+
const err = new chunkMLVA534Z_js.SoundCloudError(lastResponse.status, lastResponse.statusText, body);
|
|
118
|
+
emitTelemetry(err.message);
|
|
119
|
+
throw err;
|
|
92
120
|
};
|
|
93
121
|
try {
|
|
94
122
|
return await execute();
|
|
@@ -101,29 +129,53 @@ async function scFetch(options, refreshCtx) {
|
|
|
101
129
|
throw err;
|
|
102
130
|
}
|
|
103
131
|
}
|
|
104
|
-
async function scFetchUrl(url, token, retryConfig) {
|
|
132
|
+
async function scFetchUrl(url, token, retryConfig, onRequest) {
|
|
105
133
|
const config = retryConfig ?? DEFAULT_RETRY;
|
|
106
134
|
const headers = { Accept: "application/json" };
|
|
107
135
|
if (token) headers["Authorization"] = `OAuth ${token}`;
|
|
136
|
+
const startTime = Date.now();
|
|
137
|
+
let retryCount = 0;
|
|
138
|
+
let finalStatus = 0;
|
|
139
|
+
const emitTelemetry = (error) => {
|
|
140
|
+
if (!onRequest) return;
|
|
141
|
+
onRequest({
|
|
142
|
+
method: "GET",
|
|
143
|
+
path: url,
|
|
144
|
+
durationMs: Date.now() - startTime,
|
|
145
|
+
status: finalStatus,
|
|
146
|
+
retryCount,
|
|
147
|
+
...error ? { error } : {}
|
|
148
|
+
});
|
|
149
|
+
};
|
|
108
150
|
let lastResponse;
|
|
109
151
|
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
|
|
110
152
|
const response = await fetch(url, { method: "GET", headers, redirect: "manual" });
|
|
153
|
+
finalStatus = response.status;
|
|
111
154
|
if (response.status === 302) {
|
|
112
155
|
const location = response.headers.get("location");
|
|
113
|
-
if (location)
|
|
156
|
+
if (location) {
|
|
157
|
+
emitTelemetry();
|
|
158
|
+
return location;
|
|
159
|
+
}
|
|
114
160
|
}
|
|
115
161
|
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
162
|
+
emitTelemetry();
|
|
116
163
|
return void 0;
|
|
117
164
|
}
|
|
118
165
|
if (response.ok) {
|
|
119
|
-
|
|
166
|
+
const result = response.json();
|
|
167
|
+
emitTelemetry();
|
|
168
|
+
return result;
|
|
120
169
|
}
|
|
121
170
|
if (!isRetryable(response.status)) {
|
|
122
171
|
const body2 = await parseErrorBody(response);
|
|
123
|
-
|
|
172
|
+
const err2 = new chunkMLVA534Z_js.SoundCloudError(response.status, response.statusText, body2);
|
|
173
|
+
emitTelemetry(err2.message);
|
|
174
|
+
throw err2;
|
|
124
175
|
}
|
|
125
176
|
lastResponse = response;
|
|
126
177
|
if (attempt < config.maxRetries) {
|
|
178
|
+
retryCount = attempt + 1;
|
|
127
179
|
const delayMs = getRetryDelay(response, attempt, config);
|
|
128
180
|
config.onDebug?.(
|
|
129
181
|
`Retry ${attempt + 1}/${config.maxRetries} after ${Math.round(delayMs)}ms (status ${response.status})`
|
|
@@ -132,7 +184,9 @@ async function scFetchUrl(url, token, retryConfig) {
|
|
|
132
184
|
}
|
|
133
185
|
}
|
|
134
186
|
const body = await parseErrorBody(lastResponse);
|
|
135
|
-
|
|
187
|
+
const err = new chunkMLVA534Z_js.SoundCloudError(lastResponse.status, lastResponse.statusText, body);
|
|
188
|
+
emitTelemetry(err.message);
|
|
189
|
+
throw err;
|
|
136
190
|
}
|
|
137
191
|
|
|
138
192
|
// src/client/paginate.ts
|
|
@@ -211,14 +265,16 @@ exports.SoundCloudClient = class _SoundCloudClient {
|
|
|
211
265
|
return result;
|
|
212
266
|
},
|
|
213
267
|
setToken: (a, r) => this.setToken(a, r),
|
|
214
|
-
retry: retryConfig
|
|
268
|
+
retry: retryConfig,
|
|
269
|
+
onRequest: config.onRequest
|
|
215
270
|
} : {
|
|
216
271
|
getToken,
|
|
217
272
|
setToken: (
|
|
218
273
|
/* v8 ignore next */
|
|
219
274
|
(a, r) => this.setToken(a, r)
|
|
220
275
|
),
|
|
221
|
-
retry: retryConfig
|
|
276
|
+
retry: retryConfig,
|
|
277
|
+
onRequest: config.onRequest
|
|
222
278
|
};
|
|
223
279
|
this.auth = new _SoundCloudClient.Auth(this.config);
|
|
224
280
|
this.me = new _SoundCloudClient.Me(getToken, refreshCtx);
|
|
@@ -268,7 +324,8 @@ exports.SoundCloudClient = class _SoundCloudClient {
|
|
|
268
324
|
*/
|
|
269
325
|
paginate(firstPage) {
|
|
270
326
|
const token = this._accessToken;
|
|
271
|
-
|
|
327
|
+
const onReq = this.config.onRequest;
|
|
328
|
+
return paginate(firstPage, (url) => scFetchUrl(url, token, void 0, onReq));
|
|
272
329
|
}
|
|
273
330
|
/**
|
|
274
331
|
* Async generator that yields individual items across all pages.
|
|
@@ -285,7 +342,8 @@ exports.SoundCloudClient = class _SoundCloudClient {
|
|
|
285
342
|
*/
|
|
286
343
|
paginateItems(firstPage) {
|
|
287
344
|
const token = this._accessToken;
|
|
288
|
-
|
|
345
|
+
const onReq = this.config.onRequest;
|
|
346
|
+
return paginateItems(firstPage, (url) => scFetchUrl(url, token, void 0, onReq));
|
|
289
347
|
}
|
|
290
348
|
/**
|
|
291
349
|
* Collects all pages into a single flat array.
|
|
@@ -303,7 +361,8 @@ exports.SoundCloudClient = class _SoundCloudClient {
|
|
|
303
361
|
*/
|
|
304
362
|
fetchAll(firstPage, options) {
|
|
305
363
|
const token = this._accessToken;
|
|
306
|
-
|
|
364
|
+
const onReq = this.config.onRequest;
|
|
365
|
+
return fetchAll(firstPage, (url) => scFetchUrl(url, token, void 0, onReq), options);
|
|
307
366
|
}
|
|
308
367
|
};
|
|
309
368
|
((SoundCloudClient2) => {
|
|
@@ -1394,13 +1453,15 @@ exports.SoundCloudClient = class _SoundCloudClient {
|
|
|
1394
1453
|
|
|
1395
1454
|
// src/auth/getClientToken.ts
|
|
1396
1455
|
var getClientToken = (clientId, clientSecret) => {
|
|
1456
|
+
const basicAuth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
|
1397
1457
|
return scFetch({
|
|
1398
1458
|
path: "/oauth/token",
|
|
1399
1459
|
method: "POST",
|
|
1460
|
+
headers: {
|
|
1461
|
+
Authorization: `Basic ${basicAuth}`
|
|
1462
|
+
},
|
|
1400
1463
|
body: new URLSearchParams({
|
|
1401
|
-
grant_type: "client_credentials"
|
|
1402
|
-
client_id: clientId,
|
|
1403
|
-
client_secret: clientSecret
|
|
1464
|
+
grant_type: "client_credentials"
|
|
1404
1465
|
})
|
|
1405
1466
|
});
|
|
1406
1467
|
};
|
|
@@ -1743,5 +1804,5 @@ exports.unrepostPlaylist = unrepostPlaylist;
|
|
|
1743
1804
|
exports.unrepostTrack = unrepostTrack;
|
|
1744
1805
|
exports.updatePlaylist = updatePlaylist;
|
|
1745
1806
|
exports.updateTrack = updateTrack;
|
|
1746
|
-
//# sourceMappingURL=chunk-
|
|
1747
|
-
//# sourceMappingURL=chunk-
|
|
1807
|
+
//# sourceMappingURL=chunk-7IRQD552.js.map
|
|
1808
|
+
//# sourceMappingURL=chunk-7IRQD552.js.map
|