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 (
|
|
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: [
|
|
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
|
|
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.
|
|
586
|
+
qs.from = formatPhoneNumber(phoneNumber);
|
|
576
587
|
if (cursor)
|
|
577
588
|
qs.cursor = cursor;
|
|
578
|
-
if (
|
|
579
|
-
qs.
|
|
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
|
-
|
|
637
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
887
|
+
target_url: webhookUrl,
|
|
868
888
|
version: version,
|
|
869
889
|
active: active
|
|
870
890
|
};
|
|
871
891
|
if (events) {
|
|
872
|
-
body.
|
|
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.
|
|
917
|
+
body.target_url = webhookUrl;
|
|
898
918
|
}
|
|
899
919
|
if (events) {
|
|
900
|
-
body.
|
|
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
|
|
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)
|