mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 05:43:41 +00:00
store in-flight sync so multiple calls to the sync service are avoided
This commit is contained in:
@@ -195,5 +195,50 @@ describe("DefaultSyncService", () => {
|
||||
expect(apiService.refreshIdentityToken).toHaveBeenCalledTimes(1);
|
||||
expect(apiService.getSync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe("in-flight syncs", () => {
|
||||
let mockFullSync: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
// Mock the _fullSync method so the tests can control the promise resolution
|
||||
// the internal implementation isn't what is tested here.
|
||||
mockFullSync = jest.spyOn(sut as any, "_fullSync").mockImplementation(() => {
|
||||
return new Promise((resolve) => setTimeout(() => resolve(true), 50));
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("does not call internal sync when one is already in progress", async () => {
|
||||
const fullSyncPromises = [sut.fullSync(true), sut.fullSync(false), sut.fullSync(false)];
|
||||
|
||||
jest.advanceTimersByTime(100);
|
||||
|
||||
const [result1, result2, result3] = await Promise.all(fullSyncPromises);
|
||||
|
||||
expect(result1).toBe(true);
|
||||
expect(result2).toBe(true);
|
||||
expect(result3).toBe(true);
|
||||
|
||||
expect(mockFullSync).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("resets the in-flight sync when the complete", async () => {
|
||||
const fullSyncPromises = [sut.fullSync(true), sut.fullSync(false)];
|
||||
|
||||
expect(sut["inFlightSync"]).not.toBeNull();
|
||||
|
||||
jest.advanceTimersByTime(50);
|
||||
|
||||
await Promise.all(fullSyncPromises);
|
||||
|
||||
expect(sut["inFlightSync"]).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,6 +59,9 @@ import { SyncOptions } from "./sync.service";
|
||||
export class DefaultSyncService extends CoreSyncService {
|
||||
syncInProgress = false;
|
||||
|
||||
/** The promise associated with any in-flight sync operations. When null, no sync is in-flight. */
|
||||
private inFlightSync: Promise<boolean> | null = null;
|
||||
|
||||
constructor(
|
||||
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
accountService: AccountService,
|
||||
@@ -107,7 +110,16 @@ export class DefaultSyncService extends CoreSyncService {
|
||||
forceSync: boolean,
|
||||
allowThrowOnErrorOrOptions?: boolean | SyncOptions,
|
||||
): Promise<boolean> {
|
||||
return this._fullSync(forceSync, allowThrowOnErrorOrOptions);
|
||||
if (this.inFlightSync !== null) {
|
||||
return this.inFlightSync;
|
||||
}
|
||||
|
||||
this.inFlightSync = this._fullSync(forceSync, allowThrowOnErrorOrOptions).finally(() => {
|
||||
// Reset the in-flight sync promise when it completes
|
||||
this.inFlightSync = null;
|
||||
});
|
||||
|
||||
return this.inFlightSync;
|
||||
}
|
||||
|
||||
private async needsSyncing(forceSync: boolean) {
|
||||
|
||||
Reference in New Issue
Block a user