mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
fix(browser): restore timer based background syncs (#14031)
* docs: fix a typo * fix(browser): restore timer-based background syncs The browser extension was not performing scheduled background syncs every 30 minutes as expected. This was due to missing task scheduling code that was accidentally removed during the web push implementation (PR #11346). This commit: - Creates a new BackgroundSyncService to manage sync scheduling - Properly initializes the sync interval in main.background.ts - Adds a test to ensure the sync initialization code isn't accidentally removed again - Organizes platform module structure to support the new service Fixes PM-19396 * review: remove unecassary await keyword
This commit is contained in:
13
apps/browser/src/background/main.background.spec.ts
Normal file
13
apps/browser/src/background/main.background.spec.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// This test skips all the initilization of the background script and just
|
||||
// focuses on making sure we don't accidently delete the initilization of
|
||||
// background vault syncing. This has happened before!
|
||||
describe("MainBackground sync task scheduling", () => {
|
||||
it("includes code to schedule the sync interval task", () => {
|
||||
// Get the bootstrap method source code as string
|
||||
const { default: MainBackground } = jest.requireActual("./main.background");
|
||||
const bootstrapSource = MainBackground.prototype.bootstrap.toString();
|
||||
|
||||
// Check that the source includes the critical sync interval scheduling code
|
||||
expect(bootstrapSource).toContain("this.backgroundSyncService.init();");
|
||||
});
|
||||
});
|
||||
@@ -127,7 +127,6 @@ import {
|
||||
WebPushNotificationsApiService,
|
||||
WorkerWebPushConnectionService,
|
||||
} from "@bitwarden/common/platform/notifications/internal";
|
||||
import { ScheduledTaskNames } from "@bitwarden/common/platform/scheduling";
|
||||
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
||||
import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service";
|
||||
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
|
||||
@@ -222,6 +221,7 @@ import {
|
||||
KdfConfigService,
|
||||
KeyService as KeyServiceAbstraction,
|
||||
} from "@bitwarden/key-management";
|
||||
import { BackgroundSyncService } from "@bitwarden/platform/background-sync";
|
||||
import {
|
||||
IndividualVaultExportService,
|
||||
IndividualVaultExportServiceAbstraction,
|
||||
@@ -391,6 +391,7 @@ export default class MainBackground {
|
||||
offscreenDocumentService: OffscreenDocumentService;
|
||||
syncServiceListener: SyncServiceListener;
|
||||
browserInitialInstallService: BrowserInitialInstallService;
|
||||
backgroundSyncService: BackgroundSyncService;
|
||||
|
||||
webPushConnectionService: WorkerWebPushConnectionService | UnsupportedWebPushConnectionService;
|
||||
themeStateService: DefaultThemeStateService;
|
||||
@@ -585,9 +586,9 @@ export default class MainBackground {
|
||||
this.logService,
|
||||
this.stateProvider,
|
||||
);
|
||||
this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.scheduleNextSyncInterval, () =>
|
||||
this.fullSync(),
|
||||
);
|
||||
|
||||
this.backgroundSyncService = new BackgroundSyncService(this.taskSchedulerService);
|
||||
this.backgroundSyncService.register(() => this.fullSync());
|
||||
|
||||
this.environmentService = new BrowserEnvironmentService(
|
||||
this.logService,
|
||||
@@ -1368,6 +1369,7 @@ export default class MainBackground {
|
||||
setTimeout(async () => {
|
||||
await this.refreshBadge();
|
||||
await this.fullSync(false);
|
||||
this.backgroundSyncService.init();
|
||||
this.notificationsService.startListening();
|
||||
resolve();
|
||||
}, 500);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"@bitwarden/key-management": ["../../libs/key-management/src"],
|
||||
"@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"],
|
||||
"@bitwarden/platform": ["../../libs/platform/src"],
|
||||
"@bitwarden/platform/*": ["../../libs/platform/src/*"],
|
||||
"@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"],
|
||||
"@bitwarden/tools-card": ["../../libs/tools/card/src"],
|
||||
"@bitwarden/ui-common": ["../../libs/ui/common/src"],
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ScheduledTaskName } from "./scheduled-task-name.enum";
|
||||
* in the future but the task that is ran is NOT the remainder of your RXJS pipeline. The
|
||||
* task you want ran must instead be registered in a location reachable on a service worker
|
||||
* startup (on browser). An example of an acceptible location is the constructor of a service
|
||||
* you know is created in `MainBackground`. Uses of this API is other clients _can_ have the
|
||||
* you know is created in `MainBackground`. Uses of this API in other clients _can_ have the
|
||||
* `registerTaskHandler` call in more places, but in order to have it work across clients
|
||||
* it is recommended to register it according to the rules of browser.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling";
|
||||
|
||||
import { BackgroundSyncService, DEFAULT_SYNC_INTERVAL_MS } from "./background-sync.service";
|
||||
|
||||
describe("BackgroundSyncService", () => {
|
||||
let taskSchedulerService: MockProxy<TaskSchedulerService>;
|
||||
let backgroundSyncService: BackgroundSyncService;
|
||||
|
||||
beforeEach(() => {
|
||||
taskSchedulerService = mock<TaskSchedulerService>();
|
||||
backgroundSyncService = new BackgroundSyncService(taskSchedulerService);
|
||||
});
|
||||
|
||||
describe("register", () => {
|
||||
it("registers a task handler with the correct task name", () => {
|
||||
// Arrange
|
||||
const syncCallback = jest.fn().mockResolvedValue(undefined);
|
||||
|
||||
// Act
|
||||
backgroundSyncService.register(syncCallback);
|
||||
|
||||
// Assert
|
||||
expect(taskSchedulerService.registerTaskHandler).toHaveBeenCalledTimes(1);
|
||||
expect(taskSchedulerService.registerTaskHandler).toHaveBeenCalledWith(
|
||||
ScheduledTaskNames.scheduleNextSyncInterval,
|
||||
syncCallback,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("init", () => {
|
||||
it("schedules the sync interval task with default interval", () => {
|
||||
// Act
|
||||
backgroundSyncService.init();
|
||||
|
||||
// Assert
|
||||
expect(taskSchedulerService.setInterval).toHaveBeenCalledTimes(1);
|
||||
expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
|
||||
ScheduledTaskNames.scheduleNextSyncInterval,
|
||||
DEFAULT_SYNC_INTERVAL_MS,
|
||||
);
|
||||
});
|
||||
|
||||
it("schedules the sync interval task with custom interval", () => {
|
||||
// Arrange
|
||||
const customInterval = 60000; // 1 minute
|
||||
|
||||
// Act
|
||||
backgroundSyncService.init(customInterval);
|
||||
|
||||
// Assert
|
||||
expect(taskSchedulerService.setInterval).toHaveBeenCalledTimes(1);
|
||||
expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
|
||||
ScheduledTaskNames.scheduleNextSyncInterval,
|
||||
customInterval,
|
||||
);
|
||||
});
|
||||
|
||||
it("correctly handles zero interval by using default", () => {
|
||||
// Act
|
||||
backgroundSyncService.init(0);
|
||||
|
||||
// Assert
|
||||
expect(taskSchedulerService.setInterval).toHaveBeenCalledTimes(1);
|
||||
expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
|
||||
ScheduledTaskNames.scheduleNextSyncInterval,
|
||||
DEFAULT_SYNC_INTERVAL_MS,
|
||||
);
|
||||
});
|
||||
|
||||
it("correctly handles negative interval by using default", () => {
|
||||
// Act
|
||||
backgroundSyncService.init(-1000);
|
||||
|
||||
// Assert
|
||||
expect(taskSchedulerService.setInterval).toHaveBeenCalledTimes(1);
|
||||
expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
|
||||
ScheduledTaskNames.scheduleNextSyncInterval,
|
||||
DEFAULT_SYNC_INTERVAL_MS,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("full integration", () => {
|
||||
it("registers and initializes correctly in sequence", () => {
|
||||
// Arrange
|
||||
const syncCallback = jest.fn().mockResolvedValue(undefined);
|
||||
const customInterval = 45000; // 45 seconds
|
||||
|
||||
// Act
|
||||
backgroundSyncService.register(syncCallback);
|
||||
backgroundSyncService.init(customInterval);
|
||||
|
||||
// Assert
|
||||
expect(taskSchedulerService.registerTaskHandler).toHaveBeenCalledWith(
|
||||
ScheduledTaskNames.scheduleNextSyncInterval,
|
||||
syncCallback,
|
||||
);
|
||||
expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
|
||||
ScheduledTaskNames.scheduleNextSyncInterval,
|
||||
customInterval,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
44
libs/platform/src/background-sync/background-sync.service.ts
Normal file
44
libs/platform/src/background-sync/background-sync.service.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling";
|
||||
|
||||
/**
|
||||
* The default interval between background syncs.
|
||||
* 300,000ms = 5 minutes
|
||||
*/
|
||||
export const DEFAULT_SYNC_INTERVAL_MS = 300000;
|
||||
|
||||
/**
|
||||
* Service responsible for registering and managing background synchronization for the browser extension.
|
||||
* Handles scheduling of periodic sync operations using the task scheduler infrastructure.
|
||||
*/
|
||||
|
||||
export class BackgroundSyncService {
|
||||
/**
|
||||
* Creates a new instance of BackgroundSyncService.
|
||||
* @param taskSchedulerService - Service that handles scheduling and execution of periodic tasks
|
||||
*/
|
||||
constructor(private taskSchedulerService: TaskSchedulerService) {}
|
||||
|
||||
/**
|
||||
* Registers a callback function to be executed when the sync interval task is triggered.
|
||||
* This associates the sync task name with the provided callback in the task scheduler.
|
||||
*
|
||||
* @param syncCallback - The function to execute when the sync task is triggered
|
||||
*/
|
||||
register(syncCallback: () => Promise<void>) {
|
||||
this.taskSchedulerService.registerTaskHandler(
|
||||
ScheduledTaskNames.scheduleNextSyncInterval,
|
||||
syncCallback,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the background sync service by scheduling the sync interval task.
|
||||
* This sets up a recurring timer that triggers the registered sync callback at regular intervals.
|
||||
*
|
||||
* @param intervalMs - The interval in milliseconds between sync operations (defaults to 300000ms/5 minutes)
|
||||
*/
|
||||
init(intervalMs: number = DEFAULT_SYNC_INTERVAL_MS) {
|
||||
intervalMs = intervalMs < 1 ? DEFAULT_SYNC_INTERVAL_MS : intervalMs;
|
||||
this.taskSchedulerService.setInterval(ScheduledTaskNames.scheduleNextSyncInterval, intervalMs);
|
||||
}
|
||||
}
|
||||
1
libs/platform/src/background-sync/index.ts
Normal file
1
libs/platform/src/background-sync/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./background-sync.service";
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./services/browser-service";
|
||||
export * from "./background-sync";
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"extends": "../shared/tsconfig",
|
||||
"compilerOptions": {
|
||||
"paths": {}
|
||||
"paths": {
|
||||
"@bitwarden/common/*": ["../common/src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "spec"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"@bitwarden/key-management-ui": ["./libs/key-management-ui/src"],
|
||||
"@bitwarden/node/*": ["./libs/node/src/*"],
|
||||
"@bitwarden/platform": ["./libs/platform/src"],
|
||||
"@bitwarden/platform/*": ["./libs/platform/src/*"],
|
||||
"@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"],
|
||||
"@bitwarden/tools-card": ["./libs/tools/card/src"],
|
||||
"@bitwarden/ui-common": ["./libs/ui/common/src"],
|
||||
|
||||
Reference in New Issue
Block a user