hbsig 0.2.8 → 0.3.0

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/commit.js CHANGED
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
 
3
- function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
3
  Object.defineProperty(exports, "__esModule", {
5
4
  value: true
6
5
  });
@@ -8,6 +7,8 @@ exports.commit = void 0;
8
7
  var _id = require("./id.js");
9
8
  var _utils = require("./utils.js");
10
9
  var _signerUtils = require("./signer-utils.js");
10
+ var _crypto = _interopRequireDefault(require("crypto"));
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
11
12
  function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
12
13
  function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
13
14
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
@@ -16,16 +17,58 @@ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object
16
17
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
17
18
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
18
19
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
19
- function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
20
- function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
21
20
  function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
22
21
  function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
22
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
23
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
24
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
25
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
26
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
27
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
28
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
29
+ // Helper to compute SHA-256 content-digest in RFC 9530 format
30
+ var computeContentDigest = function computeContentDigest(body) {
31
+ var bodyBuffer;
32
+ if (Buffer.isBuffer(body)) {
33
+ bodyBuffer = body;
34
+ } else if (body instanceof Blob) {
35
+ return null; // Can't compute synchronously for Blob
36
+ } else if (typeof body === "string") {
37
+ bodyBuffer = Buffer.from(body, "binary");
38
+ } else {
39
+ bodyBuffer = Buffer.from(String(body), "binary");
40
+ }
41
+ var hash = _crypto["default"].createHash("sha256").update(bodyBuffer).digest("base64");
42
+ return "sha-256=:".concat(hash, ":");
43
+ };
44
+
45
+ // Helper to build ao-types string from an object
46
+ var buildAoTypes = function buildAoTypes(obj) {
47
+ var types = [];
48
+ for (var _i = 0, _Object$entries = Object.entries(obj); _i < _Object$entries.length; _i++) {
49
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
50
+ key = _Object$entries$_i[0],
51
+ value = _Object$entries$_i[1];
52
+ if (typeof value === "number") {
53
+ types.push("".concat(key, "=\"").concat(Number.isInteger(value) ? "integer" : "float", "\""));
54
+ } else if (typeof value === "boolean") {
55
+ types.push("".concat(key, "=\"atom\""));
56
+ } else if (value === null) {
57
+ types.push("".concat(key, "=\"atom\""));
58
+ } else if (_typeof(value) === "symbol") {
59
+ // Symbols are Erlang atoms
60
+ types.push("".concat(key, "=\"atom\""));
61
+ }
62
+ }
63
+ return types.length > 0 ? types.join(", ") : null;
64
+ };
65
+
23
66
  // todo: handle @
