@smartledger/bsv 3.4.0 → 3.4.2

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +60 -32
  3. package/bsv-anchor.min.js +12 -0
  4. package/bsv-covenant.min.js +8 -8
  5. package/bsv-didweb.min.js +12 -0
  6. package/bsv-gdaf.min.js +9 -9
  7. package/bsv-ltp.min.js +9 -9
  8. package/bsv-mnemonic.min.js +2 -2
  9. package/bsv-shamir.min.js +3 -3
  10. package/bsv-smartcontract.min.js +5 -5
  11. package/bsv-statuslist.min.js +18 -0
  12. package/bsv-vcjwt.min.js +12 -0
  13. package/bsv.bundle.js +9 -9
  14. package/bsv.min.js +5 -5
  15. package/build/webpack.anchor.config.js +9 -13
  16. package/build/webpack.didweb.config.js +10 -14
  17. package/build/webpack.statuslist.config.js +9 -14
  18. package/build/webpack.vcjwt.config.js +9 -13
  19. package/examples/legacy/README.md +11 -0
  20. package/index.js +24 -6
  21. package/lib/browser-utxo-manager-es5.js +11 -4
  22. package/lib/browser-utxo-manager.js +15 -8
  23. package/lib/ltp/claim.js +1 -0
  24. package/lib/ltp/obligation.js +1 -0
  25. package/lib/ltp/registry.js +2 -0
  26. package/lib/ltp/right.js +1 -0
  27. package/lib/transaction/transaction.js +1 -1
  28. package/lib/util/_.js +7 -1
  29. package/package.json +9 -11
  30. package/demos/gdaf_core_test.js +0 -131
  31. package/examples/scripts/custom_script_signature_test.js +0 -344
  32. package/tests/browser-compatibility/README.md +0 -35
  33. package/tests/browser-compatibility/test-cdn-vs-local.html +0 -186
  34. package/tests/browser-compatibility/test-pbkdf2.html +0 -51
  35. package/tests/bundle-completeness-test.html +0 -131
  36. package/tests/bundle-demo.html +0 -476
  37. package/tests/smartcontract-test.html +0 -239
  38. package/tests/standalone-modules-test.html +0 -260
  39. package/tests/test.html +0 -612
  40. package/tests/test_standalone_shamir.html +0 -83
  41. package/tests/unpkg-demo.html +0 -194
  42. package/utilities/blockchain-state.json +0 -118565
  43. /package/{lib/smart_contract/test_integration.js → examples/legacy/smart_contract_test_integration.js} +0 -0
  44. /package/{tests → examples/legacy}/test_builtin_verify.js +0 -0
  45. /package/{tests → examples/legacy}/test_debug_integration.js +0 -0
  46. /package/{tests → examples/legacy}/test_ecdsa_little.js +0 -0
  47. /package/{tests → examples/legacy}/test_shamir.js +0 -0
  48. /package/{tests → examples/legacy}/test_smartverify_der.js +0 -0
