wao 0.26.2 → 0.27.1
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 +1522 -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 +1199 -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/signer.js
CHANGED
|
@@ -1,95 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { httpbis } from "http-message-signatures"
|
|
4
|
-
import { parseItem, serializeList } from "structured-headers"
|
|
5
|
-
const { verifyMessage } = httpbis
|
|
6
|
-
const {
|
|
7
|
-
augmentHeaders,
|
|
8
|
-
createSignatureBase,
|
|
9
|
-
createSigningParameters,
|
|
10
|
-
formatSignatureBase,
|
|
11
|
-
} = httpbis
|
|
12
|
-
|
|
13
|
-
export function hbEncodeValue(value) {
|
|
14
|
-
if (isBytes(value)) {
|
|
15
|
-
if (value.byteLength === 0) return hbEncodeValue("")
|
|
16
|
-
return [undefined, value]
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (typeof value === "string") {
|
|
20
|
-
if (value.length === 0) return ["empty-binary", undefined]
|
|
21
|
-
return [undefined, value]
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (Array.isArray(value)) {
|
|
25
|
-
if (value.length === 0) return ["empty-list", undefined]
|
|
26
|
-
if (value.some(isPojo)) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
`Array with objects should have been lifted: ${JSON.stringify(value)}`
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const encoded = value
|
|
33
|
-
.map(v => {
|
|
34
|
-
if (typeof v === "string") {
|
|
35
|
-
if (v === "") return `"(ao-type-empty-binary) "`
|
|
36
|
-
const escaped = v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
|
37
|
-
return `"${escaped}"`
|
|
38
|
-
} else if (typeof v === "number") return String(v)
|
|
39
|
-
else if (typeof v === "boolean") return v ? "?1" : "?0"
|
|
40
|
-
else if (typeof v === "symbol") {
|
|
41
|
-
const desc = v.description || "symbol"
|
|
42
|
-
const escaped = desc.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
|
43
|
-
return `"(ao-type-atom) ${escaped}"`
|
|
44
|
-
} else if (v === null) return `"(ao-type-atom) null"`
|
|
45
|
-
else if (v === undefined) return `"(ao-type-atom) undefined"`
|
|
46
|
-
else if (Array.isArray(v) && v.length === 0) {
|
|
47
|
-
return `"(ao-type-empty-list) "`
|
|
48
|
-
}
|
|
49
|
-
return `"${String(v)}"`
|
|
50
|
-
})
|
|
51
|
-
.join(", ")
|
|
52
|
-
return ["list", encoded]
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (typeof value === "number") {
|
|
56
|
-
if (!Number.isInteger(value)) return ["float", `${value}`]
|
|
57
|
-
return ["integer", String(value)]
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (typeof value === "boolean") {
|
|
61
|
-
return ["atom", `"${value ? "true" : "false"}"`]
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (typeof value === "symbol") {
|
|
65
|
-
const desc = value.description || "symbol"
|
|
66
|
-
return ["atom", `"${desc}"`]
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (value === null) return ["atom", `"null"`]
|
|
70
|
-
|
|
71
|
-
if (value === undefined) return ["atom", `"undefined"`]
|
|
72
|
-
|
|
73
|
-
throw new Error(`Cannot encode value: ${String(value)}`)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const toView = value => {
|
|
77
|
-
if (ArrayBuffer.isView(value)) {
|
|
78
|
-
return Buffer.from(value.buffer, value.byteOffset, value.byteLength)
|
|
79
|
-
} else if (typeof value === "string") return base64url.toBuffer(value)
|
|
80
|
-
|
|
81
|
-
throw new Error(
|
|
82
|
-
"Value must be Uint8Array, ArrayBuffer, or base64url-encoded string"
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const httpSigName = address => {
|
|
87
|
-
const decoded = base64url.toBuffer(address)
|
|
88
|
-
const hexString = [...decoded.subarray(1, 9)]
|
|
89
|
-
.map(byte => byte.toString(16).padStart(2, "0"))
|
|
90
|
-
.join("")
|
|
91
|
-
return `http-sig-${hexString}`
|
|
92
|
-
}
|
|
1
|
+
import { toHttpSigner } from "./send.js"
|
|
2
|
+
import { enc } from "./encode.js"
|
|
93
3
|
|
|
94
4
|
const joinUrl = ({ url, path }) => {
|
|
95
5
|
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
@@ -98,542 +8,17 @@ const joinUrl = ({ url, path }) => {
|
|
|
98
8
|
return url.endsWith("/") ? url.slice(0, -1) + path : url + path
|
|
99
9
|
}
|
|
100
10
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
function encode_body_keys(bodyKeys) {
|
|
104
|
-
if (!bodyKeys || bodyKeys.length === 0) return ""
|
|
105
|
-
const items = bodyKeys.map(key => `"${key}"`)
|
|
106
|
-
const result = items.join(", ")
|
|
107
|
-
return result
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function hasNewline(value) {
|
|
111
|
-
if (typeof value === "string") return value.includes("\n")
|
|
112
|
-
if (value instanceof Blob) {
|
|
113
|
-
value = await value.text()
|
|
114
|
-
return value.includes("\n")
|
|
115
|
-
}
|
|
116
|
-
if (isBytes(value)) return Buffer.from(value).includes("\n")
|
|
117
|
-
return false
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async function sha256(data) {
|
|
121
|
-
// Convert data to Uint8Array if needed
|
|
122
|
-
let uint8Array
|
|
123
|
-
if (data instanceof ArrayBuffer) {
|
|
124
|
-
uint8Array = new Uint8Array(data)
|
|
125
|
-
} else if (data instanceof Uint8Array) {
|
|
126
|
-
uint8Array = data
|
|
127
|
-
} else if (ArrayBuffer.isView(data)) {
|
|
128
|
-
uint8Array = new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
129
|
-
} else {
|
|
130
|
-
throw new Error("sha256 expects ArrayBuffer or ArrayBufferView")
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// fast-sha256 returns Uint8Array, convert to ArrayBuffer
|
|
134
|
-
const hashResult = hash(uint8Array)
|
|
135
|
-
return hashResult.buffer.slice(
|
|
136
|
-
hashResult.byteOffset,
|
|
137
|
-
hashResult.byteOffset + hashResult.byteLength
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function isBytes(value) {
|
|
142
|
-
return value instanceof ArrayBuffer || ArrayBuffer.isView(value)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function isPojo(value) {
|
|
146
|
-
return (
|
|
147
|
-
!isBytes(value) &&
|
|
148
|
-
!Array.isArray(value) &&
|
|
149
|
-
!(value instanceof Blob) &&
|
|
150
|
-
typeof value === "object" &&
|
|
151
|
-
value !== null
|
|
152
|
-
)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function hbEncodeLift(obj, parent = "", top = {}) {
|
|
156
|
-
const [flattened, types] = Object.entries({ ...obj }).reduce(
|
|
157
|
-
(acc, [key, value]) => {
|
|
158
|
-
const storageKey = parent ? `${parent}/${key}` : key
|
|
159
|
-
if (value == null) {
|
|
160
|
-
const [type, encoded] = hbEncodeValue(value)
|
|
161
|
-
if (encoded !== undefined) acc[0][key] = encoded
|
|
162
|
-
if (type) acc[1][key.toLowerCase()] = type
|
|
163
|
-
return acc
|
|
164
|
-
}
|
|
165
|
-
if (Array.isArray(value)) {
|
|
166
|
-
const hasObjects = value.some(isPojo)
|
|
167
|
-
const hasBinary = value.some(isBytes)
|
|
168
|
-
if (hasObjects || hasBinary) {
|
|
169
|
-
const indexedObj = value.reduce(
|
|
170
|
-
(obj, v, idx) => Object.assign(obj, { [idx]: v }),
|
|
171
|
-
{}
|
|
172
|
-
)
|
|
173
|
-
acc[1][key.toLowerCase()] = "list"
|
|
174
|
-
hbEncodeLift(indexedObj, storageKey, top)
|
|
175
|
-
return acc
|
|
176
|
-
} else {
|
|
177
|
-
const [type, encoded] = hbEncodeValue(value)
|
|
178
|
-
if (type) acc[1][key.toLowerCase()] = type
|
|
179
|
-
if (encoded !== undefined) acc[0][key] = encoded
|
|
180
|
-
return acc
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const originalValue = value
|
|
185
|
-
|
|
186
|
-
if (isPojo(value)) {
|
|
187
|
-
if (Object.keys(value).length === 0) {
|
|
188
|
-
acc[1][key.toLowerCase()] = "empty-message"
|
|
189
|
-
return acc
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const hasComplexValues = Object.values(value).some(
|
|
193
|
-
v => isPojo(v) || (Array.isArray(v) && v.some(item => isPojo(item)))
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
if (!hasComplexValues) {
|
|
197
|
-
const items = []
|
|
198
|
-
const hasAnyNonEmptyValues = Object.values(value).some(v => {
|
|
199
|
-
return !(
|
|
200
|
-
v === null ||
|
|
201
|
-
v === undefined ||
|
|
202
|
-
v === "" ||
|
|
203
|
-
(Array.isArray(v) && v.length === 0) ||
|
|
204
|
-
(isPojo(v) && Object.keys(v).length === 0)
|
|
205
|
-
)
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
Object.entries(value).forEach(([k, v]) => {
|
|
209
|
-
const subKey = k.toLowerCase()
|
|
210
|
-
|
|
211
|
-
if (v === null) {
|
|
212
|
-
items.push(`${subKey}="null"`)
|
|
213
|
-
acc[1][`${key.toLowerCase()}%2f${subKey}`] = "atom"
|
|
214
|
-
} else if (v === undefined) {
|
|
215
|
-
items.push(`${subKey}="undefined"`)
|
|
216
|
-
acc[1][`${key.toLowerCase()}%2f${subKey}`] = "atom"
|
|
217
|
-
} else if (typeof v === "string") {
|
|
218
|
-
if (v === "") {
|
|
219
|
-
acc[1][`${key.toLowerCase()}%2f${subKey}`] = "empty-binary"
|
|
220
|
-
} else {
|
|
221
|
-
const escaped = v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
|
222
|
-
items.push(`${subKey}="${escaped}"`)
|
|
223
|
-
}
|
|
224
|
-
} else if (typeof v === "number") {
|
|
225
|
-
items.push(`${subKey}=${v}`)
|
|
226
|
-
if (Number.isInteger(v)) {
|
|
227
|
-
acc[1][`${key.toLowerCase()}%2f${subKey}`] = "integer"
|
|
228
|
-
} else {
|
|
229
|
-
acc[1][`${key.toLowerCase()}%2f${subKey}`] = "float"
|
|
230
|
-
}
|
|
231
|
-
} else if (typeof v === "boolean") {
|
|
232
|
-
items.push(`${subKey}=${v ? "?1" : "?0"}`)
|
|
233
|
-
acc[1][`${key.toLowerCase()}%2f${subKey}`] = "boolean"
|
|
234
|
-
} else if (typeof v === "symbol") {
|
|
235
|
-
const desc = v.description || "symbol"
|
|
236
|
-
const escaped = desc.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
|
237
|
-
items.push(`${subKey}="${escaped}"`)
|
|
238
|
-
acc[1][`${key.toLowerCase()}%2f${subKey}`] = "atom"
|
|
239
|
-
} else if (Array.isArray(v) && !v.some(item => isPojo(item))) {
|
|
240
|
-
if (v.length === 0) {
|
|
241
|
-
items.push(`${subKey}=()`)
|
|
242
|
-
acc[1][`${key.toLowerCase()}%2f${subKey}`] = "empty-list"
|
|
243
|
-
} else {
|
|
244
|
-
const listItems = v.map(item => {
|
|
245
|
-
if (typeof item === "string") {
|
|
246
|
-
return `"${item.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
247
|
-
} else if (typeof item === "number") {
|
|
248
|
-
return String(item)
|
|
249
|
-
} else if (typeof item === "boolean") {
|
|
250
|
-
return item ? "?1" : "?0"
|
|
251
|
-
} else if (typeof item === "symbol") {
|
|
252
|
-
const desc = item.description || "symbol"
|
|
253
|
-
return `"${desc.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
254
|
-
} else if (item === null) {
|
|
255
|
-
return `"null"`
|
|
256
|
-
} else if (item === undefined) {
|
|
257
|
-
return `"undefined"`
|
|
258
|
-
} else {
|
|
259
|
-
return `"${String(item)}"`
|
|
260
|
-
}
|
|
261
|
-
})
|
|
262
|
-
items.push(`${subKey}=(${listItems.join(" ")})`)
|
|
263
|
-
}
|
|
264
|
-
} else if (isPojo(v) && Object.keys(v).length === 0) {
|
|
265
|
-
items.push(`${subKey}`)
|
|
266
|
-
acc[1][`${key.toLowerCase()}%2f${subKey}`] = "empty-message"
|
|
267
|
-
}
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
const encodedValue = items.join(", ")
|
|
271
|
-
|
|
272
|
-
const hasOnlyEmptyValues = Object.entries(value).every(([k, v]) => {
|
|
273
|
-
return (
|
|
274
|
-
v === null ||
|
|
275
|
-
v === undefined ||
|
|
276
|
-
v === "" ||
|
|
277
|
-
(Array.isArray(v) && v.length === 0) ||
|
|
278
|
-
(isPojo(v) && Object.keys(v).length === 0)
|
|
279
|
-
)
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
if (!hasAnyNonEmptyValues) {
|
|
283
|
-
acc[1][key.toLowerCase()] = "map"
|
|
284
|
-
} else if (encodedValue === "") {
|
|
285
|
-
acc[1][key.toLowerCase()] = "empty-message"
|
|
286
|
-
} else {
|
|
287
|
-
acc[0][key] = encodedValue
|
|
288
|
-
acc[1][key.toLowerCase()] = "map"
|
|
289
|
-
}
|
|
290
|
-
} else hbEncodeLift(value, storageKey, top)
|
|
291
|
-
return acc
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const [type, encoded] = hbEncodeValue(value)
|
|
295
|
-
|
|
296
|
-
if (encoded !== undefined) {
|
|
297
|
-
if (isBytes(encoded)) top[storageKey] = encoded
|
|
298
|
-
else {
|
|
299
|
-
acc[0][key] = encoded
|
|
300
|
-
if (type) acc[1][key.toLowerCase()] = type
|
|
301
|
-
}
|
|
302
|
-
} else if (type) acc[1][key.toLowerCase()] = type
|
|
303
|
-
return acc
|
|
304
|
-
},
|
|
305
|
-
[{}, {}]
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
if (Object.keys(flattened).length === 0 && Object.keys(types).length === 0)
|
|
309
|
-
return top
|
|
310
|
-
|
|
311
|
-
if (Object.keys(types).length > 0) {
|
|
312
|
-
const aoTypeItems = Object.entries(types).map(([key, value]) => {
|
|
313
|
-
const safeKey = key
|
|
314
|
-
.toLowerCase()
|
|
315
|
-
.replace(
|
|
316
|
-
/[^a-z0-9_\-.*\/]/g,
|
|
317
|
-
c => "%" + c.charCodeAt(0).toString(16).padStart(2, "0")
|
|
318
|
-
)
|
|
319
|
-
.replace(/\//g, "%2f")
|
|
320
|
-
return `${safeKey}="${value}"`
|
|
321
|
-
})
|
|
322
|
-
aoTypeItems.sort()
|
|
323
|
-
const aoTypes = aoTypeItems.join(", ")
|
|
324
|
-
|
|
325
|
-
if (Buffer.from(aoTypes).byteLength > MAX_HEADER_LENGTH) {
|
|
326
|
-
const flatK = parent ? `${parent}/ao-types` : "ao-types"
|
|
327
|
-
top[flatK] = aoTypes
|
|
328
|
-
} else flattened["ao-types"] = aoTypes
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (parent) top[parent] = flattened
|
|
332
|
-
else Object.assign(top, flattened)
|
|
333
|
-
return top
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function encodePart(name, { headers = {}, body }) {
|
|
337
|
-
const headerEntries =
|
|
338
|
-
headers instanceof Headers
|
|
339
|
-
? Array.from(headers.entries())
|
|
340
|
-
: Object.entries(headers || {})
|
|
341
|
-
|
|
342
|
-
const parts = headerEntries.reduce(
|
|
343
|
-
(acc, [name, value]) => {
|
|
344
|
-
acc.push(`${name}: `, value, "\r\n")
|
|
345
|
-
return acc
|
|
346
|
-
},
|
|
347
|
-
[`content-disposition: form-data;name="${name}"\r\n`]
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
if (body) parts.push("\r\n", body)
|
|
351
|
-
|
|
352
|
-
return new Blob(parts)
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
async function encode(obj = {}) {
|
|
356
|
-
if (Object.keys(obj).length === 0) return { headers: {}, body: undefined }
|
|
357
|
-
const originalObj = obj
|
|
358
|
-
const flattened = hbEncodeLift(obj)
|
|
359
|
-
|
|
360
|
-
const bodyKeys = []
|
|
361
|
-
const headerKeys = []
|
|
362
|
-
|
|
363
|
-
await Promise.all(
|
|
364
|
-
Object.keys(flattened).map(async key => {
|
|
365
|
-
const value = flattened[key]
|
|
366
|
-
|
|
367
|
-
if (isPojo(value)) {
|
|
368
|
-
const subPart = await encode(value)
|
|
369
|
-
if (!subPart) return
|
|
370
|
-
|
|
371
|
-
bodyKeys.push(key)
|
|
372
|
-
flattened[key] = encodePart(key, subPart)
|
|
373
|
-
return
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (isBytes(value)) {
|
|
377
|
-
bodyKeys.push(key)
|
|
378
|
-
const uint8Array =
|
|
379
|
-
value instanceof Uint8Array
|
|
380
|
-
? value
|
|
381
|
-
: value instanceof ArrayBuffer
|
|
382
|
-
? new Uint8Array(value)
|
|
383
|
-
: Buffer.isBuffer(value)
|
|
384
|
-
? new Uint8Array(value.buffer, value.byteOffset, value.length)
|
|
385
|
-
: new Uint8Array(
|
|
386
|
-
value.buffer,
|
|
387
|
-
value.byteOffset,
|
|
388
|
-
value.byteLength
|
|
389
|
-
)
|
|
390
|
-
flattened[key] = new Blob([
|
|
391
|
-
`content-disposition: form-data;name="${key}"\r\n\r\n`,
|
|
392
|
-
uint8Array,
|
|
393
|
-
])
|
|
394
|
-
return
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const valueStr = String(value)
|
|
398
|
-
if (
|
|
399
|
-
(await hasNewline(valueStr)) ||
|
|
400
|
-
key.includes("/") ||
|
|
401
|
-
Buffer.from(valueStr).byteLength > MAX_HEADER_LENGTH ||
|
|
402
|
-
(isPojo(value) && valueStr === "[object Object]")
|
|
403
|
-
) {
|
|
404
|
-
bodyKeys.push(key)
|
|
405
|
-
flattened[key] = new Blob([
|
|
406
|
-
`content-disposition: form-data;name="${key}"\r\n\r\n`,
|
|
407
|
-
value,
|
|
408
|
-
])
|
|
409
|
-
return
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
headerKeys.push(key)
|
|
413
|
-
})
|
|
414
|
-
)
|
|
415
|
-
|
|
416
|
-
const headers = {}
|
|
417
|
-
headerKeys.forEach(key => {
|
|
418
|
-
headers[key] = flattened[key]
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
if ("data" in originalObj && !bodyKeys.includes("data")) {
|
|
422
|
-
bodyKeys.push("data")
|
|
423
|
-
delete headers["data"]
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
if ("body" in originalObj && !bodyKeys.includes("body")) {
|
|
427
|
-
bodyKeys.push("body")
|
|
428
|
-
delete headers["body"]
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if (bodyKeys.length > 0) {
|
|
432
|
-
headers["body-keys"] = encode_body_keys(bodyKeys)
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
let body = undefined
|
|
436
|
-
let promoteToBody = true
|
|
437
|
-
if (bodyKeys.length > 0) {
|
|
438
|
-
if (bodyKeys.length === 1) {
|
|
439
|
-
const bodyKey = bodyKeys[0]
|
|
440
|
-
const originalValue = originalObj[bodyKey]
|
|
441
|
-
const flattenedValue = flattened[bodyKey]
|
|
442
|
-
|
|
443
|
-
if (
|
|
444
|
-
!isPojo(originalValue) ||
|
|
445
|
-
(isPojo(originalValue) && typeof flattenedValue === "string")
|
|
446
|
-
) {
|
|
447
|
-
if (
|
|
448
|
-
(bodyKey === "body" || bodyKey === "data") &&
|
|
449
|
-
isPojo(originalValue) &&
|
|
450
|
-
typeof flattenedValue === "string"
|
|
451
|
-
) {
|
|
452
|
-
body = new Blob([flattenedValue])
|
|
453
|
-
} else if (Array.isArray(originalValue)) {
|
|
454
|
-
const hasSymbols = originalValue.some(
|
|
455
|
-
item => typeof item === "symbol"
|
|
456
|
-
)
|
|
457
|
-
if (hasSymbols) {
|
|
458
|
-
const [type, encoded] = hbEncodeValue(originalValue)
|
|
459
|
-
body = new Blob([encoded || originalValue.toString()])
|
|
460
|
-
} else body = new Blob([originalValue.toString()])
|
|
461
|
-
} else body = new Blob([originalValue || flattenedValue])
|
|
462
|
-
headers["inline-body-key"] = bodyKey
|
|
463
|
-
} else promoteToBody = false
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
if (!promoteToBody || bodyKeys.length > 1) {
|
|
467
|
-
const bodyParts = await Promise.all(
|
|
468
|
-
bodyKeys.map(async name => {
|
|
469
|
-
if (flattened[name] instanceof Blob) return flattened[name]
|
|
470
|
-
const value = originalObj[name] || flattened[name] || ""
|
|
471
|
-
if (
|
|
472
|
-
name === "body" &&
|
|
473
|
-
isPojo(originalObj[name]) &&
|
|
474
|
-
typeof flattened[name] === "string"
|
|
475
|
-
) {
|
|
476
|
-
const partBlob = new Blob([
|
|
477
|
-
`content-disposition: form-data;name="${name}"\r\n\r\n`,
|
|
478
|
-
flattened[name],
|
|
479
|
-
])
|
|
480
|
-
return partBlob
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
let valueToEncode = value
|
|
484
|
-
if (Array.isArray(value)) {
|
|
485
|
-
const hasSymbols = value.some(item => typeof item === "symbol")
|
|
486
|
-
if (hasSymbols) {
|
|
487
|
-
const [type, encoded] = hbEncodeValue(value)
|
|
488
|
-
valueToEncode = encoded || value.toString()
|
|
489
|
-
}
|
|
490
|
-
} else if (isBytes(value)) valueToEncode = value
|
|
491
|
-
let partBlob
|
|
492
|
-
if (isBytes(valueToEncode)) {
|
|
493
|
-
const uint8Array =
|
|
494
|
-
valueToEncode instanceof Uint8Array
|
|
495
|
-
? valueToEncode
|
|
496
|
-
: valueToEncode instanceof ArrayBuffer
|
|
497
|
-
? new Uint8Array(valueToEncode)
|
|
498
|
-
: Buffer.isBuffer(valueToEncode)
|
|
499
|
-
? new Uint8Array(
|
|
500
|
-
valueToEncode.buffer,
|
|
501
|
-
valueToEncode.byteOffset,
|
|
502
|
-
valueToEncode.length
|
|
503
|
-
)
|
|
504
|
-
: new Uint8Array(
|
|
505
|
-
valueToEncode.buffer,
|
|
506
|
-
valueToEncode.byteOffset,
|
|
507
|
-
valueToEncode.byteLength
|
|
508
|
-
)
|
|
509
|
-
partBlob = new Blob([
|
|
510
|
-
`content-disposition: form-data;name="${name}"\r\n\r\n`,
|
|
511
|
-
uint8Array,
|
|
512
|
-
])
|
|
513
|
-
} else {
|
|
514
|
-
partBlob = new Blob([
|
|
515
|
-
`content-disposition: form-data;name="${name}"\r\n\r\n`,
|
|
516
|
-
valueToEncode,
|
|
517
|
-
])
|
|
518
|
-
}
|
|
519
|
-
return partBlob
|
|
520
|
-
})
|
|
521
|
-
)
|
|
522
|
-
|
|
523
|
-
const allPartsBuffer = await new Blob(bodyParts).arrayBuffer()
|
|
524
|
-
const hashResult = await sha256(allPartsBuffer)
|
|
525
|
-
const boundary = base64url.encode(Buffer.from(hashResult))
|
|
526
|
-
|
|
527
|
-
const finalParts = []
|
|
528
|
-
for (const part of bodyParts) {
|
|
529
|
-
finalParts.push(`--${boundary}\r\n`)
|
|
530
|
-
finalParts.push(part)
|
|
531
|
-
finalParts.push("\r\n")
|
|
532
|
-
}
|
|
533
|
-
finalParts.push(`--${boundary}--`)
|
|
534
|
-
|
|
535
|
-
headers["content-type"] = `multipart/form-data; boundary="${boundary}"`
|
|
536
|
-
body = new Blob(finalParts)
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
if (body) {
|
|
540
|
-
const finalContent = await body.arrayBuffer()
|
|
541
|
-
const contentDigest = await sha256(finalContent)
|
|
542
|
-
const base64 = base64url.toBase64(base64url.encode(contentDigest))
|
|
543
|
-
headers["content-digest"] = `sha-256=:${base64}:`
|
|
544
|
-
headers["content-length"] = String(finalContent.byteLength)
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
return { headers, body }
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
const toHttpSigner = signer => {
|
|
552
|
-
const params = ["alg", "keyid"].sort()
|
|
553
|
-
|
|
554
|
-
return async ({ request, fields }) => {
|
|
555
|
-
let signatureBase
|
|
556
|
-
let signatureInput
|
|
557
|
-
let createCalled = false
|
|
558
|
-
|
|
559
|
-
const create = injected => {
|
|
560
|
-
createCalled = true
|
|
561
|
-
|
|
562
|
-
const { publicKey, alg = "rsa-pss-sha512" } = injected
|
|
563
|
-
|
|
564
|
-
const publicKeyBuffer = toView(publicKey)
|
|
565
|
-
|
|
566
|
-
const signingParameters = createSigningParameters({
|
|
567
|
-
params,
|
|
568
|
-
paramValues: {
|
|
569
|
-
keyid: base64url.encode(publicKeyBuffer),
|
|
570
|
-
alg,
|
|
571
|
-
},
|
|
572
|
-
})
|
|
573
|
-
|
|
574
|
-
const signatureBaseArray = createSignatureBase({ fields }, request)
|
|
575
|
-
signatureInput = serializeList([
|
|
576
|
-
[
|
|
577
|
-
signatureBaseArray.map(([item]) => parseItem(item)),
|
|
578
|
-
signingParameters,
|
|
579
|
-
],
|
|
580
|
-
])
|
|
581
|
-
|
|
582
|
-
signatureBaseArray.push(['"@signature-params"', [signatureInput]])
|
|
583
|
-
signatureBase = formatSignatureBase(signatureBaseArray)
|
|
584
|
-
|
|
585
|
-
return new TextEncoder().encode(signatureBase)
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
const result = await signer(create, "httpsig")
|
|
589
|
-
|
|
590
|
-
if (!createCalled) {
|
|
591
|
-
throw new Error(
|
|
592
|
-
"create() must be invoked in order to construct the data to sign"
|
|
593
|
-
)
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
if (!result.signature || !result.address) {
|
|
597
|
-
throw new Error("Signer must return signature and address")
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
const signatureBuffer = toView(result.signature)
|
|
601
|
-
const signedHeaders = augmentHeaders(
|
|
602
|
-
request.headers,
|
|
603
|
-
signatureBuffer,
|
|
604
|
-
signatureInput,
|
|
605
|
-
httpSigName(result.address)
|
|
606
|
-
)
|
|
607
|
-
|
|
608
|
-
const finalHeaders = {}
|
|
609
|
-
for (const [key, value] of Object.entries(signedHeaders)) {
|
|
610
|
-
if (key === "Signature" || key === "Signature-Input") {
|
|
611
|
-
finalHeaders[key.toLowerCase()] = value
|
|
612
|
-
} else finalHeaders[key] = value
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
return { ...request, headers: finalHeaders }
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
export function createRequest(config) {
|
|
11
|
+
export function signer(config) {
|
|
620
12
|
const { signer, url = "http://localhost:10001" } = config
|
|
621
13
|
|
|
622
14
|
if (!signer) {
|
|
623
15
|
throw new Error("Signer is required for mainnet mode")
|
|
624
16
|
}
|
|
625
17
|
|
|
626
|
-
return async function
|
|
18
|
+
return async function sign(fields) {
|
|
627
19
|
const { path = "/relay/process", method = "POST", ...restFields } = fields
|
|
628
20
|
const aoFields = { ...restFields }
|
|
629
|
-
const
|
|
630
|
-
const binaryKeys = rootKeys.filter(key => isBytes(aoFields[key]))
|
|
631
|
-
|
|
632
|
-
if (binaryKeys.length > 1 && !aoFields.body && !aoFields.data) {
|
|
633
|
-
aoFields.body = "1984"
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
const encoded = await encode(aoFields)
|
|
21
|
+
const encoded = await enc(aoFields)
|
|
637
22
|
const headersObj = encoded ? encoded.headers : {}
|
|
638
23
|
const body = encoded ? encoded.body : undefined
|
|
639
24
|
|
|
@@ -685,34 +70,3 @@ export function createRequest(config) {
|
|
|
685
70
|
return result
|
|
686
71
|
}
|
|
687
72
|
}
|
|
688
|
-
export async function send(signedMsg, fetchImpl = fetch) {
|
|
689
|
-
const fetchOptions = {
|
|
690
|
-
method: signedMsg.method,
|
|
691
|
-
headers: signedMsg.headers,
|
|
692
|
-
redirect: "follow",
|
|
693
|
-
}
|
|
694
|
-
if (
|
|
695
|
-
signedMsg.body !== undefined &&
|
|
696
|
-
signedMsg.method !== "GET" &&
|
|
697
|
-
signedMsg.method !== "HEAD"
|
|
698
|
-
) {
|
|
699
|
-
fetchOptions.body = signedMsg.body
|
|
700
|
-
}
|
|
701
|
-
const response = await fetchImpl(signedMsg.url, fetchOptions)
|
|
702
|
-
|
|
703
|
-
if (response.status >= 400) {
|
|
704
|
-
throw new Error(`${response.status}: ${await response.text()}`)
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
let headers = {}
|
|
708
|
-
if (response.headers && typeof response.headers.forEach === "function") {
|
|
709
|
-
response.headers.forEach((v, k) => (headers[k] = v))
|
|
710
|
-
} else headers = response.headers
|
|
711
|
-
|
|
712
|
-
return {
|
|
713
|
-
response,
|
|
714
|
-
headers,
|
|
715
|
-
body: await response.text(),
|
|
716
|
-
status: response.status,
|
|
717
|
-
}
|
|
718
|
-
}
|
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
import assert from "assert"
|
|
2
2
|
import { describe, it, before, after, beforeEach } from "node:test"
|
|
3
|
-
import { wait, toAddr } from "wao/test"
|
|
4
|
-
import {
|
|
3
|
+
import { HyperBEAM, wait, toAddr } from "wao/test"
|
|
4
|
+
import { HB } from "wao"
|
|
5
5
|
import { resolve } from "path"
|
|
6
6
|
import { readFileSync } from "fs"
|
|
7
7
|
|
|
8
|
-
const cwd = "
|
|
8
|
+
const cwd = "../dev/wao/HyperBEAM"
|
|
9
9
|
const wallet = ".wallet.json"
|
|
10
10
|
|
|
11
11
|
describe("HyperBEAM", function () {
|
|
12
12
|
let hbeam, hb, jwk
|
|
13
13
|
|
|
14
14
|
before(async () => {
|
|
15
|
-
hbeam = new HyperBEAM({ cwd, wallet })
|
|
16
|
-
jwk = JSON.parse(
|
|
17
|
-
readFileSync(resolve(import.meta.dirname, cwd, wallet), "utf8")
|
|
18
|
-
)
|
|
19
|
-
await wait(5000)
|
|
15
|
+
hbeam = await new HyperBEAM({ c: "12", cmake: "3.5", cwd, wallet }).ready()
|
|
16
|
+
jwk = JSON.parse(readFileSync(resolve(process.cwd(), cwd, wallet), "utf8"))
|
|
20
17
|
})
|
|
21
18
|
|
|
22
19
|
beforeEach(async () => (hb = await new HB({}).init(jwk)))
|
|
@@ -24,7 +21,7 @@ describe("HyperBEAM", function () {
|
|
|
24
21
|
after(async () => hbeam.kill())
|
|
25
22
|
|
|
26
23
|
it("should run a HyperBEAM node", async () => {
|
|
27
|
-
const info = await hb.meta.info
|
|
24
|
+
const info = await hb.getJSON({ path: "/~meta@1.0/info" })
|
|
28
25
|
assert.equal(info.address, toAddr(jwk.n))
|
|
29
26
|
})
|
|
30
27
|
})
|