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.
- package/README.md +58 -570
- package/client/README.md +73 -14
- package/client/auth/crypto/aead.js +214 -0
- package/client/auth/crypto/constants.js +32 -0
- package/client/auth/crypto/encoding.js +104 -0
- package/client/auth/crypto/files.md +27 -0
- package/client/auth/crypto/kdf.js +217 -0
- package/client/auth/crypto-utils.js +118 -0
- package/client/auth/files.md +52 -0
- package/client/auth/key-recovery.js +288 -0
- package/client/auth/recovery/constants.js +37 -0
- package/client/auth/recovery/files.md +23 -0
- package/client/auth/recovery/key-derivation.js +61 -0
- package/client/auth/recovery/sss-browser.js +189 -0
- package/client/auth/share-storage.js +205 -0
- package/client/auth/storage/constants.js +18 -0
- package/client/auth/storage/db.js +132 -0
- package/client/auth/storage/files.md +27 -0
- package/client/auth/storage/keys.js +173 -0
- package/client/auth/storage/shares.js +200 -0
- package/client/browser.js +190 -23
- package/client/connectSocket.js +418 -988
- package/client/connection/README.md +23 -0
- package/client/connection/fileDownload.js +256 -0
- package/client/connection/fileHandling.js +450 -0
- package/client/connection/fileUtils.js +346 -0
- package/client/connection/files.md +71 -0
- package/client/connection/messageHandler.js +105 -0
- package/client/connection/network.js +350 -0
- package/client/connection/proxy.js +233 -0
- package/client/connection/sender.js +333 -0
- package/client/connection/state.js +321 -0
- package/client/connection/subscriptions.js +151 -0
- package/client/files.md +53 -0
- package/client/index.js +298 -142
- package/client/transports/README.md +50 -0
- package/client/transports/files.md +41 -0
- package/client/transports/streamParser.js +195 -0
- package/client/transports/streaming.js +555 -202
- package/dist/ape.js +6 -1
- package/dist/ape.js.map +4 -4
- package/index.d.ts +38 -16
- package/package.json +32 -7
- package/server/README.md +287 -53
- package/server/adapters/README.md +28 -19
- package/server/adapters/files.md +68 -0
- package/server/adapters/firebase.js +543 -160
- package/server/adapters/index.js +362 -112
- package/server/adapters/mongo.js +530 -140
- package/server/adapters/postgres.js +534 -155
- package/server/adapters/redis.js +508 -143
- package/server/adapters/supabase.js +555 -186
- package/server/client/README.md +43 -0
- package/server/client/connection.js +586 -0
- package/server/client/files.md +40 -0
- package/server/client/index.js +342 -0
- package/server/files.md +54 -0
- package/server/index.js +332 -27
- package/server/lib/README.md +26 -0
- package/server/lib/broadcast/clients.js +219 -0
- package/server/lib/broadcast/files.md +58 -0
- package/server/lib/broadcast/index.js +57 -0
- package/server/lib/broadcast/publishProxy.js +110 -0
- package/server/lib/broadcast/pubsub.js +137 -0
- package/server/lib/broadcast/sendProxy.js +103 -0
- package/server/lib/bun.js +315 -99
- package/server/lib/fileTransfer/README.md +63 -0
- package/server/lib/fileTransfer/files.md +30 -0
- package/server/lib/fileTransfer/streaming.js +435 -0
- package/server/lib/fileTransfer.js +710 -326
- package/server/lib/files.md +111 -0
- package/server/lib/httpUtils.js +283 -0
- package/server/lib/loader.js +208 -7
- package/server/lib/longPolling/README.md +63 -0
- package/server/lib/longPolling/files.md +44 -0
- package/server/lib/longPolling/getHandler.js +365 -0
- package/server/lib/longPolling/postHandler.js +327 -0
- package/server/lib/longPolling.js +174 -221
- package/server/lib/main.js +369 -532
- package/server/lib/runtimes/README.md +42 -0
- package/server/lib/runtimes/bun.js +586 -0
- package/server/lib/runtimes/files.md +56 -0
- package/server/lib/runtimes/node.js +511 -0
- package/server/lib/wiring.js +539 -98
- package/server/lib/ws/README.md +35 -0
- package/server/lib/ws/adapters/README.md +54 -0
- package/server/lib/ws/adapters/bun.js +538 -170
- package/server/lib/ws/adapters/deno.js +623 -149
- package/server/lib/ws/adapters/files.md +42 -0
- package/server/lib/ws/files.md +74 -0
- package/server/lib/ws/frames.js +532 -154
- package/server/lib/ws/index.js +207 -10
- package/server/lib/ws/server.js +385 -92
- package/server/lib/ws/socket.js +549 -181
- package/server/lib/wsProvider.js +363 -89
- package/server/plugins/binary.js +282 -0
- package/server/security/README.md +92 -0
- package/server/security/auth/README.md +319 -0
- package/server/security/auth/adapters/files.md +95 -0
- package/server/security/auth/adapters/ldap/constants.js +37 -0
- package/server/security/auth/adapters/ldap/files.md +19 -0
- package/server/security/auth/adapters/ldap/helpers.js +111 -0
- package/server/security/auth/adapters/ldap.js +353 -0
- package/server/security/auth/adapters/oauth2/constants.js +41 -0
- package/server/security/auth/adapters/oauth2/files.md +19 -0
- package/server/security/auth/adapters/oauth2/helpers.js +123 -0
- package/server/security/auth/adapters/oauth2.js +273 -0
- package/server/security/auth/adapters/opaque-handlers.js +314 -0
- package/server/security/auth/adapters/opaque.js +205 -0
- package/server/security/auth/adapters/saml/constants.js +52 -0
- package/server/security/auth/adapters/saml/files.md +19 -0
- package/server/security/auth/adapters/saml/helpers.js +74 -0
- package/server/security/auth/adapters/saml.js +173 -0
- package/server/security/auth/adapters/totp.js +703 -0
- package/server/security/auth/adapters/webauthn.js +625 -0
- package/server/security/auth/files.md +61 -0
- package/server/security/auth/framework/constants.js +27 -0
- package/server/security/auth/framework/files.md +23 -0
- package/server/security/auth/framework/handlers.js +272 -0
- package/server/security/auth/framework/socket-auth.js +177 -0
- package/server/security/auth/handlers/auth-messages.js +143 -0
- package/server/security/auth/handlers/files.md +28 -0
- package/server/security/auth/index.js +290 -0
- package/server/security/auth/mfa/crypto/aead.js +148 -0
- package/server/security/auth/mfa/crypto/constants.js +35 -0
- package/server/security/auth/mfa/crypto/files.md +27 -0
- package/server/security/auth/mfa/crypto/kdf.js +120 -0
- package/server/security/auth/mfa/crypto/utils.js +68 -0
- package/server/security/auth/mfa/crypto-utils.js +80 -0
- package/server/security/auth/mfa/files.md +77 -0
- package/server/security/auth/mfa/ledger/constants.js +75 -0
- package/server/security/auth/mfa/ledger/errors.js +73 -0
- package/server/security/auth/mfa/ledger/files.md +23 -0
- package/server/security/auth/mfa/ledger/share-record.js +32 -0
- package/server/security/auth/mfa/ledger.js +255 -0
- package/server/security/auth/mfa/recovery/constants.js +67 -0
- package/server/security/auth/mfa/recovery/files.md +19 -0
- package/server/security/auth/mfa/recovery/handlers.js +216 -0
- package/server/security/auth/mfa/recovery.js +191 -0
- package/server/security/auth/mfa/sss/constants.js +21 -0
- package/server/security/auth/mfa/sss/files.md +23 -0
- package/server/security/auth/mfa/sss/gf256.js +103 -0
- package/server/security/auth/mfa/sss/serialization.js +82 -0
- package/server/security/auth/mfa/sss.js +161 -0
- package/server/security/auth/mfa/two-of-three/constants.js +58 -0
- package/server/security/auth/mfa/two-of-three/files.md +23 -0
- package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
- package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
- package/server/security/auth/mfa/two-of-three.js +136 -0
- package/server/security/auth/nonce-manager.js +89 -0
- package/server/security/auth/state-machine-mfa.js +269 -0
- package/server/security/auth/state-machine.js +257 -0
- package/server/security/extractRootDomain.js +144 -16
- package/server/security/files.md +51 -0
- package/server/security/origin.js +197 -15
- package/server/security/reply.js +274 -16
- package/server/socket/README.md +119 -0
- package/server/socket/authMiddleware.js +299 -0
- package/server/socket/files.md +86 -0
- package/server/socket/open.js +154 -8
- package/server/socket/pluginHooks.js +334 -0
- package/server/socket/receive.js +184 -225
- package/server/socket/receiveContext.js +117 -0
- package/server/socket/send.js +416 -78
- package/server/socket/tagUtils.js +402 -0
- package/server/utils/README.md +19 -0
- package/server/utils/deepRequire.js +255 -30
- package/server/utils/files.md +57 -0
- package/server/utils/genId.js +182 -20
- package/server/utils/parseUserAgent.js +313 -251
- package/server/utils/userAgent/README.md +65 -0
- package/server/utils/userAgent/files.md +46 -0
- package/server/utils/userAgent/patterns.js +545 -0
- package/utils/README.md +21 -0
- package/utils/files.md +66 -0
- package/utils/jss/README.md +21 -0
- package/utils/jss/decode.js +471 -0
- package/utils/jss/encode.js +312 -0
- package/utils/jss/files.md +68 -0
- package/utils/jss/plugins.js +210 -0
- package/utils/jss.js +219 -273
- package/utils/messageHash.js +238 -35
- package/dist/api-ape.min.js +0 -2
- package/dist/api-ape.min.js.map +0 -7
- package/server/client.js +0 -308
- package/server/lib/broadcast.js +0 -146
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Common utilities for file handling in api-ape client
|
|
3
|
+
*
|
|
4
|
+
* This module provides shared utility functions used by both file upload
|
|
5
|
+
* (fileHandling.js) and file download (fileDownload.js) modules.
|
|
6
|
+
*
|
|
7
|
+
* ## Key Functions
|
|
8
|
+
*
|
|
9
|
+
* - **Binary Detection**: `isBinaryData()`, `getBinaryTag()`
|
|
10
|
+
* - **Hash Generation**: `generateUploadHash()`
|
|
11
|
+
* - **Object Traversal**: `setValueAtPath()`, `findTaggedProps()`, `cleanTaggedKeys()`
|
|
12
|
+
*
|
|
13
|
+
* ## Tag System
|
|
14
|
+
*
|
|
15
|
+
* api-ape uses special key suffixes to mark binary data references:
|
|
16
|
+
* - `<!L>` - Server-linked binary (download from server)
|
|
17
|
+
* - `<!F>` - File share (client-to-client transfer)
|
|
18
|
+
* - `<!A>` - ArrayBuffer upload
|
|
19
|
+
* - `<!B>` - Blob upload
|
|
20
|
+
*
|
|
21
|
+
* @module client/connection/fileUtils
|
|
22
|
+
* @see {@link module:client/connection/fileHandling} for upload processing
|
|
23
|
+
* @see {@link module:client/connection/fileDownload} for download processing
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* import {
|
|
27
|
+
* isBinaryData,
|
|
28
|
+
* findTaggedProps,
|
|
29
|
+
* setValueAtPath
|
|
30
|
+
* } from './fileUtils'
|
|
31
|
+
*
|
|
32
|
+
* // Check for binary data
|
|
33
|
+
* const data = new ArrayBuffer(100)
|
|
34
|
+
* console.log(isBinaryData(data)) // true
|
|
35
|
+
*
|
|
36
|
+
* // Find tagged properties in response
|
|
37
|
+
* const response = { 'image<!L>': 'abc123', name: 'photo.jpg' }
|
|
38
|
+
* const tags = findTaggedProps(response, 'L')
|
|
39
|
+
* // [{ path: 'image', hash: 'abc123', originalKey: 'image<!L>' }]
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if a value is binary data
|
|
44
|
+
*
|
|
45
|
+
* Detects ArrayBuffer, TypedArray views (Uint8Array, Int32Array, etc.),
|
|
46
|
+
* and Blob objects. Used to determine if a value needs special handling
|
|
47
|
+
* during serialization.
|
|
48
|
+
*
|
|
49
|
+
* @param {any} value - The value to check
|
|
50
|
+
* @returns {boolean} True if the value is binary data, false otherwise
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // ArrayBuffer
|
|
54
|
+
* isBinaryData(new ArrayBuffer(10)) // true
|
|
55
|
+
*
|
|
56
|
+
* // TypedArray views
|
|
57
|
+
* isBinaryData(new Uint8Array(10)) // true
|
|
58
|
+
* isBinaryData(new Float32Array(10)) // true
|
|
59
|
+
*
|
|
60
|
+
* // Blob (browser only)
|
|
61
|
+
* isBinaryData(new Blob(['hello'])) // true
|
|
62
|
+
*
|
|
63
|
+
* // Non-binary
|
|
64
|
+
* isBinaryData('string') // false
|
|
65
|
+
* isBinaryData({ key: 'value' }) // false
|
|
66
|
+
* isBinaryData(null) // false
|
|
67
|
+
* isBinaryData(undefined) // false
|
|
68
|
+
*/
|
|
69
|
+
export function isBinaryData(value) {
|
|
70
|
+
if (value === null || value === undefined) return false;
|
|
71
|
+
return (
|
|
72
|
+
value instanceof ArrayBuffer ||
|
|
73
|
+
ArrayBuffer.isView(value) ||
|
|
74
|
+
(typeof Blob !== "undefined" && value instanceof Blob)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get the binary type tag for a value
|
|
80
|
+
*
|
|
81
|
+
* Returns a single character tag indicating the binary data type:
|
|
82
|
+
* - `'A'` for ArrayBuffer and TypedArray views
|
|
83
|
+
* - `'B'` for Blob objects
|
|
84
|
+
*
|
|
85
|
+
* This tag is used in the wire protocol to indicate how to
|
|
86
|
+
* reconstruct the binary data on the receiving end.
|
|
87
|
+
*
|
|
88
|
+
* @param {ArrayBuffer|ArrayBufferView|Blob} value - The binary value to tag
|
|
89
|
+
* @returns {'A'|'B'} The type tag character
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* getBinaryTag(new ArrayBuffer(10)) // 'A'
|
|
93
|
+
* getBinaryTag(new Uint8Array(10)) // 'A'
|
|
94
|
+
* getBinaryTag(new Blob(['hello'])) // 'B'
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // Used when building upload metadata
|
|
98
|
+
* const tag = getBinaryTag(fileData)
|
|
99
|
+
* const key = `attachment<!${tag}>` // 'attachment<!A>' or 'attachment<!B>'
|
|
100
|
+
*/
|
|
101
|
+
export function getBinaryTag(value) {
|
|
102
|
+
if (typeof Blob !== "undefined" && value instanceof Blob) return "B";
|
|
103
|
+
return "A";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Generate a simple hash for binary upload path identification
|
|
108
|
+
*
|
|
109
|
+
* Creates a short hash string from a path string, used to uniquely
|
|
110
|
+
* identify binary data uploads. Uses a simple string hashing algorithm
|
|
111
|
+
* with base-36 encoding for compact representation.
|
|
112
|
+
*
|
|
113
|
+
* Note: This is not cryptographically secure - it's for identification only.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} path - The property path to hash (e.g., 'user.avatar')
|
|
116
|
+
* @returns {string} Base-36 encoded hash string
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* generateUploadHash('image') // e.g., 'k3m9x'
|
|
120
|
+
* generateUploadHash('user.avatar') // e.g., 'p7n2w'
|
|
121
|
+
* generateUploadHash('files.0') // e.g., 'q1r8t'
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* // Used in upload processing
|
|
125
|
+
* const hash = generateUploadHash('documents.contract')
|
|
126
|
+
* const uploadUrl = `/api/ape/data/${queryId}/${hash}`
|
|
127
|
+
*/
|
|
128
|
+
export function generateUploadHash(path) {
|
|
129
|
+
let hash = 0;
|
|
130
|
+
for (let i = 0; i < path.length; i++) {
|
|
131
|
+
const char = path.charCodeAt(i);
|
|
132
|
+
hash = (hash << 5) - hash + char;
|
|
133
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
134
|
+
}
|
|
135
|
+
return Math.abs(hash).toString(36);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Set a value at a nested dot-notation path in an object
|
|
140
|
+
*
|
|
141
|
+
* Traverses an object following a dot-separated path and sets
|
|
142
|
+
* the value at the final key. The path must be valid (all intermediate
|
|
143
|
+
* objects must exist).
|
|
144
|
+
*
|
|
145
|
+
* @param {Object} obj - The object to modify
|
|
146
|
+
* @param {string} path - Dot-notation path (e.g., 'user.profile.avatar')
|
|
147
|
+
* @param {any} value - The value to set at the path
|
|
148
|
+
* @returns {void}
|
|
149
|
+
* @throws {TypeError} If intermediate path segments don't exist
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* const data = { user: { profile: { name: 'Alice' } } }
|
|
153
|
+
*
|
|
154
|
+
* setValueAtPath(data, 'user.profile.avatar', new ArrayBuffer(100))
|
|
155
|
+
* // data.user.profile.avatar is now the ArrayBuffer
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* // Simple path
|
|
159
|
+
* const obj = { image: 'placeholder' }
|
|
160
|
+
* setValueAtPath(obj, 'image', binaryData)
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* // Array index path
|
|
164
|
+
* const obj = { files: [null, null] }
|
|
165
|
+
* setValueAtPath(obj, 'files.0', fileBuffer)
|
|
166
|
+
* setValueAtPath(obj, 'files.1', anotherBuffer)
|
|
167
|
+
*/
|
|
168
|
+
export function setValueAtPath(obj, path, value) {
|
|
169
|
+
const parts = path.split(".");
|
|
170
|
+
let current = obj;
|
|
171
|
+
|
|
172
|
+
// Navigate to parent of target property
|
|
173
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
174
|
+
current = current[parts[i]];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Set the value at the final key
|
|
178
|
+
current[parts[parts.length - 1]] = value;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Find properties with a specific tag suffix in a nested object
|
|
183
|
+
*
|
|
184
|
+
* Recursively searches through an object (including arrays) for keys
|
|
185
|
+
* ending with a tag suffix like `<!L>`, `<!F>`, `<!A>`, or `<!B>`.
|
|
186
|
+
* Returns information about each tagged property found.
|
|
187
|
+
*
|
|
188
|
+
* ## Tag Types
|
|
189
|
+
* - `L` - Linked binary resource (server sends hash, client downloads)
|
|
190
|
+
* - `F` - File share (client-to-client transfer)
|
|
191
|
+
* - `A` - ArrayBuffer upload marker
|
|
192
|
+
* - `B` - Blob upload marker
|
|
193
|
+
*
|
|
194
|
+
* @param {Object|Array|any} obj - The object to search
|
|
195
|
+
* @param {string} tag - The tag to look for (without <! and >)
|
|
196
|
+
* @param {string} [path=''] - Current path (used internally for recursion)
|
|
197
|
+
* @returns {Array<{path: string, hash: string, originalKey: string}>} Array of found tagged properties
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* // Find server-linked binary references
|
|
201
|
+
* const response = {
|
|
202
|
+
* name: 'Report',
|
|
203
|
+
* 'pdf<!L>': 'hash123',
|
|
204
|
+
* 'thumbnail<!L>': 'hash456'
|
|
205
|
+
* }
|
|
206
|
+
*
|
|
207
|
+
* const links = findTaggedProps(response, 'L')
|
|
208
|
+
* // Returns:
|
|
209
|
+
* // [
|
|
210
|
+
* // { path: 'pdf', hash: 'hash123', originalKey: 'pdf<!L>' },
|
|
211
|
+
* // { path: 'thumbnail', hash: 'hash456', originalKey: 'thumbnail<!L>' }
|
|
212
|
+
* // ]
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* // Find nested tagged properties
|
|
216
|
+
* const data = {
|
|
217
|
+
* user: {
|
|
218
|
+
* 'avatar<!L>': 'avatarHash',
|
|
219
|
+
* documents: [
|
|
220
|
+
* { 'file<!L>': 'doc1Hash' },
|
|
221
|
+
* { 'file<!L>': 'doc2Hash' }
|
|
222
|
+
* ]
|
|
223
|
+
* }
|
|
224
|
+
* }
|
|
225
|
+
*
|
|
226
|
+
* const links = findTaggedProps(data, 'L')
|
|
227
|
+
* // Returns paths: 'user.avatar', 'user.documents.0.file', 'user.documents.1.file'
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* // Find file share tags
|
|
231
|
+
* const message = { 'attachment<!F>': 'shareHash123' }
|
|
232
|
+
* const shares = findTaggedProps(message, 'F')
|
|
233
|
+
*/
|
|
234
|
+
export function findTaggedProps(obj, tag, path = "") {
|
|
235
|
+
const results = [];
|
|
236
|
+
|
|
237
|
+
// Base case: null, undefined, or non-object
|
|
238
|
+
if (obj === null || obj === undefined || typeof obj !== "object") {
|
|
239
|
+
return results;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Handle arrays - recurse into each element
|
|
243
|
+
if (Array.isArray(obj)) {
|
|
244
|
+
for (let i = 0; i < obj.length; i++) {
|
|
245
|
+
results.push(
|
|
246
|
+
...findTaggedProps(obj[i], tag, path ? `${path}.${i}` : String(i)),
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
return results;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Handle objects - check each key for tag suffix
|
|
253
|
+
const suffix = `<!${tag}>`;
|
|
254
|
+
|
|
255
|
+
for (const key of Object.keys(obj)) {
|
|
256
|
+
if (key.endsWith(suffix)) {
|
|
257
|
+
// Found a tagged key - extract the clean name and hash
|
|
258
|
+
const cleanKey = key.slice(0, -4); // Remove <!X> suffix (4 chars)
|
|
259
|
+
results.push({
|
|
260
|
+
path: path ? `${path}.${cleanKey}` : cleanKey,
|
|
261
|
+
hash: obj[key],
|
|
262
|
+
originalKey: key,
|
|
263
|
+
});
|
|
264
|
+
} else {
|
|
265
|
+
// Not tagged - recurse into value
|
|
266
|
+
results.push(
|
|
267
|
+
...findTaggedProps(obj[key], tag, path ? `${path}.${key}` : key),
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return results;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Clean tagged keys from an object (rename `key<!X>` to `key`)
|
|
277
|
+
*
|
|
278
|
+
* Creates a new object with tag suffixes removed from keys.
|
|
279
|
+
* Recursively processes nested objects and arrays.
|
|
280
|
+
*
|
|
281
|
+
* This is used after finding tagged properties to create a clean
|
|
282
|
+
* object structure where the tags have been replaced with actual values.
|
|
283
|
+
*
|
|
284
|
+
* @param {Object|Array|any} obj - The object to clean
|
|
285
|
+
* @param {string} tag - The tag to remove (without <! and >)
|
|
286
|
+
* @returns {Object|Array|any} New object with cleaned keys
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* // Clean L-tagged keys
|
|
290
|
+
* const response = {
|
|
291
|
+
* name: 'Photo',
|
|
292
|
+
* 'image<!L>': 'hash123',
|
|
293
|
+
* size: 1024
|
|
294
|
+
* }
|
|
295
|
+
*
|
|
296
|
+
* const cleaned = cleanTaggedKeys(response, 'L')
|
|
297
|
+
* // Returns: { name: 'Photo', image: 'hash123', size: 1024 }
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* // Nested cleaning
|
|
301
|
+
* const data = {
|
|
302
|
+
* user: {
|
|
303
|
+
* 'avatar<!L>': 'avatarHash'
|
|
304
|
+
* }
|
|
305
|
+
* }
|
|
306
|
+
*
|
|
307
|
+
* const cleaned = cleanTaggedKeys(data, 'L')
|
|
308
|
+
* // Returns: { user: { avatar: 'avatarHash' } }
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* // Array handling
|
|
312
|
+
* const files = [
|
|
313
|
+
* { 'data<!L>': 'hash1' },
|
|
314
|
+
* { 'data<!L>': 'hash2' }
|
|
315
|
+
* ]
|
|
316
|
+
*
|
|
317
|
+
* const cleaned = cleanTaggedKeys(files, 'L')
|
|
318
|
+
* // Returns: [{ data: 'hash1' }, { data: 'hash2' }]
|
|
319
|
+
*/
|
|
320
|
+
export function cleanTaggedKeys(obj, tag) {
|
|
321
|
+
// Base case: null, undefined, or non-object - return as-is
|
|
322
|
+
if (obj === null || obj === undefined || typeof obj !== "object") {
|
|
323
|
+
return obj;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Handle arrays - map each element
|
|
327
|
+
if (Array.isArray(obj)) {
|
|
328
|
+
return obj.map((item) => cleanTaggedKeys(item, tag));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Handle objects - process each key
|
|
332
|
+
const cleaned = {};
|
|
333
|
+
const suffix = `<!${tag}>`;
|
|
334
|
+
|
|
335
|
+
for (const key of Object.keys(obj)) {
|
|
336
|
+
if (key.endsWith(suffix)) {
|
|
337
|
+
// Remove the tag suffix from the key
|
|
338
|
+
cleaned[key.slice(0, -4)] = obj[key];
|
|
339
|
+
} else {
|
|
340
|
+
// Keep key as-is, but recursively clean the value
|
|
341
|
+
cleaned[key] = cleanTaggedKeys(obj[key], tag);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return cleaned;
|
|
346
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Connection Module Files
|
|
2
|
+
|
|
3
|
+
This module handles the core mechanics of maintaining a WebSocket connection to an api-ape server. It manages connection state, message sending, request/response correlation, and binary file transfers.
|
|
4
|
+
|
|
5
|
+
## Guidelines
|
|
6
|
+
|
|
7
|
+
- **Browser-safe only** — All code must run in browsers without Node.js APIs
|
|
8
|
+
- **State consistency** — Connection state changes must flow through `state.js`
|
|
9
|
+
- **QueryId correlation** — All requests must use `sender.js` for proper response matching
|
|
10
|
+
- **Binary handling** — File transfers use the tag system (`<!B>`, `<!A>`, `<!L>`) — coordinate with `fileHandling.js`
|
|
11
|
+
- **Network detection** — Changes to connectivity detection should account for captive portals
|
|
12
|
+
|
|
13
|
+
## Directory Structure
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
connection/
|
|
17
|
+
├── fileDownload.js # Binary file download handling
|
|
18
|
+
├── fileHandling.js # File upload/download coordination
|
|
19
|
+
├── fileUtils.js # File transfer utilities
|
|
20
|
+
├── messageHandler.js # Message processing and dispatch
|
|
21
|
+
├── network.js # Network state detection
|
|
22
|
+
├── proxy.js # Proxy-based API generation
|
|
23
|
+
├── sender.js # Message sending with queryId correlation
|
|
24
|
+
├── state.js # Connection state machine
|
|
25
|
+
└── subscriptions.js # Pub/sub subscription manager
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Files
|
|
29
|
+
|
|
30
|
+
### `proxy.js`
|
|
31
|
+
|
|
32
|
+
Creates the Proxy-based API that allows `api.users.list()` syntax. Intercepts property access and method calls, converting them into WebSocket messages with the appropriate endpoint path.
|
|
33
|
+
|
|
34
|
+
### `sender.js`
|
|
35
|
+
|
|
36
|
+
Handles message serialization and request/response correlation. Generates unique `queryId` for each request, tracks pending requests, manages timeouts, and resolves Promises when responses arrive.
|
|
37
|
+
|
|
38
|
+
### `state.js`
|
|
39
|
+
|
|
40
|
+
Connection state machine that tracks the current connection status. Emits state change events and provides the `onConnectionChange` callback functionality.
|
|
41
|
+
|
|
42
|
+
**States:** `offline` → `connecting` → `connected` → `disconnected` → `walled`
|
|
43
|
+
|
|
44
|
+
### `network.js`
|
|
45
|
+
|
|
46
|
+
Detects network conditions to provide accurate connection state:
|
|
47
|
+
- **Offline** — Browser reports no network (`navigator.onLine`)
|
|
48
|
+
- **Walled garden** — Captive portal detected (WiFi login page)
|
|
49
|
+
- **Online** — Full internet connectivity confirmed
|
|
50
|
+
|
|
51
|
+
### `fileDownload.js`
|
|
52
|
+
|
|
53
|
+
Handles downloading binary data from the server. When a response contains `<!L>` (link) tags, fetches the binary data via HTTP GET with session cookies for authentication.
|
|
54
|
+
|
|
55
|
+
### `fileHandling.js`
|
|
56
|
+
|
|
57
|
+
Coordinates file uploads and downloads by detecting special tags in messages:
|
|
58
|
+
- `<!B>` / `<!A>` — Binary data that needs to be uploaded
|
|
59
|
+
- `<!L>` — Download links that need to be fetched
|
|
60
|
+
|
|
61
|
+
### `fileUtils.js`
|
|
62
|
+
|
|
63
|
+
Shared utilities for file transfer operations including content type detection, hash generation, and ArrayBuffer/Buffer conversions.
|
|
64
|
+
|
|
65
|
+
### `messageHandler.js`
|
|
66
|
+
|
|
67
|
+
Handles incoming message processing and dispatch. Processes binary data hydration (fetching linked resources) and dispatches messages to registered handlers and subscription callbacks. Provides the `setOnReceiver` function for registering message handlers.
|
|
68
|
+
|
|
69
|
+
### `subscriptions.js`
|
|
70
|
+
|
|
71
|
+
Manages channel subscriptions for the chained subscription syntax. Tracks local callbacks per channel, sends subscribe/unsubscribe messages to server, dispatches incoming data to callbacks, and handles resubscription on reconnect.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Message handling utilities for api-ape client
|
|
3
|
+
*
|
|
4
|
+
* Handles incoming message processing, including binary data hydration
|
|
5
|
+
* and dispatching to registered handlers and subscription callbacks.
|
|
6
|
+
*
|
|
7
|
+
* @module client/connection/messageHandler
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
fetchLinkedResources,
|
|
12
|
+
fetchSharedFiles,
|
|
13
|
+
} from "./fileDownload";
|
|
14
|
+
import {
|
|
15
|
+
hasSubscribers,
|
|
16
|
+
dispatch as dispatchToSubscribers,
|
|
17
|
+
} from "./subscriptions";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Array of universal message receivers (called for all message types)
|
|
21
|
+
* @type {Array<function({err: any, type: string, data: any}): void>}
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
const receiverArray = [];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Map of type-specific message receivers
|
|
28
|
+
* @type {Object.<string, Array<function({err: any, type: string, data: any}): void>>}
|
|
29
|
+
* @private
|
|
30
|
+
*/
|
|
31
|
+
const ofTypesOb = {};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Process incoming message data to hydrate binary resources
|
|
35
|
+
*
|
|
36
|
+
* This function handles two types of binary data references:
|
|
37
|
+
* - L-tagged: Binary data linked from server responses (fetchLinkedResources)
|
|
38
|
+
* - F-tagged: Shared files from other clients (fetchSharedFiles)
|
|
39
|
+
*
|
|
40
|
+
* @param {any} data - Raw data from server message
|
|
41
|
+
* @param {Error|null} err - Error from the message, if any
|
|
42
|
+
* @returns {Promise<any>} Hydrated data with binary resources fetched
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* // Server sends: { image<!L>: 'abc123' }
|
|
46
|
+
* // After hydration: { image: ArrayBuffer }
|
|
47
|
+
*/
|
|
48
|
+
export async function processIncomingData(data, err) {
|
|
49
|
+
if (!data || err) return data;
|
|
50
|
+
try {
|
|
51
|
+
let result = await fetchLinkedResources(data);
|
|
52
|
+
return await fetchSharedFiles(result);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error(`🦍 Failed to hydrate data:`, e);
|
|
55
|
+
return data;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Dispatch a received message to all registered handlers
|
|
61
|
+
*
|
|
62
|
+
* Messages are delivered to:
|
|
63
|
+
* 1. Chained subscription callbacks (new v2 syntax)
|
|
64
|
+
* 2. Type-specific handlers registered via setOnReceiver(type, handler)
|
|
65
|
+
* 3. Universal handlers registered via setOnReceiver(handler)
|
|
66
|
+
*
|
|
67
|
+
* @param {string} type - Message type identifier
|
|
68
|
+
* @param {Error|null} err - Error payload, if any
|
|
69
|
+
* @param {any} data - Message data payload
|
|
70
|
+
*/
|
|
71
|
+
export function dispatchMessage(type, err, data) {
|
|
72
|
+
// Dispatch to chained subscription callbacks (v2 syntax)
|
|
73
|
+
if (hasSubscribers(type)) {
|
|
74
|
+
dispatchToSubscribers(type, data);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Legacy handlers
|
|
78
|
+
if (ofTypesOb[type]) ofTypesOb[type].forEach((w) => w({ err, type, data }));
|
|
79
|
+
receiverArray.forEach((w) => w({ err, type, data }));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Register a message receiver/handler
|
|
84
|
+
*
|
|
85
|
+
* @param {string|function} onTypeStFn - Message type to listen for, or universal handler function
|
|
86
|
+
* @param {function=} handlerFn - Handler function (if first arg is type string)
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* // Type-specific handler
|
|
90
|
+
* setOnReceiver('notification', (msg) => {
|
|
91
|
+
* console.log('Got notification:', msg.data)
|
|
92
|
+
* })
|
|
93
|
+
*
|
|
94
|
+
* // Universal handler (receives all messages)
|
|
95
|
+
* setOnReceiver((msg) => {
|
|
96
|
+
* console.log('Got message:', msg.type, msg.data)
|
|
97
|
+
* })
|
|
98
|
+
*/
|
|
99
|
+
export function setOnReceiver(onTypeStFn, handlerFn) {
|
|
100
|
+
if (typeof onTypeStFn === "string") {
|
|
101
|
+
ofTypesOb[onTypeStFn] = [handlerFn];
|
|
102
|
+
} else if (!receiverArray.includes(onTypeStFn)) {
|
|
103
|
+
receiverArray.push(onTypeStFn);
|
|
104
|
+
}
|
|
105
|
+
}
|