feat: Allow user-agent based conditions (#1821)

* Closes #1819
This commit is contained in:
Dermot Duffy
2025-01-10 07:41:19 -08:00
committed by GitHub
parent 7657bdf22c
commit 2678500670
14 changed files with 232 additions and 11 deletions
@@ -127,6 +127,10 @@ export class CardElementManager {
this._api.getQueryStringManager().requestExecution,
);
this._api.getConditionsManager()?.setState({
user_agent: navigator.userAgent,
});
// Manually request query string execute as the card will be
// disconnected/reconnected when dashboard 'tab' changes happen within HA.
this._api.getQueryStringManager().requestExecution();
+11
View File
@@ -19,6 +19,7 @@ import {
import { localize } from '../localize/localize';
import { FrigateCardError } from '../types';
import { desparsifyArrays } from '../utils/basic';
import { isCompanionApp } from '../utils/companion';
import { CardConditionAPI, KeysState } from './types';
interface MicrophoneConditionState {
@@ -39,6 +40,7 @@ interface ConditionState {
microphone?: MicrophoneConditionState;
user?: CurrentUser;
keys?: KeysState;
user_agent?: string;
}
class OverrideConfigurationError extends FrigateCardError {}
@@ -353,6 +355,15 @@ export class ConditionsManager {
(conditionObj.shift === undefined ||
conditionObj.shift === !!state.keys[conditionObj.key].shift)
);
case 'user_agent':
return (
!!state.user_agent &&
(!conditionObj.user_agent || conditionObj.user_agent === state.user_agent) &&
(conditionObj.companion === undefined ||
conditionObj.companion === isCompanionApp(state.user_agent)) &&
(conditionObj.user_agent_re === undefined ||
new RegExp(conditionObj.user_agent_re).test(state.user_agent))
);
}
}
+1
View File
@@ -125,6 +125,7 @@ export interface CardDownloadAPI {
export interface CardElementAPI {
getActionsManager(): ActionsManager;
getCameraManager(): CameraManager;
getConditionsManager(): ConditionsManager;
getConfigManager(): ConfigManager;
getDefaultManager(): DefaultManager;
getExpandManager(): ExpandManager;
+20
View File
@@ -726,6 +726,25 @@ const keyConditionSchema = z.object({
alt: z.boolean().optional(),
meta: z.boolean().optional(),
});
const userAgentConditionSchema = z.object({
condition: z.literal('user_agent'),
user_agent: z.string().optional(),
user_agent_re: z
.string()
.refine(
(val) => {
try {
new RegExp(val);
} catch {
return false;
}
return true;
},
{ message: 'Invalid regular expression' },
)
.optional(),
companion: z.boolean().optional(),
});
export const frigateCardConditionSchema = z.discriminatedUnion('condition', [
// Stock conditions:
@@ -745,6 +764,7 @@ export const frigateCardConditionSchema = z.discriminatedUnion('condition', [
interactionConditionSchema,
microphoneConditionSchema,
keyConditionSchema,
userAgentConditionSchema,
]);
export type FrigateCardCondition = z.infer<typeof frigateCardConditionSchema>;
+3
View File
@@ -0,0 +1,3 @@
export const isCompanionApp = (userAgent: string): boolean => {
return !!userAgent.match(/Home ?Assistant/);
};
+2 -5
View File
@@ -3,6 +3,7 @@ import { localize } from '../localize/localize';
import { ExtendedHomeAssistant, FrigateCardError } from '../types';
import { ViewMedia } from '../view/media';
import { errorToConsole } from './basic';
import { isCompanionApp } from './companion';
import { homeAssistantSignPath } from './ha';
export const downloadURL = (url: string, filename = 'download'): void => {
@@ -11,11 +12,7 @@ export const downloadURL = (url: string, filename = 'download'): void => {
const isSameOrigin = new URL(url).origin === window.location.origin;
const dataURL = url.startsWith('data:');
if (
navigator.userAgent.startsWith('Home Assistant/') ||
navigator.userAgent.startsWith('HomeAssistant/') ||
(!isSameOrigin && !dataURL)
) {
if (isCompanionApp(navigator.userAgent) || (!isSameOrigin && !dataURL)) {
// Home Assistant companion apps cannot download files without opening a
// new browser window.
//