@audd/sdk 1.5.12 → 1.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/longpoll.cjs CHANGED
@@ -76,13 +76,38 @@ function asObjectLenient(raw) {
76
76
  return asObject(raw) ?? {};
77
77
  }
78
78
  function asString(v) {
79
- return typeof v === "string" ? v : void 0;
79
+ if (typeof v === "string") return v;
80
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
81
+ return void 0;
82
+ }
83
+ function toFiniteNumber(v) {
84
+ if (typeof v === "number") return Number.isFinite(v) ? v : void 0;
85
+ if (typeof v === "boolean") return v ? 1 : 0;
86
+ if (typeof v === "string") {
87
+ const t = v.trim();
88
+ if (t === "") return void 0;
89
+ const n = Number(t);
90
+ return Number.isFinite(n) ? n : void 0;
91
+ }
92
+ return void 0;
80
93
  }
81
94
  function asNumber(v) {
82
- return typeof v === "number" ? v : void 0;
95
+ return toFiniteNumber(v);
96
+ }
97
+ function asInteger(v) {
98
+ const n = toFiniteNumber(v);
99
+ return n === void 0 ? void 0 : Math.trunc(n);
83
100
  }
84
101
  function asBoolean(v) {
85
- return typeof v === "boolean" ? v : void 0;
102
+ if (typeof v === "boolean") return v;
103
+ if (typeof v === "number") return v !== 0;
104
+ if (typeof v === "string") {
105
+ const t = v.trim().toLowerCase();
106
+ if (t === "true" || t === "1" || t === "yes" || t === "on") return true;
107
+ if (t === "false" || t === "0" || t === "no" || t === "off" || t === "") return false;
108
+ return void 0;
109
+ }
110
+ return void 0;
86
111
  }
