signal-sdk 0.1.0 → 0.1.2

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,258 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for enhanced parsing functionality (Phase 5)
4
+ * Tests profile and group data parsing methods
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const SignalCli_1 = require("../SignalCli");
8
+ describe('SignalCli - Enhanced Parsing (Phase 5)', () => {
9
+ let signal;
10
+ beforeEach(() => {
11
+ signal = new SignalCli_1.SignalCli('+33123456789');
12
+ // Mock sendJsonRpcRequest to avoid actual signal-cli calls
13
+ signal.sendJsonRpcRequest = jest.fn();
14
+ });
15
+ describe('parseContactProfile', () => {
16
+ it('should parse contact with full profile data', () => {
17
+ const contact = {
18
+ number: '+33123456789',
19
+ name: 'John Doe',
20
+ uuid: 'test-uuid',
21
+ blocked: false,
22
+ givenName: 'John',
23
+ familyName: 'Doe',
24
+ mobileCoinAddress: 'mc1_test_address_123',
25
+ profileName: 'John Doe',
26
+ };
27
+ const parsed = signal.parseContactProfile(contact);
28
+ expect(parsed.givenName).toBe('John');
29
+ expect(parsed.familyName).toBe('Doe');
30
+ expect(parsed.mobileCoinAddress).toBe('mc1_test_address_123');
31
+ expect(parsed.profileName).toBe('John Doe');
32
+ });
33
+ it('should build profileName from givenName and familyName if missing', () => {
34
+ const contact = {
35
+ number: '+33123456789',
36
+ name: 'John Doe',
37
+ uuid: 'test-uuid',
38
+ blocked: false,
39
+ givenName: 'John',
40
+ familyName: 'Doe',
41
+ };
42
+ const parsed = signal.parseContactProfile(contact);
43
+ expect(parsed.profileName).toBe('John Doe');
44
+ });
45
+ it('should use givenName as profileName if no familyName', () => {
46
+ const contact = {
47
+ number: '+33123456789',
48
+ name: 'John',
49
+ uuid: 'test-uuid',
50
+ blocked: false,
51
+ givenName: 'John',
52
+ };
53
+ const parsed = signal.parseContactProfile(contact);
54
+ expect(parsed.profileName).toBe('John');
55
+ });
56
+ it('should handle contact with minimal data', () => {
57
+ const contact = {
58
+ number: '+33123456789',
59
+ name: 'Unknown',
60
+ blocked: false,
61
+ };
62
+ const parsed = signal.parseContactProfile(contact);
63
+ expect(parsed.givenName).toBeUndefined();
64
+ expect(parsed.familyName).toBeUndefined();
65
+ expect(parsed.mobileCoinAddress).toBeUndefined();
66
+ });
67
+ it('should preserve additional contact fields', () => {
68
+ const contact = {
69
+ number: '+33123456789',
70
+ name: 'John Doe',
71
+ uuid: 'test-uuid',
72
+ blocked: true,
73
+ profileAvatar: 'avatar.jpg',
74
+ color: 'blue',
75
+ archived: true,
76
+ mutedUntil: 123456789,
77
+ hideStory: true,
78
+ givenName: 'John',
79
+ };
80
+ const parsed = signal.parseContactProfile(contact);
81
+ expect(parsed.blocked).toBe(true);
82
+ expect(parsed.profileAvatar).toBe('avatar.jpg');
83
+ expect(parsed.color).toBe('blue');
84
+ expect(parsed.archived).toBe(true);
85
+ expect(parsed.mutedUntil).toBe(123456789);
86
+ expect(parsed.hideStory).toBe(true);
87
+ });
88
+ });
89
+ describe('parseGroupDetails', () => {
90
+ it('should parse group with full membership data', () => {
91
+ const group = {
92
+ groupId: 'group123==',
93
+ name: 'Test Group',
94
+ description: 'A test group',
95
+ isMember: true,
96
+ isBlocked: false,
97
+ messageExpirationTime: 0,
98
+ members: [
99
+ { number: '+33111111111' },
100
+ { number: '+33222222222' },
101
+ ],
102
+ pendingMembers: [
103
+ { number: '+33333333333' },
104
+ ],
105
+ requestingMembers: [],
106
+ admins: [
107
+ { number: '+33111111111' },
108
+ ],
109
+ banned: [
110
+ { number: '+33444444444' },
111
+ ],
112
+ permissionAddMember: 'ONLY_ADMINS',
113
+ permissionEditDetails: 'ONLY_ADMINS',
114
+ permissionSendMessage: 'EVERY_MEMBER',
115
+ groupInviteLink: 'https://signal.group/test',
116
+ };
117
+ const parsed = signal.parseGroupDetails(group);
118
+ expect(parsed.pendingMembers).toHaveLength(1);
119
+ expect(parsed.pendingMembers[0].number).toBe('+33333333333');
120
+ expect(parsed.banned).toHaveLength(1);
121
+ expect(parsed.banned[0].number).toBe('+33444444444');
122
+ expect(parsed.inviteLink).toBe('https://signal.group/test');
123
+ expect(parsed.groupInviteLink).toBe('https://signal.group/test');
124
+ });
125
+ it('should normalize inviteLink field', () => {
126
+ const group1 = {
127
+ groupId: 'group123==',
128
+ name: 'Test Group',
129
+ isMember: true,
130
+ isBlocked: false,
131
+ messageExpirationTime: 0,
132
+ members: [],
133
+ pendingMembers: [],
134
+ requestingMembers: [],
135
+ admins: [],
136
+ banned: [],
137
+ permissionAddMember: 'EVERY_MEMBER',
138
+ permissionEditDetails: 'EVERY_MEMBER',
139
+ permissionSendMessage: 'EVERY_MEMBER',
140
+ groupInviteLink: 'https://signal.group/test1',
141
+ };
142
+ const parsed1 = signal.parseGroupDetails(group1);
143
+ expect(parsed1.inviteLink).toBe('https://signal.group/test1');
144
+ expect(parsed1.groupInviteLink).toBe('https://signal.group/test1');
145
+ const group2 = {
146
+ ...group1,
147
+ groupInviteLink: undefined,
148
+ inviteLink: 'https://signal.group/test2',
149
+ };
150
+ const parsed2 = signal.parseGroupDetails(group2);
151
+ expect(parsed2.inviteLink).toBe('https://signal.group/test2');
152
+ expect(parsed2.groupInviteLink).toBe('https://signal.group/test2');
153
+ });
154
+ it('should ensure membership arrays exist', () => {
155
+ const group = {
156
+ groupId: 'group123==',
157
+ name: 'Minimal Group',
158
+ isMember: true,
159
+ isBlocked: false,
160
+ messageExpirationTime: 0,
161
+ members: [],
162
+ pendingMembers: [],
163
+ requestingMembers: [],
164
+ admins: [],
165
+ banned: [],
166
+ permissionAddMember: 'EVERY_MEMBER',
167
+ permissionEditDetails: 'EVERY_MEMBER',
168
+ permissionSendMessage: 'EVERY_MEMBER',
169
+ };
170
+ const parsed = signal.parseGroupDetails(group);
171
+ expect(Array.isArray(parsed.members)).toBe(true);
172
+ expect(Array.isArray(parsed.pendingMembers)).toBe(true);
173
+ expect(Array.isArray(parsed.requestingMembers)).toBe(true);
174
+ expect(Array.isArray(parsed.admins)).toBe(true);
175
+ expect(Array.isArray(parsed.banned)).toBe(true);
176
+ });
177
+ it('should preserve group permissions', () => {
178
+ const group = {
179
+ groupId: 'group123==',
180
+ name: 'Restricted Group',
181
+ isMember: true,
182
+ isBlocked: false,
183
+ messageExpirationTime: 3600,
184
+ members: [],
185
+ pendingMembers: [],
186
+ requestingMembers: [],
187
+ admins: [],
188
+ banned: [],
189
+ permissionAddMember: 'ONLY_ADMINS',
190
+ permissionEditDetails: 'ONLY_ADMINS',
191
+ permissionSendMessage: 'ONLY_ADMINS',
192
+ };
193
+ const parsed = signal.parseGroupDetails(group);
194
+ expect(parsed.permissionAddMember).toBe('ONLY_ADMINS');
195
+ expect(parsed.permissionEditDetails).toBe('ONLY_ADMINS');
196
+ expect(parsed.permissionSendMessage).toBe('ONLY_ADMINS');
197
+ expect(parsed.messageExpirationTime).toBe(3600);
198
+ });
199
+ });
200
+ describe('getContactsWithProfiles', () => {
201
+ it('should return enriched contacts list', async () => {
202
+ const mockContacts = [
203
+ {
204
+ number: '+33111111111',
205
+ name: 'Alice',
206
+ blocked: false,
207
+ givenName: 'Alice',
208
+ familyName: 'Smith',
209
+ },
210
+ {
211
+ number: '+33222222222',
212
+ name: 'Bob',
213
+ blocked: false,
214
+ givenName: 'Bob',
215
+ mobileCoinAddress: 'mc1_bob',
216
+ },
217
+ ];
218
+ signal.sendJsonRpcRequest = jest.fn().mockResolvedValue(mockContacts);
219
+ const enriched = await signal.getContactsWithProfiles();
220
+ expect(enriched).toHaveLength(2);
221
+ expect(enriched[0].profileName).toBe('Alice Smith');
222
+ expect(enriched[1].profileName).toBe('Bob');
223
+ expect(enriched[1].mobileCoinAddress).toBe('mc1_bob');
224
+ });
225
+ });
226
+ describe('getGroupsWithDetails', () => {
227
+ it('should return enriched groups list', async () => {
228
+ const mockGroups = [
229
+ {
230
+ groupId: 'group1==',
231
+ name: 'Group 1',
232
+ isMember: true,
233
+ isBlocked: false,
234
+ messageExpirationTime: 0,
235
+ members: [{ number: '+33111111111' }],
236
+ pendingMembers: [{ number: '+33222222222' }],
237
+ requestingMembers: [],
238
+ admins: [{ number: '+33111111111' }],
239
+ banned: [],
240
+ permissionAddMember: 'EVERY_MEMBER',
241
+ permissionEditDetails: 'EVERY_MEMBER',
242
+ permissionSendMessage: 'EVERY_MEMBER',
243
+ groupInviteLink: 'https://signal.group/group1',
244
+ },
245
+ ];
246
+ signal.sendJsonRpcRequest = jest.fn().mockResolvedValue(mockGroups);
247
+ const enriched = await signal.getGroupsWithDetails();
248
+ expect(enriched).toHaveLength(1);
249
+ expect(enriched[0].inviteLink).toBe('https://signal.group/group1');
250
+ expect(enriched[0].pendingMembers).toHaveLength(1);
251
+ });
252
+ it('should support list options', async () => {
253
+ signal.sendJsonRpcRequest = jest.fn().mockResolvedValue([]);
254
+ await signal.getGroupsWithDetails({ detailed: true });
255
+ expect(signal.sendJsonRpcRequest).toHaveBeenCalledWith('listGroups', expect.objectContaining({ detailed: true }));
256
+ });
257
+ });
258
+ });
@@ -155,31 +155,23 @@ describe('SignalCli', () => {
155
155
  expect(result).toEqual(mockResponse);
156
156
  });
