eimzo-sign-core 0.1.4 → 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 +29 -3
- package/dist/index.cjs +210 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -1
- package/dist/index.d.ts +100 -1
- package/dist/index.js +209 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +14 -0
- package/src/sign-flow.ts +340 -0
- 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
|
|
|
@@ -113,6 +117,28 @@ interface Cert {
|
|
|
113
117
|
|
|
114
118
|
---
|
|
115
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
|
+
|
|
116
142
|
## Sozlamalar
|
|
117
143
|
|
|
118
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 = {};
|
|
@@ -1345,8 +1332,218 @@ function dedupeCerts(certs) {
|
|
|
1345
1332
|
return out;
|
|
1346
1333
|
}
|
|
1347
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
|
+
|
|
1348
1543
|
exports.EimzoClient = EimzoClient;
|
|
1349
1544
|
exports.EimzoError = EimzoError;
|
|
1545
|
+
exports.ID_CARD_SERIAL = ID_CARD_SERIAL;
|
|
1546
|
+
exports.SignFlow = SignFlow;
|
|
1350
1547
|
exports.certExpiresSoon = certExpiresSoon;
|
|
1351
1548
|
exports.certIsExpired = certIsExpired;
|
|
1352
1549
|
exports.certIsIndividual = certIsIndividual;
|