1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 21:50:15 +00:00

Prevent SDK from disposing withit debounce period (#18775)

This commit is contained in:
Bernd Schoolmann
2026-02-09 21:19:38 +01:00
committed by GitHub
parent 37eeffd03a
commit ea04b0562f
2 changed files with 40 additions and 3 deletions

View File

@@ -143,17 +143,44 @@ describe("DefaultSdkService", () => {
});
it("destroys the internal SDK client when all subscriptions are closed", async () => {
jest.useFakeTimers();
const subject_1 = new BehaviorSubject<Rc<PasswordManagerClient> | undefined>(undefined);
const subject_2 = new BehaviorSubject<Rc<PasswordManagerClient> | undefined>(undefined);
const subscription_1 = service.userClient$(userId).subscribe(subject_1);
const subscription_2 = service.userClient$(userId).subscribe(subject_2);
await new Promise(process.nextTick);
await jest.advanceTimersByTimeAsync(0);
subscription_1.unsubscribe();
subscription_2.unsubscribe();
await new Promise(process.nextTick);
await jest.advanceTimersByTimeAsync(0);
expect(mockClient.free).not.toHaveBeenCalled();
await jest.advanceTimersByTimeAsync(1000);
expect(mockClient.free).toHaveBeenCalledTimes(1);
jest.useRealTimers();
});
it("does not destroy the internal SDK client if resubscribed within 1 second", async () => {
jest.useFakeTimers();
const subject_1 = new BehaviorSubject<Rc<PasswordManagerClient> | undefined>(undefined);
const subscription_1 = service.userClient$(userId).subscribe(subject_1);
await jest.advanceTimersByTimeAsync(0);
subscription_1.unsubscribe();
await jest.advanceTimersByTimeAsync(500);
expect(mockClient.free).not.toHaveBeenCalled();
// Resubscribe before the 1 second delay
const subject_2 = new BehaviorSubject<Rc<PasswordManagerClient> | undefined>(undefined);
const subscription_2 = service.userClient$(userId).subscribe(subject_2);
await jest.advanceTimersByTimeAsync(1000);
// Client should not be freed since we resubscribed
expect(mockClient.free).not.toHaveBeenCalled();
expect(sdkClientFactory.createSdkClient).toHaveBeenCalledTimes(1);
subscription_2.unsubscribe();
jest.useRealTimers();
});
it("destroys the internal SDK client when the userKey is unset (i.e. lock or logout)", async () => {
@@ -218,6 +245,7 @@ describe("DefaultSdkService", () => {
});
it("destroys the internal client when an override is set", async () => {
jest.useFakeTimers();
const mockInternalClient = createMockClient();
const mockOverrideClient = createMockClient();
sdkClientFactory.createSdkClient.mockResolvedValue(mockInternalClient);
@@ -227,7 +255,10 @@ describe("DefaultSdkService", () => {
service.setClient(userId, mockOverrideClient);
await userClientTracker.pauseUntilReceived(2);
expect(mockInternalClient.free).not.toHaveBeenCalled();
await jest.advanceTimersByTimeAsync(1000);
expect(mockInternalClient.free).toHaveBeenCalled();
jest.useRealTimers();
});
it("destroys the override client when explicitly setting the client to undefined", async () => {

View File

@@ -2,7 +2,10 @@ import {
combineLatest,
concatMap,
Observable,
share,
shareReplay,
ReplaySubject,
timer,
map,
distinctUntilChanged,
tap,
@@ -263,7 +266,10 @@ export class DefaultSdkService implements SdkService {
},
),
tap({ finalize: () => this.sdkClientCache.delete(userId) }),
shareReplay({ refCount: true, bufferSize: 1 }),
share({
connector: () => new ReplaySubject(1),
resetOnRefCountZero: () => timer(1000),
}),
);
this.sdkClientCache.set(userId, client$);