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,289 @@
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
+ return Buffer.from(obj, "utf8")
65
+ } else {
66
+ // In string mode, strings stay as strings
67
+ return obj
68
+ }
69
+ }
70
+
71
+ // Other primitives pass through
72
+ if (typeof obj === "number" || typeof obj === "boolean") {
73
+ return obj
74
+ }
75
+
76
+ // Buffers pass through
77
+ if (obj instanceof Buffer || obj instanceof Uint8Array) {
78
+ return Buffer.from(obj)
79
+ }
80
+
81
+ // Arrays - normalize each element
82
+ if (Array.isArray(obj)) {
83
+ return obj.map(item => {
84
+ // undefined in arrays becomes null (JSON behavior)
85
+ if (item === undefined) {
86
+ return null
87
+ }
88
+ return normalize(item, binaryMode)
89
+ })
90
+ }
91
+
92
+ // Objects - normalize each value and remove undefined values
93
+ if (typeof obj === "object" && obj !== null) {
94
+ const result = {}
95
+ for (const [k, v] of Object.entries(obj)) {
96
+ const normalized = normalize(v, binaryMode)
97
+ // Skip undefined values in objects
98
+ if (normalized !== undefined) {
99
+ result[k] = normalized
100
+ }
101
+ }
102
+ return result
103
+ }
104
+
105
+ return obj
106
+ }
107
+
108
+ // Convert from annotated JSON to JS with Erlang types
109
+ function convertFromAnnotatedJSON(json) {
110
+ // Handle null/undefined
111
+ if (json === null) {
112
+ return null
113
+ }
114
+
115
+ if (json === undefined) {
116
+ return undefined
117
+ }
118
+
119
+ // Handle booleans
120
+ if (json === true || json === false) {
121
+ return json
122
+ }
123
+
124
+ // Handle numbers and strings
125
+ if (typeof json === "number") {
126
+ return json
127
+ }
128
+
129
+ if (typeof json === "string") {
130
+ // Check for binary structured field format :base64:
131
+ if (json.startsWith(":") && json.endsWith(":") && json.length >= 2) {
132
+ try {
133
+ // Handle empty binary special case
134
+ if (json === "::") {
135
+ return Buffer.alloc(0)
136
+ }
137
+ // Simple base64 decode for structured field binaries
138
+ const base64 = json.slice(1, -1)
139
+ return Buffer.from(base64, "base64")
140
+ } catch (e) {
141
+ // Not valid base64, return as-is
142
+ return json
143
+ }
144
+ }
145
+ // Check for token structured field format %token%
146
+ if (json.startsWith("%") && json.endsWith("%") && json.length >= 2) {
147
+ // Handle empty token special case
148
+ if (json === "%%") {
149
+ return Symbol.for("")
150
+ }
151
+ // Extract token and convert to symbol
152
+ const token = json.slice(1, -1)
153
+ return Symbol.for(token)
154
+ }
155
+ // Handle empty string from empty binary conversion
156
+ if (json === "") {
157
+ // In context, this might be an empty binary, but we can't be sure
158
+ // so we return it as-is
159
+ return json
160
+ }
161
+ return json
162
+ }
163
+
164
+ // Handle arrays
165
+ if (Array.isArray(json)) {
166
+ return json.map(item => convertFromAnnotatedJSON(item))
167
+ }
168
+
169
+ // Handle objects
170
+ if (json && typeof json === "object") {
171
+ const keys = Object.keys(json)
172
+
173
+ // Check for type annotations (single key objects)
174
+ if (keys.length === 1) {
175
+ const [key] = keys
176
+ const value = json[key]
177
+
178
+ switch (key) {
179
+ case "$empty":
180
+ switch (value) {
181
+ case "binary":
182
+ return Buffer.alloc(0)
183
+ case "list":
184
+ return []
185
+ case "map":
186
+ return {}
187
+ default:
188
+ return json
189
+ }
190
+ }
191
+ }
192
+
193
+ // Regular object - convert recursively
194
+ const result = {}
195
+ for (const [k, v] of Object.entries(json)) {
196
+ result[k] = convertFromAnnotatedJSON(v)
197
+ }
198
+ return result
199
+ }
200
+
201
+ // Fallback
202
+ return json
203
+ }
204
+
205
+ // Convert from JS with Erlang types to annotated JSON
206
+ function convertToAnnotatedJSON(obj) {
207
+ // Handle null - pass through as JSON null
208
+ if (obj === null) {
209
+ return null
210
+ }
211
+
212
+ // Handle undefined - convert to null (JSON doesn't support undefined)
213
+ if (obj === undefined) {
214
+ return null
215
+ }
216
+
217
+ // Handle booleans - pass through as JSON booleans
218
+ if (typeof obj === "boolean") {
219
+ return obj
220
+ }
221
+
222
+ // Handle numbers - pass through
223
+ if (typeof obj === "number") {
224
+ return obj
225
+ }
226
+
227
+ // Handle strings - pass through
228
+ if (typeof obj === "string") {
229
+ return obj
230
+ }
231
+
232
+ // Handle symbols (atoms) - use token structured field format
233
+ if (typeof obj === "symbol") {
234
+ // Try to get the key from the global symbol registry
235
+ const key = Symbol.keyFor(obj)
236
+ // If it's not a global symbol, use its description
237
+ const name = key || obj.description || obj.toString().slice(7, -1)
238
+
239
+ // Special atoms that become JSON values
240
+ if (name === "null") return null
241
+ if (name === "true") return true
242
+ if (name === "false") return false
243
+
244
+ // Other atoms become token structured fields
245
+ return `%${name}%`
246
+ }
247
+
248
+ // Handle Buffers/Uint8Arrays (binaries)
249
+ if (obj instanceof Buffer || obj instanceof Uint8Array) {
250
+ if (obj.length === 0) {
251
+ return { $empty: "binary" }
252
+ }
253
+ // Use structured fields format for binaries
254
+ return `:${Buffer.from(obj).toString("base64")}:`
255
+ }
256
+
257
+ // Handle arrays
258
+ if (Array.isArray(obj)) {
259
+ if (obj.length === 0) {
260
+ return { $empty: "list" }
261
+ }
262
+ return obj.map(item => convertToAnnotatedJSON(item))
263
+ }
264
+
265
+ // Handle objects
266
+ if (typeof obj === "object" && obj !== null) {
267
+ const result = {}
268
+ let hasKeys = false
269
+
270
+ for (const [k, v] of Object.entries(obj)) {
271
+ // Skip undefined values completely
272
+ if (v === undefined) {
273
+ continue
274
+ }
275
+ hasKeys = true
276
+ result[k] = convertToAnnotatedJSON(v)
277
+ }
278
+
279
+ // Return $empty annotation if no keys remain
280
+ if (!hasKeys) {
281
+ return { $empty: "map" }
282
+ }
283
+
284
+ return result
285
+ }
286
+
287
+ // Fallback
288
+ return obj
289
+ }