@@ -48,6 +48,12 @@ export type DeviceCodeAuthResult = {
4848 appId : string ;
4949} ;
5050
51+ export type DeviceCodeAuthDisplayOptions = {
52+ openBrowser ?: boolean ;
53+ promptOnly ?: boolean ;
54+ onPrompt ?: ( message : string ) => void | Promise < void > ;
55+ } ;
56+
5157const UUID_CLIENT_ID_REGEX = / ^ [ 0 - 9 a - f ] { 8 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 12 } $ / i;
5258export class DeviceCodeAuthProvider {
5359 constructor (
@@ -56,7 +62,7 @@ export class DeviceCodeAuthProvider {
5662 private readonly logger : Logger = new Logger ( "DeviceCodeAuthProvider" ) ,
5763 ) { }
5864
59- async authenticate ( ) : Promise < DeviceCodeAuthResult > {
65+ async authenticate ( displayOptions : DeviceCodeAuthDisplayOptions = { } ) : Promise < DeviceCodeAuthResult > {
6066 if ( ! this . stamper . getKeyInfo ( ) ) {
6167 await this . stamper . init ( ) ;
6268 }
@@ -72,7 +78,15 @@ export class DeviceCodeAuthProvider {
7278 const nonce = await _deriveNonce ( keyPair , "" ) ;
7379 const deviceAuth = await this . requestDeviceCode ( clientConfig . client_id , nonce ) ;
7480
75- await this . displayDeviceCode ( deviceAuth , clientConfig . client_id , keyInfo . publicKey ) ;
81+ const promptText = await this . displayDeviceCode (
82+ deviceAuth ,
83+ clientConfig . client_id ,
84+ keyInfo . publicKey ,
85+ displayOptions ,
86+ ) ;
87+ if ( displayOptions . promptOnly && promptText ) {
88+ throw new Error ( promptText ) ;
89+ }
7690
7791 const tokens = await this . pollForTokens (
7892 clientConfig . client_id ,
@@ -198,26 +212,29 @@ export class DeviceCodeAuthProvider {
198212 deviceAuth : DeviceAuthorizationResponse ,
199213 clientId : string ,
200214 publicKey : string ,
201- ) : Promise < void > {
215+ displayOptions : DeviceCodeAuthDisplayOptions = { } ,
216+ ) : Promise < string | null > {
202217 const { user_code } = deviceAuth ;
203218 const urlToOpen = `${ this . options . connectBaseUrl } /device-connect?user_code=${ encodeURIComponent ( user_code ) } &client_id=${ encodeURIComponent ( clientId ) } &public_key=${ encodeURIComponent ( publicKey ) } ` ;
204219 this . logger . info ( `Generated device connect URL: ${ urlToOpen } ` ) ;
205220
206221 let browserOpened = false ;
207- try {
208- await new Promise < void > ( ( resolve , reject ) => {
209- if ( process . platform === "win32" ) {
210- execFile ( "cmd" , [ "/c" , "start" , "" , urlToOpen ] , err => ( err ? reject ( err ) : resolve ( ) ) ) ;
211- } else {
212- const cmd = process . platform === "darwin" ? "open" : "xdg-open" ;
213- execFile ( cmd , [ urlToOpen ] , err => ( err ? reject ( err ) : resolve ( ) ) ) ;
214- }
215- } ) ;
216- browserOpened = true ;
217- this . logger . info ( `Browser opened for device authorization: ${ urlToOpen } ` ) ;
218- } catch ( error ) {
219- const msg = error instanceof Error ? error . message : String ( error ) ;
220- this . logger . warn ( `Could not open browser automatically: ${ msg } ` ) ;
222+ if ( displayOptions . openBrowser !== false ) {
223+ try {
224+ await new Promise < void > ( ( resolve , reject ) => {
225+ if ( process . platform === "win32" ) {
226+ execFile ( "cmd" , [ "/c" , "start" , "" , urlToOpen ] , err => ( err ? reject ( err ) : resolve ( ) ) ) ;
227+ } else {
228+ const cmd = process . platform === "darwin" ? "open" : "xdg-open" ;
229+ execFile ( cmd , [ urlToOpen ] , err => ( err ? reject ( err ) : resolve ( ) ) ) ;
230+ }
231+ } ) ;
232+ browserOpened = true ;
233+ this . logger . info ( `Browser opened for device authorization: ${ urlToOpen } ` ) ;
234+ } catch ( error ) {
235+ const msg = error instanceof Error ? error . message : String ( error ) ;
236+ this . logger . warn ( `Could not open browser automatically: ${ msg } ` ) ;
237+ }
221238 }
222239
223240 const line = ( content : string ) => process . stderr . write ( content + "\n" ) ;
@@ -233,14 +250,27 @@ export class DeviceCodeAuthProvider {
233250 line ( "║ Waiting for approval... ║" ) ;
234251 line ( "╚══════════════════════════════════════════════╝" ) ;
235252 line ( "" ) ;
236- return ;
253+ return null ;
237254 }
238255
239256 const hyperlink = `\x1b]8;;${ urlToOpen } \x07${ urlToOpen } \x1b]8;;\x07` ;
240257 const qr = await new Promise < string > ( resolve => {
241258 qrcode . generate ( urlToOpen , { small : true } , resolve ) ;
242259 } ) ;
243260
261+ if ( displayOptions . onPrompt ) {
262+ const promptText = [
263+ "Phantom Wallet — Device Authorization" ,
264+ "" ,
265+ "Please visit this link to approve the connection:" ,
266+ urlToOpen ,
267+ "" ,
268+ `Code: ${ user_code } ` ,
269+ ] . join ( "\n" ) ;
270+ await displayOptions . onPrompt ( promptText ) ;
271+ return promptText ;
272+ }
273+
244274 line ( "" ) ;
245275 line ( "╔══════════════════════════════════════════════╗" ) ;
246276 line ( "║ Phantom Wallet — Device Authorization ║" ) ;
@@ -257,6 +287,7 @@ export class DeviceCodeAuthProvider {
257287 process . stderr . write ( qr ) ;
258288 line ( hyperlink ) ;
259289 line ( "" ) ;
290+ return null ;
260291 }
261292
262293 private async pollForTokens (
0 commit comments