Skip to content

Commit

Permalink
Add vanilla js homebridge ui plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
dgreif committed Aug 19, 2023
1 parent 7cf663a commit 7b9fbec
Show file tree
Hide file tree
Showing 7 changed files with 4,080 additions and 2,801 deletions.
5 changes: 5 additions & 0 deletions .changeset/weak-falcons-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'homebridge-ring': patch
---

Switched from a React-based UI to a simple JS UI for the homebridge-ring plugin UI. This should reduce the package size and simplify development. No visual changes should be noticiable in the UI.
14 changes: 14 additions & 0 deletions .github/DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Development

## Homebridge Ring

* Create a `.homebridge/config.json` file to get started
* `npm run dev` to start homebridge in watch mode

## Homebridge UI

* `npm i -g homebridge-config-ui-x` to install the config ui plugin globally. You will need to add a `homebridge-config-ui-x` entry to your `config.json` file to access the UI
* `cd packages/homebridge-ring && npm link` - this will make the `homebridge-ring` plugin globally available for `homebridge-config-ui-x` to find it and show it as an installed plugin
* `npm run dev` to start all the build processes in watch mode

Changes to the `index.html` file will require a full restart of the `npm run dev` process, but changes to the `homebridge-ring-ui.ts` file should be picked up by watch mode and be available in the browser after a few seconds.
6,672 changes: 3,874 additions & 2,798 deletions package-lock.json

Large diffs are not rendered by default.

162 changes: 162 additions & 0 deletions packages/homebridge-ring/homebridge-ui/public/homebridge-ring-ui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
const { homebridge } = window
const newTokenButton = document.getElementById('ring-new-token')
const linkAccountHeader = document.getElementById('ring-link-account-header')

if (!newTokenButton) {
homebridge.toast.error('no ring-new-token element found!')
throw new Error('no ring-new-token element found!')
}

if (!linkAccountHeader) {
homebridge.toast.error('no ring-link-account-header element found!')
throw new Error('no ring-link-account-header element found!')
}

newTokenButton.addEventListener('click', () => {
showLoginForm()
})

/**
* Render the correct form, based on whether we have an existing refresh token
*/
async function renderForm() {
const [config] = await homebridge.getPluginConfig()
const needToken = !config?.refreshToken

homebridge.hideSpinner()

if (needToken) {
showLoginForm()
} else {
showStandardForm()
}
}
renderForm()

async function setRefreshToken(refreshToken: string) {
const [config, ...otherConfigs] = await homebridge.getPluginConfig()
await homebridge.updatePluginConfig([
{ ...config, refreshToken },
...otherConfigs,
])
homebridge.toast.success('Refresh Token Updated', 'Ring Login Successful')
showStandardForm()
}

function showStandardForm() {
newTokenButton?.style.setProperty('display', 'block')
linkAccountHeader?.style.setProperty('display', 'none')
homebridge.showSchemaForm()
}

function showLoginForm() {
// Hide the standard form
newTokenButton?.style.setProperty('display', 'none')
linkAccountHeader?.style.setProperty('display', 'block')
homebridge.hideSchemaForm()

// Create a new login form
const form = homebridge.createForm(
{
schema: {
type: 'object',
properties: {
email: {
title: 'Email',
type: 'string',
'x-schema-form': {
type: 'email',
},
required: true,
},
password: {
title: 'Password',
type: 'string',
'x-schema-form': {
type: 'password',
},
required: true,
},
},
},
},
{},
'Log In'
)

form.onSubmit(async ({ email, password }) => {
homebridge.showSpinner()

try {
const response = (await homebridge.request('/send-code', {
email,
password,
})) as { codePrompt: string } | { refreshToken: string }

if ('refreshToken' in response) {
// didn't need 2fa, return token without asking for code
setRefreshToken(response.refreshToken)
} else {
// Need a token, so show the token form
showTokenForm({
email,
password,
codePrompt: response.codePrompt,
})
}
} catch (e: any) {
// eslint-disable-next-line no-console
console.error(e)
homebridge.toast.error(e.message, 'Ring Login Failed')
} finally {
homebridge.hideSpinner()
}
})
}

function showTokenForm(loginInfo: {
email: string
password: string
codePrompt: string
}) {
const form = homebridge.createForm(
{
schema: {
type: 'object',
properties: {
code: {
title: 'Code',
type: 'string',
required: true,
description: loginInfo.codePrompt,
},
},
},
},
{},
'Link Account',
'Change Email'
)

form.onSubmit(async ({ code }) => {
homebridge.showSpinner()

try {
const { refreshToken } = await homebridge.request('/token', {
email: loginInfo.email,
password: loginInfo.password,
code,
})

setRefreshToken(refreshToken)
} catch (e: any) {
// eslint-disable-next-line no-console
console.error(e)
homebridge.toast.error(e.message, 'Failed to Link Account')
} finally {
homebridge.hideSpinner()
}
})

form.onCancel(() => showLoginForm())
}
22 changes: 22 additions & 0 deletions packages/homebridge-ring/homebridge-ui/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<button id="ring-new-token" class="btn btn-link m-0 p-0" style="display: none;">
<i class="fa fa-redo mr-2"></i>
Generate New Refresh Token
</button>

<h4 id="ring-link-account-header" class="text-center primary-text mb-3" style="display: none;">
Link Ring Account
</h4>

<script>
// Show the spinner while the JS is loading
homebridge.showSpinner()

// Create a script tag to load the JS onto the page
const script = document.createElement('script')

// Use the installed version of homebridge to bust the cache for the script
script.src = `homebridge-ring-ui.js?v=${homebridge.plugin.installedVersion}`

// Append the script to the head of the page to load it
document.head.appendChild(script)
</script>
3 changes: 1 addition & 2 deletions packages/homebridge-ring/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Homebridge plugin for Ring doorbells, cameras, security alarm system and smart lighting",
"main": "lib/index.js",
"scripts": {
"build": "rm -rf lib && tsc && cp -r ../homebridge-ui/build ./lib/homebridge-ui/public",
"build": "rm -rf lib && tsc && cp ./homebridge-ui/public/index.html ./lib/homebridge-ui/public/",
"lint": "eslint . --ext .ts && tsc --noEmit",
"dev": "concurrently -c yellow,blue --kill-others \"npm:dev:build\" \"npm:dev:run\" ",
"dev:build": "tsc --watch --preserveWatchOutput",
Expand All @@ -20,7 +20,6 @@
"concurrently": "^8.2.0",
"eslint-config-shared": "*",
"homebridge": "1.6.1",
"homebridge-ui": "*",
"nodemon": "^3.0.1",
"tsconfig": "*",
"typescript": "5.1.6"
Expand Down
3 changes: 2 additions & 1 deletion packages/homebridge-ring/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extends": "tsconfig/tsconfig.json",
"compilerOptions": {
"outDir": "./lib"
"outDir": "./lib",
"types": ["@homebridge/plugin-ui-utils/dist/ui.interface"],
},
"include": ["**/*.ts"]
}

0 comments on commit 7b9fbec

Please sign in to comment.