loukai-app 0.3.22 → 0.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 (49) hide show
  1. package/README.md +5 -5
  2. package/package.json +2 -2
  3. package/src/main/audioEngine.js +11 -10
  4. package/src/main/creator/conversionService.js +9 -8
  5. package/src/main/creator/downloadManager.js +70 -67
  6. package/src/main/creator/ffmpegService.js +3 -2
  7. package/src/main/creator/installLogger.js +5 -7
  8. package/src/main/creator/llmService.js +13 -7
  9. package/src/main/creator/lrclibService.js +5 -6
  10. package/src/main/creator/pythonRunner.js +3 -2
  11. package/src/main/creator/stemBuilder.js +29 -28
  12. package/src/main/handlers/audioHandlers.js +2 -1
  13. package/src/main/handlers/canvasHandlers.js +4 -3
  14. package/src/main/handlers/creatorHandlers.js +2 -1
  15. package/src/main/handlers/editorHandlers.js +8 -7
  16. package/src/main/handlers/fileHandlers.js +1 -1
  17. package/src/main/handlers/index.js +21 -19
  18. package/src/main/handlers/libraryHandlers.js +3 -0
  19. package/src/main/handlers/queueHandlers.js +3 -2
  20. package/src/main/handlers/settingsHandlers.js +2 -1
  21. package/src/main/handlers/webServerHandlers.js +3 -2
  22. package/src/main/logger.js +12 -0
  23. package/src/main/main.js +71 -66
  24. package/src/main/statePersistence.js +13 -12
  25. package/src/main/utils/pathValidator.js +21 -17
  26. package/src/main/webServer.js +102 -95
  27. package/src/renderer/components/creator/CreateTab.jsx +6 -6
  28. package/src/renderer/dist/assets/{kaiPlayer-CoMx__a_.js → kaiPlayer-DSaY7TxC.js} +2 -2
  29. package/src/renderer/dist/assets/kaiPlayer-DSaY7TxC.js.map +1 -0
  30. package/src/renderer/dist/assets/songLoaders-CcYVonLu.js +2 -0
  31. package/src/renderer/dist/assets/{songLoaders-BaTgGib4.js.map → songLoaders-CcYVonLu.js.map} +1 -1
  32. package/src/renderer/dist/renderer.css +1 -1
  33. package/src/renderer/dist/renderer.js +11 -51
  34. package/src/renderer/dist/renderer.js.map +1 -1
  35. package/src/renderer/js/kaiPlayer.js +9 -7
  36. package/src/renderer/lib/cdgraphics.js +0 -1
  37. package/src/shared/services/creatorService.js +2 -2
  38. package/src/shared/services/libraryService.js +4 -1
  39. package/src/shared/services/serverSettingsService.js +0 -1
  40. package/src/shared/services/settingsService.js +0 -2
  41. package/src/utils/m4aLoader.js +1 -1
  42. package/src/web/dist/assets/index-CGbmW1VG.js +11 -0
  43. package/src/web/dist/assets/{index-0H-RnRrV.js.map → index-CGbmW1VG.js.map} +1 -1
  44. package/src/web/dist/assets/index-GLKJK41r.css +1 -0
  45. package/src/web/dist/index.html +2 -2
  46. package/src/renderer/dist/assets/kaiPlayer-CoMx__a_.js.map +0 -1
  47. package/src/renderer/dist/assets/songLoaders-BaTgGib4.js +0 -2
  48. package/src/web/dist/assets/index-0H-RnRrV.js +0 -51
  49. package/src/web/dist/assets/index-DYW2zB0u.css +0 -1
package/src/main/main.js CHANGED
@@ -23,10 +23,11 @@ import {
23
23
  loadAndSync,
24
24
  getBroadcastChannel,
25
25
  } from '../shared/services/settingsService.js';
26
+ import { log } from './logger.js';
26
27
 
27
- console.log('📦 About to import registerAllHandlers...');
28
+ log('📦 About to import registerAllHandlers...');
28
29
  import { registerAllHandlers } from './handlers/index.js';
29
- console.log('✅ registerAllHandlers imported:', typeof registerAllHandlers);
30
+ log('✅ registerAllHandlers imported:', typeof registerAllHandlers);
30
31
 
31
32
  // ESM equivalent of __dirname
32
33
  const __filename = fileURLToPath(import.meta.url);
@@ -153,9 +154,13 @@ class KaiPlayerApp {
153
154
  }
