homebridge-eosstb 2.2.15 → 2.3.0
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/CHANGELOG.md +17 -0
- package/README.md +7 -9
- package/config.schema.json +27 -10
- package/index.js +455 -99
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,23 @@ Please restart Homebridge after every plugin update.
|
|
|
9
9
|
* Add ability to log and read current program name
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
## 2.3.0-beta.8 (2024-01-25)
|
|
13
|
+
* Added auto endpoint detection for all services, this fixes connection issues in many countries
|
|
14
|
+
* Added ability to set authentication method. You must select the method in the plugin config. If none set, logon method falls back to using country code
|
|
15
|
+
* Added Disable Session Watchdog to config.schema to make it easier to debug by turning off the session watchdog
|
|
16
|
+
* Fixed issue connecting to mqtt broker (issue started ca. 23 Jan 2024) by adding extra subprotocol headers
|
|
17
|
+
* Fixed bug in getMostWatchedChannels where the endpoint was incorrect
|
|
18
|
+
* Updated Readme plugin status for various countries
|
|
19
|
+
* Updated iOS version references in Readme
|
|
20
|
+
* Bumped dependency "axios": "^1.6.6"
|
|
21
|
+
* Bumped dependency "mqtt": "^5.3.5"
|
|
22
|
+
* IN WORK: Reworking GB authentication methods. NOT YET WORKING, PLEASE BE PATIENT
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## 2.2.16 (2024-01-16)
|
|
26
|
+
* Removed AZ, CZ, DE, HU, RO from config.json and Readme. These countries no longer offer UPC TV.
|
|
27
|
+
|
|
28
|
+
|
|
12
29
|
## 2.2.15 (2024-01-14)
|
|
13
30
|
* Fixed issue with MQTT connection failure in CH due to change of MQTT endpoint
|
|
14
31
|
* Bumped dependency "axios-cookiejar-support": "^5.0.0"
|
package/README.md
CHANGED
|
@@ -47,11 +47,9 @@ As [UPC](https://en.wikipedia.org/wiki/UPC_Broadband) (the operator of the Horiz
|
|
|
47
47
|
| GB | [Virgin Media](https://www.virginmedia.com/) | [Virgin TV Go](https://virgintvgo.virginmedia.com/en.html) | [Virgin TV 360](https://www.virginmedia.com/shop/tv/virgin-tv-360) and [Virgin TV 360 Mini](https://www.virginmedia.com/shop/tv/multiroom) | Fully Working |
|
|
48
48
|
| IE | [Virgin Media](https://www.virginmedia.ie/) | [Virgin TV Anywhere](https://www.virginmediatv.ie/en.html) | [360 Box](https://www.virginmedia.ie/virgintv360support/) | Fully Working |
|
|
49
49
|
| NL | [Ziggo](https://www.ziggo.nl/) | [Ziggo GO](https://www.ziggogo.tv/nl.html) | [Mediabox Next](https://www.ziggo.nl/televisie/mediaboxen/mediabox-next#ziggo-tv) | Fully Working |
|
|
50
|
+
| PL | [UPC PL](https://www.upc.pl/) | [UPC TV GO](https://www.upctv.pl/pl/home) | UPC TV Box | Fully Working |
|
|
50
51
|
| ------- | ----------- | ------- | -------- | ------------- |
|
|
51
|
-
|
|
|
52
|
-
| DE | [Vodafone DE](https://zuhauseplus.vodafone.de/digital-fernsehen/) | [Horizon Go](https://www.horizon.tv/de_de.html) | [GigaTV Cable Box](https://zuhauseplus.vodafone.de/digital-fernsehen/tv-endgeraete/) | _Testers Wanted_ |
|
|
53
|
-
| PL | [UPC PL](https://www.upc.pl/) | [UPC TV GO](https://www.upctv.pl/pl/home) | Horizon decoder | _Testers Wanted_ |
|
|
54
|
-
| SK | [UPC Broadband Slovakia](https://www.upc.sk/) | [Horizon Go](https://www.horizon.tv/sk_sk.html) | Horizon TV | _Testers Wanted_ |
|
|
52
|
+
| SK | [UPC Broadband Slovakia](https://www.upc.sk/) | [UPC TV](https://www.upctv.sk/sk/home) | UPC TV Box | _Testers Wanted_ |
|
|
55
53
|
|
|
56
54
|
|
|
57
55
|
If you subscribe to a TV service from one of these countries, you are lucky, this plugin will work for you.
|
|
@@ -72,7 +70,7 @@ In January 2023, an ARRIS VIP5002W appeared, which identifies itself as an APLST
|
|
|
72
70
|
This plugin is not provided by Magenta or Telenet or Sunrise or Virgin Media or Ziggo any other affiliate of [UPC](https://en.wikipedia.org/wiki/UPC_Broadband). It is neither endorsed nor supported nor developed by [UPC](https://en.wikipedia.org/wiki/UPC_Broadband) or any affiliates. [UPC](https://en.wikipedia.org/wiki/UPC_Broadband) can change their systems at any time and that might break this plugin. But I hope not.
|
|
73
71
|
|
|
74
72
|
## Requirements
|
|
75
|
-
* An Apple iPhone or iPad with iOS/iPadOS 14.0 (or later). Developed on iOS 14.1...17.
|
|
73
|
+
* An Apple iPhone or iPad with iOS/iPadOS 14.0 (or later). Developed on iOS 14.1...17.3, earlier versions not tested.
|
|
76
74
|
* [Homebridge](https://homebridge.io/) v1.1.116 (or later). Developed on Homebridge 1.1.116....1.7.0, earlier versions not tested.
|
|
77
75
|
* A TV subscription from one of the supported countries and TV providers.
|
|
78
76
|
* An online account for viewing TV in the web app (often part of your TV package), see the table above.
|
|
@@ -115,7 +113,7 @@ This plugin is not provided by Magenta or Telenet or Sunrise or Virgin Media or
|
|
|
115
113
|
|
|
116
114
|
* **Fully Configurable**: A large amount of configuration items exist to allow you to configure your plugin the way you want.
|
|
117
115
|
|
|
118
|
-
* **Future Feature Support**: The plugin also supports current and target media state as well as closed captions, even though the Home app accessory cannot currently display or control this data in the home app (as at iOS 17.
|
|
116
|
+
* **Future Feature Support**: The plugin also supports current and target media state as well as closed captions, even though the Home app accessory cannot currently display or control this data in the home app (as at iOS 17.3). Hopefully, Apple will add support for these features in the future. You can however use this data in Home Automations or the Shortcuts app.
|
|
119
117
|
|
|
120
118
|
|
|
121
119
|
|
|
@@ -173,7 +171,7 @@ The following keys are supported by in the **Apple TV Remote** in the Control Ce
|
|
|
173
171
|
| Volume Up | volUpCommand | - |
|
|
174
172
|
| Volume Down | volDownCommand | 3 clicks = mute |
|
|
175
173
|
|
|
176
|
-
NOTE: The Mute and Power buttons appear in the Remote Control as of iOS 17.
|
|
174
|
+
NOTE: The Mute and Power buttons appear in the Remote Control as of iOS 17.3, however they are disabled. Currently, I do not know how to enable these buttons. If you have any information about these buttons, please get in touch with me.
|
|
177
175
|
|
|
178
176
|
The table shows the default key mappings. You can map any Apple TV Remote button to any set-top box remote control button, see the Wiki for all of the known [KeyEvents](https://github.com/jsiegenthaler/homebridge-eosstb/wiki/KeyEvents).
|
|
179
177
|
|
|
@@ -208,13 +206,13 @@ Services used in this set-top box accessory are:
|
|
|
208
206
|
4. Input service. The input (TV channels) utilises one service per input. The maximum possible channels (inputs) are thus 100 - 3 = 97. I have limited the inputs to maximum 95, but you can override this in the config (helpful to reduce log entries when debugging). The inputs are hard limited to 95 inputs.
|
|
209
207
|
|
|
210
208
|
### Media State (Play/Pause) Limitations
|
|
211
|
-
The eosstb plugin can detect the target and current media state and shows STOP, PLAY, PAUSE or LOADING (loading is displayed only for current media state when fast-forwarding or rewinding) in the Homebridge logs. Unfortunately, the Apple Home app cannot do anything with the media state (as at iOS 17.
|
|
209
|
+
The eosstb plugin can detect the target and current media state and shows STOP, PLAY, PAUSE or LOADING (loading is displayed only for current media state when fast-forwarding or rewinding) in the Homebridge logs. Unfortunately, the Apple Home app cannot do anything with the media state (as at iOS 17.3) apart from allow you to read it in Shortcuts or Automations. Hopefully this will improve in the future.
|
|
212
210
|
|
|
213
211
|
### Recording State Limitations
|
|
214
212
|
The eosstb plugin can detect the current recording state of the set-top box, both for local HDD-based recording (for boxes that have a HDD fitted) and for network recording. The plugin shows IDLE, ONGOING_NDVR or ONGOING_LOCALDVR in the Homebridge logs. DVR means digital video recorder; N for network and LOCAL for local HDD based recording. The Apple Home app cannot natively do anything with the recording state but the eosstb plugin uses it to set the inUse charateristic if the set-top box is turned on or is recording to the local HDD. This is useful in Shortcuts or Automations.
|
|
215
213
|
|
|
216
214
|
### Closed Captions Limitations
|
|
217
|
-
The eosstb plugin can detect the closed captions state (**Subtitle options** in the set-top box menu) and shows ENABLED or DISABLED in the Homebridge logs. Unfortunately, the Apple Home app cannot do anything with the closed captions state (as at iOS 17.
|
|
215
|
+
The eosstb plugin can detect the closed captions state (**Subtitle options** in the set-top box menu) and shows ENABLED or DISABLED in the Homebridge logs. Unfortunately, the Apple Home app cannot do anything with the closed captions state (as at iOS 17.3) apart from allow you to read it in Shortcuts or Automations. Hopefully this will improve in the future.
|
|
218
216
|
|
|
219
217
|
## Configuration
|
|
220
218
|
Add a new platform to the platforms section of your homebridge `config.json`.
|
package/config.schema.json
CHANGED
|
@@ -23,19 +23,14 @@
|
|
|
23
23
|
"default": "ch",
|
|
24
24
|
"required": true,
|
|
25
25
|
"oneOf": [
|
|
26
|
-
{ "title": "AT: Magenta TV", "enum": ["at"] },
|
|
27
26
|
{ "title": "BE-FR: Telenet TV", "enum": ["be-fr"] },
|
|
28
27
|
{ "title": "BE-NL: Telenet TV", "enum": ["be-nl"] },
|
|
29
28
|
{ "title": "CH: Sunrise TV", "enum": ["ch"] },
|
|
30
|
-
{ "title": "CZ", "enum": ["cz"] },
|
|
31
|
-
{ "title": "DE", "enum": ["de"] },
|
|
32
29
|
{ "title": "GB: Virgin Media TV 360", "enum": ["gb"] },
|
|
33
|
-
{ "title": "HU", "enum": ["hu"] },
|
|
34
30
|
{ "title": "IE. Virgin Media TV 360", "enum": ["ie"] },
|
|
35
31
|
{ "title": "NL: Ziggo TV", "enum": ["nl"] },
|
|
36
|
-
{ "title": "PL", "enum": ["pl"] },
|
|
37
|
-
{ "title": "SK", "enum": ["sk"] }
|
|
38
|
-
{ "title": "RO", "enum": ["ro"] }
|
|
32
|
+
{ "title": "PL: UPC TV GO", "enum": ["pl"] },
|
|
33
|
+
{ "title": "SK: UPC TV", "enum": ["sk"] }
|
|
39
34
|
]
|
|
40
35
|
},
|
|
41
36
|
"username": {
|
|
@@ -52,7 +47,27 @@
|
|
|
52
47
|
"placeholder": "yourTvProviderPassword",
|
|
53
48
|
"required": true
|
|
54
49
|
},
|
|
55
|
-
|
|
50
|
+
"authmethod": {
|
|
51
|
+
"title": "Authentication Method",
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "The authentication method. Select the option for your country. If it doesn't work, try another method.",
|
|
54
|
+
"default": "A",
|
|
55
|
+
"required": true,
|
|
56
|
+
"oneOf": [
|
|
57
|
+
{ "title": "Method A: CH, NL, IE", "enum": ["A"] },
|
|
58
|
+
{ "title": "Method B: BE", "enum": ["B"] },
|
|
59
|
+
{ "title": "Method C: GB", "enum": ["C"] },
|
|
60
|
+
{ "title": "Method D: OAuth 2.0 PKCE EXPERIMENTAL", "enum": ["D"] }
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
"watchdogDisabled": {
|
|
65
|
+
"title": "Disable Session Watchdog",
|
|
66
|
+
"type": "boolean",
|
|
67
|
+
"description": "Disables the session watchdog to assist with debugging. Default: false",
|
|
68
|
+
"default": false
|
|
69
|
+
},
|
|
70
|
+
|
|
56
71
|
"doublePressTime": {
|
|
57
72
|
"title": "Double-Press Time",
|
|
58
73
|
"type": "integer",
|
|
@@ -846,8 +861,10 @@
|
|
|
846
861
|
"type": "flex",
|
|
847
862
|
"flex-flow": "column",
|
|
848
863
|
"items": [
|
|
849
|
-
"country"
|
|
850
|
-
|
|
864
|
+
"country",
|
|
865
|
+
"authmethod",
|
|
866
|
+
"watchdogDisabled"
|
|
867
|
+
]
|
|
851
868
|
},
|
|
852
869
|
{
|
|
853
870
|
"type": "flex",
|
package/index.js
CHANGED
|
@@ -18,6 +18,11 @@ const semver = require('semver') // https://github.com/npm/node-semver
|
|
|
18
18
|
|
|
19
19
|
const mqtt = require('mqtt'); // https://github.com/mqttjs
|
|
20
20
|
const qs = require('qs'); // https://github.com/ljharb/qs
|
|
21
|
+
const WebSocket = require('ws'); // https://github.com/websockets/ws for the mqtt websocket
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
// needed for sso logon with pkce OAuth 2.0
|
|
25
|
+
const {randomBytes, createHash} = require("node:crypto");
|
|
21
26
|
|
|
22
27
|
|
|
23
28
|
// axios-cookiejar-support v2.0.2 syntax
|
|
@@ -51,59 +56,34 @@ axiosCookieJarSupport(axiosWS);
|
|
|
51
56
|
// without any trailing /
|
|
52
57
|
// refer https://github.com/Sholofly/lghorizon-python/blob/features/telenet/lghorizon/const.py
|
|
53
58
|
const countryBaseUrlArray = {
|
|
54
|
-
|
|
55
|
-
'be-fr': 'https://prod.spark.telenet.tv',
|
|
56
|
-
'be-nl': 'https://prod.spark.telenet.tv',
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
'
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
'
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
//'gb': 'https://web-api-prod-obo.horizon.tv/oesp/v4/GB/eng/web',
|
|
69
|
-
//'nl': 'https://web-api-prod-obo.horizon.tv/oesp/v4/NL/nld/web', // old
|
|
70
|
-
//'pl': 'https://web-api-pepper.horizon.tv/oesp/v4/PL/pol/web', // v2, v3 and v4 work
|
|
71
|
-
//'ie': 'https://prod.oesp.virginmediatv.ie/oesp/v4/IE/eng/web/', // old
|
|
72
|
-
//'sk': 'https://web-api-pepper.horizon.tv/oesp/v4/SK/slk/web', // v2, v3 and v4 work
|
|
59
|
+
//https://spark-prod-be.gnp.cloud.telenet.tv/be/en/config-service/conf/web/backoffice.json
|
|
60
|
+
//'be-fr': 'https://prod.spark.telenet.tv',
|
|
61
|
+
//'be-nl': 'https://prod.spark.telenet.tv',
|
|
62
|
+
'be': 'https://spark-prod-be.gnp.cloud.telenet.tv', // verified 14.01.2024
|
|
63
|
+
// https://spark-prod-ch.gnp.cloud.sunrisetv.ch/ch/en/config-service/conf/web/backoffice.json
|
|
64
|
+
//'ch': 'https://prod.spark.sunrisetv.ch',
|
|
65
|
+
'ch': 'https://spark-prod-ch.gnp.cloud.sunrisetv.ch', // verified 14.01.2024
|
|
66
|
+
'gb': 'https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com', // verified 14.01.2024
|
|
67
|
+
'ie': 'https://spark-prod-ie.gnp.cloud.virginmediatv.ie', // verified 14.01.2024
|
|
68
|
+
'nl': 'https://prod.spark.ziggogo.tv', // verified 14.01.2024
|
|
69
|
+
//'pl': 'https://prod.spark.upctv.pl',
|
|
70
|
+
'pl': 'https://spark-prod-pl.gnp.cloud.upctv.pl', // verified 14.01.2024
|
|
71
|
+
//'sk': 'https://prod.spark.upctv.sk',
|
|
72
|
+
'sk': 'https://spark-prod-sk.gnp.cloud.upctv.sk', // verified 14.01.2024
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
// mqtt endpoints varies by country, unchanged after backend change on 13.10.2022
|
|
76
|
+
/*
|
|
76
77
|
const mqttUrlArray = {
|
|
77
|
-
'at': 'wss://obomsg.prod.at.horizon.tv/mqtt',
|
|
78
78
|
'be-fr': 'wss://obomsg.prod.be.horizon.tv/mqtt',
|
|
79
79
|
'be-nl': 'wss://obomsg.prod.be.horizon.tv/mqtt',
|
|
80
|
-
//'ch': 'wss://obomsg.prod.ch.horizon.tv/mqtt',
|
|
81
80
|
'ch': 'wss://messagebroker-prod-ch.gnp.cloud.dmdsdp.com/mqtt', // from 11.02.2024
|
|
82
|
-
'de': 'wss://obomsg.prod.de.horizon.tv/mqtt',
|
|
83
81
|
'gb': 'wss://obomsg.prod.gb.horizon.tv/mqtt',
|
|
84
82
|
'ie': 'wss://obomsg.prod.ie.horizon.tv/mqtt',
|
|
85
83
|
'nl': 'wss://obomsg.prod.nl.horizon.tv/mqtt',
|
|
86
84
|
'pl': 'wss://obomsg.prod.pl.horizon.tv/mqtt',
|
|
87
85
|
'sk': 'wss://obomsg.prod.sk.horizon.tv/mqtt'
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// profile url endpoints varies by country
|
|
91
|
-
// https://prod.spark.sunrisetv.ch/deu/web/personalization-service/v1/customer/{household_id}/devices
|
|
92
|
-
// without terminating /
|
|
93
|
-
// no longer needed in v2
|
|
94
|
-
/*
|
|
95
|
-
const personalizationServiceUrlArray = {
|
|
96
|
-
'at': 'https://prod.spark.magentatv.at/deu/web/personalization-service/v1/customer/{householdId}',
|
|
97
|
-
'be-fr': 'https://prod.spark.telenettv.be/fr/web/personalization-service/v1/customer/{householdId}',
|
|
98
|
-
'be-nl': 'https://prod.spark.telenettv.be/nld/web/personalization-service/v1/customer/{householdId}',
|
|
99
|
-
'ch': 'https://prod.spark.sunrisetv.ch/eng/web/personalization-service/v1/customer/{householdId}',
|
|
100
|
-
'de': '',
|
|
101
|
-
'gb': 'https://prod.spark.virginmedia.com/eng/web/personalization-service/v1/customer/{householdId}',
|
|
102
|
-
'ie': 'https://prod.spark.virginmediatv.ie/eng/web/personalization-service/v1/customer/{householdId}',
|
|
103
|
-
'nl': 'https://prod.spark.ziggogo.tv/nld/web/personalization-service/v1/customer/{householdId}',
|
|
104
|
-
'pl': 'https://prod.spark.unknown.pl/pol/web/personalization-service/v1/customer/{householdId}'
|
|
105
|
-
};
|
|
106
|
-
*/
|
|
86
|
+
};*/
|
|
107
87
|
|
|
108
88
|
|
|
109
89
|
// openid logon url used in Telenet.be Belgium for be-nl and be-fr sessions
|
|
@@ -275,6 +255,17 @@ async function waitprom(ms) {
|
|
|
275
255
|
}
|
|
276
256
|
|
|
277
257
|
|
|
258
|
+
// generate PKCE code verifier pair for OAuth 2.0
|
|
259
|
+
function generatePKCEPair() {
|
|
260
|
+
const NUM_OF_BYTES = 22; // Total of 44 characters (1 Byte = 2 char) (standard states that: 43 chars <= verifier <= 128 chars)
|
|
261
|
+
const HASH_ALG = "sha256";
|
|
262
|
+
const code_verifier = randomBytes(NUM_OF_BYTES).toString('hex');
|
|
263
|
+
const code_verifier_hash = createHash(HASH_ALG).update(code_verifier).digest('base64');
|
|
264
|
+
const code_challenge = code_verifier_hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); // Clean base64 to make it URL safe
|
|
265
|
+
return {verifier: code_verifier, code_challenge}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
278
269
|
|
|
279
270
|
// ++++++++++++++++++++++++++++++++++++++++++++
|
|
280
271
|
// config end
|
|
@@ -346,7 +337,11 @@ class stbPlatform {
|
|
|
346
337
|
setTimeout(this.sessionWatchdog.bind(this),500); // wait 500ms then call this.sessionWatchdog
|
|
347
338
|
|
|
348
339
|
// the session watchdog creates a session when none exists, and recreates one if the session ever fails due to internet failure or anything else
|
|
349
|
-
|
|
340
|
+
if ((this.config.watchdogDisabled || false) == true) {
|
|
341
|
+
this.log.warn('WARNING: Session watchdog disabled')
|
|
342
|
+
} else {
|
|
343
|
+
this.checkSessionInterval = setInterval(this.sessionWatchdog.bind(this),SESSION_WATCHDOG_INTERVAL_MS);
|
|
344
|
+
}
|
|
350
345
|
|
|
351
346
|
// check for a channel list update every MASTER_CHANNEL_LIST_REFRESH_CHECK_INTERVAL_S seconds
|
|
352
347
|
this.checkChannelListInterval = setInterval(() => {
|
|
@@ -386,7 +381,7 @@ class stbPlatform {
|
|
|
386
381
|
debug('stbPlatform:apievent :: shutdown')
|
|
387
382
|
if (this.config.debugLevel > 2) { this.log.warn('API event: shutdown'); }
|
|
388
383
|
isShuttingDown = true;
|
|
389
|
-
this.
|
|
384
|
+
this.endMqttSession()
|
|
390
385
|
.then(() => {
|
|
391
386
|
this.log('Goodbye');
|
|
392
387
|
}
|
|
@@ -533,72 +528,80 @@ class stbPlatform {
|
|
|
533
528
|
if (this.config.debugLevel > 2) { this.log.warn('%s: Attempting to create session', watchdogInstance); }
|
|
534
529
|
|
|
535
530
|
// asnyc startup sequence with chain of promises
|
|
536
|
-
this.log.debug('%s: ++++ step 1: calling
|
|
537
|
-
errorTitle = 'Failed to
|
|
538
|
-
debug(debugPrefix + 'calling
|
|
539
|
-
await this.
|
|
531
|
+
this.log.debug('%s: ++++ step 1: calling config service', watchdogInstance)
|
|
532
|
+
errorTitle = 'Failed to get config';
|
|
533
|
+
debug(debugPrefix + 'calling getConfig')
|
|
534
|
+
await this.getConfig(this.config.country.toLowerCase()) // returns config, stores config in this.config
|
|
535
|
+
.then((session) => {
|
|
536
|
+
this.log.debug('%s: ++++++ step 2: config was retrieved', watchdogInstance)
|
|
537
|
+
this.log.debug('%s: ++++++ step 2: calling createSession with country code %s ', watchdogInstance, this.config.country.toLowerCase())
|
|
538
|
+
this.log('Creating session...');
|
|
539
|
+
errorTitle = 'Failed to create session';
|
|
540
|
+
debug(debugPrefix + 'calling createSession')
|
|
541
|
+
return this.createSession(this.config.country.toLowerCase()) // returns householdId, stores session in this.session
|
|
542
|
+
})
|
|
540
543
|
.then((sessionHouseholdId) => {
|
|
541
|
-
this.log.debug('%s: ++++++ step
|
|
542
|
-
this.log.debug('%s: ++++++ step
|
|
544
|
+
this.log.debug('%s: ++++++ step 3: session was created, connected to sessionHouseholdId %s', watchdogInstance, sessionHouseholdId)
|
|
545
|
+
this.log.debug('%s: ++++++ step 3: calling getPersonalizationData with sessionHouseholdId %s ', watchdogInstance, sessionHouseholdId)
|
|
543
546
|
this.log('Discovering platform...');
|
|
544
547
|
errorTitle = 'Failed to discover platform';
|
|
545
548
|
debug(debugPrefix + 'calling getPersonalizationData')
|
|
546
549
|
return this.getPersonalizationData(this.session.householdId) // returns customer object, with devices and profiles, stores object in this.customer
|
|
547
550
|
})
|
|
548
551
|
.then((objCustomer) => {
|
|
549
|
-
this.log.debug('%s: ++++++ step
|
|
550
|
-
this.log.debug('%s: ++++++ step
|
|
552
|
+
this.log.debug('%s: ++++++ step 4: personalization data was retrieved, customerId %s customerStatus %s', watchdogInstance, objCustomer.customerId, objCustomer.customerStatus)
|
|
553
|
+
this.log.debug('%s: ++++++ step 4: calling getEntitlements with customerId %s ', watchdogInstance, objCustomer.customerId)
|
|
551
554
|
debug(debugPrefix + 'calling getEntitlements')
|
|
552
555
|
return this.getEntitlements(this.customer.customerId) // returns customer object
|
|
553
556
|
})
|
|
554
557
|
.then((objEntitlements) => {
|
|
555
|
-
this.log.debug('%s: ++++++ step
|
|
556
|
-
this.log.debug('%s: ++++++ step
|
|
558
|
+
this.log.debug('%s: ++++++ step 5: entitlements data was retrieved, objEntitlements.token %s', watchdogInstance, objEntitlements.token)
|
|
559
|
+
this.log.debug('%s: ++++++ step 5: calling refreshMasterChannelList', watchdogInstance)
|
|
557
560
|
debug(debugPrefix + 'calling refreshMasterChannelList')
|
|
558
561
|
return this.refreshMasterChannelList() // returns entitlements object
|
|
559
562
|
})
|
|
560
563
|
.then((objChannels) => {
|
|
561
|
-
this.log.debug('%s: ++++++ step
|
|
564
|
+
this.log.debug('%s: ++++++ step 6: masterchannelList data was retrieved, channels found: %s', watchdogInstance, objChannels.length)
|
|
562
565
|
// Recording needs entitlements of PVR or LOCALDVR
|
|
563
566
|
const pvrFeatureFound = this.entitlements.features.find(feature => (feature === 'PVR' || feature === 'LOCALDVR'));
|
|
564
|
-
this.log.debug('%s: ++++++ step
|
|
567
|
+
this.log.debug('%s: ++++++ step 6: foundPvrEntitlement %s', watchdogInstance, pvrFeatureFound);
|
|
565
568
|
if (pvrFeatureFound) {
|
|
566
|
-
this.log.debug('%s: ++++++ step
|
|
569
|
+
this.log.debug('%s: ++++++ step 6: calling getRecordingState with householdId %s', watchdogInstance, this.session.householdId)
|
|
567
570
|
this.getRecordingState(this.session.householdId) // returns true when successful
|
|
568
571
|
}
|
|
569
572
|
return true
|
|
570
573
|
})
|
|
571
574
|
.then((objRecordingStateFound) => {
|
|
572
|
-
this.log.debug('%s: ++++++ step
|
|
575
|
+
this.log.debug('%s: ++++++ step 7: recording state data was retrieved, objRecordingStateFound: %s', watchdogInstance, objRecordingStateFound)
|
|
573
576
|
// Recording needs entitlements of PVR or LOCALDVR
|
|
574
577
|
const pvrFeatureFound = this.entitlements.features.find(feature => (feature === 'PVR' || feature === 'LOCALDVR'));
|
|
575
|
-
this.log.debug('%s: ++++++ step
|
|
578
|
+
this.log.debug('%s: ++++++ step 7: foundPvrEntitlement %s', watchdogInstance, pvrFeatureFound);
|
|
576
579
|
if (pvrFeatureFound) {
|
|
577
|
-
this.log.debug('%s: ++++++ step
|
|
580
|
+
this.log.debug('%s: ++++++ step 7: calling getRecordingBookings with householdId %s', watchdogInstance, this.session.householdId)
|
|
578
581
|
this.getRecordingBookings(this.session.householdId) // returns true when successful
|
|
579
582
|
}
|
|
580
583
|
return true
|
|
581
584
|
})
|
|
582
585
|
.then((objRecordingBookingsFound) => {
|
|
583
|
-
this.log.debug('%s: ++++++ step
|
|
584
|
-
this.log.debug('%s: ++++++ step
|
|
586
|
+
this.log.debug('%s: ++++++ step 8: recording bookings data was retrieved, objRecordingBookingsFound: %s', watchdogInstance, objRecordingBookingsFound)
|
|
587
|
+
this.log.debug('%s: ++++++ step 8: calling discoverDevices', watchdogInstance)
|
|
585
588
|
errorTitle = 'Failed to discover devices';
|
|
586
589
|
debug(debugPrefix + 'calling discoverDevices')
|
|
587
590
|
return this.discoverDevices() // returns stbDevices object
|
|
588
591
|
})
|
|
589
592
|
.then((objStbDevices) => {
|
|
590
593
|
this.log('Discovery completed');
|
|
591
|
-
this.log.debug('%s: ++++++ step
|
|
592
|
-
this.log.debug('%s: ++++++ step
|
|
594
|
+
this.log.debug('%s: ++++++ step 9: devices found:', watchdogInstance, this.devices.length)
|
|
595
|
+
this.log.debug('%s: ++++++ step 9: calling getMqttToken', watchdogInstance)
|
|
593
596
|
errorTitle = 'Failed to start mqtt session';
|
|
594
597
|
debug(debugPrefix + 'calling getMqttToken')
|
|
595
598
|
return this.getMqttToken(this.session.username, this.session.accessToken, this.session.householdId);
|
|
596
599
|
})
|
|
597
600
|
.then((mqttToken) => {
|
|
598
|
-
this.log.debug('%s: ++++++ step
|
|
599
|
-
this.log.debug('%s: ++++++ step
|
|
600
|
-
debug(debugPrefix + 'calling
|
|
601
|
-
return this.
|
|
601
|
+
this.log.debug('%s: ++++++ step 10: getMqttToken token was retrieved, token %s', watchdogInstance, mqttToken)
|
|
602
|
+
this.log.debug('%s: ++++++ step 10: start mqtt client', watchdogInstance)
|
|
603
|
+
debug(debugPrefix + 'calling statMqttClient')
|
|
604
|
+
return this.statMqttClient(this, this.session.householdId, mqttToken); // returns true
|
|
602
605
|
})
|
|
603
606
|
.catch(errorReason => {
|
|
604
607
|
// log any errors and set the currentSessionState
|
|
@@ -722,7 +725,8 @@ class stbPlatform {
|
|
|
722
725
|
const axiosConfig = {
|
|
723
726
|
method: 'POST',
|
|
724
727
|
// https://prod.spark.sunrisetv.ch/auth-service/v1/authorization/refresh
|
|
725
|
-
url: countryBaseUrlArray[this.config.country.toLowerCase()] + '/auth-service/v1/authorization/refresh',
|
|
728
|
+
//url: countryBaseUrlArray[this.config.country.toLowerCase()] + '/auth-service/v1/authorization/refresh',
|
|
729
|
+
url: this.configsvc.authorizationService.URL + '/v1/authorization/refresh',
|
|
726
730
|
headers: {
|
|
727
731
|
"accept": "*/*", // mandatory
|
|
728
732
|
"content-type": "application/json; charset=UTF-8", // mandatory
|
|
@@ -782,18 +786,24 @@ class stbPlatform {
|
|
|
782
786
|
async createSession(country) {
|
|
783
787
|
return new Promise((resolve, reject) => {
|
|
784
788
|
this.currentStatusFault = Characteristic.StatusFault.NO_FAULT;
|
|
785
|
-
switch
|
|
786
|
-
|
|
789
|
+
//switch using authmethod with backup of country
|
|
790
|
+
switch(this.config.authmethod || this.config.country) {
|
|
791
|
+
case 'D': // OAuth 2.0 with PKCE
|
|
792
|
+
this.getSessionOAuth2Pkce()
|
|
793
|
+
.then((getSessionResponse) => { resolve(getSessionResponse); }) // return the getSessionResponse for the promise
|
|
794
|
+
.catch(error => { reject(error); }); // on any error, reject the promise and pass back the error
|
|
795
|
+
break;
|
|
796
|
+
case 'be-nl': case 'be-fr': case 'B':
|
|
787
797
|
this.getSessionBE()
|
|
788
798
|
.then((getSessionResponse) => { resolve(getSessionResponse); }) // return the getSessionResponse for the promise
|
|
789
799
|
.catch(error => { reject(error); }); // on any error, reject the promise and pass back the error
|
|
790
800
|
break;
|
|
791
|
-
case 'gb':
|
|
801
|
+
case 'gb': case 'C':
|
|
792
802
|
this.getSessionGB()
|
|
793
803
|
.then((getSessionResponse) => { resolve(getSessionResponse); }) // return the getSessionResponse for the promise
|
|
794
804
|
.catch(error => { reject(error); }); // on any error, reject the promise and pass back the error
|
|
795
805
|
break;
|
|
796
|
-
default: // ch, nl, ie, at
|
|
806
|
+
default: // ch, nl, ie, at, method A
|
|
797
807
|
this.getSession()
|
|
798
808
|
.then((getSessionResponse) => { resolve(getSessionResponse); }) // resolve with the getSessionResponse for the promise
|
|
799
809
|
.catch(error => { reject(error); }); // on any error, reject the promise and pass back the error
|
|
@@ -801,6 +811,286 @@ class stbPlatform {
|
|
|
801
811
|
})
|
|
802
812
|
}
|
|
803
813
|
|
|
814
|
+
|
|
815
|
+
// get session for OAuth 2.0 PKCE (special logon sequence)
|
|
816
|
+
getSessionOAuth2Pkce() {
|
|
817
|
+
return new Promise((resolve, reject) => {
|
|
818
|
+
this.log('Creating %s OAuth 2.0 PKCE session...',PLATFORM_NAME);
|
|
819
|
+
this.log.warn('++++ PLEASE NOTE: This is current test code with lots of debugging. Do not expect it to work yet. ++++');
|
|
820
|
+
currentSessionState = sessionState.LOADING;
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
824
|
+
// axios interceptors to log request and response for debugging
|
|
825
|
+
// works on all following requests in this sub
|
|
826
|
+
/*
|
|
827
|
+
axiosWS.interceptors.request.use(req => {
|
|
828
|
+
this.log.warn('+++INTERCEPTED BEFORE HTTP REQUEST COOKIEJAR:\n', cookieJar.getCookies(req.url));
|
|
829
|
+
this.log.warn('+++INTERCEPTOR HTTP REQUEST:',
|
|
830
|
+
'\nMethod:', req.method, '\nURL:', req.url,
|
|
831
|
+
'\nBaseURL:', req.baseURL, '\nHeaders:', req.headers,
|
|
832
|
+
'\nParams:', req.params, '\nData:', req.data
|
|
833
|
+
);
|
|
834
|
+
this.log.warn(req);
|
|
835
|
+
return req; // must return request
|
|
836
|
+
});
|
|
837
|
+
axiosWS.interceptors.response.use(res => {
|
|
838
|
+
this.log.warn('+++INTERCEPTED HTTP RESPONSE:', res.status, res.statusText,
|
|
839
|
+
'\nHeaders:', res.headers,
|
|
840
|
+
'\nUrl:', res.url,
|
|
841
|
+
//'\nData:', res.data,
|
|
842
|
+
'\nLast Request:', res.request
|
|
843
|
+
);
|
|
844
|
+
//this.log.warn(res);
|
|
845
|
+
this.log('+++INTERCEPTED AFTER HTTP RESPONSE COOKIEJAR:');
|
|
846
|
+
if (cookieJar) { this.log(cookieJar); }// watch out for empty cookieJar
|
|
847
|
+
return res; // must return response
|
|
848
|
+
});
|
|
849
|
+
*/
|
|
850
|
+
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
851
|
+
|
|
852
|
+
// good description of PKCE
|
|
853
|
+
// https://www.authlete.com/developers/pkce/
|
|
854
|
+
// creake a PKCE code pair and save it
|
|
855
|
+
this.pkcePair = generatePKCEPair();
|
|
856
|
+
//this.log('PKCE pair:', pkcePair);
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
// Step 1: # get authentication details
|
|
860
|
+
// Recorded sequence step 1: https://id.virginmedia.com/rest/v40/session/start?protocol=oidc&rememberMe=true
|
|
861
|
+
// const GB_AUTH_OESP_URL = 'https://web-api-prod-obo.horizon.tv/oesp/v4/GB/eng/web';
|
|
862
|
+
// https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/auth-service/v1/sso/authorization?code_challenge=aHsoE2kJlwA4qGOcx1OCH7i__1bBdV1l6yLOKUvW24U&language=en
|
|
863
|
+
let apiAuthorizationUrl = this.configsvc.authorizationService.URL + '/v1/sso/authorization?'
|
|
864
|
+
+ 'code_challenge=' + this.pkcePair.code_challenge
|
|
865
|
+
+ '&language=en';
|
|
866
|
+
|
|
867
|
+
this.log('Step 1 of 7: get authentication details');
|
|
868
|
+
if (this.config.debugLevel > 1) { this.log.warn('Step 1 of 7: get authentication details from',apiAuthorizationUrl); }
|
|
869
|
+
axiosWS.get(apiAuthorizationUrl)
|
|
870
|
+
.then(response => {
|
|
871
|
+
this.log('Step 1 of 7: response:',response.status, response.statusText);
|
|
872
|
+
this.log('Step 1 of 7: response.data',response.data);
|
|
873
|
+
|
|
874
|
+
// get the data we need for further steps
|
|
875
|
+
let auth = response.data;
|
|
876
|
+
let authState = auth.state;
|
|
877
|
+
let authAuthorizationUri = auth.authorizationUri;
|
|
878
|
+
let authValidtyToken = auth.validityToken;
|
|
879
|
+
this.log('Step 1 of 7: results: authState',authState);
|
|
880
|
+
this.log('Step 1 of 7: results: authAuthorizationUri',authAuthorizationUri);
|
|
881
|
+
this.log('Step 1 of 7: results: authValidtyToken',authValidtyToken);
|
|
882
|
+
|
|
883
|
+
// Step 2: # follow authorizationUri to get AUTH cookie (ULM-JSESSIONID)
|
|
884
|
+
this.log('Step 2 of 7: get AUTH cookie');
|
|
885
|
+
this.log.debug('Step 2 of 7: get AUTH cookie ULM-JSESSIONID from',authAuthorizationUri);
|
|
886
|
+
axiosWS.get(authAuthorizationUri, {
|
|
887
|
+
jar: cookieJar,
|
|
888
|
+
// unsure what minimum headers will here
|
|
889
|
+
headers: {
|
|
890
|
+
Accept: 'application/json, text/plain, */*'
|
|
891
|
+
//Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
|
|
892
|
+
}, })
|
|
893
|
+
.then(response => {
|
|
894
|
+
this.log('Step 2 of 7: response:',response.status, response.statusText);
|
|
895
|
+
this.log.warn('Step 2 of 7 response.data',response.data); // an html logon page
|
|
896
|
+
|
|
897
|
+
// Step 3: # login
|
|
898
|
+
this.log('Step 3 of 7: logging in with username %s', this.config.username);
|
|
899
|
+
currentSessionState = sessionState.LOGGING_IN;
|
|
900
|
+
|
|
901
|
+
// we want to POST to
|
|
902
|
+
// 'https://id.virginmedia.com/rest/v40/session/start?protocol=oidc&rememberMe=true';
|
|
903
|
+
// see https://auth0.com/intro-to-iam/what-is-openid-connect-oidc
|
|
904
|
+
const GB_AUTH_URL = 'https://id.virginmedia.com/rest/v40/session/start?protocol=oidc&rememberMe=true';
|
|
905
|
+
this.log.debug('Step 3 of 7: POST request will contain this data: {"username":"' + this.config.username + '","credential":"' + this.config.password + '"}');
|
|
906
|
+
axiosWS(GB_AUTH_URL,{
|
|
907
|
+
//axiosWS('https://id.virginmedia.com/rest/v40/session/start?protocol=oidc&rememberMe=true',{
|
|
908
|
+
jar: cookieJar,
|
|
909
|
+
// However, since v2.0, axios-cookie-jar will always ignore invalid cookies. See https://github.com/3846masa/axios-cookiejar-support/blob/main/MIGRATION.md
|
|
910
|
+
data: '{"username":"' + this.config.username + '","credential":"' + this.config.password + '"}',
|
|
911
|
+
method: "POST",
|
|
912
|
+
// minimum headers are "accept": "*/*", "content-type": "application/json; charset=UTF-8",
|
|
913
|
+
headers: {
|
|
914
|
+
"accept": "*/*", // mandatory
|
|
915
|
+
"content-type": "application/json; charset=UTF-8", // mandatory
|
|
916
|
+
},
|
|
917
|
+
maxRedirects: 0, // do not follow redirects
|
|
918
|
+
validateStatus: function (status) {
|
|
919
|
+
return ((status >= 200 && status < 300) || status == 302) ; // allow 302 redirect as OK. GB returns 200
|
|
920
|
+
},
|
|
921
|
+
})
|
|
922
|
+
.then(response => {
|
|
923
|
+
this.log('Step 3 of 7: response:',response.status, response.statusText);
|
|
924
|
+
this.log.warn('Step 3 of 7: response.headers:',response.headers);
|
|
925
|
+
// responds with a userId, this will need to be used somewhere...
|
|
926
|
+
this.log.warn('Step 3 of 7: response.data:',response.data); // { userId: 28786528, runtimeId: 79339515 }
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
var url = response.headers['x-redirect-location'] // must be lowercase
|
|
930
|
+
if (!url) { // robustness: fail if url missing
|
|
931
|
+
this.log.warn('getSessionGB: Step 3: x-redirect-location url empty!');
|
|
932
|
+
currentSessionState = sessionState.DISCONNECTED;
|
|
933
|
+
this.currentStatusFault = Characteristic.StatusFault.GENERAL_FAULT;
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
//location is h??=... if success
|
|
937
|
+
//location is https?? if not authorised
|
|
938
|
+
//location is https:... error=session_expired if session has expired
|
|
939
|
+
if (url.indexOf('authentication_error=true') > 0 ) { // >0 if found
|
|
940
|
+
//this.log.warn('Step 3 of 7: Unable to login: wrong credentials');
|
|
941
|
+
reject('Step 3 of 7: Unable to login: wrong credentials'); // reject the promise and return the error
|
|
942
|
+
} else if (url.indexOf('error=session_expired') > 0 ) { // >0 if found
|
|
943
|
+
//this.log.warn('Step 3 of 7: Unable to login: session expired');
|
|
944
|
+
cookieJar.removeAllCookies(); // remove all the locally cached cookies
|
|
945
|
+
reject('Step 3 of 7: Unable to login: session expired'); // reject the promise and return the error
|
|
946
|
+
} else {
|
|
947
|
+
this.log.debug('Step 3 of 7: login successful');
|
|
948
|
+
|
|
949
|
+
// Step 4: # follow redirect url
|
|
950
|
+
this.log('Step 4 of 7: follow redirect url');
|
|
951
|
+
axiosWS.get(url,{
|
|
952
|
+
jar: cookieJar,
|
|
953
|
+
maxRedirects: 0, // do not follow redirects
|
|
954
|
+
validateStatus: function (status) {
|
|
955
|
+
return ((status >= 200 && status < 300) || status == 302) ; // allow 302 redirect as OK
|
|
956
|
+
},
|
|
957
|
+
})
|
|
958
|
+
.then(response => {
|
|
959
|
+
this.log('Step 4 of 7: response:',response.status, response.statusText);
|
|
960
|
+
this.log.warn('Step 4 of 7: response.headers.location:',response.headers.location); // is https://www.telenet.be/nl/login_success_code=... if success
|
|
961
|
+
this.log.warn('Step 4 of 7: response.data:',response.data);
|
|
962
|
+
url = response.headers.location;
|
|
963
|
+
if (!url) { // robustness: fail if url missing
|
|
964
|
+
this.log.warn('getSessionGB: Step 4 of 7 location url empty!');
|
|
965
|
+
currentSessionState = sessionState.DISCONNECTED;
|
|
966
|
+
this.currentStatusFault = Characteristic.StatusFault.GENERAL_FAULT;
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// look for login_success?code=
|
|
971
|
+
if (url.indexOf('login_success?code=') < 0 ) { // <0 if not found
|
|
972
|
+
//this.log.warn('Step 4 of 7: Unable to login: wrong credentials');
|
|
973
|
+
reject('Step 4 of 7: Unable to login: wrong credentials'); // reject the promise and return the error
|
|
974
|
+
} else if (url.indexOf('error=session_expired') > 0 ) {
|
|
975
|
+
//this.log.warn('Step 4 of 7: Unable to login: session expired');
|
|
976
|
+
cookieJar.removeAllCookies(); // remove all the locally cached cookies
|
|
977
|
+
reject('Step 4 of 7: Unable to login: session expired'); // reject the promise and return the error
|
|
978
|
+
} else {
|
|
979
|
+
|
|
980
|
+
// Step 5: # obtain authorizationCode
|
|
981
|
+
this.log('Step 5 of 7: extract authorizationCode');
|
|
982
|
+
/*
|
|
983
|
+
url = response.headers.location;
|
|
984
|
+
if (!url) { // robustness: fail if url missing
|
|
985
|
+
this.log.warn('getSessionGB: Step 5: location url empty!');
|
|
986
|
+
currentSessionState = sessionState.DISCONNECTED;
|
|
987
|
+
this.currentStatusFault = Characteristic.StatusFault.GENERAL_FAULT;
|
|
988
|
+
return false;
|
|
989
|
+
}
|
|
990
|
+
*/
|
|
991
|
+
|
|
992
|
+
var codeMatches = url.match(/code=(?:[^&]+)/g)[0].split('=');
|
|
993
|
+
var authorizationCode = codeMatches[1];
|
|
994
|
+
if (codeMatches.length !== 2 ) { // length must be 2 if code found
|
|
995
|
+
this.log.warn('Step 5 of 7: Unable to extract authorizationCode');
|
|
996
|
+
} else {
|
|
997
|
+
this.log('Step 5 of 7: authorizationCode OK');
|
|
998
|
+
this.log.debug('Step 5 of 7: authorizationCode:',authorizationCode);
|
|
999
|
+
|
|
1000
|
+
// Step 6: # authorize again
|
|
1001
|
+
this.log('Step 6 of 7: post auth data with valid code');
|
|
1002
|
+
this.log.debug('Step 6 of 7: post auth data with valid code to',apiAuthorizationUrl);
|
|
1003
|
+
currentSessionState = sessionState.AUTHENTICATING;
|
|
1004
|
+
var payload = {'authorizationGrant':{
|
|
1005
|
+
'authorizationCode':authorizationCode,
|
|
1006
|
+
'validityToken':authValidtyToken,
|
|
1007
|
+
'state':authState
|
|
1008
|
+
}};
|
|
1009
|
+
axiosWS.post(apiAuthorizationUrl, payload, {jar: cookieJar})
|
|
1010
|
+
.then(response => {
|
|
1011
|
+
this.log('Step 6 of 7: response:',response.status, response.statusText);
|
|
1012
|
+
this.log.debug('Step 6 of 7: response.data:',response.data);
|
|
1013
|
+
|
|
1014
|
+
auth = response.data;
|
|
1015
|
+
this.log.debug('Step 6 of 7: refreshToken:',auth.refreshToken);
|
|
1016
|
+
|
|
1017
|
+
// Step 7: # get OESP code
|
|
1018
|
+
this.log('Step 7 of 7: post refreshToken request');
|
|
1019
|
+
this.log.debug('Step 7 of 7: post refreshToken request to',apiAuthorizationUrl);
|
|
1020
|
+
payload = {'refreshToken':auth.refreshToken,'username':auth.username};
|
|
1021
|
+
// must resolve to
|
|
1022
|
+
// 'https://web-api-prod-obo.horizon.tv/oesp/v4/GB/eng/web/session';',
|
|
1023
|
+
var sessionUrl = GB_AUTH_OESP_URL + '/session';
|
|
1024
|
+
axiosWS.post(sessionUrl + "?token=true", payload, {jar: cookieJar})
|
|
1025
|
+
.then(response => {
|
|
1026
|
+
this.log('Step 7 of 7: response:',response.status, response.statusText);
|
|
1027
|
+
currentSessionState = sessionState.VERIFYING;
|
|
1028
|
+
|
|
1029
|
+
this.log.debug('Step 7 of 7: response.headers:',response.headers);
|
|
1030
|
+
this.log.debug('Step 7 of 7: response.data:',response.data);
|
|
1031
|
+
this.log.debug('Cookies for the session:',cookieJar.getCookies(sessionUrl));
|
|
1032
|
+
if (this.config.debugLevel > 2) {
|
|
1033
|
+
this.log('getSessionGB: response data (saved to this.session):');
|
|
1034
|
+
this.log(response.data);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// get device data from the session
|
|
1038
|
+
this.session = response.data;
|
|
1039
|
+
// New APLSTB Apollo box on NL does not return username in during session logon, so store username from settings if missing
|
|
1040
|
+
if (this.session.username == '') { this.session.username = this.config.username; }
|
|
1041
|
+
|
|
1042
|
+
currentSessionState = sessionState.CONNECTED;
|
|
1043
|
+
this.currentStatusFault = Characteristic.StatusFault.NO_FAULT;
|
|
1044
|
+
this.log('Session created');
|
|
1045
|
+
resolve(this.session.householdId) // resolve the promise with the householdId
|
|
1046
|
+
})
|
|
1047
|
+
// Step 7 http errors
|
|
1048
|
+
.catch(error => {
|
|
1049
|
+
this.log.debug("Step 7 of 7: error:",error);
|
|
1050
|
+
reject("Step 7 of 7: Unable to get OESP token: " + error.response.status + ' ' + error.response.statusText); // reject the promise and return the error
|
|
1051
|
+
});
|
|
1052
|
+
})
|
|
1053
|
+
// Step 6 http errors
|
|
1054
|
+
.catch(error => {
|
|
1055
|
+
reject("Step 6 of 7: Unable to authorize with oauth code, http error: " + error.response.status + ' ' + error.response.statusText); // reject the promise and return the error
|
|
1056
|
+
});
|
|
1057
|
+
};
|
|
1058
|
+
};
|
|
1059
|
+
})
|
|
1060
|
+
// Step 4 http errors
|
|
1061
|
+
.catch(error => {
|
|
1062
|
+
this.log.debug("Step 4 of 7: error:",error);
|
|
1063
|
+
this.log.warn("Step 4 of 7: error:",error);
|
|
1064
|
+
reject("Step 4 of 7: Unable to oauth authorize: " + error.response.status + ' ' + error.response.statusText); // reject the promise and return the error
|
|
1065
|
+
});
|
|
1066
|
+
};
|
|
1067
|
+
})
|
|
1068
|
+
// Step 3 http errors
|
|
1069
|
+
.catch(error => {
|
|
1070
|
+
this.log.debug("Step 3 of 7: error:",error);
|
|
1071
|
+
this.log.warn("Step 3 of 7: error:",error);
|
|
1072
|
+
reject("Step 3 of 7: Unable to login: " + error.response.status + ' ' + error.response.statusText); // reject the promise and return the error
|
|
1073
|
+
});
|
|
1074
|
+
})
|
|
1075
|
+
// Step 2 http errors
|
|
1076
|
+
.catch(error => {
|
|
1077
|
+
this.log.debug("Step 2 of 7: error:",error);
|
|
1078
|
+
reject("Step 2 of 7: Could not get authorizationUri: " + error.response.status + ' ' + error.response.statusText); // reject the promise and return the error
|
|
1079
|
+
});
|
|
1080
|
+
})
|
|
1081
|
+
// Step 1 http errors
|
|
1082
|
+
.catch(error => {
|
|
1083
|
+
this.log.debug("Step 1 of 7: error:",error);
|
|
1084
|
+
reject("Step 1 of 7: Failed to create session - check your internet connection"); // reject the promise and return the error
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
currentSessionState = sessionState.DISCONNECTED;
|
|
1088
|
+
this.currentStatusFault = Characteristic.StatusFault.GENERAL_FAULT;
|
|
1089
|
+
})
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
|
|
804
1094
|
// get session ch, nl, ie, at
|
|
805
1095
|
// using new auth method, as of 13.10.2022
|
|
806
1096
|
async getSession() {
|
|
@@ -837,7 +1127,8 @@ class stbPlatform {
|
|
|
837
1127
|
|
|
838
1128
|
const axiosConfig = {
|
|
839
1129
|
method: 'POST',
|
|
840
|
-
url: countryBaseUrlArray[this.config.country.toLowerCase()] + '/auth-service/v1/authorization',
|
|
1130
|
+
//url: countryBaseUrlArray[this.config.country.toLowerCase()] + '/auth-service/v1/authorization',
|
|
1131
|
+
url: this.configsvc.authorizationService.URL + '/v1/authorization',
|
|
841
1132
|
headers: {
|
|
842
1133
|
"accept": "*/*", // added 07.08.2023
|
|
843
1134
|
"content-type": "application/json; charset=utf-8", // added 07.08.2023
|
|
@@ -958,7 +1249,8 @@ class stbPlatform {
|
|
|
958
1249
|
|
|
959
1250
|
|
|
960
1251
|
// Step 1: # get authentication details
|
|
961
|
-
let apiAuthorizationUrl = countryBaseUrlArray[this.config.country.toLowerCase()] + '/auth-service/v1/sso/authorization';
|
|
1252
|
+
//let apiAuthorizationUrl = countryBaseUrlArray[this.config.country.toLowerCase()] + '/auth-service/v1/sso/authorization';
|
|
1253
|
+
let apiAuthorizationUrl = this.configsvc.authorizationService.URL + '/v1/sso/authorization';
|
|
962
1254
|
this.log('Step 1 of 6: get authentication details');
|
|
963
1255
|
if (this.config.debugLevel > 1) { this.log.warn('Step 1 of 6: get authentication details from',apiAuthorizationUrl); }
|
|
964
1256
|
axiosWS.get(apiAuthorizationUrl)
|
|
@@ -1469,7 +1761,8 @@ class stbPlatform {
|
|
|
1469
1761
|
url = url + '&sort=channelNumber' // sort
|
|
1470
1762
|
*/
|
|
1471
1763
|
//url = 'https://prod.spark.sunrisetv.ch/eng/web/linear-service/v2/channels?cityId=401&language=en&productClass=Orion-DASH'
|
|
1472
|
-
let url = countryBaseUrlArray[this.config.country.toLowerCase()] + '/eng/web/linear-service/v2/channels';
|
|
1764
|
+
//let url = countryBaseUrlArray[this.config.country.toLowerCase()] + '/eng/web/linear-service/v2/channels';
|
|
1765
|
+
let url = this.configsvc.linearService.URL + '/v2/channels';
|
|
1473
1766
|
url = url + '?cityId=' + this.customer.cityId; //+ this.customer.cityId // cityId needed to get user-specific list
|
|
1474
1767
|
url = url + '&language=en'; // language
|
|
1475
1768
|
url = url + '&productClass=Orion-DASH'; // productClass, must be Orion-DASH
|
|
@@ -1574,6 +1867,45 @@ class stbPlatform {
|
|
|
1574
1867
|
})
|
|
1575
1868
|
}
|
|
1576
1869
|
|
|
1870
|
+
|
|
1871
|
+
// get the config (containing all endpoints) for the country
|
|
1872
|
+
// added 14.01.2024
|
|
1873
|
+
async getConfig(countryCode, callback) {
|
|
1874
|
+
return new Promise((resolve, reject) => {
|
|
1875
|
+
this.log("Retrieving config for countryCode %s", countryCode);
|
|
1876
|
+
|
|
1877
|
+
// https://spark-prod-ch.gnp.cloud.sunrisetv.ch/ch/en/config-service/conf/web/backoffice.json
|
|
1878
|
+
// https://prod.spark.upctv.ch/ch/en/config-service/conf/web/backoffice.json
|
|
1879
|
+
const ctryCode = countryCode.substr(0, 2);
|
|
1880
|
+
|
|
1881
|
+
//const url = 'https://spark-prod-ch.gnp.cloud.sunrisetv.ch/ch/en/config-service/conf/web/backoffice.json'
|
|
1882
|
+
// use countryCode.substr(1, 2) for backwards-compatibility to allow be-fr to map to be
|
|
1883
|
+
const url=countryBaseUrlArray[ctryCode] + '/' + ctryCode + '/en/config-service/conf/web/backoffice.json';
|
|
1884
|
+
if (this.config.debugLevel > 0) { this.log.warn('getConfig: GET %s', url); }
|
|
1885
|
+
axiosWS.get(url)
|
|
1886
|
+
.then(response => {
|
|
1887
|
+
if (this.config.debugLevel > 0) { this.log.warn('getConfig: response: %s %s', response.status, response.statusText); }
|
|
1888
|
+
if (this.config.debugLevel > 2) {
|
|
1889
|
+
this.log.warn('getConfig: response data (saved to this.configsvc):');
|
|
1890
|
+
this.log.warn(response.data);
|
|
1891
|
+
}
|
|
1892
|
+
this.configsvc = response.data; // store the entire config data for future use in this.configsvc
|
|
1893
|
+
resolve(this.configsvc); // resolve the promise with the configsvc object
|
|
1894
|
+
})
|
|
1895
|
+
.catch(error => {
|
|
1896
|
+
let errReason;
|
|
1897
|
+
errReason = 'Could not get config data for ' + countryCode + ' - check your internet connection'
|
|
1898
|
+
if (error.isAxiosError) {
|
|
1899
|
+
errReason = error.code + ': ' + (error.hostname || '');
|
|
1900
|
+
// if no connection then set session to disconnected to force a session reconnect
|
|
1901
|
+
if (error.code == 'ENOTFOUND') { currentSessionState = sessionState.DISCONNECTED; }
|
|
1902
|
+
}
|
|
1903
|
+
this.log.debug(`getConfig error:`, error);
|
|
1904
|
+
reject(errReason);
|
|
1905
|
+
});
|
|
1906
|
+
})
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1577
1909
|
|
|
1578
1910
|
|
|
1579
1911
|
|
|
@@ -1586,7 +1918,10 @@ class stbPlatform {
|
|
|
1586
1918
|
|
|
1587
1919
|
//const url = personalizationServiceUrlArray[this.config.country.toLowerCase()].replace("{householdId}", this.session.householdId) + '/' + requestType;
|
|
1588
1920
|
//const url='https://prod.spark.sunrisetv.ch/eng/web/personalization-service/v1/customer/' + householdId + '?with=profiles%2Cdevices';
|
|
1589
|
-
|
|
1921
|
+
// https://spark-prod-ch.gnp.cloud.sunrisetv.ch/eng/web/personalization-service
|
|
1922
|
+
//const url=countryBaseUrlArray[this.config.country.toLowerCase()] + '/eng/web/personalization-service/v1/customer/' + householdId + '?with=profiles%2Cdevices';
|
|
1923
|
+
const url = this.configsvc.personalizationService.URL + '/v1/customer/' + householdId + '?with=profiles%2Cdevices';
|
|
1924
|
+
|
|
1590
1925
|
// headers are in the web client
|
|
1591
1926
|
let config={}
|
|
1592
1927
|
if (this.config.country.toLowerCase() == 'gb'){
|
|
@@ -1693,7 +2028,9 @@ class stbPlatform {
|
|
|
1693
2028
|
async setPersonalizationDataForDevice(deviceId, deviceSettings, callback) {
|
|
1694
2029
|
if (this.config.debugLevel > 0) { this.log.warn('setPersonalizationDataForDevice: deviceSettings:', deviceSettings); }
|
|
1695
2030
|
// https://prod.spark.sunrisetv.ch/eng/web/personalization-service/v1/customer/1012345_ch/devices/3C36E4-EOSSTB-003656123456
|
|
1696
|
-
const url = countryBaseUrlArray[this.config.country.toLowerCase()] + '/eng/web/personalization-service/v1/customer/' + this.session.householdId + '/devices/' + deviceId;
|
|
2031
|
+
//const url = countryBaseUrlArray[this.config.country.toLowerCase()] + '/eng/web/personalization-service/v1/customer/' + this.session.householdId + '/devices/' + deviceId;
|
|
2032
|
+
const url = this.configsvc.personalizationService.URL + '/v1/customer/' + this.session.householdId + '/devices/' + deviceId;
|
|
2033
|
+
|
|
1697
2034
|
const data = {"settings": deviceSettings};
|
|
1698
2035
|
// gb needs x-cus, x-oesp-token and x-oesp-username
|
|
1699
2036
|
let config={}
|
|
@@ -1734,7 +2071,8 @@ class stbPlatform {
|
|
|
1734
2071
|
|
|
1735
2072
|
//const url = personalizationServiceUrlArray[this.config.country.toLowerCase()].replace("{householdId}", this.session.householdId) + '/' + requestType;
|
|
1736
2073
|
//const url='https://prod.spark.sunrisetv.ch/eng/web/purchase-service/v2/customers/107xxxx_ch/entitlements?enableDaypass=true'
|
|
1737
|
-
const url=countryBaseUrlArray[this.config.country.toLowerCase()] + '/eng/web/purchase-service/v2/customers/' + householdId + '/entitlements?enableDaypass=true';
|
|
2074
|
+
//const url=countryBaseUrlArray[this.config.country.toLowerCase()] + '/eng/web/purchase-service/v2/customers/' + householdId + '/entitlements?enableDaypass=true';
|
|
2075
|
+
const url = this.configsvc.purchaseService.URL + '/v2/customers/' + householdId + '/entitlements?enableDaypass=true';
|
|
1738
2076
|
//const config = {headers: {"x-cus": this.session.householdId, "x-oesp-token": this.session.accessToken, "x-oesp-username": this.session.username}};
|
|
1739
2077
|
const config = {headers: {
|
|
1740
2078
|
"x-cus": householdId,
|
|
@@ -1802,7 +2140,8 @@ class stbPlatform {
|
|
|
1802
2140
|
// https://prod.spark.sunrisetv.ch/eng/web/recording-service/customers/107xxxx_ch/recordings?isAdult=false&offset=0&limit=100&sort=time&sortOrder=desc&profileId=4eb38207-d869-4367-8973-9467a42cad74&language=en
|
|
1803
2141
|
// const url = countryBaseUrlArray[this.config.country.toLowerCase()] + '/' + 'networkdvrrecordings?isAdult=false&plannedOnly=false&range=1-20'; // works
|
|
1804
2142
|
// parameter plannedOnly=false did not work
|
|
1805
|
-
const url = countryBaseUrlArray[this.config.country.toLowerCase()] + '/eng/web/recording-service/customers/' + householdId + '/recordings/state'; // limit to 20 recordings for performance
|
|
2143
|
+
//const url = countryBaseUrlArray[this.config.country.toLowerCase()] + '/eng/web/recording-service/customers/' + householdId + '/recordings/state'; // limit to 20 recordings for performance
|
|
2144
|
+
const url = this.configsvc.recordingService.URL + '/customers/' + householdId + '/recordings/state'; // limit to 20 recordings for performance
|
|
1806
2145
|
if (this.config.debugLevel > 0) { this.log.warn('getRecordingState: GET %s', url); }
|
|
1807
2146
|
axiosWS.get(url, config)
|
|
1808
2147
|
.then(response => {
|
|
@@ -2036,7 +2375,8 @@ class stbPlatform {
|
|
|
2036
2375
|
let url
|
|
2037
2376
|
//url=countryBaseUrlArray[this.config.country.toLowerCase()] + '/eng/web/purchase-service/v2/customers/' + householdId + '/entitlements?enableDaypass=true';
|
|
2038
2377
|
//url='https://web-api-prod-obo.horizon.tv/oesp/v4/CH/eng/web/eng/session'
|
|
2039
|
-
url='https://prod.spark.upctv.ch/ch/en/session-service'
|
|
2378
|
+
//url='https://prod.spark.upctv.ch/ch/en/session-service'
|
|
2379
|
+
url = this.configsvc.sessionService.URL;
|
|
2040
2380
|
const config = {headers: {
|
|
2041
2381
|
"x-cus": householdId,
|
|
2042
2382
|
"x-oesp-token": this.session.accessToken,
|
|
@@ -2102,11 +2442,11 @@ class stbPlatform {
|
|
|
2102
2442
|
|
|
2103
2443
|
const mqttAxiosConfig = {
|
|
2104
2444
|
method: 'GET',
|
|
2105
|
-
//url: countryBaseUrlArray[this.config.country.toLowerCase()] + '/tokens/jwt', prior to October 2022
|
|
2106
2445
|
// examples of auth-service/v1/mqtt/token urls:
|
|
2107
2446
|
// https://prod.spark.ziggogo.tv/auth-service/v1/mqtt/token
|
|
2108
2447
|
// https://prod.spark.sunrisetv.ch/auth-service/v1/mqtt/token
|
|
2109
|
-
url: countryBaseUrlArray[this.config.country.toLowerCase()] + '/auth-service/v1/mqtt/token', // new from October 2022
|
|
2448
|
+
//url: countryBaseUrlArray[this.config.country.toLowerCase()] + '/auth-service/v1/mqtt/token', // new from October 2022
|
|
2449
|
+
url: this.configsvc.authorizationService.URL + '/v1/mqtt/token',
|
|
2110
2450
|
headers: {
|
|
2111
2451
|
'X-OESP-Token': accessToken,
|
|
2112
2452
|
'X-OESP-Username': oespUsername,
|
|
@@ -2120,8 +2460,6 @@ class stbPlatform {
|
|
|
2120
2460
|
}
|
|
2121
2461
|
mqttUsername = householdId; // used in sendKey to ensure that mqtt is connected
|
|
2122
2462
|
resolve(response.data.token); // resolve with the token
|
|
2123
|
-
//this.startMqttClient(this, householdId, response.data.token); // this starts the mqtt session
|
|
2124
|
-
|
|
2125
2463
|
})
|
|
2126
2464
|
.catch(error => {
|
|
2127
2465
|
this.log.debug('getMqttToken error details:', error);
|
|
@@ -2137,7 +2475,7 @@ class stbPlatform {
|
|
|
2137
2475
|
// a sync procedure, no promise returned
|
|
2138
2476
|
// https://github.com/mqttjs/MQTT.js#readme
|
|
2139
2477
|
// http://www.steves-internet-guide.com/mqtt-publish-subscribe/
|
|
2140
|
-
|
|
2478
|
+
statMqttClient(parent, mqttUsername, mqttPassword) {
|
|
2141
2479
|
return new Promise((resolve, reject) => {
|
|
2142
2480
|
try {
|
|
2143
2481
|
if (this.config.debugLevel > 0) {
|
|
@@ -2150,28 +2488,45 @@ class stbPlatform {
|
|
|
2150
2488
|
|
|
2151
2489
|
|
|
2152
2490
|
// create mqtt client instance and connect to the mqttUrl
|
|
2153
|
-
const
|
|
2491
|
+
//const mqttBroker = mqttUrlArray[this.config.country.toLowerCase()];
|
|
2492
|
+
const mqttBrokerUrl = this.configsvc.mqttBroker.URL;
|
|
2154
2493
|
if (this.config.debugLevel > 0) {
|
|
2155
|
-
this.log.warn('
|
|
2494
|
+
this.log.warn('statMqttClient: mqttBrokerUrl:', mqttBrokerUrl );
|
|
2156
2495
|
}
|
|
2157
2496
|
if (this.config.debugLevel > 0) {
|
|
2158
|
-
this.log.warn('
|
|
2497
|
+
this.log.warn('statMqttClient: Creating mqttClient with username %s, password %s', mqttUsername ,mqttPassword );
|
|
2159
2498
|
}
|
|
2160
2499
|
|
|
2161
|
-
// make a new mqttClientId on every session start
|
|
2500
|
+
// make a new mqttClientId on every session start (much robuster), then connect
|
|
2162
2501
|
//mqttClientId = makeId(32);
|
|
2163
2502
|
mqttClientId = makeFormattedId(32);
|
|
2164
|
-
//mqttClientId = makeId(32);
|
|
2165
2503
|
|
|
2504
|
+
// from 24 Jan 2024 we need to set the sub protocols mqtt, mqttv3.1, mqttv3.11 to connect
|
|
2505
|
+
// the required header looks like this:
|
|
2506
|
+
// "sec-websocket-protocol": "mqtt, mqttv3.1, mqttv3.11",
|
|
2507
|
+
// make a new custom websocket so we can ensure the correct mqtt protocols are used in the headers
|
|
2508
|
+
// see https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketaddress-protocols-options
|
|
2509
|
+
const createCustomWebsocket = (url, websocketSubProtocols, options) => {
|
|
2510
|
+
//this.log.warn('statMqttClient: createCustomWebsocket: ', websocketSubProtocols[0] );
|
|
2511
|
+
const subProtocols = [
|
|
2512
|
+
'mqtt',
|
|
2513
|
+
'mqttv3.1',
|
|
2514
|
+
'mqttv3.11'
|
|
2515
|
+
];
|
|
2516
|
+
//this.log.warn('statMqttClient: createCustomWebsocket: about to return' );
|
|
2517
|
+
return new WebSocket(url, subProtocols);
|
|
2518
|
+
};
|
|
2519
|
+
|
|
2166
2520
|
// https://github.com/mqttjs/MQTT.js#connect
|
|
2167
|
-
mqttClient = mqtt.connect(
|
|
2168
|
-
|
|
2521
|
+
mqttClient = mqtt.connect(mqttBrokerUrl, {
|
|
2522
|
+
createWebsocket: createCustomWebsocket,
|
|
2169
2523
|
clientId: mqttClientId,
|
|
2524
|
+
connectTimeout: 10 * 1000, // 10s
|
|
2170
2525
|
username: mqttUsername,
|
|
2171
2526
|
password: mqttPassword
|
|
2172
2527
|
});
|
|
2173
2528
|
if (this.config.debugLevel > 0) {
|
|
2174
|
-
this.log.warn('
|
|
2529
|
+
this.log.warn('statMqttClient: mqttBroker connect request sent using mqttClientId %s',mqttClientId );
|
|
2175
2530
|
}
|
|
2176
2531
|
|
|
2177
2532
|
//mqttClient.setMaxListeners(20); // default is 10 sometimes causes issues when the listeners reach 11
|
|
@@ -2578,20 +2933,21 @@ class stbPlatform {
|
|
|
2578
2933
|
|
|
2579
2934
|
|
|
2580
2935
|
if (this.config.debugLevel > 0) {
|
|
2581
|
-
this.log.warn("
|
|
2936
|
+
this.log.warn("statMqttClient: end of code block");
|
|
2582
2937
|
}
|
|
2583
2938
|
resolve(mqttClient.connected); // return the promise with the connected state
|
|
2584
2939
|
|
|
2585
2940
|
} catch (err) {
|
|
2941
|
+
this.log.error(err);
|
|
2586
2942
|
reject('Cannot connect to mqtt broker', err); // reject the promise
|
|
2587
2943
|
}
|
|
2588
2944
|
|
|
2589
2945
|
})
|
|
2590
|
-
} // end of
|
|
2946
|
+
} // end of statMqttClient
|
|
2591
2947
|
|
|
2592
2948
|
|
|
2593
|
-
// end the mqtt
|
|
2594
|
-
|
|
2949
|
+
// end the mqtt session cleanly
|
|
2950
|
+
endMqttSession() {
|
|
2595
2951
|
return new Promise((resolve, reject) => {
|
|
2596
2952
|
if (this.config.debugLevel > -1) {
|
|
2597
2953
|
this.log('Shutting down mqttClient...');
|
|
@@ -4388,11 +4744,11 @@ class stbDevice {
|
|
|
4388
4744
|
this.log("%s: Refreshing most watched channels for profile '%s'", this.name, (profile || {}).name);
|
|
4389
4745
|
|
|
4390
4746
|
// https://prod.spark.sunrisetv.ch/eng/web/linear-service/v1/mostWatchedChannels?cityId=401&productClass=Orion-DASH"
|
|
4391
|
-
let url = countryBaseUrlArray[this.config.country.toLowerCase()] + '/eng/web/linear-service/v1/mostWatchedChannels';
|
|
4747
|
+
//let url = countryBaseUrlArray[this.config.country.toLowerCase()] + '/eng/web/linear-service/v1/mostWatchedChannels';
|
|
4748
|
+
let url = this.platform.configsvc.linearService.URL + '/v1/mostWatchedChannels';
|
|
4392
4749
|
// add url standard parameters
|
|
4393
4750
|
url = url + '?cityId=' + this.customer.cityId; //+ this.customer.cityId // cityId needed to get user-specific list
|
|
4394
4751
|
url = url + '&productClass=Orion-DASH'; // productClass, must be Orion-DASH
|
|
4395
|
-
if (this.config.debugLevel > 2) { this.log.warn('getMostWatchedChannels: loading from',url); }
|
|
4396
4752
|
|
|
4397
4753
|
const config = {headers: {
|
|
4398
4754
|
"x-oesp-username": this.platform.session.username, // not sure if needed
|
package/package.json
CHANGED
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
"displayName": "Homebridge EOSSTB",
|
|
4
4
|
"description": "Add your set-top box to Homekit (for Magenta AT, Telenet BE, Sunrise CH, Virgin Media GB & IE, Ziggo NL)",
|
|
5
5
|
"author": "Jochen Siegenthaler (https://github.com/jsiegenthaler/)",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.3.0",
|
|
7
7
|
"platformname": "eosstb",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"axios-cookiejar-support": "^5.0.0",
|
|
10
|
-
"axios": "^1.6.
|
|
10
|
+
"axios": "^1.6.6",
|
|
11
11
|
"debug": "^4.3.4",
|
|
12
|
-
"mqtt": "^5.3.
|
|
12
|
+
"mqtt": "^5.3.5",
|
|
13
13
|
"qs": "^6.11.2",
|
|
14
14
|
"semver": "^7.5.4",
|
|
15
|
-
"tough-cookie": "^4.1.3"
|
|
15
|
+
"tough-cookie": "^4.1.3",
|
|
16
|
+
"ws": "^8.16.0"
|
|
16
17
|
},
|
|
17
18
|
"deprecated": false,
|
|
18
19
|
"engines": {
|