157
157
  it('should start change number', async () => {
158
- const mockResponse = {
159
- session: 'change-session-id',
160
- challenge: 'challenge-token'
161
- };
162
158
  const sendJsonRpcRequestSpy = jest.spyOn(signalCli, 'sendJsonRpcRequest')
163
- .mockResolvedValue(mockResponse);
164
- const result = await signalCli.startChangeNumber('+1987654321');
159
+ .mockResolvedValue({});
160
+ await signalCli.startChangeNumber('+1987654321');
165
161
  expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('startChangeNumber', {
166
162
  account: '+1234567890',
167
163
  number: '+1987654321',
168
164
  voice: false
169
165
  });
170
- expect(result).toEqual({
171
- session: 'change-session-id',
172
- newNumber: '+1987654321',
173
- challenge: 'challenge-token'
174
- });
175
166
  });
176
167
  it('should finish change number', async () => {
177
168
  const sendJsonRpcRequestSpy = jest.spyOn(signalCli, 'sendJsonRpcRequest')
178
169
  .mockResolvedValue({});
179
- await signalCli.finishChangeNumber('123456', 'pin-code');
170
+ await signalCli.finishChangeNumber('+1987654321', '123456', 'pin-code');
180
171
  expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('finishChangeNumber', {
181
172
  account: '+1234567890',
182
- code: '123456',
173
+ number: '+1987654321',
174
+ verificationCode: '123456',
183
175
  pin: 'pin-code'
184
176
  });
185
177
  });
