mirror of
https://github.com/janishutz/MusicPlayer.git
synced 2026-09-10 14:25:24 +02:00
feat: basic connection between frontend and backend
This commit is contained in:
@@ -14,6 +14,7 @@ import room from './routes/room';
|
|||||||
import user from './routes/user';
|
import user from './routes/user';
|
||||||
|
|
||||||
const run = () => {
|
const run = () => {
|
||||||
|
// FIXME: Use DB instead of memory optionally
|
||||||
const sdkConfig = JSON.parse( fs.readFileSync( path.join(
|
const sdkConfig = JSON.parse( fs.readFileSync( path.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
'/../config/sdk.config.testing.json'
|
'/../config/sdk.config.testing.json'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Client,
|
Client,
|
||||||
|
Room,
|
||||||
RoomStore
|
RoomStore
|
||||||
} from './room';
|
} from './room';
|
||||||
import {
|
import {
|
||||||
@@ -16,7 +17,7 @@ const rooms: RoomStore = {};
|
|||||||
* @param room - The name of the room to get
|
* @param room - The name of the room to get
|
||||||
* @returns The room, or undefined if it is not present
|
* @returns The room, or undefined if it is not present
|
||||||
*/
|
*/
|
||||||
const get = ( room: string ) => {
|
const get = ( room: string ): Room | undefined => {
|
||||||
return rooms[room];
|
return rooms[room];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -114,7 +115,9 @@ const close = ( name: string, uid: string ) => {
|
|||||||
* @returns true if update succeeded
|
* @returns true if update succeeded
|
||||||
*/
|
*/
|
||||||
const updateState = ( room: string, uid: string, playing: boolean, index: number, start: number ) => {
|
const updateState = ( room: string, uid: string, playing: boolean, index: number, start: number ) => {
|
||||||
if ( !rooms[room] || rooms[room].owner !== uid ) return false;
|
if ( !rooms[room] || rooms[room].owner !== uid ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
rooms[room].state = {
|
rooms[room].state = {
|
||||||
'playing': playing,
|
'playing': playing,
|
||||||
@@ -149,10 +152,12 @@ const sendUpdate = ( room: string, kind: 'state' | 'playlist' ) => {
|
|||||||
|
|
||||||
if ( !roomObject ) return false;
|
if ( !roomObject ) return false;
|
||||||
|
|
||||||
roomObject.clients.forEach( client => client.response.write( `data: ${ {
|
roomObject.clients.forEach( client => client.response.write( `data: ${ JSON.stringify( {
|
||||||
'type': kind,
|
'type': kind,
|
||||||
'data': JSON.stringify( roomObject[kind] )
|
'data': JSON.stringify( roomObject[kind] )
|
||||||
} }\n\n` ) );
|
} ) }\n\n` ) );
|
||||||
|
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const sseMiddleware = ( kind: 'client' | 'trackingClient', config?: Confi
|
|||||||
|
|
||||||
const room = rooms.get( request.params.id );
|
const room = rooms.get( request.params.id );
|
||||||
|
|
||||||
if ( !room ) response.sendStatus( 404 );
|
if ( !room ) return response.sendStatus( 404 );
|
||||||
|
|
||||||
response.writeHead( 200, {
|
response.writeHead( 200, {
|
||||||
'Content-Type': 'text/event-stream',
|
'Content-Type': 'text/event-stream',
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ const routes = ( app: express.Application, foss: boolean, config: Config ) => {
|
|||||||
sdk.loginCheck(),
|
sdk.loginCheck(),
|
||||||
ownership.middleware(),
|
ownership.middleware(),
|
||||||
( request: express.Request, response: express.Response ) => {
|
( request: express.Request, response: express.Response ) => {
|
||||||
if ( !request.query.room ) return response.sendStatus( 400 );
|
if ( !request.query.room || !( /^[a-zA-Z0-9-]{3,20}/ ).test( String( request.query.room ) ) ) return response.sendStatus( 400 );
|
||||||
|
|
||||||
if ( rooms.create( String( request.query.room ), sdk.getUID( request )! ) )
|
if ( rooms.create( String( request.query.room ), sdk.getUID( request )! ) )
|
||||||
response.sendStatus( 200 );
|
response.sendStatus( 200 );
|
||||||
else
|
else
|
||||||
response.sendStatus( 500 );
|
response.sendStatus( 409 );
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const routes = ( app: express.Application, foss: boolean ) => {
|
|||||||
'/room/:id/admin',
|
'/room/:id/admin',
|
||||||
corsManager.middleware( false ),
|
corsManager.middleware( false ),
|
||||||
sdk.loginCheck(),
|
sdk.loginCheck(),
|
||||||
sseMiddleware( 'client' )
|
sseMiddleware( 'trackingClient' )
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const routes = ( app: express.Application, foss: boolean, config: Config ) => {
|
|||||||
|
|
||||||
response.send(
|
response.send(
|
||||||
JSON.stringify( {
|
JSON.stringify( {
|
||||||
'state': room.state,
|
'state': room?.state,
|
||||||
'playlist': lastRequest < room.playlist.lastUpdate ? room.playlist : undefined
|
'playlist': lastRequest < room!.playlist!.lastUpdate ? room!.playlist : undefined
|
||||||
} )
|
} )
|
||||||
);
|
);
|
||||||
} );
|
} );
|
||||||
@@ -51,7 +51,7 @@ const routes = ( app: express.Application, foss: boolean, config: Config ) => {
|
|||||||
if ( rooms.updatePlaylist(
|
if ( rooms.updatePlaylist(
|
||||||
request.params.id,
|
request.params.id,
|
||||||
sdk.getUID( request )!,
|
sdk.getUID( request )!,
|
||||||
request.body.playlist ? JSON.parse( request.body.playlist ) : []
|
request.body.playlist ?? []
|
||||||
) )
|
) )
|
||||||
response.sendStatus( 200 );
|
response.sendStatus( 200 );
|
||||||
else
|
else
|
||||||
@@ -70,8 +70,9 @@ const routes = ( app: express.Application, foss: boolean, config: Config ) => {
|
|||||||
sdk.loginCheck(),
|
sdk.loginCheck(),
|
||||||
bodyParser.json(),
|
bodyParser.json(),
|
||||||
( request: express.Request, response: express.Response ) => {
|
( request: express.Request, response: express.Response ) => {
|
||||||
if ( typeof request.params.id !== 'string' )
|
if ( typeof request.params.id !== 'string' ) {
|
||||||
return response.sendStatus( 400 );
|
return response.sendStatus( 400 );
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ( rooms.updateState(
|
if ( rooms.updateState(
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
computed,
|
||||||
|
ref
|
||||||
|
} from 'vue';
|
||||||
import ProgressBar from './ProgressBar.vue';
|
import ProgressBar from './ProgressBar.vue';
|
||||||
|
import ShareManagement from './ShareManagement.vue';
|
||||||
import {
|
import {
|
||||||
beautifyTime
|
beautifyTime
|
||||||
} from '@/ts/util/time';
|
} from '@/ts/util/time';
|
||||||
import {
|
|
||||||
computed
|
|
||||||
} from 'vue';
|
|
||||||
import player from '@/ts/player';
|
import player from '@/ts/player';
|
||||||
|
|
||||||
const playbackPercentage = player.playbackPercentage;
|
const playbackPercentage = player.playbackPercentage;
|
||||||
const repeatMode = player.repeat;
|
const repeatMode = player.repeat;
|
||||||
const shuffleMode = player.shuffle;
|
const shuffleMode = player.shuffle;
|
||||||
|
const showShareMenu = ref( false );
|
||||||
|
|
||||||
const seek = () => {
|
const seek = () => {
|
||||||
player.seekTo( playbackPercentage.value );
|
player.seekTo( playbackPercentage.value );
|
||||||
@@ -30,7 +33,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openShareMenu = () => {
|
const openShareMenu = () => {
|
||||||
alert( 'Share menu not yet implemented' );
|
showShareMenu.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const current = computed( () => {
|
const current = computed( () => {
|
||||||
@@ -39,13 +42,11 @@
|
|||||||
const duration = computed( () => {
|
const duration = computed( () => {
|
||||||
return beautifyTime( player.duration.value );
|
return beautifyTime( player.duration.value );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
||||||
// TODO: Button availability
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mp-player">
|
<div class="mp-player">
|
||||||
|
<ShareManagement v-model="showShareMenu" />
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<i class="fa-solid fa-backward-step" @click="player.prev"></i>
|
<i class="fa-solid fa-backward-step" @click="player.prev"></i>
|
||||||
<i class="fa-solid fa-arrow-rotate-left quick-seek" @click="player.back10"></i>
|
<i class="fa-solid fa-arrow-rotate-left quick-seek" @click="player.back10"></i>
|
||||||
|
|||||||
@@ -63,11 +63,6 @@
|
|||||||
<i class="fa-solid fa-save"></i>
|
<i class="fa-solid fa-save"></i>
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
<!-- TODO: Should only appear when sharing -->
|
|
||||||
<button>
|
|
||||||
<i class="fa-solid fa-paper-plane"></i>
|
|
||||||
Transmit
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<SongEditor v-model="showEditSong" editing-song="" />
|
<SongEditor v-model="showEditSong" editing-song="" />
|
||||||
<AddSong v-model="showAddSong" />
|
<AddSong v-model="showAddSong" />
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import messages, {
|
||||||
|
isConnected
|
||||||
|
} from '@/ts/messages';
|
||||||
|
import PopupElement from '../popups/PopupElement.vue';
|
||||||
|
import {
|
||||||
|
ref
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
|
const showPopup = defineModel<boolean>( {
|
||||||
|
'required': true
|
||||||
|
} );
|
||||||
|
const shareName = ref( '' );
|
||||||
|
const useAntiTamper = ref( false );
|
||||||
|
const errorMessage = ref( '' );
|
||||||
|
|
||||||
|
const startShare = async () => {
|
||||||
|
if ( !await messages.createRoom( shareName.value, useAntiTamper.value ) ) {
|
||||||
|
errorMessage.value = 'Invalid room name';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopShare = () => {
|
||||||
|
messages.closeRoom();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<PopupElement v-model="showPopup" show-close>
|
||||||
|
<h2>Share</h2>
|
||||||
|
<div v-if="!isConnected">
|
||||||
|
<p>
|
||||||
|
You can use a share to show what you are currently listening to (and the progress) on a page.
|
||||||
|
</p>
|
||||||
|
<p>{{ errorMessage }}</p>
|
||||||
|
<input v-model="shareName" type="text">
|
||||||
|
<button @click="startShare">
|
||||||
|
Create Share
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<!-- TODO: Need to explain and add controls -->
|
||||||
|
<!-- TODO: How to handle anti-tamper? -->
|
||||||
|
<p>Connected</p>
|
||||||
|
<button @click="stopShare">
|
||||||
|
End share
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</PopupElement>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
computed,
|
||||||
|
ref
|
||||||
|
} from 'vue';
|
||||||
|
import type {
|
||||||
|
Song
|
||||||
|
} from '@/ts/dtype/playlist';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
'songs': Song[],
|
||||||
|
'idx': number
|
||||||
|
}>();
|
||||||
|
const songs = computed( () => props.songs?.slice( props.idx + 1 ) ?? [] );
|
||||||
|
// TODO: Move this out of this file
|
||||||
|
const showArtworks = ref( false );
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="queue-container">
|
||||||
|
<div v-for="(song, index) in songs" :key="index" class="song-list-element">
|
||||||
|
<div class="song-cover-wrapper">
|
||||||
|
<img
|
||||||
|
v-if="song.artwork && showArtworks"
|
||||||
|
:src="song.artwork"
|
||||||
|
alt="Song cover"
|
||||||
|
class="song-cover"
|
||||||
|
>
|
||||||
|
<i v-else class="fa-solid fa-music song-cover"></i>
|
||||||
|
</div>
|
||||||
|
<div class="song-details">
|
||||||
|
<h3>{{ song.name }}</h3>
|
||||||
|
<p>{{ song.artist }}</p>
|
||||||
|
<p>{{ song['additional-info'] }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="song-actions">
|
||||||
|
<p>In {{ song.duration }}min</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use '@/scss/components/queue.scss';
|
||||||
|
</style>
|
||||||
@@ -27,6 +27,15 @@ const router = createRouter( {
|
|||||||
'auth': true
|
'auth': true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'path': '/share/:name',
|
||||||
|
'name': 'share',
|
||||||
|
'component': () => import( '@/views/SharedView.vue' ),
|
||||||
|
'meta': {
|
||||||
|
'title': 'Shared',
|
||||||
|
'auth': false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'path': '/:pathMatch(.*)*',
|
'path': '/:pathMatch(.*)*',
|
||||||
'name': 'NotFound',
|
'name': 'NotFound',
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import {
|
||||||
|
duration,
|
||||||
|
playbackPercentage
|
||||||
|
} from '../player/status-tracking';
|
||||||
|
import {
|
||||||
|
isPlaying,
|
||||||
|
queue,
|
||||||
|
queueIdx
|
||||||
|
} from '../player/state';
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
watch
|
||||||
|
} from 'vue';
|
||||||
|
import request from '../request';
|
||||||
|
|
||||||
|
const RETRY_CAP = 10;
|
||||||
|
|
||||||
|
export const room = ref( localStorage.getItem( 'room' ) ?? '' );
|
||||||
|
|
||||||
|
export const isConnected = ref( false );
|
||||||
|
|
||||||
|
export const useAntiTamper = ref( false );
|
||||||
|
|
||||||
|
// const antiTamperClients = [];
|
||||||
|
|
||||||
|
let connection: null | EventSource = null;
|
||||||
|
let retries = 0;
|
||||||
|
|
||||||
|
// TODO: Persist room name in local storage
|
||||||
|
|
||||||
|
const createRoom = async ( name: string, antiTamper: boolean ): Promise<boolean> => {
|
||||||
|
if ( !( /^[a-zA-Z0-9-]{3,20}$/ ).test( name ) ) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await request.get( '/room/create?room=' + name );
|
||||||
|
} catch ( err ) {
|
||||||
|
if ( err === 'ERR_409' ) {
|
||||||
|
return await connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem( 'room', name );
|
||||||
|
useAntiTamper.value = antiTamper;
|
||||||
|
room.value = name;
|
||||||
|
|
||||||
|
return await connect();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeRoom = async () => {
|
||||||
|
// TODO: Trigger close on backend and disconnect.
|
||||||
|
isConnected.value = false;
|
||||||
|
localStorage.removeItem( 'room' );
|
||||||
|
};
|
||||||
|
|
||||||
|
const connect = (): Promise<boolean> => {
|
||||||
|
return new Promise( ( resolve, reject ) => {
|
||||||
|
if ( !useAntiTamper.value ) {
|
||||||
|
isConnected.value = true;
|
||||||
|
|
||||||
|
return resolve( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
connection = new EventSource( request.backendURL + `/room/${ room.value }/admin`, {
|
||||||
|
'withCredentials': true
|
||||||
|
} );
|
||||||
|
|
||||||
|
connection.onopen = () => {
|
||||||
|
isConnected.value = true;
|
||||||
|
console.log( '[SSE] Connection established successfully' );
|
||||||
|
resolve( true );
|
||||||
|
};
|
||||||
|
|
||||||
|
connection.onmessage = msg => {
|
||||||
|
console.log( msg );
|
||||||
|
};
|
||||||
|
|
||||||
|
connection.onerror = () => {
|
||||||
|
connection?.close();
|
||||||
|
console.error( '[SSE] Reconnecting due to error' );
|
||||||
|
|
||||||
|
if ( !isConnected.value ) reject( 'ERR_CONNECT' );
|
||||||
|
|
||||||
|
if ( retries <= RETRY_CAP ) {
|
||||||
|
retries += 1;
|
||||||
|
|
||||||
|
setTimeout( () => {
|
||||||
|
createRoom( room.value, useAntiTamper.value );
|
||||||
|
}, 1000 * retries );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
const useRoomWatchers = () => {
|
||||||
|
watch( queue, () => {
|
||||||
|
if ( isConnected.value )
|
||||||
|
request.post( `/room/${ room.value }/update/playlist`, JSON.stringify( {
|
||||||
|
'playlist': queue.value
|
||||||
|
} ) );
|
||||||
|
} );
|
||||||
|
|
||||||
|
watch( [
|
||||||
|
isPlaying,
|
||||||
|
queueIdx
|
||||||
|
], () => {
|
||||||
|
if ( isConnected.value )
|
||||||
|
request.post( `/room/${ room.value }/update/state`, JSON.stringify( {
|
||||||
|
'playing': isPlaying.value,
|
||||||
|
'index': queueIdx.value,
|
||||||
|
'start': new Date().getTime() - ( playbackPercentage.value * duration.value ) - 100
|
||||||
|
} ) );
|
||||||
|
} );
|
||||||
|
|
||||||
|
if ( room.value ) connect();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
createRoom,
|
||||||
|
useRoomWatchers,
|
||||||
|
closeRoom
|
||||||
|
};
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import type {
|
|||||||
import {
|
import {
|
||||||
load
|
load
|
||||||
} from './playlists/loader';
|
} from './playlists/loader';
|
||||||
|
import messages from '../messages';
|
||||||
import {
|
import {
|
||||||
playIndex
|
playIndex
|
||||||
} from './playlists';
|
} from './playlists';
|
||||||
@@ -148,6 +149,7 @@ const addSongFromSource = async ( source: string ): Promise<boolean> => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
messages.useRoomWatchers();
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
play,
|
play,
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const get = async ( url: string ): Promise<Response> => {
|
|||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const backendURL = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
|
||||||
const post = async ( url: string, payload: string, mime: string = 'application/json' ): Promise<Response> => {
|
const post = async ( url: string, payload: string, mime: string = 'application/json' ): Promise<Response> => {
|
||||||
return await wrapper( url, {
|
return await wrapper( url, {
|
||||||
'credentials': 'include',
|
'credentials': 'include',
|
||||||
@@ -21,11 +23,12 @@ const post = async ( url: string, payload: string, mime: string = 'application/j
|
|||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = async ( url: string, opts: RequestInit ): Promise<Response> => {
|
const wrapper = async ( url: string, opts: RequestInit ): Promise<Response> => {
|
||||||
const res = await fetch( import.meta.env.VITE_BACKEND_URL + url, opts );
|
const res = await fetch( backendURL + url, opts );
|
||||||
|
|
||||||
if ( res.ok ) {
|
if ( res.ok ) {
|
||||||
return res;
|
return res;
|
||||||
} else if ( res.status === 403 || res.status === 401 ) {
|
} else if ( res.status === 403 || res.status === 401 ) {
|
||||||
|
// TODO: Handle these errors better (probably do something like in the old version, but better)
|
||||||
throw new AuthError( 'ERR_USER_UNAUTHORIZED' );
|
throw new AuthError( 'ERR_USER_UNAUTHORIZED' );
|
||||||
} else if ( res.status === 402 ) {
|
} else if ( res.status === 402 ) {
|
||||||
throw new UnownedError( 'ERR_USER_UNOWNED' );
|
throw new UnownedError( 'ERR_USER_UNOWNED' );
|
||||||
@@ -36,5 +39,6 @@ const wrapper = async ( url: string, opts: RequestInit ): Promise<Response> => {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
get,
|
get,
|
||||||
post
|
post,
|
||||||
|
backendURL
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import request from '../request';
|
||||||
|
|
||||||
|
const RETRY_CAP = 10;
|
||||||
|
|
||||||
|
let room = location.pathname;
|
||||||
|
let connection: EventSource | null = null;
|
||||||
|
let hasConnected = false;
|
||||||
|
let retries = 0;
|
||||||
|
|
||||||
|
// TODO: Persist settings in local storage
|
||||||
|
|
||||||
|
const connect = (): Promise<void> => {
|
||||||
|
return new Promise( ( resolve, reject ) => {
|
||||||
|
room = location.pathname.substring( location.pathname.lastIndexOf( '/' ) + 1 );
|
||||||
|
connection = new EventSource( request.backendURL + `/room/${ room }/connect` );
|
||||||
|
|
||||||
|
connection.onopen = () => {
|
||||||
|
hasConnected = true;
|
||||||
|
console.log( '[SSE] Connection established successfully' );
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
connection.onmessage = msg => {
|
||||||
|
console.log( msg.data );
|
||||||
|
// TODO: On connect, retrieve data from poll endpoint
|
||||||
|
};
|
||||||
|
|
||||||
|
connection.onerror = () => {
|
||||||
|
connection?.close();
|
||||||
|
|
||||||
|
if ( !hasConnected ) return reject( 'ERR_CONNECT' );
|
||||||
|
|
||||||
|
console.error( '[SSE] Connection failed, reconnecting' );
|
||||||
|
|
||||||
|
if ( retries <= RETRY_CAP ) {
|
||||||
|
retries += 1;
|
||||||
|
setTimeout( () => {
|
||||||
|
connect();
|
||||||
|
}, 1000 * retries );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
connect
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CurrentSong from '@/components/player/CurrentSong.vue';
|
||||||
|
import SharedQueue from '@/components/shared/SharedQueue.vue';
|
||||||
|
import shared from '@/ts/shared';
|
||||||
|
|
||||||
|
shared.connect();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="shared-view">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<CurrentSong />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<SharedQueue :songs="[]" :idx="-1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user