1
0
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:
Nick Krantz
2025-05-12 11:34:03 -05:00
parent 3f686ac6a4
commit 233c8e9d4b
2 changed files with 58 additions and 1 deletions

View File

@@ -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();
});
});
});
});

View File

@@ -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) {