mirror of
				https://github.com/n8n-io/n8n-nodes-starter.git
				synced 2025-10-30 14:52:27 -05:00 
			
		
		
		
	S4DSAuth node added. Some other nodes deleted
This commit is contained in:
		
					parent
					
						
							
								21f7bc4eae
							
						
					
				
			
			
				commit
				
					
						392231e695
					
				
			
		
					 16 changed files with 4910 additions and 222 deletions
				
			
		
							
								
								
									
										179
									
								
								README_S4DS.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								README_S4DS.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,179 @@ | |||
| # S4DS Nodes para n8n | ||||
| 
 | ||||
| Este paquete de nodos personalizados para n8n proporciona integración completa con las APIs de S4DS, incluyendo autenticación y operaciones de ejemplo. | ||||
| 
 | ||||
| ## Nodos Incluidos | ||||
| 
 | ||||
| ### 1. S4DS Authentication | ||||
| Nodo principal para autenticación con las APIs de S4DS. | ||||
| 
 | ||||
| **Características:** | ||||
| - Generación de tokens Bearer | ||||
| - Validación de tokens existentes | ||||
| - Soporte para múltiples ambientes (Test, UAT, Production) | ||||
| - Soporte para múltiples clientes (Demo, Cliente 1, Cliente 2) | ||||
| - Almacenamiento automático de tokens en contexto del workflow | ||||
| - Gestión de expiración de tokens | ||||
| 
 | ||||
| **Operaciones:** | ||||
| - **Generate Token**: Genera un nuevo token de autenticación | ||||
| - **Validate Token**: Valida un token existente | ||||
| 
 | ||||
| ### 2. S4DS Example | ||||
| Nodo de ejemplo que demuestra cómo usar el token de autenticación. | ||||
| 
 | ||||
| **Características:** | ||||
| - Consume tokens del contexto del workflow | ||||
| - Soporte para tokens personalizados | ||||
| - Verificación automática de expiración | ||||
| - Operaciones de ejemplo con la API | ||||
| 
 | ||||
| **Operaciones:** | ||||
| - **Get Product Count**: Obtiene la cantidad total de productos | ||||
| 
 | ||||
| ## Instalación | ||||
| 
 | ||||
| 1. Clone este repositorio | ||||
| 2. Instale las dependencias: | ||||
|    ```bash | ||||
|    npm install | ||||
|    ``` | ||||
| 3. Compile el proyecto: | ||||
|    ```bash | ||||
|    npm run build | ||||
|    ``` | ||||
| 4. Instale el paquete en n8n: | ||||
|    ```bash | ||||
|    npm install -g . | ||||
|    ``` | ||||
| 
 | ||||
| ## Configuración | ||||
| 
 | ||||
| ### Credenciales S4DS API | ||||
| 
 | ||||
| Configure las credenciales de S4DS API en n8n: | ||||
| 
 | ||||
| 1. Vaya a **Settings** → **Credentials** | ||||
| 2. Haga clic en **Add Credential** | ||||
| 3. Seleccione **S4DS API** | ||||
| 4. Configure: | ||||
|    - **Base URL**: Seleccione el ambiente y cliente | ||||
|    - **Custom Base URL**: Para URLs personalizadas | ||||
|    - **Timeout**: Tiempo de espera para peticiones | ||||
| 
 | ||||
| ### URLs Disponibles | ||||
| 
 | ||||
| #### Demo | ||||
| - **Test**: `https://demotest.s4ds.com/demoapi-test` | ||||
| - **UAT**: `https://demouat.s4ds.com/demoapi-uat` | ||||
| - **Production**: `https://demoprod.s4ds.com/demoapi-prod` | ||||
| 
 | ||||
| #### Cliente 1 | ||||
| - **Test**: `https://cliente1test.s4ds.com/cliente1api-test` | ||||
| - **UAT**: `https://cliente1uat.s4ds.com/cliente1api-uat` | ||||
| - **Production**: `https://cliente1prod.s4ds.com/cliente1api-prod` | ||||
| 
 | ||||
| #### Cliente 2 | ||||
| - **Test**: `https://cliente2test.s4ds.com/cliente2api-test` | ||||
| - **UAT**: `https://cliente2uat.s4ds.com/cliente2api-uat` | ||||
| - **Production**: `https://cliente2prod.s4ds.com/cliente2api-prod` | ||||
| 
 | ||||
| ## Uso | ||||
| 
 | ||||
| ### Workflow Básico | ||||
| 
 | ||||
| 1. **Configurar Credenciales**: | ||||
|    - Configure las credenciales de S4DS API con usuario, contraseña y ambiente | ||||
|    - Agregue el nodo **S4DS Authentication** | ||||
|    - Configure la operación "Generate Token" | ||||
| 
 | ||||
| 2. **Usar el Token**: | ||||
|    - Agregue el nodo **S4DS Example** o **HTTP Request** | ||||
|    - Configure para usar el token del contexto | ||||
|    - Ejecute las operaciones deseadas | ||||
| 
 | ||||
| ### Ejemplo de Workflow | ||||
| 
 | ||||
| ``` | ||||
| [S4DS Auth: Generate Token] → [S4DS Example: Get User Profile] → [Process Data] | ||||
| ``` | ||||
| 
 | ||||
| ### Acceso al Token | ||||
| 
 | ||||
| El token se almacena automáticamente en el contexto del workflow y puede ser accedido usando: | ||||
| 
 | ||||
| ```javascript | ||||
| {{ $context.s4ds_token }} | ||||
| ``` | ||||
| 
 | ||||
| O con clave personalizada: | ||||
| 
 | ||||
| ```javascript | ||||
| {{ $context.your_custom_key }} | ||||
| ``` | ||||
| 
 | ||||
| ## Estructura del Token | ||||
| 
 | ||||
| El token se almacena con la siguiente estructura: | ||||
| 
 | ||||
