node-red-contrib-xmihome
Version:
Node-RED nodes for controlling Xiaomi Mi Home devices using the xmihome library.
98 lines (97 loc) • 8.97 kB
HTML
<script type="text/javascript">
var RED=window.RED,node=null;function validate(){let isUsername=!!$("#node-config-input-username").val(),isPassword=!!$("#node-config-input-password").val();return isUsername==isPassword}function updateLoginButtonState(){let loginButton=$("#node-config-button-login"),isUsername=!!$("#node-config-input-username").val(),isPassword=!!$("#node-config-input-password").val();if(isUsername&&isPassword)loginButton.prop("disabled",!1).removeClass("red-ui-button-disabled");else loginButton.prop("disabled",!0).addClass("red-ui-button-disabled")}function disableDoneButton(){$("#node-config-dialog-ok").prop("disabled",!0).addClass("disabled")}function enableDoneButton(){$("#node-config-dialog-ok").prop("disabled",!1).removeClass("disabled")}function toggleAuthSource(){let manualGroup=$("#auth-manual-group"),fileGroup=$("#auth-file-group");if($(this).val()==="file")manualGroup.hide(),fileGroup.show();else manualGroup.show(),fileGroup.hide()}RED.nodes.registerType("xmihome-config",{category:"config",defaults:{name:{value:""},credentialsFile:{value:""},debug:{value:!1},connectionType:{value:"auto"},credentialsValid:{value:!0,validate}},credentials:{username:{type:"text",validate},password:{type:"password",validate},country:{type:"text"},userId:{type:"text"},ssecurity:{type:"text"},serviceToken:{type:"text"}},label:function(){return this.name||"XiaomiMiHome"},oneditprepare:function(){node=this;let loginButton=$("#node-config-button-login"),credsFile=$("#node-config-input-credentialsFile");$("#node-config-input-username, #node-config-input-password").on("input keyup change",updateLoginButtonState),$("#node-config-input-authSource").on("change",toggleAuthSource).val(credsFile.val()?"file":"manual").trigger("change");function performAuthRequest(url,payload){disableDoneButton(),$.ajax({url,type:"POST",contentType:"application/json",data:JSON.stringify(payload),timeout:360000,success:function(data){if(data.status==="success")RED.nodes.dirty(!0),RED.notify(node._("config.dialog.loginSuccess"),"success"),enableDoneButton();else if(data.status==="2fa_required")open2FADialog(data);else if(data.status==="captcha_required")openCaptchaDialog(data)},error:function(jqXHR){let message=jqXHR.responseJSON?.error||jqXHR.statusText;console.error("[Xiaomi Login Error]:",message),alert(`${node._("config.dialog.errorLoginFailed")}:
${JSON.stringify(message)}`),enableDoneButton()},complete:function(){loginButton.prop("disabled",!1).removeClass("red-ui-button-disabled")}})}function open2FADialog({stateToken}){let dialogTemplate=$("#xmihome-2fa-dialog-template").html(),dialog=$('<div id="xmihome-2fa-dialog-container"></div>').html(dialogTemplate);dialog.find("[data-i18n]").each(function(){$(this).text(node._($(this).attr("data-i18n")))}),dialog.dialog({title:node._("config.dialog.2faTitle"),modal:!0,width:560,buttons:[{text:node._("config.dialog.buttonSubmit"),class:"primary",click:function(){let ticket=$("#xmihome-2fa-ticket-input").val();if(ticket)$(this).siblings(".ui-dialog-buttonpane").find("button").prop("disabled",!0),$(this).dialog("close"),performAuthRequest(`xmihome/${node.id}/auth/submit_ticket`,{stateToken,ticket})}},{text:node._("config.dialog.buttonCancel"),click:function(){$(this).dialog("close")}}],close:function(){dialog.remove(),enableDoneButton()}})}function openCaptchaDialog({stateToken,imageB64}){let dialogTemplate=$("#xmihome-captcha-dialog-template").html(),dialog=$('<div id="xmihome-captcha-dialog-container"></div>').html(dialogTemplate);dialog.find("[data-i18n]").each(function(){$(this).text(node._($(this).attr("data-i18n")))}),dialog.find("#xmihome-captcha-image").attr("src",imageB64),dialog.dialog({title:node._("config.dialog.captchaTitle"),modal:!0,width:450,buttons:[{text:node._("config.dialog.buttonSubmit"),class:"primary",click:function(){let captCode=$("#xmihome-captcha-input").val();if(captCode)$(this).siblings(".ui-dialog-buttonpane").find("button").prop("disabled",!0),$(this).dialog("close"),performAuthRequest(`xmihome/${node.id}/auth/submit_captcha`,{stateToken,captCode})}},{text:node._("config.dialog.buttonCancel"),click:function(){$(this).dialog("close")}}],close:function(){dialog.remove(),enableDoneButton()}})}loginButton.on("click",function(e){e.preventDefault();let username=$("#node-config-input-username").val(),password=$("#node-config-input-password").val(),country=$("#node-config-input-country").val();loginButton.prop("disabled",!0).addClass("red-ui-button-disabled"),performAuthRequest(`xmihome/${node.id}/auth`,{username,password,country})}),updateLoginButtonState()}});
</script>
<script type="text/html" data-template-name="xmihome-config">
<div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="config.label.name"></span></label>
<input type="text" id="node-config-input-name" data-i18n="[placeholder]config.placeholder.name" />
</div>
<div class="form-row">
<label style="width:auto" for="node-config-input-debug"><i class="fa fa-bug"></i> <span data-i18n="config.label.debug"></span></label>
<input type="checkbox" id="node-config-input-debug" style="display:inline-block; width:auto; vertical-align:top;" />
</div>
<fieldset>
<legend data-i18n="config.label.authentication"></legend>
<div class="form-row">
<label for="node-config-input-authSource"><i class="fa fa-cogs"></i> <span data-i18n="config.label.authSource"></span></label>
<select id="node-config-input-authSource" style="width: 70%;">
<option value="manual" data-i18n="config.label.sourceManual"></option>
<option value="file" data-i18n="config.label.sourceFile"></option>
</select>
</div>
<div id="auth-manual-group">
<div class="form-row">
<label for="node-config-input-username"><i class="fa fa-user"></i> <span data-i18n="config.label.username"></span></label>
<input type="text" id="node-config-input-username" data-i18n="[placeholder]config.placeholder.username" />
</div>
<div class="form-row">
<label for="node-config-input-password"><i class="fa fa-key"></i> <span data-i18n="config.label.password"></span></label>
<input type="password" id="node-config-input-password" />
</div>
<div class="form-row">
<label for="node-config-input-country"><i class="fa fa-globe"></i> <span data-i18n="config.label.country"></span></label>
<select id="node-config-input-country" style="width: 70%">
<option value='sg'>sg</option>
<option value='cn'>cn</option>
<option value='ru'>ru</option>
<option value='us'>us</option>
<option value='tw'>tw</option>
<option value='de'>de</option>
</select>
</div>
<div class="form-row">
<label> </label>
<button class="red-ui-button" id="node-config-button-login" style="width: 70%;">
<i class="fa fa-sign-in"></i>
<span data-i18n="config.label.login"></span>
</button>
</div>
</div>
<div id="auth-file-group">
<div class="form-row">
<label for="node-config-input-credentialsFile"><i class="fa fa-file-text-o"></i> <span data-i18n="config.label.credentialsFile"></span></label>
<input type="text" id="node-config-input-credentialsFile" data-i18n="[placeholder]config.placeholder.credentialsFile" style="width: 70%;" />
</div>
<div class="form-tips" data-i18n="[html]config.tip.credentialsFile"></div>
</div>
<fieldset>
<legend data-i18n="config.label.defaults"></legend>
<div class="form-row">
<label for="node-config-input-connectionType"><i class="fa fa-plug"></i> <span data-i18n="config.label.connectionType"></span></label>
<select id="node-config-input-connectionType" style="width: 70%">
<option value="auto" data-i18n="config.label.connAuto"></option>
<option value="cloud" data-i18n="config.label.connCloud"></option>
<option value="miio" data-i18n="config.label.connMiio"></option>
<option value="bluetooth" data-i18n="config.label.connBluetooth"></option>
</select>
</div>
</fieldset>
</fieldset>
<template id="xmihome-2fa-dialog-template">
<div class="form-row">
<p data-i18n="config.dialog.instructionsTitle" style="margin-top: 0;"></p>
</div>
<div class="form-row">
<p data-i18n="config.dialog.instruction1"></p>
</div>
<div class="form-row">
<label for="xmihome-2fa-ticket-input" style="width: auto; margin-bottom: 5px;">
<i class="fa fa-ticket"></i> <span data-i18n="config.dialog.instruction2"></span>
</label>
<input type="text" id="xmihome-2fa-ticket-input" style="width: 100%;" autocomplete="one-time-code" />
</div>
</template>
<template id="xmihome-captcha-dialog-template">
<div class="form-row">
<p data-i18n="config.dialog.captchaInstructions" style="margin-top: 0;"></p>
<img id="xmihome-captcha-image" src="" />
</div>
<div class="form-row">
<label for="xmihome-captcha-input">
<i class="fa fa-keyboard-o"></i> <span data-i18n="config.dialog.captchaInputLabel"></span>
</label>
<input type="text" id="xmihome-captcha-input" style="width: 70%;" autocomplete="off" />
</div>
</template>
</script>