hbsig 0.0.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.
Files changed (44) hide show
  1. package/cjs/bin_to_str.js +44 -0
  2. package/cjs/collect-body-keys.js +470 -0
  3. package/cjs/encode-array-item.js +110 -0
  4. package/cjs/encode-utils.js +236 -0
  5. package/cjs/encode.js +1318 -0
  6. package/cjs/erl_json.js +317 -0
  7. package/cjs/erl_str.js +1037 -0
  8. package/cjs/flat.js +222 -0
  9. package/cjs/http-message-signatures/httpbis.js +489 -0
  10. package/cjs/http-message-signatures/index.js +25 -0
  11. package/cjs/http-message-signatures/structured-header.js +129 -0
  12. package/cjs/httpsig.js +716 -0
  13. package/cjs/httpsig2.js +1160 -0
  14. package/cjs/id.js +470 -0
  15. package/cjs/index.js +63 -0
  16. package/cjs/send.js +194 -0
  17. package/cjs/signer-utils.js +617 -0
  18. package/cjs/signer.js +606 -0
  19. package/cjs/structured.js +296 -0
  20. package/cjs/test.js +27 -0
  21. package/cjs/utils.js +42 -0
  22. package/esm/bin_to_str.js +46 -0
  23. package/esm/collect-body-keys.js +436 -0
  24. package/esm/encode-array-item.js +112 -0
  25. package/esm/encode-utils.js +185 -0
  26. package/esm/encode.js +1219 -0
  27. package/esm/erl_json.js +289 -0
  28. package/esm/erl_str.js +1139 -0
  29. package/esm/flat.js +196 -0
  30. package/esm/http-message-signatures/httpbis.js +438 -0
  31. package/esm/http-message-signatures/index.js +4 -0
  32. package/esm/http-message-signatures/structured-header.js +105 -0
  33. package/esm/httpsig.js +658 -0
  34. package/esm/httpsig2.js +1097 -0
  35. package/esm/id.js +459 -0
  36. package/esm/index.js +4 -0
  37. package/esm/package.json +3 -0
  38. package/esm/send.js +124 -0
  39. package/esm/signer-utils.js +494 -0
  40. package/esm/signer.js +452 -0
  41. package/esm/structured.js +269 -0
  42. package/esm/test.js +6 -0
  43. package/esm/utils.js +28 -0
  44. package/package.json +28 -0
