@trg-admin/n8n-nodes-zoho-desk 0.1.10 → 0.1.12

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.
@@ -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
@@ -1,2 +1,3 @@
1
1
  export * from './nodes/ZohoDesk/ZohoDesk.node';
2
+ export * from './nodes/ZohoDesk/ZohoDeskTrigger.node';
2
3
  export * from './credentials/ZohoCRMDeskOAuth2Api.credentials';
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);
@@ -0,0 +1,20 @@
1
+ import type { IHookFunctions, IWebhookFunctions, ILoadOptionsFunctions, 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
+ methods: {
12
+ loadOptions: {
13
+ getDepartments(this: ILoadOptionsFunctions): Promise<{
14
+ name: string;
15
+ value: string;
16
+ }[]>;
17
+ };
18
+ };
19
+ webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
20
+ }
@@ -0,0 +1,268 @@
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 deskRequest(context, method, endpoint, organizationId, body = {}) {
31
+ const credentials = (await context.getCredentials('zohoCRMDeskOAuth2Api'));
32
+ const { oauthTokenData, accessTokenUrl, authUrl, deskBaseUrl } = credentials;
33
+ const baseUrl = getDeskBaseUrl(accessTokenUrl || authUrl || '', oauthTokenData?.api_domain || '', deskBaseUrl || '');
34
+ const options = {
35
+ method,
36
+ uri: `${baseUrl}/api/v1${endpoint}`,
37
+ headers: {
38
+ orgId: organizationId,
39
+ 'Content-Type': 'application/json',
40
+ },
41
+ json: true,
42
+ };
43
+ if (method !== 'GET' && method !== 'DELETE' && Object.keys(body).length) {
44
+ options.body = body;
45
+ }
46
+ try {
47
+ return await context.helpers.requestOAuth2?.call(context, 'zohoCRMDeskOAuth2Api', options);
48
+ }
49
+ catch (error) {
50
+ throw new n8n_workflow_1.NodeApiError(context.getNode(), error);
51
+ }
52
+ }
53
+ async function deskWebhookRequest(context, method, endpoint, body = {}) {
54
+ const organizationId = context.getNodeParameter('organizationId');
55
+ return deskRequest(context, method, endpoint, organizationId, body);
56
+ }
57
+ class ZohoDeskTrigger {
58
+ constructor() {
59
+ this.description = {
60
+ displayName: 'Zoho Desk Trigger',
61
+ name: 'zohoDeskTrigger',
62
+ icon: 'file:zohodesk.png',
63
+ group: ['trigger'],
64
+ version: 1,
65
+ 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.',
66
+ defaults: { name: 'Zoho Desk Trigger' },
67
+ inputs: [],
68
+ outputs: ['main'],
69
+ credentials: [{ name: 'zohoCRMDeskOAuth2Api', required: true }],
70
+ webhooks: [
71
+ {
72
+ name: 'default',
73
+ httpMethod: '={{$parameter["httpMethod"] || "POST"}}',
74
+ responseMode: 'onReceived',
75
+ path: 'webhook',
76
+ },
77
+ ],
78
+ properties: [
79
+ {
80
+ displayName: 'Organization ID',
81
+ name: 'organizationId',
82
+ type: 'string',
83
+ default: '',
84
+ required: true,
85
+ description: 'Your Zoho Desk Organization ID. Found in Setup → Developer Space → API.',
86
+ },
87
+ {
88
+ displayName: 'Events',
89
+ name: 'events',
90
+ type: 'multiOptions',
91
+ required: true,
92
+ default: ['Ticket_Add'],
93
+ description: 'Zoho Desk events to subscribe to. Ref: https://desk.zoho.com/support/WebhookDocument.do#EventsSupported',
94
+ options: [
95
+ { name: 'Ticket Created', value: 'Ticket_Add' },
96
+ { name: 'Ticket Updated', value: 'Ticket_Update' },
97
+ { name: 'Ticket Deleted', value: 'Ticket_Delete' },
98
+ { name: 'Comment Added', value: 'Ticket_Comment_Add' },
99
+ { name: 'Comment Updated', value: 'Ticket_Comment_Update' },
100
+ { name: 'Thread Added', value: 'Ticket_Thread_Add' },
101
+ { name: 'Contact Created', value: 'Contact_Add' },
102
+ { name: 'Contact Updated', value: 'Contact_Update' },
103
+ { name: 'Contact Deleted', value: 'Contact_Delete' },
104
+ ],
105
+ },
106
+ {
107
+ displayName: 'Departments (Optional)',
108
+ name: 'departmentIds',
109
+ type: 'multiOptions',
110
+ default: [],
111
+ description: 'Filter ticket/comment events to specific departments. Leave blank to receive events from all departments. Only applies to Ticket and Comment events.',
112
+ typeOptions: {
113
+ loadOptionsMethod: 'getDepartments',
114
+ loadOptionsDependsOn: ['organizationId'],
115
+ },
116
+ options: [],
117
+ },
118
+ {
119
+ displayName: 'Webhook Name',
120
+ name: 'webhookName',
121
+ type: 'string',
122
+ default: 'n8n Zoho Desk Trigger',
123
+ description: 'Name for the webhook subscription created in Zoho Desk',
124
+ },
125
+ {
126
+ displayName: 'Include Previous State (Ticket Update)',
127
+ name: 'includePrevState',
128
+ type: 'boolean',
129
+ default: false,
130
+ description: 'Whether to include the previous state of the ticket in Ticket_Update events. Zoho Desk returns prevState only when supported by the event.',
131
+ },
132
+ ],
133
+ };
134
+ // Lifecycle methods: create/delete webhook in Zoho Desk on workflow activate/deactivate
135
+ // Ref: POST /api/v1/webhooks (Desk.webhooks.CREATE), DELETE /api/v1/webhooks/{id} (Desk.webhooks.DELETE)
136
+ this.webhookMethods = {
137
+ default: {
138
+ async checkExists() {
139
+ const webhookData = this.getWorkflowStaticData('node');
140
+ if (!webhookData.webhookId)
141
+ return false;
142
+ // Verify the webhook still exists in Zoho Desk
143
+ // Ref: GET /api/v1/webhooks/{webhook_id} (Desk.webhooks.READ)
144
+ try {
145
+ const response = await deskWebhookRequest(this, 'GET', `/webhooks/${webhookData.webhookId}`);
146
+ return !!(response && response.id);
147
+ }
148
+ catch {
149
+ return false;
150
+ }
151
+ },
152
+ async create() {
153
+ const webhookUrl = this.getNodeWebhookUrl('default');
154
+ const events = this.getNodeParameter('events');
155
+ const deptIds = this.getNodeParameter('departmentIds', []);
156
+ const webhookName = this.getNodeParameter('webhookName');
157
+ const webhookData = this.getWorkflowStaticData('node');
158
+ // Build subscriptions object per API spec
159
+ // Ticket/Comment events support optional departmentIds filter
160
+ // Ref: https://desk.zoho.com/support/WebhookDocument.do#Webhook
161
+ const ticketEvents = new Set([
162
+ 'Ticket_Add', 'Ticket_Update', 'Ticket_Delete',
163
+ 'Ticket_Comment_Add', 'Ticket_Comment_Update', 'Ticket_Thread_Add',
164
+ ]);
165
+ const subscriptions = {};
166
+ for (const event of events) {
167
+ if (deptIds.length > 0 && ticketEvents.has(event)) {
168
+ subscriptions[event] = { departmentIds: deptIds };
169
+ }
170
+ else {
171
+ subscriptions[event] = null;
172
+ }
173
+ }
174
+ const body = {
175
+ name: webhookName,
176
+ url: webhookUrl,
177
+ subscriptions,
178
+ isEnabled: true,
179
+ };
180
+ const response = await deskWebhookRequest(this, 'POST', '/webhooks', body);
181
+ if (!response?.id) {
182
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), response, {
183
+ message: 'Zoho Desk did not return a webhook ID. Webhook creation may have failed.',
184
+ });
185
+ }
186
+ // Store the webhook ID so we can delete it on deactivate
187
+ webhookData.webhookId = response.id;
188
+ return true;
189
+ },
190
+ async delete() {
191
+ const webhookData = this.getWorkflowStaticData('node');
192
+ if (!webhookData.webhookId)
193
+ return true;
194
+ // Ref: DELETE /api/v1/webhooks/{webhooks_id} (Desk.webhooks.DELETE)
195
+ try {
196
+ await deskWebhookRequest(this, 'DELETE', `/webhooks/${webhookData.webhookId}`);
197
+ }
198
+ catch {
199
+ return false;
200
+ }
201
+ delete webhookData.webhookId;
202
+ return true;
203
+ },
204
+ },
205
+ };
206
+ this.methods = {
207
+ loadOptions: {
208
+ async getDepartments() {
209
+ const organizationId = this.getNodeParameter('organizationId', 0, '');
210
+ try {
211
+ const responseData = await deskRequest(this, 'GET', '/departments', organizationId);
212
+ const departments = Array.isArray(responseData.data)
213
+ ? responseData.data
214
+ : Array.isArray(responseData)
215
+ ? responseData
216
+ : [];
217
+ return departments.map((dept) => ({
218
+ name: dept.name || dept.departmentName || String(dept.id),
219
+ value: dept.id,
220
+ }));
221
+ }
222
+ catch (error) {
223
+ throw new Error(`Failed to load departments: ${error.message}. Ensure Organization ID is set correctly.`);
224
+ }
225
+ },
226
+ },
227
+ };
228
+ }
229
+ // Receive and process incoming webhook payload from Zoho Desk
230
+ // Ref: https://desk.zoho.com/support/WebhookDocument.do#EventsSupported
231
+ async webhook() {
232
+ const req = this.getRequestObject();
233
+ // Zoho sends a validation GET request when first creating the webhook.
234
+ // Return 200 OK immediately so registration succeeds.
235
+ if (req.method === 'GET') {
236
+ return {
237
+ webhookResponse: { status: 'ok' },
238
+ };
239
+ }
240
+ // Payload is an array of event objects
241
+ const body = this.getBodyData();
242
+ const events = Array.isArray(body) ? body : [body];
243
+ const jwtToken = req.headers['x-zdesk-jwt'] || null;
244
+ const returnItems = [];
245
+ for (const event of events) {
246
+ const output = {
247
+ eventType: event.eventType,
248
+ eventTime: event.eventTime,
249
+ orgId: event.orgId,
250
+ payload: event.payload ?? {},
251
+ };
252
+ // Include prevState for update events when present
253
+ if (event.prevState) {
254
+ output.prevState = event.prevState;
255
+ }
256
+ // Forward JWT token for caller verification if needed
257
+ // Ref: https://desk.zoho.com/support/WebhookDocument.do#WebhookAuthentication
258
+ if (jwtToken) {
259
+ output._jwtToken = jwtToken;
260
+ }
261
+ returnItems.push(output);
262
+ }
263
+ return {
264
+ workflowData: [returnItems.map((item) => ({ json: item }))],
265
+ };
266
+ }
267
+ }
268
+ 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.10",
3
+ "version": "0.1.12",
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"