n8n-nodes-withings 0.6.3 → 0.7.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.
@@ -2,6 +2,88 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WithingsApi = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
+ const constants_1 = require("../../utils/constants");
6
+ const tokenHelpers_1 = require("../../utils/tokenHelpers");
7
+ function getEndpointForResource(resource, operation) {
8
+ switch (resource) {
9
+ case 'activity':
10
+ return constants_1.ENDPOINTS.MEASURE;
11
+ case 'measure':
12
+ return operation === 'getmeas' ? constants_1.ENDPOINTS.MEASURE_V1 : constants_1.ENDPOINTS.MEASURE;
13
+ case 'sleep':
14
+ return constants_1.ENDPOINTS.SLEEP;
15
+ case 'user':
16
+ return constants_1.ENDPOINTS.USER;
17
+ default:
18
+ return constants_1.ENDPOINTS.USER;
19
+ }
20
+ }
21
+ async function executeWithRetry(context, baseEndpoint, baseQs, resource, operation) {
22
+ const retryContext = {
23
+ retries: 0,
24
+ maxRetries: constants_1.TOKEN_CONFIG.MAX_RETRIES,
25
+ tokenRefreshed: false,
26
+ };
27
+ await (0, tokenHelpers_1.performPreValidation)(context);
28
+ const directRefreshResult = await (0, tokenHelpers_1.performDirectTokenRefresh)(context);
29
+ retryContext.tokenRefreshed = directRefreshResult.success;
30
+ const createFreshOptions = () => {
31
+ return {
32
+ method: 'GET',
33
+ url: `${constants_1.WITHINGS_API.BASE_URL}${baseEndpoint}`,
34
+ qs: {
35
+ ...baseQs,
36
+ _ts: (0, tokenHelpers_1.generateUniqueTimestamp)(),
37
+ },
38
+ json: true,
39
+ headers: {
40
+ ...(0, tokenHelpers_1.createRequestHeaders)(),
41
+ 'Content-Type': 'application/json',
42
+ 'X-Request-Attempt': `${retryContext.retries + 1}`,
43
+ },
44
+ };
45
+ };
46
+ while (retryContext.retries < retryContext.maxRetries) {
47
+ try {
48
+ if (retryContext.retries > 0) {
49
+ const delay = (0, tokenHelpers_1.calculateBackoffDelay)(retryContext.retries);
50
+ await (0, n8n_workflow_1.sleep)(delay);
51
+ const refreshResult = await (0, tokenHelpers_1.refreshTokenForRetry)(context, retryContext.retries);
52
+ retryContext.tokenRefreshed = refreshResult.success;
53
+ }
54
+ else {
55
+ await (0, n8n_workflow_1.sleep)(constants_1.TOKEN_CONFIG.INITIAL_DELAY);
56
+ }
57
+ if (resource === 'sleep') {
58
+ await (0, tokenHelpers_1.validateSleepToken)(context, operation);
59
+ }
60
+ const freshOptions = createFreshOptions();
61
+ const response = await context.helpers.requestWithAuthentication.call(context, 'withingsOAuth2Api', freshOptions);
62
+ await (0, n8n_workflow_1.sleep)(500);
63
+ return response;
64
+ }
65
+ catch (error) {
66
+ if ((0, tokenHelpers_1.isTokenError)(error)) {
67
+ retryContext.retries++;
68
+ retryContext.lastError = error;
69
+ if (retryContext.retries >= retryContext.maxRetries) {
70
+ const errorMessage = (0, tokenHelpers_1.createTokenErrorMessage)(retryContext.maxRetries, error, retryContext.tokenRefreshed);
71
+ throw new n8n_workflow_1.NodeApiError(context.getNode(), error, { message: errorMessage });
72
+ }
73
+ const delay = (0, tokenHelpers_1.calculateBackoffDelay)(retryContext.retries, 1500);
74
+ await (0, n8n_workflow_1.sleep)(delay);
75
+ retryContext.tokenRefreshed = false;
76
+ const refreshResult = await (0, tokenHelpers_1.executeRefreshStrategies)(context);
77
+ retryContext.tokenRefreshed = refreshResult.success;
78
+ continue;
79
+ }
80
+ else {
81
+ throw error;
82
+ }
83
+ }
84
+ }
85
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Max retries exceeded without throwing error');
86
+ }
5
87
  class WithingsApi {
6
88
  constructor() {
7
89
  this.description = {
@@ -357,478 +439,21 @@ class WithingsApi {
357
439
  if (additionalFields.offset) {
358
440
  qs.offset = additionalFields.offset;
359
441
  }
360
- if (resource === 'activity') {
361
- endpoint = '/v2/measure';
362
- if (operation === 'getactivity') {
363
- qs.action = 'getactivity';
364
- }
365
- else if (operation === 'getsummary') {
366
- qs.action = 'getsummary';
367
- }
368
- else if (operation === 'getworkouts') {
369
- qs.action = 'getworkouts';
370
- }
371
- }
372
- else if (resource === 'measure') {
373
- if (operation === 'getmeas') {
374
- endpoint = '/measure';
375
- qs.action = 'getmeas';
376
- const measTypes = this.getNodeParameter('meastype', i, []);
377
- if (measTypes.length > 0) {
378
- qs.meastype = measTypes.join(',');
379
- }
380
- }
381
- else if (operation === 'getactivity') {
382
- endpoint = '/v2/measure';
383
- qs.action = 'getactivity';
384
- }
385
- else if (operation === 'getintradayactivity') {
386
- endpoint = '/v2/measure';
387
- qs.action = 'getintradayactivity';
388
- }
389
- }
390
- else if (resource === 'sleep') {
391
- endpoint = '/v2/sleep';
392
- if (operation === 'get') {
393
- qs.action = 'get';
394
- const dataFields = this.getNodeParameter('dataFields', i, []);
395
- if (dataFields.length > 0) {
396
- qs.data_fields = dataFields.join(',');
397
- }
398
- }
399
- else if (operation === 'getsummary') {
400
- qs.action = 'getsummary';
401
- }
402
- }
403
- else if (resource === 'user') {
404
- endpoint = '/v2/user';
405
- if (operation === 'getdevice') {
406
- qs.action = 'getdevice';
407
- }
408
- else if (operation === 'getgoals') {
409
- qs.action = 'getgoals';
410
- }
411
- else if (operation === 'get') {
412
- qs.action = 'get';
413
- }
414
- }
415
- const baseEndpoint = endpoint;
416
- const baseQs = { ...qs };
417
- let response;
418
- let retries = 0;
419
- const maxRetries = 5;
420
- const baseDelay = 1000;
421
- for (let preValidationAttempt = 0; preValidationAttempt < 7; preValidationAttempt++) {
422
- try {
423
- const uniqueTimestamp = Date.now() + Math.floor(Math.random() * 1000);
424
- await this.helpers.requestWithAuthentication.call(this, 'withingsOAuth2Api', {
425
- method: 'GET',
426
- url: 'https://wbsapi.withings.net/v2/user',
427
- qs: {
428
- action: 'getdevice',
429
- _ts: uniqueTimestamp,
430
- },
431
- json: true,
432
- headers: {
433
- 'Accept': 'application/json',
434
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
435
- 'Pragma': 'no-cache',
436
- 'Expires': '0',
437
- 'X-Request-ID': `validation-${uniqueTimestamp}`,
438
- },
439
- timeout: 10000,
440
- });
441
- await (0, n8n_workflow_1.sleep)(2000);
442
- break;
443
- }
444
- catch (validationError) {
445
- if (preValidationAttempt === 6) {
446
- await (0, n8n_workflow_1.sleep)(4000);
447
- continue;
448
- }
449
- const jitter = Math.random() * 0.4 + 0.8;
450
- await (0, n8n_workflow_1.sleep)(1800 * Math.pow(1.5, preValidationAttempt) * jitter);
451
- try {
452
- const uniqueTimestamp = Date.now() + Math.floor(Math.random() * 1000);
453
- await this.helpers.requestWithAuthentication.call(this, 'withingsOAuth2Api', {
454
- method: 'GET',
455
- url: 'https://wbsapi.withings.net/v2/measure',
456
- qs: {
457
- action: 'getactivity',
458
- _ts: uniqueTimestamp,
459
- },
460
- json: true,
461
- headers: {
462
- 'Accept': 'application/json',
463
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
464
- 'Pragma': 'no-cache',
465
- 'Expires': '0',
466
- 'X-Request-ID': `validation-alt-${uniqueTimestamp}`,
467
- },
468
- timeout: 10000,
469
- });
470
- await (0, n8n_workflow_1.sleep)(2000);
471
- break;
472
- }
473
- catch (alternateValidationError) {
474
- try {
475
- const uniqueTimestamp = Date.now() + Math.floor(Math.random() * 1000);
476
- await this.helpers.requestWithAuthentication.call(this, 'withingsOAuth2Api', {
477
- method: 'GET',
478
- url: 'https://wbsapi.withings.net/v2/sleep',
479
- qs: {
480
- action: 'get',
481
- _ts: uniqueTimestamp,
482
- },
483
- json: true,
484
- headers: {
485
- 'Accept': 'application/json',
486
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
487
- 'Pragma': 'no-cache',
488
- 'Expires': '0',
489
- 'X-Request-ID': `validation-third-${uniqueTimestamp}`,
490
- },
491
- timeout: 10000,
492
- });
493
- await (0, n8n_workflow_1.sleep)(2000);
494
- break;
495
- }
496
- catch (thirdValidationError) {
497
- try {
498
- const uniqueTimestamp = Date.now() + Math.floor(Math.random() * 1000);
499
- await this.helpers.requestWithAuthentication.call(this, 'withingsOAuth2Api', {
500
- method: 'GET',
501
- url: 'https://wbsapi.withings.net/v2/sleep',
502
- qs: {
503
- action: 'getsummary',
504
- _ts: uniqueTimestamp,
505
- },
506
- json: true,
507
- headers: {
508
- 'Accept': 'application/json',
509
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
510
- 'Pragma': 'no-cache',
511
- 'Expires': '0',
512
- 'X-Request-ID': `validation-fourth-${uniqueTimestamp}`,
513
- },
514
- timeout: 10000,
515
- });
516
- await (0, n8n_workflow_1.sleep)(3000);
517
- break;
518
- }
519
- catch (fourthValidationError) {
520
- await (0, n8n_workflow_1.sleep)(2500);
521
- }
522
- }
523
- }
524
- }
525
- }
526
- let tokenRefreshed = false;
527
- for (let directRefreshAttempt = 0; directRefreshAttempt < 5; directRefreshAttempt++) {
528
- try {
529
- const uniqueTimestamp = Date.now() + Math.floor(Math.random() * 1000);
530
- await this.helpers.requestWithAuthentication.call(this, 'withingsOAuth2Api', {
531
- method: 'GET',
532
- url: 'https://wbsapi.withings.net/v2/user',
533
- qs: {
534
- action: 'get',
535
- _ts: uniqueTimestamp,
536
- },
537
- json: true,
538
- headers: {
539
- 'Accept': 'application/json',
540
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
541
- 'Pragma': 'no-cache',
542
- 'Expires': '0',
543
- 'X-Request-ID': `direct-refresh-${uniqueTimestamp}`,
544
- },
545
- timeout: 10000,
546
- });
547
- tokenRefreshed = true;
548
- await (0, n8n_workflow_1.sleep)(2500);
549
- break;
550
- }
551
- catch (directRefreshError) {
552
- const fallbackEndpoints = [
553
- {
554
- url: 'https://wbsapi.withings.net/v2/sleep',
555
- action: 'get',
556
- waitTime: 2000,
557
- },
558
- {
559
- url: 'https://wbsapi.withings.net/v2/measure',
560
- action: 'getactivity',
561
- waitTime: 2200,
562
- },
563
- {
564
- url: 'https://wbsapi.withings.net/v2/user',
565
- action: 'getdevice',
566
- waitTime: 2500,
567
- },
568
- {
569
- url: 'https://wbsapi.withings.net/notify',
570
- action: 'list',
571
- waitTime: 3000,
572
- },
573
- ];
574
- if (directRefreshAttempt < 4) {
575
- const fallbackEndpoint = fallbackEndpoints[directRefreshAttempt];
576
- try {
577
- const uniqueTimestamp = Date.now() + Math.floor(Math.random() * 1000);
578
- await this.helpers.requestWithAuthentication.call(this, 'withingsOAuth2Api', {
579
- method: 'GET',
580
- url: fallbackEndpoint.url,
581
- qs: {
582
- action: fallbackEndpoint.action,
583
- _ts: uniqueTimestamp,
584
- },
585
- json: true,
586
- headers: {
587
- 'Accept': 'application/json',
588
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
589
- 'Pragma': 'no-cache',
590
- 'Expires': '0',
591
- 'X-Request-ID': `fallback-refresh-${directRefreshAttempt}-${uniqueTimestamp}`,
592
- },
593
- timeout: 10000,
594
- });
595
- tokenRefreshed = true;
596
- await (0, n8n_workflow_1.sleep)(fallbackEndpoint.waitTime);
597
- break;
598
- }
599
- catch (fallbackError) {
600
- const jitter = Math.random() * 0.4 + 0.8;
601
- const delay = Math.floor(1200 * Math.pow(1.5, directRefreshAttempt) * jitter);
602
- await (0, n8n_workflow_1.sleep)(delay);
603
- }
604
- }
605
- else {
606
- await (0, n8n_workflow_1.sleep)(3000);
607
- }
442
+ endpoint = getEndpointForResource(resource, operation);
443
+ qs.action = operation;
444
+ if (resource === 'measure' && operation === 'getmeas') {
445
+ const measTypes = this.getNodeParameter('meastype', i, []);
446
+ if (measTypes.length > 0) {
447
+ qs.meastype = measTypes.join(',');
608
448
  }
609
449
  }
610
- const createFreshOptions = () => {
611
- return {
612
- method: 'GET',
613
- url: `https://wbsapi.withings.net${baseEndpoint}`,
614
- qs: {
615
- ...baseQs,
616
- _ts: Date.now(),
617
- },
618
- json: true,
619
- headers: {
620
- 'Accept': 'application/json',
621
- 'Content-Type': 'application/json',
622
- 'Cache-Control': 'no-cache, no-store',
623
- 'Pragma': 'no-cache',
624
- 'X-Request-Attempt': '',
625
- },
626
- };
627
- };
628
- while (retries < maxRetries) {
629
- try {
630
- if (retries > 0) {
631
- const jitter = Math.random() * 0.3 + 0.85;
632
- const delay = Math.floor(baseDelay * Math.pow(2, retries - 1) * jitter);
633
- await (0, n8n_workflow_1.sleep)(delay);
634
- try {
635
- const refreshEndpoints = [
636
- { url: 'https://wbsapi.withings.net/v2/user', action: 'get' },
637
- { url: 'https://wbsapi.withings.net/v2/user', action: 'getdevice' },
638
- { url: 'https://wbsapi.withings.net/v2/measure', action: 'getactivity' },
639
- { url: 'https://wbsapi.withings.net/v2/sleep', action: 'get' },
640
- ];
641
- const endpoint = refreshEndpoints[retries % refreshEndpoints.length];
642
- await this.helpers.requestWithAuthentication.call(this, 'withingsOAuth2Api', {
643
- method: 'GET',
644
- url: endpoint.url,
645
- qs: {
646
- action: endpoint.action,
647
- _ts: Date.now(),
648
- },
649
- json: true,
650
- headers: {
651
- 'Accept': 'application/json',
652
- 'Cache-Control': 'no-cache, no-store',
653
- 'Pragma': 'no-cache',
654
- },
655
- });
656
- tokenRefreshed = true;
657
- await (0, n8n_workflow_1.sleep)(2000);
658
- }
659
- catch (retryRefreshError) {
660
- await (0, n8n_workflow_1.sleep)(1000);
661
- }
662
- }
663
- else {
664
- await (0, n8n_workflow_1.sleep)(1500);
665
- }
666
- const freshOptions = createFreshOptions();
667
- freshOptions.headers = {
668
- ...freshOptions.headers,
669
- 'X-Request-Attempt': `${retries + 1}`,
670
- };
671
- if (resource === 'sleep') {
672
- try {
673
- const uniqueTimestamp = Date.now() + Math.floor(Math.random() * 1000);
674
- await this.helpers.requestWithAuthentication.call(this, 'withingsOAuth2Api', {
675
- method: 'GET',
676
- url: 'https://wbsapi.withings.net/v2/sleep',
677
- qs: {
678
- action: operation === 'getsummary' ? 'getsummary' : 'get',
679
- _ts: uniqueTimestamp,
680
- },
681
- json: true,
682
- headers: {
683
- 'Accept': 'application/json',
684
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
685
- 'Pragma': 'no-cache',
686
- 'Expires': '0',
687
- 'X-Request-ID': `sleep-validation-${uniqueTimestamp}`,
688
- },
689
- timeout: 10000,
690
- });
691
- await (0, n8n_workflow_1.sleep)(2500);
692
- }
693
- catch (sleepValidationError) {
694
- await (0, n8n_workflow_1.sleep)(1500);
695
- }
696
- }
697
- response = await this.helpers.requestWithAuthentication.call(this, 'withingsOAuth2Api', freshOptions);
698
- await (0, n8n_workflow_1.sleep)(500);
699
- break;
700
- }
701
- catch (error) {
702
- const errorMsg = (error.message || '').toLowerCase();
703
- const isTokenError = errorMsg.includes('token') ||
704
- errorMsg.includes('sign') ||
705
- errorMsg.includes('auth') ||
706
- errorMsg.includes('unauthorized') ||
707
- errorMsg.includes('expired') ||
708
- errorMsg.includes('authentication') ||
709
- errorMsg.includes('credentials') ||
710
- errorMsg.includes('access') ||
711
- errorMsg.includes('permission') ||
712
- errorMsg.includes('invalid') ||
713
- errorMsg.includes('oauth') ||
714
- errorMsg.includes('401') ||
715
- errorMsg.includes('403') ||
716
- errorMsg.includes('denied') ||
717
- errorMsg.includes('reject') ||
718
- errorMsg.includes('login') ||
719
- errorMsg.includes('signature') ||
720
- errorMsg.includes('identity') ||
721
- errorMsg.includes('verify') ||
722
- errorMsg.includes('key') ||
723
- errorMsg.includes('secret');
724
- if (isTokenError) {
725
- retries++;
726
- if (retries >= maxRetries) {
727
- const detailedErrorMessage = `Failed after ${maxRetries} attempts: ${error.message}.
728
- Error type: ${error.name || 'Unknown'}, Status code: ${error.statusCode || 'N/A'}.
729
- Token refresh status: ${tokenRefreshed ? 'Refreshed' : 'Not refreshed'}.
730
-
731
- The token may be invalid or revoked. Please try the following:
732
- 1. Reconnect your Withings account in the credentials
733
- 2. Ensure your Withings Developer account is active
734
- 3. Check that your application has the required scopes
735
- 4. Verify that your Withings account is active and properly configured
736
- 5. Try again in a few minutes as Withings API may be experiencing temporary issues`;
737
- throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
738
- message: detailedErrorMessage
739
- });
740
- }
741
- if (retries <= maxRetries - 1) {
742
- const jitter = Math.random() * 0.4 + 0.8;
743
- const refreshDelay = Math.min(1500 * Math.pow(2, retries - 1) * jitter, 8000);
744
- await (0, n8n_workflow_1.sleep)(refreshDelay);
745
- tokenRefreshed = false;
746
- const refreshStrategies = [
747
- {
748
- url: 'https://wbsapi.withings.net/v2/user',
749
- qs: { action: 'getdevice', _ts: Date.now() + Math.random() },
750
- waitTime: 1200,
751
- headers: {
752
- 'Accept': 'application/json',
753
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
754
- 'Pragma': 'no-cache',
755
- 'Expires': '0',
756
- },
757
- },
758
- {
759
- url: 'https://wbsapi.withings.net/v2/measure',
760
- qs: { action: 'getactivity', _ts: Date.now() + Math.random() },
761
- waitTime: 1500,
762
- headers: {
763
- 'Accept': 'application/json',
764
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
765
- 'Pragma': 'no-cache',
766
- 'Expires': '0',
767
- },
768
- },
769
- {
770
- url: 'https://wbsapi.withings.net/v2/user',
771
- qs: { action: 'get', _ts: Date.now() + Math.random() },
772
- waitTime: 1800,
773
- headers: {
774
- 'Accept': 'application/json',
775
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
776
- 'Pragma': 'no-cache',
777
- 'Expires': '0',
778
- },
779
- },
780
- {
781
- url: 'https://wbsapi.withings.net/v2/sleep',
782
- qs: { action: 'get', _ts: Date.now() + Math.random() },
783
- waitTime: 2000,
784
- headers: {
785
- 'Accept': 'application/json',
786
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
787
- 'Pragma': 'no-cache',
788
- 'Expires': '0',
789
- },
790
- },
791
- {
792
- url: 'https://wbsapi.withings.net/notify',
793
- qs: { action: 'list', _ts: Date.now() + Math.random() },
794
- waitTime: 2500,
795
- headers: {
796
- 'Accept': 'application/json',
797
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
798
- 'Pragma': 'no-cache',
799
- 'Expires': '0',
800
- },
801
- },
802
- ];
803
- for (const strategy of refreshStrategies) {
804
- try {
805
- await (0, n8n_workflow_1.sleep)(strategy.waitTime);
806
- await this.helpers.requestWithAuthentication.call(this, 'withingsOAuth2Api', {
807
- method: 'GET',
808
- url: strategy.url,
809
- qs: strategy.qs,
810
- json: true,
811
- headers: strategy.headers,
812
- });
813
- tokenRefreshed = true;
814
- await (0, n8n_workflow_1.sleep)(2000);
815
- break;
816
- }
817
- catch (strategyError) {
818
- continue;
819
- }
820
- }
821
- if (!tokenRefreshed) {
822
- await (0, n8n_workflow_1.sleep)(3000);
823
- }
824
- }
825
- continue;
826
- }
827
- else {
828
- throw error;
829
- }
450
+ else if (resource === 'sleep' && operation === 'get') {
451
+ const dataFields = this.getNodeParameter('dataFields', i, []);
452
+ if (dataFields.length > 0) {
453
+ qs.data_fields = dataFields.join(',');
830
454
  }
831
455
  }
456
+ const response = await executeWithRetry(this, endpoint, qs, resource, operation);
832
457
  if (response.status !== 0) {
833
458
  const errorMessage = `Withings API Error: ${response.status} - ${response.error || 'Unknown error'}`;
834
459
  if (this.continueOnFail()) {
@@ -844,7 +469,7 @@ class WithingsApi {
844
469
  });
845
470
  continue;
846
471
  }
847
- throw new n8n_workflow_1.NodeApiError(this.getNode(), response, { message: errorMessage });
472
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: errorMessage });
848
473
  }
849
474
  let formattedResponse = {};
850
475
  if (response.body) {