@trg-admin/n8n-nodes-zoho-desk 0.1.9 → 0.1.11
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/dist/credentials/ZohoCRMDeskOAuth2Api.credentials.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/nodes/ZohoDesk/ZohoDesk.node.js +21 -2
- package/dist/nodes/ZohoDesk/ZohoDeskTrigger.node.d.ts +12 -0
- package/dist/nodes/ZohoDesk/ZohoDeskTrigger.node.js +242 -0
- package/package.json +3 -2
|
@@ -110,7 +110,7 @@ class ZohoCRMDeskOAuth2Api {
|
|
|
110
110
|
displayName: 'Scope',
|
|
111
111
|
name: 'scope',
|
|
112
112
|
type: 'string',
|
|
113
|
-
default: 'ZohoCRM.modules.ALL Desk.tickets.ALL Desk.contacts.ALL Desk.search.READ Desk.tasks.ALL Desk.basic.READ Desk.settings.READ',
|
|
113
|
+
default: 'ZohoCRM.modules.ALL Desk.tickets.ALL Desk.contacts.ALL Desk.search.READ Desk.tasks.ALL Desk.basic.READ Desk.settings.READ Desk.webhooks.CREATE Desk.webhooks.READ Desk.webhooks.UPDATE Desk.webhooks.DELETE',
|
|
114
114
|
required: true,
|
|
115
115
|
description: 'Space-separated OAuth scopes. Default includes both CRM and Desk permissions. Add or remove scopes as needed (e.g., Desk.settings.ALL, Desk.tasks.ALL).',
|
|
116
116
|
},
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -15,4 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./nodes/ZohoDesk/ZohoDesk.node"), exports);
|
|
18
|
+
__exportStar(require("./nodes/ZohoDesk/ZohoDeskTrigger.node"), exports);
|
|
18
19
|
__exportStar(require("./credentials/ZohoCRMDeskOAuth2Api.credentials"), exports);
|
|
@@ -161,6 +161,13 @@ class ZohoDesk {
|
|
|
161
161
|
default: '',
|
|
162
162
|
description: 'ID of existing contact (alternative to email)',
|
|
163
163
|
},
|
|
164
|
+
{
|
|
165
|
+
displayName: 'Task ID',
|
|
166
|
+
name: 'cfTaskId',
|
|
167
|
+
type: 'string',
|
|
168
|
+
default: '',
|
|
169
|
+
description: 'Custom field Task ID (maps to customFields.cf_task_id)',
|
|
170
|
+
},
|
|
164
171
|
{
|
|
165
172
|
displayName: 'Due Date',
|
|
166
173
|
name: 'dueDate',
|
|
@@ -423,12 +430,18 @@ class ZohoDesk {
|
|
|
423
430
|
const departmentId = this.getNodeParameter('departmentId', i);
|
|
424
431
|
const description = this.getNodeParameter('description', i, '');
|
|
425
432
|
const additionalFields = this.getNodeParameter('additionalFields', i, {});
|
|
433
|
+
const { cfTaskId, ...otherAdditionalFields } = additionalFields;
|
|
426
434
|
const body = {
|
|
427
435
|
subject,
|
|
428
436
|
email,
|
|
429
437
|
departmentId,
|
|
430
|
-
...
|
|
438
|
+
...otherAdditionalFields,
|
|
431
439
|
};
|
|
440
|
+
if (cfTaskId) {
|
|
441
|
+
body.customFields = {
|
|
442
|
+
cf_task_id: cfTaskId,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
432
445
|
if (description) {
|
|
433
446
|
body.description = description;
|
|
434
447
|
}
|
|
@@ -439,9 +452,15 @@ class ZohoDesk {
|
|
|
439
452
|
const ticketId = this.getNodeParameter('ticketId', i);
|
|
440
453
|
const description = this.getNodeParameter('description', i, '');
|
|
441
454
|
const additionalFields = this.getNodeParameter('additionalFields', i, {});
|
|
455
|
+
const { cfTaskId, ...otherAdditionalFields } = additionalFields;
|
|
442
456
|
const body = {
|
|
443
|
-
...
|
|
457
|
+
...otherAdditionalFields,
|
|
444
458
|
};
|
|
459
|
+
if (cfTaskId) {
|
|
460
|
+
body.customFields = {
|
|
461
|
+
cf_task_id: cfTaskId,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
445
464
|
if (description) {
|
|
446
465
|
body.description = description;
|
|
447
466
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IHookFunctions, IWebhookFunctions, INodeType, INodeTypeDescription, IWebhookResponseData } from 'n8n-workflow';
|
|
2
|
+
export declare class ZohoDeskTrigger implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
webhookMethods: {
|
|
5
|
+
default: {
|
|
6
|
+
checkExists(this: IHookFunctions): Promise<boolean>;
|
|
7
|
+
create(this: IHookFunctions): Promise<boolean>;
|
|
8
|
+
delete(this: IHookFunctions): Promise<boolean>;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ZohoDeskTrigger = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
function getDeskBaseUrl(tokenUrl = '', apiDomain = '', overrideBaseUrl = '') {
|
|
6
|
+
if (overrideBaseUrl)
|
|
7
|
+
return overrideBaseUrl;
|
|
8
|
+
if (apiDomain) {
|
|
9
|
+
if (apiDomain.includes('.com.au'))
|
|
10
|
+
return 'https://desk.zoho.com.au';
|
|
11
|
+
if (apiDomain.includes('.eu'))
|
|
12
|
+
return 'https://desk.zoho.eu';
|
|
13
|
+
if (apiDomain.includes('.in'))
|
|
14
|
+
return 'https://desk.zoho.in';
|
|
15
|
+
if (apiDomain.includes('.com.cn'))
|
|
16
|
+
return 'https://desk.zoho.com.cn';
|
|
17
|
+
if (apiDomain.includes('.com'))
|
|
18
|
+
return 'https://desk.zoho.com';
|
|
19
|
+
}
|
|
20
|
+
if (tokenUrl.includes('zoho.com.au'))
|
|
21
|
+
return 'https://desk.zoho.com.au';
|
|
22
|
+
if (tokenUrl.includes('zoho.eu'))
|
|
23
|
+
return 'https://desk.zoho.eu';
|
|
24
|
+
if (tokenUrl.includes('zoho.in'))
|
|
25
|
+
return 'https://desk.zoho.in';
|
|
26
|
+
if (tokenUrl.includes('zoho.com.cn'))
|
|
27
|
+
return 'https://desk.zoho.com.cn';
|
|
28
|
+
return 'https://desk.zoho.com';
|
|
29
|
+
}
|
|
30
|
+
async function deskWebhookRequest(context, method, endpoint, body = {}) {
|
|
31
|
+
const credentials = (await context.getCredentials('zohoCRMDeskOAuth2Api'));
|
|
32
|
+
const { oauthTokenData, accessTokenUrl, authUrl, deskBaseUrl } = credentials;
|
|
33
|
+
const organizationId = context.getNodeParameter('organizationId');
|
|
34
|
+
const baseUrl = getDeskBaseUrl(accessTokenUrl || authUrl || '', oauthTokenData?.api_domain || '', deskBaseUrl || '');
|
|
35
|
+
const options = {
|
|
36
|
+
method,
|
|
37
|
+
uri: `${baseUrl}/api/v1${endpoint}`,
|
|
38
|
+
headers: {
|
|
39
|
+
orgId: organizationId,
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
},
|
|
42
|
+
json: true,
|
|
43
|
+
};
|
|
44
|
+
if (method !== 'GET' && method !== 'DELETE' && Object.keys(body).length) {
|
|
45
|
+
options.body = body;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
return await context.helpers.requestOAuth2?.call(context, 'zohoCRMDeskOAuth2Api', options);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw new n8n_workflow_1.NodeApiError(context.getNode(), error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
class ZohoDeskTrigger {
|
|
55
|
+
constructor() {
|
|
56
|
+
this.description = {
|
|
57
|
+
displayName: 'Zoho Desk Trigger',
|
|
58
|
+
name: 'zohoDeskTrigger',
|
|
59
|
+
icon: 'file:zohodesk.png',
|
|
60
|
+
group: ['trigger'],
|
|
61
|
+
version: 1,
|
|
62
|
+
description: 'Starts the workflow when a Zoho Desk event occurs (ticket created, updated, comment added, etc.). Automatically creates and removes the webhook subscription in Zoho Desk.',
|
|
63
|
+
defaults: { name: 'Zoho Desk Trigger' },
|
|
64
|
+
inputs: [],
|
|
65
|
+
outputs: ['main'],
|
|
66
|
+
credentials: [{ name: 'zohoCRMDeskOAuth2Api', required: true }],
|
|
67
|
+
webhooks: [
|
|
68
|
+
{
|
|
69
|
+
name: 'default',
|
|
70
|
+
httpMethod: '={{$parameter["httpMethod"] || "POST"}}',
|
|
71
|
+
responseMode: 'onReceived',
|
|
72
|
+
path: 'webhook',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
properties: [
|
|
76
|
+
{
|
|
77
|
+
displayName: 'Organization ID',
|
|
78
|
+
name: 'organizationId',
|
|
79
|
+
type: 'string',
|
|
80
|
+
default: '',
|
|
81
|
+
required: true,
|
|
82
|
+
description: 'Your Zoho Desk Organization ID. Found in Setup → Developer Space → API.',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
displayName: 'Events',
|
|
86
|
+
name: 'events',
|
|
87
|
+
type: 'multiOptions',
|
|
88
|
+
required: true,
|
|
89
|
+
default: ['Ticket_Add'],
|
|
90
|
+
description: 'Zoho Desk events to subscribe to. Ref: https://desk.zoho.com/support/WebhookDocument.do#EventsSupported',
|
|
91
|
+
options: [
|
|
92
|
+
{ name: 'Ticket Created', value: 'Ticket_Add' },
|
|
93
|
+
{ name: 'Ticket Updated', value: 'Ticket_Update' },
|
|
94
|
+
{ name: 'Ticket Deleted', value: 'Ticket_Delete' },
|
|
95
|
+
{ name: 'Comment Added', value: 'Ticket_Comment_Add' },
|
|
96
|
+
{ name: 'Comment Updated', value: 'Ticket_Comment_Update' },
|
|
97
|
+
{ name: 'Thread Added', value: 'Ticket_Thread_Add' },
|
|
98
|
+
{ name: 'Contact Created', value: 'Contact_Add' },
|
|
99
|
+
{ name: 'Contact Updated', value: 'Contact_Update' },
|
|
100
|
+
{ name: 'Contact Deleted', value: 'Contact_Delete' },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
displayName: 'Department IDs (Optional)',
|
|
105
|
+
name: 'departmentIds',
|
|
106
|
+
type: 'string',
|
|
107
|
+
default: '',
|
|
108
|
+
description: 'Comma-separated department IDs to filter ticket/comment events. Leave blank to receive events from all departments. Only applies to Ticket and Comment events.',
|
|
109
|
+
placeholder: '1233000000024717,1233000000012806',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
displayName: 'Webhook Name',
|
|
113
|
+
name: 'webhookName',
|
|
114
|
+
type: 'string',
|
|
115
|
+
default: 'n8n Zoho Desk Trigger',
|
|
116
|
+
description: 'Name for the webhook subscription created in Zoho Desk',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
displayName: 'Include Previous State (Ticket Update)',
|
|
120
|
+
name: 'includePrevState',
|
|
121
|
+
type: 'boolean',
|
|
122
|
+
default: false,
|
|
123
|
+
description: 'Whether to include the previous state of the ticket in Ticket_Update events. Zoho Desk returns prevState only when supported by the event.',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
// Lifecycle methods: create/delete webhook in Zoho Desk on workflow activate/deactivate
|
|
128
|
+
// Ref: POST /api/v1/webhooks (Desk.webhooks.CREATE), DELETE /api/v1/webhooks/{id} (Desk.webhooks.DELETE)
|
|
129
|
+
this.webhookMethods = {
|
|
130
|
+
default: {
|
|
131
|
+
async checkExists() {
|
|
132
|
+
const webhookData = this.getWorkflowStaticData('node');
|
|
133
|
+
if (!webhookData.webhookId)
|
|
134
|
+
return false;
|
|
135
|
+
// Verify the webhook still exists in Zoho Desk
|
|
136
|
+
// Ref: GET /api/v1/webhooks/{webhook_id} (Desk.webhooks.READ)
|
|
137
|
+
try {
|
|
138
|
+
const response = await deskWebhookRequest(this, 'GET', `/webhooks/${webhookData.webhookId}`);
|
|
139
|
+
return !!(response && response.id);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
async create() {
|
|
146
|
+
const webhookUrl = this.getNodeWebhookUrl('default');
|
|
147
|
+
const events = this.getNodeParameter('events');
|
|
148
|
+
const departmentIdsRaw = this.getNodeParameter('departmentIds', '');
|
|
149
|
+
const webhookName = this.getNodeParameter('webhookName');
|
|
150
|
+
const webhookData = this.getWorkflowStaticData('node');
|
|
151
|
+
// Build subscriptions object per API spec
|
|
152
|
+
// Ticket/Comment events support optional departmentIds filter
|
|
153
|
+
// Ref: https://desk.zoho.com/support/WebhookDocument.do#Webhook
|
|
154
|
+
const deptIds = departmentIdsRaw
|
|
155
|
+
? departmentIdsRaw.split(',').map((id) => id.trim()).filter(Boolean)
|
|
156
|
+
: [];
|
|
157
|
+
const ticketEvents = new Set([
|
|
158
|
+
'Ticket_Add', 'Ticket_Update', 'Ticket_Delete',
|
|
159
|
+
'Ticket_Comment_Add', 'Ticket_Comment_Update', 'Ticket_Thread_Add',
|
|
160
|
+
]);
|
|
161
|
+
const subscriptions = {};
|
|
162
|
+
for (const event of events) {
|
|
163
|
+
if (deptIds.length > 0 && ticketEvents.has(event)) {
|
|
164
|
+
subscriptions[event] = { departmentIds: deptIds };
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
subscriptions[event] = null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const body = {
|
|
171
|
+
name: webhookName,
|
|
172
|
+
url: webhookUrl,
|
|
173
|
+
subscriptions,
|
|
174
|
+
isEnabled: true,
|
|
175
|
+
};
|
|
176
|
+
const response = await deskWebhookRequest(this, 'POST', '/webhooks', body);
|
|
177
|
+
if (!response?.id) {
|
|
178
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), response, {
|
|
179
|
+
message: 'Zoho Desk did not return a webhook ID. Webhook creation may have failed.',
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
// Store the webhook ID so we can delete it on deactivate
|
|
183
|
+
webhookData.webhookId = response.id;
|
|
184
|
+
return true;
|
|
185
|
+
},
|
|
186
|
+
async delete() {
|
|
187
|
+
const webhookData = this.getWorkflowStaticData('node');
|
|
188
|
+
if (!webhookData.webhookId)
|
|
189
|
+
return true;
|
|
190
|
+
// Ref: DELETE /api/v1/webhooks/{webhooks_id} (Desk.webhooks.DELETE)
|
|
191
|
+
try {
|
|
192
|
+
await deskWebhookRequest(this, 'DELETE', `/webhooks/${webhookData.webhookId}`);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
delete webhookData.webhookId;
|
|
198
|
+
return true;
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
// Receive and process incoming webhook payload from Zoho Desk
|
|
204
|
+
// Ref: https://desk.zoho.com/support/WebhookDocument.do#EventsSupported
|
|
205
|
+
async webhook() {
|
|
206
|
+
const req = this.getRequestObject();
|
|
207
|
+
// Zoho sends a validation GET request when first creating the webhook.
|
|
208
|
+
// Return 200 OK immediately so registration succeeds.
|
|
209
|
+
if (req.method === 'GET') {
|
|
210
|
+
return {
|
|
211
|
+
webhookResponse: { status: 'ok' },
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// Payload is an array of event objects
|
|
215
|
+
const body = this.getBodyData();
|
|
216
|
+
const events = Array.isArray(body) ? body : [body];
|
|
217
|
+
const jwtToken = req.headers['x-zdesk-jwt'] || null;
|
|
218
|
+
const returnItems = [];
|
|
219
|
+
for (const event of events) {
|
|
220
|
+
const output = {
|
|
221
|
+
eventType: event.eventType,
|
|
222
|
+
eventTime: event.eventTime,
|
|
223
|
+
orgId: event.orgId,
|
|
224
|
+
payload: event.payload ?? {},
|
|
225
|
+
};
|
|
226
|
+
// Include prevState for update events when present
|
|
227
|
+
if (event.prevState) {
|
|
228
|
+
output.prevState = event.prevState;
|
|
229
|
+
}
|
|
230
|
+
// Forward JWT token for caller verification if needed
|
|
231
|
+
// Ref: https://desk.zoho.com/support/WebhookDocument.do#WebhookAuthentication
|
|
232
|
+
if (jwtToken) {
|
|
233
|
+
output._jwtToken = jwtToken;
|
|
234
|
+
}
|
|
235
|
+
returnItems.push(output);
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
workflowData: [returnItems.map((item) => ({ json: item }))],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
exports.ZohoDeskTrigger = ZohoDeskTrigger;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trg-admin/n8n-nodes-zoho-desk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Community n8n node starter for Zoho Desk using built-in Zoho OAuth2 auth behavior",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"n8n": {
|
|
23
23
|
"n8nNodesApiVersion": 1,
|
|
24
24
|
"nodes": [
|
|
25
|
-
"dist/nodes/ZohoDesk/ZohoDesk.node.js"
|
|
25
|
+
"dist/nodes/ZohoDesk/ZohoDesk.node.js",
|
|
26
|
+
"dist/nodes/ZohoDesk/ZohoDeskTrigger.node.js"
|
|
26
27
|
],
|
|
27
28
|
"credentials": [
|
|
28
29
|
"dist/credentials/ZohoCRMDeskOAuth2Api.credentials.js"
|