n8n-nodes-mautic-advanced 1.1.0 → 1.2.1
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/nodes/MauticAdvanced/CompanyDescription.js +20 -0
- package/dist/nodes/MauticAdvanced/ContactDescription.js +16 -10
- package/dist/nodes/MauticAdvanced/GenericFunctions.js +44 -22
- package/dist/nodes/MauticAdvanced/MauticAdvanced.node.js +79 -25
- package/dist/nodes/MauticAdvanced/operations/CompanyOperations.js +18 -7
- package/dist/nodes/MauticAdvanced/operations/ContactOperations.js +17 -11
- package/dist/nodes/MauticAdvanced/utils/ApiHelpers.js +15 -1
- package/dist/nodes/MauticAdvanced/utils/DataHelpers.js +64 -14
- package/dist/nodes/MauticAdvanced/utils/authenticatedRequest.js +19 -5
- package/dist/nodes/MauticAdvanced/utils/loadOptionsCache.js +102 -0
- package/dist/nodes/MauticAdvanced/utils/oauthRequestQueue.js +28 -1
- package/package.json +1 -1
|
@@ -226,6 +226,16 @@ exports.companyFields = [
|
|
|
226
226
|
type: 'number',
|
|
227
227
|
default: 0,
|
|
228
228
|
},
|
|
229
|
+
{
|
|
230
|
+
displayName: 'Owner Name or ID',
|
|
231
|
+
name: 'owner',
|
|
232
|
+
type: 'options',
|
|
233
|
+
description: 'User to assign as company owner. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
234
|
+
typeOptions: {
|
|
235
|
+
loadOptionsMethod: 'getOwners',
|
|
236
|
+
},
|
|
237
|
+
default: '',
|
|
238
|
+
},
|
|
229
239
|
{
|
|
230
240
|
displayName: 'Overwrite With Blank',
|
|
231
241
|
name: 'overwriteWithBlank',
|
|
@@ -431,6 +441,16 @@ exports.companyFields = [
|
|
|
431
441
|
type: 'number',
|
|
432
442
|
default: 0,
|
|
433
443
|
},
|
|
444
|
+
{
|
|
445
|
+
displayName: 'Owner Name or ID',
|
|
446
|
+
name: 'owner',
|
|
447
|
+
type: 'options',
|
|
448
|
+
description: 'User to assign as company owner. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
449
|
+
typeOptions: {
|
|
450
|
+
loadOptionsMethod: 'getOwners',
|
|
451
|
+
},
|
|
452
|
+
default: '',
|
|
453
|
+
},
|
|
434
454
|
{
|
|
435
455
|
displayName: 'Overwrite With Blank',
|
|
436
456
|
name: 'overwriteWithBlank',
|
|
@@ -446,11 +446,14 @@ exports.contactFields = [
|
|
|
446
446
|
default: '',
|
|
447
447
|
},
|
|
448
448
|
{
|
|
449
|
-
displayName: 'Owner ID',
|
|
450
|
-
name: '
|
|
451
|
-
type: '
|
|
449
|
+
displayName: 'Owner Name or ID',
|
|
450
|
+
name: 'owner',
|
|
451
|
+
type: 'options',
|
|
452
|
+
description: 'User to assign as contact owner. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
453
|
+
typeOptions: {
|
|
454
|
+
loadOptionsMethod: 'getOwners',
|
|
455
|
+
},
|
|
452
456
|
default: '',
|
|
453
|
-
description: 'ID of a Mautic user to assign this contact to',
|
|
454
457
|
},
|
|
455
458
|
{
|
|
456
459
|
displayName: 'Phone',
|
|
@@ -1002,16 +1005,19 @@ exports.contactFields = [
|
|
|
1002
1005
|
default: '',
|
|
1003
1006
|
},
|
|
1004
1007
|
{
|
|
1005
|
-
displayName: 'Owner ID',
|
|
1006
|
-
name: '
|
|
1007
|
-
type: '
|
|
1008
|
+
displayName: 'Owner Name or ID',
|
|
1009
|
+
name: 'owner',
|
|
1010
|
+
type: 'options',
|
|
1011
|
+
description: 'User to assign as contact owner. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
1012
|
+
typeOptions: {
|
|
1013
|
+
loadOptionsMethod: 'getOwners',
|
|
1014
|
+
},
|
|
1008
1015
|
displayOptions: {
|
|
1009
1016
|
show: {
|
|
1010
1017
|
'/jsonParameters': [false],
|
|
1011
1018
|
},
|
|
1012
1019
|
},
|
|
1013
1020
|
default: '',
|
|
1014
|
-
description: 'ID of a Mautic user to assign this contact to',
|
|
1015
1021
|
},
|
|
1016
1022
|
{
|
|
1017
1023
|
displayName: 'Phone',
|
|
@@ -1392,7 +1398,7 @@ exports.contactFields = [
|
|
|
1392
1398
|
name: 'fieldsToReturn',
|
|
1393
1399
|
type: 'multiOptions',
|
|
1394
1400
|
typeOptions: {
|
|
1395
|
-
loadOptionsMethod: '
|
|
1401
|
+
loadOptionsMethod: 'getContactFieldsToReturn',
|
|
1396
1402
|
},
|
|
1397
1403
|
default: [],
|
|
1398
1404
|
description: 'Select which fields to include in the output. Leave empty to return all fields.',
|
|
@@ -1487,7 +1493,7 @@ exports.contactFields = [
|
|
|
1487
1493
|
name: 'fieldsToReturn',
|
|
1488
1494
|
type: 'multiOptions',
|
|
1489
1495
|
typeOptions: {
|
|
1490
|
-
loadOptionsMethod: '
|
|
1496
|
+
loadOptionsMethod: 'getContactFieldsToReturn',
|
|
1491
1497
|
},
|
|
1492
1498
|
default: [],
|
|
1493
1499
|
description: 'Select which fields to include in the output. Leave empty to return all fields.',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateJSON = exports.serialiseMauticWhere = exports.mauticApiRequestAllItems = exports.mauticApiRequest = void 0;
|
|
3
|
+
exports.validateJSON = exports.serialiseMauticWhere = exports.mauticApiRequestAllItems = exports.DEFAULT_MAUTIC_PAGE_SIZE = exports.mauticApiRequest = void 0;
|
|
4
4
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
5
|
const authenticatedRequest_1 = require("./utils/authenticatedRequest");
|
|
6
6
|
async function mauticApiRequest(method, endpoint, body = {}, query, uri, headers) {
|
|
@@ -40,40 +40,62 @@ exports.mauticApiRequest = mauticApiRequest;
|
|
|
40
40
|
* Make an API request to paginated mautic endpoint
|
|
41
41
|
* and return all results
|
|
42
42
|
*/
|
|
43
|
-
|
|
43
|
+
exports.DEFAULT_MAUTIC_PAGE_SIZE = 100;
|
|
44
|
+
function toPositiveInteger(value) {
|
|
45
|
+
const numericValue = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : undefined;
|
|
46
|
+
if (numericValue === undefined || !Number.isFinite(numericValue) || numericValue <= 0) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
return Math.floor(numericValue);
|
|
50
|
+
}
|
|
51
|
+
function getPaginationStart(value) {
|
|
52
|
+
const start = toPositiveInteger(value);
|
|
53
|
+
return start ?? 0;
|
|
54
|
+
}
|
|
55
|
+
function getResponseItems(responseData, propertyName) {
|
|
56
|
+
const responseProperty = responseData[propertyName];
|
|
57
|
+
if (!responseProperty || typeof responseProperty !== 'object') {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(responseProperty)) {
|
|
61
|
+
return responseProperty;
|
|
62
|
+
}
|
|
63
|
+
return Object.values(responseProperty);
|
|
64
|
+
}
|
|
44
65
|
async function mauticApiRequestAllItems(propertyName, method, endpoint, body = {}, query = {}, maxResults) {
|
|
45
66
|
const returnData = [];
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
const baseQuery = { ...query };
|
|
68
|
+
const requestedStart = getPaginationStart(baseQuery.start);
|
|
69
|
+
const totalLimit = toPositiveInteger(maxResults);
|
|
70
|
+
delete baseQuery.start;
|
|
71
|
+
delete baseQuery.limit;
|
|
72
|
+
let currentStart = requestedStart;
|
|
73
|
+
while (totalLimit === undefined || returnData.length < totalLimit) {
|
|
74
|
+
const remaining = totalLimit === undefined ? undefined : totalLimit - returnData.length;
|
|
75
|
+
const pageLimit = remaining === undefined
|
|
76
|
+
? exports.DEFAULT_MAUTIC_PAGE_SIZE
|
|
77
|
+
: Math.min(exports.DEFAULT_MAUTIC_PAGE_SIZE, remaining);
|
|
78
|
+
const pageQuery = {
|
|
79
|
+
...baseQuery,
|
|
80
|
+
limit: pageLimit,
|
|
81
|
+
start: currentStart,
|
|
82
|
+
};
|
|
50
83
|
try {
|
|
51
|
-
responseData = await mauticApiRequest.call(this, method, endpoint, body,
|
|
84
|
+
const responseData = await mauticApiRequest.call(this, method, endpoint, body, pageQuery);
|
|
52
85
|
if (responseData.errors) {
|
|
53
86
|
throw new n8n_workflow_1.NodeApiError(this.getNode(), responseData);
|
|
54
87
|
}
|
|
55
|
-
const pageItems = responseData
|
|
56
|
-
? Object.values(responseData[propertyName])
|
|
57
|
-
: [];
|
|
88
|
+
const pageItems = getResponseItems(responseData, propertyName);
|
|
58
89
|
if (!pageItems.length) {
|
|
59
90
|
break;
|
|
60
91
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
returnData.push(...pageItems.slice(0, needed));
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
returnData.push(...pageItems);
|
|
68
|
-
}
|
|
69
|
-
query.start = Number(query.start) + pageItems.length;
|
|
70
|
-
// If less than limit returned, no more data
|
|
71
|
-
if (pageItems.length < Number(query.limit)) {
|
|
92
|
+
returnData.push(...pageItems);
|
|
93
|
+
if (pageItems.length < pageLimit) {
|
|
72
94
|
break;
|
|
73
95
|
}
|
|
96
|
+
currentStart += pageItems.length;
|
|
74
97
|
}
|
|
75
98
|
catch (error) {
|
|
76
|
-
// Optional: Only wrap non-NodeApiError errors
|
|
77
99
|
if (error instanceof n8n_workflow_1.NodeApiError || error instanceof n8n_workflow_1.NodeOperationError) {
|
|
78
100
|
throw error;
|
|
79
101
|
}
|
|
@@ -25,6 +25,7 @@ const ContactSegmentDescription_1 = require("./ContactSegmentDescription");
|
|
|
25
25
|
const EmailDescription_1 = require("./EmailDescription");
|
|
26
26
|
const FieldDescription_1 = require("./FieldDescription");
|
|
27
27
|
const GenericFunctions_1 = require("./GenericFunctions");
|
|
28
|
+
const loadOptionsCache_1 = require("./utils/loadOptionsCache");
|
|
28
29
|
const NoteDescription_1 = require("./NoteDescription");
|
|
29
30
|
const NotificationDescription_1 = require("./NotificationDescription");
|
|
30
31
|
const SegmentEmailDescription_1 = require("./SegmentEmailDescription");
|
|
@@ -230,7 +231,7 @@ class MauticAdvanced {
|
|
|
230
231
|
// select them easily
|
|
231
232
|
async getTags() {
|
|
232
233
|
const returnData = [];
|
|
233
|
-
const tags = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'tags', 'GET', '/tags');
|
|
234
|
+
const tags = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'tags', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'tags', 'GET', '/tags'));
|
|
234
235
|
for (const tag of tags) {
|
|
235
236
|
returnData.push({
|
|
236
237
|
name: tag.tag,
|
|
@@ -243,7 +244,7 @@ class MauticAdvanced {
|
|
|
243
244
|
// select them easily
|
|
244
245
|
async getStages() {
|
|
245
246
|
const returnData = [];
|
|
246
|
-
const stages = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'stages', 'GET', '/stages');
|
|
247
|
+
const stages = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'stages', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'stages', 'GET', '/stages'));
|
|
247
248
|
for (const stage of stages) {
|
|
248
249
|
returnData.push({
|
|
249
250
|
name: stage.name,
|
|
@@ -256,7 +257,7 @@ class MauticAdvanced {
|
|
|
256
257
|
// select them easily
|
|
257
258
|
async getCompanyFields() {
|
|
258
259
|
const returnData = [];
|
|
259
|
-
const fields = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'fields', 'GET', '/fields/company');
|
|
260
|
+
const fields = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'fields:company', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'fields', 'GET', '/fields/company'));
|
|
260
261
|
for (const field of fields) {
|
|
261
262
|
returnData.push({
|
|
262
263
|
name: field.label,
|
|
@@ -267,7 +268,7 @@ class MauticAdvanced {
|
|
|
267
268
|
},
|
|
268
269
|
async getIndustries() {
|
|
269
270
|
const returnData = [];
|
|
270
|
-
const fields = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'fields', 'GET', '/fields/company');
|
|
271
|
+
const fields = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'fields:company', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'fields', 'GET', '/fields/company'));
|
|
271
272
|
for (const field of fields) {
|
|
272
273
|
if (field.alias === 'companyindustry') {
|
|
273
274
|
for (const { label, value } of field.properties.list) {
|
|
@@ -284,6 +285,15 @@ class MauticAdvanced {
|
|
|
284
285
|
// select them easily
|
|
285
286
|
async getContactFields() {
|
|
286
287
|
const returnData = [];
|
|
288
|
+
const seen = new Set();
|
|
289
|
+
const addField = (field) => {
|
|
290
|
+
const value = String(field.value);
|
|
291
|
+
if (seen.has(value)) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
seen.add(value);
|
|
295
|
+
returnData.push(field);
|
|
296
|
+
};
|
|
287
297
|
// Add key system fields manually (except last_active, which is already present)
|
|
288
298
|
const systemFields = [
|
|
289
299
|
{ name: 'Date Added', value: 'date_added' },
|
|
@@ -291,11 +301,57 @@ class MauticAdvanced {
|
|
|
291
301
|
{ name: 'ID', value: 'id' },
|
|
292
302
|
{ name: 'Owner ID', value: 'owner_id' },
|
|
293
303
|
];
|
|
294
|
-
|
|
304
|
+
systemFields.forEach(addField);
|
|
295
305
|
// Fetch custom and other fields from Mautic
|
|
296
|
-
const fields = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'fields', 'GET', '/fields/contact');
|
|
306
|
+
const fields = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'fields:contact', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'fields', 'GET', '/fields/contact'));
|
|
297
307
|
for (const field of fields) {
|
|
298
|
-
|
|
308
|
+
addField({
|
|
309
|
+
name: field.label,
|
|
310
|
+
value: field.alias,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
return returnData;
|
|
314
|
+
},
|
|
315
|
+
// Get all contact output fields, including raw top-level properties that are
|
|
316
|
+
// not returned by Mautic's contact fields endpoint.
|
|
317
|
+
async getContactFieldsToReturn() {
|
|
318
|
+
const returnData = [];
|
|
319
|
+
const seen = new Set();
|
|
320
|
+
const addField = (field) => {
|
|
321
|
+
const value = String(field.value);
|
|
322
|
+
if (seen.has(value)) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
seen.add(value);
|
|
326
|
+
returnData.push(field);
|
|
327
|
+
};
|
|
328
|
+
const rawContactFields = [
|
|
329
|
+
{ name: 'Date Added', value: 'date_added' },
|
|
330
|
+
{ name: 'Date Modified', value: 'date_modified' },
|
|
331
|
+
{ name: 'ID', value: 'id' },
|
|
332
|
+
{ name: 'Owner ID', value: 'owner_id' },
|
|
333
|
+
{ name: 'Color', value: 'color' },
|
|
334
|
+
{ name: 'Created By', value: 'createdBy' },
|
|
335
|
+
{ name: 'Created By User', value: 'createdByUser' },
|
|
336
|
+
{ name: 'Date Identified', value: 'date_identified' },
|
|
337
|
+
{ name: 'Do Not Contact', value: 'doNotContact' },
|
|
338
|
+
{ name: 'Frequency Rules', value: 'frequencyRules' },
|
|
339
|
+
{ name: 'IP Addresses', value: 'ipAddresses' },
|
|
340
|
+
{ name: 'Is Published', value: 'isPublished' },
|
|
341
|
+
{ name: 'Last Active', value: 'last_active' },
|
|
342
|
+
{ name: 'Modified By', value: 'modifiedBy' },
|
|
343
|
+
{ name: 'Modified By User', value: 'modifiedByUser' },
|
|
344
|
+
{ name: 'Owner', value: 'owner' },
|
|
345
|
+
{ name: 'Points', value: 'points' },
|
|
346
|
+
{ name: 'Preferred Profile Image', value: 'preferredProfileImage' },
|
|
347
|
+
{ name: 'Stage', value: 'stage' },
|
|
348
|
+
{ name: 'Tags', value: 'tags' },
|
|
349
|
+
{ name: 'UTM Tags', value: 'utmtags' },
|
|
350
|
+
];
|
|
351
|
+
rawContactFields.forEach(addField);
|
|
352
|
+
const fields = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'fields:contact', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'fields', 'GET', '/fields/contact'));
|
|
353
|
+
for (const field of fields) {
|
|
354
|
+
addField({
|
|
299
355
|
name: field.label,
|
|
300
356
|
value: field.alias,
|
|
301
357
|
});
|
|
@@ -306,7 +362,7 @@ class MauticAdvanced {
|
|
|
306
362
|
// select them easily
|
|
307
363
|
async getSegments() {
|
|
308
364
|
const returnData = [];
|
|
309
|
-
const segments = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'lists', 'GET', '/segments');
|
|
365
|
+
const segments = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'segments', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'lists', 'GET', '/segments'));
|
|
310
366
|
for (const segment of segments) {
|
|
311
367
|
returnData.push({
|
|
312
368
|
name: segment.name,
|
|
@@ -318,7 +374,7 @@ class MauticAdvanced {
|
|
|
318
374
|
// Get all the available segments (by alias) for search filtering
|
|
319
375
|
async getSegmentAliases() {
|
|
320
376
|
const returnData = [];
|
|
321
|
-
const segments = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'lists', 'GET', '/segments');
|
|
377
|
+
const segments = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'segments', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'lists', 'GET', '/segments'));
|
|
322
378
|
for (const segment of segments) {
|
|
323
379
|
// Use alias if available, otherwise skip (alias is required for search syntax)
|
|
324
380
|
const alias = segment.alias || segment.id?.toString();
|
|
@@ -334,15 +390,13 @@ class MauticAdvanced {
|
|
|
334
390
|
// Get all the available owners (users) for filtering
|
|
335
391
|
async getOwners() {
|
|
336
392
|
const returnData = [];
|
|
337
|
-
const response = await GenericFunctions_1.mauticApiRequest.call(this, 'GET', '/
|
|
338
|
-
const
|
|
339
|
-
for (const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
value: owner.id,
|
|
345
|
-
});
|
|
393
|
+
const response = await GenericFunctions_1.mauticApiRequest.call(this, 'GET', '/users');
|
|
394
|
+
const users = response?.users ? Object.values(response.users) : [];
|
|
395
|
+
for (const user of users) {
|
|
396
|
+
const fullName = `${user.firstName || ''} ${user.lastName || ''}`.trim();
|
|
397
|
+
const display = fullName || user.username || `User ${user.id}`;
|
|
398
|
+
const label = user.email ? `${display} (${user.email})` : display;
|
|
399
|
+
returnData.push({ name: label, value: user.id });
|
|
346
400
|
}
|
|
347
401
|
return returnData;
|
|
348
402
|
},
|
|
@@ -350,7 +404,7 @@ class MauticAdvanced {
|
|
|
350
404
|
// select them easily
|
|
351
405
|
async getCampaigns() {
|
|
352
406
|
const returnData = [];
|
|
353
|
-
const campaings = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'campaigns', 'GET', '/campaigns');
|
|
407
|
+
const campaings = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'campaigns', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'campaigns', 'GET', '/campaigns'));
|
|
354
408
|
for (const campaign of campaings) {
|
|
355
409
|
returnData.push({
|
|
356
410
|
name: campaign.name,
|
|
@@ -363,7 +417,7 @@ class MauticAdvanced {
|
|
|
363
417
|
// select them easily
|
|
364
418
|
async getEmails() {
|
|
365
419
|
const returnData = [];
|
|
366
|
-
const emails = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'emails', 'GET', '/emails');
|
|
420
|
+
const emails = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'emails', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'emails', 'GET', '/emails'));
|
|
367
421
|
for (const email of emails) {
|
|
368
422
|
returnData.push({
|
|
369
423
|
name: email.name,
|
|
@@ -376,7 +430,7 @@ class MauticAdvanced {
|
|
|
376
430
|
// select them easily
|
|
377
431
|
async getSegmentEmails() {
|
|
378
432
|
const returnData = [];
|
|
379
|
-
const emails = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'emails', 'GET', '/emails');
|
|
433
|
+
const emails = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'emails', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'emails', 'GET', '/emails'));
|
|
380
434
|
for (const email of emails) {
|
|
381
435
|
if (email.emailType === 'list') {
|
|
382
436
|
returnData.push({
|
|
@@ -391,7 +445,7 @@ class MauticAdvanced {
|
|
|
391
445
|
// select them easily
|
|
392
446
|
async getCampaignEmails() {
|
|
393
447
|
const returnData = [];
|
|
394
|
-
const emails = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'emails', 'GET', '/emails');
|
|
448
|
+
const emails = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'emails', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'emails', 'GET', '/emails'));
|
|
395
449
|
for (const email of emails) {
|
|
396
450
|
if (email.emailType === 'template') {
|
|
397
451
|
returnData.push({
|
|
@@ -406,7 +460,7 @@ class MauticAdvanced {
|
|
|
406
460
|
// select them easily
|
|
407
461
|
async getEmailCategories() {
|
|
408
462
|
const returnData = [];
|
|
409
|
-
const categories = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'categories', 'GET', '/categories');
|
|
463
|
+
const categories = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'categories', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'categories', 'GET', '/categories'));
|
|
410
464
|
for (const category of categories) {
|
|
411
465
|
// Filter for email bundle categories
|
|
412
466
|
if (category.bundle === 'email' || category.bundle === 'global') {
|
|
@@ -422,7 +476,7 @@ class MauticAdvanced {
|
|
|
422
476
|
// select them easily
|
|
423
477
|
async getForms() {
|
|
424
478
|
const returnData = [];
|
|
425
|
-
const forms = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'forms', 'GET', '/forms');
|
|
479
|
+
const forms = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'forms', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'forms', 'GET', '/forms'));
|
|
426
480
|
for (const form of forms) {
|
|
427
481
|
returnData.push({
|
|
428
482
|
name: form.name,
|
|
@@ -435,7 +489,7 @@ class MauticAdvanced {
|
|
|
435
489
|
// select them easily
|
|
436
490
|
async getAssets() {
|
|
437
491
|
const returnData = [];
|
|
438
|
-
const assets = await GenericFunctions_1.mauticApiRequestAllItems.call(this, 'assets', 'GET', '/assets');
|
|
492
|
+
const assets = await (0, loadOptionsCache_1.getCachedMauticLoadOptions)(this, 'assets', () => GenericFunctions_1.mauticApiRequestAllItems.call(this, 'assets', 'GET', '/assets'));
|
|
439
493
|
for (const asset of assets) {
|
|
440
494
|
returnData.push({
|
|
441
495
|
name: asset.title,
|
|
@@ -38,7 +38,7 @@ async function createCompany(context, itemIndex) {
|
|
|
38
38
|
const name = (0, ApiHelpers_1.getRequiredParam)(context, 'name', itemIndex);
|
|
39
39
|
const body = { companyname: name };
|
|
40
40
|
const additionalFields = (0, ApiHelpers_1.getOptionalParam)(context, 'additionalFields', itemIndex, {});
|
|
41
|
-
const { addressUi, customFieldsUi, companyEmail, fax, industry, numberOfEmployees, phone, website, annualRevenue, description, ...rest } = additionalFields;
|
|
41
|
+
const { addressUi, customFieldsUi, companyEmail, fax, industry, numberOfEmployees, owner, phone, website, annualRevenue, description, ...rest } = additionalFields;
|
|
42
42
|
if (addressUi?.addressValues) {
|
|
43
43
|
const { addressValues } = addressUi;
|
|
44
44
|
body.companyaddress1 = addressValues.address1;
|
|
@@ -56,6 +56,8 @@ async function createCompany(context, itemIndex) {
|
|
|
56
56
|
body.companyindustry = industry;
|
|
57
57
|
if (numberOfEmployees)
|
|
58
58
|
body.companynumber_of_employees = numberOfEmployees;
|
|
59
|
+
if (owner)
|
|
60
|
+
body.owner = owner;
|
|
59
61
|
if (phone)
|
|
60
62
|
body.companyphone = phone;
|
|
61
63
|
if (website)
|
|
@@ -73,7 +75,7 @@ async function createCompany(context, itemIndex) {
|
|
|
73
75
|
const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', '/companies/new', body);
|
|
74
76
|
let result = response.company;
|
|
75
77
|
if (simple) {
|
|
76
|
-
result = result
|
|
78
|
+
result = toSimpleCompany(result);
|
|
77
79
|
}
|
|
78
80
|
return result;
|
|
79
81
|
}
|
|
@@ -82,7 +84,7 @@ async function updateCompany(context, itemIndex) {
|
|
|
82
84
|
const simple = (0, ApiHelpers_1.getOptionalParam)(context, 'simple', itemIndex, false);
|
|
83
85
|
const body = {};
|
|
84
86
|
const updateFields = (0, ApiHelpers_1.getOptionalParam)(context, 'updateFields', itemIndex, {});
|
|
85
|
-
const { addressUi, customFieldsUi, companyEmail, name, fax, industry, numberOfEmployees, phone, website, annualRevenue, description, ...rest } = updateFields;
|
|
87
|
+
const { addressUi, customFieldsUi, companyEmail, name, fax, industry, numberOfEmployees, owner, phone, website, annualRevenue, description, ...rest } = updateFields;
|
|
86
88
|
if (addressUi?.addressValues) {
|
|
87
89
|
const { addressValues } = addressUi;
|
|
88
90
|
body.companyaddress1 = addressValues.address1;
|
|
@@ -102,6 +104,8 @@ async function updateCompany(context, itemIndex) {
|
|
|
102
104
|
body.companyindustry = industry;
|
|
103
105
|
if (numberOfEmployees)
|
|
104
106
|
body.companynumber_of_employees = numberOfEmployees;
|
|
107
|
+
if (owner)
|
|
108
|
+
body.owner = owner;
|
|
105
109
|
if (phone)
|
|
106
110
|
body.companyphone = phone;
|
|
107
111
|
if (website)
|
|
@@ -119,7 +123,7 @@ async function updateCompany(context, itemIndex) {
|
|
|
119
123
|
const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'PATCH', `/companies/${companyId}/edit`, body);
|
|
120
124
|
let result = response.company;
|
|
121
125
|
if (simple) {
|
|
122
|
-
result = result
|
|
126
|
+
result = toSimpleCompany(result);
|
|
123
127
|
}
|
|
124
128
|
return result;
|
|
125
129
|
}
|
|
@@ -129,7 +133,7 @@ async function getCompany(context, itemIndex) {
|
|
|
129
133
|
const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/companies/${companyId}`);
|
|
130
134
|
let result = response.company;
|
|
131
135
|
if (simple) {
|
|
132
|
-
result = result
|
|
136
|
+
result = toSimpleCompany(result);
|
|
133
137
|
}
|
|
134
138
|
return (0, DataHelpers_1.convertNumericStrings)(result);
|
|
135
139
|
}
|
|
@@ -154,7 +158,7 @@ async function getAllCompanies(context, itemIndex) {
|
|
|
154
158
|
responseData = (response.companies ? Object.values(response.companies) : []);
|
|
155
159
|
}
|
|
156
160
|
if (simple) {
|
|
157
|
-
responseData = responseData.map((item) => item
|
|
161
|
+
responseData = responseData.map((item) => toSimpleCompany(item));
|
|
158
162
|
}
|
|
159
163
|
return (0, DataHelpers_1.convertNumericStrings)(responseData);
|
|
160
164
|
}
|
|
@@ -164,7 +168,14 @@ async function deleteCompany(context, itemIndex) {
|
|
|
164
168
|
const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'DELETE', `/companies/${companyId}/delete`);
|
|
165
169
|
let result = response.company;
|
|
166
170
|
if (simple) {
|
|
167
|
-
result = result
|
|
171
|
+
result = toSimpleCompany(result);
|
|
168
172
|
}
|
|
169
173
|
return result;
|
|
170
174
|
}
|
|
175
|
+
function toSimpleCompany(company) {
|
|
176
|
+
return {
|
|
177
|
+
id: company.id,
|
|
178
|
+
owner: company.owner ?? null,
|
|
179
|
+
...company.fields.all,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -5,6 +5,7 @@ const n8n_workflow_1 = require("n8n-workflow");
|
|
|
5
5
|
const change_case_1 = require("change-case");
|
|
6
6
|
const ApiHelpers_1 = require("../utils/ApiHelpers");
|
|
7
7
|
const DataHelpers_1 = require("../utils/DataHelpers");
|
|
8
|
+
const GenericFunctions_1 = require("../GenericFunctions");
|
|
8
9
|
async function executeContactOperation(context, operation, i) {
|
|
9
10
|
let responseData;
|
|
10
11
|
try {
|
|
@@ -245,7 +246,8 @@ async function getAllContacts(context, itemIndex) {
|
|
|
245
246
|
const anyDncOnly = options.anyDncOnly === true;
|
|
246
247
|
const useDncPostFilter = emailDncOnly || smsDncOnly || anyDncOnly;
|
|
247
248
|
if (useDncPostFilter) {
|
|
248
|
-
|
|
249
|
+
const limit = returnAll ? undefined : (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, 30);
|
|
250
|
+
responseData = await getContactsWithDncFilter(context, qs, options, limit);
|
|
249
251
|
}
|
|
250
252
|
else {
|
|
251
253
|
if (returnAll) {
|
|
@@ -622,13 +624,15 @@ async function getContactsWithDncFilter(context, qs, options, limit) {
|
|
|
622
624
|
const emailDncOnly = options.emailDncOnly === true;
|
|
623
625
|
const smsDncOnly = options.smsDncOnly === true;
|
|
624
626
|
const anyDncOnly = options.anyDncOnly === true;
|
|
625
|
-
const
|
|
626
|
-
const
|
|
627
|
+
const requestedStart = Number(qs.start ?? 0);
|
|
628
|
+
const maxResults = typeof limit === 'number' && limit > 0 ? Math.floor(limit) : undefined;
|
|
627
629
|
const contacts = [];
|
|
628
|
-
let remaining =
|
|
629
|
-
let currentStart =
|
|
630
|
-
while (remaining > 0) {
|
|
631
|
-
const pageLimit =
|
|
630
|
+
let remaining = maxResults;
|
|
631
|
+
let currentStart = Number.isFinite(requestedStart) && requestedStart > 0 ? requestedStart : 0;
|
|
632
|
+
while (remaining === undefined || remaining > 0) {
|
|
633
|
+
const pageLimit = remaining === undefined
|
|
634
|
+
? GenericFunctions_1.DEFAULT_MAUTIC_PAGE_SIZE
|
|
635
|
+
: Math.min(remaining, GenericFunctions_1.DEFAULT_MAUTIC_PAGE_SIZE);
|
|
632
636
|
const pageQs = { ...qs, start: currentStart, limit: pageLimit };
|
|
633
637
|
const pageResponse = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/contacts', {}, pageQs);
|
|
634
638
|
const pageContacts = pageResponse.contacts ? Object.values(pageResponse.contacts) : [];
|
|
@@ -644,17 +648,19 @@ async function getContactsWithDncFilter(context, qs, options, limit) {
|
|
|
644
648
|
return hasEmailDnc || hasSmsDnc;
|
|
645
649
|
return true;
|
|
646
650
|
});
|
|
647
|
-
const toAdd = filtered.slice(0, remaining);
|
|
651
|
+
const toAdd = remaining === undefined ? filtered : filtered.slice(0, remaining);
|
|
648
652
|
contacts.push(...toAdd);
|
|
653
|
+
if (remaining !== undefined) {
|
|
654
|
+
remaining -= toAdd.length;
|
|
655
|
+
}
|
|
649
656
|
if (pageContacts.length < pageLimit)
|
|
650
657
|
break;
|
|
651
|
-
|
|
652
|
-
currentStart += pageLimit;
|
|
658
|
+
currentStart += pageContacts.length;
|
|
653
659
|
}
|
|
654
660
|
return contacts;
|
|
655
661
|
}
|
|
656
662
|
async function getContactOwners(context) {
|
|
657
|
-
const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/
|
|
663
|
+
const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/users');
|
|
658
664
|
return (0, DataHelpers_1.convertNumericStrings)(response);
|
|
659
665
|
}
|
|
660
666
|
async function getContactFields(context) {
|
|
@@ -17,10 +17,24 @@ async function makeApiRequest(context, method, endpoint, body = {}, query = {},
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
exports.makeApiRequest = makeApiRequest;
|
|
20
|
+
function toOptionalPositiveInteger(value) {
|
|
21
|
+
const numericValue = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : undefined;
|
|
22
|
+
if (numericValue === undefined || !Number.isFinite(numericValue) || numericValue <= 0) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
return Math.floor(numericValue);
|
|
26
|
+
}
|
|
27
|
+
function preparePaginatedQuery(query, explicitLimit) {
|
|
28
|
+
const queryForPagination = { ...query };
|
|
29
|
+
const limit = explicitLimit ?? toOptionalPositiveInteger(queryForPagination.limit);
|
|
30
|
+
delete queryForPagination.limit;
|
|
31
|
+
return { query: queryForPagination, limit };
|
|
32
|
+
}
|
|
20
33
|
// Standardized paginated API request wrapper
|
|
21
34
|
async function makePaginatedRequest(context, resource, method, endpoint, body = {}, query = {}, limit) {
|
|
35
|
+
const pagination = preparePaginatedQuery(query, limit);
|
|
22
36
|
try {
|
|
23
|
-
return await GenericFunctions_1.mauticApiRequestAllItems.call(context, resource, method, endpoint, body, query, limit);
|
|
37
|
+
return await GenericFunctions_1.mauticApiRequestAllItems.call(context, resource, method, endpoint, body, pagination.query, pagination.limit);
|
|
24
38
|
}
|
|
25
39
|
catch (error) {
|
|
26
40
|
if (error instanceof n8n_workflow_1.NodeApiError || error instanceof n8n_workflow_1.NodeOperationError) {
|
|
@@ -25,27 +25,77 @@ function wrapSingleItem(data) {
|
|
|
25
25
|
exports.wrapSingleItem = wrapSingleItem;
|
|
26
26
|
// Process contact fields data for simple mode
|
|
27
27
|
function processContactFields(responseData, options, fieldsToReturn) {
|
|
28
|
+
const sourceData = Array.isArray(responseData) ? responseData : [responseData];
|
|
29
|
+
const filterFields = fieldsToReturn ?? options.fieldsToReturn;
|
|
28
30
|
if (options.rawData === true) {
|
|
29
|
-
|
|
31
|
+
if (Array.isArray(filterFields) && filterFields.length > 0) {
|
|
32
|
+
return sourceData.map((item) => filterContactFields(item, item, filterFields));
|
|
33
|
+
}
|
|
34
|
+
return sourceData;
|
|
30
35
|
}
|
|
31
|
-
const processedData =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
const processedData = sourceData.map((item) => {
|
|
37
|
+
const all = item.fields?.all;
|
|
38
|
+
if (!all)
|
|
39
|
+
return item;
|
|
40
|
+
return { id: item.id, owner: item.owner ?? null, ...all };
|
|
41
|
+
});
|
|
35
42
|
if (Array.isArray(filterFields) && filterFields.length > 0) {
|
|
36
|
-
return processedData.map((item) =>
|
|
37
|
-
const filtered = {};
|
|
38
|
-
for (const field of filterFields) {
|
|
39
|
-
if (Object.prototype.hasOwnProperty.call(item, field)) {
|
|
40
|
-
filtered[field] = item[field];
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return filtered;
|
|
44
|
-
});
|
|
43
|
+
return processedData.map((item, index) => filterContactFields(item, sourceData[index], filterFields));
|
|
45
44
|
}
|
|
46
45
|
return processedData;
|
|
47
46
|
}
|
|
48
47
|
exports.processContactFields = processContactFields;
|
|
48
|
+
function filterContactFields(preferredSource, originalSource, filterFields) {
|
|
49
|
+
const filtered = {};
|
|
50
|
+
for (const field of filterFields) {
|
|
51
|
+
const preferredLookup = readContactField(preferredSource, field);
|
|
52
|
+
const lookup = preferredLookup.found
|
|
53
|
+
? preferredLookup
|
|
54
|
+
: readContactField(originalSource, field);
|
|
55
|
+
if (lookup.found && lookup.value !== undefined) {
|
|
56
|
+
filtered[field] = lookup.value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return filtered;
|
|
60
|
+
}
|
|
61
|
+
function readContactField(source, field) {
|
|
62
|
+
if (!source) {
|
|
63
|
+
return { found: false };
|
|
64
|
+
}
|
|
65
|
+
if (Object.prototype.hasOwnProperty.call(source, field)) {
|
|
66
|
+
return { found: true, value: source[field] };
|
|
67
|
+
}
|
|
68
|
+
const rawField = readRawContactFieldAlias(source, field);
|
|
69
|
+
if (rawField.found) {
|
|
70
|
+
return rawField;
|
|
71
|
+
}
|
|
72
|
+
const fieldsAll = source.fields?.all;
|
|
73
|
+
if (fieldsAll && Object.prototype.hasOwnProperty.call(fieldsAll, field)) {
|
|
74
|
+
return { found: true, value: fieldsAll[field] };
|
|
75
|
+
}
|
|
76
|
+
return { found: false };
|
|
77
|
+
}
|
|
78
|
+
function readRawContactFieldAlias(source, field) {
|
|
79
|
+
const rawAliasMap = {
|
|
80
|
+
date_added: ['dateAdded'],
|
|
81
|
+
date_identified: ['dateIdentified'],
|
|
82
|
+
date_modified: ['dateModified'],
|
|
83
|
+
last_active: ['lastActive'],
|
|
84
|
+
};
|
|
85
|
+
for (const alias of rawAliasMap[field] || []) {
|
|
86
|
+
if (Object.prototype.hasOwnProperty.call(source, alias)) {
|
|
87
|
+
return { found: true, value: source[alias] };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (field === 'owner_id' && Object.prototype.hasOwnProperty.call(source, 'owner')) {
|
|
91
|
+
const owner = source.owner;
|
|
92
|
+
if (owner && typeof owner === 'object' && Object.prototype.hasOwnProperty.call(owner, 'id')) {
|
|
93
|
+
return { found: true, value: owner.id };
|
|
94
|
+
}
|
|
95
|
+
return { found: true, value: owner };
|
|
96
|
+
}
|
|
97
|
+
return { found: false };
|
|
98
|
+
}
|
|
49
99
|
// Validate and parse JSON input parameters
|
|
50
100
|
function validateJsonParameter(context, paramName, itemIndex) {
|
|
51
101
|
const jsonString = context.getNodeParameter(paramName, itemIndex);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.requestMauticAuthenticated = void 0;
|
|
4
4
|
const oauthRequestQueue_1 = require("./oauthRequestQueue");
|
|
5
|
+
const authErrorHelpers_1 = require("./authErrorHelpers");
|
|
5
6
|
function trimTrailingSlash(url) {
|
|
6
7
|
return url.endsWith('/') ? url.slice(0, -1) : url;
|
|
7
8
|
}
|
|
@@ -34,6 +35,11 @@ function getOAuthCredentialQueueKey(node) {
|
|
|
34
35
|
const keyPart = credentialRefPart(credentialRef) ?? 'unknown';
|
|
35
36
|
return `mauticAdvancedOAuth2Api:${keyPart}`;
|
|
36
37
|
}
|
|
38
|
+
async function requestOAuth2WithMauticOptions(context, helperContext, requestOptions) {
|
|
39
|
+
return (await helperContext.helpers.requestOAuth2.call(context, 'mauticAdvancedOAuth2Api', requestOptions, {
|
|
40
|
+
includeCredentialsOnRefreshOnBody: true,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
37
43
|
async function requestMauticAuthenticated(context, authenticationMethod, options) {
|
|
38
44
|
const credentialType = authenticationMethod === 'credentials' ? 'mauticAdvancedApi' : 'mauticAdvancedOAuth2Api';
|
|
39
45
|
const credentials = await context.getCredentials(credentialType);
|
|
@@ -43,10 +49,18 @@ async function requestMauticAuthenticated(context, authenticationMethod, options
|
|
|
43
49
|
return (await helperContext.helpers.requestWithAuthentication.call(context, 'mauticAdvancedApi', requestOptions));
|
|
44
50
|
}
|
|
45
51
|
const queueKey = getOAuthCredentialQueueKey(context.getNode());
|
|
46
|
-
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
try {
|
|
53
|
+
return await (0, oauthRequestQueue_1.runTrackedCredentialRequest)(queueKey, async () => requestOAuth2WithMauticOptions(context, helperContext, requestOptions));
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (!(0, authErrorHelpers_1.isInvalidGrantError)(error)) {
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
await (0, oauthRequestQueue_1.waitForTrackedCredentialRequests)(queueKey);
|
|
60
|
+
return await (0, oauthRequestQueue_1.runWithCredentialLock)(queueKey, async () => {
|
|
61
|
+
await (0, oauthRequestQueue_1.waitForTrackedCredentialRequests)(queueKey);
|
|
62
|
+
return await requestOAuth2WithMauticOptions(context, helperContext, requestOptions);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
51
65
|
}
|
|
52
66
|
exports.requestMauticAuthenticated = requestMauticAuthenticated;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clearMauticLoadOptionsCacheForTests = exports.getCachedMauticLoadOptions = exports.MAUTIC_LOAD_OPTIONS_CACHE_TTL_MS = void 0;
|
|
4
|
+
exports.MAUTIC_LOAD_OPTIONS_CACHE_TTL_MS = 60000;
|
|
5
|
+
const loadOptionsCache = new Map();
|
|
6
|
+
function getCredentialType(authenticationMethod) {
|
|
7
|
+
return authenticationMethod === 'credentials' ? 'mauticAdvancedApi' : 'mauticAdvancedOAuth2Api';
|
|
8
|
+
}
|
|
9
|
+
function withoutTrailingSlashes(value) {
|
|
10
|
+
return value.replace(/\/+$/, '');
|
|
11
|
+
}
|
|
12
|
+
function normalizeCredentialUrl(value) {
|
|
13
|
+
if (typeof value !== 'string') {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
const trimmed = value.trim();
|
|
17
|
+
if (!trimmed) {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const url = new URL(trimmed);
|
|
22
|
+
url.protocol = url.protocol.toLowerCase();
|
|
23
|
+
url.hostname = url.hostname.toLowerCase();
|
|
24
|
+
url.hash = '';
|
|
25
|
+
url.search = '';
|
|
26
|
+
url.pathname = withoutTrailingSlashes(url.pathname);
|
|
27
|
+
return withoutTrailingSlashes(url.toString());
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return withoutTrailingSlashes(trimmed);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function getMauticVersion(credentials) {
|
|
34
|
+
return String(credentials.mauticVersion || 'v6');
|
|
35
|
+
}
|
|
36
|
+
function credentialRefPart(value) {
|
|
37
|
+
if (typeof value === 'string' && value) {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value !== 'object' || value === null) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const credentialRef = value;
|
|
44
|
+
if (credentialRef.id) {
|
|
45
|
+
return credentialRef.id;
|
|
46
|
+
}
|
|
47
|
+
if (credentialRef.name) {
|
|
48
|
+
return credentialRef.name;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
function getCredentialReference(context, credentialType) {
|
|
53
|
+
const contextWithNode = context;
|
|
54
|
+
const credentialRef = contextWithNode.getNode?.().credentials?.[credentialType];
|
|
55
|
+
const nodeCredentialPart = credentialRefPart(credentialRef);
|
|
56
|
+
if (nodeCredentialPart) {
|
|
57
|
+
return nodeCredentialPart;
|
|
58
|
+
}
|
|
59
|
+
return 'unknown';
|
|
60
|
+
}
|
|
61
|
+
function getCacheKey(authenticationMethod, credentialType, credentialReference, credentials, datasetKey) {
|
|
62
|
+
return JSON.stringify([
|
|
63
|
+
authenticationMethod,
|
|
64
|
+
credentialType,
|
|
65
|
+
credentialReference,
|
|
66
|
+
normalizeCredentialUrl(credentials.url),
|
|
67
|
+
getMauticVersion(credentials),
|
|
68
|
+
datasetKey,
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
async function getCachedMauticLoadOptions(context, datasetKey, loadOptions, ttlMs = exports.MAUTIC_LOAD_OPTIONS_CACHE_TTL_MS) {
|
|
72
|
+
const authenticationMethod = context.getNodeParameter('authentication', 'credentials');
|
|
73
|
+
const credentialType = getCredentialType(authenticationMethod);
|
|
74
|
+
const credentials = await context.getCredentials(credentialType);
|
|
75
|
+
const credentialReference = getCredentialReference(context, credentialType);
|
|
76
|
+
const cacheKey = getCacheKey(authenticationMethod, credentialType, credentialReference, credentials, datasetKey);
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
const cachedEntry = loadOptionsCache.get(cacheKey);
|
|
79
|
+
if (cachedEntry && cachedEntry.expiresAt > now) {
|
|
80
|
+
return await cachedEntry.promise;
|
|
81
|
+
}
|
|
82
|
+
if (cachedEntry) {
|
|
83
|
+
loadOptionsCache.delete(cacheKey);
|
|
84
|
+
}
|
|
85
|
+
const promise = (async () => loadOptions())().catch((error) => {
|
|
86
|
+
const latestEntry = loadOptionsCache.get(cacheKey);
|
|
87
|
+
if (latestEntry?.promise === promise) {
|
|
88
|
+
loadOptionsCache.delete(cacheKey);
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
});
|
|
92
|
+
loadOptionsCache.set(cacheKey, {
|
|
93
|
+
expiresAt: now + ttlMs,
|
|
94
|
+
promise,
|
|
95
|
+
});
|
|
96
|
+
return await promise;
|
|
97
|
+
}
|
|
98
|
+
exports.getCachedMauticLoadOptions = getCachedMauticLoadOptions;
|
|
99
|
+
function clearMauticLoadOptionsCacheForTests() {
|
|
100
|
+
loadOptionsCache.clear();
|
|
101
|
+
}
|
|
102
|
+
exports.clearMauticLoadOptionsCacheForTests = clearMauticLoadOptionsCacheForTests;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports._getOAuthRequestQueueSizeForTests = exports.runWithCredentialLock = void 0;
|
|
3
|
+
exports._getTrackedOAuthRequestCountForTests = exports._getOAuthRequestQueueSizeForTests = exports.waitForTrackedCredentialRequests = exports.runTrackedCredentialRequest = exports.runWithCredentialLock = void 0;
|
|
4
4
|
const oauthRequestQueue = new Map();
|
|
5
|
+
const oauthInFlightRequests = new Map();
|
|
5
6
|
async function runWithCredentialLock(key, fn) {
|
|
6
7
|
const previous = oauthRequestQueue.get(key) ?? Promise.resolve();
|
|
7
8
|
const run = (async () => {
|
|
@@ -20,7 +21,33 @@ async function runWithCredentialLock(key, fn) {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
exports.runWithCredentialLock = runWithCredentialLock;
|
|
24
|
+
async function runTrackedCredentialRequest(key, fn) {
|
|
25
|
+
const run = Promise.resolve().then(fn);
|
|
26
|
+
const tracker = run.then(() => undefined, () => undefined);
|
|
27
|
+
const requests = oauthInFlightRequests.get(key) ?? new Set();
|
|
28
|
+
requests.add(tracker);
|
|
29
|
+
oauthInFlightRequests.set(key, requests);
|
|
30
|
+
try {
|
|
31
|
+
return await run;
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
requests.delete(tracker);
|
|
35
|
+
if (requests.size === 0) {
|
|
36
|
+
oauthInFlightRequests.delete(key);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.runTrackedCredentialRequest = runTrackedCredentialRequest;
|
|
41
|
+
async function waitForTrackedCredentialRequests(key) {
|
|
42
|
+
const requests = Array.from(oauthInFlightRequests.get(key) ?? []);
|
|
43
|
+
await Promise.all(requests.map(async (request) => await request.catch(() => undefined)));
|
|
44
|
+
}
|
|
45
|
+
exports.waitForTrackedCredentialRequests = waitForTrackedCredentialRequests;
|
|
23
46
|
function _getOAuthRequestQueueSizeForTests() {
|
|
24
47
|
return oauthRequestQueue.size;
|
|
25
48
|
}
|
|
26
49
|
exports._getOAuthRequestQueueSizeForTests = _getOAuthRequestQueueSizeForTests;
|
|
50
|
+
function _getTrackedOAuthRequestCountForTests(key) {
|
|
51
|
+
return oauthInFlightRequests.get(key)?.size ?? 0;
|
|
52
|
+
}
|
|
53
|
+
exports._getTrackedOAuthRequestCountForTests = _getTrackedOAuthRequestCountForTests;
|
package/package.json
CHANGED