1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

[PM-6426] Create TaskSchedulerService and update long lived timeouts in the extension to leverage the new service (#8566)

* [PM-6426] Create TaskSchedulerService and update usage of long lived timeouts

* [PM-6426] Implementing nextSync timeout using TaskScheduler

* [PM-6426] Implementing systemClearClipboard using TaskScheduler

* [PM-6426] Fixing race condition with setting/unsetting active alarms

* [PM-6426] Implementing clear clipboard call on generatePasswordToClipboard with the TaskSchedulerService

* [PM-6426] Implementing abortTimeout for Fido2ClientService using TaskSchedulerService

* [PM-6426] Implementing reconnect timer timeout for NotificationService using the TaskSchedulerService

* [PM-6426] Implementing reconnect timer timeout for NotificationService using the TaskSchedulerService

* [PM-6426] Implementing sessionTimeout for LoginStrategyService using TaskSchedulerService

* [PM-6426] Implementing eventUploadInterval using TaskScheduler

* [PM-6426] Adding jest tests for the base TaskSchedulerService class

* [PM-6426] Updating jest tests for GeneratePasswordToClipboardCommand

* [PM-6426] Setting up the full sync process as an interval rather than a timeout

* [PM-6426] Renaming the scheduleNextSync alarm name

* [PM-6426] Fixing dependency references in services.module.ts

* [PM-6426] Adding jest tests for added BrowserApi methods

* [PM-6426] Refactoring small detail for how we identify the clear clipboard timeout in SystemService

* [PM-6426] Ensuring that we await clearing an established scheduled task for the notification service

* [PM-6426] Changing the name of the state definition for the TaskScheduler

* [PM-6426] Implementing jest tests for the BrowserTaskSchedulerService

* [PM-6426] Implementing jest tests for the BrowserTaskSchedulerService

* [PM-6426] Adding jest tests for the base TaskSchedulerService class

* [PM-6426] Finalizing jest tests for BrowserTaskScheduler class

* [PM-6426] Finalizing documentation on BrowserTaskSchedulerService

* [PM-6426] Fixing jest test for LoginStrategyService

* [PM-6426] Implementing compatibility for the browser.alarms api

* [PM-6426] Fixing how we check for the browser alarms api

* [PM-6426] Adding jest tests to the BrowserApi implementation

* [PM-6426] Aligning the implementation with our code guidelines for Angular components

* [PM-6426] Fixing jest tests and lint errors

* [PM-6426] Moving alarms api calls out of BrowserApi and structuring them within the BrowserTaskSchedulerService

* [PM-6426] Reworking implementation to register handlers separately from the call to those handlers

* [PM-6426] Adjusting how we register the fullSync scheduled task

* [PM-6426] Implementing approach for incorporating the user UUID when setting task handlers

* [PM-6426] Attempting to re-work implementation to facilitate userId-spcific alarms

* [PM-6426] Refactoring smaller details of the implementation

* [PM-6426] Working through the details of the implementation and setting up final refinments

* [PM-6426] Fixing some issues surrounding duplicate alarms triggering

* [PM-6426] Adjusting name for generate password to clipboard command task name

* [PM-6426] Fixing generate password to clipboard command jest tests

* [PM-6426] Working through jest tests and implementing a method to guard against setting a task without having a registered callback

* [PM-6426] Working through jest tests and implementing a method to guard against setting a task without having a registered callback

* [PM-6426] Implementing methodology for having a fallback to setTimeout if the browser context is lost in some manner

* [PM-6426] Working through jest tests

* [PM-6426] Working through jest tests

* [PM-6426] Working through jest tests

* [PM-6426] Working through jest tests

* [PM-6426] Finalizing stepped setInterval implementation

* [PM-6426] Implementing Jest tests for DefaultTaskSchedulerService

* [PM-6426] Adjusting jest tests

* [PM-6426] Adjusting jest tests

* [PM-6426] Adjusting jest tests

* [PM-6426] Fixing issues identified in code review

* [PM-6426] Fixing issues identified in code review

* [PM-6426] Removing user-based alarms and fixing an issue found with setting steppedd alarm interavals

* [PM-6426] Removing user-based alarms and fixing an issue found with setting steppedd alarm interavals

* [PM-6426] Fixing issue with typing information on a test

* [PM-6426] Using the getUpperBoundDelayInMinutes method to handle setting stepped alarms and setTimeout fallbacks

* [PM-6426] Removing the potential for the TaskScheduler to be optional

* [PM-6426] Reworking implementation to leverage subscription based deregistration of alarms

* [PM-6426] Fixing jest tests

* [PM-6426] Implementing foreground and background task scheduler services to avoid duplication of task scheudlers and to have the background setup as a fallback to the poopup tasks

* [PM-6426] Implementing foreground and background task scheduler services to avoid duplication of task scheudlers and to have the background setup as a fallback to the poopup tasks

* [PM-6426] Merging main into branch

* [PM-6426] Fixing issues with the CLI Service Container implementation

* [PM-6426] Reworking swallowed promises to contain a catch statement allow us to debug potential issues with registrations of alarms

* [PM-6426] Adding jest tests to the ForegroundTaskSchedulerService and the BackgroundTaskSchedulerService

* [PM-6426] Adding jest tests to the ForegroundTaskSchedulerService and the BackgroundTaskSchedulerService

* [PM-6426] Adding jest tests to the ForegroundTaskSchedulerService and the BackgroundTaskSchedulerService

* [PM-6426] Adding jest tests to the ForegroundTaskSchedulerService and the BackgroundTaskSchedulerService

* [PM-6426] Adjusting implementation based on code review feedback

* [PM-6426] Reworking file structure

* [PM-6426] Reworking file structure

* [PM-6426] Adding comments to provide clarity on how the login strategy cache experiation state is used

* [PM-6426] Catching and logging erorrs that appear from methods that return a promise within VaultTimeoutService
This commit is contained in:
Cesar Gonzalez
2024-07-15 10:32:30 -05:00
committed by GitHub
parent 5fcf4bbd10
commit 974162b1a4
39 changed files with 1854 additions and 283 deletions

View File

@@ -0,0 +1,123 @@
import { mock, MockProxy } from "jest-mock-extended";
import { LogService } from "../abstractions/log.service";
import { ScheduledTaskNames } from "../scheduling/scheduled-task-name.enum";
import { DefaultTaskSchedulerService } from "./default-task-scheduler.service";
describe("DefaultTaskSchedulerService", () => {
const callback = jest.fn();
const delayInMs = 1000;
const intervalInMs = 1100;
let logService: MockProxy<LogService>;
let taskSchedulerService: DefaultTaskSchedulerService;
beforeEach(() => {
jest.useFakeTimers();
logService = mock<LogService>();
taskSchedulerService = new DefaultTaskSchedulerService(logService);
taskSchedulerService.registerTaskHandler(
ScheduledTaskNames.loginStrategySessionTimeout,
callback,
);
});
afterEach(() => {
jest.clearAllTimers();
jest.clearAllMocks();
});
it("triggers an error when setting a timeout for a task that is not registered", async () => {
expect(() =>
taskSchedulerService.setTimeout(ScheduledTaskNames.notificationsReconnectTimeout, 1000),
).toThrow(
`Task handler for ${ScheduledTaskNames.notificationsReconnectTimeout} not registered. Unable to schedule task.`,
);
});
it("triggers an error when setting an interval for a task that is not registered", async () => {
expect(() =>
taskSchedulerService.setInterval(ScheduledTaskNames.notificationsReconnectTimeout, 1000),
).toThrow(
`Task handler for ${ScheduledTaskNames.notificationsReconnectTimeout} not registered. Unable to schedule task.`,
);
});
it("overrides the handler for a previously registered task and provides a warning about the task registration", () => {
taskSchedulerService.registerTaskHandler(
ScheduledTaskNames.loginStrategySessionTimeout,
callback,
);
expect(logService.warning).toHaveBeenCalledWith(
`Task handler for ${ScheduledTaskNames.loginStrategySessionTimeout} already exists. Overwriting.`,
);
expect(
taskSchedulerService["taskHandlers"].get(ScheduledTaskNames.loginStrategySessionTimeout),
).toBeDefined();
});
it("sets a timeout and returns the timeout id", () => {
const timeoutId = taskSchedulerService.setTimeout(
ScheduledTaskNames.loginStrategySessionTimeout,
delayInMs,
);
expect(timeoutId).toBeDefined();
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(delayInMs);
expect(callback).toHaveBeenCalled();
});
it("sets an interval timeout and results the interval id", () => {
const intervalId = taskSchedulerService.setInterval(
ScheduledTaskNames.loginStrategySessionTimeout,
intervalInMs,
);
expect(intervalId).toBeDefined();
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(intervalInMs);
expect(callback).toHaveBeenCalled();
jest.advanceTimersByTime(intervalInMs);
expect(callback).toHaveBeenCalledTimes(2);
});
it("clears scheduled tasks using the timeout id", () => {
const timeoutHandle = taskSchedulerService.setTimeout(
ScheduledTaskNames.loginStrategySessionTimeout,
delayInMs,
);
expect(timeoutHandle).toBeDefined();
expect(callback).not.toHaveBeenCalled();
timeoutHandle.unsubscribe();
jest.advanceTimersByTime(delayInMs);
expect(callback).not.toHaveBeenCalled();
});
it("clears scheduled tasks using the interval id", () => {
const intervalHandle = taskSchedulerService.setInterval(
ScheduledTaskNames.loginStrategySessionTimeout,
intervalInMs,
);
expect(intervalHandle).toBeDefined();
expect(callback).not.toHaveBeenCalled();
intervalHandle.unsubscribe();
jest.advanceTimersByTime(intervalInMs);
expect(callback).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,97 @@
import { Subscription } from "rxjs";
import { LogService } from "../abstractions/log.service";
import { ScheduledTaskName } from "../scheduling/scheduled-task-name.enum";
import { TaskSchedulerService } from "../scheduling/task-scheduler.service";
export class DefaultTaskSchedulerService extends TaskSchedulerService {
constructor(protected logService: LogService) {
super();
this.taskHandlers = new Map();
}
/**
* Sets a timeout and returns the timeout id.
*
* @param taskName - The name of the task. Unused in the base implementation.
* @param delayInMs - The delay in milliseconds.
*/
setTimeout(taskName: ScheduledTaskName, delayInMs: number): Subscription {
this.validateRegisteredTask(taskName);
const timeoutHandle = globalThis.setTimeout(() => this.triggerTask(taskName), delayInMs);
return new Subscription(() => globalThis.clearTimeout(timeoutHandle));
}
/**
* Sets an interval and returns the interval id.
*
* @param taskName - The name of the task. Unused in the base implementation.
* @param intervalInMs - The interval in milliseconds.
* @param _initialDelayInMs - The initial delay in milliseconds. Unused in the base implementation.
*/
setInterval(
taskName: ScheduledTaskName,
intervalInMs: number,
_initialDelayInMs?: number,
): Subscription {
this.validateRegisteredTask(taskName);
const intervalHandle = globalThis.setInterval(() => this.triggerTask(taskName), intervalInMs);
return new Subscription(() => globalThis.clearInterval(intervalHandle));
}
/**
* Registers a task handler.
*
* @param taskName - The name of the task.
* @param handler - The task handler.
*/
registerTaskHandler(taskName: ScheduledTaskName, handler: () => void) {
const existingHandler = this.taskHandlers.get(taskName);
if (existingHandler) {
this.logService.warning(`Task handler for ${taskName} already exists. Overwriting.`);
this.unregisterTaskHandler(taskName);
}
this.taskHandlers.set(taskName, handler);
}
/**
* Unregisters a task handler.
*
* @param taskName - The name of the task.
*/
unregisterTaskHandler(taskName: ScheduledTaskName) {
this.taskHandlers.delete(taskName);
}
/**
* Triggers a task.
*
* @param taskName - The name of the task.
* @param _periodInMinutes - The period in minutes. Unused in the base implementation.
*/
protected async triggerTask(
taskName: ScheduledTaskName,
_periodInMinutes?: number,
): Promise<void> {
const handler = this.taskHandlers.get(taskName);
if (handler) {
handler();
}
}
/**
* Validates that a task handler is registered.
*
* @param taskName - The name of the task.
*/
protected validateRegisteredTask(taskName: ScheduledTaskName): void {
if (!this.taskHandlers.has(taskName)) {
throw new Error(`Task handler for ${taskName} not registered. Unable to schedule task.`);
}
}
}

View File

@@ -0,0 +1,3 @@
export { TaskSchedulerService } from "./task-scheduler.service";
export { DefaultTaskSchedulerService } from "./default-task-scheduler.service";
export { ScheduledTaskNames, ScheduledTaskName } from "./scheduled-task-name.enum";

View File

@@ -0,0 +1,12 @@
export const ScheduledTaskNames = {
generatePasswordClearClipboardTimeout: "generatePasswordClearClipboardTimeout",
systemClearClipboardTimeout: "systemClearClipboardTimeout",
loginStrategySessionTimeout: "loginStrategySessionTimeout",
notificationsReconnectTimeout: "notificationsReconnectTimeout",
fido2ClientAbortTimeout: "fido2ClientAbortTimeout",
scheduleNextSyncInterval: "scheduleNextSyncInterval",
eventUploadsInterval: "eventUploadsInterval",
vaultTimeoutCheckInterval: "vaultTimeoutCheckInterval",
} as const;
export type ScheduledTaskName = (typeof ScheduledTaskNames)[keyof typeof ScheduledTaskNames];

View File

@@ -0,0 +1,16 @@
import { Subscription } from "rxjs";
import { ScheduledTaskName } from "./scheduled-task-name.enum";
export abstract class TaskSchedulerService {
protected taskHandlers: Map<string, () => void>;
abstract setTimeout(taskName: ScheduledTaskName, delayInMs: number): Subscription;
abstract setInterval(
taskName: ScheduledTaskName,
intervalInMs: number,
initialDelayInMs?: number,
): Subscription;
abstract registerTaskHandler(taskName: ScheduledTaskName, handler: () => void): void;
abstract unregisterTaskHandler(taskName: ScheduledTaskName): void;
protected abstract triggerTask(taskName: ScheduledTaskName, periodInMinutes?: number): void;
}