api-ape 3.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +58 -570
  2. package/client/README.md +73 -14
  3. package/client/auth/crypto/aead.js +214 -0
  4. package/client/auth/crypto/constants.js +32 -0
  5. package/client/auth/crypto/encoding.js +104 -0
  6. package/client/auth/crypto/files.md +27 -0
  7. package/client/auth/crypto/kdf.js +217 -0
  8. package/client/auth/crypto-utils.js +118 -0
  9. package/client/auth/files.md +52 -0
  10. package/client/auth/key-recovery.js +288 -0
  11. package/client/auth/recovery/constants.js +37 -0
  12. package/client/auth/recovery/files.md +23 -0
  13. package/client/auth/recovery/key-derivation.js +61 -0
  14. package/client/auth/recovery/sss-browser.js +189 -0
  15. package/client/auth/share-storage.js +205 -0
  16. package/client/auth/storage/constants.js +18 -0
  17. package/client/auth/storage/db.js +132 -0
  18. package/client/auth/storage/files.md +27 -0
  19. package/client/auth/storage/keys.js +173 -0
  20. package/client/auth/storage/shares.js +200 -0
  21. package/client/browser.js +190 -23
  22. package/client/connectSocket.js +418 -988
  23. package/client/connection/README.md +23 -0
  24. package/client/connection/fileDownload.js +256 -0
  25. package/client/connection/fileHandling.js +450 -0
  26. package/client/connection/fileUtils.js +346 -0
  27. package/client/connection/files.md +71 -0
  28. package/client/connection/messageHandler.js +105 -0
  29. package/client/connection/network.js +350 -0
  30. package/client/connection/proxy.js +233 -0
  31. package/client/connection/sender.js +333 -0
  32. package/client/connection/state.js +321 -0
  33. package/client/connection/subscriptions.js +151 -0
  34. package/client/files.md +53 -0
  35. package/client/index.js +298 -142
  36. package/client/transports/README.md +50 -0
  37. package/client/transports/files.md +41 -0
  38. package/client/transports/streamParser.js +195 -0
  39. package/client/transports/streaming.js +555 -202
  40. package/dist/ape.js +6 -1
  41. package/dist/ape.js.map +4 -4
  42. package/index.d.ts +38 -16
  43. package/package.json +32 -7
  44. package/server/README.md +287 -53
  45. package/server/adapters/README.md +28 -19
  46. package/server/adapters/files.md +68 -0
  47. package/server/adapters/firebase.js +543 -160
  48. package/server/adapters/index.js +362 -112
  49. package/server/adapters/mongo.js +530 -140
  50. package/server/adapters/postgres.js +534 -155
  51. package/server/adapters/redis.js +508 -143
  52. package/server/adapters/supabase.js +555 -186
  53. package/server/client/README.md +43 -0
  54. package/server/client/connection.js +586 -0
  55. package/server/client/files.md +40 -0
  56. package/server/client/index.js +342 -0
  57. package/server/files.md +54 -0
  58. package/server/index.js +332 -27
  59. package/server/lib/README.md +26 -0
  60. package/server/lib/broadcast/clients.js +219 -0
  61. package/server/lib/broadcast/files.md +58 -0
  62. package/server/lib/broadcast/index.js +57 -0
  63. package/server/lib/broadcast/publishProxy.js +110 -0
  64. package/server/lib/broadcast/pubsub.js +137 -0
  65. package/server/lib/broadcast/sendProxy.js +103 -0
  66. package/server/lib/bun.js +315 -99
  67. package/server/lib/fileTransfer/README.md +63 -0
  68. package/server/lib/fileTransfer/files.md +30 -0
  69. package/server/lib/fileTransfer/streaming.js +435 -0
  70. package/server/lib/fileTransfer.js +710 -326
  71. package/server/lib/files.md +111 -0
  72. package/server/lib/httpUtils.js +283 -0
  73. package/server/lib/loader.js +208 -7
  74. package/server/lib/longPolling/README.md +63 -0
  75. package/server/lib/longPolling/files.md +44 -0
  76. package/server/lib/longPolling/getHandler.js +365 -0
  77. package/server/lib/longPolling/postHandler.js +327 -0
  78. package/server/lib/longPolling.js +174 -221
  79. package/server/lib/main.js +369 -532
  80. package/server/lib/runtimes/README.md +42 -0
  81. package/server/lib/runtimes/bun.js +586 -0
  82. package/server/lib/runtimes/files.md +56 -0
  83. package/server/lib/runtimes/node.js +511 -0
  84. package/server/lib/wiring.js +539 -98
  85. package/server/lib/ws/README.md +35 -0
  86. package/server/lib/ws/adapters/README.md +54 -0
  87. package/server/lib/ws/adapters/bun.js +538 -170
  88. package/server/lib/ws/adapters/deno.js +623 -149
  89. package/server/lib/ws/adapters/files.md +42 -0
  90. package/server/lib/ws/files.md +74 -0
  91. package/server/lib/ws/frames.js +532 -154
  92. package/server/lib/ws/index.js +207 -10
  93. package/server/lib/ws/server.js +385 -92
  94. package/server/lib/ws/socket.js +549 -181
  95. package/server/lib/wsProvider.js +363 -89
  96. package/server/plugins/binary.js +282 -0
  97. package/server/security/README.md +92 -0
  98. package/server/security/auth/README.md +319 -0
  99. package/server/security/auth/adapters/files.md +95 -0
  100. package/server/security/auth/adapters/ldap/constants.js +37 -0
  101. package/server/security/auth/adapters/ldap/files.md +19 -0
  102. package/server/security/auth/adapters/ldap/helpers.js +111 -0
  103. package/server/security/auth/adapters/ldap.js +353 -0
  104. package/server/security/auth/adapters/oauth2/constants.js +41 -0
  105. package/server/security/auth/adapters/oauth2/files.md +19 -0
  106. package/server/security/auth/adapters/oauth2/helpers.js +123 -0
  107. package/server/security/auth/adapters/oauth2.js +273 -0
  108. package/server/security/auth/adapters/opaque-handlers.js +314 -0
  109. package/server/security/auth/adapters/opaque.js +205 -0
  110. package/server/security/auth/adapters/saml/constants.js +52 -0
  111. package/server/security/auth/adapters/saml/files.md +19 -0
  112. package/server/security/auth/adapters/saml/helpers.js +74 -0
  113. package/server/security/auth/adapters/saml.js +173 -0
  114. package/server/security/auth/adapters/totp.js +703 -0
  115. package/server/security/auth/adapters/webauthn.js +625 -0
  116. package/server/security/auth/files.md +61 -0
  117. package/server/security/auth/framework/constants.js +27 -0
  118. package/server/security/auth/framework/files.md +23 -0
  119. package/server/security/auth/framework/handlers.js +272 -0
  120. package/server/security/auth/framework/socket-auth.js +177 -0
  121. package/server/security/auth/handlers/auth-messages.js +143 -0
  122. package/server/security/auth/handlers/files.md +28 -0
  123. package/server/security/auth/index.js +290 -0
  124. package/server/security/auth/mfa/crypto/aead.js +148 -0
  125. package/server/security/auth/mfa/crypto/constants.js +35 -0
  126. package/server/security/auth/mfa/crypto/files.md +27 -0
  127. package/server/security/auth/mfa/crypto/kdf.js +120 -0
  128. package/server/security/auth/mfa/crypto/utils.js +68 -0
  129. package/server/security/auth/mfa/crypto-utils.js +80 -0
  130. package/server/security/auth/mfa/files.md +77 -0
  131. package/server/security/auth/mfa/ledger/constants.js +75 -0
  132. package/server/security/auth/mfa/ledger/errors.js +73 -0
  133. package/server/security/auth/mfa/ledger/files.md +23 -0
  134. package/server/security/auth/mfa/ledger/share-record.js +32 -0
  135. package/server/security/auth/mfa/ledger.js +255 -0
  136. package/server/security/auth/mfa/recovery/constants.js +67 -0
  137. package/server/security/auth/mfa/recovery/files.md +19 -0
  138. package/server/security/auth/mfa/recovery/handlers.js +216 -0
  139. package/server/security/auth/mfa/recovery.js +191 -0
  140. package/server/security/auth/mfa/sss/constants.js +21 -0
  141. package/server/security/auth/mfa/sss/files.md +23 -0
  142. package/server/security/auth/mfa/sss/gf256.js +103 -0
  143. package/server/security/auth/mfa/sss/serialization.js +82 -0
  144. package/server/security/auth/mfa/sss.js +161 -0
  145. package/server/security/auth/mfa/two-of-three/constants.js +58 -0
  146. package/server/security/auth/mfa/two-of-three/files.md +23 -0
  147. package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
  148. package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
  149. package/server/security/auth/mfa/two-of-three.js +136 -0
  150. package/server/security/auth/nonce-manager.js +89 -0
  151. package/server/security/auth/state-machine-mfa.js +269 -0
  152. package/server/security/auth/state-machine.js +257 -0
  153. package/server/security/extractRootDomain.js +144 -16
  154. package/server/security/files.md +51 -0
  155. package/server/security/origin.js +197 -15
  156. package/server/security/reply.js +274 -16
  157. package/server/socket/README.md +119 -0
  158. package/server/socket/authMiddleware.js +299 -0
  159. package/server/socket/files.md +86 -0
  160. package/server/socket/open.js +154 -8
  161. package/server/socket/pluginHooks.js +334 -0
  162. package/server/socket/receive.js +184 -225
  163. package/server/socket/receiveContext.js +117 -0
  164. package/server/socket/send.js +416 -78
  165. package/server/socket/tagUtils.js +402 -0
  166. package/server/utils/README.md +19 -0
  167. package/server/utils/deepRequire.js +255 -30
  168. package/server/utils/files.md +57 -0
  169. package/server/utils/genId.js +182 -20
  170. package/server/utils/parseUserAgent.js +313 -251
  171. package/server/utils/userAgent/README.md +65 -0
  172. package/server/utils/userAgent/files.md +46 -0
  173. package/server/utils/userAgent/patterns.js +545 -0
  174. package/utils/README.md +21 -0
  175. package/utils/files.md +66 -0
  176. package/utils/jss/README.md +21 -0
  177. package/utils/jss/decode.js +471 -0
  178. package/utils/jss/encode.js +312 -0
  179. package/utils/jss/files.md +68 -0
  180. package/utils/jss/plugins.js +210 -0
  181. package/utils/jss.js +219 -273
  182. package/utils/messageHash.js +238 -35
  183. package/dist/api-ape.min.js +0 -2
  184. package/dist/api-ape.min.js.map +0 -7
  185. package/server/client.js +0 -308
  186. package/server/lib/broadcast.js +0 -146
