@islom929/react-eimzo 0.2.0 → 0.3.1
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 +412 -0
- package/dist/index.cjs +5 -2
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -2
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
# @islom929/react-eimzo
|
|
2
|
+
|
|
3
|
+
Headless E-IMZO digital signature integration for React. No UI included — bring your own components.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @islom929/react-eimzo
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
No additional setup required. SDK is bundled and auto-injected.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### 1. Wrap your app with EimzoProvider
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { EimzoProvider } from '@islom929/react-eimzo'
|
|
19
|
+
|
|
20
|
+
function App() {
|
|
21
|
+
return (
|
|
22
|
+
<EimzoProvider>
|
|
23
|
+
<YourApp />
|
|
24
|
+
</EimzoProvider>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 2. Use the hook
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { useEimzo } from '@islom929/react-eimzo'
|
|
33
|
+
import type { ICertificate } from '@islom929/react-eimzo'
|
|
34
|
+
|
|
35
|
+
function SignDocument() {
|
|
36
|
+
const { sign, loadKeys, keyList, isInstalled, isLoading } = useEimzo()
|
|
37
|
+
|
|
38
|
+
const handleSign = (cert: ICertificate) => {
|
|
39
|
+
sign({
|
|
40
|
+
keyId: cert,
|
|
41
|
+
data: JSON.stringify({ document: 'content' }),
|
|
42
|
+
onSuccess: (pkcs7) => {
|
|
43
|
+
console.log('Signed:', pkcs7)
|
|
44
|
+
},
|
|
45
|
+
onError: (err) => {
|
|
46
|
+
console.error('Error:', err)
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div>
|
|
53
|
+
<button onClick={loadKeys} disabled={!isInstalled || isLoading}>
|
|
54
|
+
Load keys
|
|
55
|
+
</button>
|
|
56
|
+
|
|
57
|
+
{keyList.map((cert) => (
|
|
58
|
+
<button key={cert.serialNumber} onClick={() => handleSign(cert)}>
|
|
59
|
+
{cert.CN}
|
|
60
|
+
</button>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage Examples
|
|
68
|
+
|
|
69
|
+
### Sign with PFX certificate
|
|
70
|
+
|
|
71
|
+
User selects a certificate from the list. E-IMZO app prompts for password.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { useEimzo } from '@islom929/react-eimzo'
|
|
75
|
+
import type { ICertificate } from '@islom929/react-eimzo'
|
|
76
|
+
|
|
77
|
+
function PfxSign() {
|
|
78
|
+
const { sign, loadKeys, keyList, isInstalled, isLoading } = useEimzo()
|
|
79
|
+
const [result, setResult] = useState('')
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (isInstalled) loadKeys()
|
|
83
|
+
}, [isInstalled])
|
|
84
|
+
|
|
85
|
+
const handleSign = (cert: ICertificate) => {
|
|
86
|
+
sign({
|
|
87
|
+
keyId: cert,
|
|
88
|
+
data: JSON.stringify({ orderId: 123, amount: 50000 }),
|
|
89
|
+
onSuccess: (pkcs7) => {
|
|
90
|
+
setResult(pkcs7)
|
|
91
|
+
// Send to backend
|
|
92
|
+
fetch('/api/verify', {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
body: JSON.stringify({ pkcs7 }),
|
|
95
|
+
})
|
|
96
|
+
},
|
|
97
|
+
onError: (err) => {
|
|
98
|
+
alert(err) // "Ввод пароля отменен" if user cancels
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div>
|
|
105
|
+
<h3>Select certificate:</h3>
|
|
106
|
+
{keyList.map((cert, i) => (
|
|
107
|
+
<div key={`${cert.serialNumber}-${i}`}>
|
|
108
|
+
<p>{cert.CN} — {cert.O}</p>
|
|
109
|
+
<p>PINFL: {cert.PINFL} | STIR: {cert.TIN}</p>
|
|
110
|
+
<p>Valid until: {new Date(cert.validTo).toLocaleDateString()}</p>
|
|
111
|
+
<button
|
|
112
|
+
onClick={() => handleSign(cert)}
|
|
113
|
+
disabled={cert.expired || isLoading}
|
|
114
|
+
>
|
|
115
|
+
{cert.expired ? 'Expired' : 'Sign'}
|
|
116
|
+
</button>
|
|
117
|
+
</div>
|
|
118
|
+
))}
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Sign with hardware device
|
|
125
|
+
|
|
126
|
+
No certificate selection needed. Pass device type directly.
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
function DeviceSign() {
|
|
130
|
+
const { sign, deviceStatus, loadKeys, isInstalled, isLoading } = useEimzo()
|
|
131
|
+
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (isInstalled) loadKeys()
|
|
134
|
+
}, [isInstalled])
|
|
135
|
+
|
|
136
|
+
const handleDeviceSign = (device: 'idcard' | 'baikey' | 'ckc') => {
|
|
137
|
+
sign({
|
|
138
|
+
keyId: device,
|
|
139
|
+
data: JSON.stringify({ document: 'content' }),
|
|
140
|
+
onSuccess: (pkcs7) => console.log('Signed:', pkcs7),
|
|
141
|
+
onError: (err) => console.error(err),
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div>
|
|
147
|
+
<button
|
|
148
|
+
onClick={() => handleDeviceSign('idcard')}
|
|
149
|
+
disabled={!deviceStatus.idcard || isLoading}
|
|
150
|
+
>
|
|
151
|
+
ID Card {deviceStatus.idcard ? '(connected)' : '(not connected)'}
|
|
152
|
+
</button>
|
|
153
|
+
|
|
154
|
+
<button
|
|
155
|
+
onClick={() => handleDeviceSign('baikey')}
|
|
156
|
+
disabled={!deviceStatus.baikey || isLoading}
|
|
157
|
+
>
|
|
158
|
+
BAIK Token {deviceStatus.baikey ? '(connected)' : '(not connected)'}
|
|
159
|
+
</button>
|
|
160
|
+
|
|
161
|
+
<button
|
|
162
|
+
onClick={() => handleDeviceSign('ckc')}
|
|
163
|
+
disabled={!deviceStatus.ckc || isLoading}
|
|
164
|
+
>
|
|
165
|
+
CKC {deviceStatus.ckc ? '(connected)' : '(not connected)'}
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Form submission with signature
|
|
173
|
+
|
|
174
|
+
Sign form data before submitting to backend.
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
function ApplicationForm() {
|
|
178
|
+
const { sign, loadKeys, keyList, isInstalled } = useEimzo()
|
|
179
|
+
const [step, setStep] = useState<'form' | 'sign'>('form')
|
|
180
|
+
const [formData, setFormData] = useState({ name: '', amount: 0 })
|
|
181
|
+
|
|
182
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
183
|
+
e.preventDefault()
|
|
184
|
+
loadKeys()
|
|
185
|
+
setStep('sign')
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const handleSign = (cert: ICertificate) => {
|
|
189
|
+
sign({
|
|
190
|
+
keyId: cert,
|
|
191
|
+
data: JSON.stringify(formData),
|
|
192
|
+
onSuccess: async (pkcs7) => {
|
|
193
|
+
await fetch('/api/applications', {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
headers: { 'Content-Type': 'application/json' },
|
|
196
|
+
body: JSON.stringify({ ...formData, pkcs7 }),
|
|
197
|
+
})
|
|
198
|
+
alert('Application submitted!')
|
|
199
|
+
setStep('form')
|
|
200
|
+
},
|
|
201
|
+
onError: (err) => alert(`Signing failed: ${err}`),
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (step === 'form') {
|
|
206
|
+
return (
|
|
207
|
+
<form onSubmit={handleSubmit}>
|
|
208
|
+
<input
|
|
209
|
+
value={formData.name}
|
|
210
|
+
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
211
|
+
placeholder="Name"
|
|
212
|
+
/>
|
|
213
|
+
<input
|
|
214
|
+
type="number"
|
|
215
|
+
value={formData.amount}
|
|
216
|
+
onChange={(e) => setFormData({ ...formData, amount: +e.target.value })}
|
|
217
|
+
placeholder="Amount"
|
|
218
|
+
/>
|
|
219
|
+
<button type="submit" disabled={!isInstalled}>
|
|
220
|
+
Sign & Submit
|
|
221
|
+
</button>
|
|
222
|
+
</form>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<div>
|
|
228
|
+
<h3>Select certificate to sign:</h3>
|
|
229
|
+
{keyList.map((cert, i) => (
|
|
230
|
+
<button
|
|
231
|
+
key={`${cert.serialNumber}-${i}`}
|
|
232
|
+
onClick={() => handleSign(cert)}
|
|
233
|
+
disabled={cert.expired}
|
|
234
|
+
>
|
|
235
|
+
{cert.CN}
|
|
236
|
+
</button>
|
|
237
|
+
))}
|
|
238
|
+
<button onClick={() => setStep('form')}>Back</button>
|
|
239
|
+
</div>
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Production API keys
|
|
245
|
+
|
|
246
|
+
Add your domain's API key for production deployment.
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
<EimzoProvider
|
|
250
|
+
apiKeys={[
|
|
251
|
+
'yourdomain.uz', 'YOUR_PRODUCTION_API_KEY_HERE',
|
|
252
|
+
'test.yourdomain.uz', 'YOUR_TEST_API_KEY_HERE',
|
|
253
|
+
]}
|
|
254
|
+
>
|
|
255
|
+
<App />
|
|
256
|
+
</EimzoProvider>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Default keys for `localhost` and `127.0.0.1` are always included.
|
|
260
|
+
|
|
261
|
+
### Check E-IMZO installation
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
function EimzoStatus() {
|
|
265
|
+
const { isInstalled } = useEimzo()
|
|
266
|
+
|
|
267
|
+
if (!isInstalled) {
|
|
268
|
+
return (
|
|
269
|
+
<div>
|
|
270
|
+
<p>E-IMZO is not installed.</p>
|
|
271
|
+
<a href="https://e-imzo.uz/main/downloads/">Download E-IMZO</a>
|
|
272
|
+
</div>
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return <p>E-IMZO is ready</p>
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Filter certificates
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
function FilteredKeys() {
|
|
284
|
+
const { keyList, loadKeys, isInstalled } = useEimzo()
|
|
285
|
+
|
|
286
|
+
useEffect(() => {
|
|
287
|
+
if (isInstalled) loadKeys()
|
|
288
|
+
}, [isInstalled])
|
|
289
|
+
|
|
290
|
+
// Only valid (not expired) certificates
|
|
291
|
+
const validCerts = keyList.filter((cert) => !cert.expired)
|
|
292
|
+
|
|
293
|
+
// Filter by organization
|
|
294
|
+
const orgCerts = keyList.filter((cert) => cert.O === 'MY COMPANY')
|
|
295
|
+
|
|
296
|
+
// Filter by PINFL
|
|
297
|
+
const userCerts = keyList.filter((cert) => cert.PINFL === '12345678901234')
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<div>
|
|
301
|
+
<p>All: {keyList.length}</p>
|
|
302
|
+
<p>Valid: {validCerts.length}</p>
|
|
303
|
+
<p>My org: {orgCerts.length}</p>
|
|
304
|
+
</div>
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## API
|
|
310
|
+
|
|
311
|
+
### EimzoProvider
|
|
312
|
+
|
|
313
|
+
Wraps your app. Initializes E-IMZO SDK automatically.
|
|
314
|
+
|
|
315
|
+
| Prop | Type | Description |
|
|
316
|
+
|------|------|-------------|
|
|
317
|
+
| `apiKeys` | `string[]` | Optional. Additional domain + API key pairs |
|
|
318
|
+
| `children` | `ReactNode` | Required |
|
|
319
|
+
|
|
320
|
+
### useEimzo()
|
|
321
|
+
|
|
322
|
+
| Property | Type | Description |
|
|
323
|
+
|----------|------|-------------|
|
|
324
|
+
| `isInstalled` | `boolean` | E-IMZO app detected and running |
|
|
325
|
+
| `isLoading` | `boolean` | Operation in progress (loadKeys or sign) |
|
|
326
|
+
| `keyList` | `ICertificate[]` | Available certificates |
|
|
327
|
+
| `deviceStatus` | `IDeviceStatus` | Connected hardware devices |
|
|
328
|
+
| `loadKeys` | `() => Promise<void>` | Load certificates and check devices |
|
|
329
|
+
| `sign` | `(params: ISignParams) => void` | Sign data |
|
|
330
|
+
|
|
331
|
+
### sign(params)
|
|
332
|
+
|
|
333
|
+
| Param | Type | Description |
|
|
334
|
+
|-------|------|-------------|
|
|
335
|
+
| `keyId` | `ICertificate \| string` | Certificate object or `'idcard'` / `'baikey'` / `'ckc'` |
|
|
336
|
+
| `data` | `string` | Data to sign (usually JSON.stringify) |
|
|
337
|
+
| `onSuccess` | `(pkcs7: string) => void` | Called with base64 PKCS#7 signature |
|
|
338
|
+
| `onError` | `(error: string) => void` | Optional. Called on failure |
|
|
339
|
+
|
|
340
|
+
## Types
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
import type {
|
|
344
|
+
ICertificate,
|
|
345
|
+
ISignParams,
|
|
346
|
+
IDeviceStatus,
|
|
347
|
+
IEimzoContext,
|
|
348
|
+
IEimzoProviderProps,
|
|
349
|
+
TKeyType,
|
|
350
|
+
} from '@islom929/react-eimzo'
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### ICertificate
|
|
354
|
+
|
|
355
|
+
| Field | Type | Description |
|
|
356
|
+
|-------|------|-------------|
|
|
357
|
+
| `CN` | `string` | Full name |
|
|
358
|
+
| `PINFL` | `string` | Personal ID number |
|
|
359
|
+
| `TIN` | `string` | Tax ID (STIR) |
|
|
360
|
+
| `O` | `string` | Organization |
|
|
361
|
+
| `T` | `string` | Title/Position |
|
|
362
|
+
| `UID` | `string` | User ID |
|
|
363
|
+
| `serialNumber` | `string` | Certificate serial number |
|
|
364
|
+
| `validFrom` | `Date` | Start of validity |
|
|
365
|
+
| `validTo` | `Date` | End of validity |
|
|
366
|
+
| `type` | `'pfx' \| 'ftjc'` | Certificate type |
|
|
367
|
+
| `expired` | `boolean` | Whether certificate has expired |
|
|
368
|
+
|
|
369
|
+
### IDeviceStatus
|
|
370
|
+
|
|
371
|
+
| Field | Type | Description |
|
|
372
|
+
|-------|------|-------------|
|
|
373
|
+
| `idcard` | `boolean` | ID card / EIMZO-Token connected |
|
|
374
|
+
| `baikey` | `boolean` | BAIK-Token connected |
|
|
375
|
+
| `ckc` | `boolean` | CKC device connected |
|
|
376
|
+
|
|
377
|
+
## Supported Key Types
|
|
378
|
+
|
|
379
|
+
| Type | Description | E-IMZO Version |
|
|
380
|
+
|------|-------------|----------------|
|
|
381
|
+
| PFX | Local certificate file (ERI) | v3.36+ |
|
|
382
|
+
| ID-card / EIMZO-Token | Physical smart card | v4.12+ |
|
|
383
|
+
| BAIK-Token | BAIK hardware token | v4.86+ |
|
|
384
|
+
| CKC | CryptKeyContainer (universal) | v4.86+ |
|
|
385
|
+
|
|
386
|
+
## How It Works
|
|
387
|
+
|
|
388
|
+
```
|
|
389
|
+
Your React App
|
|
390
|
+
↓ useEimzo()
|
|
391
|
+
@islom929/react-eimzo
|
|
392
|
+
↓ WebSocket (wss://127.0.0.1:64443)
|
|
393
|
+
E-IMZO Desktop App
|
|
394
|
+
↓
|
|
395
|
+
PFX files / USB tokens / ID cards
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
1. Package injects E-IMZO SDK into the page automatically
|
|
399
|
+
2. SDK connects to E-IMZO desktop app via WebSocket
|
|
400
|
+
3. `loadKeys()` fetches available certificates
|
|
401
|
+
4. `sign()` sends data to E-IMZO app for signing
|
|
402
|
+
5. E-IMZO app prompts user for password/PIN
|
|
403
|
+
6. Signed PKCS#7 (base64) returned via `onSuccess`
|
|
404
|
+
|
|
405
|
+
## Requirements
|
|
406
|
+
|
|
407
|
+
- React 18+
|
|
408
|
+
- [E-IMZO desktop application](https://e-imzo.uz/main/downloads/) installed on user's computer
|
|
409
|
+
|
|
410
|
+
## License
|
|
411
|
+
|
|
412
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -839,7 +839,7 @@ function EimzoProvider({ apiKeys, children }) {
|
|
|
839
839
|
}
|
|
840
840
|
}, []);
|
|
841
841
|
const sign = (0, import_react.useCallback)(
|
|
842
|
-
async ({ keyId, data }) => {
|
|
842
|
+
async ({ keyId, data, onSuccess, onError }) => {
|
|
843
843
|
setIsLoading(true);
|
|
844
844
|
try {
|
|
845
845
|
let id;
|
|
@@ -849,7 +849,10 @@ function EimzoProvider({ apiKeys, children }) {
|
|
|
849
849
|
id = keyId;
|
|
850
850
|
}
|
|
851
851
|
const pkcs7 = await createPkcs7(id, data);
|
|
852
|
-
|
|
852
|
+
onSuccess(pkcs7);
|
|
853
|
+
} catch (err) {
|
|
854
|
+
const message = typeof err === "string" ? err : "E-IMZO signing failed";
|
|
855
|
+
onError?.(message);
|
|
853
856
|
} finally {
|
|
854
857
|
setIsLoading(false);
|
|
855
858
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -26,6 +26,8 @@ interface ICertificate {
|
|
|
26
26
|
interface ISignParams {
|
|
27
27
|
keyId: ICertificate | string;
|
|
28
28
|
data: string;
|
|
29
|
+
onSuccess: (pkcs7: string) => void;
|
|
30
|
+
onError?: (error: string) => void;
|
|
29
31
|
}
|
|
30
32
|
interface IDeviceStatus {
|
|
31
33
|
idcard: boolean;
|
|
@@ -42,7 +44,7 @@ interface IEimzoContext {
|
|
|
42
44
|
keyList: ICertificate[];
|
|
43
45
|
deviceStatus: IDeviceStatus;
|
|
44
46
|
loadKeys: () => Promise<void>;
|
|
45
|
-
sign: (params: ISignParams) =>
|
|
47
|
+
sign: (params: ISignParams) => void;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
interface IEimzoProviderProps {
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ interface ICertificate {
|
|
|
26
26
|
interface ISignParams {
|
|
27
27
|
keyId: ICertificate | string;
|
|
28
28
|
data: string;
|
|
29
|
+
onSuccess: (pkcs7: string) => void;
|
|
30
|
+
onError?: (error: string) => void;
|
|
29
31
|
}
|
|
30
32
|
interface IDeviceStatus {
|
|
31
33
|
idcard: boolean;
|
|
@@ -42,7 +44,7 @@ interface IEimzoContext {
|
|
|
42
44
|
keyList: ICertificate[];
|
|
43
45
|
deviceStatus: IDeviceStatus;
|
|
44
46
|
loadKeys: () => Promise<void>;
|
|
45
|
-
sign: (params: ISignParams) =>
|
|
47
|
+
sign: (params: ISignParams) => void;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
interface IEimzoProviderProps {
|
package/dist/index.js
CHANGED
|
@@ -818,7 +818,7 @@ function EimzoProvider({ apiKeys, children }) {
|
|
|
818
818
|
}
|
|
819
819
|
}, []);
|
|
820
820
|
const sign = useCallback(
|
|
821
|
-
async ({ keyId, data }) => {
|
|
821
|
+
async ({ keyId, data, onSuccess, onError }) => {
|
|
822
822
|
setIsLoading(true);
|
|
823
823
|
try {
|
|
824
824
|
let id;
|
|
@@ -828,7 +828,10 @@ function EimzoProvider({ apiKeys, children }) {
|
|
|
828
828
|
id = keyId;
|
|
829
829
|
}
|
|
830
830
|
const pkcs7 = await createPkcs7(id, data);
|
|
831
|
-
|
|
831
|
+
onSuccess(pkcs7);
|
|
832
|
+
} catch (err) {
|
|
833
|
+
const message = typeof err === "string" ? err : "E-IMZO signing failed";
|
|
834
|
+
onError?.(message);
|
|
832
835
|
} finally {
|
|
833
836
|
setIsLoading(false);
|
|
834
837
|
}
|