diff --git a/MusicPlayerV2-GUI/src/components/playlistsView.vue b/MusicPlayerV2-GUI/src/components/playlistsView.vue index 4c10b5c..eddaeee 100644 --- a/MusicPlayerV2-GUI/src/components/playlistsView.vue +++ b/MusicPlayerV2-GUI/src/components/playlistsView.vue @@ -8,7 +8,10 @@
You are not logged into Apple Music. We therefore can't show you your playlists. Change that
++ You are not logged into Apple Music. We therefore can't show you your playlists. + Change that +
Use the button below to load songs from your local disk
{ return new Promise( ( resolve, reject ) => { - fetch( localStorage.getItem( 'url' ) + '/createRoomToken?roomName=' + roomName + '&useAntiTamper=' + useAntiTamper, { + fetch( localStorage.getItem( 'url' ) + '/createRoomToken?roomName=' + + roomName + '&useAntiTamper=' + useAntiTamper, { 'credentials': 'include' } ).then( res => { if ( res.status === 200 ) { @@ -110,7 +111,8 @@ class NotificationHandler { 'credentials': 'include' } ).then( res => { if ( res.status === 200 ) { - this.eventSource = new EventSource( localStorage.getItem( 'url' ) + '/socket/connection?room=' + this.roomName, { + this.eventSource = new EventSource( localStorage.getItem( 'url' ) + + '/socket/connection?room=' + this.roomName, { 'withCredentials': true } ); @@ -118,7 +120,8 @@ class NotificationHandler { this.isConnected = true; this.connectionWasSuccessful = true; this.reconnectRetryCount = 0; - console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Connection successfully established!' ); + console.log( '[ SSE Connection ] - ' + + new Date().toISOString() + ': Connection successfully established!' ); resolve(); }; @@ -135,8 +138,10 @@ class NotificationHandler { this.isConnected = false; this.eventSource?.close(); this.openConnectionsCount -= 1; - console.debug( e ); - console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Reconnecting due to connection error!' ); + console.debug( '[ SSE Connection ] - Error encountered: ', e ); + console.log( '[ SSE Connection ] - ' + + new Date().toISOString() + + ': Reconnecting due to connection error!' ); this.eventSource = undefined; @@ -146,7 +151,8 @@ class NotificationHandler { }, 1000 * this.reconnectRetryCount ); } }; - } else if ( res.status === 403 || res.status === 401 || res.status === 404 || res.status === 402 ) { + } else if ( res.status === 403 || res.status === 401 + || res.status === 404 || res.status === 402 ) { document.dispatchEvent( new Event( 'musicplayer:autherror' ) ); reject( 'ERR_UNAUTHORIZED' ); } else { @@ -158,7 +164,8 @@ class NotificationHandler { reject( 'ERR_ROOM_CONNECTING' ); } else { this.openConnectionsCount -= 1; - console.log( '[ SSE Connection ] - ' + new Date().toISOString() + ': Reconnecting due to severe connection error!' ); + console.log( '[ SSE Connection ] - ' + new Date().toISOString() + + ': Reconnecting due to severe connection error!' ); this.eventSource = undefined; diff --git a/backend/src/app.ts b/backend/src/app.ts index 6b42062..8e44477 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -21,6 +21,7 @@ import storeSDK from '@janishutz/store-sdk'; import sdk from '@janishutz/login-sdk-server'; import sse from './sse'; import socket from './socket'; +import logger from './logger'; // const isFossVersion = true; // @@ -42,15 +43,13 @@ const run = () => { const httpServer = createServer( app ); if ( !isFossVersion ) { - console.error( '[ APP ] Starting in non-FOSS version' ); + logger.info( '[ APP ] Starting in non-FOSS version' ); const storeConfig = JSON.parse( fs.readFileSync( path.join( __dirname, '/config/store-sdk.config.secret.json' ) ).toString() ); - console.error( storeConfig ); - storeSDK.configure( storeConfig ); // ─────────────────────────────────────────────────────────────────── @@ -139,6 +138,7 @@ const run = () => { 'useAntiTamper': request.query.useAntiTamper === 'true' ? true : false, }; + logger.debug( `Created room "${ roomName }"` ); response.send( roomToken ); } else { if ( @@ -211,7 +211,7 @@ const run = () => { } else { storeSDK.getSubscriptions( uid ) .then( stat => { - console.error( 'Subscription check was successful' ); + logger.log( 'Subscription check was successful' ); const now = new Date().getTime(); for ( const sub in stat ) { @@ -232,7 +232,7 @@ const run = () => { resolve( false ); } ) .catch( e => { - console.error( 'Subscription check unsuccessful with error', e ); + logger.error( 'Subscription check unsuccessful with error', e ); reject( 'ERR_NOT_OWNED' ); } ); } diff --git a/backend/src/logger.ts b/backend/src/logger.ts new file mode 100644 index 0000000..f8bd59d --- /dev/null +++ b/backend/src/logger.ts @@ -0,0 +1,104 @@ +import { + writeFile +} from 'node:fs'; + +const log = ( ...msg: string[] ) => { + output( 'log', log.caller.toString(), ...msg ); +}; + +const info = ( ...msg: string[] ) => { + output( 'info', log.caller.toString(), ...msg ); +}; + +const debug = ( ...msg: string[] ) => { + output( 'debug', log.caller.toString(), ...msg ); +}; + +const warn = ( ...msg: string[] ) => { + output( 'warn', log.caller.toString(), ...msg ); +}; + +const error = ( ...msg: string[] ) => { + output( 'error', log.caller.toString(), ...msg ); +}; + +const fatal = ( ...msg: string[] ) => { + output( 'fatal', log.caller.toString(), ...msg ); +}; + + +let loc = 'stderr'; +let lev = 0; +type LogLevel = 'debug' | 'info' | 'log' | 'warn' | 'error' | 'fatal'; +const levels = [ + 'debug', + 'info', + 'log', + 'warn', + 'error', + 'fatal' +]; + +const configure = ( location: 'stderr' | 'file', minLevel: LogLevel, file?: string ) => { + if ( location === 'file' && !file ) { + throw new Error( 'File parameter required when location is "file"' ); + } + + loc = location === 'stderr' ? 'stderr' : file; + lev = levels.indexOf( minLevel ); +}; + + +const logfile: string[] = []; + +const output = ( level: LogLevel, caller: string, ...message: string[] ) => { + if ( levels.indexOf( level ) < lev ) { + return; + } + + const msg = message.join( ' ' ); + const out = `[${ level.toUpperCase() }] (${ new Date().toISOString() }) in ${ caller }: ${ msg }`; + + if ( loc === 'stderr' ) { + console.error( out ); + } else { + logfile.push( out ); + save(); + } +}; + +let isSaving = false; +let waitingOnSave = false; + +const save = () => { + if ( isSaving ) { + waitingOnSave = true; + + return; + } + + isSaving = true; + writeFile( loc, JSON.stringify( logfile ), err => { + if ( err ) + console.error( '[LOGGER] Failed to save with error ' + err ); + + if ( waitingOnSave ) { + waitingOnSave = false; + isSaving = false; + save(); + } + + isSaving = false; + } ); +}; + + +export default { + log, + info, + debug, + warn, + error, + fatal, + configure +}; diff --git a/backend/src/sse.ts b/backend/src/sse.ts index 6b31744..3e94b83 100644 --- a/backend/src/sse.ts +++ b/backend/src/sse.ts @@ -4,6 +4,7 @@ import bodyParser from 'body-parser'; import { SocketData } from './definitions'; +import logger from './logger'; const useSSE = ( app: express.Application, @@ -86,7 +87,11 @@ const useSSE = ( for ( const c in cl ) { if ( cl[ c ] === sid ) { - cl.splice( parseInt( c ), 1 ); + try { + cl.splice( parseInt( c ), 1 ); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch ( _ ) { /* empty */ } + break; } } @@ -120,12 +125,13 @@ const useSSE = ( ( request: express.Request, response: express.Response ) => { if ( request.query.room ) { if ( socketData[ String( request.query.room ) ] ) { + logger.debug( `Room "${ request.query.room }" was joined` ); response.send( 'ok' ); } else { response.status( 404 ).send( 'ERR_ROOM_NOT_FOUND' ); } } else { - response.status( 404 ).send( 'ERR_NO_ROOM_SPECIFIED' ); + response.status( 400 ).send( 'ERR_NO_ROOM_SPECIFIED' ); } } ); @@ -138,8 +144,17 @@ const useSSE = ( ( request: express.Request, response: express.Response ) => { if ( socketData[ request.body.roomName ] ) { if ( request.body.event === 'tampering' ) { + logger.debug( `Room "${ + request.query.roomName }" has new event: Tampering` ); + const clients = clientReference[ request.body.roomName ]; + if ( !clients ) { + response.send( 'ERR_CANNOT_SEND' ); + + return; + } + for ( const client in clients ) { if ( importantClients[ clients[ client ] ] ) { importantClients[ clients[ client ] ] @@ -181,9 +196,18 @@ const useSSE = ( .playlistIndex = request.body.data; } + logger.debug( `Room "${ + request.query.roomName }" has new event: ${ update }` ); + if ( send ) { const clients = clientReference[ request.body.roomName ]; + if ( !clients ) { + response.send( 'ERR_CANNOT_SEND' ); + + return; + } + for ( const client in clients ) { if ( connectedClients[ clients[ client ] ] ) { connectedClients[ clients[ client ] ] @@ -222,9 +246,17 @@ const useSSE = ( socketData[ request.body.roomName ].roomToken === request.body.roomToken ) { + logger.debug( `Room "${ + request.query.roomName }" was deleted` ); socketData[ request.body.roomName ] = undefined; const clients = clientReference[ request.body.roomName ]; + if ( !clients ) { + response.send( 'ok' ); + + return; + } + for ( const client in clients ) { if ( connectedClients[ clients[ client ] ] ) { connectedClients[ clients[ client ] ] @@ -234,6 +266,8 @@ const useSSE = ( } ) + '\n\n' ); } } + + response.send( 'ok' ); } else { response.send( 403 ).send( 'ERR_UNAUTHORIZED' ); } diff --git a/backend/src/storage/db.ts b/backend/src/storage/db.ts index 23303b9..59710fb 100644 --- a/backend/src/storage/db.ts +++ b/backend/src/storage/db.ts @@ -10,6 +10,7 @@ import path from 'path'; import fs from 'fs'; import * as sqlDB from './mysqldb.js'; +import logger from '../logger.js'; declare let __dirname: string | undefined; @@ -33,9 +34,9 @@ dbh.connect(); */ const initDB = (): undefined => { ( async () => { - console.log( '[ DB ] Setting up...' ); + logger.info( '[ DB ] Setting up...' ); dbh.setupDB(); - console.log( '[ DB ] Setting up complete!' ); + logger.info( '[ DB ] Setting up complete!' ); } )(); }; diff --git a/backend/src/storage/mysqldb.ts b/backend/src/storage/mysqldb.ts index a44571f..703915e 100644 --- a/backend/src/storage/mysqldb.ts +++ b/backend/src/storage/mysqldb.ts @@ -10,6 +10,7 @@ import mysql from 'mysql'; import fs from 'fs'; import path from 'path'; +import logger from '../logger'; declare let __dirname: string | undefined; @@ -63,20 +64,20 @@ class SQLDB { const self = this; if ( this.isRecovering ) { - console.log( '[ SQL ] Attempting to recover from critical error' ); + logger.info( '[ SQL ] Attempting to recover from critical error' ); this.sqlConnection = mysql.createConnection( this.config ); this.isRecovering = false; } this.sqlConnection.connect( err => { if ( err ) { - console.error( '[ SQL ]: An error ocurred whilst connecting: ' + err.stack ); + logger.error( '[ SQL ]: An error ocurred whilst connecting: ' + err.stack ); reject( err ); return; } - console.log( '[ SQL ] Connected to database successfully' ); + logger.info( '[ SQL ] Connected to database successfully' ); self.sqlConnection.on( 'error', err => { if ( err.code === 'ECONNRESET' ) { self.isRecovering = true; @@ -85,7 +86,7 @@ class SQLDB { self.connect(); }, 1000 ); } else { - console.error( err ); + logger.error( err ); } } ); resolve( 'connection' );