├── .eslintrc ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── config.yml │ ├── feature-request.yml │ └── support-request.yml ├── PULL_REQUEST_TEMPLATE │ └── pull_request.md ├── dependabot.yml ├── labeler.yml ├── release-drafter.yml └── workflows │ ├── beta-release.yml │ ├── build.yml │ ├── changerelease.yml │ ├── labeler.yml │ ├── release-drafter.yml │ ├── release.yml │ └── stale.yml ├── .gitignore ├── .npmignore ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── branding ├── Homebridge_x_No-IP.svg └── icon.png ├── config.schema.json ├── docs ├── .nojekyll ├── assets │ ├── dmt │ │ ├── dmt-component-data.js │ │ ├── dmt-components.css │ │ ├── dmt-components.js │ │ ├── dmt-search.cmp │ │ └── dmt-theme.css │ ├── hierarchy.js │ ├── highlight.css │ ├── icons.js │ ├── icons.svg │ ├── main.js │ └── style.css ├── functions │ └── default.html ├── index.html └── modules.html ├── eslint.config.js ├── nodemon.json ├── package-lock.json ├── package.json ├── src ├── devices │ ├── contactsensor.ts │ └── device.ts ├── homebridge-ui │ ├── public │ │ └── index.html │ └── server.ts ├── index.test.ts ├── index.ts ├── platform.ts ├── settings.test.ts └── settings.ts ├── tsconfig.json └── typedoc.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:@typescript-eslint/eslint-recommended", 6 | "plugin:@typescript-eslint/recommended" // uses the recommended rules from the @typescript-eslint/eslint-plugin 7 | ], 8 | "parserOptions": { 9 | "ecmaVersion": 2021, 10 | "sourceType": "module" 11 | }, 12 | "ignorePatterns": ["dist", "server"], 13 | "rules": { 14 | "quotes": ["warn", "single"], 15 | "indent": [ 16 | "warn", 17 | 2, 18 | { 19 | "SwitchCase": 1 20 | } 21 | ], 22 | "linebreak-style": ["warn", "unix"], 23 | "semi": ["warn", "always"], 24 | "comma-dangle": ["warn", "always-multiline"], 25 | "dot-notation": "off", 26 | "eqeqeq": "warn", 27 | "curly": ["warn", "all"], 28 | "brace-style": ["warn"], 29 | "prefer-arrow-callback": ["warn"], 30 | "max-len": ["warn", 150], 31 | "no-console": ["warn"], // use the provided Homebridge log method instead 32 | "no-non-null-assertion": ["off"], 33 | "comma-spacing": ["error"], 34 | "no-multi-spaces": [ 35 | "warn", 36 | { 37 | "ignoreEOLComments": true 38 | } 39 | ], 40 | "no-trailing-spaces": ["warn"], 41 | "lines-between-class-members": [ 42 | "warn", 43 | "always", 44 | { 45 | "exceptAfterSingleLine": true 46 | } 47 | ], 48 | "@typescript-eslint/explicit-function-return-type": "off", 49 | "@typescript-eslint/no-non-null-assertion": "off", 50 | "@typescript-eslint/explicit-module-boundary-types": "off", 51 | "@typescript-eslint/no-explicit-any": "off" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: donavanbecker 4 | custom: paypal.me/DonavanBecker 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | **Describe The Bug:** 13 | 14 | 15 | 16 | **To Reproduce:** 17 | 18 | 19 | 20 | **Expected behavior:** 21 | 22 | 23 | 24 | **Logs:** 25 | 26 | 27 | 28 | ``` 29 | Show the Homebridge / Homebridge Config UI X logs here. 30 | ``` 31 | 32 | **Homebridge Config:** 33 | 34 | \```json 35 | Show your homebridge config.json here 36 | \``` 37 | 38 | **Screenshots:** 39 | 40 | 41 | 42 | **Environment:** 43 | 44 | - **Node.js Version**: 45 | - **NPM Version**: 46 | - **Homebridge Version**: 47 | - **Homebridge NoIP Plugin Version**: 48 | - **Homebridge Config UI X Version**: 49 | - **Operating System**: Raspbian / Ubuntu / Debian / Windows / macOS / Docker 50 | - **Process Supervisor**: Docker / Systemd / init.d / pm2 / launchctl / hb-service / other / none 51 | 52 | 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug-Report 2 | description: Report a Bug to help us improve 3 | title: "Bug:" 4 | labels: [bug] 5 | assignees: 'donavanbecker' 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | You must use the issue template below when submitting a bug. 11 | - type: input 12 | attributes: 13 | label: Describe The Bug 14 | placeholder: A clear and concise description of what the bug is. 15 | validations: 16 | required: true 17 | - type: input 18 | attributes: 19 | label: To Reproduce 20 | placeholder: Steps to reproduce the behavior. 21 | validations: 22 | required: true 23 | - type: input 24 | attributes: 25 | label: Expected behavior 26 | placeholder: A clear and concise description of what you expected to happen. 27 | validations: 28 | required: true 29 | - type: textarea 30 | id: Logs 31 | attributes: 32 | label: Relevant log output 33 | placeholder: Bug reports that do not contain logs may be closed without warning. Show the Homebridge logs here. If using Homebridge Config-UI-X, you can easly download your log by visiting the log page on the UI and Clicking the Download Icon in the top right. Remove any sensitive information, such as your homebridge-noip hostname. Please Turn on Debug Mode before submitting your Issue so more logs can be shared to fix the issue being submitted. 34 | render: shell 35 | validations: 36 | required: true 37 | - type: textarea 38 | id: Config 39 | attributes: 40 | label: Config for homebridge-noip 41 | placeholder: Paste your homebridge config.json here. Remove any sensitive information, such as your homebridge-noip username/password/hostname. 42 | render: shell 43 | validations: 44 | required: true 45 | - type: textarea 46 | attributes: 47 | label: Screenshots 48 | description: If applicable add screenshots to help explain your problem. 49 | placeholder: You can attach images or log files by clicking this area to highlight it and then dragging files in. 50 | validations: 51 | required: false 52 | - type: markdown 53 | attributes: 54 | value: | 55 | Environment 56 | - type: input 57 | attributes: 58 | label: Node.js Version 59 | placeholder: v14.17.6 60 | validations: 61 | required: true 62 | - type: input 63 | attributes: 64 | label: NPM Version 65 | placeholder: v7.22.0 66 | validations: 67 | required: true 68 | - type: input 69 | attributes: 70 | label: Homebridge Version 71 | placeholder: If applicable, add screenshots to help explain your problem. 72 | validations: 73 | required: true 74 | - type: input 75 | attributes: 76 | label: Homebridge NoIP Plugin Version 77 | placeholder: v1.1.0 78 | validations: 79 | required: true 80 | - type: input 81 | attributes: 82 | label: Homebridge Config UI X Plugin Version 83 | placeholder: v4.41.2 84 | validations: 85 | required: false 86 | - type: input 87 | attributes: 88 | label: Operating System 89 | placeholder: Raspbian / Ubuntu / Debian / Windows / macOS / Docker 90 | validations: 91 | required: true 92 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Homebridge Discord Channel for No-IP 4 | url: https://discord.gg/8fpZA4S 5 | about: Please ask and answer questions here. 6 | - name: @homebridge-plugins/homebridge-noip - Pull Requests 7 | url: https://github.com/homebridge-plugins/homebridge-noip/pulls 8 | about: Please report security vulnerabilities here. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this project 3 | title: "Feature Request: " 4 | labels: [enhancement] 5 | assignees: 'donavanbecker' 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Is your feature request related to a problem? Please describe. 11 | - type: input 12 | attributes: 13 | label: Problem 14 | placeholder: A clear and concise description of what the problem is. Ex. I am always frustrated when [...] 15 | validations: 16 | required: false 17 | - type: input 18 | attributes: 19 | label: Solution 20 | placeholder: A clear and concise description of what you want to happen. 21 | validations: 22 | required: true 23 | - type: input 24 | attributes: 25 | label: Alternatives 26 | placeholder: A clear and concise description of any alternative solutions or features you have considered. 27 | validations: 28 | required: false 29 | - type: input 30 | attributes: 31 | label: Additional context 32 | placeholder: Add any other context or screenshots about the feature request here. Post devicediscovery logs here. 33 | validations: 34 | required: false 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support-request.yml: -------------------------------------------------------------------------------- 1 | name: Support Request 2 | description: Need help? 3 | title: "Support Request:" 4 | labels: [question] 5 | assignees: 'donavanbecker' 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | You must use the issue template below when submitting a support request. 11 | - type: input 12 | attributes: 13 | label: Describe Your Problem 14 | placeholder: A clear and concise description of what problem you are trying to solve. 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: Logs 19 | attributes: 20 | label: Relevant log output 21 | placeholder: Bug reports that do not contain logs may be closed without warning. Show the Homebridge logs here. If using Homebridge Config-UI-X, you can easly download your log by visiting the log page on the UI and Clicking the Download Icon in the top right. Remove any sensitive information, such as your homebridge-noip hostname. Please Turn on Debug Mode before submitting your Issue so more logs can be shared to fix the issue being submitted. 22 | render: shell 23 | validations: 24 | required: false 25 | - type: textarea 26 | id: Config 27 | attributes: 28 | label: Config for homebridge-noip 29 | placeholder: Paste your homebridge config.json here. Remove any sensitive information, such as your homebridge-noip username/password/hostname. 30 | render: shell 31 | validations: 32 | required: false 33 | - type: textarea 34 | attributes: 35 | label: Screenshots 36 | description: If applicable add screenshots to help explain your problem. 37 | placeholder: You can attach images or log files by clicking this area to highlight it and then dragging files in. 38 | validations: 39 | required: false 40 | - type: input 41 | attributes: 42 | label: Device & Model 43 | placeholder: If applicable, add screenshots to help explain your problem. 44 | validations: 45 | required: true 46 | - type: markdown 47 | attributes: 48 | value: | 49 | Environment 50 | - type: input 51 | attributes: 52 | label: Node.js Version 53 | placeholder: v14.17.6 54 | validations: 55 | required: true 56 | - type: input 57 | attributes: 58 | label: NPM Version 59 | placeholder: v7.22.0 60 | validations: 61 | required: true 62 | - type: input 63 | attributes: 64 | label: Homebridge Version 65 | placeholder: If applicable, add screenshots to help explain your problem. 66 | validations: 67 | required: true 68 | - type: input 69 | attributes: 70 | label: Homebridge NoIP Plugin Version 71 | placeholder: v1.1.0 72 | validations: 73 | required: true 74 | - type: input 75 | attributes: 76 | label: Homebridge Config UI X Plugin Version 77 | placeholder: v4.41.2 78 | validations: 79 | required: false 80 | - type: input 81 | attributes: 82 | label: Operating System 83 | placeholder: Raspbian / Ubuntu / Debian / Windows / macOS / Docker 84 | validations: 85 | required: true 86 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement 3 | about: Contribute to Plugin through Pull Request 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: 'donavanbecker' 7 | --- 8 | 9 | **Is your enhancement related to a problem? Please describe.** 10 | 11 | 12 | 13 | **Describe the solution you are addding** 14 | 15 | 16 | 17 | **Changes Proposed in this Pull Request** 18 | 19 | 20 | 21 | **Describe alternatives you've considered** 22 | 23 | 24 | 25 | **Additional context** 26 | 27 | 28 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: 'npm' # See documentation for possible values 9 | directory: '/' # Location of package manifests 10 | target-branch: 'beta-*.*.*' 11 | schedule: 12 | interval: 'daily' 13 | - package-ecosystem: 'github-actions' # See documentation for possible values 14 | directory: '/' # Location of package manifests 15 | target-branch: 'beta-*.*.*' 16 | schedule: 17 | interval: 'daily' 18 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # Add 'branding' label to any changes within 'docs' folder or any subfolders 2 | branding: 3 | - changed-files: 4 | - any-glob-to-any-file: branding/** 5 | 6 | # Add 'docs' label to any change to .md files within the entire repository 7 | docs: 8 | - changed-files: 9 | - any-glob-to-any-file: '**/*.md' 10 | 11 | # Add 'enhancement' label to any change to src files within the source dir EXCEPT for the docs sub-folder 12 | enhancement: 13 | - changed-files: 14 | - any-glob-to-any-file: 'src/**/*' 15 | - any-glob-to-any-file: 'config.schema.json' 16 | 17 | # Add 'dependencies' label to any change to src files within the source dir EXCEPT for the docs sub-folder 18 | dependencies: 19 | - changed-files: 20 | - any-glob-to-any-file: 'package.json' 21 | - any-glob-to-any-file: 'package-lock.json' 22 | 23 | # Add 'beta' label to any PR that is opened against the `beta` branch 24 | beta: 25 | - base-branch: 'beta*' 26 | 27 | # Add 'alpha' label to any PR that is opened against the `alpha` branch 28 | alpha: 29 | - base-branch: 'alpha*' 30 | 31 | # Add 'latest' label to any PR that is opened against the `latest` branch 32 | latest: 33 | - base-branch: 'latest' 34 | 35 | # Add 'workflow' to any changes within 'workflow' folder or any subfolders 36 | workflow: 37 | - changed-files: 38 | - any-glob-to-any-file: .github/** -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | 4 | categories: 5 | - title: 'Workflow Changes' 6 | labels: 7 | - 'workflow' 8 | - title: 'Enhancements' 9 | labels: 10 | - 'enhancement' 11 | - title: 'Updated Dependencies' 12 | labels: 13 | - 'dependencies' 14 | - title: 'Documentation' 15 | labels: 16 | - 'docs' 17 | 18 | change-template: '- $TITLE @$AUTHOR [#$NUMBER]' 19 | version-resolver: 20 | major: 21 | labels: 22 | - 'major' 23 | minor: 24 | labels: 25 | - 'minor' 26 | patch: 27 | labels: 28 | - 'patch' 29 | default: patch 30 | template: | 31 | ## Changes 32 | 33 | $CHANGES -------------------------------------------------------------------------------- /.github/workflows/beta-release.yml: -------------------------------------------------------------------------------- 1 | name: Beta Release 2 | 3 | on: 4 | push: 5 | branches: [beta-*.*.*, beta] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build_and_test: 10 | uses: homebridge/.github/.github/workflows/nodejs-build-and-test.yml@latest 11 | with: 12 | enable_coverage: false 13 | secrets: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | lint: 16 | needs: build_and_test 17 | uses: homebridge/.github/.github/workflows/eslint.yml@latest 18 | 19 | publish: 20 | needs: lint 21 | if: ${{ github.repository == 'homebridge-plugins/homebridge-noip' }} 22 | permissions: 23 | id-token: write 24 | uses: homebridge/.github/.github/workflows/npm-publish-esm.yml@latest 25 | with: 26 | tag: 'beta' 27 | dynamically_adjust_version: true 28 | npm_version_command: 'pre' 29 | pre_id: 'beta' 30 | secrets: 31 | npm_auth_token: ${{ secrets.npm_token }} 32 | 33 | pre-release: 34 | needs: publish 35 | if: ${{ github.repository == 'homebridge-plugins/homebridge-noip' }} 36 | uses: homebridge/.github/.github/workflows/pre-release.yml@latest 37 | with: 38 | npm_version: ${{ needs.publish.outputs.NPM_VERSION }} 39 | body: | 40 | **Beta Release** 41 | **Version**: v${{ needs.publish.outputs.NPM_VERSION }} 42 | [How To Test Beta Releases](https://github.com/homebridge-plugins/homebridge-noip/wiki/Beta-Version) 43 | 44 | github-releases-to-discord: 45 | name: Discord Webhooks 46 | needs: [build_and_test,publish] 47 | if: ${{ github.repository == 'homebridge-plugins/homebridge-noip' }} 48 | uses: homebridge/.github/.github/workflows/discord-webhooks.yml@latest 49 | with: 50 | title: "NoIP Beta Release" 51 | description: | 52 | Version `v${{ needs.publish.outputs.NPM_VERSION }}` 53 | url: "https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v${{ needs.publish.outputs.NPM_VERSION }}" 54 | secrets: 55 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL_BETA || secrets.DISCORD_WEBHOOK_URL_LATEST }} 56 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Node Build 2 | 3 | on: 4 | push: 5 | branches: [latest] 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build_and_test: 11 | uses: homebridge/.github/.github/workflows/nodejs-build-and-test.yml@latest 12 | with: 13 | enable_coverage: false 14 | secrets: 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | lint: 17 | needs: build_and_test 18 | uses: homebridge/.github/.github/workflows/eslint.yml@latest 19 | -------------------------------------------------------------------------------- /.github/workflows/changerelease.yml: -------------------------------------------------------------------------------- 1 | name: Changelog to Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | changerelease: 9 | uses: homebridge/.github/.github/workflows/change-release.yml@latest 10 | secrets: 11 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: Labeler 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | labeler: 7 | uses: homebridge/.github/.github/workflows/labeler.yml@latest 8 | secrets: 9 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: [latest] 6 | pull_request: # required for autolabeler 7 | types: [opened, reopened, synchronize] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | release-drafter: 12 | uses: homebridge/.github/.github/workflows/release-drafter.yml@latest 13 | secrets: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build_and_test: 9 | uses: homebridge/.github/.github/workflows/nodejs-build-and-test.yml@latest 10 | with: 11 | enable_coverage: false 12 | secrets: 13 | token: ${{ secrets.GITHUB_TOKEN }} 14 | 15 | publish: 16 | needs: build_and_test 17 | if: ${{ github.repository == 'homebridge-plugins/homebridge-noip' }} 18 | permissions: 19 | id-token: write 20 | uses: homebridge/.github/.github/workflows/npm-publish-esm.yml@latest 21 | secrets: 22 | npm_auth_token: ${{ secrets.npm_token }} 23 | 24 | github-releases-to-discord: 25 | name: Discord Webhooks 26 | needs: [build_and_test,publish] 27 | if: ${{ github.repository == 'homebridge-plugins/homebridge-noip' }} 28 | uses: homebridge/.github/.github/workflows/discord-webhooks.yml@latest 29 | with: 30 | title: "NoIP Release" 31 | description: | 32 | Version `v${{ needs.publish.outputs.NPM_VERSION }}` 33 | url: "https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v${{ needs.publish.outputs.NPM_VERSION }}" 34 | secrets: 35 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL_LATEST }} 36 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale workflow 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '45 11 * * *' 7 | 8 | jobs: 9 | stale: 10 | uses: homebridge/.github/.github/workflows/stale.yml@latest 11 | secrets: 12 | token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore compiled code 2 | 3 | dist 4 | 5 | # ------------- Defaults ------------- 6 | 7 | # Logs 8 | 9 | logs 10 | _.log 11 | npm-debug.log_ 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log\* 15 | 16 | # Diagnostic reports (https://nodejs.org/api/report.html) 17 | 18 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 19 | 20 | # Runtime data 21 | 22 | pids 23 | _.pid 24 | _.seed 25 | \*.pid.lock 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | 33 | coverage 34 | \*.lcov 35 | 36 | # nyc test coverage 37 | 38 | .nyc_output 39 | 40 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 41 | 42 | .grunt 43 | 44 | # Bower dependency directory (https://bower.io/) 45 | 46 | bower_components 47 | 48 | # node-waf configuration 49 | 50 | .lock-wscript 51 | 52 | # Compiled binary addons (https://nodejs.org/api/addons.html) 53 | 54 | build/Release 55 | 56 | # Dependency directories 57 | 58 | node_modules/ 59 | jspm_packages/ 60 | 61 | # Snowpack dependency directory (https://snowpack.dev/) 62 | 63 | web_modules/ 64 | 65 | # TypeScript cache 66 | 67 | \*.tsbuildinfo 68 | 69 | # Optional npm cache directory 70 | 71 | .npm 72 | 73 | # Optional eslint cache 74 | 75 | .eslintcache 76 | 77 | # Microbundle cache 78 | 79 | .rpt2_cache/ 80 | .rts2_cache_cjs/ 81 | .rts2_cache_es/ 82 | .rts2_cache_umd/ 83 | 84 | # Optional REPL history 85 | 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | 90 | \*.tgz 91 | 92 | # Yarn Integrity file 93 | 94 | .yarn-integrity 95 | 96 | # dotenv environment variables file 97 | 98 | .env 99 | .env.test 100 | 101 | # parcel-bundler cache (https://parceljs.org/) 102 | 103 | .cache 104 | .parcel-cache 105 | 106 | # Next.js build output 107 | 108 | .next 109 | 110 | # Nuxt.js build / generate output 111 | 112 | .nuxt 113 | dist 114 | 115 | # Gatsby files 116 | 117 | .cache/ 118 | 119 | # Comment in the public line in if your project uses Gatsby and not Next.js 120 | 121 | # https://nextjs.org/blog/next-9-1#public-directory-support 122 | 123 | # public 124 | 125 | # vuepress build output 126 | 127 | .vuepress/dist 128 | 129 | # Serverless directories 130 | 131 | .serverless/ 132 | 133 | # FuseBox cache 134 | 135 | .fusebox/ 136 | 137 | # DynamoDB Local files 138 | 139 | .dynamodb/ 140 | 141 | # TernJS port file 142 | 143 | .tern-port 144 | 145 | # Stores VSCode versions used for testing VSCode extensions 146 | 147 | .vscode-test 148 | 149 | # yarn v2 150 | 151 | .yarn/cache 152 | .yarn/unplugged 153 | .yarn/build-state.yml 154 | .pnp.\* 155 | 156 | # Homebridge Testing 157 | 158 | persist/ 159 | .DS_Store 160 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore source code 2 | 3 | src 4 | 5 | # ------------- Defaults ------------- 6 | 7 | # eslint 8 | 9 | .eslintrc 10 | 11 | # typescript 12 | 13 | tsconfig.json 14 | 15 | # vscode 16 | 17 | .vscode 18 | 19 | # nodemon 20 | 21 | nodemon.json 22 | 23 | # Logs 24 | 25 | logs 26 | _.log 27 | npm-debug.log_ 28 | yarn-debug.log* 29 | yarn-error.log* 30 | lerna-debug.log\* 31 | 32 | # Diagnostic reports (https://nodejs.org/api/report.html) 33 | 34 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 35 | 36 | # Runtime data 37 | 38 | pids 39 | _.pid 40 | _.seed 41 | \*.pid.lock 42 | 43 | # Directory for instrumented libs generated by jscoverage/JSCover 44 | 45 | lib-cov 46 | 47 | # Coverage directory used by tools like istanbul 48 | 49 | coverage 50 | \*.lcov 51 | 52 | # nyc test coverage 53 | 54 | .nyc_output 55 | 56 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 57 | 58 | .grunt 59 | 60 | # Bower dependency directory (https://bower.io/) 61 | 62 | bower_components 63 | 64 | # node-waf configuration 65 | 66 | .lock-wscript 67 | 68 | # Compiled binary addons (https://nodejs.org/api/addons.html) 69 | 70 | build/Release 71 | 72 | # Dependency directories 73 | 74 | node_modules/ 75 | jspm_packages/ 76 | 77 | # Snowpack dependency directory (https://snowpack.dev/) 78 | 79 | web_modules/ 80 | 81 | # TypeScript cache 82 | 83 | \*.tsbuildinfo 84 | 85 | # Optional npm cache directory 86 | 87 | .npm 88 | 89 | # Optional eslint cache 90 | 91 | .eslintcache 92 | 93 | # Microbundle cache 94 | 95 | .rpt2_cache/ 96 | .rts2_cache_cjs/ 97 | .rts2_cache_es/ 98 | .rts2_cache_umd/ 99 | 100 | # Optional REPL history 101 | 102 | .node_repl_history 103 | 104 | # Output of 'npm pack' 105 | 106 | \*.tgz 107 | 108 | # Yarn Integrity file 109 | 110 | .yarn-integrity 111 | 112 | # dotenv environment variables file 113 | 114 | .env 115 | .env.test 116 | 117 | # parcel-bundler cache (https://parceljs.org/) 118 | 119 | .cache 120 | .parcel-cache 121 | 122 | # Next.js build output 123 | 124 | .next 125 | 126 | # Nuxt.js build / generate output 127 | 128 | .nuxt 129 | dist 130 | 131 | # Gatsby files 132 | 133 | .cache/ 134 | 135 | # Comment in the public line in if your project uses Gatsby and not Next.js 136 | 137 | # https://nextjs.org/blog/next-9-1#public-directory-support 138 | 139 | # public 140 | 141 | # vuepress build output 142 | 143 | .vuepress/dist 144 | 145 | # Serverless directories 146 | 147 | .serverless/ 148 | 149 | # FuseBox cache 150 | 151 | .fusebox/ 152 | 153 | # DynamoDB Local files 154 | 155 | .dynamodb/ 156 | 157 | # TernJS port file 158 | 159 | .tern-port 160 | 161 | # Stores VSCode versions used for testing VSCode extensions 162 | 163 | .vscode-test 164 | 165 | # yarn v2 166 | 167 | .yarn/cache 168 | .yarn/unplugged 169 | .yarn/build-state.yml 170 | .pnp.\* 171 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.eol": "\n", 3 | "prettier.requireConfig": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": "explicit" 6 | }, 7 | "editor.defaultFormatter": "esbenp.prettier-vscode", 8 | "[javascript]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "vscode.typescript-language-features" 13 | }, 14 | "[markdown]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | }, 17 | "typescript.format.enable": true, 18 | "editor.rulers": [150], 19 | "eslint.enable": true, 20 | "eslint.workingDirectories": [ 21 | { 22 | "mode": "auto" 23 | } 24 | ], 25 | "[json]": { 26 | "editor.defaultFormatter": "vscode.json-language-features" 27 | }, 28 | "[jsonc]": { 29 | "editor.defaultFormatter": "vscode.json-language-features" 30 | }, 31 | "codeQL.githubDatabase.download": "never" 32 | } 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. This project uses [Semantic Versioning](https://semver.org/) 4 | 5 | ## [4.1.2](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v4.1.2) (2025-03-04) 6 | 7 | # *No New Releases During Lent* 8 | 9 | ### What's Changes 10 | - Housekeeping and updated dependencies. 11 | 12 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v4.1.1...v4.1.2 13 | 14 | ## [4.1.1](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v4.1.1) (2025-01-25) 15 | 16 | ### What's Changes 17 | - Housekeeping and updated dependencies. 18 | 19 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v4.1.0...v4.1.1 20 | 21 | ## [4.1.0](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v4.1.0) (2025-01-16) 22 | 23 | ### What's Changes 24 | - Added ipv6 support [#161](https://github.com/homebridge-plugins/homebridge-noip/pull/161), Thanks [@v0lume](https://github.com/v0lume) 25 | - Added support for other ip providers 26 | - `ipify.org`, `ipinfo.io`, `ipapi.co`, `my-ip.io`, and `getmyip.dev` 27 | - Housekeeping and updated dependencies. 28 | 29 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v4.0.0...v4.1.0 30 | 31 | ## [4.0.0](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v4.0.0) (2025-01-16) 32 | 33 | ### What's Changes 34 | - This plugins has moved to a scoped plugin under the `@homebridge-plugins` org. 35 | - Homebridge UI is designed to transition you to the new scoped plugin. 36 | 37 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v3.0.6...v4.0.0 38 | 39 | ## [3.0.6](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v3.0.6) (2024-11-04) 40 | 41 | ### What's Changes 42 | - Fix refreshRate Issue 43 | 44 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v3.0.5...v3.0.6 45 | 46 | ## [3.0.5](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v3.0.5) (2024-11-03) 47 | 48 | ### What's Changes 49 | - Housekeeping and updated dependencies. 50 | 51 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v3.0.4...v3.0.5 52 | 53 | ## [3.0.4](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v3.0.4) (2024-05-26) 54 | 55 | ### What's Changes 56 | - Housekeeping and updated dependencies. 57 | 58 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v3.0.3...v3.0.4 59 | 60 | ## [3.0.3](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v3.0.3) (2024-05-26) 61 | 62 | ### What's Changes 63 | - Housekeeping and updated dependencies. 64 | 65 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v3.0.2...v3.0.3 66 | 67 | ## [3.0.2](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v3.0.2) (2024-02-13) 68 | 69 | ### What's Changes 70 | - Housekeeping and updated dependencies. 71 | 72 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v3.0.1...v3.0.2 73 | 74 | ## [3.0.1](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v3.0.1) (2024-01-31) 75 | 76 | ### What's Changes 77 | - Housekeeping and updated dependencies. 78 | 79 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v3.0.0...v3.0.1 80 | 81 | ## [3.0.0](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v3.0.0) (2023-12-23) 82 | 83 | ### What's Changes 84 | - Moved from CommonJS to ES Module 85 | - Housekeeping and updated dependencies. 86 | 87 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v2.0.4...v3.0.0 88 | 89 | ## [2.0.4](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v2.0.4) (2023-12-15) 90 | 91 | ### What's Changes 92 | - Housekeeping and updated dependencies. 93 | 94 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v2.0.3...v2.0.4 95 | 96 | ## [2.0.3](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v2.0.3) (2023-11-26) 97 | 98 | ### What's Changes 99 | - Housekeeping and updated dependencies. 100 | 101 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v2.0.2...v2.0.3 102 | 103 | ## [2.0.2](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v2.0.2) (2023-10-31) 104 | 105 | ### What's Changes 106 | - Housekeeping and updated dependencies. 107 | 108 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v2.0.1...v2.0.2 109 | 110 | ## [2.0.1](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v2.0.1) (2023-08-27) 111 | 112 | ### What's Changes 113 | - Housekeeping and updated dependencies. 114 | 115 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v2.0.0...v2.0.1 116 | 117 | ## [2.0.0](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v2.0.0) (2023-08-19) 118 | 119 | ### What's Changes 120 | #### Major Changes 121 | - Added Support to update multiple hostnames. 122 | - Must setup hostnames again after upgrading to v2.0.0 123 | 124 | #### Other Changes 125 | - Housekeeping and updated dependencies. 126 | 127 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.6.2...v2.0.0 128 | 129 | ## [1.6.2](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.6.2) (2023-04-07) 130 | 131 | ### What's Changes 132 | - Housekeeping and updated dependencies. 133 | - This release will end support for Node v14. 134 | 135 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.6.1...v1.6.2 136 | 137 | ## [1.6.1](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.6.1) (2022-12-08) 138 | 139 | ### What's Changes 140 | - Housekeeping and updated dependencies. 141 | 142 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.6.0...v1.6.1 143 | 144 | ## [1.6.0](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.6.0) (2022-10-18) 145 | 146 | ### What's Changes 147 | - Added Config to allow manually setting firmware version. 148 | - Housekeeping and updated dependencies. 149 | 150 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.5.8...v1.6.0 151 | 152 | ## [1.5.8](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.5.8) (2022-08-31) 153 | 154 | ### What's Changes 155 | 156 | - Housekeeping and updated dependencies. 157 | 158 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.5.7...v1.5.8 159 | 160 | ## [1.5.7](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.5.7) (2022-06-25) 161 | 162 | ### What's Changes 163 | 164 | - Housekeeping and updated dependencies. 165 | 166 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.5.6...v1.5.7 167 | 168 | ## [1.5.6](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.5.6) (2022-05-04) 169 | 170 | ### What's Changes 171 | 172 | - Housekeeping and updated dependencies. 173 | 174 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.5.5...v1.5.6 175 | 176 | ## [1.5.5](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.5.5) (2022-03-19) 177 | 178 | ### What's Changes 179 | 180 | - Housekeeping and updated dependencies. 181 | 182 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.5.4...v1.5.5 183 | 184 | ## [1.5.4](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.5.4) (2022-02-15) 185 | 186 | ### What's Changes 187 | 188 | - Housekeeping and updated dependencies. 189 | 190 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.5.3...v1.5.4 191 | 192 | ## [1.5.3](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.5.3) (2022-02-12) 193 | 194 | ### What's Changes 195 | 196 | - Housekeeping and updated dependencies. 197 | 198 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.5.2...v1.5.3 199 | 200 | ## [1.5.2](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.5.2) (2022-01-29) 201 | 202 | ### What's Changes 203 | 204 | - Housekeeping and updated dependencies. 205 | 206 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.5.1...v1.5.2 207 | 208 | ## [1.5.1](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.5.1) (2022-01-22) 209 | 210 | ### What's Changes 211 | 212 | - Housekeeping and updated dependencies. 213 | 214 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.5.0...v1.5.1 215 | 216 | ## [1.5.0](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.5.0) (2022-01-13) 217 | 218 | ### What's Changes 219 | 220 | - Removed dependency `easy-ip` to use built in Public IP finder. 221 | 222 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.4.0...v1.5.0 223 | 224 | ## [1.4.0](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.4.0) (2022-01-06) 225 | 226 | ### What's Changes 227 | 228 | - Change from dependency `systeminformation` to `easy-ip`. 229 | 230 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.3.0...v1.4.0 231 | 232 | ## [1.3.0](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.3.0) (2022-01-06) 233 | 234 | ### What's Changes 235 | 236 | ### Major Change To `Logging`: 237 | 238 | - Added the following Logging Options: 239 | - `Standard` 240 | - `None` 241 | - `Debug` 242 | - Removed Device Logging Option, which was pushed into new logging under debug. 243 | - Added Device Logging Override for each Device, by using the Device Config. 244 | - Change from dependency `public-ip` to `systeminformation` to `easy-ip`. 245 | - Housekeeping and updated dependencies. 246 | 247 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.2.4...v1.3.0 248 | 249 | ## [1.2.4](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v1.2.4) (2021-12-15) 250 | 251 | ### What's Changes 252 | 253 | - Housekeeping and updated dependencies. 254 | 255 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.2.3...v1.2.4 256 | 257 | ## [1.2.3](https://github.com/homebridge-plugins/homebridge-noip/compare/v1.2.2...v1.2.3) (2021-11-12) 258 | 259 | ### What's Changes 260 | 261 | - Housekeeping and updated dependencies. 262 | 263 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.2.2...v1.2.3 264 | 265 | ## [1.2.2](https://github.com/homebridge-plugins/homebridge-noip/compare/v1.2.1...v1.2.2) (2021-10-28) 266 | 267 | ### What's Changes 268 | 269 | - Housekeeping and updated dependencies. 270 | 271 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.2.1...v1.2.2 272 | 273 | ## [1.2.1](https://github.com/homebridge-plugins/homebridge-noip/compare/v1.2.0...v1.2.1) (2021-10-20) 274 | 275 | ### What's Changes 276 | 277 | - Update Plugin Debug Logging Config Setting to show more logs and added Device Logging option. 278 | - Housekeeping and updated dependencies. 279 | 280 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.2.0...v1.2.1 281 | 282 | ## [1.2.0](https://github.com/homebridge-plugins/homebridge-noip/compare/v1.1.2...v1.2.0) (2021-10-13) 283 | 284 | ### What's Changes 285 | 286 | - Removed dependency for `no-ip` and implemented built in noip updater. 287 | 288 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.1.2...v1.2.0 289 | 290 | ## [1.1.2](https://github.com/homebridge-plugins/homebridge-noip/compare/v1.1.1...v1.1.2) (2021-10-02) 291 | 292 | ### What's Changes 293 | 294 | - Potential Fix for Error: `Possible EventEmitter memory leak detected. 11 error listeners added to [NoIP]` 295 | 296 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.1.1...v1.1.2 297 | 298 | ## [1.1.1](https://github.com/homebridge-plugins/homebridge-noip/compare/v1.1.0...v1.1.1) (2021-09-19) 299 | 300 | ### What's Changes 301 | 302 | - Fixes incorrect status of contact sensor. 303 | 304 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.1.0...v1.1.1 305 | 306 | ## [1.1.0](https://github.com/homebridge-plugins/homebridge-noip/compare/v1.0.0...v1.0.1) (2021-09-17) 307 | 308 | ### What's Changes 309 | 310 | - Add Plugin debug config. 311 | 312 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v1.0.0...v1.1.0 313 | 314 | ## [1.0.0](https://github.com/homebridge-plugins/homebridge-noip/compare/v0.1.0...v1.0.0) (2021-08-09) 315 | 316 | ### What's Changes 317 | 318 | - Release of [homebridge-noip](https://github.com/homebridge-plugins/homebridge-noip) which allows you to update your No-IP hostname. 319 | 320 | **Full Changelog**: https://github.com/homebridge-plugins/homebridge-noip/compare/v0.1.0...v1.0.0 321 | 322 | ## [0.1.0](https://github.com/homebridge-plugins/homebridge-noip/releases/tag/v0.1.0) (2021-08-09) 323 | 324 | ### What's Changes 325 | 326 | - Initial Release 327 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License (ISC) 2 | Copyright (c) 2023 donavanbecker 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | homebridge-verified 4 | 5 | # Homebridge No-IP 6 | 7 | 8 | 9 | 10 | 11 | 12 |

