homey-api 3.17.4 → 3.17.6

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,5 @@
1
1
  'use strict';
2
2
 
3
- const SocketIOClient = require('socket.io-client');
4
3
  const Util = require('../Util');
5
4
  const HomeyAPI = require('./HomeyAPI');
6
5
  const HomeyAPIError = require('./HomeyAPIError');
@@ -14,6 +13,8 @@ const ManagerUsers = require('./HomeyAPIV3/ManagerUsers');
14
13
  const ManagerZones = require('./HomeyAPIV3/ManagerZones');
15
14
  const DiscoveryManager = require('./HomeyAPIV3/DiscoveryManager');
16
15
  const Manager = require('./HomeyAPIV3/Manager');
16
+ const SocketSession = require('./HomeyAPIV3/SocketSession');
17
+ const SubscriptionRegistry = require('./HomeyAPIV3/SubscriptionRegistry');
17
18
 
18
19
  /**
19
20
  * An authenticated Homey API. Do not construct this class manually.
@@ -118,6 +119,13 @@ class HomeyAPIV3 extends HomeyAPI {
118
119
  });
119
120
 
120
121
  this.__discoveryManager = new DiscoveryManager(this);
122
+ this.__socketSession = new SocketSession(this);
123
+ this.__subscriptionRegistry = new SubscriptionRegistry({
124
+ homey: this,
125
+ session: this.__socketSession,
126
+ });
127
+
128
+ this.__bindSocketSessionEvents();
121
129
 
122
130
  this.generateManagersFromSpecification();
123
131
  }
@@ -143,6 +151,31 @@ class HomeyAPIV3 extends HomeyAPI {
143
151
  return this.__strategyId;
144
152
  }
145
153
 
154
+ get __socket() {
155
+ return this.__socketSession.socket;
156
+ }
157
+
158
+ get __homeySocket() {
159
+ return this.__socketSession.homeySocket;
160
+ }
161
+
162
+ __bindSocketSessionEvents() {
163
+ for (const event of [
164
+ 'connect',
165
+ 'disconnect',
166
+ 'reconnect',
167
+ 'reconnect_attempt',
168
+ 'reconnecting',
169
+ 'reconnect_error',
170
+ 'connect_error',
171
+ 'error',
172
+ ]) {
173
+ this.__socketSession.on(event, (...args) => {
174
+ this.emit(event, ...args);
175
+ });
176
+ }
177
+ }
178
+
146
179
  /*
147
180
  * Generate Managers from JSON specification
148
181
  * A manager instance is created when it's first accessed
@@ -465,234 +498,46 @@ class HomeyAPIV3 extends HomeyAPI {
465
498
  * @returns {Boolean}
466
499
  */
467
500
  isConnected() {
468
- return Boolean(this.__homeySocket && this.__homeySocket.connected);
501
+ return this.__socketSession.isConnected();
469
502
  }
470
503
 
