1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-20032] Give option to skip token refresh on fullSync (#14423)

* Give option to skip token refresh on fullSync

* Fix listener
This commit is contained in:
Justin Baur
2025-05-01 09:32:10 -04:00
committed by GitHub
parent abf7c949d9
commit 1d00495078
8 changed files with 355 additions and 20 deletions

View File

@@ -8,6 +8,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SyncOptions } from "@bitwarden/common/platform/sync/sync.service";
import { FakeStateProvider, mockAccountServiceWith } from "@bitwarden/common/spec";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
@@ -80,7 +81,72 @@ describe("ForegroundSyncService", () => {
const fullSyncPromise = sut.fullSync(true, false);
expect(sut.syncInProgress).toBe(true);
const requestId = getAndAssertRequestId({ forceSync: true, allowThrowOnError: false });
const requestId = getAndAssertRequestId({
forceSync: true,
options: { allowThrowOnError: false, skipTokenRefresh: false },
});
// Pretend the sync has finished
messages.next({ successfully: true, errorMessage: null, requestId: requestId });
const result = await fullSyncPromise;
expect(sut.syncInProgress).toBe(false);
expect(result).toBe(true);
});
const testData: {
input: boolean | SyncOptions | undefined;
normalized: Required<SyncOptions>;
}[] = [
{
input: undefined,
normalized: { allowThrowOnError: false, skipTokenRefresh: false },
},
{
input: true,
normalized: { allowThrowOnError: true, skipTokenRefresh: false },
},
{
input: false,
normalized: { allowThrowOnError: false, skipTokenRefresh: false },
},
{
input: { allowThrowOnError: false },
normalized: { allowThrowOnError: false, skipTokenRefresh: false },
},
{
input: { allowThrowOnError: true },
normalized: { allowThrowOnError: true, skipTokenRefresh: false },
},
{
input: { allowThrowOnError: false, skipTokenRefresh: false },
normalized: { allowThrowOnError: false, skipTokenRefresh: false },
},
{
input: { allowThrowOnError: true, skipTokenRefresh: false },
normalized: { allowThrowOnError: true, skipTokenRefresh: false },
},
{
input: { allowThrowOnError: true, skipTokenRefresh: true },
normalized: { allowThrowOnError: true, skipTokenRefresh: true },
},
{
input: { allowThrowOnError: false, skipTokenRefresh: true },
normalized: { allowThrowOnError: false, skipTokenRefresh: true },
},
];
it.each(testData)("normalize input $input options correctly", async ({ input, normalized }) => {
const messages = new Subject<FullSyncFinishedMessage>();
messageListener.messages$.mockReturnValue(messages);
const fullSyncPromise = sut.fullSync(true, input);
expect(sut.syncInProgress).toBe(true);
const requestId = getAndAssertRequestId({
forceSync: true,
options: normalized,
});
// Pretend the sync has finished
messages.next({ successfully: true, errorMessage: null, requestId: requestId });
@@ -97,7 +163,10 @@ describe("ForegroundSyncService", () => {
const fullSyncPromise = sut.fullSync(false, false);
expect(sut.syncInProgress).toBe(true);
const requestId = getAndAssertRequestId({ forceSync: false, allowThrowOnError: false });
const requestId = getAndAssertRequestId({
forceSync: false,
options: { allowThrowOnError: false, skipTokenRefresh: false },
});
// Pretend the sync has finished
messages.next({
@@ -118,7 +187,10 @@ describe("ForegroundSyncService", () => {
const fullSyncPromise = sut.fullSync(true, true);
expect(sut.syncInProgress).toBe(true);
const requestId = getAndAssertRequestId({ forceSync: true, allowThrowOnError: true });
const requestId = getAndAssertRequestId({
forceSync: true,
options: { allowThrowOnError: true, skipTokenRefresh: false },
});
// Pretend the sync has finished
messages.next({

View File

@@ -14,6 +14,7 @@ import {
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { StateProvider } from "@bitwarden/common/platform/state";
import { CoreSyncService } from "@bitwarden/common/platform/sync/internal";
import { SyncOptions } from "@bitwarden/common/platform/sync/sync.service";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -22,7 +23,7 @@ import { InternalFolderService } from "@bitwarden/common/vault/abstractions/fold
import { FULL_SYNC_FINISHED } from "./sync-service.listener";
export type FullSyncMessage = { forceSync: boolean; allowThrowOnError: boolean; requestId: string };
export type FullSyncMessage = { forceSync: boolean; options: SyncOptions; requestId: string };
export const DO_FULL_SYNC = new CommandDefinition<FullSyncMessage>("doFullSync");
@@ -60,9 +61,20 @@ export class ForegroundSyncService extends CoreSyncService {
);
}
async fullSync(forceSync: boolean, allowThrowOnError: boolean = false): Promise<boolean> {
async fullSync(
forceSync: boolean,
allowThrowOnErrorOrOptions?: boolean | SyncOptions,
): Promise<boolean> {
this.syncInProgress = true;
try {
// Normalize options
const options =
typeof allowThrowOnErrorOrOptions === "boolean"
? { allowThrowOnError: allowThrowOnErrorOrOptions, skipTokenRefresh: false }
: {
allowThrowOnError: allowThrowOnErrorOrOptions?.allowThrowOnError ?? false,
skipTokenRefresh: allowThrowOnErrorOrOptions?.skipTokenRefresh ?? false,
};
const requestId = Utils.newGuid();
const syncCompletedPromise = firstValueFrom(
this.messageListener.messages$(FULL_SYNC_FINISHED).pipe(
@@ -79,10 +91,10 @@ export class ForegroundSyncService extends CoreSyncService {
}),
),
);
this.messageSender.send(DO_FULL_SYNC, { forceSync, allowThrowOnError, requestId });
this.messageSender.send(DO_FULL_SYNC, { forceSync, options, requestId });
const result = await syncCompletedPromise;
if (allowThrowOnError && result.errorMessage != null) {
if (options.allowThrowOnError && result.errorMessage != null) {
throw new Error(result.errorMessage);
}

View File

@@ -27,11 +27,18 @@ describe("SyncServiceListener", () => {
const emissionPromise = firstValueFrom(listener);
syncService.fullSync.mockResolvedValueOnce(value);
messages.next({ forceSync: true, allowThrowOnError: false, requestId: "1" });
messages.next({
forceSync: true,
options: { allowThrowOnError: false, skipTokenRefresh: false },
requestId: "1",
});
await emissionPromise;
expect(syncService.fullSync).toHaveBeenCalledWith(true, false);
expect(syncService.fullSync).toHaveBeenCalledWith(true, {
allowThrowOnError: false,
skipTokenRefresh: false,
});
expect(messageSender.send).toHaveBeenCalledWith(FULL_SYNC_FINISHED, {
successfully: value,
errorMessage: null,
@@ -45,11 +52,18 @@ describe("SyncServiceListener", () => {
const emissionPromise = firstValueFrom(listener);
syncService.fullSync.mockRejectedValueOnce(new Error("SyncError"));
messages.next({ forceSync: true, allowThrowOnError: false, requestId: "1" });
messages.next({
forceSync: true,
options: { allowThrowOnError: false, skipTokenRefresh: false },
requestId: "1",
});
await emissionPromise;
expect(syncService.fullSync).toHaveBeenCalledWith(true, false);
expect(syncService.fullSync).toHaveBeenCalledWith(true, {
allowThrowOnError: false,
skipTokenRefresh: false,
});
expect(messageSender.send).toHaveBeenCalledWith(FULL_SYNC_FINISHED, {
successfully: false,
errorMessage: "SyncError",

View File

@@ -9,6 +9,7 @@ import {
MessageSender,
isExternalMessage,
} from "@bitwarden/common/platform/messaging";
import { SyncOptions } from "@bitwarden/common/platform/sync/sync.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DO_FULL_SYNC } from "./foreground-sync.service";
@@ -34,15 +35,15 @@ export class SyncServiceListener {
listener$(): Observable<void> {
return this.messageListener.messages$(DO_FULL_SYNC).pipe(
filter((message) => isExternalMessage(message)),
concatMap(async ({ forceSync, allowThrowOnError, requestId }) => {
await this.doFullSync(forceSync, allowThrowOnError, requestId);
concatMap(async ({ forceSync, options, requestId }) => {
await this.doFullSync(forceSync, options, requestId);
}),
);
}
private async doFullSync(forceSync: boolean, allowThrowOnError: boolean, requestId: string) {
private async doFullSync(forceSync: boolean, options: SyncOptions, requestId: string) {
try {
const result = await this.syncService.fullSync(forceSync, allowThrowOnError);
const result = await this.syncService.fullSync(forceSync, options);
this.messageSender.send(FULL_SYNC_FINISHED, {
successfully: result,
errorMessage: null,