n8n-nodes-cribops 0.1.17 → 0.1.20

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.
@@ -25,6 +25,13 @@ class CribopsApi {
25
25
  required: true,
26
26
  description: 'Base URL of the Cribops API',
27
27
  },
28
+ {
29
+ displayName: 'Organization ID',
30
+ name: 'organizationId',
31
+ type: 'string',
32
+ default: '',
33
+ description: 'Organization ID (optional - if not provided, will use the default organization for the API token)',
34
+ },
28
35
  ];
29
36
  authenticate = {
30
37
  type: 'generic',
@@ -1,9 +1,9 @@
1
- import { INodeType, INodeTypeDescription, ITriggerFunctions, ITriggerResponse, IWebhookFunctions, IWebhookResponseData, IHookFunctions, ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
1
+ import { INodeType, INodeTypeDescription, IWebhookFunctions, IWebhookResponseData, IHookFunctions, ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
2
2
  export declare class CribopsTrigger implements INodeType {
3
3
  description: INodeTypeDescription;
4
4
  methods: {
5
5
  loadOptions: {
6
- getAgents(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
6
+ getWebhooks(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
7
7
  };
8
8
  };
9
9
  webhookMethods: {
@@ -13,6 +13,5 @@ export declare class CribopsTrigger implements INodeType {
13
13
  delete(this: IHookFunctions): Promise<boolean>;
14
14
  };
15
15
  };
16
- trigger(this: ITriggerFunctions): Promise<ITriggerResponse>;
17
16
  webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
18
17
  }
@@ -10,7 +10,7 @@ class CribopsTrigger {
10
10
  icon: 'file:cribopstrigger.svg',
11
11
  group: ['trigger'],
12
12
  version: 1,
13
- description: 'Triggers when receiving messages from Cribops agents',
13
+ description: 'Triggers when receiving messages via Cribops webhook',
14
14
  defaults: {
15
15
  name: 'Cribops Trigger',
16
16
  },
@@ -27,92 +27,21 @@ class CribopsTrigger {
27
27
  name: 'default',
28
28
  httpMethod: 'POST',
29
29
  responseMode: 'onReceived',
30
- // Dynamic path to trigger UUID generation
31
- path: '={{$parameter["agentId"]}}',
32
- isFullPath: true,
30
+ path: '={{$parameter["webhookId"]}}',
31
+ isFullPath: false,
33
32
  },
34
33
  ],
35
- polling: true,
36
34
  properties: [
37
35
  {
38
- displayName: 'Trigger Mode',
39
- name: 'triggerMode',
36
+ displayName: 'Webhook Name or ID',
37
+ name: 'webhookId',
40
38
  type: 'options',
41
- options: [
42
- {
43
- name: 'Polling',
44
- value: 'polling',
45
- description: 'Poll the queue for messages at regular intervals',
46
- },
47
- {
48
- name: 'Webhook',
49
- value: 'webhook',
50
- description: 'Receive messages via webhook',
51
- },
52
- ],
53
- default: 'polling',
54
- description: 'How to receive messages from Cribops',
55
- },
56
- {
57
- displayName: 'Tenant ID',
58
- name: 'tenantId',
59
- type: 'string',
60
39
  required: true,
61
- default: '',
62
- description: 'The tenant ID for your Cribops organization',
63
- },
64
- {
65
- displayName: 'Agent Name or ID',
66
- name: 'agentId',
67
- type: 'options',
68
- required: true,
69
- displayOptions: {
70
- show: {
71
- triggerMode: ['webhook'],
72
- },
73
- },
74
40
  typeOptions: {
75
- loadOptionsMethod: 'getAgents',
41
+ loadOptionsMethod: 'getWebhooks',
76
42
  },
77
43
  default: '',
78
- description: 'The Cribops agent to receive messages from. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
79
- },
80
- {
81
- displayName: 'Queue Name',
82
- name: 'queueName',
83
- type: 'string',
84
- displayOptions: {
85
- show: {
86
- triggerMode: ['polling'],
87
- },
88
- },
89
- default: '',
90
- placeholder: 'e.g., stripe_events',
91
- description: 'Specific queue to poll (optional). Leave empty to poll all queues.',
92
- },
93
- {
94
- displayName: 'Poll Interval',
95
- name: 'pollInterval',
96
- type: 'number',
97
- displayOptions: {
98
- show: {
99
- triggerMode: ['polling'],
100
- },
101
- },
102
- default: 30,
103
- description: 'How often to poll for messages in seconds',
104
- },
105
- {
106
- displayName: 'Batch Size',
107
- name: 'batchSize',
108
- type: 'number',
109
- displayOptions: {
110
- show: {
111
- triggerMode: ['polling'],
112
- },
113
- },
114
- default: 10,
115
- description: 'Number of messages to retrieve per poll (max 100)',
44
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
116
45
  },
117
46
  {
118
47
  displayName: 'Event Types',
@@ -134,8 +63,13 @@ class CribopsTrigger {
134
63
  value: 'file_attachment',
135
64
  description: 'File attachments',
136
65
  },
66
+ {
67
+ name: 'System Event',
68
+ value: 'system_event',
69
+ description: 'System events and notifications',
70
+ },
137
71
  ],
138
- default: ['user_message'],
72
+ default: ['user_message', 'agent_response'],
139
73
  description: 'Types of events to trigger on',
140
74
  },
141
75
  {
@@ -153,7 +87,14 @@ class CribopsTrigger {
153
87
  password: true,
154
88
  },
155
89
  default: '',
156
- description: 'Secret token for webhook authentication',
90
+ description: 'Secret token for webhook signature validation',
91
+ },
92
+ {
93
+ displayName: 'Include Headers',
94
+ name: 'includeHeaders',
95
+ type: 'boolean',
96
+ default: false,
97
+ description: 'Whether to include the webhook headers in the output',
157
98
  },
158
99
  ],
159
100
  },
@@ -161,22 +102,28 @@ class CribopsTrigger {
161
102
  };
162
103
  methods = {
163
104
  loadOptions: {
164
- async getAgents() {
105
+ async getWebhooks() {
165
106
  const credentials = await this.getCredentials('cribopsApi');
166
107
  const cribopsHttp = new CribopsHttp_1.CribopsHttp({
167
108
  baseUrl: credentials.baseUrl,
168
109
  apiToken: credentials.apiToken,
169
110
  });
170
111
  try {
171
- const agents = await cribopsHttp.getAgents();
172
- return agents.map((agent) => ({
173
- name: agent.name,
174
- value: agent.id,
175
- description: `ID: ${agent.id}`,
112
+ // Get organization ID from credentials if provided
113
+ const organizationId = credentials.organizationId;
114
+ const webhooks = await cribopsHttp.getWebhooks(organizationId);
115
+ // Filter for N8N type webhooks that are active and not linked
116
+ const availableWebhooks = webhooks.filter((webhook) => webhook.type === 'N8N' &&
117
+ webhook.status === 'active' &&
118
+ !webhook.linked_workflow_id);
119
+ return availableWebhooks.map((webhook) => ({
120
+ name: webhook.name,
121
+ value: webhook.id,
122
+ description: webhook.description || `Type: ${webhook.type}`,
176
123
  }));
177
124
  }
178
125
  catch (error) {
179
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to load agents: ${error instanceof Error ? error.message : String(error)}`);
126
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to load webhooks: ${error instanceof Error ? error.message : String(error)}`);
180
127
  }
181
128
  },
182
129
  },
@@ -184,52 +131,38 @@ class CribopsTrigger {
184
131
  webhookMethods = {
185
132
  default: {
186
133
  async checkExists() {
187
- const webhookUrl = this.getNodeWebhookUrl('default');
188
- const agentId = this.getNodeParameter('agentId');
189
- const credentials = await this.getCredentials('cribopsApi');
190
- const cribopsHttp = new CribopsHttp_1.CribopsHttp({
191
- baseUrl: credentials.baseUrl,
192
- apiToken: credentials.apiToken,
193
- });
194
- try {
195
- // Check if webhook exists for this agent
196
- // This would need to be implemented in your Cribops API
197
- // For now, return false to always create
198
- return false;
199
- }
200
- catch (error) {
201
- return false;
202
- }
134
+ // Always return false to create webhook registration
135
+ // The actual webhook already exists in Cribops backend
136
+ return false;
203
137
  },
204
138
  async create() {
205
139
  const webhookUrl = this.getNodeWebhookUrl('default');
206
- const agentId = this.getNodeParameter('agentId');
207
- const eventTypes = this.getNodeParameter('eventTypes', []);
208
- const additionalFields = this.getNodeParameter('additionalFields', {});
140
+ const webhookId = this.getNodeParameter('webhookId');
209
141
  const credentials = await this.getCredentials('cribopsApi');
142
+ const workflowId = this.getWorkflow().id;
143
+ const workflowName = this.getWorkflow().name;
210
144
  const cribopsHttp = new CribopsHttp_1.CribopsHttp({
211
145
  baseUrl: credentials.baseUrl,
212
146
  apiToken: credentials.apiToken,
213
147
  });
214
148
  try {
215
- // Register webhook with Cribops
216
- // This is a placeholder - implement actual API call
149
+ // Link this workflow to the webhook entity
217
150
  const body = {
218
- url: webhookUrl,
219
- agent_id: agentId,
220
- event_types: eventTypes,
221
- secret: additionalFields.secretToken || undefined,
151
+ workflow_id: workflowId,
152
+ webhook_url: webhookUrl,
153
+ test_webhook_url: webhookUrl.replace('/webhook/', '/webhook-test/'),
154
+ workflow_name: workflowName || 'Unnamed Workflow',
222
155
  };
223
- // TODO: Make actual API call to register webhook
224
- // await cribopsHttp.registerWebhook(agentId, body);
156
+ // Link the n8n workflow to the webhook entity
157
+ await cribopsHttp.request('POST', `/api/v1/webhooks/${webhookId}/link`, body);
225
158
  // Store webhook data for later use
226
159
  const webhookData = this.getWorkflowStaticData('node');
227
- webhookData.webhookId = `webhook_${agentId}_${Date.now()}`;
228
- webhookData.agentId = agentId;
160
+ webhookData.webhookId = webhookId;
161
+ webhookData.webhookUrl = webhookUrl;
229
162
  return true;
230
163
  }
231
164
  catch (error) {
232
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to register webhook: ${error instanceof Error ? error.message : String(error)}`);
165
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to link webhook: ${error instanceof Error ? error.message : String(error)}`);
233
166
  }
234
167
  },
235
168
  async delete() {
@@ -243,118 +176,34 @@ class CribopsTrigger {
243
176
  apiToken: credentials.apiToken,
244
177
  });
245
178
  try {
246
- // Unregister webhook from Cribops
247
- // TODO: Make actual API call to unregister webhook
248
- // await cribopsHttp.deleteWebhook(webhookData.agentId, webhookData.webhookId);
179
+ // Unlink the workflow from the webhook entity
180
+ await cribopsHttp.request('DELETE', `/api/v1/webhooks/${webhookData.webhookId}/link`);
249
181
  delete webhookData.webhookId;
250
- delete webhookData.agentId;
182
+ delete webhookData.webhookUrl;
251
183
  return true;
252
184
  }
253
185
  catch (error) {
254
- return false;
186
+ // Log error but don't fail
187
+ console.error('Failed to unlink webhook:', error);
188
+ return true;
255
189
  }
256
190
  },
257
191
  },
258
192
  };
259
- async trigger() {
260
- const triggerMode = this.getNodeParameter('triggerMode');
261
- if (triggerMode === 'webhook') {
262
- // Webhook mode - minimal implementation as webhooks are handled by webhook() method
263
- return {
264
- closeFunction: async () => { },
265
- manualTriggerFunction: async () => {
266
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'This node only works with webhooks in webhook mode. Please activate the workflow.');
267
- },
268
- };
269
- }
270
- // Polling mode
271
- const credentials = await this.getCredentials('cribopsApi');
272
- const tenantId = this.getNodeParameter('tenantId');
273
- const pollInterval = this.getNodeParameter('pollInterval', 30);
274
- const batchSize = this.getNodeParameter('batchSize', 10);
275
- const queueName = this.getNodeParameter('queueName', '') || undefined;
276
- const cribopsHttp = new CribopsHttp_1.CribopsHttp({
277
- baseUrl: credentials.baseUrl,
278
- apiToken: credentials.apiToken,
279
- });
280
- let intervalId;
281
- const poll = async () => {
282
- try {
283
- const messages = await cribopsHttp.pollQueue(tenantId, batchSize, queueName);
284
- if (messages.length > 0) {
285
- const messageIds = messages.map(msg => msg.id);
286
- // Process each message
287
- for (const message of messages) {
288
- // Parse the webhook data if it's JSON
289
- let parsedData = message.data.data;
290
- try {
291
- parsedData = JSON.parse(message.data.data);
292
- }
293
- catch (e) {
294
- // Keep as string if not valid JSON
295
- }
296
- // Emit the message
297
- this.emit([
298
- [
299
- {
300
- json: {
301
- id: message.id,
302
- correlation_id: message.correlation_id,
303
- queue_name: message.queue_name,
304
- data: parsedData,
305
- headers: message.data.headers,
306
- params: message.data.params,
307
- inserted_at: message.inserted_at,
308
- // Extract useful fields from headers
309
- tenant_id: message.data.headers['x-cribops-tenant-id'] || tenantId,
310
- path: message.data.headers['x-cribops-path'],
311
- },
312
- },
313
- ],
314
- ]);
315
- }
316
- // Acknowledge messages after processing
317
- try {
318
- await cribopsHttp.acknowledgeMessages(tenantId, messageIds);
319
- }
320
- catch (ackError) {
321
- console.error('Failed to acknowledge messages:', ackError);
322
- }
323
- }
324
- }
325
- catch (error) {
326
- console.error('Polling error:', error);
327
- // Don't throw - continue polling
328
- }
329
- };
330
- // Start polling
331
- poll(); // Initial poll
332
- intervalId = setInterval(poll, pollInterval * 1000);
333
- // Manual trigger function for testing
334
- const manualTriggerFunction = async () => {
335
- await poll();
336
- };
337
- // Cleanup function
338
- const closeFunction = async () => {
339
- if (intervalId) {
340
- clearInterval(intervalId);
341
- }
342
- };
343
- return {
344
- closeFunction,
345
- manualTriggerFunction,
346
- };
347
- }
348
193
  async webhook() {
349
194
  const body = this.getBodyData();
350
195
  const headers = this.getHeaderData();
351
196
  const eventTypes = this.getNodeParameter('eventTypes', []);
352
197
  const additionalFields = this.getNodeParameter('additionalFields', {});
353
- const agentId = this.getNodeParameter('agentId');
198
+ const webhookId = this.getNodeParameter('webhookId');
354
199
  // Validate secret token if provided
355
200
  if (additionalFields.secretToken) {
356
- const receivedToken = headers['x-cribops-signature'] || headers['authorization'];
357
- if (receivedToken !== additionalFields.secretToken) {
201
+ // Check for signature in headers (could be HMAC signature or bearer token)
202
+ const signature = headers['x-cribops-signature'] || headers['x-webhook-signature'];
203
+ const authHeader = headers['authorization'];
204
+ // You can implement HMAC signature validation here if needed
205
+ // For now, simple token comparison
206
+ if (additionalFields.secretToken !== signature && `Bearer ${additionalFields.secretToken}` !== authHeader) {
358
207
  return {
359
208
  webhookResponse: {
360
209
  status: 401,
@@ -363,8 +212,9 @@ class CribopsTrigger {
363
212
  };
364
213
  }
365
214
  }
366
- // Filter by event type
367
- if (eventTypes.length > 0 && body.type && !eventTypes.includes(body.type)) {
215
+ // Filter by event type if specified
216
+ const eventType = body.event_type || body.type || body.eventType;
217
+ if (eventTypes.length > 0 && eventType && !eventTypes.includes(eventType)) {
368
218
  return {
369
219
  webhookResponse: {
370
220
  status: 200,
@@ -372,27 +222,40 @@ class CribopsTrigger {
372
222
  },
373
223
  };
374
224
  }
375
- // Enrich the output with agent_id and other useful metadata
225
+ // Prepare output data with all relevant fields
376
226
  const outputData = {
227
+ // Core webhook data
228
+ webhook_id: webhookId,
229
+ event_type: eventType,
230
+ // Message content
231
+ message: body.message || body.content || body.text,
232
+ // Conversation/thread tracking
233
+ conversation_id: body.conversation_id || body.conversationId || body.thread_id || body.threadId,
234
+ // User/agent identification
235
+ user_id: body.user_id || body.userId || body.from_user || body.fromUser,
236
+ agent_id: body.agent_id || body.agentId || body.to_agent || body.toAgent,
237
+ // Response handling
238
+ response_webhook: body.response_webhook || body.responseWebhook || body.callback_url || body.callbackUrl,
239
+ // Metadata
240
+ metadata: body.metadata || {},
241
+ // File attachments if any
242
+ attachments: body.attachments || body.files || [],
243
+ // Timestamp
244
+ timestamp: body.timestamp || body.created_at || body.createdAt || new Date().toISOString(),
245
+ // Include any other fields from the webhook
377
246
  ...body,
378
- agent_id: agentId,
379
- // Ensure conversation_id is available (handle different field names)
380
- conversation_id: body.conversation_id || body.conversationId || body.thread_id,
381
- // Ensure response_webhook is available if it exists
382
- response_webhook: body.response_webhook || body.responseWebhook || body.callback_url,
383
247
  };
384
- // Log what we're outputting for debugging
385
- console.log('CribopsTrigger output:', JSON.stringify(outputData, null, 2));
248
+ // Include headers if requested
249
+ const workflowData = additionalFields.includeHeaders
250
+ ? { json: outputData, headers }
251
+ : { json: outputData };
386
252
  // Return the data to the workflow
387
253
  return {
388
- workflowData: [
389
- [
390
- {
391
- json: outputData,
392
- headers,
393
- },
394
- ],
395
- ],
254
+ workflowData: [[workflowData]],
255
+ webhookResponse: {
256
+ status: 200,
257
+ body: { received: true },
258
+ },
396
259
  };
397
260
  }
398
261
  }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-cribops",
3
- "version": "0.1.17",
3
+ "version": "0.1.20",
4
4
  "description": "n8n community node for Cribops AI platform integration",
5
5
  "keywords": [
6
6
  "n8n-community-node-package"
@@ -1,4 +1,4 @@
1
- import { IDataObject } from 'n8n-workflow';
1
+ import { IDataObject, IHttpRequestMethods, IHttpRequestOptions } from 'n8n-workflow';
2
2
  export interface CribopsHttpConfig {
3
3
  baseUrl: string;
4
4
  apiToken: string;
@@ -37,6 +37,18 @@ export interface CribopsQueueMessage {
37
37
  };
38
38
  inserted_at: string;
39
39
  }
40
+ export interface CribopsWebhookEntity {
41
+ id: string;
42
+ name: string;
43
+ description?: string;
44
+ type: 'N8N' | 'GHL_API' | 'GENERIC';
45
+ status: 'active' | 'inactive';
46
+ linked_workflow_id?: string;
47
+ linked_workflow_name?: string;
48
+ organization_id: string;
49
+ created_at: string;
50
+ updated_at: string;
51
+ }
40
52
  export declare class CribopsHttp {
41
53
  private config;
42
54
  constructor(config: CribopsHttpConfig);
@@ -57,4 +69,13 @@ export declare class CribopsHttp {
57
69
  status: string;
58
70
  updated_count: number;
59
71
  }>;
72
+ getWebhooks(organizationId?: string): Promise<CribopsWebhookEntity[]>;
73
+ linkWebhook(webhookId: string, linkData: {
74
+ workflow_id: string;
75
+ webhook_url: string;
76
+ test_webhook_url: string;
77
+ workflow_name: string;
78
+ }): Promise<any>;
79
+ unlinkWebhook(webhookId: string): Promise<any>;
80
+ request<T = any>(method: IHttpRequestMethods, endpoint: string, data?: IDataObject, options?: Partial<IHttpRequestOptions>): Promise<T>;
60
81
  }
@@ -10,7 +10,21 @@ class CribopsHttp {
10
10
  };
11
11
  }
12
12
  async makeRequest(method, endpoint, data, options) {
13
- const url = `${this.config.baseUrl}${endpoint}`;
13
+ let url = `${this.config.baseUrl}${endpoint}`;
14
+ // Handle query parameters
15
+ if (method === 'GET' && data?.params) {
16
+ const params = new URLSearchParams();
17
+ Object.entries(data.params).forEach(([key, value]) => {
18
+ if (value !== undefined && value !== null) {
19
+ params.append(key, String(value));
20
+ }
21
+ });
22
+ const queryString = params.toString();
23
+ if (queryString) {
24
+ url += `?${queryString}`;
25
+ }
26
+ delete data.params;
27
+ }
14
28
  const requestHeaders = {
15
29
  'Authorization': `Bearer ${this.config.apiToken}`,
16
30
  'Content-Type': 'application/json',
@@ -25,7 +39,7 @@ class CribopsHttp {
25
39
  const response = await fetch(url, {
26
40
  method,
27
41
  headers: requestHeaders,
28
- body: data ? JSON.stringify(data) : undefined,
42
+ body: (method !== 'GET' && data) ? JSON.stringify(data) : undefined,
29
43
  });
30
44
  if (!response.ok) {
31
45
  const errorText = await response.text();
@@ -144,5 +158,36 @@ class CribopsHttp {
144
158
  throw new Error(`Failed to mark messages as failed for tenant ${tenantId}: ${error}`);
145
159
  }
146
160
  }
161
+ // Webhook-specific methods
162
+ async getWebhooks(organizationId) {
163
+ try {
164
+ const params = organizationId ? { params: { organization_id: organizationId } } : undefined;
165
+ const response = await this.makeRequest('GET', '/api/v1/webhooks', params);
166
+ return response.data || [];
167
+ }
168
+ catch (error) {
169
+ throw new Error(`Failed to fetch webhooks: ${error}`);
170
+ }
171
+ }
172
+ async linkWebhook(webhookId, linkData) {
173
+ try {
174
+ return await this.makeRequest('POST', `/api/v1/webhooks/${webhookId}/link`, linkData);
175
+ }
176
+ catch (error) {
177
+ throw new Error(`Failed to link webhook: ${error}`);
178
+ }
179
+ }
180
+ async unlinkWebhook(webhookId) {
181
+ try {
182
+ return await this.makeRequest('DELETE', `/api/v1/webhooks/${webhookId}/link`);
183
+ }
184
+ catch (error) {
185
+ throw new Error(`Failed to unlink webhook: ${error}`);
186
+ }
187
+ }
188
+ // Generic request method for custom API calls
189
+ async request(method, endpoint, data, options) {
190
+ return this.makeRequest(method, endpoint, data, options);
191
+ }
147
192
  }
148
193
  exports.CribopsHttp = CribopsHttp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-cribops",
3
- "version": "0.1.17",
3
+ "version": "0.1.20",
4
4
  "description": "n8n community node for Cribops AI platform integration",
5
5
  "keywords": [
6
6
  "n8n-community-node-package"