diff --git a/libs/auth/routing/src/effects/redirect.effects.spec.ts b/libs/auth/routing/src/effects/redirect.effects.spec.ts index a9c113dd9e..bb0bdf85e3 100644 --- a/libs/auth/routing/src/effects/redirect.effects.spec.ts +++ b/libs/auth/routing/src/effects/redirect.effects.spec.ts @@ -14,10 +14,7 @@ import { Observable } from 'rxjs'; import { provideDaffAuthRoutingConfig } from '@daffodil/auth/routing'; import { - DaffAuthCheckFailure, - DaffAuthGuardLogout, DaffAuthLoginSuccess, - DaffAuthLogoutSuccess, DaffAuthRegisterSuccess, DaffResetPasswordSuccess, } from '@daffodil/auth/state'; @@ -155,82 +152,4 @@ describe('@daffodil/auth/routing | DaffAuthRedirectEffects', () => { }); }); }); - - describe('when DaffAuthLogoutSuccess is dispatched', () => { - beforeEach(() => { - actions$ = hot('--a', { a: new DaffAuthLogoutSuccess() }); - }); - - it('should navigate to the login page', () => { - const expected = cold('---'); - - expect(effects.redirectAfterLogout$).toBeObservable(expected); - expect(routerNavigateSpy).toHaveBeenCalledWith(logoutRedirectUrl); - }); - - describe('and when the redirect QP is set', () => { - beforeEach(() => { - qpSpy.get.withArgs('redirect').and.returnValue(redirectUrl); - }); - - it('should navigate to the redirect URL', () => { - const expected = cold('---'); - - expect(effects.redirectAfterLogout$).toBeObservable(expected); - expect(routerNavigateSpy).toHaveBeenCalledWith(redirectUrl); - }); - }); - }); - - describe('when DaffAuthCheckFailure is dispatched', () => { - beforeEach(() => { - actions$ = hot('--a', { a: new DaffAuthCheckFailure(null) }); - }); - - it('should navigate to the home page', () => { - const expected = cold('---'); - - expect(effects.redirectAfterExpiration$).toBeObservable(expected); - expect(routerNavigateSpy).toHaveBeenCalledWith(expirationRedirectUrl); - }); - - describe('and when the redirect QP is set', () => { - beforeEach(() => { - qpSpy.get.withArgs('redirect').and.returnValue(redirectUrl); - }); - - it('should navigate to the redirect URL', () => { - const expected = cold('---'); - - expect(effects.redirectAfterExpiration$).toBeObservable(expected); - expect(routerNavigateSpy).toHaveBeenCalledWith(redirectUrl); - }); - }); - }); - - describe('when DaffAuthGuardLogout is dispatched', () => { - beforeEach(() => { - actions$ = hot('--a', { a: new DaffAuthGuardLogout(null) }); - }); - - it('should navigate to the home page', () => { - const expected = cold('---'); - - expect(effects.redirectAfterExpiration$).toBeObservable(expected); - expect(routerNavigateSpy).toHaveBeenCalledWith(expirationRedirectUrl); - }); - - describe('and when the redirect QP is set', () => { - beforeEach(() => { - qpSpy.get.withArgs('redirect').and.returnValue(redirectUrl); - }); - - it('should navigate to the redirect URL', () => { - const expected = cold('---'); - - expect(effects.redirectAfterExpiration$).toBeObservable(expected); - expect(routerNavigateSpy).toHaveBeenCalledWith(redirectUrl); - }); - }); - }); }); diff --git a/libs/auth/routing/src/effects/redirect.effects.ts b/libs/auth/routing/src/effects/redirect.effects.ts index b102a99aa7..d728eb74c8 100644 --- a/libs/auth/routing/src/effects/redirect.effects.ts +++ b/libs/auth/routing/src/effects/redirect.effects.ts @@ -63,23 +63,4 @@ export class DaffAuthRedirectEffects { }), switchMap(() => EMPTY), ), { dispatch: false }); - - redirectAfterLogout$ = createEffect(() => this.actions$.pipe( - ofType(DaffAuthLoginActionTypes.LogoutSuccessAction), - tap((action) => { - this.router.navigateByUrl(this.route.snapshot.queryParamMap.get(this.config.redirectUrlParam) || this.config.logoutRedirectPath); - }), - switchMap(() => EMPTY), - ), { dispatch: false }); - - redirectAfterExpiration$ = createEffect(() => this.actions$.pipe( - ofType( - DaffAuthActionTypes.AuthCheckFailureAction, - DaffAuthActionTypes.AuthGuardLogoutAction, - ), - tap(() => { - this.router.navigateByUrl(this.route.snapshot.queryParamMap.get(this.config.redirectUrlParam) || this.config.tokenExpirationRedirectPath); - }), - switchMap(() => EMPTY), - ), { dispatch: false }); } diff --git a/libs/auth/routing/src/helpers/public_api.ts b/libs/auth/routing/src/helpers/public_api.ts new file mode 100644 index 0000000000..17f06b2de2 --- /dev/null +++ b/libs/auth/routing/src/helpers/public_api.ts @@ -0,0 +1 @@ +export { daffAuthRoutingRedirectUnauthenticatedHookFactory } from './redirect-unauthenticated-hook-factory'; diff --git a/libs/auth/routing/src/helpers/redirect-unauthenticated-hook-factory.spec.ts b/libs/auth/routing/src/helpers/redirect-unauthenticated-hook-factory.spec.ts new file mode 100644 index 0000000000..9555135725 --- /dev/null +++ b/libs/auth/routing/src/helpers/redirect-unauthenticated-hook-factory.spec.ts @@ -0,0 +1,148 @@ +import { TestBed } from '@angular/core/testing'; +import { + ActivatedRoute, + ParamMap, + Router, +} from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Observable } from 'rxjs'; + +import { DAFF_AUTH_ROUTING_CONFIG_DEFAULT } from '@daffodil/auth/routing'; +import { + DaffAuthActionTypes, + DaffAuthLoginActionTypes, + DaffAuthUnauthenticatedHook, +} from '@daffodil/auth/state'; + +import { daffAuthRoutingRedirectUnauthenticatedHookFactory } from './redirect-unauthenticated-hook-factory'; + + +describe('@daffodil/auth/routing | daffAuthRoutingRedirectUnauthenticatedHookFactory', () => { + let router: Router; + let route: ActivatedRoute; + + let hook: DaffAuthUnauthenticatedHook; + let result: Observable; + + let routerNavigateSpy: jasmine.Spy; + let qpSpy: jasmine.SpyObj; + let logoutRedirectUrl: string; + let expirationRedirectUrl: string; + let redirectUrl: string; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + ], + }); + + router = TestBed.inject(Router); + route = TestBed.inject(ActivatedRoute); + + logoutRedirectUrl = '/login'; + expirationRedirectUrl = '/'; + redirectUrl = '/redirect'; + + qpSpy = jasmine.createSpyObj('ParamMap', ['get']); + routerNavigateSpy = spyOn(router, 'navigateByUrl'); + routerNavigateSpy.and.returnValue(new Promise((resolve) => resolve(true))); + + hook = daffAuthRoutingRedirectUnauthenticatedHookFactory( + router, + { + ...route, + snapshot: { + ...route.snapshot, + queryParamMap: qpSpy, + }, + }, + { + ...DAFF_AUTH_ROUTING_CONFIG_DEFAULT, + logoutRedirectPath: logoutRedirectUrl, + tokenExpirationRedirectPath: expirationRedirectUrl, + }, + ); + }); + + describe('when the hook is triggered with DaffAuthLogoutSuccess', () => { + beforeEach(() => { + result = hook(DaffAuthLoginActionTypes.LogoutSuccessAction); + }); + + it('should navigate to the login page', (done) => { + result.subscribe(() => { + expect(routerNavigateSpy).toHaveBeenCalledWith(logoutRedirectUrl); + done(); + }); + }); + + describe('and when the redirect QP is set', () => { + beforeEach(() => { + qpSpy.get.withArgs('redirect').and.returnValue(redirectUrl); + result = hook(DaffAuthLoginActionTypes.LogoutSuccessAction); + }); + + it('should navigate to the redirect URL', (done) => { + result.subscribe(() => { + expect(routerNavigateSpy).toHaveBeenCalledWith(redirectUrl); + done(); + }); + }); + }); + }); + + describe('when the hook is triggered with DaffAuthCheckFailure', () => { + beforeEach(() => { + result = hook(DaffAuthActionTypes.AuthCheckFailureAction); + }); + + it('should navigate to the home page', (done) => { + result.subscribe(() => { + expect(routerNavigateSpy).toHaveBeenCalledWith(expirationRedirectUrl); + done(); + }); + }); + + describe('and when the redirect QP is set', () => { + beforeEach(() => { + qpSpy.get.withArgs('redirect').and.returnValue(redirectUrl); + result = hook(DaffAuthActionTypes.AuthCheckFailureAction); + }); + + it('should navigate to the redirect URL', (done) => { + result.subscribe(() => { + expect(routerNavigateSpy).toHaveBeenCalledWith(redirectUrl); + done(); + }); + }); + }); + }); + + describe('when the hook is triggered with DaffAuthGuardLogout', () => { + beforeEach(() => { + result = hook(DaffAuthActionTypes.AuthGuardLogoutAction); + }); + + it('should navigate to the home page', (done) => { + result.subscribe(() => { + expect(routerNavigateSpy).toHaveBeenCalledWith(expirationRedirectUrl); + done(); + }); + }); + + describe('and when the redirect QP is set', () => { + beforeEach(() => { + qpSpy.get.withArgs('redirect').and.returnValue(redirectUrl); + result = hook(DaffAuthActionTypes.AuthGuardLogoutAction); + }); + + it('should navigate to the redirect URL', (done) => { + result.subscribe(() => { + expect(routerNavigateSpy).toHaveBeenCalledWith(redirectUrl); + done(); + }); + }); + }); + }); +}); diff --git a/libs/auth/routing/src/helpers/redirect-unauthenticated-hook-factory.ts b/libs/auth/routing/src/helpers/redirect-unauthenticated-hook-factory.ts new file mode 100644 index 0000000000..e57fbbb432 --- /dev/null +++ b/libs/auth/routing/src/helpers/redirect-unauthenticated-hook-factory.ts @@ -0,0 +1,32 @@ +import { + ActivatedRoute, + Router, +} from '@angular/router'; +import { + from, + of, +} from 'rxjs'; + +import { + DaffAuthActionTypes, + DaffAuthLoginActionTypes, + DaffAuthUnauthenticatedHook, +} from '@daffodil/auth/state'; + +import { DaffAuthRoutingConfig } from '../config/public_api'; + +export function daffAuthRoutingRedirectUnauthenticatedHookFactory(router: Router, route: ActivatedRoute, config: DaffAuthRoutingConfig): DaffAuthUnauthenticatedHook { + return (trigger) => { + switch (trigger) { + case DaffAuthLoginActionTypes.LogoutSuccessAction: + return from(router.navigateByUrl(route.snapshot.queryParamMap.get(config.redirectUrlParam) || config.logoutRedirectPath)); + + case DaffAuthActionTypes.AuthCheckFailureAction: + case DaffAuthActionTypes.AuthGuardLogoutAction: + return from(router.navigateByUrl(route.snapshot.queryParamMap.get(config.redirectUrlParam) || config.tokenExpirationRedirectPath)); + + default: + return of(null); + } + }; +} diff --git a/libs/auth/routing/src/public_api.ts b/libs/auth/routing/src/public_api.ts index 33e7f7f59b..b27dd0dccf 100644 --- a/libs/auth/routing/src/public_api.ts +++ b/libs/auth/routing/src/public_api.ts @@ -1,5 +1,6 @@ export * from './guards/public_api'; export * from './config/public_api'; +export * from './helpers/public_api'; export * from './module'; export * from './redirect.module'; diff --git a/libs/auth/routing/src/redirect.module.ts b/libs/auth/routing/src/redirect.module.ts index 9cbefc654e..447969a8ec 100644 --- a/libs/auth/routing/src/redirect.module.ts +++ b/libs/auth/routing/src/redirect.module.ts @@ -1,7 +1,28 @@ -import { NgModule } from '@angular/core'; +import { + NgModule, + inject, +} from '@angular/core'; +import { + ActivatedRoute, + Router, +} from '@angular/router'; import { EffectsModule } from '@ngrx/effects'; +import { + from, + of, +} from 'rxjs'; +import { + DAFF_AUTH_UNAUTHENTICATED_HOOKS, + DaffAuthActionTypes, + DaffAuthLoginActionTypes, + DaffAuthUnauthenticatedHook, +} from '@daffodil/auth/state'; + +import { DAFF_AUTH_ROUTING_CONFIG } from './config/public_api'; import { DaffAuthRedirectEffects } from './effects/redirect.effects'; +import { daffAuthRoutingRedirectUnauthenticatedHookFactory } from './helpers/public_api'; + @NgModule({ imports: [ @@ -9,5 +30,17 @@ import { DaffAuthRedirectEffects } from './effects/redirect.effects'; DaffAuthRedirectEffects, ]), ], + providers: [ + { + provide: DAFF_AUTH_UNAUTHENTICATED_HOOKS, + multi: true, + useFactory: () => + daffAuthRoutingRedirectUnauthenticatedHookFactory( + inject(Router), + inject(ActivatedRoute), + inject(DAFF_AUTH_ROUTING_CONFIG), + ), + }, + ], }) export class DaffAuthRoutingRedirectModule {} diff --git a/libs/auth/state/src/actions/auth.actions.ts b/libs/auth/state/src/actions/auth.actions.ts index 46bb89f548..1ca819c3f6 100644 --- a/libs/auth/state/src/actions/auth.actions.ts +++ b/libs/auth/state/src/actions/auth.actions.ts @@ -18,6 +18,10 @@ export enum DaffAuthActionTypes { */ export class DaffAuthResetToUnauthenticated implements Action { readonly type = DaffAuthActionTypes.ResetToUnauthenticatedAction; + + constructor( + public reason: Action['type'], + ) {} } /* diff --git a/libs/auth/state/src/effects/auth.effects.spec.ts b/libs/auth/state/src/effects/auth.effects.spec.ts index 2cb651dfda..487466eeac 100644 --- a/libs/auth/state/src/effects/auth.effects.spec.ts +++ b/libs/auth/state/src/effects/auth.effects.spec.ts @@ -98,6 +98,7 @@ describe('@daffodil/auth/state | DaffAuthEffects', () => { daffAuthDriver = TestBed.inject>(DaffAuthDriver); daffAuthStorageService = TestBed.inject(DaffAuthStorageService); + unauthenticatedHook.and.returnValue(of(null)); removeTokenSpy = spyOn(daffAuthStorageService, 'removeAuthToken'); getTokenSpy = spyOn(daffAuthStorageService, 'getAuthToken'); }); @@ -219,7 +220,7 @@ describe('@daffodil/auth/state | DaffAuthEffects', () => { beforeEach(() => { authLogoutSuccessAction = new DaffAuthCheckFailure({ code: 'code', message: 'message' }); actions$ = hot('--a', { a: authLogoutSuccessAction }); - expected = cold('--a', { a: new DaffAuthResetToUnauthenticated() }); + expected = cold('--a', { a: new DaffAuthResetToUnauthenticated(authLogoutSuccessAction.type) }); }); it('should dispatch DaffAuthResetToUnauthenticated', () => { @@ -233,7 +234,7 @@ describe('@daffodil/auth/state | DaffAuthEffects', () => { beforeEach(() => { authLogoutSuccessAction = new DaffAuthGuardLogout({ code: 'code', message: 'message' }); actions$ = hot('--a', { a: authLogoutSuccessAction }); - expected = cold('--a', { a: new DaffAuthResetToUnauthenticated() }); + expected = cold('--a', { a: new DaffAuthResetToUnauthenticated(authLogoutSuccessAction.type) }); }); it('should dispatch DaffAuthResetToUnauthenticated', () => { @@ -247,7 +248,7 @@ describe('@daffodil/auth/state | DaffAuthEffects', () => { beforeEach(() => { authLogoutSuccessAction = new DaffAuthLogoutSuccess(); actions$ = hot('--a', { a: authLogoutSuccessAction }); - expected = cold('--a', { a: new DaffAuthResetToUnauthenticated() }); + expected = cold('--a', { a: new DaffAuthResetToUnauthenticated(authLogoutSuccessAction.type) }); }); it('should dispatch DaffAuthResetToUnauthenticated', () => { @@ -263,14 +264,14 @@ describe('@daffodil/auth/state | DaffAuthEffects', () => { let revokeAction: DaffAuthResetToUnauthenticated; beforeEach(() => { - revokeAction = new DaffAuthResetToUnauthenticated(); + revokeAction = new DaffAuthResetToUnauthenticated('trigger'); actions$ = hot('--a', { a: revokeAction }); expected = cold('---'); }); it('should reset the client cache after a delay', () => { expect(effects.clearClientCache$).toBeObservable(expected); - expect(unauthenticatedHook).toHaveBeenCalledWith(); + expect(unauthenticatedHook).toHaveBeenCalledWith('trigger'); expect(clientCacheSpy.reset).toHaveBeenCalledWith(); }); }); diff --git a/libs/auth/state/src/effects/auth.effects.ts b/libs/auth/state/src/effects/auth.effects.ts index 3ed65ad9da..0be4e61fd2 100644 --- a/libs/auth/state/src/effects/auth.effects.ts +++ b/libs/auth/state/src/effects/auth.effects.ts @@ -54,6 +54,7 @@ import { DaffAuthStateConfig, DAFF_AUTH_STATE_CONFIG, } from '../config/public_api'; +import { DaffAuthUnauthenticatedHook } from '../injection-tokens/public_api'; import { DAFF_AUTH_UNAUTHENTICATED_HOOK } from '../injection-tokens/unauthenticated/hook.token'; @Injectable() @@ -65,7 +66,7 @@ export class DaffAuthEffects { private storage: DaffAuthStorageService, @Inject(DAFF_AUTH_STATE_CONFIG) private config: DaffAuthStateConfig, @Inject(DAFF_DRIVER_HTTP_CLIENT_CACHE_SERVICE) private clientCache: DaffDriverHttpClientCacheServiceInterface, - @Inject(DAFF_AUTH_UNAUTHENTICATED_HOOK) private unauthenticatedHook: () => void, + @Inject(DAFF_AUTH_UNAUTHENTICATED_HOOK) private unauthenticatedHook: DaffAuthUnauthenticatedHook, ) {} check$ = createEffect(() => this.actions$.pipe( @@ -122,18 +123,14 @@ export class DaffAuthEffects { DaffAuthActionTypes.AuthGuardLogoutAction, DaffAuthLoginActionTypes.LogoutSuccessAction, ), - map(() => new DaffAuthResetToUnauthenticated()), + map((action) => new DaffAuthResetToUnauthenticated(action.type)), )); clearClientCache$ = createEffect(() => this.actions$.pipe( ofType( DaffAuthActionTypes.ResetToUnauthenticatedAction, ), - // map here to be sure that the stream waits for the hook to complete - // tap is for side effects and might use setTimeout - map(() => { - this.unauthenticatedHook(); - }), + switchMap((action) => this.unauthenticatedHook(action.reason)), tap(() => { this.clientCache.reset(); }), diff --git a/libs/auth/state/src/injection-tokens/unauthenticated/hook.token.spec.ts b/libs/auth/state/src/injection-tokens/unauthenticated/hook.token.spec.ts index 4c534d84fa..e530e114ab 100644 --- a/libs/auth/state/src/injection-tokens/unauthenticated/hook.token.spec.ts +++ b/libs/auth/state/src/injection-tokens/unauthenticated/hook.token.spec.ts @@ -1,13 +1,17 @@ import { TestBed } from '@angular/core/testing'; -import { daffAuthProvideUnauthenticatedHooks } from '@daffodil/auth/state'; +import { + DaffAuthActionTypes, + DaffAuthUnauthenticatedHook, + daffAuthProvideUnauthenticatedHooks, +} from '@daffodil/auth/state'; import { DAFF_AUTH_UNAUTHENTICATED_HOOK } from './hook.token'; describe('@daffodil/auth/state | DAFF_AUTH_UNAUTHENTICATED_HOOK', () => { let spy1: jasmine.Spy; let spy2: jasmine.Spy; - let result: () => void; + let result: DaffAuthUnauthenticatedHook; beforeEach(() => { spy1 = jasmine.createSpy(); @@ -26,8 +30,8 @@ describe('@daffodil/auth/state | DAFF_AUTH_UNAUTHENTICATED_HOOK', () => { }); it('should provide a function that calls all the hooks', () => { - result(); - expect(spy1).toHaveBeenCalledWith(); - expect(spy2).toHaveBeenCalledWith(); + result(DaffAuthActionTypes.AuthCheckFailureAction); + expect(spy1).toHaveBeenCalledWith(DaffAuthActionTypes.AuthCheckFailureAction); + expect(spy2).toHaveBeenCalledWith(DaffAuthActionTypes.AuthCheckFailureAction); }); }); diff --git a/libs/auth/state/src/injection-tokens/unauthenticated/hook.token.ts b/libs/auth/state/src/injection-tokens/unauthenticated/hook.token.ts index de200e1645..1c05308354 100644 --- a/libs/auth/state/src/injection-tokens/unauthenticated/hook.token.ts +++ b/libs/auth/state/src/injection-tokens/unauthenticated/hook.token.ts @@ -2,7 +2,9 @@ import { inject, InjectionToken, } from '@angular/core'; +import { combineLatest } from 'rxjs'; +import { DaffAuthUnauthenticatedHook } from './hook.type'; import { DAFF_AUTH_UNAUTHENTICATED_HOOKS } from './hooks.token'; /** @@ -11,12 +13,12 @@ import { DAFF_AUTH_UNAUTHENTICATED_HOOKS } from './hooks.token'; * * @docs-private */ -export const DAFF_AUTH_UNAUTHENTICATED_HOOK = new InjectionToken<() => void>( +export const DAFF_AUTH_UNAUTHENTICATED_HOOK = new InjectionToken( 'DAFF_AUTH_UNAUTHENTICATED_HOOK', -{ - factory: () => inject(DAFF_AUTH_UNAUTHENTICATED_HOOKS).reduce((acc, hook) => () => { - acc(); - hook(); - }, () => {}), -}, + { + factory: () => { + const hooks = inject(DAFF_AUTH_UNAUTHENTICATED_HOOKS); + return (action) => combineLatest(hooks.map((hook) => hook(action))); + }, + }, ); diff --git a/libs/auth/state/src/injection-tokens/unauthenticated/hook.type.ts b/libs/auth/state/src/injection-tokens/unauthenticated/hook.type.ts new file mode 100644 index 0000000000..38b83ebe9f --- /dev/null +++ b/libs/auth/state/src/injection-tokens/unauthenticated/hook.type.ts @@ -0,0 +1,10 @@ +import { Action } from '@ngrx/store'; +import { Observable } from 'rxjs'; + +/** + * A function that is called when the user is unauthenticated. + * When a logged-in user is unauthenticated, various packages may perform cleanup tasks. + * These hooks will be called in no particular order before the client driver state is reset. + * Daffodil will wait for at least one emission from each hook before proceeding. + */ +export type DaffAuthUnauthenticatedHook = (trigger: Action['type']) => Observable; diff --git a/libs/auth/state/src/injection-tokens/unauthenticated/hooks.token.spec.ts b/libs/auth/state/src/injection-tokens/unauthenticated/hooks.token.spec.ts index 45e12ba9bb..3e7cef9c74 100644 --- a/libs/auth/state/src/injection-tokens/unauthenticated/hooks.token.spec.ts +++ b/libs/auth/state/src/injection-tokens/unauthenticated/hooks.token.spec.ts @@ -1,18 +1,20 @@ import { TestBed } from '@angular/core/testing'; +import { of } from 'rxjs'; +import { DaffAuthUnauthenticatedHook } from './hook.type'; import { daffAuthProvideUnauthenticatedHooks, DAFF_AUTH_UNAUTHENTICATED_HOOKS, } from './hooks.token'; describe('@daffodil/auth/state | daffAuthProvideUnauthenticatedHooks', () => { - let hooks: (() => void)[]; - let result: (() => void)[]; + let hooks: DaffAuthUnauthenticatedHook[]; + let result: DaffAuthUnauthenticatedHook[]; beforeEach(() => { hooks = [ - () => {}, - () => {}, + () => of(), + () => of(), ]; TestBed.configureTestingModule({ diff --git a/libs/auth/state/src/injection-tokens/unauthenticated/hooks.token.ts b/libs/auth/state/src/injection-tokens/unauthenticated/hooks.token.ts index 429468e496..dc1f1f5126 100644 --- a/libs/auth/state/src/injection-tokens/unauthenticated/hooks.token.ts +++ b/libs/auth/state/src/injection-tokens/unauthenticated/hooks.token.ts @@ -3,16 +3,17 @@ import { Provider, } from '@angular/core'; +import { DaffAuthUnauthenticatedHook } from './hook.type'; + /** * A token to hold the unauthenticated hooks. - * When a logged-in user is unauthenticated, various packages may perform cleanup tasks. - * These are guaranteed to run synchronously before the client driver state is fully reset. + * See {@link DaffAuthUnauthenticatedHook}. * * Prefer using {@link daffAuthProvideUnauthenticatedHooks}. */ -export const DAFF_AUTH_UNAUTHENTICATED_HOOKS = new InjectionToken<(() => void)[]>( +export const DAFF_AUTH_UNAUTHENTICATED_HOOKS = new InjectionToken( 'DAFF_AUTH_UNAUTHENTICATED_HOOKS', -{ factory: () => []}, + { factory: () => []}, ); /** @@ -28,7 +29,7 @@ export const DAFF_AUTH_UNAUTHENTICATED_HOOKS = new InjectionToken<(() => void)[] * ``` */ export function daffAuthProvideUnauthenticatedHooks( - ...hooks: (() => void)[] + ...hooks: DaffAuthUnauthenticatedHook[] ): Provider[] { return hooks.map(hook => ({ provide: DAFF_AUTH_UNAUTHENTICATED_HOOKS, diff --git a/libs/auth/state/src/injection-tokens/unauthenticated/public_api.ts b/libs/auth/state/src/injection-tokens/unauthenticated/public_api.ts index 973597af11..eddf85cb46 100644 --- a/libs/auth/state/src/injection-tokens/unauthenticated/public_api.ts +++ b/libs/auth/state/src/injection-tokens/unauthenticated/public_api.ts @@ -2,3 +2,4 @@ export { daffAuthProvideUnauthenticatedHooks, DAFF_AUTH_UNAUTHENTICATED_HOOKS, } from './hooks.token'; +export { DaffAuthUnauthenticatedHook } from './hook.type'; diff --git a/libs/cart-customer/state/src/state.module.ts b/libs/cart-customer/state/src/state.module.ts index 680f5e6a0a..f510ad9c17 100644 --- a/libs/cart-customer/state/src/state.module.ts +++ b/libs/cart-customer/state/src/state.module.ts @@ -4,8 +4,12 @@ import { } from '@angular/core'; import { EffectsModule } from '@ngrx/effects'; import { combineReducers } from '@ngrx/store'; +import { of } from 'rxjs'; -import { DAFF_AUTH_UNAUTHENTICATED_HOOKS } from '@daffodil/auth/state'; +import { + DAFF_AUTH_UNAUTHENTICATED_HOOKS, + DaffAuthUnauthenticatedHook, +} from '@daffodil/auth/state'; import { DaffCartStorageService } from '@daffodil/cart'; import { daffCartProvideExtraReducers } from '@daffodil/cart/state'; @@ -24,11 +28,15 @@ import { daffCartCustomerUnauthenticatedReset } from './reducers/unauthenticated provide: DAFF_AUTH_UNAUTHENTICATED_HOOKS, useFactory: () => { const storage = inject(DaffCartStorageService); - return () => { + const hook: DaffAuthUnauthenticatedHook = () => { try { - storage.removeCartId(); - } catch {} + return of(storage.removeCartId()); + } catch { + return of(null); + } }; + + return hook; }, multi: true, },