eimzo-ts 0.1.1 → 0.1.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.
@@ -0,0 +1,150 @@
1
+ # eimzo-ts examples
2
+
3
+ All snippets assume one shared instance:
4
+
5
+ ```ts
6
+ // eimzo.ts
7
+ import { createEimzo, createFetchBackend, DEV_API_KEYS } from 'eimzo-ts';
8
+
9
+ export const eimzo = createEimzo({
10
+ apiKeys: { ...DEV_API_KEYS },
11
+ backend: createFetchBackend({
12
+ challengeUrl: '/api/eimzo/challenge',
13
+ loginUrl: '/api/eimzo/login',
14
+ timestampUrl: '/api/eimzo/timestamp',
15
+ }),
16
+ });
17
+ ```
18
+
19
+ ## Key picker data
20
+
21
+ ```ts
22
+ const keys = await eimzo.listKeys();
23
+ const options = keys.map((k, i) => ({
24
+ value: i,
25
+ label: `${k.CN} — ${k.TIN || k.PINFL}${k.O ? `, ${k.O}` : ''}`,
26
+ disabled: k.expired,
27
+ }));
28
+ ```
29
+
30
+ ## Login
31
+
32
+ ```ts
33
+ import { isEimzoError, type EimzoKey } from 'eimzo-ts';
34
+
35
+ async function loginWith(key: EimzoKey) {
36
+ try {
37
+ const user = await eimzo.login(key); // whatever your /api/eimzo/login returns
38
+ return user;
39
+ } catch (e) {
40
+ if (isEimzoError(e, 'CANCELLED')) return null;
41
+ throw new Error(eimzo.t(e));
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## Sign a document with a timestamp and send it
47
+
48
+ ```ts
49
+ await eimzo.loadKey(key); // once; later signatures reuse the keyId
50
+ const pkcs7 = await eimzo.signDocument(JSON.stringify({ id: docId, nonce: crypto.randomUUID(), body }));
51
+ await fetch(`/api/documents/${docId}/sign`, { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: pkcs7 });
52
+ ```
53
+
54
+ Put a unique ID into the signed content so the backend can reject a replayed signature.
55
+
56
+ ## Sign a file (detached)
57
+
58
+ ```ts
59
+ const file = input.files![0];
60
+ const pkcs7 = await eimzo.signFile(file, { maxSize: 20 * 1024 * 1024 });
61
+ // send both the file and pkcs7; the backend verifies with /backend/pkcs7/verify/detached
62
+ ```
63
+
64
+ ## Logout
65
+
66
+ ```ts
67
+ await eimzo.unloadKey();
68
+ ```
69
+
70
+ ## Angular service
71
+
72
+ ```ts
73
+ import { Injectable } from '@angular/core';
74
+ import { createEimzo, createFetchBackend, DEV_API_KEYS } from 'eimzo-ts';
75
+
76
+ @Injectable({ providedIn: 'root' })
77
+ export class EimzoService {
78
+ readonly client = createEimzo({
79
+ apiKeys: { ...DEV_API_KEYS },
80
+ backend: createFetchBackend({ challengeUrl: '/api/eimzo/challenge', loginUrl: '/api/eimzo/login' }),
81
+ });
82
+ }
83
+ ```
84
+
85
+ To reuse `HttpClient` interceptors, pass a custom backend:
86
+
87
+ ```ts
88
+ backend: {
89
+ getChallenge: () => firstValueFrom(this.http.get<{ challenge: string }>('/api/eimzo/challenge')).then((r) => r.challenge),
90
+ login: (pkcs7) => firstValueFrom(this.http.post('/api/eimzo/login', pkcs7, { headers: { 'Content-Type': 'text/plain' } })),
91
+ }
92
+ ```
93
+
94
+ ## React
95
+
96
+ ```tsx
97
+ import { useEffect, useState } from 'react';
98
+ import type { EimzoKey } from 'eimzo-ts';
99
+ import { eimzo } from './eimzo';
100
+
101
+ export function KeyPicker({ onPick }: { onPick: (k: EimzoKey) => void }) {
102
+ const [keys, setKeys] = useState<EimzoKey[]>([]);
103
+ const [error, setError] = useState<string>();
104
+ useEffect(() => { eimzo.listKeys().then(setKeys, (e) => setError(eimzo.t(e))); }, []);
105
+ if (error) return <p role="alert">{error}</p>;
106
+ return (
107
+ <ul>
108
+ {keys.map((k) => (
109
+ <li key={k.serialNumber}>
110
+ <button disabled={k.expired} onClick={() => onPick(k)}>{k.CN} — {k.TIN || k.PINFL}</button>
111
+ </li>
112
+ ))}
113
+ </ul>
114
+ );
115
+ }
116
+ ```
117
+
118
+ In Next.js, put this in a `'use client'` component.
119
+
120
+ ## Vue
121
+
122
+ ```ts
123
+ import { onMounted, ref } from 'vue';
124
+ import type { EimzoKey } from 'eimzo-ts';
125
+ import { eimzo } from './eimzo';
126
+
127
+ const keys = ref<EimzoKey[]>([]);
128
+ onMounted(async () => { keys.value = await eimzo.listKeys(); });
129
+ ```
130
+
131
+ ## Unit test with a fake transport
132
+
133
+ ```ts
134
+ import { createEimzo } from 'eimzo-ts';
135
+
136
+ const eimzo = createEimzo({
137
+ transport: async (req) => {
138
+ const name = req.plugin ? `${req.plugin}.${req.name}` : req.name;
139
+ switch (name) {
140
+ case 'version': return { success: true, major: '6', minor: '3' };
141
+ case 'pfx.list_all_certificates': return { success: true, certificates: [] };
142
+ case 'pfx.load_key': return { success: true, keyId: 'K1' };
143
+ case 'pkcs7.create_pkcs7': return { success: true, pkcs7_64: 'MIAG...' };
144
+ default: return { success: false, reason: `not mocked: ${name}` };
145
+ }
146
+ },
147
+ });
148
+ ```
149
+
150
+ Simulate the user cancelling: `{ success: false, status: -5000, reason: 'Ввод пароля отменен' }` → `CANCELLED`.
@@ -0,0 +1,39 @@
1
+ # Troubleshooting eimzo-ts
2
+
3
+ Read `error.code` first, then `error.reason` / `error.status` (raw E-IMZO data) or `error.httpStatus` (backend).
4
+
5
+ | code | Typical cause | Fix |
6
+ |---|---|---|
7
+ | `NOT_INSTALLED` | E-IMZO is not running or not installed; the WebSocket to 127.0.0.1 closed. `wsCode` has the close code. | Ask the user to start E-IMZO or install it from https://e-imzo.uz/main/downloads/. |
8
+ | `WEBSOCKET_UNSUPPORTED` | No `WebSocket` (very old browser, or code ran on the server during SSR). | Run on the client only; in Node pass `WebSocketImpl`. |
9
+ | `TIMEOUT` | `timeoutMs` is set and E-IMZO (or the user) did not answer in time. | Raise or remove `timeoutMs`. |
10
+ | `OUTDATED_VERSION` | Installed E-IMZO is older than `minVersion` (default 4.86). | Ask the user to update E-IMZO. |
11
+ | `API_KEY_REJECTED` | No valid API key for the page's domain. | `DEV_API_KEYS` for localhost; "Developer mode" in the E-IMZO tray for test domains; an issued key per production domain/subdomain. |
12
+ | `WRONG_PASSWORD` | Wrong key password. `reason` "Password is not ASCII" means a non-Latin keyboard layout. | Let the user retry. E-IMZO itself already showed a message. |
13
+ | `CANCELLED` | The user pressed "Cancel" (status -5000). | Do nothing or reset the UI. Do not show an error. |
14
+ | `KEY_NOT_FOUND` | E-IMZO forgot the `keyId` (status -1010/-1014): restart, 24 h, or unloaded. With `autoReloadKey` (default) `sign()` retries once. | Call `loadKey()` again. |
15
+ | `KEY_NOT_LOADED` | `sign`/`signFile`/`signDocument` before `loadKey()`. | Fix the calling code. `login()` loads the key itself. |
16
+ | `FILE_TOO_LARGE` | File larger than `maxSize` (10 MB default). | Raise `maxSize` or reject the file. |
17
+ | `BACKEND_NOT_CONFIGURED` | `login()` without `getChallenge`/`login`, or `signDocument()` without `timestamp`. | Configure `backend`. |
18
+ | `BACKEND_ERROR` | Backend returned non-2xx or `status !== 1`. | Inspect `httpStatus`, `params.detail`; see backend statuses below. |
19
+ | `EIMZO_ERROR` | Anything else from E-IMZO; status 0 with "Функция не найдена" = unknown function or outdated E-IMZO. | Read `reason`. |
20
+
21
+ ## Other symptoms
22
+
23
+ - **Password asked on every signature.** Several `Eimzo` instances, or `loadKey()` before each `sign()`.
24
+ Keep one instance and load the key once. Without "Remember for 6 hours" E-IMZO keeps the password 1 minute.
25
+ - **Fewer keys than in E-IMZO.** Keys without TIN and PINFL are skipped by design.
26
+ - **Works on localhost, fails on the real domain.** Missing API key for that domain, or the page is https and
27
+ E-IMZO's `wss://127.0.0.1:64443` is not reachable.
28
+
29
+ ## e-imzo-server `/backend/auth` statuses
30
+
31
+ | status | Meaning | Action |
32
+ |---|---|---|
33
+ | 1 | OK | Find the user by PINFL (`1.2.860.3.16.1.2`) or TIN (`1.2.860.3.16.1.1`, else `UID`). |
34
+ | -1 | Certificate status could not be checked (often VPN down) | HTTP 503, ask to retry later; do not log in. |
35
+ | -5 | Signing time outside the allowed window | Ask the user to fix the computer clock. |
36
+ | -10 | Invalid signature | 401 |
37
+ | -11, -12 | Certificate invalid (at signing time) | 401 |
38
+ | -20 | Challenge not found or expired (TTL 120 s) | Restart login. |
39
+ | -24, -25 | Certificate policy / CA not allowed | 401 |