mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
[PM-19432] Fix Multiple WS Connections (#13985)
* Test facilitation changes * Fix multiple connections to SignalR
This commit is contained in:
@@ -23,6 +23,11 @@ export type ReceiveMessage = { type: "ReceiveMessage"; message: NotificationResp
|
|||||||
|
|
||||||
export type SignalRNotification = Heartbeat | ReceiveMessage;
|
export type SignalRNotification = Heartbeat | ReceiveMessage;
|
||||||
|
|
||||||
|
export type TimeoutManager = {
|
||||||
|
setTimeout: (handler: TimerHandler, timeout: number) => number;
|
||||||
|
clearTimeout: (timeoutId: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
class SignalRLogger implements ILogger {
|
class SignalRLogger implements ILogger {
|
||||||
constructor(private readonly logService: LogService) {}
|
constructor(private readonly logService: LogService) {}
|
||||||
|
|
||||||
@@ -51,11 +56,14 @@ export class SignalRConnectionService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly apiService: ApiService,
|
private readonly apiService: ApiService,
|
||||||
private readonly logService: LogService,
|
private readonly logService: LogService,
|
||||||
|
private readonly hubConnectionBuilderFactory: () => HubConnectionBuilder = () =>
|
||||||
|
new HubConnectionBuilder(),
|
||||||
|
private readonly timeoutManager: TimeoutManager = globalThis,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
connect$(userId: UserId, notificationsUrl: string) {
|
connect$(userId: UserId, notificationsUrl: string) {
|
||||||
return new Observable<SignalRNotification>((subsciber) => {
|
return new Observable<SignalRNotification>((subsciber) => {
|
||||||
const connection = new HubConnectionBuilder()
|
const connection = this.hubConnectionBuilderFactory()
|
||||||
.withUrl(notificationsUrl + "/hub", {
|
.withUrl(notificationsUrl + "/hub", {
|
||||||
accessTokenFactory: () => this.apiService.getActiveBearerToken(),
|
accessTokenFactory: () => this.apiService.getActiveBearerToken(),
|
||||||
skipNegotiation: true,
|
skipNegotiation: true,
|
||||||
@@ -76,48 +84,60 @@ export class SignalRConnectionService {
|
|||||||
let reconnectSubscription: Subscription | null = null;
|
let reconnectSubscription: Subscription | null = null;
|
||||||
|
|
||||||
// Create schedule reconnect function
|
// Create schedule reconnect function
|
||||||
const scheduleReconnect = (): Subscription => {
|
const scheduleReconnect = () => {
|
||||||
if (
|
if (
|
||||||
connection == null ||
|
connection == null ||
|
||||||
connection.state !== HubConnectionState.Disconnected ||
|
connection.state !== HubConnectionState.Disconnected ||
|
||||||
(reconnectSubscription != null && !reconnectSubscription.closed)
|
(reconnectSubscription != null && !reconnectSubscription.closed)
|
||||||
) {
|
) {
|
||||||
return Subscription.EMPTY;
|
// Skip scheduling a new reconnect, either the connection isn't disconnected
|
||||||
|
// or an active reconnect is already scheduled.
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const randomTime = this.random();
|
// If we've somehow gotten here while the subscriber is closed,
|
||||||
const timeoutHandler = setTimeout(() => {
|
// we do not want to reconnect. So leave.
|
||||||
|
if (subsciber.closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomTime = this.randomReconnectTime();
|
||||||
|
const timeoutHandler = this.timeoutManager.setTimeout(() => {
|
||||||
connection
|
connection
|
||||||
.start()
|
.start()
|
||||||
.then(() => (reconnectSubscription = null))
|
.then(() => {
|
||||||
|
reconnectSubscription = null;
|
||||||
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
reconnectSubscription = scheduleReconnect();
|
scheduleReconnect();
|
||||||
});
|
});
|
||||||
}, randomTime);
|
}, randomTime);
|
||||||
|
|
||||||
return new Subscription(() => clearTimeout(timeoutHandler));
|
reconnectSubscription = new Subscription(() =>
|
||||||
|
this.timeoutManager.clearTimeout(timeoutHandler),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
connection.onclose((error) => {
|
connection.onclose((error) => {
|
||||||
reconnectSubscription = scheduleReconnect();
|
scheduleReconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start connection
|
// Start connection
|
||||||
connection.start().catch(() => {
|
connection.start().catch(() => {
|
||||||
reconnectSubscription = scheduleReconnect();
|
scheduleReconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
// Cancel any possible scheduled reconnects
|
||||||
|
reconnectSubscription?.unsubscribe();
|
||||||
connection?.stop().catch((error) => {
|
connection?.stop().catch((error) => {
|
||||||
this.logService.error("Error while stopping SignalR connection", error);
|
this.logService.error("Error while stopping SignalR connection", error);
|
||||||
// TODO: Does calling stop call `onclose`?
|
|
||||||
reconnectSubscription?.unsubscribe();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private random() {
|
private randomReconnectTime() {
|
||||||
return (
|
return (
|
||||||
Math.floor(Math.random() * (MAX_RECONNECT_TIME - MIN_RECONNECT_TIME + 1)) + MIN_RECONNECT_TIME
|
Math.floor(Math.random() * (MAX_RECONNECT_TIME - MIN_RECONNECT_TIME + 1)) + MIN_RECONNECT_TIME
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user