2020-04-24 09:03:00 +02:00
|
|
|
<template>
|
|
|
|
<div id="quick-uploader">
|
|
|
|
<!-- static landing UI -->
|
|
|
|
<div v-show="!(showStream && canStream)" class="container has-text-centered">
|
|
|
|
<div class="columns quick-uploader">
|
|
|
|
<!-- trailer phrase that invite to add an account -->
|
2020-11-20 14:11:32 +01:00
|
|
|
<div class="column is-full quick-uploader-header" :class="{ 'is-invisible' : accountCount > 0 }">
|
2020-04-24 09:03:00 +02:00
|
|
|
{{ $t('twofaccounts.no_account_here') }}<br>
|
|
|
|
{{ $t('twofaccounts.add_first_account') }}
|
|
|
|
</div>
|
2020-11-20 14:11:32 +01:00
|
|
|
<!-- Livescan button -->
|
2020-04-24 09:03:00 +02:00
|
|
|
<div class="column is-full quick-uploader-button" >
|
|
|
|
<div class="quick-uploader-centerer">
|
2020-11-20 14:11:32 +01:00
|
|
|
<!-- upload a qr code (with basic file field and backend decoding) -->
|
2020-11-20 18:59:36 +01:00
|
|
|
<label v-if="$root.appSettings.useBasicQrcodeReader" class="button is-link is-medium is-rounded is-focused" ref="qrcodeInputLabel">
|
2020-11-20 14:11:32 +01:00
|
|
|
<input class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput">
|
|
|
|
{{ $t('twofaccounts.forms.upload_qrcode') }}
|
|
|
|
</label>
|
2020-04-24 09:03:00 +02:00
|
|
|
<!-- scan button that launch camera stream -->
|
2020-11-20 14:11:32 +01:00
|
|
|
<label v-else class="button is-link is-medium is-rounded is-focused" @click="enableStream()">
|
2020-04-24 09:03:00 +02:00
|
|
|
{{ $t('twofaccounts.forms.scan_qrcode') }}
|
|
|
|
</label>
|
2020-11-20 14:11:32 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- alternative methods -->
|
|
|
|
<div class="column is-full">
|
|
|
|
<div class="block has-text-light">Alternative methods</div>
|
|
|
|
<!-- upload a qr code (with qrcode-capture component) -->
|
|
|
|
<div class="block has-text-link" v-if="!$root.appSettings.useBasicQrcodeReader">
|
|
|
|
<form @submit.prevent="createAccount" @keydown="form.onKeydown($event)">
|
2020-11-20 18:59:36 +01:00
|
|
|
<label :class="{'is-loading' : form.isBusy}" class="button is-link is-outlined is-rounded" ref="qrcodeInputLabel">
|
2020-11-20 14:11:32 +01:00
|
|
|
<qrcode-capture @decode="submitUri" class="file-input" ref="qrcodeInput" />
|
|
|
|
{{ $t('twofaccounts.forms.upload_qrcode') }}
|
2020-04-24 09:03:00 +02:00
|
|
|
</label>
|
|
|
|
<field-error :form="form" field="qrcode" />
|
|
|
|
<field-error :form="form" field="uri" />
|
|
|
|
</form>
|
|
|
|
</div>
|
2020-11-20 14:11:32 +01:00
|
|
|
<!-- link to advanced form -->
|
|
|
|
<div class="block has-text-link">
|
|
|
|
<router-link class="button is-link is-outlined is-rounded" :to="{ name: 'createAccount' }" >
|
|
|
|
{{ $t('twofaccounts.forms.use_advanced_form') }}
|
|
|
|
</router-link>
|
|
|
|
</div>
|
2020-04-24 09:03:00 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
2020-11-20 14:11:32 +01:00
|
|
|
<!-- Footer -->
|
|
|
|
<vue-footer :showButtons="true">
|
|
|
|
<!-- back button -->
|
|
|
|
<p class="control">
|
2020-11-20 18:59:36 +01:00
|
|
|
<router-link class="button is-dark is-rounded" :to="{ name: 'accounts' }" >
|
2020-11-20 14:11:32 +01:00
|
|
|
{{ $t('commons.back') }}
|
|
|
|
</router-link>
|
|
|
|
</p>
|
|
|
|
</vue-footer>
|
2020-04-24 09:03:00 +02:00
|
|
|
</div>
|
|
|
|
<!-- camera stream fullscreen scanner -->
|
|
|
|
<div v-show="showStream && canStream">
|
|
|
|
<div class="fullscreen-alert has-text-centered">
|
|
|
|
<span class="is-size-4 has-text-light">
|
|
|
|
<font-awesome-icon :icon="['fas', 'spinner']" size="2x" spin />
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<div class="fullscreen-streamer">
|
2020-11-20 14:11:32 +01:00
|
|
|
<qrcode-stream @decode="submitUri" @init="onStreamerInit" :camera="camera" />
|
2020-04-24 09:03:00 +02:00
|
|
|
</div>
|
|
|
|
<div class="fullscreen-footer">
|
|
|
|
<!-- Cancel button -->
|
|
|
|
<label class="button is-large is-warning is-rounded" @click="disableStream()">
|
|
|
|
{{ $t('commons.cancel') }}
|
|
|
|
</label>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
2020-11-20 18:47:32 +01:00
|
|
|
/**
|
|
|
|
* Start view
|
|
|
|
*
|
|
|
|
* route: '/start'
|
|
|
|
*
|
|
|
|
* Offer the user all available possibilities for capturing an account :
|
|
|
|
* - By decoding a QR code acquired by live scan
|
|
|
|
* - By decoding a QR code submitted with a form 'File' field
|
|
|
|
* - By browsing to the advanced form
|
|
|
|
*
|
|
|
|
* 2 QR code decoders are implemented :
|
|
|
|
* - vue-qrcode-reader (the default one)
|
|
|
|
* ~ QR codes are acquired by live scan or with a 'File' field by the js front-end
|
|
|
|
* ~ Decoding is done by the js front-end, there is no call to the back-end API
|
|
|
|
* - chillerlan/php-qrcode (aka 'BasicQrcodeReader')
|
|
|
|
* ~ QR codes are acquired with a 'File' field and uploaded to the php backend
|
|
|
|
* ~ Decoding is done by the php back-end
|
|
|
|
*
|
|
|
|
* Output : both decoders provide an URI and push it the Create form
|
|
|
|
*
|
2020-11-20 18:59:36 +01:00
|
|
|
* The view behavior is affected by the user options :
|
2020-11-20 18:47:32 +01:00
|
|
|
* - 'appSettings.useBasicQrcodeReader' totally disable the vue-qrcode-reader decoder
|
|
|
|
* - 'appSettings.useDirectCapture' trigger the acquisition mode set by 'appSettings.defaultCaptureMode' automatically at vue @created event
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2020-11-20 14:11:32 +01:00
|
|
|
import Form from './../components/Form'
|
2020-04-24 09:03:00 +02:00
|
|
|
|
|
|
|
export default {
|
2020-11-20 14:11:32 +01:00
|
|
|
name: 'Start',
|
2020-04-24 09:03:00 +02:00
|
|
|
|
|
|
|
data(){
|
|
|
|
return {
|
|
|
|
form: new Form({
|
|
|
|
qrcode: null,
|
|
|
|
uri: '',
|
|
|
|
}),
|
|
|
|
errorName: '',
|
|
|
|
errorText: '',
|
|
|
|
showStream: false,
|
2020-11-20 18:59:36 +01:00
|
|
|
canStream: null,
|
2020-04-24 09:03:00 +02:00
|
|
|
camera: 'auto',
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-11-20 14:11:32 +01:00
|
|
|
props: ['accountCount'],
|
2020-04-24 09:03:00 +02:00
|
|
|
|
|
|
|
created() {
|
2020-11-20 18:59:36 +01:00
|
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
if( this.$root.appSettings.useDirectCapture && this.$root.appSettings.defaultCaptureMode === 'upload' ) {
|
|
|
|
this.$refs.qrcodeInputLabel.click()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2020-04-24 09:03:00 +02:00
|
|
|
if( this.$root.appSettings.useBasicQrcodeReader ) {
|
2020-11-20 14:11:32 +01:00
|
|
|
// User has set the basic QR code reader (run by backend) so we disable the other one (run by js)
|
2020-04-24 09:03:00 +02:00
|
|
|
this.canStream = this.showStream = false
|
|
|
|
}
|
2020-11-20 18:59:36 +01:00
|
|
|
else if( this.accountCount > 0 && this.$root.appSettings.useDirectCapture ) {
|
|
|
|
if( this.$root.appSettings.defaultCaptureMode === 'livescan' ) {
|
2020-04-24 09:03:00 +02:00
|
|
|
this.enableStream()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
beforeDestroy() {
|
|
|
|
this.form.clear()
|
|
|
|
},
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
|
|
async enableStream() {
|
|
|
|
|
|
|
|
this.camera = 'auto'
|
|
|
|
|
2020-11-20 18:59:36 +01:00
|
|
|
if( this.canStream ) {
|
|
|
|
this.showStream = true
|
|
|
|
|
|
|
|
console.log('stream started')
|
|
|
|
}
|
|
|
|
else if( this.errorText && !this.$root.appSettings.useBasicQrcodeReader ) {
|
|
|
|
this.$notify({ type: 'is-warning', text: this.errorText })
|
|
|
|
}
|
2020-04-24 09:03:00 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
async disableStream() {
|
|
|
|
|
|
|
|
this.camera = 'off'
|
|
|
|
this.showStream = false
|
|
|
|
|
2020-11-20 18:59:36 +01:00
|
|
|
console.log('stream stopped')
|
2020-04-24 09:03:00 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
async onStreamerInit (promise) {
|
|
|
|
|
|
|
|
this.errorText = ''
|
|
|
|
this.errorName = ''
|
|
|
|
|
|
|
|
try {
|
|
|
|
await promise
|
2020-11-20 18:59:36 +01:00
|
|
|
|
|
|
|
this.canStream = true
|
|
|
|
console.log('stream is possible')
|
2020-04-24 09:03:00 +02:00
|
|
|
}
|
|
|
|
catch (error) {
|
|
|
|
|
|
|
|
this.errorName = error.name
|
|
|
|
|
|
|
|
if (error.name === 'NotAllowedError') {
|
|
|
|
this.errorText = this.$t('twofaccounts.stream.need_grant_permission')
|
|
|
|
|
|
|
|
} else if (error.name === 'NotReadableError') {
|
|
|
|
this.errorText = this.$t('twofaccounts.stream.not_readable')
|
|
|
|
|
|
|
|
} else if (error.name === 'NotFoundError') {
|
|
|
|
this.errorText = this.$t('twofaccounts.stream.no_cam_on_device')
|
|
|
|
|
|
|
|
} else if (error.name === 'NotSupportedError' || error.name === 'InsecureContextError') {
|
|
|
|
this.errorText = this.$t('twofaccounts.stream.secured_context_required')
|
|
|
|
|
|
|
|
} else if (error.name === 'OverconstrainedError') {
|
|
|
|
this.errorText = this.$t('twofaccounts.stream.camera_not_suitable')
|
|
|
|
|
|
|
|
} else if (error.name === 'StreamApiNotSupportedError') {
|
|
|
|
this.errorText = this.$t('twofaccounts.stream.stream_api_not_supported')
|
|
|
|
}
|
2020-09-25 23:36:11 +02:00
|
|
|
|
2020-11-20 14:11:32 +01:00
|
|
|
this.canStream = false
|
2020-04-24 09:03:00 +02:00
|
|
|
|
2020-11-20 18:59:36 +01:00
|
|
|
if( !this.$root.appSettings.useBasicQrcodeReader && this.$root.appSettings.useDirectCapture && this.$root.appSettings.defaultCaptureMode === 'livescan') {
|
|
|
|
this.$notify({ type: 'is-warning', text: this.errorText })
|
2020-11-20 14:11:32 +01:00
|
|
|
}
|
2020-05-08 11:37:49 +02:00
|
|
|
|
2020-11-20 18:59:36 +01:00
|
|
|
console.log('stream no possible : ' + this.errorText)
|
2020-04-24 09:03:00 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
|
2020-11-20 14:11:32 +01:00
|
|
|
/**
|
2020-11-20 18:59:36 +01:00
|
|
|
* the basicQRcodeReader option is On, so qrcode decoding will be done by the backend, which in return
|
2020-11-20 14:11:32 +01:00
|
|
|
* send the corresponding URI
|
|
|
|
*/
|
|
|
|
async submitQrCode() {
|
2020-04-24 09:03:00 +02:00
|
|
|
|
2020-11-20 14:11:32 +01:00
|
|
|
let imgdata = new FormData();
|
|
|
|
imgdata.append('qrcode', this.$refs.qrcodeInput.files[0]);
|
|
|
|
imgdata.append('inputFormat', 'fileUpload');
|
2020-04-24 09:03:00 +02:00
|
|
|
|
2020-11-20 14:11:32 +01:00
|
|
|
const { data } = await this.form.upload('/api/qrcode/decode', imgdata)
|
2020-04-24 09:03:00 +02:00
|
|
|
|
2020-11-20 14:11:32 +01:00
|
|
|
this.pushUriToCreateForm(data.uri)
|
|
|
|
},
|
2020-04-24 09:03:00 +02:00
|
|
|
|
|
|
|
|
2020-11-20 14:11:32 +01:00
|
|
|
/**
|
|
|
|
* The basicQRcodeReader option is Off, so qrcode decoding has already be done by vue-qrcode-reader, whether
|
|
|
|
* from livescan or file input.
|
|
|
|
* We simply check the uri validity to prevent useless push to the Create form, but the form will check uri validity too.
|
|
|
|
*/
|
|
|
|
async submitUri(event) {
|
2020-11-20 18:59:36 +01:00
|
|
|
|
2020-11-20 14:11:32 +01:00
|
|
|
this.form.uri = event
|
2020-04-24 09:03:00 +02:00
|
|
|
|
2020-11-20 14:11:32 +01:00
|
|
|
if( !this.form.uri ) {
|
|
|
|
this.$notify({type: 'is-warning', text: this.$t('errors.qrcode_cannot_be_read') })
|
|
|
|
}
|
|
|
|
else if( this.form.uri.slice(0, 15 ).toLowerCase() !== "otpauth://totp/" && this.form.uri.slice(0, 15 ).toLowerCase() !== "otpauth://hotp/" ) {
|
|
|
|
this.$notify({type: 'is-warning', text: this.$t('errors.no_valid_otp') })
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.pushUriToCreateForm(this.form.uri)
|
|
|
|
}
|
2020-04-24 09:03:00 +02:00
|
|
|
},
|
|
|
|
|
2020-11-20 14:11:32 +01:00
|
|
|
pushUriToCreateForm(data) {
|
|
|
|
this.$router.push({ name: 'createAccount', params: { decodedUri: data } });
|
|
|
|
}
|
|
|
|
|
2020-04-24 09:03:00 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
</script>
|