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.
- package/lib/HomeyAPI/HomeyAPIV3/Item.js +38 -81
- package/lib/HomeyAPI/HomeyAPIV3/Manager.js +91 -181
- package/lib/HomeyAPI/HomeyAPIV3/RealtimeConsumer.js +140 -0
- package/lib/HomeyAPI/HomeyAPIV3/SocketSession.js +711 -0
- package/lib/HomeyAPI/HomeyAPIV3/SubscriptionRegistry.js +345 -0
- package/lib/HomeyAPI/HomeyAPIV3.js +55 -332
- package/package.json +1 -1
|
@@ -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
|
|
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.
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
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.
|
|
530
|
+
return this.__socketSession.isConnecting();
|
|
659
531
|
}
|
|
660
532
|
|
|
661
533
|
async disconnect() {
|
|
662
|
-
|
|
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
|
-
|
|
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.');
|