@@ -415,6 +407,51 @@ describe('SignalCli', () => {
415
407
  expect(result).toEqual(mockResponse.accounts);
416
408
  });
417
409
  });
410
+ // Test device management (v0.13.23+)
411
+ describe('Device Management', () => {
412
+ beforeEach(() => {
413
+ jest.spyOn(signalCli, 'sendJsonRpcRequest').mockResolvedValue({});
414
+ });
415
+ it('should list devices', async () => {
416
+ const mockDevices = [
417
+ { id: 1, name: 'Device 1' },
418
+ { id: 2, name: 'Device 2' }
419
+ ];
420
+ const sendJsonRpcRequestSpy = jest.spyOn(signalCli, 'sendJsonRpcRequest')
421
+ .mockResolvedValue(mockDevices);
422
+ const result = await signalCli.listDevices();
423
+ expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('listDevices', {
424
+ account: '+1234567890'
425
+ });
426
+ expect(result).toEqual(mockDevices);
427
+ });
428
+ it('should update device name', async () => {
429
+ const sendJsonRpcRequestSpy = jest.spyOn(signalCli, 'sendJsonRpcRequest')
430
+ .mockResolvedValue(undefined);
431
+ await signalCli.updateDevice({
432
+ deviceId: 2,
433
+ deviceName: 'My Updated Device'
434
+ });
435
+ expect(sendJsonRpcRequestSpy).toHaveBeenCalledWith('updateDevice', {
436
+ account: '+1234567890',
437
+ deviceId: 2,
438
+ deviceName: 'My Updated Device'
439
+ });
440
+ });
441
+ it('should validate device ID when updating', async () => {
442
+ await expect(signalCli.updateDevice({
443
+ deviceId: -1,
444
+ deviceName: 'Invalid'
445
+ })).rejects.toThrow();
446
+ });
447
+ it('should validate device name length', async () => {
448
+ const longName = 'a'.repeat(201);
449
+ await expect(signalCli.updateDevice({
450
+ deviceId: 2,
451
+ deviceName: longName
452
+ })).rejects.toThrow();
453
+ });
454
+ });
418
455
  // Test v0.1.0 features: Synchronization
