Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | 1x 8x 1x 1x 7x 1x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 6x 6x 5x 1x 2x 1x 2x 1x 8x | import { ChangeDetectionStrategy, Component, computed, effect, inject, input, } from '@angular/core'; import type { InputSignal, Signal } from '@angular/core'; import { Router } from '@angular/router'; import { SpinnerComponent } from '@app/shared/spinner/spinner.component'; /** * https://firebase.google.com/docs/reference/js/auth.actioncodeurl */ export interface ActionCodeState { /** We may include a next url when verifying email. */ readonly continueUrl: string | undefined; /** Currently not used, but the language code of the email sent to with the oobCode. */ readonly lang: string | undefined; /** Action to be performed by the oobCode. */ readonly mode: string | undefined; /** Out of Band Code to perform sensitive Authentication action. */ readonly oobCode: string; } /** * Firebase Action continueUrl is fully qualified. If it has a value convert it into a relative URL. */ const cleanUrl = (continueUrl: string | undefined): string | undefined => { if (continueUrl) { const url = new URL(continueUrl); return `${url.pathname}${url.search}${url.hash}`; } return undefined; }; /** * Self handle Firebase Authentication Actions * https://firebase.google.com/docs/auth/custom-email-handler * * Strips the query string parameters from the URL and stores them in the Router state for the * specific Components to handle. */ @Component({ changeDetection: ChangeDetectionStrategy.OnPush, imports: [ SpinnerComponent ], selector: 'app-actions', template: '<app-spinner class="modal-block" />', }) export class ActionsComponent { /** Query parameter from Firebase Authentication link. */ public readonly continueUrl: InputSignal<string | undefined> = input<string>(); /** Query parameter from Firebase Authentication link. */ public readonly lang: InputSignal<string | undefined> = input<string>(); /** Query parameter from Firebase Authentication link. */ public readonly mode: InputSignal<string> = input.required<string>(); /** Query parameter from Firebase Authentication link. */ public readonly oobCode: InputSignal<string> = input.required<string>(); private readonly _$actionState: Signal<Partial<ActionCodeState>>; private readonly _modePaths: Record<string, string>; private readonly _router: Router; /** * Collects the Firebase action codes from the URL query parameters and stores them in the router * state. * Maps Firebase action mode to our specific Components for handling the sensitive actions. * Replaces this URL in the history stack to prevent reverse navigation from attepting to apply * the code again. */ constructor() { this._$actionState = computed((): Partial<ActionCodeState> => ({ continueUrl: cleanUrl(this.continueUrl()), lang: this.lang(), mode: this.mode(), oobCode: this.oobCode(), })); this._modePaths = { recoverEmail: '/recover-email', resetPassword: '/reset-password', verifyAndChangeEmail: '/verify-email', verifyEmail: '/verify-email', }; this._router = inject(Router); // eslint-disable-next-line @typescript-eslint/no-misused-promises -- This works, for now, but perhaps not in the future! effect(async (): Promise<void> => { const state = this._$actionState(); if (state.mode && state.oobCode) { const path = this._modePaths[state.mode]; if (path) { // If this promise is not awaited then test cases fail :-( await this._router.navigateByUrl(path, { replaceUrl: true, state }); } else { console.error(`Unknown mode '${state.mode}'`); } } else { if (!state.mode) { console.error('Missing ActionCodeSettings#mode'); } if (!state.oobCode) { console.error('Missing ActionCodeSettings#oobCode'); } } // Something about this action is invalid. // Navigate to root to allow default redirectTo Route to decide initial destination. await this._router.navigateByUrl('/', { replaceUrl: true }); }); } } |