hbsig 0.3.1 → 0.3.3
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/.babelrc-cjs +5 -0
- package/.babelrc-esm +5 -0
- package/README.md +1 -0
- package/dist/package.json +39 -0
- package/make.js +36 -0
- package/package.json +16 -16
- package/src/bin_to_str.js +46 -0
- package/src/collect-body-keys.js +436 -0
- package/src/commit.js +219 -0
- package/src/encode-array-item.js +112 -0
- package/src/encode-utils.js +191 -0
- package/src/encode.js +1256 -0
- package/src/erl_json.js +292 -0
- package/src/erl_str.js +1144 -0
- package/src/flat.js +250 -0
- package/src/http-message-signatures/httpbis.js +438 -0
- package/src/http-message-signatures/index.js +4 -0
- package/src/http-message-signatures/structured-header.js +105 -0
- package/src/httpsig.js +866 -0
- package/src/id.js +459 -0
- package/src/index.js +13 -0
- package/src/nocrypto.js +4 -0
- package/src/parser.js +171 -0
- package/src/send-utils.js +1132 -0
- package/src/send.js +142 -0
- package/src/signer-utils.js +375 -0
- package/src/signer.js +312 -0
- package/src/structured.js +496 -0
- package/src/test.js +2 -0
- package/src/utils.js +29 -0
- package/test/commit.test.js +41 -0
- package/test/erl_json.test.js +8 -0
- package/test/flat.test.js +27 -0
- package/test/httpsig.test.js +31 -0
- package/test/id.test.js +114 -0
- package/test/lib/all_cases.js +408 -0
- package/test/lib/cases.js +408 -0
- package/test/lib/erl_json_cases.js +161 -0
- package/test/lib/flat_cases.js +189 -0
- package/test/lib/gen.js +528 -0
- package/test/lib/httpsig_cases.js +313 -0
- package/test/lib/structured_cases.js +222 -0
- package/test/lib/test-utils.js +399 -0
- package/test/signer.test.js +48 -0
- package/test/structured.test.js +35 -0
- /package/{cjs → dist/cjs}/bin_to_str.js +0 -0
- /package/{cjs → dist/cjs}/collect-body-keys.js +0 -0
- /package/{cjs → dist/cjs}/commit.js +0 -0
- /package/{cjs → dist/cjs}/encode-array-item.js +0 -0
- /package/{cjs → dist/cjs}/encode-utils.js +0 -0
- /package/{cjs → dist/cjs}/encode.js +0 -0
- /package/{cjs → dist/cjs}/erl_json.js +0 -0
- /package/{cjs → dist/cjs}/erl_str.js +0 -0
- /package/{cjs → dist/cjs}/flat.js +0 -0
- /package/{cjs → dist/cjs}/http-message-signatures/httpbis.js +0 -0
- /package/{cjs → dist/cjs}/http-message-signatures/index.js +0 -0
- /package/{cjs → dist/cjs}/http-message-signatures/structured-header.js +0 -0
- /package/{cjs → dist/cjs}/httpsig.js +0 -0
- /package/{cjs → dist/cjs}/id.js +0 -0
- /package/{cjs → dist/cjs}/index.js +0 -0
- /package/{cjs → dist/cjs}/nocrypto.js +0 -0
- /package/{cjs → dist/cjs}/parser.js +0 -0
- /package/{cjs → dist/cjs}/send-utils.js +0 -0
- /package/{cjs → dist/cjs}/send.js +0 -0
- /package/{cjs → dist/cjs}/signer-utils.js +0 -0
- /package/{cjs → dist/cjs}/signer.js +0 -0
- /package/{cjs → dist/cjs}/structured.js +0 -0
- /package/{cjs → dist/cjs}/test.js +0 -0
- /package/{cjs → dist/cjs}/utils.js +0 -0
- /package/{esm → dist/esm}/bin_to_str.js +0 -0
- /package/{esm → dist/esm}/collect-body-keys.js +0 -0
- /package/{esm → dist/esm}/commit.js +0 -0
- /package/{esm → dist/esm}/encode-array-item.js +0 -0
- /package/{esm → dist/esm}/encode-utils.js +0 -0
- /package/{esm → dist/esm}/encode.js +0 -0
- /package/{esm → dist/esm}/erl_json.js +0 -0
- /package/{esm → dist/esm}/erl_str.js +0 -0
- /package/{esm → dist/esm}/flat.js +0 -0
- /package/{esm → dist/esm}/http-message-signatures/httpbis.js +0 -0
- /package/{esm → dist/esm}/http-message-signatures/index.js +0 -0
- /package/{esm → dist/esm}/http-message-signatures/structured-header.js +0 -0
- /package/{esm → dist/esm}/httpsig.js +0 -0
- /package/{esm → dist/esm}/id.js +0 -0
- /package/{esm → dist/esm}/index.js +0 -0
- /package/{esm → dist/esm}/nocrypto.js +0 -0
- /package/{esm → dist/esm}/package.json +0 -0
- /package/{esm → dist/esm}/parser.js +0 -0
- /package/{esm → dist/esm}/send-utils.js +0 -0
- /package/{esm → dist/esm}/send.js +0 -0
- /package/{esm → dist/esm}/signer-utils.js +0 -0
- /package/{esm → dist/esm}/signer.js +0 -0
- /package/{esm → dist/esm}/structured.js +0 -0
- /package/{esm → dist/esm}/test.js +0 -0
- /package/{esm → dist/esm}/utils.js +0 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { send } from "../../src/send.js"
|
|
2
|
+
import { erl_json_to, normalize } from "../../src/erl_json.js"
|
|
3
|
+
import { erl_str_from } from "../../src/erl_str.js"
|
|
4
|
+
import assert from "assert"
|
|
5
|
+
import { describe, it, before, after } from "node:test"
|
|
6
|
+
import { HyperBEAM } from "../../../src/test.js"
|
|
7
|
+
import { createSigner } from "../../src/signer.js"
|
|
8
|
+
|
|
9
|
+
function mod(obj) {
|
|
10
|
+
// Handle undefined - convert to string "undefined"
|
|
11
|
+
if (obj === undefined) return "undefined"
|
|
12
|
+
|
|
13
|
+
// Handle symbols - convert to their description or "symbol"
|
|
14
|
+
if (typeof obj === "symbol") {
|
|
15
|
+
const desc = obj.description || "symbol"
|
|
16
|
+
// Special handling for symbols with descriptions that match special values
|
|
17
|
+
if (desc === "null") return null
|
|
18
|
+
if (desc === "undefined") return undefined
|
|
19
|
+
if (desc === "true") return true
|
|
20
|
+
if (desc === "false") return false
|
|
21
|
+
return desc
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Handle arrays
|
|
25
|
+
if (Array.isArray(obj)) {
|
|
26
|
+
return obj.map(item => mod(item))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Handle binary data (Buffer, Uint8Array, etc.)
|
|
30
|
+
if (
|
|
31
|
+
obj instanceof Uint8Array ||
|
|
32
|
+
obj instanceof ArrayBuffer ||
|
|
33
|
+
Buffer.isBuffer(obj)
|
|
34
|
+
) {
|
|
35
|
+
// Convert to empty string for empty buffers
|
|
36
|
+
const buffer = Buffer.isBuffer(obj) ? obj : Buffer.from(obj)
|
|
37
|
+
return buffer.length === 0 ? "" : buffer.toString("base64")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Handle objects
|
|
41
|
+
if (typeof obj === "object" && obj !== null) {
|
|
42
|
+
const result = {}
|
|
43
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
44
|
+
// Lowercase the key when creating the result object
|
|
45
|
+
result[key.toLowerCase()] = mod(value)
|
|
46
|
+
}
|
|
47
|
+
return result
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Handle strings - check if it's already an atom-like string
|
|
51
|
+
if (typeof obj === "string" && obj.match(/^[a-z_][a-zA-Z0-9_]*$/)) {
|
|
52
|
+
// This looks like an atom value, keep as is
|
|
53
|
+
return obj
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Return primitive values as-is (strings, numbers, booleans, null)
|
|
57
|
+
return obj
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Recursively transform values to match expected format, removing undefined values
|
|
61
|
+
function mod2(obj) {
|
|
62
|
+
// Handle undefined - return undefined to signal removal
|
|
63
|
+
if (obj === undefined) return undefined
|
|
64
|
+
|
|
65
|
+
// Handle symbols - convert to their description or "symbol"
|
|
66
|
+
if (typeof obj === "symbol") {
|
|
67
|
+
const desc = obj.description || "symbol"
|
|
68
|
+
// Special handling for symbols with descriptions that match special values
|
|
69
|
+
if (desc === "null") return null
|
|
70
|
+
if (desc === "undefined") return undefined // This will be removed
|
|
71
|
+
if (desc === "true") return true
|
|
72
|
+
if (desc === "false") return false
|
|
73
|
+
return desc
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Handle arrays - filter out undefined values
|
|
77
|
+
if (Array.isArray(obj)) {
|
|
78
|
+
return obj.map(item => mod2(item)).filter(item => item !== undefined)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Handle binary data (Buffer, Uint8Array, etc.)
|
|
82
|
+
if (
|
|
83
|
+
obj instanceof Uint8Array ||
|
|
84
|
+
obj instanceof ArrayBuffer ||
|
|
85
|
+
Buffer.isBuffer(obj)
|
|
86
|
+
) {
|
|
87
|
+
// Convert to empty string for empty buffers
|
|
88
|
+
const buffer = Buffer.isBuffer(obj) ? obj : Buffer.from(obj)
|
|
89
|
+
return buffer.length === 0 ? "" : buffer.toString("base64")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Handle objects - remove undefined properties
|
|
93
|
+
if (typeof obj === "object" && obj !== null) {
|
|
94
|
+
const result = {}
|
|
95
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
96
|
+
const modifiedValue = mod2(value)
|
|
97
|
+
// Only add the property if the value is not undefined
|
|
98
|
+
if (modifiedValue !== undefined) {
|
|
99
|
+
// Lowercase the key when creating the result object
|
|
100
|
+
result[key.toLowerCase()] = modifiedValue
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return result
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Handle strings - check if it's already an atom-like string
|
|
107
|
+
if (typeof obj === "string" && obj.match(/^[a-z_][a-zA-Z0-9_]*$/)) {
|
|
108
|
+
// This looks like an atom value, keep as is
|
|
109
|
+
return obj
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Return primitive values as-is (strings, numbers, booleans, null)
|
|
113
|
+
return obj
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Helper to recursively remove ao-types fields without applying type conversions
|
|
117
|
+
const removeAoTypesField = obj => {
|
|
118
|
+
if (obj === null || obj === undefined) return obj
|
|
119
|
+
if (typeof obj !== "object") return obj
|
|
120
|
+
if (Buffer.isBuffer(obj)) return obj
|
|
121
|
+
if (Array.isArray(obj)) return obj.map(removeAoTypesField)
|
|
122
|
+
|
|
123
|
+
const result = {}
|
|
124
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
125
|
+
if (key !== "ao-types") {
|
|
126
|
+
result[key] = removeAoTypesField(value)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return result
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const test = async (sign, cases, path, mod = v => v, pmod = v => v, skipAoTypes = false, removeAoTypes = false) => {
|
|
133
|
+
let err = []
|
|
134
|
+
let success = []
|
|
135
|
+
let i = 0
|
|
136
|
+
for (const v of cases) {
|
|
137
|
+
console.log(`[${++i}]...........................................`, v)
|
|
138
|
+
try {
|
|
139
|
+
const _pmod = pmod(v)
|
|
140
|
+
const json = erl_json_to(_pmod)
|
|
141
|
+
const signed = await sign({ path, body: JSON.stringify(json) })
|
|
142
|
+
const { out } = await send(signed)
|
|
143
|
+
const input = normalize(_pmod)
|
|
144
|
+
const output = erl_str_from(out)
|
|
145
|
+
let expected = normalize(mod(_pmod), true)
|
|
146
|
+
// Apply ao-types conversions to output (unless skipAoTypes is true for flat/structured codec tests)
|
|
147
|
+
let output_b = skipAoTypes ? erl_str_from(out, true) : applyAoTypes(erl_str_from(out, true))
|
|
148
|
+
// For tests that need ao-types removed but not converted (e.g., flat codec)
|
|
149
|
+
// Apply the same transformation to both expected and actual for fair comparison
|
|
150
|
+
if (removeAoTypes) {
|
|
151
|
+
output_b = removeAoTypesField(output_b)
|
|
152
|
+
expected = removeAoTypesField(expected)
|
|
153
|
+
}
|
|
154
|
+
// DEBUG: Print comparison on failure
|
|
155
|
+
try {
|
|
156
|
+
assert.deepEqual(expected, output_b)
|
|
157
|
+
} catch (assertErr) {
|
|
158
|
+
const stringify = (o) => JSON.stringify(o, (k,v) => {
|
|
159
|
+
if (Buffer.isBuffer(v)) return v.toString('utf-8')
|
|
160
|
+
if (v?.type === 'Buffer' && Array.isArray(v?.data)) return Buffer.from(v.data).toString('utf-8')
|
|
161
|
+
if (typeof v === 'symbol') return `Symbol(${v.description})`
|
|
162
|
+
return v
|
|
163
|
+
}, 2)
|
|
164
|
+
console.log("DEBUG expected:", stringify(expected))
|
|
165
|
+
console.log("DEBUG actual:", stringify(output_b))
|
|
166
|
+
throw assertErr
|
|
167
|
+
}
|
|
168
|
+
success.push(v)
|
|
169
|
+
} catch (e) {
|
|
170
|
+
console.log(e)
|
|
171
|
+
err.push(v)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
console.log(`${err.length} / ${cases.length} failed!`)
|
|
175
|
+
if (err.length > 0) {
|
|
176
|
+
for (let v of err) console.log(v)
|
|
177
|
+
throw new Error(`${err.length} test case(s) failed`)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const genTest = ({ desc = "HyperBEAM", its = [] }) => {
|
|
182
|
+
describe(desc, function () {
|
|
183
|
+
let hbeam, sign
|
|
184
|
+
before(async () => {
|
|
185
|
+
hbeam = await new HyperBEAM({ reset: true, linkify_mode: false }).ready()
|
|
186
|
+
sign = createSigner(hbeam.jwk, hbeam.url)
|
|
187
|
+
})
|
|
188
|
+
after(async () => hbeam.kill())
|
|
189
|
+
for (const v of its) {
|
|
190
|
+
const testFn = v.skip ? it.skip : it
|
|
191
|
+
testFn(
|
|
192
|
+
v.it ?? "should run",
|
|
193
|
+
async () =>
|
|
194
|
+
await test(
|
|
195
|
+
sign,
|
|
196
|
+
v.cases,
|
|
197
|
+
v.path ?? "/~hbsig@1.0/json_to_erl",
|
|
198
|
+
v.mod,
|
|
199
|
+
v.pmod,
|
|
200
|
+
v.skipAoTypes ?? false,
|
|
201
|
+
v.removeAoTypes ?? false
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Recursive helper to apply ao-types conversions
|
|
209
|
+
const applyAoTypes = obj => {
|
|
210
|
+
if (obj === null || obj === undefined) return obj
|
|
211
|
+
if (typeof obj !== "object") return obj
|
|
212
|
+
if (Buffer.isBuffer(obj)) return obj
|
|
213
|
+
if (Array.isArray(obj)) return obj.map(applyAoTypes)
|
|
214
|
+
|
|
215
|
+
// First, recursively process nested objects (to handle their ao-types)
|
|
216
|
+
for (const key of Object.keys(obj)) {
|
|
217
|
+
if (key !== "ao-types") {
|
|
218
|
+
obj[key] = applyAoTypes(obj[key])
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
// Process ao-types in this object
|
|
224
|
+
let aoTypesRaw = obj["ao-types"]
|
|
225
|
+
// Convert Buffer to string if needed
|
|
226
|
+
const aoTypes =
|
|
227
|
+
Buffer.isBuffer(aoTypesRaw) ? aoTypesRaw.toString() : aoTypesRaw
|
|
228
|
+
|
|
229
|
+
// Check if ao-types looks like a structured field dictionary (contains key="value" patterns)
|
|
230
|
+
// If it's just a user value like "test", we should preserve it
|
|
231
|
+
const isAoTypesDictionary =
|
|
232
|
+
aoTypes &&
|
|
233
|
+
typeof aoTypes === "string" &&
|
|
234
|
+
/[^=,\s]+="[^"]+"/g.test(aoTypes)
|
|
235
|
+
|
|
236
|
+
if (isAoTypesDictionary) {
|
|
237
|
+
const typeMatches = aoTypes.matchAll(/([^=,\s]+)="([^"]+)"/g)
|
|
238
|
+
for (const match of typeMatches) {
|
|
239
|
+
let key = match[1]
|
|
240
|
+
// Handle dot (.) which means the object itself is a list
|
|
241
|
+
if (key === ".") continue
|
|
242
|
+
|
|
243
|
+
// URL-decode the key (e.g., data%46ield -> dataField)
|
|
244
|
+
const decodedKey = decodeURIComponent(key)
|
|
245
|
+
|
|
246
|
+
// Find the actual key in the object (case-insensitive match)
|
|
247
|
+
const lowerKey = decodedKey.toLowerCase()
|
|
248
|
+
const actualKey =
|
|
249
|
+
Object.keys(obj).find(k => k.toLowerCase() === lowerKey) || lowerKey
|
|
250
|
+
const type = match[2]
|
|
251
|
+
const value = obj[actualKey]
|
|
252
|
+
|
|
253
|
+
// Handle empty types (for keys that don't exist)
|
|
254
|
+
if (!(actualKey in obj)) {
|
|
255
|
+
if (type === "empty-binary") {
|
|
256
|
+
obj[actualKey] = Buffer.from([])
|
|
257
|
+
} else if (type === "empty-list") {
|
|
258
|
+
obj[actualKey] = []
|
|
259
|
+
} else if (type === "empty-message") {
|
|
260
|
+
obj[actualKey] = {}
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
// Convert existing values to their proper types
|
|
264
|
+
// Handle both strings and Buffers
|
|
265
|
+
const strValue = Buffer.isBuffer(value) ? value.toString() : value
|
|
266
|
+
if (type === "integer" && typeof strValue === "string") {
|
|
267
|
+
obj[actualKey] = parseInt(strValue, 10)
|
|
268
|
+
} else if (type === "float" && typeof strValue === "string") {
|
|
269
|
+
obj[actualKey] = parseFloat(strValue)
|
|
270
|
+
} else if (type === "atom" && typeof strValue === "string") {
|
|
271
|
+
if (strValue === "true") {
|
|
272
|
+
obj[actualKey] = true
|
|
273
|
+
} else if (strValue === "false") {
|
|
274
|
+
obj[actualKey] = false
|
|
275
|
+
} else if (strValue === "null") {
|
|
276
|
+
obj[actualKey] = null
|
|
277
|
+
} else if (strValue === "undefined") {
|
|
278
|
+
obj[actualKey] = undefined
|
|
279
|
+
} else {
|
|
280
|
+
// General atoms (like "ok") become global Symbols
|
|
281
|
+
obj[actualKey] = Symbol.for(strValue)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Check if the whole object should be converted to an array (. = "list")
|
|
288
|
+
if (aoTypes.includes('.="list"')) {
|
|
289
|
+
const keys = Object.keys(obj).filter(k => k !== "ao-types")
|
|
290
|
+
const isArrayLike = keys.every(k => /^\d+$/.test(k))
|
|
291
|
+
if (isArrayLike && keys.length > 0) {
|
|
292
|
+
const arr = []
|
|
293
|
+
const sortedKeys = keys.sort((a, b) => parseInt(a, 10) - parseInt(b, 10))
|
|
294
|
+
for (const k of sortedKeys) {
|
|
295
|
+
arr.push(obj[k])
|
|
296
|
+
}
|
|
297
|
+
return arr
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Only delete ao-types if it was a real type annotation dictionary
|
|
302
|
+
delete obj["ao-types"]
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return obj
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const modOut = out => {
|
|
309
|
+
let output = erl_str_from(out)
|
|
310
|
+
|
|
311
|
+
// Handle inline-body-key: rename 'body' to the key specified by inline-body-key
|
|
312
|
+
// But only if the key is different from 'body' (otherwise we'd delete it)
|
|
313
|
+
const inlineBodyKey = output["inline-body-key"]
|
|
314
|
+
if (inlineBodyKey && inlineBodyKey !== "body" && output.body !== undefined) {
|
|
315
|
+
output[inlineBodyKey] = output.body
|
|
316
|
+
delete output.body
|
|
317
|
+
}
|
|
318
|
+
// Special case: inline-body-key: body with undefined body means empty body
|
|
319
|
+
// This is how encode.js signals an empty body field
|
|
320
|
+
if (inlineBodyKey === "body" && output.body === undefined) {
|
|
321
|
+
output.body = Buffer.from([])
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Handle ao-body-key similarly
|
|
325
|
+
const aoBodyKey = output["ao-body-key"]
|
|
326
|
+
if (aoBodyKey && output.body !== undefined && !inlineBodyKey) {
|
|
327
|
+
output[aoBodyKey] = output.body
|
|
328
|
+
delete output.body
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Apply ao-types conversions recursively (handles type conversions and array reconstruction)
|
|
332
|
+
output = applyAoTypes(output)
|
|
333
|
+
|
|
334
|
+
// Delete TABM metadata fields
|
|
335
|
+
delete output.commitments
|
|
336
|
+
delete output.path
|
|
337
|
+
delete output.method
|
|
338
|
+
delete output["content-length"]
|
|
339
|
+
delete output["content-type"]
|
|
340
|
+
delete output["inline-body-key"]
|
|
341
|
+
delete output["ao-body-key"]
|
|
342
|
+
delete output["body-keys"]
|
|
343
|
+
// Delete HTTP request headers that Erlang includes in the response
|
|
344
|
+
delete output.accept
|
|
345
|
+
delete output["accept-bundle"]
|
|
346
|
+
delete output["accept-encoding"]
|
|
347
|
+
delete output["accept-language"]
|
|
348
|
+
delete output["sec-fetch-mode"]
|
|
349
|
+
delete output["user-agent"]
|
|
350
|
+
delete output.connection
|
|
351
|
+
delete output.host
|
|
352
|
+
delete output["content-digest"]
|
|
353
|
+
delete output.signature
|
|
354
|
+
delete output["signature-input"]
|
|
355
|
+
// Note: We no longer delete empty body buffers because they may be intentional
|
|
356
|
+
// (e.g., test case { body: Buffer.from([]) })
|
|
357
|
+
return output
|
|
358
|
+
}
|
|
359
|
+
const modIn = inp => {
|
|
360
|
+
let inp2 = normalize(inp)
|
|
361
|
+
|
|
362
|
+
// Recursive function to lowercase all object keys and convert empty strings to Buffer
|
|
363
|
+
const lowercaseKeys = (obj, isTopLevel = true) => {
|
|
364
|
+
// Handle null/undefined
|
|
365
|
+
if (obj === null || obj === undefined) {
|
|
366
|
+
return obj
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Handle empty strings - convert to empty Buffer
|
|
370
|
+
if (obj === "") {
|
|
371
|
+
return Buffer.from([])
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Handle arrays - recurse on each element
|
|
375
|
+
if (Array.isArray(obj)) {
|
|
376
|
+
return obj.map(item => lowercaseKeys(item, false))
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Handle objects - lowercase keys and recurse on values
|
|
380
|
+
if (typeof obj === "object" && obj.constructor === Object) {
|
|
381
|
+
const result = {}
|
|
382
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
383
|
+
// Lowercase the key and recurse on the value
|
|
384
|
+
result[key.toLowerCase()] = lowercaseKeys(value, false)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Note: The old data→body transformation is removed because with inline-body-key
|
|
388
|
+
// support, the signer now preserves original key names correctly.
|
|
389
|
+
|
|
390
|
+
return result
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Return other primitive values as-is
|
|
394
|
+
return obj
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return lowercaseKeys(inp2, true)
|
|
398
|
+
}
|
|
399
|
+
export { mod, mod2, test, genTest, modOut, modIn }
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import assert from "assert"
|
|
2
|
+
import { after, describe, it, before, beforeEach } from "node:test"
|
|
3
|
+
import { modIn, modOut } from "./lib/test-utils.js"
|
|
4
|
+
import { verify } from "../src/signer-utils.js"
|
|
5
|
+
import { HyperBEAM } from "../../src/test.js"
|
|
6
|
+
import cases from "./lib/cases.js"
|
|
7
|
+
import { normalize } from "../src/erl_json.js"
|
|
8
|
+
import { createSigner } from "../src/signer.js"
|
|
9
|
+
import { send } from "../src/send.js"
|
|
10
|
+
import { erl_str_from, erl_str_to } from "../src/erl_str.js"
|
|
11
|
+
|
|
12
|
+
describe("Hyperbeam Signer", function () {
|
|
13
|
+
let hbeam, sign
|
|
14
|
+
before(async () => {
|
|
15
|
+
hbeam = await new HyperBEAM({ reset: true, linkify_mode: false }).ready()
|
|
16
|
+
sign = createSigner(hbeam.jwk, hbeam.url)
|
|
17
|
+
})
|
|
18
|
+
after(async () => hbeam.kill())
|
|
19
|
+
|
|
20
|
+
it("should test signer", async () => {
|
|
21
|
+
let err = []
|
|
22
|
+
let success = []
|
|
23
|
+
let i = 0
|
|
24
|
+
let _cases = cases
|
|
25
|
+
for (const v of _cases) {
|
|
26
|
+
console.log(`[${++i}]...........................................`, v)
|
|
27
|
+
try {
|
|
28
|
+
const signed = await sign(
|
|
29
|
+
{ path: "/~hbsig@1.0/msg2", ...v },
|
|
30
|
+
{ path: false }
|
|
31
|
+
)
|
|
32
|
+
const { out } = await send(signed)
|
|
33
|
+
const output = modOut(out)
|
|
34
|
+
const exp = modIn(normalize(v))
|
|
35
|
+
assert.deepEqual(exp, output)
|
|
36
|
+
success.push(v)
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.log(e)
|
|
39
|
+
err.push(v)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
console.log(`${err.length} / ${_cases.length} failed!`)
|
|
43
|
+
if (err.length > 0) {
|
|
44
|
+
for (let v of err) console.log(v)
|
|
45
|
+
throw new Error(`${err.length} test case(s) failed`)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { structured_from, structured_to } from "../src/structured.js"
|
|
2
|
+
import { cases_from, cases_to } from "./lib/structured_cases.js"
|
|
3
|
+
import { ok } from "./lib/cases.js"
|
|
4
|
+
import { normalize } from "../src/erl_json.js"
|
|
5
|
+
import { genTest } from "./lib/test-utils.js"
|
|
6
|
+
|
|
7
|
+
genTest({
|
|
8
|
+
its: [
|
|
9
|
+
{
|
|
10
|
+
it: "should test structured_from (cases_from)",
|
|
11
|
+
path: "/~hbsig@1.0/structured_from",
|
|
12
|
+
cases: cases_from,
|
|
13
|
+
// For simple string-only cases, the output should match the input
|
|
14
|
+
// HB's structured codec doesn't add ao-types for pure string values
|
|
15
|
+
mod: v => normalize(v),
|
|
16
|
+
skipAoTypes: true,
|
|
17
|
+
removeAoTypes: true,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
it: "should test structured_from (ok cases)",
|
|
21
|
+
path: "/~hbsig@1.0/structured_from",
|
|
22
|
+
cases: ok,
|
|
23
|
+
// For complex cases, use structured_from to get expected TABM format
|
|
24
|
+
mod: v => structured_from(normalize(v)),
|
|
25
|
+
skipAoTypes: true,
|
|
26
|
+
removeAoTypes: true,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
it: "should test structured_to (cases_to)",
|
|
30
|
+
path: "/~hbsig@1.0/structured_to",
|
|
31
|
+
cases: cases_to,
|
|
32
|
+
mod: v => structured_to(normalize(v)),
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/{cjs → dist/cjs}/id.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/{esm → dist/esm}/id.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|