1+ import type { APIClient } from "@/browser/contexts/API" ;
12import { getStoredAuthToken } from "@/browser/components/AuthTokenModal/AuthTokenModal" ;
23import { getBrowserBackendBaseUrl } from "@/browser/utils/backendBaseUrl" ;
34import type { EventSoundSettings } from "@/common/config/schemas/appConfigOnDisk" ;
@@ -8,32 +9,62 @@ function getServerAuthToken(): string | null {
89 return urlToken ?. length ? urlToken : getStoredAuthToken ( ) ;
910}
1011
11- function toManagedPlaybackPath ( assetId : string ) : string {
12- // Browser mode can run behind a path-based app proxy (for example Coder),
13- // so resolve against the backend base URL instead of assuming "/".
14- const backendBaseUrl = getBrowserBackendBaseUrl ( ) ;
15- const playbackUrl = new URL (
16- `assets/event-sounds/${ encodeURIComponent ( assetId ) } ` ,
17- `${ backendBaseUrl } /`
18- ) ;
12+ function toManagedPlaybackPath ( baseUrl : string , assetId : string , authToken : string | null ) : string {
13+ const playbackUrl = new URL ( `assets/event-sounds/${ encodeURIComponent ( assetId ) } ` , `${ baseUrl } /` ) ;
1914
2015 // <audio> cannot attach Authorization headers, so pass the server token as
2116 // a query param when token-auth is in use.
22- const authToken = getServerAuthToken ( ) ;
2317 if ( authToken ) {
2418 playbackUrl . searchParams . set ( "token" , authToken ) ;
2519 }
2620
2721 return playbackUrl . toString ( ) ;
2822}
2923
24+ function toBrowserManagedPlaybackPath ( assetId : string ) : string {
25+ // Browser mode can run behind a path-based app proxy (for example Coder),
26+ // so resolve against the backend base URL instead of assuming "/".
27+ return toManagedPlaybackPath ( getBrowserBackendBaseUrl ( ) , assetId , getServerAuthToken ( ) ) ;
28+ }
29+
30+ async function toDesktopManagedPlaybackPath (
31+ assetId : string ,
32+ apiClient : APIClient | null | undefined
33+ ) : Promise < string | null > {
34+ if ( ! apiClient ?. server ?. getApiServerStatus ) {
35+ return null ;
36+ }
37+
38+ try {
39+ const apiServerStatus = await apiClient . server . getApiServerStatus ( ) ;
40+ if ( ! apiServerStatus . running || ! apiServerStatus . baseUrl ) {
41+ return null ;
42+ }
43+
44+ return toManagedPlaybackPath ( apiServerStatus . baseUrl , assetId , apiServerStatus . token ) ;
45+ } catch {
46+ return null ;
47+ }
48+ }
49+
50+ function playAudioFromPath ( path : string , eventKey : EventSoundKey ) : void {
51+ const audio = new Audio ( path ) ;
52+ void audio . play ( ) . catch ( ( error ) => {
53+ console . debug ( "Event sound playback failed" , {
54+ eventKey,
55+ error : String ( error ) ,
56+ } ) ;
57+ } ) ;
58+ }
59+
3060/**
3161 * Attempt to play the configured sound for the given event key.
3262 * Fails silently with debug logging if no sound is configured or playback fails.
3363 */
3464export function playEventSound (
3565 eventSoundSettings : EventSoundSettings | undefined ,
36- eventKey : EventSoundKey
66+ eventKey : EventSoundKey ,
67+ apiClient ?: APIClient | null
3768) : void {
3869 if ( ! eventSoundSettings ) {
3970 return ;
@@ -44,11 +75,16 @@ export function playEventSound(
4475 return ;
4576 }
4677
47- const audio = new Audio ( toManagedPlaybackPath ( config . source . assetId ) ) ;
48- void audio . play ( ) . catch ( ( error ) => {
49- console . debug ( "Event sound playback failed" , {
50- eventKey,
51- error : String ( error ) ,
52- } ) ;
78+ if ( ! window . api ) {
79+ playAudioFromPath ( toBrowserManagedPlaybackPath ( config . source . assetId ) , eventKey ) ;
80+ return ;
81+ }
82+
83+ void toDesktopManagedPlaybackPath ( config . source . assetId , apiClient ) . then ( ( playbackPath ) => {
84+ if ( ! playbackPath ) {
85+ return ;
86+ }
87+
88+ playAudioFromPath ( playbackPath , eventKey ) ;
5389 } ) ;
5490}
0 commit comments