nskd-lbr 1.0.3 → 1.1.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.
@@ -1,8 +1,444 @@
1
- class NskdLbr{constructor(t={}){this.apiUrl=t.apiUrl||"https://check.noskid.today/",this.debug=t.debug||!1,this.timeout=t.timeout||1e4,this.strictCheck=void 0===t.strictCheck||t.strictCheck,this.onLog=t.onLog||null,this.useLegacyAPI=t.useLegacyAPI||!1,this.certificateData=null,this.verificationKey=null,this.localData=null,this.isValid=!1}nskdLbrLog(t,e="info"){if(this.debug){var r=`[${(new Date).toLocaleTimeString()}] NoSkid:`;switch(e){case"error":console.error(r,t);break;case"warning":console.warn(r,t);break;case"success":console.log(`%c${r} `+t,"color: green");break;default:console.log(r,t)}}this.onLog&&"function"==typeof this.onLog&&this.onLog(t,e)}async loadFromFile(t){try{if(this.nskdLbrLog("Starting certificate verification process...","info"),!t)throw new Error("No file provided");if(!t.name.toLowerCase().endsWith(".png"))throw new Error("File must be a PNG image");this.nskdLbrLog("Processing certificate file: "+t.name,"info");var e=await this.readFileAsArrayBuffer(t),r=await this.extractTextFromPng(e);if(!r)throw new Error("Could not extract verification data from file");if(this.verificationKey=this.extractVerificationKey(r),this.verificationKey)return this.nskdLbrLog("Successfully extracted verification key","success"),this.localData=this.extractLocalData(r),this.localData&&(this.nskdLbrLog("Local certificate data extracted:","info"),this.nskdLbrLog("Username: "+this.localData.username,"info"),this.nskdLbrLog("Creation Date: "+this.localData.creationDate,"info")),await this.verifyWithAPI();throw new Error("No valid verification key found in certificate")}catch(t){throw this.nskdLbrLog("Error loading certificate: "+t.message,"error"),t}}async verifyWithKey(t){try{if(!t||"string"!=typeof t)throw new Error("Invalid verification key provided");if(/^[a-f0-9]{64}$/i.test(t))return this.verificationKey=t.toLowerCase(),this.nskdLbrLog(`Verifying certificate with key: ${this.verificationKey.substring(0,16)}...`,"info"),await this.verifyWithAPI();throw new Error("Verification key must be a 64-character hexadecimal string")}catch(t){throw this.nskdLbrLog("Error verifying certificate: "+t.message,"error"),t}}getCertificateData(){return this.certificateData?{...this.certificateData,key:this.verificationKey,localUsername:this.localData?this.localData.username:null,localCreationDate:this.localData?this.localData.creationDate:null}:null}isValidCertificate(){return this.isValid}getFormattedDetails(){var t,e;return this.certificateData?(t=this.certificateData,e=!this.useLegacyAPI&&t.nickname||t.username,`
1
+ class NskdLbr{constructor(e={}){this.apiUrl=e.apiUrl||"https://check.noskid.today/",this.debug=e.debug||!1,this.timeout=e.timeout||1e4,this.strictCheck=void 0===e.strictCheck||e.strictCheck,this.onLog=e.onLog||null,this.useLegacyAPI=e.useLegacyAPI||!1,this.loginEndpoint=e.loginEndpoint||"",this.onLoginSuccess=e.onLoginSuccess||null,this.onLoginFail=e.onLoginFail||null,this.certificateData=null,this.verificationKey=null,this.localData=null,this.isValid=!1,this.currentLoginModal=null}nskdLbrLog(e,t="info"){if(this.debug){var i=`[${(new Date).toLocaleTimeString()}] NoSkid:`;switch(t){case"error":console.error(i,e);break;case"warning":console.warn(i,e);break;case"success":console.log(`%c${i} `+e,"color: green");break;default:console.log(i,e)}}this.onLog&&"function"==typeof this.onLog&&this.onLog(e,t)}async showLoginModal(){if("undefined"==typeof window)throw new Error("Login modal is only available in browser environments");if(this.loginEndpoint&&""!==this.loginEndpoint.trim())return new Promise((e,t)=>{this.createLoginModal(e,t)});throw new Error("Login endpoint is not configured")}createLoginModal(i,t){this.currentLoginModal&&this.closeLoginModal();let r=document.createElement("div");r.id="noskid-login-overlay",r.style.cssText=`
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ background: rgba(139, 69, 19, 0.15);
8
+ display: flex;
9
+ justify-content: center;
10
+ align-items: center;
11
+ z-index: 10000;
12
+ backdrop-filter: blur(8px);
13
+ opacity: 0;
14
+ transition: opacity 0.3s ease;
15
+ `;var e=document.createElement("div"),a=(e.id="noskid-login-modal",e.style.cssText=`
16
+ background: linear-gradient(135deg, #fcfaf5 0%, #f0ede0 100%);
17
+ border: 2px solid #8b4513;
18
+ border-radius: 16px;
19
+ padding: 0;
20
+ max-width: 480px;
21
+ width: 95%;
22
+ max-height: 95vh;
23
+ overflow: hidden;
24
+ box-shadow:
25
+ 0 25px 50px -12px rgba(139, 69, 19, 0.25),
26
+ 0 0 0 1px rgba(139, 69, 19, 0.1),
27
+ inset 0 1px 0 rgba(252, 250, 245, 0.9);
28
+ transform: translateY(20px) scale(0.95);
29
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
30
+ position: relative;
31
+ `,document.createElement("style"));a.textContent=`
32
+ @keyframes noskidModalSlideIn {
33
+ from {
34
+ opacity: 0;
35
+ transform: translateY(20px) scale(0.95);
36
+ }
37
+ to {
38
+ opacity: 1;
39
+ transform: translateY(0) scale(1);
40
+ }
41
+ }
42
+
43
+ @keyframes noskidPulse {
44
+ 0%, 100% { transform: scale(1); }
45
+ 50% { transform: scale(1.05); }
46
+ }
47
+
48
+ @keyframes noskidShake {
49
+ 0%, 100% { transform: translateX(0); }
50
+ 25% { transform: translateX(-4px); }
51
+ 75% { transform: translateX(4px); }
52
+ }
53
+
54
+ .noskid-modal-active {
55
+ opacity: 1 !important;
56
+ }
57
+
58
+ .noskid-modal-active #noskid-login-modal {
59
+ transform: translateY(0) scale(1) !important;
60
+ }
61
+
62
+ .noskid-header {
63
+ background: linear-gradient(135deg, #f9f7f0 0%, #e8e5d8 100%);
64
+ border-bottom: 1px solid #8b4513;
65
+ padding: 24px;
66
+ text-align: center;
67
+ position: relative;
68
+ }
69
+
70
+ .noskid-header::before {
71
+ content: '';
72
+ position: absolute;
73
+ top: 0;
74
+ left: 0;
75
+ right: 0;
76
+ height: 1px;
77
+ background: linear-gradient(90deg, transparent, rgba(139, 69, 19, 0.3), transparent);
78
+ }
79
+
80
+ .noskid-content {
81
+ padding: 32px;
82
+ overflow-y: auto;
83
+ max-height: calc(95vh - 140px);
84
+ }
85
+
86
+ .noskid-title {
87
+ margin: 0;
88
+ color: #8b4513;
89
+ font-size: 24px;
90
+ font-family: 'Times New Roman', serif;
91
+ font-weight: bold;
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ gap: 12px;
96
+ }
97
+
98
+ .noskid-subtitle {
99
+ margin: 8px 0 0 0;
100
+ color: rgba(139, 69, 19, 0.7);
101
+ font-size: 14px;
102
+ font-family: system-ui, -apple-system, sans-serif;
103
+ }
104
+
105
+ .noskid-icon {
106
+ width: 28px;
107
+ height: 28px;
108
+ background: #8b4513;
109
+ border-radius: 50%;
110
+ display: flex;
111
+ align-items: center;
112
+ justify-content: center;
113
+ color: #fcfaf5;
114
+ font-size: 16px;
115
+ box-shadow: 0 2px 4px rgba(139, 69, 19, 0.2);
116
+ }
117
+
118
+ .noskid-form-group {
119
+ margin-bottom: 24px;
120
+ }
121
+
122
+ .noskid-label {
123
+ display: block;
124
+ margin-bottom: 8px;
125
+ color: #8b4513;
126
+ font-size: 14px;
127
+ font-weight: 600;
128
+ font-family: system-ui, -apple-system, sans-serif;
129
+ }
130
+
131
+ .noskid-cert-upload {
132
+ border: 2px dashed rgba(139, 69, 19, 0.3);
133
+ border-radius: 12px;
134
+ padding: 24px;
135
+ text-align: center;
136
+ background: linear-gradient(135deg, rgba(252, 250, 245, 0.5), rgba(240, 237, 224, 0.5));
137
+ transition: all 0.3s ease;
138
+ cursor: pointer;
139
+ position: relative;
140
+ min-height: 120px;
141
+ display: flex;
142
+ flex-direction: column;
143
+ align-items: center;
144
+ justify-content: center;
145
+ }
146
+
147
+ .noskid-cert-upload:hover {
148
+ border-color: #8b4513;
149
+ background: linear-gradient(135deg, rgba(252, 250, 245, 0.8), rgba(240, 237, 224, 0.8));
150
+ transform: translateY(-2px);
151
+ box-shadow: 0 4px 12px rgba(139, 69, 19, 0.1);
152
+ }
153
+
154
+ .noskid-cert-upload.dragover {
155
+ border-color: #8b4513;
156
+ background: linear-gradient(135deg, rgba(139, 69, 19, 0.1), rgba(139, 69, 19, 0.05));
157
+ animation: noskidPulse 1s infinite;
158
+ }
159
+
160
+ .noskid-cert-upload.error {
161
+ border-color: #dc2626;
162
+ animation: noskidShake 0.5s ease-in-out;
163
+ }
164
+
165
+ .noskid-cert-upload.success {
166
+ border-color: #059669;
167
+ background: linear-gradient(135deg, rgba(5, 150, 105, 0.1), rgba(5, 150, 105, 0.05));
168
+ }
169
+
170
+ .noskid-upload-icon {
171
+ width: 48px;
172
+ height: 48px;
173
+ margin-bottom: 12px;
174
+ background: rgba(139, 69, 19, 0.1);
175
+ border-radius: 50%;
176
+ display: flex;
177
+ align-items: center;
178
+ justify-content: center;
179
+ color: #8b4513;
180
+ font-size: 24px;
181
+ transition: all 0.3s ease;
182
+ }
183
+
184
+ .noskid-cert-upload:hover .noskid-upload-icon {
185
+ background: rgba(139, 69, 19, 0.2);
186
+ transform: scale(1.1);
187
+ }
188
+
189
+ .noskid-upload-text {
190
+ color: #8b4513;
191
+ font-size: 16px;
192
+ font-weight: 600;
193
+ margin-bottom: 4px;
194
+ }
195
+
196
+ .noskid-upload-hint {
197
+ color: rgba(139, 69, 19, 0.6);
198
+ font-size: 12px;
199
+ line-height: 1.4;
200
+ }
201
+
202
+ .noskid-file-input {
203
+ position: absolute;
204
+ top: 0;
205
+ left: 0;
206
+ width: 100%;
207
+ height: 100%;
208
+ opacity: 0;
209
+ cursor: pointer;
210
+ }
211
+
212
+ .noskid-input {
213
+ width: 100%;
214
+ padding: 16px;
215
+ border: 2px solid rgba(139, 69, 19, 0.2);
216
+ border-radius: 12px;
217
+ font-size: 16px;
218
+ box-sizing: border-box;
219
+ background: rgba(252, 250, 245, 0.8);
220
+ color: #8b4513;
221
+ font-family: system-ui, -apple-system, sans-serif;
222
+ transition: all 0.3s ease;
223
+ }
224
+
225
+ .noskid-input:focus {
226
+ outline: none;
227
+ border-color: #8b4513;
228
+ box-shadow: 0 0 0 4px rgba(139, 69, 19, 0.1);
229
+ background: #fcfaf5;
230
+ }
231
+
232
+ .noskid-input::placeholder {
233
+ color: rgba(139, 69, 19, 0.5);
234
+ }
235
+
236
+ .noskid-btn {
237
+ background: linear-gradient(135deg, #8b4513 0%, #6d3410 100%);
238
+ color: #fcfaf5;
239
+ border: none;
240
+ padding: 16px 32px;
241
+ border-radius: 12px;
242
+ cursor: pointer;
243
+ font-size: 16px;
244
+ font-weight: 600;
245
+ font-family: system-ui, -apple-system, sans-serif;
246
+ transition: all 0.3s ease;
247
+ box-shadow: 0 4px 12px rgba(139, 69, 19, 0.2);
248
+ position: relative;
249
+ overflow: hidden;
250
+ }
251
+
252
+ .noskid-btn::before {
253
+ content: '';
254
+ position: absolute;
255
+ top: 0;
256
+ left: -100%;
257
+ width: 100%;
258
+ height: 100%;
259
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
260
+ transition: left 0.5s ease;
261
+ }
262
+
263
+ .noskid-btn:hover::before {
264
+ left: 100%;
265
+ }
266
+
267
+ .noskid-btn:hover {
268
+ background: linear-gradient(135deg, #6d3410 0%, #5a2b0d 100%);
269
+ transform: translateY(-2px);
270
+ box-shadow: 0 6px 20px rgba(139, 69, 19, 0.3);
271
+ }
272
+
273
+ .noskid-btn:active {
274
+ transform: translateY(0);
275
+ }
276
+
277
+ .noskid-btn:disabled {
278
+ background: rgba(139, 69, 19, 0.3);
279
+ cursor: not-allowed;
280
+ transform: none;
281
+ box-shadow: none;
282
+ }
283
+
284
+ .noskid-btn:disabled::before {
285
+ display: none;
286
+ }
287
+
288
+ .noskid-btn-secondary {
289
+ background: linear-gradient(135deg, rgba(139, 69, 19, 0.1) 0%, rgba(139, 69, 19, 0.2) 100%);
290
+ color: #8b4513;
291
+ border: 2px solid rgba(139, 69, 19, 0.3);
292
+ padding: 14px 30px;
293
+ border-radius: 12px;
294
+ cursor: pointer;
295
+ font-size: 16px;
296
+ font-weight: 600;
297
+ font-family: system-ui, -apple-system, sans-serif;
298
+ transition: all 0.3s ease;
299
+ }
300
+
301
+ .noskid-btn-secondary:hover {
302
+ background: linear-gradient(135deg, rgba(139, 69, 19, 0.2) 0%, rgba(139, 69, 19, 0.3) 100%);
303
+ border-color: #8b4513;
304
+ transform: translateY(-2px);
305
+ }
306
+
307
+ .noskid-error {
308
+ color: #dc2626;
309
+ font-size: 14px;
310
+ margin-top: 8px;
311
+ padding: 12px;
312
+ background: rgba(220, 38, 38, 0.1);
313
+ border: 1px solid rgba(220, 38, 38, 0.2);
314
+ border-radius: 8px;
315
+ font-family: system-ui, -apple-system, sans-serif;
316
+ }
317
+
318
+ .noskid-success {
319
+ color: #059669;
320
+ font-size: 14px;
321
+ margin-top: 8px;
322
+ padding: 12px;
323
+ background: rgba(5, 150, 105, 0.1);
324
+ border: 1px solid rgba(5, 150, 105, 0.2);
325
+ border-radius: 8px;
326
+ font-family: system-ui, -apple-system, sans-serif;
327
+ display: flex;
328
+ align-items: center;
329
+ gap: 8px;
330
+ }
331
+
332
+ .noskid-actions {
333
+ display: flex;
334
+ gap: 16px;
335
+ justify-content: flex-end;
336
+ margin-top: 32px;
337
+ }
338
+
339
+ .noskid-cert-info {
340
+ background: rgba(139, 69, 19, 0.05);
341
+ border: 1px solid rgba(139, 69, 19, 0.2);
342
+ border-radius: 8px;
343
+ padding: 16px;
344
+ margin-top: 12px;
345
+ }
346
+
347
+ .noskid-cert-user {
348
+ font-weight: 600;
349
+ color: #8b4513;
350
+ margin-bottom: 4px;
351
+ }
352
+
353
+ .noskid-cert-details {
354
+ font-size: 12px;
355
+ color: rgba(139, 69, 19, 0.7);
356
+ }
357
+
358
+ .noskid-paste-hint {
359
+ margin-top: 8px;
360
+ font-size: 11px;
361
+ color: rgba(139, 69, 19, 0.5);
362
+ text-align: center;
363
+ font-style: italic;
364
+ }
365
+
366
+ @media (max-width: 600px) {
367
+ .noskid-content {
368
+ padding: 24px;
369
+ }
370
+
371
+ .noskid-header {
372
+ padding: 20px;
373
+ }
374
+
375
+ .noskid-title {
376
+ font-size: 20px;
377
+ }
378
+
379
+ .noskid-actions {
380
+ flex-direction: column-reverse;
381
+ }
382
+
383
+ .noskid-btn,
384
+ .noskid-btn-secondary {
385
+ width: 100%;
386
+ justify-content: center;
387
+ }
388
+ }
389
+ `,document.head.appendChild(a),e.innerHTML=`
390
+ <div class="noskid-header">
391
+ <h2 class="noskid-title">
392
+ <div class="noskid-icon">🔐</div>
393
+ Login with NoSkid
394
+ </h2>
395
+ <p class="noskid-subtitle">Upload your certificate and enter your password to authenticate</p>
396
+ </div>
397
+
398
+ <div class="noskid-content">
399
+ <div class="noskid-form-group">
400
+ <label class="noskid-label">Certificate File</label>
401
+ <div class="noskid-cert-upload" id="noskid-cert-upload">
402
+ <div class="noskid-upload-icon">📄</div>
403
+ <div class="noskid-upload-text">Drop certificate here or click to browse</div>
404
+ <div class="noskid-upload-hint">
405
+ Supports .png files • Drag & drop • Paste from clipboard<br>
406
+ <kbd>Ctrl+V</kbd> to paste certificate
407
+ </div>
408
+ <input type="file" id="noskid-cert-file" accept=".png" class="noskid-file-input">
409
+ </div>
410
+ <div id="noskid-file-error" class="noskid-error" style="display: none;"></div>
411
+ <div id="noskid-file-success" class="noskid-success" style="display: none;">
412
+ <span>✓</span>
413
+ <div id="noskid-cert-info"></div>
414
+ </div>
415
+ </div>
416
+
417
+ <div class="noskid-form-group">
418
+ <label class="noskid-label">Password</label>
419
+ <input type="password" id="noskid-password" class="noskid-input" placeholder="Enter your password">
420
+ <div id="noskid-password-error" class="noskid-error" style="display: none;"></div>
421
+ </div>
422
+
423
+ <div id="noskid-login-error" class="noskid-error" style="display: none;"></div>
424
+
425
+ <div class="noskid-actions">
426
+ <button id="noskid-cancel-btn" class="noskid-btn-secondary">Cancel</button>
427
+ <button id="noskid-login-btn" class="noskid-btn" disabled>
428
+ <span>Login</span>
429
+ </button>
430
+ </div>
431
+ </div>
432
+ `,r.appendChild(e),document.body.appendChild(r),this.currentLoginModal=r,requestAnimationFrame(()=>{r.classList.add("noskid-modal-active")});let o=e.querySelector("#noskid-cert-upload");a=e.querySelector("#noskid-cert-file");let s=e.querySelector("#noskid-password"),n=e.querySelector("#noskid-login-btn");var d=e.querySelector("#noskid-cancel-btn");let l=e.querySelector("#noskid-file-error"),c=e.querySelector("#noskid-file-success"),g=e.querySelector("#noskid-cert-info"),f=e.querySelector("#noskid-password-error"),p=e.querySelector("#noskid-login-error"),u=!1,h=null,b=async e=>{if(l.style.display="none",c.style.display="none",o.classList.remove("error","success"),u=!1,h=null,k(),e){try{this.nskdLbrLog("Processing certificate for login...","info");var t=new NskdLbr({apiUrl:this.apiUrl,debug:this.debug,timeout:this.timeout,strictCheck:this.strictCheck,useLegacyAPI:this.useLegacyAPI}),i=await t.loadFromFile(e);if(!i.valid)throw new Error(i.message||"Certificate verification failed");u=!0,h=t.getCertificateData(),g.innerHTML=`
433
+ <div class="noskid-cert-user">${h.localUsername||"Unknown User"}</div>
434
+ <div class="noskid-cert-details">
435
+ Certificate verified • ${(e.size/1024).toFixed(1)} KB
436
+ </div>
437
+ `,o.classList.add("success"),o.querySelector(".noskid-upload-text").textContent="Certificate loaded successfully!",o.querySelector(".noskid-upload-icon").textContent="✓",c.style.display="block",this.nskdLbrLog("Certificate verified for login","success")}catch(e){o.classList.add("error"),o.querySelector(".noskid-upload-text").textContent="Certificate verification failed",o.querySelector(".noskid-upload-icon").textContent="❌",l.textContent=e.message,l.style.display="block",this.nskdLbrLog("Certificate verification failed: "+e.message,"error")}k()}},k=(a.addEventListener("change",async e=>{e=e.target.files[0];await b(e)}),o.addEventListener("dragover",e=>{e.preventDefault(),o.classList.add("dragover")}),o.addEventListener("dragleave",e=>{e.preventDefault(),o.contains(e.relatedTarget)||o.classList.remove("dragover")}),o.addEventListener("drop",async e=>{e.preventDefault(),o.classList.remove("dragover");var e=e.dataTransfer.files;0<e.length&&("image/png"===(e=e[0]).type||e.name.endsWith(".png")?await b(e):(o.classList.add("error"),l.textContent="Please drop a valid PNG certificate file",l.style.display="block"))}),document.addEventListener("paste",async e=>{var t;if(r.classList.contains("noskid-modal-active"))for(t of e.clipboardData.items)if("image/png"===t.type){e.preventDefault();var i=t.getAsFile();i&&await b(i);break}}),s.addEventListener("input",()=>{f.style.display="none",p.style.display="none",k()}),()=>{var e=0<s.value.trim().length;n.disabled=!u||!e,n.innerHTML="<span>Login</span>"}),m=(n.addEventListener("click",async()=>{var t=s.value.trim();if(u&&h)if(t){n.disabled=!0,n.innerHTML="<span>Logging in...</span>",p.style.display="none";try{let e=await this.performLogin(h,t);if(!e.success)throw new Error(e.message||"Login failed");this.nskdLbrLog("Login successful","success"),this.certificateData=h,this.verificationKey=h.key,this.isValid=!0,this.onLoginSuccess&&"function"==typeof this.onLoginSuccess&&this.onLoginSuccess(e,h),n.innerHTML="<span>Success!</span>",setTimeout(()=>{this.closeLoginModal(),i({success:!0,data:h,response:e})},800)}catch(e){this.nskdLbrLog("Login failed: "+e.message,"error"),p.textContent=e.message,p.style.display="block",this.onLoginFail&&"function"==typeof this.onLoginFail&&this.onLoginFail(e,h),n.disabled=!1,n.innerHTML="<span>Try Again</span>"}}else f.textContent="Please enter your password",f.style.display="block";else f.textContent="Please upload a valid certificate first",f.style.display="block"}),d.addEventListener("click",()=>{this.nskdLbrLog("Login cancelled by user","info"),this.closeLoginModal(),t(new Error("Login cancelled by user"))}),r.addEventListener("click",e=>{e.target===r&&(this.nskdLbrLog("Login cancelled by clicking outside","info"),this.closeLoginModal(),t(new Error("Login cancelled")))}),e=>{"Escape"===e.key&&(this.nskdLbrLog("Login cancelled by ESC key","info"),this.closeLoginModal(),document.removeEventListener("keydown",m),t(new Error("Login cancelled")))});document.addEventListener("keydown",m),u&&s.focus()}async performLogin(e,t){let i=new AbortController;var r=setTimeout(()=>i.abort(),this.timeout);try{var a=await fetch(this.loginEndpoint,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":"NskdLbr/1.1.0"},body:JSON.stringify({certificate:{key:e.key,username:e.localUsername,nickname:e.nickname,certificate_number:e.certificate_number,percentage:e.percentage,country:e.country,countryCode:e.countryCode,creationDate:e.creationDate},password:t}),signal:i.signal});if(clearTimeout(r),a.ok)return await a.json();throw new Error(`HTTP ${a.status}: `+a.statusText)}catch(e){if(clearTimeout(r),"AbortError"===e.name)throw new Error("Login timeout - server took too long to respond");throw new Error("Login API error: "+e.message)}}closeLoginModal(){this.currentLoginModal&&(this.currentLoginModal.remove(),this.currentLoginModal=null)}async loadFromFile(e){try{if(this.nskdLbrLog("Starting certificate verification process...","info"),!e)throw new Error("No file provided");if(!e.name.toLowerCase().endsWith(".png"))throw new Error("File must be a PNG image");this.nskdLbrLog("Processing certificate file: "+e.name,"info");var t=await this.readFileAsArrayBuffer(e),i=await this.extractTextFromPng(t);if(!i)throw new Error("Could not extract verification data from file");if(this.verificationKey=this.extractVerificationKey(i),this.verificationKey)return this.nskdLbrLog("Successfully extracted verification key","success"),this.localData=this.extractLocalData(i),this.localData&&(this.nskdLbrLog("Local certificate data extracted:","info"),this.nskdLbrLog("Username: "+this.localData.username,"info"),this.nskdLbrLog("Creation Date: "+this.localData.creationDate,"info")),await this.verifyWithAPI();throw new Error("No valid verification key found in certificate")}catch(e){throw this.nskdLbrLog("Error loading certificate: "+e.message,"error"),e}}async verifyWithKey(e){try{if(!e||"string"!=typeof e)throw new Error("Invalid verification key provided");if(/^[a-f0-9]{64}$/i.test(e))return this.verificationKey=e.toLowerCase(),this.nskdLbrLog(`Verifying certificate with key: ${this.verificationKey.substring(0,16)}...`,"info"),await this.verifyWithAPI();throw new Error("Verification key must be a 64-character hexadecimal string")}catch(e){throw this.nskdLbrLog("Error verifying certificate: "+e.message,"error"),e}}getCertificateData(){return this.certificateData?{...this.certificateData,key:this.verificationKey,localUsername:this.localData?this.localData.username:null,localCreationDate:this.localData?this.localData.creationDate:null}:null}isValidCertificate(){return this.isValid}getFormattedDetails(){var e,t;return this.certificateData?(e=this.certificateData,t=!this.useLegacyAPI&&e.nickname||e.username,`
2
438
  Certificate Details:
3
- - Certificate #: ${t.certificate_number}
4
- - Username: ${e}
5
- - Percentage: ${t.percentage}%
6
- - Creation Date: ${t.creationDate}
7
- - Country: ${t.country} (${t.countryCode})
8
- `.trim()):"No certificate data available"}reset(){this.certificateData=null,this.verificationKey=null,this.localData=null,this.isValid=!1,this.nskdLbrLog("Certificate data reset","info")}readFileAsArrayBuffer(a){return new Promise((e,t)=>{var r=new FileReader;r.onload=t=>e(t.target.result),r.onerror=()=>t(new Error("Error reading file")),r.readAsArrayBuffer(a)})}async extractTextFromPng(r){try{var a=new Uint8Array(r);if(137!==a[0]||80!==a[1]||78!==a[2]||71!==a[3])throw new Error("Not a valid PNG file");let t=8,e=null;for(;t<a.length-12;){var i=a[t]<<24|a[t+1]<<16|a[t+2]<<8|a[t+3];if("tEXt"===String.fromCharCode(a[t+4],a[t+5],a[t+6],a[t+7])){var s=a.slice(t+8,t+8+i),o=new TextDecoder("utf-8").decode(s),n=o.indexOf("\0");if(-1!==n){var c=o.substring(0,n),l=o.substring(n+1);if("noskid-key"===c){e=l;break}}}t+=8+i+4}return e?(this.nskdLbrLog("Certificate data extracted successfully from PNG","success"),e):(this.nskdLbrLog("No 'noskid-key' text chunk found in PNG","error"),null)}catch(t){return this.nskdLbrLog("Error extracting text from PNG: "+t.message,"error"),null}}extractVerificationKey(t){try{var e=/-*BEGIN NOSKID KEY-*\s*([a-f0-9]{64})/i,r=t.match(e);return r?r[1].toLowerCase():null}catch(t){return this.nskdLbrLog("Error extracting verification key: "+t.message,"error"),null}}extractLocalData(t){try{var e,r,a,i,s,o=/-----BEGIN NOSKID KEY-----\s*([a-f0-9]+)\s*([A-Za-z0-9+/=]+)\s*([A-Za-z0-9+/=]+)\s*-----END NOSKID KEY-----/,n=t.match(o);return n?(e=n[2],a=(r=atob(e.replace(/=/g,"")).match(/CERT-\d+-(.+)/))?r[1]:null,i=n[3],{username:a,creationDate:(s=atob(i.replace(/=/g,"")).match(/CREATED-(.+)/))?s[1]:null}):null}catch(t){return this.nskdLbrLog("Error extracting local data: "+t.message,"error"),null}}async verifyWithAPI(){try{this.nskdLbrLog("Verifying certificate with server...","info");let t=new AbortController;var e=setTimeout(()=>t.abort(),this.timeout),r=await fetch(this.apiUrl+"?key="+encodeURIComponent(this.verificationKey),{signal:t.signal,headers:{"User-Agent":"NskdLbr/1.0.0"}});if(clearTimeout(e),!r.ok)throw new Error(`HTTP ${r.status}: `+r.statusText);var a=await r.json();if(!a.success)return this.isValid=!1,this.nskdLbrLog("Certificate verification failed: "+a.message,"error"),{valid:!1,message:a.message,cached:a.cached||!1};if(this.localData&&this.strictCheck){var i=!this.useLegacyAPI&&a.data.nickname||a.data.username,s=this.compareData(this.localData,{...a.data,username:i});if(!s.valid)return this.isValid=!1,this.nskdLbrLog("Certificate data mismatch!","error"),this.nskdLbrLog("Mismatch reason: "+s.reason,"error"),this.nskdLbrLog("Note: Strict checking is enabled. Set strictCheck to false to skip local data validation.","warning"),{valid:!1,message:"Data mismatch: "+s.reason,cached:a.cached||!1,strictCheck:!0};this.nskdLbrLog("Local data validation passed","success")}else this.localData&&!this.strictCheck&&this.nskdLbrLog("Strict checking disabled - skipping local data validation","warning");return this.isValid=!0,this.certificateData=a.data,this.nskdLbrLog("Certificate is VALID!","success"),{valid:!0,message:"Certificate verified successfully",data:a.data,cached:a.cached||!1,strictCheck:this.strictCheck}}catch(t){if("AbortError"===t.name)throw new Error("Request timeout - server took too long to respond");throw new Error("API verification failed: "+t.message)}}compareData(t,e){return t&&e?t.username!==e.username?{valid:!1,reason:`Username mismatch: Local=${t.username}, API=`+e.username}:(t=t.creationDate.substring(0,16))!==(e=e.creationDate.substring(0,16))?{valid:!1,reason:`Creation date mismatch: Local=${t}, API=`+e}:{valid:!0}:{valid:!1,reason:"Missing data for comparison"}}}"undefined"!=typeof module&&module.exports?module.exports=NskdLbr:window.NskdLbr=NskdLbr;
439
+ - Certificate #: ${e.certificate_number}
440
+ - Username: ${t}
441
+ - Percentage: ${e.percentage}%
442
+ - Creation Date: ${e.creationDate}
443
+ - Country: ${e.country} (${e.countryCode})
444
+ `.trim()):"No certificate data available"}reset(){this.certificateData=null,this.verificationKey=null,this.localData=null,this.isValid=!1,this.closeLoginModal(),this.nskdLbrLog("Certificate data reset","info")}readFileAsArrayBuffer(r){return new Promise((t,e)=>{var i=new FileReader;i.onload=e=>t(e.target.result),i.onerror=()=>e(new Error("Error reading file")),i.readAsArrayBuffer(r)})}async extractTextFromPng(i){try{var r=new Uint8Array(i);if(137!==r[0]||80!==r[1]||78!==r[2]||71!==r[3])throw new Error("Not a valid PNG file");let e=8,t=null;for(;e<r.length-12;){var a=r[e]<<24|r[e+1]<<16|r[e+2]<<8|r[e+3];if("tEXt"===String.fromCharCode(r[e+4],r[e+5],r[e+6],r[e+7])){var o=r.slice(e+8,e+8+a),s=new TextDecoder("utf-8").decode(o),n=s.indexOf("\0");if(-1!==n){var d=s.substring(0,n),l=s.substring(n+1);if("noskid-key"===d){t=l;break}}}e+=8+a+4}return t?(this.nskdLbrLog("Certificate data extracted successfully from PNG","success"),t):(this.nskdLbrLog("No 'noskid-key' text chunk found in PNG","error"),null)}catch(e){return this.nskdLbrLog("Error extracting text from PNG: "+e.message,"error"),null}}extractVerificationKey(e){try{var t=/-*BEGIN NOSKID KEY-*\s*([a-f0-9]{64})/i,i=e.match(t);return i?i[1].toLowerCase():null}catch(e){return this.nskdLbrLog("Error extracting verification key: "+e.message,"error"),null}}extractLocalData(e){try{var t,i,r,a,o,s=/-----BEGIN NOSKID KEY-----\s*([a-f0-9]+)\s*([A-Za-z0-9+/=]+)\s*([A-Za-z0-9+/=]+)\s*-----END NOSKID KEY-----/,n=e.match(s);return n?(t=n[2],r=(i=atob(t.replace(/=/g,"")).match(/CERT-\d+-(.+)/))?i[1]:null,a=n[3],{username:r,creationDate:(o=atob(a.replace(/=/g,"")).match(/CREATED-(.+)/))?o[1]:null}):null}catch(e){return this.nskdLbrLog("Error extracting local data: "+e.message,"error"),null}}async verifyWithAPI(){try{this.nskdLbrLog("Verifying certificate with server...","info");let e=new AbortController;var t=setTimeout(()=>e.abort(),this.timeout),i=await fetch(this.apiUrl+"?key="+encodeURIComponent(this.verificationKey),{signal:e.signal,headers:{"User-Agent":"NskdLbr/1.1.0"}});if(clearTimeout(t),!i.ok)throw new Error(`HTTP ${i.status}: `+i.statusText);var r=await i.json();if(!r.success)return this.isValid=!1,this.nskdLbrLog("Certificate verification failed: "+r.message,"error"),{valid:!1,message:r.message,cached:r.cached||!1};if(this.localData&&this.strictCheck){var a=!this.useLegacyAPI&&r.data.nickname||r.data.username,o=this.compareData(this.localData,{...r.data,username:a});if(!o.valid)return this.isValid=!1,this.nskdLbrLog("Certificate data mismatch!","error"),this.nskdLbrLog("Mismatch reason: "+o.reason,"error"),this.nskdLbrLog("Note: Strict checking is enabled. Set strictCheck to false to skip local data validation.","warning"),{valid:!1,message:"Data mismatch: "+o.reason,cached:r.cached||!1,strictCheck:!0};this.nskdLbrLog("Local data validation passed","success")}else this.localData&&!this.strictCheck&&this.nskdLbrLog("Strict checking disabled - skipping local data validation","warning");return this.isValid=!0,this.certificateData=r.data,this.nskdLbrLog("Certificate is VALID!","success"),{valid:!0,message:"Certificate verified successfully",data:r.data,cached:r.cached||!1,strictCheck:this.strictCheck}}catch(e){if("AbortError"===e.name)throw new Error("Request timeout - server took too long to respond");throw new Error("API verification failed: "+e.message)}}compareData(e,t){return e&&t?e.username!==t.username?{valid:!1,reason:`Username mismatch: Local=${e.username}, API=`+t.username}:(e=e.creationDate.substring(0,16))!==(t=t.creationDate.substring(0,16))?{valid:!1,reason:`Creation date mismatch: Local=${e}, API=`+t}:{valid:!0}:{valid:!1,reason:"Missing data for comparison"}}}"undefined"!=typeof module&&module.exports?module.exports=NskdLbr:window.NskdLbr=NskdLbr;