wolfronix-sdk 1.2.0 → 1.3.1

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 CHANGED
@@ -1,19 +1,17 @@
1
- # @wolfronix/sdk
1
+ # wolfronix-sdk
2
2
 
3
3
  Official JavaScript/TypeScript SDK for Wolfronix - Zero-knowledge encryption made simple.
4
4
 
5
- [![npm version](https://badge.fury.io/js/@wolfronix%2Fsdk.svg)](https://www.npmjs.com/package/@wolfronix/sdk)
5
+ [![npm version](https://badge.fury.io/js/wolfronix-sdk.svg)](https://www.npmjs.com/package/wolfronix-sdk)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
8
  ## Features
9
9
 
10
10
  - 🔐 **Zero-Knowledge Encryption** - Keys generated client-side, never leave your device
11
11
  - 🏢 **Enterprise Ready** - Seamless integration with your existing storage
12
-
13
12
  - 🚀 **Simple API** - Encrypt files in 2 lines of code
14
13
  - 📦 **TypeScript Native** - Full type definitions included
15
- - 🌐 **Universal** - Works in Node.js and browsers
16
- - ⚡ **Streaming** - Handle large files with progress tracking
14
+ - 🌐 **Universal** - Works in Node.js 16+ and modern browsers
17
15
  - 🔄 **Auto Retry** - Built-in retry logic with exponential backoff
18
16
 
19
17
  ## Backend Integration (Enterprise Mode)
@@ -30,17 +28,17 @@ Wolfronix handles all encryption/decryption keys and logic; you only handle the
30
28
  ## Installation
31
29
 
32
30
  ```bash
33
- npm install @wolfronix/sdk
31
+ npm install wolfronix-sdk
34
32
  # or
35
- yarn add @wolfronix/sdk
33
+ yarn add wolfronix-sdk
36
34
  # or
37
- pnpm add @wolfronix/sdk
35
+ pnpm add wolfronix-sdk
38
36
  ```
39
37
 
40
38
  ## Quick Start
41
39
 
42
40
  ```typescript
43
- import Wolfronix from '@wolfronix/sdk';
41
+ import Wolfronix from 'wolfronix-sdk';
44
42
 
45
43
  // Initialize client
46
44
  const wfx = new Wolfronix({
@@ -67,7 +65,7 @@ const decrypted = await wfx.decrypt(result.file_id);
67
65
  ### Browser (React, Vue, Angular, etc.)
68
66
 
69
67
  ```typescript
70
- import Wolfronix from '@wolfronix/sdk';
68
+ import Wolfronix from 'wolfronix-sdk';
71
69
 
72
70
  const wfx = new Wolfronix('https://wolfronix-server:5002');
73
71
 
@@ -77,7 +75,6 @@ const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
77
75
  if (!file) return;
78
76
 
79
77
  try {
80
- // Keys are automatically handled by the SDK
81
78
  const { file_id } = await wfx.encrypt(file);
82
79
  console.log('File encrypted with your private key:', file_id);
83
80
  } catch (error) {
@@ -102,7 +99,7 @@ const handleDownload = async (fileId: string, filename: string) => {
102
99
  ### Node.js
103
100
 
104
101
  ```typescript
105
- import Wolfronix from '@wolfronix/sdk';
102
+ import Wolfronix from 'wolfronix-sdk';
106
103
  import * as fs from 'fs';
107
104
 
108
105
  const wfx = new Wolfronix({
@@ -129,26 +126,50 @@ async function main() {
129
126
  fs.writeFileSync('decrypted.pdf', Buffer.from(decrypted));
130
127
  }
131
128
 
129
+ main();
130
+ // Decrypt and save
131
+ const decrypted = await wfx.decryptToBuffer(file_id);
132
+ fs.writeFileSync('decrypted.pdf', Buffer.from(decrypted));
133
+ }
134
+
132
135
  main();
133
136
  ```
134
137
 
135
- ### Large File Streaming with Progress
138
+ ### 💬 E2E Encrypted Chat Integration
139
+
140
+ Turn any chat app into a secure, end-to-end encrypted messenger in minutes.
136
141
 
142
+ **Sender (Alice):**
137
143
  ```typescript
138
- const wfx = new Wolfronix('https://wolfronix-server:5002');
144
+ // 1. Get Bob's Public Key & Encrypt Message
145
+ const securePacket = await wfx.encryptMessage("Secret details at 5 PM", "bob_user_id");
139
146
 
140
- // Encrypt with progress
141
- const result = await wfx.encryptStream(largeFile, (percent) => {
142
- console.log(`Uploading: ${percent}%`);
143
- progressBar.value = percent;
147
+ // 2. Send 'securePacket' string via your normal chat API (Socket.io, Firebase, etc.)
148
+ chatSocket.emit('message', {
149
+ to: 'bob',
150
+ text: securePacket // Valid JSON string
144
151
  });
152
+ ```
145
153
 
146
- // Decrypt with progress
147
- const blob = await wfx.decryptStream(fileId, (percent) => {
148
- console.log(`Downloading: ${percent}%`);
154
+ **Recipient (Bob):**
155
+ ```typescript
156
+ // 1. Receive message from chat server
157
+ chatSocket.on('message', async (msg) => {
158
+ try {
159
+ // 2. Decrypt locally with Bob's Private Key
160
+ const plainText = await wfx.decryptMessage(msg.text);
161
+ console.log("Decrypted:", plainText);
162
+ } catch (err) {
163
+ console.error("Could not decrypt message");
164
+ }
149
165
  });
150
166
  ```
151
167
 
168
+ **Features:**
169
+ - **Hybrid Encryption:** Uses AES-256 for messages + RSA-2048 for key exchange (Fast & Secure).
170
+ - **Zero-Knowledge:** Your chat server only sees encrypted packets.
171
+ - **Universal:** Works with any backend (Socket.io, Firebase, PostgreSQL, etc).
172
+
152
173
  ## API Reference
153
174
 
154
175
  ### Constructor
@@ -181,13 +202,19 @@ new Wolfronix(config: WolfronixConfig | string)
181
202
  | Method | Description |
182
203
  |--------|-------------|
183
204
  | `encrypt(file, filename?)` | Encrypt and store file |
184
- | `encryptStream(file, onProgress?)` | Encrypt large file with progress |
185
205
  | `decrypt(fileId)` | Decrypt file (returns Blob) |
186
206
  | `decryptToBuffer(fileId)` | Decrypt file (returns ArrayBuffer) |
187
- | `decryptStream(fileId, onProgress?)` | Decrypt large file with progress |
188
207
  | `listFiles()` | List user's encrypted files |
189
208
  | `deleteFile(fileId)` | Delete encrypted file |
190
209
 
210
+ ### E2E Chat Encryption
211
+
212
+ | Method | Description |
213
+ |--------|-------------|
214
+ | `getPublicKey(userId)` | Fetch a user's RSA public key |
215
+ | `encryptMessage(text, recipientId)` | Encrypt text for a recipient (returns packet string) |
216
+ | `decryptMessage(packetString)` | Decrypt a received message packet |
217
+
191
218
  ### Utility
192
219
 
193
220
  | Method | Description |
@@ -207,7 +234,7 @@ import Wolfronix, {
207
234
  PermissionDeniedError,
208
235
  NetworkError,
209
236
  ValidationError
210
- } from '@wolfronix/sdk';
237
+ } from 'wolfronix-sdk';
211
238
 
212
239
  try {
213
240
  await wfx.encrypt(file);
@@ -246,7 +273,7 @@ import Wolfronix, {
246
273
  FileInfo,
247
274
  ListFilesResponse,
248
275
  MetricsResponse
249
- } from '@wolfronix/sdk';
276
+ } from 'wolfronix-sdk';
250
277
 
251
278
  // All methods are fully typed
252
279
  const config: WolfronixConfig = {
@@ -263,7 +290,7 @@ const response: EncryptResponse = await wfx.encrypt(file);
263
290
  ```typescript
264
291
  // useWolfronix.ts
265
292
  import { useState, useCallback, useMemo } from 'react';
266
- import Wolfronix, { FileInfo as WolfronixFile } from '@wolfronix/sdk';
293
+ import Wolfronix, { FileInfo as WolfronixFile } from 'wolfronix-sdk';
267
294
 
268
295
  export function useWolfronix(baseUrl: string, clientId?: string) {
269
296
  const [isLoading, setIsLoading] = useState(false);
@@ -351,10 +378,25 @@ function FileManager() {
351
378
  }
352
379
  ```
353
380
 
381
+ ## Real-World Use Cases
382
+
383
+ Wolfronix can be integrated into **any application** that handles sensitive data:
384
+
385
+ | Industry | Application | How Wolfronix Helps |
386
+ |----------|------------|---------------------|
387
+ | 🏥 **Healthcare** | Patient records, lab reports | HIPAA-compliant encryption at rest |
388
+ | 🏦 **Finance** | Invoices, tax docs, receipts | End-to-end encrypted banking documents |
389
+ | ⚖️ **Legal** | Contracts, case files | Zero-knowledge confidential storage |
390
+ | ☁️ **Cloud Storage** | Drive/Dropbox alternatives | Encrypted file vault with user-owned keys |
391
+ | 🏢 **Enterprise** | HR records, internal docs | Per-employee encryption isolation |
392
+ | 🎓 **Education** | Exam papers, student data | Tamper-proof academic records |
393
+ | 💬 **Messaging** | File attachments | Encrypted file sharing in chat apps |
394
+ | 🛒 **E-commerce** | Order docs, payment receipts | PCI-compliant document storage |
395
+
354
396
  ## Requirements
355
397
 
356
398
  - Node.js 16+ (for Node.js usage)
357
- - Modern browser with Fetch API support
399
+ - Modern browser with Web Crypto API support
358
400
 
359
401
  ## License
360
402
 
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 1.2.0
6
+ * @version 1.3.0
7
7
  */
8
8
  interface WolfronixConfig {
9
9
  /** Wolfronix server base URL */
@@ -25,7 +25,7 @@ interface AuthResponse {
25
25
  }
26
26
  interface EncryptResponse {
27
27
  status: string;
28
- file_id: number;
28
+ file_id: string;
29
29
  file_size: number;
30
30
  enc_time_ms: number;
31
31
  }
@@ -51,6 +51,11 @@ interface MetricsResponse {
51
51
  total_bytes_encrypted: number;
52
52
  total_bytes_decrypted: number;
53
53
  }
54
+ interface EncryptMessagePacket {
55
+ key: string;
56
+ iv: string;
57
+ msg: string;
58
+ }
54
59
  declare class WolfronixError extends Error {
55
60
  readonly code: string;
56
61
  readonly statusCode?: number;
@@ -188,6 +193,25 @@ declare class Wolfronix {
188
193
  * ```
189
194
  */
190
195
  deleteFile(fileId: string): Promise<DeleteResponse>;
196
+ /**
197
+ * Get another user's public key (for E2E encryption)
198
+ * @param userId The ID of the recipient
199
+ */
200
+ getPublicKey(userId: string): Promise<string>;
201
+ /**
202
+ * Encrypt a short text message for a recipient (Hybrid Encryption: RSA + AES)
203
+ * Returns a secure JSON string (packet) to send via chat
204
+ *
205
+ * @param text The plain text message
206
+ * @param recipientId The recipient's user ID
207
+ */
208
+ encryptMessage(text: string, recipientId: string): Promise<string>;
209
+ /**
210
+ * Decrypt a message packet received from chat
211
+ *
212
+ * @param packetJson The secure JSON string packet
213
+ */
214
+ decryptMessage(packetJson: string): Promise<string>;
191
215
  /**
192
216
  * Get encryption/decryption metrics
193
217
  *
@@ -218,4 +242,4 @@ declare class Wolfronix {
218
242
  */
219
243
  declare function createClient(config: WolfronixConfig | string): Wolfronix;
220
244
 
221
- export { type AuthResponse, AuthenticationError, type DeleteResponse, type EncryptResponse, type FileInfo, FileNotFoundError, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, ValidationError, Wolfronix, type WolfronixConfig, WolfronixError, createClient, Wolfronix as default };
245
+ export { type AuthResponse, AuthenticationError, type DeleteResponse, type EncryptMessagePacket, type EncryptResponse, type FileInfo, FileNotFoundError, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, ValidationError, Wolfronix, type WolfronixConfig, WolfronixError, 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 1.2.0
6
+ * @version 1.3.0
7
7
  */
8
8
  interface WolfronixConfig {
9
9
  /** Wolfronix server base URL */
@@ -25,7 +25,7 @@ interface AuthResponse {
25
25
  }
26
26
  interface EncryptResponse {
27
27
  status: string;
28
- file_id: number;
28
+ file_id: string;
29
29
  file_size: number;
30
30
  enc_time_ms: number;
31
31
  }
@@ -51,6 +51,11 @@ interface MetricsResponse {
51
51
  total_bytes_encrypted: number;
52
52
  total_bytes_decrypted: number;
53
53
  }
54
+ interface EncryptMessagePacket {
55
+ key: string;
56
+ iv: string;
57
+ msg: string;
58
+ }
54
59
  declare class WolfronixError extends Error {
55
60
  readonly code: string;
56
61
  readonly statusCode?: number;
@@ -188,6 +193,25 @@ declare class Wolfronix {
188
193
  * ```
189
194
  */
190
195
  deleteFile(fileId: string): Promise<DeleteResponse>;
196
+ /**
197
+ * Get another user's public key (for E2E encryption)
198
+ * @param userId The ID of the recipient
199
+ */
200
+ getPublicKey(userId: string): Promise<string>;
201
+ /**
202
+ * Encrypt a short text message for a recipient (Hybrid Encryption: RSA + AES)
203
+ * Returns a secure JSON string (packet) to send via chat
204
+ *
205
+ * @param text The plain text message
206
+ * @param recipientId The recipient's user ID
207
+ */
208
+ encryptMessage(text: string, recipientId: string): Promise<string>;
209
+ /**
210
+ * Decrypt a message packet received from chat
211
+ *
212
+ * @param packetJson The secure JSON string packet
213
+ */
214
+ decryptMessage(packetJson: string): Promise<string>;
191
215
  /**
192
216
  * Get encryption/decryption metrics
193
217
  *
@@ -218,4 +242,4 @@ declare class Wolfronix {
218
242
  */
219
243
  declare function createClient(config: WolfronixConfig | string): Wolfronix;
220
244
 
221
- export { type AuthResponse, AuthenticationError, type DeleteResponse, type EncryptResponse, type FileInfo, FileNotFoundError, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, ValidationError, Wolfronix, type WolfronixConfig, WolfronixError, createClient, Wolfronix as default };
245
+ export { type AuthResponse, AuthenticationError, type DeleteResponse, type EncryptMessagePacket, type EncryptResponse, type FileInfo, FileNotFoundError, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, ValidationError, Wolfronix, type WolfronixConfig, WolfronixError, createClient, Wolfronix as default };
package/dist/index.js CHANGED
@@ -33,6 +33,14 @@ __export(index_exports, {
33
33
  module.exports = __toCommonJS(index_exports);
34
34
 
35
35
  // src/crypto.ts
36
+ var getCrypto = () => {
37
+ if (typeof globalThis.crypto !== "undefined") {
38
+ return globalThis.crypto;
39
+ }
40
+ throw new Error(
41
+ "Web Crypto API not available. Requires a modern browser or Node.js 16+."
42
+ );
43
+ };
36
44
  var RSA_ALG = {
37
45
  name: "RSA-OAEP",
38
46
  modulusLength: 2048,
@@ -40,9 +48,10 @@ var RSA_ALG = {
40
48
  hash: "SHA-256"
41
49
  };
42
50
  var WRAP_ALG = "AES-GCM";
51
+ var SESSION_ALG = "AES-GCM";
43
52
  var PBKDF2_ITERATIONS = 1e5;
44
53
  async function generateKeyPair() {
45
- return await window.crypto.subtle.generateKey(
54
+ return await getCrypto().subtle.generateKey(
46
55
  RSA_ALG,
47
56
  true,
48
57
  // extractable
@@ -51,7 +60,7 @@ async function generateKeyPair() {
51
60
  }
52
61
  async function exportKeyToPEM(key, type) {
53
62
  const format = type === "public" ? "spki" : "pkcs8";
54
- const exported = await window.crypto.subtle.exportKey(format, key);
63
+ const exported = await getCrypto().subtle.exportKey(format, key);
55
64
  const exportedAsBase64 = arrayBufferToBase64(exported);
56
65
  const pemHeader = type === "public" ? "-----BEGIN PUBLIC KEY-----" : "-----BEGIN PRIVATE KEY-----";
57
66
  const pemFooter = type === "public" ? "-----END PUBLIC KEY-----" : "-----END PRIVATE KEY-----";
@@ -66,7 +75,7 @@ async function importKeyFromPEM(pem, type) {
66
75
  const binaryDer = base64ToArrayBuffer(pemContents);
67
76
  const format = type === "public" ? "spki" : "pkcs8";
68
77
  const usage = type === "public" ? ["encrypt", "wrapKey"] : ["decrypt", "unwrapKey"];
69
- return await window.crypto.subtle.importKey(
78
+ return await getCrypto().subtle.importKey(
70
79
  format,
71
80
  binaryDer,
72
81
  RSA_ALG,
@@ -76,7 +85,7 @@ async function importKeyFromPEM(pem, type) {
76
85
  }
77
86
  async function deriveWrappingKey(password, saltHex) {
78
87
  const enc = new TextEncoder();
79
- const passwordKey = await window.crypto.subtle.importKey(
88
+ const passwordKey = await getCrypto().subtle.importKey(
80
89
  "raw",
81
90
  enc.encode(password),
82
91
  "PBKDF2",
@@ -84,7 +93,7 @@ async function deriveWrappingKey(password, saltHex) {
84
93
  ["deriveKey"]
85
94
  );
86
95
  const salt = hexToArrayBuffer(saltHex);
87
- return await window.crypto.subtle.deriveKey(
96
+ return await getCrypto().subtle.deriveKey(
88
97
  {
89
98
  name: "PBKDF2",
90
99
  salt,
@@ -98,12 +107,13 @@ async function deriveWrappingKey(password, saltHex) {
98
107
  );
99
108
  }
100
109
  async function wrapPrivateKey(privateKey, password) {
101
- const salt = window.crypto.getRandomValues(new Uint8Array(16));
110
+ const crypto = getCrypto();
111
+ const salt = crypto.getRandomValues(new Uint8Array(16));
102
112
  const saltHex = arrayBufferToHex(salt.buffer);
103
113
  const wrappingKey = await deriveWrappingKey(password, saltHex);
104
- const exportedKey = await window.crypto.subtle.exportKey("pkcs8", privateKey);
105
- const iv = window.crypto.getRandomValues(new Uint8Array(12));
106
- const encryptedContent = await window.crypto.subtle.encrypt(
114
+ const exportedKey = await crypto.subtle.exportKey("pkcs8", privateKey);
115
+ const iv = crypto.getRandomValues(new Uint8Array(12));
116
+ const encryptedContent = await crypto.subtle.encrypt(
107
117
  {
108
118
  name: WRAP_ALG,
109
119
  iv
@@ -125,7 +135,7 @@ async function unwrapPrivateKey(encryptedKeyBase64, password, saltHex) {
125
135
  const iv = combinedArray.slice(0, 12);
126
136
  const data = combinedArray.slice(12);
127
137
  const wrappingKey = await deriveWrappingKey(password, saltHex);
128
- const decryptedKeyData = await window.crypto.subtle.decrypt(
138
+ const decryptedKeyData = await getCrypto().subtle.decrypt(
129
139
  {
130
140
  name: WRAP_ALG,
131
141
  iv
@@ -133,7 +143,7 @@ async function unwrapPrivateKey(encryptedKeyBase64, password, saltHex) {
133
143
  wrappingKey,
134
144
  data
135
145
  );
136
- return await window.crypto.subtle.importKey(
146
+ return await getCrypto().subtle.importKey(
137
147
  "pkcs8",
138
148
  decryptedKeyData,
139
149
  RSA_ALG,
@@ -141,17 +151,98 @@ async function unwrapPrivateKey(encryptedKeyBase64, password, saltHex) {
141
151
  ["decrypt", "unwrapKey"]
142
152
  );
143
153
  }
154
+ async function generateSessionKey() {
155
+ return await getCrypto().subtle.generateKey(
156
+ {
157
+ name: SESSION_ALG,
158
+ length: 256
159
+ },
160
+ true,
161
+ ["encrypt", "decrypt"]
162
+ );
163
+ }
164
+ async function encryptData(data, key) {
165
+ const enc = new TextEncoder();
166
+ const encodedData = enc.encode(data);
167
+ const iv = getCrypto().getRandomValues(new Uint8Array(12));
168
+ const encryptedContent = await getCrypto().subtle.encrypt(
169
+ {
170
+ name: SESSION_ALG,
171
+ iv
172
+ },
173
+ key,
174
+ encodedData
175
+ );
176
+ return {
177
+ encrypted: arrayBufferToBase64(encryptedContent),
178
+ iv: arrayBufferToBase64(iv.buffer)
179
+ };
180
+ }
181
+ async function decryptData(encryptedBase64, ivBase64, key) {
182
+ const encryptedData = base64ToArrayBuffer(encryptedBase64);
183
+ const iv = base64ToArrayBuffer(ivBase64);
184
+ const decryptedContent = await getCrypto().subtle.decrypt(
185
+ {
186
+ name: SESSION_ALG,
187
+ iv
188
+ },
189
+ key,
190
+ encryptedData
191
+ );
192
+ const dec = new TextDecoder();
193
+ return dec.decode(decryptedContent);
194
+ }
195
+ async function rsaEncrypt(data, publicKey) {
196
+ const encrypted = await getCrypto().subtle.encrypt(
197
+ {
198
+ name: "RSA-OAEP"
199
+ },
200
+ publicKey,
201
+ data
202
+ );
203
+ return arrayBufferToBase64(encrypted);
204
+ }
205
+ async function rsaDecrypt(encryptedBase64, privateKey) {
206
+ const data = base64ToArrayBuffer(encryptedBase64);
207
+ const decrypted = await getCrypto().subtle.decrypt(
208
+ {
209
+ name: "RSA-OAEP"
210
+ },
211
+ privateKey,
212
+ data
213
+ );
214
+ return decrypted;
215
+ }
216
+ async function exportSessionKey(key) {
217
+ return await getCrypto().subtle.exportKey("raw", key);
218
+ }
219
+ async function importSessionKey(raw) {
220
+ return await getCrypto().subtle.importKey(
221
+ "raw",
222
+ raw,
223
+ SESSION_ALG,
224
+ true,
225
+ ["encrypt", "decrypt"]
226
+ );
227
+ }
144
228
  function arrayBufferToBase64(buffer) {
145
- let binary = "";
146
229
  const bytes = new Uint8Array(buffer);
230
+ if (typeof Buffer !== "undefined") {
231
+ return Buffer.from(bytes).toString("base64");
232
+ }
233
+ let binary = "";
147
234
  const len = bytes.byteLength;
148
235
  for (let i = 0; i < len; i++) {
149
236
  binary += String.fromCharCode(bytes[i]);
150
237
  }
151
- return window.btoa(binary);
238
+ return btoa(binary);
152
239
  }
153
240
  function base64ToArrayBuffer(base64) {
154
- const binary_string = window.atob(base64);
241
+ if (typeof Buffer !== "undefined") {
242
+ const buf = Buffer.from(base64, "base64");
243
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
244
+ }
245
+ const binary_string = atob(base64);
155
246
  const len = binary_string.length;
156
247
  const bytes = new Uint8Array(len);
157
248
  for (let i = 0; i < len; i++) {
@@ -492,9 +583,15 @@ var Wolfronix = class {
492
583
  throw new Error("Public key not available. Is user logged in?");
493
584
  }
494
585
  formData.append("client_public_key", this.publicKeyPEM);
495
- return this.request("POST", "/api/v1/encrypt", {
586
+ const response = await this.request("POST", "/api/v1/encrypt", {
496
587
  formData
497
588
  });
589
+ return {
590
+ status: response.status,
591
+ file_id: String(response.file_id),
592
+ file_size: response.file_size,
593
+ enc_time_ms: response.enc_time_ms
594
+ };
498
595
  }
499
596
  /**
500
597
  * Decrypt and retrieve a file
@@ -585,6 +682,68 @@ var Wolfronix = class {
585
682
  }
586
683
  return this.request("DELETE", `/api/v1/files/${fileId}`);
587
684
  }
685
+ // ============================================================================
686
+ // E2E Chat Encryption Methods
687
+ // ============================================================================
688
+ /**
689
+ * Get another user's public key (for E2E encryption)
690
+ * @param userId The ID of the recipient
691
+ */
692
+ async getPublicKey(userId) {
693
+ this.ensureAuthenticated();
694
+ const result = await this.request("GET", `/api/v1/keys/${userId}`);
695
+ return result.public_key;
696
+ }
697
+ /**
698
+ * Encrypt a short text message for a recipient (Hybrid Encryption: RSA + AES)
699
+ * Returns a secure JSON string (packet) to send via chat
700
+ *
701
+ * @param text The plain text message
702
+ * @param recipientId The recipient's user ID
703
+ */
704
+ async encryptMessage(text, recipientId) {
705
+ this.ensureAuthenticated();
706
+ const recipientPubKeyPEM = await this.getPublicKey(recipientId);
707
+ const recipientPubKey = await importKeyFromPEM(recipientPubKeyPEM, "public");
708
+ const sessionKey = await generateSessionKey();
709
+ const { encrypted: encryptedMsg, iv } = await encryptData(text, sessionKey);
710
+ const rawSessionKey = await exportSessionKey(sessionKey);
711
+ const encryptedSessionKey = await rsaEncrypt(rawSessionKey, recipientPubKey);
712
+ const packet = {
713
+ key: encryptedSessionKey,
714
+ iv,
715
+ msg: encryptedMsg
716
+ };
717
+ return JSON.stringify(packet);
718
+ }
719
+ /**
720
+ * Decrypt a message packet received from chat
721
+ *
722
+ * @param packetJson The secure JSON string packet
723
+ */
724
+ async decryptMessage(packetJson) {
725
+ this.ensureAuthenticated();
726
+ if (!this.privateKey) {
727
+ throw new Error("Private key not available. Is user logged in?");
728
+ }
729
+ let packet;
730
+ try {
731
+ packet = JSON.parse(packetJson);
732
+ } catch (e) {
733
+ throw new ValidationError("Invalid message packet format");
734
+ }
735
+ if (!packet.key || !packet.iv || !packet.msg) {
736
+ throw new ValidationError("Invalid message packet structure");
737
+ }
738
+ try {
739
+ const rawSessionKey = await rsaDecrypt(packet.key, this.privateKey);
740
+ const sessionKey = await importSessionKey(rawSessionKey);
741
+ const plainText = await decryptData(packet.msg, packet.iv, sessionKey);
742
+ return plainText;
743
+ } catch (error) {
744
+ throw new Error("Decryption failed. You may not be the intended recipient.");
745
+ }
746
+ }
588
747
  // ==========================================================================
589
748
  // Metrics & Status
590
749
  // ==========================================================================
package/dist/index.mjs CHANGED
@@ -1,4 +1,12 @@
1
1
  // src/crypto.ts
2
+ var getCrypto = () => {
3
+ if (typeof globalThis.crypto !== "undefined") {
4
+ return globalThis.crypto;
5
+ }
6
+ throw new Error(
7
+ "Web Crypto API not available. Requires a modern browser or Node.js 16+."
8
+ );
9
+ };
2
10
  var RSA_ALG = {
3
11
  name: "RSA-OAEP",
4
12
  modulusLength: 2048,
@@ -6,9 +14,10 @@ var RSA_ALG = {
6
14
  hash: "SHA-256"
7
15
  };
8
16
  var WRAP_ALG = "AES-GCM";
17
+ var SESSION_ALG = "AES-GCM";
9
18
  var PBKDF2_ITERATIONS = 1e5;
10
19
  async function generateKeyPair() {
11
- return await window.crypto.subtle.generateKey(
20
+ return await getCrypto().subtle.generateKey(
12
21
  RSA_ALG,
13
22
  true,
14
23
  // extractable
@@ -17,7 +26,7 @@ async function generateKeyPair() {
17
26
  }
18
27
  async function exportKeyToPEM(key, type) {
19
28
  const format = type === "public" ? "spki" : "pkcs8";
20
- const exported = await window.crypto.subtle.exportKey(format, key);
29
+ const exported = await getCrypto().subtle.exportKey(format, key);
21
30
  const exportedAsBase64 = arrayBufferToBase64(exported);
22
31
  const pemHeader = type === "public" ? "-----BEGIN PUBLIC KEY-----" : "-----BEGIN PRIVATE KEY-----";
23
32
  const pemFooter = type === "public" ? "-----END PUBLIC KEY-----" : "-----END PRIVATE KEY-----";
@@ -32,7 +41,7 @@ async function importKeyFromPEM(pem, type) {
32
41
  const binaryDer = base64ToArrayBuffer(pemContents);
33
42
  const format = type === "public" ? "spki" : "pkcs8";
34
43
  const usage = type === "public" ? ["encrypt", "wrapKey"] : ["decrypt", "unwrapKey"];
35
- return await window.crypto.subtle.importKey(
44
+ return await getCrypto().subtle.importKey(
36
45
  format,
37
46
  binaryDer,
38
47
  RSA_ALG,
@@ -42,7 +51,7 @@ async function importKeyFromPEM(pem, type) {
42
51
  }
43
52
  async function deriveWrappingKey(password, saltHex) {
44
53
  const enc = new TextEncoder();
45
- const passwordKey = await window.crypto.subtle.importKey(
54
+ const passwordKey = await getCrypto().subtle.importKey(
46
55
  "raw",
47
56
  enc.encode(password),
48
57
  "PBKDF2",
@@ -50,7 +59,7 @@ async function deriveWrappingKey(password, saltHex) {
50
59
  ["deriveKey"]
51
60
  );
52
61
  const salt = hexToArrayBuffer(saltHex);
53
- return await window.crypto.subtle.deriveKey(
62
+ return await getCrypto().subtle.deriveKey(
54
63
  {
55
64
  name: "PBKDF2",
56
65
  salt,
@@ -64,12 +73,13 @@ async function deriveWrappingKey(password, saltHex) {
64
73
  );
65
74
  }
66
75
  async function wrapPrivateKey(privateKey, password) {
67
- const salt = window.crypto.getRandomValues(new Uint8Array(16));
76
+ const crypto = getCrypto();
77
+ const salt = crypto.getRandomValues(new Uint8Array(16));
68
78
  const saltHex = arrayBufferToHex(salt.buffer);
69
79
  const wrappingKey = await deriveWrappingKey(password, saltHex);
70
- const exportedKey = await window.crypto.subtle.exportKey("pkcs8", privateKey);
71
- const iv = window.crypto.getRandomValues(new Uint8Array(12));
72
- const encryptedContent = await window.crypto.subtle.encrypt(
80
+ const exportedKey = await crypto.subtle.exportKey("pkcs8", privateKey);
81
+ const iv = crypto.getRandomValues(new Uint8Array(12));
82
+ const encryptedContent = await crypto.subtle.encrypt(
73
83
  {
74
84
  name: WRAP_ALG,
75
85
  iv
@@ -91,7 +101,7 @@ async function unwrapPrivateKey(encryptedKeyBase64, password, saltHex) {
91
101
  const iv = combinedArray.slice(0, 12);
92
102
  const data = combinedArray.slice(12);
93
103
  const wrappingKey = await deriveWrappingKey(password, saltHex);
94
- const decryptedKeyData = await window.crypto.subtle.decrypt(
104
+ const decryptedKeyData = await getCrypto().subtle.decrypt(
95
105
  {
96
106
  name: WRAP_ALG,
97
107
  iv
@@ -99,7 +109,7 @@ async function unwrapPrivateKey(encryptedKeyBase64, password, saltHex) {
99
109
  wrappingKey,
100
110
  data
101
111
  );
102
- return await window.crypto.subtle.importKey(
112
+ return await getCrypto().subtle.importKey(
103
113
  "pkcs8",
104
114
  decryptedKeyData,
105
115
  RSA_ALG,
@@ -107,17 +117,98 @@ async function unwrapPrivateKey(encryptedKeyBase64, password, saltHex) {
107
117
  ["decrypt", "unwrapKey"]
108
118
  );
109
119
  }
120
+ async function generateSessionKey() {
121
+ return await getCrypto().subtle.generateKey(
122
+ {
123
+ name: SESSION_ALG,
124
+ length: 256
125
+ },
126
+ true,
127
+ ["encrypt", "decrypt"]
128
+ );
129
+ }
130
+ async function encryptData(data, key) {
131
+ const enc = new TextEncoder();
132
+ const encodedData = enc.encode(data);
133
+ const iv = getCrypto().getRandomValues(new Uint8Array(12));
134
+ const encryptedContent = await getCrypto().subtle.encrypt(
135
+ {
136
+ name: SESSION_ALG,
137
+ iv
138
+ },
139
+ key,
140
+ encodedData
141
+ );
142
+ return {
143
+ encrypted: arrayBufferToBase64(encryptedContent),
144
+ iv: arrayBufferToBase64(iv.buffer)
145
+ };
146
+ }
147
+ async function decryptData(encryptedBase64, ivBase64, key) {
148
+ const encryptedData = base64ToArrayBuffer(encryptedBase64);
149
+ const iv = base64ToArrayBuffer(ivBase64);
150
+ const decryptedContent = await getCrypto().subtle.decrypt(
151
+ {
152
+ name: SESSION_ALG,
153
+ iv
154
+ },
155
+ key,
156
+ encryptedData
157
+ );
158
+ const dec = new TextDecoder();
159
+ return dec.decode(decryptedContent);
160
+ }
161
+ async function rsaEncrypt(data, publicKey) {
162
+ const encrypted = await getCrypto().subtle.encrypt(
163
+ {
164
+ name: "RSA-OAEP"
165
+ },
166
+ publicKey,
167
+ data
168
+ );
169
+ return arrayBufferToBase64(encrypted);
170
+ }
171
+ async function rsaDecrypt(encryptedBase64, privateKey) {
172
+ const data = base64ToArrayBuffer(encryptedBase64);
173
+ const decrypted = await getCrypto().subtle.decrypt(
174
+ {
175
+ name: "RSA-OAEP"
176
+ },
177
+ privateKey,
178
+ data
179
+ );
180
+ return decrypted;
181
+ }
182
+ async function exportSessionKey(key) {
183
+ return await getCrypto().subtle.exportKey("raw", key);
184
+ }
185
+ async function importSessionKey(raw) {
186
+ return await getCrypto().subtle.importKey(
187
+ "raw",
188
+ raw,
189
+ SESSION_ALG,
190
+ true,
191
+ ["encrypt", "decrypt"]
192
+ );
193
+ }
110
194
  function arrayBufferToBase64(buffer) {
111
- let binary = "";
112
195
  const bytes = new Uint8Array(buffer);
196
+ if (typeof Buffer !== "undefined") {
197
+ return Buffer.from(bytes).toString("base64");
198
+ }
199
+ let binary = "";
113
200
  const len = bytes.byteLength;
114
201
  for (let i = 0; i < len; i++) {
115
202
  binary += String.fromCharCode(bytes[i]);
116
203
  }
117
- return window.btoa(binary);
204
+ return btoa(binary);
118
205
  }
119
206
  function base64ToArrayBuffer(base64) {
120
- const binary_string = window.atob(base64);
207
+ if (typeof Buffer !== "undefined") {
208
+ const buf = Buffer.from(base64, "base64");
209
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
210
+ }
211
+ const binary_string = atob(base64);
121
212
  const len = binary_string.length;
122
213
  const bytes = new Uint8Array(len);
123
214
  for (let i = 0; i < len; i++) {
@@ -458,9 +549,15 @@ var Wolfronix = class {
458
549
  throw new Error("Public key not available. Is user logged in?");
459
550
  }
460
551
  formData.append("client_public_key", this.publicKeyPEM);
461
- return this.request("POST", "/api/v1/encrypt", {
552
+ const response = await this.request("POST", "/api/v1/encrypt", {
462
553
  formData
463
554
  });
555
+ return {
556
+ status: response.status,
557
+ file_id: String(response.file_id),
558
+ file_size: response.file_size,
559
+ enc_time_ms: response.enc_time_ms
560
+ };
464
561
  }
465
562
  /**
466
563
  * Decrypt and retrieve a file
@@ -551,6 +648,68 @@ var Wolfronix = class {
551
648
  }
552
649
  return this.request("DELETE", `/api/v1/files/${fileId}`);
553
650
  }
651
+ // ============================================================================
652
+ // E2E Chat Encryption Methods
653
+ // ============================================================================
654
+ /**
655
+ * Get another user's public key (for E2E encryption)
656
+ * @param userId The ID of the recipient
657
+ */
658
+ async getPublicKey(userId) {
659
+ this.ensureAuthenticated();
660
+ const result = await this.request("GET", `/api/v1/keys/${userId}`);
661
+ return result.public_key;
662
+ }
663
+ /**
664
+ * Encrypt a short text message for a recipient (Hybrid Encryption: RSA + AES)
665
+ * Returns a secure JSON string (packet) to send via chat
666
+ *
667
+ * @param text The plain text message
668
+ * @param recipientId The recipient's user ID
669
+ */
670
+ async encryptMessage(text, recipientId) {
671
+ this.ensureAuthenticated();
672
+ const recipientPubKeyPEM = await this.getPublicKey(recipientId);
673
+ const recipientPubKey = await importKeyFromPEM(recipientPubKeyPEM, "public");
674
+ const sessionKey = await generateSessionKey();
675
+ const { encrypted: encryptedMsg, iv } = await encryptData(text, sessionKey);
676
+ const rawSessionKey = await exportSessionKey(sessionKey);
677
+ const encryptedSessionKey = await rsaEncrypt(rawSessionKey, recipientPubKey);
678
+ const packet = {
679
+ key: encryptedSessionKey,
680
+ iv,
681
+ msg: encryptedMsg
682
+ };
683
+ return JSON.stringify(packet);
684
+ }
685
+ /**
686
+ * Decrypt a message packet received from chat
687
+ *
688
+ * @param packetJson The secure JSON string packet
689
+ */
690
+ async decryptMessage(packetJson) {
691
+ this.ensureAuthenticated();
692
+ if (!this.privateKey) {
693
+ throw new Error("Private key not available. Is user logged in?");
694
+ }
695
+ let packet;
696
+ try {
697
+ packet = JSON.parse(packetJson);
698
+ } catch (e) {
699
+ throw new ValidationError("Invalid message packet format");
700
+ }
701
+ if (!packet.key || !packet.iv || !packet.msg) {
702
+ throw new ValidationError("Invalid message packet structure");
703
+ }
704
+ try {
705
+ const rawSessionKey = await rsaDecrypt(packet.key, this.privateKey);
706
+ const sessionKey = await importSessionKey(rawSessionKey);
707
+ const plainText = await decryptData(packet.msg, packet.iv, sessionKey);
708
+ return plainText;
709
+ } catch (error) {
710
+ throw new Error("Decryption failed. You may not be the intended recipient.");
711
+ }
712
+ }
554
713
  // ==========================================================================
555
714
  // Metrics & Status
556
715
  // ==========================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolfronix-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Official Wolfronix SDK for JavaScript/TypeScript - Zero-knowledge encryption made simple",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",