eimzo-sign-core 0.1.3 → 0.1.5

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
@@ -1,9 +1,13 @@
1
1
  # eimzo-sign-core
2
2
 
3
- E-IMZO bilan imzolash mantig'i — **oynasiz**, framework'siz.
4
- UI'ni o'zingiz qurasiz. Vue / React / oddiy JS — farqi yo'q.
3
+ O'zbekiston elektron raqamli imzosi (**E-IMZO**) bilan hujjat imzolash mantig'i —
4
+ **oynasiz, framework'siz**. Sertifikat ro'yxati va tugmalarni o'zingiz chizasiz,
5
+ bu paket E-IMZO dasturi bilan aloqani, timeout'larni, xatolar va seriya
6
+ tekshiruvini o'z zimmasiga oladi. React / Svelte / vanilla JS — farqi yo'q.
5
7
 
6
- Tayyor Vue oyna kerak bo'lsa: [`eimzo-sign-vue`](https://www.npmjs.com/package/eimzo-sign-vue).
8
+ > Vue uchun **tayyor imzolash oynasi** kerak bo'lsa
9
+ > [**eimzo-sign-vue**](https://www.npmjs.com/package/eimzo-sign-vue) (u shu
10
+ > paketni ichida ishlatadi).
7
11
 
8
12
  ---
9
13
 
