UNPKG

n8n-nodes-base

Version:

Base nodes of n8n

204 lines (201 loc) 9.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FunctionItem = void 0; const vm2_1 = require("vm2"); const n8n_workflow_1 = require("n8n-workflow"); const JavaScriptSandbox_1 = require("../Code/JavaScriptSandbox"); class FunctionItem { description = { displayName: 'Function Item', name: 'functionItem', hidden: true, icon: 'fa:code', group: ['transform'], version: 1, description: 'Run custom function code which gets executed once per item', defaults: { name: 'Function Item', color: '#ddbb33', }, inputs: [n8n_workflow_1.NodeConnectionTypes.Main], outputs: [n8n_workflow_1.NodeConnectionTypes.Main], properties: [ { displayName: 'A newer version of this node type is available, called the ‘Code’ node', name: 'notice', type: 'notice', default: '', }, { displayName: 'JavaScript Code', name: 'functionCode', typeOptions: { alwaysOpenEditWindow: true, codeAutocomplete: 'functionItem', editor: 'jsEditor', rows: 10, }, type: 'string', default: `// Code here will run once per input item. // More info and help: https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.functionitem/ // Tip: You can use luxon for dates and $jmespath for querying JSON structures // Add a new field called 'myNewField' to the JSON of the item item.myNewField = 1; // You can write logs to the browser console console.log('Done!'); return item;`, description: 'The JavaScript code to execute for each item', noDataExpression: true, }, ], }; async execute() { const items = this.getInputData(); const returnData = []; const length = items.length; let item; const cleanupData = (inputData) => { Object.keys(inputData).map((key) => { if (inputData[key] !== null && typeof inputData[key] === 'object') { if (inputData[key].constructor.name === 'Object') { // Is regular node.js object so check its data inputData[key] = cleanupData(inputData[key]); } else { // Is some special object like a Date so stringify inputData[key] = (0, n8n_workflow_1.deepCopy)(inputData[key]); } } }); return inputData; }; for (let itemIndex = 0; itemIndex < length; itemIndex++) { const mode = this.getMode(); try { item = items[itemIndex]; item.index = itemIndex; // Copy the items as they may get changed in the functions item = (0, n8n_workflow_1.deepCopy)(item); // Define the global objects for the custom function const sandbox = { /** @deprecated for removal - replaced by getBinaryDataAsync() */ getBinaryData: () => { if (mode === 'manual') { this.sendMessageToUI('getBinaryData(...) is deprecated and will be removed in a future version. Please consider switching to getBinaryDataAsync(...) instead.'); } return item.binary; }, /** @deprecated for removal - replaced by setBinaryDataAsync() */ setBinaryData: async (data) => { if (mode === 'manual') { this.sendMessageToUI('setBinaryData(...) is deprecated and will be removed in a future version. Please consider switching to setBinaryDataAsync(...) instead.'); } item.binary = data; }, getNodeParameter: this.getNodeParameter.bind(this), getWorkflowStaticData: this.getWorkflowStaticData.bind(this), helpers: this.helpers, item: item.json, getBinaryDataAsync: async () => { // Fetch Binary Data, if available. Cannot check item with `if (item?.index)`, as index may be 0. if (item?.binary && item?.index !== undefined && item?.index !== null) { for (const binaryPropertyName of Object.keys(item.binary)) { item.binary[binaryPropertyName].data = (await this.helpers.getBinaryDataBuffer(item.index, binaryPropertyName))?.toString('base64'); } } // Return Data return item.binary; }, setBinaryDataAsync: async (data) => { // Ensure data is present if (!data) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No data was provided to setBinaryDataAsync (data: IBinaryKeyData).'); } // Set Binary Data for (const binaryPropertyName of Object.keys(data)) { const binaryItem = data[binaryPropertyName]; data[binaryPropertyName] = await this.helpers.setBinaryDataBuffer(binaryItem, Buffer.from(binaryItem.data, 'base64')); } // Set Item Reference item.binary = data; }, }; // Make it possible to access data via $node, $parameter, ... const dataProxy = this.getWorkflowDataProxy(itemIndex); Object.assign(sandbox, dataProxy); const options = { console: mode === 'manual' ? 'redirect' : 'inherit', sandbox, require: JavaScriptSandbox_1.vmResolver, }; const vm = new vm2_1.NodeVM(options); if (mode === 'manual') { vm.on('console.log', this.sendMessageToUI.bind(this)); } // Get the code to execute const functionCode = this.getNodeParameter('functionCode', itemIndex); let jsonData; try { // Execute the function code jsonData = await vm.run(`module.exports = async function() {${functionCode}\n}()`, __dirname); } catch (error) { if (this.continueOnFail()) { returnData.push({ json: { error: error.message } }); continue; } else { // Try to find the line number which contains the error and attach to error message const stackLines = error.stack.split('\n'); if (stackLines.length > 0) { stackLines.shift(); const lineParts = stackLines .find((line) => line.includes('FunctionItem')) .split(':'); if (lineParts.length > 2) { const lineNumber = lineParts.splice(-2, 1); if (!isNaN(lineNumber)) { error.message = `${error.message} [Line ${lineNumber} | Item Index: ${itemIndex}]`; throw error; } } } error.message = `${error.message} [Item Index: ${itemIndex}]`; throw error; } } // Do very basic validation of the data if (jsonData === undefined) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No data got returned. Always an object has to be returned!'); } const returnItem = { json: cleanupData(jsonData), pairedItem: { item: itemIndex, }, }; if (item.binary) { returnItem.binary = item.binary; } returnData.push(returnItem); } catch (error) { if (this.continueOnFail()) { returnData.push({ json: { error: error.message, }, pairedItem: { item: itemIndex, }, }); continue; } throw error; } } return [returnData]; } } exports.FunctionItem = FunctionItem; //# sourceMappingURL=FunctionItem.node.js.map