├── .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 |
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 = `M M N E P V F C I C P M F P C P T T A A A T R `;
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 | M M N E P V F C I C P M F P C P T T A A A T R
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 | @homebridge-plugins/homebridge-noip
2 |
3 |
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 |
Installation
14 | Search for "No-IP" on the plugin screen of Homebridge Config UI X .
15 | Click Install .
16 |
17 |
Configuration
18 | 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 |
23 |
24 |
25 |
26 |
27 |
28 | Type in your NoIP Username, Password, and Hostname into the Plugin Setting UI
29 | Click Save
30 | Restart Homebridge
31 |
32 |
Supported No-IP Features
33 | IPv4 Update.
34 | IPv6 Update.
35 |
36 |
--------------------------------------------------------------------------------
/docs/modules.html:
--------------------------------------------------------------------------------
1 | @homebridge-plugins/homebridge-noip
2 |
--------------------------------------------------------------------------------
/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 |
6 |
7 |
8 |
Thank you for installing homebridge-noip
9 |
Before continuing:
10 |
11 | Login / create an account at https://noip.com .
12 | Click My Account .
13 | Go to Quick Add, Create your own NoIP hostname.
14 |
15 |
16 | Continue →
17 |
18 |
19 |
24 |
25 | Plugin is currently disabled
26 | Enable
27 |
28 |
29 |
30 |
35 |
36 |
37 |
38 | Device Name
39 |
40 |
41 |
42 |
43 |
44 | Serial Number
45 |
46 |
47 |
48 | Model
49 |
50 |
51 |
52 | Firmware Version
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
Thank you for using homebridge-noip
61 |
The links below will take you to our GitHub wiki
62 |
Setup
63 |
80 |
Features
81 |
89 |
Help/About
90 |
101 |
Disclaimer
102 |
103 | I am in no way affiliated with NoIP and this plugin is a personal project that I maintain in my free time.
104 | Use this plugin entirely at your own risk - please see licence for more information.
105 |
106 |
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 |
--------------------------------------------------------------------------------