mirror of
https://github.com/bitwarden/browser
synced 2026-02-12 14:34:02 +00:00
extract listener setup
This commit is contained in:
@@ -14,16 +14,11 @@ import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
|
||||
import {
|
||||
catchError,
|
||||
concatMap,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
firstValueFrom,
|
||||
map,
|
||||
of,
|
||||
pairwise,
|
||||
startWith,
|
||||
Subject,
|
||||
switchMap,
|
||||
take,
|
||||
takeUntil,
|
||||
tap,
|
||||
} from "rxjs";
|
||||
@@ -132,22 +127,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
this.activeUserId = account?.id;
|
||||
});
|
||||
|
||||
// Trigger processing auth requests when the active user is in an unlocked state. Runs once when
|
||||
// the popup is open.
|
||||
this.accountService.activeAccount$
|
||||
.pipe(
|
||||
map((a) => a?.id), // Extract active userId
|
||||
distinctUntilChanged(), // Only when userId actually changes
|
||||
filter((userId) => userId != null), // Require a valid userId
|
||||
switchMap((userId) => this.authService.authStatusFor$(userId).pipe(take(1))), // Get current auth status once for new user
|
||||
filter((status) => status === AuthenticationStatus.Unlocked), // Only when the new user is Unlocked
|
||||
tap(() => {
|
||||
// Trigger processing when switching users while popup is open
|
||||
void this.authRequestAnsweringService.processPendingAuthRequests();
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
this.authRequestAnsweringService.setupUnlockListenersForProcessingAuthRequests(this.destroy$);
|
||||
|
||||
this.authService.activeAccountStatus$
|
||||
.pipe(
|
||||
@@ -159,23 +139,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
// When the popup is already open and the active account transitions to Unlocked,
|
||||
// process any pending auth requests for the active user. The above subscription does not handle
|
||||
// this case.
|
||||
this.authService.activeAccountStatus$
|
||||
.pipe(
|
||||
startWith(null as unknown as AuthenticationStatus), // Seed previous value to handle initial emission
|
||||
pairwise(), // Compare previous and current statuses
|
||||
filter(
|
||||
([prev, curr]) =>
|
||||
prev !== AuthenticationStatus.Unlocked && curr === AuthenticationStatus.Unlocked, // Fire on transitions into Unlocked (incl. initial)
|
||||
),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe(() => {
|
||||
void this.authRequestAnsweringService.processPendingAuthRequests();
|
||||
});
|
||||
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
window.onmousedown = () => this.recordActivity();
|
||||
window.ontouchstart = () => this.recordActivity();
|
||||
|
||||
@@ -40,6 +40,7 @@ import { EventUploadService } from "@bitwarden/common/abstractions/event/event-u
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
@@ -186,6 +187,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private desktopAutotypeDefaultSettingPolicy: DesktopAutotypeDefaultSettingPolicy,
|
||||
private pendingAuthRequestsState: PendingAuthRequestsStateService,
|
||||
private authRequestService: AuthRequestServiceAbstraction,
|
||||
private authRequestAnsweringService: AuthRequestAnsweringService,
|
||||
) {
|
||||
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||
|
||||
@@ -198,6 +200,8 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
this.activeUserId = account?.id;
|
||||
});
|
||||
|
||||
this.authRequestAnsweringService.setupUnlockListenersForProcessingAuthRequests(this.destroy$);
|
||||
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
setTimeout(async () => {
|
||||
await this.updateAppMenu();
|
||||
@@ -530,15 +534,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
} finally {
|
||||
this.processingPendingAuth = false;
|
||||
}
|
||||
|
||||
// if (message.notificationId != null) {
|
||||
// this.dialogService.closeAll();
|
||||
// const dialogRef = LoginApprovalDialogComponent.open(this.dialogService, {
|
||||
// notificationId: message.notificationId,
|
||||
// });
|
||||
// await firstValueFrom(dialogRef.closed);
|
||||
// }
|
||||
|
||||
break;
|
||||
case "redrawMenu":
|
||||
await this.updateAppMenu();
|
||||
|
||||
@@ -320,15 +320,6 @@ export class VaultV2Component<C extends CipherViewLike>
|
||||
this.searchBarService.setEnabled(true);
|
||||
this.searchBarService.setPlaceholderText(this.i18nService.t("searchVault"));
|
||||
|
||||
// const authRequests = await firstValueFrom(
|
||||
// this.authRequestService.getLatestPendingAuthRequest$()!,
|
||||
// );
|
||||
// if (authRequests != null) {
|
||||
// this.messagingService.send("openLoginApproval", {
|
||||
// notificationId: authRequests.id,
|
||||
// });
|
||||
// }
|
||||
|
||||
this.activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getUserId),
|
||||
).catch((): any => null);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { SystemNotificationEvent } from "@bitwarden/common/platform/system-notifications/system-notifications.service";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
@@ -17,10 +19,7 @@ export abstract class AuthRequestAnsweringService {
|
||||
|
||||
/**
|
||||
* Confirms whether or not the user meets the conditions required to show an approval
|
||||
* dialog immediately. Those conditions are:
|
||||
* - User must be Unlocked
|
||||
* - User must be the active user
|
||||
* - User must not be required to set/change their password
|
||||
* dialog immediately.
|
||||
*
|
||||
* @param userId the UserId that the auth request is for.
|
||||
* @returns boolean stating whether or not the user meets conditions necessary to show
|
||||
@@ -41,4 +40,13 @@ export abstract class AuthRequestAnsweringService {
|
||||
* approval dialog.
|
||||
*/
|
||||
abstract processPendingAuthRequests(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Sets up listeners for scenarios where the user unlocks and we want to process
|
||||
* any pending auth requests in state.
|
||||
* - Implemented in Extension and Desktop
|
||||
*
|
||||
* @param destroy$ The destroy$ observable from the caller
|
||||
*/
|
||||
abstract setupUnlockListenersForProcessingAuthRequests(destroy$: Observable<void>): void;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
firstValueFrom,
|
||||
map,
|
||||
Observable,
|
||||
pairwise,
|
||||
startWith,
|
||||
switchMap,
|
||||
take,
|
||||
takeUntil,
|
||||
tap,
|
||||
} from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
@@ -39,6 +51,7 @@ export class DefaultAuthRequestAnsweringService implements AuthRequestAnsweringS
|
||||
this.masterPasswordService.forceSetPasswordReason$(userId),
|
||||
);
|
||||
|
||||
// Use must be unlocked, active, and must not be required to set/change their master password
|
||||
const meetsConditions =
|
||||
authStatus === AuthenticationStatus.Unlocked &&
|
||||
activeUserId === userId &&
|
||||
@@ -47,7 +60,7 @@ export class DefaultAuthRequestAnsweringService implements AuthRequestAnsweringS
|
||||
return meetsConditions;
|
||||
}
|
||||
|
||||
async handleAuthRequestNotificationClicked(event: SystemNotificationEvent) {
|
||||
async handleAuthRequestNotificationClicked(event: SystemNotificationEvent): Promise<void> {
|
||||
throw new Error("handleAuthRequestNotificationClicked() not implemented for this client");
|
||||
}
|
||||
|
||||
@@ -73,4 +86,38 @@ export class DefaultAuthRequestAnsweringService implements AuthRequestAnsweringS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupUnlockListenersForProcessingAuthRequests(destroy$: Observable<void>): void {
|
||||
// Trigger processing auth requests when the active user is in an unlocked state.
|
||||
this.accountService.activeAccount$
|
||||
.pipe(
|
||||
map((a) => a?.id), // Extract active userId
|
||||
distinctUntilChanged(), // Only when userId actually changes
|
||||
filter((userId) => userId != null), // Require a valid userId
|
||||
switchMap((userId) => this.authService.authStatusFor$(userId).pipe(take(1))), // Get current auth status once for new user
|
||||
filter((status) => status === AuthenticationStatus.Unlocked), // Only when the new user is Unlocked
|
||||
tap(() => {
|
||||
// Trigger processing when switching users while app is visible.
|
||||
void this.processPendingAuthRequests();
|
||||
}),
|
||||
takeUntil(destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
// When the app is already visible and the active account transitions to Unlocked, process any
|
||||
// pending auth requests for the active user. The above subscription does not handle this case.
|
||||
this.authService.activeAccountStatus$
|
||||
.pipe(
|
||||
startWith(null as unknown as AuthenticationStatus), // Seed previous value to handle initial emission
|
||||
pairwise(), // Compare previous and current statuses
|
||||
filter(
|
||||
([prev, curr]) =>
|
||||
prev !== AuthenticationStatus.Unlocked && curr === AuthenticationStatus.Unlocked, // Fire on transitions into Unlocked (incl. initial)
|
||||
),
|
||||
takeUntil(destroy$),
|
||||
)
|
||||
.subscribe(() => {
|
||||
void this.processPendingAuthRequests();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,6 @@ export class NoopAuthRequestAnsweringService implements AuthRequestAnsweringServ
|
||||
async handleAuthRequestNotificationClicked(event: SystemNotificationEvent) {}
|
||||
|
||||
async processPendingAuthRequests(): Promise<void> {}
|
||||
|
||||
setupUnlockListenersForProcessingAuthRequests(): void {}
|
||||
}
|
||||
|
||||
@@ -284,14 +284,11 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
|
||||
);
|
||||
|
||||
/**
|
||||
* This call is necessary for Desktop, which for the time being uses a noop for the
|
||||
* authRequestAnsweringService.receivedPendingAuthRequest() call just above. Desktop
|
||||
* will eventually use the new AuthRequestAnsweringService, at which point we can remove
|
||||
* this second call.
|
||||
* This call is necessary for Web, which uses a noop for the AuthRequstAnsweringService.
|
||||
*
|
||||
* The Extension AppComponent has logic (see processingPendingAuth) that only allows one
|
||||
* pending auth request to process at a time, so this second call will not cause any
|
||||
* duplicate processing conflicts on Extension.
|
||||
* The Extension and Desktop AppComponent have logic that allows only one pending
|
||||
* auth request to process at a time (see processingPendingAuth), so this second call
|
||||
* will not cause any duplicate processing conflicts on Extension/Desktop.
|
||||
*/
|
||||
this.messagingService.send("openLoginApproval", {
|
||||
notificationId: notification.payload.id,
|
||||
|
||||
Reference in New Issue
Block a user