UNPKG

nylas

Version:

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

205 lines (204 loc) 8.42 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.REQUEST_ID_HEADER = exports.FLOW_ID_HEADER = void 0; const node_fetch_1 = require("node-fetch"); const error_js_1 = require("./models/error.js"); const utils_js_1 = require("./utils.js"); const version_js_1 = require("./version.js"); const change_case_1 = require("change-case"); /** * The header key for the debugging flow ID */ exports.FLOW_ID_HEADER = 'x-fastly-id'; /** * The header key for the request ID */ exports.REQUEST_ID_HEADER = 'x-request-id'; /** * The API client for communicating with the Nylas API * @ignore Not for public use */ class APIClient { constructor({ apiKey, apiUri, timeout, headers }) { this.apiKey = apiKey; this.serverUrl = apiUri; this.timeout = timeout * 1000; // fetch timeout uses milliseconds this.headers = headers; } setRequestUrl({ overrides, path, queryParams, }) { const url = new URL(`${overrides?.apiUri || this.serverUrl}${path}`); return this.setQueryStrings(url, queryParams); } setQueryStrings(url, queryParams) { if (queryParams) { for (const [key, value] of Object.entries(queryParams)) { const snakeCaseKey = (0, change_case_1.snakeCase)(key); if (key == 'metadataPair') { // The API understands a metadata_pair filter in the form of: // <key>:<value> const metadataPair = []; for (const item in value) { metadataPair.push(`${item}:${value[item]}`); } url.searchParams.set('metadata_pair', metadataPair.join(',')); } else if (Array.isArray(value)) { for (const item of value) { url.searchParams.append(snakeCaseKey, item); } } else if (typeof value === 'object') { for (const item in value) { url.searchParams.append(snakeCaseKey, `${item}:${value[item]}`); } } else { url.searchParams.set(snakeCaseKey, value); } } } return url; } setRequestHeaders({ headers, overrides, }) { const mergedHeaders = { ...headers, ...this.headers, ...overrides?.headers, }; return { Accept: 'application/json', 'User-Agent': `Nylas Node SDK v${version_js_1.SDK_VERSION}`, Authorization: `Bearer ${overrides?.apiKey || this.apiKey}`, ...mergedHeaders, }; } async sendRequest(options) { const req = this.newRequest(options); const controller = new AbortController(); // Handle timeout let timeoutDuration; if (options.overrides?.timeout) { // Determine if the override timeout is likely in milliseconds (≥ 1000) if (options.overrides.timeout >= 1000) { timeoutDuration = options.overrides.timeout; // Keep as milliseconds for backward compatibility } else { // Treat as seconds and convert to milliseconds timeoutDuration = options.overrides.timeout * 1000; } } else { timeoutDuration = this.timeout; // Already in milliseconds from constructor } const timeout = setTimeout(() => { controller.abort(); }, timeoutDuration); try { const response = await (0, node_fetch_1.default)(req, { signal: controller.signal, }); clearTimeout(timeout); if (typeof response === 'undefined') { throw new Error('Failed to fetch response'); } const headers = response?.headers?.entries ? Object.fromEntries(response.headers.entries()) : {}; const flowId = headers[exports.FLOW_ID_HEADER]; const requestId = headers[exports.REQUEST_ID_HEADER]; if (response.status > 299) { const text = await response.text(); let error; try { const parsedError = JSON.parse(text); const camelCaseError = (0, utils_js_1.objKeysToCamelCase)(parsedError); // Check if the request is an authentication request const isAuthRequest = options.path.includes('connect/token') || options.path.includes('connect/revoke'); if (isAuthRequest) { error = new error_js_1.NylasOAuthError(camelCaseError, response.status, requestId, flowId, headers); } else { error = new error_js_1.NylasApiError(camelCaseError, response.status, requestId, flowId, headers); } } catch (e) { throw new Error(`Received an error but could not parse response from the server${flowId ? ` with flow ID ${flowId}` : ''}: ${text}`); } throw error; } return response; } catch (error) { if (error instanceof Error && error.name === 'AbortError') { // Calculate the timeout in seconds for the error message // If we determined it was milliseconds (≥ 1000), convert to seconds for the error const timeoutInSeconds = options.overrides?.timeout ? options.overrides.timeout >= 1000 ? options.overrides.timeout / 1000 // Convert ms to s for error message : options.overrides.timeout // Already in seconds : this.timeout / 1000; // Convert ms to s throw new error_js_1.NylasSdkTimeoutError(req.url, timeoutInSeconds); } clearTimeout(timeout); throw error; } } requestOptions(optionParams) { const requestOptions = {}; requestOptions.url = this.setRequestUrl(optionParams); requestOptions.headers = this.setRequestHeaders(optionParams); requestOptions.method = optionParams.method; if (optionParams.body) { requestOptions.body = JSON.stringify((0, utils_js_1.objKeysToSnakeCase)(optionParams.body, ['metadata']) // metadata should remain as is ); requestOptions.headers['Content-Type'] = 'application/json'; } if (optionParams.form) { requestOptions.body = optionParams.form; requestOptions.headers = { ...requestOptions.headers, ...optionParams.form.getHeaders(), }; } return requestOptions; } newRequest(options) { const newOptions = this.requestOptions(options); return new node_fetch_1.Request(newOptions.url, { method: newOptions.method, headers: newOptions.headers, body: newOptions.body, }); } async requestWithResponse(response) { const headers = response?.headers?.entries ? Object.fromEntries(response.headers.entries()) : {}; const flowId = headers[exports.FLOW_ID_HEADER]; const text = await response.text(); try { const responseJSON = JSON.parse(text); // Inject the flow ID and headers into the response responseJSON.flowId = flowId; responseJSON.headers = headers; return (0, utils_js_1.objKeysToCamelCase)(responseJSON, ['metadata']); } catch (e) { throw new Error(`Could not parse response from the server: ${text}`); } } async request(options) { const response = await this.sendRequest(options); return this.requestWithResponse(response); } async requestRaw(options) { const response = await this.sendRequest(options); return response.buffer(); } async requestStream(options) { const response = await this.sendRequest(options); return response.body; } } exports.default = APIClient;