11import { CheckAPIStatus } from "../../wailsjs/go/main/App" ;
22import { CHECK_TIMEOUT_MS , withTimeout } from "@/lib/async-timeout" ;
3-
43export type ApiCheckStatus = "checking" | "online" | "offline" | "idle" ;
5-
64export interface ApiSource {
75 id : string ;
86 type : string ;
97 name : string ;
108 url : string ;
119}
12-
1310interface SpotiFLACNextSource {
1411 id : string ;
1512 name : string ;
1613}
17-
1814type SpotiFLACNextStatusResponse = {
1915 tidal ?: string ;
2016 qobuz_a ?: string ;
@@ -27,53 +23,44 @@ type SpotiFLACNextStatusResponse = {
2723 amazon_c ?: string ;
2824 apple ?: string ;
2925} ;
30-
3126export const API_SOURCES : ApiSource [ ] = [
3227 { id : "tidal" , type : "tidal" , name : "Tidal" , url : "" } ,
3328 { id : "qobuz" , type : "qobuz" , name : "Qobuz" , url : "" } ,
3429 { id : "amazon" , type : "amazon" , name : "Amazon Music" , url : "" } ,
3530 { id : "musicbrainz" , type : "musicbrainz" , name : "MusicBrainz" , url : "https://musicbrainz.org" } ,
3631] ;
37-
3832export const SPOTIFLAC_NEXT_SOURCES : SpotiFLACNextSource [ ] = [
3933 { id : "tidal" , name : "Tidal" } ,
4034 { id : "qobuz" , name : "Qobuz" } ,
4135 { id : "amazon" , name : "Amazon Music" } ,
4236 { id : "deezer" , name : "Deezer" } ,
4337 { id : "apple" , name : "Apple Music" } ,
4438] ;
45-
4639const SPOTIFLAC_NEXT_STATUS_URL = "https://status.spotbye.qzz.io/status" ;
4740const SPOTIFLAC_NEXT_MAX_ATTEMPTS = 3 ;
4841const SPOTIFLAC_NEXT_RETRY_DELAY_MS = 1200 ;
49-
5042type ApiStatusState = {
5143 checkingSources : Record < string , boolean > ;
5244 statuses : Record < string , ApiCheckStatus > ;
5345 nextStatuses : Record < string , ApiCheckStatus > ;
5446} ;
55-
5647let apiStatusState : ApiStatusState = {
5748 checkingSources : { } ,
5849 statuses : { } ,
5950 nextStatuses : { } ,
6051} ;
61-
6252let activeCheckNextOnly : Promise < void > | null = null ;
6353const activeSourceChecks = new Map < string , Promise < void > > ( ) ;
6454const listeners = new Set < ( ) => void > ( ) ;
65-
6655function emitApiStatusChange ( ) {
6756 for ( const listener of listeners ) {
6857 listener ( ) ;
6958 }
7059}
71-
7260function setApiStatusState ( updater : ( current : ApiStatusState ) => ApiStatusState ) {
7361 apiStatusState = updater ( apiStatusState ) ;
7462 emitApiStatusChange ( ) ;
7563}
76-
7764async function checkSourceStatus ( source : ApiSource ) : Promise < ApiCheckStatus > {
7865 try {
7966 const isOnline = await withTimeout ( CheckAPIStatus ( source . type , source . url ) , CHECK_TIMEOUT_MS , `API status check timed out after 10 seconds for ${ source . name } ` ) ;
@@ -83,27 +70,22 @@ async function checkSourceStatus(source: ApiSource): Promise<ApiCheckStatus> {
8370 return "offline" ;
8471 }
8572}
86-
8773function statusFromNextValue ( value : string | undefined ) : ApiCheckStatus {
8874 return value === "up" ? "online" : "offline" ;
8975}
90-
9176function anyNextVariantUp ( values : Array < string | undefined > ) : ApiCheckStatus {
9277 return values . some ( ( value ) => value === "up" ) ? "online" : "offline" ;
9378}
94-
9579function delay ( ms : number ) : Promise < void > {
9680 return new Promise ( ( resolve ) => window . setTimeout ( resolve , ms ) ) ;
9781}
98-
9982function getSafeNextStatusesFallback ( currentStatuses : Record < string , ApiCheckStatus > ) : Record < string , ApiCheckStatus > {
10083 return SPOTIFLAC_NEXT_SOURCES . reduce < Record < string , ApiCheckStatus > > ( ( acc , source ) => {
10184 const current = currentStatuses [ source . id ] ;
10285 acc [ source . id ] = current === "online" || current === "offline" ? current : "idle" ;
10386 return acc ;
10487 } , { } ) ;
10588}
106-
10789async function fetchSpotiFLACNextStatusesOnce ( ) : Promise < Record < string , ApiCheckStatus > > {
10890 const response = await withTimeout ( fetch ( SPOTIFLAC_NEXT_STATUS_URL , {
10991 method : "GET" ,
@@ -112,11 +94,9 @@ async function fetchSpotiFLACNextStatusesOnce(): Promise<Record<string, ApiCheck
11294 Accept : "application/json" ,
11395 } ,
11496 } ) , CHECK_TIMEOUT_MS , "SpotiFLAC Next status check timed out after 10 seconds" ) ;
115-
11697 if ( ! response . ok ) {
11798 throw new Error ( `SpotiFLAC Next status returned ${ response . status } ` ) ;
11899 }
119-
120100 const payload = ( await response . json ( ) ) as SpotiFLACNextStatusResponse ;
121101 return {
122102 tidal : statusFromNextValue ( payload . tidal ) ,
@@ -126,10 +106,8 @@ async function fetchSpotiFLACNextStatusesOnce(): Promise<Record<string, ApiCheck
126106 apple : statusFromNextValue ( payload . apple ) ,
127107 } ;
128108}
129-
130109async function checkSpotiFLACNextStatuses ( ) : Promise < Record < string , ApiCheckStatus > > {
131110 let lastError : unknown = null ;
132-
133111 for ( let attempt = 1 ; attempt <= SPOTIFLAC_NEXT_MAX_ATTEMPTS ; attempt ++ ) {
134112 try {
135113 return await fetchSpotiFLACNextStatusesOnce ( ) ;
@@ -141,33 +119,27 @@ async function checkSpotiFLACNextStatuses(): Promise<Record<string, ApiCheckStat
141119 }
142120 }
143121 }
144-
145122 throw lastError instanceof Error ? lastError : new Error ( "SpotiFLAC Next status check failed" ) ;
146123}
147-
148124export function getApiStatusState ( ) : ApiStatusState {
149125 return apiStatusState ;
150126}
151-
152127export function subscribeApiStatus ( listener : ( ) => void ) : ( ) => void {
153128 listeners . add ( listener ) ;
154129 return ( ) => {
155130 listeners . delete ( listener ) ;
156131 } ;
157132}
158-
159133function hasSpotiFLACNextResults ( ) : boolean {
160134 return SPOTIFLAC_NEXT_SOURCES . some ( ( source ) => {
161135 const status = apiStatusState . nextStatuses [ source . id ] ;
162136 return status === "online" || status === "offline" ;
163137 } ) ;
164138}
165-
166139export async function checkSpotiFLACNextStatusesOnly ( ) : Promise < void > {
167140 if ( activeCheckNextOnly ) {
168141 return activeCheckNextOnly ;
169142 }
170-
171143 activeCheckNextOnly = ( async ( ) => {
172144 const checkingNextStatuses = Object . fromEntries ( SPOTIFLAC_NEXT_SOURCES . map ( ( source ) => [ source . id , "checking" as ApiCheckStatus ] ) ) ;
173145 setApiStatusState ( ( current ) => ( {
@@ -177,15 +149,12 @@ export async function checkSpotiFLACNextStatusesOnly(): Promise<void> {
177149 ...checkingNextStatuses ,
178150 } ,
179151 } ) ) ;
180-
181152 try {
182153 setApiStatusState ( ( current ) => ( {
183154 ...current ,
184155 nextStatuses : { ...current . nextStatuses } ,
185156 } ) ) ;
186-
187157 const nextStatuses = await checkSpotiFLACNextStatuses ( ) ;
188-
189158 setApiStatusState ( ( current ) => ( {
190159 ...current ,
191160 nextStatuses : {
@@ -204,27 +173,22 @@ export async function checkSpotiFLACNextStatusesOnly(): Promise<void> {
204173 activeCheckNextOnly = null ;
205174 }
206175 } ) ( ) ;
207-
208176 return activeCheckNextOnly ;
209177}
210-
211178export function ensureSpotiFLACNextStatusCheckStarted ( ) : void {
212179 if ( ! activeCheckNextOnly && ! hasSpotiFLACNextResults ( ) ) {
213180 void checkSpotiFLACNextStatusesOnly ( ) ;
214181 }
215182}
216-
217183export async function checkApiStatus ( sourceId : string ) : Promise < void > {
218184 const source = API_SOURCES . find ( ( item ) => item . id === sourceId ) ;
219185 if ( ! source ) {
220186 return ;
221187 }
222-
223188 const activeCheck = activeSourceChecks . get ( sourceId ) ;
224189 if ( activeCheck ) {
225190 return activeCheck ;
226191 }
227-
228192 const task = ( async ( ) => {
229193 setApiStatusState ( ( current ) => ( {
230194 ...current ,
@@ -237,10 +201,8 @@ export async function checkApiStatus(sourceId: string): Promise<void> {
237201 [ sourceId ] : "checking" ,
238202 } ,
239203 } ) ) ;
240-
241204 try {
242205 const status = await checkSourceStatus ( source ) ;
243-
244206 setApiStatusState ( ( current ) => ( {
245207 ...current ,
246208 statuses : {
@@ -260,7 +222,6 @@ export async function checkApiStatus(sourceId: string): Promise<void> {
260222 activeSourceChecks . delete ( sourceId ) ;
261223 }
262224 } ) ( ) ;
263-
264225 activeSourceChecks . set ( sourceId , task ) ;
265226 return task ;
266227}
0 commit comments