471
504
  async subscribe(
472
505
  uri,
473
- {
474
- onConnect = () => {},
475
- onReconnect = () => {},
476
- onReconnectError = () => {},
477
- onDisconnect = () => {},
478
- onEvent = () => {},
479
- }
506
+ handlers
480
507
  ) {
481
- this.__debug('subscribe', uri);
482
-
483
- await this.connect();
484
- await Util.timeout(
485
- new Promise((resolve, reject) => {
486
- if (this.isConnected() !== true) {
487
- reject(new Error('Not connected after connect.'));
488
- return;
489
- }
490
-
491
- this.__homeySocket.once('disconnect', (reason) => {
492
- reject(new Error(reason));
493
- });
494
- this.__debug('subscribing', uri);
495
- this.__homeySocket.emit('subscribe', uri, (err) => {
496
- if (err) {
497
- this.__debug('Failed to subscribe', uri, err);
498
- return reject(err);
499
- }
500
-
501
- this.__debug('subscribed', uri);
502
- return resolve();
503
- });
504
- }),
505
- this.constructor.SUBSCRIBE_TIMEOUT,
506
- `Failed to subscribe to ${uri} (Timeout after ${this.constructor.SUBSCRIBE_TIMEOUT}ms).`
507
- );
508
-
509
- // On Connect
510
- const __onEvent = (event, data) => {
511
- onEvent(event, data);
512
- };
513
- this.__homeySocket.on(uri, __onEvent);
514
-
515
- onConnect();
516
-
517
- // On Disconnect
518
- const __onDisconnect = (reason) => {
519
- onDisconnect(reason);
520
- };
521
- this.__socket.on('disconnect', __onDisconnect);
522
-
523
- // On Reconnect
524
- const __onReconnect = () => {
525
- Promise.resolve()
526
- .then(async () => {
527
- await this.connect();
528
- await Util.timeout(
529
- new Promise((resolve, reject) => {
530
- if (this.isConnected() !== true) {
531
- reject(new Error('Not connected after connect. (Reconnect)'));
532
- return;
533
- }
534
-
535
- this.__homeySocket.once('disconnect', (reason) => {
536
- reject(new Error(reason));
537
- });
538
- this.__debug('subscribing', uri);
539
- this.__homeySocket.emit('subscribe', uri, (err) => {
540
- if (err) {
541
- this.__debug('Failed to subscribe', uri, err);
542
- return reject(err);
543
- }
544
-
545
- this.__debug('subscribed', uri);
546
- return resolve();
547
- });
548
- }),
549
- this.constructor.SUBSCRIBE_TIMEOUT,
550
- `Failed to subscribe to ${uri} (Timeout after ${this.constructor.SUBSCRIBE_TIMEOUT}ms).`
551
- );
552
-
553
- this.__homeySocket.on(uri, __onEvent);
554
-
555
- onReconnect();
556
- })
557
- .catch((err) => onReconnectError(err));
558
- };
559
- this.__socket.on('reconnect', __onReconnect);
560
-
561
- return {
562
- unsubscribe: () => {
563
- if (this.__homeySocket) {
564
- this.__homeySocket.emit('unsubscribe', uri);
565
- this.__homeySocket.removeListener(uri, __onEvent);
566
- }
508
+ return this.__subscriptionRegistry.subscribe(uri, handlers);
509
+ }
567
510
 
568
- if (this.__socket) {
569
- this.__socket.removeListener('disconnect', __onDisconnect);
570
- this.__socket.removeListener('reconnect', __onReconnect);
571
- }
572
- },
573
- };
511
+ async __apiRequest({
512
+ uri,
513
+ operation,
514
+ args,
515
+ timeout = this.constructor.DEFAULT_TIMEOUT,
516
+ }) {
517
+ return this.__socketSession.requestApi({
518
+ uri,
519
+ operation,
520
+ args,
521
+ timeout,
522
+ });
574
523
  }
575
524
 
