@@ -4,6 +4,13 @@ import {
44} from "../../../src/gateway/events.js" ;
55import type { GatewayEventFrame , GatewayHelloOk } from "../api/gateway.ts" ;
66import type { UpdateAvailable } from "../api/types.ts" ;
7+ import {
8+ closeDevicePairSetup as closeDevicePairSetupState ,
9+ openDevicePairSetup as openDevicePairSetupState ,
10+ refreshDevicePairSetup as refreshDevicePairSetupState ,
11+ type DevicePairSetup ,
12+ type DevicePairSetupState ,
13+ } from "../lib/device-pair-setup.ts" ;
714import {
815 clearResolvedExecApprovalPrompt ,
916 dismissExecApprovalPrompt ,
@@ -31,6 +38,11 @@ export type ApplicationOverlaySnapshot = {
3138 approvalQueue : readonly ExecApprovalRequest [ ] ;
3239 approvalBusy : boolean ;
3340 approvalError : string | null ;
41+ devicePairSetupOpen : boolean ;
42+ devicePairSetupLoading : boolean ;
43+ devicePairSetupError : string | null ;
44+ devicePairSetup : DevicePairSetup | null ;
45+ devicePairPendingCount : number ;
3446} ;
3547
3648export type ApplicationOverlays = {
@@ -39,6 +51,9 @@ export type ApplicationOverlays = {
3951 runUpdate : ( ) => Promise < void > ;
4052 dismissUpdate : ( ) => void ;
4153 decideApproval : ( decision : ExecApprovalDecision ) => Promise < void > ;
54+ openDevicePairSetup : ( ) => Promise < void > ;
55+ refreshDevicePairSetup : ( ) => Promise < void > ;
56+ closeDevicePairSetup : ( ) => void ;
4257 dispose : ( ) => void ;
4358} ;
4459
@@ -186,6 +201,11 @@ export function createApplicationOverlays(gateway: ApplicationGateway): Applicat
186201 approvalQueue : [ ] ,
187202 approvalBusy : false ,
188203 approvalError : null ,
204+ devicePairSetupOpen : false ,
205+ devicePairSetupLoading : false ,
206+ devicePairSetupError : null ,
207+ devicePairSetup : null ,
208+ devicePairPendingCount : 0 ,
189209 } ;
190210 const listeners = new Set < ( next : ApplicationOverlaySnapshot ) => void > ( ) ;
191211 let disposed = false ;
@@ -195,6 +215,16 @@ export function createApplicationOverlays(gateway: ApplicationGateway): Applicat
195215 let updateRunGeneration = 0 ;
196216 let updateVerificationGeneration = 0 ;
197217 let updateVerificationTimer : ReturnType < typeof globalThis . setTimeout > | null = null ;
218+ let devicePairPendingCountGeneration = 0 ;
219+ const devicePairSetupState : DevicePairSetupState & { pendingCount : number } = {
220+ client : gateway . snapshot . client ,
221+ connected : gateway . snapshot . connected ,
222+ devicePairSetupOpen : false ,
223+ devicePairSetupLoading : false ,
224+ devicePairSetupError : null ,
225+ devicePairSetup : null ,
226+ pendingCount : 0 ,
227+ } ;
198228 const promptState : ExecApprovalPromptState = {
199229 client : activeClient ,
200230 execApprovalQueue : [ ] ,
@@ -211,13 +241,48 @@ export function createApplicationOverlays(gateway: ApplicationGateway): Applicat
211241 approvalQueue : promptState . execApprovalQueue ,
212242 approvalBusy : promptState . execApprovalBusy ,
213243 approvalError : promptState . execApprovalError ,
244+ devicePairSetupOpen : devicePairSetupState . devicePairSetupOpen ,
245+ devicePairSetupLoading : devicePairSetupState . devicePairSetupLoading ,
246+ devicePairSetupError : devicePairSetupState . devicePairSetupError ,
247+ devicePairSetup : devicePairSetupState . devicePairSetup ,
248+ devicePairPendingCount : devicePairSetupState . pendingCount ,
214249 } ;
215250 for ( const listener of listeners ) {
216251 listener ( snapshot ) ;
217252 }
218253 } ;
219254 promptState . execApprovalExpired = publish ;
220255
256+ const refreshDevicePairPendingCount = async ( ) => {
257+ const client = gateway . snapshot . client ;
258+ if (
259+ ! client ||
260+ ! gateway . snapshot . connected ||
261+ disposed ||
262+ ! devicePairSetupState . devicePairSetupOpen
263+ ) {
264+ return ;
265+ }
266+ const generation = ++ devicePairPendingCountGeneration ;
267+ let result : { pending ?: unknown } ;
268+ try {
269+ result = await client . request < { pending ?: unknown } > ( "device.pair.list" , { } ) ;
270+ } catch {
271+ return ;
272+ }
273+ if (
274+ disposed ||
275+ generation !== devicePairPendingCountGeneration ||
276+ gateway . snapshot . client !== client ||
277+ ! gateway . snapshot . connected ||
278+ ! devicePairSetupState . devicePairSetupOpen
279+ ) {
280+ return ;
281+ }
282+ devicePairSetupState . pendingCount = Array . isArray ( result . pending ) ? result . pending . length : 0 ;
283+ publish ( ) ;
284+ } ;
285+
221286 const refreshApprovals = async ( client : NonNullable < typeof activeClient > ) => {
222287 const applied = await refreshPendingApprovalQueue ( promptState , {
223288 isCurrentClient : ( requestClient ) =>
@@ -343,6 +408,13 @@ export function createApplicationOverlays(gateway: ApplicationGateway): Applicat
343408 const previousClient = activeClient ;
344409 activeClient = next . client ;
345410 promptState . client = next . client ;
411+ devicePairSetupState . client = next . client ;
412+ devicePairSetupState . connected = next . connected ;
413+ if ( previousClient !== next . client || ! next . connected ) {
414+ devicePairPendingCountGeneration += 1 ;
415+ closeDevicePairSetupState ( devicePairSetupState ) ;
416+ devicePairSetupState . pendingCount = 0 ;
417+ }
346418 if ( ! next . connected || ! next . client ) {
347419 promptState . execApprovalQueue = [ ] ;
348420 promptState . execApprovalBusy = false ;
@@ -370,6 +442,10 @@ export function createApplicationOverlays(gateway: ApplicationGateway): Applicat
370442 if ( disposed || ! isGatewayEvent ( event ) ) {
371443 return ;
372444 }
445+ if ( event . event === "device.pair.requested" || event . event === "device.pair.resolved" ) {
446+ void refreshDevicePairPendingCount ( ) ;
447+ return ;
448+ }
373449 if ( event . event === GATEWAY_EVENT_UPDATE_AVAILABLE ) {
374450 const payload = event . payload as GatewayUpdateAvailableEventPayload | undefined ;
375451 snapshot = { ...snapshot , updateAvailable : payload ?. updateAvailable ?? null } ;
@@ -538,10 +614,42 @@ export function createApplicationOverlays(gateway: ApplicationGateway): Applicat
538614 publish ( ) ;
539615 }
540616 } ,
617+ async openDevicePairSetup ( ) {
618+ if ( disposed ) {
619+ return ;
620+ }
621+ devicePairSetupState . pendingCount = 0 ;
622+ const setupOperation = openDevicePairSetupState ( devicePairSetupState ) ;
623+ const pendingCountOperation = refreshDevicePairPendingCount ( ) ;
624+ publish ( ) ;
625+ await Promise . all ( [ setupOperation , pendingCountOperation ] ) ;
626+ if ( ! disposed ) {
627+ publish ( ) ;
628+ }
629+ } ,
630+ async refreshDevicePairSetup ( ) {
631+ if ( disposed ) {
632+ return ;
633+ }
634+ const operation = refreshDevicePairSetupState ( devicePairSetupState ) ;
635+ publish ( ) ;
636+ await operation ;
637+ if ( ! disposed ) {
638+ publish ( ) ;
639+ }
640+ } ,
641+ closeDevicePairSetup ( ) {
642+ devicePairPendingCountGeneration += 1 ;
643+ closeDevicePairSetupState ( devicePairSetupState ) ;
644+ devicePairSetupState . pendingCount = 0 ;
645+ publish ( ) ;
646+ } ,
541647 dispose ( ) {
542648 disposed = true ;
543649 updateRunGeneration += 1 ;
650+ devicePairPendingCountGeneration += 1 ;
544651 cancelUpdateVerification ( ) ;
652+ closeDevicePairSetupState ( devicePairSetupState ) ;
545653 stopGateway ( ) ;
546654 stopEvents ( ) ;
547655 for ( const timer of promptState . execApprovalExpiryTimers ?. values ( ) ?? [ ] ) {
0 commit comments