n8n-nodes-cribops 0.1.5
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.
- package/LICENSE +21 -0
- package/README.md +319 -0
- package/dist/credentials/CribopsApi.credentials.d.ts +9 -0
- package/dist/credentials/CribopsApi.credentials.js +69 -0
- package/dist/nodes/Cribops/Cribops.node.d.ts +10 -0
- package/dist/nodes/Cribops/Cribops.node.js +568 -0
- package/dist/nodes/Cribops/cribops.svg +7 -0
- package/dist/nodes/CribopsTrigger/CribopsTrigger.node.d.ts +18 -0
- package/dist/nodes/CribopsTrigger/CribopsTrigger.node.js +248 -0
- package/dist/nodes/CribopsTrigger/cribopstrigger.svg +8 -0
- package/dist/package.json +59 -0
- package/dist/utils/CribopsHttp.d.ts +40 -0
- package/dist/utils/CribopsHttp.js +116 -0
- package/dist/utils/FileUtils.d.ts +36 -0
- package/dist/utils/FileUtils.js +165 -0
- package/package.json +59 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CribopsTrigger = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const CribopsHttp_1 = require("../../utils/CribopsHttp");
|
|
6
|
+
class CribopsTrigger {
|
|
7
|
+
description = {
|
|
8
|
+
displayName: 'Cribops Trigger',
|
|
9
|
+
name: 'cribopsTrigger',
|
|
10
|
+
icon: 'file:cribopstrigger.svg',
|
|
11
|
+
group: ['trigger'],
|
|
12
|
+
version: 1,
|
|
13
|
+
description: 'Triggers when receiving messages from Cribops agents',
|
|
14
|
+
defaults: {
|
|
15
|
+
name: 'Cribops Trigger',
|
|
16
|
+
},
|
|
17
|
+
inputs: [],
|
|
18
|
+
outputs: ["main" /* NodeConnectionType.Main */],
|
|
19
|
+
credentials: [
|
|
20
|
+
{
|
|
21
|
+
name: 'cribopsApi',
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
webhooks: [
|
|
26
|
+
{
|
|
27
|
+
name: 'default',
|
|
28
|
+
httpMethod: 'POST',
|
|
29
|
+
responseMode: 'onReceived',
|
|
30
|
+
// Dynamic path to trigger UUID generation
|
|
31
|
+
path: '={{$parameter["agentId"]}}',
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
properties: [
|
|
35
|
+
{
|
|
36
|
+
displayName: 'Agent Name or ID',
|
|
37
|
+
name: 'agentId',
|
|
38
|
+
type: 'options',
|
|
39
|
+
required: true,
|
|
40
|
+
typeOptions: {
|
|
41
|
+
loadOptionsMethod: 'getAgents',
|
|
42
|
+
},
|
|
43
|
+
default: '',
|
|
44
|
+
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>.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
displayName: 'Event Types',
|
|
48
|
+
name: 'eventTypes',
|
|
49
|
+
type: 'multiOptions',
|
|
50
|
+
options: [
|
|
51
|
+
{
|
|
52
|
+
name: 'Agent Response',
|
|
53
|
+
value: 'agent_response',
|
|
54
|
+
description: 'Responses from agents',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'User Message',
|
|
58
|
+
value: 'user_message',
|
|
59
|
+
description: 'Messages from users',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'File Attachment',
|
|
63
|
+
value: 'file_attachment',
|
|
64
|
+
description: 'File attachments',
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
default: ['user_message'],
|
|
68
|
+
description: 'Types of events to trigger on',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
displayName: 'Additional Fields',
|
|
72
|
+
name: 'additionalFields',
|
|
73
|
+
type: 'collection',
|
|
74
|
+
placeholder: 'Add Field',
|
|
75
|
+
default: {},
|
|
76
|
+
options: [
|
|
77
|
+
{
|
|
78
|
+
displayName: 'Secret Token',
|
|
79
|
+
name: 'secretToken',
|
|
80
|
+
type: 'string',
|
|
81
|
+
typeOptions: {
|
|
82
|
+
password: true,
|
|
83
|
+
},
|
|
84
|
+
default: '',
|
|
85
|
+
description: 'Secret token for webhook authentication',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
methods = {
|
|
92
|
+
loadOptions: {
|
|
93
|
+
async getAgents() {
|
|
94
|
+
const credentials = await this.getCredentials('cribopsApi');
|
|
95
|
+
const cribopsHttp = new CribopsHttp_1.CribopsHttp({
|
|
96
|
+
baseUrl: credentials.baseUrl,
|
|
97
|
+
apiToken: credentials.apiToken,
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
const agents = await cribopsHttp.getAgents();
|
|
101
|
+
return agents.map((agent) => ({
|
|
102
|
+
name: agent.name,
|
|
103
|
+
value: agent.id,
|
|
104
|
+
description: `ID: ${agent.id}`,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to load agents: ${error instanceof Error ? error.message : String(error)}`);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
webhookMethods = {
|
|
114
|
+
default: {
|
|
115
|
+
async checkExists() {
|
|
116
|
+
const webhookUrl = this.getNodeWebhookUrl('default');
|
|
117
|
+
const agentId = this.getNodeParameter('agentId');
|
|
118
|
+
const credentials = await this.getCredentials('cribopsApi');
|
|
119
|
+
const cribopsHttp = new CribopsHttp_1.CribopsHttp({
|
|
120
|
+
baseUrl: credentials.baseUrl,
|
|
121
|
+
apiToken: credentials.apiToken,
|
|
122
|
+
});
|
|
123
|
+
try {
|
|
124
|
+
// Check if webhook exists for this agent
|
|
125
|
+
// This would need to be implemented in your Cribops API
|
|
126
|
+
// For now, return false to always create
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
async create() {
|
|
134
|
+
const webhookUrl = this.getNodeWebhookUrl('default');
|
|
135
|
+
const agentId = this.getNodeParameter('agentId');
|
|
136
|
+
const eventTypes = this.getNodeParameter('eventTypes', []);
|
|
137
|
+
const additionalFields = this.getNodeParameter('additionalFields', {});
|
|
138
|
+
const credentials = await this.getCredentials('cribopsApi');
|
|
139
|
+
const cribopsHttp = new CribopsHttp_1.CribopsHttp({
|
|
140
|
+
baseUrl: credentials.baseUrl,
|
|
141
|
+
apiToken: credentials.apiToken,
|
|
142
|
+
});
|
|
143
|
+
try {
|
|
144
|
+
// Register webhook with Cribops
|
|
145
|
+
// This is a placeholder - implement actual API call
|
|
146
|
+
const body = {
|
|
147
|
+
url: webhookUrl,
|
|
148
|
+
agent_id: agentId,
|
|
149
|
+
event_types: eventTypes,
|
|
150
|
+
secret: additionalFields.secretToken || undefined,
|
|
151
|
+
};
|
|
152
|
+
// TODO: Make actual API call to register webhook
|
|
153
|
+
// await cribopsHttp.registerWebhook(agentId, body);
|
|
154
|
+
// Store webhook data for later use
|
|
155
|
+
const webhookData = this.getWorkflowStaticData('node');
|
|
156
|
+
webhookData.webhookId = `webhook_${agentId}_${Date.now()}`;
|
|
157
|
+
webhookData.agentId = agentId;
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to register webhook: ${error instanceof Error ? error.message : String(error)}`);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
async delete() {
|
|
165
|
+
const webhookData = this.getWorkflowStaticData('node');
|
|
166
|
+
const credentials = await this.getCredentials('cribopsApi');
|
|
167
|
+
if (!webhookData.webhookId) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
const cribopsHttp = new CribopsHttp_1.CribopsHttp({
|
|
171
|
+
baseUrl: credentials.baseUrl,
|
|
172
|
+
apiToken: credentials.apiToken,
|
|
173
|
+
});
|
|
174
|
+
try {
|
|
175
|
+
// Unregister webhook from Cribops
|
|
176
|
+
// TODO: Make actual API call to unregister webhook
|
|
177
|
+
// await cribopsHttp.deleteWebhook(webhookData.agentId, webhookData.webhookId);
|
|
178
|
+
delete webhookData.webhookId;
|
|
179
|
+
delete webhookData.agentId;
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
async trigger() {
|
|
189
|
+
// Minimal implementation - webhooks are handled by webhook() method
|
|
190
|
+
return {
|
|
191
|
+
closeFunction: async () => { },
|
|
192
|
+
manualTriggerFunction: async () => {
|
|
193
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'This node only works with webhooks. Please activate the workflow.');
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async webhook() {
|
|
198
|
+
const body = this.getBodyData();
|
|
199
|
+
const headers = this.getHeaderData();
|
|
200
|
+
const eventTypes = this.getNodeParameter('eventTypes', []);
|
|
201
|
+
const additionalFields = this.getNodeParameter('additionalFields', {});
|
|
202
|
+
const agentId = this.getNodeParameter('agentId');
|
|
203
|
+
// Validate secret token if provided
|
|
204
|
+
if (additionalFields.secretToken) {
|
|
205
|
+
const receivedToken = headers['x-cribops-signature'] || headers['authorization'];
|
|
206
|
+
if (receivedToken !== additionalFields.secretToken) {
|
|
207
|
+
return {
|
|
208
|
+
webhookResponse: {
|
|
209
|
+
status: 401,
|
|
210
|
+
body: { error: 'Unauthorized' },
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Filter by event type
|
|
216
|
+
if (eventTypes.length > 0 && body.type && !eventTypes.includes(body.type)) {
|
|
217
|
+
return {
|
|
218
|
+
webhookResponse: {
|
|
219
|
+
status: 200,
|
|
220
|
+
body: { received: true, filtered: true },
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
// Enrich the output with agent_id and other useful metadata
|
|
225
|
+
const outputData = {
|
|
226
|
+
...body,
|
|
227
|
+
agent_id: agentId,
|
|
228
|
+
// Ensure conversation_id is available (handle different field names)
|
|
229
|
+
conversation_id: body.conversation_id || body.conversationId || body.thread_id,
|
|
230
|
+
// Ensure response_webhook is available if it exists
|
|
231
|
+
response_webhook: body.response_webhook || body.responseWebhook || body.callback_url,
|
|
232
|
+
};
|
|
233
|
+
// Log what we're outputting for debugging
|
|
234
|
+
console.log('CribopsTrigger output:', JSON.stringify(outputData, null, 2));
|
|
235
|
+
// Return the data to the workflow
|
|
236
|
+
return {
|
|
237
|
+
workflowData: [
|
|
238
|
+
[
|
|
239
|
+
{
|
|
240
|
+
json: outputData,
|
|
241
|
+
headers,
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
exports.CribopsTrigger = CribopsTrigger;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="60" height="60" rx="12" fill="#059669"/>
|
|
3
|
+
<path d="M15 20C15 17.7909 16.7909 16 19 16H41C43.2091 16 45 17.7909 45 20V32C45 34.2091 43.2091 36 41 36H26L18 42V36H19C16.7909 36 15 34.2091 15 32V20Z" fill="white"/>
|
|
4
|
+
<path d="M20 24L28 28L20 32V24Z" fill="#059669"/>
|
|
5
|
+
<rect x="32" y="24" width="8" height="2" fill="#059669"/>
|
|
6
|
+
<rect x="32" y="28" width="8" height="2" fill="#059669"/>
|
|
7
|
+
<rect x="32" y="32" width="6" height="2" fill="#059669"/>
|
|
8
|
+
</svg>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-cribops",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "n8n community node for Cribops AI platform integration",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package"
|
|
7
|
+
],
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"author": {
|
|
10
|
+
"name": "CloudBedrock",
|
|
11
|
+
"email": "support@cloudbedrock.com"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/CloudBedrock/n8n-nodes-cribops.git"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://cribops.com",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/CloudBedrock/n8n-nodes-cribops/issues"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20.15"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "npx rimraf dist && tsc && gulp build:icons && cp package.json dist/",
|
|
26
|
+
"dev": "tsc --watch",
|
|
27
|
+
"format": "prettier nodes credentials --write",
|
|
28
|
+
"lint": "eslint nodes/**/*.ts credentials/**/*.ts --ext .ts",
|
|
29
|
+
"lintfix": "eslint nodes/**/*.ts credentials/**/*.ts --ext .ts --fix",
|
|
30
|
+
"prepublishOnly": "npm run build && npm run lint",
|
|
31
|
+
"test": "npm run build && n8n-node-dev test"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"n8n": {
|
|
37
|
+
"n8nNodesApiVersion": 1,
|
|
38
|
+
"credentials": [
|
|
39
|
+
"dist/credentials/CribopsApi.credentials.js"
|
|
40
|
+
],
|
|
41
|
+
"nodes": [
|
|
42
|
+
"dist/nodes/Cribops/Cribops.node.js",
|
|
43
|
+
"dist/nodes/CribopsTrigger/CribopsTrigger.node.js"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^24.0.12",
|
|
48
|
+
"@typescript-eslint/parser": "~8.32.0",
|
|
49
|
+
"eslint": "^8.57.0",
|
|
50
|
+
"eslint-plugin-n8n-nodes-base": "^1.16.3",
|
|
51
|
+
"gulp": "^5.0.0",
|
|
52
|
+
"n8n-workflow": "^1.82.0",
|
|
53
|
+
"prettier": "^3.5.0",
|
|
54
|
+
"rimraf": "^6.0.1",
|
|
55
|
+
"typescript": "^5.8.3",
|
|
56
|
+
"@aws-sdk/client-sqs": "^3.729.0",
|
|
57
|
+
"@aws-sdk/client-sns": "^3.729.0"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { IDataObject } from 'n8n-workflow';
|
|
2
|
+
export interface CribopsHttpConfig {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
apiToken: string;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface CribopsAgent {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
status: 'active' | 'inactive';
|
|
12
|
+
tenantId: string;
|
|
13
|
+
organizationId: string;
|
|
14
|
+
metadata?: IDataObject;
|
|
15
|
+
}
|
|
16
|
+
export interface CribopsWebhookMessage {
|
|
17
|
+
id: string;
|
|
18
|
+
type: 'user_message' | 'agent_response';
|
|
19
|
+
content: string;
|
|
20
|
+
conversationId: string;
|
|
21
|
+
userId?: string;
|
|
22
|
+
agentId: string;
|
|
23
|
+
timestamp: string;
|
|
24
|
+
metadata?: IDataObject;
|
|
25
|
+
fileUrl?: string;
|
|
26
|
+
fileName?: string;
|
|
27
|
+
fileType?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare class CribopsHttp {
|
|
30
|
+
private config;
|
|
31
|
+
constructor(config: CribopsHttpConfig);
|
|
32
|
+
private makeRequest;
|
|
33
|
+
getAgents(): Promise<CribopsAgent[]>;
|
|
34
|
+
getAgent(agentId: string): Promise<CribopsAgent>;
|
|
35
|
+
sendMessage(agentId: string, message: Partial<CribopsWebhookMessage>): Promise<CribopsWebhookMessage>;
|
|
36
|
+
downloadFile(fileUrl: string): Promise<Buffer>;
|
|
37
|
+
validateWebhook(payload: any, signature: string): Promise<boolean>;
|
|
38
|
+
testConnection(): Promise<boolean>;
|
|
39
|
+
sendTypingIndicator(agentId: string, conversationId: string, typing: boolean): Promise<any>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CribopsHttp = void 0;
|
|
4
|
+
class CribopsHttp {
|
|
5
|
+
config;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = {
|
|
8
|
+
timeout: 30000,
|
|
9
|
+
...config,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
async makeRequest(method, endpoint, data, options) {
|
|
13
|
+
const url = `${this.config.baseUrl}${endpoint}`;
|
|
14
|
+
const requestHeaders = {
|
|
15
|
+
'Authorization': `Bearer ${this.config.apiToken}`,
|
|
16
|
+
'Content-Type': 'application/json',
|
|
17
|
+
'Accept': 'application/json',
|
|
18
|
+
};
|
|
19
|
+
if (options?.headers) {
|
|
20
|
+
Object.keys(options.headers).forEach(key => {
|
|
21
|
+
requestHeaders[key] = String(options.headers[key]);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(url, {
|
|
26
|
+
method,
|
|
27
|
+
headers: requestHeaders,
|
|
28
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
const errorText = await response.text();
|
|
32
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
33
|
+
}
|
|
34
|
+
const result = await response.json();
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new Error(`Request failed: ${error}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async getAgents() {
|
|
42
|
+
try {
|
|
43
|
+
const response = await this.makeRequest('GET', '/api/v1/agents');
|
|
44
|
+
return response.agents || [];
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new Error(`Failed to fetch agents: ${error}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async getAgent(agentId) {
|
|
51
|
+
try {
|
|
52
|
+
const response = await this.makeRequest('GET', `/api/v1/agents/${agentId}`);
|
|
53
|
+
return response.agent;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
throw new Error(`Failed to fetch agent ${agentId}: ${error}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async sendMessage(agentId, message) {
|
|
60
|
+
try {
|
|
61
|
+
const response = await this.makeRequest('POST', `/webhooks/agents/${agentId}/message`, message);
|
|
62
|
+
return response.message;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
throw new Error(`Failed to send message to agent ${agentId}: ${error}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async downloadFile(fileUrl) {
|
|
69
|
+
try {
|
|
70
|
+
const response = await fetch(fileUrl, {
|
|
71
|
+
headers: {
|
|
72
|
+
'Authorization': `Bearer ${this.config.apiToken}`,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
const errorText = await response.text();
|
|
77
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
78
|
+
}
|
|
79
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
80
|
+
return Buffer.from(arrayBuffer);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw new Error(`Failed to download file: ${error}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async validateWebhook(payload, signature) {
|
|
87
|
+
// Implement webhook signature validation if needed
|
|
88
|
+
// This would typically use HMAC verification
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
async testConnection() {
|
|
92
|
+
try {
|
|
93
|
+
await this.getAgents();
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async sendTypingIndicator(agentId, conversationId, typing) {
|
|
101
|
+
try {
|
|
102
|
+
const response = await this.makeRequest('POST', `/api/agents/${agentId}/callback`, {
|
|
103
|
+
data: {
|
|
104
|
+
typing: typing,
|
|
105
|
+
},
|
|
106
|
+
conversation_id: conversationId,
|
|
107
|
+
callback_type: 'typing',
|
|
108
|
+
});
|
|
109
|
+
return response;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
throw new Error(`Failed to send typing indicator for agent ${agentId}: ${error}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.CribopsHttp = CribopsHttp;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { IDataObject } from 'n8n-workflow';
|
|
2
|
+
export interface FileAttachment {
|
|
3
|
+
url: string;
|
|
4
|
+
name: string;
|
|
5
|
+
type: string;
|
|
6
|
+
size?: number;
|
|
7
|
+
data?: Buffer;
|
|
8
|
+
}
|
|
9
|
+
export interface S3PresignedUrl {
|
|
10
|
+
uploadUrl: string;
|
|
11
|
+
downloadUrl: string;
|
|
12
|
+
fileName: string;
|
|
13
|
+
fileType: string;
|
|
14
|
+
expiresAt: string;
|
|
15
|
+
}
|
|
16
|
+
export declare class FileUtils {
|
|
17
|
+
private static readonly SUPPORTED_TYPES;
|
|
18
|
+
private static readonly MAX_FILE_SIZE;
|
|
19
|
+
static validateFileType(fileType: string): boolean;
|
|
20
|
+
static validateFileSize(size: number): boolean;
|
|
21
|
+
static extractFileInfo(url: string): {
|
|
22
|
+
name: string;
|
|
23
|
+
type: string;
|
|
24
|
+
};
|
|
25
|
+
static downloadFileFromUrl(url: string, headers?: IDataObject): Promise<Buffer>;
|
|
26
|
+
static formatFileSize(bytes: number): string;
|
|
27
|
+
static isImageFile(fileType: string): boolean;
|
|
28
|
+
static isTextFile(fileType: string): boolean;
|
|
29
|
+
static convertToBase64(buffer: Buffer): Promise<string>;
|
|
30
|
+
static convertFromBase64(base64: string): Promise<Buffer>;
|
|
31
|
+
static sanitizeFileName(fileName: string): string;
|
|
32
|
+
static generateUniqueFileName(originalName: string): string;
|
|
33
|
+
static parseS3PresignedUrl(url: string): S3PresignedUrl | null;
|
|
34
|
+
static validateFileContent(buffer: Buffer, expectedType: string): Promise<boolean>;
|
|
35
|
+
static createFileAttachment(url: string, name?: string, type?: string, size?: number): FileAttachment;
|
|
36
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FileUtils = void 0;
|
|
4
|
+
class FileUtils {
|
|
5
|
+
static SUPPORTED_TYPES = [
|
|
6
|
+
'image/jpeg',
|
|
7
|
+
'image/png',
|
|
8
|
+
'image/gif',
|
|
9
|
+
'image/webp',
|
|
10
|
+
'text/plain',
|
|
11
|
+
'text/csv',
|
|
12
|
+
'application/pdf',
|
|
13
|
+
'application/json',
|
|
14
|
+
'application/xml',
|
|
15
|
+
'application/zip',
|
|
16
|
+
'application/x-zip-compressed',
|
|
17
|
+
'application/msword',
|
|
18
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
19
|
+
'application/vnd.ms-excel',
|
|
20
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
21
|
+
];
|
|
22
|
+
static MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
23
|
+
static validateFileType(fileType) {
|
|
24
|
+
return this.SUPPORTED_TYPES.includes(fileType.toLowerCase());
|
|
25
|
+
}
|
|
26
|
+
static validateFileSize(size) {
|
|
27
|
+
return size <= this.MAX_FILE_SIZE;
|
|
28
|
+
}
|
|
29
|
+
static extractFileInfo(url) {
|
|
30
|
+
const urlParts = url.split('/');
|
|
31
|
+
const fileName = urlParts[urlParts.length - 1];
|
|
32
|
+
const fileExtension = fileName.split('.').pop()?.toLowerCase() || '';
|
|
33
|
+
const typeMapping = {
|
|
34
|
+
'jpg': 'image/jpeg',
|
|
35
|
+
'jpeg': 'image/jpeg',
|
|
36
|
+
'png': 'image/png',
|
|
37
|
+
'gif': 'image/gif',
|
|
38
|
+
'webp': 'image/webp',
|
|
39
|
+
'txt': 'text/plain',
|
|
40
|
+
'csv': 'text/csv',
|
|
41
|
+
'pdf': 'application/pdf',
|
|
42
|
+
'json': 'application/json',
|
|
43
|
+
'xml': 'application/xml',
|
|
44
|
+
'zip': 'application/zip',
|
|
45
|
+
'doc': 'application/msword',
|
|
46
|
+
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
47
|
+
'xls': 'application/vnd.ms-excel',
|
|
48
|
+
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
name: fileName,
|
|
52
|
+
type: typeMapping[fileExtension] || 'application/octet-stream',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
static async downloadFileFromUrl(url, headers) {
|
|
56
|
+
try {
|
|
57
|
+
const requestHeaders = {};
|
|
58
|
+
if (headers) {
|
|
59
|
+
Object.keys(headers).forEach(key => {
|
|
60
|
+
requestHeaders[key] = String(headers[key]);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const response = await fetch(url, {
|
|
64
|
+
headers: requestHeaders,
|
|
65
|
+
});
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
68
|
+
}
|
|
69
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
70
|
+
return Buffer.from(arrayBuffer);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw new Error(`Failed to download file from URL: ${error}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
static formatFileSize(bytes) {
|
|
77
|
+
if (bytes === 0)
|
|
78
|
+
return '0 Bytes';
|
|
79
|
+
const k = 1024;
|
|
80
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
81
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
82
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
83
|
+
}
|
|
84
|
+
static isImageFile(fileType) {
|
|
85
|
+
return fileType.startsWith('image/');
|
|
86
|
+
}
|
|
87
|
+
static isTextFile(fileType) {
|
|
88
|
+
return fileType.startsWith('text/') || fileType.includes('json') || fileType.includes('xml');
|
|
89
|
+
}
|
|
90
|
+
static async convertToBase64(buffer) {
|
|
91
|
+
return buffer.toString('base64');
|
|
92
|
+
}
|
|
93
|
+
static async convertFromBase64(base64) {
|
|
94
|
+
return Buffer.from(base64, 'base64');
|
|
95
|
+
}
|
|
96
|
+
static sanitizeFileName(fileName) {
|
|
97
|
+
// Remove or replace invalid characters
|
|
98
|
+
return fileName
|
|
99
|
+
.replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
100
|
+
.replace(/_{2,}/g, '_')
|
|
101
|
+
.replace(/^_|_$/g, '');
|
|
102
|
+
}
|
|
103
|
+
static generateUniqueFileName(originalName) {
|
|
104
|
+
const timestamp = Date.now();
|
|
105
|
+
const randomSuffix = Math.random().toString(36).substring(2, 8);
|
|
106
|
+
const sanitizedName = this.sanitizeFileName(originalName);
|
|
107
|
+
const parts = sanitizedName.split('.');
|
|
108
|
+
if (parts.length > 1) {
|
|
109
|
+
const extension = parts.pop();
|
|
110
|
+
const baseName = parts.join('.');
|
|
111
|
+
return `${baseName}_${timestamp}_${randomSuffix}.${extension}`;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
return `${sanitizedName}_${timestamp}_${randomSuffix}`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
static parseS3PresignedUrl(url) {
|
|
118
|
+
try {
|
|
119
|
+
const urlObj = new URL(url);
|
|
120
|
+
const pathParts = urlObj.pathname.split('/');
|
|
121
|
+
const fileName = pathParts[pathParts.length - 1];
|
|
122
|
+
const fileInfo = this.extractFileInfo(fileName);
|
|
123
|
+
return {
|
|
124
|
+
uploadUrl: url,
|
|
125
|
+
downloadUrl: url,
|
|
126
|
+
fileName: fileInfo.name,
|
|
127
|
+
fileType: fileInfo.type,
|
|
128
|
+
expiresAt: urlObj.searchParams.get('X-Amz-Expires') || '',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
static async validateFileContent(buffer, expectedType) {
|
|
136
|
+
// Basic file content validation
|
|
137
|
+
if (buffer.length === 0) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
// Check file signatures (magic numbers) for common types
|
|
141
|
+
const fileSignatures = {
|
|
142
|
+
'image/jpeg': [[0xFF, 0xD8, 0xFF]],
|
|
143
|
+
'image/png': [[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]],
|
|
144
|
+
'image/gif': [[0x47, 0x49, 0x46, 0x38]],
|
|
145
|
+
'application/pdf': [[0x25, 0x50, 0x44, 0x46]],
|
|
146
|
+
'application/zip': [[0x50, 0x4B, 0x03, 0x04], [0x50, 0x4B, 0x05, 0x06]],
|
|
147
|
+
};
|
|
148
|
+
const signatures = fileSignatures[expectedType];
|
|
149
|
+
if (!signatures) {
|
|
150
|
+
return true; // No signature check available, assume valid
|
|
151
|
+
}
|
|
152
|
+
const fileHeader = Array.from(buffer.slice(0, 8));
|
|
153
|
+
return signatures.some(signature => signature.every((byte, index) => fileHeader[index] === byte));
|
|
154
|
+
}
|
|
155
|
+
static createFileAttachment(url, name, type, size) {
|
|
156
|
+
const fileInfo = name && type ? { name, type } : this.extractFileInfo(url);
|
|
157
|
+
return {
|
|
158
|
+
url,
|
|
159
|
+
name: fileInfo.name,
|
|
160
|
+
type: fileInfo.type,
|
|
161
|
+
size,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.FileUtils = FileUtils;
|