1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-08-26 23:09:46 +02:00

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

This commit is contained in:
Cesar Gonzalez 2024-04-23 10:52:58 -05:00
parent e7d1769f50
commit f0f4bd5af1
No known key found for this signature in database
GPG Key ID: 3381A5457F8CCECF
10 changed files with 302 additions and 358 deletions

View File

@ -503,14 +503,20 @@ export default class MainBackground {
this.globalStateProvider,
this.derivedStateProvider,
);
this.taskSchedulerService = new BrowserTaskSchedulerService(
this.logService,
this.stateProvider,
);
void this.taskSchedulerService.registerTaskHandler(
ScheduledTaskNames.scheduleNextSyncInterval,
() => this.fullSync(),
);
// The taskSchedulerService needs to be instantiated a single time in a potential context.
// Since the popup creates a new instance of the main background in mv3, we need to guard against a duplicate registration.
if (!this.popupOnlyContext) {
this.taskSchedulerService = new BrowserTaskSchedulerService(
this.logService,
this.stateProvider,
);
void this.taskSchedulerService.registerTaskHandler(
ScheduledTaskNames.scheduleNextSyncInterval,
() => this.fullSync(),
);
}
this.environmentService = new BrowserEnvironmentService(
this.logService,
this.stateProvider,
@ -921,13 +927,13 @@ export default class MainBackground {
this.logService,
);
const systemUtilsServiceReloadCallback = () => {
const systemUtilsServiceReloadCallback = async () => {
const forceWindowReload =
this.platformUtilsService.isSafari() ||
this.platformUtilsService.isFirefox() ||
this.platformUtilsService.isOpera();
await this.taskSchedulerService?.clearAllScheduledTasks();
BrowserApi.reloadExtension(forceWindowReload ? self : null);
return Promise.resolve();
};
this.systemService = new SystemService(
@ -1162,12 +1168,12 @@ export default class MainBackground {
}
await this.fullSync(true);
await this.taskSchedulerService.setInterval(
await this.taskSchedulerService?.setInterval(
ScheduledTaskNames.scheduleNextSyncInterval,
5 * 60 * 1000, // check every 5 minutes
);
setTimeout(() => this.notificationsService.init(), 2500);
await this.taskSchedulerService.verifyAlarmsState();
await this.taskSchedulerService?.verifyAlarmsState();
resolve();
}, 500);
});
@ -1244,7 +1250,6 @@ export default class MainBackground {
userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.eventUploadService.uploadEvents(userId as UserId);
await this.taskSchedulerService.clearAllScheduledTasks();
await Promise.all([
this.syncService.setLastSync(new Date(0), userId),

View File

@ -60,7 +60,8 @@
"clipboardWrite",
"idle",
"scripting",
"offscreen"
"offscreen",
"alarms"
],
"optional_permissions": ["nativeMessaging", "privacy"],
"host_permissions": ["<all_urls>"],

View File

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

View File

@ -30,15 +30,15 @@ describe("BrowserTaskSchedulerService", () => {
jest.useFakeTimers();
activeAlarms = [
mock<ActiveAlarm>({
taskName: ScheduledTaskNames.eventUploadsInterval,
alarmName: ScheduledTaskNames.eventUploadsInterval,
createInfo: eventUploadsIntervalCreateInfo,
}),
mock<ActiveAlarm>({
taskName: ScheduledTaskNames.scheduleNextSyncInterval,
alarmName: ScheduledTaskNames.scheduleNextSyncInterval,
createInfo: scheduleNextSyncIntervalCreateInfo,
}),
mock<ActiveAlarm>({
taskName: ScheduledTaskNames.fido2ClientAbortTimeout,
alarmName: ScheduledTaskNames.fido2ClientAbortTimeout,
startTime: Date.now() - 60001,
createInfo: { delayInMinutes: 1, periodInMinutes: undefined },
}),
@ -87,24 +87,6 @@ describe("BrowserTaskSchedulerService", () => {
expect.any(Function),
);
});
it("adds the alarm name to the set of recovered alarms if the alarm create info indicates it has expired", () => {
expect(
browserTaskSchedulerService["recoveredAlarms"].has(
ScheduledTaskNames.fido2ClientAbortTimeout,
),
).toBe(true);
});
it("clears the list of recovered alarms after 10 seconds", () => {
jest.advanceTimersByTime(10 * 1000);
expect(
browserTaskSchedulerService["recoveredAlarms"].has(
ScheduledTaskNames.fido2ClientAbortTimeout,
),
).toBe(false);
});
});
describe("setTimeout", () => {
@ -130,32 +112,6 @@ describe("BrowserTaskSchedulerService", () => {
);
});
it("triggers a recovered alarm immediately and skips creating the alarm", async () => {
activeAlarms = [
mock<ActiveAlarm>({ taskName: ScheduledTaskNames.loginStrategySessionTimeout }),
];
browserTaskSchedulerService["recoveredAlarms"].add(
ScheduledTaskNames.loginStrategySessionTimeout,
);
const callback = jest.fn();
await browserTaskSchedulerService.registerTaskHandler(
ScheduledTaskNames.loginStrategySessionTimeout,
callback,
);
await browserTaskSchedulerService.setTimeout(
ScheduledTaskNames.loginStrategySessionTimeout,
60 * 1000,
);
expect(callback).toHaveBeenCalled();
expect(chrome.alarms.create).not.toHaveBeenCalledWith(
ScheduledTaskNames.loginStrategySessionTimeout,
{ delayInMinutes: 1 },
expect.any(Function),
);
});
it("creates a timeout alarm", async () => {
const callback = jest.fn();
const delayInMinutes = 2;
@ -176,27 +132,27 @@ describe("BrowserTaskSchedulerService", () => {
);
});
it("skips creating a duplicate timeout alarm", async () => {
const callback = jest.fn();
const delayInMinutes = 2;
jest.spyOn(browserTaskSchedulerService as any, "getAlarm").mockResolvedValue(
mock<chrome.alarms.Alarm>({
name: ScheduledTaskNames.loginStrategySessionTimeout,
}),
);
jest.spyOn(browserTaskSchedulerService, "createAlarm");
await browserTaskSchedulerService.registerTaskHandler(
ScheduledTaskNames.loginStrategySessionTimeout,
callback,
);
await browserTaskSchedulerService.setTimeout(
ScheduledTaskNames.loginStrategySessionTimeout,
delayInMinutes * 60 * 1000,
);
expect(browserTaskSchedulerService.createAlarm).not.toHaveBeenCalled();
});
// it("skips creating a duplicate timeout alarm", async () => {
// const callback = jest.fn();
// const delayInMinutes = 2;
// jest.spyOn(browserTaskSchedulerService as any, "getAlarm").mockResolvedValue(
// mock<chrome.alarms.Alarm>({
// name: ScheduledTaskNames.loginStrategySessionTimeout,
// }),
// );
// jest.spyOn(browserTaskSchedulerService, "createAlarm");
// await browserTaskSchedulerService.registerTaskHandler(
// ScheduledTaskNames.loginStrategySessionTimeout,
// callback,
// );
//
// await browserTaskSchedulerService.setTimeout(
// ScheduledTaskNames.loginStrategySessionTimeout,
// delayInMinutes * 60 * 1000,
// );
//
// expect(browserTaskSchedulerService.createAlarm).not.toHaveBeenCalled();
// });
// it("logs a warning if a duplicate handler is registered when creating an alarm", () => {
// const callback = jest.fn();
@ -234,33 +190,6 @@ describe("BrowserTaskSchedulerService", () => {
);
});
it("triggers a recovered alarm before creating the interval alarm", async () => {
const periodInMinutes = 4;
activeAlarms = [
mock<ActiveAlarm>({ taskName: ScheduledTaskNames.loginStrategySessionTimeout }),
];
browserTaskSchedulerService["recoveredAlarms"].add(
ScheduledTaskNames.loginStrategySessionTimeout,
);
const callback = jest.fn();
await browserTaskSchedulerService.registerTaskHandler(
ScheduledTaskNames.loginStrategySessionTimeout,
callback,
);
await browserTaskSchedulerService.setInterval(
ScheduledTaskNames.loginStrategySessionTimeout,
periodInMinutes * 60 * 1000,
);
expect(callback).toHaveBeenCalled();
expect(chrome.alarms.create).toHaveBeenCalledWith(
ScheduledTaskNames.loginStrategySessionTimeout,
{ periodInMinutes, delayInMinutes: periodInMinutes },
expect.any(Function),
);
});
it("creates an interval alarm", async () => {
const callback = jest.fn();
const periodInMinutes = 2;
@ -324,19 +253,19 @@ describe("BrowserTaskSchedulerService", () => {
});
});
describe("clearAllScheduledTasks", () => {
it("clears all scheduled tasks and extension alarms", async () => {
jest.spyOn(browserTaskSchedulerService, "clearAllAlarms");
jest.spyOn(browserTaskSchedulerService as any, "updateActiveAlarms");
await browserTaskSchedulerService.clearAllScheduledTasks();
expect(browserTaskSchedulerService.clearAllAlarms).toHaveBeenCalled();
expect(browserTaskSchedulerService["updateActiveAlarms"]).toHaveBeenCalledWith([]);
// expect(browserTaskSchedulerService["onAlarmHandlers"]).toEqual({});
expect(browserTaskSchedulerService["recoveredAlarms"].size).toBe(0);
});
});
// describe("clearAllScheduledTasks", () => {
// it("clears all scheduled tasks and extension alarms", async () => {
// jest.spyOn(browserTaskSchedulerService, "clearAllAlarms");
// jest.spyOn(browserTaskSchedulerService as any, "updateActiveAlarms");
//
// await browserTaskSchedulerService.clearAllScheduledTasks();
//
// expect(browserTaskSchedulerService.clearAllAlarms).toHaveBeenCalled();
// expect(browserTaskSchedulerService["updateActiveAlarms"]).toHaveBeenCalledWith([]);
// // expect(browserTaskSchedulerService["onAlarmHandlers"]).toEqual({});
// expect(browserTaskSchedulerService["recoveredAlarms"].size).toBe(0);
// });
// });
// describe("handleOnAlarm", () => {
// it("triggers the alarm", async () => {
@ -350,133 +279,133 @@ describe("BrowserTaskSchedulerService", () => {
// });
// });
describe("clearAlarm", () => {
it("uses the browser.alarms API if it is available", async () => {
const alarmName = "alarm-name";
globalThis.browser = {
// eslint-disable-next-line
// @ts-ignore
alarms: {
clear: jest.fn(),
},
};
await browserTaskSchedulerService.clearAlarm(alarmName);
expect(browser.alarms.clear).toHaveBeenCalledWith(alarmName);
});
it("clears the alarm with the provided name", async () => {
const alarmName = "alarm-name";
const wasCleared = await browserTaskSchedulerService.clearAlarm(alarmName);
expect(chrome.alarms.clear).toHaveBeenCalledWith(alarmName, expect.any(Function));
expect(wasCleared).toBe(true);
});
});
describe("clearAllAlarms", () => {
it("uses the browser.alarms API if it is available", async () => {
globalThis.browser = {
// eslint-disable-next-line
// @ts-ignore
alarms: {
clearAll: jest.fn(),
},
};
await browserTaskSchedulerService.clearAllAlarms();
expect(browser.alarms.clearAll).toHaveBeenCalled();
});
it("clears all alarms", async () => {
const wasCleared = await browserTaskSchedulerService.clearAllAlarms();
expect(chrome.alarms.clearAll).toHaveBeenCalledWith(expect.any(Function));
expect(wasCleared).toBe(true);
});
});
describe("createAlarm", () => {
it("uses the browser.alarms API if it is available", async () => {
const alarmName = "alarm-name";
const alarmInfo = { when: 1000 };
globalThis.browser = {
// eslint-disable-next-line
// @ts-ignore
alarms: {
create: jest.fn(),
},
};
await browserTaskSchedulerService.createAlarm(alarmName, alarmInfo);
expect(browser.alarms.create).toHaveBeenCalledWith(alarmName, alarmInfo);
});
it("creates an alarm", async () => {
const alarmName = "alarm-name";
const alarmInfo = { when: 1000 };
await browserTaskSchedulerService.createAlarm(alarmName, alarmInfo);
expect(chrome.alarms.create).toHaveBeenCalledWith(alarmName, alarmInfo, expect.any(Function));
});
});
describe.skip("getAlarm", () => {
it("uses the browser.alarms API if it is available", async () => {
const alarmName = "alarm-name";
globalThis.browser = {
// eslint-disable-next-line
// @ts-ignore
alarms: {
get: jest.fn(),
},
};
await browserTaskSchedulerService.getAlarm(alarmName);
expect(browser.alarms.get).toHaveBeenCalledWith(alarmName);
});
it("gets the alarm by name", async () => {
const alarmName = "alarm-name";
const alarmMock = mock<chrome.alarms.Alarm>();
chrome.alarms.get = jest.fn().mockImplementation((_name, callback) => callback(alarmMock));
const receivedAlarm = await browserTaskSchedulerService.getAlarm(alarmName);
expect(chrome.alarms.get).toHaveBeenCalledWith(alarmName, expect.any(Function));
expect(receivedAlarm).toBe(alarmMock);
});
});
describe("getAllAlarms", () => {
it("uses the browser.alarms API if it is available", async () => {
globalThis.browser = {
// eslint-disable-next-line
// @ts-ignore
alarms: {
getAll: jest.fn(),
},
};
await browserTaskSchedulerService.getAllAlarms();
expect(browser.alarms.getAll).toHaveBeenCalled();
});
it("gets all registered alarms", async () => {
const alarms = [mock<chrome.alarms.Alarm>(), mock<chrome.alarms.Alarm>()];
chrome.alarms.getAll = jest.fn().mockImplementation((callback) => callback(alarms));
const receivedAlarms = await browserTaskSchedulerService.getAllAlarms();
expect(chrome.alarms.getAll).toHaveBeenCalledWith(expect.any(Function));
expect(receivedAlarms).toBe(alarms);
});
});
// describe("clearAlarm", () => {
// it("uses the browser.alarms API if it is available", async () => {
// const alarmName = "alarm-name";
// globalThis.browser = {
// // eslint-disable-next-line
// // @ts-ignore
// alarms: {
// clear: jest.fn(),
// },
// };
//
// await browserTaskSchedulerService.clearAlarm(alarmName);
//
// expect(browser.alarms.clear).toHaveBeenCalledWith(alarmName);
// });
//
// it("clears the alarm with the provided name", async () => {
// const alarmName = "alarm-name";
//
// const wasCleared = await browserTaskSchedulerService.clearAlarm(alarmName);
//
// expect(chrome.alarms.clear).toHaveBeenCalledWith(alarmName, expect.any(Function));
// expect(wasCleared).toBe(true);
// });
// });
//
// describe("clearAllAlarms", () => {
// it("uses the browser.alarms API if it is available", async () => {
// globalThis.browser = {
// // eslint-disable-next-line
// // @ts-ignore
// alarms: {
// clearAll: jest.fn(),
// },
// };
//
// await browserTaskSchedulerService.clearAllAlarms();
//
// expect(browser.alarms.clearAll).toHaveBeenCalled();
// });
//
// it("clears all alarms", async () => {
// const wasCleared = await browserTaskSchedulerService.clearAllAlarms();
//
// expect(chrome.alarms.clearAll).toHaveBeenCalledWith(expect.any(Function));
// expect(wasCleared).toBe(true);
// });
// });
//
// describe("createAlarm", () => {
// it("uses the browser.alarms API if it is available", async () => {
// const alarmName = "alarm-name";
// const alarmInfo = { when: 1000 };
// globalThis.browser = {
// // eslint-disable-next-line
// // @ts-ignore
// alarms: {
// create: jest.fn(),
// },
// };
//
// await browserTaskSchedulerService.createAlarm(alarmName, alarmInfo);
//
// expect(browser.alarms.create).toHaveBeenCalledWith(alarmName, alarmInfo);
// });
//
// it("creates an alarm", async () => {
// const alarmName = "alarm-name";
// const alarmInfo = { when: 1000 };
//
// await browserTaskSchedulerService.createAlarm(alarmName, alarmInfo);
//
// expect(chrome.alarms.create).toHaveBeenCalledWith(alarmName, alarmInfo, expect.any(Function));
// });
// });
//
// describe.skip("getAlarm", () => {
// it("uses the browser.alarms API if it is available", async () => {
// const alarmName = "alarm-name";
// globalThis.browser = {
// // eslint-disable-next-line
// // @ts-ignore
// alarms: {
// get: jest.fn(),
// },
// };
//
// await browserTaskSchedulerService.getAlarm(alarmName);
//
// expect(browser.alarms.get).toHaveBeenCalledWith(alarmName);
// });
//
// it("gets the alarm by name", async () => {
// const alarmName = "alarm-name";
// const alarmMock = mock<chrome.alarms.Alarm>();
// chrome.alarms.get = jest.fn().mockImplementation((_name, callback) => callback(alarmMock));
//
// const receivedAlarm = await browserTaskSchedulerService.getAlarm(alarmName);
//
// expect(chrome.alarms.get).toHaveBeenCalledWith(alarmName, expect.any(Function));
// expect(receivedAlarm).toBe(alarmMock);
// });
// });
//
// describe("getAllAlarms", () => {
// it("uses the browser.alarms API if it is available", async () => {
// globalThis.browser = {
// // eslint-disable-next-line
// // @ts-ignore
// alarms: {
// getAll: jest.fn(),
// },
// };
//
// await browserTaskSchedulerService.getAllAlarms();
//
// expect(browser.alarms.getAll).toHaveBeenCalled();
// });
//
// it("gets all registered alarms", async () => {
// const alarms = [mock<chrome.alarms.Alarm>(), mock<chrome.alarms.Alarm>()];
// chrome.alarms.getAll = jest.fn().mockImplementation((callback) => callback(alarms));
//
// const receivedAlarms = await browserTaskSchedulerService.getAllAlarms();
//
// expect(chrome.alarms.getAll).toHaveBeenCalledWith(expect.any(Function));
// expect(receivedAlarms).toBe(alarms);
// });
// });
});

View File

@ -28,7 +28,6 @@ export class BrowserTaskSchedulerService
{
private activeAlarmsState: GlobalState<ActiveAlarm[]>;
readonly activeAlarms$: Observable<ActiveAlarm[]>;
private recoveredAlarms: Set<string> = new Set();
constructor(logService: LogService, stateProvider: StateProvider) {
super(logService, stateProvider);
@ -58,12 +57,8 @@ export class BrowserTaskSchedulerService
return super.setTimeout(taskName, delayInMs);
}
if (this.recoveredAlarms.has(taskName)) {
await this.triggerRecoveredAlarm(taskName);
return;
}
await this.scheduleAlarm(taskName, { delayInMinutes });
const alarmName = await this.getActiveUserAlarmName(taskName);
await this.scheduleAlarm(alarmName, { delayInMinutes });
}
/**
@ -85,12 +80,9 @@ export class BrowserTaskSchedulerService
return super.setInterval(taskName, intervalInMs);
}
if (this.recoveredAlarms.has(taskName)) {
await this.triggerRecoveredAlarm(taskName, intervalInMinutes);
}
const alarmName = await this.getActiveUserAlarmName(taskName);
const initialDelayInMinutes = initialDelayInMs ? initialDelayInMs / 1000 / 60 : undefined;
await this.scheduleAlarm(taskName, {
await this.scheduleAlarm(alarmName, {
periodInMinutes: intervalInMinutes,
delayInMinutes: initialDelayInMinutes ?? intervalInMinutes,
});
@ -111,10 +103,10 @@ export class BrowserTaskSchedulerService
return;
}
const wasCleared = await this.clearAlarm(taskName);
const alarmName = await this.getActiveUserAlarmName(taskName);
const wasCleared = await this.clearAlarm(alarmName);
if (wasCleared) {
await this.deleteActiveAlarm(taskName);
this.recoveredAlarms.delete(taskName);
await this.deleteActiveAlarm(alarmName);
}
}
@ -125,7 +117,6 @@ export class BrowserTaskSchedulerService
async clearAllScheduledTasks(): Promise<void> {
await this.clearAllAlarms();
await this.updateActiveAlarms([]);
this.recoveredAlarms.clear();
}
/**
@ -137,8 +128,12 @@ export class BrowserTaskSchedulerService
const activeAlarms = await firstValueFrom(this.activeAlarms$);
for (const alarm of activeAlarms) {
const { taskName, startTime, createInfo } = alarm;
const existingAlarm = await this.getAlarm(taskName);
const { alarmName, startTime, createInfo } = alarm;
if (!alarmName) {
return;
}
const existingAlarm = await this.getAlarm(alarmName);
if (existingAlarm) {
continue;
}
@ -149,61 +144,72 @@ export class BrowserTaskSchedulerService
createInfo.delayInMinutes &&
startTime + createInfo.delayInMinutes * 60 * 1000 < currentTime;
if (shouldAlarmHaveBeenTriggered || hasSetTimeoutAlarmExceededDelay) {
this.recoveredAlarms.add(taskName);
await this.triggerRecoveredAlarm(alarmName);
continue;
}
void this.scheduleAlarm(taskName, createInfo);
void this.scheduleAlarm(alarmName, createInfo);
}
// 10 seconds after verifying the alarm state, we should treat any newly created alarms as non-recovered alarms.
globalThis.setTimeout(() => this.recoveredAlarms.clear(), 10 * 1000);
}
/**
* Creates a browser extension alarm with the given name and create info.
*
* @param taskName - The name of the alarm.
* @param alarmName - The name of the alarm.
* @param createInfo - The alarm create info.
*/
private async scheduleAlarm(
taskName: ScheduledTaskName,
alarmName: string,
createInfo: chrome.alarms.AlarmCreateInfo,
): Promise<void> {
const existingAlarm = await this.getAlarm(taskName);
if (existingAlarm) {
this.logService.warning(`Alarm ${taskName} already exists. Skipping creation.`);
if (!alarmName) {
return;
}
await this.createAlarm(taskName, createInfo);
const existingAlarm = await this.getAlarm(alarmName);
if (existingAlarm) {
this.logService.warning(`Alarm ${alarmName} already exists. Skipping creation.`);
return;
}
await this.setActiveAlarm({
taskName,
startTime: Date.now(),
createInfo,
});
const taskName = this.getTaskFromAlarmName(alarmName);
const existingTaskBasedAlarm = await this.getAlarm(taskName);
if (existingTaskBasedAlarm) {
await this.clearAlarm(taskName);
}
await this.createAlarm(alarmName, createInfo);
await this.setActiveAlarm(alarmName, createInfo);
}
/**
* Sets an active alarm in state.
*
* @param alarm - The active alarm to set.
* @param alarmName - The name of the active alarm to set.
* @param createInfo - The creation info of the active alarm.
*/
private async setActiveAlarm(alarm: ActiveAlarm): Promise<void> {
private async setActiveAlarm(
alarmName: string,
createInfo: chrome.alarms.AlarmCreateInfo,
): Promise<void> {
const activeAlarms = await firstValueFrom(this.activeAlarms$);
activeAlarms.push(alarm);
await this.updateActiveAlarms(activeAlarms);
const filteredAlarms = activeAlarms?.filter((alarm) => alarm.alarmName !== alarmName);
filteredAlarms.push({
alarmName,
startTime: Date.now(),
createInfo,
});
await this.updateActiveAlarms(filteredAlarms);
}
/**
* Deletes an active alarm from state.
*
* @param taskName - The name of the active alarm to delete.
* @param alarmName - The name of the active alarm to delete.
*/
private async deleteActiveAlarm(taskName: ScheduledTaskName): Promise<void> {
private async deleteActiveAlarm(alarmName: string): Promise<void> {
const activeAlarms = await firstValueFrom(this.activeAlarms$);
const filteredAlarms = activeAlarms?.filter((alarm) => alarm.taskName !== taskName);
const filteredAlarms = activeAlarms?.filter((alarm) => alarm.alarmName !== alarmName);
await this.updateActiveAlarms(filteredAlarms || []);
}
@ -219,15 +225,11 @@ export class BrowserTaskSchedulerService
/**
* Triggers a recovered alarm by deleting it from the recovered alarms set
*
* @param name - The name of the recovered alarm to trigger.
* @param alarmName - The name of the recovered alarm to trigger.
* @param periodInMinutes - The period in minutes of the recovered alarm.
*/
private async triggerRecoveredAlarm(
name: ScheduledTaskName,
periodInMinutes?: number,
): Promise<void> {
this.recoveredAlarms.delete(name);
await this.triggerTask(name, periodInMinutes);
private async triggerRecoveredAlarm(alarmName: string, periodInMinutes?: number): Promise<void> {
await this.triggerTask(alarmName, periodInMinutes);
}
/**
@ -244,7 +246,7 @@ export class BrowserTaskSchedulerService
*/
private handleOnAlarm = async (alarm: chrome.alarms.Alarm): Promise<void> => {
const { name, periodInMinutes } = alarm;
await this.triggerTask(name as ScheduledTaskName, periodInMinutes);
await this.triggerTask(name, periodInMinutes);
};
/**
@ -254,12 +256,9 @@ export class BrowserTaskSchedulerService
* @param alarmName - The name of the alarm to trigger.
* @param periodInMinutes - The period in minutes of an interval alarm.
*/
protected async triggerTask(
alarmName: ScheduledTaskName,
periodInMinutes?: number,
): Promise<void> {
const activeUserAlarmName = this.getTaskFromAlarmName(alarmName);
const handler = this.taskHandlers.get(activeUserAlarmName);
protected async triggerTask(alarmName: string, periodInMinutes?: number): Promise<void> {
const taskName = this.getTaskFromAlarmName(alarmName);
const handler = this.taskHandlers.get(taskName);
if (!periodInMinutes) {
await this.deleteActiveAlarm(alarmName);
}
@ -267,28 +266,44 @@ export class BrowserTaskSchedulerService
if (handler) {
handler();
}
if (alarmName === taskName) {
await this.verifyNonUserBasedAlarm(taskName);
}
}
private async verifyNonUserBasedAlarm(alarmName: ScheduledTaskName): Promise<void> {
const activeUserAlarm = await this.getActiveUserAlarmName(alarmName);
const existingAlarm = await this.getAlarm(activeUserAlarm);
if (!existingAlarm) {
return;
}
const wasCleared = await this.clearAlarm(alarmName);
if (wasCleared) {
await this.deleteActiveAlarm(alarmName);
}
}
/**
* Clears a new alarm with the given name and create info. Returns a promise
* that indicates when the alarm has been cleared successfully.
*
* @param taskName - The name of the alarm to create.
* @param alarmName - The name of the alarm to create.
*/
async clearAlarm(taskName: string): Promise<boolean> {
const activeUserAlarmName = await this.getActiveUserAlarmName(taskName);
private async clearAlarm(alarmName: string): Promise<boolean> {
if (typeof browser !== "undefined" && browser.alarms) {
return browser.alarms.clear(activeUserAlarmName);
return browser.alarms.clear(alarmName);
}
return new Promise((resolve) => chrome.alarms.clear(activeUserAlarmName, resolve));
return new Promise((resolve) => chrome.alarms.clear(alarmName, resolve));
}
/**
* Clears all alarms that have been set by the extension. Returns a promise
* that indicates when all alarms have been cleared successfully.
*/
clearAllAlarms(): Promise<boolean> {
private clearAllAlarms(): Promise<boolean> {
if (typeof browser !== "undefined" && browser.alarms) {
return browser.alarms.clearAll();
}
@ -299,44 +314,34 @@ export class BrowserTaskSchedulerService
/**
* Creates a new alarm with the given name and create info.
*
* @param taskName - The name of the alarm to create.
* @param alarmName - The name of the alarm to create.
* @param createInfo - The creation info for the alarm.
*/
async createAlarm(taskName: string, createInfo: chrome.alarms.AlarmCreateInfo): Promise<void> {
const activeUserAlarmName = await this.getActiveUserAlarmName(taskName);
private async createAlarm(
alarmName: string,
createInfo: chrome.alarms.AlarmCreateInfo,
): Promise<void> {
if (typeof browser !== "undefined" && browser.alarms) {
return browser.alarms.create(activeUserAlarmName, createInfo);
return browser.alarms.create(alarmName, createInfo);
}
return new Promise((resolve) => chrome.alarms.create(activeUserAlarmName, createInfo, resolve));
return new Promise((resolve) => chrome.alarms.create(alarmName, createInfo, resolve));
}
/**
* Gets the alarm with the given name.
*
* @param taskName - The name of the alarm to get.
* @param alarmName - The name of the alarm to get.
*/
async getAlarm(taskName: string): Promise<chrome.alarms.Alarm> {
const activeUserAlarmName = await this.getActiveUserAlarmName(taskName);
private async getAlarm(alarmName: string): Promise<chrome.alarms.Alarm> {
if (typeof browser !== "undefined" && browser.alarms) {
return browser.alarms.get(activeUserAlarmName);
return browser.alarms.get(alarmName);
}
return new Promise((resolve) => chrome.alarms.get(activeUserAlarmName, resolve));
return new Promise((resolve) => chrome.alarms.get(alarmName, resolve));
}
/**
* Gets all alarms that have been set by the extension.
*/
getAllAlarms(): Promise<chrome.alarms.Alarm[]> {
if (typeof browser !== "undefined" && browser.alarms) {
return browser.alarms.getAll();
}
return new Promise((resolve) => chrome.alarms.getAll(resolve));
}
protected async getActiveUserAlarmName(taskName: string): Promise<string> {
private async getActiveUserAlarmName(taskName: ScheduledTaskName): Promise<string> {
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
if (!activeUserId) {
return taskName;
@ -345,7 +350,12 @@ export class BrowserTaskSchedulerService
return `${activeUserId}__${taskName}`;
}
private getTaskFromAlarmName(alarmName: string): string {
return alarmName.split("__")[1];
private getTaskFromAlarmName(alarmName: string): ScheduledTaskName {
const activeUserTask = alarmName.split("__")[1] as ScheduledTaskName;
if (activeUserTask) {
return activeUserTask;
}
return alarmName as ScheduledTaskName;
}
}

View File

@ -107,7 +107,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
protected stateProvider: GlobalStateProvider,
protected billingAccountProfileStateService: BillingAccountProfileStateService,
protected taskSchedulerService: TaskSchedulerService,
protected taskSchedulerService?: TaskSchedulerService,
) {
this.currentAuthnTypeState = this.stateProvider.get(CURRENT_LOGIN_STRATEGY_KEY);
this.loginStrategyCacheState = this.stateProvider.get(CACHE_KEY);
@ -115,7 +115,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
this.authRequestPushNotificationState = this.stateProvider.get(
AUTH_REQUEST_PUSH_NOTIFICATION_KEY,
);
void this.taskSchedulerService.registerTaskHandler(
void this.taskSchedulerService?.registerTaskHandler(
ScheduledTaskNames.loginStrategySessionTimeout,
() => this.clearCache(),
);
@ -312,7 +312,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
await this.loginStrategyCacheExpirationState.update(
(_) => new Date(Date.now() + sessionTimeoutLength),
);
this.sessionTimeout = await this.taskSchedulerService.setTimeout(
this.sessionTimeout = await this.taskSchedulerService?.setTimeout(
ScheduledTaskNames.loginStrategySessionTimeout,
sessionTimeoutLength,
);
@ -320,7 +320,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
private async clearSessionTimeout(): Promise<void> {
await this.loginStrategyCacheExpirationState.update((_) => null);
await this.taskSchedulerService.clearScheduledTask({
await this.taskSchedulerService?.clearScheduledTask({
taskName: ScheduledTaskNames.loginStrategySessionTimeout,
timeoutId: this.sessionTimeout,
});

View File

@ -27,9 +27,9 @@ export class SystemService implements SystemServiceAbstraction {
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
private biometricStateService: BiometricStateService,
private taskSchedulerService: TaskSchedulerService,
private taskSchedulerService?: TaskSchedulerService,
) {
void this.taskSchedulerService.registerTaskHandler(
void this.taskSchedulerService?.registerTaskHandler(
ScheduledTaskNames.systemClearClipboardTimeout,
() => this.clearPendingClipboard(),
);
@ -102,7 +102,7 @@ export class SystemService implements SystemServiceAbstraction {
}
async clearClipboard(clipboardValue: string, timeoutMs: number = null): Promise<void> {
await this.taskSchedulerService.clearScheduledTask({
await this.taskSchedulerService?.clearScheduledTask({
taskName: ScheduledTaskNames.systemClearClipboardTimeout,
timeoutId: this.clearClipboardTimeout,
});
@ -130,7 +130,7 @@ export class SystemService implements SystemServiceAbstraction {
}
};
this.clearClipboardTimeout = this.taskSchedulerService.setTimeout(
this.clearClipboardTimeout = this.taskSchedulerService?.setTimeout(
ScheduledTaskNames.systemClearClipboardTimeout,
taskTimeoutInMs,
);

View File

@ -21,9 +21,9 @@ export class EventUploadService implements EventUploadServiceAbstraction {
private stateProvider: StateProvider,
private logService: LogService,
private authService: AuthService,
private taskSchedulerService: TaskSchedulerService,
private taskSchedulerService?: TaskSchedulerService,
) {
void this.taskSchedulerService.registerTaskHandler(
void this.taskSchedulerService?.registerTaskHandler(
ScheduledTaskNames.eventUploadsInterval,
() => this.uploadEvents(),
);
@ -37,7 +37,7 @@ export class EventUploadService implements EventUploadServiceAbstraction {
this.inited = true;
if (checkOnInterval) {
void this.uploadEvents();
void this.taskSchedulerService.setInterval(
void this.taskSchedulerService?.setInterval(
ScheduledTaskNames.eventUploadsInterval,
60 * 1000, // check every 60 seconds
);

View File

@ -44,9 +44,9 @@ export class NotificationsService implements NotificationsServiceAbstraction {
private authService: AuthService,
private authRequestService: AuthRequestServiceAbstraction,
private messagingService: MessagingService,
private taskSchedulerService: TaskSchedulerService,
private taskSchedulerService?: TaskSchedulerService,
) {
void this.taskSchedulerService.registerTaskHandler(
void this.taskSchedulerService?.registerTaskHandler(
ScheduledTaskNames.notificationsReconnectTimeout,
() => this.reconnect(this.isSyncingOnReconnect),
);
@ -225,7 +225,7 @@ export class NotificationsService implements NotificationsServiceAbstraction {
}
private async reconnect(sync: boolean) {
await this.taskSchedulerService.clearScheduledTask({
await this.taskSchedulerService?.clearScheduledTask({
taskName: ScheduledTaskNames.notificationsReconnectTimeout,
timeoutId: this.reconnectTimer,
});
@ -250,7 +250,7 @@ export class NotificationsService implements NotificationsServiceAbstraction {
if (!this.connected) {
this.isSyncingOnReconnect = sync;
this.reconnectTimer = await this.taskSchedulerService.setTimeout(
this.reconnectTimer = await this.taskSchedulerService?.setTimeout(
ScheduledTaskNames.notificationsReconnectTimeout,
this.random(120000, 300000),
);

View File

@ -60,10 +60,10 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
private authService: AuthService,
private vaultSettingsService: VaultSettingsService,
private domainSettingsService: DomainSettingsService,
private taskSchedulerService: TaskSchedulerService,
private taskSchedulerService?: TaskSchedulerService,
private logService?: LogService,
) {
void this.taskSchedulerService.registerTaskHandler(
void this.taskSchedulerService?.registerTaskHandler(
ScheduledTaskNames.fido2ClientAbortTimeout,
() => this.timeoutAbortController?.abort(),
);
@ -229,7 +229,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
};
}
await this.taskSchedulerService.clearScheduledTask({
await this.taskSchedulerService?.clearScheduledTask({
taskName: ScheduledTaskNames.fido2ClientAbortTimeout,
timeoutId: timeout,
});
@ -334,7 +334,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
this.logService?.info(`[Fido2Client] Aborted with AbortController`);
throw new DOMException("The operation either timed out or was not allowed.", "AbortError");
}
await this.taskSchedulerService.clearScheduledTask({
await this.taskSchedulerService?.clearScheduledTask({
taskName: ScheduledTaskNames.fido2ClientAbortTimeout,
timeoutId: timeout,
});
@ -368,7 +368,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
}
this.timeoutAbortController = abortController;
return await this.taskSchedulerService.setTimeout(
return await this.taskSchedulerService?.setTimeout(
ScheduledTaskNames.fido2ClientAbortTimeout,
clampedTimeout,
);