mirror of
https://github.com/n8n-io/n8n-nodes-starter.git
synced 2025-10-29 06:22:24 -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
|
fi
|
||||||
|
|
||||||
# Set the target directory based on the package name.
|
# 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 "Detected package name: '$PACKAGE_NAME'"
|
||||||
echo "Target deployment directory: '$TARGET_DIR'"
|
echo "Target deployment directory: '$TARGET_DIR'"
|
||||||
|
|
@ -53,7 +53,7 @@ echo "Deployment complete."
|
||||||
# Step 3: Restart n8n
|
# Step 3: Restart n8n
|
||||||
##############################
|
##############################
|
||||||
echo "Restarting n8n..."
|
echo "Restarting n8n..."
|
||||||
docker container restart n8n-self-hosted-n8n-1
|
docker container restart n8n-nodes-starter-s4ds-n8n-1
|
||||||
|
|
||||||
# Logging for debugging
|
# 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
|
image: docker.n8n.io/n8nio/n8n
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:5678:5678"
|
- "5678:5678"
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=true
|
- traefik.enable=true
|
||||||
- traefik.http.routers.n8n.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`)
|
- 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": {
|
"n8n": {
|
||||||
"n8nNodesApiVersion": 1,
|
"n8nNodesApiVersion": 1,
|
||||||
"credentials": [
|
"credentials": [
|
||||||
"dist/credentials/ExampleCredentialsApi.credentials.js",
|
"dist/credentials/HttpBinApi.credentials.js",
|
||||||
"dist/credentials/HttpBinApi.credentials.js"
|
"dist/credentials/S4DSApi.credentials.js"
|
||||||
],
|
],
|
||||||
"nodes": [
|
"nodes": [
|
||||||
"dist/nodes/ExampleNode/ExampleNode.node.js",
|
"dist/nodes/HttpBin/HttpBin.node.js",
|
||||||
"dist/nodes/ExampleNode2/ExampleNode2.node.js",
|
"dist/nodes/S4DSAuth/S4DSAuth.node.js",
|
||||||
"dist/nodes/HttpBin/HttpBin.node.js"
|
"dist/nodes/S4DSExample/S4DSExample.node.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue