@xiboplayer/utils 0.4.9 → 0.5.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/package.json +2 -2
- package/src/config.js +42 -37
- package/src/config.test.js +24 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiboplayer/utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Shared utilities for Xibo Player packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"./config": "./src/config.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@xiboplayer/crypto": "0.
|
|
14
|
+
"@xiboplayer/crypto": "0.5.1"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"vitest": "^2.0.0"
|
package/src/config.js
CHANGED
|
@@ -12,7 +12,7 @@ const HW_DB_VERSION = 1;
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Check for environment variable config (highest priority).
|
|
15
|
-
* Env vars:
|
|
15
|
+
* Env vars: CMS_URL, CMS_KEY, DISPLAY_NAME, HARDWARE_KEY, XMR_CHANNEL
|
|
16
16
|
* Returns config object if any env vars are set, null otherwise.
|
|
17
17
|
*/
|
|
18
18
|
function loadFromEnv() {
|
|
@@ -20,7 +20,7 @@ function loadFromEnv() {
|
|
|
20
20
|
const env = typeof process !== 'undefined' && process.env ? process.env : {};
|
|
21
21
|
|
|
22
22
|
const envConfig = {
|
|
23
|
-
|
|
23
|
+
cmsUrl: env.CMS_URL || '',
|
|
24
24
|
cmsKey: env.CMS_KEY || '',
|
|
25
25
|
displayName: env.DISPLAY_NAME || '',
|
|
26
26
|
hardwareKey: env.HARDWARE_KEY || '',
|
|
@@ -53,51 +53,49 @@ export class Config {
|
|
|
53
53
|
|
|
54
54
|
// Priority 2: localStorage (browser)
|
|
55
55
|
if (typeof localStorage === 'undefined') {
|
|
56
|
-
return {
|
|
56
|
+
return { cmsUrl: '', cmsKey: '', displayName: '', hardwareKey: '', xmrChannel: '' };
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// Try to load from localStorage
|
|
60
60
|
const json = localStorage.getItem(STORAGE_KEY);
|
|
61
|
+
let config = {};
|
|
61
62
|
|
|
62
63
|
if (json) {
|
|
63
64
|
try {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// CRITICAL: Hardware key must persist
|
|
67
|
-
if (!config.hardwareKey || config.hardwareKey.length < 10) {
|
|
68
|
-
console.error('[Config] CRITICAL: Invalid/missing hardwareKey in localStorage!');
|
|
69
|
-
config.hardwareKey = this.generateStableHardwareKey();
|
|
70
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
|
|
71
|
-
this._backupHardwareKey(config.hardwareKey);
|
|
72
|
-
} else {
|
|
73
|
-
console.log('[Config] ✓ Loaded existing hardwareKey:', config.hardwareKey);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return config;
|
|
65
|
+
config = JSON.parse(json);
|
|
77
66
|
} catch (e) {
|
|
78
|
-
console.error('[Config] Failed to parse config
|
|
79
|
-
// Fall through to create new config
|
|
67
|
+
console.error('[Config] Failed to parse localStorage config:', e);
|
|
80
68
|
}
|
|
81
69
|
}
|
|
82
70
|
|
|
83
|
-
//
|
|
84
|
-
|
|
71
|
+
// ── Single validation gate (same path for fresh + pre-seeded) ──
|
|
72
|
+
let changed = false;
|
|
73
|
+
|
|
74
|
+
if (!config.hardwareKey || config.hardwareKey.length < 10) {
|
|
75
|
+
console.warn('[Config] Missing/invalid hardwareKey — generating');
|
|
76
|
+
config.hardwareKey = this.generateStableHardwareKey();
|
|
77
|
+
this._backupHardwareKey(config.hardwareKey);
|
|
78
|
+
changed = true;
|
|
79
|
+
} else {
|
|
80
|
+
console.log('[Config] ✓ Loaded existing hardwareKey:', config.hardwareKey);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!config.xmrChannel) {
|
|
84
|
+
console.warn('[Config] Missing xmrChannel — generating');
|
|
85
|
+
config.xmrChannel = this.generateXmrChannel();
|
|
86
|
+
changed = true;
|
|
87
|
+
}
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
hardwareKey: this.generateStableHardwareKey(),
|
|
91
|
-
xmrChannel: this.generateXmrChannel()
|
|
92
|
-
};
|
|
89
|
+
// Ensure optional fields have defaults
|
|
90
|
+
config.cmsUrl = config.cmsUrl || '';
|
|
91
|
+
config.cmsKey = config.cmsKey || '';
|
|
92
|
+
config.displayName = config.displayName || '';
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
console.log('[Config] ✓ Saved new config to localStorage');
|
|
98
|
-
console.log('[Config] Hardware key will persist across reloads:', newConfig.hardwareKey);
|
|
94
|
+
if (changed) {
|
|
95
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
|
|
96
|
+
}
|
|
99
97
|
|
|
100
|
-
return
|
|
98
|
+
return config;
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
/**
|
|
@@ -188,7 +186,7 @@ export class Config {
|
|
|
188
186
|
}
|
|
189
187
|
|
|
190
188
|
isConfigured() {
|
|
191
|
-
return !!(this.data.
|
|
189
|
+
return !!(this.data.cmsUrl && this.data.cmsKey && this.data.displayName);
|
|
192
190
|
}
|
|
193
191
|
|
|
194
192
|
generateStableHardwareKey() {
|
|
@@ -302,8 +300,8 @@ export class Config {
|
|
|
302
300
|
return result.substring(0, 32);
|
|
303
301
|
}
|
|
304
302
|
|
|
305
|
-
get
|
|
306
|
-
set
|
|
303
|
+
get cmsUrl() { return this.data.cmsUrl; }
|
|
304
|
+
set cmsUrl(val) { this.data.cmsUrl = val; this.save(); }
|
|
307
305
|
|
|
308
306
|
get cmsKey() { return this.data.cmsKey; }
|
|
309
307
|
set cmsKey(val) { this.data.cmsKey = val; this.save(); }
|
|
@@ -320,7 +318,14 @@ export class Config {
|
|
|
320
318
|
}
|
|
321
319
|
return this.data.hardwareKey;
|
|
322
320
|
}
|
|
323
|
-
get xmrChannel() {
|
|
321
|
+
get xmrChannel() {
|
|
322
|
+
if (!this.data.xmrChannel) {
|
|
323
|
+
console.warn('[Config] xmrChannel missing at access time — generating');
|
|
324
|
+
this.data.xmrChannel = this.generateXmrChannel();
|
|
325
|
+
this.save();
|
|
326
|
+
}
|
|
327
|
+
return this.data.xmrChannel;
|
|
328
|
+
}
|
|
324
329
|
get xmrPubKey() { return this.data.xmrPubKey || ''; }
|
|
325
330
|
get xmrPrivKey() { return this.data.xmrPrivKey || ''; }
|
|
326
331
|
|
package/src/config.test.js
CHANGED
|
@@ -76,7 +76,7 @@ describe('Config', () => {
|
|
|
76
76
|
config = new Config();
|
|
77
77
|
|
|
78
78
|
expect(config.data).toBeDefined();
|
|
79
|
-
expect(config.data.
|
|
79
|
+
expect(config.data.cmsUrl).toBe('');
|
|
80
80
|
expect(config.data.cmsKey).toBe('');
|
|
81
81
|
expect(config.data.displayName).toBe('');
|
|
82
82
|
expect(config.data.hardwareKey).toMatch(/^pwa-/);
|
|
@@ -100,7 +100,7 @@ describe('Config', () => {
|
|
|
100
100
|
|
|
101
101
|
it('should load existing config from localStorage', () => {
|
|
102
102
|
const existingConfig = {
|
|
103
|
-
|
|
103
|
+
cmsUrl: 'https://test.cms.com',
|
|
104
104
|
cmsKey: 'test-key',
|
|
105
105
|
displayName: 'Test Display',
|
|
106
106
|
hardwareKey: 'pwa-existinghardwarekey1234567',
|
|
@@ -116,7 +116,7 @@ describe('Config', () => {
|
|
|
116
116
|
|
|
117
117
|
it('should regenerate hardware key if invalid in stored config', () => {
|
|
118
118
|
const invalidConfig = {
|
|
119
|
-
|
|
119
|
+
cmsUrl: 'https://test.cms.com',
|
|
120
120
|
cmsKey: 'test-key',
|
|
121
121
|
displayName: 'Test Display',
|
|
122
122
|
hardwareKey: 'short', // Invalid: too short
|
|
@@ -298,20 +298,20 @@ describe('Config', () => {
|
|
|
298
298
|
config = new Config();
|
|
299
299
|
});
|
|
300
300
|
|
|
301
|
-
it('should get/set
|
|
302
|
-
expect(config.
|
|
301
|
+
it('should get/set cmsUrl', () => {
|
|
302
|
+
expect(config.cmsUrl).toBe('');
|
|
303
303
|
|
|
304
|
-
config.
|
|
304
|
+
config.cmsUrl = 'https://new.cms.com';
|
|
305
305
|
|
|
306
|
-
expect(config.
|
|
307
|
-
expect(config.data.
|
|
306
|
+
expect(config.cmsUrl).toBe('https://new.cms.com');
|
|
307
|
+
expect(config.data.cmsUrl).toBe('https://new.cms.com');
|
|
308
308
|
});
|
|
309
309
|
|
|
310
|
-
it('should save to localStorage when
|
|
311
|
-
config.
|
|
310
|
+
it('should save to localStorage when cmsUrl set', () => {
|
|
311
|
+
config.cmsUrl = 'https://test.com';
|
|
312
312
|
|
|
313
313
|
const stored = JSON.parse(mockLocalStorage.getItem('xibo_config'));
|
|
314
|
-
expect(stored.
|
|
314
|
+
expect(stored.cmsUrl).toBe('https://test.com');
|
|
315
315
|
});
|
|
316
316
|
|
|
317
317
|
it('should get/set cmsKey', () => {
|
|
@@ -351,8 +351,8 @@ describe('Config', () => {
|
|
|
351
351
|
expect(config.isConfigured()).toBe(false);
|
|
352
352
|
});
|
|
353
353
|
|
|
354
|
-
it('should return false when only
|
|
355
|
-
config.
|
|
354
|
+
it('should return false when only cmsUrl set', () => {
|
|
355
|
+
config.cmsUrl = 'https://test.com';
|
|
356
356
|
|
|
357
357
|
expect(config.isConfigured()).toBe(false);
|
|
358
358
|
});
|
|
@@ -370,7 +370,7 @@ describe('Config', () => {
|
|
|
370
370
|
});
|
|
371
371
|
|
|
372
372
|
it('should return true when all required fields set', () => {
|
|
373
|
-
config.
|
|
373
|
+
config.cmsUrl = 'https://test.com';
|
|
374
374
|
config.cmsKey = 'test-key';
|
|
375
375
|
config.displayName = 'Test Display';
|
|
376
376
|
|
|
@@ -384,21 +384,21 @@ describe('Config', () => {
|
|
|
384
384
|
});
|
|
385
385
|
|
|
386
386
|
it('should save current config to localStorage', () => {
|
|
387
|
-
config.data.
|
|
387
|
+
config.data.cmsUrl = 'https://manual.com';
|
|
388
388
|
config.data.cmsKey = 'manual-key';
|
|
389
389
|
|
|
390
390
|
config.save();
|
|
391
391
|
|
|
392
392
|
const stored = JSON.parse(mockLocalStorage.getItem('xibo_config'));
|
|
393
|
-
expect(stored.
|
|
393
|
+
expect(stored.cmsUrl).toBe('https://manual.com');
|
|
394
394
|
expect(stored.cmsKey).toBe('manual-key');
|
|
395
395
|
});
|
|
396
396
|
|
|
397
397
|
it('should auto-save when setters used', () => {
|
|
398
|
-
config.
|
|
398
|
+
config.cmsUrl = 'https://auto.com';
|
|
399
399
|
|
|
400
400
|
const stored = JSON.parse(mockLocalStorage.getItem('xibo_config'));
|
|
401
|
-
expect(stored.
|
|
401
|
+
expect(stored.cmsUrl).toBe('https://auto.com');
|
|
402
402
|
});
|
|
403
403
|
});
|
|
404
404
|
|
|
@@ -420,7 +420,7 @@ describe('Config', () => {
|
|
|
420
420
|
describe('Edge Cases', () => {
|
|
421
421
|
it('should handle missing hardwareKey in loaded config', () => {
|
|
422
422
|
mockLocalStorage.setItem('xibo_config', JSON.stringify({
|
|
423
|
-
|
|
423
|
+
cmsUrl: 'https://test.com',
|
|
424
424
|
cmsKey: 'test-key',
|
|
425
425
|
displayName: 'Test'
|
|
426
426
|
// hardwareKey missing
|
|
@@ -434,7 +434,7 @@ describe('Config', () => {
|
|
|
434
434
|
|
|
435
435
|
it('should handle null values in config', () => {
|
|
436
436
|
mockLocalStorage.setItem('xibo_config', JSON.stringify({
|
|
437
|
-
|
|
437
|
+
cmsUrl: null,
|
|
438
438
|
cmsKey: null,
|
|
439
439
|
displayName: null,
|
|
440
440
|
hardwareKey: 'pwa-1234567812344567890123456789',
|
|
@@ -444,7 +444,8 @@ describe('Config', () => {
|
|
|
444
444
|
config = new Config();
|
|
445
445
|
|
|
446
446
|
expect(config.isConfigured()).toBe(false);
|
|
447
|
-
|
|
447
|
+
// null values are normalized to empty strings by the validation gate
|
|
448
|
+
expect(config.cmsUrl).toBe('');
|
|
448
449
|
});
|
|
449
450
|
|
|
450
451
|
it('should handle very long strings', () => {
|
|
@@ -478,13 +479,13 @@ describe('Config', () => {
|
|
|
478
479
|
|
|
479
480
|
it('should persist configuration changes', () => {
|
|
480
481
|
const config1 = new Config();
|
|
481
|
-
config1.
|
|
482
|
+
config1.cmsUrl = 'https://persist.com';
|
|
482
483
|
config1.cmsKey = 'persist-key';
|
|
483
484
|
config1.displayName = 'Persist Display';
|
|
484
485
|
|
|
485
486
|
const config2 = new Config();
|
|
486
487
|
|
|
487
|
-
expect(config2.
|
|
488
|
+
expect(config2.cmsUrl).toBe('https://persist.com');
|
|
488
489
|
expect(config2.cmsKey).toBe('persist-key');
|
|
489
490
|
expect(config2.displayName).toBe('Persist Display');
|
|
490
491
|
});
|