diff options
author | dartcafe <github@dartcafe.de> | 2022-11-13 23:34:48 +0300 |
---|---|---|
committer | dartcafe <github@dartcafe.de> | 2022-11-13 23:34:48 +0300 |
commit | ce8ae122eb3a6a2259affb00759c3941b3a9e033 (patch) | |
tree | 63b2520821bec5a93e8c29729fa6fb98abb957db | |
parent | 0f098ccc6ecb26131ff37a18cc29b9d3d02fdcda (diff) |
updates and refactor watchPollsref/axios-requests
Signed-off-by: dartcafe <github@dartcafe.de>
-rw-r--r-- | src/js/Api/AxiosHelper.js | 2 | ||||
-rw-r--r-- | src/js/Api/polls.js | 9 | ||||
-rw-r--r-- | src/js/Api/public.js | 3 | ||||
-rw-r--r-- | src/js/helpers/AxiosHelper.js | 32 | ||||
-rw-r--r-- | src/js/mixins/watchPolls.js | 188 | ||||
-rw-r--r-- | src/js/views/SideBar.vue | 22 |
6 files changed, 102 insertions, 154 deletions
diff --git a/src/js/Api/AxiosHelper.js b/src/js/Api/AxiosHelper.js index f5ffbbe9..78c974a4 100644 --- a/src/js/Api/AxiosHelper.js +++ b/src/js/Api/AxiosHelper.js @@ -47,7 +47,7 @@ const axiosOcsInstance = axios.create(axiosOcsConfig) /**
* Description
*
- * @param {any} apiObject
+ * @param {any} apiObject apiObject
* @return {any}
*/
const createCancelTokenHandler = (apiObject) => {
diff --git a/src/js/Api/polls.js b/src/js/Api/polls.js index 92911f4e..3b278070 100644 --- a/src/js/Api/polls.js +++ b/src/js/Api/polls.js @@ -50,6 +50,15 @@ const polls = { })
},
+ watchPoll(pollId = 0, lastUpdated) {
+ return axiosInstance.request({
+ method: 'GET',
+ url: `poll/${pollId}/watch`,
+ params: { offset: lastUpdated },
+ cancelToken: cancelTokenHandlerObject[this.watchPoll.name].handleRequestCancellation().token,
+ })
+ },
+
takeOver(pollId) {
return axiosInstance.request({
method: 'PUT',
diff --git a/src/js/Api/public.js b/src/js/Api/public.js index 362665fc..e32460d9 100644 --- a/src/js/Api/public.js +++ b/src/js/Api/public.js @@ -32,10 +32,11 @@ const publicPoll = { })
},
- watch(shareToken) {
+ watchPoll(shareToken, lastUpdated) {
return axiosInstance.request({
method: 'GET',
url: `s/${shareToken}/watch`,
+ params: { offset: lastUpdated },
cancelToken: cancelTokenHandlerObject[this.watch.name].handleRequestCancellation().token,
})
},
diff --git a/src/js/helpers/AxiosHelper.js b/src/js/helpers/AxiosHelper.js deleted file mode 100644 index df5e15af..00000000 --- a/src/js/helpers/AxiosHelper.js +++ /dev/null @@ -1,32 +0,0 @@ -/**
- * @copyright Copyright (c) 2022 Rene Gieling <github@dartcafe.de>
- *
- * @author Rene Gieling <github@dartcafe.de>
- *
- * @license AGPL-3.0-or-later
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-const clientSessionId = Math.random().toString(36).substring(2)
-
-const axiosDefaultConfig = {
- headers: {
- Accept: 'application/json',
- 'Nc-Polls-Client-Id': clientSessionId,
- },
-}
-
-export { axiosDefaultConfig }
diff --git a/src/js/mixins/watchPolls.js b/src/js/mixins/watchPolls.js index 7534d2df..a6a846ed 100644 --- a/src/js/mixins/watchPolls.js +++ b/src/js/mixins/watchPolls.js @@ -21,29 +21,25 @@ * */ -import axios from '@nextcloud/axios' -import { generateUrl } from '@nextcloud/router' import { getCurrentUser } from '@nextcloud/auth' import { mapState } from 'vuex' import { InvalidJSON } from '../Exceptions/Exceptions.js' -import { axiosDefaultConfig } from '../helpers/AxiosHelper.js' +import { PollsAPI } from '../Api/polls.js' +import { PublicAPI } from '../Api/public.js' -const defaultSleepTimeout = 30 +const SLEEP_TIMEOUT_DEFAULT = 30 +const MAX_TRIES = 5 export const watchPolls = { data() { return { - cancelToken: null, restart: false, watching: true, lastUpdated: Math.round(Date.now() / 1000), - retryCounter: 0, - maxTries: 5, endPoint: '', isLoggedin: !!getCurrentUser(), isAdmin: !!getCurrentUser()?.isAdmin, - sleepTimeout: defaultSleepTimeout, // seconds - gotValidResponse: true, + sleepTimeout: SLEEP_TIMEOUT_DEFAULT, // seconds } }, @@ -51,170 +47,132 @@ export const watchPolls = { ...mapState({ updateType: (state) => state.appSettings.updateType, }), - }, - methods: { - async watchPolls() { - if (this.cancelToken) { - this.cancelWatch() // there is already a cancelToken, cancel the previous session + pollingDisabled() { + if (this.updateType !== 'noPolling') { + return false } - this.cancelToken = axios.CancelToken.source() // get a new cancel token + console.debug('[polls]', 'Polling for updates is disabled. Cancel watch.') + return true + }, + }, - while (this.retryCounter < this.maxTries) { - this.sleepTimeout = defaultSleepTimeout // reset sleep timer to default - this.gotValidResponse = false + methods: { + async watchPolls() { + let retryCounter = 0 + const sleepTimeout = SLEEP_TIMEOUT_DEFAULT - if (this.updateType === 'noPolling') { - // leave if polling is disabled - console.debug('[polls]', 'Polling for updates is disabled. Cancel watch.') - this.cancelWatch() - return - } + while (retryCounter < MAX_TRIES) { + if (this.pollingDisabled) return - // loop while tab is hidden and avoid further requests + // avoid requests when app is in background and pause while (document.hidden) { - console.debug('[polls]', 'app is in background') - await new Promise((resolve) => setTimeout(resolve, 2000)) + console.debug('[polls]', 'App in background, pause watching') + await new Promise((resolve) => setTimeout(resolve, 5000)) } try { - console.debug('[polls]', 'Watch for updates') - await this.handleResponse(await this.fetchUpdates()) + const response = await this.fetchUpdates() - } catch (e) { - if (axios.isCancel(e)) { - this.handleCanceledRequest() + if (response.headers['content-type'].includes('application/json')) { + retryCounter = 0 + this.loadStores(response.data.updates) } else { - this.handleConnectionError(e) + throw new InvalidJSON(`No JSON response recieved, got "${response.headers['content-type']}"`) } + + } catch (e) { + this.sleepTimeout = e?.response.headers['retry-after'] ?? SLEEP_TIMEOUT_DEFAULT + retryCounter = await this.handleConnectionException(e, retryCounter, sleepTimeout) } - // sleep if request was invalid or polling is set to something else than "longPolling" - if (this.updateType !== 'longPolling' || !this.gotValidResponse) { - await this.sleep() - console.debug('[polls]', 'continue after sleep') + // sleep if request was invalid or polling is set to "peeriodicPolling" + if (this.updateType === 'periodicPolling' || retryCounter) { + await this.sleep(sleepTimeout) + console.debug('[polls]', 'Continue after sleep') } } - // invalidate the cancel token before leaving - this.cancelToken = null - - if (this.retryCounter) { - console.debug('[polls]', `Cancel watch after ${this.retryCounter} failed requests`) + if (retryCounter) { + console.debug('[polls]', `Cancel watch after ${retryCounter} failed requests`) } }, async fetchUpdates() { - if (this.$route.name === 'publicVote') { - this.endPoint = `apps/polls/s/${this.$route.params.token}/watch` - } else { - this.endPoint = `apps/polls/poll/${this.$route.params.id ?? 0}/watch` - } - + console.debug('[polls]', `Watching for updates (${this.updateType})`) await this.$store.dispatch('appSettings/get') - const response = await axios.get(generateUrl(this.endPoint), { - ...axiosDefaultConfig, - params: { offset: this.lastUpdated }, - cancelToken: this.cancelToken.token, - }) - - return response + if (this.$route.name === 'publicVote') { + return await PublicAPI.watchPoll(this.$route.params.token, this.lastUpdated) + } + return await PollsAPI.watchPoll(this.$route.params.id, this.lastUpdated) }, - cancelWatch() { - this.cancelToken.cancel() + sleep(retryCounter, sleepTimeout) { + const reason = retryCounter ? `Connection error, Attempt: ${retryCounter}/${MAX_TRIES})` : this.updateType + console.debug('[polls]', `Sleep for ${sleepTimeout} seconds (reason: ${reason})`) + return new Promise((resolve) => setTimeout(resolve, sleepTimeout * 1000)) }, - sleep() { - let reason = `Connection error, Attempt: ${this.retryCounter}/${this.maxTries})` - - if (this.gotValidResponse) { - reason = this.updateType - } - - console.debug('[polls]', `Sleep for ${this.sleepTimeout} seconds (reason: ${reason})`) - return new Promise((resolve) => setTimeout(resolve, this.sleepTimeout * 1000)) - }, + async handleConnectionException(e, retryCounter, sleepTimeout) { + retryCounter += 1 - handleResponse(response) { - if (response.headers['content-type'].includes('application/json')) { - this.gotValidResponse = true - console.debug('[polls]', `Update detected (${this.updateType})`, response.data.updates) - this.loadTables(response.data.updates) - this.retryCounter = 0 // reset retryCounter after we got a valid response - return + if (e?.code === 'ERR_CANCELED') { + return 0 } - this.gotValidResponse = false - throw new InvalidJSON(`No JSON response recieved, got "${response.headers['content-type']}"`) - }, - - handleCanceledRequest() { - console.debug('[polls]', 'Fetch canceled') - this.cancelToken = axios.CancelToken.source() - }, - - async handleConnectionError(e) { if (e.response?.status === 304) { - console.debug('[polls]', `No updates (using ${this.updateType})`) - this.gotValidResponse = true - this.retryCounter = 0 // reset retryCounter, after we get a 304 - return + console.debug('[polls]', 'No updates') + return 0 } - this.retryCounter += 1 - if (e?.response?.status === 503) { // Server possibly in maintenance mode - this.sleepTimeout = e.response.headers['retry-after'] ?? this.sleepTimeout - console.debug('[polls]', `Service not avaiable - retry after ${this.sleepTimeout} seconds`) - return + console.debug('[polls]', `Service not avaiable - retry after ${sleepTimeout} seconds`) + return retryCounter } + if (e.response) { console.error('[polls]', e) - return + return retryCounter } - console.debug('[polls]', e.message ?? `No response - request aborted - failed request ${this.retryCounter}/${this.maxTries}`) + console.debug('[polls]', e.message ?? `No response - request aborted - failed request ${retryCounter}/${MAX_TRIES}`) }, - async loadTables(tables) { + async loadStores(stores) { + console.debug('[polls]', 'Updates detected', stores) + let dispatches = ['activity/list'] - console.debug('[polls]', 'fetching updates', tables) - tables.forEach((item) => { + + stores.forEach((item) => { this.lastUpdated = Math.max(item.updated, this.lastUpdated) + if (item.table === 'polls') { - if (this.isAdmin) { - console.debug('[polls]', 'update admin view', item.table) - // If user is an admin, also load admin list - dispatches = [...dispatches, 'pollsAdmin/list'] - } - if (item.pollId === parseInt(this.$route.params.id ?? this.$store.state.share.pollId)) { - // if current poll is affected, load current poll configuration - console.debug('[polls]', 'current poll', item.table) + // If user is an admin, also load admin list + if (this.isAdmin) dispatches = [...dispatches, 'pollsAdmin/list'] + + // if user is an authorized user load polls list and combo + if (this.isLoggedin) dispatches = [...dispatches, `${item.table}/list`, 'combo/cleanUp'] + + // if current poll is affected, load current poll configuration + if (item.pollId === this.$store.state.poll.id) { dispatches = [...dispatches, 'poll/get'] } - if (this.isLoggedin) { - // if user is an authorized user load polls list - console.debug('[polls]', 'update list', item.table) - dispatches = [...dispatches, `${item.table}/list`] - } } else if (!this.isLoggedin && (item.table === 'shares')) { // if current user is guest and table is shares only reload current share dispatches = [...dispatches, 'share/get'] } else { - // otherwise load table + // otherwise just load particulair store dispatches = [...dispatches, `${item.table}/list`] } }) - dispatches = [...new Set(dispatches)] // remove duplicates - await Promise.all(dispatches.map((dispatches) => this.$store.dispatch(dispatches))) - await this.$store.dispatch('combo/cleanUp') + dispatches = [...new Set(dispatches)] // remove duplicates and add combo + return Promise.all(dispatches.map((dispatches) => this.$store.dispatch(dispatches))) }, }, diff --git a/src/js/views/SideBar.vue b/src/js/views/SideBar.vue index 73a88f1d..2696c372 100644 --- a/src/js/views/SideBar.vue +++ b/src/js/views/SideBar.vue @@ -25,7 +25,7 @@ :title="t('polls', 'Details')" @close="closeSideBar()"> <NcAppSidebarTab v-if="acl.allowEdit" - :id="'configuration'" + id="configuration" :order="1" :name="t('polls', 'Configuration')"> <template #icon> @@ -35,7 +35,7 @@ </NcAppSidebarTab> <NcAppSidebarTab v-if="acl.allowEdit" - :id="'options'" + id="options" :order="2" :name="t('polls', 'Options')"> <template #icon> @@ -45,7 +45,7 @@ </NcAppSidebarTab> <NcAppSidebarTab v-if="acl.allowEdit" - :id="'sharing'" + id="sharing" :order="3" :name="t('polls', 'Sharing')"> <template #icon> @@ -55,7 +55,7 @@ </NcAppSidebarTab> <NcAppSidebarTab v-if="acl.allowComment" - :id="'comments'" + id="comments" :order="5" :name="t('polls', 'Comments')"> <template #icon> @@ -65,7 +65,7 @@ </NcAppSidebarTab> <NcAppSidebarTab v-if="acl.allowEdit && useActivity" - :id="'activity'" + id="activity" :order="6" :name="t('polls', 'Activity')"> <template #icon> @@ -85,11 +85,23 @@ import SidebarOptionsIcon from 'vue-material-design-icons/FormatListChecks.vue' import SidebarShareIcon from 'vue-material-design-icons/ShareVariant.vue' import SidebarCommentsIcon from 'vue-material-design-icons/CommentProcessing.vue' import SidebarActivityIcon from 'vue-material-design-icons/LightningBolt.vue' +// test static loading +// import SideBarTabConfiguration from '../components/SideBar/SideBarTabConfiguration.vue' +// import SideBarTabComments from '../components/SideBar/SideBarTabComments.vue' +// import SideBarTabOptions from '../components/SideBar/SideBarTabOptions.vue' +// import SideBarTabShare from '../components/SideBar/SideBarTabShare.vue' +// import SideBarTabActivity from '../components/SideBar/SideBarTabActivity.vue' export default { name: 'SideBar', components: { + // test static loading + // SideBarTabConfiguration, + // SideBarTabComments, + // SideBarTabOptions, + // SideBarTabShare, + // SideBarTabActivity, SideBarTabConfiguration: () => import('../components/SideBar/SideBarTabConfiguration.vue'), SideBarTabComments: () => import('../components/SideBar/SideBarTabComments.vue'), SideBarTabOptions: () => import('../components/SideBar/SideBarTabOptions.vue'), |