hbsig 0.3.2 → 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.
Files changed (95) hide show
  1. package/.babelrc-cjs +5 -0
  2. package/.babelrc-esm +5 -0
  3. package/README.md +1 -0
  4. package/dist/package.json +39 -0
  5. package/make.js +36 -0
  6. package/package.json +16 -17
  7. package/src/bin_to_str.js +46 -0
  8. package/src/collect-body-keys.js +436 -0
  9. package/src/commit.js +219 -0
  10. package/src/encode-array-item.js +112 -0
  11. package/src/encode-utils.js +191 -0
  12. package/src/encode.js +1256 -0
  13. package/src/erl_json.js +292 -0
  14. package/src/erl_str.js +1144 -0
  15. package/src/flat.js +250 -0
  16. package/src/http-message-signatures/httpbis.js +438 -0
  17. package/src/http-message-signatures/index.js +4 -0
  18. package/src/http-message-signatures/structured-header.js +105 -0
  19. package/src/httpsig.js +866 -0
  20. package/src/id.js +459 -0
  21. package/src/index.js +13 -0
  22. package/src/nocrypto.js +4 -0
  23. package/src/parser.js +171 -0
  24. package/src/send-utils.js +1132 -0
  25. package/src/send.js +142 -0
  26. package/src/signer-utils.js +375 -0
  27. package/src/signer.js +312 -0
  28. package/src/structured.js +496 -0
  29. package/src/test.js +2 -0
  30. package/src/utils.js +29 -0
  31. package/test/commit.test.js +41 -0
  32. package/test/erl_json.test.js +8 -0
  33. package/test/flat.test.js +27 -0
  34. package/test/httpsig.test.js +31 -0
  35. package/test/id.test.js +114 -0
  36. package/test/lib/all_cases.js +408 -0
  37. package/test/lib/cases.js +408 -0
  38. package/test/lib/erl_json_cases.js +161 -0
  39. package/test/lib/flat_cases.js +189 -0
  40. package/test/lib/gen.js +528 -0
  41. package/test/lib/httpsig_cases.js +313 -0
  42. package/test/lib/structured_cases.js +222 -0
  43. package/test/lib/test-utils.js +399 -0
  44. package/test/signer.test.js +48 -0
  45. package/test/structured.test.js +35 -0
  46. package/bin/install-deps +0 -0
  47. /package/{cjs → dist/cjs}/bin_to_str.js +0 -0
  48. /package/{cjs → dist/cjs}/collect-body-keys.js +0 -0
  49. /package/{cjs → dist/cjs}/commit.js +0 -0
  50. /package/{cjs → dist/cjs}/encode-array-item.js +0 -0
  51. /package/{cjs → dist/cjs}/encode-utils.js +0 -0
  52. /package/{cjs → dist/cjs}/encode.js +0 -0
  53. /package/{cjs → dist/cjs}/erl_json.js +0 -0
  54. /package/{cjs → dist/cjs}/erl_str.js +0 -0
  55. /package/{cjs → dist/cjs}/flat.js +0 -0
  56. /package/{cjs → dist/cjs}/http-message-signatures/httpbis.js +0 -0
  57. /package/{cjs → dist/cjs}/http-message-signatures/index.js +0 -0
  58. /package/{cjs → dist/cjs}/http-message-signatures/structured-header.js +0 -0
  59. /package/{cjs → dist/cjs}/httpsig.js +0 -0
  60. /package/{cjs → dist/cjs}/id.js +0 -0
  61. /package/{cjs → dist/cjs}/index.js +0 -0
  62. /package/{cjs → dist/cjs}/nocrypto.js +0 -0
  63. /package/{cjs → dist/cjs}/parser.js +0 -0
  64. /package/{cjs → dist/cjs}/send-utils.js +0 -0
  65. /package/{cjs → dist/cjs}/send.js +0 -0
  66. /package/{cjs → dist/cjs}/signer-utils.js +0 -0
  67. /package/{cjs → dist/cjs}/signer.js +0 -0
  68. /package/{cjs → dist/cjs}/structured.js +0 -0
  69. /package/{cjs → dist/cjs}/test.js +0 -0
  70. /package/{cjs → dist/cjs}/utils.js +0 -0
  71. /package/{esm → dist/esm}/bin_to_str.js +0 -0
  72. /package/{esm → dist/esm}/collect-body-keys.js +0 -0
  73. /package/{esm → dist/esm}/commit.js +0 -0
  74. /package/{esm → dist/esm}/encode-array-item.js +0 -0
  75. /package/{esm → dist/esm}/encode-utils.js +0 -0
  76. /package/{esm → dist/esm}/encode.js +0 -0
  77. /package/{esm → dist/esm}/erl_json.js +0 -0
  78. /package/{esm → dist/esm}/erl_str.js +0 -0
  79. /package/{esm → dist/esm}/flat.js +0 -0
  80. /package/{esm → dist/esm}/http-message-signatures/httpbis.js +0 -0
  81. /package/{esm → dist/esm}/http-message-signatures/index.js +0 -0
  82. /package/{esm → dist/esm}/http-message-signatures/structured-header.js +0 -0
  83. /package/{esm → dist/esm}/httpsig.js +0 -0
  84. /package/{esm → dist/esm}/id.js +0 -0
  85. /package/{esm → dist/esm}/index.js +0 -0
  86. /package/{esm → dist/esm}/nocrypto.js +0 -0
  87. /package/{esm → dist/esm}/package.json +0 -0
  88. /package/{esm → dist/esm}/parser.js +0 -0
  89. /package/{esm → dist/esm}/send-utils.js +0 -0
  90. /package/{esm → dist/esm}/send.js +0 -0
  91. /package/{esm → dist/esm}/signer-utils.js +0 -0
  92. /package/{esm → dist/esm}/signer.js +0 -0
  93. /package/{esm → dist/esm}/structured.js +0 -0
  94. /package/{esm → dist/esm}/test.js +0 -0
  95. /package/{esm → dist/esm}/utils.js +0 -0
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Codec for converting between JS objects with Erlang types and annotated JSON
3
+ * Only handles types that HyperBEAM supports
4
+ */
5
+
6
+ /**
7
+ * Convert annotated JSON to JS object with Erlang types
8
+ * @param {Object} json - Annotated JSON object
9
+ * @returns {*} - JS object with Buffer for binaries, Symbol for atoms
10
+ */
11
+ export function erl_json_from(json) {
12
+ return convertFromAnnotatedJSON(json)
13
+ }
14
+
15
+ /**
16
+ * Convert JS object with Erlang types to annotated JSON
17
+ * @param {*} jsObj - JS object with Erlang types
18
+ * @returns {Object} - Annotated JSON object
19
+ */
20
+ export function erl_json_to(jsObj) {
21
+ return convertToAnnotatedJSON(jsObj)
22
+ }
23
+
24
+ /**
25
+ * Normalize JS values to match what comes back from Erlang through erl_str_from
26
+ * This function is deterministic and matches the behavior of the Erlang round-trip
27
+ * @param {*} obj - JS object to normalize
28
+ * @param {boolean} binaryMode - true for binary mode, false for string mode (default)
29
+ * @returns {*} - Normalized JS object
30
+ */
31
+ export function normalize(obj, binaryMode = false) {
32
+ if (obj === null) return null
33
+ if (obj === undefined) return undefined
34
+
35
+ // Handle symbols - convert to their special cases or match erl_str_from behavior
36
+ if (typeof obj === "symbol") {
37
+ const key = Symbol.keyFor(obj)
38
+ const name = key || obj.description || obj.toString().slice(7, -1)
39
+
40
+ // Special symbols that become primitives
41
+ if (name === "null") return null
42
+ if (name === "true") return true
43
+ if (name === "false") return false
44
+
45
+ // Note: Symbol('undefined') stays as Symbol.for('undefined')
46
+ // It does NOT become JavaScript undefined
47
+ // This matches what erl_str_from actually returns
48
+
49
+ // For all symbols (including 'undefined'), convert to global symbol
50
+ // This is because non-global symbols can't round-trip through JSON
51
+ // Symbol('ok') -> '%ok%' -> Symbol.for('ok')
52
+ return Symbol.for(name)
53
+ }
54
+
55
+ // Strings
56
+ if (typeof obj === "string") {
57
+ // Special case: "::" becomes empty buffer through Erlang
58
+ if (obj === "::") {
59
+ return Buffer.alloc(0)
60
+ }
61
+
62
+ if (binaryMode) {
63
+ // In binary mode, convert strings to buffers
64
+ // Use binary/latin1 encoding only if all chars are <= 255 (preserves raw bytes)
65
+ // Use UTF-8 for strings with chars > 255 (proper multi-byte encoding)
66
+ const needsUtf8 = [...obj].some(ch => ch.codePointAt(0) > 255)
67
+ return Buffer.from(obj, needsUtf8 ? "utf8" : "binary")
68
+ } else {
69
+ // In string mode, strings stay as strings
70
+ return obj
71
+ }
72
+ }
73
+
74
+ // Other primitives pass through
75
+ if (typeof obj === "number" || typeof obj === "boolean") {
76
+ return obj
77
+ }
78
+
79
+ // Buffers pass through
80
+ if (obj instanceof Buffer || obj instanceof Uint8Array) {
81
+ return Buffer.from(obj)
82
+ }
83
+
84
+ // Arrays - normalize each element
85
+ if (Array.isArray(obj)) {
86
+ return obj.map(item => {
87
+ // undefined in arrays becomes null (JSON behavior)
88
+ if (item === undefined) {
89
+ return null
90
+ }
91
+ return normalize(item, binaryMode)
92
+ })
93
+ }
94
+
95
+ // Objects - normalize each value and remove undefined values
96
+ if (typeof obj === "object" && obj !== null) {
97
+ const result = {}
98
+ for (const [k, v] of Object.entries(obj)) {
99
+ const normalized = normalize(v, binaryMode)
100
+ // Skip undefined values in objects
101
+ if (normalized !== undefined) {
102
+ result[k] = normalized
103
+ }
104
+ }
105
+ return result
106
+ }
107
+
108
+ return obj
109
+ }
110
+
111
+ // Convert from annotated JSON to JS with Erlang types
112
+ function convertFromAnnotatedJSON(json) {
113
+ // Handle null/undefined
114
+ if (json === null) {
115
+ return null
116
+ }
117
+
118
+ if (json === undefined) {
119
+ return undefined
120
+ }
121
+
122
+ // Handle booleans
123
+ if (json === true || json === false) {
124
+ return json
125
+ }
126
+
127
+ // Handle numbers and strings
128
+ if (typeof json === "number") {
129
+ return json
130
+ }
131
+
132
+ if (typeof json === "string") {
133
+ // Check for binary structured field format :base64:
134
+ if (json.startsWith(":") && json.endsWith(":") && json.length >= 2) {
135
+ try {
136
+ // Handle empty binary special case
137
+ if (json === "::") {
138
+ return Buffer.alloc(0)
139
+ }
140
+ // Simple base64 decode for structured field binaries
141
+ const base64 = json.slice(1, -1)
142
+ return Buffer.from(base64, "base64")
143
+ } catch (e) {
144
+ // Not valid base64, return as-is
145
+ return json
146
+ }
147
+ }
148
+ // Check for token structured field format %token%
149
+ if (json.startsWith("%") && json.endsWith("%") && json.length >= 2) {
150
+ // Handle empty token special case
151
+ if (json === "%%") {
152
+ return Symbol.for("")
153
+ }
154
+ // Extract token and convert to symbol
155
+ const token = json.slice(1, -1)
156
+ return Symbol.for(token)
157
+ }
158
+ // Handle empty string from empty binary conversion
159
+ if (json === "") {
160
+ // In context, this might be an empty binary, but we can't be sure
161
+ // so we return it as-is
162
+ return json
163
+ }
164
+ return json
165
+ }
166
+
167
+ // Handle arrays
168
+ if (Array.isArray(json)) {
169
+ return json.map(item => convertFromAnnotatedJSON(item))
170
+ }
171
+
172
+ // Handle objects
173
+ if (json && typeof json === "object") {
174
+ const keys = Object.keys(json)
175
+
176
+ // Check for type annotations (single key objects)
177
+ if (keys.length === 1) {
178
+ const [key] = keys
179
+ const value = json[key]
180
+
181
+ switch (key) {
182
+ case "$empty":
183
+ switch (value) {
184
+ case "binary":
185
+ return Buffer.alloc(0)
186
+ case "list":
187
+ return []
188
+ case "map":
189
+ return {}
190
+ default:
191
+ return json
192
+ }
193
+ }
194
+ }
195
+
196
+ // Regular object - convert recursively
197
+ const result = {}
198
+ for (const [k, v] of Object.entries(json)) {
199
+ result[k] = convertFromAnnotatedJSON(v)
200
+ }
201
+ return result
202
+ }
203
+
204
+ // Fallback
205
+ return json
206
+ }
207
+
208
+ // Convert from JS with Erlang types to annotated JSON
209
+ function convertToAnnotatedJSON(obj) {
210
+ // Handle null - pass through as JSON null
211
+ if (obj === null) {
212
+ return null
213
+ }
214
+
215
+ // Handle undefined - convert to null (JSON doesn't support undefined)
216
+ if (obj === undefined) {
217
+ return null
218
+ }
219
+
220
+ // Handle booleans - pass through as JSON booleans
221
+ if (typeof obj === "boolean") {
222
+ return obj
223
+ }
224
+
225
+ // Handle numbers - pass through
226
+ if (typeof obj === "number") {
227
+ return obj
228
+ }
229
+
230
+ // Handle strings - pass through
231
+ if (typeof obj === "string") {
232
+ return obj
233
+ }
234
+
235
+ // Handle symbols (atoms) - use token structured field format
236
+ if (typeof obj === "symbol") {
237
+ // Try to get the key from the global symbol registry
238
+ const key = Symbol.keyFor(obj)
239
+ // If it's not a global symbol, use its description
240
+ const name = key || obj.description || obj.toString().slice(7, -1)
241
+
242
+ // Special atoms that become JSON values
243
+ if (name === "null") return null
244
+ if (name === "true") return true
245
+ if (name === "false") return false
246
+
247
+ // Other atoms become token structured fields
248
+ return `%${name}%`
249
+ }
250
+
251
+ // Handle Buffers/Uint8Arrays (binaries)
252
+ if (obj instanceof Buffer || obj instanceof Uint8Array) {
253
+ if (obj.length === 0) {
254
+ return { $empty: "binary" }
255
+ }
256
+ // Use structured fields format for binaries
257
+ return `:${Buffer.from(obj).toString("base64")}:`
258
+ }
259
+
260
+ // Handle arrays
261
+ if (Array.isArray(obj)) {
262
+ if (obj.length === 0) {
263
+ return { $empty: "list" }
264
+ }
265
+ return obj.map(item => convertToAnnotatedJSON(item))
266
+ }
267
+
268
+ // Handle objects
269
+ if (typeof obj === "object" && obj !== null) {
270
+ const result = {}
271
+ let hasKeys = false
272
+
273
+ for (const [k, v] of Object.entries(obj)) {
274
+ // Skip undefined values completely
275
+ if (v === undefined) {
276
+ continue
277
+ }
278
+ hasKeys = true
279
+ result[k] = convertToAnnotatedJSON(v)
280
+ }
281
+
282
+ // Return $empty annotation if no keys remain
283
+ if (!hasKeys) {
284
+ return { $empty: "map" }
285
+ }
286
+
287
+ return result
288
+ }
289
+
290
+ // Fallback
291
+ return obj
292
+ }