87
112
  var STREAM_CALLBACK_SONG_KEYS = [
88
113
  "artist",
@@ -132,14 +157,14 @@ var STREAM_CALLBACK_MATCH_KEYS = [
132
157
  ];
133
158
  function parseStreamCallbackMatch(raw) {
134
159
  const r = asObjectLenient(raw);
135
- const radioId = asNumber(r.radio_id);
160
+ const radioId = asInteger(r.radio_id);
136
161
  const resultsRaw = Array.isArray(r.results) ? r.results : [];
137
162
  const songs = resultsRaw.filter((x) => asObject(x) !== void 0).map(parseStreamCallbackSong);
138
163
  const [first, ...rest] = songs;
139
164
  return {
140
165
  radioId,
141
166
  timestamp: asString(r.timestamp),
142
- playLength: asNumber(r.play_length),
167
+ playLength: asInteger(r.play_length),
143
168
  song: first,
144
169
  alternatives: rest,
145
170
  extras: pickExtras(r, STREAM_CALLBACK_MATCH_KEYS),
@@ -154,8 +179,8 @@ var STREAM_CALLBACK_NOTIFICATION_KEYS = [
154
179
  ];
155
180
  function parseStreamCallbackNotification(raw) {
156
181
  const r = asObjectLenient(raw);
157
- const radioId = asNumber(r.radio_id);
158
- const code = asNumber(r.notification_code);
182
+ const radioId = asInteger(r.radio_id);
183
+ const code = asInteger(r.notification_code);
159
184
  const message = asString(r.notification_message);
160
185
  return {
161
186
  radioId,
@@ -405,7 +430,7 @@ async function runRetried(fn, policy) {
405
430
  }
406
431
 
407
432
  // src/version.ts
408
- var VERSION = "1.5.12";
433
+ var VERSION = "1.5.13";
409
434
 
410
435
  // src/userAgent.ts
411
436
  function userAgent() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/models.ts","../src/longpollCore.ts","../src/retry.ts","../src/version.ts","../src/userAgent.ts","../src/longpoll.ts"],"names":[],"mappings":"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","file":"longpoll.cjs","sourcesContent":["/** Exception hierarchy for AudD API errors. */\n\nexport interface AudDApiErrorFields {\n errorCode: number;\n message: string;\n httpStatus: number;\n requestId: string | null;\n requestedParams: Record<string, unknown>;\n requestMethod: string | null;\n brandedMessage: string | null;\n rawResponse: unknown;\n}\n\nexport type AudDApiErrorInit = Partial<AudDApiErrorFields> &\n Pick<AudDApiErrorFields, \"errorCode\" | \"message\" | \"httpStatus\">;\n\n/** Base for everything thrown by this SDK. */\nexport class AudDError extends Error {\n override name = \"AudDError\";\n}\n\n/** Server returned `status: error`. Carries the AudD error code + the full echo. */\nexport class AudDAPIError extends AudDError {\n override name = \"AudDAPIError\";\n errorCode: number;\n httpStatus: number;\n requestId: string | null;\n requestedParams: Record<string, unknown>;\n requestMethod: string | null;\n brandedMessage: string | null;\n rawResponse: unknown;\n /** Original `error_message` from the server. (`Error.message` may be overridden by subclasses.) */\n serverMessage: string;\n\n constructor(init: AudDApiErrorInit) {\n super(`[#${init.errorCode}] ${init.message}`);\n this.errorCode = init.errorCode;\n this.serverMessage = init.message;\n this.httpStatus = init.httpStatus;\n this.requestId = init.requestId ?? null;\n this.requestedParams = init.requestedParams ?? {};\n this.requestMethod = init.requestMethod ?? null;\n this.brandedMessage = init.brandedMessage ?? null;\n this.rawResponse = init.rawResponse ?? null;\n }\n}\n\nexport class AudDAuthenticationError extends AudDAPIError {\n override name = \"AudDAuthenticationError\";\n}\nexport class AudDQuotaError extends AudDAPIError {\n override name = \"AudDQuotaError\";\n}\nexport class AudDSubscriptionError extends AudDAPIError {\n override name = \"AudDSubscriptionError\";\n}\nexport class AudDCustomCatalogAccessError extends AudDSubscriptionError {\n override name = \"AudDCustomCatalogAccessError\";\n}\nexport class AudDInvalidRequestError extends AudDAPIError {\n override name = \"AudDInvalidRequestError\";\n}\nexport class AudDInvalidAudioError extends AudDAPIError {\n override name = \"AudDInvalidAudioError\";\n}\nexport class AudDRateLimitError extends AudDAPIError {\n override name = \"AudDRateLimitError\";\n}\nexport class AudDStreamLimitError extends AudDAPIError {\n override name = \"AudDStreamLimitError\";\n}\nexport class AudDNotReleasedError extends AudDAPIError {\n override name = \"AudDNotReleasedError\";\n}\nexport class AudDBlockedError extends AudDAPIError {\n override name = \"AudDBlockedError\";\n}\nexport class AudDNeedsUpdateError extends AudDAPIError {\n override name = \"AudDNeedsUpdateError\";\n}\nexport class AudDServerError extends AudDAPIError {\n override name = \"AudDServerError\";\n}\n\nexport class AudDConnectionError extends AudDError {\n override name = \"AudDConnectionError\";\n override cause?: unknown;\n constructor(message: string, cause?: unknown) {\n super(message);\n this.cause = cause;\n }\n}\n\n/**\n * Internal marker for aborts triggered by the caller's own AbortSignal —\n * distinguishes user cancellation from the SDK's own timeout abort. Never\n * retried; surfaced as an {@link AudDConnectionError} with a cancellation\n * message.\n */\nconst USER_ABORT_ERROR_NAME = \"UserAbortError\";\n\nexport function userAbortError(cause: unknown): Error {\n const err = new Error(\"Request was cancelled by the caller's AbortSignal\");\n err.name = USER_ABORT_ERROR_NAME;\n (err as { cause?: unknown }).cause = cause;\n return err;\n}\n\nexport function isUserAbortError(err: unknown): boolean {\n return (\n err !== null &&\n typeof err === \"object\" &&\n (err as { name?: unknown }).name === USER_ABORT_ERROR_NAME\n );\n}\n\nexport class AudDSerializationError extends AudDError {\n override name = \"AudDSerializationError\";\n rawText: string;\n constructor(message: string, rawText = \"\") {\n super(message);\n this.rawText = rawText;\n }\n}\n\nexport type AudDAPIErrorCtor = new (init: AudDApiErrorInit) => AudDAPIError;\n\nconst CODE_MAP: Record<number, AudDAPIErrorCtor> = {\n 900: AudDAuthenticationError,\n 901: AudDAuthenticationError,\n 903: AudDAuthenticationError,\n 902: AudDQuotaError,\n 904: AudDSubscriptionError,\n 905: AudDSubscriptionError,\n 50: AudDInvalidRequestError,\n 51: AudDInvalidRequestError,\n 600: AudDInvalidRequestError,\n 601: AudDInvalidRequestError,\n 602: AudDInvalidRequestError,\n 700: AudDInvalidRequestError,\n 701: AudDInvalidRequestError,\n 702: AudDInvalidRequestError,\n 906: AudDInvalidRequestError,\n 300: AudDInvalidAudioError,\n 400: AudDInvalidAudioError,\n 500: AudDInvalidAudioError,\n 610: AudDStreamLimitError,\n 611: AudDRateLimitError,\n 907: AudDNotReleasedError,\n 19: AudDBlockedError,\n 31337: AudDBlockedError,\n 20: AudDNeedsUpdateError,\n 100: AudDServerError,\n 1000: AudDServerError,\n};\n\nexport function errorClassForCode(code: number): AudDAPIErrorCtor {\n return CODE_MAP[code] ?? AudDServerError;\n}\n\nfunction brandedMessageOf(result: unknown): string | null {\n if (typeof result !== \"object\" || result === null) return null;\n const r = result as { artist?: unknown; title?: unknown };\n const parts: string[] = [];\n if (typeof r.artist === \"string\" && r.artist.length > 0) parts.push(r.artist);\n if (typeof r.title === \"string\" && r.title.length > 0) parts.push(r.title);\n return parts.length === 0 ? null : parts.join(\" — \");\n}\n\nexport interface ServerErrorBody {\n error?: { error_code?: number | undefined; error_message?: string | undefined } | undefined;\n result?: unknown;\n request_params?: Record<string, unknown> | undefined;\n requested_params?: Record<string, unknown> | undefined;\n request_api_method?: string | undefined;\n}\n\nexport interface RaiseFromErrorOpts {\n httpStatus: number;\n requestId: string | null;\n customCatalogContext?: boolean;\n}\n\nconst CUSTOM_CATALOG_PREFIX =\n \"Adding songs to your custom catalog requires enterprise access that isn't enabled on your account.\\n\\n\" +\n \"Note: the custom-catalog endpoint is for adding songs to your private fingerprint database, not for music recognition. \" +\n \"If you intended to identify music, use recognize(...) (or recognizeEnterprise(...) for files longer than 25 seconds) instead.\\n\\n\" +\n \"To request custom-catalog access, contact api@audd.io.\\n\\n\";\n\nexport function raiseFromErrorResponse(body: ServerErrorBody, opts: RaiseFromErrorOpts): never {\n const code = body.error?.error_code ?? 0;\n const message = body.error?.error_message ?? \"\";\n const requestedParams = body.request_params ?? body.requested_params ?? {};\n const requestMethod = body.request_api_method ?? null;\n const branded = brandedMessageOf(body.result);\n\n const fields: AudDApiErrorFields = {\n errorCode: code,\n message,\n httpStatus: opts.httpStatus,\n requestId: opts.requestId,\n requestedParams,\n requestMethod,\n brandedMessage: branded,\n rawResponse: body,\n };\n\n const Cls = errorClassForCode(code);\n if (opts.customCatalogContext === true && Cls === AudDSubscriptionError) {\n const overridden = `${CUSTOM_CATALOG_PREFIX}[Server message: ${message}]`;\n throw new AudDCustomCatalogAccessError({ ...fields, message: overridden });\n }\n throw new Cls(fields);\n}\n","/**\n * Typed models. Forward-compatible: each model captures unknown keys into\n * `extras`, mirroring audd-python's Pydantic `extra=\"allow\"` pattern.\n */\n\nfunction pickExtras(\n raw: Record<string, unknown>,\n knownKeys: ReadonlyArray<string>,\n): Record<string, unknown> {\n const known = new Set(knownKeys);\n const extras: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(raw)) {\n if (!known.has(k)) extras[k] = v;\n }\n return extras;\n}\n\nexport function asObject(raw: unknown): Record<string, unknown> | undefined {\n if (typeof raw === \"object\" && raw !== null && !Array.isArray(raw)) {\n return raw as Record<string, unknown>;\n }\n return undefined;\n}\n\n/**\n * Lenient object coercion for response payloads: wrong-typed input degrades\n * to an empty object (every field comes out `undefined`) instead of throwing.\n * Response parsing never throws on missing or wrong-typed fields; call sites\n * that can skip a bad entry entirely filter with {@link asObject} first.\n */\nfunction asObjectLenient(raw: unknown): Record<string, unknown> {\n return asObject(raw) ?? {};\n}\n\nfunction asString(v: unknown): string | undefined {\n return typeof v === \"string\" ? v : undefined;\n}\nfunction asNumber(v: unknown): number | undefined {\n return typeof v === \"number\" ? v : undefined;\n}\nfunction asBoolean(v: unknown): boolean | undefined {\n return typeof v === \"boolean\" ? v : undefined;\n}\n\n/**\n * Parse an offset into seconds. Accepts `\"SS\"`, `\"MM:SS\"`, `\"HH:MM:SS\"`, or a\n * numeric value. Returns `undefined` when the value is absent or unparseable —\n * never throws (this SDK degrades on bad response fields).\n */\nexport function parseOffsetToSeconds(o: unknown): number | undefined {\n if (o === undefined || o === null) return undefined;\n if (typeof o === \"number\") return Number.isFinite(o) ? o : undefined;\n const t = String(o).trim();\n if (t === \"\") return undefined;\n let total = 0;\n for (const part of t.split(\":\")) {\n if (part.trim() === \"\") return undefined;\n const n = Number(part);\n if (!Number.isFinite(n)) return undefined;\n total = total * 60 + n;\n }\n return total;\n}\n\n/** Streaming providers supported by the lis.tn redirect helper. */\nexport type StreamingProvider =\n | \"spotify\"\n | \"apple_music\"\n | \"deezer\"\n | \"napster\"\n | \"youtube\";\n\nconst STREAMING_PROVIDERS: readonly StreamingProvider[] = [\n \"spotify\",\n \"apple_music\",\n \"deezer\",\n \"napster\",\n \"youtube\",\n] as const;\n\nfunction lisTnStreamingUrl(songLink: string | undefined, provider: string): string | null {\n if (songLink === undefined || songLink === \"\") return null;\n let parsed: URL;\n try {\n parsed = new URL(songLink);\n } catch {\n return null;\n }\n if (parsed.hostname !== \"lis.tn\") return null;\n const sep = parsed.search ? \"&\" : \"?\";\n return `${songLink}${sep}${provider}`;\n}\n\nexport interface RecognitionResult {\n /** Always present on a match. */\n timecode?: string | undefined;\n /** Set on custom-catalog matches. */\n audioId?: number | undefined;\n artist?: string | undefined;\n title?: string | undefined;\n album?: string | undefined;\n releaseDate?: string | undefined;\n label?: string | undefined;\n songLink?: string | undefined;\n isrc?: string | undefined;\n upc?: string | undefined;\n appleMusic?: Record<string, unknown> | undefined;\n spotify?: Record<string, unknown> | undefined;\n deezer?: Record<string, unknown> | undefined;\n napster?: Record<string, unknown> | undefined;\n musicbrainz?: ReadonlyArray<Record<string, unknown>> | undefined;\n /** Forward-compat: any unknown keys from the server. */\n extras: Record<string, unknown>;\n /** Full unparsed payload for the caller's own inspection. */\n rawResponse: Record<string, unknown>;\n /** True when `audioId` is set (custom-catalog match). */\n readonly isCustomMatch: boolean;\n /** True when `audioId` is unset and `artist` or `title` are present. */\n readonly isPublicMatch: boolean;\n /** Cover-art URL for `lis.tn`-hosted song_links, else `null`. */\n readonly thumbnailUrl: string | null;\n /**\n * Direct or redirect URL for a streaming provider, with smart fallback.\n *\n * Resolution order:\n * 1. Direct URL from the metadata block (e.g. `apple_music.url`,\n * `spotify.external_urls.spotify`, `deezer.link`, `napster.href`) when\n * the user requested that provider via `return=`.\n * 2. lis.tn redirect (`{songLink}?{provider}`) when `songLink` is on lis.tn.\n * 3. `null` otherwise. YouTube has only the lis.tn-redirect path.\n */\n streamingUrl(provider: StreamingProvider): string | null;\n /** Map of every provider with a resolvable URL — direct or via lis.tn redirect. */\n streamingUrls(): Partial<Record<StreamingProvider, string>>;\n /**\n * First available 30-second preview URL across providers, in priority order:\n * `apple_music.previews[0].url` → `spotify.preview_url` → `deezer.preview`.\n *\n * **Note:** previews are governed by the respective providers' terms of use.\n * The SDK consumer is responsible for honoring those terms (caching limits,\n * attribution, redistribution constraints).\n */\n previewUrl(): string | null;\n}\n\nfunction directStreamingUrl(\n result: Pick<RecognitionResult, \"appleMusic\" | \"spotify\" | \"deezer\" | \"napster\">,\n provider: string,\n): string | null {\n if (provider === \"apple_music\" && result.appleMusic !== undefined) {\n const url = result.appleMusic[\"url\"];\n if (typeof url === \"string\" && url.length > 0) return url;\n } else if (provider === \"spotify\" && result.spotify !== undefined) {\n const ext = result.spotify[\"external_urls\"];\n if (typeof ext === \"object\" && ext !== null) {\n const sp = (ext as Record<string, unknown>)[\"spotify\"];\n if (typeof sp === \"string\" && sp.length > 0) return sp;\n }\n const uri = result.spotify[\"uri\"];\n if (typeof uri === \"string\" && uri.length > 0) return uri;\n } else if (provider === \"deezer\" && result.deezer !== undefined) {\n const link = result.deezer[\"link\"];\n if (typeof link === \"string\" && link.length > 0) return link;\n } else if (provider === \"napster\" && result.napster !== undefined) {\n const href = result.napster[\"href\"];\n if (typeof href === \"string\" && href.length > 0) return href;\n }\n return null;\n}\n\nfunction previewFrom(result: Pick<RecognitionResult, \"appleMusic\" | \"spotify\" | \"deezer\">): string | null {\n if (result.appleMusic !== undefined) {\n const previews = result.appleMusic[\"previews\"];\n if (Array.isArray(previews) && previews.length > 0) {\n const first = previews[0];\n if (typeof first === \"object\" && first !== null) {\n const url = (first as Record<string, unknown>)[\"url\"];\n if (typeof url === \"string\" && url.length > 0) return url;\n }\n }\n }\n if (result.spotify !== undefined) {\n const url = result.spotify[\"preview_url\"];\n if (typeof url === \"string\" && url.length > 0) return url;\n }\n if (result.deezer !== undefined) {\n const url = result.deezer[\"preview\"];\n if (typeof url === \"string\" && url.length > 0) return url;\n }\n return null;\n}\n\nconst RECOGNITION_KEYS = [\n \"timecode\",\n \"audio_id\",\n \"artist\",\n \"title\",\n \"album\",\n \"release_date\",\n \"label\",\n \"song_link\",\n \"isrc\",\n \"upc\",\n \"apple_music\",\n \"spotify\",\n \"deezer\",\n \"napster\",\n \"musicbrainz\",\n] as const;\n\nexport function parseRecognitionResult(raw: unknown): RecognitionResult {\n const r = asObjectLenient(raw);\n const timecode = asString(r.timecode);\n const audioId = asNumber(r.audio_id);\n const artist = asString(r.artist);\n const title = asString(r.title);\n const songLink = asString(r.song_link);\n const musicbrainz = Array.isArray(r.musicbrainz)\n ? (r.musicbrainz.filter((x): x is Record<string, unknown> => asObject(x) !== undefined) as\n | Record<string, unknown>[]\n | undefined)\n : undefined;\n\n const result: RecognitionResult = {\n timecode,\n audioId,\n artist,\n title,\n album: asString(r.album),\n releaseDate: asString(r.release_date),\n label: asString(r.label),\n songLink,\n isrc: asString(r.isrc),\n upc: asString(r.upc),\n appleMusic: asObject(r.apple_music),\n spotify: asObject(r.spotify),\n deezer: asObject(r.deezer),\n napster: asObject(r.napster),\n musicbrainz,\n extras: pickExtras(r, RECOGNITION_KEYS),\n rawResponse: r,\n get isCustomMatch() {\n return audioId !== undefined;\n },\n get isPublicMatch() {\n return audioId === undefined && (artist !== undefined || title !== undefined);\n },\n get thumbnailUrl() {\n return lisTnStreamingUrl(songLink, \"thumb\");\n },\n streamingUrl(provider: StreamingProvider): string | null {\n if (!STREAMING_PROVIDERS.includes(provider)) {\n throw new TypeError(\n `Unknown streaming provider: ${String(provider)}. ` +\n `Valid: ${STREAMING_PROVIDERS.join(\", \")}`,\n );\n }\n const direct = directStreamingUrl(result, provider);\n if (direct !== null) return direct;\n return lisTnStreamingUrl(songLink, provider);\n },\n streamingUrls(): Partial<Record<StreamingProvider, string>> {\n const out: Partial<Record<StreamingProvider, string>> = {};\n for (const p of STREAMING_PROVIDERS) {\n const url = result.streamingUrl(p);\n if (url !== null) out[p] = url;\n }\n return out;\n },\n previewUrl(): string | null {\n return previewFrom(result);\n },\n };\n return result;\n}\n\nexport interface EnterpriseMatch {\n score?: number | undefined;\n timecode?: string | undefined;\n artist?: string | undefined;\n title?: string | undefined;\n album?: string | undefined;\n releaseDate?: string | undefined;\n label?: string | undefined;\n isrc?: string | undefined;\n upc?: string | undefined;\n songLink?: string | undefined;\n startOffset?: number | undefined;\n endOffset?: number | undefined;\n /**\n * Where this song plays in your file, in seconds — the chunk's file offset\n * plus `startOffset`/`endOffset`. `undefined` when the chunk offset is\n * absent or unparseable. Computed during {@link parseEnterpriseChunkResult}.\n */\n startSeconds?: number | undefined;\n endSeconds?: number | undefined;\n extras: Record<string, unknown>;\n rawResponse: Record<string, unknown>;\n readonly thumbnailUrl: string | null;\n /** lis.tn redirect URL for the given streaming provider, or null if `songLink` is non-lis.tn. */\n streamingUrl(provider: StreamingProvider): string | null;\n /** All providers with a resolvable lis.tn redirect URL (or empty when `songLink` is off lis.tn). */\n streamingUrls(): Partial<Record<StreamingProvider, string>>;\n}\n\nconst ENTERPRISE_MATCH_KEYS = [\n \"score\",\n \"timecode\",\n \"artist\",\n \"title\",\n \"album\",\n \"release_date\",\n \"label\",\n \"isrc\",\n \"upc\",\n \"song_link\",\n \"start_offset\",\n \"end_offset\",\n \"start_seconds\",\n \"end_seconds\",\n] as const;\n\nexport function parseEnterpriseMatch(raw: unknown): EnterpriseMatch {\n const r = asObjectLenient(raw);\n const score = asNumber(r.score);\n const timecode = asString(r.timecode);\n const songLink = asString(r.song_link);\n return {\n score,\n timecode,\n artist: asString(r.artist),\n title: asString(r.title),\n album: asString(r.album),\n releaseDate: asString(r.release_date),\n label: asString(r.label),\n isrc: asString(r.isrc),\n upc: asString(r.upc),\n songLink,\n startOffset: asNumber(r.start_offset),\n endOffset: asNumber(r.end_offset),\n startSeconds: undefined,\n endSeconds: undefined,\n extras: pickExtras(r, ENTERPRISE_MATCH_KEYS),\n rawResponse: r,\n streamingUrl(provider: StreamingProvider): string | null {\n if (!STREAMING_PROVIDERS.includes(provider)) {\n throw new TypeError(`Unknown streaming provider: ${String(provider)}`);\n }\n return lisTnStreamingUrl(songLink, provider);\n },\n streamingUrls(): Partial<Record<StreamingProvider, string>> {\n const out: Partial<Record<StreamingProvider, string>> = {};\n for (const p of STREAMING_PROVIDERS) {\n const url = lisTnStreamingUrl(songLink, p);\n if (url !== null) out[p] = url;\n }\n return out;\n },\n get thumbnailUrl() {\n if (songLink === undefined || songLink === \"\") return null;\n try {\n const parsed = new URL(songLink);\n if (parsed.hostname !== \"lis.tn\") return null;\n const sep = parsed.search ? \"&\" : \"?\";\n return `${songLink}${sep}thumb`;\n } catch {\n return null;\n }\n },\n };\n}\n\nexport interface EnterpriseChunkResult {\n songs: EnterpriseMatch[];\n offset?: string | undefined;\n extras: Record<string, unknown>;\n rawResponse: Record<string, unknown>;\n}\n\nconst ENTERPRISE_CHUNK_KEYS = [\"songs\", \"offset\"] as const;\n\nexport function parseEnterpriseChunkResult(raw: unknown): EnterpriseChunkResult {\n const r = asObjectLenient(raw);\n const songsRaw = Array.isArray(r.songs)\n ? r.songs.filter((s) => asObject(s) !== undefined) // skip wrong-typed entries\n : [];\n const offset = asString(r.offset);\n // The chunk offset is the fragment's position in the user's file. Anchor each\n // song's millisecond offsets to it, in seconds. Skip when unparseable.\n const base = parseOffsetToSeconds(r.offset);\n const songs = songsRaw.map((s) => {\n const match = parseEnterpriseMatch(s);\n if (base !== undefined) {\n match.startSeconds = base + (match.startOffset ?? 0) / 1000;\n match.endSeconds = base + (match.endOffset ?? 0) / 1000;\n }\n return match;\n });\n return {\n songs,\n offset,\n extras: pickExtras(r, ENTERPRISE_CHUNK_KEYS),\n rawResponse: r,\n };\n}\n\nexport interface Stream {\n radioId?: number | undefined;\n url?: string | undefined;\n streamRunning?: boolean | undefined;\n longpollCategory?: string | undefined;\n extras: Record<string, unknown>;\n rawResponse: Record<string, unknown>;\n}\n\nconst STREAM_KEYS = [\"radio_id\", \"url\", \"stream_running\", \"longpoll_category\"] as const;\n\nexport function parseStream(raw: unknown): Stream {\n const r = asObjectLenient(raw);\n const radioId = asNumber(r.radio_id);\n const url = asString(r.url);\n const streamRunning = asBoolean(r.stream_running);\n return {\n radioId,\n url,\n streamRunning,\n longpollCategory: asString(r.longpoll_category),\n extras: pickExtras(r, STREAM_KEYS),\n rawResponse: r,\n };\n}\n\n/**\n * One candidate song in a recognition match.\n *\n * Almost every match has exactly one Song; the rare extra candidates that\n * appear under {@link StreamCallbackMatch.alternatives} may have a *different*\n * artist or title from the top song — they're variant catalog releases of the\n * same recording (e.g. a \"feat.\" credit vs. the bare-artist re-release, or\n * regional edits with different titles), not lower-confidence guesses at the\n * same track.\n */\nexport interface StreamCallbackSong {\n artist?: string | undefined;\n title?: string | undefined;\n score?: number | undefined;\n album?: string | undefined;\n releaseDate?: string | undefined;\n label?: string | undefined;\n songLink?: string | undefined;\n isrc?: string | undefined;\n upc?: string | undefined;\n appleMusic?: Record<string, unknown> | undefined;\n spotify?: Record<string, unknown> | undefined;\n deezer?: Record<string, unknown> | undefined;\n napster?: Record<string, unknown> | undefined;\n musicbrainz?: ReadonlyArray<Record<string, unknown>> | undefined;\n extras: Record<string, unknown>;\n}\n\nconst STREAM_CALLBACK_SONG_KEYS = [\n \"artist\",\n \"title\",\n \"score\",\n \"album\",\n \"release_date\",\n \"label\",\n \"song_link\",\n \"isrc\",\n \"upc\",\n \"apple_music\",\n \"spotify\",\n \"deezer\",\n \"napster\",\n \"musicbrainz\",\n] as const;\n\nfunction parseStreamCallbackSong(raw: unknown): StreamCallbackSong {\n const r = asObjectLenient(raw);\n const artist = asString(r.artist);\n const title = asString(r.title);\n const score = asNumber(r.score);\n const musicbrainz = Array.isArray(r.musicbrainz)\n ? (r.musicbrainz.filter((x): x is Record<string, unknown> => asObject(x) !== undefined) as\n | Record<string, unknown>[]\n | undefined)\n : undefined;\n return {\n artist,\n title,\n score,\n album: asString(r.album),\n releaseDate: asString(r.release_date),\n label: asString(r.label),\n songLink: asString(r.song_link),\n isrc: asString(r.isrc),\n upc: asString(r.upc),\n appleMusic: asObject(r.apple_music),\n spotify: asObject(r.spotify),\n deezer: asObject(r.deezer),\n napster: asObject(r.napster),\n musicbrainz,\n extras: pickExtras(r, STREAM_CALLBACK_SONG_KEYS),\n };\n}\n\n/**\n * One recognition event from a stream callback or longpoll.\n *\n * The top match lives in {@link song}; rare extra candidates live in\n * {@link alternatives}. Alternatives entries may have a different artist or\n * title from the top song — they're variant catalog releases of the same\n * recording, not lower-confidence guesses at the same track.\n */\nexport interface StreamCallbackMatch {\n radioId?: number | undefined;\n timestamp?: string | undefined;\n playLength?: number | undefined;\n /** Top match, when present. */\n song?: StreamCallbackSong | undefined;\n /** Variant catalog releases (may have different artist/title); empty array when only one match. */\n alternatives: StreamCallbackSong[];\n extras: Record<string, unknown>;\n rawResponse: Record<string, unknown>;\n}\n\nconst STREAM_CALLBACK_MATCH_KEYS = [\n \"radio_id\",\n \"timestamp\",\n \"play_length\",\n \"results\",\n] as const;\n\nexport function parseStreamCallbackMatch(raw: unknown): StreamCallbackMatch {\n const r = asObjectLenient(raw);\n const radioId = asNumber(r.radio_id);\n const resultsRaw = Array.isArray(r.results) ? r.results : [];\n const songs = resultsRaw\n .filter((x): x is Record<string, unknown> => asObject(x) !== undefined)\n .map(parseStreamCallbackSong);\n const [first, ...rest] = songs;\n return {\n radioId,\n timestamp: asString(r.timestamp),\n playLength: asNumber(r.play_length),\n song: first,\n alternatives: rest,\n extras: pickExtras(r, STREAM_CALLBACK_MATCH_KEYS),\n rawResponse: r,\n };\n}\n\nexport interface StreamCallbackNotification {\n radioId?: number | undefined;\n streamRunning?: boolean | undefined;\n notificationCode?: number | undefined;\n notificationMessage?: string | undefined;\n /** Outer `time` field on the callback envelope (epoch seconds). */\n time?: number | undefined;\n extras: Record<string, unknown>;\n rawResponse: Record<string, unknown>;\n}\n\nconst STREAM_CALLBACK_NOTIFICATION_KEYS = [\n \"radio_id\",\n \"stream_running\",\n \"notification_code\",\n \"notification_message\",\n] as const;\n\nexport function parseStreamCallbackNotification(raw: unknown): StreamCallbackNotification {\n const r = asObjectLenient(raw);\n const radioId = asNumber(r.radio_id);\n const code = asNumber(r.notification_code);\n const message = asString(r.notification_message);\n return {\n radioId,\n streamRunning: asBoolean(r.stream_running),\n notificationCode: code,\n notificationMessage: message,\n extras: pickExtras(r, STREAM_CALLBACK_NOTIFICATION_KEYS),\n rawResponse: r,\n };\n}\n\nexport interface LyricsResult {\n artist?: string | undefined;\n title?: string | undefined;\n lyrics?: string | undefined;\n songId?: number | undefined;\n fullTitle?: string | undefined;\n artistId?: number | undefined;\n songLink?: string | undefined;\n media?: string | undefined;\n extras: Record<string, unknown>;\n rawResponse: Record<string, unknown>;\n}\n\nconst LYRICS_KEYS = [\n \"artist\",\n \"title\",\n \"lyrics\",\n \"song_id\",\n \"full_title\",\n \"artist_id\",\n \"song_link\",\n \"media\",\n] as const;\n\nexport function parseLyricsResult(raw: unknown): LyricsResult {\n const r = asObjectLenient(raw);\n const artist = asString(r.artist);\n const title = asString(r.title);\n return {\n artist,\n title,\n lyrics: asString(r.lyrics),\n songId: asNumber(r.song_id),\n fullTitle: asString(r.full_title),\n artistId: asNumber(r.artist_id),\n songLink: asString(r.song_link),\n media: asString(r.media),\n extras: pickExtras(r, LYRICS_KEYS),\n rawResponse: r,\n };\n}\n","/**\n * Shared longpoll loop + handle, used by both the authenticated `Streams`\n * namespace (token in URL via `HttpClient.get`) and the tokenless\n * `LongpollConsumer` exported from `audd/longpoll`.\n *\n * Browser-safe: no node:crypto, no node:fs, no node:http imports. The whole\n * file relies on `fetch`-style I/O the caller injects.\n */\n\nimport {\n AudDConnectionError,\n AudDSerializationError,\n AudDServerError,\n} from \"./errors.js\";\nimport type { HttpResponse } from \"./http.js\";\nimport type {\n StreamCallbackMatch,\n StreamCallbackNotification,\n} from \"./models.js\";\nimport {\n asObject,\n parseStreamCallbackMatch,\n parseStreamCallbackNotification,\n} from \"./models.js\";\n\nconst HTTP_CLIENT_ERROR_FLOOR = 400;\n\n/**\n * Active long-poll subscription. Two typed `AsyncIterable`s carry the\n * happy-path output and a third carries any terminal error.\n *\n * Consume `matches` and `notifications` independently or in parallel:\n *\n * ```ts\n * const poll = await audd.streams.longpoll(category);\n * for await (const m of poll.matches) {\n * console.log(m.song?.artist ?? \"?\", m.song?.title ?? \"?\");\n * }\n * ```\n *\n * Concurrent consumption:\n *\n * ```ts\n * await Promise.all([\n * (async () => { for await (const m of poll.matches) {} })(),\n * (async () => { for await (const n of poll.notifications) {} })(),\n * (async () => { for await (const e of poll.errors) console.error(e); })(),\n * ]);\n * ```\n *\n * `close()` (or the `await using` resource-management form) tears down the\n * background loop and closes all three iterables.\n */\nexport interface LongpollPoll {\n /** Recognition events. */\n readonly matches: AsyncIterable<StreamCallbackMatch>;\n /** Stream-lifecycle events (\"stream stopped\", \"can't connect\", ...). */\n readonly notifications: AsyncIterable<StreamCallbackNotification>;\n /**\n * Terminal errors. The poll keeps polling on transient HTTP/JSON failures\n * (subject to the retry policy); errors that surface here are the ones the\n * loop gives up on. Each fired error is followed by closure of all three\n * iterables.\n */\n readonly errors: AsyncIterable<Error>;\n /** Stop the background loop. Idempotent. */\n close(): void;\n /** `await using` support — calls `close()`. */\n [Symbol.asyncDispose](): Promise<void>;\n}\n\n/**\n * Caller-supplied bits the loop needs:\n * - `fetchOnce` — performs a single longpoll HTTP GET, with caller-managed\n * auth (token in URL, Bearer header, etc.) and retry policy.\n * - `getInitialSince` — initial `since_time` to send (often `undefined`).\n * - `timeout` — server-side longpoll timeout in seconds.\n * - `category` — the longpoll category derived from `(token, radio_id)`.\n * - `signal` — aborts the in-flight HTTP when the poll is closed.\n */\nexport interface LongpollLoopConfig {\n category: string;\n timeout: number;\n sinceTime: number | undefined;\n fetchOnce: (\n params: Record<string, string | undefined>,\n signal: AbortSignal,\n ) => Promise<HttpResponse>;\n}\n\n/**\n * Internal: a single-consumer-friendly bounded queue used to demultiplex\n * the loop output onto separate AsyncIterables.\n */\nclass AsyncQueue<T> {\n private readonly items: T[] = [];\n private readonly waiters: ((v: IteratorResult<T>) => void)[] = [];\n private done = false;\n\n push(value: T): void {\n if (this.done) return;\n const w = this.waiters.shift();\n if (w !== undefined) {\n w({ value, done: false });\n return;\n }\n this.items.push(value);\n }\n\n finish(): void {\n if (this.done) return;\n this.done = true;\n while (this.waiters.length > 0) {\n const w = this.waiters.shift();\n if (w !== undefined) w({ value: undefined as never, done: true });\n }\n }\n\n next(): Promise<IteratorResult<T>> {\n const item = this.items.shift();\n if (item !== undefined) return Promise.resolve({ value: item, done: false });\n if (this.done) return Promise.resolve({ value: undefined as never, done: true });\n return new Promise((resolve) => {\n this.waiters.push(resolve);\n });\n }\n\n toIterable(): AsyncIterable<T> {\n const next = (): Promise<IteratorResult<T>> => this.next();\n return {\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return { next };\n },\n };\n }\n}\n\n/**\n * Decode one longpoll HTTP response into a Match | Notification | keep-alive\n * (keep-alive = `{timeout, timestamp}` or any non-event body — caller keeps\n * polling). Throws on HTTP non-2xx and non-JSON bodies; wrong-typed event\n * payloads degrade to keep-alives so one odd response never kills the loop.\n */\nfunction decodeOne(\n resp: HttpResponse,\n): { kind: \"match\"; match: StreamCallbackMatch } | { kind: \"notification\"; notification: StreamCallbackNotification } | { kind: \"keepalive\" } {\n if (resp.httpStatus >= HTTP_CLIENT_ERROR_FLOOR) {\n throw new AudDServerError({\n errorCode: 0,\n message: `Longpoll endpoint returned HTTP ${String(resp.httpStatus)}`,\n httpStatus: resp.httpStatus,\n requestId: resp.requestId,\n rawResponse: resp.jsonBody ?? resp.rawText,\n });\n }\n const body = resp.jsonBody;\n if (typeof body !== \"object\" || body === null || Array.isArray(body)) {\n throw new AudDSerializationError(\n \"Longpoll response was not a JSON object\",\n resp.rawText,\n );\n }\n const dict = body as Record<string, unknown>;\n if (asObject(dict.notification) !== undefined) {\n const notification = parseStreamCallbackNotification(dict.notification);\n const t = dict[\"time\"];\n if (typeof t === \"number\") notification.time = t;\n return { kind: \"notification\", notification };\n }\n if (asObject(dict.result) !== undefined) {\n return { kind: \"match\", match: parseStreamCallbackMatch(dict.result) };\n }\n // Either a `{timeout, timestamp}` keep-alive or an empty/unknown body —\n // skip silently and keep polling.\n return { kind: \"keepalive\" };\n}\n\n/**\n * Start the longpoll loop on a background async task. Returns a\n * {@link LongpollPoll} handle whose iterables are filled as events arrive.\n * The caller's `fetchOnce` is responsible for retries and auth.\n */\nexport function startLongpoll(cfg: LongpollLoopConfig): LongpollPoll {\n const matches = new AsyncQueue<StreamCallbackMatch>();\n const notifications = new AsyncQueue<StreamCallbackNotification>();\n const errors = new AsyncQueue<Error>();\n\n const controller = new AbortController();\n let closed = false;\n\n const finishAll = (): void => {\n matches.finish();\n notifications.finish();\n errors.finish();\n };\n\n const close = (): void => {\n if (closed) return;\n closed = true;\n controller.abort();\n finishAll();\n };\n\n const run = async (): Promise<void> => {\n let curSince = cfg.sinceTime;\n while (!closed) {\n const params: Record<string, string | undefined> = {\n category: cfg.category,\n timeout: String(cfg.timeout),\n };\n if (curSince !== undefined) params[\"since_time\"] = String(curSince);\n\n let resp: HttpResponse;\n try {\n resp = await cfg.fetchOnce(params, controller.signal);\n } catch (err) {\n if (closed) return;\n if (err instanceof TypeError) {\n errors.push(new AudDConnectionError(`Network error: ${err.message}`, err));\n } else if (\n err !== null &&\n typeof err === \"object\" &&\n (err as { name?: string }).name === \"AbortError\"\n ) {\n // Closed-from-outside path — drop quietly.\n if (closed) return;\n errors.push(new AudDConnectionError(\"Request was aborted (timeout)\", err));\n } else if (err instanceof Error) {\n errors.push(err);\n } else {\n errors.push(new Error(String(err)));\n }\n finishAll();\n return;\n }\n\n try {\n const decoded = decodeOne(resp);\n if (decoded.kind === \"match\") matches.push(decoded.match);\n else if (decoded.kind === \"notification\") notifications.push(decoded.notification);\n // else keep-alive: skip.\n } catch (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)));\n finishAll();\n return;\n }\n\n // Bump since_time off the response body's timestamp, as the audd-go\n // reference does — keeps reconnects from re-replaying old events.\n const dict = resp.jsonBody;\n if (typeof dict === \"object\" && dict !== null && !Array.isArray(dict)) {\n const ts = (dict as Record<string, unknown>)[\"timestamp\"];\n if (typeof ts === \"number\") curSince = ts;\n }\n }\n };\n\n // Fire-and-forget. Failures land on the `errors` iterable.\n void run();\n\n return {\n matches: matches.toIterable(),\n notifications: notifications.toIterable(),\n errors: errors.toIterable(),\n close,\n async [Symbol.asyncDispose](): Promise<void> {\n close();\n await Promise.resolve();\n },\n };\n}\n","import { AudDConnectionError, isUserAbortError } from \"./errors.js\";\nimport type { HttpResponse } from \"./http.js\";\n\n/**\n * Cost-aware retry classes.\n *\n * - READ — idempotent reads (`streams.list`, `streams.getCallbackUrl`):\n * retry on 408/429/5xx + any connection error.\n * - RECOGNITION — `recognize`, `recognizeEnterprise`, `advanced.findLyrics`:\n * retry on pre-upload connection failures + 5xx.\n * DO NOT retry on read-timeout-after-upload (cost protection).\n * - MUTATING — `streams.add`, `streams.delete`, etc.:\n * retry only on pre-upload connection failures. DO NOT retry\n * 5xx (the side effect may have happened).\n * - NONE — `customCatalog.add`: never retry. Custom-catalog upload is\n * metered; auto-retry on transport failure could double-charge\n * for the same audio fingerprinting. Surface a clean error and\n * let the caller decide.\n */\nexport type RetryClass = \"read\" | \"recognition\" | \"mutating\" | \"none\";\n\nexport interface RetryPolicy {\n retryClass: RetryClass;\n maxAttempts: number;\n backoffFactorMs: number;\n backoffMaxMs: number;\n}\n\nexport const DEFAULT_BACKOFF_FACTOR_MS = 500;\nexport const DEFAULT_BACKOFF_MAX_MS = 30_000;\nexport const DEFAULT_MAX_ATTEMPTS = 3;\n\nconst HTTP_REQUEST_TIMEOUT = 408;\nconst HTTP_TOO_MANY_REQUESTS = 429;\nconst HTTP_SERVER_ERROR_FLOOR = 500;\n\nexport function defaultPolicy(retryClass: RetryClass): RetryPolicy {\n return {\n retryClass,\n maxAttempts: DEFAULT_MAX_ATTEMPTS,\n backoffFactorMs: DEFAULT_BACKOFF_FACTOR_MS,\n backoffMaxMs: DEFAULT_BACKOFF_MAX_MS,\n };\n}\n\nfunction backoffDelayMs(attempt: number, policy: RetryPolicy): number {\n const base = Math.min(policy.backoffFactorMs * 2 ** attempt, policy.backoffMaxMs);\n return base * (0.5 + Math.random());\n}\n\n/**\n * `fetch()` raises `TypeError(\"fetch failed\")` when DNS/TCP/TLS failed before\n * the request body finished. AbortError (or DOMException/AbortError) means the\n * caller's AbortSignal fired — could be pre- or post-upload, treated as\n * post-upload conservatively for cost-protection.\n */\nfunction isAbortError(err: unknown): boolean {\n if (err === null || typeof err !== \"object\") return false;\n const e = err as { name?: unknown };\n return e.name === \"AbortError\";\n}\n\nfunction isPreUploadConnectionError(err: unknown): boolean {\n if (isAbortError(err)) return false;\n // Node fetch wraps DNS/TCP/TLS errors as TypeError. Browsers do too.\n return err instanceof TypeError;\n}\n\nfunction shouldRetryResponse(resp: HttpResponse, retryClass: RetryClass): boolean {\n const s = resp.httpStatus;\n switch (retryClass) {\n case \"read\":\n return (\n s === HTTP_REQUEST_TIMEOUT ||\n s === HTTP_TOO_MANY_REQUESTS ||\n s >= HTTP_SERVER_ERROR_FLOOR\n );\n case \"recognition\":\n return s >= HTTP_SERVER_ERROR_FLOOR;\n case \"mutating\":\n return false;\n case \"none\":\n return false;\n }\n}\n\nfunction shouldRetryError(err: unknown, retryClass: RetryClass): boolean {\n switch (retryClass) {\n case \"read\":\n // Pre-upload connection errors and post-upload read timeouts are both\n // safe-ish: idempotent reads can be retried freely.\n return isPreUploadConnectionError(err) || isAbortError(err);\n case \"recognition\":\n // Pre-upload only — explicitly NOT post-upload AbortError (cost protection).\n return isPreUploadConnectionError(err);\n case \"mutating\":\n // Pre-upload only — server-side side-effect may have happened.\n return isPreUploadConnectionError(err);\n case \"none\":\n // Metered upload — never retry on transport failure (could double-charge).\n return false;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function retry<T extends HttpResponse>(\n fn: () => Promise<T>,\n policy: RetryPolicy,\n): Promise<T> {\n let lastError: unknown;\n let lastResp: T | undefined;\n let haveResp = false;\n for (let attempt = 0; attempt < policy.maxAttempts; attempt++) {\n try {\n const resp = await fn();\n if (!shouldRetryResponse(resp, policy.retryClass)) return resp;\n lastResp = resp;\n haveResp = true;\n lastError = undefined;\n } catch (err) {\n if (!shouldRetryError(err, policy.retryClass)) throw err;\n lastError = err;\n haveResp = false;\n if (attempt + 1 >= policy.maxAttempts) throw err;\n }\n if (attempt + 1 >= policy.maxAttempts) break;\n await sleep(backoffDelayMs(attempt, policy));\n }\n if (haveResp && lastResp !== undefined) return lastResp;\n throw lastError;\n}\n\n/**\n * Run `fn` under the retry policy and map transport-level failures onto\n * {@link AudDConnectionError}:\n *\n * - caller-cancelled requests (their AbortSignal fired) → cancellation\n * message, never retried;\n * - `TypeError` from fetch (DNS/TCP/TLS) → network error;\n * - `AbortError` (the SDK's own timeout) → timeout message.\n */\nexport async function runRetried<T extends HttpResponse>(\n fn: () => Promise<T>,\n policy: RetryPolicy,\n): Promise<T> {\n try {\n return await retry(fn, policy);\n } catch (err) {\n if (isUserAbortError(err)) {\n throw new AudDConnectionError(\"Request was cancelled by the caller's AbortSignal\", err);\n }\n if (err instanceof TypeError) {\n throw new AudDConnectionError(`Network error: ${err.message}`, err);\n }\n if (err !== null && typeof err === \"object\" && (err as { name?: string }).name === \"AbortError\") {\n throw new AudDConnectionError(\"Request was aborted (timeout)\", err);\n }\n throw err;\n }\n}\n","export const VERSION = \"1.5.12\";\n","import { VERSION } from \"./version.js\";\n\nexport function userAgent(): string {\n const node =\n typeof process !== \"undefined\" && process.version\n ? process.version.replace(/^v/, \"\")\n : \"unknown\";\n const platform =\n typeof process !== \"undefined\" && process.platform ? process.platform : \"browser\";\n return `audd-node/${VERSION} node/${node} (${platform})`;\n}\n","/**\n * Tokenless longpoll consumer for browser/widget/extension use cases.\n *\n * Carries no api_token. The category alone authorizes the subscription. The\n * user/server who derived the category is responsible for ensuring a\n * callback URL is set on their account (we can't preflight that without a\n * token).\n *\n * Behavior:\n * - HTTP non-2xx → terminal {@link AudDServerError} on the `errors` iterable\n * (no silent infinite loop).\n * - JSON decode failure on 2xx → terminal {@link AudDSerializationError}.\n * - READ-class retries on 5xx + connection failures, configurable via\n * `maxRetries` / `backoffFactorMs`.\n * - `[Symbol.asyncDispose]()` + manual `close()` for resource cleanup.\n *\n * Imported from `audd/longpoll` (browser-safe sub-entry, NOT the main\n * `audd` module — bundlers tree-shake the auth client out).\n */\n\nimport { userAbortError } from \"./errors.js\";\nimport type { FetchLike, HttpResponse } from \"./http.js\";\nimport {\n startLongpoll,\n type LongpollPoll,\n} from \"./longpollCore.js\";\nimport { defaultPolicy, runRetried, type RetryPolicy } from \"./retry.js\";\nimport { userAgent } from \"./userAgent.js\";\n\nexport type { LongpollPoll } from \"./longpollCore.js\";\n\nconst LONGPOLL_URL = \"https://api.audd.io/longpoll/\";\n\nexport interface LongpollConsumerOptions {\n /** Maximum retry attempts (default 3). */\n maxRetries?: number;\n /** Initial backoff in ms (default 500), jittered, exponential. */\n backoffFactorMs?: number;\n /** Custom fetch (e.g., for proxy/mTLS). */\n fetch?: FetchLike;\n}\n\nexport interface IterateOptions {\n sinceTime?: number;\n /** Server-side timeout in seconds (default 50). */\n timeout?: number;\n}\n\n/**\n * Tokenless longpoll consumer.\n *\n * ```ts\n * import { LongpollConsumer } from 'audd/longpoll';\n *\n * const consumer = new LongpollConsumer(\"abc123def\");\n * const poll = consumer.iterate({ timeout: 30 });\n * for await (const m of poll.matches) {\n * console.log(m.song?.artist ?? \"?\", m.song?.title ?? \"?\");\n * }\n * ```\n *\n * Or iterate matches and notifications concurrently:\n *\n * ```ts\n * await Promise.all([\n * (async () => { for await (const m of poll.matches) { ... } })(),\n * (async () => { for await (const n of poll.notifications) { ... } })(),\n * ]);\n * ```\n */\nexport class LongpollConsumer {\n private readonly fetchImpl: FetchLike;\n private readonly policy: RetryPolicy;\n\n constructor(public readonly category: string, opts: LongpollConsumerOptions = {}) {\n this.fetchImpl = opts.fetch ?? ((globalThis.fetch as FetchLike).bind(globalThis));\n const base = defaultPolicy(\"read\");\n this.policy = {\n ...base,\n maxAttempts: opts.maxRetries ?? base.maxAttempts,\n backoffFactorMs: opts.backoffFactorMs ?? base.backoffFactorMs,\n };\n }\n\n iterate(opts: IterateOptions = {}): LongpollPoll {\n const fetchImpl = this.fetchImpl;\n const policy = this.policy;\n const timeoutSec = opts.timeout ?? 50;\n\n const fetchOnceRaw = async (\n params: Record<string, string | undefined>,\n signal: AbortSignal,\n ): Promise<HttpResponse> => {\n const u = new URL(LONGPOLL_URL);\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined) u.searchParams.set(k, v);\n }\n const response = await fetchImpl(u.toString(), {\n method: \"GET\",\n headers: { \"User-Agent\": userAgent() },\n signal,\n });\n const text = await response.text();\n let json: unknown = null;\n if (text !== \"\") {\n try {\n json = JSON.parse(text);\n } catch {\n json = null;\n }\n }\n return {\n jsonBody: json,\n httpStatus: response.status,\n requestId: response.headers.get(\"x-request-id\"),\n rawText: text,\n };\n };\n\n const fetchOnce = (\n params: Record<string, string | undefined>,\n signal: AbortSignal,\n ): Promise<HttpResponse> =>\n runRetried(async () => {\n try {\n return await fetchOnceRaw(params, signal);\n } catch (err) {\n // The only abort source here is the poll handle's own close() —\n // a caller cancellation. Mark it so it is never retried.\n if (\n signal.aborted &&\n err !== null &&\n typeof err === \"object\" &&\n (err as { name?: unknown }).name === \"AbortError\"\n ) {\n throw userAbortError(err);\n }\n throw err;\n }\n }, policy);\n\n return startLongpoll({\n category: this.category,\n timeout: timeoutSec,\n sinceTime: opts.sinceTime,\n fetchOnce,\n });\n }\n\n /** Release any underlying resources. */\n close(): void {\n /* no persistent connections in fetch */\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n this.close();\n await Promise.resolve();\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/models.ts","../src/longpollCore.ts","../src/retry.ts","../src/version.ts","../src/userAgent.ts","../src/longpoll.ts"],"names":[],"mappings":"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","file":"longpoll.cjs","sourcesContent":["/** Exception hierarchy for AudD API errors. */\n\nexport interface AudDApiErrorFields {\n errorCode: number;\n message: string;\n httpStatus: number;\n requestId: string | null;\n requestedParams: Record<string, unknown>;\n requestMethod: string | null;\n brandedMessage: string | null;\n rawResponse: unknown;\n}\n\nexport type AudDApiErrorInit = Partial<AudDApiErrorFields> &\n Pick<AudDApiErrorFields, \"errorCode\" | \"message\" | \"httpStatus\">;\n\n/** Base for everything thrown by this SDK. */\nexport class AudDError extends Error {\n override name = \"AudDError\";\n}\n\n/** Server returned `status: error`. Carries the AudD error code + the full echo. */\nexport class AudDAPIError extends AudDError {\n override name = \"AudDAPIError\";\n errorCode: number;\n httpStatus: number;\n requestId: string | null;\n requestedParams: Record<string, unknown>;\n requestMethod: string | null;\n brandedMessage: string | null;\n rawResponse: unknown;\n /** Original `error_message` from the server. (`Error.message` may be overridden by subclasses.) */\n serverMessage: string;\n\n constructor(init: AudDApiErrorInit) {\n super(`[#${init.errorCode}] ${init.message}`);\n this.errorCode = init.errorCode;\n this.serverMessage = init.message;\n this.httpStatus = init.httpStatus;\n this.requestId = init.requestId ?? null;\n this.requestedParams = init.requestedParams ?? {};\n this.requestMethod = init.requestMethod ?? null;\n this.brandedMessage = init.brandedMessage ?? null;\n this.rawResponse = init.rawResponse ?? null;\n }\n}\n\nexport class AudDAuthenticationError extends AudDAPIError {\n override name = \"AudDAuthenticationError\";\n}\nexport class AudDQuotaError extends AudDAPIError {\n override name = \"AudDQuotaError\";\n}\nexport class AudDSubscriptionError extends AudDAPIError {\n override name = \"AudDSubscriptionError\";\n}\nexport class AudDCustomCatalogAccessError extends AudDSubscriptionError {\n override name = \"AudDCustomCatalogAccessError\";\n}\nexport class AudDInvalidRequestError extends AudDAPIError {\n override name = \"AudDInvalidRequestError\";\n}\nexport class AudDInvalidAudioError extends AudDAPIError {\n override name = \"AudDInvalidAudioError\";\n}\nexport class AudDRateLimitError extends AudDAPIError {\n override name = \"AudDRateLimitError\";\n}\nexport class AudDStreamLimitError extends AudDAPIError {\n override name = \"AudDStreamLimitError\";\n}\nexport class AudDNotReleasedError extends AudDAPIError {\n override name = \"AudDNotReleasedError\";\n}\nexport class AudDBlockedError extends AudDAPIError {\n override name = \"AudDBlockedError\";\n}\nexport class AudDNeedsUpdateError extends AudDAPIError {\n override name = \"AudDNeedsUpdateError\";\n}\nexport class AudDServerError extends AudDAPIError {\n override name = \"AudDServerError\";\n}\n\nexport class AudDConnectionError extends AudDError {\n override name = \"AudDConnectionError\";\n override cause?: unknown;\n constructor(message: string, cause?: unknown) {\n super(message);\n this.cause = cause;\n }\n}\n\n/**\n * Internal marker for aborts triggered by the caller's own AbortSignal —\n * distinguishes user cancellation from the SDK's own timeout abort. Never\n * retried; surfaced as an {@link AudDConnectionError} with a cancellation\n * message.\n */\nconst USER_ABORT_ERROR_NAME = \"UserAbortError\";\n\nexport function userAbortError(cause: unknown): Error {\n const err = new Error(\"Request was cancelled by the caller's AbortSignal\");\n err.name = USER_ABORT_ERROR_NAME;\n (err as { cause?: unknown }).cause = cause;\n return err;\n}\n\nexport function isUserAbortError(err: unknown): boolean {\n return (\n err !== null &&\n typeof err === \"object\" &&\n (err as { name?: unknown }).name === USER_ABORT_ERROR_NAME\n );\n}\n\nexport class AudDSerializationError extends AudDError {\n override name = \"AudDSerializationError\";\n rawText: string;\n constructor(message: string, rawText = \"\") {\n super(message);\n this.rawText = rawText;\n }\n}\n\nexport type AudDAPIErrorCtor = new (init: AudDApiErrorInit) => AudDAPIError;\n\nconst CODE_MAP: Record<number, AudDAPIErrorCtor> = {\n 900: AudDAuthenticationError,\n 901: AudDAuthenticationError,\n 903: AudDAuthenticationError,\n 902: AudDQuotaError,\n 904: AudDSubscriptionError,\n 905: AudDSubscriptionError,\n 50: AudDInvalidRequestError,\n 51: AudDInvalidRequestError,\n 600: AudDInvalidRequestError,\n 601: AudDInvalidRequestError,\n 602: AudDInvalidRequestError,\n 700: AudDInvalidRequestError,\n 701: AudDInvalidRequestError,\n 702: AudDInvalidRequestError,\n 906: AudDInvalidRequestError,\n 300: AudDInvalidAudioError,\n 400: AudDInvalidAudioError,\n 500: AudDInvalidAudioError,\n 610: AudDStreamLimitError,\n 611: AudDRateLimitError,\n 907: AudDNotReleasedError,\n 19: AudDBlockedError,\n 31337: AudDBlockedError,\n 20: AudDNeedsUpdateError,\n 100: AudDServerError,\n 1000: AudDServerError,\n};\n\nexport function errorClassForCode(code: number): AudDAPIErrorCtor {\n return CODE_MAP[code] ?? AudDServerError;\n}\n\nfunction brandedMessageOf(result: unknown): string | null {\n if (typeof result !== \"object\" || result === null) return null;\n const r = result as { artist?: unknown; title?: unknown };\n const parts: string[] = [];\n if (typeof r.artist === \"string\" && r.artist.length > 0) parts.push(r.artist);\n if (typeof r.title === \"string\" && r.title.length > 0) parts.push(r.title);\n return parts.length === 0 ? null : parts.join(\" — \");\n}\n\nexport interface ServerErrorBody {\n error?: { error_code?: number | undefined; error_message?: string | undefined } | undefined;\n result?: unknown;\n request_params?: Record<string, unknown> | undefined;\n requested_params?: Record<string, unknown> | undefined;\n request_api_method?: string | undefined;\n}\n\nexport interface RaiseFromErrorOpts {\n httpStatus: number;\n requestId: string | null;\n customCatalogContext?: boolean;\n}\n\nconst CUSTOM_CATALOG_PREFIX =\n \"Adding songs to your custom catalog requires enterprise access that isn't enabled on your account.\\n\\n\" +\n \"Note: the custom-catalog endpoint is for adding songs to your private fingerprint database, not for music recognition. \" +\n \"If you intended to identify music, use recognize(...) (or recognizeEnterprise(...) for files longer than 25 seconds) instead.\\n\\n\" +\n \"To request custom-catalog access, contact api@audd.io.\\n\\n\";\n\nexport function raiseFromErrorResponse(body: ServerErrorBody, opts: RaiseFromErrorOpts): never {\n const code = body.error?.error_code ?? 0;\n const message = body.error?.error_message ?? \"\";\n const requestedParams = body.request_params ?? body.requested_params ?? {};\n const requestMethod = body.request_api_method ?? null;\n const branded = brandedMessageOf(body.result);\n\n const fields: AudDApiErrorFields = {\n errorCode: code,\n message,\n httpStatus: opts.httpStatus,\n requestId: opts.requestId,\n requestedParams,\n requestMethod,\n brandedMessage: branded,\n rawResponse: body,\n };\n\n const Cls = errorClassForCode(code);\n if (opts.customCatalogContext === true && Cls === AudDSubscriptionError) {\n const overridden = `${CUSTOM_CATALOG_PREFIX}[Server message: ${message}]`;\n throw new AudDCustomCatalogAccessError({ ...fields, message: overridden });\n }\n throw new Cls(fields);\n}\n","/**\n * Typed models. Forward-compatible: each model captures unknown keys into\n * `extras`, mirroring audd-python's Pydantic `extra=\"allow\"` pattern.\n */\n\nfunction pickExtras(\n raw: Record<string, unknown>,\n knownKeys: ReadonlyArray<string>,\n): Record<string, unknown> {\n const known = new Set(knownKeys);\n const extras: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(raw)) {\n if (!known.has(k)) extras[k] = v;\n }\n return extras;\n}\n\nexport function asObject(raw: unknown): Record<string, unknown> | undefined {\n if (typeof raw === \"object\" && raw !== null && !Array.isArray(raw)) {\n return raw as Record<string, unknown>;\n }\n return undefined;\n}\n\n/**\n * Lenient object coercion for response payloads: wrong-typed input degrades\n * to an empty object (every field comes out `undefined`) instead of throwing.\n * Response parsing never throws on missing or wrong-typed fields; call sites\n * that can skip a bad entry entirely filter with {@link asObject} first.\n */\nfunction asObjectLenient(raw: unknown): Record<string, unknown> {\n return asObject(raw) ?? {};\n}\n\n/**\n * Coerce a scalar response field to a `string`. Wrong-typed-but-convertible\n * values are rendered rather than dropped: numbers and booleans become their\n * JS-canonical `String(v)` form (`85` → `\"85\"`, `8.5` → `\"8.5\"`, `true` →\n * `\"true\"`). Objects, arrays, `null`, and `undefined` are not representable as\n * a scalar string and degrade to `undefined`.\n */\nfunction asString(v: unknown): string | undefined {\n if (typeof v === \"string\") return v;\n if (typeof v === \"number\" || typeof v === \"boolean\") return String(v);\n return undefined;\n}\n\n/**\n * Parse a value into a finite JS number, or `undefined`. Numeric strings are\n * accepted only when trimmed, non-empty, and finite — guarding the JS pitfall\n * that `Number(\"\")`, `Number(\" \")`, and `Number(\"Infinity\")` don't represent\n * a real numeric field (`\"\"`/`\"7a\"`/`\"NaN\"`/`\"Infinity\"` → `undefined`; `\"7\"`,\n * `\"8.5\"`, `\"-3\"`, `\"1e2\"` parse). Booleans map to `0`/`1`.\n */\nfunction toFiniteNumber(v: unknown): number | undefined {\n if (typeof v === \"number\") return Number.isFinite(v) ? v : undefined;\n if (typeof v === \"boolean\") return v ? 1 : 0;\n if (typeof v === \"string\") {\n const t = v.trim();\n if (t === \"\") return undefined;\n const n = Number(t);\n return Number.isFinite(n) ? n : undefined;\n }\n return undefined;\n}\n\n/**\n * Coerce a scalar response field to a `number` (float-flavored — for fields\n * like `score` that may be fractional). Numeric strings and booleans are\n * converted; non-numeric strings, objects, arrays, `null`, and `undefined`\n * degrade to `undefined`.\n */\nfunction asNumber(v: unknown): number | undefined {\n return toFiniteNumber(v);\n}\n\n/**\n * Coerce a scalar response field to an integer — for semantically integer\n * fields (ids, counts, timestamps, offsets). Floats and numeric strings are\n * truncated toward zero (`8.9` → `8`, `\"7.9\"` → `7`); booleans map to `0`/`1`.\n * Non-numeric values degrade to `undefined` rather than a misleading `0`.\n */\nfunction asInteger(v: unknown): number | undefined {\n const n = toFiniteNumber(v);\n return n === undefined ? undefined : Math.trunc(n);\n}\n\n/**\n * Coerce a scalar response field to a `boolean`. Numbers map by `v !== 0`.\n * Strings are matched against a strict whitelist (trimmed, case-insensitive):\n * `\"true\"`/`\"1\"`/`\"yes\"`/`\"on\"` → `true`; `\"false\"`/`\"0\"`/`\"no\"`/`\"off\"`/`\"\"`\n * → `false`; any other string is not an unambiguous boolean and degrades to\n * `undefined`. Objects, arrays, `null`, and `undefined` degrade to `undefined`.\n */\nfunction asBoolean(v: unknown): boolean | undefined {\n if (typeof v === \"boolean\") return v;\n if (typeof v === \"number\") return v !== 0;\n if (typeof v === \"string\") {\n const t = v.trim().toLowerCase();\n if (t === \"true\" || t === \"1\" || t === \"yes\" || t === \"on\") return true;\n if (t === \"false\" || t === \"0\" || t === \"no\" || t === \"off\" || t === \"\") return false;\n return undefined;\n }\n return undefined;\n}\n\n/**\n * Parse an offset into seconds. Accepts `\"SS\"`, `\"MM:SS\"`, `\"HH:MM:SS\"`, or a\n * numeric value. Returns `undefined` when the value is absent or unparseable —\n * never throws (this SDK degrades on bad response fields).\n */\nexport function parseOffsetToSeconds(o: unknown): number | undefined {\n if (o === undefined || o === null) return undefined;\n if (typeof o === \"number\") return Number.isFinite(o) ? o : undefined;\n const t = String(o).trim();\n if (t === \"\") return undefined;\n let total = 0;\n for (const part of t.split(\":\")) {\n if (part.trim() === \"\") return undefined;\n const n = Number(part);\n if (!Number.isFinite(n)) return undefined;\n total = total * 60 + n;\n }\n return total;\n}\n\n/** Streaming providers supported by the lis.tn redirect helper. */\nexport type StreamingProvider =\n | \"spotify\"\n | \"apple_music\"\n | \"deezer\"\n | \"napster\"\n | \"youtube\";\n\nconst STREAMING_PROVIDERS: readonly StreamingProvider[] = [\n \"spotify\",\n \"apple_music\",\n \"deezer\",\n \"napster\",\n \"youtube\",\n] as const;\n\nfunction lisTnStreamingUrl(songLink: string | undefined, provider: string): string | null {\n if (songLink === undefined || songLink === \"\") return null;\n let parsed: URL;\n try {\n parsed = new URL(songLink);\n } catch {\n return null;\n }\n if (parsed.hostname !== \"lis.tn\") return null;\n const sep = parsed.search ? \"&\" : \"?\";\n return `${songLink}${sep}${provider}`;\n}\n\nexport interface RecognitionResult {\n /** Always present on a match. */\n timecode?: string | undefined;\n /** Set on custom-catalog matches. */\n audioId?: number | undefined;\n artist?: string | undefined;\n title?: string | undefined;\n album?: string | undefined;\n releaseDate?: string | undefined;\n label?: string | undefined;\n songLink?: string | undefined;\n isrc?: string | undefined;\n upc?: string | undefined;\n appleMusic?: Record<string, unknown> | undefined;\n spotify?: Record<string, unknown> | undefined;\n deezer?: Record<string, unknown> | undefined;\n napster?: Record<string, unknown> | undefined;\n musicbrainz?: ReadonlyArray<Record<string, unknown>> | undefined;\n /** Forward-compat: any unknown keys from the server. */\n extras: Record<string, unknown>;\n /** Full unparsed payload for the caller's own inspection. */\n rawResponse: Record<string, unknown>;\n /** True when `audioId` is set (custom-catalog match). */\n readonly isCustomMatch: boolean;\n /** True when `audioId` is unset and `artist` or `title` are present. */\n readonly isPublicMatch: boolean;\n /** Cover-art URL for `lis.tn`-hosted song_links, else `null`. */\n readonly thumbnailUrl: string | null;\n /**\n * Direct or redirect URL for a streaming provider, with smart fallback.\n *\n * Resolution order:\n * 1. Direct URL from the metadata block (e.g. `apple_music.url`,\n * `spotify.external_urls.spotify`, `deezer.link`, `napster.href`) when\n * the user requested that provider via `return=`.\n * 2. lis.tn redirect (`{songLink}?{provider}`) when `songLink` is on lis.tn.\n * 3. `null` otherwise. YouTube has only the lis.tn-redirect path.\n */\n streamingUrl(provider: StreamingProvider): string | null;\n /** Map of every provider with a resolvable URL — direct or via lis.tn redirect. */\n streamingUrls(): Partial<Record<StreamingProvider, string>>;\n /**\n * First available 30-second preview URL across providers, in priority order:\n * `apple_music.previews[0].url` → `spotify.preview_url` → `deezer.preview`.\n *\n * **Note:** previews are governed by the respective providers' terms of use.\n * The SDK consumer is responsible for honoring those terms (caching limits,\n * attribution, redistribution constraints).\n */\n previewUrl(): string | null;\n}\n\nfunction directStreamingUrl(\n result: Pick<RecognitionResult, \"appleMusic\" | \"spotify\" | \"deezer\" | \"napster\">,\n provider: string,\n): string | null {\n if (provider === \"apple_music\" && result.appleMusic !== undefined) {\n const url = result.appleMusic[\"url\"];\n if (typeof url === \"string\" && url.length > 0) return url;\n } else if (provider === \"spotify\" && result.spotify !== undefined) {\n const ext = result.spotify[\"external_urls\"];\n if (typeof ext === \"object\" && ext !== null) {\n const sp = (ext as Record<string, unknown>)[\"spotify\"];\n if (typeof sp === \"string\" && sp.length > 0) return sp;\n }\n const uri = result.spotify[\"uri\"];\n if (typeof uri === \"string\" && uri.length > 0) return uri;\n } else if (provider === \"deezer\" && result.deezer !== undefined) {\n const link = result.deezer[\"link\"];\n if (typeof link === \"string\" && link.length > 0) return link;\n } else if (provider === \"napster\" && result.napster !== undefined) {\n const href = result.napster[\"href\"];\n if (typeof href === \"string\" && href.length > 0) return href;\n }\n return null;\n}\n\nfunction previewFrom(result: Pick<RecognitionResult, \"appleMusic\" | \"spotify\" | \"deezer\">): string | null {\n if (result.appleMusic !== undefined) {\n const previews = result.appleMusic[\"previews\"];\n if (Array.isArray(previews) && previews.length > 0) {\n const first = previews[0];\n if (typeof first === \"object\" && first !== null) {\n const url = (first as Record<string, unknown>)[\"url\"];\n if (typeof url === \"string\" && url.length > 0) return url;\n }\n }\n }\n if (result.spotify !== undefined) {\n const url = result.spotify[\"preview_url\"];\n if (typeof url === \"string\" && url.length > 0) return url;\n }\n if (result.deezer !== undefined) {\n const url = result.deezer[\"preview\"];\n if (typeof url === \"string\" && url.length > 0) return url;\n }\n return null;\n}\n\nconst RECOGNITION_KEYS = [\n \"timecode\",\n \"audio_id\",\n \"artist\",\n \"title\",\n \"album\",\n \"release_date\",\n \"label\",\n \"song_link\",\n \"isrc\",\n \"upc\",\n \"apple_music\",\n \"spotify\",\n \"deezer\",\n \"napster\",\n \"musicbrainz\",\n] as const;\n\nexport function parseRecognitionResult(raw: unknown): RecognitionResult {\n const r = asObjectLenient(raw);\n const timecode = asString(r.timecode);\n const audioId = asInteger(r.audio_id);\n const artist = asString(r.artist);\n const title = asString(r.title);\n const songLink = asString(r.song_link);\n const musicbrainz = Array.isArray(r.musicbrainz)\n ? (r.musicbrainz.filter((x): x is Record<string, unknown> => asObject(x) !== undefined) as\n | Record<string, unknown>[]\n | undefined)\n : undefined;\n\n const result: RecognitionResult = {\n timecode,\n audioId,\n artist,\n title,\n album: asString(r.album),\n releaseDate: asString(r.release_date),\n label: asString(r.label),\n songLink,\n isrc: asString(r.isrc),\n upc: asString(r.upc),\n appleMusic: asObject(r.apple_music),\n spotify: asObject(r.spotify),\n deezer: asObject(r.deezer),\n napster: asObject(r.napster),\n musicbrainz,\n extras: pickExtras(r, RECOGNITION_KEYS),\n rawResponse: r,\n get isCustomMatch() {\n return audioId !== undefined;\n },\n get isPublicMatch() {\n return audioId === undefined && (artist !== undefined || title !== undefined);\n },\n get thumbnailUrl() {\n return lisTnStreamingUrl(songLink, \"thumb\");\n },\n streamingUrl(provider: StreamingProvider): string | null {\n if (!STREAMING_PROVIDERS.includes(provider)) {\n throw new TypeError(\n `Unknown streaming provider: ${String(provider)}. ` +\n `Valid: ${STREAMING_PROVIDERS.join(\", \")}`,\n );\n }\n const direct = directStreamingUrl(result, provider);\n if (direct !== null) return direct;\n return lisTnStreamingUrl(songLink, provider);\n },\n streamingUrls(): Partial<Record<StreamingProvider, string>> {\n const out: Partial<Record<StreamingProvider, string>> = {};\n for (const p of STREAMING_PROVIDERS) {\n const url = result.streamingUrl(p);\n if (url !== null) out[p] = url;\n }\n return out;\n },\n previewUrl(): string | null {\n return previewFrom(result);\n },\n };\n return result;\n}\n\nexport interface EnterpriseMatch {\n score?: number | undefined;\n timecode?: string | undefined;\n artist?: string | undefined;\n title?: string | undefined;\n album?: string | undefined;\n releaseDate?: string | undefined;\n label?: string | undefined;\n isrc?: string | undefined;\n upc?: string | undefined;\n songLink?: string | undefined;\n startOffset?: number | undefined;\n endOffset?: number | undefined;\n /**\n * Where this song plays in your file, in seconds — the chunk's file offset\n * plus `startOffset`/`endOffset`. `undefined` when the chunk offset is\n * absent or unparseable. Computed during {@link parseEnterpriseChunkResult}.\n */\n startSeconds?: number | undefined;\n endSeconds?: number | undefined;\n extras: Record<string, unknown>;\n rawResponse: Record<string, unknown>;\n readonly thumbnailUrl: string | null;\n /** lis.tn redirect URL for the given streaming provider, or null if `songLink` is non-lis.tn. */\n streamingUrl(provider: StreamingProvider): string | null;\n /** All providers with a resolvable lis.tn redirect URL (or empty when `songLink` is off lis.tn). */\n streamingUrls(): Partial<Record<StreamingProvider, string>>;\n}\n\nconst ENTERPRISE_MATCH_KEYS = [\n \"score\",\n \"timecode\",\n \"artist\",\n \"title\",\n \"album\",\n \"release_date\",\n \"label\",\n \"isrc\",\n \"upc\",\n \"song_link\",\n \"start_offset\",\n \"end_offset\",\n \"start_seconds\",\n \"end_seconds\",\n] as const;\n\nexport function parseEnterpriseMatch(raw: unknown): EnterpriseMatch {\n const r = asObjectLenient(raw);\n const score = asNumber(r.score);\n const timecode = asString(r.timecode);\n const songLink = asString(r.song_link);\n return {\n score,\n timecode,\n artist: asString(r.artist),\n title: asString(r.title),\n album: asString(r.album),\n releaseDate: asString(r.release_date),\n label: asString(r.label),\n isrc: asString(r.isrc),\n upc: asString(r.upc),\n songLink,\n startOffset: asInteger(r.start_offset),\n endOffset: asInteger(r.end_offset),\n startSeconds: undefined,\n endSeconds: undefined,\n extras: pickExtras(r, ENTERPRISE_MATCH_KEYS),\n rawResponse: r,\n streamingUrl(provider: StreamingProvider): string | null {\n if (!STREAMING_PROVIDERS.includes(provider)) {\n throw new TypeError(`Unknown streaming provider: ${String(provider)}`);\n }\n return lisTnStreamingUrl(songLink, provider);\n },\n streamingUrls(): Partial<Record<StreamingProvider, string>> {\n const out: Partial<Record<StreamingProvider, string>> = {};\n for (const p of STREAMING_PROVIDERS) {\n const url = lisTnStreamingUrl(songLink, p);\n if (url !== null) out[p] = url;\n }\n return out;\n },\n get thumbnailUrl() {\n if (songLink === undefined || songLink === \"\") return null;\n try {\n const parsed = new URL(songLink);\n if (parsed.hostname !== \"lis.tn\") return null;\n const sep = parsed.search ? \"&\" : \"?\";\n return `${songLink}${sep}thumb`;\n } catch {\n return null;\n }\n },\n };\n}\n\nexport interface EnterpriseChunkResult {\n songs: EnterpriseMatch[];\n offset?: string | undefined;\n extras: Record<string, unknown>;\n rawResponse: Record<string, unknown>;\n}\n\nconst ENTERPRISE_CHUNK_KEYS = [\"songs\", \"offset\"] as const;\n\nexport function parseEnterpriseChunkResult(raw: unknown): EnterpriseChunkResult {\n const r = asObjectLenient(raw);\n const songsRaw = Array.isArray(r.songs)\n ? r.songs.filter((s) => asObject(s) !== undefined) // skip wrong-typed entries\n : [];\n const offset = asString(r.offset);\n // The chunk offset is the fragment's position in the user's file. Anchor each\n // song's millisecond offsets to it, in seconds. Skip when unparseable.\n const base = parseOffsetToSeconds(r.offset);\n const songs = songsRaw.map((s) => {\n const match = parseEnterpriseMatch(s);\n if (base !== undefined) {\n match.startSeconds = base + (match.startOffset ?? 0) / 1000;\n match.endSeconds = base + (match.endOffset ?? 0) / 1000;\n }\n return match;\n });\n return {\n songs,\n offset,\n extras: pickExtras(r, ENTERPRISE_CHUNK_KEYS),\n rawResponse: r,\n };\n}\n\nexport interface Stream {\n radioId?: number | undefined;\n url?: string | undefined;\n streamRunning?: boolean | undefined;\n longpollCategory?: string | undefined;\n extras: Record<string, unknown>;\n rawResponse: Record<string, unknown>;\n}\n\nconst STREAM_KEYS = [\"radio_id\", \"url\", \"stream_running\", \"longpoll_category\"] as const;\n\nexport function parseStream(raw: unknown): Stream {\n const r = asObjectLenient(raw);\n const radioId = asInteger(r.radio_id);\n const url = asString(r.url);\n const streamRunning = asBoolean(r.stream_running);\n return {\n radioId,\n url,\n streamRunning,\n longpollCategory: asString(r.longpoll_category),\n extras: pickExtras(r, STREAM_KEYS),\n rawResponse: r,\n };\n}\n\n/**\n * One candidate song in a recognition match.\n *\n * Almost every match has exactly one Song; the rare extra candidates that\n * appear under {@link StreamCallbackMatch.alternatives} may have a *different*\n * artist or title from the top song — they're variant catalog releases of the\n * same recording (e.g. a \"feat.\" credit vs. the bare-artist re-release, or\n * regional edits with different titles), not lower-confidence guesses at the\n * same track.\n */\nexport interface StreamCallbackSong {\n artist?: string | undefined;\n title?: string | undefined;\n score?: number | undefined;\n album?: string | undefined;\n releaseDate?: string | undefined;\n label?: string | undefined;\n songLink?: string | undefined;\n isrc?: string | undefined;\n upc?: string | undefined;\n appleMusic?: Record<string, unknown> | undefined;\n spotify?: Record<string, unknown> | undefined;\n deezer?: Record<string, unknown> | undefined;\n napster?: Record<string, unknown> | undefined;\n musicbrainz?: ReadonlyArray<Record<string, unknown>> | undefined;\n extras: Record<string, unknown>;\n}\n\nconst STREAM_CALLBACK_SONG_KEYS = [\n \"artist\",\n \"title\",\n \"score\",\n \"album\",\n \"release_date\",\n \"label\",\n \"song_link\",\n \"isrc\",\n \"upc\",\n \"apple_music\",\n \"spotify\",\n \"deezer\",\n \"napster\",\n \"musicbrainz\",\n] as const;\n\nfunction parseStreamCallbackSong(raw: unknown): StreamCallbackSong {\n const r = asObjectLenient(raw);\n const artist = asString(r.artist);\n const title = asString(r.title);\n const score = asNumber(r.score);\n const musicbrainz = Array.isArray(r.musicbrainz)\n ? (r.musicbrainz.filter((x): x is Record<string, unknown> => asObject(x) !== undefined) as\n | Record<string, unknown>[]\n | undefined)\n : undefined;\n return {\n artist,\n title,\n score,\n album: asString(r.album),\n releaseDate: asString(r.release_date),\n label: asString(r.label),\n songLink: asString(r.song_link),\n isrc: asString(r.isrc),\n upc: asString(r.upc),\n appleMusic: asObject(r.apple_music),\n spotify: asObject(r.spotify),\n deezer: asObject(r.deezer),\n napster: asObject(r.napster),\n musicbrainz,\n extras: pickExtras(r, STREAM_CALLBACK_SONG_KEYS),\n };\n}\n\n/**\n * One recognition event from a stream callback or longpoll.\n *\n * The top match lives in {@link song}; rare extra candidates live in\n * {@link alternatives}. Alternatives entries may have a different artist or\n * title from the top song — they're variant catalog releases of the same\n * recording, not lower-confidence guesses at the same track.\n */\nexport interface StreamCallbackMatch {\n radioId?: number | undefined;\n timestamp?: string | undefined;\n playLength?: number | undefined;\n /** Top match, when present. */\n song?: StreamCallbackSong | undefined;\n /** Variant catalog releases (may have different artist/title); empty array when only one match. */\n alternatives: StreamCallbackSong[];\n extras: Record<string, unknown>;\n rawResponse: Record<string, unknown>;\n}\n\nconst STREAM_CALLBACK_MATCH_KEYS = [\n \"radio_id\",\n \"timestamp\",\n \"play_length\",\n \"results\",\n] as const;\n\nexport function parseStreamCallbackMatch(raw: unknown): StreamCallbackMatch {\n const r = asObjectLenient(raw);\n const radioId = asInteger(r.radio_id);\n const resultsRaw = Array.isArray(r.results) ? r.results : [];\n const songs = resultsRaw\n .filter((x): x is Record<string, unknown> => asObject(x) !== undefined)\n .map(parseStreamCallbackSong);\n const [first, ...rest] = songs;\n return {\n radioId,\n timestamp: asString(r.timestamp),\n playLength: asInteger(r.play_length),\n song: first,\n alternatives: rest,\n extras: pickExtras(r, STREAM_CALLBACK_MATCH_KEYS),\n rawResponse: r,\n };\n}\n\nexport interface StreamCallbackNotification {\n radioId?: number | undefined;\n streamRunning?: boolean | undefined;\n notificationCode?: number | undefined;\n notificationMessage?: string | undefined;\n /** Outer `time` field on the callback envelope (epoch seconds). */\n time?: number | undefined;\n extras: Record<string, unknown>;\n rawResponse: Record<string, unknown>;\n}\n\nconst STREAM_CALLBACK_NOTIFICATION_KEYS = [\n \"radio_id\",\n \"stream_running\",\n \"notification_code\",\n \"notification_message\",\n] as const;\n\nexport function parseStreamCallbackNotification(raw: unknown): StreamCallbackNotification {\n const r = asObjectLenient(raw);\n const radioId = asInteger(r.radio_id);\n const code = asInteger(r.notification_code);\n const message = asString(r.notification_message);\n return {\n radioId,\n streamRunning: asBoolean(r.stream_running),\n notificationCode: code,\n notificationMessage: message,\n extras: pickExtras(r, STREAM_CALLBACK_NOTIFICATION_KEYS),\n rawResponse: r,\n };\n}\n\nexport interface LyricsResult {\n artist?: string | undefined;\n title?: string | undefined;\n lyrics?: string | undefined;\n songId?: number | undefined;\n fullTitle?: string | undefined;\n artistId?: number | undefined;\n songLink?: string | undefined;\n media?: string | undefined;\n extras: Record<string, unknown>;\n rawResponse: Record<string, unknown>;\n}\n\nconst LYRICS_KEYS = [\n \"artist\",\n \"title\",\n \"lyrics\",\n \"song_id\",\n \"full_title\",\n \"artist_id\",\n \"song_link\",\n \"media\",\n] as const;\n\nexport function parseLyricsResult(raw: unknown): LyricsResult {\n const r = asObjectLenient(raw);\n const artist = asString(r.artist);\n const title = asString(r.title);\n return {\n artist,\n title,\n lyrics: asString(r.lyrics),\n songId: asInteger(r.song_id),\n fullTitle: asString(r.full_title),\n artistId: asInteger(r.artist_id),\n songLink: asString(r.song_link),\n media: asString(r.media),\n extras: pickExtras(r, LYRICS_KEYS),\n rawResponse: r,\n };\n}\n","/**\n * Shared longpoll loop + handle, used by both the authenticated `Streams`\n * namespace (token in URL via `HttpClient.get`) and the tokenless\n * `LongpollConsumer` exported from `audd/longpoll`.\n *\n * Browser-safe: no node:crypto, no node:fs, no node:http imports. The whole\n * file relies on `fetch`-style I/O the caller injects.\n */\n\nimport {\n AudDConnectionError,\n AudDSerializationError,\n AudDServerError,\n} from \"./errors.js\";\nimport type { HttpResponse } from \"./http.js\";\nimport type {\n StreamCallbackMatch,\n StreamCallbackNotification,\n} from \"./models.js\";\nimport {\n asObject,\n parseStreamCallbackMatch,\n parseStreamCallbackNotification,\n} from \"./models.js\";\n\nconst HTTP_CLIENT_ERROR_FLOOR = 400;\n\n/**\n * Active long-poll subscription. Two typed `AsyncIterable`s carry the\n * happy-path output and a third carries any terminal error.\n *\n * Consume `matches` and `notifications` independently or in parallel:\n *\n * ```ts\n * const poll = await audd.streams.longpoll(category);\n * for await (const m of poll.matches) {\n * console.log(m.song?.artist ?? \"?\", m.song?.title ?? \"?\");\n * }\n * ```\n *\n * Concurrent consumption:\n *\n * ```ts\n * await Promise.all([\n * (async () => { for await (const m of poll.matches) {} })(),\n * (async () => { for await (const n of poll.notifications) {} })(),\n * (async () => { for await (const e of poll.errors) console.error(e); })(),\n * ]);\n * ```\n *\n * `close()` (or the `await using` resource-management form) tears down the\n * background loop and closes all three iterables.\n */\nexport interface LongpollPoll {\n /** Recognition events. */\n readonly matches: AsyncIterable<StreamCallbackMatch>;\n /** Stream-lifecycle events (\"stream stopped\", \"can't connect\", ...). */\n readonly notifications: AsyncIterable<StreamCallbackNotification>;\n /**\n * Terminal errors. The poll keeps polling on transient HTTP/JSON failures\n * (subject to the retry policy); errors that surface here are the ones the\n * loop gives up on. Each fired error is followed by closure of all three\n * iterables.\n */\n readonly errors: AsyncIterable<Error>;\n /** Stop the background loop. Idempotent. */\n close(): void;\n /** `await using` support — calls `close()`. */\n [Symbol.asyncDispose](): Promise<void>;\n}\n\n/**\n * Caller-supplied bits the loop needs:\n * - `fetchOnce` — performs a single longpoll HTTP GET, with caller-managed\n * auth (token in URL, Bearer header, etc.) and retry policy.\n * - `getInitialSince` — initial `since_time` to send (often `undefined`).\n * - `timeout` — server-side longpoll timeout in seconds.\n * - `category` — the longpoll category derived from `(token, radio_id)`.\n * - `signal` — aborts the in-flight HTTP when the poll is closed.\n */\nexport interface LongpollLoopConfig {\n category: string;\n timeout: number;\n sinceTime: number | undefined;\n fetchOnce: (\n params: Record<string, string | undefined>,\n signal: AbortSignal,\n ) => Promise<HttpResponse>;\n}\n\n/**\n * Internal: a single-consumer-friendly bounded queue used to demultiplex\n * the loop output onto separate AsyncIterables.\n */\nclass AsyncQueue<T> {\n private readonly items: T[] = [];\n private readonly waiters: ((v: IteratorResult<T>) => void)[] = [];\n private done = false;\n\n push(value: T): void {\n if (this.done) return;\n const w = this.waiters.shift();\n if (w !== undefined) {\n w({ value, done: false });\n return;\n }\n this.items.push(value);\n }\n\n finish(): void {\n if (this.done) return;\n this.done = true;\n while (this.waiters.length > 0) {\n const w = this.waiters.shift();\n if (w !== undefined) w({ value: undefined as never, done: true });\n }\n }\n\n next(): Promise<IteratorResult<T>> {\n const item = this.items.shift();\n if (item !== undefined) return Promise.resolve({ value: item, done: false });\n if (this.done) return Promise.resolve({ value: undefined as never, done: true });\n return new Promise((resolve) => {\n this.waiters.push(resolve);\n });\n }\n\n toIterable(): AsyncIterable<T> {\n const next = (): Promise<IteratorResult<T>> => this.next();\n return {\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return { next };\n },\n };\n }\n}\n\n/**\n * Decode one longpoll HTTP response into a Match | Notification | keep-alive\n * (keep-alive = `{timeout, timestamp}` or any non-event body — caller keeps\n * polling). Throws on HTTP non-2xx and non-JSON bodies; wrong-typed event\n * payloads degrade to keep-alives so one odd response never kills the loop.\n */\nfunction decodeOne(\n resp: HttpResponse,\n): { kind: \"match\"; match: StreamCallbackMatch } | { kind: \"notification\"; notification: StreamCallbackNotification } | { kind: \"keepalive\" } {\n if (resp.httpStatus >= HTTP_CLIENT_ERROR_FLOOR) {\n throw new AudDServerError({\n errorCode: 0,\n message: `Longpoll endpoint returned HTTP ${String(resp.httpStatus)}`,\n httpStatus: resp.httpStatus,\n requestId: resp.requestId,\n rawResponse: resp.jsonBody ?? resp.rawText,\n });\n }\n const body = resp.jsonBody;\n if (typeof body !== \"object\" || body === null || Array.isArray(body)) {\n throw new AudDSerializationError(\n \"Longpoll response was not a JSON object\",\n resp.rawText,\n );\n }\n const dict = body as Record<string, unknown>;\n if (asObject(dict.notification) !== undefined) {\n const notification = parseStreamCallbackNotification(dict.notification);\n const t = dict[\"time\"];\n if (typeof t === \"number\") notification.time = t;\n return { kind: \"notification\", notification };\n }\n if (asObject(dict.result) !== undefined) {\n return { kind: \"match\", match: parseStreamCallbackMatch(dict.result) };\n }\n // Either a `{timeout, timestamp}` keep-alive or an empty/unknown body —\n // skip silently and keep polling.\n return { kind: \"keepalive\" };\n}\n\n/**\n * Start the longpoll loop on a background async task. Returns a\n * {@link LongpollPoll} handle whose iterables are filled as events arrive.\n * The caller's `fetchOnce` is responsible for retries and auth.\n */\nexport function startLongpoll(cfg: LongpollLoopConfig): LongpollPoll {\n const matches = new AsyncQueue<StreamCallbackMatch>();\n const notifications = new AsyncQueue<StreamCallbackNotification>();\n const errors = new AsyncQueue<Error>();\n\n const controller = new AbortController();\n let closed = false;\n\n const finishAll = (): void => {\n matches.finish();\n notifications.finish();\n errors.finish();\n };\n\n const close = (): void => {\n if (closed) return;\n closed = true;\n controller.abort();\n finishAll();\n };\n\n const run = async (): Promise<void> => {\n let curSince = cfg.sinceTime;\n while (!closed) {\n const params: Record<string, string | undefined> = {\n category: cfg.category,\n timeout: String(cfg.timeout),\n };\n if (curSince !== undefined) params[\"since_time\"] = String(curSince);\n\n let resp: HttpResponse;\n try {\n resp = await cfg.fetchOnce(params, controller.signal);\n } catch (err) {\n if (closed) return;\n if (err instanceof TypeError) {\n errors.push(new AudDConnectionError(`Network error: ${err.message}`, err));\n } else if (\n err !== null &&\n typeof err === \"object\" &&\n (err as { name?: string }).name === \"AbortError\"\n ) {\n // Closed-from-outside path — drop quietly.\n if (closed) return;\n errors.push(new AudDConnectionError(\"Request was aborted (timeout)\", err));\n } else if (err instanceof Error) {\n errors.push(err);\n } else {\n errors.push(new Error(String(err)));\n }\n finishAll();\n return;\n }\n\n try {\n const decoded = decodeOne(resp);\n if (decoded.kind === \"match\") matches.push(decoded.match);\n else if (decoded.kind === \"notification\") notifications.push(decoded.notification);\n // else keep-alive: skip.\n } catch (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)));\n finishAll();\n return;\n }\n\n // Bump since_time off the response body's timestamp, as the audd-go\n // reference does — keeps reconnects from re-replaying old events.\n const dict = resp.jsonBody;\n if (typeof dict === \"object\" && dict !== null && !Array.isArray(dict)) {\n const ts = (dict as Record<string, unknown>)[\"timestamp\"];\n if (typeof ts === \"number\") curSince = ts;\n }\n }\n };\n\n // Fire-and-forget. Failures land on the `errors` iterable.\n void run();\n\n return {\n matches: matches.toIterable(),\n notifications: notifications.toIterable(),\n errors: errors.toIterable(),\n close,\n async [Symbol.asyncDispose](): Promise<void> {\n close();\n await Promise.resolve();\n },\n };\n}\n","import { AudDConnectionError, isUserAbortError } from \"./errors.js\";\nimport type { HttpResponse } from \"./http.js\";\n\n/**\n * Cost-aware retry classes.\n *\n * - READ — idempotent reads (`streams.list`, `streams.getCallbackUrl`):\n * retry on 408/429/5xx + any connection error.\n * - RECOGNITION — `recognize`, `recognizeEnterprise`, `advanced.findLyrics`:\n * retry on pre-upload connection failures + 5xx.\n * DO NOT retry on read-timeout-after-upload (cost protection).\n * - MUTATING — `streams.add`, `streams.delete`, etc.:\n * retry only on pre-upload connection failures. DO NOT retry\n * 5xx (the side effect may have happened).\n * - NONE — `customCatalog.add`: never retry. Custom-catalog upload is\n * metered; auto-retry on transport failure could double-charge\n * for the same audio fingerprinting. Surface a clean error and\n * let the caller decide.\n */\nexport type RetryClass = \"read\" | \"recognition\" | \"mutating\" | \"none\";\n\nexport interface RetryPolicy {\n retryClass: RetryClass;\n maxAttempts: number;\n backoffFactorMs: number;\n backoffMaxMs: number;\n}\n\nexport const DEFAULT_BACKOFF_FACTOR_MS = 500;\nexport const DEFAULT_BACKOFF_MAX_MS = 30_000;\nexport const DEFAULT_MAX_ATTEMPTS = 3;\n\nconst HTTP_REQUEST_TIMEOUT = 408;\nconst HTTP_TOO_MANY_REQUESTS = 429;\nconst HTTP_SERVER_ERROR_FLOOR = 500;\n\nexport function defaultPolicy(retryClass: RetryClass): RetryPolicy {\n return {\n retryClass,\n maxAttempts: DEFAULT_MAX_ATTEMPTS,\n backoffFactorMs: DEFAULT_BACKOFF_FACTOR_MS,\n backoffMaxMs: DEFAULT_BACKOFF_MAX_MS,\n };\n}\n\nfunction backoffDelayMs(attempt: number, policy: RetryPolicy): number {\n const base = Math.min(policy.backoffFactorMs * 2 ** attempt, policy.backoffMaxMs);\n return base * (0.5 + Math.random());\n}\n\n/**\n * `fetch()` raises `TypeError(\"fetch failed\")` when DNS/TCP/TLS failed before\n * the request body finished. AbortError (or DOMException/AbortError) means the\n * caller's AbortSignal fired — could be pre- or post-upload, treated as\n * post-upload conservatively for cost-protection.\n */\nfunction isAbortError(err: unknown): boolean {\n if (err === null || typeof err !== \"object\") return false;\n const e = err as { name?: unknown };\n return e.name === \"AbortError\";\n}\n\nfunction isPreUploadConnectionError(err: unknown): boolean {\n if (isAbortError(err)) return false;\n // Node fetch wraps DNS/TCP/TLS errors as TypeError. Browsers do too.\n return err instanceof TypeError;\n}\n\nfunction shouldRetryResponse(resp: HttpResponse, retryClass: RetryClass): boolean {\n const s = resp.httpStatus;\n switch (retryClass) {\n case \"read\":\n return (\n s === HTTP_REQUEST_TIMEOUT ||\n s === HTTP_TOO_MANY_REQUESTS ||\n s >= HTTP_SERVER_ERROR_FLOOR\n );\n case \"recognition\":\n return s >= HTTP_SERVER_ERROR_FLOOR;\n case \"mutating\":\n return false;\n case \"none\":\n return false;\n }\n}\n\nfunction shouldRetryError(err: unknown, retryClass: RetryClass): boolean {\n switch (retryClass) {\n case \"read\":\n // Pre-upload connection errors and post-upload read timeouts are both\n // safe-ish: idempotent reads can be retried freely.\n return isPreUploadConnectionError(err) || isAbortError(err);\n case \"recognition\":\n // Pre-upload only — explicitly NOT post-upload AbortError (cost protection).\n return isPreUploadConnectionError(err);\n case \"mutating\":\n // Pre-upload only — server-side side-effect may have happened.\n return isPreUploadConnectionError(err);\n case \"none\":\n // Metered upload — never retry on transport failure (could double-charge).\n return false;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function retry<T extends HttpResponse>(\n fn: () => Promise<T>,\n policy: RetryPolicy,\n): Promise<T> {\n let lastError: unknown;\n let lastResp: T | undefined;\n let haveResp = false;\n for (let attempt = 0; attempt < policy.maxAttempts; attempt++) {\n try {\n const resp = await fn();\n if (!shouldRetryResponse(resp, policy.retryClass)) return resp;\n lastResp = resp;\n haveResp = true;\n lastError = undefined;\n } catch (err) {\n if (!shouldRetryError(err, policy.retryClass)) throw err;\n lastError = err;\n haveResp = false;\n if (attempt + 1 >= policy.maxAttempts) throw err;\n }\n if (attempt + 1 >= policy.maxAttempts) break;\n await sleep(backoffDelayMs(attempt, policy));\n }\n if (haveResp && lastResp !== undefined) return lastResp;\n throw lastError;\n}\n\n/**\n * Run `fn` under the retry policy and map transport-level failures onto\n * {@link AudDConnectionError}:\n *\n * - caller-cancelled requests (their AbortSignal fired) → cancellation\n * message, never retried;\n * - `TypeError` from fetch (DNS/TCP/TLS) → network error;\n * - `AbortError` (the SDK's own timeout) → timeout message.\n */\nexport async function runRetried<T extends HttpResponse>(\n fn: () => Promise<T>,\n policy: RetryPolicy,\n): Promise<T> {\n try {\n return await retry(fn, policy);\n } catch (err) {\n if (isUserAbortError(err)) {\n throw new AudDConnectionError(\"Request was cancelled by the caller's AbortSignal\", err);\n }\n if (err instanceof TypeError) {\n throw new AudDConnectionError(`Network error: ${err.message}`, err);\n }\n if (err !== null && typeof err === \"object\" && (err as { name?: string }).name === \"AbortError\") {\n throw new AudDConnectionError(\"Request was aborted (timeout)\", err);\n }\n throw err;\n }\n}\n","export const VERSION = \"1.5.13\";\n","import { VERSION } from \"./version.js\";\n\nexport function userAgent(): string {\n const node =\n typeof process !== \"undefined\" && process.version\n ? process.version.replace(/^v/, \"\")\n : \"unknown\";\n const platform =\n typeof process !== \"undefined\" && process.platform ? process.platform : \"browser\";\n return `audd-node/${VERSION} node/${node} (${platform})`;\n}\n","/**\n * Tokenless longpoll consumer for browser/widget/extension use cases.\n *\n * Carries no api_token. The category alone authorizes the subscription. The\n * user/server who derived the category is responsible for ensuring a\n * callback URL is set on their account (we can't preflight that without a\n * token).\n *\n * Behavior:\n * - HTTP non-2xx → terminal {@link AudDServerError} on the `errors` iterable\n * (no silent infinite loop).\n * - JSON decode failure on 2xx → terminal {@link AudDSerializationError}.\n * - READ-class retries on 5xx + connection failures, configurable via\n * `maxRetries` / `backoffFactorMs`.\n * - `[Symbol.asyncDispose]()` + manual `close()` for resource cleanup.\n *\n * Imported from `audd/longpoll` (browser-safe sub-entry, NOT the main\n * `audd` module — bundlers tree-shake the auth client out).\n */\n\nimport { userAbortError } from \"./errors.js\";\nimport type { FetchLike, HttpResponse } from \"./http.js\";\nimport {\n startLongpoll,\n type LongpollPoll,\n} from \"./longpollCore.js\";\nimport { defaultPolicy, runRetried, type RetryPolicy } from \"./retry.js\";\nimport { userAgent } from \"./userAgent.js\";\n\nexport type { LongpollPoll } from \"./longpollCore.js\";\n\nconst LONGPOLL_URL = \"https://api.audd.io/longpoll/\";\n\nexport interface LongpollConsumerOptions {\n /** Maximum retry attempts (default 3). */\n maxRetries?: number;\n /** Initial backoff in ms (default 500), jittered, exponential. */\n backoffFactorMs?: number;\n /** Custom fetch (e.g., for proxy/mTLS). */\n fetch?: FetchLike;\n}\n\nexport interface IterateOptions {\n sinceTime?: number;\n /** Server-side timeout in seconds (default 50). */\n timeout?: number;\n}\n\n/**\n * Tokenless longpoll consumer.\n *\n * ```ts\n * import { LongpollConsumer } from 'audd/longpoll';\n *\n * const consumer = new LongpollConsumer(\"abc123def\");\n * const poll = consumer.iterate({ timeout: 30 });\n * for await (const m of poll.matches) {\n * console.log(m.song?.artist ?? \"?\", m.song?.title ?? \"?\");\n * }\n * ```\n *\n * Or iterate matches and notifications concurrently:\n *\n * ```ts\n * await Promise.all([\n * (async () => { for await (const m of poll.matches) { ... } })(),\n * (async () => { for await (const n of poll.notifications) { ... } })(),\n * ]);\n * ```\n */\nexport class LongpollConsumer {\n private readonly fetchImpl: FetchLike;\n private readonly policy: RetryPolicy;\n\n constructor(public readonly category: string, opts: LongpollConsumerOptions = {}) {\n this.fetchImpl = opts.fetch ?? ((globalThis.fetch as FetchLike).bind(globalThis));\n const base = defaultPolicy(\"read\");\n this.policy = {\n ...base,\n maxAttempts: opts.maxRetries ?? base.maxAttempts,\n backoffFactorMs: opts.backoffFactorMs ?? base.backoffFactorMs,\n };\n }\n\n iterate(opts: IterateOptions = {}): LongpollPoll {\n const fetchImpl = this.fetchImpl;\n const policy = this.policy;\n const timeoutSec = opts.timeout ?? 50;\n\n const fetchOnceRaw = async (\n params: Record<string, string | undefined>,\n signal: AbortSignal,\n ): Promise<HttpResponse> => {\n const u = new URL(LONGPOLL_URL);\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined) u.searchParams.set(k, v);\n }\n const response = await fetchImpl(u.toString(), {\n method: \"GET\",\n headers: { \"User-Agent\": userAgent() },\n signal,\n });\n const text = await response.text();\n let json: unknown = null;\n if (text !== \"\") {\n try {\n json = JSON.parse(text);\n } catch {\n json = null;\n }\n }\n return {\n jsonBody: json,\n httpStatus: response.status,\n requestId: response.headers.get(\"x-request-id\"),\n rawText: text,\n };\n };\n\n const fetchOnce = (\n params: Record<string, string | undefined>,\n signal: AbortSignal,\n ): Promise<HttpResponse> =>\n runRetried(async () => {\n try {\n return await fetchOnceRaw(params, signal);\n } catch (err) {\n // The only abort source here is the poll handle's own close() —\n // a caller cancellation. Mark it so it is never retried.\n if (\n signal.aborted &&\n err !== null &&\n typeof err === \"object\" &&\n (err as { name?: unknown }).name === \"AbortError\"\n ) {\n throw userAbortError(err);\n }\n throw err;\n }\n }, policy);\n\n return startLongpoll({\n category: this.category,\n timeout: timeoutSec,\n sinceTime: opts.sinceTime,\n fetchOnce,\n });\n }\n\n /** Release any underlying resources. */\n close(): void {\n /* no persistent connections in fetch */\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n this.close();\n await Promise.resolve();\n }\n}\n"]}
package/dist/longpoll.js CHANGED
@@ -74,13 +74,38 @@ function asObjectLenient(raw) {
74
74
  return asObject(raw) ?? {};
75
75
  }
76
76
  function asString(v) {
77
- return typeof v === "string" ? v : void 0;
77
+ if (typeof v === "string") return v;
78
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
79
+ return void 0;
80
+ }
81
+ function toFiniteNumber(v) {
82
+ if (typeof v === "number") return Number.isFinite(v) ? v : void 0;
83
+ if (typeof v === "boolean") return v ? 1 : 0;
84
+ if (typeof v === "string") {
85
+ const t = v.trim();
86
+ if (t === "") return void 0;
87
+ const n = Number(t);
88
+ return Number.isFinite(n) ? n : void 0;
89
+ }
90
+ return void 0;
78
91
  }
79
92
  function asNumber(v) {
80
- return typeof v === "number" ? v : void 0;
93
+ return toFiniteNumber(v);
94
+ }
95
+ function asInteger(v) {
96
+ const n = toFiniteNumber(v);
97
+ return n === void 0 ? void 0 : Math.trunc(n);
81
98
  }
82
99
  function asBoolean(v) {
83
- return typeof v === "boolean" ? v : void 0;
100
+ if (typeof v === "boolean") return v;
101
+ if (typeof v === "number") return v !== 0;
102
+ if (typeof v === "string") {
103
+ const t = v.trim().toLowerCase();
104
+ if (t === "true" || t === "1" || t === "yes" || t === "on") return true;
105
+ if (t === "false" || t === "0" || t === "no" || t === "off" || t === "") return false;
106
+ return void 0;
107
+ }
108
+ return void 0;
84
109
  }
85
110
  var STREAM_CALLBACK_SONG_KEYS = [
86
111
  "artist",
@@ -130,14 +155,14 @@ var STREAM_CALLBACK_MATCH_KEYS = [
130
155
  ];
131
156
  function parseStreamCallbackMatch(raw) {
132
157
  const r = asObjectLenient(raw);
133
- const radioId = asNumber(r.radio_id);
158
+ const radioId = asInteger(r.radio_id);
134
159
  const resultsRaw = Array.isArray(r.results) ? r.results : [];
135
160
  const songs = resultsRaw.filter((x) => asObject(x) !== void 0).map(parseStreamCallbackSong);
136
161
  const [first, ...rest] = songs;
137
162
  return {
138
163
  radioId,
139
164
  timestamp: asString(r.timestamp),
140
- playLength: asNumber(r.play_length),
165
+ playLength: asInteger(r.play_length),
141
166
  song: first,
142
167
  alternatives: rest,
143
168
  extras: pickExtras(r, STREAM_CALLBACK_MATCH_KEYS),
@@ -152,8 +177,8 @@ var STREAM_CALLBACK_NOTIFICATION_KEYS = [
152
177
  ];
153
178
  function parseStreamCallbackNotification(raw) {
154
179
  const r = asObjectLenient(raw);
155
- const radioId = asNumber(r.radio_id);
156
- const code = asNumber(r.notification_code);
180
+ const radioId = asInteger(r.radio_id);
181
+ const code = asInteger(r.notification_code);
157
182
  const message = asString(r.notification_message);
158
183
  return {
159
184
  radioId,
@@ -403,7 +428,7 @@ async function runRetried(fn, policy) {
403
428
  }
404
429
 
405
430
  // src/version.ts
406
- var VERSION = "1.5.12";
431
+ var VERSION = "1.5.13";
407
432
 
408
433
  // src/userAgent.ts
409
434
  function userAgent() {