diff --git a/package.json b/package.json index 356affb..bab0fd2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@internxt/sdk", "author": "Internxt ", - "version": "1.13.2", + "version": "1.14.2", "description": "An sdk for interacting with Internxt's services", "repository": { "type": "git", @@ -44,7 +44,9 @@ }, "dependencies": { "axios": "1.13.5", - "uuid": "13.0.0" + "uuid": "13.0.0", + "internxt-crypto": "0.0.13" + }, "lint-staged": { "*.{js,jsx,tsx,ts}": [ diff --git a/src/index.ts b/src/index.ts index dd4156a..27db597 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,3 +7,4 @@ export * as Workspaces from './workspaces'; export * from './meet'; export * as Payments from './payments'; export * from './misc'; +export * from './mail'; diff --git a/src/mail/index.ts b/src/mail/index.ts new file mode 100644 index 0000000..bad73d4 --- /dev/null +++ b/src/mail/index.ts @@ -0,0 +1,271 @@ +import { ApiSecurity, ApiUrl, AppDetails } from '../shared'; +import { headersWithToken } from '../shared/headers'; +import { HttpClient } from '../shared/http/client'; +import { + EncryptedKeystore, + KeystoreType, + PublicKeysBase64, + PrivateKeys, + HybridEncryptedEmail, + PwdProtectedEmail, + base64ToPublicKey, + EmailKeys, + Email, + createEncryptionAndRecoveryKeystores, + encryptEmailHybrid, + User, + openEncryptionKeystore, + UserWithPublicKeys, + encryptEmailHybridForMultipleRecipients, + decryptEmailHybrid, + createPwdProtectedEmail, + decryptPwdProtectedEmail, + openRecoveryKeystore, +} from 'internxt-crypto'; + +export class Mail { + private readonly client: HttpClient; + private readonly appDetails: AppDetails; + private readonly apiSecurity: ApiSecurity; + private readonly apiUrl: ApiUrl; + + public static client(apiUrl: ApiUrl, appDetails: AppDetails, apiSecurity: ApiSecurity) { + return new Mail(apiUrl, appDetails, apiSecurity); + } + + private constructor(apiUrl: ApiUrl, appDetails: AppDetails, apiSecurity: ApiSecurity) { + this.client = HttpClient.create(apiUrl, apiSecurity.unauthorizedCallback); + this.appDetails = appDetails; + this.apiSecurity = apiSecurity; + this.apiUrl = apiUrl; + } + + /** + * Uploads encrypted keystore to the server + * + * @param encryptedKeystore - The encrypted keystore + * @returns Server response + */ + async uploadKeystoreToServer(encryptedKeystore: EncryptedKeystore): Promise { + return this.client.post(`${this.apiUrl}/keystore`, { encryptedKeystore }, this.headers()); + } + + /** + * Creates recovery and encryption keystores and uploads them to the server + * + * @param userEmail - The email of the user + * @param baseKey - The secret key of the user + * @returns The recovery codes for opening recovery keystore + */ + async createAndUploadKeystores(userEmail: string, baseKey: Uint8Array): Promise { + const { encryptionKeystore, recoveryKeystore, recoveryCodes } = await createEncryptionAndRecoveryKeystores( + userEmail, + baseKey, + ); + await Promise.all([this.uploadKeystoreToServer(encryptionKeystore), this.uploadKeystoreToServer(recoveryKeystore)]); + return recoveryCodes; + } + + /** + * Requests encrypted keystore from the server + * + * @param userEmail - The email of the user + * @param keystoreType - The type of the keystore + * @returns The encrypted keystore + */ + async downloadKeystoreFromServer(userEmail: string, keystoreType: KeystoreType): Promise { + return this.client.getWithParams(`${this.apiUrl}/user/keystore`, { userEmail, keystoreType }, this.headers()); + } + + /** + * Requests encrypted keystore from the server and opens it + * + * @param userEmail - The email of the user + * @param baseKey - The secret key of the user + * @returns The email keys of the user + */ + async getUserEmailKeys(userEmail: string, baseKey: Uint8Array): Promise { + const keystore = await this.downloadKeystoreFromServer(userEmail, KeystoreType.ENCRYPTION); + return openEncryptionKeystore(keystore, baseKey); + } + + /** + * Requests recovery keystore from the server and opens it + * + * @param userEmail - The email of the user + * @param recoveryCodes - The recovery codes of the user + * @returns The email keys of the user + */ + async recoverUserEmailKeys(userEmail: string, recoveryCodes: string): Promise { + const keystore = await this.downloadKeystoreFromServer(userEmail, KeystoreType.RECOVERY); + return openRecoveryKeystore(recoveryCodes, keystore); + } + + /** + * Request user with corresponding public keys from the server + * + * @param userEmail - The email of the user + * @returns User with corresponding public keys + */ + async getUserWithPublicKeys(userEmail: string): Promise { + const response = await this.client.post<{ publicKeys: PublicKeysBase64; user: User }[]>( + `${this.apiUrl}/users/public-keys`, + { emails: [userEmail] }, + this.headers(), + ); + if (!response[0]) throw new Error(`No public keys found for ${userEmail}`); + const singleResponse = response[0]; + const publicKeys = await base64ToPublicKey(singleResponse.publicKeys); + const result = { ...singleResponse.user, publicKeys }; + return result; + } + + /** + * Request users with corresponding public keys from the server + * + * @param emails - The emails of the users + * @returns Users with corresponding public keys + */ + async getSeveralUsersWithPublicKeys(emails: string[]): Promise { + const response = await this.client.post<{ publicKeys: PublicKeysBase64; user: User }[]>( + `${this.apiUrl}/users/public-keys`, + { emails }, + this.headers(), + ); + + const result = await Promise.all( + response.map(async (item) => { + const publicKeys = await base64ToPublicKey(item.publicKeys); + return { ...item.user, publicKeys }; + }), + ); + + return result; + } + + /** + * Sends the encrypted email to the server + * + * @param email - The encrypted email + * @returns Server response + */ + async sendEncryptedEmail(email: HybridEncryptedEmail): Promise { + return this.client.post(`${this.apiUrl}/emails`, { emails: [email] }, this.headers()); + } + + /** + * Encrypts email and sends it to the server + * + * @param email - The message to encrypt + * @param senderPrivateKeys - The private keys of the sender + * @param isSubjectEncrypted - Indicates if the subject field should be encrypted + * @returns Server response + */ + async encryptAndSendEmail( + email: Email, + senderPrivateKeys: PrivateKeys, + isSubjectEncrypted: boolean = false, + ): Promise { + const recipient = await this.getUserWithPublicKeys(email.params.recipient.email); + const encEmail = await encryptEmailHybrid(email, recipient, senderPrivateKeys, isSubjectEncrypted); + return this.sendEncryptedEmail(encEmail); + } + + /** + * Sends the encrypted emails for multiple recipients to the server + * + * @param emails - The encrypted emails + * @returns Server response + */ + async sendEncryptedEmailToMultipleRecipients(emails: HybridEncryptedEmail[]): Promise { + return this.client.post(`${this.apiUrl}/emails`, { emails }, this.headers()); + } + + /** + * Encrypts emails for multiple recipients and sends emails to the server + * + * @param email - The message to encrypt + * @param senderPrivateKeys - The private keys of the sender + * @param isSubjectEncrypted - Indicates if the subject field should be encrypted + * @returns Server response + */ + async encryptAndSendEmailToMultipleRecipients( + email: Email, + senderPrivateKeys: PrivateKeys, + isSubjectEncrypted: boolean = false, + ): Promise { + const recipientEmails = email.params.recipients + ? email.params.recipients.map((user) => user.email) + : [email.params.recipient.email]; + + const recipients = await this.getSeveralUsersWithPublicKeys(recipientEmails); + const encEmails = await encryptEmailHybridForMultipleRecipients( + email, + recipients, + senderPrivateKeys, + isSubjectEncrypted, + ); + return this.sendEncryptedEmailToMultipleRecipients(encEmails); + } + + /** + * Sends the password-protected email to the server + * + * @param email - The password-protected email + * @returns Server response + */ + async sendPasswordProtectedEmail(email: PwdProtectedEmail): Promise { + return this.client.post(`${this.apiUrl}/emails`, { email }, this.headers()); + } + + /** + * Creates the password-protected email and sends it to the server + * + * @param email - The email + * @param pwd - The password + * @param isSubjectEncrypted - Indicates if the subject field should be encrypted + * @returns Server response + */ + async passwordProtectAndSendEmail(email: Email, pwd: string, isSubjectEncrypted: boolean = false): Promise { + const encEmail = await createPwdProtectedEmail(email, pwd, isSubjectEncrypted); + return this.sendPasswordProtectedEmail(encEmail); + } + + /** + * Opens the password-protected email + * + * @param email - The password-protected email + * @param pwd - The shared password + * @returns The decrypted email + */ + async openPasswordProtectedEmail(email: PwdProtectedEmail, pwd: string): Promise { + return decryptPwdProtectedEmail(email, pwd); + } + + /** + * Decrypt the email + * + * @param email - The encrypted email + * @param recipientPrivateKeys - The private keys of the email recipient + * @returns The decrypted email + */ + async decryptEmail(email: HybridEncryptedEmail, recipientPrivateKeys: PrivateKeys): Promise { + const senderEmail = email.params.sender.email; + const sender = await this.getUserWithPublicKeys(senderEmail); + return decryptEmailHybrid(email, sender.publicKeys, recipientPrivateKeys); + } + + /** + * Returns the needed headers for the module requests + * @private + */ + private headers() { + return headersWithToken({ + clientName: this.appDetails.clientName, + clientVersion: this.appDetails.clientVersion, + token: this.apiSecurity.token, + desktopToken: this.appDetails.desktopHeader, + customHeaders: this.appDetails.customHeaders, + }); + } +} diff --git a/test/mail/index.test.ts b/test/mail/index.test.ts new file mode 100644 index 0000000..26c9578 --- /dev/null +++ b/test/mail/index.test.ts @@ -0,0 +1,348 @@ +import { HttpClient } from '../../src/shared/http/client'; +import { Mail } from '../../src/mail/index'; +import { ApiSecurity, AppDetails } from '../../src/shared'; +import { headersWithToken } from '../../src/shared/headers'; +import { + createEncryptionAndRecoveryKeystores, + genSymmetricKey, + KeystoreType, + generateEmailKeys, + publicKeyToBase64, + Email, + generateUuid, + createPwdProtectedEmail, + encryptEmailHybrid, +} from 'internxt-crypto'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +describe('Mail service tests', () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + describe('test keystore call methods', async () => { + const email = 'test@internxt.com'; + const baseKey = genSymmetricKey(); + const { encryptionKeystore, recoveryCodes, recoveryKeystore, keys } = await createEncryptionAndRecoveryKeystores( + email, + baseKey, + ); + + it('When a keystore upload is requested, then it should successfully upload the keystore', async () => { + const { client, headers } = clientAndHeadersWithToken(); + const postCall = vi.spyOn(HttpClient.prototype, 'post').mockResolvedValue({}); + await client.uploadKeystoreToServer(encryptionKeystore); + + expect(postCall.mock.calls[0]).toEqual([ + '/keystore', + { + encryptedKeystore: encryptionKeystore, + }, + headers, + ]); + }); + + it('When keystore creation is requested, then it should create and upload two keystores', async () => { + const { client, headers } = clientAndHeadersWithToken(); + const postCall = vi.spyOn(HttpClient.prototype, 'post').mockResolvedValue({}); + await client.createAndUploadKeystores(email, baseKey); + + expect(postCall.mock.calls[0]).toEqual([ + '/keystore', + { + encryptedKeystore: { + userEmail: email, + type: KeystoreType.ENCRYPTION, + encryptedKeys: expect.objectContaining({ + publicKeys: { eccPublicKeyBase64: expect.any(String), kyberPublicKeyBase64: expect.any(String) }, + privateKeys: { + eccPrivateKeyBase64: expect.any(String), + kyberPrivateKeyBase64: expect.any(String), + }, + }), + }, + }, + headers, + ]); + + expect(postCall.mock.calls[1]).toEqual([ + '/keystore', + { + encryptedKeystore: { + userEmail: email, + type: KeystoreType.RECOVERY, + encryptedKeys: expect.objectContaining({ + publicKeys: { eccPublicKeyBase64: expect.any(String), kyberPublicKeyBase64: expect.any(String) }, + privateKeys: { + eccPrivateKeyBase64: expect.any(String), + kyberPrivateKeyBase64: expect.any(String), + }, + }), + }, + }, + headers, + ]); + }); + it('When a keystore downloading is requested, then it should successfully download the keystore', async () => { + const { client, headers } = clientAndHeadersWithToken(); + const postCall = vi.spyOn(HttpClient.prototype, 'getWithParams').mockResolvedValue({ encryptionKeystore }); + const result = await client.downloadKeystoreFromServer(email, KeystoreType.ENCRYPTION); + + expect(postCall.mock.calls[0]).toEqual([ + '/user/keystore', + { + userEmail: email, + keystoreType: KeystoreType.ENCRYPTION, + }, + headers, + ]); + expect(result).toEqual({ encryptionKeystore: encryptionKeystore }); + }); + + it('When user email keys are requested, then it should successfully download keystore and open it', async () => { + const { client, headers } = clientAndHeadersWithToken(); + const postCall = vi.spyOn(HttpClient.prototype, 'getWithParams').mockResolvedValue(encryptionKeystore); + const result = await client.getUserEmailKeys(email, baseKey); + + expect(postCall.mock.calls[0]).toEqual([ + '/user/keystore', + { + userEmail: email, + keystoreType: KeystoreType.ENCRYPTION, + }, + headers, + ]); + expect(result).toEqual(keys); + }); + + it('When email key recovery is requested, then it should successfully download keystore and open it', async () => { + const { client, headers } = clientAndHeadersWithToken(); + const postCall = vi.spyOn(HttpClient.prototype, 'getWithParams').mockResolvedValue(recoveryKeystore); + const result = await client.recoverUserEmailKeys(email, recoveryCodes); + + expect(postCall.mock.calls[0]).toEqual([ + '/user/keystore', + { + userEmail: email, + keystoreType: KeystoreType.RECOVERY, + }, + headers, + ]); + expect(result).toEqual(keys); + }); + }); + + describe('test public keys call methods', async () => { + const userA = { + email: 'user A email', + name: 'user A name', + }; + const userB = { + email: 'user B email', + name: 'user B name', + }; + const userC = { + email: 'user C email', + name: 'user C name', + }; + const emailKeysA = await generateEmailKeys(); + const emailKeysB = await generateEmailKeys(); + const emailKeysC = await generateEmailKeys(); + + const userAwithKeys = { ...userA, publicKeys: emailKeysA.publicKeys }; + const userBwithKeys = { ...userB, publicKeys: emailKeysB.publicKeys }; + const userCwithKeys = { ...userC, publicKeys: emailKeysC.publicKeys }; + + const emailKeysABase64 = await publicKeyToBase64(emailKeysA.publicKeys); + const emailKeysBBase64 = await publicKeyToBase64(emailKeysB.publicKeys); + const emailKeysCBase64 = await publicKeyToBase64(emailKeysC.publicKeys); + + it('When user email public keys are requested, then it should successfully get them', async () => { + const { client, headers } = clientAndHeadersWithToken(); + const postCall = vi + .spyOn(HttpClient.prototype, 'post') + .mockResolvedValue([{ publicKeys: emailKeysABase64, user: userA }]); + const result = await client.getUserWithPublicKeys(userA.email); + + expect(postCall.mock.calls[0]).toEqual([ + '/users/public-keys', + { + emails: [userA.email], + }, + headers, + ]); + expect(result).toStrictEqual(userAwithKeys); + }); + + it('When public keys are requested for several users, then it should successfully get all of them', async () => { + const { client, headers } = clientAndHeadersWithToken(); + const postCall = vi.spyOn(HttpClient.prototype, 'post').mockResolvedValue([ + { publicKeys: emailKeysABase64, user: userA }, + { publicKeys: emailKeysBBase64, user: userB }, + { publicKeys: emailKeysCBase64, user: userC }, + ]); + const result = await client.getSeveralUsersWithPublicKeys([userA.email, userB.email, userC.email]); + + expect(postCall.mock.calls[0]).toEqual([ + '/users/public-keys', + { + emails: [userA.email, userB.email, userC.email], + }, + headers, + ]); + expect(result).toEqual([userAwithKeys, userBwithKeys, userCwithKeys]); + }); + }); + + describe('test email call methods', async () => { + const userA = { + email: 'user A email', + name: 'user A name', + }; + + const userB = { + email: 'user B email', + name: 'user B name', + }; + const uuid = generateUuid(); + + const emailKeysA = await generateEmailKeys(); + const emailKeysB = await generateEmailKeys(); + const emailKeysABase64 = await publicKeyToBase64(emailKeysA.publicKeys); + const emailKeysBBase64 = await publicKeyToBase64(emailKeysB.publicKeys); + + const email: Email = { + id: uuid, + body: { + text: 'Email text', + attachments: ['Email attachment'], + }, + params: { + subject: 'Email subject', + createdAt: '2026-01-21T15:11:22.000Z', + sender: userA, + recipient: userB, + recipients: [userB], + replyToEmailID: uuid, + labels: ['inbox', 'test'], + }, + }; + + const pwd = 'mock password'; + + it('When user request encrypting email, then it should successfully encrypt and send an email', async () => { + const { client, headers } = clientAndHeadersWithToken(); + const postCall = vi + .spyOn(HttpClient.prototype, 'post') + .mockResolvedValueOnce([{ publicKeys: emailKeysBBase64, user: userB }]) + .mockResolvedValueOnce({}); + await client.encryptAndSendEmail(email, emailKeysA.privateKeys, false); + + expect(postCall.mock.calls[0]).toEqual([ + '/users/public-keys', + { + emails: [userB.email], + }, + headers, + ]); + + expect(postCall.mock.calls[1]).toEqual([ + '/emails', + { + emails: [ + { + encryptedKey: { + kyberCiphertext: expect.any(String), + encryptedKey: expect.any(String), + }, + enc: { + encText: expect.any(String), + encAttachments: [expect.any(String)], + }, + recipientEmail: userB.email, + params: email.params, + id: email.id, + isSubjectEncrypted: false, + }, + ], + }, + headers, + ]); + }); + + it('When user request password protect email, then it should successfully protect and send an email', async () => { + const { client, headers } = clientAndHeadersWithToken(); + const postCall = vi.spyOn(HttpClient.prototype, 'post').mockResolvedValue({}); + await client.passwordProtectAndSendEmail(email, pwd, false); + + expect(postCall.mock.calls[0]).toEqual([ + '/emails', + { + email: { + encryptedKey: { + encryptedKey: expect.any(String), + salt: expect.any(String), + }, + enc: { + encText: expect.any(String), + encAttachments: [expect.any(String)], + }, + params: email.params, + id: email.id, + isSubjectEncrypted: false, + }, + }, + headers, + ]); + }); + + it('When user request opening a password protect email, then it should successfully open it', async () => { + const { client } = clientAndHeadersWithToken(); + const encEmail = await createPwdProtectedEmail(email, pwd, true); + const result = await client.openPasswordProtectedEmail(encEmail, pwd); + + expect(result).toEqual(email); + }); + + it('When user request decrypting an encrypted email, then it should successfully decrypt it', async () => { + const { client, headers } = clientAndHeadersWithToken(); + const recipient = { ...userB, publicKeys: emailKeysB.publicKeys }; + const postCall = vi + .spyOn(HttpClient.prototype, 'post') + .mockResolvedValue([{ publicKeys: emailKeysABase64, user: userA }]); + const encEmail = await encryptEmailHybrid(email, recipient, emailKeysA.privateKeys, true); + const result = await client.decryptEmail(encEmail, emailKeysB.privateKeys); + + expect(postCall.mock.calls[0]).toEqual([ + '/users/public-keys', + { + emails: [userA.email], + }, + headers, + ]); + + expect(result).toEqual(email); + }); + }); +}); + +function clientAndHeadersWithToken( + apiUrl = '', + clientName = 'c-name', + clientVersion = '0.1', + token = 'my-token', +): { + client: Mail; + headers: object; +} { + const appDetails: AppDetails = { + clientName, + clientVersion, + }; + const apiSecurity: ApiSecurity = { + token, + }; + const client = Mail.client(apiUrl, appDetails, apiSecurity); + const headers = headersWithToken({ clientName, clientVersion, token }); + return { client, headers }; +} diff --git a/yarn.lock b/yarn.lock index 97c142e..84dc0b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -425,6 +425,26 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@noble/curves@~2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-2.0.1.tgz#64ba8bd5e8564a02942655602515646df1cdb3ad" + integrity sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw== + dependencies: + "@noble/hashes" "2.0.1" + +"@noble/hashes@2.0.1", "@noble/hashes@^2.0.1", "@noble/hashes@~2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-2.0.1.tgz#fc1a928061d1232b0a52bb754393c37a5216c89e" + integrity sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw== + +"@noble/post-quantum@^0.5.2": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@noble/post-quantum/-/post-quantum-0.5.4.tgz#bd1095647c61e4c8fd317fa8a3977db8cd28a4b9" + integrity sha512-leww0zzIirrvwaYMPI9fj6aRIlA/c6Y0/lifQQ1YOOyHEr0MNH3yYpjXeiVG+tWdPps4XxGclFWX2INPO3Yo5w== + dependencies: + "@noble/curves" "~2.0.0" + "@noble/hashes" "~2.0.0" + "@redocly/ajv@^8.11.2": version "8.17.3" resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.17.3.tgz#98b30ee03111b27a5acdcab15d3f51622836d0e8" @@ -580,6 +600,19 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz#a03348e7b559c792b6277cc58874b89ef46e1e72" integrity sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA== +"@scure/base@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-2.0.0.tgz#ba6371fddf92c2727e88ad6ab485db6e624f9a98" + integrity sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w== + +"@scure/bip39@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-2.0.1.tgz#47a6dc15e04faf200041239d46ae3bb7c3c96add" + integrity sha512-PsxdFj/d2AcJcZDX1FXN3dDgitDDTmwf78rKZq1a6c1P1Nan1X/Sxc7667zU3U+AN60g7SxxP0YCVw2H/hBycg== + dependencies: + "@noble/hashes" "2.0.1" + "@scure/base" "2.0.0" + "@standard-schema/spec@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" @@ -1298,6 +1331,11 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== +flexsearch@^0.8.205: + version "0.8.212" + resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.8.212.tgz#b9509af778a991b938292e36fe0809a4ece4b940" + integrity sha512-wSyJr1GUWoOOIISRu+X2IXiOcVfg9qqBRyCPRUdLMIGJqPzMo+jMRlvE83t14v1j0dRMEaBbER/adQjp6Du2pw== + follow-redirects@^1.15.11: version "1.15.11" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" @@ -1401,6 +1439,11 @@ has-tostringtag@^1.0.2: dependencies: has-symbols "^1.0.3" +hash-wasm@^4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/hash-wasm/-/hash-wasm-4.12.0.tgz#f9f1a9f9121e027a9acbf6db5d59452ace1ef9bb" + integrity sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ== + hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" @@ -1421,11 +1464,16 @@ https-proxy-agent@^7.0.5: agent-base "^7.1.2" debug "4" -husky@9.1.7: +husky@9.1.7, husky@^9.1.7: version "9.1.7" resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.7.tgz#d46a38035d101b46a70456a850ff4201344c0b2d" integrity sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA== +idb@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/idb/-/idb-8.0.3.tgz#c91e558f15a8d53f1d7f53a094d226fc3ad71fd9" + integrity sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg== + ignore@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" @@ -1454,6 +1502,20 @@ index-to-position@^1.1.0: resolved "https://registry.yarnpkg.com/index-to-position/-/index-to-position-1.2.0.tgz#c800eb34dacf4dbf96b9b06c7eb78d5f704138b4" integrity sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw== +internxt-crypto@0.0.13: + version "0.0.13" + resolved "https://registry.yarnpkg.com/internxt-crypto/-/internxt-crypto-0.0.13.tgz#90c83828a34667ecf6938ad7c22d94c9f0d9808b" + integrity sha512-V8Epf4oFZQZwMyIt8mcw7w36M+tryQ9VcfaiKtcruNW/129bawZnPXEeoOfLvAV+9OYlY2ZAwIYP0EtOD2ec6g== + dependencies: + "@noble/hashes" "^2.0.1" + "@noble/post-quantum" "^0.5.2" + "@scure/bip39" "^2.0.1" + flexsearch "^0.8.205" + hash-wasm "^4.12.0" + husky "^9.1.7" + idb "^8.0.3" + uuid "^13.0.0" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -2145,7 +2207,7 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -uuid@13.0.0: +uuid@13.0.0, uuid@^13.0.0: version "13.0.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-13.0.0.tgz#263dc341b19b4d755eb8fe36b78d95a6b65707e8" integrity sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==