mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
Merge branch 'feature/SG-771-org-domain-claiming-web' into feature/SG-680-create-domain-verification-comp
This commit is contained in:
@@ -241,4 +241,72 @@ describe("Utils Service", () => {
|
||||
expect(Utils.fromByteStringToArray(null)).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapToRecord", () => {
|
||||
it("should handle null", () => {
|
||||
expect(Utils.mapToRecord(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it("should handle empty map", () => {
|
||||
expect(Utils.mapToRecord(new Map())).toEqual({});
|
||||
});
|
||||
|
||||
it("should handle convert a Map to a Record", () => {
|
||||
const map = new Map([
|
||||
["key1", "value1"],
|
||||
["key2", "value2"],
|
||||
]);
|
||||
expect(Utils.mapToRecord(map)).toEqual({ key1: "value1", key2: "value2" });
|
||||
});
|
||||
|
||||
it("should handle convert a Map to a Record with non-string keys", () => {
|
||||
const map = new Map([
|
||||
[1, "value1"],
|
||||
[2, "value2"],
|
||||
]);
|
||||
const result = Utils.mapToRecord(map);
|
||||
expect(result).toEqual({ 1: "value1", 2: "value2" });
|
||||
expect(Utils.recordToMap(result)).toEqual(map);
|
||||
});
|
||||
|
||||
it("should not convert an object if it's not a map", () => {
|
||||
const obj = { key1: "value1", key2: "value2" };
|
||||
expect(Utils.mapToRecord(obj as any)).toEqual(obj);
|
||||
});
|
||||
});
|
||||
|
||||
describe("recordToMap", () => {
|
||||
it("should handle null", () => {
|
||||
expect(Utils.recordToMap(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it("should handle empty record", () => {
|
||||
expect(Utils.recordToMap({})).toEqual(new Map());
|
||||
});
|
||||
|
||||
it("should handle convert a Record to a Map", () => {
|
||||
const record = { key1: "value1", key2: "value2" };
|
||||
expect(Utils.recordToMap(record)).toEqual(new Map(Object.entries(record)));
|
||||
});
|
||||
|
||||
it("should handle convert a Record to a Map with non-string keys", () => {
|
||||
const record = { 1: "value1", 2: "value2" };
|
||||
const result = Utils.recordToMap(record);
|
||||
expect(result).toEqual(
|
||||
new Map([
|
||||
[1, "value1"],
|
||||
[2, "value2"],
|
||||
])
|
||||
);
|
||||
expect(Utils.mapToRecord(result)).toEqual(record);
|
||||
});
|
||||
|
||||
it("should not convert an object if already a map", () => {
|
||||
const map = new Map([
|
||||
["key1", "value1"],
|
||||
["key2", "value2"],
|
||||
]);
|
||||
expect(Utils.recordToMap(map as any)).toEqual(map);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -163,6 +163,11 @@ import { TwoFactorYubiKeyResponse } from "../models/response/two-factor-yubi-key
|
||||
import { UserKeyResponse } from "../models/response/user-key.response";
|
||||
import { SendAccessView } from "../models/view/send-access.view";
|
||||
|
||||
/**
|
||||
* @deprecated The `ApiService` class is deprecated and calls should be extracted into individual
|
||||
* api services. The `send` method is still allowed to be used within api services. For background
|
||||
* of this decision please read https://contributing.bitwarden.com/architecture/adr/refactor-api-service.
|
||||
*/
|
||||
export abstract class ApiService {
|
||||
send: (
|
||||
method: "GET" | "POST" | "PUT" | "DELETE",
|
||||
|
||||
@@ -6,6 +6,6 @@ export abstract class I18nService {
|
||||
translationLocale: string;
|
||||
collator: Intl.Collator;
|
||||
localeNames: Map<string, string>;
|
||||
t: (id: string, p1?: string, p2?: string, p3?: string) => string;
|
||||
t: (id: string, p1?: string | number, p2?: string | number, p3?: string | number) => string;
|
||||
translate: (id: string, p1?: string, p2?: string, p3?: string) => string;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,10 @@ export function canAccessAdmin(i18nService: I18nService) {
|
||||
);
|
||||
}
|
||||
|
||||
export function isNotProviderUser(org: Organization): boolean {
|
||||
return !org.isProviderUser;
|
||||
}
|
||||
|
||||
export abstract class OrganizationService {
|
||||
organizations$: Observable<Organization[]>;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ export abstract class PlatformUtilsService {
|
||||
isViewOpen: () => Promise<boolean>;
|
||||
launchUri: (uri: string, options?: any) => void;
|
||||
getApplicationVersion: () => Promise<string>;
|
||||
getApplicationVersionNumber: () => Promise<string>;
|
||||
supportsWebAuthn: (win: Window) => boolean;
|
||||
supportsDuo: () => boolean;
|
||||
showToast: (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiService } from "../abstractions/api.service";
|
||||
|
||||
import { Forwarder } from "./forwarder";
|
||||
import { ForwarderOptions } from "./forwarderOptions";
|
||||
import { ForwarderOptions } from "./forwarder-options";
|
||||
|
||||
export class AnonAddyForwarder implements Forwarder {
|
||||
async generate(apiService: ApiService, options: ForwarderOptions): Promise<string> {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiService } from "../abstractions/api.service";
|
||||
|
||||
import { Forwarder } from "./forwarder";
|
||||
import { ForwarderOptions } from "./forwarderOptions";
|
||||
import { ForwarderOptions } from "./forwarder-options";
|
||||
|
||||
export class DuckDuckGoForwarder implements Forwarder {
|
||||
async generate(apiService: ApiService, options: ForwarderOptions): Promise<string> {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiService } from "../abstractions/api.service";
|
||||
|
||||
import { Forwarder } from "./forwarder";
|
||||
import { ForwarderOptions } from "./forwarderOptions";
|
||||
import { ForwarderOptions } from "./forwarder-options";
|
||||
|
||||
export class FastmailForwarder implements Forwarder {
|
||||
async generate(apiService: ApiService, options: ForwarderOptions): Promise<string> {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiService } from "../abstractions/api.service";
|
||||
|
||||
import { Forwarder } from "./forwarder";
|
||||
import { ForwarderOptions } from "./forwarderOptions";
|
||||
import { ForwarderOptions } from "./forwarder-options";
|
||||
|
||||
export class FirefoxRelayForwarder implements Forwarder {
|
||||
async generate(apiService: ApiService, options: ForwarderOptions): Promise<string> {
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApiService } from "../abstractions/api.service";
|
||||
|
||||
import { ForwarderOptions } from "./forwarderOptions";
|
||||
import { ForwarderOptions } from "./forwarder-options";
|
||||
|
||||
export interface Forwarder {
|
||||
generate(apiService: ApiService, options: ForwarderOptions): Promise<string>;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiService } from "../abstractions/api.service";
|
||||
|
||||
import { Forwarder } from "./forwarder";
|
||||
import { ForwarderOptions } from "./forwarderOptions";
|
||||
import { ForwarderOptions } from "./forwarder-options";
|
||||
|
||||
export class SimpleLoginForwarder implements Forwarder {
|
||||
async generate(apiService: ApiService, options: ForwarderOptions): Promise<string> {
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
import { getHostname, parse } from "tldts";
|
||||
import { Merge } from "type-fest";
|
||||
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
import { EncryptService } from "../abstractions/encrypt.service";
|
||||
@@ -55,6 +56,10 @@ export class Utils {
|
||||
}
|
||||
|
||||
static fromB64ToArray(str: string): Uint8Array {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Utils.isNode) {
|
||||
return new Uint8Array(Buffer.from(str, "base64"));
|
||||
} else {
|
||||
@@ -108,6 +113,9 @@ export class Utils {
|
||||
}
|
||||
|
||||
static fromBufferToB64(buffer: ArrayBuffer): string {
|
||||
if (buffer == null) {
|
||||
return null;
|
||||
}
|
||||
if (Utils.isNode) {
|
||||
return Buffer.from(buffer).toString("base64");
|
||||
} else {
|
||||
@@ -423,6 +431,57 @@ export class Utils {
|
||||
return this.global.bitwardenContainerService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts map to a Record<string, V> with the same data. Inverse of recordToMap
|
||||
* Useful in toJSON methods, since Maps are not serializable
|
||||
* @param map
|
||||
* @returns
|
||||
*/
|
||||
static mapToRecord<K extends string | number, V>(map: Map<K, V>): Record<string, V> {
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
if (!(map instanceof Map)) {
|
||||
return map;
|
||||
}
|
||||
return Object.fromEntries(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts record to a Map<string, V> with the same data. Inverse of mapToRecord
|
||||
* Useful in fromJSON methods, since Maps are not serializable
|
||||
*
|
||||
* Warning: If the record has string keys that are numbers, they will be converted to numbers in the map
|
||||
* @param record
|
||||
* @returns
|
||||
*/
|
||||
static recordToMap<K extends string | number, V>(record: Record<K, V>): Map<K, V> {
|
||||
if (record == null) {
|
||||
return null;
|
||||
} else if (record instanceof Map) {
|
||||
return record;
|
||||
}
|
||||
|
||||
const entries = Object.entries(record);
|
||||
if (entries.length === 0) {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
if (isNaN(Number(entries[0][0]))) {
|
||||
return new Map(entries) as Map<K, V>;
|
||||
} else {
|
||||
return new Map(entries.map((e) => [Number(e[0]), e[1]])) as Map<K, V>;
|
||||
}
|
||||
}
|
||||
|
||||
/** Applies Object.assign, but converts the type nicely using Type-Fest Merge<Destination, Source> */
|
||||
static merge<Destination, Source>(
|
||||
destination: Destination,
|
||||
source: Source
|
||||
): Merge<Destination, Source> {
|
||||
return Object.assign(destination, source) as unknown as Merge<Destination, Source>;
|
||||
}
|
||||
|
||||
private static isMobile(win: Window) {
|
||||
let mobile = false;
|
||||
((a) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Except, Jsonify } from "type-fest";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { AuthenticationStatus } from "../../enums/authenticationStatus";
|
||||
import { KdfType } from "../../enums/kdfType";
|
||||
@@ -40,7 +40,7 @@ export class EncryptionPair<TEncrypted, TDecrypted> {
|
||||
}
|
||||
|
||||
static fromJSON<TEncrypted, TDecrypted>(
|
||||
obj: Jsonify<EncryptionPair<Jsonify<TEncrypted>, Jsonify<TDecrypted>>>,
|
||||
obj: { encrypted?: Jsonify<TEncrypted>; decrypted?: string | Jsonify<TDecrypted> },
|
||||
decryptedFromJson?: (decObj: Jsonify<TDecrypted> | string) => TDecrypted,
|
||||
encryptedFromJson?: (encObj: Jsonify<TEncrypted>) => TEncrypted
|
||||
) {
|
||||
@@ -123,7 +123,7 @@ export class AccountKeys {
|
||||
apiKeyClientSecret?: string;
|
||||
|
||||
toJSON() {
|
||||
return Object.assign(this as Except<AccountKeys, "publicKey">, {
|
||||
return Utils.merge(this, {
|
||||
publicKey: Utils.fromBufferToByteString(this.publicKey),
|
||||
});
|
||||
}
|
||||
@@ -251,7 +251,7 @@ export class AccountSettings {
|
||||
}
|
||||
|
||||
export type AccountSettingsSettings = {
|
||||
equivalentDomains?: { [id: string]: any };
|
||||
equivalentDomains?: string[][];
|
||||
};
|
||||
|
||||
export class AccountTokens {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType";
|
||||
import { OrganizationUserType } from "../../enums/organizationUserType";
|
||||
import { ProductType } from "../../enums/productType";
|
||||
@@ -205,4 +207,15 @@ export class Organization {
|
||||
get hasProvider() {
|
||||
return this.providerId != null || this.providerName != null;
|
||||
}
|
||||
|
||||
static fromJSON(json: Jsonify<Organization>) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new Organization(), json, {
|
||||
familySponsorshipLastSyncDate: new Date(json.familySponsorshipLastSyncDate),
|
||||
familySponsorshipValidUntil: new Date(json.familySponsorshipValidUntil),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,25 @@ import { State } from "./state";
|
||||
describe("state", () => {
|
||||
describe("fromJSON", () => {
|
||||
it("should deserialize to an instance of itself", () => {
|
||||
expect(State.fromJSON({})).toBeInstanceOf(State);
|
||||
expect(State.fromJSON({}, () => new Account({}))).toBeInstanceOf(State);
|
||||
});
|
||||
|
||||
it("should always assign an object to accounts", () => {
|
||||
const state = State.fromJSON({});
|
||||
const state = State.fromJSON({}, () => new Account({}));
|
||||
expect(state.accounts).not.toBeNull();
|
||||
expect(state.accounts).toEqual({});
|
||||
});
|
||||
|
||||
it("should build an account map", () => {
|
||||
const accountsSpy = jest.spyOn(Account, "fromJSON");
|
||||
const state = State.fromJSON({
|
||||
accounts: {
|
||||
userId: {},
|
||||
const state = State.fromJSON(
|
||||
{
|
||||
accounts: {
|
||||
userId: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
Account.fromJSON
|
||||
);
|
||||
|
||||
expect(state.accounts["userId"]).toBeInstanceOf(Account);
|
||||
expect(accountsSpy).toHaveBeenCalled();
|
||||
|
||||
@@ -19,26 +19,28 @@ export class State<
|
||||
|
||||
// TODO, make Jsonify<State,TGlobalState,TAccount> work. It currently doesn't because Globals doesn't implement Jsonify.
|
||||
static fromJSON<TGlobalState extends GlobalState, TAccount extends Account>(
|
||||
obj: any
|
||||
obj: any,
|
||||
accountDeserializer: (json: Jsonify<TAccount>) => TAccount
|
||||
): State<TGlobalState, TAccount> {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new State(null), obj, {
|
||||
accounts: State.buildAccountMapFromJSON(obj?.accounts),
|
||||
accounts: State.buildAccountMapFromJSON(obj?.accounts, accountDeserializer),
|
||||
});
|
||||
}
|
||||
|
||||
private static buildAccountMapFromJSON(
|
||||
jsonAccounts: Jsonify<{ [userId: string]: Jsonify<Account> }>
|
||||
private static buildAccountMapFromJSON<TAccount extends Account>(
|
||||
jsonAccounts: { [userId: string]: Jsonify<TAccount> },
|
||||
accountDeserializer: (json: Jsonify<TAccount>) => TAccount
|
||||
) {
|
||||
if (!jsonAccounts) {
|
||||
return {};
|
||||
}
|
||||
const accounts: { [userId: string]: Account } = {};
|
||||
const accounts: { [userId: string]: TAccount } = {};
|
||||
for (const userId in jsonAccounts) {
|
||||
accounts[userId] = Account.fromJSON(jsonAccounts[userId]);
|
||||
accounts[userId] = accountDeserializer(jsonAccounts[userId]);
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DeepJsonify } from "../../types/deep-jsonify";
|
||||
import { SendFile } from "../domain/send-file";
|
||||
|
||||
import { View } from "./view";
|
||||
@@ -28,4 +29,12 @@ export class SendFileView implements View {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static fromJSON(json: DeepJsonify<SendFileView>) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new SendFileView(), json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DeepJsonify } from "../../types/deep-jsonify";
|
||||
import { SendText } from "../domain/send-text";
|
||||
|
||||
import { View } from "./view";
|
||||
@@ -17,4 +18,12 @@ export class SendTextView implements View {
|
||||
get maskedText(): string {
|
||||
return this.text != null ? "••••••••" : null;
|
||||
}
|
||||
|
||||
static fromJSON(json: DeepJsonify<SendTextView>) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new SendTextView(), json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { SendType } from "../../enums/sendType";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { DeepJsonify } from "../../types/deep-jsonify";
|
||||
import { Send } from "../domain/send";
|
||||
import { SymmetricCryptoKey } from "../domain/symmetric-crypto-key";
|
||||
|
||||
@@ -65,4 +66,26 @@ export class SendView implements View {
|
||||
get pendingDelete(): boolean {
|
||||
return this.deletionDate <= new Date();
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return Utils.merge(this, {
|
||||
key: Utils.fromBufferToB64(this.key),
|
||||
});
|
||||
}
|
||||
|
||||
static fromJSON(json: DeepJsonify<SendView>) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new SendView(), json, {
|
||||
key: Utils.fromB64ToArray(json.key)?.buffer,
|
||||
cryptoKey: SymmetricCryptoKey.fromJSON(json.cryptoKey),
|
||||
text: SendTextView.fromJSON(json.text),
|
||||
file: SendFileView.fromJSON(json.file),
|
||||
revisionDate: json.revisionDate == null ? null : new Date(json.revisionDate),
|
||||
deletionDate: json.deletionDate == null ? null : new Date(json.deletionDate),
|
||||
expirationDate: json.expirationDate == null ? null : new Date(json.expirationDate),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +172,11 @@ import { TwoFactorYubiKeyResponse } from "../models/response/two-factor-yubi-key
|
||||
import { UserKeyResponse } from "../models/response/user-key.response";
|
||||
import { SendAccessView } from "../models/view/send-access.view";
|
||||
|
||||
/**
|
||||
* @deprecated The `ApiService` class is deprecated and calls should be extracted into individual
|
||||
* api services. The `send` method is still allowed to be used within api services. For background
|
||||
* of this decision please read https://contributing.bitwarden.com/architecture/adr/refactor-api-service.
|
||||
*/
|
||||
export class ApiService implements ApiServiceAbstraction {
|
||||
private device: DeviceType;
|
||||
private deviceType: string;
|
||||
@@ -2081,7 +2086,7 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
request.headers.set("Bitwarden-Client-Name", this.platformUtilsService.getClientType());
|
||||
request.headers.set(
|
||||
"Bitwarden-Client-Version",
|
||||
await this.platformUtilsService.getApplicationVersion()
|
||||
await this.platformUtilsService.getApplicationVersionNumber()
|
||||
);
|
||||
return this.nativeFetch(request);
|
||||
}
|
||||
|
||||
@@ -412,7 +412,7 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
: firstValueFrom(this.settingsService.settings$).then(
|
||||
(settings: AccountSettingsSettings) => {
|
||||
let matches: any[] = [];
|
||||
settings.equivalentDomains?.forEach((eqDomain: any) => {
|
||||
settings?.equivalentDomains?.forEach((eqDomain: any) => {
|
||||
if (eqDomain.length && eqDomain.indexOf(domain) >= 0) {
|
||||
matches = matches.concat(eqDomain);
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ export class I18nService implements I18nServiceAbstraction {
|
||||
return this.translate(id, p1, p2, p3);
|
||||
}
|
||||
|
||||
translate(id: string, p1?: string, p2?: string, p3?: string): string {
|
||||
translate(id: string, p1?: string | number, p2?: string | number, p3?: string | number): string {
|
||||
let result: string;
|
||||
// eslint-disable-next-line
|
||||
if (this.localeMessages.hasOwnProperty(id) && this.localeMessages[id]) {
|
||||
@@ -136,13 +136,13 @@ export class I18nService implements I18nServiceAbstraction {
|
||||
|
||||
if (result !== "") {
|
||||
if (p1 != null) {
|
||||
result = result.split("__$1__").join(p1);
|
||||
result = result.split("__$1__").join(p1.toString());
|
||||
}
|
||||
if (p2 != null) {
|
||||
result = result.split("__$2__").join(p2);
|
||||
result = result.split("__$2__").join(p2.toString());
|
||||
}
|
||||
if (p3 != null) {
|
||||
result = result.split("__$3__").join(p3);
|
||||
result = result.split("__$3__").join(p3.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { OrganizationData } from "../../models/data/organization.data";
|
||||
import { Organization } from "../../models/domain/organization";
|
||||
|
||||
export class OrganizationService implements InternalOrganizationServiceAbstraction {
|
||||
private _organizations = new BehaviorSubject<Organization[]>([]);
|
||||
protected _organizations = new BehaviorSubject<Organization[]>([]);
|
||||
|
||||
organizations$ = this._organizations.asObservable();
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ListResponse } from "../../models/response/list.response";
|
||||
import { PolicyResponse } from "../../models/response/policy.response";
|
||||
|
||||
export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
private _policies: BehaviorSubject<Policy[]> = new BehaviorSubject([]);
|
||||
protected _policies: BehaviorSubject<Policy[]> = new BehaviorSubject([]);
|
||||
|
||||
policies$ = this._policies.asObservable();
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Utils } from "../misc/utils";
|
||||
import { AccountSettingsSettings } from "../models/domain/account";
|
||||
|
||||
export class SettingsService implements SettingsServiceAbstraction {
|
||||
private _settings: BehaviorSubject<AccountSettingsSettings> = new BehaviorSubject({});
|
||||
protected _settings: BehaviorSubject<AccountSettingsSettings> = new BehaviorSubject({});
|
||||
|
||||
settings$ = this._settings.asObservable();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BehaviorSubject, concatMap } from "rxjs";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { LogService } from "../abstractions/log.service";
|
||||
import { StateService as StateServiceAbstraction } from "../abstractions/state.service";
|
||||
@@ -13,6 +14,7 @@ import { StorageLocation } from "../enums/storageLocation";
|
||||
import { ThemeType } from "../enums/themeType";
|
||||
import { UriMatchType } from "../enums/uriMatchType";
|
||||
import { StateFactory } from "../factories/stateFactory";
|
||||
import { Utils } from "../misc/utils";
|
||||
import { CipherData } from "../models/data/cipher.data";
|
||||
import { CollectionData } from "../models/data/collection.data";
|
||||
import { EncryptedOrganizationKeyData } from "../models/data/encrypted-organization-key.data";
|
||||
@@ -65,13 +67,13 @@ export class StateService<
|
||||
TAccount extends Account = Account
|
||||
> implements StateServiceAbstraction<TAccount>
|
||||
{
|
||||
private accountsSubject = new BehaviorSubject<{ [userId: string]: TAccount }>({});
|
||||
protected accountsSubject = new BehaviorSubject<{ [userId: string]: TAccount }>({});
|
||||
accounts$ = this.accountsSubject.asObservable();
|
||||
|
||||
private activeAccountSubject = new BehaviorSubject<string | null>(null);
|
||||
protected activeAccountSubject = new BehaviorSubject<string | null>(null);
|
||||
activeAccount$ = this.activeAccountSubject.asObservable();
|
||||
|
||||
private activeAccountUnlockedSubject = new BehaviorSubject<boolean>(false);
|
||||
protected activeAccountUnlockedSubject = new BehaviorSubject<boolean>(false);
|
||||
activeAccountUnlocked$ = this.activeAccountUnlockedSubject.asObservable();
|
||||
|
||||
private hasBeenInited = false;
|
||||
@@ -79,6 +81,9 @@ export class StateService<
|
||||
|
||||
private accountDiskCache = new Map<string, TAccount>();
|
||||
|
||||
// default account serializer, must be overridden by child class
|
||||
protected accountDeserializer = Account.fromJSON as (json: Jsonify<TAccount>) => TAccount;
|
||||
|
||||
constructor(
|
||||
protected storageService: AbstractStorageService,
|
||||
protected secureStorageService: AbstractStorageService,
|
||||
@@ -676,7 +681,7 @@ export class StateService<
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
return this.recordToMap(account?.keys?.organizationKeys?.decrypted);
|
||||
return Utils.recordToMap(account?.keys?.organizationKeys?.decrypted);
|
||||
}
|
||||
|
||||
async setDecryptedOrganizationKeys(
|
||||
@@ -686,7 +691,7 @@ export class StateService<
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
account.keys.organizationKeys.decrypted = this.mapToRecord(value);
|
||||
account.keys.organizationKeys.decrypted = Utils.mapToRecord(value);
|
||||
await this.saveAccount(
|
||||
account,
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
@@ -774,7 +779,7 @@ export class StateService<
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
return this.recordToMap(account?.keys?.providerKeys?.decrypted);
|
||||
return Utils.recordToMap(account?.keys?.providerKeys?.decrypted);
|
||||
}
|
||||
|
||||
async setDecryptedProviderKeys(
|
||||
@@ -784,7 +789,7 @@ export class StateService<
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
account.keys.providerKeys.decrypted = this.mapToRecord(value);
|
||||
account.keys.providerKeys.decrypted = Utils.mapToRecord(value);
|
||||
await this.saveAccount(
|
||||
account,
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
@@ -2744,7 +2749,7 @@ export class StateService<
|
||||
|
||||
protected async state(): Promise<State<TGlobalState, TAccount>> {
|
||||
const state = await this.memoryStorageService.get<State<TGlobalState, TAccount>>(keys.state, {
|
||||
deserializer: (s) => State.fromJSON(s),
|
||||
deserializer: (s) => State.fromJSON(s, this.accountDeserializer),
|
||||
});
|
||||
return state;
|
||||
}
|
||||
@@ -2765,50 +2770,6 @@ export class StateService<
|
||||
await this.setState(updatedState);
|
||||
});
|
||||
}
|
||||
|
||||
private mapToRecord<V>(map: Map<string, V>): Record<string, V> {
|
||||
return map == null ? null : Object.fromEntries(map);
|
||||
}
|
||||
|
||||
private recordToMap<V>(record: Record<string, V>): Map<string, V> {
|
||||
return record == null ? null : new Map(Object.entries(record));
|
||||
}
|
||||
}
|
||||
|
||||
export function withPrototype<T>(
|
||||
constructor: new (...args: any[]) => T,
|
||||
converter: (input: any) => T = (i) => i
|
||||
): (
|
||||
target: any,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: PropertyDescriptor
|
||||
) => { value: (...args: any[]) => Promise<T> } {
|
||||
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
return {
|
||||
value: function (...args: any[]) {
|
||||
const originalResult: Promise<T> = originalMethod.apply(this, args);
|
||||
|
||||
if (!(originalResult instanceof Promise)) {
|
||||
throw new Error(
|
||||
`Error applying prototype to stored value -- result is not a promise for method ${String(
|
||||
propertyKey
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
return originalResult.then((result) => {
|
||||
return result == null ||
|
||||
result.constructor.name === constructor.prototype.constructor.name
|
||||
? converter(result as T)
|
||||
: converter(
|
||||
Object.create(constructor.prototype, Object.getOwnPropertyDescriptors(result)) as T
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function withPrototypeForArrayMembers<T>(
|
||||
@@ -2847,7 +2808,7 @@ function withPrototypeForArrayMembers<T>(
|
||||
return result.map((r) => {
|
||||
return r == null ||
|
||||
r.constructor.name === memberConstructor.prototype.constructor.name
|
||||
? memberConverter(r)
|
||||
? r
|
||||
: memberConverter(
|
||||
Object.create(memberConstructor.prototype, Object.getOwnPropertyDescriptors(r))
|
||||
);
|
||||
|
||||
@@ -2,13 +2,13 @@ import { ApiService } from "../abstractions/api.service";
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { UsernameGenerationService as BaseUsernameGenerationService } from "../abstractions/usernameGeneration.service";
|
||||
import { AnonAddyForwarder } from "../emailForwarders/anonAddyForwarder";
|
||||
import { DuckDuckGoForwarder } from "../emailForwarders/duckDuckGoForwarder";
|
||||
import { FastmailForwarder } from "../emailForwarders/fastmailForwarder";
|
||||
import { FirefoxRelayForwarder } from "../emailForwarders/firefoxRelayForwarder";
|
||||
import { Forwarder } from "../emailForwarders/forwarder";
|
||||
import { ForwarderOptions } from "../emailForwarders/forwarderOptions";
|
||||
import { SimpleLoginForwarder } from "../emailForwarders/simpleLoginForwarder";
|
||||
import { AnonAddyForwarder } from "../email-forwarders/anon-addy-forwarder";
|
||||
import { DuckDuckGoForwarder } from "../email-forwarders/duck-duck-go-forwarder";
|
||||
import { FastmailForwarder } from "../email-forwarders/fastmail-forwarder";
|
||||
import { FirefoxRelayForwarder } from "../email-forwarders/firefox-relay-forwarder";
|
||||
import { Forwarder } from "../email-forwarders/forwarder";
|
||||
import { ForwarderOptions } from "../email-forwarders/forwarder-options";
|
||||
import { SimpleLoginForwarder } from "../email-forwarders/simple-login-forwarder";
|
||||
import { EFFLongWordList } from "../misc/wordlist";
|
||||
|
||||
const DefaultOptions = {
|
||||
|
||||
Reference in New Issue
Block a user