UNPKG

fastmail-masked-email

Version:

A library for creating, deleting, and updating Fastmail masked emails

340 lines 14.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MaskedEmailService = void 0; const axios_1 = __importDefault(require("axios")); const debug_1 = __importDefault(require("debug")); const constants_1 = require("./constants"); const invalidArgumentError_1 = require("./error/invalidArgumentError"); /** * MaskedEmailService - A comprehensive service for managing Fastmail masked emails * Provides methods for creating, retrieving, updating, and deleting masked email addresses */ class MaskedEmailService { /** * Creates a new MaskedEmailService instance * @param token - Optional Fastmail API token for authentication * @param hostname - Optional Fastmail API hostname; defaults to api.fastmail.com */ constructor(token, hostname) { this.token = token; this.hostname = hostname; this.session = null; this.debugLogger = (0, debug_1.default)('MaskedEmailService:debug'); this.errorLogger = (0, debug_1.default)('MaskedEmailService:error'); this.token = token || process.env.JMAP_TOKEN; this.hostname = hostname || process.env.JMAP_HOSTNAME || constants_1.API_HOSTNAME; } /** * Initialize the service by getting a session from the JMAP server * @returns Promise that resolves when the session is established * @throws Error if no auth token is provided */ async initialize() { if (!this.token) { throw new Error('No auth token provided and JMAP_TOKEN environment variable is not set. Please provide a token.'); } const authUrl = `https://${this.hostname}/jmap/session`; const headers = this.buildHeaders(this.token); try { const response = await axios_1.default.get(authUrl, { headers }); this.session = response.data; this.debugLogger('Session initialized: %o', JSON.stringify(this.session)); } catch (error) { return this.handleAxiosError(error, constants_1.Action.SESSION); } } getSession() { this.ensureInitialized(); return this.session; } /** * Creates a new masked email address * @param options - The {@link CreateOptions|options} for creating the masked email * @throws {@link InvalidArgumentError} if no session is provided */ async createEmail(options = {}) { this.ensureInitialized(); const { apiUrl, accountId } = this.parseSession(); const headers = this.buildHeaders(this.token); const state = options.state || 'enabled'; const requestBody = { using: [constants_1.JMAP.CORE, constants_1.MASKED_EMAIL_CAPABILITY], methodCalls: [ [ constants_1.MASKED_EMAIL_CALLS.set, { accountId, create: { ['0']: Object.assign(Object.assign({}, options), { state }) } }, 'a' ] ] }; this.debugLogger('createEmail() request body: %o', JSON.stringify(requestBody)); try { const response = await axios_1.default.post(apiUrl, requestBody, { headers }); this.debugLogger('createEmail() response: %o', JSON.stringify(response.data)); const { data } = response; return Object.assign(Object.assign({}, data.methodResponses[0][1].created['0']), { state }); } catch (error) { return this.handleAxiosError(error, constants_1.Action.CREATE); } } /** * Retrieves all masked emails * @throws {@link InvalidArgumentError} if no session is provided * @returns A list of all {@link MaskedEmail} objects */ async getAllEmails() { this.ensureInitialized(); const { apiUrl, accountId } = this.parseSession(); const headers = this.buildHeaders(this.token); const body = { using: [constants_1.JMAP.CORE, constants_1.MASKED_EMAIL_CAPABILITY], methodCalls: [[constants_1.MASKED_EMAIL_CALLS.get, { accountId, ids: null }, 'a']] }; this.debugLogger('getAllEmails() body: %o', JSON.stringify(body)); try { const response = await axios_1.default.post(apiUrl, body, { headers }); this.debugLogger('getAllEmails() response: %o', JSON.stringify(response.data)); const jmapResponse = response.data; const methodResponse = jmapResponse.methodResponses[0][1]; return methodResponse.list; } catch (error) { return this.handleAxiosError(error, constants_1.Action.LIST); } } /** * Get a masked email by id * @param id - The id of the masked email address. * @returns A {@link MaskedEmail} object * @throws {@link InvalidArgumentError} if no session is provided or no id is provided */ async getEmailById(id) { this.ensureInitialized(); if (!id) { return Promise.reject(new invalidArgumentError_1.InvalidArgumentError('No id provided')); } const { apiUrl, accountId } = this.parseSession(); const headers = this.buildHeaders(this.token); const body = { using: [constants_1.JMAP.CORE, constants_1.MASKED_EMAIL_CAPABILITY], methodCalls: [[constants_1.MASKED_EMAIL_CALLS.get, { accountId, ids: [id] }, 'a']] }; try { const response = await axios_1.default.post(apiUrl, body, { headers }); this.debugLogger('getEmailById() body: %o', JSON.stringify(body)); const responseData = response.data; this.debugLogger('getEmailById() response %o', JSON.stringify(response.data)); if (this.maskedEmailNotFound(id, responseData)) { return Promise.reject(new Error(`No masked email found with id ${id}`)); } return responseData.methodResponses[0][1].list[0]; } catch (error) { return this.handleAxiosError(error, constants_1.Action.GET_BY_ID); } } /** * Get a masked email by address * @param address - The address to retrieve * @returns A {@link MaskedEmail} object */ async getEmailByAddress(address) { try { const maskedEmails = await this.getAllEmails(); return this.filterByAddress(address, maskedEmails); } catch (error) { return Promise.reject(error); } } /** * Updates a masked email * @param id - The id of the masked email to update * @param options - The {@link Options} containing the fields to update * @throws {@link InvalidArgumentError} if no id is provided, no session is provided, or the {@link Options} are empty */ async updateEmail(id, options) { this.ensureInitialized(); if (!id) { return Promise.reject(new invalidArgumentError_1.InvalidArgumentError('No id provided')); } if (Object.keys(options).length === 0) { return Promise.reject(new invalidArgumentError_1.InvalidArgumentError('No options provided. Please provide at least one option to updateEmail.')); } const validOptions = ['description', 'forDomain', 'state']; const invalidOptions = Object.keys(options).filter((option) => !validOptions.includes(option)); if (invalidOptions.length > 0) { return Promise.reject(new invalidArgumentError_1.InvalidArgumentError(`Invalid options provided: ${invalidOptions.join(', ')}`)); } const { apiUrl, accountId } = this.parseSession(); const headers = this.buildHeaders(this.token); const body = { using: [constants_1.JMAP.CORE, constants_1.MASKED_EMAIL_CAPABILITY], methodCalls: [ [ constants_1.MASKED_EMAIL_CALLS.set, { accountId, update: { [id]: Object.assign({}, options) } }, 'a' ] ] }; this.debugLogger('updateEmail() body: %o', JSON.stringify(body)); try { const response = await axios_1.default.post(apiUrl, body, { headers }); this.debugLogger('updateEmail() response: %o', JSON.stringify(response.data)); const data = await response.data; return data.methodResponses[0][1].updated; } catch (error) { return this.handleAxiosError(error, constants_1.Action.UPDATE); } } /** * Deletes a masked email by setting the state to deleted * @param id - The id of the masked email to deleteEmail */ async deleteEmail(id) { return await this.updateEmail(id, { state: 'deleted' }); } /** * Disables a masked email by setting the state to disabled * @param id - The id of the masked email to disableEmail */ async disableEmail(id) { return await this.updateEmail(id, { state: 'disabled' }); } /** * Enables a masked email by setting the state to enabled * @param id - The id of the masked email to enableEmail */ async enableEmail(id) { return await this.updateEmail(id, { state: 'enabled' }); } /** * Permanently deletes a masked email * @param id - The id of the masked email to permanently deleteEmail * @throws {@link InvalidArgumentError} if no id is provided or no session is provided */ async permanentlyDeleteEmail(id) { this.ensureInitialized(); if (!id) { return Promise.reject(new invalidArgumentError_1.InvalidArgumentError('No id provided')); } const { apiUrl, accountId } = this.parseSession(); const headers = this.buildHeaders(this.token); const body = { using: [constants_1.JMAP.CORE, constants_1.MASKED_EMAIL_CAPABILITY], methodCalls: [[constants_1.MASKED_EMAIL_CALLS.set, { accountId, destroy: [id] }, 'a']] }; this.debugLogger('permanentlyDeleteEmail() body: %o', JSON.stringify(body)); try { const response = await axios_1.default.post(apiUrl, body, { headers }); this.debugLogger('permanentlyDeleteEmail() response: %o', JSON.stringify(response.data)); const data = await response.data; // Check if the email was not destroyed if (data.methodResponses[0][1].notDestroyed) { const notDestroyedObj = data.methodResponses[0][1].notDestroyed[id]; this.errorLogger('permanentlyDeleteEmail() error: %o', JSON.stringify(notDestroyedObj)); return Promise.reject(new Error(notDestroyedObj.description)); } else { return data.methodResponses[0][1].destroyed; } } catch (error) { return this.handleAxiosError(error, constants_1.Action.DELETE); } } /** * Filter masked emails by state * @param state - The state to filter by * @param list - The list of masked emails (optional, will fetch all if not provided) * @returns Promise that resolves to a filtered {@link MaskedEmail} array */ async filterByState(state, list) { const emails = list || (await this.getAllEmails()); return emails.filter((me) => me.state === state); } /** * Filter masked emails by domain * @param domain - The domain to filter by * @param list - The list of masked emails (optional, will fetch all if not provided) * @returns Promise that resolves to a filtered {@link MaskedEmail} array */ async filterByForDomain(domain, list) { const emails = list || (await this.getAllEmails()); return emails.filter((me) => me.forDomain === domain); } // Private utility methods ensureInitialized() { if (!this.session) { throw new invalidArgumentError_1.InvalidArgumentError('Service not initialized. Call initialize() first.'); } } parseSession() { const accountId = this.session.primaryAccounts[constants_1.JMAP.CORE]; const { apiUrl } = this.session; return { accountId, apiUrl }; } /** * Builds headers for requests using the JMAP token * @param authToken - The JMAP authentication token */ buildHeaders(authToken) { return { 'Content-Type': 'application/json', Authorization: `Bearer ${authToken}` }; } maskedEmailNotFound(id, response) { const notFoundIds = response.methodResponses[0][1].notFound; if (notFoundIds && notFoundIds.length > 0) { return notFoundIds.includes(id); } return false; } filterByAddress(address, list) { return list.filter((me) => me.email === address); } /** * Handles an axios error and returns a rejected promise with a formatted error message based on the type of error and action attempted. * @param error - The axios error * @param action - The action that was being performed when the error occurred */ async handleAxiosError(error, action) { if (error.response) { const errorMessage = `${action} failed with status code ${error.response.status}: ${error.response.statusText}. ${error.response.data}`; this.errorLogger('Error response from axios: %o', error.response); return Promise.reject(new Error(errorMessage)); } else if (error.request) { const errorMessage = `${action} request was made, but no response was received. Error message: ${error.message}`; this.errorLogger('Error request: %o', error.request); return Promise.reject(new Error(errorMessage)); } else { const errorMessage = `An error occurred while ${action.toLowerCase()}. Error message: ${error.message}`; this.errorLogger('Error: %o', error); return Promise.reject(new Error(errorMessage)); } } } exports.MaskedEmailService = MaskedEmailService; //# sourceMappingURL=MaskedEmailService.js.map