rosinterface 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/Generate.js +20 -1
- package/dist/cli/Generate.js.map +1 -1
- 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 +337 -2
- package/dist/client/CommandBuilder.js +483 -15
- package/dist/client/CommandBuilder.js.map +1 -1
- package/dist/client/MikrotikClient.d.ts +349 -1
- package/dist/client/MikrotikClient.js +364 -1
- package/dist/client/MikrotikClient.js.map +1 -1
- package/dist/client/MikrotikPool.d.ts +30 -0
- package/dist/client/MikrotikPool.js +31 -1
- package/dist/client/MikrotikPool.js.map +1 -1
- package/dist/client/MikrotikSwarm.d.ts +167 -0
- package/dist/client/MikrotikSwarm.js +178 -1
- package/dist/client/MikrotikSwarm.js.map +1 -1
- 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 +29 -16
- package/dist/core/HttpConstants.js +23 -7
- package/dist/core/HttpConstants.js.map +1 -1
- 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 +9 -0
- package/dist/core/RestProtocol.js +16 -1
- package/dist/core/RestProtocol.js.map +1 -1
- package/dist/core/RosError.d.ts +15 -0
- package/dist/core/RosError.js +36 -1
- package/dist/core/RosError.js.map +1 -1
- 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 +50 -0
- package/dist/features/FileManager.js +72 -6
- 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 +85 -0
- package/dist/utils/MikrotikCollection.js +97 -1
- package/dist/utils/MikrotikCollection.js.map +1 -1
- package/package.json +2 -2
|
@@ -38,29 +38,48 @@ const net = __importStar(require("net"));
|
|
|
38
38
|
const tls = __importStar(require("tls"));
|
|
39
39
|
const events_1 = require("events");
|
|
40
40
|
const buffer_1 = require("buffer");
|
|
41
|
-
const RosProtocol_1 = require("./RosProtocol");
|
|
41
|
+
const RosProtocol_1 = require("./RosProtocol"); // Asegúrate de tener este archivo
|
|
42
|
+
/**
|
|
43
|
+
* Low-level TCP/TLS Client.
|
|
44
|
+
* Responsibilities:
|
|
45
|
+
* 1. Transport Layer: Decides between 'net' (Plain) and 'tls' (Secure).
|
|
46
|
+
* 2. Event Handling: Manages socket errors, closures, and timeouts.
|
|
47
|
+
* 3. Framing: Buffers incoming raw bytes and splits them into valid MikroTik words.
|
|
48
|
+
*/
|
|
42
49
|
class SocketClient extends events_1.EventEmitter {
|
|
43
50
|
constructor(options) {
|
|
44
51
|
super();
|
|
52
|
+
// Union type to support both standard and secure sockets
|
|
45
53
|
this.socket = null;
|
|
54
|
+
// State tracking
|
|
46
55
|
this.connected = false;
|
|
56
|
+
// Buffer accumulator for handling TCP packet fragmentation
|
|
47
57
|
this.receiveBuffer = buffer_1.Buffer.alloc(0);
|
|
48
58
|
this.options = {
|
|
49
59
|
timeout: 10,
|
|
50
|
-
rejectUnauthorized: false,
|
|
60
|
+
rejectUnauthorized: false, // For development usually
|
|
51
61
|
keepAlive: true,
|
|
52
62
|
useTLS: false,
|
|
53
63
|
...options
|
|
54
64
|
};
|
|
55
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Establishes the connection to the router.
|
|
68
|
+
* Supports both Plain TCP and TLS based on configuration.
|
|
69
|
+
* @returns Promise that resolves when the connection is fully established.
|
|
70
|
+
*/
|
|
56
71
|
connect() {
|
|
57
72
|
return new Promise((resolve, reject) => {
|
|
58
73
|
if (this.connected)
|
|
59
74
|
return resolve();
|
|
75
|
+
// 1. Clean up any previous socket instance
|
|
60
76
|
this.cleanup();
|
|
77
|
+
// 2. Calculate timeout in milliseconds
|
|
61
78
|
const timeoutMs = (this.options.timeout || 10) * 1000;
|
|
79
|
+
// 3. LOGIC DECISION: TLS vs PLAIN TCP
|
|
62
80
|
try {
|
|
63
81
|
if (this.options.useTLS) {
|
|
82
|
+
// --- SECURE MODE (TLS) ---
|
|
64
83
|
const tlsOptions = {
|
|
65
84
|
host: this.options.host,
|
|
66
85
|
port: this.options.port,
|
|
@@ -70,6 +89,7 @@ class SocketClient extends events_1.EventEmitter {
|
|
|
70
89
|
this.socket = tls.connect(tlsOptions);
|
|
71
90
|
}
|
|
72
91
|
else {
|
|
92
|
+
// --- PLAIN MODE (TCP) ---
|
|
73
93
|
this.socket = new net.Socket();
|
|
74
94
|
this.socket.setTimeout(timeoutMs);
|
|
75
95
|
this.socket.connect(this.options.port, this.options.host);
|
|
@@ -78,24 +98,30 @@ class SocketClient extends events_1.EventEmitter {
|
|
|
78
98
|
catch (err) {
|
|
79
99
|
return reject(err);
|
|
80
100
|
}
|
|
101
|
+
// 4. Handle Connection Timeout (Handshake phase)
|
|
81
102
|
this.socket.once('timeout', () => {
|
|
82
103
|
const err = new Error(`Connection timed out after ${this.options.timeout} seconds`);
|
|
83
104
|
this.destroy();
|
|
84
105
|
reject(err);
|
|
85
106
|
});
|
|
107
|
+
// 5. Optimization: Disable Nagle's Algorithm (Lower latency for small packets)
|
|
86
108
|
this.socket.setNoDelay(true);
|
|
109
|
+
// 6. Keep-Alive
|
|
87
110
|
if (this.options.keepAlive && this.socket instanceof net.Socket) {
|
|
88
111
|
this.socket.setKeepAlive(true, 10000);
|
|
89
112
|
}
|
|
113
|
+
// --- EVENT BINDING ---
|
|
90
114
|
const connectEvent = this.options.useTLS ? 'secureConnect' : 'connect';
|
|
91
115
|
this.socket.once(connectEvent, () => {
|
|
92
116
|
this.connected = true;
|
|
117
|
+
// Clear initial timeout so we don't disconnect during operation
|
|
93
118
|
if (this.socket)
|
|
94
119
|
this.socket.setTimeout(0);
|
|
95
|
-
this.emit('connect');
|
|
120
|
+
this.emit('connect'); // Notify MikrotikClient
|
|
96
121
|
resolve();
|
|
97
122
|
});
|
|
98
123
|
this.socket.on('error', (err) => {
|
|
124
|
+
// If error happens during connection phase, reject the promise
|
|
99
125
|
if (!this.connected) {
|
|
100
126
|
reject(err);
|
|
101
127
|
}
|
|
@@ -107,7 +133,9 @@ class SocketClient extends events_1.EventEmitter {
|
|
|
107
133
|
this.connected = false;
|
|
108
134
|
this.emit('close', hadError);
|
|
109
135
|
});
|
|
136
|
+
// DATA RECEIVING LOOP
|
|
110
137
|
this.socket.on('data', (chunk) => {
|
|
138
|
+
// Defensive programming
|
|
111
139
|
const bufferChunk = buffer_1.Buffer.isBuffer(chunk)
|
|
112
140
|
? chunk
|
|
113
141
|
: buffer_1.Buffer.from(chunk, 'utf8');
|
|
@@ -115,18 +143,29 @@ class SocketClient extends events_1.EventEmitter {
|
|
|
115
143
|
});
|
|
116
144
|
});
|
|
117
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Writes raw bytes to the socket stream.
|
|
148
|
+
*/
|
|
118
149
|
write(data) {
|
|
119
150
|
if (!this.connected || !this.socket) {
|
|
120
151
|
throw new Error('Socket is not connected. Call connect() first.');
|
|
121
152
|
}
|
|
153
|
+
// Uncomment to see OUTGOING raw bytes
|
|
154
|
+
// console.log('>>> OUT [Buffer]:', data);
|
|
122
155
|
this.socket.write(data);
|
|
123
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Gracefully closes the connection.
|
|
159
|
+
*/
|
|
124
160
|
close() {
|
|
125
161
|
if (this.socket && !this.socket.destroyed) {
|
|
126
162
|
this.socket.end();
|
|
127
163
|
this.connected = false;
|
|
128
164
|
}
|
|
129
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Forcefully destroys the connection.
|
|
168
|
+
*/
|
|
130
169
|
destroy() {
|
|
131
170
|
if (this.socket && !this.socket.destroyed) {
|
|
132
171
|
this.socket.destroy();
|
|
@@ -141,15 +180,24 @@ class SocketClient extends events_1.EventEmitter {
|
|
|
141
180
|
}
|
|
142
181
|
this.receiveBuffer = buffer_1.Buffer.alloc(0);
|
|
143
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* CORE LOGIC: TCP Framing.
|
|
185
|
+
* Extracts valid MikroTik "Words" from the TCP stream.
|
|
186
|
+
*/
|
|
144
187
|
handleDataChunk(chunk) {
|
|
188
|
+
// Append new data
|
|
145
189
|
this.receiveBuffer = buffer_1.Buffer.concat([this.receiveBuffer, chunk]);
|
|
190
|
+
// Loop to process as many words as possible
|
|
146
191
|
while (this.receiveBuffer.length > 0) {
|
|
192
|
+
// Decode length using RosProtocol utility
|
|
147
193
|
const lengthInfo = RosProtocol_1.RosProtocol.decodeLength(this.receiveBuffer);
|
|
194
|
+
// If we don't have enough bytes for the length header, wait for next packet
|
|
148
195
|
if (!lengthInfo) {
|
|
149
196
|
break;
|
|
150
197
|
}
|
|
151
198
|
const { length, byteLength } = lengthInfo;
|
|
152
199
|
const totalPacketSize = byteLength + length;
|
|
200
|
+
// If we have the header but not the full body, wait.
|
|
153
201
|
if (this.receiveBuffer.length < totalPacketSize) {
|
|
154
202
|
break;
|
|
155
203
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SocketClient.js","sourceRoot":"","sources":["../../src/core/SocketClient.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,yCAA2B;AAC3B,yCAA2B;AAC3B,mCAAsC;AACtC,mCAAgC;AAChC,+CAA4C;
|
|
1
|
+
{"version":3,"file":"SocketClient.js","sourceRoot":"","sources":["../../src/core/SocketClient.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,yCAA2B;AAC3B,yCAA2B;AAC3B,mCAAsC;AACtC,mCAAgC;AAChC,+CAA4C,CAAC,kCAAkC;AAoB/E;;;;;;GAMG;AACH,MAAa,YAAa,SAAQ,qBAAY;IAW1C,YAAY,OAA4B;QACpC,KAAK,EAAE,CAAC;QAXZ,yDAAyD;QACjD,WAAM,GAAsC,IAAI,CAAC;QAGzD,iBAAiB;QACV,cAAS,GAAY,KAAK,CAAC;QAElC,2DAA2D;QACnD,kBAAa,GAAW,eAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAI5C,IAAI,CAAC,OAAO,GAAG;YACX,OAAO,EAAE,EAAE;YACX,kBAAkB,EAAE,KAAK,EAAE,0BAA0B;YACrD,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,KAAK;YACb,GAAG,OAAO;SACb,CAAC;IACN,CAAC;IAED;;;;OAIG;IACI,OAAO;QACV,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnC,IAAI,IAAI,CAAC,SAAS;gBAAE,OAAO,OAAO,EAAE,CAAC;YAErC,2CAA2C;YAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;YAEf,uCAAuC;YACvC,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;YAEtD,sCAAsC;YACtC,IAAI,CAAC;gBACD,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;oBACtB,4BAA4B;oBAC5B,MAAM,UAAU,GAA0B;wBACtC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI;wBACvB,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI;wBACvB,kBAAkB,EAAE,IAAI,CAAC,OAAO,CAAC,kBAAkB;wBACnD,OAAO,EAAE,SAAS;qBACrB,CAAC;oBACF,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;gBAC1C,CAAC;qBAAM,CAAC;oBACJ,2BAA2B;oBAC3B,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;oBAC/B,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;oBAClC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBAC9D,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,CAAC;YAED,iDAAiD;YACjD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;gBAC7B,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,8BAA8B,IAAI,CAAC,OAAO,CAAC,OAAO,UAAU,CAAC,CAAC;gBACpF,IAAI,CAAC,OAAO,EAAE,CAAC;gBACf,MAAM,CAAC,GAAG,CAAC,CAAC;YAChB,CAAC,CAAC,CAAC;YAEH,+EAA+E;YAC/E,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAE7B,gBAAgB;YAChB,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,CAAC;gBAC9D,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC1C,CAAC;YAED,wBAAwB;YAExB,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC;YAEvE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,EAAE;gBAChC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;gBACtB,gEAAgE;gBAChE,IAAI,IAAI,CAAC,MAAM;oBAAE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;gBAE3C,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,wBAAwB;gBAC9C,OAAO,EAAE,CAAC;YACd,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBAC5B,+DAA+D;gBAC/D,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;oBAClB,MAAM,CAAC,GAAG,CAAC,CAAC;gBAChB,CAAC;qBAAM,CAAC;oBACJ,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;gBAC5B,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE;gBACjC,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;gBACvB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YACjC,CAAC,CAAC,CAAC;YAEH,sBAAsB;YACtB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAsB,EAAE,EAAE;gBAC9C,wBAAwB;gBACxB,MAAM,WAAW,GAAG,eAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;oBACtC,CAAC,CAAC,KAAK;oBACP,CAAC,CAAC,eAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;gBAEjC,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,IAAY;QACrB,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACtE,CAAC;QAED,sCAAsC;QACtC,0CAA0C;QAE1C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED;;OAEG;IACI,KAAK;QACR,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACxC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;YAClB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QAC3B,CAAC;IACL,CAAC;IAED;;OAEG;IACI,OAAO;QACV,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACxC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QAC3B,CAAC;IACL,CAAC;IAEO,OAAO;QACX,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;YACjC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,eAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC;IAED;;;OAGG;IACK,eAAe,CAAC,KAAa;QACjC,kBAAkB;QAClB,IAAI,CAAC,aAAa,GAAG,eAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC;QAEhE,4CAA4C;QAC5C,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAEnC,0CAA0C;YAC1C,MAAM,UAAU,GAAG,yBAAW,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAEhE,4EAA4E;YAC5E,IAAI,CAAC,UAAU,EAAE,CAAC;gBACd,MAAM;YACV,CAAC;YAED,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,CAAC;YAC1C,MAAM,eAAe,GAAG,UAAU,GAAG,MAAM,CAAC;YAE5C,qDAAqD;YACrD,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;gBAC9C,MAAM;YACV,CAAC;YAED,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;YAEtE,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;YAE/D,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAEtC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC5B,CAAC;IACL,CAAC;CACJ;AA/LD,oCA+LC"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { MikrotikClient } from '../client/MikrotikClient';
|
|
2
|
+
/**
|
|
3
|
+
* Represents a file object from RouterOS /file/print
|
|
4
|
+
*/
|
|
2
5
|
export interface IFileEntry {
|
|
3
6
|
'.id': string;
|
|
4
7
|
name: string;
|
|
@@ -7,16 +10,63 @@ export interface IFileEntry {
|
|
|
7
10
|
'creation-time': string;
|
|
8
11
|
contents?: string;
|
|
9
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* **FileManager**
|
|
15
|
+
* * Advanced abstraction layer for file operations on RouterOS.
|
|
16
|
+
* Features:
|
|
17
|
+
* - Text File I/O (Read/Write scripts/logs).
|
|
18
|
+
* - System Backup & Restore management.
|
|
19
|
+
* - RSC Script Execution (Import).
|
|
20
|
+
* - Directory listing and cleaning.
|
|
21
|
+
*/
|
|
10
22
|
export declare class FileManager {
|
|
11
23
|
private client;
|
|
12
24
|
constructor(client: MikrotikClient);
|
|
25
|
+
/**
|
|
26
|
+
* Lists files on the router, optionally filtering by name.
|
|
27
|
+
* @param filterName Optional substring to filter files (e.g., ".backup")
|
|
28
|
+
*/
|
|
13
29
|
list(filterName?: string): Promise<IFileEntry[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Checks if a file exists on the router.
|
|
32
|
+
*/
|
|
14
33
|
exists(fileName: string): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Deletes a file or multiple files.
|
|
36
|
+
* @param fileName Name of the file (or array of names).
|
|
37
|
+
*/
|
|
15
38
|
delete(fileName: string | string[]): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Writes text content to a file.
|
|
41
|
+
* Handles creation if it doesn't exist.
|
|
42
|
+
* * WARNING: RouterOS API has a limit for property values (~4MB in v7, less in v6).
|
|
43
|
+
* Do not use this for binary files (.npk) or massive logs.
|
|
44
|
+
*/
|
|
16
45
|
writeText(fileName: string, content: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Reads text content from a file.
|
|
48
|
+
* @returns string content, or throws error if file is binary/too large.
|
|
49
|
+
*/
|
|
17
50
|
readText(fileName: string): Promise<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Creates a System Backup (.backup) file.
|
|
53
|
+
* @param name Backup name (without extension).
|
|
54
|
+
* @param password Optional encryption password.
|
|
55
|
+
*/
|
|
18
56
|
createSystemBackup(name: string, password?: string): Promise<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Restores a System Backup.
|
|
59
|
+
* DANGER: This will reboot the router.
|
|
60
|
+
*/
|
|
19
61
|
restoreSystemBackup(name: string, password?: string): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Exports configuration to an RSC script file.
|
|
64
|
+
* @param name File name (e.g., "daily_export")
|
|
65
|
+
*/
|
|
20
66
|
createExport(name: string): Promise<string>;
|
|
67
|
+
/**
|
|
68
|
+
* Uploads a script (.rsc) and immediately executes it.
|
|
69
|
+
* Great for provisioning or mass updates.
|
|
70
|
+
*/
|
|
21
71
|
runScript(content: string, scriptName?: string): Promise<void>;
|
|
22
72
|
}
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FileManager = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* **FileManager**
|
|
6
|
+
* * Advanced abstraction layer for file operations on RouterOS.
|
|
7
|
+
* Features:
|
|
8
|
+
* - Text File I/O (Read/Write scripts/logs).
|
|
9
|
+
* - System Backup & Restore management.
|
|
10
|
+
* - RSC Script Execution (Import).
|
|
11
|
+
* - Directory listing and cleaning.
|
|
12
|
+
*/
|
|
4
13
|
class FileManager {
|
|
5
14
|
constructor(client) {
|
|
6
15
|
this.client = client;
|
|
7
16
|
}
|
|
17
|
+
// ==========================================
|
|
18
|
+
// CORE FILE OPERATIONS
|
|
19
|
+
// ==========================================
|
|
20
|
+
/**
|
|
21
|
+
* Lists files on the router, optionally filtering by name.
|
|
22
|
+
* @param filterName Optional substring to filter files (e.g., ".backup")
|
|
23
|
+
*/
|
|
8
24
|
async list(filterName) {
|
|
9
25
|
const cmd = this.client.command('/file');
|
|
10
26
|
const result = await cmd.print();
|
|
@@ -14,14 +30,23 @@ class FileManager {
|
|
|
14
30
|
}
|
|
15
31
|
return files;
|
|
16
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Checks if a file exists on the router.
|
|
35
|
+
*/
|
|
17
36
|
async exists(fileName) {
|
|
18
37
|
const file = await this.client.command('/file')
|
|
19
38
|
.where('name', fileName)
|
|
20
39
|
.first();
|
|
21
40
|
return !!file;
|
|
22
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Deletes a file or multiple files.
|
|
44
|
+
* @param fileName Name of the file (or array of names).
|
|
45
|
+
*/
|
|
23
46
|
async delete(fileName) {
|
|
24
47
|
const targets = Array.isArray(fileName) ? fileName : [fileName];
|
|
48
|
+
// We need IDs to remove safely, or use 'remove [find where name=X]' logic
|
|
49
|
+
// But the API supports removing by ID mostly.
|
|
25
50
|
for (const name of targets) {
|
|
26
51
|
const file = await this.client.command('/file').where('name', name).first();
|
|
27
52
|
if (file) {
|
|
@@ -30,37 +55,62 @@ class FileManager {
|
|
|
30
55
|
}
|
|
31
56
|
}
|
|
32
57
|
}
|
|
58
|
+
// ==========================================
|
|
59
|
+
// TEXT I/O (Scripts, Configs, Hotspot)
|
|
60
|
+
// ==========================================
|
|
61
|
+
/**
|
|
62
|
+
* Writes text content to a file.
|
|
63
|
+
* Handles creation if it doesn't exist.
|
|
64
|
+
* * WARNING: RouterOS API has a limit for property values (~4MB in v7, less in v6).
|
|
65
|
+
* Do not use this for binary files (.npk) or massive logs.
|
|
66
|
+
*/
|
|
33
67
|
async writeText(fileName, content) {
|
|
34
|
-
if (content.length > 1024 * 1024) {
|
|
68
|
+
if (content.length > 1024 * 1024) { // 1MB Guard
|
|
35
69
|
console.warn(`[FileManager] Warning: Uploading ${content.length} bytes via API is risky.`);
|
|
36
70
|
}
|
|
37
71
|
const exists = await this.exists(fileName);
|
|
38
72
|
if (!exists) {
|
|
73
|
+
// Trick: 'print file=name' creates an empty file
|
|
39
74
|
await this.client.write('/file/print', {
|
|
40
75
|
file: fileName,
|
|
41
|
-
where: 'false'
|
|
42
|
-
}).catch(() => { });
|
|
76
|
+
where: 'false' // Suppress output
|
|
77
|
+
}).catch(() => { }); // Ignore "interrupted" or empty return errors
|
|
78
|
+
// Wait a tiny bit for FS sync
|
|
43
79
|
await new Promise(r => setTimeout(r, 200));
|
|
44
80
|
}
|
|
81
|
+
// Update content
|
|
45
82
|
await this.client.write('/file/set', {
|
|
46
|
-
'numbers': fileName,
|
|
83
|
+
'numbers': fileName, // API allows name in numbers for some versions, but IDs are safer.
|
|
47
84
|
'contents': content
|
|
48
85
|
});
|
|
49
86
|
console.log(`[FileManager] Saved: ${fileName}`);
|
|
50
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Reads text content from a file.
|
|
90
|
+
* @returns string content, or throws error if file is binary/too large.
|
|
91
|
+
*/
|
|
51
92
|
async readText(fileName) {
|
|
52
93
|
const file = await this.client.command('/file').where('name', fileName).first();
|
|
53
94
|
if (!file)
|
|
54
95
|
throw new Error(`File '${fileName}' not found.`);
|
|
96
|
+
// RouterOS logic: If 'contents' is missing, the file is too big or binary.
|
|
55
97
|
if (file.contents === undefined) {
|
|
56
98
|
const size = Number(file.size);
|
|
57
|
-
if (size > 4096) {
|
|
99
|
+
if (size > 4096) { // Heuristic: API often hides contents > 4KB (v6) or 64KB (v7)
|
|
58
100
|
throw new Error(`File '${fileName}' is too large (${size} bytes) or binary to read via API. Use SFTP.`);
|
|
59
101
|
}
|
|
60
|
-
return '';
|
|
102
|
+
return ''; // Empty file
|
|
61
103
|
}
|
|
62
104
|
return file.contents;
|
|
63
105
|
}
|
|
106
|
+
// ==========================================
|
|
107
|
+
// SYSTEM & AUTOMATION
|
|
108
|
+
// ==========================================
|
|
109
|
+
/**
|
|
110
|
+
* Creates a System Backup (.backup) file.
|
|
111
|
+
* @param name Backup name (without extension).
|
|
112
|
+
* @param password Optional encryption password.
|
|
113
|
+
*/
|
|
64
114
|
async createSystemBackup(name, password) {
|
|
65
115
|
const fullName = `${name}.backup`;
|
|
66
116
|
const params = { name: name };
|
|
@@ -70,6 +120,10 @@ class FileManager {
|
|
|
70
120
|
await this.client.write('/system/backup/save', params);
|
|
71
121
|
return fullName;
|
|
72
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Restores a System Backup.
|
|
125
|
+
* DANGER: This will reboot the router.
|
|
126
|
+
*/
|
|
73
127
|
async restoreSystemBackup(name, password) {
|
|
74
128
|
console.warn(`[FileManager] RESTORING BACKUP ${name}. ROUTER WILL REBOOT.`);
|
|
75
129
|
const params = { name: name };
|
|
@@ -77,15 +131,26 @@ class FileManager {
|
|
|
77
131
|
params.password = password;
|
|
78
132
|
await this.client.write('/system/backup/load', params);
|
|
79
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Exports configuration to an RSC script file.
|
|
136
|
+
* @param name File name (e.g., "daily_export")
|
|
137
|
+
*/
|
|
80
138
|
async createExport(name) {
|
|
81
139
|
const fullName = name.endsWith('.rsc') ? name : `${name}.rsc`;
|
|
82
140
|
console.log(`[FileManager] Exporting config to ${fullName}...`);
|
|
141
|
+
// /export file=name
|
|
83
142
|
await this.client.write('/export', { file: name });
|
|
84
143
|
return fullName;
|
|
85
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Uploads a script (.rsc) and immediately executes it.
|
|
147
|
+
* Great for provisioning or mass updates.
|
|
148
|
+
*/
|
|
86
149
|
async runScript(content, scriptName = 'temp_worker.rsc') {
|
|
87
150
|
try {
|
|
151
|
+
// Upload
|
|
88
152
|
await this.writeText(scriptName, content);
|
|
153
|
+
// Execute (/import)
|
|
89
154
|
console.log(`[FileManager] Executing script: ${scriptName}...`);
|
|
90
155
|
await this.client.write('/import', { 'file-name': scriptName });
|
|
91
156
|
console.log(`[FileManager] Script executed successfully.`);
|
|
@@ -95,6 +160,7 @@ class FileManager {
|
|
|
95
160
|
throw error;
|
|
96
161
|
}
|
|
97
162
|
finally {
|
|
163
|
+
// Cleanup (Optional: remove script after run)
|
|
98
164
|
await this.delete(scriptName);
|
|
99
165
|
}
|
|
100
166
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileManager.js","sourceRoot":"","sources":["../../src/features/FileManager.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"FileManager.js","sourceRoot":"","sources":["../../src/features/FileManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,18 +1,69 @@
|
|
|
1
1
|
import { MikrotikClient } from "../client/MikrotikClient";
|
|
2
2
|
import { SnapshotSubscription, SnapshotDiff } from "../client/SnapshotSubscription";
|
|
3
|
+
/**
|
|
4
|
+
* Flexible callback definition.
|
|
5
|
+
* Can receive a standard Array OR a Diff object depending on configuration.
|
|
6
|
+
*/
|
|
3
7
|
export type SnapshotCallback<T> = (data: T[] | SnapshotDiff<T>) => void;
|
|
8
|
+
/**
|
|
9
|
+
* LiveCollection
|
|
10
|
+
* ==========================================
|
|
11
|
+
* Manages a real-time synchronized collection of items from the router.
|
|
12
|
+
*
|
|
13
|
+
* Instead of firing simple callbacks, it manages 'SnapshotSubscription' instances.
|
|
14
|
+
* This allows each subscriber to have their own Throttle settings and Diff modes.
|
|
15
|
+
*
|
|
16
|
+
* @template T The type of the items in the collection.
|
|
17
|
+
*/
|
|
4
18
|
export declare class LiveCollection<T extends Record<string, any>> {
|
|
5
19
|
private client;
|
|
6
20
|
private path;
|
|
7
21
|
private query;
|
|
22
|
+
/** Internal storage mapped by item ID (e.g., "*1A") */
|
|
8
23
|
private localCache;
|
|
24
|
+
/** The active low-level subscription object returned by the Client/Socket */
|
|
9
25
|
private subscription;
|
|
26
|
+
/**
|
|
27
|
+
* CHANGED: List of active "Smart Subscriptions" instead of simple functions.
|
|
28
|
+
* This allows us to call .processUpdate() on each one.
|
|
29
|
+
*/
|
|
10
30
|
private subscriptions;
|
|
31
|
+
/** Flag to prevent double initialization */
|
|
11
32
|
private isInitializing;
|
|
33
|
+
/**
|
|
34
|
+
* Creates an instance of LiveCollection.
|
|
35
|
+
* @param client The main MikrotikClient instance.
|
|
36
|
+
* @param path The menu path to listen to (e.g., '/ppp/active').
|
|
37
|
+
* @param query Optional filter object (e.g. { name: 'admin' }).
|
|
38
|
+
*/
|
|
12
39
|
constructor(client: MikrotikClient, path: string, query?: Record<string, string | number | boolean>);
|
|
40
|
+
/**
|
|
41
|
+
* Subscribes to real-time updates.
|
|
42
|
+
*
|
|
43
|
+
* v1.2.0 Change:
|
|
44
|
+
* Returns a `SnapshotSubscription` object instead of a cleanup function.
|
|
45
|
+
* This enables chaining methods like `.onDiff()`, `.throttle()`, and `.join()`.
|
|
46
|
+
*
|
|
47
|
+
* @param callback Function to execute when data changes.
|
|
48
|
+
* @returns The Subscription object for chaining configuration.
|
|
49
|
+
*/
|
|
13
50
|
onSnapshot(callback: SnapshotCallback<T>): SnapshotSubscription<T>;
|
|
51
|
+
/**
|
|
52
|
+
* Internal method to establish the connection via the client.
|
|
53
|
+
*/
|
|
14
54
|
private startListening;
|
|
55
|
+
/**
|
|
56
|
+
* Processes incoming raw packets from RouterOS.
|
|
57
|
+
* Handles creation, updates, and deletion based on packet flags.
|
|
58
|
+
* @param packet Raw data from the router.
|
|
59
|
+
*/
|
|
15
60
|
private processPacket;
|
|
61
|
+
/**
|
|
62
|
+
* Broadcasts the current array of items to all smart subscriptions.
|
|
63
|
+
*/
|
|
16
64
|
private emit;
|
|
65
|
+
/**
|
|
66
|
+
* Stops the low-level connection to the router.
|
|
67
|
+
*/
|
|
17
68
|
private stopListening;
|
|
18
69
|
}
|
|
@@ -2,28 +2,67 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LiveCollection = void 0;
|
|
4
4
|
const SnapshotSubscription_1 = require("../client/SnapshotSubscription");
|
|
5
|
+
/**
|
|
6
|
+
* LiveCollection
|
|
7
|
+
* ==========================================
|
|
8
|
+
* Manages a real-time synchronized collection of items from the router.
|
|
9
|
+
*
|
|
10
|
+
* Instead of firing simple callbacks, it manages 'SnapshotSubscription' instances.
|
|
11
|
+
* This allows each subscriber to have their own Throttle settings and Diff modes.
|
|
12
|
+
*
|
|
13
|
+
* @template T The type of the items in the collection.
|
|
14
|
+
*/
|
|
5
15
|
class LiveCollection {
|
|
16
|
+
/**
|
|
17
|
+
* Creates an instance of LiveCollection.
|
|
18
|
+
* @param client The main MikrotikClient instance.
|
|
19
|
+
* @param path The menu path to listen to (e.g., '/ppp/active').
|
|
20
|
+
* @param query Optional filter object (e.g. { name: 'admin' }).
|
|
21
|
+
*/
|
|
6
22
|
constructor(client, path, query = {}) {
|
|
7
23
|
this.client = client;
|
|
8
24
|
this.path = path;
|
|
9
25
|
this.query = query;
|
|
26
|
+
/** Internal storage mapped by item ID (e.g., "*1A") */
|
|
10
27
|
this.localCache = new Map();
|
|
28
|
+
/** The active low-level subscription object returned by the Client/Socket */
|
|
11
29
|
this.subscription = null;
|
|
30
|
+
/**
|
|
31
|
+
* CHANGED: List of active "Smart Subscriptions" instead of simple functions.
|
|
32
|
+
* This allows us to call .processUpdate() on each one.
|
|
33
|
+
*/
|
|
12
34
|
this.subscriptions = [];
|
|
35
|
+
/** Flag to prevent double initialization */
|
|
13
36
|
this.isInitializing = false;
|
|
14
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Subscribes to real-time updates.
|
|
40
|
+
*
|
|
41
|
+
* v1.2.0 Change:
|
|
42
|
+
* Returns a `SnapshotSubscription` object instead of a cleanup function.
|
|
43
|
+
* This enables chaining methods like `.onDiff()`, `.throttle()`, and `.join()`.
|
|
44
|
+
*
|
|
45
|
+
* @param callback Function to execute when data changes.
|
|
46
|
+
* @returns The Subscription object for chaining configuration.
|
|
47
|
+
*/
|
|
15
48
|
onSnapshot(callback) {
|
|
49
|
+
// Define the cleanup logic for this specific subscriber
|
|
16
50
|
const unsubscribeLogic = () => {
|
|
17
51
|
this.subscriptions = this.subscriptions.filter(s => s !== sub);
|
|
52
|
+
// If no one is listening anymore, stop the router connection to save bandwidth
|
|
18
53
|
if (this.subscriptions.length === 0) {
|
|
19
54
|
this.stopListening();
|
|
20
55
|
}
|
|
21
56
|
};
|
|
57
|
+
// Create the Smart Subscription
|
|
22
58
|
const sub = new SnapshotSubscription_1.SnapshotSubscription(this.client, callback, unsubscribeLogic);
|
|
23
59
|
this.subscriptions.push(sub);
|
|
60
|
+
// Send immediate data if we already have cache (Hot Observable behavior)
|
|
24
61
|
if (this.localCache.size > 0) {
|
|
62
|
+
// We use processUpdate so logic like Join or Diff runs even on the first data
|
|
25
63
|
sub.processUpdate(Array.from(this.localCache.values()));
|
|
26
64
|
}
|
|
65
|
+
// Start Router connection if this is the first listener
|
|
27
66
|
if (!this.subscription && !this.isInitializing) {
|
|
28
67
|
this.startListening().catch(err => {
|
|
29
68
|
console.error("RosInterface: Error starting live listener", err);
|
|
@@ -31,13 +70,20 @@ class LiveCollection {
|
|
|
31
70
|
}
|
|
32
71
|
return sub;
|
|
33
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Internal method to establish the connection via the client.
|
|
75
|
+
*/
|
|
34
76
|
async startListening() {
|
|
35
77
|
if (this.isInitializing)
|
|
36
78
|
return;
|
|
37
79
|
this.isInitializing = true;
|
|
38
80
|
try {
|
|
81
|
+
// Initialize Command
|
|
39
82
|
const cmd = this.client.command(this.path);
|
|
83
|
+
// Optimization: Request specific fields to reduce CPU load on Router.
|
|
84
|
+
// CRITICAL: We MUST include '.dead' to detect deletions.
|
|
40
85
|
cmd.where('.proplist', '.id,.dead,name,comment,disabled,profile,service,user,password,address,uptime,mac-address,caller-id,bytes-in,bytes-out,radius');
|
|
86
|
+
// Apply Filters from Query
|
|
41
87
|
if (this.query) {
|
|
42
88
|
Object.keys(this.query).forEach(key => {
|
|
43
89
|
const value = this.query[key];
|
|
@@ -46,6 +92,7 @@ class LiveCollection {
|
|
|
46
92
|
}
|
|
47
93
|
});
|
|
48
94
|
}
|
|
95
|
+
// Start Streaming
|
|
49
96
|
this.subscription = cmd.listen((packet) => {
|
|
50
97
|
this.processPacket(packet);
|
|
51
98
|
});
|
|
@@ -55,31 +102,53 @@ class LiveCollection {
|
|
|
55
102
|
this.isInitializing = false;
|
|
56
103
|
}
|
|
57
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Processes incoming raw packets from RouterOS.
|
|
107
|
+
* Handles creation, updates, and deletion based on packet flags.
|
|
108
|
+
* @param packet Raw data from the router.
|
|
109
|
+
*/
|
|
58
110
|
processPacket(packet) {
|
|
111
|
+
// RouterOS raw packets use '.id', but sometimes it might be parsed as 'id'
|
|
59
112
|
const rawId = packet['.id'] || packet['id'];
|
|
113
|
+
// Ignore status packets (!done, !fatal)
|
|
60
114
|
if (!rawId)
|
|
61
115
|
return;
|
|
116
|
+
// HANDLE DELETION
|
|
62
117
|
if (packet['.dead'] === true || packet['dead'] === true) {
|
|
63
118
|
this.localCache.delete(rawId);
|
|
64
119
|
}
|
|
120
|
+
// HANDLE UPDATE / INSERT
|
|
65
121
|
else {
|
|
66
122
|
const existing = this.localCache.get(rawId) || {};
|
|
123
|
+
// Normalize keys: Remove leading dots
|
|
67
124
|
const cleanPacket = {};
|
|
68
125
|
for (const key of Object.keys(packet)) {
|
|
69
126
|
const cleanKey = key.startsWith('.') ? key.substring(1) : key;
|
|
70
127
|
cleanPacket[cleanKey] = packet[key];
|
|
71
128
|
}
|
|
129
|
+
// Merge with existing data to support partial updates
|
|
72
130
|
const updated = { ...existing, ...cleanPacket };
|
|
73
131
|
this.localCache.set(rawId, updated);
|
|
74
132
|
}
|
|
133
|
+
// BROADCAST TO SMART SUBSCRIPTIONS
|
|
134
|
+
// Instead of sending the array directly, we let the Subscription object
|
|
135
|
+
// decide IF and WHEN to send it (Throttle) and HOW (Diff/Join).
|
|
75
136
|
this.emit();
|
|
76
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Broadcasts the current array of items to all smart subscriptions.
|
|
140
|
+
*/
|
|
77
141
|
emit() {
|
|
78
142
|
const fullList = Array.from(this.localCache.values());
|
|
143
|
+
// We delegate the logic to each subscription.
|
|
144
|
+
// One user might want Diffs, another might want the full list throttled.
|
|
79
145
|
this.subscriptions.forEach(sub => {
|
|
80
146
|
sub.processUpdate(fullList);
|
|
81
147
|
});
|
|
82
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Stops the low-level connection to the router.
|
|
151
|
+
*/
|
|
83
152
|
stopListening() {
|
|
84
153
|
if (this.subscription) {
|
|
85
154
|
try {
|