@audd/sdk 1.4.9 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -20
- package/dist/index.cjs +311 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +184 -273
- package/dist/index.d.ts +184 -273
- package/dist/index.js +310 -106
- package/dist/index.js.map +1 -1
- package/dist/longpoll.cjs +307 -74
- package/dist/longpoll.cjs.map +1 -1
- package/dist/longpoll.d.cts +21 -10
- package/dist/longpoll.d.ts +21 -10
- package/dist/longpoll.js +307 -74
- package/dist/longpoll.js.map +1 -1
- package/dist/longpollCore-DMBdcGSM.d.cts +251 -0
- package/dist/longpollCore-DMBdcGSM.d.ts +251 -0
- package/package.json +1 -1
- package/dist/http-y2YwjfAl.d.cts +0 -37
- package/dist/http-y2YwjfAl.d.ts +0 -37
package/README.md
CHANGED
|
@@ -240,40 +240,86 @@ await audd.streams.add({
|
|
|
240
240
|
const streams = await audd.streams.list();
|
|
241
241
|
```
|
|
242
242
|
|
|
243
|
-
|
|
243
|
+
### Handling callback POSTs
|
|
244
|
+
|
|
245
|
+
Drop `handleCallback` into any HTTP handler — Express, Fastify, Hono,
|
|
246
|
+
or the bare `node:http` module. It duck-types the request: a Web
|
|
247
|
+
`Request`, a Node `IncomingMessage`, or a framework request whose body
|
|
248
|
+
has already been parsed all work without configuration.
|
|
244
249
|
|
|
245
250
|
```ts
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
251
|
+
import express from "express";
|
|
252
|
+
import { handleCallback } from "@audd/sdk";
|
|
253
|
+
|
|
254
|
+
const app = express();
|
|
255
|
+
app.use(express.json());
|
|
256
|
+
|
|
257
|
+
app.post("/audd-callback", async (req, res) => {
|
|
258
|
+
const { match, notification } = await handleCallback(req);
|
|
259
|
+
if (match) {
|
|
260
|
+
console.log(`${match.song.artist} - ${match.song.title} score=${match.song.score}`);
|
|
261
|
+
for (const alt of match.alternatives) {
|
|
262
|
+
// alternatives are variant catalog releases — different artist/title is possible
|
|
263
|
+
console.log(` alt: ${alt.artist} - ${alt.title}`);
|
|
264
|
+
}
|
|
265
|
+
} else if (notification) {
|
|
266
|
+
console.log(`#${notification.notificationCode} ${notification.notificationMessage}`);
|
|
250
267
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
payload.notification!.notificationMessage);
|
|
254
|
-
}
|
|
268
|
+
res.json({ ok: true });
|
|
269
|
+
});
|
|
255
270
|
```
|
|
256
271
|
|
|
272
|
+
If you already have the body bytes (queue consumer, replay tool), call
|
|
273
|
+
`parseCallback(body)` directly — it accepts a parsed JSON object or a
|
|
274
|
+
JSON string and returns the same `{ match, notification }` shape.
|
|
275
|
+
|
|
257
276
|
### Receiving events without a callback URL (longpoll)
|
|
258
277
|
|
|
259
|
-
Useful when you can't expose a public HTTPS receiver.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
278
|
+
Useful when you can't expose a public HTTPS receiver. The poll handle
|
|
279
|
+
exposes three async-iterables — `matches`, `notifications`, `errors` —
|
|
280
|
+
filled by a background loop. Iterate them independently, or in parallel
|
|
281
|
+
via `Promise.all`.
|
|
282
|
+
|
|
283
|
+
Before the first request the SDK runs a one-time `getCallbackUrl`
|
|
284
|
+
preflight: AudD silently discards events for accounts without any
|
|
285
|
+
callback URL set, and the preflight surfaces that as an actionable
|
|
286
|
+
error. Pass `skipCallbackCheck: true` to bypass.
|
|
263
287
|
|
|
264
288
|
```ts
|
|
265
|
-
|
|
266
|
-
|
|
289
|
+
const category = audd.streams.deriveLongpollCategory(12345); // pure, no network call
|
|
290
|
+
const poll = await audd.streams.longpoll(category, { timeout: 30 });
|
|
291
|
+
|
|
292
|
+
for await (const m of poll.matches) {
|
|
293
|
+
console.log(m.song.artist, m.song.title);
|
|
267
294
|
}
|
|
268
295
|
```
|
|
269
296
|
|
|
270
|
-
|
|
271
|
-
`radioId`:
|
|
297
|
+
Consume matches and notifications concurrently:
|
|
272
298
|
|
|
273
299
|
```ts
|
|
274
|
-
|
|
300
|
+
await Promise.all([
|
|
301
|
+
(async () => {
|
|
302
|
+
for await (const m of poll.matches) {
|
|
303
|
+
console.log("match:", m.song.artist, m.song.title);
|
|
304
|
+
}
|
|
305
|
+
})(),
|
|
306
|
+
(async () => {
|
|
307
|
+
for await (const n of poll.notifications) {
|
|
308
|
+
console.log("notification:", n.notificationMessage);
|
|
309
|
+
}
|
|
310
|
+
})(),
|
|
311
|
+
(async () => {
|
|
312
|
+
for await (const err of poll.errors) {
|
|
313
|
+
console.error(err);
|
|
314
|
+
poll.close();
|
|
315
|
+
}
|
|
316
|
+
})(),
|
|
317
|
+
]);
|
|
275
318
|
```
|
|
276
319
|
|
|
320
|
+
`poll.close()` (or the `await using` resource-management form) tears
|
|
321
|
+
down the background loop and completes all three iterables.
|
|
322
|
+
|
|
277
323
|
### Browser / widget consumers
|
|
278
324
|
|
|
279
325
|
The `audd/longpoll` sub-entry exports a tokenless `LongpollConsumer` for
|
|
@@ -285,8 +331,9 @@ client out of the resulting bundle.
|
|
|
285
331
|
import { LongpollConsumer } from "@audd/sdk/longpoll";
|
|
286
332
|
|
|
287
333
|
const consumer = new LongpollConsumer("abc123def");
|
|
288
|
-
|
|
289
|
-
|
|
334
|
+
const poll = consumer.iterate({ timeout: 30 });
|
|
335
|
+
for await (const m of poll.matches) {
|
|
336
|
+
console.log(m.song.artist, m.song.title);
|
|
290
337
|
}
|
|
291
338
|
```
|
|
292
339
|
|
package/dist/index.cjs
CHANGED
|
@@ -522,7 +522,7 @@ function parseStream(raw) {
|
|
|
522
522
|
rawResponse: r
|
|
523
523
|
};
|
|
524
524
|
}
|
|
525
|
-
var
|
|
525
|
+
var STREAM_CALLBACK_SONG_KEYS = [
|
|
526
526
|
"artist",
|
|
527
527
|
"title",
|
|
528
528
|
"score",
|
|
@@ -530,20 +530,22 @@ var STREAM_CALLBACK_RESULT_ENTRY_KEYS = [
|
|
|
530
530
|
"release_date",
|
|
531
531
|
"label",
|
|
532
532
|
"song_link",
|
|
533
|
+
"isrc",
|
|
534
|
+
"upc",
|
|
533
535
|
"apple_music",
|
|
534
536
|
"spotify",
|
|
535
537
|
"deezer",
|
|
536
538
|
"napster",
|
|
537
539
|
"musicbrainz"
|
|
538
540
|
];
|
|
539
|
-
function
|
|
540
|
-
const r = requireObject(raw, "
|
|
541
|
+
function parseStreamCallbackSong(raw) {
|
|
542
|
+
const r = requireObject(raw, "StreamCallbackSong");
|
|
541
543
|
const artist = asString(r.artist);
|
|
542
544
|
const title = asString(r.title);
|
|
543
545
|
const score = asNumber(r.score);
|
|
544
|
-
if (artist === void 0) throw new TypeError("
|
|
545
|
-
if (title === void 0) throw new TypeError("
|
|
546
|
-
if (score === void 0) throw new TypeError("
|
|
546
|
+
if (artist === void 0) throw new TypeError("StreamCallbackSong: artist not string");
|
|
547
|
+
if (title === void 0) throw new TypeError("StreamCallbackSong: title not string");
|
|
548
|
+
if (score === void 0) throw new TypeError("StreamCallbackSong: score not number");
|
|
547
549
|
const musicbrainz = Array.isArray(r.musicbrainz) ? r.musicbrainz.filter((x) => asObject(x) !== void 0) : void 0;
|
|
548
550
|
return {
|
|
549
551
|
artist,
|
|
@@ -553,33 +555,39 @@ function parseStreamCallbackResultEntry(raw) {
|
|
|
553
555
|
releaseDate: asString(r.release_date),
|
|
554
556
|
label: asString(r.label),
|
|
555
557
|
songLink: asString(r.song_link),
|
|
558
|
+
isrc: asString(r.isrc),
|
|
559
|
+
upc: asString(r.upc),
|
|
556
560
|
appleMusic: asObject(r.apple_music),
|
|
557
561
|
spotify: asObject(r.spotify),
|
|
558
562
|
deezer: asObject(r.deezer),
|
|
559
563
|
napster: asObject(r.napster),
|
|
560
564
|
musicbrainz,
|
|
561
|
-
extras: pickExtras(r,
|
|
562
|
-
rawResponse: r
|
|
565
|
+
extras: pickExtras(r, STREAM_CALLBACK_SONG_KEYS)
|
|
563
566
|
};
|
|
564
567
|
}
|
|
565
|
-
var
|
|
568
|
+
var STREAM_CALLBACK_MATCH_KEYS = [
|
|
566
569
|
"radio_id",
|
|
567
570
|
"timestamp",
|
|
568
571
|
"play_length",
|
|
569
572
|
"results"
|
|
570
573
|
];
|
|
571
|
-
function
|
|
572
|
-
const r = requireObject(raw, "
|
|
574
|
+
function parseStreamCallbackMatch(raw) {
|
|
575
|
+
const r = requireObject(raw, "StreamCallbackMatch");
|
|
573
576
|
const radioId = asNumber(r.radio_id);
|
|
574
|
-
if (radioId === void 0) throw new TypeError("
|
|
577
|
+
if (radioId === void 0) throw new TypeError("StreamCallbackMatch: radio_id not number");
|
|
575
578
|
const resultsRaw = r.results;
|
|
576
|
-
if (!Array.isArray(resultsRaw)) throw new TypeError("
|
|
579
|
+
if (!Array.isArray(resultsRaw)) throw new TypeError("StreamCallbackMatch: results not array");
|
|
580
|
+
if (resultsRaw.length === 0) throw new TypeError("StreamCallbackMatch: results is empty");
|
|
581
|
+
const songs = resultsRaw.map(parseStreamCallbackSong);
|
|
582
|
+
const [first, ...rest] = songs;
|
|
583
|
+
if (first === void 0) throw new TypeError("StreamCallbackMatch: results is empty");
|
|
577
584
|
return {
|
|
578
585
|
radioId,
|
|
579
586
|
timestamp: asString(r.timestamp),
|
|
580
587
|
playLength: asNumber(r.play_length),
|
|
581
|
-
|
|
582
|
-
|
|
588
|
+
song: first,
|
|
589
|
+
alternatives: rest,
|
|
590
|
+
extras: pickExtras(r, STREAM_CALLBACK_MATCH_KEYS),
|
|
583
591
|
rawResponse: r
|
|
584
592
|
};
|
|
585
593
|
}
|
|
@@ -609,39 +617,6 @@ function parseStreamCallbackNotification(raw) {
|
|
|
609
617
|
rawResponse: r
|
|
610
618
|
};
|
|
611
619
|
}
|
|
612
|
-
function parseStreamCallback(raw) {
|
|
613
|
-
const r = requireObject(raw, "StreamCallbackPayload");
|
|
614
|
-
if ("notification" in r) {
|
|
615
|
-
const notification = parseStreamCallbackNotification(r.notification);
|
|
616
|
-
const time = asNumber(r.time) ?? null;
|
|
617
|
-
return {
|
|
618
|
-
result: null,
|
|
619
|
-
notification,
|
|
620
|
-
time,
|
|
621
|
-
rawPayload: r,
|
|
622
|
-
get isResult() {
|
|
623
|
-
return false;
|
|
624
|
-
},
|
|
625
|
-
get isNotification() {
|
|
626
|
-
return true;
|
|
627
|
-
}
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
const inner = requireObject(r.result, "StreamCallbackPayload.result");
|
|
631
|
-
const result = parseStreamCallbackResult(inner);
|
|
632
|
-
return {
|
|
633
|
-
result,
|
|
634
|
-
notification: null,
|
|
635
|
-
time: null,
|
|
636
|
-
rawPayload: r,
|
|
637
|
-
get isResult() {
|
|
638
|
-
return true;
|
|
639
|
-
},
|
|
640
|
-
get isNotification() {
|
|
641
|
-
return false;
|
|
642
|
-
}
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
620
|
var LYRICS_KEYS = [
|
|
646
621
|
"artist",
|
|
647
622
|
"title",
|
|
@@ -840,6 +815,246 @@ function addReturnToUrl(url, returnMetadata) {
|
|
|
840
815
|
u.searchParams.set("return", value);
|
|
841
816
|
return u.toString();
|
|
842
817
|
}
|
|
818
|
+
function parseCallback(body) {
|
|
819
|
+
let raw = body;
|
|
820
|
+
if (typeof body === "string") {
|
|
821
|
+
try {
|
|
822
|
+
raw = JSON.parse(body);
|
|
823
|
+
} catch (err) {
|
|
824
|
+
throw new AudDSerializationError(
|
|
825
|
+
`callback body is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
826
|
+
body
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
831
|
+
throw new AudDSerializationError(
|
|
832
|
+
"callback body is not a JSON object",
|
|
833
|
+
typeof body === "string" ? body : ""
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
const r = raw;
|
|
837
|
+
if ("notification" in r) {
|
|
838
|
+
let notification;
|
|
839
|
+
try {
|
|
840
|
+
notification = parseStreamCallbackNotification(r.notification);
|
|
841
|
+
} catch (err) {
|
|
842
|
+
throw new AudDSerializationError(
|
|
843
|
+
`callback notification: ${err instanceof Error ? err.message : String(err)}`,
|
|
844
|
+
typeof body === "string" ? body : JSON.stringify(r)
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
const t = r.time;
|
|
848
|
+
if (typeof t === "number") notification.time = t;
|
|
849
|
+
return { match: null, notification };
|
|
850
|
+
}
|
|
851
|
+
if ("result" in r) {
|
|
852
|
+
let match;
|
|
853
|
+
try {
|
|
854
|
+
match = parseStreamCallbackMatch(r.result);
|
|
855
|
+
} catch (err) {
|
|
856
|
+
throw new AudDSerializationError(
|
|
857
|
+
`callback result: ${err instanceof Error ? err.message : String(err)}`,
|
|
858
|
+
typeof body === "string" ? body : JSON.stringify(r)
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
return { match, notification: null };
|
|
862
|
+
}
|
|
863
|
+
throw new AudDSerializationError(
|
|
864
|
+
"callback body has neither result nor notification",
|
|
865
|
+
typeof body === "string" ? body : JSON.stringify(r)
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
async function handleCallback(req) {
|
|
869
|
+
if (typeof req.text === "function") {
|
|
870
|
+
const text = await req.text();
|
|
871
|
+
return parseCallback(text);
|
|
872
|
+
}
|
|
873
|
+
const reqRecord = req;
|
|
874
|
+
if ("body" in reqRecord && reqRecord.body !== void 0 && reqRecord.body !== null) {
|
|
875
|
+
const body = reqRecord.body;
|
|
876
|
+
if (typeof body === "object" && body !== null && Symbol.asyncIterator in body && // Plain Buffer/Uint8Array also has Symbol.iterator — accept those by value.
|
|
877
|
+
!(body instanceof Uint8Array)) {
|
|
878
|
+
return parseCallback(await drainAsyncIterable(body));
|
|
879
|
+
}
|
|
880
|
+
if (body instanceof Uint8Array) {
|
|
881
|
+
return parseCallback(new TextDecoder().decode(body));
|
|
882
|
+
}
|
|
883
|
+
return parseCallback(body);
|
|
884
|
+
}
|
|
885
|
+
if (typeof req === "object" && req !== null && Symbol.asyncIterator in req) {
|
|
886
|
+
return parseCallback(await drainAsyncIterable(req));
|
|
887
|
+
}
|
|
888
|
+
throw new AudDSerializationError(
|
|
889
|
+
"handleCallback: request has no body, no text() method, and is not async-iterable"
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
async function drainAsyncIterable(it) {
|
|
893
|
+
const chunks = [];
|
|
894
|
+
let stringBuf = "";
|
|
895
|
+
for await (const chunk of it) {
|
|
896
|
+
if (typeof chunk === "string") {
|
|
897
|
+
stringBuf += chunk;
|
|
898
|
+
} else if (chunk instanceof Uint8Array) {
|
|
899
|
+
chunks.push(chunk);
|
|
900
|
+
} else if (chunk !== null && chunk !== void 0) {
|
|
901
|
+
stringBuf += String(chunk);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (chunks.length > 0) {
|
|
905
|
+
let total = 0;
|
|
906
|
+
for (const c of chunks) total += c.byteLength;
|
|
907
|
+
const merged = new Uint8Array(total);
|
|
908
|
+
let offset = 0;
|
|
909
|
+
for (const c of chunks) {
|
|
910
|
+
merged.set(c, offset);
|
|
911
|
+
offset += c.byteLength;
|
|
912
|
+
}
|
|
913
|
+
stringBuf = new TextDecoder().decode(merged) + stringBuf;
|
|
914
|
+
}
|
|
915
|
+
return stringBuf;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// src/longpollCore.ts
|
|
919
|
+
var HTTP_CLIENT_ERROR_FLOOR = 400;
|
|
920
|
+
var AsyncQueue = class {
|
|
921
|
+
items = [];
|
|
922
|
+
waiters = [];
|
|
923
|
+
done = false;
|
|
924
|
+
push(value) {
|
|
925
|
+
if (this.done) return;
|
|
926
|
+
const w = this.waiters.shift();
|
|
927
|
+
if (w !== void 0) {
|
|
928
|
+
w({ value, done: false });
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
this.items.push(value);
|
|
932
|
+
}
|
|
933
|
+
finish() {
|
|
934
|
+
if (this.done) return;
|
|
935
|
+
this.done = true;
|
|
936
|
+
while (this.waiters.length > 0) {
|
|
937
|
+
const w = this.waiters.shift();
|
|
938
|
+
if (w !== void 0) w({ value: void 0, done: true });
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
next() {
|
|
942
|
+
const item = this.items.shift();
|
|
943
|
+
if (item !== void 0) return Promise.resolve({ value: item, done: false });
|
|
944
|
+
if (this.done) return Promise.resolve({ value: void 0, done: true });
|
|
945
|
+
return new Promise((resolve) => {
|
|
946
|
+
this.waiters.push(resolve);
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
toIterable() {
|
|
950
|
+
const next = () => this.next();
|
|
951
|
+
return {
|
|
952
|
+
[Symbol.asyncIterator]() {
|
|
953
|
+
return { next };
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
function decodeOne(resp) {
|
|
959
|
+
if (resp.httpStatus >= HTTP_CLIENT_ERROR_FLOOR) {
|
|
960
|
+
throw new AudDServerError({
|
|
961
|
+
errorCode: 0,
|
|
962
|
+
message: `Longpoll endpoint returned HTTP ${String(resp.httpStatus)}`,
|
|
963
|
+
httpStatus: resp.httpStatus,
|
|
964
|
+
requestId: resp.requestId,
|
|
965
|
+
rawResponse: resp.jsonBody ?? resp.rawText
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
const body = resp.jsonBody;
|
|
969
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
970
|
+
throw new AudDSerializationError(
|
|
971
|
+
"Longpoll response was not a JSON object",
|
|
972
|
+
resp.rawText
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
const dict = body;
|
|
976
|
+
if ("notification" in dict) {
|
|
977
|
+
const notification = parseStreamCallbackNotification(dict.notification);
|
|
978
|
+
const t = dict["time"];
|
|
979
|
+
if (typeof t === "number") notification.time = t;
|
|
980
|
+
return { kind: "notification", notification };
|
|
981
|
+
}
|
|
982
|
+
if ("result" in dict) {
|
|
983
|
+
return { kind: "match", match: parseStreamCallbackMatch(dict.result) };
|
|
984
|
+
}
|
|
985
|
+
return { kind: "keepalive" };
|
|
986
|
+
}
|
|
987
|
+
function startLongpoll(cfg) {
|
|
988
|
+
const matches = new AsyncQueue();
|
|
989
|
+
const notifications = new AsyncQueue();
|
|
990
|
+
const errors = new AsyncQueue();
|
|
991
|
+
const controller = new AbortController();
|
|
992
|
+
let closed = false;
|
|
993
|
+
const finishAll = () => {
|
|
994
|
+
matches.finish();
|
|
995
|
+
notifications.finish();
|
|
996
|
+
errors.finish();
|
|
997
|
+
};
|
|
998
|
+
const close = () => {
|
|
999
|
+
if (closed) return;
|
|
1000
|
+
closed = true;
|
|
1001
|
+
controller.abort();
|
|
1002
|
+
finishAll();
|
|
1003
|
+
};
|
|
1004
|
+
const run = async () => {
|
|
1005
|
+
let curSince = cfg.sinceTime;
|
|
1006
|
+
while (!closed) {
|
|
1007
|
+
const params = {
|
|
1008
|
+
category: cfg.category,
|
|
1009
|
+
timeout: String(cfg.timeout)
|
|
1010
|
+
};
|
|
1011
|
+
if (curSince !== void 0) params["since_time"] = String(curSince);
|
|
1012
|
+
let resp;
|
|
1013
|
+
try {
|
|
1014
|
+
resp = await cfg.fetchOnce(params, controller.signal);
|
|
1015
|
+
} catch (err) {
|
|
1016
|
+
if (closed) return;
|
|
1017
|
+
if (err instanceof TypeError) {
|
|
1018
|
+
errors.push(new AudDConnectionError(`Network error: ${err.message}`, err));
|
|
1019
|
+
} else if (err !== null && typeof err === "object" && err.name === "AbortError") {
|
|
1020
|
+
if (closed) return;
|
|
1021
|
+
errors.push(new AudDConnectionError("Request was aborted (timeout)", err));
|
|
1022
|
+
} else if (err instanceof Error) {
|
|
1023
|
+
errors.push(err);
|
|
1024
|
+
} else {
|
|
1025
|
+
errors.push(new Error(String(err)));
|
|
1026
|
+
}
|
|
1027
|
+
finishAll();
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
try {
|
|
1031
|
+
const decoded = decodeOne(resp);
|
|
1032
|
+
if (decoded.kind === "match") matches.push(decoded.match);
|
|
1033
|
+
else if (decoded.kind === "notification") notifications.push(decoded.notification);
|
|
1034
|
+
} catch (err) {
|
|
1035
|
+
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
1036
|
+
finishAll();
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
const dict = resp.jsonBody;
|
|
1040
|
+
if (typeof dict === "object" && dict !== null && !Array.isArray(dict)) {
|
|
1041
|
+
const ts = dict["timestamp"];
|
|
1042
|
+
if (typeof ts === "number") curSince = ts;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
void run();
|
|
1047
|
+
return {
|
|
1048
|
+
matches: matches.toIterable(),
|
|
1049
|
+
notifications: notifications.toIterable(),
|
|
1050
|
+
errors: errors.toIterable(),
|
|
1051
|
+
close,
|
|
1052
|
+
async [Symbol.asyncDispose]() {
|
|
1053
|
+
close();
|
|
1054
|
+
await Promise.resolve();
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
843
1058
|
|
|
844
1059
|
// src/streams.ts
|
|
845
1060
|
var API_BASE = "https://api.audd.io";
|
|
@@ -946,69 +1161,58 @@ var Streams = class {
|
|
|
946
1161
|
deriveLongpollCategory(radioId) {
|
|
947
1162
|
return deriveLongpollCategory(this.apiToken, radioId);
|
|
948
1163
|
}
|
|
949
|
-
/**
|
|
1164
|
+
/**
|
|
1165
|
+
* Parse a callback POST body into `{ match, notification }`. Pass an
|
|
1166
|
+
* already-parsed JSON value or a string. Exactly one field is non-null on
|
|
1167
|
+
* success. See {@link parseCallback} for details.
|
|
1168
|
+
*/
|
|
950
1169
|
parseCallback(body) {
|
|
951
|
-
return
|
|
1170
|
+
return parseCallback(body);
|
|
952
1171
|
}
|
|
953
1172
|
/**
|
|
954
|
-
*
|
|
955
|
-
*
|
|
1173
|
+
* Long-poll the AudD subscription endpoint.
|
|
1174
|
+
*
|
|
1175
|
+
* Returns a {@link LongpollPoll} handle with three async-iterables —
|
|
1176
|
+
* `matches`, `notifications`, `errors` — that are filled by a background
|
|
1177
|
+
* loop. Iterate them independently or in parallel via `Promise.all([...])`.
|
|
956
1178
|
*
|
|
957
|
-
*
|
|
958
|
-
*
|
|
959
|
-
*
|
|
960
|
-
* `skipCallbackCheck: true`
|
|
1179
|
+
* Before the first request the SDK runs a one-time `getCallbackUrl`
|
|
1180
|
+
* preflight: AudD silently discards events for accounts that haven't set a
|
|
1181
|
+
* callback URL, and the preflight surfaces that misconfiguration as an
|
|
1182
|
+
* actionable {@link AudDInvalidRequestError}. Pass `skipCallbackCheck: true`
|
|
1183
|
+
* to bypass.
|
|
961
1184
|
*/
|
|
962
|
-
longpoll(category, opts = {}) {
|
|
963
|
-
|
|
1185
|
+
async longpoll(category, opts = {}) {
|
|
1186
|
+
if (opts.skipCallbackCheck !== true) {
|
|
1187
|
+
await this.preflightCallbackUrl();
|
|
1188
|
+
}
|
|
964
1189
|
const timeoutSec = opts.timeout ?? 50;
|
|
965
|
-
const
|
|
966
|
-
const getCallbackUrl = () => this.getCallbackUrl();
|
|
967
|
-
const httpGet = (params) => this.http.get(`${API_BASE}/longpoll/`, params);
|
|
1190
|
+
const httpClient = this.http;
|
|
968
1191
|
const readPolicy = this.readPolicy;
|
|
969
|
-
return {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
}
|
|
991
|
-
const params = {
|
|
992
|
-
category,
|
|
993
|
-
timeout: String(timeoutSec)
|
|
994
|
-
};
|
|
995
|
-
if (curSince !== void 0) params["since_time"] = String(curSince);
|
|
996
|
-
const resp = await runRetried(() => httpGet(params), readPolicy);
|
|
997
|
-
const body = resp.jsonBody;
|
|
998
|
-
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
999
|
-
throw new AudDSerializationError(
|
|
1000
|
-
"Unparseable longpoll response",
|
|
1001
|
-
resp.rawText
|
|
1002
|
-
);
|
|
1003
|
-
}
|
|
1004
|
-
const dict = body;
|
|
1005
|
-
const ts = dict["timestamp"];
|
|
1006
|
-
if (typeof ts === "number") curSince = ts;
|
|
1007
|
-
return { value: dict, done: false };
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1192
|
+
return startLongpoll({
|
|
1193
|
+
category,
|
|
1194
|
+
timeout: timeoutSec,
|
|
1195
|
+
sinceTime: opts.sinceTime,
|
|
1196
|
+
fetchOnce: (params, signal) => runRetried(
|
|
1197
|
+
() => httpClient.get(`${API_BASE}/longpoll/`, params, { signal }),
|
|
1198
|
+
readPolicy
|
|
1199
|
+
)
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
async preflightCallbackUrl() {
|
|
1203
|
+
try {
|
|
1204
|
+
await this.getCallbackUrl();
|
|
1205
|
+
} catch (err) {
|
|
1206
|
+
if (err instanceof AudDAPIError && err.errorCode === NO_CALLBACK_ERROR_CODE) {
|
|
1207
|
+
throw new AudDInvalidRequestError({
|
|
1208
|
+
errorCode: 0,
|
|
1209
|
+
message: PREFLIGHT_NO_CALLBACK_HINT,
|
|
1210
|
+
httpStatus: err.httpStatus,
|
|
1211
|
+
requestId: err.requestId
|
|
1212
|
+
});
|
|
1010
1213
|
}
|
|
1011
|
-
|
|
1214
|
+
throw err;
|
|
1215
|
+
}
|
|
1012
1216
|
}
|
|
1013
1217
|
};
|
|
1014
1218
|
|
|
@@ -1146,7 +1350,7 @@ var Advanced = class {
|
|
|
1146
1350
|
var API_BASE3 = "https://api.audd.io";
|
|
1147
1351
|
var ENTERPRISE_BASE = "https://enterprise.audd.io";
|
|
1148
1352
|
var DEPRECATED_PARAMS_CODE = 51;
|
|
1149
|
-
var
|
|
1353
|
+
var HTTP_CLIENT_ERROR_FLOOR2 = 400;
|
|
1150
1354
|
var TOKEN_ENV_VAR = "AUDD_API_TOKEN";
|
|
1151
1355
|
function safeEmit(hook, event) {
|
|
1152
1356
|
if (hook === void 0) return;
|
|
@@ -1194,7 +1398,7 @@ function maybeWarnAndStrip(body) {
|
|
|
1194
1398
|
function decodeOrRaise(resp) {
|
|
1195
1399
|
const body = resp.jsonBody;
|
|
1196
1400
|
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
1197
|
-
if (resp.httpStatus >=
|
|
1401
|
+
if (resp.httpStatus >= HTTP_CLIENT_ERROR_FLOOR2) {
|
|
1198
1402
|
throw new AudDServerError({
|
|
1199
1403
|
errorCode: 0,
|
|
1200
1404
|
message: `HTTP ${String(resp.httpStatus)} with non-JSON response body`,
|
|
@@ -1441,6 +1645,7 @@ exports.VERSION = VERSION;
|
|
|
1441
1645
|
exports.addReturnToUrl = addReturnToUrl;
|
|
1442
1646
|
exports.deriveLongpollCategory = deriveLongpollCategory;
|
|
1443
1647
|
exports.errorClassForCode = errorClassForCode;
|
|
1444
|
-
exports.
|
|
1648
|
+
exports.handleCallback = handleCallback;
|
|
1649
|
+
exports.parseCallback = parseCallback;
|
|
1445
1650
|
//# sourceMappingURL=index.cjs.map
|
|
1446
1651
|
//# sourceMappingURL=index.cjs.map
|