24
67
  var commit = exports.commit = /*#__PURE__*/function () {
25
68
  var _ref = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(obj, opts) {
26
- var msg, _yield$verify, components, body, inlineBodyKey, _iterator, _step, v, key, bodyContent, arrayBuffer, hmacId, rsaId, pub, committer, meta, meta2, sigs, committed;
69
+ var msg, _yield$verify, components, body, inlineBodyKey, NATIVE_BODY_KEYS, isNativeBodyKey, headerLookup, _i2, _Object$entries2, _Object$entries2$_i, k, v, objLookup, _i3, _Object$entries3, _Object$entries3$_i, _k, _v, _iterator, _step, _v2, _key, originalValue, headerValue, bodyContent, arrayBuffer, committedSetLower, bodyKeysLower, _i4, _Object$entries4, _Object$entries4$_i, key, value, aoTypes, rsaId, pub, pubKeyBase64, committer, extractKeyidFromSigInput, keyid, committedFields, bodyFieldName, extractSignature, rawSignature, meta, sigs, committed, _t;
27
70
  return _regenerator().w(function (_context) {
28
- while (1) switch (_context.n) {
71
+ while (1) switch (_context.p = _context.n) {
29
72
  case 0:
30
73
  _context.n = 1;
31
74
  return opts.signer(obj, opts);
@@ -36,73 +79,250 @@ var commit = exports.commit = /*#__PURE__*/function () {
36
79
  case 2:
37
80
  _yield$verify = _context.v;
38
81
  components = _yield$verify.decodedSignatureInput.components;
39
- body = {}; // Check for inline-body-key
40
- inlineBodyKey = msg.headers["inline-body-key"]; // Build body from components
82
+ body = {}; // Check for inline-body-key (indicates a field was moved to HTTP body during encoding)
83
+ inlineBodyKey = msg.headers["inline-body-key"] || msg.headers["ao-body-key"]; // Body field names that HyperBEAM's inline_key() recognizes natively.
84
+ // For these, normalize_for_encoding() re-derives ao-body-key automatically.
85
+ // For custom names (e.g., "json"), we must keep ao-body-key in committed list
86
+ // so HyperBEAM knows which field to inline during verification.
87
+ NATIVE_BODY_KEYS = new Set(["body", "data"]);
88
+ isNativeBodyKey = !inlineBodyKey || NATIVE_BODY_KEYS.has(inlineBodyKey); // Build body from committed components.
89
+ // IMPORTANT: Use original typed values from obj (preserves integers, booleans, etc.)
90
+ // instead of string values from headers. This is critical because:
91
+ // 1. HTTP headers are always strings (e.g., quantity: 100 → "100")
92
+ // 2. We include ao-types to tell HyperBEAM the original types
93
+ // 3. HyperBEAM applies ao-types BEFORE signature verification
94
+ // 4. If we send strings, HyperBEAM converts to integers, breaking signature
95
+ // 5. If we send original integers, HyperBEAM re-encodes them as strings for verification
96
+ //
97
+ // Always skip content-digest and inline-body-key (transport artifacts re-derived by HyperBEAM).
98
+ // Skip ao-body-key only for native body keys (HyperBEAM re-derives it).
99
+ // Keep ao-body-key for custom body keys (HyperBEAM needs it to find the body field).
100
+ // Create case-insensitive lookup maps
101
+ headerLookup = new Map();
102
+ for (_i2 = 0, _Object$entries2 = Object.entries(msg.headers); _i2 < _Object$entries2.length; _i2++) {
103
+ _Object$entries2$_i = _slicedToArray(_Object$entries2[_i2], 2), k = _Object$entries2$_i[0], v = _Object$entries2$_i[1];
104
+ headerLookup.set(k.toLowerCase(), v);
105
+ }
106
+ // Case-insensitive lookup for original object values (preserves types)
107
+ objLookup = new Map();
108
+ for (_i3 = 0, _Object$entries3 = Object.entries(obj); _i3 < _Object$entries3.length; _i3++) {
109
+ _Object$entries3$_i = _slicedToArray(_Object$entries3[_i3], 2), _k = _Object$entries3$_i[0], _v = _Object$entries3$_i[1];
110
+ objLookup.set(_k.toLowerCase(), _v);
111
+ }
41
112
  _iterator = _createForOfIteratorHelper(components);
42
- try {
43
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
44
- v = _step.value;
45
- key = v === "@path" ? "path" : v;
46
- body[key] = msg.headers[key];
113
+ _context.p = 3;
114
+ _iterator.s();
115
+ case 4:
116
+ if ((_step = _iterator.n()).done) {
117
+ _context.n = 10;
118
+ break;
119
+ }
120
+ _v2 = _step.value;
121
+ _key = _v2 === "@path" ? "path" : _v2;
122
+ if (!(_key === "content-length")) {
123
+ _context.n = 5;
124
+ break;
125
+ }
126
+ return _context.a(3, 9);
127
+ case 5:
128
+ if (!(_key === "content-digest")) {
129
+ _context.n = 6;
130
+ break;
131
+ }
132
+ return _context.a(3, 9);
133
+ case 6:
134
+ if (!(_key === "inline-body-key")) {
135
+ _context.n = 7;
136
+ break;
137
+ }
138
+ return _context.a(3, 9);
139
+ case 7:
140
+ if (!(isNativeBodyKey && _key === "ao-body-key")) {
141
+ _context.n = 8;
142
+ break;
143
+ }
144
+ return _context.a(3, 9);
145
+ case 8:
146
+ // Prefer original value from input object (preserves integer/boolean/etc. types)
147
+ // Fall back to header string value if not found in original object
148
+ originalValue = objLookup.get(_key.toLowerCase());
149
+ if (originalValue !== undefined) {
150
+ body[_key] = originalValue;
151
+ } else {
152
+ headerValue = headerLookup.get(_key);
153
+ if (headerValue !== undefined) {
154
+ body[_key] = headerValue;
47
155
  }
48
-
49
- // Handle body resolution
50
- } catch (err) {
51
- _iterator.e(err);
52
- } finally {
53
- _iterator.f();
54
156
  }
157
+ case 9:
158
+ _context.n = 4;
159
+ break;
160
+ case 10:
161
+ _context.n = 12;
162
+ break;
163
+ case 11:
164
+ _context.p = 11;
165
+ _t = _context.v;
166
+ _iterator.e(_t);
167
+ case 12:
168
+ _context.p = 12;
169
+ _iterator.f();
170
+ return _context.f(12);
171
+ case 13:
172
+ // Handle body resolution - restore the inlined field to its AO-Core name
173
+ bodyContent = null;
55
174
  if (!msg.body) {
56
- _context.n = 6;
175
+ _context.n = 17;
57
176
  break;
58
177
  }
59
178
  if (!(msg.body instanceof Blob)) {
60
- _context.n = 4;
179
+ _context.n = 15;
61
180
  break;
62
181
  }
63
- _context.n = 3;
182
+ _context.n = 14;
64
183
  return msg.body.arrayBuffer();
65
- case 3:
184
+ case 14:
66
185
  arrayBuffer = _context.v;
67
186
  bodyContent = Buffer.from(arrayBuffer);
68
- _context.n = 5;
187
+ _context.n = 16;
69
188
  break;
70
- case 4:
189
+ case 15:
71
190
  bodyContent = msg.body;
72
- case 5:
73
- // If inline-body-key is "data", put content in data field
74
- if (inlineBodyKey === "data") {
75
- body.data = bodyContent;
191
+ case 16:
192
+ // Put body content under the original field name (e.g., "data", "json")
193
+ if (inlineBodyKey) {
194
+ body[inlineBodyKey] = bodyContent;
76
195
  } else {
77
196
  body.body = bodyContent;
78
197
  }
79
- case 6:
80
- // Remove inline-body-key from the final body as it's just metadata
81
- delete body["inline-body-key"];
82
- hmacId = (0, _id.hmacid)(msg.headers);
198
+ case 17:
199
+ // Include non-committed fields from the original object as JSON values.
200
+ // This covers: (1) body-key fields (arrays/objects encoded as multipart,
201
+ // which can't be included as raw multipart in JSON), and (2) any other
202
+ // fields excluded from signing. These fields are unsigned but present
203
+ // so HyperBEAM can parse them directly as JSON types.
204
+ // Use case-insensitive matching: signing normalizes keys to lowercase,
205
+ // but the original object may use mixed case (e.g., "To" vs "to").
206
+ committedSetLower = new Set(components.map(function (v) {
207
+ return (v === "@path" ? "path" : v).toLowerCase();
208
+ })); // Also track lowercase keys already in body to prevent duplicates
209
+ bodyKeysLower = new Set(Object.keys(body).map(function (k) {
210
+ return k.toLowerCase();
211
+ }));
212
+ _i4 = 0, _Object$entries4 = Object.entries(obj);
213
+ case 18:
214
+ if (!(_i4 < _Object$entries4.length)) {
215
+ _context.n = 24;
216
+ break;
217
+ }
218
+ _Object$entries4$_i = _slicedToArray(_Object$entries4[_i4], 2), key = _Object$entries4$_i[0], value = _Object$entries4$_i[1];
219
+ if (!(key === "method")) {
220
+ _context.n = 19;
221
+ break;
222
+ }
223
+ return _context.a(3, 23);
224
+ case 19:
225
+ if (!committedSetLower.has(key.toLowerCase())) {
226
+ _context.n = 20;
227
+ break;
228
+ }
229
+ return _context.a(3, 23);
230
+ case 20:
231
+ if (!bodyKeysLower.has(key.toLowerCase())) {
232
+ _context.n = 21;
233
+ break;
234
+ }
235
+ return _context.a(3, 23);
236
+ case 21:
237
+ if (!(value === undefined)) {
238
+ _context.n = 22;
239
+ break;
240
+ }
241
+ return _context.a(3, 23);
242
+ case 22:
243
+ body[key] = value;
244
+ bodyKeysLower.add(key.toLowerCase());
245
+ case 23:
246
+ _i4++;
247
+ _context.n = 18;
248
+ break;
249
+ case 24:
250
+ // Include ao-types so HyperBEAM knows how to convert string values to proper types.
251
+ // First check if it was set by the signer, otherwise compute it from the original object
252
+ aoTypes = msg.headers["ao-types"];
253
+ if (!aoTypes) {
254
+ // Compute ao-types from the original typed values in obj
255
+ aoTypes = buildAoTypes(obj);
256
+ }
257
+ if (aoTypes) {
258
+ body["ao-types"] = aoTypes;
259
+ }
83
260
  rsaId = (0, _id.rsaid)(msg.headers);
84
261
  pub = (0, _signerUtils.extractPubKey)(msg.headers);
85
- committer = (0, _utils.toAddr)(pub.toString("base64"));
262
+ pubKeyBase64 = pub.toString("base64");
263
+ committer = (0, _utils.toAddr)(pubKeyBase64); // Extract keyid from signature-input to ensure it matches what was signed
264
+ // Format: sig-xxx=("field1"...);alg="...";keyid="..."
265
+ // The keyid may already have a scheme prefix (e.g., "publickey:base64data")
266
+ extractKeyidFromSigInput = function extractKeyidFromSigInput(sigInput) {
267
+ if (!sigInput) return null;
268
+ var match = sigInput.match(/keyid="([^"]+)"/);
269
+ if (!match) return null;
270
+ // Return as-is - the keyid already includes the prefix from signing
271
+ return match[1];
272
+ };
273
+ keyid = extractKeyidFromSigInput(msg.headers["signature-input"]) || "publickey:".concat(pubKeyBase64); // Build the list of committed fields.
274
+ // Always transform HTTPSig transport keys to AO-Core keys:
275
+ // - Replace content-digest with the body field name (HyperBEAM re-derives content-digest)
276
+ // - For native body keys ("body", "data"): also remove ao-body-key (HyperBEAM re-derives it)
277
+ // - For custom body keys (e.g., "json"): keep ao-body-key (HyperBEAM needs it to find the field)
278
+ committedFields = components.map(function (v) {
279
+ return v === "@path" ? "path" : v;
280
+ });
281
+ if (committedFields.includes("content-digest") && (inlineBodyKey || msg.body)) {
282
+ bodyFieldName = inlineBodyKey || "body";
283
+ committedFields = committedFields.filter(function (k) {
284
+ return k !== "content-digest";
285
+ });
286
+ if (!committedFields.includes(bodyFieldName)) {
287
+ committedFields.push(bodyFieldName);
288
+ }
289
+ }
290
+ if (isNativeBodyKey) {
291
+ // For native body keys, ao-body-key is a transport artifact - remove it
292
+ committedFields = committedFields.filter(function (k) {
293
+ return k !== "ao-body-key";
294
+ });
295
+ }
296
+
297
+ // Extract just the base64 signature data from the header format "sig-xxx=:base64data:"
298
+ // HyperBEAM expects raw base64 without colons (uses b64fast:encode/decode)
299
+ extractSignature = function extractSignature(sigHeader) {
300
+ if (!sigHeader) return sigHeader;
301
+ // Match the base64 data between colons after the label
302
+ var match = sigHeader.match(/=:([^:]+):/);
303
+ return match ? match[1] : sigHeader;
304
+ };
305
+ rawSignature = extractSignature(msg.headers.signature);
86
306
  meta = {
307
+ type: "rsa-pss-sha512",
87
308
  alg: "rsa-pss-sha512",
88
- "commitment-device": "httpsig@1.0"
89
- };
90
- meta2 = {
91
- alg: "hmac-sha256",
92
- "commitment-device": "httpsig@1.0"
309
+ "commitment-device": "httpsig@1.0",
310
+ keyid: keyid,
311
+ committed: committedFields
93
312
  };
94
313
  sigs = {
95
- signature: msg.headers.signature,
314
+ signature: rawSignature,
96
315
  "signature-input": msg.headers["signature-input"]
97
- };
316
+ }; // Only include the RSA commitment - HMAC commitments are created by HyperBEAM
317
+ // when needed and require the server's HMAC key which we don't have
98
318
  committed = _objectSpread({
99
- commitments: _defineProperty(_defineProperty({}, rsaId, _objectSpread(_objectSpread({}, meta), {}, {
319
+ commitments: _defineProperty({}, rsaId, _objectSpread(_objectSpread({}, meta), {}, {
100
320
  committer: committer
101
- }, sigs)), hmacId, _objectSpread(_objectSpread({}, meta2), sigs))
321
+ }, sigs))
102
322
  }, body);
103
323
  return _context.a(2, committed);
104
324
  }
105
- }, _callee);
325
+ }, _callee, null, [[3, 11, 12, 13]]);
106
326
  }));
