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/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 16+."
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 = "session_" + Date.now();
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 = "session_" + Date.now();
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 privateKeyPEM = await exportKeyToPEM(this.privateKey, "private");
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
- headers: {
588
- "X-Private-Key": privateKeyPEM,
589
- "X-User-Role": "owner"
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 privateKeyPEM = await exportKeyToPEM(this.privateKey, "private");
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
- headers: {
608
- "X-Private-Key": privateKeyPEM,
609
- "X-User-Role": "owner"
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 result = await this.request("GET", `/api/v1/keys/${userId}`);
661
- return result.public_key;
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
  };