diff --git a/src/card-controller/actions-manager.ts b/src/card-controller/actions-manager.ts index 89060699..69c54ab4 100644 --- a/src/card-controller/actions-manager.ts +++ b/src/card-controller/actions-manager.ts @@ -1,3 +1,4 @@ +import { z } from 'zod'; import { Actions, ActionsConfig, @@ -13,6 +14,15 @@ import { import { getStreamCameraID } from '../utils/substream.js'; import { CardActionsManagerAPI } from './types.js'; +const interactionSchema = z.object({ + action: z.enum(['tap', 'double_tap', 'hold', 'start_tap', 'end_tap']), +}); +export type Interaction = z.infer; + +const interactionEventSchema = z.object({ + detail: interactionSchema, +}); + export class ActionsManager { protected _api: CardActionsManagerAPI; @@ -49,7 +59,12 @@ export class ActionsManager { /** * Handle an human interaction called on an element (e.g. 'tap'). */ - public handleInteraction(interaction: string): void { + public handleInteractionEvent = (ev: Event): void => { + const result = interactionEventSchema.safeParse(ev); + if (!result.success) { + return; + } + const interaction = result.data.detail.action; const hass = this._api.getHASSManager().getHASS(); const config = this.getMergedActions(); const actionConfig = getActionConfigGivenAction(interaction, config); @@ -70,12 +85,11 @@ export class ActionsManager { actionConfig, ); } - } + }; public handleActionEvent = (ev: Event): void => { if (!('detail' in ev)) { - // The event may not actually be a CustomEvent object, but may still have a - // detail field. See: + // The event may not be a CustomEvent object, see: // https://github.com/custom-cards/custom-card-helpers/blob/master/src/fire-event.ts#L70 return; } diff --git a/src/card-controller/card-element-manager.ts b/src/card-controller/card-element-manager.ts index 38599fd2..1b442f1e 100644 --- a/src/card-controller/card-element-manager.ts +++ b/src/card-controller/card-element-manager.ts @@ -77,7 +77,11 @@ export class CardElementManager { this._api.getActionsManager().handleActionEvent, ); this._element.addEventListener( - '@action', + 'action', + this._api.getActionsManager().handleInteractionEvent, + ); + this._element.addEventListener( + 'action', this._api.getInteractionManager().reportInteraction, ); @@ -123,7 +127,11 @@ export class CardElementManager { this._api.getActionsManager().handleActionEvent, ); this._element.removeEventListener( - '@action', + 'action', + this._api.getActionsManager().handleInteractionEvent, + ); + this._element.removeEventListener( + 'action', this._api.getInteractionManager().reportInteraction, ); diff --git a/tests/card-controller/actions-manager.test.ts b/tests/card-controller/actions-manager.test.ts index 02742759..8b11b640 100644 --- a/tests/card-controller/actions-manager.test.ts +++ b/tests/card-controller/actions-manager.test.ts @@ -14,7 +14,7 @@ import { frigateCardHandleActionConfig, getActionConfigGivenAction, } from '../../src/utils/action.js'; -import { ActionsManager } from '../../src/card-controller/actions-manager'; +import { ActionsManager, Interaction } from '../../src/card-controller/actions-manager'; import { createCardAPI, createConfig, @@ -165,7 +165,9 @@ describe('ActionsManager.handleInteraction', () => { }; vi.mocked(getActionConfigGivenAction).mockReturnValue(actionForThisInteraction); - manager.handleInteraction('tap'); + manager.handleInteractionEvent( + new CustomEvent('event', { detail: { action: 'tap' } }), + ); expect(frigateCardHandleActionConfig).toBeCalledWith( element, @@ -176,13 +178,29 @@ describe('ActionsManager.handleInteraction', () => { ); }); - it('should not handle interaction', () => { + it('should not handle interaction without hass', () => { const api = createCardAPI(); const manager = new ActionsManager(api); vi.mocked(api.getHASSManager().getHASS).mockReturnValue(null); // No values of hass. - manager.handleInteraction('tap'); + manager.handleInteractionEvent( + new CustomEvent('event', { detail: { action: 'tap' } }), + ); + expect(frigateCardHandleActionConfig).not.toBeCalledWith(); + }); + + it('should not handle malformed interaction', () => { + const api = createCardAPI(); + const manager = new ActionsManager(api); + + manager.handleInteractionEvent( + new CustomEvent('event', { + + // Malformed interaction type. + detail: { action: 'double_finger_snap' } as unknown as Interaction, + }), + ); expect(frigateCardHandleActionConfig).not.toBeCalledWith(); }); }); diff --git a/tests/card-controller/card-element-manager.test.ts b/tests/card-controller/card-element-manager.test.ts index c378ab10..82f9392b 100644 --- a/tests/card-controller/card-element-manager.test.ts +++ b/tests/card-controller/card-element-manager.test.ts @@ -122,7 +122,11 @@ describe('CardElementManager', () => { api.getActionsManager().handleActionEvent, ); expect(addEventListener).toBeCalledWith( - '@action', + 'action', + api.getActionsManager().handleInteractionEvent, + ); + expect(addEventListener).toBeCalledWith( + 'action', api.getInteractionManager().reportInteraction, ); expect(windowAddEventListener).toBeCalledWith('location-changed', expect.anything()); @@ -169,7 +173,11 @@ describe('CardElementManager', () => { api.getActionsManager().handleActionEvent, ); expect(removeEventListener).toBeCalledWith( - '@action', + 'action', + api.getActionsManager().handleInteractionEvent, + ); + expect(removeEventListener).toBeCalledWith( + 'action', api.getInteractionManager().reportInteraction, ); expect(windowRemoveEventListener).toBeCalledWith(