homebridge-nest-accfactory 0.2.11 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +14 -7
  3. package/config.schema.json +118 -0
  4. package/dist/HomeKitDevice.js +203 -77
  5. package/dist/HomeKitHistory.js +1 -1
  6. package/dist/config.js +207 -0
  7. package/dist/devices.js +118 -0
  8. package/dist/index.js +2 -1
  9. package/dist/nexustalk.js +46 -48
  10. package/dist/{camera.js → plugins/camera.js} +216 -241
  11. package/dist/{doorbell.js → plugins/doorbell.js} +32 -30
  12. package/dist/plugins/floodlight.js +91 -0
  13. package/dist/plugins/heatlink.js +17 -0
  14. package/dist/{protect.js → plugins/protect.js} +26 -43
  15. package/dist/{tempsensor.js → plugins/tempsensor.js} +15 -19
  16. package/dist/{thermostat.js → plugins/thermostat.js} +426 -383
  17. package/dist/{weather.js → plugins/weather.js} +26 -60
  18. package/dist/protobuf/nest/services/apigateway.proto +31 -1
  19. package/dist/protobuf/nest/trait/firmware.proto +207 -89
  20. package/dist/protobuf/nest/trait/hvac.proto +1052 -312
  21. package/dist/protobuf/nest/trait/located.proto +51 -8
  22. package/dist/protobuf/nest/trait/network.proto +366 -36
  23. package/dist/protobuf/nest/trait/occupancy.proto +145 -17
  24. package/dist/protobuf/nest/trait/product/protect.proto +57 -43
  25. package/dist/protobuf/nest/trait/resourcedirectory.proto +8 -0
  26. package/dist/protobuf/nest/trait/sensor.proto +7 -1
  27. package/dist/protobuf/nest/trait/service.proto +3 -1
  28. package/dist/protobuf/nest/trait/structure.proto +60 -14
  29. package/dist/protobuf/nest/trait/ui.proto +41 -1
  30. package/dist/protobuf/nest/trait/user.proto +6 -1
  31. package/dist/protobuf/nest/trait/voiceassistant.proto +2 -1
  32. package/dist/protobuf/nestlabs/eventingapi/v1.proto +20 -1
  33. package/dist/protobuf/root.proto +1 -0
  34. package/dist/protobuf/wdl.proto +18 -2
  35. package/dist/protobuf/weave/common.proto +2 -1
  36. package/dist/protobuf/weave/trait/heartbeat.proto +41 -1
  37. package/dist/protobuf/weave/trait/power.proto +1 -0
  38. package/dist/protobuf/weave/trait/security.proto +10 -1
  39. package/dist/streamer.js +74 -78
  40. package/dist/system.js +1213 -1264
  41. package/dist/webrtc.js +39 -34
  42. package/package.json +11 -11
  43. package/dist/floodlight.js +0 -97
