@zi2/relay-sdk 1.0.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/CHANGELOG.md +22 -0
- package/LICENSE +64 -0
- package/README.md +855 -0
- package/dist/adapters/prisma-adapter.d.ts +57 -0
- package/dist/adapters/prisma-adapter.js +181 -0
- package/dist/client/index.d.ts +63 -0
- package/dist/client/index.js +298 -0
- package/dist/index.d.ts +592 -0
- package/dist/index.js +2537 -0
- package/dist/server/fastify-plugin.d.ts +26 -0
- package/dist/server/fastify-plugin.js +125 -0
- package/dist/types-CPJUrmcy.d.ts +288 -0
- package/package.json +75 -0
- package/prisma/schema.prisma +89 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
import { E as EncryptionAdapter, B as BroadcastAdapter, L as LoggerAdapter, A as AuditAdapter, a as AuditEntry, D as DatabaseAdapter, S as SmsProviderAdapter, b as SmsSendResponse, F as FallbackConfig, C as ConnectionBroker, P as PhoneRelayRecord, c as CreateRelayInput, d as PhoneRelayPairingRecord, e as CreatePairingInput, R as RelayMessageRecord, f as CreateMessageInput, g as RelaySDKConfig, h as RelaySDK } from './types-CPJUrmcy.js';
|
|
2
|
+
export { M as MessageList, i as PairingCompleteResult, j as PairingResult, k as RelayDetail, l as RelayListItem, m as SendResult } from './types-CPJUrmcy.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { WebSocket } from 'ws';
|
|
5
|
+
|
|
6
|
+
declare const RELAY_MESSAGE_TYPES: {
|
|
7
|
+
readonly AUTH: "auth";
|
|
8
|
+
readonly SEND_SMS: "send_sms";
|
|
9
|
+
readonly SMS_ACK: "sms_ack";
|
|
10
|
+
readonly DELIVERY_RECEIPT: "delivery_receipt";
|
|
11
|
+
readonly PONG: "pong";
|
|
12
|
+
readonly STATUS: "status";
|
|
13
|
+
readonly REKEY: "rekey";
|
|
14
|
+
readonly REKEY_ACK: "rekey_ack";
|
|
15
|
+
};
|
|
16
|
+
declare const RELAY_STATUS: {
|
|
17
|
+
readonly ACTIVE: "active";
|
|
18
|
+
readonly INACTIVE: "inactive";
|
|
19
|
+
readonly REVOKED: "revoked";
|
|
20
|
+
readonly DEGRADED: "degraded";
|
|
21
|
+
};
|
|
22
|
+
declare const RELAY_MESSAGE_STATUS: {
|
|
23
|
+
readonly PENDING: "pending";
|
|
24
|
+
readonly SENT_TO_DEVICE: "sent_to_device";
|
|
25
|
+
readonly DELIVERED: "delivered";
|
|
26
|
+
readonly FAILED: "failed";
|
|
27
|
+
readonly EXPIRED: "expired";
|
|
28
|
+
};
|
|
29
|
+
declare const RELAY_DELIVERY_STATUS: {
|
|
30
|
+
readonly SENT: "sent";
|
|
31
|
+
readonly DELIVERED: "delivered";
|
|
32
|
+
readonly FAILED: "failed";
|
|
33
|
+
};
|
|
34
|
+
declare const RELAY_PAIRING_STATUS: {
|
|
35
|
+
readonly PENDING: "pending";
|
|
36
|
+
readonly COMPLETED: "completed";
|
|
37
|
+
readonly EXPIRED: "expired";
|
|
38
|
+
};
|
|
39
|
+
declare const RELAY_LIMITS: {
|
|
40
|
+
readonly AUTH_TIMEOUT_MS: 5000;
|
|
41
|
+
readonly HEARTBEAT_INTERVAL_MS: 30000;
|
|
42
|
+
readonly PAIRING_EXPIRY_MINUTES: 5;
|
|
43
|
+
readonly MESSAGE_EXPIRY_HOURS: 24;
|
|
44
|
+
readonly QUEUE_DRAIN_DELAY_MS: 3000;
|
|
45
|
+
readonly DEFAULT_SMS_TIMEOUT_MS: 30000;
|
|
46
|
+
readonly MAX_SMS_PER_MINUTE: 20;
|
|
47
|
+
readonly MAX_SMS_PER_HOUR: 200;
|
|
48
|
+
readonly MAX_SMS_PER_DAY: 1000;
|
|
49
|
+
readonly DEGRADED_THRESHOLD_MS: number;
|
|
50
|
+
};
|
|
51
|
+
declare const RELAY_PLATFORMS: {
|
|
52
|
+
readonly ANDROID: "android";
|
|
53
|
+
readonly IOS: "ios";
|
|
54
|
+
};
|
|
55
|
+
type RelayMessageType = (typeof RELAY_MESSAGE_TYPES)[keyof typeof RELAY_MESSAGE_TYPES];
|
|
56
|
+
type RelayStatus = (typeof RELAY_STATUS)[keyof typeof RELAY_STATUS];
|
|
57
|
+
type RelayMessageStatus = (typeof RELAY_MESSAGE_STATUS)[keyof typeof RELAY_MESSAGE_STATUS];
|
|
58
|
+
type RelayPlatform = (typeof RELAY_PLATFORMS)[keyof typeof RELAY_PLATFORMS];
|
|
59
|
+
|
|
60
|
+
declare const completePairingSchema: z.ZodObject<{
|
|
61
|
+
pairingId: z.ZodString;
|
|
62
|
+
pairingToken: z.ZodString;
|
|
63
|
+
devicePublicKey: z.ZodString;
|
|
64
|
+
deviceName: z.ZodString;
|
|
65
|
+
platform: z.ZodEnum<["android", "ios"]>;
|
|
66
|
+
phoneNumber: z.ZodOptional<z.ZodString>;
|
|
67
|
+
}, "strip", z.ZodTypeAny, {
|
|
68
|
+
deviceName: string;
|
|
69
|
+
platform: "android" | "ios";
|
|
70
|
+
devicePublicKey: string;
|
|
71
|
+
pairingToken: string;
|
|
72
|
+
pairingId: string;
|
|
73
|
+
phoneNumber?: string | undefined;
|
|
74
|
+
}, {
|
|
75
|
+
deviceName: string;
|
|
76
|
+
platform: "android" | "ios";
|
|
77
|
+
devicePublicKey: string;
|
|
78
|
+
pairingToken: string;
|
|
79
|
+
pairingId: string;
|
|
80
|
+
phoneNumber?: string | undefined;
|
|
81
|
+
}>;
|
|
82
|
+
declare const updatePhoneRelaySchema: z.ZodObject<{
|
|
83
|
+
deviceName: z.ZodOptional<z.ZodString>;
|
|
84
|
+
maxSmsPerMinute: z.ZodOptional<z.ZodNumber>;
|
|
85
|
+
maxSmsPerHour: z.ZodOptional<z.ZodNumber>;
|
|
86
|
+
maxSmsPerDay: z.ZodOptional<z.ZodNumber>;
|
|
87
|
+
}, "strip", z.ZodTypeAny, {
|
|
88
|
+
deviceName?: string | undefined;
|
|
89
|
+
maxSmsPerMinute?: number | undefined;
|
|
90
|
+
maxSmsPerHour?: number | undefined;
|
|
91
|
+
maxSmsPerDay?: number | undefined;
|
|
92
|
+
}, {
|
|
93
|
+
deviceName?: string | undefined;
|
|
94
|
+
maxSmsPerMinute?: number | undefined;
|
|
95
|
+
maxSmsPerHour?: number | undefined;
|
|
96
|
+
maxSmsPerDay?: number | undefined;
|
|
97
|
+
}>;
|
|
98
|
+
declare const testRelaySmsSchema: z.ZodObject<{
|
|
99
|
+
to: z.ZodString;
|
|
100
|
+
body: z.ZodOptional<z.ZodString>;
|
|
101
|
+
}, "strip", z.ZodTypeAny, {
|
|
102
|
+
to: string;
|
|
103
|
+
body?: string | undefined;
|
|
104
|
+
}, {
|
|
105
|
+
to: string;
|
|
106
|
+
body?: string | undefined;
|
|
107
|
+
}>;
|
|
108
|
+
declare const relayMessagesQuerySchema: z.ZodObject<{
|
|
109
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
110
|
+
offset: z.ZodDefault<z.ZodNumber>;
|
|
111
|
+
}, "strip", z.ZodTypeAny, {
|
|
112
|
+
limit: number;
|
|
113
|
+
offset: number;
|
|
114
|
+
}, {
|
|
115
|
+
limit?: number | undefined;
|
|
116
|
+
offset?: number | undefined;
|
|
117
|
+
}>;
|
|
118
|
+
type CompletePairingInput = z.infer<typeof completePairingSchema>;
|
|
119
|
+
type UpdatePhoneRelayInput = z.infer<typeof updatePhoneRelaySchema>;
|
|
120
|
+
type TestRelaySmsInput = z.infer<typeof testRelaySmsSchema>;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Structured error class for the Relay SDK.
|
|
124
|
+
*
|
|
125
|
+
* PCI DSS v4 Req 6.2.4: Error messages must NOT expose internal system details,
|
|
126
|
+
* stack traces, database info, or cryptographic implementation details.
|
|
127
|
+
* All messages are generic and safe for client display.
|
|
128
|
+
*
|
|
129
|
+
* SOC 2 CC7.2: Errors are coded for audit trail correlation.
|
|
130
|
+
*/
|
|
131
|
+
declare class RelayError extends Error {
|
|
132
|
+
code: string;
|
|
133
|
+
statusCode: number;
|
|
134
|
+
i18nKey?: string | undefined;
|
|
135
|
+
constructor(code: string, statusCode: number, message: string, i18nKey?: string | undefined);
|
|
136
|
+
/** Safe serialization that never leaks stack traces to clients */
|
|
137
|
+
toJSON(): {
|
|
138
|
+
error: {
|
|
139
|
+
code: string;
|
|
140
|
+
message: string;
|
|
141
|
+
i18nKey: string | undefined;
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
declare const RELAY_ERRORS: {
|
|
146
|
+
readonly RELAY_NOT_FOUND: {
|
|
147
|
+
readonly code: "RELAY_NOT_FOUND";
|
|
148
|
+
readonly status: 404;
|
|
149
|
+
readonly message: "Relay device not found";
|
|
150
|
+
readonly i18nKey: "errors.relayNotFound";
|
|
151
|
+
};
|
|
152
|
+
readonly RELAY_INACTIVE: {
|
|
153
|
+
readonly code: "RELAY_INACTIVE";
|
|
154
|
+
readonly status: 409;
|
|
155
|
+
readonly message: "Relay is not active";
|
|
156
|
+
readonly i18nKey: "errors.relayInactive";
|
|
157
|
+
};
|
|
158
|
+
readonly RELAY_OFFLINE: {
|
|
159
|
+
readonly code: "RELAY_OFFLINE";
|
|
160
|
+
readonly status: 503;
|
|
161
|
+
readonly message: "Relay device is offline";
|
|
162
|
+
readonly i18nKey: "errors.relayOffline";
|
|
163
|
+
};
|
|
164
|
+
readonly PAIRING_NOT_FOUND: {
|
|
165
|
+
readonly code: "PAIRING_NOT_FOUND";
|
|
166
|
+
readonly status: 404;
|
|
167
|
+
readonly message: "Pairing session not found";
|
|
168
|
+
readonly i18nKey: "errors.pairingNotFound";
|
|
169
|
+
};
|
|
170
|
+
readonly PAIRING_EXPIRED: {
|
|
171
|
+
readonly code: "PAIRING_EXPIRED";
|
|
172
|
+
readonly status: 410;
|
|
173
|
+
readonly message: "Pairing session has expired";
|
|
174
|
+
readonly i18nKey: "errors.pairingExpired";
|
|
175
|
+
};
|
|
176
|
+
readonly PAIRING_INVALID_TOKEN: {
|
|
177
|
+
readonly code: "PAIRING_INVALID_TOKEN";
|
|
178
|
+
readonly status: 403;
|
|
179
|
+
readonly message: "Invalid pairing token";
|
|
180
|
+
readonly i18nKey: "errors.pairingInvalidToken";
|
|
181
|
+
};
|
|
182
|
+
readonly AUTH_TIMEOUT: {
|
|
183
|
+
readonly code: "AUTH_TIMEOUT";
|
|
184
|
+
readonly status: 408;
|
|
185
|
+
readonly message: "WebSocket authentication timeout";
|
|
186
|
+
readonly i18nKey: "errors.authTimeout";
|
|
187
|
+
};
|
|
188
|
+
readonly AUTH_FAILED: {
|
|
189
|
+
readonly code: "AUTH_FAILED";
|
|
190
|
+
readonly status: 401;
|
|
191
|
+
readonly message: "Invalid relay credentials";
|
|
192
|
+
readonly i18nKey: "errors.authFailed";
|
|
193
|
+
};
|
|
194
|
+
readonly RATE_LIMITED: {
|
|
195
|
+
readonly code: "RATE_LIMITED";
|
|
196
|
+
readonly status: 429;
|
|
197
|
+
readonly message: "Rate limit exceeded";
|
|
198
|
+
readonly i18nKey: "errors.rateLimited";
|
|
199
|
+
};
|
|
200
|
+
readonly ENCRYPTION_FAILED: {
|
|
201
|
+
readonly code: "ENCRYPTION_FAILED";
|
|
202
|
+
readonly status: 500;
|
|
203
|
+
readonly message: "Encryption operation failed";
|
|
204
|
+
readonly i18nKey: "errors.encryptionFailed";
|
|
205
|
+
};
|
|
206
|
+
readonly REKEY_FAILED: {
|
|
207
|
+
readonly code: "REKEY_FAILED";
|
|
208
|
+
readonly status: 500;
|
|
209
|
+
readonly message: "Key rotation failed";
|
|
210
|
+
readonly i18nKey: "errors.rekeyFailed";
|
|
211
|
+
};
|
|
212
|
+
readonly SMS_SEND_FAILED: {
|
|
213
|
+
readonly code: "SMS_SEND_FAILED";
|
|
214
|
+
readonly status: 502;
|
|
215
|
+
readonly message: "SMS delivery failed";
|
|
216
|
+
readonly i18nKey: "errors.smsSendFailed";
|
|
217
|
+
};
|
|
218
|
+
readonly SMS_TIMEOUT: {
|
|
219
|
+
readonly code: "SMS_TIMEOUT";
|
|
220
|
+
readonly status: 504;
|
|
221
|
+
readonly message: "SMS acknowledgement timeout";
|
|
222
|
+
readonly i18nKey: "errors.smsTimeout";
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
declare const RELAY_ERRORS_EXTENDED: {
|
|
226
|
+
readonly AUTH_LOCKED: {
|
|
227
|
+
readonly code: "AUTH_LOCKED";
|
|
228
|
+
readonly status: 423;
|
|
229
|
+
readonly message: "Too many failed attempts. Try again later.";
|
|
230
|
+
readonly i18nKey: "errors.authLocked";
|
|
231
|
+
};
|
|
232
|
+
readonly TLS_REQUIRED: {
|
|
233
|
+
readonly code: "TLS_REQUIRED";
|
|
234
|
+
readonly status: 426;
|
|
235
|
+
readonly message: "TLS/SSL connection required";
|
|
236
|
+
readonly i18nKey: "errors.tlsRequired";
|
|
237
|
+
};
|
|
238
|
+
readonly ORG_MISMATCH: {
|
|
239
|
+
readonly code: "ORG_MISMATCH";
|
|
240
|
+
readonly status: 403;
|
|
241
|
+
readonly message: "Organization access denied";
|
|
242
|
+
readonly i18nKey: "errors.orgMismatch";
|
|
243
|
+
};
|
|
244
|
+
readonly RELAY_NOT_FOUND: {
|
|
245
|
+
readonly code: "RELAY_NOT_FOUND";
|
|
246
|
+
readonly status: 404;
|
|
247
|
+
readonly message: "Relay device not found";
|
|
248
|
+
readonly i18nKey: "errors.relayNotFound";
|
|
249
|
+
};
|
|
250
|
+
readonly RELAY_INACTIVE: {
|
|
251
|
+
readonly code: "RELAY_INACTIVE";
|
|
252
|
+
readonly status: 409;
|
|
253
|
+
readonly message: "Relay is not active";
|
|
254
|
+
readonly i18nKey: "errors.relayInactive";
|
|
255
|
+
};
|
|
256
|
+
readonly RELAY_OFFLINE: {
|
|
257
|
+
readonly code: "RELAY_OFFLINE";
|
|
258
|
+
readonly status: 503;
|
|
259
|
+
readonly message: "Relay device is offline";
|
|
260
|
+
readonly i18nKey: "errors.relayOffline";
|
|
261
|
+
};
|
|
262
|
+
readonly PAIRING_NOT_FOUND: {
|
|
263
|
+
readonly code: "PAIRING_NOT_FOUND";
|
|
264
|
+
readonly status: 404;
|
|
265
|
+
readonly message: "Pairing session not found";
|
|
266
|
+
readonly i18nKey: "errors.pairingNotFound";
|
|
267
|
+
};
|
|
268
|
+
readonly PAIRING_EXPIRED: {
|
|
269
|
+
readonly code: "PAIRING_EXPIRED";
|
|
270
|
+
readonly status: 410;
|
|
271
|
+
readonly message: "Pairing session has expired";
|
|
272
|
+
readonly i18nKey: "errors.pairingExpired";
|
|
273
|
+
};
|
|
274
|
+
readonly PAIRING_INVALID_TOKEN: {
|
|
275
|
+
readonly code: "PAIRING_INVALID_TOKEN";
|
|
276
|
+
readonly status: 403;
|
|
277
|
+
readonly message: "Invalid pairing token";
|
|
278
|
+
readonly i18nKey: "errors.pairingInvalidToken";
|
|
279
|
+
};
|
|
280
|
+
readonly AUTH_TIMEOUT: {
|
|
281
|
+
readonly code: "AUTH_TIMEOUT";
|
|
282
|
+
readonly status: 408;
|
|
283
|
+
readonly message: "WebSocket authentication timeout";
|
|
284
|
+
readonly i18nKey: "errors.authTimeout";
|
|
285
|
+
};
|
|
286
|
+
readonly AUTH_FAILED: {
|
|
287
|
+
readonly code: "AUTH_FAILED";
|
|
288
|
+
readonly status: 401;
|
|
289
|
+
readonly message: "Invalid relay credentials";
|
|
290
|
+
readonly i18nKey: "errors.authFailed";
|
|
291
|
+
};
|
|
292
|
+
readonly RATE_LIMITED: {
|
|
293
|
+
readonly code: "RATE_LIMITED";
|
|
294
|
+
readonly status: 429;
|
|
295
|
+
readonly message: "Rate limit exceeded";
|
|
296
|
+
readonly i18nKey: "errors.rateLimited";
|
|
297
|
+
};
|
|
298
|
+
readonly ENCRYPTION_FAILED: {
|
|
299
|
+
readonly code: "ENCRYPTION_FAILED";
|
|
300
|
+
readonly status: 500;
|
|
301
|
+
readonly message: "Encryption operation failed";
|
|
302
|
+
readonly i18nKey: "errors.encryptionFailed";
|
|
303
|
+
};
|
|
304
|
+
readonly REKEY_FAILED: {
|
|
305
|
+
readonly code: "REKEY_FAILED";
|
|
306
|
+
readonly status: 500;
|
|
307
|
+
readonly message: "Key rotation failed";
|
|
308
|
+
readonly i18nKey: "errors.rekeyFailed";
|
|
309
|
+
};
|
|
310
|
+
readonly SMS_SEND_FAILED: {
|
|
311
|
+
readonly code: "SMS_SEND_FAILED";
|
|
312
|
+
readonly status: 502;
|
|
313
|
+
readonly message: "SMS delivery failed";
|
|
314
|
+
readonly i18nKey: "errors.smsSendFailed";
|
|
315
|
+
};
|
|
316
|
+
readonly SMS_TIMEOUT: {
|
|
317
|
+
readonly code: "SMS_TIMEOUT";
|
|
318
|
+
readonly status: 504;
|
|
319
|
+
readonly message: "SMS acknowledgement timeout";
|
|
320
|
+
readonly i18nKey: "errors.smsTimeout";
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
declare function createRelayError(key: keyof typeof RELAY_ERRORS): RelayError;
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* AES-256-GCM encryption adapter.
|
|
327
|
+
*
|
|
328
|
+
* PCI DSS v4 compliance:
|
|
329
|
+
* - Req 3.5.1: Key material validated at construction
|
|
330
|
+
* - Req 3.4.1: No plaintext fallback in strict mode (default)
|
|
331
|
+
* - Req 3.7.1: Key must be 256-bit (32 bytes / 64 hex chars)
|
|
332
|
+
*
|
|
333
|
+
* SOC 2 CC6.1: Authenticated encryption prevents tampering
|
|
334
|
+
*/
|
|
335
|
+
declare class AesGcmEncryption implements EncryptionAdapter {
|
|
336
|
+
private keyBuffer;
|
|
337
|
+
private allowPlaintextFallback;
|
|
338
|
+
/**
|
|
339
|
+
* @param key 64-char hex string OR 32-byte Buffer for AES-256
|
|
340
|
+
* @param options.allowPlaintextFallback Allow decrypting legacy unencrypted values.
|
|
341
|
+
* Set to false (default) for PCI DSS v4 compliance. Set to true only during migration.
|
|
342
|
+
*/
|
|
343
|
+
constructor(key: string | Buffer, options?: {
|
|
344
|
+
allowPlaintextFallback?: boolean;
|
|
345
|
+
});
|
|
346
|
+
encrypt(plaintext: string): string;
|
|
347
|
+
decrypt(ciphertext: string): string;
|
|
348
|
+
/**
|
|
349
|
+
* Securely destroy the key material (PCI DSS v4 Req 3.5.1).
|
|
350
|
+
* Call when the SDK instance is being shut down.
|
|
351
|
+
*/
|
|
352
|
+
destroy(): void;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
declare class NoopBroadcast implements BroadcastAdapter {
|
|
356
|
+
broadcast(_orgId: string, _message: Record<string, unknown>): void;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
declare class ConsoleLogger implements LoggerAdapter {
|
|
360
|
+
debug(msg: string, data?: Record<string, unknown>): void;
|
|
361
|
+
info(msg: string, data?: Record<string, unknown>): void;
|
|
362
|
+
warn(msg: string, data?: Record<string, unknown>): void;
|
|
363
|
+
error(msg: string, data?: Record<string, unknown>): void;
|
|
364
|
+
}
|
|
365
|
+
/** Utility for SDK consumers to wrap their own logger with redaction */
|
|
366
|
+
declare function withRedaction(logger: LoggerAdapter): LoggerAdapter;
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* No-op audit adapter. Replace with a real implementation for
|
|
370
|
+
* PCI DSS v4 Req 10 / SOC 2 CC7.2 compliance.
|
|
371
|
+
*
|
|
372
|
+
* A compliant implementation must:
|
|
373
|
+
* - Write audit entries to tamper-evident storage (append-only log, WORM, or SIEM)
|
|
374
|
+
* - Retain entries for at least 12 months (PCI DSS v4 Req 10.7.1)
|
|
375
|
+
* - Include timestamp, user, action, resource, and outcome
|
|
376
|
+
* - Be protected from unauthorized modification (SOC 2 CC7.2)
|
|
377
|
+
*/
|
|
378
|
+
declare class NoopAudit implements AuditAdapter {
|
|
379
|
+
log(_entry: AuditEntry): Promise<void>;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Console audit adapter for development/testing.
|
|
383
|
+
* NOT suitable for production compliance — logs to stdout which is ephemeral.
|
|
384
|
+
*/
|
|
385
|
+
declare class ConsoleAudit implements AuditAdapter {
|
|
386
|
+
log(entry: AuditEntry): Promise<void>;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* IP-based authentication failure rate limiter.
|
|
391
|
+
*
|
|
392
|
+
* PCI DSS v4 Req 8.3.4: Lock out after N failed attempts.
|
|
393
|
+
* SOC 2 CC6.1: Protect against brute-force attacks.
|
|
394
|
+
*
|
|
395
|
+
* Tracks failed auth attempts per IP with automatic expiry.
|
|
396
|
+
*/
|
|
397
|
+
declare class AuthLimiter {
|
|
398
|
+
private attempts;
|
|
399
|
+
private maxFailures;
|
|
400
|
+
private windowMs;
|
|
401
|
+
private lockoutMs;
|
|
402
|
+
private cleanupInterval;
|
|
403
|
+
/**
|
|
404
|
+
* @param maxFailures Max auth failures before lockout (default: 10)
|
|
405
|
+
* @param windowMs Time window for counting failures in ms (default: 15 min)
|
|
406
|
+
* @param lockoutMs Duration of lockout after max failures (default: 30 min)
|
|
407
|
+
*/
|
|
408
|
+
constructor(maxFailures?: number, windowMs?: number, lockoutMs?: number);
|
|
409
|
+
/** Returns true if the IP is currently locked out */
|
|
410
|
+
isLocked(ip: string): boolean;
|
|
411
|
+
/** Record a failed auth attempt. Returns remaining attempts before lockout. */
|
|
412
|
+
recordFailure(ip: string): number;
|
|
413
|
+
/** Clear failure count for an IP after successful auth */
|
|
414
|
+
recordSuccess(ip: string): void;
|
|
415
|
+
/** Get remaining lockout time in seconds (0 if not locked) */
|
|
416
|
+
getLockoutRemaining(ip: string): number;
|
|
417
|
+
private cleanup;
|
|
418
|
+
destroy(): void;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
interface RelayEvents {
|
|
422
|
+
'relay:online': (relayId: string, orgId: string) => void;
|
|
423
|
+
'relay:offline': (relayId: string, orgId: string) => void;
|
|
424
|
+
'message:delivered': (messageId: string, relayId: string) => void;
|
|
425
|
+
'message:failed': (messageId: string, relayId: string, error: string) => void;
|
|
426
|
+
'message:ack': (messageId: string, status: string) => void;
|
|
427
|
+
}
|
|
428
|
+
declare class RelayEventEmitter {
|
|
429
|
+
private emitter;
|
|
430
|
+
constructor();
|
|
431
|
+
on<K extends keyof RelayEvents>(event: K, listener: RelayEvents[K]): () => void;
|
|
432
|
+
once<K extends keyof RelayEvents>(event: K, listener: RelayEvents[K]): void;
|
|
433
|
+
emit<K extends keyof RelayEvents>(event: K, ...args: Parameters<RelayEvents[K]>): void;
|
|
434
|
+
removeAllListeners(event?: keyof RelayEvents): void;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
interface QueueServiceDeps {
|
|
438
|
+
db: DatabaseAdapter;
|
|
439
|
+
encryption: EncryptionAdapter;
|
|
440
|
+
logger: LoggerAdapter;
|
|
441
|
+
events: RelayEventEmitter;
|
|
442
|
+
limits: typeof RELAY_LIMITS;
|
|
443
|
+
}
|
|
444
|
+
declare function createQueueService(deps: QueueServiceDeps): {
|
|
445
|
+
enqueueAndWait: (options: {
|
|
446
|
+
relayId: string;
|
|
447
|
+
orgId: string;
|
|
448
|
+
to: string;
|
|
449
|
+
body: string;
|
|
450
|
+
timeoutMs?: number;
|
|
451
|
+
}) => Promise<{
|
|
452
|
+
messageId: string;
|
|
453
|
+
status: string;
|
|
454
|
+
}>;
|
|
455
|
+
handleAck: (messageId: string, status: string, nativeMessageId?: string, errorMessage?: string) => Promise<void>;
|
|
456
|
+
handleDeliveryReceipt: (messageId: string, deliveryStatus: string) => Promise<void>;
|
|
457
|
+
drainQueue: (relayId: string) => Promise<void>;
|
|
458
|
+
};
|
|
459
|
+
type QueueService = ReturnType<typeof createQueueService>;
|
|
460
|
+
|
|
461
|
+
declare class PhoneRelayProvider implements SmsProviderAdapter {
|
|
462
|
+
readonly name = "phone-relay";
|
|
463
|
+
private relayId;
|
|
464
|
+
private orgId;
|
|
465
|
+
private queue;
|
|
466
|
+
constructor(queue: QueueService);
|
|
467
|
+
initialize(credentials: Record<string, string>): void;
|
|
468
|
+
sendSms(to: string, body: string): Promise<SmsSendResponse>;
|
|
469
|
+
validateCredentials(credentials: Record<string, string>): Promise<boolean>;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
declare class FallbackProvider implements SmsProviderAdapter {
|
|
473
|
+
readonly name = "relay-fallback";
|
|
474
|
+
private config;
|
|
475
|
+
constructor(config: FallbackConfig);
|
|
476
|
+
initialize(_credentials: Record<string, string>): void;
|
|
477
|
+
sendSms(to: string, body: string): Promise<SmsSendResponse>;
|
|
478
|
+
validateCredentials(_credentials: Record<string, string>): Promise<boolean>;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
declare function getRelaySocket(relayId: string): WebSocket | undefined;
|
|
482
|
+
declare function isRelayOnline(relayId: string): boolean;
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Redis-based ConnectionBroker for horizontal scaling.
|
|
486
|
+
*
|
|
487
|
+
* When running multiple API servers behind a load balancer, each server
|
|
488
|
+
* holds a subset of WebSocket connections. The Redis broker ensures SMS
|
|
489
|
+
* messages reach the correct server via pub/sub.
|
|
490
|
+
*
|
|
491
|
+
* Requires `ioredis` as a peer dependency (not bundled).
|
|
492
|
+
*
|
|
493
|
+
* @example
|
|
494
|
+
* ```typescript
|
|
495
|
+
* import Redis from 'ioredis';
|
|
496
|
+
* import { RedisBroker } from '@zi2/relay-sdk';
|
|
497
|
+
*
|
|
498
|
+
* const pub = new Redis(process.env.REDIS_URL);
|
|
499
|
+
* const sub = new Redis(process.env.REDIS_URL);
|
|
500
|
+
* const broker = new RedisBroker(pub, sub);
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
|
|
504
|
+
declare class RedisBroker implements ConnectionBroker {
|
|
505
|
+
private pub;
|
|
506
|
+
private sub;
|
|
507
|
+
private handlers;
|
|
508
|
+
private prefix;
|
|
509
|
+
constructor(pubClient: any, subClient: any, options?: {
|
|
510
|
+
prefix?: string;
|
|
511
|
+
});
|
|
512
|
+
publish(channel: string, message: string): Promise<void>;
|
|
513
|
+
subscribe(channel: string, handler: (message: string) => void): Promise<void>;
|
|
514
|
+
unsubscribe(channel: string): Promise<void>;
|
|
515
|
+
destroy(): Promise<void>;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
declare class MemoryAdapter implements DatabaseAdapter {
|
|
519
|
+
private relays;
|
|
520
|
+
private pairings;
|
|
521
|
+
private messages;
|
|
522
|
+
findRelay(id: string, orgId?: string): Promise<PhoneRelayRecord | null>;
|
|
523
|
+
findRelayByTokenHash(id: string, tokenHash: string, statuses: string[]): Promise<PhoneRelayRecord | null>;
|
|
524
|
+
findRelays(orgId: string, excludeStatus?: string): Promise<PhoneRelayRecord[]>;
|
|
525
|
+
createRelay(data: CreateRelayInput): Promise<PhoneRelayRecord>;
|
|
526
|
+
updateRelay(id: string, data: Partial<PhoneRelayRecord>): Promise<void>;
|
|
527
|
+
updateRelayByOrg(id: string, orgId: string, data: Partial<PhoneRelayRecord>): Promise<void>;
|
|
528
|
+
deleteRelay(id: string): Promise<void>;
|
|
529
|
+
incrementRelayStat(id: string, field: 'totalSmsSent' | 'totalSmsFailed' | 'dailySmsSent', amount?: number): Promise<void>;
|
|
530
|
+
countRelays(orgId: string): Promise<number>;
|
|
531
|
+
markDegradedRelays(threshold: Date): Promise<number>;
|
|
532
|
+
resetDailyCounters(): Promise<number>;
|
|
533
|
+
findPairing(id: string): Promise<PhoneRelayPairingRecord | null>;
|
|
534
|
+
createPairing(data: CreatePairingInput): Promise<PhoneRelayPairingRecord>;
|
|
535
|
+
updatePairingStatus(id: string, status: string): Promise<void>;
|
|
536
|
+
deleteExpiredPairings(): Promise<number>;
|
|
537
|
+
findMessage(id: string): Promise<RelayMessageRecord | null>;
|
|
538
|
+
createMessage(data: CreateMessageInput): Promise<RelayMessageRecord>;
|
|
539
|
+
updateMessage(id: string, data: Partial<RelayMessageRecord>): Promise<void>;
|
|
540
|
+
findPendingMessages(relayId: string, limit: number): Promise<RelayMessageRecord[]>;
|
|
541
|
+
findMessages(relayId: string, limit: number, offset: number): Promise<{
|
|
542
|
+
messages: RelayMessageRecord[];
|
|
543
|
+
total: number;
|
|
544
|
+
}>;
|
|
545
|
+
deleteMessagesByRelay(relayId: string): Promise<number>;
|
|
546
|
+
expireStaleMessages(): Promise<number>;
|
|
547
|
+
createMessages(batch: CreateMessageInput[]): Promise<RelayMessageRecord[]>;
|
|
548
|
+
updateMessages(ids: string[], data: Partial<RelayMessageRecord>): Promise<number>;
|
|
549
|
+
clear(): void;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
declare function getTranslation(locale: string, key: string): string;
|
|
553
|
+
declare function getSupportedLocales(): string[];
|
|
554
|
+
declare function getRelayTranslations(locale: string): Record<string, string>;
|
|
555
|
+
|
|
556
|
+
declare function generateX25519KeyPair(): {
|
|
557
|
+
publicKey: string;
|
|
558
|
+
privateKey: string;
|
|
559
|
+
};
|
|
560
|
+
declare function deriveSharedKey(privateKeyHex: string, publicKeyHex: string): Buffer;
|
|
561
|
+
declare function encryptE2E(plaintext: string, sharedKey: Buffer): string;
|
|
562
|
+
declare function decryptE2E(ciphertext: string, sharedKey: Buffer): string;
|
|
563
|
+
|
|
564
|
+
declare function hashToken(token: string): string;
|
|
565
|
+
declare function generateSecureToken(bytes?: number): string;
|
|
566
|
+
declare function timingSafeCompare(a: string, b: string): boolean;
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* @zi2/relay-sdk — Standalone SMS Relay SDK
|
|
570
|
+
*
|
|
571
|
+
* Enterprise-grade SMS relay system with E2E encryption, WebSocket device management,
|
|
572
|
+
* database adapter pattern, provider fallback, and i18n support.
|
|
573
|
+
*
|
|
574
|
+
* @example
|
|
575
|
+
* ```typescript
|
|
576
|
+
* import { createRelay, AesGcmEncryption } from '@zi2/relay-sdk';
|
|
577
|
+
* import { PrismaAdapter } from '@zi2/relay-sdk/adapters/prisma';
|
|
578
|
+
*
|
|
579
|
+
* const sdk = createRelay({
|
|
580
|
+
* db: new PrismaAdapter(prisma),
|
|
581
|
+
* encryption: new AesGcmEncryption(process.env.ENCRYPTION_KEY!),
|
|
582
|
+
* apiUrl: 'https://api.example.com',
|
|
583
|
+
* });
|
|
584
|
+
*
|
|
585
|
+
* // Send SMS through a paired relay device
|
|
586
|
+
* const result = await sdk.sendSMS({ relayId, orgId, to: '+1234567890', body: 'Hello' });
|
|
587
|
+
* ```
|
|
588
|
+
*/
|
|
589
|
+
|
|
590
|
+
declare function createRelay(config: RelaySDKConfig): RelaySDK;
|
|
591
|
+
|
|
592
|
+
export { AesGcmEncryption, AuditAdapter, AuditEntry, AuthLimiter, BroadcastAdapter, type CompletePairingInput, ConnectionBroker, ConsoleAudit, ConsoleLogger, CreateMessageInput, CreatePairingInput, CreateRelayInput, DatabaseAdapter, EncryptionAdapter, FallbackConfig, FallbackProvider, LoggerAdapter, MemoryAdapter, NoopAudit, NoopBroadcast, PhoneRelayPairingRecord, PhoneRelayProvider, PhoneRelayRecord, RELAY_DELIVERY_STATUS, RELAY_ERRORS, RELAY_ERRORS_EXTENDED, RELAY_LIMITS, RELAY_MESSAGE_STATUS, RELAY_MESSAGE_TYPES, RELAY_PAIRING_STATUS, RELAY_PLATFORMS, RELAY_STATUS, RedisBroker, RelayError, RelayMessageRecord, type RelayMessageStatus, type RelayMessageType, type RelayPlatform, RelaySDK, RelaySDKConfig, type RelayStatus, SmsProviderAdapter, SmsSendResponse, type TestRelaySmsInput, type UpdatePhoneRelayInput, completePairingSchema, createRelay, createRelayError, decryptE2E, deriveSharedKey, encryptE2E, generateSecureToken, generateX25519KeyPair, getRelaySocket, getRelayTranslations, getSupportedLocales, getTranslation, hashToken, isRelayOnline, relayMessagesQuerySchema, testRelaySmsSchema, timingSafeCompare, updatePhoneRelaySchema, withRedaction };
|