Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"es6-object-assign": "^1.1.0",
"eslint-import-resolver-webpack": "^0.10.1",
"fecha": "^2.3.3",
"home-assistant-js-websocket": "^3.1.5",
"home-assistant-js-websocket": "^3.2.4",
"intl-messageformat": "^2.2.0",
"jquery": "^3.3.1",
"js-yaml": "^3.12.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,34 @@ import { Auth } from "home-assistant-js-websocket";
const CALLBACK_SET_TOKEN = "externalAuthSetToken";
const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken";

interface BasePayload {
callback: string;
}

interface RefreshTokenResponse {
access_token: string;
expires_in: number;
}

declare global {
interface Window {
externalApp?: {
getExternalAuth(payload: BasePayload);
revokeExternalAuth(payload: BasePayload);
};
webkit?: {
messageHandlers: {
getExternalAuth: {
postMessage(payload: BasePayload);
};
revokeExternalAuth: {
postMessage(payload: BasePayload);
};
};
};
}
}

if (!window.externalApp && !window.webkit) {
throw new Error(
"External auth requires either externalApp or webkit defined on Window object."
Expand All @@ -14,21 +42,24 @@ if (!window.externalApp && !window.webkit) {

export default class ExternalAuth extends Auth {
constructor(hassUrl) {
super();

this.data = {
super({
hassUrl,
clientId: "",
refresh_token: "",
access_token: "",
expires_in: 0,
// This will trigger connection to do a refresh right away
expires: 0,
};
});
}

async refreshAccessToken() {
const responseProm = new Promise((resolve, reject) => {
window[CALLBACK_SET_TOKEN] = (success, data) =>
success ? resolve(data) : reject(data);
});
public async refreshAccessToken() {
const responseProm = new Promise<RefreshTokenResponse>(
(resolve, reject) => {
window[CALLBACK_SET_TOKEN] = (success, data) =>
success ? resolve(data) : reject(data);
}
);

// Allow promise to set resolve on window object.
await 0;
Expand All @@ -38,23 +69,18 @@ export default class ExternalAuth extends Auth {
if (window.externalApp) {
window.externalApp.getExternalAuth(callbackPayload);
} else {
window.webkit.messageHandlers.getExternalAuth.postMessage(
window.webkit!.messageHandlers.getExternalAuth.postMessage(
callbackPayload
);
}

// Response we expect back:
// {
// "access_token": "qwere",
// "expires_in": 1800
// }
const tokens = await responseProm;

this.data.access_token = tokens.access_token;
this.data.expires = tokens.expires_in * 1000 + Date.now();
}

async revoke() {
public async revoke() {
const responseProm = new Promise((resolve, reject) => {
window[CALLBACK_REVOKE_TOKEN] = (success, data) =>
success ? resolve(data) : reject(data);
Expand All @@ -68,7 +94,7 @@ export default class ExternalAuth extends Auth {
if (window.externalApp) {
window.externalApp.revokeExternalAuth(callbackPayload);
} else {
window.webkit.messageHandlers.revokeExternalAuth.postMessage(
window.webkit!.messageHandlers.revokeExternalAuth.postMessage(
callbackPayload
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { AuthData } from "home-assistant-js-websocket";

const storage = window.localStorage || {};

declare global {
interface Window {
__tokenCache: {
// undefined: we haven't loaded yet
// null: none stored
tokens?: AuthData | null;
writeEnabled?: boolean;
};
}
}

// So that core.js and main app hit same shared object.
let tokenCache = window.__tokenCache;
if (!tokenCache) {
Expand All @@ -15,18 +28,22 @@ export function askWrite() {
);
}

export function saveTokens(tokens) {
export function saveTokens(tokens: AuthData | null) {
tokenCache.tokens = tokens;
if (tokenCache.writeEnabled) {
try {
storage.hassTokens = JSON.stringify(tokens);
} catch (err) {} // eslint-disable-line
} catch (err) {
// write failed, ignore it. Happens if storage is full or private mode.
}
}
}

export function enableWrite() {
tokenCache.writeEnabled = true;
saveTokens(tokenCache.tokens);
if (tokenCache.tokens) {
saveTokens(tokenCache.tokens);
}
}

export function loadTokens() {
Expand Down
9 changes: 6 additions & 3 deletions src/data/ws-notifications.js → src/data/ws-notifications.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createCollection } from "home-assistant-js-websocket";
import { createCollection, Connection } from "home-assistant-js-websocket";

const fetchNotifications = (conn) =>
conn.sendMessagePromise({
Expand All @@ -11,8 +11,11 @@ const subscribeUpdates = (conn, store) =>
"persistent_notifications_updated"
);

export const subscribeNotifications = (conn, onChange) =>
createCollection(
export const subscribeNotifications = (
conn: Connection,
onChange: (notifications: Notification[]) => void
) =>
createCollection<Notification[]>(
"_ntf",
fetchNotifications,
subscribeUpdates,
Expand Down
10 changes: 0 additions & 10 deletions src/data/ws-panels.js

This file was deleted.

14 changes: 14 additions & 0 deletions src/data/ws-panels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createCollection, Connection } from "home-assistant-js-websocket";
import { Panels } from "../types";

export const subscribePanels = (
conn: Connection,
onChange: (panels: Panels) => void
) =>
createCollection<Panels>(
"_pnl",
() => conn.sendMessagePromise({ type: "get_panels" }),
undefined,
conn,
onChange
);
15 changes: 0 additions & 15 deletions src/data/ws-themes.js

This file was deleted.

25 changes: 25 additions & 0 deletions src/data/ws-themes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createCollection, Connection } from "home-assistant-js-websocket";
import { Themes } from "../types";

const fetchThemes = (conn) =>
conn.sendMessagePromise({
type: "frontend/get_themes",
});

const subscribeUpdates = (conn, store) =>
conn.subscribeEvents(
(event) => store.setState(event.data, true),
"themes_updated"
);

export const subscribeThemes = (
conn: Connection,
onChange: (themes: Themes) => void
) =>
createCollection<Themes>(
"_thm",
fetchThemes,
subscribeUpdates,
conn,
onChange
);
4 changes: 0 additions & 4 deletions src/data/ws-user.js

This file was deleted.

20 changes: 20 additions & 0 deletions src/data/ws-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
createCollection,
getUser,
Connection,
} from "home-assistant-js-websocket";
import { User } from "../types";

export const subscribeUser = (
conn: Connection,
onChange: (user: User) => void
) =>
createCollection<User>(
"_usr",
// the getUser command is mistyped in current verrsion of HAWS.
// Fixed in 3.2.5
() => (getUser(conn) as unknown) as Promise<User>,
undefined,
conn,
onChange
);
27 changes: 22 additions & 5 deletions src/entrypoints/core.js → src/entrypoints/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@ import {
subscribeEntities,
subscribeServices,
ERR_INVALID_AUTH,
Auth,
Connection,
} from "home-assistant-js-websocket";

import { loadTokens, saveTokens } from "../common/auth/token_storage";
import { subscribePanels } from "../data/ws-panels";
import { subscribeThemes } from "../data/ws-themes";
import { subscribeUser } from "../data/ws-user";
import { HomeAssistant } from "../types";

declare global {
interface Window {
hassConnection: Promise<{ auth: Auth; conn: Connection }>;
}
}

const hassUrl = `${location.protocol}//${location.host}`;
const isExternal = location.search.includes("external_auth=1");
Expand All @@ -33,7 +42,7 @@ const connProm = async (auth) => {

// Clear url if we have been able to establish a connection
if (location.search.includes("auth_callback=1")) {
history.replaceState(null, null, location.pathname);
history.replaceState(null, "", location.pathname);
}

return { auth, conn };
Expand All @@ -43,7 +52,9 @@ const connProm = async (auth) => {
}
// We can get invalid auth if auth tokens were stored that are no longer valid
// Clear stored tokens.
if (!isExternal) saveTokens(null);
if (!isExternal) {
saveTokens(null);
}
auth = await authProm();
const conn = await createConnection({ auth });
return { auth, conn };
Expand All @@ -54,7 +65,9 @@ window.hassConnection = authProm().then(connProm);

// Start fetching some of the data that we will need.
window.hassConnection.then(({ conn }) => {
const noop = () => {};
const noop = () => {
// do nothing
};
subscribeEntities(conn, noop);
subscribeConfig(conn, noop);
subscribeServices(conn, noop);
Expand All @@ -64,8 +77,12 @@ window.hassConnection.then(({ conn }) => {
});

window.addEventListener("error", (e) => {
const homeAssistant = document.querySelector("home-assistant");
if (homeAssistant && homeAssistant.hass && homeAssistant.hass.callService) {
const homeAssistant = document.querySelector("home-assistant") as any;
if (
homeAssistant &&
homeAssistant.hass &&
(homeAssistant.hass as HomeAssistant).callService
) {
homeAssistant.hass.callService("system_log", "write", {
logger: `frontend.${
__DEV__ ? "js_dev" : "js"
Expand Down
Loading