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.
- package/cjs/bin_to_str.js +44 -0
- package/cjs/collect-body-keys.js +470 -0
- package/cjs/encode-array-item.js +110 -0
- package/cjs/encode-utils.js +236 -0
- package/cjs/encode.js +1318 -0
- package/cjs/erl_json.js +317 -0
- package/cjs/erl_str.js +1037 -0
- package/cjs/flat.js +222 -0
- package/cjs/http-message-signatures/httpbis.js +489 -0
- package/cjs/http-message-signatures/index.js +25 -0
- package/cjs/http-message-signatures/structured-header.js +129 -0
- package/cjs/httpsig.js +716 -0
- package/cjs/httpsig2.js +1160 -0
- package/cjs/id.js +470 -0
- package/cjs/index.js +63 -0
- package/cjs/send.js +194 -0
- package/cjs/signer-utils.js +617 -0
- package/cjs/signer.js +606 -0
- package/cjs/structured.js +296 -0
- package/cjs/test.js +27 -0
- package/cjs/utils.js +42 -0
- package/esm/bin_to_str.js +46 -0
- package/esm/collect-body-keys.js +436 -0
- package/esm/encode-array-item.js +112 -0
- package/esm/encode-utils.js +185 -0
- package/esm/encode.js +1219 -0
- package/esm/erl_json.js +289 -0
- package/esm/erl_str.js +1139 -0
- package/esm/flat.js +196 -0
- package/esm/http-message-signatures/httpbis.js +438 -0
- package/esm/http-message-signatures/index.js +4 -0
- package/esm/http-message-signatures/structured-header.js +105 -0
- package/esm/httpsig.js +658 -0
- package/esm/httpsig2.js +1097 -0
- package/esm/id.js +459 -0
- package/esm/index.js +4 -0
- package/esm/package.json +3 -0
- package/esm/send.js +124 -0
- package/esm/signer-utils.js +494 -0
- package/esm/signer.js +452 -0
- package/esm/structured.js +269 -0
- package/esm/test.js +6 -0
- package/esm/utils.js +28 -0
- package/package.json +28 -0
package/esm/erl_json.js
ADDED
|
@@ -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
|
+
}
|