@drm3/sdk 0.1.2
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/LICENSE +75 -0
- package/README.md +426 -0
- package/bin/drm3.js +255 -0
- package/dist/alerts-bell.d.ts +7 -0
- package/dist/alerts-bell.d.ts.map +1 -0
- package/dist/alerts-bell.js +118 -0
- package/dist/alerts-bell.js.map +1 -0
- package/dist/alerts-fetch.d.ts +41 -0
- package/dist/alerts-fetch.d.ts.map +1 -0
- package/dist/alerts-fetch.js +60 -0
- package/dist/alerts-fetch.js.map +1 -0
- package/dist/alerts-styles.d.ts +4 -0
- package/dist/alerts-styles.d.ts.map +1 -0
- package/dist/alerts-styles.js +49 -0
- package/dist/alerts-styles.js.map +1 -0
- package/dist/alerts.d.ts +3 -0
- package/dist/alerts.d.ts.map +1 -0
- package/dist/alerts.global.js +206 -0
- package/dist/alerts.global.text.js +2 -0
- package/dist/alerts.js +21 -0
- package/dist/alerts.js.map +1 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +117 -0
- package/dist/index.js.map +1 -0
- package/dist/keys-canonical.d.ts +7 -0
- package/dist/keys-canonical.d.ts.map +1 -0
- package/dist/keys-canonical.js +57 -0
- package/dist/keys-canonical.js.map +1 -0
- package/dist/keys-generate.d.ts +39 -0
- package/dist/keys-generate.d.ts.map +1 -0
- package/dist/keys-generate.js +39 -0
- package/dist/keys-generate.js.map +1 -0
- package/dist/keys-verify.d.ts +62 -0
- package/dist/keys-verify.d.ts.map +1 -0
- package/dist/keys-verify.js +68 -0
- package/dist/keys-verify.js.map +1 -0
- package/dist/keys.d.ts +4 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +22 -0
- package/dist/keys.js.map +1 -0
- package/dist/meter/adapter.d.ts +35 -0
- package/dist/meter/adapter.d.ts.map +1 -0
- package/dist/meter/adapter.js +0 -0
- package/dist/meter/adapter.js.map +1 -0
- package/dist/meter/cascade.d.ts +56 -0
- package/dist/meter/cascade.d.ts.map +1 -0
- package/dist/meter/cascade.js +64 -0
- package/dist/meter/cascade.js.map +1 -0
- package/dist/meter/client.d.ts +78 -0
- package/dist/meter/client.d.ts.map +1 -0
- package/dist/meter/client.js +84 -0
- package/dist/meter/client.js.map +1 -0
- package/dist/meter/core.d.ts +37 -0
- package/dist/meter/core.d.ts.map +1 -0
- package/dist/meter/core.js +49 -0
- package/dist/meter/core.js.map +1 -0
- package/dist/meter/gate-client.d.ts +94 -0
- package/dist/meter/gate-client.d.ts.map +1 -0
- package/dist/meter/gate-client.js +77 -0
- package/dist/meter/gate-client.js.map +1 -0
- package/dist/meter/gate-decide.d.ts +61 -0
- package/dist/meter/gate-decide.d.ts.map +1 -0
- package/dist/meter/gate-decide.js +54 -0
- package/dist/meter/gate-decide.js.map +1 -0
- package/dist/meter/gate.d.ts +10 -0
- package/dist/meter/gate.d.ts.map +1 -0
- package/dist/meter/gate.js +10 -0
- package/dist/meter/gate.js.map +1 -0
- package/dist/meter/index.d.ts +28 -0
- package/dist/meter/index.d.ts.map +1 -0
- package/dist/meter/index.js +28 -0
- package/dist/meter/index.js.map +1 -0
- package/dist/meter/meters-buckets.d.ts +56 -0
- package/dist/meter/meters-buckets.d.ts.map +1 -0
- package/dist/meter/meters-buckets.js +111 -0
- package/dist/meter/meters-buckets.js.map +1 -0
- package/dist/meter/meters-charge.d.ts +13 -0
- package/dist/meter/meters-charge.d.ts.map +1 -0
- package/dist/meter/meters-charge.js +76 -0
- package/dist/meter/meters-charge.js.map +1 -0
- package/dist/meter/meters.d.ts +63 -0
- package/dist/meter/meters.d.ts.map +1 -0
- package/dist/meter/meters.js +44 -0
- package/dist/meter/meters.js.map +1 -0
- package/dist/meter/plans.d.ts +63 -0
- package/dist/meter/plans.d.ts.map +1 -0
- package/dist/meter/plans.js +114 -0
- package/dist/meter/plans.js.map +1 -0
- package/dist/meter/pricebook.d.ts +32 -0
- package/dist/meter/pricebook.d.ts.map +1 -0
- package/dist/meter/pricebook.js +38 -0
- package/dist/meter/pricebook.js.map +1 -0
- package/dist/meter/resolve.d.ts +103 -0
- package/dist/meter/resolve.d.ts.map +1 -0
- package/dist/meter/resolve.js +100 -0
- package/dist/meter/resolve.js.map +1 -0
- package/dist/meter/sink.d.ts +26 -0
- package/dist/meter/sink.d.ts.map +1 -0
- package/dist/meter/sink.js +9 -0
- package/dist/meter/sink.js.map +1 -0
- package/dist/meter/sql/d1.d.ts +32 -0
- package/dist/meter/sql/d1.d.ts.map +1 -0
- package/dist/meter/sql/d1.js +41 -0
- package/dist/meter/sql/d1.js.map +1 -0
- package/dist/meter/sql/driver.d.ts +34 -0
- package/dist/meter/sql/driver.d.ts.map +1 -0
- package/dist/meter/sql/driver.js +17 -0
- package/dist/meter/sql/driver.js.map +1 -0
- package/dist/meter/sql/postgres.d.ts +23 -0
- package/dist/meter/sql/postgres.d.ts.map +1 -0
- package/dist/meter/sql/postgres.js +36 -0
- package/dist/meter/sql/postgres.js.map +1 -0
- package/dist/meter/sql/schema.d.ts +13 -0
- package/dist/meter/sql/schema.d.ts.map +1 -0
- package/dist/meter/sql/schema.js +32 -0
- package/dist/meter/sql/schema.js.map +1 -0
- package/dist/meter/sql/sql-adapter.d.ts +36 -0
- package/dist/meter/sql/sql-adapter.d.ts.map +1 -0
- package/dist/meter/sql/sql-adapter.js +124 -0
- package/dist/meter/sql/sql-adapter.js.map +1 -0
- package/dist/meter/sql/statements.d.ts +30 -0
- package/dist/meter/sql/statements.d.ts.map +1 -0
- package/dist/meter/sql/statements.js +24 -0
- package/dist/meter/sql/statements.js.map +1 -0
- package/dist/meter/types.d.ts +48 -0
- package/dist/meter/types.d.ts.map +1 -0
- package/dist/meter/types.js +8 -0
- package/dist/meter/types.js.map +1 -0
- package/dist/meter/units.d.ts +33 -0
- package/dist/meter/units.d.ts.map +1 -0
- package/dist/meter/units.js +45 -0
- package/dist/meter/units.js.map +1 -0
- package/dist/meter/wallet-buckets.d.ts +63 -0
- package/dist/meter/wallet-buckets.d.ts.map +1 -0
- package/dist/meter/wallet-buckets.js +131 -0
- package/dist/meter/wallet-buckets.js.map +1 -0
- package/dist/meter/wallet-charge.d.ts +15 -0
- package/dist/meter/wallet-charge.d.ts.map +1 -0
- package/dist/meter/wallet-charge.js +64 -0
- package/dist/meter/wallet-charge.js.map +1 -0
- package/dist/meter/wallet.d.ts +68 -0
- package/dist/meter/wallet.d.ts.map +1 -0
- package/dist/meter/wallet.js +47 -0
- package/dist/meter/wallet.js.map +1 -0
- package/dist/receipts-api.d.ts +5 -0
- package/dist/receipts-api.d.ts.map +1 -0
- package/dist/receipts-api.js +117 -0
- package/dist/receipts-api.js.map +1 -0
- package/dist/receipts-chain.d.ts +3 -0
- package/dist/receipts-chain.d.ts.map +1 -0
- package/dist/receipts-chain.js +121 -0
- package/dist/receipts-chain.js.map +1 -0
- package/dist/receipts-modal.d.ts +14 -0
- package/dist/receipts-modal.d.ts.map +1 -0
- package/dist/receipts-modal.js +100 -0
- package/dist/receipts-modal.js.map +1 -0
- package/dist/receipts-registry.d.ts +4 -0
- package/dist/receipts-registry.d.ts.map +1 -0
- package/dist/receipts-registry.js +54 -0
- package/dist/receipts-registry.js.map +1 -0
- package/dist/receipts-render.d.ts +9 -0
- package/dist/receipts-render.d.ts.map +1 -0
- package/dist/receipts-render.js +83 -0
- package/dist/receipts-render.js.map +1 -0
- package/dist/receipts-types.d.ts +61 -0
- package/dist/receipts-types.d.ts.map +1 -0
- package/dist/receipts-types.js +12 -0
- package/dist/receipts-types.js.map +1 -0
- package/dist/receipts-ui.d.ts +10 -0
- package/dist/receipts-ui.d.ts.map +1 -0
- package/dist/receipts-ui.js +103 -0
- package/dist/receipts-ui.js.map +1 -0
- package/dist/receipts-verify.d.ts +8 -0
- package/dist/receipts-verify.d.ts.map +1 -0
- package/dist/receipts-verify.js +113 -0
- package/dist/receipts-verify.js.map +1 -0
- package/dist/receipts.d.ts +9 -0
- package/dist/receipts.d.ts.map +1 -0
- package/dist/receipts.global.js +587 -0
- package/dist/receipts.global.text.js +2 -0
- package/dist/receipts.js +28 -0
- package/dist/receipts.js.map +1 -0
- package/dist/sso/index.d.ts +4 -0
- package/dist/sso/index.d.ts.map +1 -0
- package/dist/sso/index.js +10 -0
- package/dist/sso/index.js.map +1 -0
- package/dist/sso/jwt.d.ts +29 -0
- package/dist/sso/jwt.d.ts.map +1 -0
- package/dist/sso/jwt.js +73 -0
- package/dist/sso/jwt.js.map +1 -0
- package/dist/sso/launch.d.ts +70 -0
- package/dist/sso/launch.d.ts.map +1 -0
- package/dist/sso/launch.js +75 -0
- package/dist/sso/launch.js.map +1 -0
- package/dist/sso/revocation.d.ts +23 -0
- package/dist/sso/revocation.d.ts.map +1 -0
- package/dist/sso/revocation.js +53 -0
- package/dist/sso/revocation.js.map +1 -0
- package/dist/switcher.d.ts +22 -0
- package/dist/switcher.d.ts.map +1 -0
- package/dist/switcher.js +174 -0
- package/dist/switcher.js.map +1 -0
- package/dist/x402/chain.d.ts +49 -0
- package/dist/x402/chain.d.ts.map +1 -0
- package/dist/x402/chain.js +33 -0
- package/dist/x402/chain.js.map +1 -0
- package/dist/x402/crypto.d.ts +25 -0
- package/dist/x402/crypto.d.ts.map +1 -0
- package/dist/x402/crypto.js +74 -0
- package/dist/x402/crypto.js.map +1 -0
- package/dist/x402/eip712.d.ts +20 -0
- package/dist/x402/eip712.d.ts.map +1 -0
- package/dist/x402/eip712.js +49 -0
- package/dist/x402/eip712.js.map +1 -0
- package/dist/x402/envelope.d.ts +45 -0
- package/dist/x402/envelope.d.ts.map +1 -0
- package/dist/x402/envelope.js +50 -0
- package/dist/x402/envelope.js.map +1 -0
- package/dist/x402/index.d.ts +41 -0
- package/dist/x402/index.d.ts.map +1 -0
- package/dist/x402/index.js +41 -0
- package/dist/x402/index.js.map +1 -0
- package/dist/x402/verify.d.ts +51 -0
- package/dist/x402/verify.d.ts.map +1 -0
- package/dist/x402/verify.js +110 -0
- package/dist/x402/verify.js.map +1 -0
- package/package.json +100 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var drm3Alerts = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/alerts.ts
|
|
22
|
+
var alerts_exports = {};
|
|
23
|
+
__export(alerts_exports, {
|
|
24
|
+
fetchAlerts: () => fetchAlerts,
|
|
25
|
+
mountAlertsBell: () => mountAlertsBell
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// src/alerts-fetch.ts
|
|
29
|
+
var DEFAULT_HUB = "https://drm3.network";
|
|
30
|
+
function esc(s) {
|
|
31
|
+
return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
32
|
+
}
|
|
33
|
+
function safeLink(link) {
|
|
34
|
+
const l = String(link || "");
|
|
35
|
+
return /^https?:\/\//i.test(l) || l.startsWith("/") ? l : "";
|
|
36
|
+
}
|
|
37
|
+
function timeAgo(iso) {
|
|
38
|
+
if (!iso) return "";
|
|
39
|
+
const t = new Date(iso).getTime();
|
|
40
|
+
if (Number.isNaN(t)) return "";
|
|
41
|
+
const s = Math.max(0, (Date.now() - t) / 1e3);
|
|
42
|
+
if (s < 60) return "just now";
|
|
43
|
+
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
44
|
+
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
|
|
45
|
+
return `${Math.floor(s / 86400)}d ago`;
|
|
46
|
+
}
|
|
47
|
+
function sortNewest(list) {
|
|
48
|
+
return [...list].sort((a, b) => String(b.created_at || "").localeCompare(String(a.created_at || "")));
|
|
49
|
+
}
|
|
50
|
+
function hubOf(opts) {
|
|
51
|
+
return (opts.hub || DEFAULT_HUB).replace(/\/+$/, "");
|
|
52
|
+
}
|
|
53
|
+
async function fetchAlerts(opts = {}) {
|
|
54
|
+
try {
|
|
55
|
+
const r = await fetch(`${hubOf(opts)}/api/sso/alerts`, {
|
|
56
|
+
credentials: "include",
|
|
57
|
+
headers: opts.ssoToken ? { authorization: `Bearer ${opts.ssoToken}` } : void 0
|
|
58
|
+
});
|
|
59
|
+
if (!r.ok) return null;
|
|
60
|
+
const d = await r.json();
|
|
61
|
+
if (!Array.isArray(d.alerts)) return null;
|
|
62
|
+
const unread = typeof d.unread === "number" ? d.unread : d.alerts.filter((a) => !a.read).length;
|
|
63
|
+
return { alerts: d.alerts, unread };
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/alerts-styles.ts
|
|
70
|
+
var KIND_DOTS = {
|
|
71
|
+
info: "#60a5fa",
|
|
72
|
+
success: "#4ade80",
|
|
73
|
+
warning: "#fbbf24",
|
|
74
|
+
critical: "#f87171"
|
|
75
|
+
};
|
|
76
|
+
var BELL_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>`;
|
|
77
|
+
function injectStyles() {
|
|
78
|
+
if (typeof document === "undefined" || document.getElementById("drm3al-css")) return;
|
|
79
|
+
const s = document.createElement("style");
|
|
80
|
+
s.id = "drm3al-css";
|
|
81
|
+
s.textContent = `
|
|
82
|
+
.drm3al{position:relative;display:inline-block;font-family:ui-sans-serif,system-ui,sans-serif}
|
|
83
|
+
.drm3al-btn{position:relative;display:grid;place-items:center;width:38px;height:38px;border:0;border-radius:10px;
|
|
84
|
+
background:transparent;color:inherit;cursor:pointer;opacity:.8}
|
|
85
|
+
.drm3al-btn:hover{opacity:1;background:rgba(127,127,127,.12)}
|
|
86
|
+
.drm3al-btn svg{width:20px;height:20px}
|
|
87
|
+
.drm3al-badge{position:absolute;top:2px;right:2px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;
|
|
88
|
+
background:#dc2626;color:#fff;font-size:10px;font-weight:700;line-height:16px;text-align:center;pointer-events:none}
|
|
89
|
+
.drm3al-panel{position:absolute;right:0;top:46px;width:340px;max-height:70vh;overflow-y:auto;z-index:9999;
|
|
90
|
+
background:#0f1318;border:1px solid #1c2530;border-radius:14px;box-shadow:0 18px 50px rgba(0,0,0,.5);
|
|
91
|
+
display:none;color:#f2f6fa;text-align:left}
|
|
92
|
+
.drm3al[data-open="1"] .drm3al-panel{display:block}
|
|
93
|
+
.drm3al-h{padding:12px 14px 6px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#9fb1c4}
|
|
94
|
+
.drm3al-list{padding:0 6px 6px}
|
|
95
|
+
.drm3al-row{display:flex;gap:10px;align-items:flex-start;padding:10px 8px;border-radius:10px;text-decoration:none;color:inherit}
|
|
96
|
+
.drm3al-row+.drm3al-row{margin-top:2px}
|
|
97
|
+
a.drm3al-row:hover{background:#161e28}
|
|
98
|
+
.drm3al-unread{background:rgba(96,165,250,.08)}
|
|
99
|
+
.drm3al-dot{flex:none;width:8px;height:8px;border-radius:50%;margin-top:5px}
|
|
100
|
+
.drm3al-c{flex:1;min-width:0}
|
|
101
|
+
.drm3al-t{font-size:13px;font-weight:600;color:#f2f6fa;line-height:1.35;word-break:break-word}
|
|
102
|
+
.drm3al-b{font-size:12px;color:#9fb1c4;line-height:1.45;margin-top:2px;word-break:break-word}
|
|
103
|
+
.drm3al-ago{font-size:11px;color:#9fb1c4;margin-top:3px}
|
|
104
|
+
.drm3al-empty{padding:18px 14px 22px;color:#9fb1c4;font-size:12px}
|
|
105
|
+
.drm3al-foot{padding:10px 14px;border-top:1px solid #1c2530}
|
|
106
|
+
.drm3al-foot a{color:#7aa7d6;font-size:12px;text-decoration:none}
|
|
107
|
+
.drm3al-foot a:hover{text-decoration:underline}
|
|
108
|
+
@media (max-width:479px){.drm3al-panel{position:fixed;left:10px;right:10px;top:64px;width:auto;max-height:calc(100vh - 84px)}}`;
|
|
109
|
+
document.head.appendChild(s);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/alerts-bell.ts
|
|
113
|
+
function mountAlertsBell(target, opts = {}) {
|
|
114
|
+
if (!target || typeof document === "undefined") return;
|
|
115
|
+
if (opts.authed === false) {
|
|
116
|
+
target.replaceChildren();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
void fetchAlerts(opts).then((d) => {
|
|
120
|
+
if (d) buildBell(target, opts, d);
|
|
121
|
+
else target.replaceChildren();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function buildBell(target, opts, initial) {
|
|
125
|
+
injectStyles();
|
|
126
|
+
const hub = hubOf(opts);
|
|
127
|
+
target.classList.add("drm3al");
|
|
128
|
+
target.innerHTML = `<button class="drm3al-btn" type="button" aria-label="Alerts" aria-haspopup="true" aria-expanded="false">${BELL_SVG}<span class="drm3al-badge" hidden></span></button>
|
|
129
|
+
<div class="drm3al-panel" role="menu" aria-label="Alerts">
|
|
130
|
+
<div class="drm3al-h">Alerts</div>
|
|
131
|
+
<div class="drm3al-list"></div>
|
|
132
|
+
<div class="drm3al-foot"><a href="${esc(hub)}/account?tab=alerts">All alerts →</a></div>
|
|
133
|
+
</div>`;
|
|
134
|
+
const btn = target.querySelector(".drm3al-btn");
|
|
135
|
+
const badge = target.querySelector(".drm3al-badge");
|
|
136
|
+
const list = target.querySelector(".drm3al-list");
|
|
137
|
+
let alerts = sortNewest(initial.alerts);
|
|
138
|
+
const setUnread = (n) => {
|
|
139
|
+
badge.hidden = n <= 0;
|
|
140
|
+
badge.textContent = n > 9 ? "9+" : String(n);
|
|
141
|
+
opts.onCount?.(n);
|
|
142
|
+
};
|
|
143
|
+
const row = (a) => {
|
|
144
|
+
const dot = KIND_DOTS[a.kind] || KIND_DOTS.info;
|
|
145
|
+
const link = safeLink(a.link);
|
|
146
|
+
const body = a.body ? `<div class="drm3al-b">${esc(a.body)}</div>` : "";
|
|
147
|
+
const inner = `<span class="drm3al-dot" style="background:${dot}"></span><div class="drm3al-c"><div class="drm3al-t">${esc(a.title || "")}</div>${body}<div class="drm3al-ago">${esc(timeAgo(a.created_at))}</div></div>`;
|
|
148
|
+
const cls = `drm3al-row${a.read ? "" : " drm3al-unread"}`;
|
|
149
|
+
return link ? `<a class="${cls}" href="${esc(link)}">${inner}</a>` : `<div class="${cls}">${inner}</div>`;
|
|
150
|
+
};
|
|
151
|
+
const render = () => {
|
|
152
|
+
list.innerHTML = alerts.length ? alerts.map(row).join("") : `<div class="drm3al-empty">No alerts.</div>`;
|
|
153
|
+
};
|
|
154
|
+
const refresh = async () => {
|
|
155
|
+
const d = await fetchAlerts(opts);
|
|
156
|
+
if (!d) return;
|
|
157
|
+
alerts = sortNewest(d.alerts);
|
|
158
|
+
setUnread(d.unread);
|
|
159
|
+
if (target.getAttribute("data-open") === "1") render();
|
|
160
|
+
};
|
|
161
|
+
const markAllRead = async () => {
|
|
162
|
+
try {
|
|
163
|
+
await fetch(`${hub}/api/sso/alerts/read`, {
|
|
164
|
+
method: "POST",
|
|
165
|
+
credentials: "include",
|
|
166
|
+
headers: { "content-type": "application/json", ...opts.ssoToken ? { authorization: `Bearer ${opts.ssoToken}` } : {} },
|
|
167
|
+
body: JSON.stringify({ all: true })
|
|
168
|
+
});
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const close = () => {
|
|
173
|
+
target.removeAttribute("data-open");
|
|
174
|
+
btn.setAttribute("aria-expanded", "false");
|
|
175
|
+
};
|
|
176
|
+
const open = async () => {
|
|
177
|
+
target.setAttribute("data-open", "1");
|
|
178
|
+
btn.setAttribute("aria-expanded", "true");
|
|
179
|
+
render();
|
|
180
|
+
const d = await fetchAlerts(opts);
|
|
181
|
+
if (d) {
|
|
182
|
+
alerts = sortNewest(d.alerts);
|
|
183
|
+
render();
|
|
184
|
+
}
|
|
185
|
+
void markAllRead();
|
|
186
|
+
setUnread(0);
|
|
187
|
+
for (const a of alerts) a.read = true;
|
|
188
|
+
};
|
|
189
|
+
btn.addEventListener("click", (e) => {
|
|
190
|
+
e.stopPropagation();
|
|
191
|
+
if (target.getAttribute("data-open") === "1") return close();
|
|
192
|
+
void open();
|
|
193
|
+
});
|
|
194
|
+
document.addEventListener("click", (e) => {
|
|
195
|
+
if (!target.contains(e.target)) close();
|
|
196
|
+
});
|
|
197
|
+
document.addEventListener("keydown", (e) => {
|
|
198
|
+
if (e.key === "Escape") close();
|
|
199
|
+
});
|
|
200
|
+
setUnread(initial.unread);
|
|
201
|
+
render();
|
|
202
|
+
const pollMs = opts.pollMs ?? 12e4;
|
|
203
|
+
if (pollMs > 0) setInterval(() => void refresh(), pollMs);
|
|
204
|
+
}
|
|
205
|
+
return __toCommonJS(alerts_exports);
|
|
206
|
+
})();
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/wrap-text.mjs from dist/alerts.global.js - do not edit.
|
|
2
|
+
export default "\"use strict\";\nvar drm3Alerts = (() => {\n var __defProp = Object.defineProperty;\n var __getOwnPropDesc = Object.getOwnPropertyDescriptor;\n var __getOwnPropNames = Object.getOwnPropertyNames;\n var __hasOwnProp = Object.prototype.hasOwnProperty;\n var __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n };\n var __copyProps = (to, from, except, desc) => {\n if (from && typeof from === \"object\" || typeof from === \"function\") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n };\n var __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n // src/alerts.ts\n var alerts_exports = {};\n __export(alerts_exports, {\n fetchAlerts: () => fetchAlerts,\n mountAlertsBell: () => mountAlertsBell\n });\n\n // src/alerts-fetch.ts\n var DEFAULT_HUB = \"https://drm3.network\";\n function esc(s) {\n return String(s).replace(/[&<>\"]/g, (c) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\" })[c]);\n }\n function safeLink(link) {\n const l = String(link || \"\");\n return /^https?:\\/\\//i.test(l) || l.startsWith(\"/\") ? l : \"\";\n }\n function timeAgo(iso) {\n if (!iso) return \"\";\n const t = new Date(iso).getTime();\n if (Number.isNaN(t)) return \"\";\n const s = Math.max(0, (Date.now() - t) / 1e3);\n if (s < 60) return \"just now\";\n if (s < 3600) return `${Math.floor(s / 60)}m ago`;\n if (s < 86400) return `${Math.floor(s / 3600)}h ago`;\n return `${Math.floor(s / 86400)}d ago`;\n }\n function sortNewest(list) {\n return [...list].sort((a, b) => String(b.created_at || \"\").localeCompare(String(a.created_at || \"\")));\n }\n function hubOf(opts) {\n return (opts.hub || DEFAULT_HUB).replace(/\\/+$/, \"\");\n }\n async function fetchAlerts(opts = {}) {\n try {\n const r = await fetch(`${hubOf(opts)}/api/sso/alerts`, {\n credentials: \"include\",\n headers: opts.ssoToken ? { authorization: `Bearer ${opts.ssoToken}` } : void 0\n });\n if (!r.ok) return null;\n const d = await r.json();\n if (!Array.isArray(d.alerts)) return null;\n const unread = typeof d.unread === \"number\" ? d.unread : d.alerts.filter((a) => !a.read).length;\n return { alerts: d.alerts, unread };\n } catch {\n return null;\n }\n }\n\n // src/alerts-styles.ts\n var KIND_DOTS = {\n info: \"#60a5fa\",\n success: \"#4ade80\",\n warning: \"#fbbf24\",\n critical: \"#f87171\"\n };\n var BELL_SVG = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9\"/><path d=\"M10.3 21a1.94 1.94 0 0 0 3.4 0\"/></svg>`;\n function injectStyles() {\n if (typeof document === \"undefined\" || document.getElementById(\"drm3al-css\")) return;\n const s = document.createElement(\"style\");\n s.id = \"drm3al-css\";\n s.textContent = `\n.drm3al{position:relative;display:inline-block;font-family:ui-sans-serif,system-ui,sans-serif}\n.drm3al-btn{position:relative;display:grid;place-items:center;width:38px;height:38px;border:0;border-radius:10px;\n background:transparent;color:inherit;cursor:pointer;opacity:.8}\n.drm3al-btn:hover{opacity:1;background:rgba(127,127,127,.12)}\n.drm3al-btn svg{width:20px;height:20px}\n.drm3al-badge{position:absolute;top:2px;right:2px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;\n background:#dc2626;color:#fff;font-size:10px;font-weight:700;line-height:16px;text-align:center;pointer-events:none}\n.drm3al-panel{position:absolute;right:0;top:46px;width:340px;max-height:70vh;overflow-y:auto;z-index:9999;\n background:#0f1318;border:1px solid #1c2530;border-radius:14px;box-shadow:0 18px 50px rgba(0,0,0,.5);\n display:none;color:#f2f6fa;text-align:left}\n.drm3al[data-open=\"1\"] .drm3al-panel{display:block}\n.drm3al-h{padding:12px 14px 6px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#9fb1c4}\n.drm3al-list{padding:0 6px 6px}\n.drm3al-row{display:flex;gap:10px;align-items:flex-start;padding:10px 8px;border-radius:10px;text-decoration:none;color:inherit}\n.drm3al-row+.drm3al-row{margin-top:2px}\na.drm3al-row:hover{background:#161e28}\n.drm3al-unread{background:rgba(96,165,250,.08)}\n.drm3al-dot{flex:none;width:8px;height:8px;border-radius:50%;margin-top:5px}\n.drm3al-c{flex:1;min-width:0}\n.drm3al-t{font-size:13px;font-weight:600;color:#f2f6fa;line-height:1.35;word-break:break-word}\n.drm3al-b{font-size:12px;color:#9fb1c4;line-height:1.45;margin-top:2px;word-break:break-word}\n.drm3al-ago{font-size:11px;color:#9fb1c4;margin-top:3px}\n.drm3al-empty{padding:18px 14px 22px;color:#9fb1c4;font-size:12px}\n.drm3al-foot{padding:10px 14px;border-top:1px solid #1c2530}\n.drm3al-foot a{color:#7aa7d6;font-size:12px;text-decoration:none}\n.drm3al-foot a:hover{text-decoration:underline}\n@media (max-width:479px){.drm3al-panel{position:fixed;left:10px;right:10px;top:64px;width:auto;max-height:calc(100vh - 84px)}}`;\n document.head.appendChild(s);\n }\n\n // src/alerts-bell.ts\n function mountAlertsBell(target, opts = {}) {\n if (!target || typeof document === \"undefined\") return;\n if (opts.authed === false) {\n target.replaceChildren();\n return;\n }\n void fetchAlerts(opts).then((d) => {\n if (d) buildBell(target, opts, d);\n else target.replaceChildren();\n });\n }\n function buildBell(target, opts, initial) {\n injectStyles();\n const hub = hubOf(opts);\n target.classList.add(\"drm3al\");\n target.innerHTML = `<button class=\"drm3al-btn\" type=\"button\" aria-label=\"Alerts\" aria-haspopup=\"true\" aria-expanded=\"false\">${BELL_SVG}<span class=\"drm3al-badge\" hidden></span></button>\n<div class=\"drm3al-panel\" role=\"menu\" aria-label=\"Alerts\">\n <div class=\"drm3al-h\">Alerts</div>\n <div class=\"drm3al-list\"></div>\n <div class=\"drm3al-foot\"><a href=\"${esc(hub)}/account?tab=alerts\">All alerts →</a></div>\n</div>`;\n const btn = target.querySelector(\".drm3al-btn\");\n const badge = target.querySelector(\".drm3al-badge\");\n const list = target.querySelector(\".drm3al-list\");\n let alerts = sortNewest(initial.alerts);\n const setUnread = (n) => {\n badge.hidden = n <= 0;\n badge.textContent = n > 9 ? \"9+\" : String(n);\n opts.onCount?.(n);\n };\n const row = (a) => {\n const dot = KIND_DOTS[a.kind] || KIND_DOTS.info;\n const link = safeLink(a.link);\n const body = a.body ? `<div class=\"drm3al-b\">${esc(a.body)}</div>` : \"\";\n const inner = `<span class=\"drm3al-dot\" style=\"background:${dot}\"></span><div class=\"drm3al-c\"><div class=\"drm3al-t\">${esc(a.title || \"\")}</div>${body}<div class=\"drm3al-ago\">${esc(timeAgo(a.created_at))}</div></div>`;\n const cls = `drm3al-row${a.read ? \"\" : \" drm3al-unread\"}`;\n return link ? `<a class=\"${cls}\" href=\"${esc(link)}\">${inner}</a>` : `<div class=\"${cls}\">${inner}</div>`;\n };\n const render = () => {\n list.innerHTML = alerts.length ? alerts.map(row).join(\"\") : `<div class=\"drm3al-empty\">No alerts.</div>`;\n };\n const refresh = async () => {\n const d = await fetchAlerts(opts);\n if (!d) return;\n alerts = sortNewest(d.alerts);\n setUnread(d.unread);\n if (target.getAttribute(\"data-open\") === \"1\") render();\n };\n const markAllRead = async () => {\n try {\n await fetch(`${hub}/api/sso/alerts/read`, {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"content-type\": \"application/json\", ...opts.ssoToken ? { authorization: `Bearer ${opts.ssoToken}` } : {} },\n body: JSON.stringify({ all: true })\n });\n } catch {\n }\n };\n const close = () => {\n target.removeAttribute(\"data-open\");\n btn.setAttribute(\"aria-expanded\", \"false\");\n };\n const open = async () => {\n target.setAttribute(\"data-open\", \"1\");\n btn.setAttribute(\"aria-expanded\", \"true\");\n render();\n const d = await fetchAlerts(opts);\n if (d) {\n alerts = sortNewest(d.alerts);\n render();\n }\n void markAllRead();\n setUnread(0);\n for (const a of alerts) a.read = true;\n };\n btn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (target.getAttribute(\"data-open\") === \"1\") return close();\n void open();\n });\n document.addEventListener(\"click\", (e) => {\n if (!target.contains(e.target)) close();\n });\n document.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Escape\") close();\n });\n setUnread(initial.unread);\n render();\n const pollMs = opts.pollMs ?? 12e4;\n if (pollMs > 0) setInterval(() => void refresh(), pollMs);\n }\n return __toCommonJS(alerts_exports);\n})();\n";
|
package/dist/alerts.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// @drm3/sdk/alerts - the DRM3 cross-app alerts bell, as a one-call drop-in.
|
|
2
|
+
//
|
|
3
|
+
// import { mountAlertsBell } from "@drm3/sdk/alerts";
|
|
4
|
+
// mountAlertsBell(document.getElementById("drm3-alerts"));
|
|
5
|
+
//
|
|
6
|
+
// Framework-agnostic, zero deps. Injects its own scoped styles (.drm3al* namespace)
|
|
7
|
+
// and renders a bell with an unread badge plus a dropdown feed of the signed-in
|
|
8
|
+
// user's platform alerts from the DRM3 account hub. Opening the panel acknowledges
|
|
9
|
+
// the feed (POST /api/sso/alerts/read) and clears the badge.
|
|
10
|
+
//
|
|
11
|
+
// Signed-in-only, fail closed: when the feed is not readable (no session, a 401, a
|
|
12
|
+
// network failure) NOTHING is rendered, the same posture as the app switcher.
|
|
13
|
+
//
|
|
14
|
+
// Self-contained by design (zero internal imports beyond the ./alerts-* siblings) so
|
|
15
|
+
// it bundles to a tiny standalone browser global. This is a thin barrel: the
|
|
16
|
+
// implementation lives in ./alerts-fetch (feed client + helpers), ./alerts-bell (the
|
|
17
|
+
// bell UI), and ./alerts-styles (injected CSS + SVG). The browser build runs
|
|
18
|
+
// `esbuild src/alerts.ts`, which bundles all three transitively.
|
|
19
|
+
export { fetchAlerts } from "./alerts-fetch.js";
|
|
20
|
+
export { mountAlertsBell } from "./alerts-bell.js";
|
|
21
|
+
//# sourceMappingURL=alerts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alerts.js","sourceRoot":"","sources":["../src/alerts.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,wDAAwD;AACxD,6DAA6D;AAC7D,EAAE;AACF,oFAAoF;AACpF,gFAAgF;AAChF,mFAAmF;AACnF,6DAA6D;AAC7D,EAAE;AACF,mFAAmF;AACnF,8EAA8E;AAC9E,EAAE;AACF,qFAAqF;AACrF,6EAA6E;AAC7E,qFAAqF;AACrF,6EAA6E;AAC7E,iEAAiE;AACjE,OAAO,EAAsC,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export declare const ACCOUNT_HUB = "https://drm3.network";
|
|
2
|
+
export declare const STATUS_HUB = "https://status.drm3.network";
|
|
3
|
+
/** A DRM3 network app, as published by the canonical SKU registry on status.drm3.network. */
|
|
4
|
+
export interface DRM3App {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
url: string;
|
|
8
|
+
tagline?: string;
|
|
9
|
+
/** Brand accent (hex) used by the switcher tile gradient. */
|
|
10
|
+
color?: string;
|
|
11
|
+
/** Live rollup of the app's health: "up" | "degraded" | "down" | "unknown". */
|
|
12
|
+
health?: "up" | "degraded" | "down" | "unknown";
|
|
13
|
+
}
|
|
14
|
+
/** Live credit balance for the signed-in user, as returned by /api/sso/balance. */
|
|
15
|
+
export interface DRM3Balance {
|
|
16
|
+
available: number;
|
|
17
|
+
credits: number;
|
|
18
|
+
}
|
|
19
|
+
/** Send the user to the DRM3 account hub to sign in, returning here afterward. */
|
|
20
|
+
export declare function signInUrl(returnTo?: string): string;
|
|
21
|
+
/** Sign the user out of DRM3 everywhere, returning here afterward. */
|
|
22
|
+
export declare function signOutUrl(returnTo?: string): string;
|
|
23
|
+
/** Deep-link to the user's DRM3 account hub (credits, keys, apps, activity). */
|
|
24
|
+
export declare function accountUrl(): string;
|
|
25
|
+
/**
|
|
26
|
+
* The canonical DRM3 app catalog with live health, from status.drm3.network.
|
|
27
|
+
* Single source of truth - the same list the account hub and every switcher render.
|
|
28
|
+
*/
|
|
29
|
+
export declare function fetchApps(): Promise<DRM3App[]>;
|
|
30
|
+
/**
|
|
31
|
+
* The signed-in user's live DRM3 credit balance.
|
|
32
|
+
* Pass the `drm3_sso` token (Bearer) if you have it; otherwise the hub reads the cookie.
|
|
33
|
+
* Returns null when not signed in or the hub is unreachable - never throws.
|
|
34
|
+
*/
|
|
35
|
+
export declare function fetchBalance(ssoToken?: string): Promise<DRM3Balance | null>;
|
|
36
|
+
/** Result of an app-namespaced Data Passport write. Never thrown - inspect `.ok`. */
|
|
37
|
+
export interface PassportWriteResult {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
/** The full stored key on success, e.g. `app.<clientId>.<slug>`. */
|
|
40
|
+
key?: string;
|
|
41
|
+
/** Human-readable reason on failure (HTTP status or the hub's error message). */
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Read the audience (the app's client id) out of a passport token without verifying it.
|
|
46
|
+
* The hub re-verifies on every call - this is only used to namespace the write key locally.
|
|
47
|
+
*/
|
|
48
|
+
export declare function passportAudience(passportToken: string): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Write one field into the user's Data Passport, in YOUR app's namespace.
|
|
51
|
+
*
|
|
52
|
+
* The app holds an audience-bound passport token (scope `app:write`, granted at the hub's consent
|
|
53
|
+
* flow). Pass a bare `slug` ([a-z0-9_], <=40 chars) and it is namespaced automatically to
|
|
54
|
+
* `app.<yourClientId>.<slug>` (the client id is read from the token); pass an already-qualified
|
|
55
|
+
* `app.<id>.<slug>` key and it is sent as-is. `value` is any JSON-serialisable value
|
|
56
|
+
* (<=4KB stringified). Never throws - inspect `.ok`/`.error`.
|
|
57
|
+
*
|
|
58
|
+
* await appWrite(token, "favorite_topic", "ai-policy");
|
|
59
|
+
*/
|
|
60
|
+
export declare function appWrite(passportToken: string, slug: string, value: unknown): Promise<PassportWriteResult>;
|
|
61
|
+
export { verifySsoToken, verifyActiveSsoToken, mintSsoToken, readSsoCookie, createRevocationGate, type SsoClaims, type RevocationGate, type RevocationFeed, } from "./sso/index.js";
|
|
62
|
+
/** The hub's revocation feed URL - pass to createRevocationGate(). */
|
|
63
|
+
export declare const REVOCATIONS_URL = "https://drm3.network/api/sso/revocations";
|
|
64
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,WAAW,yBAAyB,CAAC;AAClD,eAAO,MAAM,UAAU,gCAAgC,CAAC;AAExD,6FAA6F;AAC7F,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,MAAM,CAAC,EAAE,IAAI,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;CACjD;AAED,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,QAAQ,GAAE,MAAqB,GAAG,MAAM,CAEjE;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,QAAQ,GAAE,MAAqB,GAAG,MAAM,CAElE;AAED,gFAAgF;AAChF,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED;;;GAGG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CASpD;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAajF;AAED,qFAAqF;AACrF,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAarE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,QAAQ,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAehH;AAUD,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AAExB,sEAAsE;AACtE,eAAO,MAAM,eAAe,6CAAuC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// @drm3/sdk - used to integrate apps with the DRM3 network.
|
|
2
|
+
//
|
|
3
|
+
// Wires DRM3's own apps to shared identity, the user's credit balance, a cross-app switcher, and
|
|
4
|
+
// (server-side) offline SSO-token verification, so each app doesn't re-implement them. Identity is
|
|
5
|
+
// verified offline via @drm3/sdk/sso; everything else is a thin fetch to the DRM3 hubs.
|
|
6
|
+
export const ACCOUNT_HUB = "https://drm3.network";
|
|
7
|
+
export const STATUS_HUB = "https://status.drm3.network";
|
|
8
|
+
/** Send the user to the DRM3 account hub to sign in, returning here afterward. */
|
|
9
|
+
export function signInUrl(returnTo = currentUrl()) {
|
|
10
|
+
return `${ACCOUNT_HUB}/account?login&next=${encodeURIComponent(returnTo)}`;
|
|
11
|
+
}
|
|
12
|
+
/** Sign the user out of DRM3 everywhere, returning here afterward. */
|
|
13
|
+
export function signOutUrl(returnTo = currentUrl()) {
|
|
14
|
+
return `${ACCOUNT_HUB}/account/signout?next=${encodeURIComponent(returnTo)}`;
|
|
15
|
+
}
|
|
16
|
+
/** Deep-link to the user's DRM3 account hub (credits, keys, apps, activity). */
|
|
17
|
+
export function accountUrl() {
|
|
18
|
+
return `${ACCOUNT_HUB}/account`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The canonical DRM3 app catalog with live health, from status.drm3.network.
|
|
22
|
+
* Single source of truth - the same list the account hub and every switcher render.
|
|
23
|
+
*/
|
|
24
|
+
export async function fetchApps() {
|
|
25
|
+
try {
|
|
26
|
+
const r = await fetch(`${STATUS_HUB}/api/apps`, { credentials: "omit" });
|
|
27
|
+
if (!r.ok)
|
|
28
|
+
return [];
|
|
29
|
+
const d = (await r.json());
|
|
30
|
+
return Array.isArray(d.apps) ? d.apps : [];
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The signed-in user's live DRM3 credit balance.
|
|
38
|
+
* Pass the `drm3_sso` token (Bearer) if you have it; otherwise the hub reads the cookie.
|
|
39
|
+
* Returns null when not signed in or the hub is unreachable - never throws.
|
|
40
|
+
*/
|
|
41
|
+
export async function fetchBalance(ssoToken) {
|
|
42
|
+
try {
|
|
43
|
+
const r = await fetch(`${ACCOUNT_HUB}/api/sso/balance`, {
|
|
44
|
+
credentials: "include",
|
|
45
|
+
headers: ssoToken ? { authorization: `Bearer ${ssoToken}` } : undefined,
|
|
46
|
+
});
|
|
47
|
+
if (!r.ok)
|
|
48
|
+
return null;
|
|
49
|
+
const d = (await r.json());
|
|
50
|
+
if (typeof d.available !== "number")
|
|
51
|
+
return null;
|
|
52
|
+
return { available: d.available, credits: d.credits ?? d.available };
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Read the audience (the app's client id) out of a passport token without verifying it.
|
|
60
|
+
* The hub re-verifies on every call - this is only used to namespace the write key locally.
|
|
61
|
+
*/
|
|
62
|
+
export function passportAudience(passportToken) {
|
|
63
|
+
try {
|
|
64
|
+
const payload = passportToken.split(".")[1];
|
|
65
|
+
if (!payload)
|
|
66
|
+
return null;
|
|
67
|
+
const b64 = payload
|
|
68
|
+
.replace(/-/g, "+")
|
|
69
|
+
.replace(/_/g, "/")
|
|
70
|
+
.padEnd(Math.ceil(payload.length / 4) * 4, "=");
|
|
71
|
+
const claims = JSON.parse(atob(b64));
|
|
72
|
+
return typeof claims.aud === "string" ? claims.aud : null;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Write one field into the user's Data Passport, in YOUR app's namespace.
|
|
80
|
+
*
|
|
81
|
+
* The app holds an audience-bound passport token (scope `app:write`, granted at the hub's consent
|
|
82
|
+
* flow). Pass a bare `slug` ([a-z0-9_], <=40 chars) and it is namespaced automatically to
|
|
83
|
+
* `app.<yourClientId>.<slug>` (the client id is read from the token); pass an already-qualified
|
|
84
|
+
* `app.<id>.<slug>` key and it is sent as-is. `value` is any JSON-serialisable value
|
|
85
|
+
* (<=4KB stringified). Never throws - inspect `.ok`/`.error`.
|
|
86
|
+
*
|
|
87
|
+
* await appWrite(token, "favorite_topic", "ai-policy");
|
|
88
|
+
*/
|
|
89
|
+
export async function appWrite(passportToken, slug, value) {
|
|
90
|
+
const aud = passportAudience(passportToken);
|
|
91
|
+
const key = slug.startsWith("app.") || !aud ? slug : `app.${aud}.${slug}`;
|
|
92
|
+
try {
|
|
93
|
+
const r = await fetch(`${ACCOUNT_HUB}/api/passport/app-write`, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { authorization: `Bearer ${passportToken}`, "content-type": "application/json" },
|
|
96
|
+
body: JSON.stringify({ key, value }),
|
|
97
|
+
});
|
|
98
|
+
const d = (await r.json().catch(() => ({})));
|
|
99
|
+
if (!r.ok || d.ok !== true)
|
|
100
|
+
return { ok: false, error: d.error ?? `HTTP ${r.status}` };
|
|
101
|
+
return { ok: true, key: d.key ?? key };
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function currentUrl() {
|
|
108
|
+
return typeof location !== "undefined" ? location.href : ACCOUNT_HUB;
|
|
109
|
+
}
|
|
110
|
+
// Re-export the offline SSO verifier so a server app gets identity from one import. Includes the
|
|
111
|
+
// revocation gate: verifyActiveSsoToken() = verify + not-revoked, so a banned account loses access
|
|
112
|
+
// across every app within the cache TTL (~60s) without a per-request call-home. Point a gate at
|
|
113
|
+
// `${ACCOUNT_HUB}/api/sso/revocations`.
|
|
114
|
+
export { verifySsoToken, verifyActiveSsoToken, mintSsoToken, readSsoCookie, createRevocationGate, } from "./sso/index.js";
|
|
115
|
+
/** The hub's revocation feed URL - pass to createRevocationGate(). */
|
|
116
|
+
export const REVOCATIONS_URL = `${ACCOUNT_HUB}/api/sso/revocations`;
|
|
117
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function canonicalize(obj: unknown): string;
|
|
2
|
+
export declare function bytesToHex(b: Uint8Array): string;
|
|
3
|
+
export declare function b64(bytes: Uint8Array): string;
|
|
4
|
+
export declare function unb64(s: string): Uint8Array;
|
|
5
|
+
export declare function verifySig(publicKey: string, signature: string, payload: string): Promise<boolean>;
|
|
6
|
+
export declare function signPayload(privateKey: CryptoKey, payload: string): Promise<string>;
|
|
7
|
+
//# sourceMappingURL=keys-canonical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys-canonical.d.ts","sourceRoot":"","sources":["../src/keys-canonical.ts"],"names":[],"mappings":"AAQA,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAMjD;AASD,wBAAgB,UAAU,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAEhD;AACD,wBAAgB,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAI7C;AACD,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAK3C;AAKD,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOvG;AACD,wBAAsB,WAAW,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGzF"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// DRM3 partner signing keys - byte-for-byte canonicalization plus the low-level
|
|
2
|
+
// Ed25519 / hex / base64 helpers shared by ./keys-generate and ./keys-verify.
|
|
3
|
+
// MOVED verbatim from ./keys: do NOT change any byte order, constant, or check.
|
|
4
|
+
//
|
|
5
|
+
// Framework-agnostic, zero deps: stdlib WebCrypto (Ed25519) only.
|
|
6
|
+
// ── canonicalization (byte-for-byte the provenance discipline: recursively sorted keys,
|
|
7
|
+
// compact separators, UTF-8) - deliberately duplicated, pinned by test ──
|
|
8
|
+
export function canonicalize(obj) {
|
|
9
|
+
if (obj === null || typeof obj !== "object")
|
|
10
|
+
return JSON.stringify(obj);
|
|
11
|
+
if (Array.isArray(obj))
|
|
12
|
+
return `[${obj.map(canonicalize).join(",")}]`;
|
|
13
|
+
const keys = Object.keys(obj).sort();
|
|
14
|
+
const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalize(obj[k])}`);
|
|
15
|
+
return `{${parts.join(",")}}`;
|
|
16
|
+
}
|
|
17
|
+
const enc = new TextEncoder();
|
|
18
|
+
function hexToBytes(hex) {
|
|
19
|
+
const clean = hex.replace(/^ed25519:/, "");
|
|
20
|
+
const out = new Uint8Array(clean.length / 2);
|
|
21
|
+
for (let i = 0; i < out.length; i++)
|
|
22
|
+
out[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
export function bytesToHex(b) {
|
|
26
|
+
return [...b].map((x) => x.toString(16).padStart(2, "0")).join("");
|
|
27
|
+
}
|
|
28
|
+
export function b64(bytes) {
|
|
29
|
+
let s = "";
|
|
30
|
+
for (const x of bytes)
|
|
31
|
+
s += String.fromCharCode(x);
|
|
32
|
+
return btoa(s);
|
|
33
|
+
}
|
|
34
|
+
export function unb64(s) {
|
|
35
|
+
const raw = atob(s);
|
|
36
|
+
const out = new Uint8Array(raw.length);
|
|
37
|
+
for (let i = 0; i < raw.length; i++)
|
|
38
|
+
out[i] = raw.charCodeAt(i);
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
async function importVerifyKey(publicKey) {
|
|
42
|
+
return crypto.subtle.importKey("raw", hexToBytes(publicKey), { name: "Ed25519" }, false, ["verify"]);
|
|
43
|
+
}
|
|
44
|
+
export async function verifySig(publicKey, signature, payload) {
|
|
45
|
+
try {
|
|
46
|
+
const key = await importVerifyKey(publicKey);
|
|
47
|
+
return await crypto.subtle.verify("Ed25519", key, hexToBytes(signature), enc.encode(payload));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function signPayload(privateKey, payload) {
|
|
54
|
+
const sig = new Uint8Array(await crypto.subtle.sign("Ed25519", privateKey, enc.encode(payload)));
|
|
55
|
+
return `ed25519:${bytesToHex(sig)}`;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=keys-canonical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys-canonical.js","sourceRoot":"","sources":["../src/keys-canonical.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** A locally generated Ed25519 signing key. The private key exists ONLY here. */
|
|
2
|
+
export interface GeneratedSigningKey {
|
|
3
|
+
/** Public key, `ed25519:<64 hex>` - the only part DRM3 ever receives. */
|
|
4
|
+
publicKey: string;
|
|
5
|
+
/** Private key, PKCS8 base64 - store it yourself (env var / secret manager), never send it. */
|
|
6
|
+
privateKeyPkcs8: string;
|
|
7
|
+
/** The live WebCrypto pair, usable for signing in-process without re-import. */
|
|
8
|
+
keyPair: CryptoKeyPair;
|
|
9
|
+
}
|
|
10
|
+
/** A certificate signing request (`drm3-csr/1`) - self-signed proof-of-possession. */
|
|
11
|
+
export interface Csr {
|
|
12
|
+
schema: "drm3-csr/1";
|
|
13
|
+
/** The subject name being requested (e.g. "acme" / "truth-foundry"). */
|
|
14
|
+
subject: string;
|
|
15
|
+
/** The public key to certify, `ed25519:<hex>`. */
|
|
16
|
+
subject_key: string;
|
|
17
|
+
/** What the key will sign (default: receipts). */
|
|
18
|
+
purposes: string[];
|
|
19
|
+
requested_valid_days: number;
|
|
20
|
+
/** Random nonce so a CSR can't be replayed. */
|
|
21
|
+
nonce: string;
|
|
22
|
+
signed_at: string;
|
|
23
|
+
/** Self-signature by subject_key over canonicalize(this object minus signature). */
|
|
24
|
+
signature: string;
|
|
25
|
+
}
|
|
26
|
+
/** Generate an Ed25519 signing keypair locally. DRM3 never sees the private half. */
|
|
27
|
+
export declare function generateSigningKey(): Promise<GeneratedSigningKey>;
|
|
28
|
+
/** Re-import a stored PKCS8-base64 private key (from generateSigningKey) for signing. */
|
|
29
|
+
export declare function importPrivateKeyPkcs8(privateKeyPkcs8: string): Promise<CryptoKey>;
|
|
30
|
+
/** Build and self-sign a `drm3-csr/1`. The self-signature proves you hold the private key. */
|
|
31
|
+
export declare function buildCSR(opts: {
|
|
32
|
+
subject: string;
|
|
33
|
+
keyPair: CryptoKeyPair;
|
|
34
|
+
purposes?: string[];
|
|
35
|
+
requestedValidDays?: number;
|
|
36
|
+
}): Promise<Csr>;
|
|
37
|
+
/** Verify a CSR's self-signature (the CA-side proof-of-possession check). */
|
|
38
|
+
export declare function verifyCsr(csr: Csr): Promise<boolean>;
|
|
39
|
+
//# sourceMappingURL=keys-generate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys-generate.d.ts","sourceRoot":"","sources":["../src/keys-generate.ts"],"names":[],"mappings":"AAKA,iFAAiF;AACjF,MAAM,WAAW,mBAAmB;IAClC,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,+FAA+F;IAC/F,eAAe,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,sFAAsF;AACtF,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,YAAY,CAAC;IACrB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qFAAqF;AACrF,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAKvE;AAED,yFAAyF;AACzF,wBAAsB,qBAAqB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAEvF;AAED,8FAA8F;AAC9F,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,GAAG,OAAO,CAAC,GAAG,CAAC,CAcf;AAED,6EAA6E;AAC7E,wBAAsB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAI1D"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// DRM3 partner signing keys - local Ed25519 keygen + CSR (drm3-csr/1) build & verify.
|
|
2
|
+
// The partner generates the key and keeps the private half; DRM3 only ever sees the
|
|
3
|
+
// public key. MOVED verbatim from ./keys: do NOT change any byte order or check.
|
|
4
|
+
import { b64, bytesToHex, canonicalize, signPayload, unb64, verifySig } from "./keys-canonical.js";
|
|
5
|
+
/** Generate an Ed25519 signing keypair locally. DRM3 never sees the private half. */
|
|
6
|
+
export async function generateSigningKey() {
|
|
7
|
+
const keyPair = (await crypto.subtle.generateKey({ name: "Ed25519" }, true, ["sign", "verify"]));
|
|
8
|
+
const rawPub = new Uint8Array(await crypto.subtle.exportKey("raw", keyPair.publicKey));
|
|
9
|
+
const pkcs8 = new Uint8Array(await crypto.subtle.exportKey("pkcs8", keyPair.privateKey));
|
|
10
|
+
return { publicKey: `ed25519:${bytesToHex(rawPub)}`, privateKeyPkcs8: b64(pkcs8), keyPair };
|
|
11
|
+
}
|
|
12
|
+
/** Re-import a stored PKCS8-base64 private key (from generateSigningKey) for signing. */
|
|
13
|
+
export async function importPrivateKeyPkcs8(privateKeyPkcs8) {
|
|
14
|
+
return crypto.subtle.importKey("pkcs8", unb64(privateKeyPkcs8), { name: "Ed25519" }, false, ["sign"]);
|
|
15
|
+
}
|
|
16
|
+
/** Build and self-sign a `drm3-csr/1`. The self-signature proves you hold the private key. */
|
|
17
|
+
export async function buildCSR(opts) {
|
|
18
|
+
const rawPub = new Uint8Array(await crypto.subtle.exportKey("raw", opts.keyPair.publicKey));
|
|
19
|
+
const nonce = bytesToHex(crypto.getRandomValues(new Uint8Array(16)));
|
|
20
|
+
const unsigned = {
|
|
21
|
+
schema: "drm3-csr/1",
|
|
22
|
+
subject: opts.subject,
|
|
23
|
+
subject_key: `ed25519:${bytesToHex(rawPub)}`,
|
|
24
|
+
purposes: opts.purposes ?? ["receipt.sign"],
|
|
25
|
+
requested_valid_days: opts.requestedValidDays ?? 365,
|
|
26
|
+
nonce,
|
|
27
|
+
signed_at: new Date().toISOString(),
|
|
28
|
+
};
|
|
29
|
+
const signature = await signPayload(opts.keyPair.privateKey, canonicalize(unsigned));
|
|
30
|
+
return { ...unsigned, signature };
|
|
31
|
+
}
|
|
32
|
+
/** Verify a CSR's self-signature (the CA-side proof-of-possession check). */
|
|
33
|
+
export async function verifyCsr(csr) {
|
|
34
|
+
const { signature, ...unsigned } = csr;
|
|
35
|
+
if (!signature || !csr.subject_key || csr.schema !== "drm3-csr/1")
|
|
36
|
+
return false;
|
|
37
|
+
return verifySig(csr.subject_key, signature, canonicalize(unsigned));
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=keys-generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys-generate.js","sourceRoot":"","sources":["../src/keys-generate.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,oFAAoF;AACpF,iFAAiF;AACjF,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AA6BnG,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,OAAO,GAAG,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAkB,CAAC;IAClH,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IACvF,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IACzF,OAAO,EAAE,SAAS,EAAE,WAAW,UAAU,CAAC,MAAM,CAAC,EAAE,EAAE,eAAe,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;AAC9F,CAAC;AAED,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,eAAuB;IACjE,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,eAAe,CAAiB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;AACxH,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAK9B;IACC,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5F,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACrE,MAAM,QAAQ,GAAG;QACf,MAAM,EAAE,YAAqB;QAC7B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,WAAW,EAAE,WAAW,UAAU,CAAC,MAAM,CAAC,EAAE;QAC5C,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,CAAC,cAAc,CAAC;QAC3C,oBAAoB,EAAE,IAAI,CAAC,kBAAkB,IAAI,GAAG;QACpD,KAAK;QACL,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IACF,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrF,OAAO,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,CAAC;AACpC,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAQ;IACtC,MAAM,EAAE,SAAS,EAAE,GAAG,QAAQ,EAAE,GAAG,GAAG,CAAC;IACvC,IAAI,CAAC,SAAS,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,MAAM,KAAK,YAAY;QAAE,OAAO,KAAK,CAAC;IAChF,OAAO,SAAS,CAAC,GAAG,CAAC,WAAW,EAAE,SAAS,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;AACvE,CAAC"}
|