Use custom websocket to avoid needing admin privileges.
This commit is contained in:
@@ -46,9 +46,9 @@ export class CardElementManager {
|
||||
this._menuToggleCallback();
|
||||
}
|
||||
|
||||
public update(): void {
|
||||
public update = (): void => {
|
||||
this._element.requestUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
public hasUpdated(): boolean {
|
||||
return this._element.hasUpdated;
|
||||
@@ -69,6 +69,17 @@ export class CardElementManager {
|
||||
this._api.getKeyboardStateManager().initialize();
|
||||
this._api.getDefaultManager().initialize();
|
||||
|
||||
this._api
|
||||
.getHASSManager()
|
||||
.getStateWatcher()
|
||||
?.subscribe(this.update, [
|
||||
...(this._api.getConfigManager().getConfig()?.view.render_entities ?? []),
|
||||
|
||||
// Refresh the card if media player state changes:
|
||||
// https://github.com/dermotduffy/frigate-hass-card/issues/881
|
||||
...(this._api.getMediaPlayerManager().getMediaPlayers() ?? []),
|
||||
]);
|
||||
|
||||
// Whether or not the card is in panel mode on the dashboard.
|
||||
setOrRemoveAttribute(this._element, isCardInPanel(this._element), 'panel');
|
||||
setOrRemoveAttribute(this._element, true, 'tabindex', '0');
|
||||
@@ -129,6 +140,7 @@ export class CardElementManager {
|
||||
this._api.getKeyboardStateManager().uninitialize();
|
||||
this._api.getActionsManager().uninitialize();
|
||||
this._api.getDefaultManager().uninitialize();
|
||||
this._api.getHASSManager().getStateWatcher()?.unsubscribe(this.update);
|
||||
|
||||
// Uninitialize cameras to cause them to reinitialize on
|
||||
// reconnection, to ensure the state subscription/unsubscription works
|
||||
|
||||
@@ -20,7 +20,7 @@ import { ConfigManager } from './config/config-manager';
|
||||
import { DownloadManager } from './download-manager';
|
||||
import { ExpandManager } from './expand-manager';
|
||||
import { FullscreenManager } from './fullscreen-manager';
|
||||
import { HASSManager } from './hass-manager';
|
||||
import { HASSManager } from './hass/hass-manager';
|
||||
import { InitializationManager } from './initialization-manager';
|
||||
import { InteractionManager } from './interaction-manager';
|
||||
import { MediaLoadedInfoManager } from './media-info-manager';
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import PQueue from 'p-queue';
|
||||
import { DestroyCallback, subscribeToTrigger } from '../utils/ha';
|
||||
import { createGeneralAction } from '../utils/action';
|
||||
import { isActionAllowedBasedOnInteractionState } from '../utils/interaction-mode';
|
||||
import { Timer } from '../utils/timer';
|
||||
import { CardDefaultManagerAPI } from './types';
|
||||
import { createGeneralAction } from '../utils/action';
|
||||
|
||||
/**
|
||||
* Manages automated resetting to the default view.
|
||||
@@ -11,7 +10,6 @@ import { createGeneralAction } from '../utils/action';
|
||||
export class DefaultManager {
|
||||
protected _timer = new Timer();
|
||||
protected _api: CardDefaultManagerAPI;
|
||||
protected _unsubscribeCallback: DestroyCallback | null = null;
|
||||
protected _initializationLimit = new PQueue({ concurrency: 1 });
|
||||
|
||||
constructor(api: CardDefaultManagerAPI) {
|
||||
@@ -47,8 +45,7 @@ export class DefaultManager {
|
||||
|
||||
public uninitialize(): void {
|
||||
this._timer.stop();
|
||||
this._unsubscribeCallback?.();
|
||||
this._unsubscribeCallback = null;
|
||||
this._api.getHASSManager().getStateWatcher().unsubscribe(this._stateChangeHandler);
|
||||
this._api.getAutomationsManager().deleteAutomations(this);
|
||||
}
|
||||
|
||||
@@ -59,19 +56,11 @@ export class DefaultManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._unsubscribeCallback) {
|
||||
await this._unsubscribeCallback();
|
||||
}
|
||||
|
||||
this._unsubscribeCallback = await subscribeToTrigger(
|
||||
hass,
|
||||
() => this._setToDefaultIfAllowed(),
|
||||
{
|
||||
entityID: config.entities,
|
||||
platform: 'state',
|
||||
stateOnly: true,
|
||||
},
|
||||
);
|
||||
this._api.getHASSManager().getStateWatcher().unsubscribe(this._stateChangeHandler);
|
||||
this._api
|
||||
.getHASSManager()
|
||||
.getStateWatcher()
|
||||
.subscribe(this._stateChangeHandler, config.entities);
|
||||
|
||||
// If the timer is running, restart it with the newly configured timer.
|
||||
if (this._timer.isRunning()) {
|
||||
@@ -82,6 +71,10 @@ export class DefaultManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected _stateChangeHandler = (): void => {
|
||||
this._setToDefaultIfAllowed();
|
||||
};
|
||||
|
||||
protected _setToDefaultIfAllowed(): void {
|
||||
if (this._isAutomatedUpdateAllowed()) {
|
||||
this._api.getViewManager().setViewDefault();
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { localize } from '../localize/localize';
|
||||
import { ExtendedHomeAssistant } from '../types';
|
||||
import { hasHAConnectionStateChanged, isHassDifferent } from '../utils/ha';
|
||||
import { CardHASSAPI } from './types';
|
||||
import { localize } from '../../localize/localize';
|
||||
import { ExtendedHomeAssistant } from '../../types';
|
||||
import { hasHAConnectionStateChanged } from '../../utils/ha';
|
||||
import { CardHASSAPI } from '../types';
|
||||
import { StateWatcher, StateWatcherSubscriptionInterface } from './state-watcher';
|
||||
|
||||
export class HASSManager {
|
||||
protected _hass: ExtendedHomeAssistant | null = null;
|
||||
protected _api: CardHASSAPI;
|
||||
protected _stateWatcher: StateWatcher = new StateWatcher();
|
||||
|
||||
constructor(api: CardHASSAPI) {
|
||||
this._api = api;
|
||||
@@ -15,6 +17,10 @@ export class HASSManager {
|
||||
return this._hass;
|
||||
}
|
||||
|
||||
public getStateWatcher(): StateWatcherSubscriptionInterface {
|
||||
return this._stateWatcher;
|
||||
}
|
||||
|
||||
public setHASS(hass?: ExtendedHomeAssistant | null): void {
|
||||
if (hasHAConnectionStateChanged(this._hass, hass)) {
|
||||
if (!hass?.connected) {
|
||||
@@ -36,18 +42,6 @@ export class HASSManager {
|
||||
const oldHass = this._hass;
|
||||
this._hass = hass;
|
||||
|
||||
if (
|
||||
isHassDifferent(this._hass, oldHass, [
|
||||
...(this._api.getConfigManager().getConfig()?.view.render_entities ?? []),
|
||||
|
||||
// Refresh the card if media player state changes:
|
||||
// https://github.com/dermotduffy/frigate-hass-card/issues/881
|
||||
...this._api.getMediaPlayerManager().getMediaPlayers(),
|
||||
])
|
||||
) {
|
||||
this._api.getCardElementManager().update();
|
||||
}
|
||||
|
||||
if (this._api.getConditionsManager().hasHAStateConditions()) {
|
||||
this._api.getConditionsManager().setState({
|
||||
state: this._hass.states,
|
||||
@@ -57,5 +51,7 @@ export class HASSManager {
|
||||
|
||||
// Dark mode may depend on HASS.
|
||||
this._api.getStyleManager().setLightOrDarkMode();
|
||||
|
||||
this._stateWatcher.setHASS(oldHass, hass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
|
||||
import { getHassDifferences, HassStateDifference } from '../../utils/ha';
|
||||
|
||||
type StateWatcherCallback = (difference: HassStateDifference) => void;
|
||||
|
||||
export interface StateWatcherSubscriptionInterface {
|
||||
subscribe(callback: StateWatcherCallback, entityIDs: string[]): void;
|
||||
unsubscribe(callback: StateWatcherCallback): void;
|
||||
}
|
||||
|
||||
export class StateWatcher implements StateWatcherSubscriptionInterface {
|
||||
protected _watcherCallbacks = new Map<StateWatcherCallback, string[]>();
|
||||
|
||||
public setHASS(oldHass: HomeAssistant | null, hass: HomeAssistant): void {
|
||||
if (!oldHass) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [callback, entityIDs] of this._watcherCallbacks.entries()) {
|
||||
const differences = getHassDifferences(hass, oldHass, entityIDs, {
|
||||
stateOnly: true,
|
||||
firstOnly: true,
|
||||
});
|
||||
if (differences.length) {
|
||||
callback(differences[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls callback when the state of any of the entities changes. The callback is
|
||||
* called with the state difference of the first entity that changed.
|
||||
* @param callback The callback.
|
||||
* @param entityIDs An array of entity IDs to watch.
|
||||
*/
|
||||
public subscribe(callback: StateWatcherCallback, entityIDs: string[]): boolean {
|
||||
if (!entityIDs.length) {
|
||||
return false;
|
||||
}
|
||||
if (this._watcherCallbacks.has(callback)) {
|
||||
this._watcherCallbacks.get(callback)?.push(...entityIDs);
|
||||
} else {
|
||||
this._watcherCallbacks.set(callback, entityIDs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public unsubscribe(callback: StateWatcherCallback): void {
|
||||
this._watcherCallbacks.delete(callback);
|
||||
}
|
||||
}
|
||||
@@ -42,16 +42,17 @@ export class MessageManager {
|
||||
}
|
||||
}
|
||||
|
||||
public setErrorIfHigherPriority(error: unknown): void {
|
||||
public setErrorIfHigherPriority(error: unknown, prefix?: string): void {
|
||||
// This object should accept unknown objects to be able to seamlessly
|
||||
// process arguments to catch() which can only be unknown/any.
|
||||
if (!(error instanceof Error)) {
|
||||
// process arguments to catch() which can only be unknown/any. HA may throw
|
||||
// non Error() based errors.
|
||||
if (!error || typeof error !== 'object' || !('message' in error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
errorToConsole(error);
|
||||
this.setMessageIfHigherPriority({
|
||||
message: error.message,
|
||||
message: prefix ? `${prefix}: ${error.message}` : error.message,
|
||||
type: 'error',
|
||||
...(error instanceof FrigateCardError && { context: error.context }),
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ export class StyleManager {
|
||||
this._api = api;
|
||||
}
|
||||
|
||||
public setLightOrDarkMode(): void {
|
||||
public setLightOrDarkMode = (): void => {
|
||||
const config = this._api.getConfigManager().getConfig();
|
||||
const isDarkMode =
|
||||
config?.view.dark_mode === 'on' ||
|
||||
@@ -24,7 +24,7 @@ export class StyleManager {
|
||||
isDarkMode,
|
||||
'dark',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
public setExpandedMode(): void {
|
||||
const card = this._api.getCardElementManager().getElement();
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { DefaultManager } from './default-manager';
|
||||
import type { DownloadManager } from './download-manager';
|
||||
import type { ExpandManager } from './expand-manager';
|
||||
import type { FullscreenManager } from './fullscreen-manager';
|
||||
import type { HASSManager } from './hass-manager';
|
||||
import type { HASSManager } from './hass/hass-manager';
|
||||
import type { InitializationManager } from './initialization-manager';
|
||||
import type { InteractionManager } from './interaction-manager';
|
||||
import type { KeyboardStateManager } from './keyboard-state-manager';
|
||||
@@ -121,13 +121,16 @@ export interface CardDownloadAPI {
|
||||
export interface CardElementAPI {
|
||||
getActionsManager(): ActionsManager;
|
||||
getCameraManager(): CameraManager;
|
||||
getConfigManager(): ConfigManager;
|
||||
getDefaultManager(): DefaultManager;
|
||||
getExpandManager(): ExpandManager;
|
||||
getFullscreenManager(): FullscreenManager;
|
||||
getInitializationManager(): InitializationManager;
|
||||
getInteractionManager(): InteractionManager;
|
||||
getHASSManager(): HASSManager;
|
||||
getKeyboardStateManager(): KeyboardStateManager;
|
||||
getMediaLoadedInfoManager(): MediaLoadedInfoManager;
|
||||
getMediaPlayerManager(): MediaPlayerManager;
|
||||
getMicrophoneManager(): MicrophoneManager;
|
||||
getQueryStringManager(): QueryStringManager;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user