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({
selector: 'app-actions',
imports: [ SpinnerComponent ],
template: '<app-spinner class="modal-block" />',
changeDetection: ChangeDetectionStrategy.OnPush,
})
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 });
});
}
}
|