@gibme/mikrotik 20.0.1 → 22.0.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.
package/dist/index.js CHANGED
@@ -18,28 +18,16 @@
18
18
  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
19
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
20
  // SOFTWARE.
21
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
- return new (P || (P = Promise))(function (resolve, reject) {
24
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27
- step((generator = generator.apply(thisArg, _arguments || [])).next());
28
- });
29
- };
30
21
  var __importDefault = (this && this.__importDefault) || function (mod) {
31
22
  return (mod && mod.__esModule) ? mod : { "default": mod };
32
23
  };
33
24
  Object.defineProperty(exports, "__esModule", { value: true });
34
- exports.Mikrotik = exports.AbortSignal = exports.AbortController = void 0;
25
+ exports.Mikrotik = void 0;
35
26
  const ssh_1 = __importDefault(require("@gibme/ssh"));
36
27
  const ip_address_1 = require("ip-address");
37
28
  const memory_1 = __importDefault(require("@gibme/cache/memory"));
38
29
  const dns_1 = require("dns");
39
30
  const semver_1 = require("semver");
40
- const abort_controller_1 = require("abort-controller");
41
- Object.defineProperty(exports, "AbortController", { enumerable: true, get: function () { return abort_controller_1.AbortController; } });
42
- Object.defineProperty(exports, "AbortSignal", { enumerable: true, get: function () { return abort_controller_1.AbortSignal; } });
43
31
  /** @ignore */
44
32
  const toVersion = (version) => (0, semver_1.valid)((0, semver_1.coerce)(version)) || version;
45
33
  /** @ignore */
