diff --git a/package.json b/package.json index e410708..16a269e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@internxt/sdk", "author": "Internxt ", - "version": "1.15.6", + "version": "1.15.7", "description": "An sdk for interacting with Internxt's services", "repository": { "type": "git", @@ -25,7 +25,8 @@ "build": "yarn clean && tsc", "lint": "eslint ./src", "format": "prettier --write **/*.{js,jsx,tsx,ts}", - "swagger": "openapi-typescript https://gateway.internxt.com/drive/api-json -o ./src/schema.ts && yarn format" + "swagger": "openapi-typescript https://gateway.internxt.com/drive/api-json -o ./src/schema.ts && yarn format", + "swagger:mail": "openapi-typescript http://localhost:3100/api-json -o ./src/mail/schema.ts && yarn format" }, "devDependencies": { "@internxt/eslint-config-internxt": "2.1.0", @@ -51,4 +52,4 @@ "prettier --write" ] } -} \ No newline at end of file +} diff --git a/src/mail/api.ts b/src/mail/api.ts new file mode 100644 index 0000000..0729774 --- /dev/null +++ b/src/mail/api.ts @@ -0,0 +1,148 @@ +import { ApiSecurity, ApiUrl, AppDetails } from '../shared'; +import { headersWithToken } from '../shared/headers'; +import { HttpClient } from '../shared/http/client'; +import { + EncryptedKeystore, + KeystoreType, + HybridEncryptedEmail, + PwdProtectedEmail, + RecipientWithPublicKey, + base64ToUint8Array, + EmailPublicParameters, +} from 'internxt-crypto'; +import { + MailboxResponse, + EmailListResponse, + EmailResponse, + EmailCreatedResponse, + SendEmailRequest, + DraftEmailRequest, + UpdateEmailRequest, + ListEmailsQuery, +} from './types'; + +export class MailApi { + 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 MailApi(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 keystore - The encrypted keystore + * @returns Server response + */ + async uploadKeystore(keystore: EncryptedKeystore): Promise { + return this.client.post(`${this.apiUrl}/keystore`, { encryptedKeystore: keystore }, this.headers()); + } + + /** + * 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 downloadKeystore(userEmail: string, keystoreType: KeystoreType): Promise { + return this.client.getWithParams(`${this.apiUrl}/user/keystore`, { userEmail, keystoreType }, this.headers()); + } + + /** + * Requests users with corresponding public keys from the server + * + * @param emails - The emails of the users + * @returns Users with corresponding public keys + */ + async getUsersWithPublicKeys(emails: string[]): Promise { + const response = await this.client.post<{ publicKey: string; email: string }[]>( + `${this.apiUrl}/users/public-keys`, + { emails }, + this.headers(), + ); + + const result = await Promise.all( + response.map(async (item) => { + const publicHybridKey = base64ToUint8Array(item.publicKey); + return { email: item.email, publicHybridKey }; + }), + ); + + return result; + } + + /** + * Sends the encrypted emails to the server + * + * @param emails - The encrypted emails + * @param params - The public parameters of the email + * @returns Server response + */ + async sendE2EEmails(emails: HybridEncryptedEmail[], params: EmailPublicParameters): Promise { + return this.client.post(`${this.apiUrl}/emails`, { emails, params }, this.headers()); + } + + /** + * Sends the password-protected email to the server + * + * @param email - The password-protected email + * @param params - The public parameters of the email + * @returns Server response + */ + async sendE2EPasswordProtectedEmail(email: PwdProtectedEmail, params: EmailPublicParameters): Promise { + return this.client.post(`${this.apiUrl}/emails`, { email, params }, this.headers()); + } + + async getMailboxes(): Promise { + return this.client.get(`${this.apiUrl}/email/mailboxes`, this.headers()); + } + + async listEmails(query?: ListEmailsQuery): Promise { + return this.client.getWithParams(`${this.apiUrl}/email`, query ?? {}, this.headers()); + } + + async getEmail(id: string): Promise { + return this.client.get(`${this.apiUrl}/email/${id}`, this.headers()); + } + + async deleteEmail(id: string): Promise { + return this.client.delete(`${this.apiUrl}/email/${id}`, this.headers()); + } + + async updateEmail(id: string, body: UpdateEmailRequest): Promise { + return this.client.patch(`${this.apiUrl}/email/${id}`, body, this.headers()); + } + + async sendEmail(body: SendEmailRequest): Promise { + return this.client.post(`${this.apiUrl}/email/send`, body, this.headers()); + } + + async saveDraft(body: DraftEmailRequest): Promise { + return this.client.post(`${this.apiUrl}/email/drafts`, body, this.headers()); + } + + /** + * 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/src/mail/create.ts b/src/mail/crypto.ts similarity index 100% rename from src/mail/create.ts rename to src/mail/crypto.ts diff --git a/src/mail/index.ts b/src/mail/index.ts index fa4c202..f28f3b0 100644 --- a/src/mail/index.ts +++ b/src/mail/index.ts @@ -1,200 +1,2 @@ -import { ApiSecurity, ApiUrl, AppDetails } from '../shared'; -import { headersWithToken } from '../shared/headers'; -import { HttpClient } from '../shared/http/client'; -import { - EncryptedKeystore, - KeystoreType, - HybridEncryptedEmail, - PwdProtectedEmail, - HybridKeyPair, - Email, - RecipientWithPublicKey, - base64ToUint8Array, - EmailPublicParameters, -} from 'internxt-crypto'; - -import { createKeystores, encryptEmail, passwordProtectAndSendEmail, openKeystore, recoverKeys } from './create'; - -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 and keys of the user - */ - async createAndUploadKeystores( - userEmail: string, - baseKey: Uint8Array, - ): Promise<{ recoveryCodes: string; keys: HybridKeyPair }> { - const { encryptionKeystore, recoveryKeystore, recoveryCodes, keys } = await createKeystores(userEmail, baseKey); - await Promise.all([this.uploadKeystoreToServer(encryptionKeystore), this.uploadKeystoreToServer(recoveryKeystore)]); - return { recoveryCodes, keys }; - } - - /** - * 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 hybrid keys of the user - */ - async getUserEmailKeys(userEmail: string, baseKey: Uint8Array): Promise { - const keystore = await this.downloadKeystoreFromServer(userEmail, KeystoreType.ENCRYPTION); - return openKeystore(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 hybrid keys of the user - */ - async recoverUserEmailKeys(userEmail: string, recoveryCodes: string): Promise { - const keystore = await this.downloadKeystoreFromServer(userEmail, KeystoreType.RECOVERY); - return recoverKeys(keystore, recoveryCodes); - } - - /** - * 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<{ publicKey: string; email: string }[]>( - `${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 publicHybridKey = base64ToUint8Array(singleResponse.publicKey); - const result = { email: singleResponse.email, publicHybridKey }; - 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<{ publicKey: string; email: string }[]>( - `${this.apiUrl}/users/public-keys`, - { emails }, - this.headers(), - ); - - const result = await Promise.all( - response.map(async (item) => { - const publicHybridKey = base64ToUint8Array(item.publicKey); - return { email: item.email, publicHybridKey }; - }), - ); - - return result; - } - - /** - * Sends the encrypted emails to the server - * - * @param emails - The encrypted emails - * @param params - The public parameters of the email (sender, recipients, CCs, BCCs, etc.) - * @returns Server response - */ - async sendEncryptedEmail(emails: HybridEncryptedEmail[], params: EmailPublicParameters): Promise { - return this.client.post(`${this.apiUrl}/emails`, { emails, params }, this.headers()); - } - - /** - * Encrypts and sends email(s) to the server - * - * @param email - The message to encrypt - * @param aux - The optional auxilary data to encrypt together with the email (e.g. email sender) - * @returns Server response - */ - async encryptAndSendEmail(email: Email, aux?: string): Promise { - const recipientEmails = email.params.recipients.map((user) => user.email); - const recipients = await this.getSeveralUsersWithPublicKeys(recipientEmails); - - const encEmails = await encryptEmail(email, recipients, aux); - return this.sendEncryptedEmail(encEmails, email.params); - } - - /** - * Sends the password-protected email to the server - * - * @param email - The password-protected email - * @returns Server response - */ - async sendPasswordProtectedEmail(email: PwdProtectedEmail, params: EmailPublicParameters): Promise { - return this.client.post(`${this.apiUrl}/emails`, { email, params }, this.headers()); - } - - /** - * Creates the password-protected email and sends it to the server - * - * @param email - The email - * @param pwd - The password - * @param aux - The optional auxilary data to encrypt together with the email (e.g. email sender) - * @returns Server response - */ - async passwordProtectAndSendEmail(email: Email, pwd: string, aux?: string): Promise { - const encEmail = await passwordProtectAndSendEmail(email, pwd, aux); - return this.sendPasswordProtectedEmail(encEmail, email.params); - } - - /** - * 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, - }); - } -} +export * from './types'; +export * from './mail'; diff --git a/src/mail/mail.ts b/src/mail/mail.ts new file mode 100644 index 0000000..0aecf54 --- /dev/null +++ b/src/mail/mail.ts @@ -0,0 +1,200 @@ +import { ApiSecurity, ApiUrl, AppDetails } from '../shared'; +import { + EncryptedKeystore, + KeystoreType, + HybridEncryptedEmail, + PwdProtectedEmail, + HybridKeyPair, + Email, + RecipientWithPublicKey, + EmailPublicParameters, +} from 'internxt-crypto'; + +import { MailApi } from './api'; +import { + MailboxResponse, + EmailListResponse, + EmailResponse, + EmailCreatedResponse, + SendEmailRequest, + DraftEmailRequest, + UpdateEmailRequest, + ListEmailsQuery, +} from './types'; +import { createKeystores, encryptEmail, passwordProtectAndSendEmail, openKeystore, recoverKeys } from './crypto'; + +export class Mail { + private readonly api: MailApi; + + public static client(apiUrl: ApiUrl, appDetails: AppDetails, apiSecurity: ApiSecurity) { + return new Mail(apiUrl, appDetails, apiSecurity); + } + + private constructor(apiUrl: ApiUrl, appDetails: AppDetails, apiSecurity: ApiSecurity) { + this.api = MailApi.client(apiUrl, appDetails, apiSecurity); + } + + /** + * Uploads encrypted keystore to the server + * + * @param encryptedKeystore - The encrypted keystore + * @returns Server response + */ + async uploadKeystoreToServer(encryptedKeystore: EncryptedKeystore): Promise { + return this.api.uploadKeystore(encryptedKeystore); + } + + /** + * 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 and keys of the user + */ + async createAndUploadKeystores( + userEmail: string, + baseKey: Uint8Array, + ): Promise<{ recoveryCodes: string; keys: HybridKeyPair }> { + const { encryptionKeystore, recoveryKeystore, recoveryCodes, keys } = await createKeystores(userEmail, baseKey); + await Promise.all([this.api.uploadKeystore(encryptionKeystore), this.api.uploadKeystore(recoveryKeystore)]); + return { recoveryCodes, keys }; + } + + /** + * 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.api.downloadKeystore(userEmail, keystoreType); + } + + /** + * 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 hybrid keys of the user + */ + async getUserEmailKeys(userEmail: string, baseKey: Uint8Array): Promise { + const keystore = await this.api.downloadKeystore(userEmail, KeystoreType.ENCRYPTION); + return openKeystore(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 hybrid keys of the user + */ + async recoverUserEmailKeys(userEmail: string, recoveryCodes: string): Promise { + const keystore = await this.api.downloadKeystore(userEmail, KeystoreType.RECOVERY); + return recoverKeys(keystore, recoveryCodes); + } + + /** + * 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 results = await this.api.getUsersWithPublicKeys([userEmail]); + if (!results[0]) throw new Error(`No public keys found for ${userEmail}`); + return results[0]; + } + + /** + * 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 { + return this.api.getUsersWithPublicKeys(emails); + } + + /** + * Sends the encrypted emails to the server + * + * @param emails - The encrypted emails + * @param params - The public parameters of the email (sender, recipients, CCs, BCCs, etc.) + * @returns Server response + */ + async sendE2EEncryptedEmail(emails: HybridEncryptedEmail[], params: EmailPublicParameters): Promise { + return this.api.sendE2EEmails(emails, params); + } + + /** + * Encrypts and sends email(s) to the server + * + * @param email - The message to encrypt + * @param aux - The optional auxilary data to encrypt together with the email (e.g. email sender) + * @returns Server response + */ + async e2eEncryptAndSendEmail(email: Email, aux?: string): Promise { + const recipientEmails = email.params.recipients?.map((user) => user.email); + + if (!recipientEmails) throw new Error('No recipients found'); + + const recipients = await this.api.getUsersWithPublicKeys(recipientEmails); + + const encEmails = await encryptEmail(email, recipients, aux); + return this.api.sendE2EEmails(encEmails, email.params); + } + + /** + * Sends the password-protected email to the server + * + * @param email - The password-protected email + * @param params - The public parameters of the email + * @returns Server response + */ + async sendE2EPasswordProtectedEmail(email: PwdProtectedEmail, params: EmailPublicParameters): Promise { + return this.api.sendE2EPasswordProtectedEmail(email, params); + } + + /** + * Creates the password-protected E2E email and sends it to the server + * + * @param email - The email + * @param pwd - The password + * @param aux - The optional auxiliary data to encrypt together with the email (e.g. email sender) + * @returns Server response + */ + async e2ePasswordProtectAndSendEmail(email: Email, pwd: string, aux?: string): Promise { + const encEmail = await passwordProtectAndSendEmail(email, pwd, aux); + return this.api.sendE2EPasswordProtectedEmail(encEmail, email.params); + } + + async getMailboxes(): Promise { + return this.api.getMailboxes(); + } + + async listEmails(query?: ListEmailsQuery): Promise { + return this.api.listEmails(query); + } + + async getEmail(id: string): Promise { + return this.api.getEmail(id); + } + + async deleteEmail(id: string): Promise { + return this.api.deleteEmail(id); + } + + async updateEmail(id: string, body: UpdateEmailRequest): Promise { + return this.api.updateEmail(id, body); + } + + async sendEmail(body: SendEmailRequest): Promise { + return this.api.sendEmail(body); + } + + async saveDraft(body: DraftEmailRequest): Promise { + return this.api.saveDraft(body); + } +} diff --git a/src/mail/schema.ts b/src/mail/schema.ts new file mode 100644 index 0000000..9c08212 --- /dev/null +++ b/src/mail/schema.ts @@ -0,0 +1,487 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + '/health': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations['HealthController_check']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/email/mailboxes': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List mailboxes + * @description Returns every mailbox for the authenticated user, including folder counts. + */ + get: operations['EmailController_getMailboxes']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/email': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List emails + * @description Paginated list of email summaries for a given mailbox. Defaults to the inbox. + */ + get: operations['EmailController_list']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/email/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get email by ID + * @description Returns the full email including body content, headers, and metadata. + */ + get: operations['EmailController_get']; + put?: never; + post?: never; + /** + * Delete an email + * @description Permanently deletes an email by ID. + */ + delete: operations['EmailController_delete']; + options?: never; + head?: never; + /** + * Update an email + * @description Partially update an email: move it to another mailbox, mark as read/unread, or flag/unflag. Multiple operations can be combined in a single request. + */ + patch: operations['EmailController_update']; + trace?: never; + }; + '/email/send': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Send an email + * @description Composes and sends an email on behalf of the authenticated user. At least one recipient in `to` is required. + */ + post: operations['EmailController_send']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/email/drafts': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Save a draft + * @description Creates a new draft email. All fields are optional so partial drafts can be saved. + */ + post: operations['EmailController_saveDraft']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + MailboxResponseDto: { + /** @example f3a1b2c4-… */ + id: string; + /** @example Inbox */ + name: string; + /** + * @example inbox + * @enum {string|null} + */ + type: 'inbox' | 'drafts' | 'sent' | 'trash' | 'spam' | 'archive' | null; + /** @example null */ + parentId: Record | null; + /** @example 142 */ + totalEmails: number; + /** @example 3 */ + unreadEmails: number; + }; + EmailAddressDto: { + /** @example Alice Smith */ + name?: string; + /** @example alice@internxt.me */ + email: string; + }; + EmailSummaryResponseDto: { + /** @example Ma1f09b… */ + id: string; + /** @example T1a2b3c… */ + threadId: string; + from: components['schemas']['EmailAddressDto'][]; + to: components['schemas']['EmailAddressDto'][]; + /** @example Weekly sync notes */ + subject: string; + /** @example 2025-06-15T10:30:00Z */ + receivedAt: string; + /** @example Hi team, here are the notes from… */ + preview: string; + /** @example true */ + isRead: boolean; + /** @example false */ + isFlagged: boolean; + /** @example false */ + hasAttachment: boolean; + /** + * @description Size in bytes + * @example 4096 + */ + size: number; + }; + EmailListResponseDto: { + emails: components['schemas']['EmailSummaryResponseDto'][]; + /** + * @description Total emails in the mailbox + * @example 142 + */ + total: number; + }; + EmailResponseDto: { + /** @example Ma1f09b… */ + id: string; + /** @example T1a2b3c… */ + threadId: string; + from: components['schemas']['EmailAddressDto'][]; + to: components['schemas']['EmailAddressDto'][]; + /** @example Weekly sync notes */ + subject: string; + /** @example 2025-06-15T10:30:00Z */ + receivedAt: string; + /** @example Hi team, here are the notes from… */ + preview: string; + /** @example true */ + isRead: boolean; + /** @example false */ + isFlagged: boolean; + /** @example false */ + hasAttachment: boolean; + /** + * @description Size in bytes + * @example 4096 + */ + size: number; + cc: components['schemas']['EmailAddressDto'][]; + bcc: components['schemas']['EmailAddressDto'][]; + replyTo: components['schemas']['EmailAddressDto'][]; + /** @example 2025-06-15T10:29:55Z */ + sentAt: Record | null; + /** @example Hi team, here are the notes… */ + textBody: Record | null; + /** @example

Hi team, here are the notes…

*/ + htmlBody: Record | null; + }; + SendEmailRequestDto: { + /** @description Primary recipients (at least one required) */ + to: components['schemas']['EmailAddressDto'][]; + cc?: components['schemas']['EmailAddressDto'][]; + bcc?: components['schemas']['EmailAddressDto'][]; + /** @example Weekly sync notes */ + subject: string; + /** + * @description Plain-text version of the email body + * @example Hi team, here are the notes from today… + */ + textBody?: string; + /** + * @description HTML version of the email body + * @example

Hi team, here are the notes from today…

+ */ + htmlBody?: string; + }; + EmailCreatedResponseDto: { + /** + * @description ID of the created email + * @example Ma1f09b… + */ + id: string; + }; + DraftEmailRequestDto: { + to?: components['schemas']['EmailAddressDto'][]; + cc?: components['schemas']['EmailAddressDto'][]; + bcc?: components['schemas']['EmailAddressDto'][]; + /** @example Draft: project update */ + subject?: string; + /** @example Still working on this… */ + textBody?: string; + /** @example

Still working on this…

*/ + htmlBody?: string; + }; + UpdateEmailRequestDto: { + /** + * @description Move the email to this mailbox + * @example trash + * @enum {string} + */ + mailbox?: 'inbox' | 'drafts' | 'sent' | 'trash' | 'spam' | 'archive'; + /** + * @description Mark the email as read or unread + * @example true + */ + isRead?: boolean; + /** + * @description Flag or unflag the email + * @example false + */ + isFlagged?: boolean; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + HealthController_check: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + EmailController_getMailboxes: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['MailboxResponseDto'][]; + }; + }; + }; + }; + EmailController_list: { + parameters: { + query?: { + /** @description Mailbox to list. Defaults to `inbox`. */ + mailbox?: 'inbox' | 'drafts' | 'sent' | 'trash' | 'spam' | 'archive'; + /** @description Maximum number of emails to return. Defaults to `20`. */ + limit?: number; + /** @description Zero-based offset for pagination. Defaults to `0`. */ + position?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['EmailListResponseDto']; + }; + }; + }; + }; + EmailController_get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Email ID */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['EmailResponseDto']; + }; + }; + /** @description Email not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + EmailController_delete: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Email ID */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Email deleted successfully */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Email not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + EmailController_update: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Email ID */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['UpdateEmailRequestDto']; + }; + }; + responses: { + /** @description Email updated successfully */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Email not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + EmailController_send: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['SendEmailRequestDto']; + }; + }; + responses: { + /** @description Email sent successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['EmailCreatedResponseDto']; + }; + }; + }; + }; + EmailController_saveDraft: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['DraftEmailRequestDto']; + }; + }; + responses: { + /** @description Draft saved successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['EmailCreatedResponseDto']; + }; + }; + }; + }; +} diff --git a/src/mail/types.ts b/src/mail/types.ts new file mode 100644 index 0000000..7b4a244 --- /dev/null +++ b/src/mail/types.ts @@ -0,0 +1,12 @@ +import { components, operations } from './schema'; + +export type MailboxResponse = components['schemas']['MailboxResponseDto']; +export type EmailSummaryResponse = components['schemas']['EmailSummaryResponseDto']; +export type EmailListResponse = components['schemas']['EmailListResponseDto']; +export type EmailResponse = components['schemas']['EmailResponseDto']; +export type EmailCreatedResponse = components['schemas']['EmailCreatedResponseDto']; +export type SendEmailRequest = components['schemas']['SendEmailRequestDto']; +export type DraftEmailRequest = components['schemas']['DraftEmailRequestDto']; +export type UpdateEmailRequest = components['schemas']['UpdateEmailRequestDto']; +export type EmailAddress = components['schemas']['EmailAddressDto']; +export type ListEmailsQuery = operations['EmailController_list']['parameters']['query']; diff --git a/src/shared/types/errors.ts b/src/shared/types/errors.ts index 0e5c587..49241b8 100644 --- a/src/shared/types/errors.ts +++ b/src/shared/types/errors.ts @@ -11,5 +11,7 @@ export default class AppError extends Error { this.code = code; this.headers = headers; this.requestId = headers?.['x-request-id']; + + Object.setPrototypeOf(this, AppError.prototype); } } diff --git a/test/mail/index.test.ts b/test/mail/index.test.ts index 77fa04f..26c74cb 100644 --- a/test/mail/index.test.ts +++ b/test/mail/index.test.ts @@ -14,7 +14,7 @@ import { uint8ArrayToBase64, } from 'internxt-crypto'; import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { decryptEmail, openPasswordProtectedEmail } from '../../src/mail/create'; +import { decryptEmail, openPasswordProtectedEmail } from '../../src/mail/crypto'; describe('Mail service tests', () => { beforeEach(() => { @@ -215,7 +215,7 @@ describe('Mail service tests', () => { .spyOn(HttpClient.prototype, 'post') .mockResolvedValueOnce([{ publicKey: publicKeyB, email: userB.email }]) .mockResolvedValueOnce({}); - await client.encryptAndSendEmail(email); + await client.e2eEncryptAndSendEmail(email); expect(postCall.mock.calls[0]).toEqual([ '/users/public-keys', @@ -251,7 +251,7 @@ describe('Mail service tests', () => { 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); + await client.e2ePasswordProtectAndSendEmail(email, pwd); expect(postCall.mock.calls[0]).toEqual([ '/emails', diff --git a/yarn.lock b/yarn.lock index ce92077..eb546f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2073,4 +2073,4 @@ yargs-parser@^21.1.1: yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== \ No newline at end of file