wolfronix-sdk 1.3.2 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -16
- package/dist/chunk-EDTKPA2L.mjs +33 -0
- package/dist/index.d.mts +372 -7
- package/dist/index.d.ts +372 -7
- package/dist/index.js +18933 -21
- package/dist/index.mjs +506 -18
- package/dist/undici-BDVTXO27.mjs +18410 -0
- package/package.json +5 -12
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import "./chunk-EDTKPA2L.mjs";
|
|
2
|
+
|
|
1
3
|
// src/crypto.ts
|
|
2
4
|
var getCrypto = () => {
|
|
3
5
|
if (typeof globalThis.crypto !== "undefined") {
|
|
4
6
|
return globalThis.crypto;
|
|
5
7
|
}
|
|
6
8
|
throw new Error(
|
|
7
|
-
"Web Crypto API not available. Requires a modern browser or Node.js
|
|
9
|
+
"Web Crypto API not available. Requires a modern browser or Node.js 18+."
|
|
8
10
|
);
|
|
9
11
|
};
|
|
10
12
|
var RSA_ALG = {
|
|
@@ -179,6 +181,10 @@ async function rsaDecrypt(encryptedBase64, privateKey) {
|
|
|
179
181
|
);
|
|
180
182
|
return decrypted;
|
|
181
183
|
}
|
|
184
|
+
async function rsaDecryptBase64(encryptedBase64, privateKey) {
|
|
185
|
+
const decrypted = await rsaDecrypt(encryptedBase64, privateKey);
|
|
186
|
+
return arrayBufferToBase64(decrypted);
|
|
187
|
+
}
|
|
182
188
|
async function exportSessionKey(key) {
|
|
183
189
|
return await getCrypto().subtle.exportKey("raw", key);
|
|
184
190
|
}
|
|
@@ -291,6 +297,7 @@ var Wolfronix = class {
|
|
|
291
297
|
this.config = {
|
|
292
298
|
baseUrl: config,
|
|
293
299
|
clientId: "",
|
|
300
|
+
wolfronixKey: "",
|
|
294
301
|
timeout: 3e4,
|
|
295
302
|
retries: 3,
|
|
296
303
|
insecure: false
|
|
@@ -299,6 +306,7 @@ var Wolfronix = class {
|
|
|
299
306
|
this.config = {
|
|
300
307
|
baseUrl: config.baseUrl,
|
|
301
308
|
clientId: config.clientId || "",
|
|
309
|
+
wolfronixKey: config.wolfronixKey || "",
|
|
302
310
|
timeout: config.timeout || 3e4,
|
|
303
311
|
retries: config.retries || 3,
|
|
304
312
|
insecure: config.insecure || false
|
|
@@ -306,6 +314,10 @@ var Wolfronix = class {
|
|
|
306
314
|
}
|
|
307
315
|
this.config.baseUrl = this.config.baseUrl.replace(/\/$/, "");
|
|
308
316
|
}
|
|
317
|
+
/** Expose private key status for testing */
|
|
318
|
+
hasPrivateKey() {
|
|
319
|
+
return this.privateKey !== null;
|
|
320
|
+
}
|
|
309
321
|
// ==========================================================================
|
|
310
322
|
// Private Helpers
|
|
311
323
|
// ==========================================================================
|
|
@@ -316,6 +328,9 @@ var Wolfronix = class {
|
|
|
316
328
|
if (this.config.clientId) {
|
|
317
329
|
headers["X-Client-ID"] = this.config.clientId;
|
|
318
330
|
}
|
|
331
|
+
if (this.config.wolfronixKey) {
|
|
332
|
+
headers["X-Wolfronix-Key"] = this.config.wolfronixKey;
|
|
333
|
+
}
|
|
319
334
|
if (includeAuth && this.token) {
|
|
320
335
|
headers["Authorization"] = `Bearer ${this.token}`;
|
|
321
336
|
if (this.userId) {
|
|
@@ -341,6 +356,15 @@ var Wolfronix = class {
|
|
|
341
356
|
headers,
|
|
342
357
|
signal: controller.signal
|
|
343
358
|
};
|
|
359
|
+
if (this.config.insecure && typeof process !== "undefined") {
|
|
360
|
+
try {
|
|
361
|
+
const { Agent } = await import("./undici-BDVTXO27.mjs");
|
|
362
|
+
fetchOptions.dispatcher = new Agent({
|
|
363
|
+
connect: { rejectUnauthorized: false }
|
|
364
|
+
});
|
|
365
|
+
} catch {
|
|
366
|
+
}
|
|
367
|
+
}
|
|
344
368
|
if (formData) {
|
|
345
369
|
fetchOptions.body = formData;
|
|
346
370
|
} else if (body) {
|
|
@@ -428,7 +452,7 @@ var Wolfronix = class {
|
|
|
428
452
|
this.publicKey = keyPair.publicKey;
|
|
429
453
|
this.privateKey = keyPair.privateKey;
|
|
430
454
|
this.publicKeyPEM = publicKeyPEM;
|
|
431
|
-
this.token = "
|
|
455
|
+
this.token = "zk-session";
|
|
432
456
|
}
|
|
433
457
|
return response;
|
|
434
458
|
}
|
|
@@ -463,7 +487,7 @@ var Wolfronix = class {
|
|
|
463
487
|
this.publicKeyPEM = response.public_key_pem;
|
|
464
488
|
this.publicKey = await importKeyFromPEM(response.public_key_pem, "public");
|
|
465
489
|
this.userId = email;
|
|
466
|
-
this.token = "
|
|
490
|
+
this.token = "zk-session";
|
|
467
491
|
return {
|
|
468
492
|
success: true,
|
|
469
493
|
user_id: email,
|
|
@@ -560,7 +584,14 @@ var Wolfronix = class {
|
|
|
560
584
|
};
|
|
561
585
|
}
|
|
562
586
|
/**
|
|
563
|
-
* Decrypt and retrieve a file
|
|
587
|
+
* Decrypt and retrieve a file using zero-knowledge flow.
|
|
588
|
+
*
|
|
589
|
+
* Flow:
|
|
590
|
+
* 1. GET /api/v1/files/{id}/key → encrypted key_part_a
|
|
591
|
+
* 2. Decrypt key_part_a client-side with private key (RSA-OAEP)
|
|
592
|
+
* 3. POST /api/v1/files/{id}/decrypt with { decrypted_key_a } in body
|
|
593
|
+
*
|
|
594
|
+
* The private key NEVER leaves the client.
|
|
564
595
|
*
|
|
565
596
|
* @example
|
|
566
597
|
* ```typescript
|
|
@@ -573,7 +604,7 @@ var Wolfronix = class {
|
|
|
573
604
|
* fs.writeFileSync('decrypted.pdf', buffer);
|
|
574
605
|
* ```
|
|
575
606
|
*/
|
|
576
|
-
async decrypt(fileId) {
|
|
607
|
+
async decrypt(fileId, role = "owner") {
|
|
577
608
|
this.ensureAuthenticated();
|
|
578
609
|
if (!fileId) {
|
|
579
610
|
throw new ValidationError("File ID is required");
|
|
@@ -581,19 +612,20 @@ var Wolfronix = class {
|
|
|
581
612
|
if (!this.privateKey) {
|
|
582
613
|
throw new Error("Private key not available. Is user logged in?");
|
|
583
614
|
}
|
|
584
|
-
const
|
|
615
|
+
const keyResponse = await this.getFileKey(fileId);
|
|
616
|
+
const decryptedKeyA = await rsaDecryptBase64(keyResponse.key_part_a, this.privateKey);
|
|
585
617
|
return this.request("POST", `/api/v1/files/${fileId}/decrypt`, {
|
|
586
618
|
responseType: "blob",
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
619
|
+
body: {
|
|
620
|
+
decrypted_key_a: decryptedKeyA,
|
|
621
|
+
user_role: role
|
|
590
622
|
}
|
|
591
623
|
});
|
|
592
624
|
}
|
|
593
625
|
/**
|
|
594
|
-
* Decrypt and return as ArrayBuffer
|
|
626
|
+
* Decrypt and return as ArrayBuffer (zero-knowledge flow)
|
|
595
627
|
*/
|
|
596
|
-
async decryptToBuffer(fileId) {
|
|
628
|
+
async decryptToBuffer(fileId, role = "owner") {
|
|
597
629
|
this.ensureAuthenticated();
|
|
598
630
|
if (!fileId) {
|
|
599
631
|
throw new ValidationError("File ID is required");
|
|
@@ -601,15 +633,29 @@ var Wolfronix = class {
|
|
|
601
633
|
if (!this.privateKey) {
|
|
602
634
|
throw new Error("Private key not available. Is user logged in?");
|
|
603
635
|
}
|
|
604
|
-
const
|
|
636
|
+
const keyResponse = await this.getFileKey(fileId);
|
|
637
|
+
const decryptedKeyA = await rsaDecryptBase64(keyResponse.key_part_a, this.privateKey);
|
|
605
638
|
return this.request("POST", `/api/v1/files/${fileId}/decrypt`, {
|
|
606
639
|
responseType: "arraybuffer",
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
640
|
+
body: {
|
|
641
|
+
decrypted_key_a: decryptedKeyA,
|
|
642
|
+
user_role: role
|
|
610
643
|
}
|
|
611
644
|
});
|
|
612
645
|
}
|
|
646
|
+
/**
|
|
647
|
+
* Fetch the encrypted key_part_a for a file (for client-side decryption)
|
|
648
|
+
*
|
|
649
|
+
* @param fileId The file ID to get the key for
|
|
650
|
+
* @returns KeyPartResponse containing the RSA-OAEP encrypted key_part_a
|
|
651
|
+
*/
|
|
652
|
+
async getFileKey(fileId) {
|
|
653
|
+
this.ensureAuthenticated();
|
|
654
|
+
if (!fileId) {
|
|
655
|
+
throw new ValidationError("File ID is required");
|
|
656
|
+
}
|
|
657
|
+
return this.request("GET", `/api/v1/files/${fileId}/key`);
|
|
658
|
+
}
|
|
613
659
|
/**
|
|
614
660
|
* List all encrypted files for current user
|
|
615
661
|
*
|
|
@@ -654,11 +700,19 @@ var Wolfronix = class {
|
|
|
654
700
|
/**
|
|
655
701
|
* Get another user's public key (for E2E encryption)
|
|
656
702
|
* @param userId The ID of the recipient
|
|
703
|
+
* @param clientId Optional: override the configured clientId
|
|
657
704
|
*/
|
|
658
|
-
async getPublicKey(userId) {
|
|
705
|
+
async getPublicKey(userId, clientId) {
|
|
659
706
|
this.ensureAuthenticated();
|
|
660
|
-
const
|
|
661
|
-
|
|
707
|
+
const cid = clientId || this.config.clientId;
|
|
708
|
+
if (!cid) {
|
|
709
|
+
throw new ValidationError("clientId is required for getPublicKey(). Set it in config or pass as second argument.");
|
|
710
|
+
}
|
|
711
|
+
const result = await this.request(
|
|
712
|
+
"GET",
|
|
713
|
+
`/api/v1/keys/public/${encodeURIComponent(cid)}/${encodeURIComponent(userId)}`
|
|
714
|
+
);
|
|
715
|
+
return result.public_key_pem;
|
|
662
716
|
}
|
|
663
717
|
/**
|
|
664
718
|
* Encrypt a short text message for a recipient (Hybrid Encryption: RSA + AES)
|
|
@@ -711,6 +765,166 @@ var Wolfronix = class {
|
|
|
711
765
|
}
|
|
712
766
|
}
|
|
713
767
|
// ==========================================================================
|
|
768
|
+
// Server-Side Message Encryption (Dual-Key Split)
|
|
769
|
+
// ==========================================================================
|
|
770
|
+
/**
|
|
771
|
+
* Encrypt a text message via the Wolfronix server (dual-key split).
|
|
772
|
+
* The server generates an AES key, encrypts the message, and splits the key —
|
|
773
|
+
* you get key_part_a, the server holds key_part_b.
|
|
774
|
+
*
|
|
775
|
+
* Use this for server-managed message encryption (e.g., stored encrypted messages).
|
|
776
|
+
* For true E2E (where the server never sees plaintext), use encryptMessage() instead.
|
|
777
|
+
*
|
|
778
|
+
* @param message The plaintext message to encrypt
|
|
779
|
+
* @param options.layer 3 = AES only (full key returned), 4 = dual-key split (default)
|
|
780
|
+
*
|
|
781
|
+
* @example
|
|
782
|
+
* ```typescript
|
|
783
|
+
* const result = await wfx.serverEncrypt('Hello, World!');
|
|
784
|
+
* // Store result.encrypted_message, result.nonce, result.key_part_a, result.message_tag
|
|
785
|
+
* ```
|
|
786
|
+
*/
|
|
787
|
+
async serverEncrypt(message, options) {
|
|
788
|
+
this.ensureAuthenticated();
|
|
789
|
+
if (!message) {
|
|
790
|
+
throw new ValidationError("Message is required");
|
|
791
|
+
}
|
|
792
|
+
return this.request("POST", "/api/v1/messages/encrypt", {
|
|
793
|
+
body: {
|
|
794
|
+
message,
|
|
795
|
+
user_id: this.userId,
|
|
796
|
+
layer: options?.layer || 4
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Decrypt a message previously encrypted via serverEncrypt().
|
|
802
|
+
*
|
|
803
|
+
* @param params The encrypted message data (from serverEncrypt result)
|
|
804
|
+
* @returns The decrypted plaintext message
|
|
805
|
+
*
|
|
806
|
+
* @example
|
|
807
|
+
* ```typescript
|
|
808
|
+
* const text = await wfx.serverDecrypt({
|
|
809
|
+
* encryptedMessage: result.encrypted_message,
|
|
810
|
+
* nonce: result.nonce,
|
|
811
|
+
* keyPartA: result.key_part_a,
|
|
812
|
+
* messageTag: result.message_tag,
|
|
813
|
+
* });
|
|
814
|
+
* ```
|
|
815
|
+
*/
|
|
816
|
+
async serverDecrypt(params) {
|
|
817
|
+
this.ensureAuthenticated();
|
|
818
|
+
if (!params.encryptedMessage || !params.nonce || !params.keyPartA) {
|
|
819
|
+
throw new ValidationError("encryptedMessage, nonce, and keyPartA are required");
|
|
820
|
+
}
|
|
821
|
+
const response = await this.request(
|
|
822
|
+
"POST",
|
|
823
|
+
"/api/v1/messages/decrypt",
|
|
824
|
+
{
|
|
825
|
+
body: {
|
|
826
|
+
encrypted_message: params.encryptedMessage,
|
|
827
|
+
nonce: params.nonce,
|
|
828
|
+
key_part_a: params.keyPartA,
|
|
829
|
+
message_tag: params.messageTag || "",
|
|
830
|
+
user_id: this.userId
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
);
|
|
834
|
+
return response.message;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Encrypt multiple messages in a single round-trip (batch).
|
|
838
|
+
* All messages share one AES key (different nonce per message).
|
|
839
|
+
* Efficient for chat history encryption or bulk operations.
|
|
840
|
+
*
|
|
841
|
+
* @param messages Array of { id, message } objects (max 100)
|
|
842
|
+
* @param options.layer 3 or 4 (default: 4)
|
|
843
|
+
*
|
|
844
|
+
* @example
|
|
845
|
+
* ```typescript
|
|
846
|
+
* const result = await wfx.serverEncryptBatch([
|
|
847
|
+
* { id: 'msg1', message: 'Hello' },
|
|
848
|
+
* { id: 'msg2', message: 'World' },
|
|
849
|
+
* ]);
|
|
850
|
+
* // result.results[0].encrypted_message, result.key_part_a, result.batch_tag
|
|
851
|
+
* ```
|
|
852
|
+
*/
|
|
853
|
+
async serverEncryptBatch(messages, options) {
|
|
854
|
+
this.ensureAuthenticated();
|
|
855
|
+
if (!messages || messages.length === 0) {
|
|
856
|
+
throw new ValidationError("At least one message is required");
|
|
857
|
+
}
|
|
858
|
+
if (messages.length > 100) {
|
|
859
|
+
throw new ValidationError("Maximum 100 messages per batch");
|
|
860
|
+
}
|
|
861
|
+
return this.request("POST", "/api/v1/messages/batch/encrypt", {
|
|
862
|
+
body: {
|
|
863
|
+
messages,
|
|
864
|
+
user_id: this.userId,
|
|
865
|
+
layer: options?.layer || 4
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Decrypt a single message from a batch result.
|
|
871
|
+
* Uses the shared key_part_a and batch_tag from the batch result.
|
|
872
|
+
*
|
|
873
|
+
* @param batchResult The batch encrypt result
|
|
874
|
+
* @param index The index of the message to decrypt
|
|
875
|
+
*/
|
|
876
|
+
async serverDecryptBatchItem(batchResult, index) {
|
|
877
|
+
if (index < 0 || index >= batchResult.results.length) {
|
|
878
|
+
throw new ValidationError("Invalid batch index");
|
|
879
|
+
}
|
|
880
|
+
const item = batchResult.results[index];
|
|
881
|
+
return this.serverDecrypt({
|
|
882
|
+
encryptedMessage: item.encrypted_message,
|
|
883
|
+
nonce: item.nonce,
|
|
884
|
+
keyPartA: batchResult.key_part_a,
|
|
885
|
+
messageTag: batchResult.batch_tag
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
// ==========================================================================
|
|
889
|
+
// Real-Time Streaming Encryption (WebSocket)
|
|
890
|
+
// ==========================================================================
|
|
891
|
+
/**
|
|
892
|
+
* Create a streaming encryption/decryption session over WebSocket.
|
|
893
|
+
* Data flows in real-time: send chunks, receive encrypted/decrypted chunks back.
|
|
894
|
+
*
|
|
895
|
+
* @param direction 'encrypt' for plaintext→ciphertext, 'decrypt' for reverse
|
|
896
|
+
* @param streamKey Required for decrypt — the key_part_a and stream_tag from the encrypt session
|
|
897
|
+
*
|
|
898
|
+
* @example
|
|
899
|
+
* ```typescript
|
|
900
|
+
* // Encrypt stream
|
|
901
|
+
* const stream = await wfx.createStream('encrypt');
|
|
902
|
+
* stream.onData((chunk, seq) => console.log('Encrypted chunk', seq));
|
|
903
|
+
* stream.send('Hello chunk 1');
|
|
904
|
+
* stream.send('Hello chunk 2');
|
|
905
|
+
* const summary = await stream.end();
|
|
906
|
+
* // Save stream.keyPartA and stream.streamTag for decryption
|
|
907
|
+
*
|
|
908
|
+
* // Decrypt stream
|
|
909
|
+
* const dStream = await wfx.createStream('decrypt', {
|
|
910
|
+
* keyPartA: stream.keyPartA!,
|
|
911
|
+
* streamTag: stream.streamTag!,
|
|
912
|
+
* });
|
|
913
|
+
* dStream.onData((chunk, seq) => console.log('Decrypted:', chunk));
|
|
914
|
+
* dStream.send(encryptedChunk1);
|
|
915
|
+
* await dStream.end();
|
|
916
|
+
* ```
|
|
917
|
+
*/
|
|
918
|
+
async createStream(direction, streamKey) {
|
|
919
|
+
this.ensureAuthenticated();
|
|
920
|
+
if (direction === "decrypt" && !streamKey) {
|
|
921
|
+
throw new ValidationError("streamKey (keyPartA + streamTag) is required for decrypt streams");
|
|
922
|
+
}
|
|
923
|
+
const stream = new WolfronixStream(this.config, this.userId);
|
|
924
|
+
await stream.connect(direction, streamKey);
|
|
925
|
+
return stream;
|
|
926
|
+
}
|
|
927
|
+
// ==========================================================================
|
|
714
928
|
// Metrics & Status
|
|
715
929
|
// ==========================================================================
|
|
716
930
|
/**
|
|
@@ -740,9 +954,281 @@ var Wolfronix = class {
|
|
|
740
954
|
}
|
|
741
955
|
}
|
|
742
956
|
};
|
|
957
|
+
var WolfronixStream = class {
|
|
958
|
+
/** @internal */
|
|
959
|
+
constructor(config, userId) {
|
|
960
|
+
this.config = config;
|
|
961
|
+
this.userId = userId;
|
|
962
|
+
this.ws = null;
|
|
963
|
+
this.dataCallbacks = [];
|
|
964
|
+
this.errorCallbacks = [];
|
|
965
|
+
this.pendingChunks = /* @__PURE__ */ new Map();
|
|
966
|
+
this.seqCounter = 0;
|
|
967
|
+
/** Client's key half (available after encrypt stream init) */
|
|
968
|
+
this.keyPartA = null;
|
|
969
|
+
/** Stream tag (available after encrypt stream init) */
|
|
970
|
+
this.streamTag = null;
|
|
971
|
+
}
|
|
972
|
+
/** @internal Connect and initialize the stream session */
|
|
973
|
+
async connect(direction, streamKey) {
|
|
974
|
+
return new Promise((resolve, reject) => {
|
|
975
|
+
const wsBase = this.config.baseUrl.replace(/^http/, "ws");
|
|
976
|
+
const params = new URLSearchParams();
|
|
977
|
+
if (this.config.wolfronixKey) {
|
|
978
|
+
params.set("wolfronix_key", this.config.wolfronixKey);
|
|
979
|
+
}
|
|
980
|
+
if (this.config.clientId) {
|
|
981
|
+
params.set("client_id", this.config.clientId);
|
|
982
|
+
}
|
|
983
|
+
const wsUrl = `${wsBase}/api/v1/stream?${params.toString()}`;
|
|
984
|
+
this.ws = new WebSocket(wsUrl);
|
|
985
|
+
this.ws.onopen = () => {
|
|
986
|
+
const initMsg = { type: "init", direction };
|
|
987
|
+
if (direction === "decrypt" && streamKey) {
|
|
988
|
+
initMsg.key_part_a = streamKey.keyPartA;
|
|
989
|
+
initMsg.stream_tag = streamKey.streamTag;
|
|
990
|
+
}
|
|
991
|
+
this.ws.send(JSON.stringify(initMsg));
|
|
992
|
+
};
|
|
993
|
+
let initResolved = false;
|
|
994
|
+
this.ws.onmessage = (event) => {
|
|
995
|
+
try {
|
|
996
|
+
const msg = JSON.parse(event.data);
|
|
997
|
+
if (msg.type === "error") {
|
|
998
|
+
const err = new Error(msg.error);
|
|
999
|
+
if (!initResolved) {
|
|
1000
|
+
initResolved = true;
|
|
1001
|
+
reject(err);
|
|
1002
|
+
}
|
|
1003
|
+
this.errorCallbacks.forEach((cb) => cb(err));
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
if (msg.type === "init_ack" && !initResolved) {
|
|
1007
|
+
initResolved = true;
|
|
1008
|
+
if (msg.key_part_a) this.keyPartA = msg.key_part_a;
|
|
1009
|
+
if (msg.stream_tag) this.streamTag = msg.stream_tag;
|
|
1010
|
+
resolve();
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
if (msg.type === "data") {
|
|
1014
|
+
this.dataCallbacks.forEach((cb) => cb(msg.data, msg.seq));
|
|
1015
|
+
const pending = this.pendingChunks.get(msg.seq);
|
|
1016
|
+
if (pending) {
|
|
1017
|
+
pending.resolve(msg.data);
|
|
1018
|
+
this.pendingChunks.delete(msg.seq);
|
|
1019
|
+
}
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
if (msg.type === "end_ack") {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
} catch (e) {
|
|
1026
|
+
const err = new Error("Failed to parse stream message");
|
|
1027
|
+
this.errorCallbacks.forEach((cb) => cb(err));
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
this.ws.onerror = (event) => {
|
|
1031
|
+
const err = new Error("WebSocket error");
|
|
1032
|
+
if (!initResolved) {
|
|
1033
|
+
initResolved = true;
|
|
1034
|
+
reject(err);
|
|
1035
|
+
}
|
|
1036
|
+
this.errorCallbacks.forEach((cb) => cb(err));
|
|
1037
|
+
};
|
|
1038
|
+
this.ws.onclose = () => {
|
|
1039
|
+
this.pendingChunks.forEach((p) => p.reject(new Error("Stream closed")));
|
|
1040
|
+
this.pendingChunks.clear();
|
|
1041
|
+
};
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Send a data chunk for encryption/decryption.
|
|
1046
|
+
* Returns a promise that resolves with the processed (encrypted/decrypted) chunk.
|
|
1047
|
+
*
|
|
1048
|
+
* @param data String or base64-encoded binary data
|
|
1049
|
+
* @returns The processed chunk (base64-encoded)
|
|
1050
|
+
*/
|
|
1051
|
+
async send(data) {
|
|
1052
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
1053
|
+
throw new Error("Stream not connected");
|
|
1054
|
+
}
|
|
1055
|
+
const b64Data = this.isBase64(data) ? data : btoa(data);
|
|
1056
|
+
const seq = this.seqCounter++;
|
|
1057
|
+
return new Promise((resolve, reject) => {
|
|
1058
|
+
this.pendingChunks.set(seq, { resolve, reject });
|
|
1059
|
+
this.ws.send(JSON.stringify({ type: "data", data: b64Data }));
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Send raw binary data for encryption/decryption.
|
|
1064
|
+
*
|
|
1065
|
+
* @param buffer ArrayBuffer or Uint8Array
|
|
1066
|
+
* @returns The processed chunk (base64-encoded)
|
|
1067
|
+
*/
|
|
1068
|
+
async sendBinary(buffer) {
|
|
1069
|
+
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
1070
|
+
let binary = "";
|
|
1071
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
1072
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1073
|
+
}
|
|
1074
|
+
const b64 = btoa(binary);
|
|
1075
|
+
return this.send(b64);
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Register a callback for incoming data chunks.
|
|
1079
|
+
*
|
|
1080
|
+
* @param callback Called with (base64Data, sequenceNumber) for each chunk
|
|
1081
|
+
*/
|
|
1082
|
+
onData(callback) {
|
|
1083
|
+
this.dataCallbacks.push(callback);
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Register a callback for stream errors.
|
|
1087
|
+
*/
|
|
1088
|
+
onError(callback) {
|
|
1089
|
+
this.errorCallbacks.push(callback);
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* End the stream session. Returns the total number of chunks processed.
|
|
1093
|
+
*/
|
|
1094
|
+
async end() {
|
|
1095
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
1096
|
+
return { chunksProcessed: this.seqCounter };
|
|
1097
|
+
}
|
|
1098
|
+
return new Promise((resolve) => {
|
|
1099
|
+
const originalHandler = this.ws.onmessage;
|
|
1100
|
+
this.ws.onmessage = (event) => {
|
|
1101
|
+
try {
|
|
1102
|
+
const msg = JSON.parse(event.data);
|
|
1103
|
+
if (msg.type === "end_ack") {
|
|
1104
|
+
resolve({ chunksProcessed: msg.chunks_processed || this.seqCounter });
|
|
1105
|
+
this.ws.close();
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
} catch {
|
|
1109
|
+
}
|
|
1110
|
+
if (originalHandler && this.ws) originalHandler.call(this.ws, event);
|
|
1111
|
+
};
|
|
1112
|
+
this.ws.send(JSON.stringify({ type: "end" }));
|
|
1113
|
+
setTimeout(() => {
|
|
1114
|
+
resolve({ chunksProcessed: this.seqCounter });
|
|
1115
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
1116
|
+
this.ws.close();
|
|
1117
|
+
}
|
|
1118
|
+
}, 5e3);
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Close the stream immediately without sending an end message.
|
|
1123
|
+
*/
|
|
1124
|
+
close() {
|
|
1125
|
+
if (this.ws) {
|
|
1126
|
+
this.ws.close();
|
|
1127
|
+
this.ws = null;
|
|
1128
|
+
}
|
|
1129
|
+
this.pendingChunks.forEach((p) => p.reject(new Error("Stream closed")));
|
|
1130
|
+
this.pendingChunks.clear();
|
|
1131
|
+
}
|
|
1132
|
+
isBase64(str) {
|
|
1133
|
+
if (str.length % 4 !== 0) return false;
|
|
1134
|
+
return /^[A-Za-z0-9+/]*={0,2}$/.test(str);
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
743
1137
|
function createClient(config) {
|
|
744
1138
|
return new Wolfronix(config);
|
|
745
1139
|
}
|
|
1140
|
+
var WolfronixAdmin = class {
|
|
1141
|
+
constructor(config) {
|
|
1142
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
1143
|
+
this.adminKey = config.adminKey;
|
|
1144
|
+
this.timeout = config.timeout || 3e4;
|
|
1145
|
+
this.insecure = config.insecure || false;
|
|
1146
|
+
}
|
|
1147
|
+
async request(method, endpoint, body) {
|
|
1148
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
1149
|
+
const headers = {
|
|
1150
|
+
"X-Admin-Key": this.adminKey,
|
|
1151
|
+
"Accept": "application/json"
|
|
1152
|
+
};
|
|
1153
|
+
if (body) {
|
|
1154
|
+
headers["Content-Type"] = "application/json";
|
|
1155
|
+
}
|
|
1156
|
+
const controller = new AbortController();
|
|
1157
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
1158
|
+
const fetchOptions = {
|
|
1159
|
+
method,
|
|
1160
|
+
headers,
|
|
1161
|
+
signal: controller.signal
|
|
1162
|
+
};
|
|
1163
|
+
if (this.insecure && typeof process !== "undefined") {
|
|
1164
|
+
try {
|
|
1165
|
+
const { Agent } = await import("./undici-BDVTXO27.mjs");
|
|
1166
|
+
fetchOptions.dispatcher = new Agent({
|
|
1167
|
+
connect: { rejectUnauthorized: false }
|
|
1168
|
+
});
|
|
1169
|
+
} catch {
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
if (body) {
|
|
1173
|
+
fetchOptions.body = JSON.stringify(body);
|
|
1174
|
+
}
|
|
1175
|
+
const response = await fetch(url, fetchOptions);
|
|
1176
|
+
clearTimeout(timeoutId);
|
|
1177
|
+
if (!response.ok) {
|
|
1178
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
1179
|
+
throw new WolfronixError(
|
|
1180
|
+
errorBody.error || `Request failed with status ${response.status}`,
|
|
1181
|
+
"ADMIN_REQUEST_ERROR",
|
|
1182
|
+
response.status,
|
|
1183
|
+
errorBody
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
return await response.json();
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Register a new enterprise client.
|
|
1190
|
+
* For managed connectors (supabase, mongodb, mysql, firebase, postgresql),
|
|
1191
|
+
* provide db_type + db_config. For custom APIs, use db_type: 'custom_api' + api_endpoint.
|
|
1192
|
+
*/
|
|
1193
|
+
async registerClient(params) {
|
|
1194
|
+
return this.request("POST", "/api/v1/enterprise/register", params);
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* List all registered enterprise clients.
|
|
1198
|
+
*/
|
|
1199
|
+
async listClients() {
|
|
1200
|
+
return this.request("GET", "/api/v1/enterprise/clients");
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Get details for a specific client.
|
|
1204
|
+
*/
|
|
1205
|
+
async getClient(clientId) {
|
|
1206
|
+
return this.request("GET", `/api/v1/enterprise/clients/${encodeURIComponent(clientId)}`);
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Update a client's configuration (api_endpoint, db_type, db_config).
|
|
1210
|
+
*/
|
|
1211
|
+
async updateClient(clientId, params) {
|
|
1212
|
+
return this.request("PUT", `/api/v1/enterprise/clients/${encodeURIComponent(clientId)}`, params);
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Deactivate (soft-delete) a client. Their wolfronix_key will stop working.
|
|
1216
|
+
*/
|
|
1217
|
+
async deactivateClient(clientId) {
|
|
1218
|
+
return this.request("DELETE", `/api/v1/enterprise/clients/${encodeURIComponent(clientId)}`);
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Check server health.
|
|
1222
|
+
*/
|
|
1223
|
+
async healthCheck() {
|
|
1224
|
+
try {
|
|
1225
|
+
await this.request("GET", "/health");
|
|
1226
|
+
return true;
|
|
1227
|
+
} catch {
|
|
1228
|
+
return false;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
746
1232
|
var index_default = Wolfronix;
|
|
747
1233
|
export {
|
|
748
1234
|
AuthenticationError,
|
|
@@ -751,7 +1237,9 @@ export {
|
|
|
751
1237
|
PermissionDeniedError,
|
|
752
1238
|
ValidationError,
|
|
753
1239
|
Wolfronix,
|
|
1240
|
+
WolfronixAdmin,
|
|
754
1241
|
WolfronixError,
|
|
1242
|
+
WolfronixStream,
|
|
755
1243
|
createClient,
|
|
756
1244
|
index_default as default
|
|
757
1245
|
};
|