mirror of
https://github.com/bitwarden/browser.git
synced 2024-10-06 05:28:51 +02:00
[PM-6426] Finalizing stepped setInterval implementation
This commit is contained in:
parent
182b7619aa
commit
e3f5e92163
@ -242,23 +242,57 @@ describe("BrowserTaskSchedulerService", () => {
|
||||
});
|
||||
|
||||
describe("setInterval", () => {
|
||||
it("uses the global setInterval API if the interval is less than 1000ms", async () => {
|
||||
const intervalInMs = 999;
|
||||
jest.spyOn(globalThis, "setInterval");
|
||||
describe("setting an interval that is less than 1 minute", () => {
|
||||
const intervalInMs = 10000;
|
||||
|
||||
await browserTaskSchedulerService.setInterval(
|
||||
ScheduledTaskNames.loginStrategySessionTimeout,
|
||||
intervalInMs,
|
||||
);
|
||||
it("sets up stepped alarms that trigger behavior after the first minute of setInterval execution", async () => {
|
||||
await browserTaskSchedulerService.setInterval(
|
||||
ScheduledTaskNames.loginStrategySessionTimeout,
|
||||
intervalInMs,
|
||||
);
|
||||
|
||||
expect(globalThis.setInterval).toHaveBeenCalledWith(expect.any(Function), intervalInMs);
|
||||
expect(chrome.alarms.create).toHaveBeenCalledWith(
|
||||
`${getAlarmNameMock(ScheduledTaskNames.loginStrategySessionTimeout)}__0`,
|
||||
{ periodInMinutes: 0.5 },
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(chrome.alarms.create).toHaveBeenCalledWith(
|
||||
`${getAlarmNameMock(ScheduledTaskNames.loginStrategySessionTimeout)}__1`,
|
||||
{ periodInMinutes: 0.6666666666666666 },
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(chrome.alarms.create).toHaveBeenCalledWith(
|
||||
`${getAlarmNameMock(ScheduledTaskNames.loginStrategySessionTimeout)}__2`,
|
||||
{ periodInMinutes: 0.8333333333333333 },
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it("sets an interval using the global setInterval API", async () => {
|
||||
jest.spyOn(globalThis, "setInterval");
|
||||
|
||||
await browserTaskSchedulerService.setInterval(
|
||||
ScheduledTaskNames.loginStrategySessionTimeout,
|
||||
intervalInMs,
|
||||
);
|
||||
|
||||
expect(globalThis.setInterval).toHaveBeenCalledWith(expect.any(Function), intervalInMs);
|
||||
});
|
||||
|
||||
it("clears the global setInterval instance once the interval has elapsed the minimum required delay for an alarm", async () => {
|
||||
jest.useFakeTimers();
|
||||
jest.spyOn(globalThis, "clearInterval");
|
||||
|
||||
await browserTaskSchedulerService.setInterval(
|
||||
ScheduledTaskNames.loginStrategySessionTimeout,
|
||||
intervalInMs,
|
||||
);
|
||||
jest.advanceTimersByTime(50000);
|
||||
|
||||
expect(globalThis.clearInterval).toHaveBeenCalledWith(expect.any(Number));
|
||||
});
|
||||
});
|
||||
|
||||
it.todo(
|
||||
"sets up stepped alarms that trigger behavior after the first minute of setInterval execution",
|
||||
() => {},
|
||||
);
|
||||
|
||||
it("creates an interval alarm", async () => {
|
||||
const periodInMinutes = 2;
|
||||
const initialDelayInMs = 1000;
|
||||
|
@ -93,7 +93,7 @@ export class BrowserTaskSchedulerServiceImplementation
|
||||
: intervalInMinutes;
|
||||
|
||||
if (intervalInMinutes < 1) {
|
||||
return this.setupSteppedIntervalAlarms(taskName, alarmName, intervalInMinutes, intervalInMs);
|
||||
return this.setupSteppedIntervalAlarms(taskName, alarmName, intervalInMs);
|
||||
}
|
||||
|
||||
await this.scheduleAlarm(alarmName, {
|
||||
@ -102,35 +102,51 @@ export class BrowserTaskSchedulerServiceImplementation
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in cases where the interval is less than 1 minute. This method will set up a setInterval
|
||||
* to initialize expected recurring behavior, then create a series of alarms to handle the
|
||||
* expected scheduled task through the alarms api. This is necessary because the alarms
|
||||
* api does not support intervals less than 1 minute.
|
||||
*
|
||||
* @param taskName - The name of the task, separate of any user id.
|
||||
* @param alarmName - The name of the alarm to create, could contain a user id.
|
||||
* @param intervalInMs - The interval in milliseconds.
|
||||
*/
|
||||
private async setupSteppedIntervalAlarms(
|
||||
taskName: ScheduledTaskName,
|
||||
alarmName: string,
|
||||
intervalInMinutes: number,
|
||||
intervalInMs: number,
|
||||
) {
|
||||
let elapsedMs = 0;
|
||||
const intervalId: number | NodeJS.Timeout = globalThis.setInterval(async () => {
|
||||
elapsedMs += intervalInMs;
|
||||
const elapsedMinutes = elapsedMs / 1000 / 60;
|
||||
if (elapsedMinutes >= this.getAlarmMinDelayInMinutes()) {
|
||||
globalThis.clearInterval(intervalId);
|
||||
return;
|
||||
}
|
||||
await this.triggerTask(alarmName, intervalInMinutes);
|
||||
}, intervalInMs);
|
||||
|
||||
): Promise<number | NodeJS.Timeout> {
|
||||
const alarmMinDelayInMinutes = this.getAlarmMinDelayInMinutes();
|
||||
const intervalInMinutes = intervalInMs / 1000 / 60;
|
||||
const numberOfAlarmsToCreate = Math.ceil(1 / intervalInMinutes);
|
||||
for (let i = 0; i < numberOfAlarmsToCreate; i++) {
|
||||
const steppedAlarmName = `${alarmName}__${i}`;
|
||||
const periodInMinutes = this.getAlarmMinDelayInMinutes() + i * intervalInMinutes;
|
||||
for (let alarmIndex = 0; alarmIndex < numberOfAlarmsToCreate; alarmIndex++) {
|
||||
const steppedAlarmName = `${alarmName}__${alarmIndex}`;
|
||||
const periodInMinutes = alarmMinDelayInMinutes + intervalInMinutes * alarmIndex;
|
||||
|
||||
// We need to clear alarms based on the task name as well as the
|
||||
// user-based alarm name to ensure duplicate alarms are not created.
|
||||
await this.clearScheduledAlarm(`${taskName}__${alarmIndex}`);
|
||||
await this.clearScheduledAlarm(steppedAlarmName);
|
||||
await this.clearScheduledAlarm(`${taskName}__${i}`);
|
||||
|
||||
await this.scheduleAlarm(steppedAlarmName, {
|
||||
periodInMinutes: this.getUpperBoundDelayInMinutes(periodInMinutes),
|
||||
});
|
||||
}
|
||||
|
||||
let elapsedMs = 0;
|
||||
const intervalId: number | NodeJS.Timeout = globalThis.setInterval(async () => {
|
||||
elapsedMs += intervalInMs;
|
||||
const elapsedMinutes = elapsedMs / 1000 / 60;
|
||||
|
||||
if (elapsedMinutes >= alarmMinDelayInMinutes) {
|
||||
globalThis.clearInterval(intervalId);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.triggerTask(alarmName, intervalInMinutes);
|
||||
}, intervalInMs);
|
||||
|
||||
return intervalId;
|
||||
}
|
||||
|
||||
@ -353,12 +369,7 @@ export class BrowserTaskSchedulerServiceImplementation
|
||||
* @param alarmName - The alarm name to parse.
|
||||
*/
|
||||
private getTaskFromAlarmName(alarmName: string): ScheduledTaskName {
|
||||
const activeUserTask = alarmName.split("__")[0] as ScheduledTaskName;
|
||||
if (activeUserTask) {
|
||||
return activeUserTask;
|
||||
}
|
||||
|
||||
return alarmName as ScheduledTaskName;
|
||||
return alarmName.split("__")[0] as ScheduledTaskName;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -427,14 +438,17 @@ export class BrowserTaskSchedulerServiceImplementation
|
||||
return typeof browser !== "undefined" && !!browser.alarms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum delay in minutes for an alarm. This is used to ensure that the
|
||||
* delay is at least 1 minute in non-Chrome environments. In Chrome environments, the
|
||||
* delay can be as low as 0.5 minutes.
|
||||
*/
|
||||
private getAlarmMinDelayInMinutes(): number {
|
||||
return this.isNonChromeEnvironment() ? 1 : 0.5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the upper bound delay in minutes for a given delay in minutes. This is
|
||||
* used to ensure that the delay is at least 1 minute in non-Chrome environments.
|
||||
* In Chrome environments, the delay can be as low as 0.5 minutes.
|
||||
* Gets the upper bound delay in minutes for a given delay in minutes.
|
||||
*
|
||||
* @param delayInMinutes - The delay in minutes.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user