@@ -82,7 +86,25 @@ try {
82
86
  | `EimzoClient.idCardCert()` | ID-karta uchun "sertifikat" obyekti |
83
87
 
84
88
  Yordamchilar: `sortCerts` (amaldagilar tepada), `certIsUsable`, `certIsExpired`,
85
- `certExpiresSoon`, `dedupeCerts`.
89
+ `certIsNotYetValid`, `certExpiresSoon`, `dedupeCerts`, `certOrg` (tashkilot nomi;
90
+ `"НЕ УКАЗАНО"` kabi E-IMZO placeholderlari bo'sh string), `certIsIndividual`.
91
+
92
+ ### `Cert` maydonlari
93
+
94
+ ```ts
95
+ interface Cert {
96
+ serialNumber: string
97
+ CN: string // egasi (F.I.Sh yoki rahbar)
98
+ TIN: string // STIR (INN)
99
+ PINFL: string // JSHSHIR — jismoniy shaxs id (bo'lmasligi mumkin)
100
+ UID: string
101
+ O: string // tashkilot; jismoniy shaxsda bo'sh yoki "НЕ УКАЗАНО"
102
+ T: string
103
+ validFrom: Date
104
+ validTo: Date
105
+ type: 'pfx' | 'ftjc' | 'idcard'
106
+ }
107
+ ```
86
108
 
87
109
  ### `signBatch` natijasi
88
110
 
@@ -95,6 +117,28 @@ Yordamchilar: `sortCerts` (amaldagilar tepada), `certIsUsable`, `certIsExpired`,
95
117
 
96
118
  ---
97
119
 
120
+ ## `SignFlow` — modal holat mashinasi
121
+
122
+ Framework-agnostik oqim: yuklash → sertifikat tanlash → imzolash → qisman
123
+ xato / qayta urinish → yakun. O'z UI'ingiz (Vue 2.7, React, …) shu holatga
124
+ qarab chiziladi.
125
+
126
+ ```ts
127
+ import { SignFlow } from 'eimzo-sign-core'
128
+
129
+ const flow = new SignFlow({ client, docs: [{ id: 'p1', title: "To'lov", content: xml }] })
130
+ flow.subscribe((s) => render(s)) // s.status, s.certs, s.docs, s.progress …
131
+ flow.selectCert(serial)
132
+ flow.confirm() // "Imzolash"
133
+ flow.cancel() // "Bekor qilish" / Esc
134
+
135
+ const { results } = await flow.promise // yoki reject: EimzoError (.code === 'E_CANCELLED')
136
+ ```
137
+
138
+ `eimzo-sign-vue` ning modali aynan shu ustiga qurilgan.
139
+
140
+ ---
141
+
98
142
  ## Sozlamalar
99
143
 
100
144
  ```ts
package/dist/index.cjs CHANGED
@@ -1,12 +1,5 @@
1
1
  'use strict';
2
2
 
3
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
- }) : x)(function(x) {
6
- if (typeof require !== "undefined") return require.apply(this, arguments);
7
- throw Error('Dynamic require of "' + x + '" is not supported');
8
- });
9
-
10
3
  // src/errors.ts
11
4
  var EimzoError = class _EimzoError extends Error {
12
5
  constructor(code, message, options) {
@@ -81,9 +74,6 @@ function withTimeout(timeoutMs, timeoutError, executor, signal) {
81
74
  var _Base64 = global.Base64;
82
75
  var version = "2.1.4";
83
76
  var buffer;
84
- if (typeof module !== "undefined" && module.exports) {
85
- buffer = __require("buffer").Buffer;
86
- }
87
77
  var b64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
88
78
  var b64tab = (function(bin) {
89
79
  var t = {};
@@ -229,9 +219,6 @@ function withTimeout(timeoutMs, timeoutError, executor, signal) {
229
219
  var _Base64 = global.Base64;
230
220
  var version = "2.1.4";
231
221
  var buffer;
232
- if (typeof module !== "undefined" && module.exports) {
233
- buffer = __require("buffer").Buffer;
234
- }
235
222
  var b64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
236
223
  var b64tab = (function(bin) {
237
224
  var t = {};
@@ -1060,6 +1047,7 @@ var EimzoClient = class {
1060
1047
  CN: "ID card",
1061
1048
  TIN: "",
1062
1049
  UID: "",
1050
+ PINFL: "",
1063
1051
  O: "",
1064
1052
  T: "",
1065
1053
  validFrom: /* @__PURE__ */ new Date(0),
@@ -1264,6 +1252,7 @@ function normalizeCert(raw) {
1264
1252
  CN: str(raw.CN),
1265
1253
  TIN: str(raw.TIN),
1266
1254
  UID: str(raw.UID),
1255
+ PINFL: str(raw.PINFL),
1267
1256
  O: str(raw.O),
1268
1257
  T: str(raw.T),
1269
1258
  validFrom: toDate(raw.validFrom),
@@ -1297,6 +1286,22 @@ function assertUniqueIds(docs) {
1297
1286
  function certIsExpired(cert, at = /* @__PURE__ */ new Date()) {
1298
1287
  return cert.validTo.getTime() < at.getTime();
1299
1288
  }
1289
+ var ORG_PLACEHOLDERS = /* @__PURE__ */ new Set([
1290
+ "\u041D\u0415 \u0423\u041A\u0410\u0417\u0410\u041D\u041E",
1291
+ "\u041D\u0415 \u0423\u041A\u0410\u0417\u0410\u041D\u0410",
1292
+ "NE UKAZANO",
1293
+ "KO'RSATILMAGAN",
1294
+ "\u041A\u040E\u0420\u0421\u0410\u0422\u0418\u041B\u041C\u0410\u0413\u0410\u041D",
1295
+ "-",
1296
+ ""
1297
+ ]);
1298
+ function certOrg(cert) {
1299
+ const o = (cert.O ?? "").trim();
1300
+ return ORG_PLACEHOLDERS.has(o.toUpperCase()) ? "" : o;
1301
+ }
1302
+ function certIsIndividual(cert) {
1303
+ return certOrg(cert) === "";
1304
+ }
1300
1305
  function certIsNotYetValid(cert, at = /* @__PURE__ */ new Date()) {
1301
1306
  return cert.validFrom.getTime() > at.getTime();
1302
1307
  }
@@ -1327,12 +1332,224 @@ function dedupeCerts(certs) {
1327
1332
  return out;
1328
1333
  }
1329
1334
 
1335
+ // src/sign-flow.ts
1336
+ var ID_CARD_SERIAL = "IDCARD";
1337
+ var SignFlow = class {
1338
+ constructor(deps) {
1339
+ this.listeners = /* @__PURE__ */ new Set();
1340
+ this.ac = new AbortController();
1341
+ this.finished = false;
1342
+ this.client = deps.client;
1343
+ this.autoInstall = deps.autoInstall ?? true;
1344
+ this.state = {
1345
+ status: "loading",
1346
+ certs: [],
1347
+ idCardAvailable: false,
1348
+ selectedSerial: null,
1349
+ docs: deps.docs.map((d) => ({
1350
+ id: d.id,
1351
+ title: d.title,
1352
+ content: d.content,
1353
+ preview: d.preview ?? [],
1354
+ status: "pending"
1355
+ })),
1356
+ progress: { done: 0, total: deps.docs.length }
1357
+ };
1358
+ this.promise = new Promise((resolve, reject) => {
1359
+ this.resolveOuter = resolve;
1360
+ this.rejectOuter = reject;
1361
+ });
1362
+ void this.load();
1363
+ }
1364
+ // ─── Kuzatish ──────────────────────────────────────────────────────────
1365
+ getState() {
1366
+ return this.state;
1367
+ }
1368
+ subscribe(listener) {
1369
+ this.listeners.add(listener);
1370
+ return () => this.listeners.delete(listener);
1371
+ }
1372
+ setState(patch) {
1373
+ this.state = { ...this.state, ...patch };
1374
+ for (const listener of this.listeners) listener(this.state);
1375
+ }
1376
+ // ─── Hosil qilingan qiymatlar ──────────────────────────────────────────
1377
+ get selectedCert() {
1378
+ const { selectedSerial, certs } = this.state;
1379
+ if (!selectedSerial) return null;
1380
+ if (selectedSerial === ID_CARD_SERIAL) return EimzoClient.idCardCert();
1381
+ return certs.find((c) => c.serialNumber === selectedSerial) ?? null;
1382
+ }
1383
+ get canConfirm() {
1384
+ return (this.state.status === "ready" || this.state.status === "partial") && this.selectedCert !== null && this.pendingDocs.length > 0;
1385
+ }
1386
+ /** Navbatdagi imzolанадиган hujjатlар (partial rejimда — faqat xatоликдагилар). */
1387
+ get pendingDocs() {
1388
+ return this.state.docs.filter((d) => d.status !== "done");
1389
+ }
1390
+ // ─── Harakatlar ────────────────────────────────────────────────────────
1391
+ async load() {
1392
+ this.setState({ status: "loading", fatal: void 0, lastError: void 0 });
1393
+ try {
1394
+ if (this.autoInstall) await this.client.install();
1395
+ const [rawCerts, idCard] = await Promise.all([
1396
+ this.client.listCerts(),
1397
+ this.client.isIdCardPlugged()
1398
+ ]);
1399
+ const certs = sortCerts(dedupeCerts(rawCerts));
1400
+ if (certs.length === 0 && !idCard) {
1401
+ this.fail(
1402
+ new EimzoError("E_NO_CERTS", "Imzolash uchun sertifikat topilmadi.")
1403
+ );
1404
+ return;
1405
+ }
1406
+ const firstUsable = certs.find((c) => certIsUsable(c));
1407
+ this.setState({
1408
+ status: "ready",
1409
+ certs,
1410
+ idCardAvailable: idCard,
1411
+ selectedSerial: firstUsable?.serialNumber ?? (idCard ? ID_CARD_SERIAL : certs[0]?.serialNumber ?? null)
1412
+ });
1413
+ } catch (error) {
1414
+ this.fail(toEimzoError(error, "E_NOT_INSTALLED", "E-IMZO bilan ulanib bo'lmadi."));
1415
+ }
1416
+ }
1417
+ /** Fatal xatodan keyin qayta yuklash. */
1418
+ retryLoad() {
1419
+ if (this.state.status !== "error" || this.finished) return;
1420
+ void this.load();
1421
+ }
1422
+ selectCert(serial) {
1423
+ if (this.state.status !== "ready" && this.state.status !== "partial") return;
1424
+ this.setState({ selectedSerial: serial, lastError: void 0 });
1425
+ }
1426
+ /** "Imzolash" tugmasi. */
1427
+ async confirm() {
1428
+ if (!this.canConfirm || this.finished) return;
1429
+ const cert = this.selectedCert;
1430
+ if (!cert) return;
1431
+ const targets = this.pendingDocs;
1432
+ this.patchDocs(targets.map((d) => d.id), { status: "pending", error: void 0 });
1433
+ this.setState({
1434
+ status: "signing",
1435
+ lastError: void 0,
1436
+ progress: {
1437
+ done: this.state.docs.length - targets.length,
1438
+ total: this.state.docs.length
1439
+ }
1440
+ });
1441
+ try {
1442
+ const items = await this.client.signBatch(
1443
+ cert,
1444
+ targets.map((d) => ({ id: d.id, content: d.content })),
1445
+ {
1446
+ signal: this.ac.signal,
1447
+ onProgress: (p) => {
1448
+ const doneTotal = this.state.docs.length - targets.length + p.done;
1449
+ this.setState({ progress: { done: doneTotal, total: this.state.docs.length } });
1450
+ if (p.currentId) {
1451
+ this.patchDocs([p.currentId], { status: "signing" });
1452
+ }
1453
+ }
1454
+ }
1455
+ );
1456
+ for (const item of items) {
1457
+ if (item.ok) {
1458
+ this.patchDocs([item.id], {
1459
+ status: "done",
1460
+ result: {
1461
+ id: item.id,
1462
+ pkcs7_64: item.pkcs7_64,
1463
+ signature_hex: item.signature_hex,
1464
+ signer_serial_number: item.signer_serial_number
1465
+ }
1466
+ });
1467
+ } else {
1468
+ this.patchDocs([item.id], { status: "failed", error: item.error });
1469
+ }
1470
+ }
1471
+ const failed = this.state.docs.filter((d) => d.status === "failed");
1472
+ if (failed.length === 0) {
1473
+ this.succeed();
1474
+ } else {
1475
+ this.setState({
1476
+ status: "partial",
1477
+ lastError: new EimzoError(
1478
+ "E_SIGN_FAILED",
1479
+ `${failed.length} ta hujjat imzolanmadi.`
1480
+ )
1481
+ });
1482
+ }
1483
+ } catch (error) {
1484
+ if (EimzoError.is(error, "E_CANCELLED")) {
1485
+ this.cancelledByAbort();
1486
+ return;
1487
+ }
1488
+ this.patchDocs(targets.map((d) => d.id), { status: "pending" });
1489
+ this.setState({
1490
+ status: this.state.docs.some((d) => d.status === "done") ? "partial" : "ready",
1491
+ lastError: toEimzoError(error, "E_SIGN_FAILED", "Imzolab bo'lmadi.")
1492
+ });
1493
+ }
1494
+ }
1495
+ /** "Bekor qilish" / Esc / overlay / idle-timeout / "Yopish" (fatal ekranда). */
1496
+ cancel() {
1497
+ if (this.finished) return;
1498
+ if (this.state.status === "signing") {
1499
+ this.ac.abort();
1500
+ return;
1501
+ }
1502
+ if (this.state.status === "error" && this.state.fatal) {
1503
+ const fatal = this.state.fatal;
1504
+ this.finish(() => this.rejectOuter(fatal));
1505
+ return;
1506
+ }
1507
+ this.finish(() => this.rejectOuter(this.cancelError()));
1508
+ }
1509
+ // ─── Yakuniy holatlar ──────────────────────────────────────────────────
1510
+ succeed() {
1511
+ const results = this.state.docs.map((d) => d.result).filter((r) => Boolean(r));
1512
+ this.finish(() => this.resolveOuter({ results }));
1513
+ }
1514
+ cancelledByAbort() {
1515
+ this.patchDocs(
1516
+ this.state.docs.filter((d) => d.status === "signing").map((d) => d.id),
1517
+ { status: "pending" }
1518
+ );
1519
+ this.finish(() => this.rejectOuter(this.cancelError()));
1520
+ }
1521
+ fail(error) {
1522
+ this.setState({ status: "error", fatal: error });
1523
+ }
1524
+ cancelError() {
1525
+ const partial = this.state.docs.map((d) => d.result).filter((r) => Boolean(r));
1526
+ const err = new EimzoError("E_CANCELLED", "Imzolash bekor qilindi.");
1527
+ err.partial = partial;
1528
+ return err;
1529
+ }
1530
+ finish(action) {
1531
+ if (this.finished) return;
1532
+ this.finished = true;
1533
+ action();
1534
+ }
1535
+ patchDocs(ids, patch) {
1536
+ const set = new Set(ids);
1537
+ this.setState({
1538
+ docs: this.state.docs.map((d) => set.has(d.id) ? { ...d, ...patch } : d)
1539
+ });
1540
+ }
1541
+ };
1542
+
1330
1543
  exports.EimzoClient = EimzoClient;
1331
1544
  exports.EimzoError = EimzoError;
1545
+ exports.ID_CARD_SERIAL = ID_CARD_SERIAL;
1546
+ exports.SignFlow = SignFlow;
1332
1547
  exports.certExpiresSoon = certExpiresSoon;
1333
1548
  exports.certIsExpired = certIsExpired;
1549
+ exports.certIsIndividual = certIsIndividual;
1334
1550
  exports.certIsNotYetValid = certIsNotYetValid;
1335
1551
  exports.certIsUsable = certIsUsable;
1552
+ exports.certOrg = certOrg;
1336
1553
  exports.dedupeCerts = dedupeCerts;
1337
1554
  exports.sortCerts = sortCerts;
1338
1555
  exports.toEimzoError = toEimzoError;