package/dist/config.js ADDED
@@ -0,0 +1,207 @@
1
+ // Configuration validation and processing
2
+ // Part of homebridge-nest-accfactory
3
+ //
4
+ // Code version 2025.06.15
5
+ // Mark Hulskamp
6
+ 'use strict';
7
+
8
+ // Define nodejs module requirements
9
+ import fs from 'fs';
10
+ import path from 'node:path';
11
+ import crypto from 'node:crypto';
12
+ import process from 'node:process';
13
+ import child_process from 'node:child_process';
14
+
15
+ // Define constants
16
+ const FFMPEG_VERSION = '6.0.0';
17
+ const ACCOUNT_TYPE = {
18
+ NEST: 'Nest',
19
+ GOOGLE: 'Google',
20
+ };
21
+
22
+ function processConfig(config, log) {
23
+ let options = (config.options = typeof config?.options === 'object' ? config.options : {});
24
+
25
+ options.eveHistory = config.options?.eveHistory === true;
26
+ options.weather = config.options?.weather === true;
27
+ options.hksv = config.options?.hksv === true;
28
+ options.exclude = config.options?.exclude === true;
29
+
30
+ options.elevation =
31
+ isNaN(config.options?.elevation) === false && Number(config.options.elevation) >= 0 && Number(config.options.elevation) <= 8848
32
+ ? Number(config.options.elevation)
33
+ : 0;
34
+
35
+ // Controls what APIs we use, default is to use both Nest and protobuf APIs
36
+ options.useNestAPI = config.options?.useNestAPI === true || config.options?.useNestAPI === undefined;
37
+ options.useGoogleAPI = config.options?.useGoogleAPI === true || config.options?.useGoogleAPI === undefined;
38
+
39
+ // Get configuration for max number of concurrent 'live view' streams. For HomeKit Secure Video, this will always be 1
40
+ options.maxStreams = isNaN(config.options?.maxStreams) === false ? Number(config.options.maxStreams) : 2;
41
+
42
+ // Check if a ffmpeg binary exist via a specific path in configuration OR /usr/local/bin
43
+ options.ffmpeg = {};
44
+ options.ffmpeg.debug = config.options?.ffmpegDebug === true;
45
+ options.ffmpeg.binary = path.resolve(
46
+ typeof config.options?.ffmpegPath === 'string' && config.options.ffmpegPath !== '' ? config.options.ffmpegPath : '/usr/local/bin',
47
+ );
48
+
49
+ // If the path doesn't include 'ffmpeg' on the end, we'll add it here
50
+ if (options.ffmpeg.binary.endsWith('/ffmpeg') === false) {
51
+ options.ffmpeg.binary += '/ffmpeg';
52
+ }
53
+
54
+ options.ffmpeg.version = undefined;
55
+ options.ffmpeg.libspeex = false;
56
+ options.ffmpeg.libopus = false;
57
+ options.ffmpeg.libx264 = false;
58
+ options.ffmpeg.libfdk_aac = false;
59
+
60
+ if (fs.existsSync(options.ffmpeg.binary) === false) {
61
+ // If we flag ffmpegPath as undefined, no video streaming/record support enabled for camers/doorbells
62
+ log?.warn?.('Specified ffmpeg binary "%s" was not found', options.ffmpeg.binary);
63
+ log?.warn?.('Stream video/recording from camera/doorbells will be unavailable');
64
+ options.ffmpeg.binary = undefined;
65
+ }
66
+
67
+ if (fs.existsSync(options.ffmpeg.binary) === true) {
68
+ let ffmpegProcess = child_process.spawnSync(options.ffmpeg.binary, ['-version'], {
69
+ env: process.env,
70
+ });
71
+
72
+ if (ffmpegProcess.stdout !== null) {
73
+ let stdout = ffmpegProcess.stdout.toString();
74
+
75
+ // Determine what libraries ffmpeg is compiled with
76
+ options.ffmpeg.version = stdout.match(/(?:ffmpeg version:(\d+)\.)?(?:(\d+)\.)?(?:(\d+)\.\d+)(.*?)/gim)?.[0];
77
+ options.ffmpeg.libspeex = stdout.includes('--enable-libspeex') === true;
78
+ options.ffmpeg.libopus = stdout.includes('--enable-libopus') === true;
79
+ options.ffmpeg.libx264 = stdout.includes('--enable-libx264') === true;
80
+ options.ffmpeg.libfdk_aac = stdout.includes('--enable-libfdk-aac') === true;
81
+
82
+ let versionTooOld =
83
+ options.ffmpeg.version?.localeCompare(FFMPEG_VERSION, undefined, {
84
+ numeric: true,
85
+ sensitivity: 'case',
86
+ caseFirst: 'upper',
87
+ }) === -1;
88
+
89
+ if (
90
+ versionTooOld ||
91
+ options.ffmpeg.libspeex === false ||
92
+ options.ffmpeg.libopus === false ||
93
+ options.ffmpeg.libx264 === false ||
94
+ options.ffmpeg.libfdk_aac === false
95
+ ) {
96
+ log?.warn?.('ffmpeg binary "%s" does not meet the minimum support requirements', options.ffmpeg.binary);
97
+
98
+ if (versionTooOld) {
99
+ log?.warn?.('Minimum binary version is "%s", however the installed version is "%s"', FFMPEG_VERSION, options.ffmpeg.version);
100
+ log?.warn?.('Stream video/recording from camera/doorbells will be unavailable');
101
+ options.ffmpeg.binary = undefined; // No ffmpeg since below min version
102
+ }
103
+
104
+ if (!options.ffmpeg.libspeex && options.ffmpeg.libx264 && options.ffmpeg.libfdk_aac) {
105
+ log?.warn?.('Missing libspeex in ffmpeg binary, talkback on certain camera/doorbells will be unavailable');
106
+ }
107
+
108
+ if (options.ffmpeg.libx264 && !options.ffmpeg.libfdk_aac && !options.ffmpeg.libopus) {
109
+ log?.warn?.('Missing libfdk_aac and libopus in ffmpeg binary, audio from camera/doorbells will be unavailable');
110
+ }
111
+
112
+ if (options.ffmpeg.libx264 && !options.ffmpeg.libfdk_aac) {
113
+ log?.warn?.('Missing libfdk_aac in ffmpeg binary, audio from camera/doorbells will be unavailable');
114
+ }
115
+
116
+ if (options.ffmpeg.libx264 && options.ffmpeg.libfdk_aac && !options.ffmpeg.libopus) {
117
+ log?.warn?.('Missing libopus in ffmpeg binary, audio (including talkback) from certain camera/doorbells will be unavailable');
118
+ }
119
+
120
+ if (!options.ffmpeg.libx264) {
121
+ log?.warn?.('Missing libx264 in ffmpeg binary, stream video/recording from camera/doorbells will be unavailable');
122
+ options.ffmpeg.binary = undefined; // No ffmpeg since we do not have all the required libraries
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ if (options.ffmpeg.binary !== undefined) {
129
+ log?.success?.('Found valid ffmpeg binary in %s', options.ffmpeg.binary);
130
+ }
131
+
132
+ // Process per device configuration(s)
133
+ if (config?.devices === undefined) {
134
+ config.devices = [];
135
+ }
136
+
137
+ if (config?.devices !== undefined && Array.isArray(config.devices) === false) {
138
+ // If the devices section is a JSON oject keyed by the devices serial number, convert to devices array object
139
+ let newDeviceArray = [];
140
+ for (const [serialNumber, props] of Object.entries(config.devices)) {
141
+ newDeviceArray.push({
142
+ serialNumber,
143
+ ...props,
144
+ });
145
+ }
146
+ config.devices = newDeviceArray;
147
+
148
+ // Alert user to changed configuration for them to update config
149
+ log?.warn?.('');
150
+ log?.warn?.('NOTICE');
151
+ log?.warn?.('> The per device configuration contains legacy options. Please review the readme at the link below');
152
+ log?.warn?.('> Consider updating your configuration file as the mapping from legacy to current per device configuration maybe removed');
153
+ log?.warn?.('> https://github.com/n0rt0nthec4t/homebridge-nest-accfactory/blob/main/src/README.md');
154
+ log?.warn?.('');
155
+ }
156
+
157
+ return config;
158
+ }
159
+
160
+ function buildConnections(config) {
161
+ let connections = {};
162
+
163
+ Object.keys(config).forEach((key) => {
164
+ let section = config[key];
165
+
166
+ if (typeof section?.access_token === 'string' && section.access_token !== '') {
167
+ let fieldTest = section?.fieldTest === true;
168
+ connections[crypto.randomUUID()] = {
169
+ name: key,
170
+ type: ACCOUNT_TYPE.NEST,
171
+ authorised: false,
172
+ access_token: section.access_token,
173
+ fieldTest,
174
+ referer: fieldTest ? 'home.ft.nest.com' : 'home.nest.com',
175
+ restAPIHost: fieldTest ? 'home.ft.nest.com' : 'home.nest.com',
176
+ cameraAPIHost: fieldTest ? 'camera.home.ft.nest.com' : 'camera.home.nest.com',
177
+ protobufAPIHost: fieldTest ? 'grpc-web.ft.nest.com' : 'grpc-web.production.nest.com',
178
+ };
179
+ }
180
+
181
+ if (
182
+ typeof section?.issuetoken === 'string' &&
183
+ section.issuetoken !== '' &&
184
+ typeof section?.cookie === 'string' &&
185
+ section.cookie !== ''
186
+ ) {
187
+ let fieldTest = section?.fieldTest === true;
188
+ connections[crypto.randomUUID()] = {
189
+ name: key,
190
+ type: ACCOUNT_TYPE.GOOGLE,
191
+ authorised: false,
192
+ issuetoken: section.issuetoken,
193
+ cookie: section.cookie,
194
+ fieldTest,
195
+ referer: fieldTest ? 'home.ft.nest.com' : 'home.nest.com',
196
+ restAPIHost: fieldTest ? 'home.ft.nest.com' : 'home.nest.com',
197
+ cameraAPIHost: fieldTest ? 'camera.home.ft.nest.com' : 'camera.home.nest.com',
198
+ protobufAPIHost: fieldTest ? 'grpc-web.ft.nest.com' : 'grpc-web.production.nest.com',
199
+ };
200
+ }
201
+ });
202
+
203
+ return connections;
204
+ }
205
+
206
+ // Define exports
207
+ export { ACCOUNT_TYPE, processConfig, buildConnections };
@@ -0,0 +1,118 @@
1
+ // Device support loader
2
+ // Part of homebridge-nest-accfactory
3
+ //
4
+ // Code version 2025.06.15
5
+ // Mark Hulskamp
6
+ 'use strict';
7
+
8
+ // Define nodejs module requirements
9
+ import path from 'node:path';
10
+ import fs from 'node:fs/promises';
11
+ import url from 'node:url';
12
+
13
+ // Import our modules
14
+ import HomeKitDevice from './HomeKitDevice.js';
15
+
16
+ // Define constants
17
+ const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
18
+ const DEVICE_TYPE = Object.freeze({
19
+ THERMOSTAT: 'Thermostat',
20
+ TEMPSENSOR: 'TemperatureSensor',
21
+ SMOKESENSOR: 'Protect',
22
+ CAMERA: 'Camera',
23
+ DOORBELL: 'Doorbell',
24
+ FLOODLIGHT: 'FloodlightCamera',
25
+ WEATHER: 'Weather',
26
+ HEATLINK: 'Heatlink',
27
+ LOCK: 'Lock',
28
+ ALARM: 'Alarm',
29
+ });
30
+
31
+ async function loadDeviceModules(log, pluginDir = '') {
32
+ let baseDir = path.join(__dirname, pluginDir);
33
+ let deviceMap = new Map();
34
+ let files = (await fs.readdir(baseDir)).sort();
35
+
36
+ for (const file of files) {
37
+ if (file.endsWith('.js') === false) {
38
+ continue;
39
+ }
40
+
41
+ try {
42
+ let module = await import(url.pathToFileURL(path.join(baseDir, file)).href);
43
+ let exportsToCheck = Object.values(module);
44
+
45
+ for (const exported of exportsToCheck) {
46
+ if (typeof exported !== 'function') {
47
+ continue;
48
+ }
49
+
50
+ let proto = Object.getPrototypeOf(exported);
51
+ while (proto !== undefined && proto.name !== '') {
52
+ if (proto === HomeKitDevice) {
53
+ if (
54
+ typeof exported.TYPE !== 'string' ||
55
+ exported.TYPE === '' ||
56
+ typeof exported.VERSION !== 'string' ||
57
+ exported.VERSION === ''
58
+ ) {
59
+ log?.warn?.('Skipping device module %s (missing TYPE or VERSION)', file);
60
+ break;
61
+ }
62
+
63
+ if (deviceMap.has(exported.TYPE) === false) {
64
+ deviceMap.set(exported.TYPE, exported);
65
+ log?.info?.('Loaded device module "%s" (v%s)', exported.TYPE, exported.VERSION);
66
+ }
67
+
68
+ break;
69
+ }
70
+
71
+ proto = Object.getPrototypeOf(proto);
72
+ }
73
+ }
74
+ } catch (error) {
75
+ log?.warn?.('Failed to load device support file "%s": %s', file, error.message);
76
+ }
77
+ }
78
+
79
+ return deviceMap;
80
+ }
81
+
82
+ function getDeviceHKCategory(type) {
83
+ let category = 1; // Categories.OTHER
84
+
85
+ if (type === DEVICE_TYPE.LOCK) {
86
+ category = 6; // Categories.DOOR_LOCK
87
+ }
88
+
89
+ if (type === DEVICE_TYPE.THERMOSTAT) {
90
+ category = 9; // Categories.THERMOSTAT
91
+ }
92
+
93
+ if (
94
+ type === DEVICE_TYPE.TEMPSENSOR ||
95
+ type === DEVICE_TYPE.HEATLINK ||
96
+ type === DEVICE_TYPE.SMOKESENSOR ||
97
+ type === DEVICE_TYPE.WEATHER
98
+ ) {
99
+ category = 10; // Categories.SENSOR
100
+ }
101
+
102
+ if (type === DEVICE_TYPE.ALARM) {
103
+ category = 11; // Categories.SECURITY_SYSTEM
104
+ }
105
+
106
+ if (type === DEVICE_TYPE.CAMERA || type === DEVICE_TYPE.FLOODLIGHT) {
107
+ category = 17; // Categories.IP_CAMERA
108
+ }
109
+
110
+ if (type === DEVICE_TYPE.DOORBELL) {
111
+ category = 18; // Categories.VIDEO_DOORBELL
112
+ }
113
+
114
+ return category;
115
+ }
116
+
117
+ // Define exports
118
+ export { DEVICE_TYPE, loadDeviceModules, getDeviceHKCategory };
package/dist/index.js CHANGED
@@ -10,13 +10,14 @@
10
10
  // Nest Temp Sensors (1st gen)
11
11
  // Nest Cameras (Cam Indoor, IQ Indoor, Outdoor, IQ Outdoor, Cam with Floodlight)
12
12
  // Nest Doorbells (wired 1st gen)
13
+ // Nest HeatLink
13
14
  //
14
15
  // The accessory supports authentication to Nest/Google using either a Nest account OR Google (migrated Nest account) account.
15
16
  // "preliminary" support for using FieldTest account types also.
16
17
  //
17
18
  // Supports both Nest REST and Protobuf APIs for communication
18
19
  //
19
- // Code version 7/10/2024
20
+ // Code version 2025.06.05
20
21
  // Mark Hulskamp
21
22
  'use strict';
22
23
 
package/dist/nexustalk.js CHANGED
@@ -5,7 +5,7 @@
5
5
  //
6
6
  // Credit to https://github.com/Brandawg93/homebridge-nest-cam for the work on the Nest Camera comms code on which this is based
7
7
  //
8
- // Code version 20/11/2024
8
+ // Code version 2025.06.15
9
9
  // Mark Hulskamp
10
10
  'use strict';
11
11
 
@@ -25,11 +25,11 @@ import { fileURLToPath } from 'node:url';
25
25
  import Streamer from './streamer.js';
26
26
 
27
27
  // Define constants
28
- const PINGINTERVAL = 15000; // Ping interval to nexus server while stream active
29
- const USERAGENT = 'Nest/5.78.0 (iOScom.nestlabs.jasper.release) os=18.0'; // User Agent string
28
+ const PING_INTERVAL = 15000; // Ping interval to nexus server while stream active
29
+ const USER_AGENT = 'Nest/5.78.0 (iOScom.nestlabs.jasper.release) os=18.0'; // User Agent string
30
30
  const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Make a defined for JS __dirname
31
31
 
32
- const PacketType = {
32
+ const PACKET_TYPE = {
33
33
  PING: 1,
34
34
  HELLO: 100,
35
35
  PING_CAMERA: 101,
@@ -56,7 +56,7 @@ const PacketType = {
56
56
  };
57
57
 
58
58
  // Blank audio in AAC format, mono channel @48000
59
- const AACMONO48000BLANK = Buffer.from([
59
+ const AAC_MONO_48000_BLANK = Buffer.from([
60
60
  0xff, 0xf1, 0x4c, 0x40, 0x03, 0x9f, 0xfc, 0xde, 0x02, 0x00, 0x4c, 0x61, 0x76, 0x63, 0x35, 0x39, 0x2e, 0x31, 0x38, 0x2e, 0x31, 0x30, 0x30,
61
61
  0x00, 0x02, 0x30, 0x40, 0x0e,
62
62
  ]);
@@ -68,7 +68,7 @@ export default class NexusTalk extends Streamer {
68
68
  pingTimer = undefined; // Timer object for ping interval
69
69
  stalledTimer = undefined; // Timer object for no received data
70
70
  host = ''; // Host to connect to or connected too
71
- blankAudio = AACMONO48000BLANK;
71
+ blankAudio = AAC_MONO_48000_BLANK;
72
72
  video = {}; // Video stream details once connected
73
73
  audio = {}; // Audio stream details once connected
74
74
 
@@ -123,11 +123,11 @@ export default class NexusTalk extends Streamer {
123
123
  }
124
124
 
125
125
  this.connected = false; // Starting connection
126
- this?.log?.debug && this.log.debug('Connection started to "%s"', host);
126
+ this?.log?.debug?.('Connection started to "%s"', host);
127
127
 
128
128
  this.#socket = tls.connect({ host: host, port: 1443 }, () => {
129
129
  // Opened connection to Nexus server, so now need to authenticate ourselves
130
- this?.log?.debug && this.log.debug('Connection established to "%s"', host);
130
+ this?.log?.debug?.('Connection established to "%s"', host);
131
131
 
132
132
  this.#socket.setKeepAlive(true); // Keep socket connection alive
133
133
  this.host = host; // update internal host name since we've connected
@@ -144,7 +144,7 @@ export default class NexusTalk extends Streamer {
144
144
  });
145
145
 
146
146
  this.#socket.on('close', (hadError) => {
147
- this?.log?.debug && this.log.debug('Connection closed to "%s"', host);
147
+ this?.log?.debug?.('Connection closed to "%s"', host);
148
148
 
149
149
  clearInterval(this.pingTimer);
150
150
  clearTimeout(this.stalledTimer);
@@ -199,7 +199,7 @@ export default class NexusTalk extends Streamer {
199
199
 
200
200
  if (this.host !== deviceData.streaming_host) {
201
201
  this.host = deviceData.streaming_host;
202
- this?.log?.debug && this.log.debug('New host has been requested for connection. Host requested is "%s"', this.host);
202
+ this?.log?.debug?.('New host has been requested for connection. Host requested is "%s"', this.host);
203
203
  }
204
204
 
205
205
  // Let our parent handle the remaining updates
@@ -219,7 +219,7 @@ export default class NexusTalk extends Streamer {
219
219
  sampleRate: 16000,
220
220
  }),
221
221
  ).finish();
222
- this.#sendMessage(PacketType.AUDIO_PAYLOAD, encodedData);
222
+ this.#sendMessage(PACKET_TYPE.AUDIO_PAYLOAD, encodedData);
223
223
  }
224
224
  }
225
225
  }
@@ -248,7 +248,7 @@ export default class NexusTalk extends Streamer {
248
248
  profileNotFoundAction: 'REDIRECT',
249
249
  }),
250
250
  ).finish();
251
- this.#sendMessage(PacketType.START_PLAYBACK, encodedData);
251
+ this.#sendMessage(PACKET_TYPE.START_PLAYBACK, encodedData);
252
252
  }
253
253
  }
