diff --git a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts index 60a5ecb461d..a0547858936 100644 --- a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts +++ b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts @@ -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"); } } diff --git a/libs/common/src/platform/server-notifications/internal/signalr-connection.service.ts b/libs/common/src/platform/server-notifications/internal/signalr-connection.service.ts index 58d6311c668..dc78479d366 100644 --- a/libs/common/src/platform/server-notifications/internal/signalr-connection.service.ts +++ b/libs/common/src/platform/server-notifications/internal/signalr-connection.service.ts @@ -76,9 +76,17 @@ export class SignalRConnectionService { connect$(userId: UserId, notificationsUrl: string) { return new Observable((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); }); diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 6a670368b1f..a3c373bc2d1 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -1522,10 +1522,20 @@ export class ApiService implements ApiServiceAbstraction { // Helpers async getActiveBearerToken(): Promise { + 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; }