Vendored
+2
-1
@@ -10,6 +10,7 @@
|
|||||||
"redhat.vscode-yaml",
|
"redhat.vscode-yaml",
|
||||||
"lokalise.i18n-ally",
|
"lokalise.i18n-ally",
|
||||||
"vitest.explorer",
|
"vitest.explorer",
|
||||||
"inlang.vs-code-extension"
|
"inlang.vs-code-extension",
|
||||||
|
"yoavbls.pretty-ts-errors"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ certain configurations (in `overrides`) or to display "picture elements" (in
|
|||||||
|
|
||||||
## `camera`
|
## `camera`
|
||||||
|
|
||||||
|
Matches based on the selected camera.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
conditions:
|
conditions:
|
||||||
- condition: camera
|
- condition: camera
|
||||||
@@ -29,6 +31,8 @@ conditions:
|
|||||||
|
|
||||||
## `expand`
|
## `expand`
|
||||||
|
|
||||||
|
Matches based on whether the card is in "expanded" mode.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
conditions:
|
conditions:
|
||||||
- condition: expand
|
- condition: expand
|
||||||
@@ -42,6 +46,8 @@ conditions:
|
|||||||
|
|
||||||
## `fullscreen`
|
## `fullscreen`
|
||||||
|
|
||||||
|
Matches based on whether the card is in fullscreen.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
conditions:
|
conditions:
|
||||||
- condition: fullscreen
|
- condition: fullscreen
|
||||||
@@ -55,6 +61,8 @@ conditions:
|
|||||||
|
|
||||||
## `interaction`
|
## `interaction`
|
||||||
|
|
||||||
|
Matches based on whether the card has been interacted with.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
conditions:
|
conditions:
|
||||||
- condition: interaction
|
- condition: interaction
|
||||||
@@ -68,6 +76,8 @@ conditions:
|
|||||||
|
|
||||||
## `key`
|
## `key`
|
||||||
|
|
||||||
|
Matches based on key state.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
conditions:
|
conditions:
|
||||||
- condition: key
|
- condition: key
|
||||||
@@ -86,6 +96,8 @@ conditions:
|
|||||||
|
|
||||||
## `media_loaded`
|
## `media_loaded`
|
||||||
|
|
||||||
|
Matches based on whether the selected live or media stream has loaded.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
conditions:
|
conditions:
|
||||||
- condition: media_loaded
|
- condition: media_loaded
|
||||||
@@ -99,6 +111,8 @@ conditions:
|
|||||||
|
|
||||||
## `microphone`
|
## `microphone`
|
||||||
|
|
||||||
|
Matches based on microphone state.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
conditions:
|
conditions:
|
||||||
- condition: microphone
|
- condition: microphone
|
||||||
@@ -111,18 +125,25 @@ conditions:
|
|||||||
| `connected` | Optional: If `true` or `false` the condition is satisfied if the microphone is connected or disconnected respectively. |
|
| `connected` | Optional: If `true` or `false` the condition is satisfied if the microphone is connected or disconnected respectively. |
|
||||||
| `muted` | Optional: If `true` or `false` the condition is satisfied if the microphone is muted or unmuted respectively. |
|
| `muted` | Optional: If `true` or `false` the condition is satisfied if the microphone is muted or unmuted respectively. |
|
||||||
|
|
||||||
|
When multiple parameters are specified they must all match for the condition to
|
||||||
|
match.
|
||||||
|
|
||||||
## `numeric_state`
|
## `numeric_state`
|
||||||
|
|
||||||
|
Matches based on numeric Home Assistant state.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
conditions:
|
conditions:
|
||||||
- condition: numeric_state
|
- condition: numeric_state
|
||||||
# [...]
|
# [...]
|
||||||
```
|
```
|
||||||
|
|
||||||
This stock Home Assistant condition works out of the box. See [Home Assistant conditions documentation](https://www.home-assistant.io/dashboards/conditional/#numeric-state).
|
See [Home Assistant conditions documentation](https://www.home-assistant.io/dashboards/conditional/#numeric-state).
|
||||||
|
|
||||||
## `screen`
|
## `screen`
|
||||||
|
|
||||||
|
Matches based on [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
conditions:
|
conditions:
|
||||||
- condition: screen
|
- condition: screen
|
||||||
@@ -138,16 +159,20 @@ See the [screen conditions examples](../examples.md?id=screen-conditions).
|
|||||||
|
|
||||||
## `state`
|
## `state`
|
||||||
|
|
||||||
|
Matches based on Home Assistant state.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
conditions:
|
conditions:
|
||||||
- condition: state
|
- condition: state
|
||||||
# [...]
|
# [...]
|
||||||
```
|
```
|
||||||
|
|
||||||
This stock Home Assistant condition works out of the box. See [Home Assistant conditions documentation](https://www.home-assistant.io/dashboards/conditional/#state).
|
See [Home Assistant conditions documentation](https://www.home-assistant.io/dashboards/conditional/#state).
|
||||||
|
|
||||||
## `triggered`
|
## `triggered`
|
||||||
|
|
||||||
|
Matches based on whether the selected camera has been triggered.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
conditions:
|
conditions:
|
||||||
- condition: triggered
|
- condition: triggered
|
||||||
@@ -161,16 +186,40 @@ conditions:
|
|||||||
|
|
||||||
## `user`
|
## `user`
|
||||||
|
|
||||||
|
Matches based on the Home Assistant user that is logged in. See [Home Assistant conditions documentation](https://www.home-assistant.io/dashboards/conditional/#user).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
conditions:
|
conditions:
|
||||||
- condition: user
|
- condition: user
|
||||||
# [...]
|
# [...]
|
||||||
```
|
```
|
||||||
|
|
||||||
This stock Home Assistant condition works out of the box. See [Home Assistant conditions documentation](https://www.home-assistant.io/dashboards/conditional/#user).
|
## `user_agent`
|
||||||
|
|
||||||
|
Matches based on the [User-Agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
conditions:
|
||||||
|
- condition: user_agent
|
||||||
|
# [...]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `condition` | Must be `user_agent`. |
|
||||||
|
| `user_agent` | Exactly matches a user-agent, e.g. `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36` |
|
||||||
|
| `user_agent_re` | Matches a user-agent based on a regular expression, e.g. `Chrome/`. |
|
||||||
|
| `companion` | If `true` matches if the user-agent is the Home Assistant companion app, if `false` matches if the user-agent is _NOT_ the Home Assistant companion app. |
|
||||||
|
|
||||||
|
When multiple parameters are specified they must all match for the condition to
|
||||||
|
match.
|
||||||
|
|
||||||
|
See the [user-agent overrides example](../examples.md?id=disable-ptz-controls-in-the-home-assistant-companion-app).
|
||||||
|
|
||||||
## `view`
|
## `view`
|
||||||
|
|
||||||
|
Matches based on the selected view.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
conditions:
|
conditions:
|
||||||
- condition: view
|
- condition: view
|
||||||
@@ -230,6 +279,10 @@ conditions:
|
|||||||
- condition: user
|
- condition: user
|
||||||
users:
|
users:
|
||||||
- 581fca7fdc014b8b894519cc531f9a04
|
- 581fca7fdc014b8b894519cc531f9a04
|
||||||
|
- condition: user_agent
|
||||||
|
user_agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||||
|
user_agent_re: "Chrome/"
|
||||||
|
companion: true
|
||||||
- condition: view
|
- condition: view
|
||||||
views:
|
views:
|
||||||
- live
|
- live
|
||||||
|
|||||||
@@ -39,13 +39,13 @@ Principles used in the selection of options set by `low-performance` profile mod
|
|||||||
- Get 'out of the box' performance similar to the basic "Home Assistant Picture Glance" card.
|
- Get 'out of the box' performance similar to the basic "Home Assistant Picture Glance" card.
|
||||||
- Do not break the visual aesthetic of the card.
|
- Do not break the visual aesthetic of the card.
|
||||||
|
|
||||||
See the [source code](https://github.com/dermotduffy/frigate-hass-card/blob/dev/src/config/profiles/low-performance.ts) for an exhaustive list of defaults set by this profile.
|
See the [source code](https://github.com/dermotduffy/frigate-hass-card/blob/main/src/config/profiles/low-performance.ts) for an exhaustive list of defaults set by this profile.
|
||||||
|
|
||||||
## `scrubbing`
|
## `scrubbing`
|
||||||
|
|
||||||
Configures the `live` view and media viewer to allow media "scrubbing" as the timeline is dragged back and forth.
|
Configures the `live` view and media viewer to allow media "scrubbing" as the timeline is dragged back and forth.
|
||||||
|
|
||||||
See the [source code](https://github.com/dermotduffy/frigate-hass-card/blob/dev/src/config/profiles/scrubbing.ts) for an exhaustive list of options set by this profile.
|
See the [source code](https://github.com/dermotduffy/frigate-hass-card/blob/main/src/config/profiles/scrubbing.ts) for an exhaustive list of options set by this profile.
|
||||||
|
|
||||||
## Fully expanded reference
|
## Fully expanded reference
|
||||||
|
|
||||||
|
|||||||
@@ -624,6 +624,22 @@ overrides:
|
|||||||
- 'cameras[1]'
|
- 'cameras[1]'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Disable PTZ controls in the Home Assistant Companion App
|
||||||
|
|
||||||
|
This example disables the PTZ controls when the card is viewed on the Companion app:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
type: custom:frigate-card
|
||||||
|
cameras:
|
||||||
|
- camera_entity: camera.office
|
||||||
|
overrides:
|
||||||
|
- conditions:
|
||||||
|
- condition: user_agent
|
||||||
|
companion: true
|
||||||
|
set:
|
||||||
|
live.controls.ptz.mode: 'off'
|
||||||
|
```
|
||||||
|
|
||||||
## PTZ control
|
## PTZ control
|
||||||
|
|
||||||
The card supports using PTZ controls to conveniently control pan, tilt and zoom
|
The card supports using PTZ controls to conveniently control pan, tilt and zoom
|
||||||
|
|||||||
@@ -127,6 +127,10 @@ export class CardElementManager {
|
|||||||
this._api.getQueryStringManager().requestExecution,
|
this._api.getQueryStringManager().requestExecution,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this._api.getConditionsManager()?.setState({
|
||||||
|
user_agent: navigator.userAgent,
|
||||||
|
});
|
||||||
|
|
||||||
// Manually request query string execute as the card will be
|
// Manually request query string execute as the card will be
|
||||||
// disconnected/reconnected when dashboard 'tab' changes happen within HA.
|
// disconnected/reconnected when dashboard 'tab' changes happen within HA.
|
||||||
this._api.getQueryStringManager().requestExecution();
|
this._api.getQueryStringManager().requestExecution();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
import { localize } from '../localize/localize';
|
import { localize } from '../localize/localize';
|
||||||
import { FrigateCardError } from '../types';
|
import { FrigateCardError } from '../types';
|
||||||
import { desparsifyArrays } from '../utils/basic';
|
import { desparsifyArrays } from '../utils/basic';
|
||||||
|
import { isCompanionApp } from '../utils/companion';
|
||||||
import { CardConditionAPI, KeysState } from './types';
|
import { CardConditionAPI, KeysState } from './types';
|
||||||
|
|
||||||
interface MicrophoneConditionState {
|
interface MicrophoneConditionState {
|
||||||
@@ -39,6 +40,7 @@ interface ConditionState {
|
|||||||
microphone?: MicrophoneConditionState;
|
microphone?: MicrophoneConditionState;
|
||||||
user?: CurrentUser;
|
user?: CurrentUser;
|
||||||
keys?: KeysState;
|
keys?: KeysState;
|
||||||
|
user_agent?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class OverrideConfigurationError extends FrigateCardError {}
|
class OverrideConfigurationError extends FrigateCardError {}
|
||||||
@@ -353,6 +355,15 @@ export class ConditionsManager {
|
|||||||
(conditionObj.shift === undefined ||
|
(conditionObj.shift === undefined ||
|
||||||
conditionObj.shift === !!state.keys[conditionObj.key].shift)
|
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))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ export interface CardDownloadAPI {
|
|||||||
export interface CardElementAPI {
|
export interface CardElementAPI {
|
||||||
getActionsManager(): ActionsManager;
|
getActionsManager(): ActionsManager;
|
||||||
getCameraManager(): CameraManager;
|
getCameraManager(): CameraManager;
|
||||||
|
getConditionsManager(): ConditionsManager;
|
||||||
getConfigManager(): ConfigManager;
|
getConfigManager(): ConfigManager;
|
||||||
getDefaultManager(): DefaultManager;
|
getDefaultManager(): DefaultManager;
|
||||||
getExpandManager(): ExpandManager;
|
getExpandManager(): ExpandManager;
|
||||||
|
|||||||
@@ -726,6 +726,25 @@ const keyConditionSchema = z.object({
|
|||||||
alt: z.boolean().optional(),
|
alt: z.boolean().optional(),
|
||||||
meta: 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', [
|
export const frigateCardConditionSchema = z.discriminatedUnion('condition', [
|
||||||
// Stock conditions:
|
// Stock conditions:
|
||||||
@@ -745,6 +764,7 @@ export const frigateCardConditionSchema = z.discriminatedUnion('condition', [
|
|||||||
interactionConditionSchema,
|
interactionConditionSchema,
|
||||||
microphoneConditionSchema,
|
microphoneConditionSchema,
|
||||||
keyConditionSchema,
|
keyConditionSchema,
|
||||||
|
userAgentConditionSchema,
|
||||||
]);
|
]);
|
||||||
export type FrigateCardCondition = z.infer<typeof frigateCardConditionSchema>;
|
export type FrigateCardCondition = z.infer<typeof frigateCardConditionSchema>;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const isCompanionApp = (userAgent: string): boolean => {
|
||||||
|
return !!userAgent.match(/Home ?Assistant/);
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import { localize } from '../localize/localize';
|
|||||||
import { ExtendedHomeAssistant, FrigateCardError } from '../types';
|
import { ExtendedHomeAssistant, FrigateCardError } from '../types';
|
||||||
import { ViewMedia } from '../view/media';
|
import { ViewMedia } from '../view/media';
|
||||||
import { errorToConsole } from './basic';
|
import { errorToConsole } from './basic';
|
||||||
|
import { isCompanionApp } from './companion';
|
||||||
import { homeAssistantSignPath } from './ha';
|
import { homeAssistantSignPath } from './ha';
|
||||||
|
|
||||||
export const downloadURL = (url: string, filename = 'download'): void => {
|
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 isSameOrigin = new URL(url).origin === window.location.origin;
|
||||||
const dataURL = url.startsWith('data:');
|
const dataURL = url.startsWith('data:');
|
||||||
|
|
||||||
if (
|
if (isCompanionApp(navigator.userAgent) || (!isSameOrigin && !dataURL)) {
|
||||||
navigator.userAgent.startsWith('Home Assistant/') ||
|
|
||||||
navigator.userAgent.startsWith('HomeAssistant/') ||
|
|
||||||
(!isSameOrigin && !dataURL)
|
|
||||||
) {
|
|
||||||
// Home Assistant companion apps cannot download files without opening a
|
// Home Assistant companion apps cannot download files without opening a
|
||||||
// new browser window.
|
// new browser window.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1023,5 +1023,75 @@ describe('ConditionsManager', () => {
|
|||||||
expect(manager.evaluateConditions(conditions)).toBeTruthy();
|
expect(manager.evaluateConditions(conditions)).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('with user agent condition', () => {
|
||||||
|
const userAgent =
|
||||||
|
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
|
||||||
|
|
||||||
|
it('should match exact user agent', () => {
|
||||||
|
const manager = new ConditionsManager(createCardAPI());
|
||||||
|
const conditions = [{ condition: 'user_agent' as const, user_agent: userAgent }];
|
||||||
|
expect(manager.evaluateConditions(conditions)).toBeFalsy();
|
||||||
|
manager.setState({
|
||||||
|
user_agent: userAgent,
|
||||||
|
});
|
||||||
|
expect(manager.evaluateConditions(conditions)).toBeTruthy();
|
||||||
|
manager.setState({
|
||||||
|
user_agent: 'Something else',
|
||||||
|
});
|
||||||
|
expect(manager.evaluateConditions(conditions)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match user agent regex', () => {
|
||||||
|
const manager = new ConditionsManager(createCardAPI());
|
||||||
|
const conditions = [
|
||||||
|
{ condition: 'user_agent' as const, user_agent_re: 'Chrome/' },
|
||||||
|
];
|
||||||
|
expect(manager.evaluateConditions(conditions)).toBeFalsy();
|
||||||
|
manager.setState({
|
||||||
|
user_agent: userAgent,
|
||||||
|
});
|
||||||
|
expect(manager.evaluateConditions(conditions)).toBeTruthy();
|
||||||
|
manager.setState({
|
||||||
|
user_agent: 'Something else',
|
||||||
|
});
|
||||||
|
expect(manager.evaluateConditions(conditions)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match companion app', () => {
|
||||||
|
const manager = new ConditionsManager(createCardAPI());
|
||||||
|
const conditions = [{ condition: 'user_agent' as const, companion: true }];
|
||||||
|
expect(manager.evaluateConditions(conditions)).toBeFalsy();
|
||||||
|
manager.setState({
|
||||||
|
user_agent: 'Home Assistant/',
|
||||||
|
});
|
||||||
|
expect(manager.evaluateConditions(conditions)).toBeTruthy();
|
||||||
|
manager.setState({
|
||||||
|
user_agent: userAgent,
|
||||||
|
});
|
||||||
|
expect(manager.evaluateConditions(conditions)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match multiple parameters', () => {
|
||||||
|
const manager = new ConditionsManager(createCardAPI());
|
||||||
|
const conditions = [
|
||||||
|
{
|
||||||
|
condition: 'user_agent' as const,
|
||||||
|
companion: true,
|
||||||
|
user_agent: 'Home Assistant/',
|
||||||
|
user_agent_re: 'Home.Assistant',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(manager.evaluateConditions(conditions)).toBeFalsy();
|
||||||
|
manager.setState({
|
||||||
|
user_agent: 'Home Assistant/',
|
||||||
|
});
|
||||||
|
expect(manager.evaluateConditions(conditions)).toBeTruthy();
|
||||||
|
manager.setState({
|
||||||
|
user_agent: 'Something else',
|
||||||
|
});
|
||||||
|
expect(manager.evaluateConditions(conditions)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
conditionalSchema,
|
conditionalSchema,
|
||||||
customSchema,
|
customSchema,
|
||||||
dimensionsConfigSchema,
|
dimensionsConfigSchema,
|
||||||
|
frigateCardConditionSchema,
|
||||||
frigateCardCustomActionsBaseSchema,
|
frigateCardCustomActionsBaseSchema,
|
||||||
frigateCardCustomActionSchema,
|
frigateCardCustomActionSchema,
|
||||||
} from '../../src/config/types';
|
} from '../../src/config/types';
|
||||||
@@ -401,6 +402,29 @@ it('should transform dimensions.aspect_ratio', () => {
|
|||||||
).toEqual(expect.objectContaining({ aspect_ratio: [16, 9] }));
|
).toEqual(expect.objectContaining({ aspect_ratio: [16, 9] }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('should refine user_agent_re conditions', () => {
|
||||||
|
it('should successfully parse valid user_agent_re condition', () => {
|
||||||
|
expect(
|
||||||
|
frigateCardConditionSchema.parse({
|
||||||
|
condition: 'user_agent',
|
||||||
|
user_agent_re: 'Chrome/',
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
condition: 'user_agent',
|
||||||
|
user_agent_re: 'Chrome/',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid user_agent_re conditions', () => {
|
||||||
|
expect(() =>
|
||||||
|
frigateCardConditionSchema.parse({
|
||||||
|
condition: 'user_agent',
|
||||||
|
user_agent_re: '[',
|
||||||
|
}),
|
||||||
|
).toThrowError(/Invalid regular expression/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should transform action', () => {
|
it('should transform action', () => {
|
||||||
expect(
|
expect(
|
||||||
frigateCardCustomActionsBaseSchema.parse({
|
frigateCardCustomActionsBaseSchema.parse({
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { isCompanionApp } from '../../src/utils/companion';
|
||||||
|
|
||||||
|
describe('isCompanionApp', () => {
|
||||||
|
it('should return true for userAgent starting with "Home Assistant/"', () => {
|
||||||
|
expect(isCompanionApp('Home Assistant/1.0')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for userAgent starting with "HomeAssistant/"', () => {
|
||||||
|
expect(isCompanionApp('HomeAssistant/1.0')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for userAgent not starting with "Home Assistant/" or "HomeAssistant/"', () => {
|
||||||
|
expect(isCompanionApp('Mozilla/5.0')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for an empty userAgent', () => {
|
||||||
|
expect(isCompanionApp('')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -21,6 +21,7 @@ const FULL_COVERAGE_FILES_RELATIVE = [
|
|||||||
'utils/basic.ts',
|
'utils/basic.ts',
|
||||||
'utils/camera.ts',
|
'utils/camera.ts',
|
||||||
'utils/casting.ts',
|
'utils/casting.ts',
|
||||||
|
'utils/companion.ts',
|
||||||
'utils/custom-icons.ts',
|
'utils/custom-icons.ts',
|
||||||
'utils/debug.ts',
|
'utils/debug.ts',
|
||||||
'utils/diagnostics.ts',
|
'utils/diagnostics.ts',
|
||||||
|
|||||||
Reference in New Issue
Block a user