Угловой APP_INITIALIZER в модуле не работает должным образом

У меня есть модуль аутентификации, который импортирует и устанавливает конфигурацию msal до инициализации приложения. Я использую APP_INITIALIZER, чтобы заблокировать запуск приложения и получить конфигурацию для MSAL-ANGULAR. Проблема только в Firefox. Приложение отлично работает в Chrome, Egde, Safari, но не в Firefox. В любом другом браузере, отличном от Firefox, APP_INITIALIZER блокирует запуск до тех пор, пока не будет загружен config.json. Я заметил, что MSALInstanceFactory вызывается перед функцией initializerFactory.

Код, который я использую в auth.module

      export function initializerFactory(env: ConfigService, configUrl: string): any {
// APP_INITIALIZER, angular doesnt starts application untill it completes loading config.json
  const promise = env.init(configUrl).then(val => {});
  return () => promise;
}

export function loggerCallback(logLevel: LogLevel, message: string): void {
   console.log(message);
}

export function MSALInstanceFactory(configService: ConfigService, constant: SecurityConstants): IPublicClientApplication {
   return new PublicClientApplication({
      auth: {
         clientId: configService.getSettings('clientId'),
         redirectUri: configService.getSettings('redirectUri'),
         authority: configService.getSettings('authority'),
      },
      cache: {
         cacheLocation: BrowserCacheLocation.LocalStorage,
         storeAuthStateInCookie: isIE,
      },
      system: {
         loggerOptions: {
            loggerCallback,
            logLevel: LogLevel.Info,
            piiLoggingEnabled: false,
         },
      },
   });
}

export function MSALInterceptorConfigFactory(configService: ConfigService, constant: SecurityConstants): MsalInterceptorConfiguration {
   const protectedResourceMap = new Map<string, Array<string>>();
   protectedResourceMap.set('/*/', [configService.getSettings('scope')]);
   return {
      interactionType: InteractionType.Redirect,
      protectedResourceMap,
   };
}

export function MSALGuardConfigFactory(configService: ConfigService, constant: SecurityConstants): MsalGuardConfiguration {
   return {
      interactionType: InteractionType.Redirect,
      authRequest: {
         scopes: [configService.getSettings('scope')],
      },
   };
}

@NgModule({
   imports: [MsalModule]
})
export class AuthModule {
   static forRoot(configFile: string) {
      return {
         ngModule: AuthModule,
         providers: [
            ConfigService,
            MsalService,
            { provide: AUTH_CONFIG_URL_TOKEN, useValue: configFile },
            {
               provide: APP_INITIALIZER,
               useFactory: initializerFactory,
               deps: [ConfigService, AUTH_CONFIG_URL_TOKEN, SecurityConstants],
               multi: true,
            },
            {
               provide: HTTP_INTERCEPTORS,
               useClass: MsalInterceptor,
               multi: true,
            },
            {
               provide: MSAL_INSTANCE,
               useFactory: MSALInstanceFactory, // this is initiated before APP_INITIALIZER 
               deps: [ConfigService, SecurityConstants],
            },
            {
               provide: MSAL_GUARD_CONFIG,
               useFactory: MSALGuardConfigFactory,
               deps: [ConfigService, SecurityConstants],
            },
            {
               provide: MSAL_INTERCEPTOR_CONFIG,
               useFactory: MSALInterceptorConfigFactory,
               deps: [ConfigService, SecurityConstants],
            },
            MsalGuard,
            MsalBroadcastService,
         ],
      };
   }
}

Код в ConfigService

         @Injectable({
       providedIn: 'root',
    })
    export class ConfigService {
       config: any;
       private http: HttpClient;
    
       constructor(private readonly httpHandler: HttpBackend) {
          this.http = new HttpClient(httpHandler);
       }
    
       init(endpoint: string): Promise<boolean> {
          let promise = new Promise<boolean>((resolve, reject) => {
             this.http
                .get<ConfigResponse>(endpoint)
                .pipe(map((res) => res))
                .subscribe(
                   (value) => {
                         this.config = value;
                   },
                   (error) => {
                      reject(false);
                   },
                   () => {
                      resolve(true);
                   }
                );
          });
             return promise;
       }
    
       getSettings(key?: string | Array<string>): any {
          if (!key || (Array.isArray(key) && !key[0])) {
             return this.config;
          }
    
          if (!Array.isArray(key)) {
             key = key.split('.');
          }
    
          let result = key.reduce((acc: any, current: string) => acc && acc[current], this.config);
    
          return result;
       }
    }

Код в app.module

      @NgModule({
   declarations: [AppComponent],
   imports: [
      AuthModule.forRoot('assets/config.json'),
      CommonModule,
      BrowserModule,
      BrowserAnimationsModule,
      HttpClientModule,
      RouterModule
   ],
   bootstrap: [AppComponent, MsalRedirectComponent],
})
export class AppModule {}

2 ответа

Я обнаружил, что APP_INITIALIZER не гарантирует, что ваша служба конфигурации завершится первой. Об этой проблеме также сообщается здесь: https://github.com/angular/angular/issues/23279, которая, к сожалению, до сих пор не решена. Первоначально перед этим загружается MSAL_Instance, и для решения этой проблемы я изменил модуль начальной загрузки main.ts.

          // main.ts
function bootstrapFailed(val) {
    document.getElementById('bootstrap-fail').style.display = 'block';
    console.error('bootstrap-fail', val);
}

fetch('config.json')
    .then(response => response.json())
    .then(config => {
        if (!config || !config['isConfig']) {
            bootstrapFailed(config);
            return;
        }

   // Store the response somewhere that your ConfigService can read it.
   window['tempConfigStorage'] = config;

    platformBrowserDynamic()
            .bootstrapModule(AppModule)
            .catch(bootstrapFailed);
    })
    .catch(bootstrapFailed);

У меня также были проблемы с APP_INITIALIZER.

Я обнаружил, что если я заменю эту строку app-routing.module (это было упомянуто в документации )

      initialNavigation: !isIframe ? 'enabled' : 'disabled'

с этим

      initialNavigation: !isIframe ? 'enabledNonBlocking' : 'disabled'

проблема устранена!

Вы также можете использовать BrowserUtils msal вместо переменной isIframe.

      initialNavigation: !BrowserUtils.isInIframe() && !BrowserUtils.isInPopup() ? 'enabledNonBlocking' : 'disabled'
Другие вопросы по тегам