n8n-nodes-linq 0.2.0 → 3.1.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/credentials/LinqApi.credentials.d.ts +8 -0
- package/dist/credentials/LinqApi.credentials.js +9 -1
- package/dist/nodes/Linq/Linq.node.js +165 -81
- package/dist/nodes/LinqTrigger/LinqTrigger.node.d.ts +2 -2
- package/dist/nodes/LinqTrigger/LinqTrigger.node.js +89 -19
- package/package.json +1 -1
|
@@ -4,4 +4,12 @@ export declare class LinqApi implements ICredentialType {
|
|
|
4
4
|
displayName: string;
|
|
5
5
|
documentationUrl: string;
|
|
6
6
|
properties: INodeProperties[];
|
|
7
|
+
authenticate: {
|
|
8
|
+
readonly type: "generic";
|
|
9
|
+
readonly properties: {
|
|
10
|
+
readonly headers: {
|
|
11
|
+
readonly Authorization: "Bearer {{$credentials.integrationToken}}";
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
};
|
|
7
15
|
}
|
|
@@ -12,12 +12,20 @@ class LinqApi {
|
|
|
12
12
|
name: 'integrationToken',
|
|
13
13
|
type: 'string',
|
|
14
14
|
default: '',
|
|
15
|
-
description: 'Your
|
|
15
|
+
description: 'Your Linq Integration Token',
|
|
16
16
|
typeOptions: {
|
|
17
17
|
password: true,
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
];
|
|
21
|
+
this.authenticate = {
|
|
22
|
+
type: 'generic',
|
|
23
|
+
properties: {
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: 'Bearer {{$credentials.integrationToken}}',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
21
29
|
}
|
|
22
30
|
}
|
|
23
31
|
exports.LinqApi = LinqApi;
|
|
@@ -224,7 +224,7 @@ class Linq {
|
|
|
224
224
|
type: 'string',
|
|
225
225
|
displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
|
|
226
226
|
default: '',
|
|
227
|
-
description: 'Required: your Linq phone number (
|
|
227
|
+
description: 'Required: your Linq phone number (from)',
|
|
228
228
|
},
|
|
229
229
|
{
|
|
230
230
|
displayName: 'From Phone Number',
|
|
@@ -243,18 +243,29 @@ class Linq {
|
|
|
243
243
|
description: 'Comma-separated list of phone numbers to find chat between them',
|
|
244
244
|
},
|
|
245
245
|
{
|
|
246
|
-
displayName: '
|
|
247
|
-
name: '
|
|
246
|
+
displayName: 'Cursor',
|
|
247
|
+
name: 'cursor',
|
|
248
|
+
type: 'string',
|
|
249
|
+
displayOptions: { show: { resource: ['chat', 'chatMessage'], operation: ['getAll'] } },
|
|
250
|
+
default: '',
|
|
251
|
+
description: 'Cursor for pagination',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
displayName: 'Limit',
|
|
255
|
+
name: 'limit',
|
|
248
256
|
type: 'number',
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
257
|
+
typeOptions: {
|
|
258
|
+
minValue: 1,
|
|
259
|
+
},
|
|
260
|
+
displayOptions: { show: { resource: ['chat', 'chatMessage'], operation: ['getAll'] } },
|
|
261
|
+
default: 50,
|
|
262
|
+
description: 'Max number of results to return',
|
|
252
263
|
},
|
|
253
264
|
{
|
|
254
265
|
displayName: 'Per Page',
|
|
255
266
|
name: 'perPage',
|
|
256
267
|
type: 'number',
|
|
257
|
-
displayOptions: { show: { resource: [
|
|
268
|
+
displayOptions: { show: { resource: [], operation: [] } }, // Deprecated, hiding but keeping for compatibility if needed, though we should probably migrate logic
|
|
258
269
|
default: 25,
|
|
259
270
|
description: 'Number of items per page',
|
|
260
271
|
},
|
|
@@ -291,6 +302,14 @@ class Linq {
|
|
|
291
302
|
default: '',
|
|
292
303
|
description: 'The text of the message to send',
|
|
293
304
|
},
|
|
305
|
+
{
|
|
306
|
+
displayName: 'Attachment URLs',
|
|
307
|
+
name: 'attachmentUrls',
|
|
308
|
+
type: 'string',
|
|
309
|
+
displayOptions: { show: { resource: ['chat'], operation: ['create'] } },
|
|
310
|
+
default: '',
|
|
311
|
+
description: 'Comma-separated list of attachment URLs',
|
|
312
|
+
},
|
|
294
313
|
// Chat Message parameters
|
|
295
314
|
{
|
|
296
315
|
displayName: 'Chat Message ID',
|
|
@@ -488,6 +507,67 @@ class Linq {
|
|
|
488
507
|
const items = this.getInputData();
|
|
489
508
|
const returnData = [];
|
|
490
509
|
const credentials = await this.getCredentials('linqApi');
|
|
510
|
+
const uploadAttachment = async (url) => {
|
|
511
|
+
const fileResponse = await this.helpers.request({
|
|
512
|
+
method: 'GET',
|
|
513
|
+
url,
|
|
514
|
+
encoding: null,
|
|
515
|
+
resolveWithFullResponse: true,
|
|
516
|
+
});
|
|
517
|
+
const buffer = fileResponse.body;
|
|
518
|
+
// Limit file size to 50MB to avoid OOM
|
|
519
|
+
// Note: This loads the file into memory. For larger files, streaming would be preferred
|
|
520
|
+
// but is not implemented here to maintain compatibility with n8n helper request patterns.
|
|
521
|
+
const MAX_SIZE = 50 * 1024 * 1024;
|
|
522
|
+
if (buffer.length > MAX_SIZE) {
|
|
523
|
+
throw new n8n_workflow_1.ApplicationError(`File at ${url} exceeds the 50MB size limit.`);
|
|
524
|
+
}
|
|
525
|
+
const contentType = fileResponse.headers['content-type'];
|
|
526
|
+
const uploadConfig = await this.helpers.request({
|
|
527
|
+
method: 'POST',
|
|
528
|
+
url: 'https://api.linqapp.com/api/partner/v3/attachments',
|
|
529
|
+
headers: {
|
|
530
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
531
|
+
'Content-Type': 'application/json',
|
|
532
|
+
'Accept': 'application/json',
|
|
533
|
+
},
|
|
534
|
+
body: {
|
|
535
|
+
content_type: contentType,
|
|
536
|
+
},
|
|
537
|
+
json: true,
|
|
538
|
+
});
|
|
539
|
+
await this.helpers.request({
|
|
540
|
+
method: 'PUT',
|
|
541
|
+
url: uploadConfig.upload_url,
|
|
542
|
+
headers: {
|
|
543
|
+
'Content-Type': contentType,
|
|
544
|
+
},
|
|
545
|
+
body: buffer,
|
|
546
|
+
json: false,
|
|
547
|
+
});
|
|
548
|
+
return uploadConfig.id;
|
|
549
|
+
};
|
|
550
|
+
const createParts = async (text, attachmentUrlsString) => {
|
|
551
|
+
const parts = [];
|
|
552
|
+
if (text) {
|
|
553
|
+
parts.push({ type: 'text', value: text });
|
|
554
|
+
}
|
|
555
|
+
if (attachmentUrlsString) {
|
|
556
|
+
const urls = attachmentUrlsString.split(',').map(u => u.trim()).filter(Boolean);
|
|
557
|
+
// Upload attachments in batches to limit concurrency and avoid OOM
|
|
558
|
+
const CONCURRENCY_LIMIT = 3;
|
|
559
|
+
const attachmentIds = [];
|
|
560
|
+
for (let i = 0; i < urls.length; i += CONCURRENCY_LIMIT) {
|
|
561
|
+
const chunk = urls.slice(i, i + CONCURRENCY_LIMIT);
|
|
562
|
+
const chunkResults = await Promise.all(chunk.map(url => uploadAttachment(url)));
|
|
563
|
+
attachmentIds.push(...chunkResults);
|
|
564
|
+
}
|
|
565
|
+
attachmentIds.forEach(id => {
|
|
566
|
+
parts.push({ type: 'media', attachment_id: id });
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
return parts;
|
|
570
|
+
};
|
|
491
571
|
for (let i = 0; i < items.length; i++) {
|
|
492
572
|
const resource = this.getNodeParameter('resource', i);
|
|
493
573
|
const operation = this.getNodeParameter('operation', i);
|
|
@@ -497,24 +577,23 @@ class Linq {
|
|
|
497
577
|
if (resource === 'chat') {
|
|
498
578
|
if (operation === 'getAll') {
|
|
499
579
|
const phoneNumber = this.getNodeParameter('phoneNumber', i, '');
|
|
500
|
-
const
|
|
501
|
-
const
|
|
580
|
+
const cursor = this.getNodeParameter('cursor', i, '');
|
|
581
|
+
const limit = this.getNodeParameter('limit', i);
|
|
502
582
|
if (!phoneNumber) {
|
|
503
583
|
throw new n8n_workflow_1.ApplicationError('phone_number is required by Linq API');
|
|
504
584
|
}
|
|
505
585
|
const qs = {};
|
|
506
|
-
qs.
|
|
507
|
-
if (
|
|
508
|
-
qs.
|
|
509
|
-
if (
|
|
510
|
-
qs.
|
|
511
|
-
qs.phone_number = formatPhoneNumber(phoneNumber);
|
|
586
|
+
qs.from = formatPhoneNumber(phoneNumber);
|
|
587
|
+
if (cursor)
|
|
588
|
+
qs.cursor = cursor;
|
|
589
|
+
if (limit && limit !== 50)
|
|
590
|
+
qs.limit = limit;
|
|
512
591
|
responseData = await this.helpers.request({
|
|
513
592
|
method: 'GET',
|
|
514
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
593
|
+
url: 'https://api.linqapp.com/api/partner/v3/chats',
|
|
515
594
|
qs,
|
|
516
595
|
headers: {
|
|
517
|
-
'
|
|
596
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
518
597
|
'Accept': 'application/json'
|
|
519
598
|
},
|
|
520
599
|
json: true,
|
|
@@ -524,9 +603,9 @@ class Linq {
|
|
|
524
603
|
const chatId = this.getNodeParameter('chatId', i);
|
|
525
604
|
responseData = await this.helpers.request({
|
|
526
605
|
method: 'GET',
|
|
527
|
-
url: `https://api.linqapp.com/api/partner/
|
|
606
|
+
url: `https://api.linqapp.com/api/partner/v3/chats/${chatId}`,
|
|
528
607
|
headers: {
|
|
529
|
-
'
|
|
608
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
530
609
|
'Accept': 'application/json'
|
|
531
610
|
},
|
|
532
611
|
json: true,
|
|
@@ -540,16 +619,15 @@ class Linq {
|
|
|
540
619
|
}
|
|
541
620
|
const qs = {};
|
|
542
621
|
if (phoneNumbers) {
|
|
543
|
-
// Check if we have a single phone number or multiple
|
|
544
622
|
const phoneNumbersArray = phoneNumbers.split(',').map(p => formatPhoneNumber(p.trim()));
|
|
545
623
|
qs['phone_numbers[]'] = phoneNumbersArray;
|
|
546
624
|
}
|
|
547
625
|
responseData = await this.helpers.request({
|
|
548
626
|
method: 'GET',
|
|
549
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
627
|
+
url: 'https://api.linqapp.com/api/partner/v3/chats/find',
|
|
550
628
|
qs,
|
|
551
629
|
headers: {
|
|
552
|
-
'
|
|
630
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
553
631
|
'Accept': 'application/json'
|
|
554
632
|
},
|
|
555
633
|
json: true,
|
|
@@ -560,26 +638,26 @@ class Linq {
|
|
|
560
638
|
const displayName = this.getNodeParameter('displayName', i);
|
|
561
639
|
const phoneNumbers = this.getNodeParameter('phoneNumbers', i);
|
|
562
640
|
const messageText = this.getNodeParameter('messageText', i);
|
|
641
|
+
const attachmentUrls = this.getNodeParameter('attachmentUrls', i, '');
|
|
563
642
|
if (!sendFrom) {
|
|
564
643
|
throw new n8n_workflow_1.ApplicationError('send_from is required by Linq when creating chats');
|
|
565
644
|
}
|
|
645
|
+
const parts = await createParts(messageText, attachmentUrls);
|
|
566
646
|
const body = {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
},
|
|
647
|
+
from: formatPhoneNumber(sendFrom),
|
|
648
|
+
to: phoneNumbers.split(',').map(p => formatPhoneNumber(p.trim())),
|
|
570
649
|
message: {
|
|
571
|
-
|
|
650
|
+
parts
|
|
572
651
|
}
|
|
573
652
|
};
|
|
574
|
-
body.send_from = formatPhoneNumber(sendFrom);
|
|
575
653
|
if (displayName) {
|
|
576
|
-
body.
|
|
654
|
+
body.display_name = displayName;
|
|
577
655
|
}
|
|
578
656
|
responseData = await this.helpers.request({
|
|
579
657
|
method: 'POST',
|
|
580
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
658
|
+
url: 'https://api.linqapp.com/api/partner/v3/chats',
|
|
581
659
|
headers: {
|
|
582
|
-
'
|
|
660
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
583
661
|
'Content-Type': 'application/json',
|
|
584
662
|
'Accept': 'application/json'
|
|
585
663
|
},
|
|
@@ -594,9 +672,9 @@ class Linq {
|
|
|
594
672
|
}
|
|
595
673
|
responseData = await this.helpers.request({
|
|
596
674
|
method: 'POST',
|
|
597
|
-
url: `https://api.linqapp.com/api/partner/
|
|
675
|
+
url: `https://api.linqapp.com/api/partner/v3/chats/${chatId}/share_contact`,
|
|
598
676
|
headers: {
|
|
599
|
-
'
|
|
677
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
600
678
|
'Content-Type': 'application/json',
|
|
601
679
|
'Accept': 'application/json'
|
|
602
680
|
},
|
|
@@ -608,14 +686,22 @@ class Linq {
|
|
|
608
686
|
if (resource === 'chatMessage') {
|
|
609
687
|
if (operation === 'getAll') {
|
|
610
688
|
const chatId = this.getNodeParameter('chatId', i);
|
|
689
|
+
const cursor = this.getNodeParameter('cursor', i, '');
|
|
690
|
+
const limit = this.getNodeParameter('limit', i);
|
|
611
691
|
if (!chatId) {
|
|
612
692
|
throw new n8n_workflow_1.ApplicationError('chatId is required to list chat messages');
|
|
613
693
|
}
|
|
694
|
+
const qs = {};
|
|
695
|
+
if (cursor)
|
|
696
|
+
qs.cursor = cursor;
|
|
697
|
+
if (limit && limit !== 50)
|
|
698
|
+
qs.limit = limit;
|
|
614
699
|
responseData = await this.helpers.request({
|
|
615
700
|
method: 'GET',
|
|
616
|
-
url: `https://api.linqapp.com/api/partner/
|
|
701
|
+
url: `https://api.linqapp.com/api/partner/v3/chats/${chatId}/messages`,
|
|
702
|
+
qs,
|
|
617
703
|
headers: {
|
|
618
|
-
'
|
|
704
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
619
705
|
'Accept': 'application/json'
|
|
620
706
|
},
|
|
621
707
|
json: true,
|
|
@@ -625,9 +711,9 @@ class Linq {
|
|
|
625
711
|
const chatMessageId = this.getNodeParameter('chatMessageId', i);
|
|
626
712
|
responseData = await this.helpers.request({
|
|
627
713
|
method: 'GET',
|
|
628
|
-
url: `https://api.linqapp.com/api/partner/
|
|
714
|
+
url: `https://api.linqapp.com/api/partner/v3/messages/${chatMessageId}`,
|
|
629
715
|
headers: {
|
|
630
|
-
'
|
|
716
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
631
717
|
'Accept': 'application/json'
|
|
632
718
|
},
|
|
633
719
|
json: true,
|
|
@@ -641,23 +727,20 @@ class Linq {
|
|
|
641
727
|
if (!chatId) {
|
|
642
728
|
throw new n8n_workflow_1.ApplicationError('chatId is required to create a chat message');
|
|
643
729
|
}
|
|
730
|
+
const parts = await createParts(messageText, attachmentUrls);
|
|
644
731
|
const body = {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
if (attachmentUrls) {
|
|
648
|
-
const parsed = attachmentUrls.split(',').map(u => u.trim()).filter(Boolean);
|
|
649
|
-
if (parsed.length) {
|
|
650
|
-
body.attachment_urls = parsed;
|
|
732
|
+
message: {
|
|
733
|
+
parts
|
|
651
734
|
}
|
|
652
|
-
}
|
|
735
|
+
};
|
|
653
736
|
if (idempotencyKey) {
|
|
654
|
-
body.idempotency_key = idempotencyKey;
|
|
737
|
+
body.message.idempotency_key = idempotencyKey;
|
|
655
738
|
}
|
|
656
739
|
responseData = await this.helpers.request({
|
|
657
740
|
method: 'POST',
|
|
658
|
-
url: `https://api.linqapp.com/api/partner/
|
|
741
|
+
url: `https://api.linqapp.com/api/partner/v3/chats/${chatId}/messages`,
|
|
659
742
|
headers: {
|
|
660
|
-
'
|
|
743
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
661
744
|
'Content-Type': 'application/json',
|
|
662
745
|
'Accept': 'application/json'
|
|
663
746
|
},
|
|
@@ -669,9 +752,9 @@ class Linq {
|
|
|
669
752
|
const chatMessageId = this.getNodeParameter('chatMessageId', i);
|
|
670
753
|
responseData = await this.helpers.request({
|
|
671
754
|
method: 'DELETE',
|
|
672
|
-
url: `https://api.linqapp.com/api/partner/
|
|
755
|
+
url: `https://api.linqapp.com/api/partner/v3/messages/${chatMessageId}`,
|
|
673
756
|
headers: {
|
|
674
|
-
'
|
|
757
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
675
758
|
'Accept': 'application/json'
|
|
676
759
|
},
|
|
677
760
|
json: true,
|
|
@@ -685,9 +768,9 @@ class Linq {
|
|
|
685
768
|
};
|
|
686
769
|
responseData = await this.helpers.request({
|
|
687
770
|
method: 'POST',
|
|
688
|
-
url: `https://api.linqapp.com/api/partner/
|
|
771
|
+
url: `https://api.linqapp.com/api/partner/v3/messages/${chatMessageId}/edit`,
|
|
689
772
|
headers: {
|
|
690
|
-
'
|
|
773
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
691
774
|
'Content-Type': 'application/json',
|
|
692
775
|
'Accept': 'application/json'
|
|
693
776
|
},
|
|
@@ -699,13 +782,14 @@ class Linq {
|
|
|
699
782
|
const chatMessageId = this.getNodeParameter('chatMessageId', i);
|
|
700
783
|
const reaction = this.getNodeParameter('reaction', i);
|
|
701
784
|
const body = {
|
|
702
|
-
|
|
785
|
+
operation: 'add',
|
|
786
|
+
type: reaction
|
|
703
787
|
};
|
|
704
788
|
responseData = await this.helpers.request({
|
|
705
789
|
method: 'POST',
|
|
706
|
-
url: `https://api.linqapp.com/api/partner/
|
|
790
|
+
url: `https://api.linqapp.com/api/partner/v3/messages/${chatMessageId}/reactions`,
|
|
707
791
|
headers: {
|
|
708
|
-
'
|
|
792
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
709
793
|
'Content-Type': 'application/json',
|
|
710
794
|
'Accept': 'application/json'
|
|
711
795
|
},
|
|
@@ -720,9 +804,9 @@ class Linq {
|
|
|
720
804
|
}
|
|
721
805
|
responseData = await this.helpers.request({
|
|
722
806
|
method: 'GET',
|
|
723
|
-
url: `https://api.linqapp.com/api/partner/
|
|
807
|
+
url: `https://api.linqapp.com/api/partner/v3/chat_message_reactions/${reactionId}`,
|
|
724
808
|
headers: {
|
|
725
|
-
'
|
|
809
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
726
810
|
'Accept': 'application/json'
|
|
727
811
|
},
|
|
728
812
|
json: true,
|
|
@@ -734,9 +818,9 @@ class Linq {
|
|
|
734
818
|
if (operation === 'getAll') {
|
|
735
819
|
responseData = await this.helpers.request({
|
|
736
820
|
method: 'GET',
|
|
737
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
821
|
+
url: 'https://api.linqapp.com/api/partner/v3/phone_numbers',
|
|
738
822
|
headers: {
|
|
739
|
-
'
|
|
823
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
740
824
|
'Accept': 'application/json'
|
|
741
825
|
},
|
|
742
826
|
json: true,
|
|
@@ -758,9 +842,9 @@ class Linq {
|
|
|
758
842
|
}
|
|
759
843
|
responseData = await this.helpers.request({
|
|
760
844
|
method: 'PUT',
|
|
761
|
-
url: `https://api.linqapp.com/api/partner/
|
|
845
|
+
url: `https://api.linqapp.com/api/partner/v3/phone_numbers/${phoneNumberId}`,
|
|
762
846
|
headers: {
|
|
763
|
-
'
|
|
847
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
764
848
|
'Content-Type': 'application/json',
|
|
765
849
|
'Accept': 'application/json'
|
|
766
850
|
},
|
|
@@ -774,9 +858,9 @@ class Linq {
|
|
|
774
858
|
if (operation === 'getAll') {
|
|
775
859
|
responseData = await this.helpers.request({
|
|
776
860
|
method: 'GET',
|
|
777
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
861
|
+
url: 'https://api.linqapp.com/api/partner/v3/webhook_subscriptions',
|
|
778
862
|
headers: {
|
|
779
|
-
'
|
|
863
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
780
864
|
'Accept': 'application/json'
|
|
781
865
|
},
|
|
782
866
|
json: true,
|
|
@@ -786,9 +870,9 @@ class Linq {
|
|
|
786
870
|
const webhookSubscriptionId = this.getNodeParameter('webhookSubscriptionId', i);
|
|
787
871
|
responseData = await this.helpers.request({
|
|
788
872
|
method: 'GET',
|
|
789
|
-
url: `https://api.linqapp.com/api/partner/
|
|
873
|
+
url: `https://api.linqapp.com/api/partner/v3/webhook_subscriptions/${webhookSubscriptionId}`,
|
|
790
874
|
headers: {
|
|
791
|
-
'
|
|
875
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
792
876
|
'Accept': 'application/json'
|
|
793
877
|
},
|
|
794
878
|
json: true,
|
|
@@ -800,18 +884,18 @@ class Linq {
|
|
|
800
884
|
const version = this.getNodeParameter('version', i);
|
|
801
885
|
const active = this.getNodeParameter('active', i);
|
|
802
886
|
const body = {
|
|
803
|
-
|
|
887
|
+
target_url: webhookUrl,
|
|
804
888
|
version: version,
|
|
805
889
|
active: active
|
|
806
890
|
};
|
|
807
891
|
if (events) {
|
|
808
|
-
body.
|
|
892
|
+
body.subscribed_events = events.split(',').map(e => e.trim());
|
|
809
893
|
}
|
|
810
894
|
responseData = await this.helpers.request({
|
|
811
895
|
method: 'POST',
|
|
812
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
896
|
+
url: 'https://api.linqapp.com/api/partner/v3/webhook_subscriptions',
|
|
813
897
|
headers: {
|
|
814
|
-
'
|
|
898
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
815
899
|
'Content-Type': 'application/json',
|
|
816
900
|
'Accept': 'application/json'
|
|
817
901
|
},
|
|
@@ -830,16 +914,16 @@ class Linq {
|
|
|
830
914
|
active: active
|
|
831
915
|
};
|
|
832
916
|
if (webhookUrl) {
|
|
833
|
-
body.
|
|
917
|
+
body.target_url = webhookUrl;
|
|
834
918
|
}
|
|
835
919
|
if (events) {
|
|
836
|
-
body.
|
|
920
|
+
body.subscribed_events = events.split(',').map(e => e.trim());
|
|
837
921
|
}
|
|
838
922
|
responseData = await this.helpers.request({
|
|
839
923
|
method: 'PUT',
|
|
840
|
-
url: `https://api.linqapp.com/api/partner/
|
|
924
|
+
url: `https://api.linqapp.com/api/partner/v3/webhook_subscriptions/${webhookSubscriptionId}`,
|
|
841
925
|
headers: {
|
|
842
|
-
'
|
|
926
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
843
927
|
'Content-Type': 'application/json',
|
|
844
928
|
'Accept': 'application/json'
|
|
845
929
|
},
|
|
@@ -851,9 +935,9 @@ class Linq {
|
|
|
851
935
|
const webhookSubscriptionId = this.getNodeParameter('webhookSubscriptionId', i);
|
|
852
936
|
responseData = await this.helpers.request({
|
|
853
937
|
method: 'DELETE',
|
|
854
|
-
url: `https://api.linqapp.com/api/partner/
|
|
938
|
+
url: `https://api.linqapp.com/api/partner/v3/webhook_subscriptions/${webhookSubscriptionId}`,
|
|
855
939
|
headers: {
|
|
856
|
-
'
|
|
940
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
857
941
|
'Accept': 'application/json'
|
|
858
942
|
},
|
|
859
943
|
json: true,
|
|
@@ -889,9 +973,9 @@ class Linq {
|
|
|
889
973
|
body.contact.location = location;
|
|
890
974
|
responseData = await this.helpers.request({
|
|
891
975
|
method: 'POST',
|
|
892
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
976
|
+
url: 'https://api.linqapp.com/api/partner/v3/contacts',
|
|
893
977
|
headers: {
|
|
894
|
-
'
|
|
978
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
895
979
|
'Content-Type': 'application/json',
|
|
896
980
|
'Accept': 'application/json'
|
|
897
981
|
},
|
|
@@ -903,9 +987,9 @@ class Linq {
|
|
|
903
987
|
const contactId = this.getNodeParameter('contactId', i);
|
|
904
988
|
responseData = await this.helpers.request({
|
|
905
989
|
method: 'GET',
|
|
906
|
-
url: `https://api.linqapp.com/api/partner/
|
|
990
|
+
url: `https://api.linqapp.com/api/partner/v3/contacts/${contactId}`,
|
|
907
991
|
headers: {
|
|
908
|
-
'
|
|
992
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
909
993
|
'Accept': 'application/json'
|
|
910
994
|
},
|
|
911
995
|
json: true,
|
|
@@ -939,9 +1023,9 @@ class Linq {
|
|
|
939
1023
|
body.contact.location = location;
|
|
940
1024
|
responseData = await this.helpers.request({
|
|
941
1025
|
method: 'PUT',
|
|
942
|
-
url: `https://api.linqapp.com/api/partner/
|
|
1026
|
+
url: `https://api.linqapp.com/api/partner/v3/contacts/${contactId}`,
|
|
943
1027
|
headers: {
|
|
944
|
-
'
|
|
1028
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
945
1029
|
'Content-Type': 'application/json',
|
|
946
1030
|
'Accept': 'application/json'
|
|
947
1031
|
},
|
|
@@ -953,9 +1037,9 @@ class Linq {
|
|
|
953
1037
|
const contactId = this.getNodeParameter('contactId', i);
|
|
954
1038
|
responseData = await this.helpers.request({
|
|
955
1039
|
method: 'DELETE',
|
|
956
|
-
url: `https://api.linqapp.com/api/partner/
|
|
1040
|
+
url: `https://api.linqapp.com/api/partner/v3/contacts/${contactId}`,
|
|
957
1041
|
headers: {
|
|
958
|
-
'
|
|
1042
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
959
1043
|
'Accept': 'application/json'
|
|
960
1044
|
},
|
|
961
1045
|
json: true,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { INodeType, INodeTypeDescription, IWebhookResponseData } from 'n8n-workflow';
|
|
1
|
+
import { IWebhookFunctions, INodeType, INodeTypeDescription, IWebhookResponseData } from 'n8n-workflow';
|
|
2
2
|
export declare class LinqTrigger implements INodeType {
|
|
3
3
|
description: INodeTypeDescription;
|
|
4
|
-
webhook(this:
|
|
4
|
+
webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
|
|
5
5
|
}
|
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.LinqTrigger = void 0;
|
|
4
37
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
38
|
+
const crypto = __importStar(require("crypto"));
|
|
5
39
|
class LinqTrigger {
|
|
6
40
|
constructor() {
|
|
7
41
|
this.description = {
|
|
@@ -74,31 +108,67 @@ class LinqTrigger {
|
|
|
74
108
|
};
|
|
75
109
|
}
|
|
76
110
|
async webhook() {
|
|
77
|
-
const
|
|
111
|
+
const req = this.getRequestObject();
|
|
78
112
|
const headers = this.getHeaderData();
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
113
|
+
const bodyData = this.getBodyData();
|
|
114
|
+
// 1. Validate and Parse Signature Header
|
|
115
|
+
const signatureHeader = headers['x-webhook-signature'];
|
|
116
|
+
const timestampHeader = headers['x-webhook-timestamp'];
|
|
117
|
+
if (!signatureHeader) {
|
|
118
|
+
throw new n8n_workflow_1.ApplicationError('Missing X-Webhook-Signature header');
|
|
119
|
+
}
|
|
120
|
+
if (!timestampHeader) {
|
|
121
|
+
throw new n8n_workflow_1.ApplicationError('Missing X-Webhook-Timestamp header');
|
|
122
|
+
}
|
|
123
|
+
// Verify timestamp freshness (within 5 minutes)
|
|
124
|
+
const timestamp = parseInt(timestampHeader, 10);
|
|
125
|
+
const now = Math.floor(Date.now() / 1000);
|
|
126
|
+
if (Math.abs(now - timestamp) > 300) {
|
|
127
|
+
throw new n8n_workflow_1.ApplicationError('Request timestamp is too old');
|
|
128
|
+
}
|
|
129
|
+
const [scheme, signature] = signatureHeader.split('=');
|
|
130
|
+
if (scheme !== 'sha256' || !signature) {
|
|
131
|
+
throw new n8n_workflow_1.ApplicationError('Invalid signature format');
|
|
132
|
+
}
|
|
133
|
+
// 2. Verify HMAC Signature
|
|
82
134
|
const credentials = await this.getCredentials('linqApi');
|
|
83
|
-
const
|
|
135
|
+
const token = credentials.integrationToken;
|
|
136
|
+
// Use rawBody for accurate HMAC verification
|
|
137
|
+
const rawBody = req.rawBody;
|
|
138
|
+
if (!rawBody) {
|
|
139
|
+
console.warn('LinqTrigger: rawBody is missing, falling back to JSON.stringify. Signature verification might fail.');
|
|
140
|
+
}
|
|
141
|
+
const bodyString = rawBody || JSON.stringify(bodyData);
|
|
142
|
+
const payloadToHash = `${timestampHeader}.${bodyString}`;
|
|
84
143
|
const expectedSignature = crypto
|
|
85
|
-
.createHmac('sha256',
|
|
86
|
-
.update(
|
|
87
|
-
.digest(
|
|
88
|
-
|
|
144
|
+
.createHmac('sha256', token)
|
|
145
|
+
.update(payloadToHash)
|
|
146
|
+
.digest();
|
|
147
|
+
const sourceSignature = Buffer.from(signature, 'hex');
|
|
148
|
+
if (sourceSignature.length !== expectedSignature.length || !crypto.timingSafeEqual(sourceSignature, expectedSignature)) {
|
|
89
149
|
throw new n8n_workflow_1.ApplicationError('Invalid signature');
|
|
90
150
|
}
|
|
91
|
-
//
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
151
|
+
// 3. Event Filtering
|
|
152
|
+
const events = this.getNodeParameter('events', []);
|
|
153
|
+
const incomingEvent = bodyData.event;
|
|
154
|
+
// Filter if events are specified
|
|
155
|
+
if (events.length > 0 && !events.includes(incomingEvent)) {
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
// 4. Extract Payload
|
|
159
|
+
// The V3 structure is { event: string, payload: object }
|
|
160
|
+
const { payload } = bodyData;
|
|
100
161
|
return {
|
|
101
|
-
workflowData: [
|
|
162
|
+
workflowData: [
|
|
163
|
+
[
|
|
164
|
+
{
|
|
165
|
+
json: {
|
|
166
|
+
event: incomingEvent,
|
|
167
|
+
...payload,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
],
|
|
102
172
|
};
|
|
103
173
|
}
|
|
104
174
|
}
|