| ```json | ||||
| { | ||||
|   "token": "8a6c71b3-fa62-434d-8b38-907de24c3176", | ||||
|   "token_type": "Bearer", | ||||
|   "expires_in": 3600, | ||||
|   "expires_at": "2024-01-01T12:00:00.000Z" | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ## Manejo de Errores | ||||
| 
 | ||||
| Los nodos manejan automáticamente: | ||||
| - Errores de autenticación | ||||
| - Tokens expirados | ||||
| - Errores de red | ||||
| - Timeouts | ||||
| - Respuestas malformadas | ||||
| 
 | ||||
| ## Desarrollo | ||||
| 
 | ||||
| ### Estructura del Proyecto | ||||
| 
 | ||||
| ``` | ||||
| n8n-nodes-starter-s4ds/ | ||||
| ├── nodes/ | ||||
| │   ├── S4DSAuth/           # Nodo de autenticación | ||||
| │   ├── S4DSExample/        # Nodo de ejemplo | ||||
| │   └── ... | ||||
| ├── credentials/ | ||||
| │   └── S4DSApi.credentials.ts | ||||
| ├── package.json | ||||
| └── index.js | ||||
| ``` | ||||
| 
 | ||||
| ### Comandos de Desarrollo | ||||
| 
 | ||||
| ```bash | ||||
| # Compilar | ||||
| npm run build | ||||
| 
 | ||||
| # Desarrollo con watch | ||||
| npm run dev | ||||
| 
 | ||||
| # Linting | ||||
| npm run lint | ||||
| 
 | ||||
| # Formatear código | ||||
| npm run format | ||||
| ``` | ||||
| 
 | ||||
| ## Contribución | ||||
| 
 | ||||
| 1. Fork el repositorio | ||||
| 2. Cree una rama para su feature | ||||
| 3. Haga commit de sus cambios | ||||
| 4. Push a la rama | ||||
| 5. Abra un Pull Request | ||||
| 
 | ||||
| ## Licencia | ||||
| 
 | ||||
| MIT License - vea el archivo LICENSE para más detalles.  | ||||
|  | @ -1,59 +0,0 @@ | |||
| import { | ||||
| 	IAuthenticateGeneric, | ||||
| 	ICredentialTestRequest, | ||||
| 	ICredentialType, | ||||
| 	INodeProperties, | ||||
| } from 'n8n-workflow'; | ||||
| 
 | ||||
| export class ExampleCredentialsApi implements ICredentialType { | ||||
| 	name = 'exampleCredentialsApi'; | ||||
| 	displayName = 'Example Credentials API'; | ||||
| 
 | ||||
| 	documentationUrl = 'https://your-docs-url'; | ||||
| 
 | ||||
| 	properties: INodeProperties[] = [ | ||||
| 		// The credentials to get from user and save encrypted.
 | ||||
| 		// Properties can be defined exactly in the same way
 | ||||
| 		// as node properties.
 | ||||
| 		{ | ||||
| 			displayName: 'User Name', | ||||
| 			name: 'username', | ||||
| 			type: 'string', | ||||
| 			default: '', | ||||
| 		}, | ||||
| 		{ | ||||
| 			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: '', | ||||
| 		}, | ||||
| 	}; | ||||
| } | ||||
							
								
								
									
										100
									
								
								credentials/S4DSApi.credentials.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								credentials/S4DSApi.credentials.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,100 @@ | |||
| import { | ||||
| 	ICredentialType, | ||||
| 	INodeProperties, | ||||
| } from 'n8n-workflow'; | ||||
| 
 | ||||
| export class S4DSApi implements ICredentialType { | ||||
| 	name = 's4dsApi'; | ||||
| 	displayName = 'S4DS API'; | ||||
| 	documentationUrl = 'https://docs.n8n.io/integrations/builtin/credentials/s4ds/'; | ||||
| 	properties: INodeProperties[] = [ | ||||
| 		{ | ||||
| 			displayName: 'Base URL', | ||||
| 			name: 'baseUrl', | ||||
| 			type: 'options', | ||||
| 			options: [ | ||||
| 				{ | ||||
| 					name: 'Demo Test', | ||||
| 					value: 'https://demotest.s4ds.com/demoapi-test', | ||||
| 				}, | ||||
| 				{ | ||||
| 					name: 'Demo UAT', | ||||
| 					value: 'https://demouat.s4ds.com/demoapi-uat', | ||||
| 				}, | ||||
| 				{ | ||||
| 					name: 'Demo Production', | ||||
| 					value: 'https://demoprod.s4ds.com/demoapi-prod', | ||||
| 				}, | ||||
| 				{ | ||||
| 					name: 'Cliente 1 Test', | ||||
| 					value: 'https://cliente1test.s4ds.com/cliente1api-test', | ||||
| 				}, | ||||
| 				{ | ||||
| 					name: 'Cliente 1 UAT', | ||||
| 					value: 'https://cliente1uat.s4ds.com/cliente1api-uat', | ||||
| 				}, | ||||
| 				{ | ||||
| 					name: 'Cliente 1 Production', | ||||
| 					value: 'https://cliente1prod.s4ds.com/cliente1api-prod', | ||||
| 				}, | ||||
| 				{ | ||||
| 					name: 'Cliente 2 Test', | ||||
| 					value: 'https://cliente2test.s4ds.com/cliente2api-test', | ||||
| 				}, | ||||
| 				{ | ||||
| 					name: 'Cliente 2 UAT', | ||||
| 					value: 'https://cliente2uat.s4ds.com/cliente2api-uat', | ||||
| 				}, | ||||
| 				{ | ||||
| 					name: 'Cliente 2 Production', | ||||
| 					value: 'https://cliente2prod.s4ds.com/cliente2api-prod', | ||||
| 				}, | ||||
| 				{ | ||||
| 					name: 'Custom URL', | ||||
| 					value: 'custom', | ||||
| 				}, | ||||
| 			], | ||||
| 			default: 'https://demotest.s4ds.com/demoapi-test', | ||||
| 			description: 'Select the S4DS environment and client', | ||||
| 		}, | ||||
| 		{ | ||||
| 			displayName: 'Custom Base URL', | ||||
| 			name: 'customBaseUrl', | ||||
| 			type: 'string', | ||||
| 			default: '', | ||||
| 			placeholder: 'https://your-custom-domain.s4ds.com/yourapi', | ||||
| 			description: 'Custom base URL for S4DS API', | ||||
| 			displayOptions: { | ||||
| 				show: { | ||||
| 					baseUrl: ['custom'], | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			displayName: 'Username', | ||||
| 			name: 'username', | ||||
| 			type: 'string', | ||||
| 			default: '', | ||||
| 			required: true, | ||||
| 			description: 'Username for S4DS authentication', | ||||
| 		}, | ||||
| 		{ | ||||
| 			displayName: 'Password', | ||||
| 			name: 'password', | ||||
| 			type: 'string', | ||||
| 			typeOptions: { | ||||
| 				password: true, | ||||
| 			}, | ||||
| 			default: '', | ||||
| 			required: true, | ||||
| 			description: 'Password for S4DS authentication', | ||||
| 		}, | ||||
| 		{ | ||||
| 			displayName: 'Timeout', | ||||
| 			name: 'timeout', | ||||
| 			type: 'number', | ||||
| 			default: 30000, | ||||
| 			description: 'Request timeout in milliseconds', | ||||
| 		}, | ||||
| 	]; | ||||
| }  | ||||
							
								
								
									
										3944
									
								
								cursor_nodo_personalizado.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3944
									
								
								cursor_nodo_personalizado.md
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -21,7 +21,7 @@ if [ -z "$PACKAGE_NAME" ]; then | |||
| fi | ||||
|   | ||||
| # Set the target directory based on the package name. | ||||
| TARGET_DIR="/var/lib/docker/volumes/n8n-self-hosted_n8n_data/_data/custom/$PACKAGE_NAME" | ||||
| TARGET_DIR="/var/lib/docker/volumes/n8n-nodes-starter-s4ds_n8n_data/_data/custom/$PACKAGE_NAME" | ||||
|   | ||||
| echo "Detected package name: '$PACKAGE_NAME'" | ||||
| echo "Target deployment directory: '$TARGET_DIR'" | ||||
|  | @ -53,7 +53,7 @@ echo "Deployment complete." | |||
| # Step 3: Restart n8n | ||||
| ############################## | ||||
| echo "Restarting n8n..." | ||||
| docker container restart n8n-self-hosted-n8n-1 | ||||
| docker container restart n8n-nodes-starter-s4ds-n8n-1 | ||||
|   | ||||
| # Logging for debugging | ||||
| docker logs -f n8n-self-hosted-n8n-1 | ||||
| docker logs -f n8n-nodes-starter-s4ds-n8n-1 | ||||
|  | @ -24,7 +24,7 @@ services: | |||
|     image: docker.n8n.io/n8nio/n8n | ||||
|     restart: always | ||||
|     ports: | ||||
|       - "127.0.0.1:5678:5678" | ||||
|       - "5678:5678" | ||||
|     labels: | ||||
|       - traefik.enable=true | ||||
|       - traefik.http.routers.n8n.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`) | ||||
|  |  | |||
							
								
								
									
										11
									
								
								index.js
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								index.js
									
										
									
									
									
								
							|  | @ -0,0 +1,11 @@ | |||
