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

This commit is contained in:
Cesar Gonzalez 2024-04-17 16:16:13 -05:00
parent 6cb2577f90
commit 6beab4fbd2
No known key found for this signature in database
GPG Key ID: 3381A5457F8CCECF
5 changed files with 72 additions and 57 deletions

View File

@ -2,7 +2,7 @@ import { TaskSchedulerService } from "@bitwarden/common/platform/abstractions/ta
import { ScheduledTaskName } from "@bitwarden/common/platform/enums/scheduled-task-name.enum";
export type ActiveAlarm = {
name: ScheduledTaskName;
taskName: ScheduledTaskName;
startTime: number;
createInfo: chrome.alarms.AlarmCreateInfo;
};

View File

@ -30,15 +30,15 @@ describe("BrowserTaskSchedulerService", () => {
jest.useFakeTimers();
activeAlarms = [
mock<ActiveAlarm>({
name: ScheduledTaskNames.eventUploadsInterval,
taskName: ScheduledTaskNames.eventUploadsInterval,
createInfo: eventUploadsIntervalCreateInfo,
}),
mock<ActiveAlarm>({
name: ScheduledTaskNames.scheduleNextSyncInterval,
taskName: ScheduledTaskNames.scheduleNextSyncInterval,
createInfo: scheduleNextSyncIntervalCreateInfo,
}),
mock<ActiveAlarm>({
name: ScheduledTaskNames.fido2ClientAbortTimeout,
taskName: ScheduledTaskNames.fido2ClientAbortTimeout,
startTime: Date.now() - 60001,
createInfo: { delayInMinutes: 1, periodInMinutes: undefined },
}),
@ -131,7 +131,9 @@ describe("BrowserTaskSchedulerService", () => {
});
it("triggers a recovered alarm immediately and skips creating the alarm", async () => {
activeAlarms = [mock<ActiveAlarm>({ name: ScheduledTaskNames.loginStrategySessionTimeout })];
activeAlarms = [
mock<ActiveAlarm>({ taskName: ScheduledTaskNames.loginStrategySessionTimeout }),
];
browserTaskSchedulerService["recoveredAlarms"].add(
ScheduledTaskNames.loginStrategySessionTimeout,
);
@ -234,7 +236,9 @@ describe("BrowserTaskSchedulerService", () => {
it("triggers a recovered alarm before creating the interval alarm", async () => {
const periodInMinutes = 4;
activeAlarms = [mock<ActiveAlarm>({ name: ScheduledTaskNames.loginStrategySessionTimeout })];
activeAlarms = [
mock<ActiveAlarm>({ taskName: ScheduledTaskNames.loginStrategySessionTimeout }),
];
browserTaskSchedulerService["recoveredAlarms"].add(
ScheduledTaskNames.loginStrategySessionTimeout,
);

View File

@ -132,21 +132,26 @@ export class BrowserTaskSchedulerService
/**
* Creates a browser extension alarm with the given name and create info.
*
* @param name - The name of the alarm.
* @param taskName - The name of the alarm.
* @param createInfo - The alarm create info.
*/
private async scheduleAlarm(
name: ScheduledTaskName,
taskName: ScheduledTaskName,
createInfo: chrome.alarms.AlarmCreateInfo,
): Promise<void> {
const existingAlarm = await this.getAlarm(name);
const existingAlarm = await this.getAlarm(taskName);
if (existingAlarm) {
this.logService.warning(`Alarm ${name} already exists. Skipping creation.`);
this.logService.warning(`Alarm ${taskName} already exists. Skipping creation.`);
return;
}
await this.createAlarm(name, createInfo);
await this.setActiveAlarm({ name, startTime: Date.now(), createInfo });
await this.createAlarm(taskName, createInfo);
await this.setActiveAlarm({
taskName,
startTime: Date.now(),
createInfo,
});
}
/**
@ -158,8 +163,8 @@ export class BrowserTaskSchedulerService
const activeAlarms = await firstValueFrom(this.activeAlarms$);
for (const alarm of activeAlarms) {
const { name, startTime, createInfo } = alarm;
const existingAlarm = await this.getAlarm(name);
const { taskName, startTime, createInfo } = alarm;
const existingAlarm = await this.getAlarm(taskName);
if (existingAlarm) {
continue;
}
@ -170,11 +175,11 @@ export class BrowserTaskSchedulerService
createInfo.delayInMinutes &&
startTime + createInfo.delayInMinutes * 60 * 1000 < currentTime;
if (shouldAlarmHaveBeenTriggered || hasSetTimeoutAlarmExceededDelay) {
this.recoveredAlarms.add(name);
this.recoveredAlarms.add(taskName);
continue;
}
void this.scheduleAlarm(name, createInfo);
void this.scheduleAlarm(taskName, createInfo);
}
// 10 seconds after verifying the alarm state, we should treat any newly created alarms as non-recovered alarms.
@ -195,11 +200,11 @@ export class BrowserTaskSchedulerService
/**
* Deletes an active alarm from state.
*
* @param name - The name of the active alarm to delete.
* @param taskName - The name of the active alarm to delete.
*/
private async deleteActiveAlarm(name: ScheduledTaskName): Promise<void> {
private async deleteActiveAlarm(taskName: ScheduledTaskName): Promise<void> {
const activeAlarms = await firstValueFrom(this.activeAlarms$);
const filteredAlarms = activeAlarms?.filter((alarm) => alarm.name !== name);
const filteredAlarms = activeAlarms?.filter((alarm) => alarm.taskName !== taskName);
await this.updateActiveAlarms(filteredAlarms || []);
}
@ -247,13 +252,17 @@ export class BrowserTaskSchedulerService
* Triggers an alarm by calling its handler and
* deleting it if it is a one-time alarm.
*
* @param name - The name of the alarm to trigger.
* @param alarmName - The name of the alarm to trigger.
* @param periodInMinutes - The period in minutes of an interval alarm.
*/
protected async triggerTask(name: ScheduledTaskName, periodInMinutes?: number): Promise<void> {
const handler = this.taskHandlers.get(name);
protected async triggerTask(
alarmName: ScheduledTaskName,
periodInMinutes?: number,
): Promise<void> {
const activeUserAlarmName = await this.getActiveUserAlarmName(alarmName);
const handler = this.taskHandlers.get(activeUserAlarmName);
if (!periodInMinutes) {
await this.deleteActiveAlarm(name);
await this.deleteActiveAlarm(alarmName);
}
if (handler) {
@ -265,14 +274,15 @@ export class BrowserTaskSchedulerService
* Clears a new alarm with the given name and create info. Returns a promise
* that indicates when the alarm has been cleared successfully.
*
* @param alarmName - The name of the alarm to create.
* @param taskName - The name of the alarm to create.
*/
clearAlarm(alarmName: string): Promise<boolean> {
async clearAlarm(taskName: string): Promise<boolean> {
const activeUserAlarmName = await this.getActiveUserAlarmName(taskName);
if (typeof browser !== "undefined" && browser.alarms) {
return browser.alarms.clear(alarmName);
return browser.alarms.clear(activeUserAlarmName);
}
return new Promise((resolve) => chrome.alarms.clear(alarmName, resolve));
return new Promise((resolve) => chrome.alarms.clear(activeUserAlarmName, resolve));
}
/**
@ -290,28 +300,30 @@ export class BrowserTaskSchedulerService
/**
* Creates a new alarm with the given name and create info.
*
* @param name - The name of the alarm to create.
* @param taskName - The name of the alarm to create.
* @param createInfo - The creation info for the alarm.
*/
async createAlarm(name: string, createInfo: chrome.alarms.AlarmCreateInfo): Promise<void> {
async createAlarm(taskName: string, createInfo: chrome.alarms.AlarmCreateInfo): Promise<void> {
const activeUserAlarmName = await this.getActiveUserAlarmName(taskName);
if (typeof browser !== "undefined" && browser.alarms) {
return browser.alarms.create(name, createInfo);
return browser.alarms.create(activeUserAlarmName, createInfo);
}
return new Promise((resolve) => chrome.alarms.create(name, createInfo, resolve));
return new Promise((resolve) => chrome.alarms.create(activeUserAlarmName, createInfo, resolve));
}
/**
* Gets the alarm with the given name.
*
* @param alarmName - The name of the alarm to get.
* @param taskName - The name of the alarm to get.
*/
getAlarm(alarmName: string): Promise<chrome.alarms.Alarm> {
async getAlarm(taskName: string): Promise<chrome.alarms.Alarm> {
const activeUserAlarmName = await this.getActiveUserAlarmName(taskName);
if (typeof browser !== "undefined" && browser.alarms) {
return browser.alarms.get(alarmName);
return browser.alarms.get(activeUserAlarmName);
}
return new Promise((resolve) => chrome.alarms.get(alarmName, resolve));
return new Promise((resolve) => chrome.alarms.get(activeUserAlarmName, resolve));
}
/**
@ -324,4 +336,13 @@ export class BrowserTaskSchedulerService
return new Promise((resolve) => chrome.alarms.getAll(resolve));
}
protected async getActiveUserAlarmName(taskName: string): Promise<string> {
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
if (!activeUserId) {
return taskName;
}
return `${activeUserId}_${taskName}`;
}
}

View File

@ -17,9 +17,9 @@ export abstract class TaskSchedulerService {
protected stateProvider: StateProvider,
) {}
abstract registerTaskHandler(taskName: ScheduledTaskName, handler: () => void): Promise<void>;
abstract registerTaskHandler(taskName: ScheduledTaskName, handler: () => void): void;
abstract unregisterTaskHandler(taskName: ScheduledTaskName): Promise<void>;
abstract unregisterTaskHandler(taskName: ScheduledTaskName): void;
abstract setTimeout(
taskName: ScheduledTaskName,

View File

@ -1,5 +1,3 @@
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";
@ -12,23 +10,24 @@ export class DefaultTaskSchedulerService extends TaskSchedulerService {
this.taskHandlers = new Map();
}
async registerTaskHandler(taskName: ScheduledTaskName, handler: () => void): Promise<void> {
const activeUserTaskName = await this.getActiveUserTaskName(taskName);
const existingHandler = this.taskHandlers.get(activeUserTaskName);
registerTaskHandler(taskName: ScheduledTaskName, handler: () => void) {
const existingHandler = this.taskHandlers.get(taskName);
if (existingHandler) {
this.logService.warning(`Task handler for ${taskName} already exists. Overwriting.`);
await this.unregisterTaskHandler(taskName);
this.unregisterTaskHandler(taskName);
}
this.taskHandlers.set(activeUserTaskName, handler);
this.taskHandlers.set(taskName, handler);
}
async unregisterTaskHandler(taskName: ScheduledTaskName): Promise<void> {
const activeUserTaskName = await this.getActiveUserTaskName(taskName);
this.taskHandlers.delete(activeUserTaskName);
unregisterTaskHandler(taskName: ScheduledTaskName) {
this.taskHandlers.delete(taskName);
}
protected triggerTask(taskName: ScheduledTaskName, _periodInMinutes?: number): void {
protected async triggerTask(
taskName: ScheduledTaskName,
_periodInMinutes?: number,
): Promise<void> {
const handler = this.taskHandlers.get(taskName);
if (handler) {
handler();
@ -77,13 +76,4 @@ export class DefaultTaskSchedulerService extends TaskSchedulerService {
globalThis.clearInterval(taskIdentifier.intervalId);
}
}
private async getActiveUserTaskName(taskName: ScheduledTaskName): Promise<string> {
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
if (!activeUserId) {
return taskName;
}
return `${activeUserId}_${taskName}`;
}
}