wolfronix-sdk 2.4.3 → 2.4.4
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 +189 -726
- package/dist/index.d.mts +161 -3
- package/dist/index.d.ts +161 -3
- package/dist/index.global.js +3 -3
- package/dist/index.js +1067 -18
- package/dist/index.mjs +1067 -18
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Zero-knowledge encryption made simple
|
|
4
4
|
*
|
|
5
5
|
* @package @wolfronix/sdk
|
|
6
|
-
* @version 2.4.
|
|
6
|
+
* @version 2.4.4
|
|
7
7
|
*/
|
|
8
8
|
interface WolfronixConfig {
|
|
9
9
|
/** Wolfronix server base URL */
|
|
@@ -25,6 +25,14 @@ interface AuthResponse {
|
|
|
25
25
|
token: string;
|
|
26
26
|
message: string;
|
|
27
27
|
}
|
|
28
|
+
interface RecoverySetup {
|
|
29
|
+
recoveryPhrase: string;
|
|
30
|
+
recoveryWords: string[];
|
|
31
|
+
}
|
|
32
|
+
interface RegisterOptions {
|
|
33
|
+
enableRecovery?: boolean;
|
|
34
|
+
recoveryPhrase?: string;
|
|
35
|
+
}
|
|
28
36
|
interface EncryptResponse {
|
|
29
37
|
status: string;
|
|
30
38
|
file_id: string;
|
|
@@ -39,6 +47,36 @@ interface EncryptResponse {
|
|
|
39
47
|
/** Any extra fields from the server response */
|
|
40
48
|
[key: string]: unknown;
|
|
41
49
|
}
|
|
50
|
+
interface ChunkedEncryptResult {
|
|
51
|
+
upload_id: string;
|
|
52
|
+
filename: string;
|
|
53
|
+
total_chunks: number;
|
|
54
|
+
chunk_size_bytes: number;
|
|
55
|
+
uploaded_chunks: number;
|
|
56
|
+
chunk_file_ids: string[];
|
|
57
|
+
complete: boolean;
|
|
58
|
+
}
|
|
59
|
+
interface ResumableUploadState {
|
|
60
|
+
upload_id: string;
|
|
61
|
+
filename: string;
|
|
62
|
+
file_size: number;
|
|
63
|
+
chunk_size_bytes: number;
|
|
64
|
+
total_chunks: number;
|
|
65
|
+
uploaded_chunks: number[];
|
|
66
|
+
chunk_file_ids: string[];
|
|
67
|
+
created_at: number;
|
|
68
|
+
updated_at: number;
|
|
69
|
+
}
|
|
70
|
+
interface ResumableEncryptOptions {
|
|
71
|
+
filename?: string;
|
|
72
|
+
chunkSizeBytes?: number;
|
|
73
|
+
existingState?: ResumableUploadState;
|
|
74
|
+
onProgress?: (uploadedChunks: number, totalChunks: number) => void;
|
|
75
|
+
}
|
|
76
|
+
interface ChunkedDecryptManifest {
|
|
77
|
+
filename: string;
|
|
78
|
+
chunk_file_ids: string[];
|
|
79
|
+
}
|
|
42
80
|
interface FileInfo {
|
|
43
81
|
file_id: string;
|
|
44
82
|
original_name: string;
|
|
@@ -71,6 +109,50 @@ interface EncryptMessagePacket {
|
|
|
71
109
|
iv: string;
|
|
72
110
|
msg: string;
|
|
73
111
|
}
|
|
112
|
+
interface GroupEncryptPacket {
|
|
113
|
+
v: 1;
|
|
114
|
+
type: 'group_sender_key';
|
|
115
|
+
sender_id: string;
|
|
116
|
+
group_id: string;
|
|
117
|
+
timestamp: number;
|
|
118
|
+
ciphertext: string;
|
|
119
|
+
iv: string;
|
|
120
|
+
recipient_keys: Record<string, string>;
|
|
121
|
+
}
|
|
122
|
+
interface PfsPreKeyBundle {
|
|
123
|
+
protocol: 'wfx-dr-v1';
|
|
124
|
+
user_id?: string;
|
|
125
|
+
ratchet_pub_jwk: JsonWebKey;
|
|
126
|
+
created_at: number;
|
|
127
|
+
}
|
|
128
|
+
interface PfsMessagePacket {
|
|
129
|
+
v: 1;
|
|
130
|
+
type: 'pfs_ratchet';
|
|
131
|
+
session_id: string;
|
|
132
|
+
n: number;
|
|
133
|
+
pn: number;
|
|
134
|
+
ratchet_pub_jwk: JsonWebKey;
|
|
135
|
+
iv: string;
|
|
136
|
+
ciphertext: string;
|
|
137
|
+
timestamp: number;
|
|
138
|
+
}
|
|
139
|
+
interface PfsSessionState {
|
|
140
|
+
protocol: 'wfx-dr-v1';
|
|
141
|
+
session_id: string;
|
|
142
|
+
role: 'initiator' | 'responder';
|
|
143
|
+
root_key: string;
|
|
144
|
+
send_chain_key: string;
|
|
145
|
+
recv_chain_key: string;
|
|
146
|
+
send_count: number;
|
|
147
|
+
recv_count: number;
|
|
148
|
+
prev_send_count: number;
|
|
149
|
+
my_ratchet_private_jwk: JsonWebKey;
|
|
150
|
+
my_ratchet_public_jwk: JsonWebKey;
|
|
151
|
+
their_ratchet_public_jwk: JsonWebKey;
|
|
152
|
+
skipped_keys: Record<string, string>;
|
|
153
|
+
created_at: number;
|
|
154
|
+
updated_at: number;
|
|
155
|
+
}
|
|
74
156
|
interface ServerEncryptResult {
|
|
75
157
|
/** Base64-encoded ciphertext */
|
|
76
158
|
encrypted_message: string;
|
|
@@ -211,6 +293,9 @@ declare class Wolfronix {
|
|
|
211
293
|
private publicKey;
|
|
212
294
|
private privateKey;
|
|
213
295
|
private publicKeyPEM;
|
|
296
|
+
private pfsIdentityPrivateJwk;
|
|
297
|
+
private pfsIdentityPublicJwk;
|
|
298
|
+
private pfsSessions;
|
|
214
299
|
/** Expose private key status for testing */
|
|
215
300
|
hasPrivateKey(): boolean;
|
|
216
301
|
/**
|
|
@@ -229,6 +314,11 @@ declare class Wolfronix {
|
|
|
229
314
|
private request;
|
|
230
315
|
private sleep;
|
|
231
316
|
private ensureAuthenticated;
|
|
317
|
+
private toBlob;
|
|
318
|
+
private ensurePfsIdentity;
|
|
319
|
+
private getPfsSession;
|
|
320
|
+
private ratchetForSend;
|
|
321
|
+
private ratchetForReceive;
|
|
232
322
|
/**
|
|
233
323
|
* Register a new user
|
|
234
324
|
*
|
|
@@ -237,7 +327,7 @@ declare class Wolfronix {
|
|
|
237
327
|
* const { user_id, token } = await wfx.register('user@example.com', 'password123');
|
|
238
328
|
* ```
|
|
239
329
|
*/
|
|
240
|
-
register(email: string, password: string): Promise<AuthResponse
|
|
330
|
+
register(email: string, password: string, options?: RegisterOptions): Promise<AuthResponse & Partial<RecoverySetup>>;
|
|
241
331
|
/**
|
|
242
332
|
* Login with existing credentials
|
|
243
333
|
*
|
|
@@ -247,6 +337,20 @@ declare class Wolfronix {
|
|
|
247
337
|
* ```
|
|
248
338
|
*/
|
|
249
339
|
login(email: string, password: string): Promise<AuthResponse>;
|
|
340
|
+
/**
|
|
341
|
+
* Recover account keys using a 24-word recovery phrase and set a new password.
|
|
342
|
+
* Returns a fresh local auth session if recovery succeeds.
|
|
343
|
+
*/
|
|
344
|
+
recoverAccount(email: string, recoveryPhrase: string, newPassword: string): Promise<AuthResponse>;
|
|
345
|
+
/**
|
|
346
|
+
* Rotates long-term RSA identity keys and re-wraps with password (+ optional recovery phrase).
|
|
347
|
+
* Use this periodically to reduce long-term key exposure.
|
|
348
|
+
*/
|
|
349
|
+
rotateIdentityKeys(password: string, recoveryPhrase?: string): Promise<{
|
|
350
|
+
success: boolean;
|
|
351
|
+
message: string;
|
|
352
|
+
recoveryPhrase?: string;
|
|
353
|
+
}>;
|
|
250
354
|
/**
|
|
251
355
|
* Set authentication token directly (useful for server-side apps)
|
|
252
356
|
*
|
|
@@ -283,6 +387,15 @@ declare class Wolfronix {
|
|
|
283
387
|
* ```
|
|
284
388
|
*/
|
|
285
389
|
encrypt(file: File | Blob | ArrayBuffer | Uint8Array, filename?: string): Promise<EncryptResponse>;
|
|
390
|
+
/**
|
|
391
|
+
* Resumable large-file encryption upload.
|
|
392
|
+
* Splits a file into chunks (default 10MB) and uploads each chunk independently.
|
|
393
|
+
* If upload fails mid-way, pass the returned state as `existingState` to resume.
|
|
394
|
+
*/
|
|
395
|
+
encryptResumable(file: File | Blob | ArrayBuffer | Uint8Array, options?: ResumableEncryptOptions): Promise<{
|
|
396
|
+
result: ChunkedEncryptResult;
|
|
397
|
+
state: ResumableUploadState;
|
|
398
|
+
}>;
|
|
286
399
|
/**
|
|
287
400
|
* Decrypt and retrieve a file using zero-knowledge flow.
|
|
288
401
|
*
|
|
@@ -309,6 +422,15 @@ declare class Wolfronix {
|
|
|
309
422
|
* Decrypt and return as ArrayBuffer (zero-knowledge flow)
|
|
310
423
|
*/
|
|
311
424
|
decryptToBuffer(fileId: string, role?: string): Promise<ArrayBuffer>;
|
|
425
|
+
/**
|
|
426
|
+
* Decrypts and reassembles a chunked upload produced by `encryptResumable`.
|
|
427
|
+
*/
|
|
428
|
+
decryptChunkedToBuffer(manifest: ChunkedDecryptManifest, role?: string): Promise<ArrayBuffer>;
|
|
429
|
+
/**
|
|
430
|
+
* Decrypts and reassembles a chunked upload into a Blob.
|
|
431
|
+
* This is a browser-friendly alias over `decryptChunkedToBuffer`.
|
|
432
|
+
*/
|
|
433
|
+
decryptChunkedManifest(manifest: ChunkedDecryptManifest, role?: string): Promise<Blob>;
|
|
312
434
|
/**
|
|
313
435
|
* Fetch the encrypted key_part_a for a file (for client-side decryption)
|
|
314
436
|
*
|
|
@@ -355,6 +477,42 @@ declare class Wolfronix {
|
|
|
355
477
|
* @param packetJson The secure JSON string packet
|
|
356
478
|
*/
|
|
357
479
|
decryptMessage(packetJson: string): Promise<string>;
|
|
480
|
+
/**
|
|
481
|
+
* Create/share a pre-key bundle for Double Ratchet PFS session setup.
|
|
482
|
+
* Exchange this bundle out-of-band with the peer.
|
|
483
|
+
*/
|
|
484
|
+
createPfsPreKeyBundle(): Promise<PfsPreKeyBundle>;
|
|
485
|
+
/**
|
|
486
|
+
* Initialize a local PFS ratchet session from peer bundle.
|
|
487
|
+
* Both sides must call this with opposite `asInitiator` values.
|
|
488
|
+
*/
|
|
489
|
+
initPfsSession(sessionId: string, peerBundle: PfsPreKeyBundle, asInitiator: boolean): Promise<PfsSessionState>;
|
|
490
|
+
/**
|
|
491
|
+
* Export session state for persistence (e.g., localStorage/DB).
|
|
492
|
+
*/
|
|
493
|
+
exportPfsSession(sessionId: string): PfsSessionState;
|
|
494
|
+
/**
|
|
495
|
+
* Import session state from storage.
|
|
496
|
+
*/
|
|
497
|
+
importPfsSession(session: PfsSessionState): void;
|
|
498
|
+
/**
|
|
499
|
+
* Encrypt a message using Double Ratchet session state.
|
|
500
|
+
*/
|
|
501
|
+
pfsEncryptMessage(sessionId: string, plaintext: string): Promise<PfsMessagePacket>;
|
|
502
|
+
/**
|
|
503
|
+
* Decrypt a Double Ratchet packet for a session.
|
|
504
|
+
* Handles basic out-of-order delivery through skipped message keys.
|
|
505
|
+
*/
|
|
506
|
+
pfsDecryptMessage(sessionId: string, packet: PfsMessagePacket | string): Promise<string>;
|
|
507
|
+
/**
|
|
508
|
+
* Group message encryption using sender-key fanout:
|
|
509
|
+
* message encrypted once with AES key, AES key wrapped for each group member with their RSA public key.
|
|
510
|
+
*/
|
|
511
|
+
encryptGroupMessage(text: string, groupId: string, recipientIds: string[]): Promise<string>;
|
|
512
|
+
/**
|
|
513
|
+
* Decrypt a packet produced by `encryptGroupMessage`.
|
|
514
|
+
*/
|
|
515
|
+
decryptGroupMessage(packetJson: string): Promise<string>;
|
|
358
516
|
/**
|
|
359
517
|
* Encrypt a text message via the Wolfronix server (dual-key split).
|
|
360
518
|
* The server generates an AES key, encrypts the message, and splits the key —
|
|
@@ -615,4 +773,4 @@ declare class WolfronixAdmin {
|
|
|
615
773
|
healthCheck(): Promise<boolean>;
|
|
616
774
|
}
|
|
617
775
|
|
|
618
|
-
export { type AuthResponse, AuthenticationError, type DBType, type DeactivateClientResponse, type DeleteResponse, type EncryptMessagePacket, type EncryptResponse, type EnterpriseClient, type FileInfo, FileNotFoundError, type KeyPartResponse, type ListClientsResponse, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, type RegisterClientRequest, type RegisterClientResponse, type ServerBatchEncryptResult, type ServerDecryptParams, type ServerEncryptResult, type StreamChunk, type StreamSession, type UpdateClientRequest, type UpdateClientResponse, ValidationError, Wolfronix, WolfronixAdmin, type WolfronixAdminConfig, type WolfronixConfig, WolfronixError, WolfronixStream, createClient, Wolfronix as default };
|
|
776
|
+
export { type AuthResponse, AuthenticationError, type ChunkedDecryptManifest, type ChunkedEncryptResult, type DBType, type DeactivateClientResponse, type DeleteResponse, type EncryptMessagePacket, type EncryptResponse, type EnterpriseClient, type FileInfo, FileNotFoundError, type GroupEncryptPacket, type KeyPartResponse, type ListClientsResponse, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, type PfsMessagePacket, type PfsPreKeyBundle, type PfsSessionState, type RecoverySetup, type RegisterClientRequest, type RegisterClientResponse, type RegisterOptions, type ResumableEncryptOptions, type ResumableUploadState, type ServerBatchEncryptResult, type ServerDecryptParams, type ServerEncryptResult, type StreamChunk, type StreamSession, type UpdateClientRequest, type UpdateClientResponse, ValidationError, Wolfronix, WolfronixAdmin, type WolfronixAdminConfig, type WolfronixConfig, WolfronixError, WolfronixStream, createClient, Wolfronix as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Zero-knowledge encryption made simple
|
|
4
4
|
*
|
|
5
5
|
* @package @wolfronix/sdk
|
|
6
|
-
* @version 2.4.
|
|
6
|
+
* @version 2.4.4
|
|
7
7
|
*/
|
|
8
8
|
interface WolfronixConfig {
|
|
9
9
|
/** Wolfronix server base URL */
|
|
@@ -25,6 +25,14 @@ interface AuthResponse {
|
|
|
25
25
|
token: string;
|
|
26
26
|
message: string;
|
|
27
27
|
}
|
|
28
|
+
interface RecoverySetup {
|
|
29
|
+
recoveryPhrase: string;
|
|
30
|
+
recoveryWords: string[];
|
|
31
|
+
}
|
|
32
|
+
interface RegisterOptions {
|
|
33
|
+
enableRecovery?: boolean;
|
|
34
|
+
recoveryPhrase?: string;
|
|
35
|
+
}
|
|
28
36
|
interface EncryptResponse {
|
|
29
37
|
status: string;
|
|
30
38
|
file_id: string;
|
|
@@ -39,6 +47,36 @@ interface EncryptResponse {
|
|
|
39
47
|
/** Any extra fields from the server response */
|
|
40
48
|
[key: string]: unknown;
|
|
41
49
|
}
|
|
50
|
+
interface ChunkedEncryptResult {
|
|
51
|
+
upload_id: string;
|
|
52
|
+
filename: string;
|
|
53
|
+
total_chunks: number;
|
|
54
|
+
chunk_size_bytes: number;
|
|
55
|
+
uploaded_chunks: number;
|
|
56
|
+
chunk_file_ids: string[];
|
|
57
|
+
complete: boolean;
|
|
58
|
+
}
|
|
59
|
+
interface ResumableUploadState {
|
|
60
|
+
upload_id: string;
|
|
61
|
+
filename: string;
|
|
62
|
+
file_size: number;
|
|
63
|
+
chunk_size_bytes: number;
|
|
64
|
+
total_chunks: number;
|
|
65
|
+
uploaded_chunks: number[];
|
|
66
|
+
chunk_file_ids: string[];
|
|
67
|
+
created_at: number;
|
|
68
|
+
updated_at: number;
|
|
69
|
+
}
|
|
70
|
+
interface ResumableEncryptOptions {
|
|
71
|
+
filename?: string;
|
|
72
|
+
chunkSizeBytes?: number;
|
|
73
|
+
existingState?: ResumableUploadState;
|
|
74
|
+
onProgress?: (uploadedChunks: number, totalChunks: number) => void;
|
|
75
|
+
}
|
|
76
|
+
interface ChunkedDecryptManifest {
|
|
77
|
+
filename: string;
|
|
78
|
+
chunk_file_ids: string[];
|
|
79
|
+
}
|
|
42
80
|
interface FileInfo {
|
|
43
81
|
file_id: string;
|
|
44
82
|
original_name: string;
|
|
@@ -71,6 +109,50 @@ interface EncryptMessagePacket {
|
|
|
71
109
|
iv: string;
|
|
72
110
|
msg: string;
|
|
73
111
|
}
|
|
112
|
+
interface GroupEncryptPacket {
|
|
113
|
+
v: 1;
|
|
114
|
+
type: 'group_sender_key';
|
|
115
|
+
sender_id: string;
|
|
116
|
+
group_id: string;
|
|
117
|
+
timestamp: number;
|
|
118
|
+
ciphertext: string;
|
|
119
|
+
iv: string;
|
|
120
|
+
recipient_keys: Record<string, string>;
|
|
121
|
+
}
|
|
122
|
+
interface PfsPreKeyBundle {
|
|
123
|
+
protocol: 'wfx-dr-v1';
|
|
124
|
+
user_id?: string;
|
|
125
|
+
ratchet_pub_jwk: JsonWebKey;
|
|
126
|
+
created_at: number;
|
|
127
|
+
}
|
|
128
|
+
interface PfsMessagePacket {
|
|
129
|
+
v: 1;
|
|
130
|
+
type: 'pfs_ratchet';
|
|
131
|
+
session_id: string;
|
|
132
|
+
n: number;
|
|
133
|
+
pn: number;
|
|
134
|
+
ratchet_pub_jwk: JsonWebKey;
|
|
135
|
+
iv: string;
|
|
136
|
+
ciphertext: string;
|
|
137
|
+
timestamp: number;
|
|
138
|
+
}
|
|
139
|
+
interface PfsSessionState {
|
|
140
|
+
protocol: 'wfx-dr-v1';
|
|
141
|
+
session_id: string;
|
|
142
|
+
role: 'initiator' | 'responder';
|
|
143
|
+
root_key: string;
|
|
144
|
+
send_chain_key: string;
|
|
145
|
+
recv_chain_key: string;
|
|
146
|
+
send_count: number;
|
|
147
|
+
recv_count: number;
|
|
148
|
+
prev_send_count: number;
|
|
149
|
+
my_ratchet_private_jwk: JsonWebKey;
|
|
150
|
+
my_ratchet_public_jwk: JsonWebKey;
|
|
151
|
+
their_ratchet_public_jwk: JsonWebKey;
|
|
152
|
+
skipped_keys: Record<string, string>;
|
|
153
|
+
created_at: number;
|
|
154
|
+
updated_at: number;
|
|
155
|
+
}
|
|
74
156
|
interface ServerEncryptResult {
|
|
75
157
|
/** Base64-encoded ciphertext */
|
|
76
158
|
encrypted_message: string;
|
|
@@ -211,6 +293,9 @@ declare class Wolfronix {
|
|
|
211
293
|
private publicKey;
|
|
212
294
|
private privateKey;
|
|
213
295
|
private publicKeyPEM;
|
|
296
|
+
private pfsIdentityPrivateJwk;
|
|
297
|
+
private pfsIdentityPublicJwk;
|
|
298
|
+
private pfsSessions;
|
|
214
299
|
/** Expose private key status for testing */
|
|
215
300
|
hasPrivateKey(): boolean;
|
|
216
301
|
/**
|
|
@@ -229,6 +314,11 @@ declare class Wolfronix {
|
|
|
229
314
|
private request;
|
|
230
315
|
private sleep;
|
|
231
316
|
private ensureAuthenticated;
|
|
317
|
+
private toBlob;
|
|
318
|
+
private ensurePfsIdentity;
|
|
319
|
+
private getPfsSession;
|
|
320
|
+
private ratchetForSend;
|
|
321
|
+
private ratchetForReceive;
|
|
232
322
|
/**
|
|
233
323
|
* Register a new user
|
|
234
324
|
*
|
|
@@ -237,7 +327,7 @@ declare class Wolfronix {
|
|
|
237
327
|
* const { user_id, token } = await wfx.register('user@example.com', 'password123');
|
|
238
328
|
* ```
|
|
239
329
|
*/
|
|
240
|
-
register(email: string, password: string): Promise<AuthResponse
|
|
330
|
+
register(email: string, password: string, options?: RegisterOptions): Promise<AuthResponse & Partial<RecoverySetup>>;
|
|
241
331
|
/**
|
|
242
332
|
* Login with existing credentials
|
|
243
333
|
*
|
|
@@ -247,6 +337,20 @@ declare class Wolfronix {
|
|
|
247
337
|
* ```
|
|
248
338
|
*/
|
|
249
339
|
login(email: string, password: string): Promise<AuthResponse>;
|
|
340
|
+
/**
|
|
341
|
+
* Recover account keys using a 24-word recovery phrase and set a new password.
|
|
342
|
+
* Returns a fresh local auth session if recovery succeeds.
|
|
343
|
+
*/
|
|
344
|
+
recoverAccount(email: string, recoveryPhrase: string, newPassword: string): Promise<AuthResponse>;
|
|
345
|
+
/**
|
|
346
|
+
* Rotates long-term RSA identity keys and re-wraps with password (+ optional recovery phrase).
|
|
347
|
+
* Use this periodically to reduce long-term key exposure.
|
|
348
|
+
*/
|
|
349
|
+
rotateIdentityKeys(password: string, recoveryPhrase?: string): Promise<{
|
|
350
|
+
success: boolean;
|
|
351
|
+
message: string;
|
|
352
|
+
recoveryPhrase?: string;
|
|
353
|
+
}>;
|
|
250
354
|
/**
|
|
251
355
|
* Set authentication token directly (useful for server-side apps)
|
|
252
356
|
*
|
|
@@ -283,6 +387,15 @@ declare class Wolfronix {
|
|
|
283
387
|
* ```
|
|
284
388
|
*/
|
|
285
389
|
encrypt(file: File | Blob | ArrayBuffer | Uint8Array, filename?: string): Promise<EncryptResponse>;
|
|
390
|
+
/**
|
|
391
|
+
* Resumable large-file encryption upload.
|
|
392
|
+
* Splits a file into chunks (default 10MB) and uploads each chunk independently.
|
|
393
|
+
* If upload fails mid-way, pass the returned state as `existingState` to resume.
|
|
394
|
+
*/
|
|
395
|
+
encryptResumable(file: File | Blob | ArrayBuffer | Uint8Array, options?: ResumableEncryptOptions): Promise<{
|
|
396
|
+
result: ChunkedEncryptResult;
|
|
397
|
+
state: ResumableUploadState;
|
|
398
|
+
}>;
|
|
286
399
|
/**
|
|
287
400
|
* Decrypt and retrieve a file using zero-knowledge flow.
|
|
288
401
|
*
|
|
@@ -309,6 +422,15 @@ declare class Wolfronix {
|
|
|
309
422
|
* Decrypt and return as ArrayBuffer (zero-knowledge flow)
|
|
310
423
|
*/
|
|
311
424
|
decryptToBuffer(fileId: string, role?: string): Promise<ArrayBuffer>;
|
|
425
|
+
/**
|
|
426
|
+
* Decrypts and reassembles a chunked upload produced by `encryptResumable`.
|
|
427
|
+
*/
|
|
428
|
+
decryptChunkedToBuffer(manifest: ChunkedDecryptManifest, role?: string): Promise<ArrayBuffer>;
|
|
429
|
+
/**
|
|
430
|
+
* Decrypts and reassembles a chunked upload into a Blob.
|
|
431
|
+
* This is a browser-friendly alias over `decryptChunkedToBuffer`.
|
|
432
|
+
*/
|
|
433
|
+
decryptChunkedManifest(manifest: ChunkedDecryptManifest, role?: string): Promise<Blob>;
|
|
312
434
|
/**
|
|
313
435
|
* Fetch the encrypted key_part_a for a file (for client-side decryption)
|
|
314
436
|
*
|
|
@@ -355,6 +477,42 @@ declare class Wolfronix {
|
|
|
355
477
|
* @param packetJson The secure JSON string packet
|
|
356
478
|
*/
|
|
357
479
|
decryptMessage(packetJson: string): Promise<string>;
|
|
480
|
+
/**
|
|
481
|
+
* Create/share a pre-key bundle for Double Ratchet PFS session setup.
|
|
482
|
+
* Exchange this bundle out-of-band with the peer.
|
|
483
|
+
*/
|
|
484
|
+
createPfsPreKeyBundle(): Promise<PfsPreKeyBundle>;
|
|
485
|
+
/**
|
|
486
|
+
* Initialize a local PFS ratchet session from peer bundle.
|
|
487
|
+
* Both sides must call this with opposite `asInitiator` values.
|
|
488
|
+
*/
|
|
489
|
+
initPfsSession(sessionId: string, peerBundle: PfsPreKeyBundle, asInitiator: boolean): Promise<PfsSessionState>;
|
|
490
|
+
/**
|
|
491
|
+
* Export session state for persistence (e.g., localStorage/DB).
|
|
492
|
+
*/
|
|
493
|
+
exportPfsSession(sessionId: string): PfsSessionState;
|
|
494
|
+
/**
|
|
495
|
+
* Import session state from storage.
|
|
496
|
+
*/
|
|
497
|
+
importPfsSession(session: PfsSessionState): void;
|
|
498
|
+
/**
|
|
499
|
+
* Encrypt a message using Double Ratchet session state.
|
|
500
|
+
*/
|
|
501
|
+
pfsEncryptMessage(sessionId: string, plaintext: string): Promise<PfsMessagePacket>;
|
|
502
|
+
/**
|
|
503
|
+
* Decrypt a Double Ratchet packet for a session.
|
|
504
|
+
* Handles basic out-of-order delivery through skipped message keys.
|
|
505
|
+
*/
|
|
506
|
+
pfsDecryptMessage(sessionId: string, packet: PfsMessagePacket | string): Promise<string>;
|
|
507
|
+
/**
|
|
508
|
+
* Group message encryption using sender-key fanout:
|
|
509
|
+
* message encrypted once with AES key, AES key wrapped for each group member with their RSA public key.
|
|
510
|
+
*/
|
|
511
|
+
encryptGroupMessage(text: string, groupId: string, recipientIds: string[]): Promise<string>;
|
|
512
|
+
/**
|
|
513
|
+
* Decrypt a packet produced by `encryptGroupMessage`.
|
|
514
|
+
*/
|
|
515
|
+
decryptGroupMessage(packetJson: string): Promise<string>;
|
|
358
516
|
/**
|
|
359
517
|
* Encrypt a text message via the Wolfronix server (dual-key split).
|
|
360
518
|
* The server generates an AES key, encrypts the message, and splits the key —
|
|
@@ -615,4 +773,4 @@ declare class WolfronixAdmin {
|
|
|
615
773
|
healthCheck(): Promise<boolean>;
|
|
616
774
|
}
|
|
617
775
|
|
|
618
|
-
export { type AuthResponse, AuthenticationError, type DBType, type DeactivateClientResponse, type DeleteResponse, type EncryptMessagePacket, type EncryptResponse, type EnterpriseClient, type FileInfo, FileNotFoundError, type KeyPartResponse, type ListClientsResponse, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, type RegisterClientRequest, type RegisterClientResponse, type ServerBatchEncryptResult, type ServerDecryptParams, type ServerEncryptResult, type StreamChunk, type StreamSession, type UpdateClientRequest, type UpdateClientResponse, ValidationError, Wolfronix, WolfronixAdmin, type WolfronixAdminConfig, type WolfronixConfig, WolfronixError, WolfronixStream, createClient, Wolfronix as default };
|
|
776
|
+
export { type AuthResponse, AuthenticationError, type ChunkedDecryptManifest, type ChunkedEncryptResult, type DBType, type DeactivateClientResponse, type DeleteResponse, type EncryptMessagePacket, type EncryptResponse, type EnterpriseClient, type FileInfo, FileNotFoundError, type GroupEncryptPacket, type KeyPartResponse, type ListClientsResponse, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, type PfsMessagePacket, type PfsPreKeyBundle, type PfsSessionState, type RecoverySetup, type RegisterClientRequest, type RegisterClientResponse, type RegisterOptions, type ResumableEncryptOptions, type ResumableUploadState, type ServerBatchEncryptResult, type ServerDecryptParams, type ServerEncryptResult, type StreamChunk, type StreamSession, type UpdateClientRequest, type UpdateClientResponse, ValidationError, Wolfronix, WolfronixAdmin, type WolfronixAdminConfig, type WolfronixConfig, WolfronixError, WolfronixStream, createClient, Wolfronix as default };
|
package/dist/index.global.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var WolfronixSDK=(()=>{var
|
|
2
|
-
${
|
|
3
|
-
${e==="public"?"-----END PUBLIC KEY-----":"-----END PRIVATE KEY-----"}`}async function I(n,e){let t=e==="public"?"-----BEGIN PUBLIC KEY-----":"-----BEGIN PRIVATE KEY-----",r=e==="public"?"-----END PUBLIC KEY-----":"-----END PRIVATE KEY-----",s=n.replace(t,"").replace(r,"").replace(/\s/g,""),i=_(s),o=e==="public"?"spki":"pkcs8",u=e==="public"?["encrypt","wrapKey"]:["decrypt","unwrapKey"];return await y().subtle.importKey(o,i,T,!0,u)}async function N(n,e){let t=new TextEncoder,r=await y().subtle.importKey("raw",t.encode(n),"PBKDF2",!1,["deriveKey"]),s=re(e);return await y().subtle.deriveKey({name:"PBKDF2",salt:s,iterations:ee,hash:"SHA-256"},r,{name:S,length:256},!1,["encrypt","decrypt","wrapKey","unwrapKey"])}async function F(n,e){let t=y(),r=t.getRandomValues(new Uint8Array(16)),s=te(r.buffer),i=await N(e,s),o=await t.subtle.exportKey("pkcs8",n),u=t.getRandomValues(new Uint8Array(12)),l=await t.subtle.encrypt({name:S,iv:u},i,o),c=new Uint8Array(u.length+l.byteLength);return c.set(u),c.set(new Uint8Array(l),u.length),{encryptedKey:w(c.buffer),salt:s}}async function L(n,e,t){let r=_(n),s=new Uint8Array(r),i=s.slice(0,12),o=s.slice(12),u=await N(e,t),l=await y().subtle.decrypt({name:S,iv:i},u,o);return await y().subtle.importKey("pkcs8",l,T,!0,["decrypt","unwrapKey"])}async function $(){return await y().subtle.generateKey({name:K,length:256},!0,["encrypt","decrypt"])}async function G(n,e){let r=new TextEncoder().encode(n),s=y().getRandomValues(new Uint8Array(12)),i=await y().subtle.encrypt({name:K,iv:s},e,r);return{encrypted:w(i),iv:w(s.buffer)}}async function W(n,e,t){let r=_(n),s=_(e),i=await y().subtle.decrypt({name:K,iv:s},t,r);return new TextDecoder().decode(i)}async function j(n,e){let t=await y().subtle.encrypt({name:"RSA-OAEP"},e,n);return w(t)}async function D(n,e){let t=_(n);return await y().subtle.decrypt({name:"RSA-OAEP"},e,t)}async function B(n,e){let t=await D(n,e);return w(t)}async function H(n){return await y().subtle.exportKey("raw",n)}async function J(n){return await y().subtle.importKey("raw",n,K,!0,["encrypt","decrypt"])}function w(n){let e=new Uint8Array(n);if(typeof Buffer<"u")return Buffer.from(e).toString("base64");let t="",r=e.byteLength;for(let s=0;s<r;s++)t+=String.fromCharCode(e[s]);return btoa(t)}function _(n){if(typeof Buffer<"u"){let s=Buffer.from(n,"base64");return s.buffer.slice(s.byteOffset,s.byteOffset+s.byteLength)}let e=atob(n),t=e.length,r=new Uint8Array(t);for(let s=0;s<t;s++)r[s]=e.charCodeAt(s);return r.buffer}function te(n){return[...new Uint8Array(n)].map(e=>e.toString(16).padStart(2,"0")).join("")}function re(n){let e=new Uint8Array(n.length/2);for(let t=0;t<n.length;t+=2)e[t/2]=parseInt(n.substring(t,t+2),16);return e.buffer}var f=class extends Error{constructor(e,t,r,s){super(e),this.name="WolfronixError",this.code=t,this.statusCode=r,this.details=s}},b=class extends f{constructor(e="Authentication failed"){super(e,"AUTH_ERROR",401),this.name="AuthenticationError"}},E=class extends f{constructor(e){super(`File not found: ${e}`,"FILE_NOT_FOUND",404),this.name="FileNotFoundError"}},v=class extends f{constructor(e="Permission denied"){super(e,"PERMISSION_DENIED",403),this.name="PermissionDeniedError"}},A=class extends f{constructor(e="Network request failed"){super(e,"NETWORK_ERROR"),this.name="NetworkError"}},p=class extends f{constructor(e){super(e,"VALIDATION_ERROR",400),this.name="ValidationError"}},k=class{constructor(e){this.token=null;this.userId=null;this.tokenExpiry=null;this.publicKey=null;this.privateKey=null;this.publicKeyPEM=null;typeof e=="string"?this.config={baseUrl:e,clientId:"",wolfronixKey:"",timeout:3e4,retries:3,insecure:!1}:this.config={baseUrl:e.baseUrl,clientId:e.clientId||"",wolfronixKey:e.wolfronixKey||"",timeout:e.timeout||3e4,retries:e.retries||3,insecure:e.insecure||!1},this.config.baseUrl=this.config.baseUrl.replace(/\/$/,"")}hasPrivateKey(){return this.privateKey!==null}getHeaders(e=!0){let t={Accept:"application/json"};return this.config.clientId&&(t["X-Client-ID"]=this.config.clientId),this.config.wolfronixKey&&(t["X-Wolfronix-Key"]=this.config.wolfronixKey),e&&this.token&&(t.Authorization=`Bearer ${this.token}`,this.userId&&(t["X-User-ID"]=this.userId)),t}async request(e,t,r={}){let{body:s,formData:i,includeAuth:o=!0,responseType:u="json",headers:l}=r,c=`${this.config.baseUrl}${t}`,a={...this.getHeaders(o),...l};s&&!i&&(a["Content-Type"]="application/json");let d=null;for(let g=1;g<=this.config.retries;g++)try{let m=new AbortController,U=i?null:setTimeout(()=>m.abort(),this.config.timeout),R={method:e,headers:a,signal:m.signal};this.config.insecure&&typeof process<"u"&&process.env&&(process.env.NODE_TLS_REJECT_UNAUTHORIZED="0"),i?R.body=i:s&&(R.body=JSON.stringify(s));let h=await fetch(c,R);if(U&&clearTimeout(U),!h.ok){let P=await h.json().catch(()=>({}));throw h.status===401?new b(P.error||"Authentication failed"):h.status===403?new v(P.error||"Permission denied"):h.status===404?new E(t):new f(P.error||`Request failed with status ${h.status}`,"REQUEST_ERROR",h.status,P)}return u==="blob"?await h.blob():u==="arraybuffer"?await h.arrayBuffer():await h.json()}catch(m){if(d=m,m instanceof b||m instanceof v||m instanceof E)throw m;if(g<this.config.retries){await this.sleep(Math.pow(2,g)*100);continue}}throw d||new A("Request failed after retries")}sleep(e){return new Promise(t=>setTimeout(t,e))}ensureAuthenticated(){if(!this.token)throw new b("Not authenticated. Call login() or register() first.")}async register(e,t){if(!e||!t)throw new p("Email and password are required");let r=await O(),s=await M(r.publicKey,"public"),{encryptedKey:i,salt:o}=await F(r.privateKey,t),u=await this.request("POST","/api/v1/keys/register",{body:{client_id:this.config.clientId,user_id:e,public_key_pem:s,encrypted_private_key:i,salt:o},includeAuth:!1});return u.success&&(this.userId=e,this.publicKey=r.publicKey,this.privateKey=r.privateKey,this.publicKeyPEM=s,this.token="zk-session"),u}async login(e,t){if(!e||!t)throw new p("Email and password are required");let r=await this.request("POST","/api/v1/keys/login",{body:{client_id:this.config.clientId,user_id:e},includeAuth:!1});if(!r.encrypted_private_key||!r.salt)throw new b("Invalid credentials or keys not found");try{return this.privateKey=await L(r.encrypted_private_key,t,r.salt),this.publicKeyPEM=r.public_key_pem,this.publicKey=await I(r.public_key_pem,"public"),this.userId=e,this.token="zk-session",{success:!0,user_id:e,token:this.token,message:"Logged in successfully"}}catch{throw new b("Invalid password (decryption failed)")}}setToken(e,t){this.token=e,this.userId=t||null,this.tokenExpiry=new Date(Date.now()+1440*60*1e3)}logout(){this.token=null,this.userId=null,this.tokenExpiry=null,this.publicKey=null,this.privateKey=null,this.publicKeyPEM=null}isAuthenticated(){return!!this.token&&(!this.tokenExpiry||this.tokenExpiry>new Date)}getUserId(){return this.userId}async encrypt(e,t){this.ensureAuthenticated();let r=new FormData;if(e instanceof File)r.append("file",e);else if(e instanceof Blob)r.append("file",e,t||"file");else if(e instanceof ArrayBuffer){let i=new Blob([new Uint8Array(e)]);r.append("file",i,t||"file")}else if(e instanceof Uint8Array){let i=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength),o=new Blob([i]);r.append("file",o,t||"file")}else throw new p("Invalid file type. Expected File, Blob, Buffer, or ArrayBuffer");if(r.append("user_id",this.userId||""),!this.publicKeyPEM)throw new Error("Public key not available. Is user logged in?");r.append("client_public_key",this.publicKeyPEM);let s=await this.request("POST","/api/v1/encrypt",{formData:r});return{...s,file_id:String(s.file_id)}}async decrypt(e,t="owner"){if(this.ensureAuthenticated(),!e)throw new p("File ID is required");if(!this.privateKey)throw new Error("Private key not available. Is user logged in?");let r=await this.getFileKey(e),s=await B(r.key_part_a,this.privateKey);return this.request("POST",`/api/v1/files/${e}/decrypt`,{responseType:"blob",body:{decrypted_key_a:s,user_role:t}})}async decryptToBuffer(e,t="owner"){if(this.ensureAuthenticated(),!e)throw new p("File ID is required");if(!this.privateKey)throw new Error("Private key not available. Is user logged in?");let r=await this.getFileKey(e),s=await B(r.key_part_a,this.privateKey);return this.request("POST",`/api/v1/files/${e}/decrypt`,{responseType:"arraybuffer",body:{decrypted_key_a:s,user_role:t}})}async getFileKey(e){if(this.ensureAuthenticated(),!e)throw new p("File ID is required");return this.request("GET",`/api/v1/files/${e}/key`)}async listFiles(){this.ensureAuthenticated();let e=await this.request("GET","/api/v1/files");return{success:!0,files:(e||[]).map(t=>({file_id:t.id,original_name:t.name,encrypted_size:t.size_bytes,created_at:t.date})),total:(e||[]).length}}async deleteFile(e){if(this.ensureAuthenticated(),!e)throw new p("File ID is required");return this.request("DELETE",`/api/v1/files/${e}`)}async getPublicKey(e,t){this.ensureAuthenticated();let r=t||this.config.clientId;if(!r)throw new p("clientId is required for getPublicKey(). Set it in config or pass as second argument.");return(await this.request("GET",`/api/v1/keys/public/${encodeURIComponent(r)}/${encodeURIComponent(e)}`)).public_key_pem}async encryptMessage(e,t){this.ensureAuthenticated();let r=await this.getPublicKey(t),s=await I(r,"public"),i=await $(),{encrypted:o,iv:u}=await G(e,i),l=await H(i),a={key:await j(l,s),iv:u,msg:o};return JSON.stringify(a)}async decryptMessage(e){if(this.ensureAuthenticated(),!this.privateKey)throw new Error("Private key not available. Is user logged in?");let t;try{t=JSON.parse(e)}catch{throw new p("Invalid message packet format")}if(!t.key||!t.iv||!t.msg)throw new p("Invalid message packet structure");try{let r=await D(t.key,this.privateKey),s=await J(r);return await W(t.msg,t.iv,s)}catch{throw new Error("Decryption failed. You may not be the intended recipient.")}}async serverEncrypt(e,t){if(this.ensureAuthenticated(),!e)throw new p("Message is required");return this.request("POST","/api/v1/messages/encrypt",{body:{message:e,user_id:this.userId,layer:t?.layer||4}})}async serverDecrypt(e){if(this.ensureAuthenticated(),!e.encryptedMessage||!e.nonce||!e.keyPartA)throw new p("encryptedMessage, nonce, and keyPartA are required");return(await this.request("POST","/api/v1/messages/decrypt",{body:{encrypted_message:e.encryptedMessage,nonce:e.nonce,key_part_a:e.keyPartA,message_tag:e.messageTag||"",user_id:this.userId}})).message}async serverEncryptBatch(e,t){if(this.ensureAuthenticated(),!e||e.length===0)throw new p("At least one message is required");if(e.length>100)throw new p("Maximum 100 messages per batch");return this.request("POST","/api/v1/messages/batch/encrypt",{body:{messages:e,user_id:this.userId,layer:t?.layer||4}})}async serverDecryptBatchItem(e,t){if(t<0||t>=e.results.length)throw new p("Invalid batch index");let r=e.results[t];return this.serverDecrypt({encryptedMessage:r.encrypted_message,nonce:r.nonce,keyPartA:e.key_part_a,messageTag:e.batch_tag})}async createStream(e,t){if(this.ensureAuthenticated(),e==="decrypt"&&!t)throw new p("streamKey (keyPartA + streamTag) is required for decrypt streams");let r=new C(this.config,this.userId);return await r.connect(e,t),r}async getMetrics(){return this.ensureAuthenticated(),this.request("GET","/api/v1/metrics/summary")}async healthCheck(){try{return await this.request("GET","/health",{includeAuth:!1}),!0}catch{return!1}}},C=class{constructor(e,t){this.config=e;this.userId=t;this.ws=null;this.dataCallbacks=[];this.errorCallbacks=[];this.pendingChunks=new Map;this.seqCounter=0;this.keyPartA=null;this.streamTag=null}async connect(e,t){return new Promise((r,s)=>{let i=this.config.baseUrl.replace(/^http/,"ws"),o=new URLSearchParams;this.config.wolfronixKey&&o.set("wolfronix_key",this.config.wolfronixKey),this.config.clientId&&o.set("client_id",this.config.clientId);let u=`${i}/api/v1/stream?${o.toString()}`;this.ws=new WebSocket(u),this.ws.onopen=()=>{let c={type:"init",direction:e};e==="decrypt"&&t&&(c.key_part_a=t.keyPartA,c.stream_tag=t.streamTag),this.ws.send(JSON.stringify(c))};let l=!1;this.ws.onmessage=c=>{try{let a=JSON.parse(c.data);if(a.type==="error"){let d=new Error(a.error);l||(l=!0,s(d)),this.errorCallbacks.forEach(g=>g(d));return}if(a.type==="init_ack"&&!l){l=!0,a.key_part_a&&(this.keyPartA=a.key_part_a),a.stream_tag&&(this.streamTag=a.stream_tag),r();return}if(a.type==="data"){this.dataCallbacks.forEach(g=>g(a.data,a.seq));let d=this.pendingChunks.get(a.seq);d&&(d.resolve(a.data),this.pendingChunks.delete(a.seq));return}if(a.type==="end_ack")return}catch{let d=new Error("Failed to parse stream message");this.errorCallbacks.forEach(g=>g(d))}},this.ws.onerror=c=>{let a=new Error("WebSocket error");l||(l=!0,s(a)),this.errorCallbacks.forEach(d=>d(a))},this.ws.onclose=()=>{this.pendingChunks.forEach(c=>c.reject(new Error("Stream closed"))),this.pendingChunks.clear()}})}async send(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("Stream not connected");let t=this.isBase64(e)?e:btoa(e),r=this.seqCounter++;return new Promise((s,i)=>{this.pendingChunks.set(r,{resolve:s,reject:i}),this.ws.send(JSON.stringify({type:"data",data:t}))})}async sendBinary(e){let t=e instanceof Uint8Array?e:new Uint8Array(e),r="";for(let i=0;i<t.byteLength;i++)r+=String.fromCharCode(t[i]);let s=btoa(r);return this.send(s)}onData(e){this.dataCallbacks.push(e)}onError(e){this.errorCallbacks.push(e)}async end(){return!this.ws||this.ws.readyState!==WebSocket.OPEN?{chunksProcessed:this.seqCounter}:new Promise(e=>{let t=this.ws.onmessage;this.ws.onmessage=r=>{try{let s=JSON.parse(r.data);if(s.type==="end_ack"){e({chunksProcessed:s.chunks_processed||this.seqCounter}),this.ws.close();return}}catch{}t&&this.ws&&t.call(this.ws,r)},this.ws.send(JSON.stringify({type:"end"})),setTimeout(()=>{e({chunksProcessed:this.seqCounter}),this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.close()},5e3)})}close(){this.ws&&(this.ws.close(),this.ws=null),this.pendingChunks.forEach(e=>e.reject(new Error("Stream closed"))),this.pendingChunks.clear()}isBase64(e){return e.length%4!==0?!1:/^[A-Za-z0-9+/]*={0,2}$/.test(e)}};function se(n){return new k(n)}var q=class{constructor(e){this.baseUrl=e.baseUrl.replace(/\/$/,""),this.adminKey=e.adminKey,this.timeout=e.timeout||3e4,this.insecure=e.insecure||!1}async request(e,t,r){let s=`${this.baseUrl}${t}`,i={"X-Admin-Key":this.adminKey,Accept:"application/json"};r&&(i["Content-Type"]="application/json");let o=new AbortController,u=setTimeout(()=>o.abort(),this.timeout),l={method:e,headers:i,signal:o.signal};this.insecure&&typeof process<"u"&&process.env&&(process.env.NODE_TLS_REJECT_UNAUTHORIZED="0"),r&&(l.body=JSON.stringify(r));let c=await fetch(s,l);if(clearTimeout(u),!c.ok){let a=await c.json().catch(()=>({}));throw new f(a.error||`Request failed with status ${c.status}`,"ADMIN_REQUEST_ERROR",c.status,a)}return await c.json()}async registerClient(e){return this.request("POST","/api/v1/enterprise/register",e)}async listClients(){return this.request("GET","/api/v1/enterprise/clients")}async getClient(e){return this.request("GET",`/api/v1/enterprise/clients/${encodeURIComponent(e)}`)}async updateClient(e,t){return this.request("PUT",`/api/v1/enterprise/clients/${encodeURIComponent(e)}`,t)}async deactivateClient(e){return this.request("DELETE",`/api/v1/enterprise/clients/${encodeURIComponent(e)}`)}async healthCheck(){try{return await this.request("GET","/health"),!0}catch{return!1}}},ne=k;return Q(ie);})();
|
|
1
|
+
"use strict";var WolfronixSDK=(()=>{var U=Object.defineProperty;var ue=Object.getOwnPropertyDescriptor;var pe=Object.getOwnPropertyNames;var le=Object.prototype.hasOwnProperty;var ye=(s,e)=>{for(var t in e)U(s,t,{get:e[t],enumerable:!0})},de=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of pe(e))!le.call(s,n)&&n!==t&&U(s,n,{get:()=>e[n],enumerable:!(r=ue(e,n))||r.enumerable});return s};var he=s=>de(U({},"__esModule",{value:!0}),s);var Ee={};ye(Ee,{AuthenticationError:()=>b,FileNotFoundError:()=>x,NetworkError:()=>I,PermissionDeniedError:()=>K,ValidationError:()=>p,Wolfronix:()=>C,WolfronixAdmin:()=>Z,WolfronixError:()=>m,WolfronixStream:()=>D,createClient:()=>Ae,default:()=>Se});var f=()=>{if(typeof globalThis.crypto<"u")return globalThis.crypto;throw new Error("Web Crypto API not available. Requires a modern browser or Node.js 18+.")},q={name:"RSA-OAEP",modulusLength:2048,publicExponent:new Uint8Array([1,0,1]),hash:"SHA-256"},O="AES-GCM",R="AES-GCM",fe=1e5;async function Q(){return await f().subtle.generateKey(q,!0,["encrypt","decrypt","wrapKey","unwrapKey"])}async function ee(s,e){let t=e==="public"?"spki":"pkcs8",r=await f().subtle.exportKey(t,s),n=P(r);return`${e==="public"?"-----BEGIN PUBLIC KEY-----":"-----BEGIN PRIVATE KEY-----"}
|
|
2
|
+
${n}
|
|
3
|
+
${e==="public"?"-----END PUBLIC KEY-----":"-----END PRIVATE KEY-----"}`}async function E(s,e){let t=e==="public"?"-----BEGIN PUBLIC KEY-----":"-----BEGIN PRIVATE KEY-----",r=e==="public"?"-----END PUBLIC KEY-----":"-----END PRIVATE KEY-----",n=s.replace(t,"").replace(r,"").replace(/\s/g,""),i=S(n),a=e==="public"?"spki":"pkcs8",l=e==="public"?["encrypt","wrapKey"]:["decrypt","unwrapKey"];return await f().subtle.importKey(a,i,q,!0,l)}async function te(s,e){let t=new TextEncoder,r=await f().subtle.importKey("raw",t.encode(s),"PBKDF2",!1,["deriveKey"]),n=me(e);return await f().subtle.deriveKey({name:"PBKDF2",salt:n,iterations:fe,hash:"SHA-256"},r,{name:O,length:256},!1,["encrypt","decrypt","wrapKey","unwrapKey"])}async function B(s,e){let t=f(),r=t.getRandomValues(new Uint8Array(16)),n=ge(r.buffer),i=await te(e,n),a=await t.subtle.exportKey("pkcs8",s),l=t.getRandomValues(new Uint8Array(12)),o=await t.subtle.encrypt({name:O,iv:l},i,a),c=new Uint8Array(l.length+o.byteLength);return c.set(l),c.set(new Uint8Array(o),l.length),{encryptedKey:P(c.buffer),salt:n}}async function M(s,e,t){let r=S(s),n=new Uint8Array(r),i=n.slice(0,12),a=n.slice(12),l=await te(e,t),o=await f().subtle.decrypt({name:O,iv:i},l,a);return await f().subtle.importKey("pkcs8",o,q,!0,["decrypt","unwrapKey"])}async function J(){return await f().subtle.generateKey({name:R,length:256},!0,["encrypt","decrypt"])}async function j(s,e){let r=new TextEncoder().encode(s),n=f().getRandomValues(new Uint8Array(12)),i=await f().subtle.encrypt({name:R,iv:n},e,r);return{encrypted:P(i),iv:P(n.buffer)}}async function N(s,e,t){let r=S(s),n=S(e),i=await f().subtle.decrypt({name:R,iv:n},t,r);return new TextDecoder().decode(i)}async function W(s,e){let t=await f().subtle.encrypt({name:"RSA-OAEP"},e,s);return P(t)}async function T(s,e){let t=S(s);return await f().subtle.decrypt({name:"RSA-OAEP"},e,t)}async function F(s,e){let t=await T(s,e);return P(t)}async function $(s){return await f().subtle.exportKey("raw",s)}async function L(s){return await f().subtle.importKey("raw",s,R,!0,["encrypt","decrypt"])}function P(s){let e=new Uint8Array(s);if(typeof Buffer<"u")return Buffer.from(e).toString("base64");let t="",r=e.byteLength;for(let n=0;n<r;n++)t+=String.fromCharCode(e[n]);return btoa(t)}function S(s){if(typeof Buffer<"u"){let n=Buffer.from(s,"base64");return n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)}let e=atob(s),t=e.length,r=new Uint8Array(t);for(let n=0;n<t;n++)r[n]=e.charCodeAt(n);return r.buffer}function ge(s){return[...new Uint8Array(s)].map(e=>e.toString(16).padStart(2,"0")).join("")}function me(s){let e=new Uint8Array(s.length/2);for(let t=0;t<s.length;t+=2)e[t/2]=parseInt(s.substring(t,t+2),16);return e.buffer}var m=class extends Error{constructor(e,t,r,n){super(e),this.name="WolfronixError",this.code=t,this.statusCode=r,this.details=n}},b=class extends m{constructor(e="Authentication failed"){super(e,"AUTH_ERROR",401),this.name="AuthenticationError"}},x=class extends m{constructor(e){super(`File not found: ${e}`,"FILE_NOT_FOUND",404),this.name="FileNotFoundError"}},K=class extends m{constructor(e="Permission denied"){super(e,"PERMISSION_DENIED",403),this.name="PermissionDeniedError"}},I=class extends m{constructor(e="Network request failed"){super(e,"NETWORK_ERROR"),this.name="NetworkError"}},p=class extends m{constructor(e){super(e,"VALIDATION_ERROR",400),this.name="ValidationError"}},re=["able","about","absorb","access","acid","across","action","adapt","admit","adult","agent","agree","ahead","air","alert","alpha","anchor","angle","apple","arch","arena","argue","armed","arrow","asset","atlas","attack","audio","august","auto","avoid","awake","aware","badge","balance","banana","basic","beach","beauty","before","begin","below","benefit","best","beyond","bicycle","bird","black","bless","board","bold","bonus","border","borrow","bottle","bottom","brain","brand","brave","breeze","brick","brief","bring","brother","budget","build","camera","camp","canal","carbon","carry","casual","center","chain","change","charge","chase","cheap","check","chief","choice","circle","city","claim","class","clean","clear","client","clock","cloud","coach","coast","color","column","combo","common","concept","confirm","connect","copy","core","corner","correct","cost","cover","craft","create","credit","cross","crowd","crystal","current","custom","cycle","daily","danger","data","dealer","debate","decide","deep","define","degree","delay","demand","denial","design","detail","device","dialog","digital","direct","doctor","domain","double","draft","dragon","drama","dream","drive","early","earth","easy","echo","edge","edit","effect","either","elder","element","elite","email","energy","engine","enough","enter","equal","error","escape","estate","event","exact","example","exchange","exist","expand","expect","expert","extra","fabric","factor","family","famous","feature","fence","field","figure","filter","final","finger","finish","first","focus","follow","force","forest","format","forward","frame","fresh","front","future","gallery","general","giant","global","gold","good","grace","grant","green","group","guard","habit","half","hammer","handle","happy","harbor","health","height","hidden","history","honest","host","hotel","human","hybrid","idea","image","impact","income","index","input","inside","insight","island","item","jacket","jazz","join","jungle","keep","keyboard","kind","king","kitchen","label","ladder","language","large","laser","later","launch","layer","leader","learn","level","light","limit","linear","link","listen","local","logic","lucky","machine","magic","major","manage","manual","market","master","matrix","matter","member","memory","message","method","middle","million","mind","mirror","mobile","model","module","moment","monitor","moral","motion","mountain","music","native","nature","network","never","normal","notice","number","object","ocean","offer","office","online","option","orange","order","origin","output","owner","packet","panel","paper","parent","partner","pattern","pause","payment","people","perfect","phone","phrase","pilot","pixel","planet","platform","please","plus","policy","portal","position","power","predict","premium","prepare","present","pretty","price","prime","private","process","profile","project","protect","public","quality","quick","quiet","radio","random","rapid","rate","ready","reason","record","recover","region","release","remote","repair","repeat","report","request","result","return","review","right","rival","river","robot","route","royal","safe","sample","scale","scene","school","science","screen","search","secure","select","seller","senior","series","server","session","shadow","shape","share","shield","shift","ship","short","signal","silver","simple","single","skill","smart","smooth","social","solid","source","space","special","speed","spirit","split","square","stable","stack","stage","start","state","status","steel","step","stock","store","storm","story","stream","strike","strong","studio","style","subject","submit","success","sudden","sugar","supply","support","surface","switch","system","table","target","task","team","temple","tempo","tenant","term","test","theme","theory","thing","thread","time","title","token","tool","topic","total","tower","track","trade","traffic","train","travel","trust","tunnel","type","unable","update","upload","usage","useful","user","valid","value","vector","verify","version","video","view","virtual","vision","voice","volume","wait","wallet","watch","water","wealth","web","welcome","window","winner","wire","wise","wonder","work","world","write","xenon","year","yield","zone"];function be(s){let e=new Uint32Array(1);return globalThis.crypto.getRandomValues(e),e[0]%s}function we(s=24){let e=[];for(let t=0;t<s;t++)e.push(re[be(re.length)]);return e}var Y="wfx-dr-v1",oe=new Uint8Array(32);function k(s){if(typeof Buffer<"u")return Buffer.from(s).toString("base64");let e=new Uint8Array(s),t="";for(let r=0;r<e.length;r++)t+=String.fromCharCode(e[r]);return btoa(t)}function A(s){if(typeof Buffer<"u"){let r=Buffer.from(s,"base64");return r.buffer.slice(r.byteOffset,r.byteOffset+r.byteLength)}let e=atob(s),t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=e.charCodeAt(r);return t.buffer}function X(s){return JSON.stringify({kty:s.kty||"",crv:s.crv||"",x:s.x||"",y:s.y||""})}function ne(s,e){let t=X(s);return typeof Buffer<"u"?`${Buffer.from(t).toString("base64")}:${e}`:`${btoa(t)}:${e}`}async function se(){return globalThis.crypto.subtle.generateKey({name:"ECDH",namedCurve:"P-256"},!0,["deriveBits"])}async function ie(s){return globalThis.crypto.subtle.exportKey("jwk",s)}async function ae(s){return globalThis.crypto.subtle.exportKey("jwk",s)}async function _e(s){return globalThis.crypto.subtle.importKey("jwk",s,{name:"ECDH",namedCurve:"P-256"},!1,[])}async function ke(s){return globalThis.crypto.subtle.importKey("jwk",s,{name:"ECDH",namedCurve:"P-256"},!1,["deriveBits"])}async function z(s,e){let t=await ke(s),r=await _e(e);return globalThis.crypto.subtle.deriveBits({name:"ECDH",public:r},t,256)}async function ve(s,e,t,r){let n=await globalThis.crypto.subtle.importKey("raw",s,"HKDF",!1,["deriveBits"]);return globalThis.crypto.subtle.deriveBits({name:"HKDF",hash:"SHA-256",salt:e,info:new TextEncoder().encode(t)},n,r)}async function ce(s,e){let t=await globalThis.crypto.subtle.importKey("raw",s,{name:"HMAC",hash:"SHA-256"},!1,["sign"]);return globalThis.crypto.subtle.sign("HMAC",t,new TextEncoder().encode(e))}async function G(s,e){let t=s?A(s):oe.buffer,r=await ve(e,t,`${Y}:root`,768),n=new Uint8Array(r);return{rootKey:k(n.slice(0,32).buffer),chainA:k(n.slice(32,64).buffer),chainB:k(n.slice(64,96).buffer)}}async function H(s,e){return ce(A(s),`msg:${e}`)}async function V(s){let e=await ce(A(s),"chain");return k(e)}async function Pe(s,e){let t=await globalThis.crypto.subtle.importKey("raw",s,{name:"AES-GCM"},!1,["encrypt"]),r=globalThis.crypto.getRandomValues(new Uint8Array(12)),n=await globalThis.crypto.subtle.encrypt({name:"AES-GCM",iv:r},t,new TextEncoder().encode(e));return{ciphertext:k(n),iv:k(r.buffer)}}async function Ke(s,e,t){let r=await globalThis.crypto.subtle.importKey("raw",s,{name:"AES-GCM"},!1,["decrypt"]),n=await globalThis.crypto.subtle.decrypt({name:"AES-GCM",iv:new Uint8Array(A(t))},r,A(e));return new TextDecoder().decode(n)}var C=class{constructor(e){this.token=null;this.userId=null;this.tokenExpiry=null;this.publicKey=null;this.privateKey=null;this.publicKeyPEM=null;this.pfsIdentityPrivateJwk=null;this.pfsIdentityPublicJwk=null;this.pfsSessions=new Map;typeof e=="string"?this.config={baseUrl:e,clientId:"",wolfronixKey:"",timeout:3e4,retries:3,insecure:!1}:this.config={baseUrl:e.baseUrl,clientId:e.clientId||"",wolfronixKey:e.wolfronixKey||"",timeout:e.timeout||3e4,retries:e.retries||3,insecure:e.insecure||!1},this.config.baseUrl=this.config.baseUrl.replace(/\/$/,"")}hasPrivateKey(){return this.privateKey!==null}getHeaders(e=!0){let t={Accept:"application/json"};return this.config.clientId&&(t["X-Client-ID"]=this.config.clientId),this.config.wolfronixKey&&(t["X-Wolfronix-Key"]=this.config.wolfronixKey),e&&this.token&&(t.Authorization=`Bearer ${this.token}`,this.userId&&(t["X-User-ID"]=this.userId)),t}async request(e,t,r={}){let{body:n,formData:i,includeAuth:a=!0,responseType:l="json",headers:o}=r,c=`${this.config.baseUrl}${t}`,u={...this.getHeaders(a),...o};n&&!i&&(u["Content-Type"]="application/json");let h=null;for(let y=1;y<=this.config.retries;y++)try{let d=new AbortController,w=i?null:setTimeout(()=>d.abort(),this.config.timeout),_={method:e,headers:u,signal:d.signal};this.config.insecure&&typeof process<"u"&&process.env&&(process.env.NODE_TLS_REJECT_UNAUTHORIZED="0"),i?_.body=i:n&&(_.body=JSON.stringify(n));let g=await fetch(c,_);if(w&&clearTimeout(w),!g.ok){let v=await g.json().catch(()=>({}));throw g.status===401?new b(v.error||"Authentication failed"):g.status===403?new K(v.error||"Permission denied"):g.status===404?new x(t):new m(v.error||`Request failed with status ${g.status}`,"REQUEST_ERROR",g.status,v)}return l==="blob"?await g.blob():l==="arraybuffer"?await g.arrayBuffer():await g.json()}catch(d){if(h=d,d instanceof b||d instanceof K||d instanceof x)throw d;if(y<this.config.retries){await this.sleep(Math.pow(2,y)*100);continue}}throw h||new I("Request failed after retries")}sleep(e){return new Promise(t=>setTimeout(t,e))}ensureAuthenticated(){if(!this.token)throw new b("Not authenticated. Call login() or register() first.")}toBlob(e){if(e instanceof File||e instanceof Blob)return e;if(e instanceof ArrayBuffer)return new Blob([new Uint8Array(e)]);if(e instanceof Uint8Array){let t=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);return new Blob([t])}throw new p("Invalid file type. Expected File, Blob, Buffer, or ArrayBuffer")}async ensurePfsIdentity(){if(this.pfsIdentityPrivateJwk&&this.pfsIdentityPublicJwk)return;let e=await se();this.pfsIdentityPrivateJwk=await ae(e.privateKey),this.pfsIdentityPublicJwk=await ie(e.publicKey)}getPfsSession(e){let t=this.pfsSessions.get(e);if(!t)throw new p(`PFS session not found: ${e}`);return t}async ratchetForSend(e){let t=await se(),r=await ae(t.privateKey),n=await ie(t.publicKey),i=await z(r,e.their_ratchet_public_jwk),a=await G(e.root_key,i);e.root_key=a.rootKey,e.send_chain_key=a.chainA,e.recv_chain_key=a.chainB,e.prev_send_count=e.send_count,e.send_count=0,e.my_ratchet_private_jwk=r,e.my_ratchet_public_jwk=n,e.updated_at=Date.now()}async ratchetForReceive(e,t){let r=await z(e.my_ratchet_private_jwk,t),n=await G(e.root_key,r);e.root_key=n.rootKey,e.recv_chain_key=n.chainA,e.send_chain_key=n.chainB,e.recv_count=0,e.their_ratchet_public_jwk=t,e.updated_at=Date.now()}async register(e,t,r={}){if(!e||!t)throw new p("Email and password are required");let n=await Q(),i=await ee(n.publicKey,"public"),{encryptedKey:a,salt:l}=await B(n.privateKey,t),o=r.enableRecovery!==!1,c=o?r.recoveryPhrase?r.recoveryPhrase.trim().split(/\s+/).filter(Boolean):we(24):[],u=c.join(" "),h="",y="";if(o&&u){let _=await B(n.privateKey,u);h=_.encryptedKey,y=_.salt}let d=await this.request("POST","/api/v1/keys/register",{body:{client_id:this.config.clientId,user_id:e,public_key_pem:i,encrypted_private_key:a,salt:l,recovery_encrypted_private_key:h,recovery_salt:y},includeAuth:!1});(d.status==="success"||d.success)&&(this.userId=e,this.publicKey=n.publicKey,this.privateKey=n.privateKey,this.publicKeyPEM=i,this.token="zk-session");let w={success:d.status==="success"||d.success===!0,user_id:d.user_id||e,token:this.token||"zk-session",message:d.message||"Keys registered successfully"};return o&&u&&(w.recoveryPhrase=u,w.recoveryWords=c),w}async login(e,t){if(!e||!t)throw new p("Email and password are required");let r=await this.request("POST","/api/v1/keys/login",{body:{client_id:this.config.clientId,user_id:e},includeAuth:!1});if(!r.encrypted_private_key||!r.salt)throw new b("Invalid credentials or keys not found");try{return this.privateKey=await M(r.encrypted_private_key,t,r.salt),this.publicKeyPEM=r.public_key_pem,this.publicKey=await E(r.public_key_pem,"public"),this.userId=e,this.token="zk-session",{success:!0,user_id:e,token:this.token,message:"Logged in successfully"}}catch{throw new b("Invalid password (decryption failed)")}}async recoverAccount(e,t,r){if(!e||!t||!r)throw new p("email, recoveryPhrase, and newPassword are required");let n=await this.request("POST","/api/v1/keys/recover",{body:{client_id:this.config.clientId,user_id:e},includeAuth:!1});if(!n.recovery_encrypted_private_key||!n.recovery_salt||!n.public_key_pem)throw new b("Recovery material not found for this account");let i=await M(n.recovery_encrypted_private_key,t,n.recovery_salt),a=await B(i,r);return await this.request("POST","/api/v1/keys/update-password",{body:{client_id:this.config.clientId,user_id:e,encrypted_private_key:a.encryptedKey,salt:a.salt},includeAuth:!1}),this.privateKey=i,this.publicKeyPEM=n.public_key_pem,this.publicKey=await E(n.public_key_pem,"public"),this.userId=e,this.token="zk-session",{success:!0,user_id:e,token:this.token,message:"Account recovered successfully"}}async rotateIdentityKeys(e,t){throw this.ensureAuthenticated(),e?t!==void 0&&!t.trim()?new p("recoveryPhrase must be non-empty when provided"):new m("rotateIdentityKeys is not supported by the current server API. Use recoverAccount() to re-wrap the existing private key with a new password.","NOT_SUPPORTED",501):new p("password is required")}setToken(e,t){this.token=e,this.userId=t||null,this.tokenExpiry=new Date(Date.now()+1440*60*1e3)}logout(){this.token=null,this.userId=null,this.tokenExpiry=null,this.publicKey=null,this.privateKey=null,this.publicKeyPEM=null}isAuthenticated(){return!!this.token&&(!this.tokenExpiry||this.tokenExpiry>new Date)}getUserId(){return this.userId}async encrypt(e,t){this.ensureAuthenticated();let r=new FormData,n=this.toBlob(e);if(r.append("file",n,t||(e instanceof File?e.name:"file")),r.append("user_id",this.userId||""),!this.publicKeyPEM)throw new Error("Public key not available. Is user logged in?");r.append("client_public_key",this.publicKeyPEM);let i=await this.request("POST","/api/v1/encrypt",{formData:r});return{...i,file_id:String(i.file_id)}}async encryptResumable(e,t={}){this.ensureAuthenticated();let r=t.chunkSizeBytes||10*1024*1024;if(r<1024*1024)throw new p("chunkSizeBytes must be at least 1MB");let n=this.toBlob(e),i=t.filename||(e instanceof File?e.name:"file.bin"),a=Math.ceil(n.size/r),l=`${Date.now()}-${Math.random().toString(36).slice(2,10)}`,o=t.existingState||{upload_id:l,filename:i,file_size:n.size,chunk_size_bytes:r,total_chunks:a,uploaded_chunks:[],chunk_file_ids:new Array(a).fill(""),created_at:Date.now(),updated_at:Date.now()};if(o.file_size!==n.size||o.total_chunks!==a)throw new p("existingState does not match current file/chunking settings");let c=new Set(o.uploaded_chunks),u=c.size;for(let y=0;y<a;y++){if(c.has(y))continue;let d=y*r,w=Math.min(d+r,n.size),_=n.slice(d,w),g=`${i}.part-${String(y+1).padStart(6,"0")}-of-${String(a).padStart(6,"0")}`,v=await this.encrypt(_,g);o.chunk_file_ids[y]=v.file_id,o.uploaded_chunks.push(y),o.updated_at=Date.now(),u++,t.onProgress&&t.onProgress(u,a)}return{result:{upload_id:o.upload_id,filename:o.filename,total_chunks:o.total_chunks,chunk_size_bytes:o.chunk_size_bytes,uploaded_chunks:o.uploaded_chunks.length,chunk_file_ids:o.chunk_file_ids,complete:o.uploaded_chunks.length===o.total_chunks},state:o}}async decrypt(e,t="owner"){if(this.ensureAuthenticated(),!e)throw new p("File ID is required");if(!this.privateKey)throw new Error("Private key not available. Is user logged in?");let r=await this.getFileKey(e),n=await F(r.key_part_a,this.privateKey);return this.request("POST",`/api/v1/files/${e}/decrypt`,{responseType:"blob",body:{decrypted_key_a:n,user_role:t}})}async decryptToBuffer(e,t="owner"){if(this.ensureAuthenticated(),!e)throw new p("File ID is required");if(!this.privateKey)throw new Error("Private key not available. Is user logged in?");let r=await this.getFileKey(e),n=await F(r.key_part_a,this.privateKey);return this.request("POST",`/api/v1/files/${e}/decrypt`,{responseType:"arraybuffer",body:{decrypted_key_a:n,user_role:t}})}async decryptChunkedToBuffer(e,t="owner"){if(this.ensureAuthenticated(),!e?.chunk_file_ids?.length)throw new p("manifest.chunk_file_ids is required");let r=[],n=0;for(let l of e.chunk_file_ids){if(!l)throw new p("manifest contains empty chunk file ID");let o=await this.decryptToBuffer(l,t),c=new Uint8Array(o);r.push(c),n+=c.byteLength}let i=new Uint8Array(n),a=0;for(let l of r)i.set(l,a),a+=l.byteLength;return i.buffer}async decryptChunkedManifest(e,t="owner"){let r=await this.decryptChunkedToBuffer(e,t);return new Blob([r],{type:"application/octet-stream"})}async getFileKey(e){if(this.ensureAuthenticated(),!e)throw new p("File ID is required");return this.request("GET",`/api/v1/files/${e}/key`)}async listFiles(){this.ensureAuthenticated();let e=await this.request("GET","/api/v1/files");return{success:!0,files:(e||[]).map(t=>({file_id:t.id,original_name:t.name,encrypted_size:t.size_bytes,created_at:t.date})),total:(e||[]).length}}async deleteFile(e){if(this.ensureAuthenticated(),!e)throw new p("File ID is required");return this.request("DELETE",`/api/v1/files/${e}`)}async getPublicKey(e,t){this.ensureAuthenticated();let r=t||this.config.clientId;if(!r)throw new p("clientId is required for getPublicKey(). Set it in config or pass as second argument.");return(await this.request("GET",`/api/v1/keys/public/${encodeURIComponent(r)}/${encodeURIComponent(e)}`)).public_key_pem}async encryptMessage(e,t){this.ensureAuthenticated();let r=await this.getPublicKey(t),n=await E(r,"public"),i=await J(),{encrypted:a,iv:l}=await j(e,i),o=await $(i),u={key:await W(o,n),iv:l,msg:a};return JSON.stringify(u)}async decryptMessage(e){if(this.ensureAuthenticated(),!this.privateKey)throw new Error("Private key not available. Is user logged in?");let t;try{t=JSON.parse(e)}catch{throw new p("Invalid message packet format")}if(!t.key||!t.iv||!t.msg)throw new p("Invalid message packet structure");try{let r=await T(t.key,this.privateKey),n=await L(r);return await N(t.msg,t.iv,n)}catch{throw new Error("Decryption failed. You may not be the intended recipient.")}}async createPfsPreKeyBundle(){return this.ensureAuthenticated(),await this.ensurePfsIdentity(),{protocol:"wfx-dr-v1",user_id:this.userId||void 0,ratchet_pub_jwk:this.pfsIdentityPublicJwk,created_at:Date.now()}}async initPfsSession(e,t,r){if(this.ensureAuthenticated(),!e)throw new p("sessionId is required");if(!t||t.protocol!==Y||!t.ratchet_pub_jwk)throw new p("Invalid peerBundle");await this.ensurePfsIdentity();let n=this.pfsIdentityPrivateJwk,i=this.pfsIdentityPublicJwk,a=t.ratchet_pub_jwk,l=await z(n,a),o=await G(k(oe.buffer),l),c={protocol:"wfx-dr-v1",session_id:e,role:r?"initiator":"responder",root_key:o.rootKey,send_chain_key:r?o.chainA:o.chainB,recv_chain_key:r?o.chainB:o.chainA,send_count:0,recv_count:0,prev_send_count:0,my_ratchet_private_jwk:n,my_ratchet_public_jwk:i,their_ratchet_public_jwk:a,skipped_keys:{},created_at:Date.now(),updated_at:Date.now()};return this.pfsSessions.set(e,c),c}exportPfsSession(e){let t=this.getPfsSession(e);return JSON.parse(JSON.stringify(t))}importPfsSession(e){if(!e||e.protocol!==Y||!e.session_id)throw new p("Invalid PFS session payload");this.pfsSessions.set(e.session_id,JSON.parse(JSON.stringify(e)))}async pfsEncryptMessage(e,t){if(this.ensureAuthenticated(),!t)throw new p("plaintext is required");let r=this.getPfsSession(e);await this.ratchetForSend(r);let n=r.send_count,i=await H(r.send_chain_key,n),a=await Pe(i,t);return r.send_chain_key=await V(r.send_chain_key),r.send_count+=1,r.updated_at=Date.now(),{v:1,type:"pfs_ratchet",session_id:e,n,pn:r.prev_send_count,ratchet_pub_jwk:r.my_ratchet_public_jwk,iv:a.iv,ciphertext:a.ciphertext,timestamp:Date.now()}}async pfsDecryptMessage(e,t){this.ensureAuthenticated();let r=this.getPfsSession(e),n=typeof t=="string"?JSON.parse(t):t;if(!n||n.type!=="pfs_ratchet"||n.session_id!==e)throw new p("Invalid PFS message packet");for(X(n.ratchet_pub_jwk)!==X(r.their_ratchet_public_jwk)&&await this.ratchetForReceive(r,n.ratchet_pub_jwk);r.recv_count<n.n;){let l=await H(r.recv_chain_key,r.recv_count);r.skipped_keys[ne(r.their_ratchet_public_jwk,r.recv_count)]=k(l),r.recv_chain_key=await V(r.recv_chain_key),r.recv_count+=1}let i=ne(r.their_ratchet_public_jwk,n.n),a;return r.skipped_keys[i]?(a=A(r.skipped_keys[i]),delete r.skipped_keys[i]):(a=await H(r.recv_chain_key,n.n),r.recv_chain_key=await V(r.recv_chain_key),r.recv_count=n.n+1),r.updated_at=Date.now(),Ke(a,n.ciphertext,n.iv)}async encryptGroupMessage(e,t,r){if(this.ensureAuthenticated(),!e||!t)throw new p("text and groupId are required");if(!r?.length)throw new p("recipientIds cannot be empty");let n=Array.from(new Set(r.filter(Boolean)));this.userId&&!n.includes(this.userId)&&n.push(this.userId);let i=await J(),{encrypted:a,iv:l}=await j(e,i),o=await $(i),c={};for(let h of n){let y=await this.getPublicKey(h),d=await E(y,"public");c[h]=await W(o,d)}let u={v:1,type:"group_sender_key",sender_id:this.userId||"",group_id:t,timestamp:Date.now(),ciphertext:a,iv:l,recipient_keys:c};return JSON.stringify(u)}async decryptGroupMessage(e){if(this.ensureAuthenticated(),!this.privateKey||!this.userId)throw new Error("Private key not available. Is user logged in?");let t;try{t=JSON.parse(e)}catch{throw new p("Invalid group packet format")}if(t.type!=="group_sender_key"||!t.recipient_keys||!t.ciphertext||!t.iv)throw new p("Invalid group packet structure");let r=t.recipient_keys[this.userId];if(!r)throw new K("You are not a recipient of this group message");let n=await T(r,this.privateKey),i=await L(n);return N(t.ciphertext,t.iv,i)}async serverEncrypt(e,t){if(this.ensureAuthenticated(),!e)throw new p("Message is required");return this.request("POST","/api/v1/messages/encrypt",{body:{message:e,user_id:this.userId,layer:t?.layer||4}})}async serverDecrypt(e){if(this.ensureAuthenticated(),!e.encryptedMessage||!e.nonce||!e.keyPartA)throw new p("encryptedMessage, nonce, and keyPartA are required");return(await this.request("POST","/api/v1/messages/decrypt",{body:{encrypted_message:e.encryptedMessage,nonce:e.nonce,key_part_a:e.keyPartA,message_tag:e.messageTag||"",user_id:this.userId}})).message}async serverEncryptBatch(e,t){if(this.ensureAuthenticated(),!e||e.length===0)throw new p("At least one message is required");if(e.length>100)throw new p("Maximum 100 messages per batch");return this.request("POST","/api/v1/messages/batch/encrypt",{body:{messages:e,user_id:this.userId,layer:t?.layer||4}})}async serverDecryptBatchItem(e,t){if(t<0||t>=e.results.length)throw new p("Invalid batch index");let r=e.results[t];return this.serverDecrypt({encryptedMessage:r.encrypted_message,nonce:r.nonce,keyPartA:e.key_part_a,messageTag:e.batch_tag})}async createStream(e,t){if(this.ensureAuthenticated(),e==="decrypt"&&!t)throw new p("streamKey (keyPartA + streamTag) is required for decrypt streams");let r=new D(this.config,this.userId);return await r.connect(e,t),r}async getMetrics(){return this.ensureAuthenticated(),this.request("GET","/api/v1/metrics/summary")}async healthCheck(){try{return await this.request("GET","/health",{includeAuth:!1}),!0}catch{return!1}}},D=class{constructor(e,t){this.config=e;this.userId=t;this.ws=null;this.dataCallbacks=[];this.errorCallbacks=[];this.pendingChunks=new Map;this.seqCounter=0;this.keyPartA=null;this.streamTag=null}async connect(e,t){return new Promise((r,n)=>{let i=this.config.baseUrl.replace(/^http/,"ws"),a=new URLSearchParams;this.config.wolfronixKey&&a.set("wolfronix_key",this.config.wolfronixKey),this.config.clientId&&a.set("client_id",this.config.clientId);let l=`${i}/api/v1/stream?${a.toString()}`;this.ws=new WebSocket(l),this.ws.onopen=()=>{let c={type:"init",direction:e};e==="decrypt"&&t&&(c.key_part_a=t.keyPartA,c.stream_tag=t.streamTag),this.ws.send(JSON.stringify(c))};let o=!1;this.ws.onmessage=c=>{try{let u=JSON.parse(c.data);if(u.type==="error"){let h=new Error(u.error);o||(o=!0,n(h)),this.errorCallbacks.forEach(y=>y(h));return}if(u.type==="init_ack"&&!o){o=!0,u.key_part_a&&(this.keyPartA=u.key_part_a),u.stream_tag&&(this.streamTag=u.stream_tag),r();return}if(u.type==="data"){this.dataCallbacks.forEach(y=>y(u.data,u.seq));let h=this.pendingChunks.get(u.seq);h&&(h.resolve(u.data),this.pendingChunks.delete(u.seq));return}if(u.type==="end_ack")return}catch{let h=new Error("Failed to parse stream message");this.errorCallbacks.forEach(y=>y(h))}},this.ws.onerror=c=>{let u=new Error("WebSocket error");o||(o=!0,n(u)),this.errorCallbacks.forEach(h=>h(u))},this.ws.onclose=()=>{this.pendingChunks.forEach(c=>c.reject(new Error("Stream closed"))),this.pendingChunks.clear()}})}async send(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("Stream not connected");let t=this.isBase64(e)?e:btoa(e),r=this.seqCounter++;return new Promise((n,i)=>{this.pendingChunks.set(r,{resolve:n,reject:i}),this.ws.send(JSON.stringify({type:"data",data:t}))})}async sendBinary(e){let t=e instanceof Uint8Array?e:new Uint8Array(e),r="";for(let i=0;i<t.byteLength;i++)r+=String.fromCharCode(t[i]);let n=btoa(r);return this.send(n)}onData(e){this.dataCallbacks.push(e)}onError(e){this.errorCallbacks.push(e)}async end(){return!this.ws||this.ws.readyState!==WebSocket.OPEN?{chunksProcessed:this.seqCounter}:new Promise(e=>{let t=this.ws.onmessage;this.ws.onmessage=r=>{try{let n=JSON.parse(r.data);if(n.type==="end_ack"){e({chunksProcessed:n.chunks_processed||this.seqCounter}),this.ws.close();return}}catch{}t&&this.ws&&t.call(this.ws,r)},this.ws.send(JSON.stringify({type:"end"})),setTimeout(()=>{e({chunksProcessed:this.seqCounter}),this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.close()},5e3)})}close(){this.ws&&(this.ws.close(),this.ws=null),this.pendingChunks.forEach(e=>e.reject(new Error("Stream closed"))),this.pendingChunks.clear()}isBase64(e){return e.length%4!==0?!1:/^[A-Za-z0-9+/]*={0,2}$/.test(e)}};function Ae(s){return new C(s)}var Z=class{constructor(e){this.baseUrl=e.baseUrl.replace(/\/$/,""),this.adminKey=e.adminKey,this.timeout=e.timeout||3e4,this.insecure=e.insecure||!1}async request(e,t,r){let n=`${this.baseUrl}${t}`,i={"X-Admin-Key":this.adminKey,Accept:"application/json"};r&&(i["Content-Type"]="application/json");let a=new AbortController,l=setTimeout(()=>a.abort(),this.timeout),o={method:e,headers:i,signal:a.signal};this.insecure&&typeof process<"u"&&process.env&&(process.env.NODE_TLS_REJECT_UNAUTHORIZED="0"),r&&(o.body=JSON.stringify(r));let c=await fetch(n,o);if(clearTimeout(l),!c.ok){let u=await c.json().catch(()=>({}));throw new m(u.error||`Request failed with status ${c.status}`,"ADMIN_REQUEST_ERROR",c.status,u)}return await c.json()}async registerClient(e){return this.request("POST","/api/v1/enterprise/register",e)}async listClients(){return this.request("GET","/api/v1/enterprise/clients")}async getClient(e){return this.request("GET",`/api/v1/enterprise/clients/${encodeURIComponent(e)}`)}async updateClient(e,t){return this.request("PUT",`/api/v1/enterprise/clients/${encodeURIComponent(e)}`,t)}async deactivateClient(e){return this.request("DELETE",`/api/v1/enterprise/clients/${encodeURIComponent(e)}`)}async healthCheck(){try{return await this.request("GET","/health"),!0}catch{return!1}}},Se=C;return he(Ee);})();
|