sse: properly handle sse opening/closing

This commit is contained in:
axolotle 2023-06-13 14:20:55 +02:00
parent 0b1030a2fb
commit 2d78749c1d
4 changed files with 39 additions and 41 deletions

View file

@ -81,7 +81,6 @@
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { connectSSE } from '@/api/handlers'
import { HistoryConsole, ViewLockOverlay } from '@/views/_partials' import { HistoryConsole, ViewLockOverlay } from '@/views/_partials'
export default { export default {
@ -118,7 +117,7 @@ export default {
// state will be automaticly reseted and user will be prompt with the login view. // state will be automaticly reseted and user will be prompt with the login view.
if (this.connected) { if (this.connected) {
this.$store.dispatch('GET_YUNOHOST_INFOS') this.$store.dispatch('GET_YUNOHOST_INFOS')
connectSSE() this.$store.dispatch('SSE_CONNECT')
} }
}, },

View file

@ -4,7 +4,7 @@
*/ */
import store from '@/store' import store from '@/store'
import { openWebSocket, getResponseData, handleError } from './handlers' import { getResponseData, handleError } from './handlers'
/** /**

View file

@ -24,40 +24,6 @@ export async function getResponseData (response) {
} }
/**
* Opens a WebSocket connection to the server in case it sends messages.
* Currently, the connection is closed by the server right after an API call so
* we have to open it for every calls.
* Messages are dispatch to the store so it can handle them.
*
* @param {Object} request - Request info data.
* @return {Promise<Event>} Promise that resolve on websocket 'open' or 'error' event.
*/
export function openWebSocket (request) {
return new Promise(resolve => {
const ws = new WebSocket(`wss://${store.getters.host}/yunohost/api/messages`)
ws.onmessage = ({ data }) => {
store.dispatch('DISPATCH_MESSAGE', { request, messages: JSON.parse(data) })
}
// ws.onclose = (e) => {}
ws.onopen = resolve
// Resolve also on error so the actual fetch may be called.
ws.onerror = resolve
})
}
export function connectSSE () {
const host = store.getters.host.split(':')[0]
const evtSource = new EventSource(`https://${host}/yunohost/api/sse`)
evtSource.onmessage = (event) => {
store.dispatch('ON_SSE_MESSAGE', JSON.parse(atob(event.data)))
}
// FIXME handle 'onerror' hook
}
/** /**
* Handler for API errors. * Handler for API errors.
* *

View file

@ -2,13 +2,13 @@ import Vue from 'vue'
import router from '@/router' import router from '@/router'
import i18n from '@/i18n' import i18n from '@/i18n'
import api from '@/api' import api from '@/api'
import { connectSSE } from '@/api/handlers'
import { timeout, isEmptyValue, isObjectLiteral } from '@/helpers/commons' import { timeout, isEmptyValue, isObjectLiteral } from '@/helpers/commons'
export default { export default {
state: { state: {
host: window.location.host, // String host: window.location.host, // String
connected: localStorage.getItem('connected') === 'true', // Boolean connected: localStorage.getItem('connected') === 'true', // Boolean
sse: null, // EventSource
yunohost: null, // Object { version, repo } yunohost: null, // Object { version, repo }
waiting: false, // Boolean waiting: false, // Boolean
reconnecting: null, // null|Object { attemps, delay, initialDelay } reconnecting: null, // null|Object { attemps, delay, initialDelay }
@ -28,6 +28,17 @@ export default {
state.connected = boolean state.connected = boolean
}, },
'SET_SSE_SOURCE' (state, sse) {
state.sse = sse
},
'CLOSE_SSE_SOURCE' (state) {
if (state.sse) {
state.sse.close()
state.sse = null
}
},
'SET_YUNOHOST_INFOS' (state, yunohost) { 'SET_YUNOHOST_INFOS' (state, yunohost) {
state.yunohost = yunohost state.yunohost = yunohost
}, },
@ -123,13 +134,30 @@ export default {
'CONNECT' ({ commit, dispatch }) { 'CONNECT' ({ commit, dispatch }) {
commit('SET_CONNECTED', true) commit('SET_CONNECTED', true)
connectSSE()
dispatch('GET_YUNOHOST_INFOS') dispatch('GET_YUNOHOST_INFOS')
dispatch('SSE_CONNECT')
},
'SSE_CONNECT' ({ commit, dispatch }) {
const sse = new EventSource(`/yunohost/api/sse`, { withCredentials: true })
sse.onopen = () => {
commit('SET_SSE_SOURCE', sse)
console.log('connected')
};
sse.onmessage = (event) => {
dispatch('ON_SSE_MESSAGE', JSON.parse(atob(event.data)))
}
// sse.onerror = (event) => {
// }
}, },
'RESET_CONNECTED' ({ commit }) { 'RESET_CONNECTED' ({ commit }) {
commit('SET_CONNECTED', false) commit('SET_CONNECTED', false)
commit('SET_YUNOHOST_INFOS', null) commit('SET_YUNOHOST_INFOS', null)
commit('CLOSE_SSE_SOURCE')
}, },
'DISCONNECT' ({ dispatch }, route = router.currentRoute) { 'DISCONNECT' ({ dispatch }, route = router.currentRoute) {
@ -249,7 +277,13 @@ export default {
}, },
async 'ON_SSE_MESSAGE' ({ state, commit, dispatch }, data) { async 'ON_SSE_MESSAGE' ({ state, commit, dispatch }, data) {
let action = state.history.findLast((action) => action.operationId === data.operation_id) let action
if (data.type === 'start') {
action = state.requests.findLast((request) => request.status === 'pending')
} else {
action = state.history.findLast((action) => action.operationId === data.operation_id)
}
if (!action) { if (!action) {
action = await dispatch('START_EXTERNAL_ACTION', { operationId: data.operation_id, timestamp: data.timestamp }) action = await dispatch('START_EXTERNAL_ACTION', { operationId: data.operation_id, timestamp: data.timestamp })
} }
@ -288,7 +322,6 @@ export default {
} }
commit('ADD_TEMP_MESSAGE', { request: action, message, type }) commit('ADD_TEMP_MESSAGE', { request: action, message, type })
} }
action.messages.push(message)
} }
}, },