@audd/sdk 1.4.7 → 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 +79 -35
- package/dist/index.cjs +315 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +184 -271
- package/dist/index.d.ts +184 -271
- package/dist/index.js +314 -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/dist/longpoll.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FetchLike } from './
|
|
1
|
+
import { F as FetchLike, L as LongpollPoll } from './longpollCore-DMBdcGSM.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Tokenless longpoll consumer for browser/widget/extension use cases.
|
|
@@ -9,11 +9,12 @@ import { F as FetchLike } from './http-y2YwjfAl.cjs';
|
|
|
9
9
|
* token).
|
|
10
10
|
*
|
|
11
11
|
* Behavior:
|
|
12
|
-
* - HTTP non-2xx →
|
|
13
|
-
*
|
|
12
|
+
* - HTTP non-2xx → terminal {@link AudDServerError} on the `errors` iterable
|
|
13
|
+
* (no silent infinite loop).
|
|
14
|
+
* - JSON decode failure on 2xx → terminal {@link AudDSerializationError}.
|
|
14
15
|
* - READ-class retries on 5xx + connection failures, configurable via
|
|
15
|
-
* `maxRetries` / `backoffFactorMs
|
|
16
|
-
* - `[Symbol.asyncDispose]()` + manual `close()` for resource cleanup
|
|
16
|
+
* `maxRetries` / `backoffFactorMs`.
|
|
17
|
+
* - `[Symbol.asyncDispose]()` + manual `close()` for resource cleanup.
|
|
17
18
|
*
|
|
18
19
|
* Imported from `audd/longpoll` (browser-safe sub-entry, NOT the main
|
|
19
20
|
* `audd` module — bundlers tree-shake the auth client out).
|
|
@@ -33,26 +34,36 @@ interface IterateOptions {
|
|
|
33
34
|
timeout?: number;
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
36
|
-
* Tokenless
|
|
37
|
+
* Tokenless longpoll consumer.
|
|
37
38
|
*
|
|
38
39
|
* ```ts
|
|
39
40
|
* import { LongpollConsumer } from 'audd/longpoll';
|
|
40
41
|
*
|
|
41
42
|
* const consumer = new LongpollConsumer("abc123def");
|
|
42
|
-
*
|
|
43
|
-
*
|
|
43
|
+
* const poll = consumer.iterate({ timeout: 30 });
|
|
44
|
+
* for await (const m of poll.matches) {
|
|
45
|
+
* console.log(m.song.artist, m.song.title);
|
|
44
46
|
* }
|
|
45
47
|
* ```
|
|
48
|
+
*
|
|
49
|
+
* Or iterate matches and notifications concurrently:
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* await Promise.all([
|
|
53
|
+
* (async () => { for await (const m of poll.matches) { ... } })(),
|
|
54
|
+
* (async () => { for await (const n of poll.notifications) { ... } })(),
|
|
55
|
+
* ]);
|
|
56
|
+
* ```
|
|
46
57
|
*/
|
|
47
58
|
declare class LongpollConsumer {
|
|
48
59
|
readonly category: string;
|
|
49
60
|
private readonly fetchImpl;
|
|
50
61
|
private readonly policy;
|
|
51
62
|
constructor(category: string, opts?: LongpollConsumerOptions);
|
|
52
|
-
iterate(opts?: IterateOptions):
|
|
63
|
+
iterate(opts?: IterateOptions): LongpollPoll;
|
|
53
64
|
/** Release any underlying resources. */
|
|
54
65
|
close(): void;
|
|
55
66
|
[Symbol.asyncDispose](): Promise<void>;
|
|
56
67
|
}
|
|
57
68
|
|
|
58
|
-
export { type IterateOptions, LongpollConsumer, type LongpollConsumerOptions };
|
|
69
|
+
export { type IterateOptions, LongpollConsumer, type LongpollConsumerOptions, LongpollPoll };
|
package/dist/longpoll.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FetchLike } from './
|
|
1
|
+
import { F as FetchLike, L as LongpollPoll } from './longpollCore-DMBdcGSM.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Tokenless longpoll consumer for browser/widget/extension use cases.
|
|
@@ -9,11 +9,12 @@ import { F as FetchLike } from './http-y2YwjfAl.js';
|
|
|
9
9
|
* token).
|
|
10
10
|
*
|
|
11
11
|
* Behavior:
|
|
12
|
-
* - HTTP non-2xx →
|
|
13
|
-
*
|
|
12
|
+
* - HTTP non-2xx → terminal {@link AudDServerError} on the `errors` iterable
|
|
13
|
+
* (no silent infinite loop).
|
|
14
|
+
* - JSON decode failure on 2xx → terminal {@link AudDSerializationError}.
|
|
14
15
|
* - READ-class retries on 5xx + connection failures, configurable via
|
|
15
|
-
* `maxRetries` / `backoffFactorMs
|
|
16
|
-
* - `[Symbol.asyncDispose]()` + manual `close()` for resource cleanup
|
|
16
|
+
* `maxRetries` / `backoffFactorMs`.
|
|
17
|
+
* - `[Symbol.asyncDispose]()` + manual `close()` for resource cleanup.
|
|
17
18
|
*
|
|
18
19
|
* Imported from `audd/longpoll` (browser-safe sub-entry, NOT the main
|
|
19
20
|
* `audd` module — bundlers tree-shake the auth client out).
|
|
@@ -33,26 +34,36 @@ interface IterateOptions {
|
|
|
33
34
|
timeout?: number;
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
36
|
-
* Tokenless
|
|
37
|
+
* Tokenless longpoll consumer.
|
|
37
38
|
*
|
|
38
39
|
* ```ts
|
|
39
40
|
* import { LongpollConsumer } from 'audd/longpoll';
|
|
40
41
|
*
|
|
41
42
|
* const consumer = new LongpollConsumer("abc123def");
|
|
42
|
-
*
|
|
43
|
-
*
|
|
43
|
+
* const poll = consumer.iterate({ timeout: 30 });
|
|
44
|
+
* for await (const m of poll.matches) {
|
|
45
|
+
* console.log(m.song.artist, m.song.title);
|
|
44
46
|
* }
|
|
45
47
|
* ```
|
|
48
|
+
*
|
|
49
|
+
* Or iterate matches and notifications concurrently:
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* await Promise.all([
|
|
53
|
+
* (async () => { for await (const m of poll.matches) { ... } })(),
|
|
54
|
+
* (async () => { for await (const n of poll.notifications) { ... } })(),
|
|
55
|
+
* ]);
|
|
56
|
+
* ```
|
|
46
57
|
*/
|
|
47
58
|
declare class LongpollConsumer {
|
|
48
59
|
readonly category: string;
|
|
49
60
|
private readonly fetchImpl;
|
|
50
61
|
private readonly policy;
|
|
51
62
|
constructor(category: string, opts?: LongpollConsumerOptions);
|
|
52
|
-
iterate(opts?: IterateOptions):
|
|
63
|
+
iterate(opts?: IterateOptions): LongpollPoll;
|
|
53
64
|
/** Release any underlying resources. */
|
|
54
65
|
close(): void;
|
|
55
66
|
[Symbol.asyncDispose](): Promise<void>;
|
|
56
67
|
}
|
|
57
68
|
|
|
58
|
-
export { type IterateOptions, LongpollConsumer, type LongpollConsumerOptions };
|
|
69
|
+
export { type IterateOptions, LongpollConsumer, type LongpollConsumerOptions, LongpollPoll };
|
package/dist/longpoll.js
CHANGED
|
@@ -45,6 +45,272 @@ var AudDSerializationError = class extends AudDError {
|
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
// src/models.ts
|
|
49
|
+
function pickExtras(raw, knownKeys) {
|
|
50
|
+
const known = new Set(knownKeys);
|
|
51
|
+
const extras = {};
|
|
52
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
53
|
+
if (!known.has(k)) extras[k] = v;
|
|
54
|
+
}
|
|
55
|
+
return extras;
|
|
56
|
+
}
|
|
57
|
+
function asObject(raw) {
|
|
58
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
59
|
+
return raw;
|
|
60
|
+
}
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
function requireObject(raw, what) {
|
|
64
|
+
const o = asObject(raw);
|
|
65
|
+
if (o === void 0) throw new TypeError(`${what}: expected object`);
|
|
66
|
+
return o;
|
|
67
|
+
}
|
|
68
|
+
function asString(v) {
|
|
69
|
+
return typeof v === "string" ? v : void 0;
|
|
70
|
+
}
|
|
71
|
+
function asNumber(v) {
|
|
72
|
+
return typeof v === "number" ? v : void 0;
|
|
73
|
+
}
|
|
74
|
+
function asBoolean(v) {
|
|
75
|
+
return typeof v === "boolean" ? v : void 0;
|
|
76
|
+
}
|
|
77
|
+
var STREAM_CALLBACK_SONG_KEYS = [
|
|
78
|
+
"artist",
|
|
79
|
+
"title",
|
|
80
|
+
"score",
|
|
81
|
+
"album",
|
|
82
|
+
"release_date",
|
|
83
|
+
"label",
|
|
84
|
+
"song_link",
|
|
85
|
+
"isrc",
|
|
86
|
+
"upc",
|
|
87
|
+
"apple_music",
|
|
88
|
+
"spotify",
|
|
89
|
+
"deezer",
|
|
90
|
+
"napster",
|
|
91
|
+
"musicbrainz"
|
|
92
|
+
];
|
|
93
|
+
function parseStreamCallbackSong(raw) {
|
|
94
|
+
const r = requireObject(raw, "StreamCallbackSong");
|
|
95
|
+
const artist = asString(r.artist);
|
|
96
|
+
const title = asString(r.title);
|
|
97
|
+
const score = asNumber(r.score);
|
|
98
|
+
if (artist === void 0) throw new TypeError("StreamCallbackSong: artist not string");
|
|
99
|
+
if (title === void 0) throw new TypeError("StreamCallbackSong: title not string");
|
|
100
|
+
if (score === void 0) throw new TypeError("StreamCallbackSong: score not number");
|
|
101
|
+
const musicbrainz = Array.isArray(r.musicbrainz) ? r.musicbrainz.filter((x) => asObject(x) !== void 0) : void 0;
|
|
102
|
+
return {
|
|
103
|
+
artist,
|
|
104
|
+
title,
|
|
105
|
+
score,
|
|
106
|
+
album: asString(r.album),
|
|
107
|
+
releaseDate: asString(r.release_date),
|
|
108
|
+
label: asString(r.label),
|
|
109
|
+
songLink: asString(r.song_link),
|
|
110
|
+
isrc: asString(r.isrc),
|
|
111
|
+
upc: asString(r.upc),
|
|
112
|
+
appleMusic: asObject(r.apple_music),
|
|
113
|
+
spotify: asObject(r.spotify),
|
|
114
|
+
deezer: asObject(r.deezer),
|
|
115
|
+
napster: asObject(r.napster),
|
|
116
|
+
musicbrainz,
|
|
117
|
+
extras: pickExtras(r, STREAM_CALLBACK_SONG_KEYS)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
var STREAM_CALLBACK_MATCH_KEYS = [
|
|
121
|
+
"radio_id",
|
|
122
|
+
"timestamp",
|
|
123
|
+
"play_length",
|
|
124
|
+
"results"
|
|
125
|
+
];
|
|
126
|
+
function parseStreamCallbackMatch(raw) {
|
|
127
|
+
const r = requireObject(raw, "StreamCallbackMatch");
|
|
128
|
+
const radioId = asNumber(r.radio_id);
|
|
129
|
+
if (radioId === void 0) throw new TypeError("StreamCallbackMatch: radio_id not number");
|
|
130
|
+
const resultsRaw = r.results;
|
|
131
|
+
if (!Array.isArray(resultsRaw)) throw new TypeError("StreamCallbackMatch: results not array");
|
|
132
|
+
if (resultsRaw.length === 0) throw new TypeError("StreamCallbackMatch: results is empty");
|
|
133
|
+
const songs = resultsRaw.map(parseStreamCallbackSong);
|
|
134
|
+
const [first, ...rest] = songs;
|
|
135
|
+
if (first === void 0) throw new TypeError("StreamCallbackMatch: results is empty");
|
|
136
|
+
return {
|
|
137
|
+
radioId,
|
|
138
|
+
timestamp: asString(r.timestamp),
|
|
139
|
+
playLength: asNumber(r.play_length),
|
|
140
|
+
song: first,
|
|
141
|
+
alternatives: rest,
|
|
142
|
+
extras: pickExtras(r, STREAM_CALLBACK_MATCH_KEYS),
|
|
143
|
+
rawResponse: r
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
var STREAM_CALLBACK_NOTIFICATION_KEYS = [
|
|
147
|
+
"radio_id",
|
|
148
|
+
"stream_running",
|
|
149
|
+
"notification_code",
|
|
150
|
+
"notification_message"
|
|
151
|
+
];
|
|
152
|
+
function parseStreamCallbackNotification(raw) {
|
|
153
|
+
const r = requireObject(raw, "StreamCallbackNotification");
|
|
154
|
+
const radioId = asNumber(r.radio_id);
|
|
155
|
+
if (radioId === void 0)
|
|
156
|
+
throw new TypeError("StreamCallbackNotification: radio_id not number");
|
|
157
|
+
const code = asNumber(r.notification_code);
|
|
158
|
+
if (code === void 0)
|
|
159
|
+
throw new TypeError("StreamCallbackNotification: notification_code not number");
|
|
160
|
+
const message = asString(r.notification_message);
|
|
161
|
+
if (message === void 0)
|
|
162
|
+
throw new TypeError("StreamCallbackNotification: notification_message not string");
|
|
163
|
+
return {
|
|
164
|
+
radioId,
|
|
165
|
+
streamRunning: asBoolean(r.stream_running),
|
|
166
|
+
notificationCode: code,
|
|
167
|
+
notificationMessage: message,
|
|
168
|
+
extras: pickExtras(r, STREAM_CALLBACK_NOTIFICATION_KEYS),
|
|
169
|
+
rawResponse: r
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/longpollCore.ts
|
|
174
|
+
var HTTP_CLIENT_ERROR_FLOOR = 400;
|
|
175
|
+
var AsyncQueue = class {
|
|
176
|
+
items = [];
|
|
177
|
+
waiters = [];
|
|
178
|
+
done = false;
|
|
179
|
+
push(value) {
|
|
180
|
+
if (this.done) return;
|
|
181
|
+
const w = this.waiters.shift();
|
|
182
|
+
if (w !== void 0) {
|
|
183
|
+
w({ value, done: false });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this.items.push(value);
|
|
187
|
+
}
|
|
188
|
+
finish() {
|
|
189
|
+
if (this.done) return;
|
|
190
|
+
this.done = true;
|
|
191
|
+
while (this.waiters.length > 0) {
|
|
192
|
+
const w = this.waiters.shift();
|
|
193
|
+
if (w !== void 0) w({ value: void 0, done: true });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
next() {
|
|
197
|
+
const item = this.items.shift();
|
|
198
|
+
if (item !== void 0) return Promise.resolve({ value: item, done: false });
|
|
199
|
+
if (this.done) return Promise.resolve({ value: void 0, done: true });
|
|
200
|
+
return new Promise((resolve) => {
|
|
201
|
+
this.waiters.push(resolve);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
toIterable() {
|
|
205
|
+
const next = () => this.next();
|
|
206
|
+
return {
|
|
207
|
+
[Symbol.asyncIterator]() {
|
|
208
|
+
return { next };
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
function decodeOne(resp) {
|
|
214
|
+
if (resp.httpStatus >= HTTP_CLIENT_ERROR_FLOOR) {
|
|
215
|
+
throw new AudDServerError({
|
|
216
|
+
errorCode: 0,
|
|
217
|
+
message: `Longpoll endpoint returned HTTP ${String(resp.httpStatus)}`,
|
|
218
|
+
httpStatus: resp.httpStatus,
|
|
219
|
+
requestId: resp.requestId,
|
|
220
|
+
rawResponse: resp.jsonBody ?? resp.rawText
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const body = resp.jsonBody;
|
|
224
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
225
|
+
throw new AudDSerializationError(
|
|
226
|
+
"Longpoll response was not a JSON object",
|
|
227
|
+
resp.rawText
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
const dict = body;
|
|
231
|
+
if ("notification" in dict) {
|
|
232
|
+
const notification = parseStreamCallbackNotification(dict.notification);
|
|
233
|
+
const t = dict["time"];
|
|
234
|
+
if (typeof t === "number") notification.time = t;
|
|
235
|
+
return { kind: "notification", notification };
|
|
236
|
+
}
|
|
237
|
+
if ("result" in dict) {
|
|
238
|
+
return { kind: "match", match: parseStreamCallbackMatch(dict.result) };
|
|
239
|
+
}
|
|
240
|
+
return { kind: "keepalive" };
|
|
241
|
+
}
|
|
242
|
+
function startLongpoll(cfg) {
|
|
243
|
+
const matches = new AsyncQueue();
|
|
244
|
+
const notifications = new AsyncQueue();
|
|
245
|
+
const errors = new AsyncQueue();
|
|
246
|
+
const controller = new AbortController();
|
|
247
|
+
let closed = false;
|
|
248
|
+
const finishAll = () => {
|
|
249
|
+
matches.finish();
|
|
250
|
+
notifications.finish();
|
|
251
|
+
errors.finish();
|
|
252
|
+
};
|
|
253
|
+
const close = () => {
|
|
254
|
+
if (closed) return;
|
|
255
|
+
closed = true;
|
|
256
|
+
controller.abort();
|
|
257
|
+
finishAll();
|
|
258
|
+
};
|
|
259
|
+
const run = async () => {
|
|
260
|
+
let curSince = cfg.sinceTime;
|
|
261
|
+
while (!closed) {
|
|
262
|
+
const params = {
|
|
263
|
+
category: cfg.category,
|
|
264
|
+
timeout: String(cfg.timeout)
|
|
265
|
+
};
|
|
266
|
+
if (curSince !== void 0) params["since_time"] = String(curSince);
|
|
267
|
+
let resp;
|
|
268
|
+
try {
|
|
269
|
+
resp = await cfg.fetchOnce(params, controller.signal);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
if (closed) return;
|
|
272
|
+
if (err instanceof TypeError) {
|
|
273
|
+
errors.push(new AudDConnectionError(`Network error: ${err.message}`, err));
|
|
274
|
+
} else if (err !== null && typeof err === "object" && err.name === "AbortError") {
|
|
275
|
+
if (closed) return;
|
|
276
|
+
errors.push(new AudDConnectionError("Request was aborted (timeout)", err));
|
|
277
|
+
} else if (err instanceof Error) {
|
|
278
|
+
errors.push(err);
|
|
279
|
+
} else {
|
|
280
|
+
errors.push(new Error(String(err)));
|
|
281
|
+
}
|
|
282
|
+
finishAll();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const decoded = decodeOne(resp);
|
|
287
|
+
if (decoded.kind === "match") matches.push(decoded.match);
|
|
288
|
+
else if (decoded.kind === "notification") notifications.push(decoded.notification);
|
|
289
|
+
} catch (err) {
|
|
290
|
+
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
291
|
+
finishAll();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const dict = resp.jsonBody;
|
|
295
|
+
if (typeof dict === "object" && dict !== null && !Array.isArray(dict)) {
|
|
296
|
+
const ts = dict["timestamp"];
|
|
297
|
+
if (typeof ts === "number") curSince = ts;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
void run();
|
|
302
|
+
return {
|
|
303
|
+
matches: matches.toIterable(),
|
|
304
|
+
notifications: notifications.toIterable(),
|
|
305
|
+
errors: errors.toIterable(),
|
|
306
|
+
close,
|
|
307
|
+
async [Symbol.asyncDispose]() {
|
|
308
|
+
close();
|
|
309
|
+
await Promise.resolve();
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
48
314
|
// src/retry.ts
|
|
49
315
|
var DEFAULT_BACKOFF_FACTOR_MS = 500;
|
|
50
316
|
var DEFAULT_BACKOFF_MAX_MS = 3e4;
|
|
@@ -133,23 +399,6 @@ function userAgent() {
|
|
|
133
399
|
|
|
134
400
|
// src/longpoll.ts
|
|
135
401
|
var LONGPOLL_URL = "https://api.audd.io/longpoll/";
|
|
136
|
-
var HTTP_CLIENT_ERROR_FLOOR = 400;
|
|
137
|
-
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
138
|
-
function decode(jsonBody, httpStatus, rawText) {
|
|
139
|
-
if (httpStatus >= HTTP_CLIENT_ERROR_FLOOR) {
|
|
140
|
-
throw new AudDServerError({
|
|
141
|
-
errorCode: 0,
|
|
142
|
-
message: `Longpoll endpoint returned HTTP ${String(httpStatus)}`,
|
|
143
|
-
httpStatus,
|
|
144
|
-
requestId: null,
|
|
145
|
-
rawResponse: jsonBody ?? rawText
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
if (typeof jsonBody !== "object" || jsonBody === null || Array.isArray(jsonBody)) {
|
|
149
|
-
throw new AudDSerializationError("Longpoll response was not a JSON object", rawText);
|
|
150
|
-
}
|
|
151
|
-
return jsonBody;
|
|
152
|
-
}
|
|
153
402
|
var LongpollConsumer = class {
|
|
154
403
|
constructor(category, opts = {}) {
|
|
155
404
|
this.category = category;
|
|
@@ -165,70 +414,54 @@ var LongpollConsumer = class {
|
|
|
165
414
|
fetchImpl;
|
|
166
415
|
policy;
|
|
167
416
|
iterate(opts = {}) {
|
|
168
|
-
const category = this.category;
|
|
169
|
-
const policy = this.policy;
|
|
170
417
|
const fetchImpl = this.fetchImpl;
|
|
171
|
-
const
|
|
418
|
+
const policy = this.policy;
|
|
172
419
|
const timeoutSec = opts.timeout ?? 50;
|
|
173
|
-
async
|
|
420
|
+
const fetchOnceRaw = async (params, signal) => {
|
|
174
421
|
const u = new URL(LONGPOLL_URL);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (text !== "") {
|
|
191
|
-
try {
|
|
192
|
-
json = JSON.parse(text);
|
|
193
|
-
} catch {
|
|
194
|
-
json = null;
|
|
195
|
-
}
|
|
422
|
+
for (const [k, v] of Object.entries(params)) {
|
|
423
|
+
if (v !== void 0) u.searchParams.set(k, v);
|
|
424
|
+
}
|
|
425
|
+
const response = await fetchImpl(u.toString(), {
|
|
426
|
+
method: "GET",
|
|
427
|
+
headers: { "User-Agent": userAgent() },
|
|
428
|
+
signal
|
|
429
|
+
});
|
|
430
|
+
const text = await response.text();
|
|
431
|
+
let json = null;
|
|
432
|
+
if (text !== "") {
|
|
433
|
+
try {
|
|
434
|
+
json = JSON.parse(text);
|
|
435
|
+
} catch {
|
|
436
|
+
json = null;
|
|
196
437
|
}
|
|
197
|
-
return {
|
|
198
|
-
jsonBody: json,
|
|
199
|
-
httpStatus: response.status,
|
|
200
|
-
requestId: response.headers.get("x-request-id"),
|
|
201
|
-
rawText: text
|
|
202
|
-
};
|
|
203
|
-
} finally {
|
|
204
|
-
clearTimeout(timer);
|
|
205
438
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const body = decode(resp.jsonBody, resp.httpStatus, resp.rawText);
|
|
225
|
-
const ts = body["timestamp"];
|
|
226
|
-
if (typeof ts === "number") curSince = ts;
|
|
227
|
-
return { value: body, done: false };
|
|
228
|
-
}
|
|
229
|
-
};
|
|
439
|
+
return {
|
|
440
|
+
jsonBody: json,
|
|
441
|
+
httpStatus: response.status,
|
|
442
|
+
requestId: response.headers.get("x-request-id"),
|
|
443
|
+
rawText: text
|
|
444
|
+
};
|
|
445
|
+
};
|
|
446
|
+
const fetchOnce = async (params, signal) => {
|
|
447
|
+
try {
|
|
448
|
+
return await retry(() => fetchOnceRaw(params, signal), policy);
|
|
449
|
+
} catch (err) {
|
|
450
|
+
if (err instanceof TypeError) {
|
|
451
|
+
throw new AudDConnectionError(`Network error: ${err.message}`, err);
|
|
452
|
+
}
|
|
453
|
+
if (err !== null && typeof err === "object" && err.name === "AbortError") {
|
|
454
|
+
throw new AudDConnectionError("Request was aborted (timeout)", err);
|
|
455
|
+
}
|
|
456
|
+
throw err;
|
|
230
457
|
}
|
|
231
458
|
};
|
|
459
|
+
return startLongpoll({
|
|
460
|
+
category: this.category,
|
|
461
|
+
timeout: timeoutSec,
|
|
462
|
+
sinceTime: opts.sinceTime,
|
|
463
|
+
fetchOnce
|
|
464
|
+
});
|
|
232
465
|
}
|
|
233
466
|
/** Release any underlying resources. */
|
|
234
467
|
close() {
|