diff --git a/libs/angular-integration-interface/src/index.ts b/libs/angular-integration-interface/src/index.ts index 65de6cd6..8b01a8b5 100644 --- a/libs/angular-integration-interface/src/index.ts +++ b/libs/angular-integration-interface/src/index.ts @@ -5,6 +5,7 @@ export * from './lib/services/user.service' export * from './lib/services/portal-message.service' export * from './lib/services/theme.service' export * from './lib/services/remote-components.service' +export * from './lib/services/initialize-module-guard.service' // models export * from './lib/model/config-key.model' @@ -14,5 +15,6 @@ export * from './lib/api/iauth.service' export * from './lib/api/injection-tokens' // utils +export * from './lib/utils/add-initialize-module-guard.utils' export { MfeInfo, Theme } from '@onecx/integration-interface' diff --git a/libs/angular-integration-interface/src/lib/api/injection-tokens.ts b/libs/angular-integration-interface/src/lib/api/injection-tokens.ts index 17a2a49f..3e323273 100644 --- a/libs/angular-integration-interface/src/lib/api/injection-tokens.ts +++ b/libs/angular-integration-interface/src/lib/api/injection-tokens.ts @@ -15,6 +15,11 @@ export interface LibConfig { } export const APP_CONFIG = new InjectionToken('APP_CONFIG') +/** + * @deprecated + * Please do not inject the auth service at all. + * There are better ways to achieve what you want. Please use permission service or user service. + */ export const AUTH_SERVICE = new InjectionToken('AUTH_SERVICE') export const SANITY_CHECK = new InjectionToken('OCXSANITY_CHECK') diff --git a/libs/angular-integration-interface/src/lib/services/initialize-module-guard.service.ts b/libs/angular-integration-interface/src/lib/services/initialize-module-guard.service.ts new file mode 100644 index 00000000..e16ed84b --- /dev/null +++ b/libs/angular-integration-interface/src/lib/services/initialize-module-guard.service.ts @@ -0,0 +1,66 @@ +import { Injectable } from '@angular/core' +import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree } from '@angular/router' +import { TranslateService } from '@ngx-translate/core' +import { filter, from, isObservable, map, mergeMap, Observable, of, tap, zip } from 'rxjs' +import { AppStateService } from './app-state.service'; +import { ConfigurationService } from './configuration.service'; +import { UserService } from './user.service'; + +@Injectable({ providedIn: 'any' }) +export class InitializeModuleGuard implements CanActivate { + private SUPPORTED_LANGS = ['en', 'de'] + private DEFAULT_LANG = 'en' + constructor( + protected translateService: TranslateService, + protected configService: ConfigurationService, + protected appStateService: AppStateService, + protected userService: UserService + ) {} + + canActivate( + _route: ActivatedRouteSnapshot, + _state: RouterStateSnapshot + ): Observable | Promise | boolean | UrlTree { + console.time('InitializeModuleGuard') + return zip([ + this.loadTranslations(), + from(this.configService.isInitialized), + from(this.userService.isInitialized), + from(this.appStateService.currentWorkspace$.isInitialized), + ]).pipe( + tap(() => { + console.timeEnd('InitializeModuleGuard') + }), + map(() => true) + ) + } + + getBestMatchLanguage(lang: string) { + if (this.SUPPORTED_LANGS.includes(lang)) { + return lang + } else { + console.log(`${lang} is not supported. Using ${this.DEFAULT_LANG} as a fallback.`) + } + return this.DEFAULT_LANG + } + + loadTranslations(): Observable { + return this.userService.lang$.pipe( + filter((v) => v !== undefined), + mergeMap((lang) => { + const bestMatchLang = this.getBestMatchLanguage(lang as string) + return this.translateService.use(bestMatchLang) + }), + mergeMap(() => of(true)) + ) + } + + protected toObservable( + canActivateResult: Observable | Promise | boolean | UrlTree + ): Observable { + if (isObservable(canActivateResult)) { + return canActivateResult + } + return from(Promise.resolve(canActivateResult)) + } +} diff --git a/libs/angular-integration-interface/src/lib/utils/add-initialize-module-guard.utils.spec.ts b/libs/angular-integration-interface/src/lib/utils/add-initialize-module-guard.utils.spec.ts new file mode 100644 index 00000000..c25f1739 --- /dev/null +++ b/libs/angular-integration-interface/src/lib/utils/add-initialize-module-guard.utils.spec.ts @@ -0,0 +1,240 @@ +import { TranslateService } from '@ngx-translate/core' +import { AppStateService } from '../services/app-state.service'; +import { UserService } from '../services/user.service'; +import { ConfigurationService } from '../services/configuration.service'; +import { InitializeModuleGuard } from '../services/initialize-module-guard.service' +import { addInitializeModuleGuard } from './add-initialize-module-guard.utils' + +class MockInitializeModuleGuard extends InitializeModuleGuard { + constructor( + translateService: TranslateService, + configService: ConfigurationService, + appStateService: AppStateService, + userService: UserService + ) { + super(translateService, configService, appStateService, userService) + } +} + +describe('AddInitializeGuard', () => { + it('should add canActivate array with InitializeModuleGuard to routes without canActivate and redirectTo properties', () => { + const testRoutesNoCanActivate = [ + { + path: 'testPathAddInitializerModuleGuard1', + }, + { + path: 'testPathAddInitializerModuleGuard2', + }, + ] + + const expectedRoutes = [ + { + path: 'testPathAddInitializerModuleGuard1', + canActivate: [InitializeModuleGuard], + }, + { + path: 'testPathAddInitializerModuleGuard2', + canActivate: [InitializeModuleGuard], + }, + ] + const modifiedRoutes = addInitializeModuleGuard(testRoutesNoCanActivate) + + expect(modifiedRoutes).toEqual(expectedRoutes) + expect(testRoutesNoCanActivate).not.toEqual(expectedRoutes) + }) + + it('should add InitializeModuleGuard to canActivate array which already has some other guard', () => { + const testRoutesWithMockInitializeModuleGuard = [ + { + path: 'testPathHasMockInitializeModueGuard1', + canActivate: [MockInitializeModuleGuard], + }, + { + path: 'testPath', + }, + { + path: 'testPathHasMockInitializeModueGuard2', + canActivate: [MockInitializeModuleGuard], + }, + ] + + const expectedRoutes = [ + { + path: 'testPathHasMockInitializeModueGuard1', + canActivate: [MockInitializeModuleGuard, InitializeModuleGuard], + }, + { + path: 'testPath', + canActivate: [InitializeModuleGuard], + }, + { + path: 'testPathHasMockInitializeModueGuard2', + canActivate: [MockInitializeModuleGuard, InitializeModuleGuard], + }, + ] + const modifiedRoutes = addInitializeModuleGuard(testRoutesWithMockInitializeModuleGuard) + + expect(modifiedRoutes).toEqual(expectedRoutes) + expect(testRoutesWithMockInitializeModuleGuard).not.toEqual(expectedRoutes) + }) + + it('should not add another InitializeModuleGuard to canActivate array which already has an InitializeModuleGuard', () => { + const testRoutesWithInitializeModuleGuard = [ + { + path: 'testPath1', + }, + { + path: 'testPathHasInitializeModueGuard1', + canActivate: [InitializeModuleGuard], + }, + { + path: 'testPathHasInitializeModueGuard2', + canActivate: [InitializeModuleGuard], + }, + { + path: 'testPath2', + }, + { + path: 'testPathHasInitializeModueGuard3', + canActivate: [InitializeModuleGuard], + }, + ] + + const expectedRoutes = [ + { + path: 'testPath1', + canActivate: [InitializeModuleGuard], + }, + { + path: 'testPathHasInitializeModueGuard1', + canActivate: [InitializeModuleGuard], + }, + { + path: 'testPathHasInitializeModueGuard2', + canActivate: [InitializeModuleGuard], + }, + { + path: 'testPath2', + canActivate: [InitializeModuleGuard], + }, + { + path: 'testPathHasInitializeModueGuard3', + canActivate: [InitializeModuleGuard], + }, + ] + const modifiedRoutes = addInitializeModuleGuard(testRoutesWithInitializeModuleGuard) + + expect(modifiedRoutes).toEqual(expectedRoutes) + expect(testRoutesWithInitializeModuleGuard).not.toEqual(expectedRoutes) + }) + + it('should not add InitializeModuleGuard if a route has the redirectTo property', () => { + const testRoutesWithRedirectTo = [ + { + path: 'testPathHasRedirectTo1', + redirectTo: 'redirectToPath1', + }, + { + path: 'someTestPath1', + }, + { + path: 'testPathHasRedirectTo2', + redirectTo: 'redirectToPath2', + }, + { + path: 'someTestPath2', + }, + ] + + const expectedRoutes = [ + { + path: 'testPathHasRedirectTo1', + redirectTo: 'redirectToPath1', + }, + { + path: 'someTestPath1', + canActivate: [InitializeModuleGuard], + }, + { + path: 'testPathHasRedirectTo2', + redirectTo: 'redirectToPath2', + }, + { + path: 'someTestPath2', + canActivate: [InitializeModuleGuard], + }, + ] + const modifiedRoutes = addInitializeModuleGuard(testRoutesWithRedirectTo) + + expect(modifiedRoutes).toEqual(expectedRoutes) + expect(testRoutesWithRedirectTo).not.toEqual(expectedRoutes) + }) + + it('should add canActivate array with MockInitializeModuleGuard to routes without canActivate and redirectTo properties', () => { + const testRoutesNoCanActivate = [ + { + path: 'testPathAddMockInitializerModuleGuard1', + }, + { + path: 'testPathAddMockInitializerModuleGuard2', + }, + { + path: 'testPathAddMockInitializerModuleGuard3', + }, + ] + + const expectedRoutes = [ + { + path: 'testPathAddMockInitializerModuleGuard1', + canActivate: [MockInitializeModuleGuard], + }, + { + path: 'testPathAddMockInitializerModuleGuard2', + canActivate: [MockInitializeModuleGuard], + }, + { + path: 'testPathAddMockInitializerModuleGuard3', + canActivate: [MockInitializeModuleGuard], + }, + ] + const modifiedRoutes = addInitializeModuleGuard(testRoutesNoCanActivate, MockInitializeModuleGuard) + + expect(modifiedRoutes).toEqual(expectedRoutes) + expect(testRoutesNoCanActivate).not.toEqual(expectedRoutes) + }) + + it('should add MockInitializeModuleGuard to canActivate array', () => { + const testRoutesWithMockInitializeModuleGuard = [ + { + path: 'testPathHasMockInitializeModueGuard1', + canActivate: [MockInitializeModuleGuard], + }, + { + path: 'somePath', + }, + { + path: 'testPathHasMockInitializeModueGuard2', + canActivate: [InitializeModuleGuard], + }, + ] + + const expectedRoutes = [ + { + path: 'testPathHasMockInitializeModueGuard1', + canActivate: [MockInitializeModuleGuard], + }, + { + path: 'somePath', + canActivate: [MockInitializeModuleGuard], + }, + { + path: 'testPathHasMockInitializeModueGuard2', + canActivate: [InitializeModuleGuard, MockInitializeModuleGuard], + }, + ] + const modifiedRoutes = addInitializeModuleGuard(testRoutesWithMockInitializeModuleGuard, MockInitializeModuleGuard) + + expect(modifiedRoutes).toEqual(expectedRoutes) + expect(testRoutesWithMockInitializeModuleGuard).not.toEqual(expectedRoutes) + }) +}) diff --git a/libs/angular-integration-interface/src/lib/utils/add-initialize-module-guard.utils.ts b/libs/angular-integration-interface/src/lib/utils/add-initialize-module-guard.utils.ts new file mode 100644 index 00000000..c1b346a2 --- /dev/null +++ b/libs/angular-integration-interface/src/lib/utils/add-initialize-module-guard.utils.ts @@ -0,0 +1,21 @@ +import { CanActivateFn, Route } from '@angular/router' +import { InitializeModuleGuard } from '../services/initialize-module-guard.service' + +export function addInitializeModuleGuard( + routes: Route[], + initializeModuleGuard: typeof InitializeModuleGuard | CanActivateFn = InitializeModuleGuard +): Route[] { + return routes.map((r) => { + if (r.redirectTo) { + return r + } + const route = { + canActivate: [], + ...r, + } + if (!route.canActivate.includes(initializeModuleGuard)) { + route.canActivate.push(initializeModuleGuard) + } + return route + }) +} \ No newline at end of file diff --git a/libs/portal-integration-angular/src/index.ts b/libs/portal-integration-angular/src/index.ts index c53b80bb..320d9c0c 100644 --- a/libs/portal-integration-angular/src/index.ts +++ b/libs/portal-integration-angular/src/index.ts @@ -82,4 +82,20 @@ export * from './lib/core/utils/translate-service-initializer.utils' export * from './lib/core/utils/portal-api-configuration.utils' export * from '@onecx/angular-accelerator' -export * from '@onecx/angular-integration-interface' +export { + AppStateService, + ConfigurationService, + UserService, + PortalMessageService, + ThemeService, + RemoteComponentsService, + CONFIG_KEY, + IAuthService, + LibConfig, + MfeInfo, + Theme, + APP_CONFIG, + AUTH_SERVICE, + SANITY_CHECK, + APPLICATION_NAME +} from '@onecx/angular-integration-interface' diff --git a/libs/portal-integration-angular/src/lib/core/utils/add-initialize-module-guard.utils.ts b/libs/portal-integration-angular/src/lib/core/utils/add-initialize-module-guard.utils.ts index 0bb601e9..ac28239c 100644 --- a/libs/portal-integration-angular/src/lib/core/utils/add-initialize-module-guard.utils.ts +++ b/libs/portal-integration-angular/src/lib/core/utils/add-initialize-module-guard.utils.ts @@ -1,21 +1,15 @@ import { CanActivateFn, Route } from '@angular/router' import { InitializeModuleGuard } from '../../services/initialize-module-guard.service' +import { addInitializeModuleGuard as _addInitializeModuleGuard } from '@onecx/angular-integration-interface' +/** + * @deprecated + * Please import from @onecx/angular-integration-interface, because in edge cases permission errors occur, + * when @onecx/angular-integration-interface is not shared and the version from portal-integration-angular is used. + */ export function addInitializeModuleGuard( routes: Route[], initializeModuleGuard: typeof InitializeModuleGuard | CanActivateFn = InitializeModuleGuard ): Route[] { - return routes.map((r) => { - if (r.redirectTo) { - return r - } - const route = { - canActivate: [], - ...r, - } - if (!route.canActivate.includes(initializeModuleGuard)) { - route.canActivate.push(initializeModuleGuard) - } - return route - }) -} \ No newline at end of file + return _addInitializeModuleGuard(routes, initializeModuleGuard) +} diff --git a/libs/portal-integration-angular/src/lib/services/initialize-module-guard.service.ts b/libs/portal-integration-angular/src/lib/services/initialize-module-guard.service.ts index 5588dffd..9766a4f0 100644 --- a/libs/portal-integration-angular/src/lib/services/initialize-module-guard.service.ts +++ b/libs/portal-integration-angular/src/lib/services/initialize-module-guard.service.ts @@ -1,65 +1,20 @@ import { Injectable } from '@angular/core' -import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree } from '@angular/router' import { TranslateService } from '@ngx-translate/core' -import { filter, from, isObservable, map, mergeMap, Observable, of, tap, zip } from 'rxjs' -import { AppStateService, ConfigurationService } from '@onecx/angular-integration-interface' -import { UserService } from '@onecx/angular-integration-interface' +import { AppStateService, ConfigurationService, UserService, InitializeModuleGuard as _InitializeModuleGuard } from '@onecx/angular-integration-interface' +/** + * @deprecated + * Please import from @onecx/angular-integration-interface, because in edge cases permission errors occur, + * when @onecx/angular-integration-interface is not shared and the version from portal-integration-angular is used. + */ @Injectable({ providedIn: 'any' }) -export class InitializeModuleGuard implements CanActivate { - private SUPPORTED_LANGS = ['en', 'de'] - private DEFAULT_LANG = 'en' +export class InitializeModuleGuard extends _InitializeModuleGuard { constructor( - protected translateService: TranslateService, - protected configService: ConfigurationService, - protected appStateService: AppStateService, - protected userService: UserService - ) {} - - canActivate( - _route: ActivatedRouteSnapshot, - _state: RouterStateSnapshot - ): Observable | Promise | boolean | UrlTree { - console.time('InitializeModuleGuard') - return zip([ - this.loadTranslations(), - from(this.configService.isInitialized), - from(this.userService.isInitialized), - from(this.appStateService.currentWorkspace$.isInitialized), - ]).pipe( - tap(() => { - console.timeEnd('InitializeModuleGuard') - }), - map(() => true) - ) - } - - getBestMatchLanguage(lang: string) { - if (this.SUPPORTED_LANGS.includes(lang)) { - return lang - } else { - console.log(`${lang} is not supported. Using ${this.DEFAULT_LANG} as a fallback.`) - } - return this.DEFAULT_LANG - } - - loadTranslations(): Observable { - return this.userService.lang$.pipe( - filter((v) => v !== undefined), - mergeMap((lang) => { - const bestMatchLang = this.getBestMatchLanguage(lang as string) - return this.translateService.use(bestMatchLang) - }), - mergeMap(() => of(true)) - ) - } - - protected toObservable( - canActivateResult: Observable | Promise | boolean | UrlTree - ): Observable { - if (isObservable(canActivateResult)) { - return canActivateResult - } - return from(Promise.resolve(canActivateResult)) + translateService: TranslateService, + configService: ConfigurationService, + appStateService: AppStateService, + userService: UserService + ) { + super(translateService, configService, appStateService, userService) } }