diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/core/project-notification.service.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/core/project-notification.service.ts index 42fb0e51c39..84a72b91eb6 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/core/project-notification.service.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/core/project-notification.service.ts @@ -16,13 +16,25 @@ export class ProjectNotificationService { private authService: AuthService, private readonly onlineService: OnlineStatusService ) { - this.connection = new HubConnectionBuilder().withUrl('/project-notifications', this.options).build(); + this.connection = new HubConnectionBuilder() + .withUrl('/project-notifications', this.options) + .withAutomaticReconnect() + .withStatefulReconnect() + .build(); } get appOnline(): boolean { return this.onlineService.isOnline && this.onlineService.isBrowserOnline; } + removeNotifyBuildProgressHandler(handler: any): void { + this.connection.off('notifyBuildProgress', handler); + } + + removeNotifySyncProgressHandler(handler: any): void { + this.connection.off('notifySyncProgress', handler); + } + setNotifyBuildProgressHandler(handler: any): void { this.connection.on('notifyBuildProgress', handler); } diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/sync/sync-progress/sync-progress.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/sync/sync-progress/sync-progress.component.ts index fa2160e7920..c5c6b791cd2 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/sync/sync-progress/sync-progress.component.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/sync/sync-progress/sync-progress.component.ts @@ -65,6 +65,9 @@ export class SyncProgressComponent { private sourceProjectDoc?: SFProjectDoc; private _projectDoc?: SFProjectDoc; + private readonly syncProgressHandler = (projectId: string, progressState: ProgressState): void => + this.updateProgressState(projectId, progressState); + constructor( private readonly projectService: SFProjectService, private readonly projectNotificationService: ProjectNotificationService, @@ -73,11 +76,10 @@ export class SyncProgressComponent { private readonly onlineStatus: OnlineStatusService, private destroyRef: DestroyRef ) { - this.projectNotificationService.setNotifySyncProgressHandler((projectId: string, progressState: ProgressState) => { - this.updateProgressState(projectId, progressState); - }); + this.projectNotificationService.setNotifySyncProgressHandler(this.syncProgressHandler); this.destroyRef.onDestroy(async () => { await this.projectNotificationService.stop(); + this.projectNotificationService.removeNotifySyncProgressHandler(this.syncProgressHandler); }); } diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.ts index dc39d52e7ef..863f49de420 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-history-list/draft-history-list.component.ts @@ -25,6 +25,9 @@ export class DraftHistoryListComponent { // This is just after SFv5.33.0 was released readonly draftHistoryCutOffDate: Date = new Date('2025-06-03T21:00:00Z'); readonly draftHistoryCutOffDateFormatted: string = this.i18n.formatDate(this.draftHistoryCutOffDate); + private readonly notifyBuildProgressHandler = (projectId: string): void => { + this.loadHistory(projectId); + }; constructor( activatedProject: ActivatedProjectService, @@ -48,13 +51,12 @@ export class DraftHistoryListComponent { await projectNotificationService.subscribeToProject(projectId); // When build notifications are received, reload the build history // NOTE: We do not need the build state, so just ignore it. - projectNotificationService.setNotifyBuildProgressHandler((projectId: string) => { - this.loadHistory(projectId); - }); + projectNotificationService.setNotifyBuildProgressHandler(this.notifyBuildProgressHandler); }); destroyRef.onDestroy(async () => { // Stop the SignalR connection when the component is destroyed await projectNotificationService.stop(); + projectNotificationService.removeNotifyBuildProgressHandler(this.notifyBuildProgressHandler); }); } diff --git a/src/SIL.XForge.Scripture/Startup.cs b/src/SIL.XForge.Scripture/Startup.cs index 5dcd4326567..d27600f0232 100644 --- a/src/SIL.XForge.Scripture/Startup.cs +++ b/src/SIL.XForge.Scripture/Startup.cs @@ -289,7 +289,10 @@ IExceptionHandler exceptionHandler { endpoints.MapControllers(); endpoints.MapRazorPages(); - endpoints.MapHub(pattern: $"/{UrlConstants.ProjectNotifications}"); + endpoints.MapHub( + pattern: $"/{UrlConstants.ProjectNotifications}", + options => options.AllowStatefulReconnects = true + ); var authOptions = Configuration.GetOptions(); endpoints.MapHangfireDashboard( new DashboardOptions