@audd/sdk 1.5.10 → 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/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import * as path from 'path';
3
3
  import { createHash } from 'crypto';
4
4
 
5
5
  // src/version.ts
6
- var VERSION = "1.5.8";
6
+ var VERSION = "1.5.12";
7
7
 
8
8
  // src/errors.ts
9
9
  var AudDError = class extends Error {
@@ -76,6 +76,16 @@ var AudDConnectionError = class extends AudDError {
76
76
  this.cause = cause;
77
77
  }
78
78
  };
79
+ var USER_ABORT_ERROR_NAME = "UserAbortError";
80
+ function userAbortError(cause) {
81
+ const err = new Error("Request was cancelled by the caller's AbortSignal");
82
+ err.name = USER_ABORT_ERROR_NAME;
83
+ err.cause = cause;
84
+ return err;
85
+ }
86
+ function isUserAbortError(err) {
87
+ return err !== null && typeof err === "object" && err.name === USER_ABORT_ERROR_NAME;
88
+ }
79
89
  var AudDSerializationError = class extends AudDError {
80
90
  name = "AudDSerializationError";
81
91
  rawText;
@@ -148,6 +158,55 @@ function raiseFromErrorResponse(body, opts) {
148
158
  throw new Cls(fields);
149
159
  }
150
160
 
161
+ // src/events.ts
162
+ function safeEmit(hook, event) {
163
+ if (hook === void 0) return;
164
+ try {
165
+ hook(event);
166
+ } catch {
167
+ }
168
+ }
169
+ async function emitAround(hook, method, url, fn) {
170
+ const startedAt = Date.now();
171
+ safeEmit(hook, {
172
+ kind: "request",
173
+ method,
174
+ url,
175
+ requestId: null,
176
+ httpStatus: null,
177
+ elapsedMs: null,
178
+ errorCode: null,
179
+ extras: {}
180
+ });
181
+ let resp;
182
+ try {
183
+ resp = await fn();
184
+ } catch (err) {
185
+ safeEmit(hook, {
186
+ kind: "exception",
187
+ method,
188
+ url,
189
+ requestId: null,
190
+ httpStatus: null,
191
+ elapsedMs: Date.now() - startedAt,
192
+ errorCode: null,
193
+ extras: { name: err instanceof Error ? err.name : String(err) }
194
+ });
195
+ throw err;
196
+ }
197
+ safeEmit(hook, {
198
+ kind: "response",
199
+ method,
200
+ url,
201
+ requestId: resp.requestId,
202
+ httpStatus: resp.httpStatus,
203
+ elapsedMs: Date.now() - startedAt,
204
+ errorCode: null,
205
+ extras: {}
206
+ });
207
+ return resp;
208
+ }
209
+
151
210
  // src/userAgent.ts
152
211
  function userAgent() {
153
212
  const node = typeof process !== "undefined" && process.version ? process.version.replace(/^v/, "") : "unknown";
@@ -225,6 +284,11 @@ var HttpClient = class {
225
284
  requestId: response.headers.get("x-request-id"),
226
285
  rawText: text
227
286
  };
287
+ } catch (err) {
288
+ if (userSignal?.aborted === true && err !== null && typeof err === "object" && err.name === "AbortError") {
289
+ throw userAbortError(err);
290
+ }
291
+ throw err;
228
292
  } finally {
229
293
  clearTimeout(timer);
230
294
  if (userSignal !== void 0) {
@@ -249,10 +313,8 @@ function asObject(raw) {
249
313
  }
250
314
  return void 0;
251
315
  }
252
- function requireObject(raw, what) {
253
- const o = asObject(raw);
254
- if (o === void 0) throw new TypeError(`${what}: expected object`);
255
- return o;
316
+ function asObjectLenient(raw) {
317
+ return asObject(raw) ?? {};
256
318
  }
257
319
  function asString(v) {
258
320
  return typeof v === "string" ? v : void 0;
@@ -263,6 +325,20 @@ function asNumber(v) {
263
325
  function asBoolean(v) {
264
326
  return typeof v === "boolean" ? v : void 0;
265
327
  }
328
+ function parseOffsetToSeconds(o) {
329
+ if (o === void 0 || o === null) return void 0;
330
+ if (typeof o === "number") return Number.isFinite(o) ? o : void 0;
331
+ const t = String(o).trim();
332
+ if (t === "") return void 0;
333
+ let total = 0;
334
+ for (const part of t.split(":")) {
335
+ if (part.trim() === "") return void 0;
336
+ const n = Number(part);
337
+ if (!Number.isFinite(n)) return void 0;
338
+ total = total * 60 + n;
339
+ }
340
+ return total;
341
+ }
266
342
  var STREAMING_PROVIDERS = [
267
343
  "spotify",
268
344
  "apple_music",
@@ -342,7 +418,7 @@ var RECOGNITION_KEYS = [
342
418
  "musicbrainz"
343
419
  ];
344
420
  function parseRecognitionResult(raw) {
345
- const r = requireObject(raw, "RecognitionResult");
421
+ const r = asObjectLenient(raw);
346
422
  const timecode = asString(r.timecode);
347
423
  const audioId = asNumber(r.audio_id);
348
424
  const artist = asString(r.artist);
@@ -412,10 +488,12 @@ var ENTERPRISE_MATCH_KEYS = [
412
488
  "upc",
413
489
  "song_link",
414
490
  "start_offset",
415
- "end_offset"
491
+ "end_offset",
492
+ "start_seconds",
493
+ "end_seconds"
416
494
  ];
417
495
  function parseEnterpriseMatch(raw) {
418
- const r = requireObject(raw, "EnterpriseMatch");
496
+ const r = asObjectLenient(raw);
419
497
  const score = asNumber(r.score);
420
498
  const timecode = asString(r.timecode);
421
499
  const songLink = asString(r.song_link);
@@ -432,6 +510,8 @@ function parseEnterpriseMatch(raw) {
432
510
  songLink,
433
511
  startOffset: asNumber(r.start_offset),
434
512
  endOffset: asNumber(r.end_offset),
513
+ startSeconds: void 0,
514
+ endSeconds: void 0,
435
515
  extras: pickExtras(r, ENTERPRISE_MATCH_KEYS),
436
516
  rawResponse: r,
437
517
  streamingUrl(provider) {
@@ -463,11 +543,20 @@ function parseEnterpriseMatch(raw) {
463
543
  }
464
544
  var ENTERPRISE_CHUNK_KEYS = ["songs", "offset"];
465
545
  function parseEnterpriseChunkResult(raw) {
466
- const r = requireObject(raw, "EnterpriseChunkResult");
467
- const songsRaw = Array.isArray(r.songs) ? r.songs : [];
546
+ const r = asObjectLenient(raw);
547
+ const songsRaw = Array.isArray(r.songs) ? r.songs.filter((s) => asObject(s) !== void 0) : [];
468
548
  const offset = asString(r.offset);
549
+ const base = parseOffsetToSeconds(r.offset);
550
+ const songs = songsRaw.map((s) => {
551
+ const match = parseEnterpriseMatch(s);
552
+ if (base !== void 0) {
553
+ match.startSeconds = base + (match.startOffset ?? 0) / 1e3;
554
+ match.endSeconds = base + (match.endOffset ?? 0) / 1e3;
555
+ }
556
+ return match;
557
+ });
469
558
  return {
470
- songs: songsRaw.map(parseEnterpriseMatch),
559
+ songs,
471
560
  offset,
472
561
  extras: pickExtras(r, ENTERPRISE_CHUNK_KEYS),
473
562
  rawResponse: r
@@ -475,7 +564,7 @@ function parseEnterpriseChunkResult(raw) {
475
564
  }
476
565
  var STREAM_KEYS = ["radio_id", "url", "stream_running", "longpoll_category"];
477
566
  function parseStream(raw) {
478
- const r = requireObject(raw, "Stream");
567
+ const r = asObjectLenient(raw);
479
568
  const radioId = asNumber(r.radio_id);
480
569
  const url = asString(r.url);
481
570
  const streamRunning = asBoolean(r.stream_running);
@@ -505,7 +594,7 @@ var STREAM_CALLBACK_SONG_KEYS = [
505
594
  "musicbrainz"
506
595
  ];
507
596
  function parseStreamCallbackSong(raw) {
508
- const r = requireObject(raw, "StreamCallbackSong");
597
+ const r = asObjectLenient(raw);
509
598
  const artist = asString(r.artist);
510
599
  const title = asString(r.title);
511
600
  const score = asNumber(r.score);
@@ -535,7 +624,7 @@ var STREAM_CALLBACK_MATCH_KEYS = [
535
624
  "results"
536
625
  ];
537
626
  function parseStreamCallbackMatch(raw) {
538
- const r = requireObject(raw, "StreamCallbackMatch");
627
+ const r = asObjectLenient(raw);
539
628
  const radioId = asNumber(r.radio_id);
540
629
  const resultsRaw = Array.isArray(r.results) ? r.results : [];
541
630
  const songs = resultsRaw.filter((x) => asObject(x) !== void 0).map(parseStreamCallbackSong);
@@ -557,7 +646,7 @@ var STREAM_CALLBACK_NOTIFICATION_KEYS = [
557
646
  "notification_message"
558
647
  ];
559
648
  function parseStreamCallbackNotification(raw) {
560
- const r = requireObject(raw, "StreamCallbackNotification");
649
+ const r = asObjectLenient(raw);
561
650
  const radioId = asNumber(r.radio_id);
562
651
  const code = asNumber(r.notification_code);
563
652
  const message = asString(r.notification_message);
@@ -581,7 +670,7 @@ var LYRICS_KEYS = [
581
670
  "media"
582
671
  ];
583
672
  function parseLyricsResult(raw) {
584
- const r = requireObject(raw, "LyricsResult");
673
+ const r = asObjectLenient(raw);
585
674
  const artist = asString(r.artist);
586
675
  const title = asString(r.title);
587
676
  return {
@@ -677,6 +766,22 @@ async function retry(fn, policy) {
677
766
  if (haveResp && lastResp !== void 0) return lastResp;
678
767
  throw lastError;
679
768
  }
769
+ async function runRetried(fn, policy) {
770
+ try {
771
+ return await retry(fn, policy);
772
+ } catch (err) {
773
+ if (isUserAbortError(err)) {
774
+ throw new AudDConnectionError("Request was cancelled by the caller's AbortSignal", err);
775
+ }
776
+ if (err instanceof TypeError) {
777
+ throw new AudDConnectionError(`Network error: ${err.message}`, err);
778
+ }
779
+ if (err !== null && typeof err === "object" && err.name === "AbortError") {
780
+ throw new AudDConnectionError("Request was aborted (timeout)", err);
781
+ }
782
+ throw err;
783
+ }
784
+ }
680
785
  function looksLikeUrl(s) {
681
786
  return s.startsWith("http://") || s.startsWith("https://");
682
787
  }
@@ -928,13 +1033,13 @@ function decodeOne(resp) {
928
1033
  );
929
1034
  }
930
1035
  const dict = body;
931
- if ("notification" in dict) {
1036
+ if (asObject(dict.notification) !== void 0) {
932
1037
  const notification = parseStreamCallbackNotification(dict.notification);
933
1038
  const t = dict["time"];
934
1039
  if (typeof t === "number") notification.time = t;
935
1040
  return { kind: "notification", notification };
936
1041
  }
937
- if ("result" in dict) {
1042
+ if (asObject(dict.result) !== void 0) {
938
1043
  return { kind: "match", match: parseStreamCallbackMatch(dict.result) };
939
1044
  }
940
1045
  return { kind: "keepalive" };
@@ -1013,6 +1118,7 @@ function startLongpoll(cfg) {
1013
1118
 
1014
1119
  // src/streams.ts
1015
1120
  var API_BASE = "https://api.audd.io";
1121
+ var LONGPOLL_TIMEOUT_MARGIN_SEC = 10;
1016
1122
  var NO_CALLBACK_ERROR_CODE = 19;
1017
1123
  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.";
1018
1124
  function decodeSuccess(body, httpStatus, requestId) {
@@ -1037,34 +1143,26 @@ function decodeSuccess(body, httpStatus, requestId) {
1037
1143
  rawResponse: obj
1038
1144
  });
1039
1145
  }
1040
- async function runRetried(fn, policy) {
1041
- try {
1042
- return await retry(fn, policy);
1043
- } catch (err) {
1044
- if (err instanceof TypeError) {
1045
- throw new AudDConnectionError(`Network error: ${err.message}`, err);
1046
- }
1047
- if (err !== null && typeof err === "object" && err.name === "AbortError") {
1048
- throw new AudDConnectionError("Request was aborted (timeout)", err);
1049
- }
1050
- throw err;
1051
- }
1052
- }
1053
1146
  var Streams = class {
1054
- constructor(http, readPolicy, mutatingPolicy, apiToken) {
1147
+ constructor(http, readPolicy, mutatingPolicy, apiToken, onEvent) {
1055
1148
  this.http = http;
1056
1149
  this.readPolicy = readPolicy;
1057
1150
  this.mutatingPolicy = mutatingPolicy;
1058
1151
  this.apiToken = apiToken;
1152
+ this.onEvent = onEvent;
1059
1153
  }
1060
1154
  http;
1061
1155
  readPolicy;
1062
1156
  mutatingPolicy;
1063
1157
  apiToken;
1158
+ onEvent;
1064
1159
  async post(path2, fields, policy) {
1065
- const resp = await runRetried(
1066
- () => this.http.postForm(`${API_BASE}/${path2}/`, fields),
1067
- policy
1160
+ const url = `${API_BASE}/${path2}/`;
1161
+ const resp = await emitAround(
1162
+ this.onEvent,
1163
+ path2,
1164
+ url,
1165
+ () => runRetried(() => this.http.postForm(url, fields), policy)
1068
1166
  );
1069
1167
  return decodeSuccess(resp.jsonBody, resp.httpStatus, resp.requestId);
1070
1168
  }
@@ -1111,11 +1209,11 @@ var Streams = class {
1111
1209
  async list() {
1112
1210
  const result = await this.post("getStreams", {}, this.readPolicy);
1113
1211
  if (!Array.isArray(result)) return [];
1114
- return result.map(parseStream);
1212
+ return result.filter((x) => asObject(x) !== void 0).map(parseStream);
1115
1213
  }
1116
1214
  /** Compute the 9-char longpoll category locally — pure, no network. */
1117
1215
  deriveLongpollCategory(radioId) {
1118
- return deriveLongpollCategory(this.apiToken, radioId);
1216
+ return deriveLongpollCategory(this.apiToken(), radioId);
1119
1217
  }
1120
1218
  /**
1121
1219
  * Parse a callback POST body into `{ match, notification }`. Pass an
@@ -1157,13 +1255,21 @@ var Streams = class {
1157
1255
  const timeoutSec = effectiveOpts.timeout ?? 50;
1158
1256
  const httpClient = this.http;
1159
1257
  const readPolicy = this.readPolicy;
1258
+ const onEvent = this.onEvent;
1259
+ const longpollUrl = `${API_BASE}/longpoll/`;
1260
+ const timeoutMs = (timeoutSec + LONGPOLL_TIMEOUT_MARGIN_SEC) * 1e3;
1160
1261
  return startLongpoll({
1161
1262
  category,
1162
1263
  timeout: timeoutSec,
1163
1264
  sinceTime: effectiveOpts.sinceTime,
1164
- fetchOnce: (params, signal) => runRetried(
1165
- () => httpClient.get(`${API_BASE}/longpoll/`, params, { signal }),
1166
- readPolicy
1265
+ fetchOnce: (params, signal) => emitAround(
1266
+ onEvent,
1267
+ "longpoll",
1268
+ longpollUrl,
1269
+ () => runRetried(
1270
+ () => httpClient.get(longpollUrl, params, { signal, timeoutMs }),
1271
+ readPolicy
1272
+ )
1167
1273
  )
1168
1274
  });
1169
1275
  }
@@ -1208,26 +1314,15 @@ function decodeSuccess2(body, httpStatus, requestId) {
1208
1314
  });
1209
1315
  }
1210
1316
  }
1211
- async function runRetried2(fn, policy) {
1212
- try {
1213
- return await retry(fn, policy);
1214
- } catch (err) {
1215
- if (err instanceof TypeError) {
1216
- throw new AudDConnectionError(`Network error: ${err.message}`, err);
1217
- }
1218
- if (err !== null && typeof err === "object" && err.name === "AbortError") {
1219
- throw new AudDConnectionError("Request was aborted (timeout)", err);
1220
- }
1221
- throw err;
1222
- }
1223
- }
1224
1317
  var CustomCatalog = class {
1225
- constructor(http, noRetryPolicy) {
1318
+ constructor(http, noRetryPolicy, onEvent) {
1226
1319
  this.http = http;
1227
1320
  this.noRetryPolicy = noRetryPolicy;
1321
+ this.onEvent = onEvent;
1228
1322
  }
1229
1323
  http;
1230
1324
  noRetryPolicy;
1325
+ onEvent;
1231
1326
  /**
1232
1327
  * **This is NOT how you submit audio for music recognition.** For
1233
1328
  * recognition, use `audd.recognize()` (or `audd.recognizeEnterprise()` for
@@ -1248,42 +1343,36 @@ var CustomCatalog = class {
1248
1343
  async add(opts) {
1249
1344
  const reopen = prepareSource(opts.source);
1250
1345
  const audioId = String(opts.audioId);
1251
- const resp = await runRetried2(async () => {
1252
- const prepared = await reopen();
1253
- const fields = { ...prepared.fields, audio_id: audioId };
1254
- return this.http.postForm(UPLOAD_URL, fields);
1255
- }, this.noRetryPolicy);
1346
+ const resp = await emitAround(
1347
+ this.onEvent,
1348
+ "customCatalogAdd",
1349
+ UPLOAD_URL,
1350
+ () => runRetried(async () => {
1351
+ const prepared = await reopen();
1352
+ const fields = { ...prepared.fields, audio_id: audioId };
1353
+ return this.http.postForm(UPLOAD_URL, fields);
1354
+ }, this.noRetryPolicy)
1355
+ );
1256
1356
  decodeSuccess2(resp.jsonBody, resp.httpStatus, resp.requestId);
1257
1357
  }
1258
1358
  };
1259
1359
 
1260
1360
  // src/advanced.ts
1261
1361
  var API_BASE2 = "https://api.audd.io";
1262
- async function runRetried3(fn, policy) {
1263
- try {
1264
- return await retry(fn, policy);
1265
- } catch (err) {
1266
- if (err instanceof TypeError) {
1267
- throw new AudDConnectionError(`Network error: ${err.message}`, err);
1268
- }
1269
- if (err !== null && typeof err === "object" && err.name === "AbortError") {
1270
- throw new AudDConnectionError("Request was aborted (timeout)", err);
1271
- }
1272
- throw err;
1273
- }
1274
- }
1275
1362
  var Advanced = class {
1276
1363
  /**
1277
1364
  * @param recognitionPolicy The findLyrics endpoint is metered and shouldn't
1278
1365
  * double-bill on post-upload read timeout: Advanced uses RECOGNITION retry
1279
1366
  * policy, not READ.
1280
1367
  */
1281
- constructor(http, recognitionPolicy) {
1368
+ constructor(http, recognitionPolicy, onEvent) {
1282
1369
  this.http = http;
1283
1370
  this.recognitionPolicy = recognitionPolicy;
1371
+ this.onEvent = onEvent;
1284
1372
  }
1285
1373
  http;
1286
1374
  recognitionPolicy;
1375
+ onEvent;
1287
1376
  /** Find lyrics by free-text query. Returns a list of matches (possibly empty). */
1288
1377
  async findLyrics(query) {
1289
1378
  const body = await this.rawRequest("findLyrics", { q: query });
@@ -1307,9 +1396,12 @@ var Advanced = class {
1307
1396
  if (v === void 0) continue;
1308
1397
  fields[k] = String(v);
1309
1398
  }
1310
- const resp = await runRetried3(
1311
- () => this.http.postForm(`${API_BASE2}/${method}/`, fields),
1312
- this.recognitionPolicy
1399
+ const url = `${API_BASE2}/${method}/`;
1400
+ const resp = await emitAround(
1401
+ this.onEvent,
1402
+ method,
1403
+ url,
1404
+ () => runRetried(() => this.http.postForm(url, fields), this.recognitionPolicy)
1313
1405
  );
1314
1406
  const body = resp.jsonBody;
1315
1407
  if (typeof body !== "object" || body === null || Array.isArray(body)) {
@@ -1325,13 +1417,6 @@ var ENTERPRISE_BASE = "https://enterprise.audd.io";
1325
1417
  var DEPRECATED_PARAMS_CODE = 51;
1326
1418
  var HTTP_CLIENT_ERROR_FLOOR2 = 400;
1327
1419
  var TOKEN_ENV_VAR = "AUDD_API_TOKEN";
1328
- function safeEmit(hook, event) {
1329
- if (hook === void 0) return;
1330
- try {
1331
- hook(event);
1332
- } catch {
1333
- }
1334
- }
1335
1420
  function resolveToken(apiToken) {
1336
1421
  if (apiToken !== void 0 && apiToken !== "") return apiToken;
1337
1422
  const env = (typeof process !== "undefined" ? process.env[TOKEN_ENV_VAR] : void 0) ?? "";
@@ -1354,8 +1439,8 @@ function buildEnterpriseFields(opts) {
1354
1439
  if (opts.skipFirstSeconds !== void 0)
1355
1440
  fields["skip_first_seconds"] = String(opts.skipFirstSeconds);
1356
1441
  if (opts.useTimecode !== void 0) fields["use_timecode"] = opts.useTimecode ? "true" : "false";
1357
- if (opts.accurateOffsets !== void 0)
1358
- fields["accurate_offsets"] = opts.accurateOffsets ? "true" : "false";
1442
+ const accurate = opts.accurateOffsets ?? true;
1443
+ fields["accurate_offsets"] = accurate ? "true" : "false";
1359
1444
  return fields;
1360
1445
  }
1361
1446
  function isDeprecationPassThrough(body) {
@@ -1404,7 +1489,7 @@ function decodeOrRaise(resp) {
1404
1489
  function decodeRecognize(resp) {
1405
1490
  const body = decodeOrRaise(resp);
1406
1491
  const result = body["result"];
1407
- if (result == null) return null;
1492
+ if (asObject(result) === void 0) return null;
1408
1493
  return parseRecognitionResult(result);
1409
1494
  }
1410
1495
  function decodeEnterprise(resp) {
@@ -1413,24 +1498,12 @@ function decodeEnterprise(resp) {
1413
1498
  if (!Array.isArray(chunks)) return [];
1414
1499
  const out = [];
1415
1500
  for (const c of chunks) {
1501
+ if (asObject(c) === void 0) continue;
1416
1502
  const chunk = parseEnterpriseChunkResult(c);
1417
1503
  out.push(...chunk.songs);
1418
1504
  }
1419
1505
  return out;
1420
1506
  }
1421
- async function runRetried4(fn, policy) {
1422
- try {
1423
- return await retry(fn, policy);
1424
- } catch (err) {
1425
- if (err instanceof TypeError) {
1426
- throw new AudDConnectionError(`Network error: ${err.message}`, err);
1427
- }
1428
- if (err !== null && typeof err === "object" && err.name === "AbortError") {
1429
- throw new AudDConnectionError("Request was aborted (timeout)", err);
1430
- }
1431
- throw err;
1432
- }
1433
- }
1434
1507
  var AudD = class {
1435
1508
  _http;
1436
1509
  _enterpriseHttp;
@@ -1488,7 +1561,8 @@ var AudD = class {
1488
1561
  this._http,
1489
1562
  this.policyFor("read"),
1490
1563
  this.policyFor("mutating"),
1491
- this._apiToken
1564
+ () => this._apiToken,
1565
+ this._onEvent
1492
1566
  );
1493
1567
  }
1494
1568
  return this._streams;
@@ -1496,14 +1570,14 @@ var AudD = class {
1496
1570
  /** Sub-namespace for the private fingerprint catalog. NOT for recognition. */
1497
1571
  get customCatalog() {
1498
1572
  if (this._customCatalog === void 0) {
1499
- this._customCatalog = new CustomCatalog(this._http, this.policyFor("none"));
1573
+ this._customCatalog = new CustomCatalog(this._http, this.policyFor("none"), this._onEvent);
1500
1574
  }
1501
1575
  return this._customCatalog;
1502
1576
  }
1503
1577
  /** Lyrics search + raw-request escape hatch. */
1504
1578
  get advanced() {
1505
1579
  if (this._advanced === void 0) {
1506
- this._advanced = new Advanced(this._http, this.policyFor("recognition"));
1580
+ this._advanced = new Advanced(this._http, this.policyFor("recognition"), this._onEvent);
1507
1581
  }
1508
1582
  return this._advanced;
1509
1583
  }
@@ -1519,20 +1593,11 @@ var AudD = class {
1519
1593
  const market = opts.market;
1520
1594
  const policy = this.policyFor("recognition");
1521
1595
  const url = `${API_BASE3}/`;
1522
- const startedAt = Date.now();
1523
- safeEmit(this._onEvent, {
1524
- kind: "request",
1525
- method: "recognize",
1596
+ const resp = await emitAround(
1597
+ this._onEvent,
1598
+ "recognize",
1526
1599
  url,
1527
- requestId: null,
1528
- httpStatus: null,
1529
- elapsedMs: null,
1530
- errorCode: null,
1531
- extras: {}
1532
- });
1533
- let resp;
1534
- try {
1535
- resp = await runRetried4(async () => {
1600
+ () => runRetried(async () => {
1536
1601
  const prepared = await reopen();
1537
1602
  const fields = { ...prepared.fields };
1538
1603
  if (opts.extraParameters) Object.assign(fields, opts.extraParameters);
@@ -1542,30 +1607,8 @@ var AudD = class {
1542
1607
  ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
1543
1608
  ...opts.signal !== void 0 ? { signal: opts.signal } : {}
1544
1609
  });
1545
- }, policy);
1546
- } catch (err) {
1547
- safeEmit(this._onEvent, {
1548
- kind: "exception",
1549
- method: "recognize",
1550
- url,
1551
- requestId: null,
1552
- httpStatus: null,
1553
- elapsedMs: Date.now() - startedAt,
1554
- errorCode: null,
1555
- extras: { name: err instanceof Error ? err.name : String(err) }
1556
- });
1557
- throw err;
1558
- }
1559
- safeEmit(this._onEvent, {
1560
- kind: "response",
1561
- method: "recognize",
1562
- url,
1563
- requestId: resp.requestId,
1564
- httpStatus: resp.httpStatus,
1565
- elapsedMs: Date.now() - startedAt,
1566
- errorCode: null,
1567
- extras: {}
1568
- });
1610
+ }, policy)
1611
+ );
1569
1612
  return decodeRecognize(resp);
1570
1613
  }
1571
1614
  /**
@@ -1578,14 +1621,20 @@ var AudD = class {
1578
1621
  const reopen = prepareSource(source);
1579
1622
  const extra = buildEnterpriseFields(opts);
1580
1623
  const policy = this.policyFor("recognition");
1581
- const resp = await runRetried4(async () => {
1582
- const prepared = await reopen();
1583
- const fields = { ...prepared.fields, ...extra };
1584
- return this._enterpriseHttp.postForm(`${ENTERPRISE_BASE}/`, fields, {
1585
- ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
1586
- ...opts.signal !== void 0 ? { signal: opts.signal } : {}
1587
- });
1588
- }, policy);
1624
+ const url = `${ENTERPRISE_BASE}/`;
1625
+ const resp = await emitAround(
1626
+ this._onEvent,
1627
+ "recognizeEnterprise",
1628
+ url,
1629
+ () => runRetried(async () => {
1630
+ const prepared = await reopen();
1631
+ const fields = { ...prepared.fields, ...extra };
1632
+ return this._enterpriseHttp.postForm(url, fields, {
1633
+ ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
1634
+ ...opts.signal !== void 0 ? { signal: opts.signal } : {}
1635
+ });
1636
+ }, policy)
1637
+ );
1589
1638
  return decodeEnterprise(resp);
1590
1639
  }
1591
1640
  /** Release any underlying resources. (Currently a no-op for `fetch`-based transport.) */