94 lines
3.2 KiB
TypeScript
94 lines
3.2 KiB
TypeScript
import { TRPCError } from '@trpc/server';
|
|
import Consola from 'consola';
|
|
import { ZodError, z } from 'zod';
|
|
|
|
|
|
import { createTRPCRouter, protectedProcedure } from '../../trpc';
|
|
|
|
import { findAppProperty } from '~/tools/client/app-properties';
|
|
import { getConfig } from '~/tools/config/getConfig';
|
|
import { HomeAssistantSingleton } from '~/tools/singleton/HomeAssistantSingleton';
|
|
import { ISmartHomeEntityStateWidget } from '~/widgets/smart-home/entity-state/entity-state.widget';
|
|
|
|
export const smartHomeEntityStateRouter = createTRPCRouter({
|
|
retrieveStatus: protectedProcedure
|
|
.input(
|
|
z.object({
|
|
configName: z.string(),
|
|
// TODO: passing entity ID directly can be unsafe
|
|
entityId: z.string().regex(/^[A-Za-z0-9-_\.]+$/),
|
|
}),
|
|
)
|
|
.query(async ({ input }) => {
|
|
const config = getConfig(input.configName);
|
|
|
|
const instances = config.apps.filter((app) => app.integration?.type == 'homeAssistant');
|
|
|
|
for (const instance of instances) {
|
|
const url = new URL(instance.url);
|
|
const client = HomeAssistantSingleton.getOrSet(url, findAppProperty(instance, 'apiKey'));
|
|
const state = await client.getEntityState(input.entityId);
|
|
|
|
if (!state.success) {
|
|
if (!(state.error instanceof ZodError)) {
|
|
continue;
|
|
}
|
|
|
|
throw new TRPCError({
|
|
code: 'NOT_IMPLEMENTED',
|
|
message: `Unable to handle Home Assistant entity state. This may be due to malformed response or unknown entity type. Check log for details`,
|
|
});
|
|
}
|
|
|
|
if (!state.data) {
|
|
throw new TRPCError({
|
|
code: 'INTERNAL_SERVER_ERROR',
|
|
message: `Home Assistant: Unable to connect to app '${instance.id}'. Check logs for details`,
|
|
});
|
|
}
|
|
|
|
return state.data;
|
|
}
|
|
|
|
return null;
|
|
}),
|
|
triggerAutomation: protectedProcedure
|
|
.input(z.object({
|
|
widgetId: z.string(),
|
|
configName: z.string(),
|
|
})).mutation(async ({ input }) => {
|
|
const config = getConfig(input.configName);
|
|
const widget = config.widgets.find(widget => widget.id === input.widgetId) as ISmartHomeEntityStateWidget | null;
|
|
|
|
if (!widget) {
|
|
Consola.error(`Referenced widget ${input.widgetId} does not exist on backend.`);
|
|
throw new TRPCError({
|
|
code: 'CONFLICT',
|
|
message: 'Referenced widget does not exist on backend',
|
|
});
|
|
}
|
|
|
|
if (!widget.properties.automationId || widget.properties.automationId.length < 1) {
|
|
Consola.error(`Referenced widget ${input.widgetId} does not have the required property set.`);
|
|
throw new TRPCError({
|
|
code: 'CONFLICT',
|
|
message: 'Referenced widget does not have the required property',
|
|
});
|
|
}
|
|
|
|
const instances = config.apps.filter((app) => app.integration?.type == 'homeAssistant');
|
|
|
|
for (const instance of instances) {
|
|
const url = new URL(instance.url);
|
|
const client = HomeAssistantSingleton.getOrSet(url, findAppProperty(instance, 'apiKey'));
|
|
const state = await client.triggerAutomation(widget.properties.automationId);
|
|
|
|
if (state) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}),
|
|
});
|