The Homebridge No-IP 13 | plugin allows you update your No-IP hostnames from the IP that your 14 | Homebridge instance is on. 15 |

16 | 17 |
18 | 19 | ## Installation 20 | 21 | 1. Search for "No-IP" on the plugin screen of [Homebridge Config UI X](https://github.com/oznu/homebridge-config-ui-x). 22 | 2. Click **Install**. 23 | 24 | ## Configuration 25 | 26 | 1. Login / create an account at https://noip.com/ 27 | - If you haven't already you can also create your No-IP hostname here as well. 28 | 29 |

30 | 31 | 32 | 33 |

34 | 35 | 2. Type in your NoIP Username, Password, and Hostname into the Plugin Setting UI 36 | 3. Click Save 37 | 4. Restart Homebridge 38 | 39 | ## Supported No-IP Features 40 | 41 | - IPv4 Update. 42 | - IPv6 Update. 43 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 1.0.0 | :white_check_mark: | 11 | | < 0.1.0 | :x: | 12 | 13 | ## Reporting a Vulnerability 14 | 15 | Use this section to tell people how to report a vulnerability. 16 | 17 | Tell them where to go, how often they can expect to get an update on a 18 | reported vulnerability, what to expect if the vulnerability is accepted or 19 | declined, etc. 20 | -------------------------------------------------------------------------------- /branding/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/homebridge-plugins/homebridge-noip/196abd6f3ca1ead7ec0e94770387a254f32aeb33/branding/icon.png -------------------------------------------------------------------------------- /config.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginAlias": "NoIP", 3 | "pluginType": "platform", 4 | "singular": true, 5 | "customUi": true, 6 | "customUiPath": "./dist/homebridge-ui", 7 | "headerDisplay": "

