Skip to content
Snippets Groups Projects
Select Git revision
  • main
  • rollup-refactor
  • demo protected
  • production protected
  • 6-send-more-error-context-with-this-sendsetpropertyevent
5 results

dbp-check-in-lit-element.js

Blame
  • dbp-check-in-lit-element.js 23.47 KiB
    import DBPLitElement from '@dbp-toolkit/common/dbp-lit-element';
    import {getStackTrace} from '@dbp-toolkit/common/error';
    import {send} from '@dbp-toolkit/common/notification';
    
    /**
     * Dummy function to mark strings as i18next keys for i18next-scanner
     *
     * @param {string} key
     * @param {object} [options]
     * @returns {string} The key param as is
     */
    function i18nKey(key, options) {
        return key;
    }
    
    export default class DBPCheckInLitElement extends DBPLitElement {
        constructor() {
            super();
            this.isSessionRefreshed = false;
            this.auth = {};
        }
    
        static get properties() {
            return {
                ...super.properties,
                auth: {type: Object},
            };
        }
    
        connectedCallback() {
            super.connectedCallback();
    
            this._loginStatus = '';
            this._loginState = [];
        }
    
        /**
         *  Request a re-render every time isLoggedIn()/isLoading() changes
         */
        _updateAuth() {
            this._loginStatus = this.auth['login-status'];
    
            let newLoginState = [this.isLoggedIn(), this.isLoading()];
            if (this._loginState.toString() !== newLoginState.toString()) {
                this.requestUpdate();
            }
            this._loginState = newLoginState;
        }
    
        update(changedProperties) {
            changedProperties.forEach((oldValue, propName) => {
                switch (propName) {
                    case 'auth':
                        this._updateAuth();
                        break;
                }
            });
    
            super.update(changedProperties);
        }
    
        /**
         * Returns if a person is set in or not
         *
         * @returns {boolean} true or false
         */
        isLoggedIn() {
            return this.auth.person !== undefined && this.auth.person !== null;
        }
    
        /**
         * Returns true if a person has successfully logged in
         *
         * @returns {boolean} true or false
         */
        isLoading() {
            if (this._loginStatus === 'logged-out') return false;
            return !this.isLoggedIn() && this.auth.token !== undefined;
        }
    
        /**
         * Send a fetch to given url with given options
         *
         * @param url
         * @param options
         * @returns {object} response (error or result)
         */
        async httpGetAsync(url, options) {
            let response = await fetch(url, options)
                .then((result) => {
                    if (!result.ok) throw result;
                    return result;
                })
                .catch((error) => {
                    return error;
                });
    
            return response;
        }
    
        /**
         * Gets the active checkins of the current logged in user
         *
         * @returns {object} response
         */
        async getActiveCheckIns() {
            let response;
    
            const options = {
                method: 'GET',
                headers: {
                    'Content-Type': 'application/ld+json',
                    Authorization: 'Bearer ' + this.auth.token,
                },
            };
            response = await this.httpGetAsync(
                this.entryPointUrl + '/checkin/check-in-actions',
                options
            );
            return response;
        }
    
        /**
         * Checkout at a specific location
         *
         * @param  locationHash
         * @param seatNumber (optional)
         * @returns {object} response
         */
        async sendCheckOutRequest(locationHash, seatNumber) {
            let response;
    
            let body = {
                location: '/checkin/places/' + locationHash,
                seatNumber: parseInt(seatNumber),
            };
    
            const options = {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/ld+json',
                    Authorization: 'Bearer ' + this.auth.token,
                },
                body: JSON.stringify(body),
            };
    
            response = await this.httpGetAsync(
                this.entryPointUrl + '/checkin/check-out-actions',
                options
            );
            return response;
        }
    
        /**
         * Checkin at a specific location
         *
         * @param  locationHash
         * @param seatNumber (optional)
         * @returns {object} response
         */
        async sendCheckInRequest(locationHash, seatNumber) {
            let body = {
                location: '/checkin/places/' + locationHash,
                seatNumber: parseInt(seatNumber),
            };
    
            const options = {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/ld+json',
                    Authorization: 'Bearer ' + this.auth.token,
                },
                body: JSON.stringify(body),
            };
    
            return await this.httpGetAsync(this.entryPointUrl + '/checkin/check-in-actions', options);
        }
    
        /**
         * Sends an analytics error event for the request of a room
         *
         * @param category
         * @param action
         * @param room
         * @param responseData
         */
        async sendErrorAnalyticsEvent(category, action, room, responseData = {}) {
            let responseBody = {};
    
            // Use a clone of responseData to prevent "Failed to execute 'json' on 'Response': body stream already read"
            // after this function, but still a TypeError will occur if .json() was already called before this function
            try {
                responseBody = await responseData.clone().json();
            } catch (e) {
                // NOP
            }
    
            const data = {
                status: responseData.status || '',
                url: responseData.url || '',
                description: responseBody['hydra:description'] || '',
                room: room,
                // get 5 items from the stack trace
                stack: getStackTrace().slice(1, 6),
            };
    
            // console.log("sendErrorEvent", data);
            this.sendSetPropertyEvent('analytics-event', {
                category: category,
                action: action,
                name: JSON.stringify(data),
            });
        }
    
        /**
         * Sends a Checkin request and do error handling and parsing
         * Include message for user when it worked or not
         * Saves invalid QR codes in array in this.wrongHash, so no multiple requests are send
         *
         * Possible paths: checkin, refresh session, invalid input, roomhash wrong, invalid seat number
         * no seat number, already checkedin, no permissions, any other errors, location hash empty
         *
         * @param locationHash
         * @param seatNumber
         * @param locationName
         * @param category
         * @param refresh (default = false)
         * @param setAdditional (default = false)
         */
        async doCheckIn(
            locationHash,
            seatNumber,
            locationName,
            category,
            refresh = false,
            setAdditional = false
        ) {
            const i18n = this._i18n;
    
            // Error: no location hash detected
            if (locationHash.length <= 0) {
                this.saveWrongHashAndNotify(
                    i18n.t('check-in.error-title'),
                    i18n.t('check-in.error-body'),
                    locationHash,
                    seatNumber
                );
                this.sendSetPropertyEvent('analytics-event', {
                    category: category,
                    action: 'CheckInFailedNoLocationHash',
                });
                return;
            }
    
            let responseData = await this.sendCheckInRequest(locationHash, seatNumber);
            await this.checkCheckinResponse(
                responseData,
                locationHash,
                seatNumber,
                locationName,
                category,
                refresh,
                setAdditional
            );
        }
    
        /**
         * Parse the response of a checkin or guest checkin request
         * Include message for user when it worked or not
         * Saves invalid QR codes in array in this.wrongHash, so no multiple requests are send
         *
         * Possible paths: checkin, refresh session, invalid input, roomhash wrong, invalid seat number
         * no seat number, already checkedin, no permissions, any other errors, location hash empty
         *
         * @param responseData
         * @param locationHash
         * @param seatNumber
         * @param locationName
         * @param category
         * @param refresh (default = false)
         * @param setAdditional (default = false)
         */
        async checkCheckinResponse(
            responseData,
            locationHash,
            seatNumber,
            locationName,
            category,
            refresh = false,
            setAdditional = false
        ) {
            const i18n = this._i18n;
    
            let status = responseData.status;
            let responseBody = await responseData.clone().json();
            switch (status) {
                case 201:
                    if (setAdditional) {
                        this.checkedInRoom = responseBody.location.name;
                        this.checkedInSeat = responseBody.seatNumber;
                        this.checkedInEndTime = responseBody.endTime;
                        this.identifier = responseBody['identifier'];
                        this.agent = responseBody['agent'];
                        this.stopQRReader();
                        this.isCheckedIn = true;
                        this._('#text-switch')._active = '';
                        locationName = responseBody.location.name;
                    }
    
                    if (refresh) {
                        send({
                            summary: i18n.t('check-in.success-refresh-title', {room: locationName}),
                            body: i18n.t('check-in.success-refresh-body', {room: locationName}),
                            type: 'success',
                            timeout: 5,
                        });
    
                        this.sendSetPropertyEvent('analytics-event', {
                            category: category,
                            action: 'RefreshSuccess',
                            name: locationName,
                        });
                    } else if (category === 'GuestCheckInRequest') {
                        send({
                            summary: i18n.t('guest-check-in.success-checkin-title', {
                                email: this.guestEmail,
                            }),
                            body: i18n.t('guest-check-in.success-checkin-body', {
                                email: this.guestEmail,
                            }),
                            type: 'success',
                            timeout: 5,
                        });
    
                        locationName = responseBody.location.name;
    
                        //Refresh necessary fields and values - keep time and place because it is nice to have for the next guest
                        this._('#email-field').value = '';
                        this.guestEmail = '';
                        this._('#select-seat').value = '';
                        this.seatNr = '';
                        this.isEmailSet = false;
                    } else {
                        send({
                            summary: i18n.t('check-in.success-checkin-title', {room: locationName}),
                            body:
                                seatNumber !== ''
                                    ? i18n.t('check-in.success-checkin-seat-body', {
                                          room: locationName,
                                          seat: seatNumber,
                                      })
                                    : i18n.t('check-in.success-checkin-body', {room: locationName}),
                            type: 'success',
                            timeout: 5,
                        });
                    }
                    this.sendSetPropertyEvent('analytics-event', {
                        category: category,
                        action: 'CheckInSuccess',
                        name: locationName,
                    });
                    await this.checkOtherCheckins(locationHash, seatNumber);
                    break;
    
                // Invalid Input
                case 400:
                    this.saveWrongHashAndNotify(
                        i18n.t('check-in.invalid-input-title'),
                        i18n.t('check-in.invalid-input-body'),
                        locationHash,
                        seatNumber
                    );
                    this.sendSetPropertyEvent('analytics-event', {
                        category: category,
                        action: 'CheckInFailed400',
                        name: locationName,
                    });
                    break;
    
                // No permissions
                case 403:
                    this.saveWrongHashAndNotify(
                        i18n.t('check-in.no-permission-title'),
                        i18n.t('check-in.no-permission-body'),
                        locationHash,
                        seatNumber
                    );
                    await this.sendErrorAnalyticsEvent(
                        category,
                        'CheckInFailed403',
                        locationName,
                        responseData
                    );
                    break;
    
                // Error if room not exists
                case 404:
                    this.saveWrongHashAndNotify(
                        i18n.t('check-in.hash-false-title'),
                        i18n.t('check-in.hash-false-body'),
                        locationHash,
                        seatNumber
                    );
                    this.sendSetPropertyEvent('analytics-event', {
                        category: category,
                        action: 'CheckInFailed404',
                        name: locationName,
                    });
                    break;
    
                // Can't checkin at provided place
                case 424:
                    await this.sendErrorAnalyticsEvent(
                        category,
                        'CheckInFailed424',
                        locationName,
                        responseData
                    );
                    await this.checkErrorDescription(
                        responseBody['hydra:description'],
                        locationHash,
                        seatNumber
                    );
                    break;
    
                // Error: something else doesn't work
                default:
                    this.saveWrongHashAndNotify(
                        i18n.t('check-in.error-title'),
                        i18n.t('check-in.error-body'),
                        locationHash,
                        seatNumber
                    );
                    this.sendSetPropertyEvent('analytics-event', {
                        category: category,
                        action: 'CheckInFailed',
                        name: locationName,
                    });
                    break;
            }
        }
    
        async checkErrorDescription(errorDescription, locationHash, seatNumber) {
            const i18n = this._i18n;
            console.log(errorDescription);
            switch (errorDescription) {
                // Error: invalid seat number
                case 'seatNumber must not exceed maximumPhysicalAttendeeCapacity of location!':
                case 'seatNumber too low!':
                    this.saveWrongHashAndNotify(
                        i18n.t('check-in.invalid-seatnr-title'),
                        i18n.t('check-in.invalid-seatnr-body'),
                        locationHash,
                        seatNumber
                    );
                    break;
    
                // Error: no seat numbers at provided room
                case "Location doesn't have any seats activated, you cannot set a seatNumber!":
                    this.saveWrongHashAndNotify(
                        i18n.t('check-in.no-seatnr-title'),
                        i18n.t('check-in.no-seatnr-body'),
                        locationHash,
                        seatNumber
                    );
                    break;
    
                // Error: no seat given
                case 'Location has seats activated, you need to set a seatNumber!':
                    this.saveWrongHashAndNotify(
                        i18n.t('guest-check-in.no-seatnr-title'),
                        i18n.t('guest-check-in.no-seatnr-body'),
                        locationHash,
                        seatNumber
                    );
                    break;
    
                // Error: you are already checked in here
                case 'There are already check-ins at the location with provided seat for the current user!':
                    await this.checkOtherCheckins(locationHash, seatNumber, true);
                    break;
    
                // Error: Email is already checked in here
                case 'There are already check-ins at the location with provided seat for the email address!':
                    send({
                        summary: i18n.t('guest-check-in.already-checkin-title'),
                        body: i18n.t('guest-check-in.already-checkin-body'),
                        type: 'warning',
                        timeout: 5,
                    });
                    break;
    
                default:
                    // Error: the endTime is too high
                    if (errorDescription.includes("The endDate can't be after ")) {
                        send({
                            summary: i18n.t('guest-check-in.max-time-title'),
                            body: i18n.t('guest-check-in.max-time-body'),
                            type: 'danger',
                            timeout: 5,
                        });
                    }
                    break;
            }
        }
    
        async checkOtherCheckins(locationHash, seatNumber, checkOtherSeats = false) {
            const i18n = this._i18n;
    
            let getActiveCheckInsResponse = await this.getActiveCheckIns();
            if (getActiveCheckInsResponse.status !== 200) {
                this.saveWrongHashAndNotify(
                    i18n.t('check-in.error-title'),
                    i18n.t('check-in.error-body'),
                    locationHash,
                    seatNumber
                );
                return;
            }
    
            let getActiveCheckInsBody = await getActiveCheckInsResponse.json();
            let checkInsArray = getActiveCheckInsBody['hydra:member'];
    
            let checkActiveCheckin = checkInsArray.filter(
                (x) =>
                    x.location.identifier === locationHash &&
                    x.seatNumber === (seatNumber === '' ? null : parseInt(seatNumber))
            );
            if (checkActiveCheckin.length === 0) return -1;
    
            this.checkinCount = checkInsArray.length;
            if (checkInsArray.length > 1) {
                this.status = {
                    summary: i18nKey('check-in.other-checkins-notification-title'),
                    body: i18nKey('check-in.other-checkins-notification-body', {count: 0}),
                    type: 'warning',
                    options: {count: checkInsArray.length - 1},
                };
            }
    
            if (!checkOtherSeats) return;
    
            let atActualRoomCheckIn = checkInsArray.filter(
                (x) =>
                    x.location.identifier === locationHash &&
                    x.seatNumber === (seatNumber === '' ? null : parseInt(seatNumber))
            );
            if (atActualRoomCheckIn.length !== 1) {
                this.saveWrongHashAndNotify(
                    i18n.t('check-in.error-title'),
                    i18n.t('check-in.error-body'),
                    locationHash,
                    seatNumber
                );
                return;
            }
    
            this.checkedInRoom = atActualRoomCheckIn[0].location.name;
            this.checkedInEndTime = atActualRoomCheckIn[0].endTime;
            this.checkedInSeat = atActualRoomCheckIn[0].seatNumber;
            this.stopQRReader();
            this.isCheckedIn = true;
            this._('#text-switch')._active = '';
    
            send({
                summary: i18n.t('check-in.already-checkin-title'),
                body: i18n.t('check-in.already-checkin-body'),
                type: 'warning',
                timeout: 5,
            });
        }
    
        saveWrongHashAndNotify(title, body, locationHash, seatNumber) {
            send({
                summary: title,
                body: body,
                type: 'danger',
                timeout: 5,
            });
            if (this.wrongHash) this.wrongHash.push(locationHash + '-' + seatNumber);
        }
    
        /**
         * Do a refresh: sends a checkout request, if this is successfully init a checkin
         * sends an error notification if something wen wrong
         *
         * @param locationHash
         * @param seatNumber
         * @param locationName
         * @param category
         * @param setAdditionals (default = false)
         */
        async refreshSession(locationHash, seatNumber, locationName, category, setAdditionals = false) {
            const i18n = this._i18n;
            let responseCheckout = await this.sendCheckOutRequest(locationHash, seatNumber);
            if (responseCheckout.status === 201) {
                await this.doCheckIn(
                    locationHash,
                    seatNumber,
                    locationName,
                    category,
                    true,
                    setAdditionals
                );
                return;
            }
            if (responseCheckout.status === 424) {
                //check if there is a checkin at wanted seat
                let check = await this.checkOtherCheckins(this.locationHash, this.seatNumber);
                if (check === -1) {
                    this.sendSetPropertyEvent('analytics-event', {
                        category: 'CheckInRequest',
                        action: 'CheckOutFailedNoCheckin',
                        name: this.checkedInRoom,
                    });
                    await this.doCheckIn(
                        locationHash,
                        seatNumber,
                        locationName,
                        category,
                        true,
                        setAdditionals
                    );
                    return;
                }
            }
    
            send({
                summary: i18n.t('check-in.refresh-failed-title'),
                body: i18n.t('check-in.refresh-failed-body', {room: locationName}),
                type: 'warning',
                timeout: 5,
            });
    
            await this.sendErrorAnalyticsEvent(
                'CheckInRequest',
                'RefreshFailed',
                locationName,
                responseCheckout
            );
        }
    
        /**
         * Parse a incoming date to a readable date
         *
         * @param date
         * @returns {string} readable date
         */
        getReadableDate(date) {
            const i18n = this._i18n;
            let newDate = new Date(date);
            let month = newDate.getMonth() + 1;
            return (
                i18n.t('check-in.checked-in-at', {
                    clock: newDate.getHours() + ':' + ('0' + newDate.getMinutes()).slice(-2),
                }) +
                ' ' +
                newDate.getDate() +
                '.' +
                month +
                '.' +
                newDate.getFullYear()
            );
        }
    
        async checkCheckoutResponse(
            response,
            locationHash,
            seatNumber,
            locationName,
            category,
            that = null,
            setAdditional = function () {}
        ) {
            const i18n = this._i18n;
    
            if (response.status === 201) {
                send({
                    summary: i18n.t('check-out.checkout-success-title'),
                    body: i18n.t('check-out.checkout-success-body', {room: locationName}),
                    type: 'success',
                    timeout: 5,
                });
    
                this.sendSetPropertyEvent('analytics-event', {
                    category: category,
                    action: 'CheckOutSuccess',
                    name: locationName,
                });
                setAdditional(that);
            } else if (response.status === 424) {
                //check if there is a checkin at wanted seat
                let check = await this.checkOtherCheckins(locationHash, seatNumber);
                if (check === -1) {
                    this.sendSetPropertyEvent('analytics-event', {
                        category: category,
                        action: 'CheckOutFailedNoCheckin',
                        name: locationName,
                    });
                    setAdditional(that);
                    send({
                        summary: i18n.t('check-out.already-checked-out-title'),
                        body: i18n.t('check-out.already-checked-out-body', {room: locationName}),
                        type: 'warning',
                        timeout: 5,
                    });
                }
            } else {
                send({
                    summary: i18n.t('check-out.checkout-failed-title'),
                    body: i18n.t('check-out.checkout-failed-body', {room: locationName}),
                    type: 'warning',
                    timeout: 5,
                });
                await this.sendErrorAnalyticsEvent(
                    category,
                    'CheckOutFailed',
                    this.checkedInRoom,
                    response
                );
            }
        }
    }