├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── auto-merge.yml ├── dependabot.yml ├── stale.yml └── workflows │ ├── codeql.yml │ ├── dependabot-automerge.yml │ └── test-and-release.yml ├── .gitignore ├── .mocharc.json ├── .releaseconfig.json ├── LICENSE ├── README.md ├── admin ├── i18n │ ├── de.json │ ├── en.json │ ├── es.json │ ├── fr.json │ ├── it.json │ ├── nl.json │ ├── pl.json │ ├── pt.json │ ├── ru.json │ ├── uk.json │ └── zh-cn.json ├── jsonConfig.json ├── rest-api.png └── rest-api.svg ├── dist ├── lib │ ├── api │ │ ├── controllers │ │ │ ├── common.js │ │ │ ├── common.js.map │ │ │ ├── enum.js │ │ │ ├── enum.js.map │ │ │ ├── file.js │ │ │ ├── file.js.map │ │ │ ├── history.js │ │ │ ├── history.js.map │ │ │ ├── object.js │ │ │ ├── object.js.map │ │ │ ├── sendTo.js │ │ │ ├── sendTo.js.map │ │ │ ├── state.js │ │ │ └── state.js.map │ │ └── swagger │ │ │ └── swagger.yaml │ ├── common.js │ ├── common.js.map │ ├── config │ │ └── default.yaml │ ├── rest-api.js │ └── rest-api.js.map ├── main.js └── main.js.map ├── eslint.config.mjs ├── examples ├── demoBrowserClient.html ├── demoNodeClient.js └── longPolling.js ├── img ├── favicon.ico └── screen.png ├── io-package.json ├── package.json ├── prettier.config.mjs ├── src ├── lib │ ├── api │ │ ├── controllers │ │ │ ├── common.ts │ │ │ ├── enum.ts │ │ │ ├── file.ts │ │ │ ├── history.ts │ │ │ ├── object.ts │ │ │ ├── sendTo.ts │ │ │ └── state.ts │ │ └── swagger │ │ │ └── swagger.yaml │ ├── common.ts │ ├── config │ │ └── default.yaml │ ├── rest-api.ts │ └── types.d.ts └── main.ts ├── tasks.js ├── test ├── mocha.setup.js ├── testApi.js ├── testApiAsLimitedUser.js ├── testApiAsUser.js ├── testPackage.js └── testSsl.js ├── tsconfig.build.json └── tsconfig.json /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something is not working as it should 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '...' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots & Logfiles** 23 | If applicable, add screenshots and logfiles to help explain your problem. 24 | 25 | **Versions:** 26 | - Adapter version: 27 | - JS-Controller version: 28 | - Node version: 29 | - Operating system: 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/auto-merge.yml: -------------------------------------------------------------------------------- 1 | # Configure here which dependency updates should be merged automatically. 2 | # The recommended configuration is the following: 3 | - match: 4 | # Only merge patches for production dependencies 5 | dependency_type: production 6 | update_type: 'semver:patch' 7 | - match: 8 | # Except for security fixes, here we allow minor patches 9 | dependency_type: production 10 | update_type: 'security:minor' 11 | - match: 12 | # and development dependencies can have a minor update, too 13 | dependency_type: development 14 | update_type: 'semver:minor' 15 | # The syntax is based on the legacy dependabot v1 automerged_updates syntax, see: 16 | # https://dependabot.com/docs/config-file/#automerged_updates 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: github-actions 5 | directory: '/' 6 | schedule: 7 | interval: monthly 8 | time: '04:00' 9 | timezone: Europe/Berlin 10 | - package-ecosystem: npm 11 | directory: '/' 12 | schedule: 13 | interval: monthly 14 | time: '04:00' 15 | timezone: Europe/Berlin 16 | open-pull-requests-limit: 5 17 | versioning-strategy: increase 18 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 90 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 7 9 | 10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 11 | onlyLabels: [] 12 | 13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 14 | exemptLabels: 15 | - enhancement 16 | - security 17 | - bug 18 | 19 | # Set to true to ignore issues in a project (defaults to false) 20 | exemptProjects: true 21 | 22 | # Set to true to ignore issues in a milestone (defaults to false) 23 | exemptMilestones: true 24 | 25 | # Set to true to ignore issues with an assignee (defaults to false) 26 | exemptAssignees: false 27 | 28 | # Label to use when marking as stale 29 | staleLabel: wontfix 30 | 31 | # Comment to post when marking as stale. Set to `false` to disable 32 | markComment: > 33 | This issue has been automatically marked as stale because it has not had 34 | recent activity. It will be closed if no further activity occurs within the next 7 days. 35 | Please check if the issue is still relevant in the most current version of the adapter 36 | and tell us. Also check that all relevant details, logs and reproduction steps 37 | are included and update them if needed. 38 | Thank you for your contributions. 39 | 40 | Dieses Problem wurde automatisch als veraltet markiert, da es in letzter Zeit keine Aktivitäten gab. 41 | Es wird geschlossen, wenn nicht innerhalb der nächsten 7 Tage weitere Aktivitäten stattfinden. 42 | Bitte überprüft, ob das Problem auch in der aktuellsten Version des Adapters noch relevant ist, 43 | und teilt uns dies mit. Überprüft auch, ob alle relevanten Details, Logs und Reproduktionsschritte 44 | enthalten sind bzw. aktualisiert diese. 45 | Vielen Dank für Eure Unterstützung. 46 | 47 | # Comment to post when removing the stale label. 48 | # unmarkComment: > 49 | # Your comment here. 50 | 51 | # Comment to post when closing a stale Issue or Pull Request. 52 | closeComment: > 53 | This issue has been automatically closed because of inactivity. Please open a new 54 | issue if still relevant and make sure to include all relevant details, logs and 55 | reproduction steps. 56 | Thank you for your contributions. 57 | 58 | Dieses Problem wurde aufgrund von Inaktivität automatisch geschlossen. Bitte öffnet ein 59 | neues Issue, falls dies noch relevant ist und stellt sicher das alle relevanten Details, 60 | Logs und Reproduktionsschritte enthalten sind. 61 | Vielen Dank für Eure Unterstützung. 62 | 63 | # Limit the number of actions per hour, from 1-30. Default is 30 64 | limitPerRun: 30 65 | 66 | # Limit to only `issues` or `pulls` 67 | only: issues 68 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 69 | # pulls: 70 | # daysUntilStale: 30 71 | # markComment: > 72 | # This pull request has been automatically marked as stale because it has not had 73 | # recent activity. It will be closed if no further activity occurs. Thank you 74 | # for your contributions. 75 | 76 | # issues: 77 | # exemptLabels: 78 | # - confirmed 79 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: 'CodeQL' 2 | 3 | on: 4 | push: 5 | branches: ['master'] 6 | pull_request: 7 | branches: ['master'] 8 | schedule: 9 | - cron: '45 10 * * 4' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [javascript] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v3 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v3 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v3 40 | with: 41 | category: '/language:${{ matrix.language }}' 42 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-automerge.yml: -------------------------------------------------------------------------------- 1 | # Automatically merge Dependabot PRs when version comparison is within the range 2 | # that is configured in .github/auto-merge.yml 3 | 4 | name: Auto-Merge Dependabot PRs 5 | 6 | on: 7 | pull_request_target: 8 | 9 | jobs: 10 | auto-merge: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Check if PR should be auto-merged 17 | uses: ahmadnassri/action-dependabot-auto-merge@v2 18 | with: 19 | # This must be a personal access token with push access 20 | github-token: ${{ secrets.AUTO_MERGE_TOKEN }} 21 | # By default, squash and merge, so Github chooses nice commit messages 22 | command: squash and merge 23 | -------------------------------------------------------------------------------- /.github/workflows/test-and-release.yml: -------------------------------------------------------------------------------- 1 | # This is a composition of lint and test scripts 2 | # Make sure to update this file along with the others 3 | 4 | name: Test and Release 5 | 6 | # Run this job on all pushes and pull requests 7 | # as well as tags with a semantic version 8 | on: 9 | push: 10 | branches: 11 | - '*' 12 | tags: 13 | # normal versions 14 | - 'v?[0-9]+.[0-9]+.[0-9]+' 15 | # pre-releases 16 | - 'v?[0-9]+.[0-9]+.[0-9]+-**' 17 | pull_request: {} 18 | 19 | # Cancel previous PR/branch runs when a new commit is pushed 20 | concurrency: 21 | group: ${{ github.ref }} 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | # Performs quick checks before the expensive test runs 26 | check-and-lint: 27 | if: contains(github.event.head_commit.message, '[skip ci]') == false 28 | 29 | runs-on: ubuntu-latest 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | - name: Use Node.js 20.x 34 | uses: actions/setup-node@v4 35 | with: 36 | node-version: 20.x 37 | 38 | - name: Install Dependencies 39 | run: npm i 40 | 41 | - name: Build 42 | run: npm run build 43 | 44 | # - name: Perform a type check 45 | # run: npm run check:ts 46 | # env: 47 | # CI: true 48 | # - name: Lint TypeScript code 49 | # run: npm run lint 50 | # - name: Test package files 51 | # run: npm run test:package 52 | 53 | # Runs adapter tests on all supported node versions and OSes 54 | adapter-tests: 55 | if: contains(github.event.head_commit.message, '[skip ci]') == false 56 | 57 | needs: [check-and-lint] 58 | 59 | runs-on: ${{ matrix.os }} 60 | strategy: 61 | matrix: 62 | node-version: [18.x, 20.x, 22.x] 63 | os: [ubuntu-latest, macos-latest] 64 | 65 | steps: 66 | - uses: actions/checkout@v4 67 | - name: Use Node.js ${{ matrix.node-version }} 68 | uses: actions/setup-node@v4 69 | with: 70 | node-version: ${{ matrix.node-version }} 71 | 72 | - name: Install Dependencies 73 | run: npm i 74 | 75 | - name: Install Dependencies 76 | run: npm run build 77 | 78 | - name: Run local tests 79 | run: npm test 80 | # - name: Run unit tests 81 | # run: npm run test:unit 82 | # - name: Run integration tests # (linux/osx) 83 | # if: startsWith(runner.OS, 'windows') == false 84 | # run: DEBUG=testing:* npm run test:integration 85 | # - name: Run integration tests # (windows) 86 | # if: startsWith(runner.OS, 'windows') 87 | # run: set DEBUG=testing:* & npm run test:integration 88 | 89 | # Deploys the final package to NPM 90 | deploy: 91 | needs: [adapter-tests] 92 | 93 | # Trigger this step only when a commit on master is tagged with a version number 94 | if: | 95 | contains(github.event.head_commit.message, '[skip ci]') == false && 96 | github.event_name == 'push' && 97 | startsWith(github.ref, 'refs/tags/') 98 | runs-on: ubuntu-latest 99 | steps: 100 | - name: Checkout code 101 | uses: actions/checkout@v4 102 | 103 | - name: Use Node.js 20.x 104 | uses: actions/setup-node@v4 105 | with: 106 | node-version: 20.x 107 | 108 | - name: Extract the version and commit body from the tag 109 | id: extract_release 110 | # The body may be multiline, therefore we need to escape some characters 111 | run: | 112 | VERSION="${{ github.ref }}" 113 | VERSION=${VERSION##*/} 114 | VERSION=${VERSION##*v} 115 | echo "::set-output name=VERSION::$VERSION" 116 | BODY=$(git show -s --format=%b) 117 | BODY="${BODY//'%'/'%25'}" 118 | BODY="${BODY//$'\n'/'%0A'}" 119 | BODY="${BODY//$'\r'/'%0D'}" 120 | echo "::set-output name=BODY::$BODY" 121 | 122 | - name: Install Dependencies 123 | run: npm i 124 | 125 | - name: Build 126 | run: npm run build 127 | 128 | # - name: Create a clean build 129 | # run: npm run build 130 | - name: Publish package to npm 131 | run: | 132 | npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} 133 | npm whoami 134 | npm publish 135 | 136 | - name: Create Github Release 137 | uses: actions/create-release@v1 138 | env: 139 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 140 | with: 141 | tag_name: ${{ github.ref }} 142 | release_name: Release v${{ steps.extract_release.outputs.VERSION }} 143 | draft: false 144 | # Prerelease versions create pre-releases on GitHub 145 | prerelease: ${{ contains(steps.extract_release.outputs.VERSION, '-') }} 146 | body: ${{ steps.extract_release.outputs.BODY }} 147 | 148 | #- name: Notify Sentry.io about the release 149 | # run: | 150 | # npm i -g @sentry/cli 151 | # export SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} 152 | # export SENTRY_URL=https://sentry.iobroker.net 153 | # export SENTRY_ORG=iobroker 154 | # export SENTRY_PROJECT=iobroker-rest-api 155 | # export SENTRY_VERSION=iobroker.rest-api@${{ steps.extract_release.outputs.VERSION }} 156 | # sentry-cli releases new $SENTRY_VERSION 157 | # sentry-cli releases set-commits $SENTRY_VERSION --auto 158 | # sentry-cli releases finalize $SENTRY_VERSION 159 | 160 | # Add the following line BEFORE finalize if sourcemap uploads are needed 161 | # sentry-cli releases files $SENTRY_VERSION upload-sourcemaps build/ 162 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.idea 3 | package-lock.json 4 | /tmp 5 | /lib/api/swagger/swagger_extension.yaml 6 | /lib/api/swagger/swaggerEdited.yaml 7 | 8 | .dev-server -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": [ 3 | "./test/mocha.setup.js" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.releaseconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["iobroker", "license"], 3 | "exec": { 4 | "before_commit": "npm run build" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](admin/rest-api.png) 2 | # REST-API adapter 3 | 4 | ![Number of Installations](http://iobroker.live/badges/rest-api-installed.svg) ![Number of Installations](http://iobroker.live/badges/rest-api-stable.svg) [![NPM version](http://img.shields.io/npm/v/iobroker.rest-api.svg)](https://www.npmjs.com/package/iobroker.rest-api) 5 | [![Downloads](https://img.shields.io/npm/dm/iobroker.rest-api.svg)](https://www.npmjs.com/package/iobroker.rest-api) 6 | [![Tests](https://travis-ci.org/ioBroker/ioBroker.rest-api.svg?branch=master)](https://travis-ci.org/ioBroker/ioBroker.rest-api) 7 | 8 | [![NPM](https://nodei.co/npm/iobroker.rest-api.png?downloads=true)](https://nodei.co/npm/iobroker.rest-api/) 9 | 10 | **This adapter uses Sentry libraries to automatically report exceptions and code errors to the developers.** For more details and for information how to disable the error reporting, see [Sentry-Plugin Documentation](https://github.com/ioBroker/plugin-sentry#plugin-sentry)! Sentry reporting is used starting with js-controller 3.0. 11 | 12 | This is a RESTFul interface to read the objects and states from ioBroker and to write/control the states over HTTP Get/Post requests. 13 | 14 | The purpose of this adapter is similar to simple-api. But this adapter supports long-polling and URL hooks for subscribing. 15 | 16 | It has a beneficial web interface to play with the requests: 17 | 18 | ![Screenshot](img/screen.png) 19 | 20 | ## Usage 21 | Call in browser `http://ipaddress:8093/` and use Swagger UI to request and modify the states and objects. 22 | 23 | Some request examples: 24 | - `http://ipaddress:8093/v1/state/system.adapter.rest-api.0.memHeapTotal` - read state as JSON 25 | - `http://ipaddress:8093/v1/state/system.adapter.rest-api.0.memHeapTotal/plain` - read state as string (only value) 26 | - `http://ipaddress:8093/v1/state/system.adapter.rest-api.0.memHeapTotal?value=5` - write state with GET (only for back compatibility with simple-api) 27 | - `http://ipaddress:8093/v1/sendto/javascript.0?message=toScript&data={"message":"MESSAGE","data":"FROM REST-API"}` - send a message to `javascript.0` in script `scriptName` 28 | 29 | ### Authentication 30 | To enable the authentication, you must set the `Authentication` option in the configuration dialog. 31 | 32 | There are three types of authentication supported: 33 | - Credentials in query 34 | - Basic authentication 35 | - OAuth2 (Bearer) 36 | 37 | For authentication in a query, you must set `user` and `pass` in query like: 38 | ```http 39 | http://ipaddress:8093/v1/state/system.adapter.rest-api.0.memHeapTotal?user=admin&pass=admin 40 | ``` 41 | 42 | For basic authentication, you must set the `Authorization` header with the value `Basic base64(user:pass)`. 43 | 44 | For Oauth2 authentication, you must set the `Authorization` header with the value `Bearer `. 45 | 46 | The access token can be retrieved with an HTTP request like: 47 | ```http 48 | http://ipaddress:8093/oauth/token?grant_type=password&username=&password=&client_id=ioBroker 49 | ``` 50 | 51 | The answer is like: 52 | ```json 53 | { 54 | "access_token": "21f89e3eee32d3af08a71c1cc44ec72e0e3014a9", 55 | "expires_in": "2025-02-23T11:39:32.208Z", 56 | "refresh_token": "66d35faa5d53ca8242cfe57367210e76b7ffded7", 57 | "refresh_token_expires_in": "2025-03-25T10:39:32.208Z", 58 | "token_type": "Bearer" 59 | } 60 | ``` 61 | 62 | ## Subscribe to the state's or object's changes 63 | Your application could get notifications by every change of the state or object. 64 | 65 | For that, your application must provide an HTTP(S) end-point to accept the updates. 66 | 67 | Example in node.js see here [demoNodeClient.js](examples/demoNodeClient.js) 68 | 69 | ## Long polling 70 | This adapter supports a subscribing on data changes via long polling. 71 | 72 | Example for browser could be found here: [demoNodeClient.js](examples/demoBrowserClient.html) 73 | 74 | ## Web extension 75 | This adapter can run as a web extension. In this case, the path is available under `http://ipaddress:8082/rest` 76 | 77 | ## Notice 78 | - `POST` is always for creating a resource (does not matter if it was duplicated) 79 | - `PUT` is for checking if resource exists then update, else create new resource 80 | - `PATCH` is always for updating a resource 81 | 82 | ## Commands 83 | Additionally, you can execute many socket commands via special interface: 84 | 85 | `http://ipaddress:8093/v1/command/?arg1=Value2&arg2=Value2` 86 | 87 | E.g. 88 | - `http://ipaddress:8093/v1/command/getState?id=system.adapter.admin.0.alive` - to read the state of `system.adapter.admin.0.alive` 89 | - `http://ipaddress:8093/v1/command/readFile?adapter=admin.admin&fileName=admin.png` - to read the file `admin.admin/admin.png` as JSON result 90 | - `http://ipaddress:8093/v1/command/readFile?adapter=admin.admin&fileName=admin.png?binary` - to read the file `admin.admin/admin.png` as file 91 | - `http://ipaddress:8093/v1/command/extendObject?id=system.adapter.admin.0?obj={"common":{"enabled":true}}` - to restart admin 92 | 93 | You can request all commands with POST method too. As body must be an object with parameters. E.g.: 94 | ```bash 95 | curl --location --request POST 'http://ipaddress:8093/v1/command/sendTo' \ 96 | --header 'Content-Type: application/json' \ 97 | --data-raw '{ 98 | "adapterInstance": "history.0", 99 | "command": "getHistory", 100 | "message": {"id": "system.adapter.admin.0.memRss","options": {"aggregate": "onchange", "addId": true}} 101 | }' 102 | ``` 103 | 104 | You cannot send POST request to commands via GUI. 105 | 106 | 107 | ### States 108 | - `getStates(pattern)` - get the list of states for pattern (e.g. for system.adapter.admin.0.*). GUI can have problems by visualization of answer. 109 | - `getForeignStates(pattern)` - same as getStates 110 | - `getState(id)` - get state value by ID 111 | - `setState(id, state)` - set state value with JSON object (e.g. `{"val": 1, "ack": true}`) 112 | - `getBinaryState(id)` - get binary state by ID 113 | - `setBinaryState(id, base64)` - set binary state by ID 114 | 115 | ### Objects 116 | - `getObject(id)` - get object by ID 117 | - `getObjects(list)` - get all states and rooms. GUI can have problems by visualization of answer. 118 | - `getObjectView(design, search, params)` - get specific objects, e.g. design=system, search=state, params=`{"startkey": "system.adapter.admin.", "endkey": "system.adapter.admin.\u9999"}` 119 | - `setObject(id, obj)` - set object with JSON object (e.g. `{"common": {"type": "boolean"}, "native": {}, "type": "state"}`) 120 | - `delObject(id, options)` - delete object by ID 121 | 122 | ### Files 123 | - `readFile(adapter, fileName)` - read file, e.g. adapter=vis.0, fileName=main/vis-views.json. Additionally, you can set option in query binary=true to get answer as file and not as json 124 | - `readFile64(adapter, fileName)` - read file as base64 string, e.g. adapter=vis.0, fileName=main/vis-views.json. Additionally, you can set option in query binary=true to get answer as file and not as json 125 | - `writeFile64(adapter, fileName, data64, options)` - write file, e.g. adapter=vis.0, fileName=main/vis-test.json, data64=eyJhIjogMX0= 126 | - `unlink(adapter, name)` - delete file or folder 127 | - `deleteFile(adapter, name)` - delete file 128 | - `deleteFolder(adapter, name)` - delete folder 129 | - `renameFile(adapter, oldName, newName)` - rename file 130 | - `rename(adapter, oldName, newName)` - rename file or folder 131 | - `mkdir(adapter, dirName)` - create folder 132 | - `readDir(adapter, dirName, options)` - read content of folder 133 | - `chmodFile(adapter, fileName, options)` - change file mode. E.g. adapter=vis.0, fileName=main/*, options = `{"mode": 0x644}` 134 | - `chownFile(adapter, fileName, options)` - change file owner. E.g. adapter=vis.0, fileName=main/*, options = `{"owner": "newOwner", "ownerGroup": "newgroup"}` 135 | - `fileExists(adapter, fileName)` - check if file exists 136 | 137 | ### Admins 138 | - `getHostByIp(ip)` - read host information by IP. e.g. by localhost 139 | - `readLogs(host)` - read file name and size of log files. You can read them with http://ipaddress:8093/ 140 | - `delState(id)` - delete state and object. Same as delObject 141 | - `getRatings(update)` - read adapter ratings (as in admin) 142 | - `getCurrentInstance()` - read adapter namespace (always rest-api.0) 143 | - `decrypt(encryptedText)` - decrypt string with system secret 144 | - `encrypt(plainText)` - encrypt string with system secret 145 | - `getAdapters(adapterName)` - get objects of type "adapter". You can define optionally adapterName 146 | - `updateLicenses(login, password)` - read licenses from ioBroker.net portal 147 | - `getCompactInstances()` - read list of instances with short information 148 | - `getCompactAdapters()` - read list of installed adapters with short information 149 | - `getCompactInstalled(host)` - read short information about installed adapters 150 | - `getCompactSystemConfig()` - read short system config 151 | - `getCompactSystemRepositories()` 152 | - `getCompactRepository(host)` - read short repository 153 | - `getCompactHosts()` - get short information about hosts 154 | - `addUser(user, pass)` - add new user 155 | - `delUser(user)` - delete user 156 | - `addGroup(group, desc, acl)` - create new group 157 | - `delGroup(group)` - delete group 158 | - `changePassword(user, pass)` - change user password 159 | - `getAllObjects()` - read all objects as list. GUI can have problems by visualization of answer. 160 | - `extendObject(id, obj)` - modify object by ID with JSON. (.e.g. `{"common":{"enabled": true}}`) 161 | - `getForeignObjects(pattern, type)` - same as getObjects 162 | - `delObjects(id, options)` - delete objects by pattern 163 | 164 | ### Others 165 | - `updateTokenExpiration(accessToken)` 166 | - `log(text, level[info])` - no answer - add log entry to ioBroker log 167 | - `checkFeatureSupported(feature)` - check if feature is supported by js-controller. 168 | - `getHistory(id, options)` - read history. See for options: https://github.com/ioBroker/ioBroker.history/blob/master/docs/en/README.md#access-values-from-javascript-adapter 169 | - `httpGet(url)` - read URL from server. You can set binary=true to get answer as file 170 | - `sendTo(adapterInstance, command, message)` - send command to instance. E.g. adapterInstance=history.0, command=getHistory, message=`{"id": "system.adapter.admin.0.memRss","options": {"aggregate": "onchange", "addId": true}}` 171 | - `listPermissions()` - read static information with function permissions 172 | - `getUserPermissions()` - read object with user permissions 173 | - `getVersion()` - read adapter name and version 174 | - `getAdapterName()` - read adapter name (always rest-api) 175 | - `clientSubscribe(targetInstance, messageType, data)` 176 | - `getAdapterInstances(adapterName)` - get objects of type "instance". You can define optionally adapterName 177 | 178 | 179 | 180 | 184 | 185 | 186 | ## Changelog 187 | ### 3.0.1 (2025-05-21) 188 | * (@GermanBluefox) Corrected the web extension 189 | 190 | ### 3.0.0 (2025-04-27) 191 | * (@GermanBluefox) Rewritten in TypeScript 192 | * (@GermanBluefox) Removed binary states 193 | 194 | ### 2.1.0 (2025-02-27) 195 | * (@GermanBluefox) Added OAuth2 support 196 | * (@GermanBluefox) Updated packages 197 | * (@GermanBluefox) Replaced icons with SVG 198 | 199 | ### 2.0.3 (2024-07-13) 200 | * (jkuenemund) Changed response for the endpoint get states to the dictionary in swagger 201 | 202 | ### 2.0.1 (2024-05-23) 203 | * (foxriver76) ported to `@iobroker/webserver` 204 | * (theshengfui) Fixed history requests 205 | * (bluefox) Minimum required node.js version is 16 206 | 207 | ### 1.1.0 (2023-05-03) 208 | * (bluefox) Converting of the setState values to the according type 209 | * (bluefox) Implemented file operations 210 | 211 | ### 1.0.5 (2023-03-27) 212 | * (Apollon77) Prepare for future js-controller versions 213 | 214 | ### 1.0.4 (2022-08-31) 215 | * (bluefox) Check if the port is occupied only on defined interface 216 | 217 | ### 1.0.2 (2022-07-27) 218 | * (bluefox) Implemented binary read/write operations 219 | 220 | ### 1.0.1 (2022-07-27) 221 | * (bluefox) Increased the max size of body to 100Mb 222 | 223 | ### 1.0.0 (2022-05-19) 224 | * (bluefox) Final release 225 | 226 | ### 0.6.0 (2022-05-18) 227 | * (bluefox) Added sendTo path 228 | 229 | ### 0.5.0 (2022-05-17) 230 | * (bluefox) Some access errors were corrected 231 | 232 | ### 0.4.0 (2022-04-26) 233 | * (bluefox) Added socket commands 234 | 235 | ### 0.3.6 (2022-04-22) 236 | * (bluefox) Added object creation and enumerations reading 237 | 238 | ### 0.3.5 (2022-04-22) 239 | * (bluefox) Allowed the reading of current subscriptions 240 | 241 | ### 0.3.4 (2022-04-20) 242 | * (bluefox) Corrected subscription 243 | 244 | ### 0.3.1 (2022-04-15) 245 | * (bluefox) First release 246 | 247 | ### 0.1.0 (2017-09-14) 248 | * (bluefox) initial commit 249 | 250 | ## License 251 | Apache 2.0 252 | 253 | Copyright (c) 2017-2025 bluefox 254 | -------------------------------------------------------------------------------- /admin/i18n/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Nur erlauben wenn Anwender auch Besitzer ist", 3 | "Authentication": "Authentifikation", 4 | "Authentication was deactivated": "Die Authentifizierung wurde deaktiviert", 5 | "Chained certificate": "Kettenzertifikat", 6 | "Disable authentication": "Authentifizierung deaktivieren", 7 | "Extend WEB adapter": "WEB-Adapter erweitern", 8 | "How often the hook URLs will be checked": "Wie oft die Hook-URLs überprüft werden", 9 | "IP": "IP", 10 | "Ignore warning": "Warnung ignorieren", 11 | "Let's Encrypt SSL": "Let's Encrypt SSL", 12 | "Let's Encrypt settings": "Einstellungen Let's Encrypt", 13 | "List all datapoints": "Alle Datenpunkte auflisten", 14 | "Listen on all IPs": "Alle IPs zulassen", 15 | "No GUI": "Keine GUI", 16 | "Port": "Port", 17 | "Port to check the domain": "Port um die Domain zu prüfen", 18 | "Private certificate": "Privatzertifikat", 19 | "Public certificate": "Öffentliches Zertifikat", 20 | "Run as": "Laufen unter Anwender", 21 | "Secure(HTTPS)": "Verschlüsselung(HTTPS)", 22 | "Select data source": "Datenquelle auswählen", 23 | "Set certificates or load it first in the system settings (right top).": "Setze Zertificate oder lade die erst unter System/Einstellungen (oben rechts).", 24 | "Timeout for URL hook": "Timeout für URL-Hook", 25 | "Unsecure_Auth": "Das Passwort wird über unsichere Verbindung gesendet. Um Ihre Passwörter zu schützen, aktivieren Sie die sichere Verbindung (HTTPS)!", 26 | "Use Lets Encrypt certificates": "Let's Encrypt Zertifikate verwenden", 27 | "Use this instance for automatic update": "Diese Instanz für automatische Updates verwenden", 28 | "Warning!": "Warnung!", 29 | "all": "alle", 30 | "none": "keins", 31 | "place here": "Dateien hier platzieren" 32 | } -------------------------------------------------------------------------------- /admin/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Allow only when User is Owner", 3 | "Authentication": "Authentication", 4 | "Authentication was deactivated": "Authentication was deactivated", 5 | "Chained certificate": "Chained certificate", 6 | "Disable authentication": "Disable authentication", 7 | "Extend WEB adapter": "Extend WEB adapter", 8 | "How often the hook URLs will be checked": "How often the hook URLs will be checked", 9 | "IP": "IP", 10 | "Ignore warning": "Ignore warning", 11 | "Let's Encrypt SSL": "Let's Encrypt SSL", 12 | "Let's Encrypt settings": "Let's Encrypt settings", 13 | "List all datapoints": "List all data points", 14 | "Listen on all IPs": "Listen on all IPs", 15 | "No GUI": "No GUI", 16 | "Port": "Port", 17 | "Port to check the domain": "Port to check the domain", 18 | "Private certificate": "Private certificate", 19 | "Public certificate": "Public certificate", 20 | "Run as": "Run as", 21 | "Secure(HTTPS)": "Secure(HTTPS)", 22 | "Select data source": "Select data source", 23 | "Set certificates or load it first in the system settings (right top).": "Set certificates or load it first in the system settings (right top).", 24 | "Timeout for URL hook": "Timeout for URL hook", 25 | "Unsecure_Auth": "The password will be sent via unsecure connection. To protect your passwords enable the secure connection (HTTPS)!", 26 | "Use Lets Encrypt certificates": "Use Let's Encrypt certificates", 27 | "Use this instance for automatic update": "Use this instance for automatic update", 28 | "Warning!": "Warning!", 29 | "all": "all", 30 | "none": "none", 31 | "place here": "place the files here" 32 | } -------------------------------------------------------------------------------- /admin/i18n/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Permitir solo cuando el usuario es propietario", 3 | "Authentication": "Autenticación", 4 | "Authentication was deactivated": "La autenticación fue desactivada", 5 | "Chained certificate": "Certificado encadenado", 6 | "Disable authentication": "Deshabilitar autenticación", 7 | "Extend WEB adapter": "Amplíe el adaptador WEB", 8 | "How often the hook URLs will be checked": "Con qué frecuencia se verificarán las URL de enlace", 9 | "IP": "IP", 10 | "Ignore warning": "Ignorar advertencia", 11 | "Let's Encrypt SSL": "Let's Encrypt SSL", 12 | "Let's Encrypt settings": "Vamos a cifrar la configuración", 13 | "List all datapoints": "Listar todos los puntos de datos", 14 | "Listen on all IPs": "Escuchar en todas las direcciones IP", 15 | "No GUI": "Sin GUI", 16 | "Port": "Puerto", 17 | "Port to check the domain": "Puerto para verificar el dominio", 18 | "Private certificate": "Certificado privado", 19 | "Public certificate": "Certificado público", 20 | "Run as": "Correr como", 21 | "Secure(HTTPS)": "Seguro (HTTPS)", 22 | "Select data source": "Seleccionar fuente de datos", 23 | "Set certificates or load it first in the system settings (right top).": "Establezca certificados o cárguelos primero en la configuración del sistema (arriba a la derecha).", 24 | "Timeout for URL hook": "Tiempo de espera para enlace de URL", 25 | "Unsecure_Auth": "La contraseña se enviará a través de una conexión no segura. Para proteger sus contraseñas, ¡habilite la conexión segura (HTTPS)!", 26 | "Use Lets Encrypt certificates": "Utilice los certificados Let's Encrypt", 27 | "Use this instance for automatic update": "Use esta instancia para la actualización automática", 28 | "Warning!": "¡Advertencia!", 29 | "all": "todas", 30 | "none": "ninguna", 31 | "place here": "coloca los archivos aquí" 32 | } -------------------------------------------------------------------------------- /admin/i18n/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Autoriser uniquement lorsque l'utilisateur est propriétaire", 3 | "Authentication": "Authentification", 4 | "Authentication was deactivated": "L'authentification a été désactivée", 5 | "Chained certificate": "Certificat chaîné", 6 | "Disable authentication": "Désactiver l'authentification", 7 | "Extend WEB adapter": "Étendre l'adaptateur WEB", 8 | "How often the hook URLs will be checked": "À quelle fréquence les URL de crochet seront vérifiées", 9 | "IP": "IP", 10 | "Ignore warning": "Ignorer l'avertissement", 11 | "Let's Encrypt SSL": "Let's Encrypt SSL", 12 | "Let's Encrypt settings": "Cryptons les paramètres", 13 | "List all datapoints": "Lister tous les points de données", 14 | "Listen on all IPs": "Écoutez sur toutes les adresses IP", 15 | "No GUI": "Pas d'interface graphique", 16 | "Port": "Port", 17 | "Port to check the domain": "Port pour vérifier le domaine", 18 | "Private certificate": "Certificat privé", 19 | "Public certificate": "Certificat public", 20 | "Run as": "Courir comme", 21 | "Secure(HTTPS)": "Sécurisé (HTTPS)", 22 | "Select data source": "Sélectionner une source de données", 23 | "Set certificates or load it first in the system settings (right top).": "Définissez des certificats ou chargez-les d'abord dans les paramètres du système (en haut à droite).", 24 | "Timeout for URL hook": "Délai d'attente pour le hook d'URL", 25 | "Unsecure_Auth": "Le mot de passe sera envoyé via une connexion non sécurisée. Pour protéger vos mots de passe, activez la connexion sécurisée (HTTPS)!", 26 | "Use Lets Encrypt certificates": "Utiliser les certificats Let's Encrypt", 27 | "Use this instance for automatic update": "Utilisez cette instance pour la mise à jour automatique", 28 | "Warning!": "Attention!", 29 | "all": "tout", 30 | "none": "aucun", 31 | "place here": "Placez les fichiers ici" 32 | } -------------------------------------------------------------------------------- /admin/i18n/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Consenti solo quando l'utente è proprietario", 3 | "Authentication": "Autenticazione", 4 | "Authentication was deactivated": "L'autenticazione è stata disattivata", 5 | "Chained certificate": "Certificato incatenato", 6 | "Disable authentication": "Disabilitare l'autenticazione", 7 | "Extend WEB adapter": "Estendi l'adattatore WEB", 8 | "How often the hook URLs will be checked": "Con quale frequenza verranno controllati gli hook URL", 9 | "IP": "IP", 10 | "Ignore warning": "Ignora l'avviso", 11 | "Let's Encrypt SSL": "Let's Encrypt SSL", 12 | "Let's Encrypt settings": "Let's Encrypt settings", 13 | "List all datapoints": "Elenca tutti i punti dati", 14 | "Listen on all IPs": "Ascolta su tutti gli IP", 15 | "No GUI": "Nessuna interfaccia grafica", 16 | "Port": "Porta", 17 | "Port to check the domain": "Porta per controllare il dominio", 18 | "Private certificate": "Certificato privato", 19 | "Public certificate": "Certificato pubblico", 20 | "Run as": "Correre come", 21 | "Secure(HTTPS)": "Sicuro (HTTPS)", 22 | "Select data source": "Seleziona l'origine dati", 23 | "Set certificates or load it first in the system settings (right top).": "Imposta i certificati o caricali prima nelle impostazioni di sistema (in alto a destra).", 24 | "Timeout for URL hook": "Timeout per hook URL", 25 | "Unsecure_Auth": "La password verrà inviata tramite connessione non protetta. Per proteggere le tue password abilita la connessione sicura (HTTPS)!", 26 | "Use Lets Encrypt certificates": "Utilizza Let's Encrypt certificates", 27 | "Use this instance for automatic update": "Utilizza questa istanza per l'aggiornamento automatico", 28 | "Warning!": "Avvertimento!", 29 | "all": "tutti", 30 | "none": "nessuna", 31 | "place here": "posiziona i file qui" 32 | } -------------------------------------------------------------------------------- /admin/i18n/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Alleen toestaan als gebruiker eigenaar is", 3 | "Authentication": "authenticatie", 4 | "Authentication was deactivated": "Verificatie was gedeactiveerd", 5 | "Chained certificate": "Geketend certificaat", 6 | "Disable authentication": "Schakel verificatie uit", 7 | "Extend WEB adapter": "WEB-adapter uitbreiden", 8 | "How often the hook URLs will be checked": "Hoe vaak de hook-URL's worden gecontroleerd", 9 | "IP": "IK P", 10 | "Ignore warning": "Negeer waarschuwing", 11 | "Let's Encrypt SSL": "Let's Encrypt SSL", 12 | "Let's Encrypt settings": "Laten we de instellingen versleutelen", 13 | "List all datapoints": "Lijst alle gegevenspunten", 14 | "Listen on all IPs": "Luister op alle IP's", 15 | "No GUI": "Geen GUI", 16 | "Port": "Haven", 17 | "Port to check the domain": "Poort om het domein te controleren", 18 | "Private certificate": "Privé certificaat", 19 | "Public certificate": "Openbaar certificaat", 20 | "Run as": "Rennen als", 21 | "Secure(HTTPS)": "Secure (HTTPS)", 22 | "Select data source": "Selecteer gegevensbron", 23 | "Set certificates or load it first in the system settings (right top).": "Stel certificaten in of laad het eerst in de systeeminstellingen (rechtsboven).", 24 | "Timeout for URL hook": "Time-out voor URL-hook", 25 | "Unsecure_Auth": "Het wachtwoord wordt verzonden via onbeveiligde verbinding. Ter beveiliging van uw wachtwoorden schakelt u de beveiligde verbinding (HTTPS) in!", 26 | "Use Lets Encrypt certificates": "Gebruik Let's Encrypt-certificaten", 27 | "Use this instance for automatic update": "Gebruik deze instantie voor automatische update", 28 | "Warning!": "Waarschuwing!", 29 | "all": "allemaal", 30 | "none": "geen", 31 | "place here": "plaats de bestanden hier" 32 | } -------------------------------------------------------------------------------- /admin/i18n/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Zezwalaj tylko wtedy, gdy użytkownik jest właścicielem", 3 | "Authentication": "Poświadczenie", 4 | "Authentication was deactivated": "Uwierzytelnianie zostało dezaktywowane", 5 | "Chained certificate": "Przykuty certyfikat", 6 | "Disable authentication": "Wyłącz uwierzytelnianie", 7 | "Extend WEB adapter": "Przedłuż adapter WEB", 8 | "How often the hook URLs will be checked": "Jak często będą sprawdzane adresy URL haków", 9 | "IP": "IP", 10 | "Ignore warning": "Zignoruj ​​ostrzeżenie", 11 | "Let's Encrypt SSL": "Let's Encrypt SSL", 12 | "Let's Encrypt settings": "Zakodujmy ustawienia", 13 | "List all datapoints": "Wyświetl wszystkie punkty danych", 14 | "Listen on all IPs": "Posłuchaj na wszystkich IP", 15 | "No GUI": "Brak GUI", 16 | "Port": "Port", 17 | "Port to check the domain": "Port do sprawdzenia domeny", 18 | "Private certificate": "Prywatny certyfikat", 19 | "Public certificate": "Certyfikat publiczny", 20 | "Run as": "Uruchom jako", 21 | "Secure(HTTPS)": "Bezpieczne (HTTPS)", 22 | "Select data source": "Wybierz źródło danych", 23 | "Set certificates or load it first in the system settings (right top).": "Ustaw certyfikaty lub załaduj najpierw w ustawieniach systemu (prawy górny).", 24 | "Timeout for URL hook": "Limit czasu dla haka URL", 25 | "Unsecure_Auth": "Hasło zostanie wysłane przez połączenie bez zabezpieczeń. Aby chronić swoje hasła, włącz bezpieczne połączenie (HTTPS)!", 26 | "Use Lets Encrypt certificates": "Użyj Let's Encrypt certificates", 27 | "Use this instance for automatic update": "Użyj tej instancji do automatycznej aktualizacji", 28 | "Warning!": "Ostrzeżenie!", 29 | "all": "wszystko", 30 | "none": "Żaden", 31 | "place here": "umieść pliki tutaj" 32 | } -------------------------------------------------------------------------------- /admin/i18n/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Permitir apenas quando o usuário é proprietário", 3 | "Authentication": "Autenticação", 4 | "Authentication was deactivated": "A autenticação foi desativada", 5 | "Chained certificate": "Certificado acorrentado", 6 | "Disable authentication": "Desativar autenticação", 7 | "Extend WEB adapter": "Estender adaptador WEB", 8 | "How often the hook URLs will be checked": "Com que frequência os URLs de gancho serão verificados", 9 | "IP": "IP", 10 | "Ignore warning": "Ignorar aviso", 11 | "Let's Encrypt SSL": "Let's Encrypt SSL", 12 | "Let's Encrypt settings": "Vamos criptografar configurações", 13 | "List all datapoints": "Listar todos os pontos de dados", 14 | "Listen on all IPs": "Ouça todos os IPs", 15 | "No GUI": "Sem interface gráfica", 16 | "Port": "Porta", 17 | "Port to check the domain": "Porta para verificar o domínio", 18 | "Private certificate": "Certificado privado", 19 | "Public certificate": "Certificado público", 20 | "Run as": "Correr como", 21 | "Secure(HTTPS)": "Seguro (HTTPS)", 22 | "Select data source": "Selecionar fonte de dados", 23 | "Set certificates or load it first in the system settings (right top).": "Defina certificados ou carregue primeiro nas configurações do sistema (parte superior direita).", 24 | "Timeout for URL hook": "Tempo limite para gancho de URL", 25 | "Unsecure_Auth": "A senha será enviada por meio de conexão não segura. Para proteger suas senhas, ative a conexão segura (HTTPS)!", 26 | "Use Lets Encrypt certificates": "Use Vamos criptografar certificados", 27 | "Use this instance for automatic update": "Use esta instância para atualização automática", 28 | "Warning!": "Atenção!", 29 | "all": "tudo", 30 | "none": "Nenhum", 31 | "place here": "coloque os arquivos aqui" 32 | } -------------------------------------------------------------------------------- /admin/i18n/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Разрешить только когда пользователь является владельцем", 3 | "Authentication": "Аутентификация", 4 | "Authentication was deactivated": "Аутентификация была отключена", 5 | "Chained certificate": "'Chained' сертификат", 6 | "Disable authentication": "Отключить аутентификацию", 7 | "Extend WEB adapter": "Расширить веб-адаптер", 8 | "How often the hook URLs will be checked": "Как часто будут проверяться URL-адреса хуков", 9 | "IP": "IP", 10 | "Ignore warning": "Игнорировать предупреждение", 11 | "Let's Encrypt SSL": "Let's Encrypt SSL", 12 | "Let's Encrypt settings": "Настройкт Let's Encrypt", 13 | "List all datapoints": "Список всех точек данных", 14 | "Listen on all IPs": "Открыть для всех IP адресов", 15 | "No GUI": "Без графического интерфейса", 16 | "Port": "Порт", 17 | "Port to check the domain": "Порт для проверки доменного имени", 18 | "Private certificate": "'Private' сертификат", 19 | "Public certificate": "'Public' сертификат", 20 | "Run as": "Запустить от пользователя", 21 | "Secure(HTTPS)": "Шифрование(HTTPS)", 22 | "Select data source": "Выберите источник данных", 23 | "Set certificates or load it first in the system settings (right top).": "Нужно выбрать сертификаты или сначала загрузить их в системных настройках (вверху справа).", 24 | "Timeout for URL hook": "Тайм-аут для перехвата URL", 25 | "Unsecure_Auth": "Пароль будет отправлен через незащищенное соединение. Для защиты ваших паролей активируйте безопасное соединение (HTTPS)!", 26 | "Use Lets Encrypt certificates": "Использовать сертификаты Let's Encrypt", 27 | "Use this instance for automatic update": "Обновлять сертификаты в этом драйвере", 28 | "Warning!": "Предупреждение!", 29 | "all": "все", 30 | "none": "none", 31 | "place here": "разместите файлы здесь" 32 | } -------------------------------------------------------------------------------- /admin/i18n/uk.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Дозволити лише тоді, коли користувач є власником", 3 | "Authentication": "Аутентифікація", 4 | "Authentication was deactivated": "Автентифікацію було вимкнено", 5 | "Chained certificate": "Прикутий сертифікат", 6 | "Disable authentication": "Вимкнути автентифікацію", 7 | "Extend WEB adapter": "Розширення WEB-адаптера", 8 | "How often the hook URLs will be checked": "Як часто перевірятимуться URL-адреси гаків", 9 | "IP": "IP", 10 | "Ignore warning": "Ігнорувати попередження", 11 | "Let's Encrypt SSL": "Давайте зашифруємо SSL", 12 | "Let's Encrypt settings": "Давайте зашифруємо налаштування", 13 | "List all datapoints": "Перелічіть усі точки даних", 14 | "Listen on all IPs": "Прослуховування на всіх IP", 15 | "No GUI": "Без GUI", 16 | "Port": "Порт", 17 | "Port to check the domain": "Порт для перевірки домену", 18 | "Private certificate": "Приватний сертифікат", 19 | "Public certificate": "Публічний сертифікат", 20 | "Run as": "Бігати як", 21 | "Secure(HTTPS)": "Безпечний (HTTPS)", 22 | "Select data source": "Виберіть джерело даних", 23 | "Set certificates or load it first in the system settings (right top).": "Спершу встановіть сертифікати або завантажте їх у налаштуваннях системи (справа вгорі).", 24 | "Timeout for URL hook": "Час очікування URL-адреси", 25 | "Unsecure_Auth": "Пароль буде надіслано через незахищене з’єднання. Щоб захистити ваші паролі, увімкніть безпечне з'єднання (HTTPS)!", 26 | "Use Lets Encrypt certificates": "Використовуйте сертифікати Let's Encrypt", 27 | "Use this instance for automatic update": "Використовуйте цей екземпляр для автоматичного оновлення", 28 | "Warning!": "УВАГА!", 29 | "all": "все", 30 | "none": "немає", 31 | "place here": "розмістіть файли тут" 32 | } -------------------------------------------------------------------------------- /admin/i18n/zh-cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "仅当用户为所有者时才允许", 3 | "Authentication": "认证机制", 4 | "Authentication was deactivated": "身份认证机制已停用", 5 | "Chained certificate": "链式证书", 6 | "Disable authentication": "禁用身份认证机制", 7 | "Extend WEB adapter": "扩展WEB适配器", 8 | "How often the hook URLs will be checked": "多久检查一次挂钩 URL", 9 | "IP": "IP", 10 | "Ignore warning": "忽略警告", 11 | "Let's Encrypt SSL": "Let's Encrypt SSL", 12 | "Let's Encrypt settings": "设置Let's Encrypt", 13 | "List all datapoints": "列出所有数据点", 14 | "Listen on all IPs": "监听所有IP", 15 | "No GUI": "没有图形用户界面", 16 | "Port": "端口", 17 | "Port to check the domain": "自动更新证书使用的端口", 18 | "Private certificate": "私人证书", 19 | "Public certificate": "公共证书", 20 | "Run as": "运行为", 21 | "Secure(HTTPS)": "安全访问(HTTPS)", 22 | "Select data source": "选择数据源", 23 | "Set certificates or load it first in the system settings (right top).": "首先在系统设置中(右上角)设置或加载证书。", 24 | "Timeout for URL hook": "URL 挂钩超时", 25 | "Unsecure_Auth": "密码将通过不安全的连接发送。 要保护您的密码,请启用安全连接(HTTPS)!", 26 | "Use Lets Encrypt certificates": "使用Let's Encrypt证书", 27 | "Use this instance for automatic update": "自动更新Let's Encrypt证书", 28 | "Warning!": "警告!", 29 | "all": "所有", 30 | "none": "无", 31 | "place here": "将文件拖拽到这里" 32 | } -------------------------------------------------------------------------------- /admin/jsonConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "tabs", 3 | "i18n": true, 4 | "items": { 5 | "mainTab": { 6 | "type": "panel", 7 | "label": "Main settings", 8 | "items": { 9 | "webInstance": { 10 | "type": "instance", 11 | "label": "Extend WEB adapter", 12 | "all": true, 13 | "sm": 12, 14 | "md": 6, 15 | "lg": 3, 16 | "adapter": "web" 17 | }, 18 | "bind": { 19 | "hidden": "!!data.webInstance", 20 | "newLine": true, 21 | "type": "ip", 22 | "listenOnAllPorts": true, 23 | "label": "IP", 24 | "sm": 12, 25 | "md": 8, 26 | "lg": 5 27 | }, 28 | "port": { 29 | "hidden": "!!data.webInstance", 30 | "type": "number", 31 | "min": 1, 32 | "max": 65565, 33 | "label": "Port", 34 | "sm": 12, 35 | "md": 4, 36 | "lg": 3 37 | }, 38 | "secure": { 39 | "hidden": "!!data.webInstance", 40 | "newLine": true, 41 | "type": "checkbox", 42 | "label": "Secure(HTTPS)", 43 | "sm": 12, 44 | "md": 6, 45 | "lg": 2 46 | }, 47 | "certPublic": { 48 | "type": "certificate", 49 | "hidden": "!data.secure || !!data.webInstance", 50 | "certType": "public", 51 | "validator": "!data.secure || data.certPublic", 52 | "label": "Public certificate", 53 | "sm": 12, 54 | "md": 6, 55 | "lg": 2 56 | }, 57 | "certPrivate": { 58 | "hidden": "!data.secure || !!data.webInstance", 59 | "type": "certificate", 60 | "certType": "private", 61 | "validator": "!data.secure || data.certPrivate", 62 | "label": "Private certificate", 63 | "sm": 12, 64 | "md": 6, 65 | "lg": 2 66 | }, 67 | "certChained": { 68 | "hidden": "!data.secure || !!data.webInstance", 69 | "type": "certificate", 70 | "certType": "chained", 71 | "label": "Chained certificate", 72 | "sm": 12, 73 | "md": 6, 74 | "lg": 2 75 | }, 76 | "auth": { 77 | "newLine": true, 78 | "hidden": "!!data.webInstance", 79 | "type": "checkbox", 80 | "confirm": { 81 | "condition": "!data.secure && data.auth", 82 | "title": "Warning!", 83 | "text": "Unsecure_Auth", 84 | "ok": "Ignore warning", 85 | "cancel": "Disable authentication", 86 | "type": "warning", 87 | "alsoDependsOn": ["secure"] 88 | }, 89 | "label": "Authentication", 90 | "sm": 12, 91 | "md": 6, 92 | "lg": 2 93 | }, 94 | "defaultUser": { 95 | "hidden": "!!data.auth || !!data.webInstance", 96 | "type": "user", 97 | "label": "Run as", 98 | "sm": 12, 99 | "md": 6, 100 | "lg": 2 101 | }, 102 | "onlyAllowWhenUserIsOwner": { 103 | "newLine": true, 104 | "type": "checkbox", 105 | "label": "Allow only when User is Owner", 106 | "sm": 12 107 | }, 108 | "dataSource": { 109 | "newLine": true, 110 | "type": "instance", 111 | "label": "Select data source", 112 | "sm": 12, 113 | "md": 6, 114 | "lg": 3, 115 | "adapter": "_dataSources" 116 | }, 117 | "checkInterval": { 118 | "newLine": true, 119 | "type": "number", 120 | "help": "ms", 121 | "label": "How often the hook URLs will be checked", 122 | "sm": 12, 123 | "md": 6, 124 | "lg": 3 125 | }, 126 | "hookTimeout": { 127 | "type": "number", 128 | "help": "ms", 129 | "label": "Timeout for URL hook", 130 | "sm": 12, 131 | "md": 6, 132 | "lg": 3 133 | }, 134 | "noUI": { 135 | "newLine": true, 136 | "type": "checkbox", 137 | "label": "No GUI", 138 | "sm": 12, 139 | "md": 6, 140 | "lg": 2 141 | }, 142 | "noCommands": { 143 | "newLine": true, 144 | "type": "checkbox", 145 | "label": "Do not allow commands execution", 146 | "sm": 12, 147 | "md": 6, 148 | "lg": 3 149 | }, 150 | "noAdminCommands": { 151 | "hidden": "!!data.noCommands", 152 | "type": "checkbox", 153 | "label": "Do not allow admin commands execution", 154 | "sm": 12, 155 | "md": 6, 156 | "lg": 3 157 | } 158 | } 159 | }, 160 | "leTab": { 161 | "type": "panel", 162 | "label": "Let's Encrypt SSL", 163 | "disabled": "!data.secure", 164 | "items": { 165 | "_image": { 166 | "type": "staticImage", 167 | "tooltip": "Read about Let's Encrypt certificates", 168 | "href": "https://github.com/ioBroker/ioBroker.admin/blob/master/README.md#lets-encrypt-certificates", 169 | "src": "../../img/le.png", 170 | "style": { 171 | "width": 200, 172 | "height": 59 173 | } 174 | }, 175 | "_staticText": { 176 | "type": "staticText", 177 | "text": "ra_Use iobroker.acme adapter for letsencrypt certificates" 178 | } 179 | } 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /admin/rest-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioBroker/ioBroker.rest-api/9546b5b8a189bfe8380f8868fbf21a96060c99f8/admin/rest-api.png -------------------------------------------------------------------------------- /admin/rest-api.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | -------------------------------------------------------------------------------- /dist/lib/api/controllers/common.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.checkPermissions = checkPermissions; 4 | exports.findState = findState; 5 | exports.getState = getState; 6 | exports.parseUrl = parseUrl; 7 | exports.errorResponse = errorResponse; 8 | const ERROR_PERMISSION = 'permissionError'; 9 | function checkPermissions(adapter, user, requiredRights, callback) { 10 | void adapter.calculatePermissions(user, requiredRights, (acl) => { 11 | if (user !== 'system.user.admin') { 12 | // type: file, object, state, other 13 | // operation: create, read, write, list, delete, sendto, execute, sendto 14 | if (requiredRights?.[0] && acl) { 15 | // If permission required 16 | if (requiredRights[0].type) { 17 | const aclType = acl[requiredRights[0].type]; 18 | if (aclType && aclType[requiredRights[0].operation]) { 19 | callback(null); 20 | return; 21 | } 22 | } 23 | else { 24 | callback(null); 25 | return; 26 | } 27 | } 28 | adapter.log.warn(`No permission for "${user}" to call ${JSON.stringify(requiredRights)}`); 29 | callback(ERROR_PERMISSION); 30 | } 31 | else { 32 | callback(null); 33 | } 34 | }); 35 | } 36 | function findState(adapter, idOrName, user, type, callback) { 37 | if (typeof type === 'function') { 38 | callback = type; 39 | type = null; 40 | } 41 | adapter.findForeignObject(idOrName, type, 42 | // @ts-expect-error fixed in js-controller 43 | { user, checked: true, limitToOwnerRights: adapter.config.onlyAllowWhenUserIsOwner }, callback); 44 | } 45 | function getState(adapter, idOrName, user, type, callback) { 46 | if (typeof type === 'function') { 47 | callback = type; 48 | type = null; 49 | } 50 | findState(adapter, idOrName, user, type, (err, id, originId) => { 51 | if (err && (!err.message || !err.message.includes('permissionError'))) { 52 | callback?.(err, undefined, null, originId); 53 | } 54 | else { 55 | if (err?.message.includes('permissionError')) { 56 | // assume it is ID 57 | id = idOrName; 58 | } 59 | if (id) { 60 | void adapter.getForeignState(id, { user, limitToOwnerRights: adapter.config.onlyAllowWhenUserIsOwner }, (err, state) => { 61 | if (err || !state) { 62 | state = undefined; 63 | } 64 | callback?.(err, state, id, originId); 65 | }); 66 | } 67 | else { 68 | callback?.(null, undefined, null, originId); 69 | } 70 | } 71 | }); 72 | } 73 | function parseUrl(url, swagger, webExtensionPrefix) { 74 | // "/v1/object/adapter.system.admin.0.alive" 75 | const parts = url.split('?')[0].split('/'); 76 | if (parts[1] === webExtensionPrefix) { 77 | parts.shift(); // / 78 | parts.shift(); // remove swagger 79 | parts.shift(); // remove v1 80 | parts.shift(); // remove objects or states 81 | } 82 | else { 83 | parts.shift(); // / 84 | parts.shift(); // remove v1 85 | parts.shift(); // remove objects or states 86 | } 87 | const result = {}; 88 | if (swagger?.operation?.parameters) { 89 | let i = 0; 90 | swagger.operation.parameters.forEach((param) => { 91 | if (param.in === 'path') { 92 | try { 93 | result[param.name] = parts[i] !== undefined ? decodeURIComponent(parts[i]) : ''; 94 | } 95 | catch { 96 | console.error(`Cannot decode ${parts[i]}"`); 97 | result[param.name] = parts[i]; 98 | } 99 | i++; 100 | } 101 | }); 102 | } 103 | else { 104 | parts.forEach((param, i) => { 105 | try { 106 | result[`arg${i}`] = decodeURIComponent(param); 107 | } 108 | catch { 109 | console.error(`Cannot decode ${param}"`); 110 | result[`arg${i}`] = param; 111 | } 112 | }); 113 | } 114 | return result; 115 | } 116 | function errorResponse(req, res, error, response, responseCode) { 117 | error = error.toString(); 118 | if (error === 'Error: permissionError') { 119 | error = 'permissionError'; 120 | } 121 | req._adapter.log.warn(`Warning by "${req.url}": ${error}`); 122 | if (typeof response === 'number') { 123 | res.status(response).json({ error }); 124 | } 125 | else { 126 | res.status(responseCode || (error.toString().includes('permissionError') ? 403 : 500)).json(Object.assign(response || {}, { error })); 127 | } 128 | } 129 | //# sourceMappingURL=common.js.map -------------------------------------------------------------------------------- /dist/lib/api/controllers/common.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"common.js","sourceRoot":"","sources":["../../../../src/lib/api/controllers/common.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/lib/api/controllers/enum.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.readMainEnums = readMainEnums; 4 | exports.readEnums = readEnums; 5 | const common_1 = require("./common"); 6 | function readMainEnums(req, res) { 7 | (0, common_1.checkPermissions)(req._adapter, req._user, [{ type: 'object', operation: 'read' }], async (error) => { 8 | if (error) { 9 | (0, common_1.errorResponse)(req, res, error); 10 | } 11 | else { 12 | // check if instance is alive 13 | try { 14 | const enums = await req._adapter.getEnumsAsync('', { 15 | user: req._user, 16 | limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner, 17 | }); 18 | res.json(enums); 19 | } 20 | catch (error) { 21 | req._adapter.log.warn(`Cannot read enums: ${error}`); 22 | (0, common_1.errorResponse)(req, res, error); 23 | } 24 | } 25 | }); 26 | } 27 | function readEnums(req, res) { 28 | (0, common_1.checkPermissions)(req._adapter, req._user, [{ type: 'object', operation: 'read' }], async (error) => { 29 | if (error) { 30 | (0, common_1.errorResponse)(req, res, error); 31 | } 32 | else { 33 | const params = (0, common_1.parseUrl)(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX); 34 | // check if instance is alive 35 | try { 36 | const enums = await req._adapter.getEnumAsync(params.enumId, { 37 | user: req._user, 38 | limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner, 39 | }); 40 | if (enums && enums.result) { 41 | res.json(Object.keys(enums.result) 42 | .filter(id => id.split('.').length > 2) 43 | .map(id => ({ 44 | _id: id, 45 | common: enums.result[id].common, 46 | }))); 47 | } 48 | else { 49 | res.json([]); 50 | } 51 | } 52 | catch (error) { 53 | (0, common_1.errorResponse)(req, res, error); 54 | } 55 | } 56 | }); 57 | } 58 | //# sourceMappingURL=enum.js.map -------------------------------------------------------------------------------- /dist/lib/api/controllers/enum.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"enum.js","sourceRoot":"","sources":["../../../../src/lib/api/controllers/enum.ts"],"names":[],"mappings":";;AAIA,sCAkBC;AAED,8BA6BC;AArDD,qCAAqE;AAIrE,SAAgB,aAAa,CAAC,GAAe,EAAE,GAAa;IACxD,IAAA,yBAAgB,EAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;QAC7F,IAAI,KAAK,EAAE,CAAC;YACR,IAAA,sBAAa,EAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QACnC,CAAC;aAAM,CAAC;YACJ,6BAA6B;YAC7B,IAAI,CAAC;gBACD,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,EAAE;oBAC/C,IAAI,EAAE,GAAG,CAAC,KAAK;oBACf,kBAAkB,EAAE,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,wBAAwB;iBACnE,CAAC,CAAC;gBACH,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,sBAAsB,KAAK,EAAE,CAAC,CAAC;gBACrD,IAAA,sBAAa,EAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YACnC,CAAC;QACL,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAgB,SAAS,CAAC,GAAe,EAAE,GAAa;IACpD,IAAA,yBAAgB,EAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;QAC7F,IAAI,KAAK,EAAE,CAAC;YACR,IAAA,sBAAa,EAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QACnC,CAAC;aAAM,CAAC;YACJ,MAAM,MAAM,GAAG,IAAA,iBAAQ,EAAqB,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC;YACrG,6BAA6B;YAC7B,IAAI,CAAC;gBACD,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE;oBACzD,IAAI,EAAE,GAAG,CAAC,KAAK;oBACf,kBAAkB,EAAE,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,wBAAwB;iBACnE,CAAC,CAAC;gBACH,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;oBACxB,GAAG,CAAC,IAAI,CACJ,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;yBACpB,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;yBACtC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;wBACR,GAAG,EAAE,EAAE;wBACP,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM;qBAClC,CAAC,CAAC,CACV,CAAC;gBACN,CAAC;qBAAM,CAAC;oBACJ,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACjB,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,IAAA,sBAAa,EAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YACnC,CAAC;QACL,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC"} -------------------------------------------------------------------------------- /dist/lib/api/controllers/file.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.readFile = readFile; 4 | exports.deleteFile = deleteFile; 5 | exports.writeFile = writeFile; 6 | exports.readDir = readDir; 7 | const common_1 = require("./common"); 8 | function readFile(req, res) { 9 | (0, common_1.checkPermissions)(req._adapter, req._user, [{ type: 'file', operation: 'read' }], async (error) => { 10 | if (error) { 11 | (0, common_1.errorResponse)(req, res, error); 12 | } 13 | else { 14 | const params = (0, common_1.parseUrl)(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX); 15 | try { 16 | const data = await req._adapter.readFileAsync(params.objectId, params.fileName, { 17 | user: req._user, 18 | limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner, 19 | }); 20 | if (data && data.mimeType) { 21 | res.set('Content-Type', data.mimeType); 22 | res.send(data.file); 23 | } 24 | else { 25 | res.status(404).send(Buffer.from('')); 26 | } 27 | } 28 | catch (error) { 29 | (0, common_1.errorResponse)(req, res, error); 30 | } 31 | } 32 | }); 33 | } 34 | function deleteFile(req, res) { 35 | (0, common_1.checkPermissions)(req._adapter, req._user, [{ type: 'file', operation: 'delete' }], async (error) => { 36 | if (error) { 37 | (0, common_1.errorResponse)(req, res, error); 38 | } 39 | else { 40 | const params = (0, common_1.parseUrl)(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX); 41 | try { 42 | await req._adapter.delFileAsync(params.objectId, params.fileName, { 43 | user: req._user, 44 | limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner, 45 | }); 46 | res.json({ success: true }); 47 | } 48 | catch (err) { 49 | if (err.toString().includes('Not exists')) { 50 | res.status(404).json({ error: err.toString() }); 51 | } 52 | else { 53 | (0, common_1.errorResponse)(req, res, err); 54 | } 55 | } 56 | } 57 | }); 58 | } 59 | function writeFile(req, res) { 60 | (0, common_1.checkPermissions)(req._adapter, req._user, [{ type: 'file', operation: 'write' }], async (error) => { 61 | if (error) { 62 | (0, common_1.errorResponse)(req, res, error); 63 | } 64 | else { 65 | const params = (0, common_1.parseUrl)(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX); 66 | if (req.files?.file[0].buffer) { 67 | try { 68 | await req._adapter.writeFileAsync(params.objectId, params.fileName, req.files?.file[0].buffer, { 69 | user: req._user, 70 | limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner, 71 | }); 72 | res.json({ success: true }); 73 | } 74 | catch (err) { 75 | (0, common_1.errorResponse)(req, res, err); 76 | } 77 | } 78 | else { 79 | (0, common_1.errorResponse)(req, res, 'No file provided.', 422); 80 | } 81 | } 82 | }); 83 | } 84 | function readDir(req, res) { 85 | (0, common_1.checkPermissions)(req._adapter, req._user, [{ type: 'file', operation: 'list' }], async (error) => { 86 | if (error) { 87 | (0, common_1.errorResponse)(req, res, error); 88 | } 89 | else { 90 | const params = (0, common_1.parseUrl)(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX); 91 | try { 92 | const response = await req._adapter.readDirAsync(params.objectId, params.dirName || '', { 93 | user: req._user, 94 | limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner, 95 | }); 96 | res.json(response); 97 | } 98 | catch (err) { 99 | (0, common_1.errorResponse)(req, res, err); 100 | } 101 | } 102 | }); 103 | } 104 | //# sourceMappingURL=file.js.map -------------------------------------------------------------------------------- /dist/lib/api/controllers/file.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"file.js","sourceRoot":"","sources":["../../../../src/lib/api/controllers/file.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/lib/api/controllers/history.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.getHistory = getHistory; 4 | exports.postHistory = postHistory; 5 | exports.addHistoryByGet = addHistoryByGet; 6 | exports.addHistoryByPost = addHistoryByPost; 7 | const common_1 = require("./common"); 8 | const PARAMETERS = { 9 | start: 'number', 10 | end: 'number', 11 | count: 'number', 12 | from: 'boolean', 13 | ack: 'boolean', 14 | q: 'boolean', 15 | addId: 'boolean', 16 | limit: 'number', 17 | ignoreNull: 'boolean', 18 | removeBorderValues: 'boolean', 19 | returnNewestEntries: 'boolean', 20 | aggregate: ['minmax', 'max', 'min', 'average', 'total', 'count', 'percentile', 'quantile', 'integral', 'none'], 21 | percentile: 'number', 22 | quantile: 'number', 23 | integralUnit: 'string', 24 | integralInterpolation: 'string', 25 | enum: ['linear', 'none'], 26 | }; 27 | const PARAMETERS_ADD = { 28 | val: 'string', 29 | from: 'string', 30 | ack: 'boolean', 31 | q: 'number', 32 | ts: 'number', 33 | }; 34 | function getHistory(req, res) { 35 | (0, common_1.checkPermissions)(req._adapter, req._user, [{ type: 'other', operation: 'sendto' }], async (error) => { 36 | if (error) { 37 | (0, common_1.errorResponse)(req, res, error); 38 | } 39 | else { 40 | if (req._adapter.config.dataSource) { 41 | // check if instance is alive 42 | const state = await req._adapter.getForeignStateAsync(`system.adapter.${req._adapter.config.dataSource}.alive`); 43 | if (state?.val) { 44 | const params = (0, common_1.parseUrl)(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX); 45 | req._adapter.log.debug(`Read data from: ${req._adapter.config.dataSource}`); 46 | const options = { instance: req._adapter.config.dataSource }; 47 | Object.keys(PARAMETERS).forEach(attr => { 48 | if (Object.hasOwnProperty.call(req.query, attr)) { 49 | if (PARAMETERS[attr] === 'boolean') { 50 | options[attr] = req.query[attr] === 'true'; 51 | } 52 | else if (PARAMETERS[attr] === 'number') { 53 | options[attr] = parseFloat(req.query[attr]); 54 | } 55 | else if (Array.isArray(PARAMETERS[attr])) { 56 | if (PARAMETERS[attr].includes(req.query[attr])) { 57 | options[attr] = req.query[attr]; 58 | } 59 | else { 60 | req._adapter.log.warn(`Unknown value ${req.query[attr]} for attribute ${attr}. Allowed: ${PARAMETERS[attr].join(', ')}`); 61 | } 62 | } 63 | else if (PARAMETERS[attr] === 'string') { 64 | options[attr] = req.query[attr]; 65 | } 66 | } 67 | }); 68 | req._adapter.getHistory(params.stateId, options, (error, result) => { 69 | if (error) { 70 | (0, common_1.errorResponse)(req, res, error?.toString()); 71 | return; 72 | } 73 | res.json(result); 74 | }); 75 | } 76 | else { 77 | res.status(500).json({ 78 | error: 'Instance is not alive', 79 | instance: req._adapter.config.dataSource, 80 | }); 81 | } 82 | } 83 | else { 84 | res.status(422).json({ error: 'No dataSource defined!' }); 85 | } 86 | } 87 | }); 88 | } 89 | function postHistory(req, res) { 90 | (0, common_1.checkPermissions)(req._adapter, req._user, req.body.options, async (error) => { 91 | if (error) { 92 | (0, common_1.errorResponse)(req, res, error); 93 | } 94 | else { 95 | if (req._adapter.config.dataSource) { 96 | const state = await req._adapter.getForeignStateAsync(`system.adapter.${req._adapter.config.dataSource}.alive`); 97 | if (state?.val) { 98 | req._adapter.log.debug(`Read data from: ${req._adapter.config.dataSource}`); 99 | const options = { 100 | id: req.body.id, 101 | options: { 102 | instance: req._adapter.config.dataSource, 103 | }, 104 | }; 105 | if (req.body.options && typeof req.body.options === 'object') { 106 | Object.keys(PARAMETERS).forEach(attr => { 107 | if (Object.hasOwnProperty.call(req.body.options, attr)) { 108 | if (PARAMETERS[attr] === 'boolean') { 109 | options.options[attr] = req.body.options[attr] === 'true'; 110 | } 111 | else if (PARAMETERS[attr] === 'number') { 112 | options.options[attr] = parseFloat(req.body.options[attr]); 113 | } 114 | else if (Array.isArray(PARAMETERS[attr])) { 115 | if (PARAMETERS[attr].includes(req.body.options[attr])) { 116 | options.options[attr] = req.body.options[attr]; 117 | } 118 | else { 119 | req._adapter.log.warn(`Unknown value ${req.body.options[attr]} for attribute ${attr}. Allowed: ${PARAMETERS[attr].join(', ')}`); 120 | } 121 | } 122 | else if (PARAMETERS[attr] === 'string') { 123 | options.options[attr] = req.body.options[attr]; 124 | } 125 | } 126 | }); 127 | } 128 | req._adapter.getHistory(options.id, options.options, (error, result) => { 129 | if (error) { 130 | (0, common_1.errorResponse)(req, res, error?.toString()); 131 | return; 132 | } 133 | res.json(result); 134 | }); 135 | } 136 | else { 137 | res.status(500).json({ 138 | error: 'Instance is not alive', 139 | instance: req._adapter.config.dataSource, 140 | }); 141 | } 142 | } 143 | else { 144 | res.status(422).json({ error: 'No dataSource defined!' }); 145 | } 146 | } 147 | }); 148 | } 149 | function addHistoryByGet(req, res) { 150 | (0, common_1.checkPermissions)(req._adapter, req._user, [{ type: 'other', operation: 'sendto' }], async (error) => { 151 | if (error) { 152 | (0, common_1.errorResponse)(req, res, error); 153 | } 154 | else { 155 | if (req._adapter.config.dataSource) { 156 | // check if instance is alive 157 | const aliveState = await req._adapter.getForeignStateAsync(`system.adapter.${req._adapter.config.dataSource}.alive`); 158 | if (aliveState?.val) { 159 | const params = (0, common_1.parseUrl)(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX); 160 | req._adapter.log.debug(`Read data from: ${req._adapter.config.dataSource}`); 161 | const obj = await req._adapter.getForeignObjectAsync(params.stateId); 162 | if (obj?.common?.type && obj.type === 'state') { 163 | const state = { ts: Date.now() }; 164 | Object.keys(PARAMETERS_ADD).forEach(attr => { 165 | if (Object.hasOwnProperty.call(req.query, attr)) { 166 | if (PARAMETERS_ADD[attr] === 'boolean') { 167 | state[attr] = req.query[attr] === 'true'; 168 | } 169 | else if (PARAMETERS_ADD[attr] === 'number') { 170 | state[attr] = parseFloat(req.query[attr]); 171 | } 172 | else if (PARAMETERS_ADD[attr] === 'string') { 173 | state[attr] = req.query[attr]; 174 | } 175 | if (attr === 'val') { 176 | if (obj.common.type === 'boolean') { 177 | state.val = state.val === 'true' || state.val === '1'; 178 | } 179 | else if (obj.common.type === 'number') { 180 | state.val = parseFloat(state.val); 181 | } 182 | } 183 | } 184 | }); 185 | req._adapter.sendTo(req._adapter.config.dataSource, 'storeState', { id: params.stateId, state }, (result) => { 186 | if (typeof result === 'string' || result.error) { 187 | (0, common_1.errorResponse)(req, res, result.error || result); 188 | return; 189 | } 190 | // req._adapter.log.debug(`[QUERY] sendTo result = ${JSON.stringify(result)}`); 191 | res.json(result); 192 | }); 193 | } 194 | else { 195 | res.status(404).json({ error: 'State not found', stateId: params.stateId }); 196 | } 197 | } 198 | else { 199 | res.status(500).json({ 200 | error: 'Instance is not alive', 201 | instance: req._adapter.config.dataSource, 202 | }); 203 | } 204 | } 205 | else { 206 | res.status(422).json({ error: 'No dataSource defined!' }); 207 | } 208 | } 209 | }); 210 | } 211 | function addHistoryByPost(req, res) { 212 | (0, common_1.checkPermissions)(req._adapter, req._user, req.body.options, async (error) => { 213 | if (error) { 214 | (0, common_1.errorResponse)(req, res, error); 215 | } 216 | else { 217 | if (req._adapter.config.dataSource) { 218 | const state = await req._adapter.getForeignStateAsync(`system.adapter.${req._adapter.config.dataSource}.alive`); 219 | if (state?.val) { 220 | req._adapter.log.debug(`Read data from: ${req._adapter.config.dataSource}`); 221 | const options = { 222 | id: req.body.id, 223 | state: { ts: Date.now() }, 224 | }; 225 | if (req.body.state && typeof req.body.state === 'object') { 226 | Object.keys(PARAMETERS_ADD).forEach(attr => { 227 | if (Object.hasOwnProperty.call(req.body.state, attr)) { 228 | if (PARAMETERS_ADD[attr] === 'boolean') { 229 | options.state[attr] = req.body.state[attr] === 'true'; 230 | } 231 | else if (PARAMETERS_ADD[attr] === 'number') { 232 | options.state[attr] = parseFloat(req.body.state[attr]); 233 | } 234 | else if (PARAMETERS_ADD[attr] === 'string') { 235 | options.state[attr] = req.body.state[attr]; 236 | } 237 | } 238 | }); 239 | } 240 | req._adapter.sendTo(req._adapter.config.dataSource, 'storeState', options, (result) => { 241 | if (typeof result === 'string' || result.error) { 242 | (0, common_1.errorResponse)(req, res, result.error || result); 243 | return; 244 | } 245 | // req._adapter.log.debug(`[QUERY] sendTo result = ${JSON.stringify(result)}`); 246 | res.json(result); 247 | }); 248 | } 249 | else { 250 | res.status(500).json({ 251 | error: 'Instance is not alive', 252 | instance: req._adapter.config.dataSource, 253 | }); 254 | } 255 | } 256 | else { 257 | res.status(422).json({ error: 'No dataSource defined!' }); 258 | } 259 | } 260 | }); 261 | } 262 | //# sourceMappingURL=history.js.map -------------------------------------------------------------------------------- /dist/lib/api/controllers/history.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"history.js","sourceRoot":"","sources":["../../../../src/lib/api/controllers/history.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/lib/api/controllers/sendTo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.sendToPost = void 0; 4 | exports.sendTo = sendTo; 5 | const common_1 = require("./common"); 6 | function sendTo(req, res) { 7 | (0, common_1.checkPermissions)(req._adapter, req._user, [{ type: 'other', operation: 'sendto' }], async (error) => { 8 | if (error) { 9 | (0, common_1.errorResponse)(req, res, error); 10 | } 11 | else { 12 | const params = (0, common_1.parseUrl)(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX); 13 | let message = req.query.message; 14 | let noResponseStr = req.query.noResponse; 15 | let timeout = req.query.timeout; 16 | let data = req.query.data; 17 | if (req.body?.message) { 18 | message = req.body.message; 19 | } 20 | if (req.body?.timeout) { 21 | timeout = req.body.timeout; 22 | } 23 | timeout = parseInt(timeout, 10) || 10000; 24 | if (req.body?.noResponse !== undefined) { 25 | noResponseStr = req.body.noResponse; 26 | } 27 | const noResponse = noResponseStr === 'true'; 28 | if (req.body?.data !== undefined) { 29 | data = req.body.data; 30 | } 31 | else { 32 | if (data !== undefined && data !== null) { 33 | if (data === 'null') { 34 | data = null; 35 | } 36 | else if (data === 'undefined') { 37 | data = undefined; 38 | } 39 | else if (data === 'true') { 40 | data = true; 41 | } 42 | else if (data === 'false') { 43 | data = false; 44 | } 45 | else if (isFinite(data)) { 46 | data = parseFloat(data); 47 | } 48 | else if (data.startsWith('{') && data.endsWith('}')) { 49 | try { 50 | data = JSON.parse(data); 51 | } 52 | catch { 53 | // ignore 54 | } 55 | } 56 | else if (data.startsWith('[') && data.endsWith(']')) { 57 | try { 58 | data = JSON.parse(data); 59 | } 60 | catch { 61 | // ignore 62 | } 63 | } 64 | } 65 | } 66 | const instance = params.instance; 67 | if (!instance) { 68 | res.status(422).json({ error: 'No instance provided' }); 69 | return; 70 | } 71 | if (!message) { 72 | res.status(422).json({ error: 'No message provided' }); 73 | return; 74 | } 75 | // check if instance is alive 76 | let state; 77 | try { 78 | state = await req._adapter.getForeignStateAsync(`system.adapter.${instance}.alive`); 79 | if (!state || !state.val) { 80 | res.status(500).json({ error: 'instance is not online', instance }); 81 | return; 82 | } 83 | } 84 | catch { 85 | res.status(500).json({ error: 'invalid instance', instance }); 86 | return; 87 | } 88 | if (noResponse) { 89 | req._adapter.sendTo(instance, message, data); 90 | res.json({ result: 'sent' }); 91 | } 92 | else { 93 | let timer = null; 94 | let answerDone = false; 95 | if (timeout) { 96 | timer = req._adapter.setTimeout(() => { 97 | timer = null; 98 | if (!answerDone) { 99 | answerDone = true; 100 | res.status(408).json({ error: 'timeout' }); 101 | } 102 | }, timeout); 103 | } 104 | req._adapter.sendTo(instance, message, data, (result) => { 105 | if (timer) { 106 | req._adapter.clearTimeout(timer); 107 | } 108 | if (!answerDone) { 109 | answerDone = true; 110 | res.json(result); 111 | } 112 | }); 113 | } 114 | } 115 | }); 116 | } 117 | exports.sendToPost = sendTo; 118 | //# sourceMappingURL=sendTo.js.map -------------------------------------------------------------------------------- /dist/lib/api/controllers/sendTo.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"sendTo.js","sourceRoot":"","sources":["../../../../src/lib/api/controllers/sendTo.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/lib/common.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.DEFAULT_VALUES = void 0; 4 | exports.getParamNames = getParamNames; 5 | // taken from here: https://stackoverflow.com/questions/1007981/how-to-get-function-parameter-names-values-dynamically 6 | const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm; 7 | const ARGUMENT_NAMES = /([^\s,]+)/g; 8 | function getParamNames(func) { 9 | const fnStr = func.toString().replace(STRIP_COMMENTS, ''); 10 | const result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); 11 | return result || []; 12 | } 13 | exports.DEFAULT_VALUES = { 14 | log: { level: 'info' }, 15 | }; 16 | //# sourceMappingURL=common.js.map -------------------------------------------------------------------------------- /dist/lib/common.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/lib/common.ts"],"names":[],"mappings":";;;AAGA,sCAIC;AAPD,sHAAsH;AACtH,MAAM,cAAc,GAAG,kCAAkC,CAAC;AAC1D,MAAM,cAAc,GAAG,YAAY,CAAC;AACpC,SAAgB,aAAa,CAAC,IAAS;IACnC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC7F,OAAO,MAAM,IAAI,EAAE,CAAC;AACxB,CAAC;AAEY,QAAA,cAAc,GAAiD;IACxE,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;CACzB,CAAC"} -------------------------------------------------------------------------------- /dist/lib/config/default.yaml: -------------------------------------------------------------------------------- 1 | # swagger configuration file 2 | 3 | # values in the swagger hash are system configuration for swagger-node 4 | swagger: 5 | 6 | fittingsDirs: [ api/fittings ] 7 | defaultPipe: null 8 | swaggerControllerPipe: swagger_controllers # defines the standard processing pipe for controllers 9 | 10 | # values defined in the bagpipes key are the bagpipes pipes and fittings definitions 11 | # (see https://github.com/apigee-127/bagpipes) 12 | bagpipes: 13 | 14 | _router: 15 | name: swagger_router 16 | mockMode: false 17 | mockControllersDirs: [ api/mocks ] 18 | controllersDirs: [ api/controllers ] 19 | 20 | _swagger_validate: 21 | name: swagger_validator 22 | validateResponse: true 23 | 24 | #_swagger_security: 25 | # name: swagger_security 26 | # securityHandlersModule: api/helpers/auth.mw 27 | 28 | # pipe for all swagger-node controllers 29 | swagger_controllers: 30 | - onError: json_error_handler 31 | - cors 32 | #- swagger_security 33 | - _swagger_validate 34 | - express_compatibility 35 | - _router 36 | 37 | # pipe to serve swagger (endpoint is in swagger.yaml) 38 | swagger_raw: 39 | name: swagger_raw 40 | 41 | # any other values in this file are just loaded into the config for application access... 42 | -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const adapter_core_1 = require("@iobroker/adapter-core"); // Get common adapter utils 7 | const webserver_1 = require("@iobroker/webserver"); 8 | const body_parser_1 = __importDefault(require("body-parser")); 9 | const cookie_parser_1 = __importDefault(require("cookie-parser")); 10 | const express_1 = __importDefault(require("express")); 11 | const rest_api_1 = __importDefault(require("./lib/rest-api")); 12 | class RestApiAdapter extends adapter_core_1.Adapter { 13 | webServer; 14 | WEB_EXTENSION_PREFIX = ''; 15 | _addTimeout = null; 16 | constructor(options = {}) { 17 | super({ 18 | ...options, 19 | name: 'rest-api', 20 | stateChange: (id, state) => this.webServer?.api?.stateChange(id, state), 21 | objectChange: (id, obj) => this.webServer?.api?.objectChange(id, obj), 22 | unload: callback => this.onUnload(callback), 23 | ready: () => this.main(), 24 | }); 25 | this.webServer = { 26 | app: null, 27 | server: null, 28 | api: null, 29 | }; 30 | } 31 | onUnload(callback) { 32 | try { 33 | void this.setState('info.connection', false, true); 34 | this.log.info(`terminating http${this.config.secure ? 's' : ''} server on port ${this.config.port}`); 35 | if (this.webServer?.api) { 36 | void this.webServer.api.unload().then(() => { 37 | try { 38 | if (this.webServer.server) { 39 | this.webServer.server.close(); 40 | this.webServer.server = null; 41 | } 42 | } 43 | catch (error) { 44 | // ignore 45 | console.error(`Cannot close server: ${error}`); 46 | } 47 | callback(); 48 | }); 49 | } 50 | else { 51 | if (this.webServer?.server) { 52 | this.webServer.server.close(); 53 | this.webServer.server = null; 54 | } 55 | callback(); 56 | } 57 | } 58 | catch (error) { 59 | console.error(`Cannot close server: ${error}`); 60 | callback(); 61 | } 62 | } 63 | async initWebServer() { 64 | this.config.port = parseInt(this.config.port, 10); 65 | this.webServer.app = (0, express_1.default)(); 66 | this.webServer.api = new rest_api_1.default(this.webServer.server, this.config, this, null, this.webServer.app); 67 | if (this.config.port) { 68 | if (this.config.secure && !this.config.certificates) { 69 | return; 70 | } 71 | try { 72 | const webserver = new webserver_1.WebServer({ 73 | app: this.webServer.app, 74 | adapter: this, 75 | secure: this.config.secure, 76 | }); 77 | this.webServer.server = await webserver.init(); 78 | if (this.config.auth) { 79 | // Install OAuth2 handler 80 | this.webServer.app.use((0, cookie_parser_1.default)()); 81 | this.webServer.app.use(body_parser_1.default.urlencoded({ extended: true })); 82 | this.webServer.app.use(body_parser_1.default.json()); 83 | (0, webserver_1.createOAuth2Server)(this, { 84 | app: this.webServer.app, 85 | secure: this.config.secure, 86 | accessLifetime: parseInt(this.config.ttl, 10) || 3600, 87 | }); 88 | } 89 | } 90 | catch (err) { 91 | this.log.error(`Cannot create webserver: ${err}`); 92 | this.terminate ? this.terminate(1) : process.exit(1); 93 | return; 94 | } 95 | } 96 | else { 97 | this.log.error('port missing'); 98 | process.exit(1); 99 | } 100 | if (this.webServer.server) { 101 | let serverListening = false; 102 | let serverPort = this.config.port; 103 | this.webServer.server.on('error', e => { 104 | if (e.toString().includes('EACCES') && serverPort <= 1024) { 105 | this.log.error(`node.js process has no rights to start server on the port ${serverPort}.\n` + 106 | `Do you know that on linux you need special permissions for ports under 1024?\n` + 107 | `You can call in shell following scrip to allow it for node.js: "iobroker fix"`); 108 | } 109 | else { 110 | this.log.error(`Cannot start server on ${this.config.bind || '0.0.0.0'}:${serverPort}: ${e}`); 111 | } 112 | if (!serverListening) { 113 | this.terminate ? this.terminate(1) : process.exit(1); 114 | } 115 | }); 116 | this.getPort(this.config.port, !this.config.bind || this.config.bind === '0.0.0.0' ? undefined : this.config.bind || undefined, port => { 117 | if (port !== this.config.port) { 118 | this.log.error(`port ${this.config.port} already in use`); 119 | process.exit(1); 120 | } 121 | serverPort = port; 122 | this.webServer.server.listen(port, !this.config.bind || this.config.bind === '0.0.0.0' ? undefined : this.config.bind || undefined, async () => { 123 | await this.setStateAsync('info.connection', true, true); 124 | this.log.info(`http${this.config.secure ? 's' : ''} server listening on port ${port}`); 125 | serverListening = true; 126 | }); 127 | }); 128 | } 129 | } 130 | main() { 131 | if (this.config.webInstance) { 132 | console.log('Adapter runs as a part of web service'); 133 | this.log.warn('Adapter runs as a part of web service'); 134 | this.setForeignState(`system.adapter.${this.namespace}.alive`, false, true, () => setTimeout(() => process.exit(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION), 1000)); 135 | } 136 | else { 137 | if (this.config.secure) { 138 | // Load certificates 139 | this.getCertificates(undefined, undefined, undefined, (err, certificates, leConfig) => { 140 | this.config.certificates = certificates; 141 | this.config.leConfig = leConfig; 142 | void this.initWebServer(); 143 | }); 144 | } 145 | else { 146 | void this.initWebServer(); 147 | } 148 | } 149 | } 150 | } 151 | if (require.main !== module) { 152 | // Export the constructor in compact mode 153 | module.exports = (options) => new RestApiAdapter(options); 154 | } 155 | else { 156 | // otherwise start the instance directly 157 | (() => new RestApiAdapter())(); 158 | } 159 | //# sourceMappingURL=main.js.map -------------------------------------------------------------------------------- /dist/main.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import config from '@iobroker/eslint-config'; 2 | 3 | export default [ 4 | ...config, 5 | { 6 | languageOptions: { 7 | parserOptions: { 8 | allowDefaultProject: { 9 | allow: ['*.js', '*.mjs'], 10 | }, 11 | tsconfigRootDir: import.meta.dirname, 12 | project: './tsconfig.json', 13 | }, 14 | }, 15 | }, 16 | { 17 | ignores: [ 18 | 'dist/*', 19 | 'test/**/*', 20 | 'eslint.config.mjs', 21 | 'prettier.config.mjs', 22 | 'tasks.js', 23 | 'examples/**/*', 24 | ], 25 | }, 26 | { 27 | // disable temporary the rule 'jsdoc/require-param' and enable 'jsdoc/require-jsdoc' 28 | rules: { 29 | 'jsdoc/require-jsdoc': 'off', 30 | 'jsdoc/require-param': 'off', 31 | }, 32 | }, 33 | ]; 34 | -------------------------------------------------------------------------------- /examples/demoBrowserClient.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

