homey-api 3.17.3 → 3.17.5
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/assets/specifications/AthomCloudAPI.json +6 -1
- package/assets/types/homey-api.private.d.ts +4 -4
- 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 +617 -0
- package/lib/HomeyAPI/HomeyAPIV3/SubscriptionRegistry.js +341 -0
- package/lib/HomeyAPI/HomeyAPIV3.js +55 -332
- package/package.json +2 -2
|
@@ -1569,7 +1569,12 @@
|
|
|
1569
1569
|
"path": "/stats/locations",
|
|
1570
1570
|
"method": "get",
|
|
1571
1571
|
"private": true,
|
|
1572
|
-
"parameters": {
|
|
1572
|
+
"parameters": {
|
|
1573
|
+
"since": {
|
|
1574
|
+
"in": "query",
|
|
1575
|
+
"type": "number"
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1573
1578
|
},
|
|
1574
1579
|
"getVersionStatistics": {
|
|
1575
1580
|
"path": "/stats/homey-software-versions",
|
|
@@ -3892,7 +3892,7 @@ export class AthomCloudAPI {
|
|
|
3892
3892
|
|
|
3893
3893
|
getAppDriversStatistics(): Promise<any>;
|
|
3894
3894
|
|
|
3895
|
-
getLocationStatistics(): Promise<any>;
|
|
3895
|
+
getLocationStatistics(opts: { since?: number }): Promise<any>;
|
|
3896
3896
|
|
|
3897
3897
|
getVersionStatistics(): Promise<any>;
|
|
3898
3898
|
|
|
@@ -4594,7 +4594,7 @@ export class AthomCloudAPI {
|
|
|
4594
4594
|
|
|
4595
4595
|
getAppDriversStatistics(): Promise<any>;
|
|
4596
4596
|
|
|
4597
|
-
getLocationStatistics(): Promise<any>;
|
|
4597
|
+
getLocationStatistics(opts: { since?: number }): Promise<any>;
|
|
4598
4598
|
|
|
4599
4599
|
getVersionStatistics(): Promise<any>;
|
|
4600
4600
|
|
|
@@ -7374,7 +7374,7 @@ export class AthomCloudAPI {
|
|
|
7374
7374
|
|
|
7375
7375
|
getAppDriversStatistics(): Promise<any>;
|
|
7376
7376
|
|
|
7377
|
-
getLocationStatistics(): Promise<any>;
|
|
7377
|
+
getLocationStatistics(opts: { since?: number }): Promise<any>;
|
|
7378
7378
|
|
|
7379
7379
|
getVersionStatistics(): Promise<any>;
|
|
7380
7380
|
|
|
@@ -8076,7 +8076,7 @@ export class AthomCloudAPI {
|
|
|
8076
8076
|
|
|
8077
8077
|
getAppDriversStatistics(): Promise<any>;
|
|
8078
8078
|
|
|
8079
|
-
getLocationStatistics(): Promise<any>;
|
|
8079
|
+
getLocationStatistics(opts: { since?: number }): Promise<any>;
|
|
8080
8080
|
|
|
8081
8081
|
getVersionStatistics(): Promise<any>;
|
|
8082
8082
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const EventEmitter = require('../../EventEmitter');
|
|
4
|
+
const RealtimeConsumer = require('./RealtimeConsumer');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* A superclass for all CRUD Items.
|
|
@@ -61,6 +62,37 @@ class Item extends EventEmitter {
|
|
|
61
62
|
writable: true,
|
|
62
63
|
});
|
|
63
64
|
|
|
65
|
+
this.__realtimeConsumer = new RealtimeConsumer({
|
|
66
|
+
subscribe: (uri, handlers) => this.homey.subscribe(uri, handlers),
|
|
67
|
+
getUri: () => this.uri,
|
|
68
|
+
debug: (...props) => this.__debug(...props),
|
|
69
|
+
setConnected: (connected) => {
|
|
70
|
+
this.__connected = connected;
|
|
71
|
+
},
|
|
72
|
+
onConnect: () => {
|
|
73
|
+
this.onConnect();
|
|
74
|
+
},
|
|
75
|
+
onDisconnect: () => {
|
|
76
|
+
this.onDisconnect();
|
|
77
|
+
},
|
|
78
|
+
onReconnect: () => {
|
|
79
|
+
this.onReconnect();
|
|
80
|
+
},
|
|
81
|
+
onEvent: (event, data) => {
|
|
82
|
+
if (event === 'update') {
|
|
83
|
+
this.__update(this.constructor.transformGet({ ...data }));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (event === 'delete') {
|
|
88
|
+
this.__delete();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.emit(event, data);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
64
96
|
// Set Properties
|
|
65
97
|
for (const [key, value] of Object.entries(properties)) {
|
|
66
98
|
if (key === 'id') continue;
|
|
@@ -93,7 +125,7 @@ class Item extends EventEmitter {
|
|
|
93
125
|
this.manager.__debug(`[${this.constructor.name}:${this.id}]`, ...props);
|
|
94
126
|
}
|
|
95
127
|
|
|
96
|
-
__update(properties) {
|
|
128
|
+
__update(properties, { emitEvent = true } = {}) {
|
|
97
129
|
for (const [key, value] of Object.entries(properties)) {
|
|
98
130
|
if (key === 'id') continue;
|
|
99
131
|
this[key] = value;
|
|
@@ -101,7 +133,9 @@ class Item extends EventEmitter {
|
|
|
101
133
|
|
|
102
134
|
this.__lastUpdated = new Date();
|
|
103
135
|
|
|
104
|
-
|
|
136
|
+
if (emitEvent) {
|
|
137
|
+
this.emit('update', properties);
|
|
138
|
+
}
|
|
105
139
|
}
|
|
106
140
|
|
|
107
141
|
__delete() {
|
|
@@ -113,91 +147,14 @@ class Item extends EventEmitter {
|
|
|
113
147
|
* Connect to this item's Socket.io namespace.
|
|
114
148
|
*/
|
|
115
149
|
async connect() {
|
|
116
|
-
this.
|
|
117
|
-
|
|
118
|
-
// If disconnecting, await that first
|
|
119
|
-
try {
|
|
120
|
-
// Ensure all microtasks are done first. E.g. if disconnect is called in the same tick as
|
|
121
|
-
// connect. This way the disconnect is always started first so we can await the disconnect
|
|
122
|
-
// promise before we try to connect again.
|
|
123
|
-
await Promise.resolve();
|
|
124
|
-
await this.__disconnectPromise;
|
|
125
|
-
// eslint-disable-next-line no-empty
|
|
126
|
-
} catch (err) { }
|
|
127
|
-
|
|
128
|
-
this.__connectPromise = Promise.resolve().then(async () => {
|
|
129
|
-
if (!this.io) {
|
|
130
|
-
this.io = this.homey.subscribe(this.uri, {
|
|
131
|
-
onConnect: () => {
|
|
132
|
-
this.__debug('onConnect');
|
|
133
|
-
this.__connected = true;
|
|
134
|
-
|
|
135
|
-
this.onConnect();
|
|
136
|
-
},
|
|
137
|
-
onDisconnect: () => {
|
|
138
|
-
this.__debug('onDisconnect');
|
|
139
|
-
this.__connected = false;
|
|
140
|
-
|
|
141
|
-
this.onDisconnect();
|
|
142
|
-
},
|
|
143
|
-
onReconnect: () => {
|
|
144
|
-
this.__debug('onDisconnect');
|
|
145
|
-
|
|
146
|
-
this.onReconnect();
|
|
147
|
-
},
|
|
148
|
-
onEvent: (event, data) => {
|
|
149
|
-
this.__debug('onEvent', event, data);
|
|
150
|
-
|
|
151
|
-
this.emit(event, data);
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
await this.io;
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// Delete the connecting Promise
|
|
160
|
-
this.__connectPromise
|
|
161
|
-
.catch(() => { })
|
|
162
|
-
.finally(() => {
|
|
163
|
-
delete this.__connectPromise;
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
await this.__connectPromise;
|
|
150
|
+
await this.__realtimeConsumer.connect();
|
|
167
151
|
}
|
|
168
152
|
|
|
169
153
|
/**
|
|
170
154
|
* Discconnect from this item's Socket.io namespace.
|
|
171
155
|
*/
|
|
172
156
|
async disconnect() {
|
|
173
|
-
this.
|
|
174
|
-
|
|
175
|
-
// If connecting, await that first
|
|
176
|
-
try {
|
|
177
|
-
await this.__connectPromise;
|
|
178
|
-
// eslint-disable-next-line no-empty
|
|
179
|
-
} catch (err) { }
|
|
180
|
-
|
|
181
|
-
this.__disconnectPromise = Promise.resolve().then(async () => {
|
|
182
|
-
this.__connected = false;
|
|
183
|
-
|
|
184
|
-
if (this.io) {
|
|
185
|
-
this.io
|
|
186
|
-
.then((io) => io.unsubscribe())
|
|
187
|
-
.catch((err) => this.__debug('Error Disconnecting:', err));
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// Delete the disconnecting Promise
|
|
192
|
-
this.__disconnectPromise
|
|
193
|
-
.catch(() => { })
|
|
194
|
-
.finally(() => {
|
|
195
|
-
delete this.__disconnectPromise;
|
|
196
|
-
// Delete this.io so connect can start new connections.
|
|
197
|
-
delete this.io;
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
await this.__disconnectPromise;
|
|
157
|
+
await this.__realtimeConsumer.disconnect();
|
|
201
158
|
}
|
|
202
159
|
|
|
203
160
|
onConnect() {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const EventEmitter = require('../../EventEmitter');
|
|
4
4
|
const Util = require('../../Util');
|
|
5
|
-
const HomeyAPIError = require('../HomeyAPIError');
|
|
6
5
|
const Item = require('./Item');
|
|
6
|
+
const RealtimeConsumer = require('./RealtimeConsumer');
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @class
|
|
@@ -87,6 +87,71 @@ class Manager extends EventEmitter {
|
|
|
87
87
|
writable: false,
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
this.__realtimeConsumer = new RealtimeConsumer({
|
|
91
|
+
subscribe: (uri, handlers) => this.homey.subscribe(uri, handlers),
|
|
92
|
+
getUri: () => this.uri,
|
|
93
|
+
debug: (...props) => this.__debug(...props),
|
|
94
|
+
setConnected: (connected) => {
|
|
95
|
+
this.__connected = connected;
|
|
96
|
+
},
|
|
97
|
+
onEvent: (event, data) => {
|
|
98
|
+
// Transform & add to cache if this is a CRUD event
|
|
99
|
+
if (event.endsWith('.create') || event.endsWith('.update') || event.endsWith('.delete')) {
|
|
100
|
+
const [itemId, operation] = event.split('.');
|
|
101
|
+
const itemName = this.itemNames[itemId];
|
|
102
|
+
const ItemClass = this.itemClasses[itemName];
|
|
103
|
+
|
|
104
|
+
switch (operation) {
|
|
105
|
+
case 'create': {
|
|
106
|
+
const props = ItemClass.transformGet(data);
|
|
107
|
+
|
|
108
|
+
const item = new ItemClass({
|
|
109
|
+
id: props.id,
|
|
110
|
+
homey: this.homey,
|
|
111
|
+
manager: this,
|
|
112
|
+
properties: props,
|
|
113
|
+
});
|
|
114
|
+
this.__cache[ItemClass.ID][props.id] = item;
|
|
115
|
+
|
|
116
|
+
this.emit(event, item);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
case 'update': {
|
|
120
|
+
const props = ItemClass.transformGet(data);
|
|
121
|
+
|
|
122
|
+
if (this.__cache[ItemClass.ID][props.id]) {
|
|
123
|
+
const item = this.__cache[ItemClass.ID][props.id];
|
|
124
|
+
item.__update(props);
|
|
125
|
+
this.emit(event, item);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case 'delete': {
|
|
132
|
+
const props = ItemClass.transformGet(data);
|
|
133
|
+
|
|
134
|
+
if (this.__cache[ItemClass.ID][props.id]) {
|
|
135
|
+
const item = this.__cache[ItemClass.ID][props.id];
|
|
136
|
+
item.__delete();
|
|
137
|
+
delete this.__cache[ItemClass.ID][item.id];
|
|
138
|
+
this.emit(event, {
|
|
139
|
+
id: item.id,
|
|
140
|
+
});
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
default:
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.emit(event, data);
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
90
155
|
// Create methods
|
|
91
156
|
for (const [operationId, operation] of Object.entries(operations)) {
|
|
92
157
|
Object.defineProperty(
|
|
@@ -244,6 +309,7 @@ class Manager extends EventEmitter {
|
|
|
244
309
|
|
|
245
310
|
async __request({ $cache, $updateCache, $timeout, $socket, operationId, operation, path, body, headers, shouldRetry, ...args }) {
|
|
246
311
|
let result;
|
|
312
|
+
let hasSocketResult = false;
|
|
247
313
|
const benchmark = Util.benchmark();
|
|
248
314
|
|
|
249
315
|
// If connected to Socket.io,
|
|
@@ -273,47 +339,24 @@ class Manager extends EventEmitter {
|
|
|
273
339
|
// If Homey is connected to Socket.io,
|
|
274
340
|
// send the API request to socket.io.
|
|
275
341
|
// This is about ~2x faster than HTTP
|
|
276
|
-
if (this.homey.isConnected()
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
this.
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
stack: err.stack,
|
|
293
|
-
error: err.error,
|
|
294
|
-
error_description: err.error_description,
|
|
295
|
-
},
|
|
296
|
-
err.statusCode || err.code || 500
|
|
297
|
-
);
|
|
298
|
-
} else if (typeof err === 'string') {
|
|
299
|
-
err = new HomeyAPIError(
|
|
300
|
-
{
|
|
301
|
-
error: err,
|
|
302
|
-
},
|
|
303
|
-
500
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return reject(err);
|
|
308
|
-
}
|
|
342
|
+
if ($socket === true && this.homey.isConnected()) {
|
|
343
|
+
try {
|
|
344
|
+
this.__debug(`IO ${operationId}`);
|
|
345
|
+
result = await this.homey.__apiRequest({
|
|
346
|
+
uri: this.uri,
|
|
347
|
+
operation: operationId,
|
|
348
|
+
args,
|
|
349
|
+
timeout: $timeout,
|
|
350
|
+
});
|
|
351
|
+
hasSocketResult = true;
|
|
352
|
+
} catch (err) {
|
|
353
|
+
if (err.code !== 'ERR_SOCKET_SESSION_NOT_READY') {
|
|
354
|
+
throw err;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
309
358
|
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
);
|
|
313
|
-
}),
|
|
314
|
-
$timeout
|
|
315
|
-
);
|
|
316
|
-
} else {
|
|
359
|
+
if (!hasSocketResult) {
|
|
317
360
|
// Get from HTTP
|
|
318
361
|
result = await this.homey.call({
|
|
319
362
|
$timeout,
|
|
@@ -396,7 +439,11 @@ class Manager extends EventEmitter {
|
|
|
396
439
|
|
|
397
440
|
if (this.isConnected() && $updateCache === true && this.__cache[ItemClass.ID][props.id]) {
|
|
398
441
|
item = this.__cache[ItemClass.ID][props.id];
|
|
399
|
-
item.__update(props
|
|
442
|
+
item.__update(props, {
|
|
443
|
+
// Local mutation results update the cached item immediately, but the
|
|
444
|
+
// realtime manager event is responsible for the public update event.
|
|
445
|
+
emitEvent: false,
|
|
446
|
+
});
|
|
400
447
|
} else {
|
|
401
448
|
item = new ItemClass({
|
|
402
449
|
id: props.id,
|
|
@@ -462,119 +509,7 @@ class Manager extends EventEmitter {
|
|
|
462
509
|
* @returns {Promise<void>}
|
|
463
510
|
*/
|
|
464
511
|
async connect() {
|
|
465
|
-
this.
|
|
466
|
-
|
|
467
|
-
// If disconnecting, await that first
|
|
468
|
-
try {
|
|
469
|
-
await this.__disconnectPromise;
|
|
470
|
-
// eslint-disable-next-line no-empty
|
|
471
|
-
} catch (err) { }
|
|
472
|
-
|
|
473
|
-
if (this.__connectPromise) {
|
|
474
|
-
await this.__connectPromise;
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
if (this.io) {
|
|
479
|
-
await this.io;
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
this.__connectPromise = Promise.resolve().then(async () => {
|
|
484
|
-
if (!this.io) {
|
|
485
|
-
this.io = this.homey.subscribe(this.uri, {
|
|
486
|
-
onConnect: () => {
|
|
487
|
-
this.__debug('onConnect');
|
|
488
|
-
this.__connected = true;
|
|
489
|
-
},
|
|
490
|
-
onDisconnect: reason => {
|
|
491
|
-
this.__debug(`onDisconnect Reason:${reason}`);
|
|
492
|
-
this.__connected = false;
|
|
493
|
-
|
|
494
|
-
// Disable for now. We should probably only set the cache to invalid.
|
|
495
|
-
|
|
496
|
-
// Clear CRUD Item cache
|
|
497
|
-
// for (const itemId of Object.keys(this.__cache)) {
|
|
498
|
-
// this.__cache[itemId] = {};
|
|
499
|
-
// this.__cacheAllComplete[itemId] = false;
|
|
500
|
-
// }
|
|
501
|
-
},
|
|
502
|
-
onReconnect: () => {
|
|
503
|
-
this.__debug(`onReconnect`);
|
|
504
|
-
this.__connected = true;
|
|
505
|
-
},
|
|
506
|
-
onEvent: (event, data) => {
|
|
507
|
-
this.__debug('onEvent', event);
|
|
508
|
-
|
|
509
|
-
// Transform & add to cache if this is a CRUD event
|
|
510
|
-
if (event.endsWith('.create') || event.endsWith('.update') || event.endsWith('.delete')) {
|
|
511
|
-
const [itemId, operation] = event.split('.');
|
|
512
|
-
const itemName = this.itemNames[itemId];
|
|
513
|
-
const ItemClass = this.itemClasses[itemName];
|
|
514
|
-
|
|
515
|
-
switch (operation) {
|
|
516
|
-
case 'create': {
|
|
517
|
-
const props = ItemClass.transformGet(data);
|
|
518
|
-
|
|
519
|
-
const item = new ItemClass({
|
|
520
|
-
id: props.id,
|
|
521
|
-
homey: this.homey,
|
|
522
|
-
manager: this,
|
|
523
|
-
properties: props,
|
|
524
|
-
});
|
|
525
|
-
this.__cache[ItemClass.ID][props.id] = item;
|
|
526
|
-
|
|
527
|
-
return this.emit(event, item);
|
|
528
|
-
}
|
|
529
|
-
case 'update': {
|
|
530
|
-
const props = ItemClass.transformGet(data);
|
|
531
|
-
|
|
532
|
-
if (this.__cache[ItemClass.ID][props.id]) {
|
|
533
|
-
const item = this.__cache[ItemClass.ID][props.id];
|
|
534
|
-
item.__update(props);
|
|
535
|
-
return this.emit(event, item);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
break;
|
|
539
|
-
}
|
|
540
|
-
case 'delete': {
|
|
541
|
-
const props = ItemClass.transformGet(data);
|
|
542
|
-
|
|
543
|
-
if (this.__cache[ItemClass.ID][props.id]) {
|
|
544
|
-
const item = this.__cache[ItemClass.ID][props.id];
|
|
545
|
-
item.__delete();
|
|
546
|
-
delete this.__cache[ItemClass.ID][item.id];
|
|
547
|
-
return this.emit(event, {
|
|
548
|
-
id: item.id,
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
break;
|
|
553
|
-
}
|
|
554
|
-
default:
|
|
555
|
-
break;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Fire event listeners
|
|
560
|
-
this.emit(event, data);
|
|
561
|
-
},
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
await this.io;
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
// Delete the connecting Promise
|
|
569
|
-
this.__connectPromise
|
|
570
|
-
.catch(() => {
|
|
571
|
-
delete this.io;
|
|
572
|
-
})
|
|
573
|
-
.finally(() => {
|
|
574
|
-
delete this.__connectPromise;
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
await this.__connectPromise;
|
|
512
|
+
await this.__realtimeConsumer.connect();
|
|
578
513
|
}
|
|
579
514
|
|
|
580
515
|
/**
|
|
@@ -582,32 +517,7 @@ class Manager extends EventEmitter {
|
|
|
582
517
|
* @returns {Promise<void>}
|
|
583
518
|
*/
|
|
584
519
|
async disconnect() {
|
|
585
|
-
this.
|
|
586
|
-
|
|
587
|
-
// If connecting, await that first
|
|
588
|
-
try {
|
|
589
|
-
await this.__connectPromise;
|
|
590
|
-
// eslint-disable-next-line no-empty
|
|
591
|
-
} catch (err) { }
|
|
592
|
-
|
|
593
|
-
this.__disconnectPromise = Promise.resolve().then(async () => {
|
|
594
|
-
this.__connected = false;
|
|
595
|
-
|
|
596
|
-
if (this.io) {
|
|
597
|
-
await this.io.then(io => io.unsubscribe()).catch(err => this.__debug('Error Disconnecting:', err));
|
|
598
|
-
|
|
599
|
-
delete this.io;
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
// Delete the disconnecting Promise
|
|
604
|
-
this.__disconnectPromise
|
|
605
|
-
.catch(() => { })
|
|
606
|
-
.finally(() => {
|
|
607
|
-
delete this.__disconnectPromise;
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
await this.__disconnectPromise;
|
|
520
|
+
await this.__realtimeConsumer.disconnect();
|
|
611
521
|
}
|
|
612
522
|
|
|
613
523
|
/**
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class RealtimeConsumer {
|
|
4
|
+
constructor({
|
|
5
|
+
subscribe,
|
|
6
|
+
getUri,
|
|
7
|
+
debug,
|
|
8
|
+
onConnect = () => {},
|
|
9
|
+
onDisconnect = () => {},
|
|
10
|
+
onReconnect = () => {},
|
|
11
|
+
onReconnectError = () => {},
|
|
12
|
+
onEvent = () => {},
|
|
13
|
+
setConnected = () => {},
|
|
14
|
+
}) {
|
|
15
|
+
this.__subscribe = subscribe;
|
|
16
|
+
this.__getUri = getUri;
|
|
17
|
+
this.__debug = debug;
|
|
18
|
+
this.__onConnect = onConnect;
|
|
19
|
+
this.__onDisconnect = onDisconnect;
|
|
20
|
+
this.__onReconnect = onReconnect;
|
|
21
|
+
this.__onReconnectError = onReconnectError;
|
|
22
|
+
this.__onEvent = onEvent;
|
|
23
|
+
this.__setConnected = setConnected;
|
|
24
|
+
|
|
25
|
+
this.__subscriptionPromise = null;
|
|
26
|
+
this.__connectPromise = null;
|
|
27
|
+
this.__disconnectPromise = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async connect() {
|
|
31
|
+
this.__debug('connect');
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await Promise.resolve();
|
|
35
|
+
await this.__disconnectPromise;
|
|
36
|
+
// eslint-disable-next-line no-empty
|
|
37
|
+
} catch (err) {}
|
|
38
|
+
|
|
39
|
+
if (this.__connectPromise) {
|
|
40
|
+
await this.__connectPromise;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (this.__subscriptionPromise) {
|
|
45
|
+
await this.__subscriptionPromise;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const connectPromise = Promise.resolve().then(async () => {
|
|
50
|
+
if (!this.__subscriptionPromise) {
|
|
51
|
+
this.__subscriptionPromise = Promise.resolve(
|
|
52
|
+
this.__subscribe(this.__getUri(), {
|
|
53
|
+
onConnect: () => {
|
|
54
|
+
this.__debug('onConnect');
|
|
55
|
+
this.__setConnected(true);
|
|
56
|
+
this.__onConnect();
|
|
57
|
+
},
|
|
58
|
+
onDisconnect: (reason) => {
|
|
59
|
+
this.__debug(`onDisconnect Reason:${reason}`);
|
|
60
|
+
this.__setConnected(false);
|
|
61
|
+
this.__onDisconnect(reason);
|
|
62
|
+
},
|
|
63
|
+
onReconnect: () => {
|
|
64
|
+
this.__debug('onReconnect');
|
|
65
|
+
this.__setConnected(true);
|
|
66
|
+
this.__onReconnect();
|
|
67
|
+
},
|
|
68
|
+
onReconnectError: (err) => {
|
|
69
|
+
this.__debug('onReconnectError', err.message);
|
|
70
|
+
this.__onReconnectError(err);
|
|
71
|
+
},
|
|
72
|
+
onEvent: (event, data) => {
|
|
73
|
+
this.__debug('onEvent', event, data);
|
|
74
|
+
this.__onEvent(event, data);
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await this.__subscriptionPromise;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
this.__connectPromise = connectPromise;
|
|
84
|
+
|
|
85
|
+
connectPromise
|
|
86
|
+
.catch(() => {
|
|
87
|
+
this.__subscriptionPromise = null;
|
|
88
|
+
})
|
|
89
|
+
.finally(() => {
|
|
90
|
+
if (this.__connectPromise === connectPromise) {
|
|
91
|
+
this.__connectPromise = null;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await connectPromise;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async disconnect() {
|
|
99
|
+
this.__debug('disconnect');
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await this.__connectPromise;
|
|
103
|
+
// eslint-disable-next-line no-empty
|
|
104
|
+
} catch (err) {}
|
|
105
|
+
|
|
106
|
+
if (this.__disconnectPromise) {
|
|
107
|
+
await this.__disconnectPromise;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const disconnectPromise = Promise.resolve().then(async () => {
|
|
112
|
+
this.__setConnected(false);
|
|
113
|
+
|
|
114
|
+
// A failed connect clears __subscriptionPromise in connect(). In that case
|
|
115
|
+
// disconnect() becomes a safe no-op because there is no subscription handle
|
|
116
|
+
// left to unsubscribe.
|
|
117
|
+
if (this.__subscriptionPromise) {
|
|
118
|
+
await this.__subscriptionPromise
|
|
119
|
+
.then((subscription) => subscription.unsubscribe())
|
|
120
|
+
.catch((err) => this.__debug('Error Disconnecting:', err));
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
this.__disconnectPromise = disconnectPromise;
|
|
125
|
+
|
|
126
|
+
disconnectPromise
|
|
127
|
+
.catch(() => {})
|
|
128
|
+
.finally(() => {
|
|
129
|
+
if (this.__disconnectPromise === disconnectPromise) {
|
|
130
|
+
this.__disconnectPromise = null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.__subscriptionPromise = null;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await disconnectPromise;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = RealtimeConsumer;
|