diff --git a/credentials/ExampleCredentials.credentials.ts b/credentials/ExampleCredentials.credentials.ts index 038f97e..fe42466 100644 --- a/credentials/ExampleCredentials.credentials.ts +++ b/credentials/ExampleCredentials.credentials.ts @@ -1,23 +1,56 @@ -import { ICredentialType, INodeProperties } from 'n8n-workflow'; +import { + IAuthenticateGeneric, + ICredentialTestRequest, + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; export class ExampleCredentials implements ICredentialType { name = 'exampleCredentials'; displayName = 'Example Credentials'; properties: INodeProperties[] = [ - // Credential data to request from the user, saved in encrypted format. - // Credential properties are defined exactly like node properties. + // The credentials to get from user and save encrypted. + // Properties can be defined exactly in the same way + // as node properties. { - displayName: 'Base URL', - name: 'url', + displayName: 'User Name', + name: 'username', type: 'string', default: '', - placeholder: 'https://example.com', }, { - displayName: 'Access Token', - name: 'accessToken', + displayName: 'Password', + name: 'password', type: 'string', + typeOptions: { + password: true, + }, default: '', }, ]; + + // This credential is currently not used by any node directly + // but the HTTP Request node can use it to make requests. + // The credential is also testable due to the `test` property below + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + auth: { + username: '={{ $credentials.username }}', + password: '={{ $credentials.password }}', + }, + qs: { + // Send this as part of the query string + n8n: 'rocks', + }, + }, + }; + + // The block below tells how this credential can be tested + test: ICredentialTestRequest = { + request: { + baseURL: 'https://example.com/', + url: '', + }, + }; } diff --git a/credentials/HttpBinApi.credentials.ts b/credentials/HttpBinApi.credentials.ts index 8ccd44f..805b83d 100644 --- a/credentials/HttpBinApi.credentials.ts +++ b/credentials/HttpBinApi.credentials.ts @@ -1,8 +1,7 @@ import { - ICredentialDataDecryptedObject, + IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, - IHttpRequestOptions, INodeProperties, } from 'n8n-workflow'; @@ -17,12 +16,6 @@ export class HttpBinApi implements ICredentialType { type: 'string', default: '', }, - // { - // displayName: "API Key", - // name: "apiKey", - // type: "string", - // default: "", - // }, { displayName: 'Domain', name: 'domain', @@ -31,26 +24,20 @@ export class HttpBinApi implements ICredentialType { }, ]; - // authenticate = { - // type: "headerAuth", - // properties: { - // name: "api-key", - // value: "={{$credentials.apiKey}}", - // }, - // } as IAuthenticateHeaderAuth; - - authenticate = async ( - credentials: ICredentialDataDecryptedObject, - requestOptions: IHttpRequestOptions, - ): Promise => { - const headers = requestOptions.headers || {}; - const authentication = { Authorization: `Bearer ${credentials.token}` }; - Object.assign(requestOptions, { - headers: { ...authentication, ...headers }, - }); - return requestOptions; - }; + // This allows the credential to be used by other parts of n8n + // stating how this credential is injected as part of the request + // An example is the Http Request node that can make generic calls + // reusing this credential + authenticate = { + type: 'generic', + properties: { + headers: { + Authorization: '={{"Bearer " + $credentials.token}}', + }, + }, + } as IAuthenticateGeneric; + // The block below tells how this credential can be tested test: ICredentialTestRequest = { request: { baseURL: '={{$credentials?.domain}}', diff --git a/nodes/ExampleNode/ExampleNode.node.ts b/nodes/ExampleNode/ExampleNode.node.ts index 770d495..371257b 100644 --- a/nodes/ExampleNode/ExampleNode.node.ts +++ b/nodes/ExampleNode/ExampleNode.node.ts @@ -1,5 +1,10 @@ import { IExecuteFunctions } from 'n8n-core'; -import { INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; +import { + INodeExecutionData, + INodeType, + INodeTypeDescription, + NodeOperationError, +} from 'n8n-workflow'; export class ExampleNode implements INodeType { description: INodeTypeDescription = { @@ -14,29 +19,56 @@ export class ExampleNode implements INodeType { inputs: ['main'], outputs: ['main'], properties: [ - // Node properties that the user can see and change on the node. + // Node properties which the user gets displayed and + // can change on the node. { displayName: 'My String', name: 'myString', type: 'string', default: '', placeholder: 'Placeholder value', - description: 'This is a description', + description: 'The description text', }, ], }; + // The function below is responsible for actually doing whatever this node + // is supposed to do. In this case, we're just appending the `myString` property + // with whatever the user has entered. + // You can make async calls and use `await`. async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); - // The node iterates over all input items and adds the key "myString" with the - // value the parameter "myString" resolves to. (This could be a different value - // for each item in case it contains an expression.) - for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { - const myString = this.getNodeParameter('myString', itemIndex, '') as string; - const item = items[itemIndex]; + let item: INodeExecutionData; + let myString: string; - item.json['myString'] = myString; + // Iterates over all input items and add the key "myString" with the + // value the parameter "myString" resolves to. + // (This could be a different value for each item in case it contains an expression) + for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { + try { + myString = this.getNodeParameter('myString', itemIndex, '') as string; + item = items[itemIndex]; + + item.json['myString'] = myString; + } catch (error) { + // This node should never fail but we want to showcase how + // to handle errors. + if (this.continueOnFail()) { + items.push({ json: this.getInputData(itemIndex)[0].json, error }); + } else { + // Adding `itemIndex` allows other workflows to handle this error + if (error.context) { + // If the error thrown already contains the context property, + // only append the itemIndex + error.context.itemIndex = itemIndex; + throw error; + } + throw new NodeOperationError(this.getNode(), error, { + itemIndex, + }); + } + } } return this.prepareOutputData(items); diff --git a/package.json b/package.json index 45ee704..6d30295 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,8 @@ "@typescript-eslint/parser": "^5.29.0", "eslint-plugin-n8n-nodes-base": "^1.2.0", "gulp": "^4.0.2", - "n8n-core": "~0.123.0", - "n8n-workflow": "~0.105.0", + "n8n-core": "^0.124.0", + "n8n-workflow": "^0.106.0", "prettier": "^2.7.1", "tslint": "^6.1.2", "typescript": "~4.6.0"