{"version":3,"file":"index.mjs","names":[],"sources":["../../src/lib/index.ts"],"sourcesContent":["'use strict';\n\nimport type { Config, ConfigDevice, LogParam } from './types.js';\nimport { connect, MqttClient } from 'mqtt';\nimport pyatv, {\n    NodePyATVDeviceEvent,\n    NodePyATVKeys,\n} from '@sebbo2002/node-pyatv';\n\nexport default class PyAtvMqttBridge {\n    private mqttClient: MqttClient | null = null;\n    private readonly options: Config;\n    private readonly teardown: Array<() => Promise<void>> = [];\n\n    constructor(options: Config) {\n        if (!options.broker) {\n            throw new Error('options.broker is not set!');\n        }\n        if (!Array.isArray(options.devices) || !options.devices.length) {\n            throw new Error('options.devices is not set!');\n        }\n        if (options.devices.find((d) => typeof d.topic !== 'string')) {\n            throw new Error('options.devices.topic is not set!');\n        }\n        if (options.devices.find((d) => typeof d.host !== 'string')) {\n            throw new Error('options.devices.host is not set!');\n        }\n        if (options.devices.find((d) => typeof d.name !== 'string')) {\n            throw new Error('options.devices.name is not set!');\n        }\n\n        this.options = options;\n        this.teardown = [];\n\n        this.start().catch((err) => {\n            this.log({\n                level: 'error',\n                host: null,\n                message: 'Unable to start bridge',\n                error: err,\n            });\n        });\n    }\n\n    private log(msg: LogParam) {\n        if (this.options.log) {\n            try {\n                this.options.log.apply(this, [msg]);\n            } catch (err) {\n                console.log('Unable to call custom log function:');\n                console.log(err);\n            }\n        }\n    }\n\n    private async start() {\n        const errorListener = (error: Error) =>\n            this.log({\n                level: 'error',\n                host: null,\n                message: 'MQTT error',\n                error,\n            });\n\n        // this.mqttClient = connect(this.options.broker);\n        if (typeof this.options.broker === 'string') {\n            this.mqttClient = connect(this.options.broker);\n        } else {\n            this.mqttClient = connect(this.options.broker);\n        }\n\n        this.mqttClient.on('error', errorListener);\n        this.teardown.unshift(async () => {\n            if (this.mqttClient) {\n                this.mqttClient.off('error', errorListener);\n                await new Promise((resolve) => {\n                    if (this.mqttClient) {\n                        this.mqttClient.end(false, () => resolve(undefined));\n                    }\n                });\n            }\n        });\n\n        await Promise.all(\n            this.options.devices.map((device) => this.startDevice(device)),\n        );\n    }\n\n    private async startDevice(device: ConfigDevice) {\n        this.log({\n            level: 'info',\n            host: device.host,\n            message: 'Setup device…',\n        });\n\n        const atv = pyatv.device(\n            Object.assign({}, device, {\n                debug: (message: string) =>\n                    this.log({\n                        level: 'info',\n                        host: device.host,\n                        message,\n                    }),\n            }),\n        );\n\n        /* MQTT <--  PYATV */\n\n        if (this.mqttClient) {\n            this.mqttClient.publish(device.topic + '/host', device.host || '', {\n                retain: true,\n            });\n            this.mqttClient.publish(device.topic + '/name', device.name, {\n                retain: true,\n            });\n            this.mqttClient.publish(device.topic + '/id', device.id || '', {\n                retain: true,\n            });\n        }\n\n        const updateListener = (event: NodePyATVDeviceEvent | Error) => {\n            if (event instanceof NodePyATVDeviceEvent) {\n                this.log({\n                    level: 'info',\n                    host: device.host,\n                    message: JSON.stringify(event),\n                });\n\n                if (this.mqttClient) {\n                    const value =\n                        event.value === null ? '' : String(event.value);\n                    this.mqttClient.publish(\n                        device.topic + '/' + event.key,\n                        value,\n                        { retain: true },\n                    );\n                }\n            }\n        };\n        const errorListener = (error: Error | NodePyATVDeviceEvent) => {\n            if (error instanceof Error) {\n                this.log({\n                    level: 'error',\n                    host: device.host,\n                    message: 'Push Error',\n                    error,\n                });\n            }\n        };\n\n        atv.on('update', updateListener);\n        atv.on('error', errorListener);\n        this.teardown.unshift(async () => {\n            atv.off('update', updateListener);\n            atv.off('error', errorListener);\n        });\n\n        /* MQTT -->  PYATV */\n\n        if (this.mqttClient) {\n            this.mqttClient.subscribe(device.topic + '/+');\n            this.teardown.unshift(() => {\n                return new Promise((resolve, reject) => {\n                    if (this.mqttClient) {\n                        this.mqttClient.unsubscribe(\n                            device.topic + '/+',\n                            (error) => {\n                                if (error) {\n                                    reject(error);\n                                } else {\n                                    resolve();\n                                }\n                            },\n                        );\n                    }\n                });\n            });\n\n            this.mqttClient.on('message', (topic, body) => {\n                if (device.topic + '/launch' === topic) {\n                    const id = body.toString();\n                    atv.launchApp(id).catch((error) => {\n                        this.log({\n                            level: 'error',\n                            host: device.host,\n                            message: `Unable to launch app \"${id}\"`,\n                            error,\n                        });\n                    });\n                    return;\n                }\n\n                const key = Object.keys(NodePyATVKeys).find(\n                    (key) => device.topic + '/' + key === topic,\n                ) as NodePyATVKeys | undefined;\n\n                if (key) {\n                    atv.pressKey(key).catch((error) => {\n                        this.log({\n                            level: 'error',\n                            host: device.host,\n                            message: `Unable to press key \"${key}\"`,\n                            error,\n                        });\n                    });\n                }\n            });\n        }\n    }\n\n    async stop(): Promise<void> {\n        await Promise.all(this.teardown);\n    }\n}\n"],"mappings":"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"}