diff --git a/src/layouts/app/connection-mixin.js b/src/layouts/app/connection-mixin.js index 6cd8663b27..f39af318e7 100644 --- a/src/layouts/app/connection-mixin.js +++ b/src/layouts/app/connection-mixin.js @@ -13,6 +13,7 @@ import EventsMixin from '../../mixins/events-mixin.js'; import { getState } from '../../util/ha-pref-storage.js'; import { getActiveTranslation } from '../../util/hass-translation.js'; +import { fetchWithAuth } from '../../util/fetch-with-auth.js'; import hassCallApi from '../../util/hass-call-api.js'; import computeStateName from '../../common/entity/compute_state_name.js'; import { subscribePanels } from '../../data/ws-panels'; @@ -89,23 +90,9 @@ export default superClass => throw err; } }, - callApi: async (method, path, parameters) => { - const host = window.location.protocol + '//' + window.location.host; - - try { - if (auth.expired) await auth.refreshAccessToken(); - } catch (err) { - if (err === ERR_INVALID_AUTH) { - // Trigger auth flow - location.reload(); - // ensure further JS is not executed - await new Promise(() => {}); - } - throw err; - } - - return await hassCallApi(host, auth, method, path, parameters); - }, + callApi: async (method, path, parameters) => + hassCallApi(auth, method, path, parameters), + fetchWithAuth: (path, init) => fetchWithAuth(auth, `${auth.data.hassUrl}${path}`, init), // For messages that do not get a response sendWS: (msg) => { // eslint-disable-next-line diff --git a/src/onboarding/ha-onboarding.js b/src/onboarding/ha-onboarding.js index efecdf2f5b..7ad39f5475 100644 --- a/src/onboarding/ha-onboarding.js +++ b/src/onboarding/ha-onboarding.js @@ -4,11 +4,8 @@ import '@polymer/paper-input/paper-input.js'; import '@polymer/paper-button/paper-button.js'; import { html } from '@polymer/polymer/lib/utils/html-tag.js'; import { PolymerElement } from '@polymer/polymer/polymer-element.js'; -import hassCallApi from '../util/hass-call-api.js'; import localizeLiteMixin from '../mixins/localize-lite-mixin.js'; -const callApi = (method, path, data) => hassCallApi('', {}, method, path, data); - class HaOnboarding extends localizeLiteMixin(PolymerElement) { static get template() { return html` @@ -141,12 +138,23 @@ class HaOnboarding extends localizeLiteMixin(PolymerElement) { this._errorMsg = ''; try { - await callApi('post', 'onboarding/users', { - name: this._name, - username: this._username, - password: this._password, + const response = await fetch('/api/onboarding/users', { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify({ + name: this._name, + username: this._username, + password: this._password, + }) }); + if (!response.ok) { + // eslint-disable-next-line + throw { + message: `Bad response from server: ${response.status}` + }; + } + document.location = '/'; } catch (err) { // eslint-disable-next-line diff --git a/src/panels/mailbox/ha-dialog-show-audio-message.js b/src/panels/mailbox/ha-dialog-show-audio-message.js new file mode 100644 index 0000000000..5335bcd69d --- /dev/null +++ b/src/panels/mailbox/ha-dialog-show-audio-message.js @@ -0,0 +1,158 @@ +import '@polymer/paper-button/paper-button.js'; +import '@polymer/paper-dialog/paper-dialog.js'; +import '@polymer/paper-spinner/paper-spinner.js'; +import { html } from '@polymer/polymer/lib/utils/html-tag.js'; +import { PolymerElement } from '@polymer/polymer/polymer-element.js'; + +import '../../resources/ha-style.js'; + +import LocalizeMixin from '../../mixins/localize-mixin.js'; + +/* + * @appliesMixin LocalizeMixin + */ +class HaDialogShowAudioMessage extends LocalizeMixin(PolymerElement) { + static get template() { + return html` + + +

+ [[localize('ui.panel.mailbox.playback_title')]] +
+ + +
+

+
+
+ + +
+
+`; + } + + static get properties() { + return { + hass: Object, + + _currentMessage: Object, + + // Error message when can't talk to server etc + _errorMsg: String, + + _loading: { + type: Boolean, + value: false, + }, + + _opened: { + type: Boolean, + value: false, + }, + }; + } + + showDialog({ hass, message }) { + this.hass = hass; + this._loading = true; + this._errorMsg = null; + this._currentMessage = message; + this._opened = true; + this.$.transcribe.innerText = message.message; + const platform = message.platform; + const mp3 = this.$.mp3; + mp3.src = null; + const url = `/api/mailbox/media/${platform}/${message.sha}`; + this.hass.fetchWithAuth(url) + .then((response) => { + if (response.ok) { + return response.blob(); + } + return Promise.reject({ + status: response.status, + statusText: response.statusText + }); + }) + .then((blob) => { + this._loading = false; + mp3.src = window.URL.createObjectURL(blob); + mp3.play(); + }) + .catch((err) => { + this._loading = false; + this._errorMsg = `Error loading audio: ${err.statusText}`; + }); + } + + openDeleteDialog() { + if (confirm(this.localize('ui.panel.mailbox.delete_prompt'))) { + this.deleteSelected(); + } + } + + deleteSelected() { + const msg = this._currentMessage; + this.hass.callApi('DELETE', `mailbox/delete/${msg.platform}/${msg.sha}`); + this._dialogDone(); + } + + _dialogDone() { + this.$.mp3.pause(); + this.setProperties({ + _currentMessage: null, + _errorMsg: null, + _loading: false, + _opened: false, + }); + } + + _openedChanged(ev) { + // Closed dialog by clicking on the overlay + // Check against dialogClosedCallback to make sure we didn't change + // programmatically + if (!ev.detail.value) { + this._dialogDone(); + } + } +} +customElements.define('ha-dialog-show-audio-message', HaDialogShowAudioMessage); diff --git a/src/panels/mailbox/ha-panel-mailbox.js b/src/panels/mailbox/ha-panel-mailbox.js index 028770317b..af7fdead90 100644 --- a/src/panels/mailbox/ha-panel-mailbox.js +++ b/src/panels/mailbox/ha-panel-mailbox.js @@ -3,7 +3,6 @@ import '@polymer/app-layout/app-header/app-header.js'; import '@polymer/app-layout/app-toolbar/app-toolbar.js'; import '@polymer/paper-button/paper-button.js'; import '@polymer/paper-card/paper-card.js'; -import '@polymer/paper-dialog/paper-dialog.js'; import '@polymer/paper-input/paper-textarea.js'; import '@polymer/paper-item/paper-item-body.js'; import '@polymer/paper-item/paper-item.js'; @@ -17,6 +16,8 @@ import '../../resources/ha-style.js'; import formatDateTime from '../../common/datetime/format_date_time.js'; import LocalizeMixin from '../../mixins/localize-mixin.js'; +let registeredDialog = false; + /* * @appliesMixin LocalizeMixin */ @@ -57,32 +58,8 @@ class HaPanelMailbox extends LocalizeMixin(PolymerElement) { display: flex; justify-content: space-between; } - paper-dialog { - border-radius: 2px; - } - paper-dialog p { - color: var(--secondary-text-color); - } - - #mp3dialog paper-icon-button { - float: right; - } @media all and (max-width: 450px) { - paper-dialog { - margin: 0; - width: 100%; - max-height: calc(100% - 64px); - - position: fixed !important; - bottom: 0px; - left: 0px; - right: 0px; - overflow: scroll; - border-bottom-left-radius: 0px; - border-bottom-right-radius: 0px; - } - .content { width: auto; padding: 0; @@ -128,28 +105,6 @@ class HaPanelMailbox extends LocalizeMixin(PolymerElement) { - - -

- [[localize('ui.panel.mailbox.playback_title')]] - -

-
-
- -
-
- - -

[[localize('ui.panel.mailbox.delete_prompt')]]

-
- [[localize('ui.common.cancel')]] - [[localize('ui.panel.mailbox.delete_button')]] -
-
`; } @@ -176,15 +131,19 @@ class HaPanelMailbox extends LocalizeMixin(PolymerElement) { _messages: { type: Array, }, - - currentMessage: { - type: Object, - }, }; } connectedCallback() { super.connectedCallback(); + if (!registeredDialog) { + registeredDialog = true; + this.fire('register-dialog', { + dialogShowEvent: 'show-audio-message-dialog', + dialogTag: 'ha-dialog-show-audio-message', + dialogImport: () => import('./ha-dialog-show-audio-message.js'), + }); + } this.hassChanged = this.hassChanged.bind(this); this.hass.connection.subscribeEvents(this.hassChanged, 'mailbox_updated') .then(function (unsub) { this._unsubEvents = unsub; }.bind(this)); @@ -209,35 +168,19 @@ class HaPanelMailbox extends LocalizeMixin(PolymerElement) { } openMP3Dialog(event) { - var platform = event.model.item.platform; - this.currentMessage = event.model.item; - this.$.mp3dialog.open(); - this.$.mp3src.src = '/api/mailbox/media/' + platform + '/' + event.model.item.sha; - this.$.transcribe.innerText = event.model.item.message; - this.$.mp3.load(); - this.$.mp3.play(); + this.fire('show-audio-message-dialog', { + hass: this.hass, + message: event.model.item, + }); } - _mp3Closed() { - this.$.mp3.pause(); - } - - openDeleteDialog() { - this.$.confirmdel.open(); - } - - deleteSelected() { - var msg = this.currentMessage; - this.hass.callApi('DELETE', 'mailbox/delete/' + msg.platform + '/' + msg.sha); - this.$.mp3dialog.close(); - } getMessages() { const items = this.platforms.map(function (platform) { - return this.hass.callApi('GET', 'mailbox/messages/' + platform).then(function (values) { - var platformItems = []; - var arrayLength = values.length; - for (var i = 0; i < arrayLength; i++) { - var datetime = formatDateTime(new Date(values[i].info.origtime * 1000)); + return this.hass.callApi('GET', `mailbox/messages/${platform}`).then(function (values) { + const platformItems = []; + const arrayLength = values.length; + for (let i = 0; i < arrayLength; i++) { + const datetime = formatDateTime(new Date(values[i].info.origtime * 1000)); platformItems.push({ timestamp: datetime, caller: values[i].info.callerid, @@ -251,15 +194,9 @@ class HaPanelMailbox extends LocalizeMixin(PolymerElement) { }); }.bind(this)); return Promise.all(items).then(function (platformItems) { - var arrayLength = items.length; - var final = []; - for (var i = 0; i < arrayLength; i++) { - final = final.concat(platformItems[i]); - } - final.sort(function (a, b) { + return [].concat(...platformItems).sort(function (a, b) { return new Date(b.timestamp) - new Date(a.timestamp); }); - return final; }); } diff --git a/src/util/fetch-with-auth.js b/src/util/fetch-with-auth.js new file mode 100644 index 0000000000..d51daca750 --- /dev/null +++ b/src/util/fetch-with-auth.js @@ -0,0 +1,9 @@ +export const fetchWithAuth = async (auth, input, init = {}) => { + if (auth.expired) await auth.refreshAccessToken(); + init.credentials = 'same-origin'; + if (!init.headers) { + init.headers = {}; + } + init.headers.authorization = `Bearer ${auth.accessToken}`; + return await fetch(input, init); +}; diff --git a/src/util/hass-call-api.js b/src/util/hass-call-api.js index 4d31f14054..6d6b696cf3 100644 --- a/src/util/hass-call-api.js +++ b/src/util/hass-call-api.js @@ -1,52 +1,57 @@ -export default function hassCallApi(host, auth, method, path, parameters) { - var url = host + '/api/' + path; +import { fetchWithAuth } from './fetch-with-auth.js'; - return new Promise(function (resolve, reject) { - var req = new XMLHttpRequest(); - req.open(method, url, true); - req.setRequestHeader('authorization', `Bearer ${auth.accessToken}`); +/* eslint-disable no-throw-literal */ - req.onload = function () { - let body = req.responseText; - const contentType = req.getResponseHeader('content-type'); +export default async function hassCallApi(auth, method, path, parameters) { + const url = `${auth.data.hassUrl}/api/${path}`; - if (contentType && contentType.indexOf('application/json') !== -1) { - try { - body = JSON.parse(req.responseText); - } catch (err) { - reject({ - error: 'Unable to parse JSON response', - status_code: req.status, - body: body, - }); - return; - } - } + const init = { + method: method, + headers: {}, + }; - if (req.status > 199 && req.status < 300) { - resolve(body); - } else { - reject({ - error: 'Response error: ' + req.status, - status_code: req.status, - body: body - }); - } + if (parameters) { + init.headers['Content-Type'] = 'application/json;charset=UTF-8'; + init.body = JSON.stringify(parameters); + } + + let response; + + try { + response = await fetchWithAuth(auth, url, init); + } catch (err) { + throw { + error: 'Request error', + status_code: undefined, + body: undefined, }; + } - req.onerror = function () { - reject({ - error: 'Request error', - status_code: req.status, - body: req.responseText, - }); - }; + let body = null; - if (parameters) { - req.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); - req.send(JSON.stringify(parameters)); - } else { - req.send(); + const contentType = response.headers.get('content-type'); + + if (contentType && contentType.includes('application/json')) { + try { + body = await response.json(); + } catch (err) { + throw { + error: 'Unable to parse JSON response', + status_code: err.status, + body: null, + }; } - }); + } else { + body = await response.text(); + } + + if (!response.ok) { + throw { + error: `Response error: ${response.status}`, + status_code: response.status, + body: body + }; + } + + return body; }