Mikronode

repository·release·Indexed 42 days ago

https://github.com/trakkasure/mikronode

A full-featured, asynchronous MikroTik API interface for Node.js (version 2.3.11). It utilizes a channel-based communication model to allow simultaneous execution of commands over a single connection to RouterOS devices. The library supports Promise-based workflows, RxJS Observables for data streams, TLS configuration, and includes a utility to convert MikroTik sentence format into standard JavaScript objects.

Tokens
4.5K
Snippets
11
Records
15
Agent score
4%

What's inside Mikronode

  1. How Mikronode channels and connections work

    release

    Mikronode uses a channel-based communication model to interact with Mikrotik devices.

    • Connection: Represents the physical/network connection to the Mikrotik device. A single connection can host multiple channels.
    • Channel: Represents a unique command line to the Mikrotik. This allows for simultaneous execution of commands on different channels (asynchronous) while maintaining synchronous execution of commands issued on the same channel.

    This architecture allows for high performance by enabling multiple independent command streams over a single connection.

  2. Use synchronous mode for sequential commands

    release

    If you want to execute multiple commands sequentially without deeply nested callbacks, use .sync(true) on a channel. This allows you to call .write() multiple times in a row, and they will be processed synchronously on that channel.

    Note: When using sync(true), ensure you handle the 'done' event on the channel to know when the sequence of commands is complete before closing the channel or connection.

        var MikroNode = require('mikronode');
    
        var device = new MikroNode('192.168.0.1');
         device.connect().then(([login])=>login('admin','password')).then(function(conn) {
            conn.closeOnDone(true); 
            var listenChannel=conn.openChannel();
            listenChannel.write('/interface/listen');
    
            listenChannel.read.subscribe(function(data) {
                var packet=MikroNode.resultsToObj(data);
                console.log('Interface change: '+JSON.stringify(packet));
            });
    
            var actionChannel=conn.openChannel();
            actionChannel.sync(true);
    
            // These will run synchronsously
            actionChannel.write('/interface/set',{'disabled':'yes','.id':'ether1'});
            actionChannel.write('/interface/set',{'disabled':'no','.id':'ether1'});
            actionChannel.write('/interface/getall');
            actionChannel.on('done',function(packet) {
                packet.data.forEach(function(data) {
                    var packets=MikroNode.resultsToObj(data);
                    console.log('Interface: '+JSON.stringify(packet));
                });
                listenChannel.close();
            });
            actionChannel.close();
        });
  3. Use Promises and Streams for complex workflows

    release

    Mikronode supports a Promise-based API which is ideal for complex workflows involving both continuous data streams (like /interface/listen) and discrete actions (like /interface/set).

    Key patterns:

    • Streams: Use channel.data.subscribe(...) to listen to incoming data packets from the device.
    • Promises: channel.write(command, params) returns a Promise that resolves when the command is completed.
    • Unwrapped Promises: Use MikroNode.getUnwrappedPromise() to create a manual promise that you can resolve/reject based on specific stream events (e.g., when a specific data packet arrives).
    • Connection Lifecycle: Use conn.closeOnDone(true) to ensure the connection stays open until all active channels have finished their work.
        var MikroNode = require('mikronode');
        var device = new MikroNode('192.168.0.1');
         device.connect().then(([login])=>login('admin','password')).then(function(conn) {
            conn.closeOnDone(true);
            var listenChannel=conn.openChannel("listen");
    
            listenChannel.data.subscribe(function(data) {
                console.log('Interface change: ',JSON.stringify(data));
            });
    
            var actionChannel=conn.openChannel("action",false);
            actionChannel.sync(false);
            actionChannel.closeOnDone(false);
    
            actionChannel.write('/interface/set',{'disabled':'yes','.id':'ether1'}).then(results=>{
                const {promise,resolve,reject}=MikroNode.getUnwrappedPromise();
                listenChannel.data
                    .take(1)
                    .subscribe(d=>actionChannel.write('/interface/set',{'disabled':'no','.id':'ether1'}).then(resolve,reject));
                return promise;
            })
            .then(results=>{
                // ... further chaining
            });
        });
  4. Initialize and connect to MikroTik using MikroNode

    release

    The MikroNode class is the primary entry point for interacting with MikroTik devices via their API. You instantiate it with a host, an optional port (default 8728), and an optional timeout.

    To establish a session, call .connect(user, password). This returns a Promise that resolves to a login function and connection metadata. You must then use the returned login function to complete the authentication process.

    Important: If you do not listen for error or timeout events during the initial connection, an "Unhandled 'error' event" exception will be thrown if the host is unreachable or connection is refused.

    var MikroNode = require('mikronode');
    
    // Initialize the client
    var device = new MikroNode('192.168.88.1', 8728, 5);
    
    // Connect and authenticate
    device.connect('admin', 'mypassword')
      .then(function(result) {
        // result contains [login, socketOpts, ...args]
        var login = result[0];
        console.log('Connected and ready to login');
      })
      .catch(function(err) {
        console.error('Connection error: ', err);
      });
  5. Connect to a Mikrotik and add an IP address

    release

    To perform basic operations like adding an IP address to an interface, follow this pattern:

    1. Instantiate the api with the device IP.
    2. Call .connect() and then .login(user, password).
    3. Open a channel using .openChannel().
    4. Use .write(command, params) to send the command.
    5. Listen for 'trap' (errors) or 'done' (success) events on the channel.
    6. Close the channel and connection.
         var api = require('mikronode');
    
         var device = new api('192.168.0.1');
         device.connect().then(([login])=>login('admin','password')).then(function(conn) {
    
            var chan=conn.openChannel();
    
            chan.write('/ip/address/add',{'interface':'ether1','address':'192.168.1.1'});
            chan.on('trap',function(data) {
                console.log('Error setting IP: '+data);
            });
            chan.on('done',function(data) {
                console.log('IP Set.');
            });
            chan.close();
            conn.close();
         });
  6. Convert Mikrotik sentence format to objects

    release

    Mikrotik API returns data in a specific sentence format (often an array of field items). Use MikroNode.resultsToObj(dataObj) to convert these into standard JavaScript objects for easier manipulation.

    // Assuming 'item.data' is the raw sentence array from a channel
    var data = MikroNode.resultsToObj(item.data);
    console.log(data.interface, data.address);
  7. Execute commands using Channels

    release

    A Channel is used to send commands to the device. You can interact with channels via events (Observables) or Promises.

    Writing Commands

    Use channel.write(lines[, optionsObject]).

    • If lines is a string: It is split by EOL and sent as separate words. If optionsObject is provided, it is converted to =propertyName=propertyValue format.
    • If lines is an array: Each element is sent unaltered.
    • Returns a Promise that resolves when the command is done and rejects on a trap or fatal error.

    Handling Results

    • channel.done (Observable): Emits when the done sentence arrives. The data contains an array of all lines received for the last command.
    • channel.data (Observable): Emits every sentence designated for this channel. Useful for continuous streams (e.g., listen commands).
    • channel.trap (Observable): Captures any traps occurring on the channel.

    Channel Configuration

    • channel.sync(b): If true, commands are run synchronously. If false, they are executed as they are passed.
    • channel.closeOnDone(b): If true, the channel closes automatically after the done event and all queued commands are executed.
    • channel.close(force): Closes the channel. If force is true, it immediately sends a cancel command to stop running listen commands or long outputs.
    var chan = conn.openChannel("addresses");
    
    // Using Promises
    chan.write('/ip/address/print').then(() => {
      console.log('Command finished');
    });
    
    // Using Observables (Events)
    chan.on('done', function(data) {
      data.forEach(item => {
        var obj = MikroNode.resultsToObj(item.data);
        console.log(obj.interface, obj.address);
      });
      chan.close();
    });
  8. Initialize and connect to a Mikrotik device

    release

    To use Mikronode, instantiate a MikroNode object with the host address, then call .connect() to establish the connection. The connection process typically requires a login step using the function returned by the connection promise.

    new MikroNode(host[, port, socketTimeout]) returns the device object.

    var MikroNode = require('mikronode');
    var device = new MikroNode('192.168.0.1');
    
    device.connect()
      .then(([login]) => {
        return login('username', 'password');
      })
      .then((conn) => {
        // 'conn' is the Connection object
        console.log('Connected and logged in');
      });
  9. Manage Mikrotik connections

    release

    The Connection object (returned after successful login) provides methods to manage the session and its channels.

    MethodDescription
    conn.openChannel(id|name)Opens and returns a new Channel object. If id is omitted, a timestamp is used. Providing a name helps avoid duplicates.
    conn.closeChannel(id)Closes an open channel.
    conn.close(force)Closes the connection. If force is true, it forces all open channels to close first.
    conn.connected()Returns true if currently connected.
    conn.getHost()Returns the host address.
    conn.getUser()Returns the username.
    conn.closeOnDone(b)If b == true, the connection will close after all channels have been closed once a done event occurs.
  10. Use MikroNode with RxJS Observables

    release

    MikroNode is designed to work seamlessly with RxJS. The connection process returns Promises that can be converted to Observables. Once connected, you can open channels that expose data as observable streams, allowing for powerful stream manipulation like merge, map, and filter across multiple devices.

    var MikroNode = require('mikronode');
    var { Observable } = require('rxjs');
    
    var device1 = new MikroNode('192.168.88.1');
    var device2 = new MikroNode('192.168.88.2');
    
    // Convert connection promises to Observables
    var promise1 = Observable.fromPromise(device1.connect('admin', 'pass'));
    var promise2 = Observable.fromPromise(device2.connect('admin', 'pass'));
    
    Observable.zip(promise1, promise2).subscribe(function(connections) {
        connections[0].closeOnDone(true);
        connections[1].closeOnDone(true);
    
        // Open channels (null for auto-select channel number)
        var channel1 = connections[0].openChannel(null, true);
        var channel2 = connections[1].openChannel(null, true);
    
        // Merge streams from both devices and filter out 'trap' messages
        channel1.data.merge(channel2.data)
            .filter(function(sentence) { return sentence.type !== 'trap'; })
            .subscribe(function(sentence) {
                console.log(sentence);
            });
    });
  11. Configure TLS for MikroNode connections

    release

    To use TLS (typically on port 8729), use the .TLS(opts) method. You can pass a boolean true for default TLS settings, or an object containing Node.js tls.connect() options.

    If your device uses self-signed certificates, you must pass an object with rejectUnauthorized: false or provide the proper CA certificate within the options object.

    var MikroNode = require('mikronode');
    var device = new MikroNode('192.168.88.1');
    
    // Configure TLS with options for self-signed certificates
    device.TLS({
      rejectUnauthorized: false
    });
    
    device.connect('admin', 'mypassword');