@bkazemi/verily 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -10
- package/dist/browser/badge.d.ts +4 -1
- package/dist/browser/badge.js +7 -4
- package/dist/browser/connect-dialog.js +10 -6
- package/dist/browser/evidence-dialog.d.ts +17 -3
- package/dist/browser/evidence-dialog.js +279 -29
- package/dist/browser/index.d.ts +7 -1
- package/dist/browser/index.js +52 -64
- package/dist/core/index.d.ts +95 -4
- package/dist/core/index.js +59 -5
- package/dist/core/routes.d.ts +57 -0
- package/dist/core/routes.js +52 -0
- package/dist/server/domain.d.ts +76 -10
- package/dist/server/domain.js +140 -51
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +148 -34
- package/dist/server/openpgp.d.ts +4 -0
- package/dist/server/openpgp.js +10 -1
- package/dist/server/pgp.d.ts +2 -0
- package/dist/server/pgp.js +1 -1
- package/dist/server/service.d.ts +87 -2
- package/dist/server/service.js +248 -25
- package/dist/server/style.d.ts +1 -1
- package/dist/server/style.js +40 -0
- package/dist/site/index.js +2 -2
- package/dist/verily.js +371 -95
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/verily.js
CHANGED
|
@@ -9574,8 +9574,12 @@ var Verily = (() => {
|
|
|
9574
9574
|
var browser_exports = {};
|
|
9575
9575
|
__export(browser_exports, {
|
|
9576
9576
|
Refused: () => Refused,
|
|
9577
|
+
accountsOf: () => accountsOf,
|
|
9577
9578
|
attestationLabel: () => attestationLabel,
|
|
9579
|
+
dialogPath: () => dialogPath,
|
|
9580
|
+
dialogRoute: () => dialogRoute,
|
|
9578
9581
|
externalId: () => externalId,
|
|
9582
|
+
externalIdGroups: () => externalIdGroups,
|
|
9579
9583
|
externalLink: () => externalLink,
|
|
9580
9584
|
externalName: () => externalName,
|
|
9581
9585
|
fresh: () => fresh,
|
|
@@ -9586,9 +9590,13 @@ var Verily = (() => {
|
|
|
9586
9590
|
isRedirectProvider: () => isRedirectProvider,
|
|
9587
9591
|
lastProved: () => lastProved,
|
|
9588
9592
|
localSide: () => localSide,
|
|
9593
|
+
place: () => place,
|
|
9589
9594
|
presentConnections: () => presentConnections,
|
|
9590
9595
|
proofTitle: () => proofTitle,
|
|
9591
9596
|
providerMethod: () => providerMethod,
|
|
9597
|
+
routeOf: () => routeOf,
|
|
9598
|
+
routePath: () => routePath,
|
|
9599
|
+
routes: () => routes,
|
|
9592
9600
|
signedBy: () => signedBy,
|
|
9593
9601
|
status: () => status,
|
|
9594
9602
|
statusLabel: () => statusLabel,
|
|
@@ -9661,6 +9669,50 @@ var Verily = (() => {
|
|
|
9661
9669
|
return void 0;
|
|
9662
9670
|
}
|
|
9663
9671
|
|
|
9672
|
+
// src/core/routes.ts
|
|
9673
|
+
var routes = {
|
|
9674
|
+
methods: { method: "GET", path: "/methods" },
|
|
9675
|
+
start: { method: "POST", path: "/sessions" },
|
|
9676
|
+
flow: { method: "GET", path: "/flows/:id", format: "json" },
|
|
9677
|
+
submit: { method: "POST", path: "/flows/:id/submit" },
|
|
9678
|
+
approve: { method: "POST", path: "/flows/:id/approve" },
|
|
9679
|
+
disconnect: { method: "POST", path: "/connections/:id/disconnect", manages: true },
|
|
9680
|
+
mark: { method: "POST", path: "/connections/:id/mark", manages: true },
|
|
9681
|
+
share: { method: "POST", path: "/connections/:id/share", manages: true, dialog: false },
|
|
9682
|
+
shareRevoke: {
|
|
9683
|
+
method: "POST",
|
|
9684
|
+
path: "/connections/:id/share-revoke",
|
|
9685
|
+
manages: true,
|
|
9686
|
+
dialog: false
|
|
9687
|
+
}
|
|
9688
|
+
};
|
|
9689
|
+
var names = Object.keys(routes);
|
|
9690
|
+
function matches(route, path) {
|
|
9691
|
+
const want = route.path.split("/");
|
|
9692
|
+
const got = path.split("/");
|
|
9693
|
+
return want.length === got.length && want.every((part, at2) => part === ":id" ? got[at2] !== "" : part === got[at2]);
|
|
9694
|
+
}
|
|
9695
|
+
function routePath(name, id) {
|
|
9696
|
+
const route = routes[name];
|
|
9697
|
+
const path = route.path.replace(":id", encodeURIComponent(id ?? ""));
|
|
9698
|
+
return route.format ? `${path}?format=${route.format}` : path;
|
|
9699
|
+
}
|
|
9700
|
+
function routeOf(method, path) {
|
|
9701
|
+
return names.find((name) => routes[name].method === method && matches(routes[name], path));
|
|
9702
|
+
}
|
|
9703
|
+
function dialogRoute(method, path, query) {
|
|
9704
|
+
const name = routeOf(method, path);
|
|
9705
|
+
const route = name && routes[name];
|
|
9706
|
+
if (!route || route.dialog === false) return void 0;
|
|
9707
|
+
return !route.format || query.get("format") === route.format ? name : void 0;
|
|
9708
|
+
}
|
|
9709
|
+
function dialogPath(path) {
|
|
9710
|
+
return names.some((name) => {
|
|
9711
|
+
const route = routes[name];
|
|
9712
|
+
return route.dialog !== false && matches(route, path);
|
|
9713
|
+
});
|
|
9714
|
+
}
|
|
9715
|
+
|
|
9664
9716
|
// src/core/index.ts
|
|
9665
9717
|
var Refused = class extends Error {
|
|
9666
9718
|
};
|
|
@@ -9698,6 +9750,29 @@ var Verily = (() => {
|
|
|
9698
9750
|
if (evidence.status === "expired" || evidence.expiresAt <= now) return "Expired";
|
|
9699
9751
|
return evidence.status === "unconfirmed" ? "Unconfirmed" : "Verified";
|
|
9700
9752
|
}
|
|
9753
|
+
function place(account) {
|
|
9754
|
+
if (account.status === "retired") return 3;
|
|
9755
|
+
return account.mark === "preferred" ? 0 : account.mark === "unused" ? 2 : 1;
|
|
9756
|
+
}
|
|
9757
|
+
function accountsOf(records, now) {
|
|
9758
|
+
const when = (e2) => e2.connectedAt ?? e2.approvedAt;
|
|
9759
|
+
const byAccount = /* @__PURE__ */ new Map();
|
|
9760
|
+
for (const record2 of records) {
|
|
9761
|
+
const key2 = JSON.stringify([record2.provider, record2.external.id]);
|
|
9762
|
+
byAccount.set(key2, [...byAccount.get(key2) ?? [], record2]);
|
|
9763
|
+
}
|
|
9764
|
+
return [...byAccount.values()].map((unsorted) => {
|
|
9765
|
+
const held = [...unsorted].sort((a2, b2) => when(a2) - when(b2));
|
|
9766
|
+
const verified = held.filter((e2) => e2.status === "verified" && e2.expiresAt > now);
|
|
9767
|
+
const lead = verified[0] ?? held.reduce((a2, b2) => b2.approvedAt > a2.approvedAt ? b2 : a2);
|
|
9768
|
+
const external = [...lead.attestations.external];
|
|
9769
|
+
for (const other of verified.slice(1))
|
|
9770
|
+
for (const attestation of other.attestations.external)
|
|
9771
|
+
if (!external.some((shown3) => shown3.method === attestation.method))
|
|
9772
|
+
external.push(attestation);
|
|
9773
|
+
return { lead, held, verified, external, connectedAt: when(held[0]) };
|
|
9774
|
+
}).sort((a2, b2) => place(a2.lead) - place(b2.lead) || a2.connectedAt - b2.connectedAt);
|
|
9775
|
+
}
|
|
9701
9776
|
function localSide(local, fallback) {
|
|
9702
9777
|
const siteName = local.siteName ?? fallback;
|
|
9703
9778
|
if (local.kind === "site") return { heading: "Website", value: siteName };
|
|
@@ -9710,14 +9785,16 @@ var Verily = (() => {
|
|
|
9710
9785
|
function externalName(external) {
|
|
9711
9786
|
return external.kind === void 0 || external.kind === "account" ? `@${external.handle.replace(/^@/, "")}` : external.handle;
|
|
9712
9787
|
}
|
|
9713
|
-
function attestationLabel(method,
|
|
9788
|
+
function attestationLabel(method, names2, proof = {}) {
|
|
9789
|
+
const dnssec = proof.dnssec ? ", validated by DNSSEC" : "";
|
|
9714
9790
|
return {
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9791
|
+
// A site's word carries a proof only where its own DNS was read naming this verifier.
|
|
9792
|
+
declared: proof.artifactUrl ? `Stated by ${names2.site}, whose DNS names this verifier${dnssec}` : `Stated by ${names2.site}`,
|
|
9793
|
+
oauth: `Signed in with ${names2.provider}`,
|
|
9794
|
+
gist: `Published a proof on ${names2.provider}`,
|
|
9795
|
+
backlink: `Linked back to ${names2.site}`,
|
|
9796
|
+
dns: `Published a proof in its DNS${dnssec}`,
|
|
9797
|
+
wellknown: "Published a proof in a file it serves",
|
|
9721
9798
|
signature: "Proved with a signature",
|
|
9722
9799
|
code: "Entered a code sent to this address"
|
|
9723
9800
|
}[method];
|
|
@@ -9725,6 +9802,9 @@ var Verily = (() => {
|
|
|
9725
9802
|
function externalId(external) {
|
|
9726
9803
|
return external.kind === "mailbox" || external.kind === "domain" ? void 0 : external.id;
|
|
9727
9804
|
}
|
|
9805
|
+
function externalIdGroups(external) {
|
|
9806
|
+
return external.kind === "key" ? external.id.match(/.{1,4}/g) ?? void 0 : void 0;
|
|
9807
|
+
}
|
|
9728
9808
|
function externalLink(external) {
|
|
9729
9809
|
return external.kind === "mailbox" ? void 0 : external.profileUrl;
|
|
9730
9810
|
}
|
|
@@ -10016,11 +10096,12 @@ var Verily = (() => {
|
|
|
10016
10096
|
function badgeShown(element) {
|
|
10017
10097
|
return intact(element) && shown.has(element);
|
|
10018
10098
|
}
|
|
10019
|
-
function renderKey(evidence, current, label, more, linked, rows, peek) {
|
|
10099
|
+
function renderKey(evidence, current, label, more, linked, rows, peek, href) {
|
|
10020
10100
|
return JSON.stringify([
|
|
10021
10101
|
more,
|
|
10022
10102
|
linked,
|
|
10023
10103
|
peek,
|
|
10104
|
+
href,
|
|
10024
10105
|
rows.map((row) => [row.provider, row.providerName, externalName(row.external)]),
|
|
10025
10106
|
evidence.provider,
|
|
10026
10107
|
evidence.providerName,
|
|
@@ -10071,14 +10152,15 @@ var Verily = (() => {
|
|
|
10071
10152
|
more = 0,
|
|
10072
10153
|
linked = true,
|
|
10073
10154
|
rows = [],
|
|
10074
|
-
peek = false
|
|
10155
|
+
peek = false,
|
|
10156
|
+
href = evidence.evidenceUrl
|
|
10075
10157
|
} = {}) {
|
|
10076
10158
|
const provider = evidence.providerName;
|
|
10077
10159
|
const current = evidence.status === "verified" && evidence.expiresAt > Date.now();
|
|
10078
10160
|
const retired = evidence.status === "retired";
|
|
10079
10161
|
const state = current ? "verified" : retired ? "retired" : evidence.status === "revoked" ? "revoked" : "expired";
|
|
10080
10162
|
const label = statusLabel(evidence, Date.now());
|
|
10081
|
-
const key2 = renderKey(evidence, current, label, more, linked, rows, peek);
|
|
10163
|
+
const key2 = renderKey(evidence, current, label, more, linked, rows, peek, href);
|
|
10082
10164
|
if (intact(element) && shown.get(element) === key2) return null;
|
|
10083
10165
|
const handle = externalName(evidence.external);
|
|
10084
10166
|
const { pill: badge, mark: mark2 } = frame(element, state, !linked);
|
|
@@ -10087,7 +10169,7 @@ var Verily = (() => {
|
|
|
10087
10169
|
const others = `${named}${unnamed ? ` and ${unnamed} more` : ""}`;
|
|
10088
10170
|
paintMark(mark2, current ? "current" : retired ? "pending" : "inactive");
|
|
10089
10171
|
if (linked) {
|
|
10090
|
-
badge.href =
|
|
10172
|
+
badge.href = href;
|
|
10091
10173
|
badge.rel = "noreferrer";
|
|
10092
10174
|
} else badge.type = "button";
|
|
10093
10175
|
badge.setAttribute(
|
|
@@ -10162,7 +10244,7 @@ var Verily = (() => {
|
|
|
10162
10244
|
} catch {
|
|
10163
10245
|
return;
|
|
10164
10246
|
}
|
|
10165
|
-
|
|
10247
|
+
place2(panel, pill, listed, unnamed);
|
|
10166
10248
|
globalThis.addEventListener?.("scroll", hide, true);
|
|
10167
10249
|
};
|
|
10168
10250
|
panel.setAttribute("popover", "manual");
|
|
@@ -10184,7 +10266,7 @@ var Verily = (() => {
|
|
|
10184
10266
|
if (event.key === "Escape") hide();
|
|
10185
10267
|
};
|
|
10186
10268
|
}
|
|
10187
|
-
function
|
|
10269
|
+
function place2(panel, pill, listed, unnamed) {
|
|
10188
10270
|
const gap = 6;
|
|
10189
10271
|
const edge = 8;
|
|
10190
10272
|
const at2 = pill.getBoundingClientRect();
|
|
@@ -10434,7 +10516,7 @@ ${claims(evidence)}`;
|
|
|
10434
10516
|
}
|
|
10435
10517
|
|
|
10436
10518
|
// src/version.ts
|
|
10437
|
-
var version = "v0.
|
|
10519
|
+
var version = "v0.6.0";
|
|
10438
10520
|
|
|
10439
10521
|
// src/browser/evidence-dialog.ts
|
|
10440
10522
|
function stampLink() {
|
|
@@ -10451,8 +10533,16 @@ ${claims(evidence)}`;
|
|
|
10451
10533
|
h2 { margin: 0; font-size: 18px; font-weight: 650; letter-spacing: -.3px; }
|
|
10452
10534
|
button { width: 32px; height: 32px; border: 1px solid #dce2de; border-radius: 8px; background: #fff; color: #52645a; font: 20px system-ui, sans-serif; cursor: pointer; }
|
|
10453
10535
|
button:hover { background: #f2f5f1; }
|
|
10536
|
+
.icon { display: grid; place-items: center; padding: 0; }
|
|
10537
|
+
.icon svg { width: 16px; height: 16px; }
|
|
10538
|
+
.title { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
10539
|
+
.all { display: grid; place-items: center; width: 28px; height: 28px; border-radius: 8px; color: #52645a; }
|
|
10540
|
+
.all:hover { background: #f2f5f1; }
|
|
10541
|
+
.all svg { width: 16px; height: 16px; }
|
|
10454
10542
|
a { color: #245f43; text-underline-offset: 3px; overflow-wrap: anywhere; }
|
|
10455
|
-
a:focus-visible, button:focus-visible { outline: 2px solid #357ce5; outline-offset: 3px; }
|
|
10543
|
+
a:focus-visible, button:focus-visible, summary:focus-visible { outline: 2px solid #357ce5; outline-offset: 3px; }
|
|
10544
|
+
/* An element given its own display would otherwise go on showing while hidden. */
|
|
10545
|
+
[hidden] { display: none !important; }
|
|
10456
10546
|
.summary { display: flex; align-items: center; gap: 8px; margin-top: 16px; }
|
|
10457
10547
|
.mark { width: 36px; height: 36px; flex-shrink: 0; }
|
|
10458
10548
|
.provider { width: 14px; height: 14px; }
|
|
@@ -10464,7 +10554,7 @@ ${claims(evidence)}`;
|
|
|
10464
10554
|
.spinner { width: 14px; height: 14px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: spin .7s linear infinite; }
|
|
10465
10555
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
10466
10556
|
@media (prefers-reduced-motion: reduce) { .spinner { animation: none; } }
|
|
10467
|
-
.caution { display: inline-block; margin-left: 4px; color: #a15c00; cursor: help; vertical-align:
|
|
10557
|
+
.caution { display: inline-block; margin-left: 4px; color: #a15c00; cursor: help; vertical-align: -1.5px; }
|
|
10468
10558
|
.caution svg { display: block; width: 12px; height: 12px; }
|
|
10469
10559
|
.bad { color: #b3261e; font-weight: 650; text-decoration: underline dotted; text-underline-offset: 3px; cursor: help; }
|
|
10470
10560
|
.account { padding: 14px 16px; border: 1px solid #e0e6df; border-radius: 10px; margin-top: 12px; }
|
|
@@ -10473,6 +10563,9 @@ ${claims(evidence)}`;
|
|
|
10473
10563
|
/* The account's own name carries that weight; a link inside a line of prose does not. */
|
|
10474
10564
|
.summary a, .method a, .additional a { font: inherit; }
|
|
10475
10565
|
.reference { margin-top: 2px; }
|
|
10566
|
+
/* A fingerprint in fours: it breaks between groups, and copies as one unbroken word. */
|
|
10567
|
+
.fingerprint { font-family: ui-monospace, monospace; text-wrap: balance; }
|
|
10568
|
+
.fingerprint span { display: inline-block; margin-right: .5ch; }
|
|
10476
10569
|
.method { margin-top: 8px; }
|
|
10477
10570
|
/* In the corner the heading leaves empty, apart from everything that was checked. */
|
|
10478
10571
|
.listing { position: absolute; top: 12px; right: 14px; padding: 1px 8px; border: 1px solid #e0e6df; border-radius: 999px; color: #6b786f; font-size: 11px; font-weight: 550; }
|
|
@@ -10482,6 +10575,15 @@ ${claims(evidence)}`;
|
|
|
10482
10575
|
.account.retired > a, .account.retired > strong { color: #6b786f; }
|
|
10483
10576
|
.account.retired .mark { opacity: .45; }
|
|
10484
10577
|
.group { margin: 20px 0 0; color: #6b786f; font-size: 11px; font-weight: 550; }
|
|
10578
|
+
summary.group { display: flex; align-items: center; gap: 4px; width: fit-content; border-radius: 4px; list-style: none; cursor: pointer; }
|
|
10579
|
+
summary::-webkit-details-marker { display: none; }
|
|
10580
|
+
.fold { display: flex; align-items: center; gap: 4px; width: auto; height: auto; padding: 2px 8px 2px 4px; margin-left: -4px; border: 0; background: none; font: 550 12px/1.6 system-ui, sans-serif; }
|
|
10581
|
+
.fold svg, summary svg { width: 12px; height: 12px; }
|
|
10582
|
+
.fold[aria-expanded="true"] svg, details[open] > summary svg { transform: rotate(90deg); }
|
|
10583
|
+
@media (prefers-reduced-motion: no-preference) { .fold svg, summary svg { transition: transform .15s; } }
|
|
10584
|
+
/* The fold and what the holder may do share a line, until a question needs the whole of it. */
|
|
10585
|
+
.bar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
|
10586
|
+
.account .bar .row { margin: 0 0 0 auto; }
|
|
10485
10587
|
.additional { margin-top: 2px; padding-left: 12px; }
|
|
10486
10588
|
.joiner { display: block; width: 20px; height: 20px; margin: 8px auto -4px; color: #90a096; }
|
|
10487
10589
|
dl { margin: 16px 0 0; padding-top: 12px; border-top: 1px solid #e5e9e3; display: grid; grid-template-columns: auto 1fr; gap: 5px 16px; font-size: 11px; }
|
|
@@ -10496,6 +10598,14 @@ ${claims(evidence)}`;
|
|
|
10496
10598
|
.row { display: flex; justify-content: end; gap: 8px; margin-top: 16px; }
|
|
10497
10599
|
.account .row { align-items: center; justify-content: start; flex-wrap: wrap; margin-top: 12px; }
|
|
10498
10600
|
.account .action { padding: 5px 10px; font-size: 12px; }
|
|
10601
|
+
.account .action.dots { display: grid; place-items: center; align-self: stretch; width: 30px; padding: 0; }
|
|
10602
|
+
.dots svg { width: 16px; height: 16px; }
|
|
10603
|
+
/* Placed under its button each time it opens, above the dialog and clipped by none of it. */
|
|
10604
|
+
.menu { position: fixed; inset: auto; margin: 0; min-width: 170px; padding: 6px; border: 1px solid #dce2de; border-radius: 10px; background: #fff; color: #23312b; box-shadow: 0 12px 40px #10201930; }
|
|
10605
|
+
.menu:popover-open { display: grid; }
|
|
10606
|
+
.account .menu .action { border: 0; border-radius: 6px; padding: 6px 10px; font-weight: 550; }
|
|
10607
|
+
.menu .danger { color: #b3261e; }
|
|
10608
|
+
.menu hr { width: 100%; margin: 4px 0; border: 0; border-top: 1px solid #e5e9e3; }
|
|
10499
10609
|
footer { display: flex; align-items: center; justify-content: start; gap: 6px; margin: 20px 0 -8px; color: #9aa9a0; font-size: 11px; }
|
|
10500
10610
|
footer a { display: flex; color: inherit; }
|
|
10501
10611
|
.logo { display: block; height: 13px; }
|
|
@@ -10513,6 +10623,58 @@ ${claims(evidence)}`;
|
|
|
10513
10623
|
anchor.target = "_blank";
|
|
10514
10624
|
return anchor;
|
|
10515
10625
|
}
|
|
10626
|
+
function subjectUrl(e2) {
|
|
10627
|
+
const url = new URL(e2.evidenceUrl);
|
|
10628
|
+
const path = url.pathname.replace(/\/connections\/[^/]+$/, "/published");
|
|
10629
|
+
if (path === url.pathname) return e2.evidenceUrl;
|
|
10630
|
+
url.pathname = path;
|
|
10631
|
+
url.search = `site=${encodeURIComponent(e2.siteName)}&reference=${encodeURIComponent(e2.local.reference)}`;
|
|
10632
|
+
return url.href;
|
|
10633
|
+
}
|
|
10634
|
+
function awayMark() {
|
|
10635
|
+
const namespace2 = "http://www.w3.org/2000/svg";
|
|
10636
|
+
const svg = document.createElementNS(namespace2, "svg");
|
|
10637
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
10638
|
+
svg.setAttribute("fill", "none");
|
|
10639
|
+
svg.setAttribute("aria-hidden", "true");
|
|
10640
|
+
svg.setAttribute("focusable", "false");
|
|
10641
|
+
for (const d2 of [
|
|
10642
|
+
"M14 5h5v5",
|
|
10643
|
+
"M19 5l-8 8",
|
|
10644
|
+
"M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"
|
|
10645
|
+
]) {
|
|
10646
|
+
const path = document.createElementNS(namespace2, "path");
|
|
10647
|
+
path.setAttribute("d", d2);
|
|
10648
|
+
path.setAttribute("stroke", "currentColor");
|
|
10649
|
+
path.setAttribute("stroke-width", "2");
|
|
10650
|
+
path.setAttribute("stroke-linecap", "round");
|
|
10651
|
+
path.setAttribute("stroke-linejoin", "round");
|
|
10652
|
+
svg.append(path);
|
|
10653
|
+
}
|
|
10654
|
+
return svg;
|
|
10655
|
+
}
|
|
10656
|
+
function strokes(box, width, paths) {
|
|
10657
|
+
const namespace2 = "http://www.w3.org/2000/svg";
|
|
10658
|
+
const svg = document.createElementNS(namespace2, "svg");
|
|
10659
|
+
svg.setAttribute("viewBox", `0 0 ${box} ${box}`);
|
|
10660
|
+
svg.setAttribute("fill", "none");
|
|
10661
|
+
svg.setAttribute("aria-hidden", "true");
|
|
10662
|
+
svg.setAttribute("focusable", "false");
|
|
10663
|
+
for (const d2 of paths) {
|
|
10664
|
+
const path = document.createElementNS(namespace2, "path");
|
|
10665
|
+
path.setAttribute("d", d2);
|
|
10666
|
+
path.setAttribute("stroke", "currentColor");
|
|
10667
|
+
path.setAttribute("stroke-width", String(width));
|
|
10668
|
+
path.setAttribute("stroke-linecap", "round");
|
|
10669
|
+
path.setAttribute("stroke-linejoin", "round");
|
|
10670
|
+
svg.append(path);
|
|
10671
|
+
}
|
|
10672
|
+
return svg;
|
|
10673
|
+
}
|
|
10674
|
+
var foldMark = () => strokes(16, 1.8, ["M6 3.5 10.5 8 6 12.5"]);
|
|
10675
|
+
var moreMark = () => strokes(16, 2.4, ["M3 8h.01", "M8 8h.01", "M13 8h.01"]);
|
|
10676
|
+
var closeMark = () => strokes(16, 1.8, ["M4 4l8 8", "M12 4l-8 8"]);
|
|
10677
|
+
var backMark = () => strokes(16, 1.8, ["M10 3.5 5.5 8 10 12.5"]);
|
|
10516
10678
|
function linkMark() {
|
|
10517
10679
|
const namespace2 = "http://www.w3.org/2000/svg";
|
|
10518
10680
|
const svg = document.createElementNS(namespace2, "svg");
|
|
@@ -10535,8 +10697,8 @@ ${claims(evidence)}`;
|
|
|
10535
10697
|
}
|
|
10536
10698
|
return svg;
|
|
10537
10699
|
}
|
|
10538
|
-
function attestationNote(attestation,
|
|
10539
|
-
const label = attestationLabel(attestation.method,
|
|
10700
|
+
function attestationNote(attestation, names2, additional = false) {
|
|
10701
|
+
const label = attestationLabel(attestation.method, names2, attestation);
|
|
10540
10702
|
if (!label) return [];
|
|
10541
10703
|
const note = node("div", additional ? "+ " : "", `muted ${additional ? "additional" : "method"}`);
|
|
10542
10704
|
if (!attestation.artifactUrl) {
|
|
@@ -10558,12 +10720,63 @@ ${claims(evidence)}`;
|
|
|
10558
10720
|
title.append(...heading);
|
|
10559
10721
|
if (value instanceof HTMLAnchorElement) outward(value, url);
|
|
10560
10722
|
card.append(title, value);
|
|
10561
|
-
if (reference) card.append(node("div", reference, "muted reference"));
|
|
10723
|
+
if (typeof reference === "string") card.append(node("div", reference, "muted reference"));
|
|
10724
|
+
else if (reference) {
|
|
10725
|
+
const groups = node("div", "", "muted reference fingerprint");
|
|
10726
|
+
groups.append(...reference.map((group) => node("span", group)));
|
|
10727
|
+
card.append(groups);
|
|
10728
|
+
}
|
|
10562
10729
|
card.append(...extra);
|
|
10563
10730
|
return card;
|
|
10564
10731
|
}
|
|
10565
10732
|
var retiringNote = "Retiring keeps this record as history. It will no longer read as verified, and who can read it cannot be changed afterwards.";
|
|
10566
10733
|
var removable = (evidence) => evidence.links ?? (evidence.status === "revoked" ? [] : [evidence.id]);
|
|
10734
|
+
function actionsMenu(more, choices) {
|
|
10735
|
+
const menu = node("div", "", "menu");
|
|
10736
|
+
if (typeof menu.showPopover !== "function") return void 0;
|
|
10737
|
+
const label = "More actions";
|
|
10738
|
+
more.className = "action dots";
|
|
10739
|
+
more.title = label;
|
|
10740
|
+
more.setAttribute("aria-label", label);
|
|
10741
|
+
more.setAttribute("aria-haspopup", "menu");
|
|
10742
|
+
more.append(moreMark());
|
|
10743
|
+
more.popoverTargetElement = menu;
|
|
10744
|
+
menu.setAttribute("popover", "auto");
|
|
10745
|
+
menu.setAttribute("role", "menu");
|
|
10746
|
+
for (const choice of choices) choice.setAttribute("role", "menuitem");
|
|
10747
|
+
menu.append(...choices.slice(0, -1), node("hr"), choices.at(-1));
|
|
10748
|
+
const shut = () => {
|
|
10749
|
+
try {
|
|
10750
|
+
if (menu.matches(":popover-open")) menu.hidePopover();
|
|
10751
|
+
} catch {
|
|
10752
|
+
}
|
|
10753
|
+
};
|
|
10754
|
+
const gap = 4;
|
|
10755
|
+
const edge = 8;
|
|
10756
|
+
menu.addEventListener("beforetoggle", (event) => {
|
|
10757
|
+
if (event.newState !== "open") return;
|
|
10758
|
+
const at2 = more.getBoundingClientRect();
|
|
10759
|
+
menu.style.top = `${at2.bottom + gap}px`;
|
|
10760
|
+
menu.style.right = `${document.documentElement.clientWidth - at2.right}px`;
|
|
10761
|
+
});
|
|
10762
|
+
menu.addEventListener("toggle", (event) => {
|
|
10763
|
+
if (event.newState !== "open") return;
|
|
10764
|
+
const { height, bottom } = menu.getBoundingClientRect();
|
|
10765
|
+
if (bottom > innerHeight - edge)
|
|
10766
|
+
menu.style.top = `${Math.max(edge, more.getBoundingClientRect().top - gap - height)}px`;
|
|
10767
|
+
menu.closest("dialog")?.addEventListener("scroll", shut, { once: true });
|
|
10768
|
+
choices[0].focus();
|
|
10769
|
+
});
|
|
10770
|
+
menu.addEventListener("click", shut);
|
|
10771
|
+
menu.addEventListener("keydown", (event) => {
|
|
10772
|
+
const step = { ArrowDown: 1, ArrowUp: -1 }[event.key];
|
|
10773
|
+
if (!step) return;
|
|
10774
|
+
event.preventDefault();
|
|
10775
|
+
const at2 = choices.indexOf(event.target);
|
|
10776
|
+
choices[(at2 + step + choices.length) % choices.length].focus();
|
|
10777
|
+
});
|
|
10778
|
+
return menu;
|
|
10779
|
+
}
|
|
10567
10780
|
function manageRow(evidence, manage) {
|
|
10568
10781
|
const row = node("div", "", "row");
|
|
10569
10782
|
const act = (label, danger = false) => {
|
|
@@ -10606,7 +10819,11 @@ ${claims(evidence)}`;
|
|
|
10606
10819
|
retire
|
|
10607
10820
|
);
|
|
10608
10821
|
}
|
|
10609
|
-
|
|
10822
|
+
const said = note ? [node("span", note, "muted")] : [];
|
|
10823
|
+
const more = act("");
|
|
10824
|
+
const menu = listing.length ? actionsMenu(more, [...listing, remove]) : void 0;
|
|
10825
|
+
if (menu) row.replaceChildren(renew, more, menu, ...said);
|
|
10826
|
+
else row.replaceChildren(renew, ...listing, remove, ...said);
|
|
10610
10827
|
};
|
|
10611
10828
|
const retiring = () => {
|
|
10612
10829
|
const cancel = act("Cancel");
|
|
@@ -10649,7 +10866,7 @@ ${claims(evidence)}`;
|
|
|
10649
10866
|
)
|
|
10650
10867
|
);
|
|
10651
10868
|
}
|
|
10652
|
-
function signedMark(evidence, later) {
|
|
10869
|
+
function signedMark(evidence, later, warned) {
|
|
10653
10870
|
const mark2 = node("span");
|
|
10654
10871
|
const link = outward(node("a", "signed"), `${evidence.evidenceUrl}#signed`);
|
|
10655
10872
|
const see = "See the signature (opens in a new tab)";
|
|
@@ -10660,6 +10877,7 @@ ${claims(evidence)}`;
|
|
|
10660
10877
|
warning.setAttribute("role", "alert");
|
|
10661
10878
|
warning.title = `This record does not match the signature ${evidence.verifierName} put on it, so it may have been altered. Do not rely on it.`;
|
|
10662
10879
|
mark2.replaceChildren(document.createTextNode(" \xB7 "), warning);
|
|
10880
|
+
warned();
|
|
10663
10881
|
return;
|
|
10664
10882
|
}
|
|
10665
10883
|
if (first?.state === "valid" && results.every((result) => result.state === "valid")) {
|
|
@@ -10701,7 +10919,25 @@ ${claims(evidence)}`;
|
|
|
10701
10919
|
}
|
|
10702
10920
|
return svg;
|
|
10703
10921
|
}
|
|
10922
|
+
function foldToggle(folded, fold) {
|
|
10923
|
+
const toggle = node("button", "", "fold");
|
|
10924
|
+
let shown3 = fold.open;
|
|
10925
|
+
const set = (open) => {
|
|
10926
|
+
shown3 = open;
|
|
10927
|
+
for (const part of folded) part.hidden = !open;
|
|
10928
|
+
toggle.setAttribute("aria-expanded", String(open));
|
|
10929
|
+
};
|
|
10930
|
+
toggle.type = "button";
|
|
10931
|
+
toggle.append(foldMark(), document.createTextNode("Details"));
|
|
10932
|
+
set(fold.open);
|
|
10933
|
+
toggle.onclick = () => {
|
|
10934
|
+
set(!shown3);
|
|
10935
|
+
fold.toggle(shown3);
|
|
10936
|
+
};
|
|
10937
|
+
return toggle;
|
|
10938
|
+
}
|
|
10704
10939
|
function externalCard(evidence, manage, later = () => {
|
|
10940
|
+
}, fold, warned = () => {
|
|
10705
10941
|
}) {
|
|
10706
10942
|
const current = evidence.status === "verified" && evidence.expiresAt > Date.now();
|
|
10707
10943
|
const status2 = statusLabel(evidence, Date.now());
|
|
@@ -10714,7 +10950,7 @@ ${claims(evidence)}`;
|
|
|
10714
10950
|
verifier.title = `View this record at ${evidence.verifierName} (opens in a new tab)`;
|
|
10715
10951
|
attribution.append(verifier);
|
|
10716
10952
|
} else attribution.append(document.createTextNode(evidence.verifierName));
|
|
10717
|
-
if (evidence.signedUrl) attribution.append(signedMark(evidence, later));
|
|
10953
|
+
if (evidence.signedUrl) attribution.append(signedMark(evidence, later, warned));
|
|
10718
10954
|
copy.append(node("div", status2, "state"), attribution);
|
|
10719
10955
|
const retired = evidence.status === "retired";
|
|
10720
10956
|
summary.append(verificationMark(current ? "current" : retired ? "pending" : "inactive"), copy);
|
|
@@ -10736,7 +10972,14 @@ ${claims(evidence)}`;
|
|
|
10736
10972
|
for (const [label, time] of dateRows) {
|
|
10737
10973
|
dates.append(node("dt", label), node("dd", moment(time)));
|
|
10738
10974
|
}
|
|
10739
|
-
const
|
|
10975
|
+
const names2 = { site: evidence.siteName, provider };
|
|
10976
|
+
const row = manage && removable(evidence).length ? [manageRow(evidence, manage)] : [];
|
|
10977
|
+
const bar = node("div", "", "bar");
|
|
10978
|
+
const methods = [
|
|
10979
|
+
...attestationNote(main, names2),
|
|
10980
|
+
...evidence.attestations.external.slice(1).flatMap((a2) => attestationNote(a2, names2, true))
|
|
10981
|
+
];
|
|
10982
|
+
if (fold) bar.append(foldToggle([...methods, dates], fold), ...row);
|
|
10740
10983
|
const logo2 = providerMark(evidence.provider, evidence.attestations.external[0].method);
|
|
10741
10984
|
const card = accountCard(
|
|
10742
10985
|
// The mark and the name, or just the name. A mark that falls back to writing the name
|
|
@@ -10744,16 +10987,14 @@ ${claims(evidence)}`;
|
|
|
10744
10987
|
logo2 ? [logo2, document.createTextNode(provider)] : [document.createTextNode(provider)],
|
|
10745
10988
|
externalName(evidence.external),
|
|
10746
10989
|
// A mailbox's address or a domain is its name already, so there is no second identifier.
|
|
10747
|
-
externalId(evidence.external),
|
|
10990
|
+
externalIdGroups(evidence.external) ?? externalId(evidence.external),
|
|
10748
10991
|
externalLink(evidence.external),
|
|
10749
10992
|
// Status first, then how it was shown, then when. The proof explains the state
|
|
10750
10993
|
// above it, so it cannot sit before that state has been given.
|
|
10751
10994
|
summary,
|
|
10752
|
-
...
|
|
10753
|
-
...evidence.attestations.external.slice(1).flatMap((a2) => attestationNote(a2, names, true)),
|
|
10995
|
+
...fold ? [] : methods,
|
|
10754
10996
|
...evidence.status === "revoked" || retired ? [] : evidence.mark === "preferred" ? [node("div", "Preferred", "listing preferred")] : evidence.mark === "unused" ? [node("div", "No longer used", "listing")] : [],
|
|
10755
|
-
dates,
|
|
10756
|
-
...manage && removable(evidence).length ? [manageRow(evidence, manage)] : []
|
|
10997
|
+
...fold ? [bar, ...methods, dates] : [dates, ...row]
|
|
10757
10998
|
);
|
|
10758
10999
|
const surface = retired ? "account retired" : "account";
|
|
10759
11000
|
card.className = surface;
|
|
@@ -10781,7 +11022,7 @@ ${claims(evidence)}`;
|
|
|
10781
11022
|
row.append(add);
|
|
10782
11023
|
return row;
|
|
10783
11024
|
}
|
|
10784
|
-
function render(content, records, manage, later) {
|
|
11025
|
+
function render(content, records, manage, later, folds = { open: false, cards: /* @__PURE__ */ new Set() }) {
|
|
10785
11026
|
const [first] = records;
|
|
10786
11027
|
const local = localSide(first.local, first.siteName);
|
|
10787
11028
|
const localCard = accountCard(
|
|
@@ -10798,15 +11039,40 @@ ${claims(evidence)}`;
|
|
|
10798
11039
|
})
|
|
10799
11040
|
);
|
|
10800
11041
|
const retired = records.filter((record2) => record2.status === "retired");
|
|
11042
|
+
const listed = records.filter((record2) => record2.status !== "retired");
|
|
11043
|
+
const cards = (among, warned) => among.map((record2) => {
|
|
11044
|
+
const key2 = `${record2.provider}
|
|
11045
|
+
${record2.external.id}`;
|
|
11046
|
+
return externalCard(
|
|
11047
|
+
record2,
|
|
11048
|
+
manage,
|
|
11049
|
+
later,
|
|
11050
|
+
among.length > 1 ? {
|
|
11051
|
+
open: folds.cards.has(key2) !== folds.open,
|
|
11052
|
+
toggle: (open) => void (open === folds.open ? folds.cards.delete(key2) : folds.cards.add(key2))
|
|
11053
|
+
} : void 0,
|
|
11054
|
+
warned
|
|
11055
|
+
);
|
|
11056
|
+
});
|
|
11057
|
+
const history = node("details", "", "history");
|
|
11058
|
+
const count = node("summary", "", "group");
|
|
11059
|
+
count.append(foldMark(), document.createTextNode(`Retired accounts (${retired.length})`));
|
|
11060
|
+
history.append(
|
|
11061
|
+
count,
|
|
11062
|
+
...cards(retired, () => {
|
|
11063
|
+
history.open = true;
|
|
11064
|
+
})
|
|
11065
|
+
);
|
|
11066
|
+
history.open = folds.retired ?? !listed.length;
|
|
11067
|
+
count.addEventListener("click", () => {
|
|
11068
|
+
folds.retired = !history.open;
|
|
11069
|
+
});
|
|
10801
11070
|
content.replaceChildren(
|
|
10802
11071
|
localCard,
|
|
10803
11072
|
linkMark(),
|
|
10804
|
-
...
|
|
11073
|
+
...cards(listed),
|
|
10805
11074
|
...manage ? [addRow(manage)] : [],
|
|
10806
|
-
...retired.length ? [
|
|
10807
|
-
node("h3", "Retired accounts", "group"),
|
|
10808
|
-
...retired.map((record2) => externalCard(record2, manage, later))
|
|
10809
|
-
] : [],
|
|
11075
|
+
...retired.length ? [history] : [],
|
|
10810
11076
|
// Nothing below the cards may name a provider. Approval, method and dates belong to
|
|
10811
11077
|
// the card they came from, and a second provider on this subject gets its own card.
|
|
10812
11078
|
node(
|
|
@@ -10816,7 +11082,7 @@ ${claims(evidence)}`;
|
|
|
10816
11082
|
)
|
|
10817
11083
|
);
|
|
10818
11084
|
}
|
|
10819
|
-
function openEvidenceDialog(opener, load2, opened, arrange2 = (records) => records, manage) {
|
|
11085
|
+
function openEvidenceDialog(opener, load2, opened, arrange2 = (records) => records, manage, detailsOpen = false) {
|
|
10820
11086
|
const existing = openDialogs.get(opener);
|
|
10821
11087
|
if (existing?.open) {
|
|
10822
11088
|
existing.focus();
|
|
@@ -10829,7 +11095,9 @@ ${claims(evidence)}`;
|
|
|
10829
11095
|
root.adoptedStyleSheets = [sheet2];
|
|
10830
11096
|
const dialog = node("dialog");
|
|
10831
11097
|
const heading = node("h2", "Verification details");
|
|
10832
|
-
const close = node("button", "
|
|
11098
|
+
const close = node("button", "", "icon");
|
|
11099
|
+
const all = node("a", "", "all");
|
|
11100
|
+
const title = node("div", "", "title");
|
|
10833
11101
|
const header = node("header");
|
|
10834
11102
|
const stamp = node("footer");
|
|
10835
11103
|
const content = node("div", "Checking verification\u2026");
|
|
@@ -10839,13 +11107,18 @@ ${claims(evidence)}`;
|
|
|
10839
11107
|
close.setAttribute("aria-label", "Close verification details");
|
|
10840
11108
|
content.setAttribute("aria-live", "polite");
|
|
10841
11109
|
stamp.append(stampLink(), node("span", version));
|
|
10842
|
-
|
|
11110
|
+
all.append(awayMark());
|
|
11111
|
+
close.append(closeMark());
|
|
11112
|
+
close.autofocus = true;
|
|
11113
|
+
title.append(heading);
|
|
11114
|
+
header.append(title, close);
|
|
10843
11115
|
dialog.append(header, content, stamp);
|
|
10844
11116
|
const removed = /* @__PURE__ */ new Map();
|
|
10845
11117
|
const listed = [];
|
|
10846
11118
|
const listing = (record2) => JSON.stringify([record2.status, record2.mark, record2.retiredAt, record2.approvedAt]);
|
|
10847
11119
|
const listings = (given) => new Map([given].flat().map((record2) => [record2.id, listing(record2)]));
|
|
10848
11120
|
let held;
|
|
11121
|
+
const folds = { open: detailsOpen, cards: /* @__PURE__ */ new Set() };
|
|
10849
11122
|
const relisted = (evidence) => {
|
|
10850
11123
|
let record2 = evidence;
|
|
10851
11124
|
for (const { ids, as: as2, at: at2 } of listed) {
|
|
@@ -10914,11 +11187,19 @@ ${claims(evidence)}`;
|
|
|
10914
11187
|
(e2) => removed.has(e2.id) && e2.status !== "revoked" ? { ...e2, status: "revoked", revokedAt: removed.get(e2.id) } : relisted(standing(e2))
|
|
10915
11188
|
)
|
|
10916
11189
|
);
|
|
11190
|
+
const listable = records.filter((e2) => e2.visibility === "public" && e2.status !== "revoked");
|
|
11191
|
+
title.replaceChildren(heading, ...listable.length > 1 ? [all] : []);
|
|
11192
|
+
if (listable.length > 1) {
|
|
11193
|
+
const label = `View all on ${listable[0].verifierName} (opens in a new tab)`;
|
|
11194
|
+
outward(all, subjectUrl(listable[0]));
|
|
11195
|
+
all.title = label;
|
|
11196
|
+
all.setAttribute("aria-label", label);
|
|
11197
|
+
}
|
|
10917
11198
|
const key2 = JSON.stringify(records.map((e2) => [e2, statusLabel(e2, Date.now())]));
|
|
10918
11199
|
if (key2 === drawn) return;
|
|
10919
11200
|
drawn = key2;
|
|
10920
11201
|
retries.clear();
|
|
10921
|
-
render(content, records, actions, (retry) => retries.add(retry));
|
|
11202
|
+
render(content, records, actions, (retry) => retries.add(retry), folds);
|
|
10922
11203
|
};
|
|
10923
11204
|
const watch = (given) => {
|
|
10924
11205
|
clearTimeout(expiryTimer);
|
|
@@ -10997,13 +11278,15 @@ ${claims(evidence)}`;
|
|
|
10997
11278
|
/* The block scrolls and its frame does not, so the copy button stays in its corner. */
|
|
10998
11279
|
.code { position: relative; margin: 10px 0; }
|
|
10999
11280
|
pre { margin: 0; padding: 10px 64px 10px 12px; border: 1px solid #e0e6df; border-radius: 8px; background: #f6f8f5; font: 12px/1.5 ui-monospace, monospace; white-space: pre; overflow-x: auto; }
|
|
11000
|
-
.code .action { position: absolute; top:
|
|
11281
|
+
.code .action { position: absolute; top: 9px; right: 9px; padding: 3px 8px; font-size: 11px; }
|
|
11001
11282
|
label { display: grid; gap: 4px; margin-top: 12px; font-weight: 600; }
|
|
11002
11283
|
input[type=url], input[type=email], input[type=tel], input[type=text], textarea { width: 100%; padding: 8px 10px; border: 1px solid #cfd8d2; border-radius: 8px; font: 13px/1.4 system-ui, sans-serif; color: inherit; background: #fff; }
|
|
11003
11284
|
textarea { font-family: ui-monospace, monospace; font-size: 12px; resize: vertical; }
|
|
11004
11285
|
fieldset { margin: 16px 0 0; padding: 10px 14px; border: 1px solid #e0e6df; border-radius: 10px; }
|
|
11005
11286
|
legend { padding: 0 4px; color: #6b786f; font-size: 11px; font-weight: 550; }
|
|
11006
11287
|
fieldset label { display: flex; align-items: baseline; gap: 8px; margin-top: 4px; font-weight: 500; }
|
|
11288
|
+
/* A radio has no baseline to speak of, so it is set in the middle of the label's first line. */
|
|
11289
|
+
fieldset input { flex-shrink: 0; align-self: flex-start; width: 13px; height: 13px; font: inherit; margin-top: 4px; margin-top: calc((1lh - 13px) / 2); margin-bottom: 0; }
|
|
11007
11290
|
.error { color: #9b2c33; }
|
|
11008
11291
|
`;
|
|
11009
11292
|
function codeBlock(code) {
|
|
@@ -11065,7 +11348,7 @@ ${claims(evidence)}`;
|
|
|
11065
11348
|
root.adoptedStyleSheets = [sheet2];
|
|
11066
11349
|
const dialog = node("dialog");
|
|
11067
11350
|
const heading = node("h2", "Verify an account");
|
|
11068
|
-
const close = node("button", "
|
|
11351
|
+
const close = node("button", "", "icon");
|
|
11069
11352
|
const header = node("header");
|
|
11070
11353
|
const stamp = node("footer");
|
|
11071
11354
|
const content = node("div", "Loading\u2026", "steps");
|
|
@@ -11073,6 +11356,7 @@ ${claims(evidence)}`;
|
|
|
11073
11356
|
dialog.setAttribute("aria-labelledby", heading.id);
|
|
11074
11357
|
close.type = "button";
|
|
11075
11358
|
close.setAttribute("aria-label", "Close");
|
|
11359
|
+
close.append(closeMark());
|
|
11076
11360
|
content.setAttribute("aria-live", "polite");
|
|
11077
11361
|
stamp.append(stampLink(), node("span", version));
|
|
11078
11362
|
header.append(heading, close);
|
|
@@ -11082,9 +11366,10 @@ ${claims(evidence)}`;
|
|
|
11082
11366
|
openDialogs2.set(opener, dialog);
|
|
11083
11367
|
let returning = false;
|
|
11084
11368
|
if (back) {
|
|
11085
|
-
const before = node("button", "
|
|
11369
|
+
const before = node("button", "", "icon");
|
|
11086
11370
|
before.type = "button";
|
|
11087
11371
|
before.setAttribute("aria-label", "Back");
|
|
11372
|
+
before.append(backMark());
|
|
11088
11373
|
header.className = "backed";
|
|
11089
11374
|
header.replaceChildren(before, heading, close);
|
|
11090
11375
|
before.onclick = () => {
|
|
@@ -11359,7 +11644,7 @@ ${claims(evidence)}`;
|
|
|
11359
11644
|
const external = accountCard(
|
|
11360
11645
|
logo2 ? [logo2, document.createTextNode(flow.provider.name)] : [document.createTextNode(flow.provider.name)],
|
|
11361
11646
|
externalName(flow.external),
|
|
11362
|
-
externalId(flow.external),
|
|
11647
|
+
externalIdGroups(flow.external) ?? externalId(flow.external),
|
|
11363
11648
|
externalLink(flow.external)
|
|
11364
11649
|
);
|
|
11365
11650
|
const parts = [subjectCard(flow.local), linkMark(), external];
|
|
@@ -11510,15 +11795,19 @@ ${claims(evidence)}`;
|
|
|
11510
11795
|
},
|
|
11511
11796
|
latestGroup.get(element),
|
|
11512
11797
|
accounts,
|
|
11513
|
-
managers.get(element)
|
|
11798
|
+
managers.get(element),
|
|
11799
|
+
openHosts.has(element)
|
|
11514
11800
|
);
|
|
11515
11801
|
}
|
|
11516
11802
|
}
|
|
11517
11803
|
var stackedHosts = /* @__PURE__ */ new WeakSet();
|
|
11518
11804
|
var quietHosts = /* @__PURE__ */ new WeakSet();
|
|
11805
|
+
var openHosts = /* @__PURE__ */ new WeakSet();
|
|
11519
11806
|
var stackedAccounts = 4;
|
|
11520
11807
|
var peekedAccounts = 8;
|
|
11521
|
-
function arrange(element, { stacked, peek }) {
|
|
11808
|
+
function arrange(element, { stacked, peek, detailsOpen }) {
|
|
11809
|
+
if (detailsOpen) openHosts.add(element);
|
|
11810
|
+
else openHosts.delete(element);
|
|
11522
11811
|
if (stacked) stackedHosts.add(element);
|
|
11523
11812
|
else stackedHosts.delete(element);
|
|
11524
11813
|
if (peek === false) quietHosts.add(element);
|
|
@@ -11526,34 +11815,14 @@ ${claims(evidence)}`;
|
|
|
11526
11815
|
}
|
|
11527
11816
|
var live = (evidence) => evidence.status === "verified" && evidence.expiresAt > Date.now();
|
|
11528
11817
|
function accounts(records) {
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
|
|
11537
|
-
const verified = sorted.filter(live);
|
|
11538
|
-
const base = verified[0] ?? sorted.reduce((a2, b2) => b2.approvedAt > a2.approvedAt ? b2 : a2);
|
|
11539
|
-
const external = [...base.attestations.external];
|
|
11540
|
-
for (const other of verified.slice(1))
|
|
11541
|
-
for (const attestation of other.attestations.external)
|
|
11542
|
-
if (!external.some((shown3) => shown3.method === attestation.method))
|
|
11543
|
-
external.push(attestation);
|
|
11544
|
-
return {
|
|
11545
|
-
...base,
|
|
11546
|
-
connectedAt: when(sorted[0]),
|
|
11547
|
-
attestations: { ...base.attestations, external },
|
|
11548
|
-
links: sorted.filter((e2) => e2.status !== "revoked").map((e2) => e2.id),
|
|
11549
|
-
// Every record the card speaks for, so its signature mark answers for all of them.
|
|
11550
|
-
sources: verified.length ? verified : [base]
|
|
11551
|
-
};
|
|
11552
|
-
}).sort((a2, b2) => place2(a2) - place2(b2) || a2.connectedAt - b2.connectedAt);
|
|
11553
|
-
}
|
|
11554
|
-
function place2(account) {
|
|
11555
|
-
if (account.status === "retired") return 3;
|
|
11556
|
-
return account.mark === "preferred" ? 0 : account.mark === "unused" ? 2 : 1;
|
|
11818
|
+
return accountsOf(records, Date.now()).map(({ lead, held, verified, external, connectedAt }) => ({
|
|
11819
|
+
...lead,
|
|
11820
|
+
connectedAt,
|
|
11821
|
+
attestations: { ...lead.attestations, external },
|
|
11822
|
+
links: held.filter((e2) => e2.status !== "revoked").map((e2) => e2.id),
|
|
11823
|
+
// Every record the card speaks for, so its signature mark answers for all of them.
|
|
11824
|
+
sources: verified.length ? verified : [lead]
|
|
11825
|
+
}));
|
|
11557
11826
|
}
|
|
11558
11827
|
function presentGroup(element, records, load2) {
|
|
11559
11828
|
const ordered = accounts(records.map(standing));
|
|
@@ -11566,6 +11835,8 @@ ${claims(evidence)}`;
|
|
|
11566
11835
|
const badge = renderBadge(element, lead, {
|
|
11567
11836
|
more: Math.max(verified.length - 1, 0),
|
|
11568
11837
|
linked: lead.visibility === "public",
|
|
11838
|
+
// A modified click follows the link, and must reach what a plain one shows: every account.
|
|
11839
|
+
...ordered.length > 1 ? { href: subjectUrl(lead) } : {},
|
|
11569
11840
|
rows: stacked ? verified.slice(1, stackedAccounts) : peek ? verified.slice(1, peekedAccounts) : [],
|
|
11570
11841
|
peek
|
|
11571
11842
|
});
|
|
@@ -11594,7 +11865,7 @@ ${claims(evidence)}`;
|
|
|
11594
11865
|
function init({ backendUrl, handoffUrl }) {
|
|
11595
11866
|
const base = new URL(backendUrl, location.href);
|
|
11596
11867
|
if (!["https:", "http:"].includes(base.protocol)) throw new Error("Invalid backend URL");
|
|
11597
|
-
|
|
11868
|
+
const root = base.href.replace(/\/$/, "");
|
|
11598
11869
|
const remote = base.origin !== location.origin;
|
|
11599
11870
|
const bindings = /* @__PURE__ */ new Map();
|
|
11600
11871
|
let session;
|
|
@@ -11607,7 +11878,7 @@ ${claims(evidence)}`;
|
|
|
11607
11878
|
const vouched = handed.ok ? await handed.json() : void 0;
|
|
11608
11879
|
if (!record(vouched) || typeof vouched.token !== "string")
|
|
11609
11880
|
throw new Error("Verily request unavailable");
|
|
11610
|
-
const traded = await fetch(`${
|
|
11881
|
+
const traded = await fetch(`${root}/site/session`, {
|
|
11611
11882
|
method: "POST",
|
|
11612
11883
|
credentials: "omit",
|
|
11613
11884
|
cache: "no-store",
|
|
@@ -11622,11 +11893,12 @@ ${claims(evidence)}`;
|
|
|
11622
11893
|
async function ask(path, data, flow) {
|
|
11623
11894
|
if (!remote) return request(path, data);
|
|
11624
11895
|
const binding = flow && bindings.get(flow);
|
|
11625
|
-
const response = await fetch(`${
|
|
11896
|
+
const response = await fetch(`${root}${path}`, {
|
|
11626
11897
|
credentials: "omit",
|
|
11627
11898
|
cache: "no-store",
|
|
11628
11899
|
method: data ? "POST" : "GET",
|
|
11629
11900
|
headers: {
|
|
11901
|
+
Accept: "application/json",
|
|
11630
11902
|
Authorization: `Bearer ${await (session ??= openSession())}`,
|
|
11631
11903
|
...binding ? { "X-Verily-Flow": binding } : {},
|
|
11632
11904
|
...data ? { "Content-Type": "application/json" } : {}
|
|
@@ -11640,21 +11912,23 @@ ${claims(evidence)}`;
|
|
|
11640
11912
|
return answer;
|
|
11641
11913
|
}
|
|
11642
11914
|
async function request(path, data) {
|
|
11643
|
-
const response = await fetch(`${
|
|
11915
|
+
const response = await fetch(`${root}${path}`, {
|
|
11644
11916
|
credentials: "same-origin",
|
|
11645
11917
|
cache: "no-store",
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
|
|
11649
|
-
|
|
11650
|
-
|
|
11918
|
+
// Says what is wanted back, whatever is sent. A header any page may send to another
|
|
11919
|
+
// origin without asking first, so a badge on a static site reads as it did.
|
|
11920
|
+
headers: {
|
|
11921
|
+
Accept: "application/json",
|
|
11922
|
+
...data ? { "Content-Type": "application/json" } : {}
|
|
11923
|
+
},
|
|
11924
|
+
...data ? { method: "POST", body: JSON.stringify(data) } : {}
|
|
11651
11925
|
});
|
|
11652
11926
|
if (!response.ok) throw new Error("Verily request unavailable");
|
|
11653
11927
|
return response.json();
|
|
11654
11928
|
}
|
|
11655
11929
|
const flows = {
|
|
11656
11930
|
async methods() {
|
|
11657
|
-
const data = await ask("
|
|
11931
|
+
const data = await ask(routePath("methods"));
|
|
11658
11932
|
if (!record(data) || !record(data.local) || !Array.isArray(data.methods))
|
|
11659
11933
|
throw new Error("Invalid methods response");
|
|
11660
11934
|
if (typeof data.local.profileUrl === "string") safeUrl(data.local.profileUrl);
|
|
@@ -11662,15 +11936,15 @@ ${claims(evidence)}`;
|
|
|
11662
11936
|
},
|
|
11663
11937
|
start: async (provider, method, renew) => flowView(
|
|
11664
11938
|
await ask(
|
|
11665
|
-
"
|
|
11939
|
+
routePath("start"),
|
|
11666
11940
|
renew ? { kind: "renew", connectionId: renew, provider, method } : { kind: "connect", provider, method }
|
|
11667
11941
|
)
|
|
11668
11942
|
),
|
|
11669
|
-
read: async (id) => flowView(await ask(
|
|
11670
|
-
submit: async (id, artifact) => flowView(await ask(
|
|
11943
|
+
read: async (id) => flowView(await ask(routePath("flow", id), void 0, id)),
|
|
11944
|
+
submit: async (id, artifact) => flowView(await ask(routePath("submit", id), { artifact }, id)),
|
|
11671
11945
|
async approve(id, visibility, cancel) {
|
|
11672
11946
|
const data = await ask(
|
|
11673
|
-
|
|
11947
|
+
routePath("approve", id),
|
|
11674
11948
|
{ action: cancel ? "cancel" : "approve", visibility },
|
|
11675
11949
|
id
|
|
11676
11950
|
);
|
|
@@ -11700,7 +11974,7 @@ ${claims(evidence)}`;
|
|
|
11700
11974
|
);
|
|
11701
11975
|
};
|
|
11702
11976
|
window.addEventListener("message", receive);
|
|
11703
|
-
popup.location.href = `${
|
|
11977
|
+
popup.location.href = `${root}/site/enter`;
|
|
11704
11978
|
}
|
|
11705
11979
|
} : {}
|
|
11706
11980
|
};
|
|
@@ -11724,7 +11998,7 @@ ${claims(evidence)}`;
|
|
|
11724
11998
|
pill.setAttribute("aria-haspopup", "dialog");
|
|
11725
11999
|
pill.onclick = async () => {
|
|
11726
12000
|
if (typeof HTMLDialogElement === "undefined" && !remote) {
|
|
11727
|
-
location.href = `${
|
|
12001
|
+
location.href = `${root}/verify`;
|
|
11728
12002
|
return;
|
|
11729
12003
|
}
|
|
11730
12004
|
const result = await client.openConnect(element);
|
|
@@ -11839,7 +12113,7 @@ ${claims(evidence)}`;
|
|
|
11839
12113
|
},
|
|
11840
12114
|
disconnect(id) {
|
|
11841
12115
|
if (remote) session = void 0;
|
|
11842
|
-
return ask(
|
|
12116
|
+
return ask(routePath("disconnect", id), {});
|
|
11843
12117
|
},
|
|
11844
12118
|
/**
|
|
11845
12119
|
* Says how the holder would have an account listed, or retires it. Names one record
|
|
@@ -11847,10 +12121,10 @@ ${claims(evidence)}`;
|
|
|
11847
12121
|
*/
|
|
11848
12122
|
mark(id, as2) {
|
|
11849
12123
|
if (remote) session = void 0;
|
|
11850
|
-
return ask(
|
|
12124
|
+
return ask(routePath("mark", id), { as: as2 });
|
|
11851
12125
|
},
|
|
11852
|
-
issueShare: (id) => request(
|
|
11853
|
-
revokeShare: (id) => request(
|
|
12126
|
+
issueShare: (id) => request(routePath("share", id), {}),
|
|
12127
|
+
revokeShare: (id) => request(routePath("shareRevoke", id), {})
|
|
11854
12128
|
};
|
|
11855
12129
|
return client;
|
|
11856
12130
|
}
|
|
@@ -11899,7 +12173,7 @@ ${claims(evidence)}`;
|
|
|
11899
12173
|
}
|
|
11900
12174
|
function validAttestation(attestation) {
|
|
11901
12175
|
if (!record(attestation)) return false;
|
|
11902
|
-
return ["backend", "provider"].includes(String(attestation.by)) && typeof attestation.method === "string" && typeof attestation.confirmedAt === "number" && Number.isFinite(attestation.confirmedAt) && (attestation.expect === void 0 || typeof attestation.expect === "string") && // Rendered as a link later, so only http(s) may ever reach an href.
|
|
12176
|
+
return ["backend", "provider"].includes(String(attestation.by)) && typeof attestation.method === "string" && typeof attestation.confirmedAt === "number" && Number.isFinite(attestation.confirmedAt) && (attestation.expect === void 0 || typeof attestation.expect === "string") && (attestation.dnssec === void 0 || typeof attestation.dnssec === "boolean") && // Rendered as a link later, so only http(s) may ever reach an href.
|
|
11903
12177
|
(attestation.artifactUrl === void 0 || httpUrl2(attestation.artifactUrl));
|
|
11904
12178
|
}
|
|
11905
12179
|
function httpUrl2(value) {
|
|
@@ -11963,7 +12237,8 @@ ${claims(evidence)}`;
|
|
|
11963
12237
|
"handoff-url",
|
|
11964
12238
|
"connect",
|
|
11965
12239
|
"stacked",
|
|
11966
|
-
"peek"
|
|
12240
|
+
"peek",
|
|
12241
|
+
"details-open"
|
|
11967
12242
|
];
|
|
11968
12243
|
handed;
|
|
11969
12244
|
/**
|
|
@@ -12017,7 +12292,8 @@ ${claims(evidence)}`;
|
|
|
12017
12292
|
const handoffUrl = this.getAttribute("handoff-url");
|
|
12018
12293
|
const arrangement = {
|
|
12019
12294
|
stacked: this.getAttribute("stacked") !== null,
|
|
12020
|
-
peek: this.getAttribute("peek") !== "off"
|
|
12295
|
+
peek: this.getAttribute("peek") !== "off",
|
|
12296
|
+
detailsOpen: this.getAttribute("details-open") !== null
|
|
12021
12297
|
};
|
|
12022
12298
|
if (backendUrl && (handoffUrl !== null || this.getAttribute("connect") !== null)) {
|
|
12023
12299
|
const client = init({ backendUrl, handoffUrl: handoffUrl ?? void 0 });
|