@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 +9 -8
- package/dist/index.cjs +165 -143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -27
- package/dist/index.d.ts +36 -27
- package/dist/index.js +165 -143
- package/dist/index.js.map +1 -1
- package/dist/longpoll.cjs +39 -18
- package/dist/longpoll.cjs.map +1 -1
- package/dist/longpoll.d.cts +2 -2
- package/dist/longpoll.d.ts +2 -2
- package/dist/longpoll.js +39 -18
- package/dist/longpoll.js.map +1 -1
- package/dist/{longpollCore-ZtFOyojP.d.cts → longpollCore-BXx8PRmC.d.cts} +1 -1
- package/dist/{longpollCore-ZtFOyojP.d.ts → longpollCore-BXx8PRmC.d.ts} +1 -1
- package/package.json +19 -10
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# audd-node
|
|
2
2
|
|
|
3
|
+
[](https://audd.io)
|
|
3
4
|
[](https://github.com/AudDMusic/audd-node/actions/workflows/ci.yml)
|
|
4
5
|
[](https://github.com/AudDMusic/audd-node/actions/workflows/contract.yml)
|
|
5
6
|
[](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("
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
275
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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
|
|
1115
|
-
|
|
1116
|
-
|
|
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) =>
|
|
1214
|
-
|
|
1215
|
-
|
|
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
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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
|
|
1360
|
-
|
|
1361
|
-
this.
|
|
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
|
|
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
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
method: "recognize",
|
|
1618
|
+
const resp = await emitAround(
|
|
1619
|
+
this._onEvent,
|
|
1620
|
+
"recognize",
|
|
1575
1621
|
url,
|
|
1576
|
-
|
|
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
|
-
|
|
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
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
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.) */
|