├── .mocharc.json ├── test ├── mocha.setup.js ├── testPackageFiles.js ├── testSsl.js ├── testApiAsLimitedUser.js ├── testApiAsUserNoRights.js ├── testApiAsUser.js └── testApi.js ├── img └── favicon.ico ├── admin ├── simple-api.png ├── simple-api.svg ├── i18n │ ├── zh-cn.json │ ├── en.json │ ├── pt.json │ ├── uk.json │ ├── pl.json │ ├── nl.json │ ├── ru.json │ ├── es.json │ ├── de.json │ ├── it.json │ └── fr.json ├── jsonConfig.json └── words.js ├── prettier.config.mjs ├── tasks.js ├── .releaseconfig.json ├── tsconfig.build.json ├── .gitignore ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── auto-merge.yml ├── workflows │ ├── dependabot-automerge.yml │ ├── codeql.yml │ └── test-and-release.yml └── stale.yml ├── dist ├── main.d.ts ├── types.d.ts ├── lib │ └── SimpleAPI.d.ts ├── main.js.map └── main.js ├── eslint.config.mjs ├── LICENSE ├── tsconfig.json ├── src ├── types.d.ts └── main.ts ├── package.json └── io-package.json /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": ["./test/mocha.setup.js"] 3 | } 4 | -------------------------------------------------------------------------------- /test/mocha.setup.js: -------------------------------------------------------------------------------- 1 | process.on('unhandledRejection', r => { 2 | throw r; 3 | }); 4 | -------------------------------------------------------------------------------- /img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioBroker/ioBroker.simple-api/HEAD/img/favicon.ico -------------------------------------------------------------------------------- /admin/simple-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ioBroker/ioBroker.simple-api/HEAD/admin/simple-api.png -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | import prettierConfig from '@iobroker/eslint-config/prettier.config.mjs'; 2 | 3 | export default prettierConfig; 4 | -------------------------------------------------------------------------------- /tasks.js: -------------------------------------------------------------------------------- 1 | const { copyFileSync } = require('node:fs'); 2 | 3 | copyFileSync(`${__dirname}/src/types.d.ts`, `${__dirname}/dist/types.d.ts`); 4 | -------------------------------------------------------------------------------- /.releaseconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["iobroker", "license"], 3 | "exec": { 4 | "before_commit": "npm run build" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": false, 5 | "checkJs": false, 6 | "noEmit": false, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /test/testPackageFiles.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path'); 2 | const { tests } = require('@iobroker/testing'); 3 | 4 | // Validate the package files 5 | tests.packageFiles(path.join(__dirname, '..')); 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | tmp 4 | admin/i18n/flat.txt 5 | admin/i18n/*/flat.txt 6 | iob_npm.done 7 | package-lock.json 8 | *.tgz 9 | 10 | #ignore .commitinfo created by ioBroker release script 11 | .commitinfo 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot will run on day 15 of each month at 02:44 (Europe/Berlin timezone) 2 | version: 2 3 | updates: 4 | 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "cron" 9 | timezone: "Europe/Berlin" 10 | cronjob: "44 2 15 * *" 11 | open-pull-requests-limit: 15 12 | 13 | - package-ecosystem: "npm" 14 | directory: "/" 15 | schedule: 16 | interval: "cron" 17 | timezone: "Europe/Berlin" 18 | cronjob: "44 2 15 * *" 19 | open-pull-requests-limit: 15 20 | versioning-strategy: "increase" 21 | -------------------------------------------------------------------------------- /admin/simple-api.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /dist/main.d.ts: -------------------------------------------------------------------------------- 1 | import { type NextFunction, type Request, type Response } from 'express'; 2 | import { Adapter, type AdapterOptions } from '@iobroker/adapter-core'; 3 | import type { SimpleApiAdapterConfig } from './types'; 4 | export declare class SimpleApiAdapter extends Adapter { 5 | config: SimpleApiAdapterConfig; 6 | private webServer; 7 | private certificates; 8 | constructor(options?: Partial); 9 | onUnload(callback: () => void): void; 10 | main(): Promise; 11 | serveStatic: (req: Request, res: Response, next: NextFunction) => void; 12 | initWebServer(): Promise; 13 | } 14 | -------------------------------------------------------------------------------- /.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/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@v5 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/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 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import config from '@iobroker/eslint-config'; 2 | 3 | export default [ 4 | ...config, 5 | { 6 | languageOptions: { 7 | parserOptions: { 8 | projectService: { 9 | allowDefaultProject: ['*.mjs'], 10 | }, 11 | tsconfigRootDir: import.meta.dirname, 12 | project: './tsconfig.json', 13 | }, 14 | }, 15 | }, 16 | { 17 | ignores: [ 18 | 'admin/**/*', 19 | 'node_modules/**/*', 20 | 'test/**/*', 21 | 'build/**/*', 22 | 'tasks.js', 23 | 'tmp/**/*', 24 | 'dist/**/*', 25 | '.**/*', 26 | ], 27 | }, 28 | { 29 | // disable temporary the rule 'jsdoc/require-param' and enable 'jsdoc/require-jsdoc' 30 | rules: { 31 | 'jsdoc/require-jsdoc': 'off', 32 | 'jsdoc/require-param': 'off', 33 | 34 | '@typescript-eslint/no-require-imports': 'off', 35 | }, 36 | }, 37 | ]; 38 | -------------------------------------------------------------------------------- /.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: '52 4 * * 3' 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@v5 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v4 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v4 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v4 40 | with: 41 | category: '/language:${{ matrix.language }}' 42 | 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2025 bluefox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /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 | // check JS files, but do not compile them => tsconfig.build.json 9 | "allowJs": true, 10 | "checkJs": true, 11 | "skipLibCheck": true, // Don't report errors in 3rd party definitions 12 | "noEmitOnError": true, 13 | "declaration": true, 14 | "outDir": "./dist/", 15 | "removeComments": false, 16 | "module": "Node16", 17 | "moduleResolution": "node16", 18 | "esModuleInterop": true, 19 | // this is necessary for the automatic typing of the adapter config 20 | "resolveJsonModule": true, 21 | "strict": true, 22 | "target": "es2022", 23 | "sourceMap": true, 24 | "inlineSourceMap": false, 25 | "useUnknownInCatchVariables": false, 26 | "types": ["@iobroker/types", "node"] 27 | }, 28 | "include": ["src/**/*.ts", "src/*.d.ts"], 29 | "exclude": ["dist/**", "node_modules/**", "eslint.config.mjs"] 30 | } 31 | -------------------------------------------------------------------------------- /admin/i18n/zh-cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "仅当用户是所有者时才允许", 3 | "Allow origin (CORS)": "允许来源 (CORS)", 4 | "Authentication": "验证", 5 | "Authentication was deactivated": "身份验证已停用", 6 | "Chained certificate": "链式证书", 7 | "Disable authentication": "禁用身份验证", 8 | "Extend WEB adapter": "用作 Web 适配器扩展", 9 | "IP": "知识产权", 10 | "Ignore warning": "忽略警告", 11 | "Let's Encrypt settings": "Let's Encrypt 设置", 12 | "List all datapoints": "列出所有州", 13 | "Listen on all IPs": "监听所有 IP", 14 | "Port": "港口", 15 | "Port to check the domain": "检查域名的端口", 16 | "Private certificate": "私人证书", 17 | "Public certificate": "公共证书", 18 | "Run as": "以用户身份执行", 19 | "Secure(HTTPS)": "安全(HTTPS)", 20 | "Select data source": "选择数据源", 21 | "Set certificates or load it first in the system settings (right top).": "在系统设置(右上角)中设置证书或先加载。", 22 | "Unsecure_Auth": "密码将通过不安全的连接以未加密形式发送。要保护您的密码,请启用安全连接 (HTTPS)!", 23 | "Use Lets Encrypt certificates": "使用 Let's Encrypt 证书", 24 | "Use this instance for automatic update": "使用此实例进行自动更新", 25 | "Warning!": "警告!", 26 | "all": "全部", 27 | "enable_authentication_with_cors": "您可以在此处定义“Access-Control-Allow-Origin”标头。如果您计划从外部访问,建议启用身份验证。", 28 | "none": "没有任何", 29 | "place here": "将文件拖放到此处" 30 | } 31 | -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface SimpleApiAdapterConfig { 2 | port: number | string; 3 | auth: boolean; 4 | ttl: number | string; 5 | secure: boolean; 6 | bind: string; 7 | certPublic: string; 8 | certPrivate: string; 9 | certChained: string; 10 | defaultUser: string; // without 'system.user.' 11 | onlyAllowWhenUserIsOwner: boolean; 12 | webInstance: string; 13 | leEnabled: boolean; 14 | leUpdate: boolean; 15 | leCheckPort: number | string; 16 | dataSource: string; 17 | allDatapoints: boolean; 18 | accessControlAllowOrigin: string; 19 | } 20 | 21 | declare class ExtAPI { 22 | public waitForReadyTime?: number; 23 | 24 | constructor( 25 | webServer: Server, 26 | settings: { secure: boolean; port: number | string; defaultUser?: string }, 27 | adapter: ioBroker.Adapter, 28 | config?: ioBroker.InstanceObject, 29 | app?: Express, 30 | io?: SocketIO, 31 | ); 32 | 33 | welcomePage?(): LocalMultipleLinkEntry; 34 | fileChange?(id: string, fileName: string, size: number | null): void; 35 | stateChange?(id: string, state: ioBroker.State | null | undefined): void; 36 | objectChange?(id: string, state: ioBroker.Object | null | undefined): void; 37 | /** Give to the extension up to 5 seconds to be loaded */ 38 | waitForReady?(onReady: () => void): void; 39 | unload?(): Promise; 40 | } 41 | -------------------------------------------------------------------------------- /dist/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface SimpleApiAdapterConfig { 2 | port: number | string; 3 | auth: boolean; 4 | ttl: number | string; 5 | secure: boolean; 6 | bind: string; 7 | certPublic: string; 8 | certPrivate: string; 9 | certChained: string; 10 | defaultUser: string; // without 'system.user.' 11 | onlyAllowWhenUserIsOwner: boolean; 12 | webInstance: string; 13 | leEnabled: boolean; 14 | leUpdate: boolean; 15 | leCheckPort: number | string; 16 | dataSource: string; 17 | allDatapoints: boolean; 18 | accessControlAllowOrigin: string; 19 | } 20 | 21 | declare class ExtAPI { 22 | public waitForReadyTime?: number; 23 | 24 | constructor( 25 | webServer: Server, 26 | settings: { secure: boolean; port: number | string; defaultUser?: string }, 27 | adapter: ioBroker.Adapter, 28 | config?: ioBroker.InstanceObject, 29 | app?: Express, 30 | io?: SocketIO, 31 | ); 32 | 33 | welcomePage?(): LocalMultipleLinkEntry; 34 | fileChange?(id: string, fileName: string, size: number | null): void; 35 | stateChange?(id: string, state: ioBroker.State | null | undefined): void; 36 | objectChange?(id: string, state: ioBroker.Object | null | undefined): void; 37 | /** Give to the extension up to 5 seconds to be loaded */ 38 | waitForReady?(onReady: () => void): void; 39 | unload?(): Promise; 40 | } 41 | -------------------------------------------------------------------------------- /admin/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Allow only when User is Owner", 3 | "Allow origin (CORS)": "Allow origin (CORS)", 4 | "Authentication": "Authentication", 5 | "Authentication was deactivated": "Authentication was deactivated", 6 | "Chained certificate": "Chained certificate", 7 | "Disable authentication": "Disable authentication", 8 | "Extend WEB adapter": "Use as web adapter extension", 9 | "IP": "IP", 10 | "Ignore warning": "Ignore warning", 11 | "Let's Encrypt settings": "Let's Encrypt settings", 12 | "List all datapoints": "List all states", 13 | "Listen on all IPs": "Listen on all IPs", 14 | "Port": "Port", 15 | "Port to check the domain": "Port to check the domain", 16 | "Private certificate": "Private certificate", 17 | "Public certificate": "Public certificate", 18 | "Run as": "Execute as user", 19 | "Secure(HTTPS)": "Secure (HTTPS)", 20 | "Select data source": "Select data source", 21 | "Set certificates or load it first in the system settings (right top).": "Set certificates or load it first in the system settings (right top).", 22 | "Unsecure_Auth": "The password will be sent unencrypted via an unsecure connection. To protect your passwords enable the secure connection (HTTPS)!", 23 | "Use Lets Encrypt certificates": "Use Let's Encrypt certificates", 24 | "Use this instance for automatic update": "Use this instance for automatic update", 25 | "Warning!": "Warning!", 26 | "all": "all", 27 | "enable_authentication_with_cors": "You can define here the \"Access-Control-Allow-Origin\" header. It is suggested to enable authentication if you plan to access it from outside.", 28 | "none": "none", 29 | "place here": "Drop the files here" 30 | } -------------------------------------------------------------------------------- /admin/i18n/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Permitir somente quando o usuário for proprietário", 3 | "Allow origin (CORS)": "Permitir origem (CORS)", 4 | "Authentication": "Autenticação", 5 | "Authentication was deactivated": "A autenticação foi desativada", 6 | "Chained certificate": "Certificado encadeado", 7 | "Disable authentication": "Desativar autenticação", 8 | "Extend WEB adapter": "Usar como extensão do adaptador web", 9 | "IP": "Propriedade Intelectual", 10 | "Ignore warning": "Ignorar aviso", 11 | "Let's Encrypt settings": "Configurações do Let's Encrypt", 12 | "List all datapoints": "Listar todos os estados", 13 | "Listen on all IPs": "Ouça em todos os IPs", 14 | "Port": "Porta", 15 | "Port to check the domain": "Porta para verificar o domínio", 16 | "Private certificate": "Certificado privado", 17 | "Public certificate": "Certificado público", 18 | "Run as": "Executar como usuário", 19 | "Secure(HTTPS)": "Seguro (HTTPS)", 20 | "Select data source": "Selecione a fonte de dados", 21 | "Set certificates or load it first in the system settings (right top).": "Defina os certificados ou carregue-os primeiro nas configurações do sistema (canto superior direito).", 22 | "Unsecure_Auth": "A senha será enviada sem criptografia por meio de uma conexão não segura. Para proteger suas senhas, habilite a conexão segura (HTTPS)!", 23 | "Use Lets Encrypt certificates": "Use certificados Let's Encrypt", 24 | "Use this instance for automatic update": "Use esta instância para atualização automática", 25 | "Warning!": "Aviso!", 26 | "all": "todos", 27 | "enable_authentication_with_cors": "Você pode definir aqui o cabeçalho \"Access-Control-Allow-Origin\". É sugerido habilitar a autenticação se você planeja acessá-lo de fora.", 28 | "none": "nenhum", 29 | "place here": "Solte os arquivos aqui" 30 | } 31 | -------------------------------------------------------------------------------- /admin/i18n/uk.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Дозволити лише тоді, коли користувач є власником", 3 | "Allow origin (CORS)": "Дозволити походження (CORS)", 4 | "Authentication": "Аутентифікація", 5 | "Authentication was deactivated": "Автентифікацію було вимкнено", 6 | "Chained certificate": "Прикутий сертифікат", 7 | "Disable authentication": "Вимкнути автентифікацію", 8 | "Extend WEB adapter": "Використовувати як розширення веб-адаптера", 9 | "IP": "IP", 10 | "Ignore warning": "Ігнорувати попередження", 11 | "Let's Encrypt settings": "Давайте зашифруємо налаштування", 12 | "List all datapoints": "Перелічіть усі штати", 13 | "Listen on all IPs": "Прослуховування на всіх IP", 14 | "Port": "Порт", 15 | "Port to check the domain": "Порт для перевірки домену", 16 | "Private certificate": "Приватний сертифікат", 17 | "Public certificate": "Публічний сертифікат", 18 | "Run as": "Виконати як користувач", 19 | "Secure(HTTPS)": "Безпечний (HTTPS)", 20 | "Select data source": "Виберіть джерело даних", 21 | "Set certificates or load it first in the system settings (right top).": "Спершу встановіть сертифікати або завантажте їх у налаштуваннях системи (справа вгорі).", 22 | "Unsecure_Auth": "Пароль буде надіслано незашифрованим через незахищене з’єднання. Щоб захистити свої паролі, увімкніть безпечне з'єднання (HTTPS)!", 23 | "Use Lets Encrypt certificates": "Використовуйте сертифікати Let's Encrypt", 24 | "Use this instance for automatic update": "Використовуйте цей екземпляр для автоматичного оновлення", 25 | "Warning!": "УВАГА!", 26 | "all": "все", 27 | "enable_authentication_with_cors": "Тут можна визначити заголовок \"Access-Control-Allow-Origin\". Рекомендується ввімкнути автентифікацію, якщо ви плануєте отримати до нього доступ ззовні.", 28 | "none": "немає", 29 | "place here": "Перетягніть файли сюди" 30 | } 31 | -------------------------------------------------------------------------------- /admin/i18n/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Zezwól tylko, gdy użytkownik jest właścicielem", 3 | "Allow origin (CORS)": "Zezwalaj na pochodzenie (CORS)", 4 | "Authentication": "Uwierzytelnianie", 5 | "Authentication was deactivated": "Uwierzytelnianie zostało wyłączone", 6 | "Chained certificate": "Certyfikat łańcuchowy", 7 | "Disable authentication": "Wyłącz uwierzytelnianie", 8 | "Extend WEB adapter": "Użyj jako rozszerzenia adaptera internetowego", 9 | "IP": "IP", 10 | "Ignore warning": "Zignoruj ostrzeżenie", 11 | "Let's Encrypt settings": "Ustawienia Let's Encrypt", 12 | "List all datapoints": "Wypisz wszystkie stany", 13 | "Listen on all IPs": "Słuchaj na wszystkich IP", 14 | "Port": "Port", 15 | "Port to check the domain": "Port do sprawdzenia domeny", 16 | "Private certificate": "Certyfikat prywatny", 17 | "Public certificate": "Certyfikat publiczny", 18 | "Run as": "Wykonaj jako użytkownik", 19 | "Secure(HTTPS)": "Bezpieczny (HTTPS)", 20 | "Select data source": "Wybierz źródło danych", 21 | "Set certificates or load it first in the system settings (right top).": "Ustaw certyfikaty lub najpierw załaduj je w ustawieniach systemu (prawy górny róg).", 22 | "Unsecure_Auth": "Hasło zostanie wysłane bez szyfrowania za pośrednictwem niezabezpieczonego połączenia. Aby chronić swoje hasła, włącz bezpieczne połączenie (HTTPS)!", 23 | "Use Lets Encrypt certificates": "Użyj certyfikatów Let's Encrypt", 24 | "Use this instance for automatic update": "Użyj tej instancji do automatycznej aktualizacji", 25 | "Warning!": "Ostrzeżenie!", 26 | "all": "Wszystko", 27 | "enable_authentication_with_cors": "Tutaj możesz zdefiniować nagłówek „Access-Control-Allow-Origin”. Zaleca się włączenie uwierzytelniania, jeśli planujesz uzyskać do niego dostęp z zewnątrz.", 28 | "none": "nic", 29 | "place here": "Upuść pliki tutaj" 30 | } 31 | -------------------------------------------------------------------------------- /admin/i18n/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Alleen toestaan wanneer de gebruiker eigenaar is", 3 | "Allow origin (CORS)": "Oorsprong toestaan (CORS)", 4 | "Authentication": "Authenticatie", 5 | "Authentication was deactivated": "Authenticatie is gedeactiveerd", 6 | "Chained certificate": "Gekoppeld certificaat", 7 | "Disable authentication": "Authenticatie uitschakelen", 8 | "Extend WEB adapter": "Gebruik als webadapter-extensie", 9 | "IP": "IE", 10 | "Ignore warning": "Negeer waarschuwing", 11 | "Let's Encrypt settings": "Let's Encrypt-instellingen", 12 | "List all datapoints": "Lijst alle staten", 13 | "Listen on all IPs": "Luister op alle IP's", 14 | "Port": "Haven", 15 | "Port to check the domain": "Poort om het domein te controleren", 16 | "Private certificate": "Privécertificaat", 17 | "Public certificate": "Openbaar certificaat", 18 | "Run as": "Uitvoeren als gebruiker", 19 | "Secure(HTTPS)": "Veilig (HTTPS)", 20 | "Select data source": "Selecteer gegevensbron", 21 | "Set certificates or load it first in the system settings (right top).": "Stel certificaten in of laad ze eerst in de systeeminstellingen (rechtsboven).", 22 | "Unsecure_Auth": "Het wachtwoord wordt onversleuteld verzonden via een onveilige verbinding. Om uw wachtwoorden te beschermen, schakelt u de beveiligde verbinding (HTTPS) in!", 23 | "Use Lets Encrypt certificates": "Gebruik Let's Encrypt-certificaten", 24 | "Use this instance for automatic update": "Gebruik dit exemplaar voor automatische updates", 25 | "Warning!": "Waarschuwing!", 26 | "all": "alle", 27 | "enable_authentication_with_cors": "U kunt hier de header \"Access-Control-Allow-Origin\" definiëren. Het wordt aangeraden om authenticatie in te schakelen als u van plan bent om er van buitenaf toegang toe te krijgen.", 28 | "none": "geen", 29 | "place here": "Zet de bestanden hier neer" 30 | } 31 | -------------------------------------------------------------------------------- /admin/i18n/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Разрешить только когда пользователь является владельцем", 3 | "Allow origin (CORS)": "Разрешить происхождение (CORS)", 4 | "Authentication": "Аутентификация", 5 | "Authentication was deactivated": "Аутентификация была деактивирована", 6 | "Chained certificate": "Связанный сертификат", 7 | "Disable authentication": "Отключить аутентификацию", 8 | "Extend WEB adapter": "Использовать как расширение веб-адаптера", 9 | "IP": "ИС", 10 | "Ignore warning": "Игнорировать предупреждение", 11 | "Let's Encrypt settings": "Настройки Let's Encrypt", 12 | "List all datapoints": "Список всех штатов", 13 | "Listen on all IPs": "Прослушивание всех IP-адресов", 14 | "Port": "Порт", 15 | "Port to check the domain": "Порт для проверки домена", 16 | "Private certificate": "Частный сертификат", 17 | "Public certificate": "Публичный сертификат", 18 | "Run as": "Выполнить как пользователь", 19 | "Secure(HTTPS)": "Безопасный (HTTPS)", 20 | "Select data source": "Выберите источник данных", 21 | "Set certificates or load it first in the system settings (right top).": "Установите сертификаты или сначала загрузите их в настройках системы (справа вверху).", 22 | "Unsecure_Auth": "Пароль будет отправлен в незашифрованном виде через незащищенное соединение. Для защиты паролей включите защищенное соединение (HTTPS)!", 23 | "Use Lets Encrypt certificates": "Используйте сертификаты Let's Encrypt", 24 | "Use this instance for automatic update": "Используйте этот экземпляр для автоматического обновления", 25 | "Warning!": "Предупреждение!", 26 | "all": "все", 27 | "enable_authentication_with_cors": "Здесь можно определить заголовок \"Access-Control-Allow-Origin\". Рекомендуется включить аутентификацию, если вы планируете получить к нему доступ извне.", 28 | "none": "никто", 29 | "place here": "Перетащите файлы сюда" 30 | } 31 | -------------------------------------------------------------------------------- /admin/i18n/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Permitir sólo cuando el usuario sea el propietario", 3 | "Allow origin (CORS)": "Permitir origen (CORS)", 4 | "Authentication": "Autenticación", 5 | "Authentication was deactivated": "La autenticación fue desactivada", 6 | "Chained certificate": "Certificado encadenado", 7 | "Disable authentication": "Deshabilitar la autenticación", 8 | "Extend WEB adapter": "Usar como extensión del adaptador web", 9 | "IP": "Propiedad intelectual", 10 | "Ignore warning": "Ignorar la advertencia", 11 | "Let's Encrypt settings": "Configuración de Let's Encrypt", 12 | "List all datapoints": "Listar todos los estados", 13 | "Listen on all IPs": "Escuchar en todas las IP", 14 | "Port": "Puerto", 15 | "Port to check the domain": "Puerto para comprobar el dominio", 16 | "Private certificate": "Certificado privado", 17 | "Public certificate": "Certificado público", 18 | "Run as": "Ejecutar como usuario", 19 | "Secure(HTTPS)": "Seguro (HTTPS)", 20 | "Select data source": "Seleccionar fuente de datos", 21 | "Set certificates or load it first in the system settings (right top).": "Configure los certificados o cárguelos primero en la configuración del sistema (arriba a la derecha).", 22 | "Unsecure_Auth": "La contraseña se enviará sin cifrar mediante una conexión no segura. Para proteger sus contraseñas, active la conexión segura (HTTPS).", 23 | "Use Lets Encrypt certificates": "Utilice certificados Let's Encrypt", 24 | "Use this instance for automatic update": "Utilice esta instancia para la actualización automática", 25 | "Warning!": "¡Advertencia!", 26 | "all": "todo", 27 | "enable_authentication_with_cors": "Aquí puede definir el encabezado \"Access-Control-Allow-Origin\". Se recomienda habilitar la autenticación si planea acceder desde el exterior.", 28 | "none": "ninguno", 29 | "place here": "Suelta los archivos aquí" 30 | } 31 | -------------------------------------------------------------------------------- /admin/i18n/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Nur zulassen, wenn der Benutzer Eigentümer ist", 3 | "Allow origin (CORS)": "Ursprung zulassen (CORS)", 4 | "Authentication": "Authentifizierung", 5 | "Authentication was deactivated": "Authentifizierung wurde deaktiviert", 6 | "Chained certificate": "Verkettetes Zertifikat", 7 | "Disable authentication": "Authentifizierung deaktivieren", 8 | "Extend WEB adapter": "Verwendung als Webadapter-Erweiterung", 9 | "IP": "IP", 10 | "Ignore warning": "Warnung ignorieren", 11 | "Let's Encrypt settings": "Let’s Encrypt-Einstellungen", 12 | "List all datapoints": "Alle Bundesstaaten auflisten", 13 | "Listen on all IPs": "Hören Sie auf allen IPs", 14 | "Port": "Hafen", 15 | "Port to check the domain": "Port zur Überprüfung der Domäne", 16 | "Private certificate": "Privates Zertifikat", 17 | "Public certificate": "Öffentliches Zertifikat", 18 | "Run as": "Als Benutzer ausführen", 19 | "Secure(HTTPS)": "Sicher (HTTPS)", 20 | "Select data source": "Datenquelle auswählen", 21 | "Set certificates or load it first in the system settings (right top).": "Setze Zertifikate oder lade sie zuerst unter System/Einstellungen (oben rechts).", 22 | "Unsecure_Auth": "Das Passwort wird unverschlüsselt über eine unsichere Verbindung übermittelt. Um Ihre Passwörter zu schützen, aktivieren Sie die sichere Verbindung (HTTPS)!", 23 | "Use Lets Encrypt certificates": "Verwenden Sie Let’s Encrypt-Zertifikate", 24 | "Use this instance for automatic update": "Diese Instanz für automatische Updates verwenden", 25 | "Warning!": "Warnung!", 26 | "all": "alle", 27 | "enable_authentication_with_cors": "Hier können Sie den Header \"Access-Control-Allow-Origin\" definieren. Es wird empfohlen, die Authentifizierung zu aktivieren, wenn Sie von außen darauf zugreifen möchten.", 28 | "none": "keiner", 29 | "place here": "Legen Sie die Dateien hier ab" 30 | } 31 | -------------------------------------------------------------------------------- /admin/i18n/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Consenti solo quando l'utente è il proprietario", 3 | "Allow origin (CORS)": "Consenti origine (CORS)", 4 | "Authentication": "Autenticazione", 5 | "Authentication was deactivated": "L'autenticazione è stata disattivata", 6 | "Chained certificate": "Certificato concatenato", 7 | "Disable authentication": "Disabilitare l'autenticazione", 8 | "Extend WEB adapter": "Utilizzare come estensione dell'adattatore Web", 9 | "IP": "Proprietà intellettuale", 10 | "Ignore warning": "Ignora avviso", 11 | "Let's Encrypt settings": "Impostazioni di Let's Encrypt", 12 | "List all datapoints": "Elenca tutti gli stati", 13 | "Listen on all IPs": "Ascolta su tutti gli IP", 14 | "Port": "Porta", 15 | "Port to check the domain": "Porta per controllare il dominio", 16 | "Private certificate": "Certificato privato", 17 | "Public certificate": "Certificato pubblico", 18 | "Run as": "Esegui come utente", 19 | "Secure(HTTPS)": "Sicuro (HTTPS)", 20 | "Select data source": "Seleziona la fonte dei dati", 21 | "Set certificates or load it first in the system settings (right top).": "Impostare i certificati o caricarli prima nelle impostazioni di sistema (in alto a destra).", 22 | "Unsecure_Auth": "La password verrà inviata non crittografata tramite una connessione non sicura. Per proteggere le tue password abilita la connessione sicura (HTTPS)!", 23 | "Use Lets Encrypt certificates": "Utilizzare i certificati Let's Encrypt", 24 | "Use this instance for automatic update": "Utilizzare questa istanza per l'aggiornamento automatico", 25 | "Warning!": "Avvertimento!", 26 | "all": "Tutto", 27 | "enable_authentication_with_cors": "Qui puoi definire l'intestazione \"Access-Control-Allow-Origin\". Si consiglia di abilitare l'autenticazione se si prevede di accedervi dall'esterno.", 28 | "none": "nessuno", 29 | "place here": "Trascina i file qui" 30 | } 31 | -------------------------------------------------------------------------------- /admin/i18n/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Allow only when User is Owner": "Autoriser uniquement lorsque l'utilisateur est propriétaire", 3 | "Allow origin (CORS)": "Autoriser l'origine (CORS)", 4 | "Authentication": "Authentification", 5 | "Authentication was deactivated": "L'authentification a été désactivée", 6 | "Chained certificate": "Certificat chaîné", 7 | "Disable authentication": "Désactiver l'authentification", 8 | "Extend WEB adapter": "Utiliser comme extension d'adaptateur Web", 9 | "IP": "propriété intellectuelle", 10 | "Ignore warning": "Ignorer l'avertissement", 11 | "Let's Encrypt settings": "Paramètres de Let's Encrypt", 12 | "List all datapoints": "Lister tous les états", 13 | "Listen on all IPs": "Écouter sur toutes les IP", 14 | "Port": "Port", 15 | "Port to check the domain": "Port pour vérifier le domaine", 16 | "Private certificate": "Certificat privé", 17 | "Public certificate": "Certificat public", 18 | "Run as": "Exécuter en tant qu'utilisateur", 19 | "Secure(HTTPS)": "Sécurisé (HTTPS)", 20 | "Select data source": "Sélectionner la source de données", 21 | "Set certificates or load it first in the system settings (right top).": "Définissez les certificats ou chargez-les d'abord dans les paramètres système (en haut à droite).", 22 | "Unsecure_Auth": "Le mot de passe sera envoyé en clair via une connexion non sécurisée. Pour protéger vos mots de passe, activez la connexion sécurisée (HTTPS) !", 23 | "Use Lets Encrypt certificates": "Utiliser les certificats Let's Encrypt", 24 | "Use this instance for automatic update": "Utilisez cette instance pour la mise à jour automatique", 25 | "Warning!": "Avertissement!", 26 | "all": "tous", 27 | "enable_authentication_with_cors": "Vous pouvez définir ici l'en-tête « Access-Control-Allow-Origin ». Il est conseillé d'activer l'authentification si vous prévoyez d'y accéder depuis l'extérieur.", 28 | "none": "aucun", 29 | "place here": "Déposez les fichiers ici" 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iobroker.simple-api", 3 | "version": "3.0.7", 4 | "description": "RESTful interface for ioBroker.", 5 | "author": { 6 | "name": "bluefox", 7 | "email": "dogafox@gmail.com" 8 | }, 9 | "engines": { 10 | "node": ">=18" 11 | }, 12 | "contributors": [ 13 | { 14 | "name": "Apollon77", 15 | "email": "ingo@fischer-ka.de" 16 | }, 17 | { 18 | "name": "Marco.K", 19 | "email": "marco@kaminski-net.de" 20 | } 21 | ], 22 | "homepage": "https://github.com/ioBroker/ioBroker.simple-api", 23 | "keywords": [ 24 | "ioBroker", 25 | "web" 26 | ], 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/ioBroker/ioBroker.simple-api" 30 | }, 31 | "dependencies": { 32 | "@iobroker/adapter-core": "^3.2.3", 33 | "@iobroker/webserver": "^1.2.8", 34 | "body-parser": "^2.2.1", 35 | "cookie-parser": "^1.4.7", 36 | "express": "^4.21.2" 37 | }, 38 | "devDependencies": { 39 | "@alcalzone/release-script": "^3.8.0", 40 | "@alcalzone/release-script-plugin-iobroker": "^3.7.2", 41 | "@alcalzone/release-script-plugin-license": "^3.7.0", 42 | "@iobroker/adapter-dev": "^1.5.0", 43 | "@iobroker/eslint-config": "^2.2.0", 44 | "@iobroker/legacy-testing": "^2.0.2", 45 | "@iobroker/testing": "^5.2.2", 46 | "@iobroker/types": "^7.1.0", 47 | "@types/body-parser": "^1.19.6", 48 | "@types/cookie-parser": "^1.4.10", 49 | "@types/express": "^4.17.23", 50 | "@types/node": "^24.10.1", 51 | "axios": "^1.13.2", 52 | "chai": "^4.5.0", 53 | "mocha": "^11.7.5", 54 | "typescript": "^5.9.3" 55 | }, 56 | "bugs": { 57 | "url": "https://github.com/ioBroker/ioBroker.simple-api/issues" 58 | }, 59 | "main": "dist/main.js", 60 | "files": [ 61 | "admin/", 62 | "img/", 63 | "dist/", 64 | "io-package.json", 65 | "LICENSE" 66 | ], 67 | "scripts": { 68 | "test": "node node_modules/mocha/bin/mocha --exit", 69 | "build": "tsc -p tsconfig.build.json && node tasks", 70 | "lint": "eslint -c eslint.config.mjs", 71 | "release": "release-script", 72 | "release-patch": "release-script patch --yes", 73 | "release-minor": "release-script minor --yes", 74 | "release-major": "release-script major --yes", 75 | "translate": "translate-adapter" 76 | }, 77 | "license": "MIT" 78 | } 79 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /dist/lib/SimpleAPI.d.ts: -------------------------------------------------------------------------------- 1 | import type { Server as HttpServer } from 'node:http'; 2 | import type { Server as HttpsServer } from 'node:https'; 3 | import type { Express, Request, Response } from 'express'; 4 | export type Server = HttpServer | HttpsServer; 5 | declare const commandsPermissions: { 6 | [operation: string]: { 7 | type: 'state' | 'object' | ''; 8 | operation: 'read' | 'write' | 'list' | ''; 9 | }; 10 | }; 11 | type SimpleApiQuery = { 12 | user?: string; 13 | pass?: string; 14 | prettyPrint?: boolean; 15 | json?: boolean; 16 | noStringify?: boolean; 17 | wait?: number; 18 | timeRFC3339?: boolean; 19 | type?: ioBroker.CommonType | 'json'; 20 | callback?: string; 21 | ack: boolean; 22 | }; 23 | type IoBrokerStateWithIsoTime = (Omit & { 24 | ts?: string | number; 25 | lc?: string | number; 26 | }) | null; 27 | type CommandName = keyof typeof commandsPermissions; 28 | /** 29 | * SimpleAPI class 30 | * 31 | * From settings used only secure, auth and crossDomain 32 | * 33 | * @param webSettings settings of the web server, like
{secure: settings.secure, port: settings.port}
34 | * @param adapter web adapter object 35 | * @param instanceSettings instance object with common and native 36 | * @param app express application 37 | * @returns object instance 38 | */ 39 | export declare class SimpleAPI { 40 | private readonly adapter; 41 | private readonly settings; 42 | private readonly config; 43 | private readonly namespace; 44 | private readonly app?; 45 | private readonly cachedNames; 46 | private readonly cachedIds; 47 | private readonly restApiDelayed; 48 | constructor(_server: Server, webSettings: { 49 | secure: boolean; 50 | port: number | string; 51 | defaultUser?: string; 52 | auth?: boolean; 53 | language?: ioBroker.Languages; 54 | }, adapter: ioBroker.Adapter, instanceSettings: ioBroker.InstanceObject, app?: Express); 55 | static convertRelativeTime(relativeTime: string | undefined): number | null; 56 | isAuthenticated(req: Request & { 57 | user?: string; 58 | }, query: SimpleApiQuery): Promise; 59 | stateChange(id: string, state: ioBroker.State | null | undefined): void; 60 | objectChange(id: string, _obj: ioBroker.Object | null | undefined): void; 61 | static parseQuery(input: string | undefined, query: SimpleApiQuery, values: Record): void; 62 | setStates(values: Record, query: SimpleApiQuery): Promise<{ 63 | id?: string; 64 | val?: boolean | string | number; 65 | error?: string; 66 | }[]>; 67 | restApiPost(req: Request, res: Response, command: CommandName, oId: string[], values: Record, query: SimpleApiQuery): Promise; 68 | findState(idOrName: string, user: `system.user.${string}`): Promise<{ 69 | id: string; 70 | name: string; 71 | }>; 72 | getState(idOrName: string, user: `system.user.${string}`, query: SimpleApiQuery): Promise<{ 73 | state: IoBrokerStateWithIsoTime; 74 | id: string; 75 | }>; 76 | doResponse(res: Response, responseType: 'json' | 'plain', content?: any, query?: SimpleApiQuery): void; 77 | doErrorResponse(res: Response, responseType: 'json' | 'plain', status: 401 | 403 | 404 | 422 | 500, error?: string): void; 78 | checkPermissions(user: `system.user.${string}`, command: CommandName): Promise; 79 | setValue(id: string, value: ioBroker.StateValue, res: Response, wait: number, query: SimpleApiQuery, responseType: 'json' | 'plain'): Promise; 80 | restApi(req: Request, res: Response, overwriteUrl?: string): Promise; 81 | } 82 | export {}; 83 | -------------------------------------------------------------------------------- /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 | "xs": 12, 14 | "sm": 12, 15 | "md": 6, 16 | "lg": 3, 17 | "adapter": "web" 18 | }, 19 | "bind": { 20 | "hidden": "!!data.webInstance", 21 | "newLine": true, 22 | "type": "ip", 23 | "listenOnAllPorts": true, 24 | "label": "IP", 25 | "xs": 12, 26 | "sm": 12, 27 | "md": 8, 28 | "lg": 5 29 | }, 30 | "port": { 31 | "hidden": "!!data.webInstance", 32 | "type": "number", 33 | "min": 1, 34 | "max": 65565, 35 | "label": "Port", 36 | "xs": 12, 37 | "sm": 12, 38 | "md": 4, 39 | "lg": 3 40 | }, 41 | "secure": { 42 | "hidden": "!!data.webInstance", 43 | "newLine": true, 44 | "type": "checkbox", 45 | "label": "Secure(HTTPS)", 46 | "xs": 12, 47 | "sm": 12, 48 | "md": 6, 49 | "lg": 2 50 | }, 51 | "certPublic": { 52 | "type": "certificate", 53 | "hidden": "!data.secure || !!data.webInstance", 54 | "certType": "public", 55 | "validator": "!data.secure || data.certPublic", 56 | "label": "Public certificate", 57 | "xs": 12, 58 | "sm": 12, 59 | "md": 6, 60 | "lg": 2 61 | }, 62 | "certPrivate": { 63 | "hidden": "!data.secure || !!data.webInstance", 64 | "type": "certificate", 65 | "certType": "private", 66 | "validator": "!data.secure || data.certPrivate", 67 | "label": "Private certificate", 68 | "xs": 12, 69 | "sm": 12, 70 | "md": 6, 71 | "lg": 2 72 | }, 73 | "certChained": { 74 | "hidden": "!data.secure || !!data.webInstance", 75 | "type": "certificate", 76 | "certType": "chained", 77 | "label": "Chained certificate", 78 | "xs": 12, 79 | "sm": 12, 80 | "md": 6, 81 | "lg": 2 82 | }, 83 | "auth": { 84 | "newLine": true, 85 | "hidden": "!!data.webInstance", 86 | "type": "checkbox", 87 | "confirm": { 88 | "condition": "!data.secure && data.auth", 89 | "title": "Warning!", 90 | "text": "Unsecure_Auth", 91 | "ok": "Ignore warning", 92 | "cancel": "Disable authentication", 93 | "type": "warning", 94 | "alsoDependsOn": ["secure"] 95 | }, 96 | "label": "Authentication", 97 | "xs": 12, 98 | "sm": 12, 99 | "md": 6, 100 | "lg": 2 101 | }, 102 | "defaultUser": { 103 | "hidden": "!!data.auth || !!data.webInstance", 104 | "type": "user", 105 | "label": "Run as", 106 | "xs": 12, 107 | "sm": 12, 108 | "md": 6, 109 | "lg": 2 110 | }, 111 | "onlyAllowWhenUserIsOwner": { 112 | "newLine": true, 113 | "type": "checkbox", 114 | "label": "Allow only when User is Owner", 115 | "xs": 12, 116 | "sm": 12 117 | }, 118 | "dataSource": { 119 | "newLine": true, 120 | "type": "instance", 121 | "label": "Select data source", 122 | "xs": 12, 123 | "sm": 12, 124 | "md": 6, 125 | "lg": 3, 126 | "adapter": "_dataSources" 127 | }, 128 | "allDatapoints": { 129 | "type": "checkbox", 130 | "hidden": "!data.dataSource", 131 | "label": "List all datapoints", 132 | "xs": 12, 133 | "sm": 12, 134 | "md": 6, 135 | "lg": 3 136 | }, 137 | "accessControlAllowOrigin": { 138 | "type": "text", 139 | "label": "Allow origin (CORS)", 140 | "help": "enable_authentication_with_cors", 141 | "xs": 12, 142 | "sm": 12, 143 | "md": 6, 144 | "lg": 3 145 | } 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /.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@v5 33 | - name: Use Node.js 20.x 34 | uses: actions/setup-node@v6 35 | with: 36 | node-version: 20.x 37 | 38 | - name: Install Dependencies 39 | run: npm i 40 | 41 | # - name: Perform a type check 42 | # run: npm run check:ts 43 | # env: 44 | # CI: true 45 | # - name: Lint TypeScript code 46 | # run: npm run lint 47 | # - name: Test package files 48 | # run: npm run test:package 49 | 50 | # Runs adapter tests on all supported node versions and OSes 51 | adapter-tests: 52 | if: contains(github.event.head_commit.message, '[skip ci]') == false 53 | 54 | needs: [check-and-lint] 55 | 56 | runs-on: ${{ matrix.os }} 57 | strategy: 58 | matrix: 59 | node-version: [18.x, 20.x, 22.x] 60 | os: [ubuntu-latest, windows-latest, macos-latest] 61 | 62 | steps: 63 | - uses: actions/checkout@v5 64 | - name: Use Node.js ${{ matrix.node-version }} 65 | uses: actions/setup-node@v6 66 | with: 67 | node-version: ${{ matrix.node-version }} 68 | 69 | - name: Install Dependencies 70 | run: npm i 71 | 72 | - name: Build 73 | run: npm run build 74 | 75 | - name: Run local tests 76 | run: npm test 77 | # - name: Run unit tests 78 | # run: npm run test:unit 79 | # - name: Run integration tests # (linux/osx) 80 | # if: startsWith(runner.OS, 'windows') == false 81 | # run: DEBUG=testing:* npm run test:integration 82 | # - name: Run integration tests # (windows) 83 | # if: startsWith(runner.OS, 'windows') 84 | # run: set DEBUG=testing:* & npm run test:integration 85 | 86 | # Deploys the final package to NPM 87 | deploy: 88 | needs: [adapter-tests] 89 | 90 | # Trigger this step only when a commit on master is tagged with a version number 91 | if: | 92 | contains(github.event.head_commit.message, '[skip ci]') == false && 93 | github.event_name == 'push' && 94 | startsWith(github.ref, 'refs/tags/') 95 | runs-on: ubuntu-latest 96 | steps: 97 | - name: Checkout code 98 | uses: actions/checkout@v5 99 | 100 | - name: Use Node.js 20.x 101 | uses: actions/setup-node@v6 102 | with: 103 | node-version: 20.x 104 | 105 | - name: Extract the version and commit body from the tag 106 | id: extract_release 107 | # The body may be multiline, therefore we need to escape some characters 108 | run: | 109 | VERSION="${{ github.ref }}" 110 | VERSION=${VERSION##*/} 111 | VERSION=${VERSION##*v} 112 | echo "::set-output name=VERSION::$VERSION" 113 | BODY=$(git show -s --format=%b) 114 | BODY="${BODY//'%'/'%25'}" 115 | BODY="${BODY//$'\n'/'%0A'}" 116 | BODY="${BODY//$'\r'/'%0D'}" 117 | echo "::set-output name=BODY::$BODY" 118 | 119 | - name: Install Dependencies 120 | run: npm i 121 | 122 | - name: Build 123 | run: npm run build 124 | 125 | # - name: Create a clean build 126 | # run: npm run build 127 | - name: Publish package to npm 128 | run: | 129 | npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} 130 | npm whoami 131 | npm publish 132 | 133 | - name: Create Github Release 134 | uses: actions/create-release@v1 135 | env: 136 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 137 | with: 138 | tag_name: ${{ github.ref }} 139 | release_name: Release v${{ steps.extract_release.outputs.VERSION }} 140 | draft: false 141 | # Prerelease versions create pre-releases on GitHub 142 | prerelease: ${{ contains(steps.extract_release.outputs.VERSION, '-') }} 143 | body: ${{ steps.extract_release.outputs.BODY }} 144 | 145 | - name: Notify Sentry.io about the release 146 | run: | 147 | npm i -g @sentry/cli 148 | export SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} 149 | export SENTRY_URL=https://sentry.iobroker.net 150 | export SENTRY_ORG=iobroker 151 | export SENTRY_PROJECT=iobroker-simple-api 152 | export SENTRY_VERSION=iobroker.simple-api@${{ steps.extract_release.outputs.VERSION }} 153 | sentry-cli releases new $SENTRY_VERSION 154 | sentry-cli releases set-commits $SENTRY_VERSION --auto 155 | sentry-cli releases finalize $SENTRY_VERSION 156 | 157 | # Add the following line BEFORE finalize if sourcemap uploads are needed 158 | # sentry-cli releases files $SENTRY_VERSION upload-sourcemaps build/ 159 | -------------------------------------------------------------------------------- /dist/main.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /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 | exports.SimpleApiAdapter = void 0; 7 | const express_1 = __importDefault(require("express")); 8 | const node_fs_1 = require("node:fs"); 9 | const cookie_parser_1 = __importDefault(require("cookie-parser")); 10 | const body_parser_1 = __importDefault(require("body-parser")); 11 | const adapter_core_1 = require("@iobroker/adapter-core"); 12 | const webserver_1 = require("@iobroker/webserver"); 13 | const SimpleAPI_1 = require("./lib/SimpleAPI"); 14 | class SimpleApiAdapter extends adapter_core_1.Adapter { 15 | webServer = { 16 | app: null, 17 | server: null, 18 | api: null, 19 | }; 20 | certificates; 21 | constructor(options = {}) { 22 | super({ 23 | ...options, 24 | name: 'simple-api', 25 | unload: callback => this.onUnload(callback), 26 | stateChange: (id, state) => { 27 | this.webServer?.api?.stateChange(id, state); 28 | }, 29 | ready: () => this.main(), 30 | objectChange: (id, obj) => { 31 | this.webServer?.api?.objectChange(id, obj); 32 | }, 33 | }); 34 | } 35 | onUnload(callback) { 36 | try { 37 | if (this.webServer.server) { 38 | this.log.info(`terminating http${this.config.secure ? 's' : ''} server on port ${this.config.port}`); 39 | this.webServer.server.close(); 40 | this.webServer.server = null; 41 | } 42 | } 43 | catch { 44 | // ignore 45 | } 46 | if (callback) { 47 | callback(); 48 | } 49 | } 50 | async main() { 51 | if (this.config.webInstance) { 52 | console.log('Adapter runs as a part of web service'); 53 | this.log.warn('Adapter runs as a part of web service'); 54 | await this.setState('info.extension', true, true); 55 | return this.setForeignState(`system.adapter.${this.namespace}.alive`, false, true, () => this.setTimeout(() => (this.terminate ? this.terminate() : process.exit()), 1000)); 56 | } 57 | await this.setState('info.extension', false, true); 58 | if (this.config.secure) { 59 | // Load certificates 60 | await new Promise(resolve => this.getCertificates(undefined, undefined, undefined, (_err, certificates) => { 61 | this.certificates = certificates; 62 | resolve(); 63 | })); 64 | } 65 | await this.initWebServer(); 66 | } 67 | serveStatic = (req, res, next) => { 68 | if ((req.url || '').includes('favicon.ico')) { 69 | let stat; 70 | try { 71 | if ((0, node_fs_1.existsSync)(`${__dirname}/../img/favicon.ico`)) { 72 | stat = (0, node_fs_1.statSync)(`${__dirname}/../img/favicon.ico`); 73 | } 74 | } 75 | catch { 76 | // no special handling 77 | } 78 | if (stat) { 79 | res.writeHead(200, { 80 | 'Content-Type': 'image/x-icon', 81 | 'Content-Length': stat.size, 82 | }); 83 | const readStream = (0, node_fs_1.createReadStream)(`${__dirname}/../img/favicon.ico`); 84 | // We replaced all the event handlers with a simple call to readStream.pipe() 85 | readStream.pipe(res); 86 | } 87 | else { 88 | res.writeHead(404, { 'Content-Type': 'text/plain' }); 89 | res.write('404 Not Found\n'); 90 | res.end(); 91 | } 92 | } 93 | else { 94 | next(); 95 | } 96 | }; 97 | async initWebServer() { 98 | this.config.port = parseInt(this.config.port, 10); 99 | this.webServer.app = (0, express_1.default)(); 100 | this.webServer.app.use(this.serveStatic); 101 | if (this.config.port) { 102 | if (this.config.secure && !this.certificates) { 103 | return; 104 | } 105 | try { 106 | const webserver = new webserver_1.WebServer({ 107 | app: this.webServer.app, 108 | adapter: this, 109 | secure: this.config.secure, 110 | }); 111 | this.webServer.server = (await webserver.init()); 112 | if (this.config.auth) { 113 | // Install OAuth2 handler 114 | this.webServer.app.use((0, cookie_parser_1.default)()); 115 | this.webServer.app.use(body_parser_1.default.urlencoded({ extended: true })); 116 | this.webServer.app.use(body_parser_1.default.json()); 117 | (0, webserver_1.createOAuth2Server)(this, { 118 | app: this.webServer.app, 119 | secure: this.config.secure, 120 | accessLifetime: parseInt(this.config.ttl, 10) || 3600, 121 | }); 122 | } 123 | } 124 | catch (err) { 125 | this.log.error(`Cannot create webserver: ${err}`); 126 | this.terminate 127 | ? this.terminate(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION) 128 | : process.exit(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION); 129 | return; 130 | } 131 | if (!this.webServer.server) { 132 | this.log.error(`Cannot create webserver`); 133 | this.terminate 134 | ? this.terminate(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION) 135 | : process.exit(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION); 136 | return; 137 | } 138 | this.webServer.app.use((req, res) => this.webServer.api?.restApi(req, res)); 139 | this.webServer.server.__server = this.webServer; 140 | } 141 | else { 142 | this.log.error('port missing'); 143 | if (this.terminate) { 144 | this.terminate(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION); 145 | } 146 | else { 147 | process.exit(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION); 148 | } 149 | return; 150 | } 151 | if (this.webServer.server) { 152 | let serverListening = false; 153 | let serverPort = this.config.port; 154 | this.webServer.server.on('error', e => { 155 | if (e.toString().includes('EACCES') && serverPort <= 1024) { 156 | this.log.error(`node.js process has no rights to start server on the port ${serverPort}.\n` + 157 | `Do you know that on linux you need special permissions for ports under 1024?\n` + 158 | `You can call in shell following scrip to allow it for node.js: "iobroker fix"`); 159 | } 160 | else { 161 | this.log.error(`Cannot start server on ${this.config.bind || '0.0.0.0'}:${serverPort}: ${e}`); 162 | } 163 | if (!serverListening) { 164 | this.terminate 165 | ? this.terminate(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION) 166 | : process.exit(adapter_core_1.EXIT_CODES.ADAPTER_REQUESTED_TERMINATION); 167 | } 168 | }); 169 | this.getPort(this.config.port, !this.config.bind || this.config.bind === '0.0.0.0' ? undefined : this.config.bind || undefined, port => { 170 | if (port !== this.config.port) { 171 | this.log.error(`port ${this.config.port} already in use`); 172 | if (this.terminate) { 173 | this.terminate(1); 174 | } 175 | else { 176 | process.exit(1); 177 | } 178 | return; 179 | } 180 | serverPort = port; 181 | if (this.webServer.server) { 182 | // create web server 183 | this.webServer.server.listen(port, !this.config.bind || this.config.bind === '0.0.0.0' 184 | ? undefined 185 | : this.config.bind || undefined, () => (serverListening = true)); 186 | webserver_1.createOAuth2Server; 187 | this.log.info(`http${this.config.secure ? 's' : ''} server listening on port ${port}`); 188 | } 189 | else { 190 | this.log.error('server initialization failed'); 191 | if (this.terminate) { 192 | this.terminate(1); 193 | } 194 | else { 195 | process.exit(1); 196 | } 197 | } 198 | }); 199 | } 200 | this.webServer.api = new SimpleAPI_1.SimpleAPI(this.webServer.server, this.config, this, { 201 | native: this.config, 202 | _id: `system.adapter.${this.namespace}`, 203 | common: this.common, 204 | type: 'instance', 205 | objects: [], 206 | instanceObjects: [], 207 | }); 208 | } 209 | } 210 | exports.SimpleApiAdapter = SimpleApiAdapter; 211 | if (require.main !== module) { 212 | // Export the constructor in compact mode 213 | module.exports = (options) => new SimpleApiAdapter(options); 214 | } 215 | else { 216 | // otherwise start the instance directly 217 | (() => new SimpleApiAdapter())(); 218 | } 219 | //# sourceMappingURL=main.js.map -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import express, { type Express, type NextFunction, type Request, type Response } from 'express'; 2 | import { createReadStream, existsSync, type Stats, statSync } from 'node:fs'; 3 | import cookieParser from 'cookie-parser'; 4 | import bodyParser from 'body-parser'; 5 | 6 | import { Adapter, type AdapterOptions, EXIT_CODES } from '@iobroker/adapter-core'; 7 | import { createOAuth2Server, WebServer } from '@iobroker/webserver'; 8 | import { SimpleAPI, type Server } from './lib/SimpleAPI'; 9 | import type { SimpleApiAdapterConfig } from './types'; 10 | 11 | interface WebStructure { 12 | server: null | (Server & { __server: WebStructure }); 13 | api: SimpleAPI | null; 14 | app: Express | null; 15 | } 16 | 17 | export class SimpleApiAdapter extends Adapter { 18 | declare public config: SimpleApiAdapterConfig; 19 | private webServer: WebStructure = { 20 | app: null, 21 | server: null, 22 | api: null, 23 | }; 24 | private certificates: ioBroker.Certificates | undefined; 25 | 26 | public constructor(options: Partial = {}) { 27 | super({ 28 | ...options, 29 | name: 'simple-api', 30 | unload: callback => this.onUnload(callback), 31 | stateChange: (id, state) => { 32 | this.webServer?.api?.stateChange(id, state); 33 | }, 34 | ready: () => this.main(), 35 | objectChange: (id: string, obj: ioBroker.Object | null | undefined): void => { 36 | this.webServer?.api?.objectChange(id, obj); 37 | }, 38 | }); 39 | } 40 | 41 | onUnload(callback: () => void): void { 42 | try { 43 | if (this.webServer.server) { 44 | this.log.info(`terminating http${this.config.secure ? 's' : ''} server on port ${this.config.port}`); 45 | this.webServer.server.close(); 46 | this.webServer.server = null; 47 | } 48 | } catch { 49 | // ignore 50 | } 51 | if (callback) { 52 | callback(); 53 | } 54 | } 55 | 56 | async main(): Promise { 57 | if (this.config.webInstance) { 58 | console.log('Adapter runs as a part of web service'); 59 | this.log.warn('Adapter runs as a part of web service'); 60 | await this.setState('info.extension', true, true); 61 | return this.setForeignState(`system.adapter.${this.namespace}.alive`, false, true, () => 62 | this.setTimeout(() => (this.terminate ? this.terminate() : process.exit()), 1000), 63 | ); 64 | } 65 | 66 | await this.setState('info.extension', false, true); 67 | 68 | if (this.config.secure) { 69 | // Load certificates 70 | await new Promise(resolve => 71 | this.getCertificates(undefined, undefined, undefined, (_err, certificates): void => { 72 | this.certificates = certificates; 73 | resolve(); 74 | }), 75 | ); 76 | } 77 | 78 | await this.initWebServer(); 79 | } 80 | 81 | serveStatic = (req: Request, res: Response, next: NextFunction): void => { 82 | if ((req.url || '').includes('favicon.ico')) { 83 | let stat: Stats | undefined; 84 | try { 85 | if (existsSync(`${__dirname}/../img/favicon.ico`)) { 86 | stat = statSync(`${__dirname}/../img/favicon.ico`); 87 | } 88 | } catch { 89 | // no special handling 90 | } 91 | 92 | if (stat) { 93 | res.writeHead(200, { 94 | 'Content-Type': 'image/x-icon', 95 | 'Content-Length': stat.size, 96 | }); 97 | 98 | const readStream = createReadStream(`${__dirname}/../img/favicon.ico`); 99 | // We replaced all the event handlers with a simple call to readStream.pipe() 100 | readStream.pipe(res); 101 | } else { 102 | res.writeHead(404, { 'Content-Type': 'text/plain' }); 103 | res.write('404 Not Found\n'); 104 | res.end(); 105 | } 106 | } else { 107 | next(); 108 | } 109 | }; 110 | 111 | async initWebServer(): Promise { 112 | this.config.port = parseInt(this.config.port as string, 10); 113 | 114 | this.webServer.app = express(); 115 | this.webServer.app.use(this.serveStatic); 116 | 117 | if (this.config.port) { 118 | if (this.config.secure && !this.certificates) { 119 | return; 120 | } 121 | 122 | try { 123 | const webserver = new WebServer({ 124 | app: this.webServer.app, 125 | adapter: this, 126 | secure: this.config.secure, 127 | }); 128 | 129 | this.webServer.server = (await webserver.init()) as Server & { __server: WebStructure }; 130 | 131 | if (this.config.auth) { 132 | // Install OAuth2 handler 133 | this.webServer.app.use(cookieParser()); 134 | this.webServer.app.use(bodyParser.urlencoded({ extended: true })); 135 | this.webServer.app.use(bodyParser.json()); 136 | 137 | createOAuth2Server(this, { 138 | app: this.webServer.app, 139 | secure: this.config.secure, 140 | accessLifetime: parseInt(this.config.ttl as string, 10) || 3600, 141 | }); 142 | } 143 | } catch (err) { 144 | this.log.error(`Cannot create webserver: ${err}`); 145 | this.terminate 146 | ? this.terminate(EXIT_CODES.ADAPTER_REQUESTED_TERMINATION) 147 | : process.exit(EXIT_CODES.ADAPTER_REQUESTED_TERMINATION); 148 | return; 149 | } 150 | if (!this.webServer.server) { 151 | this.log.error(`Cannot create webserver`); 152 | this.terminate 153 | ? this.terminate(EXIT_CODES.ADAPTER_REQUESTED_TERMINATION) 154 | : process.exit(EXIT_CODES.ADAPTER_REQUESTED_TERMINATION); 155 | return; 156 | } 157 | 158 | this.webServer.app.use((req, res) => this.webServer.api?.restApi(req, res)); 159 | 160 | this.webServer.server.__server = this.webServer; 161 | } else { 162 | this.log.error('port missing'); 163 | if (this.terminate) { 164 | this.terminate(EXIT_CODES.ADAPTER_REQUESTED_TERMINATION); 165 | } else { 166 | process.exit(EXIT_CODES.ADAPTER_REQUESTED_TERMINATION); 167 | } 168 | return; 169 | } 170 | 171 | if (this.webServer.server) { 172 | let serverListening = false; 173 | let serverPort = this.config.port; 174 | 175 | this.webServer.server.on('error', e => { 176 | if (e.toString().includes('EACCES') && serverPort <= 1024) { 177 | this.log.error( 178 | `node.js process has no rights to start server on the port ${serverPort}.\n` + 179 | `Do you know that on linux you need special permissions for ports under 1024?\n` + 180 | `You can call in shell following scrip to allow it for node.js: "iobroker fix"`, 181 | ); 182 | } else { 183 | this.log.error(`Cannot start server on ${this.config.bind || '0.0.0.0'}:${serverPort}: ${e}`); 184 | } 185 | if (!serverListening) { 186 | this.terminate 187 | ? this.terminate(EXIT_CODES.ADAPTER_REQUESTED_TERMINATION) 188 | : process.exit(EXIT_CODES.ADAPTER_REQUESTED_TERMINATION); 189 | } 190 | }); 191 | 192 | this.getPort( 193 | this.config.port, 194 | !this.config.bind || this.config.bind === '0.0.0.0' ? undefined : this.config.bind || undefined, 195 | port => { 196 | if (port !== this.config.port) { 197 | this.log.error(`port ${this.config.port} already in use`); 198 | if (this.terminate) { 199 | this.terminate(1); 200 | } else { 201 | process.exit(1); 202 | } 203 | return; 204 | } 205 | serverPort = port; 206 | 207 | if (this.webServer.server) { 208 | // create web server 209 | this.webServer.server.listen( 210 | port, 211 | !this.config.bind || this.config.bind === '0.0.0.0' 212 | ? undefined 213 | : this.config.bind || undefined, 214 | () => (serverListening = true), 215 | ); 216 | 217 | createOAuth2Server; 218 | 219 | this.log.info(`http${this.config.secure ? 's' : ''} server listening on port ${port}`); 220 | } else { 221 | this.log.error('server initialization failed'); 222 | if (this.terminate) { 223 | this.terminate(1); 224 | } else { 225 | process.exit(1); 226 | } 227 | } 228 | }, 229 | ); 230 | } 231 | 232 | this.webServer.api = new SimpleAPI(this.webServer.server, this.config, this, { 233 | native: this.config, 234 | _id: `system.adapter.${this.namespace}`, 235 | common: this.common as ioBroker.InstanceCommon, 236 | type: 'instance', 237 | objects: [], 238 | instanceObjects: [], 239 | }); 240 | } 241 | } 242 | 243 | if (require.main !== module) { 244 | // Export the constructor in compact mode 245 | module.exports = (options: Partial | undefined) => new SimpleApiAdapter(options); 246 | } else { 247 | // otherwise start the instance directly 248 | (() => new SimpleApiAdapter())(); 249 | } 250 | -------------------------------------------------------------------------------- /test/testSsl.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect; 2 | const setup = require('@iobroker/legacy-testing'); 3 | const axios = require('axios'); 4 | const { Agent } = require('node:https'); 5 | 6 | let objects = null; 7 | let states = null; 8 | const httpsAgent = new Agent({ rejectUnauthorized: false }); 9 | 10 | process.env.NO_PROXY = 'localhost'; 11 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 12 | const TEST_STATE_ID = 'simple-api.0.testNumber'; 13 | 14 | function checkConnectionOfAdapter(cb, counter) { 15 | counter = counter || 0; 16 | console.log(`Try check #${counter}`); 17 | if (counter > 30) { 18 | if (cb) cb('Cannot check connection'); 19 | return; 20 | } 21 | 22 | states.getState('system.adapter.simple-api.0.alive', (err, state) => { 23 | if (err) console.error(err); 24 | if (state && state.val) { 25 | cb && cb(); 26 | } else { 27 | setTimeout(() => checkConnectionOfAdapter(cb, counter + 1), 1000); 28 | } 29 | }); 30 | } 31 | 32 | function createTestState(cb) { 33 | objects.setObject( 34 | TEST_STATE_ID, 35 | { 36 | _id: TEST_STATE_ID, 37 | type: 'state', 38 | common: { 39 | name: 'Test state', 40 | type: 'number', 41 | read: true, 42 | write: false, 43 | role: 'indicator.state', 44 | unit: '%', 45 | def: 0, 46 | desc: 'test state', 47 | }, 48 | native: {}, 49 | }, 50 | () => { 51 | states.setState(TEST_STATE_ID, { val: 0, ack: true }, () => { 52 | objects.setObject( 53 | 'javascript.0.test-boolean', 54 | { 55 | common: { 56 | name: 'test', 57 | type: 'boolean', 58 | role: 'value', 59 | def: 0, 60 | }, 61 | native: {}, 62 | type: 'state', 63 | }, 64 | err => { 65 | states.setState('javascript.0.test-boolean', { val: false, ack: false }, err => { 66 | cb(); 67 | }); 68 | }, 69 | ); 70 | }); 71 | }, 72 | ); 73 | } 74 | 75 | describe('Test RESTful API SSL', function () { 76 | before('Test RESTful API SSL: Start js-controller', function (_done) { 77 | this.timeout(600000); // because of the first installation from npm 78 | setup.adapterStarted = false; 79 | 80 | setup.setupController(async () => { 81 | const config = await setup.getAdapterConfig(); 82 | // enable adapter 83 | config.common.enabled = true; 84 | config.common.loglevel = 'debug'; 85 | config.native.port = 18183; 86 | config.native.auth = true; 87 | config.native.secure = true; 88 | config.native.certPublic = 'defaultPublic'; 89 | config.native.certPrivate = 'defaultPrivate'; 90 | 91 | await setup.setAdapterConfig(config.common, config.native); 92 | 93 | setup.startController((_objects, _states) => { 94 | objects = _objects; 95 | states = _states; 96 | // give some time to start server 97 | setTimeout(() => createTestState(() => _done()), 2000); 98 | }); 99 | }); 100 | }); 101 | 102 | it('Test adapter: Check if adapter started and create upload datapoint', done => { 103 | checkConnectionOfAdapter(res => { 104 | if (res) { 105 | console.log(res); 106 | } 107 | 108 | expect(res).not.to.be.equal('Cannot check connection'); 109 | 110 | objects.setObject( 111 | 'javascript.0.test-boolean', 112 | { 113 | common: { 114 | name: 'boolean', 115 | type: 'boolean', 116 | role: 'value', 117 | def: 0, 118 | }, 119 | native: {}, 120 | type: 'state', 121 | }, 122 | err => { 123 | expect(err).to.be.null; 124 | states.setState('javascript.0.test-boolean', true, err => { 125 | expect(err).to.be.null; 126 | done(); 127 | }); 128 | }, 129 | ); 130 | }); 131 | }).timeout(60000); 132 | 133 | it('Test RESTful API SSL: get - must return value', done => { 134 | axios 135 | .get('https://localhost:18183/get/system.adapter.simple-api.0.alive?user=admin&pass=iobroker', { 136 | httpsAgent, 137 | }) 138 | .then(response => { 139 | console.log(`get/system.adapter.simple-api.0.alive => ${JSON.stringify(response.data)}`); 140 | const obj = response.data; 141 | expect(obj).to.be.ok; 142 | expect(obj.val).to.be.true; 143 | expect(obj.ack).to.be.true; 144 | expect(obj.ts).to.be.ok; 145 | expect(obj.type).to.equal('state'); 146 | expect(obj._id).to.equal('system.adapter.simple-api.0.alive'); 147 | expect(obj.common).to.be.ok; 148 | expect(obj.native).to.be.ok; 149 | expect(obj.common.name).to.equal('simple-api.0 alive'); 150 | expect(obj.common.role).to.equal('indicator.state'); 151 | expect(response.status).to.equal(200); 152 | done(); 153 | }) 154 | .catch(error => { 155 | console.error(error); 156 | done(error); 157 | }); 158 | }); 159 | 160 | it('Test RESTful API SSL: get - must return value with basic authentication', done => { 161 | axios 162 | .get('https://localhost:18183/get/system.adapter.simple-api.0.alive', { 163 | auth: { 164 | username: 'admin', 165 | password: 'iobroker', 166 | }, 167 | httpsAgent, 168 | }) 169 | .then(response => { 170 | console.log(`get/system.adapter.simple-api.0.alive => ${JSON.stringify(response.data)}`); 171 | const obj = response.data; 172 | expect(obj).to.be.ok; 173 | expect(obj.val).to.be.true; 174 | expect(obj.ack).to.be.true; 175 | expect(obj.ts).to.be.ok; 176 | expect(obj.type).to.equal('state'); 177 | expect(obj._id).to.equal('system.adapter.simple-api.0.alive'); 178 | expect(obj.common).to.be.ok; 179 | expect(obj.native).to.be.ok; 180 | expect(obj.common.name).to.equal('simple-api.0 alive'); 181 | expect(obj.common.role).to.equal('indicator.state'); 182 | expect(response.status).to.equal(200); 183 | done(); 184 | }) 185 | .catch(error => { 186 | console.error(error); 187 | done(error); 188 | }); 189 | }); 190 | 191 | it('Test RESTful API SSL: get with no credentials', done => { 192 | axios 193 | .get('https://localhost:18183/get/system.adapter.simple-api.0.alive?user=admin&pass=io', { 194 | validateStatus: false, 195 | httpsAgent, 196 | }) 197 | .then(response => { 198 | console.log(`get/system.adapter.simple-api.0.alive => ${JSON.stringify(response.data)}`); 199 | expect(response.status).to.be.equal(401); 200 | done(); 201 | }) 202 | .catch(error => { 203 | console.error(error); 204 | done(error); 205 | }); 206 | }); 207 | 208 | it('Test RESTful API SSL: get with wrong credentials', done => { 209 | axios 210 | .get('https://localhost:18183/get/system.adapter.simple-api.0.alive', { validateStatus: false, httpsAgent }) 211 | .then(response => { 212 | console.log(`get/system.adapter.simple-api.0.alive => ${JSON.stringify(response.data)}`); 213 | expect(response.status).to.be.equal(401); 214 | done(); 215 | }) 216 | .catch(error => { 217 | console.error(error); 218 | done(error); 219 | }); 220 | }); 221 | 222 | it('Test RESTful API SSL: setBulk(POST) - must set values', done => { 223 | axios 224 | .post( 225 | 'https://127.0.0.1:18183/setBulk?user=admin&pass=iobroker', 226 | `${TEST_STATE_ID}=50&javascript.0.test-boolean=true`, 227 | { validateStatus: false, responseType: 'text', httpsAgent, headers: { 'Content-Type': 'text/plain' } }, 228 | ) 229 | .then(response => { 230 | console.log(`setBulk/?${TEST_STATE_ID}=50&javascript.0.test-boolean=true => ${response.data}`); 231 | const obj = JSON.parse(response.data); 232 | expect(obj).to.be.ok; 233 | expect(obj[0].val).to.be.equal(50); 234 | expect(obj[0].id).to.equal(TEST_STATE_ID); 235 | expect(obj[1].val).to.be.equal(true); 236 | expect(obj[1].id).to.equal('javascript.0.test-boolean'); 237 | expect(response.status).to.equal(200); 238 | 239 | return axios.get( 240 | `https://localhost:18183/getBulk/${TEST_STATE_ID},javascript.0.test-boolean?user=admin&pass=iobroker`, 241 | { 242 | validateStatus: false, 243 | responseType: 'text', 244 | httpsAgent, 245 | }, 246 | ); 247 | }) 248 | .then(response => { 249 | console.log(`getBulk/${TEST_STATE_ID},javascript.0.test-boolean => ${response.data}`); 250 | const obj = JSON.parse(response.data); 251 | expect(obj[0].val).equal(50); 252 | expect(obj[1].val).equal(true); 253 | expect(response.status).to.equal(200); 254 | done(); 255 | }) 256 | .catch(error => { 257 | console.log(`CAnnot get response: ${error}`); 258 | }); 259 | }); 260 | 261 | it('Test RESTful API SSL: setValueFromBody(POST) - must set values', done => { 262 | axios 263 | .post(`https://localhost:18183/setValueFromBody/${TEST_STATE_ID}?user=admin&pass=iobroker`, '55', { 264 | validateStatus: false, 265 | responseType: 'text', 266 | httpsAgent, 267 | headers: { 'Content-Type': 'text/plain' }, 268 | }) 269 | .then(response => { 270 | console.log(`setValueFromBody/?${TEST_STATE_ID} => ${response.data}`); 271 | const obj = JSON.parse(response.data); 272 | expect(obj).to.be.ok; 273 | expect(obj[0].val).to.be.equal(55); 274 | expect(obj[0].id).to.equal(TEST_STATE_ID); 275 | expect(response.status).to.equal(200); 276 | 277 | return axios.get(`https://localhost:18183/getBulk/${TEST_STATE_ID}?user=admin&pass=iobroker`, { 278 | validateStatus: false, 279 | responseType: 'text', 280 | httpsAgent, 281 | }); 282 | }) 283 | .then(response => { 284 | console.log(`getBulk/${TEST_STATE_ID} => ${response.data}`); 285 | const obj = JSON.parse(response.data); 286 | expect(obj[0].val).equal(55); 287 | expect(response.status).to.equal(200); 288 | done(); 289 | }) 290 | .catch(error => { 291 | console.error(error); 292 | done(error); 293 | }); 294 | }); 295 | 296 | after('Test RESTful API SSL: Stop js-controller', function (done) { 297 | this.timeout(9000); 298 | setup.stopController(normalTerminated => { 299 | console.log(`Adapter normal terminated: ${normalTerminated}`); 300 | setTimeout(done, 3000); 301 | }); 302 | }); 303 | }); 304 | -------------------------------------------------------------------------------- /io-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "name": "simple-api", 4 | "version": "3.0.7", 5 | "news": { 6 | "3.0.7": { 7 | "en": "corrected reading of history data", 8 | "de": "korrigiertes lesen von geschichte-daten", 9 | "ru": "исправленное чтение исторических данных", 10 | "pt": "leitura corrigida dos dados do histórico", 11 | "nl": "gecorrigeerd lezen van historische gegevens", 12 | "fr": "lecture corrigée des données d'historique", 13 | "it": "lettura corretta dei dati storici", 14 | "es": "lectura corregida de datos de historia", 15 | "pl": "poprawiony odczyt danych historycznych", 16 | "uk": "виправлено читання історичних даних", 17 | "zh-cn": "历史数据的校正读取" 18 | }, 19 | "3.0.6": { 20 | "en": "Added support for 'Access-Control-Allow-Origin'\nRemoved letsencrypt information\nAdded basic and OAuth2 authentication\nImplemented JSONP response\nImplemented relative times for query", 21 | "de": "Unterstützung für 'Access-Control-Allow-Origin '\nEntfernte letsencrypt Informationen\nHinzugefügt grundlegende und OAuth2 Authentifizierung\nUmsetzung der JSONP-Antwort\nErgänzte Relativzeiten für Abfrage", 22 | "ru": "Добавлена поддержка «Access-Control-Allow-Origin» \"\nУдалённая информация Letsencrypt\nДобавлена базовая и OAuth2 аутентификация\nРеакция JSONP\nРеализовано относительное время для запроса", 23 | "pt": "Adicionado suporte para 'Access-Control-Allow-Origin '\nRemover informação letsencrypt\nAdicionada autenticação básica e OAuth2\nResposta JSONP implementada\nTempos relativos implementados para consulta", 24 | "nl": "Toegevoegde ondersteuning voor 'Access-Control-Allow-Origin '\nVerwijderde letsencrypt-informatie\nBasis- en OAuth2-authenticatie toegevoegd\nGeïmplementeerd JSONP-antwoord\nGeïmplementeerde relatieve tijden voor query", 25 | "fr": "Ajout du support pour 'Access-Control-Autorisation-Origine '\nSupprimé les informations letsencrypt\nAjout de l'authentification basique et OAuth2\nMise en œuvre de la réponse du JSONP\nTemps relatifs mis en œuvre pour la requête", 26 | "it": "Aggiunto il supporto per 'Access-Control-Allow-Origin '\nRimuovete le informazioni letsencrypt\nAggiunta l'autenticazione di base e OAuth2\nRisposta JSONP implementata\nTempo relativo implementato per query", 27 | "es": "Apoyo añadido para 'Access-Control-Allow-Origin '\nQuitar información de letsencriptada\nAutenticación básica y OAuth2\nRespuesta aplicada a JSONP\nTiempos relativos aplicados para la consulta", 28 | "pl": "Dodano wsparcie dla 'Akcesoria - Control- Allow- Pochodzenie'\nUsunięto informacje o szyfrowaniu listów\nDodano podstawowe i uwierzytelnianie OAuth2\nWdrożona odpowiedź JSONP\nWdrożony względny czas zapytania", 29 | "uk": "Додано підтримку \"Access-Control-Allow-Origin\" Р\nВилучена інформація\nДодано базову та автентифікацію OAuth2\nРеалізовано відповідь JSONP\nРеалізовано відносні часи для запиту", 30 | "zh-cn": "添加对“ 访问控制- Allow- Origin” 的支持 '\n删除后允许加密信息\n添加基本认证和 OAuth2 认证\n已执行的JSONP反应\n已执行查询的相对时间" 31 | }, 32 | "3.0.5": { 33 | "en": "Corrected the indication of running mode in admin\nCorrected the writing of numeric values\nClear cache after 10 minutes", 34 | "de": "Korrektur der Anzeige des Betriebsmodus in admin\nKorrektur des Schreibens von numerischen Werten\nCache nach 10 Minuten", 35 | "ru": "Исправлено указание режима работы в админ\nИсправлено написание числовых значений\nЧистый кэш через 10 минут", 36 | "pt": "Corrigido a indicação do modo de execução no admin\nCorrigido a escrita de valores numéricos\nLimpar o cache após 10 minutos", 37 | "nl": "De indicatie van de lopende modus in admin gecorrigeerd\nGecorrigeerd schrijven van numerieke waarden\nCache wissen na 10 minuten", 38 | "fr": "Correction de l'indication du mode d'exécution en admin\nCorrection de l'écriture des valeurs numériques\nEffacer le cache après 10 minutes", 39 | "it": "Corretto l'indicazione della modalità di esecuzione in admin\nCorretto la scrittura dei valori numerici\nCancella la cache dopo 10 minuti", 40 | "es": "Corregido la indicación del modo de funcionamiento en admin\nCorregido la escritura de valores numéricos\nCaché transparente después de 10 minutos", 41 | "pl": "Poprawiono wskazanie trybu pracy w admin\nKorekta zapisu wartości liczbowych\nWyczyść pamięć podręczną po 10 minutach", 42 | "uk": "Виправлено показання режиму роботи в адміні\nВиправлено написання нумеричних значень\nОчистити кеш через 10 хвилин", 43 | "zh-cn": "纠正管理器中运行模式的提示\n更正数值的写入\n10分钟后清除缓存" 44 | }, 45 | "3.0.0": { 46 | "en": "Updated packages\nMigrated to TypeScript\nIf State/Object not found, the response will be 404 (and not 500)\nIf a user has no permission, the response will be 403 (and not 401)", 47 | "de": "Aktualisierte Pakete\nZu TypeScript migriert\nWenn Staat/Ziel nicht gefunden wird, wird die Antwort 404 (und nicht 500)\nWenn ein Benutzer keine Erlaubnis hat, wird die Antwort 403 (und nicht 401)", 48 | "ru": "Обновленные пакеты\nИммиграция в TypeScript\nЕсли состояние/объект не найден, ответ будет 404 (а не 500)\nЕсли у пользователя нет разрешения, ответ будет 403 (а не 401)", 49 | "pt": "Pacotes atualizados\nMigrado para TypeScript\nSe o Estado/Objeto não for encontrado, a resposta será 404 (e não 500)\nSe um usuário não tiver permissão, a resposta será 403 (e não 401)", 50 | "nl": "Bijgewerkte pakketten\nNaar typeScript migreren\nAls staat/object niet wordt gevonden, zal het antwoord 404 (en niet 500) zijn\nAls een gebruiker geen toestemming heeft, zal het antwoord 403 zijn (en niet 401)", 51 | "fr": "Paquets mis à jour\nMigré vers TypeScript\nSi l'État/l'objet n'est pas trouvé, la réponse sera 404 (et non 500)\nSi un utilisateur n'a pas la permission, la réponse sera 403 (et non 401)", 52 | "it": "Pacchetti aggiornati\nMigrato a TypeScript\nSe State/Obiettivo non trovato, la risposta sarà 404 (e non 500)\nSe un utente non ha il permesso, la risposta sarà 403 (e non 401)", 53 | "es": "Paquetes actualizados\nMigrado a TipoScript\nSi el Estado/Objeto no se encuentra, la respuesta será 404 (y no 500)\nSi un usuario no tiene permiso, la respuesta será 403 (y no 401)", 54 | "pl": "Aktualizacja pakietów\nMigrated to TypeScript\nJeżeli stan / obiekt nie zostanie znaleziony, odpowiedź wyniesie 404 (a nie 500)\nJeśli użytkownik nie ma pozwolenia, odpowiedź będzie 403 (a nie 401)", 55 | "uk": "Оновлені пакети\nMigrated до TypeScript\nЯкщо не знайдено державного/об’єкта, відповідь буде 404 (і не 500)\nЯкщо користувач не має дозволу, відповідь буде 403 (і не 401)", 56 | "zh-cn": "更新软件包\n移动到类型脚本\n如果国家/目标未找到,答复将是404(而不是500)\n如果用户未获许可,则回复为403(而不是401)" 57 | }, 58 | "2.8.0": { 59 | "en": "ported to `@iobroker/webserver`", 60 | "de": "an `@iobroker/webserver `", 61 | "ru": "в порту `@iobroker/webserver \"", 62 | "pt": "portado para `@iobroker/webserver \"", 63 | "nl": "geporteerd naar .@iobroker/webserver wat", 64 | "fr": "porté à `@iobroker/webserver \"", 65 | "it": "inviato a `@iobroker/webserver #", 66 | "es": "portada a `@iobroker/webserver `", 67 | "pl": "wysłany do '@ iobroker / webserver'", 68 | "uk": "сайт: www.iobroker.com й", 69 | "zh-cn": "移植到 xqio 经纪人/网络服务器 `" 70 | }, 71 | "2.7.2": { 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 | "zh-cn": "今后防污版本的编制" 82 | }, 83 | "2.7.1": { 84 | "en": "Check if the port is occupied only on defined interface\nAdded JSON config", 85 | "de": "Überprüfen Sie, ob der Port nur auf definierter Schnittstelle besetzt ist\nJSON config", 86 | "ru": "Проверьте, занят ли порт только на определенном интерфейсе\nДобавлено JSON config", 87 | "pt": "Verifique se a porta está ocupada apenas na interface definida\nAdicionado JSON config", 88 | "nl": "Controleer of de haven alleen bezet is op definitieve interface\n_", 89 | "fr": "Vérifiez si le port n'est occupé que sur l'interface définie\nAjouté JSON config", 90 | "it": "Controllare se la porta è occupata solo su interfaccia definita\nAggiunto JSON config", 91 | "es": "Compruebe si el puerto está ocupado sólo en la interfaz definida\nConfiguración JSON", 92 | "pl": "Jeśli port jest zajęty tylko na określonym interfejsie\nStrona internetowa JSON", 93 | "zh-cn": "如果港口只在界定的界线上被占有,则该港口将被扣押。\n增 编" 94 | } 95 | }, 96 | "titleLang": { 97 | "en": "Simple RESTful API", 98 | "de": "Einfache RESTful API", 99 | "ru": "Простой RESTful API", 100 | "pt": "API RESTful simples", 101 | "nl": "Eenvoudige RESTful API", 102 | "fr": "API RESTful simple", 103 | "it": "API RESTful semplice", 104 | "es": "API RESTful simple", 105 | "pl": "Prosty interfejs API RESTful", 106 | "uk": "Простий RESTful API", 107 | "zh-cn": "简单的RESTful API" 108 | }, 109 | "desc": { 110 | "en": "This adapter allows to read and write ioBroker objects and state with web RESTful API", 111 | "de": "Dieser Adapter ermöglicht das Lesen und Schreiben von ioBroker-Objekten und den Status mit der Web-RESTful-API", 112 | "ru": "Этот адаптер позволяет читать и записывать объекты и состояния ioBroker с помощью веб-RESTful API", 113 | "pt": "Esse adaptador permite ler e gravar objetos ioBroker e declarar com a API RESTful da web", 114 | "nl": "Deze adapter maakt het mogelijk om ioBroker-objecten te lezen en te schrijven en aan te geven met de web RESTful API", 115 | "fr": "Cet adaptateur permet de lire et d'écrire des objets et des états ioBroker avec l'API Web RESTful", 116 | "it": "Questo adattatore consente di leggere e scrivere oggetti e stato di ioBroker con l'API RESTful web", 117 | "es": "Este adaptador permite leer y escribir objetos y estados ioBroker con API RESTful web", 118 | "pl": "Ten adapter umożliwia odczyt i zapis obiektów ioBroker oraz ich stan za pomocą web RESTful API", 119 | "uk": "Цей адаптер дозволяє читати та записувати об'єкти та стан ioBroker за допомогою веб-RESTful API", 120 | "zh-cn": "该适配器允许使用Web RESTful API读写ioBroker对象和状态" 121 | }, 122 | "authors": [ 123 | "bluefox ", 124 | "Apollon77 ", 125 | "Marco.K " 126 | ], 127 | "platform": "Javascript/Node.js", 128 | "mode": "daemon", 129 | "loglevel": "info", 130 | "icon": "simple-api.svg", 131 | "webExtension": "dist/lib/SimpleAPI.js", 132 | "readme": "https://github.com/ioBroker/ioBroker.simple-api/blob/master/README.md", 133 | "keywords": [ 134 | "web", 135 | "simpleAPI", 136 | "RESTful", 137 | "communication" 138 | ], 139 | "enabled": true, 140 | "compact": true, 141 | "extIcon": "https://raw.githubusercontent.com/ioBroker/ioBroker.simple-api/master/admin/simple-api.svg", 142 | "type": "communication", 143 | "stopBeforeUpdate": true, 144 | "dependencies": [ 145 | { 146 | "js-controller": ">=6.0.17" 147 | } 148 | ], 149 | "globalDependencies": [ 150 | { 151 | "admin": ">=6.17.14" 152 | } 153 | ], 154 | "adminUI": { 155 | "config": "json" 156 | }, 157 | "plugins": { 158 | "sentry": { 159 | "dsn": "https://1ad1b116fa644ec29e6f5d724b39f999@sentry.iobroker.net/12" 160 | } 161 | }, 162 | "connectionType": "none", 163 | "dataSource": "none", 164 | "tier": 3, 165 | "licenseInformation": { 166 | "type": "free", 167 | "license": "MIT" 168 | }, 169 | "messages": [ 170 | { 171 | "condition": { 172 | "operand": "and", 173 | "rules": [ 174 | "oldVersion<3.0.0", 175 | "newVersion>=3.0.0" 176 | ] 177 | }, 178 | "title": { 179 | "en": "Important information about working as web extension", 180 | "de": "Wichtige Informationen zur Arbeit als Weberweiterung", 181 | "ru": "Важная информация о работе в качестве веб-расширения", 182 | "pt": "Informações importantes sobre o trabalho como extensão da web", 183 | "nl": "Belangrijke informatie over het werken als webextensie", 184 | "fr": "Informations importantes sur le travail en tant qu'extension Web", 185 | "it": "Informazioni importanti sul lavoro come estensione web", 186 | "es": "Información importante sobre el trabajo como extensión web", 187 | "pl": "Ważne informacje o pracy jako rozszerzenie sieciowe", 188 | "zh-cn": "关于作为Web扩展工作的相关重要信息", 189 | "uk": "Важлива інформація про роботу як веб-розширення" 190 | }, 191 | "text": { 192 | "en": "When the adapter is configured to work as a web extension, no own local port is opened anymore", 193 | "de": "Wenn der Adapter so konfiguriert ist, dass er als Weberweiterung arbeitet, wird kein eigener lokaler Port mehr geöffnet", 194 | "ru": "Когда адаптер настроен на работу в качестве веб-расширения, больше не открывается собственный локальный порт", 195 | "pt": "Quando o adaptador é configurado para funcionar como uma extensão da web, nenhuma porta local própria é mais aberta", 196 | "nl": "Wanneer de adapter is geconfigureerd om als webextensie te werken, wordt er geen eigen lokale poort meer geopend", 197 | "fr": "Lorsque l'adaptateur est configuré pour fonctionner comme une extension Web, aucun port local propre n'est plus ouvert", 198 | "it": "Quando l'adattatore è configurato per funzionare come estensione web, non viene più aperta alcuna porta locale propria", 199 | "es": "Cuando el adaptador está configurado para funcionar como una extensión web, ya no se abre ningún puerto local propio", 200 | "pl": "Kiedy adapter jest skonfigurowany do pracy jako rozszerzenie sieciowe, nie jest już otwierany żaden własny lokalny port", 201 | "zh-cn": "当适配器配置为作为Web扩展工作时,不再打开自己的本地端口", 202 | "uk": "Коли адаптер налаштований на роботу як веб-розширення, більше не відкривається власний локальний порт" 203 | }, 204 | "level": "warn", 205 | "buttons": [ 206 | "ok", 207 | "cancel" 208 | ] 209 | } 210 | ] 211 | }, 212 | "native": { 213 | "port": 8087, 214 | "auth": false, 215 | "secure": false, 216 | "bind": "0.0.0.0", 217 | "certPublic": "", 218 | "certPrivate": "", 219 | "certChained": "", 220 | "defaultUser": "admin", 221 | "onlyAllowWhenUserIsOwner": false, 222 | "webInstance": "", 223 | "leEnabled": false, 224 | "leUpdate": false, 225 | "leCheckPort": 80, 226 | "dataSource": "", 227 | "allDatapoints": false, 228 | "accessControlAllowOrigin": "*" 229 | }, 230 | "instanceObjects": [ 231 | { 232 | "_id": "info", 233 | "type": "channel", 234 | "common": { 235 | "name": { 236 | "en": "Information", 237 | "de": "Information", 238 | "ru": "Информация", 239 | "pt": "Em formação", 240 | "nl": "Informatie", 241 | "fr": "Information", 242 | "it": "Informazione", 243 | "es": "Información", 244 | "pl": "Informacja", 245 | "uk": "Інформація", 246 | "zh-cn": "信息" 247 | } 248 | }, 249 | "native": {} 250 | }, 251 | { 252 | "_id": "info.extension", 253 | "type": "state", 254 | "common": { 255 | "role": "indicator", 256 | "name": "If instance is in only extension mode", 257 | "type": "boolean", 258 | "read": true, 259 | "expert": true, 260 | "write": false, 261 | "def": false 262 | }, 263 | "native": {} 264 | } 265 | ] 266 | } 267 | -------------------------------------------------------------------------------- /test/testApiAsLimitedUser.js: -------------------------------------------------------------------------------- 1 | /* jshint -W097 */ 2 | /* jshint strict: false */ 3 | /* jslint node: true */ 4 | /* jshint expr: true*/ 5 | 6 | const expect = require('chai').expect; 7 | const setup = require('@iobroker/legacy-testing'); 8 | const axios = require('axios'); 9 | 10 | let objects = null; 11 | let states = null; 12 | 13 | const PORT = 18183; 14 | const TEST_STATE_ID = 'simple-api.0.testNumber'; 15 | 16 | process.env.NO_PROXY = '127.0.0.1'; 17 | 18 | function checkConnectionOfAdapter(cb, counter) { 19 | counter = counter || 0; 20 | console.log(`Try check #${counter}`); 21 | if (counter > 30) { 22 | if (cb) cb('Cannot check connection'); 23 | return; 24 | } 25 | 26 | states.getState('system.adapter.simple-api.0.alive', (err, state) => { 27 | if (err) console.error(err); 28 | if (state && state.val) { 29 | cb && cb(); 30 | } else { 31 | setTimeout(() => checkConnectionOfAdapter(cb, counter + 1), 1000); 32 | } 33 | }); 34 | } 35 | 36 | function createTestState(cb) { 37 | objects.setObject( 38 | TEST_STATE_ID, 39 | { 40 | _id: TEST_STATE_ID, 41 | type: 'state', 42 | common: { 43 | name: 'Test state', 44 | type: 'number', 45 | read: true, 46 | write: false, 47 | role: 'indicator.state', 48 | unit: '%', 49 | def: 0, 50 | desc: 'test state', 51 | }, 52 | native: {}, 53 | }, 54 | () => { 55 | states.setState(TEST_STATE_ID, { val: 0, ack: true }, cb); 56 | }, 57 | ); 58 | } 59 | 60 | describe('Test RESTful API as Owner-User', function () { 61 | before('Test RESTful API as Owner-User: Start js-controller', function (_done) { 62 | this.timeout(600000); // because of the first installation from npm 63 | setup.adapterStarted = false; 64 | 65 | setup.setupController(async () => { 66 | const config = await setup.getAdapterConfig(); 67 | // enable adapter 68 | config.common.enabled = true; 69 | config.common.loglevel = 'debug'; 70 | config.native.port = PORT; 71 | config.native.defaultUser = 'myuser'; 72 | config.native.onlyAllowWhenUserIsOwner = true; 73 | await setup.setAdapterConfig(config.common, config.native); 74 | 75 | setup.startController((_objects, _states) => { 76 | objects = _objects; 77 | states = _states; 78 | // give some time to start server 79 | setTimeout(() => createTestState(() => _done()), 2000); 80 | }); 81 | }); 82 | }); 83 | 84 | it('Test adapter: Check if adapter started and create upload datapoint', done => { 85 | checkConnectionOfAdapter(res => { 86 | res && console.log(res); 87 | 88 | expect(res).not.to.be.equal('Cannot check connection'); 89 | 90 | objects.setObject( 91 | 'system.group.writer', 92 | { 93 | common: { 94 | name: 'Writer', 95 | desc: '', 96 | members: ['system.user.myuser'], 97 | acl: { 98 | object: { 99 | list: true, 100 | read: true, 101 | write: false, 102 | delete: false, 103 | }, 104 | state: { 105 | list: false, 106 | read: true, 107 | write: true, 108 | create: false, 109 | delete: false, 110 | }, 111 | users: { 112 | write: false, 113 | create: false, 114 | delete: false, 115 | }, 116 | other: { 117 | execute: false, 118 | http: false, 119 | sendto: false, 120 | }, 121 | file: { 122 | list: false, 123 | read: false, 124 | write: false, 125 | create: false, 126 | delete: false, 127 | }, 128 | }, 129 | }, 130 | native: {}, 131 | acl: { 132 | object: 1638, 133 | owner: 'system.user.admin', 134 | ownerGroup: 'system.group.administrator', 135 | }, 136 | _id: 'system.group.writer', 137 | type: 'group', 138 | }, 139 | err => { 140 | expect(err).to.be.null; 141 | 142 | objects.setObject( 143 | 'system.user.myuser', 144 | { 145 | type: 'user', 146 | common: { 147 | name: 'myuser', 148 | enabled: true, 149 | groups: [], 150 | password: 151 | 'pbkdf2$10000$ab4104d8bb68390ee7e6c9397588e768de6c025f0c732c18806f3d1270c83f83fa86a7bf62583770e5f8d0b405fbb3ad32214ef3584f5f9332478f2506414443a910bf15863b36ebfcaa7cbb19253ae32cd3ca390dab87b29cd31e11be7fa4ea3a01dad625d9de44e412680e1a694227698788d71f1e089e5831dc1bbacfa794b45e1c995214bf71ee4160d98b4305fa4c3e36ee5f8da19b3708f68e7d2e8197375c0f763d90e31143eb04760cc2148c8f54937b9385c95db1742595634ed004fa567655dfe1d9b9fa698074a9fb70c05a252b2d9cf7ca1c9b009f2cd70d6972ccf0ee281d777d66a0346c6c6525436dd7fe3578b28dca2c7adbfde0ecd45148$31c3248ba4dc9600a024b4e0e7c3e585', 152 | }, 153 | _id: 'system.user.myuser', 154 | native: {}, 155 | acl: { 156 | object: 1638, 157 | }, 158 | }, 159 | err => { 160 | expect(err).to.be.null; 161 | objects.setObject( 162 | 'javascript.0.test', 163 | { 164 | common: { 165 | name: 'test', 166 | type: 'number', 167 | role: 'level', 168 | min: -100, 169 | max: 100, 170 | def: 1, 171 | }, 172 | native: {}, 173 | type: 'state', 174 | acl: { 175 | object: 1638, 176 | owner: 'system.user.myuser', 177 | ownerGroup: 'system.group.administrator', 178 | state: 1638, 179 | }, 180 | }, 181 | err => { 182 | expect(err).to.be.null; 183 | states.setState('javascript.0.test', 1, err => { 184 | console.log(`END javascript.0.test ${err}`); 185 | expect(err).to.be.null; 186 | objects.setObject( 187 | 'javascript.0.test-number', 188 | { 189 | common: { 190 | name: 'test', 191 | type: 'number', 192 | role: 'value', 193 | def: 0, 194 | }, 195 | native: {}, 196 | type: 'state', 197 | }, 198 | err => { 199 | expect(err).to.be.null; 200 | states.setState('javascript.0.test-number', 0, err => { 201 | expect(err).to.be.null; 202 | done(); 203 | }); 204 | }, 205 | ); 206 | }); 207 | }, 208 | ); 209 | }, 210 | ); 211 | }, 212 | ); 213 | }); 214 | }).timeout(60000); 215 | 216 | it('Test RESTful API as Owner-User: get - must return value', done => { 217 | console.log('START get/system.adapter.simple-api.0.alive'); 218 | axios 219 | .get(`http://127.0.0.1:${PORT}/get/system.adapter.simple-api.0.alive`, { validateStatus: false, responseType: 'text' }) 220 | .then(response => { 221 | console.log(`get/system.adapter.simple-api.0.alive => ${response.data}`); 222 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 223 | expect(response.status).to.equal(403); 224 | done(); 225 | }) 226 | .catch(error => { 227 | console.error(error); 228 | done(error); 229 | }); 230 | }); 231 | 232 | it('Test RESTful API as Owner-User: getPlainValue - must return plain value', done => { 233 | axios 234 | .get(`http://127.0.0.1:${PORT}/getPlainValue/system.adapter.simple-api.0.alive`, { 235 | validateStatus: false, 236 | responseType: 'text', 237 | }) 238 | .then(response => { 239 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 240 | expect(response.data).to.be.equal('error: permissionError'); 241 | expect(response.status).to.equal(403); 242 | done(); 243 | }) 244 | .catch(error => { 245 | console.error(error); 246 | done(error); 247 | }); 248 | }); 249 | 250 | it('Test RESTful API as Owner-User: getPlainValue 4 Test-Endpoint - must return plain value', done => { 251 | axios 252 | .get(`http://127.0.0.1:${PORT}/getPlainValue/javascript.0.test`, { 253 | responseType: 'text', 254 | }) 255 | .then(response => { 256 | console.log(`getPlainValue/javascript.0.test => ${response.data}`); 257 | expect(response.data).equal('1'); 258 | expect(response.status).to.equal(200); 259 | done(); 260 | }) 261 | .catch(error => { 262 | console.error(error); 263 | done(error); 264 | }); 265 | }); 266 | 267 | it('Test RESTful API as Owner-User: set 4 Test-Endpoint - must set value', done => { 268 | axios 269 | .get(`http://127.0.0.1:${PORT}/set/javascript.0.test?val=2`) 270 | .then(response => { 271 | console.log(`set/javascript.0.test?val=false => ${response.data}`); 272 | const obj = response.data; 273 | expect(obj).to.be.ok; 274 | expect(obj.val).to.be.equal(2); 275 | expect(obj.id).to.equal('javascript.0.test'); 276 | expect(response.status).to.equal(200); 277 | return axios.get(`http://127.0.0.1:${PORT}/getPlainValue/javascript.0.test`, { 278 | responseType: 'text', 279 | }); 280 | }) 281 | .then(response => { 282 | console.log(`getPlainValue/javascript.0.test => ${response.data}`); 283 | expect(response.data).equal('2'); 284 | expect(response.status).to.equal(200); 285 | done(); 286 | }) 287 | .catch(error => { 288 | console.error(error); 289 | done(error); 290 | }); 291 | }); 292 | 293 | it('Test RESTful API as Owner-User: set - must set value', done => { 294 | axios 295 | .get(`http://127.0.0.1:${PORT}/set/system.adapter.simple-api.0.alive?val=false`, { 296 | validateStatus: false, 297 | responseType: 'text', 298 | }) 299 | .then(response => { 300 | console.log(`set/system.adapter.simple-api.0.alive?val=false => ${response.data}`); 301 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 302 | expect(response.status).to.equal(403); 303 | done(); 304 | }) 305 | .catch(error => { 306 | console.error(error); 307 | done(error); 308 | }); 309 | }); 310 | 311 | it('Test RESTful API as Owner-User: set - must set val', done => { 312 | axios 313 | .get(`http://127.0.0.1:${PORT}/set/system.adapter.simple-api.0.alive?val=true`, { 314 | validateStatus: false, 315 | responseType: 'text', 316 | }) 317 | .then(response => { 318 | console.log(`set/system.adapter.simple-api.0.alive?val=true => ${response.data}`); 319 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 320 | expect(response.status).to.equal(403); 321 | done(); 322 | }) 323 | .catch(error => { 324 | console.error(error); 325 | done(error); 326 | }); 327 | }); 328 | 329 | it('Test RESTful API as Owner-User: objects - must return objects', done => { 330 | axios 331 | .get(`http://127.0.0.1:${PORT}/objects?pattern=system.adapter.*`) 332 | .then(response => { 333 | //console.log('objects?pattern=system.adapter.* => ' + response.data); 334 | expect(!!response.data).to.be.true; 335 | expect(response.status).to.equal(200); 336 | done(); 337 | }) 338 | .catch(error => { 339 | console.error(error); 340 | done(error); 341 | }); 342 | }); 343 | 344 | it('Test RESTful API as Owner-User: objects - must return objects', done => { 345 | axios 346 | .get(`http://127.0.0.1:${PORT}/objects?pattern=system.adapter.*&type=instance`) 347 | .then(response => { 348 | //console.log('objects?pattern=system.adapter.* => ' + response.data); 349 | //expect(response.data).to.be.equal('error: permissionError'); 350 | expect(!!response.data).to.be.true; 351 | expect(response.status).to.equal(200); 352 | done(); 353 | }) 354 | .catch(error => { 355 | console.error(error); 356 | done(error); 357 | }); 358 | }); 359 | 360 | it('Test RESTful API as Owner-User: states - must return states', done => { 361 | axios 362 | .get(`http://127.0.0.1:${PORT}/states?pattern=system.adapter.*`, { 363 | validateStatus: false, 364 | responseType: 'text', 365 | }) 366 | .then(response => { 367 | console.log(`states?pattern=system.adapter.* => ${response.data}`); 368 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 369 | expect(response.status).to.equal(403); 370 | done(); 371 | }) 372 | .catch(error => { 373 | console.error(error); 374 | done(error); 375 | }); 376 | }); 377 | 378 | it('Test RESTful API as Owner-User: setValueFromBody(POST) - must set one value', done => { 379 | axios 380 | .post(`http://127.0.0.1:${PORT}/setValueFromBody/${TEST_STATE_ID}`, '55', { 381 | validateStatus: false, 382 | responseType: 'text', 383 | }) 384 | .then(response => { 385 | console.log(`setValueFromBody/?${TEST_STATE_ID} => ${JSON.stringify(response.data)}`); 386 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 387 | expect(response.status).to.equal(403); 388 | done(); 389 | }) 390 | .catch(error => { 391 | console.error(error); 392 | done(error); 393 | }); 394 | }); 395 | 396 | after('Test RESTful API as Owner-User: Stop js-controller', function (done) { 397 | this.timeout(9000); 398 | setup.stopController(normalTerminated => { 399 | console.log(`Adapter normal terminated: ${normalTerminated}`); 400 | setTimeout(done, 3000); 401 | }); 402 | }); 403 | }); 404 | -------------------------------------------------------------------------------- /admin/words.js: -------------------------------------------------------------------------------- 1 | /*global systemDictionary:true */ 2 | /* 3 | +===================== DO NOT MODIFY ======================+ 4 | | This file was generated by translate-adapter, please use | 5 | | `translate-adapter adminLanguages2words` to update it. | 6 | +===================== DO NOT MODIFY ======================+ 7 | */ 8 | 'use strict'; 9 | 10 | systemDictionary = { 11 | "Allow only when User is Owner": { "en": "Allow only when User is Owner", "de": "Nur zulassen, wenn der Benutzer Eigentümer ist", "ru": "Разрешить только когда пользователь является владельцем", "pt": "Permitir somente quando o usuário for proprietário", "nl": "Alleen toestaan wanneer de gebruiker eigenaar is", "fr": "Autoriser uniquement lorsque l'utilisateur est propriétaire", "it": "Consenti solo quando l'utente è il proprietario", "es": "Permitir sólo cuando el usuario sea el propietario", "pl": "Zezwól tylko, gdy użytkownik jest właścicielem", "uk": "Дозволити лише тоді, коли користувач є власником", "zh-cn": "仅当用户是所有者时才允许"}, 12 | "Allow origin (CORS)": { "en": "Allow origin (CORS)", "de": "Ursprung zulassen (CORS)", "ru": "Разрешить происхождение (CORS)", "pt": "Permitir origem (CORS)", "nl": "Oorsprong toestaan (CORS)", "fr": "Autoriser l'origine (CORS)", "it": "Consenti origine (CORS)", "es": "Permitir origen (CORS)", "pl": "Zezwalaj na pochodzenie (CORS)", "uk": "Дозволити походження (CORS)", "zh-cn": "允许来源 (CORS)"}, 13 | "Authentication": { "en": "Authentication", "de": "Authentifizierung", "ru": "Аутентификация", "pt": "Autenticação", "nl": "Authenticatie", "fr": "Authentification", "it": "Autenticazione", "es": "Autenticación", "pl": "Uwierzytelnianie", "uk": "Аутентифікація", "zh-cn": "验证"}, 14 | "Authentication was deactivated": { "en": "Authentication was deactivated", "de": "Authentifizierung wurde deaktiviert", "ru": "Аутентификация была деактивирована", "pt": "A autenticação foi desativada", "nl": "Authenticatie is gedeactiveerd", "fr": "L'authentification a été désactivée", "it": "L'autenticazione è stata disattivata", "es": "La autenticación fue desactivada", "pl": "Uwierzytelnianie zostało wyłączone", "uk": "Автентифікацію було вимкнено", "zh-cn": "身份验证已停用"}, 15 | "Chained certificate": { "en": "Chained certificate", "de": "Verkettetes Zertifikat", "ru": "Связанный сертификат", "pt": "Certificado encadeado", "nl": "Gekoppeld certificaat", "fr": "Certificat chaîné", "it": "Certificato concatenato", "es": "Certificado encadenado", "pl": "Certyfikat łańcuchowy", "uk": "Прикутий сертифікат", "zh-cn": "链式证书"}, 16 | "Disable authentication": { "en": "Disable authentication", "de": "Authentifizierung deaktivieren", "ru": "Отключить аутентификацию", "pt": "Desativar autenticação", "nl": "Authenticatie uitschakelen", "fr": "Désactiver l'authentification", "it": "Disabilitare l'autenticazione", "es": "Deshabilitar la autenticación", "pl": "Wyłącz uwierzytelnianie", "uk": "Вимкнути автентифікацію", "zh-cn": "禁用身份验证"}, 17 | "Extend WEB adapter": { "en": "Use as web adapter extension", "de": "Verwendung als Webadapter-Erweiterung", "ru": "Использовать как расширение веб-адаптера", "pt": "Usar como extensão do adaptador web", "nl": "Gebruik als webadapter-extensie", "fr": "Utiliser comme extension d'adaptateur Web", "it": "Utilizzare come estensione dell'adattatore Web", "es": "Usar como extensión del adaptador web", "pl": "Użyj jako rozszerzenia adaptera internetowego", "uk": "Використовувати як розширення веб-адаптера", "zh-cn": "用作 Web 适配器扩展"}, 18 | "IP": { "en": "IP", "de": "IP", "ru": "ИС", "pt": "Propriedade Intelectual", "nl": "IE", "fr": "propriété intellectuelle", "it": "Proprietà intellettuale", "es": "Propiedad intelectual", "pl": "IP", "uk": "IP", "zh-cn": "知识产权"}, 19 | "Ignore warning": { "en": "Ignore warning", "de": "Warnung ignorieren", "ru": "Игнорировать предупреждение", "pt": "Ignorar aviso", "nl": "Negeer waarschuwing", "fr": "Ignorer l'avertissement", "it": "Ignora avviso", "es": "Ignorar la advertencia", "pl": "Zignoruj ostrzeżenie", "uk": "Ігнорувати попередження", "zh-cn": "忽略警告"}, 20 | "Let's Encrypt settings": { "en": "Let's Encrypt settings", "de": "Let’s Encrypt-Einstellungen", "ru": "Настройки Let's Encrypt", "pt": "Configurações do Let's Encrypt", "nl": "Let's Encrypt-instellingen", "fr": "Paramètres de Let's Encrypt", "it": "Impostazioni di Let's Encrypt", "es": "Configuración de Let's Encrypt", "pl": "Ustawienia Let's Encrypt", "uk": "Давайте зашифруємо налаштування", "zh-cn": "Let's Encrypt 设置"}, 21 | "List all datapoints": { "en": "List all states", "de": "Alle Bundesstaaten auflisten", "ru": "Список всех штатов", "pt": "Listar todos os estados", "nl": "Lijst alle staten", "fr": "Lister tous les états", "it": "Elenca tutti gli stati", "es": "Listar todos los estados", "pl": "Wypisz wszystkie stany", "uk": "Перелічіть усі штати", "zh-cn": "列出所有州"}, 22 | "Listen on all IPs": { "en": "Listen on all IPs", "de": "Hören Sie auf allen IPs", "ru": "Прослушивание всех IP-адресов", "pt": "Ouça em todos os IPs", "nl": "Luister op alle IP's", "fr": "Écouter sur toutes les IP", "it": "Ascolta su tutti gli IP", "es": "Escuchar en todas las IP", "pl": "Słuchaj na wszystkich IP", "uk": "Прослуховування на всіх IP", "zh-cn": "监听所有 IP"}, 23 | "Port": { "en": "Port", "de": "Hafen", "ru": "Порт", "pt": "Porta", "nl": "Haven", "fr": "Port", "it": "Porta", "es": "Puerto", "pl": "Port", "uk": "Порт", "zh-cn": "港口"}, 24 | "Port to check the domain": { "en": "Port to check the domain", "de": "Port zur Überprüfung der Domäne", "ru": "Порт для проверки домена", "pt": "Porta para verificar o domínio", "nl": "Poort om het domein te controleren", "fr": "Port pour vérifier le domaine", "it": "Porta per controllare il dominio", "es": "Puerto para comprobar el dominio", "pl": "Port do sprawdzenia domeny", "uk": "Порт для перевірки домену", "zh-cn": "检查域名的端口"}, 25 | "Private certificate": { "en": "Private certificate", "de": "Privates Zertifikat", "ru": "Частный сертификат", "pt": "Certificado privado", "nl": "Privécertificaat", "fr": "Certificat privé", "it": "Certificato privato", "es": "Certificado privado", "pl": "Certyfikat prywatny", "uk": "Приватний сертифікат", "zh-cn": "私人证书"}, 26 | "Public certificate": { "en": "Public certificate", "de": "Öffentliches Zertifikat", "ru": "Публичный сертификат", "pt": "Certificado público", "nl": "Openbaar certificaat", "fr": "Certificat public", "it": "Certificato pubblico", "es": "Certificado público", "pl": "Certyfikat publiczny", "uk": "Публічний сертифікат", "zh-cn": "公共证书"}, 27 | "Run as": { "en": "Execute as user", "de": "Als Benutzer ausführen", "ru": "Выполнить как пользователь", "pt": "Executar como usuário", "nl": "Uitvoeren als gebruiker", "fr": "Exécuter en tant qu'utilisateur", "it": "Esegui come utente", "es": "Ejecutar como usuario", "pl": "Wykonaj jako użytkownik", "uk": "Виконати як користувач", "zh-cn": "以用户身份执行"}, 28 | "Secure(HTTPS)": { "en": "Secure (HTTPS)", "de": "Sicher (HTTPS)", "ru": "Безопасный (HTTPS)", "pt": "Seguro (HTTPS)", "nl": "Veilig (HTTPS)", "fr": "Sécurisé (HTTPS)", "it": "Sicuro (HTTPS)", "es": "Seguro (HTTPS)", "pl": "Bezpieczny (HTTPS)", "uk": "Безпечний (HTTPS)", "zh-cn": "安全(HTTPS)"}, 29 | "Select data source": { "en": "Select data source", "de": "Datenquelle auswählen", "ru": "Выберите источник данных", "pt": "Selecione a fonte de dados", "nl": "Selecteer gegevensbron", "fr": "Sélectionner la source de données", "it": "Seleziona la fonte dei dati", "es": "Seleccionar fuente de datos", "pl": "Wybierz źródło danych", "uk": "Виберіть джерело даних", "zh-cn": "选择数据源"}, 30 | "Set certificates or load it first in the system settings (right top).": {"en": "Set certificates or load it first in the system settings (right top).", "de": "Setze Zertifikate oder lade sie zuerst unter System/Einstellungen (oben rechts).", "ru": "Установите сертификаты или сначала загрузите их в настройках системы (справа вверху).", "pt": "Defina os certificados ou carregue-os primeiro nas configurações do sistema (canto superior direito).", "nl": "Stel certificaten in of laad ze eerst in de systeeminstellingen (rechtsboven).", "fr": "Définissez les certificats ou chargez-les d'abord dans les paramètres système (en haut à droite).", "it": "Impostare i certificati o caricarli prima nelle impostazioni di sistema (in alto a destra).", "es": "Configure los certificados o cárguelos primero en la configuración del sistema (arriba a la derecha).", "pl": "Ustaw certyfikaty lub najpierw załaduj je w ustawieniach systemu (prawy górny róg).", "uk": "Спершу встановіть сертифікати або завантажте їх у налаштуваннях системи (справа вгорі).", "zh-cn": "在系统设置(右上角)中设置证书或先加载。"}, 31 | "Unsecure_Auth": { "en": "The password will be sent unencrypted via an unsecure connection. To protect your passwords enable the secure connection (HTTPS)!", "de": "Das Passwort wird unverschlüsselt über eine unsichere Verbindung übermittelt. Um Ihre Passwörter zu schützen, aktivieren Sie die sichere Verbindung (HTTPS)!", "ru": "Пароль будет отправлен в незашифрованном виде через незащищенное соединение. Для защиты паролей включите защищенное соединение (HTTPS)!", "pt": "A senha será enviada sem criptografia por meio de uma conexão não segura. Para proteger suas senhas, habilite a conexão segura (HTTPS)!", "nl": "Het wachtwoord wordt onversleuteld verzonden via een onveilige verbinding. Om uw wachtwoorden te beschermen, schakelt u de beveiligde verbinding (HTTPS) in!", "fr": "Le mot de passe sera envoyé en clair via une connexion non sécurisée. Pour protéger vos mots de passe, activez la connexion sécurisée (HTTPS) !", "it": "La password verrà inviata non crittografata tramite una connessione non sicura. Per proteggere le tue password abilita la connessione sicura (HTTPS)!", "es": "La contraseña se enviará sin cifrar mediante una conexión no segura. Para proteger sus contraseñas, active la conexión segura (HTTPS).", "pl": "Hasło zostanie wysłane bez szyfrowania za pośrednictwem niezabezpieczonego połączenia. Aby chronić swoje hasła, włącz bezpieczne połączenie (HTTPS)!", "uk": "Пароль буде надіслано незашифрованим через незахищене з’єднання. Щоб захистити свої паролі, увімкніть безпечне з'єднання (HTTPS)!", "zh-cn": "密码将通过不安全的连接以未加密形式发送。要保护您的密码,请启用安全连接 (HTTPS)!"}, 32 | "Use Lets Encrypt certificates": { "en": "Use Let's Encrypt certificates", "de": "Verwenden Sie Let’s Encrypt-Zertifikate", "ru": "Используйте сертификаты Let's Encrypt", "pt": "Use certificados Let's Encrypt", "nl": "Gebruik Let's Encrypt-certificaten", "fr": "Utiliser les certificats Let's Encrypt", "it": "Utilizzare i certificati Let's Encrypt", "es": "Utilice certificados Let's Encrypt", "pl": "Użyj certyfikatów Let's Encrypt", "uk": "Використовуйте сертифікати Let's Encrypt", "zh-cn": "使用 Let's Encrypt 证书"}, 33 | "Use this instance for automatic update": { "en": "Use this instance for automatic update", "de": "Diese Instanz für automatische Updates verwenden", "ru": "Используйте этот экземпляр для автоматического обновления", "pt": "Use esta instância para atualização automática", "nl": "Gebruik dit exemplaar voor automatische updates", "fr": "Utilisez cette instance pour la mise à jour automatique", "it": "Utilizzare questa istanza per l'aggiornamento automatico", "es": "Utilice esta instancia para la actualización automática", "pl": "Użyj tej instancji do automatycznej aktualizacji", "uk": "Використовуйте цей екземпляр для автоматичного оновлення", "zh-cn": "使用此实例进行自动更新"}, 34 | "Warning!": { "en": "Warning!", "de": "Warnung!", "ru": "Предупреждение!", "pt": "Aviso!", "nl": "Waarschuwing!", "fr": "Avertissement!", "it": "Avvertimento!", "es": "¡Advertencia!", "pl": "Ostrzeżenie!", "uk": "УВАГА!", "zh-cn": "警告!"}, 35 | "all": { "en": "all", "de": "alle", "ru": "все", "pt": "todos", "nl": "alle", "fr": "tous", "it": "Tutto", "es": "todo", "pl": "Wszystko", "uk": "все", "zh-cn": "全部"}, 36 | "enable_authentication_with_cors": { "en": "You can define here the \"Access-Control-Allow-Origin\" header. It is suggested to enable authentication if you plan to access it from outside.", "de": "Hier können Sie den Header \"Access-Control-Allow-Origin\" definieren. Es wird empfohlen, die Authentifizierung zu aktivieren, wenn Sie von außen darauf zugreifen möchten.", "ru": "Здесь можно определить заголовок \"Access-Control-Allow-Origin\". Рекомендуется включить аутентификацию, если вы планируете получить к нему доступ извне.", "pt": "Você pode definir aqui o cabeçalho \"Access-Control-Allow-Origin\". É sugerido habilitar a autenticação se você planeja acessá-lo de fora.", "nl": "U kunt hier de header \"Access-Control-Allow-Origin\" definiëren. Het wordt aangeraden om authenticatie in te schakelen als u van plan bent om er van buitenaf toegang toe te krijgen.", "fr": "Vous pouvez définir ici l'en-tête « Access-Control-Allow-Origin ». Il est conseillé d'activer l'authentification si vous prévoyez d'y accéder depuis l'extérieur.", "it": "Qui puoi definire l'intestazione \"Access-Control-Allow-Origin\". Si consiglia di abilitare l'autenticazione se si prevede di accedervi dall'esterno.", "es": "Aquí puede definir el encabezado \"Access-Control-Allow-Origin\". Se recomienda habilitar la autenticación si planea acceder desde el exterior.", "pl": "Tutaj możesz zdefiniować nagłówek „Access-Control-Allow-Origin”. Zaleca się włączenie uwierzytelniania, jeśli planujesz uzyskać do niego dostęp z zewnątrz.", "uk": "Тут можна визначити заголовок \"Access-Control-Allow-Origin\". Рекомендується ввімкнути автентифікацію, якщо ви плануєте отримати до нього доступ ззовні.", "zh-cn": "您可以在此处定义“Access-Control-Allow-Origin”标头。如果您计划从外部访问,建议启用身份验证。"}, 37 | "none": { "en": "none", "de": "keiner", "ru": "никто", "pt": "nenhum", "nl": "geen", "fr": "aucun", "it": "nessuno", "es": "ninguno", "pl": "nic", "uk": "немає", "zh-cn": "没有任何"}, 38 | "place here": { "en": "Drop the files here", "de": "Legen Sie die Dateien hier ab", "ru": "Перетащите файлы сюда", "pt": "Solte os arquivos aqui", "nl": "Zet de bestanden hier neer", "fr": "Déposez les fichiers ici", "it": "Trascina i file qui", "es": "Suelta los archivos aquí", "pl": "Upuść pliki tutaj", "uk": "Перетягніть файли сюди", "zh-cn": "将文件拖放到此处"}, 39 | }; -------------------------------------------------------------------------------- /test/testApiAsUserNoRights.js: -------------------------------------------------------------------------------- 1 | /* jshint -W097 */ // jshint strict:false 2 | /*jslint node: true */ 3 | /*jshint expr: true*/ 4 | const expect = require('chai').expect; 5 | const setup = require('@iobroker/legacy-testing'); 6 | const axios = require('axios'); 7 | 8 | let objects = null; 9 | let states = null; 10 | const TEST_STATE_ID = 'simple-api.0.testNumber'; 11 | 12 | process.env.NO_PROXY = '127.0.0.1'; 13 | 14 | function checkConnectionOfAdapter(cb, counter) { 15 | counter = counter || 0; 16 | console.log(`Try check #${counter}`); 17 | if (counter > 30) { 18 | if (cb) cb('Cannot check connection'); 19 | return; 20 | } 21 | 22 | states.getState('system.adapter.simple-api.0.alive', function (err, state) { 23 | if (err) console.error(err); 24 | if (state && state.val) { 25 | if (cb) cb(); 26 | } else { 27 | setTimeout(function () { 28 | checkConnectionOfAdapter(cb, counter + 1); 29 | }, 1000); 30 | } 31 | }); 32 | } 33 | 34 | function createTestState(cb) { 35 | objects.setObject( 36 | TEST_STATE_ID, 37 | { 38 | _id: TEST_STATE_ID, 39 | type: 'state', 40 | common: { 41 | name: 'Test state', 42 | type: 'number', 43 | read: true, 44 | write: false, 45 | role: 'indicator.state', 46 | unit: '%', 47 | def: 0, 48 | desc: 'test state', 49 | }, 50 | native: {}, 51 | }, 52 | () => { 53 | states.setState(TEST_STATE_ID, { val: 0, ack: true }, cb); 54 | }, 55 | ); 56 | } 57 | 58 | describe('Test RESTful API as User(No rights)', function () { 59 | before('Test RESTful API as User:(No rights) Start js-controller', function (_done) { 60 | this.timeout(600000); // because of the first installation from npm 61 | setup.adapterStarted = false; 62 | 63 | setup.setupController(async () => { 64 | const config = await setup.getAdapterConfig(); 65 | // enable adapter 66 | config.common.enabled = true; 67 | config.common.loglevel = 'debug'; 68 | config.native.port = 18183; 69 | config.native.defaultUser = 'norights'; 70 | await setup.setAdapterConfig(config.common, config.native); 71 | 72 | setup.startController((_objects, _states) => { 73 | objects = _objects; 74 | states = _states; 75 | // give some time to start server 76 | setTimeout(() => createTestState(() => _done()), 2000); 77 | }); 78 | }); 79 | }); 80 | 81 | it('Test adapter: Check if adapter started and create upload datapoint', done => { 82 | this.timeout(60000); 83 | checkConnectionOfAdapter(res => { 84 | if (res) { 85 | console.log(res); 86 | } 87 | expect(res).not.to.be.equal('Cannot check connection'); 88 | objects.setObject( 89 | 'system.group.norights', 90 | { 91 | common: { 92 | name: 'norights', 93 | desc: '', 94 | members: ['system.user.norights'], 95 | acl: { 96 | object: { 97 | list: false, 98 | read: false, 99 | write: false, 100 | delete: false, 101 | }, 102 | state: { 103 | list: true, 104 | read: true, 105 | write: true, 106 | create: false, 107 | delete: false, 108 | }, 109 | users: { 110 | write: false, 111 | create: false, 112 | delete: false, 113 | }, 114 | other: { 115 | execute: false, 116 | http: false, 117 | sendto: false, 118 | }, 119 | file: { 120 | list: false, 121 | read: false, 122 | write: false, 123 | create: false, 124 | delete: false, 125 | }, 126 | }, 127 | }, 128 | native: {}, 129 | acl: { 130 | object: 1638, 131 | owner: 'system.user.admin', 132 | ownerGroup: 'system.group.administrator', 133 | }, 134 | _id: 'system.group.norights', 135 | type: 'group', 136 | }, 137 | function (err) { 138 | expect(err).to.be.null; 139 | 140 | objects.setObject( 141 | 'system.user.norights', 142 | { 143 | type: 'user', 144 | common: { 145 | name: 'norights', 146 | enabled: true, 147 | groups: [], 148 | password: 149 | 'pbkdf2$10000$ab4104d8bb68390ee7e6c9397588e768de6c025f0c732c18806f3d1270c83f83fa86a7bf62583770e5f8d0b405fbb3ad32214ef3584f5f9332478f2506414443a910bf15863b36ebfcaa7cbb19253ae32cd3ca390dab87b29cd31e11be7fa4ea3a01dad625d9de44e412680e1a694227698788d71f1e089e5831dc1bbacfa794b45e1c995214bf71ee4160d98b4305fa4c3e36ee5f8da19b3708f68e7d2e8197375c0f763d90e31143eb04760cc2148c8f54937b9385c95db1742595634ed004fa567655dfe1d9b9fa698074a9fb70c05a252b2d9cf7ca1c9b009f2cd70d6972ccf0ee281d777d66a0346c6c6525436dd7fe3578b28dca2c7adbfde0ecd45148$31c3248ba4dc9600a024b4e0e7c3e585', 150 | }, 151 | _id: 'system.user.norights', 152 | native: {}, 153 | acl: { 154 | object: 1638, 155 | }, 156 | }, 157 | function (err) { 158 | expect(err).to.be.null; 159 | objects.setObject( 160 | 'javascript.0.test', 161 | { 162 | common: { 163 | name: 'test', 164 | type: 'number', 165 | role: 'level', 166 | min: -100, 167 | max: 100, 168 | def: 1, 169 | }, 170 | native: {}, 171 | type: 'state', 172 | acl: { 173 | object: 1638, 174 | owner: 'system.user.norights', 175 | ownerGroup: 'system.group.administrator', 176 | state: 1638, 177 | }, 178 | }, 179 | function (err) { 180 | expect(err).to.be.null; 181 | states.setState('javascript.0.test', 1, function (err) { 182 | expect(err).to.be.null; 183 | done(); 184 | }); 185 | }, 186 | ); 187 | }, 188 | ); 189 | }, 190 | ); 191 | }); 192 | }); 193 | 194 | it('Test RESTful API as User:(No rights) get - must return value', done => { 195 | axios 196 | .get('http://127.0.0.1:18183/get/system.adapter.simple-api.0.alive', { 197 | validateStatus: false, 198 | responseType: 'text', 199 | }) 200 | .then(response => { 201 | console.log(`get/system.adapter.simple-api.0.alive => ${response.data}`); 202 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 203 | expect(response.status).to.equal(403); 204 | done(); 205 | }) 206 | .catch(error => { 207 | console.error(error); 208 | done(error); 209 | }); 210 | }); 211 | 212 | it('Test RESTful API as User:(No rights) getPlainValue - must return plain value', done => { 213 | axios 214 | .get('http://127.0.0.1:18183/getPlainValue/system.adapter.simple-api.0.alive', { 215 | validateStatus: false, 216 | responseType: 'text', 217 | }) 218 | .then(response => { 219 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 220 | expect(response.data).to.be.equal('error: permissionError'); 221 | expect(response.status).to.equal(403); 222 | done(); 223 | }) 224 | .catch(error => { 225 | console.error(error); 226 | done(error); 227 | }); 228 | }); 229 | 230 | it('Test RESTful API as User:(No rights) getPlainValue 4 Test-Endpoint - must return plain value', done => { 231 | axios 232 | .get('http://127.0.0.1:18183/getPlainValue/javascript.0.test', { 233 | validateStatus: false, 234 | responseType: 'text', 235 | }) 236 | .then(response => { 237 | console.log(`getPlainValue/javascript.0.test => ${response.data}`); 238 | expect(response.data).to.be.equal('error: permissionError'); 239 | expect(response.status).to.equal(403); 240 | done(); 241 | }) 242 | .catch(error => { 243 | console.error(error); 244 | done(error); 245 | }); 246 | }); 247 | 248 | it('Test RESTful API as User:(No rights) set 4 Test-Endpoint - must set value', done => { 249 | axios 250 | .get('http://127.0.0.1:18183/set/javascript.0.test?val=2', { validateStatus: false, responseType: 'text' }) 251 | .then(response => { 252 | console.log(`set/javascript.0.test?val=false => ${response.data}`); 253 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 254 | expect(response.status).to.equal(403); 255 | return axios.get('http://127.0.0.1:18183/getPlainValue/javascript.0.test', { 256 | validateStatus: false, 257 | responseType: 'text', 258 | }); 259 | }) 260 | .then(response => { 261 | console.log(`getPlainValue/javascript.0.test => ${response.data}`); 262 | expect(response.data).to.be.equal('error: permissionError'); 263 | expect(response.status).to.equal(403); 264 | done(); 265 | }) 266 | .catch(error => { 267 | console.error(error); 268 | done(error); 269 | }); 270 | }); 271 | 272 | it('Test RESTful API as User:(No rights) set - must set value', done => { 273 | axios 274 | .get('http://127.0.0.1:18183/set/system.adapter.simple-api.0.alive?val=false', { 275 | validateStatus: false, 276 | responseType: 'text', 277 | }) 278 | .then(response => { 279 | console.log(`set/system.adapter.simple-api.0.alive?val=false => ${response.data}`); 280 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 281 | expect(response.status).to.equal(403); 282 | return axios.get('http://127.0.0.1:18183/getPlainValue/system.adapter.simple-api.0.alive', { 283 | validateStatus: false, 284 | responseType: 'text', 285 | }); 286 | }) 287 | .then(response => { 288 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 289 | expect(response.data).to.be.equal('error: permissionError'); 290 | expect(response.status).to.equal(403); 291 | done(); 292 | }) 293 | .catch(error => { 294 | console.error(error); 295 | done(error); 296 | }); 297 | }); 298 | 299 | it('Test RESTful API as User:(No rights) set - must set val', done => { 300 | axios 301 | .get('http://127.0.0.1:18183/set/system.adapter.simple-api.0.alive?val=true', { 302 | validateStatus: false, 303 | responseType: 'text', 304 | }) 305 | .then(response => { 306 | console.log(`set/system.adapter.simple-api.0.alive?val=true => ${response.data}`); 307 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 308 | expect(response.status).to.equal(403); 309 | return axios.get('http://127.0.0.1:18183/getPlainValue/system.adapter.simple-api.0.alive', { 310 | validateStatus: false, 311 | responseType: 'text', 312 | }); 313 | }) 314 | .then(response => { 315 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 316 | expect(response.data).to.be.equal('error: permissionError'); 317 | expect(response.status).to.equal(403); 318 | done(); 319 | }) 320 | .catch(error => { 321 | console.error(error); 322 | done(error); 323 | }); 324 | }); 325 | 326 | it('Test RESTful API as User:(No rights) objects - must return objects', done => { 327 | axios 328 | .get('http://127.0.0.1:18183/objects?pattern=system.adapter.*', { 329 | validateStatus: false, 330 | responseType: 'text', 331 | }) 332 | .then(response => { 333 | console.log(`objects?pattern=system.adapter.* => ${response.data}`); 334 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 335 | expect(response.status).to.equal(403); 336 | done(); 337 | }) 338 | .catch(error => { 339 | console.error(error); 340 | done(error); 341 | }); 342 | }); 343 | 344 | it('Test RESTful API as User:(No rights) objects - must return objects', done => { 345 | axios 346 | .get('http://127.0.0.1:18183/objects?pattern=system.adapter.*&type=instance', { 347 | validateStatus: false, 348 | responseType: 'text', 349 | }) 350 | .then(response => { 351 | console.log(`objects?pattern=system.adapter.* => ${response.data}`); 352 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 353 | expect(response.status).to.equal(403); 354 | done(); 355 | }) 356 | .catch(error => { 357 | console.error(error); 358 | done(error); 359 | }); 360 | }); 361 | 362 | it('Test RESTful API as User:(No rights) states - must return states', done => { 363 | axios 364 | .get('http://127.0.0.1:18183/states?pattern=system.adapter.*', { 365 | validateStatus: false, 366 | responseType: 'text', 367 | }) 368 | .then(response => { 369 | console.log(`states?pattern=system.adapter.* => ${response.data}`); 370 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 371 | expect(response.status).to.equal(403); 372 | done(); 373 | }) 374 | .catch(error => { 375 | console.error(error); 376 | done(error); 377 | }); 378 | }); 379 | 380 | it('Test RESTful API as User:(No rights) setBulk(POST) - must set values', done => { 381 | axios 382 | .post( 383 | 'http://127.0.0.1:18183/setBulk', 384 | `${TEST_STATE_ID}=50&system.adapter.simple-api.0.alive=false&javascript.0.test=4`, 385 | { validateStatus: false, responseType: 'text' }, 386 | ) 387 | .then(response => { 388 | console.log( 389 | `setBulk/?${TEST_STATE_ID}=50&system.adapter.simple-api.0.alive=false&javascript.0.test=4 => ${JSON.stringify(response.data)}`, 390 | ); 391 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 392 | expect(response.status).to.equal(403); 393 | return axios.get( 394 | `http://127.0.0.1:18183/getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive,javascript.0.test`, 395 | { validateStatus: false, responseType: 'text' }, 396 | ); 397 | }) 398 | .then(response => { 399 | console.log( 400 | `getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive,javascript.0.test => ${response.data}`, 401 | ); 402 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 403 | expect(response.status).to.equal(403); 404 | done(); 405 | }) 406 | .catch(error => { 407 | console.error(error); 408 | done(error); 409 | }); 410 | }); 411 | 412 | it('Test RESTful API as User:(No rights) setBulk(POST-GET-Mix) - must set values', done => { 413 | axios 414 | .post(`http://127.0.0.1:18183/setBulk?${TEST_STATE_ID}=51&system.adapter.simple-api.0.alive=false`, '', { 415 | validateStatus: false, 416 | responseType: 'text', 417 | }) 418 | .then(response => { 419 | console.log( 420 | `setBulk/?${TEST_STATE_ID}=51&system.adapter.simple-api.0.alive=false => ${JSON.stringify(response.data)}`, 421 | ); 422 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 423 | expect(response.status).to.equal(403); 424 | return axios.get(`http://127.0.0.1:18183/getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive`, { 425 | validateStatus: false, 426 | responseType: 'text', 427 | }); 428 | }) 429 | .then(response => { 430 | console.log(`getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive => ${response.data}`); 431 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 432 | expect(response.status).to.equal(403); 433 | done(); 434 | }) 435 | .catch(error => { 436 | console.error(error); 437 | done(error); 438 | }); 439 | }); 440 | 441 | it('Test RESTful API as User:(No rights) setValueFromBody(POST) - must set one value', done => { 442 | axios 443 | .post(`http://127.0.0.1:18183/setValueFromBody/${TEST_STATE_ID}`, '55', { 444 | validateStatus: false, 445 | responseType: 'text', 446 | }) 447 | .then(response => { 448 | console.log(`setValueFromBody/?${TEST_STATE_ID} => ${JSON.stringify(response.data)}`); 449 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 450 | expect(response.status).to.equal(403); 451 | return axios.get(`http://127.0.0.1:18183/getBulk/${TEST_STATE_ID}`, { 452 | validateStatus: false, 453 | responseType: 'text', 454 | }); 455 | }) 456 | .then(response => { 457 | console.log(`getBulk/${TEST_STATE_ID} => ${response.data}`); 458 | expect(response.data).to.be.equal('{"error":"permissionError"}'); 459 | expect(response.status).to.equal(403); 460 | done(); 461 | }) 462 | .catch(error => { 463 | console.error(error); 464 | done(error); 465 | }); 466 | }); 467 | 468 | after('Test RESTful API as User:(No rights) Stop js-controller', function (done) { 469 | this.timeout(9000); 470 | setup.stopController(normalTerminated => { 471 | console.log('Adapter normal terminated: ' + normalTerminated); 472 | setTimeout(done, 3000); 473 | }); 474 | }); 475 | }); 476 | -------------------------------------------------------------------------------- /test/testApiAsUser.js: -------------------------------------------------------------------------------- 1 | /* jshint -W097 */ 2 | /* jshint strict: false */ 3 | /* jslint node: true */ 4 | /* jshint expr: true*/ 5 | 6 | const expect = require('chai').expect; 7 | const setup = require('@iobroker/legacy-testing'); 8 | const axios = require('axios'); 9 | 10 | let objects = null; 11 | let states = null; 12 | 13 | process.env.NO_PROXY = '127.0.0.1'; 14 | const TEST_STATE_ID = 'simple-api.0.testNumber'; 15 | 16 | function checkConnectionOfAdapter(cb, counter) { 17 | counter = counter || 0; 18 | console.log(`Try check #${counter}`); 19 | if (counter > 30) { 20 | if (cb) cb('Cannot check connection'); 21 | return; 22 | } 23 | 24 | states.getState('system.adapter.simple-api.0.alive', function (err, state) { 25 | if (err) console.error(err); 26 | if (state && state.val) { 27 | if (cb) cb(); 28 | } else { 29 | setTimeout(function () { 30 | checkConnectionOfAdapter(cb, counter + 1); 31 | }, 1000); 32 | } 33 | }); 34 | } 35 | 36 | function createTestState(cb) { 37 | objects.setObject( 38 | TEST_STATE_ID, 39 | { 40 | _id: TEST_STATE_ID, 41 | type: 'state', 42 | common: { 43 | name: 'Test state', 44 | type: 'number', 45 | read: true, 46 | write: false, 47 | role: 'indicator.state', 48 | unit: '%', 49 | def: 0, 50 | desc: 'test state', 51 | }, 52 | native: {}, 53 | }, 54 | () => { 55 | states.setState(TEST_STATE_ID, { val: 0, ack: true }, cb); 56 | }, 57 | ); 58 | } 59 | 60 | describe('Test RESTful API as User', function () { 61 | before('Test RESTful API as User: Start js-controller', function (_done) { 62 | this.timeout(600000); // because of the first installation from npm 63 | setup.adapterStarted = false; 64 | 65 | setup.setupController(async () => { 66 | const config = await setup.getAdapterConfig(); 67 | // enable adapter 68 | config.common.enabled = true; 69 | config.common.loglevel = 'debug'; 70 | config.native.port = 18183; 71 | config.native.defaultUser = 'myuser'; 72 | await setup.setAdapterConfig(config.common, config.native); 73 | 74 | setup.startController(function (_objects, _states) { 75 | objects = _objects; 76 | states = _states; 77 | // give some time to start server 78 | setTimeout(() => createTestState(() => _done()), 2000); 79 | }); 80 | }); 81 | }); 82 | 83 | it('Test adapter: Check if adapter started and create upload datapoint', done => { 84 | checkConnectionOfAdapter(function (res) { 85 | if (res) console.log(res); 86 | expect(res).not.to.be.equal('Cannot check connection'); 87 | objects.setObject( 88 | 'system.group.writer', 89 | { 90 | common: { 91 | name: 'Writer', 92 | desc: '', 93 | members: ['system.user.myuser'], 94 | acl: { 95 | object: { 96 | list: true, 97 | read: true, 98 | write: false, 99 | delete: false, 100 | }, 101 | state: { 102 | list: true, 103 | read: true, 104 | write: true, 105 | create: false, 106 | delete: false, 107 | }, 108 | users: { 109 | write: false, 110 | create: false, 111 | delete: false, 112 | }, 113 | other: { 114 | execute: false, 115 | http: false, 116 | sendto: false, 117 | }, 118 | file: { 119 | list: false, 120 | read: false, 121 | write: false, 122 | create: false, 123 | delete: false, 124 | }, 125 | }, 126 | }, 127 | native: {}, 128 | acl: { 129 | object: 1638, 130 | owner: 'system.user.admin', 131 | ownerGroup: 'system.group.administrator', 132 | }, 133 | _id: 'system.group.writer', 134 | type: 'group', 135 | }, 136 | function (err) { 137 | expect(err).to.be.null; 138 | 139 | objects.setObject( 140 | 'system.user.myuser', 141 | { 142 | type: 'user', 143 | common: { 144 | name: 'myuser', 145 | enabled: true, 146 | groups: [], 147 | password: 148 | 'pbkdf2$10000$ab4104d8bb68390ee7e6c9397588e768de6c025f0c732c18806f3d1270c83f83fa86a7bf62583770e5f8d0b405fbb3ad32214ef3584f5f9332478f2506414443a910bf15863b36ebfcaa7cbb19253ae32cd3ca390dab87b29cd31e11be7fa4ea3a01dad625d9de44e412680e1a694227698788d71f1e089e5831dc1bbacfa794b45e1c995214bf71ee4160d98b4305fa4c3e36ee5f8da19b3708f68e7d2e8197375c0f763d90e31143eb04760cc2148c8f54937b9385c95db1742595634ed004fa567655dfe1d9b9fa698074a9fb70c05a252b2d9cf7ca1c9b009f2cd70d6972ccf0ee281d777d66a0346c6c6525436dd7fe3578b28dca2c7adbfde0ecd45148$31c3248ba4dc9600a024b4e0e7c3e585', 149 | }, 150 | _id: 'system.user.myuser', 151 | native: {}, 152 | acl: { 153 | object: 1638, 154 | }, 155 | }, 156 | function (err) { 157 | expect(err).to.be.null; 158 | objects.setObject( 159 | 'javascript.0.test', 160 | { 161 | common: { 162 | name: 'test', 163 | type: 'number', 164 | role: 'level', 165 | min: -100, 166 | max: 100, 167 | def: 1, 168 | }, 169 | native: {}, 170 | type: 'state', 171 | acl: { 172 | object: 1638, 173 | owner: 'system.user.myuser', 174 | ownerGroup: 'system.group.administrator', 175 | state: 1638, 176 | }, 177 | }, 178 | function (err) { 179 | expect(err).to.be.null; 180 | states.setState('javascript.0.test', 1, function (err) { 181 | expect(err).to.be.null; 182 | objects.setObject( 183 | 'javascript.0.test-number', 184 | { 185 | common: { 186 | name: 'test', 187 | type: 'number', 188 | role: 'value', 189 | def: 0, 190 | }, 191 | native: {}, 192 | type: 'state', 193 | }, 194 | err => { 195 | expect(err).to.be.null; 196 | states.setState('javascript.0.test-number', 0, err => { 197 | expect(err).to.be.null; 198 | done(); 199 | }); 200 | }, 201 | ); 202 | }); 203 | }, 204 | ); 205 | }, 206 | ); 207 | }, 208 | ); 209 | }); 210 | }).timeout(60000); 211 | 212 | it('Test RESTful API as User: get - must return value', done => { 213 | axios.get('http://127.0.0.1:18183/get/system.adapter.simple-api.0.alive').then(response => { 214 | console.log(`get/system.adapter.simple-api.0.alive => ${JSON.stringify(response.data)}`); 215 | const obj = response.data; 216 | //{ 217 | // "val" : true, 218 | // "ack" : true, 219 | // "ts" : 1455009717, 220 | // "q" : 0, 221 | // "from" : "system.adapter.simple-api.0", 222 | // "lc" : 1455009717, 223 | // "expire" : 30000, 224 | // "_id" : "system.adapter.simple-api.0.alive", 225 | // "type" : "state", 226 | // "common" : { 227 | // "name" : "simple-api.0.alive", 228 | // "type" : "boolean", 229 | // "role" : "indicator.state" 230 | // }, 231 | // "native" : {} 232 | // 233 | //} 234 | 235 | expect(obj).to.be.ok; 236 | expect(obj.val).to.be.true; 237 | expect(obj.ack).to.be.true; 238 | expect(obj.ts).to.be.ok; 239 | //expect(obj.from).to.equal("system.adapter.simple-api.0"); 240 | expect(obj.type).to.equal('state'); 241 | expect(obj._id).to.equal('system.adapter.simple-api.0.alive'); 242 | expect(obj.common).to.be.ok; 243 | expect(obj.native).to.be.ok; 244 | expect(obj.common.name).to.equal('simple-api.0 alive'); 245 | expect(obj.common.role).to.equal('indicator.state'); 246 | expect(response.status).to.equal(200); 247 | done(); 248 | }); 249 | }); 250 | 251 | it('Test RESTful API as User: getPlainValue - must return plain value', done => { 252 | axios 253 | .get('http://127.0.0.1:18183/getPlainValue/system.adapter.simple-api.0.alive', { responseType: 'text' }) 254 | .then(response => { 255 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 256 | expect(response.data).equal('true'); 257 | expect(response.status).to.equal(200); 258 | done(); 259 | }); 260 | }); 261 | 262 | it('Test RESTful API as User: getPlainValue 4 Test-Endpoint - must return plain value', done => { 263 | axios.get('http://127.0.0.1:18183/getPlainValue/javascript.0.test', { responseType: 'text' }).then(response => { 264 | console.log(`getPlainValue/javascript.0.test => ${response.data}`); 265 | expect(response.data).equal('1'); 266 | expect(response.status).to.equal(200); 267 | done(); 268 | }); 269 | }); 270 | 271 | it('Test RESTful API as User: set 4 Test-Endpoint - must set value', done => { 272 | axios.get('http://127.0.0.1:18183/set/javascript.0.test?val=2').then(response => { 273 | console.log(`set/javascript.0.test?val=false => ${response.data}`); 274 | const obj = response.data; 275 | expect(obj).to.be.ok; 276 | expect(obj.val).to.be.equal(2); 277 | expect(obj.id).to.equal('javascript.0.test'); 278 | expect(response.status).to.equal(200); 279 | axios 280 | .get('http://127.0.0.1:18183/getPlainValue/javascript.0.test', { responseType: 'text' }) 281 | .then(response => { 282 | console.log(`getPlainValue/javascript.0.test => ${response.data}`); 283 | expect(response.data).equal('2'); 284 | expect(response.status).to.equal(200); 285 | done(); 286 | }); 287 | }); 288 | }); 289 | 290 | it('Test RESTful API as User: set - must set value', done => { 291 | axios.get('http://127.0.0.1:18183/set/system.adapter.simple-api.0.alive?val=false').then(response => { 292 | console.log(`set/system.adapter.simple-api.0.alive?val=false => ${response.data}`); 293 | const obj = response.data; 294 | expect(obj).to.be.ok; 295 | expect(obj.val).to.be.false; 296 | expect(obj.id).to.equal('system.adapter.simple-api.0.alive'); 297 | expect(response.status).to.equal(200); 298 | axios 299 | .get('http://127.0.0.1:18183/getPlainValue/system.adapter.simple-api.0.alive', { responseType: 'text' }) 300 | .then(response => { 301 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 302 | expect(response.data).equal('false'); 303 | expect(response.status).to.equal(200); 304 | done(); 305 | }); 306 | }); 307 | }); 308 | 309 | it('Test RESTful API as User: set - must set val', done => { 310 | axios.get('http://127.0.0.1:18183/set/system.adapter.simple-api.0.alive?val=true').then(response => { 311 | console.log(`set/system.adapter.simple-api.0.alive?val=true => ${response.data}`); 312 | const obj = response.data; 313 | expect(obj).to.be.ok; 314 | expect(obj.val).to.be.true; 315 | expect(obj.id).to.equal('system.adapter.simple-api.0.alive'); 316 | expect(response.status).to.equal(200); 317 | axios 318 | .get('http://127.0.0.1:18183/getPlainValue/system.adapter.simple-api.0.alive', { responseType: 'text' }) 319 | .then(response => { 320 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 321 | expect(response.data).equal('true'); 322 | expect(response.status).to.equal(200); 323 | done(); 324 | }); 325 | }); 326 | }); 327 | 328 | it('Test RESTful API as User: toggle - must toggle boolean value to false', done => { 329 | axios.get('http://127.0.0.1:18183/toggle/system.adapter.simple-api.0.alive').then(response => { 330 | console.log(`toggle/system.adapter.simple-api.0.alive => ${response.data}`); 331 | const obj = response.data; 332 | expect(obj).to.be.ok; 333 | expect(obj.val).to.be.false; 334 | expect(obj.id).to.equal('system.adapter.simple-api.0.alive'); 335 | expect(response.status).to.equal(200); 336 | 337 | axios 338 | .get('http://127.0.0.1:18183/getPlainValue/system.adapter.simple-api.0.alive', { responseType: 'text' }) 339 | .then(response => { 340 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 341 | expect(response.data).equal('false'); 342 | expect(response.status).to.equal(200); 343 | done(); 344 | }); 345 | }); 346 | }); 347 | 348 | it('Test RESTful API as User: toggle - must toggle boolean value to true', done => { 349 | axios.get('http://127.0.0.1:18183/toggle/system.adapter.simple-api.0.alive').then(response => { 350 | console.log(`toggle/system.adapter.simple-api.0.alive => ${response.data}`); 351 | const obj = response.data; 352 | expect(obj).to.be.ok; 353 | expect(obj.val).to.be.true; 354 | expect(obj.id).to.equal('system.adapter.simple-api.0.alive'); 355 | expect(response.status).to.equal(200); 356 | 357 | axios 358 | .get('http://127.0.0.1:18183/getPlainValue/system.adapter.simple-api.0.alive', { responseType: 'text' }) 359 | .then(response => { 360 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 361 | expect(response.data).equal('true'); 362 | expect(response.status).to.equal(200); 363 | done(); 364 | }); 365 | }); 366 | }); 367 | 368 | it('Test RESTful API as User: toggle - must toggle number value to 100', done => { 369 | axios.get(`http://127.0.0.1:18183/toggle/${TEST_STATE_ID}`).then(response => { 370 | console.log(`toggle/${TEST_STATE_ID} => ${response.data}`); 371 | const obj = response.data; 372 | expect(obj).to.be.ok; 373 | expect(obj.val).to.be.equal(100); 374 | expect(obj.id).to.equal(TEST_STATE_ID); 375 | expect(response.status).to.equal(200); 376 | 377 | axios 378 | .get(`http://127.0.0.1:18183/getPlainValue/${TEST_STATE_ID}`, { responseType: 'text' }) 379 | .then(response => { 380 | console.log(`getPlainValue/${TEST_STATE_ID} => ${response.data}`); 381 | expect(response.data).equal('100'); 382 | expect(response.status).to.equal(200); 383 | axios.get(`http://127.0.0.1:18183/set/${TEST_STATE_ID}?val=49`).then(response => { 384 | console.log(`set/${TEST_STATE_ID}?val=49 => ` + response.data); 385 | axios.get(`http://127.0.0.1:18183/toggle/${TEST_STATE_ID}`).then(response => { 386 | console.log(`toggle/${TEST_STATE_ID} => ${response.data}`); 387 | const obj = response.data; 388 | expect(obj).to.be.ok; 389 | expect(obj.val).to.be.equal(51); 390 | expect(obj.id).to.equal(TEST_STATE_ID); 391 | expect(response.status).to.equal(200); 392 | 393 | axios 394 | .get(`http://127.0.0.1:18183/getPlainValue/${TEST_STATE_ID}`, { responseType: 'text' }) 395 | .then(response => { 396 | console.log(`getPlainValue/${TEST_STATE_ID} => ${response.data}`); 397 | expect(response.data).equal('51'); 398 | expect(response.status).to.equal(200); 399 | done(); 400 | }); 401 | }); 402 | }); 403 | }); 404 | }); 405 | }); 406 | 407 | it('Test RESTful API as User: setBulk - must set values', done => { 408 | axios 409 | .get( 410 | `http://127.0.0.1:18183/setBulk/?${TEST_STATE_ID}=50&system.adapter.simple-api.0.alive=false&javascript.0.test=3`, 411 | ) 412 | .then(response => { 413 | console.log( 414 | `setBulk/?${TEST_STATE_ID}=50&system.adapter.simple-api.0.alive=false&javascript.0.test=3 => ${response.data}`, 415 | ); 416 | const obj = response.data; 417 | expect(obj).to.be.ok; 418 | 419 | expect(obj[0].val).to.be.equal(50); 420 | expect(obj[0].id).to.equal(TEST_STATE_ID); 421 | expect(obj[1].val).to.be.equal(false); 422 | expect(obj[1].id).to.equal('system.adapter.simple-api.0.alive'); 423 | expect(obj[2].val).to.be.equal(3); 424 | expect(obj[2].id).to.equal('javascript.0.test'); 425 | expect(response.status).to.equal(200); 426 | 427 | axios 428 | .get( 429 | `http://127.0.0.1:18183/getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive,javascript.0.test`, 430 | ) 431 | .then(response => { 432 | console.log( 433 | `getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive&javascript.0.test => ${response.data}`, 434 | ); 435 | const obj = response.data; 436 | expect(obj[0].val).equal(50); 437 | expect(obj[1].val).equal(false); 438 | expect(obj[2].val).equal(3); 439 | expect(response.status).to.equal(200); 440 | done(); 441 | }); 442 | }); 443 | }); 444 | 445 | it('Test RESTful API as User: objects - must return objects', done => { 446 | axios.get('http://127.0.0.1:18183/objects?pattern=system.adapter.*').then(response => { 447 | console.log(`objects?pattern=system.adapter.* => ${response.data}`); 448 | expect(response.data).to.be.not.equal('error: permissionError'); 449 | expect(response.status).to.equal(200); 450 | done(); 451 | }); 452 | }); 453 | 454 | it('Test RESTful API as User: objects - must return objects', done => { 455 | axios.get('http://127.0.0.1:18183/objects?pattern=system.adapter.*&type=instance').then(response => { 456 | console.log(`objects?pattern=system.adapter.* => ${response.data}`); 457 | expect(response.data).to.be.not.equal('error: permissionError'); 458 | expect(response.status).to.equal(200); 459 | done(); 460 | }); 461 | }); 462 | 463 | it('Test RESTful API as User: states - must return states', done => { 464 | axios.get('http://127.0.0.1:18183/states?pattern=system.adapter.*').then(response => { 465 | console.log(`states?pattern=system.adapter.* => ${response.data}`); 466 | expect(response.data).to.be.not.equal('error: permissionError'); 467 | expect(response.status).to.equal(200); 468 | done(); 469 | }); 470 | }); 471 | 472 | it('Test RESTful API as User: setBulk(POST) - must set values', done => { 473 | axios 474 | .post( 475 | 'http://127.0.0.1:18183/setBulk', 476 | `${TEST_STATE_ID}=50&system.adapter.simple-api.0.alive=false&javascript.0.test=4`, 477 | ) 478 | .then(response => { 479 | console.log( 480 | `setBulk/?${TEST_STATE_ID}=50&system.adapter.simple-api.0.alive=false&javascript.0.test=4 => ${JSON.stringify(response.data)}`, 481 | ); 482 | const obj = response.data; 483 | expect(obj).to.be.ok; 484 | expect(obj[0].val).to.be.equal(50); 485 | expect(obj[0].id).to.equal(TEST_STATE_ID); 486 | expect(obj[1].val).to.be.equal(false); 487 | expect(obj[1].id).to.equal('system.adapter.simple-api.0.alive'); 488 | expect(obj[2].val).to.be.equal(4); 489 | expect(obj[2].id).to.equal('javascript.0.test'); 490 | expect(response.status).to.equal(200); 491 | 492 | axios 493 | .get( 494 | `http://127.0.0.1:18183/getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive,javascript.0.test`, 495 | ) 496 | .then(response => { 497 | console.log( 498 | `getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive,javascript.0.test => ${JSON.stringify(response.data)}`, 499 | ); 500 | const obj = response.data; 501 | expect(obj[0].val).equal(50); 502 | expect(obj[1].val).equal(false); 503 | expect(obj[2].val).equal(4); 504 | expect(response.status).to.equal(200); 505 | done(); 506 | }); 507 | }); 508 | }); 509 | 510 | it('Test RESTful API as User: setBulk(POST-GET-Mix) - must set values', done => { 511 | axios 512 | .post(`http://127.0.0.1:18183/setBulk?${TEST_STATE_ID}=51&system.adapter.simple-api.0.alive=false`, '') 513 | .then(response => { 514 | console.log( 515 | `setBulk/?${TEST_STATE_ID}=51&system.adapter.simple-api.0.alive=false => ${JSON.stringify(response.data)}`, 516 | ); 517 | expect(response.status).to.equal(200); 518 | 519 | const obj = response.data; 520 | expect(obj).to.be.ok; 521 | expect(obj[0].val).to.be.equal(51); 522 | expect(obj[0].id).to.equal(TEST_STATE_ID); 523 | expect(obj[1].val).to.be.equal(false); 524 | expect(obj[1].id).to.equal('system.adapter.simple-api.0.alive'); 525 | 526 | return axios.get(`http://127.0.0.1:18183/getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive`); 527 | }) 528 | .then(response => { 529 | console.log(`getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive => ${response.data}`); 530 | expect(response.status).to.equal(200); 531 | 532 | const obj = response.data; 533 | expect(obj[0].val).equal(51); 534 | expect(obj[1].val).equal(false); 535 | done(); 536 | }) 537 | .catch(error => { 538 | console.error(error); 539 | done(error); 540 | }); 541 | }); 542 | 543 | it('Test RESTful API as User: setValueFromBody(POST) - must set one value', done => { 544 | axios 545 | .post(`http://127.0.0.1:18183/setValueFromBody/${TEST_STATE_ID}`, '55') 546 | .then(response => { 547 | console.log(`setValueFromBody/?${TEST_STATE_ID} => ${JSON.stringify(response.data)}`); 548 | expect(response.status).to.equal(200); 549 | 550 | const obj = response.data; 551 | expect(obj).to.be.ok; 552 | expect(obj[0].val).to.be.equal(55); 553 | expect(obj[0].id).to.equal(TEST_STATE_ID); 554 | 555 | return axios.get(`http://127.0.0.1:18183/getBulk/${TEST_STATE_ID}`); 556 | }) 557 | .then(response => { 558 | console.log(`getBulk/${TEST_STATE_ID} => ${response.data}`); 559 | expect(response.status).to.equal(200); 560 | 561 | const obj = response.data; 562 | expect(obj[0].val).equal(55); 563 | done(); 564 | }) 565 | .catch(error => { 566 | console.error(error); 567 | done(error); 568 | }); 569 | }); 570 | 571 | after('Test RESTful API as User: Stop js-controller', function (done) { 572 | this.timeout(9000); 573 | setup.stopController(normalTerminated => { 574 | console.log(`Adapter normal terminated: ${normalTerminated}`); 575 | setTimeout(done, 3000); 576 | }); 577 | }); 578 | }); 579 | -------------------------------------------------------------------------------- /test/testApi.js: -------------------------------------------------------------------------------- 1 | /* jshint -W097 */ 2 | /* jshint strict: false */ 3 | /* jslint node: true */ 4 | /* jshint expr: true*/ 5 | const expect = require('chai').expect; 6 | const setup = require('@iobroker/legacy-testing'); 7 | const axios = require('axios'); 8 | 9 | let objects = null; 10 | let states = null; 11 | const TEST_STATE_ID = 'simple-api.0.testNumber'; 12 | 13 | process.env.NO_PROXY = '127.0.0.1'; 14 | 15 | function checkConnectionOfAdapter(cb, counter) { 16 | counter = counter || 0; 17 | console.log(`Try check #${counter}`); 18 | if (counter > 30) { 19 | if (cb) cb('Cannot check connection'); 20 | return; 21 | } 22 | 23 | states.getState('system.adapter.simple-api.0.alive', (err, state) => { 24 | if (err) console.error(err); 25 | if (state && state.val) { 26 | if (cb) cb(); 27 | } else { 28 | setTimeout(() => checkConnectionOfAdapter(cb, counter + 1), 1000); 29 | } 30 | }); 31 | } 32 | 33 | function createTestState(cb) { 34 | objects.setObject( 35 | TEST_STATE_ID, 36 | { 37 | _id: TEST_STATE_ID, 38 | type: 'state', 39 | common: { 40 | name: 'Test state', 41 | type: 'number', 42 | read: true, 43 | write: false, 44 | role: 'indicator.state', 45 | unit: '%', 46 | def: 0, 47 | desc: 'test state', 48 | }, 49 | native: {}, 50 | }, 51 | () => { 52 | states.setState(TEST_STATE_ID, { val: 0, ack: true }, cb && cb); 53 | }, 54 | ); 55 | } 56 | 57 | describe('Test RESTful API', function () { 58 | before('Test RESTful API: Start js-controller', function (_done) { 59 | this.timeout(600000); // because of the first installation from npm 60 | setup.adapterStarted = false; 61 | 62 | setup.setupController(async () => { 63 | const config = await setup.getAdapterConfig(); 64 | // enable adapter 65 | config.common.enabled = true; 66 | config.common.loglevel = 'debug'; 67 | config.native.port = 18183; 68 | await setup.setAdapterConfig(config.common, config.native); 69 | 70 | setup.startController((_objects, _states) => { 71 | objects = _objects; 72 | states = _states; 73 | 74 | // give some time to start server 75 | setTimeout(() => createTestState(() => _done()), 2000); 76 | }); 77 | }); 78 | }); 79 | 80 | it('Test adapter: Check if adapter started and create datapoint', done => { 81 | checkConnectionOfAdapter(res => { 82 | res && console.log(res); 83 | expect(res).not.to.be.equal('Cannot check connection'); 84 | objects.setObject( 85 | 'javascript.0.test-string', 86 | { 87 | common: { 88 | name: 'test', 89 | type: 'string', 90 | role: 'value', 91 | def: '', 92 | }, 93 | native: {}, 94 | type: 'state', 95 | }, 96 | err => { 97 | expect(err).to.be.null; 98 | states.setState('javascript.0.test-string', '', err => { 99 | expect(err).to.be.null; 100 | objects.setObject( 101 | 'javascript.0.test-number', 102 | { 103 | common: { 104 | name: 'test', 105 | type: 'number', 106 | role: 'value', 107 | def: 0, 108 | }, 109 | native: {}, 110 | type: 'state', 111 | }, 112 | err => { 113 | expect(err).to.be.null; 114 | states.setState('javascript.0.test-number', 0, err => { 115 | expect(err).to.be.null; 116 | done(); 117 | }); 118 | }, 119 | ); 120 | }); 121 | }, 122 | ); 123 | }); 124 | }).timeout(60000); 125 | 126 | it('Test RESTful API: get - must return value', done => { 127 | axios 128 | .get('http://127.0.0.1:18183/get/system.adapter.simple-api.0.alive') 129 | .then(response => { 130 | console.log(`get/system.adapter.simple-api.0.alive => ${response.data}`); 131 | const obj = response.data; 132 | expect(obj).to.be.ok; 133 | expect(obj.val).to.be.true; 134 | expect(obj.ack).to.be.true; 135 | expect(obj.ts).to.be.ok; 136 | expect(obj.type).to.equal('state'); 137 | expect(obj._id).to.equal('system.adapter.simple-api.0.alive'); 138 | expect(obj.common).to.be.ok; 139 | expect(obj.native).to.be.ok; 140 | expect(obj.common.name).to.equal('simple-api.0 alive'); 141 | expect(obj.common.role).to.equal('indicator.state'); 142 | expect(response.status).to.equal(200); 143 | done(); 144 | }) 145 | .catch(error => { 146 | console.error(error); 147 | done(error); 148 | }); 149 | }); 150 | 151 | it('Test RESTful API: get - must return error', done => { 152 | axios 153 | .get('http://127.0.0.1:18183/get/system.adapter.simple-api.0.alive%23test', { validateStatus: false, responseType: 'text' }) 154 | .then(response => { 155 | console.log(`get/system.adapter.simple-api.0.alive%23test => ${response.data}`); 156 | expect(response.data).to.be.equal( 157 | '{"error":"datapoint \\"system.adapter.simple-api.0.alive#test\\" not found"}', 158 | ); 159 | expect(response.status).to.equal(404); 160 | done(); 161 | }) 162 | .catch(error => { 163 | console.error(error); 164 | done(error); 165 | }); 166 | }); 167 | 168 | it('Test RESTful API: get - must return error 2', done => { 169 | axios 170 | .get('http://127.0.0.1:18183/get/system.adapter.simple-api.0.%23alive%23test', { validateStatus: false, responseType: 'text' }) 171 | .then(response => { 172 | console.log(`get/system.adapter.simple-api.0.alive#%23test => ${response.data}`); 173 | expect(response.data).to.be.equal( 174 | '{"error":"datapoint \\"system.adapter.simple-api.0.#alive#test\\" not found"}', 175 | ); 176 | expect(response.status).to.equal(404); 177 | done(); 178 | }) 179 | .catch(error => { 180 | console.error(error); 181 | done(error); 182 | }); 183 | }); 184 | 185 | it('Test RESTful API: getPlainValue - must return plain value', done => { 186 | axios 187 | .get('http://127.0.0.1:18183/getPlainValue/system.adapter.simple-api.0.alive', { responseType: 'text' }) 188 | .then(response => { 189 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 190 | expect(response.data).equal('true'); 191 | expect(response.status).to.equal(200); 192 | done(); 193 | }) 194 | .catch(error => { 195 | console.error(error); 196 | done(error); 197 | }); 198 | }); 199 | 200 | it('Test RESTful API: set - must set value', done => { 201 | axios 202 | .get('http://127.0.0.1:18183/set/system.adapter.simple-api.0.alive?val=false') 203 | .then(response => { 204 | console.log(`set/system.adapter.simple-api.0.alive?val=false => ${response.data}`); 205 | const obj = response.data; 206 | expect(obj).to.be.ok; 207 | expect(obj.val).to.be.false; 208 | expect(obj.id).to.equal('system.adapter.simple-api.0.alive'); 209 | expect(response.status).to.equal(200); 210 | return axios.get('http://127.0.0.1:18183/getPlainValue/system.adapter.simple-api.0.alive', { responseType: 'text' }); 211 | }) 212 | .then(response => { 213 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 214 | expect(response.data).equal('false'); 215 | expect(response.status).to.equal(200); 216 | return axios.get('http://127.0.0.1:18183/get/system.adapter.simple-api.0.alive'); 217 | }) 218 | .then(response => { 219 | console.log(`get/system.adapter.simple-api.0.alive => ${response.data}`); 220 | expect(response.data.val).equal(false); 221 | expect(response.status).to.equal(200); 222 | done(); 223 | }) 224 | .catch(error => { 225 | console.error(error); 226 | done(error); 227 | }); 228 | }); 229 | 230 | it('Test RESTful API: set - must set easy string value', done => { 231 | axios 232 | .get('http://127.0.0.1:18183/set/javascript.0.test-string?val=19,1-bla') 233 | .then(response => { 234 | console.log(`set/javascript.0.test-string?val=19,1-bla => ${response.data}`); 235 | const obj = response.data; 236 | expect(obj).to.be.ok; 237 | expect(obj.val).equal('19,1-bla'); 238 | expect(obj.id).to.equal('javascript.0.test-string'); 239 | expect(response.status).to.equal(200); 240 | return axios.get('http://127.0.0.1:18183/getPlainValue/javascript.0.test-string', { responseType: 'text' }); 241 | }) 242 | .then(response => { 243 | console.log(`getPlainValue/javascript.0.test-string => ${response.data}`); 244 | expect(response.data).equal('"19,1-bla"'); 245 | expect(response.status).to.equal(200); 246 | return axios.get('http://127.0.0.1:18183/get/javascript.0.test-string'); 247 | }) 248 | .then(response => { 249 | console.log(`get/javascript.0.test-string => ${response.data}`); 250 | expect(response.data.val).equal('19,1-bla'); 251 | expect(response.status).to.equal(200); 252 | done(); 253 | }) 254 | .catch(error => { 255 | console.error(error); 256 | done(error); 257 | }); 258 | }); 259 | 260 | it('Test RESTful API: set - must set encoded string value', done => { 261 | axios 262 | .get('http://127.0.0.1:18183/set/javascript.0.test-string?val=bla%26fasel%2efoo%3Dhummer+hey') 263 | .then(response => { 264 | console.log(`set/javascript.0.test-string?val=bla%20fasel%2efoo => ${response.data}`); 265 | const obj = response.data; 266 | expect(obj).to.be.ok; 267 | expect(obj.val).equal('bla&fasel.foo=hummer hey'); 268 | expect(obj.id).to.equal('javascript.0.test-string'); 269 | expect(response.status).to.equal(200); 270 | return axios.get('http://127.0.0.1:18183/getPlainValue/javascript.0.test-string', { responseType: 'text' }); 271 | }) 272 | .then(response => { 273 | console.log(`getPlainValue/javascript.0.test-string => ${response.data}`); 274 | expect(response.data).equal('"bla&fasel.foo=hummer hey"'); 275 | expect(response.status).to.equal(200); 276 | return axios.get('http://127.0.0.1:18183/get/javascript.0.test-string'); 277 | }) 278 | .then(response => { 279 | console.log(`get/javascript.0.test-string => ${response.data}`); 280 | expect(response.data.val).equal('bla&fasel.foo=hummer hey'); 281 | expect(response.status).to.equal(200); 282 | done(); 283 | }) 284 | .catch(error => { 285 | console.error(error); 286 | done(error); 287 | }); 288 | }); 289 | 290 | it('Test RESTful API: set - must set val', done => { 291 | axios 292 | .get('http://127.0.0.1:18183/set/system.adapter.simple-api.0.alive?val=true') 293 | .then(response => { 294 | console.log(`set/system.adapter.simple-api.0.alive?val=true => ${response.data}`); 295 | const obj = response.data; 296 | expect(obj).to.be.ok; 297 | expect(obj.val).to.be.true; 298 | expect(typeof obj.val).to.be.equal('boolean'); 299 | expect(obj.id).to.equal('system.adapter.simple-api.0.alive'); 300 | expect(response.status).to.equal(200); 301 | return axios.get('http://127.0.0.1:18183/getPlainValue/system.adapter.simple-api.0.alive', { responseType: 'text' }); 302 | }) 303 | .then(response => { 304 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 305 | expect(response.data).equal('true'); 306 | expect(response.status).to.equal(200); 307 | done(); 308 | }) 309 | .catch(error => { 310 | console.error(error); 311 | done(error); 312 | }); 313 | }); 314 | 315 | it('Test RESTful API: set - must have ack true', done => { 316 | axios 317 | .get('http://127.0.0.1:18183/set/system.adapter.simple-api.0.alive?val=true&ack=true') 318 | .then(response => { 319 | console.log(`set/system.adapter.simple-api.0.alive?val=true => ${response.data}`); 320 | const obj = response.data; 321 | expect(obj).to.be.ok; 322 | expect(obj.val).to.be.true; 323 | expect(typeof obj.val).to.be.equal('boolean'); 324 | expect(obj.id).to.equal('system.adapter.simple-api.0.alive'); 325 | expect(response.status).to.equal(200); 326 | return axios.get('http://127.0.0.1:18183/get/system.adapter.simple-api.0.alive'); 327 | }) 328 | .then(response => { 329 | console.log(`get/system.adapter.simple-api.0.alive => ${response.data}`); 330 | const body = response.data; 331 | expect(body.val).equal(true); 332 | expect(body.ack).equal(true); 333 | expect(response.status).to.equal(200); 334 | done(); 335 | }) 336 | .catch(error => { 337 | console.error(error); 338 | done(error); 339 | }); 340 | }); 341 | 342 | it('Test RESTful API: toggle - must toggle boolean value to false', done => { 343 | axios 344 | .get('http://127.0.0.1:18183/toggle/system.adapter.simple-api.0.alive') 345 | .then(response => { 346 | console.log(`toggle/system.adapter.simple-api.0.alive => ${response.data}`); 347 | const obj = response.data; 348 | expect(obj).to.be.ok; 349 | expect(obj.val).to.be.false; 350 | expect(typeof obj.val).to.be.equal('boolean'); 351 | expect(obj.id).to.equal('system.adapter.simple-api.0.alive'); 352 | expect(response.status).to.equal(200); 353 | return axios.get('http://127.0.0.1:18183/getPlainValue/system.adapter.simple-api.0.alive', { responseType: 'text' }); 354 | }) 355 | .then(response => { 356 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 357 | expect(response.data).equal('false'); 358 | expect(response.status).to.equal(200); 359 | done(); 360 | }) 361 | .catch(error => { 362 | console.error(error); 363 | done(error); 364 | }); 365 | }); 366 | 367 | it('Test RESTful API: toggle - must toggle boolean value to true', done => { 368 | axios 369 | .get('http://127.0.0.1:18183/toggle/system.adapter.simple-api.0.alive') 370 | .then(response => { 371 | console.log(`toggle/system.adapter.simple-api.0.alive => ${response.data}`); 372 | const obj = response.data; 373 | expect(obj).to.be.ok; 374 | expect(obj.val).to.be.true; 375 | expect(obj.id).to.equal('system.adapter.simple-api.0.alive'); 376 | expect(response.status).to.equal(200); 377 | return axios.get('http://127.0.0.1:18183/getPlainValue/system.adapter.simple-api.0.alive', { responseType: 'text' }); 378 | }) 379 | .then(response => { 380 | console.log(`getPlainValue/system.adapter.simple-api.0.alive => ${response.data}`); 381 | expect(response.data).equal('true'); 382 | expect(response.status).to.equal(200); 383 | done(); 384 | }) 385 | .catch(error => { 386 | console.error(error); 387 | done(error); 388 | }); 389 | }); 390 | 391 | it('Test RESTful API: toggle - must toggle number value to 100', done => { 392 | axios 393 | .get(`http://127.0.0.1:18183/toggle/${TEST_STATE_ID}`) 394 | .then(response => { 395 | console.log(`toggle/${TEST_STATE_ID} => ${response.data}`); 396 | const obj = response.data; 397 | expect(obj).to.be.ok; 398 | expect(obj.val).to.be.equal(100); 399 | expect(typeof obj.val).to.be.equal('number'); 400 | expect(obj.id).to.equal(TEST_STATE_ID); 401 | expect(response.status).to.equal(200); 402 | return axios.get(`http://127.0.0.1:18183/getPlainValue/${TEST_STATE_ID}`, { responseType: 'text' }); 403 | }) 404 | .then(response => { 405 | console.log(`getPlainValue/${TEST_STATE_ID} => ${response.data}`); 406 | expect(response.data).equal('100'); 407 | expect(response.status).to.equal(200); 408 | return axios.get(`http://127.0.0.1:18183/set/${TEST_STATE_ID}?val=49`); 409 | }) 410 | .then(response => { 411 | console.log(`set/${TEST_STATE_ID}?val=49 => ${response.data}`); 412 | return axios.get(`http://127.0.0.1:18183/toggle/${TEST_STATE_ID}`); 413 | }) 414 | .then(response => { 415 | console.log(`toggle/${TEST_STATE_ID} => ${response.data}`); 416 | const obj = response.data; 417 | expect(obj).to.be.ok; 418 | expect(obj.val).to.be.equal(51); 419 | expect(obj.id).to.equal(TEST_STATE_ID); 420 | expect(response.status).to.equal(200); 421 | return axios.get(`http://127.0.0.1:18183/getPlainValue/${TEST_STATE_ID}`, { responseType: 'text' }); 422 | }) 423 | .then(response => { 424 | console.log(`getPlainValue/${TEST_STATE_ID} => ${response.data}`); 425 | expect(response.data).equal('51'); 426 | expect(response.status).to.equal(200); 427 | done(); 428 | }) 429 | .catch(error => { 430 | console.error(error); 431 | done(error); 432 | }); 433 | }); 434 | 435 | it('Test RESTful API: setBulk - must set values', done => { 436 | axios 437 | .get(`http://127.0.0.1:18183/setBulk?${TEST_STATE_ID}=50&system.adapter.simple-api.0.alive=false`) 438 | .then(response => { 439 | console.log(`setBulk/?${TEST_STATE_ID}=50&system.adapter.simple-api.0.alive=false => ${response.data}`); 440 | const obj = response.data; 441 | expect(obj).to.be.ok; 442 | expect(obj[0].val).to.be.equal(50); 443 | expect(obj[0].id).to.equal(TEST_STATE_ID); 444 | expect(obj[1].val).to.be.equal(false); 445 | expect(obj[1].id).to.equal('system.adapter.simple-api.0.alive'); 446 | expect(response.status).to.equal(200); 447 | return axios.get(`http://127.0.0.1:18183/getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive`); 448 | }) 449 | .then(response => { 450 | console.log(`getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive => ${response.data}`); 451 | const obj = response.data; 452 | expect(obj[0].val).equal(50); 453 | expect(obj[1].val).equal(false); 454 | expect(response.status).to.equal(200); 455 | done(); 456 | }) 457 | .catch(error => { 458 | console.error(error); 459 | done(error); 460 | }); 461 | }); 462 | 463 | it('Test RESTful API: objects - must return objects', done => { 464 | axios.get('http://127.0.0.1:18183/objects?pattern=system.adapter.*').then(response => { 465 | console.log(`objects?pattern=system.adapter.* => ${response.data}`); 466 | const obj = response.data; 467 | expect(obj['system.adapter.simple-api.0.alive']._id).to.be.ok; 468 | expect(response.status).to.equal(200); 469 | done(); 470 | }); 471 | }); 472 | 473 | it('Test RESTful API: objects - must return objects', done => { 474 | axios 475 | .get('http://127.0.0.1:18183/objects?pattern=system.adapter.*&type=instance') 476 | .then(response => { 477 | console.log(`objects?pattern=system.adapter.* => ${response.data}`); 478 | const obj = response.data; 479 | expect(obj['system.adapter.simple-api.0']._id).to.be.ok; 480 | expect(response.status).to.equal(200); 481 | done(); 482 | }) 483 | .catch(error => { 484 | console.error(error); 485 | done(error); 486 | }); 487 | }); 488 | 489 | it('Test RESTful API: states - must return states', done => { 490 | axios 491 | .get('http://127.0.0.1:18183/states?pattern=system.adapter.*') 492 | .then(response => { 493 | console.log(`states?pattern=system.adapter.* => ${response.data}`); 494 | const states = response.data; 495 | expect(states['system.adapter.simple-api.0.uptime'].val).to.be.least(0); 496 | expect(response.status).to.equal(200); 497 | done(); 498 | }) 499 | .catch(error => { 500 | console.error(error); 501 | done(error); 502 | }); 503 | }); 504 | 505 | it('Test RESTful API: setBulk(POST) - must set values', done => { 506 | axios 507 | .post( 508 | 'http://127.0.0.1:18183/setBulk', 509 | `${TEST_STATE_ID}=50&system.adapter.simple-api.0.alive=false&javascript.0.test-string=bla%26fasel%2efoo%3Dhummer+hey&ack=true`, 510 | ) 511 | .then(response => { 512 | console.log( 513 | `setBulk/?${TEST_STATE_ID}=50&system.adapter.simple-api.0.alive=false&javascript.0.test-string=bla%26fasel%2efoo%3Dhummer+hey => ${JSON.stringify(response.data)}`, 514 | ); 515 | const obj = response.data; 516 | expect(obj).to.be.ok; 517 | expect(obj[0].val).to.be.equal(50); 518 | expect(obj[0].id).to.equal(TEST_STATE_ID); 519 | expect(obj[1].val).to.be.equal(false); 520 | expect(obj[1].id).to.equal('system.adapter.simple-api.0.alive'); 521 | expect(obj[2].val).to.be.equal('bla&fasel.foo=hummer hey'); 522 | expect(obj[2].id).to.equal('javascript.0.test-string'); 523 | expect(response.status).to.equal(200); 524 | 525 | return axios.get( 526 | `http://127.0.0.1:18183/getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive,javascript.0.test-string`, 527 | ); 528 | }) 529 | .then(response => { 530 | console.log( 531 | `getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive,javascript.0.test-string => ${response.data}`, 532 | ); 533 | const obj = response.data; 534 | expect(obj[0].val).equal(50); 535 | expect(obj[0].ack).equal(true); 536 | expect(obj[1].val).equal(false); 537 | expect(obj[1].ack).equal(true); 538 | expect(obj[2].val).equal('bla&fasel.foo=hummer hey'); 539 | expect(obj[2].ack).equal(true); 540 | expect(response.status).to.equal(200); 541 | done(); 542 | }) 543 | .catch(error => { 544 | console.error(error); 545 | done(error); 546 | }); 547 | }); 548 | 549 | it('Test RESTful API: setBulk(POST-GET-Mix) - must set values', done => { 550 | axios 551 | .post(`http://127.0.0.1:18183/setBulk?${TEST_STATE_ID}=51&system.adapter.simple-api.0.alive=false`, '') 552 | .then(response => { 553 | console.log( 554 | `setBulk/?${TEST_STATE_ID}=51&system.adapter.simple-api.0.alive=false => ${JSON.stringify(response.data)}`, 555 | ); 556 | const obj = response.data; 557 | expect(obj).to.be.ok; 558 | expect(obj[0].val).to.be.equal(51); 559 | expect(obj[0].id).to.equal(TEST_STATE_ID); 560 | expect(obj[1].val).to.be.equal(false); 561 | expect(obj[1].id).to.equal('system.adapter.simple-api.0.alive'); 562 | expect(response.status).to.equal(200); 563 | 564 | return axios.get(`http://127.0.0.1:18183/getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive`); 565 | }) 566 | .then(response => { 567 | console.log(`getBulk/${TEST_STATE_ID},system.adapter.simple-api.0.alive => ${response.data}`); 568 | const obj = response.data; 569 | expect(obj[0].val).equal(51); 570 | expect(obj[1].val).equal(false); 571 | expect(response.status).to.equal(200); 572 | done(); 573 | }) 574 | .catch(error => { 575 | console.error(error); 576 | done(error); 577 | }); 578 | }); 579 | 580 | it('Test RESTful API: setValueFromBody(POST) - must set one value', done => { 581 | axios 582 | .post(`http://127.0.0.1:18183/setValueFromBody/${TEST_STATE_ID}`, '55') 583 | .then(response => { 584 | console.log(`setValueFromBody/?${TEST_STATE_ID} => ${JSON.stringify(response.data)}`); 585 | const obj = response.data; 586 | expect(obj).to.be.ok; 587 | expect(obj[0].val).to.be.equal(55); 588 | expect(obj[0].id).to.equal(TEST_STATE_ID); 589 | expect(response.status).to.equal(200); 590 | 591 | return axios.get(`http://127.0.0.1:18183/getBulk/${TEST_STATE_ID}`); 592 | }) 593 | .then(response => { 594 | console.log(`getBulk/${TEST_STATE_ID} => ${response.data}`); 595 | const obj = response.data; 596 | expect(obj[0].val).equal(55); 597 | expect(response.status).to.equal(200); 598 | done(); 599 | }) 600 | .catch(error => { 601 | console.error(error); 602 | done(error); 603 | }); 604 | }); 605 | 606 | after('Test RESTful API: Stop js-controller', function (done) { 607 | this.timeout(9000); 608 | setup.stopController(normalTerminated => { 609 | console.log(`Adapter normal terminated: ${normalTerminated}`); 610 | setTimeout(done, 3000); 611 | }); 612 | }); 613 | }); 614 | --------------------------------------------------------------------------------