@blamejs/blamejs-shop 0.1.33 → 0.1.35
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/CHANGELOG.md +4 -0
- package/README.md +1 -0
- package/lib/asset-manifest.json +7 -3
- package/lib/storefront.js +432 -4
- package/lib/vendor/MANIFEST.json +3 -3
- package/lib/vendor/blamejs/CHANGELOG.md +6 -0
- package/lib/vendor/blamejs/README.md +3 -1
- package/lib/vendor/blamejs/api-snapshot.json +97 -2
- package/lib/vendor/blamejs/index.js +3 -0
- package/lib/vendor/blamejs/lib/crdt.js +453 -0
- package/lib/vendor/blamejs/lib/crypto-xwing.js +213 -0
- package/lib/vendor/blamejs/lib/iab-tcf.js +277 -2
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.13.2.json +27 -0
- package/lib/vendor/blamejs/release-notes/v0.13.3.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.13.4.json +23 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crdt.test.js +158 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-xwing.test.js +120 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-tcf.test.js +61 -1
- package/package.json +2 -2
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Layer 0 — b.crdt (state-based CvRDTs).
|
|
4
|
+
*
|
|
5
|
+
* Oracle: the CRDT correctness laws themselves. A state-based CvRDT's merge is
|
|
6
|
+
* a join over a semilattice, so for every type merge must be commutative,
|
|
7
|
+
* associative, and idempotent, and two replicas that apply concurrent ops then
|
|
8
|
+
* merge must converge to the same value. Those laws ARE the specification
|
|
9
|
+
* (Shapiro et al.); each is asserted below alongside worked examples.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
var b = require("../../index");
|
|
13
|
+
var helpers = require("../helpers");
|
|
14
|
+
var check = helpers.check;
|
|
15
|
+
var crdt = b.crdt;
|
|
16
|
+
function code(fn) { try { fn(); return "NO-THROW"; } catch (e) { return e.code; } }
|
|
17
|
+
function val(c) { return JSON.stringify(c.value()); }
|
|
18
|
+
|
|
19
|
+
// Assert the three merge laws for a type, given three instances carrying
|
|
20
|
+
// concurrent (disjoint-replica) ops.
|
|
21
|
+
function laws(label, a, c, d) {
|
|
22
|
+
check(label + ": commutative", val(a.merge(c)) === val(c.merge(a)));
|
|
23
|
+
check(label + ": associative", val(a.merge(c).merge(d)) === val(a.merge(c.merge(d))));
|
|
24
|
+
check(label + ": idempotent", val(a.merge(a)) === val(a));
|
|
25
|
+
// Convergence: cross-merge of two replicas lands on one value both ways.
|
|
26
|
+
check(label + ": converges", val(a.merge(c)) === val(c.merge(a)));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function testSurface() {
|
|
30
|
+
// Reference each primitive by its full b.crdt.* path (coverage gate).
|
|
31
|
+
check("b.crdt.gCounter is a factory", typeof b.crdt.gCounter === "function" && typeof b.crdt.gCounter.fromState === "function");
|
|
32
|
+
check("b.crdt.pnCounter is a factory", typeof b.crdt.pnCounter === "function" && typeof b.crdt.pnCounter.fromState === "function");
|
|
33
|
+
check("b.crdt.gSet is a factory", typeof b.crdt.gSet === "function" && typeof b.crdt.gSet.fromState === "function");
|
|
34
|
+
check("b.crdt.twoPSet is a factory", typeof b.crdt.twoPSet === "function" && typeof b.crdt.twoPSet.fromState === "function");
|
|
35
|
+
check("b.crdt.orSet is a factory", typeof b.crdt.orSet === "function" && typeof b.crdt.orSet.fromState === "function");
|
|
36
|
+
check("b.crdt.lwwRegister is a factory", typeof b.crdt.lwwRegister === "function" && typeof b.crdt.lwwRegister.fromState === "function");
|
|
37
|
+
check("b.crdt.orMap is a factory", typeof b.crdt.orMap === "function" && typeof b.crdt.orMap.fromState === "function");
|
|
38
|
+
check("b.crdt.CrdtError is a class", typeof b.crdt.CrdtError === "function");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function testGCounter() {
|
|
42
|
+
var a = crdt.gCounter({ replicaId: "a" }).inc(3);
|
|
43
|
+
var c = crdt.gCounter({ replicaId: "c" }).inc(5);
|
|
44
|
+
var d = crdt.gCounter({ replicaId: "d" }).inc(1);
|
|
45
|
+
check("gCounter sum across replicas", a.merge(c).value() === 8);
|
|
46
|
+
laws("gCounter", a, c, d);
|
|
47
|
+
check("gCounter inc default is 1", crdt.gCounter({ replicaId: "a" }).inc().value() === 1);
|
|
48
|
+
check("gCounter rejects negative inc", code(function () { crdt.gCounter().inc(-1); }) === "crdt/bad-value");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function testPNCounter() {
|
|
52
|
+
var a = crdt.pnCounter({ replicaId: "a" }).inc(5).dec(2);
|
|
53
|
+
var c = crdt.pnCounter({ replicaId: "c" }).inc(1);
|
|
54
|
+
var d = crdt.pnCounter({ replicaId: "d" }).dec(3);
|
|
55
|
+
check("pnCounter value", a.value() === 3);
|
|
56
|
+
check("pnCounter merged value", a.merge(c).merge(d).value() === 1);
|
|
57
|
+
laws("pnCounter", a, c, d);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function testGSet() {
|
|
61
|
+
var a = crdt.gSet({ replicaId: "a" }).add("x").add("y");
|
|
62
|
+
var c = crdt.gSet({ replicaId: "c" }).add("z");
|
|
63
|
+
var d = crdt.gSet({ replicaId: "d" }).add("x");
|
|
64
|
+
check("gSet union", val(a.merge(c)) === JSON.stringify(["x", "y", "z"]));
|
|
65
|
+
check("gSet has", a.has("x") && !a.has("q"));
|
|
66
|
+
check("gSet supports structured elements", crdt.gSet().add({ k: 1 }).has({ k: 1 }));
|
|
67
|
+
laws("gSet", a, c, d);
|
|
68
|
+
// value() order must converge for structured elements regardless of merge
|
|
69
|
+
// order (sorted by the encoded key, not the decoded value).
|
|
70
|
+
var s1 = crdt.gSet().add({ id: 2 }).add({ id: 1 });
|
|
71
|
+
var s2 = crdt.gSet().add({ id: 3 });
|
|
72
|
+
check("gSet structured-element value order converges", JSON.stringify(s1.merge(s2).value()) === JSON.stringify(s2.merge(s1).value()));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function testTwoPSet() {
|
|
76
|
+
var s = crdt.twoPSet().add("a").add("b").remove("a");
|
|
77
|
+
check("twoPSet remove", val(s) === JSON.stringify(["b"]));
|
|
78
|
+
check("twoPSet remove-wins (no re-add)", !s.add("a").has("a"));
|
|
79
|
+
var a = crdt.twoPSet({ replicaId: "a" }).add("x").remove("x");
|
|
80
|
+
var c = crdt.twoPSet({ replicaId: "c" }).add("y");
|
|
81
|
+
var d = crdt.twoPSet({ replicaId: "d" }).add("z");
|
|
82
|
+
laws("twoPSet", a, c, d);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function testORSet() {
|
|
86
|
+
// Concurrent re-add survives a remove that did not observe it.
|
|
87
|
+
var a = crdt.orSet({ replicaId: "a" }).add("x").add("y");
|
|
88
|
+
var c = crdt.orSet.fromState(a.state(), { replicaId: "c" });
|
|
89
|
+
a.remove("x");
|
|
90
|
+
c.add("x");
|
|
91
|
+
check("orSet concurrent re-add survives remove", a.merge(c).value().indexOf("x") !== -1);
|
|
92
|
+
// A remove that observed the add wins.
|
|
93
|
+
var s = crdt.orSet().add("z");
|
|
94
|
+
check("orSet observed remove drops element", s.remove("z").value().indexOf("z") === -1);
|
|
95
|
+
laws("orSet", crdt.orSet({ replicaId: "a" }).add("p"), crdt.orSet({ replicaId: "c" }).add("q"), crdt.orSet({ replicaId: "d" }).add("r"));
|
|
96
|
+
// tombstoneRetention cap is accepted and bounds the tombstone set.
|
|
97
|
+
var capped = crdt.orSet({ replicaId: "a", tombstoneRetention: 2 });
|
|
98
|
+
capped.add("1").add("2").add("3").remove("1").remove("2").remove("3");
|
|
99
|
+
check("orSet tombstoneRetention bounds the set", capped.state().tombstones.length <= 2);
|
|
100
|
+
check("orSet rejects bad tombstoneRetention", code(function () { crdt.orSet({ tombstoneRetention: -1 }); }) === "crdt/bad-value");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function testLWWRegister() {
|
|
104
|
+
var a = crdt.lwwRegister({ replicaId: "a" }).set("first", 1);
|
|
105
|
+
var c = crdt.lwwRegister({ replicaId: "c" }).set("second", 2);
|
|
106
|
+
check("lwwRegister higher ts wins", a.merge(c).value() === "second");
|
|
107
|
+
// Tie-break by replicaId (deterministic).
|
|
108
|
+
var r1 = crdt.lwwRegister({ replicaId: "a" }).set("a", 100);
|
|
109
|
+
var r2 = crdt.lwwRegister({ replicaId: "c" }).set("c", 100);
|
|
110
|
+
check("lwwRegister tie-break by replicaId", r1.merge(r2).value() === "c" && r2.merge(r1).value() === "c");
|
|
111
|
+
laws("lwwRegister",
|
|
112
|
+
crdt.lwwRegister({ replicaId: "a" }).set("x", 5),
|
|
113
|
+
crdt.lwwRegister({ replicaId: "c" }).set("y", 9),
|
|
114
|
+
crdt.lwwRegister({ replicaId: "d" }).set("z", 3));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function testORMap() {
|
|
118
|
+
var a = crdt.orMap({ replicaId: "a" }).set("k1", "v1", 10);
|
|
119
|
+
var c = crdt.orMap({ replicaId: "c" }).set("k1", "v2", 20).set("k2", "x", 5);
|
|
120
|
+
var merged = a.merge(c);
|
|
121
|
+
check("orMap concurrent key write resolves LWW", merged.get("k1") === "v2");
|
|
122
|
+
check("orMap disjoint keys both present", merged.has("k2") && val(merged) === JSON.stringify({ k1: "v2", k2: "x" }));
|
|
123
|
+
check("orMap commutative", val(a.merge(c)) === val(c.merge(a)));
|
|
124
|
+
// Remove a key, then converge.
|
|
125
|
+
var p = crdt.orMap.fromState(merged.state(), { replicaId: "a" }).remove("k2");
|
|
126
|
+
check("orMap remove converges", val(p.merge(merged)) === JSON.stringify({ k1: "v2" }));
|
|
127
|
+
check("orMap rejects non-string key", code(function () { crdt.orMap().set(5, "v"); }) === "crdt/bad-key");
|
|
128
|
+
// Removing a key clears its register, so a re-add starts clean — a later set
|
|
129
|
+
// with a lower timestamp than the pre-remove value still wins on this replica.
|
|
130
|
+
var rr = crdt.orMap({ replicaId: "a" }).set("k", "old", 100);
|
|
131
|
+
rr.remove("k");
|
|
132
|
+
rr.set("k", "new", 50); // lower ts than the removed "old"@100
|
|
133
|
+
check("orMap remove clears register so re-add wins", rr.get("k") === "new");
|
|
134
|
+
check("orMap re-add appears in value", JSON.stringify(rr.value()) === JSON.stringify({ k: "new" }));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function testStateRoundTrip() {
|
|
138
|
+
["gCounter", "pnCounter", "gSet", "twoPSet", "orSet", "lwwRegister", "orMap"].forEach(function (t) {
|
|
139
|
+
var inst = crdt[t]({ replicaId: "a" });
|
|
140
|
+
var s = inst.state();
|
|
141
|
+
check(t + ": fromState round-trips", JSON.stringify(crdt[t].fromState(s, { replicaId: "a" }).state()) === JSON.stringify(s));
|
|
142
|
+
check(t + ": fromState rejects a mismatched type", code(function () { crdt[t].fromState({ type: "WRONG" }); }) === "crdt/type-mismatch");
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function run() {
|
|
147
|
+
testSurface();
|
|
148
|
+
testGCounter();
|
|
149
|
+
testPNCounter();
|
|
150
|
+
testGSet();
|
|
151
|
+
testTwoPSet();
|
|
152
|
+
testORSet();
|
|
153
|
+
testLWWRegister();
|
|
154
|
+
testORMap();
|
|
155
|
+
testStateRoundTrip();
|
|
156
|
+
}
|
|
157
|
+
module.exports = { run: run };
|
|
158
|
+
if (require.main === module) { run().then(function () { console.log("[crdt] OK — " + helpers.getChecks() + " checks passed"); }, function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }); }
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Layer 0 — b.crypto.xwing (X-Wing hybrid PQ/T KEM, draft-connolly-cfrg-xwing-kem).
|
|
4
|
+
*
|
|
5
|
+
* Oracle. The X-Wing-specific contribution is the combiner — SHA3-256 over a
|
|
6
|
+
* fixed concatenation plus a fixed label — so that is known-answer-tested
|
|
7
|
+
* byte-for-byte against an independent SHA3-256. The ML-KEM-768 and X25519
|
|
8
|
+
* halves are pre-validated vendored primitives; the composition is checked by
|
|
9
|
+
* full encaps → decaps agreement, by deterministic reproducibility from fixed
|
|
10
|
+
* seeds (regression-anchored to pinned digests), and by implicit-rejection
|
|
11
|
+
* behaviour on a tampered ciphertext.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
var nodeCrypto = require("crypto");
|
|
15
|
+
var b = require("../../index");
|
|
16
|
+
var helpers = require("../helpers");
|
|
17
|
+
var check = helpers.check;
|
|
18
|
+
function code(fn) { try { fn(); return "NO-THROW"; } catch (e) { return e.code; } }
|
|
19
|
+
|
|
20
|
+
var x = b.crypto.xwing;
|
|
21
|
+
|
|
22
|
+
function testSurface() {
|
|
23
|
+
check("xwing.keygen is fn", typeof x.keygen === "function");
|
|
24
|
+
check("xwing.encapsulate is fn", typeof x.encapsulate === "function");
|
|
25
|
+
check("xwing.decapsulate is fn", typeof x.decapsulate === "function");
|
|
26
|
+
check("xwing.combiner is fn", typeof x.combiner === "function");
|
|
27
|
+
check("xwing.NAME is X-Wing", x.NAME === "X-Wing");
|
|
28
|
+
check("xwing.XWingError is class", typeof x.XWingError === "function");
|
|
29
|
+
check("SIZES pk 1216", x.SIZES.publicKey === 1216);
|
|
30
|
+
check("SIZES ct 1120", x.SIZES.ciphertext === 1120);
|
|
31
|
+
check("SIZES sk 32", x.SIZES.secretKey === 32);
|
|
32
|
+
check("SIZES ss 32", x.SIZES.sharedSecret === 32);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function testCombinerKAT() {
|
|
36
|
+
// Byte-exact against the draft: SHA3-256(ssM ‖ ssX ‖ ctX ‖ pkX ‖ label),
|
|
37
|
+
// label = the six bytes 5c2e2f2f5e5c ("\./" "/^\").
|
|
38
|
+
var ssM = Buffer.alloc(32, 0x11), ssX = Buffer.alloc(32, 0x22),
|
|
39
|
+
ctX = Buffer.alloc(32, 0x33), pkX = Buffer.alloc(32, 0x44);
|
|
40
|
+
var ref = nodeCrypto.createHash("sha3-256")
|
|
41
|
+
.update(Buffer.concat([ssM, ssX, ctX, pkX, Buffer.from("5c2e2f2f5e5c", "hex")])).digest();
|
|
42
|
+
check("combiner matches independent SHA3-256 + label byte-for-byte", x.combiner(ssM, ssX, ctX, pkX).equals(ref));
|
|
43
|
+
// Order matters: swapping two inputs changes the output.
|
|
44
|
+
check("combiner is order-sensitive", !x.combiner(ssX, ssM, ctX, pkX).equals(ref));
|
|
45
|
+
check("combiner rejects a short input", code(function () { x.combiner(Buffer.alloc(31), ssX, ctX, pkX); }) === "xwing/bad-input");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function testSizes() {
|
|
49
|
+
var kp = x.keygen();
|
|
50
|
+
check("public key is 1216 bytes", kp.publicKey.length === 1216);
|
|
51
|
+
check("secret key (seed) is 32 bytes", kp.secretKey.length === 32);
|
|
52
|
+
var enc = x.encapsulate(kp.publicKey);
|
|
53
|
+
check("ciphertext is 1120 bytes", enc.ciphertext.length === 1120);
|
|
54
|
+
check("shared secret is 32 bytes", enc.sharedSecret.length === 32);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function testRoundTrip() {
|
|
58
|
+
for (var i = 0; i < 5; i++) {
|
|
59
|
+
var kp = x.keygen();
|
|
60
|
+
var enc = x.encapsulate(kp.publicKey);
|
|
61
|
+
var ss = x.decapsulate(kp.secretKey, enc.ciphertext);
|
|
62
|
+
check("round-trip " + i + ": decaps recovers the encaps shared secret", ss.equals(enc.sharedSecret));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function testDeterminism() {
|
|
67
|
+
var seed = Buffer.alloc(32, 1);
|
|
68
|
+
check("keygen(seed) is deterministic", x.keygen(seed).publicKey.equals(x.keygen(seed).publicKey));
|
|
69
|
+
var pk = x.keygen(seed).publicKey;
|
|
70
|
+
// Distinct eseed halves so the value depends on the draft's split order
|
|
71
|
+
// (eseed[0:32] = ML-KEM coins, eseed[32:64] = X25519 ephemeral).
|
|
72
|
+
var eseed = Buffer.concat([Buffer.alloc(32, 2), Buffer.alloc(32, 3)]);
|
|
73
|
+
var a = x.encapsulate(pk, eseed), c2 = x.encapsulate(pk, eseed);
|
|
74
|
+
check("encapsulate(pk, eseed) is deterministic", a.ciphertext.equals(c2.ciphertext) && a.sharedSecret.equals(c2.sharedSecret));
|
|
75
|
+
check("deterministic encaps round-trips", x.decapsulate(seed, a.ciphertext).equals(a.sharedSecret));
|
|
76
|
+
|
|
77
|
+
// Regression anchors: pinned digests of the full deterministic flow. A change
|
|
78
|
+
// to the combiner, the seed expansion, the eseed split order, or the wire
|
|
79
|
+
// framing breaks these.
|
|
80
|
+
check("keygen(0x01) public key digest is stable",
|
|
81
|
+
nodeCrypto.createHash("sha3-256").update(pk).digest("hex") === "60068c4c0bfc7421bb1cb4a4202bf0ef75ee27e61bf2f6b08780869485cc736a");
|
|
82
|
+
check("deterministic ciphertext digest is stable",
|
|
83
|
+
nodeCrypto.createHash("sha3-256").update(a.ciphertext).digest("hex") === "1422e82c4307fcb8117b28ceaf686241cbe48d1a5546e05cd1aa7401d5fc2624");
|
|
84
|
+
check("deterministic shared secret is stable",
|
|
85
|
+
a.sharedSecret.toString("hex") === "356f5611bb146e8baf7fa61410552a9c724a170b8a0d4e742fac19161d8fdf4a");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function testImplicitRejection() {
|
|
89
|
+
var kp = x.keygen();
|
|
90
|
+
var enc = x.encapsulate(kp.publicKey);
|
|
91
|
+
// Wrong key: ML-KEM implicit rejection yields a different 32-byte secret, no throw.
|
|
92
|
+
var wrong = x.decapsulate(x.keygen().secretKey, enc.ciphertext);
|
|
93
|
+
check("wrong key yields a 32-byte secret (no throw)", Buffer.isBuffer(wrong) && wrong.length === 32);
|
|
94
|
+
check("wrong key yields a different secret", !wrong.equals(enc.sharedSecret));
|
|
95
|
+
// Tampered ciphertext: still no throw, different secret.
|
|
96
|
+
var bad = Buffer.from(enc.ciphertext); bad[0] ^= 0xff; bad[bad.length - 1] ^= 0xff;
|
|
97
|
+
var ssBad = x.decapsulate(kp.secretKey, bad);
|
|
98
|
+
check("tampered ciphertext yields a different secret without throwing", !ssBad.equals(enc.sharedSecret));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function testErrors() {
|
|
102
|
+
var kp = x.keygen();
|
|
103
|
+
check("keygen rejects a short seed", code(function () { x.keygen(Buffer.alloc(16)); }) === "xwing/bad-seed");
|
|
104
|
+
check("encapsulate rejects a short pubkey", code(function () { x.encapsulate(Buffer.alloc(100)); }) === "xwing/bad-public-key");
|
|
105
|
+
check("encapsulate rejects a short eseed", code(function () { x.encapsulate(kp.publicKey, Buffer.alloc(32)); }) === "xwing/bad-eseed");
|
|
106
|
+
check("decapsulate rejects a short seed", code(function () { x.decapsulate(Buffer.alloc(16), Buffer.alloc(1120)); }) === "xwing/bad-seed");
|
|
107
|
+
check("decapsulate rejects a short ct", code(function () { x.decapsulate(kp.secretKey, Buffer.alloc(100)); }) === "xwing/bad-ciphertext");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function run() {
|
|
111
|
+
testSurface();
|
|
112
|
+
testCombinerKAT();
|
|
113
|
+
testSizes();
|
|
114
|
+
testRoundTrip();
|
|
115
|
+
testDeterminism();
|
|
116
|
+
testImplicitRejection();
|
|
117
|
+
testErrors();
|
|
118
|
+
}
|
|
119
|
+
module.exports = { run: run };
|
|
120
|
+
if (require.main === module) { run().then(function () { console.log("[crypto-xwing] OK — " + helpers.getChecks() + " checks passed"); }, function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }); }
|
|
@@ -41,7 +41,8 @@ async function run() {
|
|
|
41
41
|
// bytes with a zero pad.
|
|
42
42
|
var totalBits = 6 + 36 + 36 + 12 + 12 + 6 + 6 + 6 + 12 + 6 + 1 + 1 +
|
|
43
43
|
12 + 24 + 24 + 1 + 6 + 6 +
|
|
44
|
-
16 + 1 + 16 + 1
|
|
44
|
+
16 + 1 + 16 + 1 + // two empty vendor sections
|
|
45
|
+
12; // NumPubRestrictions (mandatory, =0)
|
|
45
46
|
var byteLen = Math.ceil(totalBits / 8); // allow:raw-byte-literal — bits-per-byte
|
|
46
47
|
var buf = Buffer.alloc(byteLen);
|
|
47
48
|
// Set version field (top 6 bits of byte 0).
|
|
@@ -68,6 +69,65 @@ async function run() {
|
|
|
68
69
|
check("core version=4", parsed.core.version === 4);
|
|
69
70
|
check("checkVendor handles bare core",
|
|
70
71
|
b.iabTcf.checkVendor(parsed, 755).consented === false);
|
|
72
|
+
|
|
73
|
+
// ---- encode + isValid + completed parse --------------------------------
|
|
74
|
+
check("iabTcf.encode is fn", typeof b.iabTcf.encode === "function");
|
|
75
|
+
check("iabTcf.isValid is fn", typeof b.iabTcf.isValid === "function");
|
|
76
|
+
|
|
77
|
+
// Independent oracle: the worked-example string from the IAB Tech Lab
|
|
78
|
+
// "Consent string and vendor list formats v2" specification.
|
|
79
|
+
var SPEC = "CQSbk4AQSbk4ANwAAAENAwCgAAAAAAAAAAYgACPAAAAA.IDKQA4AAgAKAGQAygAAA.YAAAAAAAAAAA";
|
|
80
|
+
var SPEC_CORE = "CQSbk4AQSbk4ANwAAAENAwCgAAAAAAAAAAYgACPAAAAA";
|
|
81
|
+
var sp = b.iabTcf.parseString(SPEC);
|
|
82
|
+
check("spec core version 2", sp.core.version === 2);
|
|
83
|
+
check("spec cmpId 880", sp.core.cmpId === 880);
|
|
84
|
+
check("spec vendorListVersion 48", sp.core.vendorListVersion === 48);
|
|
85
|
+
check("spec created 2025-06-03", new Date(sp.core.createdAt).toISOString() === "2025-06-03T00:00:00.000Z");
|
|
86
|
+
check("spec vendorConsents [1,2,3,4]", JSON.stringify(Array.from(sp.core.vendorConsents.ids)) === "[1,2,3,4]");
|
|
87
|
+
check("spec disclosedVendors decoded", JSON.stringify(Array.from(sp.disclosedVendors.vendorIds)) === "[1,2,3,4,5,100,404]");
|
|
88
|
+
check("spec publisherTC fully parsed", sp.publisherTC && sp.publisherTC.present === true && typeof sp.publisherTC.numCustomPurposes === "number");
|
|
89
|
+
check("core.publisherRestrictions array", Array.isArray(sp.core.publisherRestrictions));
|
|
90
|
+
// Strong oracle: re-encoding the parsed core reproduces the spec Core
|
|
91
|
+
// segment byte-for-byte.
|
|
92
|
+
check("re-encoded core is byte-identical to the spec", b.iabTcf.encode({ core: sp.core }) === SPEC_CORE);
|
|
93
|
+
// Disclosed content round-trips (the encoder writes the minimal valid form).
|
|
94
|
+
var rt = b.iabTcf.parseString(b.iabTcf.encode({ core: sp.core, disclosedVendors: sp.disclosedVendors }));
|
|
95
|
+
check("disclosed content round-trips", JSON.stringify(Array.from(rt.disclosedVendors.vendorIds)) === "[1,2,3,4,5,100,404]");
|
|
96
|
+
|
|
97
|
+
// 36-bit timestamp regression: a real date (deciseconds > 2^31) must survive
|
|
98
|
+
// parse → encode unchanged. Guards the reader's `* 2` (not `<< 1`)
|
|
99
|
+
// accumulation, without which Created / LastUpdated truncate at 32 bits.
|
|
100
|
+
var bigCreated = Date.UTC(2026, 4, 26);
|
|
101
|
+
var encTs = b.iabTcf.encode({ core: { version: 2, createdAt: bigCreated, lastUpdatedAt: bigCreated, cmpId: 300, vendorListVersion: 100, consentLanguage: "EN", publisherCC: "DE", vendorConsents: [], vendorLIs: [] } });
|
|
102
|
+
var decTs = b.iabTcf.parseString(encTs);
|
|
103
|
+
check("36-bit timestamp survives round-trip", decTs.core.createdAt === bigCreated);
|
|
104
|
+
check("cmpId survives round-trip", decTs.core.cmpId === 300);
|
|
105
|
+
check("consentLanguage survives round-trip", decTs.core.consentLanguage === "EN");
|
|
106
|
+
|
|
107
|
+
// Vendor-section round-trip across range-favouring and sparse sets.
|
|
108
|
+
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [12, 37, 199, 755], [1]].forEach(function (vc, idx) {
|
|
109
|
+
var s = b.iabTcf.encode({ core: { version: 2, createdAt: bigCreated, lastUpdatedAt: bigCreated, cmpId: 5, vendorListVersion: 10, consentLanguage: "EN", publisherCC: "FR", purposesConsent: [1, 2, 3], vendorConsents: vc, vendorLIs: [] } });
|
|
110
|
+
var d = b.iabTcf.parseString(s);
|
|
111
|
+
check("vendorConsents round-trip " + idx, JSON.stringify(Array.from(d.core.vendorConsents.ids)) === JSON.stringify(vc));
|
|
112
|
+
check("purposesConsent round-trip " + idx, JSON.stringify(Array.from(d.core.purposesConsent)) === "[1,2,3]");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Publisher restrictions round-trip.
|
|
116
|
+
var prDec = b.iabTcf.parseString(b.iabTcf.encode({ core: { version: 2, createdAt: bigCreated, lastUpdatedAt: bigCreated, cmpId: 9, vendorListVersion: 1, consentLanguage: "IT", publisherCC: "IT", vendorConsents: [], vendorLIs: [], publisherRestrictions: [{ purposeId: 2, restrictionType: 1, vendorIds: [5, 6, 7, 50] }] } }));
|
|
117
|
+
check("publisher restriction round-trips",
|
|
118
|
+
prDec.core.publisherRestrictions.length === 1 &&
|
|
119
|
+
prDec.core.publisherRestrictions[0].purposeId === 2 &&
|
|
120
|
+
JSON.stringify(prDec.core.publisherRestrictions[0].vendorIds) === "[5,6,7,50]");
|
|
121
|
+
|
|
122
|
+
check("isValid true for the spec vector", b.iabTcf.isValid(SPEC) === true);
|
|
123
|
+
check("isValid false for garbage", b.iabTcf.isValid("nonsense!!") === false);
|
|
124
|
+
// A truncated core (a dropped trailing character cuts into the mandatory
|
|
125
|
+
// NumPubRestrictions field) must NOT validate — the reader's bounds check
|
|
126
|
+
// rejects it rather than treating the gap as "no restrictions".
|
|
127
|
+
check("isValid false for a truncated core", b.iabTcf.isValid(SPEC_CORE.slice(0, -1)) === false);
|
|
128
|
+
rejects("encode without core throws", function () { b.iabTcf.encode({}); }, "BAD_INPUT");
|
|
129
|
+
rejects("encode rejects a non-positive id",
|
|
130
|
+
function () { b.iabTcf.encode({ core: { consentLanguage: "EN", publisherCC: "DE", vendorConsents: [0], vendorLIs: [] } }); }, "BAD_VALUE");
|
|
71
131
|
}
|
|
72
132
|
|
|
73
133
|
module.exports = { run: run };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blamejs/blamejs-shop",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.35",
|
|
4
4
|
"description": "Open-source framework built on blamejs. Vendored stack, zero npm runtime deps, PQC-first crypto, security-on by default.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"test": "node test/smoke.js",
|
|
9
9
|
"vendor": "bash scripts/vendor-update.sh",
|
|
10
10
|
"sync-assets": "node scripts/sync-r2-assets.js",
|
|
11
|
-
"
|
|
11
|
+
"release": "node scripts/release.js"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=24.14.1"
|