@audd/sdk 1.5.11 → 1.5.13

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/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.13";
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,19 +335,42 @@ 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
- return typeof v === "string" ? v : void 0;
342
+ if (typeof v === "string") return v;
343
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
344
+ return void 0;
345
+ }
346
+ function toFiniteNumber(v) {
347
+ if (typeof v === "number") return Number.isFinite(v) ? v : void 0;
348
+ if (typeof v === "boolean") return v ? 1 : 0;
349
+ if (typeof v === "string") {
350
+ const t = v.trim();
351
+ if (t === "") return void 0;
352
+ const n = Number(t);
353
+ return Number.isFinite(n) ? n : void 0;
354
+ }
355
+ return void 0;
281
356
  }
282
357
  function asNumber(v) {
283
- return typeof v === "number" ? v : void 0;
358
+ return toFiniteNumber(v);
359
+ }
360
+ function asInteger(v) {
361
+ const n = toFiniteNumber(v);
362
+ return n === void 0 ? void 0 : Math.trunc(n);
284
363
  }
285
364
  function asBoolean(v) {
286
- return typeof v === "boolean" ? v : void 0;
365
+ if (typeof v === "boolean") return v;
366
+ if (typeof v === "number") return v !== 0;
367
+ if (typeof v === "string") {
368
+ const t = v.trim().toLowerCase();
369
+ if (t === "true" || t === "1" || t === "yes" || t === "on") return true;
370
+ if (t === "false" || t === "0" || t === "no" || t === "off" || t === "") return false;
371
+ return void 0;
372
+ }
373
+ return void 0;
287
374
  }
