diff --git a/backend/app.js b/backend/app.js index 3d44579..d7ca71b 100644 --- a/backend/app.js +++ b/backend/app.js @@ -4,7 +4,7 @@ const path = require( 'path' ); const expressSession = require( 'express-session' ); const fs = require( 'fs' ); const bodyParser = require( 'body-parser' ); -const favicon = require( 'serve-favicon' ); +// const favicon = require( 'serve-favicon' ); const authKey = '' + fs.readFileSync( path.join( __dirname + '/authorizationKey.txt' ) ); @@ -38,6 +38,98 @@ app.get( '/showcase.css', ( request, response ) => { response.sendFile( path.join( __dirname + '/ui/showcase.css' ) ); } ); +app.post( '/authSSE', ( req, res ) => { + if ( req.body.authKey === authKey ) { + req.session.isAuth = true; + res.send( 'ok' ); + } else { + res.send( 'hello' ); + } +} ); + +app.post( '/fancy/auth', ( req, res ) => { + if ( req.body.key === authKey ) { + req.session.isAuth = true; + res.redirect( '/fancy' ); + } else { + res.send( 'wrong' ); + } +} ); + +app.get( '/fancy', ( req, res ) => { + if ( req.session.isAuth ) { + res.sendFile( path.join( __dirname + '/ui/fancy/showcase.html' ) ); + } else { + res.sendFile( path.join( __dirname + '/ui/fancy/auth.html' ) ); + } +} ); + +app.get( '/fancy/showcase.js', ( req, res ) => { + if ( req.session.isAuth ) { + res.sendFile( path.join( __dirname + '/ui/fancy/showcase.js' ) ); + } else { + res.redirect( '/' ); + } +} ); + +app.get( '/fancy/showcase.css', ( req, res ) => { + if ( req.session.isAuth ) { + res.sendFile( path.join( __dirname + '/ui/fancy/showcase.css' ) ); + } else { + res.redirect( '/' ); + } +} ); + +app.get( '/fancy/backgroundAnim.css', ( req, res ) => { + if ( req.session.isAuth ) { + res.sendFile( path.join( __dirname + '/ui/fancy/backgroundAnim.css' ) ); + } else { + res.redirect( '/' ); + } +} ); + +let connectedMain = {}; + +app.get( '/mainNotifier', ( req, res ) => { + const ipRetrieved = req.headers[ 'x-forwarded-for' ]; + const ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : req.connection.remoteAddress; + if ( req.session.isAuth ) { + res.writeHead( 200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + } ); + res.status( 200 ); + res.flushHeaders(); + let det = { 'type': 'basics' }; + res.write( `data: ${ JSON.stringify( det ) }\n\n` ); + connectedMain = res; + } else { + res.send( 'wrong' ); + } +} ); + +// STATUS UPDATE from the client display to send to main ui +// Send update if page is closed +const allowedMainUpdates = [ 'blur', 'visibility' ]; +app.post( '/clientStatusUpdate', ( req, res ) => { + if ( allowedMainUpdates.includes( req.body.type ) ) { + const ipRetrieved = req.headers[ 'x-forwarded-for' ]; + const ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : req.connection.remoteAddress; + sendClientUpdate( req.body.type, ip ); + res.send( 'ok' ); + } else { + res.status( 400 ).send( 'ERR_UNKNOWN_TYPE' ); + } +} ); + +const sendClientUpdate = ( update, ip ) => { + try { + connectedMain.write( 'data: ' + JSON.stringify( { 'type': update, 'ip': ip } ) + '\n\n' ); + } catch ( err ) {} +} + + app.post( '/connect', ( request, response ) => { if ( request.body.authKey === authKey ) { request.session.authorized = true; diff --git a/backend/ui/fancy/auth.html b/backend/ui/fancy/auth.html new file mode 100644 index 0000000..93fe795 --- /dev/null +++ b/backend/ui/fancy/auth.html @@ -0,0 +1,73 @@ + + + + + + Authenticate - Fancy Remote Display + + + +
+

Authenticate - Fancy Remote Display

