mirror of
https://github.com/PR0M3TH3AN/bitgate.git
synced 2026-10-27 08:31:00 +00:00
fix(site): move the demo module out of the page to satisfy CSP
script-src 'self' with no 'unsafe-inline' blocks inline module scripts, so the demo initialized nothing on the deployed site while working perfectly locally — the dev server sends no CSP, so only the real deployment showed it. Extracted to assets/demo.js rather than relaxing the policy. Loosening CSP for a page that renders moderation decisions would be the wrong trade.
This commit is contained in:
1 parent
b24bf3955d
commit
015f9c3ad4
3 files changed
+155
-154
No files matched your search
@@ -12,3 +12,5 @@ coverage/
|
||||
|
||||
# Built site output (Vercel builds it)
|
||||
packages/site/dist/
|
||||
.vercel
|
||||
.env*
|
||||
@@ -0,0 +1,152 @@
|
||||
import {
|
||||
createBitGate,
|
||||
createMemoryTransport,
|
||||
defineBitGateElements,
|
||||
COMMERCE_POLICY,
|
||||
} from "../vendor/bitgate/bitgate.js";
|
||||
|
||||
defineBitGateElements();
|
||||
|
||||
const key = (seed) => seed.repeat(64).slice(0, 64);
|
||||
const ROOT = key("a1");
|
||||
const LISTING_MOD = key("b2");
|
||||
const SELLER_MOD = key("c3");
|
||||
const SELLER = key("d4");
|
||||
const SHOPPER = key("e5");
|
||||
const NOW = Math.floor(Date.now() / 1000);
|
||||
const trusted = (index) => key(index.toString(16).padStart(2, "0"));
|
||||
|
||||
const TARGET = { type: "address", kind: "30078", pubkey: SELLER, identifier: "sku-001" };
|
||||
const TARGET_KEY = `address:30078:${SELLER}:sku-001`;
|
||||
const PROFILES = ["browse", "detail", "checkout", "seller-dashboard"];
|
||||
const VIEWERS = { shopper: SHOPPER, listing: LISTING_MOD, seller: SELLER_MOD };
|
||||
|
||||
// An in-memory transport keeps the demo deterministic: nothing is fetched and
|
||||
// nothing is published, so it works offline and cannot touch real relays.
|
||||
const runtime = createBitGate({
|
||||
applicationId: "bitgate-demo",
|
||||
namespace: "demo",
|
||||
root: ROOT,
|
||||
transport: createMemoryTransport(),
|
||||
policy: COMMERCE_POLICY,
|
||||
now: () => NOW,
|
||||
});
|
||||
|
||||
const COMMERCE_ROLES = {
|
||||
listing_moderator: ["contribute-event-deny", "contribute-address-deny", "review-evidence"],
|
||||
seller_moderator: ["contribute-user-deny", "review-evidence"],
|
||||
super_admin: [
|
||||
"manage-roles", "manage-policy", "manage-community-sources",
|
||||
"contribute-user-allow", "contribute-user-deny", "contribute-event-deny",
|
||||
"contribute-address-deny", "contribute-trust-seed", "review-evidence",
|
||||
],
|
||||
};
|
||||
|
||||
let reportCount = 0;
|
||||
let muteCount = 0;
|
||||
|
||||
function seedRoster() {
|
||||
runtime.admin.setRoles({
|
||||
root: ROOT,
|
||||
roles: COMMERCE_ROLES,
|
||||
actors: {
|
||||
[ROOT]: ["super_admin"],
|
||||
[LISTING_MOD]: ["listing_moderator"],
|
||||
[SELLER_MOD]: ["seller_moderator"],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function setViewer(name) {
|
||||
runtime.setViewer(VIEWERS[name]);
|
||||
// Viewer switching clears viewer-scoped state by design, so trust is
|
||||
// re-seeded afterwards — the same order a real integration must use.
|
||||
runtime.trust.setContacts(Array.from({ length: 24 }, (_, i) => trusted(i + 1)));
|
||||
}
|
||||
|
||||
function reset() {
|
||||
reportCount = 0;
|
||||
muteCount = 0;
|
||||
runtime.reports.clearTarget(TARGET_KEY);
|
||||
runtime.mutes.lists.clear();
|
||||
runtime.admin.setContributions([]);
|
||||
seedRoster();
|
||||
setViewer("shopper");
|
||||
runtime.invalidateDecisions();
|
||||
render();
|
||||
}
|
||||
|
||||
const veil = document.getElementById("veil");
|
||||
const caps = document.getElementById("caps");
|
||||
veil.runtime = runtime;
|
||||
veil.target = TARGET;
|
||||
caps.runtime = runtime;
|
||||
|
||||
function render() {
|
||||
const body = document.querySelector("#surfaces tbody");
|
||||
body.innerHTML = PROFILES.map((profile) => {
|
||||
const decision = runtime.evaluate(TARGET, { profile });
|
||||
const transaction = decision.transaction?.effect ?? "allow";
|
||||
return `<tr>
|
||||
<td><code>${profile}</code></td>
|
||||
<td><span class="verdict ${decision.visibility.effect}">${decision.visibility.effect}</span></td>
|
||||
<td><span class="verdict ${transaction}">${transaction}</span></td>
|
||||
<td><span class="verdict ${decision.ranking.effect === "downrank" ? "downrank" : "allow"}">${decision.ranking.effect}</span></td>
|
||||
</tr>`;
|
||||
}).join("");
|
||||
|
||||
const detail = runtime.evaluate(TARGET, { profile: "seller-dashboard" });
|
||||
const reasons = detail.reasons.map((reason) => reason.id);
|
||||
document.getElementById("reasons").innerHTML = reasons.length
|
||||
? reasons.map((id) => `<li><code>${id}</code></li>`).join("")
|
||||
: "<li>No governance signals yet — add some evidence.</li>";
|
||||
}
|
||||
|
||||
runtime.on("change", render);
|
||||
|
||||
document.getElementById("add-scam").addEventListener("click", () => {
|
||||
reportCount += 1;
|
||||
runtime.reports.ingest(
|
||||
{ reporter: trusted(reportCount), category: "scam", createdAt: NOW - 60 },
|
||||
TARGET_KEY,
|
||||
);
|
||||
});
|
||||
|
||||
document.getElementById("add-malware").addEventListener("click", () => {
|
||||
reportCount += 1;
|
||||
runtime.reports.ingest(
|
||||
{ reporter: trusted(reportCount), category: "malware", createdAt: NOW - 60 },
|
||||
TARGET_KEY,
|
||||
);
|
||||
});
|
||||
|
||||
document.getElementById("add-mute").addEventListener("click", () => {
|
||||
muteCount += 1;
|
||||
runtime.mutes.replaceList({
|
||||
owner: trusted(muteCount),
|
||||
updatedAt: NOW - 60,
|
||||
entries: [{ pubkey: SELLER }],
|
||||
hasEncryptedEntries: false,
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("deny-seller").addEventListener("click", () => {
|
||||
runtime.admin.upsertContribution({
|
||||
actor: SELLER_MOD,
|
||||
kind: "user-deny",
|
||||
targets: [{ type: "user", pubkey: SELLER }],
|
||||
createdAt: NOW + reportCount + muteCount,
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("block").addEventListener("click", () => {
|
||||
runtime.trust.setBlocks([SELLER]);
|
||||
});
|
||||
|
||||
document.getElementById("reset").addEventListener("click", reset);
|
||||
|
||||
for (const button of document.querySelectorAll("[data-viewer]")) {
|
||||
button.addEventListener("click", () => setViewer(button.dataset.viewer));
|
||||
}
|
||||
|
||||
reset();
|
||||
@@ -125,160 +125,7 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module">
|
||||
import {
|
||||
createBitGate,
|
||||
createMemoryTransport,
|
||||
defineBitGateElements,
|
||||
COMMERCE_POLICY,
|
||||
} from "./vendor/bitgate/bitgate.js";
|
||||
|
||||
defineBitGateElements();
|
||||
|
||||
const key = (seed) => seed.repeat(64).slice(0, 64);
|
||||
const ROOT = key("a1");
|
||||
const LISTING_MOD = key("b2");
|
||||
const SELLER_MOD = key("c3");
|
||||
const SELLER = key("d4");
|
||||
const SHOPPER = key("e5");
|
||||
const NOW = Math.floor(Date.now() / 1000);
|
||||
const trusted = (index) => key(index.toString(16).padStart(2, "0"));
|
||||
|
||||
const TARGET = { type: "address", kind: "30078", pubkey: SELLER, identifier: "sku-001" };
|
||||
const TARGET_KEY = `address:30078:${SELLER}:sku-001`;
|
||||
const PROFILES = ["browse", "detail", "checkout", "seller-dashboard"];
|
||||
const VIEWERS = { shopper: SHOPPER, listing: LISTING_MOD, seller: SELLER_MOD };
|
||||
|
||||
// An in-memory transport keeps the demo deterministic: nothing is fetched and
|
||||
// nothing is published, so it works offline and cannot touch real relays.
|
||||
const runtime = createBitGate({
|
||||
applicationId: "bitgate-demo",
|
||||
namespace: "demo",
|
||||
root: ROOT,
|
||||
transport: createMemoryTransport(),
|
||||
policy: COMMERCE_POLICY,
|
||||
now: () => NOW,
|
||||
});
|
||||
|
||||
const COMMERCE_ROLES = {
|
||||
listing_moderator: ["contribute-event-deny", "contribute-address-deny", "review-evidence"],
|
||||
seller_moderator: ["contribute-user-deny", "review-evidence"],
|
||||
super_admin: [
|
||||
"manage-roles", "manage-policy", "manage-community-sources",
|
||||
"contribute-user-allow", "contribute-user-deny", "contribute-event-deny",
|
||||
"contribute-address-deny", "contribute-trust-seed", "review-evidence",
|
||||
],
|
||||
};
|
||||
|
||||
let reportCount = 0;
|
||||
let muteCount = 0;
|
||||
|
||||
function seedRoster() {
|
||||
runtime.admin.setRoles({
|
||||
root: ROOT,
|
||||
roles: COMMERCE_ROLES,
|
||||
actors: {
|
||||
[ROOT]: ["super_admin"],
|
||||
[LISTING_MOD]: ["listing_moderator"],
|
||||
[SELLER_MOD]: ["seller_moderator"],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function setViewer(name) {
|
||||
runtime.setViewer(VIEWERS[name]);
|
||||
// Viewer switching clears viewer-scoped state by design, so trust is
|
||||
// re-seeded afterwards — the same order a real integration must use.
|
||||
runtime.trust.setContacts(Array.from({ length: 24 }, (_, i) => trusted(i + 1)));
|
||||
}
|
||||
|
||||
function reset() {
|
||||
reportCount = 0;
|
||||
muteCount = 0;
|
||||
runtime.reports.clearTarget(TARGET_KEY);
|
||||
runtime.mutes.lists.clear();
|
||||
runtime.admin.setContributions([]);
|
||||
seedRoster();
|
||||
setViewer("shopper");
|
||||
runtime.invalidateDecisions();
|
||||
render();
|
||||
}
|
||||
|
||||
const veil = document.getElementById("veil");
|
||||
const caps = document.getElementById("caps");
|
||||
veil.runtime = runtime;
|
||||
veil.target = TARGET;
|
||||
caps.runtime = runtime;
|
||||
|
||||
function render() {
|
||||
const body = document.querySelector("#surfaces tbody");
|
||||
body.innerHTML = PROFILES.map((profile) => {
|
||||
const decision = runtime.evaluate(TARGET, { profile });
|
||||
const transaction = decision.transaction?.effect ?? "allow";
|
||||
return `<tr>
|
||||
<td><code>${profile}</code></td>
|
||||
<td><span class="verdict ${decision.visibility.effect}">${decision.visibility.effect}</span></td>
|
||||
<td><span class="verdict ${transaction}">${transaction}</span></td>
|
||||
<td><span class="verdict ${decision.ranking.effect === "downrank" ? "downrank" : "allow"}">${decision.ranking.effect}</span></td>
|
||||
</tr>`;
|
||||
}).join("");
|
||||
|
||||
const detail = runtime.evaluate(TARGET, { profile: "seller-dashboard" });
|
||||
const reasons = detail.reasons.map((reason) => reason.id);
|
||||
document.getElementById("reasons").innerHTML = reasons.length
|
||||
? reasons.map((id) => `<li><code>${id}</code></li>`).join("")
|
||||
: "<li>No governance signals yet — add some evidence.</li>";
|
||||
}
|
||||
|
||||
runtime.on("change", render);
|
||||
|
||||
document.getElementById("add-scam").addEventListener("click", () => {
|
||||
reportCount += 1;
|
||||
runtime.reports.ingest(
|
||||
{ reporter: trusted(reportCount), category: "scam", createdAt: NOW - 60 },
|
||||
TARGET_KEY,
|
||||
);
|
||||
});
|
||||
|
||||
document.getElementById("add-malware").addEventListener("click", () => {
|
||||
reportCount += 1;
|
||||
runtime.reports.ingest(
|
||||
{ reporter: trusted(reportCount), category: "malware", createdAt: NOW - 60 },
|
||||
TARGET_KEY,
|
||||
);
|
||||
});
|
||||
|
||||
document.getElementById("add-mute").addEventListener("click", () => {
|
||||
muteCount += 1;
|
||||
runtime.mutes.replaceList({
|
||||
owner: trusted(muteCount),
|
||||
updatedAt: NOW - 60,
|
||||
entries: [{ pubkey: SELLER }],
|
||||
hasEncryptedEntries: false,
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("deny-seller").addEventListener("click", () => {
|
||||
runtime.admin.upsertContribution({
|
||||
actor: SELLER_MOD,
|
||||
kind: "user-deny",
|
||||
targets: [{ type: "user", pubkey: SELLER }],
|
||||
createdAt: NOW + reportCount + muteCount,
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("block").addEventListener("click", () => {
|
||||
runtime.trust.setBlocks([SELLER]);
|
||||
});
|
||||
|
||||
document.getElementById("reset").addEventListener("click", reset);
|
||||
|
||||
for (const button of document.querySelectorAll("[data-viewer]")) {
|
||||
button.addEventListener("click", () => setViewer(button.dataset.viewer));
|
||||
}
|
||||
|
||||
reset();
|
||||
</script>
|
||||
<script type="module" src="./assets/demo.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user