254
254
 
@@ -261,13 +261,13 @@ export default class NexusTalk extends Streamer {
261
261
  sessionId: this.#id,
262
262
  }),
263
263
  ).finish();
264
- this.#sendMessage(PacketType.STOP_PLAYBACK, encodedData);
264
+ this.#sendMessage(PACKET_TYPE.STOP_PLAYBACK, encodedData);
265
265
  }
266
266
  }
267
267
  }
268
268
 
269
269
  #sendMessage(type, data) {
270
- if (this.#socket?.readyState !== 'open' || (type !== PacketType.HELLO && this.#authorised === false)) {
270
+ if (this.#socket?.readyState !== 'open' || (type !== PACKET_TYPE.HELLO && this.#authorised === false)) {
271
271
  // We're not connect and/or authorised yet, so 'cache' message for processing once this occurs
272
272
  this.#messages.push({ type: type, data: data });
273
273
  return;
@@ -275,11 +275,11 @@ export default class NexusTalk extends Streamer {
275
275
 
276
276
  // Create nexusTalk message header
277
277
  let header = Buffer.alloc(3);
278
- if (type !== PacketType.LONG_PLAYBACK_PACKET) {
278
+ if (type !== PACKET_TYPE.LONG_PLAYBACK_PACKET) {
279
279
  header.writeUInt8(type, 0);
280
280
  header.writeUInt16BE(data.length, 1);
281
281
  }
282
- if (type === PacketType.LONG_PLAYBACK_PACKET) {
282
+ if (type === PACKET_TYPE.LONG_PLAYBACK_PACKET) {
283
283
  header = Buffer.alloc(5);
284
284
  header.writeUInt8(type, 0);
285
285
  header.writeUInt32BE(data.length, 1);
@@ -308,28 +308,28 @@ export default class NexusTalk extends Streamer {
308
308
 
309
309
  if (reauthorise === true && authoriseRequest !== null) {
310
310
  // Request to re-authorise only
311
- this?.log?.debug && this.log.debug('Re-authentication requested to "%s"', this.host);
312
- this.#sendMessage(PacketType.AUTHORIZE_REQUEST, authoriseRequest);
311
+ this?.log?.debug?.('Re-authentication requested to "%s"', this.host);
312
+ this.#sendMessage(PACKET_TYPE.AUTHORIZE_REQUEST, authoriseRequest);
313
313
  }
314
314
 
315
315
  if (reauthorise === false && authoriseRequest !== null) {
316
316
  // This isn't a re-authorise request, so perform 'Hello' packet
317
317
  let TraitMap = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.Hello');
318
318
  if (TraitMap !== null) {
319
- this?.log?.debug && this.log.debug('Performing authentication to "%s"', this.host);
319
+ this?.log?.debug?.('Performing authentication to "%s"', this.host);
320
320
 
321
321
  let encodedData = TraitMap.encode(
322
322
  TraitMap.fromObject({
323
323
  protocolVersion: 'VERSION_3',
324
324
  uuid: this.uuid.split(/[._]+/)[1],
325
325
  requireConnectedCamera: false,
326
- userAgent: USERAGENT,
326
+ USER_AGENT: USER_AGENT,
327
327
  deviceId: crypto.randomUUID(),
328
328
  clientType: 'IOS',
329
329
  authoriseRequest: authoriseRequest,
330
330
  }),
331
331
  ).finish();
332
- this.#sendMessage(PacketType.HELLO, encodedData);
332
+ this.#sendMessage(PACKET_TYPE.HELLO, encodedData);
333
333
  }
334
334
  }
335
335
  }
@@ -350,7 +350,7 @@ export default class NexusTalk extends Streamer {
350
350
  return;
351
351
  }
352
352
 
353
- this?.log?.debug && this.log.debug('Redirect requested from "%s" to "%s"', this.host, redirectToHost);
353
+ this?.log?.debug?.('Redirect requested from "%s" to "%s"', this.host, redirectToHost);
354
354
 
355
355
  // Setup listener for socket close event. Once socket is closed, we'll perform the redirect
356
356
  this.#socket &&
@@ -389,7 +389,7 @@ export default class NexusTalk extends Streamer {
389
389
  this.#packets = [];
390
390
  this.#messages = [];
391
391
 
392
- this?.log?.debug && this.log.debug('Playback started from "%s" with session ID "%s"', this.host, this.#id);
392
+ this?.log?.debug?.('Playback started from "%s" with session ID "%s"', this.host, this.#id);
393
393
  }
394
394
  }
395
395
 
@@ -403,12 +403,11 @@ export default class NexusTalk extends Streamer {
403
403
  // <-- testing to see how often this occurs first
404
404
  clearTimeout(this.stalledTimer);
405
405
  this.stalledTimer = setTimeout(() => {
406
- this?.log?.debug &&
407
- this.log.debug(
408
- 'We have not received any data from nexus in the past "%s" seconds for uuid "%s". Attempting restart',
409
- 10,
410
- this.uuid,
411
- );
406
+ this?.log?.debug?.(
407
+ 'We have not received any data from nexus in the past "%s" seconds for uuid "%s". Attempting restart',
408
+ 10,
409
+ this.uuid,
410
+ );
412
411
 
413
412
  // Setup listener for socket close event. Once socket is closed, we'll perform the re-connection
414
413
  this.#socket &&
@@ -437,13 +436,12 @@ export default class NexusTalk extends Streamer {
437
436
 
438
437
  if (this.#id !== undefined && decodedMessage.reason === 'USER_ENDED_SESSION') {
439
438
  // Normal playback ended ie: when we stopped playback
440
- this?.log?.debug && this.log.debug('Playback ended on "%s"', this.host);
439
+ this?.log?.debug?.('Playback ended on "%s"', this.host);
441
440
  }
442
441
 
443
442
  if (decodedMessage.reason !== 'USER_ENDED_SESSION') {
444
443
  // Error during playback, so we'll attempt to restart by reconnection to host
445
- this?.log?.debug &&
446
- this.log.debug('Playback ended on "%s" with error "%s". Attempting reconnection', this.host, decodedMessage.reason);
444
+ this?.log?.debug?.('Playback ended on "%s" with error "%s". Attempting reconnection', this.host, decodedMessage.reason);
447
445
 
448
446
  // Setup listener for socket close event. Once socket is closed, we'll perform the re-connection
449
447
  this.#socket &&
@@ -464,7 +462,7 @@ export default class NexusTalk extends Streamer {
464
462
  this.#Authenticate(true); // Update authorisation only
465
463
  } else {
466
464
  // NexusStreamer Error, packet.message contains the message
467
- this?.log?.debug && this.log.debug('Error', decodedMessage.message);
465
+ this?.log?.debug?.('Error', decodedMessage.message);
468
466
  }
469
467
  }
470
468
  }
@@ -474,7 +472,7 @@ export default class NexusTalk extends Streamer {
474
472
  if (typeof payload === 'object' && this.#protobufNexusTalk !== undefined) {
475
473
  //let decodedMessage = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.TalkbackBegin').decode(payload).toJSON();
476
474
  this.audio.talking = true;
477
- this?.log?.debug && this.log.debug('Talking started on uuid "%s"', this.uuid);
475
+ this?.log?.debug?.('Talking started on uuid "%s"', this.uuid);
478
476
  }
479
477
  }
480
478
 
@@ -483,7 +481,7 @@ export default class NexusTalk extends Streamer {
483
481
  if (typeof payload === 'object' && this.#protobufNexusTalk !== undefined) {
484
482
  //let decodedMessage = this.#protobufNexusTalk.lookup('nest.nexustalk.v1.TalkbackEnd').decode(payload).toJSON();
485
483
  this.audio.talking = false;
486
- this?.log?.debug && this.log.debug('Talking ended on uuid "%s"', this.uuid);
484
+ this?.log?.debug?.('Talking ended on uuid "%s"', this.uuid);
487
485
  }
488
486
  }
489
487
 
@@ -496,7 +494,7 @@ export default class NexusTalk extends Streamer {
496
494
  let packetType = this.#packets.readUInt8(0);
497
495
  let packetSize = this.#packets.readUInt16BE(1);
498
496
 
499
- if (packetType === PacketType.LONG_PLAYBACK_PACKET) {
497
+ if (packetType === PACKET_TYPE.LONG_PLAYBACK_PACKET) {
500
498
  headerSize = 5;
501
499
  packetSize = this.#packets.readUInt32BE(1);
502
500
  }
@@ -511,11 +509,11 @@ export default class NexusTalk extends Streamer {
511
509
  this.#packets = this.#packets.subarray(headerSize + packetSize);
512
510
 
513
511
  switch (packetType) {
514
- case PacketType.PING: {
512
+ case PACKET_TYPE.PING: {
515
513
  break;
516
514
  }
517
515
 
518
- case PacketType.OK: {
516
+ case PACKET_TYPE.OK: {
519
517
  // process any pending messages we have stored
520
518
  this.#authorised = true; // OK message, means we're connected and authorised to Nexus
521
519
  for (let message = this.#messages.shift(); message; message = this.#messages.shift()) {
@@ -525,46 +523,46 @@ export default class NexusTalk extends Streamer {
525
523
  // Periodically send PING message to keep stream alive
526
524
  clearInterval(this.pingTimer);
527
525
  this.pingTimer = setInterval(() => {
528
- this.#sendMessage(PacketType.PING, Buffer.alloc(0));
529
- }, PINGINTERVAL);
526
+ this.#sendMessage(PACKET_TYPE.PING, Buffer.alloc(0));
527
+ }, PING_INTERVAL);
530
528
 
531
529
  // Start processing data
532
530
  this.#startNexusData();
533
531
  break;
534
532
  }
535
533
 
536
- case PacketType.ERROR: {
534
+ case PACKET_TYPE.ERROR: {
537
535
  this.#handleNexusError(protoBufPayload);
538
536
  break;
539
537
  }
540
538
 
541
- case PacketType.PLAYBACK_BEGIN: {
539
+ case PACKET_TYPE.PLAYBACK_BEGIN: {
542
540
  this.#handlePlaybackBegin(protoBufPayload);
543
541
  break;
544
542
  }
545
543
 
546
- case PacketType.PLAYBACK_END: {
544
+ case PACKET_TYPE.PLAYBACK_END: {
547
545
  this.#handlePlaybackEnd(protoBufPayload);
548
546
  break;
549
547
  }
550
548
 
551
- case PacketType.PLAYBACK_PACKET:
552
- case PacketType.LONG_PLAYBACK_PACKET: {
549
+ case PACKET_TYPE.PLAYBACK_PACKET:
550
+ case PACKET_TYPE.LONG_PLAYBACK_PACKET: {
553
551
  this.#handlePlaybackPacket(protoBufPayload);
554
552
  break;
555
553
  }
556
554
 
557
- case PacketType.REDIRECT: {
555
+ case PACKET_TYPE.REDIRECT: {
558
556
  this.#handleRedirect(protoBufPayload);
559
557
  break;
560
558
  }
561
559
 
562
- case PacketType.TALKBACK_BEGIN: {
560
+ case PACKET_TYPE.TALKBACK_BEGIN: {
563
561
  this.#handleTalkbackBegin(protoBufPayload);
564
562
  break;
565
563
  }
566
564
 
567
- case PacketType.TALKBACK_END: {
565
+ case PACKET_TYPE.TALKBACK_END: {
568
566
  this.#handleTalkbackEnd(protoBufPayload);
569
567
  break;
570
568
  }