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.
@@ -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 X-LINQ-INTEGRATION-TOKEN',
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: 'Page',
247
- name: 'page',
248
- type: 'number',
246
+ displayName: 'Cursor',
247
+ name: 'cursor',
248
+ type: 'string',
249
249
  displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
250
- default: 1,
251
- description: 'Page number for pagination',
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 page = this.getNodeParameter('page', i);
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 (page && page !== 1)
508
- qs.page = page;
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/v2/chats',
582
+ url: 'https://api.linqapp.com/api/partner/v3/chats',
515
583
  qs,
516
584
  headers: {
517
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/chats/${chatId}`,
595
+ url: `https://api.linqapp.com/api/partner/v3/chats/${chatId}`,
528
596
  headers: {
529
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/chats/find',
616
+ url: 'https://api.linqapp.com/api/partner/v3/chats/find',
550
617
  qs,
551
618
  headers: {
552
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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
- text: messageText
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/v2/chats',
649
+ url: 'https://api.linqapp.com/api/partner/v3/chats',
581
650
  headers: {
582
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/chats/${chatId}/share_contact`,
666
+ url: `https://api.linqapp.com/api/partner/v3/chats/${chatId}/share_contact`,
598
667
  headers: {
599
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/chats/${chatId}/chat_messages`,
685
+ url: `https://api.linqapp.com/api/partner/v3/chats/${chatId}/messages`,
617
686
  headers: {
618
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/chat_messages/${chatMessageId}`,
697
+ url: `https://api.linqapp.com/api/partner/v3/messages/${chatMessageId}`,
629
698
  headers: {
630
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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
- text: messageText
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/v2/chats/${chatId}/chat_messages`,
722
+ url: `https://api.linqapp.com/api/partner/v3/chats/${chatId}/messages`,
659
723
  headers: {
660
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/chat_messages/${chatMessageId}`,
736
+ url: `https://api.linqapp.com/api/partner/v3/messages/${chatMessageId}`,
673
737
  headers: {
674
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/chat_messages/${chatMessageId}/edit`,
752
+ url: `https://api.linqapp.com/api/partner/v3/messages/${chatMessageId}/edit`,
689
753
  headers: {
690
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/chat_messages/${chatMessageId}/reactions`,
770
+ url: `https://api.linqapp.com/api/partner/v3/messages/${chatMessageId}/reactions`,
707
771
  headers: {
708
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/chat_message_reactions/${reactionId}`,
787
+ url: `https://api.linqapp.com/api/partner/v3/chat_message_reactions/${reactionId}`,
724
788
  headers: {
725
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/phone_numbers',
801
+ url: 'https://api.linqapp.com/api/partner/v3/phone_numbers',
738
802
  headers: {
739
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/phone_numbers/${phoneNumberId}`,
825
+ url: `https://api.linqapp.com/api/partner/v3/phone_numbers/${phoneNumberId}`,
762
826
  headers: {
763
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/webhook_subscriptions',
841
+ url: 'https://api.linqapp.com/api/partner/v3/webhook_subscriptions',
778
842
  headers: {
779
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/webhook_subscriptions/${webhookSubscriptionId}`,
853
+ url: `https://api.linqapp.com/api/partner/v3/webhook_subscriptions/${webhookSubscriptionId}`,
790
854
  headers: {
791
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/webhook_subscriptions',
876
+ url: 'https://api.linqapp.com/api/partner/v3/webhook_subscriptions',
813
877
  headers: {
814
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/webhook_subscriptions/${webhookSubscriptionId}`,
904
+ url: `https://api.linqapp.com/api/partner/v3/webhook_subscriptions/${webhookSubscriptionId}`,
841
905
  headers: {
842
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/webhook_subscriptions/${webhookSubscriptionId}`,
918
+ url: `https://api.linqapp.com/api/partner/v3/webhook_subscriptions/${webhookSubscriptionId}`,
855
919
  headers: {
856
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/contacts',
956
+ url: 'https://api.linqapp.com/api/partner/v3/contacts',
893
957
  headers: {
894
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/contacts/${contactId}`,
970
+ url: `https://api.linqapp.com/api/partner/v3/contacts/${contactId}`,
907
971
  headers: {
908
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/contacts/${contactId}`,
1006
+ url: `https://api.linqapp.com/api/partner/v3/contacts/${contactId}`,
943
1007
  headers: {
944
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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/v2/contacts/${contactId}`,
1020
+ url: `https://api.linqapp.com/api/partner/v3/contacts/${contactId}`,
957
1021
  headers: {
958
- 'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
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: any): Promise<IWebhookResponseData>;
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 bodyData = this.getBodyData();
111
+ const req = this.getRequestObject();
78
112
  const headers = this.getHeaderData();
79
- const event = headers['x-linq-event'];
80
- const signature = headers['x-linq-signature'];
81
- // Verify signature (optional but recommended)
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 crypto = require('crypto');
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', credentials.integrationToken)
86
- .update(JSON.stringify(bodyData))
87
- .digest('hex');
88
- if (signature !== expectedSignature) {
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
- // Return the event data to start the workflow
92
- const returnData = [
93
- {
94
- json: {
95
- event,
96
- payload: bodyData,
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: [returnData],
151
+ workflowData: [
152
+ [
153
+ {
154
+ json: {
155
+ event: incomingEvent,
156
+ ...payload,
157
+ },
158
+ },
159
+ ],
160
+ ],
102
161
  };
103
162
  }
104
163
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-linq",
3
- "version": "0.2.0",
3
+ "version": "3.0.0",
4
4
  "description": "Linq API integration for n8n",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",