@aiteza/n8n-nodes-aiteza 0.3.1 → 1.0.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.
@@ -4,7 +4,6 @@ exports.getUpstreamAuthToken = getUpstreamAuthToken;
4
4
  exports.getUpstreamBaseUrl = getUpstreamBaseUrl;
5
5
  exports.isJwtExpired = isJwtExpired;
6
6
  exports.aitezaApiRequest = aitezaApiRequest;
7
- exports.aitezaApiRequestFullResponse = aitezaApiRequestFullResponse;
8
7
  exports.loadDatarooms = loadDatarooms;
9
8
  exports.loadDataroomsOptional = loadDataroomsOptional;
10
9
  exports.loadModels = loadModels;
@@ -16,116 +15,74 @@ exports.loadStandaloneFiles = loadStandaloneFiles;
16
15
  exports.loadStandaloneImages = loadStandaloneImages;
17
16
  exports.validateRequiredField = validateRequiredField;
18
17
  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
- function isAitezaTriggerType(type) {
23
- return typeof type === 'string' && /(^|\.)aitezaTrigger$/i.test(type);
24
- }
25
- function findAitezaTriggerParentName(ef) {
18
+ function readFromTriggerItems(ef, picker) {
26
19
  try {
27
20
  const currentNodeName = ef.getNode().name;
28
21
  const parents = ef.getParentNodes(currentNodeName);
29
- const trigger = parents.find((p) => isAitezaTriggerType(p.type) && !p.disabled);
30
- return trigger?.name;
31
- }
32
- catch {
33
- return undefined;
34
- }
35
- }
36
- function readFromTriggerItems(ef, picker) {
37
- const triggerName = findAitezaTriggerParentName(ef);
38
- if (!triggerName)
39
- return undefined;
40
- try {
22
+ const trigger = parents.find((p) => /(^|\.)aitezaTrigger$/i.test(p.type) && !p.disabled);
23
+ if (!trigger)
24
+ return undefined;
41
25
  const proxy = ef.getWorkflowDataProxy(0);
42
- const triggerItems = proxy.$items(triggerName) ?? [];
26
+ const triggerItems = proxy.$items(trigger.name) ?? [];
43
27
  for (const item of triggerItems) {
44
28
  const value = picker(item?.json);
45
29
  if (value !== undefined && value !== null && value !== '')
46
30
  return value;
47
31
  }
48
32
  }
49
- catch {
50
- // proxy/items unavailable – ignore
51
- }
33
+ catch { }
52
34
  return undefined;
53
35
  }
54
36
  function getUpstreamAuthToken(ef) {
55
- // 1. Prefer the token on the immediate input items (cheap and matches the
56
- // documented contract of forwarding `_authToken`).
57
37
  try {
58
- const items = ef.getInputData();
59
- for (const item of items) {
38
+ for (const item of ef.getInputData()) {
60
39
  const token = item.json?._authToken;
61
40
  if (token)
62
41
  return token;
63
42
  }
64
43
  }
65
- catch {
66
- // getInputData may not be available in every context
67
- }
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.
44
+ catch { }
71
45
  return readFromTriggerItems(ef, (json) => json?._authToken);
72
46
  }
73
47
  function getUpstreamBaseUrl(ef) {
74
48
  try {
75
- const items = ef.getInputData();
76
- for (const item of items) {
49
+ for (const item of ef.getInputData()) {
77
50
  const url = item.json?._baseUrl;
78
51
  if (url)
79
52
  return url.replace(/\/+$/, '');
80
53
  }
81
54
  }
82
- catch {
83
- // ignore
84
- }
85
- const fromTrigger = readFromTriggerItems(ef, (json) => json?._baseUrl);
55
+ catch { }
56
+ const fromTrigger = readFromTriggerItems(ef, (json) => {
57
+ const explicit = json?._baseUrl;
58
+ if (explicit)
59
+ return explicit;
60
+ const body = json?.body ?? {};
61
+ return body?.callbackUrl;
62
+ });
86
63
  return fromTrigger ? fromTrigger.replace(/\/+$/, '') : undefined;
87
64
  }
88
- // ---------------------------------------------------------------------------
89
- // JWT helpers
90
- // ---------------------------------------------------------------------------
91
- function decodeJwtPayload(token) {
65
+ function isJwtExpired(token, skewSeconds = 30) {
66
+ if (!token)
67
+ return true;
92
68
  try {
93
69
  const payload = token.split('.')[1];
94
70
  if (!payload)
95
- return undefined;
71
+ return false;
96
72
  const normalized = payload.replace(/-/g, '+').replace(/_/g, '/');
97
73
  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
- const decoded = globalThis.atob
102
- ? globalThis.atob(padded)
103
- : '';
74
+ const decoded = globalThis.atob ? globalThis.atob(padded) : '';
104
75
  if (!decoded)
105
- return undefined;
106
- return JSON.parse(decoded);
76
+ return false;
77
+ const { exp } = JSON.parse(decoded);
78
+ if (!exp)
79
+ return false;
80
+ return exp <= Math.floor(Date.now() / 1000) + skewSeconds;
107
81
  }
108
82
  catch {
109
- return undefined;
110
- }
111
- }
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
- function isJwtExpired(token, skewSeconds = 30) {
118
- if (!token)
119
- return true;
120
- const payload = decodeJwtPayload(token);
121
- if (!payload?.exp)
122
83
  return false;
123
- const nowSeconds = Math.floor(Date.now() / 1000);
124
- return payload.exp <= nowSeconds + skewSeconds;
84
+ }
125
85
  }
126
- // ---------------------------------------------------------------------------
127
- // Internal helpers
128
- // ---------------------------------------------------------------------------
129
86
  async function resolveBaseUrl(ctx, authCtx) {
130
87
  if (authCtx?.baseUrl)
131
88
  return authCtx.baseUrl;
@@ -134,28 +91,20 @@ async function resolveBaseUrl(ctx, authCtx) {
134
91
  }
135
92
  async function executeRequest(ctx, options, authCtx) {
136
93
  if (authCtx?.triggerToken) {
137
- options.headers = {
138
- ...(options.headers ?? {}),
139
- Authorization: `Bearer ${authCtx.triggerToken}`,
140
- };
94
+ options.headers = { ...(options.headers ?? {}), Authorization: `Bearer ${authCtx.triggerToken}` };
141
95
  return ctx.helpers.httpRequest(options);
142
96
  }
143
97
  return ctx.helpers.httpRequestWithAuthentication.call(ctx, 'aitezaOAuth2Api', options);
144
98
  }
145
- /** Convert API response items to n8n dropdown options. */
146
99
  function toOptions(items, nameField = 'name') {
147
100
  return items.map((item) => ({
148
101
  name: item[nameField] ?? item.id,
149
102
  value: item.id,
150
103
  }));
151
104
  }
152
- /** Extract array from API response (handles both raw arrays and paginated `{ content: [...] }`). */
153
105
  function extractItems(data) {
154
- return Array.isArray(data) ? data : data?.content ?? [];
106
+ return Array.isArray(data) ? data : (data?.content ?? []);
155
107
  }
156
- // ---------------------------------------------------------------------------
157
- // Generic authenticated request helper
158
- // ---------------------------------------------------------------------------
159
108
  async function aitezaApiRequest(method, endpoint, body = {}, qs = {}, extraOpts = {}, authCtx) {
160
109
  const baseUrl = await resolveBaseUrl(this, authCtx);
161
110
  const options = {
@@ -164,15 +113,12 @@ async function aitezaApiRequest(method, endpoint, body = {}, qs = {}, extraOpts
164
113
  json: true,
165
114
  ...extraOpts,
166
115
  };
167
- // Only set qs when there are actual values – empty qs can strip URL params.
168
- if (Object.keys(qs).length > 0) {
116
+ if (Object.keys(qs).length > 0)
169
117
  options.qs = qs;
170
- }
171
- if (method !== 'GET' && method !== 'DELETE') {
172
- options.body = body;
173
- }
174
- else if (method === 'DELETE' && Object.keys(body).length > 0) {
175
- options.body = body;
118
+ if (method !== 'GET') {
119
+ const bodyObj = body;
120
+ if (method !== 'DELETE' || Object.keys(bodyObj).length > 0)
121
+ options.body = bodyObj;
176
122
  }
177
123
  try {
178
124
  return await executeRequest(this, options, authCtx);
@@ -180,41 +126,15 @@ async function aitezaApiRequest(method, endpoint, body = {}, qs = {}, extraOpts
180
126
  catch (error) {
181
127
  const statusCode = error?.statusCode ?? error?.httpCode ?? error?.response?.status;
182
128
  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',
129
+ 401: 'The OAuth2 token is expired or invalid – check your Aiteza credentials',
130
+ 403: 'Your account does not have permission to access this resource',
131
+ 404: 'The requested resource could not be found',
186
132
  };
187
133
  throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
188
- message: messages[statusCode] ?? error.message ?? 'Unknown error',
134
+ message: messages[statusCode] ?? error.message ?? 'The request could not be completed',
189
135
  });
190
136
  }
191
137
  }
192
- // ---------------------------------------------------------------------------
193
- // Full-response variant (gives access to response headers, e.g. X-Chat-Id)
194
- // ---------------------------------------------------------------------------
195
- async function aitezaApiRequestFullResponse(method, endpoint, body = {}, qs = {}, extraOpts = {}, authCtx) {
196
- const baseUrl = await resolveBaseUrl(this, authCtx);
197
- const options = {
198
- method,
199
- url: `${baseUrl}${endpoint}`,
200
- qs,
201
- json: true,
202
- returnFullResponse: true,
203
- ...extraOpts,
204
- };
205
- if (method !== 'GET') {
206
- options.body = body;
207
- }
208
- try {
209
- return (await executeRequest(this, options, authCtx));
210
- }
211
- catch (error) {
212
- throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
213
- }
214
- }
215
- // ---------------------------------------------------------------------------
216
- // loadOptions helpers
217
- // ---------------------------------------------------------------------------
218
138
  async function loadDatarooms() {
219
139
  try {
220
140
  const data = await aitezaApiRequest.call(this, 'GET', '/api/dataroom/search?q=&sortBy=recentlyUsed');
@@ -271,11 +191,10 @@ async function loadStandaloneImages() {
271
191
  const data = await aitezaApiRequest.call(this, 'GET', '/api/images', {}, { size: 100 });
272
192
  return toOptions(extractItems(data));
273
193
  }
274
- // ---------------------------------------------------------------------------
275
- // Validation helper
276
- // ---------------------------------------------------------------------------
277
194
  function validateRequiredField(ef, value, fieldName) {
278
195
  if (value === undefined || value === null || value === '') {
279
- throw new n8n_workflow_1.NodeOperationError(ef.getNode(), `"${fieldName}" is required but was not provided`);
196
+ throw new n8n_workflow_1.NodeOperationError(ef.getNode(), `The '${fieldName}' field is required but was not provided.`, {
197
+ description: `Please fill in the '${fieldName}' field before running this node.`,
198
+ });
280
199
  }
281
200
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiteza/n8n-nodes-aiteza",
3
- "version": "0.3.1",
3
+ "version": "1.0.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",