@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiboplayer/utils",
3
- "version": "0.4.9",
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.4.9"
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: CMS_ADDRESS, CMS_KEY, DISPLAY_NAME, HARDWARE_KEY, XMR_CHANNEL
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
- cmsAddress: env.CMS_ADDRESS || env.CMS_URL || '',
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 { cmsAddress: '', cmsKey: '', displayName: '', hardwareKey: '', xmrChannel: '' };
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
- const config = JSON.parse(json);
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 from localStorage:', e);
79
- // Fall through to create new config
67
+ console.error('[Config] Failed to parse localStorage config:', e);
80
68
  }
81
69
  }
82
70
 
83
- // No config in localStorage - first time setup
84
- console.log('[Config] No config in localStorage - first time setup');
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
- const newConfig = {
87
- cmsAddress: '',
88
- cmsKey: '',
89
- displayName: '',
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
- // Save immediately
95
- localStorage.setItem(STORAGE_KEY, JSON.stringify(newConfig));
96
- this._backupHardwareKey(newConfig.hardwareKey);
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 newConfig;
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.cmsAddress && this.data.cmsKey && this.data.displayName);
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 cmsAddress() { return this.data.cmsAddress; }
306
- set cmsAddress(val) { this.data.cmsAddress = val; this.save(); }
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() { return this.data.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
 
@@ -76,7 +76,7 @@ describe('Config', () => {
76
76
  config = new Config();
77
77
 
78
78
  expect(config.data).toBeDefined();
79
- expect(config.data.cmsAddress).toBe('');
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
- cmsAddress: 'https://test.cms.com',
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
- cmsAddress: 'https://test.cms.com',
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 cmsAddress', () => {
302
- expect(config.cmsAddress).toBe('');
301
+ it('should get/set cmsUrl', () => {
302
+ expect(config.cmsUrl).toBe('');
303
303
 
304
- config.cmsAddress = 'https://new.cms.com';
304
+ config.cmsUrl = 'https://new.cms.com';
305
305
 
306
- expect(config.cmsAddress).toBe('https://new.cms.com');
307
- expect(config.data.cmsAddress).toBe('https://new.cms.com');
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 cmsAddress set', () => {
311
- config.cmsAddress = 'https://test.com';
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.cmsAddress).toBe('https://test.com');
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 cmsAddress set', () => {
355
- config.cmsAddress = 'https://test.com';
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.cmsAddress = 'https://test.com';
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.cmsAddress = 'https://manual.com';
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.cmsAddress).toBe('https://manual.com');
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.cmsAddress = 'https://auto.com';
398
+ config.cmsUrl = 'https://auto.com';
399
399
 
400
400
  const stored = JSON.parse(mockLocalStorage.getItem('xibo_config'));
401
- expect(stored.cmsAddress).toBe('https://auto.com');
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
- cmsAddress: 'https://test.com',
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
- cmsAddress: null,
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
- expect(config.cmsAddress).toBeNull();
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.cmsAddress = 'https://persist.com';
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.cmsAddress).toBe('https://persist.com');
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
  });