nylas
Version:
A NodeJS wrapper for the Nylas REST API for email, contacts, and calendar.
205 lines (204 loc) • 8.42 kB
JavaScript
;
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;