package/tests/test.html DELETED
@@ -1,612 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>SmartLedger BSV Security Test</title>
7
- <style>
8
- body {
9
- font-family: Arial, sans-serif;
10
- max-width: 1200px;
11
- margin: 0 auto;
12
- padding: 20px;
13
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
14
- color: white;
15
- }
16
-
17
- .container {
18
- background: rgba(255, 255, 255, 0.1);
19
- padding: 30px;
20
- border-radius: 15px;
21
- backdrop-filter: blur(10px);
22
- box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
23
- }
24
-
25
- h1 {
26
- text-align: center;
27
- margin-bottom: 30px;
28
- text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
29
- }
30
-
31
- .test-section {
32
- background: rgba(255, 255, 255, 0.05);
33
- padding: 20px;
34
- margin: 20px 0;
35
- border-radius: 10px;
36
- border-left: 4px solid #4CAF50;
37
- }
38
-
39
- .test-section h3 {
40
- margin-top: 0;
41
- color: #4CAF50;
42
- }
43
-
44
- .result {
45
- padding: 10px;
46
- margin: 10px 0;
47
- border-radius: 5px;
48
- font-weight: bold;
49
- }
50
-
51
- .success {
52
- background: rgba(76, 175, 80, 0.2);
53
- border: 1px solid #4CAF50;
54
- color: #4CAF50;
55
- }
56
-
57
- .error {
58
- background: rgba(244, 67, 54, 0.2);
59
- border: 1px solid #f44336;
60
- color: #f44336;
61
- }
62
-
63
- .info {
64
- background: rgba(33, 150, 243, 0.2);
65
- border: 1px solid #2196F3;
66
- color: #2196F3;
67
- }
68
-
69
- button {
70
- background: linear-gradient(45deg, #4CAF50, #45a049);
71
- color: white;
72
- border: none;
73
- padding: 12px 24px;
74
- border-radius: 6px;
75
- cursor: pointer;
76
- margin: 5px;
77
- font-size: 16px;
78
- transition: all 0.3s;
79
- }
80
-
81
- button:hover {
82
- transform: translateY(-2px);
83
- box-shadow: 0 4px 8px rgba(0,0,0,0.2);
84
- }
85
-
86
- .code-block {
87
- background: rgba(0, 0, 0, 0.3);
88
- padding: 15px;
89
- border-radius: 5px;
90
- font-family: 'Courier New', monospace;
91
- margin: 10px 0;
92
- overflow-x: auto;
93
- border-left: 3px solid #2196F3;
94
- }
95
-
96
- .stats {
97
- display: grid;
98
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
99
- gap: 15px;
100
- margin: 20px 0;
101
- }
102
-
103
- .stat-card {
104
- background: rgba(255, 255, 255, 0.1);
105
- padding: 15px;
106
- border-radius: 8px;
107
- text-align: center;
108
- }
109
-
110
- .stat-value {
111
- font-size: 24px;
112
- font-weight: bold;
113
- color: #4CAF50;
114
- }
115
-
116
- .loading {
117
- display: inline-block;
118
- width: 20px;
119
- height: 20px;
120
- border: 3px solid rgba(255,255,255,.3);
121
- border-radius: 50%;
122
- border-top-color: #fff;
123
- animation: spin 1s ease-in-out infinite;
124
- }
125
-
126
- @keyframes spin {
127
- to { transform: rotate(360deg); }
128
- }
129
- </style>
130
- </head>
131
- <body>
132
- <div class="container">
133
- <h1>🛡️ SmartLedger BSV Security Test Suite 🛡️</h1>
134
-
135
- <div class="stats">
136
- <div class="stat-card">
137
- <div class="stat-value" id="version-info">Loading...</div>
138
- <div>BSV Version</div>
139
- </div>
140
- <div class="stat-card">
141
- <div class="stat-value" id="security-status">Checking...</div>
142
- <div>Security Status</div>
143
- </div>
144
- <div class="stat-card">
145
- <div class="stat-value" id="tests-passed">0</div>
146
- <div>Tests Passed</div>
147
- </div>
148
- <div class="stat-card">
149
- <div class="stat-value" id="file-size">0 KB</div>
150
- <div>Minified Size</div>
151
- </div>
152
- </div>
153
-
154
- <div class="test-section">
155
- <h3>🔍 Library Loading Test</h3>
156
- <div id="loading-results"></div>
157
- <button onclick="testLibraryLoading()">Test Library Loading</button>
158
- </div>
159
-
160
- <div class="test-section">
161
- <h3>🔐 SmartLedger Security Features</h3>
162
- <div id="security-results"></div>
163
- <button onclick="testSecurityFeatures()">Test Security Features</button>
164
- </div>
165
-
166
- <div class="test-section">
167
- <h3>⚡ Signature Security Tests</h3>
168
- <div id="signature-results"></div>
169
- <button onclick="testSignatureSecurity()">Test Signature Security</button>
170
- </div>
171
-
172
- <div class="test-section">
173
- <h3>🚫 Vulnerability Protection Tests</h3>
174
- <div id="vulnerability-results"></div>
175
- <button onclick="testVulnerabilityProtection()">Test Vulnerability Protection</button>
176
- </div>
177
-
178
- <div class="test-section">
179
- <h3>🔄 Compatibility Tests</h3>
180
- <div id="compatibility-results"></div>
181
- <button onclick="testCompatibility()">Test BSV Compatibility</button>
182
- </div>
183
-
184
- <div class="test-section">
185
- <h3>📊 Performance Benchmark</h3>
186
- <div id="performance-results"></div>
187
- <button onclick="runPerformanceBenchmark()">Run Performance Test</button>
188
- </div>
189
-
190
- <div style="text-align: center; margin-top: 30px;">
191
- <button onclick="runAllTests()" style="background: linear-gradient(45deg, #FF6B6B, #4ECDC4); font-size: 18px; padding: 15px 30px;">
192
- 🚀 Run All Tests
193
- </button>
194
- </div>
195
- </div>
196
-
197
- <!-- Load SmartLedger BSV -->
198
- <script src="bsv.min.js"></script>
199
-
200
- <script>
201
- console.log(bsv)
202
- //look for interpreter
203
- const interpreterAvailable = typeof bsv.Script.Interpreter !== 'undefined';
204
- console.log('Interpreter available:', interpreterAvailable);
205
- // Browser-compatible buffer creation utility
206
- function createBuffer(message) {
207
- if (typeof Buffer !== 'undefined') {
208
- // Node.js environment or polyfilled Buffer
209
- return Buffer.from(message, 'utf8');
210
- } else {
211
- // Browser environment - use BSV's internal buffer handling
212
- // Convert string to Uint8Array and let BSV handle it
213
- const encoder = new TextEncoder();
214
- const uint8Array = encoder.encode(message);
215
- // Create a buffer-like object that BSV can work with
216
- return new Uint8Array(uint8Array);
217
- }
218
- }
219
-
220
- // Create a test signature using the proper BSV Message signing process
221
- function createTestSignature(message, privateKey) {
222
- try {
223
- // Use the BSV Message class to create a proper signature
224
- const bsvMessage = new bsv.Message(message);
225
-
226
- // Get the signature object from the internal _sign method
227
- // This bypasses the base64 encoding and gives us the raw signature
228
- const signatureObj = bsvMessage._sign(privateKey);
229
-
230
- return signatureObj;
231
- } catch (error) {
232
- console.log('Signature creation error:', error.message);
233
-
234
- // Fallback: Create a signature manually for testing
235
- try {
236
- const testSig = new bsv.Signature({
237
- r: new bsv.crypto.BN('21888242871839275222246405745257275088548364400416034343698204186575808495617'),
238
- s: new bsv.crypto.BN('11579208923731619542357098500868790785283756427907490438260516314151816149525')
239
- });
240
- return testSig;
241
- } catch (fallbackError) {
242
- console.log('Fallback signature creation failed:', fallbackError.message);
243
- throw fallbackError;
244
- }
245
- }
246
- }
247
-
248
- let testResults = {
249
- passed: 0,
250
- total: 0
251
- };
252
-
253
- function updateStats() {
254
- document.getElementById('tests-passed').textContent = `${testResults.passed}/${testResults.total}`;
255
- }
256
-
257
- function addResult(containerId, message, type = 'info') {
258
- const container = document.getElementById(containerId);
259
- const div = document.createElement('div');
260
- div.className = `result ${type}`;
261
- div.innerHTML = message;
262
- container.appendChild(div);
263
-
264
- if (type === 'success') {
265
- testResults.passed++;
266
- }
267
- testResults.total++;
268
- updateStats();
269
- }
270
-
271
- function clearResults(containerId) {
272
- document.getElementById(containerId).innerHTML = '';
273
- }
274
-
275
- async function testLibraryLoading() {
276
- clearResults('loading-results');
277
-
278
- try {
279
- // Check if BSV is loaded
280
- if (typeof bsv !== 'undefined') {
281
- addResult('loading-results', '✅ BSV library loaded successfully', 'success');
282
-
283
- // Check version
284
- if (bsv.version) {
285
- document.getElementById('version-info').textContent = bsv.version;
286
- addResult('loading-results', `📦 Version: ${bsv.version}`, 'info');
287
- }
288
-
289
- // Check core components
290
- const components = ['PrivateKey', 'PublicKey', 'Signature', 'Message', 'Transaction', 'Address'];
291
- let loadedComponents = 0;
292
-
293
- components.forEach(comp => {
294
- if (bsv[comp]) {
295
- loadedComponents++;
296
- addResult('loading-results', `✅ ${comp} available`, 'success');
297
- } else {
298
- addResult('loading-results', `❌ ${comp} missing`, 'error');
299
- }
300
- });
301
-
302
- addResult('loading-results', `📊 ${loadedComponents}/${components.length} core components loaded`,
303
- loadedComponents === components.length ? 'success' : 'error');
304
-
305
- } else {
306
- addResult('loading-results', '❌ BSV library failed to load', 'error');
307
- }
308
- } catch (error) {
309
- addResult('loading-results', `❌ Error: ${error.message}`, 'error');
310
- }
311
- }
312
-
313
- async function testSecurityFeatures() {
314
- clearResults('security-results');
315
-
316
- try {
317
- // Check SmartLedger features
318
- if (bsv.SmartLedger) {
319
- addResult('security-results', '✅ SmartLedger namespace available', 'success');
320
- document.getElementById('security-status').textContent = 'Enhanced';
321
- } else {
322
- addResult('security-results', '⚠️ SmartLedger namespace not found', 'error');
323
- document.getElementById('security-status').textContent = 'Standard';
324
- }
325
-
326
- if (bsv.SmartVerify) {
327
- addResult('security-results', '✅ SmartVerify module available', 'success');
328
- } else {
329
- addResult('security-results', '⚠️ SmartVerify module not found', 'error');
330
- }
331
-
332
- if (bsv.EllipticFixed) {
333
- addResult('security-results', '✅ EllipticFixed module available', 'success');
334
- } else {
335
- addResult('security-results', '⚠️ EllipticFixed module not found', 'error');
336
- }
337
-
338
- // Test signature security methods
339
- const privateKey = new bsv.PrivateKey();
340
- const message = 'SmartLedger security test';
341
-
342
- // Create signature for testing security methods
343
- const sigObj = createTestSignature(message, privateKey);
344
-
345
- if (typeof sigObj.isCanonical === 'function') {
346
- addResult('security-results', '✅ Signature.isCanonical() method available', 'success');
347
- } else {
348
- addResult('security-results', '❌ Signature.isCanonical() method missing', 'error');
349
- }
350
-
351
- if (typeof sigObj.validate === 'function') {
352
- addResult('security-results', '✅ Signature.validate() method available', 'success');
353
- } else {
354
- addResult('security-results', '❌ Signature.validate() method missing', 'error');
355
- }
356
-
357
- if (typeof sigObj.toCanonical === 'function') {
358
- addResult('security-results', '✅ Signature.toCanonical() method available', 'success');
359
- } else {
360
- addResult('security-results', '❌ Signature.toCanonical() method missing', 'error');
361
- }
362
-
363
- } catch (error) {
364
- addResult('security-results', `❌ Error testing security features: ${error.message}`, 'error');
365
- }
366
- }
367
-
368
- async function testSignatureSecurity() {
369
- clearResults('signature-results');
370
-
371
- try {
372
- const privateKey = new bsv.PrivateKey();
373
- const message = 'Security validation test message';
374
-
375
- // Create a proper signature for testing
376
- const sigObj = createTestSignature(message, privateKey);
377
-
378
- addResult('signature-results', '🔑 Generated test signature using ECDSA.sign()', 'info');
379
-
380
- // Test canonical signature validation
381
- try {
382
- const isCanonical = sigObj.isCanonical();
383
- addResult('signature-results', `✅ isCanonical() test: ${isCanonical}`, 'success');
384
- } catch (error) {
385
- addResult('signature-results', `❌ isCanonical() failed: ${error.message}`, 'error');
386
- }
387
-
388
- // Test signature validation
389
- try {
390
- const isValid = sigObj.validate();
391
- addResult('signature-results', `✅ validate() test: ${isValid}`, 'success');
392
- } catch (error) {
393
- addResult('signature-results', `❌ validate() failed: ${error.message}`, 'error');
394
- }
395
-
396
- // Test canonical conversion
397
- try {
398
- const canonical = sigObj.toCanonical();
399
- if (canonical) {
400
- addResult('signature-results', '✅ toCanonical() conversion successful', 'success');
401
- } else {
402
- addResult('signature-results', '⚠️ toCanonical() returned null', 'error');
403
- }
404
- } catch (error) {
405
- addResult('signature-results', `❌ toCanonical() failed: ${error.message}`, 'error');
406
- }
407
-
408
- // Test message verification compatibility
409
- try {
410
- const publicKey = privateKey.toPublicKey();
411
- const messageSignature = bsv.Message(message).sign(privateKey);
412
- const isVerified = bsv.Message(message).verify(publicKey.toAddress(), messageSignature);
413
- addResult('signature-results', `✅ Message verification: ${isVerified}`,
414
- isVerified ? 'success' : 'error');
415
- } catch (error) {
416
- addResult('signature-results', `❌ Message verification failed: ${error.message}`, 'error');
417
- }
418
-
419
- } catch (error) {
420
- addResult('signature-results', `❌ Signature test error: ${error.message}`, 'error');
421
- }
422
- }
423
-
424
- async function testVulnerabilityProtection() {
425
- clearResults('vulnerability-results');
426
-
427
- try {
428
- addResult('vulnerability-results', '🔍 Testing zero parameter attack protection...', 'info');
429
-
430
- // Note: In browser, we can't easily create malformed signatures
431
- // but we can test that the validation methods exist and work
432
-
433
- const privateKey = new bsv.PrivateKey();
434
- const message = 'Vulnerability test';
435
-
436
- // Create signature for testing vulnerability protection
437
- const sigObj = createTestSignature(message, privateKey);
438
- const isValid = sigObj.validate();
439
-
440
- if (isValid) {
441
- addResult('vulnerability-results', '✅ Normal signatures pass validation', 'success');
442
- } else {
443
- addResult('vulnerability-results', '❌ Normal signatures fail validation', 'error');
444
- }
445
-
446
- // Test canonical enforcement
447
- const isCanonical = sigObj.isCanonical();
448
- addResult('vulnerability-results', `✅ Canonical signature check: ${isCanonical}`, 'success');
449
-
450
- // Test that validation methods are properly hardened
451
- if (typeof sigObj.validate === 'function' &&
452
- typeof sigObj.isCanonical === 'function') {
453
- addResult('vulnerability-results', '✅ Security validation methods present', 'success');
454
- } else {
455
- addResult('vulnerability-results', '❌ Security validation methods missing', 'error');
456
- }
457
-
458
- addResult('vulnerability-results', '🛡️ Vulnerability protection tests completed', 'success');
459
-
460
- } catch (error) {
461
- addResult('vulnerability-results', `❌ Vulnerability test error: ${error.message}`, 'error');
462
- }
463
- }
464
-
465
- async function testCompatibility() {
466
- clearResults('compatibility-results');
467
-
468
- try {
469
- // Test basic BSV functionality
470
- const privateKey = new bsv.PrivateKey();
471
- const publicKey = privateKey.toPublicKey();
472
- const address = publicKey.toAddress();
473
-
474
- addResult('compatibility-results', '✅ Private key generation', 'success');
475
- addResult('compatibility-results', '✅ Public key derivation', 'success');
476
- addResult('compatibility-results', '✅ Address generation', 'success');
477
-
478
- // Test message signing
479
- const message = 'Compatibility test message';
480
- const signature = bsv.Message(message).sign(privateKey);
481
- const isVerified = bsv.Message(message).verify(address, signature);
482
-
483
- addResult('compatibility-results', `✅ Message signing: ${!!signature}`,
484
- signature ? 'success' : 'error');
485
- addResult('compatibility-results', `✅ Message verification: ${isVerified}`,
486
- isVerified ? 'success' : 'error');
487
-
488
- // Test transaction creation
489
- try {
490
- const transaction = new bsv.Transaction();
491
- addResult('compatibility-results', '✅ Transaction creation', 'success');
492
- } catch (error) {
493
- addResult('compatibility-results', `❌ Transaction creation failed: ${error.message}`, 'error');
494
- }
495
-
496
- // Test script operations
497
- try {
498
- const script = bsv.Script.buildPublicKeyHashOut(address);
499
- addResult('compatibility-results', '✅ Script operations', 'success');
500
- } catch (error) {
501
- addResult('compatibility-results', `❌ Script operations failed: ${error.message}`, 'error');
502
- }
503
-
504
- addResult('compatibility-results', '🎉 All compatibility tests completed', 'success');
505
-
506
- } catch (error) {
507
- addResult('compatibility-results', `❌ Compatibility error: ${error.message}`, 'error');
508
- }
509
- }
510
-
511
- async function runPerformanceBenchmark() {
512
- clearResults('performance-results');
513
-
514
- try {
515
- addResult('performance-results', '⏱️ Starting performance benchmark...', 'info');
516
-
517
- const iterations = 100;
518
-
519
- // Benchmark key generation
520
- const keyGenStart = performance.now();
521
- for (let i = 0; i < iterations; i++) {
522
- new bsv.PrivateKey();
523
- }
524
- const keyGenTime = performance.now() - keyGenStart;
525
-
526
- addResult('performance-results',
527
- `🔑 Key generation: ${(keyGenTime / iterations).toFixed(2)}ms per key`, 'success');
528
-
529
- // Benchmark signing
530
- const privateKey = new bsv.PrivateKey();
531
- const message = 'Performance test message';
532
-
533
- const signingStart = performance.now();
534
- for (let i = 0; i < iterations; i++) {
535
- bsv.Message(message + i).sign(privateKey);
536
- }
537
- const signingTime = performance.now() - signingStart;
538
-
539
- addResult('performance-results',
540
- `✍️ Signing: ${(signingTime / iterations).toFixed(2)}ms per signature`, 'success');
541
-
542
- // Benchmark verification
543
- const signature = bsv.Message(message).sign(privateKey);
544
- const address = privateKey.toAddress();
545
-
546
- const verifyStart = performance.now();
547
- for (let i = 0; i < iterations; i++) {
548
- bsv.Message(message).verify(address, signature);
549
- }
550
- const verifyTime = performance.now() - verifyStart;
551
-
552
- addResult('performance-results',
553
- `✅ Verification: ${(verifyTime / iterations).toFixed(2)}ms per verification`, 'success');
554
-
555
- // Test security method performance
556
- const securityStart = performance.now();
557
- const sigObj = createTestSignature(message, privateKey);
558
- for (let i = 0; i < iterations; i++) {
559
- sigObj.isCanonical();
560
- sigObj.validate();
561
- }
562
- const securityTime = performance.now() - securityStart;
563
-
564
- addResult('performance-results',
565
- `🛡️ Security validation: ${(securityTime / iterations).toFixed(2)}ms per check`, 'success');
566
-
567
- addResult('performance-results', '🏁 Performance benchmark completed', 'success');
568
-
569
- } catch (error) {
570
- addResult('performance-results', `❌ Performance test error: ${error.message}`, 'error');
571
- }
572
- }
573
-
574
- async function runAllTests() {
575
- testResults = { passed: 0, total: 0 };
576
-
577
- // Clear all results
578
- ['loading-results', 'security-results', 'signature-results',
579
- 'vulnerability-results', 'compatibility-results', 'performance-results'].forEach(clearResults);
580
-
581
- // Run all tests
582
- await testLibraryLoading();
583
- await testSecurityFeatures();
584
- await testSignatureSecurity();
585
- await testVulnerabilityProtection();
586
- await testCompatibility();
587
- await runPerformanceBenchmark();
588
-
589
- // Update final stats
590
- const successRate = ((testResults.passed / testResults.total) * 100).toFixed(1);
591
- document.getElementById('security-status').textContent =
592
- successRate >= 90 ? '🛡️ Secured' : successRate >= 70 ? '⚠️ Partial' : '❌ Issues';
593
- }
594
-
595
- // Initialize on page load
596
- window.addEventListener('load', async () => {
597
- // Calculate file size
598
- try {
599
- const response = await fetch('bsv.min.js');
600
- const blob = await response.blob();
601
- const sizeKB = Math.round(blob.size / 1024);
602
- document.getElementById('file-size').textContent = `${sizeKB} KB`;
603
- } catch (error) {
604
- document.getElementById('file-size').textContent = 'Unknown';
605
- }
606
-
607
- // Run initial loading test
608
- setTimeout(testLibraryLoading, 500);
609
- });
610
- </script>
611
- </body>
612
- </html>