aes70 2.0.17 → 2.0.19

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 CHANGED
@@ -2,6 +2,104 @@
2
2
 
3
3
  All notable changes and version updates will be documented in this file.
4
4
 
5
+ ## [2.0.19] - 2026-03-13
6
+
7
+ - Remove ws dependency. WebSocket connections can use a compatible
8
+ constructor passed to WebSocketConnection.connect() when needed.
9
+
10
+ - ClientConnection: Do not log unsubscribe errors when the connection
11
+ has been closed (CloseError).
12
+
13
+ - TypeScript: Add name property to error class declarations.
14
+
15
+ ## [2.0.18] - 2026-03-11
16
+
17
+ - ClientConnection: Add command duration for UDP. Set expected duration
18
+ on the last command when the device needs time to process.
19
+
20
+ - Connection: Make set_keepalive_interval idempotent when called
21
+ multiple times with the same interval.
22
+
23
+ - printDevice: Add --progress.
24
+
25
+ - fetchDeviceContent: Handle BigInt and OcaStatus.BadMethod.
26
+
27
+ - AbstractUdpConnection: Fix retry logic.
28
+
29
+ ## [2.0.17] - 2025-12-02
30
+
31
+ - Connection: Do not send keepalive after close.
32
+
33
+ - fetchDeviceContent: Handle BigInt data in JSON output.
34
+
35
+ - Refactor UDP command retry handling; batch commands and avoid spurious
36
+ timeouts with large write buffers.
37
+
38
+ ## [2.0.15] - 2025-11-26
39
+
40
+ - Property: Fix and test alias lookup for renamed properties (e.g. 2023).
41
+
42
+ - Refactor UDP support: connection attempt can be aborted with AbortSignal;
43
+ DNS lookup configurable for IPv4 or IPv6.
44
+
45
+ - Events: Do not throw from removeEventHandler (fixes cleanup with
46
+ removeAllEventHandlers).
47
+
48
+ - OCP1/OcaInterval: Fix template class decoder.
49
+
50
+ ## [2.0.9] - 2025-11-25
51
+
52
+ - AbstractUdpConnection: Close socket on failure when device does not
53
+ respond with keepalive in time.
54
+
55
+ ## [2.0.8] - 2025-11-24
56
+
57
+ - RemoteDevice: Fix EV2 detection when implementations return status
58
+ codes other than NotImplemented for new EV2 methods.
59
+
60
+ - wait_for_keepalive: Handle error in unsubscribe.
61
+
62
+ ## [2.0.7] - 2025-11-18
63
+
64
+ - OcaInterval: Fix OCP.1 encoder definition.
65
+
66
+ ## [2.0.6] - 2025-11-12
67
+
68
+ - OcaInterval: Fix duplicate import.
69
+
70
+ ## [2.0.5] - 2025-11-12
71
+
72
+ - TypeScript: Use explicit import paths for moduleResolution=nodenext.
73
+
74
+ - Remove deprecated types from AES70-2018.
75
+
76
+ ## [2.0.4] - 2025-10-24
77
+
78
+ - ClientConnection: Add wait_for_keepalive to wait for a single keepalive
79
+ from the device.
80
+
81
+ - Connection: Warn when keepalive interval is large (common ms vs seconds
82
+ mistake).
83
+
84
+ ## [2.0.3] - 2025-10-09
85
+
86
+ - Fix missing inout parameters in generated OCC model.
87
+
88
+ ## [2.0.2] - 2025-10-07
89
+
90
+ - NotificationError: Fix payload parsing.
91
+
92
+ - BaseEvent: Fix error handling.
93
+
94
+ - PropertySync: Implement aliases so properties are available by alias.
95
+
96
+ ## [2.0.1] - 2025-10-07
97
+
98
+ - PropertySync: Use observeProperty.
99
+
100
+ - Introduce CloseError and TimeoutError classes; emit them on close and
101
+ timeout. Suppress unhandled close error warning.
102
+
5
103
  ## [2.0.0] - 2025-10-07
6
104
 
7
105
  - Updates to AES70-2024
