@audd/sdk 1.5.11 → 1.5.12

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
@@ -1,5 +1,6 @@
1
1
  # audd-node
2
2
 
3
+ [![Powered by AudD](https://img.shields.io/badge/Music_Recognition-AudD_API-2a4eef)](https://audd.io)
3
4
  [![CI](https://github.com/AudDMusic/audd-node/actions/workflows/ci.yml/badge.svg)](https://github.com/AudDMusic/audd-node/actions/workflows/ci.yml)
4
5
  [![Contract](https://github.com/AudDMusic/audd-node/actions/workflows/contract.yml/badge.svg)](https://github.com/AudDMusic/audd-node/actions/workflows/contract.yml)
5
6
  [![npm](https://img.shields.io/npm/v/@audd/sdk.svg)](https://www.npmjs.com/package/@audd/sdk)
@@ -52,7 +53,7 @@ found no match — distinct from an error, which throws.
52
53
  Pass the token literally:
53
54
 
54
55
  ```ts
55
- const audd = new AudD("d29ebb...");
56
+ const audd = new AudD("your-api-token");
56
57
  ```
57
58
 
58
59
  Or set `AUDD_API_TOKEN` in the environment and construct without
@@ -273,7 +274,7 @@ app.use(express.json());
273
274
  app.post("/audd-callback", async (req, res) => {
274
275
  const { match, notification } = await handleCallback(req);
275
276
  if (match) {
276
- console.log(`${match.song.artist} - ${match.song.title} score=${match.song.score}`);
277
+ console.log(`${match.song?.artist ?? "?"} - ${match.song?.title ?? "?"} score=${match.song?.score ?? "?"}`);
277
278
  for (const alt of match.alternatives) {
278
279
  // alternatives are variant catalog releases — different artist/title is possible
279
280
  console.log(` alt: ${alt.artist} - ${alt.title}`);
@@ -303,7 +304,7 @@ import { handleCallback } from "@audd/sdk";
303
304
  const app = Fastify();
304
305
  app.post("/audd-callback", async (req, reply) => {
305
306
  const { match } = await handleCallback(req);
306
- if (match) console.log(`${match.song.artist} — ${match.song.title}`);
307
+ if (match) console.log(`${match.song?.artist ?? "?"} — ${match.song?.title ?? "?"}`);
307
308
  return { ok: true };
308
309
  });
309
310
  ```
@@ -322,7 +323,7 @@ const router = new Router();
322
323
  app.use(bodyParser());
323
324
  router.post("/audd-callback", async (ctx) => {
324
325
  const { match } = await handleCallback(ctx.request);
325
- if (match) console.log(`${match.song.artist} — ${match.song.title}`);
326
+ if (match) console.log(`${match.song?.artist ?? "?"} — ${match.song?.title ?? "?"}`);
326
327
  ctx.body = { ok: true };
327
328
  });
328
329
  app.use(router.routes());
@@ -336,7 +337,7 @@ import { handleCallback } from "@audd/sdk";
336
337
 
337
338
  export async function POST(req: NextRequest) {
338
339
  const { match } = await handleCallback(req);
339
- if (match) console.log(`${match.song.artist} — ${match.song.title}`);
340
+ if (match) console.log(`${match.song?.artist ?? "?"} — ${match.song?.title ?? "?"}`);
340
341
  return NextResponse.json({ ok: true });
341
342
  }
342
343
  ```
@@ -358,7 +359,7 @@ const radioId = 1; // any integer you choose — your handle for this stream
358
359
 
359
360
  const poll = await audd.streams.longpoll({ radioId, timeout: 30 });
360
361
  for await (const m of poll.matches) {
361
- console.log(m.song.artist, m.song.title);
362
+ console.log(m.song?.artist ?? "?", m.song?.title ?? "?");
362
363
  }
363
364
  ```
364
365
 
@@ -368,7 +369,7 @@ Consume matches and notifications concurrently:
368
369
  await Promise.all([
369
370
  (async () => {
370
371
  for await (const m of poll.matches) {
371
- console.log("match:", m.song.artist, m.song.title);
372
+ console.log("match:", m.song?.artist ?? "?", m.song?.title ?? "?");
372
373
  }
373
374
  })(),
374
375
  (async () => {
@@ -401,7 +402,7 @@ import { LongpollConsumer } from "@audd/sdk/longpoll";
401
402
  const consumer = new LongpollConsumer("abc123def");
402
403
  const poll = consumer.iterate({ timeout: 30 });
403
404
  for await (const m of poll.matches) {
404
- console.log(m.song.artist, m.song.title);
405
+ console.log(m.song?.artist ?? "?", m.song?.title ?? "?");
405
406
  }
406
407
  ```
407
408
 
package/dist/index.cjs CHANGED
@@ -25,7 +25,7 @@ function _interopNamespace(e) {
25
25
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
26
26
 
27
27
  // src/version.ts
28
- var VERSION = "1.5.8";
28
+ var VERSION = "1.5.12";
29
29
 
30
30
  // src/errors.ts
31
31
  var AudDError = class extends Error {
@@ -98,6 +98,16 @@ var AudDConnectionError = class extends AudDError {
98
98
  this.cause = cause;
99
99
  }
100
100
  };
101
+ var USER_ABORT_ERROR_NAME = "UserAbortError";
102
+ function userAbortError(cause) {
103
+ const err = new Error("Request was cancelled by the caller's AbortSignal");
104
+ err.name = USER_ABORT_ERROR_NAME;
105
+ err.cause = cause;
106
+ return err;
107
+ }
108
+ function isUserAbortError(err) {
109
+ return err !== null && typeof err === "object" && err.name === USER_ABORT_ERROR_NAME;
110
+ }
101
111
  var AudDSerializationError = class extends AudDError {
102
112
  name = "AudDSerializationError";
103
113
  rawText;
@@ -170,6 +180,55 @@ function raiseFromErrorResponse(body, opts) {
170
180
  throw new Cls(fields);
171
181
  }
172
182
 
183
+ // src/events.ts
184
+ function safeEmit(hook, event) {
185
+ if (hook === void 0) return;
186
+ try {
187
+ hook(event);
188
+ } catch {
189
+ }
190
+ }
191
+ async function emitAround(hook, method, url, fn) {
192
+ const startedAt = Date.now();
193
+ safeEmit(hook, {
194
+ kind: "request",
195
+ method,
196
+ url,
197
+ requestId: null,
198
+ httpStatus: null,
199
+ elapsedMs: null,
200
+ errorCode: null,
201
+ extras: {}
202
+ });
203
+ let resp;
204
+ try {
205
+ resp = await fn();
206
+ } catch (err) {
207
+ safeEmit(hook, {
208
+ kind: "exception",
209
+ method,
210
+ url,
211
+ requestId: null,
212
+ httpStatus: null,
213
+ elapsedMs: Date.now() - startedAt,
214
+ errorCode: null,
215
+ extras: { name: err instanceof Error ? err.name : String(err) }
216
+ });
217
+ throw err;
218
+ }
219
+ safeEmit(hook, {
220
+ kind: "response",
221
+ method,
222
+ url,
223
+ requestId: resp.requestId,
224
+ httpStatus: resp.httpStatus,
225
+ elapsedMs: Date.now() - startedAt,
226
+ errorCode: null,
227
+ extras: {}
228
+ });
229
+ return resp;
230
+ }
231
+
173
232
  // src/userAgent.ts
174
233
  function userAgent() {
175
234
  const node = typeof process !== "undefined" && process.version ? process.version.replace(/^v/, "") : "unknown";
@@ -247,6 +306,11 @@ var HttpClient = class {
247
306
  requestId: response.headers.get("x-request-id"),
248
307
  rawText: text
249
308
  };
309
+ } catch (err) {
310
+ if (userSignal?.aborted === true && err !== null && typeof err === "object" && err.name === "AbortError") {
311
+ throw userAbortError(err);
312
+ }
313
+ throw err;
250
314
  } finally {
251
315
  clearTimeout(timer);
252
316
  if (userSignal !== void 0) {
@@ -271,10 +335,8 @@ function asObject(raw) {
271
335
  }
272
336
  return void 0;
273
337
  }
274
- function requireObject(raw, what) {
275
- const o = asObject(raw);
276
- if (o === void 0) throw new TypeError(`${what}: expected object`);
277
- return o;
338
+ function asObjectLenient(raw) {
339
+ return asObject(raw) ?? {};
278
340
  }
279
341
  function asString(v) {
280
342
  return typeof v === "string" ? v : void 0;
@@ -378,7 +440,7 @@ var RECOGNITION_KEYS = [
378
440
  "musicbrainz"
379
441
  ];
380
442
  function parseRecognitionResult(raw) {
381
- const r = requireObject(raw, "RecognitionResult");
443
+ const r = asObjectLenient(raw);
382
444
  const timecode = asString(r.timecode);
383
445
  const audioId = asNumber(r.audio_id);
384
446
  const artist = asString(r.artist);
@@ -453,7 +515,7 @@ var ENTERPRISE_MATCH_KEYS = [
453
515
  "end_seconds"
454
516
  ];
455
517
  function parseEnterpriseMatch(raw) {
456
- const r = requireObject(raw, "EnterpriseMatch");
518
+ const r = asObjectLenient(raw);
457
519
  const score = asNumber(r.score);
458
520
  const timecode = asString(r.timecode);
459
521
  const songLink = asString(r.song_link);
@@ -503,8 +565,8 @@ function parseEnterpriseMatch(raw) {
503
565
  }
504
566
  var ENTERPRISE_CHUNK_KEYS = ["songs", "offset"];
505
567
  function parseEnterpriseChunkResult(raw) {
506
- const r = requireObject(raw, "EnterpriseChunkResult");
507
- const songsRaw = Array.isArray(r.songs) ? r.songs : [];
568
+ const r = asObjectLenient(raw);
569
+ const songsRaw = Array.isArray(r.songs) ? r.songs.filter((s) => asObject(s) !== void 0) : [];
508
570
  const offset = asString(r.offset);
509
571
  const base = parseOffsetToSeconds(r.offset);
510
572
  const songs = songsRaw.map((s) => {
@@ -524,7 +586,7 @@ function parseEnterpriseChunkResult(raw) {
524
586
  }
525
587
  var STREAM_KEYS = ["radio_id", "url", "stream_running", "longpoll_category"];
526
588
  function parseStream(raw) {
527
- const r = requireObject(raw, "Stream");
589
+ const r = asObjectLenient(raw);
528
590
  const radioId = asNumber(r.radio_id);
529
591
  const url = asString(r.url);
530
592
  const streamRunning = asBoolean(r.stream_running);
@@ -554,7 +616,7 @@ var STREAM_CALLBACK_SONG_KEYS = [
554
616
  "musicbrainz"
555
617
  ];
556
618
  function parseStreamCallbackSong(raw) {
557
- const r = requireObject(raw, "StreamCallbackSong");
619
+ const r = asObjectLenient(raw);
558
620
  const artist = asString(r.artist);
559
621
  const title = asString(r.title);
560
622
  const score = asNumber(r.score);
@@ -584,7 +646,7 @@ var STREAM_CALLBACK_MATCH_KEYS = [
584
646
  "results"
585
647
  ];
586
648
  function parseStreamCallbackMatch(raw) {
587
- const r = requireObject(raw, "StreamCallbackMatch");
649
+ const r = asObjectLenient(raw);
588
650
  const radioId = asNumber(r.radio_id);
589
651
  const resultsRaw = Array.isArray(r.results) ? r.results : [];
590
652
  const songs = resultsRaw.filter((x) => asObject(x) !== void 0).map(parseStreamCallbackSong);
@@ -606,7 +668,7 @@ var STREAM_CALLBACK_NOTIFICATION_KEYS = [
606
668
  "notification_message"
607
669
  ];
608
670
  function parseStreamCallbackNotification(raw) {
609
- const r = requireObject(raw, "StreamCallbackNotification");
671
+ const r = asObjectLenient(raw);
610
672
  const radioId = asNumber(r.radio_id);
611
673
  const code = asNumber(r.notification_code);
612
674
  const message = asString(r.notification_message);
@@ -630,7 +692,7 @@ var LYRICS_KEYS = [
630
692
  "media"
631
693
  ];
632
694
  function parseLyricsResult(raw) {
633
- const r = requireObject(raw, "LyricsResult");
695
+ const r = asObjectLenient(raw);
634
696
  const artist = asString(r.artist);
635
697
  const title = asString(r.title);
636
698
  return {
@@ -726,6 +788,22 @@ async function retry(fn, policy) {
726
788
  if (haveResp && lastResp !== void 0) return lastResp;
727
789
  throw lastError;
728
790
  }
791
+ async function runRetried(fn, policy) {
792
+ try {
793
+ return await retry(fn, policy);
794
+ } catch (err) {
795
+ if (isUserAbortError(err)) {
796
+ throw new AudDConnectionError("Request was cancelled by the caller's AbortSignal", err);
797
+ }
798
+ if (err instanceof TypeError) {
799
+ throw new AudDConnectionError(`Network error: ${err.message}`, err);
800
+ }
801
+ if (err !== null && typeof err === "object" && err.name === "AbortError") {
802
+ throw new AudDConnectionError("Request was aborted (timeout)", err);
803
+ }
804
+ throw err;
805
+ }
806
+ }
729
807
  function looksLikeUrl(s) {
730
808
  return s.startsWith("http://") || s.startsWith("https://");
731
809
  }
@@ -977,13 +1055,13 @@ function decodeOne(resp) {
977
1055
  );
978
1056
  }
979
1057
  const dict = body;
980
- if ("notification" in dict) {
1058
+ if (asObject(dict.notification) !== void 0) {
981
1059
  const notification = parseStreamCallbackNotification(dict.notification);
982
1060
  const t = dict["time"];
983
1061
  if (typeof t === "number") notification.time = t;
984
1062
  return { kind: "notification", notification };
985
1063
  }
986
- if ("result" in dict) {
1064
+ if (asObject(dict.result) !== void 0) {
987
1065
  return { kind: "match", match: parseStreamCallbackMatch(dict.result) };
988
1066
  }
989
1067
  return { kind: "keepalive" };
@@ -1062,6 +1140,7 @@ function startLongpoll(cfg) {
1062
1140
 
1063
1141
  // src/streams.ts
1064
1142
  var API_BASE = "https://api.audd.io";
1143
+ var LONGPOLL_TIMEOUT_MARGIN_SEC = 10;
1065
1144
  var NO_CALLBACK_ERROR_CODE = 19;
1066
1145
  var PREFLIGHT_NO_CALLBACK_HINT = "Longpoll won't deliver events because no callback URL is configured for this account. Set one first via streams.setCallbackUrl(...) \u2014 `https://audd.tech/empty/` is fine if you only want longpolling and don't need a real receiver. To skip this check, pass skipCallbackCheck: true.";
1067
1146
  function decodeSuccess(body, httpStatus, requestId) {
@@ -1086,34 +1165,26 @@ function decodeSuccess(body, httpStatus, requestId) {
1086
1165
  rawResponse: obj
1087
1166
  });
1088
1167
  }
1089
- async function runRetried(fn, policy) {
1090
- try {
1091
- return await retry(fn, policy);
1092
- } catch (err) {
1093
- if (err instanceof TypeError) {
1094
- throw new AudDConnectionError(`Network error: ${err.message}`, err);
1095
- }
1096
- if (err !== null && typeof err === "object" && err.name === "AbortError") {
1097
- throw new AudDConnectionError("Request was aborted (timeout)", err);
1098
- }
1099
- throw err;
1100
- }
1101
- }
1102
1168
  var Streams = class {
1103
- constructor(http, readPolicy, mutatingPolicy, apiToken) {
1169
+ constructor(http, readPolicy, mutatingPolicy, apiToken, onEvent) {
1104
1170
  this.http = http;
1105
1171
  this.readPolicy = readPolicy;
1106
1172
  this.mutatingPolicy = mutatingPolicy;
1107
1173
  this.apiToken = apiToken;
1174
+ this.onEvent = onEvent;
1108
1175
  }
1109
1176
  http;
1110
1177
  readPolicy;
1111
1178
  mutatingPolicy;
1112
1179
  apiToken;
1180
+ onEvent;
1113
1181
  async post(path2, fields, policy) {
1114
- const resp = await runRetried(
1115
- () => this.http.postForm(`${API_BASE}/${path2}/`, fields),
1116
- policy
1182
+ const url = `${API_BASE}/${path2}/`;
1183
+ const resp = await emitAround(
1184
+ this.onEvent,
1185
+ path2,
1186
+ url,
1187
+ () => runRetried(() => this.http.postForm(url, fields), policy)
1117
1188
  );
1118
1189
  return decodeSuccess(resp.jsonBody, resp.httpStatus, resp.requestId);
1119
1190
  }
@@ -1160,11 +1231,11 @@ var Streams = class {
1160
1231
  async list() {
1161
1232
  const result = await this.post("getStreams", {}, this.readPolicy);
1162
1233
  if (!Array.isArray(result)) return [];
1163
- return result.map(parseStream);
1234
+ return result.filter((x) => asObject(x) !== void 0).map(parseStream);
1164
1235
  }
1165
1236
  /** Compute the 9-char longpoll category locally — pure, no network. */
1166
1237
  deriveLongpollCategory(radioId) {
1167
- return deriveLongpollCategory(this.apiToken, radioId);
1238
+ return deriveLongpollCategory(this.apiToken(), radioId);
1168
1239
  }
1169
1240
  /**
1170
1241
  * Parse a callback POST body into `{ match, notification }`. Pass an
@@ -1206,13 +1277,21 @@ var Streams = class {
1206
1277
  const timeoutSec = effectiveOpts.timeout ?? 50;
1207
1278
  const httpClient = this.http;
1208
1279
  const readPolicy = this.readPolicy;
1280
+ const onEvent = this.onEvent;
1281
+ const longpollUrl = `${API_BASE}/longpoll/`;
1282
+ const timeoutMs = (timeoutSec + LONGPOLL_TIMEOUT_MARGIN_SEC) * 1e3;
1209
1283
  return startLongpoll({
1210
1284
  category,
1211
1285
  timeout: timeoutSec,
1212
1286
  sinceTime: effectiveOpts.sinceTime,
1213
- fetchOnce: (params, signal) => runRetried(
1214
- () => httpClient.get(`${API_BASE}/longpoll/`, params, { signal }),
1215
- readPolicy
1287
+ fetchOnce: (params, signal) => emitAround(
1288
+ onEvent,
1289
+ "longpoll",
1290
+ longpollUrl,
1291
+ () => runRetried(
1292
+ () => httpClient.get(longpollUrl, params, { signal, timeoutMs }),
1293
+ readPolicy
1294
+ )
1216
1295
  )
1217
1296
  });
1218
1297
  }
@@ -1257,26 +1336,15 @@ function decodeSuccess2(body, httpStatus, requestId) {
1257
1336
  });
1258
1337
  }
1259
1338
  }
1260
- async function runRetried2(fn, policy) {
1261
- try {
1262
- return await retry(fn, policy);
1263
- } catch (err) {
1264
- if (err instanceof TypeError) {
1265
- throw new AudDConnectionError(`Network error: ${err.message}`, err);
1266
- }
1267
- if (err !== null && typeof err === "object" && err.name === "AbortError") {
1268
- throw new AudDConnectionError("Request was aborted (timeout)", err);
1269
- }
1270
- throw err;
1271
- }
1272
- }
1273
1339
  var CustomCatalog = class {
1274
- constructor(http, noRetryPolicy) {
1340
+ constructor(http, noRetryPolicy, onEvent) {
1275
1341
  this.http = http;
1276
1342
  this.noRetryPolicy = noRetryPolicy;
1343
+ this.onEvent = onEvent;
1277
1344
  }
1278
1345
  http;
1279
1346
  noRetryPolicy;
1347
+ onEvent;
1280
1348
  /**
1281
1349
  * **This is NOT how you submit audio for music recognition.** For
1282
1350
  * recognition, use `audd.recognize()` (or `audd.recognizeEnterprise()` for
@@ -1297,42 +1365,36 @@ var CustomCatalog = class {
1297
1365
  async add(opts) {
1298
1366
  const reopen = prepareSource(opts.source);
1299
1367
  const audioId = String(opts.audioId);
1300
- const resp = await runRetried2(async () => {
1301
- const prepared = await reopen();
1302
- const fields = { ...prepared.fields, audio_id: audioId };
1303
- return this.http.postForm(UPLOAD_URL, fields);
1304
- }, this.noRetryPolicy);
1368
+ const resp = await emitAround(
1369
+ this.onEvent,
1370
+ "customCatalogAdd",
1371
+ UPLOAD_URL,
1372
+ () => runRetried(async () => {
1373
+ const prepared = await reopen();
1374
+ const fields = { ...prepared.fields, audio_id: audioId };
1375
+ return this.http.postForm(UPLOAD_URL, fields);
1376
+ }, this.noRetryPolicy)
1377
+ );
1305
1378
  decodeSuccess2(resp.jsonBody, resp.httpStatus, resp.requestId);
1306
1379
  }
1307
1380
  };
1308
1381
 
1309
1382
  // src/advanced.ts
1310
1383
  var API_BASE2 = "https://api.audd.io";
1311
- async function runRetried3(fn, policy) {
1312
- try {
1313
- return await retry(fn, policy);
1314
- } catch (err) {
1315
- if (err instanceof TypeError) {
1316
- throw new AudDConnectionError(`Network error: ${err.message}`, err);
1317
- }
1318
- if (err !== null && typeof err === "object" && err.name === "AbortError") {
1319
- throw new AudDConnectionError("Request was aborted (timeout)", err);
1320
- }
1321
- throw err;
1322
- }
1323
- }
1324
1384
  var Advanced = class {
1325
1385
  /**
1326
1386
  * @param recognitionPolicy The findLyrics endpoint is metered and shouldn't
1327
1387
  * double-bill on post-upload read timeout: Advanced uses RECOGNITION retry
1328
1388
  * policy, not READ.
1329
1389
  */
1330
- constructor(http, recognitionPolicy) {
1390
+ constructor(http, recognitionPolicy, onEvent) {
1331
1391
  this.http = http;
1332
1392
  this.recognitionPolicy = recognitionPolicy;
1393
+ this.onEvent = onEvent;
1333
1394
  }
1334
1395
  http;
1335
1396
  recognitionPolicy;
1397
+ onEvent;
1336
1398
  /** Find lyrics by free-text query. Returns a list of matches (possibly empty). */
1337
1399
  async findLyrics(query) {
1338
1400
  const body = await this.rawRequest("findLyrics", { q: query });
@@ -1356,9 +1418,12 @@ var Advanced = class {
1356
1418
  if (v === void 0) continue;
1357
1419
  fields[k] = String(v);
1358
1420
  }
1359
- const resp = await runRetried3(
1360
- () => this.http.postForm(`${API_BASE2}/${method}/`, fields),
1361
- this.recognitionPolicy
1421
+ const url = `${API_BASE2}/${method}/`;
1422
+ const resp = await emitAround(
1423
+ this.onEvent,
1424
+ method,
1425
+ url,
1426
+ () => runRetried(() => this.http.postForm(url, fields), this.recognitionPolicy)
1362
1427
  );
1363
1428
  const body = resp.jsonBody;
1364
1429
  if (typeof body !== "object" || body === null || Array.isArray(body)) {
@@ -1374,13 +1439,6 @@ var ENTERPRISE_BASE = "https://enterprise.audd.io";
1374
1439
  var DEPRECATED_PARAMS_CODE = 51;
1375
1440
  var HTTP_CLIENT_ERROR_FLOOR2 = 400;
1376
1441
  var TOKEN_ENV_VAR = "AUDD_API_TOKEN";
1377
- function safeEmit(hook, event) {
1378
- if (hook === void 0) return;
1379
- try {
1380
- hook(event);
1381
- } catch {
1382
- }
1383
- }
1384
1442
  function resolveToken(apiToken) {
1385
1443
  if (apiToken !== void 0 && apiToken !== "") return apiToken;
1386
1444
  const env = (typeof process !== "undefined" ? process.env[TOKEN_ENV_VAR] : void 0) ?? "";
@@ -1453,7 +1511,7 @@ function decodeOrRaise(resp) {
1453
1511
  function decodeRecognize(resp) {
1454
1512
  const body = decodeOrRaise(resp);
1455
1513
  const result = body["result"];
1456
- if (result == null) return null;
1514
+ if (asObject(result) === void 0) return null;
1457
1515
  return parseRecognitionResult(result);
1458
1516
  }
1459
1517
  function decodeEnterprise(resp) {
@@ -1462,24 +1520,12 @@ function decodeEnterprise(resp) {
1462
1520
  if (!Array.isArray(chunks)) return [];
1463
1521
  const out = [];
1464
1522
  for (const c of chunks) {
1523
+ if (asObject(c) === void 0) continue;
1465
1524
  const chunk = parseEnterpriseChunkResult(c);
1466
1525
  out.push(...chunk.songs);
1467
1526
  }
1468
1527
  return out;
1469
1528
  }
1470
- async function runRetried4(fn, policy) {
1471
- try {
1472
- return await retry(fn, policy);
1473
- } catch (err) {
1474
- if (err instanceof TypeError) {
1475
- throw new AudDConnectionError(`Network error: ${err.message}`, err);
1476
- }
1477
- if (err !== null && typeof err === "object" && err.name === "AbortError") {
1478
- throw new AudDConnectionError("Request was aborted (timeout)", err);
1479
- }
1480
- throw err;
1481
- }
1482
- }
1483
1529
  var AudD = class {
1484
1530
  _http;
1485
1531
  _enterpriseHttp;
@@ -1537,7 +1583,8 @@ var AudD = class {
1537
1583
  this._http,
1538
1584
  this.policyFor("read"),
1539
1585
  this.policyFor("mutating"),
1540
- this._apiToken
1586
+ () => this._apiToken,
1587
+ this._onEvent
1541
1588
  );
1542
1589
  }
1543
1590
  return this._streams;
@@ -1545,14 +1592,14 @@ var AudD = class {
1545
1592
  /** Sub-namespace for the private fingerprint catalog. NOT for recognition. */
1546
1593
  get customCatalog() {
1547
1594
  if (this._customCatalog === void 0) {
1548
- this._customCatalog = new CustomCatalog(this._http, this.policyFor("none"));
1595
+ this._customCatalog = new CustomCatalog(this._http, this.policyFor("none"), this._onEvent);
1549
1596
  }
1550
1597
  return this._customCatalog;
1551
1598
  }
1552
1599
  /** Lyrics search + raw-request escape hatch. */
1553
1600
  get advanced() {
1554
1601
  if (this._advanced === void 0) {
1555
- this._advanced = new Advanced(this._http, this.policyFor("recognition"));
1602
+ this._advanced = new Advanced(this._http, this.policyFor("recognition"), this._onEvent);
1556
1603
  }
1557
1604
  return this._advanced;
1558
1605
  }
@@ -1568,20 +1615,11 @@ var AudD = class {
1568
1615
  const market = opts.market;
1569
1616
  const policy = this.policyFor("recognition");
1570
1617
  const url = `${API_BASE3}/`;
1571
- const startedAt = Date.now();
1572
- safeEmit(this._onEvent, {
1573
- kind: "request",
1574
- method: "recognize",
1618
+ const resp = await emitAround(
1619
+ this._onEvent,
1620
+ "recognize",
1575
1621
  url,
1576
- requestId: null,
1577
- httpStatus: null,
1578
- elapsedMs: null,
1579
- errorCode: null,
1580
- extras: {}
1581
- });
1582
- let resp;
1583
- try {
1584
- resp = await runRetried4(async () => {
1622
+ () => runRetried(async () => {
1585
1623
  const prepared = await reopen();
1586
1624
  const fields = { ...prepared.fields };
1587
1625
  if (opts.extraParameters) Object.assign(fields, opts.extraParameters);
@@ -1591,30 +1629,8 @@ var AudD = class {
1591
1629
  ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
1592
1630
  ...opts.signal !== void 0 ? { signal: opts.signal } : {}
1593
1631
  });
1594
- }, policy);
1595
- } catch (err) {
1596
- safeEmit(this._onEvent, {
1597
- kind: "exception",
1598
- method: "recognize",
1599
- url,
1600
- requestId: null,
1601
- httpStatus: null,
1602
- elapsedMs: Date.now() - startedAt,
1603
- errorCode: null,
1604
- extras: { name: err instanceof Error ? err.name : String(err) }
1605
- });
1606
- throw err;
1607
- }
1608
- safeEmit(this._onEvent, {
1609
- kind: "response",
1610
- method: "recognize",
1611
- url,
1612
- requestId: resp.requestId,
1613
- httpStatus: resp.httpStatus,
1614
- elapsedMs: Date.now() - startedAt,
1615
- errorCode: null,
1616
- extras: {}
1617
- });
1632
+ }, policy)
1633
+ );
1618
1634
  return decodeRecognize(resp);
1619
1635
  }
1620
1636
  /**
@@ -1627,14 +1643,20 @@ var AudD = class {
1627
1643
  const reopen = prepareSource(source);
1628
1644
  const extra = buildEnterpriseFields(opts);
1629
1645
  const policy = this.policyFor("recognition");
1630
- const resp = await runRetried4(async () => {
1631
- const prepared = await reopen();
1632
- const fields = { ...prepared.fields, ...extra };
1633
- return this._enterpriseHttp.postForm(`${ENTERPRISE_BASE}/`, fields, {
1634
- ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
1635
- ...opts.signal !== void 0 ? { signal: opts.signal } : {}
1636
- });
1637
- }, policy);
1646
+ const url = `${ENTERPRISE_BASE}/`;
1647
+ const resp = await emitAround(
1648
+ this._onEvent,
1649
+ "recognizeEnterprise",
1650
+ url,
1651
+ () => runRetried(async () => {
1652
+ const prepared = await reopen();
1653
+ const fields = { ...prepared.fields, ...extra };
1654
+ return this._enterpriseHttp.postForm(url, fields, {
1655
+ ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
1656
+ ...opts.signal !== void 0 ? { signal: opts.signal } : {}
1657
+ });
1658
+ }, policy)
1659
+ );
1638
1660
  return decodeEnterprise(resp);
1639
1661
  }
1640
1662
  /** Release any underlying resources. (Currently a no-op for `fetch`-based transport.) */