@vaultsandbox/client 0.5.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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +612 -0
  3. package/dist/client.d.ts +231 -0
  4. package/dist/client.js +432 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/crypto/constants.d.ts +13 -0
  7. package/dist/crypto/constants.js +14 -0
  8. package/dist/crypto/constants.js.map +1 -0
  9. package/dist/crypto/decrypt.d.ts +41 -0
  10. package/dist/crypto/decrypt.js +112 -0
  11. package/dist/crypto/decrypt.js.map +1 -0
  12. package/dist/crypto/keypair.d.ts +39 -0
  13. package/dist/crypto/keypair.js +109 -0
  14. package/dist/crypto/keypair.js.map +1 -0
  15. package/dist/crypto/signature.d.ts +28 -0
  16. package/dist/crypto/signature.js +89 -0
  17. package/dist/crypto/signature.js.map +1 -0
  18. package/dist/crypto/utils.d.ts +28 -0
  19. package/dist/crypto/utils.js +60 -0
  20. package/dist/crypto/utils.js.map +1 -0
  21. package/dist/email.d.ts +63 -0
  22. package/dist/email.js +186 -0
  23. package/dist/email.js.map +1 -0
  24. package/dist/http/api-client.d.ts +145 -0
  25. package/dist/http/api-client.js +242 -0
  26. package/dist/http/api-client.js.map +1 -0
  27. package/dist/inbox.d.ts +120 -0
  28. package/dist/inbox.js +243 -0
  29. package/dist/inbox.js.map +1 -0
  30. package/dist/index.d.ts +14 -0
  31. package/dist/index.js +17 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/strategies/delivery-strategy.d.ts +29 -0
  34. package/dist/strategies/delivery-strategy.js +2 -0
  35. package/dist/strategies/delivery-strategy.js.map +1 -0
  36. package/dist/strategies/polling-strategy.d.ts +36 -0
  37. package/dist/strategies/polling-strategy.js +146 -0
  38. package/dist/strategies/polling-strategy.js.map +1 -0
  39. package/dist/strategies/sse-strategy.d.ts +49 -0
  40. package/dist/strategies/sse-strategy.js +266 -0
  41. package/dist/strategies/sse-strategy.js.map +1 -0
  42. package/dist/types/index.d.ts +434 -0
  43. package/dist/types/index.js +127 -0
  44. package/dist/types/index.js.map +1 -0
  45. package/dist/utils/email-utils.d.ts +19 -0
  46. package/dist/utils/email-utils.js +92 -0
  47. package/dist/utils/email-utils.js.map +1 -0
  48. package/dist/utils/sleep.d.ts +6 -0
  49. package/dist/utils/sleep.js +9 -0
  50. package/dist/utils/sleep.js.map +1 -0
  51. package/package.json +85 -0