@@ -13,7 +111,7 @@ All notable changes and version updates will be documented in this file.
13
111
 
14
112
  ## [1.6.1] - 2025-05-06
15
113
 
16
- - TCPConnection.connect: Add connecSignal to abort
114
+ - TCPConnection.connect: Add connectSignal to abort
17
115
  connection attempts.
18
116
 
19
117
  - Encode arguments earlier. This means that when calling
package/README.md CHANGED
@@ -96,12 +96,23 @@ NodeJS both TCP and UDP are available in addition to that.
96
96
  port: 65000,
97
97
  });
98
98
 
99
- In a web browser using a WebSocket this looks similar.
99
+ In a web browser using a WebSocket this looks similar (the global
100
+ `WebSocket` is used automatically):
100
101
 
101
102
  const connection = await OCA.WebSocketConnection.connect({
102
103
  url: 'ws://example.org',
103
104
  });
104
105
 
106
+ On Node.js, WebSocket support requires passing the WebSocket constructor
107
+ as the second argument to `connect()`, since there is no built-in
108
+ WebSocket. For example, install the `ws` package and use:
109
+
110
+ import { WebSocket } from 'ws';
111
+ const connection = await WebSocketConnection.connect(
112
+ { url: 'ws://example.org' },
113
+ WebSocket
114
+ );
115
+
105
116
  The next step is to discover what kind of objects the device has. This can be
106
117
  done using the method `RemoteDevice.get_device_tree()` method.
107
118
 
@@ -178,7 +189,7 @@ connection.on('receive', (pdu) => {
178
189
  pendingCommand.name,
179
190
  pendingCommand.get_arguments(),
180
191
  '->',
181
- Types.OcaStatus.getName(pdu.status_code)
192
+ Types.OcaStatus.getName(pdu.status_code),
182
193
  );
183
194
  }
