soundcloud-api-ts 1.2.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 +51 -0
- package/dist/chunk-34DWTDWF.js +52 -0
- package/dist/chunk-34DWTDWF.js.map +1 -0
- package/dist/chunk-GKNBLKPB.mjs +50 -0
- package/dist/chunk-GKNBLKPB.mjs.map +1 -0
- package/dist/index.d.mts +18 -3
- package/dist/index.d.ts +18 -3
- package/dist/index.js +105 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +102 -34
- package/dist/index.mjs.map +1 -1
- package/dist/types/index.d.mts +60 -1
- package/dist/types/index.d.ts +60 -1
- package/dist/types/index.js +8 -0
- package/dist/types/index.mjs +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
|
+
import { SoundCloudError } from './chunk-GKNBLKPB.mjs';
|
|
2
|
+
export { SoundCloudError } from './chunk-GKNBLKPB.mjs';
|
|
3
|
+
|
|
1
4
|
// src/client/http.ts
|
|
2
5
|
var BASE_URL = "https://api.soundcloud.com";
|
|
6
|
+
var DEFAULT_RETRY = { maxRetries: 3, retryBaseDelay: 1e3 };
|
|
7
|
+
function delay(ms) {
|
|
8
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
}
|
|
10
|
+
function isRetryable(status) {
|
|
11
|
+
return status === 429 || status >= 500 && status <= 599;
|
|
12
|
+
}
|
|
13
|
+
function getRetryDelay(response, attempt, config) {
|
|
14
|
+
if (response.status === 429) {
|
|
15
|
+
const retryAfter = response.headers.get("retry-after");
|
|
16
|
+
if (retryAfter) {
|
|
17
|
+
const seconds = Number(retryAfter);
|
|
18
|
+
if (!Number.isNaN(seconds)) return seconds * 1e3;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const base = config.retryBaseDelay * Math.pow(2, attempt);
|
|
22
|
+
return base + Math.random() * base * 0.1;
|
|
23
|
+
}
|
|
24
|
+
async function parseErrorBody(response) {
|
|
25
|
+
try {
|
|
26
|
+
return await response.json();
|
|
27
|
+
} catch {
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
3
31
|
async function scFetch(options, refreshCtx) {
|
|
32
|
+
const retryConfig = refreshCtx?.retry ?? DEFAULT_RETRY;
|
|
4
33
|
const execute = async (tokenOverride) => {
|
|
5
34
|
const url = `${BASE_URL}${options.path}`;
|
|
6
35
|
const headers = {
|
|
@@ -24,32 +53,44 @@ async function scFetch(options, refreshCtx) {
|
|
|
24
53
|
} else if (options.contentType) {
|
|
25
54
|
headers["Content-Type"] = options.contentType;
|
|
26
55
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
56
|
+
let lastResponse;
|
|
57
|
+
for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
|
|
58
|
+
const response = await fetch(url, {
|
|
59
|
+
method: options.method,
|
|
60
|
+
headers,
|
|
61
|
+
body: fetchBody,
|
|
62
|
+
redirect: "manual"
|
|
63
|
+
});
|
|
64
|
+
if (response.status === 302) {
|
|
65
|
+
const location = response.headers.get("location");
|
|
66
|
+
if (location) return location;
|
|
67
|
+
}
|
|
68
|
+
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
if (response.ok) {
|
|
72
|
+
return response.json();
|
|
73
|
+
}
|
|
74
|
+
if (!isRetryable(response.status)) {
|
|
75
|
+
const body2 = await parseErrorBody(response);
|
|
76
|
+
throw new SoundCloudError(response.status, response.statusText, body2);
|
|
77
|
+
}
|
|
78
|
+
lastResponse = response;
|
|
79
|
+
if (attempt < retryConfig.maxRetries) {
|
|
80
|
+
const delayMs = getRetryDelay(response, attempt, retryConfig);
|
|
81
|
+
retryConfig.onDebug?.(
|
|
82
|
+
`Retry ${attempt + 1}/${retryConfig.maxRetries} after ${Math.round(delayMs)}ms (status ${response.status})`
|
|
83
|
+
);
|
|
84
|
+
await delay(delayMs);
|
|
37
85
|
}
|
|
38
86
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
throw new Error(
|
|
44
|
-
`SoundCloud API error: ${response.status} ${response.statusText}`
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
return response.json();
|
|
87
|
+
const body = await parseErrorBody(lastResponse);
|
|
88
|
+
throw new SoundCloudError(lastResponse.status, lastResponse.statusText, body);
|
|
48
89
|
};
|
|
49
90
|
try {
|
|
50
91
|
return await execute();
|
|
51
92
|
} catch (err) {
|
|
52
|
-
if (refreshCtx?.onTokenRefresh && err instanceof
|
|
93
|
+
if (refreshCtx?.onTokenRefresh && err instanceof SoundCloudError && err.status === 401) {
|
|
53
94
|
const newToken = await refreshCtx.onTokenRefresh();
|
|
54
95
|
refreshCtx.setToken(newToken.access_token, newToken.refresh_token);
|
|
55
96
|
return execute(newToken.access_token);
|
|
@@ -57,21 +98,38 @@ async function scFetch(options, refreshCtx) {
|
|
|
57
98
|
throw err;
|
|
58
99
|
}
|
|
59
100
|
}
|
|
60
|
-
async function scFetchUrl(url, token) {
|
|
101
|
+
async function scFetchUrl(url, token, retryConfig) {
|
|
102
|
+
const config = retryConfig ?? DEFAULT_RETRY;
|
|
61
103
|
const headers = { Accept: "application/json" };
|
|
62
104
|
if (token) headers["Authorization"] = `OAuth ${token}`;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
105
|
+
let lastResponse;
|
|
106
|
+
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
|
|
107
|
+
const response = await fetch(url, { method: "GET", headers, redirect: "manual" });
|
|
108
|
+
if (response.status === 302) {
|
|
109
|
+
const location = response.headers.get("location");
|
|
110
|
+
if (location) return location;
|
|
111
|
+
}
|
|
112
|
+
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
if (response.ok) {
|
|
116
|
+
return response.json();
|
|
117
|
+
}
|
|
118
|
+
if (!isRetryable(response.status)) {
|
|
119
|
+
const body2 = await parseErrorBody(response);
|
|
120
|
+
throw new SoundCloudError(response.status, response.statusText, body2);
|
|
121
|
+
}
|
|
122
|
+
lastResponse = response;
|
|
123
|
+
if (attempt < config.maxRetries) {
|
|
124
|
+
const delayMs = getRetryDelay(response, attempt, config);
|
|
125
|
+
config.onDebug?.(
|
|
126
|
+
`Retry ${attempt + 1}/${config.maxRetries} after ${Math.round(delayMs)}ms (status ${response.status})`
|
|
127
|
+
);
|
|
128
|
+
await delay(delayMs);
|
|
129
|
+
}
|
|
73
130
|
}
|
|
74
|
-
|
|
131
|
+
const body = await parseErrorBody(lastResponse);
|
|
132
|
+
throw new SoundCloudError(lastResponse.status, lastResponse.statusText, body);
|
|
75
133
|
}
|
|
76
134
|
|
|
77
135
|
// src/client/paginate.ts
|
|
@@ -124,14 +182,24 @@ var SoundCloudClient = class _SoundCloudClient {
|
|
|
124
182
|
constructor(config) {
|
|
125
183
|
this.config = config;
|
|
126
184
|
const getToken = () => this._accessToken;
|
|
185
|
+
const retryConfig = {
|
|
186
|
+
maxRetries: config.maxRetries ?? 3,
|
|
187
|
+
retryBaseDelay: config.retryBaseDelay ?? 1e3,
|
|
188
|
+
onDebug: config.onDebug
|
|
189
|
+
};
|
|
127
190
|
const refreshCtx = config.onTokenRefresh ? {
|
|
128
191
|
getToken,
|
|
129
192
|
onTokenRefresh: async () => {
|
|
130
193
|
const result = await config.onTokenRefresh(this);
|
|
131
194
|
return result;
|
|
132
195
|
},
|
|
133
|
-
setToken: (a, r) => this.setToken(a, r)
|
|
134
|
-
|
|
196
|
+
setToken: (a, r) => this.setToken(a, r),
|
|
197
|
+
retry: retryConfig
|
|
198
|
+
} : {
|
|
199
|
+
getToken,
|
|
200
|
+
setToken: (a, r) => this.setToken(a, r),
|
|
201
|
+
retry: retryConfig
|
|
202
|
+
};
|
|
135
203
|
this.auth = new _SoundCloudClient.Auth(this.config);
|
|
136
204
|
this.me = new _SoundCloudClient.Me(getToken, refreshCtx);
|
|
137
205
|
this.users = new _SoundCloudClient.Users(getToken, refreshCtx);
|