@aiteza/n8n-nodes-aiteza 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AitezaProgress = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
- // ---------------------------------------------------------------------------
6
- // Helpers for reading the AITEZA Trigger's webhook body
7
- // ---------------------------------------------------------------------------
8
5
  function isAitezaTriggerType(type) {
9
6
  return typeof type === 'string' && /(^|\.)aitezaTrigger$/i.test(type);
10
7
  }
@@ -17,11 +14,6 @@ function findAitezaTriggerParentName(ef) {
17
14
  return undefined;
18
15
  }
19
16
  }
20
- /**
21
- * Look up a value first on the current input item, then on the original
22
- * AITEZA Trigger output (so intermediate Set/IF/SplitOut nodes that strip
23
- * fields don't break callbacks).
24
- */
25
17
  function readFromTrigger(ef, itemIndex, picker) {
26
18
  try {
27
19
  const items = ef.getInputData();
@@ -65,9 +57,6 @@ function readTriggerBodyField(ef, itemIndex, field) {
65
57
  return undefined;
66
58
  });
67
59
  }
68
- // ---------------------------------------------------------------------------
69
- // Node
70
- // ---------------------------------------------------------------------------
71
60
  class AitezaProgress {
72
61
  description = {
73
62
  displayName: 'AITEZA Progress',
@@ -76,10 +65,7 @@ class AitezaProgress {
76
65
  group: ['transform'],
77
66
  version: 1,
78
67
  subtitle: '={{$parameter["step"]}}',
79
- description: 'Reports workflow progress back to the AITEZA backend that triggered ' +
80
- 'this workflow. Reads callbackUrl / executionId / callbackToken from ' +
81
- 'the upstream AITEZA Trigger webhook body and POSTs to ' +
82
- '{callbackUrl}/api/internal/workflow/{executionId}/progress.',
68
+ description: 'Reports workflow progress back to Aiteza while the workflow is running. Reads the callback details from the upstream Aiteza Trigger node automatically.',
83
69
  defaults: { name: 'AITEZA Progress' },
84
70
  inputs: ['main'],
85
71
  outputs: ['main'],
@@ -91,8 +77,8 @@ class AitezaProgress {
91
77
  type: 'string',
92
78
  default: '',
93
79
  required: true,
94
- placeholder: 'Fetching files',
95
- description: 'Short, human-readable label for the current step',
80
+ placeholder: 'e.g. Fetching files',
81
+ description: 'Short label describing the current step shown to the user',
96
82
  },
97
83
  {
98
84
  displayName: 'Step Index',
@@ -100,7 +86,7 @@ class AitezaProgress {
100
86
  type: 'number',
101
87
  default: 1,
102
88
  typeOptions: { minValue: 0 },
103
- description: '1-based index of the current step',
89
+ description: 'Position of the current step (1-based)',
104
90
  },
105
91
  {
106
92
  displayName: 'Total Steps',
@@ -116,7 +102,8 @@ class AitezaProgress {
116
102
  type: 'string',
117
103
  default: '',
118
104
  typeOptions: { rows: 2 },
119
- description: 'Optional longer description of what is happening right now',
105
+ placeholder: 'e.g. Processing 5 of 20 documents',
106
+ description: 'Optional longer description of what is currently happening',
120
107
  },
121
108
  {
122
109
  displayName: 'Additional Fields (JSON)',
@@ -124,7 +111,7 @@ class AitezaProgress {
124
111
  type: 'json',
125
112
  default: '',
126
113
  placeholder: '{ "percent": 42 }',
127
- description: 'Optional JSON object that will be merged into the progress payload (e.g. percent, items processed, etc.)',
114
+ description: 'Optional JSON object merged into the progress payload (e.g. percent complete, items processed)',
128
115
  },
129
116
  {
130
117
  displayName: 'Options',
@@ -133,27 +120,36 @@ class AitezaProgress {
133
120
  placeholder: 'Add Option',
134
121
  default: {},
135
122
  options: [
123
+ {
124
+ displayName: 'Callback Token Override',
125
+ name: 'callbackTokenOverride',
126
+ type: 'string',
127
+ typeOptions: { password: true },
128
+ default: '',
129
+ description: 'Override the callback token read from the Aiteza Trigger',
130
+ },
136
131
  {
137
132
  displayName: 'Callback URL Override',
138
133
  name: 'callbackUrlOverride',
139
134
  type: 'string',
140
135
  default: '',
141
- description: 'Override the callback URL from the AITEZA Trigger. Without trailing slash.',
136
+ placeholder: 'e.g. https://aiteza.example.com',
137
+ description: 'Override the callback URL read from the Aiteza Trigger (without trailing slash)',
142
138
  },
143
139
  {
144
140
  displayName: 'Execution ID Override',
145
141
  name: 'executionIdOverride',
146
142
  type: 'string',
147
143
  default: '',
148
- description: 'Override the execution ID from the AITEZA Trigger',
144
+ placeholder: 'e.g. exec-abc123',
145
+ description: 'Override the execution ID read from the Aiteza Trigger',
149
146
  },
150
147
  {
151
- displayName: 'Callback Token Override',
152
- name: 'callbackTokenOverride',
153
- type: 'string',
154
- typeOptions: { password: true },
155
- default: '',
156
- description: 'Override the callback token from the AITEZA Trigger',
148
+ displayName: 'Fail Workflow on Callback Failure',
149
+ name: 'failOnError',
150
+ type: 'boolean',
151
+ default: false,
152
+ description: 'Whether to stop the workflow if the progress callback cannot be delivered. Defaults to false so a failed report never blocks the main flow.',
157
153
  },
158
154
  {
159
155
  displayName: 'Timeout (ms)',
@@ -161,13 +157,7 @@ class AitezaProgress {
161
157
  type: 'number',
162
158
  default: 5000,
163
159
  typeOptions: { minValue: 100 },
164
- },
165
- {
166
- displayName: 'Fail Workflow on Error',
167
- name: 'failOnError',
168
- type: 'boolean',
169
- default: false,
170
- description: 'Whether to fail the workflow if the progress callback fails. Defaults to false so progress reports never break the main flow.',
160
+ description: 'Maximum time in milliseconds to wait for the callback to complete',
171
161
  },
172
162
  ],
173
163
  },
@@ -207,7 +197,7 @@ class AitezaProgress {
207
197
  }
208
198
  catch (err) {
209
199
  if (failOnError) {
210
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `"Additional Fields" is not valid JSON: ${err.message}`, { itemIndex: i });
200
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The 'Additional Fields' value is not valid JSON: ${err.message}`, { itemIndex: i, description: 'Please provide a valid JSON object in the \'Additional Fields\' parameter, e.g. { "percent": 42 }.' });
211
201
  }
212
202
  }
213
203
  }
@@ -226,10 +216,10 @@ class AitezaProgress {
226
216
  if (!callbackUrl || !executionId) {
227
217
  reportMeta.skipped = true;
228
218
  reportMeta.reason = !callbackUrl
229
- ? 'No callbackUrl on AITEZA Trigger body'
230
- : 'No executionId on AITEZA Trigger body';
219
+ ? 'No callbackUrl on Aiteza Trigger body'
220
+ : 'No executionId on Aiteza Trigger body';
231
221
  if (failOnError) {
232
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Cannot send progress: ${reportMeta.reason}`, { itemIndex: i });
222
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Cannot send progress report: ${reportMeta.reason}`, { itemIndex: i, description: 'Make sure this node is downstream of an Aiteza Trigger node that provides \'callbackUrl\' and \'executionId\' in its body.' });
233
223
  }
234
224
  returnData.push({
235
225
  json: { ...items[i].json, _progressReport: reportMeta },
@@ -255,9 +245,9 @@ class AitezaProgress {
255
245
  catch (error) {
256
246
  reportMeta.sent = false;
257
247
  reportMeta.error =
258
- error?.message ?? error?.response?.statusText ?? 'Unknown error';
248
+ error?.message ?? error?.response?.statusText ?? 'The callback could not be delivered';
259
249
  if (failOnError) {
260
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Progress callback failed: ${reportMeta.error}`, { itemIndex: i });
250
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Progress callback could not be delivered: ${reportMeta.error}`, { itemIndex: i, description: 'Check that the \'callbackUrl\' is reachable and the callback token is valid.' });
261
251
  }
262
252
  }
263
253
  returnData.push({
@@ -8,16 +8,10 @@ class AitezaTrigger {
8
8
  icon: 'file:aiteza.svg',
9
9
  group: ['trigger'],
10
10
  version: 1,
11
- description: 'Starts the workflow when an AITEZA event is received via webhook. ' +
12
- 'Forwards the calling user\'s bearer token (from body.user.bearerToken) to downstream AITEZA nodes ' +
13
- 'so they act on behalf of the triggering user. Falls back to the request\'s Authorization header if no user token is present.',
11
+ description: 'Starts the workflow when an Aiteza event is received via webhook. Forwards the calling user\'s token to downstream Aiteza nodes so they act on behalf of the triggering user.',
14
12
  defaults: { name: 'AITEZA Trigger' },
15
13
  inputs: [],
16
14
  outputs: ['main'],
17
- // No credentials required – the trigger just receives webhooks.
18
- // Downstream AITEZA nodes still need a base URL (taken from this trigger
19
- // or from configured credentials) and will use the token from this
20
- // trigger for actual authentication.
21
15
  credentials: [],
22
16
  webhooks: [
23
17
  {
@@ -35,7 +29,7 @@ class AitezaTrigger {
35
29
  name: 'path',
36
30
  type: 'string',
37
31
  default: 'aiteza',
38
- placeholder: 'aiteza',
32
+ placeholder: 'e.g. aiteza',
39
33
  required: true,
40
34
  description: 'The webhook path to listen on. The full URL will be /webhook/{path}.',
41
35
  },
@@ -48,8 +42,7 @@ class AitezaTrigger {
48
42
  {
49
43
  name: 'Immediately',
50
44
  value: 'onReceived',
51
- description: 'Respond immediately with 200 OK as soon as the webhook is received. ' +
52
- 'Use the "AITEZA Workflow Result" node at the end of your workflow to send results back.',
45
+ description: 'Respond immediately with 200 OK as soon as the webhook is received. Use the Aiteza Workflow Result node at the end of your workflow to send results back.',
53
46
  },
54
47
  {
55
48
  name: 'When Last Node Finishes',
@@ -58,16 +51,15 @@ class AitezaTrigger {
58
51
  },
59
52
  ],
60
53
  default: 'onReceived',
61
- description: 'When and how to respond to the webhook. Use "Immediately" together with the ' +
62
- '"AITEZA Workflow Result" node to send results asynchronously.',
54
+ description: 'When and how to respond to the webhook. Use "Immediately" together with the Aiteza Workflow Result node to send results asynchronously.',
63
55
  },
64
56
  {
65
57
  displayName: 'AITEZA Base URL',
66
58
  name: 'baseUrl',
67
59
  type: 'string',
68
60
  default: '',
69
- placeholder: 'https://aiteza.example.com',
70
- description: 'Optional. If set, this URL is forwarded to downstream AITEZA nodes (as _baseUrl) so they don\'t need to repeat it. Without trailing slash.',
61
+ placeholder: 'e.g. https://aiteza.example.com',
62
+ description: 'Optional. If set, this URL is forwarded to downstream Aiteza nodes as \'_baseUrl\' so they don\'t need to repeat it. Without trailing slash.',
71
63
  },
72
64
  ],
73
65
  };
@@ -75,16 +67,10 @@ class AitezaTrigger {
75
67
  const body = this.getBodyData();
76
68
  const headers = this.getHeaderData();
77
69
  const query = this.getQueryData();
78
- // AITEZA places the calling user's bearer token inside the body (body.user.bearerToken)
79
- // so that downstream nodes can act on behalf of the user. The request's own
80
- // Authorization header is typically the service-account token used by AITEZA to
81
- // call the webhook itself – not what we want for delegated calls.
82
70
  const user = body?.user ?? {};
83
71
  const userBearer = user.bearerToken ?? '';
84
72
  const authHeader = headers.authorization || headers.Authorization || '';
85
73
  const headerToken = authHeader.replace(/^Bearer\s+/i, '');
86
- // Prefer the user-context token. Fall back to the header token (e.g. when the
87
- // webhook is called by something other than AITEZA).
88
74
  const authToken = userBearer || headerToken;
89
75
  const baseUrl = this.getNodeParameter('baseUrl', '').replace(/\/+$/, '');
90
76
  const outputData = {
@@ -94,8 +80,6 @@ class AitezaTrigger {
94
80
  };
95
81
  if (authToken)
96
82
  outputData._authToken = authToken;
97
- // Expose the service-account token separately in case a workflow explicitly
98
- // needs to call AITEZA with elevated/service privileges.
99
83
  if (headerToken && headerToken !== authToken)
100
84
  outputData._serviceAuthToken = headerToken;
101
85
  if (baseUrl)
@@ -2,9 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AitezaWorkflowResult = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
- // ---------------------------------------------------------------------------
6
- // Helpers for reading the AITEZA Trigger's webhook body
7
- // ---------------------------------------------------------------------------
8
5
  function isAitezaTriggerType(type) {
9
6
  return typeof type === 'string' && /(^|\.)aitezaTrigger$/i.test(type);
10
7
  }
@@ -17,11 +14,6 @@ function findAitezaTriggerParentName(ef) {
17
14
  return undefined;
18
15
  }
19
16
  }
20
- /**
21
- * Look up a value first on the current input item, then on the original
22
- * AITEZA Trigger output (so intermediate Set/IF/SplitOut nodes that strip
23
- * fields don't break callbacks).
24
- */
25
17
  function readFromTrigger(ef, itemIndex, picker) {
26
18
  try {
27
19
  const items = ef.getInputData();
@@ -63,9 +55,6 @@ function readTriggerBodyField(ef, itemIndex, field) {
63
55
  return undefined;
64
56
  });
65
57
  }
66
- // ---------------------------------------------------------------------------
67
- // Node
68
- // ---------------------------------------------------------------------------
69
58
  class AitezaWorkflowResult {
70
59
  description = {
71
60
  displayName: 'AITEZA Workflow Result',
@@ -74,12 +63,7 @@ class AitezaWorkflowResult {
74
63
  group: ['output'],
75
64
  version: 1,
76
65
  subtitle: 'Send result to AITEZA',
77
- description: 'Sends the final workflow result back to the AITEZA backend. ' +
78
- 'Reads callbackUrl / executionId / callbackToken from the upstream ' +
79
- 'AITEZA Trigger webhook body and POSTs to ' +
80
- '{callbackUrl}/api/internal/workflow/{executionId}/result. ' +
81
- 'Use this instead of "Respond to Webhook" which is not compatible ' +
82
- 'with custom trigger nodes.',
66
+ description: 'Sends the final workflow result back to Aiteza. Use this node instead of "Respond to Webhook" when your workflow is triggered by an Aiteza Trigger node.',
83
67
  defaults: { name: 'AITEZA Workflow Result' },
84
68
  inputs: ['main'],
85
69
  outputs: ['main'],
@@ -91,7 +75,7 @@ class AitezaWorkflowResult {
91
75
  type: 'json',
92
76
  default: '',
93
77
  placeholder: '={{ $json }}',
94
- description: 'The result payload to send back to AITEZA. Can be a JSON object or an expression that resolves to one. If empty, the entire input item JSON is sent.',
78
+ description: 'The result payload to send back to Aiteza. Can be a JSON object or an expression. If left empty, the entire input item is sent.',
95
79
  },
96
80
  {
97
81
  displayName: 'Status',
@@ -101,21 +85,23 @@ class AitezaWorkflowResult {
101
85
  {
102
86
  name: 'Success',
103
87
  value: 'success',
88
+ description: 'The workflow completed successfully',
104
89
  },
105
90
  {
106
91
  name: 'Error',
107
92
  value: 'error',
93
+ description: 'The workflow encountered an issue',
108
94
  },
109
95
  ],
110
96
  default: 'success',
111
- description: 'The result status to report back to AITEZA',
97
+ description: 'The result status to report back to Aiteza',
112
98
  },
113
99
  {
114
100
  displayName: 'Message',
115
101
  name: 'message',
116
102
  type: 'string',
117
103
  default: '',
118
- placeholder: 'Workflow completed successfully',
104
+ placeholder: 'e.g. Workflow completed successfully',
119
105
  description: 'Optional human-readable message to include with the result',
120
106
  },
121
107
  {
@@ -125,27 +111,36 @@ class AitezaWorkflowResult {
125
111
  placeholder: 'Add Option',
126
112
  default: {},
127
113
  options: [
114
+ {
115
+ displayName: 'Callback Token Override',
116
+ name: 'callbackTokenOverride',
117
+ type: 'string',
118
+ typeOptions: { password: true },
119
+ default: '',
120
+ description: 'Override the callback token read from the Aiteza Trigger',
121
+ },
128
122
  {
129
123
  displayName: 'Callback URL Override',
130
124
  name: 'callbackUrlOverride',
131
125
  type: 'string',
132
126
  default: '',
133
- description: 'Override the callback URL from the AITEZA Trigger. Without trailing slash.',
127
+ placeholder: 'e.g. https://aiteza.example.com',
128
+ description: 'Override the callback URL read from the Aiteza Trigger (without trailing slash)',
134
129
  },
135
130
  {
136
131
  displayName: 'Execution ID Override',
137
132
  name: 'executionIdOverride',
138
133
  type: 'string',
139
134
  default: '',
140
- description: 'Override the execution ID from the AITEZA Trigger',
135
+ placeholder: 'e.g. exec-abc123',
136
+ description: 'Override the execution ID read from the Aiteza Trigger',
141
137
  },
142
138
  {
143
- displayName: 'Callback Token Override',
144
- name: 'callbackTokenOverride',
145
- type: 'string',
146
- typeOptions: { password: true },
147
- default: '',
148
- description: 'Override the callback token from the AITEZA Trigger',
139
+ displayName: 'Fail Workflow on Callback Failure',
140
+ name: 'failOnError',
141
+ type: 'boolean',
142
+ default: true,
143
+ description: 'Whether to stop the workflow if the result callback cannot be delivered. Defaults to true since the result is typically critical.',
149
144
  },
150
145
  {
151
146
  displayName: 'Timeout (ms)',
@@ -153,13 +148,7 @@ class AitezaWorkflowResult {
153
148
  type: 'number',
154
149
  default: 10000,
155
150
  typeOptions: { minValue: 100 },
156
- },
157
- {
158
- displayName: 'Fail Workflow on Error',
159
- name: 'failOnError',
160
- type: 'boolean',
161
- default: true,
162
- description: 'Whether to fail the workflow if the result callback fails. Defaults to true since the result is typically critical.',
151
+ description: 'Maximum time in milliseconds to wait for the callback to complete',
163
152
  },
164
153
  ],
165
154
  },
@@ -193,7 +182,7 @@ class AitezaWorkflowResult {
193
182
  }
194
183
  catch (err) {
195
184
  if (failOnError) {
196
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `"Result Data" is not valid JSON: ${err.message}`, { itemIndex: i });
185
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The 'Result Data' value is not valid JSON: ${err.message}`, { itemIndex: i, description: 'Please provide a valid JSON object in the \'Result Data\' field, or use an expression like ={{ $json }}.' });
197
186
  }
198
187
  resultPayload = { raw: resultDataRaw };
199
188
  }
@@ -216,10 +205,10 @@ class AitezaWorkflowResult {
216
205
  if (!callbackUrl || !executionId) {
217
206
  reportMeta.skipped = true;
218
207
  reportMeta.reason = !callbackUrl
219
- ? 'No callbackUrl on AITEZA Trigger body'
220
- : 'No executionId on AITEZA Trigger body';
208
+ ? 'No callbackUrl on Aiteza Trigger body'
209
+ : 'No executionId on Aiteza Trigger body';
221
210
  if (failOnError) {
222
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Cannot send workflow result: ${reportMeta.reason}`, { itemIndex: i });
211
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Cannot send workflow result: ${reportMeta.reason}`, { itemIndex: i, description: 'Make sure this node is downstream of an Aiteza Trigger node that provides \'callbackUrl\' and \'executionId\' in its body.' });
223
212
  }
224
213
  returnData.push({
225
214
  json: { ...items[i].json, _workflowResult: reportMeta },
@@ -245,9 +234,9 @@ class AitezaWorkflowResult {
245
234
  catch (error) {
246
235
  reportMeta.sent = false;
247
236
  reportMeta.error =
248
- error?.message ?? error?.response?.statusText ?? 'Unknown error';
237
+ error?.message ?? error?.response?.statusText ?? 'The callback could not be delivered';
249
238
  if (failOnError) {
250
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Workflow result callback failed: ${reportMeta.error}`, { itemIndex: i });
239
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Workflow result could not be delivered: ${reportMeta.error}`, { itemIndex: i, description: 'Check that the \'callbackUrl\' is reachable and the callback token is valid.' });
251
240
  }
252
241
  }
253
242
  returnData.push({
@@ -5,11 +5,6 @@ export interface AitezaAuthContext {
5
5
  }
6
6
  export declare function getUpstreamAuthToken(ef: IExecuteFunctions): string | undefined;
7
7
  export declare function getUpstreamBaseUrl(ef: IExecuteFunctions): string | undefined;
8
- /**
9
- * Returns true when the token is a JWT with an `exp` claim that is in the past
10
- * (with a small skew). Returns false when the token is not a parsable JWT or
11
- * has no `exp` (we assume valid in that case – the server is the source of truth).
12
- */
13
8
  export declare function isJwtExpired(token: string | undefined, skewSeconds?: number): boolean;
14
9
  export declare function aitezaApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, endpoint: string, body?: IDataObject | IDataObject[], qs?: IDataObject, extraOpts?: Partial<IHttpRequestOptions>, authCtx?: AitezaAuthContext): Promise<any>;
15
10
  export declare function aitezaApiRequestFullResponse(this: IExecuteFunctions, method: IHttpRequestMethods, endpoint: string, body?: IDataObject, qs?: IDataObject, extraOpts?: Partial<IHttpRequestOptions>, authCtx?: AitezaAuthContext): Promise<{
@@ -16,9 +16,6 @@ exports.loadStandaloneFiles = loadStandaloneFiles;
16
16
  exports.loadStandaloneImages = loadStandaloneImages;
17
17
  exports.validateRequiredField = validateRequiredField;
18
18
  const n8n_workflow_1 = require("n8n-workflow");
19
- // Match the AITEZA Trigger node by its type name. The full node-type string
20
- // includes the package namespace (e.g. "@aiteza/n8n-nodes-aiteza.aitezaTrigger"),
21
- // so we match on the suffix.
22
19
  function isAitezaTriggerType(type) {
23
20
  return typeof type === 'string' && /(^|\.)aitezaTrigger$/i.test(type);
24
21
  }
@@ -47,13 +44,10 @@ function readFromTriggerItems(ef, picker) {
47
44
  }
48
45
  }
49
46
  catch {
50
- // proxy/items unavailable – ignore
51
47
  }
52
48
  return undefined;
53
49
  }
54
50
  function getUpstreamAuthToken(ef) {
55
- // 1. Prefer the token on the immediate input items (cheap and matches the
56
- // documented contract of forwarding `_authToken`).
57
51
  try {
58
52
  const items = ef.getInputData();
59
53
  for (const item of items) {
@@ -63,11 +57,7 @@ function getUpstreamAuthToken(ef) {
63
57
  }
64
58
  }
65
59
  catch {
66
- // getInputData may not be available in every context
67
60
  }
68
- // 2. Fall back to the AITEZA Trigger node's original output. This makes the
69
- // node resilient to intermediate transformations (Set/Edit Fields, Split
70
- // Out, IF, etc.) that strip the `_authToken` field from items.
71
61
  return readFromTriggerItems(ef, (json) => json?._authToken);
72
62
  }
73
63
  function getUpstreamBaseUrl(ef) {
@@ -80,14 +70,11 @@ function getUpstreamBaseUrl(ef) {
80
70
  }
81
71
  }
82
72
  catch {
83
- // ignore
84
73
  }
85
74
  const fromTrigger = readFromTriggerItems(ef, (json) => json?._baseUrl);
86
75
  return fromTrigger ? fromTrigger.replace(/\/+$/, '') : undefined;
87
76
  }
88
- // ---------------------------------------------------------------------------
89
77
  // JWT helpers
90
- // ---------------------------------------------------------------------------
91
78
  function decodeJwtPayload(token) {
92
79
  try {
93
80
  const payload = token.split('.')[1];
@@ -95,9 +82,6 @@ function decodeJwtPayload(token) {
95
82
  return undefined;
96
83
  const normalized = payload.replace(/-/g, '+').replace(/_/g, '/');
97
84
  const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
98
- // atob is available in Node 16+ and the browser; decodes base64 to a
99
- // binary string. JWT payloads are UTF-8 JSON; for the `exp` claim a
100
- // binary-string decode is sufficient.
101
85
  const decoded = globalThis.atob
102
86
  ? globalThis.atob(padded)
103
87
  : '';
@@ -109,11 +93,6 @@ function decodeJwtPayload(token) {
109
93
  return undefined;
110
94
  }
111
95
  }
112
- /**
113
- * Returns true when the token is a JWT with an `exp` claim that is in the past
114
- * (with a small skew). Returns false when the token is not a parsable JWT or
115
- * has no `exp` (we assume valid in that case – the server is the source of truth).
116
- */
117
96
  function isJwtExpired(token, skewSeconds = 30) {
118
97
  if (!token)
119
98
  return true;
@@ -123,9 +102,7 @@ function isJwtExpired(token, skewSeconds = 30) {
123
102
  const nowSeconds = Math.floor(Date.now() / 1000);
124
103
  return payload.exp <= nowSeconds + skewSeconds;
125
104
  }
126
- // ---------------------------------------------------------------------------
127
105
  // Internal helpers
128
- // ---------------------------------------------------------------------------
129
106
  async function resolveBaseUrl(ctx, authCtx) {
130
107
  if (authCtx?.baseUrl)
131
108
  return authCtx.baseUrl;
@@ -142,20 +119,16 @@ async function executeRequest(ctx, options, authCtx) {
142
119
  }
143
120
  return ctx.helpers.httpRequestWithAuthentication.call(ctx, 'aitezaOAuth2Api', options);
144
121
  }
145
- /** Convert API response items to n8n dropdown options. */
146
122
  function toOptions(items, nameField = 'name') {
147
123
  return items.map((item) => ({
148
124
  name: item[nameField] ?? item.id,
149
125
  value: item.id,
150
126
  }));
151
127
  }
152
- /** Extract array from API response (handles both raw arrays and paginated `{ content: [...] }`). */
153
128
  function extractItems(data) {
154
129
  return Array.isArray(data) ? data : data?.content ?? [];
155
130
  }
156
- // ---------------------------------------------------------------------------
157
131
  // Generic authenticated request helper
158
- // ---------------------------------------------------------------------------
159
132
  async function aitezaApiRequest(method, endpoint, body = {}, qs = {}, extraOpts = {}, authCtx) {
160
133
  const baseUrl = await resolveBaseUrl(this, authCtx);
161
134
  const options = {
@@ -164,7 +137,6 @@ async function aitezaApiRequest(method, endpoint, body = {}, qs = {}, extraOpts
164
137
  json: true,
165
138
  ...extraOpts,
166
139
  };
167
- // Only set qs when there are actual values – empty qs can strip URL params.
168
140
  if (Object.keys(qs).length > 0) {
169
141
  options.qs = qs;
170
142
  }
@@ -180,18 +152,16 @@ async function aitezaApiRequest(method, endpoint, body = {}, qs = {}, extraOpts
180
152
  catch (error) {
181
153
  const statusCode = error?.statusCode ?? error?.httpCode ?? error?.response?.status;
182
154
  const messages = {
183
- 401: 'OAuth2 token expired or invalid – check your AITEZA credentials',
184
- 403: 'Insufficient permissions for this resource',
185
- 404: 'Resource not found',
155
+ 401: 'The OAuth2 token is expired or invalid – check your Aiteza credentials',
156
+ 403: 'Your account does not have permission to access this resource',
157
+ 404: 'The requested resource could not be found',
186
158
  };
187
159
  throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
188
- message: messages[statusCode] ?? error.message ?? 'Unknown error',
160
+ message: messages[statusCode] ?? error.message ?? 'The request could not be completed',
189
161
  });
190
162
  }
191
163
  }
192
- // ---------------------------------------------------------------------------
193
164
  // Full-response variant (gives access to response headers, e.g. X-Chat-Id)
194
- // ---------------------------------------------------------------------------
195
165
  async function aitezaApiRequestFullResponse(method, endpoint, body = {}, qs = {}, extraOpts = {}, authCtx) {
196
166
  const baseUrl = await resolveBaseUrl(this, authCtx);
197
167
  const options = {
@@ -212,9 +182,7 @@ async function aitezaApiRequestFullResponse(method, endpoint, body = {}, qs = {}
212
182
  throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
213
183
  }
214
184
  }
215
- // ---------------------------------------------------------------------------
216
185
  // loadOptions helpers
217
- // ---------------------------------------------------------------------------
218
186
  async function loadDatarooms() {
219
187
  try {
220
188
  const data = await aitezaApiRequest.call(this, 'GET', '/api/dataroom/search?q=&sortBy=recentlyUsed');
@@ -271,11 +239,11 @@ async function loadStandaloneImages() {
271
239
  const data = await aitezaApiRequest.call(this, 'GET', '/api/images', {}, { size: 100 });
272
240
  return toOptions(extractItems(data));
273
241
  }
274
- // ---------------------------------------------------------------------------
275
242
  // Validation helper
276
- // ---------------------------------------------------------------------------
277
243
  function validateRequiredField(ef, value, fieldName) {
278
244
  if (value === undefined || value === null || value === '') {
279
- throw new n8n_workflow_1.NodeOperationError(ef.getNode(), `"${fieldName}" is required but was not provided`);
245
+ throw new n8n_workflow_1.NodeOperationError(ef.getNode(), `The '${fieldName}' field is required but was not provided.`, {
246
+ description: `Please fill in the '${fieldName}' field before running this node.`,
247
+ });
280
248
  }
281
249
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiteza/n8n-nodes-aiteza",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "n8n Community Node for the AITEZA REST API (Datarooms, Files, Chat, Search, Workflows)",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",