signal-sdk 0.0.8 → 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.
Files changed (36) hide show
  1. package/README.md +175 -59
  2. package/dist/SignalBot.d.ts +108 -0
  3. package/dist/SignalBot.js +811 -0
  4. package/dist/SignalCli.d.ts +205 -0
  5. package/dist/SignalCli.js +967 -0
  6. package/dist/__tests__/SignalBot.additional.test.d.ts +5 -0
  7. package/dist/__tests__/SignalBot.additional.test.js +333 -0
  8. package/dist/__tests__/SignalBot.test.d.ts +1 -0
  9. package/dist/__tests__/SignalBot.test.js +102 -0
  10. package/dist/__tests__/SignalCli.integration.test.d.ts +5 -0
  11. package/dist/__tests__/SignalCli.integration.test.js +218 -0
  12. package/dist/__tests__/SignalCli.methods.test.d.ts +5 -0
  13. package/dist/__tests__/SignalCli.methods.test.js +470 -0
  14. package/dist/__tests__/SignalCli.test.d.ts +1 -0
  15. package/dist/__tests__/SignalCli.test.js +479 -0
  16. package/dist/__tests__/config.test.d.ts +5 -0
  17. package/dist/__tests__/config.test.js +252 -0
  18. package/dist/__tests__/errors.test.d.ts +5 -0
  19. package/dist/__tests__/errors.test.js +276 -0
  20. package/dist/__tests__/retry.test.d.ts +4 -0
  21. package/dist/__tests__/retry.test.js +123 -0
  22. package/dist/__tests__/validators.test.d.ts +4 -0
  23. package/dist/__tests__/validators.test.js +147 -0
  24. package/dist/config.d.ts +67 -0
  25. package/dist/config.js +111 -0
  26. package/dist/errors.d.ts +32 -0
  27. package/dist/errors.js +75 -0
  28. package/dist/index.d.ts +7 -0
  29. package/dist/index.js +26 -0
  30. package/dist/interfaces.d.ts +1073 -0
  31. package/dist/interfaces.js +11 -0
  32. package/dist/retry.d.ts +56 -0
  33. package/dist/retry.js +135 -0
  34. package/dist/validators.d.ts +59 -0
  35. package/dist/validators.js +170 -0
  36. package/package.json +5 -6
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ /**
3
+ * Signal CLI SDK TypeScript Interfaces
4
+ *
5
+ * This file contains comprehensive type definitions for the Signal CLI SDK,
6
+ * which uses JSON-RPC communication with signal-cli for optimal performance.
7
+ *
8
+ * @author Signal SDK Team
9
+ * @version 0.1.0
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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;
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ /**
3
+ * Input validation utilities for Signal SDK
4
+ * Provides strict validation for all inputs
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.validatePhoneNumber = validatePhoneNumber;
8
+ exports.validateGroupId = validateGroupId;
9
+ exports.validateRecipient = validateRecipient;
10
+ exports.validateMessage = validateMessage;
11
+ exports.validateAttachments = validateAttachments;
12
+ exports.validateTimestamp = validateTimestamp;
13
+ exports.validateEmoji = validateEmoji;
14
+ exports.validateDeviceId = validateDeviceId;
15
+ exports.sanitizeInput = sanitizeInput;
16
+ const errors_1 = require("./errors");
17
+ /**
18
+ * Validates a phone number format (E.164)
19
+ * @param phoneNumber Phone number to validate
20
+ * @throws ValidationError if invalid
21
+ */
22
+ function validatePhoneNumber(phoneNumber) {
23
+ if (!phoneNumber) {
24
+ throw new errors_1.ValidationError('Phone number is required', 'phoneNumber');
25
+ }
26
+ if (typeof phoneNumber !== 'string') {
27
+ throw new errors_1.ValidationError('Phone number must be a string', 'phoneNumber');
28
+ }
29
+ // E.164 format: + followed by 1-15 digits
30
+ const e164Regex = /^\+[1-9]\d{1,14}$/;
31
+ if (!e164Regex.test(phoneNumber)) {
32
+ throw new errors_1.ValidationError('Phone number must be in E.164 format (e.g., +33123456789)', 'phoneNumber');
33
+ }
34
+ }
35
+ /**
36
+ * Validates a group ID format
37
+ * @param groupId Group ID to validate
38
+ * @throws ValidationError if invalid
39
+ */
40
+ function validateGroupId(groupId) {
41
+ if (!groupId) {
42
+ throw new errors_1.ValidationError('Group ID is required', 'groupId');
43
+ }
44
+ if (typeof groupId !== 'string') {
45
+ throw new errors_1.ValidationError('Group ID must be a string', 'groupId');
46
+ }
47
+ }
48
+ /**
49
+ * Validates a recipient (phone number, UUID, or username)
50
+ * @param recipient Recipient to validate
51
+ * @throws ValidationError if invalid
52
+ */
53
+ function validateRecipient(recipient) {
54
+ if (!recipient) {
55
+ throw new errors_1.ValidationError('Recipient is required', 'recipient');
56
+ }
57
+ if (typeof recipient !== 'string') {
58
+ throw new errors_1.ValidationError('Recipient must be a string', 'recipient');
59
+ }
60
+ // Check if it's a username (starts with u:)
61
+ if (recipient.startsWith('u:')) {
62
+ if (recipient.length < 3) {
63
+ throw new errors_1.ValidationError('Username is too short', 'recipient');
64
+ }
65
+ return;
66
+ }
67
+ // Check if it's a UUID (PNI: prefix or plain UUID)
68
+ const uuidRegex = /^(PNI:)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
69
+ if (uuidRegex.test(recipient)) {
70
+ return;
71
+ }
72
+ // Otherwise, validate as phone number
73
+ validatePhoneNumber(recipient);
74
+ }
75
+ /**
76
+ * Validates a message text
77
+ * @param message Message to validate
78
+ * @param maxLength Maximum message length
79
+ * @throws ValidationError if invalid
80
+ */
81
+ function validateMessage(message, maxLength = 10000) {
82
+ if (message === undefined || message === null) {
83
+ throw new errors_1.ValidationError('Message is required', 'message');
84
+ }
85
+ if (typeof message !== 'string') {
86
+ throw new errors_1.ValidationError('Message must be a string', 'message');
87
+ }
88
+ if (message.length > maxLength) {
89
+ throw new errors_1.ValidationError(`Message exceeds maximum length of ${maxLength} characters`, 'message');
90
+ }
91
+ }
92
+ /**
93
+ * Validates file attachments
94
+ * @param attachments Array of attachment paths
95
+ * @throws ValidationError if invalid
96
+ */
97
+ function validateAttachments(attachments) {
98
+ if (!Array.isArray(attachments)) {
99
+ throw new errors_1.ValidationError('Attachments must be an array', 'attachments');
100
+ }
101
+ for (const attachment of attachments) {
102
+ if (typeof attachment !== 'string') {
103
+ throw new errors_1.ValidationError('Each attachment must be a file path string', 'attachments');
104
+ }
105
+ if (attachment.length === 0) {
106
+ throw new errors_1.ValidationError('Attachment path cannot be empty', 'attachments');
107
+ }
108
+ }
109
+ }
110
+ /**
111
+ * Validates a timestamp
112
+ * @param timestamp Timestamp to validate
113
+ * @throws ValidationError if invalid
114
+ */
115
+ function validateTimestamp(timestamp) {
116
+ if (typeof timestamp !== 'number') {
117
+ throw new errors_1.ValidationError('Timestamp must be a number', 'timestamp');
118
+ }
119
+ if (timestamp <= 0) {
120
+ throw new errors_1.ValidationError('Timestamp must be positive', 'timestamp');
121
+ }
122
+ if (!Number.isFinite(timestamp)) {
123
+ throw new errors_1.ValidationError('Timestamp must be finite', 'timestamp');
124
+ }
125
+ }
126
+ /**
127
+ * Validates an emoji string
128
+ * @param emoji Emoji to validate
129
+ * @throws ValidationError if invalid
130
+ */
131
+ function validateEmoji(emoji) {
132
+ if (!emoji) {
133
+ throw new errors_1.ValidationError('Emoji is required', 'emoji');
134
+ }
135
+ if (typeof emoji !== 'string') {
136
+ throw new errors_1.ValidationError('Emoji must be a string', 'emoji');
137
+ }
138
+ // Basic emoji validation - should be a single grapheme cluster
139
+ if (emoji.length === 0 || emoji.length > 10) {
140
+ throw new errors_1.ValidationError('Invalid emoji format', 'emoji');
141
+ }
142
+ }
143
+ /**
144
+ * Validates device ID
145
+ * @param deviceId Device ID to validate
146
+ * @throws ValidationError if invalid
147
+ */
148
+ function validateDeviceId(deviceId) {
149
+ if (typeof deviceId !== 'number') {
150
+ throw new errors_1.ValidationError('Device ID must be a number', 'deviceId');
151
+ }
152
+ if (!Number.isInteger(deviceId)) {
153
+ throw new errors_1.ValidationError('Device ID must be an integer', 'deviceId');
154
+ }
155
+ if (deviceId <= 0) {
156
+ throw new errors_1.ValidationError('Device ID must be positive', 'deviceId');
157
+ }
158
+ }
159
+ /**
160
+ * Sanitizes user input to prevent injection attacks
161
+ * @param input Input string to sanitize
162
+ * @returns Sanitized string
163
+ */
164
+ function sanitizeInput(input) {
165
+ if (typeof input !== 'string') {
166
+ return '';
167
+ }
168
+ // Remove null bytes
169
+ return input.replace(/\0/g, '');
170
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signal-sdk",
3
- "version": "0.0.8",
3
+ "version": "0.1.0",
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",
@@ -15,6 +15,7 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "build": "tsc",
18
+ "prepublishOnly": "npm run build",
18
19
  "test": "jest",
19
20
  "postinstall": "node scripts/install.js",
20
21
  "connect": "node scripts/connect.js",
@@ -25,7 +26,8 @@
25
26
  "example:contact-management": "node examples/sdk/04-contact-management.js",
26
27
  "example:file-handling": "node examples/sdk/05-file-handling.js",
27
28
  "example:minimal-bot": "node examples/bot/01-minimal-bot.js",
28
- "example:advanced-bot": "node examples/bot/02-advanced-bot.js"
29
+ "example:advanced-bot": "node examples/bot/02-advanced-bot.js",
30
+ "example:advanced-bot-2": "node examples/bot/03-advanced-bot.js"
29
31
  },
30
32
  "keywords": [
31
33
  "signal",
@@ -53,6 +55,7 @@
53
55
  "devDependencies": {
54
56
  "@types/jest": "^30.0.0",
55
57
  "@types/node": "^24.0.10",
58
+ "dotenv": "^16.4.5",
56
59
  "@types/qrcode-terminal": "^0.12.2",
57
60
  "@types/uuid": "^9.0.8",
58
61
  "jest": "^30.0.4",
@@ -61,10 +64,6 @@
61
64
  },
62
65
  "dependencies": {
63
66
  "axios": "^1.10.0",
64
- "dotenv": "^16.4.5",
65
- "duckduckgo-chat-interface": "^1.1.5",
66
- "events": "^3.3.0",
67
- "qrcode": "^1.5.3",
68
67
  "qrcode-terminal": "^0.12.0",
69
68
  "tar": "^7.4.3",
70
69
  "uuid": "^9.0.1"