This is a client example of how to work with long polling and REST-API.

7 | 8 |
9 |
10 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /examples/demoNodeClient.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const axios = require('axios'); 4 | 5 | const IO_BROKER_SWAGGER_URL = 'http://localhost:8093/'; 6 | const OWN_PORT = 5000; 7 | const OWN_URL = `http://localhost:${OWN_PORT}/`; 8 | const STATE_ID = 'system.adapter.admin.0.memHeapTotal'; // subscribe on this state 9 | let connectInterval = null; 10 | 11 | function processAxiosError(error) { 12 | if (error.response) { 13 | console.log( 14 | `Request error: ${error.response.data ? JSON.stringify(error.response.data, null, 2) : error.response.status}`, 15 | ); 16 | } else { 17 | console.log(`Request error: ${error.message}`); 18 | } 19 | } 20 | 21 | async function initSubscribe() { 22 | try { 23 | const url = `${IO_BROKER_SWAGGER_URL}v1/state/${STATE_ID}/subscribe`; 24 | const response = await axios.post(url, { url: `${OWN_URL}api/updates` }); 25 | console.log(`Subscribed on changes of ${STATE_ID}: ${JSON.stringify(response.data)}`); 26 | connectInterval && clearInterval(connectInterval); 27 | connectInterval = null; 28 | } catch (error) { 29 | processAxiosError(error); 30 | } 31 | } 32 | 33 | function startConnect(immediate) { 34 | if (!connectInterval) { 35 | connectInterval = setInterval(async () => initSubscribe(), 5000); 36 | immediate && initSubscribe().then(() => {}); 37 | } 38 | } 39 | 40 | const app = express(); 41 | app.use(bodyParser.json()); 42 | app.post('/api/updates', async (req, res) => { 43 | // If ping, just answer with status 200 44 | if (req.body.test) { 45 | res.status(200).json({}); 46 | } else if (req.body.disconnect) { 47 | console.log('ioBroker is down. Reconnect...'); 48 | // ioBroker is down and all subscribes must be sent anew 49 | try { 50 | res.status(200).json({}); 51 | } catch (error) { 52 | // ignore 53 | } 54 | 55 | startConnect(); 56 | } else if (req.body.id) { 57 | if (req.body.obj) { 58 | console.log(`Object "${req.body.id}" changed: ${JSON.stringify(req.body.obj)}`); 59 | res.status(200).json({}); 60 | } else if (req.body.state) { 61 | console.log(`State "${req.body.id}" changed: ${JSON.stringify(req.body.state)}`); 62 | res.status(200).json({}); 63 | } else { 64 | res.status(200).json({}); 65 | // unsubscribe from state 66 | setTimeout(async () => { 67 | try { 68 | // object or state was deleted 69 | await axios.delete(`${IO_BROKER_SWAGGER_URL}v1/api/object/${id}`, { url: OWN_URL }); 70 | } catch (error) { 71 | processAxiosError(error); 72 | } 73 | }, 300); 74 | } 75 | } else { 76 | res.status(422).json({ error: 'Cannot parse data' }); 77 | } 78 | }); 79 | 80 | app.listen(OWN_PORT, async () => { 81 | console.log(`HTTP Server started on port ${OWN_PORT}`); 82 | startConnect(true); 83 | }); 84 | -------------------------------------------------------------------------------- /img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioBroker/ioBroker.rest-api/9546b5b8a189bfe8380f8868fbf21a96060c99f8/img/favicon.ico -------------------------------------------------------------------------------- /img/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioBroker/ioBroker.rest-api/9546b5b8a189bfe8380f8868fbf21a96060c99f8/img/screen.png -------------------------------------------------------------------------------- /io-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "name": "rest-api", 4 | "version": "3.0.1", 5 | "news": { 6 | "3.0.1": { 7 | "en": "Corrected the web extension", 8 | "de": "Korrektur der Web-Erweiterung", 9 | "ru": "Исправлено расширение сайта", 10 | "pt": "Corrigido a extensão web", 11 | "nl": "De webextensie gecorrigeerd", 12 | "fr": "Correction de l'extension web", 13 | "it": "Corretto l'estensione web", 14 | "es": "Corregido la extensión web", 15 | "pl": "Poprawiono rozszerzenie sieci Web", 16 | "uk": "Виправлено розширення веб", 17 | "zh-cn": "更正网络扩展名" 18 | }, 19 | "3.0.0": { 20 | "en": "Rewritten in TypeScript\nRemoved binary states", 21 | "de": "Neu geschrieben in TypeScript\nEntfernte binäre Zustände", 22 | "ru": "Переписано в TypeScript\nУдаленные бинарные состояния", 23 | "pt": "Reescrito em TypeScript\nEstados binários removidos", 24 | "nl": "Herschreven in TypeScript\nBinaire toestanden verwijderd", 25 | "fr": "Réécrit dans TypeScript\nÉtats binaires supprimés", 26 | "it": "Rescritto in TypeScript\nStati binari rimossi", 27 | "es": "Reescrito en TipoScript\nEstados binarios eliminados", 28 | "pl": "Przepisane w TypeScript\nUsunięte stany binarne", 29 | "uk": "Записатися в TypeScript\nВилучені бінарні штати", 30 | "zh-cn": "在类型脚本中重写\n删除二进制状态" 31 | }, 32 | "2.0.3": { 33 | "en": "Changed response for the endpoint get states to the dictionary in swagger", 34 | "de": "Geänderte Antwort für den Endpunkt erhalten Zustände zum Wörterbuch in Swagger", 35 | "ru": "Измененный ответ для конечной точки получить состояния в словарь в swagger", 36 | "pt": "Resposta alterada para o endpoint obter estados para o dicionário em swagger", 37 | "nl": "Veranderde respons voor het eindpunt krijgt toestanden naar het woordenboek in swagger", 38 | "fr": "Réponse modifiée pour le paramètre obtenir des états au dictionnaire dans swagger", 39 | "it": "Cambiata risposta per il punto finale ottenere stati al dizionario in swagger", 40 | "es": "Cambio de respuesta para el punto final conseguir estados al diccionario en swagger", 41 | "pl": "Zmieniona odpowiedź na punkt końcowy uzyskać stany do słownika w swagger", 42 | "uk": "Змінена відповідь на кінцеву точку, яка отримує штати словника в лебідці", 43 | "zh-cn": "对端点的更改响应以 swagger 方式将状态变为字典" 44 | }, 45 | "2.0.1": { 46 | "en": "ported to `@iobroker/webserver`\nFixed history requests\nMinimum required node.js version is 16", 47 | "de": "an `@iobroker/webserver `\nGeschichtsanfragen behoben\nMinimum erforderlich node.js Version ist 16", 48 | "ru": "в порту `@iobroker/webserver \"\nИсправленные просьбы об истории\nМинимальная требуемая версия node.js - 16", 49 | "pt": "portado para `@iobroker/webserver \"\nPedidos de histórico fixo\nA versão mínima necessária do node.js é 16", 50 | "nl": "geporteerd naar .@iobroker/webserver wat\nVaste historische verzoeken\nMinimum vereiste node.js versie is 16", 51 | "fr": "porté à `@iobroker/webserver \"\nDemande d'historique fixe\nLa version minimum requise node.js est 16", 52 | "it": "inviato a `@iobroker/webserver #\nRisolte richieste di storia\nVersione minima richiesta node.js è 16", 53 | "es": "portada a `@iobroker/webserver `\nSolicitudes de historia fija\nLa versión mínima requerida node.js es 16", 54 | "pl": "wysłany do '@ iobroker / webserver'\nPoprawione żądania dotyczące historii\nMinimalna wymagana node.js wersja jest 16", 55 | "uk": "сайт: www.iobroker.com й\nВиправлені запити історії\nМінімальний необхідний вузол.js версія 16", 56 | "zh-cn": "移植到 xqio 经纪人/网络服务器 `\n固定历史请求\n最低要求的节点.js版本为16" 57 | }, 58 | "1.1.0": { 59 | "en": "Converting of the setState values to the according type\nImplemented file operations", 60 | "de": "Umrechnung der setState-Werte in den entsprechenden Typ\nImplementierung von Dateioperationen", 61 | "ru": "Преобразование значений setState в согласно типу\nРеализованные файловые операции", 62 | "pt": "Convertendo dos valores setState para o tipo de acordo\nOperações de arquivo implementadas", 63 | "nl": "Omkeren van de setstate waarden naar het type\nGeïmplementeerde bestand operaties", 64 | "fr": "Conversion des valeurs déterminées de l'état dans le type suivant\nOpérations de fichiers mises en œuvre", 65 | "it": "Convertizione dei valori setState secondo il tipo\nOperazioni di file implementate", 66 | "es": "Convertir los valores establecidos en el tipo\nOperaciones de archivo aplicadas", 67 | "pl": "Przetłumaczył wartości zbioru zgodnie z typem\nImplementacja", 68 | "uk": "Перетворення значень setState до за типом\nРеалізовані операції файлів", 69 | "zh-cn": "避免按类型划分国家数值\n执行档案业务" 70 | }, 71 | "1.0.5": { 72 | "en": "Prepare for future js-controller versions", 73 | "de": "Bereiten Sie sich auf zukünftige js-Controller-Versionen", 74 | "ru": "Подготовьтесь к будущим версиям js-controller", 75 | "pt": "Prepare-se para futuras versões js-controller", 76 | "nl": "Bereid je voor op toekomstige Js-controller versie", 77 | "fr": "Préparez-vous pour les versions futures de js-controller", 78 | "it": "Prepararsi per le future versioni js-controller", 79 | "es": "Prepararse para futuras versiones js-controller", 80 | "pl": "Prepare for future js-controller version", 81 | "uk": "Підготовка до майбутніх версій js-controller", 82 | "zh-cn": "今后防污版本的编制" 83 | }, 84 | "1.0.4": { 85 | "en": "Check if the port is occupied only on defined interface", 86 | "de": "Überprüfen Sie, ob der Port nur auf definierter Schnittstelle besetzt ist", 87 | "ru": "Проверьте, занят ли порт только на определенном интерфейсе", 88 | "pt": "Verifique se a porta está ocupada apenas na interface definida", 89 | "nl": "Controleer of de haven alleen bezet is op definitieve interface", 90 | "fr": "Vérifiez si le port n'est occupé que sur l'interface définie", 91 | "it": "Controllare se la porta è occupata solo su interfaccia definita", 92 | "es": "Compruebe si el puerto está ocupado sólo en la interfaz definida", 93 | "pl": "Jeśli port jest zajęty tylko na określonym interfejsie", 94 | "uk": "Перевірте, чи порт зайнятий лише на визначеному інтерфейсі", 95 | "zh-cn": "如果港口只在界定的界线上被占有,则该港口将被扣押。" 96 | } 97 | }, 98 | "titleLang": { 99 | "en": "REST API", 100 | "de": "REST API", 101 | "ru": "REST API", 102 | "pt": "REST API", 103 | "nl": "REST API", 104 | "fr": "REST API", 105 | "it": "REST API", 106 | "es": "REST API", 107 | "pl": "REST API", 108 | "uk": "REST API", 109 | "zh-cn": "REST API" 110 | }, 111 | "desc": { 112 | "en": "This adapter allows to read and write ioBroker objects and state with web RESTful API and Swagger UI", 113 | "de": "Dieser Adapter erlaubt die ioBroker-Objekte mit Web RESTful API und Swagger UI zu lesen und zu schreiben", 114 | "ru": "Этот адаптер позволяет читать и записывать объекты ioBroker и контактировать с RESTful API и Swagger UI", 115 | "pt": "Esse adaptador permite ler e gravar objetos ioBroker e declarar com a API RESTful da Web e a interface do usuário do Swagger", 116 | "nl": "Deze adapter maakt het mogelijk om ioBroker-objecten te lezen en te schrijven met de web RESTful API en Swagger UI", 117 | "fr": "Cet adaptateur permet de lire et d'écrire des objets et des états ioBroker avec l'API Web RESTful et l'interface utilisateur Swagger.", 118 | "it": "Questo adattatore consente di leggere e scrivere oggetti ioBroker e lo stato con l'API RESTful Web e l'interfaccia utente di Swagger", 119 | "es": "Este adaptador permite leer y escribir objetos y estados ioBroker con la API RESTful web y la interfaz de usuario Swagger", 120 | "pl": "Ten adapter pozwala na odczyt i zapis obiektów ioBroker oraz ich stan za pomocą web RESTful API i Swagger UI", 121 | "uk": "Цей адаптер дозволяє читати та записувати об'єкти ioBroker та стан з веб RESTful API та Swagger UI", 122 | "zh-cn": "该适配器允许使用Web RESTful API和Swagger UI读写ioBroker对象和状态" 123 | }, 124 | "authors": [ 125 | "bluefox " 126 | ], 127 | "platform": "Javascript/Node.js", 128 | "mode": "daemon", 129 | "connectionType": "local", 130 | "dataSource": "push", 131 | "loglevel": "info", 132 | "icon": "rest-api.svg", 133 | "compact": true, 134 | "webExtension": "dist/lib/rest-api.js", 135 | "readme": "https://github.com/ioBroker/ioBroker.rest-api/blob/master/README.md", 136 | "keywords": [ 137 | "web", 138 | "rest-api", 139 | "RESTful", 140 | "API", 141 | "communication" 142 | ], 143 | "enabled": true, 144 | "extIcon": "https://raw.githubusercontent.com/ioBroker/ioBroker.rest-api/master/admin/rest-api.svg", 145 | "type": "communication", 146 | "stopBeforeUpdate": true, 147 | "localLinks": { 148 | "_default": { 149 | "link": "%protocol%://%ip%:%port%/", 150 | "intro": true 151 | } 152 | }, 153 | "adminUI": { 154 | "config": "json" 155 | }, 156 | "licenseInformation": { 157 | "type": "free", 158 | "license": "Apache-2.0" 159 | }, 160 | "tier": 3, 161 | "globalDependencies": [ 162 | { 163 | "admin": ">=7.4.10" 164 | } 165 | ], 166 | "dependencies": [ 167 | { 168 | "js-controller": ">=5.0.19" 169 | } 170 | ], 171 | "plugins": { 172 | "sentry": { 173 | "dsn": "https://250dffd65cee420e837563860d2b403f@sentry.iobroker.net/189" 174 | } 175 | } 176 | }, 177 | "native": { 178 | "port": 8093, 179 | "auth": false, 180 | "secure": false, 181 | "bind": "0.0.0.0", 182 | "certPublic": "", 183 | "certPrivate": "", 184 | "certChained": "", 185 | "defaultUser": "admin", 186 | "ttl": 3600, 187 | "onlyAllowWhenUserIsOwner": false, 188 | "webInstance": "", 189 | "leEnabled": false, 190 | "leUpdate": false, 191 | "leCheckPort": 80, 192 | "checkInterval": 30000, 193 | "hookTimeout": 3000, 194 | "noUI": false, 195 | "dataSource": "", 196 | "noCommands": false, 197 | "noAdminCommands": false 198 | }, 199 | "instanceObjects": [ 200 | { 201 | "_id": "info", 202 | "type": "channel", 203 | "common": { 204 | "name": "Information" 205 | }, 206 | "native": {} 207 | }, 208 | { 209 | "_id": "info.connection", 210 | "type": "state", 211 | "common": { 212 | "role": "indicator.connected", 213 | "name": "If server started", 214 | "type": "boolean", 215 | "read": true, 216 | "write": false, 217 | "def": false 218 | }, 219 | "native": {} 220 | }, 221 | { 222 | "_id": "info.extension", 223 | "type": "state", 224 | "common": { 225 | "role": "indicator", 226 | "name": "If instance is in only extension mode", 227 | "type": "boolean", 228 | "read": true, 229 | "write": false, 230 | "def": false 231 | }, 232 | "native": {} 233 | } 234 | ] 235 | } 236 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iobroker.rest-api", 3 | "version": "3.0.1", 4 | "description": "RESTful interface for ioBroker with GUI.", 5 | "author": { 6 | "name": "bluefox", 7 | "email": "dogafox@gmail.com" 8 | }, 9 | "contributors": [], 10 | "homepage": "https://github.com/ioBroker/ioBroker.rest-api", 11 | "keywords": [ 12 | "ioBroker", 13 | "rest-api", 14 | "swagger-ui", 15 | "web" 16 | ], 17 | "engines": { 18 | "node": ">=18" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/ioBroker/ioBroker.rest-api" 23 | }, 24 | "dependencies": { 25 | "@iobroker/adapter-core": "^3.2.3", 26 | "@iobroker/socket-classes": "^2.2.18", 27 | "@iobroker/webserver": "^1.2.8", 28 | "@iobroker/ws-server": "^4.3.9", 29 | "@iobroker/js-controller-common-db": "^7.0.7", 30 | "axios": "^1.9.0", 31 | "body-parser": "^2.2.0", 32 | "cookie-parser": "^1.4.7", 33 | "cors": "^2.8.5", 34 | "express": "^4.21.2", 35 | "multer": "^1.4.5-lts.2", 36 | "swagger-node-runner-fork": "^0.8.0", 37 | "swagger-ui-express": "^5.0.1", 38 | "yamljs": "^0.3.0" 39 | }, 40 | "devDependencies": { 41 | "@alcalzone/release-script": "^3.8.0", 42 | "@alcalzone/release-script-plugin-iobroker": "^3.7.2", 43 | "@alcalzone/release-script-plugin-license": "^3.7.0", 44 | "@iobroker/adapter-dev": "^1.4.0", 45 | "@iobroker/eslint-config": "^2.0.2", 46 | "@iobroker/testing": "^5.0.4", 47 | "@iobroker/types": "^7.0.7", 48 | "@types/body-parser": "^1.19.5", 49 | "@types/cookie-parser": "^1.4.8", 50 | "@types/cors": "^2.8.18", 51 | "@types/express": "^4.17.22", 52 | "@types/multer": "^1.4.12", 53 | "@types/node": "^22.15.29", 54 | "@types/swagger-node-runner": "^0.6.6", 55 | "@types/swagger-ui-express": "^4.1.8", 56 | "@types/yamljs": "^0.2.34", 57 | "typescript": "^5.8.3" 58 | }, 59 | "bugs": { 60 | "url": "https://github.com/ioBroker/ioBroker.rest-api/issues" 61 | }, 62 | "main": "dist/main.js", 63 | "files": [ 64 | "admin/", 65 | "img/", 66 | "dist/", 67 | "examples/", 68 | "LICENSE", 69 | "io-package.json" 70 | ], 71 | "scripts": { 72 | "build": "npm run build:ts && node tasks --copy-yaml", 73 | "build:all": "npm run build:ts && node tasks", 74 | "build:ts": "tsc -p tsconfig.build.json", 75 | "update-list": "node tasks --generate-list", 76 | "test": "mocha --exit", 77 | "release": "release-script", 78 | "release-patch": "release-script patch --yes --no-update-lockfile", 79 | "release-minor": "release-script minor --yes --no-update-lockfile", 80 | "release-major": "release-script major --yes --no-update-lockfile", 81 | "translate": "translate-adapter", 82 | "update-packages": "npx -y npm-check-updates --upgrade", 83 | "npm": "npm i", 84 | "lint": "eslint -c eslint.config.mjs" 85 | }, 86 | "license": "Apache-2.0" 87 | } 88 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | import prettierConfig from '@iobroker/eslint-config/prettier.config.mjs'; 2 | 3 | export default prettierConfig; 4 | -------------------------------------------------------------------------------- /src/lib/api/controllers/common.ts: -------------------------------------------------------------------------------- 1 | import type { RequestExt, RestApiAdapter, Swagger, UserName } from '../../types'; 2 | import type { CommandsPermissionsEntry } from '@iobroker/types/build/types'; 3 | import type { Response } from 'express'; 4 | 5 | const ERROR_PERMISSION = 'permissionError'; 6 | 7 | export function checkPermissions( 8 | adapter: RestApiAdapter, 9 | user: UserName, 10 | requiredRights: CommandsPermissionsEntry[], 11 | callback: (error: string | null) => void, 12 | ): void { 13 | void adapter.calculatePermissions(user, requiredRights, (acl: ioBroker.PermissionSet): void => { 14 | if (user !== 'system.user.admin') { 15 | // type: file, object, state, other 16 | // operation: create, read, write, list, delete, sendto, execute, sendto 17 | if (requiredRights?.[0] && acl) { 18 | // If permission required 19 | if (requiredRights[0].type) { 20 | const aclType = acl[requiredRights[0].type]; 21 | if (aclType && (aclType as any)[requiredRights[0].operation]) { 22 | callback(null); 23 | return; 24 | } 25 | } else { 26 | callback(null); 27 | return; 28 | } 29 | } 30 | 31 | adapter.log.warn(`No permission for "${user}" to call ${JSON.stringify(requiredRights)}`); 32 | 33 | callback(ERROR_PERMISSION); 34 | } else { 35 | callback(null); 36 | } 37 | }); 38 | } 39 | 40 | export function findState( 41 | adapter: RestApiAdapter, 42 | idOrName: string, 43 | user: UserName, 44 | type: ioBroker.CommonType | ((err?: Error | null, id?: string, name?: ioBroker.StringOrTranslated) => void) | null, 45 | callback?: (err?: Error | null, id?: string, name?: ioBroker.StringOrTranslated) => void, 46 | ): void { 47 | if (typeof type === 'function') { 48 | callback = type; 49 | type = null; 50 | } 51 | adapter.findForeignObject( 52 | idOrName, 53 | type, 54 | // @ts-expect-error fixed in js-controller 55 | { user, checked: true, limitToOwnerRights: adapter.config.onlyAllowWhenUserIsOwner }, 56 | callback, 57 | ); 58 | } 59 | 60 | export function getState( 61 | adapter: RestApiAdapter, 62 | idOrName: string, 63 | user: UserName, 64 | type: 65 | | ioBroker.CommonType 66 | | (( 67 | err: Error | null | undefined, 68 | state: ioBroker.State | undefined, 69 | id: string | null | undefined, 70 | originId: ioBroker.StringOrTranslated | undefined, 71 | ) => void) 72 | | null, 73 | callback?: ( 74 | err: Error | null | undefined, 75 | state: ioBroker.State | undefined, 76 | id: string | null | undefined, 77 | originId: ioBroker.StringOrTranslated | undefined, 78 | ) => void, 79 | ): void { 80 | if (typeof type === 'function') { 81 | callback = type; 82 | type = null; 83 | } 84 | findState(adapter, idOrName, user, type, (err, id, originId) => { 85 | if (err && (!err.message || !err.message.includes('permissionError'))) { 86 | callback?.(err, undefined, null, originId); 87 | } else { 88 | if (err?.message.includes('permissionError')) { 89 | // assume it is ID 90 | id = idOrName; 91 | } 92 | if (id) { 93 | void adapter.getForeignState( 94 | id, 95 | { user, limitToOwnerRights: adapter.config.onlyAllowWhenUserIsOwner }, 96 | (err, state) => { 97 | if (err || !state) { 98 | state = undefined; 99 | } 100 | callback?.(err, state, id, originId); 101 | }, 102 | ); 103 | } else { 104 | callback?.(null, undefined, null, originId); 105 | } 106 | } 107 | }); 108 | } 109 | 110 | export function parseUrl = Record>( 111 | url: string, 112 | swagger: Swagger, 113 | webExtensionPrefix: string, 114 | ): T { 115 | // "/v1/object/adapter.system.admin.0.alive" 116 | const parts = url.split('?')[0].split('/'); 117 | if (parts[1] === webExtensionPrefix) { 118 | parts.shift(); // / 119 | parts.shift(); // remove swagger 120 | parts.shift(); // remove v1 121 | parts.shift(); // remove objects or states 122 | } else { 123 | parts.shift(); // / 124 | parts.shift(); // remove v1 125 | parts.shift(); // remove objects or states 126 | } 127 | const result: T = {} as T; 128 | if (swagger?.operation?.parameters) { 129 | let i = 0; 130 | swagger.operation.parameters.forEach((param: { in: string; name: string }): void => { 131 | if (param.in === 'path') { 132 | try { 133 | (result as any)[param.name] = parts[i] !== undefined ? decodeURIComponent(parts[i]) : ''; 134 | } catch { 135 | console.error(`Cannot decode ${parts[i]}"`); 136 | (result as any)[param.name] = parts[i]; 137 | } 138 | 139 | i++; 140 | } 141 | }); 142 | } else { 143 | parts.forEach((param, i) => { 144 | try { 145 | (result as any)[`arg${i}`] = decodeURIComponent(param); 146 | } catch { 147 | console.error(`Cannot decode ${param}"`); 148 | (result as any)[`arg${i}`] = param; 149 | } 150 | }); 151 | } 152 | 153 | return result; 154 | } 155 | 156 | export function errorResponse( 157 | req: RequestExt, 158 | res: Response, 159 | error: string, 160 | response?: Record | 401 | 403 | 500 | 422 | 429, 161 | responseCode?: 401 | 403 | 500 | 422 | 429, 162 | ): void { 163 | error = error.toString(); 164 | if (error === 'Error: permissionError') { 165 | error = 'permissionError'; 166 | } 167 | 168 | req._adapter.log.warn(`Warning by "${req.url}": ${error}`); 169 | 170 | if (typeof response === 'number') { 171 | res.status(response).json({ error }); 172 | } else { 173 | res.status(responseCode || (error.toString().includes('permissionError') ? 403 : 500)).json( 174 | Object.assign(response || {}, { error }), 175 | ); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/lib/api/controllers/enum.ts: -------------------------------------------------------------------------------- 1 | import { checkPermissions, errorResponse, parseUrl } from './common'; 2 | import type { RequestExt } from '../../types'; 3 | import type { Response } from 'express'; 4 | 5 | export function readMainEnums(req: RequestExt, res: Response): void { 6 | checkPermissions(req._adapter, req._user, [{ type: 'object', operation: 'read' }], async error => { 7 | if (error) { 8 | errorResponse(req, res, error); 9 | } else { 10 | // check if instance is alive 11 | try { 12 | const enums = await req._adapter.getEnumsAsync('', { 13 | user: req._user, 14 | limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner, 15 | }); 16 | res.json(enums); 17 | } catch (error) { 18 | req._adapter.log.warn(`Cannot read enums: ${error}`); 19 | errorResponse(req, res, error); 20 | } 21 | } 22 | }); 23 | } 24 | 25 | export function readEnums(req: RequestExt, res: Response): void { 26 | checkPermissions(req._adapter, req._user, [{ type: 'object', operation: 'read' }], async error => { 27 | if (error) { 28 | errorResponse(req, res, error); 29 | } else { 30 | const params = parseUrl<{ enumId: string }>(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX); 31 | // check if instance is alive 32 | try { 33 | const enums = await req._adapter.getEnumAsync(params.enumId, { 34 | user: req._user, 35 | limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner, 36 | }); 37 | if (enums && enums.result) { 38 | res.json( 39 | Object.keys(enums.result) 40 | .filter(id => id.split('.').length > 2) 41 | .map(id => ({ 42 | _id: id, 43 | common: enums.result[id].common, 44 | })), 45 | ); 46 | } else { 47 | res.json([]); 48 | } 49 | } catch (error) { 50 | errorResponse(req, res, error); 51 | } 52 | } 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /src/lib/api/controllers/file.ts: -------------------------------------------------------------------------------- 1 | import { checkPermissions, errorResponse, parseUrl } from './common'; 2 | import type { RequestExt } from '../../types'; 3 | import type { Response } from 'express'; 4 | 5 | export function readFile(req: RequestExt, res: Response): void { 6 | checkPermissions(req._adapter, req._user, [{ type: 'file', operation: 'read' }], async error => { 7 | if (error) { 8 | errorResponse(req, res, error); 9 | } else { 10 | const params = parseUrl<{ objectId: string; fileName: string }>( 11 | req.url, 12 | req.swagger, 13 | req._adapter.WEB_EXTENSION_PREFIX, 14 | ); 15 | try { 16 | const data = await req._adapter.readFileAsync(params.objectId, params.fileName, { 17 | user: req._user, 18 | limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner, 19 | }); 20 | if (data && data.mimeType) { 21 | res.set('Content-Type', data.mimeType); 22 | res.send(data.file); 23 | } else { 24 | res.status(404).send(Buffer.from('')); 25 | } 26 | } catch (error) { 27 | errorResponse(req, res, error); 28 | } 29 | } 30 | }); 31 | } 32 | 33 | export function deleteFile(req: RequestExt, res: Response): void { 34 | checkPermissions(req._adapter, req._user, [{ type: 'file', operation: 'delete' }], async error => { 35 | if (error) { 36 | errorResponse(req, res, error); 37 | } else { 38 | const params = parseUrl<{ objectId: string; fileName: string }>( 39 | req.url, 40 | req.swagger, 41 | req._adapter.WEB_EXTENSION_PREFIX, 42 | ); 43 | try { 44 | await req._adapter.delFileAsync(params.objectId, params.fileName, { 45 | user: req._user, 46 | limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner, 47 | }); 48 | res.json({ success: true }); 49 | } catch (err) { 50 | if (err.toString().includes('Not exists')) { 51 | res.status(404).json({ error: err.toString() }); 52 | } else { 53 | errorResponse(req, res, err); 54 | } 55 | } 56 | } 57 | }); 58 | } 59 | 60 | export function writeFile(req: RequestExt, res: Response): void { 61 | checkPermissions(req._adapter, req._user, [{ type: 'file', operation: 'write' }], async error => { 62 | if (error) { 63 | errorResponse(req, res, error); 64 | } else { 65 | const params = parseUrl<{ objectId: string; fileName: string }>( 66 | req.url, 67 | req.swagger, 68 | req._adapter.WEB_EXTENSION_PREFIX, 69 | ); 70 | if (req.files?.file[0].buffer) { 71 | try { 72 | await req._adapter.writeFileAsync(params.objectId, params.fileName, req.files?.file[0].buffer, { 73 | user: req._user, 74 | limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner, 75 | }); 76 | res.json({ success: true }); 77 | } catch (err) { 78 | errorResponse(req, res, err); 79 | } 80 | } else { 81 | errorResponse(req, res, 'No file provided.', 422); 82 | } 83 | } 84 | }); 85 | } 86 | 87 | export function readDir(req: RequestExt, res: Response): void { 88 | checkPermissions(req._adapter, req._user, [{ type: 'file', operation: 'list' }], async error => { 89 | if (error) { 90 | errorResponse(req, res, error); 91 | } else { 92 | const params = parseUrl<{ objectId: string; dirName?: string }>( 93 | req.url, 94 | req.swagger, 95 | req._adapter.WEB_EXTENSION_PREFIX, 96 | ); 97 | try { 98 | const response = await req._adapter.readDirAsync(params.objectId, params.dirName || '', { 99 | user: req._user, 100 | limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner, 101 | }); 102 | res.json(response); 103 | } catch (err) { 104 | errorResponse(req, res, err); 105 | } 106 | } 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /src/lib/api/controllers/sendTo.ts: -------------------------------------------------------------------------------- 1 | import { checkPermissions, errorResponse, parseUrl } from './common'; 2 | import type { RequestExt } from '../../types'; 3 | import type { Response } from 'express'; 4 | 5 | export function sendTo(req: RequestExt, res: Response): void { 6 | checkPermissions(req._adapter, req._user, [{ type: 'other', operation: 'sendto' }], async error => { 7 | if (error) { 8 | errorResponse(req, res, error); 9 | } else { 10 | const params = parseUrl<{ instance: string }>(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX); 11 | let message = req.query.message as string; 12 | let noResponseStr = req.query.noResponse as string; 13 | let timeout: string | number = req.query.timeout as string; 14 | let data: any = req.query.data as string; 15 | if (req.body?.message) { 16 | message = req.body.message; 17 | } 18 | if (req.body?.timeout) { 19 | timeout = req.body.timeout; 20 | } 21 | timeout = parseInt(timeout as string, 10) || 10000; 22 | if (req.body?.noResponse !== undefined) { 23 | noResponseStr = req.body.noResponse; 24 | } 25 | const noResponse = noResponseStr === 'true'; 26 | 27 | if (req.body?.data !== undefined) { 28 | data = req.body.data; 29 | } else { 30 | if (data !== undefined && data !== null) { 31 | if (data === 'null') { 32 | data = null; 33 | } else if (data === 'undefined') { 34 | data = undefined; 35 | } else if (data === 'true') { 36 | data = true; 37 | } else if (data === 'false') { 38 | data = false; 39 | } else if (isFinite(data)) { 40 | data = parseFloat(data); 41 | } else if (data.startsWith('{') && data.endsWith('}')) { 42 | try { 43 | data = JSON.parse(data); 44 | } catch { 45 | // ignore 46 | } 47 | } else if (data.startsWith('[') && data.endsWith(']')) { 48 | try { 49 | data = JSON.parse(data); 50 | } catch { 51 | // ignore 52 | } 53 | } 54 | } 55 | } 56 | 57 | const instance = params.instance; 58 | 59 | if (!instance) { 60 | res.status(422).json({ error: 'No instance provided' }); 61 | return; 62 | } 63 | if (!message) { 64 | res.status(422).json({ error: 'No message provided' }); 65 | return; 66 | } 67 | 68 | // check if instance is alive 69 | let state; 70 | try { 71 | state = await req._adapter.getForeignStateAsync(`system.adapter.${instance}.alive`); 72 | if (!state || !state.val) { 73 | res.status(500).json({ error: 'instance is not online', instance }); 74 | return; 75 | } 76 | } catch { 77 | res.status(500).json({ error: 'invalid instance', instance }); 78 | return; 79 | } 80 | if (noResponse) { 81 | req._adapter.sendTo(instance, message, data); 82 | res.json({ result: 'sent' }); 83 | } else { 84 | let timer: ioBroker.Timeout | undefined = null; 85 | let answerDone = false; 86 | if (timeout) { 87 | timer = req._adapter.setTimeout(() => { 88 | timer = null; 89 | if (!answerDone) { 90 | answerDone = true; 91 | res.status(408).json({ error: 'timeout' }); 92 | } 93 | }, timeout); 94 | } 95 | 96 | req._adapter.sendTo(instance, message, data, (result: any): void => { 97 | if (timer) { 98 | req._adapter.clearTimeout(timer); 99 | } 100 | if (!answerDone) { 101 | answerDone = true; 102 | res.json(result); 103 | } 104 | }); 105 | } 106 | } 107 | }); 108 | } 109 | 110 | export const sendToPost = sendTo; 111 | -------------------------------------------------------------------------------- /src/lib/common.ts: -------------------------------------------------------------------------------- 1 | // taken from here: https://stackoverflow.com/questions/1007981/how-to-get-function-parameter-names-values-dynamically 2 | const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm; 3 | const ARGUMENT_NAMES = /([^\s,]+)/g; 4 | export function getParamNames(func: any): string[] { 5 | const fnStr = func.toString().replace(STRIP_COMMENTS, ''); 6 | const result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); 7 | return result || []; 8 | } 9 | 10 | export const DEFAULT_VALUES: Record = { 11 | log: { level: 'info' }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/lib/config/default.yaml: -------------------------------------------------------------------------------- 1 | # swagger configuration file 2 | 3 | # values in the swagger hash are system configuration for swagger-node 4 | swagger: 5 | 6 | fittingsDirs: [ api/fittings ] 7 | defaultPipe: null 8 | swaggerControllerPipe: swagger_controllers # defines the standard processing pipe for controllers 9 | 10 | # values defined in the bagpipes key are the bagpipes pipes and fittings definitions 11 | # (see https://github.com/apigee-127/bagpipes) 12 | bagpipes: 13 | 14 | _router: 15 | name: swagger_router 16 | mockMode: false 17 | mockControllersDirs: [ api/mocks ] 18 | controllersDirs: [ api/controllers ] 19 | 20 | _swagger_validate: 21 | name: swagger_validator 22 | validateResponse: true 23 | 24 | #_swagger_security: 25 | # name: swagger_security 26 | # securityHandlersModule: api/helpers/auth.mw 27 | 28 | # pipe for all swagger-node controllers 29 | swagger_controllers: 30 | - onError: json_error_handler 31 | - cors 32 | #- swagger_security 33 | - _swagger_validate 34 | - express_compatibility 35 | - _router 36 | 37 | # pipe to serve swagger (endpoint is in swagger.yaml) 38 | swagger_raw: 39 | name: swagger_raw 40 | 41 | # any other values in this file are just loaded into the config for application access... 42 | -------------------------------------------------------------------------------- /src/lib/types.d.ts: -------------------------------------------------------------------------------- 1 | import type { Request, Response } from 'express'; 2 | 3 | export type UserName = `system.user.${string}`; 4 | export interface Swagger { 5 | operation: { 6 | parameters: { in: string; name: string }[]; 7 | }; 8 | expressMiddleware: () => { 9 | register: (app: Express) => void; 10 | }; 11 | } 12 | 13 | export interface RestApiAdapterConfig { 14 | port: number | string; 15 | auth?: boolean; 16 | secure: boolean; 17 | bind: string; 18 | certPublic: string; 19 | certPrivate: string; 20 | certChained: string; 21 | defaultUser?: `system.user.${string}`; 22 | ttl: number | string; 23 | onlyAllowWhenUserIsOwner: boolean; 24 | webInstance: string; 25 | leEnabled: boolean; 26 | leUpdate: boolean; 27 | leCheckPort: number; 28 | checkInterval: number | string; 29 | hookTimeout: number | string; 30 | noUI: boolean; 31 | dataSource: string; 32 | noCommands: boolean; 33 | noAdminCommands: boolean; 34 | leConfig?: boolean; 35 | certificates?: ioBroker.Certificates; 36 | language?: ioBroker.Languages; 37 | } 38 | 39 | export declare interface RestApiAdapter extends ioBroker.Adapter { 40 | WEB_EXTENSION_PREFIX: string; 41 | config: RestApiAdapterConfig; 42 | _addTimeout: ((task: { id: string; val: ioBroker.State; res: Response; timeout: number }) => Promise) | null; 43 | } 44 | 45 | export type SubscribeMethod = 'polling' | 'POST' | 'GET' | 'PUT' | 'PATCH'; 46 | 47 | export type RequestExt = Request & { 48 | _adapter: RestApiAdapter; 49 | swagger: Swagger; 50 | _user: UserName; 51 | files?: { file: { buffer: Buffer | string }[] }; 52 | _swaggerObject: { 53 | registerSubscribe: ( 54 | urlHook: string, 55 | id: string, 56 | type: 'state' | 'object', 57 | user: `system.user.${string}`, 58 | options: 59 | | { 60 | delta?: number | string; 61 | method: SubscribeMethod; 62 | onchange?: boolean | string; 63 | } 64 | | SubscribeMethod, 65 | ) => Promise; 66 | unregisterSubscribe: ( 67 | urlHook: string, 68 | id: null | string, 69 | type: 'state' | 'object', 70 | user?: `system.user.${string}`, 71 | ) => Promise; 72 | getSubscribes: (url: string, pattern: string, type: 'state' | 'object') => string[] | null; 73 | }; 74 | }; 75 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { EXIT_CODES, Adapter, type AdapterOptions } from '@iobroker/adapter-core'; // Get common adapter utils 2 | import { WebServer, createOAuth2Server } from '@iobroker/webserver'; 3 | import bodyParser from 'body-parser'; 4 | import cookieParser from 'cookie-parser'; 5 | import express, { type Express, type Response } from 'express'; 6 | import type { Server as HttpServer } from 'node:http'; 7 | import type { Server as HttpsServer } from 'node:https'; 8 | import RestAPI from './lib/rest-api'; 9 | import type { RestApiAdapterConfig } from './lib/types'; 10 | 11 | type Server = HttpServer | HttpsServer; 12 | 13 | class RestApiAdapter extends Adapter { 14 | declare config: RestApiAdapterConfig; 15 | private readonly webServer: { 16 | api: RestAPI | null; 17 | server: Server | null; 18 | app: Express | null; 19 | }; 20 | public WEB_EXTENSION_PREFIX: string = ''; 21 | public _addTimeout: 22 | | ((task: { id: string; val: ioBroker.State; res: Response; timeout: number }) => Promise) 23 | | null = null; 24 | 25 | public constructor(options: Partial = {}) { 26 | super({ 27 | ...options, 28 | name: 'rest-api', 29 | stateChange: (id, state) => this.webServer?.api?.stateChange(id, state), 30 | objectChange: (id, obj) => this.webServer?.api?.objectChange(id, obj), 31 | unload: callback => this.onUnload(callback), 32 | ready: () => this.main(), 33 | }); 34 | 35 | this.webServer = { 36 | app: null, 37 | server: null, 38 | api: null, 39 | }; 40 | } 41 | 42 | onUnload(callback: () => void): void { 43 | try { 44 | void this.setState('info.connection', false, true); 45 | 46 | this.log.info(`terminating http${this.config.secure ? 's' : ''} server on port ${this.config.port}`); 47 | if (this.webServer?.api) { 48 | void this.webServer.api.unload().then(() => { 49 | try { 50 | if (this.webServer.server) { 51 | this.webServer.server.close(); 52 | this.webServer.server = null; 53 | } 54 | } catch (error) { 55 | // ignore 56 | console.error(`Cannot close server: ${error}`); 57 | } 58 | callback(); 59 | }); 60 | } else { 61 | if (this.webServer?.server) { 62 | this.webServer.server.close(); 63 | this.webServer.server = null; 64 | } 65 | callback(); 66 | } 67 | } catch (error) { 68 | console.error(`Cannot close server: ${error}`); 69 | callback(); 70 | } 71 | } 72 | 73 | async initWebServer(): Promise { 74 | this.config.port = parseInt(this.config.port as string, 10); 75 | 76 | this.webServer.app = express(); 77 | 78 | this.webServer.api = new RestAPI(this.webServer.server!, this.config, this, null, this.webServer.app); 79 | 80 | if (this.config.port) { 81 | if (this.config.secure && !this.config.certificates) { 82 | return; 83 | } 84 | 85 | try { 86 | const webserver = new WebServer({ 87 | app: this.webServer.app, 88 | adapter: this, 89 | secure: this.config.secure, 90 | }); 91 | 92 | this.webServer.server = await webserver.init(); 93 | 94 | if (this.config.auth) { 95 | // Install OAuth2 handler 96 | this.webServer.app.use(cookieParser()); 97 | this.webServer.app.use(bodyParser.urlencoded({ extended: true })); 98 | this.webServer.app.use(bodyParser.json()); 99 | 100 | createOAuth2Server(this, { 101 | app: this.webServer.app, 102 | secure: this.config.secure, 103 | accessLifetime: parseInt(this.config.ttl as string, 10) || 3600, 104 | }); 105 | } 106 | } catch (err) { 107 | this.log.error(`Cannot create webserver: ${err}`); 108 | this.terminate ? this.terminate(1) : process.exit(1); 109 | return; 110 | } 111 | } else { 112 | this.log.error('port missing'); 113 | process.exit(1); 114 | } 115 | 116 | if (this.webServer.server) { 117 | let serverListening = false; 118 | let serverPort = this.config.port; 119 | 120 | this.webServer.server.on('error', e => { 121 | if (e.toString().includes('EACCES') && serverPort <= 1024) { 122 | this.log.error( 123 | `node.js process has no rights to start server on the port ${serverPort}.\n` + 124 | `Do you know that on linux you need special permissions for ports under 1024?\n` + 125 | `You can call in shell following scrip to allow it for node.js: "iobroker fix"`, 126 | ); 127 | } else { 128 | this.log.error(`Cannot start server on ${this.config.bind || '0.0.0.0'}:${serverPort}: ${e}`); 129 | } 130 | if (!serverListening) { 131 | this.terminate ? this.terminate(1) : process.exit(1); 132 | } 133 | }); 134 | 135 | this.getPort( 136 | this.config.port, 137 | !this.config.bind || this.config.bind === '0.0.0.0' ? undefined : this.config.bind || undefined, 138 | port => { 139 | if (port !== this.config.port) { 140 | this.log.error(`port ${this.config.port} already in use`); 141 | process.exit(1); 142 | } 143 | serverPort = port; 144 | 145 | this.webServer.server!.listen( 146 | port, 147 | !this.config.bind || this.config.bind === '0.0.0.0' ? undefined : this.config.bind || undefined, 148 | async () => { 149 | await this.setStateAsync('info.connection', true, true); 150 | this.log.info(`http${this.config.secure ? 's' : ''} server listening on port ${port}`); 151 | serverListening = true; 152 | }, 153 | ); 154 | }, 155 | ); 156 | } 157 | } 158 | 159 | main(): void { 160 | if (this.config.webInstance) { 161 | console.log('Adapter runs as a part of web service'); 162 | this.log.warn('Adapter runs as a part of web service'); 163 | this.setForeignState(`system.adapter.${this.namespace}.alive`, false, true, () => 164 | setTimeout(() => process.exit(EXIT_CODES.ADAPTER_REQUESTED_TERMINATION), 1000), 165 | ); 166 | } else { 167 | if (this.config.secure) { 168 | // Load certificates 169 | this.getCertificates( 170 | undefined, 171 | undefined, 172 | undefined, 173 | ( 174 | err: Error | null | undefined, 175 | certificates: ioBroker.Certificates | undefined, 176 | leConfig: boolean | undefined, 177 | ): void => { 178 | this.config.certificates = certificates; 179 | this.config.leConfig = leConfig; 180 | void this.initWebServer(); 181 | }, 182 | ); 183 | } else { 184 | void this.initWebServer(); 185 | } 186 | } 187 | } 188 | } 189 | 190 | if (require.main !== module) { 191 | // Export the constructor in compact mode 192 | module.exports = (options: Partial | undefined) => new RestApiAdapter(options); 193 | } else { 194 | // otherwise start the instance directly 195 | (() => new RestApiAdapter())(); 196 | } 197 | -------------------------------------------------------------------------------- /test/mocha.setup.js: -------------------------------------------------------------------------------- 1 | process.on('unhandledRejection', r => { 2 | throw r; 3 | }); 4 | -------------------------------------------------------------------------------- /test/testApiAsLimitedUser.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path'); 2 | const axios = require('axios'); 3 | const { tests } = require('@iobroker/testing'); 4 | const { expect } = require('chai'); 5 | 6 | const PORT = 18186; 7 | const TESTS_TIMEOUT = 10000; 8 | process.env.NO_PROXY = '127.0.0.1'; 9 | 10 | tests.integration(path.join(__dirname, '..'), { 11 | allowedExitCodes: [11], 12 | loglevel: 'info', 13 | controllerVersion: process.env.CONTROLLER_VERSION || undefined, 14 | 15 | defineAdditionalTests({ suite }) { 16 | suite('Test RESTful API as Owner-User', getHarness => { 17 | let harness; 18 | before(async function () { 19 | // The adapter start can take a bit 20 | this.timeout(TESTS_TIMEOUT); 21 | harness = getHarness(); 22 | 23 | await harness.changeAdapterConfig(harness.adapterName, { 24 | native: { 25 | bind: '127.0.0.1', 26 | port: PORT, 27 | defaultUser: 'myuser', 28 | onlyAllowWhenUserIsOwner: true, 29 | }, 30 | }); 31 | 32 | await harness.objects.setObjectAsync('system.user.myuser', { 33 | type: 'user', 34 | common: { 35 | name: 'myuser', 36 | enabled: true, 37 | groups: [], 38 | password: 39 | 'pbkdf2$10000$ab4104d8bb68390ee7e6c9397588e768de6c025f0c732c18806f3d1270c83f83fa86a7bf62583770e5f8d0b405fbb3ad32214ef3584f5f9332478f2506414443a910bf15863b36ebfcaa7cbb19253ae32cd3ca390dab87b29cd31e11be7fa4ea3a01dad625d9de44e412680e1a694227698788d71f1e089e5831dc1bbacfa794b45e1c995214bf71ee4160d98b4305fa4c3e36ee5f8da19b3708f68e7d2e8197375c0f763d90e31143eb04760cc2148c8f54937b9385c95db1742595634ed004fa567655dfe1d9b9fa698074a9fb70c05a252b2d9cf7ca1c9b009f2cd70d6972ccf0ee281d777d66a0346c6c6525436dd7fe3578b28dca2c7adbfde0ecd45148$31c3248ba4dc9600a024b4e0e7c3e585', 40 | }, 41 | _id: 'system.user.myuser', 42 | native: {}, 43 | acl: { 44 | object: 1638, 45 | }, 46 | }); 47 | 48 | await harness.objects.setObjectAsync('system.group.writer', { 49 | common: { 50 | name: 'Writer', 51 | desc: '', 52 | members: ['system.user.myuser'], 53 | acl: { 54 | object: { 55 | list: false, 56 | read: true, 57 | write: false, 58 | delete: false, 59 | }, 60 | state: { 61 | list: false, 62 | read: true, 63 | write: true, 64 | create: false, 65 | delete: false, 66 | }, 67 | users: { 68 | write: false, 69 | create: false, 70 | delete: false, 71 | }, 72 | other: { 73 | execute: false, 74 | http: false, 75 | sendto: false, 76 | }, 77 | file: { 78 | list: false, 79 | read: false, 80 | write: false, 81 | create: false, 82 | delete: false, 83 | }, 84 | }, 85 | }, 86 | native: {}, 87 | acl: { 88 | object: 1638, // 666 89 | owner: 'system.user.admin', 90 | ownerGroup: 'system.group.administrator', 91 | }, 92 | _id: 'system.group.writer', 93 | type: 'group', 94 | }); 95 | 96 | await harness.objects.setObjectAsync('javascript.0.test', { 97 | common: { 98 | name: 'test', 99 | type: 'number', 100 | role: 'level', 101 | min: -100, 102 | max: 100, 103 | def: 1, 104 | }, 105 | native: {}, 106 | type: 'state', 107 | acl: { 108 | object: 1638, // 666 109 | owner: 'system.user.myuser', 110 | ownerGroup: 'system.group.administrator', 111 | state: 1638, 112 | }, 113 | }); 114 | 115 | await harness.states.setStateAsync('javascript.0.test', 1); 116 | 117 | // Start the adapter and wait until it has started 118 | await harness.startAdapterAndWait(true); 119 | }); 120 | 121 | it('Test RESTful API as Owner-User: get - must not return value', async () => { 122 | const response = await axios( 123 | `http://127.0.0.1:${PORT}/v1/state/system.adapter.${harness.adapterName}.0.alive`, 124 | { validateStatus: () => true }, 125 | ); 126 | console.log(`get/system.adapter.${harness.adapterName}.0.alive => ${JSON.stringify(response.data)}`); 127 | expect(response.data.error).to.be.equal('permissionError'); 128 | }); 129 | 130 | it('Test RESTful API as Owner-User: getPlainValue - must not return plain value', async () => { 131 | const response = await axios( 132 | `http://127.0.0.1:${PORT}/v1/state/system.adapter.${harness.adapterName}.0.alive/plain`, 133 | { validateStatus: () => true }, 134 | ); 135 | console.log( 136 | `v1/state/system.adapter.${harness.adapterName}.0.alive => /plain${JSON.stringify(response.data)}`, 137 | ); 138 | expect(response.data.error).to.be.equal('permissionError'); 139 | }); 140 | 141 | it('Test RESTful API as Owner-User: getPlainValue 4 Test-Endpoint - must not return plain value', async () => { 142 | const response = await axios(`http://127.0.0.1:${PORT}/v1/state/javascript.0.test/plain`, { 143 | validateStatus: () => true, 144 | }); 145 | console.log(`v1/state/javascript.0.test => /plain${JSON.stringify(response.data)}`); 146 | expect(response.data).equal(1); 147 | }); 148 | 149 | it('Test RESTful API as Owner-User: set 4 Test-Endpoint - must set value', async () => { 150 | let response = await axios(`http://127.0.0.1:${PORT}/v1/state/javascript.0.test?value=2`); 151 | console.log(`set/javascript.0.test?value=false => ${JSON.stringify(response.data)}`); 152 | 153 | const obj = response.data; 154 | expect(obj).to.be.ok; 155 | expect(obj.val).to.be.equal(2); 156 | expect(obj.id).to.equal('javascript.0.test'); 157 | 158 | response = await axios(`http://127.0.0.1:${PORT}/v1/state/javascript.0.test/plain`); 159 | console.log(`v1/state/javascript.0.test => /plain${JSON.stringify(response.data)}`); 160 | 161 | expect(response.data).equal(2); 162 | }); 163 | 164 | it('Test RESTful API as Owner-User: set - must not set value', async () => { 165 | const response = await axios( 166 | `http://127.0.0.1:${PORT}/v1/state/system.adapter.${harness.adapterName}.0.alive?value=false`, 167 | { validateStatus: () => true }, 168 | ); 169 | console.log( 170 | `set/system.adapter.${harness.adapterName}.0.alive?value=false => ${JSON.stringify(response.data)}`, 171 | ); 172 | expect(response.data.error).to.be.equal('permissionError'); 173 | }); 174 | 175 | it('Test RESTful API as Owner-User: set - must set value', async () => { 176 | const response = await axios( 177 | `http://127.0.0.1:${PORT}/v1/state/system.adapter.${harness.adapterName}.0.alive?value=true`, 178 | { validateStatus: () => true }, 179 | ); 180 | console.log( 181 | `set/system.adapter.${harness.adapterName}.0.alive?value=true => ` + JSON.stringify(response.data), 182 | ); 183 | expect(response.data.error).to.be.equal('permissionError'); 184 | }); 185 | 186 | it('Test RESTful API as Owner-User: objects - must not return objects', async () => { 187 | const response = await axios(`http://127.0.0.1:${PORT}/v1/objects?filter=system.adapter.*`, { 188 | validateStatus: () => true, 189 | }); 190 | console.log(`objects?pattern=system.adapter.* => ${JSON.stringify(response.data)}`); 191 | expect(response.data.error).to.be.equal('permissionError'); 192 | }); 193 | 194 | it('Test RESTful API as Owner-User: objects - must not return objects', async () => { 195 | const response = await axios( 196 | `http://127.0.0.1:${PORT}/v1/objects?filter=system.adapter.*&type=instance`, 197 | { validateStatus: () => true }, 198 | ); 199 | console.log(`objects?pattern=system.adapter.* => ${JSON.stringify(response.data)}`); 200 | expect(response.data.error).to.be.equal('permissionError'); 201 | }); 202 | 203 | it('Test RESTful API as Owner-User: states - must not return states', async () => { 204 | const response = await axios(`http://127.0.0.1:${PORT}/v1/states?filter=system.adapter.*`, { 205 | validateStatus: () => true, 206 | }); 207 | console.log(`states?pattern=system.adapter.* => ${JSON.stringify(response.data)}`); 208 | expect(response.data.error).to.be.equal('permissionError'); 209 | }); 210 | 211 | it('Test RESTful API as Owner-User: setValueFromBody(POST) - must not set one value', async () => { 212 | const response = await axios.patch( 213 | `http://127.0.0.1:${PORT}/v1/state/system.adapter.${harness.adapterName}.0.alive`, 214 | { val: true, ack: false }, 215 | { validateStatus: () => true }, 216 | ); 217 | console.log( 218 | `setValueFromBody/?system.adapter.${harness.adapterName}.upload => ${JSON.stringify(response.data)}`, 219 | ); 220 | expect(response.data.error).to.be.equal('permissionError'); 221 | }); 222 | }); 223 | }, 224 | }); 225 | -------------------------------------------------------------------------------- /test/testPackage.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path'); 2 | const { tests } = require('@iobroker/testing'); 3 | 4 | // Run tests 5 | tests.packageFiles(path.join(__dirname, '..')); 6 | -------------------------------------------------------------------------------- /test/testSsl.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path'); 2 | const axios = require('axios'); 3 | const { tests } = require('@iobroker/testing'); 4 | const { expect } = require('chai'); 5 | 6 | const PORT = 18186; 7 | const TESTS_TIMEOUT = 10000; 8 | 9 | process.env.NO_PROXY = '127.0.0.1'; 10 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 11 | 12 | // Run tests 13 | tests.integration(path.join(__dirname, '..'), { 14 | allowedExitCodes: [11], 15 | loglevel: 'info', 16 | controllerVersion: process.env.CONTROLLER_VERSION || undefined, 17 | 18 | defineAdditionalTests({ suite }) { 19 | suite('Test REST API SSL', getHarness => { 20 | let harness; 21 | before(async function () { 22 | // The adapter start can take a bit 23 | this.timeout(TESTS_TIMEOUT); 24 | 25 | harness = getHarness(); 26 | 27 | await harness.changeAdapterConfig(harness.adapterName, { 28 | native: { 29 | bind: '127.0.0.1', 30 | port: PORT, 31 | auth: true, 32 | secure: true, 33 | certPublic: 'defaultPublic', 34 | certPrivate: 'defaultPrivate', 35 | }, 36 | }); 37 | 38 | // Start the adapter and wait until it has started 39 | await harness.startAdapterAndWait(true); 40 | }); 41 | 42 | it('Test REST API SSL: get - must return value', async () => { 43 | const response = await axios.get( 44 | `https://127.0.0.1:${PORT}/v1/state/system.adapter.${harness.adapterName}.0.alive?user=admin&pass=iobroker`, 45 | ); 46 | const obj = response.data; 47 | console.log( 48 | `[GET] /v1/state/system.adapter.${harness.adapterName}.0.alive?user=admin&pass=iobroker => ${JSON.stringify(obj)}`, 49 | ); 50 | //{ 51 | // "val" : true, 52 | // "ack" : true, 53 | // "ts" : 1455009717, 54 | // "q" : 0, 55 | // "from" : "system.adapter.${harness.adapterName}.0", 56 | // "lc" : 1455009717, 57 | // "expire" : 30000, 58 | // "_id" : "system.adapter.${harness.adapterName}.0.alive", 59 | // "type" : "state", 60 | // "common" : { 61 | // "name" : "${harness.adapterName}.0.alive", 62 | // "type" : "boolean", 63 | // "role" : "indicator.state" 64 | // }, 65 | // "native" : {} 66 | // 67 | //} 68 | 69 | expect(obj).to.be.ok; 70 | expect(obj.val).to.be.true; 71 | expect(obj.ack).to.be.true; 72 | expect(obj.ts).to.be.ok; 73 | expect(obj.from).to.equal(`system.adapter.${harness.adapterName}.0`); 74 | }).timeout(TESTS_TIMEOUT); 75 | 76 | it('Test REST API SSL: get - must return value with auth in header', async () => { 77 | const response = await axios.get( 78 | `https://127.0.0.1:${PORT}/v1/state/system.adapter.${harness.adapterName}.0.alive`, 79 | { 80 | headers: { 81 | Authorization: 'Basic ' + Buffer.from('admin:iobroker').toString('base64'), 82 | }, 83 | }, 84 | ); 85 | const obj = response.data; 86 | console.log( 87 | `[GET/Authorization] /v1/state/system.adapter.${harness.adapterName}.0.alive => ${JSON.stringify(obj)}`, 88 | ); 89 | expect(response.status).to.be.equal(200); 90 | expect(obj).to.be.ok; 91 | expect(obj.val).to.be.true; 92 | expect(obj.ack).to.be.true; 93 | expect(obj.ts).to.be.ok; 94 | expect(obj.from).to.equal(`system.adapter.${harness.adapterName}.0`); 95 | }).timeout(TESTS_TIMEOUT); 96 | 97 | it('Test REST API SSL: get with no credentials', async () => { 98 | const response = await axios.get( 99 | `https://127.0.0.1:${PORT}/v1/state/system.adapter.${harness.adapterName}.0.alive`, 100 | { validateStatus: () => true }, 101 | ); 102 | console.log( 103 | `[GET] /v1/state/system.adapter.${harness.adapterName}.0.alive => ${JSON.stringify(response.data)}`, 104 | ); 105 | expect(response.status).to.be.equal(401); 106 | }).timeout(TESTS_TIMEOUT); 107 | 108 | it('Test REST API SSL: get with wrong credentials', async () => { 109 | const response = await axios.get( 110 | `https://127.0.0.1:${PORT}/v1/state/system.adapter.${harness.adapterName}.0.alive?user=admin&pass=io`, 111 | { validateStatus: () => true }, 112 | ); 113 | console.log( 114 | `[GET] /v1/state/system.adapter.${harness.adapterName}.0.alive?user=admin&pass=io => ${JSON.stringify(response.data)}`, 115 | ); 116 | expect(response.status).to.be.equal(401); 117 | }).timeout(TESTS_TIMEOUT); 118 | 119 | it('Test REST API SSL: get - get with wrong credentials in header', async () => { 120 | const response = await axios.get( 121 | `https://127.0.0.1:${PORT}/v1/state/system.adapter.${harness.adapterName}.0.alive`, 122 | { 123 | headers: { 124 | Authorization: `Basic ${Buffer.from('admin:io').toString('base64')}`, 125 | }, 126 | validateStatus: () => true, 127 | }, 128 | ); 129 | console.log( 130 | `[GET/Authorization] /v1/state/system.adapter.${harness.adapterName}.0.alive => ${JSON.stringify(response.data)}`, 131 | ); 132 | expect(response.status).to.be.equal(401); 133 | }).timeout(TESTS_TIMEOUT); 134 | }); 135 | }, 136 | }); 137 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": false, 5 | "checkJs": false, 6 | "noEmit": false, 7 | "declaration": false, 8 | "types": ["@iobroker/types"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // Root tsconfig to set the settings and power editor support for all TS files 2 | { 3 | "compileOnSave": true, 4 | "compilerOptions": { 5 | // do not compile anything; this file is just to configure type checking 6 | // the compilation is configured in tsconfig.build.json 7 | "noEmit": true, 8 | 9 | // check JS files, but do not compile them => tsconfig.build.json 10 | "allowJs": true, 11 | "checkJs": true, 12 | 13 | "skipLibCheck": true, // Don't report errors in 3rd party definitions 14 | "noEmitOnError": true, 15 | "outDir": "./dist", 16 | "removeComments": false, 17 | "module": "Node16", 18 | "moduleResolution": "node16", 19 | "esModuleInterop": true, 20 | // this is necessary for the automatic typing of the adapter config 21 | "resolveJsonModule": true, 22 | 23 | // Set this to false if you want to disable the very strict rules (not recommended) 24 | "strict": true, 25 | // Or enable some of those features for more fine-grained control 26 | // "strictNullChecks": true, 27 | // "strictPropertyInitialization": true, 28 | // "strictBindCallApply": true, 29 | // "noImplicitAny": true, 30 | // "noUnusedLocals": true, 31 | // "noUnusedParameters": true, 32 | 33 | // Consider targeting es2019 or higher if you only support Node.js 12+ 34 | "target": "es2022", 35 | 36 | "sourceMap": true, 37 | "inlineSourceMap": false, 38 | "useUnknownInCatchVariables": false, 39 | "types": ["@iobroker/types"] 40 | }, 41 | "include": ["src/**/*.ts"] 42 | } 43 | --------------------------------------------------------------------------------