2023-03-27 12:12:11 +02:00
|
|
|
// These are the legacy states, see InvoiceEntity
|
2024-05-15 00:49:53 +02:00
|
|
|
const STATUS_PAYABLE = ['New'];
|
|
|
|
const STATUS_PAID = ['Processing'];
|
|
|
|
const STATUS_SETTLED = ['Settled'];
|
|
|
|
const STATUS_INVALID = ['Expired', 'Invalid'];
|
2023-03-01 07:49:21 +01:00
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
2022-11-24 00:53:32 +01:00
|
|
|
|
2023-11-30 10:17:23 +01:00
|
|
|
class NDEFReaderWrapper {
|
|
|
|
constructor() {
|
|
|
|
this.onreading = null;
|
|
|
|
this.onreadingerror = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
async scan(opts) {
|
|
|
|
if (opts && opts.signal){
|
|
|
|
opts.signal.addEventListener('abort', () => {
|
|
|
|
window.parent.postMessage('nfc:abort', '*');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
window.parent.postMessage('nfc:startScan', '*');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-24 00:53:32 +01:00
|
|
|
function computeStartingLanguage() {
|
2023-03-01 07:49:21 +01:00
|
|
|
const lang = urlParams.get('lang')
|
|
|
|
if (lang && isLanguageAvailable(lang)) return lang;
|
2022-11-24 00:53:32 +01:00
|
|
|
const { defaultLang } = initialSrvModel;
|
|
|
|
return isLanguageAvailable(defaultLang) ? defaultLang : fallbackLanguage;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isLanguageAvailable(languageCode) {
|
2023-03-01 07:49:21 +01:00
|
|
|
return availableLanguages.includes(languageCode);
|
|
|
|
}
|
|
|
|
|
2023-03-02 08:34:15 +01:00
|
|
|
function updateLanguageSelect() {
|
|
|
|
// calculate and set width, as we want it center aligned
|
|
|
|
const $languageSelect = document.getElementById('DefaultLang');
|
|
|
|
const element = document.createElement('div');
|
|
|
|
element.innerText = $languageSelect.querySelector('option:checked').text;
|
|
|
|
$languageSelect.parentElement.appendChild(element);
|
|
|
|
const width = element.offsetWidth;
|
|
|
|
$languageSelect.parentElement.removeChild(element);
|
2023-03-28 13:12:47 +02:00
|
|
|
if (width && width > 0) {
|
|
|
|
$languageSelect.style.setProperty('--text-width', `${width}px`);
|
|
|
|
} else { // in case of modal this might not be rendered properly yet
|
|
|
|
window.requestAnimationFrame(updateLanguageSelect);
|
|
|
|
}
|
2023-03-02 08:34:15 +01:00
|
|
|
}
|
|
|
|
|
2023-03-01 07:49:21 +01:00
|
|
|
function updateLanguage(lang) {
|
|
|
|
if (isLanguageAvailable(lang)) {
|
|
|
|
i18next.changeLanguage(lang);
|
|
|
|
urlParams.set('lang', lang);
|
|
|
|
window.history.replaceState({}, '', `${location.pathname}?${urlParams}`);
|
2023-03-02 08:34:15 +01:00
|
|
|
updateLanguageSelect();
|
2023-03-01 07:49:21 +01:00
|
|
|
}
|
2022-11-24 00:53:32 +01:00
|
|
|
}
|
|
|
|
|
2024-04-30 11:26:27 +02:00
|
|
|
function asNumber(val) {
|
|
|
|
return val && parseFloat(val.toString().replace(/\s/g, '')); // e.g. sats are formatted with spaces: 1 000 000
|
|
|
|
}
|
|
|
|
|
2022-11-24 00:53:32 +01:00
|
|
|
Vue.use(VueI18next);
|
|
|
|
|
2023-03-01 07:49:21 +01:00
|
|
|
const fallbackLanguage = 'en';
|
|
|
|
const startingLanguage = computeStartingLanguage();
|
2022-11-24 00:53:32 +01:00
|
|
|
const i18n = new VueI18next(i18next);
|
|
|
|
|
2023-02-25 14:28:02 +01:00
|
|
|
const PaymentDetails = {
|
|
|
|
template: '#payment-details',
|
2022-11-24 00:53:32 +01:00
|
|
|
props: {
|
|
|
|
srvModel: Object,
|
2023-03-27 12:12:11 +02:00
|
|
|
isActive: Boolean,
|
|
|
|
showRecommendedFee: Boolean,
|
|
|
|
orderAmount: Number,
|
|
|
|
btcPaid: Number,
|
|
|
|
btcDue: Number
|
2024-04-30 11:26:27 +02:00
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
asNumber
|
2022-11-24 00:53:32 +01:00
|
|
|
}
|
2023-02-25 14:28:02 +01:00
|
|
|
}
|
2022-11-24 00:53:32 +01:00
|
|
|
|
|
|
|
function initApp() {
|
|
|
|
return new Vue({
|
|
|
|
i18n,
|
2024-04-05 17:43:38 +02:00
|
|
|
el: '#Checkout',
|
2022-11-24 00:53:32 +01:00
|
|
|
components: {
|
2023-02-25 14:28:02 +01:00
|
|
|
'payment-details': PaymentDetails,
|
2022-11-24 00:53:32 +01:00
|
|
|
},
|
|
|
|
data () {
|
|
|
|
const srvModel = initialSrvModel;
|
|
|
|
return {
|
|
|
|
srvModel,
|
2024-09-02 11:20:29 +02:00
|
|
|
audioContext: new AudioContext(),
|
2022-11-24 00:53:32 +01:00
|
|
|
displayPaymentDetails: false,
|
|
|
|
remainingSeconds: srvModel.expirationSeconds,
|
|
|
|
emailAddressInput: "",
|
|
|
|
emailAddressInputDirty: false,
|
|
|
|
emailAddressInputInvalid: false,
|
|
|
|
paymentMethodId: null,
|
|
|
|
endData: null,
|
2023-03-27 12:12:11 +02:00
|
|
|
isModal: srvModel.isModal,
|
2023-07-24 15:57:24 +02:00
|
|
|
pollTimeoutID: null,
|
|
|
|
paymentSound: null,
|
|
|
|
nfcReadSound: null,
|
|
|
|
errorSound: null,
|
2023-11-30 10:17:23 +01:00
|
|
|
nfc: {
|
|
|
|
supported: 'NDEFReader' in window,
|
|
|
|
scanning: false,
|
|
|
|
submitting: false,
|
|
|
|
errorMessage: null,
|
|
|
|
permissionGranted: false,
|
|
|
|
readerAbortController: null
|
|
|
|
}
|
2022-11-24 00:53:32 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
2023-03-27 12:12:11 +02:00
|
|
|
isInvalid () {
|
|
|
|
return STATUS_INVALID.includes(this.srvModel.status);
|
2022-11-24 00:53:32 +01:00
|
|
|
},
|
2023-03-27 12:12:11 +02:00
|
|
|
isSettled () {
|
|
|
|
return STATUS_SETTLED.includes(this.srvModel.status);
|
|
|
|
},
|
|
|
|
isProcessing () {
|
2022-11-24 00:53:32 +01:00
|
|
|
return STATUS_PAID.includes(this.srvModel.status);
|
|
|
|
},
|
|
|
|
isActive () {
|
2023-03-27 12:12:11 +02:00
|
|
|
return STATUS_PAYABLE.includes(this.srvModel.status);
|
2022-11-24 00:53:32 +01:00
|
|
|
},
|
2023-05-11 10:38:40 +02:00
|
|
|
isPaidPartial () {
|
|
|
|
return this.btcPaid > 0 && this.btcDue > 0;
|
|
|
|
},
|
2022-11-24 00:53:32 +01:00
|
|
|
showInfo () {
|
|
|
|
return this.showTimer || this.showPaymentDueInfo;
|
|
|
|
},
|
|
|
|
showTimer () {
|
2023-01-16 12:45:19 +01:00
|
|
|
return this.isActive && this.remainingSeconds < this.srvModel.displayExpirationTimer;
|
2022-11-24 00:53:32 +01:00
|
|
|
},
|
|
|
|
showPaymentDueInfo () {
|
2023-05-11 10:38:40 +02:00
|
|
|
return this.isPaidPartial;
|
2022-11-24 00:53:32 +01:00
|
|
|
},
|
|
|
|
showRecommendedFee () {
|
2023-03-27 12:12:11 +02:00
|
|
|
return this.isActive && this.srvModel.showRecommendedFee && this.srvModel.feeRate;
|
2022-11-24 00:53:32 +01:00
|
|
|
},
|
|
|
|
orderAmount () {
|
2023-03-27 12:12:11 +02:00
|
|
|
return this.asNumber(this.srvModel.orderAmount);
|
2022-11-24 00:53:32 +01:00
|
|
|
},
|
|
|
|
btcDue () {
|
2023-03-27 12:12:11 +02:00
|
|
|
return this.asNumber(this.srvModel.btcDue);
|
2022-11-24 00:53:32 +01:00
|
|
|
},
|
|
|
|
btcPaid () {
|
2023-03-27 12:12:11 +02:00
|
|
|
return this.asNumber(this.srvModel.btcPaid);
|
2022-11-24 00:53:32 +01:00
|
|
|
},
|
|
|
|
pmId () {
|
|
|
|
return this.paymentMethodId || this.srvModel.paymentMethodId;
|
|
|
|
},
|
|
|
|
minutesLeft () {
|
|
|
|
return Math.floor(this.remainingSeconds / 60);
|
|
|
|
},
|
|
|
|
secondsLeft () {
|
|
|
|
return Math.floor(this.remainingSeconds % 60);
|
|
|
|
},
|
|
|
|
timeText () {
|
|
|
|
return this.remainingSeconds > 0
|
|
|
|
? `${this.padTime(this.minutesLeft)}:${this.padTime(this.secondsLeft)}`
|
|
|
|
: '00:00';
|
|
|
|
},
|
|
|
|
storeLink () {
|
|
|
|
return this.srvModel.merchantRefLink && this.srvModel.merchantRefLink !== this.srvModel.receiptLink
|
|
|
|
? this.srvModel.merchantRefLink
|
|
|
|
: null;
|
|
|
|
},
|
|
|
|
paymentMethodIds () {
|
|
|
|
return this.srvModel.availableCryptos.map(function (c) { return c.paymentMethodId });
|
|
|
|
},
|
|
|
|
paymentMethodComponent () {
|
|
|
|
return this.isPluginPaymentMethod
|
|
|
|
? `${this.pmId}Checkout`
|
|
|
|
: this.srvModel.activated && this.srvModel.uiSettings.checkoutBodyVueComponentName;
|
|
|
|
},
|
|
|
|
isPluginPaymentMethod () {
|
|
|
|
return !this.paymentMethodIds.includes(this.pmId);
|
2023-03-27 12:12:11 +02:00
|
|
|
},
|
|
|
|
realCryptoCode () {
|
|
|
|
return this.srvModel.cryptoCode.toLowerCase() === 'sats' ? 'BTC' : this.srvModel.cryptoCode;
|
2022-11-24 00:53:32 +01:00
|
|
|
}
|
|
|
|
},
|
2023-03-13 02:09:56 +01:00
|
|
|
watch: {
|
2023-03-27 12:12:11 +02:00
|
|
|
isProcessing: function (newValue, oldValue) {
|
|
|
|
if (newValue === true && oldValue === false) {
|
|
|
|
// poll from here on
|
|
|
|
this.listenForConfirmations();
|
2023-04-13 01:40:21 +02:00
|
|
|
// celebration!
|
|
|
|
const self = this;
|
|
|
|
Vue.nextTick(function () {
|
|
|
|
self.celebratePayment(5000);
|
|
|
|
});
|
2023-03-27 12:12:11 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
isSettled: function (newValue, oldValue) {
|
2023-03-13 02:09:56 +01:00
|
|
|
if (newValue === true && oldValue === false) {
|
|
|
|
const duration = 5000;
|
|
|
|
const self = this;
|
2023-03-27 12:12:11 +02:00
|
|
|
// stop polling
|
|
|
|
if (this.pollTimeoutID) {
|
|
|
|
clearTimeout(this.pollTimeoutID);
|
|
|
|
}
|
2023-03-13 02:09:56 +01:00
|
|
|
// celebration!
|
|
|
|
Vue.nextTick(function () {
|
|
|
|
self.celebratePayment(duration);
|
|
|
|
});
|
|
|
|
// automatic redirect or close
|
|
|
|
if (self.srvModel.redirectAutomatically && self.storeLink) {
|
|
|
|
setTimeout(function () {
|
|
|
|
if (self.isModal && window.top.location === self.storeLink) {
|
|
|
|
self.close();
|
|
|
|
} else {
|
|
|
|
window.top.location = self.storeLink;
|
|
|
|
}
|
|
|
|
}, duration);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2023-11-30 10:17:23 +01:00
|
|
|
async mounted () {
|
2022-11-24 00:53:32 +01:00
|
|
|
this.updateData(this.srvModel);
|
|
|
|
this.updateTimer();
|
2023-03-27 12:12:11 +02:00
|
|
|
if (this.isActive || this.isProcessing) {
|
2022-11-24 00:53:32 +01:00
|
|
|
this.listenIn();
|
|
|
|
}
|
2023-03-27 12:12:11 +02:00
|
|
|
if (this.isProcessing) {
|
|
|
|
this.listenForConfirmations();
|
|
|
|
}
|
2023-07-24 15:57:24 +02:00
|
|
|
if (this.srvModel.paymentSoundUrl) {
|
|
|
|
this.prepareSound(this.srvModel.paymentSoundUrl).then(sound => this.paymentSound = sound);
|
|
|
|
this.prepareSound(this.srvModel.nfcReadSoundUrl).then(sound => this.nfcReadSound = sound);
|
|
|
|
this.prepareSound(this.srvModel.errorSoundUrl).then(sound => this.errorSound = sound);
|
|
|
|
}
|
2023-11-30 10:17:23 +01:00
|
|
|
if (this.nfc.supported) {
|
|
|
|
await this.setupNFC();
|
|
|
|
}
|
2023-03-02 08:34:15 +01:00
|
|
|
updateLanguageSelect();
|
2023-11-30 10:17:23 +01:00
|
|
|
|
2022-11-24 00:53:32 +01:00
|
|
|
window.parent.postMessage('loaded', '*');
|
|
|
|
},
|
2023-11-30 10:17:23 +01:00
|
|
|
beforeDestroy () {
|
|
|
|
if (this.nfc.readerAbortController) {
|
|
|
|
this.nfc.readerAbortController.abort()
|
|
|
|
}
|
|
|
|
},
|
2022-11-24 00:53:32 +01:00
|
|
|
methods: {
|
2024-04-30 11:26:27 +02:00
|
|
|
asNumber,
|
2022-11-24 00:53:32 +01:00
|
|
|
changePaymentMethod (id) { // payment method or plugin id
|
|
|
|
if (this.pmId !== id) {
|
|
|
|
this.paymentMethodId = id;
|
|
|
|
this.fetchData();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
changeLanguage (e) {
|
2023-03-01 07:49:21 +01:00
|
|
|
updateLanguage(e.target.value);
|
2022-11-24 00:53:32 +01:00
|
|
|
},
|
|
|
|
padTime (val) {
|
|
|
|
return val.toString().padStart(2, '0');
|
|
|
|
},
|
|
|
|
close () {
|
|
|
|
window.parent.postMessage('close', '*');
|
|
|
|
},
|
|
|
|
updateTimer () {
|
|
|
|
this.remainingSeconds = Math.floor((this.endDate.getTime() - new Date().getTime())/1000);
|
|
|
|
if (this.isActive) {
|
|
|
|
setTimeout(this.updateTimer, 500);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
listenIn () {
|
2023-07-11 21:56:13 +02:00
|
|
|
const self = this;
|
2022-11-24 00:53:32 +01:00
|
|
|
let socket = null;
|
|
|
|
const updateFn = this.fetchData;
|
|
|
|
const supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
|
|
|
|
if (supportsWebSockets) {
|
|
|
|
const protocol = window.location.protocol.replace('http', 'ws');
|
|
|
|
const wsUri = `${protocol}//${window.location.host}${statusWsUrl}`;
|
|
|
|
try {
|
|
|
|
socket = new WebSocket(wsUri);
|
|
|
|
socket.onmessage = async function (e) {
|
|
|
|
if (e.data !== 'ping') await updateFn();
|
|
|
|
};
|
|
|
|
socket.onerror = function (e) {
|
|
|
|
console.error('Error while connecting to websocket for invoice notifications (callback):', e);
|
|
|
|
};
|
2023-07-11 21:56:13 +02:00
|
|
|
socket.onclose = function () {
|
|
|
|
self.pollUpdates(2000, socket);
|
|
|
|
};
|
2022-11-24 00:53:32 +01:00
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
console.error('Error while connecting to websocket for invoice notifications', e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// fallback in case there is no websocket support
|
2023-03-27 12:12:11 +02:00
|
|
|
if (!socket || socket.readyState !== 1) {
|
|
|
|
this.pollUpdates(2000, socket)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
listenForConfirmations () {
|
|
|
|
this.pollUpdates(30000);
|
|
|
|
},
|
|
|
|
pollUpdates (interval, socket) {
|
|
|
|
const self = this;
|
|
|
|
const updateFn = this.fetchData;
|
|
|
|
if (self.pollTimeoutID) {
|
|
|
|
clearTimeout(self.pollTimeoutID);
|
|
|
|
}
|
|
|
|
(function pollFn() {
|
|
|
|
self.pollTimeoutID = setTimeout(async function () {
|
|
|
|
if (!socket || socket.readyState !== 1) {
|
2022-11-24 00:53:32 +01:00
|
|
|
await updateFn();
|
2023-03-27 12:12:11 +02:00
|
|
|
pollFn();
|
2022-11-24 00:53:32 +01:00
|
|
|
}
|
2023-03-27 12:12:11 +02:00
|
|
|
}, interval);
|
2022-11-24 00:53:32 +01:00
|
|
|
})();
|
|
|
|
},
|
|
|
|
async fetchData () {
|
|
|
|
if (this.isPluginPaymentMethod) return;
|
|
|
|
|
|
|
|
const url = `${statusUrl}&paymentMethodId=${this.pmId}`;
|
|
|
|
const response = await fetch(url);
|
|
|
|
if (response.ok) {
|
|
|
|
const data = await response.json();
|
|
|
|
this.updateData(data);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
updateData (data) {
|
|
|
|
if (this.srvModel.status !== data.status) {
|
|
|
|
const { invoiceId } = this.srvModel;
|
|
|
|
const { status } = data;
|
|
|
|
window.parent.postMessage({ invoiceId, status }, '*');
|
|
|
|
}
|
|
|
|
const newEnd = new Date();
|
|
|
|
newEnd.setSeconds(newEnd.getSeconds() + data.expirationSeconds);
|
|
|
|
this.endDate = newEnd;
|
|
|
|
|
|
|
|
// updating ui
|
|
|
|
this.srvModel = data;
|
|
|
|
},
|
|
|
|
replaceNewlines (value) {
|
|
|
|
return value ? value.replace(/\n/ig, '<br>') : '';
|
2023-03-13 02:09:56 +01:00
|
|
|
},
|
2023-07-24 15:57:24 +02:00
|
|
|
playSound (soundName) {
|
2024-09-02 11:20:29 +02:00
|
|
|
const audioBuffer = this[soundName + 'Sound'];
|
|
|
|
if (!audioBuffer || this.audioContext.state === 'suspended') return;
|
|
|
|
const source = this.audioContext.createBufferSource();
|
|
|
|
source.buffer = audioBuffer;
|
|
|
|
source.connect(this.audioContext.destination);
|
|
|
|
source.start();
|
2023-07-24 15:57:24 +02:00
|
|
|
},
|
2023-03-13 02:09:56 +01:00
|
|
|
async celebratePayment (duration) {
|
2023-07-24 15:57:24 +02:00
|
|
|
// sound
|
|
|
|
this.playSound('payment')
|
|
|
|
// confetti
|
2023-03-13 02:09:56 +01:00
|
|
|
const $confettiEl = document.getElementById('confetti')
|
|
|
|
if (window.confetti && $confettiEl && !$confettiEl.dataset.running) {
|
|
|
|
$confettiEl.dataset.running = true;
|
|
|
|
await window.confetti($confettiEl, {
|
|
|
|
duration,
|
|
|
|
spread: 90,
|
|
|
|
stagger: 5,
|
|
|
|
elementCount: 121,
|
|
|
|
colors: ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"]
|
|
|
|
});
|
|
|
|
delete $confettiEl.dataset.running;
|
|
|
|
}
|
2023-07-24 15:57:24 +02:00
|
|
|
},
|
|
|
|
async prepareSound (url) {
|
|
|
|
const response = await fetch(url)
|
|
|
|
if (!response.ok) return console.error(`Could not load payment sound, HTTP error ${response.status}`);
|
|
|
|
const arrayBuffer = await response.arrayBuffer();
|
2024-09-02 11:20:29 +02:00
|
|
|
return await this.audioContext.decodeAudioData(arrayBuffer);
|
2023-11-30 10:17:23 +01:00
|
|
|
},
|
|
|
|
async setupNFC () {
|
|
|
|
try {
|
|
|
|
this.$set(this.nfc, 'permissionGranted', navigator.permissions && (await navigator.permissions.query({ name: 'nfc' })).state === 'granted');
|
|
|
|
} catch (e) {}
|
|
|
|
if (this.nfc.permissionGranted) {
|
|
|
|
await this.startNFCScan();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async startNFCScan () {
|
|
|
|
if (this.nfc.scanning) return;
|
|
|
|
this.$set(this.nfc, 'scanning', true);
|
|
|
|
try {
|
|
|
|
const inModal = window.self !== window.top;
|
|
|
|
const ndef = inModal ? new NDEFReaderWrapper() : new NDEFReader();
|
|
|
|
this.nfc.readerAbortController = new AbortController()
|
|
|
|
this.nfc.readerAbortController.signal.onabort = () => {
|
|
|
|
this.$set(this.nfc, 'scanning', false);
|
|
|
|
};
|
|
|
|
|
|
|
|
await ndef.scan({ signal: this.nfc.readerAbortController.signal })
|
2024-09-02 11:20:29 +02:00
|
|
|
ndef.onreadingerror = () => this.handleNFCError('Could not read NFC tag')
|
2023-11-30 10:17:23 +01:00
|
|
|
ndef.onreading = async ({ message }) => {
|
|
|
|
const record = message.records[0]
|
2024-09-02 11:20:29 +02:00
|
|
|
if (record && record.data) {
|
|
|
|
const textDecoder = new TextDecoder('utf-8')
|
|
|
|
const decoded = textDecoder.decode(record.data)
|
|
|
|
this.$emit('read-nfc-data', decoded)
|
|
|
|
} else {
|
|
|
|
this.handleNFCError('Could not read NFC tag: No data')
|
|
|
|
}
|
2023-11-30 10:17:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (inModal) {
|
|
|
|
// receive messages from iframe
|
|
|
|
window.addEventListener('message', async event => {
|
|
|
|
// deny messages from other origins
|
|
|
|
if (event.origin !== window.location.origin) return
|
|
|
|
|
|
|
|
const { action, data } = event.data
|
|
|
|
switch (action) {
|
|
|
|
case 'nfc:data':
|
|
|
|
this.$emit('read-nfc-data', data)
|
|
|
|
break;
|
|
|
|
case 'nfc:error':
|
|
|
|
this.handleNFCError('Could not read NFC tag')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// we came here, so the user must have allowed NFC access
|
|
|
|
this.$set(this.nfc, 'permissionGranted', true);
|
|
|
|
} catch (error) {
|
|
|
|
this.handleNFCError(`NFC scan failed: ${error}`);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
handleNFCData() { // child component reports it is handling the data
|
|
|
|
this.playSound('nfcRead');
|
|
|
|
this.$set(this.nfc, 'submitting', true);
|
|
|
|
this.$set(this.nfc, 'errorMessage', null);
|
|
|
|
},
|
|
|
|
handleNFCResult() { // child component reports result for handling the data
|
|
|
|
this.$set(this.nfc, 'submitting', false);
|
|
|
|
},
|
2024-09-02 11:20:29 +02:00
|
|
|
handleNFCError(message) {
|
|
|
|
// internal or via child component reporting failure of handling the data
|
2023-11-30 10:17:23 +01:00
|
|
|
this.playSound('error');
|
|
|
|
this.$set(this.nfc, 'submitting', false);
|
|
|
|
this.$set(this.nfc, 'errorMessage', message);
|
|
|
|
const $nfc = document.getElementById('NFC');
|
|
|
|
if ($nfc) {
|
|
|
|
$nfc.scrollIntoView({ block: 'end', inline: 'center', behavior: 'smooth' });
|
|
|
|
}
|
2022-11-24 00:53:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
i18next
|
|
|
|
.use(window.i18nextHttpBackend)
|
|
|
|
.init({
|
|
|
|
backend: {
|
|
|
|
loadPath: i18nUrl
|
|
|
|
},
|
|
|
|
lng: startingLanguage,
|
|
|
|
fallbackLng: fallbackLanguage,
|
|
|
|
nsSeparator: false,
|
|
|
|
keySeparator: false,
|
|
|
|
load: 'currentOnly'
|
|
|
|
}, initApp);
|