Fix authorization and display issues in mailbox view (#1610)

* Mailbox: Fix authorization issues.  Remove backdrop

* Fix linting issues

* Use HA Dialog system.  Fix authorization

* Add back missing backdrop

* Linting errors

* Use callApi.  Add error checking and spinner

* linting error

* more linting errors

* minor requested fixes

* Use let/const.  Fix lint issues

* Remove blob test that can never fail

* More let vs var fixes

* More minor requested fixes

* Rework code to use fetchWithAuth

* Async tweaks

* Lint

* Fix onboarding

* Add credentials for onboarding

* Lint
This commit is contained in:
PhracturedBlue 2018-09-11 02:33:57 -07:00 committed by Paulus Schoutsen
parent 0997274f29
commit 494e3dc62c
6 changed files with 254 additions and 150 deletions

View File

@ -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

View File

@ -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

View File

@ -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`
<style include="ha-style-dialog">
.error {
color: red;
}
@media all and (max-width: 500px) {
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;
}
}
paper-dialog {
border-radius: 2px;
}
paper-dialog p {
color: var(--secondary-text-color);
}
.icon {
float: right;
}
</style>
<paper-dialog id="mp3dialog" with-backdrop opened="{{_opened}}" on-opened-changed="_openedChanged">
<h2>
[[localize('ui.panel.mailbox.playback_title')]]
<div class='icon'>
<template is="dom-if" if="[[_loading]]">
<paper-spinner active></paper-spinner>
</template>
<template is="dom-if" if="[[!_loading]]">
<paper-icon-button
on-click='openDeleteDialog'
icon='hass:delete'
></paper-icon-button>
</template>
</div>
</h2>
<div id="transcribe"></div>
<div>
<template is="dom-if" if="[[_errorMsg]]">
<div class='error'>[[_errorMsg]]</div>
</template>
<audio id="mp3" preload="none" controls> <source id="mp3src" src="" type="audio/mpeg" /></audio>
</div>
</paper-dialog>
`;
}
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);

View File

@ -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) {
</paper-card>
</div>
</app-header-layout>
<paper-dialog with-backdrop id="mp3dialog" on-iron-overlay-closed="_mp3Closed">
<h2>
[[localize('ui.panel.mailbox.playback_title')]]
<paper-icon-button
on-click='openDeleteDialog'
icon='hass:delete'
></paper-icon-button>
</h2>
<div id="transcribe"></div>
<div>
<audio id="mp3" preload="none" controls> <source id="mp3src" src="" type="audio/mpeg" /></audio>
</div>
</paper-dialog>
<paper-dialog with-backdrop id="confirmdel">
<p>[[localize('ui.panel.mailbox.delete_prompt')]]</p>
<div class="buttons">
<paper-button dialog-dismiss>[[localize('ui.common.cancel')]]</paper-button>
<paper-button dialog-confirm autofocus on-click="deleteSelected">[[localize('ui.panel.mailbox.delete_button')]]</paper-button>
</div>
</paper-dialog>
`;
}
@ -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;
});
}

View File

@ -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);
};

View File

@ -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;
}