nskd-lbr 1.1.1 → 1.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.
package/src/nskd-lbr.js CHANGED
@@ -10,9 +10,6 @@
10
10
  * @param {integer} [options.timeout=10000] - API request timeout in milliseconds
11
11
  * @param {boolean} [options.useLegacyAPI=false] - Whether to use the legacy API format
12
12
  * @param {function} [options.onLog=null] - Whether to validate local data against API response
13
- * @param {string} [options.loginEndpoint=''] - Login API endpoint for authentication
14
- * @param {function} [options.onLoginSuccess=null] - Callback function for successful login
15
- * @param {function} [options.onLoginFail=null] - Callback function for failed login
16
13
  */
17
14
 
18
15
  class NskdLbr {
@@ -23,14 +20,10 @@ class NskdLbr {
23
20
  this.strictCheck = options.strictCheck !== undefined ? options.strictCheck : true;
24
21
  this.onLog = options.onLog || null;
25
22
  this.useLegacyAPI = options.useLegacyAPI || false;
26
- this.loginEndpoint = options.loginEndpoint || '';
27
- this.onLoginSuccess = options.onLoginSuccess || null;
28
- this.onLoginFail = options.onLoginFail || null;
29
23
  this.certificateData = null;
30
24
  this.verificationKey = null;
31
25
  this.localData = null;
32
26
  this.isValid = false;
33
- this.currentLoginModal = null;
34
27
  }
35
28
 
36
29
  /**
@@ -62,762 +55,6 @@ class NskdLbr {
62
55
  }
63
56
  }
64
57
 
65
- /**
66
- * Show login modal (only available for webpages with loginEndpoint set)
67
- * @returns {Promise<Object>} Login result
68
- */
69
- async showLoginModal() {
70
- if (typeof window === 'undefined') {
71
- throw new Error('Login modal is only available in browser environments');
72
- }
73
-
74
- if (!this.loginEndpoint || this.loginEndpoint.trim() === '') {
75
- throw new Error('Login endpoint is not configured');
76
- }
77
-
78
- return new Promise((resolve, reject) => {
79
- this.createLoginModal(resolve, reject);
80
- });
81
- }
82
-
83
- /**
84
- * Create and display the login modal
85
- * @private
86
- */
87
- createLoginModal(resolve, reject) {
88
- if (this.currentLoginModal) {
89
- this.closeLoginModal();
90
- }
91
-
92
- const overlay = document.createElement('div');
93
- overlay.id = 'noskid-login-overlay';
94
- overlay.style.cssText = `
95
- position: fixed;
96
- top: 0;
97
- left: 0;
98
- width: 100%;
99
- height: 100%;
100
- background: rgba(139, 69, 19, 0.15);
101
- display: flex;
102
- justify-content: center;
103
- align-items: center;
104
- z-index: 10000;
105
- backdrop-filter: blur(8px);
106
- opacity: 0;
107
- transition: opacity 0.3s ease;
108
- `;
109
-
110
- const modal = document.createElement('div');
111
- modal.id = 'noskid-login-modal';
112
- modal.style.cssText = `
113
- background: linear-gradient(135deg, #fcfaf5 0%, #f0ede0 100%);
114
- border: 2px solid #8b4513;
115
- border-radius: 16px;
116
- padding: 0;
117
- max-width: 480px;
118
- width: 95%;
119
- max-height: 95vh;
120
- overflow: hidden;
121
- box-shadow:
122
- 0 25px 50px -12px rgba(139, 69, 19, 0.25),
123
- 0 0 0 1px rgba(139, 69, 19, 0.1),
124
- inset 0 1px 0 rgba(252, 250, 245, 0.9);
125
- transform: translateY(20px) scale(0.95);
126
- transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
127
- position: relative;
128
- `;
129
-
130
- const style = document.createElement('style');
131
- style.textContent = `
132
- @keyframes noskidModalSlideIn {
133
- from {
134
- opacity: 0;
135
- transform: translateY(20px) scale(0.95);
136
- }
137
- to {
138
- opacity: 1;
139
- transform: translateY(0) scale(1);
140
- }
141
- }
142
-
143
- @keyframes noskidPulse {
144
- 0%, 100% { transform: scale(1); }
145
- 50% { transform: scale(1.05); }
146
- }
147
-
148
- @keyframes noskidShake {
149
- 0%, 100% { transform: translateX(0); }
150
- 25% { transform: translateX(-4px); }
151
- 75% { transform: translateX(4px); }
152
- }
153
-
154
- .noskid-modal-active {
155
- opacity: 1 !important;
156
- }
157
-
158
- .noskid-modal-active #noskid-login-modal {
159
- transform: translateY(0) scale(1) !important;
160
- }
161
-
162
- .noskid-header {
163
- background: linear-gradient(135deg, #f9f7f0 0%, #e8e5d8 100%);
164
- border-bottom: 1px solid #8b4513;
165
- padding: 24px;
166
- text-align: center;
167
- position: relative;
168
- }
169
-
170
- .noskid-header::before {
171
- content: '';
172
- position: absolute;
173
- top: 0;
174
- left: 0;
175
- right: 0;
176
- height: 1px;
177
- background: linear-gradient(90deg, transparent, rgba(139, 69, 19, 0.3), transparent);
178
- }
179
-
180
- .noskid-content {
181
- padding: 32px;
182
- overflow-y: auto;
183
- max-height: calc(95vh - 140px);
184
- }
185
-
186
- .noskid-title {
187
- margin: 0;
188
- color: #8b4513;
189
- font-size: 24px;
190
- font-family: 'Times New Roman', serif;
191
- font-weight: bold;
192
- display: flex;
193
- align-items: center;
194
- justify-content: center;
195
- gap: 12px;
196
- }
197
-
198
- .noskid-subtitle {
199
- margin: 8px 0 0 0;
200
- color: rgba(139, 69, 19, 0.7);
201
- font-size: 14px;
202
- font-family: system-ui, -apple-system, sans-serif;
203
- }
204
-
205
- .noskid-icon {
206
- width: 28px;
207
- height: 28px;
208
- background: #8b4513;
209
- border-radius: 50%;
210
- display: flex;
211
- align-items: center;
212
- justify-content: center;
213
- color: #fcfaf5;
214
- font-size: 16px;
215
- box-shadow: 0 2px 4px rgba(139, 69, 19, 0.2);
216
- }
217
-
218
- .noskid-form-group {
219
- margin-bottom: 24px;
220
- }
221
-
222
- .noskid-label {
223
- display: block;
224
- margin-bottom: 8px;
225
- color: #8b4513;
226
- font-size: 14px;
227
- font-weight: 600;
228
- font-family: system-ui, -apple-system, sans-serif;
229
- }
230
-
231
- .noskid-cert-upload {
232
- border: 2px dashed rgba(139, 69, 19, 0.3);
233
- border-radius: 12px;
234
- padding: 24px;
235
- text-align: center;
236
- background: linear-gradient(135deg, rgba(252, 250, 245, 0.5), rgba(240, 237, 224, 0.5));
237
- transition: all 0.3s ease;
238
- cursor: pointer;
239
- position: relative;
240
- min-height: 120px;
241
- display: flex;
242
- flex-direction: column;
243
- align-items: center;
244
- justify-content: center;
245
- }
246
-
247
- .noskid-cert-upload:hover {
248
- border-color: #8b4513;
249
- background: linear-gradient(135deg, rgba(252, 250, 245, 0.8), rgba(240, 237, 224, 0.8));
250
- transform: translateY(-2px);
251
- box-shadow: 0 4px 12px rgba(139, 69, 19, 0.1);
252
- }
253
-
254
- .noskid-cert-upload.dragover {
255
- border-color: #8b4513;
256
- background: linear-gradient(135deg, rgba(139, 69, 19, 0.1), rgba(139, 69, 19, 0.05));
257
- animation: noskidPulse 1s infinite;
258
- }
259
-
260
- .noskid-cert-upload.error {
261
- border-color: #dc2626;
262
- animation: noskidShake 0.5s ease-in-out;
263
- }
264
-
265
- .noskid-cert-upload.success {
266
- border-color: #059669;
267
- background: linear-gradient(135deg, rgba(5, 150, 105, 0.1), rgba(5, 150, 105, 0.05));
268
- }
269
-
270
- .noskid-upload-icon {
271
- width: 48px;
272
- height: 48px;
273
- margin-bottom: 12px;
274
- background: rgba(139, 69, 19, 0.1);
275
- border-radius: 50%;
276
- display: flex;
277
- align-items: center;
278
- justify-content: center;
279
- color: #8b4513;
280
- font-size: 24px;
281
- transition: all 0.3s ease;
282
- }
283
-
284
- .noskid-cert-upload:hover .noskid-upload-icon {
285
- background: rgba(139, 69, 19, 0.2);
286
- transform: scale(1.1);
287
- }
288
-
289
- .noskid-upload-text {
290
- color: #8b4513;
291
- font-size: 16px;
292
- font-weight: 600;
293
- margin-bottom: 4px;
294
- }
295
-
296
- .noskid-upload-hint {
297
- color: rgba(139, 69, 19, 0.6);
298
- font-size: 12px;
299
- line-height: 1.4;
300
- }
301
-
302
- .noskid-file-input {
303
- position: absolute;
304
- top: 0;
305
- left: 0;
306
- width: 100%;
307
- height: 100%;
308
- opacity: 0;
309
- cursor: pointer;
310
- }
311
-
312
- .noskid-input {
313
- width: 100%;
314
- padding: 16px;
315
- border: 2px solid rgba(139, 69, 19, 0.2);
316
- border-radius: 12px;
317
- font-size: 16px;
318
- box-sizing: border-box;
319
- background: rgba(252, 250, 245, 0.8);
320
- color: #8b4513;
321
- font-family: system-ui, -apple-system, sans-serif;
322
- transition: all 0.3s ease;
323
- }
324
-
325
- .noskid-input:focus {
326
- outline: none;
327
- border-color: #8b4513;
328
- box-shadow: 0 0 0 4px rgba(139, 69, 19, 0.1);
329
- background: #fcfaf5;
330
- }
331
-
332
- .noskid-input::placeholder {
333
- color: rgba(139, 69, 19, 0.5);
334
- }
335
-
336
- .noskid-btn {
337
- background: linear-gradient(135deg, #8b4513 0%, #6d3410 100%);
338
- color: #fcfaf5;
339
- border: none;
340
- padding: 16px 32px;
341
- border-radius: 12px;
342
- cursor: pointer;
343
- font-size: 16px;
344
- font-weight: 600;
345
- font-family: system-ui, -apple-system, sans-serif;
346
- transition: all 0.3s ease;
347
- box-shadow: 0 4px 12px rgba(139, 69, 19, 0.2);
348
- position: relative;
349
- overflow: hidden;
350
- }
351
-
352
- .noskid-btn::before {
353
- content: '';
354
- position: absolute;
355
- top: 0;
356
- left: -100%;
357
- width: 100%;
358
- height: 100%;
359
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
360
- transition: left 0.5s ease;
361
- }
362
-
363
- .noskid-btn:hover::before {
364
- left: 100%;
365
- }
366
-
367
- .noskid-btn:hover {
368
- background: linear-gradient(135deg, #6d3410 0%, #5a2b0d 100%);
369
- transform: translateY(-2px);
370
- box-shadow: 0 6px 20px rgba(139, 69, 19, 0.3);
371
- }
372
-
373
- .noskid-btn:active {
374
- transform: translateY(0);
375
- }
376
-
377
- .noskid-btn:disabled {
378
- background: rgba(139, 69, 19, 0.3);
379
- cursor: not-allowed;
380
- transform: none;
381
- box-shadow: none;
382
- }
383
-
384
- .noskid-btn:disabled::before {
385
- display: none;
386
- }
387
-
388
- .noskid-btn-secondary {
389
- background: linear-gradient(135deg, rgba(139, 69, 19, 0.1) 0%, rgba(139, 69, 19, 0.2) 100%);
390
- color: #8b4513;
391
- border: 2px solid rgba(139, 69, 19, 0.3);
392
- padding: 14px 30px;
393
- border-radius: 12px;
394
- cursor: pointer;
395
- font-size: 16px;
396
- font-weight: 600;
397
- font-family: system-ui, -apple-system, sans-serif;
398
- transition: all 0.3s ease;
399
- }
400
-
401
- .noskid-btn-secondary:hover {
402
- background: linear-gradient(135deg, rgba(139, 69, 19, 0.2) 0%, rgba(139, 69, 19, 0.3) 100%);
403
- border-color: #8b4513;
404
- transform: translateY(-2px);
405
- }
406
-
407
- .noskid-error {
408
- color: #dc2626;
409
- font-size: 14px;
410
- margin-top: 8px;
411
- padding: 12px;
412
- background: rgba(220, 38, 38, 0.1);
413
- border: 1px solid rgba(220, 38, 38, 0.2);
414
- border-radius: 8px;
415
- font-family: system-ui, -apple-system, sans-serif;
416
- }
417
-
418
- .noskid-success {
419
- color: #059669;
420
- font-size: 14px;
421
- margin-top: 8px;
422
- padding: 12px;
423
- background: rgba(5, 150, 105, 0.1);
424
- border: 1px solid rgba(5, 150, 105, 0.2);
425
- border-radius: 8px;
426
- font-family: system-ui, -apple-system, sans-serif;
427
- display: flex;
428
- align-items: center;
429
- gap: 8px;
430
- }
431
-
432
- .noskid-actions {
433
- display: flex;
434
- gap: 16px;
435
- justify-content: flex-end;
436
- margin-top: 32px;
437
- }
438
-
439
- .noskid-cert-info {
440
- background: rgba(139, 69, 19, 0.05);
441
- border: 1px solid rgba(139, 69, 19, 0.2);
442
- border-radius: 8px;
443
- padding: 16px;
444
- margin-top: 12px;
445
- }
446
-
447
- .noskid-cert-user {
448
- font-weight: 600;
449
- color: #8b4513;
450
- margin-bottom: 4px;
451
- }
452
-
453
- .noskid-cert-details {
454
- font-size: 12px;
455
- color: rgba(139, 69, 19, 0.7);
456
- }
457
-
458
- .noskid-paste-hint {
459
- margin-top: 8px;
460
- font-size: 11px;
461
- color: rgba(139, 69, 19, 0.5);
462
- text-align: center;
463
- font-style: italic;
464
- }
465
-
466
- @media (max-width: 600px) {
467
- .noskid-content {
468
- padding: 24px;
469
- }
470
-
471
- .noskid-header {
472
- padding: 20px;
473
- }
474
-
475
- .noskid-title {
476
- font-size: 20px;
477
- }
478
-
479
- .noskid-actions {
480
- flex-direction: column-reverse;
481
- }
482
-
483
- .noskid-btn,
484
- .noskid-btn-secondary {
485
- width: 100%;
486
- justify-content: center;
487
- }
488
- }
489
- `;
490
- document.head.appendChild(style);
491
-
492
- modal.innerHTML = `
493
- <div class="noskid-header">
494
- <h2 class="noskid-title">
495
- <div class="noskid-icon">🔐</div>
496
- Login with NoSkid
497
- </h2>
498
- <p class="noskid-subtitle">Upload your certificate and enter your password to authenticate</p>
499
- </div>
500
-
501
- <div class="noskid-content">
502
- <div class="noskid-form-group">
503
- <label class="noskid-label">Certificate File</label>
504
- <div class="noskid-cert-upload" id="noskid-cert-upload">
505
- <div class="noskid-upload-icon">📄</div>
506
- <div class="noskid-upload-text">Drop certificate here or click to browse</div>
507
- <div class="noskid-upload-hint">
508
- Supports .png files • Drag & drop • Paste from clipboard<br>
509
- <kbd>Ctrl+V</kbd> to paste certificate
510
- </div>
511
- <input type="file" id="noskid-cert-file" accept=".png" class="noskid-file-input">
512
- </div>
513
- <div id="noskid-file-error" class="noskid-error" style="display: none;"></div>
514
- <div id="noskid-file-success" class="noskid-success" style="display: none;">
515
- <span>✓</span>
516
- <div id="noskid-cert-info"></div>
517
- </div>
518
- </div>
519
-
520
- <div class="noskid-form-group">
521
- <label class="noskid-label">Password</label>
522
- <input type="password" id="noskid-password" class="noskid-input" placeholder="Enter your password">
523
- <div id="noskid-password-error" class="noskid-error" style="display: none;"></div>
524
- </div>
525
-
526
- <div id="noskid-login-error" class="noskid-error" style="display: none;"></div>
527
-
528
- <div class="noskid-actions">
529
- <button id="noskid-cancel-btn" class="noskid-btn-secondary">Cancel</button>
530
- <button id="noskid-login-btn" class="noskid-btn" disabled>
531
- <span>Login</span>
532
- </button>
533
- </div>
534
- </div>
535
- `;
536
-
537
- overlay.appendChild(modal);
538
- document.body.appendChild(overlay);
539
- this.currentLoginModal = overlay;
540
-
541
- requestAnimationFrame(() => {
542
- overlay.classList.add('noskid-modal-active');
543
- });
544
-
545
- const uploadArea = modal.querySelector('#noskid-cert-upload');
546
- const fileInput = modal.querySelector('#noskid-cert-file');
547
- const passwordInput = modal.querySelector('#noskid-password');
548
- const loginBtn = modal.querySelector('#noskid-login-btn');
549
- const cancelBtn = modal.querySelector('#noskid-cancel-btn');
550
- const fileError = modal.querySelector('#noskid-file-error');
551
- const fileSuccess = modal.querySelector('#noskid-file-success');
552
- const certInfo = modal.querySelector('#noskid-cert-info');
553
- const passwordError = modal.querySelector('#noskid-password-error');
554
- const loginError = modal.querySelector('#noskid-login-error');
555
-
556
- let certificateValid = false;
557
- let tempCertData = null;
558
-
559
- const processFile = async (file) => {
560
- fileError.style.display = 'none';
561
- fileSuccess.style.display = 'none';
562
- uploadArea.classList.remove('error', 'success');
563
- certificateValid = false;
564
- tempCertData = null;
565
- updateLoginButton();
566
-
567
- if (!file) return;
568
-
569
- try {
570
- this.nskdLbrLog('Processing certificate for login...', 'info');
571
-
572
- const tempInstance = new NskdLbr({
573
- apiUrl: this.apiUrl,
574
- debug: this.debug,
575
- timeout: this.timeout,
576
- strictCheck: this.strictCheck,
577
- useLegacyAPI: this.useLegacyAPI
578
- });
579
-
580
- const result = await tempInstance.loadFromFile(file);
581
-
582
- if (result.valid) {
583
- certificateValid = true;
584
- tempCertData = tempInstance.getCertificateData();
585
-
586
- certInfo.innerHTML = `
587
- <div class="noskid-cert-user">${tempCertData.localUsername || 'Unknown User'}</div>
588
- <div class="noskid-cert-details">
589
- Certificate verified • ${(file.size / 1024).toFixed(1)} KB
590
- </div>
591
- `;
592
-
593
- uploadArea.classList.add('success');
594
- uploadArea.querySelector('.noskid-upload-text').textContent = 'Certificate loaded successfully!';
595
- uploadArea.querySelector('.noskid-upload-icon').textContent = '✓';
596
- fileSuccess.style.display = 'block';
597
-
598
- this.nskdLbrLog('Certificate verified for login', 'success');
599
- } else {
600
- throw new Error(result.message || 'Certificate verification failed');
601
- }
602
- } catch (error) {
603
- uploadArea.classList.add('error');
604
- uploadArea.querySelector('.noskid-upload-text').textContent = 'Certificate verification failed';
605
- uploadArea.querySelector('.noskid-upload-icon').textContent = '❌';
606
- fileError.textContent = error.message;
607
- fileError.style.display = 'block';
608
- this.nskdLbrLog(`Certificate verification failed: ${error.message}`, 'error');
609
- }
610
-
611
- updateLoginButton();
612
- };
613
-
614
- fileInput.addEventListener('change', async (e) => {
615
- const file = e.target.files[0];
616
- await processFile(file);
617
- });
618
-
619
- uploadArea.addEventListener('dragover', (e) => {
620
- e.preventDefault();
621
- uploadArea.classList.add('dragover');
622
- });
623
-
624
- uploadArea.addEventListener('dragleave', (e) => {
625
- e.preventDefault();
626
- if (!uploadArea.contains(e.relatedTarget)) {
627
- uploadArea.classList.remove('dragover');
628
- }
629
- });
630
-
631
- uploadArea.addEventListener('drop', async (e) => {
632
- e.preventDefault();
633
- uploadArea.classList.remove('dragover');
634
-
635
- const files = e.dataTransfer.files;
636
- if (files.length > 0) {
637
- const file = files[0];
638
- if (file.type === 'image/png' || file.name.endsWith('.png')) {
639
- await processFile(file);
640
- } else {
641
- uploadArea.classList.add('error');
642
- fileError.textContent = 'Please drop a valid PNG certificate file';
643
- fileError.style.display = 'block';
644
- }
645
- }
646
- });
647
-
648
- document.addEventListener('paste', async (e) => {
649
- if (!overlay.classList.contains('noskid-modal-active')) return;
650
-
651
- const items = e.clipboardData.items;
652
- for (let item of items) {
653
- if (item.type === 'image/png') {
654
- e.preventDefault();
655
- const file = item.getAsFile();
656
- if (file) {
657
- await processFile(file);
658
- }
659
- break;
660
- }
661
- }
662
- });
663
-
664
- passwordInput.addEventListener('input', () => {
665
- passwordError.style.display = 'none';
666
- loginError.style.display = 'none';
667
- updateLoginButton();
668
- });
669
-
670
- const updateLoginButton = () => {
671
- const hasPassword = passwordInput.value.trim().length > 0;
672
- loginBtn.disabled = !certificateValid || !hasPassword;
673
-
674
- loginBtn.innerHTML = '<span>Login</span>';
675
- };
676
-
677
- loginBtn.addEventListener('click', async () => {
678
- const password = passwordInput.value.trim();
679
-
680
- if (!certificateValid || !tempCertData) {
681
- passwordError.textContent = 'Please upload a valid certificate first';
682
- passwordError.style.display = 'block';
683
- return;
684
- }
685
-
686
- if (!password) {
687
- passwordError.textContent = 'Please enter your password';
688
- passwordError.style.display = 'block';
689
- return;
690
- }
691
-
692
- loginBtn.disabled = true;
693
- loginBtn.innerHTML = '<span>Logging in...</span>';
694
- loginError.style.display = 'none';
695
-
696
- try {
697
- const loginResult = await this.performLogin(tempCertData, password);
698
-
699
- if (loginResult.success) {
700
- this.nskdLbrLog('Login successful', 'success');
701
-
702
- this.certificateData = tempCertData;
703
- this.verificationKey = tempCertData.key;
704
- this.isValid = true;
705
-
706
- if (this.onLoginSuccess && typeof this.onLoginSuccess === 'function') {
707
- this.onLoginSuccess(loginResult, tempCertData);
708
- }
709
-
710
- loginBtn.innerHTML = '<span>Success!</span>';
711
- setTimeout(() => {
712
- this.closeLoginModal();
713
- resolve({ success: true, data: tempCertData, response: loginResult });
714
- }, 800);
715
- } else {
716
- throw new Error(loginResult.message || 'Login failed');
717
- }
718
- } catch (error) {
719
- this.nskdLbrLog(`Login failed: ${error.message}`, 'error');
720
- loginError.textContent = error.message;
721
- loginError.style.display = 'block';
722
-
723
- if (this.onLoginFail && typeof this.onLoginFail === 'function') {
724
- this.onLoginFail(error, tempCertData);
725
- }
726
-
727
- loginBtn.disabled = false;
728
- loginBtn.innerHTML = '<span>Try Again</span>';
729
- }
730
- });
731
-
732
- cancelBtn.addEventListener('click', () => {
733
- this.nskdLbrLog('Login cancelled by user', 'info');
734
- this.closeLoginModal();
735
- reject(new Error('Login cancelled by user'));
736
- });
737
-
738
- overlay.addEventListener('click', (e) => {
739
- if (e.target === overlay) {
740
- this.nskdLbrLog('Login cancelled by clicking outside', 'info');
741
- this.closeLoginModal();
742
- reject(new Error('Login cancelled'));
743
- }
744
- });
745
-
746
- const escHandler = (e) => {
747
- if (e.key === 'Escape') {
748
- this.nskdLbrLog('Login cancelled by ESC key', 'info');
749
- this.closeLoginModal();
750
- document.removeEventListener('keydown', escHandler);
751
- reject(new Error('Login cancelled'));
752
- }
753
- };
754
- document.addEventListener('keydown', escHandler);
755
-
756
- if (certificateValid) {
757
- passwordInput.focus();
758
- }
759
- }
760
-
761
- /**
762
- * Perform login API call
763
- * @private
764
- */
765
- async performLogin(certData, password) {
766
- const controller = new AbortController();
767
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
768
-
769
- try {
770
- const response = await fetch(this.loginEndpoint, {
771
- method: 'POST',
772
- headers: {
773
- 'Content-Type': 'application/json',
774
- 'User-Agent': 'NskdLbr/1.1.0'
775
- },
776
- body: JSON.stringify({
777
- certificate: {
778
- key: certData.key,
779
- username: certData.localUsername,
780
- nickname: certData.nickname,
781
- certificate_number: certData.certificate_number,
782
- percentage: certData.percentage,
783
- country: certData.country,
784
- countryCode: certData.countryCode,
785
- creationDate: certData.creationDate
786
- },
787
- password: password
788
- }),
789
- signal: controller.signal
790
- });
791
-
792
- clearTimeout(timeoutId);
793
-
794
- if (!response.ok) {
795
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
796
- }
797
-
798
- const result = await response.json();
799
- return result;
800
-
801
- } catch (error) {
802
- clearTimeout(timeoutId);
803
- if (error.name === 'AbortError') {
804
- throw new Error('Login timeout - server took too long to respond');
805
- }
806
- throw new Error(`Login API error: ${error.message}`);
807
- }
808
- }
809
-
810
- /**
811
- * Close the login modal
812
- * @private
813
- */
814
- closeLoginModal() {
815
- if (this.currentLoginModal) {
816
- this.currentLoginModal.remove();
817
- this.currentLoginModal = null;
818
- }
819
- }
820
-
821
58
  /**
822
59
  * Load and verify a certificate from a PNG file
823
60
  * @param {File} file - The PNG certificate file
@@ -949,7 +186,6 @@ Certificate Details:
949
186
  this.verificationKey = null;
950
187
  this.localData = null;
951
188
  this.isValid = false;
952
- this.closeLoginModal();
953
189
  this.nskdLbrLog('Certificate data reset', 'info');
954
190
  }
955
191