| const { S4DSAuth } = require('./dist/nodes/S4DSAuth/S4DSAuth.node.js'); | ||||
| const { S4DSExample } = require('./dist/nodes/S4DSExample/S4DSExample.node.js'); | ||||
| const { HttpBin } = require('./dist/nodes/HttpBin/HttpBin.node.js'); | ||||
| 
 | ||||
| const { S4DSApi } = require('./dist/credentials/S4DSApi.credentials.js'); | ||||
| const { HttpBinApi } = require('./dist/credentials/HttpBinApi.credentials.js'); | ||||
| 
 | ||||
| module.exports = { | ||||
| 	nodes: [S4DSAuth, S4DSExample, HttpBin], | ||||
| 	credentials: [S4DSApi, HttpBinApi], | ||||
| }; | ||||
|  | @ -1,77 +0,0 @@ | |||
| import type { | ||||
| 	IExecuteFunctions, | ||||
| 	INodeExecutionData, | ||||
| 	INodeType, | ||||
| 	INodeTypeDescription, | ||||
| } from 'n8n-workflow'; | ||||
| import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; | ||||
| 
 | ||||
| export class ExampleNode implements INodeType { | ||||
| 	description: INodeTypeDescription = { | ||||
| 		displayName: 'Example Node', | ||||
| 		name: 'exampleNode', | ||||
| 		group: ['transform'], | ||||
| 		version: 1, | ||||
| 		description: 'Basic Example Node', | ||||
| 		defaults: { | ||||
| 			name: 'Example Node', | ||||
| 		}, | ||||
| 		inputs: [NodeConnectionType.Main], | ||||
| 		outputs: [NodeConnectionType.Main], | ||||
| 		usableAsTool: true, | ||||
| 		properties: [ | ||||
| 			// 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: '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<INodeExecutionData[][]> { | ||||
| 		const items = this.getInputData(); | ||||
| 
 | ||||
| 		let item: INodeExecutionData; | ||||
| 		let myString: string; | ||||
| 
 | ||||
| 		// 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, pairedItem: itemIndex }); | ||||
| 				} 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 [items]; | ||||
| 	} | ||||
| } | ||||
|  | @ -1,77 +0,0 @@ | |||
| import type { | ||||
| 	IExecuteFunctions, | ||||
| 	INodeExecutionData, | ||||
| 	INodeType, | ||||
| 	INodeTypeDescription, | ||||
| } from 'n8n-workflow'; | ||||
| import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; | ||||
| 
 | ||||