154
155
 
155
156
  async initialize() {
157
+ // Set app name to 'loukai' for consistent userData path across all installs
158
+ // This ensures settings go to ~/.config/loukai/ (Linux), matching creator paths
159
+ app.setName('loukai');
160
+
156
161
  await app.whenReady();
157
162
 
158
- console.log('🚀 App starting...', {
163
+ log('🚀 App starting...', {
159
164
  isPackaged: app.isPackaged,
160
165
  __dirname,
161
166
  resourcesPath: process.resourcesPath,
@@ -226,7 +231,7 @@ class KaiPlayerApp {
226
231
 
227
232
  // Log all console messages from renderer
228
233
  this.mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
229
- console.log(`[Renderer ${level}] ${message} (${sourceId}:${line})`);
234
+ log(`[Renderer ${level}] ${message} (${sourceId}:${line})`);
230
235
  });
231
236
 
232
237
  // Log renderer loading events
@@ -235,7 +240,7 @@ class KaiPlayerApp {
235
240
  });
236
241
 
237
242
  this.mainWindow.webContents.on('did-finish-load', () => {
238
- console.log('✅ Renderer finished loading');
243
+ log('✅ Renderer finished loading');
239
244
  });
240
245
 
241
246
  // Set dock icon on macOS
@@ -276,7 +281,7 @@ class KaiPlayerApp {
276
281
  // F12 key
277
282
  if (input.key === 'F12') {
278
283
  event.preventDefault();
279
- console.log('F12 pressed, toggling DevTools...');
284
+ log('F12 pressed, toggling DevTools...');
280
285
  try {
281
286
  if (this.mainWindow.webContents.isDevToolsOpened()) {
282
287
  this.mainWindow.webContents.closeDevTools();
@@ -290,7 +295,7 @@ class KaiPlayerApp {
290
295
  // Ctrl+Shift+I
291
296
  if ((input.control || input.meta) && input.shift && input.key === 'I') {
292
297
  event.preventDefault();
293
- console.log('Ctrl+Shift+I pressed, toggling DevTools...');
298
+ log('Ctrl+Shift+I pressed, toggling DevTools...');
294
299
  try {
295
300
  if (this.mainWindow.webContents.isDevToolsOpened()) {
296
301
  this.mainWindow.webContents.closeDevTools();
@@ -344,7 +349,7 @@ class KaiPlayerApp {
344
349
  });
345
350
 
346
351
  this.canvasWindow.on('closed', () => {
347
- console.log('🔴 Child window closed, stopping streaming and cleanup');
352
+ log('🔴 Child window closed, stopping streaming and cleanup');
348
353
  this.stopCanvasStreaming();
349
354
  this.canvasWindow = null;
350
355
  });
@@ -405,12 +410,12 @@ class KaiPlayerApp {
405
410
 
406
411
  // Only proceed if child window is still open and not destroyed
407
412
  if (this.canvasWindow.isDestroyed()) {
408
- console.log('❌ Child window destroyed, cannot start streaming');
413
+ log('❌ Child window destroyed, cannot start streaming');
409
414
  return;
410
415
  }
411
416
 
412
417
  try {
413
- console.log('Starting WebRTC canvas streaming...');
418
+ log('Starting WebRTC canvas streaming...');
414
419
 
415
420
  // Set up WebRTC sender in main window via IPC
416
421
  const senderResult = await this.sendWebRTCCommand('setupSender');
@@ -430,19 +435,19 @@ class KaiPlayerApp {
430
435
  await this.establishWebRTCConnection();
431
436
 
432
437
  this.canvasStreaming.isStreaming = true;
433
- console.log('✅ WebRTC canvas streaming started successfully');
438
+ log('✅ WebRTC canvas streaming started successfully');
434
439
  } catch (error) {
435
440
  console.error('Canvas streaming setup error:', error);
436
441
  }
437
442
  }
438
443
 
439
444
  async establishWebRTCConnection() {
440
- console.log('🤝 Starting WebRTC handshake...');
445
+ log('🤝 Starting WebRTC handshake...');
441
446
 
442
447
  let offer;
443
448
  try {
444
449
  // Create offer in sender (main window) via IPC
445
- console.log('📤 Creating offer in sender...');
450
+ log('📤 Creating offer in sender...');
446
451
 
447
452
  offer = await this.sendWebRTCCommand('createOffer');
448
453
 
@@ -450,14 +455,14 @@ class KaiPlayerApp {
450
455
  throw new Error('Sender error: ' + offer.error);
451
456
  }
452
457
 
453
- console.log('✅ Offer creation successful, moving to receiver...');
458
+ log('✅ Offer creation successful, moving to receiver...');
454
459
  } catch (error) {
455
460
  console.error('❌ Failed to create offer:', error);
456
461
  throw error;
457
462
  }
458
463
 
459
464
  try {
460
- console.log('📥 Setting offer in receiver and creating answer...');
465
+ log('📥 Setting offer in receiver and creating answer...');
461
466
 
462
467
  // First check if child window is ready
463
468
  if (!this.canvasWindow || this.canvasWindow.isDestroyed()) {
@@ -465,9 +470,9 @@ class KaiPlayerApp {
465
470
  }
466
471
 
467
472
  // Check if receiver is ready via IPC
468
- console.log('🔍 Checking if child window is ready...');
473
+ log('🔍 Checking if child window is ready...');
469
474
  const childReady = await this.sendCanvasWebRTCCommand('checkReceiverReady');
470
- console.log('🏓 Child window status:', childReady);
475
+ log('🏓 Child window status:', childReady);
471
476
 
472
477
  if (!childReady.hasReceiverPC) {
473
478
  throw new Error('Receiver PC not found in child window');
@@ -481,10 +486,10 @@ class KaiPlayerApp {
481
486
  }
482
487
 
483
488
  // Set answer in sender via IPC
484
- console.log('📤 Setting answer in sender...');
489
+ log('📤 Setting answer in sender...');
485
490
  await this.sendWebRTCCommand('setAnswer', answer);
486
491
 
487
- console.log('✅ WebRTC peer connection handshake complete');
492
+ log('✅ WebRTC peer connection handshake complete');
488
493
 
489
494
  // Wait a bit for ICE connection to establish
490
495
  setTimeout(() => {
@@ -501,9 +506,9 @@ class KaiPlayerApp {
501
506
 
502
507
  const receiverStatus = await this.sendCanvasWebRTCCommand('getReceiverStatus');
503
508
 
504
- console.log('📊 Connection Status:');
505
- console.log(' Sender:', senderStatus);
506
- console.log(' Receiver:', receiverStatus);
509
+ log('📊 Connection Status:');
510
+ log(' Sender:', senderStatus);
511
+ log(' Receiver:', receiverStatus);
507
512
  } catch (error) {
508
513
  console.error('Error checking connection status:', error);
509
514
  }
@@ -513,7 +518,7 @@ class KaiPlayerApp {
513
518
  if (!this.canvasStreaming.isStreaming) return;
514
519
 
515
520
  try {
516
- console.log('Stopping canvas streaming...');
521
+ log('Stopping canvas streaming...');
517
522
 
518
523
  // Cleanup sender (main window) via IPC
519
524
  if (this.mainWindow && !this.mainWindow.isDestroyed()) {
@@ -526,7 +531,7 @@ class KaiPlayerApp {
526
531
  }
527
532
 
528
533
  this.canvasStreaming.isStreaming = false;
529
- console.log('Canvas streaming stopped');
534
+ log('Canvas streaming stopped');
530
535
  } catch (error) {
531
536
  console.error('Error stopping canvas streaming:', error);
532
537
  }
@@ -613,7 +618,7 @@ class KaiPlayerApp {
613
618
  label: 'Toggle Developer Tools',
614
619
  accelerator: process.platform === 'darwin' ? 'Alt+Cmd+I' : 'Ctrl+Shift+I',
615
620
  click: (item, focusedWindow) => {
616
- console.log('Menu: Toggle Developer Tools clicked', {
621
+ log('Menu: Toggle Developer Tools clicked', {
617
622
  hasFocusedWindow: Boolean(focusedWindow),
618
623
  windowType: focusedWindow?.getTitle(),
619
624
  });
@@ -624,10 +629,10 @@ class KaiPlayerApp {
624
629
  if (targetWindow) {
625
630
  try {
626
631
  if (targetWindow.webContents.isDevToolsOpened()) {
627
- console.log('Closing DevTools...');
632
+ log('Closing DevTools...');
628
633
  targetWindow.webContents.closeDevTools();
629
634
  } else {
630
- console.log('Opening DevTools...');
635
+ log('Opening DevTools...');
631
636
  targetWindow.webContents.openDevTools();
632
637
  }
633
638
  } catch (error) {
@@ -760,9 +765,9 @@ class KaiPlayerApp {
760
765
  // All IPC handlers have been organized into handler modules
761
766
  // See: src/main/handlers/
762
767
  try {
763
- console.log('🔧 Setting up IPC handlers...');
768
+ log('🔧 Setting up IPC handlers...');
764
769
  registerAllHandlers(this);
765
- console.log('✅ IPC setup complete');
770
+ log('✅ IPC setup complete');
766
771
  } catch (error) {
767
772
  console.error('❌ Failed to setup IPC handlers:', error);
768
773
  console.error('Stack:', error.stack);
@@ -1134,7 +1139,7 @@ class KaiPlayerApp {
1134
1139
  }
1135
1140
  } catch {
1136
1141
  // No kara atom or error reading it - not a karaoke file
1137
- console.log(`No kara atom in ${m4aFilePath}`);
1142
+ log(`No kara atom in ${m4aFilePath}`);
1138
1143
  }
1139
1144
 
1140
1145
  // Fallback to filename parsing if no tags
@@ -1221,7 +1226,7 @@ class KaiPlayerApp {
1221
1226
 
1222
1227
  async loadCDGFile(mp3Path, cdgPath, format, queueItemId = null) {
1223
1228
  try {
1224
- console.log('💿 Loading CDG file:', { mp3Path, cdgPath, format, queueItemId });
1229
+ log('💿 Loading CDG file:', { mp3Path, cdgPath, format, queueItemId });
1225
1230
 
1226
1231
  // Get requester from queue if queueItemId is provided
1227
1232
  let requester = 'KJ';
@@ -1255,7 +1260,7 @@ class KaiPlayerApp {
1255
1260
  };
1256
1261
  this.appState.setCurrentSong(songData);
1257
1262
 
1258
- console.log('💿 CDG loaded, sending to renderer');
1263
+ log('💿 CDG loaded, sending to renderer');
1259
1264
  this.sendToRenderer('song:loaded', cdgData.metadata || {});
1260
1265
  this.sendToRenderer('song:data', cdgData);
1261
1266
 
@@ -1285,7 +1290,7 @@ class KaiPlayerApp {
1285
1290
 
1286
1291
  async loadM4AFile(m4aPath, queueItemId = null) {
1287
1292
  try {
1288
- console.log('🎵 Loading M4A file:', { m4aPath, queueItemId });
1293
+ log('🎵 Loading M4A file:', { m4aPath, queueItemId });
1289
1294
 
1290
1295
  // Get requester from queue if queueItemId is provided
1291
1296
  let requester = 'KJ';
@@ -1324,7 +1329,7 @@ class KaiPlayerApp {
1324
1329
  };
1325
1330
  this.appState.setCurrentSong(songData);
1326
1331
 
1327
- console.log('🎵 M4A loaded, sending to renderer');
1332
+ log('🎵 M4A loaded, sending to renderer');
1328
1333
  this.sendToRenderer('song:loaded', m4aData.metadata || {});
1329
1334
  this.sendToRenderer('song:data', m4aData);
1330
1335
 
@@ -1357,17 +1362,17 @@ class KaiPlayerApp {
1357
1362
  const songsFolder = this.settings.getSongsFolder();
1358
1363
 
1359
1364
  if (!songsFolder) {
1360
- console.log('📁 No songs folder set, prompting user...');
1365
+ log('📁 No songs folder set, prompting user...');
1361
1366
  await this.promptForSongsFolder();
1362
1367
  } else {
1363
- console.log('📁 Songs folder:', songsFolder);
1368
+ log('📁 Songs folder:', songsFolder);
1364
1369
  // Verify folder still exists
1365
1370
  if (!fs.existsSync(songsFolder)) {
1366
- console.log('⚠️ Songs folder no longer exists, prompting for new one...');
1371
+ log('⚠️ Songs folder no longer exists, prompting for new one...');
1367
1372
  await this.promptForSongsFolder();
1368
1373
  } else {
1369
1374
  // Trigger library scan on startup in background
1370
- console.log('📚 Starting library scan...');
1375
+ log('📚 Starting library scan...');
1371
1376
  this.scanLibraryInBackground(songsFolder);
1372
1377
  }
1373
1378
  }
@@ -1383,7 +1388,7 @@ class KaiPlayerApp {
1383
1388
  const cacheData = JSON.parse(await fsPromises.readFile(cacheFile, 'utf8'));
1384
1389
  // Check if cache is for the same folder
1385
1390
  if (cacheData.songsFolder === songsFolder) {
1386
- console.log(`📂 Found library cache with ${cacheData.files.length} songs`);
1391
+ log(`📂 Found library cache with ${cacheData.files.length} songs`);
1387
1392
 
1388
1393
  // Load from cache
1389
1394
  const files = cacheData.files;
@@ -1400,21 +1405,21 @@ class KaiPlayerApp {
1400
1405
 
1401
1406
  // Notify renderer
1402
1407
  this.sendToRenderer('library:scanComplete', { count: files.length });
1403
- console.log(`✅ Library loaded from cache: ${files.length} songs`);
1408
+ log(`✅ Library loaded from cache: ${files.length} songs`);
1404
1409
  useCache = true;
1405
1410
  }
1406
1411
  } catch {
1407
1412
  // Cache doesn't exist or is invalid, will scan
1408
- console.log('📚 No valid cache found, scanning library...');
1413
+ log('📚 No valid cache found, scanning library...');
1409
1414
  }
1410
1415
 
1411
1416
  if (useCache) return;
1412
1417
 
1413
1418
  // First, quickly count all files
1414
- console.log('📊 Counting files...');
1419
+ log('📊 Counting files...');
1415
1420
  const allFiles = await this.findAllKaiFiles(songsFolder);
1416
1421
  const totalFiles = allFiles.length;
1417
- console.log(`📊 Found ${totalFiles} files to process`);
1422
+ log(`📊 Found ${totalFiles} files to process`);
1418
1423
 
1419
1424
  // Notify renderer of total count
1420
1425
  this.sendToRenderer('library:scanProgress', { current: 0, total: totalFiles });
@@ -1422,7 +1427,7 @@ class KaiPlayerApp {
1422
1427
  // Now process files with metadata extraction and progress updates
1423
1428
  // Pass null for progressCallback since this.sendToRenderer() is called directly in the method
1424
1429
  const files = await this.scanForKaiFilesWithProgress(songsFolder, totalFiles, null);
1425
- console.log(`✅ Library scan complete: ${files.length} songs found`);
1430
+ log(`✅ Library scan complete: ${files.length} songs found`);
1426
1431
 
1427
1432
  // Store in main process
1428
1433
  this.cachedLibrary = files;
@@ -1445,7 +1450,7 @@ class KaiPlayerApp {
1445
1450
  }),
1446
1451
  'utf8'
1447
1452
  );
1448
- console.log('💾 Library cache saved to disk');
1453
+ log('💾 Library cache saved to disk');
1449
1454
  } catch (err) {
1450
1455
  console.error('Failed to save library cache:', err);
1451
1456
  }
@@ -1887,7 +1892,7 @@ class KaiPlayerApp {
1887
1892
  */
1888
1893
  setSongsFolderAndScan(folder) {
1889
1894
  this.settings.setSongsFolder(folder);
1890
- console.log('📁 Songs folder set to:', folder);
1895
+ log('📁 Songs folder set to:', folder);
1891
1896
 
1892
1897
  // Notify renderer about the new library
1893
1898
  this.sendToRenderer('library:folderSet', folder);
@@ -1967,7 +1972,7 @@ class KaiPlayerApp {
1967
1972
  this.webServer.io.emit(channel, value);
1968
1973
  }
1969
1974
 
1970
- console.log(`📡 Settings broadcast: ${key} -> ${channel}`);
1975
+ log(`📡 Settings broadcast: ${key} -> ${channel}`);
1971
1976
  }
1972
1977
 
1973
1978
  // Web Server Integration Methods
@@ -1976,8 +1981,8 @@ class KaiPlayerApp {
1976
1981
  this.webServer = new WebServer(this);
1977
1982
  const port = await this.webServer.start(3069);
1978
1983
 
1979
- console.log(`🌐 Web server started at http://localhost:${port}`);
1980
- console.log(`📱 Song requests available at: http://localhost:${port}`);
1984
+ log(`🌐 Web server started at http://localhost:${port}`);
1985
+ log(`📱 Song requests available at: http://localhost:${port}`);
1981
1986
 
1982
1987
  // Connect to Socket.IO server
1983
1988
  await this.connectToSocketServer(port);
@@ -1998,32 +2003,32 @@ class KaiPlayerApp {
1998
2003
  this.socket = io(`http://localhost:${port}`);
1999
2004
 
2000
2005
  this.socket.on('connect', () => {
2001
- console.log('📡 Connected to Socket.IO server');
2006
+ log('📡 Connected to Socket.IO server');
2002
2007
 
2003
2008
  // Identify as electron app
2004
2009
  this.socket.emit('identify', { type: 'electron-app' });
2005
2010
  });
2006
2011
 
2007
2012
  this.socket.on('disconnect', () => {
2008
- console.log('📡 Disconnected from Socket.IO server');
2013
+ log('📡 Disconnected from Socket.IO server');
2009
2014
  });
2010
2015
 
2011
2016
  this.socket.on('song-request', (request) => {
2012
- console.log('🎵 Song request received:', request);
2017
+ log('🎵 Song request received:', request);
2013
2018
 
2014
2019
  // Notify renderer about new request
2015
2020
  this.sendToRenderer('songRequest:new', request);
2016
2021
  });
2017
2022
 
2018
2023
  this.socket.on('request-approved', (request) => {
2019
- console.log('✅ Request approved:', request);
2024
+ log('✅ Request approved:', request);
2020
2025
 
2021
2026
  // Notify renderer about approved request
2022
2027
  this.sendToRenderer('songRequest:approved', request);
2023
2028
  });
2024
2029
 
2025
2030
  this.socket.on('request-rejected', (request) => {
2026
- console.log('❌ Request rejected:', request);
2031
+ log('❌ Request rejected:', request);
2027
2032
 
2028
2033
  // Notify renderer about rejected request
2029
2034
  this.sendToRenderer('songRequest:rejected', request);
@@ -2034,12 +2039,12 @@ class KaiPlayerApp {
2034
2039
  });
2035
2040
 
2036
2041
  this.socket.on('effect-control', (data) => {
2037
- console.log('🎨 Effect control received:', data.action);
2042
+ log('🎨 Effect control received:', data.action);
2038
2043
  this.handleEffectControl(data.action);
2039
2044
  });
2040
2045
 
2041
2046
  this.socket.on('settings-update', (settings) => {
2042
- console.log('🔧 Settings update received from server:', settings);
2047
+ log('🔧 Settings update received from server:', settings);
2043
2048
  this.handleSettingsUpdate(settings);
2044
2049
  });
2045
2050
  } catch (error) {
@@ -2073,10 +2078,10 @@ class KaiPlayerApp {
2073
2078
  // Send effect control command to renderer process
2074
2079
  if (action === 'previous') {
2075
2080
  this.sendToRenderer('effect:previous', {});
2076
- console.log('🎨 Sent previous effect command to renderer');
2081
+ log('🎨 Sent previous effect command to renderer');
2077
2082
  } else if (action === 'next') {
2078
2083
  this.sendToRenderer('effect:next', {});
2079
- console.log('🎨 Sent next effect command to renderer');
2084
+ log('🎨 Sent next effect command to renderer');
2080
2085
  }
2081
2086
  }
2082
2087
 
@@ -2089,7 +2094,7 @@ class KaiPlayerApp {
2089
2094
 
2090
2095
  // Send settings update to renderer to update UI
2091
2096
  this.sendToRenderer('settings:update', settings);
2092
- console.log('🔧 Settings update sent to renderer');
2097
+ log('🔧 Settings update sent to renderer');
2093
2098
  }
2094
2099
 
2095
2100
  // Methods called by WebServer
@@ -2112,7 +2117,7 @@ class KaiPlayerApp {
2112
2117
  }
2113
2118
 
2114
2119
  async addSongToQueue(queueItem) {
2115
- console.log('🎵 MAIN addSongToQueue called with:', queueItem);
2120
+ log('🎵 MAIN addSongToQueue called with:', queueItem);
2116
2121
 
2117
2122
  // Use shared queueService
2118
2123
  const result = queueService.addSongToQueue(this.appState, queueItem);
@@ -2122,31 +2127,31 @@ class KaiPlayerApp {
2122
2127
  throw new Error(result.error);
2123
2128
  }
2124
2129
 
2125
- console.log('🎵 Created new queue item:', result.queueItem);
2130
+ log('🎵 Created new queue item:', result.queueItem);
2126
2131
 
2127
2132
  // Update legacy songQueue for compatibility
2128
2133
  this.songQueue = result.queue;
2129
2134
 
2130
2135
  // If queue was empty, automatically load and start playing the first song
2131
2136
  if (result.wasEmpty) {
2132
- console.log(`🎵 Queue was empty, auto-loading "${result.queueItem.title}"`);
2137
+ log(`🎵 Queue was empty, auto-loading "${result.queueItem.title}"`);
2133
2138
  try {
2134
2139
  // Use the returned queueItem which has the generated ID
2135
2140
  await this.loadKaiFile(result.queueItem.path, result.queueItem.id);
2136
- console.log('✅ Successfully auto-loaded song from queue');
2141
+ log('✅ Successfully auto-loaded song from queue');
2137
2142
  } catch (error) {
2138
2143
  console.error('❌ Failed to auto-load song from queue:', error);
2139
2144
  }
2140
2145
  }
2141
2146
 
2142
- console.log(`➕ Added "${queueItem.title}" to queue (requested by ${queueItem.requester})`);
2147
+ log(`➕ Added "${queueItem.title}" to queue (requested by ${queueItem.requester})`);
2143
2148
  return result;
2144
2149
  }
2145
2150
 
2146
2151
  onSongRequest(request) {
2147
2152
  // Notify renderer about new song request
2148
2153
  this.sendToRenderer('songRequest:new', request);
2149
- console.log(`🎤 New song request: "${request.song.title}" by ${request.requesterName}`);
2154
+ log(`🎤 New song request: "${request.song.title}" by ${request.requesterName}`);
2150
2155
  }
2151
2156
 
2152
2157
  // Effects management methods for web server
@@ -2229,7 +2234,7 @@ class KaiPlayerApp {
2229
2234
 
2230
2235
  // Player control methods for web server
2231
2236
  playerPlay() {
2232
- console.log('🎮 Admin play command - using playerService');
2237
+ log('🎮 Admin play command - using playerService');
2233
2238
  return playerService.play(this);
2234
2239
  }
2235
2240
 
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import { app } from 'electron';
4
+ import { log } from './logger.js';
4
5
 
5
6
  /**
6
7
  * StatePersistence - Saves and loads AppState to/from disk
@@ -38,13 +39,13 @@ class StatePersistence {
38
39
  // Restore mixer state if available
39
40
  if (savedState.mixer) {
40
41
  this.appState.state.mixer = savedState.mixer;
41
- console.log('📂 Loaded mixer state');
42
+ log('📂 Loaded mixer state');
42
43
  }
43
44
 
44
45
  // Restore effects state if available
45
46
  if (savedState.effects) {
46
47
  this.appState.state.effects = savedState.effects;
47
- console.log('📂 Loaded effects state');
48
+ log('📂 Loaded effects state');
48
49
  }
49
50
 
50
51
  // Restore preferences if available
@@ -53,14 +54,14 @@ class StatePersistence {
53
54
  ...this.appState.state.preferences,
54
55
  ...savedState.preferences,
55
56
  };
56
- console.log('📂 Loaded preferences');
57
+ log('📂 Loaded preferences');
57
58
  }
58
59
 
59
- console.log('✅ State loaded from disk');
60
+ log('✅ State loaded from disk');
60
61
  return true;
61
62
  } catch (error) {
62
63
  if (error.code === 'ENOENT') {
63
- console.log('ℹ️ No saved state found (first run)');
64
+ log('ℹ️ No saved state found (first run)');
64
65
  return false;
65
66
  }
66
67
 
@@ -74,12 +75,12 @@ class StatePersistence {
74
75
  // Restore from backup
75
76
  if (savedState.mixer) {
76
77
  this.appState.state.mixer = savedState.mixer;
77
- console.log('📂 Loaded mixer state from backup');
78
+ log('📂 Loaded mixer state from backup');
78
79
  }
79
80
 
80
81
  if (savedState.effects) {
81
82
  this.appState.state.effects = savedState.effects;
82
- console.log('📂 Loaded effects state from backup');
83
+ log('📂 Loaded effects state from backup');
83
84
  }
84
85
 
85
86
  if (savedState.preferences) {
@@ -87,10 +88,10 @@ class StatePersistence {
87
88
  ...this.appState.state.preferences,
88
89
  ...savedState.preferences,
89
90
  };
90
- console.log('📂 Loaded preferences from backup');
91
+ log('📂 Loaded preferences from backup');
91
92
  }
92
93
 
93
- console.log('✅ State restored from backup');
94
+ log('✅ State restored from backup');
94
95
 
95
96
  // Mark as dirty to save the restored state
96
97
  this.isDirty = true;
@@ -146,7 +147,7 @@ class StatePersistence {
146
147
  await fs.rename(tempPath, this.stateFile);
147
148
 
148
149
  this.isDirty = false;
149
- console.log('💾 State saved to disk');
150
+ log('💾 State saved to disk');
150
151
  } catch (error) {
151
152
  console.error('❌ Failed to save state:', error);
152
153
  throw error; // Propagate error so caller knows save failed
@@ -167,7 +168,7 @@ class StatePersistence {
167
168
  }
168
169
  }, 30000); // 30 seconds
169
170
 
170
- console.log('⏰ Started periodic state persistence (30s interval)');
171
+ log('⏰ Started periodic state persistence (30s interval)');
171
172
  }
172
173
 
173
174
  /**
@@ -177,7 +178,7 @@ class StatePersistence {
177
178
  if (this.saveInterval) {
178
179
  clearInterval(this.saveInterval);
179
180
  this.saveInterval = null;
180
- console.log('⏸️ Stopped periodic state persistence');
181
+ log('⏸️ Stopped periodic state persistence');
181
182
  }
182
183
  }
183
184
 
@@ -28,22 +28,26 @@ export function validateSongPath(filePath, songsFolder) {
28
28
 
29
29
  // Check if the resolved path starts with the songs folder
30
30
  // This prevents ../../../etc/passwd style attacks
31
- if (!resolvedFilePath.startsWith(resolvedSongsFolder + path.sep) &&
32
- resolvedFilePath !== resolvedSongsFolder) {
33
- return {
34
- valid: false,
35
- error: 'Access denied: path is outside songs directory'
31
+ if (
32
+ !resolvedFilePath.startsWith(resolvedSongsFolder + path.sep) &&
33
+ resolvedFilePath !== resolvedSongsFolder
34
+ ) {
35
+ return {
36
+ valid: false,
37
+ error: 'Access denied: path is outside songs directory',
36
38
  };
37
39
  }
38
40
 
39
41
  // Additional check: if the path was absolute, verify it's within songs folder
40
42
  if (path.isAbsolute(filePath)) {
41
43
  const resolvedAbsolute = path.resolve(filePath);
42
- if (!resolvedAbsolute.startsWith(resolvedSongsFolder + path.sep) &&
43
- resolvedAbsolute !== resolvedSongsFolder) {
44
- return {
45
- valid: false,
46
- error: 'Access denied: absolute path is outside songs directory'
44
+ if (
45
+ !resolvedAbsolute.startsWith(resolvedSongsFolder + path.sep) &&
46
+ resolvedAbsolute !== resolvedSongsFolder
47
+ ) {
48
+ return {
49
+ valid: false,
50
+ error: 'Access denied: absolute path is outside songs directory',
47
51
  };
48
52
  }
49
53
  // Use the absolute path as-is if it's valid
@@ -66,7 +70,7 @@ export function validateBase64Path(encodedPath, songsFolder) {
66
70
 
67
71
  try {
68
72
  const decoded = Buffer.from(encodedPath, 'base64url').toString('utf8');
69
-
73
+
70
74
  // The decoded format is "path:filename" or "path:trackName:trackIndex"
71
75
  const parts = decoded.split(':');
72
76
  if (parts.length < 2) {
@@ -74,19 +78,19 @@ export function validateBase64Path(encodedPath, songsFolder) {
74
78
  }
75
79
 
76
80
  const filePath = parts[0];
77
-
81
+
78
82
  // Validate the file path portion
79
83
  const validation = validateSongPath(filePath, songsFolder);
80
84
  if (!validation.valid) {
81
85
  return validation;
82
86
  }
83
87
 
84
- return {
85
- valid: true,
88
+ return {
89
+ valid: true,
86
90
  decodedPath: decoded,
87
- resolvedPath: validation.resolvedPath
91
+ resolvedPath: validation.resolvedPath,
88
92
  };
89
- } catch (error) {
93
+ } catch {
90
94
  return { valid: false, error: 'Invalid base64 encoding' };
91
95
  }
92
96
  }
@@ -108,5 +112,5 @@ export function isValidFile(filePath) {
108
112
  export default {
109
113
  validateSongPath,
110
114
  validateBase64Path,
111
- isValidFile
115
+ isValidFile,
112
116
  };