wao 0.26.2 → 0.27.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/cjs/encode.js +1047 -0
- package/cjs/hb.js +14 -6
- package/cjs/hyperbeam.js +88 -16
- package/cjs/send.js +187 -0
- package/cjs/signer.js +36 -822
- package/cjs/workspace/test/hyperbeam.js +6 -9
- package/esm/encode.js +701 -0
- package/esm/hb.js +6 -3
- package/esm/hyperbeam.js +58 -10
- package/esm/send.js +126 -0
- package/esm/signer.js +5 -651
- package/esm/workspace/test/hyperbeam.js +6 -9
- package/package.json +1 -1
package/esm/encode.js
ADDED
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
import base64url from "base64url"
|
|
2
|
+
import { hash } from "fast-sha256"
|
|
3
|
+
|
|
4
|
+
function isBytes(value) {
|
|
5
|
+
return (
|
|
6
|
+
value instanceof ArrayBuffer ||
|
|
7
|
+
ArrayBuffer.isView(value) ||
|
|
8
|
+
Buffer.isBuffer(value)
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isPojo(value) {
|
|
13
|
+
return (
|
|
14
|
+
!isBytes(value) &&
|
|
15
|
+
!Array.isArray(value) &&
|
|
16
|
+
!(value instanceof Blob) &&
|
|
17
|
+
typeof value === "object" &&
|
|
18
|
+
value !== null
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
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
|
+
const MAX_HEADER_LENGTH = 4096
|
|
105
|
+
|
|
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
|
+
async function hasNewline(value) {
|
|
116
|
+
if (typeof value === "string") return value.includes("\n")
|
|
117
|
+
if (value instanceof Blob) {
|
|
118
|
+
value = await value.text()
|
|
119
|
+
return value.includes("\n")
|
|
120
|
+
}
|
|
121
|
+
if (isBytes(value)) return Buffer.from(value).includes("\n")
|
|
122
|
+
return false
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function sha256(data) {
|
|
126
|
+
let uint8Array
|
|
127
|
+
if (data instanceof ArrayBuffer) {
|
|
128
|
+
uint8Array = new Uint8Array(data)
|
|
129
|
+
} else if (data instanceof Uint8Array) {
|
|
130
|
+
uint8Array = data
|
|
131
|
+
} else if (ArrayBuffer.isView(data)) {
|
|
132
|
+
uint8Array = new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
133
|
+
} else {
|
|
134
|
+
throw new Error("sha256 expects ArrayBuffer or ArrayBufferView")
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const hashResult = hash(uint8Array)
|
|
138
|
+
return hashResult.buffer.slice(
|
|
139
|
+
hashResult.byteOffset,
|
|
140
|
+
hashResult.byteOffset + hashResult.byteLength
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function collectParts(obj, path = "", parts = {}, types = {}) {
|
|
145
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
146
|
+
const currentPath = path ? `${path}/${key}` : key
|
|
147
|
+
|
|
148
|
+
if (value === null || value === undefined) {
|
|
149
|
+
if (!parts[path]) parts[path] = {}
|
|
150
|
+
parts[path][key] = value === null ? '"null"' : '"undefined"'
|
|
151
|
+
types[currentPath] = "atom"
|
|
152
|
+
} else if (isBytes(value)) {
|
|
153
|
+
if (!parts[path]) parts[path] = {}
|
|
154
|
+
parts[path][key] = value
|
|
155
|
+
|
|
156
|
+
const length = value.byteLength || value.length || 0
|
|
157
|
+
if (length === 0) {
|
|
158
|
+
types[currentPath] = "empty-binary"
|
|
159
|
+
}
|
|
160
|
+
} else if (typeof value === "string") {
|
|
161
|
+
if (value.length === 0) {
|
|
162
|
+
types[currentPath] = "empty-binary"
|
|
163
|
+
if (!parts[path]) parts[path] = {}
|
|
164
|
+
parts[path][key] = ""
|
|
165
|
+
} else {
|
|
166
|
+
if (!parts[path]) parts[path] = {}
|
|
167
|
+
parts[path][key] = value
|
|
168
|
+
}
|
|
169
|
+
} else if (Array.isArray(value)) {
|
|
170
|
+
if (value.length === 0) {
|
|
171
|
+
types[currentPath] = "empty-list"
|
|
172
|
+
if (!parts[path]) parts[path] = {}
|
|
173
|
+
parts[path][key] = "[]"
|
|
174
|
+
} else {
|
|
175
|
+
const hasObjects = value.some(item => isPojo(item))
|
|
176
|
+
|
|
177
|
+
if (hasObjects) {
|
|
178
|
+
// Arrays with objects: create separate parts for each indexed item
|
|
179
|
+
types[currentPath] = "list"
|
|
180
|
+
|
|
181
|
+
value.forEach((item, index) => {
|
|
182
|
+
const indexKey = String(index + 1)
|
|
183
|
+
const indexPath = `${currentPath}/${indexKey}`
|
|
184
|
+
|
|
185
|
+
if (isPojo(item)) {
|
|
186
|
+
// Each object in the array becomes a separate part
|
|
187
|
+
if (!parts[indexPath]) parts[indexPath] = {}
|
|
188
|
+
|
|
189
|
+
for (const [objKey, objValue] of Object.entries(item)) {
|
|
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
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
// Non-object items in the array
|
|
220
|
+
if (!parts[currentPath]) parts[currentPath] = {}
|
|
221
|
+
parts[currentPath][indexKey] = item
|
|
222
|
+
|
|
223
|
+
if (typeof item === "number") {
|
|
224
|
+
types[`${currentPath}/${indexKey}`] = Number.isInteger(item)
|
|
225
|
+
? "integer"
|
|
226
|
+
: "float"
|
|
227
|
+
} else if (typeof item === "boolean") {
|
|
228
|
+
types[`${currentPath}/${indexKey}`] = "atom"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
} else {
|
|
233
|
+
// Simple arrays without objects
|
|
234
|
+
const [type, encoded] = hbEncodeValue(value)
|
|
235
|
+
if (!parts[path]) parts[path] = {}
|
|
236
|
+
parts[path][key] = encoded
|
|
237
|
+
types[currentPath] = type || "list"
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
} else if (isPojo(value)) {
|
|
241
|
+
if (Object.keys(value).length === 0) {
|
|
242
|
+
types[currentPath] = "empty-message"
|
|
243
|
+
if (!parts[path]) parts[path] = {}
|
|
244
|
+
parts[path][key] = "{}"
|
|
245
|
+
} else {
|
|
246
|
+
const hasOnlyEmptyChildren = Object.entries(value).every(([k, v]) => {
|
|
247
|
+
return (
|
|
248
|
+
(Array.isArray(v) && v.length === 0) ||
|
|
249
|
+
(isPojo(v) && Object.keys(v).length === 0)
|
|
250
|
+
)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
if (hasOnlyEmptyChildren) {
|
|
254
|
+
if (!parts[currentPath]) parts[currentPath] = {}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
collectParts(value, currentPath, parts, types)
|
|
258
|
+
}
|
|
259
|
+
} else if (typeof value === "symbol") {
|
|
260
|
+
if (!parts[path]) parts[path] = {}
|
|
261
|
+
parts[path][key] = value.description || "symbol"
|
|
262
|
+
types[currentPath] = "atom"
|
|
263
|
+
} else if (typeof value === "boolean") {
|
|
264
|
+
if (!parts[path]) parts[path] = {}
|
|
265
|
+
parts[path][key] = value
|
|
266
|
+
types[currentPath] = "atom"
|
|
267
|
+
} else {
|
|
268
|
+
if (!parts[path]) parts[path] = {}
|
|
269
|
+
parts[path][key] = value
|
|
270
|
+
if (typeof value === "number") {
|
|
271
|
+
types[currentPath] = Number.isInteger(value) ? "integer" : "float"
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return { parts, types }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function encode(obj = {}) {
|
|
280
|
+
console.log("[encode] START with obj:", JSON.stringify(obj))
|
|
281
|
+
|
|
282
|
+
if (Object.keys(obj).length === 0) return { headers: {}, body: undefined }
|
|
283
|
+
|
|
284
|
+
// Check if we have a simple binary field
|
|
285
|
+
const objKeys = Object.keys(obj)
|
|
286
|
+
if (objKeys.length === 1 && isBytes(obj[objKeys[0]])) {
|
|
287
|
+
// Single binary field - return it directly
|
|
288
|
+
const fieldName = objKeys[0]
|
|
289
|
+
const binaryData = obj[fieldName]
|
|
290
|
+
|
|
291
|
+
const headers = {}
|
|
292
|
+
const bodyBuffer = Buffer.isBuffer(binaryData)
|
|
293
|
+
? binaryData
|
|
294
|
+
: Buffer.from(binaryData)
|
|
295
|
+
const bodyArrayBuffer = bodyBuffer.buffer.slice(
|
|
296
|
+
bodyBuffer.byteOffset,
|
|
297
|
+
bodyBuffer.byteOffset + bodyBuffer.byteLength
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
const contentDigest = await sha256(bodyArrayBuffer)
|
|
301
|
+
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
302
|
+
headers["content-digest"] = `sha-256=:${base64}:`
|
|
303
|
+
|
|
304
|
+
console.log(
|
|
305
|
+
"[encode] FINAL (simple binary field) - headers:",
|
|
306
|
+
headers,
|
|
307
|
+
"body:",
|
|
308
|
+
binaryData
|
|
309
|
+
)
|
|
310
|
+
return { headers, body: binaryData }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if ("body" in obj && isBytes(obj.body)) {
|
|
314
|
+
const headers = {}
|
|
315
|
+
const types = []
|
|
316
|
+
let needsMultipart = false
|
|
317
|
+
|
|
318
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
319
|
+
if (key === "body") continue
|
|
320
|
+
|
|
321
|
+
if (value === null) {
|
|
322
|
+
headers[key] = "null"
|
|
323
|
+
types.push(`${key}="atom"`)
|
|
324
|
+
} else if (value === undefined) {
|
|
325
|
+
headers[key] = "undefined"
|
|
326
|
+
types.push(`${key}="atom"`)
|
|
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
|
+
}
|
|
349
|
+
} else if (typeof value === "boolean") {
|
|
350
|
+
headers[key] = `"${value}"`
|
|
351
|
+
types.push(`${key}="atom"`)
|
|
352
|
+
} else if (typeof value === "symbol") {
|
|
353
|
+
headers[key] = value.description || "symbol"
|
|
354
|
+
types.push(`${key}="atom"`)
|
|
355
|
+
} else if (typeof value === "number") {
|
|
356
|
+
headers[key] = String(value)
|
|
357
|
+
types.push(`${key}="${Number.isInteger(value) ? "integer" : "float"}"`)
|
|
358
|
+
} else if (typeof value === "string") {
|
|
359
|
+
headers[key] = value
|
|
360
|
+
} else if (isPojo(value) && Object.keys(value).length === 0) {
|
|
361
|
+
types.push(`${key}="empty-message"`)
|
|
362
|
+
} else if (isPojo(value)) {
|
|
363
|
+
needsMultipart = true
|
|
364
|
+
break
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (needsMultipart) {
|
|
369
|
+
return encodeMultipart(obj)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (types.length > 0) {
|
|
373
|
+
headers["ao-types"] = types.sort().join(", ")
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const body = obj.body
|
|
377
|
+
const bodyBuffer = Buffer.isBuffer(body) ? body : Buffer.from(body)
|
|
378
|
+
const bodyArrayBuffer = bodyBuffer.buffer.slice(
|
|
379
|
+
bodyBuffer.byteOffset,
|
|
380
|
+
bodyBuffer.byteOffset + bodyBuffer.byteLength
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
const contentDigest = await sha256(bodyArrayBuffer)
|
|
384
|
+
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
385
|
+
headers["content-digest"] = `sha-256=:${base64}:`
|
|
386
|
+
|
|
387
|
+
console.log(
|
|
388
|
+
"[encode] FINAL (simple body encoding) - headers:",
|
|
389
|
+
headers,
|
|
390
|
+
"body:",
|
|
391
|
+
body
|
|
392
|
+
)
|
|
393
|
+
return { headers, body }
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return encodeMultipart(obj)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function encodeMultipart(obj) {
|
|
400
|
+
const { parts, types } = collectParts(obj)
|
|
401
|
+
console.log("[encode] Parts:", parts, "Types:", types)
|
|
402
|
+
|
|
403
|
+
const headers = {}
|
|
404
|
+
const bodyParts = []
|
|
405
|
+
const bodyKeys = []
|
|
406
|
+
|
|
407
|
+
// Collect header types for arrays with objects
|
|
408
|
+
const headerTypes = []
|
|
409
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
410
|
+
if (Array.isArray(value) && value.some(item => isPojo(item))) {
|
|
411
|
+
headerTypes.push(`${key}="list"`)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
for (const [path, content] of Object.entries(parts)) {
|
|
416
|
+
if (path === "") {
|
|
417
|
+
for (const [key, value] of Object.entries(content)) {
|
|
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]
|
|
431
|
+
|
|
432
|
+
if (type === "empty-binary" && valueStr === "") {
|
|
433
|
+
continue
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (
|
|
437
|
+
!(await hasNewline(valueStr)) &&
|
|
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
|
+
}
|
|
458
|
+
}
|
|
459
|
+
} else {
|
|
460
|
+
bodyKeys.push(path)
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
console.log(
|
|
465
|
+
"[encode] Headers before filtering:",
|
|
466
|
+
headers,
|
|
467
|
+
"BodyKeys:",
|
|
468
|
+
bodyKeys
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
bodyKeys.sort()
|
|
472
|
+
|
|
473
|
+
// Add types for values in headers
|
|
474
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
475
|
+
const type = types[key]
|
|
476
|
+
if (type) {
|
|
477
|
+
headerTypes.push(`${key}="${type}"`)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Add types for empty values not in headers
|
|
482
|
+
for (const [key, type] of Object.entries(types)) {
|
|
483
|
+
if (!key.includes("/") && type.startsWith("empty") && !headers[key]) {
|
|
484
|
+
headerTypes.push(`${key}="${type.replace("empty_", "empty-")}"`)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (headerTypes.length > 0) {
|
|
489
|
+
headers["ao-types"] = headerTypes.sort().join(", ")
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (bodyKeys.length > 0) {
|
|
493
|
+
headers["body-keys"] = encode_body_keys(bodyKeys)
|
|
494
|
+
|
|
495
|
+
if (bodyKeys.includes("data") || bodyKeys.includes("body")) {
|
|
496
|
+
const inlineKey = bodyKeys.find(k => k === "data" || k === "body")
|
|
497
|
+
headers["inline-body-key"] = inlineKey
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Create multipart body parts
|
|
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 = []
|
|
526
|
+
|
|
527
|
+
// Check if this is an inline key
|
|
528
|
+
const isInlineKey = path === "data" || path === "body"
|
|
529
|
+
|
|
530
|
+
if (isInlineKey) {
|
|
531
|
+
lines.push(`content-disposition: inline`)
|
|
532
|
+
} else {
|
|
533
|
+
lines.push(`content-disposition: form-data;name="${path}"`)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Add the field type if available
|
|
537
|
+
const type = types[path]
|
|
538
|
+
if (type) {
|
|
539
|
+
lines.push(`ao-types: ${path}="${type}"`)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Add empty line before content
|
|
543
|
+
lines.push("")
|
|
544
|
+
|
|
545
|
+
// Add the actual value
|
|
546
|
+
lines.push(String(fieldValue))
|
|
547
|
+
|
|
548
|
+
bodyParts.push(new Blob([lines.join("\r\n")]))
|
|
549
|
+
console.log(`[encode] Added non-binary field ${path} to bodyParts`)
|
|
550
|
+
continue
|
|
551
|
+
}
|
|
552
|
+
}
|
|
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
|
+
continue
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const lines = []
|
|
563
|
+
const binaryParts = []
|
|
564
|
+
const isInlineKey =
|
|
565
|
+
(path === "data" || path === "body") && !path.includes("/")
|
|
566
|
+
|
|
567
|
+
const sortedKeys = Object.keys(content).sort()
|
|
568
|
+
|
|
569
|
+
// Collect ao-types for this part
|
|
570
|
+
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
|
+
|
|
579
|
+
if (partTypes.length > 0) {
|
|
580
|
+
lines.push(`ao-types: ${partTypes.sort().join(", ")}`)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (isInlineKey) {
|
|
584
|
+
lines.push(`content-disposition: inline`)
|
|
585
|
+
} else {
|
|
586
|
+
lines.push(`content-disposition: form-data;name="${path}"`)
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Add content fields
|
|
590
|
+
for (const key of sortedKeys) {
|
|
591
|
+
const value = content[key]
|
|
592
|
+
const fullPath = path ? `${path}/${key}` : key
|
|
593
|
+
const type = types[fullPath]
|
|
594
|
+
|
|
595
|
+
if (
|
|
596
|
+
type === "empty-message" ||
|
|
597
|
+
type === "empty-list" ||
|
|
598
|
+
type === "empty-binary"
|
|
599
|
+
) {
|
|
600
|
+
continue
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (
|
|
604
|
+
(value === "[]" || value === "{}" || value === "") &&
|
|
605
|
+
type &&
|
|
606
|
+
type.startsWith("empty")
|
|
607
|
+
) {
|
|
608
|
+
continue
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (isBytes(value)) {
|
|
612
|
+
binaryParts.push({ key, value })
|
|
613
|
+
} else if (type === "atom" && typeof value === "boolean") {
|
|
614
|
+
lines.push(`${key}: "${value}"`)
|
|
615
|
+
} else if (typeof value === "symbol") {
|
|
616
|
+
// Handle Symbol values
|
|
617
|
+
const symbolValue = value.description || "symbol"
|
|
618
|
+
lines.push(`${key}: ${symbolValue}`)
|
|
619
|
+
} else {
|
|
620
|
+
lines.push(`${key}: ${value}`)
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (lines.length >= 1 || binaryParts.length > 0) {
|
|
625
|
+
if (binaryParts.length > 0) {
|
|
626
|
+
const allParts = []
|
|
627
|
+
|
|
628
|
+
// Add text headers first
|
|
629
|
+
if (lines.length > 0) {
|
|
630
|
+
allParts.push(lines.join("\r\n"))
|
|
631
|
+
allParts.push("\r\n")
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Add binary data
|
|
635
|
+
for (const binaryPart of binaryParts) {
|
|
636
|
+
allParts.push(`${binaryPart.key}: `)
|
|
637
|
+
allParts.push(binaryPart.value)
|
|
638
|
+
if (binaryParts.indexOf(binaryPart) < binaryParts.length - 1) {
|
|
639
|
+
allParts.push("\r\n")
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
bodyParts.push(new Blob(allParts))
|
|
644
|
+
} else {
|
|
645
|
+
bodyParts.push(new Blob([lines.join("\r\n")]))
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
console.log(`[encode] Total bodyParts: ${bodyParts.length}`)
|
|
651
|
+
|
|
652
|
+
// Create boundary based on parts content
|
|
653
|
+
const partsForBoundary = []
|
|
654
|
+
for (const part of bodyParts) {
|
|
655
|
+
const partContent = await part.text()
|
|
656
|
+
partsForBoundary.push(partContent)
|
|
657
|
+
}
|
|
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
|
+
|
|
670
|
+
// Create final multipart body
|
|
671
|
+
const finalParts = []
|
|
672
|
+
for (let i = 0; i < bodyParts.length; i++) {
|
|
673
|
+
finalParts.push(`--${boundary}`)
|
|
674
|
+
finalParts.push(`\r\n`)
|
|
675
|
+
finalParts.push(bodyParts[i])
|
|
676
|
+
if (i < bodyParts.length - 1) {
|
|
677
|
+
finalParts.push(`\r\n`)
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
finalParts.push(`\r\n--${boundary}--`)
|
|
681
|
+
|
|
682
|
+
headers["content-type"] = `multipart/form-data; boundary="${boundary}"`
|
|
683
|
+
const body = new Blob(finalParts)
|
|
684
|
+
|
|
685
|
+
const finalContent = await body.arrayBuffer()
|
|
686
|
+
const contentDigest = await sha256(finalContent)
|
|
687
|
+
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
688
|
+
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
|
+
}
|
|
694
|
+
|
|
695
|
+
console.log("[encode] FINAL - headers:", headers, "body:", undefined)
|
|
696
|
+
return { headers, body: undefined }
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
export async function enc(fields) {
|
|
700
|
+
return await encode(fields)
|
|
701
|
+
}
|