UNPKG

n8n-nodes-smartsuite

Version:
185 lines 8.17 kB
"use strict"; // src/nodes/SmartSuite/transport/smartSuiteApi.ts Object.defineProperty(exports, "__esModule", { value: true }); exports.apiRequest = apiRequest; exports.paginatedRequest = paginatedRequest; exports.apiRequestAllItems = paginatedRequest; const n8n_workflow_1 = require("n8n-workflow"); /** * Helper function to delay execution for retry logic */ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); /** * Core HTTP request function with retry logic for rate limiting. * - Implements exponential backoff for 429 errors * - Respects Retry-After headers when provided * - customHeaders: extra headers to merge in * - absolute-URL detection */ async function apiRequest(method, endpoint, body = {}, qs = {}, customHeaders = {}, maxRetries = 5) { const creds = await this.getCredentials('smartSuiteApi'); if (!creds.apiKey || !creds.accountId || !creds.baseUrl) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Missing required SmartSuite credentials. Please check API Key, Account ID, and Base URL.'); } const originalEndpoint = endpoint; let urlPath = endpoint; if (urlPath.startsWith('/tables/')) { urlPath = urlPath.replace(/^\/tables\//, '/applications/'); } let finalUrl; if (/^https?:\/\//.test(urlPath)) { finalUrl = urlPath; } else { if (!creds.baseUrl || typeof creds.baseUrl !== 'string' || !creds.baseUrl.startsWith('http')) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `SmartSuite baseUrl is missing or malformed: ${creds.baseUrl}`); } finalUrl = `${creds.baseUrl}${urlPath}`; } const options = { method, url: finalUrl, headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Token ${creds.apiKey}`, 'Account-Id': creds.accountId, ...customHeaders, }, qs, json: true, body: method === 'GET' ? undefined : body, }; // Retry loop with exponential backoff for rate limiting let attempt = 0; let lastError; while (attempt <= maxRetries) { try { const httpRequest = this.helpers.httpRequest; if (typeof httpRequest !== 'function') { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'HTTP Request helper not available'); } return (await httpRequest(options)); } catch (error) { lastError = error; const status = error.response?.status; const apiError = error.response?.data; // Handle rate limiting with retry if (status === 429 && attempt < maxRetries) { attempt++; // Check for Retry-After header const retryAfterHeader = error.response?.headers?.['retry-after']; let delayMs; if (retryAfterHeader) { // Retry-After can be in seconds or HTTP date format const retryAfterSeconds = parseInt(retryAfterHeader, 10); if (!isNaN(retryAfterSeconds)) { delayMs = retryAfterSeconds * 1000; } else { // Try to parse as date const retryDate = new Date(retryAfterHeader); if (!isNaN(retryDate.getTime())) { delayMs = Math.max(0, retryDate.getTime() - Date.now()); } else { // Fallback to exponential backoff delayMs = Math.min(30000, 500 * Math.pow(2, attempt - 1)); } } } else { // Exponential backoff: 0.5s, 1s, 2s, 4s, 8s... capped at 30s delayMs = Math.min(30000, 500 * Math.pow(2, attempt - 1)); } // Log the retry attempt - helpful for debugging console.log(`SmartSuite rate limit hit. Automatically retrying in ${(delayMs / 1000).toFixed(1)}s (attempt ${attempt}/${maxRetries})`); await sleep(delayMs); continue; // Retry the request } // Handle other errors (non-429 or max retries exceeded) if (status === 429) { // Max retries exceeded for rate limiting throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'SmartSuite API rate limit exceeded after multiple retries. ' + 'Please wait a moment before trying again. ' + 'Tip: If this happens frequently, consider spacing out your operations or reducing the number of simultaneous field selections.', { description: 'The SmartSuite API is temporarily limiting requests. This is normal when making many rapid changes.' }); } if (status === 400 && apiError && Object.values(apiError)[0] === 'Not allowed comparison.') { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid comparison for that field type. See https://developers.smartsuite.com/docs/solution-data/records/sort-filter#operators-by-field-type for valid operators.'); } if (apiError && typeof apiError === 'object') { // Format the error message more user-friendly let errorMessage = 'SmartSuite API Error: '; // Try to extract a meaningful error message from the API response if (apiError.message) { errorMessage += apiError.message; } else if (apiError.error) { errorMessage += apiError.error; } else if (apiError.detail) { errorMessage += apiError.detail; } else { // Fallback to stringified error errorMessage += JSON.stringify(apiError); } throw new n8n_workflow_1.NodeOperationError(this.getNode(), errorMessage, { description: `HTTP ${status} error from SmartSuite API` }); } if (status === 404) { let resourceType = 'Record'; if (originalEndpoint.startsWith('/solutions/')) { resourceType = 'Solution'; } else if (originalEndpoint.startsWith('/tables/')) { resourceType = 'Table'; } const parts = finalUrl.split('/'); const id = parts[parts.length - 1] || parts[parts.length - 2]; throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The resource you are requesting could not be found ${id} is not a valid ${resourceType} ID`); } throw error; } } // This should never be reached, but just in case throw lastError; } /** * Fetch all items from a paginated endpoint (limit/offset). */ async function paginatedRequest(method, endpoint, body = {}, qs = {}, pageSize = 100) { const allItems = []; let offset = 0; while (true) { const response = await apiRequest.call(this, method, endpoint, body, { ...qs, limit: pageSize, offset }); let pageArray = []; if (Array.isArray(response)) { pageArray = response; } else if (Array.isArray(response.data)) { pageArray = response.data; } else if (Array.isArray(response.results)) { pageArray = response.results; } else if (response != null && typeof response === 'object') { pageArray = [response]; } allItems.push(...pageArray); if (pageArray.length < pageSize) break; offset += pageSize; } return allItems; } //# sourceMappingURL=smartSuiteApi.js.map