-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ps/pm 2910/handle switch messaging (#6823)
* Handle switch messaging TODO: handle loading state for account switcher * Async updates required for state * Fallback to email for current account avatar * Await un-awaited promises * Remove unnecessary Prune Prune was getting confused in browser and deleting memory in browser on account switch. This method isn't needed since logout already removes memory data, which is the condition for pruning * Fix temp password in browser * Use direct memory access until data is serializable Safari uses a different message object extraction than firefox/chrome and is removing `UInt8Array`s. Until all data passed into StorageService is guaranteed serializable, we need to use direct access in state service * Reload badge and context menu on switch * Gracefully switch account as they log out. * Maintain location on account switch * Remove unused state definitions * Prefer null for state undefined can be misinterpreted to indicate a value has not been set. * Hack: structured clone in memory storage We are currently getting dead objects on account switch due to updating the object in the foreground state service. However, the storage service is owned by the background. This structured clone hack ensures that all objects stored in memory are owned by the appropriate context * Null check nullable values active account can be null, so we should include null safety in the equality * Correct background->foreground switch command * Already providing background memory storage * Handle connection and clipboard on switch account * Prefer strict equal * Ensure structuredClone is available to jsdom This is a deficiency in jsdom -- jsdom/jsdom#3363 -- structured clone is well supported. * Fixup types in faker class
- Loading branch information
Showing
23 changed files
with
365 additions
and
182 deletions.
There are no files selected for viewing
9 changes: 7 additions & 2 deletions
9
apps/browser/src/auth/popup/account-switching/account-switcher.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,25 @@ | ||
import { Component } from "@angular/core"; | ||
import { Router } from "@angular/router"; | ||
|
||
import { BrowserRouterService } from "../../../platform/popup/services/browser-router.service"; | ||
import { AccountSwitcherService } from "../services/account-switcher.service"; | ||
|
||
@Component({ | ||
templateUrl: "account-switcher.component.html", | ||
}) | ||
export class AccountSwitcherComponent { | ||
constructor(private accountSwitcherService: AccountSwitcherService, private router: Router) {} | ||
constructor( | ||
private accountSwitcherService: AccountSwitcherService, | ||
private router: Router, | ||
private routerService: BrowserRouterService | ||
) {} | ||
|
||
get accountOptions$() { | ||
return this.accountSwitcherService.accountOptions$; | ||
} | ||
|
||
async selectAccount(id: string) { | ||
await this.accountSwitcherService.selectAccount(id); | ||
this.router.navigate(["/home"]); | ||
this.router.navigate([this.routerService.getPreviousUrl() ?? "/home"]); | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
apps/browser/src/auth/popup/account-switching/current-account.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
<div *ngIf="currentAccount$ | async as currentAccount"> | ||
<div (click)="currentAccountClicked()" class="tw-mr-1 tw-mt-1"> | ||
<bit-avatar [id]="currentAccount.id" [text]="currentAccount.name"></bit-avatar> | ||
<bit-avatar [id]="currentAccount.id" [text]="currentAccountName$ | async"></bit-avatar> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
apps/browser/src/platform/storage/memory-storage-service-interactions.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { | ||
GlobalState, | ||
GlobalStateProvider, | ||
KeyDefinition, | ||
UserState, | ||
UserStateProvider, | ||
} from "../src/platform/state"; | ||
|
||
import { FakeGlobalState, FakeUserState } from "./fake-state"; | ||
|
||
export class FakeGlobalStateProvider implements GlobalStateProvider { | ||
states: Map<KeyDefinition<unknown>, GlobalState<unknown>> = new Map(); | ||
get<T>(keyDefinition: KeyDefinition<T>): GlobalState<T> { | ||
let result = this.states.get(keyDefinition) as GlobalState<T>; | ||
|
||
if (result == null) { | ||
result = new FakeGlobalState<T>(); | ||
this.states.set(keyDefinition, result); | ||
} | ||
return result; | ||
} | ||
|
||
getFake<T>(keyDefinition: KeyDefinition<T>): FakeGlobalState<T> { | ||
const key = Array.from(this.states.keys()).find( | ||
(k) => k.stateDefinition === keyDefinition.stateDefinition && k.key === keyDefinition.key | ||
); | ||
return this.get(key) as FakeGlobalState<T>; | ||
} | ||
} | ||
|
||
export class FakeUserStateProvider implements UserStateProvider { | ||
states: Map<KeyDefinition<unknown>, UserState<unknown>> = new Map(); | ||
get<T>(keyDefinition: KeyDefinition<T>): UserState<T> { | ||
let result = this.states.get(keyDefinition) as UserState<T>; | ||
|
||
if (result == null) { | ||
result = new FakeUserState<T>(); | ||
this.states.set(keyDefinition, result); | ||
} | ||
return result; | ||
} | ||
|
||
getFake<T>(keyDefinition: KeyDefinition<T>): FakeUserState<T> { | ||
const key = Array.from(this.states.keys()).find( | ||
(k) => k.stateDefinition === keyDefinition.stateDefinition && k.key === keyDefinition.key | ||
); | ||
return this.get(key) as FakeUserState<T>; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { ReplaySubject, firstValueFrom, timeout } from "rxjs"; | ||
|
||
import { DerivedUserState, GlobalState, UserState } from "../src/platform/state"; | ||
// eslint-disable-next-line import/no-restricted-paths -- using unexposed options for clean typing in test class | ||
import { StateUpdateOptions } from "../src/platform/state/state-update-options"; | ||
import { UserId } from "../src/types/guid"; | ||
|
||
const DEFAULT_TEST_OPTIONS: StateUpdateOptions<any, any> = { | ||
shouldUpdate: () => true, | ||
combineLatestWith: null, | ||
msTimeout: 10, | ||
}; | ||
|
||
function populateOptionsWithDefault( | ||
options: StateUpdateOptions<any, any> | ||
): StateUpdateOptions<any, any> { | ||
return { | ||
...DEFAULT_TEST_OPTIONS, | ||
...options, | ||
}; | ||
} | ||
|
||
export class FakeGlobalState<T> implements GlobalState<T> { | ||
// eslint-disable-next-line rxjs/no-exposed-subjects -- exposed for testing setup | ||
stateSubject = new ReplaySubject<T>(1); | ||
|
||
update: <TCombine>( | ||
configureState: (state: T, dependency: TCombine) => T, | ||
options?: StateUpdateOptions<T, TCombine> | ||
) => Promise<T> = jest.fn(async (configureState, options) => { | ||
options = populateOptionsWithDefault(options); | ||
if (this.stateSubject["_buffer"].length == 0) { | ||
// throw a more helpful not initialized error | ||
throw new Error( | ||
"You must initialize the state with a value before calling update. Try calling `stateSubject.next(initialState)` before calling update" | ||
); | ||
} | ||
const current = await firstValueFrom(this.state$.pipe(timeout(100))); | ||
const combinedDependencies = | ||
options.combineLatestWith != null | ||
? await firstValueFrom(options.combineLatestWith.pipe(timeout(options.msTimeout))) | ||
: null; | ||
if (!options.shouldUpdate(current, combinedDependencies)) { | ||
return; | ||
} | ||
const newState = configureState(current, combinedDependencies); | ||
this.stateSubject.next(newState); | ||
return newState; | ||
}); | ||
|
||
updateMock = this.update as jest.MockedFunction<typeof this.update>; | ||
|
||
get state$() { | ||
return this.stateSubject.asObservable(); | ||
} | ||
} | ||
|
||
export class FakeUserState<T> implements UserState<T> { | ||
// eslint-disable-next-line rxjs/no-exposed-subjects -- exposed for testing setup | ||
stateSubject = new ReplaySubject<T>(1); | ||
|
||
update: <TCombine>( | ||
configureState: (state: T, dependency: TCombine) => T, | ||
options?: StateUpdateOptions<T, TCombine> | ||
) => Promise<T> = jest.fn(async (configureState, options) => { | ||
options = populateOptionsWithDefault(options); | ||
const current = await firstValueFrom(this.state$.pipe(timeout(options.msTimeout))); | ||
const combinedDependencies = | ||
options.combineLatestWith != null | ||
? await firstValueFrom(options.combineLatestWith.pipe(timeout(options.msTimeout))) | ||
: null; | ||
if (!options.shouldUpdate(current, combinedDependencies)) { | ||
return; | ||
} | ||
const newState = configureState(current, combinedDependencies); | ||
this.stateSubject.next(newState); | ||
return newState; | ||
}); | ||
|
||
updateMock = this.update as jest.MockedFunction<typeof this.update>; | ||
|
||
updateFor: <TCombine>( | ||
userId: UserId, | ||
configureState: (state: T, dependency: TCombine) => T, | ||
options?: StateUpdateOptions<T, TCombine> | ||
) => Promise<T> = jest.fn(); | ||
|
||
createDerived: <TTo>( | ||
converter: (data: T, context: any) => Promise<TTo> | ||
) => DerivedUserState<TTo> = jest.fn(); | ||
|
||
getFromState: () => Promise<T> = jest.fn(async () => { | ||
return await firstValueFrom(this.state$.pipe(timeout(10))); | ||
}); | ||
|
||
get state$() { | ||
return this.stateSubject.asObservable(); | ||
} | ||
} |
Oops, something went wrong.