mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-17 14:26: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",
|
"es6-object-assign": "^1.1.0",
|
||||||
"eslint-import-resolver-webpack": "^0.10.1",
|
"eslint-import-resolver-webpack": "^0.10.1",
|
||||||
"fecha": "^2.3.3",
|
"fecha": "^2.3.3",
|
||||||
"home-assistant-js-websocket": "^3.1.5",
|
"home-assistant-js-websocket": "^3.2.4",
|
||||||
"intl-messageformat": "^2.2.0",
|
"intl-messageformat": "^2.2.0",
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
"js-yaml": "^3.12.0",
|
"js-yaml": "^3.12.0",
|
||||||
|
@ -6,6 +6,34 @@ import { Auth } from "home-assistant-js-websocket";
|
|||||||
const CALLBACK_SET_TOKEN = "externalAuthSetToken";
|
const CALLBACK_SET_TOKEN = "externalAuthSetToken";
|
||||||
const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken";
|
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) {
|
if (!window.externalApp && !window.webkit) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"External auth requires either externalApp or webkit defined on Window object."
|
"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 {
|
export default class ExternalAuth extends Auth {
|
||||||
constructor(hassUrl) {
|
constructor(hassUrl) {
|
||||||
super();
|
super({
|
||||||
|
|
||||||
this.data = {
|
|
||||||
hassUrl,
|
hassUrl,
|
||||||
|
clientId: "",
|
||||||
|
refresh_token: "",
|
||||||
access_token: "",
|
access_token: "",
|
||||||
|
expires_in: 0,
|
||||||
// This will trigger connection to do a refresh right away
|
// This will trigger connection to do a refresh right away
|
||||||
expires: 0,
|
expires: 0,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshAccessToken() {
|
public async refreshAccessToken() {
|
||||||
const responseProm = new Promise((resolve, reject) => {
|
const responseProm = new Promise<RefreshTokenResponse>(
|
||||||
|
(resolve, reject) => {
|
||||||
window[CALLBACK_SET_TOKEN] = (success, data) =>
|
window[CALLBACK_SET_TOKEN] = (success, data) =>
|
||||||
success ? resolve(data) : reject(data);
|
success ? resolve(data) : reject(data);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Allow promise to set resolve on window object.
|
// Allow promise to set resolve on window object.
|
||||||
await 0;
|
await 0;
|
||||||
@ -38,23 +69,18 @@ export default class ExternalAuth extends Auth {
|
|||||||
if (window.externalApp) {
|
if (window.externalApp) {
|
||||||
window.externalApp.getExternalAuth(callbackPayload);
|
window.externalApp.getExternalAuth(callbackPayload);
|
||||||
} else {
|
} else {
|
||||||
window.webkit.messageHandlers.getExternalAuth.postMessage(
|
window.webkit!.messageHandlers.getExternalAuth.postMessage(
|
||||||
callbackPayload
|
callbackPayload
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response we expect back:
|
|
||||||
// {
|
|
||||||
// "access_token": "qwere",
|
|
||||||
// "expires_in": 1800
|
|
||||||
// }
|
|
||||||
const tokens = await responseProm;
|
const tokens = await responseProm;
|
||||||
|
|
||||||
this.data.access_token = tokens.access_token;
|
this.data.access_token = tokens.access_token;
|
||||||
this.data.expires = tokens.expires_in * 1000 + Date.now();
|
this.data.expires = tokens.expires_in * 1000 + Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
async revoke() {
|
public async revoke() {
|
||||||
const responseProm = new Promise((resolve, reject) => {
|
const responseProm = new Promise((resolve, reject) => {
|
||||||
window[CALLBACK_REVOKE_TOKEN] = (success, data) =>
|
window[CALLBACK_REVOKE_TOKEN] = (success, data) =>
|
||||||
success ? resolve(data) : reject(data);
|
success ? resolve(data) : reject(data);
|
||||||
@ -68,7 +94,7 @@ export default class ExternalAuth extends Auth {
|
|||||||
if (window.externalApp) {
|
if (window.externalApp) {
|
||||||
window.externalApp.revokeExternalAuth(callbackPayload);
|
window.externalApp.revokeExternalAuth(callbackPayload);
|
||||||
} else {
|
} else {
|
||||||
window.webkit.messageHandlers.revokeExternalAuth.postMessage(
|
window.webkit!.messageHandlers.revokeExternalAuth.postMessage(
|
||||||
callbackPayload
|
callbackPayload
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,5 +1,18 @@
|
|||||||
|
import { AuthData } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
const storage = window.localStorage || {};
|
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.
|
// So that core.js and main app hit same shared object.
|
||||||
let tokenCache = window.__tokenCache;
|
let tokenCache = window.__tokenCache;
|
||||||
if (!tokenCache) {
|
if (!tokenCache) {
|
||||||
@ -15,19 +28,23 @@ export function askWrite() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveTokens(tokens) {
|
export function saveTokens(tokens: AuthData | null) {
|
||||||
tokenCache.tokens = tokens;
|
tokenCache.tokens = tokens;
|
||||||
if (tokenCache.writeEnabled) {
|
if (tokenCache.writeEnabled) {
|
||||||
try {
|
try {
|
||||||
storage.hassTokens = JSON.stringify(tokens);
|
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() {
|
export function enableWrite() {
|
||||||
tokenCache.writeEnabled = true;
|
tokenCache.writeEnabled = true;
|
||||||
|
if (tokenCache.tokens) {
|
||||||
saveTokens(tokenCache.tokens);
|
saveTokens(tokenCache.tokens);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function loadTokens() {
|
export function loadTokens() {
|
||||||
if (tokenCache.tokens === undefined) {
|
if (tokenCache.tokens === undefined) {
|
@ -1,4 +1,4 @@
|
|||||||
import { createCollection } from "home-assistant-js-websocket";
|
import { createCollection, Connection } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
const fetchNotifications = (conn) =>
|
const fetchNotifications = (conn) =>
|
||||||
conn.sendMessagePromise({
|
conn.sendMessagePromise({
|
||||||
@ -11,8 +11,11 @@ const subscribeUpdates = (conn, store) =>
|
|||||||
"persistent_notifications_updated"
|
"persistent_notifications_updated"
|
||||||
);
|
);
|
||||||
|
|
||||||
export const subscribeNotifications = (conn, onChange) =>
|
export const subscribeNotifications = (
|
||||||
createCollection(
|
conn: Connection,
|
||||||
|
onChange: (notifications: Notification[]) => void
|
||||||
|
) =>
|
||||||
|
createCollection<Notification[]>(
|
||||||
"_ntf",
|
"_ntf",
|
||||||
fetchNotifications,
|
fetchNotifications,
|
||||||
subscribeUpdates,
|
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,
|
subscribeEntities,
|
||||||
subscribeServices,
|
subscribeServices,
|
||||||
ERR_INVALID_AUTH,
|
ERR_INVALID_AUTH,
|
||||||
|
Auth,
|
||||||
|
Connection,
|
||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
|
|
||||||
import { loadTokens, saveTokens } from "../common/auth/token_storage";
|
import { loadTokens, saveTokens } from "../common/auth/token_storage";
|
||||||
import { subscribePanels } from "../data/ws-panels";
|
import { subscribePanels } from "../data/ws-panels";
|
||||||
import { subscribeThemes } from "../data/ws-themes";
|
import { subscribeThemes } from "../data/ws-themes";
|
||||||
import { subscribeUser } from "../data/ws-user";
|
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 hassUrl = `${location.protocol}//${location.host}`;
|
||||||
const isExternal = location.search.includes("external_auth=1");
|
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
|
// Clear url if we have been able to establish a connection
|
||||||
if (location.search.includes("auth_callback=1")) {
|
if (location.search.includes("auth_callback=1")) {
|
||||||
history.replaceState(null, null, location.pathname);
|
history.replaceState(null, "", location.pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { auth, conn };
|
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
|
// We can get invalid auth if auth tokens were stored that are no longer valid
|
||||||
// Clear stored tokens.
|
// Clear stored tokens.
|
||||||
if (!isExternal) saveTokens(null);
|
if (!isExternal) {
|
||||||
|
saveTokens(null);
|
||||||
|
}
|
||||||
auth = await authProm();
|
auth = await authProm();
|
||||||
const conn = await createConnection({ auth });
|
const conn = await createConnection({ auth });
|
||||||
return { auth, conn };
|
return { auth, conn };
|
||||||
@ -54,7 +65,9 @@ window.hassConnection = authProm().then(connProm);
|
|||||||
|
|
||||||
// Start fetching some of the data that we will need.
|
// Start fetching some of the data that we will need.
|
||||||
window.hassConnection.then(({ conn }) => {
|
window.hassConnection.then(({ conn }) => {
|
||||||
const noop = () => {};
|
const noop = () => {
|
||||||
|
// do nothing
|
||||||
|
};
|
||||||
subscribeEntities(conn, noop);
|
subscribeEntities(conn, noop);
|
||||||
subscribeConfig(conn, noop);
|
subscribeConfig(conn, noop);
|
||||||
subscribeServices(conn, noop);
|
subscribeServices(conn, noop);
|
||||||
@ -64,8 +77,12 @@ window.hassConnection.then(({ conn }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("error", (e) => {
|
window.addEventListener("error", (e) => {
|
||||||
const homeAssistant = document.querySelector("home-assistant");
|
const homeAssistant = document.querySelector("home-assistant") as any;
|
||||||
if (homeAssistant && homeAssistant.hass && homeAssistant.hass.callService) {
|
if (
|
||||||
|
homeAssistant &&
|
||||||
|
homeAssistant.hass &&
|
||||||
|
(homeAssistant.hass as HomeAssistant).callService
|
||||||
|
) {
|
||||||
homeAssistant.hass.callService("system_log", "write", {
|
homeAssistant.hass.callService("system_log", "write", {
|
||||||
logger: `frontend.${
|
logger: `frontend.${
|
||||||
__DEV__ ? "js_dev" : "js"
|
__DEV__ ? "js_dev" : "js"
|
30
src/types.ts
30
src/types.ts
@ -8,6 +8,12 @@ import {
|
|||||||
HassEntityAttributeBase,
|
HassEntityAttributeBase,
|
||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
var __DEV__: boolean;
|
||||||
|
var __BUILD__: "latest" | "es5";
|
||||||
|
var __VERSION__: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Credential {
|
export interface Credential {
|
||||||
auth_provider_type: string;
|
auth_provider_type: string;
|
||||||
auth_provider_id: string;
|
auth_provider_id: string;
|
||||||
@ -34,6 +40,11 @@ export interface Theme {
|
|||||||
"accent-color": string;
|
"accent-color": string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Themes {
|
||||||
|
default_theme: string;
|
||||||
|
themes: { [key: string]: Theme };
|
||||||
|
}
|
||||||
|
|
||||||
export interface Panel {
|
export interface Panel {
|
||||||
component_name: string;
|
component_name: string;
|
||||||
config?: { [key: string]: any };
|
config?: { [key: string]: any };
|
||||||
@ -42,22 +53,31 @@ export interface Panel {
|
|||||||
url_path: string;
|
url_path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Panels {
|
||||||
|
[name: string]: Panel;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Translation {
|
export interface Translation {
|
||||||
nativeName: string;
|
nativeName: string;
|
||||||
fingerprints: { [fragment: string]: string };
|
fingerprints: { [fragment: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Notification {
|
||||||
|
notification_id: string;
|
||||||
|
message: string;
|
||||||
|
title: string;
|
||||||
|
status: "read" | "unread";
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface HomeAssistant {
|
export interface HomeAssistant {
|
||||||
auth: Auth;
|
auth: Auth;
|
||||||
connection: Connection;
|
connection: Connection;
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
states: HassEntities;
|
states: HassEntities;
|
||||||
config: HassConfig;
|
config: HassConfig;
|
||||||
themes: {
|
themes: Themes;
|
||||||
default_theme: string;
|
panels: Panels;
|
||||||
themes: { [key: string]: Theme };
|
|
||||||
};
|
|
||||||
panels: { [key: string]: Panel };
|
|
||||||
panelUrl: string;
|
panelUrl: string;
|
||||||
language: string;
|
language: string;
|
||||||
resources: { [key: string]: any };
|
resources: { [key: string]: any };
|
||||||
|
@ -41,7 +41,7 @@ function createConfig(isProdBuild, latestBuild) {
|
|||||||
app: "./src/entrypoints/app.js",
|
app: "./src/entrypoints/app.js",
|
||||||
authorize: "./src/entrypoints/authorize.js",
|
authorize: "./src/entrypoints/authorize.js",
|
||||||
onboarding: "./src/entrypoints/onboarding.js",
|
onboarding: "./src/entrypoints/onboarding.js",
|
||||||
core: "./src/entrypoints/core.js",
|
core: "./src/entrypoints/core.ts",
|
||||||
compatibility: "./src/entrypoints/compatibility.js",
|
compatibility: "./src/entrypoints/compatibility.js",
|
||||||
"custom-panel": "./src/entrypoints/custom-panel.js",
|
"custom-panel": "./src/entrypoints/custom-panel.js",
|
||||||
"hass-icons": "./src/entrypoints/hass-icons.js",
|
"hass-icons": "./src/entrypoints/hass-icons.js",
|
||||||
@ -173,7 +173,7 @@ function createConfig(isProdBuild, latestBuild) {
|
|||||||
swDest: "service_worker.js",
|
swDest: "service_worker.js",
|
||||||
importWorkboxFrom: "local",
|
importWorkboxFrom: "local",
|
||||||
include: [
|
include: [
|
||||||
/core.js$/,
|
/core.ts$/,
|
||||||
/app.js$/,
|
/app.js$/,
|
||||||
/custom-panel.js$/,
|
/custom-panel.js$/,
|
||||||
/hass-icons.js$/,
|
/hass-icons.js$/,
|
||||||
|
@ -7426,10 +7426,10 @@ hoek@4.x.x:
|
|||||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
|
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
|
||||||
integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==
|
integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==
|
||||||
|
|
||||||
home-assistant-js-websocket@^3.1.5:
|
home-assistant-js-websocket@^3.2.4:
|
||||||
version "3.1.5"
|
version "3.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-3.1.5.tgz#56358660bbccb5f24e16f9197bbad12739f9b756"
|
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-3.2.4.tgz#0c4212e6ac57b60ed939aa420253994e4f9f0bef"
|
||||||
integrity sha512-BlANSkA9ob6wlUJCYS26n1tfMla+aJzB2rhhXSFi0iiTtWWfuOlcSOw8pxjeWhh1I9oAcbQ4qxhB7Np1EXG+Og==
|
integrity sha512-DaHpWIjJFLwTWNbHeGSCEUsbeyLUWAyWUgsYkiVWxzbfm+vqC5YaLNRu+Ma64SQYh5yGSYr7h25p2hip1GvyhQ==
|
||||||
|
|
||||||
home-or-tmp@^2.0.0:
|
home-or-tmp@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user