diff --git a/packages/crypto-worker/index.ts b/packages/crypto-worker/index.ts index d81bdfcaf..cd2884e7a 100644 --- a/packages/crypto-worker/index.ts +++ b/packages/crypto-worker/index.ts @@ -23,7 +23,8 @@ import { OutputFormat, Cipher, EncryptionKey, - INNCrypto + INNCrypto, + Output } from "@notesnook/crypto"; import { NNCryptoWorkerModule } from "./src/worker"; import { wrap } from "comlink"; @@ -38,6 +39,8 @@ export class NNCryptoWorker implements INNCrypto { if (!this.worker) throw new Error("worker cannot be undefined."); if (this.isReady) return; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore this.workermodule = wrap(this.worker); // this.workermodule = await spawn(this.worker); this.isReady = true; @@ -45,7 +48,7 @@ export class NNCryptoWorker implements INNCrypto { async encrypt( key: SerializedKey, - plaintext: Plaintext, + plaintext: Plaintext, outputFormat: OutputFormat = "uint8array" ): Promise { await this.init(); @@ -54,17 +57,28 @@ export class NNCryptoWorker implements INNCrypto { return this.workermodule.encrypt(key, plaintext, outputFormat); } - async decrypt( + async decrypt( key: SerializedKey, cipherData: Cipher, - outputFormat: OutputFormat = "text" - ): Promise { + outputFormat: TOutputFormat = "text" as TOutputFormat + ): Promise<Output<TOutputFormat>> { await this.init(); if (!this.workermodule) throw new Error("Worker module is not ready."); return this.workermodule.decrypt(key, cipherData, outputFormat); } + async decryptMulti<TOutputFormat extends OutputFormat>( + key: SerializedKey, + items: Cipher[], + outputFormat: TOutputFormat = "text" as TOutputFormat + ): Promise<Output<TOutputFormat>[]> { + await this.init(); + if (!this.workermodule) throw new Error("Worker module is not ready."); + + return this.workermodule.decryptMulti(key, items, outputFormat); + } + async hash(password: string, salt: string): Promise<string> { await this.init(); if (!this.workermodule) throw new Error("Worker module is not ready."); diff --git a/packages/crypto-worker/src/worker.ts b/packages/crypto-worker/src/worker.ts index e8b00cf60..af89d09b9 100644 --- a/packages/crypto-worker/src/worker.ts +++ b/packages/crypto-worker/src/worker.ts @@ -24,15 +24,23 @@ import { SerializedKey } from "@notesnook/crypto/dist/src/types"; import { expose, transfer } from "comlink"; -import { NNCrypto } from "@notesnook/crypto"; +import type { Decryption, NNCrypto, Output } from "@notesnook/crypto"; let crypto: NNCrypto | null = null; +let decryption: typeof Decryption | null = null; + async function loadNNCrypto(): Promise<NNCrypto> { if (crypto) return crypto; const { NNCrypto } = await import("@notesnook/crypto"); return (crypto = new NNCrypto()); } +async function loadDecryptionModule(): Promise<typeof Decryption> { + if (decryption) return decryption; + const { Decryption } = await import("@notesnook/crypto"); + return (decryption = Decryption); +} + const module = { exportKey: async function (password: string, salt?: string) { const crypto = await loadNNCrypto(); @@ -48,19 +56,31 @@ const module = { }, encrypt: async function ( key: SerializedKey, - plaintext: Plaintext, + plaintext: Plaintext<OutputFormat>, outputFormat?: OutputFormat ) { const crypto = await loadNNCrypto(); return crypto.encrypt(key, plaintext, outputFormat); }, - decrypt: async function ( + decrypt: async function <TOutputFormat extends OutputFormat>( key: SerializedKey, cipherData: Cipher, - outputFormat?: OutputFormat + outputFormat: TOutputFormat = "text" as TOutputFormat ) { - const crypto = await loadNNCrypto(); - return crypto.decrypt(key, cipherData, outputFormat); + const decryption = await loadDecryptionModule(); + return decryption.decrypt(key, cipherData, outputFormat); + }, + decryptMulti: async function <TOutputFormat extends OutputFormat>( + key: SerializedKey, + items: Cipher[], + outputFormat: TOutputFormat = "text" as TOutputFormat + ) { + const decryption = await loadDecryptionModule(); + const decryptedItems: Output<TOutputFormat>[] = []; + for (const cipherData of items) { + decryptedItems.push(decryption.decrypt(key, cipherData, outputFormat)); + } + return decryptedItems; }, createEncryptionStream: async function (key: SerializedKey) { const crypto = await loadNNCrypto(); diff --git a/packages/crypto/index.ts b/packages/crypto/index.ts index 1abafb5b8..5c34f9903 100644 --- a/packages/crypto/index.ts +++ b/packages/crypto/index.ts @@ -26,6 +26,7 @@ import Password from "./src/password"; import { Cipher, EncryptionKey, + Output, OutputFormat, Plaintext, SerializedKey @@ -40,24 +41,37 @@ export class NNCrypto implements INNCrypto { this.isReady = true; } - async encrypt( + async encrypt<TFormat extends OutputFormat>( key: SerializedKey, - plaintext: Plaintext, + plaintext: Plaintext<TFormat>, outputFormat: OutputFormat = "uint8array" ): Promise<Cipher> { await this.init(); return Encryption.encrypt(key, plaintext, outputFormat); } - async decrypt( + async decrypt<TOutputFormat extends OutputFormat>( key: SerializedKey, cipherData: Cipher, - outputFormat: OutputFormat = "text" - ): Promise<Plaintext> { + outputFormat: TOutputFormat = "text" as TOutputFormat + ): Promise<Output<TOutputFormat>> { await this.init(); return Decryption.decrypt(key, cipherData, outputFormat); } + async decryptMulti<TOutputFormat extends OutputFormat>( + key: SerializedKey, + items: Cipher[], + outputFormat: TOutputFormat = "text" as TOutputFormat + ): Promise<Output<TOutputFormat>[]> { + await this.init(); + const decryptedItems: Output<TOutputFormat>[] = []; + for (const cipherData of items) { + decryptedItems.push(Decryption.decrypt(key, cipherData, outputFormat)); + } + return decryptedItems; + } + async hash(password: string, salt: string): Promise<string> { await this.init(); return Password.hash(password, salt); @@ -139,3 +153,4 @@ export class NNCrypto implements INNCrypto { export * from "./src/types"; export * from "./src/interfaces"; +export { Decryption }; diff --git a/packages/crypto/src/decryption.ts b/packages/crypto/src/decryption.ts index 80426a8dc..ae642f0aa 100644 --- a/packages/crypto/src/decryption.ts +++ b/packages/crypto/src/decryption.ts @@ -29,7 +29,7 @@ import { from_hex } from "@notesnook/sodium"; import KeyUtils from "./keyutils"; -import { Cipher, OutputFormat, Plaintext, SerializedKey } from "./types"; +import { Cipher, Output, OutputFormat, SerializedKey } from "./types"; export default class Decryption { private static transformInput(cipherData: Cipher): Uint8Array { @@ -54,11 +54,11 @@ export default class Decryption { return input; } - static decrypt( + static decrypt<TOutputFormat extends OutputFormat>( key: SerializedKey, cipherData: Cipher, - outputFormat: OutputFormat = "text" - ): Plaintext { + outputFormat: TOutputFormat = "text" as TOutputFormat + ): Output<TOutputFormat> { if (!key.salt && cipherData.salt) key.salt = cipherData.salt; const encryptionKey = KeyUtils.transform(key); @@ -71,15 +71,13 @@ export default class Decryption { encryptionKey.key ); - return { - format: outputFormat, - data: - outputFormat === "base64" - ? to_base64(plaintext, base64_variants.ORIGINAL) - : outputFormat === "text" - ? to_string(plaintext) - : plaintext - }; + return ( + outputFormat === "base64" + ? to_base64(plaintext, base64_variants.ORIGINAL) + : outputFormat === "text" + ? to_string(plaintext) + : plaintext + ) as Output<TOutputFormat>; } static createStream( diff --git a/packages/crypto/src/encryption.ts b/packages/crypto/src/encryption.ts index 2279b4f58..39f082835 100644 --- a/packages/crypto/src/encryption.ts +++ b/packages/crypto/src/encryption.ts @@ -34,7 +34,9 @@ import { Chunk, Cipher, OutputFormat, Plaintext, SerializedKey } from "./types"; const encoder = new TextEncoder(); export default class Encryption { - private static transformInput(plaintext: Plaintext): Uint8Array { + private static transformInput( + plaintext: Plaintext<OutputFormat> + ): Uint8Array { let data: Uint8Array | null = null; if (typeof plaintext.data === "string" && plaintext.format === "base64") { data = from_base64(plaintext.data, base64_variants.ORIGINAL); @@ -49,7 +51,7 @@ export default class Encryption { static encrypt( key: SerializedKey, - plaintext: Plaintext, + plaintext: Plaintext<OutputFormat>, outputFormat: OutputFormat = "uint8array" ): Cipher { const encryptionKey = KeyUtils.transform(key); diff --git a/packages/crypto/src/interfaces.ts b/packages/crypto/src/interfaces.ts index dc913bdf2..acf734025 100644 --- a/packages/crypto/src/interfaces.ts +++ b/packages/crypto/src/interfaces.ts @@ -23,7 +23,8 @@ import { OutputFormat, Plaintext, SerializedKey, - Chunk + Chunk, + Output } from "./types"; export interface IStreamable { @@ -34,15 +35,21 @@ export interface IStreamable { export interface INNCrypto { encrypt( key: SerializedKey, - plaintext: Plaintext, + plaintext: Plaintext<OutputFormat>, outputFormat?: OutputFormat ): Promise<Cipher>; - decrypt( + decrypt<TOutputFormat extends OutputFormat>( key: SerializedKey, cipherData: Cipher, - outputFormat?: OutputFormat - ): Promise<Plaintext>; + outputFormat?: TOutputFormat + ): Promise<Output<TOutputFormat>>; + + decryptMulti<TOutputFormat extends OutputFormat>( + key: SerializedKey, + cipherData: Cipher[], + outputFormat?: TOutputFormat + ): Promise<Output<TOutputFormat>[]>; hash(password: string, salt: string): Promise<string>; diff --git a/packages/crypto/src/types.ts b/packages/crypto/src/types.ts index 734110e75..6728cfaad 100644 --- a/packages/crypto/src/types.ts +++ b/packages/crypto/src/types.ts @@ -30,10 +30,12 @@ export type Cipher = { length: number; }; -export type Plaintext = { - format: OutputFormat; - data: string | Uint8Array; +export type Plaintext<TFormat extends OutputFormat> = { + format: TFormat; + data: TFormat extends StringOutputFormat ? string : Uint8Array; }; +export type Output<TFormat extends OutputFormat> = + TFormat extends StringOutputFormat ? string : Uint8Array; export type SerializedKey = { password?: string;