576
525
  async connect() {
577
- if (!this.__connectPromise) {
578
- this.__connectPromise = Promise.resolve().then(async () => {
579
- // Ensure Base URL
580
- const baseUrl = await this.baseUrl;
581
-
582
- // Ensure Token
583
- if (!this.__token) await this.login();
584
-
585
- return new Promise((resolve, reject) => {
586
- this.__debug(`SocketIOClient ${baseUrl}`);
587
-
588
- this.__socket = SocketIOClient(baseUrl, {
589
- autoConnect: false,
590
- transports: ['websocket'],
591
- reconnection: this.__reconnect,
592
- });
593
-
594
- this.__socket.on('disconnect', (reason) => {
595
- this.__debug('SocketIOClient.onDisconnect', reason);
596
- this.emit('disconnect', reason);
597
- });
598
-
599
- this.__socket.on('error', (err) => {
600
- this.__debug('SocketIOClient.onError', err.message);
601
- this.emit('error', err);
602
- });
603
-
604
- this.__socket.on('reconnect', () => {
605
- this.__debug('SocketIOClient.onReconnect');
606
- this.emit('reconnect');
607
- });
608
-
609
- this.__socket.on('reconnect_attempt', () => {
610
- this.__debug(`SocketIOClient.onReconnectAttempt`);
611
- this.emit('reconnect_attempt');
612
- });
613
-
614
- this.__socket.on('reconnecting', (attempt) => {
615
- this.__debug(`SocketIOClient.onReconnecting (Attempt #${attempt})`);
616
- this.emit('reconnecting');
617
- });
618
-
619
- this.__socket.on('reconnect_error', (err) => {
620
- this.__debug('SocketIOClient.onReconnectError', err.message, err);
621
- this.emit('reconnect_error');
622
- });
623
-
624
- this.__socket.on('connect_error', (err) => {
625
- this.__debug('SocketIOClient.onConnectError', err.message);
626
- this.emit('connect_error');
627
- reject(err);
628
- });
629
-
630
- this.__socket.on('connect', () => {
631
- this.__debug('SocketIOClient.onConnect');
632
- this.emit('connect');
633
- this.__handshakeClient()
634
- .then(() => {
635
- this.__debug('SocketIOClient.onConnect.onHandshakeClientSuccess');
636
- resolve();
637
- })
638
- .catch((err) => {
639
- this.__debug('SocketIOClient.onConnect.onHandshakeClientError', err.message);
640
- reject(err);
641
- });
642
- });
643
-
644
- this.__socket.open();
645
- });
646
- });
647
-
648
- this.__connectPromise.catch((err) => {
649
- this.__debug('SocketIOClient Error', err.message);
650
- delete this.__connectPromise;
651
- });
652
- }
653
-
654
- return this.__connectPromise;
526
+ await this.__socketSession.ensureConnected();
655
527
  }
656
528
 
657
529
  isConnecting() {
658
- return this.__connectPromise != null;
530
+ return this.__socketSession.isConnecting();
659
531
  }
660
532
 
661
533
  async disconnect() {
662
- // Should we wait for connect here?
663
-
664
- // Also disconnect __homeySocket?
665
-
666
- if (this.__socket) {
667
- await new Promise((resolve) => {
668
- if (this.__socket.connected) {
669
- this.__socket.once('disconnect', () => resolve());
670
- this.__socket.disconnect();
671
- } else {
672
- resolve();
673
- }
674
- this.__socket.removeAllListeners();
675
- this.__socket = null;
676
- });
677
- }
678
-
679
- // TODO todo what?
534
+ await this.__socketSession.disconnect();
680
535
  }
681
536
 
682
537
  destroy() {
683
538
  this.__destroyed = true;
684
-
685
- if (this.__homeySocket) {
686
- this.__homeySocket.removeAllListeners();
687
- this.__homeySocket.close();
688
- this.__homeySocket = null;
689
- }
690
-
691
- if (this.__socket) {
692
- this.__socket.removeAllListeners();
693
- this.__socket.close();
694
- this.__socket = null;
695
- }
539
+ this.__subscriptionRegistry.destroy();
540
+ this.__socketSession.destroy();
696
541
 
697
542
  this.removeAllListeners();
698
543
  }
