web/satellite: rework session timeout reset with ongoing upload

Using eventbus was a terrible approach because if session would expire in less than 3 mins then we would do an explicit API request to reset session for each upload progress ping.
With this change we simply check if there is at least one ongoing upload and refresh session with one single API request and we don't show inactivity modal.

This is a possible fix for this issue (at least I can't reproduce it):
https://github.com/storj/storj/issues/5618

How to test:
setup storj-up with InactivityTimerDuration config value set to 120 seconds.
go to object browser and start upload of some heavy file, for example 1GB.
check if inactivity timer would be shown in 2 mins, additionaly you may check DevTools network tab.
in my case upload is successfully done in about 3-4 mins without any problems.

Change-Id: I09142a7affac08db1d02992ca2d2f40c6267324f
This commit is contained in:
Vitalii 2023-03-15 14:52:20 +02:00 committed by Vitalii Shpital
parent 7942f3774f
commit 132a688bec
5 changed files with 22 additions and 97 deletions

View File

@ -203,7 +203,6 @@ import { BrowserFile } from '@/types/browser';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { RouteConfig } from '@/router';
import { useNotify, useRouter, useStore } from '@/utils/hooks';
import eventBus from '@/utils/eventBus';
import { Bucket } from '@/types/buckets';
import { MODALS } from '@/utils/constants/appStatePopUps';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
@ -439,10 +438,7 @@ function filename(file: BrowserFile): string {
* Upload the current selected or dragged-and-dropped file.
*/
async function upload(e: Event): Promise<void> {
const callback = () => {
eventBus.$emit('upload_progress');
};
await store.dispatch('files/upload', { e, callback });
await store.dispatch('files/upload', { e });
await analytics.eventTriggered(AnalyticsEvent.OBJECT_UPLOADED);
const target = e.target as HTMLInputElement;
target.value = '';

View File

@ -143,7 +143,8 @@ export const makeFilesModule = (): FilesModule => ({
return groupedFiles;
},
isInitialized: (state: FilesState) => state.s3 !== null,
isInitialized: (state: FilesState): boolean => state.s3 !== null,
uploadingLength: (state: FilesState): number => state.uploading.length,
},
mutations: {
init(
@ -431,7 +432,7 @@ export const makeFilesModule = (): FilesModule => ({
commit('setObjectsCount', responseV2.KeyCount === undefined ? 0 : responseV2.KeyCount);
},
async upload({ commit, state, dispatch }, { e, callback }: { e: DragEvent, callback: () => void }) {
async upload({ commit, state, dispatch }, { e }: { e: DragEvent }) {
assertIsInitialized(state);
type Item = DataTransferItem | FileSystemEntry;
@ -525,7 +526,6 @@ export const makeFilesModule = (): FilesModule => ({
);
upload.on('httpUploadProgress', async (progress) => {
await callback();
commit('setProgress', {
Key: params.Key,
progress: Math.round((progress.loaded / progress.total) * 100),

View File

@ -1,8 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
import Vue from 'vue';
const eventBus = new Vue();
export default eventBus;

View File

@ -103,7 +103,7 @@
</template>
<script setup lang="ts">
import { computed, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import { ErrorUnauthorized } from '@/api/errors/ErrorUnauthorized';
import { RouteConfig } from '@/router';
@ -112,7 +112,6 @@ import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { USER_ACTIONS } from '@/store/modules/users';
import { CouponType } from '@/types/coupons';
import { CreditCard } from '@/types/payments';
import { Project } from '@/types/projects';
import { APP_STATE_ACTIONS, NOTIFICATION_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames';
import { AppState } from '@/utils/constants/appStateEnum';
@ -121,7 +120,6 @@ import { User } from '@/types/users';
import { AuthHttpApi } from '@/api/auth';
import { MetaUtils } from '@/utils/meta';
import { AnalyticsHttpApi } from '@/api/analytics';
import eventBus from '@/utils/eventBus';
import { AB_TESTING_ACTIONS } from '@/store/modules/abTesting';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
@ -160,7 +158,6 @@ const auth: AuthHttpApi = new AuthHttpApi();
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const resetActivityEvents: string[] = ['keypress', 'mousemove', 'mousedown', 'touchmove'];
const inactivityModalTime = 60000;
const ACTIVITY_REFRESH_TIME_LIMIT = 180000;
const sessionDuration: number = parseInt(MetaUtils.getMetaContent('inactivity-timer-duration')) * 1000;
const sessionRefreshInterval: number = sessionDuration / 2;
const debugTimerShown = MetaUtils.getMetaContent('inactivity-timer-viewer-enabled') === 'true';
@ -302,13 +299,6 @@ const isProjectListPage = computed((): boolean => {
return router.history.current?.name === RouteConfig.ProjectsList.name;
});
/**
* Returns credit cards from store.
*/
const creditCards = computed((): CreditCard[] => {
return store.state.paymentsModule.creditCards;
});
/**
* Indicates if current route is onboarding tour.
*/
@ -411,7 +401,12 @@ function restartSessionTimers(): void {
}
}, sessionRefreshInterval);
inactivityTimerId.value = setTimeout(() => {
inactivityTimerId.value = setTimeout(async () => {
if (store.getters['files/uploadingLength']) {
await refreshSession();
return;
}
if (isSessionActive.value) return;
inactivityModalShown.value = true;
inactivityTimerId.value = setTimeout(async () => {
@ -458,28 +453,6 @@ function selectProject(fetchedProjects: Project[]): void {
storeProject(fetchedProjects[0].id);
}
/**
* Used to trigger session timer update while doing not UI-related work for a long time.
*/
async function resetSessionOnLogicRelatedActivity(): Promise<void> {
const isInactivityTimerEnabled = MetaUtils.getMetaContent('inactivity-timer-enabled');
if (!isInactivityTimerEnabled) {
return;
}
const expiresAt = LocalData.getSessionExpirationDate();
if (expiresAt) {
const ms = Math.max(0, expiresAt.getTime() - Date.now());
// Isn't triggered when decent amount of session time is left.
if (ms < ACTIVITY_REFRESH_TIME_LIMIT) {
await refreshSession();
}
}
}
/**
* Refreshes session and resets session timers.
*/
@ -602,15 +575,6 @@ async function onSessionActivity(): Promise<void> {
isSessionActive.value = true;
}
/**
* Subscribes to activity events to refresh session timers.
*/
onBeforeMount(() => {
eventBus.$on('upload_progress', () => {
resetSessionOnLogicRelatedActivity();
});
});
/**
* Lifecycle hook after initial render.
* Pre fetches user`s and project information.
@ -709,7 +673,10 @@ onMounted(async () => {
});
onBeforeUnmount(() => {
eventBus.$off('upload_progress');
clearSessionTimers();
resetActivityEvents.forEach((eventName: string) => {
document.removeEventListener(eventName, onSessionActivity);
});
});
</script>

View File

@ -103,8 +103,7 @@
</template>
<script setup lang="ts">
import { computed, onBeforeMount, onBeforeUnmount, onMounted, ref } from 'vue';
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
import { MODALS } from '@/utils/constants/appStatePopUps';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
@ -132,7 +131,6 @@ import { MetaUtils } from '@/utils/meta';
import { AppState } from '@/utils/constants/appStateEnum';
import { LocalData } from '@/utils/localData';
import { CouponType } from '@/types/coupons';
import eventBus from '@/utils/eventBus';
import { AuthHttpApi } from '@/api/auth';
import Heading from '@/views/all-dashboard/components/Heading.vue';
@ -160,7 +158,6 @@ const analytics = new AnalyticsHttpApi();
const auth: AuthHttpApi = new AuthHttpApi();
const inactivityModalTime = 60000;
const ACTIVITY_REFRESH_TIME_LIMIT = 180000;
const sessionDuration: number = parseInt(MetaUtils.getMetaContent('inactivity-timer-duration')) * 1000;
const sessionRefreshInterval: number = sessionDuration / 2;
const debugTimerShown = MetaUtils.getMetaContent('inactivity-timer-viewer-enabled') === 'true';
@ -448,7 +445,12 @@ function restartSessionTimers(): void {
}
}, sessionRefreshInterval);
inactivityTimerId.value = setTimeout(() => {
inactivityTimerId.value = setTimeout(async () => {
if (store.getters['files/uploadingLength']) {
await refreshSession();
return;
}
if (isSessionActive.value) return;
inactivityModalShown.value = true;
inactivityTimerId.value = setTimeout(async () => {
@ -477,28 +479,6 @@ function restartSessionTimers(): void {
debugTimer();
}
/**
* Used to trigger session timer update while doing not UI-related work for a long time.
*/
async function resetSessionOnLogicRelatedActivity(): Promise<void> {
const isInactivityTimerEnabled = MetaUtils.getMetaContent('inactivity-timer-enabled');
if (!isInactivityTimerEnabled) {
return;
}
const expiresAt = LocalData.getSessionExpirationDate();
if (expiresAt) {
const ms = Math.max(0, expiresAt.getTime() - Date.now());
// Isn't triggered when decent amount of session time is left.
if (ms < ACTIVITY_REFRESH_TIME_LIMIT) {
await refreshSession();
}
}
}
/**
* Refreshes session and resets session timers.
*/
@ -609,17 +589,7 @@ onMounted(async () => {
await store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED);
});
/**
* Subscribes to activity events to refresh session timers.
*/
onBeforeMount(() => {
eventBus.$on('upload_progress', () => {
resetSessionOnLogicRelatedActivity();
});
});
onBeforeUnmount(() => {
eventBus.$off('upload_progress');
clearSessionTimers();
resetActivityEvents.forEach((eventName: string) => {
document.removeEventListener(eventName, onSessionActivity);