Skip to content

Commit

Permalink
Merge pull request #12584 from nathanaelg16/use_connection_composable
Browse files Browse the repository at this point in the history
Creating composable for connection monitoring and replacing existing logic with it
  • Loading branch information
rtibbles committed Aug 22, 2024
2 parents 30f3603 + 239b41f commit 6e61470
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 31 deletions.
13 changes: 13 additions & 0 deletions kolibri/core/assets/src/composables/useConnection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ref } from 'kolibri.lib.vueCompositionApi';

const connected = ref(true);
const reconnectTime = ref(null);
const reloadOnReconnect = ref(false);

export default function useConnection() {
return {
connected,
reconnectTime,
reloadOnReconnect,
};
}
6 changes: 5 additions & 1 deletion kolibri/core/assets/src/core-app/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ import qs from 'qs';
import heartbeat from 'kolibri.heartbeat';
import logger from 'kolibri.lib.logging';
import store from 'kolibri.coreVue.vuex.store';
import { get } from '@vueuse/core';
import errorCodes from '../disconnectionErrorCodes';
import useConnection from '../composables/useConnection';
import clientFactory from './baseClient';

export const logging = logger.getLogger(__filename);

const connection = useConnection();

const baseClient = clientFactory();

// Disconnection handler interceptor
baseClient.interceptors.request.use(function (config) {
if (!store.getters.connected) {
if (!get(connection.connected)) {
// If the vuex state records that we are not currently connected then cancel all
// outgoing requests.
const source = CancelToken.source();
Expand Down
5 changes: 4 additions & 1 deletion kolibri/core/assets/src/disconnection.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { createTranslator } from 'kolibri.utils.i18n';
import { get } from '@vueuse/core';
import useConnection from './composables/useConnection';

export const trs = createTranslator('DisconnectionSnackbars', {
disconnected: {
Expand Down Expand Up @@ -37,7 +39,8 @@ export function createDisconnectedSnackbar(store, beatCallback) {
// clear timers
clearTimer();
// set proper time
setDynamicReconnectTime(store.state.core.connection.reconnectTime);
const { reconnectTime } = useConnection();
setDynamicReconnectTime(get(reconnectTime));
// create snackbar
store.commit('CORE_CREATE_SNACKBAR', {
text: generateDisconnectedSnackbarText(),
Expand Down
39 changes: 20 additions & 19 deletions kolibri/core/assets/src/heartbeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import store from 'kolibri.coreVue.vuex.store';
import redirectBrowser from 'kolibri.utils.redirectBrowser';
import Lockr from 'lockr';
import urls from 'kolibri.urls';
import { get, set } from '@vueuse/core';
import useConnection from './composables/useConnection';
import clientFactory from './core-app/baseClient';
import { SIGNED_OUT_DUE_TO_INACTIVITY } from './constants';
import errorCodes from './disconnectionErrorCodes';
Expand Down Expand Up @@ -44,6 +46,7 @@ const ACTIVE_DELAY = 240;

export class HeartBeat {
constructor() {
this._connection = useConnection();
// Do this to have a consistent callback that has 'this' properly bound
// but can be repeatedly referenced to add and remove listeners.
this.setUserActive = this.setUserActive.bind(this);
Expand All @@ -58,16 +61,15 @@ export class HeartBeat {
function (response) {
// If the response does not have one of the disconnect error codes
// then we have reconnected.
if (!store.getters.connected && !errorCodes.includes(response.status)) {
if (!get(heartbeat._connection.connected) && !errorCodes.includes(response.status)) {
// Not one of our 'disconnected' status codes, so we are connected again
// Set connected and return the response here to prevent any further processing.
heartbeat._setConnected();
}
return response;
},
function (error) {
const { connected, reconnectTime } = store.getters;
if (!connected) {
if (!get(heartbeat._connection.connected)) {
// If the response does not have one of the disconnect error codes
// then we have reconnected.
if (!errorCodes.includes(error.response.status)) {
Expand All @@ -79,11 +81,11 @@ export class HeartBeat {
// If we have got here, then the error code meant that the server is still not reachable
// set the snackbar to disconnected.
// See what the previous reconnect interval was.
const reconnect = reconnectTime;
const reconnect = get(heartbeat._connection.reconnectTime);
// Set a new reconnect interval.
store.commit(
'CORE_SET_RECONNECT_TIME',
// Multiply the previous interval by our multiplier, but max out at a high interval.
// Multiply the previous interval by our multiplier, but max out at a high interval.
set(
heartbeat._connection.reconnectTime,
Math.min(RECONNECT_MULTIPLIER * reconnect, MAX_RECONNECT_TIME),
);
createDisconnectedSnackbar(store, heartbeat.pollSessionEndPoint);
Expand Down Expand Up @@ -133,12 +135,11 @@ export class HeartBeat {
this._active = false;
}
get _delay() {
const { reconnectTime } = store.getters;
if (!store.getters.connected && reconnectTime) {
if (!get(this._connection.connected) && get(this._connection.reconnectTime)) {
// If we are currently engaged in exponential backoff in trying to reconnect to the server
// use the current reconnect time preferentially instead of the standard delay.
// The reconnect time is stored in seconds, so multiply by 1000 to give the milliseconds.
return reconnectTime;
return get(this._connection.reconnectTime);
}
// If page is not visible, don't poll as frequently, as user activity is unlikely.
return store.state.pageVisible ? ACTIVE_DELAY : ACTIVE_DELAY * 2;
Expand All @@ -157,9 +158,9 @@ export class HeartBeat {
* @return {Promise} promise that resolves when the endpoint check is complete.
*/
_checkSession() {
const { currentUserId, connected } = store.getters;
const { currentUserId } = store.getters;
// Record the current user id to check if a different one is returned by the server.
if (!connected) {
if (!get(this._connection.connected)) {
// If not currently connected to the server, flag that we are currently trying to reconnect.
createTryingToReconnectSnackbar(store);
}
Expand All @@ -170,7 +171,7 @@ export class HeartBeat {
data: {
// Only send active when both connected and activity has been registered.
// Do this to prevent a user logging activity cascade on the server side.
active: connected && this._active,
active: get(this._connection.connected) && this._active,
browser,
os,
},
Expand Down Expand Up @@ -222,9 +223,9 @@ export class HeartBeat {
* if the vuex state does not already indicate disconnection.
*/
monitorDisconnect(code = 0) {
if (store.getters.connected) {
if (get(this._connection.connected)) {
// We have not already registered that we have been disconnected
store.commit('CORE_SET_CONNECTED', false);
set(this._connection.connected, false);
let reconnectionTime;
if (store.state.pageVisible) {
// If current page is not visible, back off completely
Expand All @@ -238,7 +239,7 @@ export class HeartBeat {
// back off quickly before trying to reconnect.
reconnectionTime = TIMEOUT_RECONNECT_TIME;
}
store.commit('CORE_SET_RECONNECT_TIME', reconnectionTime);
set(this._connection.reconnectTime, reconnectionTime);
createDisconnectedSnackbar(store, this.pollSessionEndPoint);
this._wait();
}
Expand All @@ -248,10 +249,10 @@ export class HeartBeat {
* on the regular heartbeat delay.
*/
_setConnected() {
store.commit('CORE_SET_CONNECTED', true);
store.commit('CORE_SET_RECONNECT_TIME', null);
set(this._connection.connected, true);
set(this._connection.reconnectTime, null);
createReconnectedSnackbar(store);
if (store.state.core.connection.reloadOnReconnect) {
if (get(this._connection.reloadOnReconnect)) {
// If we were disconnected while loading, we need to reload the page
// to ensure that we are in a consistent state.
window.location.reload();
Expand Down
4 changes: 3 additions & 1 deletion kolibri/core/assets/src/state/modules/core/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import redirectBrowser from 'kolibri.utils.redirectBrowser';
import CatchErrors from 'kolibri.utils.CatchErrors';
import Vue from 'kolibri.lib.vue';
import Lockr from 'lockr';
import { set } from '@vueuse/core';
import { baseSessionState } from '../session';
import { LoginErrors, ERROR_CONSTANTS, UPDATE_MODAL_DISMISSED } from '../../../constants';
import { browser, os } from '../../../utils/browserInfo';
import useConnection from '../../../composables/useConnection';
import errorCodes from './../../../disconnectionErrorCodes.js';

const logging = logger.getLogger(__filename);
Expand Down Expand Up @@ -79,7 +81,7 @@ export function handleApiError(store, { error, reloadOnReconnect = false } = {})
if (errorCodes.includes(error.response.status)) {
// Do not log errors for disconnections, as it disrupts the user experience
// and should already be being handled by our disconnection overlay.
store.commit('CORE_SET_RELOAD_ON_RECONNECT', reloadOnReconnect);
set(useConnection().reloadOnReconnect, reloadOnReconnect);
return;
}
// Reassign object properties here as Axios error objects have built in
Expand Down
19 changes: 10 additions & 9 deletions kolibri/core/assets/test/heartbeat.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import mock from 'xhr-mock';
import coreStore from 'kolibri.coreVue.vuex.store';
import redirectBrowser from 'kolibri.utils.redirectBrowser';
import * as serverClock from 'kolibri.utils.serverClock';
import { get, set } from '@vueuse/core';
import { HeartBeat } from '../src/heartbeat.js';
import disconnectionErrorCodes from '../src/disconnectionErrorCodes';
import { trs } from '../src/disconnection';
Expand Down Expand Up @@ -167,10 +168,10 @@ describe('HeartBeat', function () {
heartBeat.monitorDisconnect();
});
it('should set connected to false', function () {
expect(coreStore.getters.connected).toEqual(false);
expect(get(heartBeat._connection.connected)).toEqual(false);
});
it('should set reconnectTime to not null', function () {
expect(coreStore.getters.reconnectTime).not.toEqual(null);
expect(get(heartBeat._connection.reconnectTime)).not.toEqual(null);
});
it('should set current snackbar to disconnected', function () {
expect(coreStore.getters.snackbarIsVisible).toEqual(true);
Expand All @@ -181,9 +182,9 @@ describe('HeartBeat', function () {
).toEqual(true);
});
it('should not do anything if it already knows it is disconnected', function () {
coreStore.commit('CORE_SET_RECONNECT_TIME', 'fork');
set(heartBeat._connection.reconnectTime, 'fork');
heartBeat.monitorDisconnect();
expect(coreStore.getters.reconnectTime).toEqual('fork');
expect(get(heartBeat._connection.reconnectTime)).toEqual('fork');
});
});
describe('_checkSession method', function () {
Expand Down Expand Up @@ -308,11 +309,11 @@ describe('HeartBeat', function () {
});
it('should increase the reconnect time when it fails to connect', function () {
mock.put(/.*/, () => Promise.reject(new Error()));
coreStore.commit('CORE_SET_RECONNECT_TIME', 5);
set(heartBeat._connection.reconnectTime, 5);
return heartBeat._checkSession().finally(() => {
const oldReconnectTime = coreStore.getters.reconnectTime;
const oldReconnectTime = get(heartBeat._connection.reconnectTime);
return heartBeat._checkSession().finally(() => {
expect(coreStore.getters.reconnectTime).toBeGreaterThan(oldReconnectTime);
expect(get(heartBeat._connection.reconnectTime)).toBeGreaterThan(oldReconnectTime);
});
});
});
Expand All @@ -333,12 +334,12 @@ describe('HeartBeat', function () {
});
it('should set connected to true', function () {
return heartBeat._checkSession().finally(() => {
expect(coreStore.getters.connected).toEqual(true);
expect(get(heartBeat._connection.connected)).toEqual(true);
});
});
it('should set reconnect time to null', function () {
return heartBeat._checkSession().finally(() => {
expect(coreStore.getters.reconnectTime).toEqual(null);
expect(get(heartBeat._connection.reconnectTime)).toEqual(null);
});
});
});
Expand Down

0 comments on commit 6e61470

Please sign in to comment.