waengine 1.0.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.
@@ -0,0 +1,404 @@
1
+ import { WhatsAppClient } from "./client.js";
2
+ import fs from "fs";
3
+ import path from "path";
4
+
5
+ export class DeviceManager {
6
+ constructor(options = {}) {
7
+ this.devices = new Map();
8
+ this.activeDevices = new Set();
9
+ this.config = {
10
+ maxDevices: options.maxDevices || 3,
11
+ loadBalancing: options.loadBalancing || 'round-robin', // round-robin, random, failover
12
+ syncEvents: options.syncEvents !== false, // Default: true
13
+ ...options
14
+ };
15
+
16
+ this.currentDeviceIndex = 0;
17
+ this.eventHandlers = new Map();
18
+
19
+ console.log(`🔧 DeviceManager initialisiert (Max: ${this.config.maxDevices} Devices)`);
20
+ }
21
+
22
+ // ===== DEVICE MANAGEMENT =====
23
+
24
+ async addDevice(deviceId, options = {}) {
25
+ if (this.devices.has(deviceId)) {
26
+ throw new Error(`❌ Device '${deviceId}' existiert bereits!`);
27
+ }
28
+
29
+ if (this.devices.size >= this.config.maxDevices) {
30
+ throw new Error(`❌ Maximum von ${this.config.maxDevices} Devices erreicht!`);
31
+ }
32
+
33
+ // Device-spezifische Auth-Directory
34
+ const authDir = `./auth/${deviceId}`;
35
+ this.ensureAuthDir(authDir);
36
+
37
+ // Device-spezifische Browser-Konfiguration
38
+ const deviceOptions = {
39
+ authDir: authDir,
40
+ browser: [`Bot-${deviceId}`, "1.0.0", ""],
41
+ logLevel: "silent",
42
+ deviceId: deviceId,
43
+ ...options
44
+ };
45
+
46
+ const client = new WhatsAppClient(deviceOptions);
47
+
48
+ // Event-Forwarding einrichten
49
+ this.setupDeviceEvents(client, deviceId);
50
+
51
+ this.devices.set(deviceId, {
52
+ client: client,
53
+ id: deviceId,
54
+ status: 'disconnected',
55
+ lastUsed: null,
56
+ messageCount: 0,
57
+ errors: 0,
58
+ options: deviceOptions
59
+ });
60
+
61
+ console.log(`✅ Device '${deviceId}' hinzugefügt`);
62
+ return client;
63
+ }
64
+
65
+ async removeDevice(deviceId) {
66
+ if (!this.devices.has(deviceId)) {
67
+ throw new Error(`❌ Device '${deviceId}' nicht gefunden!`);
68
+ }
69
+
70
+ const device = this.devices.get(deviceId);
71
+
72
+ // Device disconnecten falls verbunden
73
+ if (device.status === 'connected') {
74
+ await device.client.disconnect();
75
+ }
76
+
77
+ this.devices.delete(deviceId);
78
+ this.activeDevices.delete(deviceId);
79
+
80
+ console.log(`🗑️ Device '${deviceId}' entfernt`);
81
+ }
82
+
83
+ // ===== CONNECTION MANAGEMENT =====
84
+
85
+ async connectDevice(deviceId) {
86
+ if (!this.devices.has(deviceId)) {
87
+ throw new Error(`❌ Device '${deviceId}' nicht gefunden!`);
88
+ }
89
+
90
+ const device = this.devices.get(deviceId);
91
+
92
+ if (device.status === 'connected') {
93
+ console.log(`✅ Device '${deviceId}' bereits verbunden`);
94
+ return device.client;
95
+ }
96
+
97
+ try {
98
+ console.log(`🔄 Verbinde Device '${deviceId}'...`);
99
+ device.status = 'connecting';
100
+
101
+ await device.client.connect();
102
+
103
+ device.status = 'connected';
104
+ this.activeDevices.add(deviceId);
105
+
106
+ console.log(`✅ Device '${deviceId}' erfolgreich verbunden!`);
107
+ this.emit('device.connected', { deviceId, device });
108
+
109
+ return device.client;
110
+ } catch (error) {
111
+ device.status = 'error';
112
+ device.errors++;
113
+ console.error(`❌ Fehler beim Verbinden von Device '${deviceId}':`, error.message);
114
+ this.emit('device.error', { deviceId, error });
115
+ throw error;
116
+ }
117
+ }
118
+
119
+ async connectAll() {
120
+ console.log(`🚀 Verbinde alle ${this.devices.size} Devices...`);
121
+
122
+ const promises = Array.from(this.devices.keys()).map(deviceId =>
123
+ this.connectDevice(deviceId).catch(error => {
124
+ console.error(`❌ Device '${deviceId}' Verbindung fehlgeschlagen:`, error.message);
125
+ return null;
126
+ })
127
+ );
128
+
129
+ const results = await Promise.allSettled(promises);
130
+ const connected = results.filter(r => r.status === 'fulfilled' && r.value).length;
131
+
132
+ console.log(`✅ ${connected}/${this.devices.size} Devices erfolgreich verbunden`);
133
+
134
+ if (connected === 0) {
135
+ throw new Error("❌ Keine Devices konnten verbunden werden!");
136
+ }
137
+
138
+ return connected;
139
+ }
140
+
141
+ async disconnectDevice(deviceId) {
142
+ if (!this.devices.has(deviceId)) return;
143
+
144
+ const device = this.devices.get(deviceId);
145
+
146
+ if (device.status === 'connected') {
147
+ await device.client.disconnect();
148
+ }
149
+
150
+ device.status = 'disconnected';
151
+ this.activeDevices.delete(deviceId);
152
+
153
+ console.log(`🔴 Device '${deviceId}' getrennt`);
154
+ this.emit('device.disconnected', { deviceId });
155
+ }
156
+
157
+ async disconnectAll() {
158
+ console.log("🔴 Trenne alle Devices...");
159
+
160
+ const promises = Array.from(this.activeDevices).map(deviceId =>
161
+ this.disconnectDevice(deviceId)
162
+ );
163
+
164
+ await Promise.all(promises);
165
+ console.log("✅ Alle Devices getrennt");
166
+ }
167
+
168
+ // ===== LOAD BALANCING =====
169
+
170
+ getNextDevice() {
171
+ const activeDeviceIds = Array.from(this.activeDevices);
172
+
173
+ if (activeDeviceIds.length === 0) {
174
+ throw new Error("❌ Keine aktiven Devices verfügbar!");
175
+ }
176
+
177
+ let selectedDeviceId;
178
+
179
+ switch (this.config.loadBalancing) {
180
+ case 'round-robin':
181
+ selectedDeviceId = activeDeviceIds[this.currentDeviceIndex % activeDeviceIds.length];
182
+ this.currentDeviceIndex++;
183
+ break;
184
+
185
+ case 'random':
186
+ selectedDeviceId = activeDeviceIds[Math.floor(Math.random() * activeDeviceIds.length)];
187
+ break;
188
+
189
+ case 'least-used':
190
+ selectedDeviceId = activeDeviceIds.reduce((least, current) => {
191
+ const leastDevice = this.devices.get(least);
192
+ const currentDevice = this.devices.get(current);
193
+ return currentDevice.messageCount < leastDevice.messageCount ? current : least;
194
+ });
195
+ break;
196
+
197
+ default:
198
+ selectedDeviceId = activeDeviceIds[0];
199
+ }
200
+
201
+ const device = this.devices.get(selectedDeviceId);
202
+ device.lastUsed = Date.now();
203
+ device.messageCount++;
204
+
205
+ return device.client;
206
+ }
207
+
208
+ getDevice(deviceId) {
209
+ if (!this.devices.has(deviceId)) {
210
+ throw new Error(`❌ Device '${deviceId}' nicht gefunden!`);
211
+ }
212
+
213
+ const device = this.devices.get(deviceId);
214
+
215
+ if (device.status !== 'connected') {
216
+ throw new Error(`❌ Device '${deviceId}' ist nicht verbunden!`);
217
+ }
218
+
219
+ return device.client;
220
+ }
221
+
222
+ // ===== BROADCAST SYSTEM =====
223
+
224
+ get broadcast() {
225
+ return {
226
+ // Message an alle aktiven Devices senden
227
+ sendMessage: async (chatId, content, options = {}) => {
228
+ const results = [];
229
+
230
+ for (const deviceId of this.activeDevices) {
231
+ try {
232
+ const client = this.getDevice(deviceId);
233
+ const result = await client.socket.sendMessage(chatId, content, options);
234
+ results.push({ deviceId, success: true, result });
235
+ } catch (error) {
236
+ results.push({ deviceId, success: false, error: error.message });
237
+ console.error(`❌ Broadcast Fehler Device '${deviceId}':`, error.message);
238
+ }
239
+ }
240
+
241
+ return results;
242
+ },
243
+
244
+ // Command an alle Devices weiterleiten
245
+ executeCommand: async (command, ...args) => {
246
+ const results = [];
247
+
248
+ for (const deviceId of this.activeDevices) {
249
+ try {
250
+ const client = this.getDevice(deviceId);
251
+ if (typeof client[command] === 'function') {
252
+ const result = await client[command](...args);
253
+ results.push({ deviceId, success: true, result });
254
+ }
255
+ } catch (error) {
256
+ results.push({ deviceId, success: false, error: error.message });
257
+ }
258
+ }
259
+
260
+ return results;
261
+ }
262
+ };
263
+ }
264
+
265
+ // ===== SMART ROUTING =====
266
+
267
+ async smartSend(chatId, content, options = {}) {
268
+ const strategy = options.strategy || 'load-balance';
269
+
270
+ switch (strategy) {
271
+ case 'load-balance':
272
+ const client = this.getNextDevice();
273
+ return await client.socket.sendMessage(chatId, content, options);
274
+
275
+ case 'broadcast':
276
+ return await this.broadcast.sendMessage(chatId, content, options);
277
+
278
+ case 'failover':
279
+ return await this.sendWithFailover(chatId, content, options);
280
+
281
+ default:
282
+ throw new Error(`❌ Unbekannte Strategy: ${strategy}`);
283
+ }
284
+ }
285
+
286
+ async sendWithFailover(chatId, content, options = {}) {
287
+ const deviceIds = Array.from(this.activeDevices);
288
+
289
+ for (const deviceId of deviceIds) {
290
+ try {
291
+ const client = this.getDevice(deviceId);
292
+ return await client.socket.sendMessage(chatId, content, options);
293
+ } catch (error) {
294
+ console.warn(`⚠️ Failover: Device '${deviceId}' fehlgeschlagen, versuche nächstes...`);
295
+
296
+ // Device als fehlerhaft markieren
297
+ const device = this.devices.get(deviceId);
298
+ device.errors++;
299
+
300
+ if (device.errors >= 3) {
301
+ console.warn(`🚨 Device '${deviceId}' hat zu viele Fehler, entferne aus aktiven Devices`);
302
+ this.activeDevices.delete(deviceId);
303
+ device.status = 'error';
304
+ }
305
+ }
306
+ }
307
+
308
+ throw new Error("❌ Alle Devices fehlgeschlagen!");
309
+ }
310
+
311
+ // ===== EVENT SYSTEM =====
312
+
313
+ setupDeviceEvents(client, deviceId) {
314
+ // Message Events weiterleiten
315
+ client.on('message', (msg) => {
316
+ if (this.config.syncEvents) {
317
+ this.emit('message', { ...msg, deviceId });
318
+ }
319
+ });
320
+
321
+ // Connection Events
322
+ client.on('connected', () => {
323
+ this.emit('device.connected', { deviceId });
324
+ });
325
+
326
+ client.on('disconnected', (data) => {
327
+ this.activeDevices.delete(deviceId);
328
+ const device = this.devices.get(deviceId);
329
+ if (device) device.status = 'disconnected';
330
+
331
+ this.emit('device.disconnected', { deviceId, ...data });
332
+ });
333
+ }
334
+
335
+ on(event, handler) {
336
+ if (!this.eventHandlers.has(event)) {
337
+ this.eventHandlers.set(event, []);
338
+ }
339
+ this.eventHandlers.get(event).push(handler);
340
+ return this;
341
+ }
342
+
343
+ emit(event, data) {
344
+ if (this.eventHandlers.has(event)) {
345
+ this.eventHandlers.get(event).forEach(handler => {
346
+ try {
347
+ handler(data);
348
+ } catch (error) {
349
+ console.error(`❌ Event Handler Fehler '${event}':`, error);
350
+ }
351
+ });
352
+ }
353
+ }
354
+
355
+ // ===== STATUS & STATISTICS =====
356
+
357
+ getStatus() {
358
+ const devices = Array.from(this.devices.entries()).map(([id, device]) => ({
359
+ id,
360
+ status: device.status,
361
+ messageCount: device.messageCount,
362
+ errors: device.errors,
363
+ lastUsed: device.lastUsed
364
+ }));
365
+
366
+ return {
367
+ totalDevices: this.devices.size,
368
+ activeDevices: this.activeDevices.size,
369
+ loadBalancing: this.config.loadBalancing,
370
+ devices
371
+ };
372
+ }
373
+
374
+ getHealthCheck() {
375
+ const status = this.getStatus();
376
+ const healthyDevices = status.devices.filter(d => d.status === 'connected').length;
377
+
378
+ return {
379
+ healthy: healthyDevices > 0,
380
+ healthyDevices,
381
+ totalDevices: status.totalDevices,
382
+ healthPercentage: Math.round((healthyDevices / status.totalDevices) * 100),
383
+ recommendation: healthyDevices === 0 ? 'Alle Devices reconnecten' :
384
+ healthyDevices < status.totalDevices ? 'Einige Devices prüfen' : 'Alles OK'
385
+ };
386
+ }
387
+
388
+ // ===== UTILITY =====
389
+
390
+ ensureAuthDir(authDir) {
391
+ if (!fs.existsSync(authDir)) {
392
+ fs.mkdirSync(authDir, { recursive: true });
393
+ console.log(`📁 Auth-Directory erstellt: ${authDir}`);
394
+ }
395
+ }
396
+
397
+ async cleanup() {
398
+ console.log("🧹 DeviceManager Cleanup...");
399
+ await this.disconnectAll();
400
+ this.devices.clear();
401
+ this.activeDevices.clear();
402
+ this.eventHandlers.clear();
403
+ }
404
+ }