Code Refactor/Cleanup Web UI (#942)

* Refactor Web UI
This commit is contained in:
tsightler
2024-11-27 00:38:28 -05:00
committed by GitHub
parent c789296798
commit 4dc404edd5
10 changed files with 765 additions and 407 deletions

View File

@@ -1,28 +1,29 @@
export * from './exithandler.js'
export * from './mqtt.js'
import './processhandlers.js'
import './mqtt.js'
import state from './state.js'
import ring from './ring.js'
import utils from './utils.js'
import tokenApp from './tokenapp.js'
import webui from './webui.js'
import chalk from 'chalk'
import isOnline from 'is-online'
import debugModule from 'debug'
const debug = debugModule('ring-mqtt')
export default new class Main {
constructor() {
// Hack to suppress spurious message from push-receiver during startup
console.warn = (data) => {
if (data.includes('PHONE_REGISTRATION_ERROR') ||
data.startsWith('Retry...') ||
data.includes('Message dropped as it could not be decrypted:')
) {
return
console.warn = (message) => {
const suppressedMessages = [
/^Retry\.\.\./,
/PHONE_REGISTRATION_ERROR/,
/Message dropped as it could not be decrypted:/
]
if (!suppressedMessages.some(suppressedMessages => suppressedMessages.test(message))) {
console.error(message)
}
console.error(data)
}
// Start event listeners
utils.event.on('generated_token', (generatedToken) => {
this.init(generatedToken)
})
@@ -33,34 +34,45 @@ export default new class Main {
async init(generatedToken) {
if (!state.valid) {
await state.init()
tokenApp.setSystemId(state.data.systemId)
}
// Is there any usable token?
if (state.data.ring_token || generatedToken) {
// Wait for the network to be online and then attempt to connect to the Ring API using the token
const hasToken = state.data.ring_token || generatedToken
if (!hasToken) {
this.handleNoToken()
return
}
await this.waitForNetwork()
await this.attemptRingConnection(generatedToken)
}
async waitForNetwork() {
while (!(await isOnline())) {
debug(chalk.yellow('Network is offline, waiting 10 seconds to check again...'))
await utils.sleep(10)
}
}
async attemptRingConnection(generatedToken) {
if (!await ring.init(state, generatedToken)) {
debug(chalk.red('Failed to connect to Ring API using saved token, generate a new token using the Web UI'))
debug(chalk.red('or wait 60 seconds to automatically retry authentication using the existing token'))
tokenApp.start()
webui.start(state.data.systemId)
await utils.sleep(60)
if (!ring.client) {
debug(chalk.yellow('Retrying authentication with existing saved token...'))
this.init()
}
}
} else {
}
handleNoToken() {
if (process.env.RUNMODE === 'addon') {
debug(chalk.red('No refresh token was found in state file, generate a token using the addon Web UI'))
} else {
tokenApp.start()
webui.start(state.data.systemId)
debug(chalk.red('No refresh token was found in the state file, use the Web UI at http://<host_ip_address>:55123/ to generate a token.'))
}
}
}
}

View File

@@ -4,13 +4,12 @@ import ring from './ring.js'
import debugModule from 'debug'
const debug = debugModule('ring-mqtt')
export default new class ExitHandler {
export default new class ProcessHandlers {
constructor() {
this.init()
}
init() {
// Setup Exit Handlers
process.on('exit', this.processExit.bind(null, 0))
process.on('SIGINT', this.processExit.bind(null, 0))
process.on('SIGTERM', this.processExit.bind(null, 0))

View File

@@ -1,123 +0,0 @@
import { RingRestClient } from 'ring-client-api/rest-client'
import utils from './utils.js'
import { dirname } from 'path'
import { fileURLToPath } from 'url'
import express from 'express'
import bodyParser from 'body-parser'
import chalk from 'chalk'
import debugModule from 'debug'
const debug = debugModule('ring-mqtt')
export default new class TokenApp {
constructor() {
this.app = express()
this.listener = false
this.ringConnected = false
this.systemId = ''
if (process.env.RUNMODE === 'addon') {
this.start()
}
utils.event.on('ring_api_state', async (state) => {
if (state === 'connected') {
this.ringConnected = true
// Only the addon leaves the web UI running all the time
if (process.env.RUNMODE !== 'addon') {
this.stop()
}
} else {
this.ringConnected = false
}
})
}
setSystemId(systemId) {
if (systemId) {
this.systemId = systemId
}
}
// Super simple web service to acquire authentication tokens from Ring
async start() {
if (this.listener) {
return
}
const webdir = dirname(fileURLToPath(new URL('.', import.meta.url)))+'/web'
let restClient
this.listener = this.app.listen(55123, () => {
debug('Succesfully started the token generator web UI')
})
this.app.use(bodyParser.urlencoded({ extended: false }))
this.app.get('/', (req, res) => {
res.cookie('systemId', `${process.env.RUNMODE === 'addon' ? 'ring-mqtt-addon' : 'ring-mqtt'}-${this.systemId.slice(-5)}`, { maxAge: 3600000, encode: String })
if (this.ringConnected) {
res.sendFile('connected.html', {root: webdir})
} else {
res.sendFile('account.html', {root: webdir})
}
})
this.app.get(/.*force-token-generation$/, (req, res) => {
res.cookie('systemId', `${process.env.RUNMODE === 'addon' ? 'ring-mqtt-addon' : 'ring-mqtt'}-${this.systemId.slice(-5)}`, { maxAge: 3600000, encode: String })
res.sendFile('account.html', {root: webdir})
})
this.app.post(/.*submit-account$/, async (req, res) => {
res.cookie('systemId', `${process.env.RUNMODE === 'addon' ? 'ring-mqtt-addon' : 'ring-mqtt'}-${this.systemId.slice(-5)}`, { maxAge: 3600000, encode: String })
const email = req.body.email
const password = req.body.password
restClient = await new RingRestClient({
email,
password,
controlCenterDisplayName: `${process.env.RUNMODE === 'addon' ? 'ring-mqtt-addon' : 'ring-mqtt'}-${this.systemId.slice(-5)}`,
systemId: this.systemId
})
// Check if the user/password was accepted
try {
await restClient.getCurrentAuth()
} catch(error) {
if (restClient.using2fa) {
debug('Username/Password was accepted, waiting for 2FA code to be entered.')
res.sendFile('code.html', {root: webdir})
} else {
const errmsg = error.message ? error.message : 'Null response, you may be temporarily throttled/blocked. Please shut down ring-mqtt and try again in a few hours.'
debug(chalk.red(errmsg))
res.cookie('error', errmsg, { maxAge: 1000, encode: String })
res.sendFile('account.html', {root: webdir})
}
}
})
this.app.post(/.*submit-code$/, async (req, res) => {
res.cookie('systemId', `${process.env.RUNMODE === 'addon' ? 'ring-mqtt-addon' : 'ring-mqtt'}-${this.systemId.slice(-5)}`, { maxAge: 3600000, encode: String })
let generatedToken
const code = req.body.code
try {
generatedToken = await restClient.getAuth(code)
} catch {
generatedToken = false
const errormsg = 'The 2FA code was not accepted, please verify the code and try again.'
debug(errormsg)
res.cookie('error', errormsg, { maxAge: 1000, encode: String })
res.sendFile('code.html', {root: webdir})
}
if (generatedToken) {
res.sendFile('restart.html', {root: webdir})
utils.event.emit('generated_token', generatedToken.refresh_token)
}
})
}
async stop() {
if (this.listener) {
await this.listener.close()
this.listener = false
}
}
}

View File

@@ -3,66 +3,67 @@ import dns from 'dns'
import os from 'os'
import { promisify } from 'util'
import { EventEmitter } from 'events'
import debugModule from 'debug'
const debug = {
mqtt: debugModule('ring-mqtt'),
attr: debugModule('ring-attr'),
disc: debugModule('ring-disc'),
rtsp: debugModule('ring-rtsp'),
wrtc: debugModule('ring-wrtc')
import debug from 'debug'
const debuggers = {
mqtt: debug('ring-mqtt'),
attr: debug('ring-attr'),
disc: debug('ring-disc'),
rtsp: debug('ring-rtsp'),
wrtc: debug('ring-wrtc')
}
export default new class Utils {
class Utils {
constructor() {
this.event = new EventEmitter()
this.dnsLookup = promisify(dns.lookup)
this.dnsLookupService = promisify(dns.lookupService)
}
config() {
return config.data
}
// Sleep function (seconds)
sleep(sec) {
return this.msleep(sec * 1000)
}
// Sleep function (milliseconds)
msleep(msec) {
return new Promise(res => setTimeout(res, msec))
}
// Return ISO time from epoch without milliseconds
getISOTime(epoch) {
return new Date(epoch).toISOString().slice(0,-5)+"Z"
return new Date(epoch).toISOString().slice(0, -5) + 'Z'
}
async getHostFqdn() {
const pLookupService = promisify(dns.lookupService)
try {
return (await pLookupService(await this.getHostIp(), 0)).hostname
} catch {
console.log('Failed to resolve FQDN, using os.hostname() instead')
const ip = await this.getHostIp()
const { hostname } = await this.dnsLookupService(ip, 0)
return hostname
} catch (error) {
console.warn('Failed to resolve FQDN, using os.hostname() instead:', error.message)
return os.hostname()
}
}
async getHostIp() {
try {
const pLookup = promisify(dns.lookup)
return (await pLookup(os.hostname())).address
} catch {
console.log('Failed to resolve hostname IP address, returning localhost instead')
const { address } = await this.dnsLookup(os.hostname())
return address
} catch (error) {
console.warn('Failed to resolve hostname IP address, returning localhost instead:', error.message)
return 'localhost'
}
}
isNumeric(num) {
return !isNaN(parseFloat(num)) && isFinite(num);
return !isNaN(parseFloat(num)) && isFinite(num)
}
debug(message, debugType) {
debugType = debugType ? debugType : 'mqtt'
debug[debugType](message)
debug(message, debugType = 'mqtt') {
debuggers[debugType]?.(message)
}
}
export default new Utils()

118
lib/webui.js Normal file
View File

@@ -0,0 +1,118 @@
import { RingRestClient } from 'ring-client-api/rest-client'
import utils from './utils.js'
import { dirname } from 'path'
import { fileURLToPath } from 'url'
import express from 'express'
import bodyParser from 'body-parser'
import chalk from 'chalk'
import debugModule from 'debug'
const PORT = 55123
const COOKIE_MAX_AGE = 3600000
const debug = debugModule('ring-mqtt')
class WebUI {
constructor() {
this.app = express()
this.listener = null
this.ringConnected = false
this.webdir = dirname(fileURLToPath(new URL('.', import.meta.url))) + '/web'
if (process.env.RUNMODE === 'addon') {
this.start()
}
this.initializeEventListeners()
}
initializeEventListeners() {
utils.event.on('ring_api_state', async (state) => {
this.ringConnected = state === 'connected'
if (this.ringConnected && process.env.RUNMODE !== 'addon') {
await this.stop()
}
})
}
async handleAccountSubmission(req, res, restClient) {
try {
await restClient.getCurrentAuth()
} catch (error) {
if (restClient.using2fa) {
debug('Username/Password was accepted, waiting for 2FA code to be entered.')
return res.sendFile('code.html', { root: this.webdir })
}
const errorMessage = error.message || 'Null response, you may be temporarily throttled/blocked. Please shut down ring-mqtt and try again in a few hours.'
debug(chalk.red(errorMessage))
res.cookie('error', errorMessage, { maxAge: 1000 })
return res.sendFile('account.html', { root: this.webdir })
}
}
async handleCodeSubmission(req, res, restClient) {
try {
const generatedToken = await restClient.getAuth(req.body.code)
if (generatedToken) {
utils.event.emit('generated_token', generatedToken.refresh_token)
return res.sendFile('restart.html', { root: this.webdir })
}
} catch (error) {
const errorMessage = error.message || 'The 2FA code was not accepted, please verify the code and try again.'
debug(chalk.red(errorMessage))
res.cookie('error', errorMessage, { maxAge: 1000 })
return res.sendFile('code.html', { root: this.webdir })
}
}
setupRoutes() {
this.app.use(bodyParser.urlencoded({ extended: false }))
this.app.get(['/', /.*force-token-generation$/], (req, res) => {
res.cookie('displayName', this.displayName, { maxAge: COOKIE_MAX_AGE })
const template = this.ringConnected ? 'connected.html' : 'account.html'
res.sendFile(template, { root: this.webdir })
})
let restClient
this.app.post(/.*submit-account$/, async (req, res) => {
res.cookie('displayName', this.displayName, { maxAge: COOKIE_MAX_AGE })
restClient = new RingRestClient({
email: req.body.email,
password: req.body.password,
controlCenterDisplayName: this.displayName,
systemId: this.systemId
})
await this.handleAccountSubmission(req, res, restClient)
})
this.app.post(/.*submit-code$/, async (req, res) => {
res.cookie('displayName', this.displayName, { maxAge: COOKIE_MAX_AGE })
await this.handleCodeSubmission(req, res, restClient)
})
}
async start(systemId) {
if (this.listener) {
return
}
this.systemId = systemId
this.displayName = `${process.env.RUNMODE === 'addon' ? 'ring-mqtt-addon' : 'ring-mqtt'}-${systemId.slice(-5)}`
this.setupRoutes()
this.listener = this.app.listen(PORT, () => {
debug('Successfully started the ring-mqtt web UI')
})
}
async stop() {
if (this.listener) {
await this.listener.close()
this.listener = null
}
}
}
export default new WebUI()

View File

@@ -1,2 +1,2 @@
#!/usr/bin/env node
export * from './lib/main.js'
import './lib/main.js'

View File

@@ -1,91 +1,199 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ring-MQTT Web Authenticator</title>
<style>
body {font-family: Arial, Helvetica, sans-serif; max-width: 500px; color: white; background-color: gray;}
* {box-sizing: border-box;}
input[type=text], select, textarea {
width: 100%;
padding: 12px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
margin-top: 6px;
margin-bottom: 16px;
resize: vertical;
:root {
--primary-color: #47a9e6;
--primary-hover: #315b82;
--bg-color: #f2f2f2;
--error-color: #dc3545;
--success-color: #39ff14;
--text-color: #333;
--spacing: 1rem;
}
input[type=password], select, textarea {
width: 100%;
padding: 12px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
margin-top: 6px;
margin-bottom: 16px;
resize: vertical;
}
input[type=submit] {
background-color: #47a9e6;
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, sans-serif;
max-width: 500px;
margin: 0 auto;
padding: var(--spacing);
color: white;
padding: 12px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
input[type=submit]:hover {
background-color: #315b82;
background-color: #666;
line-height: 1.6;
}
.container {
max-width: 500px;
border-radius: 5px;
background-color: #f2f2f2;
padding: 20px;
color: black;
background-color: var(--bg-color);
padding: calc(var(--spacing) * 1.5);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
color: var(--text-color);
margin-top: var(--spacing);
}
h2 {
margin: 0 0 var(--spacing) 0;
}
h3 {
margin: 0 0 var(--spacing) 0;
}
.container h3 {
margin-top: 0;
}
.display-name {
margin: calc(var(--spacing) / 2) 0;
padding: 0;
border-radius: 4px;
display: flex;
align-items: center;
gap: 8px;
}
.display-name span {
color: var(--success-color);
font-weight: 500;
}
.error-message {
background-color: var(--error-color);
color: white;
padding: calc(var(--spacing) / 2);
border-radius: 4px;
margin: var(--spacing) 0;
}
.form-group {
margin-bottom: var(--spacing);
}
.form-group label {
display: block;
margin-bottom: calc(var(--spacing) / 2);
font-weight: 500;
}
.form-control {
width: 100%;
padding: calc(var(--spacing) / 2);
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.2s;
}
.form-control:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(71, 169, 230, 0.2);
}
.password-toggle {
display: flex;
align-items: center;
gap: calc(var(--spacing) / 2);
margin: calc(var(--spacing) / 2) 0;
user-select: none;
}
.btn {
background-color: var(--primary-color);
color: white;
padding: calc(var(--spacing) / 2) var(--spacing);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s;
width: 100%;
}
.btn:hover {
background-color: var(--primary-hover);
}
.btn:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(71, 169, 230, 0.4);
}
@media (max-width: 600px) {
body {
padding: calc(var(--spacing) / 2);
}
.container {
padding: var(--spacing);
}
}
</style>
</head>
<body>
<h2>Ring-MQTT Authentication</h2>
<div id="displayName" class="display-name">Device Name: <span></span></div>
<p id="errormsg" class="error-message" style="display: none;"></p>
<h2>Acquire Refresh Token</h2>
<h3 id="systemId"></h3>
Please enter your Ring account information below and the 2FA code on the following page to generate the refresh token required for this application to access the Ring API.<br>
<p style="background-color:red" id="errormsg"></p>
<h3>Login</h3>
<div class="container">
<h3>Ring Account Info</h3>
<form action="./submit-account" method="post">
<label for="email">Email</label>
<input type="text" id="email" name="email">
<div class="form-group">
<label for="email">Email Address</label>
<input type="email"
id="email"
name="email"
class="form-control"
autocomplete="email"
required
aria-required="true">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password">
<input type="checkbox" onclick="showPassword()">Show Password
<br><br>
<input type="submit" value="Submit">
<input type="password"
id="password"
name="password"
class="form-control"
autocomplete="current-password"
required
aria-required="true">
<div class="password-toggle">
<input type="checkbox" id="showPassword">
<label for="showPassword">Show Password</label>
</div>
</div>
<button type="submit" class="btn">Submit</button>
</form>
</div>
<script>
function showPassword() {
var x = document.getElementById("password");
if (x.type === "password") {
x.type = "text";
} else {
x.type = "password";
}
}
// Password visibility toggle
document.getElementById('showPassword').addEventListener('change', function() {
const password = document.getElementById('password');
password.type = this.checked ? 'text' : 'password';
});
// Cookie handling
function getCookie(key) {
var keyValue = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
return keyValue ? keyValue[2] : null;
const value = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
return value ? decodeURIComponent(value[2]) : null;
}
if (getCookie('error')) {
document.getElementById("errormsg").innerHTML = getCookie('error')
// Error message handling
const errorMessage = getCookie('error');
const errorElement = document.getElementById('errormsg');
if (errorMessage) {
errorElement.style.display = 'block';
errorElement.textContent = errorMessage;
}
if (getCookie('systemId')) {
document.getElementById("systemId").innerHTML = `Device Name: <span style='color:chartreuse'> ${getCookie('systemId')}</span>`
// System ID display
const displayName = getCookie('displayName');
if (displayName) {
document.querySelector('#displayName span').textContent = displayName;
}
</script>
</body>

View File

@@ -1,66 +1,167 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ring-MQTT Web Authenticator</title>
<style>
body {font-family: Arial, Helvetica, sans-serif; max-width: 500px; color: white; background-color: gray;}
* {box-sizing: border-box;}
input[type=text], select, textarea {
width: 100%;
padding: 12px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
margin-top: 6px;
margin-bottom: 16px;
resize: vertical;
:root {
--primary-color: #47a9e6;
--primary-hover: #315b82;
--bg-color: #f2f2f2;
--error-color: #dc3545;
--success-color: #39ff14;
--text-color: #333;
--spacing: 1rem;
}
input[type=submit] {
background-color: #47a9e6;
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, sans-serif;
max-width: 500px;
margin: 0 auto;
padding: var(--spacing);
color: white;
padding: 12px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
input[type=submit]:hover {
background-color: #315b82;
background-color: #666;
line-height: 1.6;
}
.container {
max-width: 500px;
border-radius: 5px;
background-color: #f2f2f2;
padding: 20px;
color: black;
background-color: var(--bg-color);
padding: calc(var(--spacing) * 1.5);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
color: var(--text-color);
margin-top: var(--spacing);
}
h2 {
margin: 0 0 var(--spacing) 0;
}
h3 {
margin: 0 0 var(--spacing) 0;
}
.display-name {
margin: calc(var(--spacing) / 2) 0;
padding: 0;
border-radius: 4px;
display: flex;
align-items: center;
gap: 8px;
}
.display-name span {
color: var(--success-color);
font-weight: 500;
}
.error-message {
background-color: var(--error-color);
color: white;
padding: calc(var(--spacing) / 2);
border-radius: 4px;
margin: var(--spacing) 0;
}
.form-group {
margin-bottom: var(--spacing);
}
.form-group label {
display: block;
margin-bottom: calc(var(--spacing) / 2);
font-weight: 500;
}
.form-control {
width: 100%;
padding: calc(var(--spacing) / 2);
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.2s;
}
.form-control:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(71, 169, 230, 0.2);
}
.btn {
background-color: var(--primary-color);
color: white;
padding: calc(var(--spacing) / 2) var(--spacing);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s;
width: 100%;
}
.btn:hover {
background-color: var(--primary-hover);
}
.btn:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(71, 169, 230, 0.4);
}
@media (max-width: 600px) {
body {
padding: calc(var(--spacing) / 2);
}
.container {
padding: var(--spacing);
}
}
</style>
</head>
<body>
<h2>Ring-MQTT Two-Factor Authentication</h2>
<div id="displayName" class="display-name">Device Name: <span></span></div>
<p id="errormsg" class="error-message" style="display: none;"></p>
<h2>Enter 2FA Code</h2>
<h3 id="systemId"></h3>
<p style="background-color:Red;" id="errormsg"></p></br>
<div class="container">
<form action="./submit-code" method="post">
<label for="2facode">Code</label>
<input type="text" id="code" name="code">
<input type="submit" value="Submit">
<div class="form-group">
<label for="code">Enter 2FA Code</label>
<input type="text"
id="code"
name="code"
class="form-control"
autocomplete="one-time-code"
inputmode="numeric"
required
aria-required="true">
</div>
<button type="submit" class="btn">Submit</button>
</form>
</div>
<script>
// Cookie handling
function getCookie(key) {
var keyValue = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
return keyValue ? keyValue[2] : null;
const value = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
return value ? decodeURIComponent(value[2]) : null;
}
if (getCookie('error')) {
document.getElementById("errormsg").innerHTML = getCookie('error')
// Error message handling
const errorMessage = getCookie('error');
const errorElement = document.getElementById('errormsg');
if (errorMessage) {
errorElement.style.display = 'block';
errorElement.textContent = errorMessage;
}
if (getCookie('systemId')) {
document.getElementById("systemId").innerHTML = `Device Name: <span style='color:chartreuse'> ${getCookie('systemId')}</span>`
// System ID display
const displayName = getCookie('displayName');
if (displayName) {
document.querySelector('#displayName span').textContent = displayName;
}
</script>
</body>

View File

@@ -1,45 +1,113 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ring-MQTT Web Authenticator</title>
<style>
body {font-family: Arial, Helvetica, sans-serif; max-width: 500px; color: white; background-color: gray;}
input[type=submit] {
background-color: #47a9e6;
color: white;
padding: 12px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
:root {
--primary-color: #47a9e6;
--primary-hover: #315b82;
--bg-color: #f2f2f2;
--error-color: #dc3545;
--success-color: #39ff14;
--text-color: #333;
--spacing: 1rem;
}
input[type=submit]:hover {
background-color: #315b82;
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, sans-serif;
max-width: 500px;
margin: 0 auto;
padding: var(--spacing);
color: white;
background-color: #666;
line-height: 1.6;
}
h2 {
margin: 0 0 var(--spacing) 0;
}
h3 {
margin: 0 0 var(--spacing) 0;
}
.display-name {
margin: calc(var(--spacing) / 2) 0;
padding: 0;
border-radius: 4px;
display: flex;
align-items: center;
gap: 8px;
}
.display-name span {
color: var(--success-color);
font-weight: 500;
}
.container {
padding: 20px 5px;
margin-top: var(--spacing);
padding: var(--spacing) 0;
}
.message {
margin: var(--spacing) 0;
}
.btn {
background-color: var(--primary-color);
color: white;
padding: calc(var(--spacing) / 2) var(--spacing);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s;
min-width: 200px;
}
.btn:hover {
background-color: var(--primary-hover);
}
.btn:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(71, 169, 230, 0.4);
}
@media (max-width: 600px) {
body {
padding: calc(var(--spacing) / 2);
}
}
</style>
</head>
<body>
<h2>Ring Device Addon Connected</h2>
<h3 id="systemId"></h3>
It appears that this addon is already authenticated and connected to the Ring API, no additional action is required.
<h2>Ring-MQTT Addon Connected</h2>
<div id="displayName" class="display-name">Device Name: <span></span></div>
<div class="message">
It appears that <strong>ring-mqtt</strong> is already connected to the Ring API, no additional action is required.
</div>
<div class="message">
If you wish to force reauthentication, for example, to change the account used by this addon, clicking the button below will restart the authentication process.
</div>
<div class="container">
<form action="./force-token-generation" method="get">
<input type="submit" value="Force Reauthentication">
<input type="submit" value="Force Reauthentication" class="btn">
</form>
</div>
<script>
function getCookie(key) {
var keyValue = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
return keyValue ? keyValue[2] : null;
const value = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
return value ? decodeURIComponent(value[2]) : null;
}
if (getCookie('systemId')) {
document.getElementById("systemId").innerHTML = `Device Name: <span style='color:chartreuse'> ${getCookie('systemId')}</span>`
const displayName = getCookie('displayName');
if (displayName) {
document.querySelector('#displayName span').textContent = displayName;
}
</script>
</body>

View File

@@ -1,22 +1,96 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ring-MQTT Web Authenticator</title>
<style>
body {font-family: Arial, Helvetica, sans-serif; max-width: 500px; color: white; background-color: gray;}
:root {
--primary-color: #47a9e6;
--primary-hover: #315b82;
--bg-color: #f2f2f2;
--error-color: #dc3545;
--success-color: #39ff14;
--text-color: #333;
--spacing: 1rem;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, sans-serif;
max-width: 500px;
margin: 0 auto;
padding: var(--spacing);
color: white;
background-color: #666;
line-height: 1.6;
}
.container {
background-color: var(--bg-color);
padding: calc(var(--spacing) * 1.5);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
color: var(--text-color);
margin-top: var(--spacing);
}
h2 {
margin: 0 0 var(--spacing) 0;
color: var(--success-color);
}
.display-name {
margin: calc(var(--spacing) / 2) 0;
padding: 0;
border-radius: 4px;
display: flex;
align-items: center;
gap: 8px;
}
.display-name span {
color: var(--success-color);
font-weight: 500;
}
.message {
margin-top: var(--spacing);
font-size: 1.1rem;
}
strong {
color: var(--primary-color);
}
@media (max-width: 600px) {
body {
padding: calc(var(--spacing) / 2);
}
.container {
padding: var(--spacing);
}
}
</style>
</head>
<body>
<h2>Refresh Token Generation Complete</h2>
<h3 id="systemId"></h3>
The <b>Home Assistant ring-mqtt Add-on</b> will now automatically connect using the generated token. No additional configuration steps are required, please check the addon logs to monitor progress.
<h2>Ring-MQTT Authentication Complete</h2>
<div id="displayName" class="display-name">Device Name: <span></span></div>
<div class="message">
Authentication with Ring was successful and <strong>ring-mqtt</strong> will now attempt to connect to Ring servers. No additional steps are required, please review the ring-mqtt logs to monitor progress.
</div>
<script>
// Cookie handling
function getCookie(key) {
var keyValue = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
return keyValue ? keyValue[2] : null;
const value = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
return value ? decodeURIComponent(value[2]) : null;
}
if (getCookie('systemId')) {
document.getElementById("systemId").innerHTML = `Device Name: <span style='color:chartreuse'> ${getCookie('systemId')}</span>`
// System ID display
const displayName = getCookie('displayName');
if (displayName) {
document.querySelector('#displayName span').textContent = displayName;
}
</script>
</body>