# @fibercom/routeros-api

A TypeScript and Node.js client for the MikroTik RouterOS API.

The library supports plain TCP and TLS connections, tagged concurrent commands,
continuous data streams, connection retries, RouterOS traps, and convenience
methods for common router operations.

## Features

- TypeScript declarations and strict type checking
- RouterOS API over TCP or TLS
- Tagged commands on a shared connection
- Long-running streams with pause, resume, and close support
- Safe incremental decoding across fragmented TCP packets
- RouterOS `!re`, `!done`, `!trap`, `!fatal`, and `!empty` replies
- DHCP lease, firewall, route, interface, user, log, and system helpers
- Legacy RouterOS challenge-response login compatibility
- Configurable retries, timeouts, keepalive, and debug output

## Requirements

- Node.js 14 or newer
- MikroTik RouterOS API service enabled
- Network and firewall access to the API port

RouterOS normally uses:

- `8728` for the unencrypted API service
- `8729` for API-SSL

## Installation

```bash
npm install @fibercom/routeros-api
```

## Basic usage

```js
const { MikrotikAPI } = require("@fibercom/routeros-api");

async function main() {
  const api = new MikrotikAPI({
    host: "192.168.88.1",
    user: "api-user",
    password: "your-password",
    timeout: 10,
  });

  api.on("error", (error) => {
    console.error("Router error:", error.message);
  });

  api.on("timeout", (error) => {
    console.error("Router timeout:", error.message);
  });

  try {
    await api.connect();

    const identity = await api.getSystemIdentity();
    const resources = await api.getSystemResources();

    console.log({ identity, resources });
  } finally {
    if (api.connected) await api.close();
  }
}

main().catch(console.error);
```

## TypeScript usage

```ts
import { MikrotikAPI, IRosOptions } from "@fibercom/routeros-api";

const options: IRosOptions = {
  host: "192.168.88.1",
  user: "api-user",
  password: "your-password",
  timeout: 10,
};

const api = new MikrotikAPI(options);

await api.connect();
const interfaces = await api.getInterfaces();
await api.close();
```

## TLS connection

Use RouterOS API-SSL whenever the connection crosses an untrusted network.

```js
const api = new MikrotikAPI({
  host: "router.example.com",
  user: "api-user",
  password: "your-password",
  tls: true,
});
```

When `tls` is enabled and `port` is omitted, the library uses port `8729`.
You can pass Node.js TLS options instead of `true`:

```js
const fs = require("fs");

const api = new MikrotikAPI({
  host: "router.example.com",
  user: "api-user",
  password: "your-password",
  tls: {
    ca: fs.readFileSync("router-ca.pem"),
    servername: "router.example.com",
    rejectUnauthorized: true,
  },
});
```

Port `8728` is unencrypted. Usernames, passwords, commands, and responses can be
observed by anyone able to inspect that network traffic.

## Connection retries

```js
await api.connectWithRetry(3, 1000);
```

Arguments:

1. Maximum connection attempts
2. Delay between attempts in milliseconds

A socket timeout produces:

```text
Connection timeout. Possible firewall enabled, RouterOS API service disabled, or host unreachable.
```

## Raw RouterOS commands

Use `write()` for any RouterOS API command that does not have a convenience
method:

```js
const addresses = await api.write([
  "/ip/address/print",
  "?interface=bridge",
]);
```

The method accepts either one array or multiple command arguments:

```js
const identity = await api.write("/system/identity/print");

await api.write(
  "/system/identity/set",
  "=name=core-router"
);
```

RouterOS response values are returned as strings in plain JavaScript objects.

## DHCP leases

Get every lease:

```js
const leases = await api.getDhcpLeases();
```

Filter by an exact DHCP server name:

```js
const leases = await api.getDhcpLeases("Dhcp-mgmt");
```

RouterOS query values are case-sensitive. Use the exact server name returned by:

```js
const servers = await api.write("/ip/dhcp-server/print");
```

Add a static lease:

```js
await api.addDhcpLease({
  address: "192.168.88.20",
  macAddress: "00:11:22:33:44:55",
  server: "Dhcp-mgmt",
  comment: "Office printer",
  disabled: false,
});
```

Update a lease:

```js
await api.setDhcpLease("*1A", {
  address: "192.168.88.21",
  comment: "Updated printer address",
});
```

Block or unblock a lease using the `Blocked` firewall address list:

```js
await api.blockDhcpLeaseById("*1A", "Blocked by billing");
await api.unblockDhcpLeaseById("*1A");
```

Some DHCP helpers update multiple RouterOS resources sequentially. If a command
fails midway, earlier router changes are not automatically rolled back.

## Live interface traffic

