diff --git a/apps/browser/src/autofill/clipboard/generate-password-to-clipboard-command.ts b/apps/browser/src/autofill/clipboard/generate-password-to-clipboard-command.ts index f6290ce4d3..bd70aebb9d 100644 --- a/apps/browser/src/autofill/clipboard/generate-password-to-clipboard-command.ts +++ b/apps/browser/src/autofill/clipboard/generate-password-to-clipboard-command.ts @@ -17,8 +17,9 @@ export class GeneratePasswordToClipboardCommand { private autofillSettingsService: AutofillSettingsServiceAbstraction, private taskSchedulerService: BrowserTaskSchedulerService, ) { - this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.clearClipboardTimeout, () => - ClearClipboard.run(), + void this.taskSchedulerService.registerTaskHandler( + ScheduledTaskNames.clearClipboardTimeout, + () => ClearClipboard.run(), ); } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index f86048dc9f..e9941bd3d4 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -448,8 +448,9 @@ export default class MainBackground { this.logService, this.stateProvider, ); - this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.scheduleNextSyncInterval, () => - this.fullSync(), + void this.taskSchedulerService.registerTaskHandler( + ScheduledTaskNames.scheduleNextSyncInterval, + () => this.fullSync(), ); this.environmentService = new BrowserEnvironmentService( this.logService, diff --git a/apps/browser/src/platform/services/browser-task-scheduler.service.spec.ts b/apps/browser/src/platform/services/browser-task-scheduler.service.spec.ts index 12f6ded618..42509b6b00 100644 --- a/apps/browser/src/platform/services/browser-task-scheduler.service.spec.ts +++ b/apps/browser/src/platform/services/browser-task-scheduler.service.spec.ts @@ -1,10 +1,11 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { Observable } from "rxjs"; +import { BehaviorSubject, Observable } from "rxjs"; import { TaskIdentifier } from "@bitwarden/common/platform/abstractions/task-scheduler.service"; import { ScheduledTaskNames } from "@bitwarden/common/platform/enums/scheduled-task-name.enum"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { GlobalState, StateProvider } from "@bitwarden/common/platform/state"; +import { UserId } from "@bitwarden/common/types/guid"; import { ActiveAlarm } from "./abstractions/browser-task-scheduler.service"; import { BrowserTaskSchedulerService } from "./browser-task-scheduler.service"; @@ -18,6 +19,7 @@ jest.mock("rxjs", () => ({ // TODO CG - Likely need to rethink how to test this service a bit more carefully. describe("BrowserTaskSchedulerService", () => { + let activeUserIdMock$: BehaviorSubject; let logService: MockProxy; let stateProvider: MockProxy; let browserTaskSchedulerService: BrowserTaskSchedulerService; @@ -41,8 +43,10 @@ describe("BrowserTaskSchedulerService", () => { createInfo: { delayInMinutes: 1, periodInMinutes: undefined }, }), ]; + activeUserIdMock$ = new BehaviorSubject("user-uuid" as UserId); logService = mock(); stateProvider = mock({ + activeUserId$: activeUserIdMock$, getGlobal: jest.fn(() => mock>({ state$: mock>(), @@ -108,7 +112,7 @@ describe("BrowserTaskSchedulerService", () => { const callback = jest.fn(); const delayInMs = 999; jest.spyOn(globalThis, "setTimeout"); - browserTaskSchedulerService.registerTaskHandler( + await browserTaskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, callback, ); @@ -132,7 +136,7 @@ describe("BrowserTaskSchedulerService", () => { ScheduledTaskNames.loginStrategySessionTimeout, ); const callback = jest.fn(); - browserTaskSchedulerService.registerTaskHandler( + await browserTaskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, callback, ); @@ -153,7 +157,7 @@ describe("BrowserTaskSchedulerService", () => { it("creates a timeout alarm", async () => { const callback = jest.fn(); const delayInMinutes = 2; - browserTaskSchedulerService.registerTaskHandler( + await browserTaskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, callback, ); @@ -179,7 +183,7 @@ describe("BrowserTaskSchedulerService", () => { }), ); jest.spyOn(browserTaskSchedulerService, "createAlarm"); - browserTaskSchedulerService.registerTaskHandler( + await browserTaskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, callback, ); @@ -210,7 +214,7 @@ describe("BrowserTaskSchedulerService", () => { const callback = jest.fn(); const intervalInMs = 999; jest.spyOn(globalThis, "setInterval"); - browserTaskSchedulerService.registerTaskHandler( + await browserTaskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, callback, ); @@ -235,7 +239,7 @@ describe("BrowserTaskSchedulerService", () => { ScheduledTaskNames.loginStrategySessionTimeout, ); const callback = jest.fn(); - browserTaskSchedulerService.registerTaskHandler( + await browserTaskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, callback, ); @@ -257,7 +261,7 @@ describe("BrowserTaskSchedulerService", () => { const callback = jest.fn(); const periodInMinutes = 2; const initialDelayInMs = 1000; - browserTaskSchedulerService.registerTaskHandler( + await browserTaskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, callback, ); diff --git a/apps/browser/src/platform/services/browser-task-scheduler.service.ts b/apps/browser/src/platform/services/browser-task-scheduler.service.ts index c73cd2fe81..6152c8a78d 100644 --- a/apps/browser/src/platform/services/browser-task-scheduler.service.ts +++ b/apps/browser/src/platform/services/browser-task-scheduler.service.ts @@ -30,11 +30,8 @@ export class BrowserTaskSchedulerService readonly activeAlarms$: Observable; private recoveredAlarms: Set = new Set(); - constructor( - logService: LogService, - private stateProvider: StateProvider, - ) { - super(logService); + constructor(logService: LogService, stateProvider: StateProvider) { + super(logService, stateProvider); this.activeAlarmsState = this.stateProvider.getGlobal(ACTIVE_ALARMS); this.activeAlarms$ = this.activeAlarmsState.state$.pipe( diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 189dbadfe0..0c8a4f3aff 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -255,7 +255,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: TaskSchedulerService, useClass: DefaultTaskSchedulerService, - deps: [LogServiceAbstraction], + deps: [LogServiceAbstraction, StateProvider], }), ]; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index e6672d718b..47608c24d2 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1130,7 +1130,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: TaskSchedulerService, useClass: DefaultTaskSchedulerService, - deps: [LogService], + deps: [LogService, StateProvider], }), safeProvider({ provide: ProviderApiServiceAbstraction, diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 783641a982..b898ec9949 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -115,7 +115,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { this.authRequestPushNotificationState = this.stateProvider.get( AUTH_REQUEST_PUSH_NOTIFICATION_KEY, ); - this.taskSchedulerService.registerTaskHandler( + void this.taskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, () => this.clearCache(), ); diff --git a/libs/common/src/platform/abstractions/task-scheduler.service.ts b/libs/common/src/platform/abstractions/task-scheduler.service.ts index 831da93161..1e68d190f1 100644 --- a/libs/common/src/platform/abstractions/task-scheduler.service.ts +++ b/libs/common/src/platform/abstractions/task-scheduler.service.ts @@ -1,4 +1,5 @@ import { ScheduledTaskName } from "../enums/scheduled-task-name.enum"; +import { StateProvider } from "../state"; import { LogService } from "./log.service"; @@ -11,11 +12,14 @@ export type TaskIdentifier = { export abstract class TaskSchedulerService { protected taskHandlers: Map void>; - constructor(protected logService: LogService) {} + constructor( + protected logService: LogService, + protected stateProvider: StateProvider, + ) {} - abstract registerTaskHandler(taskName: ScheduledTaskName, handler: () => void): void; + abstract registerTaskHandler(taskName: ScheduledTaskName, handler: () => void): Promise; - abstract unregisterTaskHandler(taskName: ScheduledTaskName): void; + abstract unregisterTaskHandler(taskName: ScheduledTaskName): Promise; abstract setTimeout( taskName: ScheduledTaskName, diff --git a/libs/common/src/platform/services/default-task-scheduler.service.spec.ts b/libs/common/src/platform/services/default-task-scheduler.service.spec.ts index 9da947a7fd..acdd590875 100644 --- a/libs/common/src/platform/services/default-task-scheduler.service.spec.ts +++ b/libs/common/src/platform/services/default-task-scheduler.service.spec.ts @@ -1,14 +1,34 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { UserId } from "../../../../common/src/types/guid"; +import { LogService } from "../abstractions/log.service"; +import { ScheduledTaskNames } from "../enums/scheduled-task-name.enum"; +import { StateProvider } from "../state"; + import { DefaultTaskSchedulerService } from "./default-task-scheduler.service"; describe("TaskSchedulerService", () => { const callback = jest.fn(); const delayInMs = 1000; const intervalInMs = 1100; + let activeUserIdMock$: BehaviorSubject; + let logService: MockProxy; + let stateProvider: MockProxy; let taskSchedulerService: DefaultTaskSchedulerService; beforeEach(() => { jest.useFakeTimers(); - taskSchedulerService = new DefaultTaskSchedulerService(); + activeUserIdMock$ = new BehaviorSubject("user-uuid" as UserId); + logService = mock(); + stateProvider = mock({ + activeUserId$: activeUserIdMock$, + }); + taskSchedulerService = new DefaultTaskSchedulerService(logService, stateProvider); + void taskSchedulerService.registerTaskHandler( + ScheduledTaskNames.loginStrategySessionTimeout, + callback, + ); }); afterEach(() => { @@ -17,7 +37,10 @@ describe("TaskSchedulerService", () => { }); it("sets a timeout and returns the timeout id", async () => { - const timeoutId = await taskSchedulerService.setTimeout(callback, delayInMs); + const timeoutId = await taskSchedulerService.setTimeout( + ScheduledTaskNames.loginStrategySessionTimeout, + delayInMs, + ); expect(timeoutId).toBeDefined(); expect(callback).not.toHaveBeenCalled(); @@ -28,7 +51,10 @@ describe("TaskSchedulerService", () => { }); it("sets an interval timeout and results the interval id", async () => { - const intervalId = await taskSchedulerService.setInterval(callback, intervalInMs); + const intervalId = await taskSchedulerService.setInterval( + ScheduledTaskNames.loginStrategySessionTimeout, + intervalInMs, + ); expect(intervalId).toBeDefined(); expect(callback).not.toHaveBeenCalled(); @@ -43,7 +69,10 @@ describe("TaskSchedulerService", () => { }); it("clears scheduled tasks using the timeout id", async () => { - const timeoutId = await taskSchedulerService.setTimeout(callback, delayInMs); + const timeoutId = await taskSchedulerService.setTimeout( + ScheduledTaskNames.loginStrategySessionTimeout, + delayInMs, + ); expect(timeoutId).toBeDefined(); expect(callback).not.toHaveBeenCalled(); @@ -56,7 +85,10 @@ describe("TaskSchedulerService", () => { }); it("clears scheduled tasks using the interval id", async () => { - const intervalId = await taskSchedulerService.setInterval(callback, intervalInMs); + const intervalId = await taskSchedulerService.setInterval( + ScheduledTaskNames.loginStrategySessionTimeout, + intervalInMs, + ); expect(intervalId).toBeDefined(); expect(callback).not.toHaveBeenCalled(); diff --git a/libs/common/src/platform/services/default-task-scheduler.service.ts b/libs/common/src/platform/services/default-task-scheduler.service.ts index 139dccb1ca..75c5db84c7 100644 --- a/libs/common/src/platform/services/default-task-scheduler.service.ts +++ b/libs/common/src/platform/services/default-task-scheduler.service.ts @@ -1,26 +1,31 @@ +import { firstValueFrom } from "rxjs"; + import { LogService } from "../abstractions/log.service"; import { TaskIdentifier, TaskSchedulerService } from "../abstractions/task-scheduler.service"; import { ScheduledTaskName } from "../enums/scheduled-task-name.enum"; +import { StateProvider } from "../state"; export class DefaultTaskSchedulerService extends TaskSchedulerService { - constructor(logService: LogService) { - super(logService); + constructor(logService: LogService, stateProvider: StateProvider) { + super(logService, stateProvider); this.taskHandlers = new Map(); } - registerTaskHandler(taskName: ScheduledTaskName, handler: () => void): void { - const existingHandler = this.taskHandlers.get(taskName); + async registerTaskHandler(taskName: ScheduledTaskName, handler: () => void): Promise { + const activeUserTaskName = await this.getActiveUserTaskName(taskName); + const existingHandler = this.taskHandlers.get(activeUserTaskName); if (existingHandler) { this.logService.warning(`Task handler for ${taskName} already exists. Overwriting.`); - this.unregisterTaskHandler(taskName); + await this.unregisterTaskHandler(taskName); } - this.taskHandlers.set(taskName, handler); + this.taskHandlers.set(activeUserTaskName, handler); } - unregisterTaskHandler(taskName: ScheduledTaskName): void { - this.taskHandlers.delete(taskName); + async unregisterTaskHandler(taskName: ScheduledTaskName): Promise { + const activeUserTaskName = await this.getActiveUserTaskName(taskName); + this.taskHandlers.delete(activeUserTaskName); } protected triggerTask(taskName: ScheduledTaskName, _periodInMinutes?: number): void { @@ -72,4 +77,13 @@ export class DefaultTaskSchedulerService extends TaskSchedulerService { globalThis.clearInterval(taskIdentifier.intervalId); } } + + private async getActiveUserTaskName(taskName: ScheduledTaskName): Promise { + const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$); + if (!activeUserId) { + return taskName; + } + + return `${activeUserId}_${taskName}`; + } } diff --git a/libs/common/src/platform/services/system.service.ts b/libs/common/src/platform/services/system.service.ts index 9362974dcd..555970da7f 100644 --- a/libs/common/src/platform/services/system.service.ts +++ b/libs/common/src/platform/services/system.service.ts @@ -29,7 +29,7 @@ export class SystemService implements SystemServiceAbstraction { private biometricStateService: BiometricStateService, private taskSchedulerService: TaskSchedulerService, ) { - this.taskSchedulerService.registerTaskHandler( + void this.taskSchedulerService.registerTaskHandler( ScheduledTaskNames.systemClearClipboardTimeout, () => this.clearPendingClipboard(), ); diff --git a/libs/common/src/services/event/event-upload.service.ts b/libs/common/src/services/event/event-upload.service.ts index 447ba515a2..c340962861 100644 --- a/libs/common/src/services/event/event-upload.service.ts +++ b/libs/common/src/services/event/event-upload.service.ts @@ -23,8 +23,9 @@ export class EventUploadService implements EventUploadServiceAbstraction { private authService: AuthService, private taskSchedulerService: TaskSchedulerService, ) { - this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.eventUploadsInterval, () => - this.uploadEvents(), + void this.taskSchedulerService.registerTaskHandler( + ScheduledTaskNames.eventUploadsInterval, + () => this.uploadEvents(), ); } diff --git a/libs/common/src/services/notifications.service.ts b/libs/common/src/services/notifications.service.ts index ccdc05b1de..f9d2933a51 100644 --- a/libs/common/src/services/notifications.service.ts +++ b/libs/common/src/services/notifications.service.ts @@ -46,7 +46,7 @@ export class NotificationsService implements NotificationsServiceAbstraction { private messagingService: MessagingService, private taskSchedulerService: TaskSchedulerService, ) { - this.taskSchedulerService.registerTaskHandler( + void this.taskSchedulerService.registerTaskHandler( ScheduledTaskNames.notificationsReconnectTimeout, () => this.reconnect(this.isSyncingOnReconnect), ); diff --git a/libs/common/src/vault/services/fido2/fido2-client.service.ts b/libs/common/src/vault/services/fido2/fido2-client.service.ts index 71d2eaa869..cadb3a35cb 100644 --- a/libs/common/src/vault/services/fido2/fido2-client.service.ts +++ b/libs/common/src/vault/services/fido2/fido2-client.service.ts @@ -63,8 +63,9 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { private taskSchedulerService: TaskSchedulerService, private logService?: LogService, ) { - this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.fido2ClientAbortTimeout, () => - this.timeoutAbortController?.abort(), + void this.taskSchedulerService.registerTaskHandler( + ScheduledTaskNames.fido2ClientAbortTimeout, + () => this.timeoutAbortController?.abort(), ); }