signal-sdk 0.0.9 → 0.1.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 +184 -61
- package/dist/MultiAccountManager.d.ts +149 -0
- package/dist/MultiAccountManager.js +320 -0
- package/dist/SignalBot.d.ts +1 -0
- package/dist/SignalBot.js +20 -2
- package/dist/SignalCli.d.ts +315 -16
- package/dist/SignalCli.js +880 -26
- package/dist/__tests__/MultiAccountManager.test.d.ts +4 -0
- package/dist/__tests__/MultiAccountManager.test.js +209 -0
- package/dist/__tests__/SignalBot.additional.test.d.ts +5 -0
- package/dist/__tests__/SignalBot.additional.test.js +353 -0
- package/dist/__tests__/SignalBot.test.js +5 -0
- package/dist/__tests__/SignalCli.advanced.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.advanced.test.js +295 -0
- package/dist/__tests__/SignalCli.e2e.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.e2e.test.js +240 -0
- package/dist/__tests__/SignalCli.integration.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.integration.test.js +225 -0
- package/dist/__tests__/SignalCli.methods.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.methods.test.js +556 -0
- package/dist/__tests__/SignalCli.parsing.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.parsing.test.js +258 -0
- package/dist/__tests__/SignalCli.test.js +249 -13
- package/dist/__tests__/config.test.d.ts +5 -0
- package/dist/__tests__/config.test.js +252 -0
- package/dist/__tests__/errors.test.d.ts +5 -0
- package/dist/__tests__/errors.test.js +276 -0
- package/dist/__tests__/retry.test.d.ts +4 -0
- package/dist/__tests__/retry.test.js +123 -0
- package/dist/__tests__/validators.test.d.ts +4 -0
- package/dist/__tests__/validators.test.js +147 -0
- package/dist/config.d.ts +82 -0
- package/dist/config.js +116 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.js +75 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +7 -1
- package/dist/interfaces.d.ts +200 -10
- package/dist/interfaces.js +1 -1
- package/dist/retry.d.ts +56 -0
- package/dist/retry.js +152 -0
- package/dist/validators.d.ts +59 -0
- package/dist/validators.js +170 -0
- package/package.json +1 -1
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Multi-Account Manager for Signal SDK
|
|
4
|
+
*
|
|
5
|
+
* Manages multiple Signal accounts simultaneously with event routing
|
|
6
|
+
* and isolated process management.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.MultiAccountManager = void 0;
|
|
10
|
+
const SignalCli_1 = require("./SignalCli");
|
|
11
|
+
const events_1 = require("events");
|
|
12
|
+
const config_1 = require("./config");
|
|
13
|
+
/**
|
|
14
|
+
* Multi-Account Manager
|
|
15
|
+
*
|
|
16
|
+
* Manages multiple Signal accounts with event routing and lifecycle management.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const manager = new MultiAccountManager({
|
|
21
|
+
* dataPath: '/path/to/data',
|
|
22
|
+
* autoReconnect: true
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Add accounts
|
|
26
|
+
* await manager.addAccount('+33123456789');
|
|
27
|
+
* await manager.addAccount('+33987654321');
|
|
28
|
+
*
|
|
29
|
+
* // Listen to events from all accounts
|
|
30
|
+
* manager.on('message', (account, message) => {
|
|
31
|
+
* console.log(`Message from ${account}: ${message.text}`);
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // Connect all accounts
|
|
35
|
+
* await manager.connectAll();
|
|
36
|
+
*
|
|
37
|
+
* // Send from specific account
|
|
38
|
+
* await manager.sendMessage('+33123456789', '+33111111111', 'Hello!');
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
class MultiAccountManager extends events_1.EventEmitter {
|
|
42
|
+
constructor(options = {}) {
|
|
43
|
+
super();
|
|
44
|
+
this.accounts = new Map();
|
|
45
|
+
this.options = options;
|
|
46
|
+
this.logger = new config_1.Logger({
|
|
47
|
+
level: options.verbose ? 'debug' : 'info',
|
|
48
|
+
enableFile: false
|
|
49
|
+
});
|
|
50
|
+
this.logger.info('MultiAccountManager initialized');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Add an account to the manager
|
|
54
|
+
*
|
|
55
|
+
* @param account - Phone number of the account
|
|
56
|
+
* @param config - Optional SignalCli configuration
|
|
57
|
+
* @returns The SignalCli instance
|
|
58
|
+
*/
|
|
59
|
+
async addAccount(account, config = {}) {
|
|
60
|
+
if (this.accounts.has(account)) {
|
|
61
|
+
throw new Error(`Account ${account} already exists`);
|
|
62
|
+
}
|
|
63
|
+
this.logger.info(`Adding account: ${account}`);
|
|
64
|
+
// Create SignalCli instance with merged config
|
|
65
|
+
const signalConfig = {
|
|
66
|
+
signalCliPath: this.options.signalCliPath,
|
|
67
|
+
verbose: this.options.verbose,
|
|
68
|
+
...config
|
|
69
|
+
};
|
|
70
|
+
const instance = new SignalCli_1.SignalCli(account, undefined, signalConfig);
|
|
71
|
+
// Forward events from this instance
|
|
72
|
+
this.setupEventForwarding(account, instance);
|
|
73
|
+
// Store the managed account
|
|
74
|
+
const managedAccount = {
|
|
75
|
+
account,
|
|
76
|
+
instance,
|
|
77
|
+
connected: false,
|
|
78
|
+
lastActivity: Date.now()
|
|
79
|
+
};
|
|
80
|
+
this.accounts.set(account, managedAccount);
|
|
81
|
+
this.emit('accountAdded', account);
|
|
82
|
+
this.logger.info(`Account ${account} added successfully`);
|
|
83
|
+
return instance;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Remove an account from the manager
|
|
87
|
+
*
|
|
88
|
+
* @param account - Phone number of the account
|
|
89
|
+
*/
|
|
90
|
+
async removeAccount(account) {
|
|
91
|
+
const managedAccount = this.accounts.get(account);
|
|
92
|
+
if (!managedAccount) {
|
|
93
|
+
throw new Error(`Account ${account} not found`);
|
|
94
|
+
}
|
|
95
|
+
this.logger.info(`Removing account: ${account}`);
|
|
96
|
+
// Disconnect if connected
|
|
97
|
+
if (managedAccount.connected) {
|
|
98
|
+
await managedAccount.instance.disconnect();
|
|
99
|
+
}
|
|
100
|
+
// Remove all listeners
|
|
101
|
+
managedAccount.instance.removeAllListeners();
|
|
102
|
+
// Remove from map
|
|
103
|
+
this.accounts.delete(account);
|
|
104
|
+
this.emit('accountRemoved', account);
|
|
105
|
+
this.logger.info(`Account ${account} removed successfully`);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get a specific account instance
|
|
109
|
+
*
|
|
110
|
+
* @param account - Phone number of the account
|
|
111
|
+
* @returns The SignalCli instance
|
|
112
|
+
*/
|
|
113
|
+
getAccount(account) {
|
|
114
|
+
return this.accounts.get(account)?.instance;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get all managed accounts
|
|
118
|
+
*
|
|
119
|
+
* @returns Array of account phone numbers
|
|
120
|
+
*/
|
|
121
|
+
getAccounts() {
|
|
122
|
+
return Array.from(this.accounts.keys());
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Check if an account exists
|
|
126
|
+
*
|
|
127
|
+
* @param account - Phone number of the account
|
|
128
|
+
* @returns True if the account exists
|
|
129
|
+
*/
|
|
130
|
+
hasAccount(account) {
|
|
131
|
+
return this.accounts.has(account);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Connect a specific account
|
|
135
|
+
*
|
|
136
|
+
* @param account - Phone number of the account
|
|
137
|
+
*/
|
|
138
|
+
async connect(account) {
|
|
139
|
+
const managedAccount = this.accounts.get(account);
|
|
140
|
+
if (!managedAccount) {
|
|
141
|
+
throw new Error(`Account ${account} not found`);
|
|
142
|
+
}
|
|
143
|
+
if (managedAccount.connected) {
|
|
144
|
+
this.logger.warn(`Account ${account} already connected`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.logger.info(`Connecting account: ${account}`);
|
|
148
|
+
try {
|
|
149
|
+
await managedAccount.instance.connect();
|
|
150
|
+
managedAccount.connected = true;
|
|
151
|
+
managedAccount.lastActivity = Date.now();
|
|
152
|
+
this.emit('accountConnected', account);
|
|
153
|
+
this.logger.info(`Account ${account} connected successfully`);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
this.logger.error(`Failed to connect account ${account}:`, error);
|
|
157
|
+
if (this.options.autoReconnect) {
|
|
158
|
+
this.logger.info(`Will retry connection for ${account}`);
|
|
159
|
+
setTimeout(() => this.connect(account), 5000);
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Disconnect a specific account
|
|
166
|
+
*
|
|
167
|
+
* @param account - Phone number of the account
|
|
168
|
+
*/
|
|
169
|
+
async disconnect(account) {
|
|
170
|
+
const managedAccount = this.accounts.get(account);
|
|
171
|
+
if (!managedAccount) {
|
|
172
|
+
throw new Error(`Account ${account} not found`);
|
|
173
|
+
}
|
|
174
|
+
if (!managedAccount.connected) {
|
|
175
|
+
this.logger.warn(`Account ${account} not connected`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
this.logger.info(`Disconnecting account: ${account}`);
|
|
179
|
+
await managedAccount.instance.disconnect();
|
|
180
|
+
managedAccount.connected = false;
|
|
181
|
+
this.emit('accountDisconnected', account);
|
|
182
|
+
this.logger.info(`Account ${account} disconnected`);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Connect all accounts
|
|
186
|
+
*/
|
|
187
|
+
async connectAll() {
|
|
188
|
+
this.logger.info('Connecting all accounts');
|
|
189
|
+
const promises = Array.from(this.accounts.keys()).map(account => this.connect(account).catch(error => {
|
|
190
|
+
this.logger.error(`Failed to connect ${account}:`, error);
|
|
191
|
+
}));
|
|
192
|
+
await Promise.all(promises);
|
|
193
|
+
this.logger.info('All accounts connected');
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Disconnect all accounts
|
|
197
|
+
*/
|
|
198
|
+
async disconnectAll() {
|
|
199
|
+
this.logger.info('Disconnecting all accounts');
|
|
200
|
+
const promises = Array.from(this.accounts.keys()).map(account => this.disconnect(account).catch(error => {
|
|
201
|
+
this.logger.error(`Failed to disconnect ${account}:`, error);
|
|
202
|
+
}));
|
|
203
|
+
await Promise.all(promises);
|
|
204
|
+
this.logger.info('All accounts disconnected');
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Send a message from a specific account
|
|
208
|
+
*
|
|
209
|
+
* @param fromAccount - Account to send from
|
|
210
|
+
* @param recipient - Recipient phone number or group ID
|
|
211
|
+
* @param message - Message text
|
|
212
|
+
* @param options - Send options
|
|
213
|
+
*/
|
|
214
|
+
async sendMessage(fromAccount, recipient, message, options = {}) {
|
|
215
|
+
const managedAccount = this.accounts.get(fromAccount);
|
|
216
|
+
if (!managedAccount) {
|
|
217
|
+
throw new Error(`Account ${fromAccount} not found`);
|
|
218
|
+
}
|
|
219
|
+
managedAccount.lastActivity = Date.now();
|
|
220
|
+
return managedAccount.instance.sendMessage(recipient, message, options);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get account status information
|
|
224
|
+
*
|
|
225
|
+
* @param account - Phone number of the account (optional)
|
|
226
|
+
* @returns Status information for all or specific account
|
|
227
|
+
*/
|
|
228
|
+
getStatus(account) {
|
|
229
|
+
if (account) {
|
|
230
|
+
const managedAccount = this.accounts.get(account);
|
|
231
|
+
if (!managedAccount) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
account: managedAccount.account,
|
|
236
|
+
connected: managedAccount.connected,
|
|
237
|
+
lastActivity: managedAccount.lastActivity,
|
|
238
|
+
uptime: Date.now() - managedAccount.lastActivity
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// Return status for all accounts
|
|
242
|
+
const status = {
|
|
243
|
+
totalAccounts: this.accounts.size,
|
|
244
|
+
connectedAccounts: 0,
|
|
245
|
+
accounts: []
|
|
246
|
+
};
|
|
247
|
+
for (const [account, managed] of this.accounts) {
|
|
248
|
+
if (managed.connected) {
|
|
249
|
+
status.connectedAccounts++;
|
|
250
|
+
}
|
|
251
|
+
status.accounts.push({
|
|
252
|
+
account,
|
|
253
|
+
connected: managed.connected,
|
|
254
|
+
lastActivity: managed.lastActivity,
|
|
255
|
+
uptime: Date.now() - managed.lastActivity
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return status;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Setup event forwarding from an account instance
|
|
262
|
+
*
|
|
263
|
+
* @private
|
|
264
|
+
*/
|
|
265
|
+
setupEventForwarding(account, instance) {
|
|
266
|
+
// Forward all events with account prefix
|
|
267
|
+
const events = [
|
|
268
|
+
'message',
|
|
269
|
+
'receipt',
|
|
270
|
+
'typing',
|
|
271
|
+
'reaction',
|
|
272
|
+
'error',
|
|
273
|
+
'connected',
|
|
274
|
+
'disconnected'
|
|
275
|
+
];
|
|
276
|
+
events.forEach(event => {
|
|
277
|
+
instance.on(event, (...args) => {
|
|
278
|
+
// Emit with account information
|
|
279
|
+
this.emit(event, account, ...args);
|
|
280
|
+
// Also emit a generic event with account
|
|
281
|
+
this.emit('accountEvent', {
|
|
282
|
+
account,
|
|
283
|
+
event,
|
|
284
|
+
data: args
|
|
285
|
+
});
|
|
286
|
+
// Update last activity
|
|
287
|
+
const managedAccount = this.accounts.get(account);
|
|
288
|
+
if (managedAccount) {
|
|
289
|
+
managedAccount.lastActivity = Date.now();
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
// Handle disconnection
|
|
294
|
+
instance.on('disconnected', () => {
|
|
295
|
+
const managedAccount = this.accounts.get(account);
|
|
296
|
+
if (managedAccount) {
|
|
297
|
+
managedAccount.connected = false;
|
|
298
|
+
}
|
|
299
|
+
// Auto-reconnect if enabled
|
|
300
|
+
if (this.options.autoReconnect) {
|
|
301
|
+
this.logger.info(`Auto-reconnecting account ${account}`);
|
|
302
|
+
setTimeout(() => this.connect(account), 5000);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Shutdown the manager and cleanup all accounts
|
|
308
|
+
*/
|
|
309
|
+
async shutdown() {
|
|
310
|
+
this.logger.info('Shutting down MultiAccountManager');
|
|
311
|
+
await this.disconnectAll();
|
|
312
|
+
// Remove all accounts
|
|
313
|
+
for (const account of this.accounts.keys()) {
|
|
314
|
+
await this.removeAccount(account);
|
|
315
|
+
}
|
|
316
|
+
this.removeAllListeners();
|
|
317
|
+
this.logger.info('MultiAccountManager shutdown complete');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
exports.MultiAccountManager = MultiAccountManager;
|
package/dist/SignalBot.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare class SignalBot extends EventEmitter {
|
|
|
12
12
|
private actionQueue;
|
|
13
13
|
private isProcessingQueue;
|
|
14
14
|
private incomingMessageBuffer;
|
|
15
|
+
private activeTimers;
|
|
15
16
|
constructor(config: BotConfig, signalCliPath?: string);
|
|
16
17
|
/**
|
|
17
18
|
* Downloads an image from URL to a temporary file
|
package/dist/SignalBot.js
CHANGED
|
@@ -50,6 +50,7 @@ class SignalBot extends events_1.EventEmitter {
|
|
|
50
50
|
this.actionQueue = [];
|
|
51
51
|
this.isProcessingQueue = false;
|
|
52
52
|
this.incomingMessageBuffer = [];
|
|
53
|
+
this.activeTimers = [];
|
|
53
54
|
this.config = {
|
|
54
55
|
phoneNumber: config.phoneNumber,
|
|
55
56
|
admins: config.admins || [],
|
|
@@ -317,6 +318,9 @@ class SignalBot extends events_1.EventEmitter {
|
|
|
317
318
|
async stop() {
|
|
318
319
|
this.log('- Stopping Signal Bot...');
|
|
319
320
|
this.isRunning = false;
|
|
321
|
+
// Clear all active timers
|
|
322
|
+
this.activeTimers.forEach(timer => clearTimeout(timer));
|
|
323
|
+
this.activeTimers = [];
|
|
320
324
|
this.signalCli.disconnect();
|
|
321
325
|
this.emit('stopped');
|
|
322
326
|
this.log('- Bot stopped');
|
|
@@ -324,6 +328,9 @@ class SignalBot extends events_1.EventEmitter {
|
|
|
324
328
|
async gracefulShutdown() {
|
|
325
329
|
this.log('- Gracefully shutting down Signal Bot...');
|
|
326
330
|
this.isRunning = false;
|
|
331
|
+
// Clear all active timers
|
|
332
|
+
this.activeTimers.forEach(timer => clearTimeout(timer));
|
|
333
|
+
this.activeTimers = [];
|
|
327
334
|
try {
|
|
328
335
|
await this.signalCli.gracefulShutdown();
|
|
329
336
|
this.log('- Signal Bot shutdown completed gracefully');
|
|
@@ -725,11 +732,18 @@ class SignalBot extends events_1.EventEmitter {
|
|
|
725
732
|
// Wait a bit for signal-cli to finish processing the files before cleanup
|
|
726
733
|
// signal-cli responds immediately but continues processing files in background
|
|
727
734
|
if (action.cleanup && action.cleanup.length > 0) {
|
|
728
|
-
setTimeout(() => {
|
|
735
|
+
const cleanupTimer = setTimeout(() => {
|
|
729
736
|
action.cleanup.forEach(filePath => {
|
|
730
737
|
this.cleanupTempFile(filePath);
|
|
731
738
|
});
|
|
739
|
+
// Remove timer from active list
|
|
740
|
+
const index = this.activeTimers.indexOf(cleanupTimer);
|
|
741
|
+
if (index > -1)
|
|
742
|
+
this.activeTimers.splice(index, 1);
|
|
732
743
|
}, 2000); // Wait 2 seconds for signal-cli to upload files
|
|
744
|
+
if (cleanupTimer.unref)
|
|
745
|
+
cleanupTimer.unref();
|
|
746
|
+
this.activeTimers.push(cleanupTimer);
|
|
733
747
|
}
|
|
734
748
|
break;
|
|
735
749
|
case 'sendReaction':
|
|
@@ -738,7 +752,11 @@ class SignalBot extends events_1.EventEmitter {
|
|
|
738
752
|
break;
|
|
739
753
|
}
|
|
740
754
|
// Wait a bit between actions to be safe
|
|
741
|
-
await new Promise(resolve =>
|
|
755
|
+
await new Promise(resolve => {
|
|
756
|
+
const timer = setTimeout(resolve, 250);
|
|
757
|
+
if (timer.unref)
|
|
758
|
+
timer.unref();
|
|
759
|
+
});
|
|
742
760
|
}
|
|
743
761
|
catch (error) {
|
|
744
762
|
this.log(`ERROR: Failed to execute action ${action.type}: ${error?.message || error}`, 'ERROR');
|