@furlow/pipes 0.1.0
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/database/index.d.ts +3 -0
- package/dist/database/index.js +8 -0
- package/dist/database/index.js.map +1 -0
- package/dist/file/index.d.ts +3 -0
- package/dist/file/index.js +8 -0
- package/dist/file/index.js.map +1 -0
- package/dist/http/index.d.ts +61 -0
- package/dist/http/index.js +156 -0
- package/dist/http/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1282 -0
- package/dist/index.js.map +1 -0
- package/dist/mqtt/index.d.ts +82 -0
- package/dist/mqtt/index.js +277 -0
- package/dist/mqtt/index.js.map +1 -0
- package/dist/tcp/index.d.ts +167 -0
- package/dist/tcp/index.js +462 -0
- package/dist/tcp/index.js.map +1 -0
- package/dist/types-BW9r2ksN.d.ts +152 -0
- package/dist/webhook/index.d.ts +70 -0
- package/dist/webhook/index.js +169 -0
- package/dist/webhook/index.js.map +1 -0
- package/dist/websocket/index.d.ts +84 -0
- package/dist/websocket/index.js +226 -0
- package/dist/websocket/index.js.map +1 -0
- package/package.json +83 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1282 @@
|
|
|
1
|
+
// src/http/index.ts
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
var HttpPipe = class {
|
|
4
|
+
name;
|
|
5
|
+
type = "http";
|
|
6
|
+
client;
|
|
7
|
+
config;
|
|
8
|
+
requestCount = 0;
|
|
9
|
+
windowStart = Date.now();
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.name = options.name;
|
|
12
|
+
this.config = options.config;
|
|
13
|
+
this.client = axios.create({
|
|
14
|
+
baseURL: options.config.base_url,
|
|
15
|
+
timeout: this.parseDuration(options.config.timeout ?? "30s"),
|
|
16
|
+
headers: options.config.headers
|
|
17
|
+
});
|
|
18
|
+
if (options.config.auth) {
|
|
19
|
+
this.client.interceptors.request.use((config) => {
|
|
20
|
+
const auth = options.config.auth;
|
|
21
|
+
switch (auth.type) {
|
|
22
|
+
case "bearer":
|
|
23
|
+
config.headers.Authorization = `Bearer ${auth.token}`;
|
|
24
|
+
break;
|
|
25
|
+
case "basic":
|
|
26
|
+
const credentials = Buffer.from(`${auth.username}:${auth.password}`).toString("base64");
|
|
27
|
+
config.headers.Authorization = `Basic ${credentials}`;
|
|
28
|
+
break;
|
|
29
|
+
case "header":
|
|
30
|
+
const headerName = auth.header_name ?? "Authorization";
|
|
31
|
+
config.headers[headerName] = auth.token;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
return config;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
isConnected() {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Make an HTTP request
|
|
43
|
+
*/
|
|
44
|
+
async request(method, path, options = {}) {
|
|
45
|
+
if (this.config.rate_limit) {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
const windowMs = this.parseDuration(this.config.rate_limit.per);
|
|
48
|
+
if (now - this.windowStart >= windowMs) {
|
|
49
|
+
this.requestCount = 0;
|
|
50
|
+
this.windowStart = now;
|
|
51
|
+
}
|
|
52
|
+
if (this.requestCount >= this.config.rate_limit.requests) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
error: "Rate limit exceeded",
|
|
56
|
+
status: 429
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
this.requestCount++;
|
|
60
|
+
}
|
|
61
|
+
const config = {
|
|
62
|
+
method,
|
|
63
|
+
url: path,
|
|
64
|
+
data: options.body,
|
|
65
|
+
headers: options.headers,
|
|
66
|
+
params: options.params
|
|
67
|
+
};
|
|
68
|
+
try {
|
|
69
|
+
const response = await this.requestWithRetry(config);
|
|
70
|
+
return {
|
|
71
|
+
success: true,
|
|
72
|
+
data: response.data,
|
|
73
|
+
status: response.status,
|
|
74
|
+
headers: response.headers
|
|
75
|
+
};
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (axios.isAxiosError(error)) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: error.message,
|
|
81
|
+
status: error.response?.status,
|
|
82
|
+
data: error.response?.data
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Make a request with retry logic
|
|
93
|
+
*/
|
|
94
|
+
async requestWithRetry(config, attempt = 0) {
|
|
95
|
+
try {
|
|
96
|
+
return await this.client.request(config);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
const maxAttempts = this.config.retry?.attempts ?? 0;
|
|
99
|
+
const delay = this.parseDuration(this.config.retry?.delay ?? "1s");
|
|
100
|
+
if (attempt < maxAttempts && axios.isAxiosError(error)) {
|
|
101
|
+
if (!error.response || error.response.status >= 500) {
|
|
102
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
103
|
+
return this.requestWithRetry(config, attempt + 1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Convenience methods
|
|
111
|
+
*/
|
|
112
|
+
async get(path, options) {
|
|
113
|
+
return this.request("GET", path, options);
|
|
114
|
+
}
|
|
115
|
+
async post(path, body, options) {
|
|
116
|
+
return this.request("POST", path, { body, ...options });
|
|
117
|
+
}
|
|
118
|
+
async put(path, body, options) {
|
|
119
|
+
return this.request("PUT", path, { body, ...options });
|
|
120
|
+
}
|
|
121
|
+
async patch(path, body, options) {
|
|
122
|
+
return this.request("PATCH", path, { body, ...options });
|
|
123
|
+
}
|
|
124
|
+
async delete(path, options) {
|
|
125
|
+
return this.request("DELETE", path, options);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Parse duration string to milliseconds
|
|
129
|
+
*/
|
|
130
|
+
parseDuration(duration) {
|
|
131
|
+
const match = duration.match(/^(\d+)(ms|s|m|h)?$/);
|
|
132
|
+
if (!match) return 3e4;
|
|
133
|
+
const value = parseInt(match[1], 10);
|
|
134
|
+
const unit = match[2] ?? "s";
|
|
135
|
+
switch (unit) {
|
|
136
|
+
case "ms":
|
|
137
|
+
return value;
|
|
138
|
+
case "s":
|
|
139
|
+
return value * 1e3;
|
|
140
|
+
case "m":
|
|
141
|
+
return value * 60 * 1e3;
|
|
142
|
+
case "h":
|
|
143
|
+
return value * 60 * 60 * 1e3;
|
|
144
|
+
default:
|
|
145
|
+
return value * 1e3;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
function createHttpPipe(options) {
|
|
150
|
+
return new HttpPipe(options);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/websocket/index.ts
|
|
154
|
+
import WebSocket from "ws";
|
|
155
|
+
var WebSocketPipe = class {
|
|
156
|
+
name;
|
|
157
|
+
type = "websocket";
|
|
158
|
+
ws = null;
|
|
159
|
+
config;
|
|
160
|
+
reconnectAttempts = 0;
|
|
161
|
+
reconnecting = false;
|
|
162
|
+
heartbeatInterval = null;
|
|
163
|
+
messageHandlers = /* @__PURE__ */ new Map();
|
|
164
|
+
connected = false;
|
|
165
|
+
constructor(options) {
|
|
166
|
+
this.name = options.name;
|
|
167
|
+
this.config = options.config;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Connect to the WebSocket server
|
|
171
|
+
*/
|
|
172
|
+
async connect() {
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
try {
|
|
175
|
+
const headers = this.config.headers;
|
|
176
|
+
this.ws = new WebSocket(this.config.url, { headers });
|
|
177
|
+
this.ws.on("open", () => {
|
|
178
|
+
this.connected = true;
|
|
179
|
+
this.reconnectAttempts = 0;
|
|
180
|
+
this.reconnecting = false;
|
|
181
|
+
this.startHeartbeat();
|
|
182
|
+
resolve();
|
|
183
|
+
});
|
|
184
|
+
this.ws.on("message", (data) => {
|
|
185
|
+
this.handleMessage(data);
|
|
186
|
+
});
|
|
187
|
+
this.ws.on("close", () => {
|
|
188
|
+
this.connected = false;
|
|
189
|
+
this.stopHeartbeat();
|
|
190
|
+
this.handleDisconnect();
|
|
191
|
+
});
|
|
192
|
+
this.ws.on("error", (error) => {
|
|
193
|
+
if (!this.connected) {
|
|
194
|
+
reject(error);
|
|
195
|
+
}
|
|
196
|
+
this.emit("error", error);
|
|
197
|
+
});
|
|
198
|
+
} catch (error) {
|
|
199
|
+
reject(error);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Disconnect from the server
|
|
205
|
+
*/
|
|
206
|
+
async disconnect() {
|
|
207
|
+
this.stopHeartbeat();
|
|
208
|
+
this.reconnecting = false;
|
|
209
|
+
if (this.ws) {
|
|
210
|
+
this.ws.close();
|
|
211
|
+
this.ws = null;
|
|
212
|
+
}
|
|
213
|
+
this.connected = false;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Check if connected
|
|
217
|
+
*/
|
|
218
|
+
isConnected() {
|
|
219
|
+
return this.connected && this.ws?.readyState === WebSocket.OPEN;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Send a message
|
|
223
|
+
*/
|
|
224
|
+
send(data) {
|
|
225
|
+
if (!this.isConnected()) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
const message = typeof data === "string" ? data : JSON.stringify(data);
|
|
229
|
+
this.ws.send(message);
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Send and wait for a response (request-response pattern)
|
|
234
|
+
*/
|
|
235
|
+
async request(data, options = {}) {
|
|
236
|
+
const { timeout = 3e4, responseEvent = "response" } = options;
|
|
237
|
+
if (!this.isConnected()) {
|
|
238
|
+
return { success: false, error: "Not connected" };
|
|
239
|
+
}
|
|
240
|
+
return new Promise((resolve) => {
|
|
241
|
+
const timer = setTimeout(() => {
|
|
242
|
+
this.off(responseEvent, handler);
|
|
243
|
+
resolve({ success: false, error: "Request timeout" });
|
|
244
|
+
}, timeout);
|
|
245
|
+
const handler = (response) => {
|
|
246
|
+
clearTimeout(timer);
|
|
247
|
+
this.off(responseEvent, handler);
|
|
248
|
+
resolve({ success: true, data: response });
|
|
249
|
+
};
|
|
250
|
+
this.on(responseEvent, handler);
|
|
251
|
+
this.send(data);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Register a message handler
|
|
256
|
+
*/
|
|
257
|
+
on(event, handler) {
|
|
258
|
+
const handlers = this.messageHandlers.get(event) ?? [];
|
|
259
|
+
handlers.push(handler);
|
|
260
|
+
this.messageHandlers.set(event, handlers);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Remove a message handler
|
|
264
|
+
*/
|
|
265
|
+
off(event, handler) {
|
|
266
|
+
const handlers = this.messageHandlers.get(event) ?? [];
|
|
267
|
+
const index = handlers.indexOf(handler);
|
|
268
|
+
if (index !== -1) {
|
|
269
|
+
handlers.splice(index, 1);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Emit an event to handlers
|
|
274
|
+
*/
|
|
275
|
+
emit(event, data) {
|
|
276
|
+
const handlers = this.messageHandlers.get(event) ?? [];
|
|
277
|
+
for (const handler of handlers) {
|
|
278
|
+
try {
|
|
279
|
+
handler(data);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
console.error(`WebSocket handler error for "${event}":`, error);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Handle incoming messages
|
|
287
|
+
*/
|
|
288
|
+
handleMessage(rawData) {
|
|
289
|
+
let data;
|
|
290
|
+
try {
|
|
291
|
+
const str = rawData.toString();
|
|
292
|
+
data = JSON.parse(str);
|
|
293
|
+
} catch {
|
|
294
|
+
data = rawData.toString();
|
|
295
|
+
}
|
|
296
|
+
this.emit("message", data);
|
|
297
|
+
if (typeof data === "object" && data !== null && "event" in data) {
|
|
298
|
+
const event = data.event;
|
|
299
|
+
this.emit(event, data);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Handle disconnection
|
|
304
|
+
*/
|
|
305
|
+
handleDisconnect() {
|
|
306
|
+
if (!this.config.reconnect?.enabled || this.reconnecting) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const maxAttempts = this.config.reconnect.max_attempts ?? 10;
|
|
310
|
+
const delay = this.parseDuration(this.config.reconnect.delay ?? "5s");
|
|
311
|
+
if (this.reconnectAttempts >= maxAttempts) {
|
|
312
|
+
this.emit("reconnect_failed", { attempts: this.reconnectAttempts });
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
this.reconnecting = true;
|
|
316
|
+
this.reconnectAttempts++;
|
|
317
|
+
setTimeout(async () => {
|
|
318
|
+
try {
|
|
319
|
+
await this.connect();
|
|
320
|
+
this.emit("reconnected", { attempts: this.reconnectAttempts });
|
|
321
|
+
} catch {
|
|
322
|
+
this.reconnecting = false;
|
|
323
|
+
this.handleDisconnect();
|
|
324
|
+
}
|
|
325
|
+
}, delay);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Start heartbeat
|
|
329
|
+
*/
|
|
330
|
+
startHeartbeat() {
|
|
331
|
+
if (!this.config.heartbeat?.interval) return;
|
|
332
|
+
const interval = this.parseDuration(this.config.heartbeat.interval);
|
|
333
|
+
const message = this.config.heartbeat.message ?? "ping";
|
|
334
|
+
this.heartbeatInterval = setInterval(() => {
|
|
335
|
+
if (this.isConnected()) {
|
|
336
|
+
this.send(message);
|
|
337
|
+
}
|
|
338
|
+
}, interval);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Stop heartbeat
|
|
342
|
+
*/
|
|
343
|
+
stopHeartbeat() {
|
|
344
|
+
if (this.heartbeatInterval) {
|
|
345
|
+
clearInterval(this.heartbeatInterval);
|
|
346
|
+
this.heartbeatInterval = null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Parse duration string to milliseconds
|
|
351
|
+
*/
|
|
352
|
+
parseDuration(duration) {
|
|
353
|
+
const match = duration.match(/^(\d+)(ms|s|m|h)?$/);
|
|
354
|
+
if (!match) return 5e3;
|
|
355
|
+
const value = parseInt(match[1], 10);
|
|
356
|
+
const unit = match[2] ?? "s";
|
|
357
|
+
switch (unit) {
|
|
358
|
+
case "ms":
|
|
359
|
+
return value;
|
|
360
|
+
case "s":
|
|
361
|
+
return value * 1e3;
|
|
362
|
+
case "m":
|
|
363
|
+
return value * 60 * 1e3;
|
|
364
|
+
case "h":
|
|
365
|
+
return value * 60 * 60 * 1e3;
|
|
366
|
+
default:
|
|
367
|
+
return value * 1e3;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
function createWebSocketPipe(options) {
|
|
372
|
+
return new WebSocketPipe(options);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/webhook/index.ts
|
|
376
|
+
import { createHmac } from "crypto";
|
|
377
|
+
var WebhookPipe = class {
|
|
378
|
+
name;
|
|
379
|
+
type = "webhook";
|
|
380
|
+
config;
|
|
381
|
+
handlers = [];
|
|
382
|
+
constructor(options) {
|
|
383
|
+
this.name = options.name;
|
|
384
|
+
this.config = options.config;
|
|
385
|
+
}
|
|
386
|
+
isConnected() {
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Get the webhook path
|
|
391
|
+
*/
|
|
392
|
+
getPath() {
|
|
393
|
+
return this.config.path;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Get the expected method
|
|
397
|
+
*/
|
|
398
|
+
getMethod() {
|
|
399
|
+
return this.config.method ?? "POST";
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Register a handler for incoming webhooks
|
|
403
|
+
*/
|
|
404
|
+
onWebhook(handler) {
|
|
405
|
+
this.handlers.push(handler);
|
|
406
|
+
return () => {
|
|
407
|
+
const index = this.handlers.indexOf(handler);
|
|
408
|
+
if (index !== -1) {
|
|
409
|
+
this.handlers.splice(index, 1);
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Handle an incoming webhook request
|
|
415
|
+
*/
|
|
416
|
+
async handleRequest(body, headers) {
|
|
417
|
+
if (this.config.verification) {
|
|
418
|
+
const valid = this.verifySignature(body, headers);
|
|
419
|
+
if (!valid) {
|
|
420
|
+
return { success: false, error: "Invalid signature", status: 401 };
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
for (const handler of this.handlers) {
|
|
424
|
+
try {
|
|
425
|
+
await handler(body, headers);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
console.error(`Webhook handler error:`, error);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (this.config.handlers) {
|
|
431
|
+
}
|
|
432
|
+
return { success: true, status: 200 };
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Verify webhook signature
|
|
436
|
+
*/
|
|
437
|
+
verifySignature(body, headers) {
|
|
438
|
+
if (!this.config.verification) return true;
|
|
439
|
+
const { type, secret, header, algorithm } = this.config.verification;
|
|
440
|
+
const signatureHeader = header ?? "x-signature";
|
|
441
|
+
const receivedSignature = headers[signatureHeader.toLowerCase()];
|
|
442
|
+
if (!receivedSignature || !secret) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
const bodyString = typeof body === "string" ? body : JSON.stringify(body);
|
|
446
|
+
switch (type) {
|
|
447
|
+
case "hmac": {
|
|
448
|
+
const algo = algorithm ?? "sha256";
|
|
449
|
+
const expectedSignature = createHmac(algo, secret).update(bodyString).digest("hex");
|
|
450
|
+
const cleanReceived = receivedSignature.replace(/^sha\d+=/, "");
|
|
451
|
+
return this.timingSafeEqual(cleanReceived, expectedSignature);
|
|
452
|
+
}
|
|
453
|
+
case "token": {
|
|
454
|
+
return receivedSignature === secret;
|
|
455
|
+
}
|
|
456
|
+
case "signature": {
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
default:
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Timing-safe string comparison
|
|
465
|
+
*/
|
|
466
|
+
timingSafeEqual(a, b) {
|
|
467
|
+
if (a.length !== b.length) {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
let result = 0;
|
|
471
|
+
for (let i = 0; i < a.length; i++) {
|
|
472
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
473
|
+
}
|
|
474
|
+
return result === 0;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
var WebhookSender = class {
|
|
478
|
+
/**
|
|
479
|
+
* Send a Discord webhook
|
|
480
|
+
*/
|
|
481
|
+
static async sendDiscordWebhook(url, options) {
|
|
482
|
+
try {
|
|
483
|
+
const response = await fetch(url, {
|
|
484
|
+
method: "POST",
|
|
485
|
+
headers: { "Content-Type": "application/json" },
|
|
486
|
+
body: JSON.stringify(options)
|
|
487
|
+
});
|
|
488
|
+
if (!response.ok) {
|
|
489
|
+
return {
|
|
490
|
+
success: false,
|
|
491
|
+
error: `HTTP ${response.status}`,
|
|
492
|
+
status: response.status
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
return { success: true, status: response.status };
|
|
496
|
+
} catch (error) {
|
|
497
|
+
return {
|
|
498
|
+
success: false,
|
|
499
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Send a generic webhook
|
|
505
|
+
*/
|
|
506
|
+
static async send(url, body, options = {}) {
|
|
507
|
+
try {
|
|
508
|
+
const response = await fetch(url, {
|
|
509
|
+
method: options.method ?? "POST",
|
|
510
|
+
headers: {
|
|
511
|
+
"Content-Type": "application/json",
|
|
512
|
+
...options.headers
|
|
513
|
+
},
|
|
514
|
+
body: typeof body === "string" ? body : JSON.stringify(body)
|
|
515
|
+
});
|
|
516
|
+
let data;
|
|
517
|
+
try {
|
|
518
|
+
data = await response.json();
|
|
519
|
+
} catch {
|
|
520
|
+
data = await response.text();
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
success: response.ok,
|
|
524
|
+
data,
|
|
525
|
+
status: response.status
|
|
526
|
+
};
|
|
527
|
+
} catch (error) {
|
|
528
|
+
return {
|
|
529
|
+
success: false,
|
|
530
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
function createWebhookPipe(options) {
|
|
536
|
+
return new WebhookPipe(options);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/mqtt/index.ts
|
|
540
|
+
import mqtt from "mqtt";
|
|
541
|
+
var MqttPipe = class {
|
|
542
|
+
name;
|
|
543
|
+
type = "mqtt";
|
|
544
|
+
client = null;
|
|
545
|
+
config;
|
|
546
|
+
connected = false;
|
|
547
|
+
reconnectAttempts = 0;
|
|
548
|
+
messageHandlers = /* @__PURE__ */ new Map();
|
|
549
|
+
wildcardHandlers = /* @__PURE__ */ new Map();
|
|
550
|
+
constructor(options) {
|
|
551
|
+
this.name = options.name;
|
|
552
|
+
this.config = options.config;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Connect to the MQTT broker
|
|
556
|
+
*/
|
|
557
|
+
async connect() {
|
|
558
|
+
return new Promise((resolve, reject) => {
|
|
559
|
+
const protocol = this.config.protocol ?? "mqtt";
|
|
560
|
+
const port = this.config.port ?? (protocol === "mqtts" || protocol === "wss" ? 8883 : 1883);
|
|
561
|
+
const url = `${protocol}://${this.config.broker}:${port}`;
|
|
562
|
+
const options = {
|
|
563
|
+
keepalive: this.config.keepalive ?? 60,
|
|
564
|
+
clean: this.config.clean ?? true,
|
|
565
|
+
reconnectPeriod: this.config.reconnect?.enabled !== false ? this.parseDuration(this.config.reconnect?.delay ?? "5s") : 0
|
|
566
|
+
};
|
|
567
|
+
if (this.config.auth) {
|
|
568
|
+
if (this.config.auth.username) options.username = this.config.auth.username;
|
|
569
|
+
if (this.config.auth.password) options.password = this.config.auth.password;
|
|
570
|
+
if (this.config.auth.clientId) options.clientId = this.config.auth.clientId;
|
|
571
|
+
}
|
|
572
|
+
if (this.config.will) {
|
|
573
|
+
options.will = {
|
|
574
|
+
topic: this.config.will.topic,
|
|
575
|
+
payload: Buffer.from(this.config.will.payload),
|
|
576
|
+
qos: this.config.will.qos ?? 0,
|
|
577
|
+
retain: this.config.will.retain ?? false
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
try {
|
|
581
|
+
this.client = mqtt.connect(url, options);
|
|
582
|
+
this.client.on("connect", () => {
|
|
583
|
+
this.connected = true;
|
|
584
|
+
this.reconnectAttempts = 0;
|
|
585
|
+
this.emit("connected", {});
|
|
586
|
+
resolve();
|
|
587
|
+
});
|
|
588
|
+
this.client.on("message", (topic, payload, packet) => {
|
|
589
|
+
this.handleMessage(topic, payload, packet);
|
|
590
|
+
});
|
|
591
|
+
this.client.on("error", (error) => {
|
|
592
|
+
if (!this.connected) {
|
|
593
|
+
reject(error);
|
|
594
|
+
}
|
|
595
|
+
this.emit("error", error);
|
|
596
|
+
});
|
|
597
|
+
this.client.on("close", () => {
|
|
598
|
+
this.connected = false;
|
|
599
|
+
this.emit("disconnected", {});
|
|
600
|
+
});
|
|
601
|
+
this.client.on("reconnect", () => {
|
|
602
|
+
this.reconnectAttempts++;
|
|
603
|
+
this.emit("reconnecting", { attempts: this.reconnectAttempts });
|
|
604
|
+
});
|
|
605
|
+
this.client.on("offline", () => {
|
|
606
|
+
this.connected = false;
|
|
607
|
+
this.emit("offline", {});
|
|
608
|
+
});
|
|
609
|
+
} catch (error) {
|
|
610
|
+
reject(error);
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Disconnect from the broker
|
|
616
|
+
*/
|
|
617
|
+
async disconnect() {
|
|
618
|
+
return new Promise((resolve) => {
|
|
619
|
+
if (this.client) {
|
|
620
|
+
this.client.end(false, {}, () => {
|
|
621
|
+
this.client = null;
|
|
622
|
+
this.connected = false;
|
|
623
|
+
resolve();
|
|
624
|
+
});
|
|
625
|
+
} else {
|
|
626
|
+
resolve();
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Check if connected
|
|
632
|
+
*/
|
|
633
|
+
isConnected() {
|
|
634
|
+
return this.connected && this.client?.connected === true;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Subscribe to a topic
|
|
638
|
+
*/
|
|
639
|
+
async subscribe(topic, options = {}) {
|
|
640
|
+
if (!this.isConnected()) {
|
|
641
|
+
return { success: false, error: "Not connected" };
|
|
642
|
+
}
|
|
643
|
+
return new Promise((resolve) => {
|
|
644
|
+
this.client.subscribe(topic, { qos: options.qos ?? 0 }, (error, granted) => {
|
|
645
|
+
if (error) {
|
|
646
|
+
resolve({ success: false, error: error.message });
|
|
647
|
+
} else {
|
|
648
|
+
resolve({ success: true, data: granted });
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Unsubscribe from a topic
|
|
655
|
+
*/
|
|
656
|
+
async unsubscribe(topic) {
|
|
657
|
+
if (!this.isConnected()) {
|
|
658
|
+
return { success: false, error: "Not connected" };
|
|
659
|
+
}
|
|
660
|
+
return new Promise((resolve) => {
|
|
661
|
+
this.client.unsubscribe(topic, {}, (error) => {
|
|
662
|
+
if (error) {
|
|
663
|
+
resolve({ success: false, error: error.message });
|
|
664
|
+
} else {
|
|
665
|
+
resolve({ success: true });
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Publish a message to a topic
|
|
672
|
+
*/
|
|
673
|
+
async publish(topic, message, options = {}) {
|
|
674
|
+
if (!this.isConnected()) {
|
|
675
|
+
return { success: false, error: "Not connected" };
|
|
676
|
+
}
|
|
677
|
+
const payload = typeof message === "object" && !Buffer.isBuffer(message) ? JSON.stringify(message) : message;
|
|
678
|
+
const publishOptions = {
|
|
679
|
+
qos: options.qos ?? 0,
|
|
680
|
+
retain: options.retain ?? false
|
|
681
|
+
};
|
|
682
|
+
return new Promise((resolve) => {
|
|
683
|
+
this.client.publish(topic, payload, publishOptions, (error) => {
|
|
684
|
+
if (error) {
|
|
685
|
+
resolve({ success: false, error: error.message });
|
|
686
|
+
} else {
|
|
687
|
+
resolve({ success: true });
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Register a handler for a specific topic
|
|
694
|
+
* Supports MQTT wildcards: + (single level), # (multi level)
|
|
695
|
+
*/
|
|
696
|
+
on(topic, handler) {
|
|
697
|
+
if (topic.includes("+") || topic.includes("#")) {
|
|
698
|
+
const handlers = this.wildcardHandlers.get(topic) ?? [];
|
|
699
|
+
handlers.push(handler);
|
|
700
|
+
this.wildcardHandlers.set(topic, handlers);
|
|
701
|
+
} else {
|
|
702
|
+
const handlers = this.messageHandlers.get(topic) ?? [];
|
|
703
|
+
handlers.push(handler);
|
|
704
|
+
this.messageHandlers.set(topic, handlers);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Remove a handler
|
|
709
|
+
*/
|
|
710
|
+
off(topic, handler) {
|
|
711
|
+
const map = topic.includes("+") || topic.includes("#") ? this.wildcardHandlers : this.messageHandlers;
|
|
712
|
+
const handlers = map.get(topic) ?? [];
|
|
713
|
+
const index = handlers.indexOf(handler);
|
|
714
|
+
if (index !== -1) {
|
|
715
|
+
handlers.splice(index, 1);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Emit to handlers
|
|
720
|
+
*/
|
|
721
|
+
emit(event, data) {
|
|
722
|
+
const handlers = this.messageHandlers.get(event) ?? [];
|
|
723
|
+
for (const handler of handlers) {
|
|
724
|
+
try {
|
|
725
|
+
handler(event, data, {});
|
|
726
|
+
} catch (error) {
|
|
727
|
+
console.error(`MQTT handler error for "${event}":`, error);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Handle incoming messages
|
|
733
|
+
*/
|
|
734
|
+
handleMessage(topic, payload, packet) {
|
|
735
|
+
let parsedPayload = payload;
|
|
736
|
+
try {
|
|
737
|
+
const str = payload.toString();
|
|
738
|
+
parsedPayload = JSON.parse(str);
|
|
739
|
+
} catch {
|
|
740
|
+
parsedPayload = payload.toString();
|
|
741
|
+
}
|
|
742
|
+
const exactHandlers = this.messageHandlers.get(topic) ?? [];
|
|
743
|
+
for (const handler of exactHandlers) {
|
|
744
|
+
try {
|
|
745
|
+
handler(topic, parsedPayload, packet);
|
|
746
|
+
} catch (error) {
|
|
747
|
+
console.error(`MQTT handler error for topic "${topic}":`, error);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
for (const [pattern, handlers] of this.wildcardHandlers) {
|
|
751
|
+
if (this.topicMatches(pattern, topic)) {
|
|
752
|
+
for (const handler of handlers) {
|
|
753
|
+
try {
|
|
754
|
+
handler(topic, parsedPayload, packet);
|
|
755
|
+
} catch (error) {
|
|
756
|
+
console.error(`MQTT wildcard handler error for pattern "${pattern}":`, error);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
this.emit("message", { topic, payload: parsedPayload, packet });
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Check if a topic matches a pattern with MQTT wildcards
|
|
765
|
+
*/
|
|
766
|
+
topicMatches(pattern, topic) {
|
|
767
|
+
const patternParts = pattern.split("/");
|
|
768
|
+
const topicParts = topic.split("/");
|
|
769
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
770
|
+
const patternPart = patternParts[i];
|
|
771
|
+
if (patternPart === "#") {
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
if (i >= topicParts.length) {
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
if (patternPart === "+") {
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
if (patternPart !== topicParts[i]) {
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return patternParts.length === topicParts.length;
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Parse duration string to milliseconds
|
|
788
|
+
*/
|
|
789
|
+
parseDuration(duration) {
|
|
790
|
+
const match = duration.match(/^(\d+)(ms|s|m|h)?$/);
|
|
791
|
+
if (!match) return 5e3;
|
|
792
|
+
const value = parseInt(match[1], 10);
|
|
793
|
+
const unit = match[2] ?? "s";
|
|
794
|
+
switch (unit) {
|
|
795
|
+
case "ms":
|
|
796
|
+
return value;
|
|
797
|
+
case "s":
|
|
798
|
+
return value * 1e3;
|
|
799
|
+
case "m":
|
|
800
|
+
return value * 60 * 1e3;
|
|
801
|
+
case "h":
|
|
802
|
+
return value * 60 * 60 * 1e3;
|
|
803
|
+
default:
|
|
804
|
+
return value * 1e3;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
function createMqttPipe(options) {
|
|
809
|
+
return new MqttPipe(options);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/tcp/index.ts
|
|
813
|
+
import { createConnection, createServer } from "net";
|
|
814
|
+
|
|
815
|
+
// src/tcp/udp.ts
|
|
816
|
+
import { createSocket } from "dgram";
|
|
817
|
+
var UdpPipe = class {
|
|
818
|
+
name;
|
|
819
|
+
type = "udp";
|
|
820
|
+
socket = null;
|
|
821
|
+
config;
|
|
822
|
+
bound = false;
|
|
823
|
+
messageHandlers = [];
|
|
824
|
+
eventHandlers = /* @__PURE__ */ new Map();
|
|
825
|
+
constructor(options) {
|
|
826
|
+
this.name = options.name;
|
|
827
|
+
this.config = options.config;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Bind to a port to receive messages
|
|
831
|
+
*/
|
|
832
|
+
async bind(port) {
|
|
833
|
+
return new Promise((resolve, reject) => {
|
|
834
|
+
try {
|
|
835
|
+
this.socket = createSocket("udp4");
|
|
836
|
+
this.socket.on("message", (msg, rinfo) => {
|
|
837
|
+
this.handleMessage(msg, rinfo);
|
|
838
|
+
});
|
|
839
|
+
this.socket.on("error", (error) => {
|
|
840
|
+
if (!this.bound) {
|
|
841
|
+
reject(error);
|
|
842
|
+
}
|
|
843
|
+
this.emit("error", error);
|
|
844
|
+
});
|
|
845
|
+
this.socket.on("listening", () => {
|
|
846
|
+
this.bound = true;
|
|
847
|
+
const address = this.socket.address();
|
|
848
|
+
this.emit("listening", address);
|
|
849
|
+
resolve();
|
|
850
|
+
});
|
|
851
|
+
if (this.config.broadcast) {
|
|
852
|
+
this.socket.setBroadcast(true);
|
|
853
|
+
}
|
|
854
|
+
const bindPort = port ?? this.config.port;
|
|
855
|
+
const bindHost = this.config.host ?? "0.0.0.0";
|
|
856
|
+
this.socket.bind(bindPort, bindHost, () => {
|
|
857
|
+
if (this.config.multicast) {
|
|
858
|
+
this.socket.addMembership(this.config.multicast);
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
} catch (error) {
|
|
862
|
+
reject(error);
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Close the socket
|
|
868
|
+
*/
|
|
869
|
+
async disconnect() {
|
|
870
|
+
return new Promise((resolve) => {
|
|
871
|
+
if (this.socket) {
|
|
872
|
+
this.socket.close(() => {
|
|
873
|
+
this.socket = null;
|
|
874
|
+
this.bound = false;
|
|
875
|
+
resolve();
|
|
876
|
+
});
|
|
877
|
+
} else {
|
|
878
|
+
resolve();
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Check if bound
|
|
884
|
+
*/
|
|
885
|
+
isConnected() {
|
|
886
|
+
return this.bound;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Send data to a specific host and port
|
|
890
|
+
*/
|
|
891
|
+
async send(data, host, port) {
|
|
892
|
+
if (!this.socket) {
|
|
893
|
+
this.socket = createSocket("udp4");
|
|
894
|
+
if (this.config.broadcast) {
|
|
895
|
+
this.socket.setBroadcast(true);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const buffer = typeof data === "string" ? Buffer.from(data) : data;
|
|
899
|
+
return new Promise((resolve) => {
|
|
900
|
+
this.socket.send(buffer, port, host, (error) => {
|
|
901
|
+
if (error) {
|
|
902
|
+
resolve({ success: false, error: error.message });
|
|
903
|
+
} else {
|
|
904
|
+
resolve({ success: true });
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Broadcast data to all hosts on the network
|
|
911
|
+
*/
|
|
912
|
+
async broadcast(data, port, address = "255.255.255.255") {
|
|
913
|
+
if (!this.socket) {
|
|
914
|
+
this.socket = createSocket("udp4");
|
|
915
|
+
this.socket.setBroadcast(true);
|
|
916
|
+
}
|
|
917
|
+
const buffer = typeof data === "string" ? Buffer.from(data) : data;
|
|
918
|
+
return new Promise((resolve) => {
|
|
919
|
+
this.socket.send(buffer, port, address, (error) => {
|
|
920
|
+
if (error) {
|
|
921
|
+
resolve({ success: false, error: error.message });
|
|
922
|
+
} else {
|
|
923
|
+
resolve({ success: true });
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Send to multicast group
|
|
930
|
+
*/
|
|
931
|
+
async multicast(data, port, group) {
|
|
932
|
+
const multicastGroup = group ?? this.config.multicast;
|
|
933
|
+
if (!multicastGroup) {
|
|
934
|
+
return { success: false, error: "No multicast group configured" };
|
|
935
|
+
}
|
|
936
|
+
return this.send(data, multicastGroup, port);
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Register a message handler
|
|
940
|
+
*/
|
|
941
|
+
onMessage(handler) {
|
|
942
|
+
this.messageHandlers.push(handler);
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Remove a message handler
|
|
946
|
+
*/
|
|
947
|
+
offMessage(handler) {
|
|
948
|
+
const index = this.messageHandlers.indexOf(handler);
|
|
949
|
+
if (index !== -1) {
|
|
950
|
+
this.messageHandlers.splice(index, 1);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Register an event handler
|
|
955
|
+
*/
|
|
956
|
+
on(event, handler) {
|
|
957
|
+
const handlers = this.eventHandlers.get(event) ?? [];
|
|
958
|
+
handlers.push(handler);
|
|
959
|
+
this.eventHandlers.set(event, handlers);
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Remove an event handler
|
|
963
|
+
*/
|
|
964
|
+
off(event, handler) {
|
|
965
|
+
const handlers = this.eventHandlers.get(event) ?? [];
|
|
966
|
+
const index = handlers.indexOf(handler);
|
|
967
|
+
if (index !== -1) {
|
|
968
|
+
handlers.splice(index, 1);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Emit an event
|
|
973
|
+
*/
|
|
974
|
+
emit(event, data) {
|
|
975
|
+
const handlers = this.eventHandlers.get(event) ?? [];
|
|
976
|
+
for (const handler of handlers) {
|
|
977
|
+
try {
|
|
978
|
+
handler(data);
|
|
979
|
+
} catch (error) {
|
|
980
|
+
console.error(`UDP handler error for "${event}":`, error);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Handle incoming messages
|
|
986
|
+
*/
|
|
987
|
+
handleMessage(data, rinfo) {
|
|
988
|
+
const msg = { data, rinfo };
|
|
989
|
+
for (const handler of this.messageHandlers) {
|
|
990
|
+
try {
|
|
991
|
+
handler(msg);
|
|
992
|
+
} catch (error) {
|
|
993
|
+
console.error("UDP message handler error:", error);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
this.emit("message", msg);
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
function createUdpPipe(options) {
|
|
1000
|
+
return new UdpPipe(options);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/tcp/index.ts
|
|
1004
|
+
var TcpPipe = class {
|
|
1005
|
+
name;
|
|
1006
|
+
type = "tcp";
|
|
1007
|
+
socket = null;
|
|
1008
|
+
server = null;
|
|
1009
|
+
config;
|
|
1010
|
+
connected = false;
|
|
1011
|
+
reconnectAttempts = 0;
|
|
1012
|
+
reconnecting = false;
|
|
1013
|
+
dataHandlers = [];
|
|
1014
|
+
eventHandlers = /* @__PURE__ */ new Map();
|
|
1015
|
+
constructor(options) {
|
|
1016
|
+
this.name = options.name;
|
|
1017
|
+
this.config = options.config;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Connect to a TCP server (client mode)
|
|
1021
|
+
*/
|
|
1022
|
+
async connect() {
|
|
1023
|
+
return new Promise((resolve, reject) => {
|
|
1024
|
+
try {
|
|
1025
|
+
this.socket = createConnection({
|
|
1026
|
+
host: this.config.host,
|
|
1027
|
+
port: this.config.port
|
|
1028
|
+
});
|
|
1029
|
+
this.socket.setEncoding(this.config.encoding ?? "utf8");
|
|
1030
|
+
this.socket.on("connect", () => {
|
|
1031
|
+
this.connected = true;
|
|
1032
|
+
this.reconnectAttempts = 0;
|
|
1033
|
+
this.reconnecting = false;
|
|
1034
|
+
this.emit("connected");
|
|
1035
|
+
resolve();
|
|
1036
|
+
});
|
|
1037
|
+
this.socket.on("data", (data) => {
|
|
1038
|
+
this.handleData(data);
|
|
1039
|
+
});
|
|
1040
|
+
this.socket.on("close", () => {
|
|
1041
|
+
this.connected = false;
|
|
1042
|
+
this.emit("disconnected");
|
|
1043
|
+
this.handleDisconnect();
|
|
1044
|
+
});
|
|
1045
|
+
this.socket.on("error", (error) => {
|
|
1046
|
+
if (!this.connected) {
|
|
1047
|
+
reject(error);
|
|
1048
|
+
}
|
|
1049
|
+
this.emit("error", error);
|
|
1050
|
+
});
|
|
1051
|
+
this.socket.on("end", () => {
|
|
1052
|
+
this.connected = false;
|
|
1053
|
+
this.emit("end");
|
|
1054
|
+
});
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
reject(error);
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Start a TCP server (server mode)
|
|
1062
|
+
*/
|
|
1063
|
+
async listen(port) {
|
|
1064
|
+
return new Promise((resolve, reject) => {
|
|
1065
|
+
try {
|
|
1066
|
+
this.server = createServer((socket) => {
|
|
1067
|
+
socket.setEncoding(this.config.encoding ?? "utf8");
|
|
1068
|
+
socket.on("data", (data) => {
|
|
1069
|
+
this.handleData(data);
|
|
1070
|
+
});
|
|
1071
|
+
socket.on("error", (error) => {
|
|
1072
|
+
this.emit("client_error", error);
|
|
1073
|
+
});
|
|
1074
|
+
this.emit("connection", socket);
|
|
1075
|
+
});
|
|
1076
|
+
this.server.on("error", (error) => {
|
|
1077
|
+
reject(error);
|
|
1078
|
+
});
|
|
1079
|
+
this.server.listen(port ?? this.config.port, () => {
|
|
1080
|
+
this.connected = true;
|
|
1081
|
+
this.emit("listening");
|
|
1082
|
+
resolve();
|
|
1083
|
+
});
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
reject(error);
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Disconnect / close
|
|
1091
|
+
*/
|
|
1092
|
+
async disconnect() {
|
|
1093
|
+
this.reconnecting = false;
|
|
1094
|
+
return new Promise((resolve) => {
|
|
1095
|
+
if (this.socket) {
|
|
1096
|
+
this.socket.end(() => {
|
|
1097
|
+
this.socket?.destroy();
|
|
1098
|
+
this.socket = null;
|
|
1099
|
+
this.connected = false;
|
|
1100
|
+
resolve();
|
|
1101
|
+
});
|
|
1102
|
+
} else if (this.server) {
|
|
1103
|
+
this.server.close(() => {
|
|
1104
|
+
this.server = null;
|
|
1105
|
+
this.connected = false;
|
|
1106
|
+
resolve();
|
|
1107
|
+
});
|
|
1108
|
+
} else {
|
|
1109
|
+
resolve();
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Check if connected
|
|
1115
|
+
*/
|
|
1116
|
+
isConnected() {
|
|
1117
|
+
return this.connected;
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Send data
|
|
1121
|
+
*/
|
|
1122
|
+
async send(data) {
|
|
1123
|
+
if (!this.socket || !this.connected) {
|
|
1124
|
+
return { success: false, error: "Not connected" };
|
|
1125
|
+
}
|
|
1126
|
+
return new Promise((resolve) => {
|
|
1127
|
+
this.socket.write(data, (error) => {
|
|
1128
|
+
if (error) {
|
|
1129
|
+
resolve({ success: false, error: error.message });
|
|
1130
|
+
} else {
|
|
1131
|
+
resolve({ success: true });
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Send and wait for response (request-response pattern)
|
|
1138
|
+
*/
|
|
1139
|
+
async request(data, options = {}) {
|
|
1140
|
+
const { timeout = 3e4 } = options;
|
|
1141
|
+
if (!this.socket || !this.connected) {
|
|
1142
|
+
return { success: false, error: "Not connected" };
|
|
1143
|
+
}
|
|
1144
|
+
return new Promise((resolve) => {
|
|
1145
|
+
const timer = setTimeout(() => {
|
|
1146
|
+
this.offData(handler);
|
|
1147
|
+
resolve({ success: false, error: "Request timeout" });
|
|
1148
|
+
}, timeout);
|
|
1149
|
+
const handler = (response) => {
|
|
1150
|
+
clearTimeout(timer);
|
|
1151
|
+
this.offData(handler);
|
|
1152
|
+
resolve({ success: true, data: response });
|
|
1153
|
+
};
|
|
1154
|
+
this.onData(handler);
|
|
1155
|
+
this.send(data);
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Register a data handler
|
|
1160
|
+
*/
|
|
1161
|
+
onData(handler) {
|
|
1162
|
+
this.dataHandlers.push(handler);
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Remove a data handler
|
|
1166
|
+
*/
|
|
1167
|
+
offData(handler) {
|
|
1168
|
+
const index = this.dataHandlers.indexOf(handler);
|
|
1169
|
+
if (index !== -1) {
|
|
1170
|
+
this.dataHandlers.splice(index, 1);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Register an event handler
|
|
1175
|
+
*/
|
|
1176
|
+
on(event, handler) {
|
|
1177
|
+
const handlers = this.eventHandlers.get(event) ?? [];
|
|
1178
|
+
handlers.push(handler);
|
|
1179
|
+
this.eventHandlers.set(event, handlers);
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Remove an event handler
|
|
1183
|
+
*/
|
|
1184
|
+
off(event, handler) {
|
|
1185
|
+
const handlers = this.eventHandlers.get(event) ?? [];
|
|
1186
|
+
const index = handlers.indexOf(handler);
|
|
1187
|
+
if (index !== -1) {
|
|
1188
|
+
handlers.splice(index, 1);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Emit an event
|
|
1193
|
+
*/
|
|
1194
|
+
emit(event, data) {
|
|
1195
|
+
const handlers = this.eventHandlers.get(event) ?? [];
|
|
1196
|
+
for (const handler of handlers) {
|
|
1197
|
+
try {
|
|
1198
|
+
handler(data);
|
|
1199
|
+
} catch (error) {
|
|
1200
|
+
console.error(`TCP handler error for "${event}":`, error);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Handle incoming data
|
|
1206
|
+
*/
|
|
1207
|
+
handleData(data) {
|
|
1208
|
+
for (const handler of this.dataHandlers) {
|
|
1209
|
+
try {
|
|
1210
|
+
handler(data);
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
console.error("TCP data handler error:", error);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
this.emit("data", data);
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Handle disconnection with reconnect logic
|
|
1219
|
+
*/
|
|
1220
|
+
handleDisconnect() {
|
|
1221
|
+
if (!this.config.reconnect?.enabled || this.reconnecting) {
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const maxAttempts = this.config.reconnect.max_attempts ?? 10;
|
|
1225
|
+
const delay = this.parseDuration(this.config.reconnect.delay ?? "5s");
|
|
1226
|
+
if (this.reconnectAttempts >= maxAttempts) {
|
|
1227
|
+
this.emit("reconnect_failed", { attempts: this.reconnectAttempts });
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
this.reconnecting = true;
|
|
1231
|
+
this.reconnectAttempts++;
|
|
1232
|
+
setTimeout(async () => {
|
|
1233
|
+
try {
|
|
1234
|
+
await this.connect();
|
|
1235
|
+
this.emit("reconnected", { attempts: this.reconnectAttempts });
|
|
1236
|
+
} catch {
|
|
1237
|
+
this.reconnecting = false;
|
|
1238
|
+
this.handleDisconnect();
|
|
1239
|
+
}
|
|
1240
|
+
}, delay);
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Parse duration string to milliseconds
|
|
1244
|
+
*/
|
|
1245
|
+
parseDuration(duration) {
|
|
1246
|
+
const match = duration.match(/^(\d+)(ms|s|m|h)?$/);
|
|
1247
|
+
if (!match) return 5e3;
|
|
1248
|
+
const value = parseInt(match[1], 10);
|
|
1249
|
+
const unit = match[2] ?? "s";
|
|
1250
|
+
switch (unit) {
|
|
1251
|
+
case "ms":
|
|
1252
|
+
return value;
|
|
1253
|
+
case "s":
|
|
1254
|
+
return value * 1e3;
|
|
1255
|
+
case "m":
|
|
1256
|
+
return value * 60 * 1e3;
|
|
1257
|
+
case "h":
|
|
1258
|
+
return value * 60 * 60 * 1e3;
|
|
1259
|
+
default:
|
|
1260
|
+
return value * 1e3;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
function createTcpPipe(options) {
|
|
1265
|
+
return new TcpPipe(options);
|
|
1266
|
+
}
|
|
1267
|
+
export {
|
|
1268
|
+
HttpPipe,
|
|
1269
|
+
MqttPipe,
|
|
1270
|
+
TcpPipe,
|
|
1271
|
+
UdpPipe,
|
|
1272
|
+
WebSocketPipe,
|
|
1273
|
+
WebhookPipe,
|
|
1274
|
+
WebhookSender,
|
|
1275
|
+
createHttpPipe,
|
|
1276
|
+
createMqttPipe,
|
|
1277
|
+
createTcpPipe,
|
|
1278
|
+
createUdpPipe,
|
|
1279
|
+
createWebSocketPipe,
|
|
1280
|
+
createWebhookPipe
|
|
1281
|
+
};
|
|
1282
|
+
//# sourceMappingURL=index.js.map
|