package/client/README.md CHANGED
@@ -1,14 +1,25 @@
1
1
  # 🦍 api-ape Client
2
2
 
3
- WebSocket client library with auto-reconnection and proxy-based API calls.
3
+ ![api-ape mascot](../assets/friend.jpg)
4
4
 
5
- ## Files
5
+ ## Overview
6
6
 
7
- | File | Description |
8
- |------|-------------|
9
- | `index.js` | **Unified entry** — auto-initializing client with call buffering |
10
- | `browser.js` | Browser entry point — exposes `window.ape` |
11
- | `connectSocket.js` | WebSocket client with auto-reconnect, queuing, and JJS encoding |
7
+ The client module provides the browser-side WebSocket client for api-ape. It enables seamless communication with api-ape servers through a proxy-based API that converts method calls like `api.users.list()` into WebSocket messages.
8
+
9
+ **Key capabilities:**
10
+
11
+ - **Proxy-based API** Call server endpoints like local methods (`api.path.method(data)`)
12
+ - **Auto-reconnection** — Automatically reconnects with exponential backoff on disconnection
13
+ - **Call buffering** — Queues calls made before connection is established
14
+ - **JSS encoding** — Supports Date, RegExp, Error, Set, Map, and undefined over the wire
15
+ - **HTTP fallback** — Falls back to long-polling when WebSocket is blocked
16
+ - **Binary transfers** — Transparent file upload/download handling
17
+ - **Connection state** — Track connection status (offline, connecting, connected, disconnected)
18
+ - **Pub/sub subscriptions** — Subscribe to channels and receive targeted updates
19
+
20
+ The client works in both browser environments (via `<script>` tag) and bundled applications (React, Vue, etc.).
21
+
22
+ > **Contributing?** See [`files.md`](./files.md) for directory structure and file descriptions.
12
23
 
