@gerege-systems/eid-mongolia-sdk 0.15.2 → 0.15.3

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 CHANGED
@@ -35,7 +35,7 @@ Smart-ID-тэй RP-үүдийн хэв маяг: хөтөчид **нэг тов
35
35
 
36
36
  | Хаана | Юу болно |
37
37
  |---|---|
38
- | Desktop хөтөч | Цонх: QR (секунд тутам шинэчлэгдэнэ) + «регистрийн дугаараар утас руу мэдэгдэл» (push), үлдсэн хугацаа, шууд төлөв |
38
+ | Desktop хөтөч | Цонх: QR (секунд тутам шинэчлэгдэнэ) + баталгаажуулах код + «Хүсэлтийн хугацаа m:ss» (сүүлийн 30с анхааруулах өнгө) + «Регистрийн дугаараар нэвтрэх» товч (ижил цонхонд доош дэлгэгдэх push хэсэг), шууд төлөв |
39
39
  | Утасны хөтөч | Товч → `start?mode=web2app` → сервер 302 → eID апп (Web2App). Апп баталгаажуулаад `/auth/eid/callback` руу буцаана |
40
40
  | Backend | session эхлүүлэх, QR угсрах (`sessionSecret` серверт), poll, `validateAuth` (гинж + ACSP_V2 + `flowType` + Web2App-д `userChallengeVerifier`) → таны `onSuccess` |
41
41
 
