UNPKG

node-red-contrib-google-smarthome

Version:

Lets you control Node-Red via Google Assistant or the Google Home App

475 lines (430 loc) 23.4 kB
<!-- node-red-contrib-google-smarthome Copyright (C) 2024 Michael Jacobsen and others. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. --> <script type="text/x-red" data-template-name="googlesmarthome-client"> <style> ol#node-config-input-googlesmarthome-emails-container .red-ui-typedInput-container { flex:1; } </style> <div class="form-tips" style="margin-bottom: 1em"> For setup instructions, <a href="https://github.com/mikejac/node-red-contrib-google-smarthome/blob/master/docs/setup_instructions.md" target="_blank">look here </a>. </div> <div class="form-row"> <label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="googlesmarthome.label.name"></span></label> <input type="text" id="node-config-input-name" data-i18n="[placeholder]googlesmarthome.placeholder.name"> </div> <div class="form-row"> <label style="width:auto" for="node-config-input-enabledebug"><i class="fa fa-arrow-right"></i> <span data-i18n="googlesmarthome.label.enabledebug"></span></label> <input type="checkbox" checked id="node-config-input-enabledebug" style="display:inline-block; width:auto; vertical-align:top;"> </div> <div class="form-row"> <label for="node-config-input-default_lang"><i class="fa fa-tag"></i> <span data-i18n="googlesmarthome.label.default_lang"></span></label> <select id="node-config-input-default_lang" > <option value='da' data-i18n='googlesmarthome.label.da'></option> <option value='nl' data-i18n='googlesmarthome.label.nl'></option> <option value='en' data-i18n='googlesmarthome.label.en'></option> <option value='fr' data-i18n='googlesmarthome.label.fr'></option> <option value='de' data-i18n='googlesmarthome.label.de'></option> <option value='hi' data-i18n='googlesmarthome.label.hi'></option> <option value='id' data-i18n='googlesmarthome.label.id'></option> <option value='it' data-i18n='googlesmarthome.label.it'></option> <option value='ja' data-i18n='googlesmarthome.label.ja'></option> <option value='ko' data-i18n='googlesmarthome.label.ko'></option> <option value='no' data-i18n='googlesmarthome.label.no'></option> <option value='pt-BR' data-i18n='googlesmarthome.label.pt-BR'></option> <option value='es' data-i18n='googlesmarthome.label.es'></option> <option value='sv' data-i18n='googlesmarthome.label.sv'></option> <option value='th' data-i18n='googlesmarthome.label.th'></option> </select> </div> <fieldset> <legend>Local Authentication</legend> <div class="form-row"> <label style="width:auto" for="node-config-input-usegooglelogin"><i class="fa fa-arrow-right"></i> <span data-i18n="googlesmarthome.label.usegooglelogin"></span></label> <input type="checkbox" id="node-config-input-usegooglelogin" style="display:inline-block; width:auto; vertical-align:top;"> </div> <div class="form-row hidden" id="usegooglelogin" style="background: #fbfbfb"> <div class="form-row"> <label for="node-config-input-loginclientid"><i class="fa fa-user"></i> <span data-i18n="googlesmarthome.label.loginclientid"></span></label> <input type="text" id="node-config-input-loginclientid"> </div> <div class="form-row"> <div class="form-row" style="margin-bottom:0;"> <label><i class="fa fa-list"></i> <span data-i18n="googlesmarthome.label.emails"></span></label> </div> <div class="form-row node-config-input-googlesmarthome-emails-container-row"> <ol id="node-config-input-googlesmarthome-emails-container"></ol> </div> </div> </div> <div class="form-row" id="useloginpwd" style="background: #fbfbfb"> <div class="form-row"> <label for="node-config-input-username"><i class="fa fa-user"></i> <span data-i18n="googlesmarthome.label.username"></span></label> <input type="text" id="node-config-input-username"> </div> <div class="form-row"> <label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="googlesmarthome.label.password"></span></label> <input type="password" id="node-config-input-password"> </div> </div> </fieldset> <fieldset> <legend>Actions on Google Project Settings</legend> <div class="form-row"> <label for="node-config-input-clientid"><i class="fa fa-user"></i> <span data-i18n="googlesmarthome.label.clientid"></span></label> <input type="text" id="node-config-input-clientid"> </div> <div class="form-row"> <label for="node-config-input-clientsecret"><i class="fa fa-lock"></i> <span data-i18n="googlesmarthome.label.clientsecret"></span></label> <input type="password" id="node-config-input-clientsecret"> </div> </fieldset> <fieldset> <legend>Google HomeGraph Settings</legend> <div class="form-row"> <label for="node-config-input-jwtkey"><i class="fa fa-folder"></i> <span data-i18n="googlesmarthome.label.jwtkey"></span></label> <input type="text" id="node-config-input-jwtkey"> </div> </fieldset> <fieldset> <legend>Web Server Settings</legend> <div class="form-row"> <label for="node-config-input-port"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.port"></span></label> <input type="text" id="node-config-input-port" data-i18n="[placeholder]googlesmarthome.placeholder.port"> </div> <div class="form-row"> <label for="node-config-input-httppath"><i class="fa fa-folder"></i> <span data-i18n="googlesmarthome.label.httppath"></span></label> <input type="text" id="node-config-input-httppath"> </div> <div class="form-row hidden" id="connectioninfo" style="background: #fbfbfb"> <div class="form-row"> <label style="width:auto" for="node-config-input-usehttpnoderoot"><i class="fa fa-arrow-right"></i> <span data-i18n="googlesmarthome.label.usehttpnoderoot"></span></label> <input type="checkbox" id="node-config-input-usehttpnoderoot" style="display:inline-block; width:auto; vertical-align:top;"> </div> <div class="form-row"> <label style="width:auto" for="node-config-input-ssloffload"><i class="fa fa-arrow-right"></i> <span data-i18n="googlesmarthome.label.ssloffload"></span></label> <input type="checkbox" id="node-config-input-ssloffload" style="display:inline-block; width:auto; vertical-align:top;"> </div> <div class="form-row hidden" id="ssloffloadkeys" style="background: #fbfbfb"> <div class="form-row"> <label for="node-config-input-publickey"><i class="fa fa-folder"></i> <span data-i18n="googlesmarthome.label.publickey"></span></label> <input type="text" id="node-config-input-publickey"> </div> <div class="form-row"> <label for="node-config-input-privatekey"><i class="fa fa-folder"></i> <span data-i18n="googlesmarthome.label.privatekey"></span></label> <input type="text" id="node-config-input-privatekey"> </div> </div> </div> </fieldset> <fieldset> <legend>Local Fulfillment</legend> <div class="form-row"> <label for="node-config-input-local_scan_type"><i class="fa fa-tag"></i> <span data-i18n="googlesmarthome.label.local_scan_type"></span></label> <select id="node-config-input-local_scan_type" > <option value='' data-i18n='googlesmarthome.label.disabled'></option> <option value='MDNS' data-i18n='googlesmarthome.label.MDNS'></option> <option value='UDP' data-i18n='googlesmarthome.label.UDP'></option> </select> </div> <div class="form-row hidden" id="local_fulfillment" style="background: #fbfbfb"> <div class="form-row" id="local_scan_port"> <label for="node-config-input-local_scan_port"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.local_scan_port"></span></label> <input type="text" id="node-config-input-local_scan_port" data-i18n="[placeholder]googlesmarthome.placeholder.local_scan_port"> </div> <div class="form-row"> <label for="node-config-input-localport"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.localport"></span></label> <input type="text" id="node-config-input-localport" data-i18n="[placeholder]googlesmarthome.placeholder.localport"> </div> </div> </fieldset> <fieldset> <legend data-i18n="googlesmarthome.label.advanced_settings"></legend> <div class="form-row"> <label for="node-config-input-accesstokenduration"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.accesstokenduration"></span></label> <input type="text" id="node-config-input-accesstokenduration" data-i18n="[placeholder]googlesmarthome.placeholder.accesstokenduration"> </div> <div class="form-row"> <label for="node-config-input-reportinterval"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.reportinterval"></span></label> <input type="text" id="node-config-input-reportinterval" data-i18n="[placeholder]googlesmarthome.placeholder.reportinterval"> </div> <div class="form-row"> <label for="node-config-input-request_sync_delay"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.request_sync_delay"></span></label> <input type="text" id="node-config-input-request_sync_delay" data-i18n="[placeholder]googlesmarthome.placeholder.request_sync_delay"> </div> <div class="form-row"> <label for="node-config-input-set_state_delay"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.set_state_delay"></span></label> <input type="text" id="node-config-input-set_state_delay" data-i18n="[placeholder]googlesmarthome.placeholder.set_state_delay"> </div> </fieldset> </script> <script type="text/javascript"> (function () { function isNonNegativeInteger(v) { const n = parseInt(v); const f = parseFloat(v); return !isNaN(v) && Number.isInteger(f) && n >= 0; } function isOptionalNonNegativeInteger(v) { return v.trim().length == 0 || isNonNegativeInteger(v); } RED.nodes.registerType('googlesmarthome-client', { category: 'config', defaults: { name: { value: "" }, enabledebug: { value: false }, default_lang: { value: "en", required: true, }, usegooglelogin: { value: false }, /* loginclientid: { value:"", required:false, validate: function(v) { let usegoogleloginField = $("#node-config-input-usegooglelogin"); let usegooglelogin = usegoogleloginField.length > 0 ? usegoogleloginField.prop('checked') : this.usegooglelogin; return usegooglelogin ? v.trim().length > 0 : true; } }, emails: { value:[], required:false, validate: function(v) { let usegoogleloginField = $("#node-config-input-usegooglelogin"); let usegooglelogin = usegoogleloginField.length > 0 ? usegoogleloginField.prop('checked') : this.usegooglelogin; return usegooglelogin ? v.trim().length > 0 : true; } }, username: { value:"", required:false, validate: function(v) { let usegoogleloginField = $("#node-config-input-usegooglelogin"); let usegooglelogin = usegoogleloginField.length > 0 ? usegoogleloginField.prop('checked') : this.usegooglelogin; return !usegooglelogin ? v.trim().length > 0 : true; } }, password: { value:"", required:false, validate: function(v) { let usegoogleloginField = $("#node-config-input-usegooglelogin"); let usegooglelogin = usegoogleloginField.length > 0 ? usegoogleloginField.prop('checked') : this.usegooglelogin; return !usegooglelogin ? v.trim().length > 0 : true; } }, */ usehttpnoderoot: { value: false }, port: { value: 3001, required: false, validate: RED.validators.number(true) }, httppath: { value: "", required: false, validate: RED.validators.regex(/^[a-zA-Z0-9_\-\/]*$/), }, ssloffload: { value: false }, /* publickey: { value:"", required:false, validate: function(v) { let ssloffloadField = $("#node-config-input-ssloffload"); let ssloffload = ssloffloadField.length > 0 ? ssloffloadField.prop('checked') : this.ssloffload; let portField = $("#node-config-input-port"); let port = portField.length > 0 ? portField.val() : this.port; port = isNaN(port) ? 0 : +port; return ssloffload || port <= 0 ? true : v.trim().length > 0; } }, privatekey: { value:"", required:false, validate: function(v) { let ssloffloadField = $("#node-config-input-ssloffload"); let ssloffload = ssloffloadField.length > 0 ? ssloffloadField.prop('checked') : this.ssloffload; let portField = $("#node-config-input-port"); let port = portField.length > 0 ? portField.val() : this.port; port = isNaN(port) ? 0 : +port; return ssloffload || port <= 0 ? true : v.trim().length > 0; } }, */ local_scan_type: { value: "", required: false }, local_scan_port: { required: false, validate: RED.validators.number(true) }, localport: { required: false, validate: RED.validators.number(true) }, /* jwtkey: { value:"", required:true }, clientid: { value:"", required:true }, clientsecret: { value:"", required:true } */ accesstokenduration: { value: 60, required: true, validate: RED.validators.number(true) }, reportinterval: { value: 60, required: true, validate: RED.validators.number(true) }, request_sync_delay: { value: '', required: false, validate: isOptionalNonNegativeInteger }, set_state_delay: { value: '', required: false, validate: isOptionalNonNegativeInteger } }, credentials: { loginclientid: { type: "text" }, emails: { type: "text" }, username: { type: "text" }, password: { type: "password" }, publickey: { type: "text" }, privatekey: { type: "text" }, jwtkey: { type: "text" }, clientid: { type: "text" }, clientsecret: { type: "password" }, }, color: "#3FADB5", icon: "google-smarthome.png", label: function () { return this.name || "googlesmarthome-client"; }, labelStyle: function () { return this.name ? "node_label_italic" : ""; }, oneditprepare: function () { // emails $('#node-config-input-googlesmarthome-emails-container').css('min-height', '150px').css('min-width', '250px').editableList({ addItem: function (container, i, opt) { let emails = opt; if (!Object.prototype.hasOwnProperty.call(emails, 'name')) { emails = { name: "" }; } container.css({ overflow: 'hidden', whiteSpace: 'nowrap' }); let fragment = document.createDocumentFragment(); let row1 = $('<div/>', { style: "display:flex;" }).appendTo(fragment); let propertyName = $('<input/>', { class: "node-config-input-googlesmarthome-emails-property-name", type: "text", style: "width: 100%" }) .appendTo(row1); propertyName.val(emails.name); container[0].appendChild(fragment); }, removable: true, sortable: true }); let emails = this.credentials.emails; if (typeof emails === 'string') { emails = emails.split(";"); } if (!emails) { emails = []; } for (let i = 0; i < emails.length; i++) { let email = emails[i]; $("#node-config-input-googlesmarthome-emails-container").editableList('addItem', { name: email }); } // Use Google login on / off let useGoogleLogin = function () { let usegooglelogin = $("#node-config-input-usegooglelogin").prop('checked'); if (usegooglelogin == false) { $("#usegooglelogin").hide(); $("#useloginpwd").show(); } else { $("#usegooglelogin").show(); $("#useloginpwd").hide(); } }; useGoogleLogin(); $("#node-config-input-usegooglelogin").change(useGoogleLogin); // Use external SSL offload on / off let sslOffLoadKeys = function () { let ssloffload = $("#node-config-input-ssloffload").prop('checked'); if (ssloffload == false) { $("#ssloffloadkeys").show(); } else { $("#ssloffloadkeys").hide(); } }; sslOffLoadKeys(); $("#node-config-input-ssloffload").change(sslOffLoadKeys); // Use same NODE-Red port let useNODERedPort = function () { let input_port = $('#node-config-input-port').val(); input_port = isNaN(input_port) ? 0 : +input_port; if ((input_port <= 0)) { // || (RED.settings.uiPort === node)) { $('#connectioninfo').hide(); } else { $('#connectioninfo').show(); } }; useNODERedPort(); $('#node-config-input-port').change(useNODERedPort); // Local FulFillment let localFulFillment = function () { let scan_type = $('#node-config-input-local_scan_type').val(); if (scan_type === 'MDNS') { $('#local_fulfillment').show(); $('#local_scan_port').hide(); } else if (scan_type === 'UDP') { $('#local_fulfillment').show(); $('#local_scan_port').show(); } else { $('#local_fulfillment').hide(); } }; localFulFillment(); $('#node-config-input-local_scan_type').change(localFulFillment); }, oneditsave: function () { let me = this; // emails let emails = $("#node-config-input-googlesmarthome-emails-container").editableList('items'); me.credentials.emails = []; emails.each(function (i) { let email = $(this); let name = email.find(".node-config-input-googlesmarthome-emails-property-name").val(); me.credentials.emails.push(name); }); }, oneditresize: function (size) { // emails let rows = $("#dialog-form>div:not(node-config-input-googlesmarthome-emails-container-row)"); let height = size.height; for (let i = 0; i < rows.length; i++) { height -= $(rows[i]).outerHeight(true); } let editorRow = $("#dialog-form>div.node-config-input-googlesmarthome-emails-container-row"); height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom"))); height += 16; $("#node-config-input-googlesmarthome-emails-container").editableList('height', height); } }); })(); </script>