rosinterface 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +381 -14
- package/dist/cli/{generate.js → Generate.js} +21 -2
- package/dist/cli/Generate.js.map +1 -0
- package/dist/cli/SchemaInferrer.d.ts +9 -0
- package/dist/cli/SchemaInferrer.js +22 -3
- package/dist/cli/SchemaInferrer.js.map +1 -1
- package/dist/client/CommandBuilder.d.ts +341 -2
- package/dist/client/CommandBuilder.js +422 -8
- package/dist/client/CommandBuilder.js.map +1 -1
- package/dist/client/MikrotikClient.d.ts +360 -4
- package/dist/client/MikrotikClient.js +477 -23
- package/dist/client/MikrotikClient.js.map +1 -1
- package/dist/client/MikrotikPool.d.ts +30 -0
- package/dist/client/MikrotikPool.js +33 -3
- package/dist/client/MikrotikPool.js.map +1 -1
- package/dist/client/MikrotikSwarm.d.ts +185 -0
- package/dist/client/MikrotikSwarm.js +270 -0
- package/dist/client/MikrotikSwarm.js.map +1 -0
- package/dist/client/MikrotikTransaction.d.ts +27 -0
- package/dist/client/MikrotikTransaction.js +28 -0
- package/dist/client/MikrotikTransaction.js.map +1 -1
- package/dist/client/ResultParser.d.ts +19 -0
- package/dist/client/ResultParser.js +31 -0
- package/dist/client/ResultParser.js.map +1 -1
- package/dist/client/SnapshotSubscription.d.ts +139 -0
- package/dist/client/SnapshotSubscription.js +169 -0
- package/dist/client/SnapshotSubscription.js.map +1 -1
- package/dist/core/Auth.d.ts +31 -0
- package/dist/core/Auth.js +46 -1
- package/dist/core/Auth.js.map +1 -1
- package/dist/core/CircuitBreaker.d.ts +26 -0
- package/dist/core/CircuitBreaker.js +26 -0
- package/dist/core/CircuitBreaker.js.map +1 -1
- package/dist/core/HttpConstants.d.ts +37 -0
- package/dist/core/HttpConstants.js +68 -0
- package/dist/core/HttpConstants.js.map +1 -0
- package/dist/core/OfflineQueue.d.ts +16 -0
- package/dist/core/OfflineQueue.js +10 -0
- package/dist/core/OfflineQueue.js.map +1 -1
- package/dist/core/RateLimiter.d.ts +24 -0
- package/dist/core/RateLimiter.js +43 -7
- package/dist/core/RateLimiter.js.map +1 -1
- package/dist/core/RestProtocol.d.ts +33 -0
- package/dist/core/RestProtocol.js +166 -0
- package/dist/core/RestProtocol.js.map +1 -0
- package/dist/core/RosError.d.ts +40 -0
- package/dist/core/RosError.js +106 -0
- package/dist/core/RosError.js.map +1 -0
- package/dist/core/RosProtocol.d.ts +21 -0
- package/dist/core/RosProtocol.js +40 -1
- package/dist/core/RosProtocol.js.map +1 -1
- package/dist/core/SchemaMapper.d.ts +41 -0
- package/dist/core/SchemaMapper.js +57 -2
- package/dist/core/SchemaMapper.js.map +1 -1
- package/dist/core/SocketClient.d.ts +34 -0
- package/dist/core/SocketClient.js +51 -3
- package/dist/core/SocketClient.js.map +1 -1
- package/dist/features/FileManager.d.ts +67 -3
- package/dist/features/FileManager.js +145 -17
- package/dist/features/FileManager.js.map +1 -1
- package/dist/features/LiveCollection.d.ts +51 -0
- package/dist/features/LiveCollection.js +69 -0
- package/dist/features/LiveCollection.js.map +1 -1
- package/dist/features/PrometheusExporter.d.ts +18 -1
- package/dist/features/PrometheusExporter.js +21 -1
- package/dist/features/PrometheusExporter.js.map +1 -1
- package/dist/index.d.ts +66 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +24 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/Helpers.d.ts +16 -0
- package/dist/utils/Helpers.js +17 -1
- package/dist/utils/Helpers.js.map +1 -1
- package/dist/utils/MikrotikCollection.d.ts +86 -0
- package/dist/utils/MikrotikCollection.js +100 -1
- package/dist/utils/MikrotikCollection.js.map +1 -1
- package/package.json +18 -8
- package/dist/cli/generate.js.map +0 -1
- /package/dist/cli/{generate.d.ts → Generate.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# RosInterface v1.
|
|
1
|
+
# RosInterface v1.3
|
|
2
2
|
|
|
3
3
|
> **High-Performance RouterOS Automation A modern TypeScript and JavasScript Library for MikroTik interactions.**
|
|
4
4
|
> Built with Reactive Streaming (onSnapshot), Circuit Breakers for fault tolerance, and an Offline Queue system.
|
|
@@ -16,8 +16,12 @@
|
|
|
16
16
|
|
|
17
17
|
## English Documentation
|
|
18
18
|
|
|
19
|
-
**RosInterface** is designed for
|
|
20
|
-
It
|
|
19
|
+
**RosInterface** is a high-performance Node.js library designed for ISPs and mission-critical network environments.
|
|
20
|
+
It goes beyond simple API wrappers by introducing a unique Hybrid Engine that seamlessly unifies the transactional safety of REST (HTTPS) with the real-time speed of Sockets (TCP).
|
|
21
|
+
This architecture allows for robust management of mixed environments (RouterOS v6 & v7) without code changes.
|
|
22
|
+
|
|
23
|
+
Built with stability and hardware protection at its core, **RosInterface** ensures efficient data access even under unstable network conditions, preventing CPU spikes and connection floods.
|
|
24
|
+
|
|
21
25
|
|
|
22
26
|
---
|
|
23
27
|
|
|
@@ -62,6 +66,19 @@ It focuses on **connection stability**, **router hardware protection**, and **ef
|
|
|
62
66
|
- **Intelligent Throttling**
|
|
63
67
|
Control the flow of real-time data with "Leading + Trailing Edge" strategies.
|
|
64
68
|
|
|
69
|
+
- **Intelligent Hybrid Engine:**
|
|
70
|
+
|
|
71
|
+
- Combines the security of the **REST (HTTPS)** protocol for CRUD commands with the speed of **Sockets (TCP)** for data streaming.
|
|
72
|
+
- *RouterOS v7:* Uses HTTPS + Sockets (Background).
|
|
73
|
+
- *RouterOS v6:* Uses pure Sockets (Legacy Mode).
|
|
74
|
+
|
|
75
|
+
- **Swarm Mode:**
|
|
76
|
+
Manages fleets of **50+ routers** from a single instance.
|
|
77
|
+
- Supports **Broadcast** (all nodes) and **Multicast** (selected groups).
|
|
78
|
+
- Fault Tolerance: If one router fails, the rest of the swarm continues operating.
|
|
79
|
+
- Protocol Agnosticism: Mixes modern and legacy routers in the same control group.
|
|
80
|
+
|
|
81
|
+
|
|
65
82
|
---
|
|
66
83
|
|
|
67
84
|
|
|
@@ -74,10 +91,47 @@ It focuses on **connection stability**, **router hardware protection**, and **ef
|
|
|
74
91
|
Create a `.env` file in the root directory of your project:
|
|
75
92
|
|
|
76
93
|
```text
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
94
|
+
Basic Credentials
|
|
95
|
+
These are the standard login credentials.
|
|
96
|
+
|
|
97
|
+
MIKROTIK_HOST: Your router's IP address (e.g., 192.168.1.1).
|
|
98
|
+
|
|
99
|
+
MIKROTIK_USER: Username with write permissions (e.g., admin).
|
|
100
|
+
|
|
101
|
+
MIKROTIK_PASSWORD: User password.
|
|
102
|
+
|
|
103
|
+
The Brain (Main Protocol)
|
|
104
|
+
Here you decide how commands (Create, Edit, Delete) will be sent.
|
|
105
|
+
|
|
106
|
+
MIKROTIK_PROTOCOL
|
|
107
|
+
|
|
108
|
+
rest: (Recommended for RouterOS v7+). Uses HTTPS. It is more secure and handles errors better.
|
|
109
|
+
|
|
110
|
+
socket: (For RouterOS v6). Uses a raw TCP connection. Use it only if your router is old and does not support REST.
|
|
111
|
+
|
|
112
|
+
MIKROTIK_PORT
|
|
113
|
+
|
|
114
|
+
This is the port for the protocol chosen above.
|
|
115
|
+
|
|
116
|
+
If you use rest: Enter 443 (the router's www-ssl port).
|
|
117
|
+
|
|
118
|
+
If you're using a socket: Enter 8728 (the API port).
|
|
119
|
+
|
|
120
|
+
SSL Security
|
|
121
|
+
MIKROTIK_INSECURE
|
|
122
|
+
|
|
123
|
+
true: (Almost always necessary on local networks). Tells the library: "Trust the router's certificate even if it's self-signed." Uses the secure dispatcher internally.
|
|
124
|
+
|
|
125
|
+
false: Only use this if you've purchased a real domain (e.g., router.example.com) and installed a valid certificate on the Mikrotik.
|
|
126
|
+
|
|
127
|
+
The "Listen" (Hybrid Engine)
|
|
128
|
+
This is the main variable for real-time events (onSnapshot) while using REST.
|
|
129
|
+
|
|
130
|
+
MIKROTIK_PORT_APISSL (or socketPort in the code)
|
|
131
|
+
|
|
132
|
+
If you leave it empty: Hybrid mode is disabled. You can only send commands, but you won't be able to listen for live changes.
|
|
133
|
+
|
|
134
|
+
If you enter 8728: You activate Hybrid Mode. The library will use REST to send commands and will open a hidden channel on 8728 to listen for onSnapshot.
|
|
81
135
|
```
|
|
82
136
|
|
|
83
137
|
> ⚠️ **Security Notice**
|
|
@@ -95,6 +149,21 @@ MIKROTIK_PORT=8729
|
|
|
95
149
|
> Use it **in moderation; excessive use of onSnapshot can negatively affect the performance of your devices**.
|
|
96
150
|
|
|
97
151
|
|
|
152
|
+
> ⚠️ **Basic Configurations on Your Equipment**
|
|
153
|
+
>
|
|
154
|
+
> Before running these codes, ensure your RouterOS configuration matches:
|
|
155
|
+
> > **Ports (Hybrid Mode):**
|
|
156
|
+
>>
|
|
157
|
+
> > REST (Commands): Requires the www-ssl service enabled on the router (Default port 443).
|
|
158
|
+
>>
|
|
159
|
+
> > Socket (Streaming): Requires the api service (Default 8728) or api-ssl (Default 8729).
|
|
160
|
+
> >
|
|
161
|
+
>> **Certificates (Self-Signed):**
|
|
162
|
+
> >
|
|
163
|
+
> > By setting `rejectUnauthorized: false` in the MikrotikClient options, the library uses the Undici Dispatcher (Agent with Scope).
|
|
164
|
+
> >
|
|
165
|
+
> **This means that traffic remains encrypted (HTTPS), but the library will accept the router's self-generated certificate without failure. This does not compromise the security of other external connections (such as Stripe/AWS) in your Node.js application.**
|
|
166
|
+
|
|
98
167
|
---
|
|
99
168
|
|
|
100
169
|
|
|
@@ -363,14 +432,125 @@ async function createSecureUser() {
|
|
|
363
432
|
```
|
|
364
433
|
|
|
365
434
|
|
|
435
|
+
#### 11. Hybrid Engine (REST + Sockets) & Idempotency
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
import { MikrotikClient } from 'rosinterface';
|
|
439
|
+
|
|
440
|
+
const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
|
|
441
|
+
|
|
442
|
+
async function runHybridTest() {
|
|
443
|
+
console.log("Starting Hybrid Engine Test");
|
|
444
|
+
|
|
445
|
+
const client = new MikrotikClient({
|
|
446
|
+
host: '10.0.0.1',
|
|
447
|
+
user: 'admin',
|
|
448
|
+
password: 'password',
|
|
449
|
+
// HYBRID CONFIGURATION:
|
|
450
|
+
protocol: 'rest', // Primary Brain: HTTPS (Port 443)
|
|
451
|
+
port: 443,
|
|
452
|
+
socketPort: 8728, // Secondary Ear: TCP (Port 8728 for Streams)
|
|
453
|
+
rejectUnauthorized: false // Accept Self-Signed Certs (Safe Dispatcher)
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
await client.connect();
|
|
458
|
+
console.log("Hybrid Connection Established (HTTPS + TCP)");
|
|
459
|
+
|
|
460
|
+
// LIVE MONITORING (Via Socket 8728)
|
|
461
|
+
console.log("Listening for changes...");
|
|
462
|
+
client.collection('/ppp/secret')
|
|
463
|
+
.onSnapshot(data => {
|
|
464
|
+
if ('added' in data) console.log('Live Stream [New]:', data.added.map(x => x.name));
|
|
465
|
+
if ('removed' in data) console.log('Live Stream [Deleted]:', data.removed.map(x => x.name));
|
|
466
|
+
})
|
|
467
|
+
.onDiff();
|
|
468
|
+
|
|
469
|
+
await delay(1000);
|
|
470
|
+
|
|
471
|
+
// IDEMPOTENCY (Via REST 443)
|
|
472
|
+
// Attempt to create the same user twice. The second time should NOT fail.
|
|
473
|
+
const userPayload = { name: 'hybrid_user', password: '123', service: 'pppoe' };
|
|
474
|
+
|
|
475
|
+
console.log("Creating User...");
|
|
476
|
+
await client.command('/ppp/secret').idempotent().add(userPayload);
|
|
477
|
+
|
|
478
|
+
console.log("Creating User Again (Idempotency Check)...");
|
|
479
|
+
// This will strictly recover the existing ID without throwing an error
|
|
480
|
+
const result = await client.command('/ppp/secret').idempotent().add(userPayload);
|
|
481
|
+
console.log("Idempotency Result:", result); // Should return the existing object
|
|
482
|
+
|
|
483
|
+
await delay(2000);
|
|
484
|
+
|
|
485
|
+
// Cleanup
|
|
486
|
+
await client.command('/ppp/secret').remove(result['.id']);
|
|
487
|
+
console.log("Cleanup done.");
|
|
488
|
+
|
|
489
|
+
} catch (e) {
|
|
490
|
+
console.error("Error:", e);
|
|
491
|
+
} finally {
|
|
492
|
+
// Graceful shutdown prevents "Socket Closed" errors
|
|
493
|
+
client.close();
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
runHybridTest();
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
#### 12. Swarm Mode (Mass Management)
|
|
501
|
+
|
|
502
|
+
```ts
|
|
503
|
+
import { MikrotikSwarm } from 'rosinterface';
|
|
504
|
+
|
|
505
|
+
async function runSwarmTest() {
|
|
506
|
+
const swarm = new MikrotikSwarm();
|
|
507
|
+
|
|
508
|
+
// Node A: Modern Router (v7) -> Uses REST
|
|
509
|
+
swarm.addNode('CORE_ROUTER', {
|
|
510
|
+
host: '192.168.1.1',
|
|
511
|
+
user: 'admin',
|
|
512
|
+
password: 'secure',
|
|
513
|
+
protocol: 'rest',
|
|
514
|
+
port: 443
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// Node B: Legacy Router (v6) -> Uses Sockets
|
|
518
|
+
swarm.addNode('TOWER_A', {
|
|
519
|
+
host: '10.20.30.50',
|
|
520
|
+
user: 'admin',
|
|
521
|
+
password: 'old',
|
|
522
|
+
protocol: 'socket',
|
|
523
|
+
port: 8728
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
console.log("Connecting Swarm...");
|
|
527
|
+
await swarm.connectAll();
|
|
528
|
+
|
|
529
|
+
// BROADCAST: Send command to ALL routers in parallel
|
|
530
|
+
console.log("Broadcasting Firewall Rule...");
|
|
531
|
+
const results = await swarm.broadcast('/ip/dns/set', {
|
|
532
|
+
'allow-remote-requests': 'yes'
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
results.forEach(res => {
|
|
536
|
+
console.log(`[${res.nodeId}] Success: ${res.success}`);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
swarm.closeAll();
|
|
540
|
+
}
|
|
366
541
|
|
|
542
|
+
runSwarmTest();
|
|
543
|
+
```
|
|
367
544
|
|
|
368
545
|
---
|
|
369
546
|
|
|
370
547
|
## Documentación en Español
|
|
371
548
|
|
|
372
|
-
**RosInterface**
|
|
373
|
-
|
|
549
|
+
**RosInterface** es una biblioteca Node.js de alto rendimiento diseñada para proveedores de servicios de internet (ISP) y entornos de red críticos.
|
|
550
|
+
Va más allá de los simples envoltorios de API al introducir un motor híbrido único que unifica a la perfección la seguridad transaccional de REST (HTTPS) con la velocidad en tiempo real de los sockets (TCP).
|
|
551
|
+
Esta arquitectura permite una gestión robusta de entornos mixtos (RouterOS v6 y v7) sin necesidad de modificar el código.
|
|
552
|
+
|
|
553
|
+
Construida con la estabilidad y la protección del hardware como base, **RosInterface** garantiza un acceso eficiente a los datos incluso en condiciones de red inestables, evitando picos de CPU y sobrecargas de conexión.
|
|
374
554
|
|
|
375
555
|
---
|
|
376
556
|
|
|
@@ -415,6 +595,20 @@ Su objetivo es garantizar **estabilidad de conexión**, **protección del hardwa
|
|
|
415
595
|
|
|
416
596
|
Controle el flujo de datos en tiempo real con estrategias de vanguardia y vanguardia.
|
|
417
597
|
|
|
598
|
+
- **Motor Híbrido Inteligente:**
|
|
599
|
+
|
|
600
|
+
Combina la seguridad del protocolo **REST (HTTPS)** para comandos CRUD con la velocidad de **Sockets (TCP)** para streaming de datos.
|
|
601
|
+
- *RouterOS v7:* Usa HTTPS + Sockets (Background).
|
|
602
|
+
- *RouterOS v6:* Usa Sockets puro (Legacy Mode).
|
|
603
|
+
|
|
604
|
+
- **Modo Enjambre (Swarm):**
|
|
605
|
+
|
|
606
|
+
Administra flotas de **50+ routers** desde una sola instancia.
|
|
607
|
+
- Soporte para **Broadcast** (todos los nodos) y **Multicast** (grupos selectos).
|
|
608
|
+
- Tolerancia a fallos: Si un router no responde, el resto del enjambre sigue operando.
|
|
609
|
+
- Agnosticismo de Protocolo: Mezcla routers modernos y antiguos en el mismo grupo de control.
|
|
610
|
+
|
|
611
|
+
|
|
418
612
|
---
|
|
419
613
|
|
|
420
614
|
### ✨ Consideraciones Importantes
|
|
@@ -424,10 +618,49 @@ Su objetivo es garantizar **estabilidad de conexión**, **protección del hardwa
|
|
|
424
618
|
Crea un archivo `.env` en el directorio raíz de tu proyecto:
|
|
425
619
|
|
|
426
620
|
```text
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
621
|
+
Credenciales Básicas
|
|
622
|
+
Son los datos de acceso estándar.
|
|
623
|
+
|
|
624
|
+
MIKROTIK_HOST: La IP de tu router (ej. 192.168.1.1).
|
|
625
|
+
|
|
626
|
+
MIKROTIK_USER: Usuario con permisos de escritura (ej. admin).
|
|
627
|
+
|
|
628
|
+
MIKROTIK_PASSWORD: Contraseña del usuario.
|
|
629
|
+
|
|
630
|
+
El Cerebro (Protocolo Principal)
|
|
631
|
+
Aquí decides cómo se enviarán los comandos (Crear, Editar, Borrar).
|
|
632
|
+
|
|
633
|
+
MIKROTIK_PROTOCOL
|
|
634
|
+
|
|
635
|
+
rest: (Recomendado para RouterOS v7+). Usa HTTPS. Es más seguro y maneja mejor los errores.
|
|
636
|
+
|
|
637
|
+
socket: (Para RouterOS v6). Usa conexión TCP cruda. Úsalo solo si tu router es viejo y no soporta REST.
|
|
638
|
+
|
|
639
|
+
MIKROTIK_PORT
|
|
640
|
+
|
|
641
|
+
Es el puerto del protocolo elegido arriba.
|
|
642
|
+
|
|
643
|
+
Si usas rest: Pon 443 (el puerto www-ssl del router).
|
|
644
|
+
|
|
645
|
+
Si usas socket: Pon 8728 (el puerto api).
|
|
646
|
+
|
|
647
|
+
Seguridad SSL
|
|
648
|
+
MIKROTIK_INSECURE
|
|
649
|
+
|
|
650
|
+
true: (Casi siempre necesario en redes locales). Le dice a la librería: "Confía en el certificado del router aunque sea autofirmado". Usa el Dispatcher seguro internamente.
|
|
651
|
+
|
|
652
|
+
false: Solo úsalo si has comprado un dominio real (ej. router.ejemplo.com) y le instalaste un certificado válido al Mikrotik.
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
El "Oído" (Motor Híbrido)
|
|
656
|
+
Esta es la variable principal para tener eventos en tiempo real (onSnapshot) mientras usas REST.
|
|
657
|
+
|
|
658
|
+
MIKROTIK_PORT_APISSL (o socketPort en el código)
|
|
659
|
+
|
|
660
|
+
Si lo dejas vacío: El modo híbrido se apaga. Solo podrás enviar comandos, pero no escuchar cambios en vivo.
|
|
661
|
+
|
|
662
|
+
Si pones 8728: Activas el Modo Híbrido. La librería usará REST para mandar órdenes y abrirá un canal oculto en el 8728 para escuchar el onSnapshot.
|
|
663
|
+
|
|
431
664
|
```
|
|
432
665
|
|
|
433
666
|
> ⚠️ **Aviso de Seguridad**
|
|
@@ -444,6 +677,22 @@ MIKROTIK_PORT=8729
|
|
|
444
677
|
> Úsala **con moderación, el abuso de onSnapshot puede afectar el rendimiento de tus equipos**.
|
|
445
678
|
|
|
446
679
|
|
|
680
|
+
> ⚠️ **Configuraciones básicas en tus equipos**
|
|
681
|
+
> Antes de ejecutar estos códigos, asegúrese de que la configuración de RouterOS coincida:
|
|
682
|
+
> > **Puertos (Modo Híbrido):**
|
|
683
|
+
> >
|
|
684
|
+
> > REST (Comandos): Requiere el servicio www-ssl habilitado en el router (Puerto predeterminado 443).
|
|
685
|
+
> >
|
|
686
|
+
> > Socket (Streaming): Requiere el servicio api (Predeterminado 8728) o api-ssl (Predeterminado 8729).
|
|
687
|
+
> >
|
|
688
|
+
>> **Certificados (Autofirmados):**
|
|
689
|
+
> >
|
|
690
|
+
> >Al configurar `rejectUnauthorized: false` en las opciones de MikrotikClient, la biblioteca utiliza el Despachador Undici (Agente con Ámbito).
|
|
691
|
+
> >
|
|
692
|
+
> **Esto implica que el tráfico sigue estando cifrado (HTTPS), pero la biblioteca aceptará el certificado autogenerado del router sin fallar. Esto no compromete la seguridad de otras conexiones externas (como Stripe/AWS) en su aplicación Node.js.**
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
|
|
447
696
|
|
|
448
697
|
---
|
|
449
698
|
|
|
@@ -677,7 +926,7 @@ client.command('/log').print().then(result => {
|
|
|
677
926
|
> > npm run codegen -- -p /ppp/secret -n ISecret
|
|
678
927
|
>
|
|
679
928
|
> OPCIÓN B: Configuración Manual (Pruebas Rápidas / CI)
|
|
680
|
-
>
|
|
929
|
+
> Pasa los detalles de conexión directamente mediante banderas. Usa comillas dobles para contraseñas con caracteres especiales.
|
|
681
930
|
> >npm run codegen -- -p /ppp/secret -n ISecret --host ROUTER_IP --user admin --pass "secret123" --port 8728
|
|
682
931
|
|
|
683
932
|
|
|
@@ -712,6 +961,124 @@ async function createSecureUser() {
|
|
|
712
961
|
```
|
|
713
962
|
|
|
714
963
|
|
|
964
|
+
|
|
965
|
+
#### 11. Hybrid Engine (REST + Sockets) & Idempotency
|
|
966
|
+
|
|
967
|
+
```ts
|
|
968
|
+
import { MikrotikClient } from 'rosinterface';
|
|
969
|
+
|
|
970
|
+
const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
|
|
971
|
+
|
|
972
|
+
async function ejecutarTestHibrido() {
|
|
973
|
+
console.log("Iniciando Motor Híbrido");
|
|
974
|
+
|
|
975
|
+
const client = new MikrotikClient({
|
|
976
|
+
host: '192.168.1.1',
|
|
977
|
+
user: 'IVMEX',
|
|
978
|
+
password: 'password',
|
|
979
|
+
// CONFIGURACIÓN HÍBRIDA:
|
|
980
|
+
protocol: 'rest', // Cerebro Principal: HTTPS (Puerto 443)
|
|
981
|
+
port: 443,
|
|
982
|
+
socketPort: 8728, // Oído Secundario: TCP (Puerto 8728 para Streams)
|
|
983
|
+
rejectUnauthorized: false // Aceptar Certs Autofirmados (Usa Dispatcher Seguro)
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
try {
|
|
987
|
+
await client.connect();
|
|
988
|
+
console.log("Conexión Híbrida Establecida (HTTPS + TCP)");
|
|
989
|
+
|
|
990
|
+
// A. MONITOREO EN VIVO (Vía Socket 8728)
|
|
991
|
+
console.log("Escuchando cambios...");
|
|
992
|
+
client.collection('/ppp/secret')
|
|
993
|
+
.onSnapshot(data => {
|
|
994
|
+
if ('added' in data) console.log('Stream [Nuevo]:', data.added.map(x => x.name));
|
|
995
|
+
if ('removed' in data) console.log('Stream [Eliminado]:', data.removed.map(x => x.name));
|
|
996
|
+
})
|
|
997
|
+
.onDiff();
|
|
998
|
+
|
|
999
|
+
await delay(1000);
|
|
1000
|
+
|
|
1001
|
+
// B. IDEMPOTENCIA (Vía REST 443)
|
|
1002
|
+
// Intentamos crear el mismo usuario dos veces. La segunda NO debe fallar.
|
|
1003
|
+
const datosUsuario = { name: 'usuario_hibrido', password: '123', service: 'pppoe' };
|
|
1004
|
+
|
|
1005
|
+
console.log("Creando Usuario...");
|
|
1006
|
+
await client.command('/ppp/secret').idempotent().add(datosUsuario);
|
|
1007
|
+
|
|
1008
|
+
console.log("Creando Usuario Nuevamente (Prueba Idempotencia)...");
|
|
1009
|
+
// Esto recuperará el ID existente sin lanzar error
|
|
1010
|
+
const resultado = await client.command('/ppp/secret').idempotent().add(datosUsuario);
|
|
1011
|
+
console.log("Resultado Idempotencia:", resultado); // Debe devolver el objeto existente
|
|
1012
|
+
|
|
1013
|
+
await delay(2000);
|
|
1014
|
+
|
|
1015
|
+
// Limpieza
|
|
1016
|
+
await client.command('/ppp/secret').remove(resultado['.id']);
|
|
1017
|
+
console.log("Limpieza completada.");
|
|
1018
|
+
|
|
1019
|
+
} catch (e) {
|
|
1020
|
+
console.error("Error:", e);
|
|
1021
|
+
} finally {
|
|
1022
|
+
// Cierre elegante evita errores de "Socket Closed"
|
|
1023
|
+
client.close();
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
ejecutarTestHibrido();
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
#### 12. Swarm Mode (Mass Management)
|
|
1033
|
+
|
|
1034
|
+
```ts
|
|
1035
|
+
import { MikrotikSwarm } from 'rosinterface';
|
|
1036
|
+
|
|
1037
|
+
async function ejecutarTestEnjambre() {
|
|
1038
|
+
const enjambre = new MikrotikSwarm();
|
|
1039
|
+
|
|
1040
|
+
// Nodo A: Router Moderno (v7) -> Usa REST
|
|
1041
|
+
enjambre.addNode('ROUTER_CENTRAL', {
|
|
1042
|
+
host: '192.168.1.1',
|
|
1043
|
+
user: 'admin',
|
|
1044
|
+
password: 'segura',
|
|
1045
|
+
protocol: 'rest',
|
|
1046
|
+
port: 443
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
// Nodo B: Router Antiguo (v6) -> Usa Sockets
|
|
1050
|
+
enjambre.addNode('TORRE_NORTE', {
|
|
1051
|
+
host: '10.20.30.50',
|
|
1052
|
+
user: 'admin',
|
|
1053
|
+
password: 'vieja',
|
|
1054
|
+
protocol: 'socket',
|
|
1055
|
+
port: 8728
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
console.log("Conectando Enjambre...");
|
|
1059
|
+
await enjambre.connectAll();
|
|
1060
|
+
|
|
1061
|
+
// BROADCAST: Enviar comando a TODOS los routers en paralelo
|
|
1062
|
+
console.log("Difundiendo configuración DNS...");
|
|
1063
|
+
const resultados = await enjambre.broadcast('/ip/dns/set', {
|
|
1064
|
+
'allow-remote-requests': 'yes',
|
|
1065
|
+
'servers': '8.8.8.8,1.1.1.1'
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
resultados.forEach(res => {
|
|
1069
|
+
if (res.success) {
|
|
1070
|
+
console.log(`[${res.nodeId}] Éxito`);
|
|
1071
|
+
} else {
|
|
1072
|
+
console.log(`[${res.nodeId}] Falló: ${res.error}`);
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
enjambre.closeAll();
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
ejecutarTestEnjambre();
|
|
1080
|
+
```
|
|
1081
|
+
|
|
715
1082
|
---
|
|
716
1083
|
|
|
717
1084
|
## Author's Note / Nota del Autor
|
|
@@ -44,25 +44,37 @@ const path = __importStar(require("path"));
|
|
|
44
44
|
const chalk_1 = __importDefault(require("chalk"));
|
|
45
45
|
const ora_1 = __importDefault(require("ora"));
|
|
46
46
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
47
|
+
// Load .env (if available)
|
|
47
48
|
dotenv_1.default.config();
|
|
48
49
|
const program = new commander_1.Command();
|
|
49
50
|
program
|
|
50
51
|
.name('ros-codegen')
|
|
51
52
|
.description('Generates TypeScript interfaces from your live MikroTik router')
|
|
52
53
|
.version('1.2.0')
|
|
54
|
+
// Mandatory Generation Params
|
|
53
55
|
.requiredOption('-p, --path <menu>', 'MikroTik Menu Path (e.g. /ppp/secret)')
|
|
54
56
|
.requiredOption('-n, --name <interface>', 'Name of the output Interface (e.g. PPPSecret)')
|
|
55
57
|
.option('-o, --output <dir>', 'Output directory', './src/generated')
|
|
58
|
+
// Optional Connection Params (Overrides .env if provided)
|
|
56
59
|
.option('--host <host>', 'Router Host/IP')
|
|
57
60
|
.option('--user <user>', 'Router Username')
|
|
58
61
|
.option('--pass <password>', 'Router Password')
|
|
59
62
|
.option('--port <port>', 'Router API Port')
|
|
60
63
|
.option('--tls', 'Use TLS (SSL) connection', false)
|
|
61
64
|
.action(async (options) => {
|
|
65
|
+
// ==========================================
|
|
66
|
+
// 1. CREDENTIALS RESOLUTION STRATEGY
|
|
67
|
+
// Priority: CLI Flag > Environment Variable
|
|
68
|
+
// ==========================================
|
|
62
69
|
const host = options.host || process.env.MIKROTIK_HOST;
|
|
63
70
|
const user = options.user || process.env.MIKROTIK_USER;
|
|
64
71
|
const password = options.pass || process.env.MIKROTIK_PASS;
|
|
72
|
+
// TLS Logic: Flag OR Env Var string 'true'
|
|
65
73
|
const useTLS = options.tls || process.env.MIKROTIK_USE_TLS === 'true';
|
|
74
|
+
// Port Logic:
|
|
75
|
+
// 1. CLI Flag
|
|
76
|
+
// 2. Env Var
|
|
77
|
+
// 3. Default based on TLS (8729 vs 8728)
|
|
66
78
|
let port;
|
|
67
79
|
if (options.port) {
|
|
68
80
|
port = parseInt(options.port, 10);
|
|
@@ -73,6 +85,7 @@ program
|
|
|
73
85
|
else {
|
|
74
86
|
port = useTLS ? 8729 : 8728;
|
|
75
87
|
}
|
|
88
|
+
// Pre-flight Validation
|
|
76
89
|
if (!host || !user || !password) {
|
|
77
90
|
console.error(chalk_1.default.red('Error: Missing credentials.'));
|
|
78
91
|
console.error(chalk_1.default.yellow('You must provide credentials via .env file OR CLI flags.'));
|
|
@@ -83,23 +96,27 @@ Usage Example:
|
|
|
83
96
|
process.exit(1);
|
|
84
97
|
}
|
|
85
98
|
const spinner = (0, ora_1.default)(`Connecting to ${host}:${port}...`).start();
|
|
99
|
+
// Initialize Client
|
|
86
100
|
const client = new MikrotikClient_1.MikrotikClient({
|
|
87
101
|
host: host,
|
|
88
102
|
user: user,
|
|
89
103
|
password: password,
|
|
90
104
|
port: port,
|
|
91
105
|
useTLS: useTLS,
|
|
92
|
-
allowInsecureConfig: true
|
|
106
|
+
allowInsecureConfig: true // Suppress warnings for CLI tool since hardcoding is expected here
|
|
93
107
|
});
|
|
94
108
|
try {
|
|
95
109
|
await client.connect();
|
|
96
110
|
spinner.text = `Fetching schema from ${options.path}...`;
|
|
111
|
+
// Fetch Real Data (The "Truth")
|
|
97
112
|
const data = await client.write(`${options.path}/print`);
|
|
98
113
|
if (data.length === 0) {
|
|
99
114
|
spinner.warn(chalk_1.default.yellow(' Warning: No data found in this menu. Generated interface will be empty or generic.'));
|
|
100
115
|
}
|
|
116
|
+
// Infer Schema
|
|
101
117
|
spinner.text = 'Inferring Types & Generating Code...';
|
|
102
118
|
const tsCode = SchemaInferrer_1.SchemaInferrer.generateInterface(options.name, data);
|
|
119
|
+
// Add File Header
|
|
103
120
|
const fileContent = `
|
|
104
121
|
/**
|
|
105
122
|
* AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
|
|
@@ -110,11 +127,13 @@ Usage Example:
|
|
|
110
127
|
|
|
111
128
|
${tsCode}
|
|
112
129
|
`;
|
|
130
|
+
// Write to File
|
|
113
131
|
const outputDir = path.resolve(options.output);
|
|
114
132
|
await fs.ensureDir(outputDir);
|
|
115
133
|
const filePath = path.join(outputDir, `${options.name}.ts`);
|
|
116
134
|
await fs.writeFile(filePath, fileContent);
|
|
117
135
|
spinner.succeed(chalk_1.default.green(`Successfully generated ${options.name}.ts in ${options.output}`));
|
|
136
|
+
// Show Preview
|
|
118
137
|
console.log(chalk_1.default.gray('\nPreview:'));
|
|
119
138
|
console.log(chalk_1.default.cyan(tsCode.split('\n').slice(0, 15).join('\n') + '\n...'));
|
|
120
139
|
}
|
|
@@ -136,4 +155,4 @@ ${tsCode}
|
|
|
136
155
|
}
|
|
137
156
|
});
|
|
138
157
|
program.parse(process.argv);
|
|
139
|
-
//# sourceMappingURL=
|
|
158
|
+
//# sourceMappingURL=Generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Generate.js","sourceRoot":"","sources":["../../src/cli/Generate.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyzes raw MikroTik data and infers TypeScript types.
|
|
3
|
+
*/
|
|
1
4
|
export declare class SchemaInferrer {
|
|
5
|
+
/**
|
|
6
|
+
* Main entry point: Generates the Interface string from raw data items.
|
|
7
|
+
*/
|
|
2
8
|
static generateInterface(interfaceName: string, dataSample: Record<string, any>[]): string;
|
|
9
|
+
/**
|
|
10
|
+
* Heuristic Engine to determine the best TypeScript type.
|
|
11
|
+
*/
|
|
3
12
|
private static inferType;
|
|
4
13
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SchemaInferrer = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Analyzes raw MikroTik data and infers TypeScript types.
|
|
6
|
+
*/
|
|
4
7
|
class SchemaInferrer {
|
|
8
|
+
/**
|
|
9
|
+
* Main entry point: Generates the Interface string from raw data items.
|
|
10
|
+
*/
|
|
5
11
|
static generateInterface(interfaceName, dataSample) {
|
|
6
12
|
const fieldStats = {};
|
|
13
|
+
// Collect all possible values for each field across the sample
|
|
7
14
|
dataSample.forEach(item => {
|
|
8
15
|
Object.keys(item).forEach(key => {
|
|
9
16
|
if (!fieldStats[key])
|
|
@@ -11,30 +18,42 @@ class SchemaInferrer {
|
|
|
11
18
|
fieldStats[key].add(String(item[key]));
|
|
12
19
|
});
|
|
13
20
|
});
|
|
21
|
+
// Build the Interface lines
|
|
14
22
|
const lines = [];
|
|
15
23
|
lines.push(`export interface ${interfaceName} {`);
|
|
16
24
|
Object.keys(fieldStats).sort().forEach(key => {
|
|
17
25
|
const values = Array.from(fieldStats[key]);
|
|
18
26
|
const type = this.inferType(key, values);
|
|
27
|
+
// MikroTik fields are mostly optional as they depend on configuration
|
|
19
28
|
const isOptional = true;
|
|
20
|
-
const propName = key.includes('-') ? `'${key}'` : key;
|
|
29
|
+
const propName = key.includes('-') ? `'${key}'` : key; // Quote kebab-case
|
|
21
30
|
lines.push(` /** Sample values: ${values.slice(0, 3).join(', ')}... */`);
|
|
22
31
|
lines.push(` ${propName}${isOptional ? '?' : ''}: ${type};`);
|
|
23
32
|
});
|
|
24
33
|
lines.push(`}`);
|
|
25
34
|
return lines.join('\n');
|
|
26
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Heuristic Engine to determine the best TypeScript type.
|
|
38
|
+
*/
|
|
27
39
|
static inferType(key, values) {
|
|
40
|
+
// Boolean Detection (true/false/yes/no)
|
|
28
41
|
const isBoolean = values.every(v => ['true', 'false', 'yes', 'no'].includes(v.toLowerCase()));
|
|
29
42
|
if (isBoolean)
|
|
30
|
-
return 'boolean | string';
|
|
43
|
+
return 'boolean | string'; // string fallback for safety
|
|
44
|
+
// Number Detection
|
|
45
|
+
// Checks if all values look like numbers (ignoring empty strings)
|
|
31
46
|
const isNumber = values.every(v => v === '' || !isNaN(Number(v)));
|
|
32
47
|
if (isNumber && values.length > 0)
|
|
33
48
|
return 'number | string';
|
|
49
|
+
// Enum / Union Type Detection
|
|
50
|
+
// If we see very few unique values (e.g. 'running' | 'stopped'), create a Union.
|
|
51
|
+
// Limit: Max 10 unique values to consider it an Enum.
|
|
34
52
|
if (values.length > 0 && values.length < 10 && !isNumber) {
|
|
35
53
|
const union = values.map(v => `'${v}'`).join(' | ');
|
|
36
|
-
return `${union} | string`;
|
|
54
|
+
return `${union} | string`; // Append string to allow future values
|
|
37
55
|
}
|
|
56
|
+
// Default
|
|
38
57
|
return 'string';
|
|
39
58
|
}
|
|
40
59
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SchemaInferrer.js","sourceRoot":"","sources":["../../src/cli/SchemaInferrer.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"SchemaInferrer.js","sourceRoot":"","sources":["../../src/cli/SchemaInferrer.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,MAAa,cAAc;IAEvB;;OAEG;IACI,MAAM,CAAC,iBAAiB,CAAC,aAAqB,EAAE,UAAiC;QACpF,MAAM,UAAU,GAAgC,EAAE,CAAC;QAEnD,+DAA+D;QAC/D,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACtB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;gBAC5B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,UAAU,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,CAAC;gBAClD,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC3C,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;QAEH,4BAA4B;QAC5B,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,oBAAoB,aAAa,IAAI,CAAC,CAAC;QAElD,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;YACzC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAEzC,sEAAsE;YACtE,MAAM,UAAU,GAAG,IAAI,CAAC;YACxB,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,mBAAmB;YAE1E,KAAK,CAAC,IAAI,CAAC,0BAA0B,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC5E,KAAK,CAAC,IAAI,CAAC,OAAO,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,GAAG,CAAC,CAAC;QACpE,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED;;OAEG;IACK,MAAM,CAAC,SAAS,CAAC,GAAW,EAAE,MAAgB;QAClD,wCAAwC;QACxC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAC9F,IAAI,SAAS;YAAE,OAAO,kBAAkB,CAAC,CAAC,6BAA6B;QAEvE,mBAAmB;QACnB,kEAAkE;QAClE,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClE,IAAI,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,iBAAiB,CAAC;QAE5D,8BAA8B;QAC9B,iFAAiF;QACjF,sDAAsD;QACtD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACvD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpD,OAAO,GAAG,KAAK,WAAW,CAAC,CAAC,uCAAuC;QACvE,CAAC;QAED,UAAU;QACV,OAAO,QAAQ,CAAC;IACpB,CAAC;CACJ;AA5DD,wCA4DC"}
|