184
195
  } else {
@@ -7,19 +7,25 @@ import { UDPConnection } from '../src/controller/udp_connection.js';
7
7
  import { fetchDeviceContent } from '../src/controller/fetch_device_content.js';
8
8
 
9
9
  function badArguments() {
10
- console.log('Usage: node print_tree.js [--json] [--udp] <ip> <port>');
10
+ console.log(
11
+ 'Usage: node print_tree.js [--json] [--udp] [--progress] <ip> <port>'
12
+ );
11
13
  exit(1);
12
14
  }
13
15
 
14
16
  let jsonMode = false;
15
17
  let useUdp = false;
16
18
  const rest = [];
19
+ let progress = false;
17
20
 
18
21
  argv.slice(2).forEach((option) => {
19
22
  switch (option) {
20
23
  case '--json':
21
24
  jsonMode = true;
22
25
  break;
26
+ case '--progress':
27
+ progress = true;
28
+ break;
23
29
  case '-h':
24
30
  case '--help':
25
31
  badArguments();
@@ -76,8 +82,13 @@ function printTreeJson(content) {
76
82
 
77
83
  async function printDevice(device) {
78
84
  try {
79
- const content = await fetchDeviceContent(device);
80
-
85
+ const reportProgress = progress
86
+ ? (finished, total) => {
87
+ process.stderr.write(`\r${finished} of ${total}`);
88
+ }
89
+ : undefined;
90
+ const content = await fetchDeviceContent(device, reportProgress);
91
+ if (progress) process.stderr.write(`\ndone.\n`);
81
92
  if (jsonMode) {
82
93
  printTreeJson(content);
83
94
  } else {
package/dist/AES70.es5.js CHANGED
@@ -1382,6 +1382,8 @@
1382
1382
 
1383
1383
  const t = seconds * 1000;
1384
1384
 
1385
+ if (this.keepalive_interval === t) return;
1386
+
1385
1387
  this.keepalive_interval = t;
1386
1388
 
1387
1389
  // Notify the other side about our new keepalive
@@ -1816,6 +1818,19 @@
1816
1818
  this.name = name;
1817
1819
  this.lastSent = 0;
1818
1820
  this.retries = 0;
1821
+ this.duration = 0;
1822
+ }
1823
+
1824
+ /**
1825
+ * Sets the expected processing time for this command on the device.
1826
+ * This duration is used when scheduling retries.
1827
+ * Only has an effect for UDP connections.
1828
+ *
1829
+ * @param {number} interval
1830
+ * The interval in milliseconds.
1831
+ */
1832
+ set_duration(interval) {
1833
+ this.duration = interval;
1819
1834
  }
1820
1835
 
1821
1836
  get_arguments() {
@@ -1886,7 +1901,7 @@
1886
1901
  this._scheduledPendingCommands = new Set();
1887
1902
  // All pending commands wich have been sent.
1888
1903
  this._sentPendingCommands = new Set();
1889
- this._nextCommandHandle = 0;
1904
+ this._lastCommandHandle = 0;
1890
1905
  this._subscribers = new Map();
1891
1906
  this._sendCommandsTimer = new Timer(
1892
1907
  () => {
@@ -1934,7 +1949,7 @@
1934
1949
 
1935
1950
  const e = new CloseError(error);
1936
1951
  pendingCommands.forEach((pendingCommand, id) => {
1937
- pendingCommand.handleError(structuredClone(e));
1952
+ pendingCommand.handleError(e);
1938
1953
  });
1939
1954
 
1940
1955
  subscribers.forEach((cb) => {
@@ -1970,8 +1985,8 @@
1970
1985
  }
1971
1986
 
1972
1987
  do {
1973
- handle = this._nextCommandHandle;
1974
- this._nextCommandHandle = (handle + 1) | 0;
1988
+ handle = this._lastCommandHandle;
1989
+ this._lastCommandHandle = (handle + 1) | 0;
1975
1990
  } while (pendingCommands.has(handle));
1976
1991
 
1977
1992
  return handle;
@@ -1993,6 +2008,10 @@
1993
2008
  }
1994
2009
  }
1995
2010
 
2011
+ get_last_pending_command() {
2012
+ return this._pendingCommands.get(this._lastCommandHandle);
2013
+ }
2014
+
1996
2015
  send_command(command, returnTypes, callback, stack, name) {
1997
2016
  const executor = (resolve, reject) => {
1998
2017
  const handle = this._getNextCommandHandle();
@@ -28710,7 +28729,8 @@
28710
28729
  } else if (S.version > 0 && !S.has_subscribers()) {
28711
28730
  dropSubscribers();
28712
28731
  this._doUnsubscribe(S, event).catch((error) => {
28713
- console.error('Unsubscribe failed: ', error);
28732
+ if (error.name === 'aes70.CloseError') return;
28733
+ console.error('Unsubscribe failed: %o', error);
28714
28734
  });
28715
28735
  }
28716
28736
  };
@@ -29173,6 +29193,13 @@
29173
29193
 
29174
29194
  _sentPendingCommands.delete(pendingCommand);
29175
29195
 
29196
+ if (pendingCommand.lastSent + pendingCommand.duration > retryTime) {
29197
+ // This command is expected to take longer. Simply push it back to the queue,
29198
+ // we will pick it up later.
29199
+ _sentPendingCommands.add(pendingCommand);
29200
+ continue;
29201
+ }
29202
+
29176
29203
  if (pendingCommand.retries >= this.retry_count) {
29177
29204
  failed.push(pendingCommand);
29178
29205
  } else {
@@ -29346,9 +29373,9 @@
29346
29373
  if (typeof value === 'object') {
29347
29374
  if (value instanceof Arguments) {
29348
29375
  return {
29349
- [name]: value.item(0),
29350
- ['Min' + name]: value.item(1),
29351
- ['Max' + name]: value.item(2),
29376
+ [name]: formatValue(value.item(0)),
29377
+ ['Min' + name]: formatValue(value.item(1)),
29378
+ ['Max' + name]: formatValue(value.item(2)),
29352
29379
  };
29353
29380
  } else {
29354
29381
  value = formatValue(value);
@@ -29356,7 +29383,7 @@
29356
29383
  }
29357
29384
 
29358
29385
  return {
29359
- [name]: value,
29386
+ [name]: formatValue(value),
29360
29387
  };
29361
29388
  }
29362
29389
 
@@ -29388,7 +29415,11 @@
29388
29415
 
29389
29416
  Object.assign(info, formatReturnValue(name, currentValue));
29390
29417
  } catch (err) {
29391
- if (err.status != 8)
29418
+ // 8 - NotImplemented
29419
+ // 11 - BadMethod
29420
+ // Note: Some implementations respond with BadMethod for methods which have been
29421
+ // defined in more recent vrsions of aes70
29422
+ if (err.status != 8 && err.status != 11)
29392
29423
  console.error(
29393
29424
  'Fetching property',
29394
29425
  o.ClassName,
@@ -29416,10 +29447,13 @@
29416
29447
  }
29417
29448
  }
29418
29449
 
29419
- async function fetchDeviceContentRec(objects) {
29450
+ async function fetchDeviceContentRec(objects, reportProgress) {
29420
29451
  const result = [];
29421
29452
 
29422
29453
  for (let i = 0; i < objects.length; i++) {
29454
+ if (reportProgress) {
29455
+ reportProgress(i, objects.length);
29456
+ }
29423
29457
  const o = objects[i];
29424
29458
  const info = await fetchObjectInfo(o);
29425
29459
 
@@ -29438,7 +29472,7 @@
29438
29472
  return result;
29439
29473
  }
29440
29474
 
29441
- async function fetchDeviceContent(device) {
29475
+ async function fetchDeviceContent(device, reportProgress) {
29442
29476
  const objects = await device.GetDeviceTree();
29443
29477
  const managers = [
29444
29478
  device.DeviceManager,
@@ -29460,7 +29494,7 @@
29460
29494
  if (await managerExists(manager)) objects.push(manager);
29461
29495
  }
29462
29496
 
29463
- return await fetchDeviceContentRec(objects);
29497
+ return await fetchDeviceContentRec(objects, reportProgress);
29464
29498
  }
29465
29499
 
29466
29500
  /*
@@ -30670,21 +30704,32 @@
30670
30704
  }
30671
30705
  }
30672
30706
 
30707
+ const globalWebSocket =
30708
+ typeof globalThis !== 'undefined' ? globalThis.WebSocket : undefined;
30709
+
30673
30710
  /**
30674
30711
  * Connection which implements OCP.1 with WebSocket transport.
30712
+ * Works in both browser and Node.js. In the browser the global WebSocket
30713
+ * is used when not passed; on Node.js the WebSocket constructor must be
30714
+ * passed as the second argument (e.g. from the 'ws' package).
30675
30715
  */
30676
30716
  class WebSocketConnection extends WebSocketConnectionBase {
30677
30717
  /**
30678
30718
  * Connect to the given endpoint.
30679
30719
  *
30680
30720
  * @param {object} options
30681
- * @param {String} options.url
30682
- * Endpoint WebSocket url.
30683
- * @returns {Promise<WebSocketConnection>}
30684
- * The connection.
30721
+ * @param {String} options.url - Endpoint WebSocket url.
30722
+ * @param {Function} [WebSocket] - WebSocket constructor. Optional in browser (global WebSocket is used). Required on Node.js (e.g. import WebSocket from 'ws').
30723
+ * @returns {Promise<WebSocketConnection>} - The connection.
30685
30724
  */
30686
- static connect(options) {
30687
- return super.connect(WebSocket, options);
30725
+ static connect(options, WebSocket) {
30726
+ const Ctor = WebSocket !== undefined ? WebSocket : globalWebSocket;
30727
+ if (!Ctor) {
30728
+ throw new Error(
30729
+ 'WebSocket constructor is required in this environment. Pass the WebSocket class as the second argument (e.g. from the "ws" package on Node.js).'
30730
+ );
30731
+ }
30732
+ return super.connect(Ctor, options);
30688
30733
  }
30689
30734
 
30690
30735
  _now() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aes70",
3
- "version": "2.0.17",
3
+ "version": "2.0.19",
4
4
  "description": "A controller library for the AES70 protocol.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -41,9 +41,6 @@
41
41
  "url": "https://github.com/DeutscheSoft/AES70.js/issues"
42
42
  },
43
43
  "homepage": "https://github.com/DeutscheSoft/AES70.js#readme",
44
- "optionalDependencies": {
45
- "ws": "^5.2.4"
46
- },
47
44
  "sideEffects": [
48
45
  "src/bundle.browser.js",
49
46
  "dist/AES70.es5.js"
@@ -1,4 +1,5 @@
1
1
  export declare class CloseError extends Error {
2
- error?: Error;
2
+ readonly error?: Error;
3
+ readonly name: 'aes70.CloseError';
3
4
  constructor(error?: Error);
4
5
  }
package/src/connection.js CHANGED
@@ -219,6 +219,8 @@ export class Connection extends Events {
219
219
 
220
220
  const t = seconds * 1000;
221
221
 
222
+ if (this.keepalive_interval === t) return;
223
+
222
224
  this.keepalive_interval = t;
223
225
 
224
226
  // Notify the other side about our new keepalive
@@ -192,6 +192,13 @@ export class AbstractUDPConnection extends ClientConnection {
192
192
 
193
193
  _sentPendingCommands.delete(pendingCommand);
194
194
 
195
+ if (pendingCommand.lastSent + pendingCommand.duration > retryTime) {
196
+ // This command is expected to take longer. Simply push it back to the queue,
197
+ // we will pick it up later.
198
+ _sentPendingCommands.add(pendingCommand);
199
+ continue;
200
+ }
201
+
195
202
  if (pendingCommand.retries >= this.retry_count) {
196
203
  failed.push(pendingCommand);
197
204
  } else {
@@ -25,7 +25,17 @@ export interface PendingCommand {
25
25
  * If available, returns the arguments of this
26
26
  * remote method call.
27
27
  */
28
- get_arguments(): unknwon[] | undefined;
28
+ get_arguments(): unknown[] | undefined;
29
+
30
+ /**
31
+ * Sets the expected processing time for this command on the device.
32
+ * This duration is used when scheduling retries.
33
+ * Only has an effect for UDP connections.
34
+ *
35
+ * @param {number} interval
36
+ * The interval in milliseconds.
37
+ */
38
+ set_duration(interval: number): void;
29
39
  }
30
40
 
31
41
  /**
@@ -42,4 +52,6 @@ export declare class ClientConnection extends Connection {
42
52
  * Keepalive interval in seconds.
43
53
  */
44
54
  wait_for_keepalive(interval: number): Promise<void>;
55
+
56
+ get_last_pending_command(): PendingCommand | undefined;
45
57
  }
@@ -27,6 +27,19 @@ class PendingCommand {
27
27
  this.name = name;
28
28
  this.lastSent = 0;
29
29
  this.retries = 0;
30
+ this.duration = 0;
31
+ }
32
+
33
+ /**
34
+ * Sets the expected processing time for this command on the device.
35
+ * This duration is used when scheduling retries.
36
+ * Only has an effect for UDP connections.
37
+ *
38
+ * @param {number} interval
39
+ * The interval in milliseconds.
40
+ */
41
+ set_duration(interval) {
42
+ this.duration = interval;
30
43
  }
31
44
 
32
45
  get_arguments() {
@@ -97,7 +110,7 @@ export class ClientConnection extends Connection {
97
110
  this._scheduledPendingCommands = new Set();
98
111
  // All pending commands wich have been sent.
99
112
  this._sentPendingCommands = new Set();
100
- this._nextCommandHandle = 0;
113
+ this._lastCommandHandle = 0;
101
114
  this._subscribers = new Map();
102
115
  this._sendCommandsTimer = new Timer(
103
116
  () => {
@@ -145,7 +158,7 @@ export class ClientConnection extends Connection {
145
158
 
146
159
  const e = new CloseError(error);
147
160
  pendingCommands.forEach((pendingCommand, id) => {
148
- pendingCommand.handleError(structuredClone(e));
161
+ pendingCommand.handleError(e);
149
162
  });
150
163
 
151
164
  subscribers.forEach((cb) => {
@@ -181,8 +194,8 @@ export class ClientConnection extends Connection {
181
194
  }
182
195
 
183
196
  do {
184
- handle = this._nextCommandHandle;
185
- this._nextCommandHandle = (handle + 1) | 0;
197
+ handle = this._lastCommandHandle;
198
+ this._lastCommandHandle = (handle + 1) | 0;
186
199
  } while (pendingCommands.has(handle));
187
200
 
188
201
  return handle;
@@ -204,6 +217,10 @@ export class ClientConnection extends Connection {
204
217
  }
205
218
  }
206
219
 
220
+ get_last_pending_command() {
221
+ return this._pendingCommands.get(this._lastCommandHandle);
222
+ }
223
+
207
224
  send_command(command, returnTypes, callback, stack, name) {
208
225
  const executor = (resolve, reject) => {
209
226
  const handle = this._getNextCommandHandle();
@@ -37,9 +37,9 @@ function formatReturnValue(name, value) {
37
37
  if (typeof value === 'object') {
38
38
  if (value instanceof Arguments) {
39
39
  return {
40
- [name]: value.item(0),
41
- ['Min' + name]: value.item(1),
42
- ['Max' + name]: value.item(2),
40
+ [name]: formatValue(value.item(0)),
41
+ ['Min' + name]: formatValue(value.item(1)),
42
+ ['Max' + name]: formatValue(value.item(2)),
43
43
  };
44
44
  } else {
45
45
  value = formatValue(value);
@@ -47,7 +47,7 @@ function formatReturnValue(name, value) {
47
47
  }
48
48
 
49
49
  return {
50
- [name]: value,
50
+ [name]: formatValue(value),
51
51
  };
52
52
  }
53
53
 
@@ -79,7 +79,11 @@ async function fetchObjectInfo(o) {
79
79
 
80
80
  Object.assign(info, formatReturnValue(name, currentValue));
81
81
  } catch (err) {
82
- if (err.status != 8)
82
+ // 8 - NotImplemented
83
+ // 11 - BadMethod
84
+ // Note: Some implementations respond with BadMethod for methods which have been
85
+ // defined in more recent vrsions of aes70
86
+ if (err.status != 8 && err.status != 11)
83
87
  console.error(
84
88
  'Fetching property',
85
89
  o.ClassName,
@@ -107,10 +111,13 @@ async function managerExists(manager) {
107
111
  }
108
112
  }
109
113
 
110
- async function fetchDeviceContentRec(objects) {
114
+ async function fetchDeviceContentRec(objects, reportProgress) {
111
115
  const result = [];
112
116
 
113
117
  for (let i = 0; i < objects.length; i++) {
118
+ if (reportProgress) {
119
+ reportProgress(i, objects.length);
120
+ }
114
121
  const o = objects[i];
115
122
  const info = await fetchObjectInfo(o);
116
123
 
@@ -129,7 +136,7 @@ async function fetchDeviceContentRec(objects) {
129
136
  return result;
130
137
  }
131
138
 
132
- export async function fetchDeviceContent(device) {
139
+ export async function fetchDeviceContent(device, reportProgress) {
133
140
  const objects = await device.GetDeviceTree();
134
141
  const managers = [
135
142
  device.DeviceManager,
@@ -151,5 +158,5 @@ export async function fetchDeviceContent(device) {
151
158
  if (await managerExists(manager)) objects.push(manager);
152
159
  }
153
160
 
154
- return await fetchDeviceContentRec(objects);
161
+ return await fetchDeviceContentRec(objects, reportProgress);
155
162
  }
@@ -1,4 +1,4 @@
1
- import { warn, error } from '../log.js';
1
+ import { warn } from '../log.js';
2
2
 
3
3
  import { Events } from '../events.js';
4
4
 
@@ -17,13 +17,13 @@ import { OcaCodingManager } from './ControlClasses/OcaCodingManager.js';
17
17
  import { OcaDiagnosticManager } from './ControlClasses/OcaDiagnosticManager.js';
18
18
  import { OcaBlock } from './ControlClasses/OcaBlock.js';
19
19
  import { RemoteError } from './remote_error.js';
20
- import { OcaStatus } from '../types/OcaStatus.js';
21
20
  import tree_to_rolemap from './tree_to_rolemap.js';
22
21
 
23
22
  import * as RemoteControlClasses from './ControlClasses.js';
24
23
 
25
24
  import { OcaManagerDefaultObjectNumbers } from '../types/OcaManagerDefaultObjectNumbers.js';
26
25
  import { OcaNotificationDeliveryMode } from '../types/OcaNotificationDeliveryMode.js';
26
+ import { CloseError } from '../close_error.js';
27
27
 
28
28
  const emptyUint8Array = new Uint8Array(0);
29
29
 
@@ -314,7 +314,8 @@ export class RemoteDevice extends Events {
314
314
  } else if (S.version > 0 && !S.has_subscribers()) {
315
315
  dropSubscribers();
316
316
  this._doUnsubscribe(S, event).catch((error) => {
317
- console.error('Unsubscribe failed: ', error);
317
+ if (error.name === 'aes70.CloseError') return;
318
+ console.error('Unsubscribe failed: %o', error);
318
319
  });
319
320
  }
320
321
  };
@@ -1,17 +1,20 @@
1
1
  import {
2
2
  WebSocketConnectionBase,
3
3
  IWebSocketConnectionBaseOptions,
4
+ IWebSocketLike,
5
+ WebSocketConstructor,
4
6
  } from './websocket_connection_base.js';
5
7
 
6
8
  export type IWebSocketConnectionOptions = IWebSocketConnectionBaseOptions;
9
+ export type { IWebSocketLike, WebSocketConstructor };
7
10
 
8
11
  export class WebSocketConnection extends WebSocketConnectionBase {
9
- constructor(ws: WebSocket, options: WebSocketConnectionOptions);
10
- static connectWebSocket(
11
- WebSocket: typeof WebSocket,
12
- options: WebSocketConnectionOptions
13
- ): Promise<WebSocket>;
12
+ constructor(ws: IWebSocketLike, options: IWebSocketConnectionOptions);
14
13
  static connect(
15
- options: WebSocketConnectionOptions
14
+ options: IWebSocketConnectionOptions
15
+ ): Promise<WebSocketConnection>;
16
+ static connect(
17
+ options: IWebSocketConnectionOptions,
18
+ WebSocket: WebSocketConstructor
16
19
  ): Promise<WebSocketConnection>;
17
20
  }
@@ -1,20 +1,31 @@
1
1
  import { WebSocketConnectionBase } from './websocket_connection_base.js';
2
2
 
3
+ const globalWebSocket =
4
+ typeof globalThis !== 'undefined' ? globalThis.WebSocket : undefined;
5
+
3
6
  /**
4
7
  * Connection which implements OCP.1 with WebSocket transport.
8
+ * Works in both browser and Node.js. In the browser the global WebSocket
9
+ * is used when not passed; on Node.js the WebSocket constructor must be
10
+ * passed as the second argument (e.g. from the 'ws' package).
5
11
  */
6
12
  export class WebSocketConnection extends WebSocketConnectionBase {
7
13
  /**
8
14
  * Connect to the given endpoint.
9
15
  *
10
16
  * @param {object} options
11
- * @param {String} options.url
12
- * Endpoint WebSocket url.
13
- * @returns {Promise<WebSocketConnection>}
14
- * The connection.
17
+ * @param {String} options.url - Endpoint WebSocket url.
18
+ * @param {Function} [WebSocket] - WebSocket constructor. Optional in browser (global WebSocket is used). Required on Node.js (e.g. import WebSocket from 'ws').
19
+ * @returns {Promise<WebSocketConnection>} - The connection.
15
20
  */
16
- static connect(options) {
17
- return super.connect(WebSocket, options);
21
+ static connect(options, WebSocket) {
22
+ const Ctor = WebSocket !== undefined ? WebSocket : globalWebSocket;
23
+ if (!Ctor) {
24
+ throw new Error(
25
+ 'WebSocket constructor is required in this environment. Pass the WebSocket class as the second argument (e.g. from the "ws" package on Node.js).'
26
+ );
27
+ }
28
+ return super.connect(Ctor, options);
18
29
  }
19
30
 
20
31
  _now() {
@@ -3,6 +3,27 @@ import {
3
3
  ClientConnection,
4
4
  } from './client_connection.js';
5
5
 
6
+ /**
7
+ * Minimal WebSocket-like interface supported by both browser WebSocket
8
+ * and the Node.js 'ws' library. Used for typing connection options and
9
+ * the optional WebSocket constructor passed to connect().
10
+ */
11
+ export interface IWebSocketLike {
12
+ binaryType: string;
13
+ addEventListener(
14
+ type: string,
15
+ listener: (ev: { data?: ArrayBuffer | Buffer; type?: string }) => void
16
+ ): void;
17
+ removeEventListener(
18
+ type: string,
19
+ listener: (ev: { data?: ArrayBuffer | Buffer; type?: string }) => void
20
+ ): void;
21
+ send(data: ArrayBuffer | Buffer | ArrayBufferView): void;
22
+ close(): void;
23
+ }
24
+
25
+ export type WebSocketConstructor = new (url: string) => IWebSocketLike;
26
+
6
27
  export interface IWebSocketConnectionBaseOptions
7
28
  extends IClientConnectionOptions {
8
29
  url: string;
@@ -14,5 +35,6 @@ export interface IWebSocketConnectionBaseOptions
14
35
  * Browser.
15
36
  */
16
37
  export class WebSocketConnectionBase extends ClientConnection {
38
+ constructor(ws: IWebSocketLike, options: IWebSocketConnectionBaseOptions);
17
39
  write(buf: ArrayBuffer): void;
18
40
  }
package/src/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from './index.default.js';
2
- export * from './controller/websocket_connection_node.js';
2
+ export * from './controller/websocket_connection.js';
3
3
  export * from './controller/tcp_connection.js';
4
4
  export * from './controller/udp_connection.js';
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from './index.default.js';
2
- export * from './controller/websocket_connection_node.js';
2
+ export * from './controller/websocket_connection.js';
3
3
  export * from './controller/tcp_connection.js';
4
4
  export * from './controller/udp_connection.js';
@@ -1,3 +1,4 @@
1
1
  export declare class TimeoutError extends Error {
2
+ readonly name: 'aes70.TimeoutError';
2
3
  constructor();
3
4
  }
@@ -1,18 +0,0 @@
1
- import {
2
- WebSocketConnectionBase,
3
- IWebSocketConnectionBaseOptions,
4
- } from './websocket_connection_base.js';
5
- import WebSocket from 'ws';
6
-
7
- export type IWebSocketConnectionOptions = IWebSocketConnectionBaseOptions;
8
-
9
- export class WebSocketConnection extends WebSocketConnectionBase {
10
- constructor(ws: WebSocket, options: WebSocketConnectionOptions);
11
- static connectWebSocket(
12
- WebSocket: typeof WebSocket,
13
- options: WebSocketConnectionOptions
14
- ): Promise<WebSocket>;
15
- static connect(
16
- options: WebSocketConnectionOptions
17
- ): Promise<WebSocketConnection>;
18
- }
@@ -1,24 +0,0 @@
1
- /* eslint-env node */
2
-
3
- import WebSocket from 'ws';
4
- import { performance } from 'perf_hooks';
5
- import { WebSocketConnectionBase } from './websocket_connection_base.js';
6
-
7
- /**
8
- * Connection which implements OCP.1 with WebSocket transport.
9
- */
10
- export class WebSocketConnection extends WebSocketConnectionBase {
11
- /**
12
- * Connect to the given endpoint.
13
- *
14
- * @param {String} options.url - Endpoint WebSocket url.
15
- * @returns {Promise<WebSocketConnection>} - The connection.
16
- */
17
- static async connect(options) {
18
- return super.connect(WebSocket, options);
19
- }
20
-
21
- _now() {
22
- return performance.now();
23
- }
24
- }