Skip to content

Commit

Permalink
Added websocket indicator.
Browse files Browse the repository at this point in the history
  • Loading branch information
Severino committed Jan 30, 2025
1 parent 63ab353 commit 3997053
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 17 deletions.
9 changes: 3 additions & 6 deletions resources/js/bootstrap/websocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import Echo from 'laravel-echo';

import Pusher from 'pusher-js';

// Why do we handle this as window objects and not with
// import/export syntax and pollute the window object? [SO]
// This is how it's done in the Laravel Echo documentation
// and all other resources I could find.
window.Pusher = Pusher;

window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
Expand All @@ -14,6 +13,4 @@ window.Echo = new Echo({
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});

// export default Echo;
});
6 changes: 6 additions & 0 deletions resources/js/components/MainView.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<template>
<div class="row h-100 overflow-hidden">
<!-- Row scales all children to 100% that is not what we want here. -->
<div>
<WebSocketConnection />
</div>
<div
v-if="state.columnPref.left > 0"
id="tree-container"
Expand Down Expand Up @@ -170,10 +174,12 @@
import { useToast } from '@/plugins/toast.js';
import Quotation from '@/components/bibliography/Quotation.vue';
import WebSocketConnection from './indicators/WebSocketConnection.vue';
export default {
components: {
Quotation,
WebSocketConnection,
},
setup(props, context) {
const { t } = useI18n();
Expand Down
4 changes: 2 additions & 2 deletions resources/js/components/indicators/DotIndicator.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div
:class="state.merged"
style="width: 0.6rem;"
style="width: 0.6rem; height: 0.6rem;"
/>
</template>

Expand Down Expand Up @@ -91,5 +91,5 @@
state,
};
},
}
};
</script>
94 changes: 94 additions & 0 deletions resources/js/components/indicators/WebSocketConnection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<template>
<div
v-if="visible"
class="web-socket-connection d-flex align-items-center gap-2 position-absolute start-50 translate-middle mt-2 z-2 bg-white order p-2 border border-1 border-secondary rounded"
:title="status"
>
<DotIndicator :type="indicatorStatus" />
{{ message }}
</div>
</template>

<script>
import { computed, onMounted, onBeforeUnmount, ref } from 'vue';
import DotIndicator from '@/components/indicators/DotIndicator.vue';
import { getState, getConnection } from '@/helpers/websocket';
import { useI18n } from 'vue-i18n';
export default {
components: {
DotIndicator
},
setup() {
const t = useI18n().t;
const status = ref(getState());
const message = computed(() => {
if(indicatorStatus.value === 'success') {
return t('websockets.service_available_again');
} else {
return t('websockets.service_unavailable');
}
});
const indicatorStatus = computed(() => {
switch(status.value) {
case 'connected':
return 'success';
case 'connecting':
case 'initialized':
case 'unavailable':
case 'disconnected':
case 'failed':
default:
return 'danger';
}
});
const visible = ref(indicatorStatus.value === 'danger');
console.log('visible', visible.value);
let timeout = null;
const updateState = _ => {
status.value = getState();
console.log(status.value);
clearTimeout(timeout);
if(status.value === 'connected') {
timeout = setTimeout(() => {
visible.value = false;
}, 3000);
} else {
visible.value = true;
}
};
onMounted(() => {
console.log('WebSocketConnection mounted');
const connection = getConnection();
if(!connection) {
console.error('Could not get connection object');
} else {
console.log(connection, connection.bind);
connection.bind('state_change', function (states) {
updateState();
});
}
});
onBeforeUnmount(() => {
const connection = getConnection();
if(!connection) {
console.error('Could not get connection object');
} else {
connection.unbind('state_change ', updateState);
}
});
return {
status,
indicatorStatus,
message,
visible,
};
}
};
</script>
35 changes: 26 additions & 9 deletions resources/js/helpers/websocket.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// import Echo from '@/bootstrap/websocket.js';


import useEntityStore from '@/bootstrap/stores/entity.js';
import useUserStore from '@/bootstrap/stores/user.js';
Expand All @@ -13,24 +13,24 @@ function listen(channelname, event, callback) {
}

export function subscribeToTestWebSockets() {
Echo.private('private_testchannel')
window.Echo.private('private_testchannel')
.listen('TestEvent', e => {
console.log('Private WebSockets are working! Received message:', e);
});
Echo.channel('testchannel')
window.Echo.channel('testchannel')
.listen('TestEvent', e => {
console.log('Public WebSockets are working! Received message:', e);
});
}

export function unsubscribeFromTestWebSockets() {
Echo.leave('private_testchannel');
Echo.leave('testchannel');
window.Echo.leave('private_testchannel');
window.Echo.leave('testchannel');
}

// Subscribe to public/private channel and optionally listen to an event
export function subscribeTo(channelname, isPrivate = false, listenTo = null, callback = null) {
const channel = isPrivate ? Echo.private(channelname) : Echo.channel(channelname);
const channel = isPrivate ? window.Echo.private(channelname) : window.Echo.channel(channelname);
if(!activeChannels[channelname]) {
activeChannels[channelname] = channel;
}
Expand All @@ -40,7 +40,7 @@ export function subscribeTo(channelname, isPrivate = false, listenTo = null, cal
}

export function unsubscribeFrom(channelname) {
Echo.leave(channelname);
window.Echo.leave(channelname);
}

// Listen for a specific event of an already subscribed channel
Expand All @@ -66,7 +66,7 @@ export function listenToList(channelname, eventHandlerList) {

// Join a presence channel
export function join(roomname, callbacks) {
const room = Echo.join(roomname)
const room = window.Echo.join(roomname)
.here(callbacks.init)
.joining(callbacks.join)
.leaving(callbacks.leave)
Expand Down Expand Up @@ -112,12 +112,29 @@ export function joinEntityRoom(entityId) {
join: user => entityStore.addActiveUserId(user),
leave: user => entityStore.removeActiveUserId(user.id),
error: error => {
console.error("[WS] Error occured!", error);
console.error('[WS] Error occured!', error);
},
});
return roomname;
}

export function leaveEntityRoom(roomname) {
unsubscribeFrom(roomname);
}


/**
* Gets the active pusher connection.
* @returns {connection | null} - The pusher connection object or null if not available
*/
export function getConnection(){
return window.Echo?.connector?.pusher?.connection ?? null;
}

/**
*
* @returns {string} - The current state of the websocket connection can be one of the following: 'initialized', 'connecting', 'connected', 'unavailable', 'disconnected', 'failed'
*/
export function getState() {
return getConnection().state ?? 'unavailable';
}
4 changes: 4 additions & 0 deletions resources/js/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1044,5 +1044,9 @@
"keyboard": {
"tab": "Tab",
"space": "Leertaste"
},
"websockets": {
"service_available_again": "Echtzeitupdates wieder verfügbar!",
"service_unavailable": "Echtzeitupdates nicht verfügbar!"
}
}
4 changes: 4 additions & 0 deletions resources/js/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1045,5 +1045,9 @@
"keyboard": {
"tab": "Tab",
"space": "Spacebar"
},
"websockets": {
"service_available_again": "Live updates available again!",
"service_unavailable": "Live updates unavailable!"
}
}

0 comments on commit 3997053

Please sign in to comment.