n8n-nodes-wassenger 1.2.3 → 1.2.6
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/actions/chatContacts.js +15 -10
- package/dist/actions/chatFiles.js +0 -4
- package/dist/actions/chats.js +18 -13
- package/dist/actions/other.js +31 -6
- package/dist/actions/outboundMessages.js +30 -18
- package/dist/config.js +9 -0
- package/dist/constants/countries.js +2 -2
- package/dist/credentials/ApiKey.credentials.js +22 -0
- package/dist/nodes/WassengerTrigger.node.js +104 -36
- package/dist/nodes/methods.js +11 -15
- package/dist/request.js +86 -61
- package/package.json +21 -9
|
@@ -164,8 +164,8 @@ exports.chatContactProperties = [
|
|
|
164
164
|
displayName: 'Country',
|
|
165
165
|
name: 'country',
|
|
166
166
|
type: 'multiOptions',
|
|
167
|
-
options:
|
|
168
|
-
default:
|
|
167
|
+
options: countries_1.countries.map(country => ({ name: country.name + ' ' + country.flag, value: country.code })),
|
|
168
|
+
default: [],
|
|
169
169
|
placeholder: 'Select countries',
|
|
170
170
|
description: 'Filter contacts by country codes',
|
|
171
171
|
required: false
|
|
@@ -287,10 +287,8 @@ exports.chatContactProperties = [
|
|
|
287
287
|
options: [
|
|
288
288
|
{ name: 'Name A-Z', value: 'name:asc' },
|
|
289
289
|
{ name: 'Name Z-A', value: 'name:desc' },
|
|
290
|
-
{ name: 'Recently
|
|
291
|
-
{ name: 'Oldest
|
|
292
|
-
{ name: 'Recently Updated', value: 'updatedAt:desc' },
|
|
293
|
-
{ name: 'Oldest Updated', value: 'updatedAt:asc' },
|
|
290
|
+
{ name: 'Recently Updated', value: 'date:desc' },
|
|
291
|
+
{ name: 'Oldest Updated', value: 'date:asc' },
|
|
294
292
|
],
|
|
295
293
|
default: 'name:asc',
|
|
296
294
|
description: 'Sort order for results',
|
|
@@ -820,13 +818,20 @@ async function executeChatContactOperations(index) {
|
|
|
820
818
|
if (operation === 'getDeviceContacts') {
|
|
821
819
|
const filters = this.getNodeParameter('filters', index, {});
|
|
822
820
|
const queryParameters = {};
|
|
823
|
-
// Add all filters to query parameters
|
|
821
|
+
// Add all filters to query parameters. "Any country" was an option worth
|
|
822
|
+
// '', which the API rejects, and saved workflows can still carry it.
|
|
824
823
|
for (const [key, value] of Object.entries(filters)) {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
824
|
+
const kept = Array.isArray(value) ? value.filter((item) => item !== '') : value;
|
|
825
|
+
if (kept !== undefined && kept !== '' &&
|
|
826
|
+
(typeof kept !== 'object' || (Array.isArray(kept) && kept.length > 0))) {
|
|
827
|
+
queryParameters[key] = kept;
|
|
828
828
|
}
|
|
829
829
|
}
|
|
830
|
+
// The API's date sort is by update date, which these values used to call
|
|
831
|
+
// updatedAt, and saved workflows keep the value they were saved with.
|
|
832
|
+
if (typeof queryParameters.sort === 'string') {
|
|
833
|
+
queryParameters.sort = queryParameters.sort.replace(/^updatedAt:/, 'date:');
|
|
834
|
+
}
|
|
830
835
|
return (0, request_1.request)(this, 'GET', `/chat/${device}/contacts`, undefined, queryParameters);
|
|
831
836
|
}
|
|
832
837
|
// GET CONTACT DETAILS
|
|
@@ -89,9 +89,7 @@ exports.chatFileProperties = [
|
|
|
89
89
|
{ name: 'Image', value: 'image' },
|
|
90
90
|
{ name: 'Video', value: 'video' },
|
|
91
91
|
{ name: 'Audio', value: 'audio' },
|
|
92
|
-
{ name: 'Voice', value: 'ptt' },
|
|
93
92
|
{ name: 'Document', value: 'document' },
|
|
94
|
-
{ name: 'Sticker', value: 'sticker' },
|
|
95
93
|
],
|
|
96
94
|
default: [],
|
|
97
95
|
description: 'Filter files by media type',
|
|
@@ -180,8 +178,6 @@ exports.chatFileProperties = [
|
|
|
180
178
|
options: [
|
|
181
179
|
{ name: 'Recent First', value: 'date:desc' },
|
|
182
180
|
{ name: 'Oldest First', value: 'date:asc' },
|
|
183
|
-
{ name: 'Largest First', value: 'size:desc' },
|
|
184
|
-
{ name: 'Smallest First', value: 'size:asc' },
|
|
185
181
|
],
|
|
186
182
|
default: 'date:desc',
|
|
187
183
|
description: 'Files sorting criteria',
|
package/dist/actions/chats.js
CHANGED
|
@@ -269,9 +269,8 @@ exports.chatProperties = [
|
|
|
269
269
|
name: 'include',
|
|
270
270
|
type: 'multiOptions',
|
|
271
271
|
options: [
|
|
272
|
-
{ name: '
|
|
273
|
-
{ name: '
|
|
274
|
-
{ name: 'Participants', value: 'participants' },
|
|
272
|
+
{ name: 'Last Message', value: 'lastMessage' },
|
|
273
|
+
{ name: 'Group Participants', value: 'group.participants' },
|
|
275
274
|
],
|
|
276
275
|
default: [],
|
|
277
276
|
description: 'Include related data in the response',
|
|
@@ -295,13 +294,10 @@ exports.chatProperties = [
|
|
|
295
294
|
name: 'sort',
|
|
296
295
|
type: 'options',
|
|
297
296
|
options: [
|
|
298
|
-
{ name: '
|
|
299
|
-
{ name: 'Oldest First', value: '
|
|
300
|
-
{ name: 'Unread First', value: 'unreadCount:desc' },
|
|
301
|
-
{ name: 'Name A-Z', value: 'name:asc' },
|
|
302
|
-
{ name: 'Name Z-A', value: 'name:desc' },
|
|
297
|
+
{ name: 'Recent Activity First', value: 'date:desc' },
|
|
298
|
+
{ name: 'Oldest Activity First', value: 'date:asc' },
|
|
303
299
|
],
|
|
304
|
-
default: '
|
|
300
|
+
default: 'date:desc',
|
|
305
301
|
description: 'Sort order for results',
|
|
306
302
|
},
|
|
307
303
|
],
|
|
@@ -318,12 +314,10 @@ exports.chatProperties = [
|
|
|
318
314
|
},
|
|
319
315
|
},
|
|
320
316
|
options: [
|
|
321
|
-
{ name: '
|
|
322
|
-
{ name: 'Notes', value: 'notes' },
|
|
323
|
-
{ name: 'Participants', value: 'participants' },
|
|
317
|
+
{ name: 'Recent Files', value: 'files' },
|
|
324
318
|
],
|
|
325
319
|
default: [],
|
|
326
|
-
description: 'Include
|
|
320
|
+
description: 'Include the 30 most recent files shared in the chat',
|
|
327
321
|
},
|
|
328
322
|
// SEND TYPING STATUS OPTIONS
|
|
329
323
|
{
|
|
@@ -947,6 +941,13 @@ exports.chatProperties = [
|
|
|
947
941
|
description: 'Optional department ID to assign the chats to',
|
|
948
942
|
},
|
|
949
943
|
];
|
|
944
|
+
// The sorts Get Chats used to offer, which the API rejects, as the console
|
|
945
|
+
// asks the API for the same orders. Saved workflows keep the value they were
|
|
946
|
+
// saved with, and the first one was the default.
|
|
947
|
+
const OLD_CHAT_SORTS = new Map([
|
|
948
|
+
['lastMessageAt:desc', 'date:desc'],
|
|
949
|
+
['lastMessageAt:asc', 'date:asc'],
|
|
950
|
+
]);
|
|
950
951
|
async function executeChatOperations(index) {
|
|
951
952
|
const operation = this.getNodeParameter('operation', index);
|
|
952
953
|
const device = this.getNodeParameter('device', index);
|
|
@@ -961,6 +962,10 @@ async function executeChatOperations(index) {
|
|
|
961
962
|
queryParameters[key] = value;
|
|
962
963
|
}
|
|
963
964
|
}
|
|
965
|
+
const sort = OLD_CHAT_SORTS.get(String(queryParameters.sort));
|
|
966
|
+
if (sort) {
|
|
967
|
+
queryParameters.sort = sort;
|
|
968
|
+
}
|
|
964
969
|
return (0, request_1.request)(this, 'GET', `/chat/${device}/chats`, undefined, queryParameters);
|
|
965
970
|
}
|
|
966
971
|
// GET DEVICE CHAT DETAILS
|
package/dist/actions/other.js
CHANGED
|
@@ -66,7 +66,7 @@ exports.otherProperties = [
|
|
|
66
66
|
options: [{ name: 'Any country', value: '' }].concat(countries_1.countries.map(country => ({ name: country.name + ' ' + country.flag, value: country.code }))),
|
|
67
67
|
default: '',
|
|
68
68
|
placeholder: 'Select the country code...',
|
|
69
|
-
description: '
|
|
69
|
+
description: 'Country of the numbers written without their country code: a number with no + is read as a national number of this country.',
|
|
70
70
|
required: false,
|
|
71
71
|
displayOptions: {
|
|
72
72
|
show: {
|
|
@@ -249,23 +249,48 @@ exports.otherProperties = [
|
|
|
249
249
|
}
|
|
250
250
|
},
|
|
251
251
|
];
|
|
252
|
+
// The calling code the API puts in front of a national number, if the country
|
|
253
|
+
// has one. The country list keeps the territories of the North American
|
|
254
|
+
// numbering plan as 1 followed by their area code, which their national
|
|
255
|
+
// numbers already carry.
|
|
256
|
+
function callingCode(code) {
|
|
257
|
+
const country = countries_1.countries.find((entry) => entry.code === code);
|
|
258
|
+
if (!country) {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
return country.phonePrefix >= 1000 ? 1 : country.phonePrefix;
|
|
262
|
+
}
|
|
252
263
|
async function executeOtherOperations(itemIndex) {
|
|
253
264
|
const operation = this.getNodeParameter('operation', itemIndex);
|
|
254
265
|
if (operation === 'verifyNumberExists') {
|
|
255
266
|
const phone = this.getNodeParameter('phone', itemIndex);
|
|
256
|
-
return await (0, request_1.request)(this, 'POST', '/
|
|
267
|
+
return await (0, request_1.request)(this, 'POST', '/verify/number', { phone });
|
|
257
268
|
}
|
|
258
269
|
else if (operation === 'validateNumbers') {
|
|
259
270
|
const requestBody = {};
|
|
260
271
|
const country = this.getNodeParameter('country', itemIndex);
|
|
261
272
|
const numbers = this.getNodeParameter('phoneNumbers.numbers', itemIndex, []);
|
|
262
|
-
|
|
263
|
-
|
|
273
|
+
// The prefix comes from the whole value: its first two letters would turn
|
|
274
|
+
// "Mexico" into Montenegro and "Chile" into Switzerland.
|
|
275
|
+
const code = (country || '').trim().toUpperCase();
|
|
276
|
+
let prefix;
|
|
277
|
+
if (code && code !== 'ANY') {
|
|
278
|
+
requestBody.country = code.slice(0, 2);
|
|
279
|
+
prefix = callingCode(code);
|
|
264
280
|
}
|
|
265
281
|
if (numbers === null || numbers === void 0 ? void 0 : numbers.length) {
|
|
266
|
-
|
|
282
|
+
// The API reads the country only together with a dialling prefix, and
|
|
283
|
+
// puts that prefix in front of a number unless its first character is a
|
|
284
|
+
// +: a number whose + comes after a bracket or an invisible mark would
|
|
285
|
+
// get it twice, so only the numbers with no + carry it.
|
|
286
|
+
const phones = numbers
|
|
267
287
|
.filter(({ phone }) => phone && typeof phone === 'string')
|
|
268
|
-
.map(({ phone }) =>
|
|
288
|
+
.map(({ phone }) => phone.trim());
|
|
289
|
+
const national = (phone) => !/^[^\d+]*\+/.test(phone);
|
|
290
|
+
if (requestBody.country && !prefix && phones.some(national)) {
|
|
291
|
+
throw new Error(`No calling code is known for the country "${code}": use a two-letter code such as GB or ES, or write every number with its + and country code`);
|
|
292
|
+
}
|
|
293
|
+
requestBody.numbers = phones.map((phone) => (prefix && national(phone) ? { phone, prefix } : { phone }));
|
|
269
294
|
}
|
|
270
295
|
return await (0, request_1.request)(this, 'POST', '/numbers/validate', requestBody);
|
|
271
296
|
}
|
|
@@ -66,25 +66,29 @@ exports.outboundMessageProperties = [
|
|
|
66
66
|
options: [
|
|
67
67
|
{ name: 'Queued', value: 'queued' },
|
|
68
68
|
{ name: 'Processing', value: 'processing' },
|
|
69
|
-
{ name: '
|
|
70
|
-
{ name: 'Delivered', value: 'delivered' },
|
|
71
|
-
{ name: 'Read', value: 'read' },
|
|
69
|
+
{ name: 'Processed', value: 'processed' },
|
|
72
70
|
{ name: 'Failed', value: 'failed' },
|
|
73
|
-
{ name: '
|
|
71
|
+
{ name: 'Expired', value: 'expired' },
|
|
72
|
+
{ name: 'Removed', value: 'removed' },
|
|
73
|
+
{ name: 'Invalid', value: 'invalid' },
|
|
74
74
|
],
|
|
75
75
|
default: [],
|
|
76
|
-
description: 'Filter messages by status',
|
|
76
|
+
description: 'Filter messages by status. How far each one got with the recipient is the Delivery Status.',
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
displayName: 'Delivery Status',
|
|
80
80
|
name: 'deliveryStatus',
|
|
81
81
|
type: 'multiOptions',
|
|
82
82
|
options: [
|
|
83
|
-
{ name: '
|
|
83
|
+
{ name: 'Queued', value: 'queued' },
|
|
84
|
+
{ name: 'Processing', value: 'processing' },
|
|
84
85
|
{ name: 'Sent', value: 'sent' },
|
|
85
86
|
{ name: 'Delivered', value: 'delivered' },
|
|
86
87
|
{ name: 'Read', value: 'read' },
|
|
88
|
+
{ name: 'Played', value: 'played' },
|
|
87
89
|
{ name: 'Failed', value: 'failed' },
|
|
90
|
+
{ name: 'Cancelled', value: 'cancelled' },
|
|
91
|
+
{ name: 'Revoked', value: 'revoked' },
|
|
88
92
|
],
|
|
89
93
|
default: [],
|
|
90
94
|
description: 'Filter messages by delivery status',
|
|
@@ -95,8 +99,11 @@ exports.outboundMessageProperties = [
|
|
|
95
99
|
type: 'multiOptions',
|
|
96
100
|
options: [
|
|
97
101
|
{ name: 'Pending', value: 'pending' },
|
|
98
|
-
{ name: '
|
|
102
|
+
{ name: 'Notified', value: 'notified' },
|
|
99
103
|
{ name: 'Failed', value: 'failed' },
|
|
104
|
+
{ name: 'Ignored', value: 'ignored' },
|
|
105
|
+
{ name: 'Expired', value: 'expired' },
|
|
106
|
+
{ name: 'Cancelled', value: 'cancelled' },
|
|
100
107
|
],
|
|
101
108
|
default: [],
|
|
102
109
|
description: 'Filter messages by webhook status',
|
|
@@ -107,8 +114,9 @@ exports.outboundMessageProperties = [
|
|
|
107
114
|
type: 'multiOptions',
|
|
108
115
|
options: [
|
|
109
116
|
{ name: 'Device', value: 'device' },
|
|
110
|
-
{ name: '
|
|
111
|
-
{ name: '
|
|
117
|
+
{ name: 'Webhooks', value: 'webhooks' },
|
|
118
|
+
{ name: 'Logs', value: 'logs' },
|
|
119
|
+
{ name: 'Features', value: 'features' },
|
|
112
120
|
],
|
|
113
121
|
default: [],
|
|
114
122
|
description: 'Expand subentity documents per message',
|
|
@@ -166,13 +174,9 @@ exports.outboundMessageProperties = [
|
|
|
166
174
|
{ name: 'Image', value: 'image' },
|
|
167
175
|
{ name: 'Video', value: 'video' },
|
|
168
176
|
{ name: 'Audio', value: 'audio' },
|
|
169
|
-
{ name: 'Voice', value: 'voice' },
|
|
170
177
|
{ name: 'Document', value: 'document' },
|
|
171
178
|
{ name: 'Location', value: 'location' },
|
|
172
179
|
{ name: 'Contact', value: 'contact' },
|
|
173
|
-
{ name: 'Buttons', value: 'buttons' },
|
|
174
|
-
{ name: 'List', value: 'list' },
|
|
175
|
-
{ name: 'Template', value: 'template' },
|
|
176
180
|
],
|
|
177
181
|
default: [],
|
|
178
182
|
description: 'Filter messages by type',
|
|
@@ -289,10 +293,12 @@ exports.outboundMessageProperties = [
|
|
|
289
293
|
name: 'sort',
|
|
290
294
|
type: 'options',
|
|
291
295
|
options: [
|
|
292
|
-
{ name: '
|
|
293
|
-
{ name: 'Oldest First', value: '
|
|
296
|
+
{ name: 'Recently Created First', value: 'createdAt:desc' },
|
|
297
|
+
{ name: 'Oldest Created First', value: 'createdAt:asc' },
|
|
298
|
+
{ name: 'Latest Delivery First', value: 'deliverAt:desc' },
|
|
299
|
+
{ name: 'Earliest Delivery First', value: 'deliverAt:asc' },
|
|
294
300
|
],
|
|
295
|
-
default: '
|
|
301
|
+
default: 'createdAt:desc',
|
|
296
302
|
description: 'Sort order for results',
|
|
297
303
|
},
|
|
298
304
|
{
|
|
@@ -338,8 +344,9 @@ exports.outboundMessageProperties = [
|
|
|
338
344
|
},
|
|
339
345
|
options: [
|
|
340
346
|
{ name: 'Device', value: 'device' },
|
|
341
|
-
{ name: '
|
|
342
|
-
{ name: '
|
|
347
|
+
{ name: 'Webhooks', value: 'webhooks' },
|
|
348
|
+
{ name: 'Logs', value: 'logs' },
|
|
349
|
+
{ name: 'Preview', value: 'preview' },
|
|
343
350
|
],
|
|
344
351
|
default: [],
|
|
345
352
|
description: 'Include additional information in the response',
|
|
@@ -506,6 +513,11 @@ async function executeOutboundMessageOperations(index) {
|
|
|
506
513
|
queryParameters[key] = value;
|
|
507
514
|
}
|
|
508
515
|
}
|
|
516
|
+
// The sorts used to be date:desc, the default, and date:asc, which the API
|
|
517
|
+
// rejects; saved workflows keep the value they were saved with.
|
|
518
|
+
if (typeof queryParameters.sort === 'string') {
|
|
519
|
+
queryParameters.sort = queryParameters.sort.replace(/^date:/, 'createdAt:');
|
|
520
|
+
}
|
|
509
521
|
return (0, request_1.request)(this, 'GET', '/messages', undefined, queryParameters);
|
|
510
522
|
}
|
|
511
523
|
// GET MESSAGE
|
package/dist/config.js
CHANGED
|
@@ -3,10 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const name = 'Wassenger';
|
|
4
4
|
const code = 'wassenger';
|
|
5
5
|
const waba = '$waba'; // eslint-disable-line
|
|
6
|
+
const client = '$client'; // eslint-disable-line
|
|
7
|
+
// Replaced at build time for OEM/white-label builds. Verified community nodes
|
|
8
|
+
// can't read environment variables, so the value has to be baked in here.
|
|
9
|
+
const runtimeClient = client === '$client' ? '' : client;
|
|
6
10
|
const config = {
|
|
7
11
|
name,
|
|
8
12
|
code,
|
|
9
13
|
description: 'Wassenger integration for n8n',
|
|
10
14
|
waba: waba === 'true',
|
|
15
|
+
client: runtimeClient,
|
|
16
|
+
// Prefix applied to the API key for OEM builds. Empty on the public package.
|
|
17
|
+
keyPrefix: runtimeClient ? `${runtimeClient}_` : '',
|
|
18
|
+
// Label prefix used when registering webhooks with the Wassenger API.
|
|
19
|
+
webhookPrefix: runtimeClient.replace('_', '') || 'n8n',
|
|
11
20
|
};
|
|
12
21
|
exports.default = config;
|
|
@@ -252,7 +252,7 @@ exports.countries = [
|
|
|
252
252
|
name: 'Cayman Islands',
|
|
253
253
|
flag: '🇰🇾',
|
|
254
254
|
code: 'KY',
|
|
255
|
-
phonePrefix:
|
|
255
|
+
phonePrefix: 1345
|
|
256
256
|
},
|
|
257
257
|
{
|
|
258
258
|
name: 'Central African Republic',
|
|
@@ -593,7 +593,7 @@ exports.countries = [
|
|
|
593
593
|
name: 'Vatican City State',
|
|
594
594
|
flag: '🇻🇦',
|
|
595
595
|
code: 'VA',
|
|
596
|
-
phonePrefix:
|
|
596
|
+
phonePrefix: 39
|
|
597
597
|
},
|
|
598
598
|
{
|
|
599
599
|
name: 'Honduras',
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.ApiKey = void 0;
|
|
7
|
+
const config_1 = __importDefault(require("../config"));
|
|
4
8
|
class ApiKey {
|
|
5
9
|
constructor() {
|
|
6
10
|
this.name = 'wassengerApiKey';
|
|
@@ -9,12 +13,30 @@ class ApiKey {
|
|
|
9
13
|
this.properties = [
|
|
10
14
|
{
|
|
11
15
|
displayName: 'API Key',
|
|
16
|
+
// Renaming this would invalidate every saved credential, so it stays as is.
|
|
12
17
|
name: 'wassengerApiKey',
|
|
13
18
|
type: 'string',
|
|
19
|
+
typeOptions: { password: true },
|
|
14
20
|
default: '',
|
|
15
21
|
required: true,
|
|
22
|
+
description: 'Create one at https://app.wassenger.com/developers/apikeys',
|
|
16
23
|
},
|
|
17
24
|
];
|
|
25
|
+
this.authenticate = {
|
|
26
|
+
type: 'generic',
|
|
27
|
+
properties: {
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: `=Bearer ${config_1.default.keyPrefix}{{$credentials.wassengerApiKey}}`,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
this.test = {
|
|
34
|
+
request: {
|
|
35
|
+
baseURL: 'https://api.wassenger.com/v1',
|
|
36
|
+
url: '/devices',
|
|
37
|
+
method: 'GET',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
18
40
|
}
|
|
19
41
|
}
|
|
20
42
|
exports.ApiKey = ApiKey;
|
|
@@ -4,9 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.WassengerTrigger = void 0;
|
|
7
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
7
|
const request_1 = require("../request");
|
|
9
|
-
const
|
|
8
|
+
const config_1 = __importDefault(require("../config"));
|
|
10
9
|
/**
|
|
11
10
|
* The API stores the webhook URL exactly as it was registered, including the
|
|
12
11
|
* `?workflow=<id>` suffix that `create()` appends. Comparing the raw strings
|
|
@@ -388,6 +387,52 @@ const sampleWebhookEvents = [
|
|
|
388
387
|
name: 'WhatsApp number session status update',
|
|
389
388
|
event: 'number:session'
|
|
390
389
|
},
|
|
390
|
+
// The ids have to match the sample payloads the API serves from
|
|
391
|
+
// common/docs.js: `create()` sends the selected id as `?sample=`, and an id
|
|
392
|
+
// the API cannot resolve falls back to `inbound-text`, which would hand a
|
|
393
|
+
// funnel workflow a message-shaped payload to map its fields against.
|
|
394
|
+
{
|
|
395
|
+
id: 'deal-stage-changed',
|
|
396
|
+
type: 'deal',
|
|
397
|
+
name: 'Sales funnel deal moved to another stage',
|
|
398
|
+
event: 'deal:stage:changed'
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
id: 'deal-won',
|
|
402
|
+
type: 'deal',
|
|
403
|
+
name: 'Sales funnel deal won',
|
|
404
|
+
event: 'deal:won'
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
id: 'deal-lost',
|
|
408
|
+
type: 'deal',
|
|
409
|
+
name: 'Sales funnel deal lost',
|
|
410
|
+
event: 'deal:lost'
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
id: 'deal-assigned',
|
|
414
|
+
type: 'deal',
|
|
415
|
+
name: 'Sales funnel deal assigned to an agent',
|
|
416
|
+
event: 'deal:assigned'
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
id: 'deal-updated',
|
|
420
|
+
type: 'deal',
|
|
421
|
+
name: 'Sales funnel deal title or amount updated',
|
|
422
|
+
event: 'deal:updated'
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
id: 'deal-archived',
|
|
426
|
+
type: 'deal',
|
|
427
|
+
name: 'Sales funnel deal archived',
|
|
428
|
+
event: 'deal:archived'
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
id: 'deal-reopened',
|
|
432
|
+
type: 'deal',
|
|
433
|
+
name: 'Sales funnel deal reopened',
|
|
434
|
+
event: 'deal:reopened'
|
|
435
|
+
},
|
|
391
436
|
];
|
|
392
437
|
class WassengerTrigger {
|
|
393
438
|
constructor() {
|
|
@@ -461,6 +506,18 @@ class WassengerTrigger {
|
|
|
461
506
|
{ name: 'Chat Updated (Assigned, Resolved, etc.)', value: 'chat:update' },
|
|
462
507
|
{ name: 'WhatsApp Session Changes', value: 'number:session' },
|
|
463
508
|
{ name: 'Contact Information Updated', value: 'contact:update' },
|
|
509
|
+
// Sales funnel events. Subscribable since the funnel shipped, but
|
|
510
|
+
// missing from this list, which forced anyone automating the funnel
|
|
511
|
+
// onto a generic Webhook node. Gated to Professional and above by
|
|
512
|
+
// the API (see EventConfig in models/webhook.js): the registration
|
|
513
|
+
// call is what rejects a lower plan, not this list.
|
|
514
|
+
{ name: 'Sales Funnel Deal Moved to Another Stage', value: 'deal:stage:changed' },
|
|
515
|
+
{ name: 'Sales Funnel Deal Won', value: 'deal:won' },
|
|
516
|
+
{ name: 'Sales Funnel Deal Lost', value: 'deal:lost' },
|
|
517
|
+
{ name: 'Sales Funnel Deal Assigned or Unassigned', value: 'deal:assigned' },
|
|
518
|
+
{ name: 'Sales Funnel Deal Title or Amount Updated', value: 'deal:updated' },
|
|
519
|
+
{ name: 'Sales Funnel Deal Archived', value: 'deal:archived' },
|
|
520
|
+
{ name: 'Sales Funnel Deal Reopened', value: 'deal:reopened' },
|
|
464
521
|
],
|
|
465
522
|
},
|
|
466
523
|
{
|
|
@@ -483,32 +540,20 @@ class WassengerTrigger {
|
|
|
483
540
|
const credentials = await this.getCredentials('wassengerApiKey');
|
|
484
541
|
const apiKey = credentials.wassengerApiKey;
|
|
485
542
|
try {
|
|
486
|
-
const response = await (0, request_1.rawRequest)({ url: '/devices?size=100' }, apiKey);
|
|
543
|
+
const response = await (0, request_1.rawRequest)(this, { url: '/devices?size=100' }, apiKey);
|
|
487
544
|
if (Array.isArray(response.data) && response.data.length > 0) {
|
|
488
545
|
const devices = response.data.map((device) => ({
|
|
489
546
|
name: `${device.alias || 'Unnamed'} (${device.phone || 'No phone yet'})`,
|
|
490
547
|
value: device.id,
|
|
491
548
|
}));
|
|
492
|
-
|
|
493
|
-
return devices;
|
|
494
|
-
}
|
|
495
|
-
return [
|
|
496
|
-
{
|
|
497
|
-
name: 'All WhatsApp numbers in your account',
|
|
498
|
-
value: '',
|
|
499
|
-
},
|
|
500
|
-
...devices,
|
|
501
|
-
];
|
|
502
|
-
}
|
|
503
|
-
if (clientRuntime) {
|
|
504
|
-
return [
|
|
505
|
-
{
|
|
506
|
-
name: 'No WhatsApp numbers available: connect one or upgrade plan ',
|
|
507
|
-
value: 'invalid',
|
|
508
|
-
}
|
|
509
|
-
];
|
|
549
|
+
return devices;
|
|
510
550
|
}
|
|
511
|
-
return [
|
|
551
|
+
return [
|
|
552
|
+
{
|
|
553
|
+
name: 'No WhatsApp numbers available: connect one or upgrade plan ',
|
|
554
|
+
value: 'invalid',
|
|
555
|
+
}
|
|
556
|
+
];
|
|
512
557
|
}
|
|
513
558
|
catch (error) {
|
|
514
559
|
return [{ name: 'All WhatsApp numbers in your account', value: '' }];
|
|
@@ -545,7 +590,7 @@ class WassengerTrigger {
|
|
|
545
590
|
// No `workflow` query param here on purpose: the API treats it as a bulk
|
|
546
591
|
// cleanup trigger and would soft-delete the very webhook being checked,
|
|
547
592
|
// leaving the workflow with no registration at all.
|
|
548
|
-
const response = await (0, request_1.rawRequest)({ url: `/webhooks/${webhookId}` }, apiKey);
|
|
593
|
+
const response = await (0, request_1.rawRequest)(this, { url: `/webhooks/${webhookId}` }, apiKey);
|
|
549
594
|
return matchesRegistration(response.data, { url: webhookUrl, device, events });
|
|
550
595
|
}
|
|
551
596
|
catch {
|
|
@@ -557,7 +602,7 @@ class WassengerTrigger {
|
|
|
557
602
|
}
|
|
558
603
|
},
|
|
559
604
|
async create() {
|
|
560
|
-
var _a;
|
|
605
|
+
var _a, _b, _c;
|
|
561
606
|
const webhookUrl = this.getNodeWebhookUrl('default');
|
|
562
607
|
const webhookName = this.getNodeParameter('webhookName');
|
|
563
608
|
const device = this.getNodeParameter('device', '');
|
|
@@ -575,7 +620,7 @@ class WassengerTrigger {
|
|
|
575
620
|
const apiKey = credentials.wassengerApiKey;
|
|
576
621
|
const sample = this.getNodeParameter('sampleEvent', '') || 'inbound-text';
|
|
577
622
|
const url = `${webhookUrl}?workflow=${workflowId}`;
|
|
578
|
-
const prefix =
|
|
623
|
+
const prefix = config_1.default.webhookPrefix;
|
|
579
624
|
const requestBody = {
|
|
580
625
|
name: `${prefix}: ${webhookName}`,
|
|
581
626
|
url,
|
|
@@ -584,7 +629,7 @@ class WassengerTrigger {
|
|
|
584
629
|
if (device) {
|
|
585
630
|
requestBody.device = device;
|
|
586
631
|
}
|
|
587
|
-
const response = await (0, request_1.rawRequest)({
|
|
632
|
+
const response = await (0, request_1.rawRequest)(this, {
|
|
588
633
|
url: '/webhooks',
|
|
589
634
|
method: 'POST',
|
|
590
635
|
data: requestBody,
|
|
@@ -601,17 +646,19 @@ class WassengerTrigger {
|
|
|
601
646
|
return false;
|
|
602
647
|
}
|
|
603
648
|
catch (error) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
649
|
+
const statusCode = (0, request_1.errorStatusCode)(error);
|
|
650
|
+
if (statusCode === 409) {
|
|
651
|
+
return true;
|
|
652
|
+
}
|
|
653
|
+
if (statusCode) {
|
|
654
|
+
const body = (_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.body) !== null && _b !== void 0 ? _b : (_c = error === null || error === void 0 ? void 0 : error.response) === null || _c === void 0 ? void 0 : _c.data;
|
|
655
|
+
throw new Error(`Wassenger webhook registration failed: ${device || "(no device)"} ${(body === null || body === void 0 ? void 0 : body.message) || error.message}`);
|
|
609
656
|
}
|
|
610
657
|
throw error;
|
|
611
658
|
}
|
|
612
659
|
},
|
|
613
660
|
async delete() {
|
|
614
|
-
var _a;
|
|
661
|
+
var _a, _b, _c;
|
|
615
662
|
const nodeData = this.getWorkflowStaticData('node');
|
|
616
663
|
const webhookId = nodeData.webhookId;
|
|
617
664
|
if (!webhookId) {
|
|
@@ -621,21 +668,27 @@ class WassengerTrigger {
|
|
|
621
668
|
const credentials = await this.getCredentials('wassengerApiKey');
|
|
622
669
|
const apiKey = credentials.wassengerApiKey;
|
|
623
670
|
const workflowId = this.getWorkflow().id;
|
|
624
|
-
await (0, request_1.rawRequest)({
|
|
671
|
+
await (0, request_1.rawRequest)(this, {
|
|
625
672
|
url: `/webhooks/${webhookId}?workflow=${workflowId}`,
|
|
626
673
|
method: 'DELETE',
|
|
627
674
|
headers: {
|
|
628
675
|
'Content-Type': 'application/json'
|
|
629
676
|
},
|
|
630
677
|
}, apiKey);
|
|
678
|
+
// The registration is gone: forget its id, or the next activation would
|
|
679
|
+
// check (and later delete) a row that no longer belongs to this workflow.
|
|
680
|
+
delete nodeData.webhookId;
|
|
631
681
|
return true;
|
|
632
682
|
}
|
|
633
683
|
catch (error) {
|
|
634
|
-
|
|
684
|
+
const statusCode = (0, request_1.errorStatusCode)(error);
|
|
685
|
+
if (statusCode === 404) {
|
|
686
|
+
delete nodeData.webhookId;
|
|
635
687
|
return true;
|
|
636
688
|
}
|
|
637
|
-
if (
|
|
638
|
-
|
|
689
|
+
if (statusCode) {
|
|
690
|
+
const body = (_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.body) !== null && _b !== void 0 ? _b : (_c = error === null || error === void 0 ? void 0 : error.response) === null || _c === void 0 ? void 0 : _c.data;
|
|
691
|
+
console.error(`Wassenger webhook deletion failed: ${webhookId} ${statusCode} ${(body === null || body === void 0 ? void 0 : body.message) || error.message}`);
|
|
639
692
|
}
|
|
640
693
|
else {
|
|
641
694
|
console.error('Error deleting Wassenger webhook:', webhookId, error);
|
|
@@ -648,6 +701,21 @@ class WassengerTrigger {
|
|
|
648
701
|
}
|
|
649
702
|
// This method is called when a webhook request is received
|
|
650
703
|
async webhook() {
|
|
704
|
+
// The platform calls the URL this node registered, `?workflow=<id>` included.
|
|
705
|
+
// When n8n moved that path to another workflow, deliveries registered for the
|
|
706
|
+
// first one can land here: acknowledge them (200, so the platform neither
|
|
707
|
+
// retries nor flags the row) without starting an execution.
|
|
708
|
+
// Platform senders used to post to the registered URL plus '/', which put the
|
|
709
|
+
// slash after the query (`?workflow=<id>/`). Workers keep that sender until they
|
|
710
|
+
// restart, so that one slash is still accepted. It can go once every worker
|
|
711
|
+
// runs a sender that requests the registered URL as is.
|
|
712
|
+
const { workflow: rawWorkflow } = this.getQueryData();
|
|
713
|
+
const addressedTo = typeof rawWorkflow === 'string' && rawWorkflow.endsWith('/')
|
|
714
|
+
? rawWorkflow.slice(0, -1)
|
|
715
|
+
: rawWorkflow;
|
|
716
|
+
if (addressedTo !== undefined && addressedTo !== this.getWorkflow().id) {
|
|
717
|
+
return { webhookResponse: { ignored: true } };
|
|
718
|
+
}
|
|
651
719
|
const req = this.getRequestObject();
|
|
652
720
|
const body = req.body;
|
|
653
721
|
// Return the data
|
package/dist/nodes/methods.js
CHANGED
|
@@ -9,12 +9,8 @@ exports.getLabels = getLabels;
|
|
|
9
9
|
exports.getWabaDevices = getWabaDevices;
|
|
10
10
|
exports.getTemplates = getTemplates;
|
|
11
11
|
const request_1 = require("../request");
|
|
12
|
-
async function query(url, apiKey) {
|
|
13
|
-
const
|
|
14
|
-
url,
|
|
15
|
-
method: 'GET'
|
|
16
|
-
};
|
|
17
|
-
const { data } = await (0, request_1.rawRequest)(opts, apiKey);
|
|
12
|
+
async function query(context, url, apiKey) {
|
|
13
|
+
const { data } = await (0, request_1.rawRequest)(context, { url, method: 'GET' }, apiKey);
|
|
18
14
|
return data;
|
|
19
15
|
}
|
|
20
16
|
async function getDevices() {
|
|
@@ -24,7 +20,7 @@ async function getDevices() {
|
|
|
24
20
|
throw new Error('No Wassenger API Key credentials found!');
|
|
25
21
|
}
|
|
26
22
|
const apiKey = credentials.wassengerApiKey;
|
|
27
|
-
const data = await query(`/devices?size=100`, apiKey);
|
|
23
|
+
const data = await query(this, `/devices?size=100`, apiKey);
|
|
28
24
|
return data.map((device) => ({
|
|
29
25
|
name: `${device.alias} (${device.phone})`,
|
|
30
26
|
value: device.id,
|
|
@@ -43,7 +39,7 @@ async function getGroups() {
|
|
|
43
39
|
const target = this.getNodeParameter('target');
|
|
44
40
|
const apiKey = credentials.wassengerApiKey;
|
|
45
41
|
const kind = target === 'channel' ? 'channel' : 'group';
|
|
46
|
-
const data = await query(`/devices/${device}/groups?size=500&kind=${kind}`, apiKey);
|
|
42
|
+
const data = await query(this, `/devices/${device}/groups?size=500&kind=${kind}`, apiKey);
|
|
47
43
|
return data.filter((group) => {
|
|
48
44
|
return group.isReadOnly !== true;
|
|
49
45
|
}).map((group) => ({
|
|
@@ -63,7 +59,7 @@ async function getFiles() {
|
|
|
63
59
|
if (search) {
|
|
64
60
|
url += `&search=${search}`;
|
|
65
61
|
}
|
|
66
|
-
const data = await query(url, apiKey);
|
|
62
|
+
const data = await query(this, url, apiKey);
|
|
67
63
|
return data.map((file) => ({
|
|
68
64
|
name: file.filename,
|
|
69
65
|
value: file.id,
|
|
@@ -80,7 +76,7 @@ async function getTeamAgents() {
|
|
|
80
76
|
throw new Error('Please select WhatsApp device selected');
|
|
81
77
|
}
|
|
82
78
|
const apiKey = credentials.wassengerApiKey;
|
|
83
|
-
const data = await query(`/devices/${device}/team?size=100`, apiKey);
|
|
79
|
+
const data = await query(this, `/devices/${device}/team?size=100`, apiKey);
|
|
84
80
|
return data.map((agent) => ({
|
|
85
81
|
name: agent.displayName + ' - ' + agent.email,
|
|
86
82
|
value: agent.id,
|
|
@@ -96,7 +92,7 @@ async function getDepartments() {
|
|
|
96
92
|
throw new Error('Please select WhatsApp device selected');
|
|
97
93
|
}
|
|
98
94
|
const apiKey = credentials.wassengerApiKey;
|
|
99
|
-
const data = await query(`/devices/${device}/departments`, apiKey);
|
|
95
|
+
const data = await query(this, `/devices/${device}/departments`, apiKey);
|
|
100
96
|
return data.map((department) => ({
|
|
101
97
|
name: department.name,
|
|
102
98
|
value: department.id
|
|
@@ -114,7 +110,7 @@ async function getLabels() {
|
|
|
114
110
|
throw new Error('Please select WhatsApp device selected');
|
|
115
111
|
}
|
|
116
112
|
const apiKey = credentials.wassengerApiKey;
|
|
117
|
-
const data = await query(`/devices/${device}/labels?size=100`, apiKey);
|
|
113
|
+
const data = await query(this, `/devices/${device}/labels?size=100`, apiKey);
|
|
118
114
|
return data.filter((label) => label.scope === 'chat').map((label) => ({
|
|
119
115
|
name: label.name,
|
|
120
116
|
value: label.name
|
|
@@ -126,7 +122,7 @@ async function getWabaDevices() {
|
|
|
126
122
|
throw new Error('No Wassenger API Key credentials found!');
|
|
127
123
|
}
|
|
128
124
|
const apiKey = credentials.wassengerApiKey;
|
|
129
|
-
const data = await query('/devices?size=100&connector=cloud', apiKey);
|
|
125
|
+
const data = await query(this, '/devices?size=100&connector=cloud', apiKey);
|
|
130
126
|
return data
|
|
131
127
|
.filter((device) => device.waba && device.waba.id)
|
|
132
128
|
.map((device) => ({
|
|
@@ -148,13 +144,13 @@ async function getTemplates() {
|
|
|
148
144
|
// Actually, standard templates endpoint allows filtering by WABA ID, but we might just want templates for this device context.
|
|
149
145
|
// The user request says: "device.waba.id must be avaialble in order to pull templates filtered by the device waba ID field"
|
|
150
146
|
// So we fetch device first.
|
|
151
|
-
const [deviceData] = await query(`/devices/${device}`, apiKey);
|
|
147
|
+
const [deviceData] = await query(this, `/devices/${device}`, apiKey);
|
|
152
148
|
if (!deviceData || !deviceData.waba || !deviceData.waba.id) {
|
|
153
149
|
return [];
|
|
154
150
|
}
|
|
155
151
|
const wabaId = deviceData.waba.id;
|
|
156
152
|
// 2. Fetch templates
|
|
157
|
-
const templates = await query(`/waba/templates?waba=${wabaId}&status=APPROVED&size=500`, apiKey);
|
|
153
|
+
const templates = await query(this, `/waba/templates?waba=${wabaId}&status=APPROVED&size=500`, apiKey);
|
|
158
154
|
return templates.map((template) => ({
|
|
159
155
|
name: `${template.name} (${template.language})`,
|
|
160
156
|
value: template.name,
|
package/dist/request.js
CHANGED
|
@@ -3,25 +3,56 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.credentialsName = void 0;
|
|
7
|
+
exports.errorStatusCode = errorStatusCode;
|
|
6
8
|
exports.rawRequest = rawRequest;
|
|
7
9
|
exports.request = request;
|
|
8
|
-
const
|
|
10
|
+
const config_1 = __importDefault(require("./config"));
|
|
9
11
|
const baseUrl = 'https://api.wassenger.com/v1';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
exports.credentialsName = 'wassengerApiKey';
|
|
13
|
+
function authHeaders(apiKey, extra) {
|
|
14
|
+
return {
|
|
15
|
+
Authorization: `Bearer ${config_1.default.keyPrefix}${apiKey}`,
|
|
16
|
+
'x-n8n-client': 'n8n',
|
|
17
|
+
...(extra || {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Pulls an HTTP status code out of whatever shape n8n's request helper threw.
|
|
22
|
+
* The helper doesn't guarantee a single error class, so check the known fields
|
|
23
|
+
* rather than assuming one.
|
|
24
|
+
*/
|
|
25
|
+
function errorStatusCode(error) {
|
|
26
|
+
var _a, _b, _c, _d, _e;
|
|
27
|
+
return (error === null || error === void 0 ? void 0 : error.httpCode) ? Number(error.httpCode)
|
|
28
|
+
: (_c = (_a = error === null || error === void 0 ? void 0 : error.statusCode) !== null && _a !== void 0 ? _a : (_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.status) !== null && _c !== void 0 ? _c : (_e = (_d = error === null || error === void 0 ? void 0 : error.cause) === null || _d === void 0 ? void 0 : _d.response) === null || _e === void 0 ? void 0 : _e.status;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Makes an unauthenticated-context request against the Wassenger API.
|
|
32
|
+
*
|
|
33
|
+
* Returns an `{ data }` envelope, matching the shape callers already expect.
|
|
34
|
+
*/
|
|
35
|
+
async function rawRequest(context, opts, apiKey) {
|
|
12
36
|
if (!apiKey) {
|
|
13
37
|
throw new Error('API key is required');
|
|
14
38
|
}
|
|
15
39
|
if (!opts.url) {
|
|
16
40
|
throw new Error('URL path is required');
|
|
17
41
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
42
|
+
const options = {
|
|
43
|
+
url: `${baseUrl}${opts.url}`,
|
|
44
|
+
method: opts.method || 'GET',
|
|
45
|
+
headers: authHeaders(apiKey, opts.headers),
|
|
46
|
+
json: true,
|
|
47
|
+
};
|
|
48
|
+
if (opts.params) {
|
|
49
|
+
options.qs = opts.params;
|
|
50
|
+
}
|
|
51
|
+
if (opts.data !== undefined) {
|
|
52
|
+
options.body = opts.data;
|
|
53
|
+
}
|
|
54
|
+
const data = await context.helpers.httpRequest(options);
|
|
55
|
+
return { data };
|
|
25
56
|
}
|
|
26
57
|
/**
|
|
27
58
|
* Makes an authenticated request to the Wassenger API
|
|
@@ -35,69 +66,63 @@ async function rawRequest(opts, apiKey) {
|
|
|
35
66
|
* @returns The API response data
|
|
36
67
|
*/
|
|
37
68
|
async function request(executeFunctions, method, endpoint, body, query, customHeaders) {
|
|
38
|
-
var _a;
|
|
39
|
-
const credentials = await executeFunctions.getCredentials(
|
|
40
|
-
const apiKey = credentials.
|
|
69
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
70
|
+
const credentials = await executeFunctions.getCredentials(exports.credentialsName);
|
|
71
|
+
const apiKey = credentials[exports.credentialsName];
|
|
41
72
|
if (!apiKey) {
|
|
42
73
|
throw new Error('API key is required');
|
|
43
74
|
}
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
'Content-Type': 'application/json',
|
|
47
|
-
'Authorization': `Bearer ${clientRuntime ? clientRuntime + '_' : ''}${apiKey}`,
|
|
48
|
-
'x-n8n-client': 'n8n',
|
|
49
|
-
...(customHeaders || {}),
|
|
50
|
-
};
|
|
51
|
-
const config = {
|
|
52
|
-
url,
|
|
75
|
+
const options = {
|
|
76
|
+
url: `${baseUrl}${endpoint}`,
|
|
53
77
|
method,
|
|
54
|
-
headers,
|
|
55
|
-
|
|
78
|
+
headers: authHeaders(apiKey, { 'Content-Type': 'application/json', ...(customHeaders || {}) }),
|
|
79
|
+
json: true,
|
|
56
80
|
};
|
|
81
|
+
if (query) {
|
|
82
|
+
options.qs = query;
|
|
83
|
+
}
|
|
57
84
|
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method) && body) {
|
|
58
|
-
|
|
85
|
+
options.body = body;
|
|
59
86
|
}
|
|
60
87
|
try {
|
|
61
|
-
|
|
62
|
-
return response.data;
|
|
88
|
+
return await executeFunctions.helpers.httpRequest(options);
|
|
63
89
|
}
|
|
64
90
|
catch (error) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
const statusCode = errorStatusCode(error);
|
|
92
|
+
const responseBody = (_d = (_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.body) !== null && _b !== void 0 ? _b : (_c = error === null || error === void 0 ? void 0 : error.response) === null || _c === void 0 ? void 0 : _c.data) !== null && _d !== void 0 ? _d : (_f = (_e = error === null || error === void 0 ? void 0 : error.cause) === null || _e === void 0 ? void 0 : _e.response) === null || _f === void 0 ? void 0 : _f.body;
|
|
93
|
+
// Keep the detailed shape the action modules and tests already expect.
|
|
94
|
+
const errorResponse = {
|
|
95
|
+
error: error === null || error === void 0 ? void 0 : error.message,
|
|
96
|
+
statusCode,
|
|
97
|
+
};
|
|
98
|
+
if ((_g = error === null || error === void 0 ? void 0 : error.response) === null || _g === void 0 ? void 0 : _g.statusText) {
|
|
99
|
+
errorResponse.statusText = error.response.statusText;
|
|
100
|
+
}
|
|
101
|
+
if (responseBody !== undefined) {
|
|
102
|
+
if (typeof responseBody === 'object') {
|
|
103
|
+
errorResponse.responseBody = responseBody;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
try {
|
|
107
|
+
errorResponse.responseBody = JSON.parse(responseBody);
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
errorResponse.responseBody = responseBody;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const parsed = errorResponse.responseBody;
|
|
114
|
+
if (parsed && typeof parsed === 'object') {
|
|
115
|
+
if (parsed.error) {
|
|
116
|
+
errorResponse.apiError = parsed.error;
|
|
117
|
+
}
|
|
118
|
+
if (parsed.code) {
|
|
119
|
+
errorResponse.apiCode = parsed.code;
|
|
120
|
+
}
|
|
121
|
+
if (parsed.message) {
|
|
122
|
+
errorResponse.apiMessage = parsed.message;
|
|
96
123
|
}
|
|
97
124
|
}
|
|
98
|
-
throw new Error(`Wassenger API Error: ${JSON.stringify(errorResponse)}`);
|
|
99
125
|
}
|
|
100
|
-
|
|
101
|
-
throw error;
|
|
126
|
+
throw new Error(`Wassenger API Error: ${JSON.stringify(errorResponse)}`);
|
|
102
127
|
}
|
|
103
128
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-wassenger",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "n8n plugin for Wassenger",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,16 +37,11 @@
|
|
|
37
37
|
"build": "npm run clean && tsc && cp icon.png dist/nodes/icon.png",
|
|
38
38
|
"watch": "tsc --watch",
|
|
39
39
|
"lint": "eslint . --ext .ts",
|
|
40
|
-
"prepublishOnly": "npm run build",
|
|
41
40
|
"dev": "npm run build && n8n start",
|
|
42
41
|
"dev:copy": "npm run build && cp -r dist/ $HOME/.n8n/nodes/node_modules/n8n-nodes-wassenger/dist && cp package.json $HOME/.n8n/nodes/node_modules/n8n-nodes-wassenger/",
|
|
43
42
|
"test": "jest",
|
|
44
|
-
"test:watch": "jest --watch"
|
|
45
|
-
|
|
46
|
-
"dependencies": {
|
|
47
|
-
"axios": "^1.8.4",
|
|
48
|
-
"n8n-core": "=1.89.0",
|
|
49
|
-
"n8n-workflow": "=1.89.0"
|
|
43
|
+
"test:watch": "jest --watch",
|
|
44
|
+
"typecheck": "tsc --noEmit"
|
|
50
45
|
},
|
|
51
46
|
"devDependencies": {
|
|
52
47
|
"@types/jest": "^29.5.1",
|
|
@@ -55,8 +50,25 @@
|
|
|
55
50
|
"eslint-config-airbnb-base": "^14.2.1",
|
|
56
51
|
"eslint-plugin-import": "^2.22.1",
|
|
57
52
|
"jest": "^29.5.0",
|
|
53
|
+
"n8n-workflow": "=1.89.0",
|
|
58
54
|
"nock": "^13.3.1",
|
|
59
55
|
"ts-jest": "^29.1.0",
|
|
60
56
|
"typescript": "^5.6.3"
|
|
61
|
-
}
|
|
57
|
+
},
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "git+https://github.com/wassengerhq/n8n-wassenger.git"
|
|
61
|
+
},
|
|
62
|
+
"bugs": {
|
|
63
|
+
"url": "https://github.com/wassengerhq/n8n-wassenger/issues"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=20.15"
|
|
67
|
+
},
|
|
68
|
+
"files": [
|
|
69
|
+
"dist",
|
|
70
|
+
"index.js",
|
|
71
|
+
"README.md",
|
|
72
|
+
"LICENSE"
|
|
73
|
+
]
|
|
62
74
|
}
|