@@ -49,6 +49,13 @@ export async function loadConfidentialClient({
4949 } ;
5050}
5151
52+ /** Human-facing approve URL (SPA /activate — not POST /api/oauth2/device/verify). */
53+ export function deviceCodeActivateUrl ( baseUrl , userCode ) {
54+ const code = String ( userCode || '' ) . trim ( ) ;
55+ const base = String ( baseUrl || 'https://agentstack.tech' ) . replace ( / \/ $ / , '' ) ;
56+ return `${ base } /activate?user_code=${ encodeURIComponent ( code ) } ` ;
57+ }
58+
5259/**
5360 * POST application/x-www-form-urlencoded.
5461 * OAuth token endpoints often return HTTP 400 with `{ error: "authorization_pending" }` —
@@ -83,7 +90,8 @@ export async function pollDeviceToken({
8390 clientSecret = null ,
8491} ) {
8592 const deadline = Date . now ( ) + expiresInSec * 1000 ;
86- let waitMs = Math . max ( 1000 , intervalSec * 1000 ) ;
93+ // Prod oauth2_token RL is 5 req/min per IP — stay under that even when server interval=5.
94+ let waitMs = Math . max ( 12000 , intervalSec * 1000 ) ;
8795
8896 while ( Date . now ( ) < deadline ) {
8997 await new Promise ( ( r ) => setTimeout ( r , waitMs ) ) ;
@@ -93,7 +101,17 @@ export async function pollDeviceToken({
93101 client_id : clientId ,
94102 } ;
95103 if ( clientSecret ) params . client_secret = clientSecret ;
96- const token = await postForm ( tokenUrl , params , traceId ) ;
104+ let token ;
105+ try {
106+ token = await postForm ( tokenUrl , params , traceId ) ;
107+ } catch ( err ) {
108+ const msg = String ( err ?. message || err ) ;
109+ if ( msg . includes ( 'HTTP 429' ) ) {
110+ waitMs = Math . min ( waitMs + 5000 , 30000 ) ;
111+ continue ;
112+ }
113+ throw err ;
114+ }
97115 if ( token . access_token ) return token ;
98116 if ( token . error === 'authorization_pending' ) continue ;
99117 if ( token . error === 'slow_down' ) {
0 commit comments