@@ -47,6 +47,8 @@ export class PairingWizard extends EventEmitter {
4747 this . _app = null ;
4848 this . _pollTimer = null ;
4949 this . _qrUrl = null ;
50+ this . _connected = false ; // tracks server connectivity
51+ this . _consecutiveErrors = 0 ;
5052 }
5153
5254 /**
@@ -58,6 +60,12 @@ export class PairingWizard extends EventEmitter {
5860 * @returns {Promise<{ pin: string, port: number, url: string, qrUrl: string }> }
5961 */
6062 async start ( ) {
63+ // Guard: prevent double-start
64+ if ( this . _pollTimer || this . _initRetryTimer ) {
65+ console . warn ( '[pairing] start() called but already running — ignoring' ) ;
66+ return { pin : this . _pin , port : this . _port , url : 'http://localhost:' + this . _port , qrUrl : this . _qrUrl , connected : this . _connected } ;
67+ }
68+
6169 this . _paired = false ;
6270 this . _pairingResult = null ;
6371
@@ -87,13 +95,17 @@ export class PairingWizard extends EventEmitter {
8795 // Use server-assigned PIN and session ID
8896 this . _pin = String ( apiResult . pin ) ;
8997 this . _sessionId = apiResult . sessionId || apiResult . pairingSessionId || null ;
98+ this . _connected = true ;
99+ this . _consecutiveErrors = 0 ;
90100 console . log ( '[pairing] Using server PIN: ' + this . _pin + ' (session=' + this . _sessionId + ')' ) ;
91101 } else {
92- // Fallback: generate local PIN (won't work without API registration,
93- // but at least shows something to the user)
94- this . _pin = _generatePin ( ) ;
102+ // API unreachable — no valid PIN yet
103+ this . _pin = '------' ;
95104 this . _sessionId = null ;
96- console . warn ( '[pairing] Using local PIN (no API session): ' + this . _pin ) ;
105+ this . _connected = false ;
106+ console . warn ( '[pairing] API unreachable — will retry' ) ;
107+ // Start retry loop to get a valid session
108+ this . _startInitRetry ( ) ;
97109 }
98110
99111 // Build QR deep link URL
@@ -120,8 +132,9 @@ export class PairingWizard extends EventEmitter {
120132 port : port ,
121133 url : 'http://localhost:' + port ,
122134 qrUrl : this . _qrUrl ,
135+ connected : this . _connected ,
123136 } ;
124- this . emit ( 'started' , { pin : this . _pin , port : port , qrUrl : this . _qrUrl } ) ;
137+ this . emit ( 'started' , { pin : this . _pin , port : port , qrUrl : this . _qrUrl , connected : this . _connected } ) ;
125138 return info ;
126139 }
127140
@@ -134,6 +147,10 @@ export class PairingWizard extends EventEmitter {
134147 clearInterval ( this . _pollTimer ) ;
135148 this . _pollTimer = null ;
136149 }
150+ if ( this . _initRetryTimer ) {
151+ clearInterval ( this . _initRetryTimer ) ;
152+ this . _initRetryTimer = null ;
153+ }
137154
138155 if ( ! this . _server ) return ;
139156
@@ -229,6 +246,50 @@ export class PairingWizard extends EventEmitter {
229246 return uuid ;
230247 }
231248
249+ /**
250+ * Retry initPINPairing when the initial attempt failed (no connectivity).
251+ * Retries every 5s until a valid session is obtained.
252+ */
253+ _startInitRetry ( ) {
254+ if ( this . _initRetryTimer ) return ;
255+
256+ var self = this ;
257+ this . _initRetryTimer = setInterval ( async function ( ) {
258+ if ( self . _paired || self . _sessionId ) {
259+ clearInterval ( self . _initRetryTimer ) ;
260+ self . _initRetryTimer = null ;
261+ return ;
262+ }
263+
264+ try {
265+ var result = await self . _api . initPINPairing ( {
266+ uuid : self . _uuid ,
267+ deviceName : self . _deviceName ,
268+ } ) ;
269+
270+ if ( result && result . pin ) {
271+ clearInterval ( self . _initRetryTimer ) ;
272+ self . _initRetryTimer = null ;
273+
274+ self . _pin = String ( result . pin ) ;
275+ self . _sessionId = result . sessionId || result . pairingSessionId || null ;
276+ self . _qrUrl = 'https://app.allow2.com/pair?pin=' + self . _pin ;
277+ self . _connected = true ;
278+ self . _consecutiveErrors = 0 ;
279+
280+ console . log ( '[pairing] Reconnected! PIN: ' + self . _pin + ' (session=' + self . _sessionId + ')' ) ;
281+ self . emit ( 'connection-status' , { connected : true , pin : self . _pin , qrUrl : self . _qrUrl } ) ;
282+
283+ // Now start polling for parent confirmation
284+ self . _startPolling ( ) ;
285+ }
286+ } catch ( err ) {
287+ console . warn ( '[pairing] Init retry failed:' , err . message ) ;
288+ self . emit ( 'connection-status' , { connected : false } ) ;
289+ }
290+ } , 5000 ) ;
291+ }
292+
232293 /**
233294 * Start polling the Allow2 API for pairing confirmation.
234295 */
@@ -257,9 +318,23 @@ export class PairingWizard extends EventEmitter {
257318 self . _api . checkPairingStatus ( self . _sessionId ) . then ( function ( result ) {
258319 if ( result && result . paired && result . userId && result . pairId && result . pairToken ) {
259320 self . completePairing ( result ) ;
321+ return ;
322+ }
323+ // Successful poll — mark as connected
324+ if ( ! self . _connected ) {
325+ self . _connected = true ;
326+ self . _consecutiveErrors = 0 ;
327+ console . log ( '[pairing] Connection restored' ) ;
328+ self . emit ( 'connection-status' , { connected : true } ) ;
260329 }
261330 } ) . catch ( function ( err ) {
262- // Polling errors are non-fatal — just retry next interval
331+ self . _consecutiveErrors ++ ;
332+ // After 2 consecutive failures (~10s), mark as disconnected
333+ if ( self . _consecutiveErrors >= 2 && self . _connected ) {
334+ self . _connected = false ;
335+ console . warn ( '[pairing] Connection lost:' , err . message ) ;
336+ self . emit ( 'connection-status' , { connected : false } ) ;
337+ }
263338 if ( pollCount % 12 === 0 ) { // log every minute
264339 console . warn ( '[pairing] Poll error:' , err . message ) ;
265340 }
0 commit comments