add notifier to apple-music integration

This commit is contained in:
2023-11-23 16:06:06 +01:00
parent 3791dedb5b
commit 97bfd865cc
5 changed files with 135 additions and 22 deletions

View File

@@ -115,7 +115,7 @@ app.get( '/clientDisplayNotifier', ( req, res ) => {
app.get( '/mainNotifier', ( req, res ) => { app.get( '/mainNotifier', ( req, res ) => {
const ipRetrieved = req.headers[ 'x-forwarded-for' ]; const ipRetrieved = req.headers[ 'x-forwarded-for' ];
const ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : req.connection.remoteAddress; const ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : req.connection.remoteAddress;
if ( ip === '::ffff:127.0.0.1' ) { if ( ip === '::ffff:127.0.0.1' || ip === '::1' ) {
res.writeHead( 200, { res.writeHead( 200, {
'Content-Type': 'text/event-stream', 'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache', 'Cache-Control': 'no-cache',
@@ -123,7 +123,7 @@ app.get( '/mainNotifier', ( req, res ) => {
} ); } );
res.status( 200 ); res.status( 200 );
res.flushHeaders(); res.flushHeaders();
let det = { 'type': 'basics', 'data': connectedClients }; let det = { 'type': 'basics' };
res.write( `data: ${ JSON.stringify( det ) }\n\n` ); res.write( `data: ${ JSON.stringify( det ) }\n\n` );
connectedMain = res; connectedMain = res;
} else { } else {
@@ -199,22 +199,23 @@ app.post( '/statusUpdate', ( req, res ) => {
} ); } );
// STATUS UPDATE from the client display to send to main ui // STATUS UPDATE from the client display to send to main ui
const allowedMainUpdates = [ 'disconnect', 'fullScreenStatus', 'visibility' ]; // Send update if page is closed
app.get( '/clientStatusUpdate/:status', ( req, res ) => { const allowedMainUpdates = [ 'blur', 'visibility' ];
if ( allowedTypes.includes( req.body.type ) ) { app.post( '/clientStatusUpdate', ( req, res ) => {
if ( allowedMainUpdates.includes( req.body.type ) ) {
const ipRetrieved = req.headers[ 'x-forwarded-for' ]; const ipRetrieved = req.headers[ 'x-forwarded-for' ];
const ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : req.connection.remoteAddress; const ip = ipRetrieved ? ipRetrieved.split( /, / )[ 0 ] : req.connection.remoteAddress;
sendClientUpdate( req.body.type, req.body.data, ip ); sendClientUpdate( req.body.type, ip );
res.send( 'ok' ); res.send( 'ok' );
} else { } else {
res.status( 400 ).send( 'ERR_UNKNOWN_TYPE' ); res.status( 400 ).send( 'ERR_UNKNOWN_TYPE' );
} }
} ); } );
const sendClientUpdate = ( update, data, ip ) => { const sendClientUpdate = ( update, ip ) => {
for ( let main in connectedMain ) { try {
connectedMain[ main ].write( 'data: ' + JSON.stringify( { 'type': update, 'ip': ip, 'data': data } ) + '\n\n' ); connectedMain.write( 'data: ' + JSON.stringify( { 'type': update, 'ip': ip } ) + '\n\n' );
} } catch ( err ) {}
} }
app.get( '/indexDirs', ( req, res ) => { app.get( '/indexDirs', ( req, res ) => {

View File

@@ -11,6 +11,14 @@
</head> </head>
<body> <body>
<div id="app"> <div id="app">
<div v-if="isShowingWarning" class="warning">
<h3>WARNING!</h3>
<p>A client display is being tampered with!</p>
<p>A desktop notification with a warning has already been dispatched.</p>
<button @click="dismissNotification()">Ok</button>
<div class="flash"></div>
</div>
<div v-if="isPreparingToPlay" class="preparingToPlay"> <div v-if="isPreparingToPlay" class="preparingToPlay">
<span class="material-symbols-outlined loading-spinner">autorenew</span> <span class="material-symbols-outlined loading-spinner">autorenew</span>
<h1>Loading player...</h1> <h1>Loading player...</h1>

View File

@@ -38,6 +38,7 @@ const app = Vue.createApp( {
isPreparingToPlay: false, isPreparingToPlay: false,
additionalSongInfo: {}, additionalSongInfo: {},
hasFinishedInit: false, hasFinishedInit: false,
isShowingWarning: false,
// For use with playlists that are partially from apple music and // For use with playlists that are partially from apple music and
// local drive // local drive
@@ -290,7 +291,9 @@ const app = Vue.createApp( {
} ); } );
} ); } );
} }
this.audioPlayer.pause(); try {
this.audioPlayer.pause();
} catch ( err ) {}
} else { } else {
this.audioPlayer.play(); this.audioPlayer.play();
this.musicKit.pause(); this.musicKit.pause();
@@ -619,6 +622,43 @@ const app = Vue.createApp( {
const result = await this.musicKit.api.music( '/v1/catalog/ch/search', queryParameters ); const result = await this.musicKit.api.music( '/v1/catalog/ch/search', queryParameters );
console.log( result ); console.log( result );
} )(); } )();
},
connectToNotifier() {
let source = new EventSource( '/mainNotifier', { withCredentials: true } );
source.onmessage = ( e ) => {
let data;
try {
data = JSON.parse( e.data );
} catch ( err ) {
data = { 'type': e.data };
}
if ( data.type === 'blur' ) {
this.isShowingWarning = true;
} else if ( data.type === 'visibility' ) {
this.isShowingWarning = true;
}
};
source.onopen = () => {
console.log( 'client notifier connected successfully' );
};
let self = this;
source.addEventListener( 'error', function( e ) {
if ( e.eventPhase == EventSource.CLOSED ) source.close();
if ( e.target.readyState == EventSource.CLOSED ) {
console.log( 'disconnected' );
}
setTimeout( () => {
self.connectToNotifier();
}, 1000 );
}, false );
},
dismissNotification() {
this.isShowingWarning = false;
} }
}, },
watch: { watch: {
@@ -653,6 +693,7 @@ const app = Vue.createApp( {
} else { } else {
this.initMusicKit(); this.initMusicKit();
} }
this.connectToNotifier();
fetch( '/getLocalIP' ).then( res => { fetch( '/getLocalIP' ).then( res => {
if ( res.status === 200 ) { if ( res.status === 200 ) {
res.text().then( ip => { res.text().then( ip => {

View File

@@ -313,3 +313,47 @@ body, html {
.slider-inactive { .slider-inactive {
cursor: default !important; cursor: default !important;
} }
.warning {
display: flex;
justify-content: center;
align-items: center;
width: 40vw;
height: 50vh;
font-size: 2vh;
background-color: rgb(255, 0, 0);
color: white;
position: fixed;
right: 1vh;
top: 1vh;
flex-direction: column;
z-index: 100;
}
.warning h3 {
font-size: 4vh;
}
.warning .flash {
background-color: rgba(255, 0, 0, 0.4);
animation: flashing linear infinite 1s;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
position: fixed;
z-index: -1;
}
@keyframes flashing {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}

View File

@@ -17,6 +17,7 @@ createApp( {
micAnalyzer: null, micAnalyzer: null,
beatDetected: false, beatDetected: false,
colorThief: null, colorThief: null,
lastDispatch: new Date().getTime() - 5000,
}; };
}, },
computed: { computed: {
@@ -290,23 +291,41 @@ createApp( {
return flux; return flux;
}, },
notifier() { notifier() {
console.log( 'notifier enabled' ); 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 // Detect if window is currently in focus
window.onblur = () => { window.onblur = () => {
console.log( 'left browser or page' ); this.sendNotification( 'blur' );
} }
// Detect key events // Detect if browser window becomes hidden (also with blur event)
window.addEventListener( 'keypress', keyEvent => { document.onvisibilitychange = () => {
console.log( keyEvent.key ); 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 );
} ); } );
// Detect if browser window becomes hidden (also with blur event) new Notification( 'YOU ARE UNDER SURVEILLANCE', {
document.addEventListener( 'visibilitychange', visibilityEvent => { body: 'Please return to the original webpage immediately!',
if ( document.visibilityState === 'hidden' ) { requireInteraction: true,
console.log( 'left page' ); } )
}
} );
} }
}, },
mounted() { mounted() {