import { LitElement, TemplateResult, html } from "lit"; import { customElement, property, state } from "lit/decorators"; import { getAppKey } from "../data/notify_html5"; import { showPromptDialog } from "../dialogs/generic/show-dialog-box"; import { HaSwitch } from "./ha-switch"; import { HomeAssistant } from "../types"; import { fireEvent } from "../common/dom/fire_event"; export const pushSupported = "serviceWorker" in navigator && "PushManager" in window && (document.location.protocol === "https:" || document.location.hostname === "localhost" || document.location.hostname === "127.0.0.1"); @customElement("ha-push-notifications-toggle") class HaPushNotificationsToggle extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ type: Boolean }) public disabled!: boolean; @state() private _pushChecked: boolean = "Notification" in window && Notification.permission === "granted"; @state() private _loading: boolean = true; protected render(): TemplateResult { return html` `; } async connectedCallback() { super.connectedCallback(); if (!pushSupported) return; try { const reg = await navigator.serviceWorker.ready; if (!reg.pushManager) { return; } reg.pushManager.getSubscription().then((subscription) => { this._loading = false; this._pushChecked = !!subscription; }); } catch (err) { // We don't set loading to `false` so we remain disabled } } private _handlePushChange(ev: Event) { // Somehow this is triggered on Safari on page load causing // it to get into a loop and crash the page. if (!pushSupported) return; const pushnotifications = (ev.target as HaSwitch).checked; if (pushnotifications) { this._subscribePushNotifications(); } else { this._unsubscribePushNotifications(); } } private async _subscribePushNotifications() { const reg = await navigator.serviceWorker.ready; let sub; try { let browserName: string; if (navigator.userAgent.toLowerCase().indexOf("firefox") > -1) { browserName = "firefox"; } else { browserName = "chrome"; } const name = await showPromptDialog(this, { title: this.hass.localize( "ui.panel.profile.push_notifications.add_device_prompt.title" ), inputLabel: this.hass.localize( "ui.panel.profile.push_notifications.add_device_prompt.input_label" ), }); if (name == null) { this._pushChecked = false; return; } let applicationServerKey: Uint8Array | null; try { applicationServerKey = await getAppKey(this.hass); } catch (ex) { applicationServerKey = null; } if (applicationServerKey) { sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey, }); } else { sub = await reg.pushManager.subscribe({ userVisibleOnly: true }); } await this.hass.callApi("POST", "notify.html5", { subscription: sub, browser: browserName, name, }); } catch (err: any) { const message = err.message || "Notification registration failed."; if (sub) { await sub.unsubscribe(); } // eslint-disable-next-line console.error(err); fireEvent(this, "hass-notification", { message }); this._pushChecked = false; } } private async _unsubscribePushNotifications() { const reg = await navigator.serviceWorker.ready; try { const sub = await reg.pushManager.getSubscription(); if (!sub) return; await this.hass.callApi("DELETE", "notify.html5", { subscription: sub }); await sub.unsubscribe(); } catch (err: any) { const message = err.message || "Failed unsubscribing for push notifications."; // eslint-disable-next-line console.error("Error in unsub push", err); fireEvent(this, "hass-notification", { message }); this._pushChecked = true; } } } declare global { interface HTMLElementTagNameMap { "ha-push-notifications-toggle": HaPushNotificationsToggle; } }