n8n-nodes-linq 0.2.0 → 3.0.0
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 +128 -64
- package/dist/nodes/LinqTrigger/LinqTrigger.node.d.ts +2 -2
- package/dist/nodes/LinqTrigger/LinqTrigger.node.js +78 -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;
|
|
@@ -243,12 +243,12 @@ class Linq {
|
|
|
243
243
|
description: 'Comma-separated list of phone numbers to find chat between them',
|
|
244
244
|
},
|
|
245
245
|
{
|
|
246
|
-
displayName: '
|
|
247
|
-
name: '
|
|
248
|
-
type: '
|
|
246
|
+
displayName: 'Cursor',
|
|
247
|
+
name: 'cursor',
|
|
248
|
+
type: 'string',
|
|
249
249
|
displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
|
|
250
|
-
default:
|
|
251
|
-
description: '
|
|
250
|
+
default: '',
|
|
251
|
+
description: 'Cursor for pagination',
|
|
252
252
|
},
|
|
253
253
|
{
|
|
254
254
|
displayName: 'Per Page',
|
|
@@ -291,6 +291,14 @@ class Linq {
|
|
|
291
291
|
default: '',
|
|
292
292
|
description: 'The text of the message to send',
|
|
293
293
|
},
|
|
294
|
+
{
|
|
295
|
+
displayName: 'Attachment URLs',
|
|
296
|
+
name: 'attachmentUrls',
|
|
297
|
+
type: 'string',
|
|
298
|
+
displayOptions: { show: { resource: ['chat'], operation: ['create'] } },
|
|
299
|
+
default: '',
|
|
300
|
+
description: 'Comma-separated list of attachment URLs',
|
|
301
|
+
},
|
|
294
302
|
// Chat Message parameters
|
|
295
303
|
{
|
|
296
304
|
displayName: 'Chat Message ID',
|
|
@@ -488,6 +496,67 @@ class Linq {
|
|
|
488
496
|
const items = this.getInputData();
|
|
489
497
|
const returnData = [];
|
|
490
498
|
const credentials = await this.getCredentials('linqApi');
|
|
499
|
+
const uploadAttachment = async (url) => {
|
|
500
|
+
const fileResponse = await this.helpers.request({
|
|
501
|
+
method: 'GET',
|
|
502
|
+
url,
|
|
503
|
+
encoding: null,
|
|
504
|
+
resolveWithFullResponse: true,
|
|
505
|
+
});
|
|
506
|
+
const buffer = fileResponse.body;
|
|
507
|
+
// Limit file size to 50MB to avoid OOM
|
|
508
|
+
// Note: This loads the file into memory. For larger files, streaming would be preferred
|
|
509
|
+
// but is not implemented here to maintain compatibility with n8n helper request patterns.
|
|
510
|
+
const MAX_SIZE = 50 * 1024 * 1024;
|
|
511
|
+
if (buffer.length > MAX_SIZE) {
|
|
512
|
+
throw new n8n_workflow_1.ApplicationError(`File at ${url} exceeds the 50MB size limit.`);
|
|
513
|
+
}
|
|
514
|
+
const contentType = fileResponse.headers['content-type'];
|
|
515
|
+
const uploadConfig = await this.helpers.request({
|
|
516
|
+
method: 'POST',
|
|
517
|
+
url: 'https://api.linqapp.com/api/partner/v3/attachments',
|
|
518
|
+
headers: {
|
|
519
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
520
|
+
'Content-Type': 'application/json',
|
|
521
|
+
'Accept': 'application/json',
|
|
522
|
+
},
|
|
523
|
+
body: {
|
|
524
|
+
content_type: contentType,
|
|
525
|
+
},
|
|
526
|
+
json: true,
|
|
527
|
+
});
|
|
528
|
+
await this.helpers.request({
|
|
529
|
+
method: 'PUT',
|
|
530
|
+
url: uploadConfig.upload_url,
|
|
531
|
+
headers: {
|
|
532
|
+
'Content-Type': contentType,
|
|
533
|
+
},
|
|
534
|
+
body: buffer,
|
|
535
|
+
json: false,
|
|
536
|
+
});
|
|
537
|
+
return uploadConfig.id;
|
|
538
|
+
};
|
|
539
|
+
const createParts = async (text, attachmentUrlsString) => {
|
|
540
|
+
const parts = [];
|
|
541
|
+
if (text) {
|
|
542
|
+
parts.push({ type: 'text', value: text });
|
|
543
|
+
}
|
|
544
|
+
if (attachmentUrlsString) {
|
|
545
|
+
const urls = attachmentUrlsString.split(',').map(u => u.trim()).filter(Boolean);
|
|
546
|
+
// Upload attachments in batches to limit concurrency and avoid OOM
|
|
547
|
+
const CONCURRENCY_LIMIT = 3;
|
|
548
|
+
const attachmentIds = [];
|
|
549
|
+
for (let i = 0; i < urls.length; i += CONCURRENCY_LIMIT) {
|
|
550
|
+
const chunk = urls.slice(i, i + CONCURRENCY_LIMIT);
|
|
551
|
+
const chunkResults = await Promise.all(chunk.map(url => uploadAttachment(url)));
|
|
552
|
+
attachmentIds.push(...chunkResults);
|
|
553
|
+
}
|
|
554
|
+
attachmentIds.forEach(id => {
|
|
555
|
+
parts.push({ type: 'media', attachment_id: id });
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
return parts;
|
|
559
|
+
};
|
|
491
560
|
for (let i = 0; i < items.length; i++) {
|
|
492
561
|
const resource = this.getNodeParameter('resource', i);
|
|
493
562
|
const operation = this.getNodeParameter('operation', i);
|
|
@@ -497,24 +566,23 @@ class Linq {
|
|
|
497
566
|
if (resource === 'chat') {
|
|
498
567
|
if (operation === 'getAll') {
|
|
499
568
|
const phoneNumber = this.getNodeParameter('phoneNumber', i, '');
|
|
500
|
-
const
|
|
569
|
+
const cursor = this.getNodeParameter('cursor', i, '');
|
|
501
570
|
const perPage = this.getNodeParameter('perPage', i);
|
|
502
571
|
if (!phoneNumber) {
|
|
503
572
|
throw new n8n_workflow_1.ApplicationError('phone_number is required by Linq API');
|
|
504
573
|
}
|
|
505
574
|
const qs = {};
|
|
506
575
|
qs.phone_number = formatPhoneNumber(phoneNumber);
|
|
507
|
-
if (
|
|
508
|
-
qs.
|
|
576
|
+
if (cursor)
|
|
577
|
+
qs.cursor = cursor;
|
|
509
578
|
if (perPage && perPage !== 25)
|
|
510
579
|
qs.per_page = perPage;
|
|
511
|
-
qs.phone_number = formatPhoneNumber(phoneNumber);
|
|
512
580
|
responseData = await this.helpers.request({
|
|
513
581
|
method: 'GET',
|
|
514
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
582
|
+
url: 'https://api.linqapp.com/api/partner/v3/chats',
|
|
515
583
|
qs,
|
|
516
584
|
headers: {
|
|
517
|
-
'
|
|
585
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
518
586
|
'Accept': 'application/json'
|
|
519
587
|
},
|
|
520
588
|
json: true,
|
|
@@ -524,9 +592,9 @@ class Linq {
|
|
|
524
592
|
const chatId = this.getNodeParameter('chatId', i);
|
|
525
593
|
responseData = await this.helpers.request({
|
|
526
594
|
method: 'GET',
|
|
527
|
-
url: `https://api.linqapp.com/api/partner/
|
|
595
|
+
url: `https://api.linqapp.com/api/partner/v3/chats/${chatId}`,
|
|
528
596
|
headers: {
|
|
529
|
-
'
|
|
597
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
530
598
|
'Accept': 'application/json'
|
|
531
599
|
},
|
|
532
600
|
json: true,
|
|
@@ -540,16 +608,15 @@ class Linq {
|
|
|
540
608
|
}
|
|
541
609
|
const qs = {};
|
|
542
610
|
if (phoneNumbers) {
|
|
543
|
-
// Check if we have a single phone number or multiple
|
|
544
611
|
const phoneNumbersArray = phoneNumbers.split(',').map(p => formatPhoneNumber(p.trim()));
|
|
545
612
|
qs['phone_numbers[]'] = phoneNumbersArray;
|
|
546
613
|
}
|
|
547
614
|
responseData = await this.helpers.request({
|
|
548
615
|
method: 'GET',
|
|
549
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
616
|
+
url: 'https://api.linqapp.com/api/partner/v3/chats/find',
|
|
550
617
|
qs,
|
|
551
618
|
headers: {
|
|
552
|
-
'
|
|
619
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
553
620
|
'Accept': 'application/json'
|
|
554
621
|
},
|
|
555
622
|
json: true,
|
|
@@ -560,15 +627,17 @@ class Linq {
|
|
|
560
627
|
const displayName = this.getNodeParameter('displayName', i);
|
|
561
628
|
const phoneNumbers = this.getNodeParameter('phoneNumbers', i);
|
|
562
629
|
const messageText = this.getNodeParameter('messageText', i);
|
|
630
|
+
const attachmentUrls = this.getNodeParameter('attachmentUrls', i, '');
|
|
563
631
|
if (!sendFrom) {
|
|
564
632
|
throw new n8n_workflow_1.ApplicationError('send_from is required by Linq when creating chats');
|
|
565
633
|
}
|
|
634
|
+
const parts = await createParts(messageText, attachmentUrls);
|
|
566
635
|
const body = {
|
|
567
636
|
chat: {
|
|
568
637
|
phone_numbers: phoneNumbers.split(',').map(p => formatPhoneNumber(p.trim()))
|
|
569
638
|
},
|
|
570
639
|
message: {
|
|
571
|
-
|
|
640
|
+
parts
|
|
572
641
|
}
|
|
573
642
|
};
|
|
574
643
|
body.send_from = formatPhoneNumber(sendFrom);
|
|
@@ -577,9 +646,9 @@ class Linq {
|
|
|
577
646
|
}
|
|
578
647
|
responseData = await this.helpers.request({
|
|
579
648
|
method: 'POST',
|
|
580
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
649
|
+
url: 'https://api.linqapp.com/api/partner/v3/chats',
|
|
581
650
|
headers: {
|
|
582
|
-
'
|
|
651
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
583
652
|
'Content-Type': 'application/json',
|
|
584
653
|
'Accept': 'application/json'
|
|
585
654
|
},
|
|
@@ -594,9 +663,9 @@ class Linq {
|
|
|
594
663
|
}
|
|
595
664
|
responseData = await this.helpers.request({
|
|
596
665
|
method: 'POST',
|
|
597
|
-
url: `https://api.linqapp.com/api/partner/
|
|
666
|
+
url: `https://api.linqapp.com/api/partner/v3/chats/${chatId}/share_contact`,
|
|
598
667
|
headers: {
|
|
599
|
-
'
|
|
668
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
600
669
|
'Content-Type': 'application/json',
|
|
601
670
|
'Accept': 'application/json'
|
|
602
671
|
},
|
|
@@ -613,9 +682,9 @@ class Linq {
|
|
|
613
682
|
}
|
|
614
683
|
responseData = await this.helpers.request({
|
|
615
684
|
method: 'GET',
|
|
616
|
-
url: `https://api.linqapp.com/api/partner/
|
|
685
|
+
url: `https://api.linqapp.com/api/partner/v3/chats/${chatId}/messages`,
|
|
617
686
|
headers: {
|
|
618
|
-
'
|
|
687
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
619
688
|
'Accept': 'application/json'
|
|
620
689
|
},
|
|
621
690
|
json: true,
|
|
@@ -625,9 +694,9 @@ class Linq {
|
|
|
625
694
|
const chatMessageId = this.getNodeParameter('chatMessageId', i);
|
|
626
695
|
responseData = await this.helpers.request({
|
|
627
696
|
method: 'GET',
|
|
628
|
-
url: `https://api.linqapp.com/api/partner/
|
|
697
|
+
url: `https://api.linqapp.com/api/partner/v3/messages/${chatMessageId}`,
|
|
629
698
|
headers: {
|
|
630
|
-
'
|
|
699
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
631
700
|
'Accept': 'application/json'
|
|
632
701
|
},
|
|
633
702
|
json: true,
|
|
@@ -641,23 +710,18 @@ class Linq {
|
|
|
641
710
|
if (!chatId) {
|
|
642
711
|
throw new n8n_workflow_1.ApplicationError('chatId is required to create a chat message');
|
|
643
712
|
}
|
|
713
|
+
const parts = await createParts(messageText, attachmentUrls);
|
|
644
714
|
const body = {
|
|
645
|
-
|
|
715
|
+
parts
|
|
646
716
|
};
|
|
647
|
-
if (attachmentUrls) {
|
|
648
|
-
const parsed = attachmentUrls.split(',').map(u => u.trim()).filter(Boolean);
|
|
649
|
-
if (parsed.length) {
|
|
650
|
-
body.attachment_urls = parsed;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
717
|
if (idempotencyKey) {
|
|
654
718
|
body.idempotency_key = idempotencyKey;
|
|
655
719
|
}
|
|
656
720
|
responseData = await this.helpers.request({
|
|
657
721
|
method: 'POST',
|
|
658
|
-
url: `https://api.linqapp.com/api/partner/
|
|
722
|
+
url: `https://api.linqapp.com/api/partner/v3/chats/${chatId}/messages`,
|
|
659
723
|
headers: {
|
|
660
|
-
'
|
|
724
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
661
725
|
'Content-Type': 'application/json',
|
|
662
726
|
'Accept': 'application/json'
|
|
663
727
|
},
|
|
@@ -669,9 +733,9 @@ class Linq {
|
|
|
669
733
|
const chatMessageId = this.getNodeParameter('chatMessageId', i);
|
|
670
734
|
responseData = await this.helpers.request({
|
|
671
735
|
method: 'DELETE',
|
|
672
|
-
url: `https://api.linqapp.com/api/partner/
|
|
736
|
+
url: `https://api.linqapp.com/api/partner/v3/messages/${chatMessageId}`,
|
|
673
737
|
headers: {
|
|
674
|
-
'
|
|
738
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
675
739
|
'Accept': 'application/json'
|
|
676
740
|
},
|
|
677
741
|
json: true,
|
|
@@ -685,9 +749,9 @@ class Linq {
|
|
|
685
749
|
};
|
|
686
750
|
responseData = await this.helpers.request({
|
|
687
751
|
method: 'POST',
|
|
688
|
-
url: `https://api.linqapp.com/api/partner/
|
|
752
|
+
url: `https://api.linqapp.com/api/partner/v3/messages/${chatMessageId}/edit`,
|
|
689
753
|
headers: {
|
|
690
|
-
'
|
|
754
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
691
755
|
'Content-Type': 'application/json',
|
|
692
756
|
'Accept': 'application/json'
|
|
693
757
|
},
|
|
@@ -703,9 +767,9 @@ class Linq {
|
|
|
703
767
|
};
|
|
704
768
|
responseData = await this.helpers.request({
|
|
705
769
|
method: 'POST',
|
|
706
|
-
url: `https://api.linqapp.com/api/partner/
|
|
770
|
+
url: `https://api.linqapp.com/api/partner/v3/messages/${chatMessageId}/reactions`,
|
|
707
771
|
headers: {
|
|
708
|
-
'
|
|
772
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
709
773
|
'Content-Type': 'application/json',
|
|
710
774
|
'Accept': 'application/json'
|
|
711
775
|
},
|
|
@@ -720,9 +784,9 @@ class Linq {
|
|
|
720
784
|
}
|
|
721
785
|
responseData = await this.helpers.request({
|
|
722
786
|
method: 'GET',
|
|
723
|
-
url: `https://api.linqapp.com/api/partner/
|
|
787
|
+
url: `https://api.linqapp.com/api/partner/v3/chat_message_reactions/${reactionId}`,
|
|
724
788
|
headers: {
|
|
725
|
-
'
|
|
789
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
726
790
|
'Accept': 'application/json'
|
|
727
791
|
},
|
|
728
792
|
json: true,
|
|
@@ -734,9 +798,9 @@ class Linq {
|
|
|
734
798
|
if (operation === 'getAll') {
|
|
735
799
|
responseData = await this.helpers.request({
|
|
736
800
|
method: 'GET',
|
|
737
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
801
|
+
url: 'https://api.linqapp.com/api/partner/v3/phone_numbers',
|
|
738
802
|
headers: {
|
|
739
|
-
'
|
|
803
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
740
804
|
'Accept': 'application/json'
|
|
741
805
|
},
|
|
742
806
|
json: true,
|
|
@@ -758,9 +822,9 @@ class Linq {
|
|
|
758
822
|
}
|
|
759
823
|
responseData = await this.helpers.request({
|
|
760
824
|
method: 'PUT',
|
|
761
|
-
url: `https://api.linqapp.com/api/partner/
|
|
825
|
+
url: `https://api.linqapp.com/api/partner/v3/phone_numbers/${phoneNumberId}`,
|
|
762
826
|
headers: {
|
|
763
|
-
'
|
|
827
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
764
828
|
'Content-Type': 'application/json',
|
|
765
829
|
'Accept': 'application/json'
|
|
766
830
|
},
|
|
@@ -774,9 +838,9 @@ class Linq {
|
|
|
774
838
|
if (operation === 'getAll') {
|
|
775
839
|
responseData = await this.helpers.request({
|
|
776
840
|
method: 'GET',
|
|
777
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
841
|
+
url: 'https://api.linqapp.com/api/partner/v3/webhook_subscriptions',
|
|
778
842
|
headers: {
|
|
779
|
-
'
|
|
843
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
780
844
|
'Accept': 'application/json'
|
|
781
845
|
},
|
|
782
846
|
json: true,
|
|
@@ -786,9 +850,9 @@ class Linq {
|
|
|
786
850
|
const webhookSubscriptionId = this.getNodeParameter('webhookSubscriptionId', i);
|
|
787
851
|
responseData = await this.helpers.request({
|
|
788
852
|
method: 'GET',
|
|
789
|
-
url: `https://api.linqapp.com/api/partner/
|
|
853
|
+
url: `https://api.linqapp.com/api/partner/v3/webhook_subscriptions/${webhookSubscriptionId}`,
|
|
790
854
|
headers: {
|
|
791
|
-
'
|
|
855
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
792
856
|
'Accept': 'application/json'
|
|
793
857
|
},
|
|
794
858
|
json: true,
|
|
@@ -809,9 +873,9 @@ class Linq {
|
|
|
809
873
|
}
|
|
810
874
|
responseData = await this.helpers.request({
|
|
811
875
|
method: 'POST',
|
|
812
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
876
|
+
url: 'https://api.linqapp.com/api/partner/v3/webhook_subscriptions',
|
|
813
877
|
headers: {
|
|
814
|
-
'
|
|
878
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
815
879
|
'Content-Type': 'application/json',
|
|
816
880
|
'Accept': 'application/json'
|
|
817
881
|
},
|
|
@@ -837,9 +901,9 @@ class Linq {
|
|
|
837
901
|
}
|
|
838
902
|
responseData = await this.helpers.request({
|
|
839
903
|
method: 'PUT',
|
|
840
|
-
url: `https://api.linqapp.com/api/partner/
|
|
904
|
+
url: `https://api.linqapp.com/api/partner/v3/webhook_subscriptions/${webhookSubscriptionId}`,
|
|
841
905
|
headers: {
|
|
842
|
-
'
|
|
906
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
843
907
|
'Content-Type': 'application/json',
|
|
844
908
|
'Accept': 'application/json'
|
|
845
909
|
},
|
|
@@ -851,9 +915,9 @@ class Linq {
|
|
|
851
915
|
const webhookSubscriptionId = this.getNodeParameter('webhookSubscriptionId', i);
|
|
852
916
|
responseData = await this.helpers.request({
|
|
853
917
|
method: 'DELETE',
|
|
854
|
-
url: `https://api.linqapp.com/api/partner/
|
|
918
|
+
url: `https://api.linqapp.com/api/partner/v3/webhook_subscriptions/${webhookSubscriptionId}`,
|
|
855
919
|
headers: {
|
|
856
|
-
'
|
|
920
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
857
921
|
'Accept': 'application/json'
|
|
858
922
|
},
|
|
859
923
|
json: true,
|
|
@@ -889,9 +953,9 @@ class Linq {
|
|
|
889
953
|
body.contact.location = location;
|
|
890
954
|
responseData = await this.helpers.request({
|
|
891
955
|
method: 'POST',
|
|
892
|
-
url: 'https://api.linqapp.com/api/partner/
|
|
956
|
+
url: 'https://api.linqapp.com/api/partner/v3/contacts',
|
|
893
957
|
headers: {
|
|
894
|
-
'
|
|
958
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
895
959
|
'Content-Type': 'application/json',
|
|
896
960
|
'Accept': 'application/json'
|
|
897
961
|
},
|
|
@@ -903,9 +967,9 @@ class Linq {
|
|
|
903
967
|
const contactId = this.getNodeParameter('contactId', i);
|
|
904
968
|
responseData = await this.helpers.request({
|
|
905
969
|
method: 'GET',
|
|
906
|
-
url: `https://api.linqapp.com/api/partner/
|
|
970
|
+
url: `https://api.linqapp.com/api/partner/v3/contacts/${contactId}`,
|
|
907
971
|
headers: {
|
|
908
|
-
'
|
|
972
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
909
973
|
'Accept': 'application/json'
|
|
910
974
|
},
|
|
911
975
|
json: true,
|
|
@@ -939,9 +1003,9 @@ class Linq {
|
|
|
939
1003
|
body.contact.location = location;
|
|
940
1004
|
responseData = await this.helpers.request({
|
|
941
1005
|
method: 'PUT',
|
|
942
|
-
url: `https://api.linqapp.com/api/partner/
|
|
1006
|
+
url: `https://api.linqapp.com/api/partner/v3/contacts/${contactId}`,
|
|
943
1007
|
headers: {
|
|
944
|
-
'
|
|
1008
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
945
1009
|
'Content-Type': 'application/json',
|
|
946
1010
|
'Accept': 'application/json'
|
|
947
1011
|
},
|
|
@@ -953,9 +1017,9 @@ class Linq {
|
|
|
953
1017
|
const contactId = this.getNodeParameter('contactId', i);
|
|
954
1018
|
responseData = await this.helpers.request({
|
|
955
1019
|
method: 'DELETE',
|
|
956
|
-
url: `https://api.linqapp.com/api/partner/
|
|
1020
|
+
url: `https://api.linqapp.com/api/partner/v3/contacts/${contactId}`,
|
|
957
1021
|
headers: {
|
|
958
|
-
'
|
|
1022
|
+
'Authorization': `Bearer ${credentials.integrationToken}`,
|
|
959
1023
|
'Accept': 'application/json'
|
|
960
1024
|
},
|
|
961
1025
|
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,56 @@ 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
|
+
if (!signatureHeader) {
|
|
117
|
+
throw new n8n_workflow_1.ApplicationError('Missing X-Webhook-Signature header');
|
|
118
|
+
}
|
|
119
|
+
const [scheme, signature] = signatureHeader.split('=');
|
|
120
|
+
if (scheme !== 'sha256' || !signature) {
|
|
121
|
+
throw new n8n_workflow_1.ApplicationError('Invalid signature format');
|
|
122
|
+
}
|
|
123
|
+
// 2. Verify HMAC Signature
|
|
82
124
|
const credentials = await this.getCredentials('linqApi');
|
|
83
|
-
const
|
|
125
|
+
const token = credentials.integrationToken;
|
|
126
|
+
// Use rawBody for accurate HMAC verification
|
|
127
|
+
const rawBody = req.rawBody;
|
|
128
|
+
if (!rawBody) {
|
|
129
|
+
console.warn('LinqTrigger: rawBody is missing, falling back to JSON.stringify. Signature verification might fail.');
|
|
130
|
+
}
|
|
131
|
+
const payloadToHash = rawBody || JSON.stringify(bodyData);
|
|
84
132
|
const expectedSignature = crypto
|
|
85
|
-
.createHmac('sha256',
|
|
86
|
-
.update(
|
|
87
|
-
.digest(
|
|
88
|
-
|
|
133
|
+
.createHmac('sha256', token)
|
|
134
|
+
.update(payloadToHash)
|
|
135
|
+
.digest();
|
|
136
|
+
const sourceSignature = Buffer.from(signature, 'hex');
|
|
137
|
+
if (sourceSignature.length !== expectedSignature.length || !crypto.timingSafeEqual(sourceSignature, expectedSignature)) {
|
|
89
138
|
throw new n8n_workflow_1.ApplicationError('Invalid signature');
|
|
90
139
|
}
|
|
91
|
-
//
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
140
|
+
// 3. Event Filtering
|
|
141
|
+
const events = this.getNodeParameter('events', []);
|
|
142
|
+
const incomingEvent = bodyData.event;
|
|
143
|
+
// Filter if events are specified
|
|
144
|
+
if (events.length > 0 && !events.includes(incomingEvent)) {
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
147
|
+
// 4. Extract Payload
|
|
148
|
+
// The V3 structure is { event: string, payload: object }
|
|
149
|
+
const { payload } = bodyData;
|
|
100
150
|
return {
|
|
101
|
-
workflowData: [
|
|
151
|
+
workflowData: [
|
|
152
|
+
[
|
|
153
|
+
{
|
|
154
|
+
json: {
|
|
155
|
+
event: incomingEvent,
|
|
156
|
+
...payload,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
],
|
|
102
161
|
};
|
|
103
162
|
}
|
|
104
163
|
}
|