package/dist/button.cjs CHANGED
@@ -397,8 +397,9 @@ var T = {
397
397
  loading: "\u0410\u0447\u0430\u0430\u043B\u0436 \u0431\u0430\u0439\u043D\u0430",
398
398
  qrAlt: "\u041D\u044D\u0432\u0442\u0440\u044D\u0445 QR \u043A\u043E\u0434 \u2014 \u0443\u0442\u0441\u0430\u043D \u0434\u044D\u044D\u0440\u0445 eID Mongolia \u0430\u043F\u043F\u0430\u0430\u0440 \u0443\u043D\u0448\u0443\u0443\u043B\u043D\u0430",
399
399
  steps: ["\u0423\u0442\u0441\u0430\u043D \u0434\u044D\u044D\u0440\u0445 eID Mongolia \u0430\u043F\u043F\u044B\u0433 \u043D\u044D\u044D\u043D\u044D", "QR \u043A\u043E\u0434\u044B\u0433 \u0443\u043D\u0448\u0443\u0443\u043B\u043D\u0430", "PIN1 \u043A\u043E\u0434\u043E\u043E \u043E\u0440\u0443\u0443\u043B\u043D\u0430"],
400
- code: "\u0423\u0442\u0441\u0430\u043D \u0434\u044D\u044D\u0440\u0445 \u043A\u043E\u0434:",
400
+ vcLabel: "\u0411\u0430\u0442\u0430\u043B\u0433\u0430\u0430\u0436\u0443\u0443\u043B\u0430\u0445 \u043A\u043E\u0434",
401
401
  or: "\u044D\u0441\u0432\u044D\u043B",
402
+ usePush: "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0439\u043D \u0434\u0443\u0433\u0430\u0430\u0440\u0430\u0430\u0440 \u043D\u044D\u0432\u0442\u0440\u044D\u0445",
402
403
  idLabel: "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0439\u043D \u0434\u0443\u0433\u0430\u0430\u0440",
403
404
  idHint: "\u0423\u0442\u0441\u0430\u043D\u0434 \u0442\u0430\u043D\u044C \u043C\u044D\u0434\u044D\u0433\u0434\u044D\u043B \u043E\u0447\u043D\u043E. \u0418\u0440\u0433\u044D\u043D\u0438\u0439 12 \u043E\u0440\u043E\u043D\u0442\u043E\u0439 \u0434\u0443\u0433\u0430\u0430\u0440\u0430\u0430\u0440 \u0447 \u0431\u043E\u043B\u043D\u043E.",
404
405
  idEmpty: "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0439\u043D \u0434\u0443\u0433\u0430\u0430\u0440 \u043E\u0440\u0443\u0443\u043B\u043D\u0430 \u0443\u0443",
@@ -411,7 +412,7 @@ var T = {
411
412
  waitText: "\u0423\u0442\u0441\u0430\u043D \u0434\u044D\u044D\u0440\u0445 \u043A\u043E\u0434 \u0438\u0436\u0438\u043B \u0431\u043E\u043B PIN1 \u043E\u0440\u0443\u0443\u043B\u043D\u0430 \u0443\u0443. \u0418\u0436\u0438\u043B \u0431\u0438\u0448 \u0431\u043E\u043B \u0442\u0430\u0442\u0433\u0430\u043B\u0437\u0430\u043D\u0430 \u0443\u0443.",
412
413
  waiting: "\u0425\u04AF\u043B\u044D\u044D\u0436 \u0431\u0430\u0439\u043D\u0430",
413
414
  back: "\u0411\u0443\u0446\u0430\u0445",
414
- left: "\u04AE\u043B\u0434\u0441\u044D\u043D \u0445\u0443\u0433\u0430\u0446\u0430\u0430",
415
+ left: "\u0425\u04AF\u0441\u044D\u043B\u0442\u0438\u0439\u043D \u0445\u0443\u0433\u0430\u0446\u0430\u0430:",
415
416
  stale: "QR \u043A\u043E\u0434 \u0445\u0443\u0443\u0447\u0438\u0440\u0441\u0430\u043D",
416
417
  refresh: "\u0428\u0438\u043D\u044D\u0447\u043B\u044D\u0445",
417
418
  offline: "\u0425\u043E\u043B\u0431\u043E\u043B\u0442 \u0442\u0430\u0441\u0430\u0440\u0441\u0430\u043D. \u0418\u043D\u0442\u0435\u0440\u043D\u044D\u0442\u044D\u044D \u0448\u0430\u043B\u0433\u0430\u043D\u0430 \u0443\u0443 \u2014 \u0434\u0430\u0445\u0438\u043D \u0445\u043E\u043B\u0431\u043E\u0433\u0434\u043E\u0445\u044B\u0433 \u043E\u0440\u043E\u043B\u0434\u043E\u0436 \u0431\u0430\u0439\u043D\u0430.",
@@ -425,7 +426,7 @@ var T = {
425
426
  retry: "\u0414\u0430\u0445\u0438\u043D \u043E\u0440\u043E\u043B\u0434\u043E\u0445",
426
427
  success: "\u0410\u043C\u0436\u0438\u043B\u0442\u0442\u0430\u0439 \u043D\u044D\u0432\u0442\u044D\u0440\u043B\u044D\u044D",
427
428
  sent: (vc) => `\u041C\u044D\u0434\u044D\u0433\u0434\u044D\u043B \u0438\u043B\u0433\u044D\u044D\u043B\u044D\u044D. \u0423\u0442\u0441\u0430\u043D \u0434\u044D\u044D\u0440\u0445 \u043A\u043E\u0434 ${vc.split("").join(" ")}`,
428
- minute: "1 \u043C\u0438\u043D\u0443\u0442 \u04AF\u043B\u0434\u043B\u044D\u044D"
429
+ minutes: (n) => `${n} \u043C\u0438\u043D\u0443\u0442 \u04AF\u043B\u0434\u043B\u044D\u044D`
429
430
  },
430
431
  en: {
431
432
  button: "Log in with eID",
@@ -434,8 +435,9 @@ var T = {
434
435
  loading: "Loading",
435
436
  qrAlt: "Login QR code \u2014 scan it with the eID Mongolia app on your phone",
436
437
  steps: ["Open the eID Mongolia app on your phone", "Scan the QR code", "Enter your PIN1"],
437
- code: "Code on your phone:",
438
+ vcLabel: "Verification code",
438
439
  or: "or",
440
+ usePush: "Log in with registration number",
439
441
  idLabel: "Registration number",
440
442
  idHint: "A notification will be sent to your phone. The 12-digit civil ID also works.",
441
443
  idEmpty: "Enter your registration number",
@@ -448,7 +450,7 @@ var T = {
448
450
  waitText: "If the code on your phone matches, enter PIN1. Otherwise decline.",
449
451
  waiting: "Waiting",
450
452
  back: "Back",
451
- left: "Time left",
453
+ left: "Request expires in:",
452
454
  stale: "QR code expired",
453
455
  refresh: "Refresh",
454
456
  offline: "Connection lost. Check your internet \u2014 reconnecting.",
@@ -462,7 +464,7 @@ var T = {
462
464
  retry: "Try again",
463
465
  success: "Logged in",
464
466
  sent: (vc) => `Notification sent. Code on your phone ${vc.split("").join(" ")}`,
465
- minute: "1 minute left"
467
+ minutes: (n) => `${n} minute${n === 1 ? "" : "s"} left`
466
468
  }
467
469
  };
468
470
  var EID_COLOR_TOKENS = [
@@ -528,7 +530,8 @@ h2, p, ol { margin: 0; }
528
530
  .qr.stale svg { opacity: 0.12; }
529
531
  .qr-stale { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
530
532
  color: hsl(222 47% 11%); font-weight: 500; }
531
- .qr-vc { margin-top: 8px; text-align: center; font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
533
+ .qr-vc { margin-top: 12px; text-align: center; }
534
+ .vc-label { font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
532
535
  .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; letter-spacing: 0.08em; color: var(--_eid-fg-muted); }
533
536
  .steps { margin-top: 16px; padding: 0; list-style: none; counter-reset: st; display: grid; gap: 8px; }
534
537
  .steps li { counter-increment: st; display: flex; align-items: center; gap: 12px; }
@@ -536,6 +539,13 @@ h2, p, ol { margin: 0; }
536
539
  color: var(--_eid-accent-subtle-fg); display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: 600; }
537
540
  .or { display: flex; align-items: center; gap: 12px; margin: 24px 0; color: var(--_eid-fg-subtle); font-size: 0.8125rem; }
538
541
  .or::before, .or::after { content: ""; flex: 1; height: 1px; background: var(--_eid-border); }
542
+ .disc-btn .chev { width: 16px; height: 16px; flex: none; transition: transform 200ms cubic-bezier(0,0,0.2,1); }
543
+ .disc-btn[aria-expanded="true"] .chev { transform: rotate(180deg); }
544
+ .disc { margin: 0 -4px; display: grid; grid-template-rows: 0fr; transition: grid-template-rows 200ms cubic-bezier(0,0,0.2,1); }
545
+ .disc.open { grid-template-rows: 1fr; }
546
+ .disc > div { min-height: 0; overflow: hidden; visibility: hidden; transition: visibility 0s linear 200ms; }
547
+ .disc.open > div { visibility: visible; transition-delay: 0s; }
548
+ .disc form { padding: 16px 4px 4px; }
539
549
  .field { display: grid; gap: 8px; }
540
550
  .field label { font-weight: 500; }
541
551
  .field input { width: 100%; height: 44px; padding: 0 12px; border: 1px solid var(--_eid-border-strong); border-radius: var(--_eid-radius);
@@ -546,7 +556,8 @@ h2, p, ol { margin: 0; }
546
556
  .err { font-size: 0.8125rem; color: var(--_eid-danger-fg); font-weight: 500; }
547
557
  .push { margin-top: 16px; }
548
558
  .center { text-align: center; }
549
- .vc { font: 600 2.25rem/2.5rem ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.16em; padding-left: 0.16em; }
559
+ .vc { font: 700 2rem/2.5rem ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; letter-spacing: 0.16em;
560
+ padding-left: 0.16em; color: var(--_eid-fg); }
550
561
  .vc:focus, h2:focus { outline: none; }
551
562
  .text { margin-top: 12px; color: var(--_eid-fg-muted); }
552
563
  .pending { margin-top: 16px; display: inline-flex; align-items: center; gap: 8px; color: var(--_eid-fg-muted); }
@@ -555,7 +566,8 @@ h2, p, ol { margin: 0; }
555
566
  @keyframes spin { to { transform: rotate(360deg); } }
556
567
  .netwarn { margin-top: 16px; padding: 8px 12px; border: 1px solid var(--_eid-warning-border); border-radius: var(--_eid-radius); background: var(--_eid-warning-bg);
557
568
  color: var(--_eid-warning-fg); font-size: 0.8125rem; }
558
- .meta { margin-top: 16px; font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
569
+ .meta { margin-top: 4px; text-align: center; font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
570
+ .meta.low, .meta.low .mono { color: var(--_eid-warning-fg); }
559
571
  @media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition: none !important; }
560
572
  .spinner { animation: none; border-right-color: currentColor; opacity: 0.6; } }
561
573
  `;
@@ -578,6 +590,24 @@ function el(tag, attrs = {}, text) {
578
590
  if (text != null) e.textContent = text;
579
591
  return e;
580
592
  }
593
+ function chevron() {
594
+ const NS = "http://www.w3.org/2000/svg";
595
+ const svg = document.createElementNS(NS, "svg");
596
+ for (const [k, v] of Object.entries({
597
+ class: "chev",
598
+ viewBox: "0 0 24 24",
599
+ fill: "none",
600
+ stroke: "currentColor",
601
+ "stroke-width": "2",
602
+ "stroke-linecap": "round",
603
+ "stroke-linejoin": "round",
604
+ "aria-hidden": "true"
605
+ })) svg.setAttribute(k, v);
606
+ const path = document.createElementNS(NS, "path");
607
+ path.setAttribute("d", "m6 9 6 6 6-6");
608
+ svg.append(path);
609
+ return svg;
610
+ }
581
611
  var Base = typeof HTMLElement === "undefined" ? class {
582
612
  } : HTMLElement;
583
613
  var EidLoginButton = class extends Base {
@@ -710,7 +740,7 @@ var EidLoginButton = class extends Base {
710
740
  return;
711
741
  }
712
742
  if (e.key !== "Tab" || !this.backdrop) return;
713
- const items = Array.from(this.backdrop.querySelectorAll(FOCUSABLE)).filter((n) => !n.closest("[hidden]"));
743
+ const items = Array.from(this.backdrop.querySelectorAll(FOCUSABLE)).filter((n) => !n.closest("[hidden], [inert]"));
714
744
  if (items.length === 0) return;
715
745
  const first = items[0];
716
746
  const last = items[items.length - 1];
@@ -792,21 +822,22 @@ var EidLoginButton = class extends Base {
792
822
  if (s.qr) this.drawQr(s.qr);
793
823
  const nodes = [this.qrBox];
794
824
  if (s.vc) {
795
- const vc = el("p", { class: "qr-vc" }, t.code + " ");
796
- vc.append(el("span", { class: "mono" }, s.vc));
825
+ const vc = el("div", { class: "qr-vc" });
826
+ vc.append(el("p", { class: "vc-label", id: "eid-vc-label" }, t.vcLabel), el("p", { class: "vc" }, s.vc));
797
827
  nodes.push(vc);
798
828
  }
829
+ nodes.push(this.meta());
799
830
  const steps = el("ol", { class: "steps" });
800
831
  for (const st of t.steps) steps.append(el("li", {}, st));
801
- nodes.push(steps, el("div", { class: "or", role: "separator", "aria-label": t.or }, t.or), this.pushForm());
832
+ nodes.push(steps, el("div", { class: "or", role: "separator", "aria-label": t.or }, t.or), ...this.pushToggle());
802
833
  this.netwarn = el("p", { class: "netwarn", role: "status", hidden: "" }, t.offline);
803
- nodes.push(this.netwarn, this.meta());
834
+ nodes.push(this.netwarn);
804
835
  this.view(...nodes);
805
836
  }
806
837
  leftEl = null;
807
838
  meta() {
808
839
  const p = el("p", { class: "meta" }, this.t.left + " ");
809
- this.leftEl = el("span", { class: "mono", role: "timer", "aria-live": "off" }, "--:--");
840
+ this.leftEl = el("span", { class: "mono", role: "timer", "aria-live": "off" }, "-:--");
810
841
  p.append(this.leftEl);
811
842
  if (this.left < 0) p.hidden = true;
812
843
  return p;
@@ -819,8 +850,9 @@ var EidLoginButton = class extends Base {
819
850
  this.leftEl.parentElement.hidden = false;
820
851
  const m = Math.floor(this.left / 60);
821
852
  const s = this.left % 60;
822
- this.leftEl.textContent = `${m < 10 ? "0" : ""}${m}:${s < 10 ? "0" : ""}${s}`;
823
- if (this.left === 60) this.say(this.t.minute);
853
+ this.leftEl.textContent = `${m}:${s < 10 ? "0" : ""}${s}`;
854
+ this.leftEl.parentElement.classList.toggle("low", this.left <= 30);
855
+ if (this.left > 0 && s === 0 && this.left !== seconds) this.say(this.t.minutes(m));
824
856
  };
825
857
  render();
826
858
  if (seconds < 0) return;
@@ -830,6 +862,24 @@ var EidLoginButton = class extends Base {
830
862
  if (this.left <= 0) this.end("expired");
831
863
  }, 1e3);
832
864
  }
865
+ /** «Регистрийн дугаараар нэвтрэх» товч + ижил цонхонд доошоо дэлгэгдэх push хэсэг (QR, код харагдсаар; QR poll үргэлжилнэ). */
866
+ pushToggle() {
867
+ const toggle = el("button", { type: "button", class: "btn btn-outline disc-btn", "aria-expanded": "false", "aria-controls": "eid-push" });
868
+ toggle.append(el("span", {}, this.t.usePush), chevron());
869
+ const disc = el("div", { class: "disc", id: "eid-push" });
870
+ const inner = el("div", { inert: "" });
871
+ const form = this.pushForm();
872
+ inner.append(form);
873
+ disc.append(inner);
874
+ toggle.addEventListener("click", () => {
875
+ const open = toggle.getAttribute("aria-expanded") !== "true";
876
+ toggle.setAttribute("aria-expanded", String(open));
877
+ disc.classList.toggle("open", open);
878
+ inner.toggleAttribute("inert", !open);
879
+ if (open) form.querySelector("input")?.focus();
880
+ });
881
+ return [toggle, disc];
882
+ }
833
883
  pushForm() {
834
884
  const t = this.t;
835
885
  const form = el("form", { novalidate: "" });
@@ -898,14 +948,14 @@ var EidLoginButton = class extends Base {
898
948
  showWait(s) {
899
949
  const t = this.t;
900
950
  const c = el("div", { class: "center" });
901
- c.append(el("h2", { class: "sr-only" }, t.waiting));
902
- const vc = el("p", { class: "vc", tabindex: "-1", "aria-describedby": "eid-wait" }, s.vc ?? "");
951
+ c.append(el("h2", { class: "sr-only" }, t.waiting), el("p", { class: "vc-label", id: "eid-vc-label" }, t.vcLabel));
952
+ const vc = el("p", { class: "vc", tabindex: "-1", "aria-labelledby": "eid-vc-label", "aria-describedby": "eid-wait" }, s.vc ?? "");
903
953
  const pending = el("p", { class: "pending" });
904
954
  pending.append(el("span", { class: "spinner", "aria-hidden": "true" }), el("span", {}, t.waiting));
905
955
  const back = el("button", { type: "button", class: "btn btn-outline" }, t.back);
906
956
  back.addEventListener("click", () => this.open());
907
957
  this.netwarn = el("p", { class: "netwarn", role: "status", hidden: "" }, t.offline);
908
- c.append(vc, el("p", { class: "text", id: "eid-wait" }, t.waitText), pending, back, this.netwarn, this.meta());
958
+ c.append(vc, this.meta(), el("p", { class: "text", id: "eid-wait" }, t.waitText), pending, back, this.netwarn);
909
959
  this.view(c);
910
960
  this.startTimer(s.expiresIn ?? -1);
911
961
  vc.focus();
package/dist/button.d.cts CHANGED
@@ -78,6 +78,8 @@ declare class EidLoginButton extends Base {
78
78
  private leftEl;
79
79
  private meta;
80
80
  private startTimer;
81
+ /** «Регистрийн дугаараар нэвтрэх» товч + ижил цонхонд доошоо дэлгэгдэх push хэсэг (QR, код харагдсаар; QR poll үргэлжилнэ). */
82
+ private pushToggle;
81
83
  private pushForm;
82
84
  private showWait;
83
85
  private schedule;
package/dist/button.d.ts CHANGED
@@ -78,6 +78,8 @@ declare class EidLoginButton extends Base {
78
78
  private leftEl;
79
79
  private meta;
80
80
  private startTimer;
81
+ /** «Регистрийн дугаараар нэвтрэх» товч + ижил цонхонд доошоо дэлгэгдэх push хэсэг (QR, код харагдсаар; QR poll үргэлжилнэ). */
82
+ private pushToggle;
81
83
  private pushForm;
82
84
  private showWait;
83
85
  private schedule;
package/dist/button.js CHANGED
@@ -368,8 +368,9 @@ var T = {
368
368
  loading: "\u0410\u0447\u0430\u0430\u043B\u0436 \u0431\u0430\u0439\u043D\u0430",
369
369
  qrAlt: "\u041D\u044D\u0432\u0442\u0440\u044D\u0445 QR \u043A\u043E\u0434 \u2014 \u0443\u0442\u0441\u0430\u043D \u0434\u044D\u044D\u0440\u0445 eID Mongolia \u0430\u043F\u043F\u0430\u0430\u0440 \u0443\u043D\u0448\u0443\u0443\u043B\u043D\u0430",
370
370
  steps: ["\u0423\u0442\u0441\u0430\u043D \u0434\u044D\u044D\u0440\u0445 eID Mongolia \u0430\u043F\u043F\u044B\u0433 \u043D\u044D\u044D\u043D\u044D", "QR \u043A\u043E\u0434\u044B\u0433 \u0443\u043D\u0448\u0443\u0443\u043B\u043D\u0430", "PIN1 \u043A\u043E\u0434\u043E\u043E \u043E\u0440\u0443\u0443\u043B\u043D\u0430"],
371
- code: "\u0423\u0442\u0441\u0430\u043D \u0434\u044D\u044D\u0440\u0445 \u043A\u043E\u0434:",
371
+ vcLabel: "\u0411\u0430\u0442\u0430\u043B\u0433\u0430\u0430\u0436\u0443\u0443\u043B\u0430\u0445 \u043A\u043E\u0434",
372
372
  or: "\u044D\u0441\u0432\u044D\u043B",
373
+ usePush: "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0439\u043D \u0434\u0443\u0433\u0430\u0430\u0440\u0430\u0430\u0440 \u043D\u044D\u0432\u0442\u0440\u044D\u0445",
373
374
  idLabel: "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0439\u043D \u0434\u0443\u0433\u0430\u0430\u0440",
374
375
  idHint: "\u0423\u0442\u0441\u0430\u043D\u0434 \u0442\u0430\u043D\u044C \u043C\u044D\u0434\u044D\u0433\u0434\u044D\u043B \u043E\u0447\u043D\u043E. \u0418\u0440\u0433\u044D\u043D\u0438\u0439 12 \u043E\u0440\u043E\u043D\u0442\u043E\u0439 \u0434\u0443\u0433\u0430\u0430\u0440\u0430\u0430\u0440 \u0447 \u0431\u043E\u043B\u043D\u043E.",
375
376
  idEmpty: "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0439\u043D \u0434\u0443\u0433\u0430\u0430\u0440 \u043E\u0440\u0443\u0443\u043B\u043D\u0430 \u0443\u0443",
@@ -382,7 +383,7 @@ var T = {
382
383
  waitText: "\u0423\u0442\u0441\u0430\u043D \u0434\u044D\u044D\u0440\u0445 \u043A\u043E\u0434 \u0438\u0436\u0438\u043B \u0431\u043E\u043B PIN1 \u043E\u0440\u0443\u0443\u043B\u043D\u0430 \u0443\u0443. \u0418\u0436\u0438\u043B \u0431\u0438\u0448 \u0431\u043E\u043B \u0442\u0430\u0442\u0433\u0430\u043B\u0437\u0430\u043D\u0430 \u0443\u0443.",
383
384
  waiting: "\u0425\u04AF\u043B\u044D\u044D\u0436 \u0431\u0430\u0439\u043D\u0430",
384
385
  back: "\u0411\u0443\u0446\u0430\u0445",
385
- left: "\u04AE\u043B\u0434\u0441\u044D\u043D \u0445\u0443\u0433\u0430\u0446\u0430\u0430",
386
+ left: "\u0425\u04AF\u0441\u044D\u043B\u0442\u0438\u0439\u043D \u0445\u0443\u0433\u0430\u0446\u0430\u0430:",
386
387
  stale: "QR \u043A\u043E\u0434 \u0445\u0443\u0443\u0447\u0438\u0440\u0441\u0430\u043D",
387
388
  refresh: "\u0428\u0438\u043D\u044D\u0447\u043B\u044D\u0445",
388
389
  offline: "\u0425\u043E\u043B\u0431\u043E\u043B\u0442 \u0442\u0430\u0441\u0430\u0440\u0441\u0430\u043D. \u0418\u043D\u0442\u0435\u0440\u043D\u044D\u0442\u044D\u044D \u0448\u0430\u043B\u0433\u0430\u043D\u0430 \u0443\u0443 \u2014 \u0434\u0430\u0445\u0438\u043D \u0445\u043E\u043B\u0431\u043E\u0433\u0434\u043E\u0445\u044B\u0433 \u043E\u0440\u043E\u043B\u0434\u043E\u0436 \u0431\u0430\u0439\u043D\u0430.",
@@ -396,7 +397,7 @@ var T = {
396
397
  retry: "\u0414\u0430\u0445\u0438\u043D \u043E\u0440\u043E\u043B\u0434\u043E\u0445",
397
398
  success: "\u0410\u043C\u0436\u0438\u043B\u0442\u0442\u0430\u0439 \u043D\u044D\u0432\u0442\u044D\u0440\u043B\u044D\u044D",
398
399
  sent: (vc) => `\u041C\u044D\u0434\u044D\u0433\u0434\u044D\u043B \u0438\u043B\u0433\u044D\u044D\u043B\u044D\u044D. \u0423\u0442\u0441\u0430\u043D \u0434\u044D\u044D\u0440\u0445 \u043A\u043E\u0434 ${vc.split("").join(" ")}`,
399
- minute: "1 \u043C\u0438\u043D\u0443\u0442 \u04AF\u043B\u0434\u043B\u044D\u044D"
400
+ minutes: (n) => `${n} \u043C\u0438\u043D\u0443\u0442 \u04AF\u043B\u0434\u043B\u044D\u044D`
400
401
  },
401
402
  en: {
402
403
  button: "Log in with eID",
@@ -405,8 +406,9 @@ var T = {
405
406
  loading: "Loading",
406
407
  qrAlt: "Login QR code \u2014 scan it with the eID Mongolia app on your phone",
407
408
  steps: ["Open the eID Mongolia app on your phone", "Scan the QR code", "Enter your PIN1"],
408
- code: "Code on your phone:",
409
+ vcLabel: "Verification code",
409
410
  or: "or",
411
+ usePush: "Log in with registration number",
410
412
  idLabel: "Registration number",
411
413
  idHint: "A notification will be sent to your phone. The 12-digit civil ID also works.",
412
414
  idEmpty: "Enter your registration number",
@@ -419,7 +421,7 @@ var T = {
419
421
  waitText: "If the code on your phone matches, enter PIN1. Otherwise decline.",
420
422
  waiting: "Waiting",
421
423
  back: "Back",
422
- left: "Time left",
424
+ left: "Request expires in:",
423
425
  stale: "QR code expired",
424
426
  refresh: "Refresh",
425
427
  offline: "Connection lost. Check your internet \u2014 reconnecting.",
@@ -433,7 +435,7 @@ var T = {
433
435
  retry: "Try again",
434
436
  success: "Logged in",
435
437
  sent: (vc) => `Notification sent. Code on your phone ${vc.split("").join(" ")}`,
436
- minute: "1 minute left"
438
+ minutes: (n) => `${n} minute${n === 1 ? "" : "s"} left`
437
439
  }
438
440
  };
439
441
  var EID_COLOR_TOKENS = [
@@ -499,7 +501,8 @@ h2, p, ol { margin: 0; }
499
501
  .qr.stale svg { opacity: 0.12; }
500
502
  .qr-stale { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
501
503
  color: hsl(222 47% 11%); font-weight: 500; }
502
- .qr-vc { margin-top: 8px; text-align: center; font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
504
+ .qr-vc { margin-top: 12px; text-align: center; }
505
+ .vc-label { font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
503
506
  .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; letter-spacing: 0.08em; color: var(--_eid-fg-muted); }
504
507
  .steps { margin-top: 16px; padding: 0; list-style: none; counter-reset: st; display: grid; gap: 8px; }
505
508
  .steps li { counter-increment: st; display: flex; align-items: center; gap: 12px; }
@@ -507,6 +510,13 @@ h2, p, ol { margin: 0; }
507
510
  color: var(--_eid-accent-subtle-fg); display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: 600; }
508
511
  .or { display: flex; align-items: center; gap: 12px; margin: 24px 0; color: var(--_eid-fg-subtle); font-size: 0.8125rem; }
509
512
  .or::before, .or::after { content: ""; flex: 1; height: 1px; background: var(--_eid-border); }
513
+ .disc-btn .chev { width: 16px; height: 16px; flex: none; transition: transform 200ms cubic-bezier(0,0,0.2,1); }
514
+ .disc-btn[aria-expanded="true"] .chev { transform: rotate(180deg); }
515
+ .disc { margin: 0 -4px; display: grid; grid-template-rows: 0fr; transition: grid-template-rows 200ms cubic-bezier(0,0,0.2,1); }
516
+ .disc.open { grid-template-rows: 1fr; }
517
+ .disc > div { min-height: 0; overflow: hidden; visibility: hidden; transition: visibility 0s linear 200ms; }
518
+ .disc.open > div { visibility: visible; transition-delay: 0s; }
519
+ .disc form { padding: 16px 4px 4px; }
510
520
  .field { display: grid; gap: 8px; }
511
521
  .field label { font-weight: 500; }
512
522
  .field input { width: 100%; height: 44px; padding: 0 12px; border: 1px solid var(--_eid-border-strong); border-radius: var(--_eid-radius);
@@ -517,7 +527,8 @@ h2, p, ol { margin: 0; }
517
527
  .err { font-size: 0.8125rem; color: var(--_eid-danger-fg); font-weight: 500; }
518
528
  .push { margin-top: 16px; }
519
529
  .center { text-align: center; }
520
- .vc { font: 600 2.25rem/2.5rem ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.16em; padding-left: 0.16em; }
530
+ .vc { font: 700 2rem/2.5rem ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; letter-spacing: 0.16em;
531
+ padding-left: 0.16em; color: var(--_eid-fg); }
521
532
  .vc:focus, h2:focus { outline: none; }
522
533
  .text { margin-top: 12px; color: var(--_eid-fg-muted); }
523
534
  .pending { margin-top: 16px; display: inline-flex; align-items: center; gap: 8px; color: var(--_eid-fg-muted); }
@@ -526,7 +537,8 @@ h2, p, ol { margin: 0; }
526
537
  @keyframes spin { to { transform: rotate(360deg); } }
527
538
  .netwarn { margin-top: 16px; padding: 8px 12px; border: 1px solid var(--_eid-warning-border); border-radius: var(--_eid-radius); background: var(--_eid-warning-bg);
528
539
  color: var(--_eid-warning-fg); font-size: 0.8125rem; }
529
- .meta { margin-top: 16px; font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
540
+ .meta { margin-top: 4px; text-align: center; font-size: 0.8125rem; color: var(--_eid-fg-subtle); }
541
+ .meta.low, .meta.low .mono { color: var(--_eid-warning-fg); }
530
542
  @media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition: none !important; }
531
543
  .spinner { animation: none; border-right-color: currentColor; opacity: 0.6; } }
532
544
  `;
@@ -549,6 +561,24 @@ function el(tag, attrs = {}, text) {
549
561
  if (text != null) e.textContent = text;
550
562
  return e;
551
563
  }
564
+ function chevron() {
565
+ const NS = "http://www.w3.org/2000/svg";
566
+ const svg = document.createElementNS(NS, "svg");
567
+ for (const [k, v] of Object.entries({
568
+ class: "chev",
569
+ viewBox: "0 0 24 24",
570
+ fill: "none",
571
+ stroke: "currentColor",
572
+ "stroke-width": "2",
573
+ "stroke-linecap": "round",
574
+ "stroke-linejoin": "round",
575
+ "aria-hidden": "true"
576
+ })) svg.setAttribute(k, v);
577
+ const path = document.createElementNS(NS, "path");
578
+ path.setAttribute("d", "m6 9 6 6 6-6");
579
+ svg.append(path);
580
+ return svg;
581
+ }
552
582
  var Base = typeof HTMLElement === "undefined" ? class {
553
583
  } : HTMLElement;
554
584
  var EidLoginButton = class extends Base {
@@ -681,7 +711,7 @@ var EidLoginButton = class extends Base {
681
711
  return;
682
712
  }
683
713
  if (e.key !== "Tab" || !this.backdrop) return;
684
- const items = Array.from(this.backdrop.querySelectorAll(FOCUSABLE)).filter((n) => !n.closest("[hidden]"));
714
+ const items = Array.from(this.backdrop.querySelectorAll(FOCUSABLE)).filter((n) => !n.closest("[hidden], [inert]"));
685
715
  if (items.length === 0) return;
686
716
  const first = items[0];
687
717
  const last = items[items.length - 1];
@@ -763,21 +793,22 @@ var EidLoginButton = class extends Base {
763
793
  if (s.qr) this.drawQr(s.qr);
764
794
  const nodes = [this.qrBox];
765
795
  if (s.vc) {
766
- const vc = el("p", { class: "qr-vc" }, t.code + " ");
767
- vc.append(el("span", { class: "mono" }, s.vc));
796
+ const vc = el("div", { class: "qr-vc" });
797
+ vc.append(el("p", { class: "vc-label", id: "eid-vc-label" }, t.vcLabel), el("p", { class: "vc" }, s.vc));
768
798
  nodes.push(vc);
769
799
  }
800
+ nodes.push(this.meta());
770
801
  const steps = el("ol", { class: "steps" });
771
802
  for (const st of t.steps) steps.append(el("li", {}, st));
772
- nodes.push(steps, el("div", { class: "or", role: "separator", "aria-label": t.or }, t.or), this.pushForm());
803
+ nodes.push(steps, el("div", { class: "or", role: "separator", "aria-label": t.or }, t.or), ...this.pushToggle());
773
804
  this.netwarn = el("p", { class: "netwarn", role: "status", hidden: "" }, t.offline);
774
- nodes.push(this.netwarn, this.meta());
805
+ nodes.push(this.netwarn);
775
806
  this.view(...nodes);
776
807
  }
777
808
  leftEl = null;
778
809
  meta() {
779
810
  const p = el("p", { class: "meta" }, this.t.left + " ");
780
- this.leftEl = el("span", { class: "mono", role: "timer", "aria-live": "off" }, "--:--");
811
+ this.leftEl = el("span", { class: "mono", role: "timer", "aria-live": "off" }, "-:--");
781
812
  p.append(this.leftEl);
782
813
  if (this.left < 0) p.hidden = true;
783
814
  return p;
@@ -790,8 +821,9 @@ var EidLoginButton = class extends Base {
790
821
  this.leftEl.parentElement.hidden = false;
791
822
  const m = Math.floor(this.left / 60);
792
823
  const s = this.left % 60;
793
- this.leftEl.textContent = `${m < 10 ? "0" : ""}${m}:${s < 10 ? "0" : ""}${s}`;
794
- if (this.left === 60) this.say(this.t.minute);
824
+ this.leftEl.textContent = `${m}:${s < 10 ? "0" : ""}${s}`;
825
+ this.leftEl.parentElement.classList.toggle("low", this.left <= 30);
826
+ if (this.left > 0 && s === 0 && this.left !== seconds) this.say(this.t.minutes(m));
795
827
  };
796
828
  render();
797
829
  if (seconds < 0) return;
@@ -801,6 +833,24 @@ var EidLoginButton = class extends Base {
801
833
  if (this.left <= 0) this.end("expired");
802
834
  }, 1e3);
803
835
  }
836
+ /** «Регистрийн дугаараар нэвтрэх» товч + ижил цонхонд доошоо дэлгэгдэх push хэсэг (QR, код харагдсаар; QR poll үргэлжилнэ). */
837
+ pushToggle() {
838
+ const toggle = el("button", { type: "button", class: "btn btn-outline disc-btn", "aria-expanded": "false", "aria-controls": "eid-push" });
839
+ toggle.append(el("span", {}, this.t.usePush), chevron());
840
+ const disc = el("div", { class: "disc", id: "eid-push" });
841
+ const inner = el("div", { inert: "" });
842
+ const form = this.pushForm();
843
+ inner.append(form);
844
+ disc.append(inner);
845
+ toggle.addEventListener("click", () => {
846
+ const open = toggle.getAttribute("aria-expanded") !== "true";
847
+ toggle.setAttribute("aria-expanded", String(open));
848
+ disc.classList.toggle("open", open);
849
+ inner.toggleAttribute("inert", !open);
850
+ if (open) form.querySelector("input")?.focus();
851
+ });
852
+ return [toggle, disc];
853
+ }
804
854
  pushForm() {
805
855
  const t = this.t;
806
856
  const form = el("form", { novalidate: "" });
@@ -869,14 +919,14 @@ var EidLoginButton = class extends Base {
869
919
  showWait(s) {
870
920
  const t = this.t;
871
921
  const c = el("div", { class: "center" });
872
- c.append(el("h2", { class: "sr-only" }, t.waiting));
873
- const vc = el("p", { class: "vc", tabindex: "-1", "aria-describedby": "eid-wait" }, s.vc ?? "");
922
+ c.append(el("h2", { class: "sr-only" }, t.waiting), el("p", { class: "vc-label", id: "eid-vc-label" }, t.vcLabel));
923
+ const vc = el("p", { class: "vc", tabindex: "-1", "aria-labelledby": "eid-vc-label", "aria-describedby": "eid-wait" }, s.vc ?? "");
874
924
  const pending = el("p", { class: "pending" });
875
925
  pending.append(el("span", { class: "spinner", "aria-hidden": "true" }), el("span", {}, t.waiting));
876
926
  const back = el("button", { type: "button", class: "btn btn-outline" }, t.back);
877
927
  back.addEventListener("click", () => this.open());
878
928
  this.netwarn = el("p", { class: "netwarn", role: "status", hidden: "" }, t.offline);
879
- c.append(vc, el("p", { class: "text", id: "eid-wait" }, t.waitText), pending, back, this.netwarn, this.meta());
929
+ c.append(vc, this.meta(), el("p", { class: "text", id: "eid-wait" }, t.waitText), pending, back, this.netwarn);
880
930
  this.view(c);
881
931
  this.startTimer(s.expiresIn ?? -1);
882
932
  vc.focus();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gerege-systems/eid-mongolia-sdk",
3
- "version": "0.15.2",
3
+ "version": "0.15.3",
4
4
  "description": "e-ID Mongolia Relying Party SDK — иргэнээр утсаар нь нэвтрэлт ба хууль ёсны цахим гарын үсэг (RP-API v3, Smart-ID нийцтэй).",
5
5
  "license": "MIT",
6
6
  "type": "module",