mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-16 05:46:35 +00:00
Convert auth to TS (#1976)
* Convert auth to TS * Lint * Update HA-JS-WS to 3.2.0 * Migrate ws collections to TS * Upgrade to latest HAWS * Bump HAWS * Lint * Add types to WS calls
This commit is contained in:
parent
bcbf0ba75a
commit
1ca242405b
@ -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",
|
||||
|
@ -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."
|
||||
@ -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;
|
||||
@ -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);
|
||||
@ -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
|
||||
);
|
||||
}
|
@ -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) {
|
||||
@ -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() {
|
@ -1,4 +1,4 @@
|
||||
import { createCollection } from "home-assistant-js-websocket";
|
||||
import { createCollection, Connection } from "home-assistant-js-websocket";
|
||||
|
||||
const fetchNotifications = (conn) =>
|
||||
conn.sendMessagePromise({
|
||||
@ -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,
|
@ -1,10 +0,0 @@
|
||||
import { createCollection } from "home-assistant-js-websocket";
|
||||
|
||||
export const subscribePanels = (conn, onChange) =>
|
||||
createCollection(
|
||||
"_pnl",
|
||||
(conn_) => conn_.sendMessagePromise({ type: "get_panels" }),
|
||||
null,
|
||||
conn,
|
||||
onChange
|
||||
);
|
14
src/data/ws-panels.ts
Normal file
14
src/data/ws-panels.ts
Normal file
@ -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
|
||||
);
|
@ -1,15 +0,0 @@
|
||||
import { createCollection } from "home-assistant-js-websocket";
|
||||
|
||||
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, onChange) =>
|
||||
createCollection("_thm", fetchThemes, subscribeUpdates, conn, onChange);
|
25
src/data/ws-themes.ts
Normal file
25
src/data/ws-themes.ts
Normal file
@ -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
|
||||
);
|
@ -1,4 +0,0 @@
|
||||
import { createCollection, getUser } from "home-assistant-js-websocket";
|
||||
|
||||
export const subscribeUser = (conn, onChange) =>
|
||||
createCollection("_usr", (conn_) => getUser(conn_), null, conn, onChange);
|
20
src/data/ws-user.ts
Normal file
20
src/data/ws-user.ts
Normal file
@ -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
|
||||
);
|
@ -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");
|
||||
@ -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 };
|
||||
@ -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 };
|
||||
@ -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);
|
||||
@ -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"
|
30
src/types.ts
30
src/types.ts
@ -8,6 +8,12 @@ import {
|
||||
HassEntityAttributeBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
|
||||
declare global {
|
||||
var __DEV__: boolean;
|
||||
var __BUILD__: "latest" | "es5";
|
||||
var __VERSION__: string;
|
||||
}
|
||||
|
||||
export interface Credential {
|
||||
auth_provider_type: string;
|
||||
auth_provider_id: string;
|
||||
@ -34,6 +40,11 @@ export interface Theme {
|
||||
"accent-color": string;
|
||||
}
|
||||
|
||||
export interface Themes {
|
||||
default_theme: string;
|
||||
themes: { [key: string]: Theme };
|
||||
}
|
||||
|
||||
export interface Panel {
|
||||
component_name: string;
|
||||
config?: { [key: string]: any };
|
||||
@ -42,22 +53,31 @@ export interface Panel {
|
||||
url_path: string;
|
||||
}
|
||||
|
||||
export interface Panels {
|
||||
[name: string]: Panel;
|
||||
}
|
||||
|
||||
export interface Translation {
|
||||
nativeName: string;
|
||||
fingerprints: { [fragment: string]: string };
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
notification_id: string;
|
||||
message: string;
|
||||
title: string;
|
||||
status: "read" | "unread";
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface HomeAssistant {
|
||||
auth: Auth;
|
||||
connection: Connection;
|
||||
connected: boolean;
|
||||
states: HassEntities;
|
||||
config: HassConfig;
|
||||
themes: {
|
||||
default_theme: string;
|
||||
themes: { [key: string]: Theme };
|
||||
};
|
||||
panels: { [key: string]: Panel };
|
||||
themes: Themes;
|
||||
panels: Panels;
|
||||
panelUrl: string;
|
||||
language: string;
|
||||
resources: { [key: string]: any };
|
||||
|
@ -41,7 +41,7 @@ function createConfig(isProdBuild, latestBuild) {
|
||||
app: "./src/entrypoints/app.js",
|
||||
authorize: "./src/entrypoints/authorize.js",
|
||||
onboarding: "./src/entrypoints/onboarding.js",
|
||||
core: "./src/entrypoints/core.js",
|
||||
core: "./src/entrypoints/core.ts",
|
||||
compatibility: "./src/entrypoints/compatibility.js",
|
||||
"custom-panel": "./src/entrypoints/custom-panel.js",
|
||||
"hass-icons": "./src/entrypoints/hass-icons.js",
|
||||
@ -173,7 +173,7 @@ function createConfig(isProdBuild, latestBuild) {
|
||||
swDest: "service_worker.js",
|
||||
importWorkboxFrom: "local",
|
||||
include: [
|
||||
/core.js$/,
|
||||
/core.ts$/,
|
||||
/app.js$/,
|
||||
/custom-panel.js$/,
|
||||
/hass-icons.js$/,
|
||||
|
@ -7426,10 +7426,10 @@ hoek@4.x.x:
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
|
||||
integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==
|
||||
|
||||
home-assistant-js-websocket@^3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-3.1.5.tgz#56358660bbccb5f24e16f9197bbad12739f9b756"
|
||||
integrity sha512-BlANSkA9ob6wlUJCYS26n1tfMla+aJzB2rhhXSFi0iiTtWWfuOlcSOw8pxjeWhh1I9oAcbQ4qxhB7Np1EXG+Og==
|
||||
home-assistant-js-websocket@^3.2.4:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-3.2.4.tgz#0c4212e6ac57b60ed939aa420253994e4f9f0bef"
|
||||
integrity sha512-DaHpWIjJFLwTWNbHeGSCEUsbeyLUWAyWUgsYkiVWxzbfm+vqC5YaLNRu+Ma64SQYh5yGSYr7h25p2hip1GvyhQ==
|
||||
|
||||
home-or-tmp@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user