@gerege-systems/eid-mongolia-sdk 0.14.0 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -4
- package/dist/{browser-C6JHm0lR.d.cts → browser-CSZjH9Xs.d.cts} +65 -1
- package/dist/{browser-C6JHm0lR.d.ts → browser-CSZjH9Xs.d.ts} +65 -1
- package/dist/browser.d.cts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +1 -1
- package/dist/button.cjs +72 -19
- package/dist/button.d.cts +17 -3
- package/dist/button.d.ts +17 -3
- package/dist/button.js +70 -18
- package/dist/{chunk-QPJDZLHR.js → chunk-DXVHOZO7.js} +9 -0
- package/dist/index.cjs +281 -28
- package/dist/index.d.cts +136 -8
- package/dist/index.d.ts +136 -8
- package/dist/index.js +270 -29
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -61,6 +61,7 @@ export const eidLogin = createEidLogin({
|
|
|
61
61
|
callbackUrl: "https://bank.example.mn/auth/eid/callback", // host нь RP-ийн callbackHosts-д; утасны урсгалд заавал
|
|
62
62
|
async onSuccess(id) {
|
|
63
63
|
// id.etsi (PNOMN-…) — хэрэглэгчийг ҮҮГЭЭР таньна; id.givenName/surname (латин), id.person (кирилл, байвал)
|
|
64
|
+
// id.sessionId, id.documentNumber, id.certificateDerB64 — дараагийн алхамд (гарын үсэг, гэрчилгээ холбох)
|
|
64
65
|
const user = await users.findOrCreateByEtsi(id.etsi);
|
|
65
66
|
const sid = await sessions.create(user.id); // ШИНЭ session (session fixation)
|
|
66
67
|
return {
|
|
@@ -70,6 +71,7 @@ export const eidLogin = createEidLogin({
|
|
|
70
71
|
},
|
|
71
72
|
// store: redisStore, // олон instance / serverless бол (анхдагч — процессын санах ой)
|
|
72
73
|
// rateLimit: (key) => redisLimiter.allow(key), // "start:<ip>", "push:<idCode>"; анхдагч push 3/мин нэг дугаарт
|
|
74
|
+
trustProxy: 1, // handle(Request)-д клиент IP: nginx/LB нэг hop бол 1 (доор «Клиент IP»)
|
|
73
75
|
});
|
|
74
76
|
```
|
|
75
77
|
|
|
@@ -82,6 +84,18 @@ export const GET = (req: Request) => eidLogin.handle(req);
|
|
|
82
84
|
export const POST = (req: Request) => eidLogin.handle(req);
|
|
83
85
|
```
|
|
84
86
|
|
|
87
|
+
**Клиент IP** (`start:<ip>` хязгаар). Web-standard `Request`-д IP байдаггүй — `handle(req)` ашиглаж байвал аль нэгийг өг:
|
|
88
|
+
|
|
89
|
+
- `trustProxy: n` — итгэмжит reverse proxy-ийн ард: `X-Forwarded-For`-ийн **баруунаас n** дахь утга (n = таны proxy-ийн тоо),
|
|
90
|
+
XFF байхгүй бол `X-Real-IP`. `true` = XFF-ийн зүүн утга (proxy нь клиентийн XFF-ийг дарж бичдэг үед л). Proxy-гүй
|
|
91
|
+
үед бүү асаа — header-ийг клиент хуурамчаар илгээнэ.
|
|
92
|
+
- `getClientIp: (req) => …` — платформын IP (Bun `server.requestIP(req)?.address`, Deno `info.remoteAddr.hostname`,
|
|
93
|
+
Vercel `req.headers.get("x-real-ip")`). `trustProxy`-оос давуу.
|
|
94
|
+
|
|
95
|
+
Аль нь ч үгүй бол IP-ээр хязгаарлахгүй (эхний хүсэлтэд `onError`-оор нэг удаа анхааруулна). `handleRequest`-д `req.ip`
|
|
96
|
+
(Express `req.ip`, `app.set("trust proxy", …)`-тэй) шууд хэрэглэгдэнэ. Анхдагч `rateLimit` зөвхөн `push:`-ийг хязгаарладаг —
|
|
97
|
+
`start:<ip>`-ийг өөрийн `rateLimit`-ээр (Redis г.м.) хязгаарлана. Go: `eidlogin.Config.ClientIP` (`eidlogin.ForwardedClientIP(n)`) — ижил дүрэм.
|
|
98
|
+
|
|
85
99
|
Express:
|
|
86
100
|
|
|
87
101
|
```ts
|
|
@@ -103,11 +117,37 @@ app.use("/auth/eid", express.json(), async (req, res) => {
|
|
|
103
117
|
</script>
|
|
104
118
|
```
|
|
105
119
|
|
|
106
|
-
эсвэл `mountEidLoginButton(el, { startUrl, statusUrl, lang: "en", successUrl, onSuccess, onError })`.
|
|
107
|
-
Атрибут: `lang` (`mn` | `en`), `mode` (`auto` анхдагч — утас → Web2App, бусад → QR цонх; `qr`; `app`),
|
|
120
|
+
эсвэл `mountEidLoginButton(el, { startUrl, statusUrl, lang: "en", theme: "auto", successUrl, onSuccess, onError })`.
|
|
121
|
+
Атрибут: `lang` (`mn` | `en`), `mode` (`auto` анхдагч — утас → Web2App, бусад → QR цонх; `qr`; `app`),
|
|
122
|
+
`theme` (`auto` анхдагч | `light` | `dark`), `success-url`.
|
|
108
123
|
Үйл явдал: `eid-login-success`, `eid-login-error` (`detail.error`: `USER_REFUSED…`, `VALIDATION`, `EXPIRED` …).
|
|
109
|
-
|
|
110
|
-
|
|
124
|
+
Гадны хамааралгүй (QR encoder дотроо), цонх нь focus trap, Esc, `aria-live` төлөвтэй.
|
|
125
|
+
|
|
126
|
+
**Дизайн, theme.** Дизайн нь eID нэвтрэх хуудсынх (лого, өнгө). Фонт нь хуудаснаас өвлөгдөнө (товч, цонх, input бүгд).
|
|
127
|
+
`theme="auto"` үед дараалал: хуудасны `html[data-theme="dark|light"]` → `html.dark` / `html.light` → OS
|
|
128
|
+
(`prefers-color-scheme`); хуудас theme солиход (MutationObserver) шууд дагана. `theme="light|dark"` нь хуудсыг давна.
|
|
129
|
+
Өнгө, радиусыг CSS хувьсагчаар (элемент дээр — shadow-ийн `:host`-оос давуу) дарна:
|
|
130
|
+
|
|
131
|
+
```css
|
|
132
|
+
eid-login-button {
|
|
133
|
+
--eid-accent: #0b5cff; --eid-accent-fg: #fff; /* үндсэн товч, QR-ийн алхмын тоо (--eid-accent-subtle, --eid-accent-subtle-fg) */
|
|
134
|
+
--eid-bg: #fff; --eid-bg-subtle: …; --eid-bg-muted: …; /* цонх, hover */
|
|
135
|
+
--eid-fg: …; --eid-fg-muted: …; --eid-fg-subtle: …; /* текст */
|
|
136
|
+
--eid-border: …; --eid-border-strong: …; --eid-ring: …; /* хүрээ, focus */
|
|
137
|
+
--eid-warning-bg: …; --eid-warning-border: …; --eid-warning-fg: …; --eid-danger-fg: …;
|
|
138
|
+
--eid-radius: 6px; --eid-radius-lg: 8px;
|
|
139
|
+
}
|
|
140
|
+
html[data-theme="dark"] eid-login-button { --eid-accent: …; } /* dark-д өөр өнгө бол */
|
|
141
|
+
eid-login-button::part(button) { width: 100%; } /* part: button, backdrop, dialog */
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**eID аппын доод хувилбар.** `flowType` бэхлэлт (device link v3) ба Web2App-ийн `userChallengeVerifier` нь
|
|
145
|
+
**iOS 2.2.3 (build 59)**, **Android 2.2.3 (versionCode 60)**-аас эхэлсэн. eID сервер 2026-10-07-оос legacy холбоосыг
|
|
146
|
+
бүрэн хассан (v3-гүй session-д утас `DEVICE_LINK_REQUIRED`) бөгөөд доод хувилбарыг `/mobile/app-config`-оор албадан
|
|
147
|
+
шинэчлүүлдэг: Android 2026-10-07-оос ≥ 70 (төхөөрөмжийн баталгаатай), 2026-10-14-ний байдлаар iOS ≥ 2.2.7,
|
|
148
|
+
Android ≥ 83 (`curl https://ca.eidmongolia.mn/mobile/app-config`). Иймд гарын үсэг зурж чадах бүх апп эдгээрийг дэмжинэ —
|
|
149
|
+
RP талд нэмэлт шалгалт хэрэггүй. Эх сурвалж: eID CA-ийн `docs/DEVICE_LINK_V3.md` §5, §7; changelog
|
|
150
|
+
`2026-10-02-ios-min-version-2-2-3`, `2026-10-07-android-min-70`.
|
|
111
151
|
|
|
112
152
|
**Аюулгүй байдал.**
|
|
113
153
|
|
|
@@ -120,6 +160,33 @@ app.use("/auth/eid", express.json(), async (req, res) => {
|
|
|
120
160
|
(store-ийн `delete` атомар байх ёстой — Redis `DEL`).
|
|
121
161
|
- `onSuccess`-ийн `data` хөтөчид очно — нууц бүү оруул. Иргэний бүртгэлтэй эсэхийг push алдаагаар задлахгүй (`eid_unavailable`).
|
|
122
162
|
- Олон instance / serverless (Vercel г.м.) дээр анхдагч санах ойн store ажиллахгүй — `EidLoginStore` (`get`/`set`/`delete`)-ийг Redis-ээр.
|
|
163
|
+
- `onSuccess` дуудагдахаас ӨМНӨ tx store-оос атомар устгагдана (нэг удаагийн баталгаа) — `onSuccess` дотор store-оос бүү
|
|
164
|
+
унш; хэрэгтэй бүхэн `identity`-д (`sessionId`, `documentNumber`, `certificateDerB64`, …).
|
|
165
|
+
|
|
166
|
+
**Redis store** (SDK нэмэлт хамааралгүй — жишээ нь `ioredis`):
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import Redis from "ioredis";
|
|
170
|
+
import type { EidLoginStore, EidLoginTx } from "@gerege-systems/eid-mongolia-sdk";
|
|
171
|
+
|
|
172
|
+
const redis = new Redis(process.env.REDIS_URL!);
|
|
173
|
+
const key = (sid: string) => `eid:login:${sid}`;
|
|
174
|
+
|
|
175
|
+
export const redisStore: EidLoginStore = {
|
|
176
|
+
async get(sid) {
|
|
177
|
+
const v = await redis.get(key(sid));
|
|
178
|
+
return v ? (JSON.parse(v) as EidLoginTx) : undefined;
|
|
179
|
+
},
|
|
180
|
+
async set(sid, tx, ttlMs) {
|
|
181
|
+
await redis.set(key(sid), JSON.stringify(tx), "PX", ttlMs);
|
|
182
|
+
},
|
|
183
|
+
async delete(sid) {
|
|
184
|
+
return (await redis.del(key(sid))) === 1; // DEL атомар — зэрэг хоёр status-ийн зөвхөн нэг нь onSuccess дуудна
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Tx-д `sessionSecret` (QR угсрах материал) байдаг — Redis-ийг сүлжээнээс хаалттай, TLS/AUTH-тай ашигла.
|
|
123
190
|
|
|
124
191
|
## 2. Нэвтрэлт (push)
|
|
125
192
|
|
|
@@ -201,6 +268,46 @@ eid.validator.validateSign(result, digest384, { hashAlgorithm: "SHA-384", expect
|
|
|
201
268
|
|
|
202
269
|
RAW_DIGEST_SIGNATURE-д `flowType` гарын үсэгт ордоггүй (зөвхөн digest зурагдана) — энэ нь RP-API хариуны тулгалт.
|
|
203
270
|
|
|
271
|
+
**QR / Web2App / App2App** — `eid.sign.deviceLinkByEtsi` / `deviceLinkByDocument` (`callbackUrl`-тай бол Web2App/App2App);
|
|
272
|
+
холбоосыг backend-д `buildDeviceLink(deviceLinkInput(s, "QR", "mon"))` (auth/cert session-д ч мөн). `s.sign`
|
|
273
|
+
(`SignContext`: digest, hash, алгоритм, interactions, callback)-ийг session-тэй хамт серверт хадгал.
|
|
274
|
+
|
|
275
|
+
**`expiresAt`** (ms epoch) — session дуусах мөч: CA-ийн session start хариуны `expiresAt` (RFC 3339; auth / sign /
|
|
276
|
+
certificate-choice бүгдэд, 2026-10-14-өөс). Хуучин CA (талбаргүй) үед sign / certificate-choice-д SDK тооцоолно — CA-ийн
|
|
277
|
+
анхдагч 600с (`DEFAULT_SIGN_SESSION_TTL_MS`; CA админ 60–1800с болгож болно → `signSessionTtlMs`), auth-д undefined.
|
|
278
|
+
Эцсийн үнэн нь `GET /session` (`TIMEOUT`).
|
|
279
|
+
|
|
280
|
+
**Session эхлэх алдаа** (`ApiError`-ийн дэд анги, `.code`-той): `NoSigningCertificateError` — 409
|
|
281
|
+
`NO_SIGNING_CERTIFICATE` (sign / certificate-choice start, `/pdf/sign/prepare`: иргэнд идэвхтэй төхөөрөмж / хүчинтэй
|
|
282
|
+
гарын үсгийн гэрчилгээ алга, session үүсээгүй); `CertChoiceInvalidError` — 409 `CERT_CHOICE_INVALID` (linked sign-ийн
|
|
283
|
+
certificate-choice хүчингүй / хуучирсан / ашиглагдсан → cert-choice-ийг дахин эхлүүл); `RequestInvalidError` — 400
|
|
284
|
+
`REQUEST_INVALID` (ж: linked-д `linkedSessionID` дутуу).
|
|
285
|
+
|
|
286
|
+
### 3.1. Гэрчилгээг урьдчилж мэдэх (client-side PAdES / CMS)
|
|
287
|
+
|
|
288
|
+
Гэрчилгээ DTBS-д (`signingCertificateV2`) ордог тул эхлээд certificate-choice, дараа нь digest-ийг **яг тэр төхөөрөмж
|
|
289
|
+
рүү (`documentNumber`)** зуруулна. Олон утастай иргэнд etsi-ээр эхлүүлбэл өөр утас зурж, гарын үсэг гэрчилгээтэйгээ
|
|
290
|
+
таарахгүй.
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
const cc = await eid.sign.certificateChoiceByEtsi("PNOMN-12345678"); // эсвэл certificateChoiceDeviceLink() (зөвхөн QR)
|
|
294
|
+
const signer = eid.validator.validateCertificateChoice(await eid.session.waitForResult(cc.sessionId), cc.sessionId);
|
|
295
|
+
const dtbs = buildDtbs(signer.certificate); // 32 байт SHA-256, стандарт base64
|
|
296
|
+
const s = await eid.sign.digestForSigner(signer, dtbs, ix, { fileName: "Гэрээ.pdf" }); // linked/{documentNumber}
|
|
297
|
+
// QR / Web2App: eid.sign.deviceLinkForSigner(signer, dtbs, ix, { callbackUrl })
|
|
298
|
+
const r = await eid.session.waitForResult(s.sessionId);
|
|
299
|
+
const sig = eid.validator.validateSign(r, s.sign!.digest, {
|
|
300
|
+
hashAlgorithm: s.sign!.hashAlgorithm,
|
|
301
|
+
expectedFlowType: "Notification",
|
|
302
|
+
expectedAlgorithm: s.sign!.signatureAlgorithm, // sha256WithRSAEncryption — PSS хариуг татгалзана
|
|
303
|
+
expectedCertificate: signer.certificate, // өөр гэрчилгээгээр зурсан бол татгалзана
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`digestForSigner` / `deviceLinkForSigner`-ийн анхдагч: `sha256WithRSAEncryption` + `SHA-256` (CA SplitKey RSA нь хүссэн
|
|
308
|
+
алгоритмаар, PKCS#1 v1.5; хуучин threshold-ECDSA credential нь үргэлж `ecdsa-with-SHA256` — `expectedAlgorithm` үүнийг
|
|
309
|
+
зөвхөн EC P-256 гэрчилгээтэй үед зөвшөөрнө). Digest-ийн урт hash-тай таарахгүй бол хүсэлт явахгүй.
|
|
310
|
+
|
|
204
311
|
## 3a. Байгууллагын нэрийн өмнөөс гарын үсэг (onBehalfOf)
|
|
205
312
|
|
|
206
313
|
Иргэн **өөрийн PIN2 гэрчилгээгээр** зурна; тухайн мөчид байгууллагыг төлөөлөх эрхтэйг eID **өөрийн бүртгэлээр**
|
|
@@ -421,6 +528,9 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
|
|
|
421
528
|
| `ForbiddenError` | 403 — IP allowlist / mTLS / RP-ийн эрх; onBehalfOf татгалзвал `.code` (`OnBehalfErrorCode`) |
|
|
422
529
|
| `SessionFailedError` | endResult ≠ OK (TIMEOUT, USER_REFUSED…) — `.endResult`-оор салга |
|
|
423
530
|
| `ValidationError` | cert chain/signature/level шалгалт бүтэлгүйтсэн |
|
|
531
|
+
| `NoSigningCertificateError` | 409 `NO_SIGNING_CERTIFICATE` — гарын үсгийн гэрчилгээ / төхөөрөмж алга (sign, cert-choice start, PDF prepare); `ApiError`-ийн дэд анги |
|
|
532
|
+
| `CertChoiceInvalidError` | 409 `CERT_CHOICE_INVALID` — linked sign-ийн certificate-choice хүчингүй; `ApiError`-ийн дэд анги |
|
|
533
|
+
| `RequestInvalidError` | 400 `REQUEST_INVALID` — хүсэлт буруу (ж: `linkedSessionID` дутуу); `ApiError`-ийн дэд анги |
|
|
424
534
|
| `ApiError` | бусад HTTP алдаа (`.status`, `.body`, серверийн `.code`, `.retryAfterMs`, `.headers`, `.retryable` — ж: 404 `ORG_NOT_FOUND`) |
|
|
425
535
|
| `ResponseTooLargeError` | хариу SDK-ийн хязгаараас их (JSON 4 MiB, PDF `pdfMaxBytes`) — `.op`, `.limit` |
|
|
426
536
|
| `NetworkError` | timeout/сүлжээ (`.op` — таних тэмдэггүй зам, `.timeout`) |
|
|
@@ -72,7 +72,43 @@ interface NotificationSession {
|
|
|
72
72
|
rpChallenge: string;
|
|
73
73
|
/** Auth session-д validator-т өгөх контекст (sign session-д undefined). */
|
|
74
74
|
acsp?: AcspContext;
|
|
75
|
+
/** Sign session-ий хүсэлтэд илгээсэн утгууд ({@link SignContext}); auth / certificate-choice-д undefined. */
|
|
76
|
+
sign?: SignContext;
|
|
77
|
+
/** Sign / certificate-choice session-ий ТООЦООЛСОН дуусах мөч (ms epoch) — {@link sessionExpiry}; auth-д undefined. */
|
|
78
|
+
expiresAt?: number;
|
|
75
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Sign session-ий хүсэлтэд илгээсэн утгууд ЯГ ХЭВЭЭР: `validateSign` (digest, hashAlgorithm, signatureAlgorithm) ба
|
|
82
|
+
* device link (`buildDeviceLink`: digest, relyingPartyName, interactions, initialCallbackUrl)-д. RP session-тэй хамт
|
|
83
|
+
* серверт хадгална.
|
|
84
|
+
*/
|
|
85
|
+
interface SignContext {
|
|
86
|
+
/** Илгээсэн digest (стандарт base64, ЯГ ХЭВЭЭР — device link-ийн authCode-д ордог). */
|
|
87
|
+
digest: string;
|
|
88
|
+
hashAlgorithm: HashAlgorithm;
|
|
89
|
+
signatureAlgorithm: SignatureAlgorithm;
|
|
90
|
+
relyingPartyName: string;
|
|
91
|
+
/** Илгээсэн interactions-ийн base64 МӨР. */
|
|
92
|
+
interactions: string;
|
|
93
|
+
initialCallbackUrl: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* CA-ийн SIGNATURE ба CERTIFICATE_CHOICE session-ий анхдагч хүчинтэй хугацаа (мс; `EID_SIGN_SESSION_TTL_SECONDS` = 600).
|
|
97
|
+
* CA-ийн админ «Системийн тохиргоо»-нд 60–1800 секундэд өөрчилж болно. CA 2026-10-14-өөс session start хариунд
|
|
98
|
+
* `expiresAt` (RFC 3339) буцаадаг — SDK түүнийг ашиглана; хуучин CA (талбаргүй) үед л `ClientConfig.signSessionTtlMs`-ээр
|
|
99
|
+
* тооцоолно ({@link startExpiry}).
|
|
100
|
+
*/
|
|
101
|
+
declare const DEFAULT_SIGN_SESSION_TTL_MS = 600000;
|
|
102
|
+
/**
|
|
103
|
+
* Session start-ын хариунаас тооцоолох дуусах мөч (ms epoch): хүсэлт илгээхээс ӨМНӨХ цаг + ttl. CA ExpiresAt-ийг өөрийн
|
|
104
|
+
* хүлээн авсан мөчөөс тооцдог тул энэ утга CA-ийнхаас ХЭЗЭЭ Ч хойно биш (консерватив). RP UI-ийн тоолуур / «дахин
|
|
105
|
+
* эхлүүлэх» шийдвэрт л — эцсийн үнэн нь `GET /session` (`TIMEOUT`).
|
|
106
|
+
*/
|
|
107
|
+
declare function sessionExpiry(sentAtMs: number, ttlMs?: number): number;
|
|
108
|
+
/** CA-ийн `expiresAt` (RFC 3339) → ms epoch; байхгүй / задрахгүй бол undefined. */
|
|
109
|
+
declare function parseExpiresAt(v: unknown): number | undefined;
|
|
110
|
+
/** Session start-ын дуусах мөч: CA-ийн `expiresAt` байвал түүнийг, үгүй бол {@link sessionExpiry} (тооцоолол). */
|
|
111
|
+
declare function startExpiry(raw: unknown, sentAtMs: number, ttlMs?: number): number;
|
|
76
112
|
/**
|
|
77
113
|
* Device-link (QR/Web2App/App2App) session. Иргэн QR уншиж эсвэл товч дарж нэгдэнэ.
|
|
78
114
|
* `sessionToken`, `sessionSecret`-ийг ЗӨВХӨН backend-д хадгална — браузер руу бүү гарга.
|
|
@@ -99,6 +135,17 @@ interface DeviceLinkSession {
|
|
|
99
135
|
* ЗӨВХӨН backend-д хадгална.
|
|
100
136
|
*/
|
|
101
137
|
receivedAt: number;
|
|
138
|
+
/** auth | sign | cert ({@link deviceLinkInput}-д). Хуучин хадгалсан session-д undefined = auth. */
|
|
139
|
+
sessionType?: "auth" | "sign" | "cert";
|
|
140
|
+
/** Sign session-д ({@link deviceLinkInput}, `validateSign`); бусад үед undefined. */
|
|
141
|
+
sign?: SignContext;
|
|
142
|
+
/** Certificate-choice session-ий хүсэлтэд илгээсэн нэр (cert холбоосын authCode-д). */
|
|
143
|
+
relyingPartyName?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Session дуусах мөч (ms epoch): CA-ийн start хариуны `expiresAt`; хуучин CA-д sign / certificate-choice-д тооцоолсон
|
|
146
|
+
* ({@link startExpiry}), auth-д undefined.
|
|
147
|
+
*/
|
|
148
|
+
expiresAt?: number;
|
|
102
149
|
}
|
|
103
150
|
/** Session poll-ийн нэгтгэсэн үр дүн (validator-т орох түүхий материал). */
|
|
104
151
|
interface SessionResult {
|
|
@@ -379,6 +426,23 @@ declare class ApiError extends EidError {
|
|
|
379
426
|
*/
|
|
380
427
|
get retryable(): boolean;
|
|
381
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* 409 `NO_SIGNING_CERTIFICATE` — иргэнд (баримтад) идэвхтэй төхөөрөмж / SIGN credential / хүчинтэй гарын үсгийн
|
|
431
|
+
* гэрчилгээ алга: sign ба certificate-choice session start, `/pdf/sign/prepare`. Session үүсээгүй. Танихгүй
|
|
432
|
+
* лавлагаанд ч ижил хариу (бүртгэл задлахгүй) — хэрэглэгчид «eID гарын үсгийн гэрчилгээгүй» гэж харуулна.
|
|
433
|
+
*/
|
|
434
|
+
declare class NoSigningCertificateError extends ApiError {
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* 409 `CERT_CHOICE_INVALID` — linked sign-ийн `linkedSessionID` (certificate-choice) хүчингүй: өөр RP, COMPLETE+OK биш,
|
|
438
|
+
* өөр documentNumber, 5 минутаас хуучин, гэрчилгээ солигдсон, эсвэл аль хэдийн ашиглагдсан. Certificate-choice-ийг
|
|
439
|
+
* дахин эхлүүлнэ.
|
|
440
|
+
*/
|
|
441
|
+
declare class CertChoiceInvalidError extends ApiError {
|
|
442
|
+
}
|
|
443
|
+
/** 400 `REQUEST_INVALID` — хүсэлтийн бие/талбар буруу (ж: linked sign-д `linkedSessionID` дутуу). Програмын алдаа. */
|
|
444
|
+
declare class RequestInvalidError extends ApiError {
|
|
445
|
+
}
|
|
382
446
|
/**
|
|
383
447
|
* CA-ийн түр (давтаж болох) алдааны кодууд — `ApiError.code`. PDF_PREPARE_BUSY (429 prepare, Retry-After 5),
|
|
384
448
|
* DOCUMENT_PENDING (503 document, 5), TSA_UNAVAILABLE (503, 30), LTV_UNAVAILABLE (503, 60), RATE_LIMITED (429 токен),
|
|
@@ -444,4 +508,4 @@ declare class ValidationError extends EidError {
|
|
|
444
508
|
declare class DeviceLinkError extends EidError {
|
|
445
509
|
}
|
|
446
510
|
|
|
447
|
-
export { type AcspContext as A,
|
|
511
|
+
export { type AcspContext as A, type SessionState as B, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type SignContext as G, type HashAlgorithm as H, type Interaction as I, type SignatureAlgorithmParameters as J, type SignatureProtocol as K, type TokenCertStatus as L, deviceLink as M, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PersonBlock as P, isMobileUserAgent as Q, type RpCredentials as R, type SignatureAlgorithm as S, type TokenCertSessionBlock as T, openOrShowQR as U, ValidationError as V, parseExpiresAt as W, parseRetryAfter as X, sessionExpiry as Y, startExpiry as Z, type SessionResult as a, type PdfSessionBlock as b, type RepresentationsResponse as c, ApiError as d, AuthenticationError as e, CertChoiceInvalidError as f, DEFAULT_SIGN_SESSION_TTL_MS as g, DeviceLinkError as h, type DeviceLinkInput as i, type EndResult as j, ForbiddenError as k, type HttpErrorInit as l, type NavigatorLike as m, NetworkError as n, NoSigningCertificateError as o, type OnBehalfErrorCode as p, type OpenOrShowQROptions as q, type OrgResult as r, type PdfDocumentStatus as s, type PdfSigner as t, RETRYABLE_ERROR_CODES as u, type Representation as v, RequestInvalidError as w, ResponseTooLargeError as x, type RetryableErrorCode as y, SessionFailedError as z };
|
|
@@ -72,7 +72,43 @@ interface NotificationSession {
|
|
|
72
72
|
rpChallenge: string;
|
|
73
73
|
/** Auth session-д validator-т өгөх контекст (sign session-д undefined). */
|
|
74
74
|
acsp?: AcspContext;
|
|
75
|
+
/** Sign session-ий хүсэлтэд илгээсэн утгууд ({@link SignContext}); auth / certificate-choice-д undefined. */
|
|
76
|
+
sign?: SignContext;
|
|
77
|
+
/** Sign / certificate-choice session-ий ТООЦООЛСОН дуусах мөч (ms epoch) — {@link sessionExpiry}; auth-д undefined. */
|
|
78
|
+
expiresAt?: number;
|
|
75
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Sign session-ий хүсэлтэд илгээсэн утгууд ЯГ ХЭВЭЭР: `validateSign` (digest, hashAlgorithm, signatureAlgorithm) ба
|
|
82
|
+
* device link (`buildDeviceLink`: digest, relyingPartyName, interactions, initialCallbackUrl)-д. RP session-тэй хамт
|
|
83
|
+
* серверт хадгална.
|
|
84
|
+
*/
|
|
85
|
+
interface SignContext {
|
|
86
|
+
/** Илгээсэн digest (стандарт base64, ЯГ ХЭВЭЭР — device link-ийн authCode-д ордог). */
|
|
87
|
+
digest: string;
|
|
88
|
+
hashAlgorithm: HashAlgorithm;
|
|
89
|
+
signatureAlgorithm: SignatureAlgorithm;
|
|
90
|
+
relyingPartyName: string;
|
|
91
|
+
/** Илгээсэн interactions-ийн base64 МӨР. */
|
|
92
|
+
interactions: string;
|
|
93
|
+
initialCallbackUrl: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* CA-ийн SIGNATURE ба CERTIFICATE_CHOICE session-ий анхдагч хүчинтэй хугацаа (мс; `EID_SIGN_SESSION_TTL_SECONDS` = 600).
|
|
97
|
+
* CA-ийн админ «Системийн тохиргоо»-нд 60–1800 секундэд өөрчилж болно. CA 2026-10-14-өөс session start хариунд
|
|
98
|
+
* `expiresAt` (RFC 3339) буцаадаг — SDK түүнийг ашиглана; хуучин CA (талбаргүй) үед л `ClientConfig.signSessionTtlMs`-ээр
|
|
99
|
+
* тооцоолно ({@link startExpiry}).
|
|
100
|
+
*/
|
|
101
|
+
declare const DEFAULT_SIGN_SESSION_TTL_MS = 600000;
|
|
102
|
+
/**
|
|
103
|
+
* Session start-ын хариунаас тооцоолох дуусах мөч (ms epoch): хүсэлт илгээхээс ӨМНӨХ цаг + ttl. CA ExpiresAt-ийг өөрийн
|
|
104
|
+
* хүлээн авсан мөчөөс тооцдог тул энэ утга CA-ийнхаас ХЭЗЭЭ Ч хойно биш (консерватив). RP UI-ийн тоолуур / «дахин
|
|
105
|
+
* эхлүүлэх» шийдвэрт л — эцсийн үнэн нь `GET /session` (`TIMEOUT`).
|
|
106
|
+
*/
|
|
107
|
+
declare function sessionExpiry(sentAtMs: number, ttlMs?: number): number;
|
|
108
|
+
/** CA-ийн `expiresAt` (RFC 3339) → ms epoch; байхгүй / задрахгүй бол undefined. */
|
|
109
|
+
declare function parseExpiresAt(v: unknown): number | undefined;
|
|
110
|
+
/** Session start-ын дуусах мөч: CA-ийн `expiresAt` байвал түүнийг, үгүй бол {@link sessionExpiry} (тооцоолол). */
|
|
111
|
+
declare function startExpiry(raw: unknown, sentAtMs: number, ttlMs?: number): number;
|
|
76
112
|
/**
|
|
77
113
|
* Device-link (QR/Web2App/App2App) session. Иргэн QR уншиж эсвэл товч дарж нэгдэнэ.
|
|
78
114
|
* `sessionToken`, `sessionSecret`-ийг ЗӨВХӨН backend-д хадгална — браузер руу бүү гарга.
|
|
@@ -99,6 +135,17 @@ interface DeviceLinkSession {
|
|
|
99
135
|
* ЗӨВХӨН backend-д хадгална.
|
|
100
136
|
*/
|
|
101
137
|
receivedAt: number;
|
|
138
|
+
/** auth | sign | cert ({@link deviceLinkInput}-д). Хуучин хадгалсан session-д undefined = auth. */
|
|
139
|
+
sessionType?: "auth" | "sign" | "cert";
|
|
140
|
+
/** Sign session-д ({@link deviceLinkInput}, `validateSign`); бусад үед undefined. */
|
|
141
|
+
sign?: SignContext;
|
|
142
|
+
/** Certificate-choice session-ий хүсэлтэд илгээсэн нэр (cert холбоосын authCode-д). */
|
|
143
|
+
relyingPartyName?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Session дуусах мөч (ms epoch): CA-ийн start хариуны `expiresAt`; хуучин CA-д sign / certificate-choice-д тооцоолсон
|
|
146
|
+
* ({@link startExpiry}), auth-д undefined.
|
|
147
|
+
*/
|
|
148
|
+
expiresAt?: number;
|
|
102
149
|
}
|
|
103
150
|
/** Session poll-ийн нэгтгэсэн үр дүн (validator-т орох түүхий материал). */
|
|
104
151
|
interface SessionResult {
|
|
@@ -379,6 +426,23 @@ declare class ApiError extends EidError {
|
|
|
379
426
|
*/
|
|
380
427
|
get retryable(): boolean;
|
|
381
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* 409 `NO_SIGNING_CERTIFICATE` — иргэнд (баримтад) идэвхтэй төхөөрөмж / SIGN credential / хүчинтэй гарын үсгийн
|
|
431
|
+
* гэрчилгээ алга: sign ба certificate-choice session start, `/pdf/sign/prepare`. Session үүсээгүй. Танихгүй
|
|
432
|
+
* лавлагаанд ч ижил хариу (бүртгэл задлахгүй) — хэрэглэгчид «eID гарын үсгийн гэрчилгээгүй» гэж харуулна.
|
|
433
|
+
*/
|
|
434
|
+
declare class NoSigningCertificateError extends ApiError {
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* 409 `CERT_CHOICE_INVALID` — linked sign-ийн `linkedSessionID` (certificate-choice) хүчингүй: өөр RP, COMPLETE+OK биш,
|
|
438
|
+
* өөр documentNumber, 5 минутаас хуучин, гэрчилгээ солигдсон, эсвэл аль хэдийн ашиглагдсан. Certificate-choice-ийг
|
|
439
|
+
* дахин эхлүүлнэ.
|
|
440
|
+
*/
|
|
441
|
+
declare class CertChoiceInvalidError extends ApiError {
|
|
442
|
+
}
|
|
443
|
+
/** 400 `REQUEST_INVALID` — хүсэлтийн бие/талбар буруу (ж: linked sign-д `linkedSessionID` дутуу). Програмын алдаа. */
|
|
444
|
+
declare class RequestInvalidError extends ApiError {
|
|
445
|
+
}
|
|
382
446
|
/**
|
|
383
447
|
* CA-ийн түр (давтаж болох) алдааны кодууд — `ApiError.code`. PDF_PREPARE_BUSY (429 prepare, Retry-After 5),
|
|
384
448
|
* DOCUMENT_PENDING (503 document, 5), TSA_UNAVAILABLE (503, 30), LTV_UNAVAILABLE (503, 60), RATE_LIMITED (429 токен),
|
|
@@ -444,4 +508,4 @@ declare class ValidationError extends EidError {
|
|
|
444
508
|
declare class DeviceLinkError extends EidError {
|
|
445
509
|
}
|
|
446
510
|
|
|
447
|
-
export { type AcspContext as A,
|
|
511
|
+
export { type AcspContext as A, type SessionState as B, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type SignContext as G, type HashAlgorithm as H, type Interaction as I, type SignatureAlgorithmParameters as J, type SignatureProtocol as K, type TokenCertStatus as L, deviceLink as M, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PersonBlock as P, isMobileUserAgent as Q, type RpCredentials as R, type SignatureAlgorithm as S, type TokenCertSessionBlock as T, openOrShowQR as U, ValidationError as V, parseExpiresAt as W, parseRetryAfter as X, sessionExpiry as Y, startExpiry as Z, type SessionResult as a, type PdfSessionBlock as b, type RepresentationsResponse as c, ApiError as d, AuthenticationError as e, CertChoiceInvalidError as f, DEFAULT_SIGN_SESSION_TTL_MS as g, DeviceLinkError as h, type DeviceLinkInput as i, type EndResult as j, ForbiddenError as k, type HttpErrorInit as l, type NavigatorLike as m, NetworkError as n, NoSigningCertificateError as o, type OnBehalfErrorCode as p, type OpenOrShowQROptions as q, type OrgResult as r, type PdfDocumentStatus as s, type PdfSigner as t, RETRYABLE_ERROR_CODES as u, type Representation as v, RequestInvalidError as w, ResponseTooLargeError as x, type RetryableErrorCode as y, SessionFailedError as z };
|
package/dist/browser.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { h as DeviceLinkError, i as DeviceLinkInput, D as DeviceLinkSession, E as EidError, m as NavigatorLike, q as OpenOrShowQROptions, M as deviceLink, Q as isMobileUserAgent, U as openOrShowQR } from './browser-CSZjH9Xs.cjs';
|
package/dist/browser.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { h as DeviceLinkError, i as DeviceLinkInput, D as DeviceLinkSession, E as EidError, m as NavigatorLike, q as OpenOrShowQROptions, M as deviceLink, Q as isMobileUserAgent, U as openOrShowQR } from './browser-CSZjH9Xs.js';
|
package/dist/browser.js
CHANGED
package/dist/button.cjs
CHANGED
|
@@ -22,7 +22,8 @@ var button_exports = {};
|
|
|
22
22
|
__export(button_exports, {
|
|
23
23
|
EidLoginButton: () => EidLoginButton,
|
|
24
24
|
defineEidLoginButton: () => defineEidLoginButton,
|
|
25
|
-
mountEidLoginButton: () => mountEidLoginButton
|
|
25
|
+
mountEidLoginButton: () => mountEidLoginButton,
|
|
26
|
+
resolveEidTheme: () => resolveEidTheme
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(button_exports);
|
|
28
29
|
|
|
@@ -464,7 +465,7 @@ var T = {
|
|
|
464
465
|
minute: "1 minute left"
|
|
465
466
|
}
|
|
466
467
|
};
|
|
467
|
-
var
|
|
468
|
+
var CSS_BASE = `
|
|
468
469
|
:host { display: inline-block;
|
|
469
470
|
--eid-bg: hsl(0 0% 100%); --eid-bg-subtle: hsl(210 40% 98%); --eid-bg-muted: hsl(210 40% 96%);
|
|
470
471
|
--eid-fg: hsl(222 47% 11%); --eid-fg-muted: hsl(215 19% 35%); --eid-fg-subtle: hsl(215 16% 45%);
|
|
@@ -472,22 +473,26 @@ var CSS = `
|
|
|
472
473
|
--eid-accent: oklch(0.534 0.2 259); --eid-accent-fg: #fff; --eid-accent-subtle: oklch(0.97 0.03 259);
|
|
473
474
|
--eid-accent-subtle-fg: oklch(0.474 0.19 259); --eid-ring: oklch(0.604 0.2 259);
|
|
474
475
|
--eid-warning-bg: hsl(48 100% 96%); --eid-warning-border: hsl(45 95% 78%); --eid-warning-fg: hsl(23 75% 33%);
|
|
475
|
-
--eid-danger-fg: hsl(0 74% 42%); --eid-radius: 6px; --eid-radius-lg: 8px; font-family: inherit; }
|
|
476
|
-
|
|
476
|
+
--eid-danger-fg: hsl(0 74% 42%); --eid-radius: 6px; --eid-radius-lg: 8px; font-family: inherit; color-scheme: light; }
|
|
477
|
+
button, input { font-family: inherit; }
|
|
478
|
+
`;
|
|
479
|
+
var DARK_CSS = `:host { color-scheme: dark;
|
|
477
480
|
--eid-bg: hsl(229 50% 6%); --eid-bg-subtle: hsl(222 47% 9%); --eid-bg-muted: hsl(217 33% 13%);
|
|
478
481
|
--eid-fg: hsl(210 40% 98%); --eid-fg-muted: hsl(213 27% 84%); --eid-fg-subtle: hsl(215 20% 65%);
|
|
479
482
|
--eid-border: hsl(217 33% 17%); --eid-border-strong: hsl(215 25% 27%);
|
|
480
483
|
--eid-accent: oklch(0.64 0.15 259); --eid-accent-fg: hsl(229 50% 6%); --eid-accent-subtle: oklch(0.24 0.06 259);
|
|
481
484
|
--eid-accent-subtle-fg: oklch(0.82 0.1 259); --eid-ring: oklch(0.68 0.15 259);
|
|
482
485
|
--eid-warning-bg: hsl(20 60% 10%); --eid-warning-border: hsl(22 70% 26%); --eid-warning-fg: hsl(40 92% 65%);
|
|
483
|
-
--eid-danger-fg: hsl(0 91% 71%); }
|
|
486
|
+
--eid-danger-fg: hsl(0 91% 71%); }
|
|
487
|
+
`;
|
|
488
|
+
var CSS_REST = `
|
|
484
489
|
*, *::before, *::after { box-sizing: border-box; }
|
|
485
490
|
[hidden] { display: none !important; }
|
|
486
491
|
h2, p, ol { margin: 0; }
|
|
487
492
|
:focus-visible { outline: 2px solid var(--eid-ring); outline-offset: 2px; }
|
|
488
493
|
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
|
489
494
|
.btn { display: inline-flex; width: 100%; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 0 20px;
|
|
490
|
-
border-radius: var(--eid-radius); font: 500 1rem
|
|
495
|
+
border-radius: var(--eid-radius); font-family: inherit; font-weight: 500; font-size: 1rem; line-height: 1.25; text-decoration: none; border: 1px solid transparent; cursor: pointer;
|
|
491
496
|
transition: background-color 160ms cubic-bezier(0,0,0.2,1); }
|
|
492
497
|
.btn:disabled { opacity: 0.6; cursor: progress; }
|
|
493
498
|
.btn-primary { background: var(--eid-accent); color: var(--eid-accent-fg); }
|
|
@@ -524,7 +529,7 @@ h2, p, ol { margin: 0; }
|
|
|
524
529
|
.field { display: grid; gap: 8px; }
|
|
525
530
|
.field label { font-weight: 500; }
|
|
526
531
|
.field input { width: 100%; height: 44px; padding: 0 12px; border: 1px solid var(--eid-border-strong); border-radius: var(--eid-radius);
|
|
527
|
-
background: var(--eid-bg); color: var(--eid-fg); font: 1rem
|
|
532
|
+
background: var(--eid-bg); color: var(--eid-fg); font-family: inherit; font-size: 1rem; line-height: 1.5rem; letter-spacing: 0.02em; text-transform: uppercase; }
|
|
528
533
|
.field input::placeholder { color: var(--eid-fg-subtle); text-transform: none; }
|
|
529
534
|
.field input[aria-invalid="true"] { border-color: var(--eid-danger-fg); }
|
|
530
535
|
.hint { font-size: 0.8125rem; color: var(--eid-fg-subtle); }
|
|
@@ -544,6 +549,17 @@ h2, p, ol { margin: 0; }
|
|
|
544
549
|
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition: none !important; }
|
|
545
550
|
.spinner { animation: none; border-right-color: currentColor; opacity: 0.6; } }
|
|
546
551
|
`;
|
|
552
|
+
var CSS = CSS_BASE + CSS_REST;
|
|
553
|
+
function resolveEidTheme(theme, doc = document) {
|
|
554
|
+
if (theme === "light" || theme === "dark") return theme;
|
|
555
|
+
const html = doc.documentElement;
|
|
556
|
+
const dt = html?.getAttribute("data-theme");
|
|
557
|
+
if (dt === "dark" || dt === "light") return dt;
|
|
558
|
+
if (html?.classList.contains("dark")) return "dark";
|
|
559
|
+
if (html?.classList.contains("light")) return "light";
|
|
560
|
+
const mq = doc.defaultView?.matchMedia?.("(prefers-color-scheme: dark)");
|
|
561
|
+
return mq?.matches ? "dark" : "light";
|
|
562
|
+
}
|
|
547
563
|
var ID_RE = /^([А-ЯЁӨҮ]{2}[0-9]{8}|[0-9]{8,14})$/;
|
|
548
564
|
var FOCUSABLE = 'button:not([disabled]), input:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])';
|
|
549
565
|
function el(tag, attrs = {}, text) {
|
|
@@ -555,7 +571,7 @@ function el(tag, attrs = {}, text) {
|
|
|
555
571
|
var Base = typeof HTMLElement === "undefined" ? class {
|
|
556
572
|
} : HTMLElement;
|
|
557
573
|
var EidLoginButton = class extends Base {
|
|
558
|
-
static observedAttributes = ["lang"];
|
|
574
|
+
static observedAttributes = ["lang", "theme"];
|
|
559
575
|
/** Тест/дотоод: гар утас эсэхийг дарж бичих. */
|
|
560
576
|
isMobile = () => isMobileUserAgent();
|
|
561
577
|
/** Тест/дотоод: шилжилт (location.assign). */
|
|
@@ -572,6 +588,10 @@ var EidLoginButton = class extends Base {
|
|
|
572
588
|
gen = 0;
|
|
573
589
|
// цонх нээх бүрд нэмэгдэнэ — хуучин fetch-ийн хариуг үл тооно
|
|
574
590
|
lastFocus = null;
|
|
591
|
+
darkStyle;
|
|
592
|
+
themeObs = null;
|
|
593
|
+
mq = null;
|
|
594
|
+
onScheme = () => this.applyTheme();
|
|
575
595
|
get t() {
|
|
576
596
|
return T[this.getAttribute("lang") === "en" ? "en" : "mn"];
|
|
577
597
|
}
|
|
@@ -579,21 +599,52 @@ var EidLoginButton = class extends Base {
|
|
|
579
599
|
return this.getAttribute(name) ?? def;
|
|
580
600
|
}
|
|
581
601
|
connectedCallback() {
|
|
582
|
-
if (this.root)
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
602
|
+
if (!this.root) {
|
|
603
|
+
this.root = this.attachShadow({ mode: "open" });
|
|
604
|
+
const style = el("style");
|
|
605
|
+
style.textContent = CSS;
|
|
606
|
+
this.darkStyle = el("style", { "data-eid-theme": "dark" });
|
|
607
|
+
this.trigger = el("button", { type: "button", class: "btn btn-outline trigger", part: "button", "aria-haspopup": "dialog" });
|
|
608
|
+
this.trigger.append(el("img", { src: EID_LOGO_DATA_URI, alt: "", width: "20", height: "20" }), el("span", {}, this.t.button));
|
|
609
|
+
this.trigger.addEventListener("click", () => this.start());
|
|
610
|
+
this.root.append(style, this.darkStyle, this.trigger);
|
|
611
|
+
}
|
|
612
|
+
this.watchTheme();
|
|
613
|
+
this.applyTheme();
|
|
614
|
+
}
|
|
615
|
+
attributeChangedCallback(name) {
|
|
616
|
+
if (name === "theme") {
|
|
617
|
+
if (this.root) this.applyTheme();
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
592
620
|
const label = this.trigger?.querySelector("span");
|
|
593
621
|
if (label) label.textContent = this.t.button;
|
|
594
622
|
}
|
|
595
623
|
disconnectedCallback() {
|
|
596
624
|
this.close();
|
|
625
|
+
this.themeObs?.disconnect();
|
|
626
|
+
this.themeObs = null;
|
|
627
|
+
this.mq?.removeEventListener?.("change", this.onScheme);
|
|
628
|
+
this.mq = null;
|
|
629
|
+
}
|
|
630
|
+
/** Одоогийн бодит схем (`theme` + auto үед хуудас/OS). */
|
|
631
|
+
get scheme() {
|
|
632
|
+
return resolveEidTheme(this.getAttribute("theme"));
|
|
633
|
+
}
|
|
634
|
+
applyTheme() {
|
|
635
|
+
const css = this.scheme === "dark" ? DARK_CSS : "";
|
|
636
|
+
if (this.darkStyle.textContent !== css) this.darkStyle.textContent = css;
|
|
637
|
+
}
|
|
638
|
+
/** auto үед хуудасны html[data-theme]/class ба OS-ийн схемийн өөрчлөлтийг дагана. */
|
|
639
|
+
watchTheme() {
|
|
640
|
+
if (!this.themeObs && typeof MutationObserver !== "undefined" && document.documentElement) {
|
|
641
|
+
this.themeObs = new MutationObserver(this.onScheme);
|
|
642
|
+
this.themeObs.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "class"] });
|
|
643
|
+
}
|
|
644
|
+
if (!this.mq && typeof matchMedia === "function") {
|
|
645
|
+
this.mq = matchMedia("(prefers-color-scheme: dark)");
|
|
646
|
+
this.mq.addEventListener?.("change", this.onScheme);
|
|
647
|
+
}
|
|
597
648
|
}
|
|
598
649
|
/** Товчны үйлдэл (программаар ч дуудаж болно). */
|
|
599
650
|
start() {
|
|
@@ -937,6 +988,7 @@ function mountEidLoginButton(target, opts) {
|
|
|
937
988
|
b.setAttribute("status-url", opts.statusUrl);
|
|
938
989
|
b.setAttribute("lang", opts.lang ?? "mn");
|
|
939
990
|
if (opts.mode) b.setAttribute("mode", opts.mode);
|
|
991
|
+
if (opts.theme) b.setAttribute("theme", opts.theme);
|
|
940
992
|
if (opts.successUrl) b.setAttribute("success-url", opts.successUrl);
|
|
941
993
|
if (opts.onSuccess) b.addEventListener("eid-login-success", (e) => opts.onSuccess(e.detail));
|
|
942
994
|
if (opts.onError) b.addEventListener("eid-login-error", (e) => opts.onError(e.detail.error));
|
|
@@ -948,5 +1000,6 @@ defineEidLoginButton();
|
|
|
948
1000
|
0 && (module.exports = {
|
|
949
1001
|
EidLoginButton,
|
|
950
1002
|
defineEidLoginButton,
|
|
951
|
-
mountEidLoginButton
|
|
1003
|
+
mountEidLoginButton,
|
|
1004
|
+
resolveEidTheme
|
|
952
1005
|
});
|
package/dist/button.d.cts
CHANGED
|
@@ -17,13 +17,18 @@ interface EidLoginButtonOptions {
|
|
|
17
17
|
lang?: Lang;
|
|
18
18
|
/** auto (анхдагч: утас → Web2App, бусад → QR цонх) | qr | app. */
|
|
19
19
|
mode?: "auto" | "qr" | "app";
|
|
20
|
+
/** auto (анхдагч: html[data-theme] / html.dark / prefers-color-scheme-ийг дагана) | light | dark. */
|
|
21
|
+
theme?: "auto" | "light" | "dark";
|
|
20
22
|
/** Амжилтын дараа (backend redirect өгөөгүй бол) шилжих URL. */
|
|
21
23
|
successUrl?: string;
|
|
22
24
|
onSuccess?: (s: EidLoginStatus) => void;
|
|
23
25
|
onError?: (error: string) => void;
|
|
24
26
|
}
|
|
27
|
+
type Scheme = "light" | "dark";
|
|
28
|
+
/** `theme` атрибутын утгыг (light | dark | auto) бодит схем болгоно. auto: html[data-theme] → html.dark/.light → OS. */
|
|
29
|
+
declare function resolveEidTheme(theme: string | null, doc?: Document): Scheme;
|
|
25
30
|
declare const Base: typeof HTMLElement;
|
|
26
|
-
/** `<eid-login-button>` — атрибут: start-url, status-url, lang (mn|en), mode (auto|qr|app), success-url. */
|
|
31
|
+
/** `<eid-login-button>` — атрибут: start-url, status-url, lang (mn|en), mode (auto|qr|app), theme (auto|light|dark), success-url. */
|
|
27
32
|
declare class EidLoginButton extends Base {
|
|
28
33
|
static readonly observedAttributes: string[];
|
|
29
34
|
/** Тест/дотоод: гар утас эсэхийг дарж бичих. */
|
|
@@ -41,11 +46,20 @@ declare class EidLoginButton extends Base {
|
|
|
41
46
|
private fails;
|
|
42
47
|
private gen;
|
|
43
48
|
private lastFocus;
|
|
49
|
+
private darkStyle;
|
|
50
|
+
private themeObs;
|
|
51
|
+
private mq;
|
|
52
|
+
private readonly onScheme;
|
|
44
53
|
private get t();
|
|
45
54
|
private attr;
|
|
46
55
|
connectedCallback(): void;
|
|
47
|
-
attributeChangedCallback(): void;
|
|
56
|
+
attributeChangedCallback(name: string): void;
|
|
48
57
|
disconnectedCallback(): void;
|
|
58
|
+
/** Одоогийн бодит схем (`theme` + auto үед хуудас/OS). */
|
|
59
|
+
get scheme(): Scheme;
|
|
60
|
+
private applyTheme;
|
|
61
|
+
/** auto үед хуудасны html[data-theme]/class ба OS-ийн схемийн өөрчлөлтийг дагана. */
|
|
62
|
+
private watchTheme;
|
|
49
63
|
/** Товчны үйлдэл (программаар ч дуудаж болно). */
|
|
50
64
|
start(): void;
|
|
51
65
|
private open;
|
|
@@ -76,4 +90,4 @@ declare function defineEidLoginButton(tag?: string): void;
|
|
|
76
90
|
/** Элементэд товч нэмнэ (framework-гүй хуудас, React/Vue-ийн ref). Үүссэн элементийг буцаана. */
|
|
77
91
|
declare function mountEidLoginButton(target: Element, opts: EidLoginButtonOptions): EidLoginButton;
|
|
78
92
|
|
|
79
|
-
export { EidLoginButton, type EidLoginButtonOptions, type EidLoginStatus, defineEidLoginButton, mountEidLoginButton };
|
|
93
|
+
export { EidLoginButton, type EidLoginButtonOptions, type EidLoginStatus, defineEidLoginButton, mountEidLoginButton, resolveEidTheme };
|