From 5f5f11bd188530890018c590e03748bb0574e591 Mon Sep 17 00:00:00 2001 From: coorvus Date: Tue, 24 Mar 2026 11:51:11 +0000 Subject: [PATCH] add typescript --- Refrence.js | 848 ------------------ Refrence.ts | 487 ++++++++++ app.js | 190 ---- app.ts | 201 +++++ index.js | 9 - index.ts | 3 + lib/{connect.js => connect.ts} | 143 +-- os.ts | 32 + package.json | 14 +- pnpm-lock.yaml | 438 +++++++++ test/{client.test.js => client.test.ts} | 80 +- test/{config.example.js => config.example.ts} | 4 +- test/{system.test.js => system.test.ts} | 41 +- tsconfig.json | 16 + 14 files changed, 1324 insertions(+), 1182 deletions(-) delete mode 100644 Refrence.js create mode 100644 Refrence.ts delete mode 100644 app.js create mode 100644 app.ts delete mode 100644 index.js create mode 100644 index.ts rename lib/{connect.js => connect.ts} (67%) create mode 100644 os.ts create mode 100644 pnpm-lock.yaml rename test/{client.test.js => client.test.ts} (61%) rename test/{config.example.js => config.example.ts} (73%) rename test/{system.test.js => system.test.ts} (62%) create mode 100644 tsconfig.json diff --git a/Refrence.js b/Refrence.js deleted file mode 100644 index 415dfc9..0000000 --- a/Refrence.js +++ /dev/null @@ -1,848 +0,0 @@ -/** - * RouterOS API Commands Reference Documentation - * - * This file contains a comprehensive reference of all RouterOS API commands - * organized by category with detailed JSDoc documentation. - * - * Each command is provided as either: - * - Static array for simple commands: ["/path/to/command"] - * - Function for parameterized commands: (param) => ["/path/to/command", "=param=value"] - * - * Usage with RouterOS API Client: - * const result = await api.send(SYSTEM_COMMANDS.getIdentity); - * const result = await api.send(SYSTEM_COMMANDS.setIdentity("new-name")); - * - * @version 1.1.0 - * @author AviStudio (https://github.com/AviStudio) - * @contributor RouterOS API Client Library - */ - -/** - * RouterOS API Commands Reference (with JSDoc) - * - * Each command is a function or array, returning the proper array format for the MikroTik API. - * Use these with your API client: send(...command) - */ - -// ===================== SYSTEM COMMANDS ===================== - -/** - * System-level commands for basic info, management, and users. - */ -const SYSTEM_COMMANDS = { - /** - * Get system identity (hostname). - * @returns {string[]} Command array. - */ - getIdentity: ["/system/identity/print"], - - /** - * Set system identity (hostname). - * @param {string} name - The new identity. - * @returns {string[]} Command array. - */ - setIdentity: (name) => { - if (!name) throw new Error("Name is required for setIdentity"); - return ["/system/identity/set", `=name=${name}`]; - }, - - /** - * Get system resource usage and details. - * @returns {string[]} Command array. - */ - getResources: ["/system/resource/print"], - - /** - * Get routerboard hardware info. - * @returns {string[]} Command array. - */ - getRouterboard: ["/system/routerboard/print"], - - /** - * Get system health (voltages, temperature, etc). - * @returns {string[]} Command array. - */ - getHealth: ["/system/health/print"], - - /** - * Get current system date/time. - * @returns {string[]} Command array. - */ - getClock: ["/system/clock/print"], - - /** - * Set system date and time. - * @param {{date: string, time: string}} param0 - Object with 'date' (YYYY-MM-DD) and 'time' (HH:MM:SS). - * @returns {string[]} Command array. - */ - setClock: ({ date, time }) => { - if (!date || !time) - throw new Error("Date and time are required for setClock"); - return ["/system/clock/set", `=date=${date}`, `=time=${time}`]; - }, - - /** - * Get command history. - * @returns {string[]} Command array. - */ - getHistory: ["/system/history/print"], - - /** - * Get system license details. - * @returns {string[]} Command array. - */ - getLicense: ["/system/license/print"], - - /** - * Get system log entries. - * @returns {string[]} Command array. - */ - getLogs: ["/log/print"], - - /** - * Get logging topics. - * @returns {string[]} Command array. - */ - getLogTopics: ["/system/logging/print"], - - /** - * Add a new logging topic/action. - * @param {string} topic - The logging topic (e.g., "firewall"). - * @param {string} action - The action (e.g., "echo", "memory", etc). - * @returns {string[]} Command array. - */ - addLogTopic: (topic, action) => { - if (!topic || !action) - throw new Error("Topic and action are required for addLogTopic"); - return ["/system/logging/add", `=topics=${topic}`, `=action=${action}`]; - }, - - /** - * Get all users. - * @returns {string[]} Command array. - */ - getUsers: ["/user/print"], - - /** - * Add a new user. - * @param {string} name - Username. - * @param {string} password - User's password. - * @param {string} group - User group (e.g., "full"). - * @returns {string[]} Command array. - */ - addUser: (name, password, group) => { - if (!name || !password || !group) - throw new Error("Name, password, and group are required for addUser"); - return [ - "/user/add", - `=name=${name}`, - `=password=${password}`, - `=group=${group}`, - ]; - }, - - /** - * Remove a user by ID. - * @param {string} id - User's .id (from /user/print). - * @returns {string[]} Command array. - */ - removeUser: (id) => { - if (!id) throw new Error("User ID is required for removeUser"); - return ["/user/remove", `=.id=${id}`]; - }, - - /** - * Reboot the router. - * @returns {string[]} Command array. - */ - reboot: ["/system/reboot"], - - /** - * Shutdown the router. - * @returns {string[]} Command array. - */ - shutdown: ["/system/shutdown"], - - /** - * Check for RouterOS package updates. - * @returns {string[]} Command array. - */ - checkForUpdates: ["/system/package/update/check-for-updates"], - - /** - * Install available updates. - * @returns {string[]} Command array. - */ - upgradePackages: ["/system/package/update/install"], -}; - -// ===================== INTERFACE COMMANDS ===================== - -/** - * Commands for working with network interfaces, bridges, VLANs, etc. - */ -const INTERFACE_COMMANDS = { - /** - * Get all interfaces. - * @returns {string[]} Command array. - */ - getAll: ["/interface/print"], - - /** - * Get interfaces of a specific type (ether, vlan, wireless, etc). - * @param {string} type - Interface type. - * @returns {string[]} Command array. - */ - getByType: (type) => { - if (!type) throw new Error("Type is required for getByType"); - return ["/interface/print", `?type=${type}`]; - }, - - /** - * Enable an interface by ID. - * @param {string} id - Interface .id. - * @returns {string[]} Command array. - */ - enable: (id) => { - if (!id) throw new Error("Interface ID is required for enable"); - return ["/interface/enable", `=.id=${id}`]; - }, - - /** - * Disable an interface by ID. - * @param {string} id - Interface .id. - * @returns {string[]} Command array. - */ - disable: (id) => { - if (!id) throw new Error("Interface ID is required for disable"); - return ["/interface/disable", `=.id=${id}`]; - }, - - /** - * Monitor traffic for an interface (real-time). - * @param {string} iface - Interface name. - * @returns {string[]} Command array. - */ - monitorTraffic: (iface) => { - if (!iface) - throw new Error("Interface name is required for monitorTraffic"); - return ["/interface/monitor-traffic", `=interface=${iface}`]; - }, - - /** - * Get all ethernet interfaces. - * @returns {string[]} Command array. - */ - getEthernet: ["/interface/ethernet/print"], - - /** - * Set MTU for ethernet interface. - * @param {string} id - Ethernet interface .id. - * @param {string|number} mtu - MTU value. - * @returns {string[]} Command array. - */ - setEthernetMtu: (id, mtu) => { - if (!id || !mtu) throw new Error("ID and MTU required for setEthernetMtu"); - return ["/interface/ethernet/set", `=.id=${id}`, `=mtu=${mtu}`]; - }, - - /** - * Get all bridges. - * @returns {string[]} Command array. - */ - getBridges: ["/interface/bridge/print"], - - /** - * Add a bridge. - * @param {string} name - Bridge name. - * @returns {string[]} Command array. - */ - addBridge: (name) => { - if (!name) throw new Error("Bridge name required"); - return ["/interface/bridge/add", `=name=${name}`]; - }, - - /** - * Add a port to a bridge. - * @param {string} bridge - Bridge name or ID. - * @param {string} iface - Interface to add. - * @returns {string[]} Command array. - */ - addBridgePort: (bridge, iface) => { - if (!bridge || !iface) throw new Error("Bridge and interface required"); - return [ - "/interface/bridge/port/add", - `=bridge=${bridge}`, - `=interface=${iface}`, - ]; - }, - - /** - * Get all VLANs. - * @returns {string[]} Command array. - */ - getVlans: ["/interface/vlan/print"], - - /** - * Add a VLAN interface. - * @param {string} name - VLAN name. - * @param {string|number} vlanId - VLAN ID. - * @param {string} iface - Parent interface. - * @returns {string[]} Command array. - */ - addVlan: (name, vlanId, iface) => { - if (!name || !vlanId || !iface) - throw new Error("Name, vlanId, and interface required"); - return [ - "/interface/vlan/add", - `=name=${name}`, - `=vlan-id=${vlanId}`, - `=interface=${iface}`, - ]; - }, -}; - -// ===================== IP COMMANDS ===================== - -/** - * IP-level commands: addresses, DHCP, DNS, firewall, NAT, etc. - */ -const IP_COMMANDS = { - /** - * Get all IP addresses. - * @returns {string[]} Command array. - */ - getAddresses: ["/ip/address/print"], - - /** - * Add an IP address to an interface. - * @param {string} address - IP/CIDR (e.g. 192.168.88.10/24). - * @param {string} iface - Interface name. - * @returns {string[]} Command array. - */ - addAddress: (address, iface) => { - if (!address || !iface) throw new Error("Address and interface required"); - return ["/ip/address/add", `=address=${address}`, `=interface=${iface}`]; - }, - - /** - * Remove an IP address by ID. - * @param {string} id - Address .id. - * @returns {string[]} Command array. - */ - removeAddress: (id) => { - if (!id) throw new Error("ID required for removeAddress"); - return ["/ip/address/remove", `=.id=${id}`]; - }, - - /** - * Get all DHCP servers. - * @returns {string[]} Command array. - */ - getDhcpServer: ["/ip/dhcp-server/print"], - - /** - * Add a DHCP server. - * @param {string} name - Server name. - * @param {string} iface - Interface. - * @param {string} pool - Address pool. - * @returns {string[]} Command array. - */ - addDhcpServer: (name, iface, pool) => { - if (!name || !iface || !pool) - throw new Error("Name, interface, addressPool required"); - return [ - "/ip/dhcp-server/add", - `=name=${name}`, - `=interface=${iface}`, - `=address-pool=${pool}`, - ]; - }, - - /** - * Get all DHCP leases. - * @returns {string[]} Command array. - */ - getDhcpLeases: ["/ip/dhcp-server/lease/print"], - - /** - * Add a DHCP lease. - * @param {string} address - IP address to lease. - * @param {string} macAddress - MAC address of the device. - * @param {string} [server] - (Optional) DHCP server name. - * @param {string} [comment] - (Optional) Comment (will be uppercased). - * @returns {string[]} Command array. - */ - addDhcpLease: (address, macAddress, server, comment) => { - if (!address || !macAddress) - throw new Error("Address and MAC address required for addDhcpLease"); - const command = [ - "/ip/dhcp-server/lease/add", - `=address=${address}`, - `=mac-address=${macAddress}`, - ]; - if (server) command.push(`=server=${server}`); - if (comment) command.push(`=comment=${comment.toUpperCase()}`); - return command; - }, - - /** - * Update a DHCP lease by ID. - * @param {string} id - Lease .id (from /ip/dhcp-server/lease/print). - * @param {Object} params - Lease parameters to set/update. - * @param {string} [params.address] - IP address to set. - * @param {string} [params.macAddress] - MAC address to set. - * @param {string} [params.server] - DHCP server name. - * @param {string} [params.comment] - Comment (will be uppercased). - * @param {string} [params.disabled] - "yes" or "no" to enable/disable lease. - * @returns {string[]} Command array. - */ - setDhcpLease: (id, params = {}) => { - if (!id) throw new Error("Lease ID required for setDhcpLease"); - const command = ["/ip/dhcp-server/lease/set", `=.id=${id}`]; - for (const [key, value] of Object.entries(params)) { - if (key === "comment" && typeof value === "string") { - command.push(`=comment=${value.toUpperCase()}`); - } else { - command.push(`=${key}=${value}`); - } - } - return command; - }, - - /** - * Remove a DHCP lease by ID. - * @param {string} id - Lease .id. - * @returns {string[]} Command array. - */ - removeDhcpLease: (id) => { - if (!id) throw new Error("Lease ID required for removeDhcpLease"); - return ["/ip/dhcp-server/lease/remove", `=.id=${id}`]; - }, - - /** - * Get DNS server settings. - * @returns {string[]} Command array. - */ - getDnsSettings: ["/ip/dns/print"], - - /** - * Set DNS servers. - * @param {string} servers - DNS servers (comma separated). - * @returns {string[]} Command array. - */ - setDnsServers: (servers) => { - if (!servers) throw new Error("Servers required for setDnsServers"); - return ["/ip/dns/set", `=servers=${servers}`]; - }, - - /** - * Get DNS cache entries. - * @returns {string[]} Command array. - */ - getDnsCache: ["/ip/dns/cache/print"], - - /** - * Get firewall filter rules. - * @returns {string[]} Command array. - */ - getFirewallFilter: ["/ip/firewall/filter/print"], - - /** - * Add a firewall filter rule. - * @param {string} chain - Chain name (input, forward, output). - * @param {string} action - Action (accept, drop, etc). - * @param {Object} params - Additional parameters (src-address, dst-address, comment, etc). - * @returns {string[]} Command array. - */ - addFirewallRule: (chain, action, params = {}) => { - if (!chain || !action) - throw new Error("Chain and action required for addFirewallRule"); - const command = [ - "/ip/firewall/filter/add", - `=chain=${chain}`, - `=action=${action}`, - ]; - for (const [key, value] of Object.entries(params)) { - command.push(`=${key}=${value}`); - } - return command; - }, - - /** - * Get NAT rules. - * @returns {string[]} Command array. - */ - getNatRules: ["/ip/firewall/nat/print"], - - /** - * Add a NAT rule. - * @param {string} chain - Chain name. - * @param {string} action - Action (src-nat, dst-nat, masquerade, etc). - * @param {Object} params - Additional parameters (src-address, dst-address, etc). - * @returns {string[]} Command array. - */ - addNatRule: (chain, action, params = {}) => { - if (!chain || !action) - throw new Error("Chain and action required for addNatRule"); - const command = [ - "/ip/firewall/nat/add", - `=chain=${chain}`, - `=action=${action}`, - ]; - for (const [key, value] of Object.entries(params)) { - command.push(`=${key}=${value}`); - } - return command; - }, - - /** - * Get all routes. - * @returns {string[]} Command array. - */ - getRoutes: ["/ip/route/print"], - - /** - * Add a route. - * @param {string} dst - Destination (e.g. 0.0.0.0/0). - * @param {string} gateway - Gateway IP. - * @returns {string[]} Command array. - */ - addRoute: (dst, gateway) => { - if (!dst || !gateway) - throw new Error("Destination and gateway required for addRoute"); - return ["/ip/route/add", `=dst-address=${dst}`, `=gateway=${gateway}`]; - }, - - /** - * Get IP service list (API, Winbox, etc). - * @returns {string[]} Command array. - */ - getServices: ["/ip/service/print"], - - /** - * Enable an IP service by name. - * @param {string} name - Service name. - * @returns {string[]} Command array. - */ - enableService: (name) => { - if (!name) throw new Error("Service name required"); - return ["/ip/service/set", `=name=${name}`, "=disabled=no"]; - }, - - /** - * Disable an IP service by name. - * @param {string} name - Service name. - * @returns {string[]} Command array. - */ - disableService: (name) => { - if (!name) throw new Error("Service name required"); - return ["/ip/service/set", `=name=${name}`, "=disabled=yes"]; - }, - - /** - * Add an address to a specific firewall address-list. - * @param {string} list - The address-list name. - * @param {string} address - The IP address to add. - * @param {string} [comment] - Optional comment for the entry (will be uppercased). - * @returns {string[]} API command array. - */ - addToAddressList: (list, address, comment) => { - if (!list || !address) - throw new Error("Address-list name and address are required"); - const command = [ - "/ip/firewall/address-list/add", - `=list=${list}`, - `=address=${address}`, - ]; - if (comment) command.push(`=comment=${comment.toUpperCase()}`); - return command; - }, - - /** - * Update an address-list entry by ID. - * @param {string} id - The .id of the address-list entry. - * @param {Object} params - Parameters to update (e.g. {address, list, comment, disabled}). - * @param {string} [params.address] - New IP address for the entry. - * @param {string} [params.list] - Address-list name (to move entry). - * @param {string} [params.comment] - Comment (will be uppercased). - * @param {string} [params.disabled] - "yes" or "no" to disable/enable this entry. - * @returns {string[]} API command array. - * - * @example - * // Update address and comment - * setAddressList("*8", { address: "192.168.88.201", comment: "permanent" }); - */ - setAddressList: (id, params = {}) => { - if (!id) throw new Error("Address-list ID required for setAddressList"); - const command = ["/ip/firewall/address-list/set", `=.id=${id}`]; - for (const [key, value] of Object.entries(params)) { - if (key === "comment" && typeof value === "string") { - command.push(`=comment=${value.toUpperCase()}`); - } else { - command.push(`=${key}=${value}`); - } - } - return command; - }, - - /** - * Remove an address-list entry by ID. - * @param {string} id - The .id of the address-list entry to remove. - * @returns {string[]} API command array. - */ - removeAddressList: (id) => { - if (!id) throw new Error("Address-list ID required for removeAddressList"); - return ["/ip/firewall/address-list/remove", `=.id=${id}`]; - }, -}; - -// ===================== QUEUE COMMANDS ===================== - -/** - * Queue management commands (simple, tree, types). - */ -const QUEUE_COMMANDS = { - /** - * Get all simple queues. - * @returns {string[]} Command array. - */ - getSimpleQueues: ["/queue/simple/print"], - - /** - * Add a simple queue. - * @param {string} name - Queue name. - * @param {string} target - Target address (IP or subnet). - * @param {string} maxLimit - Max bandwidth (e.g., "10M/10M"). - * @returns {string[]} Command array. - */ - addSimpleQueue: (name, target, maxLimit) => { - if (!name || !target || !maxLimit) - throw new Error("Name, target, and maxLimit required"); - return [ - "/queue/simple/add", - `=name=${name}`, - `=target=${target}`, - `=max-limit=${maxLimit}`, - ]; - }, - - /** - * Update a simple queue by ID. - * @param {string} id - Queue .id. - * @param {Object} params - Fields to update (name, target, max-limit, etc). - * @returns {string[]} Command array. - */ - updateSimpleQueue: (id, params = {}) => { - if (!id) throw new Error("ID required for updateSimpleQueue"); - const command = ["/queue/simple/set", `=.id=${id}`]; - for (const [key, value] of Object.entries(params)) { - command.push(`=${key}=${value}`); - } - return command; - }, - - /** - * Remove a simple queue by ID. - * @param {string} id - Queue .id. - * @returns {string[]} Command array. - */ - removeSimpleQueue: (id) => { - if (!id) throw new Error("ID required for removeSimpleQueue"); - return ["/queue/simple/remove", `=.id=${id}`]; - }, - - /** - * Get all tree queues. - * @returns {string[]} Command array. - */ - getTreeQueues: ["/queue/tree/print"], - - /** - * Get all queue types. - * @returns {string[]} Command array. - */ - getQueueTypes: ["/queue/type/print"], -}; - -// ===================== PPP COMMANDS ===================== - -/** - * PPP (Point-to-Point Protocol) management commands. - */ -const PPP_COMMANDS = { - /** - * Get all PPP profiles. - * @returns {string[]} Command array. - */ - getProfiles: ["/ppp/profile/print"], - - /** - * Get all PPP secrets. - * @returns {string[]} Command array. - */ - getSecrets: ["/ppp/secret/print"], - - /** - * Add a PPP secret (user). - * @param {string} name - Username. - * @param {string} password - Password. - * @param {string} service - Service type (pppoe, pptp, l2tp, etc). - * @returns {string[]} Command array. - */ - addSecret: (name, password, service) => { - if (!name || !password || !service) - throw new Error("Name, password, service required for addSecret"); - return [ - "/ppp/secret/add", - `=name=${name}`, - `=password=${password}`, - `=service=${service}`, - ]; - }, - - /** - * Get all active PPP connections. - * @returns {string[]} Command array. - */ - getActive: ["/ppp/active/print"], -}; - -// ===================== TOOLS COMMANDS ===================== - -/** - * Network tools and diagnostics (ping, traceroute, etc). - */ -const TOOLS_COMMANDS = { - /** - * Ping an address. - * @param {string} address - IP or hostname. - * @param {number} [count=4] - Number of pings. - * @returns {string[]} Command array. - */ - ping: (address, count = 4) => { - if (!address) throw new Error("Address required for ping"); - return ["/ping", `=address=${address}`, `=count=${count}`]; - }, - - /** - * Traceroute to an address. - * @param {string} address - IP or hostname. - * @returns {string[]} Command array. - */ - traceroute: (address) => { - if (!address) throw new Error("Address required for traceroute"); - return ["/tool/traceroute", `=address=${address}`]; - }, - - /** - * Run a bandwidth test. - * @param {string} address - Target IP/host. - * @param {string} [direction="both"] - "send", "receive", or "both". - * @returns {string[]} Command array. - */ - bandwidthTest: (address, direction = "both") => { - if (!address) throw new Error("Address required for bandwidthTest"); - return [ - "/tool/bandwidth-test", - `=address=${address}`, - `=direction=${direction}`, - ]; - }, - - /** - * Monitor traffic for a specific interface (real-time). - * @param {string} iface - Interface name. - * @returns {string[]} Command array. - */ - trafficMonitor: (iface) => { - if (!iface) throw new Error("Interface required for trafficMonitor"); - return ["/interface/monitor-traffic", `=interface=${iface}`]; - }, - - /** - * Get CPU profiling information. - * @param {string} [time="10s"] - Duration (e.g., "10s"). - * @returns {string[]} Command array. - */ - getCpuProfile: (time = "10s") => ["/tool/profile", `=time=${time}`], - - /** - * Real-time packet/connection monitoring (torch). - * @param {string} iface - Interface name. - * @returns {string[]} Command array. - */ - torch: (iface) => { - if (!iface) throw new Error("Interface required for torch"); - return ["/tool/torch", `=interface=${iface}`]; - }, -}; - -// ===================== WIRELESS COMMANDS ===================== - -/** - * Wireless interface management and monitoring. - */ -const WIRELESS_COMMANDS = { - /** - * Get all wireless interfaces. - * @returns {string[]} Command array. - */ - getInterfaces: ["/interface/wireless/print"], - - /** - * Get wireless registration table (connected clients). - * @returns {string[]} Command array. - */ - getRegistrationTable: ["/interface/wireless/registration-table/print"], - - /** - * Scan for wireless networks. - * @param {string} iface - Wireless interface name. - * @returns {string[]} Command array. - */ - scan: (iface) => { - if (!iface) throw new Error("Interface required for scan"); - return ["/interface/wireless/scan", `=interface=${iface}`]; - }, - - /** - * Get all wireless security profiles. - * @returns {string[]} Command array. - */ - getSecurityProfiles: ["/interface/wireless/security-profiles/print"], - - /** - * Add a wireless security profile. - * @param {string} name - Profile name. - * @param {string} mode - Security mode (dynamic-keys, static-keys-required, etc). - * @param {string} authentication - Authentication types (e.g., wpa2-psk). - * @param {string} encryption - Encryption types (aes-ccm, tkip, etc). - * @param {string} passphrase - WPA/WPA2 pre-shared key. - * @returns {string[]} Command array. - */ - addSecurityProfile: (name, mode, authentication, encryption, passphrase) => { - if (!name || !mode || !authentication || !encryption || !passphrase) - throw new Error("All parameters required for addSecurityProfile"); - return [ - "/interface/wireless/security-profiles/add", - `=name=${name}`, - `=mode=${mode}`, - `=authentication-types=${authentication}`, - `=encryption=${encryption}`, - `=wpa-pre-shared-key=${passphrase}`, - `=wpa2-pre-shared-key=${passphrase}`, - ]; - }, -}; - -// ===================== EXPORTS ===================== -export { - SYSTEM_COMMANDS, - INTERFACE_COMMANDS, - IP_COMMANDS, - QUEUE_COMMANDS, - PPP_COMMANDS, - TOOLS_COMMANDS, - WIRELESS_COMMANDS, -}; diff --git a/Refrence.ts b/Refrence.ts new file mode 100644 index 0000000..42612ba --- /dev/null +++ b/Refrence.ts @@ -0,0 +1,487 @@ +/** + * RouterOS API Commands Reference Documentation + * + * This file contains a comprehensive reference of all RouterOS API commands + * organized by category with detailed TypeScript types. + * + * Each command is provided as either: + * - Static array for simple commands: ["/path/to/command"] + * - Function for parameterized commands: (param) => ["/path/to/command", "=param=value"] + * + * Usage with RouterOS API Client: + * const result = await api.send(SYSTEM_COMMANDS.getIdentity); + * const result = await api.send(SYSTEM_COMMANDS.setIdentity("new-name")); + * + * @version 2.0.0 + * @author AviStudio (https://github.com/AviStudio) + * @contributor RouterOS API Client Library + */ + +// ===================== TYPE DEFINITIONS ===================== + +type Command = string[]; +type CommandFunction = (...args: TArgs) => Command; + +interface ClockParams { + date: string; + time: string; +} + +interface DhcpLeaseParams { + address?: string; + macAddress?: string; + server?: string; + comment?: string; + disabled?: string; +} + +interface AddressListParams { + address?: string; + list?: string; + comment?: string; + disabled?: string; +} + +interface FirewallRuleParams { + [key: string]: string; +} + +// ===================== SYSTEM COMMANDS ===================== + +const SYSTEM_COMMANDS = { + getIdentity: ["/system/identity/print"] as Command, + + setIdentity: (name: string): Command => { + if (!name) throw new Error("Name is required for setIdentity"); + return ["/system/identity/set", `=name=${name}`]; + }, + + getResources: ["/system/resource/print"] as Command, + + getRouterboard: ["/system/routerboard/print"] as Command, + + getHealth: ["/system/health/print"] as Command, + + getClock: ["/system/clock/print"] as Command, + + setClock: ({ date, time }: ClockParams): Command => { + if (!date || !time) + throw new Error("Date and time are required for setClock"); + return ["/system/clock/set", `=date=${date}`, `=time=${time}`]; + }, + + getHistory: ["/system/history/print"] as Command, + + getLicense: ["/system/license/print"] as Command, + + getLogs: ["/log/print"] as Command, + + getLogTopics: ["/system/logging/print"] as Command, + + addLogTopic: (topic: string, action: string): Command => { + if (!topic || !action) + throw new Error("Topic and action are required for addLogTopic"); + return ["/system/logging/add", `=topics=${topic}`, `=action=${action}`]; + }, + + getUsers: ["/user/print"] as Command, + + addUser: (name: string, password: string, group: string): Command => { + if (!name || !password || !group) + throw new Error("Name, password, and group are required for addUser"); + return [ + "/user/add", + `=name=${name}`, + `=password=${password}`, + `=group=${group}`, + ]; + }, + + removeUser: (id: string): Command => { + if (!id) throw new Error("User ID is required for removeUser"); + return ["/user/remove", `=.id=${id}`]; + }, + + reboot: ["/system/reboot"] as Command, + + shutdown: ["/system/shutdown"] as Command, + + checkForUpdates: ["/system/package/update/check-for-updates"] as Command, + + upgradePackages: ["/system/package/update/install"] as Command, +}; + +// ===================== INTERFACE COMMANDS ===================== + +const INTERFACE_COMMANDS = { + getAll: ["/interface/print"] as Command, + + getByType: (type: string): Command => { + if (!type) throw new Error("Type is required for getByType"); + return ["/interface/print", `?type=${type}`]; + }, + + enable: (id: string): Command => { + if (!id) throw new Error("Interface ID is required for enable"); + return ["/interface/enable", `=.id=${id}`]; + }, + + disable: (id: string): Command => { + if (!id) throw new Error("Interface ID is required for disable"); + return ["/interface/disable", `=.id=${id}`]; + }, + + monitorTraffic: (iface: string): Command => { + if (!iface) + throw new Error("Interface name is required for monitorTraffic"); + return ["/interface/monitor-traffic", `=interface=${iface}`]; + }, + + getEthernet: ["/interface/ethernet/print"] as Command, + + setEthernetMtu: (id: string, mtu: string | number): Command => { + if (!id || !mtu) throw new Error("ID and MTU required for setEthernetMtu"); + return ["/interface/ethernet/set", `=.id=${id}`, `=mtu=${mtu}`]; + }, + + getBridges: ["/interface/bridge/print"] as Command, + + addBridge: (name: string): Command => { + if (!name) throw new Error("Bridge name required"); + return ["/interface/bridge/add", `=name=${name}`]; + }, + + addBridgePort: (bridge: string, iface: string): Command => { + if (!bridge || !iface) throw new Error("Bridge and interface required"); + return [ + "/interface/bridge/port/add", + `=bridge=${bridge}`, + `=interface=${iface}`, + ]; + }, + + getVlans: ["/interface/vlan/print"] as Command, + + addVlan: (name: string, vlanId: string | number, iface: string): Command => { + if (!name || !vlanId || !iface) + throw new Error("Name, vlanId, and interface required"); + return [ + "/interface/vlan/add", + `=name=${name}`, + `=vlan-id=${vlanId}`, + `=interface=${iface}`, + ]; + }, +}; + +// ===================== IP COMMANDS ===================== + +const IP_COMMANDS = { + getAddresses: ["/ip/address/print"] as Command, + + addAddress: (address: string, iface: string): Command => { + if (!address || !iface) throw new Error("Address and interface required"); + return ["/ip/address/add", `=address=${address}`, `=interface=${iface}`]; + }, + + removeAddress: (id: string): Command => { + if (!id) throw new Error("ID required for removeAddress"); + return ["/ip/address/remove", `=.id=${id}`]; + }, + + getDhcpServer: ["/ip/dhcp-server/print"] as Command, + + addDhcpServer: (name: string, iface: string, pool: string): Command => { + if (!name || !iface || !pool) + throw new Error("Name, interface, addressPool required"); + return [ + "/ip/dhcp-server/add", + `=name=${name}`, + `=interface=${iface}`, + `=address-pool=${pool}`, + ]; + }, + + getDhcpLeases: ["/ip/dhcp-server/lease/print"] as Command, + + addDhcpLease: ( + address: string, + macAddress: string, + server?: string, + comment?: string + ): Command => { + if (!address || !macAddress) + throw new Error("Address and MAC address required for addDhcpLease"); + const command = [ + "/ip/dhcp-server/lease/add", + `=address=${address}`, + `=mac-address=${macAddress}`, + ]; + if (server) command.push(`=server=${server}`); + if (comment) command.push(`=comment=${comment.toUpperCase()}`); + return command; + }, + + setDhcpLease: (id: string, params: DhcpLeaseParams = {}): Command => { + if (!id) throw new Error("Lease ID required for setDhcpLease"); + const command = ["/ip/dhcp-server/lease/set", `=.id=${id}`]; + for (const [key, value] of Object.entries(params)) { + if (key === "comment" && typeof value === "string") { + command.push(`=comment=${value.toUpperCase()}`); + } else { + command.push(`=${key}=${value}`); + } + } + return command; + }, + + removeDhcpLease: (id: string): Command => { + if (!id) throw new Error("Lease ID required for removeDhcpLease"); + return ["/ip/dhcp-server/lease/remove", `=.id=${id}`]; + }, + + getDnsSettings: ["/ip/dns/print"] as Command, + + setDnsServers: (servers: string): Command => { + if (!servers) throw new Error("Servers required for setDnsServers"); + return ["/ip/dns/set", `=servers=${servers}`]; + }, + + getDnsCache: ["/ip/dns/cache/print"] as Command, + + getFirewallFilter: ["/ip/firewall/filter/print"] as Command, + + addFirewallRule: ( + chain: string, + action: string, + params: FirewallRuleParams = {} + ): Command => { + if (!chain || !action) + throw new Error("Chain and action required for addFirewallRule"); + const command = [ + "/ip/firewall/filter/add", + `=chain=${chain}`, + `=action=${action}`, + ]; + for (const [key, value] of Object.entries(params)) { + command.push(`=${key}=${value}`); + } + return command; + }, + + getNatRules: ["/ip/firewall/nat/print"] as Command, + + addNatRule: ( + chain: string, + action: string, + params: FirewallRuleParams = {} + ): Command => { + if (!chain || !action) + throw new Error("Chain and action required for addNatRule"); + const command = [ + "/ip/firewall/nat/add", + `=chain=${chain}`, + `=action=${action}`, + ]; + for (const [key, value] of Object.entries(params)) { + command.push(`=${key}=${value}`); + } + return command; + }, + + getRoutes: ["/ip/route/print"] as Command, + + addRoute: (dst: string, gateway: string): Command => { + if (!dst || !gateway) + throw new Error("Destination and gateway required for addRoute"); + return ["/ip/route/add", `=dst-address=${dst}`, `=gateway=${gateway}`]; + }, + + getServices: ["/ip/service/print"] as Command, + + enableService: (name: string): Command => { + if (!name) throw new Error("Service name required"); + return ["/ip/service/set", `=name=${name}`, "=disabled=no"]; + }, + + disableService: (name: string): Command => { + if (!name) throw new Error("Service name required"); + return ["/ip/service/set", `=name=${name}`, "=disabled=yes"]; + }, + + addToAddressList: (list: string, address: string, comment?: string): Command => { + if (!list || !address) + throw new Error("Address-list name and address are required"); + const command = [ + "/ip/firewall/address-list/add", + `=list=${list}`, + `=address=${address}`, + ]; + if (comment) command.push(`=comment=${comment.toUpperCase()}`); + return command; + }, + + setAddressList: (id: string, params: AddressListParams = {}): Command => { + if (!id) throw new Error("Address-list ID required for setAddressList"); + const command = ["/ip/firewall/address-list/set", `=.id=${id}`]; + for (const [key, value] of Object.entries(params)) { + if (key === "comment" && typeof value === "string") { + command.push(`=comment=${value.toUpperCase()}`); + } else { + command.push(`=${key}=${value}`); + } + } + return command; + }, + + removeAddressList: (id: string): Command => { + if (!id) throw new Error("Address-list ID required for removeAddressList"); + return ["/ip/firewall/address-list/remove", `=.id=${id}`]; + }, +}; + +// ===================== QUEUE COMMANDS ===================== + +const QUEUE_COMMANDS = { + getSimpleQueues: ["/queue/simple/print"] as Command, + + addSimpleQueue: (name: string, target: string, maxLimit: string): Command => { + if (!name || !target || !maxLimit) + throw new Error("Name, target, and maxLimit required"); + return [ + "/queue/simple/add", + `=name=${name}`, + `=target=${target}`, + `=max-limit=${maxLimit}`, + ]; + }, + + updateSimpleQueue: (id: string, params: Record = {}): Command => { + if (!id) throw new Error("ID required for updateSimpleQueue"); + const command = ["/queue/simple/set", `=.id=${id}`]; + for (const [key, value] of Object.entries(params)) { + command.push(`=${key}=${value}`); + } + return command; + }, + + removeSimpleQueue: (id: string): Command => { + if (!id) throw new Error("ID required for removeSimpleQueue"); + return ["/queue/simple/remove", `=.id=${id}`]; + }, + + getTreeQueues: ["/queue/tree/print"] as Command, + + getQueueTypes: ["/queue/type/print"] as Command, +}; + +// ===================== PPP COMMANDS ===================== + +const PPP_COMMANDS = { + getProfiles: ["/ppp/profile/print"] as Command, + + getSecrets: ["/ppp/secret/print"] as Command, + + addSecret: (name: string, password: string, service: string): Command => { + if (!name || !password || !service) + throw new Error("Name, password, service required for addSecret"); + return [ + "/ppp/secret/add", + `=name=${name}`, + `=password=${password}`, + `=service=${service}`, + ]; + }, + + getActive: ["/ppp/active/print"] as Command, +}; + +// ===================== TOOLS COMMANDS ===================== + +const TOOLS_COMMANDS = { + ping: (address: string, count: number = 4): Command => { + if (!address) throw new Error("Address required for ping"); + return ["/ping", `=address=${address}`, `=count=${count}`]; + }, + + traceroute: (address: string): Command => { + if (!address) throw new Error("Address required for traceroute"); + return ["/tool/traceroute", `=address=${address}`]; + }, + + bandwidthTest: (address: string, direction: string = "both"): Command => { + if (!address) throw new Error("Address required for bandwidthTest"); + return [ + "/tool/bandwidth-test", + `=address=${address}`, + `=direction=${direction}`, + ]; + }, + + trafficMonitor: (iface: string): Command => { + if (!iface) throw new Error("Interface required for trafficMonitor"); + return ["/interface/monitor-traffic", `=interface=${iface}`]; + }, + + getCpuProfile: (time: string = "10s"): Command => ["/tool/profile", `=time=${time}`], + + torch: (iface: string): Command => { + if (!iface) throw new Error("Interface required for torch"); + return ["/tool/torch", `=interface=${iface}`]; + }, +}; + +// ===================== WIRELESS COMMANDS ===================== + +const WIRELESS_COMMANDS = { + getInterfaces: ["/interface/wireless/print"] as Command, + + getRegistrationTable: ["/interface/wireless/registration-table/print"] as Command, + + scan: (iface: string): Command => { + if (!iface) throw new Error("Interface required for scan"); + return ["/interface/wireless/scan", `=interface=${iface}`]; + }, + + getSecurityProfiles: ["/interface/wireless/security-profiles/print"] as Command, + + addSecurityProfile: ( + name: string, + mode: string, + authentication: string, + encryption: string, + passphrase: string + ): Command => { + if (!name || !mode || !authentication || !encryption || !passphrase) + throw new Error("All parameters required for addSecurityProfile"); + return [ + "/interface/wireless/security-profiles/add", + `=name=${name}`, + `=mode=${mode}`, + `=authentication-types=${authentication}`, + `=encryption=${encryption}`, + `=wpa-pre-shared-key=${passphrase}`, + `=wpa2-pre-shared-key=${passphrase}`, + ]; + }, +}; + +// ===================== EXPORTS ===================== +export { + SYSTEM_COMMANDS, + INTERFACE_COMMANDS, + IP_COMMANDS, + QUEUE_COMMANDS, + PPP_COMMANDS, + TOOLS_COMMANDS, + WIRELESS_COMMANDS, +}; + +export type { + Command, + CommandFunction, + ClockParams, + DhcpLeaseParams, + AddressListParams, + FirewallRuleParams, +}; \ No newline at end of file diff --git a/app.js b/app.js deleted file mode 100644 index 0666aea..0000000 --- a/app.js +++ /dev/null @@ -1,190 +0,0 @@ -/** - * RouterOS API Client - Production Example & Reference Implementation - * - * This file serves as a comprehensive reference for implementing RouterOS API - * connections with advanced error handling, connectivity testing, and debugging. - * - * Features demonstrated: - * - ✅ TCP connectivity pre-testing - * - ðŸ”Ĩ Advanced error categorization and handling - * - 📊 Comprehensive event monitoring - * - ðŸšĻ Connection and operation timeouts - * - 🔍 Detailed debug logging and troubleshooting - * - * @version 1.1.0 - * @author RouterOS API Client Library - * @example - * // Run this example: - * // node app.js - */ - -const RouterOSClient = require("./index"); - -// RouterOS API connection configuration - -// Create API client instance -const api = new RouterOSClient({ - host: "192.168.1.2", // Your RouterOS IP - username: "sdworlld", // Your username - password: "Shivam!024@", // Your password - port: 8728, // Default API port - debug: true, // Enable debug mode for detailed logs -}); - -// Add event listeners for better error tracking -api.on('error', (err) => { - console.error("ðŸ”Ĩ RouterOS Client Error Event:", err); -}); - -api.on('trap', (trap, id) => { - console.error("ðŸŠĪ RouterOS Trap Event:", { trap, id }); -}); - -api.on('close', () => { - console.log("ðŸ“Ą Connection closed by RouterOS"); -}); - -api.on('timeout', () => { - console.error("⏰ Connection timeout occurred"); -}); - -// Function to test basic connectivity -async function testConnectivity() { - const net = require('net'); - - return new Promise((resolve, reject) => { - console.log("🔍 Testing basic connectivity..."); - const socket = new net.Socket(); - - socket.setTimeout(5000); // 5 second timeout - - socket.connect(api.port, api.host, () => { - console.log("✅ Basic TCP connection successful"); - socket.destroy(); - resolve(true); - }); - - socket.on('error', (err) => { - console.error("❌ TCP connection failed:", err.message); - reject(err); - }); - - socket.on('timeout', () => { - console.error("⏰ TCP connection timeout"); - socket.destroy(); - reject(new Error('TCP connection timeout')); - }); - }); -} - -// Enhanced error handling function -function handleConnectionError(err) { - console.error("❌ Connection Failed!"); - console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); - - // Log the full error object for debugging - console.error("Full Error Object:", err); - - // Check for specific error types and provide helpful messages - if (err.code) { - switch (err.code) { - case 'ECONNREFUSED': - console.error("ðŸšŦ Connection Refused:"); - console.error(" - Router is not reachable at the specified IP address"); - console.error(" - RouterOS API service might be disabled"); - console.error(" - Wrong port number (default is 8728)"); - console.error(" - Firewall blocking the connection"); - break; - - case 'ENOTFOUND': - console.error("🔍 Host Not Found:"); - console.error(" - Invalid IP address or hostname"); - console.error(" - DNS resolution failed"); - console.error(" - Network connectivity issues"); - break; - - case 'ETIMEDOUT': - console.error("⏰ Connection Timeout:"); - console.error(" - Router is not responding"); - console.error(" - Network latency issues"); - console.error(" - Router might be overloaded"); - break; - - case 'ECONNRESET': - console.error("🔄 Connection Reset:"); - console.error(" - Router forcibly closed the connection"); - console.error(" - API service might have crashed"); - break; - - default: - console.error(`🔧 Network Error (${err.code}):`); - console.error(" - Check network connectivity"); - console.error(" - Verify router configuration"); - } - } else if (err.message) { - // Check for authentication-related errors - if (err.message.includes('login') || err.message.includes('auth') || err.message.includes('password')) { - console.error("🔐 Authentication Failed:"); - console.error(" - Wrong username or password"); - console.error(" - Account might be disabled"); - console.error(" - User might not have API access permissions"); - } else if (err.message.includes('trap')) { - console.error("⚠ïļ RouterOS Trap Error:"); - console.error(" - Invalid command or parameters"); - console.error(" - Insufficient permissions"); - } else if (err.message.includes('timeout')) { - console.error("⏰ Operation Timeout:"); - console.error(" - Command took too long to execute"); - console.error(" - Router might be busy"); - } else { - console.error("❓ Unknown Error:"); - console.error(` - ${err.message}`); - } - } - - console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); - console.error("🔧 Troubleshooting Steps:"); - console.error("1. Verify the router IP address and port"); - console.error("2. Check if RouterOS API is enabled"); - console.error("3. Confirm username and password are correct"); - console.error("4. Ensure the user has API access permissions"); - console.error("5. Check firewall rules on both router and client"); - console.error("6. Test connectivity with ping or telnet"); - console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); -} - -async function example() { - console.log("🔄 Attempting to connect to RouterOS..."); - console.log(`ðŸ“Ą Host: ${api.host || 'undefined'}`); - console.log(`ðŸ‘Ī Username: ${api.username || 'undefined'}`); - console.log(`🔌 Port: ${api.port || 'undefined'}`); - console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); - - try { - // First test basic TCP connectivity - await testConnectivity(); - - // Attempt RouterOS API connection with timeout - console.log("🔄 Attempting RouterOS API connection..."); - const connectionPromise = api.connect(); - const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => reject(new Error('RouterOS API connection timeout after 10 seconds')), 10000); - }); - - await Promise.race([connectionPromise, timeoutPromise]); - console.log("✅ RouterOS API connected successfully!"); - console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); - - // Get system identity - console.log("🔍 Fetching system identity..."); - const identity = await api.send(["/system/identity/print"]); - console.log("ðŸ–Ĩïļ Router identity:", identity); - - await api.close(); - console.log("✅ Connection closed successfully!"); - } catch (err) { - handleConnectionError(err); - } -} - -example(); \ No newline at end of file diff --git a/app.ts b/app.ts new file mode 100644 index 0000000..d4569fc --- /dev/null +++ b/app.ts @@ -0,0 +1,201 @@ +/** + * RouterOS API Client - Production Example & Reference Implementation + * + * This file serves as a comprehensive reference for implementing RouterOS API + * connections with advanced error handling, connectivity testing, and debugging. + * + * Features demonstrated: + * - TCP connectivity pre-testing + * - Advanced error categorization and handling + * - Comprehensive event monitoring + * - Connection and operation timeouts + * - Detailed debug logging and troubleshooting + * + * @version 2.0.0 + * @author RouterOS API Client Library + * @example + * // Run this example: + * // ts-node app.ts + */ + +import * as net from "net"; +import { RouterOSClient } from "./lib/connect"; + +interface ConnectionConfig { + host: string; + username: string; + password: string; + port: number; + debug: boolean; +} + +interface ErrorWithCode extends Error { + code?: string; +} + +const config: ConnectionConfig = { + host: "192.168.1.2", + username: "sdworlld", + password: "Shivam!024@", + port: 8728, + debug: true, +}; + +const api = new RouterOSClient(config); + +api.on("error", (err: Error) => { + console.error("RouterOS Client Error Event:", err); +}); + +api.on("trap", (trap: unknown, id: unknown) => { + console.error("RouterOS Trap Event:", { trap, id }); +}); + +api.on("close", () => { + console.log("Connection closed by RouterOS"); +}); + +api.on("timeout", () => { + console.error("Connection timeout occurred"); +}); + +async function testConnectivity(host: string, port: number): Promise { + return new Promise((resolve, reject) => { + console.log("Testing basic connectivity..."); + const socket = new net.Socket(); + + socket.setTimeout(5000); + + socket.connect(port, host, () => { + console.log("Basic TCP connection successful"); + socket.destroy(); + resolve(true); + }); + + socket.on("error", (err: Error) => { + console.error("TCP connection failed:", err.message); + reject(err); + }); + + socket.on("timeout", () => { + console.error("TCP connection timeout"); + socket.destroy(); + reject(new Error("TCP connection timeout")); + }); + }); +} + +function handleConnectionError(err: ErrorWithCode): void { + console.error("Connection Failed!"); + console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + + console.error("Full Error Object:", err); + + if (err.code) { + switch (err.code) { + case "ECONNREFUSED": + console.error("Connection Refused:"); + console.error(" - Router is not reachable at the specified IP address"); + console.error(" - RouterOS API service might be disabled"); + console.error(" - Wrong port number (default is 8728)"); + console.error(" - Firewall blocking the connection"); + break; + + case "ENOTFOUND": + console.error("Host Not Found:"); + console.error(" - Invalid IP address or hostname"); + console.error(" - DNS resolution failed"); + console.error(" - Network connectivity issues"); + break; + + case "ETIMEDOUT": + console.error("Connection Timeout:"); + console.error(" - Router is not responding"); + console.error(" - Network latency issues"); + console.error(" - Router might be overloaded"); + break; + + case "ECONNRESET": + console.error("Connection Reset:"); + console.error(" - Router forcibly closed the connection"); + console.error(" - API service might have crashed"); + break; + + default: + console.error(`Network Error (${err.code}):`); + console.error(" - Check network connectivity"); + console.error(" - Verify router configuration"); + } + } else if (err.message) { + if ( + err.message.includes("login") || + err.message.includes("auth") || + err.message.includes("password") + ) { + console.error("Authentication Failed:"); + console.error(" - Wrong username or password"); + console.error(" - Account might be disabled"); + console.error(" - User might not have API access permissions"); + } else if (err.message.includes("trap")) { + console.error("RouterOS Trap Error:"); + console.error(" - Invalid command or parameters"); + console.error(" - Insufficient permissions"); + } else if (err.message.includes("timeout")) { + console.error("Operation Timeout:"); + console.error(" - Command took too long to execute"); + console.error(" - Router might be busy"); + } else { + console.error("Unknown Error:"); + console.error(` - ${err.message}`); + } + } + + console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + console.error("Troubleshooting Steps:"); + console.error("1. Verify the router IP address and port"); + console.error("2. Check if RouterOS API is enabled"); + console.error("3. Confirm username and password are correct"); + console.error("4. Ensure the user has API access permissions"); + console.error("5. Check firewall rules on both router and client"); + console.error("6. Test connectivity with ping or telnet"); + console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); +} + +async function example(): Promise { + console.log("Attempting to connect to RouterOS..."); + console.log(`Host: ${config.host}`); + console.log(`Username: ${config.username}`); + console.log(`Port: ${config.port}`); + console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + + try { + await testConnectivity(config.host, config.port); + + console.log("Attempting RouterOS API connection..."); + const connectionPromise = api.connect(); + const timeoutPromise = new Promise((_, reject) => { + setTimeout( + () => reject(new Error("RouterOS API connection timeout after 10 seconds")), + 10000 + ); + }); + + await Promise.race([connectionPromise, timeoutPromise]); + console.log("RouterOS API connected successfully!"); + console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + + console.log("Fetching system identity..."); + const identity = await api.send(["/system/identity/print"]); + console.log("Router identity:", identity); + + await api.close(); + console.log("Connection closed successfully!"); + } catch (err) { + handleConnectionError(err as ErrorWithCode); + } +} + +example(); + +export { example, testConnectivity, handleConnectionError }; +export type { ConnectionConfig, ErrorWithCode }; \ No newline at end of file diff --git a/index.js b/index.js deleted file mode 100644 index c8ec48c..0000000 --- a/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * RouterOS API Client - * A Node.js module for interacting with MikroTik RouterOS API - */ - -const RouterOSClient = require("./lib/connect"); - -// Export the RouterOS client class -module.exports = RouterOSClient; diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..dc2c161 --- /dev/null +++ b/index.ts @@ -0,0 +1,3 @@ +import { RouterOSClient } from "./lib/connect"; + +export { RouterOSClient }; \ No newline at end of file diff --git a/lib/connect.js b/lib/connect.ts similarity index 67% rename from lib/connect.js rename to lib/connect.ts index 983c7c8..d1b62ab 100644 --- a/lib/connect.js +++ b/lib/connect.ts @@ -1,10 +1,48 @@ -const net = require("net"); -const tls = require("tls"); -const iconv = require("iconv-lite"); -const { EventEmitter } = require("events"); +import { EventEmitter } from "events"; +import * as net from "net"; +import * as tls from "tls"; +import * as iconv from "iconv-lite"; + +// ===================== TYPE DEFINITIONS ===================== + +interface RouterOSClientOptions { + host?: string; + port?: number; + username?: string; + password?: string; + timeout?: number; + tls?: boolean; + debug?: boolean; +} + +interface RouterOSResponse { + error: string | null; + data: RouterOSData[]; + raw: string[][]; +} + +type RouterOSData = Record; + +type ResponseCallback = (response: RouterOSResponse) => void; -class RouterOSClient extends EventEmitter { - constructor(options = {}) { +// ===================== ROUTEROS CLIENT ===================== + +export class RouterOSClient extends EventEmitter { + private host: string; + private port: number; + private username: string; + private password: string; + private timeout: number; + private tls: boolean; + private socket: net.Socket | tls.TLSSocket | null; + private connected: boolean; + private buffer: Buffer; + private currentRequest: ResponseCallback | null; + private debug: boolean; + private sentences: string[][]; + private currentSentence: string[] | null; + + constructor(options: RouterOSClientOptions = {}) { super(); this.host = options.host || "192.168.88.1"; this.port = options.port || 8728; @@ -19,9 +57,10 @@ class RouterOSClient extends EventEmitter { this.currentRequest = null; this.debug = options.debug || false; this.sentences = []; + this.currentSentence = null; } - connect() { + connect(): Promise { return new Promise((resolve, reject) => { if (this.socket) { this.socket.destroy(); @@ -55,12 +94,12 @@ class RouterOSClient extends EventEmitter { this._login().then(resolve).catch(reject); }); - this.socket.on("data", (data) => { + this.socket.on("data", (data: Buffer) => { this.buffer = Buffer.concat([this.buffer, data]); this._parseResponse(); }); - this.socket.on("error", (err) => { + this.socket.on("error", (err: Error) => { clearTimeout(timeoutId); this.emit("error", err); reject(err); @@ -73,11 +112,11 @@ class RouterOSClient extends EventEmitter { }); } - _login() { + private _login(): Promise { return new Promise((resolve, reject) => { - this.sentences = []; // Clear any previous data + this.sentences = []; - this._sendCommand(["/login"], (response) => { + this._sendCommand(["/login"], (response: RouterOSResponse) => { if (response.error) { reject(new Error(response.error)); return; @@ -85,10 +124,9 @@ class RouterOSClient extends EventEmitter { if (this.debug) console.log("Login response:", response); - // Modern login method (v6.43+) this._sendCommand( ["/login", `=name=${this.username}`, `=password=${this.password}`], - (loginResponse) => { + (loginResponse: RouterOSResponse) => { if (this.debug) console.log("Auth response:", loginResponse); if (loginResponse.error) { @@ -104,17 +142,16 @@ class RouterOSClient extends EventEmitter { }); } - send(words) { + send(words: string[]): Promise { return new Promise((resolve, reject) => { if (!this.connected) { reject(new Error("Not connected")); return; } - // Clear previous response data this.sentences = []; - this._sendCommand(words, (response) => { + this._sendCommand(words, (response: RouterOSResponse) => { if (response.error) { reject(new Error(response.error)); } else { @@ -124,7 +161,7 @@ class RouterOSClient extends EventEmitter { }); } - close() { + close(): Promise { return new Promise((resolve) => { if (this.socket) { this.socket.end(); @@ -139,38 +176,32 @@ class RouterOSClient extends EventEmitter { }); } - _parseResponse() { + private _parseResponse(): void { while (this.buffer.length > 0) { const length = this._readLength(); if (length === null) break; if (length === 0) { - // End of sentence if (!this.currentSentence) { this.currentSentence = []; } - // Only add non-empty sentences if (this.currentSentence.length > 0) { this.sentences.push(this.currentSentence); } - // Reset for next sentence this.currentSentence = []; - // Check if we have !done, which indicates end of response const lastSentence = this.sentences[this.sentences.length - 1]; if (lastSentence && lastSentence[0] === "!done") { this._processCompleteResponse(); } } else { - // Read word data if (this.buffer.length < length) break; const word = iconv.decode(this.buffer.slice(0, length), "utf-8"); this.buffer = this.buffer.slice(length); - // Initialize current sentence if needed if (!this.currentSentence) { this.currentSentence = []; } @@ -180,14 +211,13 @@ class RouterOSClient extends EventEmitter { } } - _processCompleteResponse() { + private _processCompleteResponse(): void { if (!this.currentRequest) return; const callback = this.currentRequest; this.currentRequest = null; - // Check for trap/error - let error = null; + let error: string | null = null; for (const sentence of this.sentences) { if (sentence[0] === "!trap") { const msgItem = sentence.find((word) => word.startsWith("=message=")); @@ -200,11 +230,10 @@ class RouterOSClient extends EventEmitter { } } - // Convert !re sentences to data objects - const data = []; + const data: RouterOSData[] = []; for (const sentence of this.sentences) { if (sentence[0] === "!re") { - const item = {}; + const item: RouterOSData = {}; for (const word of sentence.slice(1)) { if (word.startsWith("=")) { const equalPos = word.indexOf("=", 1); @@ -221,11 +250,9 @@ class RouterOSClient extends EventEmitter { } } - // Clear sentences for next command const rawSentences = [...this.sentences]; this.sentences = []; - // Return the result callback({ error: error, data: data, @@ -233,30 +260,39 @@ class RouterOSClient extends EventEmitter { }); } - _readLength() { + private _readLength(): number | null { if (this.buffer.length < 1) return null; const b = this.buffer[0]; - let len, size; + let len: number; + let size: number; if ((b & 0x80) === 0x00) { len = b; size = 1; - } else if ((b & 0xC0) === 0x80) { + } else if ((b & 0xc0) === 0x80) { if (this.buffer.length < 2) return null; - len = ((b & ~0xC0) << 8) + this.buffer[1]; + len = ((b & ~0xc0) << 8) + this.buffer[1]; size = 2; - } else if ((b & 0xE0) === 0xC0) { + } else if ((b & 0xe0) === 0xc0) { if (this.buffer.length < 3) return null; - len = ((b & ~0xE0) << 16) + (this.buffer[1] << 8) + this.buffer[2]; + len = ((b & ~0xe0) << 16) + (this.buffer[1] << 8) + this.buffer[2]; size = 3; - } else if ((b & 0xF0) === 0xE0) { + } else if ((b & 0xf0) === 0xe0) { if (this.buffer.length < 4) return null; - len = ((b & ~0xF0) << 24) + (this.buffer[1] << 16) + (this.buffer[2] << 8) + this.buffer[3]; + len = + ((b & ~0xf0) << 24) + + (this.buffer[1] << 16) + + (this.buffer[2] << 8) + + this.buffer[3]; size = 4; - } else if (b === 0xF0) { + } else if (b === 0xf0) { if (this.buffer.length < 5) return null; - len = (this.buffer[1] << 24) + (this.buffer[2] << 16) + (this.buffer[3] << 8) + this.buffer[4]; + len = + (this.buffer[1] << 24) + + (this.buffer[2] << 16) + + (this.buffer[3] << 8) + + this.buffer[4]; size = 5; } else { throw new Error("Invalid length byte"); @@ -268,8 +304,8 @@ class RouterOSClient extends EventEmitter { return len; } - _writeLength(length) { - const bytes = []; + private _writeLength(length: number): Buffer { + const bytes: number[] = []; while (true) { let byte = length & 0x7f; @@ -286,22 +322,29 @@ class RouterOSClient extends EventEmitter { return Buffer.from(bytes); } - _encodeWord(word) { + private _encodeWord(word: string): Buffer { const wordBuf = Buffer.from(word, "utf8"); const lengthBuf = this._writeLength(wordBuf.length); return Buffer.concat([lengthBuf, wordBuf]); } - _sendCommand(words, callback) { + private _sendCommand(words: string[], callback: ResponseCallback): void { this.currentRequest = callback; const data = Buffer.concat([ ...words.map((word) => this._encodeWord(word)), - this._writeLength(0), // End of sentence + this._writeLength(0), ]); - this.socket.write(data); + this.socket!.write(data); } } -module.exports = RouterOSClient; +// ===================== EXPORTS ===================== + +export type { + RouterOSClientOptions, + RouterOSResponse, + RouterOSData, + ResponseCallback, +}; \ No newline at end of file diff --git a/os.ts b/os.ts new file mode 100644 index 0000000..3e6ed15 --- /dev/null +++ b/os.ts @@ -0,0 +1,32 @@ +import { RouterOSClient } from "./lib/connect"; + + + +// Create API client instance +const api = new RouterOSClient({ + host: "192.168.1.64", + username: "admin", + password: "13ans", + port: 8728, + tls: false, // Set to true for encrypted connection +}); + +async function example() { + try { + await api.connect(); + console.log("✅ Connected successfully!"); + + // Get system identity + const identity = await api.send(["/system/identity/print"]); + console.log("ðŸ–Ĩïļ Router identity:", identity); + + // Get all interfaces + const interfaces = await api.send(["/ip/hotspot/user/profile/print", "?-kat"]); + console.log("🌐 Interfaces:", interfaces); + await api.close(); + } catch (err) { + console.error("❌ Error:", err instanceof Error ? err.message : err); + } +} + +example(); \ No newline at end of file diff --git a/package.json b/package.json index 3e4c039..5146b46 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "ros-client", "version": "1.1.2", "description": "Node.js client for MikroTik RouterOS API with support for plain text and encrypted connections", - "main": "index.js", + "main": "index.ts", "readme": "README.md", "readmeFilename": "README.md", "scripts": { @@ -26,6 +26,12 @@ ], "author": "Shivam Dwivedi", "license": "MIT", + "devDependencies": { + "@types/node": "^20.5.6", + "nodemon": "^3.1.10", + "ts-node": "^10.9.2", + "typescript": "^5.9.3" + }, "dependencies": { "crypto": "^1.0.1", "events": "^3.3.0", @@ -46,9 +52,9 @@ "README.md", "CHANGELOG.md", "LICENSE", - "index.js", + "index.ts", "lib/", - "app.js", - "Refrence.js" + "app.ts", + "Refrence.ts" ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..739295e --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,438 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + crypto: + specifier: ^1.0.1 + version: 1.0.1 + events: + specifier: ^3.3.0 + version: 3.3.0 + iconv-lite: + specifier: ^0.6.3 + version: 0.6.3 + devDependencies: + '@types/node': + specifier: ^20.5.6 + version: 20.19.19 + nodemon: + specifier: ^3.1.10 + version: 3.1.10 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.19.19)(typescript@5.9.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + +packages: + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/node@20.19.19': + resolution: {integrity: sha512-pb1Uqj5WJP7wrcbLU7Ru4QtA0+3kAXrkutGiD26wUKzSMgNNaPARTUDQmElUXp64kh3cWdou3Q0C7qwwxqSFmg==} + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + crypto@1.0.1: + resolution: {integrity: sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==} + deprecated: This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in. + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore-by-default@1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nodemon@3.1.10: + resolution: {integrity: sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==} + engines: {node: '>=10'} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pstree.remy@1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + touch@3.1.1: + resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} + hasBin: true + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undefsafe@2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + +snapshots: + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/node@20.19.19': + dependencies: + undici-types: 6.21.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@4.1.3: {} + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + concat-map@0.0.1: {} + + create-require@1.1.1: {} + + crypto@1.0.1: {} + + debug@4.4.3(supports-color@5.5.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 5.5.0 + + diff@4.0.2: {} + + events@3.3.0: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + fsevents@2.3.3: + optional: true + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + has-flag@3.0.0: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore-by-default@1.0.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + make-error@1.3.6: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + ms@2.1.3: {} + + nodemon@3.1.10: + dependencies: + chokidar: 3.6.0 + debug: 4.4.3(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 3.1.2 + pstree.remy: 1.1.8 + semver: 7.7.2 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.1 + undefsafe: 2.0.5 + + normalize-path@3.0.0: {} + + picomatch@2.3.1: {} + + pstree.remy@1.1.8: {} + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + safer-buffer@2.1.2: {} + + semver@7.7.2: {} + + simple-update-notifier@2.0.0: + dependencies: + semver: 7.7.2 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + touch@3.1.1: {} + + ts-node@10.9.2(@types/node@20.19.19)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.19.19 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + typescript@5.9.3: {} + + undefsafe@2.0.5: {} + + undici-types@6.21.0: {} + + v8-compile-cache-lib@3.0.1: {} + + yn@3.1.1: {} diff --git a/test/client.test.js b/test/client.test.ts similarity index 61% rename from test/client.test.js rename to test/client.test.ts index a5787eb..9fd2f8f 100644 --- a/test/client.test.js +++ b/test/client.test.ts @@ -1,144 +1,114 @@ -const RouterOSClient = require("../index.js"); - /** - * System examples test function - * @param {Object} config RouterOS connection configuration + * RouterOS API Client - Test Examples + * + * Comprehensive test suite demonstrating various RouterOS API operations + * organized by category with proper error handling and TypeScript types. + * + * @version 2.0.0 + * @author RouterOS API Client Library */ -async function systemExamples(config) { + +import { RouterOSClient } from "../index"; +import { RouterOSClientOptions, RouterOSData } from "../lib/connect"; + +async function systemExamples(config: RouterOSClientOptions): Promise { const api = new RouterOSClient(config); console.log("\n=== SYSTEM EXAMPLES ==="); - // Get system identity const identity = await api.send(["/system/identity/print"]); console.log("Router Identity:", identity[0]?.name || "Unknown"); - // Get system resources const resources = await api.send(["/system/resource/print"]); console.log("CPU Load:", resources[0]?.["cpu-load"] + "%"); console.log("Free Memory:", resources[0]?.["free-memory"] + " bytes"); - // Get system users const users = await api.send(["/user/print"]); console.log("Users:", users.map((user) => user.name).join(", ")); } -/** - * Interface examples test function - * @param {Object} config RouterOS connection configuration - */ -async function interfaceExamples(config) { +async function interfaceExamples(config: RouterOSClientOptions): Promise { const api = new RouterOSClient(config); console.log("\n=== INTERFACE EXAMPLES ==="); - // Get all interfaces const interfaces = await api.send(["/interface/print"]); console.log(`Total interfaces: ${interfaces.length}`); - // Show ethernet interfaces const ethernet = await api.send(["/interface/ethernet/print"]); console.log("Ethernet interfaces:"); - ethernet.forEach((iface) => { + ethernet.forEach((iface: RouterOSData) => { console.log( `- ${iface.name}: ${iface.disabled === "true" ? "DISABLED" : "ENABLED"}` ); }); } -/** - * IP examples test function - * @param {Object} config RouterOS connection configuration - */ -async function ipExamples(config) { +async function ipExamples(config: RouterOSClientOptions): Promise { const api = new RouterOSClient(config); console.log("\n=== IP EXAMPLES ==="); - // Get IP addresses const addresses = await api.send(["/ip/address/print"]); console.log("IP Addresses:"); - addresses.forEach((addr) => { + addresses.forEach((addr: RouterOSData) => { console.log(`- ${addr.address} on ${addr.interface}`); }); - // Get DHCP leases const leases = await api.send(["/ip/dhcp-server/lease/print"]); console.log(`Active DHCP leases: ${leases.length}`); - leases.slice(0, 5).forEach((lease) => { + leases.slice(0, 5).forEach((lease: RouterOSData) => { console.log( - `- ${lease.address} -> ${lease.host - name || lease.mac - address}` + `- ${lease.address} -> ${lease["host-name"] || lease["mac-address"]}` ); }); } -/** - * Queue examples test function - * @param {Object} config RouterOS connection configuration - */ -async function queueExamples(config) { +async function queueExamples(config: RouterOSClientOptions): Promise { const api = new RouterOSClient(config); console.log("\n=== QUEUE EXAMPLES ==="); - // Get all simple queues const queues = await api.send(["/queue/simple/print"]); console.log(`Total simple queues: ${queues.length}`); - // Show queue details - queues.forEach((queue) => { + queues.forEach((queue: RouterOSData) => { console.log( `- ${queue.name}: Target ${queue.target}, Max limit ${queue["max-limit"]}` ); }); } -/** - * Tool examples test function - * @param {Object} config RouterOS connection configuration - */ -async function toolExamples(config) { +async function toolExamples(config: RouterOSClientOptions): Promise { const api = new RouterOSClient(config); console.log("\n=== TOOLS EXAMPLES ==="); - // Ping example (note: this returns raw ping results) console.log("Pinging 8.8.8.8..."); const pingResult = await api.send(["/ping", "=address=8.8.8.8", "=count=3"]); console.log(`Ping results: ${pingResult.length} packets`); } -// Main function to run all examples -/** - * Run all example tests - * @param {Object} config RouterOS connection configuration - */ -async function runExamples(config) { +async function runExamples(config: RouterOSClientOptions): Promise { const api = new RouterOSClient(config); try { console.log("Connecting to RouterOS..."); await api.connect(); console.log("Connected successfully!"); - // Run examples await systemExamples(config); await interfaceExamples(config); await ipExamples(config); await queueExamples(config); await toolExamples(config); - // Close connection await api.close(); console.log("\nConnection closed."); } catch (err) { - console.error("Error:", err.message); + console.error("Error:", (err as Error).message); } } -// Run the examples -// runExamples(); - -// Export functions for individual use -module.exports = { +export { systemExamples, interfaceExamples, ipExamples, queueExamples, toolExamples, runExamples, -}; +}; \ No newline at end of file diff --git a/test/config.example.js b/test/config.example.ts similarity index 73% rename from test/config.example.js rename to test/config.example.ts index dfdb703..0a77458 100644 --- a/test/config.example.js +++ b/test/config.example.ts @@ -1,8 +1,8 @@ /** * Example configuration file for RouterOS API tests - * Copy this file to config.js and update with your router details + * Copy this file to config.ts and update with your router details */ -module.exports = { +export const config = { host: "192.168.1.1", // Your RouterOS device IP username: "admin", // Your username password: "password", // Your password diff --git a/test/system.test.js b/test/system.test.ts similarity index 62% rename from test/system.test.js rename to test/system.test.ts index f750489..1ee23a9 100644 --- a/test/system.test.js +++ b/test/system.test.ts @@ -1,10 +1,16 @@ -const RouterOSClient = require("../lib/connect.js"); - /** - * System identity test function - * @param {Object} config RouterOS connection configuration + * RouterOS API Client - System Tests + * + * Basic test suite for verifying RouterOS API connectivity and core operations. + * Each test creates its own connection and properly cleans up afterwards. + * + * @version 2.0.0 + * @author RouterOS API Client Library */ -async function systemIdentityTest(config) { + +import { RouterOSClient, RouterOSClientOptions } from "../lib/connect"; + +async function systemIdentityTest(config: RouterOSClientOptions): Promise { const api = new RouterOSClient(config); try { console.log("\n=== SYSTEM IDENTITY TEST ==="); @@ -16,11 +22,7 @@ async function systemIdentityTest(config) { } } -/** - * Interface list test function - * @param {Object} config RouterOS connection configuration - */ -async function interfaceListTest(config) { +async function interfaceListTest(config: RouterOSClientOptions): Promise { const api = new RouterOSClient(config); try { console.log("\n=== INTERFACE LIST TEST ==="); @@ -32,11 +34,7 @@ async function interfaceListTest(config) { } } -/** - * System resource test function - * @param {Object} config RouterOS connection configuration - */ -async function systemResourceTest(config) { +async function systemResourceTest(config: RouterOSClientOptions): Promise { const api = new RouterOSClient(config); try { console.log("\n=== SYSTEM RESOURCE TEST ==="); @@ -48,11 +46,7 @@ async function systemResourceTest(config) { } } -/** - * Run all system tests - * @param {Object} config RouterOS connection configuration - */ -async function runTests(config) { +async function runTests(config: RouterOSClientOptions): Promise { try { console.log("Running tests sequentially..."); await systemIdentityTest(config); @@ -60,14 +54,13 @@ async function runTests(config) { await interfaceListTest(config); console.log("\nAll tests completed."); } catch (err) { - console.error("Error:", err.message); + console.error("Error:", (err as Error).message); } } -// Export functions for individual use -module.exports = { +export { systemIdentityTest, systemResourceTest, interfaceListTest, runTests, -}; +}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..98b1608 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "typeRoots": ["./types", "./node_modules/@types"] + }, + "include": ["./**/*"], + "exclude": ["node_modules"] +} \ No newline at end of file