13
24
  ## Usage
14
25
 
@@ -17,11 +28,14 @@ WebSocket client library with auto-reconnection and proxy-based API calls.
17
28
  ```html
18
29
  <script src="/api/ape.js"></script>
19
30
  <script>
20
- // Call server functions
31
+ // Call server functions (RPC)
21
32
  api.hello('World').then(result => console.log(result))
22
-
23
- // Listen for broadcasts
24
- api.on('message', ({ data }) => console.log(data))
33
+
34
+ // Subscribe to channels (pass a callback function)
35
+ const unsub = api.message(data => console.log(data))
36
+
37
+ // Unsubscribe when done
38
+ unsub()
25
39
  </script>
26
40
  ```
27
41
 
@@ -37,8 +51,13 @@ import api from 'api-ape'
37
51
  // Just use it! Calls are buffered until connected.
38
52
  api.users.list().then(users => console.log(users))
39
53
 
40
- // Listen for broadcasts
41
- api.on('message', ({ data }) => console.log(data))
54
+ // Subscribe to channels (pass a callback function)
55
+ const unsub = api.news.banking(data => {
56
+ console.log('Received:', data)
57
+ })
58
+
59
+ // Unsubscribe when done
60
+ unsub()
42
61
 
43
62
  // Track connection state
44
63
  api.onConnectionChange((state) => {
@@ -53,6 +72,7 @@ api.onConnectionChange((state) => {
53
72
  - `disconnected` — Had connection, lost it
54
73
  - `connecting` — Actively connecting
55
74
  - `connected` — Ready to use
75
+ - `closing` — Connection is closing
56
76
 
57
77
  **No async setup needed!** The client auto-initializes and buffers calls until connected.
58
78
 
@@ -61,7 +81,7 @@ api.onConnectionChange((state) => {
61
81
  - **Proxy-based API** — `ape.path.method(data)` converts to WebSocket calls
62
82
  - **Auto-reconnect** — Reconnects on disconnect with queued messages
63
83
  - **Promise-based** — All calls return promises with matched responses via queryId
64
- - **JJS encoding** — Supports Date, RegExp, Error, Set, Map, undefined over the wire
84
+ - **JSS encoding** — Supports Date, RegExp, Error, Set, Map, undefined over the wire
65
85
  - **Request timeout** — Configurable timeout (default: 10s)
66
86
 
67
87
  ## File Transfers
@@ -95,6 +115,45 @@ await api.files.upload({
95
115
 
96
116
  Binary transfers use `/api/ape/data/:hash` endpoints with session verification.
97
117
 
118
+ ## Pub/Sub Subscriptions
119
+
120
+ Subscribe to channels using the same chaining syntax as RPC calls. The key difference: **pass a callback function to subscribe, pass data to make an RPC call**.
121
+
122
+ ```js
123
+ // RPC call - passing data
124
+ await api.news.banking({ category: 'stocks' }) // Returns Promise<response>
125
+
126
+ // Subscribe - passing a callback function
127
+ const unsub = api.news.banking(data => {
128
+ console.log('Received:', data)
129
+ })
130
+
131
+ // Unsubscribe when done
132
+ unsub()
133
+ ```
134
+
135
+ ### Chained Subscriptions
136
+
137
+ ```js
138
+ // Subscribe to nested channels
139
+ const unsub1 = api.stock.AAPL(data => {
140
+ console.log('AAPL:', data.price)
141
+ })
142
+
143
+ const unsub2 = api.health(data => {
144
+ console.log('Health update:', data)
145
+ })
146
+
147
+ // Clean up
148
+ unsub1()
149
+ unsub2()
150
+ ```
151
+
152
+ **Behavior:**
153
+ - On subscribe, you receive the last published message immediately (if any)
154
+ - Subscriptions are automatically restored on reconnect
155
+ - Subscriptions are automatically cleaned up on disconnect
156
+
98
157
  ## Security
99
158
 
100
159
  ### CSRF Protection
@@ -0,0 +1,214 @@
1
+ /**
2
+ * @file AES-256-GCM AEAD encryption for browser
3
+ */
4
+ 'use strict';
5
+
6
+ const {
7
+ CryptoError,
8
+ AES_KEY_LENGTH,
9
+ GCM_NONCE_LENGTH,
10
+ GCM_TAG_LENGTH,
11
+ } = require('./constants');
12
+ const {
13
+ isCryptoAvailable,
14
+ bufferToUint8Array,
15
+ stringToUint8Array,
16
+ randomBytes,
17
+ } = require('./encoding');
18
+
19
+ /**
20
+ * Import raw key for AES-GCM
21
+ * @param {Uint8Array} keyData - Key bytes
22
+ * @returns {Promise<CryptoKey>}
23
+ */
24
+ async function importKey(keyData) {
25
+ return crypto.subtle.importKey('raw', keyData, { name: 'AES-GCM' }, false, [
26
+ 'encrypt',
27
+ 'decrypt',
28
+ ]);
29
+ }
30
+
31
+ /**
32
+ * Encrypt data using AES-256-GCM with AEAD
33
+ * @param {Uint8Array} key - 32-byte encryption key
34
+ * @param {Uint8Array|string} plaintext - Data to encrypt
35
+ * @param {Uint8Array|string} [aad] - Additional authenticated data
36
+ * @returns {Promise<Object>} Encrypted data components
37
+ */
38
+ async function aeadEncrypt(key, plaintext, aad = '') {
39
+ if (!isCryptoAvailable()) {
40
+ const err = new Error('Web Crypto API not available');
41
+ err.code = CryptoError.CRYPTO_NOT_AVAILABLE;
42
+ throw err;
43
+ }
44
+
45
+ if (!(key instanceof Uint8Array) || key.length !== AES_KEY_LENGTH) {
46
+ const err = new Error(`Key must be a ${AES_KEY_LENGTH}-byte Uint8Array`);
47
+ err.code = CryptoError.INVALID_KEY_LENGTH;
48
+ throw err;
49
+ }
50
+
51
+ const plaintextArray =
52
+ typeof plaintext === 'string' ? stringToUint8Array(plaintext) : plaintext;
53
+ const aadArray =
54
+ typeof aad === 'string' ? stringToUint8Array(aad) : aad || new Uint8Array(0);
55
+
56
+ const nonce = randomBytes(GCM_NONCE_LENGTH);
57
+ const cryptoKey = await importKey(key);
58
+
59
+ const encryptedBuffer = await crypto.subtle.encrypt(
60
+ {
61
+ name: 'AES-GCM',
62
+ iv: nonce,
63
+ additionalData: aadArray,
64
+ tagLength: GCM_TAG_LENGTH * 8,
65
+ },
66
+ cryptoKey,
67
+ plaintextArray
68
+ );
69
+
70
+ const encrypted = bufferToUint8Array(encryptedBuffer);
71
+ const ciphertext = encrypted.slice(0, encrypted.length - GCM_TAG_LENGTH);
72
+ const tag = encrypted.slice(encrypted.length - GCM_TAG_LENGTH);
73
+
74
+ return { ciphertext, nonce, tag };
75
+ }
76
+
77
+ /**
78
+ * Decrypt data using AES-256-GCM with AEAD
79
+ * @param {Uint8Array} key - 32-byte decryption key
80
+ * @param {Uint8Array} ciphertext - Data to decrypt
81
+ * @param {Uint8Array} nonce - 12-byte nonce
82
+ * @param {Uint8Array} tag - 16-byte auth tag
83
+ * @param {Uint8Array|string} [aad] - Additional authenticated data
84
+ * @returns {Promise<Uint8Array>} Decrypted plaintext
85
+ */
86
+ async function aeadDecrypt(key, ciphertext, nonce, tag, aad = '') {
87
+ if (!isCryptoAvailable()) {
88
+ const err = new Error('Web Crypto API not available');
89
+ err.code = CryptoError.CRYPTO_NOT_AVAILABLE;
90
+ throw err;
91
+ }
92
+
93
+ if (!(key instanceof Uint8Array) || key.length !== AES_KEY_LENGTH) {
94
+ const err = new Error(`Key must be a ${AES_KEY_LENGTH}-byte Uint8Array`);
95
+ err.code = CryptoError.INVALID_KEY_LENGTH;
96
+ throw err;
97
+ }
98
+
99
+ if (!(nonce instanceof Uint8Array) || nonce.length !== GCM_NONCE_LENGTH) {
100
+ const err = new Error(`Nonce must be a ${GCM_NONCE_LENGTH}-byte Uint8Array`);
101
+ err.code = CryptoError.INVALID_NONCE;
102
+ throw err;
103
+ }
104
+
105
+ if (!(tag instanceof Uint8Array) || tag.length !== GCM_TAG_LENGTH) {
106
+ const err = new Error(`Tag must be a ${GCM_TAG_LENGTH}-byte Uint8Array`);
107
+ err.code = CryptoError.INVALID_TAG;
108
+ throw err;
109
+ }
110
+
111
+ const aadArray =
112
+ typeof aad === 'string' ? stringToUint8Array(aad) : aad || new Uint8Array(0);
113
+
114
+ const combined = new Uint8Array(ciphertext.length + tag.length);
115
+ combined.set(ciphertext);
116
+ combined.set(tag, ciphertext.length);
117
+
118
+ const cryptoKey = await importKey(key);
119
+
120
+ try {
121
+ const decryptedBuffer = await crypto.subtle.decrypt(
122
+ {
123
+ name: 'AES-GCM',
124
+ iv: nonce,
125
+ additionalData: aadArray,
126
+ tagLength: GCM_TAG_LENGTH * 8,
127
+ },
128
+ cryptoKey,
129
+ combined
130
+ );
131
+
132
+ return bufferToUint8Array(decryptedBuffer);
133
+ } catch {
134
+ const err = new Error('Decryption failed: authentication tag mismatch');
135
+ err.code = CryptoError.DECRYPTION_FAILED;
136
+ throw err;
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Pack encrypted data into single Uint8Array for storage
142
+ * Format: [nonce:12][tag:16][ciphertext:N]
143
+ * @param {Object} encrypted - Encrypted data components
144
+ * @param {Uint8Array} encrypted.ciphertext - The encrypted data
145
+ * @param {Uint8Array} encrypted.nonce - The nonce
146
+ * @param {Uint8Array} encrypted.tag - The auth tag
147
+ * @returns {Uint8Array}
148
+ */
149
+ function packEncrypted(encrypted) {
150
+ const { ciphertext, nonce, tag } = encrypted;
151
+ const packed = new Uint8Array(nonce.length + tag.length + ciphertext.length);
152
+ packed.set(nonce);
153
+ packed.set(tag, nonce.length);
154
+ packed.set(ciphertext, nonce.length + tag.length);
155
+ return packed;
156
+ }
157
+
158
+ /**
159
+ * Unpack encrypted data from storage format
160
+ * @param {Uint8Array} packed - Packed encrypted data
161
+ * @returns {Object} Unpacked components
162
+ */
163
+ function unpackEncrypted(packed) {
164
+ if (!(packed instanceof Uint8Array)) {
165
+ const err = new Error('Packed data must be a Uint8Array');
166
+ err.code = CryptoError.INVALID_CIPHERTEXT;
167
+ throw err;
168
+ }
169
+
170
+ const minLength = GCM_NONCE_LENGTH + GCM_TAG_LENGTH;
171
+ if (packed.length < minLength) {
172
+ const err = new Error(`Packed data too short: minimum ${minLength} bytes`);
173
+ err.code = CryptoError.INVALID_CIPHERTEXT;
174
+ throw err;
175
+ }
176
+
177
+ const nonce = packed.slice(0, GCM_NONCE_LENGTH);
178
+ const tag = packed.slice(GCM_NONCE_LENGTH, GCM_NONCE_LENGTH + GCM_TAG_LENGTH);
179
+ const ciphertext = packed.slice(GCM_NONCE_LENGTH + GCM_TAG_LENGTH);
180
+
181
+ return { ciphertext, nonce, tag };
182
+ }
183
+
184
+ /**
185
+ * Encrypt and pack in one step
186
+ * @param {Uint8Array} key - Encryption key
187
+ * @param {Uint8Array|string} plaintext - Data to encrypt
188
+ * @param {Uint8Array|string} [aad] - Additional authenticated data
189
+ * @returns {Promise<Uint8Array>}
190
+ */
191
+ async function encryptAndPack(key, plaintext, aad = '') {
192
+ return packEncrypted(await aeadEncrypt(key, plaintext, aad));
193
+ }
194
+
195
+ /**
196
+ * Unpack and decrypt in one step
197
+ * @param {Uint8Array} key - Decryption key
198
+ * @param {Uint8Array} packed - Packed encrypted data
199
+ * @param {Uint8Array|string} [aad] - Additional authenticated data
200
+ * @returns {Promise<Uint8Array>}
201
+ */
202
+ async function unpackAndDecrypt(key, packed, aad = '') {
203
+ const { ciphertext, nonce, tag } = unpackEncrypted(packed);
204
+ return aeadDecrypt(key, ciphertext, nonce, tag, aad);
205
+ }
206
+
207
+ module.exports = {
208
+ aeadEncrypt,
209
+ aeadDecrypt,
210
+ packEncrypted,
211
+ unpackEncrypted,
212
+ encryptAndPack,
213
+ unpackAndDecrypt,
214
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @file Cryptographic constants and error codes
3
+ */
4
+ 'use strict';
5
+
6
+ /**
7
+ * Error codes for crypto operations
8
+ * @enum {string}
9
+ */
10
+ const CryptoError = {
11
+ DECRYPTION_FAILED: 'DECRYPTION_FAILED',
12
+ INVALID_KEY_LENGTH: 'INVALID_KEY_LENGTH',
13
+ INVALID_CIPHERTEXT: 'INVALID_CIPHERTEXT',
14
+ INVALID_NONCE: 'INVALID_NONCE',
15
+ INVALID_TAG: 'INVALID_TAG',
16
+ ARGON2_NOT_AVAILABLE: 'ARGON2_NOT_AVAILABLE',
17
+ KDF_FAILED: 'KDF_FAILED',
18
+ CRYPTO_NOT_AVAILABLE: 'CRYPTO_NOT_AVAILABLE',
19
+ };
20
+
21
+ const AES_KEY_LENGTH = 32; // 256 bits for AES-256-GCM
22
+ const GCM_NONCE_LENGTH = 12; // 96 bits for GCM
23
+ const GCM_TAG_LENGTH = 16; // 128 bits auth tag
24
+ const PBKDF2_ITERATIONS = 600000; // OWASP recommended minimum
25
+
26
+ module.exports = {
27
+ CryptoError,
28
+ AES_KEY_LENGTH,
29
+ GCM_NONCE_LENGTH,
30
+ GCM_TAG_LENGTH,
31
+ PBKDF2_ITERATIONS,
32
+ };
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @file Data encoding utilities for browser crypto
3
+ */
4
+ 'use strict';
5
+
6
+ /**
7
+ * Check if Web Crypto API is available
8
+ * @returns {boolean}
9
+ */
10
+ function isCryptoAvailable() {
11
+ return typeof crypto !== 'undefined' && crypto.subtle;
12
+ }
13
+
14
+ /**
15
+ * Convert ArrayBuffer to Uint8Array
16
+ * @param {ArrayBuffer} buffer - Buffer to convert
17
+ * @returns {Uint8Array}
18
+ */
19
+ function bufferToUint8Array(buffer) {
20
+ return new Uint8Array(buffer);
21
+ }
22
+
23
+ /**
24
+ * Convert string to Uint8Array
25
+ * @param {string} str - String to convert
26
+ * @returns {Uint8Array}
27
+ */
28
+ function stringToUint8Array(str) {
29
+ return new TextEncoder().encode(str);
30
+ }
31
+
32
+ /**
33
+ * Convert Uint8Array to string
34
+ * @param {Uint8Array} array - Array to convert
35
+ * @returns {string}
36
+ */
37
+ function uint8ArrayToString(array) {
38
+ return new TextDecoder().decode(array);
39
+ }
40
+
41
+ /**
42
+ * Convert Uint8Array to base64
43
+ * @param {Uint8Array} array - Array to convert
44
+ * @returns {string}
45
+ */
46
+ function uint8ArrayToBase64(array) {
47
+ let binary = '';
48
+ for (let i = 0; i < array.length; i++) {
49
+ binary += String.fromCharCode(array[i]);
50
+ }
51
+ return btoa(binary);
52
+ }
53
+
54
+ /**
55
+ * Convert base64 to Uint8Array
56
+ * @param {string} base64 - Base64 string to convert
57
+ * @returns {Uint8Array}
58
+ */
59
+ function base64ToUint8Array(base64) {
60
+ const binary = atob(base64);
61
+ const array = new Uint8Array(binary.length);
62
+ for (let i = 0; i < binary.length; i++) {
63
+ array[i] = binary.charCodeAt(i);
64
+ }
65
+ return array;
66
+ }
67
+
68
+ /**
69
+ * Generate random bytes
70
+ * @param {number} length - Number of bytes
71
+ * @returns {Uint8Array}
72
+ */
73
+ function randomBytes(length) {
74
+ const array = new Uint8Array(length);
75
+ crypto.getRandomValues(array);
76
+ return array;
77
+ }
78
+
79
+ /**
80
+ * Timing-safe comparison (best effort in browser)
81
+ * @param {Uint8Array} a - First array
82
+ * @param {Uint8Array} b - Second array
83
+ * @returns {boolean}
84
+ */
85
+ function timingSafeEqual(a, b) {
86
+ if (a.length !== b.length) return false;
87
+
88
+ let result = 0;
89
+ for (let i = 0; i < a.length; i++) {
90
+ result |= a[i] ^ b[i];
91
+ }
92
+ return result === 0;
93
+ }
94
+
95
+ module.exports = {
96
+ isCryptoAvailable,
97
+ bufferToUint8Array,
98
+ stringToUint8Array,
99
+ uint8ArrayToString,
100
+ uint8ArrayToBase64,
101
+ base64ToUint8Array,
102
+ randomBytes,
103
+ timingSafeEqual,
104
+ };
@@ -0,0 +1,27 @@
1
+ # Client Auth Crypto Module
2
+
3
+ Browser-side cryptographic utilities using Web Crypto API.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ crypto/
9
+ ├── aead.js - AES-GCM encryption/decryption
10
+ ├── constants.js - Crypto constants and error codes
11
+ ├── encoding.js - Base64/hex encoding utilities
12
+ └── kdf.js - Key derivation functions (PBKDF2)
13
+ ```
14
+
15
+ ## Files
16
+
17
+ ### `constants.js`
18
+ Defines AES key lengths, nonce lengths, and error codes for crypto operations.
19
+
20
+ ### `encoding.js`
21
+ Provides base64url and hex encoding/decoding utilities for browser environments.
22
+
23
+ ### `aead.js`
24
+ AES-256-GCM authenticated encryption using Web Crypto API.
25
+
26
+ ### `kdf.js`
27
+ PBKDF2 key derivation for encrypting shares client-side.