n8n-nodes-sendit 1.0.3 → 1.0.4

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.
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SendIt = void 0;
4
+ // Base URL for SendIt API
5
+ const SENDIT_API_BASE_URL = 'https://sendit.infiniteappsai.com/api/v1';
4
6
  class SendIt {
5
7
  constructor() {
6
8
  this.description = {
@@ -23,7 +25,7 @@ class SendIt {
23
25
  },
24
26
  ],
25
27
  requestDefaults: {
26
- baseURL: 'https://sendit.infiniteappsai.com/api/v1',
28
+ baseURL: SENDIT_API_BASE_URL,
27
29
  headers: {
28
30
  Accept: 'application/json',
29
31
  'Content-Type': 'application/json',
@@ -52,6 +54,10 @@ class SendIt {
52
54
  name: 'Validation',
53
55
  value: 'validation',
54
56
  },
57
+ {
58
+ name: 'Analytics',
59
+ value: 'analytics',
60
+ },
55
61
  ],
56
62
  default: 'post',
57
63
  },
@@ -157,6 +163,27 @@ class SendIt {
157
163
  ],
158
164
  default: 'validate',
159
165
  },
166
+ // Analytics operations
167
+ {
168
+ displayName: 'Operation',
169
+ name: 'operation',
170
+ type: 'options',
171
+ noDataExpression: true,
172
+ displayOptions: {
173
+ show: {
174
+ resource: ['analytics'],
175
+ },
176
+ },
177
+ options: [
178
+ {
179
+ name: 'Get Analytics',
180
+ value: 'getAnalytics',
181
+ description: 'Get engagement analytics for posts on a platform',
182
+ action: 'Get analytics for a platform',
183
+ },
184
+ ],
185
+ default: 'getAnalytics',
186
+ },
160
187
  // Platforms field
161
188
  {
162
189
  displayName: 'Platforms',
@@ -263,6 +290,28 @@ class SendIt {
263
290
  },
264
291
  description: 'Filter scheduled posts by platform',
265
292
  },
293
+ // Analytics platform selector
294
+ {
295
+ displayName: 'Platform',
296
+ name: 'analyticsPlatform',
297
+ type: 'options',
298
+ options: [
299
+ { name: 'LinkedIn', value: 'linkedin' },
300
+ { name: 'Instagram', value: 'instagram' },
301
+ { name: 'Threads', value: 'threads' },
302
+ { name: 'TikTok', value: 'tiktok' },
303
+ { name: 'X (Twitter)', value: 'x' },
304
+ ],
305
+ default: 'linkedin',
306
+ required: true,
307
+ displayOptions: {
308
+ show: {
309
+ resource: ['analytics'],
310
+ operation: ['getAnalytics'],
311
+ },
312
+ },
313
+ description: 'The platform to get analytics for',
314
+ },
266
315
  // Additional options
267
316
  {
268
317
  displayName: 'Additional Options',
@@ -328,8 +377,10 @@ class SendIt {
328
377
  };
329
378
  response = await this.helpers.httpRequestWithAuthentication.call(this, 'sendItApi', {
330
379
  method: 'POST',
380
+ baseURL: SENDIT_API_BASE_URL,
331
381
  url: '/publish',
332
382
  body,
383
+ json: true,
333
384
  });
334
385
  }
335
386
  }
@@ -341,6 +392,7 @@ class SendIt {
341
392
  const scheduledTime = this.getNodeParameter('scheduledTime', i);
342
393
  response = await this.helpers.httpRequestWithAuthentication.call(this, 'sendItApi', {
343
394
  method: 'POST',
395
+ baseURL: SENDIT_API_BASE_URL,
344
396
  url: '/schedule',
345
397
  body: {
346
398
  platforms,
@@ -350,6 +402,7 @@ class SendIt {
350
402
  },
351
403
  scheduledTime,
352
404
  },
405
+ json: true,
353
406
  });
354
407
  }
