@@ -7,17 +7,56 @@ import type {
77
88@Injectable ( { providedIn : "root" } )
99export class HostBridgeClient implements IHostBridge {
10+ private nextId = 0 ;
11+ private readonly listeners = new Set < ( event : unknown ) => void > ( ) ;
12+ private readonly pending = new Map < string , { resolve ( value : unknown ) : void ; reject ( error : Error ) : void } > ( ) ;
13+
1014 request < K extends keyof IHostRequestMap > (
1115 operation : K ,
1216 input : IHostRequestMap [ K ] ,
1317 ) : Promise < IHostResponseMap [ K ] > {
14- void operation ;
15- void input ;
16- return Promise . reject ( new Error ( "Host bridge is not connected" ) ) ;
18+ const id = `ui-${ ++ this . nextId } ` ;
19+ const frame = JSON . stringify ( { jsonrpc : "2.0" , id, method : operation , params : input ?? { } } ) ;
20+ const tauri = ( globalThis as { __TAURI__ ?: { invoke ?: ( command : string , args ?: Record < string , unknown > ) => Promise < unknown > ; event ?: { listen ?: ( name : string , callback : ( event : { payload : unknown } ) => void ) => Promise < ( ) => void > } } } ) . __TAURI__ ;
21+ if ( tauri ?. invoke ) return this . requestTauri ( id , frame , tauri ) as Promise < IHostResponseMap [ K ] > ;
22+ return this . requestBrowser ( frame ) as Promise < IHostResponseMap [ K ] > ;
1723 }
1824
1925 subscribe ( listener : ( event : unknown ) => void ) : ( ) => void {
20- void listener ;
21- return ( ) => undefined ;
26+ this . listeners . add ( listener ) ;
27+ const tauri = ( globalThis as { __TAURI__ ?: { event ?: { listen ?: ( name : string , callback : ( event : { payload : unknown } ) => void ) => Promise < ( ) => void > } } } ) . __TAURI__ ;
28+ void tauri ?. event ?. listen ?.( "tanit://ipc-message" , ( event ) => {
29+ this . dispatchEvent ( event . payload ) ;
30+ listener ( event . payload ) ;
31+ } ) ;
32+ return ( ) => this . listeners . delete ( listener ) ;
33+ }
34+
35+ private async requestTauri ( id : string , frame : string , tauri : { invoke ?: ( command : string , args ?: Record < string , unknown > ) => Promise < unknown > ; event ?: { listen ?: ( name : string , callback : ( event : { payload : unknown } ) => void ) => Promise < ( ) => void > } } ) : Promise < unknown > {
36+ if ( ! tauri . invoke || ! tauri . event ?. listen ) throw new Error ( "Tauri bridge is incomplete" ) ;
37+ const response = new Promise < unknown > ( ( resolve , reject ) => {
38+ this . pending . set ( id , { resolve, reject } ) ;
39+ } ) ;
40+ await tauri . invoke ( "send_to_sidecar" , { frame } ) ;
41+ return response ;
42+ }
43+
44+ private async requestBrowser ( frame : string ) : Promise < unknown > {
45+ const token = typeof document !== "undefined" ? document . documentElement . dataset . tanitToken : undefined ;
46+ const response = await fetch ( "/api" , { method : "POST" , headers : { "content-type" : "application/json" , ...( token ? { "x-tanit-token" : token } : { } ) } , body : frame } ) ;
47+ const payload = await response . json ( ) as { result ?: unknown ; error ?: { message ?: string } } ;
48+ if ( ! response . ok || payload . error ) throw new Error ( payload . error ?. message ?? "Host request failed" ) ;
49+ return payload . result ;
50+ }
51+
52+ private dispatchEvent ( event : unknown ) : void {
53+ if ( ! event || typeof event !== "object" ) return ;
54+ const payload = event as { id ?: unknown ; result ?: unknown ; error ?: { message ?: string } } ;
55+ if ( typeof payload . id !== "string" ) return ;
56+ const pending = this . pending . get ( payload . id ) ;
57+ if ( ! pending ) return ;
58+ this . pending . delete ( payload . id ) ;
59+ if ( payload . error ) pending . reject ( new Error ( payload . error . message ?? "Host request failed" ) ) ;
60+ else pending . resolve ( payload . result ) ;
2261 }
2362}
0 commit comments