mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 22:33:35 +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.refreshIdentityToken).toHaveBeenCalledTimes(1);
|
||||||
expect(apiService.getSync).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 {
|
export class DefaultSyncService extends CoreSyncService {
|
||||||
syncInProgress = false;
|
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(
|
constructor(
|
||||||
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||||
accountService: AccountService,
|
accountService: AccountService,
|
||||||
@@ -107,7 +110,16 @@ export class DefaultSyncService extends CoreSyncService {
|
|||||||
forceSync: boolean,
|
forceSync: boolean,
|
||||||
allowThrowOnErrorOrOptions?: boolean | SyncOptions,
|
allowThrowOnErrorOrOptions?: boolean | SyncOptions,
|
||||||
): Promise<boolean> {
|
): 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) {
|
private async needsSyncing(forceSync: boolean) {
|
||||||
|
|||||||
Reference in New Issue
Block a user