signal-sdk 0.1.1 → 0.1.3
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 +23 -11
- package/dist/MultiAccountManager.js +11 -19
- package/dist/SignalBot.js +40 -36
- package/dist/SignalCli.d.ts +25 -301
- package/dist/SignalCli.js +226 -971
- package/dist/__tests__/DeviceManager.test.d.ts +1 -0
- package/dist/__tests__/DeviceManager.test.js +135 -0
- package/dist/__tests__/MultiAccountManager.coverage.test.d.ts +1 -0
- package/dist/__tests__/MultiAccountManager.coverage.test.js +33 -0
- package/dist/__tests__/MultiAccountManager.test.js +3 -3
- package/dist/__tests__/SignalBot.additional.test.js +40 -37
- package/dist/__tests__/SignalBot.coverage.test.d.ts +1 -0
- package/dist/__tests__/SignalBot.coverage.test.js +385 -0
- package/dist/__tests__/SignalBot.test.js +8 -8
- package/dist/__tests__/SignalCli.advanced.test.js +47 -58
- package/dist/__tests__/SignalCli.connections.test.d.ts +1 -0
- package/dist/__tests__/SignalCli.connections.test.js +110 -0
- package/dist/__tests__/SignalCli.e2e.test.js +28 -32
- package/dist/__tests__/SignalCli.events.test.d.ts +1 -0
- package/dist/__tests__/SignalCli.events.test.js +113 -0
- package/dist/__tests__/SignalCli.integration.test.js +6 -5
- package/dist/__tests__/SignalCli.methods.test.js +150 -66
- package/dist/__tests__/SignalCli.parsing.test.js +4 -13
- package/dist/__tests__/SignalCli.simple.test.d.ts +1 -0
- package/dist/__tests__/SignalCli.simple.test.js +77 -0
- package/dist/__tests__/SignalCli.test.js +133 -74
- package/dist/__tests__/config.test.js +19 -29
- package/dist/__tests__/errors.test.js +2 -2
- package/dist/__tests__/retry.test.js +10 -8
- package/dist/__tests__/robustness.test.d.ts +1 -0
- package/dist/__tests__/robustness.test.js +59 -0
- package/dist/__tests__/security.test.d.ts +1 -0
- package/dist/__tests__/security.test.js +50 -0
- package/dist/config.js +3 -3
- package/dist/interfaces.d.ts +27 -0
- package/dist/managers/AccountManager.d.ts +27 -0
- package/dist/managers/AccountManager.js +147 -0
- package/dist/managers/BaseManager.d.ts +9 -0
- package/dist/managers/BaseManager.js +17 -0
- package/dist/managers/ContactManager.d.ts +15 -0
- package/dist/managers/ContactManager.js +123 -0
- package/dist/managers/DeviceManager.d.ts +11 -0
- package/dist/managers/DeviceManager.js +139 -0
- package/dist/managers/GroupManager.d.ts +12 -0
- package/dist/managers/GroupManager.js +78 -0
- package/dist/managers/MessageManager.d.ts +18 -0
- package/dist/managers/MessageManager.js +301 -0
- package/dist/managers/StickerManager.d.ts +8 -0
- package/dist/managers/StickerManager.js +39 -0
- package/dist/retry.js +3 -3
- package/dist/validators.d.ts +9 -0
- package/dist/validators.js +20 -0
- package/package.json +11 -4
- package/scripts/install.js +1 -1
|
@@ -14,7 +14,7 @@ describe('Config Additional Tests', () => {
|
|
|
14
14
|
test('should merge user config with defaults', () => {
|
|
15
15
|
const userConfig = {
|
|
16
16
|
verbose: true,
|
|
17
|
-
maxRetries: 5
|
|
17
|
+
maxRetries: 5,
|
|
18
18
|
};
|
|
19
19
|
const config = (0, config_1.validateConfig)(userConfig);
|
|
20
20
|
expect(config.verbose).toBe(true);
|
|
@@ -22,38 +22,28 @@ describe('Config Additional Tests', () => {
|
|
|
22
22
|
expect(config.connectionTimeout).toBe(config_1.DEFAULT_CONFIG.connectionTimeout);
|
|
23
23
|
});
|
|
24
24
|
test('should throw error for negative connectionTimeout', () => {
|
|
25
|
-
expect(() => (0, config_1.validateConfig)({ connectionTimeout: -1 }))
|
|
26
|
-
.toThrow('connectionTimeout must be non-negative');
|
|
25
|
+
expect(() => (0, config_1.validateConfig)({ connectionTimeout: -1 })).toThrow('connectionTimeout must be non-negative');
|
|
27
26
|
});
|
|
28
27
|
test('should throw error for negative requestTimeout', () => {
|
|
29
|
-
expect(() => (0, config_1.validateConfig)({ requestTimeout: -100 }))
|
|
30
|
-
.toThrow('requestTimeout must be non-negative');
|
|
28
|
+
expect(() => (0, config_1.validateConfig)({ requestTimeout: -100 })).toThrow('requestTimeout must be non-negative');
|
|
31
29
|
});
|
|
32
30
|
test('should throw error for negative maxRetries', () => {
|
|
33
|
-
expect(() => (0, config_1.validateConfig)({ maxRetries: -5 }))
|
|
34
|
-
.toThrow('maxRetries must be non-negative');
|
|
31
|
+
expect(() => (0, config_1.validateConfig)({ maxRetries: -5 })).toThrow('maxRetries must be non-negative');
|
|
35
32
|
});
|
|
36
33
|
test('should throw error for negative retryDelay', () => {
|
|
37
|
-
expect(() => (0, config_1.validateConfig)({ retryDelay: -1000 }))
|
|
38
|
-
.toThrow('retryDelay must be non-negative');
|
|
34
|
+
expect(() => (0, config_1.validateConfig)({ retryDelay: -1000 })).toThrow('retryDelay must be non-negative');
|
|
39
35
|
});
|
|
40
36
|
test('should throw error for maxConcurrentRequests less than 1', () => {
|
|
41
|
-
expect(() => (0, config_1.validateConfig)({ maxConcurrentRequests: 0 }))
|
|
42
|
-
|
|
43
|
-
expect(() => (0, config_1.validateConfig)({ maxConcurrentRequests: -1 }))
|
|
44
|
-
.toThrow('maxConcurrentRequests must be at least 1');
|
|
37
|
+
expect(() => (0, config_1.validateConfig)({ maxConcurrentRequests: 0 })).toThrow('maxConcurrentRequests must be at least 1');
|
|
38
|
+
expect(() => (0, config_1.validateConfig)({ maxConcurrentRequests: -1 })).toThrow('maxConcurrentRequests must be at least 1');
|
|
45
39
|
});
|
|
46
40
|
test('should throw error for negative minRequestInterval', () => {
|
|
47
|
-
expect(() => (0, config_1.validateConfig)({ minRequestInterval: -50 }))
|
|
48
|
-
.toThrow('minRequestInterval must be non-negative');
|
|
41
|
+
expect(() => (0, config_1.validateConfig)({ minRequestInterval: -50 })).toThrow('minRequestInterval must be non-negative');
|
|
49
42
|
});
|
|
50
43
|
test('should accept all valid trustNewIdentities values', () => {
|
|
51
|
-
expect(() => (0, config_1.validateConfig)({ trustNewIdentities: 'on-first-use' }))
|
|
52
|
-
|
|
53
|
-
expect(() => (0, config_1.validateConfig)({ trustNewIdentities: '
|
|
54
|
-
.not.toThrow();
|
|
55
|
-
expect(() => (0, config_1.validateConfig)({ trustNewIdentities: 'never' }))
|
|
56
|
-
.not.toThrow();
|
|
44
|
+
expect(() => (0, config_1.validateConfig)({ trustNewIdentities: 'on-first-use' })).not.toThrow();
|
|
45
|
+
expect(() => (0, config_1.validateConfig)({ trustNewIdentities: 'always' })).not.toThrow();
|
|
46
|
+
expect(() => (0, config_1.validateConfig)({ trustNewIdentities: 'never' })).not.toThrow();
|
|
57
47
|
});
|
|
58
48
|
test('should accept zero values for valid fields', () => {
|
|
59
49
|
const config = (0, config_1.validateConfig)({
|
|
@@ -61,7 +51,7 @@ describe('Config Additional Tests', () => {
|
|
|
61
51
|
requestTimeout: 0,
|
|
62
52
|
maxRetries: 0,
|
|
63
53
|
retryDelay: 0,
|
|
64
|
-
minRequestInterval: 0
|
|
54
|
+
minRequestInterval: 0,
|
|
65
55
|
});
|
|
66
56
|
expect(config.connectionTimeout).toBe(0);
|
|
67
57
|
expect(config.requestTimeout).toBe(0);
|
|
@@ -84,7 +74,7 @@ describe('Config Additional Tests', () => {
|
|
|
84
74
|
minRequestInterval: 200,
|
|
85
75
|
autoReconnect: false,
|
|
86
76
|
trustNewIdentities: 'never',
|
|
87
|
-
disableSendLog: true
|
|
77
|
+
disableSendLog: true,
|
|
88
78
|
};
|
|
89
79
|
const config = (0, config_1.validateConfig)(fullConfig);
|
|
90
80
|
expect(config).toMatchObject(fullConfig);
|
|
@@ -98,7 +88,7 @@ describe('Config Additional Tests', () => {
|
|
|
98
88
|
enableConsole: true,
|
|
99
89
|
enableFile: false,
|
|
100
90
|
includeTimestamp: true,
|
|
101
|
-
includeLevel: true
|
|
91
|
+
includeLevel: true,
|
|
102
92
|
});
|
|
103
93
|
});
|
|
104
94
|
test('should create logger with default config', () => {
|
|
@@ -144,7 +134,7 @@ describe('Config Additional Tests', () => {
|
|
|
144
134
|
enableConsole: false,
|
|
145
135
|
enableFile: false,
|
|
146
136
|
includeTimestamp: true,
|
|
147
|
-
includeLevel: true
|
|
137
|
+
includeLevel: true,
|
|
148
138
|
});
|
|
149
139
|
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
150
140
|
consoleLogger.info('Should not appear in console');
|
|
@@ -157,7 +147,7 @@ describe('Config Additional Tests', () => {
|
|
|
157
147
|
enableConsole: true,
|
|
158
148
|
enableFile: false,
|
|
159
149
|
includeTimestamp: false,
|
|
160
|
-
includeLevel: true
|
|
150
|
+
includeLevel: true,
|
|
161
151
|
});
|
|
162
152
|
const consoleDebugSpy = jest.spyOn(console, 'debug').mockImplementation();
|
|
163
153
|
const consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation();
|
|
@@ -182,7 +172,7 @@ describe('Config Additional Tests', () => {
|
|
|
182
172
|
enableConsole: true,
|
|
183
173
|
enableFile: false,
|
|
184
174
|
includeTimestamp: false,
|
|
185
|
-
includeLevel: true
|
|
175
|
+
includeLevel: true,
|
|
186
176
|
});
|
|
187
177
|
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
188
178
|
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
@@ -204,7 +194,7 @@ describe('Config Additional Tests', () => {
|
|
|
204
194
|
enableConsole: true,
|
|
205
195
|
enableFile: false,
|
|
206
196
|
includeTimestamp: false,
|
|
207
|
-
includeLevel: true
|
|
197
|
+
includeLevel: true,
|
|
208
198
|
});
|
|
209
199
|
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
|
210
200
|
noTimestampLogger.info('No timestamp');
|
|
@@ -219,7 +209,7 @@ describe('Config Additional Tests', () => {
|
|
|
219
209
|
enableConsole: true,
|
|
220
210
|
enableFile: false,
|
|
221
211
|
includeTimestamp: false,
|
|
222
|
-
includeLevel: false
|
|
212
|
+
includeLevel: false,
|
|
223
213
|
});
|
|
224
214
|
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
|
225
215
|
noLevelLogger.info('No level');
|
|
@@ -224,9 +224,9 @@ describe('Error Classes Additional Tests', () => {
|
|
|
224
224
|
new errors_1.ValidationError('test'),
|
|
225
225
|
new errors_1.TimeoutError('test'),
|
|
226
226
|
new errors_1.GroupError('test'),
|
|
227
|
-
new errors_1.MessageError('test')
|
|
227
|
+
new errors_1.MessageError('test'),
|
|
228
228
|
];
|
|
229
|
-
errors.forEach(error => {
|
|
229
|
+
errors.forEach((error) => {
|
|
230
230
|
expect(error.stack).toBeDefined();
|
|
231
231
|
expect(error.stack?.length).toBeGreaterThan(0);
|
|
232
232
|
});
|
|
@@ -17,12 +17,13 @@ describe('Retry utilities', () => {
|
|
|
17
17
|
expect(operation).toHaveBeenCalledTimes(1);
|
|
18
18
|
});
|
|
19
19
|
it('should retry on failure', async () => {
|
|
20
|
-
const operation = jest
|
|
20
|
+
const operation = jest
|
|
21
|
+
.fn()
|
|
21
22
|
.mockRejectedValueOnce(new Error('Connection failed'))
|
|
22
23
|
.mockResolvedValue('success');
|
|
23
24
|
const result = await (0, retry_1.withRetry)(operation, {
|
|
24
25
|
maxAttempts: 2,
|
|
25
|
-
initialDelay: 10
|
|
26
|
+
initialDelay: 10,
|
|
26
27
|
});
|
|
27
28
|
expect(result).toBe('success');
|
|
28
29
|
expect(operation).toHaveBeenCalledTimes(2);
|
|
@@ -32,7 +33,7 @@ describe('Retry utilities', () => {
|
|
|
32
33
|
await expect((0, retry_1.withRetry)(operation, {
|
|
33
34
|
maxAttempts: 3,
|
|
34
35
|
initialDelay: 10,
|
|
35
|
-
isRetryable: () => true // Force retry for test
|
|
36
|
+
isRetryable: () => true, // Force retry for test
|
|
36
37
|
})).rejects.toThrow('Connection timeout');
|
|
37
38
|
expect(operation).toHaveBeenCalledTimes(3);
|
|
38
39
|
});
|
|
@@ -40,12 +41,13 @@ describe('Retry utilities', () => {
|
|
|
40
41
|
const operation = jest.fn().mockRejectedValue({ code: 401, message: 'Unauthorized' });
|
|
41
42
|
await expect((0, retry_1.withRetry)(operation, {
|
|
42
43
|
maxAttempts: 3,
|
|
43
|
-
initialDelay: 10
|
|
44
|
+
initialDelay: 10,
|
|
44
45
|
})).rejects.toMatchObject({ code: 401 });
|
|
45
46
|
expect(operation).toHaveBeenCalledTimes(1);
|
|
46
47
|
});
|
|
47
48
|
it('should call onRetry callback', async () => {
|
|
48
|
-
const operation = jest
|
|
49
|
+
const operation = jest
|
|
50
|
+
.fn()
|
|
49
51
|
.mockRejectedValueOnce(new Error('Connection failed'))
|
|
50
52
|
.mockResolvedValue('success');
|
|
51
53
|
const onRetry = jest.fn();
|
|
@@ -53,7 +55,7 @@ describe('Retry utilities', () => {
|
|
|
53
55
|
maxAttempts: 2,
|
|
54
56
|
initialDelay: 10,
|
|
55
57
|
isRetryable: () => true, // Force retry for test
|
|
56
|
-
onRetry
|
|
58
|
+
onRetry,
|
|
57
59
|
});
|
|
58
60
|
expect(onRetry).toHaveBeenCalledTimes(1);
|
|
59
61
|
expect(onRetry).toHaveBeenCalledWith(1, expect.any(Error));
|
|
@@ -66,7 +68,7 @@ describe('Retry utilities', () => {
|
|
|
66
68
|
expect(result).toBe('success');
|
|
67
69
|
});
|
|
68
70
|
it('should reject with TimeoutError if operation takes too long', async () => {
|
|
69
|
-
const operation = new Promise(resolve => setTimeout(resolve, 1000));
|
|
71
|
+
const operation = new Promise((resolve) => setTimeout(resolve, 1000));
|
|
70
72
|
await expect((0, retry_1.withTimeout)(operation, 100)).rejects.toThrow(errors_1.TimeoutError);
|
|
71
73
|
});
|
|
72
74
|
});
|
|
@@ -95,7 +97,7 @@ describe('Retry utilities', () => {
|
|
|
95
97
|
limiter.execute(operation),
|
|
96
98
|
limiter.execute(operation),
|
|
97
99
|
limiter.execute(operation),
|
|
98
|
-
limiter.execute(operation)
|
|
100
|
+
limiter.execute(operation),
|
|
99
101
|
];
|
|
100
102
|
await Promise.all(promises);
|
|
101
103
|
// Allow some tolerance for race conditions
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const SignalCli_1 = require("../SignalCli");
|
|
4
|
+
const errors_1 = require("../errors");
|
|
5
|
+
// Mock dependencies
|
|
6
|
+
jest.mock('child_process');
|
|
7
|
+
jest.mock('../config', () => ({
|
|
8
|
+
Logger: jest.fn().mockImplementation(() => ({
|
|
9
|
+
debug: jest.fn(),
|
|
10
|
+
info: jest.fn(),
|
|
11
|
+
warn: jest.fn(),
|
|
12
|
+
error: jest.fn(),
|
|
13
|
+
})),
|
|
14
|
+
validateConfig: jest.fn().mockImplementation((config) => ({
|
|
15
|
+
verbose: false,
|
|
16
|
+
requestTimeout: config.requestTimeout || 1000,
|
|
17
|
+
maxConcurrentRequests: 10,
|
|
18
|
+
minRequestInterval: 100,
|
|
19
|
+
})),
|
|
20
|
+
}));
|
|
21
|
+
describe('SignalCli - Robustness', () => {
|
|
22
|
+
let signalCli;
|
|
23
|
+
const mockAccount = '+1234567890';
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
jest.clearAllMocks();
|
|
26
|
+
// Set a short timeout for tests
|
|
27
|
+
signalCli = new SignalCli_1.SignalCli(mockAccount, undefined, { requestTimeout: 100 });
|
|
28
|
+
});
|
|
29
|
+
describe('RPC Timeouts', () => {
|
|
30
|
+
it('should throw ConnectionError when RPC request times out', async () => {
|
|
31
|
+
// Mock cliProcess to simulate a slow response
|
|
32
|
+
signalCli.cliProcess = {
|
|
33
|
+
stdin: { write: jest.fn() },
|
|
34
|
+
killed: false,
|
|
35
|
+
};
|
|
36
|
+
// We don't call handleRpcResponse, so the promise will never resolve
|
|
37
|
+
// but the timeout should trigger
|
|
38
|
+
await expect(signalCli.getVersion()).rejects.toThrow(errors_1.ConnectionError);
|
|
39
|
+
await expect(signalCli.getVersion()).rejects.toThrow(/RPC request timeout/);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('Exponential Backoff Reconnection', () => {
|
|
43
|
+
it('should attempt reconnection when process closes', async () => {
|
|
44
|
+
// Spy on connect
|
|
45
|
+
const connectSpy = jest.spyOn(signalCli, 'connect').mockResolvedValue(undefined);
|
|
46
|
+
const loggerSpy = jest.spyOn(signalCli.logger, 'warn');
|
|
47
|
+
jest.useFakeTimers();
|
|
48
|
+
// Simulate process close
|
|
49
|
+
signalCli.handleProcessClose(1);
|
|
50
|
+
expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('Reconnecting in 1000ms'));
|
|
51
|
+
// Advance timers by 1s
|
|
52
|
+
jest.advanceTimersByTime(1000);
|
|
53
|
+
// Wait for any microtasks to clear
|
|
54
|
+
await Promise.resolve();
|
|
55
|
+
expect(connectSpy).toHaveBeenCalled();
|
|
56
|
+
jest.useRealTimers();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const SignalCli_1 = require("../SignalCli");
|
|
4
|
+
const validators_1 = require("../validators");
|
|
5
|
+
const errors_1 = require("../errors");
|
|
6
|
+
describe('Security - Injection Prevention', () => {
|
|
7
|
+
describe('validateSanitizedString', () => {
|
|
8
|
+
const unsafeStrings = [
|
|
9
|
+
'device; rm -rf /',
|
|
10
|
+
'device & echo "hacked"',
|
|
11
|
+
'device | cat /etc/passwd',
|
|
12
|
+
'device && ls',
|
|
13
|
+
'$(whoami)',
|
|
14
|
+
'`id`',
|
|
15
|
+
'device > output.txt',
|
|
16
|
+
'device < input.txt',
|
|
17
|
+
'device\nnewline',
|
|
18
|
+
'device\rreturn',
|
|
19
|
+
'device\ttab',
|
|
20
|
+
'device"quote',
|
|
21
|
+
"device'quote",
|
|
22
|
+
'device(paren)',
|
|
23
|
+
'device{brace}',
|
|
24
|
+
'device[bracket]',
|
|
25
|
+
'device\\backslash',
|
|
26
|
+
'device!exclamation',
|
|
27
|
+
'device$dollar',
|
|
28
|
+
];
|
|
29
|
+
unsafeStrings.forEach((input) => {
|
|
30
|
+
it(`should throw ValidationError for unsafe input: "${input}"`, () => {
|
|
31
|
+
expect(() => (0, validators_1.validateSanitizedString)(input)).toThrow(errors_1.ValidationError);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
it('should allow safe alphanumeric strings', () => {
|
|
35
|
+
expect(() => (0, validators_1.validateSanitizedString)('MyDevice123')).not.toThrow();
|
|
36
|
+
expect(() => (0, validators_1.validateSanitizedString)('device-name_dot.123')).not.toThrow();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe('SignalCli Security', () => {
|
|
40
|
+
it('should throw error if account number contains injection in constructor', () => {
|
|
41
|
+
const maliciousAccount = '+33123456789; rm -rf /';
|
|
42
|
+
expect(() => new SignalCli_1.SignalCli(maliciousAccount)).toThrow();
|
|
43
|
+
});
|
|
44
|
+
it('should throw error if device name contains injection in deviceLink', async () => {
|
|
45
|
+
const signal = new SignalCli_1.SignalCli('+1234567890');
|
|
46
|
+
const maliciousName = 'phone & echo hacked';
|
|
47
|
+
await expect(signal.deviceLink({ name: maliciousName })).rejects.toThrow(/contains unsafe characters/);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
package/dist/config.js
CHANGED
|
@@ -25,7 +25,7 @@ exports.DEFAULT_CONFIG = {
|
|
|
25
25
|
socketPath: '/tmp/signal-cli.sock',
|
|
26
26
|
tcpHost: 'localhost',
|
|
27
27
|
tcpPort: 7583,
|
|
28
|
-
httpBaseUrl: 'http://localhost:8080'
|
|
28
|
+
httpBaseUrl: 'http://localhost:8080',
|
|
29
29
|
};
|
|
30
30
|
/**
|
|
31
31
|
* Validates and merges configuration with defaults
|
|
@@ -60,7 +60,7 @@ exports.DEFAULT_LOGGER_CONFIG = {
|
|
|
60
60
|
enableConsole: true,
|
|
61
61
|
enableFile: false,
|
|
62
62
|
includeTimestamp: true,
|
|
63
|
-
includeLevel: true
|
|
63
|
+
includeLevel: true,
|
|
64
64
|
};
|
|
65
65
|
/**
|
|
66
66
|
* Simple logger implementation
|
|
@@ -71,7 +71,7 @@ class Logger {
|
|
|
71
71
|
debug: 0,
|
|
72
72
|
info: 1,
|
|
73
73
|
warn: 2,
|
|
74
|
-
error: 3
|
|
74
|
+
error: 3,
|
|
75
75
|
};
|
|
76
76
|
this.config = { ...exports.DEFAULT_LOGGER_CONFIG, ...config };
|
|
77
77
|
}
|
package/dist/interfaces.d.ts
CHANGED
|
@@ -108,6 +108,11 @@ export interface JsonRpcSendParams {
|
|
|
108
108
|
expiresInSeconds?: number;
|
|
109
109
|
/** Whether this is a view-once message */
|
|
110
110
|
isViewOnce?: boolean;
|
|
111
|
+
/** Link preview information */
|
|
112
|
+
previewUrl?: string;
|
|
113
|
+
previewTitle?: string;
|
|
114
|
+
previewDescription?: string;
|
|
115
|
+
previewImage?: string;
|
|
111
116
|
}
|
|
112
117
|
/**
|
|
113
118
|
* Parameters for the 'send_reaction' JSON-RPC method.
|
|
@@ -125,6 +130,8 @@ export interface JsonRpcReactionParams {
|
|
|
125
130
|
emoji: string;
|
|
126
131
|
/** Whether to remove the reaction */
|
|
127
132
|
remove?: boolean;
|
|
133
|
+
/** Whether this is a reaction to a story */
|
|
134
|
+
story?: boolean;
|
|
128
135
|
}
|
|
129
136
|
/**
|
|
130
137
|
* Parameters for the 'send_typing' JSON-RPC method.
|
|
@@ -512,6 +519,9 @@ export interface SendMessageOptions {
|
|
|
512
519
|
isViewOnce?: boolean;
|
|
513
520
|
linkPreview?: boolean;
|
|
514
521
|
previewUrl?: string;
|
|
522
|
+
previewTitle?: string;
|
|
523
|
+
previewDescription?: string;
|
|
524
|
+
previewImage?: string;
|
|
515
525
|
editTimestamp?: number;
|
|
516
526
|
storyTimestamp?: number;
|
|
517
527
|
storyAuthor?: string;
|
|
@@ -523,6 +533,8 @@ export interface SendMessageOptions {
|
|
|
523
533
|
*/
|
|
524
534
|
export interface Profile {
|
|
525
535
|
name?: string;
|
|
536
|
+
givenName?: string;
|
|
537
|
+
familyName?: string;
|
|
526
538
|
about?: string;
|
|
527
539
|
aboutEmoji?: string;
|
|
528
540
|
avatar?: string;
|
|
@@ -535,6 +547,11 @@ export interface Profile {
|
|
|
535
547
|
*/
|
|
536
548
|
export interface ContactUpdateOptions {
|
|
537
549
|
name?: string;
|
|
550
|
+
givenName?: string;
|
|
551
|
+
familyName?: string;
|
|
552
|
+
nickGivenName?: string;
|
|
553
|
+
nickFamilyName?: string;
|
|
554
|
+
note?: string;
|
|
538
555
|
color?: string;
|
|
539
556
|
block?: boolean;
|
|
540
557
|
unblock?: boolean;
|
|
@@ -542,6 +559,7 @@ export interface ContactUpdateOptions {
|
|
|
542
559
|
muted?: boolean;
|
|
543
560
|
mutedUntil?: number;
|
|
544
561
|
hideStory?: boolean;
|
|
562
|
+
expiration?: number;
|
|
545
563
|
}
|
|
546
564
|
/**
|
|
547
565
|
* Represents account configuration.
|
|
@@ -1095,6 +1113,15 @@ export interface AccountUpdateResult {
|
|
|
1095
1113
|
/** Error message if failed */
|
|
1096
1114
|
error?: string;
|
|
1097
1115
|
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Options for updating a linked device
|
|
1118
|
+
*/
|
|
1119
|
+
export interface UpdateDeviceOptions {
|
|
1120
|
+
/** Device ID to update */
|
|
1121
|
+
deviceId: number;
|
|
1122
|
+
/** New device name */
|
|
1123
|
+
deviceName: string;
|
|
1124
|
+
}
|
|
1098
1125
|
/**
|
|
1099
1126
|
* Options for receiving messages
|
|
1100
1127
|
*/
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BaseManager } from './BaseManager';
|
|
2
|
+
import { AccountConfiguration, UpdateAccountOptions, AccountUpdateResult, PaymentNotificationData, SendResponse, RateLimitChallengeResult } from '../interfaces';
|
|
3
|
+
export declare class AccountManager extends BaseManager {
|
|
4
|
+
register(number: string, voice?: boolean, captcha?: string): Promise<void>;
|
|
5
|
+
verify(number: string, token: string, pin?: string): Promise<void>;
|
|
6
|
+
updateProfile(name: string, about?: string, aboutEmoji?: string, avatar?: string, options?: {
|
|
7
|
+
familyName?: string;
|
|
8
|
+
mobileCoinAddress?: string;
|
|
9
|
+
removeAvatar?: boolean;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
unregister(): Promise<void>;
|
|
12
|
+
deleteLocalAccountData(): Promise<void>;
|
|
13
|
+
updateAccountConfiguration(config: AccountConfiguration): Promise<void>;
|
|
14
|
+
setPin(pin: string): Promise<void>;
|
|
15
|
+
removePin(): Promise<void>;
|
|
16
|
+
listAccounts(): Promise<string[]>;
|
|
17
|
+
listAccountsDetailed(): Promise<Array<{
|
|
18
|
+
number: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
uuid?: string;
|
|
21
|
+
}>>;
|
|
22
|
+
updateAccount(options: UpdateAccountOptions): Promise<AccountUpdateResult>;
|
|
23
|
+
sendPaymentNotification(recipient: string, paymentData: PaymentNotificationData): Promise<SendResponse>;
|
|
24
|
+
submitRateLimitChallenge(challenge: string, captcha: string): Promise<RateLimitChallengeResult>;
|
|
25
|
+
startChangeNumber(newNumber: string, voice?: boolean, captcha?: string): Promise<void>;
|
|
26
|
+
finishChangeNumber(newNumber: string, verificationCode: string, pin?: string): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AccountManager = void 0;
|
|
4
|
+
const BaseManager_1 = require("./BaseManager");
|
|
5
|
+
const validators_1 = require("../validators");
|
|
6
|
+
class AccountManager extends BaseManager_1.BaseManager {
|
|
7
|
+
async register(number, voice, captcha) {
|
|
8
|
+
await this.sendRequest('register', { account: number, voice, captcha });
|
|
9
|
+
}
|
|
10
|
+
async verify(number, token, pin) {
|
|
11
|
+
await this.sendRequest('verify', { account: number, token, pin });
|
|
12
|
+
}
|
|
13
|
+
async updateProfile(name, about, aboutEmoji, avatar, options = {}) {
|
|
14
|
+
const params = { account: this.account, name };
|
|
15
|
+
if (about)
|
|
16
|
+
params.about = about;
|
|
17
|
+
if (aboutEmoji)
|
|
18
|
+
params.aboutEmoji = aboutEmoji;
|
|
19
|
+
if (avatar)
|
|
20
|
+
params.avatar = avatar;
|
|
21
|
+
if (options.familyName)
|
|
22
|
+
params.familyName = options.familyName;
|
|
23
|
+
if (options.mobileCoinAddress)
|
|
24
|
+
params.mobileCoinAddress = options.mobileCoinAddress;
|
|
25
|
+
if (options.removeAvatar)
|
|
26
|
+
params.removeAvatar = true;
|
|
27
|
+
await this.sendRequest('updateProfile', params);
|
|
28
|
+
}
|
|
29
|
+
async unregister() {
|
|
30
|
+
await this.sendRequest('unregister', { account: this.account });
|
|
31
|
+
}
|
|
32
|
+
async deleteLocalAccountData() {
|
|
33
|
+
await this.sendRequest('deleteLocalAccountData', { account: this.account });
|
|
34
|
+
}
|
|
35
|
+
async updateAccountConfiguration(config) {
|
|
36
|
+
await this.sendRequest('updateConfiguration', { account: this.account, ...config });
|
|
37
|
+
}
|
|
38
|
+
async setPin(pin) {
|
|
39
|
+
await this.sendRequest('setPin', { account: this.account, pin });
|
|
40
|
+
}
|
|
41
|
+
async removePin() {
|
|
42
|
+
await this.sendRequest('removePin', { account: this.account });
|
|
43
|
+
}
|
|
44
|
+
async listAccounts() {
|
|
45
|
+
const result = await this.sendRequest('listAccounts');
|
|
46
|
+
return result.accounts.map((acc) => acc.number);
|
|
47
|
+
}
|
|
48
|
+
async listAccountsDetailed() {
|
|
49
|
+
this.logger.debug('Listing all accounts');
|
|
50
|
+
const result = await this.sendRequest('listAccounts');
|
|
51
|
+
return result.accounts || [];
|
|
52
|
+
}
|
|
53
|
+
async updateAccount(options) {
|
|
54
|
+
this.logger.debug('Updating account', options);
|
|
55
|
+
const params = { account: this.account };
|
|
56
|
+
if (options.deviceName)
|
|
57
|
+
params.deviceName = options.deviceName;
|
|
58
|
+
if (options.username)
|
|
59
|
+
params.username = options.username;
|
|
60
|
+
if (options.deleteUsername)
|
|
61
|
+
params.deleteUsername = true;
|
|
62
|
+
if (options.unrestrictedUnidentifiedSender !== undefined) {
|
|
63
|
+
params.unrestrictedUnidentifiedSender = options.unrestrictedUnidentifiedSender;
|
|
64
|
+
}
|
|
65
|
+
if (options.discoverableByNumber !== undefined) {
|
|
66
|
+
params.discoverableByNumber = options.discoverableByNumber;
|
|
67
|
+
}
|
|
68
|
+
if (options.numberSharing !== undefined) {
|
|
69
|
+
params.numberSharing = options.numberSharing;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const result = await this.sendRequest('updateAccount', params);
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
username: result.username,
|
|
76
|
+
usernameLink: result.usernameLink,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async sendPaymentNotification(recipient, paymentData) {
|
|
87
|
+
this.logger.info(`Sending payment notification to ${recipient}`);
|
|
88
|
+
(0, validators_1.validateRecipient)(recipient);
|
|
89
|
+
if (!paymentData.receipt || paymentData.receipt.trim().length === 0) {
|
|
90
|
+
throw new Error('Payment receipt is required');
|
|
91
|
+
}
|
|
92
|
+
const params = {
|
|
93
|
+
receipt: paymentData.receipt,
|
|
94
|
+
account: this.account,
|
|
95
|
+
};
|
|
96
|
+
if (paymentData.note)
|
|
97
|
+
params.note = paymentData.note;
|
|
98
|
+
if (this.isGroupId(recipient)) {
|
|
99
|
+
params.groupId = recipient;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
params.recipient = recipient;
|
|
103
|
+
}
|
|
104
|
+
return this.sendRequest('sendPaymentNotification', params);
|
|
105
|
+
}
|
|
106
|
+
async submitRateLimitChallenge(challenge, captcha) {
|
|
107
|
+
const params = {
|
|
108
|
+
account: this.account,
|
|
109
|
+
challenge,
|
|
110
|
+
captcha,
|
|
111
|
+
};
|
|
112
|
+
const result = await this.sendRequest('submitRateLimitChallenge', params);
|
|
113
|
+
return {
|
|
114
|
+
success: result.success || false,
|
|
115
|
+
retryAfter: result.retryAfter,
|
|
116
|
+
message: result.message,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async startChangeNumber(newNumber, voice = false, captcha) {
|
|
120
|
+
this.logger.info(`Starting change number to ${newNumber} (voice: ${voice})`);
|
|
121
|
+
(0, validators_1.validatePhoneNumber)(newNumber);
|
|
122
|
+
const params = {
|
|
123
|
+
account: this.account,
|
|
124
|
+
number: newNumber,
|
|
125
|
+
voice,
|
|
126
|
+
};
|
|
127
|
+
if (captcha)
|
|
128
|
+
params.captcha = captcha;
|
|
129
|
+
await this.sendRequest('startChangeNumber', params);
|
|
130
|
+
}
|
|
131
|
+
async finishChangeNumber(newNumber, verificationCode, pin) {
|
|
132
|
+
this.logger.info(`Finishing change number to ${newNumber}`);
|
|
133
|
+
(0, validators_1.validatePhoneNumber)(newNumber);
|
|
134
|
+
if (!verificationCode || verificationCode.trim().length === 0) {
|
|
135
|
+
throw new Error('Verification code is required');
|
|
136
|
+
}
|
|
137
|
+
const params = {
|
|
138
|
+
account: this.account,
|
|
139
|
+
number: newNumber,
|
|
140
|
+
verificationCode,
|
|
141
|
+
};
|
|
142
|
+
if (pin)
|
|
143
|
+
params.pin = pin;
|
|
144
|
+
await this.sendRequest('finishChangeNumber', params);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.AccountManager = AccountManager;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SignalCliConfig, Logger } from '../config';
|
|
2
|
+
export declare abstract class BaseManager {
|
|
3
|
+
protected readonly sendRequest: (method: string, params?: any) => Promise<any>;
|
|
4
|
+
protected readonly account: string | undefined;
|
|
5
|
+
protected readonly logger: Logger;
|
|
6
|
+
protected readonly config: Required<SignalCliConfig>;
|
|
7
|
+
constructor(sendRequest: (method: string, params?: any) => Promise<any>, account: string | undefined, logger: Logger, config: Required<SignalCliConfig>);
|
|
8
|
+
protected isGroupId(recipient: string): boolean;
|
|
9
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseManager = void 0;
|
|
4
|
+
class BaseManager {
|
|
5
|
+
constructor(sendRequest, account, logger, config) {
|
|
6
|
+
this.sendRequest = sendRequest;
|
|
7
|
+
this.account = account;
|
|
8
|
+
this.logger = logger;
|
|
9
|
+
this.config = config;
|
|
10
|
+
}
|
|
11
|
+
isGroupId(recipient) {
|
|
12
|
+
return (recipient.includes('=') ||
|
|
13
|
+
recipient.includes('/') ||
|
|
14
|
+
(recipient.includes('+') && !recipient.startsWith('+')));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.BaseManager = BaseManager;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseManager } from './BaseManager';
|
|
2
|
+
import { Contact, ContactUpdateOptions, RemoveContactOptions, IdentityKey, UserStatusResult, GetAvatarOptions } from '../interfaces';
|
|
3
|
+
export declare class ContactManager extends BaseManager {
|
|
4
|
+
updateContact(number: string, name?: string, options?: Omit<ContactUpdateOptions, 'name'>): Promise<void>;
|
|
5
|
+
block(recipients: string[], groupId?: string): Promise<void>;
|
|
6
|
+
unblock(recipients: string[], groupId?: string): Promise<void>;
|
|
7
|
+
listContacts(): Promise<Contact[]>;
|
|
8
|
+
removeContact(number: string, options?: RemoveContactOptions): Promise<void>;
|
|
9
|
+
getUserStatus(numbers?: string[], usernames?: string[]): Promise<UserStatusResult[]>;
|
|
10
|
+
listIdentities(number?: string): Promise<IdentityKey[]>;
|
|
11
|
+
trustIdentity(number: string, safetyNumber: string, verified?: boolean): Promise<void>;
|
|
12
|
+
getAvatar(options: GetAvatarOptions): Promise<string>;
|
|
13
|
+
parseContactProfile(contact: Contact): Contact;
|
|
14
|
+
getContactsWithProfiles(): Promise<Contact[]>;
|
|
15
|
+
}
|