107
327
  return function commit(_x, _x2) {
108
328
  return _ref.apply(this, arguments);
@@ -154,21 +154,27 @@ function getValueByPath(obj, path) {
154
154
  }
155
155
 
156
156
  // Get the ao-type for a value
157
+ // Note: dev_codec_structured.erl only supports: integer, float, atom, list, map
158
+ // Do NOT use empty-binary, empty-list, empty-message as they cause binary_to_existing_atom errors
157
159
  function getAoType(value) {
158
160
  if (typeof value === "boolean" || value === null || value === undefined || _typeof(value) === "symbol") {
159
161
  return "atom";
160
162
  } else if (typeof value === "number") {
161
163
  return Number.isInteger(value) ? "integer" : "float";
162
164
  } else if (typeof value === "string" && value.length === 0) {
163
- return "empty-binary";
165
+ // Empty strings become empty binaries naturally - no type annotation needed
166
+ return null;
164
167
  } else if (isBytes(value) && (value.length === 0 || value.byteLength === 0)) {
165
- return "empty-binary";
168
+ // Empty buffers become empty binaries naturally - no type annotation needed
169
+ return null;
166
170
  } else if (Array.isArray(value) && value.length === 0) {
167
- return "empty-list";
171
+ // Use "list" for empty arrays (structured codec compatible)
172
+ return "list";
168
173
  } else if (Array.isArray(value)) {
169
174
  return "list";
170
175
  } else if (isPojo(value) && Object.keys(value).length === 0) {
171
- return "empty-message";
176
+ // Use "map" for empty objects (structured codec compatible)
177
+ return "map";
172
178
  }
173
179
  return null;
174
180
  }
package/cjs/encode.js CHANGED
@@ -48,7 +48,19 @@ function handleSingleEmptyBinaryField(obj) {
48
48
  var fieldValue = obj[fieldName];
49
49
  if ((0, _encodeUtils.isBytes)(fieldValue) && (fieldValue.length === 0 || fieldValue.byteLength === 0)) {
50
50
  var headers = {};
51
- headers["ao-types"] = "".concat(fieldName.toLowerCase(), "=\"empty-binary\"");
51
+ // For 'body' field, we can't send it as a header (reserved name, gets stripped).
52
+ // Use inline-body-key to signal that body was present but empty.
53
+ // The modOut function will reconstruct body: <<>> when it sees inline-body-key: body
54
+ // with no actual body content.
55
+ if (fieldName.toLowerCase() === "body") {
56
+ headers["inline-body-key"] = "body";
57
+ return {
58
+ headers: headers,
59
+ body: undefined
60
+ };
61
+ }
62
+ // Include the key with empty value - empty string becomes empty binary in Erlang
63
+ headers[fieldName.toLowerCase()] = "";
52
64
  return {
53
65
  headers: headers,
54
66
  body: undefined
@@ -261,14 +273,17 @@ function processHeaderFields(obj, bodyKeys, headers, headerTypes) {
261
273
  headerTypes.push("".concat(key.toLowerCase(), "=\"").concat(Number.isInteger(value) ? "integer" : "float", "\""));
262
274
  } else if (typeof value === "string") {
263
275
  if (value.length === 0) {
264
- headerTypes.push("".concat(key.toLowerCase(), "=\"empty-binary\""));
276
+ // Empty string becomes empty binary in Erlang - no ao-types needed
277
+ headers[key] = "";
265
278
  } else if ((0, _encodeUtils.hasNonAscii)(value)) {
266
279
  return 1; // continue
267
280
  } else {
268
281
  headers[key] = value;
269
282
  }
270
283
  } else if (Array.isArray(value) && value.length === 0) {
271
- headerTypes.push("".concat(key.toLowerCase(), "=\"empty-list\""));
284
+ // Empty array - use list type annotation
285
+ headers[key] = "";
286
+ headerTypes.push("".concat(key.toLowerCase(), "=\"list\""));
272
287
  } else if (Array.isArray(value) && !value.some(function (item) {
273
288
  return (0, _encodeUtils.isPojo)(item);
274
289
  })) {
@@ -283,9 +298,18 @@ function processHeaderFields(obj, bodyKeys, headers, headerTypes) {
283
298
  headerTypes.push("".concat(key.toLowerCase(), "=\"list\""));
284
299
  }
285
300
  } else if ((0, _encodeUtils.isBytes)(value) && (value.length === 0 || value.byteLength === 0)) {
286
- headerTypes.push("".concat(key.toLowerCase(), "=\"empty-binary\""));
301
+ // Empty buffer becomes empty binary in Erlang - no ao-types needed
302
+ // For 'body' field, we can't send it as a header (reserved name, gets stripped).
303
+ // Use inline-body-key to signal that body was present but empty.
304
+ if (key.toLowerCase() === "body") {
305
+ headers["inline-body-key"] = "body";
306
+ } else {
307
+ headers[key] = "";
308
+ }
287
309
  } else if ((0, _encodeUtils.isPojo)(value) && Object.keys(value).length === 0) {
288
- headerTypes.push("".concat(key.toLowerCase(), "=\"empty-message\""));
310
+ // Empty object - use map type annotation
311
+ headers[key] = "";
312
+ headerTypes.push("".concat(key.toLowerCase(), "=\"map\""));
289
313
  }
290
314
  } else {
291
315
  // Fields that need body still get type annotations
@@ -354,18 +378,31 @@ function handleSingleBodyKeyOptimization(_x5, _x6, _x7, _x8) {
354
378
  } // Step 11: Sort body keys
355
379
  function _handleSingleBodyKeyOptimization() {
356
380
  _handleSingleBodyKeyOptimization = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5(obj, bodyKeys, headers, headerTypes) {
357
- var singleKey, value, contentToHash, bodyContent, bodyBuffer, encoder, encoded, contentDigest, base64;
381
+ var otherFields, singleKey, value, contentToHash, bodyContent, bodyBuffer, encoder, encoded, contentDigest, base64;
358
382
  return _regenerator().w(function (_context5) {
359
383
  while (1) switch (_context5.n) {
360
384
  case 0:
385
+ // Skip this optimization if there are other header fields - need full multipart encoding
386
+ // to be compatible with HyperBEAM scheduler endpoint
387
+ otherFields = Object.keys(obj).filter(function (k) {
388
+ return !bodyKeys.includes(k) && !bodyKeys.some(function (bk) {
389
+ return bk.startsWith("".concat(k, "/"));
390
+ });
391
+ });
392
+ if (!(otherFields.length > 0 && bodyKeys.length === 1)) {
393
+ _context5.n = 1;
394
+ break;
395
+ }
396
+ return _context5.a(2, null);
397
+ case 1:
361
398
  if (!(bodyKeys.length === 1)) {
362
- _context5.n = 2;
399
+ _context5.n = 3;
363
400
  break;
364
401
  }
365
402
  singleKey = bodyKeys[0];
366
403
  value = (0, _encodeUtils.getValueByPath)(obj, singleKey); // Apply optimization for binary data OR strings with newlines
367
404
  if (!((0, _encodeUtils.isBytes)(value) && value.length > 0 || typeof value === "string" && value.includes("\n"))) {
368
- _context5.n = 2;
405
+ _context5.n = 3;
369
406
  break;
370
407
  }
371
408
  bodyContent = value;
@@ -379,9 +416,9 @@ function _handleSingleBodyKeyOptimization() {
379
416
  contentToHash = encoded.buffer;
380
417
  bodyContent = value;
381
418
  }
382
- _context5.n = 1;
419
+ _context5.n = 2;
383
420
  return (0, _encodeUtils.sha256)(contentToHash);
384
- case 1:
421
+ case 2:
385
422
  contentDigest = _context5.v;
386
423
  base64 = _base64url["default"].toBase64(_base64url["default"].encode(contentDigest));
387
424
  headers["content-digest"] = "sha-256=:".concat(base64, ":");
@@ -395,7 +432,7 @@ function _handleSingleBodyKeyOptimization() {
395
432
  headers: headers,
396
433
  body: bodyContent
397
434
  });
398
- case 2:
435
+ case 3:
399
436
  return _context5.a(2, null);
400
437
  }
401
438
  }, _callee5);
@@ -520,14 +557,16 @@ function processArrayItems(value, indicesWithOwnParts, hasNestedObjectParts, pat
520
557
  return;
521
558
  }
522
559
  if (typeof item === "string" && item === "") {
523
- partTypes.push("".concat(index, "=\"empty-binary\""));
560
+ // Empty strings become empty binaries naturally - no type annotation needed
524
561
  } else if ((0, _encodeUtils.isPojo)(item) && Object.keys(item).length === 0) {
525
- partTypes.push("".concat(index, "=\"empty-message\""));
562
+ // Use "map" instead of "empty-message" for structured codec compatibility
563
+ partTypes.push("".concat(index, "=\"map\""));
526
564
  } else if ((0, _encodeUtils.isPojo)(item)) {
527
565
  // Non-empty objects are handled elsewhere
528
566
  } else if (Array.isArray(item)) {
529
567
  if (item.length === 0) {
530
- partTypes.push("".concat(index, "=\"empty-list\""));
568
+ // Use "list" instead of "empty-list" for structured codec compatibility
569
+ partTypes.push("".concat(index, "=\"list\""));
531
570
  } else {
532
571
  partTypes.push("".concat(index, "=\"list\""));
533
572
  var encodedItems = item.map(function (subItem) {
@@ -591,7 +630,7 @@ function processArrayItems(value, indicesWithOwnParts, hasNestedObjectParts, pat
591
630
  } else if ((0, _encodeUtils.isBytes)(item)) {
592
631
  var buffer = (0, _encodeUtils.toBuffer)(item);
593
632
  if (buffer.length === 0) {
594
- partTypes.push("".concat(index, "=\"empty-binary\""));
633
+ // Empty buffers become empty binaries naturally - no type annotation needed
595
634
  } else {
596
635
  partTypes.push("".concat(index, "=\"binary\""));
597
636
  }
@@ -681,12 +720,13 @@ function processObjectFields(value, bodyKey, sortedBodyKeys) {
681
720
  var arrayTypes = [];
682
721
 
683
722
  // First collect array types
723
+ // Note: Use "list" for both empty and non-empty arrays for structured codec compatibility
684
724
  for (var _i3 = 0, _Object$entries3 = Object.entries(value); _i3 < _Object$entries3.length; _i3++) {
685
725
  var _Object$entries3$_i = _slicedToArray(_Object$entries3[_i3], 2),
686
726
  k = _Object$entries3$_i[0],
687
727
  v = _Object$entries3$_i[1];
688
728
  if (Array.isArray(v)) {
689
- arrayTypes.push("".concat(k.toLowerCase(), "=\"").concat(v.length === 0 ? "empty-list" : "list", "\""));
729
+ arrayTypes.push("".concat(k.toLowerCase(), "=\"list\""));
690
730
  }
691
731
  }
692
732
 
@@ -716,11 +756,12 @@ function processObjectFields(value, bodyKey, sortedBodyKeys) {
716
756
  } else if (typeof _v === "number") {
717
757
  objectTypes.push("".concat(_k.toLowerCase(), "=\"").concat(Number.isInteger(_v) ? "integer" : "float", "\""));
718
758
  } else if (typeof _v === "string" && _v.length === 0) {
719
- objectTypes.push("".concat(_k.toLowerCase(), "=\"empty-binary\""));
759
+ // Empty strings become empty binaries naturally - no type annotation needed
720
760
  } else if ((0, _encodeUtils.isBytes)(_v) && (_v.length === 0 || _v.byteLength === 0)) {
721
- objectTypes.push("".concat(_k.toLowerCase(), "=\"empty-binary\""));
761
+ // Empty buffers become empty binaries naturally - no type annotation needed
722
762
  } else if ((0, _encodeUtils.isPojo)(_v) && Object.keys(_v).length === 0) {
723
- objectTypes.push("".concat(_k.toLowerCase(), "=\"empty-message\""));
763
+ // Use "map" instead of "empty-message" for structured codec compatibility
764
+ objectTypes.push("".concat(_k.toLowerCase(), "=\"map\""));
724
765
  }
725
766
  if (typeof _v === "string") {
726
767
  if (_v.length === 0) {
@@ -745,6 +786,12 @@ function processObjectFields(value, bodyKey, sortedBodyKeys) {
745
786
  key: _k,
746
787
  buffer: buffer
747
788
  });
789
+ } else if (Array.isArray(_v) && _v.length === 0) {
790
+ // Empty array - include the key so Erlang knows it exists
791
+ fieldLines.push("".concat(_k, ": "));
792
+ } else if ((0, _encodeUtils.isPojo)(_v) && Object.keys(_v).length === 0) {
793
+ // Empty object - include the key so Erlang knows it exists
794
+ fieldLines.push("".concat(_k, ": "));
748
795
  } else if (Array.isArray(_v) && _v.length > 0) {
749
796
  var _childPath = "".concat(bodyKey, "/").concat(_k);
750
797
  if (!sortedBodyKeys.includes(_childPath)) {
package/cjs/erl_json.js CHANGED
@@ -8,10 +8,14 @@ exports.erl_json_to = erl_json_to;
8
8
  exports.normalize = normalize;
9
9
  function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
10
10
  function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
11
- function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
12
- function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
13
11
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
14
12
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
13
+ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
14
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
15
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
16
+ function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
17
+ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
18
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
15
19
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
16
20
  /**
17
21
  * Codec for converting between JS objects with Erlang types and annotated JSON
@@ -76,7 +80,12 @@ function normalize(obj) {
76
80
  }
77
81
  if (binaryMode) {
78
82
  // In binary mode, convert strings to buffers
79
- return Buffer.from(obj, "utf8");
83
+ // Use binary/latin1 encoding only if all chars are <= 255 (preserves raw bytes)
84
+ // Use UTF-8 for strings with chars > 255 (proper multi-byte encoding)
85
+ var needsUtf8 = _toConsumableArray(obj).some(function (ch) {
86
+ return ch.codePointAt(0) > 255;
87
+ });
88
+ return Buffer.from(obj, needsUtf8 ? "utf8" : "binary");
80
89
  } else {
81
90
  // In string mode, strings stay as strings
82
91
  return obj;
package/cjs/erl_str.js CHANGED
@@ -38,6 +38,11 @@ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" ==
38
38
  */
39
39
  function erl_str_from(str) {
40
40
  var binaryMode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
41
+ // Handle null/undefined input
42
+ if (str === null || str === undefined) {
43
+ return null;
44
+ }
45
+
41
46
  // Handle the new response format
42
47
  if (str.startsWith("#erl_response{")) {
43
48
  var rawMatch = str.match(/#erl_response\{raw=([^]*?),formatted=([^]*?)\}$/);