wao 0.27.0 → 0.27.2
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/cjs/encode.js +1452 -792
- package/cjs/toerl.js +173 -0
- package/esm/encode.js +1340 -510
- package/esm/toerl.js +162 -0
- package/package.json +1 -1
package/esm/encode.js
CHANGED
|
@@ -5,7 +5,11 @@ function isBytes(value) {
|
|
|
5
5
|
return (
|
|
6
6
|
value instanceof ArrayBuffer ||
|
|
7
7
|
ArrayBuffer.isView(value) ||
|
|
8
|
-
Buffer.isBuffer(value)
|
|
8
|
+
Buffer.isBuffer(value) ||
|
|
9
|
+
(value &&
|
|
10
|
+
typeof value === "object" &&
|
|
11
|
+
value.type === "Buffer" &&
|
|
12
|
+
Array.isArray(value.data))
|
|
9
13
|
)
|
|
10
14
|
}
|
|
11
15
|
|
|
@@ -19,99 +23,8 @@ function isPojo(value) {
|
|
|
19
23
|
)
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
function hbEncodeValue(value) {
|
|
23
|
-
if (isBytes(value)) {
|
|
24
|
-
const length = value.byteLength || value.length || 0
|
|
25
|
-
if (length === 0) return ["empty-binary", ""]
|
|
26
|
-
return [undefined, value]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (typeof value === "string") {
|
|
30
|
-
if (value.length === 0) return ["empty-string", ""]
|
|
31
|
-
return [undefined, value]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (Array.isArray(value)) {
|
|
35
|
-
if (value.length === 0) return ["empty-list", "[]"]
|
|
36
|
-
|
|
37
|
-
const hasObjects = value.some(item => isPojo(item))
|
|
38
|
-
if (hasObjects) {
|
|
39
|
-
return ["list_with_objects", value]
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const items = value.map(item => {
|
|
43
|
-
if (typeof item === "string") {
|
|
44
|
-
return `"${item}"`
|
|
45
|
-
} else if (typeof item === "number") {
|
|
46
|
-
if (Number.isInteger(item)) {
|
|
47
|
-
return `"(ao-type-integer) ${item}"`
|
|
48
|
-
} else {
|
|
49
|
-
return `"(ao-type-float) ${item.toExponential(20).replace("e+0", "e+00")}"`
|
|
50
|
-
}
|
|
51
|
-
} else if (typeof item === "boolean") {
|
|
52
|
-
return `"(ao-type-atom) \\"${item ? "true" : "false"}\\""`
|
|
53
|
-
} else if (typeof item === "symbol") {
|
|
54
|
-
const desc = item.description || "symbol"
|
|
55
|
-
return `"(ao-type-atom) \\"${desc}\\""`
|
|
56
|
-
} else if (item === null) {
|
|
57
|
-
return `"(ao-type-atom) \\"null\\""`
|
|
58
|
-
} else if (item === undefined) {
|
|
59
|
-
return `"(ao-type-atom) \\"undefined\\""`
|
|
60
|
-
} else if (isBytes(item)) {
|
|
61
|
-
const length = item.byteLength || item.length || 0
|
|
62
|
-
if (length === 0) {
|
|
63
|
-
return `""`
|
|
64
|
-
} else {
|
|
65
|
-
const base64 = base64url.encode(Buffer.from(item))
|
|
66
|
-
return `"(ao-type-binary) ${base64}"`
|
|
67
|
-
}
|
|
68
|
-
} else if (Array.isArray(item)) {
|
|
69
|
-
const [, encoded] = hbEncodeValue(item)
|
|
70
|
-
const escapedEncoded = encoded.replace(/"/g, '\\"')
|
|
71
|
-
return `"(ao-type-list) ${escapedEncoded}"`
|
|
72
|
-
} else {
|
|
73
|
-
return `"${String(item)}"`
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
return ["list", items.join(", ")]
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (typeof value === "number") {
|
|
81
|
-
if (!Number.isInteger(value)) return ["float", `${value}`]
|
|
82
|
-
return ["integer", String(value)]
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (typeof value === "boolean") {
|
|
86
|
-
return ["atom", value ? "true" : "false"]
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (typeof value === "symbol") {
|
|
90
|
-
const desc = value.description || "symbol"
|
|
91
|
-
return ["atom", desc]
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (value === null) return ["atom", "null"]
|
|
95
|
-
if (value === undefined) return ["atom", "undefined"]
|
|
96
|
-
|
|
97
|
-
if (isPojo(value)) {
|
|
98
|
-
throw new Error("Objects must be lifted")
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
throw new Error(`Cannot encode value: ${String(value)}`)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
26
|
const MAX_HEADER_LENGTH = 4096
|
|
105
27
|
|
|
106
|
-
function encode_body_keys(bodyKeys) {
|
|
107
|
-
if (!bodyKeys || bodyKeys.length === 0) return ""
|
|
108
|
-
const items = bodyKeys.map(key => {
|
|
109
|
-
const escaped = key.replace(/"/g, '\\"')
|
|
110
|
-
return `"${escaped}"`
|
|
111
|
-
})
|
|
112
|
-
return items.join(", ")
|
|
113
|
-
}
|
|
114
|
-
|
|
115
28
|
async function hasNewline(value) {
|
|
116
29
|
if (typeof value === "string") return value.includes("\n")
|
|
117
30
|
if (value instanceof Blob) {
|
|
@@ -141,157 +54,706 @@ async function sha256(data) {
|
|
|
141
54
|
)
|
|
142
55
|
}
|
|
143
56
|
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
57
|
+
function formatFloat(num) {
|
|
58
|
+
let exp = num.toExponential(20)
|
|
59
|
+
exp = exp.replace(/e\+(\d)$/, "e+0$1")
|
|
60
|
+
exp = exp.replace(/e-(\d)$/, "e-0$1")
|
|
61
|
+
return exp
|
|
62
|
+
}
|
|
147
63
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
64
|
+
function hasNonAscii(str) {
|
|
65
|
+
return /[^\x00-\x7F]/.test(str)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function encodeArrayItem(item) {
|
|
69
|
+
if (typeof item === "number") {
|
|
70
|
+
if (Number.isInteger(item)) {
|
|
71
|
+
return `"(ao-type-integer) ${item}"`
|
|
72
|
+
} else {
|
|
73
|
+
return `"(ao-type-float) ${formatFloat(item)}"`
|
|
74
|
+
}
|
|
75
|
+
} else if (typeof item === "string") {
|
|
76
|
+
return `"${item}"`
|
|
77
|
+
} else if (item === null) {
|
|
78
|
+
return `"(ao-type-atom) \\"null\\""`
|
|
79
|
+
} else if (item === undefined) {
|
|
80
|
+
return `"(ao-type-atom) \\"undefined\\""`
|
|
81
|
+
} else if (typeof item === "symbol") {
|
|
82
|
+
const desc = item.description || "Symbol.for()"
|
|
83
|
+
return `"(ao-type-atom) \\"${desc}\\""`
|
|
84
|
+
} else if (typeof item === "boolean") {
|
|
85
|
+
return `"(ao-type-atom) \\"${item}\\""`
|
|
86
|
+
} else if (Array.isArray(item)) {
|
|
87
|
+
const nestedItems = item
|
|
88
|
+
.map(nestedItem => {
|
|
89
|
+
if (typeof nestedItem === "number") {
|
|
90
|
+
if (Number.isInteger(nestedItem)) {
|
|
91
|
+
return `\\"(ao-type-integer) ${nestedItem}\\"`
|
|
92
|
+
} else {
|
|
93
|
+
return `\\"(ao-type-float) ${formatFloat(nestedItem)}\\"`
|
|
94
|
+
}
|
|
95
|
+
} else if (typeof nestedItem === "string") {
|
|
96
|
+
return `\\"${nestedItem}\\"`
|
|
97
|
+
} else if (nestedItem === null) {
|
|
98
|
+
return `\\"(ao-type-atom) \\\\\\"null\\\\\\"\\"`
|
|
99
|
+
} else if (nestedItem === undefined) {
|
|
100
|
+
return `\\"(ao-type-atom) \\\\\\"undefined\\\\\\"\\"`
|
|
101
|
+
} else if (typeof nestedItem === "symbol") {
|
|
102
|
+
const desc = nestedItem.description || "Symbol.for()"
|
|
103
|
+
return `\\"(ao-type-atom) \\\\\\"${desc}\\\\\\"\\"`
|
|
104
|
+
} else if (typeof nestedItem === "boolean") {
|
|
105
|
+
return `\\"(ao-type-atom) \\\\\\"${nestedItem}\\\\\\"\\"`
|
|
106
|
+
} else if (Array.isArray(nestedItem)) {
|
|
107
|
+
// Handle nested arrays recursively
|
|
108
|
+
const deeperItems = nestedItem
|
|
109
|
+
.map(deepItem => {
|
|
110
|
+
if (typeof deepItem === "number") {
|
|
111
|
+
if (Number.isInteger(deepItem)) {
|
|
112
|
+
return `\\\\\\"(ao-type-integer) ${deepItem}\\\\\\"`
|
|
113
|
+
} else {
|
|
114
|
+
return `\\\\\\"(ao-type-float) ${formatFloat(deepItem)}\\\\\\"`
|
|
115
|
+
}
|
|
116
|
+
} else if (typeof deepItem === "string") {
|
|
117
|
+
return `\\\\\\"${deepItem}\\\\\\"`
|
|
118
|
+
} else if (Array.isArray(deepItem)) {
|
|
119
|
+
// Even deeper nesting - need to escape more
|
|
120
|
+
const deepestItems = deepItem
|
|
121
|
+
.map(deepestItem => {
|
|
122
|
+
if (typeof deepestItem === "number") {
|
|
123
|
+
if (Number.isInteger(deepestItem)) {
|
|
124
|
+
return `\\\\\\\\\\\\\\"(ao-type-integer) ${deepestItem}\\\\\\\\\\\\\\"`
|
|
125
|
+
} else {
|
|
126
|
+
return `\\\\\\\\\\\\\\"(ao-type-float) ${formatFloat(deepestItem)}\\\\\\\\\\\\\\"`
|
|
127
|
+
}
|
|
128
|
+
} else if (typeof deepestItem === "string") {
|
|
129
|
+
return `\\\\\\\\\\\\\\"${deepestItem}\\\\\\\\\\\\\\"`
|
|
130
|
+
} else {
|
|
131
|
+
return `\\\\\\\\\\\\\\"${String(deepestItem)}\\\\\\\\\\\\\\"`
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
.join(", ")
|
|
135
|
+
return `\\\\\\"(ao-type-list) ${deepestItems}\\\\\\"`
|
|
136
|
+
} else if (deepItem === null) {
|
|
137
|
+
return `\\\\\\"(ao-type-atom) \\\\\\\\\\\\\\"null\\\\\\\\\\\\\\"\\\\\\"`
|
|
138
|
+
} else if (deepItem === undefined) {
|
|
139
|
+
return `\\\\\\"(ao-type-atom) \\\\\\\\\\\\\\"undefined\\\\\\\\\\\\\\"\\\\\\"`
|
|
140
|
+
} else if (typeof deepItem === "symbol") {
|
|
141
|
+
const desc = deepItem.description || "Symbol.for()"
|
|
142
|
+
return `\\\\\\"(ao-type-atom) \\\\\\\\\\\\\\"${desc}\\\\\\\\\\\\\\"\\\\\\"`
|
|
143
|
+
} else if (typeof deepItem === "boolean") {
|
|
144
|
+
return `\\\\\\"(ao-type-atom) \\\\\\\\\\\\\\"${deepItem}\\\\\\\\\\\\\\"\\\\\\"`
|
|
145
|
+
} else {
|
|
146
|
+
return `\\\\\\"${String(deepItem)}\\\\\\"`
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
.join(", ")
|
|
150
|
+
return `\\"(ao-type-list) ${deeperItems}\\"`
|
|
151
|
+
} else {
|
|
152
|
+
return `\\"${String(nestedItem)}\\"`
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
.join(", ")
|
|
156
|
+
return `"(ao-type-list) ${nestedItems}"`
|
|
157
|
+
} else if (isBytes(item)) {
|
|
158
|
+
const buffer = toBuffer(item)
|
|
159
|
+
if (buffer.length === 0 || buffer.byteLength === 0) {
|
|
160
|
+
return `""`
|
|
161
|
+
}
|
|
162
|
+
return `"(ao-type-binary)"`
|
|
163
|
+
} else if (isPojo(item)) {
|
|
164
|
+
const json = JSON.stringify(item)
|
|
165
|
+
const escaped = json.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
|
166
|
+
return `"(ao-type-map) ${escaped}"`
|
|
167
|
+
} else {
|
|
168
|
+
return `"${String(item)}"`
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function toBuffer(value) {
|
|
173
|
+
if (Buffer.isBuffer(value)) {
|
|
174
|
+
return value
|
|
175
|
+
} else if (
|
|
176
|
+
value &&
|
|
177
|
+
typeof value === "object" &&
|
|
178
|
+
value.type === "Buffer" &&
|
|
179
|
+
Array.isArray(value.data)
|
|
180
|
+
) {
|
|
181
|
+
return Buffer.from(value.data)
|
|
182
|
+
} else if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
|
|
183
|
+
return Buffer.from(value)
|
|
184
|
+
} else {
|
|
185
|
+
return Buffer.from(value)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function collectBodyKeys(obj, prefix = "") {
|
|
190
|
+
console.log("=== collectBodyKeys START ===")
|
|
191
|
+
console.log("Input object:", JSON.stringify(obj))
|
|
192
|
+
|
|
193
|
+
const keys = []
|
|
194
|
+
|
|
195
|
+
function traverse(current, path) {
|
|
196
|
+
console.log(`[traverse] Called with path: "${path}"`)
|
|
197
|
+
let hasSimpleFields = false
|
|
198
|
+
const nestedPaths = []
|
|
199
|
+
let hasArraysWithObjects = false
|
|
200
|
+
|
|
201
|
+
for (const [key, value] of Object.entries(current)) {
|
|
202
|
+
const fullPath = path ? `${path}/${key}` : key
|
|
203
|
+
|
|
204
|
+
if (Array.isArray(value)) {
|
|
205
|
+
console.log(
|
|
206
|
+
`[traverse] Found array at ${fullPath}, length: ${value.length}`
|
|
207
|
+
)
|
|
208
|
+
const hasObjects = value.some(item => isPojo(item))
|
|
209
|
+
const hasNonObjects = value.some(item => !isPojo(item))
|
|
210
|
+
|
|
211
|
+
if (value.length === 0) {
|
|
212
|
+
console.log(
|
|
213
|
+
`[traverse] Empty array at ${fullPath} - marking parent as having simple fields`
|
|
214
|
+
)
|
|
215
|
+
hasSimpleFields = true
|
|
216
|
+
} else if (hasObjects) {
|
|
217
|
+
hasArraysWithObjects = true
|
|
218
|
+
// Check if we need special handling for mixed arrays
|
|
219
|
+
const hasEmptyStrings = value.some(
|
|
220
|
+
item => typeof item === "string" && item === ""
|
|
221
|
+
)
|
|
222
|
+
const hasEmptyObjects = value.some(
|
|
223
|
+
item => isPojo(item) && Object.keys(item).length === 0
|
|
224
|
+
)
|
|
225
|
+
const hasNonEmptyObjects = value.some(
|
|
226
|
+
item => isPojo(item) && Object.keys(item).length > 0
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
// Check if objects contain only empty values (not empty objects)
|
|
230
|
+
const hasObjectsWithOnlyEmptyValues = value.some(item => {
|
|
231
|
+
if (!isPojo(item) || Object.keys(item).length === 0) return false
|
|
232
|
+
return Object.values(item).every(
|
|
233
|
+
v =>
|
|
234
|
+
(typeof v === "string" && v === "") ||
|
|
235
|
+
(Array.isArray(v) && v.length === 0) ||
|
|
236
|
+
(isPojo(v) && Object.keys(v).length === 0)
|
|
237
|
+
)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
// Only use special handling if we have BOTH empty elements AND non-empty objects
|
|
241
|
+
if ((hasEmptyStrings || hasEmptyObjects) && hasNonEmptyObjects) {
|
|
242
|
+
// Special case: mixed array with empty strings/objects - only non-empty objects get parts
|
|
243
|
+
value.forEach((item, index) => {
|
|
244
|
+
if (isPojo(item) && Object.keys(item).length > 0) {
|
|
245
|
+
const itemPath = `${fullPath}/${index + 1}`
|
|
246
|
+
keys.push(itemPath)
|
|
247
|
+
nestedPaths.push(itemPath)
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
if (hasNonObjects) {
|
|
251
|
+
hasSimpleFields = true
|
|
252
|
+
keys.push(fullPath)
|
|
253
|
+
}
|
|
254
|
+
} else if (hasObjectsWithOnlyEmptyValues && !hasNonObjects) {
|
|
255
|
+
// Special case: objects that contain only empty values should get parts
|
|
256
|
+
value.forEach((item, index) => {
|
|
257
|
+
if (isPojo(item)) {
|
|
258
|
+
const itemPath = `${fullPath}/${index + 1}`
|
|
259
|
+
keys.push(itemPath)
|
|
260
|
+
if (Object.keys(item).length > 0) {
|
|
261
|
+
nestedPaths.push(itemPath)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
} else {
|
|
266
|
+
// Normal case: all objects get parts
|
|
267
|
+
value.forEach((item, index) => {
|
|
268
|
+
if (isPojo(item)) {
|
|
269
|
+
const itemPath = `${fullPath}/${index + 1}`
|
|
270
|
+
keys.push(itemPath)
|
|
271
|
+
if (Object.keys(item).length > 0) {
|
|
272
|
+
nestedPaths.push(itemPath)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
if (hasNonObjects) {
|
|
277
|
+
hasSimpleFields = true
|
|
278
|
+
keys.push(fullPath)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
console.log(
|
|
283
|
+
`[traverse] Non-empty array without objects at ${fullPath} - marking as simple field`
|
|
284
|
+
)
|
|
285
|
+
hasSimpleFields = true
|
|
286
|
+
}
|
|
287
|
+
} else if (isPojo(value)) {
|
|
288
|
+
if (Object.keys(value).length === 0) {
|
|
289
|
+
console.log(
|
|
290
|
+
`[traverse] Empty object at ${fullPath} - marking parent as having simple fields`
|
|
291
|
+
)
|
|
292
|
+
hasSimpleFields = true
|
|
293
|
+
} else {
|
|
294
|
+
// Don't traverse into the object if it only contains empty values
|
|
295
|
+
const containsOnlyEmptyCollections = Object.entries(value).every(
|
|
296
|
+
([k, v]) => {
|
|
297
|
+
return (
|
|
298
|
+
(Array.isArray(v) && v.length === 0) ||
|
|
299
|
+
(isPojo(v) && Object.keys(v).length === 0) ||
|
|
300
|
+
(isBytes(v) && (v.length === 0 || v.byteLength === 0)) ||
|
|
301
|
+
(typeof v === "string" && v.length === 0)
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
if (containsOnlyEmptyCollections && Object.keys(value).length > 0) {
|
|
307
|
+
console.log(
|
|
308
|
+
`[traverse] Object at ${fullPath} contains only empty collections - adding as body key`
|
|
309
|
+
)
|
|
310
|
+
keys.push(fullPath)
|
|
311
|
+
} else {
|
|
312
|
+
// Check if this object contains arrays with only empty elements
|
|
313
|
+
const hasArraysWithOnlyEmptyElements = Object.entries(value).some(
|
|
314
|
+
([k, v]) => {
|
|
315
|
+
return (
|
|
316
|
+
Array.isArray(v) &&
|
|
317
|
+
v.length > 0 &&
|
|
318
|
+
v.every(
|
|
319
|
+
item =>
|
|
320
|
+
(Array.isArray(item) && item.length === 0) ||
|
|
321
|
+
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
322
|
+
(typeof item === "string" && item === "")
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
if (hasArraysWithOnlyEmptyElements) {
|
|
329
|
+
// This object needs a body part to show its array types
|
|
330
|
+
console.log(
|
|
331
|
+
`[traverse] Object at ${fullPath} has arrays with empty elements - adding as body key`
|
|
332
|
+
)
|
|
333
|
+
keys.push(fullPath)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
console.log(
|
|
337
|
+
`[traverse] Non-empty object at ${fullPath} - will traverse into it`
|
|
338
|
+
)
|
|
339
|
+
nestedPaths.push(fullPath)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
} else if (isBytes(value)) {
|
|
343
|
+
const buffer = toBuffer(value)
|
|
344
|
+
if (buffer.length > 0) {
|
|
345
|
+
hasSimpleFields = true
|
|
346
|
+
}
|
|
347
|
+
} else if (
|
|
348
|
+
typeof value === "string" ||
|
|
349
|
+
typeof value === "number" ||
|
|
350
|
+
typeof value === "boolean" ||
|
|
351
|
+
value === null ||
|
|
352
|
+
value === undefined ||
|
|
353
|
+
typeof value === "symbol"
|
|
354
|
+
) {
|
|
355
|
+
hasSimpleFields = true
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (hasSimpleFields) {
|
|
360
|
+
console.log(`[traverse] Adding "${path}" to keys (has simple fields)`)
|
|
361
|
+
keys.push(path)
|
|
362
|
+
} else if (hasArraysWithObjects && path) {
|
|
363
|
+
// If the object only contains arrays with objects, we still need to add it as a body key
|
|
364
|
+
console.log(
|
|
365
|
+
`[traverse] Adding "${path}" to keys (contains arrays with objects)`
|
|
366
|
+
)
|
|
367
|
+
keys.push(path)
|
|
368
|
+
}
|
|
155
369
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
370
|
+
// Check for arrays with only empty elements that need their own body parts
|
|
371
|
+
for (const [key, value] of Object.entries(current)) {
|
|
372
|
+
const fullPath = path ? `${path}/${key}` : key
|
|
373
|
+
|
|
374
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
375
|
+
const hasOnlyEmptyElements = value.every(
|
|
376
|
+
item =>
|
|
377
|
+
(Array.isArray(item) && item.length === 0) ||
|
|
378
|
+
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
379
|
+
(typeof item === "string" && item === "")
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
if (hasOnlyEmptyElements) {
|
|
383
|
+
console.log(
|
|
384
|
+
`[traverse] Array at ${fullPath} has only empty elements - adding as body key`
|
|
385
|
+
)
|
|
386
|
+
keys.push(fullPath)
|
|
387
|
+
}
|
|
159
388
|
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
for (const nestedPath of nestedPaths) {
|
|
392
|
+
const parts = nestedPath.split("/")
|
|
393
|
+
let nestedObj = obj
|
|
394
|
+
|
|
395
|
+
for (const part of parts) {
|
|
396
|
+
if (/^\d+$/.test(part)) {
|
|
397
|
+
nestedObj = nestedObj[parseInt(part) - 1]
|
|
398
|
+
} else {
|
|
399
|
+
nestedObj = nestedObj[part]
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (isPojo(nestedObj)) {
|
|
404
|
+
traverse(nestedObj, nestedPath)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const objKeys = Object.keys(obj)
|
|
410
|
+
|
|
411
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
412
|
+
console.log(`\n[main loop] Processing key: "${key}"`)
|
|
413
|
+
console.log(
|
|
414
|
+
`[main loop] Value type: ${Array.isArray(value) ? "array" : typeof value}`
|
|
415
|
+
)
|
|
416
|
+
console.log(
|
|
417
|
+
`[main loop] Array length: ${Array.isArray(value) ? value.length : "N/A"}`
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
if (
|
|
421
|
+
(key === "data" || key === "body") &&
|
|
422
|
+
(typeof value === "string" ||
|
|
423
|
+
typeof value === "boolean" ||
|
|
424
|
+
typeof value === "number" ||
|
|
425
|
+
value === null ||
|
|
426
|
+
value === undefined ||
|
|
427
|
+
typeof value === "symbol") &&
|
|
428
|
+
objKeys.length > 1
|
|
429
|
+
) {
|
|
430
|
+
// Special handling: only add to body keys if there's no other data/body field with an object
|
|
431
|
+
if (
|
|
432
|
+
key === "data" &&
|
|
433
|
+
obj.body &&
|
|
434
|
+
isPojo(obj.body) &&
|
|
435
|
+
Object.keys(obj.body).length > 0
|
|
436
|
+
) {
|
|
437
|
+
console.log(`[main loop] Skipping special data field`)
|
|
438
|
+
} else if (
|
|
439
|
+
key === "body" &&
|
|
440
|
+
obj.data &&
|
|
441
|
+
isPojo(obj.data) &&
|
|
442
|
+
Object.keys(obj.data).length > 0
|
|
443
|
+
) {
|
|
444
|
+
console.log(`[main loop] Skipping special body field`)
|
|
165
445
|
} else {
|
|
166
|
-
|
|
167
|
-
|
|
446
|
+
console.log(`[main loop] Adding special data/body key: "${key}"`)
|
|
447
|
+
keys.push(key)
|
|
168
448
|
}
|
|
169
449
|
} else if (Array.isArray(value)) {
|
|
170
450
|
if (value.length === 0) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
451
|
+
console.log(`[main loop] SKIPPING empty array for key: "${key}"`)
|
|
452
|
+
continue
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const hasObjects = value.some(item => isPojo(item))
|
|
456
|
+
const hasArrays = value.some(item => Array.isArray(item))
|
|
457
|
+
const hasNonObjects = value.some(item => !isPojo(item))
|
|
176
458
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
459
|
+
// Check if this is an array of arrays containing objects
|
|
460
|
+
const hasArraysOfObjects = value.some(
|
|
461
|
+
item => Array.isArray(item) && item.some(subItem => isPojo(subItem))
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
console.log(
|
|
465
|
+
`[main loop] Array analysis: hasObjects=${hasObjects}, hasArrays=${hasArrays}, hasNonObjects=${hasNonObjects}, hasArraysOfObjects=${hasArraysOfObjects}`
|
|
466
|
+
)
|
|
180
467
|
|
|
468
|
+
if (value.length > 0) {
|
|
469
|
+
let bodyPartCounter = 1 // Start counting from 1
|
|
470
|
+
|
|
471
|
+
// Check for special mixed array case
|
|
472
|
+
const hasEmptyStrings = value.some(
|
|
473
|
+
item => typeof item === "string" && item === ""
|
|
474
|
+
)
|
|
475
|
+
const hasEmptyObjects = value.some(
|
|
476
|
+
item => isPojo(item) && Object.keys(item).length === 0
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
// Check for objects that contain only empty values
|
|
480
|
+
const hasObjectsWithOnlyEmptyValues = value.some(item => {
|
|
481
|
+
if (!isPojo(item) || Object.keys(item).length === 0) return false
|
|
482
|
+
return Object.values(item).every(
|
|
483
|
+
v =>
|
|
484
|
+
(typeof v === "string" && v === "") ||
|
|
485
|
+
(Array.isArray(v) && v.length === 0) ||
|
|
486
|
+
(isPojo(v) && Object.keys(v).length === 0)
|
|
487
|
+
)
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
if (hasArraysOfObjects) {
|
|
491
|
+
// Handle arrays of arrays containing objects
|
|
181
492
|
value.forEach((item, index) => {
|
|
182
|
-
|
|
183
|
-
|
|
493
|
+
if (Array.isArray(item)) {
|
|
494
|
+
item.forEach((subItem, subIndex) => {
|
|
495
|
+
if (isPojo(subItem)) {
|
|
496
|
+
const path = `${key}/${index + 1}/${subIndex + 1}`
|
|
497
|
+
console.log(
|
|
498
|
+
`[main loop] Adding nested object path: "${path}"`
|
|
499
|
+
)
|
|
500
|
+
keys.push(path)
|
|
501
|
+
}
|
|
502
|
+
})
|
|
503
|
+
}
|
|
504
|
+
bodyPartCounter++
|
|
505
|
+
})
|
|
506
|
+
// Always add the main array key
|
|
507
|
+
console.log(`[main loop] ADDING main array key: "${key}"`)
|
|
508
|
+
keys.push(key)
|
|
509
|
+
} else if (
|
|
510
|
+
hasObjects &&
|
|
511
|
+
(hasEmptyStrings || hasEmptyObjects) &&
|
|
512
|
+
!hasObjectsWithOnlyEmptyValues
|
|
513
|
+
) {
|
|
514
|
+
// Special handling: only non-empty objects get parts
|
|
515
|
+
value.forEach((item, index) => {
|
|
516
|
+
if (isPojo(item) && Object.keys(item).length > 0) {
|
|
517
|
+
const path = `${key}/${bodyPartCounter}`
|
|
518
|
+
console.log(
|
|
519
|
+
`[main loop] Adding non-empty object path: "${path}" (array index ${index})`
|
|
520
|
+
)
|
|
521
|
+
keys.push(path)
|
|
522
|
+
// Add paths for nested objects
|
|
523
|
+
for (const [nestedKey, nestedValue] of Object.entries(item)) {
|
|
524
|
+
if (isPojo(nestedValue)) {
|
|
525
|
+
const nestedPath = `${key}/${bodyPartCounter}/${nestedKey}`
|
|
526
|
+
console.log(
|
|
527
|
+
`[main loop] Adding nested object path: "${nestedPath}"`
|
|
528
|
+
)
|
|
529
|
+
keys.push(nestedPath)
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
bodyPartCounter++
|
|
534
|
+
})
|
|
535
|
+
// Always add the main array key
|
|
536
|
+
console.log(`[main loop] ADDING main array key: "${key}"`)
|
|
537
|
+
keys.push(key)
|
|
538
|
+
} else if (hasObjects) {
|
|
539
|
+
// Normal handling: all objects get parts (except if parent array has only empty elements)
|
|
540
|
+
let skipEmptyObjects = false
|
|
541
|
+
|
|
542
|
+
// Check if this array contains only empty elements
|
|
543
|
+
const arrayHasOnlyEmptyElements = value.every(
|
|
544
|
+
item =>
|
|
545
|
+
(Array.isArray(item) && item.length === 0) ||
|
|
546
|
+
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
547
|
+
(typeof item === "string" && item === "")
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
if (arrayHasOnlyEmptyElements) {
|
|
551
|
+
skipEmptyObjects = true
|
|
552
|
+
}
|
|
184
553
|
|
|
554
|
+
value.forEach((item, index) => {
|
|
185
555
|
if (isPojo(item)) {
|
|
186
|
-
//
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
parts[indexPath][objKey] = objValue
|
|
191
|
-
|
|
192
|
-
// Set types for object fields
|
|
193
|
-
const fieldPath = `${indexPath}/${objKey}`
|
|
194
|
-
if (typeof objValue === "number") {
|
|
195
|
-
types[fieldPath] = Number.isInteger(objValue)
|
|
196
|
-
? "integer"
|
|
197
|
-
: "float"
|
|
198
|
-
} else if (typeof objValue === "boolean") {
|
|
199
|
-
types[fieldPath] = "atom"
|
|
200
|
-
} else if (typeof objValue === "symbol") {
|
|
201
|
-
types[fieldPath] = "atom"
|
|
202
|
-
// Store the symbol's description for later use
|
|
203
|
-
parts[indexPath][objKey] = objValue
|
|
204
|
-
} else if (
|
|
205
|
-
typeof objValue === "string" &&
|
|
206
|
-
objValue.length === 0
|
|
207
|
-
) {
|
|
208
|
-
types[fieldPath] = "empty-binary"
|
|
209
|
-
} else if (Array.isArray(objValue) && objValue.length === 0) {
|
|
210
|
-
types[fieldPath] = "empty-list"
|
|
211
|
-
} else if (
|
|
212
|
-
isPojo(objValue) &&
|
|
213
|
-
Object.keys(objValue).length === 0
|
|
214
|
-
) {
|
|
215
|
-
types[fieldPath] = "empty-message"
|
|
216
|
-
}
|
|
556
|
+
// Skip empty objects if array has only empty elements
|
|
557
|
+
if (skipEmptyObjects && Object.keys(item).length === 0) {
|
|
558
|
+
bodyPartCounter++
|
|
559
|
+
return
|
|
217
560
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
561
|
+
|
|
562
|
+
const path = `${key}/${bodyPartCounter}`
|
|
563
|
+
console.log(
|
|
564
|
+
`[main loop] Adding object path: "${path}" (array index ${index}, empty=${Object.keys(item).length === 0})`
|
|
565
|
+
)
|
|
566
|
+
keys.push(path)
|
|
567
|
+
// Add paths for nested objects (but not empty ones)
|
|
568
|
+
if (Object.keys(item).length > 0) {
|
|
569
|
+
for (const [nestedKey, nestedValue] of Object.entries(item)) {
|
|
570
|
+
if (
|
|
571
|
+
isPojo(nestedValue) &&
|
|
572
|
+
Object.keys(nestedValue).length > 0
|
|
573
|
+
) {
|
|
574
|
+
const nestedPath = `${key}/${bodyPartCounter}/${nestedKey}`
|
|
575
|
+
console.log(
|
|
576
|
+
`[main loop] Adding nested object path: "${nestedPath}"`
|
|
577
|
+
)
|
|
578
|
+
keys.push(nestedPath)
|
|
579
|
+
}
|
|
580
|
+
}
|
|
229
581
|
}
|
|
582
|
+
} else if (typeof item === "string" && item === "") {
|
|
583
|
+
// Empty strings may get parts in some formats
|
|
584
|
+
const path = `${key}/${bodyPartCounter}`
|
|
585
|
+
console.log(
|
|
586
|
+
`[main loop] Adding empty string path: "${path}" (array index ${index})`
|
|
587
|
+
)
|
|
588
|
+
keys.push(path)
|
|
230
589
|
}
|
|
590
|
+
bodyPartCounter++
|
|
231
591
|
})
|
|
592
|
+
// Don't add main array key for arrays with only objects containing empty values
|
|
593
|
+
if (
|
|
594
|
+
!hasObjectsWithOnlyEmptyValues ||
|
|
595
|
+
value.some(item => !isPojo(item))
|
|
596
|
+
) {
|
|
597
|
+
// Check if array has only empty elements
|
|
598
|
+
const hasOnlyEmptyElements = value.every(
|
|
599
|
+
item =>
|
|
600
|
+
(Array.isArray(item) && item.length === 0) ||
|
|
601
|
+
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
602
|
+
(typeof item === "string" && item === "")
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
if (!hasOnlyEmptyElements) {
|
|
606
|
+
// Always add the main array key
|
|
607
|
+
console.log(`[main loop] ADDING main array key: "${key}"`)
|
|
608
|
+
keys.push(key)
|
|
609
|
+
}
|
|
610
|
+
}
|
|
232
611
|
} else {
|
|
233
|
-
//
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
612
|
+
// Check if array has only empty elements
|
|
613
|
+
const hasOnlyEmptyArraysOrObjects = value.every(
|
|
614
|
+
item =>
|
|
615
|
+
(Array.isArray(item) && item.length === 0) ||
|
|
616
|
+
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
617
|
+
(typeof item === "string" && item === "")
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
if (hasOnlyEmptyArraysOrObjects && value.length > 0) {
|
|
621
|
+
// Always add the main array key for arrays with only empty elements
|
|
622
|
+
console.log(
|
|
623
|
+
`[main loop] ADDING main array key for empty elements: "${key}"`
|
|
624
|
+
)
|
|
625
|
+
keys.push(key)
|
|
626
|
+
} else if (!hasOnlyEmptyArraysOrObjects) {
|
|
627
|
+
// Always add the main array key
|
|
628
|
+
console.log(`[main loop] ADDING main array key: "${key}"`)
|
|
629
|
+
keys.push(key)
|
|
630
|
+
}
|
|
238
631
|
}
|
|
239
632
|
}
|
|
240
633
|
} else if (isPojo(value)) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
634
|
+
console.log(`[main loop] Processing object at key: "${key}"`)
|
|
635
|
+
// Objects should be traversed, not have their fields individually added
|
|
636
|
+
traverse(value, key)
|
|
637
|
+
} else if (isBytes(value)) {
|
|
638
|
+
const buffer = toBuffer(value)
|
|
639
|
+
if (buffer.length > 0) {
|
|
640
|
+
console.log(`[main loop] Adding key for non-empty bytes: "${key}"`)
|
|
641
|
+
keys.push(key)
|
|
642
|
+
}
|
|
643
|
+
} else if (typeof value === "string" && value.includes("\n")) {
|
|
644
|
+
console.log(`[main loop] Adding key for string with newline: "${key}"`)
|
|
645
|
+
keys.push(key)
|
|
646
|
+
} else if (typeof value === "string" && hasNonAscii(value)) {
|
|
647
|
+
console.log(`[main loop] Adding key for non-ASCII string: "${key}"`)
|
|
648
|
+
keys.push(key)
|
|
649
|
+
} else {
|
|
650
|
+
console.log(`[main loop] Skipping key: "${key}" (no match)`)
|
|
651
|
+
}
|
|
652
|
+
}
|
|
252
653
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
654
|
+
const result = [...new Set(keys)].filter(k => {
|
|
655
|
+
if (k === "") return false
|
|
256
656
|
|
|
257
|
-
|
|
657
|
+
// Check if this is a path to an empty object inside an array with only empty elements
|
|
658
|
+
const parts = k.split("/")
|
|
659
|
+
if (parts.length >= 2 && /^\d+$/.test(parts[parts.length - 1])) {
|
|
660
|
+
// This is an array element path like "maps/1"
|
|
661
|
+
const arrayPath = parts.slice(0, -1).join("/")
|
|
662
|
+
let arrayValue = obj
|
|
663
|
+
|
|
664
|
+
// Navigate to the array
|
|
665
|
+
for (const part of parts.slice(0, -1)) {
|
|
666
|
+
if (/^\d+$/.test(part)) {
|
|
667
|
+
arrayValue = arrayValue[parseInt(part) - 1]
|
|
668
|
+
} else {
|
|
669
|
+
arrayValue = arrayValue[part]
|
|
670
|
+
}
|
|
258
671
|
}
|
|
259
|
-
|
|
260
|
-
if
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
672
|
+
|
|
673
|
+
// Check if this array contains only empty elements
|
|
674
|
+
if (Array.isArray(arrayValue)) {
|
|
675
|
+
const hasOnlyEmptyElements = arrayValue.every(
|
|
676
|
+
item =>
|
|
677
|
+
(Array.isArray(item) && item.length === 0) ||
|
|
678
|
+
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
679
|
+
(typeof item === "string" && item === "")
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
if (hasOnlyEmptyElements) {
|
|
683
|
+
// Filter out paths to individual empty elements
|
|
684
|
+
console.log(`[filter] Removing path to empty element: "${k}"`)
|
|
685
|
+
return false
|
|
686
|
+
}
|
|
272
687
|
}
|
|
273
688
|
}
|
|
274
|
-
}
|
|
275
689
|
|
|
276
|
-
|
|
690
|
+
return true
|
|
691
|
+
})
|
|
692
|
+
console.log("\n=== collectBodyKeys RESULT ===")
|
|
693
|
+
console.log("Final bodyKeys:", JSON.stringify(result))
|
|
694
|
+
console.log("=== collectBodyKeys END ===\n")
|
|
695
|
+
|
|
696
|
+
return result
|
|
277
697
|
}
|
|
278
698
|
|
|
279
699
|
async function encode(obj = {}) {
|
|
280
|
-
console.log("
|
|
700
|
+
console.log("\n=== ENCODE START ===")
|
|
701
|
+
console.log("Encoding object:", JSON.stringify(obj))
|
|
281
702
|
|
|
282
|
-
|
|
703
|
+
const processValue = value => {
|
|
704
|
+
if (typeof value === "symbol") {
|
|
705
|
+
return value.description || "Symbol.for()"
|
|
706
|
+
} else if (Array.isArray(value)) {
|
|
707
|
+
return value.map(processValue)
|
|
708
|
+
} else if (isPojo(value)) {
|
|
709
|
+
const result = {}
|
|
710
|
+
for (const [k, v] of Object.entries(value)) {
|
|
711
|
+
result[k] = processValue(v)
|
|
712
|
+
}
|
|
713
|
+
return result
|
|
714
|
+
}
|
|
715
|
+
return value
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const processedObj = {}
|
|
719
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
720
|
+
processedObj[k] = processValue(v)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (Object.keys(obj).length === 0) {
|
|
724
|
+
return { headers: {}, body: undefined }
|
|
725
|
+
}
|
|
283
726
|
|
|
284
|
-
// Check if we have a simple binary field
|
|
285
727
|
const objKeys = Object.keys(obj)
|
|
286
|
-
|
|
287
|
-
|
|
728
|
+
|
|
729
|
+
if (objKeys.length === 1) {
|
|
288
730
|
const fieldName = objKeys[0]
|
|
289
|
-
const
|
|
731
|
+
const fieldValue = obj[fieldName]
|
|
732
|
+
|
|
733
|
+
if (
|
|
734
|
+
isBytes(fieldValue) &&
|
|
735
|
+
(fieldValue.length === 0 || fieldValue.byteLength === 0)
|
|
736
|
+
) {
|
|
737
|
+
const headers = {}
|
|
738
|
+
headers["ao-types"] = `${fieldName.toLowerCase()}="empty-binary"`
|
|
739
|
+
return { headers, body: undefined }
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (
|
|
744
|
+
obj.body &&
|
|
745
|
+
isBytes(obj.body) &&
|
|
746
|
+
(obj.body.length === 0 || obj.body.byteLength === 0) &&
|
|
747
|
+
objKeys.length > 1
|
|
748
|
+
) {
|
|
749
|
+
}
|
|
290
750
|
|
|
751
|
+
const hasBodyBinary = obj.body && isBytes(obj.body)
|
|
752
|
+
const otherFields = Object.keys(obj).filter(k => k !== "body")
|
|
753
|
+
|
|
754
|
+
if (hasBodyBinary && otherFields.length === 0) {
|
|
291
755
|
const headers = {}
|
|
292
|
-
const bodyBuffer =
|
|
293
|
-
? binaryData
|
|
294
|
-
: Buffer.from(binaryData)
|
|
756
|
+
const bodyBuffer = toBuffer(obj.body)
|
|
295
757
|
const bodyArrayBuffer = bodyBuffer.buffer.slice(
|
|
296
758
|
bodyBuffer.byteOffset,
|
|
297
759
|
bodyBuffer.byteOffset + bodyBuffer.byteLength
|
|
@@ -301,187 +763,309 @@ async function encode(obj = {}) {
|
|
|
301
763
|
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
302
764
|
headers["content-digest"] = `sha-256=:${base64}:`
|
|
303
765
|
|
|
304
|
-
|
|
305
|
-
"[encode] FINAL (simple binary field) - headers:",
|
|
306
|
-
headers,
|
|
307
|
-
"body:",
|
|
308
|
-
binaryData
|
|
309
|
-
)
|
|
310
|
-
return { headers, body: binaryData }
|
|
766
|
+
return { headers, body: obj.body }
|
|
311
767
|
}
|
|
312
768
|
|
|
313
|
-
if (
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
|
|
769
|
+
if (objKeys.length === 1) {
|
|
770
|
+
const fieldName = objKeys[0]
|
|
771
|
+
const fieldValue = obj[fieldName]
|
|
772
|
+
|
|
773
|
+
if (isBytes(fieldValue) && fieldValue.length > 0) {
|
|
774
|
+
const headers = {}
|
|
775
|
+
const bodyBuffer = toBuffer(fieldValue)
|
|
776
|
+
const bodyArrayBuffer = bodyBuffer.buffer.slice(
|
|
777
|
+
bodyBuffer.byteOffset,
|
|
778
|
+
bodyBuffer.byteOffset + bodyBuffer.byteLength
|
|
779
|
+
)
|
|
780
|
+
|
|
781
|
+
const contentDigest = await sha256(bodyArrayBuffer)
|
|
782
|
+
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
783
|
+
headers["content-digest"] = `sha-256=:${base64}:`
|
|
784
|
+
|
|
785
|
+
if (fieldName !== "body") {
|
|
786
|
+
headers["inline-body-key"] = fieldName
|
|
787
|
+
}
|
|
317
788
|
|
|
318
|
-
|
|
319
|
-
|
|
789
|
+
return { headers, body: fieldValue }
|
|
790
|
+
} else if (
|
|
791
|
+
(fieldName === "data" || fieldName === "body") &&
|
|
792
|
+
(typeof fieldValue === "string" ||
|
|
793
|
+
typeof fieldValue === "boolean" ||
|
|
794
|
+
typeof fieldValue === "number" ||
|
|
795
|
+
fieldValue === null ||
|
|
796
|
+
fieldValue === undefined ||
|
|
797
|
+
typeof fieldValue === "symbol")
|
|
798
|
+
) {
|
|
799
|
+
const headers = {}
|
|
800
|
+
|
|
801
|
+
let bodyContent
|
|
802
|
+
if (typeof fieldValue === "string") {
|
|
803
|
+
bodyContent = fieldValue
|
|
804
|
+
} else if (typeof fieldValue === "boolean") {
|
|
805
|
+
bodyContent = `"${fieldValue}"`
|
|
806
|
+
} else if (typeof fieldValue === "number") {
|
|
807
|
+
bodyContent = String(fieldValue)
|
|
808
|
+
} else if (fieldValue === null) {
|
|
809
|
+
bodyContent = '"null"'
|
|
810
|
+
} else if (fieldValue === undefined) {
|
|
811
|
+
bodyContent = '"undefined"'
|
|
812
|
+
} else if (typeof fieldValue === "symbol") {
|
|
813
|
+
bodyContent = `"${fieldValue.description || "Symbol.for()"}"`
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const encoder = new TextEncoder()
|
|
817
|
+
const encoded = encoder.encode(bodyContent)
|
|
818
|
+
const contentDigest = await sha256(encoded.buffer)
|
|
819
|
+
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
820
|
+
headers["content-digest"] = `sha-256=:${base64}:`
|
|
821
|
+
|
|
822
|
+
if (
|
|
823
|
+
typeof fieldValue === "boolean" ||
|
|
824
|
+
fieldValue === null ||
|
|
825
|
+
fieldValue === undefined ||
|
|
826
|
+
typeof fieldValue === "symbol"
|
|
827
|
+
) {
|
|
828
|
+
headers["ao-types"] = `${fieldName.toLowerCase()}="atom"`
|
|
829
|
+
} else if (typeof fieldValue === "number") {
|
|
830
|
+
headers["ao-types"] =
|
|
831
|
+
`${fieldName.toLowerCase()}="${Number.isInteger(fieldValue) ? "integer" : "float"}"`
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (fieldName !== "body") {
|
|
835
|
+
headers["inline-body-key"] = fieldName
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return { headers, body: bodyContent }
|
|
839
|
+
} else if (typeof fieldValue === "string" && hasNonAscii(fieldValue)) {
|
|
840
|
+
const headers = {}
|
|
841
|
+
const encoder = new TextEncoder()
|
|
842
|
+
const encoded = encoder.encode(fieldValue)
|
|
843
|
+
const contentDigest = await sha256(encoded.buffer)
|
|
844
|
+
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
845
|
+
headers["content-digest"] = `sha-256=:${base64}:`
|
|
846
|
+
|
|
847
|
+
if (fieldName !== "body") {
|
|
848
|
+
headers["inline-body-key"] = fieldName
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
return { headers, body: fieldValue }
|
|
852
|
+
}
|
|
853
|
+
}
|
|
320
854
|
|
|
855
|
+
const bodyKeys = collectBodyKeys(obj)
|
|
856
|
+
const headers = {}
|
|
857
|
+
const headerTypes = []
|
|
858
|
+
|
|
859
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
860
|
+
const needsBody =
|
|
861
|
+
bodyKeys.includes(key) || bodyKeys.some(k => k.startsWith(`${key}/`))
|
|
862
|
+
|
|
863
|
+
if (!needsBody) {
|
|
321
864
|
if (value === null) {
|
|
322
|
-
headers[key] = "null"
|
|
323
|
-
|
|
865
|
+
headers[key] = '"null"'
|
|
866
|
+
headerTypes.push(`${key.toLowerCase()}="atom"`)
|
|
324
867
|
} else if (value === undefined) {
|
|
325
|
-
headers[key] = "undefined"
|
|
326
|
-
|
|
327
|
-
} else if (typeof value === "string" && value.length === 0) {
|
|
328
|
-
types.push(`${key}="empty-binary"`)
|
|
329
|
-
} else if (Array.isArray(value)) {
|
|
330
|
-
if (value.length === 0) {
|
|
331
|
-
types.push(`${key}="empty-list"`)
|
|
332
|
-
} else if (value.some(item => isPojo(item))) {
|
|
333
|
-
// Arrays with objects need multipart
|
|
334
|
-
types.push(`${key}="list"`)
|
|
335
|
-
needsMultipart = true
|
|
336
|
-
break
|
|
337
|
-
} else {
|
|
338
|
-
const [type, encoded] = hbEncodeValue(value)
|
|
339
|
-
headers[key] = encoded
|
|
340
|
-
types.push(`${key}="${type}"`)
|
|
341
|
-
}
|
|
342
|
-
} else if (isBytes(value)) {
|
|
343
|
-
if (value.length === 0 || value.byteLength === 0) {
|
|
344
|
-
types.push(`${key}="empty-binary"`)
|
|
345
|
-
} else {
|
|
346
|
-
needsMultipart = true
|
|
347
|
-
break
|
|
348
|
-
}
|
|
868
|
+
headers[key] = '"undefined"'
|
|
869
|
+
headerTypes.push(`${key.toLowerCase()}="atom"`)
|
|
349
870
|
} else if (typeof value === "boolean") {
|
|
350
871
|
headers[key] = `"${value}"`
|
|
351
|
-
|
|
872
|
+
headerTypes.push(`${key.toLowerCase()}="atom"`)
|
|
352
873
|
} else if (typeof value === "symbol") {
|
|
353
|
-
headers[key] = value.description || "
|
|
354
|
-
|
|
874
|
+
headers[key] = `"${value.description || "Symbol.for()"}"`
|
|
875
|
+
headerTypes.push(`${key.toLowerCase()}="atom"`)
|
|
355
876
|
} else if (typeof value === "number") {
|
|
356
877
|
headers[key] = String(value)
|
|
357
|
-
|
|
878
|
+
headerTypes.push(
|
|
879
|
+
`${key.toLowerCase()}="${Number.isInteger(value) ? "integer" : "float"}"`
|
|
880
|
+
)
|
|
358
881
|
} else if (typeof value === "string") {
|
|
359
|
-
|
|
882
|
+
if (value.length === 0) {
|
|
883
|
+
headerTypes.push(`${key.toLowerCase()}="empty-binary"`)
|
|
884
|
+
} else if (hasNonAscii(value)) {
|
|
885
|
+
continue
|
|
886
|
+
} else {
|
|
887
|
+
headers[key] = value
|
|
888
|
+
}
|
|
889
|
+
} else if (Array.isArray(value) && value.length === 0) {
|
|
890
|
+
headerTypes.push(`${key.toLowerCase()}="empty-list"`)
|
|
891
|
+
} else if (Array.isArray(value) && !value.some(item => isPojo(item))) {
|
|
892
|
+
const hasNonAsciiItems = value.some(
|
|
893
|
+
item => typeof item === "string" && hasNonAscii(item)
|
|
894
|
+
)
|
|
895
|
+
if (!hasNonAsciiItems) {
|
|
896
|
+
const encodedItems = value
|
|
897
|
+
.map(item => encodeArrayItem(item))
|
|
898
|
+
.join(", ")
|
|
899
|
+
headers[key] = encodedItems
|
|
900
|
+
headerTypes.push(`${key.toLowerCase()}="list"`)
|
|
901
|
+
}
|
|
902
|
+
} else if (
|
|
903
|
+
isBytes(value) &&
|
|
904
|
+
(value.length === 0 || value.byteLength === 0)
|
|
905
|
+
) {
|
|
906
|
+
headerTypes.push(`${key.toLowerCase()}="empty-binary"`)
|
|
360
907
|
} else if (isPojo(value) && Object.keys(value).length === 0) {
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
908
|
+
headerTypes.push(`${key.toLowerCase()}="empty-message"`)
|
|
909
|
+
}
|
|
910
|
+
} else {
|
|
911
|
+
if (isBytes(value) && (value.length === 0 || value.byteLength === 0)) {
|
|
912
|
+
headerTypes.push(`${key.toLowerCase()}="empty-binary"`)
|
|
913
|
+
} else if (typeof value === "string" && value.length === 0) {
|
|
914
|
+
headerTypes.push(`${key.toLowerCase()}="empty-binary"`)
|
|
915
|
+
} else if (Array.isArray(value) && value.length === 0) {
|
|
916
|
+
headerTypes.push(`${key.toLowerCase()}="empty-list"`)
|
|
917
|
+
} else if (isPojo(value) && Object.keys(value).length === 0) {
|
|
918
|
+
headerTypes.push(`${key.toLowerCase()}="empty-message"`)
|
|
919
|
+
} else if (
|
|
920
|
+
typeof value === "boolean" ||
|
|
921
|
+
value === null ||
|
|
922
|
+
value === undefined ||
|
|
923
|
+
typeof value === "symbol"
|
|
924
|
+
) {
|
|
925
|
+
headerTypes.push(`${key.toLowerCase()}="atom"`)
|
|
926
|
+
} else if (typeof value === "number") {
|
|
927
|
+
headerTypes.push(
|
|
928
|
+
`${key.toLowerCase()}="${Number.isInteger(value) ? "integer" : "float"}"`
|
|
929
|
+
)
|
|
365
930
|
}
|
|
366
931
|
}
|
|
932
|
+
}
|
|
367
933
|
|
|
368
|
-
|
|
369
|
-
|
|
934
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
935
|
+
if (Array.isArray(value)) {
|
|
936
|
+
if (
|
|
937
|
+
bodyKeys.includes(key) ||
|
|
938
|
+
bodyKeys.some(k => k.startsWith(`${key}/`))
|
|
939
|
+
) {
|
|
940
|
+
if (!headerTypes.some(t => t.startsWith(`${key.toLowerCase()}=`))) {
|
|
941
|
+
headerTypes.push(`${key.toLowerCase()}="list"`)
|
|
942
|
+
}
|
|
943
|
+
}
|
|
370
944
|
}
|
|
945
|
+
}
|
|
371
946
|
|
|
372
|
-
|
|
373
|
-
|
|
947
|
+
if (bodyKeys.length === 0) {
|
|
948
|
+
console.log("No bodyKeys, returning headers only")
|
|
949
|
+
if (headerTypes.length > 0) {
|
|
950
|
+
headers["ao-types"] = headerTypes.sort().join(", ")
|
|
374
951
|
}
|
|
952
|
+
return { headers, body: undefined }
|
|
953
|
+
}
|
|
375
954
|
|
|
376
|
-
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
955
|
+
const allBodyKeysAreEmptyBinaries = bodyKeys.every(key => {
|
|
956
|
+
const pathParts = key.split("/")
|
|
957
|
+
let value = obj
|
|
958
|
+
for (const part of pathParts) {
|
|
959
|
+
if (/^\d+$/.test(part)) {
|
|
960
|
+
const index = parseInt(part) - 1
|
|
961
|
+
console.log(
|
|
962
|
+
`[Body part] Getting array element at index ${index} from part ${part}`
|
|
963
|
+
)
|
|
964
|
+
value = value[index]
|
|
965
|
+
} else {
|
|
966
|
+
value = value[part]
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return isBytes(value) && (value.length === 0 || value.byteLength === 0)
|
|
970
|
+
})
|
|
386
971
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
headers,
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
)
|
|
393
|
-
return { headers, body }
|
|
972
|
+
if (allBodyKeysAreEmptyBinaries) {
|
|
973
|
+
if (headerTypes.length > 0) {
|
|
974
|
+
headers["ao-types"] = headerTypes.sort().join(", ")
|
|
975
|
+
}
|
|
976
|
+
return { headers, body: undefined }
|
|
394
977
|
}
|
|
395
978
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
979
|
+
if (bodyKeys.length === 1) {
|
|
980
|
+
const singleKey = bodyKeys[0]
|
|
981
|
+
const pathParts = singleKey.split("/")
|
|
982
|
+
let value = obj
|
|
983
|
+
for (const part of pathParts) {
|
|
984
|
+
if (/^\d+$/.test(part)) {
|
|
985
|
+
value = value[parseInt(part) - 1]
|
|
986
|
+
} else {
|
|
987
|
+
value = value[part]
|
|
988
|
+
}
|
|
989
|
+
}
|
|
402
990
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
991
|
+
const otherFieldsAreEmpty = Object.entries(obj).every(([key, val]) => {
|
|
992
|
+
if (key === singleKey) return true
|
|
993
|
+
return (
|
|
994
|
+
(Array.isArray(val) && val.length === 0) ||
|
|
995
|
+
(isPojo(val) && Object.keys(val).length === 0) ||
|
|
996
|
+
(isBytes(val) && (val.length === 0 || val.byteLength === 0)) ||
|
|
997
|
+
(typeof val === "string" && val.length === 0)
|
|
998
|
+
)
|
|
999
|
+
})
|
|
406
1000
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
1001
|
+
if (otherFieldsAreEmpty && isBytes(value) && value.length > 0) {
|
|
1002
|
+
const bodyBuffer = toBuffer(value)
|
|
1003
|
+
const bodyArrayBuffer = bodyBuffer.buffer.slice(
|
|
1004
|
+
bodyBuffer.byteOffset,
|
|
1005
|
+
bodyBuffer.byteOffset + bodyBuffer.byteLength
|
|
1006
|
+
)
|
|
414
1007
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (isBytes(value)) {
|
|
419
|
-
const length = value.byteLength || value.length || 0
|
|
420
|
-
if (length === 0) {
|
|
421
|
-
// Empty binaries stay in headers
|
|
422
|
-
console.log(`[encode] Empty binary field ${key} staying in headers`)
|
|
423
|
-
continue
|
|
424
|
-
} else {
|
|
425
|
-
console.log(`[encode] Binary field ${key} going to body`)
|
|
426
|
-
bodyKeys.push(key)
|
|
427
|
-
}
|
|
428
|
-
} else {
|
|
429
|
-
const valueStr = String(value)
|
|
430
|
-
const type = types[key]
|
|
1008
|
+
const contentDigest = await sha256(bodyArrayBuffer)
|
|
1009
|
+
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
1010
|
+
headers["content-digest"] = `sha-256=:${base64}:`
|
|
431
1011
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
1012
|
+
if (singleKey !== "body") {
|
|
1013
|
+
headers["inline-body-key"] = singleKey
|
|
1014
|
+
}
|
|
435
1015
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
!key.includes("/") &&
|
|
439
|
-
Buffer.from(valueStr).byteLength <= MAX_HEADER_LENGTH &&
|
|
440
|
-
!isPojo(value) &&
|
|
441
|
-
!(key === "data" || key === "body") // Don't put inline keys in headers
|
|
442
|
-
) {
|
|
443
|
-
if (typeof value === "number") {
|
|
444
|
-
headers[key] = String(value)
|
|
445
|
-
} else if (typeof value === "boolean") {
|
|
446
|
-
headers[key] = `"${value}"`
|
|
447
|
-
} else if (value === "[]" && type === "empty_list") {
|
|
448
|
-
headers[key] = "[]"
|
|
449
|
-
} else if (value === '"null"' || value === '"undefined"') {
|
|
450
|
-
headers[key] = value
|
|
451
|
-
} else {
|
|
452
|
-
headers[key] = value
|
|
453
|
-
}
|
|
454
|
-
} else {
|
|
455
|
-
bodyKeys.push(key)
|
|
456
|
-
}
|
|
457
|
-
}
|
|
1016
|
+
if (headerTypes.length > 0) {
|
|
1017
|
+
headers["ao-types"] = headerTypes.sort().join(", ")
|
|
458
1018
|
}
|
|
459
|
-
|
|
460
|
-
|
|
1019
|
+
|
|
1020
|
+
return { headers, body: value }
|
|
461
1021
|
}
|
|
462
1022
|
}
|
|
463
1023
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
1024
|
+
// Sort body keys: main array comes first, then element parts by index
|
|
1025
|
+
const sortedBodyKeys = bodyKeys.sort((a, b) => {
|
|
1026
|
+
const aIsArrayElement = /\/\d+$/.test(a)
|
|
1027
|
+
const bIsArrayElement = /\/\d+$/.test(b)
|
|
1028
|
+
const aBase = a.split("/")[0]
|
|
1029
|
+
const bBase = b.split("/")[0]
|
|
1030
|
+
|
|
1031
|
+
// If both are for the same array
|
|
1032
|
+
if (aBase === bBase) {
|
|
1033
|
+
// Main array comes before element parts
|
|
1034
|
+
if (!aIsArrayElement && bIsArrayElement) {
|
|
1035
|
+
return -1 // main array comes first
|
|
1036
|
+
}
|
|
1037
|
+
if (aIsArrayElement && !bIsArrayElement) {
|
|
1038
|
+
return 1 // element parts come after
|
|
1039
|
+
}
|
|
1040
|
+
// Both are elements - sort by index
|
|
1041
|
+
if (aIsArrayElement && bIsArrayElement) {
|
|
1042
|
+
const aIndex = parseInt(a.split("/")[1])
|
|
1043
|
+
const bIndex = parseInt(b.split("/")[1])
|
|
1044
|
+
return aIndex - bIndex
|
|
1045
|
+
}
|
|
1046
|
+
return a.localeCompare(b)
|
|
478
1047
|
}
|
|
479
|
-
}
|
|
480
1048
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
1049
|
+
// Different arrays, sort by base name
|
|
1050
|
+
return a.localeCompare(b)
|
|
1051
|
+
})
|
|
1052
|
+
|
|
1053
|
+
// Check if we have the special case where data contains body with bytes and body contains data with bytes
|
|
1054
|
+
const hasSpecialDataBody =
|
|
1055
|
+
sortedBodyKeys.includes("data") &&
|
|
1056
|
+
sortedBodyKeys.includes("body") &&
|
|
1057
|
+
obj.data &&
|
|
1058
|
+
obj.data.body &&
|
|
1059
|
+
isBytes(obj.data.body) &&
|
|
1060
|
+
obj.body &&
|
|
1061
|
+
obj.body.data &&
|
|
1062
|
+
isBytes(obj.body.data)
|
|
1063
|
+
|
|
1064
|
+
headers["body-keys"] = sortedBodyKeys.map(k => `"${k}"`).join(", ")
|
|
1065
|
+
|
|
1066
|
+
if (!hasSpecialDataBody) {
|
|
1067
|
+
if (sortedBodyKeys.includes("body") && sortedBodyKeys.length === 1) {
|
|
1068
|
+
headers["inline-body-key"] = "body"
|
|
485
1069
|
}
|
|
486
1070
|
}
|
|
487
1071
|
|
|
@@ -489,211 +1073,457 @@ async function encodeMultipart(obj) {
|
|
|
489
1073
|
headers["ao-types"] = headerTypes.sort().join(", ")
|
|
490
1074
|
}
|
|
491
1075
|
|
|
492
|
-
|
|
493
|
-
|
|
1076
|
+
const bodyParts = []
|
|
1077
|
+
|
|
1078
|
+
for (const bodyKey of sortedBodyKeys) {
|
|
1079
|
+
console.log(`\n[Body part] Processing bodyKey: ${bodyKey}`)
|
|
1080
|
+
const lines = []
|
|
1081
|
+
|
|
1082
|
+
const pathParts = bodyKey.split("/")
|
|
1083
|
+
let value = obj
|
|
1084
|
+
let parent = null
|
|
494
1085
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
1086
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
1087
|
+
parent = value
|
|
1088
|
+
const part = pathParts[i]
|
|
1089
|
+
|
|
1090
|
+
if (/^\d+$/.test(part)) {
|
|
1091
|
+
value = value[parseInt(part) - 1]
|
|
1092
|
+
} else {
|
|
1093
|
+
value = value[part]
|
|
1094
|
+
}
|
|
498
1095
|
}
|
|
499
1096
|
|
|
500
|
-
|
|
501
|
-
for (const path of bodyKeys) {
|
|
502
|
-
console.log(`[encode] Processing bodyKey: ${path}`)
|
|
503
|
-
|
|
504
|
-
// Handle root-level fields
|
|
505
|
-
if (!path.includes("/")) {
|
|
506
|
-
// This is a root-level field
|
|
507
|
-
const fieldValue = parts[""] && parts[""][path]
|
|
508
|
-
console.log(`[encode] Root field ${path} value:`, fieldValue)
|
|
509
|
-
|
|
510
|
-
if (fieldValue !== undefined) {
|
|
511
|
-
if (isBytes(fieldValue)) {
|
|
512
|
-
// Binary field - create a simple multipart section
|
|
513
|
-
// The format should be:
|
|
514
|
-
// content-disposition: form-data;name="fieldname"
|
|
515
|
-
// [empty line]
|
|
516
|
-
// [binary data]
|
|
517
|
-
const headerStr = `content-disposition: form-data;name="${path}"\r\n\r\n`
|
|
518
|
-
const headerBlob = new Blob([headerStr])
|
|
519
|
-
const dataBlob = new Blob([fieldValue])
|
|
520
|
-
bodyParts.push(new Blob([headerBlob, dataBlob]))
|
|
521
|
-
console.log(`[encode] Added binary field ${path} to bodyParts`)
|
|
522
|
-
continue
|
|
523
|
-
} else {
|
|
524
|
-
// Non-binary root field that needs to go in body
|
|
525
|
-
const lines = []
|
|
1097
|
+
console.log(`[Body part] Value at ${bodyKey}:`, JSON.stringify(value))
|
|
526
1098
|
|
|
527
|
-
|
|
528
|
-
|
|
1099
|
+
// Special handling for empty strings in arrays
|
|
1100
|
+
if (typeof value === "string" && value === "" && pathParts.length > 1) {
|
|
1101
|
+
console.log(`[Body part] Creating part for empty string at ${bodyKey}`)
|
|
1102
|
+
lines.push(`content-disposition: form-data;name="${bodyKey}"`)
|
|
1103
|
+
lines.push("")
|
|
1104
|
+
lines.push("")
|
|
1105
|
+
bodyParts.push(new Blob([lines.join("\r\n")]))
|
|
1106
|
+
continue
|
|
1107
|
+
}
|
|
529
1108
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
1109
|
+
// Handle direct binary values
|
|
1110
|
+
if (isBytes(value)) {
|
|
1111
|
+
console.log(`[Body part] Creating part for binary at ${bodyKey}`)
|
|
1112
|
+
lines.push(`content-disposition: form-data;name="${bodyKey}"`)
|
|
1113
|
+
const buffer = toBuffer(value)
|
|
1114
|
+
const headerText = lines.join("\r\n") + "\r\n\r\n"
|
|
1115
|
+
bodyParts.push(new Blob([headerText, buffer]))
|
|
1116
|
+
continue
|
|
1117
|
+
}
|
|
535
1118
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
1119
|
+
if (Array.isArray(value)) {
|
|
1120
|
+
const hasOnlyEmptyElements =
|
|
1121
|
+
value.length > 0 &&
|
|
1122
|
+
value.every(
|
|
1123
|
+
item =>
|
|
1124
|
+
(Array.isArray(item) && item.length === 0) ||
|
|
1125
|
+
(isPojo(item) && Object.keys(item).length === 0) ||
|
|
1126
|
+
(typeof item === "string" && item === "")
|
|
1127
|
+
)
|
|
1128
|
+
const hasOnlyNonEmptyObjects =
|
|
1129
|
+
value.length > 0 &&
|
|
1130
|
+
value.every(item => isPojo(item) && Object.keys(item).length > 0)
|
|
1131
|
+
const hasOnlyEmptyObjects =
|
|
1132
|
+
value.length > 0 &&
|
|
1133
|
+
value.every(item => isPojo(item) && Object.keys(item).length === 0)
|
|
1134
|
+
const hasObjects = value.some(item => isPojo(item))
|
|
1135
|
+
const hasArrays = value.some(item => Array.isArray(item))
|
|
1136
|
+
const nonObjectItems = value
|
|
1137
|
+
.map((item, index) => ({ item, index: index + 1 }))
|
|
1138
|
+
.filter(({ item }) => !isPojo(item))
|
|
1139
|
+
|
|
1140
|
+
if (hasOnlyNonEmptyObjects) {
|
|
1141
|
+
continue
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// For arrays containing only empty elements, we still need to show type info
|
|
1145
|
+
if (hasOnlyEmptyElements) {
|
|
1146
|
+
const fieldLines = []
|
|
1147
|
+
const partTypes = []
|
|
1148
|
+
|
|
1149
|
+
// Process items for type information
|
|
1150
|
+
value.forEach((item, idx) => {
|
|
1151
|
+
const index = idx + 1
|
|
1152
|
+
if (Array.isArray(item) && item.length === 0) {
|
|
1153
|
+
partTypes.push(`${index}="empty-list"`)
|
|
1154
|
+
} else if (isPojo(item) && Object.keys(item).length === 0) {
|
|
1155
|
+
partTypes.push(`${index}="empty-message"`)
|
|
1156
|
+
} else if (typeof item === "string" && item === "") {
|
|
1157
|
+
partTypes.push(`${index}="empty-binary"`)
|
|
1158
|
+
}
|
|
1159
|
+
})
|
|
541
1160
|
|
|
542
|
-
|
|
543
|
-
|
|
1161
|
+
const isInline =
|
|
1162
|
+
bodyKey === "body" && headers["inline-body-key"] === "body"
|
|
1163
|
+
|
|
1164
|
+
if (isInline) {
|
|
1165
|
+
const orderedLines = []
|
|
1166
|
+
if (partTypes.length > 0) {
|
|
1167
|
+
orderedLines.push(
|
|
1168
|
+
`ao-types: ${partTypes
|
|
1169
|
+
.sort((a, b) => {
|
|
1170
|
+
const aNum = parseInt(a.split("=")[0])
|
|
1171
|
+
const bNum = parseInt(b.split("=")[0])
|
|
1172
|
+
return aNum - bNum
|
|
1173
|
+
})
|
|
1174
|
+
.join(", ")}`
|
|
1175
|
+
)
|
|
1176
|
+
}
|
|
1177
|
+
orderedLines.push("content-disposition: inline")
|
|
1178
|
+
orderedLines.push("")
|
|
1179
|
+
bodyParts.push(new Blob([orderedLines.join("\r\n")]))
|
|
1180
|
+
} else {
|
|
1181
|
+
const orderedLines = []
|
|
1182
|
+
if (partTypes.length > 0) {
|
|
1183
|
+
orderedLines.push(
|
|
1184
|
+
`ao-types: ${partTypes
|
|
1185
|
+
.sort((a, b) => {
|
|
1186
|
+
const aNum = parseInt(a.split("=")[0])
|
|
1187
|
+
const bNum = parseInt(b.split("=")[0])
|
|
1188
|
+
return aNum - bNum
|
|
1189
|
+
})
|
|
1190
|
+
.join(", ")}`
|
|
1191
|
+
)
|
|
1192
|
+
}
|
|
1193
|
+
orderedLines.push(`content-disposition: form-data;name="${bodyKey}"`)
|
|
544
1194
|
|
|
545
|
-
|
|
546
|
-
|
|
1195
|
+
// Check if this is the last body part
|
|
1196
|
+
const isLastBodyPart =
|
|
1197
|
+
sortedBodyKeys.indexOf(bodyKey) === sortedBodyKeys.length - 1
|
|
1198
|
+
const hasOnlyTypes = partTypes.length > 0 && fieldLines.length === 0
|
|
547
1199
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
1200
|
+
if (isLastBodyPart && hasOnlyTypes) {
|
|
1201
|
+
// Don't add empty line for last part with only types
|
|
1202
|
+
bodyParts.push(new Blob([orderedLines.join("\r\n")]))
|
|
1203
|
+
} else {
|
|
1204
|
+
orderedLines.push("")
|
|
1205
|
+
bodyParts.push(new Blob([orderedLines.join("\r\n")]))
|
|
551
1206
|
}
|
|
552
1207
|
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// Handle nested paths
|
|
556
|
-
const content = parts[path]
|
|
557
|
-
if (!content) {
|
|
558
|
-
console.log(`[encode] No content found for path: ${path}`)
|
|
559
1208
|
continue
|
|
560
1209
|
}
|
|
561
1210
|
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
(
|
|
1211
|
+
// Build list of which indices have their own parts
|
|
1212
|
+
const indicesWithOwnParts = new Set()
|
|
1213
|
+
sortedBodyKeys.forEach(key => {
|
|
1214
|
+
if (key.startsWith(bodyKey + "/")) {
|
|
1215
|
+
const subPath = key.substring(bodyKey.length + 1)
|
|
1216
|
+
const match = subPath.match(/^(\d+)/)
|
|
1217
|
+
if (match) {
|
|
1218
|
+
indicesWithOwnParts.add(parseInt(match[1]))
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
})
|
|
566
1222
|
|
|
567
|
-
|
|
1223
|
+
// Check if this array contains sub-arrays with objects that have their own parts
|
|
1224
|
+
const hasNestedObjectParts = sortedBodyKeys.some(
|
|
1225
|
+
key =>
|
|
1226
|
+
key.startsWith(bodyKey + "/") &&
|
|
1227
|
+
key.split("/").length > pathParts.length + 1
|
|
1228
|
+
)
|
|
568
1229
|
|
|
569
|
-
|
|
1230
|
+
const fieldLines = []
|
|
570
1231
|
const partTypes = []
|
|
571
|
-
for (const key of sortedKeys) {
|
|
572
|
-
const fullPath = path ? `${path}/${key}` : key
|
|
573
|
-
const type = types[fullPath]
|
|
574
|
-
if (type) {
|
|
575
|
-
partTypes.push(`${key}="${type}"`)
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
1232
|
|
|
579
|
-
|
|
580
|
-
|
|
1233
|
+
// For arrays that contain sub-arrays with objects, we need to add type info for the sub-arrays
|
|
1234
|
+
if (hasNestedObjectParts) {
|
|
1235
|
+
value.forEach((item, idx) => {
|
|
1236
|
+
const index = idx + 1
|
|
1237
|
+
if (Array.isArray(item)) {
|
|
1238
|
+
partTypes.push(`${index}="list"`)
|
|
1239
|
+
}
|
|
1240
|
+
})
|
|
581
1241
|
}
|
|
582
1242
|
|
|
583
|
-
if
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
1243
|
+
// Check if this array has mixed content with empty strings/objects
|
|
1244
|
+
const hasEmptyStrings = value.some(
|
|
1245
|
+
item => typeof item === "string" && item === ""
|
|
1246
|
+
)
|
|
1247
|
+
const hasEmptyObjects = value.some(
|
|
1248
|
+
item => isPojo(item) && Object.keys(item).length === 0
|
|
1249
|
+
)
|
|
588
1250
|
|
|
589
|
-
//
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
1251
|
+
// Check if we have objects with only empty values (like {empty: ""})
|
|
1252
|
+
const hasObjectsWithOnlyEmptyValues = value.some(item => {
|
|
1253
|
+
if (!isPojo(item) || Object.keys(item).length === 0) return false
|
|
1254
|
+
return Object.values(item).every(
|
|
1255
|
+
v =>
|
|
1256
|
+
(typeof v === "string" && v === "") ||
|
|
1257
|
+
(Array.isArray(v) && v.length === 0) ||
|
|
1258
|
+
(isPojo(v) && Object.keys(v).length === 0)
|
|
1259
|
+
)
|
|
1260
|
+
})
|
|
594
1261
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
1262
|
+
const isMixedArray =
|
|
1263
|
+
hasObjects &&
|
|
1264
|
+
(hasEmptyStrings || hasEmptyObjects) &&
|
|
1265
|
+
!hasObjectsWithOnlyEmptyValues
|
|
1266
|
+
|
|
1267
|
+
// Process ALL items for type information
|
|
1268
|
+
value.forEach((item, idx) => {
|
|
1269
|
+
const index = idx + 1
|
|
1270
|
+
|
|
1271
|
+
// Skip type info for elements that have their own parts
|
|
1272
|
+
if (indicesWithOwnParts.has(index)) {
|
|
1273
|
+
return
|
|
601
1274
|
}
|
|
602
1275
|
|
|
1276
|
+
// If we have nested object parts and this is an array with objects, skip processing it inline
|
|
603
1277
|
if (
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
1278
|
+
hasNestedObjectParts &&
|
|
1279
|
+
Array.isArray(item) &&
|
|
1280
|
+
item.some(subItem => isPojo(subItem))
|
|
607
1281
|
) {
|
|
608
|
-
|
|
1282
|
+
// Type info already added above
|
|
1283
|
+
return
|
|
609
1284
|
}
|
|
610
1285
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
} else if (
|
|
616
|
-
//
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
1286
|
+
// For all arrays (not just mixed ones), we need to process all items
|
|
1287
|
+
if (typeof item === "string" && item === "") {
|
|
1288
|
+
// Empty strings get type annotation but no field line
|
|
1289
|
+
partTypes.push(`${index}="empty-binary"`)
|
|
1290
|
+
} else if (isPojo(item) && Object.keys(item).length === 0) {
|
|
1291
|
+
// Empty objects don't get field lines but do get type annotations
|
|
1292
|
+
partTypes.push(`${index}="empty-message"`)
|
|
1293
|
+
} else if (isPojo(item)) {
|
|
1294
|
+
// Non-empty objects might have parts
|
|
1295
|
+
} else if (Array.isArray(item)) {
|
|
1296
|
+
if (item.length === 0) {
|
|
1297
|
+
// Empty arrays don't get field lines but do get type annotations
|
|
1298
|
+
partTypes.push(`${index}="empty-list"`)
|
|
1299
|
+
} else {
|
|
1300
|
+
partTypes.push(`${index}="list"`)
|
|
1301
|
+
// Encode array
|
|
1302
|
+
const encodedItems = item
|
|
1303
|
+
.map(subItem => {
|
|
1304
|
+
if (typeof subItem === "number") {
|
|
1305
|
+
if (Number.isInteger(subItem)) {
|
|
1306
|
+
return `"(ao-type-integer) ${subItem}"`
|
|
1307
|
+
} else {
|
|
1308
|
+
return `"(ao-type-float) ${formatFloat(subItem)}"`
|
|
1309
|
+
}
|
|
1310
|
+
} else if (typeof subItem === "string") {
|
|
1311
|
+
return `"${subItem}"`
|
|
1312
|
+
} else if (subItem === null) {
|
|
1313
|
+
return `"(ao-type-atom) \\"null\\""`
|
|
1314
|
+
} else if (subItem === undefined) {
|
|
1315
|
+
return `"(ao-type-atom) \\"undefined\\""`
|
|
1316
|
+
} else if (typeof subItem === "symbol") {
|
|
1317
|
+
const desc = subItem.description || "Symbol.for()"
|
|
1318
|
+
return `"(ao-type-atom) \\"${desc}\\""`
|
|
1319
|
+
} else if (typeof subItem === "boolean") {
|
|
1320
|
+
return `"(ao-type-atom) \\"${subItem}\\""`
|
|
1321
|
+
} else if (Array.isArray(subItem)) {
|
|
1322
|
+
// Use the full encodeArrayItem for nested arrays
|
|
1323
|
+
return encodeArrayItem(subItem)
|
|
1324
|
+
} else if (isBytes(subItem)) {
|
|
1325
|
+
const buffer = toBuffer(subItem)
|
|
1326
|
+
if (buffer.length === 0 || buffer.byteLength === 0) {
|
|
1327
|
+
return `""`
|
|
1328
|
+
}
|
|
1329
|
+
return `"(ao-type-binary)"`
|
|
1330
|
+
} else if (isPojo(subItem)) {
|
|
1331
|
+
const json = JSON.stringify(subItem)
|
|
1332
|
+
const escaped = json
|
|
1333
|
+
.replace(/\\/g, "\\\\")
|
|
1334
|
+
.replace(/"/g, '\\"')
|
|
1335
|
+
return `"(ao-type-map) ${escaped}"`
|
|
1336
|
+
} else {
|
|
1337
|
+
return `"${String(subItem)}"`
|
|
1338
|
+
}
|
|
1339
|
+
})
|
|
1340
|
+
.join(", ")
|
|
1341
|
+
fieldLines.push(`${index}: ${encodedItems}`)
|
|
1342
|
+
}
|
|
1343
|
+
} else if (typeof item === "number") {
|
|
1344
|
+
if (Number.isInteger(item)) {
|
|
1345
|
+
partTypes.push(`${index}="integer"`)
|
|
1346
|
+
fieldLines.push(`${index}: ${item}`)
|
|
1347
|
+
} else {
|
|
1348
|
+
partTypes.push(`${index}="float"`)
|
|
1349
|
+
fieldLines.push(`${index}: ${formatFloat(item)}`)
|
|
1350
|
+
}
|
|
1351
|
+
} else if (typeof item === "string") {
|
|
1352
|
+
// Non-empty strings just get field lines, no type annotation
|
|
1353
|
+
fieldLines.push(`${index}: ${item}`)
|
|
1354
|
+
} else if (
|
|
1355
|
+
item === null ||
|
|
1356
|
+
item === undefined ||
|
|
1357
|
+
typeof item === "symbol" ||
|
|
1358
|
+
typeof item === "boolean"
|
|
1359
|
+
) {
|
|
1360
|
+
partTypes.push(`${index}="atom"`)
|
|
1361
|
+
if (item === null) {
|
|
1362
|
+
fieldLines.push(`${index}: null`)
|
|
1363
|
+
} else if (item === undefined) {
|
|
1364
|
+
fieldLines.push(`${index}: undefined`)
|
|
1365
|
+
} else if (typeof item === "symbol") {
|
|
1366
|
+
const desc = item.description || "Symbol.for()"
|
|
1367
|
+
fieldLines.push(`${index}: ${desc}`)
|
|
1368
|
+
} else {
|
|
1369
|
+
fieldLines.push(`${index}: ${item}`)
|
|
1370
|
+
}
|
|
1371
|
+
} else if (isBytes(item)) {
|
|
1372
|
+
const buffer = toBuffer(item)
|
|
1373
|
+
if (buffer.length === 0) {
|
|
1374
|
+
partTypes.push(`${index}="empty-binary"`)
|
|
1375
|
+
} else {
|
|
1376
|
+
partTypes.push(`${index}="binary"`)
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
})
|
|
1380
|
+
|
|
1381
|
+
const isInline =
|
|
1382
|
+
bodyKey === "body" && headers["inline-body-key"] === "body"
|
|
1383
|
+
|
|
1384
|
+
if (isInline) {
|
|
1385
|
+
const orderedLines = []
|
|
1386
|
+
|
|
1387
|
+
if (partTypes.length > 0) {
|
|
1388
|
+
orderedLines.push(
|
|
1389
|
+
`ao-types: ${partTypes
|
|
1390
|
+
.sort((a, b) => {
|
|
1391
|
+
const aNum = parseInt(a.split("=")[0])
|
|
1392
|
+
const bNum = parseInt(b.split("=")[0])
|
|
1393
|
+
return aNum - bNum
|
|
1394
|
+
})
|
|
1395
|
+
.join(", ")}`
|
|
1396
|
+
)
|
|
621
1397
|
}
|
|
622
|
-
}
|
|
623
1398
|
|
|
624
|
-
|
|
625
|
-
if (binaryParts.length > 0) {
|
|
626
|
-
const allParts = []
|
|
1399
|
+
orderedLines.push("content-disposition: inline")
|
|
627
1400
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
1401
|
+
if (fieldLines.length > 0) {
|
|
1402
|
+
orderedLines.push("")
|
|
1403
|
+
for (const line of fieldLines) {
|
|
1404
|
+
orderedLines.push(line)
|
|
632
1405
|
}
|
|
1406
|
+
}
|
|
633
1407
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
1408
|
+
bodyParts.push(new Blob([orderedLines.join("\r\n") + "\r\n"]))
|
|
1409
|
+
} else {
|
|
1410
|
+
// Put ao-types first, then content-disposition, then field lines
|
|
1411
|
+
const orderedLines = []
|
|
1412
|
+
|
|
1413
|
+
if (partTypes.length > 0) {
|
|
1414
|
+
orderedLines.push(
|
|
1415
|
+
`ao-types: ${partTypes
|
|
1416
|
+
.sort((a, b) => {
|
|
1417
|
+
const aNum = parseInt(a.split("=")[0])
|
|
1418
|
+
const bNum = parseInt(b.split("=")[0])
|
|
1419
|
+
return aNum - bNum
|
|
1420
|
+
})
|
|
1421
|
+
.join(", ")}`
|
|
1422
|
+
)
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
orderedLines.push(`content-disposition: form-data;name="${bodyKey}"`)
|
|
1426
|
+
|
|
1427
|
+
// Add field lines directly without blank line
|
|
1428
|
+
for (const line of fieldLines) {
|
|
1429
|
+
orderedLines.push(line)
|
|
1430
|
+
}
|
|
642
1431
|
|
|
643
|
-
|
|
1432
|
+
// Add trailing blank line if we have field lines
|
|
1433
|
+
if (fieldLines.length > 0) {
|
|
1434
|
+
bodyParts.push(new Blob([orderedLines.join("\r\n") + "\r\n"]))
|
|
644
1435
|
} else {
|
|
645
|
-
bodyParts.push(new Blob([
|
|
1436
|
+
bodyParts.push(new Blob([orderedLines.join("\r\n")]))
|
|
646
1437
|
}
|
|
647
1438
|
}
|
|
1439
|
+
continue
|
|
648
1440
|
}
|
|
649
1441
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
partsForBoundary.push(partContent)
|
|
1442
|
+
// This should not be reached for binary values as they're handled above
|
|
1443
|
+
const isInline = bodyKey === "body" && headers["inline-body-key"] === "body"
|
|
1444
|
+
if (isInline) {
|
|
1445
|
+
lines.push(`content-disposition: inline`)
|
|
1446
|
+
} else {
|
|
1447
|
+
lines.push(`content-disposition: form-data;name="${bodyKey}"`)
|
|
657
1448
|
}
|
|
658
|
-
const allPartsContent = partsForBoundary.join("")
|
|
659
|
-
console.log(`[encode] All parts content length: ${allPartsContent.length}`)
|
|
660
|
-
|
|
661
|
-
const allPartsBuffer = Buffer.from(allPartsContent)
|
|
662
|
-
const hashResult = await sha256(
|
|
663
|
-
allPartsBuffer.buffer.slice(
|
|
664
|
-
allPartsBuffer.byteOffset,
|
|
665
|
-
allPartsBuffer.byteOffset + allPartsBuffer.byteLength
|
|
666
|
-
)
|
|
667
|
-
)
|
|
668
|
-
const boundary = base64url.encode(Buffer.from(hashResult))
|
|
669
1449
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
1450
|
+
if (typeof value === "string") {
|
|
1451
|
+
lines.push("")
|
|
1452
|
+
lines.push(value)
|
|
1453
|
+
bodyParts.push(new Blob([lines.join("\r\n")]))
|
|
1454
|
+
} else if (
|
|
1455
|
+
typeof value === "boolean" ||
|
|
1456
|
+
typeof value === "number" ||
|
|
1457
|
+
value === null ||
|
|
1458
|
+
value === undefined ||
|
|
1459
|
+
typeof value === "symbol"
|
|
1460
|
+
) {
|
|
1461
|
+
let content
|
|
1462
|
+
if (typeof value === "boolean") {
|
|
1463
|
+
content = `"${value}"`
|
|
1464
|
+
} else if (typeof value === "number") {
|
|
1465
|
+
content = String(value)
|
|
1466
|
+
} else if (value === null) {
|
|
1467
|
+
content = '"null"'
|
|
1468
|
+
} else if (value === undefined) {
|
|
1469
|
+
content = '"undefined"'
|
|
1470
|
+
} else if (typeof value === "symbol") {
|
|
1471
|
+
content = `"${value.description || "Symbol.for()"}"`
|
|
678
1472
|
}
|
|
1473
|
+
|
|
1474
|
+
lines.push("")
|
|
1475
|
+
lines.push(content)
|
|
1476
|
+
bodyParts.push(new Blob([lines.join("\r\n")]))
|
|
679
1477
|
}
|
|
680
|
-
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
// Add the special data/body part if needed
|
|
1481
|
+
if (
|
|
1482
|
+
hasSpecialDataBody &&
|
|
1483
|
+
obj.data &&
|
|
1484
|
+
obj.data.body &&
|
|
1485
|
+
isBytes(obj.data.body)
|
|
1486
|
+
) {
|
|
1487
|
+
const buffer = toBuffer(obj.data.body)
|
|
1488
|
+
const specialPart = [
|
|
1489
|
+
`content-disposition: form-data;name="data/body"`,
|
|
1490
|
+
"",
|
|
1491
|
+
"",
|
|
1492
|
+
].join("\r\n")
|
|
1493
|
+
bodyParts.push(new Blob([specialPart, buffer]))
|
|
1494
|
+
}
|
|
681
1495
|
|
|
682
|
-
|
|
683
|
-
|
|
1496
|
+
const partsContent = await Promise.all(bodyParts.map(part => part.text()))
|
|
1497
|
+
const allContent = partsContent.join("")
|
|
1498
|
+
const boundaryHash = await sha256(new TextEncoder().encode(allContent))
|
|
1499
|
+
const boundary = base64url.encode(Buffer.from(boundaryHash))
|
|
684
1500
|
|
|
685
|
-
|
|
1501
|
+
const finalParts = []
|
|
1502
|
+
for (let i = 0; i < bodyParts.length; i++) {
|
|
1503
|
+
if (i === 0) {
|
|
1504
|
+
finalParts.push(new Blob([`--${boundary}\r\n`]))
|
|
1505
|
+
} else {
|
|
1506
|
+
finalParts.push(new Blob([`\r\n--${boundary}\r\n`]))
|
|
1507
|
+
}
|
|
1508
|
+
finalParts.push(bodyParts[i])
|
|
1509
|
+
}
|
|
1510
|
+
finalParts.push(new Blob([`\r\n--${boundary}--`]))
|
|
1511
|
+
|
|
1512
|
+
headers["content-type"] = `multipart/form-data; boundary="${boundary}"`
|
|
1513
|
+
const body = new Blob(finalParts)
|
|
1514
|
+
|
|
1515
|
+
const finalContent = await body.arrayBuffer()
|
|
1516
|
+
|
|
1517
|
+
if (finalContent.byteLength > 0) {
|
|
686
1518
|
const contentDigest = await sha256(finalContent)
|
|
687
1519
|
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
688
1520
|
headers["content-digest"] = `sha-256=:${base64}:`
|
|
689
|
-
headers["content-length"] = String(finalContent.byteLength)
|
|
690
|
-
|
|
691
|
-
console.log("[encode] FINAL - headers:", headers, "body:", body)
|
|
692
|
-
return { headers, body }
|
|
693
1521
|
}
|
|
694
1522
|
|
|
695
|
-
|
|
696
|
-
|
|
1523
|
+
headers["content-length"] = String(finalContent.byteLength)
|
|
1524
|
+
|
|
1525
|
+
console.log("=== ENCODE END ===\n")
|
|
1526
|
+
return { headers, body }
|
|
697
1527
|
}
|
|
698
1528
|
|
|
699
1529
|
export async function enc(fields) {
|