```js
const stream = api.streamInterfaceStats(
  {
    interface: "sfp-sfpplus1",
    interval: 1,
  },
  (error, packet) => {
    if (error) {
      console.error(error.message);
      return;
    }

    console.log(
      packet["rx-bits-per-second"],
      packet["tx-bits-per-second"]
    );
  }
);

await new Promise((resolve) => setTimeout(resolve, 5000));
await stream.pause();

await new Promise((resolve) => setTimeout(resolve, 3000));
await stream.resume();

await new Promise((resolve) => setTimeout(resolve, 5000));
await stream.close();
```

A paused stream remains registered with the API. Resuming creates a fresh
RouterOS command tag. Calling `close()` or `stop()` performs final cleanup.

Streams also emit events:

```js
stream.on("data", (packet) => console.log(packet));
stream.on("paused", () => console.log("paused"));
stream.on("started", () => console.log("started"));
stream.on("done", () => console.log("done"));
stream.on("close", () => console.log("closed"));
stream.on("stopped", () => console.log("stopped"));
stream.on("trap", (trap) => console.error("trap", trap));
stream.on("error", (error) => console.error(error));
```

## Convenience methods

### System

- `getSystemInfo()`
- `getSystemResources()`
- `getSystemIdentity()`
- `setSystemIdentity(name)`
- `getSystemLogs(topics?)`
- `streamSystemLogs(callback?)`
- `reboot()`
- `exportConfig(filename?)`
- `importConfig(filename)`

### Interfaces and networking

- `getInterfaces()`
- `getIPAddresses()`
- `getInterfaceStats(interfaceName?)`
- `streamInterfaceStats(options, callback?)`
- `getBridges()`
- `getRoutes()`
- `addRoute(destination, gateway, distance?)`

### DHCP

- `getDhcpLeases(serverName?)`
- `addDhcpLease(lease)`
- `setDhcpLease(id, parameters)`
- `setDhcpLeaseAndUpdateAddressLists(id, parameters)`
- `removeDhcpLease(id)`
- `getDhcpServersWithGateways()`
- `getFreeIpsForDhcpServer(serverName, gatewayCidr)`
- `blockDhcpLeaseById(id, comment?)`
- `unblockDhcpLeaseById(id)`

### Firewall, wireless, and users

- `getFirewallRules(chain?)`
- `addFirewallRule(...)`
- `getWirelessInterfaces()`
- `getWirelessRegistrations()`
- `streamWirelessRegistrations(callback?)`
- `getUsers()`
- `addUser(name, password, group?)`
- `runScript(scriptName)`

## Connection options

```ts
interface IRosOptions {
  host: string;
  user?: string;
  password?: string;
  port?: number;
  timeout?: number;
  tls?: boolean | TlsOptions;
  keepalive?: boolean;
  debug?: boolean;
}
```

`timeout` is measured in seconds.

## Errors

RouterOS and connection failures use `RosException`:

```js
const { RosException } = require("@fibercom/routeros-api");

try {
  await api.connect();
} catch (error) {
  if (error instanceof RosException) {
    console.error(error.errno, error.message);
  } else {
    throw error;
  }
}
```

RouterOS `!trap` replies reject command promises. Listen for the API's `error`
and `timeout` events when maintaining a long-lived connection.

## Live router smoke test

The repository includes a read-only test in `index.js`. It reads router system
information, interfaces, DHCP leases, and exercises traffic streaming with this
sequence:

```text
start -> 5 seconds -> pause -> 3 seconds -> resume -> 5 seconds -> close
```

Copy `.env.example` to `.env` and configure it:

```dotenv
ROUTEROS_HOST=192.168.88.1
ROUTEROS_PORT=8728
ROUTEROS_USER=api-user
ROUTEROS_PASSWORD=change-me
ROUTEROS_TIMEOUT=10
ROUTEROS_TLS=false
ROUTEROS_DEBUG=false
ROUTEROS_TEST_INTERFACE=sfp-sfpplus1
ROUTEROS_DHCP_SERVER=Dhcp-mgmt
```

Then run:

```bash
npm run test:router
```

The `.env` file is ignored by Git. Do not commit router credentials.

## Development

Install dependencies:

```bash
npm install
```

Build the library:

```bash
npm run build
```

Run strict compilation and the automated tests:

```bash
npm test
```

Watch TypeScript sources:

```bash
npm run watch
```

Compiled JavaScript and declarations are written to `dist/`.

## Security

- Prefer API-SSL on port `8729`.
- Restrict API access with RouterOS firewall rules and `/ip service` address
  restrictions.
- Use a dedicated RouterOS user with only the permissions your application
  needs.
- Never commit passwords, `.env` files, private keys, or router backups.
- Test configuration-changing helpers on a non-production router first.

## License

MIT