+
+ + + +
+
+ + \ No newline at end of file diff --git a/backend/ui/fancy/backgroundAnim.css b/backend/ui/fancy/backgroundAnim.css new file mode 100644 index 0000000..59d4fba --- /dev/null +++ b/backend/ui/fancy/backgroundAnim.css @@ -0,0 +1,44 @@ +.background { + position: fixed; + left: -50vw; + width: 200vw; + height: 200vw; + top: -50vw; + z-index: -1; + filter: blur(10px); + background: conic-gradient( blue, green, red, blue ); + animation: gradientAnim 10s infinite linear; + background-position: center; +} + +.beat, .beat-manual { + height: 100%; + width: 100%; + background-color: rgba( 0, 0, 0, 0.15 ); + display: none; +} + +.beat { + animation: beatAnim 0.6s infinite linear; +} + +@keyframes beatAnim { + 0% { + background-color: rgba( 0, 0, 0, 0.2 ); + } + 20% { + background-color: rgba( 0, 0, 0, 0 ); + } + 100% { + background-color: rgba( 0, 0, 0, 0.2 ); + } +} + +@keyframes gradientAnim { + from { + transform: rotate( 0deg ); + } + to { + transform: rotate( 360deg ); + } +} \ No newline at end of file diff --git a/backend/ui/fancy/showcase.css b/backend/ui/fancy/showcase.css new file mode 100644 index 0000000..bc3c4b3 --- /dev/null +++ b/backend/ui/fancy/showcase.css @@ -0,0 +1,208 @@ +.material-symbols-outlined { + font-variation-settings: + 'FILL' 0, + 'wght' 400, + 'GRAD' 0, + 'opsz' 24 +} + +body, html { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + color: white; +} + +body { + font-family: sans-serif; +} + +.content { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.playing-symbols { + position: absolute; + left: 10vw; + display: flex; + justify-content: center; + align-items: center; + flex-direction: row; + width: 5vw; + height: 5vw; + background-color: rgba( 0, 0, 0, 0.6 ); +} + +.playing-symbols-wrapper { + width: 4vw; + height: 5vw; + display: flex; + justify-content: center; + align-items: center; + flex-direction: row; +} + +.playing-bar { + height: 60%; + background-color: white; + width: 10%; + border-radius: 50px; + margin: auto; +} + +#bar-1 { + animation: music-playing 0.9s infinite ease-in-out; +} + +#bar-2 { + animation: music-playing 0.9s infinite ease-in-out; + animation-delay: 0.3s; +} + +#bar-3 { + animation: music-playing 0.9s infinite ease-in-out; + animation-delay: 0.6s; +} + +@keyframes music-playing { + 0% { + transform: scaleY( 1 ); + } + 50% { + transform: scaleY( 0.5 ); + } + 100% { + transform: scaleY( 1 ); + } +} + +.song-list-wrapper { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.song-list { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + width: 80%; + margin: 2px; + padding: 1vh; + border: 1px white solid; + background-color: rgba( 0, 0, 0, 0.4 ); +} + +.song-details-wrapper { + margin: 0; + display: block; + margin-left: 10px; + margin-right: auto; +} + +.song-list .song-image { + width: 5vw; + height: 5vw; + object-fit: cover; + object-position: center; + font-size: 5vw; +} + +.pause-icon { + width: 5vw; + height: 5vw; + object-fit: cover; + object-position: center; + font-size: 5vw !important; + user-select: none; +} + +.current-song-wrapper { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + height: 55vh; + width: 100%; + margin-bottom: 0.5%; + margin-top: 0.25%; +} + +.current-song { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + margin-top: 1vh; + padding: 1vh; + text-align: center; + background-color: rgba( 0, 0, 0, 0.4 ); +} + +.fancy-view-song-art { + height: 30vh; + width: 30vh; + object-fit: cover; + object-position: center; + margin-bottom: 10px; + font-size: 30vh !important; +} + +#app { + background-color: rgba( 0, 0, 0, 0 ); +} + +#progress, #progress::-webkit-progress-bar { + background-color: rgba(45, 28, 145); + color: rgba(45, 28, 145); + width: 30vw; + border: none; + border-radius: 0px; + accent-color: white; + -webkit-appearance: none; + appearance: none; +} + +#progress::-moz-progress-bar { + background-color: white; +} + +#progress::-webkit-progress-value { + background-color: white !important; +} + +.mode-selector-wrapper { + opacity: 0; + position: fixed; + right: 0.5%; + top: 0.5%; + padding: 0.5%; +} + +.mode-selector-wrapper:hover { + opacity: 1; +} + +.dancing-style { + font-size: 250%; + margin: 0; + font-weight: bolder; +} + +.info { + position: fixed; + font-size: 12px; + transform: rotate(270deg); + left: -150px; + margin: 0; + padding: 0; + top: 50%; +} \ No newline at end of file diff --git a/backend/ui/fancy/showcase.html b/backend/ui/fancy/showcase.html new file mode 100644 index 0000000..86973e6 --- /dev/null +++ b/backend/ui/fancy/showcase.html @@ -0,0 +1,72 @@ + + + + + + + + Showcase - MusicPlayerV2 + + + + + +
Designed and developed by Janis Hutz https://janishutz.com
+
+
+
+ music_note + + +
+ +

{{ playingSong.title }}

+

{{ playingSong.dancingStyle }}

+

{{ playingSong.artist }}

+
+
+
+ +
+
+
+ music_note + + +
+
+
+
+
+
+
+ pause +
+

{{ song.title }}

+

{{ song.artist }}

+
+
+ {{ getTimeUntil( song ) }} +
+
+ +
+
+
+

Loading...

+
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/backend/ui/fancy/showcase.js b/backend/ui/fancy/showcase.js new file mode 100644 index 0000000..3eeaa5d --- /dev/null +++ b/backend/ui/fancy/showcase.js @@ -0,0 +1,351 @@ +// eslint-disable-next-line no-undef +const { createApp } = Vue; + +createApp( { + data() { + return { + hasLoaded: false, + songs: [], + playingSong: {}, + isPlaying: false, + pos: 0, + queuePos: 0, + colourPalette: [], + progressBar: 0, + timeTracker: null, + visualizationSettings: 'mic', + micAnalyzer: null, + beatDetected: false, + colorThief: null, + lastDispatch: new Date().getTime() - 5000, + isReconnecting: false, + }; + }, + computed: { + songQueue() { + let ret = []; + let pos = 0; + for ( let song in this.songs ) { + if ( pos >= this.queuePos ) { + ret.push( this.songs[ song ] ); + } + pos += 1; + } + return ret; + }, + getTimeUntil() { + return ( song ) => { + let timeRemaining = 0; + for ( let i = this.queuePos; i < Object.keys( this.songs ).length - 1; i++ ) { + if ( this.songs[ i ] == song ) { + break; + } + timeRemaining += parseInt( this.songs[ i ].duration ); + } + if ( this.isPlaying ) { + if ( timeRemaining === 0 ) { + return 'Currently playing'; + } else { + return 'Playing in less than ' + Math.ceil( timeRemaining / 60 - this.pos / 60 ) + 'min'; + } + } else { + if ( timeRemaining === 0 ) { + return 'Plays next'; + } else { + return 'Playing less than ' + Math.ceil( timeRemaining / 60 - this.pos / 60 ) + 'min after starting to play'; + } + } + } + } + }, + methods: { + startTimeTracker () { + this.timeTracker = setInterval( () => { + this.pos = ( new Date().getTime() - this.playingSong.startTime ) / 1000 + this.oldPos; + this.progressBar = ( this.pos / this.playingSong.duration ) * 1000; + if ( isNaN( this.progressBar ) ) { + this.progressBar = 0; + } + }, 100 ); + }, + stopTimeTracker () { + clearInterval( this.timeTracker ); + this.oldPos = this.pos; + }, + getImageData() { + return new Promise( ( resolve, reject ) => { + if ( this.playingSong.hasCoverArt ) { + setTimeout( () => { + const img = document.getElementById( 'current-image' ); + if ( img.complete ) { + resolve( this.colorThief.getPalette( img ) ); + } else { + img.addEventListener( 'load', () => { + resolve( this.colorThief.getPalette( img ) ); + } ); + } + }, 500 ); + } else { + reject( 'no image' ); + } + } ); + }, + connect() { + this.colorThief = new ColorThief(); + let source = new EventSource( '/clientDisplayNotifier', { withCredentials: true } ); + source.onmessage = ( e ) => { + let data; + try { + data = JSON.parse( e.data ); + } catch ( err ) { + data = { 'type': e.data }; + } + if ( data.type === 'basics' ) { + this.isPlaying = data.data.isPlaying ?? false; + this.playingSong = data.data.playingSong ?? {}; + this.songs = data.data.songQueue ?? []; + this.pos = data.data.pos ?? 0; + this.oldPos = data.data.pos ?? 0; + this.progressBar = this.pos / this.playingSong.duration * 1000; + this.queuePos = data.data.queuePos ?? 0; + this.getImageData().then( palette => { + this.colourPalette = palette; + this.handleBackground(); + } ).catch( () => { + this.colourPalette = [ { 'r': 255, 'g': 0, 'b': 0 }, { 'r': 0, 'g': 255, 'b': 0 }, { 'r': 0, 'g': 0, 'b': 255 } ]; + this.handleBackground(); + } ); + } else if ( data.type === 'pos' ) { + this.pos = data.data; + this.oldPos = data.data; + this.progressBar = data.data / this.playingSong.duration * 1000; + } else if ( data.type === 'isPlaying' ) { + this.isPlaying = data.data; + this.handleBackground(); + } else if ( data.type === 'songQueue' ) { + this.songs = data.data; + } else if ( data.type === 'playingSong' ) { + this.playingSong = data.data; + this.getImageData().then( palette => { + this.colourPalette = palette; + this.handleBackground(); + } ).catch( () => { + this.colourPalette = [ [ 255, 0, 0 ], [ 0, 255, 0 ], [ 0, 0, 255 ] ]; + this.handleBackground(); + } ); + } else if ( data.type === 'queuePos' ) { + this.queuePos = data.data; + } + }; + + source.onopen = () => { + this.hasLoaded = true; + }; + + let self = this; + + source.addEventListener( 'error', function( e ) { + if ( e.eventPhase == EventSource.CLOSED ) source.close(); + + if ( e.target.readyState == EventSource.CLOSED ) { + console.log( 'disconnected' ); + } + + // TODO: Notify about disconnect + setTimeout( () => { + if ( !self.isReconnecting ) { + self.isReconnecting = true; + self.connect(); + } + }, 1000 ); + }, false ); + }, + handleBackground() { + let colourDetails = []; + let colours = []; + let differentEnough = true; + if ( this.colourPalette[ 0 ] ) { + for ( let i in this.colourPalette ) { + for ( let colour in colourDetails ) { + const colourDiff = ( Math.abs( colourDetails[ colour ][ 0 ] - this.colourPalette[ i ][ 0 ] ) / 255 + + Math.abs( colourDetails[ colour ][ 1 ] - this.colourPalette[ i ][ 1 ] ) / 255 + + Math.abs( colourDetails[ colour ][ 2 ] - this.colourPalette[ i ][ 2 ] ) / 255 ) / 3 * 100; + if ( colourDiff > 15 ) { + differentEnough = true; + } + } + if ( differentEnough ) { + colourDetails.push( this.colourPalette[ i ] ); + colours.push( 'rgb(' + this.colourPalette[ i ][ 0 ] + ',' + this.colourPalette[ i ][ 1 ] + ',' + this.colourPalette[ i ][ 2 ] + ')' ); + } + differentEnough = false; + } + } + let outColours = 'conic-gradient('; + if ( colours.length < 3 ) { + for ( let i = 0; i < 3; i++ ) { + if ( colours[ i ] ) { + outColours += colours[ i ] + ','; + } else { + if ( i === 0 ) { + outColours += 'blue,'; + } else if ( i === 1 ) { + outColours += 'green,'; + } else if ( i === 2 ) { + outColours += 'red,'; + } + } + } + } else if ( colours.length < 11 ) { + for ( let i in colours ) { + outColours += colours[ i ] + ','; + } + } else { + for ( let i = 0; i < 10; i++ ) { + outColours += colours[ i ] + ','; + } + } + outColours += colours[ 0 ] ?? 'blue' + ')'; + + $( '#background' ).css( 'background', outColours ); + this.setVisualization(); + }, + setVisualization () { + if ( Object.keys( this.playingSong ).length > 0 ) { + if ( this.visualizationSettings === 'bpm' ) { + if ( this.playingSong.bpm && this.isPlaying ) { + $( '.beat' ).show(); + $( '.beat' ).css( 'animation-duration', 60 / this.playingSong.bpm ); + $( '.beat' ).css( 'animation-delay', this.pos % ( 60 / this.playingSong.bpm * this.pos ) + this.playingSong.bpmOffset - ( 60 / this.playingSong.bpm * this.pos / 2 ) ); + } else { + $( '.beat' ).hide(); + } + try { + clearInterval( this.micAnalyzer ); + } catch ( err ) {} + } else if ( this.visualizationSettings === 'off' ) { + $( '.beat' ).hide(); + try { + clearInterval( this.micAnalyzer ); + } catch ( err ) {} + } else if ( this.visualizationSettings === 'mic' ) { + $( '.beat-manual' ).hide(); + try { + clearInterval( this.micAnalyzer ); + } catch ( err ) {} + this.micAudioHandler(); + } + } else { + console.log( 'not playing yet' ); + } + }, + micAudioHandler () { + const audioContext = new ( window.AudioContext || window.webkitAudioContext )(); + const analyser = audioContext.createAnalyser(); + analyser.fftSize = 256; + const bufferLength = analyser.frequencyBinCount; + const dataArray = new Uint8Array( bufferLength ); + + navigator.mediaDevices.getUserMedia( { audio: true } ).then( ( stream ) => { + const mic = audioContext.createMediaStreamSource( stream ); + mic.connect( analyser ); + analyser.getByteFrequencyData( dataArray ); + let prevSpectrum = null; + let threshold = 10; // Adjust as needed + this.beatDetected = false; + this.micAnalyzer = setInterval( () => { + analyser.getByteFrequencyData( dataArray ); + // Convert the frequency data to a numeric array + const currentSpectrum = Array.from( dataArray ); + + if ( prevSpectrum ) { + // Calculate the spectral flux + const flux = this.calculateSpectralFlux( prevSpectrum, currentSpectrum ); + + if ( flux > threshold && !this.beatDetected ) { + // Beat detected + this.beatDetected = true; + this.animateBeat(); + } + } + prevSpectrum = currentSpectrum; + }, 20 ); + } ); + }, + animateBeat () { + $( '.beat-manual' ).stop(); + const duration = Math.ceil( 60 / ( this.playingSong.bpm ?? 180 ) * 500 ) - 50; + $( '.beat-manual' ).fadeIn( 50 ); + setTimeout( () => { + $( '.beat-manual' ).fadeOut( duration ); + setTimeout( () => { + $( '.beat-manual' ).stop(); + this.beatDetected = false; + }, duration ); + }, 50 ); + }, + calculateSpectralFlux( prevSpectrum, currentSpectrum ) { + let flux = 0; + + for ( let i = 0; i < prevSpectrum.length; i++ ) { + const diff = currentSpectrum[ i ] - prevSpectrum[ i ]; + flux += Math.max( 0, diff ); + } + + return flux; + }, + notifier() { + if ( parseInt( this.lastDispatch ) + 5000 < new Date().getTime() ) { + + } + Notification.requestPermission(); + + console.warn( '[ notifier ]: Status is now enabled \n\n-> Any leaving or tampering with the website will send a notification to the host' ); + // Detect if window is currently in focus + window.onblur = () => { + this.sendNotification( 'blur' ); + } + + // Detect if browser window becomes hidden (also with blur event) + document.onvisibilitychange = () => { + if ( document.visibilityState === 'hidden' ) { + this.sendNotification( 'visibility' ); + } + }; + }, + sendNotification( notification ) { + let fetchOptions = { + method: 'post', + body: JSON.stringify( { 'type': notification } ), + headers: { + 'Content-Type': 'application/json', + 'charset': 'utf-8' + }, + }; + fetch( '/clientStatusUpdate', fetchOptions ).catch( err => { + console.error( err ); + } ); + + new Notification( 'YOU ARE UNDER SURVEILLANCE', { + body: 'Please return to the original webpage immediately!', + requireInteraction: true, + } ) + } + }, + mounted() { + this.connect(); + this.notifier(); + // if ( this.visualizationSettings === 'mic' ) { + // this.micAudioHandler(); + // } + }, + watch: { + isPlaying( value ) { + if ( value ) { + this.startTimeTracker(); + } else { + this.stopTimeTracker(); + } + } + } +} ).mount( '#app' ); diff --git a/backend/ui/showcase.js b/backend/ui/showcase.js index 31e2a88..3724e99 100644 --- a/backend/ui/showcase.js +++ b/backend/ui/showcase.js @@ -13,6 +13,7 @@ createApp( { colourPalette: [], progressBar: 0, timeTracker: null, + isReconnecting: false, }; }, computed: { @@ -125,7 +126,10 @@ createApp( { } setTimeout( () => { - self.connect(); + if ( !self.isReconnecting ) { + self.isReconnecting = true; + self.connect(); + } }, 1000 ); }, false ); }, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0136e12..800445f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,7 @@ "cors": "^2.8.5", "csv-parser": "^3.0.0", "electron-squirrel-startup": "^1.0.0", + "eventsource": "^2.0.2", "express-session": "^1.17.3", "ip": "^1.1.8", "jquery": "^3.7.1", @@ -6869,6 +6870,14 @@ "node": ">=0.8.x" } }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/execa": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/execa/-/execa-1.0.0.tgz", @@ -20172,6 +20181,11 @@ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true }, + "eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==" + }, "execa": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/execa/-/execa-1.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9fdab61..8efd48e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,6 +31,7 @@ "cors": "^2.8.5", "csv-parser": "^3.0.0", "electron-squirrel-startup": "^1.0.0", + "eventsource": "^2.0.2", "express-session": "^1.17.3", "ip": "^1.1.8", "jquery": "^3.7.1", diff --git a/frontend/src/app.js b/frontend/src/app.js index 97a5af9..535b60c 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -12,6 +12,7 @@ const ip = require( 'ip' ); const jwt = require( 'jsonwebtoken' ); const shell = require( 'electron' ).shell; const beautify = require( 'json-beautify' ); +const EventSource = require( 'eventsource' ); app.use( bodyParser.urlencoded( { extended: false } ) ); @@ -41,12 +42,72 @@ const connect = () => { } ).catch( err => { console.error( err ); } ); + connectToSSESource(); return 'connecting'; } else { return 'noAuthKey'; } }; +let isSSEAuth = false; +let sessionToken = ''; +let errorCount = 0; +let isReconnecting = false; + +const connectToSSESource = () => { + if ( isSSEAuth ) { + let source = new EventSource( remoteURL + '/mainNotifier', { + https: true, + withCredentials: true, + headers: { + 'Cookie': sessionToken + } + } ); + source.onmessage = ( e ) => { + let data; + try { + data = JSON.parse( e.data ); + } catch ( err ) { + data = { 'type': e.data }; + } + if ( data.type === 'blur' ) { + sendClientUpdate( data.type, data.ip ); + } else if ( data.type === 'visibility' ) { + sendClientUpdate( data.type, data.ip ); + } + }; + + source.onopen = () => { + console.log( '[ BACKEND INTEGRATION ] Connection to notifier successful' ); + }; + + source.addEventListener( 'error', function( e ) { + if ( e.eventPhase == EventSource.CLOSED ) source.close(); + + setTimeout( () => { + if ( !isReconnecting ) { + if ( errorCount > 5 ) { + isSSEAuth = false; + } + isReconnecting = true; + console.log( '[ BACKEND INTEGRATION ] Disconnected from notifier, reconnecting...' ); + connectToSSESource(); + } + }, 1000 ); + }, false ); + } else { + axios.post( remoteURL + '/authSSE', { 'authKey': authKey } ).then( res => { + if ( res.status == 200 ) { + sessionToken = res.headers[ 'set-cookie' ][ 0 ].slice( 0, res.headers[ 'set-cookie' ][ 0 ].indexOf( ';' ) ); + isSSEAuth = true; + connectToSSESource(); + } else { + connectToSSESource(); + } + } ); + } +} + let authKey = conf.authKey ?? ''; connect(); diff --git a/frontend/src/client/showcase.js b/frontend/src/client/showcase.js index 64cde9b..7294eaf 100644 --- a/frontend/src/client/showcase.js +++ b/frontend/src/client/showcase.js @@ -152,7 +152,10 @@ createApp( { // TODO: Notify about disconnect setTimeout( () => { - self.connect(); + if ( !self.isReconnecting ) { + self.isReconnecting = true; + self.connect(); + } }, 1000 ); }, false ); },