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.
Files changed (54) hide show
  1. package/README.md +23 -11
  2. package/dist/MultiAccountManager.js +11 -19
  3. package/dist/SignalBot.js +40 -36
  4. package/dist/SignalCli.d.ts +25 -301
  5. package/dist/SignalCli.js +226 -971
  6. package/dist/__tests__/DeviceManager.test.d.ts +1 -0
  7. package/dist/__tests__/DeviceManager.test.js +135 -0
  8. package/dist/__tests__/MultiAccountManager.coverage.test.d.ts +1 -0
  9. package/dist/__tests__/MultiAccountManager.coverage.test.js +33 -0
  10. package/dist/__tests__/MultiAccountManager.test.js +3 -3
  11. package/dist/__tests__/SignalBot.additional.test.js +40 -37
  12. package/dist/__tests__/SignalBot.coverage.test.d.ts +1 -0
  13. package/dist/__tests__/SignalBot.coverage.test.js +385 -0
  14. package/dist/__tests__/SignalBot.test.js +8 -8
  15. package/dist/__tests__/SignalCli.advanced.test.js +47 -58
  16. package/dist/__tests__/SignalCli.connections.test.d.ts +1 -0
  17. package/dist/__tests__/SignalCli.connections.test.js +110 -0
  18. package/dist/__tests__/SignalCli.e2e.test.js +28 -32
  19. package/dist/__tests__/SignalCli.events.test.d.ts +1 -0
  20. package/dist/__tests__/SignalCli.events.test.js +113 -0
  21. package/dist/__tests__/SignalCli.integration.test.js +6 -5
  22. package/dist/__tests__/SignalCli.methods.test.js +150 -66
  23. package/dist/__tests__/SignalCli.parsing.test.js +4 -13
  24. package/dist/__tests__/SignalCli.simple.test.d.ts +1 -0
  25. package/dist/__tests__/SignalCli.simple.test.js +77 -0
  26. package/dist/__tests__/SignalCli.test.js +133 -74
  27. package/dist/__tests__/config.test.js +19 -29
  28. package/dist/__tests__/errors.test.js +2 -2
  29. package/dist/__tests__/retry.test.js +10 -8
  30. package/dist/__tests__/robustness.test.d.ts +1 -0
  31. package/dist/__tests__/robustness.test.js +59 -0
  32. package/dist/__tests__/security.test.d.ts +1 -0
  33. package/dist/__tests__/security.test.js +50 -0
  34. package/dist/config.js +3 -3
  35. package/dist/interfaces.d.ts +27 -0
  36. package/dist/managers/AccountManager.d.ts +27 -0
  37. package/dist/managers/AccountManager.js +147 -0
  38. package/dist/managers/BaseManager.d.ts +9 -0
  39. package/dist/managers/BaseManager.js +17 -0
  40. package/dist/managers/ContactManager.d.ts +15 -0
  41. package/dist/managers/ContactManager.js +123 -0
  42. package/dist/managers/DeviceManager.d.ts +11 -0
  43. package/dist/managers/DeviceManager.js +139 -0
  44. package/dist/managers/GroupManager.d.ts +12 -0
  45. package/dist/managers/GroupManager.js +78 -0
  46. package/dist/managers/MessageManager.d.ts +18 -0
  47. package/dist/managers/MessageManager.js +301 -0
  48. package/dist/managers/StickerManager.d.ts +8 -0
  49. package/dist/managers/StickerManager.js +39 -0
  50. package/dist/retry.js +3 -3
  51. package/dist/validators.d.ts +9 -0
  52. package/dist/validators.js +20 -0
  53. package/package.json +11 -4
  54. 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
- .toThrow('maxConcurrentRequests must be at least 1');
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
- .not.toThrow();
53
- expect(() => (0, config_1.validateConfig)({ trustNewIdentities: 'always' }))
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.fn()
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.fn()
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
  }
@@ -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
+ }