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,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Upload Tag Utilities for Socket Receive Handler
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for detecting and processing special binary data
|
|
5
|
+
* tags in api-ape messages. The tag system enables binary data (files, ArrayBuffers)
|
|
6
|
+
* to be referenced in JSON messages and transferred separately via HTTP.
|
|
7
|
+
*
|
|
8
|
+
* Tag Types:
|
|
9
|
+
* - `<!B>` (Buffer): Client will upload binary data (Buffer/ArrayBuffer)
|
|
10
|
+
* - `<!A>` (ArrayBuffer): Client will upload an ArrayBuffer
|
|
11
|
+
* - `<!F>` (File): References a file for streaming client-to-client transfer
|
|
12
|
+
* - `<!L>` (Link): Server returns a download link for binary data
|
|
13
|
+
*
|
|
14
|
+
* Data Flow:
|
|
15
|
+
* 1. Client sends message with tagged keys: `{ "image<!B>": "hash123" }`
|
|
16
|
+
* 2. Server extracts upload tags using `findUploadTags()`
|
|
17
|
+
* 3. Server waits for binary data upload via HTTP PUT
|
|
18
|
+
* 4. Server cleans tags and injects actual data using `cleanUploadTags()`
|
|
19
|
+
* 5. Controller receives clean object: `{ image: <Buffer> }`
|
|
20
|
+
*
|
|
21
|
+
* @module server/socket/tagUtils
|
|
22
|
+
* @see {@link module:server/socket/receive} - Socket receive handler using these utilities
|
|
23
|
+
* @see {@link module:server/lib/fileTransfer} - File transfer management
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const { findUploadTags, cleanUploadTags, setValueAtPath } = require('./tagUtils')
|
|
27
|
+
*
|
|
28
|
+
* // Message from client with binary upload tag
|
|
29
|
+
* const message = { "avatar<!B>": "abc123", name: "Alice" }
|
|
30
|
+
*
|
|
31
|
+
* // Find all upload tags
|
|
32
|
+
* const uploads = findUploadTags(message)
|
|
33
|
+
* // [{ path: 'avatar', hash: 'abc123', tag: 'B', originalKey: 'avatar<!B>' }]
|
|
34
|
+
*
|
|
35
|
+
* // After receiving the binary data, clean the object
|
|
36
|
+
* const cleaned = cleanUploadTags(message)
|
|
37
|
+
* // { avatar: 'abc123', name: 'Alice' }
|
|
38
|
+
*
|
|
39
|
+
* // Inject the actual binary data
|
|
40
|
+
* setValueAtPath(cleaned, 'avatar', actualBuffer)
|
|
41
|
+
* // { avatar: <Buffer ...>, name: 'Alice' }
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {Object} UploadTag
|
|
46
|
+
* Represents a detected upload tag in a message object.
|
|
47
|
+
*
|
|
48
|
+
* @property {string} path - Dot-notation path to the value (e.g., 'user.profile.avatar')
|
|
49
|
+
* @property {string} hash - The hash/identifier for the upload
|
|
50
|
+
* @property {'B'|'A'} tag - Tag type: 'B' for Buffer, 'A' for ArrayBuffer
|
|
51
|
+
* @property {string} originalKey - The original key including the tag (e.g., 'avatar<!B>')
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @typedef {Object} FileTag
|
|
56
|
+
* Represents a detected file tag for streaming transfers.
|
|
57
|
+
*
|
|
58
|
+
* @property {string} path - Dot-notation path to the value
|
|
59
|
+
* @property {string} hash - The file identifier/hash
|
|
60
|
+
* @property {string} originalKey - The original key including the tag
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Recursively finds all upload tags (`<!B>` and `<!A>`) in an object.
|
|
65
|
+
*
|
|
66
|
+
* Traverses the object tree and identifies keys ending with `<!B>` or `<!A>`,
|
|
67
|
+
* which indicate that the client will upload binary data for that field.
|
|
68
|
+
*
|
|
69
|
+
* The function handles:
|
|
70
|
+
* - Nested objects: `{ user: { "avatar<!B>": "hash" } }` → path: 'user.avatar'
|
|
71
|
+
* - Arrays: `{ files: [{ "data<!B>": "h1" }, { "data<!B>": "h2" }] }` → paths: 'files.0.data', 'files.1.data'
|
|
72
|
+
* - Multiple tags at any level
|
|
73
|
+
*
|
|
74
|
+
* @function findUploadTags
|
|
75
|
+
* @param {Object} obj - The object to search for upload tags
|
|
76
|
+
* @param {string} [path=''] - Current path prefix (used in recursion)
|
|
77
|
+
* @returns {UploadTag[]} Array of detected upload tags with their paths and metadata
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* // Simple upload tag
|
|
81
|
+
* const uploads = findUploadTags({ "image<!B>": "hash123" })
|
|
82
|
+
* // [{ path: 'image', hash: 'hash123', tag: 'B', originalKey: 'image<!B>' }]
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* // Nested upload tags
|
|
86
|
+
* const uploads = findUploadTags({
|
|
87
|
+
* user: {
|
|
88
|
+
* "avatar<!B>": "hash1",
|
|
89
|
+
* profile: {
|
|
90
|
+
* "banner<!A>": "hash2"
|
|
91
|
+
* }
|
|
92
|
+
* }
|
|
93
|
+
* })
|
|
94
|
+
* // [
|
|
95
|
+
* // { path: 'user.avatar', hash: 'hash1', tag: 'B', originalKey: 'avatar<!B>' },
|
|
96
|
+
* // { path: 'user.profile.banner', hash: 'hash2', tag: 'A', originalKey: 'banner<!A>' }
|
|
97
|
+
* // ]
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* // Tags in arrays
|
|
101
|
+
* const uploads = findUploadTags({
|
|
102
|
+
* attachments: [
|
|
103
|
+
* { "file<!B>": "hash1" },
|
|
104
|
+
* { "file<!B>": "hash2" }
|
|
105
|
+
* ]
|
|
106
|
+
* })
|
|
107
|
+
* // [
|
|
108
|
+
* // { path: 'attachments.0.file', hash: 'hash1', tag: 'B', originalKey: 'file<!B>' },
|
|
109
|
+
* // { path: 'attachments.1.file', hash: 'hash2', tag: 'B', originalKey: 'file<!B>' }
|
|
110
|
+
* // ]
|
|
111
|
+
*/
|
|
112
|
+
function findUploadTags(obj, path = "") {
|
|
113
|
+
const uploads = [];
|
|
114
|
+
|
|
115
|
+
// Base case: null, undefined, or primitives have no tags
|
|
116
|
+
if (obj === null || obj === undefined || typeof obj !== "object") {
|
|
117
|
+
return uploads;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Handle arrays - recurse into each element with index as path segment
|
|
121
|
+
if (Array.isArray(obj)) {
|
|
122
|
+
for (let i = 0; i < obj.length; i++) {
|
|
123
|
+
uploads.push(
|
|
124
|
+
...findUploadTags(obj[i], path ? `${path}.${i}` : String(i)),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return uploads;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Handle objects - check each key for tags or recurse
|
|
131
|
+
for (const key of Object.keys(obj)) {
|
|
132
|
+
// Check for <!B> (Buffer) tag
|
|
133
|
+
const bMatch = key.match(/^(.+)<!B>$/);
|
|
134
|
+
// Check for <!A> (ArrayBuffer) tag
|
|
135
|
+
const aMatch = key.match(/^(.+)<!A>$/);
|
|
136
|
+
|
|
137
|
+
if (bMatch) {
|
|
138
|
+
uploads.push({
|
|
139
|
+
path: path ? `${path}.${bMatch[1]}` : bMatch[1],
|
|
140
|
+
hash: obj[key],
|
|
141
|
+
tag: "B",
|
|
142
|
+
originalKey: key,
|
|
143
|
+
});
|
|
144
|
+
} else if (aMatch) {
|
|
145
|
+
uploads.push({
|
|
146
|
+
path: path ? `${path}.${aMatch[1]}` : aMatch[1],
|
|
147
|
+
hash: obj[key],
|
|
148
|
+
tag: "A",
|
|
149
|
+
originalKey: key,
|
|
150
|
+
});
|
|
151
|
+
} else {
|
|
152
|
+
// No tag on this key, recurse into the value
|
|
153
|
+
uploads.push(...findUploadTags(obj[key], path ? `${path}.${key}` : key));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return uploads;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Recursively finds all file tags (`<!F>`) in an object.
|
|
162
|
+
*
|
|
163
|
+
* File tags are used for streaming client-to-client file transfers.
|
|
164
|
+
* Unlike upload tags, file tags reference files that are streamed
|
|
165
|
+
* between clients without the server storing the entire file.
|
|
166
|
+
*
|
|
167
|
+
* @function findFileTags
|
|
168
|
+
* @param {Object} obj - The object to search for file tags
|
|
169
|
+
* @param {string} [path=''] - Current path prefix (used in recursion)
|
|
170
|
+
* @returns {FileTag[]} Array of detected file tags with their paths and metadata
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* const files = findFileTags({
|
|
174
|
+
* "document<!F>": "file123",
|
|
175
|
+
* metadata: { name: "report.pdf" }
|
|
176
|
+
* })
|
|
177
|
+
* // [{ path: 'document', hash: 'file123', originalKey: 'document<!F>' }]
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* // Nested file tags
|
|
181
|
+
* const files = findFileTags({
|
|
182
|
+
* attachments: {
|
|
183
|
+
* "primary<!F>": "f1",
|
|
184
|
+
* "secondary<!F>": "f2"
|
|
185
|
+
* }
|
|
186
|
+
* })
|
|
187
|
+
* // [
|
|
188
|
+
* // { path: 'attachments.primary', hash: 'f1', originalKey: 'primary<!F>' },
|
|
189
|
+
* // { path: 'attachments.secondary', hash: 'f2', originalKey: 'secondary<!F>' }
|
|
190
|
+
* // ]
|
|
191
|
+
*/
|
|
192
|
+
function findFileTags(obj, path = "") {
|
|
193
|
+
const files = [];
|
|
194
|
+
|
|
195
|
+
// Base case: null, undefined, or primitives have no tags
|
|
196
|
+
if (obj === null || obj === undefined || typeof obj !== "object") {
|
|
197
|
+
return files;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Handle arrays - recurse into each element with index as path segment
|
|
201
|
+
if (Array.isArray(obj)) {
|
|
202
|
+
for (let i = 0; i < obj.length; i++) {
|
|
203
|
+
files.push(...findFileTags(obj[i], path ? `${path}.${i}` : String(i)));
|
|
204
|
+
}
|
|
205
|
+
return files;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Handle objects - check each key for file tags or recurse
|
|
209
|
+
for (const key of Object.keys(obj)) {
|
|
210
|
+
// Check for <!F> (File) tag
|
|
211
|
+
const fMatch = key.match(/^(.+)<!F>$/);
|
|
212
|
+
|
|
213
|
+
if (fMatch) {
|
|
214
|
+
files.push({
|
|
215
|
+
path: path ? `${path}.${fMatch[1]}` : fMatch[1],
|
|
216
|
+
hash: obj[key],
|
|
217
|
+
originalKey: key,
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
// No tag on this key, recurse into the value
|
|
221
|
+
files.push(...findFileTags(obj[key], path ? `${path}.${key}` : key));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return files;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Recursively cleans upload tags from an object.
|
|
230
|
+
*
|
|
231
|
+
* Transforms keys like `"avatar<!B>"` to `"avatar"` throughout the object tree.
|
|
232
|
+
* This prepares the object for controller consumption after binary data
|
|
233
|
+
* has been collected.
|
|
234
|
+
*
|
|
235
|
+
* The function:
|
|
236
|
+
* - Removes `<!B>` and `<!A>` suffixes from keys
|
|
237
|
+
* - Preserves the structure of the object
|
|
238
|
+
* - Recursively processes nested objects and arrays
|
|
239
|
+
* - Returns a new object (does not mutate the original)
|
|
240
|
+
*
|
|
241
|
+
* @function cleanUploadTags
|
|
242
|
+
* @param {Object} obj - The object with tagged keys to clean
|
|
243
|
+
* @returns {Object} New object with tags removed from keys
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* // Clean a simple object
|
|
247
|
+
* const cleaned = cleanUploadTags({
|
|
248
|
+
* "image<!B>": "hash123",
|
|
249
|
+
* name: "Photo"
|
|
250
|
+
* })
|
|
251
|
+
* // { image: 'hash123', name: 'Photo' }
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* // Clean nested objects
|
|
255
|
+
* const cleaned = cleanUploadTags({
|
|
256
|
+
* user: {
|
|
257
|
+
* "avatar<!B>": "hash1",
|
|
258
|
+
* profile: {
|
|
259
|
+
* "banner<!A>": "hash2",
|
|
260
|
+
* bio: "Hello"
|
|
261
|
+
* }
|
|
262
|
+
* }
|
|
263
|
+
* })
|
|
264
|
+
* // {
|
|
265
|
+
* // user: {
|
|
266
|
+
* // avatar: 'hash1',
|
|
267
|
+
* // profile: {
|
|
268
|
+
* // banner: 'hash2',
|
|
269
|
+
* // bio: 'Hello'
|
|
270
|
+
* // }
|
|
271
|
+
* // }
|
|
272
|
+
* // }
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* // Clean arrays
|
|
276
|
+
* const cleaned = cleanUploadTags({
|
|
277
|
+
* files: [
|
|
278
|
+
* { "data<!B>": "h1", name: "file1" },
|
|
279
|
+
* { "data<!B>": "h2", name: "file2" }
|
|
280
|
+
* ]
|
|
281
|
+
* })
|
|
282
|
+
* // {
|
|
283
|
+
* // files: [
|
|
284
|
+
* // { data: 'h1', name: 'file1' },
|
|
285
|
+
* // { data: 'h2', name: 'file2' }
|
|
286
|
+
* // ]
|
|
287
|
+
* // }
|
|
288
|
+
*/
|
|
289
|
+
function cleanUploadTags(obj) {
|
|
290
|
+
// Base case: null, undefined, or primitives pass through unchanged
|
|
291
|
+
if (obj === null || obj === undefined || typeof obj !== "object") {
|
|
292
|
+
return obj;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Handle arrays - recursively clean each element
|
|
296
|
+
if (Array.isArray(obj)) {
|
|
297
|
+
return obj.map(cleanUploadTags);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Handle objects - clean tagged keys and recurse into values
|
|
301
|
+
const cleaned = {};
|
|
302
|
+
|
|
303
|
+
for (const key of Object.keys(obj)) {
|
|
304
|
+
// Check for <!B> (Buffer) tag
|
|
305
|
+
const bMatch = key.match(/^(.+)<!B>$/);
|
|
306
|
+
// Check for <!A> (ArrayBuffer) tag
|
|
307
|
+
const aMatch = key.match(/^(.+)<!A>$/);
|
|
308
|
+
|
|
309
|
+
if (bMatch) {
|
|
310
|
+
// Remove <!B> tag, keep the value (hash for now, will be replaced with data)
|
|
311
|
+
cleaned[bMatch[1]] = obj[key];
|
|
312
|
+
} else if (aMatch) {
|
|
313
|
+
// Remove <!A> tag, keep the value
|
|
314
|
+
cleaned[aMatch[1]] = obj[key];
|
|
315
|
+
} else {
|
|
316
|
+
// No tag, recursively clean the value
|
|
317
|
+
cleaned[key] = cleanUploadTags(obj[key]);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return cleaned;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Sets a value at a dot-notation path in an object.
|
|
326
|
+
*
|
|
327
|
+
* Used to inject binary data into the cleaned message object after
|
|
328
|
+
* the upload has been received. Navigates to the specified path
|
|
329
|
+
* and sets the value.
|
|
330
|
+
*
|
|
331
|
+
* **Note**: This function mutates the original object.
|
|
332
|
+
*
|
|
333
|
+
* @function setValueAtPath
|
|
334
|
+
* @param {Object} obj - The object to modify
|
|
335
|
+
* @param {string} path - Dot-notation path (e.g., 'user.profile.avatar')
|
|
336
|
+
* @param {*} value - The value to set at the path
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* // Set a simple path
|
|
340
|
+
* const obj = { avatar: 'hash123' }
|
|
341
|
+
* setValueAtPath(obj, 'avatar', Buffer.from([1, 2, 3]))
|
|
342
|
+
* // obj is now: { avatar: <Buffer 01 02 03> }
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* // Set a nested path
|
|
346
|
+
* const obj = { user: { profile: { avatar: 'hash' } } }
|
|
347
|
+
* setValueAtPath(obj, 'user.profile.avatar', Buffer.from('image'))
|
|
348
|
+
* // obj.user.profile.avatar is now a Buffer
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* // Set a path in an array
|
|
352
|
+
* const obj = { files: [{ data: 'h1' }, { data: 'h2' }] }
|
|
353
|
+
* setValueAtPath(obj, 'files.0.data', Buffer.from('content1'))
|
|
354
|
+
* // obj.files[0].data is now a Buffer
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* // Usage in upload processing flow
|
|
358
|
+
* const message = cleanUploadTags({ "image<!B>": "hash123" })
|
|
359
|
+
* // message: { image: 'hash123' }
|
|
360
|
+
*
|
|
361
|
+
* // After receiving the binary upload...
|
|
362
|
+
* setValueAtPath(message, 'image', uploadedBuffer)
|
|
363
|
+
* // message: { image: <Buffer ...> }
|
|
364
|
+
*/
|
|
365
|
+
function setValueAtPath(obj, path, value) {
|
|
366
|
+
const parts = path.split(".");
|
|
367
|
+
let current = obj;
|
|
368
|
+
|
|
369
|
+
// Navigate to the parent of the target
|
|
370
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
371
|
+
current = current[parts[i]];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Set the value at the final key
|
|
375
|
+
current[parts[parts.length - 1]] = value;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
module.exports = {
|
|
379
|
+
/**
|
|
380
|
+
* Find all upload tags (<!B> and <!A>) in an object.
|
|
381
|
+
* @function
|
|
382
|
+
*/
|
|
383
|
+
findUploadTags,
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Find all file tags (<!F>) in an object.
|
|
387
|
+
* @function
|
|
388
|
+
*/
|
|
389
|
+
findFileTags,
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Clean upload tags from object keys.
|
|
393
|
+
* @function
|
|
394
|
+
*/
|
|
395
|
+
cleanUploadTags,
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Set a value at a dot-notation path.
|
|
399
|
+
* @function
|
|
400
|
+
*/
|
|
401
|
+
setValueAtPath,
|
|
402
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Server Utils Module
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Server-side utility functions used throughout the api-ape server implementation. These utilities provide essential functionality for controller loading, unique ID generation, and client identification via User-Agent parsing.
|
|
6
|
+
|
|
7
|
+
**Key capabilities:**
|
|
8
|
+
|
|
9
|
+
- **Controller loading** — Recursively load JavaScript modules and map file paths to API endpoints
|
|
10
|
+
- **ID generation** — Generate unique, URL-safe identifiers using Crockford Base32 encoding
|
|
11
|
+
- **User-Agent parsing** — Zero-dependency parsing for browser, OS, device, and bot detection
|
|
12
|
+
|
|
13
|
+
> **Contributing?** See [`files.md`](./files.md) for directory structure and file descriptions.
|
|
14
|
+
|
|
15
|
+
## See Also
|
|
16
|
+
|
|
17
|
+
- [`userAgent/README.md`](./userAgent/README.md) — User-agent detection patterns
|
|
18
|
+
- [`../lib/loader.js`](../lib/loader.js) — Controller loader using deepRequire
|
|
19
|
+
- [`../lib/wiring.js`](../lib/wiring.js) — Uses parseUserAgent for client info
|