diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index a892e34a5a..4cf3f3b6e7 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -32,7 +32,6 @@ import { storeModuleConfig } from './app.reducer'; import { LocaleService } from './core/locale/locale.service'; import { authReducer } from './core/auth/auth.reducer'; import { provideMockStore } from '@ngrx/store/testing'; -import { GoogleAnalyticsService } from './statistics/google-analytics.service'; import { ThemeService } from './shared/theme-support/theme.service'; import { getMockThemeService } from './shared/mocks/theme-service.mock'; import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; @@ -46,16 +45,16 @@ const initialState = { core: { auth: { loading: false } } }; +export function getMockLocaleService(): LocaleService { + return jasmine.createSpyObj('LocaleService', { + setCurrentLanguageCode: jasmine.createSpy('setCurrentLanguageCode') + }); +} + describe('App component', () => { let breadcrumbsServiceSpy; - function getMockLocaleService(): LocaleService { - return jasmine.createSpyObj('LocaleService', { - setCurrentLanguageCode: jasmine.createSpy('setCurrentLanguageCode') - }); - } - const getDefaultTestBedConf = () => { breadcrumbsServiceSpy = jasmine.createSpyObj(['listenForRouteChanges']); @@ -131,39 +130,6 @@ describe('App component', () => { }); - describe('the constructor', () => { - it('should call breadcrumbsService.listenForRouteChanges', () => { - expect(breadcrumbsServiceSpy.listenForRouteChanges).toHaveBeenCalledTimes(1); - }); - }); - - describe('when GoogleAnalyticsService is provided', () => { - let googleAnalyticsSpy; - - beforeEach(() => { - // NOTE: Cannot override providers once components have been compiled, so TestBed needs to be reset - TestBed.resetTestingModule(); - TestBed.configureTestingModule(getDefaultTestBedConf()); - googleAnalyticsSpy = jasmine.createSpyObj('googleAnalyticsService', [ - 'addTrackingIdToPage', - ]); - TestBed.overrideProvider(GoogleAnalyticsService, {useValue: googleAnalyticsSpy}); - fixture = TestBed.createComponent(AppComponent); - comp = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create component', () => { - expect(comp).toBeTruthy(); - }); - - describe('the constructor', () => { - it('should call googleAnalyticsService.addTrackingIdToPage()', () => { - expect(googleAnalyticsSpy.addTrackingIdToPage).toHaveBeenCalledTimes(1); - }); - }); - }); - describe('when ThemeService returns a custom theme', () => { let document; let headSpy; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index ea2fb9fde6..c7b99b42a8 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -18,36 +18,24 @@ import { Router, } from '@angular/router'; -import { isEqual } from 'lodash'; -import { BehaviorSubject, Observable, of } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { select, Store } from '@ngrx/store'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateService } from '@ngx-translate/core'; -import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; - -import { MetadataService } from './core/metadata/metadata.service'; import { HostWindowResizeAction } from './shared/host-window.actions'; import { HostWindowState } from './shared/search/host-window.reducer'; import { NativeWindowRef, NativeWindowService } from './core/services/window.service'; import { isAuthenticationBlocking } from './core/auth/selectors'; import { AuthService } from './core/auth/auth.service'; import { CSSVariableService } from './shared/sass-helper/sass-helper.service'; -import { MenuService } from './shared/menu/menu.service'; -import { HostWindowService } from './shared/host-window.service'; -import { HeadTagConfig, ThemeConfig } from '../config/theme.model'; -import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider'; +import { HeadTagConfig } from '../config/theme.model'; import { environment } from '../environments/environment'; import { models } from './core/core.module'; -import { LocaleService } from './core/locale/locale.service'; import { hasNoValue, hasValue, isNotEmpty } from './shared/empty.util'; -import { KlaroService } from './shared/cookies/klaro.service'; -import { GoogleAnalyticsService } from './statistics/google-analytics.service'; import { ThemeService } from './shared/theme-support/theme.service'; import { BASE_THEME_NAME } from './shared/theme-support/theme.constants'; -import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; import { IdleModalComponent } from './shared/idle-modal/idle-modal.component'; import { getDefaultThemeConfig } from '../config/config.util'; -import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface'; @Component({ selector: 'ds-app', @@ -56,11 +44,6 @@ import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent implements OnInit, AfterViewInit { - sidebarVisible: Observable; - slideSidebarOver: Observable; - collapsedSidebarWidth: Observable; - totalSidebarWidth: Observable; - theme: Observable = of({} as any); notificationOptions; models; @@ -90,29 +73,14 @@ export class AppComponent implements OnInit, AfterViewInit { @Inject(NativeWindowService) private _window: NativeWindowRef, @Inject(DOCUMENT) private document: any, @Inject(PLATFORM_ID) private platformId: any, - @Inject(APP_CONFIG) private appConfig: AppConfig, private themeService: ThemeService, private translate: TranslateService, private store: Store, - private metadata: MetadataService, - private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics, - private angulartics2DSpace: Angulartics2DSpace, private authService: AuthService, private router: Router, private cssService: CSSVariableService, - private menuService: MenuService, - private windowService: HostWindowService, - private localeService: LocaleService, - private breadcrumbsService: BreadcrumbsService, private modalService: NgbModal, - @Optional() private cookiesService: KlaroService, - @Optional() private googleAnalyticsService: GoogleAnalyticsService, ) { - - if (!isEqual(environment, this.appConfig)) { - throw new Error('environment does not match app config!'); - } - this.notificationOptions = environment.notifications; /* Use models object so all decorators are actually called */ @@ -136,47 +104,18 @@ export class AppComponent implements OnInit, AfterViewInit { }); if (isPlatformBrowser(this.platformId)) { - this.authService.trackTokenExpiration(); this.trackIdleModal(); } - // Load all the languages that are defined as active from the config file - translate.addLangs(environment.languages.filter((LangConfig) => LangConfig.active === true).map((a) => a.code)); - - // Load the default language from the config file - // translate.setDefaultLang(environment.defaultLanguage); - - // set the current language code - this.localeService.setCurrentLanguageCode(); - - // analytics - if (hasValue(googleAnalyticsService)) { - googleAnalyticsService.addTrackingIdToPage(); - } - angulartics2DSpace.startTracking(); - - metadata.listenForRouteChange(); - breadcrumbsService.listenForRouteChanges(); - - if (environment.debug) { - console.info(environment); - } this.storeCSSVariables(); } ngOnInit() { - this.isAuthBlocking$ = this.store.pipe(select(isAuthenticationBlocking)).pipe( + this.isAuthBlocking$ = this.store.pipe( + select(isAuthenticationBlocking), distinctUntilChanged() ); - this.isAuthBlocking$ - .pipe( - filter((isBlocking: boolean) => isBlocking === false), - take(1) - ).subscribe(() => this.initializeKlaro()); - const env: string = environment.production ? 'Production' : 'Development'; - const color: string = environment.production ? 'red' : 'green'; - console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`); this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight); } @@ -239,12 +178,6 @@ export class AppComponent implements OnInit, AfterViewInit { ); } - private initializeKlaro() { - if (hasValue(this.cookiesService)) { - this.cookiesService.initialize(); - } - } - private loadGlobalThemeConfig(themeName: string): void { this.setThemeCss(themeName); this.setHeadTags(themeName); diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 5738948ebd..95b20a1142 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -4,7 +4,7 @@ import { HttpHeaders } from '@angular/common/http'; import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens'; import { Observable, of as observableOf } from 'rxjs'; -import { map, startWith, switchMap, take } from 'rxjs/operators'; +import { filter, map, startWith, switchMap, take } from 'rxjs/operators'; import { select, Store } from '@ngrx/store'; import { CookieAttributes } from 'js-cookie'; @@ -88,6 +88,8 @@ export class AuthService { private translateService: TranslateService ) { this.store.pipe( + // when this service is constructed the store is not fully initialized yet + filter((state: any) => state?.core?.auth !== undefined), select(isAuthenticated), startWith(false) ).subscribe((authenticated: boolean) => this._authenticated = authenticated); @@ -325,7 +327,7 @@ export class AuthService { let token: AuthTokenInfo; let currentlyRefreshingToken = false; this.store.pipe(select(getAuthenticationToken)).subscribe((authTokenInfo: AuthTokenInfo) => { - // If new token is undefined an it wasn't previously => Refresh failed + // If new token is undefined and it wasn't previously => Refresh failed if (currentlyRefreshingToken && token !== undefined && authTokenInfo === undefined) { // Token refresh failed => Error notification => 10 second wait => Page reloads & user logged out this.notificationService.error(this.translateService.get('auth.messages.token-refresh-failed')); diff --git a/src/app/init.service.spec.ts b/src/app/init.service.spec.ts index 7bbd50e4b7..7fb555f23c 100644 --- a/src/app/init.service.spec.ts +++ b/src/app/init.service.spec.ts @@ -1,12 +1,42 @@ import { InitService } from './init.service'; import { APP_CONFIG } from 'src/config/app-config.interface'; -import { APP_INITIALIZER } from '@angular/core'; +import { APP_INITIALIZER, Injectable } from '@angular/core'; +import { inject, TestBed, waitForAsync } from '@angular/core/testing'; +import { GoogleAnalyticsService } from './statistics/google-analytics.service'; +import { MetadataService } from './core/metadata/metadata.service'; +import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; +import { CommonModule } from '@angular/common'; +import { Store, StoreModule } from '@ngrx/store'; +import { authReducer } from './core/auth/auth.reducer'; +import { storeModuleConfig } from './app.reducer'; +import { AngularticsProviderMock } from './shared/mocks/angulartics-provider.service.mock'; +import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider'; +import { AuthService } from './core/auth/auth.service'; +import { AuthServiceMock } from './shared/mocks/auth.service.mock'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RouterMock } from './shared/mocks/router.mock'; +import { MockActivatedRoute } from './shared/mocks/active-router.mock'; +import { MenuService } from './shared/menu/menu.service'; +import { LocaleService } from './core/locale/locale.service'; +import { environment } from '../environments/environment'; +import { provideMockStore } from '@ngrx/store/testing'; +import { AppComponent } from './app.component'; +import { RouteService } from './core/services/route.service'; +import { getMockLocaleService } from './app.component.spec'; +import { MenuServiceStub } from './shared/testing/menu-service.stub'; +import { CorrelationIdService } from './correlation-id/correlation-id.service'; +import { DSpaceTransferState } from '../modules/transfer-state/dspace-transfer-state.service'; +import { KlaroService } from './shared/cookies/klaro.service'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoaderMock } from './shared/mocks/translate-loader.mock'; +import { getTestScheduler } from 'jasmine-marbles'; import objectContaining = jasmine.objectContaining; import createSpyObj = jasmine.createSpyObj; import SpyObj = jasmine.SpyObj; let spy: SpyObj; +@Injectable() export class ConcreteInitServiceMock extends InitService { protected static resolveAppConfig() { spy.resolveAppConfig(); @@ -18,6 +48,15 @@ export class ConcreteInitServiceMock extends InitService { } } +const initialState = { + core: { + auth: { + loading: false, + blocking: true, + } + } +}; + describe('InitService', () => { describe('providers', () => { @@ -79,5 +118,156 @@ describe('InitService', () => { expect(spy.init).toHaveBeenCalled(); }); }); + + describe('common initialization steps', () => { + let correlationIdServiceSpy; + let dspaceTransferStateSpy; + let transferStateSpy; + let metadataServiceSpy; + let breadcrumbsServiceSpy; + + beforeEach(waitForAsync(() => { + correlationIdServiceSpy = jasmine.createSpyObj('correlationIdServiceSpy', [ + 'initCorrelationId', + ]); + dspaceTransferStateSpy = jasmine.createSpyObj('dspaceTransferStateSpy', [ + 'transfer', + ]); + transferStateSpy = jasmine.createSpyObj('dspaceTransferStateSpy', [ + 'get', 'hasKey' + ]); + breadcrumbsServiceSpy = jasmine.createSpyObj('breadcrumbsServiceSpy', [ + 'listenForRouteChanges', + ]); + metadataServiceSpy = jasmine.createSpyObj('metadataService', [ + 'listenForRouteChange', + ]); + + + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + imports: [ + CommonModule, + StoreModule.forRoot(authReducer, storeModuleConfig), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + providers: [ + { provide: InitService, useClass: ConcreteInitServiceMock }, + { provide: CorrelationIdService, useValue: correlationIdServiceSpy }, + { provide: DSpaceTransferState, useValue: dspaceTransferStateSpy }, + { provide: APP_CONFIG, useValue: environment }, + { provide: LocaleService, useValue: getMockLocaleService() }, + { provide: Angulartics2DSpace, useValue: new AngularticsProviderMock() }, + { provide: MetadataService, useValue: metadataServiceSpy }, + { provide: BreadcrumbsService, useValue: breadcrumbsServiceSpy }, + { provide: AuthService, useValue: new AuthServiceMock() }, + { provide: Router, useValue: new RouterMock() }, + { provide: ActivatedRoute, useValue: new MockActivatedRoute() }, + { provide: MenuService, useValue: new MenuServiceStub() }, + { provide: KlaroService, useValue: undefined }, + { provide: GoogleAnalyticsService, useValue: undefined }, + provideMockStore({ initialState }), + AppComponent, + RouteService, + ] + }); + })); + + describe('initÀnalytics', () => { + describe('when GoogleAnalyticsService is provided', () => { + let googleAnalyticsSpy; + + beforeEach(() => { + googleAnalyticsSpy = jasmine.createSpyObj('googleAnalyticsService', [ + 'addTrackingIdToPage', + ]); + + TestBed.overrideProvider(GoogleAnalyticsService, { useValue: googleAnalyticsSpy }); + }); + + it('should call googleAnalyticsService.addTrackingIdToPage()', inject([InitService], (service) => { + // @ts-ignore + service.initAnalytics(); + expect(googleAnalyticsSpy.addTrackingIdToPage).toHaveBeenCalledTimes(1); + })); + }); + }); + + describe('initRouteListeners', () => { + it('should call listenForRouteChanges', inject([InitService], (service) => { + // @ts-ignore + service.initRouteListeners(); + expect(metadataServiceSpy.listenForRouteChange).toHaveBeenCalledTimes(1); + expect(breadcrumbsServiceSpy.listenForRouteChanges).toHaveBeenCalledTimes(1); + })); + }); + + describe('initKlaro', () => { + const BLOCKING = { + t: { core: { auth: { blocking: true } } }, + f: { core: { auth: { blocking: false } } }, + }; + + it('should not error out if KlaroService is not provided', inject([InitService], (service) => { + // @ts-ignore + service.initKlaro(); + })); + + describe('when KlaroService is provided', () => { + let klaroServiceSpy; + + beforeEach(() => { + klaroServiceSpy = jasmine.createSpyObj('klaroServiceSpy', [ + 'initialize', + ]); + + TestBed.overrideProvider(KlaroService, { useValue: klaroServiceSpy }); + }); + + it('should not initialize Klaro while auth is blocking', () => { + getTestScheduler().run(({ cold, flush}) => { + TestBed.overrideProvider(Store, { useValue: cold('t--t--t--', BLOCKING) }); + const service = TestBed.inject(InitService); + + // @ts-ignore + service.initKlaro(); + flush(); + expect(klaroServiceSpy.initialize).not.toHaveBeenCalled(); + }); + }); + + + it('should only initialize Klaro the first time auth is unblocked', () => { + getTestScheduler().run(({ cold, flush}) => { + TestBed.overrideProvider(Store, { useValue: cold('t--t--f--t--f--', BLOCKING) }); + const service = TestBed.inject(InitService); + + // @ts-ignore + service.initKlaro(); + flush(); + expect(klaroServiceSpy.initialize).toHaveBeenCalledTimes(1); + }); + }); + }); + + describe('when KlaroService is not provided', () => { + it('should not error out when auth is unblocked', () => { + getTestScheduler().run(({ cold, flush}) => { + TestBed.overrideProvider(Store, { useValue: cold('t--t--f--t--f--', BLOCKING) }); + const service = TestBed.inject(InitService); + + // @ts-ignore + service.initKlaro(); + flush(); + }); + }); + }); + }); + }); }); diff --git a/src/app/init.service.ts b/src/app/init.service.ts index d8ecf8d23a..e5b04163c0 100644 --- a/src/app/init.service.ts +++ b/src/app/init.service.ts @@ -5,25 +5,50 @@ * * http://www.dspace.org/license/ */ -import { Store } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; import { CheckAuthenticationTokenAction } from './core/auth/auth.actions'; import { CorrelationIdService } from './correlation-id/correlation-id.service'; import { DSpaceTransferState } from '../modules/transfer-state/dspace-transfer-state.service'; -import { APP_INITIALIZER, Provider, Type } from '@angular/core'; +import { APP_INITIALIZER, Inject, Optional, Provider, Type } from '@angular/core'; import { TransferState } from '@angular/platform-browser'; -import { APP_CONFIG } from '../config/app-config.interface'; +import { APP_CONFIG, AppConfig } from '../config/app-config.interface'; import { environment } from '../environments/environment'; import { AppState } from './app.reducer'; +import { isEqual } from 'lodash'; +import { TranslateService } from '@ngx-translate/core'; +import { LocaleService } from './core/locale/locale.service'; +import { hasValue } from './shared/empty.util'; +import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider'; +import { GoogleAnalyticsService } from './statistics/google-analytics.service'; +import { MetadataService } from './core/metadata/metadata.service'; +import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; +import { distinctUntilChanged, filter, take, tap } from 'rxjs/operators'; +import { isAuthenticationBlocking } from './core/auth/selectors'; +import { KlaroService } from './shared/cookies/klaro.service'; /** * Performs the initialization of the app. + * * Should be extended to implement server- & browser-specific functionality. + * Initialization steps shared between the server and brower implementations + * can be included in this class. + * + * Note that the service cannot (indirectly) depend on injection tokens that are only available _after_ APP_INITIALIZER. + * For example, NgbModal depends on ApplicationRef and can therefore not be used during initialization. */ export abstract class InitService { protected constructor( protected store: Store, protected correlationIdService: CorrelationIdService, protected dspaceTransferState: DSpaceTransferState, + @Inject(APP_CONFIG) protected appConfig: AppConfig, + protected translate: TranslateService, + protected localeService: LocaleService, + protected angulartics2DSpace: Angulartics2DSpace, + @Optional() protected googleAnalyticsService: GoogleAnalyticsService, + protected metadata: MetadataService, + protected breadcrumbsService: BreadcrumbsService, + @Optional() protected klaroService: KlaroService, ) { } @@ -86,15 +111,108 @@ export abstract class InitService { // Common initialization steps + /** + * Dispatch a {@link CheckAuthenticationTokenAction} to start off the chain of + * actions used to determine whether a user is already logged in. + * @protected + */ protected checkAuthenticationToken(): void { this.store.dispatch(new CheckAuthenticationTokenAction()); } + /** + * Initialize the correlation ID (from cookie, NgRx store or random) + * @protected + */ protected initCorrelationId(): void { this.correlationIdService.initCorrelationId(); } + /** + * Transfer the application's NgRx state between server-side and client-side + * @protected + */ protected async transferAppState(): Promise { return this.dspaceTransferState.transfer(); } + + /** + * Make sure the {@link environment} matches {@link APP_CONFIG} and print + * some information about it to the console + * @protected + */ + protected checkEnvironment(): void { + if (!isEqual(environment, this.appConfig)) { + throw new Error('environment does not match app config!'); + } + + if (environment.debug) { + console.info(environment); + } + + const env: string = environment.production ? 'Production' : 'Development'; + const color: string = environment.production ? 'red' : 'green'; + console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`); + } + + /** + * Initialize internationalization services + * - Specify the active languages + * - Set the current locale + * @protected + */ + protected initI18n(): void { + // Load all the languages that are defined as active from the config file + this.translate.addLangs( + environment.languages + .filter((LangConfig) => LangConfig.active === true) + .map((a) => a.code) + ); + + // Load the default language from the config file + // translate.setDefaultLang(environment.defaultLanguage); + + this.localeService.setCurrentLanguageCode(); + } + + /** + * Initialize analytics services + * - Angulartics + * - Google Analytics (if enabled) + * @protected + */ + protected initAnalytics(): void { + if (hasValue(this.googleAnalyticsService)) { + this.googleAnalyticsService.addTrackingIdToPage(); + } + this.angulartics2DSpace.startTracking(); + } + + /** + * Start route-listening subscriptions + * - {@link MetadataService.listenForRouteChange} + * - {@link BreadcrumbsService.listenForRouteChanges} + * @protected + */ + protected initRouteListeners(): void { + this.metadata.listenForRouteChange(); + this.breadcrumbsService.listenForRouteChanges(); + } + + /** + * Initialize Klaro (if enabled) + * @protected + */ + protected initKlaro() { + if (hasValue(this.klaroService)) { + this.store.pipe( + select(isAuthenticationBlocking), + distinctUntilChanged(), + filter((isBlocking: boolean) => isBlocking === false), + take(1) + ).subscribe(() => { + this.klaroService.initialize(); + }); + } + } } diff --git a/src/modules/app/browser-init.service.ts b/src/modules/app/browser-init.service.ts index 5c55795383..f675c55718 100644 --- a/src/modules/app/browser-init.service.ts +++ b/src/modules/app/browser-init.service.ts @@ -10,12 +10,21 @@ import { Store } from '@ngrx/store'; import { AppState } from '../../app/app.reducer'; import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service'; import { TransferState } from '@angular/platform-browser'; -import { APP_CONFIG_STATE, AppConfig } from '../../config/app-config.interface'; +import { APP_CONFIG, APP_CONFIG_STATE, AppConfig } from '../../config/app-config.interface'; import { DefaultAppConfig } from '../../config/default-app-config'; import { extendEnvironmentWithAppConfig } from '../../config/config.util'; import { environment } from '../../environments/environment'; import { CorrelationIdService } from '../../app/correlation-id/correlation-id.service'; -import { Injectable } from '@angular/core'; +import { Inject, Injectable, Optional } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { LocaleService } from '../../app/core/locale/locale.service'; +import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider'; +import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service'; +import { MetadataService } from '../../app/core/metadata/metadata.service'; +import { BreadcrumbsService } from '../../app/breadcrumbs/breadcrumbs.service'; +import { CSSVariableService } from '../../app/shared/sass-helper/sass-helper.service'; +import { KlaroService } from '../../app/shared/cookies/klaro.service'; +import { AuthService } from '../../app/core/auth/auth.service'; /** * Performs client-side initialization. @@ -27,8 +36,30 @@ export class BrowserInitService extends InitService { protected correlationIdService: CorrelationIdService, protected transferState: TransferState, protected dspaceTransferState: DSpaceTransferState, + @Inject(APP_CONFIG) protected appConfig: AppConfig, + protected translate: TranslateService, + protected localeService: LocaleService, + protected angulartics2DSpace: Angulartics2DSpace, + @Optional() protected googleAnalyticsService: GoogleAnalyticsService, + protected metadata: MetadataService, + protected breadcrumbsService: BreadcrumbsService, + protected cssService: CSSVariableService, + @Optional() protected klaroService: KlaroService, + protected authService: AuthService, ) { - super(store, correlationIdService, dspaceTransferState); + super( + store, + correlationIdService, + dspaceTransferState, + appConfig, + translate, + localeService, + angulartics2DSpace, + googleAnalyticsService, + metadata, + breadcrumbsService, + klaroService, + ); } protected static resolveAppConfig( @@ -47,7 +78,22 @@ export class BrowserInitService extends InitService { this.checkAuthenticationToken(); this.initCorrelationId(); + this.checkEnvironment(); + + this.initI18n(); + this.initAnalytics(); + this.initRouteListeners(); + this.trackAuthTokenExpiration(); + + this.initKlaro(); + return true; }; } + + // Browser-only initialization steps + + private trackAuthTokenExpiration(): void { + this.authService.trackTokenExpiration(); + } } diff --git a/src/modules/app/server-init.service.ts b/src/modules/app/server-init.service.ts index 11fcc482ca..fc7fc66bf7 100644 --- a/src/modules/app/server-init.service.ts +++ b/src/modules/app/server-init.service.ts @@ -11,9 +11,17 @@ import { AppState } from '../../app/app.reducer'; import { TransferState } from '@angular/platform-browser'; import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service'; import { CorrelationIdService } from '../../app/correlation-id/correlation-id.service'; -import { APP_CONFIG_STATE, AppConfig } from '../../config/app-config.interface'; +import { APP_CONFIG, APP_CONFIG_STATE, AppConfig } from '../../config/app-config.interface'; import { environment } from '../../environments/environment'; -import { Injectable } from '@angular/core'; +import { Inject, Injectable, Optional } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { LocaleService } from '../../app/core/locale/locale.service'; +import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider'; +import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service'; +import { MetadataService } from '../../app/core/metadata/metadata.service'; +import { BreadcrumbsService } from '../../app/breadcrumbs/breadcrumbs.service'; +import { CSSVariableService } from '../../app/shared/sass-helper/sass-helper.service'; +import { KlaroService } from '../../app/shared/cookies/klaro.service'; /** * Performs server-side initialization. @@ -25,8 +33,29 @@ export class ServerInitService extends InitService { protected correlationIdService: CorrelationIdService, protected transferState: TransferState, protected dspaceTransferState: DSpaceTransferState, + @Inject(APP_CONFIG) protected appConfig: AppConfig, + protected translate: TranslateService, + protected localeService: LocaleService, + protected angulartics2DSpace: Angulartics2DSpace, + @Optional() protected googleAnalyticsService: GoogleAnalyticsService, + protected metadata: MetadataService, + protected breadcrumbsService: BreadcrumbsService, + protected cssService: CSSVariableService, + @Optional() protected klaroService: KlaroService, ) { - super(store, correlationIdService, dspaceTransferState); + super( + store, + correlationIdService, + dspaceTransferState, + appConfig, + translate, + localeService, + angulartics2DSpace, + googleAnalyticsService, + metadata, + breadcrumbsService, + klaroService, + ); } protected init(): () => Promise { @@ -36,10 +65,19 @@ export class ServerInitService extends InitService { this.transferAppState(); // todo: SSR breaks if we await this (why?) this.initCorrelationId(); + this.checkEnvironment(); + this.initI18n(); + this.initAnalytics(); + this.initRouteListeners(); + + this.initKlaro(); + return true; }; } + // Server-only initialization steps + private saveAppConfigForCSR(): void { this.transferState.set(APP_CONFIG_STATE, environment as AppConfig); }