diff --git a/LICENSE.md b/LICENSE.md index b3aadc2a0f..aac54547eb 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -21,7 +21,7 @@ Software: n8n License: Apache 2.0 -Licensor: Jan Oberhauser +Licensor: n8n GmbH --------------------------------------------------------------------- diff --git a/packages/cli/LICENSE.md b/packages/cli/LICENSE.md index b3aadc2a0f..aac54547eb 100644 --- a/packages/cli/LICENSE.md +++ b/packages/cli/LICENSE.md @@ -21,7 +21,7 @@ Software: n8n License: Apache 2.0 -Licensor: Jan Oberhauser +Licensor: n8n GmbH --------------------------------------------------------------------- diff --git a/packages/cli/package.json b/packages/cli/package.json index 913ea23410..bb18b70179 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.65.0", + "version": "0.66.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -95,10 +95,10 @@ "lodash.get": "^4.4.2", "mongodb": "^3.5.5", "mysql2": "^2.0.1", - "n8n-core": "~0.32.0", - "n8n-editor-ui": "~0.43.0", - "n8n-nodes-base": "~0.60.0", - "n8n-workflow": "~0.29.0", + "n8n-core": "~0.33.0", + "n8n-editor-ui": "~0.44.0", + "n8n-nodes-base": "~0.61.0", + "n8n-workflow": "~0.30.0", "open": "^7.0.0", "pg": "^7.11.0", "request-promise-native": "^1.0.7", diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index b6a07aab44..ad2a3c1de5 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -512,11 +512,8 @@ class App { const sessionId = GenericHelpers.getSessionId(req); - // Check if workflow is saved as webhooks can only be tested with saved workflows. - // If that is the case check if any webhooks calls are present we have to wait for and - // if that is the case wait till we receive it. - if (WorkflowHelpers.isWorkflowIdValid(workflowData.id) === true && (runData === undefined || startNodes === undefined || startNodes.length === 0 || destinationNode === undefined)) { - // Webhooks can only be tested with saved workflows + // If webhooks nodes exist and are active we have to wait for till we receive a call + if (runData === undefined || startNodes === undefined || startNodes.length === 0 || destinationNode === undefined) { const credentials = await WorkflowCredentials(workflowData.nodes); const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials); const nodeTypes = NodeTypes(); diff --git a/packages/cli/src/TestWebhooks.ts b/packages/cli/src/TestWebhooks.ts index 61d7213305..45ae624e2b 100644 --- a/packages/cli/src/TestWebhooks.ts +++ b/packages/cli/src/TestWebhooks.ts @@ -129,6 +129,10 @@ export class TestWebhooks { return false; } + if (workflow.id === undefined) { + throw new Error('Webhooks can only be added for saved workflows as an id is needed!'); + } + // Remove test-webhooks automatically if they do not get called (after 120 seconds) const timeout = setTimeout(() => { this.cancelTestWebhook(workflowData.id.toString()); diff --git a/packages/core/LICENSE.md b/packages/core/LICENSE.md index b3aadc2a0f..aac54547eb 100644 --- a/packages/core/LICENSE.md +++ b/packages/core/LICENSE.md @@ -21,7 +21,7 @@ Software: n8n License: Apache 2.0 -Licensor: Jan Oberhauser +Licensor: n8n GmbH --------------------------------------------------------------------- diff --git a/packages/core/package.json b/packages/core/package.json index 8ee7217eb4..b58064344b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.32.0", + "version": "0.33.0", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -44,7 +44,7 @@ "crypto-js": "3.1.9-1", "lodash.get": "^4.4.2", "mmmagic": "^0.5.2", - "n8n-workflow": "~0.29.0", + "n8n-workflow": "~0.30.0", "p-cancelable": "^2.0.0", "request": "^2.88.2", "request-promise-native": "^1.0.7" diff --git a/packages/editor-ui/LICENSE.md b/packages/editor-ui/LICENSE.md index b3aadc2a0f..aac54547eb 100644 --- a/packages/editor-ui/LICENSE.md +++ b/packages/editor-ui/LICENSE.md @@ -21,7 +21,7 @@ Software: n8n License: Apache 2.0 -Licensor: Jan Oberhauser +Licensor: n8n GmbH --------------------------------------------------------------------- diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index f99eecd94d..f25c05530f 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.43.0", + "version": "0.44.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -64,7 +64,7 @@ "lodash.debounce": "^4.0.8", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", - "n8n-workflow": "~0.29.0", + "n8n-workflow": "~0.30.0", "node-sass": "^4.12.0", "prismjs": "^1.17.1", "quill": "^2.0.0-dev.3", diff --git a/packages/node-dev/LICENSE.md b/packages/node-dev/LICENSE.md index b3aadc2a0f..aac54547eb 100644 --- a/packages/node-dev/LICENSE.md +++ b/packages/node-dev/LICENSE.md @@ -21,7 +21,7 @@ Software: n8n License: Apache 2.0 -Licensor: Jan Oberhauser +Licensor: n8n GmbH --------------------------------------------------------------------- diff --git a/packages/nodes-base/LICENSE.md b/packages/nodes-base/LICENSE.md index b3aadc2a0f..aac54547eb 100644 --- a/packages/nodes-base/LICENSE.md +++ b/packages/nodes-base/LICENSE.md @@ -21,7 +21,7 @@ Software: n8n License: Apache 2.0 -Licensor: Jan Oberhauser +Licensor: n8n GmbH --------------------------------------------------------------------- diff --git a/packages/nodes-base/credentials/Sms77Api.credentials.ts b/packages/nodes-base/credentials/Sms77Api.credentials.ts new file mode 100644 index 0000000000..e560a9f124 --- /dev/null +++ b/packages/nodes-base/credentials/Sms77Api.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class Sms77Api implements ICredentialType { + name = 'sms77Api'; + displayName = 'Sms77 API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Facebook/FacebookGraphApi.node.ts b/packages/nodes-base/nodes/Facebook/FacebookGraphApi.node.ts index 2117defb31..02c9bf40c2 100644 --- a/packages/nodes-base/nodes/Facebook/FacebookGraphApi.node.ts +++ b/packages/nodes-base/nodes/Facebook/FacebookGraphApi.node.ts @@ -381,7 +381,22 @@ export class FacebookGraphApi implements INodeType { throw error; } - returnItems.push(items[itemIndex]); + let errorItem; + if (error.response !== undefined) { + // Since this is a Graph API node and we already know the request was + // not successful, we'll go straight to the error details. + const graphApiErrors = error.response.body?.error ?? {}; + + errorItem = { + statusCode: error.statusCode, + ...graphApiErrors, + headers: error.response.headers, + }; + } else { + // Unknown Graph API response, we'll dump everything in the response item + errorItem = error; + } + returnItems.push({ json: { ...errorItem } }); continue; } @@ -391,7 +406,7 @@ export class FacebookGraphApi implements INodeType { throw new Error('Response body is not valid JSON.'); } - returnItems.push(items[itemIndex]); + returnItems.push({ json: { message: response } }); continue; } diff --git a/packages/nodes-base/nodes/Facebook/facebook.png b/packages/nodes-base/nodes/Facebook/facebook.png index b6cba78f88..e0cc044609 100644 Binary files a/packages/nodes-base/nodes/Facebook/facebook.png and b/packages/nodes-base/nodes/Facebook/facebook.png differ diff --git a/packages/nodes-base/nodes/Github/Github.node.ts b/packages/nodes-base/nodes/Github/Github.node.ts index 59df672290..b61c6d373f 100644 --- a/packages/nodes-base/nodes/Github/Github.node.ts +++ b/packages/nodes-base/nodes/Github/Github.node.ts @@ -212,7 +212,7 @@ export class Github implements INodeType { { name: 'Get Emails', value: 'getEmails', - description: 'Returns the repositories of a user', + description: 'Returns the email addresses of a user', }, { name: 'Get Repositories', diff --git a/packages/nodes-base/nodes/Sms77/GenericFunctions.ts b/packages/nodes-base/nodes/Sms77/GenericFunctions.ts new file mode 100644 index 0000000000..30499b3b29 --- /dev/null +++ b/packages/nodes-base/nodes/Sms77/GenericFunctions.ts @@ -0,0 +1,58 @@ +import { + IExecuteFunctions, + IHookFunctions, +} from 'n8n-core'; + +import { + ICredentialDataDecryptedObject, + IDataObject, +} from 'n8n-workflow'; + +/** + * Make an API request to MSG91 + * + * @param {IHookFunctions | IExecuteFunctions} this + * @param {string} method + * @param {string} endpoint + * @param {object} form + * @param {object | undefined} qs + * @returns {Promise} + */ +export async function sms77ApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, form: IDataObject, qs?: IDataObject): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('sms77Api'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + if ('GET' === method) { + qs = setPayload(credentials, qs); + } else { + form = setPayload(credentials, form); + } + const response = await this.helpers.request({ + form, + json: true, + method, + qs, + uri: `https://gateway.sms77.io/api/${endpoint}`, + }); + + if ('100' !== response.success) { + throw new Error('Invalid sms77 credentials or API error!'); + } + + return response; +} + + +function setPayload(credentials: ICredentialDataDecryptedObject, o?: IDataObject) { + if (!o) { + o = {}; + } + + o.p = credentials!.apiKey as string; + o.json = 1; + o.sendwith = 'n8n'; + + return o; +} diff --git a/packages/nodes-base/nodes/Sms77/Sms77.node.ts b/packages/nodes-base/nodes/Sms77/Sms77.node.ts new file mode 100644 index 0000000000..ac8a10f4b6 --- /dev/null +++ b/packages/nodes-base/nodes/Sms77/Sms77.node.ts @@ -0,0 +1,144 @@ +import {IExecuteFunctions,} from 'n8n-core'; +import {IDataObject, INodeExecutionData, INodeType, INodeTypeDescription,} from 'n8n-workflow'; +import {sms77ApiRequest} from './GenericFunctions'; + +export class Sms77 implements INodeType { + description: INodeTypeDescription = { + displayName: 'Sms77', + name: 'sms77', + icon: 'file:sms77.png', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Send SMS', + defaults: { + name: 'Sms77', + color: '#18D46A', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'sms77Api', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'SMS', + value: 'sms', + }, + ], + default: 'sms', + description: 'The resource to operate on.', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'sms', + ], + }, + }, + options: [ + { + name: 'Send', + value: 'send', + description: 'Send SMS', + }, + ], + default: 'send', + description: 'The operation to perform.', + }, + { + displayName: 'From', + name: 'from', + type: 'string', + default: '', + placeholder: '+4901234567890', + required: false, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'sms', + ], + }, + }, + description: 'The number from which to send the message.', + }, + { + displayName: 'To', + name: 'to', + type: 'string', + default: '', + placeholder: '+49876543210', + required: true, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'sms', + ], + }, + }, + description: 'The number, with coutry code, to which to send the message.', + }, + { + displayName: 'Message', + name: 'message', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'sms', + ], + }, + }, + description: 'The message to send', + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const returnData: IDataObject[] = []; + + for (let i = 0; i < this.getInputData().length; i++) { + const resource = this.getNodeParameter('resource', i); + if ('sms' !== resource) { + throw new Error(`The resource "${resource}" is not known!`); + } + + const operation = this.getNodeParameter('operation', i); + if ('send' !== operation) { + throw new Error(`The operation "${operation}" is not known!`); + } + + const responseData = await sms77ApiRequest.call(this, 'POST', 'sms', {}, { + from: this.getNodeParameter('from', i), + to: this.getNodeParameter('to', i), + text: this.getNodeParameter('message', i), + }); + + returnData.push(responseData); + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Sms77/sms77.png b/packages/nodes-base/nodes/Sms77/sms77.png new file mode 100644 index 0000000000..500ba005ae Binary files /dev/null and b/packages/nodes-base/nodes/Sms77/sms77.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index dcc39d4ba0..2bb3628c13 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.60.0", + "version": "0.61.0", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -93,6 +93,7 @@ "dist/credentials/RundeckApi.credentials.js", "dist/credentials/ShopifyApi.credentials.js", "dist/credentials/SlackApi.credentials.js", + "dist/credentials/Sms77Api.credentials.js", "dist/credentials/Smtp.credentials.js", "dist/credentials/StripeApi.credentials.js", "dist/credentials/SalesmateApi.credentials.js", @@ -221,6 +222,7 @@ "dist/nodes/Shopify/Shopify.node.js", "dist/nodes/Shopify/ShopifyTrigger.node.js", "dist/nodes/Slack/Slack.node.js", + "dist/nodes/Sms77/Sms77.node.js", "dist/nodes/SplitInBatches.node.js", "dist/nodes/SpreadsheetFile.node.js", "dist/nodes/SseTrigger.node.js", @@ -274,7 +276,7 @@ "@types/xml2js": "^0.4.3", "gulp": "^4.0.0", "jest": "^24.9.0", - "n8n-workflow": "~0.29.0", + "n8n-workflow": "~0.30.0", "ts-jest": "^24.0.2", "tslint": "^5.17.0", "typescript": "~3.7.4" @@ -298,7 +300,7 @@ "moment-timezone": "^0.5.28", "mongodb": "^3.5.5", "mysql2": "^2.0.1", - "n8n-core": "~0.32.0", + "n8n-core": "~0.33.0", "nodemailer": "^5.1.1", "pdf-parse": "^1.1.1", "pg-promise": "^9.0.3", diff --git a/packages/workflow/LICENSE.md b/packages/workflow/LICENSE.md index b3aadc2a0f..aac54547eb 100644 --- a/packages/workflow/LICENSE.md +++ b/packages/workflow/LICENSE.md @@ -21,7 +21,7 @@ Software: n8n License: Apache 2.0 -Licensor: Jan Oberhauser +Licensor: n8n GmbH --------------------------------------------------------------------- diff --git a/packages/workflow/package.json b/packages/workflow/package.json index 7e90bfb26f..72af56092d 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "0.29.0", + "version": "0.30.0", "description": "Workflow base code of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index 76d8f8b55f..08e9660b99 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -728,12 +728,6 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData: return []; } - if (workflow.id === undefined) { - // Workflow has no id which means it is not saved and so webhooks - // will not be enabled - return []; - } - const nodeType = workflow.nodeTypes.getByName(node.type) as INodeType; if (nodeType.description.webhooks === undefined) { @@ -741,12 +735,14 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData: return []; } + const workflowId = workflow.id || '__UNSAVED__'; + const returnData: IWebhookData[] = []; for (const webhookDescription of nodeType.description.webhooks) { let nodeWebhookPath = workflow.getSimpleParameterValue(node, webhookDescription['path'], 'GET'); if (nodeWebhookPath === undefined) { // TODO: Use a proper logger - console.error(`No webhook path could be found for node "${node.name}" in workflow "${workflow.id}".`); + console.error(`No webhook path could be found for node "${node.name}" in workflow "${workflowId}".`); continue; } @@ -756,13 +752,13 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData: nodeWebhookPath = nodeWebhookPath.slice(1); } - const path = getNodeWebhookPath(workflow.id, node, nodeWebhookPath); + const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath); const httpMethod = workflow.getSimpleParameterValue(node, webhookDescription['httpMethod'], 'GET'); if (httpMethod === undefined) { // TODO: Use a proper logger - console.error(`The webhook "${path}" for node "${node.name}" in workflow "${workflow.id}" could not be added because the httpMethod is not defined.`); + console.error(`The webhook "${path}" for node "${node.name}" in workflow "${workflowId}" could not be added because the httpMethod is not defined.`); continue; } @@ -771,7 +767,7 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData: node: node.name, path, webhookDescription, - workflowId: workflow.id, + workflowId, workflowExecuteAdditionalData: additionalData, }); }