senza-sdk 4.4.5 → 4.4.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "senza-sdk",
3
- "version": "4.4.5",
3
+ "version": "4.4.7",
4
4
  "main": "./src/api.js",
5
5
  "description": "API for Senza application",
6
6
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  "eslint-plugin-jest": "^28.11.0",
35
35
  "globals": "^16.0.0",
36
36
  "jest": "^30.2.0",
37
- "jest-environment-jsdom" : "^30.2.0",
37
+ "jest-environment-jsdom": "^30.2.0",
38
38
  "jsdoc-to-markdown": "^7.1.1",
39
39
  "webpack": "^5.72.1",
40
40
  "webpack-cli": "^5.1.4"
@@ -1,4 +1,4 @@
1
- import { DeviceManager as DeviceManagerInterface } from "../interface/deviceManager";
1
+ import { DeviceManager as DeviceManagerInterface} from "../interface/deviceManager";
2
2
  import { getFCID, sdkLogger, getRestResponse } from "./utils";
3
3
  import { sessionInfo } from "./SessionInfo";
4
4
 
@@ -9,6 +9,186 @@ let wifi_status_last_update = 0;
9
9
  const WIFI_STATUS_CACHE_SECONDS = 5;
10
10
  const FACTORY_RESET_TIMEOUT_SECONDS = 5;
11
11
 
