signal-sdk 0.0.9 → 0.1.0
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 +175 -59
- package/dist/SignalCli.d.ts +72 -2
- package/dist/SignalCli.js +257 -1
- package/dist/__tests__/SignalBot.additional.test.d.ts +5 -0
- package/dist/__tests__/SignalBot.additional.test.js +333 -0
- package/dist/__tests__/SignalCli.integration.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.integration.test.js +218 -0
- package/dist/__tests__/SignalCli.methods.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.methods.test.js +470 -0
- package/dist/__tests__/SignalCli.test.js +244 -0
- 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 +67 -0
- package/dist/config.js +111 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.js +75 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/interfaces.d.ts +136 -1
- package/dist/interfaces.js +1 -1
- package/dist/retry.d.ts +56 -0
- package/dist/retry.js +135 -0
- package/dist/validators.d.ts +59 -0
- package/dist/validators.js +170 -0
- package/package.json +1 -1
package/dist/config.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration management for Signal SDK
|
|
4
|
+
* Provides centralized configuration with validation
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.Logger = exports.DEFAULT_LOGGER_CONFIG = exports.DEFAULT_CONFIG = void 0;
|
|
8
|
+
exports.validateConfig = validateConfig;
|
|
9
|
+
exports.DEFAULT_CONFIG = {
|
|
10
|
+
signalCliPath: '',
|
|
11
|
+
account: '',
|
|
12
|
+
connectionTimeout: 30000,
|
|
13
|
+
requestTimeout: 60000,
|
|
14
|
+
enableRetry: true,
|
|
15
|
+
maxRetries: 3,
|
|
16
|
+
retryDelay: 1000,
|
|
17
|
+
verbose: false,
|
|
18
|
+
logFile: '',
|
|
19
|
+
maxConcurrentRequests: 5,
|
|
20
|
+
minRequestInterval: 100,
|
|
21
|
+
autoReconnect: true,
|
|
22
|
+
trustNewIdentities: 'on-first-use',
|
|
23
|
+
disableSendLog: false
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Validates and merges configuration with defaults
|
|
27
|
+
* @param userConfig User-provided configuration
|
|
28
|
+
* @returns Validated configuration
|
|
29
|
+
*/
|
|
30
|
+
function validateConfig(userConfig = {}) {
|
|
31
|
+
const config = { ...exports.DEFAULT_CONFIG, ...userConfig };
|
|
32
|
+
// Validate numeric values
|
|
33
|
+
if (config.connectionTimeout < 0) {
|
|
34
|
+
throw new Error('connectionTimeout must be non-negative');
|
|
35
|
+
}
|
|
36
|
+
if (config.requestTimeout < 0) {
|
|
37
|
+
throw new Error('requestTimeout must be non-negative');
|
|
38
|
+
}
|
|
39
|
+
if (config.maxRetries < 0) {
|
|
40
|
+
throw new Error('maxRetries must be non-negative');
|
|
41
|
+
}
|
|
42
|
+
if (config.retryDelay < 0) {
|
|
43
|
+
throw new Error('retryDelay must be non-negative');
|
|
44
|
+
}
|
|
45
|
+
if (config.maxConcurrentRequests < 1) {
|
|
46
|
+
throw new Error('maxConcurrentRequests must be at least 1');
|
|
47
|
+
}
|
|
48
|
+
if (config.minRequestInterval < 0) {
|
|
49
|
+
throw new Error('minRequestInterval must be non-negative');
|
|
50
|
+
}
|
|
51
|
+
return config;
|
|
52
|
+
}
|
|
53
|
+
exports.DEFAULT_LOGGER_CONFIG = {
|
|
54
|
+
level: 'info',
|
|
55
|
+
enableConsole: true,
|
|
56
|
+
enableFile: false,
|
|
57
|
+
includeTimestamp: true,
|
|
58
|
+
includeLevel: true
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Simple logger implementation
|
|
62
|
+
*/
|
|
63
|
+
class Logger {
|
|
64
|
+
constructor(config = {}) {
|
|
65
|
+
this.levels = {
|
|
66
|
+
debug: 0,
|
|
67
|
+
info: 1,
|
|
68
|
+
warn: 2,
|
|
69
|
+
error: 3
|
|
70
|
+
};
|
|
71
|
+
this.config = { ...exports.DEFAULT_LOGGER_CONFIG, ...config };
|
|
72
|
+
}
|
|
73
|
+
shouldLog(level) {
|
|
74
|
+
return this.levels[level] >= this.levels[this.config.level];
|
|
75
|
+
}
|
|
76
|
+
format(level, message, data) {
|
|
77
|
+
const parts = [];
|
|
78
|
+
if (this.config.includeTimestamp) {
|
|
79
|
+
parts.push(`[${new Date().toISOString()}]`);
|
|
80
|
+
}
|
|
81
|
+
if (this.config.includeLevel) {
|
|
82
|
+
parts.push(`[${level.toUpperCase()}]`);
|
|
83
|
+
}
|
|
84
|
+
parts.push(message);
|
|
85
|
+
if (data !== undefined) {
|
|
86
|
+
parts.push(JSON.stringify(data, null, 2));
|
|
87
|
+
}
|
|
88
|
+
return parts.join(' ');
|
|
89
|
+
}
|
|
90
|
+
debug(message, data) {
|
|
91
|
+
if (this.shouldLog('debug') && this.config.enableConsole) {
|
|
92
|
+
console.debug(this.format('debug', message, data));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
info(message, data) {
|
|
96
|
+
if (this.shouldLog('info') && this.config.enableConsole) {
|
|
97
|
+
console.info(this.format('info', message, data));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
warn(message, data) {
|
|
101
|
+
if (this.shouldLog('warn') && this.config.enableConsole) {
|
|
102
|
+
console.warn(this.format('warn', message, data));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
error(message, data) {
|
|
106
|
+
if (this.shouldLog('error') && this.config.enableConsole) {
|
|
107
|
+
console.error(this.format('error', message, data));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.Logger = Logger;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error classes for Signal SDK
|
|
3
|
+
* Provides typed errors for better error handling
|
|
4
|
+
*/
|
|
5
|
+
export declare class SignalError extends Error {
|
|
6
|
+
code?: string | undefined;
|
|
7
|
+
constructor(message: string, code?: string | undefined);
|
|
8
|
+
}
|
|
9
|
+
export declare class ConnectionError extends SignalError {
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class AuthenticationError extends SignalError {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
export declare class RateLimitError extends SignalError {
|
|
16
|
+
retryAfter?: number | undefined;
|
|
17
|
+
challenge?: string | undefined;
|
|
18
|
+
constructor(message: string, retryAfter?: number | undefined, challenge?: string | undefined);
|
|
19
|
+
}
|
|
20
|
+
export declare class ValidationError extends SignalError {
|
|
21
|
+
field?: string | undefined;
|
|
22
|
+
constructor(message: string, field?: string | undefined);
|
|
23
|
+
}
|
|
24
|
+
export declare class TimeoutError extends SignalError {
|
|
25
|
+
constructor(message?: string);
|
|
26
|
+
}
|
|
27
|
+
export declare class GroupError extends SignalError {
|
|
28
|
+
constructor(message: string);
|
|
29
|
+
}
|
|
30
|
+
export declare class MessageError extends SignalError {
|
|
31
|
+
constructor(message: string);
|
|
32
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Custom error classes for Signal SDK
|
|
4
|
+
* Provides typed errors for better error handling
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.MessageError = exports.GroupError = exports.TimeoutError = exports.ValidationError = exports.RateLimitError = exports.AuthenticationError = exports.ConnectionError = exports.SignalError = void 0;
|
|
8
|
+
class SignalError extends Error {
|
|
9
|
+
constructor(message, code) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.name = 'SignalError';
|
|
13
|
+
Object.setPrototypeOf(this, SignalError.prototype);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.SignalError = SignalError;
|
|
17
|
+
class ConnectionError extends SignalError {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message, 'CONNECTION_ERROR');
|
|
20
|
+
this.name = 'ConnectionError';
|
|
21
|
+
Object.setPrototypeOf(this, ConnectionError.prototype);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.ConnectionError = ConnectionError;
|
|
25
|
+
class AuthenticationError extends SignalError {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message, 'AUTH_ERROR');
|
|
28
|
+
this.name = 'AuthenticationError';
|
|
29
|
+
Object.setPrototypeOf(this, AuthenticationError.prototype);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.AuthenticationError = AuthenticationError;
|
|
33
|
+
class RateLimitError extends SignalError {
|
|
34
|
+
constructor(message, retryAfter, challenge) {
|
|
35
|
+
super(message, 'RATE_LIMIT');
|
|
36
|
+
this.retryAfter = retryAfter;
|
|
37
|
+
this.challenge = challenge;
|
|
38
|
+
this.name = 'RateLimitError';
|
|
39
|
+
Object.setPrototypeOf(this, RateLimitError.prototype);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.RateLimitError = RateLimitError;
|
|
43
|
+
class ValidationError extends SignalError {
|
|
44
|
+
constructor(message, field) {
|
|
45
|
+
super(message, 'VALIDATION_ERROR');
|
|
46
|
+
this.field = field;
|
|
47
|
+
this.name = 'ValidationError';
|
|
48
|
+
Object.setPrototypeOf(this, ValidationError.prototype);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.ValidationError = ValidationError;
|
|
52
|
+
class TimeoutError extends SignalError {
|
|
53
|
+
constructor(message = 'Operation timed out') {
|
|
54
|
+
super(message, 'TIMEOUT');
|
|
55
|
+
this.name = 'TimeoutError';
|
|
56
|
+
Object.setPrototypeOf(this, TimeoutError.prototype);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.TimeoutError = TimeoutError;
|
|
60
|
+
class GroupError extends SignalError {
|
|
61
|
+
constructor(message) {
|
|
62
|
+
super(message, 'GROUP_ERROR');
|
|
63
|
+
this.name = 'GroupError';
|
|
64
|
+
Object.setPrototypeOf(this, GroupError.prototype);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.GroupError = GroupError;
|
|
68
|
+
class MessageError extends SignalError {
|
|
69
|
+
constructor(message) {
|
|
70
|
+
super(message, 'MESSAGE_ERROR');
|
|
71
|
+
this.name = 'MessageError';
|
|
72
|
+
Object.setPrototypeOf(this, MessageError.prototype);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.MessageError = MessageError;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -20,3 +20,7 @@ Object.defineProperty(exports, "SignalCli", { enumerable: true, get: function ()
|
|
|
20
20
|
var SignalBot_1 = require("./SignalBot");
|
|
21
21
|
Object.defineProperty(exports, "SignalBot", { enumerable: true, get: function () { return SignalBot_1.SignalBot; } });
|
|
22
22
|
__exportStar(require("./interfaces"), exports);
|
|
23
|
+
__exportStar(require("./errors"), exports);
|
|
24
|
+
__exportStar(require("./validators"), exports);
|
|
25
|
+
__exportStar(require("./retry"), exports);
|
|
26
|
+
__exportStar(require("./config"), exports);
|
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.
|
|
@@ -936,3 +936,138 @@ export interface UploadProgress {
|
|
|
936
936
|
/** Estimated time remaining in seconds */
|
|
937
937
|
timeRemaining?: number;
|
|
938
938
|
}
|
|
939
|
+
/**
|
|
940
|
+
* Options for creating a poll
|
|
941
|
+
*/
|
|
942
|
+
export interface PollCreateOptions {
|
|
943
|
+
/** The poll question */
|
|
944
|
+
question: string;
|
|
945
|
+
/** Array of poll options */
|
|
946
|
+
options: string[];
|
|
947
|
+
/** Allow multiple selections (default: true) */
|
|
948
|
+
multiSelect?: boolean;
|
|
949
|
+
/** Recipients to send the poll to */
|
|
950
|
+
recipients?: string[];
|
|
951
|
+
/** Group ID to send the poll to */
|
|
952
|
+
groupId?: string;
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Options for voting on a poll
|
|
956
|
+
*/
|
|
957
|
+
export interface PollVoteOptions {
|
|
958
|
+
/** Author of the poll */
|
|
959
|
+
pollAuthor: string;
|
|
960
|
+
/** Timestamp of the poll message */
|
|
961
|
+
pollTimestamp: number;
|
|
962
|
+
/** Array of option indexes to vote for */
|
|
963
|
+
optionIndexes: number[];
|
|
964
|
+
/** Vote count (increase for each vote) */
|
|
965
|
+
voteCount?: number;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Options for terminating a poll
|
|
969
|
+
*/
|
|
970
|
+
export interface PollTerminateOptions {
|
|
971
|
+
/** Timestamp of the poll message to terminate */
|
|
972
|
+
pollTimestamp: number;
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Options for sending a story
|
|
976
|
+
*/
|
|
977
|
+
export interface StoryOptions {
|
|
978
|
+
/** Story content (text or attachment path) */
|
|
979
|
+
content?: string;
|
|
980
|
+
/** Attachment for the story */
|
|
981
|
+
attachment?: string;
|
|
982
|
+
/** Text attachment with style */
|
|
983
|
+
textAttachment?: {
|
|
984
|
+
text: string;
|
|
985
|
+
textStyle?: 'DEFAULT' | 'REGULAR' | 'BOLD' | 'SERIF' | 'SCRIPT' | 'CONDENSED';
|
|
986
|
+
textForegroundColor?: string;
|
|
987
|
+
textBackgroundColor?: string;
|
|
988
|
+
preview?: {
|
|
989
|
+
url: string;
|
|
990
|
+
title?: string;
|
|
991
|
+
description?: string;
|
|
992
|
+
};
|
|
993
|
+
};
|
|
994
|
+
/** Allow replies (default: true) */
|
|
995
|
+
allowReplies?: boolean;
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Options for getting attachment data
|
|
999
|
+
*/
|
|
1000
|
+
export interface GetAttachmentOptions {
|
|
1001
|
+
/** Attachment ID */
|
|
1002
|
+
id: string;
|
|
1003
|
+
/** Recipient who sent the attachment */
|
|
1004
|
+
recipient?: string;
|
|
1005
|
+
/** Group ID where attachment was sent */
|
|
1006
|
+
groupId?: string;
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Options for getting avatar data
|
|
1010
|
+
*/
|
|
1011
|
+
export interface GetAvatarOptions {
|
|
1012
|
+
/** Contact number for contact avatar */
|
|
1013
|
+
contact?: string;
|
|
1014
|
+
/** Profile number for profile avatar */
|
|
1015
|
+
profile?: string;
|
|
1016
|
+
/** Group ID for group avatar */
|
|
1017
|
+
groupId?: string;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Options for getting sticker data
|
|
1021
|
+
*/
|
|
1022
|
+
export interface GetStickerOptions {
|
|
1023
|
+
/** Sticker pack ID (hex encoded) */
|
|
1024
|
+
packId: string;
|
|
1025
|
+
/** Sticker index in the pack */
|
|
1026
|
+
stickerId: number;
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Options for updating account information
|
|
1030
|
+
*/
|
|
1031
|
+
export interface UpdateAccountOptions {
|
|
1032
|
+
/** New device name */
|
|
1033
|
+
deviceName?: string;
|
|
1034
|
+
/** Username to set (with or without discriminator) */
|
|
1035
|
+
username?: string;
|
|
1036
|
+
/** Delete the current username */
|
|
1037
|
+
deleteUsername?: boolean;
|
|
1038
|
+
/** Enable unrestricted unidentified sender */
|
|
1039
|
+
unrestrictedUnidentifiedSender?: boolean;
|
|
1040
|
+
/** Enable discoverability by phone number */
|
|
1041
|
+
discoverableByNumber?: boolean;
|
|
1042
|
+
/** Enable number sharing */
|
|
1043
|
+
numberSharing?: boolean;
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Result from account update with username
|
|
1047
|
+
*/
|
|
1048
|
+
export interface AccountUpdateResult {
|
|
1049
|
+
/** Success status */
|
|
1050
|
+
success: boolean;
|
|
1051
|
+
/** New username with discriminator */
|
|
1052
|
+
username?: string;
|
|
1053
|
+
/** Username link URL */
|
|
1054
|
+
usernameLink?: string;
|
|
1055
|
+
/** Error message if failed */
|
|
1056
|
+
error?: string;
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Options for sending contacts sync
|
|
1060
|
+
*/
|
|
1061
|
+
export interface SendContactsOptions {
|
|
1062
|
+
/** Include all recipients, not just contacts */
|
|
1063
|
+
includeAllRecipients?: boolean;
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Options for listing groups
|
|
1067
|
+
*/
|
|
1068
|
+
export interface ListGroupsOptions {
|
|
1069
|
+
/** Show detailed information */
|
|
1070
|
+
detailed?: boolean;
|
|
1071
|
+
/** Filter by specific group IDs */
|
|
1072
|
+
groupIds?: string[];
|
|
1073
|
+
}
|
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,135 @@
|
|
|
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
|
+
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
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Sleep for a specified duration
|
|
84
|
+
* @param ms Duration in milliseconds
|
|
85
|
+
*/
|
|
86
|
+
function sleep(ms) {
|
|
87
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Rate limiter to prevent exceeding API limits
|
|
91
|
+
*/
|
|
92
|
+
class RateLimiter {
|
|
93
|
+
constructor(maxConcurrent = 5, minInterval = 100) {
|
|
94
|
+
this.maxConcurrent = maxConcurrent;
|
|
95
|
+
this.minInterval = minInterval;
|
|
96
|
+
this.queue = [];
|
|
97
|
+
this.activeRequests = 0;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Execute an operation with rate limiting
|
|
101
|
+
* @param operation Function to execute
|
|
102
|
+
* @returns Result of the operation
|
|
103
|
+
*/
|
|
104
|
+
async execute(operation) {
|
|
105
|
+
// Wait for slot and reserve it
|
|
106
|
+
await this.reserveSlot();
|
|
107
|
+
try {
|
|
108
|
+
const result = await operation();
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
// Wait minimum interval before allowing next request
|
|
113
|
+
await sleep(this.minInterval);
|
|
114
|
+
// Release slot
|
|
115
|
+
this.activeRequests--;
|
|
116
|
+
const next = this.queue.shift();
|
|
117
|
+
if (next) {
|
|
118
|
+
next();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
reserveSlot() {
|
|
123
|
+
if (this.activeRequests < this.maxConcurrent) {
|
|
124
|
+
this.activeRequests++;
|
|
125
|
+
return Promise.resolve();
|
|
126
|
+
}
|
|
127
|
+
return new Promise(resolve => {
|
|
128
|
+
this.queue.push(() => {
|
|
129
|
+
this.activeRequests++;
|
|
130
|
+
resolve();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
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;
|