1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-23 16:38:45 +01:00

[EC-281] Add de/serialization methods to CipherView objects (#2970)

This commit is contained in:
Thomas Rittson 2022-08-05 08:07:24 +10:00 committed by GitHub
parent ee0d87690b
commit 8626e1d4e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 349 additions and 70 deletions

View File

@ -92,7 +92,7 @@ export class LocalBackedSessionStorageService extends AbstractStorageService {
storedKey = await this.keyGenerationService.makeEphemeralKey();
await this.setSessionEncKey(storedKey);
}
return SymmetricCryptoKey.initFromJson(
return SymmetricCryptoKey.fromJSON(
Object.create(SymmetricCryptoKey.prototype, Object.getOwnPropertyDescriptors(storedKey))
);
}

View File

@ -6,7 +6,7 @@ import { Attachment } from "@bitwarden/common/models/domain/attachment";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
import { ContainerService } from "@bitwarden/common/services/container.service";
import { makeStaticByteArray, mockEnc } from "../utils";
import { makeStaticByteArray, mockEnc } from "../../utils";
describe("Attachment", () => {
let data: AttachmentData;

View File

@ -1,7 +1,7 @@
import { CardData } from "@bitwarden/common/models/data/cardData";
import { Card } from "@bitwarden/common/models/domain/card";
import { mockEnc } from "../utils";
import { mockEnc } from "../../utils";
describe("Card", () => {
let data: CardData;

View File

@ -15,7 +15,7 @@ import { CardView } from "@bitwarden/common/models/view/cardView";
import { IdentityView } from "@bitwarden/common/models/view/identityView";
import { LoginView } from "@bitwarden/common/models/view/loginView";
import { mockEnc } from "../utils";
import { mockEnc } from "../../utils";
describe("Cipher DTO", () => {
it("Convert from empty CipherData", () => {

View File

@ -1,7 +1,7 @@
import { CollectionData } from "@bitwarden/common/models/data/collectionData";
import { Collection } from "@bitwarden/common/models/domain/collection";
import { mockEnc } from "../utils";
import { mockEnc } from "../../utils";
describe("Collection", () => {
let data: CollectionData;

View File

@ -2,7 +2,7 @@ import { FieldType } from "@bitwarden/common/enums/fieldType";
import { FieldData } from "@bitwarden/common/models/data/fieldData";
import { Field } from "@bitwarden/common/models/domain/field";
import { mockEnc } from "../utils";
import { mockEnc } from "../../utils";
describe("Field", () => {
let data: FieldData;

View File

@ -1,7 +1,7 @@
import { FolderData } from "@bitwarden/common/models/data/folderData";
import { Folder } from "@bitwarden/common/models/domain/folder";
import { mockEnc } from "../utils";
import { mockEnc } from "../../utils";
describe("Folder", () => {
let data: FolderData;

View File

@ -1,7 +1,7 @@
import { IdentityData } from "@bitwarden/common/models/data/identityData";
import { Identity } from "@bitwarden/common/models/domain/identity";
import { mockEnc } from "../utils";
import { mockEnc } from "../../utils";
describe("Identity", () => {
let data: IdentityData;

View File

@ -6,7 +6,7 @@ import { Login } from "@bitwarden/common/models/domain/login";
import { LoginUri } from "@bitwarden/common/models/domain/loginUri";
import { LoginUriView } from "@bitwarden/common/models/view/loginUriView";
import { mockEnc } from "../utils";
import { mockEnc } from "../../utils";
describe("Login DTO", () => {
it("Convert from empty LoginData", () => {

View File

@ -2,7 +2,7 @@ import { UriMatchType } from "@bitwarden/common/enums/uriMatchType";
import { LoginUriData } from "@bitwarden/common/models/data/loginUriData";
import { LoginUri } from "@bitwarden/common/models/domain/loginUri";
import { mockEnc } from "../utils";
import { mockEnc } from "../../utils";
describe("LoginUri", () => {
let data: LoginUriData;

View File

@ -1,7 +1,7 @@
import { PasswordHistoryData } from "@bitwarden/common/models/data/passwordHistoryData";
import { Password } from "@bitwarden/common/models/domain/password";
import { mockEnc } from "../utils";
import { mockEnc } from "../../utils";
describe("Password", () => {
let data: PasswordHistoryData;

View File

@ -8,7 +8,7 @@ import { Send } from "@bitwarden/common/models/domain/send";
import { SendText } from "@bitwarden/common/models/domain/sendText";
import { ContainerService } from "@bitwarden/common/services/container.service";
import { makeStaticByteArray, mockEnc } from "../utils";
import { makeStaticByteArray, mockEnc } from "../../utils";
describe("Send", () => {
let data: SendData;

View File

@ -5,7 +5,7 @@ import { SendAccess } from "@bitwarden/common/models/domain/sendAccess";
import { SendText } from "@bitwarden/common/models/domain/sendText";
import { SendAccessResponse } from "@bitwarden/common/models/response/sendAccessResponse";
import { mockEnc } from "../utils";
import { mockEnc } from "../../utils";
describe("SendAccess", () => {
let request: SendAccessResponse;

View File

@ -1,7 +1,7 @@
import { SendFileData } from "@bitwarden/common/models/data/sendFileData";
import { SendFile } from "@bitwarden/common/models/domain/sendFile";
import { mockEnc } from "../utils";
import { mockEnc } from "../../utils";
describe("SendFile", () => {
let data: SendFileData;

View File

@ -1,7 +1,7 @@
import { SendTextData } from "@bitwarden/common/models/data/sendTextData";
import { SendText } from "@bitwarden/common/models/domain/sendText";
import { mockEnc } from "../utils";
import { mockEnc } from "../../utils";
describe("SendText", () => {
let data: SendTextData;

View File

@ -1,7 +1,7 @@
import { EncryptionType } from "@bitwarden/common/enums/encryptionType";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
import { makeStaticByteArray } from "../utils";
import { makeStaticByteArray } from "../../utils";
describe("SymmetricCryptoKey", () => {
it("errors if no key", () => {
@ -66,4 +66,21 @@ describe("SymmetricCryptoKey", () => {
expect(t).toThrowError("Unable to determine encType.");
});
});
it("toJSON creates object for serialization", () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(64).buffer);
const actual = key.toJSON();
const expected = { keyB64: key.keyB64 };
expect(actual).toEqual(expected);
});
it("fromJSON hydrates new object", () => {
const expected = new SymmetricCryptoKey(makeStaticByteArray(64).buffer);
const actual = SymmetricCryptoKey.fromJSON({ keyB64: expected.keyB64 });
expect(actual).toEqual(expected);
expect(actual).toBeInstanceOf(SymmetricCryptoKey);
});
});

View File

@ -0,0 +1,17 @@
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
import { AttachmentView } from "@bitwarden/common/models/view/attachmentView";
jest.mock("@bitwarden/common/models/domain/symmetricCryptoKey");
describe("AttachmentView", () => {
it("fromJSON initializes nested objects", () => {
const mockFromJson = (stub: string) => stub + "_fromJSON";
jest.spyOn(SymmetricCryptoKey, "fromJSON").mockImplementation(mockFromJson as any);
const actual = AttachmentView.fromJSON({
key: "encKeyB64" as any,
});
expect(actual.key).toEqual("encKeyB64_fromJSON");
});
});

View File

@ -0,0 +1,76 @@
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { AttachmentView } from "@bitwarden/common/models/view/attachmentView";
import { CardView } from "@bitwarden/common/models/view/cardView";
import { CipherView } from "@bitwarden/common/models/view/cipherView";
import { FieldView } from "@bitwarden/common/models/view/fieldView";
import { IdentityView } from "@bitwarden/common/models/view/identityView";
import { LoginView } from "@bitwarden/common/models/view/loginView";
import { PasswordHistoryView } from "@bitwarden/common/models/view/passwordHistoryView";
import { SecureNoteView } from "@bitwarden/common/models/view/secureNoteView";
jest.mock("@bitwarden/common/models/view/loginView");
jest.mock("@bitwarden/common/models/view/attachmentView");
jest.mock("@bitwarden/common/models/view/fieldView");
jest.mock("@bitwarden/common/models/view/passwordHistoryView");
describe("CipherView", () => {
beforeEach(() => {
(LoginView as any).mockClear();
(AttachmentView as any).mockClear();
(FieldView as any).mockClear();
(PasswordHistoryView as any).mockClear();
});
describe("fromJSON", () => {
const mockFromJson = (stub: any) => (stub + "_fromJSON") as any;
it("initializes nested objects", () => {
jest.spyOn(AttachmentView, "fromJSON").mockImplementation(mockFromJson);
jest.spyOn(FieldView, "fromJSON").mockImplementation(mockFromJson);
jest.spyOn(PasswordHistoryView, "fromJSON").mockImplementation(mockFromJson);
const revisionDate = new Date("2022-08-04T01:06:40.441Z");
const deletedDate = new Date("2022-09-04T01:06:40.441Z");
const actual = CipherView.fromJSON({
revisionDate: revisionDate.toISOString(),
deletedDate: deletedDate.toISOString(),
attachments: ["attachment1", "attachment2"] as any,
fields: ["field1", "field2"] as any,
passwordHistory: ["ph1", "ph2", "ph3"] as any,
});
const expected = {
revisionDate: revisionDate,
deletedDate: deletedDate,
attachments: ["attachment1_fromJSON", "attachment2_fromJSON"],
fields: ["field1_fromJSON", "field2_fromJSON"],
passwordHistory: ["ph1_fromJSON", "ph2_fromJSON", "ph3_fromJSON"],
};
expect(actual).toMatchObject(expected);
});
test.each([
// Test description, CipherType, expected output
["LoginView", CipherType.Login, { login: "myLogin_fromJSON" }],
["CardView", CipherType.Card, { card: "myCard_fromJSON" }],
["IdentityView", CipherType.Identity, { identity: "myIdentity_fromJSON" }],
["Secure Note", CipherType.SecureNote, { secureNote: "mySecureNote_fromJSON" }],
])("initializes %s", (description: string, cipherType: CipherType, expected: any) => {
jest.spyOn(LoginView, "fromJSON").mockImplementation(mockFromJson);
jest.spyOn(IdentityView, "fromJSON").mockImplementation(mockFromJson);
jest.spyOn(CardView, "fromJSON").mockImplementation(mockFromJson);
jest.spyOn(SecureNoteView, "fromJSON").mockImplementation(mockFromJson);
const actual = CipherView.fromJSON({
login: "myLogin",
card: "myCard",
identity: "myIdentity",
secureNote: "mySecureNote",
type: cipherType,
} as any);
expect(actual).toMatchObject(expected);
});
});
});

View File

@ -0,0 +1,27 @@
import { LoginUriView } from "@bitwarden/common/models/view/loginUriView";
import { LoginView } from "@bitwarden/common/models/view/loginView";
jest.mock("@bitwarden/common/models/view/loginUriView");
describe("LoginView", () => {
beforeEach(() => {
(LoginUriView as any).mockClear();
});
it("fromJSON initializes nested objects", () => {
const mockFromJson = (stub: string) => stub + "_fromJSON";
jest.spyOn(LoginUriView, "fromJSON").mockImplementation(mockFromJson as any);
const passwordRevisionDate = new Date();
const actual = LoginView.fromJSON({
passwordRevisionDate: passwordRevisionDate.toISOString(),
uris: ["uri1", "uri2", "uri3"] as any,
});
expect(actual).toMatchObject({
passwordRevisionDate: passwordRevisionDate,
uris: ["uri1_fromJSON", "uri2_fromJSON", "uri3_fromJSON"],
});
});
});

View File

@ -0,0 +1,13 @@
import { PasswordHistoryView } from "@bitwarden/common/models/view/passwordHistoryView";
describe("PasswordHistoryView", () => {
it("fromJSON initializes nested objects", () => {
const lastUsedDate = new Date();
const actual = PasswordHistoryView.fromJSON({
lastUsedDate: lastUsedDate.toISOString(),
});
expect(actual.lastUsedDate).toEqual(lastUsedDate);
});
});

View File

@ -1,5 +1,8 @@
import { Jsonify } from "type-fest";
import { Utils } from "@bitwarden/common/misc/utils";
import { EncryptionType } from "../../enums/encryptionType";
import { Utils } from "../../misc/utils";
export class SymmetricCryptoKey {
key: ArrayBuffer;
@ -55,21 +58,17 @@ export class SymmetricCryptoKey {
}
}
static initFromJson(jsonResult: SymmetricCryptoKey): SymmetricCryptoKey {
if (jsonResult == null) {
return jsonResult;
toJSON() {
// The whole object is constructed from the initial key, so just store the B64 key
return { keyB64: this.keyB64 };
}
static fromJSON(obj: Jsonify<SymmetricCryptoKey>): SymmetricCryptoKey {
if (obj == null) {
return null;
}
if (jsonResult.keyB64 != null) {
jsonResult.key = Utils.fromB64ToArray(jsonResult.keyB64).buffer;
}
if (jsonResult.encKeyB64 != null) {
jsonResult.encKey = Utils.fromB64ToArray(jsonResult.encKeyB64).buffer;
}
if (jsonResult.macKeyB64 != null) {
jsonResult.macKey = Utils.fromB64ToArray(jsonResult.macKeyB64).buffer;
}
return jsonResult;
const arrayBuffer = Utils.fromB64ToArray(obj.keyB64).buffer;
return new SymmetricCryptoKey(arrayBuffer);
}
}

View File

@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";
import { Attachment } from "../domain/attachment";
import { SymmetricCryptoKey } from "../domain/symmetricCryptoKey";
@ -32,4 +34,9 @@ export class AttachmentView implements View {
}
return 0;
}
static fromJSON(obj: Partial<Jsonify<AttachmentView>>): AttachmentView {
const key = obj.key == null ? null : SymmetricCryptoKey.fromJSON(obj.key);
return Object.assign(new AttachmentView(), obj, { key: key });
}
}

View File

@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";
import { CardLinkedId as LinkedId } from "../../enums/linkedIdType";
import { linkedFieldOption } from "../../misc/linkedFieldOption.decorator";
@ -17,10 +19,6 @@ export class CardView extends ItemView {
private _number: string = null;
private _subTitle: string = null;
constructor() {
super();
}
get maskedCode(): string {
return this.code != null ? "•".repeat(this.code.length) : null;
}
@ -79,4 +77,8 @@ export class CardView extends ItemView {
private formatYear(year: string): string {
return year.length === 2 ? "20" + year : year;
}
static fromJSON(obj: Partial<Jsonify<CardView>>): CardView {
return Object.assign(new CardView(), obj);
}
}

View File

@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";
import { CipherRepromptType } from "../../enums/cipherRepromptType";
import { CipherType } from "../../enums/cipherType";
import { LinkedIdType } from "../../enums/linkedIdType";
@ -131,4 +133,40 @@ export class CipherView implements View {
linkedFieldI18nKey(id: LinkedIdType): string {
return this.linkedFieldOptions.get(id)?.i18nKey;
}
static fromJSON(obj: Partial<Jsonify<CipherView>>): CipherView {
const view = new CipherView();
const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate);
const deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate);
const attachments = obj.attachments?.map((a: any) => AttachmentView.fromJSON(a));
const fields = obj.fields?.map((f: any) => FieldView.fromJSON(f));
const passwordHistory = obj.passwordHistory?.map((ph: any) => PasswordHistoryView.fromJSON(ph));
Object.assign(view, obj, {
revisionDate: revisionDate,
deletedDate: deletedDate,
attachments: attachments,
fields: fields,
passwordHistory: passwordHistory,
});
switch (obj.type) {
case CipherType.Card:
view.card = CardView.fromJSON(obj.card);
break;
case CipherType.Identity:
view.identity = IdentityView.fromJSON(obj.identity);
break;
case CipherType.Login:
view.login = LoginView.fromJSON(obj.login);
break;
case CipherType.SecureNote:
view.secureNote = SecureNoteView.fromJSON(obj.secureNote);
break;
default:
break;
}
return view;
}
}

View File

@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";
import { FieldType } from "../../enums/fieldType";
import { LinkedIdType } from "../../enums/linkedIdType";
import { Field } from "../domain/field";
@ -25,4 +27,8 @@ export class FieldView implements View {
get maskedValue(): string {
return this.value != null ? "••••••••" : null;
}
static fromJSON(obj: Partial<Jsonify<FieldView>>): FieldView {
return Object.assign(new FieldView(), obj);
}
}

View File

@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";
import { IdentityLinkedId as LinkedId } from "../../enums/linkedIdType";
import { linkedFieldOption } from "../../misc/linkedFieldOption.decorator";
import { Utils } from "../../misc/utils";
@ -139,4 +141,8 @@ export class IdentityView extends ItemView {
addressPart2 += ", " + postalCode;
return addressPart2;
}
static fromJSON(obj: Partial<Jsonify<IdentityView>>): IdentityView {
return Object.assign(new IdentityView(), obj);
}
}

View File

@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";
import { UriMatchType } from "../../enums/uriMatchType";
import { Utils } from "../../misc/utils";
import { LoginUri } from "../domain/loginUri";
@ -124,4 +126,8 @@ export class LoginUriView implements View {
? "http://" + this.uri
: this.uri;
}
static fromJSON(obj: Partial<Jsonify<LoginUriView>>): LoginUriView {
return Object.assign(new LoginUriView(), obj);
}
}

View File

@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";
import { LoginLinkedId as LinkedId } from "../../enums/linkedIdType";
import { linkedFieldOption } from "../../misc/linkedFieldOption.decorator";
import { Utils } from "../../misc/utils";
@ -60,4 +62,15 @@ export class LoginView extends ItemView {
get hasUris(): boolean {
return this.uris != null && this.uris.length > 0;
}
static fromJSON(obj: Partial<Jsonify<LoginView>>): LoginView {
const passwordRevisionDate =
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate);
const uris = obj.uris?.map((uri: any) => LoginUriView.fromJSON(uri));
return Object.assign(new LoginView(), obj, {
passwordRevisionDate: passwordRevisionDate,
uris: uris,
});
}
}

View File

@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";
import { Password } from "../domain/password";
import { View } from "./view";
@ -13,4 +15,12 @@ export class PasswordHistoryView implements View {
this.lastUsedDate = ph.lastUsedDate;
}
static fromJSON(obj: Partial<Jsonify<PasswordHistoryView>>): PasswordHistoryView {
const lastUsedDate = obj.lastUsedDate == null ? null : new Date(obj.lastUsedDate);
return Object.assign(new PasswordHistoryView(), obj, {
lastUsedDate: lastUsedDate,
});
}
}

View File

@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";
import { SecureNoteType } from "../../enums/secureNoteType";
import { SecureNote } from "../domain/secureNote";
@ -18,4 +20,8 @@ export class SecureNoteView extends ItemView {
get subTitle(): string {
return null;
}
static fromJSON(obj: Partial<Jsonify<SecureNoteView>>): SecureNoteView {
return Object.assign(new SecureNoteView(), obj);
}
}

View File

@ -498,7 +498,7 @@ export class StateService<
);
}
@withPrototype(SymmetricCryptoKey, SymmetricCryptoKey.initFromJson)
@withPrototype(SymmetricCryptoKey, SymmetricCryptoKey.fromJSON)
async getCryptoMasterKey(options?: StorageOptions): Promise<SymmetricCryptoKey> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
@ -661,7 +661,7 @@ export class StateService<
);
}
@withPrototype(SymmetricCryptoKey, SymmetricCryptoKey.initFromJson)
@withPrototype(SymmetricCryptoKey, SymmetricCryptoKey.fromJSON)
async getDecryptedCryptoSymmetricKey(options?: StorageOptions): Promise<SymmetricCryptoKey> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
@ -682,7 +682,7 @@ export class StateService<
);
}
@withPrototypeForMap(SymmetricCryptoKey, SymmetricCryptoKey.initFromJson)
@withPrototypeForMap(SymmetricCryptoKey, SymmetricCryptoKey.fromJSON)
async getDecryptedOrganizationKeys(
options?: StorageOptions
): Promise<Map<string, SymmetricCryptoKey>> {
@ -789,7 +789,7 @@ export class StateService<
);
}
@withPrototypeForMap(SymmetricCryptoKey, SymmetricCryptoKey.initFromJson)
@withPrototypeForMap(SymmetricCryptoKey, SymmetricCryptoKey.fromJSON)
async getDecryptedProviderKeys(
options?: StorageOptions
): Promise<Map<string, SymmetricCryptoKey>> {
@ -2748,7 +2748,7 @@ export class StateService<
export function withPrototype<T>(
constructor: new (...args: any[]) => T,
converter: (input: T) => T = (i) => i
converter: (input: any) => T = (i) => i
): (
target: any,
propertyKey: string | symbol,
@ -2784,7 +2784,7 @@ export function withPrototype<T>(
function withPrototypeForArrayMembers<T>(
memberConstructor: new (...args: any[]) => T,
memberConverter: (input: T) => T = (i) => i
memberConverter: (input: any) => T = (i) => i
): (
target: any,
propertyKey: string | symbol,
@ -2832,7 +2832,7 @@ function withPrototypeForArrayMembers<T>(
function withPrototypeForObjectValues<T>(
valuesConstructor: new (...args: any[]) => T,
valuesConverter: (input: T) => T = (i) => i
valuesConverter: (input: any) => T = (i) => i
): (
target: any,
propertyKey: string | symbol,
@ -2879,7 +2879,7 @@ function withPrototypeForObjectValues<T>(
function withPrototypeForMap<T>(
valuesConstructor: new (...args: any[]) => T,
valuesConverter: (input: T) => T = (i) => i
valuesConverter: (input: any) => T = (i) => i
): (
target: any,
propertyKey: string | symbol,

89
package-lock.json generated
View File

@ -166,6 +166,7 @@
"ts-jest": "^28.0.6",
"ts-loader": "^9.2.5",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"type-fest": "^2.18.0",
"typescript": "4.6.4",
"url": "^0.11.0",
"util": "^0.12.4",
@ -3965,6 +3966,18 @@
"node": "*"
}
},
"node_modules/@eslint/eslintrc/node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@figspec/components": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@figspec/components/-/components-1.0.1.tgz",
@ -15965,6 +15978,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/boxen/node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/bplist-parser": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.1.tgz",
@ -20353,18 +20378,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/electron-store/node_modules/type-fest": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.17.0.tgz",
"integrity": "sha512-U+g3/JVXnOki1kLSc+xZGPRll3Ah9u2VIG6Sn9iH9YX6UkPERmt6O/0fIyTgsd2/whV0+gAaHAg8fz6sG1QzMA==",
"dev": true,
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.200",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.200.tgz",
@ -21732,6 +21745,18 @@
"node": "*"
}
},
"node_modules/eslint/node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/espree": {
"version": "9.3.2",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz",
@ -40504,12 +40529,12 @@
}
},
"node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.18.0.tgz",
"integrity": "sha512-pRS+/yrW5TjPPHNOvxhbNZexr2bS63WjrMU8a+VzEBhUi9Tz1pZeD+vQz3ut0svZ46P+SRqMEPnJmk2XnvNzTw==",
"dev": true,
"engines": {
"node": ">=10"
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@ -45457,6 +45482,12 @@
"requires": {
"brace-expansion": "^1.1.7"
}
},
"type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true
}
}
},
@ -54924,6 +54955,12 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
"dev": true
},
"type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true
}
}
},
@ -58370,14 +58407,6 @@
"requires": {
"conf": "^10.1.2",
"type-fest": "^2.12.2"
},
"dependencies": {
"type-fest": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.17.0.tgz",
"integrity": "sha512-U+g3/JVXnOki1kLSc+xZGPRll3Ah9u2VIG6Sn9iH9YX6UkPERmt6O/0fIyTgsd2/whV0+gAaHAg8fz6sG1QzMA==",
"dev": true
}
}
},
"electron-to-chromium": {
@ -59119,6 +59148,12 @@
"requires": {
"brace-expansion": "^1.1.7"
}
},
"type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true
}
}
},
@ -73936,9 +73971,9 @@
"dev": true
},
"type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.18.0.tgz",
"integrity": "sha512-pRS+/yrW5TjPPHNOvxhbNZexr2bS63WjrMU8a+VzEBhUi9Tz1pZeD+vQz3ut0svZ46P+SRqMEPnJmk2XnvNzTw==",
"dev": true
},
"type-is": {

View File

@ -128,6 +128,7 @@
"ts-jest": "^28.0.6",
"ts-loader": "^9.2.5",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"type-fest": "^2.18.0",
"typescript": "4.6.4",
"url": "^0.11.0",
"util": "^0.12.4",