355
408
  else if (operation === 'getAll') {
@@ -360,6 +413,7 @@ class SendIt {
360
413
  }
361
414
  response = await this.helpers.httpRequestWithAuthentication.call(this, 'sendItApi', {
362
415
  method: 'GET',
416
+ baseURL: SENDIT_API_BASE_URL,
363
417
  url: '/scheduled',
364
418
  qs,
365
419
  });
@@ -368,6 +422,7 @@ class SendIt {
368
422
  const scheduleId = this.getNodeParameter('scheduleId', i);
369
423
  response = await this.helpers.httpRequestWithAuthentication.call(this, 'sendItApi', {
370
424
  method: 'DELETE',
425
+ baseURL: SENDIT_API_BASE_URL,
371
426
  url: `/scheduled/${scheduleId}`,
372
427
  });
373
428
  }
@@ -375,6 +430,7 @@ class SendIt {
375
430
  const scheduleId = this.getNodeParameter('scheduleId', i);
376
431
  response = await this.helpers.httpRequestWithAuthentication.call(this, 'sendItApi', {
377
432
  method: 'POST',
433
+ baseURL: SENDIT_API_BASE_URL,
378
434
  url: `/scheduled/${scheduleId}/trigger`,
379
435
  });
380
436
  }
@@ -383,6 +439,7 @@ class SendIt {
383
439
  if (operation === 'getAll') {
384
440
  response = await this.helpers.httpRequestWithAuthentication.call(this, 'sendItApi', {
385
441
  method: 'GET',
442
+ baseURL: SENDIT_API_BASE_URL,
386
443
  url: '/accounts',
387
444
  });
388
445
  }
@@ -395,6 +452,7 @@ class SendIt {
395
452
  const additionalOptions = this.getNodeParameter('additionalOptions', i);
396
453
  response = await this.helpers.httpRequestWithAuthentication.call(this, 'sendItApi', {
397
454
  method: 'POST',
455
+ baseURL: SENDIT_API_BASE_URL,
398
456
  url: '/validate',
399
457
  body: {
400
458
  platforms,
@@ -407,6 +465,20 @@ class SendIt {
407
465
  mediaType: additionalOptions.mediaType || 'auto',
408
466
  },
409
467
  },
468
+ json: true,
469
+ });
470
+ }
471
+ }
472
+ else if (resource === 'analytics') {
473
+ if (operation === 'getAnalytics') {
474
+ const platform = this.getNodeParameter('analyticsPlatform', i);
475
+ response = await this.helpers.httpRequestWithAuthentication.call(this, 'sendItApi', {
476
+ method: 'GET',
477
+ baseURL: SENDIT_API_BASE_URL,
478
+ url: '/analytics',
479
+ qs: {
480
+ platform,
481
+ },
410
482
  });
411
483
  }
412
484
  }
@@ -1,6 +1,55 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.SendItTrigger = void 0;
7
+ const n8n_workflow_1 = require("n8n-workflow");
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ // Base URL for SendIt API
10
+ const SENDIT_API_BASE_URL = 'https://sendit.infiniteappsai.com/api/v1';
11
+ // Signature tolerance in seconds (5 minutes)
12
+ const SIGNATURE_TOLERANCE_SECONDS = 300;
13
+ /**
14
+ * Verify webhook signature using HMAC-SHA256
15
+ * Signature format: t=<timestamp>,v1=<hex_signature>
16
+ */
17
+ function verifyWebhookSignature(payload, signature, secret) {
18
+ try {
19
+ const parts = signature.split(',');
20
+ const timestampPart = parts.find((p) => p.startsWith('t='));
21
+ const signaturePart = parts.find((p) => p.startsWith('v1='));
22
+ if (!timestampPart || !signaturePart) {
23
+ return false;
24
+ }
25
+ const timestamp = parseInt(timestampPart.substring(2), 10);
26
+ const expectedSignature = signaturePart.substring(3);
27
+ // Check timestamp tolerance
28
+ const now = Math.floor(Date.now() / 1000);
29
+ if (Math.abs(now - timestamp) > SIGNATURE_TOLERANCE_SECONDS) {
30
+ return false;
31
+ }
32
+ // Compute expected signature
33
+ const signedPayload = `${timestamp}.${payload}`;
34
+ const computedSignature = crypto_1.default
35
+ .createHmac('sha256', secret)
36
+ .update(signedPayload)
37
+ .digest('hex');
38
+ // Use timing-safe comparison
39
+ if (expectedSignature.length !== computedSignature.length) {
40
+ return false;
41
+ }
42
+ const expectedBuf = Buffer.from(expectedSignature, 'hex');
43
+ const computedBuf = Buffer.from(computedSignature, 'hex');
44
+ if (expectedBuf.length !== computedBuf.length) {
45
+ return false;
46
+ }
47
+ return crypto_1.default.timingSafeEqual(expectedBuf, computedBuf);
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
4
53
  class SendItTrigger {
5
54
  constructor() {
6
55
  this.description = {
@@ -84,7 +133,8 @@ class SendItTrigger {
84
133
  try {
85
134
  const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sendItApi', {
86
135
  method: 'GET',
87
- url: `https://sendit.infiniteappsai.com/api/v1/webhooks/${webhookData.webhookId}`,
136
+ baseURL: SENDIT_API_BASE_URL,
137
+ url: `/webhooks/${webhookData.webhookId}`,
88
138
  });
89
139
  if (response && ((_a = response.webhook) === null || _a === void 0 ? void 0 : _a.url) === webhookUrl) {
90
140
  return true;
@@ -107,8 +157,12 @@ class SendItTrigger {
107
157
  };
108
158
  const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sendItApi', {
109
159
  method: 'POST',
110
- url: 'https://sendit.infiniteappsai.com/api/v1/webhooks',
111
- body,
160
+ baseURL: SENDIT_API_BASE_URL,
161
+ url: '/webhooks',
162
+ headers: {
163
+ 'Content-Type': 'application/json',
164
+ },
165
+ body: JSON.stringify(body),
112
166
  });
113
167
  const webhookResponse = response;
114
168
  if ((_a = webhookResponse.webhook) === null || _a === void 0 ? void 0 : _a.id) {
@@ -124,7 +178,8 @@ class SendItTrigger {
124
178
  try {
125
179
  await this.helpers.httpRequestWithAuthentication.call(this, 'sendItApi', {
126
180
  method: 'DELETE',
127
- url: `https://sendit.infiniteappsai.com/api/v1/webhooks/${webhookData.webhookId}`,
181
+ baseURL: SENDIT_API_BASE_URL,
182
+ url: `/webhooks/${webhookData.webhookId}`,
128
183
  });
129
184
  }
130
185
  catch {
@@ -139,7 +194,24 @@ class SendItTrigger {
139
194
  };
140
195
  }
141
196
  async webhook() {
197
+ const req = this.getRequestObject();
198
+ const webhookData = this.getWorkflowStaticData('node');
199
+ const secret = webhookData.webhookSecret;
200
+ // Get the signature header
201
+ const signature = req.headers['x-sendit-signature'];
202
+ // Get raw body for signature verification
142
203
  const bodyData = this.getBodyData();
204
+ const rawBody = JSON.stringify(bodyData);
205
+ // Verify signature if we have a secret
206
+ if (secret) {
207
+ if (!signature) {
208
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Missing X-SendIt-Signature header. Webhook request rejected.');
209
+ }
210
+ const isValid = verifyWebhookSignature(rawBody, signature, secret);
211
+ if (!isValid) {
212
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid webhook signature. Request may be tampered with or expired.');
213
+ }
214
+ }
143
215
  return {
144
216
  workflowData: [
145
217
  this.helpers.returnJsonArray(bodyData),
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60">
2
+ <rect width="60" height="60" rx="12" fill="#4F46E5"/>
3
+ <path d="M15 30L25 40L45 20" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
4
+ <path d="M30 15L42 27" stroke="white" stroke-width="3" stroke-linecap="round" opacity="0.6"/>
5
+ <path d="M18 33L33 45" stroke="white" stroke-width="3" stroke-linecap="round" opacity="0.6"/>
6
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-sendit",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "n8n community node for SendIt - Multi-platform social media publishing",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",
@@ -11,7 +11,8 @@
11
11
  "tiktok",
12
12
  "twitter",
13
13
  "publishing",
14
- "scheduling"
14
+ "scheduling",
15
+ "analytics"
15
16
  ],
16
17
  "license": "MIT",
17
18
  "homepage": "https://sendit.infiniteappsai.com",
@@ -26,7 +27,7 @@
26
27
  "main": "index.js",
27
28
  "scripts": {
28
29
  "build": "tsc && npm run copy:icons",
29
- "copy:icons": "mkdir -p dist/nodes/SendIt && cp nodes/SendIt/*.svg dist/nodes/SendIt/ 2>/dev/null || true",
30
+ "copy:icons": "mkdir -p dist/nodes/SendIt && cp nodes/SendIt/*.svg dist/nodes/SendIt/",
30
31
  "dev": "tsc --watch",
31
32
  "format": "prettier nodes credentials --write",
32
33
  "lint": "eslint nodes credentials --ext .ts --fix",