1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-05 11:13:44 +00:00

fix(extension-device-approval): [PM-14943] Answering Service Full Implementation - Lots of debug statements added. Seeing unknown error closing connection.

This commit is contained in:
Patrick Pimentel
2025-08-28 16:18:08 -04:00
parent 83faf0a8b2
commit 38aaf2ba86
3 changed files with 83 additions and 3 deletions

View File

@@ -11,6 +11,8 @@ import {
Observable,
share,
switchMap,
tap,
finalize,
} from "rxjs";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
@@ -68,6 +70,7 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
.pipe(
switchMap((inactiveUserServerNotificationEnabled) => {
if (inactiveUserServerNotificationEnabled) {
console.debug("[DefaultServerNotificationsService] inactive notification processing");
return this.accountService.accounts$.pipe(
map((accounts) => Object.keys(accounts) as UserId[]),
switchMap((userIds) => {
@@ -86,6 +89,7 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
);
}
console.debug("[DefaultServerNotificationsService] NOT inactive notification processing");
return this.accountService.activeAccount$.pipe(
map((account) => account?.id),
distinctUntilChanged(),
@@ -115,6 +119,9 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
distinctUntilChanged(),
switchMap((notificationsUrl) => {
if (notificationsUrl === DISABLED_NOTIFICATIONS_URL) {
console.debug(
`[DefaultServerNotificationsService] notifications disabled via URL for user ${userId}`,
);
return EMPTY;
}
@@ -127,12 +134,18 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
return this.hasAccessToken$(userId).pipe(
switchMap((hasAccessToken) => {
if (!hasAccessToken) {
console.debug(
`[DefaultServerNotificationsService] no access token for user ${userId}, skipping notifications`,
);
return EMPTY;
}
return this.activitySubject;
}),
switchMap((activityStatus) => {
console.debug(
`[DefaultServerNotificationsService] activity status for user ${userId}: ${activityStatus}`,
);
if (activityStatus === "inactive") {
return EMPTY;
}
@@ -142,15 +155,24 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
supportSwitch({
supported: (service) => {
this.logService.info("Using WebPush for server notifications");
console.debug(
`[DefaultServerNotificationsService] WebPush supported for user ${userId}`,
);
return service.notifications$.pipe(
catchError((err: unknown) => {
this.logService.warning("Issue with web push, falling back to SignalR", err);
console.debug(
`[DefaultServerNotificationsService] WebPush error for user ${userId}, falling back to SignalR`,
);
return this.connectSignalR$(userId, notificationsUrl);
}),
);
},
notSupported: () => {
this.logService.info("Using SignalR for server notifications");
console.debug(
`[DefaultServerNotificationsService] WebPush not supported for user ${userId}, using SignalR`,
);
return this.connectSignalR$(userId, notificationsUrl);
},
}),
@@ -158,9 +180,17 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
}
private connectSignalR$(userId: UserId, notificationsUrl: string) {
console.debug(
`[DefaultServerNotificationsService] Connecting SignalR for user ${userId} to ${notificationsUrl}`,
);
return this.signalRConnectionService.connect$(userId, notificationsUrl).pipe(
filter((n) => n.type === "ReceiveMessage"),
map((n) => (n as ReceiveMessage).message),
finalize(() =>
console.debug(
`[DefaultServerNotificationsService] SignalR stream finalized for user ${userId}`,
),
),
);
}
@@ -300,8 +330,14 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
}
startListening() {
console.debug("[DefaultServerNotificationsService] startListening subscribe");
return this.notifications$
.pipe(
tap(([notification, userId]) =>
console.debug(
`[DefaultServerNotificationsService] received notification type ${notification.type} for user ${userId}`,
),
),
mergeMap(async ([notification, userId]) => this.processNotification(notification, userId)),
)
.subscribe({
@@ -311,10 +347,12 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
}
reconnectFromActivity(): void {
console.debug("[DefaultServerNotificationsService] reconnectFromActivity called");
this.activitySubject.next("active");
}
disconnectFromInactivity(): void {
console.debug("[DefaultServerNotificationsService] disconnectFromInactivity called");
this.activitySubject.next("inactive");
}
}

View File

@@ -76,9 +76,17 @@ export class SignalRConnectionService {
connect$(userId: UserId, notificationsUrl: string) {
return new Observable<SignalRNotification>((subsciber) => {
console.debug(
`[SignalRConnectionService] creating connection for user ${userId} to ${notificationsUrl}`,
);
const connection = this.hubConnectionBuilderFactory()
.withUrl(notificationsUrl + "/hub", {
accessTokenFactory: () => this.apiService.getActiveBearerToken(),
accessTokenFactory: async () => {
console.debug(
`[SignalRConnectionService] accessTokenFactory called for user ${userId}`,
);
return this.apiService.getActiveBearerToken();
},
skipNegotiation: true,
transport: HttpTransportType.WebSockets,
})
@@ -87,10 +95,16 @@ export class SignalRConnectionService {
.build();
connection.on("ReceiveMessage", (data: any) => {
console.debug(
`[SignalRConnectionService] ReceiveMessage for user ${userId}: ${JSON.stringify(
data?.Type ?? data?.type ?? "unknown",
)}`,
);
subsciber.next({ type: "ReceiveMessage", message: new NotificationResponse(data) });
});
connection.on("Heartbeat", () => {
console.debug(`[SignalRConnectionService] Heartbeat for user ${userId}`);
subsciber.next({ type: "Heartbeat" });
});
@@ -115,13 +129,22 @@ export class SignalRConnectionService {
}
const randomTime = this.randomReconnectTime();
console.debug(
`[SignalRConnectionService] scheduling reconnect for user ${userId} in ${randomTime}ms`,
);
const timeoutHandler = this.timeoutManager.setTimeout(() => {
connection
.start()
.then(() => {
console.debug(
`[SignalRConnectionService] reconnected for user ${userId}`,
);
reconnectSubscription = null;
})
.catch(() => {
console.debug(
`[SignalRConnectionService] reconnect failed for user ${userId}, rescheduling`,
);
scheduleReconnect();
});
}, randomTime);
@@ -132,17 +155,26 @@ export class SignalRConnectionService {
};
connection.onclose((error) => {
console.debug(
`[SignalRConnectionService] onclose for user ${userId}: ${error?.toString?.() ?? "unknown"}`,
);
scheduleReconnect();
});
// Start connection
connection.start().catch(() => {
connection.start().then(() => {
console.debug(`[SignalRConnectionService] connected for user ${userId}`);
}).catch(() => {
console.debug(
`[SignalRConnectionService] initial connect failed for user ${userId}, scheduling reconnect`,
);
scheduleReconnect();
});
return () => {
// Cancel any possible scheduled reconnects
reconnectSubscription?.unsubscribe();
console.debug(`[SignalRConnectionService] stopping connection for user ${userId}`);
connection?.stop().catch((error) => {
this.logService.error("Error while stopping SignalR connection", error);
});

View File

@@ -1522,10 +1522,20 @@ export class ApiService implements ApiServiceAbstraction {
// Helpers
async getActiveBearerToken(): Promise<string> {
console.debug("[ApiService] getActiveBearerToken called");
let accessToken = await this.tokenService.getAccessToken();
if (await this.tokenService.tokenNeedsRefresh()) {
const needsRefresh = await this.tokenService.tokenNeedsRefresh();
if (needsRefresh) {
console.debug("[ApiService] access token needs refresh; refreshing now");
accessToken = await this.refreshToken();
} else {
console.debug("[ApiService] access token does not need refresh");
}
try {
const decoded = await this.tokenService.decodeAccessToken();
const subject = (decoded as any)?.sub ?? "unknown";
console.debug(`[ApiService] active token subject: ${subject}`);
} catch {}
return accessToken;
}