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 +48 -4
- package/dist/index.cjs +230 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -3
- package/dist/index.d.ts +108 -3
- package/dist/index.js +227 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cert-utils.ts +22 -0
- package/src/client.ts +2 -0
- package/src/index.ts +16 -0
- package/src/raw-types.ts +2 -0
- package/src/sign-flow.ts +340 -0
- package/src/types.ts +4 -2
- package/src/vendor/VENDOR.md +16 -1
- package/src/vendor/e-imzo.js +4 -8
package/README.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# eimzo-sign-core
|
|
2
2
|
|
|
3
|
-
E-IMZO bilan imzolash mantig'i —
|
|
4
|
-
|
|
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
|
-
|
|
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;
|