| export class ExampleNode2 implements INodeType { | ||||
| 	description: INodeTypeDescription = { | ||||
| 		displayName: 'Example Node 2', | ||||
| 		name: 'exampleNode2', | ||||
| 		group: ['transform'], | ||||
| 		version: 1, | ||||
| 		description: 'Basic Example Node', | ||||
| 		defaults: { | ||||
| 			name: 'Example Node', | ||||
| 		}, | ||||
| 		inputs: [NodeConnectionType.Main], | ||||
| 		outputs: [NodeConnectionType.Main], | ||||
| 		usableAsTool: true, | ||||
| 		properties: [ | ||||
| 			// 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: '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<INodeExecutionData[][]> { | ||||
| 		const items = this.getInputData(); | ||||
| 
 | ||||
| 		let item: INodeExecutionData; | ||||
| 		let myString: string; | ||||
| 
 | ||||
| 		// 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, pairedItem: itemIndex }); | ||||
| 				} 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 [items]; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										109
									
								
								nodes/S4DSAuth/AuthDescription.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								nodes/S4DSAuth/AuthDescription.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,109 @@ | |||
| import { INodeProperties } from 'n8n-workflow'; | ||||
| 
 | ||||
| // Operaciones disponibles para el recurso de autenticación
 | ||||
| export const authOperations: INodeProperties[] = [ | ||||
| 	{ | ||||
| 		displayName: 'Operation', | ||||
| 		name: 'operation', | ||||
| 		type: 'options', | ||||
| 		noDataExpression: true, | ||||
| 		displayOptions: { | ||||
| 			show: { | ||||
| 				resource: ['auth'], | ||||
| 			}, | ||||
| 		}, | ||||
| 		options: [ | ||||
| 			{ | ||||
| 				name: 'Generate Token', | ||||
| 				value: 'generateToken', | ||||
| 				description: 'Generate authentication token using credentials', | ||||
| 				action: 'Generate authentication token', | ||||
| 				routing: { | ||||
| 					request: { | ||||
| 						method: 'POST', | ||||
| 						url: '/login/generateToken', | ||||
| 						body: { | ||||
| 							username: '={{$credentials.s4dsApi.username}}', | ||||
| 							password: '={{$credentials.s4dsApi.password}}', | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			{ | ||||
| 				name: 'Validate Token', | ||||
| 				value: 'validateToken', | ||||
| 				description: 'Validate existing token', | ||||
| 				action: 'Validate existing token', | ||||
| 				routing: { | ||||
| 					request: { | ||||
| 						method: 'GET', | ||||
| 						url: '/auth/validate', | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		], | ||||
| 		default: 'generateToken', | ||||
| 	}, | ||||
| ]; | ||||
| 
 | ||||
| // Campos para la operación de generación de token
 | ||||
| const generateTokenOperation: INodeProperties[] = [ | ||||
| 	{ | ||||
| 		displayName: 'Additional Fields', | ||||
| 		name: 'additionalFields', | ||||
| 		type: 'collection', | ||||
| 		placeholder: 'Add Field', | ||||
| 		default: {}, | ||||
| 		displayOptions: { | ||||
| 			show: { | ||||
| 				resource: ['auth'], | ||||
| 				operation: ['generateToken'], | ||||
| 			}, | ||||
| 		}, | ||||
| 		options: [ | ||||
| 			{ | ||||
| 				displayName: 'Store Token in Context', | ||||
| 				name: 'storeInContext', | ||||
| 				type: 'boolean', | ||||
| 				default: true, | ||||
| 				description: 'Whether to store the token in the workflow context for other nodes to use', | ||||
| 			}, | ||||
| 			{ | ||||
| 				displayName: 'Token Context Key', | ||||
| 				name: 'tokenContextKey', | ||||
| 				type: 'string', | ||||
| 				default: 's4ds_token', | ||||
| 				description: 'Key to store the token in the workflow context', | ||||
| 				displayOptions: { | ||||
| 					show: { | ||||
| 						storeInContext: [true], | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		], | ||||
| 	}, | ||||
| ]; | ||||
| 
 | ||||
| // Campos para la operación de validación de token
 | ||||
| const validateTokenOperation: INodeProperties[] = [ | ||||
| 	{ | ||||
| 		displayName: 'Token', | ||||
| 		name: 'token', | ||||
| 		type: 'string', | ||||
| 		required: true, | ||||
| 		default: '', | ||||
| 		displayOptions: { | ||||
| 			show: { | ||||
| 				resource: ['auth'], | ||||
| 				operation: ['validateToken'], | ||||
| 			}, | ||||
| 		}, | ||||
| 		description: 'Token to validate', | ||||
| 	}, | ||||
| ]; | ||||
| 
 | ||||
| // Exportar todos los campos
 | ||||
| export const authFields: INodeProperties[] = [ | ||||
| 	...generateTokenOperation, | ||||
| 	...validateTokenOperation, | ||||
| ];  | ||||
							
								
								
									
										111
									
								
								nodes/S4DSAuth/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								nodes/S4DSAuth/README.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,111 @@ | |||
| # S4DS Authentication Node | ||||
| 
 | ||||
| Este nodo permite autenticarse con las APIs de S4DS y gestionar tokens de acceso. | ||||
| 
 | ||||
| ## Características | ||||
| 
 | ||||
| - **Generación de tokens**: Autenticación con usuario y contraseña | ||||
| - **Validación de tokens**: Verificar si un token es válido | ||||
| - **Múltiples ambientes**: Soporte para Test, UAT y Production | ||||
| - **Múltiples clientes**: Configuración para diferentes clientes (Demo, Cliente 1, Cliente 2) | ||||
| - **Gestión de contexto**: Almacenamiento automático de tokens para uso en otros nodos | ||||
| 
 | ||||
| ## Configuración | ||||
| 
 | ||||
| ### Credenciales | ||||
| 
 | ||||
| 1. Configure las credenciales de S4DS API: | ||||
|    - **Base URL**: Seleccione el ambiente y cliente correspondiente | ||||
|    - **Username**: Nombre de usuario para autenticación | ||||
|    - **Password**: Contraseña para autenticación | ||||
|    - **Custom Base URL**: Para URLs personalizadas | ||||
|    - **Timeout**: Tiempo de espera para las peticiones (ms) | ||||
| 
 | ||||
| ### Operaciones | ||||
| 
 | ||||
| #### Generate Token | ||||
| 
 | ||||
| Genera un token de autenticación usando las credenciales configuradas. | ||||
| 
 | ||||
| **Parámetros opcionales:** | ||||
| - **Store Token in Context**: Almacenar token en el contexto del workflow | ||||
| - **Token Context Key**: Clave para almacenar el token (por defecto: `s4ds_token`) | ||||
| 
 | ||||
| **Parámetros opcionales:** | ||||
| - **Store Token in Context**: Almacenar token en el contexto del workflow | ||||
| - **Token Context Key**: Clave para almacenar el token (por defecto: `s4ds_token`) | ||||
| 
 | ||||
| **Respuesta:** | ||||
| ```json | ||||
| { | ||||
|   "token_type": "Bearer", | ||||
|   "expires_in": 3600, | ||||
|   "token": "8a6c71b3-fa62-434d-8b38-907de24c3176" | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| #### Validate Token | ||||
| 
 | ||||
| Valida si un token existente es válido. | ||||
| 
 | ||||
| **Parámetros requeridos:** | ||||
| - **Token**: Token a validar | ||||
| 
 | ||||
| ## Uso en Workflows | ||||
| 
 | ||||
| ### Flujo básico de autenticación | ||||
| 
 | ||||
| 1. Configure el nodo **S4DS Auth** con la operación "Generate Token" | ||||
| 2. Configure las credenciales y parámetros de autenticación | ||||
| 3. El token se almacenará automáticamente en el contexto del workflow | ||||
| 4. Use el token en otros nodos de API S4DS | ||||
| 
 | ||||
| ### Acceso al token desde otros nodos | ||||
| 
 | ||||
| El token se almacena en el contexto del workflow y puede ser accedido usando: | ||||
| 
 | ||||
| ``` | ||||
| {{ $context.s4ds_token }} | ||||
| ``` | ||||
| 
 | ||||
| O si configuró una clave personalizada: | ||||
| 
 | ||||
| ``` | ||||
| {{ $context.your_custom_key }} | ||||
| ``` | ||||
| 
 | ||||
| ## URLs de API | ||||
| 
 | ||||
| ### Demo | ||||
| - **Test**: `https://demotest.s4ds.com/demoapi-test` | ||||
| - **UAT**: `https://demouat.s4ds.com/demoapi-uat` | ||||
| - **Production**: `https://demoprod.s4ds.com/demoapi-prod` | ||||
| 
 | ||||
| ### Cliente 1 | ||||
| - **Test**: `https://cliente1test.s4ds.com/cliente1api-test` | ||||
| - **UAT**: `https://cliente1uat.s4ds.com/cliente1api-uat` | ||||
| - **Production**: `https://cliente1prod.s4ds.com/cliente1api-prod` | ||||
| 
 | ||||
| ### Cliente 2 | ||||
| - **Test**: `https://cliente2test.s4ds.com/cliente2api-test` | ||||
| - **UAT**: `https://cliente2uat.s4ds.com/cliente2api-uat` | ||||
| - **Production**: `https://cliente2prod.s4ds.com/cliente2api-prod` | ||||
| 
 | ||||
| ## Manejo de Errores | ||||
| 
 | ||||
| El nodo maneja automáticamente: | ||||
| - Errores de autenticación (credenciales inválidas) | ||||
| - Errores de red y timeout | ||||
| - Tokens expirados | ||||
| - Errores de formato de respuesta | ||||
| 
 | ||||
| ## Ejemplo de Workflow | ||||
| 
 | ||||
| ``` | ||||
| [S4DS Auth: Generate Token] → [HTTP Request: API Call] → [Process Data] | ||||
| ``` | ||||
| 
 | ||||
| En el nodo HTTP Request, configure el header de autorización: | ||||
| ``` | ||||
| Authorization: Bearer {{ $context.s4ds_token }} | ||||
| ```  | ||||
							
								
								
									
										168
									
								
								nodes/S4DSAuth/S4DSAuth.node.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								nodes/S4DSAuth/S4DSAuth.node.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,168 @@ | |||
| import { INodeType, INodeTypeDescription, NodeConnectionType, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; | ||||
| import { authOperations, authFields } from './AuthDescription'; | ||||
| 
 | ||||
| export class S4DSAuth implements INodeType { | ||||
| 	description: INodeTypeDescription = { | ||||
| 		displayName: 'S4DS Authentication', | ||||
| 		name: 's4dsAuth', | ||||
| 		icon: { light: 'file:s4ds.svg', dark: 'file:s4ds.svg' }, | ||||
| 		group: ['transform'], | ||||
| 		version: 1, | ||||
| 		subtitle: '={{$parameter["operation"]}}', | ||||
| 		description: 'Authenticate with S4DS API and manage tokens', | ||||
| 		defaults: { | ||||
| 			name: 'S4DS Auth', | ||||
| 		}, | ||||
| 		inputs: [NodeConnectionType.Main], | ||||
| 		outputs: [NodeConnectionType.Main], | ||||
| 		usableAsTool: true, | ||||
| 		credentials: [ | ||||
| 			{ | ||||
| 				name: 's4dsApi', | ||||
| 				required: true, | ||||
| 			}, | ||||
| 		], | ||||
| 		requestDefaults: { | ||||
| 			baseURL: '={{$credentials.s4dsApi.baseUrl === "custom" ? $credentials.s4dsApi.customBaseUrl : $credentials.s4dsApi.baseUrl}}', | ||||
| 			url: '', | ||||
| 			headers: { | ||||
| 				Accept: 'application/json', | ||||
| 				'Content-Type': 'application/json', | ||||
| 			}, | ||||
| 		}, | ||||
| 		properties: [ | ||||
| 			{ | ||||
| 				displayName: 'Resource', | ||||
| 				name: 'resource', | ||||
| 				type: 'options', | ||||
| 				noDataExpression: true, | ||||
| 				options: [ | ||||
| 					{ | ||||
| 						name: 'Authentication', | ||||
| 						value: 'auth', | ||||
| 					}, | ||||
| 				], | ||||
| 				default: 'auth', | ||||
| 			}, | ||||
| 			...authOperations, | ||||
| 			...authFields, | ||||
| 		], | ||||
| 	}; | ||||
| 
 | ||||
| 	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { | ||||
| 		const items = this.getInputData(); | ||||
| 		const returnData: INodeExecutionData[] = []; | ||||
| 
 | ||||
| 		for (let i = 0; i < items.length; i++) { | ||||
| 			try { | ||||
| 				const resource = this.getNodeParameter('resource', i) as string; | ||||
| 				const operation = this.getNodeParameter('operation', i) as string; | ||||
| 
 | ||||
| 				if (resource === 'auth') { | ||||
| 					if (operation === 'generateToken') { | ||||
| 						// Obtener credenciales
 | ||||
| 						const credentials = await this.getCredentials('s4dsApi'); | ||||
| 						 | ||||
| 						// Determinar la URL base
 | ||||
| 						let baseUrl = credentials.baseUrl; | ||||
| 						if (baseUrl === 'custom') { | ||||
| 							baseUrl = credentials.customBaseUrl; | ||||
| 						} | ||||
| 						 | ||||
| 						if (!baseUrl) { | ||||
| 							throw new Error('Base URL is required. Please configure the S4DS API credentials.'); | ||||
| 						} | ||||
| 
 | ||||
| 						// Ejecutar la petición HTTP para generar token
 | ||||
| 						const response = await this.helpers.httpRequest({ | ||||
| 							method: 'POST', | ||||
| 							url: `${baseUrl}/login/generateToken`, | ||||
| 							headers: { | ||||
| 								'Accept': 'application/json', | ||||
| 								'Content-Type': 'application/json', | ||||
| 							}, | ||||
| 							body: { | ||||
| 								username: credentials.username, | ||||
| 								password: credentials.password, | ||||
| 							}, | ||||
| 						}); | ||||
| 
 | ||||
| 						// Almacenar token en contexto
 | ||||
| 						const additionalFields = this.getNodeParameter('additionalFields', i) as { | ||||
| 							storeInContext?: boolean; | ||||
| 							tokenContextKey?: string; | ||||
| 						}; | ||||
| 
 | ||||
| 						const contextKey = additionalFields.tokenContextKey || 's4ds_token'; | ||||
| 						const tokenData = { | ||||
| 							token: response.token, | ||||
| 							token_type: response.token_type, | ||||
| 							expires_in: response.expires_in, | ||||
| 							expires_at: new Date(Date.now() + response.expires_in * 1000).toISOString(), | ||||
| 							authorization_header: `${response.token_type} ${response.token}`, | ||||
| 						}; | ||||
| 						 | ||||
| 						// Almacenar en contexto del workflow
 | ||||
| 						this.getWorkflowStaticData('global')[contextKey] = tokenData; | ||||
| 
 | ||||
| 						// Retornar respuesta sin el token por seguridad
 | ||||
| 						const secureResponse = { | ||||
| 							success: true, | ||||
| 							token_type: response.token_type, | ||||
| 							expires_in: response.expires_in, | ||||
| 							expires_at: tokenData.expires_at, | ||||
| 							message: 'Token generated successfully and stored in workflow context', | ||||
| 							context_key: contextKey, | ||||
| 						}; | ||||
| 
 | ||||
| 						returnData.push({ | ||||
| 							json: secureResponse, | ||||
| 						}); | ||||
| 					} else if (operation === 'validateToken') { | ||||
| 						const token = this.getNodeParameter('token', i) as string; | ||||
| 						 | ||||
| 						// Obtener credenciales
 | ||||
| 						const credentials = await this.getCredentials('s4dsApi'); | ||||
| 						 | ||||
| 						// Determinar la URL base
 | ||||
| 						let baseUrl = credentials.baseUrl; | ||||
| 						if (baseUrl === 'custom') { | ||||
| 							baseUrl = credentials.customBaseUrl; | ||||
| 						} | ||||
| 						 | ||||
| 						if (!baseUrl) { | ||||
| 							throw new Error('Base URL is required. Please configure the S4DS API credentials.'); | ||||
| 						} | ||||
| 
 | ||||
| 						// Ejecutar validación de token
 | ||||
| 						const response = await this.helpers.httpRequest({ | ||||
| 							method: 'GET', | ||||
| 							url: `${baseUrl}/auth/validate`, | ||||
| 							headers: { | ||||
| 								'Accept': 'application/json', | ||||
| 								'Content-Type': 'application/json', | ||||
| 								'Authorization': `Bearer ${token}`, | ||||
| 							}, | ||||
| 						}); | ||||
| 
 | ||||
| 						returnData.push({ | ||||
| 							json: response, | ||||
| 						}); | ||||
| 					} | ||||
| 				} | ||||
| 			} catch (error) { | ||||
| 				if (this.continueOnFail()) { | ||||
| 					returnData.push({ | ||||
| 						json: { | ||||
| 							error: error.message, | ||||
| 						}, | ||||
| 					}); | ||||
| 					continue; | ||||
| 				} | ||||
| 				throw error; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return [returnData]; | ||||
| 	} | ||||
| }  | ||||
							
								
								
									
										80
									
								
								nodes/S4DSAuth/S4DSHelper.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								nodes/S4DSAuth/S4DSHelper.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,80 @@ | |||
| import { IExecuteFunctions } from 'n8n-workflow'; | ||||
| 
 | ||||
| export interface S4DSTokenData { | ||||
| 	token: string; | ||||
| 	token_type: string; | ||||
| 	expires_in: number; | ||||
| 	expires_at: string; | ||||
| 	authorization_header: string; | ||||
| } | ||||
| 
 | ||||
| export class S4DSHelper { | ||||
| 	/** | ||||
| 	 * Obtiene el token de autorización del contexto del workflow | ||||
| 	 * @param this - Contexto de ejecución del nodo | ||||
| 	 * @param contextKey - Clave del contexto donde está almacenado el token (por defecto: 's4ds_token') | ||||
| 	 * @returns Objeto con datos del token incluyendo el header de autorización | ||||
| 	 */ | ||||
| 	static getTokenFromContext(this: IExecuteFunctions, contextKey: string = 's4ds_token'): S4DSTokenData { | ||||
| 		const tokenData = this.getWorkflowStaticData('global')[contextKey] as S4DSTokenData; | ||||
| 		 | ||||
| 		if (!tokenData || !tokenData.token) { | ||||
| 			throw new Error(`No S4DS token found in context with key: ${contextKey}. Please run S4DS Auth node first.`); | ||||
| 		} | ||||
| 
 | ||||
| 		// Verificar si el token ha expirado
 | ||||
| 		if (tokenData.expires_at && new Date(tokenData.expires_at) <= new Date()) { | ||||
| 			throw new Error('S4DS token has expired. Please regenerate token using S4DS Auth node.'); | ||||
| 		} | ||||
| 
 | ||||
| 		return tokenData; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Obtiene el header de autorización listo para usar | ||||
| 	 * @param this - Contexto de ejecución del nodo | ||||
| 	 * @param contextKey - Clave del contexto donde está almacenado el token (por defecto: 's4ds_token') | ||||
| 	 * @returns Header de autorización completo (ej: "Bearer 8a6c71b3-fa62-434d-8b38-907de24c3176") | ||||
| 	 */ | ||||
| 	static getAuthorizationHeader(this: IExecuteFunctions, contextKey: string = 's4ds_token'): string { | ||||
| 		const tokenData = S4DSHelper.getTokenFromContext.call(this, contextKey); | ||||
| 		return tokenData.authorization_header; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Obtiene headers HTTP completos con autorización | ||||
| 	 * @param this - Contexto de ejecución del nodo | ||||
| 	 * @param contextKey - Clave del contexto donde está almacenado el token (por defecto: 's4ds_token') | ||||
| 	 * @param additionalHeaders - Headers adicionales opcionales | ||||
| 	 * @returns Objeto con headers HTTP completos | ||||
| 	 */ | ||||
| 	static getHeadersWithAuth( | ||||
| 		this: IExecuteFunctions,  | ||||
| 		contextKey: string = 's4ds_token', | ||||
| 		additionalHeaders: Record<string, string> = {} | ||||
| 	): Record<string, string> { | ||||
| 		const authHeader = S4DSHelper.getAuthorizationHeader.call(this, contextKey); | ||||
| 		 | ||||
| 		return { | ||||
| 			Authorization: authHeader, | ||||
| 			Accept: 'application/json', | ||||
| 			'Content-Type': 'application/json', | ||||
| 			...additionalHeaders, | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Verifica si existe un token válido en el contexto | ||||
| 	 * @param this - Contexto de ejecución del nodo | ||||
| 	 * @param contextKey - Clave del contexto donde está almacenado el token (por defecto: 's4ds_token') | ||||
| 	 * @returns true si existe un token válido, false en caso contrario | ||||
| 	 */ | ||||
| 	static hasValidToken(this: IExecuteFunctions, contextKey: string = 's4ds_token'): boolean { | ||||
| 		try { | ||||
| 			S4DSHelper.getTokenFromContext.call(this, contextKey); | ||||
| 			return true; | ||||
| 		} catch { | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
| }  | ||||
							
								
								
									
										5
									
								
								nodes/S4DSAuth/s4ds.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								nodes/S4DSAuth/s4ds.svg
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | ||||
|   <path d="M12 2L2 7l10 5 10-5-10-5z"/> | ||||
|   <path d="M2 17l10 5 10-5"/> | ||||
|   <path d="M2 12l10 5 10-5"/> | ||||
| </svg>  | ||||
| After Width: | Height: | Size: 289 B | 
							
								
								
									
										194
									
								
								nodes/S4DSExample/S4DSExample.node.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								nodes/S4DSExample/S4DSExample.node.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,194 @@ | |||
| import { INodeType, INodeTypeDescription, NodeConnectionType, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; | ||||
| import { S4DSHelper } from '../S4DSAuth/S4DSHelper'; | ||||
| 
 | ||||
| export class S4DSExample implements INodeType { | ||||
| 	description: INodeTypeDescription = { | ||||
| 		displayName: 'S4DS Example', | ||||
| 		name: 's4dsExample', | ||||
| 		icon: { light: 'file:s4ds.svg', dark: 'file:s4ds.svg' }, | ||||
| 		group: ['transform'], | ||||
| 		version: 1, | ||||
| 		subtitle: '={{$parameter["operation"]}}', | ||||
| 		description: 'Example node that uses S4DS authentication token', | ||||
| 		defaults: { | ||||
| 			name: 'S4DS Example', | ||||
| 		}, | ||||
| 		inputs: [NodeConnectionType.Main], | ||||
| 		outputs: [NodeConnectionType.Main], | ||||
| 		credentials: [ | ||||
| 			{ | ||||
| 				name: 's4dsApi', | ||||
| 				required: true, | ||||
| 			}, | ||||
| 		], | ||||
| 		requestDefaults: { | ||||
| 			baseURL: '={{$credentials.s4dsApi.baseUrl}}', | ||||
| 			url: '', | ||||
| 			headers: { | ||||
| 				Accept: 'application/json', | ||||
| 				'Content-Type': 'application/json', | ||||
| 			}, | ||||
| 		}, | ||||
| 		properties: [ | ||||
| 			{ | ||||
| 				displayName: 'Operation', | ||||
| 				name: 'operation', | ||||
| 				type: 'options', | ||||
| 				noDataExpression: true, | ||||
| 				options: [ | ||||
| 					{ | ||||
| 						name: 'Get Product Count', | ||||
| 						value: 'getProductCount', | ||||
| 						description: 'Get the total count of products', | ||||
| 						action: 'Get product count', | ||||
| 						routing: { | ||||
| 							request: { | ||||
| 								method: 'GET', | ||||
| 								url: '/product/count', | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				], | ||||
| 				default: 'getProductCount', | ||||
| 			}, | ||||
| 			{ | ||||
| 				displayName: 'Token Source', | ||||
| 				name: 'tokenSource', | ||||
| 				type: 'options', | ||||
| 				required: true, | ||||
| 				default: 'context', | ||||
| 				options: [ | ||||
| 					{ | ||||
| 						name: 'From Context', | ||||
| 						value: 'context', | ||||
| 						description: 'Use token stored in workflow context', | ||||
| 					}, | ||||
| 					{ | ||||
| 						name: 'Custom Token', | ||||
| 						value: 'custom', | ||||
| 						description: 'Provide custom token', | ||||
| 					}, | ||||
| 				], | ||||
| 			}, | ||||
| 			{ | ||||
| 				displayName: 'Context Token Key', | ||||
| 				name: 'contextTokenKey', | ||||
| 				type: 'string', | ||||
| 				default: 's4ds_token', | ||||
| 				required: true, | ||||
| 				displayOptions: { | ||||
| 					show: { | ||||
| 						tokenSource: ['context'], | ||||
| 					}, | ||||
| 				}, | ||||
| 				description: 'Key used to store token in workflow context', | ||||
| 			}, | ||||
| 			{ | ||||
| 				displayName: 'Custom Token', | ||||
| 				name: 'customToken', | ||||
| 				type: 'string', | ||||
| 				default: '', | ||||
| 				required: true, | ||||
| 				displayOptions: { | ||||
| 					show: { | ||||
| 						tokenSource: ['custom'], | ||||
| 					}, | ||||
| 				}, | ||||
| 				description: 'Custom authentication token', | ||||
| 			}, | ||||
| 		], | ||||
| 	}; | ||||
| 
 | ||||
| 	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { | ||||
| 		const items = this.getInputData(); | ||||
| 		const returnData: INodeExecutionData[] = []; | ||||
| 
 | ||||
| 		for (let i = 0; i < items.length; i++) { | ||||
| 			try { | ||||
| 				const operation = this.getNodeParameter('operation', i) as string; | ||||
| 				const tokenSource = this.getNodeParameter('tokenSource', i) as string; | ||||
| 
 | ||||
| 				// Obtener headers según la fuente
 | ||||
| 				let headers: Record<string, string>; | ||||
| 				 | ||||
| 				if (tokenSource === 'context') { | ||||
| 					const contextKey = this.getNodeParameter('contextTokenKey', i) as string; | ||||
| 					 | ||||
| 					// Debug: Verificar qué hay en el contexto
 | ||||
| 					const contextData = this.getWorkflowStaticData('global'); | ||||
| 					const availableKeys = Object.keys(contextData); | ||||
| 					 | ||||
| 					// Usar el helper para obtener headers con autorización
 | ||||
| 					try { | ||||
| 						headers = S4DSHelper.getHeadersWithAuth.call(this, contextKey); | ||||
| 					} catch (error) { | ||||
| 						// Si falla, retornar información de debug
 | ||||
| 						returnData.push({ | ||||
| 							json: { | ||||
| 								error: error.message, | ||||
| 								debug_info: { | ||||
| 									context_key_requested: contextKey, | ||||
| 									available_context_keys: availableKeys, | ||||
| 									context_data: contextData, | ||||
| 									message: 'Please ensure S4DS Auth node runs before this node and generates a token successfully.' | ||||
| 								} | ||||
| 							}, | ||||
| 						}); | ||||
| 						continue; | ||||
| 					} | ||||
| 				} else { | ||||
| 					const customToken = this.getNodeParameter('customToken', i) as string; | ||||
| 					headers = { | ||||
| 						Authorization: customToken, | ||||
| 						Accept: 'application/json', | ||||
| 						'Content-Type': 'application/json', | ||||
| 					}; | ||||
| 				} | ||||
| 
 | ||||
| 				// Ejecutar la operación
 | ||||
| 				let response; | ||||
| 				if (operation === 'getProductCount') { | ||||
| 					// Obtener credenciales para construir la URL base
 | ||||
| 					const credentials = await this.getCredentials('s4dsApi'); | ||||
| 					 | ||||
| 					// Determinar la URL base
 | ||||
| 					let baseUrl = credentials.baseUrl; | ||||
| 					if (baseUrl === 'custom') { | ||||
| 						baseUrl = credentials.customBaseUrl; | ||||
| 					} | ||||
| 					 | ||||
| 					if (!baseUrl) { | ||||
| 						throw new Error('Base URL is required. Please configure the S4DS API credentials.'); | ||||
| 					} | ||||
| 
 | ||||
| 					response = await this.helpers.httpRequest({ | ||||
| 						method: 'GET', | ||||
| 						url: `${baseUrl}/product/count`, | ||||
| 						headers, | ||||
| 					}); | ||||
| 				} | ||||
| 
 | ||||
| 				returnData.push({ | ||||
| 					json: { | ||||
| 						...response, | ||||
| 						_token_info: { | ||||
| 							source: tokenSource, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}); | ||||
| 			} catch (error) { | ||||
| 				if (this.continueOnFail()) { | ||||
| 					returnData.push({ | ||||
| 						json: { | ||||
| 							error: error.message, | ||||
| 						}, | ||||
| 					}); | ||||
| 					continue; | ||||
| 				} | ||||
| 				throw error; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return [returnData]; | ||||
| 	} | ||||
| }  | ||||
							
								
								
									
										10
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								package.json
									
										
									
									
									
								
							|  | @ -33,13 +33,13 @@ | |||
|   "n8n": { | ||||
|     "n8nNodesApiVersion": 1, | ||||
|     "credentials": [ | ||||
|       "dist/credentials/ExampleCredentialsApi.credentials.js", | ||||
|       "dist/credentials/HttpBinApi.credentials.js" | ||||
|       "dist/credentials/HttpBinApi.credentials.js", | ||||
|       "dist/credentials/S4DSApi.credentials.js" | ||||
|     ], | ||||
|     "nodes": [ | ||||
|       "dist/nodes/ExampleNode/ExampleNode.node.js", | ||||
|       "dist/nodes/ExampleNode2/ExampleNode2.node.js", | ||||
|       "dist/nodes/HttpBin/HttpBin.node.js" | ||||
|       "dist/nodes/HttpBin/HttpBin.node.js", | ||||
|       "dist/nodes/S4DSAuth/S4DSAuth.node.js", | ||||
|       "dist/nodes/S4DSExample/S4DSExample.node.js" | ||||
|     ] | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue