@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/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, names) {
9788
+ function attestationLabel(method, names2, proof = {}) {
9789
+ const dnssec = proof.dnssec ? ", validated by DNSSEC" : "";
9714
9790
  return {
9715
- declared: `Stated by ${names.site}`,
9716
- oauth: `Signed in with ${names.provider}`,
9717
- gist: `Published a proof on ${names.provider}`,
9718
- backlink: `Linked back to ${names.site}`,
9719
- dns: `Named ${names.site} in a DNS record`,
9720
- wellknown: `Named ${names.site} in a file it serves`,
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 = evidence.evidenceUrl;
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
- place(panel, pill, listed, unnamed);
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 place(panel, pill, listed, unnamed) {
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.4.0";
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: middle; }
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, names, additional = false) {
10539
- const label = attestationLabel(attestation.method, names);
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
- row.replaceChildren(renew, ...listing, remove, ...note ? [node("span", note, "muted")] : []);
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 names = { site: evidence.siteName, provider };
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
- ...attestationNote(main, names),
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
- ...records.filter((record2) => record2.status !== "retired").map((record2) => externalCard(record2, manage, later)),
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", "\xD7");
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
- header.append(heading, close);
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: 6px; right: 6px; padding: 3px 8px; font-size: 11px; }
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", "\xD7");
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", "\u2039");
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
- const when = (e2) => e2.connectedAt ?? e2.approvedAt;
11530
- const byAccount = /* @__PURE__ */ new Map();
11531
- for (const record2 of records) {
11532
- const key2 = JSON.stringify([record2.provider, record2.external.id]);
11533
- byAccount.set(key2, [...byAccount.get(key2) ?? [], record2]);
11534
- }
11535
- return [...byAccount.values()].map((held) => {
11536
- const sorted = [...held].sort((a2, b2) => when(a2) - when(b2));
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
- base.pathname = base.pathname.replace(/\/$/, "");
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(`${base.href}/site/session`, {
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(`${base.href}${path}`, {
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(`${base.href}${path}`, {
11915
+ const response = await fetch(`${root}${path}`, {
11644
11916
  credentials: "same-origin",
11645
11917
  cache: "no-store",
11646
- ...data ? {
11647
- method: "POST",
11648
- headers: { "Content-Type": "application/json" },
11649
- body: JSON.stringify(data)
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("/methods");
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
- "/sessions",
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(`/flows/${encodeURIComponent(id)}?format=json`, void 0, id)),
11670
- submit: async (id, artifact) => flowView(await ask(`/flows/${encodeURIComponent(id)}/submit`, { artifact }, id)),
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
- `/flows/${encodeURIComponent(id)}/approve`,
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 = `${base.href}/site/enter`;
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 = `${base.href}/verify`;
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(`/connections/${encodeURIComponent(id)}/disconnect`, {});
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(`/connections/${encodeURIComponent(id)}/mark`, { as: as2 });
12124
+ return ask(routePath("mark", id), { as: as2 });
11851
12125
  },
11852
- issueShare: (id) => request(`/connections/${encodeURIComponent(id)}/share`, {}),
11853
- revokeShare: (id) => request(`/connections/${encodeURIComponent(id)}/share-revoke`, {})
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 });