UNPKG

nylas

Version:

A NodeJS wrapper for the Nylas REST API for email, contacts, and calendar.

176 lines (175 loc) 6.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createFileRequestBuilder = createFileRequestBuilder; exports.encodeAttachmentStreams = encodeAttachmentStreams; exports.objKeysToCamelCase = objKeysToCamelCase; exports.objKeysToSnakeCase = objKeysToSnakeCase; exports.safePath = safePath; exports.makePathParams = makePathParams; exports.calculateTotalPayloadSize = calculateTotalPayloadSize; const change_case_1 = require("change-case"); const fs = require("fs"); const path = require("path"); const mime = require("mime-types"); const stream_1 = require("stream"); function createFileRequestBuilder(filePath) { const stats = fs.statSync(filePath); const filename = path.basename(filePath); const contentType = mime.lookup(filePath) || 'application/octet-stream'; const content = fs.createReadStream(filePath); return { filename, contentType, content, size: stats.size, }; } /** * Converts a ReadableStream to a base64 encoded string. * @param stream The ReadableStream containing the binary data. * @returns The stream base64 encoded to a string. */ function streamToBase64(stream) { return new Promise((resolve, reject) => { const chunks = []; stream.on('data', (chunk) => { chunks.push(chunk); }); stream.on('end', () => { const base64 = Buffer.concat(chunks).toString('base64'); resolve(base64); }); stream.on('error', (err) => { reject(err); }); }); } /** * Encodes the content of each attachment stream to base64. * @param attachments The attachments to encode. * @returns The attachments with their content encoded to base64. */ async function encodeAttachmentStreams(attachments) { return await Promise.all(attachments.map(async (attachment) => { const base64EncodedContent = attachment.content instanceof stream_1.Readable ? await streamToBase64(attachment.content) : attachment.content; return { ...attachment, content: base64EncodedContent }; // Replace the stream with its base64 string })); } /** * Applies the casing function and ensures numeric parts are preceded by underscores in snake_case. * @param casingFunction The original casing function. * @param input The string to convert. * @returns The converted string. */ function applyCasing(casingFunction, input) { const transformed = casingFunction(input); if (casingFunction === change_case_1.snakeCase) { return transformed.replace(/(\d+)/g, '_$1'); } else { return transformed.replace(/_+(\d+)/g, (match, p1) => p1); } } /** * A utility function that recursively converts all keys in an object to a given case. * @param obj The object to convert * @param casingFunction The function to use to convert the keys * @param excludeKeys An array of keys to exclude from conversion * @returns The converted object * @ignore Not for public use. */ function convertCase(obj, casingFunction, excludeKeys) { const newObj = {}; for (const key in obj) { if (excludeKeys?.includes(key)) { newObj[key] = obj[key]; } else if (Array.isArray(obj[key])) { newObj[applyCasing(casingFunction, key)] = obj[key].map((item) => { if (typeof item === 'object') { return convertCase(item, casingFunction); } else { return item; } }); } else if (typeof obj[key] === 'object' && obj[key] !== null) { newObj[applyCasing(casingFunction, key)] = convertCase(obj[key], casingFunction); } else { newObj[applyCasing(casingFunction, key)] = obj[key]; } } return newObj; } /** * A utility function that recursively converts all keys in an object to camelCase. * @param obj The object to convert * @param exclude An array of keys to exclude from conversion * @returns The converted object * @ignore Not for public use. */ function objKeysToCamelCase(obj, exclude) { return convertCase(obj, change_case_1.camelCase, exclude); } /** * A utility function that recursively converts all keys in an object to snake_case. * @param obj The object to convert * @param exclude An array of keys to exclude from conversion * @returns The converted object */ function objKeysToSnakeCase(obj, exclude) { return convertCase(obj, change_case_1.snakeCase, exclude); } /** * Safely encodes a path template with replacements. * @param pathTemplate The path template to encode. * @param replacements The replacements to encode. * @returns The encoded path. */ function safePath(pathTemplate, replacements) { return pathTemplate.replace(/\{(\w+)\}/g, (_, key) => { const val = replacements[key]; if (val == null) throw new Error(`Missing replacement for ${key}`); // Decode first (handles already encoded values), then encode // This prevents double encoding while ensuring everything is properly encoded try { const decoded = decodeURIComponent(val); return encodeURIComponent(decoded); } catch (error) { // If decoding fails, the value wasn't properly encoded, so just encode it return encodeURIComponent(val); } }); } // Helper to create PathParams with type safety and runtime interpolation function makePathParams(path, params) { return safePath(path, params); } /** * Calculates the total payload size for a message request, including body and attachments. * This is used to determine if multipart/form-data should be used instead of JSON. * @param requestBody The message request body * @returns The total estimated payload size in bytes */ function calculateTotalPayloadSize(requestBody) { let totalSize = 0; // Calculate size of the message body (JSON payload without attachments) const messagePayloadWithoutAttachments = { ...requestBody, attachments: undefined, }; const messagePayloadString = JSON.stringify(objKeysToSnakeCase(messagePayloadWithoutAttachments)); totalSize += Buffer.byteLength(messagePayloadString, 'utf8'); // Add attachment sizes const attachmentSize = requestBody.attachments?.reduce((total, attachment) => { return total + (attachment.size || 0); }, 0) || 0; totalSize += attachmentSize; return totalSize; }