@@ -0,0 +1,231 @@
1
+ /**
2
+ * VaultSandboxClient - Main entry point for the SDK
3
+ */
4
+ import { EventEmitter } from 'events';
5
+ import { Inbox } from './inbox.js';
6
+ import type { ClientConfig, CreateInboxOptions, ServerInfo, Subscription, IEmail, ExportedInboxData } from './types/index.js';
7
+ /**
8
+ * An event emitter for monitoring multiple inboxes simultaneously.
9
+ * @emits email - When a new email arrives in any of the monitored inboxes.
10
+ *
11
+ * @example
12
+ * const monitor = client.monitorInboxes([inbox1, inbox2]);
13
+ * monitor.on('email', (inbox, email) => {
14
+ * console.log(`New email in ${inbox.emailAddress}: ${email.subject}`);
15
+ * });
16
+ * // To stop monitoring:
17
+ * monitor.unsubscribe();
18
+ */
19
+ export declare class InboxMonitor extends EventEmitter {
20
+ private subscriptions;
21
+ /**
22
+ * @internal
23
+ * Adds a subscription to the monitor.
24
+ * @param subscription - The subscription to add.
25
+ */
26
+ addSubscription(subscription: Subscription): void;
27
+ /**
28
+ * Unsubscribes from all monitored inboxes and cleans up resources.
29
+ */
30
+ unsubscribe(): void;
31
+ /**
32
+ * @internal
33
+ * Emits an 'email' event for a specific inbox.
34
+ * @param inbox - The inbox that received the email.
35
+ * @param email - The email that was received.
36
+ */
37
+ emitEmail(inbox: Inbox, email: IEmail): void;
38
+ }
39
+ /**
40
+ * The main client for interacting with the VaultSandbox API.
41
+ *
42
+ * This class provides methods for creating and managing inboxes,
43
+ * as well as for monitoring them for new emails.
44
+ */
45
+ export declare class VaultSandboxClient {
46
+ private apiClient;
47
+ private config;
48
+ private serverPublicKey;
49
+ private inboxes;
50
+ private strategy;
51
+ /**
52
+ * Creates a new VaultSandboxClient instance.
53
+ * @param config - The client configuration.
54
+ */
55
+ constructor(config: ClientConfig);
56
+ /**
57
+ * Initializes the client by fetching server info and creating a delivery strategy.
58
+ * This method is called automatically when needed and should not be called directly.
59
+ * @private
60
+ */
61
+ private ensureInitialized;
62
+ /**
63
+ * Creates the appropriate delivery strategy based on configuration.
64
+ * SSE for email events (/api/events) is always available on the server.
65
+ * @private
66
+ */
67
+ private createStrategy;
68
+ /**
69
+ * Creates a new, temporary email inbox.
70
+ *
71
+ * This method generates a new quantum-safe keypair, registers the inbox
72
+ * with the VaultSandbox server, and returns an `Inbox` instance.
73
+ *
74
+ * @param options - Optional parameters for inbox creation.
75
+ * @returns A promise that resolves to a new `Inbox` instance.
76
+ * @example
77
+ * const inbox = await client.createInbox({ ttl: 3600 }); // Inbox expires in 1 hour
78
+ */
79
+ createInbox(options?: CreateInboxOptions): Promise<Inbox>;
80
+ /**
81
+ * Deletes all inboxes associated with the current API key.
82
+ *
83
+ * @returns A promise that resolves to the number of inboxes deleted.
84
+ */
85
+ deleteAllInboxes(): Promise<number>;
86
+ /**
87
+ * Retrieves information about the VaultSandbox server.
88
+ *
89
+ * @returns A promise that resolves to the server information.
90
+ */
91
+ getServerInfo(): Promise<ServerInfo>;
92
+ /**
93
+ * Checks if the configured API key is valid.
94
+ *
95
+ * @returns A promise that resolves to `true` if the API key is valid, `false` otherwise.
96
+ */
97
+ checkKey(): Promise<boolean>;
98
+ /**
99
+ * Monitors multiple inboxes simultaneously for new emails.
100
+ *
101
+ * @param inboxes - An array of `Inbox` instances to monitor.
102
+ * @returns An `InboxMonitor` instance that emits 'email' events.
103
+ */
104
+ monitorInboxes(inboxes: Inbox[]): InboxMonitor;
105
+ /**
106
+ * Exports an inbox's data for backup or sharing purposes.
107
+ *
108
+ * @param inboxOrEmail - Either an Inbox instance or an email address string
109
+ * @returns The exported inbox data containing all necessary information to import the inbox
110
+ * @throws {InboxNotFoundError} If the inbox is not found in the client
111
+ * @example
112
+ * const exportedData = client.exportInbox(inbox);
113
+ * // or
114
+ * const exportedData = client.exportInbox('test@example.com');
115
+ */
116
+ exportInbox(inboxOrEmail: Inbox | string): ExportedInboxData;
117
+ /**
118
+ * Imports an inbox from exported data.
119
+ *
120
+ * @param data - The exported inbox data
121
+ * @returns A promise that resolves to the imported Inbox instance
122
+ * @throws {InvalidImportDataError} If the data is invalid or malformed
123
+ * @throws {InboxAlreadyExistsError} If an inbox with this email already exists
124
+ * @example
125
+ * const importedInbox = await client.importInbox(exportedData);
126
+ */
127
+ importInbox(data: ExportedInboxData): Promise<Inbox>;
128
+ /**
129
+ * Validates that all required fields are present and non-empty in the exported inbox data.
130
+ * @private
131
+ * @param data - The exported inbox data to validate
132
+ * @throws {InvalidImportDataError} If any required field is missing, not a string, or empty
133
+ */
134
+ private validateRequiredFields;
135
+ /**
136
+ * Ensures the public key is present in the exported data, deriving it from the secret key if necessary.
137
+ * In ML-KEM (Kyber), the public key is embedded in the secret key, so we can extract it if missing.
138
+ * @private
139
+ * @param data - The exported inbox data (will be mutated to add publicKeyB64 if missing)
140
+ * @throws {InvalidImportDataError} If the secret key is invalid or derivation fails
141
+ */
142
+ private ensurePublicKeyPresent;
143
+ /**
144
+ * Validates that the timestamp fields contain valid ISO 8601 date strings.
145
+ * @private
146
+ * @param data - The exported inbox data containing timestamps to validate
147
+ * @throws {InvalidImportDataError} If either timestamp is not a valid date format
148
+ */
149
+ private validateTimestamps;
150
+ /**
151
+ * Checks that an inbox with the given email address is not already tracked by this client.
152
+ * @private
153
+ * @param emailAddress - The email address to check
154
+ * @throws {InboxAlreadyExistsError} If an inbox with this email address already exists
155
+ */
156
+ private checkInboxDoesNotExist;
157
+ /**
158
+ * Validates that the server public key in the exported data matches the current server's key.
159
+ * This prevents importing inboxes that were created for a different VaultSandbox server.
160
+ * @private
161
+ * @param serverSigPk - The server public key from the exported data
162
+ * @throws {InvalidImportDataError} If the server public keys don't match
163
+ */
164
+ private validateServerPublicKey;
165
+ /**
166
+ * Decodes the cryptographic keys from base64 and validates their lengths.
167
+ * @private
168
+ * @param data - The exported inbox data containing base64-encoded keys
169
+ * @returns A keypair object with decoded keys and base64url-encoded public key
170
+ * @throws {InvalidImportDataError} If keys cannot be decoded or have invalid lengths
171
+ */
172
+ private decodeAndValidateKeys;
173
+ /**
174
+ * Decodes a base64-encoded cryptographic key to a byte array.
175
+ * @private
176
+ * @param keyB64 - The base64-encoded key string
177
+ * @param keyType - The type of key (e.g., 'public', 'secret') for error messages
178
+ * @returns The decoded key as a Uint8Array
179
+ * @throws {InvalidImportDataError} If the base64 string is malformed
180
+ */
181
+ private decodeBase64Key;
182
+ /**
183
+ * Validates that a cryptographic key has the expected byte length.
184
+ * @private
185
+ * @param key - The decoded key to validate
186
+ * @param expectedLength - The expected length in bytes
187
+ * @param keyType - The type of key (e.g., 'public', 'secret') for error messages
188
+ * @throws {InvalidImportDataError} If the key length doesn't match the expected length
189
+ */
190
+ private validateKeyLength;
191
+ /**
192
+ * Constructs an InboxData object from exported data.
193
+ * @private
194
+ * @param data - The exported inbox data
195
+ * @returns An InboxData object ready for creating an Inbox instance
196
+ */
197
+ private buildInboxData;
198
+ /**
199
+ * Creates a new Inbox instance, configures it with the delivery strategy, and adds it to tracking.
200
+ * @private
201
+ * @param inboxData - The inbox metadata
202
+ * @param keypair - The cryptographic keypair for the inbox
203
+ * @returns The newly created and tracked Inbox instance
204
+ */
205
+ private createAndTrackInbox;
206
+ /**
207
+ * Exports an inbox to a JSON file.
208
+ *
209
+ * @param inboxOrEmail - Either an Inbox instance or an email address string
210
+ * @param filePath - The path where the file should be written
211
+ * @throws {InboxNotFoundError} If the inbox is not found in the client
212
+ * @example
213
+ * await client.exportInboxToFile(inbox, './inbox-backup.json');
214
+ */
215
+ exportInboxToFile(inboxOrEmail: Inbox | string, filePath: string): Promise<void>;
216
+ /**
217
+ * Imports an inbox from a JSON file.
218
+ *
219
+ * @param filePath - The path to the exported inbox JSON file
220
+ * @returns A promise that resolves to the imported Inbox instance
221
+ * @throws {InvalidImportDataError} If the file cannot be read or parsed
222
+ * @throws {InboxAlreadyExistsError} If an inbox with this email already exists
223
+ * @example
224
+ * const importedInbox = await client.importInboxFromFile('./inbox-backup.json');
225
+ */
226
+ importInboxFromFile(filePath: string): Promise<Inbox>;
227
+ /**
228
+ * Closes the client, terminates any active connections, and cleans up resources.
229
+ */
230
+ close(): Promise<void>;
231
+ }
package/dist/client.js ADDED
@@ -0,0 +1,432 @@
1
+ /**
2
+ * VaultSandboxClient - Main entry point for the SDK
3
+ */
4
+ import { EventEmitter } from 'events';
5
+ import { readFile, writeFile } from 'fs/promises';
6
+ import createDebug from 'debug';
7
+ import { ApiClient } from './http/api-client.js';
8
+ import { Inbox } from './inbox.js';
9
+ import { generateKeypair, PUBLIC_KEY_SIZE, SECRET_KEY_SIZE, derivePublicKeyFromSecret } from './crypto/keypair.js';
10
+ import { toBase64Url, toBase64, fromBase64 } from './crypto/utils.js';
11
+ import { SSEStrategy } from './strategies/sse-strategy.js';
12
+ import { PollingStrategy } from './strategies/polling-strategy.js';
13
+ import { InboxNotFoundError, InboxAlreadyExistsError, InvalidImportDataError, StrategyError } from './types/index.js';
14
+ const debug = createDebug('vaultsandbox:client');
15
+ /**
16
+ * An event emitter for monitoring multiple inboxes simultaneously.
17
+ * @emits email - When a new email arrives in any of the monitored inboxes.
18
+ *
19
+ * @example
20
+ * const monitor = client.monitorInboxes([inbox1, inbox2]);
21
+ * monitor.on('email', (inbox, email) => {
22
+ * console.log(`New email in ${inbox.emailAddress}: ${email.subject}`);
23
+ * });
24
+ * // To stop monitoring:
25
+ * monitor.unsubscribe();
26
+ */
27
+ export class InboxMonitor extends EventEmitter {
28
+ subscriptions = [];
29
+ /**
30
+ * @internal
31
+ * Adds a subscription to the monitor.
32
+ * @param subscription - The subscription to add.
33
+ */
34
+ addSubscription(subscription) {
35
+ this.subscriptions.push(subscription);
36
+ }
37
+ /**
38
+ * Unsubscribes from all monitored inboxes and cleans up resources.
39
+ */
40
+ unsubscribe() {
41
+ this.subscriptions.forEach((sub) => sub.unsubscribe());
42
+ this.subscriptions = [];
43
+ this.removeAllListeners();
44
+ }
45
+ /**
46
+ * @internal
47
+ * Emits an 'email' event for a specific inbox.
48
+ * @param inbox - The inbox that received the email.
49
+ * @param email - The email that was received.
50
+ */
51
+ emitEmail(inbox, email) {
52
+ this.emit('email', inbox, email);
53
+ }
54
+ }
55
+ /**
56
+ * The main client for interacting with the VaultSandbox API.
57
+ *
58
+ * This class provides methods for creating and managing inboxes,
59
+ * as well as for monitoring them for new emails.
60
+ */
61
+ export class VaultSandboxClient {
62
+ apiClient;
63
+ config;
64
+ serverPublicKey = null;
65
+ inboxes = new Map();
66
+ strategy = null;
67
+ /**
68
+ * Creates a new VaultSandboxClient instance.
69
+ * @param config - The client configuration.
70
+ */
71
+ constructor(config) {
72
+ this.config = config;
73
+ this.apiClient = new ApiClient(config);
74
+ }
75
+ /**
76
+ * Initializes the client by fetching server info and creating a delivery strategy.
77
+ * This method is called automatically when needed and should not be called directly.
78
+ * @private
79
+ */
80
+ async ensureInitialized() {
81
+ if (this.serverPublicKey) {
82
+ return;
83
+ }
84
+ const serverInfo = await this.apiClient.getServerInfo();
85
+ this.serverPublicKey = serverInfo.serverSigPk;
86
+ // Create delivery strategy based on config
87
+ // Note: SSE for email events (/api/events) is always available
88
+ this.strategy = this.createStrategy();
89
+ }
90
+ /**
91
+ * Creates the appropriate delivery strategy based on configuration.
92
+ * SSE for email events (/api/events) is always available on the server.
93
+ * @private
94
+ */
95
+ createStrategy() {
96
+ const strategyType = this.config.strategy ?? 'auto';
97
+ // SSE strategy (default for 'auto' and 'sse')
98
+ if (strategyType === 'sse' || strategyType === 'auto') {
99
+ debug('Using SSE strategy for real-time delivery');
100
+ return new SSEStrategy(this.apiClient, {
101
+ url: this.config.url,
102
+ apiKey: this.config.apiKey,
103
+ reconnectInterval: this.config.sseReconnectInterval ?? 5000,
104
+ maxReconnectAttempts: this.config.sseMaxReconnectAttempts ?? 10,
105
+ backoffMultiplier: 2,
106
+ });
107
+ }
108
+ // Polling strategy (explicit only)
109
+ debug('Using polling strategy');
110
+ return new PollingStrategy(this.apiClient, {
111
+ initialInterval: this.config.pollingInterval ?? 2000,
112
+ maxBackoff: 30000,
113
+ backoffMultiplier: 1.5,
114
+ jitterFactor: 0.3,
115
+ });
116
+ }
117
+ /**
118
+ * Creates a new, temporary email inbox.
119
+ *
120
+ * This method generates a new quantum-safe keypair, registers the inbox
121
+ * with the VaultSandbox server, and returns an `Inbox` instance.
122
+ *
123
+ * @param options - Optional parameters for inbox creation.
124
+ * @returns A promise that resolves to a new `Inbox` instance.
125
+ * @example
126
+ * const inbox = await client.createInbox({ ttl: 3600 }); // Inbox expires in 1 hour
127
+ */
128
+ async createInbox(options = {}) {
129
+ await this.ensureInitialized();
130
+ // Generate keypair
131
+ const keypair = generateKeypair();
132
+ // Create inbox on server
133
+ const inboxData = await this.apiClient.createInbox(keypair.publicKeyB64, options.ttl, options.emailAddress);
134
+ // Create Inbox instance (use serverSigPk from response)
135
+ const inbox = new Inbox(inboxData, keypair, this.apiClient, inboxData.serverSigPk);
136
+ // Set delivery strategy
137
+ if (this.strategy) {
138
+ inbox.setStrategy(this.strategy);
139
+ }
140
+ // Track inbox
141
+ this.inboxes.set(inbox.emailAddress, inbox);
142
+ return inbox;
143
+ }
144
+ /**
145
+ * Deletes all inboxes associated with the current API key.
146
+ *
147
+ * @returns A promise that resolves to the number of inboxes deleted.
148
+ */
149
+ async deleteAllInboxes() {
150
+ const result = await this.apiClient.deleteAllInboxes();
151
+ this.inboxes.clear();
152
+ return result.deleted;
153
+ }
154
+ /**
155
+ * Retrieves information about the VaultSandbox server.
156
+ *
157
+ * @returns A promise that resolves to the server information.
158
+ */
159
+ async getServerInfo() {
160
+ return this.apiClient.getServerInfo();
161
+ }
162
+ /**
163
+ * Checks if the configured API key is valid.
164
+ *
165
+ * @returns A promise that resolves to `true` if the API key is valid, `false` otherwise.
166
+ */
167
+ async checkKey() {
168
+ return this.apiClient.checkKey();
169
+ }
170
+ /**
171
+ * Monitors multiple inboxes simultaneously for new emails.
172
+ *
173
+ * @param inboxes - An array of `Inbox` instances to monitor.
174
+ * @returns An `InboxMonitor` instance that emits 'email' events.
175
+ */
176
+ monitorInboxes(inboxes) {
177
+ if (!this.strategy) {
178
+ throw new StrategyError('No delivery strategy available. Client not initialized.');
179
+ }
180
+ const monitor = new InboxMonitor();
181
+ // Subscribe to each inbox
182
+ for (const inbox of inboxes) {
183
+ const subscription = inbox.onNewEmail((email) => {
184
+ monitor.emitEmail(inbox, email);
185
+ });
186
+ monitor.addSubscription(subscription);
187
+ }
188
+ return monitor;
189
+ }
190
+ /**
191
+ * Exports an inbox's data for backup or sharing purposes.
192
+ *
193
+ * @param inboxOrEmail - Either an Inbox instance or an email address string
194
+ * @returns The exported inbox data containing all necessary information to import the inbox
195
+ * @throws {InboxNotFoundError} If the inbox is not found in the client
196
+ * @example
197
+ * const exportedData = client.exportInbox(inbox);
198
+ * // or
199
+ * const exportedData = client.exportInbox('test@example.com');
200
+ */
201
+ exportInbox(inboxOrEmail) {
202
+ // Get the inbox instance
203
+ const emailAddress = typeof inboxOrEmail === 'string' ? inboxOrEmail : inboxOrEmail.emailAddress;
204
+ const inbox = this.inboxes.get(emailAddress);
205
+ if (!inbox) {
206
+ throw new InboxNotFoundError(`Inbox not found: ${emailAddress}`);
207
+ }
208
+ return inbox.export();
209
+ }
210
+ /**
211
+ * Imports an inbox from exported data.
212
+ *
213
+ * @param data - The exported inbox data
214
+ * @returns A promise that resolves to the imported Inbox instance
215
+ * @throws {InvalidImportDataError} If the data is invalid or malformed
216
+ * @throws {InboxAlreadyExistsError} If an inbox with this email already exists
217
+ * @example
218
+ * const importedInbox = await client.importInbox(exportedData);
219
+ */
220
+ async importInbox(data) {
221
+ this.validateRequiredFields(data);
222
+ this.ensurePublicKeyPresent(data);
223
+ this.validateTimestamps(data);
224
+ this.checkInboxDoesNotExist(data.emailAddress);
225
+ await this.ensureInitialized();
226
+ this.validateServerPublicKey(data.serverSigPk);
227
+ const keypair = this.decodeAndValidateKeys(data);
228
+ const inboxData = this.buildInboxData(data);
229
+ return this.createAndTrackInbox(inboxData, keypair);
230
+ }
231
+ /**
232
+ * Validates that all required fields are present and non-empty in the exported inbox data.
233
+ * @private
234
+ * @param data - The exported inbox data to validate
235
+ * @throws {InvalidImportDataError} If any required field is missing, not a string, or empty
236
+ */
237
+ validateRequiredFields(data) {
238
+ const requiredFields = [
239
+ 'emailAddress',
240
+ 'expiresAt',
241
+ 'inboxHash',
242
+ 'serverSigPk',
243
+ 'secretKeyB64',
244
+ 'exportedAt',
245
+ ];
246
+ for (const field of requiredFields) {
247
+ if (!data[field] || typeof data[field] !== 'string' || data[field].trim() === '') {
248
+ throw new InvalidImportDataError(`Missing or invalid field: ${field}`);
249
+ }
250
+ }
251
+ }
252
+ /**
253
+ * Ensures the public key is present in the exported data, deriving it from the secret key if necessary.
254
+ * In ML-KEM (Kyber), the public key is embedded in the secret key, so we can extract it if missing.
255
+ * @private
256
+ * @param data - The exported inbox data (will be mutated to add publicKeyB64 if missing)
257
+ * @throws {InvalidImportDataError} If the secret key is invalid or derivation fails
258
+ */
259
+ ensurePublicKeyPresent(data) {
260
+ if (data.publicKeyB64) {
261
+ return;
262
+ }
263
+ try {
264
+ const secretKeyBytes = fromBase64(data.secretKeyB64);
265
+ const publicKeyBytes = derivePublicKeyFromSecret(secretKeyBytes);
266
+ data.publicKeyB64 = toBase64(publicKeyBytes);
267
+ }
268
+ catch (error) {
269
+ const message = error instanceof Error ? error.message : String(error);
270
+ throw new InvalidImportDataError(`Failed to derive public key from secret key: ${message}`);
271
+ }
272
+ }
273
+ /**
274
+ * Validates that the timestamp fields contain valid ISO 8601 date strings.
275
+ * @private
276
+ * @param data - The exported inbox data containing timestamps to validate
277
+ * @throws {InvalidImportDataError} If either timestamp is not a valid date format
278
+ */
279
+ validateTimestamps(data) {
280
+ try {
281
+ new Date(data.expiresAt).toISOString();
282
+ new Date(data.exportedAt).toISOString();
283
+ }
284
+ catch {
285
+ throw new InvalidImportDataError('Invalid timestamp format');
286
+ }
287
+ }
288
+ /**
289
+ * Checks that an inbox with the given email address is not already tracked by this client.
290
+ * @private
291
+ * @param emailAddress - The email address to check
292
+ * @throws {InboxAlreadyExistsError} If an inbox with this email address already exists
293
+ */
294
+ checkInboxDoesNotExist(emailAddress) {
295
+ if (this.inboxes.has(emailAddress)) {
296
+ throw new InboxAlreadyExistsError(`Inbox already exists: ${emailAddress}`);
297
+ }
298
+ }
299
+ /**
300
+ * Validates that the server public key in the exported data matches the current server's key.
301
+ * This prevents importing inboxes that were created for a different VaultSandbox server.
302
+ * @private
303
+ * @param serverSigPk - The server public key from the exported data
304
+ * @throws {InvalidImportDataError} If the server public keys don't match
305
+ */
306
+ validateServerPublicKey(serverSigPk) {
307
+ if (serverSigPk !== this.serverPublicKey) {
308
+ throw new InvalidImportDataError('Server public key mismatch. This inbox was created for a different server.');
309
+ }
310
+ }
311
+ /**
312
+ * Decodes the cryptographic keys from base64 and validates their lengths.
313
+ * @private
314
+ * @param data - The exported inbox data containing base64-encoded keys
315
+ * @returns A keypair object with decoded keys and base64url-encoded public key
316
+ * @throws {InvalidImportDataError} If keys cannot be decoded or have invalid lengths
317
+ */
318
+ decodeAndValidateKeys(data) {
319
+ const publicKey = this.decodeBase64Key(data.publicKeyB64, 'public');
320
+ const secretKey = this.decodeBase64Key(data.secretKeyB64, 'secret');
321
+ this.validateKeyLength(publicKey, PUBLIC_KEY_SIZE, 'public');
322
+ this.validateKeyLength(secretKey, SECRET_KEY_SIZE, 'secret');
323
+ return {
324
+ publicKey,
325
+ secretKey,
326
+ publicKeyB64: toBase64Url(publicKey),
327
+ };
328
+ }
329
+ /**
330
+ * Decodes a base64-encoded cryptographic key to a byte array.
331
+ * @private
332
+ * @param keyB64 - The base64-encoded key string
333
+ * @param keyType - The type of key (e.g., 'public', 'secret') for error messages
334
+ * @returns The decoded key as a Uint8Array
335
+ * @throws {InvalidImportDataError} If the base64 string is malformed
336
+ */
337
+ decodeBase64Key(keyB64, keyType) {
338
+ try {
339
+ return fromBase64(keyB64);
340
+ }
341
+ catch {
342
+ throw new InvalidImportDataError(`Invalid base64 encoding in ${keyType} key`);
343
+ }
344
+ }
345
+ /**
346
+ * Validates that a cryptographic key has the expected byte length.
347
+ * @private
348
+ * @param key - The decoded key to validate
349
+ * @param expectedLength - The expected length in bytes
350
+ * @param keyType - The type of key (e.g., 'public', 'secret') for error messages
351
+ * @throws {InvalidImportDataError} If the key length doesn't match the expected length
352
+ */
353
+ validateKeyLength(key, expectedLength, keyType) {
354
+ if (key.length !== expectedLength) {
355
+ throw new InvalidImportDataError(`Invalid ${keyType} key length: expected ${expectedLength}, got ${key.length}`);
356
+ }
357
+ }
358
+ /**
359
+ * Constructs an InboxData object from exported data.
360
+ * @private
361
+ * @param data - The exported inbox data
362
+ * @returns An InboxData object ready for creating an Inbox instance
363
+ */
364
+ buildInboxData(data) {
365
+ return {
366
+ emailAddress: data.emailAddress,
367
+ expiresAt: data.expiresAt,
368
+ inboxHash: data.inboxHash,
369
+ serverSigPk: data.serverSigPk,
370
+ };
371
+ }
372
+ /**
373
+ * Creates a new Inbox instance, configures it with the delivery strategy, and adds it to tracking.
374
+ * @private
375
+ * @param inboxData - The inbox metadata
376
+ * @param keypair - The cryptographic keypair for the inbox
377
+ * @returns The newly created and tracked Inbox instance
378
+ */
379
+ createAndTrackInbox(inboxData, keypair) {
380
+ const inbox = new Inbox(inboxData, keypair, this.apiClient, inboxData.serverSigPk);
381
+ if (this.strategy) {
382
+ inbox.setStrategy(this.strategy);
383
+ }
384
+ this.inboxes.set(inbox.emailAddress, inbox);
385
+ return inbox;
386
+ }
387
+ /**
388
+ * Exports an inbox to a JSON file.
389
+ *
390
+ * @param inboxOrEmail - Either an Inbox instance or an email address string
391
+ * @param filePath - The path where the file should be written
392
+ * @throws {InboxNotFoundError} If the inbox is not found in the client
393
+ * @example
394
+ * await client.exportInboxToFile(inbox, './inbox-backup.json');
395
+ */
396
+ async exportInboxToFile(inboxOrEmail, filePath) {
397
+ const data = this.exportInbox(inboxOrEmail);
398
+ const json = JSON.stringify(data, null, 2);
399
+ await writeFile(filePath, json, 'utf-8');
400
+ }
401
+ /**
402
+ * Imports an inbox from a JSON file.
403
+ *
404
+ * @param filePath - The path to the exported inbox JSON file
405
+ * @returns A promise that resolves to the imported Inbox instance
406
+ * @throws {InvalidImportDataError} If the file cannot be read or parsed
407
+ * @throws {InboxAlreadyExistsError} If an inbox with this email already exists
408
+ * @example
409
+ * const importedInbox = await client.importInboxFromFile('./inbox-backup.json');
410
+ */
411
+ async importInboxFromFile(filePath) {
412
+ let data;
413
+ try {
414
+ const fileContents = await readFile(filePath, 'utf-8');
415
+ data = JSON.parse(fileContents);
416
+ }
417
+ catch (error) {
418
+ throw new InvalidImportDataError(`Failed to read or parse file: ${error instanceof Error ? error.message : 'Unknown error'}`);
419
+ }
420
+ return await this.importInbox(data);
421
+ }
422
+ /**
423
+ * Closes the client, terminates any active connections, and cleans up resources.
424
+ */
425
+ async close() {
426
+ if (this.strategy) {
427
+ this.strategy.close();
428
+ }
429
+ this.inboxes.clear();
430
+ }
431
+ }
432
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}