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.
@@ -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) return 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
- return response.json();
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
- throw new chunkMLVA534Z_js.SoundCloudError(response.status, response.statusText, body2);
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
- throw new chunkMLVA534Z_js.SoundCloudError(lastResponse.status, lastResponse.statusText, body);
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) return 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
- return response.json();
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
- throw new chunkMLVA534Z_js.SoundCloudError(response.status, response.statusText, body2);
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
- throw new chunkMLVA534Z_js.SoundCloudError(lastResponse.status, lastResponse.statusText, body);
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
- return paginate(firstPage, (url) => scFetchUrl(url, token));
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
- return paginateItems(firstPage, (url) => scFetchUrl(url, token));
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
- return fetchAll(firstPage, (url) => scFetchUrl(url, token), options);
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-YKKLNC4A.js.map
1747
- //# sourceMappingURL=chunk-YKKLNC4A.js.map
1807
+ //# sourceMappingURL=chunk-7IRQD552.js.map
1808
+ //# sourceMappingURL=chunk-7IRQD552.js.map