@@ -700,128 +545,6 @@ class HomeyAPIV3 extends HomeyAPI {
700
545
  isDestroyed() {
701
546
  return this.__destroyed;
702
547
  }
703
-
704
- async __handshakeClient() {
705
- this.__debug('__handshakeClient');
706
-
707
- const onResult = ({ namespace }) => {
708
- this.__debug('SocketIOClient.onHandshakeClientSuccess', `Namespace: ${namespace}`);
709
-
710
- return new Promise((resolve, reject) => {
711
- this.__homeySocket = this.__socket.io.socket(namespace);
712
-
713
- this.__homeySocket.once('connect', () => {
714
- this.__debug(`SocketIOClient.Namespace[${namespace}].onConnect`);
715
- resolve();
716
- });
717
-
718
- this.__homeySocket.once('connect_error', (err) => {
719
- this.__debug(`SocketIOClient.Namespace[${namespace}].onConnectError`, err.message);
720
- if (err) {
721
- if (err instanceof Error) {
722
- return reject(err);
723
- }
724
-
725
- // .statusCode for homey-core .code for homey-client.
726
- if (typeof err === 'object') {
727
- return reject(
728
- new HomeyAPIError({ error_description: err.message }, err.statusCode || err.code)
729
- );
730
- }
731
-
732
- return reject(new Error(String(err)));
733
- }
734
-
735
- reject(new Error(`Unknown error connecting to namespace ${namespace}.`));
736
- });
737
-
738
- this.__homeySocket.on('disconnect', (reason) => {
739
- this.__debug(`SocketIOClient.Namespace[${namespace}].onDisconnect`, reason);
740
- });
741
-
742
- this.__homeySocket.on('reconnecting', (attempt) => {
743
- this.__debug(
744
- `SocketIOClient.Namespace[${namespace}].onReconnecting (Attempt #${attempt})`
745
- );
746
- });
747
-
748
- this.__homeySocket.on('reconnect', () => {
749
- this.__debug(`SocketIOClient.Namespace[${namespace}].onReconnect`);
750
- });
751
-
752
- this.__homeySocket.on('reconnect_error', (err) => {
753
- this.__debug(`SocketIOClient.Namespace[${namespace}].onReconnectError`, err.message);
754
- });
755
-
756
- this.__homeySocket.open();
757
- });
758
- };
759
-
760
- const handshakeClient = async (token) => {
761
- return new Promise((resolve, reject) => {
762
- this.__socket.emit(
763
- 'handshakeClient',
764
- {
765
- token,
766
- homeyId: this.id,
767
- },
768
- (err, result) => {
769
- if (err != null) {
770
- if (typeof err === 'object') {
771
- err = new HomeyAPIError(
772
- {
773
- stack: err.stack,
774
- error: err.error,
775
- error_description: err.error_description,
776
- },
777
- err.statusCode || err.code || 500
778
- );
779
- } else if (typeof err === 'string') {
780
- err = new HomeyAPIError(
781
- {
782
- error: err,
783
- },
784
- 500
785
- );
786
- }
787
-
788
- return reject(err);
789
- }
790
-
791
- return resolve(result);
792
- }
793
- );
794
- });
795
- };
796
-
797
- const token = this.__token;
798
-
799
- try {
800
- const result = await Util.timeout(
801
- handshakeClient(token),
802
- this.constructor.HANDSHAKE_TIMEOUT,
803
- `Failed to handshake client (Timeout after ${this.constructor.HANDSHAKE_TIMEOUT}ms).`
804
- );
805
-
806
- return onResult(result);
807
- } catch (err) {
808
- // Only try if AthomCloudAPI is available so we never try to refresh on local api's.
809
- if ((err.statusCode === 401 || err.code === 401) && this.__api != null) {
810
- this.__debug('Token expired, refreshing...');
811
- await this.refreshForToken(token, false);
812
- const result = await Util.timeout(
813
- handshakeClient(this.__token),
814
- this.constructor.HANDSHAKE_TIMEOUT,
815
- `Failed to handshake client (Timeout after ${this.constructor.HANDSHAKE_TIMEOUT}ms).`
816
- );
817
-
818
- return onResult(result);
819
- }
820
-
821
- throw err;
822
- }
823
- }
824
-
825
548
  hasScope(scope) {
826
549
  if (this.__session == null) {
827
550
  this.__debug('Tried to call hasScope without a session present.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homey-api",
3
- "version": "3.17.4",
3
+ "version": "3.17.6",
4
4
  "description": "Homey API",
5
5
  "main": "index.js",
6
6
  "license": "SEE LICENSE",