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
package/dist/interfaces.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* which uses JSON-RPC communication with signal-cli for optimal performance.
|
|
6
6
|
*
|
|
7
7
|
* @author Signal SDK Team
|
|
8
|
-
* @version
|
|
8
|
+
* @version 0.1.0
|
|
9
9
|
*/
|
|
10
10
|
/**
|
|
11
11
|
* @deprecated This interface is no longer used since switching to JSON-RPC format.
|
|
@@ -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
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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).
|
|
@@ -936,3 +976,153 @@ export interface UploadProgress {
|
|
|
936
976
|
/** Estimated time remaining in seconds */
|
|
937
977
|
timeRemaining?: number;
|
|
938
978
|
}
|
|
979
|
+
/**
|
|
980
|
+
* Options for creating a poll
|
|
981
|
+
*/
|
|
982
|
+
export interface PollCreateOptions {
|
|
983
|
+
/** The poll question */
|
|
984
|
+
question: string;
|
|
985
|
+
/** Array of poll options */
|
|
986
|
+
options: string[];
|
|
987
|
+
/** Allow multiple selections (default: true) */
|
|
988
|
+
multiSelect?: boolean;
|
|
989
|
+
/** Recipients to send the poll to */
|
|
990
|
+
recipients?: string[];
|
|
991
|
+
/** Group ID to send the poll to */
|
|
992
|
+
groupId?: string;
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Options for voting on a poll
|
|
996
|
+
*/
|
|
997
|
+
export interface PollVoteOptions {
|
|
998
|
+
/** Author of the poll */
|
|
999
|
+
pollAuthor: string;
|
|
1000
|
+
/** Timestamp of the poll message */
|
|
1001
|
+
pollTimestamp: number;
|
|
1002
|
+
/** Array of option indexes to vote for */
|
|
1003
|
+
optionIndexes: number[];
|
|
1004
|
+
/** Vote count (increase for each vote) */
|
|
1005
|
+
voteCount?: number;
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Options for terminating a poll
|
|
1009
|
+
*/
|
|
1010
|
+
export interface PollTerminateOptions {
|
|
1011
|
+
/** Timestamp of the poll message to terminate */
|
|
1012
|
+
pollTimestamp: number;
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Options for sending a story
|
|
1016
|
+
*/
|
|
1017
|
+
export interface StoryOptions {
|
|
1018
|
+
/** Story content (text or attachment path) */
|
|
1019
|
+
content?: string;
|
|
1020
|
+
/** Attachment for the story */
|
|
1021
|
+
attachment?: string;
|
|
1022
|
+
/** Text attachment with style */
|
|
1023
|
+
textAttachment?: {
|
|
1024
|
+
text: string;
|
|
1025
|
+
textStyle?: 'DEFAULT' | 'REGULAR' | 'BOLD' | 'SERIF' | 'SCRIPT' | 'CONDENSED';
|
|
1026
|
+
textForegroundColor?: string;
|
|
1027
|
+
textBackgroundColor?: string;
|
|
1028
|
+
preview?: {
|
|
1029
|
+
url: string;
|
|
1030
|
+
title?: string;
|
|
1031
|
+
description?: string;
|
|
1032
|
+
};
|
|
1033
|
+
};
|
|
1034
|
+
/** Allow replies (default: true) */
|
|
1035
|
+
allowReplies?: boolean;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Options for getting attachment data
|
|
1039
|
+
*/
|
|
1040
|
+
export interface GetAttachmentOptions {
|
|
1041
|
+
/** Attachment ID */
|
|
1042
|
+
id: string;
|
|
1043
|
+
/** Recipient who sent the attachment */
|
|
1044
|
+
recipient?: string;
|
|
1045
|
+
/** Group ID where attachment was sent */
|
|
1046
|
+
groupId?: string;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Options for getting avatar data
|
|
1050
|
+
*/
|
|
1051
|
+
export interface GetAvatarOptions {
|
|
1052
|
+
/** Contact number for contact avatar */
|
|
1053
|
+
contact?: string;
|
|
1054
|
+
/** Profile number for profile avatar */
|
|
1055
|
+
profile?: string;
|
|
1056
|
+
/** Group ID for group avatar */
|
|
1057
|
+
groupId?: string;
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Options for getting sticker data
|
|
1061
|
+
*/
|
|
1062
|
+
export interface GetStickerOptions {
|
|
1063
|
+
/** Sticker pack ID (hex encoded) */
|
|
1064
|
+
packId: string;
|
|
1065
|
+
/** Sticker index in the pack */
|
|
1066
|
+
stickerId: number;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Options for updating account information
|
|
1070
|
+
*/
|
|
1071
|
+
export interface UpdateAccountOptions {
|
|
1072
|
+
/** New device name */
|
|
1073
|
+
deviceName?: string;
|
|
1074
|
+
/** Username to set (with or without discriminator) */
|
|
1075
|
+
username?: string;
|
|
1076
|
+
/** Delete the current username */
|
|
1077
|
+
deleteUsername?: boolean;
|
|
1078
|
+
/** Enable unrestricted unidentified sender */
|
|
1079
|
+
unrestrictedUnidentifiedSender?: boolean;
|
|
1080
|
+
/** Enable discoverability by phone number */
|
|
1081
|
+
discoverableByNumber?: boolean;
|
|
1082
|
+
/** Enable number sharing */
|
|
1083
|
+
numberSharing?: boolean;
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Result from account update with username
|
|
1087
|
+
*/
|
|
1088
|
+
export interface AccountUpdateResult {
|
|
1089
|
+
/** Success status */
|
|
1090
|
+
success: boolean;
|
|
1091
|
+
/** New username with discriminator */
|
|
1092
|
+
username?: string;
|
|
1093
|
+
/** Username link URL */
|
|
1094
|
+
usernameLink?: string;
|
|
1095
|
+
/** Error message if failed */
|
|
1096
|
+
error?: string;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Options for receiving messages
|
|
1100
|
+
*/
|
|
1101
|
+
export interface ReceiveOptions {
|
|
1102
|
+
/** Timeout in seconds (default: 5) */
|
|
1103
|
+
timeout?: number;
|
|
1104
|
+
/** Maximum number of messages to receive (default: unlimited) */
|
|
1105
|
+
maxMessages?: number;
|
|
1106
|
+
/** Skip downloading attachments */
|
|
1107
|
+
ignoreAttachments?: boolean;
|
|
1108
|
+
/** Skip receiving stories */
|
|
1109
|
+
ignoreStories?: boolean;
|
|
1110
|
+
/** Send read receipts automatically */
|
|
1111
|
+
sendReadReceipts?: boolean;
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Options for sending contacts sync
|
|
1115
|
+
*/
|
|
1116
|
+
export interface SendContactsOptions {
|
|
1117
|
+
/** Include all recipients, not just contacts */
|
|
1118
|
+
includeAllRecipients?: boolean;
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Options for listing groups
|
|
1122
|
+
*/
|
|
1123
|
+
export interface ListGroupsOptions {
|
|
1124
|
+
/** Show detailed information */
|
|
1125
|
+
detailed?: boolean;
|
|
1126
|
+
/** Filter by specific group IDs */
|
|
1127
|
+
groupIds?: string[];
|
|
1128
|
+
}
|
package/dist/interfaces.js
CHANGED
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry utilities with exponential backoff
|
|
3
|
+
* Provides robust retry mechanisms for operations
|
|
4
|
+
*/
|
|
5
|
+
export interface RetryOptions {
|
|
6
|
+
/** Maximum number of retry attempts */
|
|
7
|
+
maxAttempts?: number;
|
|
8
|
+
/** Initial delay in milliseconds */
|
|
9
|
+
initialDelay?: number;
|
|
10
|
+
/** Maximum delay in milliseconds */
|
|
11
|
+
maxDelay?: number;
|
|
12
|
+
/** Multiplier for exponential backoff */
|
|
13
|
+
backoffMultiplier?: number;
|
|
14
|
+
/** Timeout for each attempt in milliseconds */
|
|
15
|
+
timeout?: number;
|
|
16
|
+
/** Function to determine if error is retryable */
|
|
17
|
+
isRetryable?: (error: any) => boolean;
|
|
18
|
+
/** Callback for each retry attempt */
|
|
19
|
+
onRetry?: (attempt: number, error: any) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Executes an operation with retry logic and exponential backoff
|
|
23
|
+
* @param operation Function to execute
|
|
24
|
+
* @param options Retry configuration options
|
|
25
|
+
* @returns Result of the operation
|
|
26
|
+
*/
|
|
27
|
+
export declare function withRetry<T>(operation: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Executes an operation with a timeout
|
|
30
|
+
* @param promise Promise to execute
|
|
31
|
+
* @param timeoutMs Timeout in milliseconds
|
|
32
|
+
* @returns Result of the promise
|
|
33
|
+
*/
|
|
34
|
+
export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T>;
|
|
35
|
+
/**
|
|
36
|
+
* Sleep for a specified duration
|
|
37
|
+
* @param ms Duration in milliseconds
|
|
38
|
+
*/
|
|
39
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Rate limiter to prevent exceeding API limits
|
|
42
|
+
*/
|
|
43
|
+
export declare class RateLimiter {
|
|
44
|
+
private maxConcurrent;
|
|
45
|
+
private minInterval;
|
|
46
|
+
private queue;
|
|
47
|
+
private activeRequests;
|
|
48
|
+
constructor(maxConcurrent?: number, minInterval?: number);
|
|
49
|
+
/**
|
|
50
|
+
* Execute an operation with rate limiting
|
|
51
|
+
* @param operation Function to execute
|
|
52
|
+
* @returns Result of the operation
|
|
53
|
+
*/
|
|
54
|
+
execute<T>(operation: () => Promise<T>): Promise<T>;
|
|
55
|
+
private reserveSlot;
|
|
56
|
+
}
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Retry utilities with exponential backoff
|
|
4
|
+
* Provides robust retry mechanisms for operations
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.RateLimiter = void 0;
|
|
8
|
+
exports.withRetry = withRetry;
|
|
9
|
+
exports.withTimeout = withTimeout;
|
|
10
|
+
exports.sleep = sleep;
|
|
11
|
+
const errors_1 = require("./errors");
|
|
12
|
+
const DEFAULT_RETRY_OPTIONS = {
|
|
13
|
+
maxAttempts: 3,
|
|
14
|
+
initialDelay: 1000,
|
|
15
|
+
maxDelay: 30000,
|
|
16
|
+
backoffMultiplier: 2,
|
|
17
|
+
timeout: 60000,
|
|
18
|
+
isRetryable: (error) => {
|
|
19
|
+
// Retry on connection errors, timeouts, and certain server errors
|
|
20
|
+
if (!error)
|
|
21
|
+
return false;
|
|
22
|
+
const errorMessage = error.message?.toLowerCase() || '';
|
|
23
|
+
const isConnectionError = errorMessage.includes('connection') ||
|
|
24
|
+
errorMessage.includes('timeout') ||
|
|
25
|
+
errorMessage.includes('econnrefused') ||
|
|
26
|
+
errorMessage.includes('econnreset');
|
|
27
|
+
const isServerError = error.code === 500 || error.code === 502 || error.code === 503;
|
|
28
|
+
// Don't retry on authentication or validation errors
|
|
29
|
+
const isClientError = error.code === 401 || error.code === 403 || error.code === 400;
|
|
30
|
+
return (isConnectionError || isServerError) && !isClientError;
|
|
31
|
+
},
|
|
32
|
+
onRetry: () => { }
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Executes an operation with retry logic and exponential backoff
|
|
36
|
+
* @param operation Function to execute
|
|
37
|
+
* @param options Retry configuration options
|
|
38
|
+
* @returns Result of the operation
|
|
39
|
+
*/
|
|
40
|
+
async function withRetry(operation, options = {}) {
|
|
41
|
+
const config = { ...DEFAULT_RETRY_OPTIONS, ...options };
|
|
42
|
+
let lastError;
|
|
43
|
+
for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
|
|
44
|
+
try {
|
|
45
|
+
// Execute with timeout
|
|
46
|
+
const result = await withTimeout(operation(), config.timeout);
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
lastError = error;
|
|
51
|
+
// Check if we should retry
|
|
52
|
+
const shouldRetry = attempt < config.maxAttempts && config.isRetryable(error);
|
|
53
|
+
if (!shouldRetry) {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
// Calculate delay with exponential backoff
|
|
57
|
+
const delay = Math.min(config.initialDelay * Math.pow(config.backoffMultiplier, attempt - 1), config.maxDelay);
|
|
58
|
+
// Notify about retry
|
|
59
|
+
config.onRetry(attempt, error);
|
|
60
|
+
// Wait before retrying
|
|
61
|
+
await sleep(delay);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw lastError;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Executes an operation with a timeout
|
|
68
|
+
* @param promise Promise to execute
|
|
69
|
+
* @param timeoutMs Timeout in milliseconds
|
|
70
|
+
* @returns Result of the promise
|
|
71
|
+
*/
|
|
72
|
+
async function withTimeout(promise, timeoutMs) {
|
|
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
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Sleep for a specified duration
|
|
101
|
+
* @param ms Duration in milliseconds
|
|
102
|
+
*/
|
|
103
|
+
function sleep(ms) {
|
|
104
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Rate limiter to prevent exceeding API limits
|
|
108
|
+
*/
|
|
109
|
+
class RateLimiter {
|
|
110
|
+
constructor(maxConcurrent = 5, minInterval = 100) {
|
|
111
|
+
this.maxConcurrent = maxConcurrent;
|
|
112
|
+
this.minInterval = minInterval;
|
|
113
|
+
this.queue = [];
|
|
114
|
+
this.activeRequests = 0;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Execute an operation with rate limiting
|
|
118
|
+
* @param operation Function to execute
|
|
119
|
+
* @returns Result of the operation
|
|
120
|
+
*/
|
|
121
|
+
async execute(operation) {
|
|
122
|
+
// Wait for slot and reserve it
|
|
123
|
+
await this.reserveSlot();
|
|
124
|
+
try {
|
|
125
|
+
const result = await operation();
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
// Wait minimum interval before allowing next request
|
|
130
|
+
await sleep(this.minInterval);
|
|
131
|
+
// Release slot
|
|
132
|
+
this.activeRequests--;
|
|
133
|
+
const next = this.queue.shift();
|
|
134
|
+
if (next) {
|
|
135
|
+
next();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
reserveSlot() {
|
|
140
|
+
if (this.activeRequests < this.maxConcurrent) {
|
|
141
|
+
this.activeRequests++;
|
|
142
|
+
return Promise.resolve();
|
|
143
|
+
}
|
|
144
|
+
return new Promise(resolve => {
|
|
145
|
+
this.queue.push(() => {
|
|
146
|
+
this.activeRequests++;
|
|
147
|
+
resolve();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
exports.RateLimiter = RateLimiter;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input validation utilities for Signal SDK
|
|
3
|
+
* Provides strict validation for all inputs
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Validates a phone number format (E.164)
|
|
7
|
+
* @param phoneNumber Phone number to validate
|
|
8
|
+
* @throws ValidationError if invalid
|
|
9
|
+
*/
|
|
10
|
+
export declare function validatePhoneNumber(phoneNumber: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* Validates a group ID format
|
|
13
|
+
* @param groupId Group ID to validate
|
|
14
|
+
* @throws ValidationError if invalid
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateGroupId(groupId: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* Validates a recipient (phone number, UUID, or username)
|
|
19
|
+
* @param recipient Recipient to validate
|
|
20
|
+
* @throws ValidationError if invalid
|
|
21
|
+
*/
|
|
22
|
+
export declare function validateRecipient(recipient: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* Validates a message text
|
|
25
|
+
* @param message Message to validate
|
|
26
|
+
* @param maxLength Maximum message length
|
|
27
|
+
* @throws ValidationError if invalid
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateMessage(message: string, maxLength?: number): void;
|
|
30
|
+
/**
|
|
31
|
+
* Validates file attachments
|
|
32
|
+
* @param attachments Array of attachment paths
|
|
33
|
+
* @throws ValidationError if invalid
|
|
34
|
+
*/
|
|
35
|
+
export declare function validateAttachments(attachments: string[]): void;
|
|
36
|
+
/**
|
|
37
|
+
* Validates a timestamp
|
|
38
|
+
* @param timestamp Timestamp to validate
|
|
39
|
+
* @throws ValidationError if invalid
|
|
40
|
+
*/
|
|
41
|
+
export declare function validateTimestamp(timestamp: number): void;
|
|
42
|
+
/**
|
|
43
|
+
* Validates an emoji string
|
|
44
|
+
* @param emoji Emoji to validate
|
|
45
|
+
* @throws ValidationError if invalid
|
|
46
|
+
*/
|
|
47
|
+
export declare function validateEmoji(emoji: string): void;
|
|
48
|
+
/**
|
|
49
|
+
* Validates device ID
|
|
50
|
+
* @param deviceId Device ID to validate
|
|
51
|
+
* @throws ValidationError if invalid
|
|
52
|
+
*/
|
|
53
|
+
export declare function validateDeviceId(deviceId: number): void;
|
|
54
|
+
/**
|
|
55
|
+
* Sanitizes user input to prevent injection attacks
|
|
56
|
+
* @param input Input string to sanitize
|
|
57
|
+
* @returns Sanitized string
|
|
58
|
+
*/
|
|
59
|
+
export declare function sanitizeInput(input: string): string;
|