mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-08 19:18:02 +01:00
[PM-11661]Add New Reseed - Fill Buffer Behind Feature Flag (#10905)
* Add New Reseed - Fill Buffer Behind Feature Flag * Add Tests * Lint
This commit is contained in:
parent
03b3345bf6
commit
92e71d9252
@ -1463,7 +1463,14 @@ export default class MainBackground {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (needStorageReseed) {
|
if (needStorageReseed) {
|
||||||
await this.reseedStorage();
|
await this.reseedStorage(
|
||||||
|
await firstValueFrom(
|
||||||
|
this.configService.userCachedFeatureFlag$(
|
||||||
|
FeatureFlag.StorageReseedRefactor,
|
||||||
|
userBeingLoggedOut,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BrowserApi.isManifestVersion(3)) {
|
if (BrowserApi.isManifestVersion(3)) {
|
||||||
@ -1518,7 +1525,7 @@ export default class MainBackground {
|
|||||||
await SafariApp.sendMessageToApp("showPopover", null, true);
|
await SafariApp.sendMessageToApp("showPopover", null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reseedStorage() {
|
async reseedStorage(doFillBuffer: boolean) {
|
||||||
if (
|
if (
|
||||||
!this.platformUtilsService.isChrome() &&
|
!this.platformUtilsService.isChrome() &&
|
||||||
!this.platformUtilsService.isVivaldi() &&
|
!this.platformUtilsService.isVivaldi() &&
|
||||||
@ -1527,7 +1534,11 @@ export default class MainBackground {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.storageService.reseed();
|
if (doFillBuffer) {
|
||||||
|
await this.storageService.fillBuffer();
|
||||||
|
} else {
|
||||||
|
await this.storageService.reseed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearClipboard(clipboardValue: string, clearMs: number) {
|
async clearClipboard(clipboardValue: string, clearMs: number) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { firstValueFrom, map, mergeMap } from "rxjs";
|
import { firstValueFrom, map, mergeMap, of, switchMap } from "rxjs";
|
||||||
|
|
||||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
@ -272,9 +272,25 @@ export default class RuntimeBackground {
|
|||||||
await this.main.refreshBadge();
|
await this.main.refreshBadge();
|
||||||
await this.main.refreshMenu();
|
await this.main.refreshMenu();
|
||||||
break;
|
break;
|
||||||
case "bgReseedStorage":
|
case "bgReseedStorage": {
|
||||||
await this.main.reseedStorage();
|
const doFillBuffer = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(
|
||||||
|
switchMap((account) => {
|
||||||
|
if (account == null) {
|
||||||
|
return of(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.configService.userCachedFeatureFlag$(
|
||||||
|
FeatureFlag.StorageReseedRefactor,
|
||||||
|
account.id,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.main.reseedStorage(doFillBuffer);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case "authResult": {
|
case "authResult": {
|
||||||
const env = await firstValueFrom(this.environmentService.environment$);
|
const env = await firstValueFrom(this.environmentService.environment$);
|
||||||
const vaultUrl = env.getWebVaultUrl();
|
const vaultUrl = env.getWebVaultUrl();
|
||||||
|
@ -32,6 +32,46 @@ export default class BrowserLocalStorageService extends AbstractChromeStorageSer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fillBuffer() {
|
||||||
|
// Write 4MB of data in chrome.storage.local, log files will hold 4MB of data (by default)
|
||||||
|
// before forcing a compaction. To force a compaction and have it remove previously saved data,
|
||||||
|
// we want to fill it's buffer so that anything newly marked for deletion is gone.
|
||||||
|
// https://github.com/google/leveldb/blob/main/doc/impl.md#log-files
|
||||||
|
// It's important that if Google uses a different buffer length that we match that, as far as I can tell
|
||||||
|
// Google uses the default value in Chromium:
|
||||||
|
// https://github.com/chromium/chromium/blob/148774efa6b3a047369af6179a4248566b39d68f/components/value_store/lazy_leveldb.cc#L65-L66
|
||||||
|
const fakeData = "0".repeat(1024 * 1024); // 1MB of data
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
this.chromeStorageApi.set(
|
||||||
|
{
|
||||||
|
fake_data_1: fakeData,
|
||||||
|
fake_data_2: fakeData,
|
||||||
|
fake_data_3: fakeData,
|
||||||
|
fake_data_4: fakeData,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
return reject(chrome.runtime.lastError);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
this.chromeStorageApi.remove(
|
||||||
|
["fake_data_1", "fake_data_2", "fake_data_3", "fake_data_4"],
|
||||||
|
() => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
return reject(chrome.runtime.lastError);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
override async get<T>(key: string): Promise<T> {
|
override async get<T>(key: string): Promise<T> {
|
||||||
await this.awaitReseed();
|
await this.awaitReseed();
|
||||||
return super.get(key);
|
return super.get(key);
|
||||||
|
@ -34,6 +34,7 @@ export enum FeatureFlag {
|
|||||||
AccountDeprovisioning = "pm-10308-account-deprovisioning",
|
AccountDeprovisioning = "pm-10308-account-deprovisioning",
|
||||||
NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements",
|
NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements",
|
||||||
AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api",
|
AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api",
|
||||||
|
StorageReseedRefactor = "storage-reseed-refactor",
|
||||||
CipherKeyEncryption = "cipher-key-encryption",
|
CipherKeyEncryption = "cipher-key-encryption",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +77,7 @@ export const DefaultFeatureFlagValue = {
|
|||||||
[FeatureFlag.EnableUpgradePasswordManagerSub]: FALSE,
|
[FeatureFlag.EnableUpgradePasswordManagerSub]: FALSE,
|
||||||
[FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE,
|
[FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE,
|
||||||
[FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE,
|
[FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE,
|
||||||
|
[FeatureFlag.StorageReseedRefactor]: FALSE,
|
||||||
[FeatureFlag.AccountDeprovisioning]: FALSE,
|
[FeatureFlag.AccountDeprovisioning]: FALSE,
|
||||||
[FeatureFlag.NotificationBarAddLoginImprovements]: FALSE,
|
[FeatureFlag.NotificationBarAddLoginImprovements]: FALSE,
|
||||||
[FeatureFlag.AC2476_DeprecateStripeSourcesAPI]: FALSE,
|
[FeatureFlag.AC2476_DeprecateStripeSourcesAPI]: FALSE,
|
||||||
|
@ -2,6 +2,7 @@ import { Observable } from "rxjs";
|
|||||||
import { SemVer } from "semver";
|
import { SemVer } from "semver";
|
||||||
|
|
||||||
import { FeatureFlag, FeatureFlagValueType } from "../../../enums/feature-flag.enum";
|
import { FeatureFlag, FeatureFlagValueType } from "../../../enums/feature-flag.enum";
|
||||||
|
import { UserId } from "../../../types/guid";
|
||||||
import { Region } from "../environment.service";
|
import { Region } from "../environment.service";
|
||||||
|
|
||||||
import { ServerConfig } from "./server-config";
|
import { ServerConfig } from "./server-config";
|
||||||
@ -17,6 +18,18 @@ export abstract class ConfigService {
|
|||||||
* @returns An observable that emits the value of the feature flag, updates as the server config changes
|
* @returns An observable that emits the value of the feature flag, updates as the server config changes
|
||||||
*/
|
*/
|
||||||
getFeatureFlag$: <Flag extends FeatureFlag>(key: Flag) => Observable<FeatureFlagValueType<Flag>>;
|
getFeatureFlag$: <Flag extends FeatureFlag>(key: Flag) => Observable<FeatureFlagValueType<Flag>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the cached feature flag value for a give user. This will NOT call to the server to get
|
||||||
|
* the most up to date feature flag.
|
||||||
|
* @param key The feature flag key to get the value for.
|
||||||
|
* @param userId The user id of the user to get the feature flag value for.
|
||||||
|
*/
|
||||||
|
abstract userCachedFeatureFlag$<Flag extends FeatureFlag>(
|
||||||
|
key: Flag,
|
||||||
|
userId: UserId,
|
||||||
|
): Observable<FeatureFlagValueType<Flag>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the value of a feature flag for the currently active user
|
* Retrieves the value of a feature flag for the currently active user
|
||||||
* @param key The feature flag to retrieve
|
* @param key The feature flag to retrieve
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { AllowedFeatureFlagTypes } from "../../../enums/feature-flag.enum";
|
||||||
import { BaseResponse } from "../../../models/response/base.response";
|
import { BaseResponse } from "../../../models/response/base.response";
|
||||||
import { Region } from "../../abstractions/environment.service";
|
import { Region } from "../../abstractions/environment.service";
|
||||||
|
|
||||||
@ -6,7 +7,7 @@ export class ServerConfigResponse extends BaseResponse {
|
|||||||
gitHash: string;
|
gitHash: string;
|
||||||
server: ThirdPartyServerConfigResponse;
|
server: ThirdPartyServerConfigResponse;
|
||||||
environment: EnvironmentServerConfigResponse;
|
environment: EnvironmentServerConfigResponse;
|
||||||
featureStates: { [key: string]: string } = {};
|
featureStates: { [key: string]: AllowedFeatureFlagTypes } = {};
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
import { subscribeTo } from "../../../../spec/observable-tracker";
|
import { subscribeTo } from "../../../../spec/observable-tracker";
|
||||||
import { AuthService } from "../../../auth/abstractions/auth.service";
|
import { AuthService } from "../../../auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
||||||
|
import { FeatureFlag } from "../../../enums/feature-flag.enum";
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
import { ConfigApiServiceAbstraction } from "../../abstractions/config/config-api.service.abstraction";
|
import { ConfigApiServiceAbstraction } from "../../abstractions/config/config-api.service.abstraction";
|
||||||
import { ServerConfig } from "../../abstractions/config/server-config";
|
import { ServerConfig } from "../../abstractions/config/server-config";
|
||||||
@ -277,6 +278,48 @@ describe("ConfigService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("userCachedFeatureFlag$", () => {
|
||||||
|
it("maps saved user config to a feature flag", async () => {
|
||||||
|
const updateFeature = (value: boolean) => {
|
||||||
|
return new ServerConfig(
|
||||||
|
new ServerConfigData({
|
||||||
|
featureStates: {
|
||||||
|
"test-feature": value,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const configService = new DefaultConfigService(
|
||||||
|
configApiService,
|
||||||
|
environmentService,
|
||||||
|
logService,
|
||||||
|
stateProvider,
|
||||||
|
authService,
|
||||||
|
);
|
||||||
|
|
||||||
|
userState.nextState(null);
|
||||||
|
|
||||||
|
const promise = firstValueFrom(
|
||||||
|
configService
|
||||||
|
.userCachedFeatureFlag$("test-feature" as FeatureFlag, userId)
|
||||||
|
.pipe(bufferCount(3)),
|
||||||
|
);
|
||||||
|
|
||||||
|
userState.nextState(updateFeature(true));
|
||||||
|
userState.nextState(updateFeature(false));
|
||||||
|
|
||||||
|
const values = await promise;
|
||||||
|
|
||||||
|
// We wouldn't normally expect this to be undefined, the logic
|
||||||
|
// should normally return the feature flags default value but since
|
||||||
|
// we are faking a feature flag key, undefined is expected
|
||||||
|
expect(values[0]).toBe(undefined);
|
||||||
|
expect(values[1]).toBe(true);
|
||||||
|
expect(values[2]).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("slow configuration", () => {
|
describe("slow configuration", () => {
|
||||||
const environmentSubject = new BehaviorSubject<Environment>(null);
|
const environmentSubject = new BehaviorSubject<Environment>(null);
|
||||||
|
|
||||||
|
@ -115,16 +115,27 @@ export class DefaultConfigService implements ConfigService {
|
|||||||
|
|
||||||
getFeatureFlag$<Flag extends FeatureFlag>(key: Flag) {
|
getFeatureFlag$<Flag extends FeatureFlag>(key: Flag) {
|
||||||
return this.serverConfig$.pipe(
|
return this.serverConfig$.pipe(
|
||||||
map((serverConfig) => {
|
map((serverConfig) => this.getFeatureFlagValue(serverConfig, key)),
|
||||||
if (serverConfig?.featureStates == null || serverConfig.featureStates[key] == null) {
|
|
||||||
return DefaultFeatureFlagValue[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
return serverConfig.featureStates[key] as FeatureFlagValueType<Flag>;
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getFeatureFlagValue<Flag extends FeatureFlag>(
|
||||||
|
serverConfig: ServerConfig | null,
|
||||||
|
flag: Flag,
|
||||||
|
) {
|
||||||
|
if (serverConfig?.featureStates == null || serverConfig.featureStates[flag] == null) {
|
||||||
|
return DefaultFeatureFlagValue[flag];
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverConfig.featureStates[flag] as FeatureFlagValueType<Flag>;
|
||||||
|
}
|
||||||
|
|
||||||
|
userCachedFeatureFlag$<Flag extends FeatureFlag>(key: Flag, userId: UserId) {
|
||||||
|
return this.stateProvider
|
||||||
|
.getUser(userId, USER_SERVER_CONFIG)
|
||||||
|
.state$.pipe(map((config) => this.getFeatureFlagValue(config, key)));
|
||||||
|
}
|
||||||
|
|
||||||
async getFeatureFlag<Flag extends FeatureFlag>(key: Flag) {
|
async getFeatureFlag<Flag extends FeatureFlag>(key: Flag) {
|
||||||
return await firstValueFrom(this.getFeatureFlag$(key));
|
return await firstValueFrom(this.getFeatureFlag$(key));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user