@@ -0,0 +1,436 @@
1
+ import {
2
+ hasNonAscii,
3
+ sha256,
4
+ hasNewline,
5
+ isBytes,
6
+ isPojo,
7
+ } from "./encode-utils.js"
8
+
9
+ // Helper functions
10
+ const isEmpty = value => {
11
+ if (typeof value === "string") return value === ""
12
+ if (Array.isArray(value)) return value.length === 0
13
+ if (isPojo(value)) return Object.keys(value).length === 0
14
+ if (isBytes(value)) return value.length === 0 || value.byteLength === 0
15
+ return false
16
+ }
17
+
18
+ const hasOnlyEmptyValues = obj => {
19
+ if (!isPojo(obj)) return false
20
+ return Object.values(obj).every(
21
+ v =>
22
+ (typeof v === "string" && v === "") ||
23
+ (Array.isArray(v) && v.length === 0) ||
24
+ (isPojo(v) && Object.keys(v).length === 0)
25
+ )
26
+ }
27
+
28
+ const isSimpleValue = value => {
29
+ return (
30
+ typeof value === "string" ||
31
+ typeof value === "number" ||
32
+ typeof value === "boolean" ||
33
+ value === null ||
34
+ value === undefined ||
35
+ typeof value === "symbol"
36
+ )
37
+ }
38
+
39
+ const getValueByPath = (obj, path) => {
40
+ const parts = path.split("/")
41
+ let value = obj
42
+ for (const part of parts) {
43
+ if (/^\d+$/.test(part)) {
44
+ value = value[parseInt(part) - 1]
45
+ } else {
46
+ value = value[part]
47
+ }
48
+ }
49
+ return value
50
+ }
51
+
52
+ const canArrayBeInHeader = array => {
53
+ // Empty arrays can be in headers
54
+ if (array.length === 0) return true
55
+
56
+ // Arrays with objects must go to body
57
+ if (array.some(item => isPojo(item))) return false
58
+
59
+ // Arrays with binary data must go to body
60
+ if (array.some(item => isBytes(item) && item.length > 0)) return false
61
+
62
+ // Arrays with non-ASCII strings must go to body
63
+ if (array.some(item => typeof item === "string" && hasNonAscii(item)))
64
+ return false
65
+
66
+ // Arrays with nested arrays must go to body (to match original behavior)
67
+ if (array.some(item => Array.isArray(item))) return false
68
+
69
+ // Arrays with nested arrays that have objects must go to body
70
+ if (
71
+ array.some(
72
+ item => Array.isArray(item) && item.some(subItem => isPojo(subItem))
73
+ )
74
+ )
75
+ return false
76
+
77
+ // Simple arrays of primitives can stay in headers
78
+ return true
79
+ }
80
+
81
+ // Array analysis helper
82
+ const analyzeArray = array => {
83
+ const analysis = {
84
+ hasObjects: false,
85
+ hasArrays: false,
86
+ hasNonObjects: false,
87
+ hasArraysOfObjects: false,
88
+ hasEmptyStrings: false,
89
+ hasEmptyObjects: false,
90
+ hasNonEmptyObjects: false,
91
+ hasObjectsWithOnlyEmptyValues: false,
92
+ hasOnlyEmptyElements: true,
93
+ }
94
+
95
+ for (const item of array) {
96
+ if (isPojo(item)) {
97
+ analysis.hasObjects = true
98
+ if (Object.keys(item).length === 0) {
99
+ analysis.hasEmptyObjects = true
100
+ } else {
101
+ analysis.hasNonEmptyObjects = true
102
+ if (hasOnlyEmptyValues(item)) {
103
+ analysis.hasObjectsWithOnlyEmptyValues = true
104
+ }
105
+ }
106
+ } else if (Array.isArray(item)) {
107
+ analysis.hasArrays = true
108
+ if (item.some(subItem => isPojo(subItem))) {
109
+ analysis.hasArraysOfObjects = true
110
+ }
111
+ } else {
112
+ analysis.hasNonObjects = true
113
+ }
114
+
115
+ if (typeof item === "string" && item === "") {
116
+ analysis.hasEmptyStrings = true
117
+ }
118
+
119
+ if (!isEmpty(item)) {
120
+ analysis.hasOnlyEmptyElements = false
121
+ }
122
+ }
123
+
124
+ return analysis
125
+ }
126
+
127
+ // Body key collector class
128
+ class BodyKeyCollector {
129
+ constructor(obj) {
130
+ this.obj = obj
131
+ this.keys = []
132
+ }
133
+
134
+ collect() {
135
+ this.processRootObject()
136
+ return this.deduplicateAndFilter()
137
+ }
138
+
139
+ // Process top-level object
140
+ processRootObject() {
141
+ const objKeys = Object.keys(this.obj)
142
+
143
+ for (const [key, value] of Object.entries(this.obj)) {
144
+ if (this.isSpecialDataBodyField(key, value, objKeys)) {
145
+ this.keys.push(key)
146
+ } else if (Array.isArray(value) && value.length > 0) {
147
+ // Check if array can stay in header
148
+ if (!canArrayBeInHeader(value)) {
149
+ this.processRootArray(key, value)
150
+ }
151
+ } else if (isPojo(value)) {
152
+ this.processRootNestedObject(key, value)
153
+ } else if (this.needsBodyKey(key, value)) {
154
+ this.keys.push(key)
155
+ }
156
+ }
157
+ }
158
+
159
+ // Check if field is special data/body field
160
+ isSpecialDataBodyField(key, value, objKeys) {
161
+ if (
162
+ (key === "data" || key === "body") &&
163
+ isSimpleValue(value) &&
164
+ objKeys.length > 1
165
+ ) {
166
+ const otherKey = key === "data" ? "body" : "data"
167
+ const otherValue = this.obj[otherKey]
168
+
169
+ if (
170
+ otherValue &&
171
+ isPojo(otherValue) &&
172
+ Object.keys(otherValue).length > 0
173
+ ) {
174
+ return false
175
+ }
176
+ return true
177
+ }
178
+ return false
179
+ }
180
+
181
+ // Check if value needs a body key
182
+ needsBodyKey(key, value) {
183
+ return (
184
+ (isBytes(value) && value.length > 0) ||
185
+ (typeof value === "string" && value.includes("\n")) ||
186
+ (typeof value === "string" && hasNonAscii(value))
187
+ )
188
+ }
189
+
190
+ // Process root-level arrays
191
+ processRootArray(key, array) {
192
+ const analysis = analyzeArray(array)
193
+ let bodyPartCounter = 1
194
+
195
+ if (analysis.hasArraysOfObjects) {
196
+ // Handle arrays of arrays containing objects
197
+ array.forEach((item, index) => {
198
+ if (Array.isArray(item)) {
199
+ item.forEach((subItem, subIndex) => {
200
+ if (isPojo(subItem)) {
201
+ this.keys.push(`${key}/${index + 1}/${subIndex + 1}`)
202
+ }
203
+ })
204
+ }
205
+ })
206
+ this.keys.push(key)
207
+ } else if (
208
+ analysis.hasObjects &&
209
+ (analysis.hasEmptyStrings || analysis.hasEmptyObjects) &&
210
+ !analysis.hasObjectsWithOnlyEmptyValues
211
+ ) {
212
+ // Mixed array: only non-empty objects get parts
213
+ array.forEach(item => {
214
+ if (isPojo(item) && Object.keys(item).length > 0) {
215
+ const path = `${key}/${bodyPartCounter}`
216
+ this.keys.push(path)
217
+ this.addNestedObjectPaths(item, path)
218
+ }
219
+ bodyPartCounter++
220
+ })
221
+ this.keys.push(key)
222
+ } else if (analysis.hasObjects) {
223
+ // Regular array with objects
224
+ const skipEmptyObjects = analysis.hasOnlyEmptyElements
225
+
226
+ array.forEach(item => {
227
+ if (isPojo(item)) {
228
+ if (!(skipEmptyObjects && Object.keys(item).length === 0)) {
229
+ const path = `${key}/${bodyPartCounter}`
230
+ this.keys.push(path)
231
+ if (Object.keys(item).length > 0) {
232
+ this.addNestedObjectPaths(item, path)
233
+ }
234
+ }
235
+ } else if (typeof item === "string" && item === "") {
236
+ this.keys.push(`${key}/${bodyPartCounter}`)
237
+ }
238
+ bodyPartCounter++
239
+ })
240
+
241
+ // Add main array key conditionally
242
+ if (!analysis.hasObjectsWithOnlyEmptyValues || analysis.hasNonObjects) {
243
+ if (!analysis.hasOnlyEmptyElements) {
244
+ this.keys.push(key)
245
+ }
246
+ }
247
+ } else {
248
+ // Simple array without objects
249
+ const hasOnlyEmptyArraysOrObjects = array.every(
250
+ item =>
251
+ isEmpty(item) &&
252
+ (Array.isArray(item) || isPojo(item) || typeof item === "string")
253
+ )
254
+
255
+ if (hasOnlyEmptyArraysOrObjects || !array.every(isEmpty)) {
256
+ this.keys.push(key)
257
+ }
258
+ }
259
+ }
260
+
261
+ // Add paths for nested objects within an object
262
+ addNestedObjectPaths(obj, basePath) {
263
+ for (const [nestedKey, nestedValue] of Object.entries(obj)) {
264
+ if (isPojo(nestedValue) && Object.keys(nestedValue).length > 0) {
265
+ this.keys.push(`${basePath}/${nestedKey}`)
266
+ }
267
+ }
268
+ }
269
+
270
+ // Process root-level nested object
271
+ processRootNestedObject(key, obj) {
272
+ // Check for arrays with only empty elements
273
+ for (const [k, v] of Object.entries(obj)) {
274
+ if (Array.isArray(v) && v.length > 0 && v.every(isEmpty)) {
275
+ this.keys.push(`${key}/${k}`)
276
+ }
277
+ }
278
+
279
+ // Traverse the object
280
+ this.traverse(obj, key)
281
+ }
282
+
283
+ // Traverse nested structures
284
+ traverse(current, path) {
285
+ let hasSimpleFields = false
286
+ const nestedPaths = []
287
+ let hasArraysWithObjects = false
288
+
289
+ // First pass: analyze structure
290
+ for (const [key, value] of Object.entries(current)) {
291
+ const fullPath = path ? `${path}/${key}` : key
292
+ const result = this.analyzeFieldInTraversal(value, fullPath, nestedPaths)
293
+
294
+ hasSimpleFields = hasSimpleFields || result.hasSimpleFields
295
+ hasArraysWithObjects = hasArraysWithObjects || result.hasArraysWithObjects
296
+ }
297
+
298
+ // Add current path if needed
299
+ if (hasSimpleFields || (hasArraysWithObjects && path)) {
300
+ this.keys.push(path)
301
+ }
302
+
303
+ // Second pass: check for arrays with only empty elements
304
+ for (const [key, value] of Object.entries(current)) {
305
+ const fullPath = path ? `${path}/${key}` : key
306
+ if (Array.isArray(value) && value.length > 0 && value.every(isEmpty)) {
307
+ this.keys.push(fullPath)
308
+ }
309
+ }
310
+
311
+ // Traverse nested objects
312
+ for (const nestedPath of nestedPaths) {
313
+ const nestedObj = getValueByPath(this.obj, nestedPath)
314
+ if (isPojo(nestedObj)) {
315
+ this.traverse(nestedObj, nestedPath)
316
+ }
317
+ }
318
+ }
319
+
320
+ // Analyze a field during traversal
321
+ analyzeFieldInTraversal(value, fullPath, nestedPaths) {
322
+ let hasSimpleFields = false
323
+ let hasArraysWithObjects = false
324
+
325
+ if (Array.isArray(value)) {
326
+ if (value.length === 0) {
327
+ hasSimpleFields = true
328
+ } else {
329
+ const analysis = analyzeArray(value)
330
+
331
+ if (analysis.hasObjects) {
332
+ hasArraysWithObjects = true
333
+ this.processArrayInTraversal(value, fullPath, nestedPaths, analysis)
334
+
335
+ if (analysis.hasNonObjects) {
336
+ hasSimpleFields = true
337
+ // Only add to keys if array can't be in header
338
+ if (!canArrayBeInHeader(value)) {
339
+ this.keys.push(fullPath)
340
+ }
341
+ }
342
+ } else {
343
+ hasSimpleFields = true
344
+ }
345
+ }
346
+ } else if (isPojo(value)) {
347
+ if (Object.keys(value).length === 0) {
348
+ hasSimpleFields = true
349
+ } else if (hasOnlyEmptyValues(value)) {
350
+ this.keys.push(fullPath)
351
+ } else {
352
+ const hasArraysWithOnlyEmptyElements = Object.entries(value).some(
353
+ ([k, v]) => Array.isArray(v) && v.length > 0 && v.every(isEmpty)
354
+ )
355
+
356
+ if (hasArraysWithOnlyEmptyElements) {
357
+ this.keys.push(fullPath)
358
+ }
359
+ nestedPaths.push(fullPath)
360
+ }
361
+ } else if ((isBytes(value) && value.length > 0) || isSimpleValue(value)) {
362
+ hasSimpleFields = true
363
+ }
364
+
365
+ return { hasSimpleFields, hasArraysWithObjects }
366
+ }
367
+
368
+ // Process arrays found during traversal
369
+ processArrayInTraversal(array, fullPath, nestedPaths, analysis) {
370
+ if (
371
+ (analysis.hasEmptyStrings || analysis.hasEmptyObjects) &&
372
+ analysis.hasNonEmptyObjects
373
+ ) {
374
+ // Special case: only non-empty objects get parts
375
+ array.forEach((item, index) => {
376
+ if (isPojo(item) && Object.keys(item).length > 0) {
377
+ const itemPath = `${fullPath}/${index + 1}`
378
+ this.keys.push(itemPath)
379
+ nestedPaths.push(itemPath)
380
+ }
381
+ })
382
+ } else if (
383
+ analysis.hasObjectsWithOnlyEmptyValues &&
384
+ !analysis.hasNonObjects
385
+ ) {
386
+ // Objects with only empty values
387
+ array.forEach((item, index) => {
388
+ if (isPojo(item)) {
389
+ const itemPath = `${fullPath}/${index + 1}`
390
+ this.keys.push(itemPath)
391
+ if (Object.keys(item).length > 0) {
392
+ nestedPaths.push(itemPath)
393
+ }
394
+ }
395
+ })
396
+ } else {
397
+ // Normal case
398
+ array.forEach((item, index) => {
399
+ if (isPojo(item)) {
400
+ const itemPath = `${fullPath}/${index + 1}`
401
+ this.keys.push(itemPath)
402
+ if (Object.keys(item).length > 0) {
403
+ nestedPaths.push(itemPath)
404
+ }
405
+ }
406
+ })
407
+ }
408
+ }
409
+
410
+ // Deduplicate and filter results
411
+ deduplicateAndFilter() {
412
+ const uniqueKeys = [...new Set(this.keys)]
413
+
414
+ return uniqueKeys.filter(k => {
415
+ if (!k) return false
416
+
417
+ // Check if this is a path to an element inside an array with only empty elements
418
+ const parts = k.split("/")
419
+ if (parts.length >= 2 && /^\d+$/.test(parts[parts.length - 1])) {
420
+ const arrayPath = parts.slice(0, -1).join("/")
421
+ const arrayValue = getValueByPath(this.obj, arrayPath)
422
+
423
+ if (Array.isArray(arrayValue) && arrayValue.every(isEmpty)) {
424
+ return false
425
+ }
426
+ }
427
+
428
+ return true
429
+ })
430
+ }
431
+ }
432
+
433
+ export default function collectBodyKeys(obj, prefix = "") {
434
+ const collector = new BodyKeyCollector(obj)
435
+ return collector.collect()
436
+ }
@@ -0,0 +1,112 @@
1
+ import { toBuffer, formatFloat, isBytes, isPojo } from "./encode-utils.js"
2
+
3
+ // Helper to generate the correct number of backslashes for a given nesting level
4
+ function getBackslashes(level) {
5
+ return "\\".repeat(Math.pow(2, level) - 1)
6
+ }
7
+
8
+ // Helper to encode primitive values at a specific nesting level
9
+ function encodePrimitiveAtLevel(value, level) {
10
+ const bs = getBackslashes(level)
11
+
12
+ if (typeof value === "number") {
13
+ if (Number.isInteger(value)) {
14
+ return `${bs}"(ao-type-integer) ${value}${bs}"`
15
+ } else {
16
+ return `${bs}"(ao-type-float) ${formatFloat(value)}${bs}"`
17
+ }
18
+ } else if (typeof value === "string") {
19
+ return `${bs}"${value}${bs}"`
20
+ } else if (value === null) {
21
+ const innerBs = getBackslashes(level + 1)
22
+ return `${bs}"(ao-type-atom) ${innerBs}"null${innerBs}"${bs}"`
23
+ } else if (value === undefined) {
24
+ const innerBs = getBackslashes(level + 1)
25
+ return `${bs}"(ao-type-atom) ${innerBs}"undefined${innerBs}"${bs}"`
26
+ } else if (typeof value === "symbol") {
27
+ const desc = value.description || "Symbol.for()"
28
+ const innerBs = getBackslashes(level + 1)
29
+ return `${bs}"(ao-type-atom) ${innerBs}"${desc}${innerBs}"${bs}"`
30
+ } else if (typeof value === "boolean") {
31
+ const innerBs = getBackslashes(level + 1)
32
+ return `${bs}"(ao-type-atom) ${innerBs}"${value}${innerBs}"${bs}"`
33
+ } else {
34
+ return `${bs}"${String(value)}${bs}"`
35
+ }
36
+ }
37
+
38
+ // Recursive function to handle arrays at any nesting level
39
+ function encodeArrayAtLevel(items, level) {
40
+ // The original code only goes 3 levels deep for arrays
41
+ if (level >= 3) {
42
+ const bs = getBackslashes(level)
43
+ const stringItems = items
44
+ .map(item => {
45
+ if (typeof item === "number") {
46
+ if (Number.isInteger(item)) {
47
+ return `${bs}"(ao-type-integer) ${item}${bs}"`
48
+ } else {
49
+ return `${bs}"(ao-type-float) ${formatFloat(item)}${bs}"`
50
+ }
51
+ } else if (typeof item === "string") {
52
+ return `${bs}"${item}${bs}"`
53
+ } else {
54
+ return `${bs}"${String(item)}${bs}"`
55
+ }
56
+ })
57
+ .join(", ")
58
+ return `${getBackslashes(level - 1)}"(ao-type-list) ${stringItems}${getBackslashes(level - 1)}"`
59
+ }
60
+
61
+ const encodedItems = items
62
+ .map(item => {
63
+ if (Array.isArray(item)) {
64
+ return encodeArrayAtLevel(item, level + 1)
65
+ } else {
66
+ return encodePrimitiveAtLevel(item, level)
67
+ }
68
+ })
69
+ .join(", ")
70
+
71
+ if (level === 0) {
72
+ return `"(ao-type-list) ${encodedItems}"`
73
+ } else {
74
+ const bs = getBackslashes(level - 1)
75
+ return `${bs}"(ao-type-list) ${encodedItems}${bs}"`
76
+ }
77
+ }
78
+
79
+ export default function encodeArrayItem(item) {
80
+ if (typeof item === "number") {
81
+ if (Number.isInteger(item)) {
82
+ return `"(ao-type-integer) ${item}"`
83
+ } else {
84
+ return `"(ao-type-float) ${formatFloat(item)}"`
85
+ }
86
+ } else if (typeof item === "string") {
87
+ return `"${item}"`
88
+ } else if (item === null) {
89
+ return `"(ao-type-atom) \\"null\\""`
90
+ } else if (item === undefined) {
91
+ return `"(ao-type-atom) \\"undefined\\""`
92
+ } else if (typeof item === "symbol") {
93
+ const desc = item.description || "Symbol.for()"
94
+ return `"(ao-type-atom) \\"${desc}\\""`
95
+ } else if (typeof item === "boolean") {
96
+ return `"(ao-type-atom) \\"${item}\\""`
97
+ } else if (Array.isArray(item)) {
98
+ return encodeArrayAtLevel(item, 1)
99
+ } else if (isBytes(item)) {
100
+ const buffer = toBuffer(item)
101
+ if (buffer.length === 0 || buffer.byteLength === 0) {
102
+ return `""`
103
+ }
104
+ return `"(ao-type-binary)"`
105
+ } else if (isPojo(item)) {
106
+ const json = JSON.stringify(item)
107
+ const escaped = json.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
108
+ return `"(ao-type-map) ${escaped}"`
109
+ } else {
110
+ return `"${String(item)}"`
111
+ }
112
+ }