1
0
mirror of https://github.com/Luzifer/vault-otp-ui.git synced 2024-09-16 15:48:32 +00:00
vault-otp-ui/application.js
Knut Ahlers 8935773c53
Be nicer to background tabs, try to survive them
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2019-09-19 23:26:34 +02:00

208 lines
5.7 KiB
JavaScript

const iterationCurrent = 'current'
const iterationNext = 'next'
const app = new Vue({
computed: {
filteredItems() {
if (this.filter === '') {
return this.otpItems
}
const items = []
for (let i of this.otpItems) {
if (i.name.toLowerCase().match(this.filter.toLowerCase())) {
items.push(i)
}
}
return items
},
minPeriod() {
let min = 99999
for (let i of this.otpItems) {
if (i.period > 0 && i.period < min) {
min = i.period
}
}
if (min === 99999) {
min = 30
}
return min
},
},
data: {
authUrl,
backoff: 500,
currentTimeout: null,
fetchInProgress: false,
filter: '',
inactivityTimeout: null,
lastFetch: null,
loading: true,
preFetch: null,
signedIn,
otpItems: [],
refreshTimerProgressTicker: null,
timeLeftPerc: 0.0,
},
el: '#application',
methods: {
// Let user know whether the copy command was successful
codeCopyResult(success) {
this.createAlert(success ? 'success' : 'danger', 'Copy to clipboard...', 'Code copied to clipboard')
},
// Wrapper around toast creation
createAlert(variant, title, text, autoHideDelay=2000) {
this.$bvToast.toast(text, {
autoHideDelay,
title,
toaster: 'b-toaster-bottom-center',
variant,
})
},
// Main functionality: Fetch codes, handle errors including backoff
fetchCodes(iteration=iterationCurrent) {
if (this.fetchInProgress || !this.signedIn) {
return
}
if (this.lastFetch && this.lastFetch.getTime() + this.backoff > new Date().getTime()) {
// slow down spammy requests
return
}
this.lastFetch = new Date()
this.fetchInProgress=true
let successFunc= iteration == iterationCurrent ? this.updateCodes : this.updatePreFetch
if (iteration == iterationCurrent && this.preFetch !== null) {
successFunc(this.preFetch)
this.fetchInProgress = false
return
}
axios.get(`codes.json?it=${iteration}`)
.then(resp => {
successFunc(resp.data)
this.backoff = 500 // Reset backoff to 500ms
})
.catch(err => {
this.backoff = this.backoff * 1.5 > 30000 ? 30000 : this.backoff * 1.5
if (err.response && err.response.status) {
switch (err.response.status) {
case 401:
this.createAlert('danger', 'Logged out...', 'Server has no valid token for you: You need to re-login.')
this.signedIn = false
this.otpItems = []
break
case 500:
this.createAlert('danger', 'Oops.', `Something went wrong when fetching your codes, will try again in ${Math.round(this.backoff / 1000)}s...`, this.backoff)
break;
}
} else {
console.error(err)
this.createAlert('danger', 'Oops.', `The request went wrong, will try again in ${Math.round(this.backoff / 1000)}s...`, this.backoff)
}
if (iteration === iterationCurrent) {
this.otpItems = []
this.loading = true
}
})
.finally(() => { this.fetchInProgress=false })
},
// Format code for better readability: 000 000 or 00 000 000
formatCode(code) {
return code
.replace(/^([0-9]{3})([0-9]{3})$/, '$1 $2') // 6 digits
.replace(/^([0-9]{2})([0-9]{3})([0-9]{2})$/, '$1 $2 $3') // 7 digits
.replace(/^([0-9]{2})([0-9]{3})([0-9]{3})$/, '$1 $2 $3') // 8 digits
},
// Update timer bar and trigger re-fetch of codes by time remaining
refreshTimerProgress() {
const secondsLeft = this.timeLeft()
this.timeLeftPerc = secondsLeft / this.minPeriod * 100
if (secondsLeft < 3 && !this.preFetch && this.signedIn) {
// Do a pre-fetch to provide a seamless experience
this.fetchCodes(secondsLeft < 0 ? iterationCurrent : iterationNext)
}
},
// Calculate remaining time for the current batch of codes
timeLeft() {
if (!this.currentTimeout) {
return 0
}
const now = new Date().getTime()
return (this.currentTimeout.getTime() - now) / 1000
},
// Update displayed codes
updateCodes(data) {
this.currentTimeout = new Date(data.next_wrap)
this.otpItems = data.tokens
this.loading = false
this.preFetch = null
window.setTimeout(this.fetchCodes, this.timeLeft()*1000)
},
// Store received data for later usage
updatePreFetch(data) {
this.preFetch = data
},
windowBlur(evt) {
// Prepare shutdown of background refresh as long as page has no focus
this.inactivityTimeout = window.setTimeout(() => {
window.clearInterval(this.refreshTimerProgressTicker)
this.refreshTimerProgressTicker = null
this.inactivityTimeout = null
}, 30000)
},
windowFocus(evt) {
if (this.inactivityTimeout) {
// Page is in focus again, do not suspend
window.clearTimeout(this.inactivityTimeout)
this.inactivityTimeout = null
}
if (!this.refreshTimerProgressTicker) {
// In case the refresh ticker was stopped, revive it
this.refreshTimerProgressTicker = window.setInterval(this.refreshTimerProgress, 500)
}
},
},
// Initialize application
mounted() {
this.refreshTimerProgressTicker = window.setInterval(this.refreshTimerProgress, 500)
this.fetchCodes(iterationCurrent)
window.addEventListener('blur', (evt) => this.windowBlur(evt))
window.addEventListener('focus', (evt) => this.windowFocus(evt))
},
})