\n\nThe **Homebridge No-IP** plugin allows you to update your No-IP hostnames. \n\nTo get started link your No-IP account using the button below.", 8 | "footerDisplay": "Your No-IP account has been linked. Please raise any issues on our [project page](https://github.com/homebridge-plugins/homebridge-noip/issues).\n\nIf you would like to have other features, fill out [Feature Request Form](https://github.com/homebridge-plugins/homebridge-noip/issues/new?assignees=&labels=&template=feature_request.md).", 9 | "schema": { 10 | "type": "object", 11 | "properties": { 12 | "name": { 13 | "type": "string", 14 | "title": "Name", 15 | "default": "NoIP", 16 | "required": true 17 | }, 18 | "devices": { 19 | "type": "array", 20 | "items": { 21 | "title": "Devices", 22 | "type": "object", 23 | "properties": { 24 | "configDeviceName": { 25 | "title": "Device Name", 26 | "type": "string", 27 | "pattern": "^[a-zA-Z0-9]+([a-zA-Z0-9 ]*[a-zA-Z0-9]+)?$", 28 | "placeholder": "My Device" 29 | }, 30 | "username": { 31 | "title": "NoIP Username/Email", 32 | "type": "string", 33 | "required": true, 34 | "x-schema-form": { 35 | "type": "email" 36 | } 37 | }, 38 | "password": { 39 | "title": "NoIP Password", 40 | "type": "string", 41 | "x-schema-form": { 42 | "type": "password" 43 | } 44 | }, 45 | "ipv4or6": { 46 | "title": "IPv4 or IPv6", 47 | "type": "string", 48 | "required": true, 49 | "default": "ipv4", 50 | "oneOf": [ 51 | { 52 | "title": "IPv4", 53 | "enum": ["ipv4"] 54 | }, 55 | { 56 | "title": "IPv6", 57 | "enum": ["ipv6"] 58 | } 59 | ] 60 | }, 61 | "ipProvider": { 62 | "title": "IP Provider", 63 | "type": "string", 64 | "required": true, 65 | "default": "ipinfo", 66 | "oneOf": [ 67 | { 68 | "title": "ipify.org", 69 | "enum": ["ipify"] 70 | }, 71 | { 72 | "title": "getmyip.dev", 73 | "enum": ["getmyip"] 74 | }, 75 | { 76 | "title": "ipapi.co", 77 | "enum": ["ipapi"] 78 | }, 79 | { 80 | "title": "my-ip.io", 81 | "enum": ["myip"] 82 | }, 83 | { 84 | "title": "ipinfo.io", 85 | "enum": ["ipinfo"] 86 | } 87 | ] 88 | }, 89 | "hostname": { 90 | "title": "Hostname", 91 | "type": "string" 92 | }, 93 | "firmware": { 94 | "title": "Firmware Override", 95 | "type": "string", 96 | "placeholder": "1.2.8" 97 | }, 98 | "refreshRate": { 99 | "title": "Device Refresh Rate", 100 | "type": "number", 101 | "minimum": 1800, 102 | "placeholder": 1800, 103 | "description": "Indicates the number of seconds between polls of the No-IP service." 104 | }, 105 | "logging": { 106 | "title": "Device Logging Override Setting", 107 | "type": "string", 108 | "required": true, 109 | "default": "", 110 | "oneOf": [ 111 | { 112 | "title": "Default Logging", 113 | "enum": [""] 114 | }, 115 | { 116 | "title": "Standard Logging", 117 | "enum": ["standard"] 118 | }, 119 | { 120 | "title": "No Logging", 121 | "enum": ["none"] 122 | }, 123 | { 124 | "title": "Debug Logging", 125 | "enum": ["debug"] 126 | } 127 | ] 128 | }, 129 | "delete": { 130 | "title": "Delete Device", 131 | "type": "boolean" 132 | } 133 | } 134 | } 135 | }, 136 | "refreshRate": { 137 | "title": "Refresh Rate", 138 | "type": "number", 139 | "minimum": 1800, 140 | "placeholder": 1800, 141 | "description": "Indicates the number of seconds between polls of the No-IP service." 142 | }, 143 | "logging": { 144 | "title": "Plugin Logging Setting", 145 | "type": "string", 146 | "required": true, 147 | "default": "", 148 | "oneOf": [ 149 | { 150 | "title": "Default Logging", 151 | "enum": [""] 152 | }, 153 | { 154 | "title": "Standard Logging", 155 | "enum": ["standard"] 156 | }, 157 | { 158 | "title": "No Logging", 159 | "enum": ["none"] 160 | }, 161 | { 162 | "title": "Debug Logging", 163 | "enum": ["debug"] 164 | } 165 | ] 166 | }, 167 | "allowInvalidCharacters": { 168 | "title": "Allow Invalid Characters", 169 | "type": "boolean", 170 | "default": false, 171 | "description": "Allow invalid characters in the configDeviceName. This is not recommended." 172 | } 173 | } 174 | }, 175 | "layout": [ 176 | { 177 | "key": "devices", 178 | "notitle": false, 179 | "type": "tabarray", 180 | "title": "{{ value.configDeviceName || value.hostname || 'New Hostname' }}", 181 | "expandable": true, 182 | "expanded": false, 183 | "orderable": false, 184 | "items": [ 185 | "devices[].configDeviceName", 186 | "devices[].hostname", 187 | "devices[].username", 188 | "devices[].password", 189 | "devices[].ipv4or6", 190 | "devices[].ipProvider", 191 | "devices[].firmware", 192 | "devices[].refreshRate", 193 | "devices[].logging" 194 | ] 195 | }, 196 | { 197 | "type": "fieldset", 198 | "title": "Advanced Settings", 199 | "expandable": true, 200 | "expanded": false, 201 | "items": [ 202 | { 203 | "type": "help", 204 | "helpvalue": "
Refresh Rate
Refresh Rate indicates the number of seconds between polls of the No-IP service." 205 | }, 206 | { 207 | "key": "refreshRate", 208 | "notitle": true 209 | }, 210 | "logging", 211 | "allowInvalidCharacters" 212 | ] 213 | } 214 | ] 215 | } 216 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/assets/dmt/dmt-component-data.js: -------------------------------------------------------------------------------- 1 | globalThis.dmtComponentDataBCMP = 'TZLPbhoxEMYNolKUZ+ghx17SsstCNjeitJVQmxZVVe/GHlgLr72yvflz4xHoIyQs7K6aklsfgF4Lp15y4VliYLPkZM9oPs9vPN8aHeaMSPGZiaFeo2qqQV0yAuMktrdxHuEBdASF6zWqpKGkMQd9X5xvAxPyh13Q0V1MhrZ48SDwJRtgw6QohNUsxGpI5ZUYz7SMFYGfa/QqMXBtUgp9HHOTRNgEf/qxIBudflektx2SIRO0PSUca/0foYwEjFMFwt7n36DPYav5ZIvW6PWtl3/BIegIE7j1kw8iDu8abvYDK4Z7HO5aXvax6DJxXH96vnl14jZbeUcYUH0rmzQd99e5pTAqJkaqxKm7XtZVMgJlbhK37vmzCzCBpNOGe9Ly8y5WtqVVz9yW63hedkYIaC1V6tZPT5ymm3+/ieCMM6xTzzn1GnUvt+BgZyCQ+g3fb9X97L0kcQjC7PlrW/yDEv6oRG/vuP+N9tTLCnoJvayiEnlZQwXw8gDtaZcjVJKuUA2hPeYKHdmwZFyhtv33Z8AVGiH0WwNWJPgabfe12XE/5nyDvphyFjJzONdgDBODsqSSY8HCrTH+znUgry52zrHuW9xrRqGHVWFDNNd2BuumroIIBH18QyXRx+1AhtBTjA7gOOLxgFmnvEgJyaIn'; -------------------------------------------------------------------------------- /docs/assets/dmt/dmt-components.css: -------------------------------------------------------------------------------- 1 | label.svelte-1y7unmi{align-items:center;cursor:pointer;display:flex;-webkit-user-select:none;user-select:none;width:fit-content}input.svelte-1y7unmi{cursor:pointer;height:1.5em;opacity:0;position:absolute;width:1.5em}svg.svelte-1y7unmi{border-radius:.33em;cursor:pointer;height:1.5em;margin-right:.5em;opacity:.99;width:1.5em} 2 | section.svelte-20evnz{display:flex;flex-direction:column;gap:.5rem;margin-top:.75rem} 3 | details.svelte-1kxmj7q.svelte-1kxmj7q{margin-left:var(--tjs-folder-details-margin-left,-.4em);padding-left:var(--tjs-folder-details-padding-left,.4em)}summary.svelte-1kxmj7q.svelte-1kxmj7q{align-items:center;background:var(--tjs-folder-summary-background,none);background-blend-mode:var(--tjs-folder-summary-background-blend-mode,initial);border:var(--tjs-folder-summary-border,none);border-radius:var(--tjs-folder-summary-border-radius,0);border-width:var(--tjs-folder-summary-border-width,initial);cursor:var(--tjs-folder-summary-cursor,pointer);display:flex;font-family:var(--tjs-folder-summary-font-family,inherit);font-size:var(--tjs-folder-summary-font-size,inherit);font-weight:var(--tjs-folder-summary-font-weight,bold);gap:var(--tjs-folder-summary-gap,.125em);list-style:none;margin:var(--tjs-folder-summary-margin,0 0 0 -.4em);padding:var(--tjs-folder-summary-padding,.25em) 0;position:relative;transition:var(--tjs-folder-summary-transition,background .1s);-webkit-user-select:none;user-select:none;width:var(--tjs-folder-summary-width,fit-content)}summary.svelte-1kxmj7q.svelte-1kxmj7q::-webkit-details-marker{display:none}.default-cursor.svelte-1kxmj7q.svelte-1kxmj7q{cursor:default}summary.svelte-1kxmj7q svg.svelte-1kxmj7q{border-radius:var(--tjs-folder-summary-chevron-border-radius,0);color:var(--tjs-folder-summary-chevron-color,currentColor);cursor:var(--tjs-folder-summary-cursor,pointer);flex-shrink:0;height:var(--tjs-folder-summary-chevron-size,var(--tjs-folder-summary-font-size,1.25em));margin:var(--tjs-folder-summary-chevron-margin,0);opacity:var(--tjs-folder-summary-chevron-opacity,.2);transform:var(--tjs-folder-summary-chevron-rotate-closed,rotate(-90deg));transition:var(--tjs-folder-summary-chevron-transition,opacity .2s,transform .1s);width:var(--tjs-folder-summary-chevron-size,var(--tjs-folder-summary-font-size,1.25em))}summary.disabled.svelte-1kxmj7q.svelte-1kxmj7q{color:var(--tjs-folder-summary-disabled-color,inherit);cursor:var(--tjs-folder-summary-disabled-cursor,default)}summary.disabled.svelte-1kxmj7q svg.svelte-1kxmj7q{color:var(--tjs-folder-summary-disabled-color,currentColor);cursor:var(--tjs-folder-summary-disabled-cursor,default)}summary.svelte-1kxmj7q.svelte-1kxmj7q:focus-visible{box-shadow:var(--tjs-folder-summary-box-shadow-focus-visible,var(--tjs-default-box-shadow-focus-visible));outline:var(--tjs-folder-summary-outline-focus-visible,var(--tjs-default-outline-focus-visible,revert));transition:var(--tjs-folder-summary-transition-focus-visible,var(--tjs-default-transition-focus-visible))}summary.svelte-1kxmj7q:focus-visible .label.svelte-1kxmj7q{text-shadow:var(--tjs-folder-summary-label-text-shadow-focus-visible,var(--tjs-default-text-shadow-focus-hover,revert))}summary.svelte-1kxmj7q:focus-visible .tjs-folder-focus-indicator.svelte-1kxmj7q{background:var(--tjs-folder-summary-focus-indicator-background,var(--tjs-default-focus-indicator-background,#fff))}summary.svelte-1kxmj7q:focus-visible svg.svelte-1kxmj7q{opacity:var(--tjs-folder-summary-chevron-opacity-focus-visible,1)}summary.svelte-1kxmj7q:focus-visible svg.focus-chevron.svelte-1kxmj7q{outline:var(--tjs-folder-summary-outline-focus-visible,var(--tjs-default-outline-focus-visible,revert))}summary:focus-visible.remove-focus-visible.svelte-1kxmj7q.svelte-1kxmj7q{outline:none}summary.svelte-1kxmj7q:hover:not(.disabled) svg.svelte-1kxmj7q{opacity:var(--tjs-folder-summary-chevron-opacity-hover,1)}.tjs-folder-focus-indicator.svelte-1kxmj7q.svelte-1kxmj7q{align-self:var(--tjs-folder-summary-focus-indicator-align-self,var(--tjs-default-focus-indicator-align-self,stretch));border:var(--tjs-folder-summary-focus-indicator-border,var(--tjs-default-focus-indicator-border));border-radius:var(--tjs-folder-summary-focus-indicator-border-radius,var(--tjs-default-focus-indicator-border-radius,.1em));flex:0 0 var(--tjs-folder-summary-focus-indicator-width,var(--tjs-default-focus-indicator-width,0.25em));height:var(--tjs-folder-summary-focus-indicator-height,var(--tjs-default-focus-indicator-height));pointer-events:none;transition:var(--tjs-folder-summary-focus-indicator-transition,var(--tjs-default-focus-indicator-transition))}details[open].svelte-1kxmj7q>summary.svelte-1kxmj7q{background:var(--tjs-folder-summary-background-open,var(--tjs-folder-summary-background,inherit))}[open].svelte-1kxmj7q:not(details[data-closing=true])>summary svg.svelte-1kxmj7q{transform:rotate(var(--tjs-folder-summary-chevron-rotate-open,0))}.contents.svelte-1kxmj7q.svelte-1kxmj7q{background:var(--tjs-folder-contents-background,none);background-blend-mode:var(--tjs-folder-contents-background-blend-mode,initial);border:var(--tjs-folder-contents-border,none);border-left:var(--tjs-folder-contents-border-left,none);display:var(--tjs-folder-contents-display,flex);flex-direction:var(--tjs-folder-contents-flex-direction,column);gap:var(--tjs-folder-contents-gap);margin:var(--tjs-folder-contents-margin,0 0 0 -.4em);padding:var(--tjs-folder-contents-padding,0 0 0 calc(var(--tjs-folder-summary-font-size, 1em)*.8));position:relative}.contents.svelte-1kxmj7q.svelte-1kxmj7q:before{content:"";height:calc(100% + .65em);left:0;position:absolute;top:-.65em;width:0}.contents.hidden.svelte-1kxmj7q.svelte-1kxmj7q{display:none}.label.svelte-1kxmj7q.svelte-1kxmj7q{overflow:var(--tjs-folder-summary-label-overflow,hidden);text-overflow:var(--tjs-folder-summary-label-text-overflow,ellipsis);white-space:var(--tjs-folder-summary-label-white-space,nowrap);width:var(--tjs-folder-summary-label-width,fit-content)}summary.svelte-1kxmj7q:focus-visible+.contents.svelte-1kxmj7q:before{height:100%;top:0} 4 | details.svelte-ozx17m.svelte-ozx17m{margin-left:var(--tjs-folder-details-margin-left,-.4em);padding-left:var(--tjs-folder-details-padding-left,.4em)}summary.svelte-ozx17m.svelte-ozx17m{align-items:center;background:var(--tjs-folder-summary-background,none);background-blend-mode:var(--tjs-folder-summary-background-blend-mode,initial);border:var(--tjs-folder-summary-border,none);border-radius:var(--tjs-folder-summary-border-radius,0);border-width:var(--tjs-folder-summary-border-width,initial);cursor:var(--tjs-folder-summary-cursor,pointer);display:flex;font-family:var(--tjs-folder-summary-font-family,inherit);font-size:var(--tjs-folder-summary-font-size,inherit);font-weight:var(--tjs-folder-summary-font-weight,bold);gap:var(--tjs-folder-summary-gap,.125em);list-style:none;margin:var(--tjs-folder-summary-margin,0);padding:var(--tjs-folder-summary-padding,.25em) 0;position:relative;transition:var(--tjs-folder-summary-transition,background .1s);-webkit-user-select:none;user-select:none;width:var(--tjs-folder-summary-width,fit-content)}summary.svelte-ozx17m.svelte-ozx17m::-webkit-details-marker{display:none}summary.svelte-ozx17m i.svelte-ozx17m{border-radius:var(--tjs-folder-summary-chevron-border-radius,0);color:var(--tjs-folder-summary-chevron-color,inherit);cursor:var(--tjs-folder-summary-cursor,pointer);flex-shrink:0;margin:var(--tjs-folder-summary-chevron-margin,0 0 0 .25em);opacity:var(--tjs-folder-summary-chevron-opacity,1);transition:var(--tjs-folder-summary-chevron-transition,opacity .2s,transform .1s);width:var(--tjs-folder-summary-chevron-width,1.25em)}summary.disabled.svelte-ozx17m i.svelte-ozx17m,summary.disabled.svelte-ozx17m.svelte-ozx17m{color:var(--tjs-folder-summary-disabled-color,inherit);cursor:var(--tjs-folder-summary-disabled-cursor,default)}summary.svelte-ozx17m.svelte-ozx17m:focus-visible{box-shadow:var(--tjs-folder-summary-box-shadow-focus-visible,var(--tjs-default-box-shadow-focus-visible));outline:var(--tjs-folder-summary-outline-focus-visible,var(--tjs-default-outline-focus-visible,revert));transition:var(--tjs-folder-summary-transition-focus-visible,var(--tjs-default-transition-focus-visible))}summary.svelte-ozx17m:focus-visible .label.svelte-ozx17m{text-shadow:var(--tjs-folder-summary-label-text-shadow-focus-visible,var(--tjs-default-text-shadow-focus-hover,revert))}summary.svelte-ozx17m:focus-visible .tjs-folder-focus-indicator.svelte-ozx17m{background:var(--tjs-folder-summary-focus-indicator-background,var(--tjs-default-focus-indicator-background,#fff))}summary.svelte-ozx17m:focus-visible i.svelte-ozx17m{opacity:var(--tjs-folder-summary-chevron-opacity-focus-visible,1)}summary.svelte-ozx17m:focus-visible i.focus-chevron.svelte-ozx17m:before{outline:var(--tjs-folder-summary-outline-focus-visible,var(--tjs-default-outline-focus-visible,revert))}summary:focus-visible.remove-focus-visible.svelte-ozx17m.svelte-ozx17m{outline:none}summary.svelte-ozx17m:hover i.svelte-ozx17m{opacity:var(--tjs-folder-summary-chevron-opacity-hover,1)}.tjs-folder-focus-indicator.svelte-ozx17m.svelte-ozx17m{align-self:var(--tjs-folder-summary-focus-indicator-align-self,var(--tjs-default-focus-indicator-align-self,stretch));border:var(--tjs-folder-summary-focus-indicator-border,var(--tjs-default-focus-indicator-border));border-radius:var(--tjs-folder-summary-focus-indicator-border-radius,var(--tjs-default-focus-indicator-border-radius,.1em));flex:0 0 var(--tjs-folder-summary-focus-indicator-width,var(--tjs-default-focus-indicator-width,0.25em));height:var(--tjs-folder-summary-focus-indicator-height,var(--tjs-default-focus-indicator-height));pointer-events:none;transition:var(--tjs-folder-summary-focus-indicator-transition,var(--tjs-default-focus-indicator-transition))}.default-cursor.svelte-ozx17m.svelte-ozx17m{cursor:default}details[open].svelte-ozx17m>summary.svelte-ozx17m{background:var(--tjs-folder-summary-background-open,var(--tjs-folder-summary-background,inherit))}.contents.svelte-ozx17m.svelte-ozx17m{background:var(--tjs-folder-contents-background,none);background-blend-mode:var(--tjs-folder-contents-background-blend-mode,initial);border:var(--tjs-folder-contents-border,none);border-left:var(--tjs-folder-contents-border-left,none);display:var(--tjs-folder-contents-display,flex);flex-direction:var(--tjs-folder-contents-flex-direction,column);gap:var(--tjs-folder-contents-gap);margin:var(--tjs-folder-contents-margin,0 0 0 -.4em);padding:var(--tjs-folder-contents-padding,0 0 0 calc(var(--tjs-folder-summary-font-size, 1em)*.8));position:relative}.contents.svelte-ozx17m.svelte-ozx17m:before{content:"";height:calc(100% + .65em);left:0;position:absolute;top:-.65em;width:0}.contents.hidden.svelte-ozx17m.svelte-ozx17m{display:none}.label.svelte-ozx17m.svelte-ozx17m{overflow:var(--tjs-folder-summary-label-overflow,hidden);text-overflow:var(--tjs-folder-summary-label-text-overflow,ellipsis);white-space:var(--tjs-folder-summary-label-white-space,nowrap);width:var(--tjs-folder-summary-label-width,fit-content)}summary.svelte-ozx17m:focus-visible+.contents.svelte-ozx17m:before{height:100%;top:0} 5 | a.svelte-14yttr0{width:fit-content}a.svelte-14yttr0,span.svelte-14yttr0{margin-right:.25rem}span.svelte-14yttr0,svg.svelte-14yttr0{pointer-events:none}.indent-icon.svelte-14yttr0{margin-left:var(--dmt-nav-entry-indent-icon,28px)}.indent-no-icon.svelte-14yttr0{margin-left:var(--dmt-nav-entry-indent-no-icon,18px)}.indent-none.svelte-14yttr0{margin-left:.25rem} 6 | .dmt-navigation-tree.svelte-gftu0p{display:flex;flex-direction:column;--tjs-folder-summary-font-weight:normal;--tjs-folder-summary-font-size:1em;--tjs-folder-summary-margin:0;--tjs-folder-summary-padding:0;--tjs-folder-summary-width:100%;--tjs-folder-contents-margin:var(--dmt-nav-folder-contents-margin,0 0 0 7px);--tjs-folder-contents-border-left:var(--dmt-nav-folder-contents-border-left,2px solid var(--dmt-nav-folder-contents-border-color,#0003));--tjs-folder-contents-padding:var(--dmt-nav-folder-contents-padding,0 0 0 9px);overflow-x:auto;padding-left:var(--dmt-nav-tree-padding-left,3px);padding-top:var(--dmt-nav-tree-padding-top,.25rem);touch-action:pan-x pan-y}.dmt-navigation-tree.svelte-gftu0p:focus-visible{outline:#0000}.dmt-navigation-tree.svelte-gftu0p>:last-child{margin-bottom:var(--dmt-nav-tree-bottom-margin,.25rem)} 7 | hr.svelte-wwxcg7{border:unset;border-bottom:var(--dmt-container-border);margin:.25rem .5rem 0 0}section.svelte-wwxcg7{--tjs-folder-summary-margin:0 0.25rem;--tjs-folder-summary-width:100%;background:var(--dmt-container-background);border:var(--dmt-container-border);border-radius:var(--dmt-container-border-radius);box-shadow:var(--dmt-container-box-shadow);margin:0 1rem 0 0;padding-bottom:.25rem} 8 | a.svelte-gzwnip{display:block;overflow:var(--dmt-sidebarlinks-overflow,unset);text-overflow:var(--dmt-sidebarlinks-text-overflow,ellipsis);white-space:var(--dmt-sidebarlinks-white-space,normal)}section.dmt-sidebar-links.svelte-gzwnip{background:var(--dmt-container-background);border:var(--dmt-container-border);border-radius:var(--dmt-container-border-radius);box-shadow:var(--dmt-container-box-shadow);display:flex;flex-direction:column;gap:.25em;margin:0 1rem 0 0;padding:.25rem}section.dmt-sidebar-links.svelte-gzwnip:focus-visible{outline:#0000} 9 | div.svelte-1uxeem0{background:var(--dmt-overlay-panel-background);border-bottom:1px solid #0003;border-left:1px solid #0003;border-radius:0 0 .25rem .25rem;border-right:1px solid #0003;box-shadow:-.25rem 0 .25rem -.25rem var(--dmt-box-shadow-color),.25rem 0 .25rem -.25rem var(--dmt-box-shadow-color),0 .75rem .75rem -.75rem var(--dmt-box-shadow-color);display:flex;flex-direction:column;gap:.25rem;height:fit-content;padding:.5rem;position:absolute;top:2.6rem;width:calc(100% - 1.5rem)}span.svelte-1uxeem0{align-items:center;display:flex;gap:.5rem}svg.svelte-1uxeem0{margin:0}.key.svelte-1uxeem0{background:#ffffff40;border:1px solid #999;border-radius:.25rem;box-shadow:1px 1px var(--dmt-help-panel-key-box-shadow-color);min-width:52px;padding:0 3px;text-align:center}.reflection-kinds.svelte-1uxeem0{display:grid;grid-template-columns:1fr 1fr;grid-row-gap:.1rem}.title.svelte-1uxeem0{-webkit-text-decoration:underline;text-decoration:underline}:root[data-theme=dark]{--dmt-help-panel-key-box-shadow-color:#aaa}:root[data-theme=light]{--dmt-help-panel-key-box-shadow-color:#444}@media(prefers-color-scheme:dark){:root{--dmt-help-panel-key-box-shadow-color:#aaa}}@media(prefers-color-scheme:light){:root{--dmt-help-panel-key-box-shadow-color:#444}} 10 | button.svelte-1e7n8f7{background:#0000;border-radius:50%;cursor:pointer;height:2rem;line-height:0;width:2rem}section.svelte-1e7n8f7{align-items:center;background:var(--color-background);border-bottom:var(--dmt-container-border);display:flex;gap:.5rem;height:fit-content;padding:.25rem 1rem .25rem 0;position:sticky;top:0;width:inherit;z-index:10}section.svelte-1e7n8f7:focus-visible{outline:#0000}svg.svelte-1e7n8f7{height:1rem;width:1rem;fill:currentColor;stroke:currentColor;overflow:hidden}.flipped-vertical.svelte-1e7n8f7{transform:scaleY(-1)} 11 | section.svelte-13h1skr{--dmt-nav-tree-bottom-margin:1rem} 12 | button.svelte-10arjs8{background:#0000;border-radius:50%;cursor:pointer;height:33px;line-height:0;margin:0 3px 3px;width:33px} 13 | li.svelte-5rxzhp{align-items:center;background-color:var(--color-background);cursor:pointer;display:flex;gap:.25rem;overflow:hidden;padding:0 10px;text-overflow:ellipsis;transition:background .15s ease-in-out}li.selected.svelte-5rxzhp{background:var(--dmt-menu-item-background-selected)}li.svelte-5rxzhp:hover{background:var(--dmt-menu-item-background-hover)}li.svelte-5rxzhp:not(:last-child){border-bottom:var(--dmt-container-separator-border)}li.svelte-5rxzhp:nth-child(2n):not(:hover):not(.selected){background-color:var(--color-background-secondary)}ul.svelte-5rxzhp{border:var(--dmt-container-floating-border);border-bottom-left-radius:.5rem;border-bottom-right-radius:.5rem;box-shadow:var(--dmt-container-floating-box-shadow);list-style:none;margin:0 0 0 -4px;overflow:hidden;padding:0;position:absolute;top:calc(var(--dmt-header-height) - 2px);width:calc(100% - 4px)} 14 | #dmt-search-field.svelte-tuln0o,#tsd-search .field input{background:revert;border:1px solid var(--color-accent);border-radius:.5em;box-sizing:border-box;color:var(--color-text);font-size:16px;height:35px;opacity:1;outline:2px solid #0000;padding:revert;position:relative;right:4px;top:0;width:100%;z-index:10} 15 | div.svelte-wmon9h{box-sizing:border-box;line-height:0;padding:4px 0;position:absolute;right:-40px;width:40px}.dmt-widget.svelte-wmon9h{display:inline-block;height:40px;opacity:.8;overflow:hidden;transition:opacity .1s,background-color .2s;vertical-align:bottom} 16 | div.svelte-kjcljd{flex:0;height:0;width:0}div.svelte-kjcljd:focus{outline:none} 17 | section.svelte-1pcybcf.svelte-1pcybcf{background:var(--color-background);border:var(--dmt-container-border);border-radius:var(--dmt-container-border-radius);box-shadow:0 0 6px #000;height:max-content;left:50%;overflow:hidden;padding:.25rem;position:absolute;top:calc(100% + 10px);transform:translateX(-50%);width:max-content;z-index:1}div.svelte-1pcybcf.svelte-1pcybcf{align-items:center;display:flex;flex-direction:column;flex-wrap:nowrap;gap:.5rem}section.svelte-1pcybcf.svelte-1pcybcf:focus-visible{outline:2px solid #0000}a.svelte-1pcybcf.svelte-1pcybcf{align-items:center;display:flex;flex-shrink:0}a.svelte-1pcybcf:focus-visible img.svelte-1pcybcf{filter:brightness(1.5)}img.svelte-1pcybcf.svelte-1pcybcf{max-height:24px;width:auto}img.svelte-1pcybcf.svelte-1pcybcf:hover{filter:brightness(1.5)} 18 | button.svelte-1ebh1s9{background:#0000;border-radius:50%;cursor:pointer;height:33px;line-height:0;margin:1px 3px 0;width:33px}section.svelte-1ebh1s9{display:flex;margin-left:auto;min-width:fit-content;position:relative}svg.svelte-1ebh1s9{fill:var(--color-text)} 19 | a.svelte-1f93d1m.svelte-1f93d1m{align-items:center;display:flex;flex-shrink:0;margin:4px}a.svelte-1f93d1m:focus-visible img.svelte-1f93d1m{filter:brightness(1.5)}img.svelte-1f93d1m.svelte-1f93d1m{height:24px;width:auto}img.svelte-1f93d1m.svelte-1f93d1m:hover{filter:brightness(1.5)}section.svelte-1f93d1m.svelte-1f93d1m{align-items:center;display:flex;flex-wrap:nowrap;gap:.35rem;margin-left:auto;min-width:0;overflow:hidden} 20 | section.svelte-j9157o{align-items:center;display:flex;flex-wrap:nowrap;height:100%;margin-right:.5rem;width:100%} -------------------------------------------------------------------------------- /docs/assets/dmt/dmt-search.cmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/homebridge-plugins/homebridge-noip/196abd6f3ca1ead7ec0e94770387a254f32aeb33/docs/assets/dmt/dmt-search.cmp -------------------------------------------------------------------------------- /docs/assets/dmt/dmt-theme.css: -------------------------------------------------------------------------------- 1 | :root{--dmt-box-shadow-color:#00000080;--dmt-container-border-radius:0.5rem;--dmt-container-box-shadow:0 0 0.15rem var(--dmt-box-shadow-color);--dmt-container-box-shadow-floating:0 0 0.5rem var(--dmt-box-shadow-color);--dmt-container-max-height:calc(100vh - var(--dmt-header-height));--dmt-container-padding:1rem;--dmt-container-padding-member:0.5rem;--dmt-header-height:42px;--dmt-overlay-panel-background:#27292e;--dmt-menu-item-background-hover:var(--color-accent);--dmt-menu-item-background-selected:var(--color-accent);--dmt-nav-folder-contents-border-color:var(--color-active-menu-item);--dmt-page-menu-max-height:calc(100vh - var(--dmt-header-height) - var(--dmt-footer-height, 0px) - 1.5rem);--dmt-focus-visible-color:orange;--dmt-outline-focus-visible:3px solid var(--dmt-focus-visible-color);--tjs-folder-summary-chevron-border-radius:0.25rem;--tjs-default-outline-focus-visible:var(--dmt-outline-focus-visible)}:root[data-theme=dark]{--dmt-box-shadow-color:#0009;--dmt-container-background:#0000001a;--dmt-container-border:2px solid #0003;--dmt-container-floating-border:2px solid #0009;--dmt-container-separator-border:1px solid #000c;--dmt-overlay-panel-background:#27292e}:root[data-theme=light]{--dmt-box-shadow-color:#0000004d;--dmt-container-background:#0000000d;--dmt-container-border:2px solid #0000001a;--dmt-container-floating-border:2px solid #0000004d;--dmt-container-separator-border:1px solid #0006;--dmt-overlay-panel-background:#e6e8eb}@media (prefers-color-scheme:dark){:root{--dmt-box-shadow-color:#0009;--dmt-container-background:#0000001a;--dmt-container-border:2px solid #0003;--dmt-container-floating-border:2px solid #0009;--dmt-container-separator-border:1px solid #000c;--dmt-overlay-panel-background:#27292e}}@media (prefers-color-scheme:light){:root{--dmt-box-shadow-color:#0000004d;--dmt-container-background:#0000000d;--dmt-container-border:2px solid #0000001a;--dmt-container-floating-border:2px solid #0000004d;--dmt-container-separator-border:1px solid #0006;--dmt-overlay-panel-background:#e6e8eb}}#dmt-toolbar{container:dmt-toolbar/inline-size}.col-content{container:col-content/inline-size}.site-menu{container:site-menu/inline-size}body main{box-sizing:border-box;display:flex;flex-direction:column;height:100vh;overflow:hidden;width:100vw}body footer{flex-shrink:0;height:fit-content;width:100%}::-webkit-scrollbar{height:.75rem}.dmt-title-header-flex{align-items:center;display:flex;flex-wrap:nowrap}.dmt-title-header-flex h1{margin-right:auto}a.tsd-anchor-icon:focus-visible>svg{visibility:visible}summary.tsd-accordion-summary:focus-visible{outline:revert}.tsd-panel.tsd-member .tsd-anchor-link .tsd-anchor-icon{margin-right:auto}.col-content:focus-visible,.col-sidebar:focus-visible,.site-menu:focus-visible,details.tsd-page-navigation .tsd-accordion-details:focus-visible,div.container.container-main:focus-visible{outline:#0000}header.tsd-page-toolbar{flex-shrink:0;height:var(--dmt-header-height);left:unset;position:unset;top:unset}.container{max-width:unset}.container.container-main{box-sizing:border-box;flex-grow:1;overflow-x:hidden;width:100%}.col-content{contain:layout style paint;display:flex;flex-direction:column;height:fit-content;max-width:1300px;overflow:hidden;padding-bottom:1rem;padding-top:.75rem}.col-content .tsd-panel,.col-content .tsd-panel-group{margin-bottom:0}.col-content details[open].tsd-hierarchy>.tsd-accordion-summary,.col-content details[open].tsd-index-content>.tsd-accordion-summary{border-bottom:var(--dmt-container-border);padding-bottom:var(--dmt-container-padding)}.col-content details.tsd-panel-group summary{background:linear-gradient(to right,var(--dmt-container-background),var(--color-background));border-bottom:var(--dmt-container-border);border-radius:var(--dmt-container-border-radius) 0 0 var(--dmt-container-border-radius);padding:.25rem}.site-menu{contain:layout style paint;margin-top:0;max-height:unset;padding-left:.1rem;top:0}.site-menu .tsd-accordion-summary{align-items:center;display:flex}.site-menu .tsd-nested-navigation{margin-left:2.5rem}.page-menu{contain:layout style paint;display:flex;flex-direction:column;gap:1rem;max-height:var(--dmt-page-menu-max-height);overflow:revert;padding:.75rem .5rem .5rem;position:sticky;top:0}@media (min-width:770px) and (max-width:1399px){.col-sidebar{max-height:unset;padding-top:0;top:0}}details.tsd-accordion{will-change:height}details.tsd-accordion[open]:not(details[data-closing=true])>summary svg{transform:rotate(0deg)}details.tsd-accordion:not([open])>summary svg{transform:rotate(-90deg)}.dmt-summary-svg{transition:transform .1s}.container-main{background:var(--color-background)}:focus-visible{outline:var(--dmt-outline-focus-visible)}details .tsd-accordion-summary:focus-visible{outline:none}details .tsd-accordion-summary:focus-visible svg{border-radius:.25rem;outline:var(--dmt-outline-focus-visible)}details .tsd-accordion-summary h3,details .tsd-accordion-summary h5{align-items:center;display:flex;gap:.25rem}nav.tsd-navigation{display:flex;flex-direction:column;gap:.75rem}nav.tsd-navigation>section:first-child{margin-top:.75rem}nav.tsd-navigation>section:first-child.source-index{margin-top:0}.tsd-navigation a,.tsd-navigation summary>span,.tsd-page-navigation a{padding:.25rem}a{border-radius:.25rem}@container dmt-toolbar (min-width: 0){.title{font-size:clamp(.75rem,2.5cqi,1rem)}}@container col-content (min-width: 0){.tsd-page-title h1{font-size:clamp(1rem,3.5cqi,2rem)}.tsd-kind-icon~span,h5{font-size:clamp(.8rem,2.5cqi,1rem)}}@container site-menu (min-width: 0){.tsd-navigation span{font-size:clamp(.8rem,5cqi,1rem)}}.tsd-page-toolbar{box-shadow:var(--dmt-header-box-shadow)}.tsd-page-toolbar .tsd-toolbar-contents{padding:0 .25rem 0 1rem}.tsd-page-toolbar .tsd-toolbar-icon{padding:12px}.tsd-page-toolbar .tsd-toolbar-icon.menu{height:30px;margin:6px;padding:6px;width:28px}#dmt-toolbar{align-items:center;display:flex}#dmt-toolbar a.title{height:32px;line-height:32px;padding-right:.5rem}.container{margin:0 auto;padding:0 1rem}.container .tsd-signature{border-radius:.5rem}.container .tsd-signatures .tsd-signature{border-radius:0}.container .tsd-panel-group.tsd-index-group details,.container .tsd-panel-group.tsd-index-group details .tsd-accordion-summary .tsd-index-heading{margin:0}.container .tsd-panel-group.tsd-index-group details .tsd-accordion-details .tsd-index-heading{margin:0 0 15px}.container .tsd-panel-group.tsd-index-group details .tsd-accordion-details section:not(:last-child){margin-bottom:15px}.container .tsd-panel-group.tsd-index-group .tsd-index-list{overflow:auto}.container .tsd-panel-group.tsd-hierarchy .tsd-accordion-summary a,.container .tsd-panel-group.tsd-index-group .tsd-accordion-summary a{width:unset}.container .tsd-panel-group.tsd-hierarchy details,.container .tsd-panel-group.tsd-index-group details{background:var(--dmt-container-background);border:var(--dmt-container-border);border-radius:var(--dmt-container-border-radius);box-shadow:var(--dmt-container-box-shadow);padding:var(--dmt-container-padding)}.container .tsd-panel-group.tsd-hierarchy .tsd-accordion-details{padding-top:.75rem}.container .tsd-panel-group.tsd-hierarchy .tsd-accordion-details ul.tsd-hierarchy{padding-left:1.25rem}.container section.tsd-index-section .tsd-index-list{padding:var(--dmt-container-padding)}.container section.tsd-index-section .tsd-index-list,.container section.tsd-panel.tsd-member{background:var(--dmt-container-background);border:var(--dmt-container-border);border-radius:var(--dmt-container-border-radius);box-shadow:var(--dmt-container-box-shadow)}.container section.tsd-panel.tsd-member{margin-bottom:2rem;padding:var(--dmt-container-padding-member)}.container .tsd-returns-title p,.container li.tsd-description,.container ul.tsd-signatures{margin-bottom:0}.container ul.tsd-parameter-list{background:var(--dmt-container-background);border:var(--dmt-container-border);border-radius:var(--dmt-container-border-radius);box-shadow:var(--dmt-container-box-shadow);padding:var(--dmt-container-padding-member);padding-left:1.5rem}.container ul.tsd-parameter-list h5:first-child,.container ul.tsd-parameter-list h5:last-child{margin-top:0}.container ul.tsd-parameter-list ul.tsd-parameters{background:unset;border:unset;border-radius:unset;box-shadow:unset}.container ul.tsd-parameters{background:var(--dmt-container-background);border:var(--dmt-container-border);border-radius:var(--dmt-container-border-radius);box-shadow:var(--dmt-container-box-shadow);display:flex;flex-direction:column;gap:1rem;margin-bottom:0;padding:var(--dmt-container-padding-member);padding-left:1.5rem}.container ul.tsd-parameters h5:first-child{margin-top:0}.container ul.tsd-parameters h5:last-child{margin-bottom:0}.container ul.tsd-parameters.no-children{background:unset;border:unset;border-radius:unset;box-shadow:unset;margin:0;padding:0}.container .tsd-breadcrumb li:last-child:after{content:""}@media (min-width:2190px){.page-menu{max-width:350px}.container-main{grid-template-columns:minmax(0,520px) minmax(0,1300px) 1fr;padding-left:calc(50% - 1095px)}}@media (width < 1200px){.tsd-accordion.tsd-page-navigation{display:none}.page-menu{margin-bottom:.75rem;padding-right:1rem;position:static}.site-menu{max-height:unset}}@media (max-width:769px){.from-has-menu .col-sidebar{display:flex;flex-direction:column}.from-has-menu .col-sidebar,.has-menu .col-sidebar{gap:.5rem;padding:0 0 0 1rem}}.page-menu .tsd-navigation.settings{background:var(--dmt-container-background);border:var(--dmt-container-border);border-radius:var(--dmt-container-border-radius);box-shadow:var(--dmt-container-box-shadow-floating);margin:0;padding:var(--dmt-container-padding)}.page-menu .tsd-navigation.settings .tsd-accordion-summary{width:inherit}.page-menu .tsd-navigation.settings .tsd-filter-visibility h4{padding-top:.25rem}.page-menu .tsd-navigation.settings input[type=checkbox]:focus-visible+svg{outline:var(--dmt-outline-focus-visible)}.page-menu details.tsd-accordion.tsd-page-navigation{background:var(--dmt-container-background);border:var(--dmt-container-border);border-radius:var(--dmt-container-border-radius);box-shadow:var(--dmt-container-box-shadow-floating);overflow:hidden;padding:var(--dmt-container-padding)}.page-menu details.tsd-accordion.tsd-page-navigation .tsd-accordion-details{height:calc(100% - var(--dmt-header-height));overflow-x:hidden;overflow-y:auto}.page-menu details.tsd-accordion.tsd-page-navigation .tsd-accordion-details ul{padding:.25rem 0 .25rem .5rem}.page-menu details[open].tsd-accordion.tsd-page-navigation{padding:var(--dmt-container-padding) 0 var(--dmt-container-padding) var(--dmt-container-padding)}.page-menu details[open].tsd-accordion.tsd-page-navigation::details-content{display:contents}.page-menu details[open].tsd-accordion.tsd-page-navigation>.tsd-accordion-summary{width:calc(100% - 1rem)}.page-menu details[open].tsd-accordion.tsd-page-navigation .tsd-accordion-details{padding-top:.75rem}.page-menu .tsd-navigation.settings details[open]>.tsd-accordion-summary,.page-menu details[open].tsd-page-navigation>.tsd-accordion-summary{border-bottom:var(--dmt-container-border);padding-bottom:var(--dmt-container-padding)}.page-menu .tsd-page-navigation details.tsd-page-navigation-section{margin:0 var(--dmt-container-padding) 0 0}.page-menu .tsd-page-navigation details.tsd-page-navigation-section:not(:last-child){margin-bottom:.5rem}.page-menu .tsd-page-navigation details.tsd-page-navigation-section summary{align-items:center;background:linear-gradient(to right,var(--dmt-container-background),#0000);border-bottom:var(--dmt-container-border);border-radius:var(--dmt-container-border-radius) 0 0 var(--dmt-container-border-radius);display:flex;gap:.25rem}.page-menu .tsd-page-navigation details.tsd-page-navigation-section>div{margin-left:8px;overflow-wrap:anywhere;width:calc(100% + 8px)} -------------------------------------------------------------------------------- /docs/assets/hierarchy.js: -------------------------------------------------------------------------------- 1 | window.hierarchyData = "eJyrVirKzy8pVrKKjtVRKkpNy0lNLsnMzytWsqqurQUAmx4Kpg==" -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-code-background: #FFFFFF; 3 | --dark-code-background: #1E1E1E; 4 | } 5 | 6 | @media (prefers-color-scheme: light) { :root { 7 | --code-background: var(--light-code-background); 8 | } } 9 | 10 | @media (prefers-color-scheme: dark) { :root { 11 | --code-background: var(--dark-code-background); 12 | } } 13 | 14 | :root[data-theme='light'] { 15 | --code-background: var(--light-code-background); 16 | } 17 | 18 | :root[data-theme='dark'] { 19 | --code-background: var(--dark-code-background); 20 | } 21 | 22 | pre, code { background: var(--code-background); } 23 | -------------------------------------------------------------------------------- /docs/assets/icons.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | addIcons(); 3 | function addIcons() { 4 | if (document.readyState === "loading") return document.addEventListener("DOMContentLoaded", addIcons); 5 | const svg = document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); 6 | svg.innerHTML = `MMNEPVFCICPMFPCPTTAAATR`; 7 | svg.style.display = "none"; 8 | if (location.protocol === "file:") updateUseElements(); 9 | } 10 | 11 | function updateUseElements() { 12 | document.querySelectorAll("use").forEach(el => { 13 | if (el.getAttribute("href").includes("#icon-")) { 14 | el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); 15 | } 16 | }); 17 | } 18 | })() -------------------------------------------------------------------------------- /docs/assets/icons.svg: -------------------------------------------------------------------------------- 1 | MMNEPVFCICPMFPCPTTAAATR -------------------------------------------------------------------------------- /docs/assets/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | window.translations={"copy":"Copy","copied":"Copied!","normally_hidden":"This member is normally hidden due to your filter settings.","hierarchy_expand":"Expand","hierarchy_collapse":"Collapse","folder":"Folder","kind_1":"Project","kind_2":"Module","kind_4":"Namespace","kind_8":"Enumeration","kind_16":"Enumeration Member","kind_32":"Variable","kind_64":"Function","kind_128":"Class","kind_256":"Interface","kind_512":"Constructor","kind_1024":"Property","kind_2048":"Method","kind_4096":"Call Signature","kind_8192":"Index Signature","kind_16384":"Constructor Signature","kind_32768":"Parameter","kind_65536":"Type Literal","kind_131072":"Type Parameter","kind_262144":"Accessor","kind_524288":"Get Signature","kind_1048576":"Set Signature","kind_2097152":"Type Alias","kind_4194304":"Reference","kind_8388608":"Document"}; 3 | "use strict";(()=>{var De=Object.create;var le=Object.defineProperty;var Fe=Object.getOwnPropertyDescriptor;var Ne=Object.getOwnPropertyNames;var Ve=Object.getPrototypeOf,Be=Object.prototype.hasOwnProperty;var qe=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var je=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Ne(e))!Be.call(t,i)&&i!==n&&le(t,i,{get:()=>e[i],enumerable:!(r=Fe(e,i))||r.enumerable});return t};var $e=(t,e,n)=>(n=t!=null?De(Ve(t)):{},je(e||!t||!t.__esModule?le(n,"default",{value:t,enumerable:!0}):n,t));var pe=qe((de,he)=>{(function(){var t=function(e){var n=new t.Builder;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),n.searchPipeline.add(t.stemmer),e.call(n,n),n.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(n){e.console&&console.warn&&console.warn(n)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var n=Object.create(null),r=Object.keys(e),i=0;i0){var d=t.utils.clone(n)||{};d.position=[a,c],d.index=s.length,s.push(new t.Token(r.slice(a,o),d))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. 4 | `,e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(r){var i=t.Pipeline.registeredFunctions[r];if(i)n.add(i);else throw new Error("Cannot load unregistered function: "+r)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(n){t.Pipeline.warnIfFunctionNotRegistered(n),this._stack.push(n)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");r=r+1,this._stack.splice(r,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");this._stack.splice(r,0,n)},t.Pipeline.prototype.remove=function(e){var n=this._stack.indexOf(e);n!=-1&&this._stack.splice(n,1)},t.Pipeline.prototype.run=function(e){for(var n=this._stack.length,r=0;r1&&(oe&&(r=s),o!=e);)i=r-n,s=n+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ol?d+=2:a==l&&(n+=r[c+1]*i[d+1],c+=2,d+=2);return n},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),n=1,r=0;n0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}if(s.str.length==0&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}s.str.length==1&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var d=s.str.charAt(0),m=s.str.charAt(1),p;m in s.node.edges?p=s.node.edges[m]:(p=new t.TokenSet,s.node.edges[m]=p),s.str.length==1&&(p.final=!0),i.push({node:p,editsRemaining:s.editsRemaining-1,str:d+s.str.slice(2)})}}}return r},t.TokenSet.fromString=function(e){for(var n=new t.TokenSet,r=n,i=0,s=e.length;i=e;n--){var r=this.uncheckedNodes[n],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(n){var r=new t.QueryParser(e,n);r.parse()})},t.Index.prototype.query=function(e){for(var n=new t.Query(this.fields),r=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),l=0;l1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,n){var r=e[this._ref],i=Object.keys(this._fields);this._documents[r]=n||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,n;do e=this.next(),n=e.charCodeAt(0);while(n>47&&n<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var n=e.next();if(n==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(n.charCodeAt(0)==92){e.escapeCharacter();continue}if(n==":")return t.QueryLexer.lexField;if(n=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(n=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(n=="+"&&e.width()===1||n=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(n.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,n){this.lexer=new t.QueryLexer(e),this.query=n,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var n=e.peekLexeme();if(n!=null)switch(n.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+n.type;throw n.str.length>=1&&(r+=" with value '"+n.str+"'"),new t.QueryParseError(r,n.start,n.end)}},t.QueryParser.parsePresence=function(e){var n=e.consumeLexeme();if(n!=null){switch(n.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+n.str+"'";throw new t.QueryParseError(r,n.start,n.end)}var i=e.peekLexeme();if(i==null){var r="expecting term or field, found nothing";throw new t.QueryParseError(r,n.start,n.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(r,i.start,i.end)}}},t.QueryParser.parseField=function(e){var n=e.consumeLexeme();if(n!=null){if(e.query.allFields.indexOf(n.str)==-1){var r=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+n.str+"', possible fields: "+r;throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.fields=[n.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,n.start,n.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var n=e.consumeLexeme();if(n!=null){e.currentClause.term=n.str.toLowerCase(),n.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(r==null){e.nextClause();return}switch(r.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+r.type+"'";throw new t.QueryParseError(i,r.start,r.end)}}},t.QueryParser.parseEditDistance=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="edit distance must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.editDistance=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="boost must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.boost=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,n){typeof define=="function"&&define.amd?define(n):typeof de=="object"?he.exports=n():e.lunr=n()}(this,function(){return t})})()});window.translations||={copy:"Copy",copied:"Copied!",normally_hidden:"This member is normally hidden due to your filter settings.",hierarchy_expand:"Expand",hierarchy_collapse:"Collapse",folder:"Folder",kind_1:"Project",kind_2:"Module",kind_4:"Namespace",kind_8:"Enumeration",kind_16:"Enumeration Member",kind_32:"Variable",kind_64:"Function",kind_128:"Class",kind_256:"Interface",kind_512:"Constructor",kind_1024:"Property",kind_2048:"Method",kind_4096:"Call Signature",kind_8192:"Index Signature",kind_16384:"Constructor Signature",kind_32768:"Parameter",kind_65536:"Type Literal",kind_131072:"Type Parameter",kind_262144:"Accessor",kind_524288:"Get Signature",kind_1048576:"Set Signature",kind_2097152:"Type Alias",kind_4194304:"Reference",kind_8388608:"Document"};var ce=[];function G(t,e){ce.push({selector:e,constructor:t})}var J=class{alwaysVisibleMember=null;constructor(){this.createComponents(document.body),this.ensureFocusedElementVisible(),this.listenForCodeCopies(),window.addEventListener("hashchange",()=>this.ensureFocusedElementVisible()),document.body.style.display||(this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}createComponents(e){ce.forEach(n=>{e.querySelectorAll(n.selector).forEach(r=>{r.dataset.hasInstance||(new n.constructor({el:r,app:this}),r.dataset.hasInstance=String(!0))})})}filterChanged(){this.ensureFocusedElementVisible()}showPage(){document.body.style.display&&(document.body.style.removeProperty("display"),this.ensureFocusedElementVisible(),this.updateIndexVisibility(),this.scrollToHash())}scrollToHash(){if(location.hash){let e=document.getElementById(location.hash.substring(1));if(!e)return;e.scrollIntoView({behavior:"instant",block:"start"})}}ensureActivePageVisible(){let e=document.querySelector(".tsd-navigation .current"),n=e?.parentElement;for(;n&&!n.classList.contains(".tsd-navigation");)n instanceof HTMLDetailsElement&&(n.open=!0),n=n.parentElement;if(e&&!ze(e)){let r=e.getBoundingClientRect().top-document.documentElement.clientHeight/4;document.querySelector(".site-menu").scrollTop=r,document.querySelector(".col-sidebar").scrollTop=r}}updateIndexVisibility(){let e=document.querySelector(".tsd-index-content"),n=e?.open;e&&(e.open=!0),document.querySelectorAll(".tsd-index-section").forEach(r=>{r.style.display="block";let i=Array.from(r.querySelectorAll(".tsd-index-link")).every(s=>s.offsetParent==null);r.style.display=i?"none":"block"}),e&&(e.open=n)}ensureFocusedElementVisible(){if(this.alwaysVisibleMember&&(this.alwaysVisibleMember.classList.remove("always-visible"),this.alwaysVisibleMember.firstElementChild.remove(),this.alwaysVisibleMember=null),!location.hash)return;let e=document.getElementById(location.hash.substring(1));if(!e)return;let n=e.parentElement;for(;n&&n.tagName!=="SECTION";)n=n.parentElement;if(!n)return;let r=n.offsetParent==null,i=n;for(;i!==document.body;)i instanceof HTMLDetailsElement&&(i.open=!0),i=i.parentElement;if(n.offsetParent==null){this.alwaysVisibleMember=n,n.classList.add("always-visible");let s=document.createElement("p");s.classList.add("warning"),s.textContent=window.translations.normally_hidden,n.prepend(s)}r&&e.scrollIntoView()}listenForCodeCopies(){document.querySelectorAll("pre > button").forEach(e=>{let n;e.addEventListener("click",()=>{e.previousElementSibling instanceof HTMLElement&&navigator.clipboard.writeText(e.previousElementSibling.innerText.trim()),e.textContent=window.translations.copied,e.classList.add("visible"),clearTimeout(n),n=setTimeout(()=>{e.classList.remove("visible"),n=setTimeout(()=>{e.textContent=window.translations.copy},100)},1e3)})})}};function ze(t){let e=t.getBoundingClientRect(),n=Math.max(document.documentElement.clientHeight,window.innerHeight);return!(e.bottom<0||e.top-n>=0)}var ue=(t,e=100)=>{let n;return()=>{clearTimeout(n),n=setTimeout(()=>t(),e)}};var ge=$e(pe(),1);async function A(t){let e=Uint8Array.from(atob(t),s=>s.charCodeAt(0)),r=new Blob([e]).stream().pipeThrough(new DecompressionStream("deflate")),i=await new Response(r).text();return JSON.parse(i)}async function fe(t,e){if(!window.searchData)return;let n=await A(window.searchData);t.data=n,t.index=ge.Index.load(n.index),e.classList.remove("loading"),e.classList.add("ready")}function ve(){let t=document.getElementById("tsd-search");if(!t)return;let e={base:document.documentElement.dataset.base+"/"},n=document.getElementById("tsd-search-script");t.classList.add("loading"),n&&(n.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),n.addEventListener("load",()=>{fe(e,t)}),fe(e,t));let r=document.querySelector("#tsd-search input"),i=document.querySelector("#tsd-search .results");if(!r||!i)throw new Error("The input field or the result list wrapper was not found");i.addEventListener("mouseup",()=>{re(t)}),r.addEventListener("focus",()=>t.classList.add("has-focus")),We(t,i,r,e)}function We(t,e,n,r){n.addEventListener("input",ue(()=>{Ue(t,e,n,r)},200)),n.addEventListener("keydown",i=>{i.key=="Enter"?Je(e,t):i.key=="ArrowUp"?(me(e,n,-1),i.preventDefault()):i.key==="ArrowDown"&&(me(e,n,1),i.preventDefault())}),document.body.addEventListener("keypress",i=>{i.altKey||i.ctrlKey||i.metaKey||!n.matches(":focus")&&i.key==="/"&&(i.preventDefault(),n.focus())}),document.body.addEventListener("keyup",i=>{t.classList.contains("has-focus")&&(i.key==="Escape"||!e.matches(":focus-within")&&!n.matches(":focus"))&&(n.blur(),re(t))})}function re(t){t.classList.remove("has-focus")}function Ue(t,e,n,r){if(!r.index||!r.data)return;e.textContent="";let i=n.value.trim(),s;if(i){let o=i.split(" ").map(a=>a.length?`*${a}*`:"").join(" ");s=r.index.search(o)}else s=[];for(let o=0;oa.score-o.score);for(let o=0,a=Math.min(10,s.length);o`,d=ye(l.name,i);globalThis.DEBUG_SEARCH_WEIGHTS&&(d+=` (score: ${s[o].score.toFixed(2)})`),l.parent&&(d=` 5 | ${ye(l.parent,i)}.${d}`);let m=document.createElement("li");m.classList.value=l.classes??"";let p=document.createElement("a");p.href=r.base+l.url,p.innerHTML=c+d,m.append(p),p.addEventListener("focus",()=>{e.querySelector(".current")?.classList.remove("current"),m.classList.add("current")}),e.appendChild(m)}}function me(t,e,n){let r=t.querySelector(".current");if(!r)r=t.querySelector(n==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let i=r;if(n===1)do i=i.nextElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);else do i=i.previousElementSibling??void 0;while(i instanceof HTMLElement&&i.offsetParent==null);i?(r.classList.remove("current"),i.classList.add("current")):n===-1&&(r.classList.remove("current"),e.focus())}}function Je(t,e){let n=t.querySelector(".current");if(n||(n=t.querySelector("li:first-child")),n){let r=n.querySelector("a");r&&(window.location.href=r.href),re(e)}}function ye(t,e){if(e==="")return t;let n=t.toLocaleLowerCase(),r=e.toLocaleLowerCase(),i=[],s=0,o=n.indexOf(r);for(;o!=-1;)i.push(ne(t.substring(s,o)),`${ne(t.substring(o,o+r.length))}`),s=o+r.length,o=n.indexOf(r,s);return i.push(ne(t.substring(s))),i.join("")}var Ge={"&":"&","<":"<",">":">","'":"'",'"':"""};function ne(t){return t.replace(/[&<>"'"]/g,e=>Ge[e])}var I=class{el;app;constructor(e){this.el=e.el,this.app=e.app}};var H="mousedown",Ee="mousemove",B="mouseup",X={x:0,y:0},xe=!1,ie=!1,Xe=!1,D=!1,be=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(be?"is-mobile":"not-mobile");be&&"ontouchstart"in document.documentElement&&(Xe=!0,H="touchstart",Ee="touchmove",B="touchend");document.addEventListener(H,t=>{ie=!0,D=!1;let e=H=="touchstart"?t.targetTouches[0]:t;X.y=e.pageY||0,X.x=e.pageX||0});document.addEventListener(Ee,t=>{if(ie&&!D){let e=H=="touchstart"?t.targetTouches[0]:t,n=X.x-(e.pageX||0),r=X.y-(e.pageY||0);D=Math.sqrt(n*n+r*r)>10}});document.addEventListener(B,()=>{ie=!1});document.addEventListener("click",t=>{xe&&(t.preventDefault(),t.stopImmediatePropagation(),xe=!1)});var Y=class extends I{active;className;constructor(e){super(e),this.className=this.el.dataset.toggle||"",this.el.addEventListener(B,n=>this.onPointerUp(n)),this.el.addEventListener("click",n=>n.preventDefault()),document.addEventListener(H,n=>this.onDocumentPointerDown(n)),document.addEventListener(B,n=>this.onDocumentPointerUp(n))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let n=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(n),setTimeout(()=>document.documentElement.classList.remove(n),500)}onPointerUp(e){D||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-sidebar, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!D&&this.active&&e.target.closest(".col-sidebar")){let n=e.target.closest("a");if(n){let r=window.location.href;r.indexOf("#")!=-1&&(r=r.substring(0,r.indexOf("#"))),n.href.substring(0,r.length)==r&&setTimeout(()=>this.setActive(!1),250)}}}};var se;try{se=localStorage}catch{se={getItem(){return null},setItem(){}}}var C=se;var Le=document.head.appendChild(document.createElement("style"));Le.dataset.for="filters";var Z=class extends I{key;value;constructor(e){super(e),this.key=`filter-${this.el.name}`,this.value=this.el.checked,this.el.addEventListener("change",()=>{this.setLocalStorage(this.el.checked)}),this.setLocalStorage(this.fromLocalStorage()),Le.innerHTML+=`html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } 6 | `,this.app.updateIndexVisibility()}fromLocalStorage(){let e=C.getItem(this.key);return e?e==="true":this.el.checked}setLocalStorage(e){C.setItem(this.key,e.toString()),this.value=e,this.handleValueChange()}handleValueChange(){this.el.checked=this.value,document.documentElement.classList.toggle(this.key,this.value),this.app.filterChanged(),this.app.updateIndexVisibility()}};var oe=new Map,ae=class{open;accordions=[];key;constructor(e,n){this.key=e,this.open=n}add(e){this.accordions.push(e),e.open=this.open,e.addEventListener("toggle",()=>{this.toggle(e.open)})}toggle(e){for(let n of this.accordions)n.open=e;C.setItem(this.key,e.toString())}},K=class extends I{constructor(e){super(e);let n=this.el.querySelector("summary"),r=n.querySelector("a");r&&r.addEventListener("click",()=>{location.assign(r.href)});let i=`tsd-accordion-${n.dataset.key??n.textContent.trim().replace(/\s+/g,"-").toLowerCase()}`,s;if(oe.has(i))s=oe.get(i);else{let o=C.getItem(i),a=o?o==="true":this.el.open;s=new ae(i,a),oe.set(i,s)}s.add(this.el)}};function Se(t){let e=C.getItem("tsd-theme")||"os";t.value=e,we(e),t.addEventListener("change",()=>{C.setItem("tsd-theme",t.value),we(t.value)})}function we(t){document.documentElement.dataset.theme=t}var ee;function Ce(){let t=document.getElementById("tsd-nav-script");t&&(t.addEventListener("load",Te),Te())}async function Te(){let t=document.getElementById("tsd-nav-container");if(!t||!window.navigationData)return;let e=await A(window.navigationData);ee=document.documentElement.dataset.base,ee.endsWith("/")||(ee+="/"),t.innerHTML="";for(let n of e)Ie(n,t,[]);window.app.createComponents(t),window.app.showPage(),window.app.ensureActivePageVisible()}function Ie(t,e,n){let r=e.appendChild(document.createElement("li"));if(t.children){let i=[...n,t.text],s=r.appendChild(document.createElement("details"));s.className=t.class?`${t.class} tsd-accordion`:"tsd-accordion";let o=s.appendChild(document.createElement("summary"));o.className="tsd-accordion-summary",o.dataset.key=i.join("$"),o.innerHTML='',ke(t,o);let a=s.appendChild(document.createElement("div"));a.className="tsd-accordion-details";let l=a.appendChild(document.createElement("ul"));l.className="tsd-nested-navigation";for(let c of t.children)Ie(c,l,i)}else ke(t,r,t.class)}function ke(t,e,n){if(t.path){let r=e.appendChild(document.createElement("a"));if(r.href=ee+t.path,n&&(r.className=n),location.pathname===r.pathname&&!r.href.includes("#")&&r.classList.add("current"),t.kind){let i=window.translations[`kind_${t.kind}`].replaceAll('"',""");r.innerHTML=``}r.appendChild(document.createElement("span")).textContent=t.text}else{let r=e.appendChild(document.createElement("span")),i=window.translations.folder.replaceAll('"',""");r.innerHTML=``,r.appendChild(document.createElement("span")).textContent=t.text}}var te=document.documentElement.dataset.base;te.endsWith("/")||(te+="/");function Pe(){document.querySelector(".tsd-full-hierarchy")?Ye():document.querySelector(".tsd-hierarchy")&&Ze()}function Ye(){document.addEventListener("click",r=>{let i=r.target;for(;i.parentElement&&i.parentElement.tagName!="LI";)i=i.parentElement;i.dataset.dropdown&&(i.dataset.dropdown=String(i.dataset.dropdown!=="true"))});let t=new Map,e=new Set;for(let r of document.querySelectorAll(".tsd-full-hierarchy [data-refl]")){let i=r.querySelector("ul");t.has(r.dataset.refl)?e.add(r.dataset.refl):i&&t.set(r.dataset.refl,i)}for(let r of e)n(r);function n(r){let i=t.get(r).cloneNode(!0);i.querySelectorAll("[id]").forEach(s=>{s.removeAttribute("id")}),i.querySelectorAll("[data-dropdown]").forEach(s=>{s.dataset.dropdown="false"});for(let s of document.querySelectorAll(`[data-refl="${r}"]`)){let o=tt(),a=s.querySelector("ul");s.insertBefore(o,a),o.dataset.dropdown=String(!!a),a||s.appendChild(i.cloneNode(!0))}}}function Ze(){let t=document.getElementById("tsd-hierarchy-script");t&&(t.addEventListener("load",Qe),Qe())}async function Qe(){let t=document.querySelector(".tsd-panel.tsd-hierarchy:has(h4 a)");if(!t||!window.hierarchyData)return;let e=+t.dataset.refl,n=await A(window.hierarchyData),r=t.querySelector("ul"),i=document.createElement("ul");if(i.classList.add("tsd-hierarchy"),Ke(i,n,e),r.querySelectorAll("li").length==i.querySelectorAll("li").length)return;let s=document.createElement("span");s.classList.add("tsd-hierarchy-toggle"),s.textContent=window.translations.hierarchy_expand,t.querySelector("h4 a")?.insertAdjacentElement("afterend",s),s.insertAdjacentText("beforebegin",", "),s.addEventListener("click",()=>{s.textContent===window.translations.hierarchy_expand?(r.insertAdjacentElement("afterend",i),r.remove(),s.textContent=window.translations.hierarchy_collapse):(i.insertAdjacentElement("afterend",r),i.remove(),s.textContent=window.translations.hierarchy_expand)})}function Ke(t,e,n){let r=e.roots.filter(i=>et(e,i,n));for(let i of r)t.appendChild(_e(e,i,n))}function _e(t,e,n,r=new Set){if(r.has(e))return;r.add(e);let i=t.reflections[e],s=document.createElement("li");if(s.classList.add("tsd-hierarchy-item"),e===n){let o=s.appendChild(document.createElement("span"));o.textContent=i.name,o.classList.add("tsd-hierarchy-target")}else{for(let a of i.uniqueNameParents||[]){let l=t.reflections[a],c=s.appendChild(document.createElement("a"));c.textContent=l.name,c.href=te+l.url,c.className=l.class+" tsd-signature-type",s.append(document.createTextNode("."))}let o=s.appendChild(document.createElement("a"));o.textContent=t.reflections[e].name,o.href=te+i.url,o.className=i.class+" tsd-signature-type"}if(i.children){let o=s.appendChild(document.createElement("ul"));o.classList.add("tsd-hierarchy");for(let a of i.children){let l=_e(t,a,n,r);l&&o.appendChild(l)}}return r.delete(e),s}function et(t,e,n){if(e===n)return!0;let r=new Set,i=[t.reflections[e]];for(;i.length;){let s=i.pop();if(!r.has(s)){r.add(s);for(let o of s.children||[]){if(o===n)return!0;i.push(t.reflections[o])}}}return!1}function tt(){let t=document.createElementNS("http://www.w3.org/2000/svg","svg");return t.setAttribute("width","20"),t.setAttribute("height","20"),t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","none"),t.innerHTML='',t}G(Y,"a[data-toggle]");G(Z,".tsd-filter-item input[type=checkbox]");var Oe=document.getElementById("tsd-theme");Oe&&Se(Oe);var nt=new J;Object.defineProperty(window,"app",{value:nt});})(); 7 | /*! Bundled license information: 8 | 9 | lunr/lunr.js: 10 | (** 11 | * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 12 | * Copyright (C) 2020 Oliver Nightingale 13 | * @license MIT 14 | *) 15 | (*! 16 | * lunr.utils 17 | * Copyright (C) 2020 Oliver Nightingale 18 | *) 19 | (*! 20 | * lunr.Set 21 | * Copyright (C) 2020 Oliver Nightingale 22 | *) 23 | (*! 24 | * lunr.tokenizer 25 | * Copyright (C) 2020 Oliver Nightingale 26 | *) 27 | (*! 28 | * lunr.Pipeline 29 | * Copyright (C) 2020 Oliver Nightingale 30 | *) 31 | (*! 32 | * lunr.Vector 33 | * Copyright (C) 2020 Oliver Nightingale 34 | *) 35 | (*! 36 | * lunr.stemmer 37 | * Copyright (C) 2020 Oliver Nightingale 38 | * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt 39 | *) 40 | (*! 41 | * lunr.stopWordFilter 42 | * Copyright (C) 2020 Oliver Nightingale 43 | *) 44 | (*! 45 | * lunr.trimmer 46 | * Copyright (C) 2020 Oliver Nightingale 47 | *) 48 | (*! 49 | * lunr.TokenSet 50 | * Copyright (C) 2020 Oliver Nightingale 51 | *) 52 | (*! 53 | * lunr.Index 54 | * Copyright (C) 2020 Oliver Nightingale 55 | *) 56 | (*! 57 | * lunr.Builder 58 | * Copyright (C) 2020 Oliver Nightingale 59 | *) 60 | */ 61 | -------------------------------------------------------------------------------- /docs/functions/default.html: -------------------------------------------------------------------------------- 1 | default | @homebridge-plugins/homebridge-noip
2 |

    Function default

    • Parameters

      • api: API

      Returns void

    -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | @homebridge-plugins/homebridge-noip
    2 |

    @homebridge-plugins/homebridge-noip

    3 |

    homebridge-verified

    4 |

    Homebridge No-IP

    5 | 6 | 7 |

    8 |

    The Homebridge No-IP 9 | plugin allows you update your No-IP hostnames from the IP that your 10 | Homebridge instance is on. 11 |

    12 |
    13 |
      14 |
    1. Search for "No-IP" on the plugin screen of Homebridge Config UI X.
    2. 15 |
    3. Click Install.
    4. 16 |
    17 |
      18 |
    1. Login / create an account at https://noip.com/ 19 |
        20 |
      • If you haven't already you can also create your No-IP hostname here as well.
      • 21 |
      22 |
    2. 23 |
    24 |

    25 | 26 |

    27 |
      28 |
    1. Type in your NoIP Username, Password, and Hostname into the Plugin Setting UI
    2. 29 |
    3. Click Save
    4. 30 |
    5. Restart Homebridge
    6. 31 |
    32 |
      33 |
    • IPv4 Update.
    • 34 |
    • IPv6 Update.
    • 35 |
    36 |
    -------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | @homebridge-plugins/homebridge-noip
    2 |

      @homebridge-plugins/homebridge-noip

      Functions

      default
      -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | 3 | export default antfu( 4 | { 5 | ignores: ['dist', 'docs'], 6 | jsx: false, 7 | typescript: true, 8 | formatters: { 9 | markdown: true, 10 | }, 11 | rules: { 12 | 'curly': ['error', 'multi-line'], 13 | 'import/extensions': ['error', 'ignorePackages'], 14 | 'import/order': 0, 15 | 'jsdoc/check-alignment': 'error', 16 | 'jsdoc/check-line-alignment': 'error', 17 | 'perfectionist/sort-exports': 'error', 18 | 'perfectionist/sort-imports': [ 19 | 'error', 20 | { 21 | groups: [ 22 | 'builtin-type', 23 | 'external-type', 24 | 'internal-type', 25 | ['parent-type', 'sibling-type', 'index-type'], 26 | 'builtin', 27 | 'external', 28 | 'internal', 29 | ['parent', 'sibling', 'index'], 30 | 'object', 31 | 'unknown', 32 | ], 33 | order: 'asc', 34 | type: 'natural', 35 | }, 36 | ], 37 | 'perfectionist/sort-named-exports': 'error', 38 | 'perfectionist/sort-named-imports': 'error', 39 | 'sort-imports': 0, 40 | 'style/brace-style': ['error', '1tbs', { allowSingleLine: true }], 41 | 'style/quote-props': ['error', 'consistent-as-needed'], 42 | 'test/no-only-tests': 'error', 43 | 'unicorn/no-useless-spread': 'error', 44 | 'unused-imports/no-unused-vars': ['error', { caughtErrors: 'none' }], 45 | 'no-new': 0, // Disable the no-new rule 46 | 'new-cap': 0, // Disable the new-cap rule 47 | 'no-undef': 0, // Disable the no-undef rule 48 | }, 49 | }, 50 | ) 51 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "src", 4 | "config.schema.json" 5 | ], 6 | "ext": "ts, html, json", 7 | "ignore": [], 8 | "exec": "DEBUG= tsc && homebridge -T -D -P -I -U ~/.homebridge-dev ..", 9 | "signal": "SIGTERM", 10 | "env": { 11 | "NODE_OPTIONS": "--trace-warnings" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@homebridge-plugins/homebridge-noip", 3 | "displayName": "No-IP", 4 | "type": "module", 5 | "version": "4.1.2", 6 | "description": "The No-IP plugin allows you to update your No-IP hostname(s) for your homebridge instance.", 7 | "author": { 8 | "name": "donavanbecker", 9 | "url": "https://github.com/donavanbecker" 10 | }, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "maintainers": [ 15 | "donavanbecker" 16 | ], 17 | "license": "ISC", 18 | "funding": [ 19 | { 20 | "type": "Paypal", 21 | "url": "https://www.paypal.me/donavanbecker" 22 | }, 23 | { 24 | "type": "GitHub", 25 | "url": "https://github.com/sponsors/donavanbecker" 26 | } 27 | ], 28 | "homepage": "https://github.com/homebridge-plugins/homebridge-noip", 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/homebridge-plugins/homebridge-noip.git" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/homebridge-plugins/homebridge-noip/issues" 35 | }, 36 | "keywords": [ 37 | "homebridge-plugin", 38 | "noip", 39 | "No-IP" 40 | ], 41 | "main": "dist/index.js", 42 | "engines": { 43 | "homebridge": "^1.9.0 || ^2.0.0 || ^2.0.0-beta.26 || ^2.0.0-alpha.37", 44 | "node": "^20 || ^22" 45 | }, 46 | "scripts": { 47 | "check": "npm install && npm outdated", 48 | "lint": "eslint src/**/*.ts", 49 | "lint:fix": "eslint src/**/*.ts --fix", 50 | "watch": "npm run build && npm run plugin-ui && npm link && nodemon", 51 | "plugin-ui": "rsync ./src/homebridge-ui/public/index.html ./dist/homebridge-ui/public/", 52 | "build": "npm run clean && tsc && npm run plugin-ui", 53 | "prepublishOnly": "npm run lint && npm run build && npm run plugin-ui && npm run docs && npm run docs:lint && npm run docs:theme", 54 | "postpublish": "npm run clean && npm ci", 55 | "clean": "shx rm -rf ./dist", 56 | "test": "vitest run", 57 | "test:watch": "vitest watch", 58 | "test-coverage": "npm run test -- --coverage", 59 | "docs": "typedoc", 60 | "docs:lint": "typedoc --emit none --treatWarningsAsErrors", 61 | "docs:theme": "typedoc --theme default-modern" 62 | }, 63 | "dependencies": { 64 | "@homebridge/plugin-ui-utils": "^2.0.1", 65 | "rxjs": "^7.8.1", 66 | "undici": "^7.4.0", 67 | "validator": "^13.12.0" 68 | }, 69 | "devDependencies": { 70 | "@antfu/eslint-config": "^4.4.0", 71 | "@types/aes-js": "^3.1.4", 72 | "@types/debug": "^4.1.12", 73 | "@types/fs-extra": "^11.0.4", 74 | "@types/mdast": "^4.0.4", 75 | "@types/node": "^22.13.6", 76 | "@types/semver": "^7.5.8", 77 | "@types/source-map-support": "^0.5.10", 78 | "@typhonjs-typedoc/typedoc-theme-dmt": "^0.3.1", 79 | "@vitest/coverage-v8": "^3.0.7", 80 | "eslint": "^9.21.0", 81 | "eslint-plugin-format": "^1.0.1", 82 | "homebridge": "^1.9.0", 83 | "homebridge-config-ui-x": "4.71.2", 84 | "nodemon": "^3.1.9", 85 | "shx": "^0.3.4", 86 | "ts-node": "^10.9.2", 87 | "typedoc": "^0.27.9", 88 | "typescript": "^5.8.2", 89 | "vitest": "^3.0.7" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/devices/contactsensor.ts: -------------------------------------------------------------------------------- 1 | /* Copyright(C) 2021-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved. 2 | * 3 | * contactsensor.ts: @homebridge-plugins/homebridge-noip. 4 | */ 5 | import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' 6 | 7 | import type { NoIPPlatform } from '../platform.js' 8 | import type { devicesConfig } from '../settings.js' 9 | 10 | import { Buffer } from 'node:buffer' 11 | 12 | import { interval, throwError } from 'rxjs' 13 | import { skipWhile, timeout } from 'rxjs/operators' 14 | import { request } from 'undici' 15 | 16 | import { noip } from '../settings.js' 17 | import { deviceBase } from './device.js' 18 | 19 | /** 20 | * Platform Accessory 21 | * An instance of this class is created for each accessory your platform registers 22 | * Each accessory may expose multiple services of different service types. 23 | */ 24 | export class ContactSensor extends deviceBase { 25 | // Service 26 | private ContactSensor!: { 27 | Service: Service 28 | ContactSensorState: CharacteristicValue 29 | } 30 | 31 | // Others 32 | interval: any 33 | 34 | // Updates 35 | SensorUpdateInProgress!: boolean 36 | 37 | constructor( 38 | readonly platform: NoIPPlatform, 39 | accessory: PlatformAccessory, 40 | device: devicesConfig, 41 | ) { 42 | super(platform, accessory, device) 43 | 44 | // Contact Sensor Service 45 | this.debugLog('Configure Contact Sensor Service') 46 | this.ContactSensor = { 47 | Service: this.accessory.getService(this.hap.Service.ContactSensor) ?? this.accessory.addService(this.hap.Service.ContactSensor), 48 | ContactSensorState: this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED, 49 | } 50 | 51 | // Add Contact Sensor Service's Characteristics 52 | this.ContactSensor.Service 53 | .setCharacteristic(this.hap.Characteristic.Name, device.hostname.split('.')[0]) 54 | 55 | // this is subject we use to track when we need to POST changes to the NoIP API 56 | this.SensorUpdateInProgress = false 57 | 58 | // Retrieve initial values and updateHomekit 59 | this.refreshStatus() 60 | this.updateHomeKitCharacteristics() 61 | 62 | // Start an update interval 63 | interval(this.deviceRefreshRate * 1000) 64 | .pipe(skipWhile(() => this.SensorUpdateInProgress)) 65 | .subscribe(async () => { 66 | await this.refreshStatus() 67 | }) 68 | } 69 | 70 | /** 71 | * Parse the device status from the noip api 72 | */ 73 | async parseStatus(response: string | string[]) { 74 | if (response.includes('nochg')) { 75 | this.ContactSensor.ContactSensorState = this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED 76 | } else { 77 | this.ContactSensor.ContactSensorState = this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED 78 | } 79 | await this.debugLog(`ContactSensorState: ${this.ContactSensor.ContactSensorState}`) 80 | } 81 | 82 | /** 83 | * Asks the NoIP API for the latest device information 84 | */ 85 | async refreshStatus() { 86 | try { 87 | const ip = this.device.ipv4or6 === 'ipv6' ? await this.platform.publicIPv6(this.device) : await this.platform.publicIPv4(this.device) 88 | const ipv4or6 = this.device.ipv4or6 === 'ipv6' ? 'IPv6' : 'IPv4' 89 | const ipProvider = this.device.ipProvider === 'ipify' ? 'ipify.org' : this.device.ipProvider === 'getmyip' ? 'getmyip.dev' : this.device.ipProvider === 'ipapi' ? 'ipapi.co' : this.device.ipProvider === 'myip' ? 'my-ip.io' : 'ipinfo.io' 90 | const { body, statusCode } = await request(noip, { 91 | method: 'GET', 92 | query: { 93 | hostname: this.device.hostname, 94 | myip: ip, 95 | }, 96 | headers: { 97 | 'Authorization': `Basic ${Buffer.from(`${this.device.username}:${this.device.password}`).toString('base64')}`, 98 | 'User-Agent': `Homebridge-NoIP/v${this.device.firmware}`, 99 | }, 100 | }) 101 | const response = await body.text() 102 | await this.debugWarnLog(`statusCode: ${JSON.stringify(statusCode)}`) 103 | await this.debugLog(`${ipProvider} ${ipv4or6} respsonse: ${JSON.stringify(response)}`) 104 | const data = response.trim() 105 | const f = data.match(/good|nochg/g) 106 | if (f) { 107 | await this.debugLog(`data: ${f[0]}`) 108 | this.status(f, data) 109 | } else { 110 | await this.errorLog(`error: ${data}`) 111 | } 112 | await this.parseStatus(response) 113 | await this.updateHomeKitCharacteristics() 114 | } catch (e: any) { 115 | await this.errorLog(`failed to update status, Error: ${JSON.stringify(e.message ?? e)}`) 116 | await this.apiError(e) 117 | } 118 | } 119 | 120 | async status(f: any, data: any): Promise { 121 | switch (f[0]) { 122 | case 'nochg': 123 | await this.debugLog(`IP Address has not updated, IP Address: ${data.split(' ')[1]}`) 124 | break 125 | case 'good': 126 | await this.warnLog(`IP Address has been updated, IP Address: ${data.split(' ')[1]}`) 127 | break 128 | case 'nohost': 129 | await this.errorLog('Hostname supplied does not exist under specified account, client exit and require user to enter new login credentials before performing an additional request.') 130 | await this.timeout() 131 | break 132 | case 'badauth': 133 | await this.errorLog('Invalid username password combination.') 134 | await this.timeout() 135 | break 136 | case 'badagent': 137 | await this.errorLog('Client disabled. Client should exit and not perform any more updates without user intervention. ') 138 | await this.timeout() 139 | break 140 | case '!donator': 141 | await this.errorLog('An update request was sent, ' + 'including a feature that is not available to that particular user such as offline options.') 142 | await this.timeout() 143 | break 144 | case 'abuse': 145 | await this.errorLog('Username is blocked due to abuse. Either for not following our update specifications or disabled due to violation of the No-IP terms of service. Our terms of service can be viewed [here](https://www.noip.com/legal/tos). Client should stop sending updates.') 146 | await this.timeout() 147 | break 148 | case '911': 149 | await this.errorLog('A fatal error on our side such as a database outage. Retry the update no sooner than 30 minutes. ') 150 | await this.timeout() 151 | break 152 | default: 153 | await this.debugLog(data) 154 | } 155 | } 156 | 157 | private async timeout(): Promise { 158 | this.interval.pipe(timeout({ each: 1000, with: () => throwError(() => new Error('nohost')) })).subscribe({ error: this.errorLog }) 159 | } 160 | 161 | /** 162 | * Updates the status for each of the HomeKit Characteristics 163 | */ 164 | async updateHomeKitCharacteristics(): Promise { 165 | // ContactSensorState 166 | await this.updateCharacteristic(this.ContactSensor.Service, this.hap.Characteristic.ContactSensorState, this.ContactSensor.ContactSensorState, 'ContactSensorState') 167 | } 168 | 169 | public async apiError(e: any): Promise { 170 | this.ContactSensor.Service.updateCharacteristic(this.hap.Characteristic.ContactSensorState, e) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/devices/device.ts: -------------------------------------------------------------------------------- 1 | /* Copyright(C) 2021-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved. 2 | * 3 | * device.ts: @homebridge-plugins/homebridge-noip. 4 | */ 5 | import type { API, CharacteristicValue, HAP, Logging, PlatformAccessory, Service } from 'homebridge' 6 | 7 | import type { NoIPPlatform } from '../platform.js' 8 | import type { devicesConfig, NoIPPlatformConfig } from '../settings.js' 9 | 10 | export abstract class deviceBase { 11 | public readonly api: API 12 | public readonly log: Logging 13 | public readonly config!: NoIPPlatformConfig 14 | protected readonly hap: HAP 15 | 16 | // Config 17 | protected deviceLogging!: string 18 | protected deviceRefreshRate!: number 19 | protected deviceUpdateRate!: number 20 | protected devicePushRate!: number 21 | protected deviceFirmwareVersion!: string 22 | 23 | constructor( 24 | protected readonly platform: NoIPPlatform, 25 | protected accessory: PlatformAccessory, 26 | protected device: devicesConfig, 27 | ) { 28 | this.api = this.platform.api 29 | this.log = this.platform.log 30 | this.config = this.platform.config 31 | this.hap = this.api.hap 32 | 33 | this.getDeviceLogSettings(device) 34 | this.getDeviceRateSettings(device) 35 | this.getDeviceConfigSettings(device) 36 | this.getDeviceContext(accessory, device) 37 | 38 | // Set accessory information 39 | accessory 40 | .getService(this.hap.Service.AccessoryInformation)! 41 | .setCharacteristic(this.hap.Characteristic.Manufacturer, 'No-IP') 42 | .setCharacteristic(this.hap.Characteristic.Name, accessory.displayName) 43 | .setCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName) 44 | .setCharacteristic(this.hap.Characteristic.Model, accessory.context.model) 45 | .setCharacteristic(this.hap.Characteristic.SerialNumber, accessory.context.serialNumber) 46 | .setCharacteristic(this.hap.Characteristic.FirmwareRevision, this.deviceFirmwareVersion) 47 | .getCharacteristic(this.hap.Characteristic.FirmwareRevision) 48 | .updateValue(this.deviceFirmwareVersion) 49 | } 50 | 51 | async getDeviceLogSettings(device: devicesConfig): Promise { 52 | this.deviceLogging = this.platform.debugMode ? 'debugMode' : device.logging ?? this.platform.platformLogging ?? 'standard' 53 | const logging = this.platform.debugMode ? 'Debug Mode' : device.logging ? 'Device Config' : this.platform.platformLogging ? 'Platform Config' : 'Default' 54 | await this.debugLog(`Using ${logging} Logging: ${this.deviceLogging}`) 55 | } 56 | 57 | async getDeviceRateSettings(device: devicesConfig): Promise { 58 | // refreshRate 59 | this.deviceRefreshRate = device.refreshRate ?? this.platform.platformRefreshRate ?? 1800 60 | const refreshRate = device.refreshRate ? 'Device Config' : this.platform.platformRefreshRate ? 'Platform Config' : 'Default' 61 | await this.debugLog(`Using ${refreshRate} refreshRate: ${this.deviceRefreshRate}`) 62 | // updateRate 63 | this.deviceUpdateRate = device.updateRate ?? this.platform.platformUpdateRate ?? 5 64 | const updateRate = device.updateRate ? 'Device Config' : this.platform.platformUpdateRate ? 'Platform Config' : 'Default' 65 | await this.debugLog(`Using ${updateRate} updateRate: ${this.deviceUpdateRate}`) 66 | // pushRate 67 | this.devicePushRate = device.pushRate ?? this.platform.platformPushRate ?? 1 68 | const pushRate = device.pushRate ? 'Device Config' : this.platform.platformPushRate ? 'Platform Config' : 'Default' 69 | await this.debugLog(`Using ${pushRate} pushRate: ${this.devicePushRate}`) 70 | } 71 | 72 | async getDeviceConfigSettings(device: devicesConfig): Promise { 73 | const deviceConfig = {} 74 | const properties = [ 75 | 'logging', 76 | 'refreshRate', 77 | 'updateRate', 78 | 'pushRate', 79 | 'external', 80 | 'showRainSensor', 81 | 'showValveSensor', 82 | 'showProgramASwitch', 83 | 'showProgramBSwitch', 84 | 'showProgramCSwitch', 85 | 'showProgramDSwitch', 86 | 'showDelayIrrigationSwitch', 87 | 'showStopIrrigationSwitch', 88 | 'minValueRemainingDuration', 89 | 'maxValueRemainingDuration', 90 | 'syncTime', 91 | 'showRequestResponse', 92 | 'showZoneValve', 93 | 'includeZones', 94 | 'irrigationDelay', 95 | ] 96 | properties.forEach((prop) => { 97 | if (device[prop] !== undefined) { 98 | deviceConfig[prop] = device[prop] 99 | } 100 | }) 101 | if (Object.keys(deviceConfig).length !== 0) { 102 | this.infoLog(`Config: ${JSON.stringify(deviceConfig)}`) 103 | } 104 | } 105 | 106 | async getDeviceContext(accessory: PlatformAccessory, device: devicesConfig): Promise { 107 | const deviceFirmwareVersion = device.firmware ?? this.platform.version ?? '0.0.0' 108 | const version = deviceFirmwareVersion.toString() 109 | this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) 110 | if (version?.includes('.') === false) { 111 | const replace = version?.replace(/^V|-.*$/g, '') 112 | const match = replace?.match(/./g) 113 | const validVersion = match?.join('.') 114 | this.deviceFirmwareVersion = validVersion ?? '0.0.0' 115 | } else { 116 | this.deviceFirmwareVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' 117 | } 118 | accessory 119 | .getService(this.hap.Service.AccessoryInformation)! 120 | .setCharacteristic(this.hap.Characteristic.HardwareRevision, this.deviceFirmwareVersion) 121 | .setCharacteristic(this.hap.Characteristic.SoftwareRevision, this.deviceFirmwareVersion) 122 | .setCharacteristic(this.hap.Characteristic.FirmwareRevision, this.deviceFirmwareVersion) 123 | .getCharacteristic(this.hap.Characteristic.FirmwareRevision) 124 | .updateValue(this.deviceFirmwareVersion) 125 | this.debugSuccessLog(`deviceFirmwareVersion: ${this.deviceFirmwareVersion}`) 126 | } 127 | 128 | /** 129 | * Update the characteristic value and log the change. 130 | * 131 | * @param Service Service 132 | * @param Characteristic Characteristic 133 | * @param CharacteristicValue CharacteristicValue | undefined 134 | * @param CharacteristicName string 135 | * @return: void 136 | * 137 | */ 138 | async updateCharacteristic(Service: Service, Characteristic: any, CharacteristicValue: CharacteristicValue | undefined, CharacteristicName: string): Promise { 139 | if (CharacteristicValue === undefined) { 140 | this.debugLog(`${CharacteristicName}: ${CharacteristicValue}`) 141 | } else { 142 | Service.updateCharacteristic(Characteristic, CharacteristicValue) 143 | this.debugLog(`updateCharacteristic ${CharacteristicName}: ${CharacteristicValue}`) 144 | this.debugWarnLog(`${CharacteristicName} context before: ${this.accessory.context[CharacteristicName]}`) 145 | this.accessory.context[CharacteristicName] = CharacteristicValue 146 | this.debugWarnLog(`${CharacteristicName} context after: ${this.accessory.context[CharacteristicName]}`) 147 | } 148 | } 149 | 150 | /** 151 | * Logging for Device 152 | */ 153 | async infoLog(...log: any[]): Promise { 154 | if (await this.enablingDeviceLogging()) { 155 | this.log.info(`Contact Sensor: ${this.accessory.displayName} `, String(...log)) 156 | } 157 | } 158 | 159 | async successLog(...log: any[]): Promise { 160 | if (await this.enablingDeviceLogging()) { 161 | this.log.success(`Contact Sensor: ${this.accessory.displayName} `, String(...log)) 162 | } 163 | } 164 | 165 | async debugSuccessLog(...log: any[]): Promise { 166 | if (await this.enablingDeviceLogging()) { 167 | if (await this.loggingIsDebug()) { 168 | this.log.success(`[DEBUG] Contact Sensor: ${this.accessory.displayName} `, String(...log)) 169 | } 170 | } 171 | } 172 | 173 | async warnLog(...log: any[]): Promise { 174 | if (await this.enablingDeviceLogging()) { 175 | this.log.warn(`Contact Sensor: ${this.accessory.displayName} `, String(...log)) 176 | } 177 | } 178 | 179 | async debugWarnLog(...log: any[]): Promise { 180 | if (await this.enablingDeviceLogging()) { 181 | if (await this.loggingIsDebug()) { 182 | this.log.warn(`[DEBUG] Contact Sensor: ${this.accessory.displayName} `, String(...log)) 183 | } 184 | } 185 | } 186 | 187 | async errorLog(...log: any[]): Promise { 188 | if (await this.enablingDeviceLogging()) { 189 | this.log.error(`Contact Sensor: ${this.accessory.displayName} `, String(...log)) 190 | } 191 | } 192 | 193 | async debugErrorLog(...log: any[]): Promise { 194 | if (await this.enablingDeviceLogging()) { 195 | if (await this.loggingIsDebug()) { 196 | this.log.error(`[DEBUG] Contact Sensor: ${this.accessory.displayName} `, String(...log)) 197 | } 198 | } 199 | } 200 | 201 | async debugLog(...log: any[]): Promise { 202 | if (await this.enablingDeviceLogging()) { 203 | if (this.deviceLogging === 'debug') { 204 | this.log.info(`[DEBUG] Contact Sensor: ${this.accessory.displayName} `, String(...log)) 205 | } else if (this.deviceLogging === 'debugMode') { 206 | this.log.debug(`Contact Sensor: ${this.accessory.displayName} `, String(...log)) 207 | } 208 | } 209 | } 210 | 211 | async loggingIsDebug(): Promise { 212 | return this.deviceLogging === 'debugMode' || this.deviceLogging === 'debug' 213 | } 214 | 215 | async enablingDeviceLogging(): Promise { 216 | return this.deviceLogging === 'debugMode' || this.deviceLogging === 'debug' || this.deviceLogging === 'standard' 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/homebridge-ui/public/index.html: -------------------------------------------------------------------------------- 1 |

      2 | homebridge-noip logo 6 |

      7 | 19 | 24 | 28 | 59 | 107 | 232 | -------------------------------------------------------------------------------- /src/homebridge-ui/server.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | 3 | /* Copyright(C) 2021-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved. 4 | * 5 | * server.ts: @homebridge-plugins/homebridge-noip. 6 | */ 7 | import { HomebridgePluginUiServer } from '@homebridge/plugin-ui-utils' 8 | 9 | class PluginUiServer extends HomebridgePluginUiServer { 10 | constructor() { 11 | super() 12 | /* 13 | A native method getCachedAccessories() was introduced in config-ui-x v4.37.0 14 | The following is for users who have a lower version of config-ui-x 15 | */ 16 | this.onRequest('getCachedAccessories', () => { 17 | try { 18 | const plugin = '@homebridge-plugins/homebridge-noip' 19 | const devicesToReturn = [] 20 | 21 | // The path and file of the cached accessories 22 | const accFile = `${this.homebridgeStoragePath}/accessories/cachedAccessories` 23 | 24 | // Check the file exists 25 | if (fs.existsSync(accFile)) { 26 | // read the cached accessories file 27 | const cachedAccessories: any[] = JSON.parse(fs.readFileSync(accFile, 'utf8')) 28 | 29 | cachedAccessories.forEach((accessory: any) => { 30 | // Check the accessory is from this plugin 31 | if (accessory.plugin === plugin) { 32 | // Add the cached accessory to the array 33 | devicesToReturn.push(accessory.accessory as never) 34 | } 35 | }) 36 | } 37 | // Return the array 38 | return devicesToReturn 39 | } catch { 40 | // Just return an empty accessory list in case of any errors 41 | return [] 42 | } 43 | }) 44 | this.ready() 45 | } 46 | } 47 | 48 | function startPluginUiServer(): PluginUiServer { 49 | return new PluginUiServer() 50 | } 51 | 52 | startPluginUiServer() 53 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import type { API } from 'homebridge' 2 | 3 | import { describe, expect, it, vi } from 'vitest' 4 | 5 | import registerPlatform from './index.js' 6 | import { NoIPPlatform } from './platform.js' 7 | import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js' 8 | 9 | describe('registerPlatform', () => { 10 | it('should register the platform with homebridge', () => { 11 | const api = { 12 | registerPlatform: vi.fn(), 13 | } as unknown as API 14 | 15 | registerPlatform(api) 16 | 17 | expect(api.registerPlatform).toHaveBeenCalledWith(PLUGIN_NAME, PLATFORM_NAME, NoIPPlatform) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* Copyright(C) 2021-2023, donavanbecker (https://github.com/donavanbecker). All rights reserved. 2 | * 3 | * index.ts: @homebridge-plugins/homebridge-noip. 4 | */ 5 | import type { API } from 'homebridge' 6 | 7 | import { NoIPPlatform } from './platform.js' 8 | import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js' 9 | 10 | // Register our platform with homebridge. 11 | export default (api: API): void => { 12 | api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, NoIPPlatform) 13 | } 14 | -------------------------------------------------------------------------------- /src/platform.ts: -------------------------------------------------------------------------------- 1 | /* Copyright(C) 2021-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved. 2 | * 3 | * platform.ts: @homebridge-plugins/homebridge-noip. 4 | */ 5 | import type { API, DynamicPlatformPlugin, HAP, Logging, PlatformAccessory } from 'homebridge' 6 | 7 | import type { devicesConfig, NoIPPlatformConfig, options } from './settings.js' 8 | 9 | import { readFileSync } from 'node:fs' 10 | import { argv } from 'node:process' 11 | 12 | import { request } from 'undici' 13 | import validator from 'validator' 14 | 15 | import { ContactSensor } from './devices/contactsensor.js' 16 | import { getmyip_v4, getmyip_v6, ipapi_v4, ipapi_v6, ipify_v4, ipify_v6, ipinfo_v4, ipinfo_v6, myip_v4, myip_v6, PLATFORM_NAME, PLUGIN_NAME } from './settings.js' 17 | 18 | /** 19 | * HomebridgePlatform 20 | * This class is the main constructor for your plugin, this is where you should 21 | * parse the user config and discover/register accessories with Homebridge. 22 | */ 23 | export class NoIPPlatform implements DynamicPlatformPlugin { 24 | public accessories: PlatformAccessory[] 25 | public readonly api: API 26 | public readonly log: Logging 27 | protected readonly hap: HAP 28 | public config!: NoIPPlatformConfig 29 | 30 | platformConfig!: NoIPPlatformConfig 31 | platformLogging!: options['logging'] 32 | platformRefreshRate!: options['refreshRate'] 33 | platformPushRate!: options['pushRate'] 34 | platformUpdateRate!: options['updateRate'] 35 | debugMode!: boolean 36 | version!: string 37 | 38 | constructor( 39 | log: Logging, 40 | config: NoIPPlatformConfig, 41 | api: API, 42 | ) { 43 | this.accessories = [] 44 | this.api = api 45 | this.hap = this.api.hap 46 | this.log = log 47 | // only load if configured 48 | if (!config) { 49 | return 50 | } 51 | 52 | // Plugin options into our config variables. 53 | this.config = { 54 | platform: 'NoIP', 55 | name: config.name, 56 | devices: config.devices as devicesConfig[], 57 | options: config.options as options, 58 | } 59 | 60 | // Plugin Configuration 61 | this.getPlatformLogSettings() 62 | this.getPlatformRateSettings() 63 | this.getPlatformConfigSettings() 64 | this.getVersion() 65 | 66 | // Finish initializing the platform 67 | this.debugLog(`Finished initializing platform: ${config.name}`); 68 | 69 | // verify the config 70 | (async () => { 71 | try { 72 | await this.verifyConfig() 73 | await this.debugLog('Config OK') 74 | } catch (e: any) { 75 | await this.errorLog(`Verify Config, Error Message: ${e.message ?? e}, Submit Bugs Here: https://bit.ly/homebridge-noip-bug-report`) 76 | } 77 | })() 78 | 79 | // When this event is fired it means Homebridge has restored all cached accessories from disk. 80 | // Dynamic Platform plugins should only register new accessories after this event was fired, 81 | // in order to ensure they weren't added to homebridge already. This event can also be used 82 | // to start discovery of new accessories. 83 | this.api.on('didFinishLaunching', async () => { 84 | log.debug('Executed didFinishLaunching callback') 85 | // run the method to discover / register your devices as accessories 86 | try { 87 | await this.discoverDevices() 88 | } catch (e: any) { 89 | await this.errorLog(`Failed to Discover Devices ${JSON.stringify(e.message ?? e)}`) 90 | } 91 | }) 92 | } 93 | 94 | /** 95 | * This function is invoked when homebridge restores cached accessories from disk at startup. 96 | * It should be used to setup event handlers for characteristics and update respective values. 97 | */ 98 | async configureAccessory(accessory: PlatformAccessory) { 99 | await this.infoLog(`Loading accessory from cache: ${accessory.displayName}`) 100 | 101 | // add the restored accessory to the accessories cache so we can track if it has already been registered 102 | this.accessories.push(accessory) 103 | } 104 | 105 | /** 106 | * Verify the config passed to the plugin is valid 107 | */ 108 | async verifyConfig() { 109 | /** 110 | * Hidden Device Discovery Option 111 | * This will disable adding any device and will just output info. 112 | */ 113 | this.config.logging = this.config.logging || 'standard' 114 | 115 | // Old Config 116 | if (this.config.hostname || this.config.username || this.config.password) { 117 | const oldConfig = { 118 | hostname: this.config.hostname, 119 | username: this.config.username, 120 | password: this.config.password, 121 | } 122 | await this.errorLog(`You still have old config that will be ignored, Old Config: ${JSON.stringify(oldConfig)}`) 123 | } 124 | // Device Config 125 | if (this.config.devices) { 126 | for (const deviceConfig of this.config.devices) { 127 | if (!deviceConfig.hostname) { 128 | await this.errorLog('Missing Domain, Need Domain that will be updated.') 129 | } 130 | if (!deviceConfig.username) { 131 | await this.errorLog('Missing Your No-IP Username(E-mail)') 132 | } else if (!this.validateEmail(deviceConfig.username)) { 133 | await this.errorLog('Provide a valid Email') 134 | } 135 | if (!deviceConfig.password) { 136 | await this.errorLog('Missing your No-IP Password') 137 | } 138 | } 139 | } else { 140 | await this.errorLog('verifyConfig, No Device Config') 141 | } 142 | } 143 | 144 | /** 145 | * This method is used to discover the your location and devices. 146 | * Accessories are registered by either their DeviceClass, DeviceModel, or DeviceID 147 | */ 148 | async discoverDevices() { 149 | try { 150 | for (const device of this.config.devices!) { 151 | await this.infoLog(`Discovered ${device.hostname}`) 152 | this.createContactSensor(device) 153 | } 154 | } catch { 155 | await this.errorLog('discoverDevices, No Device Config') 156 | } 157 | } 158 | 159 | public async createContactSensor(device: any) { 160 | const uuid = this.api.hap.uuid.generate(device.hostname) 161 | 162 | // see if an accessory with the same uuid has already been registered and restored from 163 | // the cached devices we stored in the `configureAccessory` method above 164 | const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) 165 | 166 | if (existingAccessory) { 167 | // the accessory already exists 168 | if (!device.delete) { 169 | // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: 170 | const hostname = device.hostname.split('.')[0] 171 | existingAccessory.context = existingAccessory.context || {} // Ensure context is initialized 172 | existingAccessory.context.device = device 173 | existingAccessory.displayName = device.configDeviceName 174 | ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.userDefinedDeviceName) 175 | : await this.validateAndCleanDisplayName(hostname, 'hostname', hostname) 176 | 177 | // Ensure displayName is not empty 178 | if (!existingAccessory.displayName) { 179 | existingAccessory.displayName = 'Unnamed Accessory' 180 | } 181 | 182 | existingAccessory.context.serialNumber = device.ipv4or6 === 'ipv6' ? await this.publicIPv6(device) : await this.publicIPv4(device) 183 | existingAccessory.context.model = 'DUC' 184 | existingAccessory.context.version = await this.getVersion() 185 | this.api.updatePlatformAccessories([existingAccessory]) 186 | // Restore accessory 187 | await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`) 188 | // create the accessory handler for the restored accessory 189 | // this is imported from `platformAccessory.ts` 190 | new ContactSensor(this, existingAccessory, device) 191 | await this.debugLog(`uuid: ${device.hostname}`) 192 | } else { 193 | this.unregisterPlatformAccessories(existingAccessory) 194 | } 195 | } else if (!device.delete) { 196 | // create a new accessory 197 | const accessory = new this.api.platformAccessory(device.hostname, uuid) 198 | 199 | // store a copy of the device object in the `accessory.context` 200 | // the `context` property can be used to store any data about the accessory you may need 201 | const hostname = device.hostname.split('.')[0] 202 | accessory.context = accessory.context || {} // Ensure context is initialized 203 | accessory.context.device = device 204 | accessory.displayName = device.configDeviceName 205 | ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.userDefinedDeviceName) 206 | : await this.validateAndCleanDisplayName(hostname, 'hostname', hostname) 207 | 208 | // Ensure displayName is not empty 209 | if (!accessory.displayName) { 210 | accessory.displayName = 'Unnamed Accessory' 211 | } 212 | 213 | accessory.context.serialNumber = device.ipv4or6 === 'ipv6' ? await this.publicIPv6(device) : await this.publicIPv4(device) 214 | accessory.context.model = 'DUC' 215 | accessory.context.version = await this.getVersion() 216 | // the accessory does not yet exist, so we need to create it 217 | await this.infoLog(`Adding new accessory: ${device.hostname}`) 218 | // create the accessory handler for the newly create accessory 219 | // this is imported from `platformAccessory.ts` 220 | new ContactSensor(this, accessory, device) 221 | await this.debugLog(`${device.hostname} uuid: ${device.hostname}`) 222 | 223 | // link the accessory to your platform 224 | this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]) 225 | this.accessories.push(accessory) 226 | } else { 227 | this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.hostname)}`) 228 | } 229 | } 230 | 231 | public async unregisterPlatformAccessories(existingAccessory: PlatformAccessory) { 232 | // remove platform accessories when no longer present 233 | this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]) 234 | await this.warnLog(`Removing existing accessory from cache: ${existingAccessory.displayName}`) 235 | } 236 | 237 | async publicIPv4(device: devicesConfig) { 238 | try { 239 | const { body, statusCode } = await request(device.ipProvider === 'ipify' ? ipify_v4 : device.ipProvider === 'getmyip' ? getmyip_v4 : device.ipProvider === 'ipapi' ? ipapi_v4 : device.ipProvider === 'myip' ? myip_v4 : ipinfo_v4, { 240 | method: 'GET', 241 | }) 242 | const pubIp: any = await body.json() 243 | this.debugSuccessLog(`IPv4 Address: ${JSON.stringify(pubIp.ip)}`) 244 | this.debugSuccessLog(`Status Code: ${JSON.stringify(statusCode)}`) 245 | const IPv4 = pubIp.ip 246 | return IPv4 247 | } catch { 248 | await this.errorLog('Not Able To Retreive IPv4 Address') 249 | } 250 | } 251 | 252 | async publicIPv6(device: devicesConfig) { 253 | try { 254 | const { body, statusCode } = await request(device.ipProvider === 'ipify' ? ipify_v6 : device.ipProvider === 'getmyip' ? getmyip_v6 : device.ipProvider === 'ipapi' ? ipapi_v6 : device.ipProvider === 'myip' ? myip_v6 : ipinfo_v6, { 255 | method: 'GET', 256 | }) 257 | const pubIp: any = await body.json() 258 | this.debugSuccessLog(`IPv6 Address: ${JSON.stringify(pubIp.ip)}`) 259 | this.debugSuccessLog(`Status Code: ${JSON.stringify(statusCode)}`) 260 | const IPv6 = pubIp.ip 261 | return IPv6 262 | } catch { 263 | await this.errorLog('Not Able To Retreive IPv6 Address') 264 | } 265 | } 266 | 267 | validateEmail(email: string | undefined) { 268 | if (!email) { 269 | return false 270 | } else { 271 | return validator.isEmail(email) 272 | } 273 | } 274 | 275 | async getPlatformLogSettings() { 276 | this.debugMode = argv.includes('-D') ?? argv.includes('--debug') 277 | this.platformLogging = (this.config.options?.logging === 'debug' || this.config.options?.logging === 'standard' 278 | || this.config.options?.logging === 'none') 279 | ? this.config.options.logging 280 | : this.debugMode ? 'debugMode' : 'standard' 281 | const logging = this.config.options?.logging ? 'Platform Config' : this.debugMode ? 'debugMode' : 'Default' 282 | await this.debugLog(`Using ${logging} Logging: ${this.platformLogging}`) 283 | } 284 | 285 | async getPlatformRateSettings() { 286 | // RefreshRate 287 | this.platformRefreshRate = this.config.options?.refreshRate ? this.config.options.refreshRate : undefined 288 | const refreshRate = this.config.options?.refreshRate ? 'Using Platform Config refreshRate' : 'Platform Config refreshRate Not Set' 289 | await this.debugLog(`${refreshRate}: ${this.platformRefreshRate}`) 290 | // UpdateRate 291 | this.platformUpdateRate = this.config.options?.updateRate ? this.config.options.updateRate : undefined 292 | const updateRate = this.config.options?.updateRate ? 'Using Platform Config updateRate' : 'Platform Config updateRate Not Set' 293 | await this.debugLog(`${updateRate}: ${this.platformUpdateRate}`) 294 | // PushRate 295 | this.platformPushRate = this.config.options?.pushRate ? this.config.options.pushRate : undefined 296 | const pushRate = this.config.options?.pushRate ? 'Using Platform Config pushRate' : 'Platform Config pushRate Not Set' 297 | await this.debugLog(`${pushRate}: ${this.platformPushRate}`) 298 | } 299 | 300 | async getPlatformConfigSettings() { 301 | if (this.config.options) { 302 | const platformConfig: NoIPPlatformConfig = { 303 | platform: 'NoIP', 304 | } 305 | platformConfig.logging = this.config.options.logging ? this.config.options.logging : undefined 306 | platformConfig.refreshRate = this.config.options.refreshRate ? this.config.options.refreshRate : undefined 307 | platformConfig.updateRate = this.config.options.updateRate ? this.config.options.updateRate : undefined 308 | platformConfig.pushRate = this.config.options.pushRate ? this.config.options.pushRate : undefined 309 | if (Object.entries(platformConfig).length !== 0) { 310 | await this.debugLog(`Platform Config: ${JSON.stringify(platformConfig)}`) 311 | } 312 | this.platformConfig = platformConfig 313 | } 314 | } 315 | 316 | /** 317 | * Asynchronously retrieves the version of the plugin from the package.json file. 318 | * 319 | * This method reads the package.json file located in the parent directory, 320 | * parses its content to extract the version, and logs the version using the debug logger. 321 | * The extracted version is then assigned to the `version` property of the class. 322 | * 323 | * @returns {Promise} A promise that resolves when the version has been retrieved and logged. 324 | */ 325 | async getVersion(): Promise { 326 | const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8')) 327 | this.debugLog(`Plugin Version: ${version}`) 328 | this.version = version 329 | } 330 | 331 | /** 332 | * Validate and clean a string value for a Name Characteristic. 333 | * @param displayName - The display name of the accessory. 334 | * @param name - The name of the characteristic. 335 | * @param value - The value to be validated and cleaned. 336 | * @returns The cleaned string value. 337 | */ 338 | async validateAndCleanDisplayName(displayName: string, name: string, value: string): Promise { 339 | if (this.config.options?.allowInvalidCharacters) { 340 | return value 341 | } else { 342 | const validPattern = /^[\p{L}\p{N}][\p{L}\p{N} ']*[\p{L}\p{N}]$/u 343 | const invalidCharsPattern = /[^\p{L}\p{N} ']/gu 344 | const invalidStartEndPattern = /^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu 345 | 346 | if (typeof value === 'string' && !validPattern.test(value)) { 347 | this.warnLog(`WARNING: The accessory '${displayName}' has an invalid '${name}' characteristic ('${value}'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.`) 348 | 349 | // Remove invalid characters 350 | if (invalidCharsPattern.test(value)) { 351 | const before = value 352 | this.warnLog(`Removing invalid characters from '${name}' characteristic, if you feel this is incorrect, please enable \'allowInvalidCharacter\' in the config to allow all characters`) 353 | value = value.replace(invalidCharsPattern, '') 354 | this.warnLog(`${name} Before: '${before}' After: '${value}'`) 355 | } 356 | 357 | // Ensure it starts and ends with an alphanumeric character 358 | if (invalidStartEndPattern.test(value)) { 359 | const before = value 360 | this.warnLog(`Removing invalid starting or ending characters from '${name}' characteristic, if you feel this is incorrect, please enable \'allowInvalidCharacter\' in the config to allow all characters`) 361 | value = value.replace(invalidStartEndPattern, '') 362 | this.warnLog(`${name} Before: '${before}' After: '${value}'`) 363 | } 364 | } 365 | 366 | return value 367 | } 368 | } 369 | 370 | /** 371 | * If device level logging is turned on, log to log.warn 372 | * Otherwise send debug logs to log.debug 373 | */ 374 | async infoLog(...log: any[]): Promise { 375 | if (await this.enablingPlatformLogging()) { 376 | this.log.info(String(...log)) 377 | } 378 | } 379 | 380 | async successLog(...log: any[]): Promise { 381 | if (await this.enablingPlatformLogging()) { 382 | this.log.success(String(...log)) 383 | } 384 | } 385 | 386 | async debugSuccessLog(...log: any[]): Promise { 387 | if (await this.enablingPlatformLogging()) { 388 | if (await this.loggingIsDebug()) { 389 | this.log.success('[DEBUG]', String(...log)) 390 | } 391 | } 392 | } 393 | 394 | async warnLog(...log: any[]): Promise { 395 | if (await this.enablingPlatformLogging()) { 396 | this.log.warn(String(...log)) 397 | } 398 | } 399 | 400 | async debugWarnLog(...log: any[]): Promise { 401 | if (await this.enablingPlatformLogging()) { 402 | if (await this.loggingIsDebug()) { 403 | this.log.warn('[DEBUG]', String(...log)) 404 | } 405 | } 406 | } 407 | 408 | async errorLog(...log: any[]): Promise { 409 | if (await this.enablingPlatformLogging()) { 410 | this.log.error(String(...log)) 411 | } 412 | } 413 | 414 | async debugErrorLog(...log: any[]): Promise { 415 | if (await this.enablingPlatformLogging()) { 416 | if (await this.loggingIsDebug()) { 417 | this.log.error('[DEBUG]', String(...log)) 418 | } 419 | } 420 | } 421 | 422 | async debugLog(...log: any[]): Promise { 423 | if (await this.enablingPlatformLogging()) { 424 | if (this.platformLogging === 'debugMode') { 425 | this.log.debug(String(...log)) 426 | } else if (this.platformLogging === 'debug') { 427 | this.log.info('[DEBUG]', String(...log)) 428 | } 429 | } 430 | } 431 | 432 | async loggingIsDebug(): Promise { 433 | return this.platformLogging === 'debugMode' || this.platformLogging === 'debug' 434 | } 435 | 436 | async enablingPlatformLogging(): Promise { 437 | return this.platformLogging === 'debugMode' || this.platformLogging === 'debug' || this.platformLogging === 'standard' 438 | } 439 | } 440 | -------------------------------------------------------------------------------- /src/settings.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { getmyip_v4, getmyip_v6, ipapi_v4, ipapi_v6, ipify_v4, ipify_v6, ipinfo_v4, ipinfo_v6, myip_v4, myip_v6, noip, PLATFORM_NAME, PLUGIN_NAME } from './settings.js' 4 | 5 | describe('settings', () => { 6 | it('should have correct PLATFORM_NAME', () => { 7 | expect(PLATFORM_NAME).toBe('NoIP') 8 | }) 9 | 10 | it('should have correct PLUGIN_NAME', () => { 11 | expect(PLUGIN_NAME).toBe('@homebridge-plugins/homebridge-noip') 12 | }) 13 | 14 | it('should have correct API URLs', () => { 15 | expect(ipinfo_v4).toBe('https://ipinfo.io/json') 16 | expect(getmyip_v4).toBe('https://ipv4.getmyip.dev') 17 | expect(ipify_v4).toBe('https://api.ipify.org?format=json') 18 | expect(ipapi_v4).toBe('https://ipapi.co/json') 19 | expect(myip_v4).toBe('https://api4.my-ip.io/v2/ip.json') 20 | expect(ipinfo_v6).toBe('https://v6.ipinfo.io/json') 21 | expect(getmyip_v6).toBe('https://ipv6.getmyip.dev') 22 | expect(ipify_v6).toBe('https://api64.ipify.org?format=json') 23 | expect(ipapi_v6).toBe('https://ip6api.co/json') 24 | expect(myip_v6).toBe('https://api6.my-ip.io/v2/ip.txt') 25 | expect(noip).toBe('https://dynupdate.no-ip.com/nic/update') 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | /* Copyright(C) 2021-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved. 2 | * 3 | * settings.ts: @homebridge-plugins/homebridge-noip. 4 | */ 5 | import type { PlatformConfig } from 'homebridge' 6 | 7 | /** 8 | * This is the name of the platform that users will use to register the plugin in the Homebridge config.json 9 | */ 10 | export const PLATFORM_NAME = 'NoIP' 11 | 12 | /** 13 | * This must match the name of your plugin as defined the package.json 14 | */ 15 | export const PLUGIN_NAME = '@homebridge-plugins/homebridge-noip' 16 | 17 | // API URLs 18 | export const ipinfo_v4 = 'https://ipinfo.io/json' 19 | export const getmyip_v4 = 'https://ipv4.getmyip.dev' 20 | export const ipify_v4 = 'https://api.ipify.org?format=json' 21 | export const ipapi_v4 = 'https://ipapi.co/json' 22 | export const myip_v4 = 'https://api4.my-ip.io/v2/ip.json' 23 | export const ipinfo_v6 = 'https://v6.ipinfo.io/json' 24 | export const getmyip_v6 = 'https://ipv6.getmyip.dev' 25 | export const ipify_v6 = 'https://api64.ipify.org?format=json' 26 | export const ipapi_v6 = 'https://ip6api.co/json' 27 | export const myip_v6 = 'https://api6.my-ip.io/v2/ip.txt' 28 | export const noip = 'https://dynupdate.no-ip.com/nic/update' 29 | 30 | // Config 31 | export interface NoIPPlatformConfig extends PlatformConfig { 32 | name?: string 33 | devices?: devicesConfig[] 34 | options?: options 35 | } 36 | 37 | export interface devicesConfig { 38 | configDeviceName?: string 39 | hostname: string 40 | username?: string 41 | password?: string 42 | ipv4or6?: string 43 | ipProvider?: string 44 | firmware: string 45 | refreshRate?: number 46 | updateRate?: number 47 | pushRate?: number 48 | logging?: string 49 | delete?: boolean 50 | } 51 | 52 | export interface options { 53 | refreshRate?: number 54 | updateRate?: number 55 | pushRate?: number 56 | logging?: string 57 | allowInvalidCharacters?: boolean 58 | } 59 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": [ 5 | "DOM", 6 | "ES2022" 7 | ], 8 | "rootDir": "src", 9 | "module": "ES2022", 10 | "moduleResolution": "bundler", 11 | "strict": true, 12 | "noImplicitAny": false, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "outDir": "dist", 16 | "sourceMap": true, 17 | "allowSyntheticDefaultImports": true, 18 | "esModuleInterop": true, 19 | "forceConsistentCasingInFileNames": true 20 | }, 21 | "include": [ 22 | "src" 23 | ], 24 | "exclude": [ 25 | "**/*.spec.ts" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": [ 3 | "@typhonjs-typedoc/typedoc-theme-dmt" 4 | ], 5 | "theme": "default-modern", 6 | "out": "docs", 7 | "exclude": ["src/**/*.spec.ts"], 8 | "entryPoints": [ 9 | "src/index.ts" 10 | ], 11 | "excludePrivate": true, 12 | "excludeProtected": true, 13 | "excludeExternals": true, 14 | "hideGenerator": true, 15 | "includeVersion": false, 16 | "validation": { 17 | "invalidLink": true, 18 | "notExported": false 19 | }, 20 | "inlineTags": ["@link", "@see"] 21 | } 22 | --------------------------------------------------------------------------------