n8n-nodes-linq 3.0.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.
@@ -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 (phone_number)',
227
+ description: 'Required: your Linq phone number (from)',
228
228
  },
229
229
  {
230
230
  displayName: 'From Phone Number',
@@ -246,15 +246,26 @@ class Linq {
246
246
  displayName: 'Cursor',
247
247
  name: 'cursor',
248
248
  type: 'string',
249
- displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
249
+ displayOptions: { show: { resource: ['chat', 'chatMessage'], operation: ['getAll'] } },
250
250
  default: '',
251
251
  description: 'Cursor for pagination',
252
252
  },
253
+ {
254
+ displayName: 'Limit',
255
+ name: 'limit',
256
+ type: 'number',
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',
263
+ },
253
264
  {
254
265
  displayName: 'Per Page',
255
266
  name: 'perPage',
256
267
  type: 'number',
257
- displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
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
  },
@@ -567,16 +578,16 @@ class Linq {
567
578
  if (operation === 'getAll') {
568
579
  const phoneNumber = this.getNodeParameter('phoneNumber', i, '');
569
580
  const cursor = this.getNodeParameter('cursor', i, '');
570
- const perPage = this.getNodeParameter('perPage', i);
581
+ const limit = this.getNodeParameter('limit', i);
571
582
  if (!phoneNumber) {
572
583
  throw new n8n_workflow_1.ApplicationError('phone_number is required by Linq API');
573
584
  }
574
585
  const qs = {};
575
- qs.phone_number = formatPhoneNumber(phoneNumber);
586
+ qs.from = formatPhoneNumber(phoneNumber);
576
587
  if (cursor)
577
588
  qs.cursor = cursor;
578
- if (perPage && perPage !== 25)
579
- qs.per_page = perPage;
589
+ if (limit && limit !== 50)
590
+ qs.limit = limit;
580
591
  responseData = await this.helpers.request({
581
592
  method: 'GET',
582
593
  url: 'https://api.linqapp.com/api/partner/v3/chats',
@@ -633,16 +644,14 @@ class Linq {
633
644
  }
634
645
  const parts = await createParts(messageText, attachmentUrls);
635
646
  const body = {
636
- chat: {
637
- phone_numbers: phoneNumbers.split(',').map(p => formatPhoneNumber(p.trim()))
638
- },
647
+ from: formatPhoneNumber(sendFrom),
648
+ to: phoneNumbers.split(',').map(p => formatPhoneNumber(p.trim())),
639
649
  message: {
640
650
  parts
641
651
  }
642
652
  };
643
- body.send_from = formatPhoneNumber(sendFrom);
644
653
  if (displayName) {
645
- body.chat.display_name = displayName;
654
+ body.display_name = displayName;
646
655
  }
647
656
  responseData = await this.helpers.request({
648
657
  method: 'POST',
@@ -677,12 +686,20 @@ class Linq {
677
686
  if (resource === 'chatMessage') {
678
687
  if (operation === 'getAll') {
679
688
  const chatId = this.getNodeParameter('chatId', i);
689
+ const cursor = this.getNodeParameter('cursor', i, '');
690
+ const limit = this.getNodeParameter('limit', i);
680
691
  if (!chatId) {
681
692
  throw new n8n_workflow_1.ApplicationError('chatId is required to list chat messages');
682
693
  }
694
+ const qs = {};
695
+ if (cursor)
696
+ qs.cursor = cursor;
697
+ if (limit && limit !== 50)
698
+ qs.limit = limit;
683
699
  responseData = await this.helpers.request({
684
700
  method: 'GET',
685
701
  url: `https://api.linqapp.com/api/partner/v3/chats/${chatId}/messages`,
702
+ qs,
686
703
  headers: {
687
704
  'Authorization': `Bearer ${credentials.integrationToken}`,
688
705
  'Accept': 'application/json'
@@ -712,10 +729,12 @@ class Linq {
712
729
  }
713
730
  const parts = await createParts(messageText, attachmentUrls);
714
731
  const body = {
715
- parts
732
+ message: {
733
+ parts
734
+ }
716
735
  };
717
736
  if (idempotencyKey) {
718
- body.idempotency_key = idempotencyKey;
737
+ body.message.idempotency_key = idempotencyKey;
719
738
  }
720
739
  responseData = await this.helpers.request({
721
740
  method: 'POST',
@@ -763,7 +782,8 @@ class Linq {
763
782
  const chatMessageId = this.getNodeParameter('chatMessageId', i);
764
783
  const reaction = this.getNodeParameter('reaction', i);
765
784
  const body = {
766
- reaction: reaction
785
+ operation: 'add',
786
+ type: reaction
767
787
  };
768
788
  responseData = await this.helpers.request({
769
789
  method: 'POST',
@@ -864,12 +884,12 @@ class Linq {
864
884
  const version = this.getNodeParameter('version', i);
865
885
  const active = this.getNodeParameter('active', i);
866
886
  const body = {
867
- webhook_url: webhookUrl,
887
+ target_url: webhookUrl,
868
888
  version: version,
869
889
  active: active
870
890
  };
871
891
  if (events) {
872
- body.events = events.split(',').map(e => e.trim());
892
+ body.subscribed_events = events.split(',').map(e => e.trim());
873
893
  }
874
894
  responseData = await this.helpers.request({
875
895
  method: 'POST',
@@ -894,10 +914,10 @@ class Linq {
894
914
  active: active
895
915
  };
896
916
  if (webhookUrl) {
897
- body.webhook_url = webhookUrl;
917
+ body.target_url = webhookUrl;
898
918
  }
899
919
  if (events) {
900
- body.events = events.split(',').map(e => e.trim());
920
+ body.subscribed_events = events.split(',').map(e => e.trim());
901
921
  }
902
922
  responseData = await this.helpers.request({
903
923
  method: 'PUT',
@@ -113,9 +113,19 @@ class LinqTrigger {
113
113
  const bodyData = this.getBodyData();
114
114
  // 1. Validate and Parse Signature Header
115
115
  const signatureHeader = headers['x-webhook-signature'];
116
+ const timestampHeader = headers['x-webhook-timestamp'];
116
117
  if (!signatureHeader) {
117
118
  throw new n8n_workflow_1.ApplicationError('Missing X-Webhook-Signature header');
118
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
+ }
119
129
  const [scheme, signature] = signatureHeader.split('=');
120
130
  if (scheme !== 'sha256' || !signature) {
121
131
  throw new n8n_workflow_1.ApplicationError('Invalid signature format');
@@ -128,7 +138,8 @@ class LinqTrigger {
128
138
  if (!rawBody) {
129
139
  console.warn('LinqTrigger: rawBody is missing, falling back to JSON.stringify. Signature verification might fail.');
130
140
  }
131
- const payloadToHash = rawBody || JSON.stringify(bodyData);
141
+ const bodyString = rawBody || JSON.stringify(bodyData);
142
+ const payloadToHash = `${timestampHeader}.${bodyString}`;
132
143
  const expectedSignature = crypto
133
144
  .createHmac('sha256', token)
134
145
  .update(payloadToHash)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-linq",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "description": "Linq API integration for n8n",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",