419
456
  describe('Synchronization', () => {
420
457
  beforeEach(() => {
package/dist/config.d.ts CHANGED
@@ -31,8 +31,23 @@ export interface SignalCliConfig {
31
31
  trustNewIdentities?: 'on-first-use' | 'always' | 'never';
32
32
  /** Disable send log (for message resending) */
33
33
  disableSendLog?: boolean;
34
+ /** Daemon connection mode: json-rpc (default), unix-socket, tcp, http */
35
+ daemonMode?: 'json-rpc' | 'unix-socket' | 'tcp' | 'http';
36
+ /** Unix socket path (for unix-socket mode) */
37
+ socketPath?: string;
38
+ /** TCP host (for tcp mode) */
39
+ tcpHost?: string;
40
+ /** TCP port (for tcp or http mode) */
41
+ tcpPort?: number;
42
+ /** HTTP base URL (for http mode) */
43
+ httpBaseUrl?: string;
34
44
  }
35
- export declare const DEFAULT_CONFIG: Required<SignalCliConfig>;
45
+ export declare const DEFAULT_CONFIG: Required<Omit<SignalCliConfig, 'socketPath' | 'tcpHost' | 'tcpPort' | 'httpBaseUrl'> & {
46
+ socketPath: string;
47
+ tcpHost: string;
48
+ tcpPort: number;
49
+ httpBaseUrl: string;
50
+ }>;
36
51
  /**
37
52
  * Validates and merges configuration with defaults
38
53
  * @param userConfig User-provided configuration
package/dist/config.js CHANGED
@@ -20,7 +20,12 @@ exports.DEFAULT_CONFIG = {
20
20
  minRequestInterval: 100,
21
21
  autoReconnect: true,
22
22
  trustNewIdentities: 'on-first-use',
23
- disableSendLog: false
23
+ disableSendLog: false,
24
+ daemonMode: 'json-rpc',
25
+ socketPath: '/tmp/signal-cli.sock',
26
+ tcpHost: 'localhost',
27
+ tcpPort: 7583,
28
+ httpBaseUrl: 'http://localhost:8080'
24
29
  };
25
30
  /**
26
31
  * Validates and merges configuration with defaults
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { SignalCli } from './SignalCli';
2
2
  export { SignalBot } from './SignalBot';
3
+ export { MultiAccountManager } from './MultiAccountManager';
3
4
  export * from './interfaces';
4
5
  export * from './errors';
5
6
  export * from './validators';
package/dist/index.js CHANGED
@@ -14,11 +14,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.SignalBot = exports.SignalCli = void 0;
17
+ exports.MultiAccountManager = exports.SignalBot = exports.SignalCli = void 0;
18
18
  var SignalCli_1 = require("./SignalCli");
19
19
  Object.defineProperty(exports, "SignalCli", { enumerable: true, get: function () { return SignalCli_1.SignalCli; } });
20
20
  var SignalBot_1 = require("./SignalBot");
21
21
  Object.defineProperty(exports, "SignalBot", { enumerable: true, get: function () { return SignalBot_1.SignalBot; } });
22
+ var MultiAccountManager_1 = require("./MultiAccountManager");
23
+ Object.defineProperty(exports, "MultiAccountManager", { enumerable: true, get: function () { return MultiAccountManager_1.MultiAccountManager; } });
22
24
  __exportStar(require("./interfaces"), exports);
23
25
  __exportStar(require("./errors"), exports);
24
26
  __exportStar(require("./validators"), exports);
@@ -278,6 +278,8 @@ export interface SendResponse {
278
278
  export interface GroupInfo {
279
279
  /** Base64 encoded group ID */
280
280
  groupId: string;
281
+ /** Alias for groupId */
282
+ id?: string;
281
283
  /** Group display name */
282
284
  name: string;
283
285
  /** Group description */
@@ -306,17 +308,23 @@ export interface GroupInfo {
306
308
  permissionSendMessage: string;
307
309
  /** Group invite link */
308
310
  groupInviteLink?: string;
311
+ /** Alias for groupInviteLink */
312
+ inviteLink?: string;
309
313
  }
310
314
  /**
311
315
  * Represents a received message.
312
316
  */
313
317
  export interface Message {
314
- id: string;
318
+ id?: string;
315
319
  source: string;
320
+ sourceUuid?: string;
321
+ sourceDevice?: number;
316
322
  text?: string;
317
323
  timestamp: number;
318
324
  attachments?: Attachment[];
319
325
  reactions?: Reaction[];
326
+ reaction?: any;
327
+ groupId?: string;
320
328
  groupInfo?: {
321
329
  id: string;
322
330
  name?: string;
@@ -333,8 +341,12 @@ export interface Message {
333
341
  textStyles?: TextStyle[];
334
342
  expiresInSeconds?: number;
335
343
  isViewOnce?: boolean;
344
+ viewOnce?: boolean;
336
345
  remoteDelete?: boolean;
337
346
  isExpirationUpdate?: boolean;
347
+ syncMessage?: any;
348
+ receipt?: any;
349
+ typing?: any;
338
350
  }
339
351
  /**
340
352
  * Represents a mention in a message.
@@ -343,6 +355,7 @@ export interface Mention {
343
355
  start: number;
344
356
  length: number;
345
357
  number: string;
358
+ recipient?: string;
346
359
  name?: string;
347
360
  }
348
361
  /**
@@ -379,11 +392,23 @@ export interface Contact {
379
392
  uuid?: string;
380
393
  blocked: boolean;
381
394
  profileName?: string;
395
+ /** Given name (first name) from profile */
396
+ givenName?: string;
397
+ /** Family name (last name) from profile */
398
+ familyName?: string;
399
+ /** MobileCoin address for payments */
400
+ mobileCoinAddress?: string;
382
401
  profileAvatar?: string;
383
402
  color?: string;
384
403
  archived?: boolean;
385
404
  mutedUntil?: number;
386
405
  hideStory?: boolean;
406
+ /** Profile key for encrypted profile access */
407
+ profileKey?: string;
408
+ /** Contact's username */
409
+ username?: string;
410
+ /** Whether this contact is a registered Signal user */
411
+ registered?: boolean;
387
412
  }
388
413
  /**
389
414
  * Represents a Signal group.
@@ -393,6 +418,14 @@ export interface Group {
393
418
  name: string;
394
419
  members: string[];
395
420
  admins?: string[];
421
+ /** Pending members awaiting invite acceptance */
422
+ pendingMembers?: string[];
423
+ /** Banned members who cannot rejoin */
424
+ bannedMembers?: string[];
425
+ /** Group invite link for joining */
426
+ inviteLink?: string;
427
+ /** Whether invite link approval is required */
428
+ inviteLinkRequiresApproval?: boolean;
396
429
  isBlocked: boolean;
397
430
  isMember: boolean;
398
431
  isAdmin?: boolean;
@@ -401,12 +434,15 @@ export interface Group {
401
434
  color?: string;
402
435
  archived?: boolean;
403
436
  mutedUntil?: number;
404
- inviteLink?: string;
405
437
  permissionAddMember?: 'EVERY_MEMBER' | 'ONLY_ADMINS';
406
438
  permissionEditDetails?: 'EVERY_MEMBER' | 'ONLY_ADMINS';
407
439
  permissionSendMessage?: 'EVERY_MEMBER' | 'ONLY_ADMINS';
408
440
  announcementsOnly?: boolean;
409
441
  expirationTimer?: number;
442
+ /** Group version (v1 or v2) */
443
+ version?: number;
444
+ /** Group master key for v2 groups */
445
+ masterKey?: string;
410
446
  }
411
447
  /**
412
448
  * Options for updating a Signal group.
@@ -469,15 +505,18 @@ export interface SendMessageOptions {
469
505
  text?: string;
470
506
  attachments?: Attachment[];
471
507
  mentions?: Mention[];
508
+ textStyles?: TextStyle[];
472
509
  };
473
510
  sticker?: Sticker;
474
511
  expiresInSeconds?: number;
475
512
  isViewOnce?: boolean;
476
513
  linkPreview?: boolean;
477
- editMessage?: {
478
- timestamp: number;
479
- dataMessage: any;
480
- };
514
+ previewUrl?: string;
515
+ editTimestamp?: number;
516
+ storyTimestamp?: number;
517
+ storyAuthor?: string;
518
+ noteToSelf?: boolean;
519
+ endSession?: boolean;
481
520
  }
482
521
  /**
483
522
  * Represents a user profile.
@@ -583,9 +622,10 @@ export interface PinConfiguration {
583
622
  export interface IdentityKey {
584
623
  number: string;
585
624
  identityKey: string;
586
- trustLevel: 'TRUSTED_UNVERIFIED' | 'TRUSTED_VERIFIED' | 'UNTRUSTED';
587
- addedDate: number;
588
- firstUse: boolean;
625
+ safetyNumber?: string;
626
+ trustLevel?: 'TRUSTED_UNVERIFIED' | 'TRUSTED_VERIFIED' | 'UNTRUSTED' | 'TRUST_ON_FIRST_USE';
627
+ addedDate?: number;
628
+ firstUse?: boolean;
589
629
  }
590
630
  /**
591
631
  * Represents a user status (registered/unregistered).
@@ -1055,6 +1095,30 @@ export interface AccountUpdateResult {
1055
1095
  /** Error message if failed */
1056
1096
  error?: string;
1057
1097
  }
1098
+ /**
1099
+ * Options for updating a linked device
1100
+ */
1101
+ export interface UpdateDeviceOptions {
1102
+ /** Device ID to update */
1103
+ deviceId: number;
1104
+ /** New device name */
1105
+ deviceName: string;
1106
+ }
1107
+ /**
1108
+ * Options for receiving messages
1109
+ */
1110
+ export interface ReceiveOptions {
1111
+ /** Timeout in seconds (default: 5) */
1112
+ timeout?: number;
1113
+ /** Maximum number of messages to receive (default: unlimited) */
1114
+ maxMessages?: number;
1115
+ /** Skip downloading attachments */
1116
+ ignoreAttachments?: boolean;
1117
+ /** Skip receiving stories */
1118
+ ignoreStories?: boolean;
1119
+ /** Send read receipts automatically */
1120
+ sendReadReceipts?: boolean;
1121
+ }
1058
1122
  /**
1059
1123
  * Options for sending contacts sync
1060
1124
  */
package/dist/retry.js CHANGED
@@ -70,14 +70,31 @@ async function withRetry(operation, options = {}) {
70
70
  * @returns Result of the promise
71
71
  */
72
72
  async function withTimeout(promise, timeoutMs) {
73
- return Promise.race([
74
- promise,
75
- new Promise((_, reject) => {
76
- setTimeout(() => {
77
- reject(new errors_1.TimeoutError(`Operation timed out after ${timeoutMs}ms`));
78
- }, timeoutMs);
79
- })
80
- ]);
73
+ let timeoutHandle = null;
74
+ const timeoutPromise = new Promise((_, reject) => {
75
+ timeoutHandle = setTimeout(() => {
76
+ reject(new errors_1.TimeoutError(`Operation timed out after ${timeoutMs}ms`));
77
+ }, timeoutMs);
78
+ // Use unref() to prevent this timer from keeping the process alive
79
+ if (timeoutHandle.unref) {
80
+ timeoutHandle.unref();
81
+ }
82
+ });
83
+ try {
84
+ const result = await Promise.race([promise, timeoutPromise]);
85
+ // Clear the timeout if the promise resolves first
86
+ if (timeoutHandle) {
87
+ clearTimeout(timeoutHandle);
88
+ }
89
+ return result;
90
+ }
91
+ catch (error) {
92
+ // Clear the timeout if the promise rejects
93
+ if (timeoutHandle) {
94
+ clearTimeout(timeoutHandle);
95
+ }
96
+ throw error;
97
+ }
81
98
  }
82
99
  /**
83
100
  * Sleep for a specified duration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signal-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "A comprehensive TypeScript SDK for Signal Messenger with native JSON-RPC support, providing high-performance messaging, bot framework, and full signal-cli integration.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -3,7 +3,7 @@ const fs = require('fs');
3
3
  const path = require('path');
4
4
  const tar = require('tar');
5
5
 
6
- const VERSION = '0.13.22';
6
+ const VERSION = '0.13.23';
7
7
  const BASE_URL = `https://github.com/AsamK/signal-cli/releases/download/v${VERSION}`;
8
8
 
9
9
  const platform = process.platform;