12
+ const byteSize = str => new Blob([str]).size;
13
+
14
+ /**
15
+ * MessageChannel is not extended from the interface as its objects are only instantiated internally.
16
+ * The implementation here needs to match the interface definition which exists purely for documentation.
17
+ */
18
+ class MessageChannel extends EventTarget {
19
+ constructor(deviceManager, channelType) {
20
+ super();
21
+ switch (channelType) {
22
+ case deviceManager.DeviceMessageChannelType.SZE_HOST_APP:
23
+ this._type = deviceManager.DeviceMessageChannelType.SZE_HOST_APP;
24
+ this._description = "Message channel for bidirectional communication with the Senza Embedded device's host application";
25
+ this._capabilities = {
26
+ send: true,
27
+ receive: true,
28
+ dataTypes: ["string"],
29
+ maxMessageSize: 60 * 1024 // 60KB - to account for SCTP default max message size of 64Kb minus the wrapping object overhead
30
+ };
31
+ break;
32
+ case deviceManager.DeviceMessageChannelType.USB_SERIAL:
33
+ this._type = deviceManager.DeviceMessageChannelType.USB_SERIAL;
34
+ this._description = "Message channel for sending messages to the device's USB serial interface";
35
+ this._capabilities = {
36
+ send: true,
37
+ receive: false,
38
+ dataTypes: ["string"],
39
+ maxMessageSize: 60 * 1024 // 60KB
40
+ };
41
+ break;
42
+ default:
43
+ throw new Error("Invalid channel type");
44
+ }
45
+
46
+ this._open = true;
47
+ this._manager = deviceManager;
48
+ }
49
+
50
+ /**
51
+ * Calls EventTarget's addEventListener after checking if channel is open
52
+ */
53
+ addEventListener(type, callback) {
54
+ if (!this._open) throw new Error("Channel is closed");
55
+ EventTarget.prototype.addEventListener.call(this, type, callback);
56
+ }
57
+
58
+ /**
59
+ * Calls EventTarget's removeEventListener after checking if channel is open
60
+ */
61
+ removeEventListener(type, callback) {
62
+ if (!this._open) throw new Error("Channel is closed");
63
+ EventTarget.prototype.removeEventListener.call(this, type, callback);
64
+ }
65
+
66
+ /**
67
+ * Marks channel as closed and not usable anymore. Also removes it from the DeviceManager's createdChannels map.
68
+ */
69
+ dispose() {
70
+ if (!this._open) throw new Error("Channel is closed");
71
+ this._open = false;
72
+ if (this._manager && typeof this._manager._removeChannel === "function") {
73
+ this._manager._removeChannel(this._type);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Get the type of this message channel.
79
+ * @returns {DeviceMessageChannelType} The channel type
80
+ */
81
+ get type() {
82
+ if (!this._open) throw new Error("Channel is closed");
83
+ return this._type;
84
+ }
85
+
86
+ /**
87
+ * Get the description of this message channel.
88
+ * @returns {string} The channel description
89
+ */
90
+ get description() {
91
+ if (!this._open) throw new Error("Channel is closed");
92
+ return this._description;
93
+ }
94
+
95
+ /**
96
+ * Get the capabilities of this message channel.
97
+ * @returns {Object} The capabilities object containing send/receive flags, supported data types, etc.
98
+ */
99
+ get capabilities() {
100
+ if (!this._open) throw new Error("Channel is closed");
101
+ return this._capabilities;
102
+ }
103
+
104
+ /**
105
+ * @private
106
+ * Performs checks to see if a message can be sent through this channel.
107
+ * @param {*} message The message to check
108
+ */
109
+ _checkMessageBeforeSend(message) {
110
+ if (!this._open) throw new Error("Channel is closed");
111
+ if (typeof window === "undefined" || typeof window.cefQuery !== "function") {
112
+ throw new Error("cefQuery is not available");
113
+ }
114
+ if (typeof message !== "string" && !(message instanceof String)) {
115
+ throw new Error("message must be a string");
116
+ }
117
+ // Enforce maxMessageSize limit
118
+ const maxSize = this.capabilities.maxMessageSize;
119
+ const messageSize = byteSize(message);
120
+ if (messageSize > maxSize) {
121
+ throw new Error(`Message size (${messageSize} bytes) exceeds maximum allowed size (${maxSize} bytes)`);
122
+ }
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Senza Embedded Host App Message Channel
128
+ */
129
+ class SzeHostAppMessageChannel extends MessageChannel {
130
+ _docListener = (e) => {
131
+ const logger = sdkLogger.withFields({ "FCID": e?.detail?.fcid });
132
+ logger.log("Got hs/deviceMsgSzeHostApp", JSON.stringify(e?.detail));
133
+
134
+ const event = new Event("message");
135
+ event.message = e?.detail?.message || "";
136
+ this.dispatchEvent(event);
137
+ };
138
+
139
+ constructor(deviceManager) {
140
+ super(deviceManager, deviceManager.DeviceMessageChannelType.SZE_HOST_APP);
141
+ // Listen for device host app to web app messages and forward to EventTarget listeners
142
+ if (typeof document !== "undefined") {
143
+ document.addEventListener("hs/deviceMsgSzeHostApp", this._docListener);
144
+ }
145
+ }
146
+
147
+ async sendMessage(message) {
148
+ this._checkMessageBeforeSend(message);
149
+ const tc_message = { type: "deviceMsgSzeHostApp", message: message };
150
+ const request = {
151
+ target: "TC",
152
+ waitForResponse: false,
153
+ message: JSON.stringify(tc_message)
154
+ };
155
+ return new Promise((resolve, reject) => {
156
+ window.cefQuery({
157
+ request: JSON.stringify(request),
158
+ persistent: false,
159
+ onSuccess: () => {
160
+ resolve();
161
+ },
162
+ onFailure: (code, msg) => {
163
+ reject(new Error(`Request failed: ${code} ${msg}`));
164
+ }
165
+ });
166
+ });
167
+ }
168
+
169
+ dispose() {
170
+ if (typeof document !== "undefined") {
171
+ document.removeEventListener("hs/deviceMsgSzeHostApp", this._docListener);
172
+ }
173
+ super.dispose();
174
+ }
175
+ }
176
+
177
+ /**
178
+ * USB Serial Message Channel implementation
179
+ */
180
+ class UsbSerialMessageChannel extends MessageChannel {
181
+ constructor(deviceManager) {
182
+ super(deviceManager, deviceManager.DeviceMessageChannelType.USB_SERIAL);
183
+ }
184
+
185
+ async sendMessage(message) {
186
+ this._checkMessageBeforeSend(message);
187
+ // Use the existing sendDataToDevice functionality
188
+ return getDeviceManagerInstance().sendDataToDevice(message);
189
+ }
190
+ }
191
+
12
192
  async function getWifiApData() {
13
193
  // Wi-Fi access point data is static, so it needs to be retrieved only once
14
194
  if (!wifi_ap_data) {
@@ -38,6 +218,13 @@ class DeviceManager extends DeviceManagerInterface {
38
218
 
39
219
  constructor() {
40
220
  super();
221
+ this.availableMessageChannels = [
222
+ // Future enhancement: add available channels based on cfg or some device info
223
+ this.DeviceMessageChannelType.SZE_HOST_APP,
224
+ this.DeviceMessageChannelType.USB_SERIAL
225
+ ];
226
+ // Track created channels in a map to enforce one-per-type limit, i.e. only one SZE_HOST_APP channel at a time
227
+ this.createdChannels = new Map();
41
228
  }
42
229
 
43
230
  get deviceInfo() {
@@ -202,10 +389,67 @@ class DeviceManager extends DeviceManagerInterface {
202
389
  await Promise.all([getWifiApData(), getWifiStatus()]);
203
390
  return { ...wifi_ap_data, ...wifi_status };
204
391
  }
392
+
393
+ /**
394
+ * Returns a list of available message channels for the device.
395
+ * @returns {DeviceMessageChannelType[]} An array of DeviceMessageChannelType values representing the available message channels.
396
+ */
397
+ getAvailableMessageChannels() {
398
+ return this.availableMessageChannels || [];
399
+ }
400
+
401
+ /**
402
+ * Create a message channel of the specified type.
403
+ * Only one channel of each type can be created at a time.
404
+ * @param {DeviceMessageChannelType} type The type of message channel to create.
405
+ * @returns {MessageChannel} A MessageChannel object representing the created message channel.
406
+ * @throws {Error} If the specified type is not supported or already exists.
407
+ */
408
+ createMessageChannel(type) {
409
+ const channelType = this.availableMessageChannels.find(c => c === type);
410
+ const availableTypes = this.availableMessageChannels.join(", ");
411
+ if (!channelType) {
412
+ throw new Error(`Unsupported channel type: ${type}. Available types: ${availableTypes}`);
413
+ }
414
+ // Check if a channel of this type already exists
415
+ if (this.createdChannels.has(type)) {
416
+ throw new Error(`A message channel of type '${type}' already exists. Only one channel per type is allowed.`);
417
+ }
418
+ // Create channel implementation based on type
419
+ let channel;
420
+ if (type === this.DeviceMessageChannelType.SZE_HOST_APP) {
421
+ channel = new SzeHostAppMessageChannel(this);
422
+ } else if (type === this.DeviceMessageChannelType.USB_SERIAL) {
423
+ channel = new UsbSerialMessageChannel(this);
424
+ }
425
+ // else channel type is unsupported, but is already checked above
426
+
427
+ this.createdChannels.set(type, channel);
428
+ return channel;
429
+ }
430
+
431
+ /**
432
+ * Remove a message channel of the specified type.
433
+ * @private
434
+ * @param {DeviceMessageChannelType} type The type of message channel to remove.
435
+ */
436
+ _removeChannel(type) {
437
+ if (!this.createdChannels || !this.createdChannels.has(type)) {
438
+ throw new Error(`No channel of type '${type}' exists.`);
439
+ }
440
+ this.createdChannels.delete(type);
441
+ }
205
442
  }
206
443
 
207
- /**
444
+ // Create the singleton instance
445
+ const deviceManagerInstance = new DeviceManager();
446
+
447
+ // Function to get the singleton instance (for forward reference)
448
+ function getDeviceManagerInstance() {
449
+ return deviceManagerInstance;
450
+ }
208
451
 
452
+ /**
209
453
  * @module
210
454
  * @example
211
455
  * import { deviceManager } from "senza-sdk";
@@ -214,6 +458,13 @@ class DeviceManager extends DeviceManagerInterface {
214
458
  * await deviceManager.clearWifi();
215
459
  * deviceManager.reboot();
216
460
  *
461
+ * // Message channels
462
+ * const channels = deviceManager.getAvailableMessageChannels();
463
+ * const channel = deviceManager.createMessageChannel(deviceManager.DeviceMessageChannelType.SZE_HOST_APP);
464
+ * channel.addEventListener('message', (event) => {
465
+ * console.log(`Received: ${event.message}`);
466
+ * });
467
+ *
217
468
  * @return {DeviceManager} pointer to the DeviceManager singleton
218
469
  */
219
- export const deviceManager = new DeviceManager();
470
+ export const deviceManager = deviceManagerInstance;