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

feat: add experimental version of a callback solution

This commit is contained in:
Andreas Coroiu
2025-08-12 11:02:28 +02:00
parent 9775bb8f7c
commit d84c011a8c
2 changed files with 170 additions and 83 deletions

View File

@@ -1,4 +1,4 @@
import { combineLatest, distinctUntilChanged, mergeMap, of, Subject, switchMap } from "rxjs";
import { combineLatest, distinctUntilChanged, mergeMap, Subject } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
@@ -6,7 +6,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { BadgeService } from "../../platform/badge/badge.service";
import { BadgeService, StateSetting } from "../../platform/badge/badge.service";
import { BadgeStatePriority } from "../../platform/badge/priority";
import { BrowserApi } from "../../platform/browser/browser-api";
@@ -24,89 +24,129 @@ export class AutofillBadgeUpdaterService {
private badgeSettingsService: BadgeSettingsServiceAbstraction,
private logService: LogService,
) {
const cipherViews$ = this.accountService.activeAccount$.pipe(
switchMap((account) => (account?.id ? this.cipherService.cipherViews$(account?.id) : of([]))),
);
combineLatest({
account: this.accountService.activeAccount$,
enableBadgeCounter:
this.badgeSettingsService.enableBadgeCounter$.pipe(distinctUntilChanged()),
ciphers: cipherViews$,
})
.pipe(
mergeMap(async ({ account, enableBadgeCounter, ciphers }) => {
if (!account) {
return;
}
const tabs = await BrowserApi.tabsQuery({});
for (const tab of tabs) {
if (!tab.id) {
continue;
}
if (enableBadgeCounter) {
await this.setTabState(tab, account.id);
} else {
await this.clearTabState(tab.id);
}
}
}),
)
.subscribe();
combineLatest({
account: this.accountService.activeAccount$,
enableBadgeCounter: this.badgeSettingsService.enableBadgeCounter$,
replaced: this.tabReplaced$,
ciphers: cipherViews$,
})
.pipe(
mergeMap(async ({ account, enableBadgeCounter, replaced }) => {
this.badgeService.setDynamicState("autofill-state", (tab) => {
return combineLatest({
account: this.accountService.activeAccount$,
enableBadgeCounter:
this.badgeSettingsService.enableBadgeCounter$.pipe(distinctUntilChanged()),
}).pipe(
mergeMap(async ({ account, enableBadgeCounter }) => {
if (!account || !enableBadgeCounter) {
return;
return {
priority: BadgeStatePriority.Default,
state: {},
tabId: tab.id,
} satisfies StateSetting;
}
await this.clearTabState(replaced.removedTabId);
await this.setTabState(replaced.addedTab, account.id);
}),
)
.subscribe();
const ciphers = tab.url
? await this.cipherService.getAllDecryptedForUrl(tab.url, account.id)
: [];
const cipherCount = ciphers.length;
combineLatest({
account: this.accountService.activeAccount$,
enableBadgeCounter: this.badgeSettingsService.enableBadgeCounter$,
tab: this.tabUpdated$,
ciphers: cipherViews$,
})
.pipe(
mergeMap(async ({ account, enableBadgeCounter, tab }) => {
if (!account || !enableBadgeCounter) {
return;
if (cipherCount === 0) {
return {
priority: BadgeStatePriority.Default,
state: { text: "0" },
tabId: tab.id,
};
}
await this.setTabState(tab, account.id);
const countText = cipherCount > 9 ? "9+" : cipherCount.toString();
return {
priority: BadgeStatePriority.Default,
state: {
text: countText,
},
tabId: tab.id,
};
}),
)
.subscribe();
);
});
combineLatest({
account: this.accountService.activeAccount$,
enableBadgeCounter: this.badgeSettingsService.enableBadgeCounter$,
tabId: this.tabRemoved$,
ciphers: cipherViews$,
})
.pipe(
mergeMap(async ({ account, enableBadgeCounter, tabId }) => {
if (!account || !enableBadgeCounter) {
return;
}
// const cipherViews$ = this.accountService.activeAccount$.pipe(
// switchMap((account) => (account?.id ? this.cipherService.cipherViews$(account?.id) : of([]))),
// );
await this.clearTabState(tabId);
}),
)
.subscribe();
// combineLatest({
// account: this.accountService.activeAccount$,
// enableBadgeCounter:
// this.badgeSettingsService.enableBadgeCounter$.pipe(distinctUntilChanged()),
// ciphers: cipherViews$,
// })
// .pipe(
// mergeMap(async ({ account, enableBadgeCounter, ciphers }) => {
// if (!account) {
// return;
// }
// const tabs = await BrowserApi.tabsQuery({});
// for (const tab of tabs) {
// if (!tab.id) {
// continue;
// }
// if (enableBadgeCounter) {
// await this.setTabState(tab, account.id);
// } else {
// await this.clearTabState(tab.id);
// }
// }
// }),
// )
// .subscribe();
// combineLatest({
// account: this.accountService.activeAccount$,
// enableBadgeCounter: this.badgeSettingsService.enableBadgeCounter$,
// replaced: this.tabReplaced$,
// ciphers: cipherViews$,
// })
// .pipe(
// mergeMap(async ({ account, enableBadgeCounter, replaced }) => {
// if (!account || !enableBadgeCounter) {
// return;
// }
// await this.clearTabState(replaced.removedTabId);
// await this.setTabState(replaced.addedTab, account.id);
// }),
// )
// .subscribe();
// combineLatest({
// account: this.accountService.activeAccount$,
// enableBadgeCounter: this.badgeSettingsService.enableBadgeCounter$,
// tab: this.tabUpdated$,
// ciphers: cipherViews$,
// })
// .pipe(
// mergeMap(async ({ account, enableBadgeCounter, tab }) => {
// if (!account || !enableBadgeCounter) {
// return;
// }
// await this.setTabState(tab, account.id);
// }),
// )
// .subscribe();
// combineLatest({
// account: this.accountService.activeAccount$,
// enableBadgeCounter: this.badgeSettingsService.enableBadgeCounter$,
// tabId: this.tabRemoved$,
// ciphers: cipherViews$,
// })
// .pipe(
// mergeMap(async ({ account, enableBadgeCounter, tabId }) => {
// if (!account || !enableBadgeCounter) {
// return;
// }
// await this.clearTabState(tabId);
// }),
// )
// .subscribe();
}
init() {

View File

@@ -1,4 +1,15 @@
import { concatMap, firstValueFrom, Subscription, withLatestFrom } from "rxjs";
import {
BehaviorSubject,
combineLatest,
concatMap,
EMPTY,
firstValueFrom,
map,
Observable,
Subscription,
switchMap,
withLatestFrom,
} from "rxjs";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import {
@@ -8,23 +19,28 @@ import {
StateProvider,
} from "@bitwarden/common/platform/state";
import { BrowserApi } from "../browser/browser-api";
import { BadgeBrowserApi, RawBadgeState } from "./badge-browser-api";
import { DefaultBadgeState } from "./consts";
import { BadgeStatePriority } from "./priority";
import { BadgeState, Unset } from "./state";
interface StateSetting {
export interface StateSetting {
priority: BadgeStatePriority;
state: BadgeState;
tabId?: number;
}
const BADGE_STATES = new KeyDefinition(BADGE_MEMORY, "badgeStates", {
deserializer: (value: Record<string, StateSetting>) => value ?? { states: {} },
deserializer: (value: Record<string, StateSetting>) => value ?? {},
});
export class BadgeService {
private serviceState: GlobalState<Record<string, StateSetting>>;
private stateCalculators = new BehaviorSubject<
Record<string, (tab: chrome.tabs.Tab) => Observable<StateSetting>>
>({});
constructor(
private stateProvider: StateProvider,
@@ -40,11 +56,32 @@ export class BadgeService {
*/
startListening(): Subscription {
// React to tab changes
return this.badgeApi.activeTab$
return combineLatest([this.badgeApi.activeTab$, this.stateCalculators])
.pipe(
concatMap(async ([activeTab, stateCalculators]) => {
if (activeTab == undefined) {
return { tab: undefined, stateCalculators };
}
return { activeTab: await BrowserApi.getTab(activeTab.tabId), stateCalculators };
}),
switchMap(({ activeTab, stateCalculators }) => {
if (activeTab == undefined) {
return EMPTY;
}
return combineLatest(
Object.values(stateCalculators).map((calculator) => calculator(activeTab)),
).pipe(map((states) => ({ activeTab, states })));
}),
withLatestFrom(this.serviceState.state$),
concatMap(async ([activeTab, serviceState]) => {
await this.updateBadge(activeTab, serviceState, activeTab?.tabId);
concatMap(async ([{ activeTab, states }, serviceState]) => {
const allStates = [...Object.values(serviceState ?? {}), ...states];
await this.updateBadge(
{ tabId: activeTab.id ?? 0, windowId: activeTab.windowId },
allStates,
activeTab?.id,
);
}),
)
.subscribe();
@@ -75,6 +112,16 @@ export class BadgeService {
await this.updateBadge(activeTab, newServiceState, tabId);
}
setDynamicState(
name: string,
calculatorFactory: (tab: chrome.tabs.Tab) => Observable<StateSetting>,
) {
this.stateCalculators.next({
...this.stateCalculators.value,
[name]: calculatorFactory,
});
}
/**
* Clear the state with the given name.
*
@@ -145,7 +192,7 @@ export class BadgeService {
*/
private async updateBadge(
activeTab: chrome.tabs.TabActiveInfo | null | undefined,
serviceState: Record<string, StateSetting> | null | undefined,
serviceState: Record<string, StateSetting> | null | undefined | Array<StateSetting>,
tabId: number | undefined,
) {
if (activeTab === undefined) {