11import type { AccountInfo , Configuration } from '@azure/msal-node' ;
22import { PublicClientApplication } from '@azure/msal-node' ;
3- import keytar from 'keytar' ;
43import logger from './logger.js' ;
54import fs , { existsSync , readFileSync } from 'fs' ;
65import { fileURLToPath } from 'url' ;
76import path from 'path' ;
87
8+ // Ok so this is a hack to lazily import keytar only when needed
9+ // since --http mode may not need it at all, and keytar can be a pain to install (looking at you alpine)
10+ let keytar : typeof import ( 'keytar' ) | null = null ;
11+ async function getKeytar ( ) {
12+ if ( keytar === undefined ) {
13+ return null ;
14+ }
15+ if ( keytar === null ) {
16+ try {
17+ keytar = await import ( 'keytar' ) ;
18+ return keytar ;
19+ } catch ( error ) {
20+ logger . info ( 'keytar not available, using file-based credential storage' ) ;
21+ keytar = undefined as any ;
22+ return null ;
23+ }
24+ }
25+ return keytar ;
26+ }
27+
928interface EndpointConfig {
1029 pathPattern : string ;
1130 method : string ;
@@ -147,9 +166,12 @@ class AuthManager {
147166 let cacheData : string | undefined ;
148167
149168 try {
150- const cachedData = await keytar . getPassword ( SERVICE_NAME , TOKEN_CACHE_ACCOUNT ) ;
151- if ( cachedData ) {
152- cacheData = cachedData ;
169+ const kt = await getKeytar ( ) ;
170+ if ( kt ) {
171+ const cachedData = await kt . getPassword ( SERVICE_NAME , TOKEN_CACHE_ACCOUNT ) ;
172+ if ( cachedData ) {
173+ cacheData = cachedData ;
174+ }
153175 }
154176 } catch ( keytarError ) {
155177 logger . warn (
@@ -177,9 +199,12 @@ class AuthManager {
177199 let selectedAccountData : string | undefined ;
178200
179201 try {
180- const cachedData = await keytar . getPassword ( SERVICE_NAME , SELECTED_ACCOUNT_KEY ) ;
181- if ( cachedData ) {
182- selectedAccountData = cachedData ;
202+ const kt = await getKeytar ( ) ;
203+ if ( kt ) {
204+ const cachedData = await kt . getPassword ( SERVICE_NAME , SELECTED_ACCOUNT_KEY ) ;
205+ if ( cachedData ) {
206+ selectedAccountData = cachedData ;
207+ }
183208 }
184209 } catch ( keytarError ) {
185210 logger . warn (
@@ -206,7 +231,12 @@ class AuthManager {
206231 const cacheData = this . msalApp . getTokenCache ( ) . serialize ( ) ;
207232
208233 try {
209- await keytar . setPassword ( SERVICE_NAME , TOKEN_CACHE_ACCOUNT , cacheData ) ;
234+ const kt = await getKeytar ( ) ;
235+ if ( kt ) {
236+ await kt . setPassword ( SERVICE_NAME , TOKEN_CACHE_ACCOUNT , cacheData ) ;
237+ } else {
238+ fs . writeFileSync ( FALLBACK_PATH , cacheData ) ;
239+ }
210240 } catch ( keytarError ) {
211241 logger . warn (
212242 `Keychain save failed, falling back to file storage: ${ ( keytarError as Error ) . message } `
@@ -224,7 +254,12 @@ class AuthManager {
224254 const selectedAccountData = JSON . stringify ( { accountId : this . selectedAccountId } ) ;
225255
226256 try {
227- await keytar . setPassword ( SERVICE_NAME , SELECTED_ACCOUNT_KEY , selectedAccountData ) ;
257+ const kt = await getKeytar ( ) ;
258+ if ( kt ) {
259+ await kt . setPassword ( SERVICE_NAME , SELECTED_ACCOUNT_KEY , selectedAccountData ) ;
260+ } else {
261+ fs . writeFileSync ( SELECTED_ACCOUNT_PATH , selectedAccountData ) ;
262+ }
228263 } catch ( keytarError ) {
229264 logger . warn (
230265 `Keychain save failed for selected account, falling back to file storage: ${ ( keytarError as Error ) . message } `
@@ -403,8 +438,11 @@ class AuthManager {
403438 this . selectedAccountId = null ;
404439
405440 try {
406- await keytar . deletePassword ( SERVICE_NAME , TOKEN_CACHE_ACCOUNT ) ;
407- await keytar . deletePassword ( SERVICE_NAME , SELECTED_ACCOUNT_KEY ) ;
441+ const kt = await getKeytar ( ) ;
442+ if ( kt ) {
443+ await kt . deletePassword ( SERVICE_NAME , TOKEN_CACHE_ACCOUNT ) ;
444+ await kt . deletePassword ( SERVICE_NAME , SELECTED_ACCOUNT_KEY ) ;
445+ }
408446 } catch ( keytarError ) {
409447 logger . warn ( `Keychain deletion failed: ${ ( keytarError as Error ) . message } ` ) ;
410448 }
0 commit comments