aes70 2.0.14 → 2.0.15

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/dist/AES70.es5.js CHANGED
@@ -31,12 +31,13 @@
31
31
  class Events {
32
32
  constructor() {
33
33
  this.event_handlers = new Map();
34
+ this.event_handlers_cleared = false;
34
35
  }
35
36
 
36
37
  /**
37
38
  * Emit an event.
38
39
  *
39
- * @param {String} name - Name of the event.
40
+ * @param {string} name - Name of the event.
40
41
  * @param {...*} args - Extra arguments.
41
42
  */
42
43
  emit(name) {
@@ -57,7 +58,7 @@
57
58
  /**
58
59
  * Subscribe to an event.
59
60
  *
60
- * @param {String} name - Name of the event.
61
+ * @param {string} name - Name of the event.
61
62
  * @param {Function} cb - Callback function.
62
63
  */
63
64
  on(name, cb) {
@@ -81,14 +82,17 @@
81
82
  /**
82
83
  * Removes an event handler.
83
84
  *
84
- * @param {String} name - Name of the event.
85
+ * @param {string} name - Name of the event.
85
86
  * @param {Function} cb - Callback function.
86
87
  */
87
88
  removeEventListener(name, cb) {
88
89
  const handlers = this.event_handlers.get(name);
89
90
 
90
91
  if (!handlers || !handlers.has(cb)) {
91
- throw new Error('removeEventListeners(): not installed.');
92
+ if (!this.event_handlers_cleared) {
93
+ console.warn('removeEventListeners(): not installed:', name, cb);
94
+ }
95
+ return;
92
96
  }
93
97
 
94
98
  handlers.delete(cb);
@@ -97,7 +101,7 @@
97
101
  /**
98
102
  * Removes an event handler.
99
103
  *
100
- * @param {strign} name
104
+ * @param {string} name
101
105
  * @param {Function} cb
102
106
  */
103
107
  off(name, cb) {
@@ -109,6 +113,7 @@
109
113
  */
110
114
  removeAllEventListeners() {
111
115
  this.event_handlers.clear();
116
+ this.event_handlers_cleared = true;
112
117
  }
113
118
 
114
119
  /**
@@ -1585,6 +1590,67 @@
1585
1590
  }
1586
1591
  }
1587
1592
 
1593
+ class Subscriptions {
1594
+ constructor() {
1595
+ this._callbacks = [];
1596
+ }
1597
+
1598
+ /**
1599
+ * Add a subscription.
1600
+ *
1601
+ * @param {Function[]} cbs
1602
+ */
1603
+ add(...cbs) {
1604
+ cbs.forEach((cb) => {
1605
+ this._callbacks.push(cb);
1606
+ });
1607
+ }
1608
+
1609
+ unsubscribe() {
1610
+ this._callbacks.forEach((cb) => {
1611
+ try {
1612
+ cb();
1613
+ } catch (err) {
1614
+ console.error(err);
1615
+ }
1616
+ });
1617
+ this._callbacks.length = 0;
1618
+ }
1619
+ }
1620
+
1621
+ function addEvent(target, name, callback) {
1622
+ if (target.addEventListener) {
1623
+ target.addEventListener(name, callback);
1624
+ } else if (target.on) {
1625
+ target.on(name, callback);
1626
+ } else {
1627
+ throw new TypeError('Unsupported event target ', target);
1628
+ }
1629
+ }
1630
+
1631
+ function removeEvent(target, name, callback) {
1632
+ if (target.removeEventListener) {
1633
+ target.removeEventListener(name, callback);
1634
+ } else if (target.off) {
1635
+ target.off(name, callback);
1636
+ } else {
1637
+ throw new TypeError('Unsupported event target ', target);
1638
+ }
1639
+ }
1640
+
1641
+ /**
1642
+ *
1643
+ * @param {EventTarget|Events} target
1644
+ * @param {string} name
1645
+ * @param {callback} callback
1646
+ */
1647
+ function subscribeEvent(target, name, callback) {
1648
+ addEvent(target, name, callback);
1649
+ return () => {
1650
+ removeEvent(target, name, callback);
1651
+ };
1652
+ }
1653
+
1588
1654
  class PendingCommand {
1589
1655
  get handle() {
1590
1656
  return this.command.handle;
@@ -1829,35 +1895,34 @@
1829
1895
  * the connection will be closed and the returned promise will reject.
1830
1896
  * @param {number} interval
1831
1897
  * Keepalive interval in seconds.
1898
+ * @param {AbortSignal} [signal]
1899
+ * Optional abort signal which can be used to abort
1900
+ * the process.
1832
1901
  */
1833
- wait_for_keepalive(interval) {
1834
- return new Promise((resolve, reject) => {
1835
- const subscriptions = [];
1902
+ wait_for_keepalive(interval, signal) {
1903
+ const subscriptions = new Subscriptions();
1836
1904
 
1837
- const cleanup = () => {
1838
- subscriptions.forEach((cb) => {
1839
- try {
1840
- cb();
1841
- } catch (e) {}
1842
- });
1843
- subscriptions.length = 0;
1844
- };
1845
- subscriptions.push(
1905
+ return new Promise((resolve, reject) => {
1906
+ if (signal) {
1907
+ subscriptions.add(
1908
+ subscribeEvent(signal, 'abort', () => {
1909
+ reject(signal.reason);
1910
+ })
1911
+ );
1912
+ }
1913
+ subscriptions.add(
1846
1914
  this.subscribe('error', (error) => {
1847
1915
  reject(error);
1848
- cleanup();
1849
1916
  }),
1850
1917
  this.subscribe('close', () => {
1851
1918
  reject(new CloseError());
1852
- cleanup();
1853
1919
  }),
1854
1920
  this.subscribe('keepalive', () => {
1855
1921
  resolve();
1856
- cleanup();
1857
1922
  })
1858
1923
  );
1859
1924
  this.set_keepalive_interval(interval);
1860
- });
1925
+ }).finally(() => subscriptions.unsubscribe());
1861
1926
  }
1862
1927
  }
1863
1928
 
@@ -3486,8 +3551,7 @@
3486
3551
  * @returns {Function} The getter. If none could be found, null is returned.
3487
3552
  */
3488
3553
  getter(o, no_bind) {
3489
- let name = this.name;
3490
- let i = 0;
3554
+ const name = this.name;
3491
3555
  const aliases = this.aliases;
3492
3556
  const accessors = this.accessors;
3493
3557
 
@@ -3529,10 +3593,13 @@
3529
3593
  return no_bind ? fun : fun.bind(o);
3530
3594
  }
3531
3595
 
3532
- do {
3596
+ // iterate all possible names
3597
+ const possibleNames = aliases ? [name, ...aliases] : [name];
3598
+
3599
+ for (const possibleName of possibleNames) {
3533
3600
  if (this.static) {
3534
3601
  const c = o.constructor;
3535
- const v = c[name];
3602
+ const v = c[possibleName];
3536
3603
 
3537
3604
  if (v !== void 0) {
3538
3605
  return function () {
@@ -3540,16 +3607,11 @@
3540
3607
  };
3541
3608
  }
3542
3609
  } else {
3543
- const fun = o['Get' + name];
3610
+ const fun = o['Get' + possibleName];
3544
3611
 
3545
3612
  if (fun) return no_bind ? fun : fun.bind(o);
3546
3613
  }
3547
-
3548
- if (aliases && i < aliases.length) {
3549
- name = aliases[i++];
3550
- continue;
3551
- }
3552
- } while (false);
3614
+ }
3553
3615
 
3554
3616
  return null;
3555
3617
  }
@@ -3565,20 +3627,15 @@
3565
3627
  setter(o, no_bind) {
3566
3628
  if (this.readonly || this.static) return null;
3567
3629
 
3568
- let name = this.name,
3569
- i = 0;
3630
+ const name = this.name;
3570
3631
  const aliases = this.aliases;
3632
+ const possibleNames = aliases ? [name, ...aliases] : [name];
3571
3633
 
3572
- do {
3573
- const fun = o['Set' + name];
3634
+ for (const possibleName of possibleNames) {
3635
+ const fun = o['Set' + possibleName];
3574
3636
 
3575
3637
  if (fun) return no_bind ? fun : fun.bind(o);
3576
-
3577
- if (aliases && i < aliases.length) {
3578
- name = aliases[i++];
3579
- continue;
3580
- }
3581
- } while (false);
3638
+ }
3582
3639
 
3583
3640
  return null;
3584
3641
  }
@@ -28733,35 +28790,6 @@
28733
28790
 
28734
28791
  /* eslint-env node */
28735
28792
 
28736
-
28737
- function delay(n) {
28738
- return new Promise((resolve) => setTimeout(resolve, n));
28739
- }
28740
-
28741
- async function waitForKeepalive(socket, options) {
28742
- const waiter = socket.receiveMessage().then((buffer) => {
28743
- const pdus = [];
28744
- const pos = decodeMessage(new DataView(buffer), 0, pdus);
28745
-
28746
- if (pdus.length !== 1) throw new Error('Expected keepalive response.');
28747
- if (pos !== buffer.byteLength)
28748
- throw new Error('Trailing data in initial keepalive pdu.');
28749
-
28750
- return true;
28751
- });
28752
-
28753
- const msg = encodeMessage(new KeepAlive(1000));
28754
- const t = 5 * (options.retry_interval || 250);
28755
-
28756
- for (let i = 0; i < 3; i++) {
28757
- socket.send(msg);
28758
-
28759
- if (await Promise.race([waiter, delay(t)])) return;
28760
- }
28761
-
28762
- throw new Error('Failed to connect.');
28763
- }
28764
-
28765
28793
  /**
28766
28794
  * :class:`ClientConnection` subclass which implements OCP.1 with UDP
28767
28795
  * transport.
@@ -28807,8 +28835,12 @@
28807
28835
  /**
28808
28836
  * Connect to the given endpoint.
28809
28837
  *
28810
- * @param {String} options.host - hostname or ip
28811
- * @param {number} options.port - port number
28838
+ * @param {String} options.host
28839
+ * Hostname or ip address.
28840
+ * @param {number} options.port
28841
+ * Port number.
28842
+ * @param {'udp4' | 'udp6'} [options.type]
28843
+ * Optional ip protocol type.
28812
28844
  * @param {number} [options.delay=10] - Delay in ms between individual packets.
28813
28845
  * This can be a useful strategy when communicating with devices which
28814
28846
  * cannot handle high packet rates.
@@ -28822,23 +28854,33 @@
28822
28854
  * in an individual UDP packet. Note that AES70 messages which are larger
28823
28855
  * than this limit are sent anyway. This only limits how many seperate
28824
28856
  * messages are batched into a single packet.
28825
- * @returns {Promise<UDPConnection>}
28857
+ * @param {AbortSignal} [options.connectSignal]
28858
+ * An optional AbortSignal which can be used to abort the connect attempt.
28859
+ * Note that this is different from the `signal` option which will destroy
28860
+ * the socket also after the connect attempt has been successful.
28861
+ * @returns {Promise<AbstractUDPConnection>}
28826
28862
  * The connection.
28827
28863
  */
28828
28864
  static async connect(udpApi, options) {
28829
- const socket = await udpApi.connect(
28830
- options.host,
28831
- options.port,
28832
- options.type
28833
- );
28865
+ const { host, port, type, connectSignal } = options;
28866
+ const socket = await udpApi.connect({
28867
+ host,
28868
+ port,
28869
+ type,
28870
+ signal: connectSignal,
28871
+ });
28872
+
28873
+ const connection = new this(socket, options);
28834
28874
 
28835
28875
  try {
28836
- await waitForKeepalive(socket, options);
28837
- return new this(socket, options);
28876
+ await connection.wait_for_keepalive(1, connectSignal);
28877
+ connectSignal?.throwIfAborted();
28838
28878
  } catch (err) {
28839
- socket.close();
28879
+ connection.close();
28840
28880
  throw err;
28841
28881
  }
28882
+
28883
+ return connection;
28842
28884
  }
28843
28885
 
28844
28886
  write(buf) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aes70",
3
- "version": "2.0.14",
3
+ "version": "2.0.15",
4
4
  "description": "A controller library for the AES70 protocol.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -1,38 +1,6 @@
1
1
  /* eslint-env node */
2
-
3
- import { encodeMessage } from '../OCP1/encode_message.js';
4
- import { decodeMessage } from '../OCP1/decode_message.js';
5
- import { KeepAlive } from '../OCP1/keepalive.js';
6
2
  import { ClientConnection } from './client_connection.js';
7
3
 
8
- function delay(n) {
9
- return new Promise((resolve) => setTimeout(resolve, n));
10
- }
11
-
12
- async function waitForKeepalive(socket, options) {
13
- const waiter = socket.receiveMessage().then((buffer) => {
14
- const pdus = [];
15
- const pos = decodeMessage(new DataView(buffer), 0, pdus);
16
-
17
- if (pdus.length !== 1) throw new Error('Expected keepalive response.');
18
- if (pos !== buffer.byteLength)
19
- throw new Error('Trailing data in initial keepalive pdu.');
20
-
21
- return true;
22
- });
23
-
24
- const msg = encodeMessage(new KeepAlive(1000));
25
- const t = 5 * (options.retry_interval || 250);
26
-
27
- for (let i = 0; i < 3; i++) {
28
- socket.send(msg);
29
-
30
- if (await Promise.race([waiter, delay(t)])) return;
31
- }
32
-
33
- throw new Error('Failed to connect.');
34
- }
35
-
36
4
  /**
37
5
  * :class:`ClientConnection` subclass which implements OCP.1 with UDP
38
6
  * transport.
@@ -78,8 +46,12 @@ export class AbstractUDPConnection extends ClientConnection {
78
46
  /**
79
47
  * Connect to the given endpoint.
80
48
  *
81
- * @param {String} options.host - hostname or ip
82
- * @param {number} options.port - port number
49
+ * @param {String} options.host
50
+ * Hostname or ip address.
51
+ * @param {number} options.port
52
+ * Port number.
53
+ * @param {'udp4' | 'udp6'} [options.type]
54
+ * Optional ip protocol type.
83
55
  * @param {number} [options.delay=10] - Delay in ms between individual packets.
84
56
  * This can be a useful strategy when communicating with devices which
85
57
  * cannot handle high packet rates.
@@ -93,23 +65,33 @@ export class AbstractUDPConnection extends ClientConnection {
93
65
  * in an individual UDP packet. Note that AES70 messages which are larger
94
66
  * than this limit are sent anyway. This only limits how many seperate
95
67
  * messages are batched into a single packet.
96
- * @returns {Promise<UDPConnection>}
68
+ * @param {AbortSignal} [options.connectSignal]
69
+ * An optional AbortSignal which can be used to abort the connect attempt.
70
+ * Note that this is different from the `signal` option which will destroy
71
+ * the socket also after the connect attempt has been successful.
72
+ * @returns {Promise<AbstractUDPConnection>}
97
73
  * The connection.
98
74
  */
99
75
  static async connect(udpApi, options) {
100
- const socket = await udpApi.connect(
101
- options.host,
102
- options.port,
103
- options.type
104
- );
76
+ const { host, port, type, connectSignal } = options;
77
+ const socket = await udpApi.connect({
78
+ host,
79
+ port,
80
+ type,
81
+ signal: connectSignal,
82
+ });
83
+
84
+ const connection = new this(socket, options);
105
85
 
106
86
  try {
107
- await waitForKeepalive(socket, options);
108
- return new this(socket, options);
87
+ await connection.wait_for_keepalive(1, connectSignal);
88
+ connectSignal?.throwIfAborted();
109
89
  } catch (err) {
110
- socket.close();
90
+ connection.close();
111
91
  throw err;
112
92
  }
93
+
94
+ return connection;
113
95
  }
114
96
 
115
97
  write(buf) {
@@ -9,6 +9,8 @@ import { Arguments } from './arguments.js';
9
9
  import { OcaStatus } from '../types/OcaStatus.js';
10
10
  import { EncodedArguments } from '../OCP1/encoded_arguments.js';
11
11
  import { CloseError } from '../close_error.js';
12
+ import { Subscriptions } from '../utils/subscriptions.js';
13
+ import { subscribeEvent } from '../utils/subscribeEvent.js';
12
14
 
13
15
  class PendingCommand {
14
16
  get handle() {
@@ -254,34 +256,33 @@ export class ClientConnection extends Connection {
254
256
  * the connection will be closed and the returned promise will reject.
255
257
  * @param {number} interval
256
258
  * Keepalive interval in seconds.
259
+ * @param {AbortSignal} [signal]
260
+ * Optional abort signal which can be used to abort
261
+ * the process.
257
262
  */
258
- wait_for_keepalive(interval) {
263
+ wait_for_keepalive(interval, signal) {
264
+ const subscriptions = new Subscriptions();
265
+
259
266
  return new Promise((resolve, reject) => {
260
- const subscriptions = [];
261
-
262
- const cleanup = () => {
263
- subscriptions.forEach((cb) => {
264
- try {
265
- cb();
266
- } catch (e) {}
267
- });
268
- subscriptions.length = 0;
269
- };
270
- subscriptions.push(
267
+ if (signal) {
268
+ subscriptions.add(
269
+ subscribeEvent(signal, 'abort', () => {
270
+ reject(signal.reason);
271
+ })
272
+ );
273
+ }
274
+ subscriptions.add(
271
275
  this.subscribe('error', (error) => {
272
276
  reject(error);
273
- cleanup();
274
277
  }),
275
278
  this.subscribe('close', () => {
276
279
  reject(new CloseError());
277
- cleanup();
278
280
  }),
279
281
  this.subscribe('keepalive', () => {
280
282
  resolve();
281
- cleanup();
282
283
  })
283
284
  );
284
285
  this.set_keepalive_interval(interval);
285
- });
286
+ }).finally(() => subscriptions.unsubscribe());
286
287
  }
287
288
  }
@@ -1,14 +1,16 @@
1
1
  /* eslint-env node */
2
2
 
3
3
  import { createSocket } from 'dgram';
4
- import { isIP } from 'net';
4
+ import { isIP, isIPv4 } from 'net';
5
5
  import { lookup } from 'dns';
6
+ import { Subscriptions } from '../utils/subscriptions.js';
7
+ import { subscribeEvent } from '../utils/subscribeEvent.js';
6
8
 
7
- function lookup_address(host) {
9
+ function lookup_address(host, family) {
8
10
  if (isIP(host)) return Promise.resolve(host);
9
11
 
10
12
  return new Promise((resolve, reject) => {
11
- lookup(host, { family: 4 }, (err, address) => {
13
+ lookup(host, { family }, (err, address) => {
12
14
  if (err) reject(err);
13
15
  else resolve(address);
14
16
  });
@@ -52,7 +54,11 @@ export class NodeUDP {
52
54
  this._inbuf = [];
53
55
 
54
56
  for (let i = 0; i < inbuf.length; i++) {
55
- onmessage(inbuf[i].buffer);
57
+ try {
58
+ onmessage(inbuf[i].buffer);
59
+ } catch (err) {
60
+ console.error(err);
61
+ }
56
62
  }
57
63
  }
58
64
 
@@ -73,30 +79,11 @@ export class NodeUDP {
73
79
  this.socket.send(Buffer.from(buf));
74
80
  }
75
81
 
76
- receiveMessage(timeout) {
77
- return new Promise((resolve, reject) => {
78
- if (this._error) {
79
- reject(this._error);
80
- } else if (this._inbuf.length) {
81
- resolve(this._inbuf.shift());
82
- } else {
83
- this.onmessage = (msg) => {
84
- this.onmessage = null;
85
- this.onerror = null;
86
- resolve(msg);
87
- };
88
- this.onerror = (err) => {
89
- this.onmessage = null;
90
- this.onerror = null;
91
- reject(err);
92
- };
93
- }
94
- });
95
- }
96
-
97
82
  close() {
98
- this.socket.close();
99
- this.socket = null;
83
+ if (this.socket) {
84
+ this.socket.close();
85
+ this.socket = null;
86
+ }
100
87
  }
101
88
 
102
89
  constructor(socket) {
@@ -114,14 +101,63 @@ export class NodeUDP {
114
101
  });
115
102
  }
116
103
 
117
- static connect(host, port, type) {
118
- return lookup_address(host).then((ip) => {
119
- return new Promise((resolve, reject) => {
120
- const socket = createSocket(type || 'udp4');
104
+ /**
105
+ * Creates a new udp socket and connects it to the target address.
106
+ * @param {string} options.host
107
+ * The hostname or ip address to connect to.
108
+ * @param {number} options.port
109
+ * The port.
110
+ * @param {'udp4' | 'udp6'} [options.type]
111
+ * The ip protocol to use. This is only relevant if host is
112
+ * not an ip address and hostname lookup is used.
113
+ * @param {Abortsignal} [options.signal]
114
+ * An optional AbortSignal to abort the connect operation.
115
+ * @returns
116
+ */
117
+ static connect(options) {
118
+ const { host, port, type, signal } = options;
119
+
120
+ signal?.throwIfAborted();
121
+
122
+ const subscriptions = new Subscriptions();
123
+
124
+ return new Promise((resolve, reject) => {
125
+ let socket;
126
+
127
+ if (signal) {
128
+ subscriptions.add(
129
+ subscribeEvent(signal, 'abort', (reason) => {
130
+ const err = signal.reason;
131
+ reject(err);
132
+ })
133
+ );
134
+ }
135
+
136
+ const lookupFamily =
137
+ type === 'udp4' ? 4 : type === 'udp6' ? 6 : undefined;
138
+ lookup_address(host, lookupFamily).then((ip) => {
139
+ if (signal?.aborted) {
140
+ // IF the abort signal was triggered during the ip lookup,
141
+ // we have to simply ignore the resolve result.
142
+ return;
143
+ }
144
+ const type = isIPv4(ip) ? 'udp4' : 'udp6';
145
+ socket = createSocket(type);
146
+
147
+ if (signal) {
148
+ subscriptions.add(
149
+ subscribeEvent(signal, 'abort', () => {
150
+ socket.close();
151
+ })
152
+ );
153
+ }
154
+
121
155
  const onerror = function (ev) {
122
156
  reject(ev);
157
+ socket.close();
123
158
  };
124
- socket.on('error', onerror);
159
+ subscriptions.add(subscribeEvent(socket, 'error', onerror));
160
+
125
161
  socket.bind(
126
162
  {
127
163
  exclusive: true,
@@ -129,12 +165,11 @@ export class NodeUDP {
129
165
  () => {
130
166
  socket.on('connect', () => {
131
167
  resolve(new this(socket));
132
- socket.removeListener('error', onerror);
133
168
  });
134
- socket.connect(port, host);
169
+ socket.connect(port, ip);
135
170
  }
136
171
  );
137
- });
138
- });
172
+ }, reject);
173
+ }).finally(() => subscriptions.unsubscribe());
139
174
  }
140
175
  }
@@ -56,8 +56,7 @@ export class Property {
56
56
  * @returns {Function} The getter. If none could be found, null is returned.
57
57
  */
58
58
  getter(o, no_bind) {
59
- let name = this.name;
60
- let i = 0;
59
+ const name = this.name;
61
60
  const aliases = this.aliases;
62
61
  const accessors = this.accessors;
63
62
 
@@ -99,10 +98,13 @@ export class Property {
99
98
  return no_bind ? fun : fun.bind(o);
100
99
  }
101
100
 
102
- do {
101
+ // iterate all possible names
102
+ const possibleNames = aliases ? [name, ...aliases] : [name];
103
+
104
+ for (const possibleName of possibleNames) {
103
105
  if (this.static) {
104
106
  const c = o.constructor;
105
- const v = c[name];
107
+ const v = c[possibleName];
106
108
 
107
109
  if (v !== void 0) {
108
110
  return function () {
@@ -110,16 +112,11 @@ export class Property {
110
112
  };
111
113
  }
112
114
  } else {
113
- const fun = o['Get' + name];
115
+ const fun = o['Get' + possibleName];
114
116
 
115
117
  if (fun) return no_bind ? fun : fun.bind(o);
116
118
  }
117
-
118
- if (aliases && i < aliases.length) {
119
- name = aliases[i++];
120
- continue;
121
- }
122
- } while (false);
119
+ }
123
120
 
124
121
  return null;
125
122
  }
@@ -135,20 +132,15 @@ export class Property {
135
132
  setter(o, no_bind) {
136
133
  if (this.readonly || this.static) return null;
137
134
 
138
- let name = this.name,
139
- i = 0;
135
+ const name = this.name;
140
136
  const aliases = this.aliases;
137
+ const possibleNames = aliases ? [name, ...aliases] : [name];
141
138
 
142
- do {
143
- const fun = o['Set' + name];
139
+ for (const possibleName of possibleNames) {
140
+ const fun = o['Set' + possibleName];
144
141
 
145
142
  if (fun) return no_bind ? fun : fun.bind(o);
146
-
147
- if (aliases && i < aliases.length) {
148
- name = aliases[i++];
149
- continue;
150
- }
151
- } while (false);
143
+ }
152
144
 
153
145
  return null;
154
146
  }
package/src/events.js CHANGED
@@ -4,12 +4,13 @@
4
4
  export class Events {
5
5
  constructor() {
6
6
  this.event_handlers = new Map();
7
+ this.event_handlers_cleared = false;
7
8
  }
8
9
 
9
10
  /**
10
11
  * Emit an event.
11
12
  *
12
- * @param {String} name - Name of the event.
13
+ * @param {string} name - Name of the event.
13
14
  * @param {...*} args - Extra arguments.
14
15
  */
15
16
  emit(name) {
@@ -30,7 +31,7 @@ export class Events {
30
31
  /**
31
32
  * Subscribe to an event.
32
33
  *
33
- * @param {String} name - Name of the event.
34
+ * @param {string} name - Name of the event.
34
35
  * @param {Function} cb - Callback function.
35
36
  */
36
37
  on(name, cb) {
@@ -54,14 +55,17 @@ export class Events {
54
55
  /**
55
56
  * Removes an event handler.
56
57
  *
57
- * @param {String} name - Name of the event.
58
+ * @param {string} name - Name of the event.
58
59
  * @param {Function} cb - Callback function.
59
60
  */
60
61
  removeEventListener(name, cb) {
61
62
  const handlers = this.event_handlers.get(name);
62
63
 
63
64
  if (!handlers || !handlers.has(cb)) {
64
- throw new Error('removeEventListeners(): not installed.');
65
+ if (!this.event_handlers_cleared) {
66
+ console.warn('removeEventListeners(): not installed:', name, cb);
67
+ }
68
+ return;
65
69
  }
66
70
 
67
71
  handlers.delete(cb);
@@ -70,7 +74,7 @@ export class Events {
70
74
  /**
71
75
  * Removes an event handler.
72
76
  *
73
- * @param {strign} name
77
+ * @param {string} name
74
78
  * @param {Function} cb
75
79
  */
76
80
  off(name, cb) {
@@ -82,6 +86,7 @@ export class Events {
82
86
  */
83
87
  removeAllEventListeners() {
84
88
  this.event_handlers.clear();
89
+ this.event_handlers_cleared = true;
85
90
  }
86
91
 
87
92
  /**
@@ -0,0 +1,32 @@
1
+ function addEvent(target, name, callback) {
2
+ if (target.addEventListener) {
3
+ target.addEventListener(name, callback);
4
+ } else if (target.on) {
5
+ target.on(name, callback);
6
+ } else {
7
+ throw new TypeError('Unsupported event target ', target);
8
+ }
9
+ }
10
+
11
+ function removeEvent(target, name, callback) {
12
+ if (target.removeEventListener) {
13
+ target.removeEventListener(name, callback);
14
+ } else if (target.off) {
15
+ target.off(name, callback);
16
+ } else {
17
+ throw new TypeError('Unsupported event target ', target);
18
+ }
19
+ }
20
+
21
+ /**
22
+ *
23
+ * @param {EventTarget|Events} target
24
+ * @param {string} name
25
+ * @param {callback} callback
26
+ */
27
+ export function subscribeEvent(target, name, callback) {
28
+ addEvent(target, name, callback);
29
+ return () => {
30
+ removeEvent(target, name, callback);
31
+ };
32
+ }
@@ -0,0 +1,27 @@
1
+ export class Subscriptions {
2
+ constructor() {
3
+ this._callbacks = [];
4
+ }
5
+
6
+ /**
7
+ * Add a subscription.
8
+ *
9
+ * @param {Function[]} cbs
10
+ */
11
+ add(...cbs) {
12
+ cbs.forEach((cb) => {
13
+ this._callbacks.push(cb);
14
+ });
15
+ }
16
+
17
+ unsubscribe() {
18
+ this._callbacks.forEach((cb) => {
19
+ try {
20
+ cb();
21
+ } catch (err) {
22
+ console.error(err);
23
+ }
24
+ });
25
+ this._callbacks.length = 0;
26
+ }
27
+ }