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
@@ -0,0 +1,23 @@
1
+ # Client Connection Module
2
+
3
+ ## Overview
4
+
5
+ The connection module handles the core mechanics of maintaining a WebSocket connection to an api-ape server. It manages the full lifecycle of client-server communication including connection state, message sending, request/response correlation, and binary file transfers.
6
+
7
+ This module powers the seamless API experience where `api.users.list()` transparently becomes a WebSocket message, waits for the response, and returns the result as a Promise—all while handling reconnection, queuing, and binary data automatically.
8
+
9
+ **Key capabilities:**
10
+
11
+ - **Connection state management** — Track and expose connection status (offline, connecting, connected, disconnected, walled)
12
+ - **Proxy API generation** — Convert `api.path.method()` calls into WebSocket messages
13
+ - **Request correlation** — Match responses to requests via `queryId`
14
+ - **Network detection** — Detect offline state, captive portals, and connectivity changes
15
+ - **File transfers** — Handle binary uploads and downloads transparently
16
+
17
+ > **Contributing?** See [`files.md`](./files.md) for directory structure and file descriptions.
18
+
19
+ ## See Also
20
+
21
+ - [`../README.md`](../README.md) — Client module overview
22
+ - [`../transports/README.md`](../transports/README.md) — HTTP fallback transport
23
+ - [`../connectSocket.js`](../connectSocket.js) — Main WebSocket client
@@ -0,0 +1,256 @@
1
+ /**
2
+ * @fileoverview Binary file download/fetch utilities for api-ape client
3
+ *
4
+ * This module handles fetching binary data that is referenced in server responses.
5
+ * When the server sends binary data (like images, files, etc.), it doesn't include
6
+ * the raw bytes in the WebSocket message. Instead, it sends a tagged reference
7
+ * that this module resolves by fetching the actual data via HTTP.
8
+ *
9
+ * ## Tag Types
10
+ *
11
+ * - `<!L>` - **Linked Resource**: Binary data from server responses (server → client)
12
+ * - `<!F>` - **Shared File**: Binary data from other clients (client → client via server)
13
+ *
14
+ * ## Data Flow
15
+ *
16
+ * ```
17
+ * Server Response:
18
+ * { "image<!L>": "abc123", "name": "photo.jpg" }
19
+ *
20
+ * After fetchLinkedResources():
21
+ * { "image": ArrayBuffer(...), "name": "photo.jpg" }
22
+ * ```
23
+ *
24
+ * ## Retry Logic
25
+ *
26
+ * Shared files (F-tagged) use exponential backoff retry because the file
27
+ * might not be immediately available when the message arrives (the sender
28
+ * might still be uploading).
29
+ *
30
+ * @module client/connection/fileDownload
31
+ * @see {@link module:client/connection/fileHandling} for upload utilities
32
+ * @see {@link module:client/connection/fileUtils} for shared utility functions
33
+ *
34
+ * @example
35
+ * // Hydrating a server response
36
+ * import { fetchLinkedResources, fetchSharedFiles } from './fileDownload'
37
+ *
38
+ * const serverData = { "avatar<!L>": "hash123", username: "alice" }
39
+ *
40
+ * // Fetch the binary data
41
+ * const hydrated = await fetchLinkedResources(serverData)
42
+ * // Result: { avatar: ArrayBuffer(...), username: "alice" }
43
+ *
44
+ * @example
45
+ * // Handling shared files from other clients
46
+ * const messageData = { "attachment<!F>": "filehash", text: "Check this out!" }
47
+ *
48
+ * // Fetch with retry logic
49
+ * const hydrated = await fetchSharedFiles(messageData)
50
+ * // Result: { attachment: ArrayBuffer(...), text: "Check this out!" }
51
+ */
52
+
53
+ import { getBaseUrl } from "./network";
54
+ import { setValueAtPath, findTaggedProps, cleanTaggedKeys } from "./fileUtils";
55
+
56
+ /**
57
+ * Fetch binary resources linked from server responses
58
+ *
59
+ * This function processes data objects that contain L-tagged binary references.
60
+ * Each L-tagged property is replaced with the actual binary data fetched from
61
+ * the server's data endpoint.
62
+ *
63
+ * ## Processing Steps
64
+ *
65
+ * 1. Scan the data object for properties ending with `<!L>`
66
+ * 2. For each tagged property, extract the hash value
67
+ * 3. Fetch the binary data from `/api/ape/data/{hash}`
68
+ * 4. Replace the hash with the fetched ArrayBuffer
69
+ * 5. Rename the key to remove the `<!L>` suffix
70
+ *
71
+ * ## Error Handling
72
+ *
73
+ * If a fetch fails, the property is set to `null` rather than throwing.
74
+ * This prevents one failed resource from breaking the entire response.
75
+ *
76
+ * @param {Object} data - Data object potentially containing L-tagged binary references
77
+ * @param {string} [clientId] - Optional client ID for authentication header
78
+ * @returns {Promise<Object>} Hydrated data object with binary resources fetched
79
+ *
80
+ * @example
81
+ * // Server sends avatar as a linked resource
82
+ * const serverResponse = {
83
+ * "profilePic<!L>": "abc123def456",
84
+ * "username": "alice",
85
+ * "bio": "Hello world!"
86
+ * }
87
+ *
88
+ * const hydrated = await fetchLinkedResources(serverResponse)
89
+ * // Result:
90
+ * // {
91
+ * // profilePic: ArrayBuffer(12345), // The actual image data
92
+ * // username: "alice",
93
+ * // bio: "Hello world!"
94
+ * // }
95
+ *
96
+ * @example
97
+ * // Multiple binary resources
98
+ * const response = {
99
+ * "thumbnail<!L>": "hash1",
100
+ * "fullImage<!L>": "hash2",
101
+ * "metadata": { width: 1920, height: 1080 }
102
+ * }
103
+ *
104
+ * const hydrated = await fetchLinkedResources(response)
105
+ * // Both thumbnail and fullImage are fetched in parallel
106
+ *
107
+ * @example
108
+ * // Nested binary resources
109
+ * const response = {
110
+ * user: {
111
+ * name: "Bob",
112
+ * "avatar<!L>": "avatarhash"
113
+ * },
114
+ * attachments: [
115
+ * { "file<!L>": "file1hash", name: "doc.pdf" },
116
+ * { "file<!L>": "file2hash", name: "image.png" }
117
+ * ]
118
+ * }
119
+ *
120
+ * const hydrated = await fetchLinkedResources(response)
121
+ * // All nested binary resources are fetched
122
+ */
123
+ export async function fetchLinkedResources(data, clientId) {
124
+ const resources = findTaggedProps(data, "L");
125
+ if (resources.length === 0) return data;
126
+
127
+ console.log(`🦍 Fetching ${resources.length} binary resource(s)`);
128
+ const cleanedData = cleanTaggedKeys(data, "L");
129
+ const baseUrl = getBaseUrl();
130
+
131
+ await Promise.all(
132
+ resources.map(async ({ path, hash }) => {
133
+ try {
134
+ const response = await fetch(`${baseUrl}/api/ape/data/${hash}`, {
135
+ credentials: "include",
136
+ headers: { "X-Ape-Client-Id": clientId || "" },
137
+ });
138
+ if (!response.ok) throw new Error(`Failed: ${response.status}`);
139
+ const arrayBuffer = await response.arrayBuffer();
140
+ setValueAtPath(cleanedData, path, arrayBuffer);
141
+ } catch (err) {
142
+ console.error(`🦍 Failed to fetch binary resource at ${path}:`, err);
143
+ setValueAtPath(cleanedData, path, null);
144
+ }
145
+ }),
146
+ );
147
+
148
+ return cleanedData;
149
+ }
150
+
151
+ /**
152
+ * Fetch shared files from client-to-client transfers
153
+ *
154
+ * This function handles F-tagged file references, which represent files
155
+ * shared between clients via the server. Unlike L-tagged resources, F-tagged
156
+ * files use retry logic because the file might not be immediately available
157
+ * (the sending client might still be uploading).
158
+ *
159
+ * ## Retry Behavior
160
+ *
161
+ * - Uses exponential backoff starting at 100ms
162
+ * - Doubles delay after each retry (100ms → 200ms → 400ms → ...)
163
+ * - Retries up to `maxRetries` times (default: 5)
164
+ * - Only retries on 404 errors (file not yet uploaded)
165
+ *
166
+ * ## Use Case
167
+ *
168
+ * Client-to-client file sharing workflow:
169
+ * 1. Client A sends message with file reference
170
+ * 2. Server broadcasts message to Client B
171
+ * 3. Client A uploads file to server (may take time)
172
+ * 4. Client B receives message, attempts to fetch file
173
+ * 5. If 404, retry until file is available
174
+ *
175
+ * @param {Object} data - Data object potentially containing F-tagged file references
176
+ * @param {number} [maxRetries=5] - Maximum number of retry attempts per file
177
+ * @returns {Promise<Object>} Hydrated data object with shared files fetched
178
+ *
179
+ * @example
180
+ * // Receiving a shared file from another client
181
+ * const message = {
182
+ * "sharedDoc<!F>": "uniquefilehash",
183
+ * "sender": "alice",
184
+ * "text": "Here's the document you requested"
185
+ * }
186
+ *
187
+ * const hydrated = await fetchSharedFiles(message)
188
+ * // Result:
189
+ * // {
190
+ * // sharedDoc: ArrayBuffer(...), // The shared file
191
+ * // sender: "alice",
192
+ * // text: "Here's the document you requested"
193
+ * // }
194
+ *
195
+ * @example
196
+ * // With custom retry count
197
+ * const hydrated = await fetchSharedFiles(data, 10) // Up to 10 retries
198
+ *
199
+ * @example
200
+ * // Multiple shared files
201
+ * const data = {
202
+ * "photo<!F>": "hash1",
203
+ * "video<!F>": "hash2",
204
+ * caption: "My vacation pics!"
205
+ * }
206
+ *
207
+ * // Both files fetched in parallel with independent retry logic
208
+ * const hydrated = await fetchSharedFiles(data)
209
+ */
210
+ export async function fetchSharedFiles(data, maxRetries = 5) {
211
+ const files = findTaggedProps(data, "F");
212
+ if (files.length === 0) return data;
213
+
214
+ console.log(`🦍 Fetching ${files.length} shared file(s)`);
215
+ const cleanedData = cleanTaggedKeys(data, "F");
216
+ const baseUrl = getBaseUrl();
217
+
218
+ await Promise.all(
219
+ files.map(async ({ path, hash }) => {
220
+ let retries = 0;
221
+ let backoff = 100;
222
+
223
+ while (retries < maxRetries) {
224
+ try {
225
+ const response = await fetch(`${baseUrl}/api/ape/data/${hash}`, {
226
+ credentials: "include",
227
+ });
228
+
229
+ if (!response.ok) {
230
+ // Retry on 404 (file not yet uploaded)
231
+ if (response.status === 404 && retries < maxRetries - 1) {
232
+ retries++;
233
+ await new Promise((r) => setTimeout(r, backoff));
234
+ backoff *= 2;
235
+ continue;
236
+ }
237
+ throw new Error(`Failed to fetch shared file: ${response.status}`);
238
+ }
239
+
240
+ setValueAtPath(cleanedData, path, await response.arrayBuffer());
241
+ break;
242
+ } catch (err) {
243
+ if (retries >= maxRetries - 1) {
244
+ console.error(`🦍 Failed to fetch shared file at ${path}:`, err);
245
+ setValueAtPath(cleanedData, path, null);
246
+ }
247
+ retries++;
248
+ await new Promise((r) => setTimeout(r, backoff));
249
+ backoff *= 2;
250
+ }
251
+ }
252
+ }),
253
+ );
254
+
255
+ return cleanedData;
256
+ }