288
375
  function parseOffsetToSeconds(o) {
289
376
  if (o === void 0 || o === null) return void 0;
@@ -378,9 +465,9 @@ var RECOGNITION_KEYS = [
378
465
  "musicbrainz"
379
466
  ];
380
467
  function parseRecognitionResult(raw) {
381
- const r = requireObject(raw, "RecognitionResult");
468
+ const r = asObjectLenient(raw);
382
469
  const timecode = asString(r.timecode);
383
- const audioId = asNumber(r.audio_id);
470
+ const audioId = asInteger(r.audio_id);
384
471
  const artist = asString(r.artist);
385
472
  const title = asString(r.title);
386
473
  const songLink = asString(r.song_link);
@@ -453,7 +540,7 @@ var ENTERPRISE_MATCH_KEYS = [
453
540
  "end_seconds"
454
541
  ];
455
542
  function parseEnterpriseMatch(raw) {
456
- const r = requireObject(raw, "EnterpriseMatch");
543
+ const r = asObjectLenient(raw);
457
544
  const score = asNumber(r.score);
458
545
  const timecode = asString(r.timecode);
459
546
  const songLink = asString(r.song_link);
@@ -468,8 +555,8 @@ function parseEnterpriseMatch(raw) {
468
555
  isrc: asString(r.isrc),
469
556
  upc: asString(r.upc),
470
557
  songLink,
471
- startOffset: asNumber(r.start_offset),
472
- endOffset: asNumber(r.end_offset),
558
+ startOffset: asInteger(r.start_offset),
559
+ endOffset: asInteger(r.end_offset),
473
560
  startSeconds: void 0,
474
561
  endSeconds: void 0,
475
562
  extras: pickExtras(r, ENTERPRISE_MATCH_KEYS),
@@ -503,8 +590,8 @@ function parseEnterpriseMatch(raw) {
503
590
  }
504
591
  var ENTERPRISE_CHUNK_KEYS = ["songs", "offset"];
505
592
  function parseEnterpriseChunkResult(raw) {
506
- const r = requireObject(raw, "EnterpriseChunkResult");
507
- const songsRaw = Array.isArray(r.songs) ? r.songs : [];
593
+ const r = asObjectLenient(raw);
594
+ const songsRaw = Array.isArray(r.songs) ? r.songs.filter((s) => asObject(s) !== void 0) : [];
508
595
  const offset = asString(r.offset);
509
596
  const base = parseOffsetToSeconds(r.offset);
510
597
  const songs = songsRaw.map((s) => {
@@ -524,8 +611,8 @@ function parseEnterpriseChunkResult(raw) {
524
611
  }
525
612
  var STREAM_KEYS = ["radio_id", "url", "stream_running", "longpoll_category"];
526
613
  function parseStream(raw) {
527
- const r = requireObject(raw, "Stream");
528
- const radioId = asNumber(r.radio_id);
614
+ const r = asObjectLenient(raw);
615
+ const radioId = asInteger(r.radio_id);
529
616
  const url = asString(r.url);
530
617
  const streamRunning = asBoolean(r.stream_running);
531
618
  return {
@@ -554,7 +641,7 @@ var STREAM_CALLBACK_SONG_KEYS = [
554
641
  "musicbrainz"
555
642
  ];
556
643
  function parseStreamCallbackSong(raw) {
557
- const r = requireObject(raw, "StreamCallbackSong");
644
+ const r = asObjectLenient(raw);
558
645
  const artist = asString(r.artist);
559
646
  const title = asString(r.title);
560
647
  const score = asNumber(r.score);
@@ -584,15 +671,15 @@ var STREAM_CALLBACK_MATCH_KEYS = [
584
671
  "results"
585
672
  ];
586
673
  function parseStreamCallbackMatch(raw) {
587
- const r = requireObject(raw, "StreamCallbackMatch");
588
- const radioId = asNumber(r.radio_id);
674
+ const r = asObjectLenient(raw);
675
+ const radioId = asInteger(r.radio_id);
589
676
  const resultsRaw = Array.isArray(r.results) ? r.results : [];
590
677
  const songs = resultsRaw.filter((x) => asObject(x) !== void 0).map(parseStreamCallbackSong);
591
678
  const [first, ...rest] = songs;
592
679
  return {
593
680
  radioId,
594
681
  timestamp: asString(r.timestamp),
595
- playLength: asNumber(r.play_length),
682
+ playLength: asInteger(r.play_length),
596
683
  song: first,
597
684
  alternatives: rest,
598
685
  extras: pickExtras(r, STREAM_CALLBACK_MATCH_KEYS),
@@ -606,9 +693,9 @@ var STREAM_CALLBACK_NOTIFICATION_KEYS = [
606
693
  "notification_message"
607
694
  ];
608
695
  function parseStreamCallbackNotification(raw) {
609
- const r = requireObject(raw, "StreamCallbackNotification");
610
- const radioId = asNumber(r.radio_id);
611
- const code = asNumber(r.notification_code);
696
+ const r = asObjectLenient(raw);
697
+ const radioId = asInteger(r.radio_id);
698
+ const code = asInteger(r.notification_code);
612
699
  const message = asString(r.notification_message);
613
700
  return {
614
701
  radioId,
@@ -630,16 +717,16 @@ var LYRICS_KEYS = [
630
717
  "media"
631
718
  ];
632
719
  function parseLyricsResult(raw) {
633
- const r = requireObject(raw, "LyricsResult");
720
+ const r = asObjectLenient(raw);
634
721
  const artist = asString(r.artist);
635
722
  const title = asString(r.title);
636
723
  return {
637
724
  artist,
638
725
  title,
639
726
  lyrics: asString(r.lyrics),
640
- songId: asNumber(r.song_id),
727
+ songId: asInteger(r.song_id),
641
728
  fullTitle: asString(r.full_title),
642
- artistId: asNumber(r.artist_id),
729
+ artistId: asInteger(r.artist_id),
643
730
  songLink: asString(r.song_link),
644
731
  media: asString(r.media),
645
732
  extras: pickExtras(r, LYRICS_KEYS),
@@ -726,6 +813,22 @@ async function retry(fn, policy) {
726
813
  if (haveResp && lastResp !== void 0) return lastResp;
727
814
  throw lastError;
728
815
  }
816
+ async function runRetried(fn, policy) {
817
+ try {
818
+ return await retry(fn, policy);
819
+ } catch (err) {
820
+ if (isUserAbortError(err)) {
821
+ throw new AudDConnectionError("Request was cancelled by the caller's AbortSignal", err);
822
+ }
823
+ if (err instanceof TypeError) {
824
+ throw new AudDConnectionError(`Network error: ${err.message}`, err);
825
+ }
826
+ if (err !== null && typeof err === "object" && err.name === "AbortError") {
827
+ throw new AudDConnectionError("Request was aborted (timeout)", err);
828
+ }
829
+ throw err;
830
+ }
831
+ }
729
832
  function looksLikeUrl(s) {
730
833
  return s.startsWith("http://") || s.startsWith("https://");
731
834
  }
@@ -977,13 +1080,13 @@ function decodeOne(resp) {
977
1080
  );
978
1081
  }
979
1082
  const dict = body;
980
- if ("notification" in dict) {
1083
+ if (asObject(dict.notification) !== void 0) {
981
1084
  const notification = parseStreamCallbackNotification(dict.notification);
982
1085
  const t = dict["time"];
983
1086
  if (typeof t === "number") notification.time = t;
984
1087
  return { kind: "notification", notification };
985
1088
  }
986
- if ("result" in dict) {
1089
+ if (asObject(dict.result) !== void 0) {
987
1090
  return { kind: "match", match: parseStreamCallbackMatch(dict.result) };
988
1091
  }
989
1092
  return { kind: "keepalive" };
@@ -1062,6 +1165,7 @@ function startLongpoll(cfg) {
1062
1165
 
1063
1166
  // src/streams.ts
1064
1167
  var API_BASE = "https://api.audd.io";
1168
+ var LONGPOLL_TIMEOUT_MARGIN_SEC = 10;
1065
1169
  var NO_CALLBACK_ERROR_CODE = 19;
1066
1170
  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
1171
  function decodeSuccess(body, httpStatus, requestId) {
@@ -1086,34 +1190,26 @@ function decodeSuccess(body, httpStatus, requestId) {
1086
1190
  rawResponse: obj
1087
1191
  });
1088
1192
  }
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
1193
  var Streams = class {
1103
- constructor(http, readPolicy, mutatingPolicy, apiToken) {
1194
+ constructor(http, readPolicy, mutatingPolicy, apiToken, onEvent) {
1104
1195
  this.http = http;
1105
1196
  this.readPolicy = readPolicy;
1106
1197
  this.mutatingPolicy = mutatingPolicy;
1107
1198
  this.apiToken = apiToken;
1199
+ this.onEvent = onEvent;
1108
1200
  }
1109
1201
  http;
1110
1202
  readPolicy;
1111
1203
  mutatingPolicy;
1112
1204
  apiToken;
1205
+ onEvent;
1113
1206
  async post(path2, fields, policy) {
1114
- const resp = await runRetried(
1115
- () => this.http.postForm(`${API_BASE}/${path2}/`, fields),
1116
- policy
1207
+ const url = `${API_BASE}/${path2}/`;
1208
+ const resp = await emitAround(
1209
+ this.onEvent,
1210
+ path2,
1211
+ url,
1212
+ () => runRetried(() => this.http.postForm(url, fields), policy)
1117
1213
  );
1118
1214
  return decodeSuccess(resp.jsonBody, resp.httpStatus, resp.requestId);
1119
1215
  }
@@ -1160,11 +1256,11 @@ var Streams = class {
1160
1256
  async list() {
1161
1257
  const result = await this.post("getStreams", {}, this.readPolicy);
1162
1258
  if (!Array.isArray(result)) return [];
1163
- return result.map(parseStream);
1259
+ return result.filter((x) => asObject(x) !== void 0).map(parseStream);
1164
1260
  }
1165
1261
  /** Compute the 9-char longpoll category locally — pure, no network. */
1166
1262
  deriveLongpollCategory(radioId) {
1167
- return deriveLongpollCategory(this.apiToken, radioId);
1263
+ return deriveLongpollCategory(this.apiToken(), radioId);
1168
1264
  }
1169
1265
  /**
1170
1266
  * Parse a callback POST body into `{ match, notification }`. Pass an
@@ -1206,13 +1302,21 @@ var Streams = class {
1206
1302
  const timeoutSec = effectiveOpts.timeout ?? 50;
1207
1303
  const httpClient = this.http;
1208
1304
  const readPolicy = this.readPolicy;
1305
+ const onEvent = this.onEvent;
1306
+ const longpollUrl = `${API_BASE}/longpoll/`;
1307
+ const timeoutMs = (timeoutSec + LONGPOLL_TIMEOUT_MARGIN_SEC) * 1e3;
1209
1308
  return startLongpoll({
1210
1309
  category,
1211
1310
  timeout: timeoutSec,
1212
1311
  sinceTime: effectiveOpts.sinceTime,
1213
- fetchOnce: (params, signal) => runRetried(
1214
- () => httpClient.get(`${API_BASE}/longpoll/`, params, { signal }),
1215
- readPolicy
1312
+ fetchOnce: (params, signal) => emitAround(
1313
+ onEvent,
1314
+ "longpoll",
1315
+ longpollUrl,
1316
+ () => runRetried(
1317
+ () => httpClient.get(longpollUrl, params, { signal, timeoutMs }),
1318
+ readPolicy
1319
+ )
1216
1320
  )
1217
1321
  });
1218
1322
  }
@@ -1257,26 +1361,15 @@ function decodeSuccess2(body, httpStatus, requestId) {
1257
1361
  });
1258
1362
  }
1259
1363
  }
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
1364
  var CustomCatalog = class {
1274
- constructor(http, noRetryPolicy) {
1365
+ constructor(http, noRetryPolicy, onEvent) {
1275
1366
  this.http = http;
1276
1367
  this.noRetryPolicy = noRetryPolicy;
1368
+ this.onEvent = onEvent;
1277
1369
  }
1278
1370
  http;
1279
1371
  noRetryPolicy;
1372
+ onEvent;
1280
1373
  /**
1281
1374
  * **This is NOT how you submit audio for music recognition.** For
1282
1375
  * recognition, use `audd.recognize()` (or `audd.recognizeEnterprise()` for
@@ -1297,42 +1390,36 @@ var CustomCatalog = class {
1297
1390
  async add(opts) {
1298
1391
  const reopen = prepareSource(opts.source);
1299
1392
  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);
1393
+ const resp = await emitAround(
1394
+ this.onEvent,
1395
+ "customCatalogAdd",
1396
+ UPLOAD_URL,
1397
+ () => runRetried(async () => {
1398
+ const prepared = await reopen();
1399
+ const fields = { ...prepared.fields, audio_id: audioId };
1400
+ return this.http.postForm(UPLOAD_URL, fields);
1401
+ }, this.noRetryPolicy)
1402
+ );
1305
1403
  decodeSuccess2(resp.jsonBody, resp.httpStatus, resp.requestId);
1306
1404
  }
1307
1405
  };
1308
1406
 
1309
1407
  // src/advanced.ts
1310
1408
  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
1409
  var Advanced = class {
1325
1410
  /**
1326
1411
  * @param recognitionPolicy The findLyrics endpoint is metered and shouldn't
1327
1412
  * double-bill on post-upload read timeout: Advanced uses RECOGNITION retry
1328
1413
  * policy, not READ.
1329
1414
  */
1330
- constructor(http, recognitionPolicy) {
1415
+ constructor(http, recognitionPolicy, onEvent) {
1331
1416
  this.http = http;
1332
1417
  this.recognitionPolicy = recognitionPolicy;
1418
+ this.onEvent = onEvent;
1333
1419
  }
1334
1420
  http;
1335
1421
  recognitionPolicy;
1422
+ onEvent;
1336
1423
  /** Find lyrics by free-text query. Returns a list of matches (possibly empty). */
1337
1424
  async findLyrics(query) {
1338
1425
  const body = await this.rawRequest("findLyrics", { q: query });
@@ -1356,9 +1443,12 @@ var Advanced = class {
1356
1443
  if (v === void 0) continue;
1357
1444
  fields[k] = String(v);
1358
1445
  }
1359
- const resp = await runRetried3(
1360
- () => this.http.postForm(`${API_BASE2}/${method}/`, fields),
1361
- this.recognitionPolicy
1446
+ const url = `${API_BASE2}/${method}/`;
1447
+ const resp = await emitAround(
1448
+ this.onEvent,
1449
+ method,
1450
+ url,
1451
+ () => runRetried(() => this.http.postForm(url, fields), this.recognitionPolicy)
1362
1452
  );
1363
1453
  const body = resp.jsonBody;
1364
1454
  if (typeof body !== "object" || body === null || Array.isArray(body)) {
@@ -1374,13 +1464,6 @@ var ENTERPRISE_BASE = "https://enterprise.audd.io";
1374
1464
  var DEPRECATED_PARAMS_CODE = 51;
1375
1465
  var HTTP_CLIENT_ERROR_FLOOR2 = 400;
1376
1466
  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
1467
  function resolveToken(apiToken) {
1385
1468
  if (apiToken !== void 0 && apiToken !== "") return apiToken;
1386
1469
  const env = (typeof process !== "undefined" ? process.env[TOKEN_ENV_VAR] : void 0) ?? "";
@@ -1453,7 +1536,7 @@ function decodeOrRaise(resp) {
1453
1536
  function decodeRecognize(resp) {
1454
1537
  const body = decodeOrRaise(resp);
1455
1538
  const result = body["result"];
1456
- if (result == null) return null;
1539
+ if (asObject(result) === void 0) return null;
1457
1540
  return parseRecognitionResult(result);
1458
1541
  }
1459
1542
  function decodeEnterprise(resp) {
@@ -1462,24 +1545,12 @@ function decodeEnterprise(resp) {
1462
1545
  if (!Array.isArray(chunks)) return [];
1463
1546
  const out = [];
1464
1547
  for (const c of chunks) {
1548
+ if (asObject(c) === void 0) continue;
1465
1549
  const chunk = parseEnterpriseChunkResult(c);
1466
1550
  out.push(...chunk.songs);
1467
1551
  }
1468
1552
  return out;
1469
1553
  }
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
1554
  var AudD = class {
1484
1555
  _http;
1485
1556
  _enterpriseHttp;
@@ -1537,7 +1608,8 @@ var AudD = class {
1537
1608
  this._http,
1538
1609
  this.policyFor("read"),
1539
1610
  this.policyFor("mutating"),
1540
- this._apiToken
1611
+ () => this._apiToken,
1612
+ this._onEvent
1541
1613
  );
1542
1614
  }
1543
1615
  return this._streams;
@@ -1545,14 +1617,14 @@ var AudD = class {
1545
1617
  /** Sub-namespace for the private fingerprint catalog. NOT for recognition. */
1546
1618
  get customCatalog() {
1547
1619
  if (this._customCatalog === void 0) {
1548
- this._customCatalog = new CustomCatalog(this._http, this.policyFor("none"));
1620
+ this._customCatalog = new CustomCatalog(this._http, this.policyFor("none"), this._onEvent);
1549
1621
  }
1550
1622
  return this._customCatalog;
1551
1623
  }
1552
1624
  /** Lyrics search + raw-request escape hatch. */
1553
1625
  get advanced() {
1554
1626
  if (this._advanced === void 0) {
1555
- this._advanced = new Advanced(this._http, this.policyFor("recognition"));
1627
+ this._advanced = new Advanced(this._http, this.policyFor("recognition"), this._onEvent);
1556
1628
  }
1557
1629
  return this._advanced;
1558
1630
  }
@@ -1568,20 +1640,11 @@ var AudD = class {
1568
1640
  const market = opts.market;
1569
1641
  const policy = this.policyFor("recognition");
1570
1642
  const url = `${API_BASE3}/`;
1571
- const startedAt = Date.now();
1572
- safeEmit(this._onEvent, {
1573
- kind: "request",
1574
- method: "recognize",
1643
+ const resp = await emitAround(
1644
+ this._onEvent,
1645
+ "recognize",
1575
1646
  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 () => {
1647
+ () => runRetried(async () => {
1585
1648
  const prepared = await reopen();
1586
1649
  const fields = { ...prepared.fields };
1587
1650
  if (opts.extraParameters) Object.assign(fields, opts.extraParameters);
@@ -1591,30 +1654,8 @@ var AudD = class {
1591
1654
  ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
1592
1655
  ...opts.signal !== void 0 ? { signal: opts.signal } : {}
1593
1656
  });
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
- });
1657
+ }, policy)
1658
+ );
1618
1659
  return decodeRecognize(resp);
1619
1660
  }
1620
1661
  /**
@@ -1627,14 +1668,20 @@ var AudD = class {
1627
1668
  const reopen = prepareSource(source);
1628
1669
  const extra = buildEnterpriseFields(opts);
1629
1670
  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);
1671
+ const url = `${ENTERPRISE_BASE}/`;
1672
+ const resp = await emitAround(
1673
+ this._onEvent,
1674
+ "recognizeEnterprise",
1675
+ url,
1676
+ () => runRetried(async () => {
1677
+ const prepared = await reopen();
1678
+ const fields = { ...prepared.fields, ...extra };
1679
+ return this._enterpriseHttp.postForm(url, fields, {
1680
+ ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
1681
+ ...opts.signal !== void 0 ? { signal: opts.signal } : {}
1682
+ });
1683
+ }, policy)
1684
+ );
1638
1685
  return decodeEnterprise(resp);
1639
1686
  }
1640
1687
  /** Release any underlying resources. (Currently a no-op for `fetch`-based transport.) */