@@ -47,11 +35,15 @@ const toIP4 = (address) => {
47
35
  try {
48
36
  return new ip_address_1.Address4(address);
49
37
  }
50
- catch (_a) {
38
+ catch {
51
39
  return undefined;
52
40
  }
53
41
  };
54
42
  class Mikrotik extends ssh_1.default {
43
+ static cache = new memory_1.default({
44
+ stdTTL: 15,
45
+ checkperiod: 17
46
+ });
55
47
  /**
56
48
  * Retrieves the routes in the routing table
57
49
  *
@@ -59,111 +51,109 @@ class Mikrotik extends ssh_1.default {
59
51
  * @param vrf
60
52
  * @param active_only
61
53
  */
62
- get_ip_routes() {
63
- return __awaiter(this, arguments, void 0, function* (min_distance = 0, vrf, active_only = true) {
64
- const [ifaces, ips] = yield Promise.all([
65
- this.get_interfaces(),
66
- this.get_ip_addresses()
67
- ]);
68
- const command = `/ip route print terse without-paging${active_only ? ' where active' : ''}`;
69
- const routes = yield this.terse(command);
70
- return routes.map(route => {
71
- var _a;
72
- const _network = route['dst-address'].split('/');
73
- const address = _network[0];
74
- const cidr = parseInt(_network[1]);
75
- const [gateway, iface] = (() => {
76
- if (!route.gateway)
77
- return [undefined, undefined];
78
- const gw = toIP4(route.gateway);
79
- if (route.gateway.includes('%')) {
80
- const [gateway, ...iface] = route.gateway.split('%');
81
- return [gateway, iface.join('%')];
82
- }
83
- else if (gw) {
84
- const ip = ips.filter(ip => ip.includes(route.gateway)).shift();
85
- return [route.gateway, ip === null || ip === void 0 ? void 0 : ip.iface];
86
- }
87
- else {
88
- const ip = ips.filter(ip => ip.iface === route.gateway).shift();
89
- return [ip === null || ip === void 0 ? void 0 : ip.ipaddress, route.gateway];
90
- }
91
- })();
92
- const distance = parseInt(route.distance);
93
- const scope = parseInt(route.scope);
94
- const target_scope = parseInt(route.target_scope) || undefined;
95
- // this does not account for if a device has multiple addresses in the same network
96
- const preferred_source = (_a = ips.filter(ip => gateway ? ip.includes(gateway) : false).shift()) === null || _a === void 0 ? void 0 : _a.ipaddress;
97
- const _vrf = route['routing-table'] || route['routing-mark'] || 'main';
98
- const tunnel = (() => {
99
- const tunnel = ifaces.filter(_iface => _iface.name === iface).shift();
100
- const parent = ips.filter(ip => ip.ipaddress === (tunnel === null || tunnel === void 0 ? void 0 : tunnel.local_address)).shift();
101
- if (!tunnel || !parent)
102
- return undefined;
103
- return Object.assign(Object.assign({}, tunnel), { parent });
104
- })();
105
- const result = {
106
- network: address,
107
- cidr,
108
- distance,
109
- scope,
110
- vrf: _vrf,
111
- includes: (ipaddress) => {
112
- const temp_address = toIP4(ipaddress);
113
- const temp_network = toIP4(`${address}/${cidr}`);
114
- if (!temp_address || !temp_network)
115
- return false;
116
- return temp_address.isInSubnet(temp_network);
117
- }
54
+ async get_ip_routes(min_distance = 0, vrf, active_only = true) {
55
+ const [ifaces, ips] = await Promise.all([
56
+ this.get_interfaces(),
57
+ this.get_ip_addresses()
58
+ ]);
59
+ const command = `/ip route print terse without-paging${active_only ? ' where active' : ''}`;
60
+ const routes = await this.terse(command);
61
+ return routes.map(route => {
62
+ const _network = route['dst-address'].split('/');
63
+ const address = _network[0];
64
+ const cidr = parseInt(_network[1]);
65
+ const [gateway, iface] = (() => {
66
+ if (!route.gateway)
67
+ return [undefined, undefined];
68
+ const gw = toIP4(route.gateway);
69
+ if (route.gateway.includes('%')) {
70
+ const [gateway, ...iface] = route.gateway.split('%');
71
+ return [gateway, iface.join('%')];
72
+ }
73
+ else if (gw) {
74
+ const ip = ips.filter(ip => ip.includes(route.gateway)).shift();
75
+ return [route.gateway, ip?.iface];
76
+ }
77
+ else {
78
+ const ip = ips.filter(ip => ip.iface === route.gateway).shift();
79
+ return [ip?.ipaddress, route.gateway];
80
+ }
81
+ })();
82
+ const distance = parseInt(route.distance);
83
+ const scope = parseInt(route.scope);
84
+ const target_scope = parseInt(route.target_scope) || undefined;
85
+ // this does not account for if a device has multiple addresses in the same network
86
+ const preferred_source = ips.filter(ip => gateway ? ip.includes(gateway) : false).shift()?.ipaddress;
87
+ const _vrf = route['routing-table'] || route['routing-mark'] || 'main';
88
+ const tunnel = (() => {
89
+ const tunnel = ifaces.filter(_iface => _iface.name === iface).shift();
90
+ const parent = ips.filter(ip => ip.ipaddress === tunnel?.local_address).shift();
91
+ if (!tunnel || !parent)
92
+ return undefined;
93
+ return {
94
+ ...tunnel,
95
+ parent
118
96
  };
119
- if (preferred_source)
120
- result.preferred_source = preferred_source;
121
- if (gateway)
122
- result.gateway = gateway;
123
- if (iface)
124
- result.iface = iface;
125
- if (tunnel)
126
- result.tunnel = tunnel;
127
- if (target_scope)
128
- result.target_scope = target_scope;
129
- if (route.comment)
130
- result.comment = route.comment;
131
- return result;
132
- })
133
- .filter(route => route.distance >= min_distance)
134
- .filter(route => vrf ? route.vrf === vrf : true);
135
- });
97
+ })();
98
+ const result = {
99
+ network: address,
100
+ cidr,
101
+ distance,
102
+ scope,
103
+ vrf: _vrf,
104
+ includes: (ipaddress) => {
105
+ const temp_address = toIP4(ipaddress);
106
+ const temp_network = toIP4(`${address}/${cidr}`);
107
+ if (!temp_address || !temp_network)
108
+ return false;
109
+ return temp_address.isInSubnet(temp_network);
110
+ }
111
+ };
112
+ if (preferred_source)
113
+ result.preferred_source = preferred_source;
114
+ if (gateway)
115
+ result.gateway = gateway;
116
+ if (iface)
117
+ result.iface = iface;
118
+ if (tunnel)
119
+ result.tunnel = tunnel;
120
+ if (target_scope)
121
+ result.target_scope = target_scope;
122
+ if (route.comment)
123
+ result.comment = route.comment;
124
+ return result;
125
+ })
126
+ .filter(route => route.distance >= min_distance)
127
+ .filter(route => vrf ? route.vrf === vrf : true);
136
128
  }
137
129
  /**
138
130
  * Retrieves the IP addresses active on the system
139
131
  *
140
132
  * @param active_only
141
133
  */
142
- get_ip_addresses() {
143
- return __awaiter(this, arguments, void 0, function* (active_only = true) {
144
- const command = `/ip address print terse without-paging${active_only ? ' where !disabled' : ''}`;
145
- const addresses = yield this.terse(command);
146
- return addresses.map(line => {
147
- const _address = line.address.split('/');
148
- const ipaddress = _address[0];
149
- const cidr = parseInt(_address[1]);
150
- const result = {
151
- ipaddress,
152
- network: line.network,
153
- cidr,
154
- iface: line.interface,
155
- includes: (ipaddress) => {
156
- const temp_address = toIP4(ipaddress);
157
- const temp_network = toIP4(`${line.network}/${cidr}`);
158
- if (!temp_address || !temp_network)
159
- return false;
160
- return temp_address.isInSubnet(temp_network);
161
- }
162
- };
163
- if (line.comment)
164
- result.comment = line.comment;
165
- return result;
166
- });
134
+ async get_ip_addresses(active_only = true) {
135
+ const command = `/ip address print terse without-paging${active_only ? ' where !disabled' : ''}`;
136
+ const addresses = await this.terse(command);
137
+ return addresses.map(line => {
138
+ const _address = line.address.split('/');
139
+ const ipaddress = _address[0];
140
+ const cidr = parseInt(_address[1]);
141
+ const result = {
142
+ ipaddress,
143
+ network: line.network,
144
+ cidr,
145
+ iface: line.interface,
146
+ includes: (ipaddress) => {
147
+ const temp_address = toIP4(ipaddress);
148
+ const temp_network = toIP4(`${line.network}/${cidr}`);
149
+ if (!temp_address || !temp_network)
150
+ return false;
151
+ return temp_address.isInSubnet(temp_network);
152
+ }
153
+ };
154
+ if (line.comment)
155
+ result.comment = line.comment;
156
+ return result;
167
157
  });
168
158
  }
169
159
  /**
@@ -171,50 +161,48 @@ class Mikrotik extends ssh_1.default {
171
161
  *
172
162
  * @param active_only
173
163
  */
174
- get_interfaces() {
175
- return __awaiter(this, arguments, void 0, function* (active_only = true) {
176
- const filter = active_only ? ' where !disabled' : '';
177
- const [ifaces, gre, ipip, eoip] = yield Promise.all([
178
- this.terse(`/interface print terse without-paging${filter}`),
179
- this.terse(`/interface gre print terse without-paging${filter}`),
180
- this.terse(`/interface ipip print terse without-paging${filter}`),
181
- this.terse(`/interface eoip print terse without-paging${filter}`)
182
- ]);
183
- const tunnels = [...gre, ...ipip, ...eoip];
184
- return ifaces.map(line => {
185
- const _type = (() => {
186
- switch (line.type) {
187
- case 'ether':
188
- return 'ethernet';
189
- case 'lo':
190
- return 'loopback';
191
- case 'gre-tunnel':
192
- return 'gre';
193
- case 'ipip-tunnel':
194
- return 'ipip';
195
- case 'eoip-tunnel':
196
- return 'eoip';
197
- case 'wg':
198
- return 'wireguard';
199
- default:
200
- return line.type;
201
- }
202
- })();
203
- const tunnel = tunnels.filter(tunnel => tunnel.name === line.name).shift();
204
- const local_address = tunnel === null || tunnel === void 0 ? void 0 : tunnel['local-address'];
205
- const remote_address = tunnel === null || tunnel === void 0 ? void 0 : tunnel['remote-address'];
206
- const result = {
207
- name: line.name,
208
- type: _type
209
- };
210
- if (local_address)
211
- result.local_address = local_address;
212
- if (remote_address)
213
- result.remote_address = remote_address;
214
- if (line.comment)
215
- result.comment = line.comment;
216
- return result;
217
- });
164
+ async get_interfaces(active_only = true) {
165
+ const filter = active_only ? ' where !disabled' : '';
166
+ const [ifaces, gre, ipip, eoip] = await Promise.all([
167
+ this.terse(`/interface print terse without-paging${filter}`),
168
+ this.terse(`/interface gre print terse without-paging${filter}`),
169
+ this.terse(`/interface ipip print terse without-paging${filter}`),
170
+ this.terse(`/interface eoip print terse without-paging${filter}`)
171
+ ]);
172
+ const tunnels = [...gre, ...ipip, ...eoip];
173
+ return ifaces.map(line => {
174
+ const _type = (() => {
175
+ switch (line.type) {
176
+ case 'ether':
177
+ return 'ethernet';
178
+ case 'lo':
179
+ return 'loopback';
180
+ case 'gre-tunnel':
181
+ return 'gre';
182
+ case 'ipip-tunnel':
183
+ return 'ipip';
184
+ case 'eoip-tunnel':
185
+ return 'eoip';
186
+ case 'wg':
187
+ return 'wireguard';
188
+ default:
189
+ return line.type;
190
+ }
191
+ })();
192
+ const tunnel = tunnels.filter(tunnel => tunnel.name === line.name).shift();
193
+ const local_address = tunnel?.['local-address'];
194
+ const remote_address = tunnel?.['remote-address'];
195
+ const result = {
196
+ name: line.name,
197
+ type: _type
198
+ };
199
+ if (local_address)
200
+ result.local_address = local_address;
201
+ if (remote_address)
202
+ result.remote_address = remote_address;
203
+ if (line.comment)
204
+ result.comment = line.comment;
205
+ return result;
218
206
  });
219
207
  }
220
208
  /**
@@ -224,35 +212,33 @@ class Mikrotik extends ssh_1.default {
224
212
  * @param min_distance
225
213
  * @param vrf
226
214
  */
227
- get_route_counts() {
228
- return __awaiter(this, arguments, void 0, function* (min_distance = 0, vrf) {
229
- const [routes, ips] = yield Promise.all([
230
- this.get_ip_routes(min_distance, vrf),
231
- this.get_ip_addresses()
232
- ]);
233
- const results = {};
234
- ips.forEach(ip => {
235
- results[ip.ipaddress] = {
236
- interface: ip.iface,
237
- count: routes.filter(route => { var _a; return route.preferred_source === ip.ipaddress || ((_a = route.tunnel) === null || _a === void 0 ? void 0 : _a.local_address) === ip.ipaddress; }).length,
238
- active: false
239
- };
240
- });
241
- const best = {
242
- ip: '',
243
- count: 0
215
+ async get_route_counts(min_distance = 0, vrf) {
216
+ const [routes, ips] = await Promise.all([
217
+ this.get_ip_routes(min_distance, vrf),
218
+ this.get_ip_addresses()
219
+ ]);
220
+ const results = {};
221
+ ips.forEach(ip => {
222
+ results[ip.ipaddress] = {
223
+ interface: ip.iface,
224
+ count: routes.filter(route => route.preferred_source === ip.ipaddress || route.tunnel?.local_address === ip.ipaddress).length,
225
+ active: false
244
226
  };
245
- Object.keys(results).forEach(key => {
246
- if (results[key].count > best.count) {
247
- best.count = results[key].count;
248
- best.ip = key;
249
- }
250
- });
251
- if (results[best.ip]) {
252
- results[best.ip].active = true;
227
+ });
228
+ const best = {
229
+ ip: '',
230
+ count: 0
231
+ };
232
+ Object.keys(results).forEach(key => {
233
+ if (results[key].count > best.count) {
234
+ best.count = results[key].count;
235
+ best.ip = key;
253
236
  }
254
- return results;
255
237
  });
238
+ if (results[best.ip]) {
239
+ results[best.ip].active = true;
240
+ }
241
+ return results;
256
242
  }
257
243
  /**
258
244
  * Executes a bandwidth test
@@ -266,148 +252,142 @@ class Mikrotik extends ssh_1.default {
266
252
  * @param password
267
253
  * @param options
268
254
  */
269
- bandwidth_test(target_1, username_1, password_1) {
270
- return __awaiter(this, arguments, void 0, function* (target, username, password, options = {}) {
271
- var _a;
272
- const sleep = (timeout) => __awaiter(this, void 0, void 0, function* () { return new Promise(resolve => setTimeout(resolve, timeout)); });
273
- const controller = new abort_controller_1.AbortController();
274
- (_a = options.signal) !== null && _a !== void 0 ? _a : (options.signal = controller.signal);
275
- // eslint-disable-next-line no-async-promise-executor
276
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
277
- var _a, _b, _c, _d, _e, _f;
278
- (_a = options.duration) !== null && _a !== void 0 ? _a : (options.duration = 15);
279
- (_b = options.direction) !== null && _b !== void 0 ? _b : (options.direction = 'both');
280
- (_c = options.protocol) !== null && _c !== void 0 ? _c : (options.protocol = 'udp');
281
- (_d = options.random_data) !== null && _d !== void 0 ? _d : (options.random_data = false);
282
- (_e = options.callback) !== null && _e !== void 0 ? _e : (options.callback = () => { });
283
- /**
284
- * This serves as a lazy "mutex" that prevents us from running
285
- * a bandwidth test for against a target host that is already
286
- * in the process of running a bandwidth test
287
- */
288
- while (yield Mikrotik.cache.includes(target)) {
289
- if (options.callback) {
290
- options.callback({
291
- status: 'queued',
292
- duration: 0,
293
- randomData: options.random_data,
294
- direction: options.direction,
295
- connectionCount: 0,
296
- localCPULoad: 0,
297
- remoteCPULoad: 0
298
- });
299
- }
300
- yield sleep(1000);
255
+ async bandwidth_test(target, username, password, options = {}) {
256
+ const sleep = async (timeout) => new Promise(resolve => setTimeout(resolve, timeout));
257
+ const controller = new AbortController();
258
+ options.signal ??= controller.signal;
259
+ // eslint-disable-next-line no-async-promise-executor
260
+ return new Promise(async (resolve, reject) => {
261
+ options.duration ??= 15;
262
+ options.direction ??= 'both';
263
+ options.protocol ??= 'udp';
264
+ options.random_data ??= false;
265
+ options.callback ??= () => { };
266
+ /**
267
+ * This serves as a lazy "mutex" that prevents us from running
268
+ * a bandwidth test for against a target host that is already
269
+ * in the process of running a bandwidth test
270
+ */
271
+ while (await Mikrotik.cache.includes(target)) {
272
+ if (options.callback) {
273
+ options.callback({
274
+ status: 'queued',
275
+ duration: 0,
276
+ randomData: options.random_data,
277
+ direction: options.direction,
278
+ connectionCount: 0,
279
+ localCPULoad: 0,
280
+ remoteCPULoad: 0
281
+ });
301
282
  }
302
- const toBPS = (value) => {
303
- if (!value) {
304
- return 0;
305
- }
306
- value = value.toLowerCase();
307
- if (value.includes('tbps')) {
308
- const tmp = parseFloat(value) || 0;
309
- return Math.round(tmp * 1000 * 1000 * 1000 * 1000);
310
- }
311
- else if (value.includes('gbps')) {
312
- const tmp = parseFloat(value) || 0;
313
- return Math.round(tmp * 1000 * 1000 * 1000);
314
- }
315
- else if (value.includes('mbps')) {
316
- const tmp = parseFloat(value) || 0;
317
- return Math.round(tmp * 1000 * 1000);
318
- }
319
- else if (value.includes('kbps')) {
320
- const tmp = parseFloat(value) || 0;
321
- return Math.round(tmp * 1000);
322
- }
323
- else {
324
- return Math.round(parseFloat(value) || 0);
325
- }
283
+ await sleep(1000);
284
+ }
285
+ const toBPS = (value) => {
286
+ if (!value) {
287
+ return 0;
288
+ }
289
+ value = value.toLowerCase();
290
+ if (value.includes('tbps')) {
291
+ const tmp = parseFloat(value) || 0;
292
+ return Math.round(tmp * 1000 * 1000 * 1000 * 1000);
293
+ }
294
+ else if (value.includes('gbps')) {
295
+ const tmp = parseFloat(value) || 0;
296
+ return Math.round(tmp * 1000 * 1000 * 1000);
297
+ }
298
+ else if (value.includes('mbps')) {
299
+ const tmp = parseFloat(value) || 0;
300
+ return Math.round(tmp * 1000 * 1000);
301
+ }
302
+ else if (value.includes('kbps')) {
303
+ const tmp = parseFloat(value) || 0;
304
+ return Math.round(tmp * 1000);
305
+ }
306
+ else {
307
+ return Math.round(parseFloat(value) || 0);
308
+ }
309
+ };
310
+ /**
311
+ * Performs further processing of the stream of data returned
312
+ * during the bandwidth testing
313
+ *
314
+ * @param buffer
315
+ */
316
+ async function handleStream(buffer) {
317
+ const frame = new Mikrotik.BandwidthTest.Frame(buffer).parse();
318
+ const result = {
319
+ status: frame.status,
320
+ duration: parseInt(frame.duration) || 0,
321
+ randomData: frame['random-data'] === 'yes',
322
+ direction: frame.direction,
323
+ connectionCount: parseInt(frame['connection-count']) || 0,
324
+ localCPULoad: (parseInt(frame['local-cpu-load']) || 0) / 100,
325
+ remoteCPULoad: (parseInt(frame['remote-cpu-load']) || 0) / 100
326
326
  };
327
- /**
328
- * Performs further processing of the stream of data returned
329
- * during the bandwidth testing
330
- *
331
- * @param buffer
332
- */
333
- function handleStream(buffer) {
334
- return __awaiter(this, void 0, void 0, function* () {
335
- const frame = new Mikrotik.BandwidthTest.Frame(buffer).parse();
336
- const result = {
337
- status: frame.status,
338
- duration: parseInt(frame.duration) || 0,
339
- randomData: frame['random-data'] === 'yes',
340
- direction: frame.direction,
341
- connectionCount: parseInt(frame['connection-count']) || 0,
342
- localCPULoad: (parseInt(frame['local-cpu-load']) || 0) / 100,
343
- remoteCPULoad: (parseInt(frame['remote-cpu-load']) || 0) / 100
344
- };
345
- if (frame['lost-packets'])
346
- result.lostPackets = parseInt(frame['lost-packets']) || 0;
347
- if (frame['tx-current']) {
348
- result.transmit = {
349
- current: toBPS(frame['tx-current']),
350
- shortAverage: toBPS(frame['tx-10-second-average']),
351
- totalAverage: toBPS(frame['tx-total-average']),
352
- size: toBPS(frame['tx-size'])
353
- };
354
- }
355
- if (frame['rx-current']) {
356
- result.receive = {
357
- current: toBPS(frame['rx-current']),
358
- shortAverage: toBPS(frame['rx-10-second-average']),
359
- totalAverage: toBPS(frame['rx-total-average']),
360
- size: toBPS(frame['rx-size'])
361
- };
362
- }
363
- switch (result.status) {
364
- case 'done testing':
365
- if (options.callback)
366
- options.callback(result);
367
- return resolve(result);
368
- case 'authentication_failed':
369
- return reject(new Error('Authentication Failed'));
370
- case 'connecting':
371
- case 'running':
372
- yield Mikrotik.cache.ttl(target, options.duration); // bump our mutex
373
- if (options.callback)
374
- options.callback(result);
375
- break;
376
- }
377
- });
327
+ if (frame['lost-packets'])
328
+ result.lostPackets = parseInt(frame['lost-packets']) || 0;
329
+ if (frame['tx-current']) {
330
+ result.transmit = {
331
+ current: toBPS(frame['tx-current']),
332
+ shortAverage: toBPS(frame['tx-10-second-average']),
333
+ totalAverage: toBPS(frame['tx-total-average']),
334
+ size: toBPS(frame['tx-size'])
335
+ };
378
336
  }
379
- const cleanup = () => __awaiter(this, void 0, void 0, function* () {
380
- yield Mikrotik.cache.del(target);
381
- });
382
- yield Mikrotik.cache.set(target, target, options.duration); // set our mutex
383
- let command = `/tool bandwidth-test protocol=${options.protocol} ` +
384
- `user=${username} password=${password} ` +
385
- `duration=${options.duration}s direction=${options.direction} ` +
386
- `address=${target} random-data=${options.random_data ? 'yes' : 'no'} interval=1s`;
387
- if (options.local_tx_speed) {
388
- command += ` local-tx-speed=${options.local_tx_speed}M`;
337
+ if (frame['rx-current']) {
338
+ result.receive = {
339
+ current: toBPS(frame['rx-current']),
340
+ shortAverage: toBPS(frame['rx-10-second-average']),
341
+ totalAverage: toBPS(frame['rx-total-average']),
342
+ size: toBPS(frame['rx-size'])
343
+ };
389
344
  }
390
- if (options.remote_tx_speed) {
391
- command += ` remote-tx-speed=${options.remote_tx_speed}M`;
345
+ switch (result.status) {
346
+ case 'done testing':
347
+ if (options.callback)
348
+ options.callback(result);
349
+ return resolve(result);
350
+ case 'authentication_failed':
351
+ return reject(new Error('Authentication Failed'));
352
+ case 'connecting':
353
+ case 'running':
354
+ await Mikrotik.cache.ttl(target, options.duration); // bump our mutex
355
+ if (options.callback)
356
+ options.callback(result);
357
+ break;
392
358
  }
393
- const stream = yield this.stream(command, { separator: '\r\n\r\n' });
394
- (_f = options.signal) === null || _f === void 0 ? void 0 : _f.addEventListener('abort', () => {
359
+ }
360
+ const cleanup = async () => {
361
+ await Mikrotik.cache.del(target);
362
+ };
363
+ await Mikrotik.cache.set(target, target, options.duration); // set our mutex
364
+ let command = `/tool bandwidth-test protocol=${options.protocol} ` +
365
+ `user=${username} password=${password} ` +
366
+ `duration=${options.duration}s direction=${options.direction} ` +
367
+ `address=${target} random-data=${options.random_data ? 'yes' : 'no'} interval=1s`;
368
+ if (options.local_tx_speed) {
369
+ command += ` local-tx-speed=${options.local_tx_speed}M`;
370
+ }
371
+ if (options.remote_tx_speed) {
372
+ command += ` remote-tx-speed=${options.remote_tx_speed}M`;
373
+ }
374
+ const stream = await this.stream(command, { separator: '\r\n\r\n' });
375
+ options.signal?.addEventListener('abort', () => {
376
+ stream.abort();
377
+ });
378
+ stream.on('data', handleStream);
379
+ stream.on('completed', async () => {
380
+ await cleanup();
381
+ });
382
+ stream.on('cancelled', async () => {
383
+ await cleanup();
384
+ return reject(new Error('Bandwidth Test Cancelled'));
385
+ });
386
+ if (options.timeout) {
387
+ setTimeout(() => {
395
388
  stream.abort();
396
- });
397
- stream.on('data', handleStream);
398
- stream.on('completed', () => __awaiter(this, void 0, void 0, function* () {
399
- yield cleanup();
400
- }));
401
- stream.on('cancelled', () => __awaiter(this, void 0, void 0, function* () {
402
- yield cleanup();
403
- return reject(new Error('Bandwidth Test Cancelled'));
404
- }));
405
- if (options.timeout) {
406
- setTimeout(() => {
407
- stream.abort();
408
- }, options.timeout);
409
- }
410
- }));
389
+ }, options.timeout);
390
+ }
411
391
  });
412
392
  }
413
393
  /**
@@ -416,34 +396,32 @@ class Mikrotik extends ssh_1.default {
416
396
  * @param target
417
397
  * @param source
418
398
  */
419
- ping(target, source) {
420
- return __awaiter(this, void 0, void 0, function* () {
421
- const result = {
422
- target,
423
- latency: 2000
424
- };
425
- if (source)
426
- result.source = source;
427
- const command = `/ping address=${target} count=1${source ? ` src-address=${source}` : ''}`;
428
- try {
429
- const response = (yield this.exec(command))
430
- .toString()
431
- .trim()
432
- .split('\n')
433
- .map(line => line.trim());
434
- for (const line of response) {
435
- const parts = line.split(/\s+/)
436
- .map(part => part.trim());
437
- if (isNaN(parseInt(parts[0])))
438
- continue;
439
- result.latency = parseInt(parts[4]) || 2000;
440
- }
441
- return result;
442
- }
443
- catch (_a) {
444
- return result;
399
+ async ping(target, source) {
400
+ const result = {
401
+ target,
402
+ latency: 2000
403
+ };
404
+ if (source)
405
+ result.source = source;
406
+ const command = `/ping address=${target} count=1${source ? ` src-address=${source}` : ''}`;
407
+ try {
408
+ const response = (await this.exec(command))
409
+ .toString()
410
+ .trim()
411
+ .split('\n')
412
+ .map(line => line.trim());
413
+ for (const line of response) {
414
+ const parts = line.split(/\s+/)
415
+ .map(part => part.trim());
416
+ if (isNaN(parseInt(parts[0])))
417
+ continue;
418
+ result.latency = parseInt(parts[4]) || 2000;
445
419
  }
446
- });
420
+ return result;
421
+ }
422
+ catch {
423
+ return result;
424
+ }
447
425
  }
448
426
  /**
449
427
  * Performs a traceroute to the target IP address from the source IP address (if supplied)
@@ -451,229 +429,213 @@ class Mikrotik extends ssh_1.default {
451
429
  * @param target
452
430
  * @param source
453
431
  */
454
- traceroute(target, source) {
455
- return __awaiter(this, void 0, void 0, function* () {
456
- const command = `/tool traceroute address=${target} count=1${source ? ` src-address=${source}` : ''}`;
457
- const response = (yield this.exec(command))
458
- .toString()
459
- .split('\r\n\r\n')
460
- .map(frame => frame.trim())
461
- .filter(frame => frame.length !== 0)
462
- .map(frame => frame.split('\r\n')
463
- .map(line => line.trim())
464
- .map(line => line.split(/\s+/)
465
- .map(col => col.trim()))
466
- .filter(col => !isNaN(parseInt(col[0]))))
467
- .pop();
468
- if (!response)
469
- throw new Error(`Could not perform traceroute to ${target}`);
470
- const results = [];
471
- const resolveDNS = (ip) => __awaiter(this, void 0, void 0, function* () {
472
- return new Promise(resolve => {
473
- try {
474
- (0, dns_1.reverse)(ip, (error, addresses) => {
475
- if (error)
476
- return resolve([ip, undefined]);
477
- return resolve([ip, addresses.shift()]);
478
- });
479
- }
480
- catch (_a) {
432
+ async traceroute(target, source) {
433
+ const command = `/tool traceroute address=${target} count=1${source ? ` src-address=${source}` : ''}`;
434
+ const response = (await this.exec(command))
435
+ .toString()
436
+ .split('\r\n\r\n')
437
+ .map(frame => frame.trim())
438
+ .filter(frame => frame.length !== 0)
439
+ .map(frame => frame.split('\r\n')
440
+ .map(line => line.trim())
441
+ .map(line => line.split(/\s+/)
442
+ .map(col => col.trim()))
443
+ .filter(col => !isNaN(parseInt(col[0]))))
444
+ .pop();
445
+ if (!response)
446
+ throw new Error(`Could not perform traceroute to ${target}`);
447
+ const results = [];
448
+ const resolveDNS = async (ip) => new Promise(resolve => {
449
+ try {
450
+ (0, dns_1.reverse)(ip, (error, addresses) => {
451
+ if (error)
481
452
  return resolve([ip, undefined]);
482
- }
453
+ return resolve([ip, addresses.shift()]);
483
454
  });
484
- });
485
- for (const [hop, address, loss, sent, last, avg, best, worst] of response) {
486
- const result = {
487
- hop: parseInt(hop),
488
- loss: (sent === 'timeout' ? parseInt(address) : parseInt(loss)) / 100,
489
- sent: sent === 'timeout' ? parseInt(loss) : parseInt(sent),
490
- last: sent === 'timeout' ? 2000 : parseInt(last),
491
- average: sent === 'timeout' ? 2000 : parseInt(avg),
492
- best: sent === 'timeout' ? 2000 : parseInt(best),
493
- worst: sent === 'timeout' ? 2000 : parseInt(worst),
494
- timeout: sent === 'timeout'
495
- };
496
- if (sent !== 'timeout')
497
- result.address = address;
498
- results.push(result);
499
455
  }
500
- (yield Promise.all(results.filter(result => result.address)
501
- .map(result => resolveDNS(result.address || ''))))
502
- .forEach(result => {
503
- for (let i = 0; i < results.length; i++) {
504
- if (result[0] === results[i].address && result[1]) {
505
- results[i].hostname = result[1];
506
- }
456
+ catch {
457
+ return resolve([ip, undefined]);
458
+ }
459
+ });
460
+ for (const [hop, address, loss, sent, last, avg, best, worst] of response) {
461
+ const result = {
462
+ hop: parseInt(hop),
463
+ loss: (sent === 'timeout' ? parseInt(address) : parseInt(loss)) / 100,
464
+ sent: sent === 'timeout' ? parseInt(loss) : parseInt(sent),
465
+ last: sent === 'timeout' ? 2000 : parseInt(last),
466
+ average: sent === 'timeout' ? 2000 : parseInt(avg),
467
+ best: sent === 'timeout' ? 2000 : parseInt(best),
468
+ worst: sent === 'timeout' ? 2000 : parseInt(worst),
469
+ timeout: sent === 'timeout'
470
+ };
471
+ if (sent !== 'timeout')
472
+ result.address = address;
473
+ results.push(result);
474
+ }
475
+ (await Promise.all(results.filter(result => result.address)
476
+ .map(result => resolveDNS(result.address || ''))))
477
+ .forEach(result => {
478
+ for (let i = 0; i < results.length; i++) {
479
+ if (result[0] === results[i].address && result[1]) {
480
+ results[i].hostname = result[1];
507
481
  }
508
- });
509
- return results;
482
+ }
510
483
  });
484
+ return results;
511
485
  }
512
486
  /**
513
487
  * Fetches routerboard information from the device
514
488
  */
515
- routerboard() {
516
- return __awaiter(this, void 0, void 0, function* () {
517
- const result = yield this.kvs('/system routerboard print');
518
- return {
519
- routerboard: result.routerboard === 'yes',
520
- board_name: result['board-name'],
521
- model: result.model,
522
- serial_number: result['serial-number'],
523
- firmware_type: result['firmware-type'],
524
- factory_firmware: toVersion(result['factory-firmware']),
525
- current_firmware: toVersion(result['current-firmware']),
526
- upgrade_firmware: toVersion(result['upgrade-firmware'])
527
- };
528
- });
489
+ async routerboard() {
490
+ const result = await this.kvs('/system routerboard print');
491
+ return {
492
+ routerboard: result.routerboard === 'yes',
493
+ board_name: result['board-name'],
494
+ model: result.model,
495
+ serial_number: result['serial-number'],
496
+ firmware_type: result['firmware-type'],
497
+ factory_firmware: toVersion(result['factory-firmware']),
498
+ current_firmware: toVersion(result['current-firmware']),
499
+ upgrade_firmware: toVersion(result['upgrade-firmware'])
500
+ };
529
501
  }
530
502
  /**
531
503
  * Fetches the identity of the device
532
504
  */
533
- identity() {
534
- return __awaiter(this, void 0, void 0, function* () {
535
- const response = yield this.kvs('/system identity print');
536
- return response.name;
537
- });
505
+ async identity() {
506
+ const response = await this.kvs('/system identity print');
507
+ return response.name;
538
508
  }
539
509
  /**
540
510
  * Fetches the resource information of the device
541
511
  */
542
- resource() {
543
- return __awaiter(this, void 0, void 0, function* () {
544
- const response = yield this.kvs('/system resource print');
545
- const result = {
546
- uptime: response.uptime,
547
- version: toVersion(response.version),
548
- build_time: new Date(`${response['build-time']}Z`),
549
- factory_sofware: response['factory-software'],
550
- free_memory: response['free-memory'],
551
- total_memory: response['total-memory'],
552
- cpu: response.cpu,
553
- cpu_count: parseInt(response['cpu-count']) || 0,
554
- cpu_frequency: parseInt(response['cpu-frequency']) || 0,
555
- cpu_load: (parseInt(response['cpu-load']) || 0) / 100,
556
- hdd_space: {
557
- free: response['free-hdd-space'],
558
- total: response['total-hdd-space']
559
- },
560
- architecture_name: response['architecture-name'],
561
- board_name: response['board-name'],
562
- platform: response.platform
512
+ async resource() {
513
+ const response = await this.kvs('/system resource print');
514
+ const result = {
515
+ uptime: response.uptime,
516
+ version: toVersion(response.version),
517
+ build_time: new Date(`${response['build-time']}Z`),
518
+ factory_sofware: response['factory-software'],
519
+ free_memory: response['free-memory'],
520
+ total_memory: response['total-memory'],
521
+ cpu: response.cpu,
522
+ cpu_count: parseInt(response['cpu-count']) || 0,
523
+ cpu_frequency: parseInt(response['cpu-frequency']) || 0,
524
+ cpu_load: (parseInt(response['cpu-load']) || 0) / 100,
525
+ hdd_space: {
526
+ free: response['free-hdd-space'],
527
+ total: response['total-hdd-space']
528
+ },
529
+ architecture_name: response['architecture-name'],
530
+ board_name: response['board-name'],
531
+ platform: response.platform
532
+ };
533
+ if (response.version.includes('long-term')) {
534
+ result.lts = true;
535
+ }
536
+ else if (response.version.includes('stable')) {
537
+ result.stable = true;
538
+ }
539
+ else if (response.version.includes('testing')) {
540
+ result.testing = true;
541
+ }
542
+ else if (response.version.includes('development')) {
543
+ result.development = true;
544
+ }
545
+ if (response['write-sect-since-reboot'] && response['write-sect-total'] && response['bad-blocks']) {
546
+ result.nvram = {
547
+ write_since_reboot: parseInt(response['write-sect-since-reboot']) || 0,
548
+ write_total: parseInt(response['write-sect-total']) || 0,
549
+ bad_blocks: parseInt(response['bad-blocks']) || 0
563
550
  };
564
- if (response.version.includes('long-term')) {
565
- result.lts = true;
566
- }
567
- else if (response.version.includes('stable')) {
568
- result.stable = true;
569
- }
570
- else if (response.version.includes('testing')) {
571
- result.testing = true;
572
- }
573
- else if (response.version.includes('development')) {
574
- result.development = true;
575
- }
576
- if (response['write-sect-since-reboot'] && response['write-sect-total'] && response['bad-blocks']) {
577
- result.nvram = {
578
- write_since_reboot: parseInt(response['write-sect-since-reboot']) || 0,
579
- write_total: parseInt(response['write-sect-total']) || 0,
580
- bad_blocks: parseInt(response['bad-blocks']) || 0
581
- };
582
- }
583
- return result;
584
- });
551
+ }
552
+ return result;
585
553
  }
586
554
  /**
587
555
  * Fetches the current RouterOS version
588
556
  */
589
- version() {
590
- return __awaiter(this, void 0, void 0, function* () {
591
- return (yield this.resource()).version;
592
- });
557
+ async version() {
558
+ return (await this.resource()).version;
593
559
  }
594
560
  /**
595
561
  * Fetches the semantic RouterOS version
596
562
  */
597
- semantic_version() {
598
- return __awaiter(this, void 0, void 0, function* () {
599
- const version = yield this.version();
600
- const [major, minor, patch] = version.split('.')
601
- .map(elem => parseInt(elem) || 0);
602
- return {
603
- major,
604
- minor,
605
- patch
606
- };
607
- });
563
+ async semantic_version() {
564
+ const version = await this.version();
565
+ const [major, minor, patch] = version.split('.')
566
+ .map(elem => parseInt(elem) || 0);
567
+ return {
568
+ major,
569
+ minor,
570
+ patch
571
+ };
608
572
  }
609
573
  /**
610
574
  * Fetches the current health information
611
575
  */
612
- health() {
613
- return __awaiter(this, void 0, void 0, function* () {
614
- const { major } = yield this.semantic_version();
615
- if (major === 6) {
616
- const result = {};
617
- const response = yield this.kvs('/system health print');
618
- result.voltage = parseFloat(response.voltage) || 0;
619
- result.current = parseFloat(response.current) || 0;
620
- result.temperature = parseFloat(response.temperature) || 0;
621
- result.power_consumption = parseFloat(response['power-consumption']) || 0;
622
- if (response['psu-voltage']) {
623
- result.psu_voltage = parseFloat(response['psu-voltage']);
624
- }
625
- if (response['psu1-voltage']) {
626
- result.psu1_voltage = parseFloat(response['psu1-voltage']);
627
- }
628
- if (response['psu2-voltage']) {
629
- result.psu2_voltage = parseFloat(response['psu2-voltage']);
630
- }
631
- return result;
576
+ async health() {
577
+ const { major } = await this.semantic_version();
578
+ if (major === 6) {
579
+ const result = {};
580
+ const response = await this.kvs('/system health print');
581
+ result.voltage = parseFloat(response.voltage) || 0;
582
+ result.current = parseFloat(response.current) || 0;
583
+ result.temperature = parseFloat(response.temperature) || 0;
584
+ result.power_consumption = parseFloat(response['power-consumption']) || 0;
585
+ if (response['psu-voltage']) {
586
+ result.psu_voltage = parseFloat(response['psu-voltage']);
632
587
  }
633
- else if (major === 7) {
634
- const result = {};
635
- const response = yield this.terse('/system health print terse');
636
- for (const { name, value } of response) {
637
- const num = parseFloat(value) || 0;
638
- switch (name) {
639
- case 'cpu-temperature':
640
- result.cpu_temperature = num;
641
- break;
642
- case 'sfp-temperature':
643
- result.sfp_temperature = num;
644
- break;
645
- case 'switch-temperature':
646
- result.switch_temperature = num;
647
- break;
648
- case 'fan-state':
649
- result.fan_state = value;
650
- break;
651
- case 'fan1-speed':
652
- result.fan1_speed = num;
653
- break;
654
- case 'fan2-speed':
655
- result.fan2_speed = num;
656
- break;
657
- case 'board-temperature1':
658
- result.board_temperature1 = num;
659
- break;
660
- case 'board-temperature2':
661
- result.board_temperature2 = num;
662
- break;
663
- case 'psu1-state':
664
- result.psu1_state = value;
665
- break;
666
- case 'psu2-state':
667
- result.psu2_state = value;
668
- break;
669
- }
670
- }
671
- return result;
588
+ if (response['psu1-voltage']) {
589
+ result.psu1_voltage = parseFloat(response['psu1-voltage']);
672
590
  }
673
- else {
674
- throw new Error('RouterOS version is not supported for this command');
591
+ if (response['psu2-voltage']) {
592
+ result.psu2_voltage = parseFloat(response['psu2-voltage']);
675
593
  }
676
- });
594
+ return result;
595
+ }
596
+ else if (major === 7) {
597
+ const result = {};
598
+ const response = await this.terse('/system health print terse');
599
+ for (const { name, value } of response) {
600
+ const num = parseFloat(value) || 0;
601
+ switch (name) {
602
+ case 'cpu-temperature':
603
+ result.cpu_temperature = num;
604
+ break;
605
+ case 'sfp-temperature':
606
+ result.sfp_temperature = num;
607
+ break;
608
+ case 'switch-temperature':
609
+ result.switch_temperature = num;
610
+ break;
611
+ case 'fan-state':
612
+ result.fan_state = value;
613
+ break;
614
+ case 'fan1-speed':
615
+ result.fan1_speed = num;
616
+ break;
617
+ case 'fan2-speed':
618
+ result.fan2_speed = num;
619
+ break;
620
+ case 'board-temperature1':
621
+ result.board_temperature1 = num;
622
+ break;
623
+ case 'board-temperature2':
624
+ result.board_temperature2 = num;
625
+ break;
626
+ case 'psu1-state':
627
+ result.psu1_state = value;
628
+ break;
629
+ case 'psu2-state':
630
+ result.psu2_state = value;
631
+ break;
632
+ }
633
+ }
634
+ return result;
635
+ }
636
+ else {
637
+ throw new Error('RouterOS version is not supported for this command');
638
+ }
677
639
  }
678
640
  /**
679
641
  * Executes a command with expected terse response and parses it accordingly
@@ -681,70 +643,68 @@ class Mikrotik extends ssh_1.default {
681
643
  * @param command
682
644
  * @protected
683
645
  */
684
- terse(command) {
685
- return __awaiter(this, void 0, void 0, function* () {
686
- const results = [];
687
- (yield this.exec(command)).toString()
688
- .split('\r\n')
689
- .map(line => line.trim())
690
- .filter(line => line.split(/\s+/).length !== 0 && line.length !== 0)
691
- .forEach(line => {
692
- const result = (() => {
693
- const [, flags] = line.split(/\s+/).map(part => part.trim());
694
- const result = {};
695
- // get the id or the number
696
- if (line.startsWith('*')) {
697
- result.id = line.split(/\s/).shift();
698
- }
699
- else if (!isNaN(parseInt(line))) {
700
- result.idx = parseInt(line);
701
- }
702
- result.flags = flags.includes('=') ? '' : flags;
703
- return result;
646
+ async terse(command) {
647
+ const results = [];
648
+ (await this.exec(command)).toString()
649
+ .split('\r\n')
650
+ .map(line => line.trim())
651
+ .filter(line => line.split(/\s+/).length !== 0 && line.length !== 0)
652
+ .forEach(line => {
653
+ const result = (() => {
654
+ const [, flags] = line.split(/\s+/).map(part => part.trim());
655
+ const result = {};
656
+ // get the id or the number
657
+ if (line.startsWith('*')) {
658
+ result.id = line.split(/\s/).shift();
659
+ }
660
+ else if (!isNaN(parseInt(line))) {
661
+ result.idx = parseInt(line);
662
+ }
663
+ result.flags = flags.includes('=') ? '' : flags;
664
+ return result;
665
+ })();
666
+ /**
667
+ * This looks like a mess; however, RouterOS will return values with spaced
668
+ * which causes havoc if a simple regex matcher is applied to the key-value pairs
669
+ */
670
+ do {
671
+ const middle = line.indexOf('=') + 1;
672
+ const start = (() => {
673
+ const start = line.substring(0, middle).lastIndexOf(' ');
674
+ return start === -1 ? 0 : start;
704
675
  })();
705
- /**
706
- * This looks like a mess; however, RouterOS will return values with spaced
707
- * which causes havoc if a simple regex matcher is applied to the key-value pairs
708
- */
709
- do {
710
- const middle = line.indexOf('=') + 1;
711
- const start = (() => {
712
- const start = line.substring(0, middle).lastIndexOf(' ');
713
- return start === -1 ? 0 : start;
714
- })();
715
- const next = (() => {
716
- const next = line.substring(middle).indexOf('=');
717
- return next === -1 ? -1 : next + middle;
718
- })();
719
- const end = (() => {
720
- if (next === -1)
721
- return -1;
722
- const end = line.substring(middle, next).lastIndexOf(' ');
723
- return end === -1 ? -1 : end + middle;
724
- })();
725
- const [key, value] = line.substring(start, end !== -1 ? end : undefined)
726
- .trim().split(/=/);
727
- if (parseFloat(value).toString() === value) {
728
- result[key] = parseFloat(value);
729
- }
730
- else if (parseInt(value).toString() === value) {
731
- result[key] = parseInt(value);
732
- }
733
- else {
734
- result[key] = value;
735
- }
736
- if (end > 0) {
737
- line = line.substring(end);
738
- }
739
- else {
740
- line = '';
741
- }
742
- } while (line.length !== 0);
743
- if (Object.keys(result).length !== 0)
744
- results.push(result);
745
- });
746
- return results;
676
+ const next = (() => {
677
+ const next = line.substring(middle).indexOf('=');
678
+ return next === -1 ? -1 : next + middle;
679
+ })();
680
+ const end = (() => {
681
+ if (next === -1)
682
+ return -1;
683
+ const end = line.substring(middle, next).lastIndexOf(' ');
684
+ return end === -1 ? -1 : end + middle;
685
+ })();
686
+ const [key, value] = line.substring(start, end !== -1 ? end : undefined)
687
+ .trim().split(/=/);
688
+ if (parseFloat(value).toString() === value) {
689
+ result[key] = parseFloat(value);
690
+ }
691
+ else if (parseInt(value).toString() === value) {
692
+ result[key] = parseInt(value);
693
+ }
694
+ else {
695
+ result[key] = value;
696
+ }
697
+ if (end > 0) {
698
+ line = line.substring(end);
699
+ }
700
+ else {
701
+ line = '';
702
+ }
703
+ } while (line.length !== 0);
704
+ if (Object.keys(result).length !== 0)
705
+ results.push(result);
747
706
  });
707
+ return results;
748
708
  }
749
709
  /**
750
710
  * Executes a command that expects the result as a 'table' of key-value pairs separated by a colon (:)
@@ -752,28 +712,29 @@ class Mikrotik extends ssh_1.default {
752
712
  * @param command
753
713
  * @protected
754
714
  */
755
- kvs(command) {
756
- return __awaiter(this, void 0, void 0, function* () {
757
- const result = {};
758
- const lines = (yield this.exec(command))
759
- .toString()
760
- .split('\r\n')
761
- .map(line => line.trim())
762
- .filter(line => line.length !== 0)
763
- .map(line => line.split(':')
764
- .map(col => col.trim()));
765
- for (const [key, ...value] of lines) {
766
- result[key] = value.join(':');
767
- }
768
- return result;
769
- });
715
+ async kvs(command) {
716
+ const result = {};
717
+ const lines = (await this.exec(command))
718
+ .toString()
719
+ .split('\r\n')
720
+ .map(line => line.trim())
721
+ .filter(line => line.length !== 0)
722
+ .map(line => line.split(':')
723
+ .map(col => col.trim()));
724
+ for (const [key, ...value] of lines) {
725
+ result[key] = value.join(':');
726
+ }
727
+ return result;
728
+ }
729
+ /**
730
+ * Destroys the SSH connection and stops the internal cache timer
731
+ */
732
+ async destroy() {
733
+ await super.destroy();
734
+ await Mikrotik.cache.disconnect();
770
735
  }
771
736
  }
772
737
  exports.Mikrotik = Mikrotik;
773
- Mikrotik.cache = new memory_1.default({
774
- stdTTL: 15,
775
- checkperiod: 17
776
- });
777
738
  (function (Mikrotik) {
778
739
  let BandwidthTest;
779
740
  (function (BandwidthTest) {
@@ -781,6 +742,8 @@ Mikrotik.cache = new memory_1.default({
781
742
  * Class that helps with processing a "frame" of the Bandwidth Test response
782
743
  */
783
744
  class Frame {
745
+ buffer;
746
+ parsed;
784
747
  constructor(buffer) {
785
748
  this.buffer = buffer;
786
749
  }