homebridge-config-ui-x
Version:
A web based management, configuration and control platform for Homebridge.
321 lines • 12.1 kB
JavaScript
"use strict";
/**
* This script is injected into a plugins custom settings ui by the Homebridge UI
* You should not include it in your own code, however you can use it for type information if desired.
* It provides the interface to interact with the Homebridge UI service.
*/
/* eslint-disable no-console */
let EventTargetConstructor = window.EventTarget;
/**
* Polyfill for older browsers that do not support EventTarget as a constructor.
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
*/
if (!Object.prototype.hasOwnProperty.call(window.EventTarget, 'caller')) {
EventTargetConstructor = function () {
// @ts-expect-error - TS2339: Property listeners does not exist on type EventTarget
this.listeners = {};
};
// @ts-expect-error - TS2339: Property listeners does not exist on type EventTarget
EventTargetConstructor.prototype.listeners = null;
EventTargetConstructor.prototype.addEventListener = function (type, callback) {
// @ts-expect-error - TS2339: Property listeners does not exist on type EventTarget
if (!(type in this.listeners)) {
// @ts-expect-error - TS2339: Property listeners does not exist on type EventTarget
this.listeners[type] = [];
}
// @ts-expect-error - TS2339: Property listeners does not exist on type EventTarget
this.listeners[type].push(callback);
};
EventTargetConstructor.prototype.removeEventListener = function (type, callback) {
// @ts-expect-error - TS2339: Property listeners does not exist on type EventTarget
if (!(type in this.listeners)) {
return;
}
// @ts-expect-error - TS2339: Property listeners does not exist on type EventTarget
const stack = this.listeners[type];
for (let i = 0, l = stack.length; i < l; i++) {
if (stack[i] === callback) {
stack.splice(i, 1);
return;
}
}
};
EventTargetConstructor.prototype.dispatchEvent = function (event) {
// @ts-expect-error - TS2339: Property listeners does not exist on type EventTarget
if (!(event.type in this.listeners)) {
return true;
}
// @ts-expect-error - TS2339: Property listeners does not exist on type EventTarget
const stack = this.listeners[event.type].slice();
for (let i = 0, l = stack.length; i < l; i++) {
stack[i].call(this, event);
}
return !event.defaultPrevented;
};
}
class HomebridgePluginUi extends EventTargetConstructor {
origin = '';
lastBodyHeight = 0;
linkRequests = [];
toast = new HomebridgeUiToastHelper();
// @ts-expect-error - TS2339: Property _homebridge does not exist on type Window & typeof globalThis
plugin = window._homebridge.plugin;
// @ts-expect-error - TS2339: Property _homebridge does not exist on type Window & typeof globalThis
serverEnv = window._homebridge.serverEnv;
constructor() {
super();
window.addEventListener('message', this._handleIncomingMessage.bind(this), false);
}
async _handleIncomingMessage(e) {
switch (e.data.action) {
case 'ready': {
await Promise.all(this.linkRequests);
this.origin = e.origin;
document.body.style.display = 'block';
this.dispatchEvent(new Event('ready'));
this.fixScrollHeight();
this._monitorFrameHeight();
break;
}
case 'response': {
this.dispatchEvent(new MessageEvent(e.data.requestId, {
data: e.data,
}));
break;
}
case 'stream': {
this.dispatchEvent(new MessageEvent(e.data.event, {
data: e.data.data,
}));
break;
}
case 'body-class': {
this._setBodyClass(e);
break;
}
case 'inline-style': {
this._setInlineStyle(e);
break;
}
case 'link-element': {
this._setLinkElement(e);
break;
}
default:
console.log(e.data);
}
}
_postMessage(message) {
window.parent.postMessage(message, this.origin || '*');
}
_setBodyClass(e) {
document.body.classList.add(e.data.class);
}
_setInlineStyle(e) {
const styleElement = document.createElement('style');
styleElement.innerHTML = e.data.style;
document.head.appendChild(styleElement);
}
_setLinkElement(e) {
const request = new Promise((resolve) => {
const linkElement = document.createElement('link');
linkElement.setAttribute('href', e.data.href);
linkElement.setAttribute('rel', e.data.rel);
linkElement.onload = resolve;
linkElement.onerror = resolve;
document.head.appendChild(linkElement);
});
this.linkRequests.push(request);
}
_monitorFrameHeight() {
if (window.ResizeObserver) {
// use ResizeObserver if available
const resizeObserver = new window.ResizeObserver(() => {
this.fixScrollHeight();
});
resizeObserver.observe(document.body);
}
else {
// fall back to polling
setInterval(() => {
if (document.body.scrollHeight !== this.lastBodyHeight) {
this.lastBodyHeight = document.body.scrollHeight;
this.fixScrollHeight();
}
}, 250);
}
}
async _requestResponse(payload) {
// generate a random request id, so we can link the response
const requestId = Math.random().toString(36).substring(2);
payload.requestId = requestId;
// post message to parent
this._postMessage(payload);
// wait for response
return new Promise((resolve, reject) => {
const responseHandler = (event) => {
this.removeEventListener(requestId, responseHandler);
if (event.data.success) {
resolve(event.data.data);
}
else {
reject(event.data.data);
}
};
this.addEventListener(requestId, responseHandler);
});
}
fixScrollHeight() {
this._postMessage({ action: 'scrollHeight', scrollHeight: document.body.scrollHeight });
}
closeSettings() {
this._postMessage({ action: 'close' });
}
showSpinner() {
this._postMessage({ action: 'spinner.show' });
}
hideSpinner() {
this._postMessage({ action: 'spinner.hide' });
}
disableSaveButton() {
this._postMessage({ action: 'button.save.disabled' });
}
enableSaveButton() {
this._postMessage({ action: 'button.save.enabled' });
}
showSchemaForm() {
this._postMessage({ action: 'schema.show' });
}
hideSchemaForm() {
this._postMessage({ action: 'schema.hide' });
}
createForm(schema, data, submitButton, cancelButton) {
return new HomebridgeUiFormHelper(this, schema, data, submitButton, cancelButton);
}
endForm() {
this._postMessage({ action: 'form.end' });
}
async getPluginConfig() {
return await this._requestResponse({ action: 'config.get' });
}
async updatePluginConfig(pluginConfig) {
return await this._requestResponse({ action: 'config.update', pluginConfig });
}
async savePluginConfig() {
return await this._requestResponse({ action: 'config.save' });
}
async getPluginConfigSchema() {
return await this._requestResponse({ action: 'config.schema' });
}
async getCachedAccessories() {
return await this._requestResponse({ action: 'cachedAccessories.get' });
}
async request(path, body) {
return await this._requestResponse({ action: 'request', path, body });
}
async userCurrentLightingMode() {
return await this._requestResponse({ action: 'user.lightingMode' });
}
async i18nCurrentLang() {
return await this._requestResponse({ action: 'i18n.lang' });
}
async i18nGetTranslation() {
return await this._requestResponse({ action: 'i18n.translations' });
}
}
class HomebridgeUiToastHelper {
_postMessage(type, message, title) {
window.parent.postMessage({ action: `toast.${type}`, message, title }, '*');
}
success(message, title) {
this._postMessage('success', message, title);
}
error(message, title) {
this._postMessage('error', message, title);
}
warning(message, title) {
this._postMessage('warning', message, title);
}
info(message, title) {
this._postMessage('info', message, title);
}
}
class HomebridgeUiFormHelper {
parent;
formId = Math.random().toString(36).substring(2);
_changeHandle;
_submitHandle;
_cancelHandle;
end;
constructor(parent, schema, data, submitButton, cancelButton) {
this.parent = parent;
this.parent._postMessage({ action: 'form.create', formId: this.formId, schema, data, submitButton, cancelButton });
const handle = this._eventHandle.bind(this);
this.parent.addEventListener(this.formId, handle);
this.end = () => {
this.parent.removeEventListener(this.formId, handle);
this.parent._postMessage({ action: 'form.end', formId: this.formId, schema, data });
};
}
_eventHandle(event) {
switch (event.data.formEvent) {
case 'change': {
// general change events
if (this._changeHandle && typeof this._changeHandle === 'function') {
this._changeHandle(event.data.formData);
}
else {
console.info('Homebridge Custom Plugin UI: Missing form onChange handler.');
}
break;
}
case 'submit': {
// submit form events
if (this._submitHandle && typeof this._submitHandle === 'function') {
this._submitHandle(event.data.formData);
}
else {
console.info('Homebridge Custom Plugin UI: Missing form onSubmit handler.');
}
break;
}
case 'cancel': {
// cancel form events
if (this._cancelHandle && typeof this._cancelHandle === 'function') {
this._cancelHandle(event.data.formData);
}
else {
console.info('Homebridge Custom Plugin UI: Missing form onCancel handler.');
}
break;
}
default: {
console.info('Unknown form event type:', event.data);
}
}
}
onChange(fn) {
if (typeof fn !== 'function') {
console.error('Homebridge Custom Plugin UI: Form onChange handler must be a function.');
return;
}
this._changeHandle = fn;
}
onSubmit(fn) {
if (typeof fn !== 'function') {
console.error('Homebridge Custom Plugin UI: Form onSubmit handler must be a function.');
return;
}
this._submitHandle = fn;
}
onCancel(fn) {
if (typeof fn !== 'function') {
console.error('Homebridge Custom Plugin UI: Form onCancel handler must be a function.');
return;
}
this._cancelHandle = fn;
}
}
// @ts-expect-error - TS2339: Property _homebridge does not exist on type Window & typeof globalThis
window.homebridge = new HomebridgePluginUi();
//# sourceMappingURL=ui.js.map