Data Format Tags
Data Stream Connection Event
Data Stream Server Event
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 000000000..a34eefeca
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,48 @@
+import antfu from '@antfu/eslint-config'
+
+export default antfu(
+ {
+ ignores: ['dist', 'docs', 'src/lib/dbus/put.ts'],
+ jsx: false,
+ typescript: true,
+ formatters: {
+ markdown: true,
+ },
+ rules: {
+ 'curly': ['error', 'multi-line'],
+ 'import/extensions': ['error', 'ignorePackages'],
+ 'import/order': 0,
+ 'jsdoc/check-alignment': 'error',
+ 'jsdoc/check-line-alignment': 'error',
+ 'no-undef': 'error',
+ 'perfectionist/sort-exports': 'error',
+ 'perfectionist/sort-imports': [
+ 'error',
+ {
+ groups: [
+ 'builtin-type',
+ 'external-type',
+ 'internal-type',
+ ['parent-type', 'sibling-type', 'index-type'],
+ 'builtin',
+ 'external',
+ 'internal',
+ ['parent', 'sibling', 'index'],
+ 'object',
+ 'unknown',
+ ],
+ order: 'asc',
+ type: 'natural',
+ },
+ ],
+ 'perfectionist/sort-named-exports': 'error',
+ 'perfectionist/sort-named-imports': 'error',
+ 'sort-imports': 0,
+ 'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
+ 'style/quote-props': ['error', 'consistent-as-needed'],
+ 'test/no-only-tests': 'error',
+ 'unicorn/no-useless-spread': 'error',
+ 'unused-imports/no-unused-vars': ['error', { caughtErrors: 'none' }],
+ },
+ },
+)
diff --git a/jest.config.json b/jest.config.json
deleted file mode 100644
index 7fa817d3a..000000000
--- a/jest.config.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "preset": "ts-jest",
- "testEnvironment": "node",
- "coverageReporters": ["lcov"],
- "collectCoverageFrom": [
- "src/**",
- "!src/accessories/**",
- "!src/lib/definitions/generate-definitions.ts",
- "!src/lib/definitions/generator-configuration.ts",
- "!src/test-utils"
- ]
-}
diff --git a/package-lock.json b/package-lock.json
index 75214df48..ac46034fe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,34 +15,35 @@
"debug": "^4.3.6",
"fast-srp-hap": "^2.0.4",
"futoin-hkdf": "^1.5.3",
+ "long": "^5.2.3",
"node-persist": "^0.0.12",
"source-map-support": "^0.5.21",
- "tslib": "^2.6.3",
- "tweetnacl": "^1.0.3"
+ "tweetnacl": "^1.0.3",
+ "xml2js": "^0.6.2"
},
"devDependencies": {
+ "@antfu/eslint-config": "^3.0.0",
"@types/debug": "^4.1.12",
"@types/escape-html": "^1.0.4",
- "@types/jest": "^29.5.12",
- "@types/node": "^22.5.0",
+ "@types/node": "^22.5.1",
"@types/plist": "^3.0.5",
"@types/semver": "^7.3.7",
"@types/source-map-support": "^0.5.10",
- "@typescript-eslint/eslint-plugin": "^8.2.0",
- "@typescript-eslint/parser": "^8.2.0",
- "axios": "^1.7.5",
+ "@types/xml2js": "^0.4.14",
+ "@vitest/coverage-v8": "^2.0.5",
+ "axios": "^1.7.6",
"commander": "^12.1.0",
"escape-html": "^1.0.3",
- "eslint": "^8.57.0",
+ "eslint": "^9.9.1",
+ "eslint-plugin-format": "^0.1.2",
"http-parser-js": "^0.5.8",
- "jest": "^29.7.0",
"rimraf": "^6.0.1",
"semver": "^7.6.3",
"simple-plist": "^1.4.0",
- "ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typedoc": "^0.26.6",
- "typescript": "^5.5.4"
+ "typescript": "^5.5.4",
+ "vitest": "^2.0.5"
},
"engines": {
"node": "^18.4.0 || ^20 || ^22"
@@ -62,166 +63,146 @@
"node": ">=6.0.0"
}
},
- "node_modules/@babel/code-frame": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
- "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/highlight": "^7.24.7",
- "picocolors": "^1.0.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/compat-data": {
- "version": "7.25.4",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz",
- "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.25.2",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz",
- "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.24.7",
- "@babel/generator": "^7.25.0",
- "@babel/helper-compilation-targets": "^7.25.2",
- "@babel/helper-module-transforms": "^7.25.2",
- "@babel/helpers": "^7.25.0",
- "@babel/parser": "^7.25.0",
- "@babel/template": "^7.25.0",
- "@babel/traverse": "^7.25.2",
- "@babel/types": "^7.25.2",
- "convert-source-map": "^2.0.0",
- "debug": "^4.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
+ "node_modules/@antfu/eslint-config": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@antfu/eslint-config/-/eslint-config-3.0.0.tgz",
+ "integrity": "sha512-3HC35LrsW5kvHyVY2U6yat3Uz20/9Re5137LAKqAtl2tKictef2CmdYk5z+qK4UsaY32MMfg98MhuBbvAvZF1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@antfu/install-pkg": "^0.4.1",
+ "@clack/prompts": "^0.7.0",
+ "@eslint-community/eslint-plugin-eslint-comments": "^4.4.0",
+ "@stylistic/eslint-plugin": "^2.6.5",
+ "@typescript-eslint/eslint-plugin": "^8.3.0",
+ "@typescript-eslint/parser": "^8.3.0",
+ "@vitest/eslint-plugin": "^1.1.0",
+ "eslint-config-flat-gitignore": "^0.3.0",
+ "eslint-flat-config-utils": "^0.3.1",
+ "eslint-merge-processors": "^0.1.0",
+ "eslint-plugin-antfu": "^2.3.6",
+ "eslint-plugin-command": "^0.2.3",
+ "eslint-plugin-import-x": "^4.1.0",
+ "eslint-plugin-jsdoc": "^50.2.2",
+ "eslint-plugin-jsonc": "^2.16.0",
+ "eslint-plugin-markdown": "^5.1.0",
+ "eslint-plugin-n": "^17.10.2",
+ "eslint-plugin-no-only-tests": "^3.3.0",
+ "eslint-plugin-perfectionist": "^3.3.0",
+ "eslint-plugin-regexp": "^2.6.0",
+ "eslint-plugin-toml": "^0.11.1",
+ "eslint-plugin-unicorn": "^55.0.0",
+ "eslint-plugin-unused-imports": "^4.1.3",
+ "eslint-plugin-vue": "^9.27.0",
+ "eslint-plugin-yml": "^1.14.0",
+ "eslint-processor-vue-blocks": "^0.1.2",
+ "globals": "^15.9.0",
+ "jsonc-eslint-parser": "^2.4.0",
+ "local-pkg": "^0.5.0",
+ "parse-gitignore": "^2.0.0",
+ "picocolors": "^1.0.1",
+ "toml-eslint-parser": "^0.10.0",
+ "vue-eslint-parser": "^9.4.3",
+ "yaml-eslint-parser": "^1.2.3",
+ "yargs": "^17.7.2"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
- }
- },
- "node_modules/@babel/core/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
"bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.25.5",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz",
- "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.25.4",
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.25",
- "jsesc": "^2.5.1"
+ "eslint-config": "bin/index.js"
},
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.25.2",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz",
- "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.25.2",
- "@babel/helper-validator-option": "^7.24.8",
- "browserslist": "^4.23.1",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
},
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
- "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.24.7",
- "@babel/types": "^7.24.7"
+ "peerDependencies": {
+ "@eslint-react/eslint-plugin": "^1.5.8",
+ "@prettier/plugin-xml": "^3.4.1",
+ "@unocss/eslint-plugin": ">=0.50.0",
+ "astro-eslint-parser": "^1.0.2",
+ "eslint": "^9.5.0",
+ "eslint-plugin-astro": "^1.2.0",
+ "eslint-plugin-format": ">=0.1.0",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.4",
+ "eslint-plugin-solid": "^0.13.2",
+ "eslint-plugin-svelte": ">=2.35.1",
+ "prettier-plugin-astro": "^0.13.0",
+ "prettier-plugin-slidev": "^1.0.5",
+ "svelte-eslint-parser": ">=0.37.0"
},
- "engines": {
- "node": ">=6.9.0"
+ "peerDependenciesMeta": {
+ "@eslint-react/eslint-plugin": {
+ "optional": true
+ },
+ "@prettier/plugin-xml": {
+ "optional": true
+ },
+ "@unocss/eslint-plugin": {
+ "optional": true
+ },
+ "astro-eslint-parser": {
+ "optional": true
+ },
+ "eslint-plugin-astro": {
+ "optional": true
+ },
+ "eslint-plugin-format": {
+ "optional": true
+ },
+ "eslint-plugin-react-hooks": {
+ "optional": true
+ },
+ "eslint-plugin-react-refresh": {
+ "optional": true
+ },
+ "eslint-plugin-solid": {
+ "optional": true
+ },
+ "eslint-plugin-svelte": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-slidev": {
+ "optional": true
+ },
+ "svelte-eslint-parser": {
+ "optional": true
+ }
}
},
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.25.2",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz",
- "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==",
+ "node_modules/@antfu/install-pkg": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.4.1.tgz",
+ "integrity": "sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-module-imports": "^7.24.7",
- "@babel/helper-simple-access": "^7.24.7",
- "@babel/helper-validator-identifier": "^7.24.7",
- "@babel/traverse": "^7.25.2"
+ "package-manager-detector": "^0.2.0",
+ "tinyexec": "^0.3.0"
},
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
}
},
- "node_modules/@babel/helper-plugin-utils": {
- "version": "7.24.8",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz",
- "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==",
+ "node_modules/@antfu/utils": {
+ "version": "0.7.10",
+ "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz",
+ "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6.9.0"
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
}
},
- "node_modules/@babel/helper-simple-access": {
+ "node_modules/@babel/code-frame": {
"version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz",
- "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
+ "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.24.7",
- "@babel/types": "^7.24.7"
+ "@babel/highlight": "^7.24.7",
+ "picocolors": "^1.0.0"
},
"engines": {
"node": ">=6.9.0"
@@ -247,30 +228,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-validator-option": {
- "version": "7.24.8",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz",
- "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.25.0",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz",
- "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.25.0",
- "@babel/types": "^7.25.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/highlight": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
@@ -366,13 +323,13 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.25.4",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz",
- "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==",
+ "version": "7.25.6",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz",
+ "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.25.4"
+ "@babel/types": "^7.25.6"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -381,333 +338,564 @@
"node": ">=6.0.0"
}
},
- "node_modules/@babel/plugin-syntax-async-generators": {
- "version": "7.8.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
- "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "node_modules/@babel/types": {
+ "version": "7.25.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
+ "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
+ "@babel/helper-string-parser": "^7.24.8",
+ "@babel/helper-validator-identifier": "^7.24.7",
+ "to-fast-properties": "^2.0.0"
},
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "engines": {
+ "node": ">=6.9.0"
}
},
- "node_modules/@babel/plugin-syntax-bigint": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
- "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@clack/core": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.4.tgz",
+ "integrity": "sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "picocolors": "^1.0.0",
+ "sisteransi": "^1.0.5"
}
},
- "node_modules/@babel/plugin-syntax-class-properties": {
- "version": "7.12.13",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
- "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "node_modules/@clack/prompts": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.7.0.tgz",
+ "integrity": "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==",
+ "bundleDependencies": [
+ "is-unicode-supported"
+ ],
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.12.13"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "@clack/core": "^0.3.3",
+ "is-unicode-supported": "*",
+ "picocolors": "^1.0.0",
+ "sisteransi": "^1.0.5"
}
},
- "node_modules/@babel/plugin-syntax-class-static-block": {
- "version": "7.14.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
- "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "node_modules/@clack/prompts/node_modules/is-unicode-supported": {
+ "version": "1.3.0",
"dev": true,
+ "inBundle": true,
"license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.14.5"
- },
"engines": {
- "node": ">=6.9.0"
+ "node": ">=12"
},
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@babel/plugin-syntax-import-attributes": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz",
- "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==",
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.24.7"
+ "@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "node": ">=12"
}
},
- "node_modules/@babel/plugin-syntax-import-meta": {
- "version": "7.10.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
- "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.10.4"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
}
},
- "node_modules/@babel/plugin-syntax-json-strings": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
- "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "node_modules/@dprint/formatter": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@dprint/formatter/-/formatter-0.3.0.tgz",
+ "integrity": "sha512-N9fxCxbaBOrDkteSOzaCqwWjso5iAe+WJPsHC021JfHNj2ThInPNEF13ORDKta3llq5D1TlclODCvOvipH7bWQ==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
+ "license": "MIT"
},
- "node_modules/@babel/plugin-syntax-jsx": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz",
- "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==",
+ "node_modules/@dprint/markdown": {
+ "version": "0.17.8",
+ "resolved": "https://registry.npmjs.org/@dprint/markdown/-/markdown-0.17.8.tgz",
+ "integrity": "sha512-ukHFOg+RpG284aPdIg7iPrCYmMs3Dqy43S1ejybnwlJoFiW02b+6Bbr5cfZKFRYNP3dKGM86BqHEnMzBOyLvvA==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.24.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
+ "license": "MIT"
},
- "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
- "version": "7.10.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
- "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "node_modules/@dprint/toml": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/@dprint/toml/-/toml-0.6.2.tgz",
+ "integrity": "sha512-Mk5unEANsL/L+WHYU3NpDXt1ARU5bNU5k5OZELxaJodDycKG6RoRnSlZXpW6+7UN2PSnETAFVUdKrh937ZwtHA==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.10.4"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
+ "license": "MIT"
},
- "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
- "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "node_modules/@es-joy/jsdoccomment": {
+ "version": "0.43.1",
+ "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.1.tgz",
+ "integrity": "sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
+ "@types/eslint": "^8.56.5",
+ "@types/estree": "^1.0.5",
+ "@typescript-eslint/types": "^7.2.0",
+ "comment-parser": "1.4.1",
+ "esquery": "^1.5.0",
+ "jsdoc-type-pratt-parser": "~4.0.0"
},
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "engines": {
+ "node": ">=16"
}
},
- "node_modules/@babel/plugin-syntax-numeric-separator": {
- "version": "7.10.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
- "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "node_modules/@es-joy/jsdoccomment/node_modules/@types/eslint": {
+ "version": "8.56.12",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz",
+ "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.10.4"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "@types/estree": "*",
+ "@types/json-schema": "*"
}
},
- "node_modules/@babel/plugin-syntax-object-rest-spread": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
- "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "node_modules/@es-joy/jsdoccomment/node_modules/@typescript-eslint/types": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
+ "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
},
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@babel/plugin-syntax-optional-catch-binding": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
- "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
}
},
- "node_modules/@babel/plugin-syntax-optional-chaining": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
- "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
}
},
- "node_modules/@babel/plugin-syntax-private-property-in-object": {
- "version": "7.14.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
- "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.14.5"
- },
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "node": ">=12"
}
},
- "node_modules/@babel/plugin-syntax-top-level-await": {
- "version": "7.14.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
- "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.14.5"
- },
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "node": ">=12"
}
},
- "node_modules/@babel/plugin-syntax-typescript": {
- "version": "7.25.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz",
- "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==",
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.24.8"
- },
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
+ "node": ">=12"
}
},
- "node_modules/@babel/template": {
- "version": "7.25.0",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
- "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==",
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.24.7",
- "@babel/parser": "^7.25.0",
- "@babel/types": "^7.25.0"
- },
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": ">=12"
}
},
- "node_modules/@babel/traverse": {
- "version": "7.25.4",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz",
- "integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==",
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.24.7",
- "@babel/generator": "^7.25.4",
- "@babel/parser": "^7.25.4",
- "@babel/template": "^7.25.0",
- "@babel/types": "^7.25.4",
- "debug": "^4.3.1",
- "globals": "^11.1.0"
- },
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": ">=12"
}
},
- "node_modules/@babel/traverse/node_modules/globals": {
- "version": "11.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
- "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": ">=4"
+ "node": ">=12"
}
},
- "node_modules/@babel/types": {
- "version": "7.25.4",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz",
- "integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==",
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.24.8",
- "@babel/helper-validator-identifier": "^7.24.7",
- "to-fast-properties": "^2.0.0"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": ">=12"
}
},
- "node_modules/@bcoe/v8-coverage": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
- "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
},
- "node_modules/@cspotcode/source-map-support": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
- "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jridgewell/trace-mapping": "0.3.9"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
"node": ">=12"
}
},
- "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
- "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-plugin-eslint-comments": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.4.0.tgz",
+ "integrity": "sha512-yljsWl5Qv3IkIRmJ38h3NrHXFCm4EUl55M8doGTF6hvzvFF8kRpextgSrg2dwHev9lzBZyafCr9RelGIyQm6fw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/resolve-uri": "^3.0.3",
- "@jridgewell/sourcemap-codec": "^1.4.10"
+ "escape-string-regexp": "^4.0.0",
+ "ignore": "^5.2.4"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
}
},
"node_modules/@eslint-community/eslint-utils": {
@@ -726,6 +914,19 @@
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
},
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
"node_modules/@eslint-community/regexpp": {
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
@@ -736,31 +937,32 @@
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
- "node_modules/@eslint/eslintrc": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
- "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "node_modules/@eslint/compat": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.1.1.tgz",
+ "integrity": "sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA==",
"dev": true,
- "license": "MIT",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz",
+ "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==",
+ "dev": true,
+ "license": "Apache-2.0",
"dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^9.6.0",
- "globals": "^13.19.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
+ "@eslint/object-schema": "^2.1.4",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
},
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
- "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+ "node_modules/@eslint/config-array/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
@@ -771,7 +973,7 @@
"concat-map": "0.0.1"
}
},
- "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+ "node_modules/@eslint/config-array/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
@@ -784,19 +986,90 @@
"node": "*"
}
},
- "node_modules/@eslint/js": {
- "version": "8.57.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
- "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
+ "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/@homebridge/ciao": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.3.1.tgz",
+ "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz",
+ "integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
+ "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@homebridge/ciao": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.3.1.tgz",
"integrity": "sha512-87tQCBNNnTymlbg8pKlQjRsk7a5uuqhWBpCbUriVYUebz3voJkLbbTmp0TQg7Sa6Jnpk/Uo6LA8zAOy2sbK9bw==",
"license": "MIT",
"dependencies": {
@@ -845,46 +1118,6 @@
"node": ">=0.3.0"
}
},
- "node_modules/@humanwhocodes/config-array": {
- "version": "0.11.14",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
- "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
- "deprecated": "Use @eslint/config-array instead",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@humanwhocodes/object-schema": "^2.0.2",
- "debug": "^4.3.1",
- "minimatch": "^3.0.5"
- },
- "engines": {
- "node": ">=10.10.0"
- }
- },
- "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@@ -899,13 +1132,19 @@
"url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@humanwhocodes/object-schema": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
- "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
- "deprecated": "Use @eslint/object-schema instead",
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz",
+ "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
@@ -938,44 +1177,6 @@
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
- "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
@@ -992,576 +1193,390 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
- "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ "node": ">=8"
}
},
- "node_modules/@istanbuljs/load-nyc-config": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
- "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "camelcase": "^5.3.1",
- "find-up": "^4.1.0",
- "get-package-type": "^0.1.0",
- "js-yaml": "^3.13.1",
- "resolve-from": "^5.0.0"
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
- "node": ">=8"
+ "node": ">=6.0.0"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "sprintf-js": "~1.0.2"
+ "engines": {
+ "node": ">=6.0.0"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
"engines": {
- "node": ">=8"
+ "node": ">=6.0.0"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "node_modules/@leichtgewicht/ip-codec": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
+ "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
+ "license": "MIT"
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "p-locate": "^4.1.0"
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
},
"engines": {
- "node": ">=8"
+ "node": ">= 8"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
"engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">= 8"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "p-limit": "^2.2.0"
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
},
"engines": {
- "node": ">=8"
+ "node": ">= 8"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"license": "MIT",
+ "optional": true,
"engines": {
- "node": ">=8"
+ "node": ">=14"
}
},
- "node_modules/@istanbuljs/schema": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
- "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "node_modules/@pkgr/core": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
+ "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unts"
}
},
- "node_modules/@jest/console": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
- "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz",
+ "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/core": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz",
- "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/console": "^29.7.0",
- "@jest/reporters": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "ansi-escapes": "^4.2.1",
- "chalk": "^4.0.0",
- "ci-info": "^3.2.0",
- "exit": "^0.1.2",
- "graceful-fs": "^4.2.9",
- "jest-changed-files": "^29.7.0",
- "jest-config": "^29.7.0",
- "jest-haste-map": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-regex-util": "^29.6.3",
- "jest-resolve": "^29.7.0",
- "jest-resolve-dependencies": "^29.7.0",
- "jest-runner": "^29.7.0",
- "jest-runtime": "^29.7.0",
- "jest-snapshot": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "jest-watcher": "^29.7.0",
- "micromatch": "^4.0.4",
- "pretty-format": "^29.7.0",
- "slash": "^3.0.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
- },
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
- }
- },
- "node_modules/@jest/environment": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
- "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz",
+ "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jest/fake-timers": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "jest-mock": "^29.7.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/expect": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz",
- "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz",
+ "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "expect": "^29.7.0",
- "jest-snapshot": "^29.7.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/expect-utils": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
- "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz",
+ "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "jest-get-type": "^29.6.3"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/fake-timers": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
- "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz",
+ "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "@sinonjs/fake-timers": "^10.0.2",
- "@types/node": "*",
- "jest-message-util": "^29.7.0",
- "jest-mock": "^29.7.0",
- "jest-util": "^29.7.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/globals": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz",
- "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz",
+ "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/expect": "^29.7.0",
- "@jest/types": "^29.6.3",
- "jest-mock": "^29.7.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/reporters": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
- "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz",
+ "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@bcoe/v8-coverage": "^0.2.3",
- "@jest/console": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@jridgewell/trace-mapping": "^0.3.18",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "collect-v8-coverage": "^1.0.0",
- "exit": "^0.1.2",
- "glob": "^7.1.3",
- "graceful-fs": "^4.2.9",
- "istanbul-lib-coverage": "^3.0.0",
- "istanbul-lib-instrument": "^6.0.0",
- "istanbul-lib-report": "^3.0.0",
- "istanbul-lib-source-maps": "^4.0.0",
- "istanbul-reports": "^3.1.3",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-worker": "^29.7.0",
- "slash": "^3.0.0",
- "string-length": "^4.0.1",
- "strip-ansi": "^6.0.0",
- "v8-to-istanbul": "^9.0.1"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
- },
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
- }
- },
- "node_modules/@jest/schemas": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
- "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz",
+ "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@sinclair/typebox": "^0.27.8"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/source-map": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz",
- "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz",
+ "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.18",
- "callsites": "^3.0.0",
- "graceful-fs": "^4.2.9"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/test-result": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
- "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz",
+ "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==",
+ "cpu": [
+ "riscv64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jest/console": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/istanbul-lib-coverage": "^2.0.0",
- "collect-v8-coverage": "^1.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/test-sequencer": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz",
- "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz",
+ "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==",
+ "cpu": [
+ "s390x"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jest/test-result": "^29.7.0",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/transform": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
- "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz",
+ "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/core": "^7.11.6",
- "@jest/types": "^29.6.3",
- "@jridgewell/trace-mapping": "^0.3.18",
- "babel-plugin-istanbul": "^6.1.1",
- "chalk": "^4.0.0",
- "convert-source-map": "^2.0.0",
- "fast-json-stable-stringify": "^2.1.0",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "jest-regex-util": "^29.6.3",
- "jest-util": "^29.7.0",
- "micromatch": "^4.0.4",
- "pirates": "^4.0.4",
- "slash": "^3.0.0",
- "write-file-atomic": "^4.0.2"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jest/types": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
- "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz",
+ "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jest/schemas": "^29.6.3",
- "@types/istanbul-lib-coverage": "^2.0.0",
- "@types/istanbul-reports": "^3.0.0",
- "@types/node": "*",
- "@types/yargs": "^17.0.8",
- "chalk": "^4.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
- "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz",
+ "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz",
+ "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz",
+ "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "dev": true,
- "license": "MIT"
+ "optional": true,
+ "os": [
+ "win32"
+ ]
},
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "node_modules/@shikijs/core": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.15.1.tgz",
+ "integrity": "sha512-DwkQTDNlhr7PwZMJswdvWIKts+2mqjIn8txByr88fhBRBtUSsIQR43RRoATjRrbeu4hyNTSTMBdxgp/vlxnxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
+ "@types/hast": "^3.0.4"
}
},
- "node_modules/@leichtgewicht/ip-codec": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
- "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
- "license": "MIT"
- },
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "node_modules/@stylistic/eslint-plugin": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.7.2.tgz",
+ "integrity": "sha512-3DVLU5HEuk2pQoBmXJlzvrxbKNpu2mJ0SRqz5O/CJjyNCr12ZiPcYMEtuArTyPOk5i7bsAU44nywh1rGfe3gKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
+ "@types/eslint": "^9.6.1",
+ "@typescript-eslint/utils": "^8.3.0",
+ "eslint-visitor-keys": "^4.0.0",
+ "espree": "^10.1.0",
+ "estraverse": "^5.3.0",
+ "picomatch": "^4.0.2"
},
"engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@shikijs/core": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.14.1.tgz",
- "integrity": "sha512-KyHIIpKNaT20FtFPFjCQB5WVSTpLR/n+jQXhWHWVUMm9MaOaG9BGOG0MSyt7yA4+Lm+4c9rTc03tt3nYzeYSfw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.4"
- }
- },
- "node_modules/@sinclair/typebox": {
- "version": "0.27.8",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
- "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@sinonjs/commons": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
- "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "type-detect": "4.0.8"
- }
- },
- "node_modules/@sinonjs/fake-timers": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
- "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.0"
+ "peerDependencies": {
+ "eslint": ">=8.40.0"
}
},
"node_modules/@tsconfig/node10": {
@@ -1592,51 +1607,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
- }
- },
- "node_modules/@types/babel__generator": {
- "version": "7.6.8",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
- "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__traverse": {
- "version": "7.20.6",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
- "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.20.7"
- }
- },
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@@ -1654,16 +1624,24 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/graceful-fs": {
- "version": "4.1.9",
- "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
- "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==",
+ "node_modules/@types/eslint": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
+ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/node": "*"
+ "@types/estree": "*",
+ "@types/json-schema": "*"
}
},
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/hast": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
@@ -1674,42 +1652,21 @@
"@types/unist": "*"
}
},
- "node_modules/@types/istanbul-lib-coverage": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
- "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true,
"license": "MIT"
},
- "node_modules/@types/istanbul-lib-report": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
- "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/istanbul-lib-coverage": "*"
- }
- },
- "node_modules/@types/istanbul-reports": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
- "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "node_modules/@types/mdast": {
+ "version": "3.0.15",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz",
+ "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/istanbul-lib-report": "*"
- }
- },
- "node_modules/@types/jest": {
- "version": "29.5.12",
- "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz",
- "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "expect": "^29.0.0",
- "pretty-format": "^29.0.0"
+ "@types/unist": "^2"
}
},
"node_modules/@types/ms": {
@@ -1720,15 +1677,22 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "22.5.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.0.tgz",
- "integrity": "sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==",
+ "version": "22.5.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.1.tgz",
+ "integrity": "sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
}
},
+ "node_modules/@types/normalize-package-data": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
+ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/plist": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz",
@@ -1757,49 +1721,35 @@
"source-map": "^0.6.0"
}
},
- "node_modules/@types/stack-utils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
- "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/unist": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
- "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
"dev": true,
"license": "MIT"
},
- "node_modules/@types/yargs": {
- "version": "17.0.33",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
- "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
+ "node_modules/@types/xml2js": {
+ "version": "0.4.14",
+ "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
+ "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/yargs-parser": "*"
+ "@types/node": "*"
}
},
- "node_modules/@types/yargs-parser": {
- "version": "21.0.3",
- "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
- "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.2.0.tgz",
- "integrity": "sha512-02tJIs655em7fvt9gps/+4k4OsKULYGtLBPJfOsmOq1+3cdClYiF0+d6mHu6qDnTcg88wJBkcPLpQhq7FyDz0A==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.3.0.tgz",
+ "integrity": "sha512-FLAIn63G5KH+adZosDYiutqkOkYEx0nvcwNNfJAf+c7Ae/H35qWwTYvPZUKFj5AS+WfHG/WJJfWnDnyNUlp8UA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.2.0",
- "@typescript-eslint/type-utils": "8.2.0",
- "@typescript-eslint/utils": "8.2.0",
- "@typescript-eslint/visitor-keys": "8.2.0",
+ "@typescript-eslint/scope-manager": "8.3.0",
+ "@typescript-eslint/type-utils": "8.3.0",
+ "@typescript-eslint/utils": "8.3.0",
+ "@typescript-eslint/visitor-keys": "8.3.0",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
@@ -1823,16 +1773,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.2.0.tgz",
- "integrity": "sha512-j3Di+o0lHgPrb7FxL3fdEy6LJ/j2NE8u+AP/5cQ9SKb+JLH6V6UHDqJ+e0hXBkHP1wn1YDFjYCS9LBQsZDlDEg==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.3.0.tgz",
+ "integrity": "sha512-h53RhVyLu6AtpUzVCYLPhZGL5jzTD9fZL+SYf/+hYOx2bDkyQXztXSc4tbvKYHzfMXExMLiL9CWqJmVz6+78IQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.2.0",
- "@typescript-eslint/types": "8.2.0",
- "@typescript-eslint/typescript-estree": "8.2.0",
- "@typescript-eslint/visitor-keys": "8.2.0",
+ "@typescript-eslint/scope-manager": "8.3.0",
+ "@typescript-eslint/types": "8.3.0",
+ "@typescript-eslint/typescript-estree": "8.3.0",
+ "@typescript-eslint/visitor-keys": "8.3.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1852,14 +1802,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.2.0.tgz",
- "integrity": "sha512-OFn80B38yD6WwpoHU2Tz/fTz7CgFqInllBoC3WP+/jLbTb4gGPTy9HBSTsbDWkMdN55XlVU0mMDYAtgvlUspGw==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.3.0.tgz",
+ "integrity": "sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.2.0",
- "@typescript-eslint/visitor-keys": "8.2.0"
+ "@typescript-eslint/types": "8.3.0",
+ "@typescript-eslint/visitor-keys": "8.3.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1870,14 +1820,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.2.0.tgz",
- "integrity": "sha512-g1CfXGFMQdT5S+0PSO0fvGXUaiSkl73U1n9LTK5aRAFnPlJ8dLKkXr4AaLFvPedW8lVDoMgLLE3JN98ZZfsj0w==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.3.0.tgz",
+ "integrity": "sha512-wrV6qh//nLbfXZQoj32EXKmwHf4b7L+xXLrP3FZ0GOUU72gSvLjeWUl5J5Ue5IwRxIV1TfF73j/eaBapxx99Lg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.2.0",
- "@typescript-eslint/utils": "8.2.0",
+ "@typescript-eslint/typescript-estree": "8.3.0",
+ "@typescript-eslint/utils": "8.3.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
@@ -1895,9 +1845,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.2.0.tgz",
- "integrity": "sha512-6a9QSK396YqmiBKPkJtxsgZZZVjYQ6wQ/TlI0C65z7vInaETuC6HAHD98AGLC8DyIPqHytvNuS8bBVvNLKyqvQ==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.3.0.tgz",
+ "integrity": "sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1909,16 +1859,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.2.0.tgz",
- "integrity": "sha512-kiG4EDUT4dImplOsbh47B1QnNmXSoUqOjWDvCJw/o8LgfD0yr7k2uy54D5Wm0j4t71Ge1NkynGhpWdS0dEIAUA==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.3.0.tgz",
+ "integrity": "sha512-Mq7FTHl0R36EmWlCJWojIC1qn/ZWo2YiWYc1XVtasJ7FIgjo0MVv9rZWXEE7IK2CGrtwe1dVOxWwqXUdNgfRCA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
- "@typescript-eslint/types": "8.2.0",
- "@typescript-eslint/visitor-keys": "8.2.0",
+ "@typescript-eslint/types": "8.3.0",
+ "@typescript-eslint/visitor-keys": "8.3.0",
"debug": "^4.3.4",
- "globby": "^11.1.0",
+ "fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
@@ -1938,16 +1888,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.2.0.tgz",
- "integrity": "sha512-O46eaYKDlV3TvAVDNcoDzd5N550ckSe8G4phko++OCSC1dYIb9LTc3HDGYdWqWIAT5qDUKphO6sd9RrpIJJPfg==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.3.0.tgz",
+ "integrity": "sha512-F77WwqxIi/qGkIGOGXNBLV7nykwfjLsdauRB/DOFPdv6LTF3BHHkBpq81/b5iMPSF055oO2BiivDJV4ChvNtXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
- "@typescript-eslint/scope-manager": "8.2.0",
- "@typescript-eslint/types": "8.2.0",
- "@typescript-eslint/typescript-estree": "8.2.0"
+ "@typescript-eslint/scope-manager": "8.3.0",
+ "@typescript-eslint/types": "8.3.0",
+ "@typescript-eslint/typescript-estree": "8.3.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1961,13 +1911,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.2.0.tgz",
- "integrity": "sha512-sbgsPMW9yLvS7IhCi8IpuK1oBmtbWUNP+hBdwl/I9nzqVsszGnNGti5r9dUtF5RLivHUFFIdRvLiTsPhzSyJ3Q==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.3.0.tgz",
+ "integrity": "sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.2.0",
+ "@typescript-eslint/types": "8.3.0",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
@@ -1978,12 +1928,232 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@ungap/structured-clone": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
- "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
- "license": "ISC"
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@vitest/coverage-v8": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.0.5.tgz",
+ "integrity": "sha512-qeFcySCg5FLO2bHHSa0tAZAOnAUbp4L6/A5JDuj9+bt53JREl8hpLjLHEWF0e/gWc8INVpJaqA7+Ene2rclpZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.3.0",
+ "@bcoe/v8-coverage": "^0.2.3",
+ "debug": "^4.3.5",
+ "istanbul-lib-coverage": "^3.2.2",
+ "istanbul-lib-report": "^3.0.1",
+ "istanbul-lib-source-maps": "^5.0.6",
+ "istanbul-reports": "^3.1.7",
+ "magic-string": "^0.30.10",
+ "magicast": "^0.3.4",
+ "std-env": "^3.7.0",
+ "test-exclude": "^7.0.1",
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "vitest": "2.0.5"
+ }
+ },
+ "node_modules/@vitest/eslint-plugin": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.0.tgz",
+ "integrity": "sha512-Ur80Y27Wbw8gFHJ3cv6vypcjXmrx6QHfw+q435h6Q2L+tf+h4Xf5pJTCL4YU/Jps9EVeggQxS85OcUZU7sdXRw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@typescript-eslint/utils": ">= 8.0",
+ "eslint": ">= 8.57.0",
+ "typescript": ">= 5.0.0",
+ "vitest": "*"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/utils": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ },
+ "vitest": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz",
+ "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "2.0.5",
+ "@vitest/utils": "2.0.5",
+ "chai": "^5.1.1",
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz",
+ "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.5.tgz",
+ "integrity": "sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "2.0.5",
+ "pathe": "^1.1.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz",
+ "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "2.0.5",
+ "magic-string": "^0.30.10",
+ "pathe": "^1.1.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz",
+ "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^3.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz",
+ "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "2.0.5",
+ "estree-walker": "^3.0.3",
+ "loupe": "^3.1.1",
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.38.tgz",
+ "integrity": "sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/parser": "^7.24.7",
+ "@vue/shared": "3.4.38",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.38.tgz",
+ "integrity": "sha512-Osc/c7ABsHXTsETLgykcOwIxFktHfGSUDkb05V61rocEfsFDcjDLH/IHJSNJP+/Sv9KeN2Lx1V6McZzlSb9EhQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@vue/compiler-core": "3.4.38",
+ "@vue/shared": "3.4.38"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.38.tgz",
+ "integrity": "sha512-s5QfZ+9PzPh3T5H4hsQDJtI8x7zdJaew/dCGgqZ2630XdzaZ3AD8xGZfBqpT8oaD/p2eedd+pL8tD5vvt5ZYJQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/parser": "^7.24.7",
+ "@vue/compiler-core": "3.4.38",
+ "@vue/compiler-dom": "3.4.38",
+ "@vue/compiler-ssr": "3.4.38",
+ "@vue/shared": "3.4.38",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.10",
+ "postcss": "^8.4.40",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.38.tgz",
+ "integrity": "sha512-YXznKFQ8dxYpAz9zLuVvfcXhc31FSPFDcqr0kyujbOwNhlmaNvL2QfIy+RZeJgSn5Fk54CWoEUeW+NVBAogGaw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@vue/compiler-dom": "3.4.38",
+ "@vue/shared": "3.4.38"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.38.tgz",
+ "integrity": "sha512-q0xCiLkuWWQLzVrecPb0RMsNWyxICOjPrcrwxTUEHb1fsnvni4dcuyG7RT/Ie7VPTvnjzIaWzRMUBsrqNj/hhw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.10",
@@ -2048,35 +2218,6 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/ansi-escapes": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
- "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "type-fest": "^0.21.3"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/ansi-escapes/node_modules/type-fest": {
- "version": "0.21.3",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
- "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -2103,18 +2244,14 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "node_modules/are-docs-informative": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz",
+ "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
+ "license": "MIT",
"engines": {
- "node": ">= 8"
+ "node": ">=14"
}
},
"node_modules/arg": {
@@ -2153,23 +2290,16 @@
"integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==",
"license": "MIT"
},
- "node_modules/array-union": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
- "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=12"
}
},
- "node_modules/async": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
- "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -2193,9 +2323,9 @@
}
},
"node_modules/axios": {
- "version": "1.7.5",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz",
- "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==",
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.6.tgz",
+ "integrity": "sha512-Ekur6XDwhnJ5RgOCaxFnXyqlPALI3rVeukZMwOdfghW7/wGz784BYKiQq+QD8NPcr91KRo30KfHOchyijwWw7g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2204,132 +2334,6 @@
"proxy-from-env": "^1.1.0"
}
},
- "node_modules/babel-jest": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
- "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/transform": "^29.7.0",
- "@types/babel__core": "^7.1.14",
- "babel-plugin-istanbul": "^6.1.1",
- "babel-preset-jest": "^29.6.3",
- "chalk": "^4.0.0",
- "graceful-fs": "^4.2.9",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.8.0"
- }
- },
- "node_modules/babel-plugin-istanbul": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
- "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@istanbuljs/load-nyc-config": "^1.0.0",
- "@istanbuljs/schema": "^0.1.2",
- "istanbul-lib-instrument": "^5.0.4",
- "test-exclude": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
- "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@babel/core": "^7.12.3",
- "@babel/parser": "^7.14.7",
- "@istanbuljs/schema": "^0.1.2",
- "istanbul-lib-coverage": "^3.2.0",
- "semver": "^6.3.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/babel-plugin-istanbul/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/babel-plugin-jest-hoist": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
- "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.3.3",
- "@babel/types": "^7.3.3",
- "@types/babel__core": "^7.1.14",
- "@types/babel__traverse": "^7.0.6"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/babel-preset-current-node-syntax": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz",
- "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/plugin-syntax-async-generators": "^7.8.4",
- "@babel/plugin-syntax-bigint": "^7.8.3",
- "@babel/plugin-syntax-class-properties": "^7.12.13",
- "@babel/plugin-syntax-class-static-block": "^7.14.5",
- "@babel/plugin-syntax-import-attributes": "^7.24.7",
- "@babel/plugin-syntax-import-meta": "^7.10.4",
- "@babel/plugin-syntax-json-strings": "^7.8.3",
- "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
- "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
- "@babel/plugin-syntax-numeric-separator": "^7.10.4",
- "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
- "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
- "@babel/plugin-syntax-optional-chaining": "^7.8.3",
- "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
- "@babel/plugin-syntax-top-level-await": "^7.14.5"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/babel-preset-jest": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
- "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "babel-plugin-jest-hoist": "^29.6.3",
- "babel-preset-current-node-syntax": "^1.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -2380,6 +2384,13 @@
"multicast-dns-service-types": "^1.1.0"
}
},
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/bplist-creator": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.1.tgz",
@@ -2459,35 +2470,35 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/bs-logger": {
- "version": "0.2.6",
- "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
- "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "license": "MIT"
+ },
+ "node_modules/builtin-modules": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
+ "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "fast-json-stable-stringify": "2.x"
- },
"engines": {
- "node": ">= 6"
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/bser": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
- "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "node-int64": "^0.4.0"
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/buffer-from": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "license": "MIT"
- },
"node_modules/call-bind": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
@@ -2517,20 +2528,10 @@
"node": ">=6"
}
},
- "node_modules/camelcase": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/caniuse-lite": {
- "version": "1.0.30001653",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz",
- "integrity": "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==",
+ "version": "1.0.30001655",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz",
+ "integrity": "sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==",
"dev": true,
"funding": [
{
@@ -2548,6 +2549,23 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/chai": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz",
+ "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -2565,20 +2583,53 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/char-regex": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
- "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "node_modules/character-entities": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
+ "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
+ "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-reference-invalid": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
+ "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+ "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=10"
+ "node": ">= 16"
}
},
"node_modules/ci-info": {
- "version": "3.9.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
- "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz",
+ "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==",
"dev": true,
"funding": [
{
@@ -2591,12 +2642,28 @@
"node": ">=8"
}
},
- "node_modules/cjs-module-lexer": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz",
- "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==",
+ "node_modules/clean-regexp": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz",
+ "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.5"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/clean-regexp/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
},
"node_modules/cliui": {
"version": "8.0.1",
@@ -2613,23 +2680,45 @@
"node": ">=12"
}
},
- "node_modules/co": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
- "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
"engines": {
- "iojs": ">= 1.0.0",
- "node": ">= 0.12.0"
+ "node": ">=8"
}
},
- "node_modules/collect-v8-coverage": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
- "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
},
"node_modules/color-convert": {
"version": "2.0.1",
@@ -2674,6 +2763,16 @@
"node": ">=18"
}
},
+ "node_modules/comment-parser": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
+ "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12.0.0"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2681,33 +2780,25 @@
"dev": true,
"license": "MIT"
},
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "node_modules/confbox": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz",
+ "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==",
"dev": true,
"license": "MIT"
},
- "node_modules/create-jest": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
- "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==",
+ "node_modules/core-js-compat": {
+ "version": "3.38.1",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz",
+ "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "^29.6.3",
- "chalk": "^4.0.0",
- "exit": "^0.1.2",
- "graceful-fs": "^4.2.9",
- "jest-config": "^29.7.0",
- "jest-util": "^29.7.0",
- "prompts": "^2.0.1"
- },
- "bin": {
- "create-jest": "bin/create-jest.js"
+ "browserslist": "^4.23.3"
},
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
}
},
"node_modules/create-require": {
@@ -2732,6 +2823,19 @@
"node": ">= 8"
}
},
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/debug": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
@@ -2749,19 +2853,14 @@
}
}
},
- "node_modules/dedent": {
- "version": "1.5.3",
- "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz",
- "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==",
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
"dev": true,
"license": "MIT",
- "peerDependencies": {
- "babel-plugin-macros": "^3.1.0"
- },
- "peerDependenciesMeta": {
- "babel-plugin-macros": {
- "optional": true
- }
+ "engines": {
+ "node": ">=6"
}
},
"node_modules/deep-equal": {
@@ -2803,16 +2902,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/deepmerge": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
- "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -2857,16 +2946,6 @@
"node": ">=0.4.0"
}
},
- "node_modules/detect-newline": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
- "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -2877,29 +2956,6 @@
"node": ">=0.3.1"
}
},
- "node_modules/diff-sequences": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
- "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/dir-glob": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
- "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-type": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/dns-packet": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz",
@@ -2938,49 +2994,34 @@
"dev": true,
"license": "MIT"
},
- "node_modules/ejs": {
- "version": "3.1.10",
- "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
- "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "jake": "^10.8.5"
- },
- "bin": {
- "ejs": "bin/cli.js"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/electron-to-chromium": {
"version": "1.5.13",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz",
"integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==",
"dev": true,
- "license": "ISC"
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
},
- "node_modules/emittery": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
- "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "node_modules/enhanced-resolve": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
+ "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
},
- "funding": {
- "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ "engines": {
+ "node": ">=10.13.0"
}
},
- "node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -3045,10 +3086,56 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es-module-lexer": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
+ "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
"node_modules/escalade": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
- "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3076,42 +3163,38 @@
}
},
"node_modules/eslint": {
- "version": "8.57.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
- "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
+ "version": "9.9.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.1.tgz",
+ "integrity": "sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.6.1",
- "@eslint/eslintrc": "^2.1.4",
- "@eslint/js": "8.57.0",
- "@humanwhocodes/config-array": "^0.11.14",
+ "@eslint-community/regexpp": "^4.11.0",
+ "@eslint/config-array": "^0.18.0",
+ "@eslint/eslintrc": "^3.1.0",
+ "@eslint/js": "9.9.1",
"@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.3.0",
"@nodelib/fs.walk": "^1.2.8",
- "@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
- "doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.2.2",
- "eslint-visitor-keys": "^3.4.3",
- "espree": "^9.6.1",
- "esquery": "^1.4.2",
+ "eslint-scope": "^8.0.2",
+ "eslint-visitor-keys": "^4.0.0",
+ "espree": "^10.1.0",
+ "esquery": "^1.5.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^6.0.1",
+ "file-entry-cache": "^8.0.0",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
- "globals": "^13.19.0",
- "graphemer": "^1.4.0",
"ignore": "^5.2.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
- "js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
@@ -3125,578 +3208,591 @@
"eslint": "bin/eslint.js"
},
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-scope": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
- "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "url": "https://eslint.org/donate"
},
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "peerDependencies": {
+ "jiti": "*"
},
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
}
},
- "node_modules/eslint/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "node_modules/eslint-compat-utils": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz",
+ "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/eslint/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/espree": {
- "version": "9.6.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
- "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "acorn": "^8.9.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^3.4.1"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true,
- "license": "BSD-2-Clause",
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "estraverse": "^5.1.0"
+ "semver": "^7.5.4"
},
"engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "estraverse": "^5.2.0"
+ "node": ">=12"
},
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/event-stream": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz",
- "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==",
- "license": "MIT",
- "dependencies": {
- "duplexer": "^0.1.1",
- "from": "^0.1.7",
- "map-stream": "0.0.7",
- "pause-stream": "^0.0.11",
- "split": "^1.0.1",
- "stream-combiner": "^0.2.2",
- "through": "^2.3.8"
+ "peerDependencies": {
+ "eslint": ">=6.0.0"
}
},
- "node_modules/execa": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
- "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "node_modules/eslint-config-flat-gitignore": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-flat-gitignore/-/eslint-config-flat-gitignore-0.3.0.tgz",
+ "integrity": "sha512-0Ndxo4qGhcewjTzw52TK06Mc00aDtHNTdeeW2JfONgDcLkRO/n/BteMRzNVpLQYxdCC/dFEilfM9fjjpGIJ9Og==",
"dev": true,
"license": "MIT",
"dependencies": {
- "cross-spawn": "^7.0.3",
- "get-stream": "^6.0.0",
- "human-signals": "^2.1.0",
- "is-stream": "^2.0.0",
- "merge-stream": "^2.0.0",
- "npm-run-path": "^4.0.1",
- "onetime": "^5.1.2",
- "signal-exit": "^3.0.3",
- "strip-final-newline": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
+ "@eslint/compat": "^1.1.1",
+ "find-up-simple": "^1.0.0"
},
"funding": {
- "url": "https://github.com/sindresorhus/execa?sponsor=1"
- }
- },
- "node_modules/exit": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
- "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
- "dev": true,
- "engines": {
- "node": ">= 0.8.0"
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "eslint": "^9.5.0"
}
},
- "node_modules/expect": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
- "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+ "node_modules/eslint-flat-config-utils": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/eslint-flat-config-utils/-/eslint-flat-config-utils-0.3.1.tgz",
+ "integrity": "sha512-eFT3EaoJN1hlN97xw4FIEX//h0TiFUobgl2l5uLkIwhVN9ahGq95Pbs+i1/B5UACA78LO3rco3JzuvxLdTUOPA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/expect-utils": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0"
+ "@types/eslint": "^9.6.0",
+ "pathe": "^1.1.2"
},
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
}
},
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "license": "MIT"
- },
- "node_modules/fast-glob": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
- "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "node_modules/eslint-formatting-reporter": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-formatting-reporter/-/eslint-formatting-reporter-0.0.0.tgz",
+ "integrity": "sha512-k9RdyTqxqN/wNYVaTk/ds5B5rA8lgoAmvceYN7bcZMBwU7TuXx5ntewJv81eF3pIL/CiJE+pJZm36llG8yhyyw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.4"
+ "prettier-linter-helpers": "^1.0.0"
},
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
},
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-srp-hap": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/fast-srp-hap/-/fast-srp-hap-2.0.4.tgz",
- "integrity": "sha512-lHRYYaaIbMrhZtsdGTwPN82UbqD9Bv8QfOlKs+Dz6YRnByZifOh93EYmf2iEWFtkOEIqR2IK8cFD0UN5wLIWBQ==",
- "license": "MIT",
- "engines": {
- "node": ">=10.17.0"
- }
- },
- "node_modules/fastq": {
- "version": "1.17.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
- "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "peerDependencies": {
+ "eslint": ">=8.40.0"
+ }
+ },
+ "node_modules/eslint-import-resolver-node": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+ "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "reusify": "^1.0.4"
+ "debug": "^3.2.7",
+ "is-core-module": "^2.13.0",
+ "resolve": "^1.22.4"
}
},
- "node_modules/fb-watchman": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
- "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "node_modules/eslint-import-resolver-node/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
"dependencies": {
- "bser": "2.1.1"
+ "ms": "^2.1.1"
}
},
- "node_modules/file-entry-cache": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
- "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "node_modules/eslint-merge-processors": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-merge-processors/-/eslint-merge-processors-0.1.0.tgz",
+ "integrity": "sha512-IvRXXtEajLeyssvW4wJcZ2etxkR9mUf4zpNwgI+m/Uac9RfXHskuJefkHUcawVzePnd6xp24enp5jfgdHzjRdQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "flat-cache": "^3.0.4"
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
},
- "engines": {
- "node": "^10.12.0 || >=12.0.0"
+ "peerDependencies": {
+ "eslint": "*"
}
},
- "node_modules/filelist": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
- "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
+ "node_modules/eslint-parser-plain": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-parser-plain/-/eslint-parser-plain-0.1.0.tgz",
+ "integrity": "sha512-oOeA6FWU0UJT/Rxc3XF5Cq0nbIZbylm7j8+plqq0CZoE6m4u32OXJrR+9iy4srGMmF6v6pmgvP1zPxSRIGh3sg==",
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT"
+ },
+ "node_modules/eslint-plugin-antfu": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-antfu/-/eslint-plugin-antfu-2.3.6.tgz",
+ "integrity": "sha512-31VwbU1Yd4BFNUUPQEazKyP79f3c+ohJtq5iZIuw38JjkRQdQAcF/31Kjr0DOKZXVDkeeNPrttKidrr3xhnhOA==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "minimatch": "^5.0.1"
+ "@antfu/utils": "^0.7.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "eslint": "*"
}
},
- "node_modules/filelist/node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "node_modules/eslint-plugin-command": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-command/-/eslint-plugin-command-0.2.3.tgz",
+ "integrity": "sha512-1bBYNfjZg60N2ZpLV5ATYSYyueIJ+zl5yKrTs0UFDdnyu07dNSZ7Xplnc+Wb6SXTdc1sIaoIrnuyhvztcltX6A==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "@es-joy/jsdoccomment": "^0.43.0"
},
- "engines": {
- "node": ">=10"
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "eslint": "*"
}
},
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "node_modules/eslint-plugin-es-x": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz",
+ "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==",
"dev": true,
+ "funding": [
+ "https://github.com/sponsors/ota-meshi",
+ "https://opencollective.com/eslint"
+ ],
"license": "MIT",
"dependencies": {
- "to-regex-range": "^5.0.1"
+ "@eslint-community/eslint-utils": "^4.1.2",
+ "@eslint-community/regexpp": "^4.11.0",
+ "eslint-compat-utils": "^0.5.1"
},
"engines": {
- "node": ">=8"
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=8"
}
},
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "node_modules/eslint-plugin-format": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-format/-/eslint-plugin-format-0.1.2.tgz",
+ "integrity": "sha512-ZrcO3aiumgJ6ENAv65IWkPjtW77ML/5mp0YrRK0jdvvaZJb+4kKWbaQTMr/XbJo6CtELRmCApAziEKh7L2NbdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
+ "@dprint/formatter": "^0.3.0",
+ "@dprint/markdown": "^0.17.1",
+ "@dprint/toml": "^0.6.2",
+ "eslint-formatting-reporter": "^0.0.0",
+ "eslint-parser-plain": "^0.1.0",
+ "prettier": "^3.3.2",
+ "synckit": "^0.9.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "eslint": "^8.40.0 || ^9.0.0"
}
},
- "node_modules/flat-cache": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
- "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "node_modules/eslint-plugin-import-x": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.1.1.tgz",
+ "integrity": "sha512-dBEM8fACIFNt4H7GoOaRmnH6evJW6JSTJTYYgmRd3vI4geBTjgDM/JyUDKUwIw0HDSyI+u7Vs3vFRXUo/BOAtA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.3",
- "rimraf": "^3.0.2"
+ "@typescript-eslint/typescript-estree": "^8.1.0",
+ "@typescript-eslint/utils": "^8.1.0",
+ "debug": "^4.3.4",
+ "doctrine": "^3.0.0",
+ "eslint-import-resolver-node": "^0.3.9",
+ "get-tsconfig": "^4.7.3",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.3",
+ "semver": "^7.6.3",
+ "stable-hash": "^0.0.4",
+ "tslib": "^2.6.3"
},
"engines": {
- "node": "^10.12.0 || >=12.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0"
}
},
- "node_modules/flat-cache/node_modules/rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "node_modules/eslint-plugin-jsdoc": {
+ "version": "50.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.2.2.tgz",
+ "integrity": "sha512-i0ZMWA199DG7sjxlzXn5AeYZxpRfMJjDPUl7lL9eJJX8TPRoIaxJU4ys/joP5faM5AXE1eqW/dslCj3uj4Nqpg==",
"dev": true,
- "license": "ISC",
+ "license": "BSD-3-Clause",
"dependencies": {
- "glob": "^7.1.3"
+ "@es-joy/jsdoccomment": "~0.48.0",
+ "are-docs-informative": "^0.0.2",
+ "comment-parser": "1.4.1",
+ "debug": "^4.3.6",
+ "escape-string-regexp": "^4.0.0",
+ "espree": "^10.1.0",
+ "esquery": "^1.6.0",
+ "parse-imports": "^2.1.1",
+ "semver": "^7.6.3",
+ "spdx-expression-parse": "^4.0.0",
+ "synckit": "^0.9.1"
},
- "bin": {
- "rimraf": "bin.js"
+ "engines": {
+ "node": ">=18"
},
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
}
},
- "node_modules/flatted": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
- "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
+ "node_modules/eslint-plugin-jsdoc/node_modules/@es-joy/jsdoccomment": {
+ "version": "0.48.0",
+ "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.48.0.tgz",
+ "integrity": "sha512-G6QUWIcC+KvSwXNsJyDTHvqUdNoAVJPPgkc3+Uk4WBKqZvoXhlvazOgm9aL0HwihJLQf0l+tOE2UFzXBqCqgDw==",
"dev": true,
- "license": "ISC"
+ "license": "MIT",
+ "dependencies": {
+ "comment-parser": "1.4.1",
+ "esquery": "^1.6.0",
+ "jsdoc-type-pratt-parser": "~4.1.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
},
- "node_modules/follow-redirects": {
- "version": "1.15.6",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
- "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
+ "node_modules/eslint-plugin-jsdoc/node_modules/jsdoc-type-pratt-parser": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz",
+ "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==",
"dev": true,
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
"license": "MIT",
"engines": {
- "node": ">=4.0"
- },
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
+ "node": ">=12.0.0"
}
},
- "node_modules/for-each": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
- "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "node_modules/eslint-plugin-jsonc": {
+ "version": "2.16.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.16.0.tgz",
+ "integrity": "sha512-Af/ZL5mgfb8FFNleH6KlO4/VdmDuTqmM+SPnWcdoWywTetv7kq+vQe99UyQb9XO3b0OWLVuTH7H0d/PXYCMdSg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "is-callable": "^1.1.3"
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "eslint-compat-utils": "^0.5.0",
+ "espree": "^9.6.1",
+ "graphemer": "^1.4.0",
+ "jsonc-eslint-parser": "^2.0.4",
+ "natural-compare": "^1.4.0",
+ "synckit": "^0.6.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ota-meshi"
+ },
+ "peerDependencies": {
+ "eslint": ">=6.0.0"
}
},
- "node_modules/foreground-child": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
- "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
+ "node_modules/eslint-plugin-jsonc/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.0",
- "signal-exit": "^4.0.1"
- },
+ "license": "Apache-2.0",
"engines": {
- "node": ">=14"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/foreground-child/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "node_modules/eslint-plugin-jsonc/node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
- "license": "ISC",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
"engines": {
- "node": ">=14"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "node_modules/eslint-plugin-jsonc/node_modules/synckit": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.6.2.tgz",
+ "integrity": "sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
+ "tslib": "^2.3.1"
},
"engines": {
- "node": ">= 6"
+ "node": ">=12.20"
}
},
- "node_modules/from": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
- "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==",
- "license": "MIT"
+ "node_modules/eslint-plugin-markdown": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-5.1.0.tgz",
+ "integrity": "sha512-SJeyKko1K6GwI0AN6xeCDToXDkfKZfXcexA6B+O2Wr2btUS9GrC+YgwSyVli5DJnctUHjFXcQ2cqTaAmVoLi2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdast-util-from-markdown": "^0.8.5"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=8"
+ }
},
- "node_modules/fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "node_modules/eslint-plugin-n": {
+ "version": "17.10.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.10.2.tgz",
+ "integrity": "sha512-e+s4eAf5NtJaxPhTNu3qMO0Iz40WANS93w9LQgYcvuljgvDmWi/a3rh+OrNyMHeng6aOWGJO0rCg5lH4zi8yTw==",
"dev": true,
- "license": "ISC"
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "enhanced-resolve": "^5.17.0",
+ "eslint-plugin-es-x": "^7.5.0",
+ "get-tsconfig": "^4.7.0",
+ "globals": "^15.8.0",
+ "ignore": "^5.2.4",
+ "minimatch": "^9.0.5",
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": ">=8.23.0"
+ }
},
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "node_modules/eslint-plugin-no-only-tests": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.3.0.tgz",
+ "integrity": "sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==",
"dev": true,
- "hasInstallScript": true,
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
"engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ "node": ">=5.0.0"
}
},
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "node_modules/eslint-plugin-perfectionist": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-3.3.0.tgz",
+ "integrity": "sha512-sGgShkEqDBqIZ3WlenGHwLe1cl3vHKTfeh9b1XXAamaxSC7AY4Os0jdNCXnGJW4l0TlpismT5t2r7CXY7sfKlw==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "^8.3.0",
+ "@typescript-eslint/utils": "^8.3.0",
+ "minimatch": "^10.0.1",
+ "natural-compare-lite": "^1.4.0"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "astro-eslint-parser": "^1.0.2",
+ "eslint": ">=8.0.0",
+ "svelte": ">=3.0.0",
+ "svelte-eslint-parser": "^0.41.0",
+ "vue-eslint-parser": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "astro-eslint-parser": {
+ "optional": true
+ },
+ "svelte": {
+ "optional": true
+ },
+ "svelte-eslint-parser": {
+ "optional": true
+ },
+ "vue-eslint-parser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-perfectionist/node_modules/minimatch": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
+ "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/functions-have-names": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
- "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "node_modules/eslint-plugin-regexp": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.6.0.tgz",
+ "integrity": "sha512-FCL851+kislsTEQEMioAlpDuK5+E5vs0hi1bF8cFlPlHcEjeRhuAzEsGikXRreE+0j4WhW2uO54MqTjXtYOi3A==",
+ "dev": true,
"license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.9.1",
+ "comment-parser": "^1.4.0",
+ "jsdoc-type-pratt-parser": "^4.0.0",
+ "refa": "^0.12.1",
+ "regexp-ast-analysis": "^0.7.1",
+ "scslre": "^0.3.0"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ },
+ "peerDependencies": {
+ "eslint": ">=8.44.0"
}
},
- "node_modules/futoin-hkdf": {
- "version": "1.5.3",
- "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.5.3.tgz",
- "integrity": "sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ==",
- "license": "Apache-2.0",
+ "node_modules/eslint-plugin-toml": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-toml/-/eslint-plugin-toml-0.11.1.tgz",
+ "integrity": "sha512-Y1WuMSzfZpeMIrmlP1nUh3kT8p96mThIq4NnHrYUhg10IKQgGfBZjAWnrg9fBqguiX4iFps/x/3Hb5TxBisfdw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "eslint-compat-utils": "^0.5.0",
+ "lodash": "^4.17.19",
+ "toml-eslint-parser": "^0.10.0"
+ },
"engines": {
- "node": ">=8"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ota-meshi"
+ },
+ "peerDependencies": {
+ "eslint": ">=6.0.0"
}
},
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "node_modules/eslint-plugin-unicorn": {
+ "version": "55.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-55.0.0.tgz",
+ "integrity": "sha512-n3AKiVpY2/uDcGrS3+QsYDkjPfaOrNrsfQxU9nt5nitd9KuvVXrfAvgCO9DYPSfap+Gqjw9EOrXIsBp5tlHZjA==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6.9.0"
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.24.5",
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "ci-info": "^4.0.0",
+ "clean-regexp": "^1.0.0",
+ "core-js-compat": "^3.37.0",
+ "esquery": "^1.5.0",
+ "globals": "^15.7.0",
+ "indent-string": "^4.0.0",
+ "is-builtin-module": "^3.2.1",
+ "jsesc": "^3.0.2",
+ "pluralize": "^8.0.0",
+ "read-pkg-up": "^7.0.1",
+ "regexp-tree": "^0.1.27",
+ "regjsparser": "^0.10.0",
+ "semver": "^7.6.1",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1"
+ },
+ "peerDependencies": {
+ "eslint": ">=8.56.0"
}
},
- "node_modules/get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "node_modules/eslint-plugin-unused-imports": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.3.tgz",
+ "integrity": "sha512-lqrNZIZjFMUr7P06eoKtQLwyVRibvG7N+LtfKtObYGizAAGrcqLkc3tDx+iAik2z7q0j/XI3ihjupIqxhFabFA==",
"dev": true,
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
+ "license": "MIT",
+ "peerDependencies": {
+ "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0",
+ "eslint": "^9.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/eslint-plugin": {
+ "optional": true
+ }
}
},
- "node_modules/get-intrinsic": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
- "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+ "node_modules/eslint-plugin-vue": {
+ "version": "9.27.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.27.0.tgz",
+ "integrity": "sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "has-proto": "^1.0.1",
- "has-symbols": "^1.0.3",
- "hasown": "^2.0.0"
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "globals": "^13.24.0",
+ "natural-compare": "^1.4.0",
+ "nth-check": "^2.1.1",
+ "postcss-selector-parser": "^6.0.15",
+ "semver": "^7.6.0",
+ "vue-eslint-parser": "^9.4.3",
+ "xml-name-validator": "^4.0.0"
},
"engines": {
- "node": ">= 0.4"
+ "node": "^14.17.0 || >=16.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "peerDependencies": {
+ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
}
},
- "node_modules/get-package-type": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
- "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "node_modules/eslint-plugin-vue/node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
"engines": {
- "node": ">=8.0.0"
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/get-stream": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
- "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "node_modules/eslint-plugin-vue/node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true,
- "license": "MIT",
+ "license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"
},
@@ -3704,42 +3800,74 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
+ "node_modules/eslint-plugin-yml": {
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-yml/-/eslint-plugin-yml-1.14.0.tgz",
+ "integrity": "sha512-ESUpgYPOcAYQO9czugcX5OqRvn/ydDVwGCPXY4YjPqc09rHaUVUA6IE6HLQys4rXk/S+qx3EwTd1wHCwam/OWQ==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
+ "debug": "^4.3.2",
+ "eslint-compat-utils": "^0.5.0",
+ "lodash": "^4.17.21",
+ "natural-compare": "^1.4.0",
+ "yaml-eslint-parser": "^1.2.1"
},
"engines": {
- "node": "*"
+ "node": "^14.17.0 || >=16.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "url": "https://github.com/sponsors/ota-meshi"
+ },
+ "peerDependencies": {
+ "eslint": ">=6.0.0"
}
},
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "node_modules/eslint-processor-vue-blocks": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-processor-vue-blocks/-/eslint-processor-vue-blocks-0.1.2.tgz",
+ "integrity": "sha512-PfpJ4uKHnqeL/fXUnzYkOax3aIenlwewXRX8jFinA1a2yCFnLgMuiH3xvCgvHHUlV2xJWQHbCTdiJWGwb3NqpQ==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/compiler-sfc": "^3.3.0",
+ "eslint": "^8.50.0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz",
+ "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
- "is-glob": "^4.0.3"
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
},
"engines": {
- "node": ">=10.13.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/glob/node_modules/brace-expansion": {
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
+ "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
@@ -3750,7 +3878,7 @@
"concat-map": "0.0.1"
}
},
- "node_modules/glob/node_modules/minimatch": {
+ "node_modules/eslint/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
@@ -3763,435 +3891,412 @@
"node": "*"
}
},
- "node_modules/globals": {
- "version": "13.24.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
- "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "node_modules/espree": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
+ "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
"dev": true,
- "license": "MIT",
+ "license": "BSD-2-Clause",
"dependencies": {
- "type-fest": "^0.20.2"
+ "acorn": "^8.12.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.0.0"
},
"engines": {
- "node": ">=8"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/globby": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
- "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
"dev": true,
- "license": "MIT",
+ "license": "BSD-3-Clause",
"dependencies": {
- "array-union": "^2.1.0",
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.2.9",
- "ignore": "^5.2.0",
- "merge2": "^1.4.1",
- "slash": "^3.0.0"
+ "estraverse": "^5.1.0"
},
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=0.10"
}
},
- "node_modules/gopd": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
- "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
- "license": "MIT",
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
- "get-intrinsic": "^1.1.3"
+ "estraverse": "^5.2.0"
},
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "engines": {
+ "node": ">=4.0"
}
},
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
- "license": "ISC"
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
},
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true,
- "license": "MIT"
- },
- "node_modules/has-bigints": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
- "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
"license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
+ "peer": true
},
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
- "license": "MIT",
+ "license": "BSD-2-Clause",
"engines": {
- "node": ">=8"
+ "node": ">=0.10.0"
}
},
- "node_modules/has-property-descriptors": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
- "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "node_modules/event-stream": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz",
+ "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==",
"license": "MIT",
"dependencies": {
- "es-define-property": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-proto": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
- "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "duplexer": "^0.1.1",
+ "from": "^0.1.7",
+ "map-stream": "0.0.7",
+ "pause-stream": "^0.0.11",
+ "split": "^1.0.1",
+ "stream-combiner": "^0.2.2",
+ "through": "^2.3.8"
}
},
- "node_modules/has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "node_modules/execa": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+ "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "has-symbols": "^1.0.3"
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^8.0.1",
+ "human-signals": "^5.0.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^3.0.0"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=16.17"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "function-bind": "^1.1.2"
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=8.6.0"
}
},
- "node_modules/hexy": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/hexy/-/hexy-0.3.5.tgz",
- "integrity": "sha512-UCP7TIZPXz5kxYJnNOym+9xaenxCLor/JyhKieo8y8/bJWunGh9xbhy3YrgYJUQ87WwfXGm05X330DszOfINZw==",
- "license": "MIT",
- "bin": {
- "hexy": "bin/hexy_cmd.js"
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
},
"engines": {
- "node": ">=10.4"
+ "node": ">= 6"
}
},
- "node_modules/html-escaper": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
- "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true,
"license": "MIT"
},
- "node_modules/http-parser-js": {
- "version": "0.5.8",
- "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
- "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==",
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true,
"license": "MIT"
},
- "node_modules/human-signals": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
- "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
- "dev": true,
- "license": "Apache-2.0",
+ "node_modules/fast-srp-hap": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/fast-srp-hap/-/fast-srp-hap-2.0.4.tgz",
+ "integrity": "sha512-lHRYYaaIbMrhZtsdGTwPN82UbqD9Bv8QfOlKs+Dz6YRnByZifOh93EYmf2iEWFtkOEIqR2IK8cFD0UN5wLIWBQ==",
+ "license": "MIT",
"engines": {
"node": ">=10.17.0"
}
},
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "node_modules/fastq": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+ "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
"engines": {
- "node": ">= 4"
+ "node": ">=16.0.0"
}
},
- "node_modules/import-fresh": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
- "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
+ "to-regex-range": "^5.0.1"
},
"engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=8"
}
},
- "node_modules/import-local": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
- "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"license": "MIT",
"dependencies": {
- "pkg-dir": "^4.2.0",
- "resolve-cwd": "^3.0.0"
- },
- "bin": {
- "import-local-fixture": "fixtures/cli.js"
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "node_modules/find-up-simple": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz",
+ "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=0.8.19"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
}
},
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "node_modules/flatted": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"dev": true,
"license": "ISC"
},
- "node_modules/internal-slot": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
- "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
+ "node_modules/follow-redirects": {
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+ "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
"license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "hasown": "^2.0.0",
- "side-channel": "^1.0.4"
- },
"engines": {
- "node": ">= 0.4"
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
}
},
- "node_modules/is-arguments": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
- "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
+ "node_modules/for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "is-callable": "^1.1.3"
}
},
- "node_modules/is-array-buffer": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
- "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==",
- "license": "MIT",
+ "node_modules/foreground-child": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
+ "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
+ "dev": true,
+ "license": "ISC",
"dependencies": {
- "call-bind": "^1.0.2",
- "get-intrinsic": "^1.2.1"
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^4.0.1"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=14"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/is-arrayish": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
- "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dev": true,
- "license": "MIT"
- },
- "node_modules/is-bigint": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
- "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
"license": "MIT",
"dependencies": {
- "has-bigints": "^1.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-boolean-object": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
- "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
},
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">= 6"
}
},
- "node_modules/is-callable": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
- "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "node_modules/from": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
+ "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==",
+ "license": "MIT"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/is-core-module": {
- "version": "2.15.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
- "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
- "dev": true,
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
- "dependencies": {
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-date-object": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
- "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
"license": "MIT",
- "dependencies": {
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
- "license": "MIT",
+ "node_modules/futoin-hkdf": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.5.3.tgz",
+ "integrity": "sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ==",
+ "license": "Apache-2.0",
"engines": {
- "node": ">=0.10.0"
+ "node": ">=8"
}
},
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
- "license": "MIT",
+ "license": "ISC",
"engines": {
- "node": ">=8"
+ "node": "6.* || 8.* || >= 10.*"
}
},
- "node_modules/is-generator-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
- "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "node_modules/get-func-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=6"
+ "node": "*"
}
},
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
+ "node_modules/get-intrinsic": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"license": "MIT",
"dependencies": {
- "is-extglob": "^2.1.1"
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
},
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-map": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
- "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
- "license": "MIT",
"engines": {
"node": ">= 0.4"
},
@@ -4199,105 +4304,160 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "node_modules/get-stream": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+ "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=0.12.0"
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-number-object": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
- "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "node_modules/get-tsconfig": {
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.0.tgz",
+ "integrity": "sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "has-tostringtag": "^1.0.0"
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/glob": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz",
+ "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^4.0.1",
+ "minimatch": "^10.0.0",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^2.0.0"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
},
"engines": {
- "node": ">= 0.4"
+ "node": "20 || >=22"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/is-path-inside": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
- "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
- "license": "MIT",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
"engines": {
- "node": ">=8"
+ "node": ">=10.13.0"
}
},
- "node_modules/is-regex": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
- "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
- "license": "MIT",
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
+ "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
+ "dev": true,
+ "license": "ISC",
"dependencies": {
- "call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
+ "brace-expansion": "^2.0.1"
},
"engines": {
- "node": ">= 0.4"
+ "node": "20 || >=22"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/is-set": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
- "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "node_modules/globals": {
+ "version": "15.9.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz",
+ "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==",
+ "dev": true,
"license": "MIT",
"engines": {
- "node": ">= 0.4"
+ "node": ">=18"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-shared-array-buffer": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
- "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.7"
- },
- "engines": {
- "node": ">= 0.4"
+ "get-intrinsic": "^1.1.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-string": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
- "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"license": "MIT",
"dependencies": {
- "has-tostringtag": "^1.0.0"
+ "es-define-property": "^1.0.0"
},
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+ "license": "MIT",
"engines": {
"node": ">= 0.4"
},
@@ -4305,14 +4465,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-symbol": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
- "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
- "license": "MIT",
- "dependencies": {
- "has-symbols": "^1.0.2"
- },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "license": "MIT",
"engines": {
"node": ">= 0.4"
},
@@ -4320,11 +4477,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-weakmap": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
- "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
"engines": {
"node": ">= 0.4"
},
@@ -4332,760 +4492,574 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-weakset": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
- "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.7",
- "get-intrinsic": "^1.2.4"
+ "function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
+ }
+ },
+ "node_modules/hexy": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/hexy/-/hexy-0.3.5.tgz",
+ "integrity": "sha512-UCP7TIZPXz5kxYJnNOym+9xaenxCLor/JyhKieo8y8/bJWunGh9xbhy3YrgYJUQ87WwfXGm05X330DszOfINZw==",
+ "license": "MIT",
+ "bin": {
+ "hexy": "bin/hexy_cmd.js"
},
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "engines": {
+ "node": ">=10.4"
}
},
- "node_modules/isarray": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
- "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "node_modules/hosted-git-info": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "node_modules/http-parser-js": {
+ "version": "0.5.8",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
+ "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==",
"dev": true,
- "license": "ISC"
+ "license": "MIT"
},
- "node_modules/istanbul-lib-coverage": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
- "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "node_modules/human-signals": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+ "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
"dev": true,
- "license": "BSD-3-Clause",
+ "license": "Apache-2.0",
"engines": {
- "node": ">=8"
+ "node": ">=16.17.0"
}
},
- "node_modules/istanbul-lib-instrument": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
- "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@babel/core": "^7.23.9",
- "@babel/parser": "^7.23.9",
- "@istanbuljs/schema": "^0.1.3",
- "istanbul-lib-coverage": "^3.2.0",
- "semver": "^7.5.4"
- },
+ "license": "MIT",
"engines": {
- "node": ">=10"
+ "node": ">= 4"
}
},
- "node_modules/istanbul-lib-report": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
- "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
- "license": "BSD-3-Clause",
+ "license": "MIT",
"dependencies": {
- "istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^4.0.0",
- "supports-color": "^7.1.0"
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
},
"engines": {
- "node": ">=10"
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/istanbul-lib-source-maps": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
- "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "debug": "^4.1.1",
- "istanbul-lib-coverage": "^3.0.0",
- "source-map": "^0.6.1"
- },
+ "license": "MIT",
"engines": {
- "node": ">=10"
+ "node": ">=0.8.19"
}
},
- "node_modules/istanbul-reports": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
- "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "html-escaper": "^2.0.0",
- "istanbul-lib-report": "^3.0.0"
- },
+ "license": "MIT",
"engines": {
"node": ">=8"
}
},
- "node_modules/jackspeak": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz",
- "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==",
- "dev": true,
- "license": "BlueOak-1.0.0",
+ "node_modules/internal-slot": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
+ "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
+ "license": "MIT",
"dependencies": {
- "@isaacs/cliui": "^8.0.2"
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.0",
+ "side-channel": "^1.0.4"
},
"engines": {
- "node": "20 || >=22"
- },
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-alphabetical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
+ "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
+ "dev": true,
+ "license": "MIT",
"funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/jake": {
- "version": "10.9.2",
- "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
- "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==",
+ "node_modules/is-alphanumerical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
+ "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
"dependencies": {
- "async": "^3.2.3",
- "chalk": "^4.0.2",
- "filelist": "^1.0.4",
- "minimatch": "^3.1.2"
- },
- "bin": {
- "jake": "bin/cli.js"
+ "is-alphabetical": "^1.0.0",
+ "is-decimal": "^1.0.0"
},
- "engines": {
- "node": ">=10"
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/jake/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
+ "node_modules/is-arguments": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
+ "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jake/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
+ "node_modules/is-array-buffer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
+ "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==",
+ "license": "MIT",
"dependencies": {
- "brace-expansion": "^1.1.7"
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.1"
},
"engines": {
- "node": "*"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
- "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-bigint": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
+ "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
"license": "MIT",
"dependencies": {
- "@jest/core": "^29.7.0",
- "@jest/types": "^29.6.3",
- "import-local": "^3.0.2",
- "jest-cli": "^29.7.0"
+ "has-bigints": "^1.0.1"
},
- "bin": {
- "jest": "bin/jest.js"
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
+ "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ "node": ">= 0.4"
},
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-changed-files": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz",
- "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==",
+ "node_modules/is-builtin-module": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
+ "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "execa": "^5.0.0",
- "jest-util": "^29.7.0",
- "p-limit": "^3.1.0"
+ "builtin-modules": "^3.3.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-circus": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz",
- "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==",
- "dev": true,
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
"license": "MIT",
- "dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/expect": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "co": "^4.6.0",
- "dedent": "^1.0.0",
- "is-generator-fn": "^2.0.0",
- "jest-each": "^29.7.0",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-runtime": "^29.7.0",
- "jest-snapshot": "^29.7.0",
- "jest-util": "^29.7.0",
- "p-limit": "^3.1.0",
- "pretty-format": "^29.7.0",
- "pure-rand": "^6.0.0",
- "slash": "^3.0.0",
- "stack-utils": "^2.0.3"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-cli": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz",
- "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==",
+ "node_modules/is-core-module": {
+ "version": "2.15.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
+ "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/core": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/types": "^29.6.3",
- "chalk": "^4.0.0",
- "create-jest": "^29.7.0",
- "exit": "^0.1.2",
- "import-local": "^3.0.2",
- "jest-config": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "yargs": "^17.3.1"
- },
- "bin": {
- "jest": "bin/jest.js"
+ "hasown": "^2.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ "node": ">= 0.4"
},
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-config": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
- "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
- "dev": true,
+ "node_modules/is-date-object": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+ "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
"license": "MIT",
"dependencies": {
- "@babel/core": "^7.11.6",
- "@jest/test-sequencer": "^29.7.0",
- "@jest/types": "^29.6.3",
- "babel-jest": "^29.7.0",
- "chalk": "^4.0.0",
- "ci-info": "^3.2.0",
- "deepmerge": "^4.2.2",
- "glob": "^7.1.3",
- "graceful-fs": "^4.2.9",
- "jest-circus": "^29.7.0",
- "jest-environment-node": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-regex-util": "^29.6.3",
- "jest-resolve": "^29.7.0",
- "jest-runner": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "micromatch": "^4.0.4",
- "parse-json": "^5.2.0",
- "pretty-format": "^29.7.0",
- "slash": "^3.0.0",
- "strip-json-comments": "^3.1.1"
+ "has-tostringtag": "^1.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "@types/node": "*",
- "ts-node": ">=9.0.0"
+ "node": ">= 0.4"
},
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "ts-node": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-diff": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
- "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
+ "node_modules/is-decimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
+ "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "chalk": "^4.0.0",
- "diff-sequences": "^29.6.3",
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/jest-docblock": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz",
- "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==",
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "detect-newline": "^3.0.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=0.10.0"
}
},
- "node_modules/jest-each": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz",
- "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==",
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "chalk": "^4.0.0",
- "jest-get-type": "^29.6.3",
- "jest-util": "^29.7.0",
- "pretty-format": "^29.7.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=8"
}
},
- "node_modules/jest-environment-node": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
- "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==",
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/fake-timers": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "jest-mock": "^29.7.0",
- "jest-util": "^29.7.0"
+ "is-extglob": "^2.1.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=0.10.0"
}
},
- "node_modules/jest-get-type": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
- "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
+ "node_modules/is-hexadecimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
+ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/jest-haste-map": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
- "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==",
- "dev": true,
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
"license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "@types/graceful-fs": "^4.1.3",
- "@types/node": "*",
- "anymatch": "^3.0.3",
- "fb-watchman": "^2.0.0",
- "graceful-fs": "^4.2.9",
- "jest-regex-util": "^29.6.3",
- "jest-util": "^29.7.0",
- "jest-worker": "^29.7.0",
- "micromatch": "^4.0.4",
- "walker": "^1.0.8"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
},
- "optionalDependencies": {
- "fsevents": "^2.3.2"
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-leak-detector": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
- "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==",
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=0.12.0"
}
},
- "node_modules/jest-matcher-utils": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
- "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
- "dev": true,
+ "node_modules/is-number-object": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+ "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
"license": "MIT",
"dependencies": {
- "chalk": "^4.0.0",
- "jest-diff": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
+ "has-tostringtag": "^1.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-message-util": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
- "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.12.13",
- "@jest/types": "^29.6.3",
- "@types/stack-utils": "^2.0.0",
- "chalk": "^4.0.0",
- "graceful-fs": "^4.2.9",
- "micromatch": "^4.0.4",
- "pretty-format": "^29.7.0",
- "slash": "^3.0.0",
- "stack-utils": "^2.0.3"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=8"
}
},
- "node_modules/jest-mock": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
- "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
- "dev": true,
+ "node_modules/is-regex": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+ "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
"license": "MIT",
"dependencies": {
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "jest-util": "^29.7.0"
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-pnp-resolver": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
- "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
- "dev": true,
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
"license": "MIT",
"engines": {
- "node": ">=6"
- },
- "peerDependencies": {
- "jest-resolve": "*"
+ "node": ">= 0.4"
},
- "peerDependenciesMeta": {
- "jest-resolve": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-regex-util": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
- "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
- "dev": true,
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
"license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7"
+ },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-resolve": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz",
- "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==",
+ "node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "chalk": "^4.0.0",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "jest-pnp-resolver": "^1.2.2",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "resolve": "^1.20.0",
- "resolve.exports": "^2.0.0",
- "slash": "^3.0.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-resolve-dependencies": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz",
- "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==",
- "dev": true,
+ "node_modules/is-string": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+ "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
"license": "MIT",
"dependencies": {
- "jest-regex-util": "^29.6.3",
- "jest-snapshot": "^29.7.0"
+ "has-tostringtag": "^1.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-runner": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz",
- "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==",
- "dev": true,
+ "node_modules/is-symbol": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+ "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
"license": "MIT",
"dependencies": {
- "@jest/console": "^29.7.0",
- "@jest/environment": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "emittery": "^0.13.1",
- "graceful-fs": "^4.2.9",
- "jest-docblock": "^29.7.0",
- "jest-environment-node": "^29.7.0",
- "jest-haste-map": "^29.7.0",
- "jest-leak-detector": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-resolve": "^29.7.0",
- "jest-runtime": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-watcher": "^29.7.0",
- "jest-worker": "^29.7.0",
- "p-limit": "^3.1.0",
- "source-map-support": "0.5.13"
+ "has-symbols": "^1.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-runner/node_modules/source-map-support": {
- "version": "0.5.13",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
- "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
- "dev": true,
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
"license": "MIT",
- "dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-runtime": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz",
- "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==",
- "dev": true,
+ "node_modules/is-weakset": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
+ "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
"license": "MIT",
"dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/fake-timers": "^29.7.0",
- "@jest/globals": "^29.7.0",
- "@jest/source-map": "^29.6.3",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "cjs-module-lexer": "^1.0.0",
- "collect-v8-coverage": "^1.0.0",
- "glob": "^7.1.3",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-mock": "^29.7.0",
- "jest-regex-util": "^29.6.3",
- "jest-resolve": "^29.7.0",
- "jest-snapshot": "^29.7.0",
- "jest-util": "^29.7.0",
- "slash": "^3.0.0",
- "strip-bom": "^4.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/jest-snapshot": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz",
- "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.11.6",
- "@babel/generator": "^7.7.2",
- "@babel/plugin-syntax-jsx": "^7.7.2",
- "@babel/plugin-syntax-typescript": "^7.7.2",
- "@babel/types": "^7.3.3",
- "@jest/expect-utils": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "babel-preset-current-node-syntax": "^1.0.0",
- "chalk": "^4.0.0",
- "expect": "^29.7.0",
- "graceful-fs": "^4.2.9",
- "jest-diff": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0",
- "natural-compare": "^1.4.0",
- "pretty-format": "^29.7.0",
- "semver": "^7.5.3"
+ "call-bind": "^1.0.7",
+ "get-intrinsic": "^1.2.4"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-util": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
- "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "ci-info": "^3.2.0",
- "graceful-fs": "^4.2.9",
- "picomatch": "^2.2.3"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
+ "license": "ISC"
},
- "node_modules/jest-validate": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
- "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "camelcase": "^6.2.0",
- "chalk": "^4.0.0",
- "jest-get-type": "^29.6.3",
- "leven": "^3.1.0",
- "pretty-format": "^29.7.0"
- },
+ "license": "BSD-3-Clause",
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=8"
}
},
- "node_modules/jest-validate/node_modules/camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
- "license": "MIT",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
"engines": {
"node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-watcher": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
- "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==",
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
+ "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
"dev": true,
- "license": "MIT",
+ "license": "BSD-3-Clause",
"dependencies": {
- "@jest/test-result": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "ansi-escapes": "^4.2.1",
- "chalk": "^4.0.0",
- "emittery": "^0.13.1",
- "jest-util": "^29.7.0",
- "string-length": "^4.0.1"
+ "@jridgewell/trace-mapping": "^0.3.23",
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=10"
}
},
- "node_modules/jest-worker": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
- "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+ "node_modules/istanbul-reports": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
+ "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-util": "^29.7.0",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.0.0"
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=8"
}
},
- "node_modules/jest-worker/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "node_modules/jackspeak": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz",
+ "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==",
"dev": true,
- "license": "MIT",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "has-flag": "^4.0.0"
+ "@isaacs/cliui": "^8.0.2"
},
"engines": {
- "node": ">=10"
+ "node": "20 || >=22"
},
"funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/js-tokens": {
@@ -5108,17 +5082,27 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/jsdoc-type-pratt-parser": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz",
+ "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/jsesc": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
- "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
+ "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
"dev": true,
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
- "node": ">=4"
+ "node": ">=6"
}
},
"node_modules/json-buffer": {
@@ -5149,47 +5133,64 @@
"dev": true,
"license": "MIT"
},
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "node_modules/jsonc-eslint-parser": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.0.tgz",
+ "integrity": "sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==",
"dev": true,
"license": "MIT",
- "bin": {
- "json5": "lib/cli.js"
+ "dependencies": {
+ "acorn": "^8.5.0",
+ "eslint-visitor-keys": "^3.0.0",
+ "espree": "^9.0.0",
+ "semver": "^7.3.5"
},
"engines": {
- "node": ">=6"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ota-meshi"
}
},
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "node_modules/jsonc-eslint-parser/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "json-buffer": "3.0.1"
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/kleur": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
- "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "node_modules/jsonc-eslint-parser/node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
- "license": "MIT",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
"engines": {
- "node": ">=6"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/leven": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
- "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6"
+ "dependencies": {
+ "json-buffer": "3.0.1"
}
},
"node_modules/levn": {
@@ -5223,6 +5224,23 @@
"uc.micro": "^2.0.0"
}
},
+ "node_modules/local-pkg": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz",
+ "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mlly": "^1.4.2",
+ "pkg-types": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -5239,10 +5257,10 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/lodash.memoize": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
- "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true,
"license": "MIT"
},
@@ -5253,14 +5271,30 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/long": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
+ "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/loupe": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz",
+ "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-func-name": "^2.0.1"
+ }
+ },
"node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz",
+ "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==",
"dev": true,
"license": "ISC",
- "dependencies": {
- "yallist": "^3.0.2"
+ "engines": {
+ "node": "20 || >=22"
}
},
"node_modules/lunr": {
@@ -5270,6 +5304,28 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/magic-string": {
+ "version": "0.30.11",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
+ "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/magicast": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
+ "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.25.4",
+ "@babel/types": "^7.25.4",
+ "source-map-js": "^1.2.0"
+ }
+ },
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
@@ -5293,16 +5349,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/makeerror": {
- "version": "1.0.12",
- "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
- "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "tmpl": "1.0.5"
- }
- },
"node_modules/map-stream": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz",
@@ -5327,6 +5373,35 @@
"markdown-it": "bin/markdown-it.mjs"
}
},
+ "node_modules/mdast-util-from-markdown": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz",
+ "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-string": "^2.0.0",
+ "micromark": "~2.11.0",
+ "parse-entities": "^2.0.0",
+ "unist-util-stringify-position": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz",
+ "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
@@ -5351,6 +5426,27 @@
"node": ">= 8"
}
},
+ "node_modules/micromark": {
+ "version": "2.11.4",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz",
+ "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.0.0",
+ "parse-entities": "^2.0.0"
+ }
+ },
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -5365,6 +5461,19 @@
"node": ">=8.6"
}
},
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -5389,13 +5498,26 @@
}
},
"node_modules/mimic-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
}
},
"node_modules/minimatch": {
@@ -5445,6 +5567,19 @@
"mkdirp": "bin/cmd.js"
}
},
+ "node_modules/mlly": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz",
+ "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.3",
+ "pathe": "^1.1.2",
+ "pkg-types": "^1.1.1",
+ "ufo": "^1.5.3"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -5470,6 +5605,25 @@
"integrity": "sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ==",
"license": "MIT"
},
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -5477,10 +5631,10 @@
"dev": true,
"license": "MIT"
},
- "node_modules/node-int64": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
- "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "node_modules/natural-compare-lite": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
+ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
"dev": true,
"license": "MIT"
},
@@ -5501,27 +5655,69 @@
"dev": true,
"license": "MIT"
},
- "node_modules/normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "node_modules/normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "node_modules/normalize-package-data/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver"
}
},
"node_modules/npm-run-path": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
- "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
+ "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "path-key": "^3.0.0"
+ "path-key": "^4.0.0"
},
"engines": {
- "node": ">=8"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/object-inspect": {
@@ -5579,27 +5775,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
"node_modules/onetime": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
- "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "mimic-fn": "^2.1.0"
+ "mimic-fn": "^4.0.0"
},
"engines": {
- "node": ">=6"
+ "node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -5672,6 +5858,13 @@
"dev": true,
"license": "BlueOak-1.0.0"
},
+ "node_modules/package-manager-detector": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.0.tgz",
+ "integrity": "sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -5679,10 +5872,53 @@
"dev": true,
"license": "MIT",
"dependencies": {
- "callsites": "^3.0.0"
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-entities": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
+ "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "character-entities": "^1.0.0",
+ "character-entities-legacy": "^1.0.0",
+ "character-reference-invalid": "^1.0.0",
+ "is-alphanumerical": "^1.0.0",
+ "is-decimal": "^1.0.0",
+ "is-hexadecimal": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/parse-gitignore": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/parse-gitignore/-/parse-gitignore-2.0.0.tgz",
+ "integrity": "sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/parse-imports": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.1.tgz",
+ "integrity": "sha512-TDT4HqzUiTMO1wJRwg/t/hYk8Wdp3iF/ToMIlAoVQfL1Xs/sTxq1dKWSMjMbQmIarfWKymOyly40+zmPHXMqCA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "es-module-lexer": "^1.5.3",
+ "slashes": "^3.0.12"
},
"engines": {
- "node": ">=6"
+ "node": ">= 18"
}
},
"node_modules/parse-json": {
@@ -5714,16 +5950,6 @@
"node": ">=8"
}
},
- "node_modules/path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -5758,24 +5984,21 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/path-scurry/node_modules/lru-cache": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz",
- "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==",
+ "node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
"dev": true,
- "license": "ISC",
- "engines": {
- "node": "20 || >=22"
- }
+ "license": "MIT"
},
- "node_modules/path-type": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
- "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "node_modules/pathval": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
+ "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">= 14.16"
}
},
"node_modules/pause-stream": {
@@ -5798,119 +6021,105 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=8.6"
+ "node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/pirates": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
- "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/pkg-dir": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
- "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "node_modules/pkg-types": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.0.tgz",
+ "integrity": "sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "find-up": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
+ "confbox": "^0.1.7",
+ "mlly": "^1.7.1",
+ "pathe": "^1.1.2"
}
},
- "node_modules/pkg-dir/node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "node_modules/plist": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
+ "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
+ "@xmldom/xmldom": "^0.8.8",
+ "base64-js": "^1.5.1",
+ "xmlbuilder": "^15.1.1"
},
"engines": {
- "node": ">=8"
+ "node": ">=10.4.0"
}
},
- "node_modules/pkg-dir/node_modules/locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "p-locate": "^4.1.0"
- },
"engines": {
- "node": ">=8"
+ "node": ">=4"
}
},
- "node_modules/pkg-dir/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
+ "node_modules/possible-typed-array-names": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
+ "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
"license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
"engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">= 0.4"
}
},
- "node_modules/pkg-dir/node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "node_modules/postcss": {
+ "version": "8.4.41",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
+ "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
"dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"license": "MIT",
"dependencies": {
- "p-limit": "^2.2.0"
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.1",
+ "source-map-js": "^1.2.0"
},
"engines": {
- "node": ">=8"
+ "node": "^10 || ^12 || >=14"
}
},
- "node_modules/plist": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
- "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@xmldom/xmldom": "^0.8.8",
- "base64-js": "^1.5.1",
- "xmlbuilder": "^15.1.1"
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
},
"engines": {
- "node": ">=10.4.0"
- }
- },
- "node_modules/possible-typed-array-names": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
- "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
+ "node": ">=4"
}
},
"node_modules/prelude-ls": {
@@ -5923,46 +6132,33 @@
"node": ">= 0.8.0"
}
},
- "node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "node_modules/prettier": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
+ "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "bin": {
+ "prettier": "bin/prettier.cjs"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
+ "node": ">=14"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/prettier/prettier?sponsor=1"
}
},
- "node_modules/prompts": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
- "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "kleur": "^3.0.3",
- "sisteransi": "^1.0.5"
+ "fast-diff": "^1.1.2"
},
"engines": {
- "node": ">= 6"
+ "node": ">=6.0.0"
}
},
"node_modules/proxy-from-env": {
@@ -5992,23 +6188,6 @@
"node": ">=6"
}
},
- "node_modules/pure-rand": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
- "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
- "dev": true,
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/dubzzz"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fast-check"
- }
- ],
- "license": "MIT"
- },
"node_modules/q": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz",
@@ -6041,12 +6220,142 @@
],
"license": "MIT"
},
- "node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "node_modules/read-pkg": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
+ "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "@types/normalize-package-data": "^2.4.0",
+ "normalize-package-data": "^2.5.0",
+ "parse-json": "^5.0.0",
+ "type-fest": "^0.6.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
+ "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.1.0",
+ "read-pkg": "^5.2.0",
+ "type-fest": "^0.8.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg/node_modules/type-fest": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
+ "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/refa": {
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz",
+ "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.8.0"
+ },
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/regexp-ast-analysis": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz",
+ "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.8.0",
+ "refa": "^0.12.1"
+ },
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/regexp-tree": {
+ "version": "0.1.27",
+ "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
+ "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "regexp-tree": "bin/regexp-tree"
+ }
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.2",
@@ -6062,8 +6371,30 @@
"engines": {
"node": ">= 0.4"
},
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regjsparser": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz",
+ "integrity": "sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "jsesc": "~0.5.0"
+ },
+ "bin": {
+ "regjsparser": "bin/parser"
+ }
+ },
+ "node_modules/regjsparser/node_modules/jsesc": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
}
},
"node_modules/require-directory": {
@@ -6094,29 +6425,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/resolve-cwd": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
- "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "resolve-from": "^5.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/resolve-cwd/node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -6127,14 +6435,14 @@
"node": ">=4"
}
},
- "node_modules/resolve.exports": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz",
- "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==",
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=10"
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/reusify": {
@@ -6168,44 +6476,40 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/rimraf/node_modules/glob": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz",
- "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==",
+ "node_modules/rollup": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz",
+ "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^4.0.1",
- "minimatch": "^10.0.0",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^2.0.0"
+ "@types/estree": "1.0.5"
},
"bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/rimraf/node_modules/minimatch": {
- "version": "10.0.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
- "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
+ "rollup": "dist/bin/rollup"
},
"engines": {
- "node": "20 || >=22"
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.21.2",
+ "@rollup/rollup-android-arm64": "4.21.2",
+ "@rollup/rollup-darwin-arm64": "4.21.2",
+ "@rollup/rollup-darwin-x64": "4.21.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.21.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.21.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.21.2",
+ "@rollup/rollup-linux-arm64-musl": "4.21.2",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.21.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.21.2",
+ "@rollup/rollup-linux-x64-gnu": "4.21.2",
+ "@rollup/rollup-linux-x64-musl": "4.21.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.21.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.21.2",
+ "@rollup/rollup-win32-x64-msvc": "4.21.2",
+ "fsevents": "~2.3.2"
}
},
"node_modules/run-parallel": {
@@ -6258,6 +6562,21 @@
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"license": "ISC"
},
+ "node_modules/scslre": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz",
+ "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.8.0",
+ "refa": "^0.12.0",
+ "regexp-ast-analysis": "^0.7.0"
+ },
+ "engines": {
+ "node": "^14.0.0 || >=16.0.0"
+ }
+ },
"node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
@@ -6327,13 +6646,13 @@
}
},
"node_modules/shiki": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.14.1.tgz",
- "integrity": "sha512-FujAN40NEejeXdzPt+3sZ3F2dx1U24BY2XTY01+MG8mbxCiA2XukXdcbyMyLAHJ/1AUUnQd1tZlvIjefWWEJeA==",
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.15.1.tgz",
+ "integrity": "sha512-QPtVwbafyHmH9Z90iEZgZL4BhqFh5RMnRq2Bic0Cqp5lgbpbkn4nNmed0zzXbh/yPFs2PpkCviM9qcrbN+9zAA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@shikijs/core": "1.14.1",
+ "@shikijs/core": "1.15.1",
"@types/hast": "^3.0.4"
}
},
@@ -6355,13 +6674,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
"dev": true,
"license": "ISC"
},
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/simple-plist": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.4.0.tgz",
@@ -6381,15 +6713,12 @@
"dev": true,
"license": "MIT"
},
- "node_modules/slash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
- "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "node_modules/slashes": {
+ "version": "3.0.12",
+ "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz",
+ "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
+ "license": "ISC"
},
"node_modules/source-map": {
"version": "0.6.1",
@@ -6400,6 +6729,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/source-map-js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -6410,6 +6749,53 @@
"source-map": "^0.6.0"
}
},
+ "node_modules/spdx-correct": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
+ "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-correct/node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-exceptions": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
+ "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
+ "dev": true,
+ "license": "CC-BY-3.0"
+ },
+ "node_modules/spdx-expression-parse": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz",
+ "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-license-ids": {
+ "version": "3.0.20",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz",
+ "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
"node_modules/split": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
@@ -6422,35 +6808,26 @@
"node": "*"
}
},
- "node_modules/sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "node_modules/stable-hash": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz",
+ "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "MIT"
},
- "node_modules/stack-utils": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
- "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "escape-string-regexp": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- }
+ "license": "MIT"
},
- "node_modules/stack-utils/node_modules/escape-string-regexp": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
- "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "node_modules/std-env": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
+ "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
+ "license": "MIT"
},
"node_modules/stop-iteration-iterator": {
"version": "1.0.0",
@@ -6484,21 +6861,26 @@
"through": "~2.3.4"
}
},
- "node_modules/string-length": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
- "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "char-regex": "^1.0.2",
- "strip-ansi": "^6.0.0"
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/string-width": {
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
@@ -6513,20 +6895,40 @@
"node": ">=8"
}
},
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
+ "ansi-regex": "^6.0.1"
},
"engines": {
- "node": ">=8"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi": {
@@ -6556,24 +6958,30 @@
"node": ">=8"
}
},
- "node_modules/strip-bom": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
- "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/strip-final-newline": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
- "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
"engines": {
- "node": ">=6"
+ "node": ">=8"
}
},
"node_modules/strip-json-comments": {
@@ -6615,43 +7023,107 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/synckit": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz",
+ "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.1.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unts"
+ }
+ },
+ "node_modules/tapable": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/test-exclude": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
- "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz",
+ "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==",
"dev": true,
"license": "ISC",
"dependencies": {
"@istanbuljs/schema": "^0.1.2",
- "glob": "^7.1.4",
- "minimatch": "^3.0.4"
+ "glob": "^10.4.1",
+ "minimatch": "^9.0.4"
},
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/test-exclude/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "node_modules/test-exclude/node_modules/glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
- "license": "MIT",
+ "license": "ISC",
"dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/test-exclude/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "node_modules/test-exclude/node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/test-exclude/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/test-exclude/node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
- "license": "ISC",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^1.1.7"
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
- "node": "*"
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/text-table": {
@@ -6673,12 +7145,49 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"license": "MIT"
},
- "node_modules/tmpl": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
- "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz",
+ "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinypool": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz",
+ "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
+ "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz",
+ "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
},
"node_modules/to-fast-properties": {
"version": "2.0.0",
@@ -6703,66 +7212,46 @@
"node": ">=8.0"
}
},
- "node_modules/ts-api-utils": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
- "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
+ "node_modules/toml-eslint-parser": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/toml-eslint-parser/-/toml-eslint-parser-0.10.0.tgz",
+ "integrity": "sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.0.0"
+ },
"engines": {
- "node": ">=16"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
- "peerDependencies": {
- "typescript": ">=4.2.0"
+ "funding": {
+ "url": "https://github.com/sponsors/ota-meshi"
}
},
- "node_modules/ts-jest": {
- "version": "29.2.5",
- "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz",
- "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==",
+ "node_modules/toml-eslint-parser/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "bs-logger": "^0.2.6",
- "ejs": "^3.1.10",
- "fast-json-stable-stringify": "^2.1.0",
- "jest-util": "^29.0.0",
- "json5": "^2.2.3",
- "lodash.memoize": "^4.1.2",
- "make-error": "^1.3.6",
- "semver": "^7.6.3",
- "yargs-parser": "^21.1.1"
- },
- "bin": {
- "ts-jest": "cli.js"
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
+ "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
+ "dev": true,
+ "license": "MIT",
"engines": {
- "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
+ "node": ">=16"
},
"peerDependencies": {
- "@babel/core": ">=7.0.0-beta.0 <8",
- "@jest/transform": "^29.0.0",
- "@jest/types": "^29.0.0",
- "babel-jest": "^29.0.0",
- "jest": "^29.0.0",
- "typescript": ">=4.3 <6"
- },
- "peerDependenciesMeta": {
- "@babel/core": {
- "optional": true
- },
- "@jest/transform": {
- "optional": true
- },
- "@jest/types": {
- "optional": true
- },
- "babel-jest": {
- "optional": true
- },
- "esbuild": {
- "optional": true
- }
+ "typescript": ">=4.2.0"
}
},
"node_modules/ts-node": {
@@ -6834,27 +7323,14 @@
"node": ">= 0.8.0"
}
},
- "node_modules/type-detect": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
- "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
"dev": true,
"license": "(MIT OR CC0-1.0)",
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=8"
}
},
"node_modules/typedoc": {
@@ -6871,114 +7347,360 @@
"yaml": "^2.4.5"
},
"bin": {
- "typedoc": "bin/typedoc"
+ "typedoc": "bin/typedoc"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
+ "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uc.micro": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ufo": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
+ "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
+ "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
+ "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.1.2",
+ "picocolors": "^1.0.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz",
+ "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.41",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz",
+ "integrity": "sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.3.5",
+ "pathe": "^1.1.2",
+ "tinyrainbow": "^1.2.0",
+ "vite": "^5.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.5.tgz",
+ "integrity": "sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.3.0",
+ "@vitest/expect": "2.0.5",
+ "@vitest/pretty-format": "^2.0.5",
+ "@vitest/runner": "2.0.5",
+ "@vitest/snapshot": "2.0.5",
+ "@vitest/spy": "2.0.5",
+ "@vitest/utils": "2.0.5",
+ "chai": "^5.1.1",
+ "debug": "^4.3.5",
+ "execa": "^8.0.1",
+ "magic-string": "^0.30.10",
+ "pathe": "^1.1.2",
+ "std-env": "^3.7.0",
+ "tinybench": "^2.8.0",
+ "tinypool": "^1.0.0",
+ "tinyrainbow": "^1.2.0",
+ "vite": "^5.0.0",
+ "vite-node": "2.0.5",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
},
"engines": {
- "node": ">= 18"
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x"
- }
- },
- "node_modules/typescript": {
- "version": "5.5.4",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
- "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
+ "@edge-runtime/vm": "*",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "@vitest/browser": "2.0.5",
+ "@vitest/ui": "2.0.5",
+ "happy-dom": "*",
+ "jsdom": "*"
},
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/uc.micro": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
- "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/undici-types": {
- "version": "6.19.8",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
- "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/update-browserslist-db": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
- "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
},
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
+ "@types/node": {
+ "optional": true
},
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
}
- ],
+ }
+ },
+ "node_modules/vue-eslint-parser": {
+ "version": "9.4.3",
+ "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
+ "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "escalade": "^3.1.2",
- "picocolors": "^1.0.1"
+ "debug": "^4.3.4",
+ "eslint-scope": "^7.1.1",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.3.1",
+ "esquery": "^1.4.0",
+ "lodash": "^4.17.21",
+ "semver": "^7.3.6"
},
- "bin": {
- "update-browserslist-db": "cli.js"
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
},
"peerDependencies": {
- "browserslist": ">= 4.21.0"
+ "eslint": ">=6.0.0"
}
},
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "node_modules/vue-eslint-parser/node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
- "punycode": "^2.1.0"
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/v8-compile-cache-lib": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
- "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/v8-to-istanbul": {
- "version": "9.3.0",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
- "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+ "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.12",
- "@types/istanbul-lib-coverage": "^2.0.1",
- "convert-source-map": "^2.0.0"
- },
+ "license": "Apache-2.0",
"engines": {
- "node": ">=10.12.0"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/walker": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
- "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "node_modules/vue-eslint-parser/node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
- "license": "Apache-2.0",
+ "license": "BSD-2-Clause",
"dependencies": {
- "makeerror": "1.0.12"
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
"node_modules/which": {
@@ -7050,6 +7772,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -7061,18 +7800,18 @@
}
},
"node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
@@ -7097,25 +7836,78 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
- "license": "ISC"
+ "license": "MIT"
},
- "node_modules/write-file-atomic": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
- "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "imurmurhash": "^0.1.4",
- "signal-exit": "^3.0.7"
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
},
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "dev": true,
+ "license": "Apache-2.0",
"engines": {
- "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ "node": ">=12"
}
},
"node_modules/xml2js": {
@@ -7160,13 +7952,6 @@
"node": ">=10"
}
},
- "node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/yaml": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
@@ -7180,6 +7965,37 @@
"node": ">= 14"
}
},
+ "node_modules/yaml-eslint-parser": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.2.3.tgz",
+ "integrity": "sha512-4wZWvE398hCP7O8n3nXKu/vdq1HcH01ixYlCREaJL5NUMwQ0g3MaGFUBNSlmBtKmhbtVG/Cm6lyYmSVTEVil8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.0.0",
+ "lodash": "^4.17.21",
+ "yaml": "^2.0.0"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ota-meshi"
+ }
+ },
+ "node_modules/yaml-eslint-parser/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
@@ -7209,6 +8025,28 @@
"node": ">=12"
}
},
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
diff --git a/package.json b/package.json
index 387879c56..1ae9163c4 100644
--- a/package.json
+++ b/package.json
@@ -1,27 +1,17 @@
{
"name": "hap-nodejs",
+ "type": "module",
"version": "1.1.0",
"description": "HAP-NodeJS is a Node.js implementation of HomeKit Accessory Server.",
- "main": "dist/index.js",
- "types": "dist/index.d.ts",
- "maintainers": [
- "Andreas Bauer
"
- ],
"author": "Khaos Tian (https://tz.is/)",
- "homepage": "https://github.com/homebridge/HAP-NodeJS",
"license": "Apache-2.0",
- "scripts": {
- "check": "npm install && npm outdated",
- "clean": "rimraf dist && rimraf coverage",
- "lint": "eslint 'src/**/*.{js,ts,json}'",
- "build": "rimraf dist && tsc && node .github/node-persist-ignore.js",
- "prepublishOnly": "npm run build",
- "postpublish": "npm run clean",
- "test": "jest",
- "test-coverage": "jest --coverage",
- "start": "node dist/BridgedCore.js",
- "docs": "typedoc",
- "lint-docs": "typedoc --emit none --treatWarningsAsErrors"
+ "homepage": "https://github.com/homebridge/HAP-NodeJS",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/homebridge/HAP-NodeJS.git"
+ },
+ "bugs": {
+ "url": "https://github.com/homebridge/HAP-NodeJS/issues"
},
"keywords": [
"hap-nodejs",
@@ -36,22 +26,34 @@
"homekit-support",
"siri"
],
- "repository": {
- "type": "git",
- "url": "git+https://github.com/homebridge/HAP-NodeJS.git"
- },
- "bugs": {
- "url": "https://github.com/homebridge/HAP-NodeJS/issues"
- },
- "engines": {
- "node": "^18.4.0 || ^20 || ^22"
- },
+ "exports": "./dist/index.js",
+ "types": "dist/index.d.ts",
+ "maintainers": [
+ "Andreas Bauer "
+ ],
"files": [
- "README.md",
+ "@types",
"LICENSE",
- "dist",
- "@types"
+ "README.md",
+ "dist"
],
+ "engines": {
+ "node": "^18.4.0 || ^20 || ^22"
+ },
+ "scripts": {
+ "build": "rimraf dist && tsc && node .github/node-persist-ignore.js",
+ "check": "npm install && npm outdated",
+ "clean": "rimraf dist && rimraf coverage",
+ "docs": "typedoc",
+ "generate-types": "node --no-warnings=ExperimentalWarning --loader ts-node/esm src/lib/definitions/generate-definitions.ts",
+ "lint": "eslint .",
+ "lint:fix": "npm run lint -- --fix",
+ "lint-docs": "typedoc --emit none --treatWarningsAsErrors",
+ "prepublishOnly": "npm run build",
+ "postpublish": "npm run clean",
+ "test": "vitest run",
+ "test-coverage": "npm run test -- --coverage"
+ },
"dependencies": {
"@homebridge/ciao": "^1.3.1",
"@homebridge/dbus-native": "^0.6.0",
@@ -59,33 +61,34 @@
"debug": "^4.3.6",
"fast-srp-hap": "^2.0.4",
"futoin-hkdf": "^1.5.3",
+ "long": "^5.2.3",
"node-persist": "^0.0.12",
"source-map-support": "^0.5.21",
- "tslib": "^2.6.3",
- "tweetnacl": "^1.0.3"
+ "tweetnacl": "^1.0.3",
+ "xml2js": "^0.6.2"
},
"devDependencies": {
+ "@antfu/eslint-config": "^3.0.0",
"@types/debug": "^4.1.12",
"@types/escape-html": "^1.0.4",
- "@types/jest": "^29.5.12",
- "@types/node": "^22.5.0",
+ "@types/node": "^22.5.1",
"@types/plist": "^3.0.5",
"@types/semver": "^7.3.7",
"@types/source-map-support": "^0.5.10",
- "@typescript-eslint/eslint-plugin": "^8.2.0",
- "@typescript-eslint/parser": "^8.2.0",
- "axios": "^1.7.5",
+ "@types/xml2js": "^0.4.14",
+ "@vitest/coverage-v8": "^2.0.5",
+ "axios": "^1.7.6",
"commander": "^12.1.0",
"escape-html": "^1.0.3",
- "eslint": "^8.57.0",
+ "eslint": "^9.9.1",
+ "eslint-plugin-format": "^0.1.2",
"http-parser-js": "^0.5.8",
- "jest": "^29.7.0",
"rimraf": "^6.0.1",
"semver": "^7.6.3",
"simple-plist": "^1.4.0",
- "ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typedoc": "^0.26.6",
- "typescript": "^5.5.4"
+ "typescript": "^5.5.4",
+ "vitest": "^2.0.5"
}
}
diff --git a/src/accessories/AirConditioner_accessory.ts b/src/accessories/AirConditioner_accessory.ts
index 6ccb53c7c..fb6c139f1 100644
--- a/src/accessories/AirConditioner_accessory.ts
+++ b/src/accessories/AirConditioner_accessory.ts
@@ -1,22 +1,22 @@
+/* eslint-disable no-console */
// In This example we create an air conditioner Accessory that Has a Thermostat linked to a Fan Service.
// For example, I've also put a Light Service that should be hidden to represent a light in the closet that is part of the AC.
// It is to show how to hide services.
// The linking and Hiding does NOT appear to be reflected in Home
// here's a fake hardware device that we'll expose to HomeKit
+import type { CharacteristicGetCallback, CharacteristicSetCallback, CharacteristicValue } from '../index.js'
+import type { VoidCallback } from '../types'
+
import {
Accessory,
AccessoryEventTypes,
Categories,
Characteristic,
CharacteristicEventTypes,
- CharacteristicGetCallback,
- CharacteristicSetCallback,
- CharacteristicValue,
Service,
uuid,
-} from "..";
-import { VoidCallback } from "../types";
+} from '../index.js'
const ACTest_data: Record = {
fanPowerOn: false,
@@ -27,133 +27,130 @@ const ACTest_data: Record = {
TargetTemperature: 32,
TemperatureDisplayUnits: 1,
LightOn: false,
-};
+}
// This is the Accessory that we'll return to HAP-NodeJS that represents our fake fan.
-const ACTest = exports.accessory = new Accessory("Air Conditioner", uuid.generate("hap-nodejs:accessories:airconditioner"));
+const ACTest = exports.accessory = new Accessory('Air Conditioner', uuid.generate('hap-nodejs:accessories:airconditioner'))
// Add properties for publishing (in case we're using Core.js and not BridgedCore.js)
// @ts-expect-error: Core/BridgeCore API
-ACTest.username = "1A:2B:3C:4D:5E:FF";
+ACTest.username = '1A:2B:3C:4D:5E:FF'
// @ts-expect-error: Core/BridgeCore API
-ACTest.pincode = "031-45-154";
-ACTest.category = Categories.THERMOSTAT;
+ACTest.pincode = '031-45-154'
+ACTest.category = Categories.THERMOSTAT
// set some basic properties (these values are arbitrary and setting them is optional)
ACTest
.getService(Service.AccessoryInformation)!
- .setCharacteristic(Characteristic.Manufacturer, "Sample Company");
+ .setCharacteristic(Characteristic.Manufacturer, 'Sample Company')
// listen for the "identify" event for this Accessory
ACTest.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => {
- console.log("Fan Identified!");
- callback(); // success
-});
+ console.log('Fan Identified!')
+ callback() // success
+})
// Add the actual Fan Service and listen for change events from iOS.
-const FanService = ACTest.addService(Service.Fan, "Blower"); // services exposed to the user should have "names" like "Fake Light" for us
+const FanService = ACTest.addService(Service.Fan, 'Blower') // services exposed to the user should have "names" like "Fake Light" for us
FanService.getCharacteristic(Characteristic.On)!
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- console.log("Fan Power Changed To "+value);
- ACTest_data.fanPowerOn=value;
- callback(); // Our fake Fan is synchronous - this value has been successfully set
- });
+ console.log(`Fan Power Changed To ${value}`)
+ ACTest_data.fanPowerOn = value
+ callback() // Our fake Fan is synchronous - this value has been successfully set
+ })
// We want to intercept requests for our current power state so we can query the hardware itself instead of
// allowing HAP-NodeJS to return the cached Characteristic.value.
FanService.getCharacteristic(Characteristic.On)!
.on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
-
// this event is emitted when you ask Siri directly whether your fan is on or not. you might query
// the fan hardware itself to find this out, then call the callback. But if you take longer than a
// few seconds to respond, Siri will give up.
- const err = null; // in case there were any problems
+ const err = null // in case there were any problems
if (ACTest_data.fanPowerOn) {
- callback(err, true);
+ callback(err, true)
} else {
- callback(err, false);
+ callback(err, false)
}
- });
-
+ })
// also add an "optional" Characteristic for speed
FanService.addCharacteristic(Characteristic.RotationSpeed)
.on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
- callback(null, ACTest_data.rSpeed);
+ callback(null, ACTest_data.rSpeed)
})
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- console.log("Setting fan rSpeed to %s", value);
- ACTest_data.rSpeed=value;
- callback();
- });
+ console.log('Setting fan rSpeed to %s', value)
+ ACTest_data.rSpeed = value
+ callback()
+ })
-const ThermostatService = ACTest.addService(Service.Thermostat, "Thermostat");
-ThermostatService.addLinkedService(FanService);
-ThermostatService.setPrimaryService();
+const ThermostatService = ACTest.addService(Service.Thermostat, 'Thermostat')
+ThermostatService.addLinkedService(FanService)
+ThermostatService.setPrimaryService()
ThermostatService.getCharacteristic(Characteristic.CurrentHeatingCoolingState)!
.on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
- callback(null, ACTest_data.CurrentHeatingCoolingState);
- })
- .on(CharacteristicEventTypes.SET,(value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- ACTest_data.CurrentHeatingCoolingState=value;
- console.log( "Characteristic CurrentHeatingCoolingState changed to %s",value);
- callback();
- });
-
- ThermostatService.getCharacteristic(Characteristic.TargetHeatingCoolingState)!
- .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
- callback(null, ACTest_data.TargetHeatingCoolingState);
- })
- .on(CharacteristicEventTypes.SET,(value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- ACTest_data.TargetHeatingCoolingState=value;
- console.log( "Characteristic TargetHeatingCoolingState changed to %s",value);
- callback();
- });
-
- ThermostatService.getCharacteristic(Characteristic.CurrentTemperature)!
- .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
- callback(null, ACTest_data.CurrentTemperature);
- })
- .on(CharacteristicEventTypes.SET,(value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- ACTest_data.CurrentTemperature=value;
- console.log( "Characteristic CurrentTemperature changed to %s",value);
- callback();
- });
-
- ThermostatService.getCharacteristic(Characteristic.TargetTemperature)!
- .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
- callback(null, ACTest_data.TargetTemperature);
- })
- .on(CharacteristicEventTypes.SET,(value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- ACTest_data.TargetTemperature=value;
- console.log( "Characteristic TargetTemperature changed to %s",value);
- callback();
- });
-
- ThermostatService.getCharacteristic(Characteristic.TemperatureDisplayUnits)!
- .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
- callback(null, ACTest_data.TemperatureDisplayUnits);
- })
- .on(CharacteristicEventTypes.SET,(value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- ACTest_data.TemperatureDisplayUnits=value;
- console.log( "Characteristic TemperatureDisplayUnits changed to %s",value);
- callback();
- });
-
-
-const LightService = ACTest.addService(Service.Lightbulb, "AC Light");
+ callback(null, ACTest_data.CurrentHeatingCoolingState)
+ })
+ .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
+ ACTest_data.CurrentHeatingCoolingState = value
+ console.log('Characteristic CurrentHeatingCoolingState changed to %s', value)
+ callback()
+ })
+
+ThermostatService.getCharacteristic(Characteristic.TargetHeatingCoolingState)!
+ .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
+ callback(null, ACTest_data.TargetHeatingCoolingState)
+ })
+ .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
+ ACTest_data.TargetHeatingCoolingState = value
+ console.log('Characteristic TargetHeatingCoolingState changed to %s', value)
+ callback()
+ })
+
+ThermostatService.getCharacteristic(Characteristic.CurrentTemperature)!
+ .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
+ callback(null, ACTest_data.CurrentTemperature)
+ })
+ .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
+ ACTest_data.CurrentTemperature = value
+ console.log('Characteristic CurrentTemperature changed to %s', value)
+ callback()
+ })
+
+ThermostatService.getCharacteristic(Characteristic.TargetTemperature)!
+ .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
+ callback(null, ACTest_data.TargetTemperature)
+ })
+ .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
+ ACTest_data.TargetTemperature = value
+ console.log('Characteristic TargetTemperature changed to %s', value)
+ callback()
+ })
+
+ThermostatService.getCharacteristic(Characteristic.TemperatureDisplayUnits)!
+ .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
+ callback(null, ACTest_data.TemperatureDisplayUnits)
+ })
+ .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
+ ACTest_data.TemperatureDisplayUnits = value
+ console.log('Characteristic TemperatureDisplayUnits changed to %s', value)
+ callback()
+ })
+
+const LightService = ACTest.addService(Service.Lightbulb, 'AC Light')
LightService.getCharacteristic(Characteristic.On)!
.on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
- callback(null, ACTest_data.LightOn);
+ callback(null, ACTest_data.LightOn)
})
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- ACTest_data.LightOn=value;
- console.log( "Characteristic Light On changed to %s",value);
- callback();
- });
-LightService.setHiddenService();
+ ACTest_data.LightOn = value
+ console.log('Characteristic Light On changed to %s', value)
+ callback()
+ })
+LightService.setHiddenService()
diff --git a/src/accessories/AppleTVRemote_accessory.ts b/src/accessories/AppleTVRemote_accessory.ts
index 2014ae4b4..ac174a908 100644
--- a/src/accessories/AppleTVRemote_accessory.ts
+++ b/src/accessories/AppleTVRemote_accessory.ts
@@ -1,167 +1,162 @@
-import escapeHTML from "escape-html";
-import { Accessory, ButtonState, ButtonType, Categories, RemoteController, uuid } from "..";
-import * as http from "http";
-import url, { UrlWithParsedQuery } from "url";
-import { GStreamerAudioProducer, GStreamerOptions } from "./gstreamer-audioProducer";
+import type { GStreamerOptions } from './gstreamer-audioProducer'
-const remoteUUID = uuid.generate("hap-nodejs:accessories:remote");
-const remote = exports.accessory = new Accessory("Remote", remoteUUID);
+import { createServer } from 'node:http'
+
+import escapeHTML from 'escape-html'
+
+import { Accessory, ButtonState, ButtonType, Categories, RemoteController, uuid } from '../index.js'
+import { GStreamerAudioProducer } from './gstreamer-audioProducer.js'
+
+const remoteUUID = uuid.generate('hap-nodejs:accessories:remote')
+const remote = exports.accessory = new Accessory('Remote', remoteUUID)
// @ts-expect-error: Core/BridgeCore API
-remote.username = "DB:AF:E0:5C:69:76";
+remote.username = 'DB:AF:E0:5C:69:76'
// @ts-expect-error: Core/BridgeCore API
-remote.pincode = "874-23-897";
-remote.category = Categories.TARGET_CONTROLLER;
+remote.pincode = '874-23-897'
+remote.category = Categories.TARGET_CONTROLLER
// ----------------- for siri support -----------------
// CHANGE this to enable siri support. Read docs in 'gstreamer-audioProducer.ts' for necessary package dependencies
-const siriSupport = false;
+const siriSupport = false
const gstreamerOptions: Partial = { // any configuration regarding the producer can be made here
-};
+}
// ----------------------------------------------------
const controller = siriSupport
? new RemoteController(GStreamerAudioProducer, gstreamerOptions)
- : new RemoteController();
-remote.configureController(controller);
+ : new RemoteController()
+remote.configureController(controller)
/*
- This example plugin exposes an simple http api to interact with the remote and play around.
+ This example plugin exposes a simple http api to interact with the remote and play around.
The supported routes are listed below. The http server runs on port 8080 as default.
This example should not be used except for testing as the http server is unsecured.
/listTargets - list all currently configured apple tvs and their respective configuration
/getActiveTarget - return the current target id of the controlled device
- /getActive - get the value of the active characteristic (active means the apple tv for the activeTarget is listening)
+ /getActive - get the value of the active characteristic (active means the Apple TV for the activeTarget is listening)
/press?button=&time= - presses a given button for a given time. Time is optional and defaults to 200ms
/button?button=&state= - send a single button event
- /getTargetId?name= - get the target identifier for the given name of the apple tv
- /setActiveTarget?identifier= - set currently controlled apple tv
+ /getTargetId?name= - get the target identifier for the given name of the Apple TV
+ /setActiveTarget?identifier= - set currently controlled Apple TV
*/
-http.createServer((request, response) => {
- if (request.method !== "GET") {
- response.writeHead(405, { "Content-Type": "text/html" });
- response.end("Method Not Allowed");
- return;
+createServer((request, response) => {
+ if (request.method !== 'GET') {
+ response.writeHead(405, { 'Content-Type': 'text/html' })
+ response.end('Method Not Allowed')
+ return
}
- const parsedPath: UrlWithParsedQuery = url.parse(request.url!, true);
- const pathname = parsedPath.pathname!.substring(1, parsedPath.pathname!.length);
- const query = parsedPath.query;
+ const parsedUrl = new URL(request.url!, `http://${request.headers.host}`)
+ const pathname = parsedUrl.pathname.substring(1)
+ const query = Object.fromEntries(parsedUrl.searchParams.entries())
- if (pathname === "setActiveTarget") {
+ if (pathname === 'setActiveTarget') {
if (query === undefined || query.identifier === undefined) {
- response.writeHead(400, { "Content-Type": "text/html" });
- response.end("Bad request. Must include 'identifier' in query string!");
- return;
+ response.writeHead(400, { 'Content-Type': 'text/html' })
+ response.end('Bad request. Must include \'identifier\' in query string!')
+ return
}
- const targetIdentifier = parseInt(query.identifier as string, 10);
+ const targetIdentifier = Number.parseInt(query.identifier as string, 10)
if (!controller.isConfigured(targetIdentifier)) {
- response.writeHead(400, { "Content-Type": "text/html" });
- response.end("Bad request. No target found for given identifier " + targetIdentifier);
- return;
+ response.writeHead(400, { 'Content-Type': 'text/html' })
+ response.end(`Bad request. No target found for given identifier ${targetIdentifier}`)
+ return
}
- controller.setActiveIdentifier(targetIdentifier);
- response.writeHead(200, { "Content-Type": "text/html" });
- response.end("OK");
- return;
- } else if (pathname === "getActiveTarget") {
- response.writeHead(200, { "Content-Type": "text/html" });
- response.end(controller.activeIdentifier + "");
- return;
- } else if (pathname === "getTargetId") {
+ controller.setActiveIdentifier(targetIdentifier)
+ response.writeHead(200, { 'Content-Type': 'text/html' })
+ response.end('OK')
+ } else if (pathname === 'getActiveTarget') {
+ response.writeHead(200, { 'Content-Type': 'text/html' })
+ response.end(`${controller.activeIdentifier}`)
+ } else if (pathname === 'getTargetId') {
if (query === undefined || query.name === undefined) {
- response.writeHead(400, { "Content-Type": "text/html" });
- response.end("Bad request. Must include 'name' in query string!");
- return;
+ response.writeHead(400, { 'Content-Type': 'text/html' })
+ response.end('Bad request. Must include \'name\' in query string!')
+ return
}
- const targetIdentifier = controller.getTargetIdentifierByName(query.name as string);
+ const targetIdentifier = controller.getTargetIdentifierByName(query.name as string)
if (targetIdentifier === undefined) {
- response.writeHead(400, { "Content-Type": "text/html" });
- response.end("Bad request. No target found for given name " + escapeHTML(query.name + ""));
- return;
+ response.writeHead(400, { 'Content-Type': 'text/html' })
+ response.end(`Bad request. No target found for given name ${escapeHTML(`${query.name}`)}`)
+ return
}
- response.writeHead(200, { "Content-Type": "text/html" });
- response.end("" + targetIdentifier);
- return;
- } else if (pathname === "button") {
+ response.writeHead(200, { 'Content-Type': 'text/html' })
+ response.end(`${targetIdentifier}`)
+ } else if (pathname === 'button') {
if (query === undefined || query.state === undefined || query.button === undefined) {
- response.writeHead(400, { "Content-Type": "text/html" });
- response.end("Bad request. Must include 'state' and 'button' in query string!");
- return;
+ response.writeHead(400, { 'Content-Type': 'text/html' })
+ response.end('Bad request. Must include \'state\' and \'button\' in query string!')
+ return
}
- const buttonState = parseInt(query.state as string, 10);
- const button = parseInt(query.button as string, 10);
+ const buttonState = Number.parseInt(query.state as string, 10)
+ const button = Number.parseInt(query.button as string, 10)
// @ts-expect-error: forceConsistentCasingInFileNames compiler option
if (ButtonState[buttonState] === undefined) {
- response.writeHead(400, { "Content-Type": "text/html" });
- response.end("Bad request. Unknown button state " + escapeHTML(query.state + ""));
- return;
+ response.writeHead(400, { 'Content-Type': 'text/html' })
+ response.end(`Bad request. Unknown button state ${escapeHTML(`${query.state}`)}`)
+ return
}
// @ts-expect-error: forceConsistentCasingInFileNames compiler option
if (ButtonType[button] === undefined) {
- response.writeHead(400, { "Content-Type": "text/html" });
- response.end("Bad request. Unknown button " + escapeHTML(query.button + ""));
- return;
+ response.writeHead(400, { 'Content-Type': 'text/html' })
+ response.end(`Bad request. Unknown button ${escapeHTML(`${query.button}`)}`)
+ return
}
if (buttonState === ButtonState.UP) {
- controller.releaseButton(button);
+ controller.releaseButton(button)
} else if (buttonState === ButtonState.DOWN) {
- controller.pushButton(button);
+ controller.pushButton(button)
}
- response.writeHead(200, { "Content-Type": "text/html" });
- response.end("OK");
- return;
- } else if (pathname === "press") {
+ response.writeHead(200, { 'Content-Type': 'text/html' })
+ response.end('OK')
+ } else if (pathname === 'press') {
if (query === undefined || query.button === undefined) {
- response.writeHead(400, { "Content-Type": "text/html" });
- response.end("Bad request. Must include 'button' in query string!");
- return;
+ response.writeHead(400, { 'Content-Type': 'text/html' })
+ response.end('Bad request. Must include \'button\' in query string!')
+ return
}
- let time = 200;
+ let time = 200
if (query.time !== undefined) {
- const parsedTime = parseInt(query.time as string, 10);
+ const parsedTime = Number.parseInt(query.time as string, 10)
if (parsedTime) {
- time = parsedTime;
+ time = parsedTime
}
}
- const button = parseInt(query.button as string, 10);
+ const button = Number.parseInt(query.button as string, 10)
// @ts-expect-error: forceConsistentCasingInFileNames compiler option
if (ButtonType[button] === undefined) {
- response.writeHead(400, { "Content-Type": "text/html" });
- response.end("Bad request. Unknown button " + escapeHTML(query.button + ""));
- return;
+ response.writeHead(400, { 'Content-Type': 'text/html' })
+ response.end(`Bad request. Unknown button ${escapeHTML(`${query.button}`)}`)
+ return
}
- controller.pushAndReleaseButton(button, time);
-
- response.writeHead(200, { "Content-Type": "text/html" });
- response.end("OK");
- return;
- } else if (pathname === "listTargets") {
- const targets = controller.targetConfigurations;
-
- response.writeHead(200, { "Content-Type": "application/json" });
- response.end(JSON.stringify(targets, undefined, 4));
- return;
- } else if (pathname === "getActive") {
- response.writeHead(200, { "Content-Type": "text/html" });
- response.end(controller.isActive()? "true": "false");
- return;
+ controller.pushAndReleaseButton(button, time)
+
+ response.writeHead(200, { 'Content-Type': 'text/html' })
+ response.end('OK')
+ } else if (pathname === 'listTargets') {
+ const targets = controller.targetConfigurations
+
+ response.writeHead(200, { 'Content-Type': 'application/json' })
+ response.end(JSON.stringify(targets, undefined, 4))
+ } else if (pathname === 'getActive') {
+ response.writeHead(200, { 'Content-Type': 'text/html' })
+ response.end(controller.isActive() ? 'true' : 'false')
} else {
- response.writeHead(404, { "Content-Type": "text/html" });
- response.end("Not Found. No path found for " + escapeHTML(pathname));
- return;
+ response.writeHead(404, { 'Content-Type': 'text/html' })
+ response.end(`Not Found. No path found for ${escapeHTML(pathname)}`)
}
-}).listen(8080);
+}).listen(8080)
diff --git a/src/accessories/Camera_accessory.ts b/src/accessories/Camera_accessory.ts
index 369dc1dab..898c2411f 100644
--- a/src/accessories/Camera_accessory.ts
+++ b/src/accessories/Camera_accessory.ts
@@ -1,63 +1,73 @@
-import assert from "assert";
-import { ChildProcess, spawn } from "child_process";
-import { once } from "events";
-import { AddressInfo, createServer, Server, Socket } from "net";
+/* eslint-disable no-console */
+import type { ChildProcess } from 'node:child_process'
+import type { AddressInfo, Server, Socket } from 'node:net'
+
+import type {
+ CameraRecordingConfiguration,
+ CameraRecordingDelegate,
+ CameraStreamingDelegate,
+ HDSProtocolSpecificErrorReason,
+ PrepareStreamCallback,
+ PrepareStreamRequest,
+ PrepareStreamResponse,
+ RecordingPacket,
+ SnapshotRequest,
+ SnapshotRequestCallback,
+ StreamingRequest,
+ StreamRequestCallback,
+ StreamSessionIdentifier,
+ VideoInfo,
+} from '../index.js'
+
+import assert from 'node:assert'
+import { Buffer } from 'node:buffer'
+import { spawn } from 'node:child_process'
+import { once } from 'node:events'
+import { createServer } from 'node:net'
+import process from 'node:process'
+
import {
Accessory,
AudioBitrate,
AudioRecordingCodecType,
AudioRecordingSamplerate,
CameraController,
- CameraRecordingConfiguration,
- CameraRecordingDelegate,
- CameraStreamingDelegate,
Categories,
Characteristic,
H264Level,
H264Profile,
- HDSProtocolSpecificErrorReason,
MediaContainerType,
- PrepareStreamCallback,
- PrepareStreamRequest,
- PrepareStreamResponse,
- RecordingPacket,
Service,
- SnapshotRequest,
- SnapshotRequestCallback,
SRTPCryptoSuites,
- StreamingRequest,
- StreamRequestCallback,
StreamRequestTypes,
- StreamSessionIdentifier,
uuid,
VideoCodecType,
- VideoInfo,
-} from "..";
+} from '../index.js'
interface MP4Atom {
- header: Buffer;
- length: number;
- type: string;
- data: Buffer;
+ header: Buffer
+ length: number
+ type: string
+ data: Buffer
}
-const cameraUUID = uuid.generate("hap-nodejs:accessories:ip-camera");
-const camera = exports.accessory = new Accessory("IPCamera", cameraUUID);
+const cameraUUID = uuid.generate('hap-nodejs:accessories:ip-camera')
+const camera = exports.accessory = new Accessory('IPCamera', cameraUUID)
// @ts-expect-error: Core/BridgeCore API
-camera.username = "9F:B2:46:0C:40:DB";
+camera.username = '9F:B2:46:0C:40:DB'
// @ts-expect-error: Core/BridgeCore API
-camera.pincode = "948-23-459";
-camera.category = Categories.IP_CAMERA;
+camera.pincode = '948-23-459'
+camera.category = Categories.IP_CAMERA
-type SessionInfo = {
- address: string, // address of the HAP controller
+interface SessionInfo {
+ address: string // address of the HAP controller
- videoPort: number, // port of the controller
- localVideoPort: number,
- videoCryptoSuite: SRTPCryptoSuites, // should be saved if multiple suites are supported
- videoSRTP: Buffer, // key and salt concatenated
- videoSSRC: number, // rtp synchronisation source
+ videoPort: number // port of the controller
+ localVideoPort: number
+ videoCryptoSuite: SRTPCryptoSuites // should be saved if multiple suites are supported
+ videoSRTP: Buffer // key and salt concatenated
+ videoSSRC: number // rtp synchronisation source
/* Won't be saved as audio is not supported by this example
audioPort: number,
@@ -67,98 +77,98 @@ type SessionInfo = {
*/
}
-type OngoingSession = {
- localVideoPort: number,
- process: ChildProcess,
+interface OngoingSession {
+ localVideoPort: number
+ process: ChildProcess
}
const FFMPEGH264ProfileNames = [
- "baseline",
- "main",
- "high",
-];
+ 'baseline',
+ 'main',
+ 'high',
+]
const FFMPEGH264LevelNames = [
- "3.1",
- "3.2",
- "4.0",
-];
+ '3.1',
+ '3.2',
+ '4.0',
+]
-const ports = new Set();
+const ports = new Set()
function getPort(): number {
- for (let i = 5011;; i++) {
+ for (let i = 5011; ; i++) {
if (!ports.has(i)) {
- ports.add(i);
- return i;
+ ports.add(i)
+ return i
}
}
}
class ExampleCamera implements CameraStreamingDelegate, CameraRecordingDelegate {
- private ffmpegDebugOutput = false;
+ private ffmpegDebugOutput = false
- controller?: CameraController;
+ controller?: CameraController
// keep track of sessions
- pendingSessions: Record = {};
- ongoingSessions: Record = {};
+ pendingSessions: Record = {}
+ ongoingSessions: Record = {}
// minimal secure video properties.
- configuration?: CameraRecordingConfiguration;
- handlingStreamingRequest = false;
- server?: MP4StreamingServer;
+ configuration?: CameraRecordingConfiguration
+ handlingStreamingRequest = false
+ server?: MP4StreamingServer
handleSnapshotRequest(request: SnapshotRequest, callback: SnapshotRequestCallback): void {
- const ffmpegCommand = `-f lavfi -i testsrc=s=${request.width}x${request.height} -vframes 1 -f mjpeg -`;
- const ffmpeg = spawn("ffmpeg", ffmpegCommand.split(" "), { env: process.env });
+ const ffmpegCommand = `-f lavfi -i testsrc=s=${request.width}x${request.height} -vframes 1 -f mjpeg -`
+ const ffmpeg = spawn('ffmpeg', ffmpegCommand.split(' '), { env: process.env })
- const snapshotBuffers: Buffer[] = [];
+ const snapshotBuffers: Buffer[] = []
- ffmpeg.stdout.on("data", data => snapshotBuffers.push(data));
- ffmpeg.stderr.on("data", data => {
+ ffmpeg.stdout.on('data', data => snapshotBuffers.push(data))
+ ffmpeg.stderr.on('data', (data) => {
if (this.ffmpegDebugOutput) {
- console.log("SNAPSHOT: " + String(data));
+ console.log(`SNAPSHOT: ${String(data)}`)
}
- });
+ })
- ffmpeg.on("exit", (code, signal) => {
+ ffmpeg.on('exit', (code, signal) => {
if (signal) {
- console.log("Snapshot process was killed with signal: " + signal);
- callback(new Error("killed with signal " + signal));
+ console.log(`Snapshot process was killed with signal: ${signal}`)
+ callback(new Error(`killed with signal ${signal}`))
} else if (code === 0) {
- console.log(`Successfully captured snapshot at ${request.width}x${request.height}`);
- callback(undefined, Buffer.concat(snapshotBuffers));
+ console.log(`Successfully captured snapshot at ${request.width}x${request.height}`)
+ callback(undefined, Buffer.concat(snapshotBuffers))
} else {
- console.log("Snapshot process exited with code " + code);
- callback(new Error("Snapshot process exited with code " + code));
+ console.log(`Snapshot process exited with code ${code}`)
+ callback(new Error(`Snapshot process exited with code ${code}`))
}
- });
+ })
}
// called when iOS request rtp setup
prepareStream(request: PrepareStreamRequest, callback: PrepareStreamCallback): void {
- const sessionId: StreamSessionIdentifier = request.sessionID;
- const targetAddress = request.targetAddress;
+ const sessionId: StreamSessionIdentifier = request.sessionID
+ const targetAddress = request.targetAddress
- const video = request.video;
+ const video = request.video
- const videoCryptoSuite = video.srtpCryptoSuite; // could be used to support multiple crypto suite (or support no suite for debugging)
- const videoSrtpKey = video.srtp_key;
- const videoSrtpSalt = video.srtp_salt;
+ const videoCryptoSuite = video.srtpCryptoSuite // could be used to support multiple crypto suite (or support no suite for debugging)
+ const videoSrtpKey = video.srtp_key
+ const videoSrtpSalt = video.srtp_salt
- const videoSSRC = CameraController.generateSynchronisationSource();
+ const videoSSRC = CameraController.generateSynchronisationSource()
- const localPort = getPort();
+ const localPort = getPort()
const sessionInfo: SessionInfo = {
address: targetAddress,
videoPort: video.port,
localVideoPort: localPort,
- videoCryptoSuite: videoCryptoSuite,
+ videoCryptoSuite,
videoSRTP: Buffer.concat([videoSrtpKey, videoSrtpSalt]),
- videoSSRC: videoSSRC,
- };
+ videoSSRC,
+ }
const response: PrepareStreamResponse = {
video: {
@@ -169,150 +179,151 @@ class ExampleCamera implements CameraStreamingDelegate, CameraRecordingDelegate
srtp_salt: videoSrtpSalt,
},
// audio is omitted as we do not support audio in this example
- };
+ }
- this.pendingSessions[sessionId] = sessionInfo;
- callback(undefined, response);
+ this.pendingSessions[sessionId] = sessionInfo
+ callback(undefined, response)
}
// called when iOS device asks stream to start/stop/reconfigure
handleStreamRequest(request: StreamingRequest, callback: StreamRequestCallback): void {
- const sessionId = request.sessionID;
+ const sessionId = request.sessionID
switch (request.type) {
- case StreamRequestTypes.START: {
- const sessionInfo = this.pendingSessions[sessionId];
-
- const video: VideoInfo = request.video;
-
- const profile = FFMPEGH264ProfileNames[video.profile];
- const level = FFMPEGH264LevelNames[video.level];
- const width = video.width;
- const height = video.height;
- const fps = video.fps;
-
- const payloadType = video.pt;
- const maxBitrate = video.max_bit_rate;
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const rtcpInterval = video.rtcp_interval; // usually 0.5
- const mtu = video.mtu; // maximum transmission unit
-
- const address = sessionInfo.address;
- const videoPort = sessionInfo.videoPort;
- const localVideoPort = sessionInfo.localVideoPort;
- const ssrc = sessionInfo.videoSSRC;
- const cryptoSuite = sessionInfo.videoCryptoSuite;
- const videoSRTP = sessionInfo.videoSRTP.toString("base64");
-
- console.log(`Starting video stream (${width}x${height}, ${fps} fps, ${maxBitrate} kbps, ${mtu} mtu)...`);
-
- let videoffmpegCommand = `-re -f lavfi -i testsrc=s=${width}x${height}:r=${fps} -map 0:0 ` +
- `-c:v h264 -pix_fmt yuv420p -r ${fps} -an -sn -dn -b:v ${maxBitrate}k ` +
- `-profile:v ${profile} -level:v ${level} ` +
- `-payload_type ${payloadType} -ssrc ${ssrc} -f rtp `;
-
- if (cryptoSuite !== SRTPCryptoSuites.NONE) {
- let suite: string;
- switch (cryptoSuite) {
- case SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80: // actually ffmpeg just supports AES_CM_128_HMAC_SHA1_80
- suite = "AES_CM_128_HMAC_SHA1_80";
- break;
- case SRTPCryptoSuites.AES_CM_256_HMAC_SHA1_80:
- suite = "AES_CM_256_HMAC_SHA1_80";
- break;
- }
-
- videoffmpegCommand += `-srtp_out_suite ${suite} -srtp_out_params ${videoSRTP} s`;
- }
-
- videoffmpegCommand += `rtp://${address}:${videoPort}?rtcpport=${videoPort}&localrtcpport=${localVideoPort}&pkt_size=${mtu}`;
-
- if (this.ffmpegDebugOutput) {
- console.log("FFMPEG command: ffmpeg " + videoffmpegCommand);
- }
-
- const ffmpegVideo = spawn("ffmpeg", videoffmpegCommand.split(" "), { env: process.env });
-
- let started = false;
- ffmpegVideo.stderr.on("data", (data: Buffer) => {
- console.log(data.toString("utf8"));
- if (!started) {
- started = true;
- console.log("FFMPEG: received first frame");
+ case StreamRequestTypes.START: {
+ const sessionInfo = this.pendingSessions[sessionId]
+
+ const video: VideoInfo = request.video
+
+ const profile = FFMPEGH264ProfileNames[video.profile]
+ const level = FFMPEGH264LevelNames[video.level]
+ const width = video.width
+ const height = video.height
+ const fps = video.fps
+
+ const payloadType = video.pt
+ const maxBitrate = video.max_bit_rate
+
+ // eslint-disable-next-line unused-imports/no-unused-vars
+ const rtcpInterval = video.rtcp_interval // usually 0.5
+ const mtu = video.mtu // maximum transmission unit
+
+ const address = sessionInfo.address
+ const videoPort = sessionInfo.videoPort
+ const localVideoPort = sessionInfo.localVideoPort
+ const ssrc = sessionInfo.videoSSRC
+ const cryptoSuite = sessionInfo.videoCryptoSuite
+ const videoSRTP = sessionInfo.videoSRTP.toString('base64')
+
+ console.log(`Starting video stream (${width}x${height}, ${fps} fps, ${maxBitrate} kbps, ${mtu} mtu)...`)
+
+ let videoffmpegCommand = `-re -f lavfi -i testsrc=s=${width}x${height}:r=${fps} -map 0:0 `
+ + `-c:v h264 -pix_fmt yuv420p -r ${fps} -an -sn -dn -b:v ${maxBitrate}k `
+ + `-profile:v ${profile} -level:v ${level} `
+ + `-payload_type ${payloadType} -ssrc ${ssrc} -f rtp `
+
+ if (cryptoSuite !== SRTPCryptoSuites.NONE) {
+ let suite: string
+ switch (cryptoSuite) {
+ case SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80: // actually ffmpeg just supports AES_CM_128_HMAC_SHA1_80
+ suite = 'AES_CM_128_HMAC_SHA1_80'
+ break
+ case SRTPCryptoSuites.AES_CM_256_HMAC_SHA1_80:
+ suite = 'AES_CM_256_HMAC_SHA1_80'
+ break
+ }
- callback(); // do not forget to execute callback once set up
+ videoffmpegCommand += `-srtp_out_suite ${suite} -srtp_out_params ${videoSRTP} s`
}
+ videoffmpegCommand += `rtp://${address}:${videoPort}?rtcpport=${videoPort}&localrtcpport=${localVideoPort}&pkt_size=${mtu}`
+
if (this.ffmpegDebugOutput) {
- console.log("VIDEO: " + String(data));
+ console.log(`FFMPEG command: ffmpeg ${videoffmpegCommand}`)
}
- });
- ffmpegVideo.on("error", error => {
- console.log("[Video] Failed to start video stream: " + error.message);
- callback(new Error("ffmpeg process creation failed!"));
- });
- ffmpegVideo.on("exit", (code, signal) => {
- const message = "[Video] ffmpeg exited with code: " + code + " and signal: " + signal;
-
- if (code == null || code === 255) {
- console.log(message + " (Video stream stopped!)");
- } else {
- console.log(message + " (error)");
+ const ffmpegVideo = spawn('ffmpeg', videoffmpegCommand.split(' '), { env: process.env })
+
+ let started = false
+ ffmpegVideo.stderr.on('data', (data: Buffer) => {
+ console.log(data.toString('utf8'))
if (!started) {
- callback(new Error(message));
+ started = true
+ console.log('FFMPEG: received first frame')
+
+ callback() // do not forget to execute callback once set up
+ }
+
+ if (this.ffmpegDebugOutput) {
+ console.log(`VIDEO: ${String(data)}`)
+ }
+ })
+ ffmpegVideo.on('error', (error) => {
+ console.log(`[Video] Failed to start video stream: ${error.message}`)
+ callback(new Error('ffmpeg process creation failed!'))
+ })
+ ffmpegVideo.on('exit', (code, signal) => {
+ const message = `[Video] ffmpeg exited with code: ${code} and signal: ${signal}`
+
+ if (code == null || code === 255) {
+ console.log(`${message} (Video stream stopped!)`)
} else {
- this.controller!.forceStopStreamingSession(sessionId);
+ console.log(`${message} (error)`)
+
+ if (!started) {
+ callback(new Error(message))
+ } else {
+ this.controller!.forceStopStreamingSession(sessionId)
+ }
}
- }
- });
+ })
- this.ongoingSessions[sessionId] = {
- localVideoPort: localVideoPort,
- process: ffmpegVideo,
- };
- delete this.pendingSessions[sessionId];
+ this.ongoingSessions[sessionId] = {
+ localVideoPort,
+ process: ffmpegVideo,
+ }
+ delete this.pendingSessions[sessionId]
- break;
- }
- case StreamRequestTypes.RECONFIGURE:
- // not supported by this example
- console.log("Received (unsupported) request to reconfigure to: " + JSON.stringify(request.video));
- callback();
- break;
- case StreamRequestTypes.STOP: {
- const ongoingSession = this.ongoingSessions[sessionId];
- if (!ongoingSession) {
- callback();
- break;
+ break
}
+ case StreamRequestTypes.RECONFIGURE:
+ // not supported by this example
+ console.log(`Received (unsupported) request to reconfigure to: ${JSON.stringify(request.video)}`)
+ callback()
+ break
+ case StreamRequestTypes.STOP: {
+ const ongoingSession = this.ongoingSessions[sessionId]
+ if (!ongoingSession) {
+ callback()
+ break
+ }
- ports.delete(ongoingSession.localVideoPort);
+ ports.delete(ongoingSession.localVideoPort)
- try {
- ongoingSession.process.kill("SIGKILL");
- } catch (e) {
- console.log("Error occurred terminating the video process!");
- console.log(e);
- }
+ try {
+ ongoingSession.process.kill('SIGKILL')
+ } catch (e) {
+ console.log('Error occurred terminating the video process!')
+ console.log(e)
+ }
- delete this.ongoingSessions[sessionId];
+ delete this.ongoingSessions[sessionId]
- console.log("Stopped streaming session!");
- callback();
- break;
- }
+ console.log('Stopped streaming session!')
+ callback()
+ break
+ }
}
}
updateRecordingActive(active: boolean): void {
// we haven't implemented a prebuffer
- console.log("Recording active set to " + active);
+ console.log(`Recording active set to ${active}`)
}
updateRecordingConfiguration(configuration: CameraRecordingConfiguration | undefined): void {
- this.configuration = configuration;
- console.log(configuration);
+ this.configuration = configuration
+ console.log(configuration)
}
/**
@@ -320,13 +331,13 @@ class ExampleCamera implements CameraStreamingDelegate, CameraRecordingDelegate
* CameraController supporting HomeKit Secure Video.
*
* An ideal implementation would diverge from this in the following ways:
- * * It would implement a prebuffer and respect the recording `active` characteristic for that.
- * * It would start to immediately record after a trigger event occurred and not just
+ * It would implement a prebuffer and respect the recording `active` characteristic for that.
+ * It would start to immediately record after a trigger event occurred and not just
* when the HomeKit Controller requests it (see the documentation of `CameraRecordingDelegate`).
*/
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ // eslint-disable-next-line unused-imports/no-unused-vars
async *handleRecordingStreamRequest(streamId: number): AsyncGenerator {
- assert(!!this.configuration);
+ assert(!!this.configuration)
/**
* With this flag you can control how the generator reacts to a reset to the motion trigger.
@@ -335,208 +346,217 @@ class ExampleCamera implements CameraStreamingDelegate, CameraRecordingDelegate
*
* Note: In a real implementation you would most likely introduce a bit of a delay.
*/
- const STOP_AFTER_MOTION_STOP = false;
+ const STOP_AFTER_MOTION_STOP = false
- this.handlingStreamingRequest = true;
+ this.handlingStreamingRequest = true
- assert(this.configuration.videoCodec.type === VideoCodecType.H264);
+ assert(this.configuration.videoCodec.type === VideoCodecType.H264)
- const profile = this.configuration.videoCodec.parameters.profile === H264Profile.HIGH ? "high"
- : this.configuration.videoCodec.parameters.profile === H264Profile.MAIN ? "main" : "baseline";
+ const profile = this.configuration.videoCodec.parameters.profile === H264Profile.HIGH
+ ? 'high'
+ : this.configuration.videoCodec.parameters.profile === H264Profile.MAIN ? 'main' : 'baseline'
- const level = this.configuration.videoCodec.parameters.level === H264Level.LEVEL4_0 ? "4.0"
- : this.configuration.videoCodec.parameters.level === H264Level.LEVEL3_2 ? "3.2" : "3.1";
+ const level = this.configuration.videoCodec.parameters.level === H264Level.LEVEL4_0
+ ? '4.0'
+ : this.configuration.videoCodec.parameters.level === H264Level.LEVEL3_2 ? '3.2' : '3.1'
const videoArgs: Array = [
- "-an",
- "-sn",
- "-dn",
- "-codec:v",
- "libx264",
- "-pix_fmt",
- "yuv420p",
-
- "-profile:v", profile,
- "-level:v", level,
- "-b:v", `${this.configuration.videoCodec.parameters.bitRate}k`,
- "-force_key_frames", `expr:eq(t,n_forced*${this.configuration.videoCodec.parameters.iFrameInterval / 1000})`,
- "-r", this.configuration.videoCodec.resolution[2].toString(),
- ];
-
- let samplerate: string;
+ '-an',
+ '-sn',
+ '-dn',
+ '-codec:v',
+ 'libx264',
+ '-pix_fmt',
+ 'yuv420p',
+
+ '-profile:v',
+ profile,
+ '-level:v',
+ level,
+ '-b:v',
+ `${this.configuration.videoCodec.parameters.bitRate}k`,
+ '-force_key_frames',
+ `expr:eq(t,n_forced*${this.configuration.videoCodec.parameters.iFrameInterval / 1000})`,
+ '-r',
+ this.configuration.videoCodec.resolution[2].toString(),
+ ]
+
+ let samplerate: string
switch (this.configuration.audioCodec.samplerate) {
- case AudioRecordingSamplerate.KHZ_8:
- samplerate = "8";
- break;
- case AudioRecordingSamplerate.KHZ_16:
- samplerate = "16";
- break;
- case AudioRecordingSamplerate.KHZ_24:
- samplerate = "24";
- break;
- case AudioRecordingSamplerate.KHZ_32:
- samplerate = "32";
- break;
- case AudioRecordingSamplerate.KHZ_44_1:
- samplerate = "44.1";
- break;
- case AudioRecordingSamplerate.KHZ_48:
- samplerate = "48";
- break;
- default:
- throw new Error("Unsupported audio samplerate: " + this.configuration.audioCodec.samplerate);
+ case AudioRecordingSamplerate.KHZ_8:
+ samplerate = '8'
+ break
+ case AudioRecordingSamplerate.KHZ_16:
+ samplerate = '16'
+ break
+ case AudioRecordingSamplerate.KHZ_24:
+ samplerate = '24'
+ break
+ case AudioRecordingSamplerate.KHZ_32:
+ samplerate = '32'
+ break
+ case AudioRecordingSamplerate.KHZ_44_1:
+ samplerate = '44.1'
+ break
+ case AudioRecordingSamplerate.KHZ_48:
+ samplerate = '48'
+ break
+ default:
+ throw new Error(`Unsupported audio samplerate: ${this.configuration.audioCodec.samplerate}`)
}
const audioArgs: Array = this.controller?.recordingManagement?.recordingManagementService.getCharacteristic(Characteristic.RecordingAudioActive)
? [
- "-acodec", "libfdk_aac",
- ...(this.configuration.audioCodec.type === AudioRecordingCodecType.AAC_LC ?
- ["-profile:a", "aac_low"] :
- ["-profile:a", "aac_eld"]),
- "-ar", `${samplerate}k`,
- "-b:a", `${this.configuration.audioCodec.bitrate}k`,
- "-ac", `${this.configuration.audioCodec.audioChannels}`,
- ]
- : [];
+ '-acodec',
+ 'libfdk_aac',
+ ...(this.configuration.audioCodec.type === AudioRecordingCodecType.AAC_LC
+ ? ['-profile:a', 'aac_low']
+ : ['-profile:a', 'aac_eld']),
+ '-ar',
+ `${samplerate}k`,
+ '-b:a',
+ `${this.configuration.audioCodec.bitrate}k`,
+ '-ac',
+ `${this.configuration.audioCodec.audioChannels}`,
+ ]
+ : []
this.server = new MP4StreamingServer(
- "ffmpeg",
+ 'ffmpeg',
`-f lavfi -i \
testsrc=s=${this.configuration.videoCodec.resolution[0]}x${this.configuration.videoCodec.resolution[1]}:r=${this.configuration.videoCodec.resolution[2]}`
- .split(/ /g),
+ .split(/ /),
audioArgs,
videoArgs,
- );
+ )
- await this.server.start();
+ await this.server.start()
if (!this.server || this.server.destroyed) {
- return; // early exit
+ return // early exit
}
- const pending: Array = [];
+ const pending: Array = []
try {
for await (const box of this.server.generator()) {
- pending.push(box.header, box.data);
+ pending.push(box.header, box.data)
- const motionDetected = camera.getService(Service.MotionSensor)?.getCharacteristic(Characteristic.MotionDetected).value;
+ const motionDetected = camera.getService(Service.MotionSensor)?.getCharacteristic(Characteristic.MotionDetected).value
- console.log("mp4 box type " + box.type + " and length " + box.length);
- if (box.type === "moov" || box.type === "mdat") {
- const fragment = Buffer.concat(pending);
- pending.splice(0, pending.length);
+ console.log(`mp4 box type ${box.type} and length ${box.length}`)
+ if (box.type === 'moov' || box.type === 'mdat') {
+ const fragment = Buffer.concat(pending)
+ pending.splice(0, pending.length)
- const isLast = STOP_AFTER_MOTION_STOP && !motionDetected;
+ const isLast = STOP_AFTER_MOTION_STOP && !motionDetected
yield {
data: fragment,
- isLast: isLast,
- };
+ isLast,
+ }
if (isLast) {
- console.log("Ending session due to motion stopped!");
- break;
+ console.log('Ending session due to motion stopped!')
+ break
}
}
}
} catch (error) {
- if (!error.message.startsWith("FFMPEG")) { // cheap way of identifying our own emitted errors
- console.error("Encountered unexpected error on generator " + error.stack);
+ if (!error.message.startsWith('FFMPEG')) { // cheap way of identifying our own emitted errors
+ console.error(`Encountered unexpected error on generator ${error.stack}`)
}
}
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ // eslint-disable-next-line unused-imports/no-unused-vars
closeRecordingStream(streamId: number, reason?: HDSProtocolSpecificErrorReason): void {
if (this.server) {
- this.server.destroy();
- this.server = undefined;
+ this.server.destroy()
+ this.server = undefined
}
- this.handlingStreamingRequest = false;
+ this.handlingStreamingRequest = false
}
acknowledgeStream(streamId: number): void {
- this.closeRecordingStream(streamId);
+ this.closeRecordingStream(streamId)
}
}
class MP4StreamingServer {
- readonly server: Server;
+ readonly server: Server
/**
* This can be configured to output ffmpeg debug output!
*/
- readonly debugMode: boolean = false;
+ readonly debugMode: boolean = false
- readonly ffmpegPath: string;
- readonly args: string[];
+ readonly ffmpegPath: string
+ readonly args: string[]
- socket?: Socket;
- childProcess?: ChildProcess;
- destroyed = false;
+ socket?: Socket
+ childProcess?: ChildProcess
+ destroyed = false
- connectPromise: Promise;
- connectResolve?: () => void;
+ connectPromise: Promise
+ connectResolve?: () => void
constructor(ffmpegPath: string, ffmpegInput: Array, audioOutputArgs: Array, videoOutputArgs: Array) {
- this.connectPromise = new Promise(resolve => this.connectResolve = resolve);
+ this.connectPromise = new Promise(resolve => this.connectResolve = resolve)
- this.server = createServer(this.handleConnection.bind(this));
- this.ffmpegPath = ffmpegPath;
- this.args = [];
+ this.server = createServer(this.handleConnection.bind(this))
+ this.ffmpegPath = ffmpegPath
+ this.args = []
- this.args.push(...ffmpegInput);
+ this.args.push(...ffmpegInput)
- this.args.push(...audioOutputArgs);
+ this.args.push(...audioOutputArgs)
- this.args.push("-f", "mp4");
- this.args.push(...videoOutputArgs);
- this.args.push("-fflags",
- "+genpts",
- "-reset_timestamps",
- "1");
+ this.args.push('-f', 'mp4')
+ this.args.push(...videoOutputArgs)
+ this.args.push('-fflags', '+genpts', '-reset_timestamps', '1')
this.args.push(
- "-movflags", "frag_keyframe+empty_moov+default_base_moof",
- );
+ '-movflags',
+ 'frag_keyframe+empty_moov+default_base_moof',
+ )
}
async start() {
- const promise = once(this.server, "listening");
- this.server.listen(); // listen on random port
- await promise;
+ const promise = once(this.server, 'listening')
+ this.server.listen() // listen on random port
+ await promise
if (this.destroyed) {
- return;
+ return
}
- const port = (this.server.address() as AddressInfo).port;
- this.args.push("tcp://127.0.0.1:" + port);
+ const port = (this.server.address() as AddressInfo).port
+ this.args.push(`tcp://127.0.0.1:${port}`)
- console.log(this.ffmpegPath + " " + this.args.join(" "));
+ console.log(`${this.ffmpegPath} ${this.args.join(' ')}`)
- this.childProcess = spawn(this.ffmpegPath, this.args, { env: process.env, stdio: this.debugMode? "pipe": "ignore" });
- if (!this.childProcess ||!this.childProcess.stdout || !this.childProcess.stderr) {
- throw new Error("ChildProcess or its streams is undefined directly after the init!");
+ this.childProcess = spawn(this.ffmpegPath, this.args, { env: process.env, stdio: this.debugMode ? 'pipe' : 'ignore' })
+ if (!this.childProcess || !this.childProcess.stdout || !this.childProcess.stderr) {
+ throw new Error('ChildProcess or its streams is undefined directly after the init!')
}
- if(this.debugMode) {
- this.childProcess.stdout?.on("data", data => console.log(data.toString()));
- this.childProcess.stderr?.on("data", data => console.log(data.toString()));
+ if (this.debugMode) {
+ this.childProcess.stdout?.on('data', data => console.log(data.toString()))
+ this.childProcess.stderr?.on('data', data => console.log(data.toString()))
}
}
destroy() {
- this.socket?.destroy();
- this.childProcess?.kill();
+ this.socket?.destroy()
+ this.childProcess?.kill()
- this.socket = undefined;
- this.childProcess = undefined;
- this.destroyed = true;
+ this.socket = undefined
+ this.childProcess = undefined
+ this.destroyed = true
}
handleConnection(socket: Socket): void {
- this.server.close(); // don't accept any further clients
- this.socket = socket;
- this.connectResolve?.();
+ this.server.close() // don't accept any further clients
+ this.socket = socket
+ this.connectResolve?.()
}
/**
@@ -544,74 +564,77 @@ class MP4StreamingServer {
* Throws error to signal EOF when socket is closed.
*/
async* generator(): AsyncGenerator {
- await this.connectPromise;
+ await this.connectPromise
if (!this.socket || !this.childProcess) {
- console.log("Socket undefined " + !!this.socket + " childProcess undefined " + !!this.childProcess);
- throw new Error("Unexpected state!");
+ console.log(`Socket undefined ${!!this.socket} childProcess undefined ${!!this.childProcess}`)
+ throw new Error('Unexpected state!')
}
while (true) {
- const header = await this.read(8);
- const length = header.readInt32BE(0) - 8;
- const type = header.slice(4).toString();
- const data = await this.read(length);
+ const header = await this.read(8)
+ const length = header.readInt32BE(0) - 8
+ const type = header.subarray(4).toString()
+ const data = await this.read(length)
yield {
- header: header,
- length: length,
- type: type,
- data: data,
- };
+ header,
+ length,
+ type,
+ data,
+ }
}
}
async read(length: number): Promise {
if (!this.socket) {
- throw Error("FFMPEG tried reading from closed socket!");
+ throw new Error('FFMPEG tried reading from closed socket!')
}
if (!length) {
- return Buffer.alloc(0);
+ return Buffer.alloc(0)
}
- const value = this.socket.read(length);
+ const value = this.socket.read(length)
if (value) {
- return value;
+ return value
}
return new Promise((resolve, reject) => {
- const readHandler = () => {
- const value = this.socket!.read(length);
- if (value) {
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
- cleanup();
- resolve(value);
- }
- };
-
- const endHandler = () => {
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
- cleanup();
- reject(new Error(`FFMPEG socket closed during read for ${length} bytes!`));
- };
-
const cleanup = () => {
- this.socket?.removeListener("readable", readHandler);
- this.socket?.removeListener("close", endHandler);
- };
+ this.socket?.removeListener('readable', () => {
+ const value = this.socket!.read(length)
+ if (value) {
+ cleanup()
+ resolve(value)
+ }
+ })
+ this.socket?.removeListener('close', () => {
+ cleanup()
+ reject(new Error(`FFMPEG socket closed during read for ${length} bytes!`))
+ })
+ }
if (!this.socket) {
- throw new Error("FFMPEG socket is closed now!");
+ throw new Error('FFMPEG socket is closed now!')
}
- this.socket.on("readable", readHandler);
- this.socket.on("close", endHandler);
- });
+ this.socket.on('readable', () => {
+ const value = this.socket!.read(length)
+ if (value) {
+ cleanup()
+ resolve(value)
+ }
+ })
+ this.socket.on('close', () => {
+ cleanup()
+ reject(new Error(`FFMPEG socket closed during read for ${length} bytes!`))
+ })
+ })
}
}
-const streamDelegate = new ExampleCamera();
+const streamDelegate = new ExampleCamera()
const cameraController = new CameraController({
cameraStreamCount: 2, // HomeKit requires at least 2 streams, but 1 is also just fine
@@ -698,15 +721,15 @@ const cameraController = new CameraController({
motion: true,
occupancy: true,
},
-});
-streamDelegate.controller = cameraController;
+})
+streamDelegate.controller = cameraController
-camera.configureController(cameraController);
+camera.configureController(cameraController)
// a service to trigger the motion sensor!
-camera.addService(Service.Switch, "MOTION TRIGGER")
+camera.addService(Service.Switch, 'MOTION TRIGGER')
.getCharacteristic(Characteristic.On)
- .onSet(value => {
+ .onSet((value) => {
camera.getService(Service.MotionSensor)
- ?.updateCharacteristic(Characteristic.MotionDetected, value);
- });
+ ?.updateCharacteristic(Characteristic.MotionDetected, value)
+ })
diff --git a/src/accessories/Fan_accessory.ts b/src/accessories/Fan_accessory.ts
index 101b46368..3c8074497 100644
--- a/src/accessories/Fan_accessory.ts
+++ b/src/accessories/Fan_accessory.ts
@@ -1,67 +1,67 @@
+/* eslint-disable no-console */
// here's a fake hardware device that we'll expose to HomeKit
+import type { CharacteristicValue, VoidCallback } from '../index.js'
+
import {
Accessory,
AccessoryEventTypes,
Categories,
Characteristic,
- CharacteristicValue,
Service,
uuid,
- VoidCallback,
-} from "..";
+} from '../index.js'
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
const FAKE_FAN: Record = {
powerOn: false,
rSpeed: 100,
setPowerOn: (on: CharacteristicValue) => {
if (on) {
- //put your code here to turn on the fan
- FAKE_FAN.powerOn = on;
+ // put your code here to turn on the fan
+ FAKE_FAN.powerOn = on
} else {
- //put your code here to turn off the fan
- FAKE_FAN.powerOn = on;
+ // put your code here to turn off the fan
+ FAKE_FAN.powerOn = on
}
},
setSpeed: (value: CharacteristicValue) => {
- console.log("Setting fan rSpeed to %s", value);
- FAKE_FAN.rSpeed = value;
- //put your code here to set the fan to a specific value
+ console.log('Setting fan rSpeed to %s', value)
+ FAKE_FAN.rSpeed = value
+ // put your code here to set the fan to a specific value
},
identify: () => {
- //put your code here to identify the fan
- console.log("Fan Identified!");
+ // put your code here to identify the fan
+ console.log('Fan Identified!')
},
-};
+}
// This is the Accessory that we'll return to HAP-NodeJS that represents our fake fan.
-const fan = exports.accessory = new Accessory("Fan", uuid.generate("hap-nodejs:accessories:Fan"));
+const fan = exports.accessory = new Accessory('Fan', uuid.generate('hap-nodejs:accessories:Fan'))
// Add properties for publishing (in case we're using Core.js and not BridgedCore.js)
// @ts-expect-error: Core/BridgeCore API
-fan.username = "1A:2B:3C:4D:5E:FF";
+fan.username = '1A:2B:3C:4D:5E:FF'
// @ts-expect-error: Core/BridgeCore API
-fan.pincode = "031-45-154";
-fan.category = Categories.FAN;
+fan.pincode = '031-45-154'
+fan.category = Categories.FAN
// set some basic properties (these values are arbitrary and setting them is optional)
fan
.getService(Service.AccessoryInformation)!
- .setCharacteristic(Characteristic.Manufacturer, "Sample Company");
+ .setCharacteristic(Characteristic.Manufacturer, 'Sample Company')
// listen for the "identify" event for this Accessory
fan.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => {
- FAKE_FAN.identify();
- callback(); // success
-});
+ FAKE_FAN.identify()
+ callback() // success
+})
// Add the actual Fan Service and listen for change events from iOS.
fan
- .addService(Service.Fan, "Fan") // services exposed to the user should have "names" like "Fake Light" for us
+ .addService(Service.Fan, 'Fan') // services exposed to the user should have "names" like "Fake Light" for us
.getCharacteristic(Characteristic.On)!
.onSet((value) => {
- FAKE_FAN.setPowerOn(value);
- });
+ FAKE_FAN.setPowerOn(value)
+ })
// We want to intercept requests for our current power state so we can query the hardware itself instead of
// allowing HAP-NodeJS to return the cached Characteristic.value.
@@ -69,21 +69,20 @@ fan
.getService(Service.Fan)!
.getCharacteristic(Characteristic.On)!
.onGet(() => {
-
// this event is emitted when you ask Siri directly whether your fan is on or not. you might query
// the fan hardware itself to find this out, then call the callback. But if you take longer than a
// few seconds to respond, Siri will give up.
- return !!FAKE_FAN.powerOn;
- });
+ return !!FAKE_FAN.powerOn
+ })
// also add an "optional" Characteristic for speed
fan
.getService(Service.Fan)!
.addCharacteristic(Characteristic.RotationSpeed)
.onGet(async () => {
- return FAKE_FAN.rSpeed;
+ return FAKE_FAN.rSpeed
})
.onSet(async (value) => {
- FAKE_FAN.setSpeed(value);
- });
+ FAKE_FAN.setSpeed(value)
+ })
diff --git a/src/accessories/GarageDoorOpener_accessory.ts b/src/accessories/GarageDoorOpener_accessory.ts
index 26dd8d3e7..174c0604a 100644
--- a/src/accessories/GarageDoorOpener_accessory.ts
+++ b/src/accessories/GarageDoorOpener_accessory.ts
@@ -1,94 +1,92 @@
+/* eslint-disable no-console */
+import type { CharacteristicSetCallback, CharacteristicValue, NodeCallback, VoidCallback } from '../index.js'
+
import {
Accessory,
AccessoryEventTypes,
Categories,
Characteristic,
- CharacteristicEventTypes, CharacteristicSetCallback, CharacteristicValue,
- NodeCallback,
+ CharacteristicEventTypes,
Service,
uuid,
- VoidCallback,
-} from "..";
+} from '../index.js'
const FAKE_GARAGE = {
opened: false,
open: () => {
- console.log("Opening the Garage!");
- //add your code here which allows the garage to open
- FAKE_GARAGE.opened = true;
+ console.log('Opening the Garage!')
+ // add your code here which allows the garage to open
+ FAKE_GARAGE.opened = true
},
close: () => {
- console.log("Closing the Garage!");
- //add your code here which allows the garage to close
- FAKE_GARAGE.opened = false;
+ console.log('Closing the Garage!')
+ // add your code here which allows the garage to close
+ FAKE_GARAGE.opened = false
},
identify: () => {
- //add your code here which allows the garage to be identified
- console.log("Identify the Garage");
+ // add your code here which allows the garage to be identified
+ console.log('Identify the Garage')
},
status: () => {
- //use this section to get sensor values. set the boolean FAKE_GARAGE.opened with a sensor value.
- console.log("Sensor queried!");
- //FAKE_GARAGE.opened = true/false;
+ // use this section to get sensor values. set the boolean FAKE_GARAGE.opened with a sensor value.
+ console.log('Sensor queried!')
+ // FAKE_GARAGE.opened = true/false;
},
-};
+}
-const garageUUID = uuid.generate("hap-nodejs:accessories:" + "GarageDoor");
-const garage = exports.accessory = new Accessory("Garage Door", garageUUID);
+const garageUUID = uuid.generate('hap-nodejs:accessories:' + 'GarageDoor')
+const garage = exports.accessory = new Accessory('Garage Door', garageUUID)
// Add properties for publishing (in case we're using Core.js and not BridgedCore.js)
// @ts-expect-error: Core/BridgeCore API
-garage.username = "C1:5D:3F:EE:5E:FA"; //edit this if you use Core.js
+garage.username = 'C1:5D:3F:EE:5E:FA' // edit this if you use Core.js
// @ts-expect-error: Core/BridgeCore API
-garage.pincode = "031-45-154";
-garage.category = Categories.GARAGE_DOOR_OPENER;
+garage.pincode = '031-45-154'
+garage.category = Categories.GARAGE_DOOR_OPENER
garage
.getService(Service.AccessoryInformation)!
- .setCharacteristic(Characteristic.Manufacturer, "Liftmaster")
- .setCharacteristic(Characteristic.Model, "Rev-1")
- .setCharacteristic(Characteristic.SerialNumber, "TW000165");
+ .setCharacteristic(Characteristic.Manufacturer, 'Liftmaster')
+ .setCharacteristic(Characteristic.Model, 'Rev-1')
+ .setCharacteristic(Characteristic.SerialNumber, 'TW000165')
garage.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => {
- FAKE_GARAGE.identify();
- callback();
-});
+ FAKE_GARAGE.identify()
+ callback()
+})
garage
- .addService(Service.GarageDoorOpener, "Garage Door")
- .setCharacteristic(Characteristic.TargetDoorState, Characteristic.TargetDoorState.CLOSED) // force initial state to CLOSED
+ .addService(Service.GarageDoorOpener, 'Garage Door')
+ .setCharacteristic(Characteristic.TargetDoorState, Characteristic.TargetDoorState.CLOSED) // force initial state to 'CLOSED'
.getCharacteristic(Characteristic.TargetDoorState)!
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
-
if (value === Characteristic.TargetDoorState.CLOSED) {
- FAKE_GARAGE.close();
- callback();
+ FAKE_GARAGE.close()
+ callback()
garage
.getService(Service.GarageDoorOpener)!
- .setCharacteristic(Characteristic.CurrentDoorState, Characteristic.CurrentDoorState.CLOSED);
+ .setCharacteristic(Characteristic.CurrentDoorState, Characteristic.CurrentDoorState.CLOSED)
} else if (value === Characteristic.TargetDoorState.OPEN) {
- FAKE_GARAGE.open();
- callback();
+ FAKE_GARAGE.open()
+ callback()
garage
.getService(Service.GarageDoorOpener)!
- .setCharacteristic(Characteristic.CurrentDoorState, Characteristic.CurrentDoorState.OPEN);
+ .setCharacteristic(Characteristic.CurrentDoorState, Characteristic.CurrentDoorState.OPEN)
}
- });
-
+ })
garage
.getService(Service.GarageDoorOpener)!
.getCharacteristic(Characteristic.CurrentDoorState)!
.on(CharacteristicEventTypes.GET, (callback: NodeCallback) => {
-
- const err = null;
- FAKE_GARAGE.status();
+ const err = null
+ FAKE_GARAGE.status()
if (FAKE_GARAGE.opened) {
- console.log("Query: Is Garage Open? Yes.");
- callback(err, Characteristic.CurrentDoorState.OPEN);
+ console.log('Query: Is Garage Open? Yes.')
+ callback(err, Characteristic.CurrentDoorState.OPEN)
} else {
- console.log("Query: Is Garage Open? No.");
- callback(err, Characteristic.CurrentDoorState.CLOSED);
+ console.log('Query: Is Garage Open? No.')
+ callback(err, Characteristic.CurrentDoorState.CLOSED)
}
- });
+ })
diff --git a/src/accessories/Light-AdaptiveLighting_accessory.ts b/src/accessories/Light-AdaptiveLighting_accessory.ts
index 6888c970b..fe4fd3d6c 100644
--- a/src/accessories/Light-AdaptiveLighting_accessory.ts
+++ b/src/accessories/Light-AdaptiveLighting_accessory.ts
@@ -1,3 +1,6 @@
+/* eslint-disable no-console */
+import util from 'node:util'
+
import {
Accessory,
AdaptiveLightingController,
@@ -7,8 +10,7 @@ import {
ColorUtils,
Service,
uuid,
-} from "..";
-import util from "util";
+} from '../index.js'
/**
* This example light gives an example how a light with AdaptiveLighting (in AUTOMATIC mode) support
@@ -22,106 +24,106 @@ import util from "util";
* AdaptiveLighting setup is pretty much at the end of the file, don't miss it.
*/
-const lightUUID = uuid.generate("hap-nodejs:accessories:light-adaptive-lighting");
-const accessory = exports.accessory = new Accessory("Light Example", lightUUID);
+const lightUUID = uuid.generate('hap-nodejs:accessories:light-adaptive-lighting')
+const accessory = exports.accessory = new Accessory('Light Example', lightUUID)
// this section stores the basic state of the lightbulb
-let on = false;
-let brightness = 100;
-let colorTemperature = 140; // 140 is the lowest color temperature in mired as by the HAP spec (you can lower the minimum though)
-let hue = 0; // we start with white color
-let saturation = 0;
+let on = false
+let brightness = 100
+let colorTemperature = 140 // 140 is the lowest color temperature in mired as by the HAP spec (you can lower the minimum though)
+let hue = 0 // we start with white color
+let saturation = 0
// Add properties for publishing (in case we're using Core.js and not BridgedCore.js)
// @ts-expect-error: Core/BridgeCore API
-accessory.username = "AA:BB:CC:DD:EE:FF";
+accessory.username = 'AA:BB:CC:DD:EE:FF'
// @ts-expect-error: Core/BridgeCore API
-accessory.pincode = "031-45-154";
-accessory.category = Categories.LIGHTBULB;
+accessory.pincode = '031-45-154'
+accessory.category = Categories.LIGHTBULB
accessory.getService(Service.AccessoryInformation)!
- .setCharacteristic(Characteristic.Manufacturer, "HAP-NodeJS")
- .setCharacteristic(Characteristic.Model, "Light with AdaptiveLighting")
- .setCharacteristic(Characteristic.FirmwareRevision, "1.0.0");
+ .setCharacteristic(Characteristic.Manufacturer, 'HAP-NodeJS')
+ .setCharacteristic(Characteristic.Model, 'Light with AdaptiveLighting')
+ .setCharacteristic(Characteristic.FirmwareRevision, '1.0.0')
-const lightbulbService = accessory.addService(Service.Lightbulb, "Light Example");
+const lightbulbService = accessory.addService(Service.Lightbulb, 'Light Example')
lightbulbService.getCharacteristic(Characteristic.On)
.onGet(() => {
- console.log("Light power is currently " + on);
- return on;
+ console.log(`Light power is currently ${on}`)
+ return on
+ })
+ .onSet((value) => {
+ console.log(`Light power was turn to ${on}`)
+ on = value as boolean
})
- .onSet(value => {
- console.log("Light power was turn to " + on);
- on = value as boolean;
- });
lightbulbService.getCharacteristic(Characteristic.Brightness) // Brightness characteristic is required for adaptive lighting
.updateValue(brightness) // ensure default value is set
.onGet(() => {
- console.log("Light brightness is currently " + brightness);
- return brightness;
+ console.log(`Light brightness is currently ${brightness}`)
+ return brightness
+ })
+ .onSet((value) => {
+ console.log(`Light brightness was set to ${value}%`)
+ brightness = value as number
})
- .onSet(value => {
- console.log("Light brightness was set to " + value + "%");
- brightness = value as number;
- });
lightbulbService.getCharacteristic(Characteristic.ColorTemperature) // ColorTemperature characteristic is required for adaptive lighting
.onGet(() => {
- console.log("Light color temperature is currently " + colorTemperature);
- return colorTemperature;
+ console.log(`Light color temperature is currently ${colorTemperature}`)
+ return colorTemperature
})
- .onSet(value => {
- console.log("Light color temperature was set to " + value);
- colorTemperature = value as number;
+ .onSet((value) => {
+ console.log(`Light color temperature was set to ${value}`)
+ colorTemperature = value as number
// following statements are only needed when using ColorTemperature characteristic in combination with Hue/Saturation
- const color = ColorUtils.colorTemperatureToHueAndSaturation(colorTemperature);
+ const color = ColorUtils.colorTemperatureToHueAndSaturation(colorTemperature)
// save internal values for read handlers
- hue = color.hue;
- saturation = color.saturation;
+ hue = color.hue
+ saturation = color.saturation
// and notify HomeKit devices about changed values
- lightbulbService.getCharacteristic(Characteristic.Hue).updateValue(hue);
- lightbulbService.getCharacteristic(Characteristic.Saturation).updateValue(saturation);
- });
+ lightbulbService.getCharacteristic(Characteristic.Hue).updateValue(hue)
+ lightbulbService.getCharacteristic(Characteristic.Saturation).updateValue(saturation)
+ })
lightbulbService.getCharacteristic(Characteristic.Hue)
.onGet(() => {
- console.log("Light hue is currently " + hue);
- return hue;
+ console.log(`Light hue is currently ${hue}`)
+ return hue
+ })
+ .onSet((value) => {
+ console.log(`Light hue was set to ${value}`)
+ hue = value as number
+ colorTemperature = 140 // setting color temperature to lowest possible value
})
- .onSet(value => {
- console.log("Light hue was set to " + value);
- hue = value as number;
- colorTemperature = 140; // setting color temperature to lowest possible value
- });
lightbulbService.getCharacteristic(Characteristic.Saturation)
.onGet(() => {
- console.log("Light saturation is currently " + saturation);
- return saturation;
+ console.log(`Light saturation is currently ${saturation}`)
+ return saturation
+ })
+ .onSet((value) => {
+ console.log(`Light saturation was set to ${value}`)
+ saturation = value as number
+ colorTemperature = 140 // setting color temperature to lowest possible value
})
- .onSet(value => {
- console.log("Light saturation was set to " + value);
- saturation = value as number;
- colorTemperature = 140; // setting color temperature to lowest possible value
- });
const adaptiveLightingController = new AdaptiveLightingController(lightbulbService, {
// options object is optional, default mode is AUTOMATIC, can be set to MANUAL to do transitions yourself
// look into the docs for more information
controllerMode: AdaptiveLightingControllerMode.AUTOMATIC,
-});
+})
// Requires AdaptiveLightingControllerMode.MANUAL to be set as a controllerMode
-adaptiveLightingController.on("update", () => {
- console.log("Adaptive Lighting updated");
-}).on("update", (update) => {
- console.log("Adaptive Lighting schedule updated to " + util.inspect(update));
-}).on("disable", () => {
- console.log("Adaptive Lighting disabled");
-});
-
-accessory.configureController(adaptiveLightingController);
+adaptiveLightingController.on('update', () => {
+ console.log('Adaptive Lighting updated')
+}).on('update', (update) => {
+ console.log(`Adaptive Lighting schedule updated to ${util.inspect(update)}`)
+}).on('disable', () => {
+ console.log('Adaptive Lighting disabled')
+})
+
+accessory.configureController(adaptiveLightingController)
diff --git a/src/accessories/Light_accessory.ts b/src/accessories/Light_accessory.ts
index 9062f1487..2fc38f8c7 100644
--- a/src/accessories/Light_accessory.ts
+++ b/src/accessories/Light_accessory.ts
@@ -1,145 +1,148 @@
+/* eslint-disable no-console */
+import type {
+ CharacteristicSetCallback,
+ CharacteristicValue,
+ NodeCallback,
+ VoidCallback,
+} from '../index.js'
+
import {
Accessory,
AccessoryEventTypes,
Categories,
Characteristic,
CharacteristicEventTypes,
- CharacteristicSetCallback,
- CharacteristicValue,
- NodeCallback,
Service,
uuid,
- VoidCallback,
-} from "..";
+} from '../index.js'
class LightControllerClass {
-
- name = "Simple Light"; //name of accessory
- pincode: CharacteristicValue = "031-45-154";
- username: CharacteristicValue = "FA:3C:ED:5A:1A:1A"; // MAC like address used by HomeKit to differentiate accessories.
- manufacturer: CharacteristicValue = "HAP-NodeJS"; //manufacturer (optional)
- model: CharacteristicValue = "v1.0"; //model (optional)
- serialNumber: CharacteristicValue = "A12S345KGB"; //serial number (optional)
-
- power: CharacteristicValue = false; //current power status
- brightness: CharacteristicValue = 100; //current brightness
- hue: CharacteristicValue = 0; //current hue
- saturation: CharacteristicValue = 0; //current saturation
-
- outputLogs = true; //output logs
-
- setPower(status: CharacteristicValue) { //set power of accessory
- if(this.outputLogs) {
- console.log("Turning the '%s' %s", this.name, status ? "on" : "off");
+ name = 'Simple Light' // name of accessory
+ pincode: CharacteristicValue = '031-45-154'
+ username: CharacteristicValue = 'FA:3C:ED:5A:1A:1A' // MAC like address used by HomeKit to differentiate accessories.
+ manufacturer: CharacteristicValue = 'HAP-NodeJS' // manufacturer (optional)
+ model: CharacteristicValue = 'v1.0' // model (optional)
+ serialNumber: CharacteristicValue = 'A12S345KGB' // serial number (optional)
+
+ power: CharacteristicValue = false // current power status
+ brightness: CharacteristicValue = 100 // current brightness
+ hue: CharacteristicValue = 0 // current hue
+ saturation: CharacteristicValue = 0 // current saturation
+
+ outputLogs = true // output logs
+
+ setPower(status: CharacteristicValue) { // set power of accessory
+ if (this.outputLogs) {
+ console.log('Turning the \'%s\' %s', this.name, status ? 'on' : 'off')
}
- this.power = status;
+ this.power = status
}
- getPower() { //get power of accessory
- if(this.outputLogs) {
- console.log("'%s' is %s.", this.name, this.power ? "on" : "off");
+ getPower() { // get power of accessory
+ if (this.outputLogs) {
+ console.log('\'%s\' is %s.', this.name, this.power ? 'on' : 'off')
}
- return this.power;
+ return this.power
}
- setBrightness(brightness: CharacteristicValue) { //set brightness
- if(this.outputLogs) {
- console.log("Setting '%s' brightness to %s", this.name, brightness);
+ setBrightness(brightness: CharacteristicValue) { // set brightness
+ if (this.outputLogs) {
+ console.log('Setting \'%s\' brightness to %s', this.name, brightness)
}
- this.brightness = brightness;
+ this.brightness = brightness
}
- getBrightness() { //get brightness
- if(this.outputLogs) {
- console.log("'%s' brightness is %s", this.name, this.brightness);
+ getBrightness() { // get brightness
+ if (this.outputLogs) {
+ console.log('\'%s\' brightness is %s', this.name, this.brightness)
}
- return this.brightness;
+ return this.brightness
}
- setSaturation(saturation: CharacteristicValue) { //set brightness
- if(this.outputLogs) {
- console.log("Setting '%s' saturation to %s", this.name, saturation);
+ setSaturation(saturation: CharacteristicValue) { // set brightness
+ if (this.outputLogs) {
+ console.log('Setting \'%s\' saturation to %s', this.name, saturation)
}
- this.saturation = saturation;
+ this.saturation = saturation
}
- getSaturation() { //get brightness
- if(this.outputLogs) {
- console.log("'%s' saturation is %s", this.name, this.saturation);
+ getSaturation() { // get brightness
+ if (this.outputLogs) {
+ console.log('\'%s\' saturation is %s', this.name, this.saturation)
}
- return this.saturation;
+ return this.saturation
}
- setHue(hue: CharacteristicValue) { //set brightness
- if(this.outputLogs) {
- console.log("Setting '%s' hue to %s", this.name, hue);
+ setHue(hue: CharacteristicValue) { // set brightness
+ if (this.outputLogs) {
+ console.log('Setting \'%s\' hue to %s', this.name, hue)
}
- this.hue = hue;
+ this.hue = hue
}
- getHue() { //get hue
- if(this.outputLogs) {
- console.log("'%s' hue is %s", this.name, this.hue);
+ getHue() { // get hue
+ if (this.outputLogs) {
+ console.log('\'%s\' hue is %s', this.name, this.hue)
}
- return this.hue;
+ return this.hue
}
- identify() { //identify the accessory
- if(this.outputLogs) {
- console.log("Identify the '%s'", this.name);
+ identify() { // identify the accessory
+ if (this.outputLogs) {
+ console.log('Identify the \'%s\'', this.name)
}
}
}
-const LightController = new LightControllerClass();
+const LightController = new LightControllerClass()
// Generate a consistent UUID for our light Accessory that will remain the same even when
// restarting our server. We use the `uuid.generate` helper function to create a deterministic
// UUID based on an arbitrary "namespace" and the word "light".
-const lightUUID = uuid.generate("hap-nodejs:accessories:light" + LightController.name);
+const lightUUID = uuid.generate(`hap-nodejs:accessories:light${LightController.name}`)
// This is the Accessory that we'll return to HAP-NodeJS that represents our light.
-const lightAccessory = exports.accessory = new Accessory(LightController.name as string, lightUUID);
+const lightAccessory = exports.accessory = new Accessory(LightController.name as string, lightUUID)
// Add properties for publishing (in case we're using Core.js and not BridgedCore.js)
// @ts-expect-error: Core/BridgeCore API
-lightAccessory.username = LightController.username;
+lightAccessory.username = LightController.username
// @ts-expect-error: Core/BridgeCore API
-lightAccessory.pincode = LightController.pincode;
-lightAccessory.category = Categories.LIGHTBULB;
+lightAccessory.pincode = LightController.pincode
+lightAccessory.category = Categories.LIGHTBULB
// set some basic properties (these values are arbitrary and setting them is optional)
lightAccessory
.getService(Service.AccessoryInformation)!
.setCharacteristic(Characteristic.Manufacturer, LightController.manufacturer)
.setCharacteristic(Characteristic.Model, LightController.model)
- .setCharacteristic(Characteristic.SerialNumber, LightController.serialNumber);
+ .setCharacteristic(Characteristic.SerialNumber, LightController.serialNumber)
// listen for the "identify" event for this Accessory
lightAccessory.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => {
- LightController.identify();
- callback();
-});
+ LightController.identify()
+ callback()
+})
// services exposed to the user should have "names" like "Light" for this case
-const lightbulb = lightAccessory.addService(Service.Lightbulb, LightController.name);
+const lightbulb = lightAccessory.addService(Service.Lightbulb, LightController.name)
lightbulb.getCharacteristic(Characteristic.On)
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- LightController.setPower(value);
+ LightController.setPower(value)
// Our light is synchronous - this value has been successfully set
// Invoke the callback when you finished processing the request
// If it's going to take more than 1s to finish the request, try to invoke the callback
// after getting the request instead of after finishing it. This avoids blocking other
// requests from HomeKit.
- callback();
+ callback()
})
// We want to intercept requests for our current power state, so we can query the hardware itself instead of
// allowing HAP-NodeJS to return the cached Characteristic.value.
.on(CharacteristicEventTypes.GET, (callback: NodeCallback) => {
- callback(null, LightController.getPower());
- });
+ callback(null, LightController.getPower())
+ })
// To inform HomeKit about changes occurred outside of HomeKit (like user physically turn on the light)
// Please use Characteristic.updateValue
@@ -152,30 +155,29 @@ lightbulb.getCharacteristic(Characteristic.On)
// also add an "optional" Characteristic for Brightness
lightbulb.addCharacteristic(Characteristic.Brightness)
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- LightController.setBrightness(value);
- callback();
+ LightController.setBrightness(value)
+ callback()
})
.on(CharacteristicEventTypes.GET, (callback: NodeCallback) => {
- callback(null, LightController.getBrightness());
- });
-
+ callback(null, LightController.getBrightness())
+ })
// also add an "optional" Characteristic for Saturation
lightbulb.addCharacteristic(Characteristic.Saturation)
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- LightController.setSaturation(value);
- callback();
+ LightController.setSaturation(value)
+ callback()
})
.on(CharacteristicEventTypes.GET, (callback: NodeCallback) => {
- callback(null, LightController.getSaturation());
- });
+ callback(null, LightController.getSaturation())
+ })
// also add an "optional" Characteristic for Hue
lightbulb.addCharacteristic(Characteristic.Hue)
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- LightController.setHue(value);
- callback();
+ LightController.setHue(value)
+ callback()
})
.on(CharacteristicEventTypes.GET, (callback: NodeCallback) => {
- callback(null, LightController.getHue());
- });
+ callback(null, LightController.getHue())
+ })
diff --git a/src/accessories/Lock_accessory.ts b/src/accessories/Lock_accessory.ts
index 1764e3f85..3a44e5f72 100644
--- a/src/accessories/Lock_accessory.ts
+++ b/src/accessories/Lock_accessory.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
import {
Accessory,
AccessoryEventTypes,
@@ -6,89 +7,87 @@ import {
CharacteristicEventTypes,
Service,
uuid,
-} from "../";
+} from '../index.js'
// here's a fake hardware device that we'll expose to HomeKit
const FAKE_LOCK = {
locked: false,
lock: () => {
- console.log("Locking the lock!");
- FAKE_LOCK.locked = true;
+ console.log('Locking the lock!')
+ FAKE_LOCK.locked = true
},
unlock: () => {
- console.log("Unlocking the lock!");
- FAKE_LOCK.locked = false;
+ console.log('Unlocking the lock!')
+ FAKE_LOCK.locked = false
},
identify: () => {
- console.log("Identify the lock!");
+ console.log('Identify the lock!')
},
-};
+}
// Generate a consistent UUID for our Lock Accessory that will remain the same even when
// restarting our server. We use the `uuid.generate` helper function to create a deterministic
// UUID based on an arbitrary "namespace" and the word "lock".
-const lockUUID = uuid.generate("hap-nodejs:accessories:lock");
+const lockUUID = uuid.generate('hap-nodejs:accessories:lock')
// This is the Accessory that we'll return to HAP-NodeJS that represents our fake lock.
-const lock = exports.accessory = new Accessory("Lock", lockUUID);
+const lock = exports.accessory = new Accessory('Lock', lockUUID)
// Add properties for publishing (in case we're using Core.js and not BridgedCore.js)
// @ts-expect-error: Core/BridgeCore API
-lock.username = "C1:5D:3A:EE:5E:FA";
+lock.username = 'C1:5D:3A:EE:5E:FA'
// @ts-expect-error: Core/BridgeCore API
-lock.pincode = "031-45-154";
-lock.category = Categories.DOOR_LOCK;
+lock.pincode = '031-45-154'
+lock.category = Categories.DOOR_LOCK
// set some basic properties (these values are arbitrary and setting them is optional)
lock
.getService(Service.AccessoryInformation)!
- .setCharacteristic(Characteristic.Manufacturer, "Lock Manufacturer")
- .setCharacteristic(Characteristic.Model, "Rev-2")
- .setCharacteristic(Characteristic.SerialNumber, "MY-Serial-Number");
+ .setCharacteristic(Characteristic.Manufacturer, 'Lock Manufacturer')
+ .setCharacteristic(Characteristic.Model, 'Rev-2')
+ .setCharacteristic(Characteristic.SerialNumber, 'MY-Serial-Number')
// listen for the "identify" event for this Accessory
lock.on(AccessoryEventTypes.IDENTIFY, (paired, callback) => {
- FAKE_LOCK.identify();
- callback(); // success
-});
+ FAKE_LOCK.identify()
+ callback() // success
+})
-const service = new Service.LockMechanism("Fake Lock");
+const service = new Service.LockMechanism('Fake Lock')
// Add the actual Door Lock Service and listen for change events from iOS.
service.getCharacteristic(Characteristic.LockTargetState)
.on(CharacteristicEventTypes.SET, (value, callback) => {
-
if (value === Characteristic.LockTargetState.UNSECURED) {
- FAKE_LOCK.unlock();
- callback(); // Our fake Lock is synchronous - this value has been successfully set
+ FAKE_LOCK.unlock()
+ callback() // Our fake Lock is synchronous - this value has been successfully set
// now we want to set our lock's "actual state" to be unsecured so it shows as unlocked in iOS apps
- service.updateCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.UNSECURED);
+ service.updateCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.UNSECURED)
} else if (value === Characteristic.LockTargetState.SECURED) {
- FAKE_LOCK.lock();
- callback(); // Our fake Lock is synchronous - this value has been successfully set
+ FAKE_LOCK.lock()
+ callback() // Our fake Lock is synchronous - this value has been successfully set
// now we want to set our lock's "actual state" to be locked so it shows as open in iOS apps
- service.updateCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.SECURED);
+ service.updateCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.SECURED)
}
- });
+ })
// We want to intercept requests for our current state so we can query the hardware itself instead of
// allowing HAP-NodeJS to return the cached Characteristic.value.
service.getCharacteristic(Characteristic.LockCurrentState)
- .on(CharacteristicEventTypes.GET, callback => {
-
+ .on(CharacteristicEventTypes.GET, (callback) => {
// this event is emitted when you ask Siri directly whether your lock is locked or not. you might query
// the lock hardware itself to find this out, then call the callback. But if you take longer than a
// few seconds to respond, Siri will give up.
if (FAKE_LOCK.locked) {
- console.log("Are we locked? Yes.");
- callback(undefined, Characteristic.LockCurrentState.SECURED);
+ console.log('Are we locked? Yes.')
+ callback(undefined, Characteristic.LockCurrentState.SECURED)
} else {
- console.log("Are we locked? No.");
- callback(undefined, Characteristic.LockCurrentState.UNSECURED);
+ console.log('Are we locked? No.')
+ callback(undefined, Characteristic.LockCurrentState.UNSECURED)
}
- });
+ })
-lock.addService(service);
+lock.addService(service)
diff --git a/src/accessories/MotionSensor_accessory.ts b/src/accessories/MotionSensor_accessory.ts
index 1dd9f2796..d4a4c4d92 100644
--- a/src/accessories/MotionSensor_accessory.ts
+++ b/src/accessories/MotionSensor_accessory.ts
@@ -1,60 +1,65 @@
+/* eslint-disable no-console */
// here's a fake hardware device that we'll expose to HomeKit
+import type {
+ CharacteristicValue,
+ NodeCallback,
+ VoidCallback,
+} from '../index.js'
+
import {
Accessory,
AccessoryEventTypes,
Categories,
Characteristic,
CharacteristicEventTypes,
- CharacteristicValue,
- NodeCallback,
Service,
- uuid, VoidCallback,
-} from "..";
+ uuid,
+} from '../index.js'
const MOTION_SENSOR = {
motionDetected: false,
getStatus: () => {
- //set the boolean here, this will be returned to the device
- MOTION_SENSOR.motionDetected = false;
+ // set the boolean here, this will be returned to the device
+ MOTION_SENSOR.motionDetected = false
},
identify: () => {
- console.log("Identify the motion sensor!");
+ console.log('Identify the motion sensor!')
},
-};
+}
// Generate a consistent UUID for our Motion Sensor Accessory that will remain the same even when
// restarting our server. We use the `uuid.generate` helper function to create a deterministic
// UUID based on an arbitrary "namespace" and the word "motionsensor".
-const motionSensorUUID = uuid.generate("hap-nodejs:accessories:motionsensor");
+const motionSensorUUID = uuid.generate('hap-nodejs:accessories:motionsensor')
// This is the Accessory that we'll return to HAP-NodeJS that represents our fake motionSensor.
-const motionSensor = exports.accessory = new Accessory("Motion Sensor", motionSensorUUID);
+const motionSensor = exports.accessory = new Accessory('Motion Sensor', motionSensorUUID)
// Add properties for publishing (in case we're using Core.js and not BridgedCore.js)
// @ts-expect-error: Core/BridgeCore API
-motionSensor.username = "1A:2B:3D:4D:2E:AF";
+motionSensor.username = '1A:2B:3D:4D:2E:AF'
// @ts-expect-error: Core/BridgeCore API
-motionSensor.pincode = "031-45-154";
-motionSensor.category = Categories.SENSOR;
+motionSensor.pincode = '031-45-154'
+motionSensor.category = Categories.SENSOR
// set some basic properties (these values are arbitrary and setting them is optional)
motionSensor
.getService(Service.AccessoryInformation)!
- .setCharacteristic(Characteristic.Manufacturer, "Oltica")
- .setCharacteristic(Characteristic.Model, "Rev-1")
- .setCharacteristic(Characteristic.SerialNumber, "A1S2NASF88EW");
+ .setCharacteristic(Characteristic.Manufacturer, 'Oltica')
+ .setCharacteristic(Characteristic.Model, 'Rev-1')
+ .setCharacteristic(Characteristic.SerialNumber, 'A1S2NASF88EW')
// listen for the "identify" event for this Accessory
motionSensor.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => {
- MOTION_SENSOR.identify();
- callback(); // success
-});
+ MOTION_SENSOR.identify()
+ callback() // success
+})
motionSensor
- .addService(Service.MotionSensor, "Fake Motion Sensor") // services exposed to the user should have "names" like "Fake Motion Sensor" for us
+ .addService(Service.MotionSensor, 'Fake Motion Sensor') // services exposed to the user should have "names" like "Fake Motion Sensor" for us
.getCharacteristic(Characteristic.MotionDetected)!
.on(CharacteristicEventTypes.GET, (callback: NodeCallback) => {
- MOTION_SENSOR.getStatus();
- callback(null, Boolean(MOTION_SENSOR.motionDetected));
- });
+ MOTION_SENSOR.getStatus()
+ callback(null, Boolean(MOTION_SENSOR.motionDetected))
+ })
diff --git a/src/accessories/Outlet_accessory.ts b/src/accessories/Outlet_accessory.ts
index e9a371f02..f9763eb9e 100644
--- a/src/accessories/Outlet_accessory.ts
+++ b/src/accessories/Outlet_accessory.ts
@@ -1,78 +1,79 @@
+/* eslint-disable no-console */
+import type { CharacteristicSetCallback, CharacteristicValue, NodeCallback, VoidCallback } from '../index.js'
+import type { Nullable } from '../types'
+
import {
Accessory,
AccessoryEventTypes,
Categories,
Characteristic,
- CharacteristicEventTypes, CharacteristicSetCallback,
- CharacteristicValue, NodeCallback,
+ CharacteristicEventTypes,
Service,
uuid,
- VoidCallback,
-} from "..";
-import { Nullable } from "../types";
+} from '../index.js'
-const err: Nullable = null; // in case there were any problems
+const err: Nullable = null // in case there were any problems
// here's a fake hardware device that we'll expose to HomeKit
const FAKE_OUTLET = {
powerOn: false,
setPowerOn: (on: CharacteristicValue) => {
- console.log("Turning the outlet %s!...", on ? "on" : "off");
+ console.log('Turning the outlet %s!...', on ? 'on' : 'off')
if (on) {
- FAKE_OUTLET.powerOn = true;
+ FAKE_OUTLET.powerOn = true
if (err) {
- return console.log(err);
+ return console.log(err)
}
- console.log("...outlet is now on.");
+ console.log('...outlet is now on.')
} else {
- FAKE_OUTLET.powerOn = false;
+ FAKE_OUTLET.powerOn = false
if (err) {
- return console.log(err);
+ return console.log(err)
}
- console.log("...outlet is now off.");
+ console.log('...outlet is now off.')
}
},
- identify: function () {
- console.log("Identify the outlet.");
+ identify() {
+ console.log('Identify the outlet.')
},
-};
+}
// Generate a consistent UUID for our outlet Accessory that will remain the same even when
// restarting our server. We use the `uuid.generate` helper function to create a deterministic
// UUID based on an arbitrary "namespace" and the accessory name.
-const outletUUID = uuid.generate("hap-nodejs:accessories:Outlet");
+const outletUUID = uuid.generate('hap-nodejs:accessories:Outlet')
// This is the Accessory that we'll return to HAP-NodeJS that represents our fake light.
-const outlet = exports.accessory = new Accessory("Outlet", outletUUID);
+const outlet = exports.accessory = new Accessory('Outlet', outletUUID)
// Add properties for publishing (in case we're using Core.js and not BridgedCore.js)
// @ts-expect-error: Core/BridgeCore API
-outlet.username = "1A:2B:3C:4D:5D:FF";
+outlet.username = '1A:2B:3C:4D:5D:FF'
// @ts-expect-error: Core/BridgeCore API
-outlet.pincode = "031-45-154";
-outlet.category = Categories.OUTLET;
+outlet.pincode = '031-45-154'
+outlet.category = Categories.OUTLET
// set some basic properties (these values are arbitrary and setting them is optional)
outlet
.getService(Service.AccessoryInformation)!
- .setCharacteristic(Characteristic.Manufacturer, "Oltica")
- .setCharacteristic(Characteristic.Model, "Rev-1")
- .setCharacteristic(Characteristic.SerialNumber, "A1S2NASF88EW");
+ .setCharacteristic(Characteristic.Manufacturer, 'Oltica')
+ .setCharacteristic(Characteristic.Model, 'Rev-1')
+ .setCharacteristic(Characteristic.SerialNumber, 'A1S2NASF88EW')
// listen for the "identify" event for this Accessory
outlet.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => {
- FAKE_OUTLET.identify();
- callback(); // success
-});
+ FAKE_OUTLET.identify()
+ callback() // success
+})
// Add the actual outlet Service and listen for change events from iOS.
outlet
- .addService(Service.Outlet, "Fake Outlet") // services exposed to the user should have "names" like "Fake Light" for us
+ .addService(Service.Outlet, 'Fake Outlet') // services exposed to the user should have "names" like "Fake Light" for us
.getCharacteristic(Characteristic.On)!
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- FAKE_OUTLET.setPowerOn(value);
- callback(); // Our fake Outlet is synchronous - this value has been successfully set
- });
+ FAKE_OUTLET.setPowerOn(value)
+ callback() // Our fake Outlet is synchronous - this value has been successfully set
+ })
// We want to intercept requests for our current power state so we can query the hardware itself instead of
// allowing HAP-NodeJS to return the cached Characteristic.value.
@@ -80,18 +81,17 @@ outlet
.getService(Service.Outlet)!
.getCharacteristic(Characteristic.On)!
.on(CharacteristicEventTypes.GET, (callback: NodeCallback) => {
-
// this event is emitted when you ask Siri directly whether your light is on or not. you might query
// the light hardware itself to find this out, then call the callback. But if you take longer than a
// few seconds to respond, Siri will give up.
- const err = null; // in case there were any problems
+ const err = null // in case there were any problems
if (FAKE_OUTLET.powerOn) {
- console.log("Are we on? Yes.");
- callback(err, true);
+ console.log('Are we on? Yes.')
+ callback(err, true)
} else {
- console.log("Are we on? No.");
- callback(err, false);
+ console.log('Are we on? No.')
+ callback(err, false)
}
- });
+ })
diff --git a/src/accessories/SmartSpeaker_accessory.ts b/src/accessories/SmartSpeaker_accessory.ts
index ea49d602b..8bdf8405f 100644
--- a/src/accessories/SmartSpeaker_accessory.ts
+++ b/src/accessories/SmartSpeaker_accessory.ts
@@ -1,64 +1,62 @@
+/* eslint-disable no-console */
+import type { CharacteristicGetCallback, CharacteristicSetCallback, CharacteristicValue } from '../index.js'
+
import {
Accessory,
Categories,
Characteristic,
CharacteristicEventTypes,
- CharacteristicGetCallback,
- CharacteristicSetCallback,
- CharacteristicValue,
Service,
uuid,
-} from "..";
+} from '../index.js'
-const speakerUUID = uuid.generate("hap-nodejs:accessories:smart-speaker");
-const speaker = exports.accessory = new Accessory("SmartSpeaker", speakerUUID);
+const speakerUUID = uuid.generate('hap-nodejs:accessories:smart-speaker')
+const speaker = exports.accessory = new Accessory('SmartSpeaker', speakerUUID)
// @ts-expect-error: Core/BridgeCore API
-speaker.username = "89:A8:E4:1E:95:EE";
+speaker.username = '89:A8:E4:1E:95:EE'
// @ts-expect-error: Core/BridgeCore API
-speaker.pincode = "676-54-344";
-speaker.category = Categories.SPEAKER;
+speaker.pincode = '676-54-344'
+speaker.category = Categories.SPEAKER
-const service = new Service.SmartSpeaker("Smart Speaker", "");
+const service = new Service.SmartSpeaker('Smart Speaker', '')
-let currentMediaState: number = Characteristic.CurrentMediaState.PAUSE;
-let targetMediaState: number = Characteristic.TargetMediaState.PAUSE;
+let currentMediaState: number = Characteristic.CurrentMediaState.PAUSE
+let targetMediaState: number = Characteristic.TargetMediaState.PAUSE
// ConfigureName is used to listen for Name changes inside the Home App.
// A device manufacturer would probably need to adjust the name of the device in the AirPlay 2 protocol (or something)
-service.setCharacteristic(Characteristic.ConfiguredName, "Smart Speaker");
-service.setCharacteristic(Characteristic.Mute, false);
-service.setCharacteristic(Characteristic.Volume, 100);
+service.setCharacteristic(Characteristic.ConfiguredName, 'Smart Speaker')
+service.setCharacteristic(Characteristic.Mute, false)
+service.setCharacteristic(Characteristic.Volume, 100)
service.getCharacteristic(Characteristic.CurrentMediaState)!
.on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
- console.log("Reading CurrentMediaState: " + currentMediaState);
- callback(undefined, currentMediaState);
+ console.log(`Reading CurrentMediaState: ${currentMediaState}`)
+ callback(undefined, currentMediaState)
})
- .updateValue(currentMediaState); // init value
+ .updateValue(currentMediaState) // init value
service.getCharacteristic(Characteristic.TargetMediaState)!
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- console.log("Setting TargetMediaState to: " + value);
- targetMediaState = value as number;
- currentMediaState = targetMediaState;
+ console.log(`Setting TargetMediaState to: ${value}`)
+ targetMediaState = value as number
+ currentMediaState = targetMediaState
- callback();
+ callback()
- service.setCharacteristic(Characteristic.CurrentMediaState, targetMediaState);
+ service.setCharacteristic(Characteristic.CurrentMediaState, targetMediaState)
})
.on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
- console.log("Reading TargetMediaState: " + targetMediaState);
- callback(undefined, targetMediaState);
+ console.log(`Reading TargetMediaState: ${targetMediaState}`)
+ callback(undefined, targetMediaState)
})
- .updateValue(targetMediaState);
+ .updateValue(targetMediaState)
service.getCharacteristic(Characteristic.ConfiguredName)!
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- console.log(`Name was changed to: '${value}'`);
- callback();
- });
-
-speaker.addService(service);
-
+ console.log(`Name was changed to: '${value}'`)
+ callback()
+ })
+speaker.addService(service)
diff --git a/src/accessories/Sprinkler_accessory.ts b/src/accessories/Sprinkler_accessory.ts
index 69139e435..52913a7fa 100644
--- a/src/accessories/Sprinkler_accessory.ts
+++ b/src/accessories/Sprinkler_accessory.ts
@@ -1,52 +1,51 @@
+/* eslint-disable no-console */
// here's a fake hardware device that we'll expose to HomeKit
+import type { CharacteristicSetCallback, CharacteristicValue, NodeCallback } from '../index.js'
+
import {
Accessory,
Categories,
Characteristic,
CharacteristicEventTypes,
- CharacteristicSetCallback,
- CharacteristicValue,
- NodeCallback,
Service,
uuid,
-} from "..";
+} from '../index.js'
const SPRINKLER = {
active: false,
- name: "Garten Hinten",
+ name: 'Garten Hinten',
timerEnd: 0,
defaultDuration: 3600,
motionDetected: false,
getStatus: () => {
- //set the boolean here, this will be returned to the device
- SPRINKLER.motionDetected = false;
+ // set the boolean here, this will be returned to the device
+ SPRINKLER.motionDetected = false
},
identify: () => {
- console.log("Identify the sprinkler!");
+ console.log('Identify the sprinkler!')
},
-};
-
+}
// Generate a consistent UUID for our Motion Sensor Accessory that will remain the same even when
// restarting our server. We use the `uuid.generate` helper function to create a deterministic
// UUID based on an arbitrary "namespace" and the word "motionsensor".
-const sprinklerUUID = uuid.generate("hap-nodejs:accessories:sprinkler");
+const sprinklerUUID = uuid.generate('hap-nodejs:accessories:sprinkler')
// This is the Accessory that we'll return to HAP-NodeJS that represents our fake motionSensor.
-const sprinkler = exports.accessory = new Accessory("Sprinkler", sprinklerUUID);
+const sprinkler = exports.accessory = new Accessory('Sprinkler', sprinklerUUID)
// Add properties for publishing (in case we're using Core.js and not BridgedCore.js)
// @ts-expect-error: Core/BridgeCore API
-sprinkler.username = "A3:AB:3D:4D:2E:A3";
+sprinkler.username = 'A3:AB:3D:4D:2E:A3'
// @ts-expect-error: Core/BridgeCore API
-sprinkler.pincode = "123-44-567";
-sprinkler.category = Categories.SPRINKLER;
+sprinkler.pincode = '123-44-567'
+sprinkler.category = Categories.SPRINKLER
// Add the actual Valve Service and listen for change events from iOS.
-const sprinklerService = sprinkler.addService(Service.Valve, "Sprinkler");
+const sprinklerService = sprinkler.addService(Service.Valve, 'Sprinkler')
-// Sprinkler Controll
+// Sprinkler Control
function openVentile() {
// Add your code here
}
@@ -57,106 +56,98 @@ function closeVentile() {
// set some basic properties (these values are arbitrary and setting them is optional)
sprinklerService
- .setCharacteristic(Characteristic.ValveType, "1") // IRRIGATION/SPRINKLER = 1; SHOWER_HEAD = 2; WATER_FAUCET = 3;
- .setCharacteristic(Characteristic.Name, SPRINKLER.name);
+ .setCharacteristic(Characteristic.ValveType, '1') // IRRIGATION/SPRINKLER = 1; SHOWER_HEAD = 2; WATER_FAUCET = 3;
+ .setCharacteristic(Characteristic.Name, SPRINKLER.name)
sprinklerService
.getCharacteristic(Characteristic.Active)!
.on(CharacteristicEventTypes.GET, (callback: NodeCallback) => {
-
- console.log("get Active");
- const err = null; // in case there were any problems
+ console.log('get Active')
+ const err = null // in case there were any problems
if (SPRINKLER.active) {
- callback(err, true);
+ callback(err, true)
} else {
- callback(err, false);
+ callback(err, false)
}
})
.on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => {
-
- console.log("set Active => setNewValue: " + newValue);
+ console.log(`set Active => setNewValue: ${newValue}`)
if (SPRINKLER.active) {
- SPRINKLER.active = false;
- closeVentile();
+ SPRINKLER.active = false
+ closeVentile()
setTimeout(() => {
- console.log("Ausgeschaltet");
- SPRINKLER.timerEnd = SPRINKLER.defaultDuration + Math.floor(new Date().getTime() / 1000);
- callback(null);
+ console.log('Ausgeschaltet')
+ SPRINKLER.timerEnd = SPRINKLER.defaultDuration + Math.floor(new Date().getTime() / 1000)
+ callback(null)
sprinkler
.getService(Service.Valve)!
- .setCharacteristic(Characteristic.SetDuration, 0);
+ .setCharacteristic(Characteristic.SetDuration, 0)
sprinkler
.getService(Service.Valve)!
- .setCharacteristic(Characteristic.InUse, 0);
-
- }, 1000);
+ .setCharacteristic(Characteristic.InUse, 0)
+ }, 1000)
} else {
- SPRINKLER.active = true;
- openVentile();
+ SPRINKLER.active = true
+ openVentile()
setTimeout(() => {
- console.log("Eingeschaltet");
- SPRINKLER.timerEnd = SPRINKLER.defaultDuration + Math.floor(new Date().getTime() / 1000);
- callback(null, SPRINKLER.defaultDuration);
+ console.log('Eingeschaltet')
+ SPRINKLER.timerEnd = SPRINKLER.defaultDuration + Math.floor(new Date().getTime() / 1000)
+ callback(null, SPRINKLER.defaultDuration)
sprinkler
.getService(Service.Valve)!
- .setCharacteristic(Characteristic.InUse, 1);
+ .setCharacteristic(Characteristic.InUse, 1)
sprinkler
.getService(Service.Valve)!
- .setCharacteristic(Characteristic.RemainingDuration, SPRINKLER.defaultDuration);
+ .setCharacteristic(Characteristic.RemainingDuration, SPRINKLER.defaultDuration)
sprinkler
.getService(Service.Valve)!
- .setCharacteristic(Characteristic.SetDuration, SPRINKLER.defaultDuration);
-
- }, 1000);
+ .setCharacteristic(Characteristic.SetDuration, SPRINKLER.defaultDuration)
+ }, 1000)
}
- });
-
+ })
sprinklerService
.getCharacteristic(Characteristic.InUse)!
.on(CharacteristicEventTypes.GET, (callback: NodeCallback) => {
- console.log("get In_Use");
- const err = null; // in case there were any problems
+ console.log('get In_Use')
+ const err = null // in case there were any problems
if (SPRINKLER.active) {
- callback(err, true);
+ callback(err, true)
} else {
- callback(err, false);
+ callback(err, false)
}
})
.on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => {
- console.log("set In_Use => NewValue: " + newValue);
- callback();
- });
-
+ console.log(`set In_Use => NewValue: ${newValue}`)
+ callback()
+ })
sprinklerService
.getCharacteristic(Characteristic.RemainingDuration)!
.on(CharacteristicEventTypes.GET, (callback: NodeCallback) => {
-
- const err = null; // in case there were any problems
+ const err = null // in case there were any problems
if (SPRINKLER.active) {
-
- const duration = SPRINKLER.timerEnd - Math.floor(new Date().getTime() / 1000);
- console.log("RemainingDuration: " + duration);
- callback(err, duration);
+ const duration = SPRINKLER.timerEnd - Math.floor(new Date().getTime() / 1000)
+ console.log(`RemainingDuration: ${duration}`)
+ callback(err, duration)
} else {
- callback(err, 0);
+ callback(err, 0)
}
- });
+ })
sprinklerService
.getCharacteristic(Characteristic.SetDuration)!
.on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => {
- console.log("SetDuration => NewValue: " + newValue);
- SPRINKLER.defaultDuration = newValue as number;
- callback();
- });
+ console.log(`SetDuration => NewValue: ${newValue}`)
+ SPRINKLER.defaultDuration = newValue as number
+ callback()
+ })
diff --git a/src/accessories/TV_accessory.ts b/src/accessories/TV_accessory.ts
index 7fdf2ccc6..af9ebd6a3 100644
--- a/src/accessories/TV_accessory.ts
+++ b/src/accessories/TV_accessory.ts
@@ -1,140 +1,140 @@
+/* eslint-disable no-console */
+import type { AccessLevel, CharacteristicSetCallback, CharacteristicValue } from '../index.js'
+
import {
AccessControlEvent,
AccessControlManagement,
- AccessLevel,
Accessory,
Categories,
Characteristic,
CharacteristicEventTypes,
- CharacteristicSetCallback,
- CharacteristicValue,
Service,
uuid,
-} from "..";
+} from '../index.js'
// Generate a consistent UUID for TV that will remain the same even when
// restarting our server. We use the `uuid.generate` helper function to create a deterministic
// UUID based on an arbitrary "namespace" and the word "tv".
-const tvUUID = uuid.generate("hap-nodejs:accessories:tv");
+const tvUUID = uuid.generate('hap-nodejs:accessories:tv')
// This is the Accessory that we'll return to HAP-NodeJS.
-const tv = exports.accessory = new Accessory("TV", tvUUID);
+const tv = exports.accessory = new Accessory('TV', tvUUID)
// Add properties for publishing (in case we're using Core.js and not BridgedCore.js)
// @ts-expect-error: Core/BridgeCore API
-tv.username = "A3:FB:3D:4D:2E:AC";
+tv.username = 'A3:FB:3D:4D:2E:AC'
// @ts-expect-error: Core/BridgeCore API
-tv.pincode = "031-45-154";
-tv.category = Categories.TELEVISION;
+tv.pincode = '031-45-154'
+tv.category = Categories.TELEVISION
// Add the actual TV Service and listen for change events from iOS.
-const televisionService = tv.addService(Service.Television, "Television", "Television");
+const televisionService = tv.addService(Service.Television, 'Television', 'Television')
televisionService
- .setCharacteristic(Characteristic.ConfiguredName, "Television");
+ .setCharacteristic(Characteristic.ConfiguredName, 'Television')
televisionService
.setCharacteristic(
Characteristic.SleepDiscoveryMode,
Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE,
- );
+ )
televisionService
.getCharacteristic(Characteristic.Active)!
.on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => {
- console.log("set Active => setNewValue: " + newValue);
- callback(null);
- });
+ console.log(`set Active => setNewValue: ${newValue}`)
+ callback(null)
+ })
televisionService
- .setCharacteristic(Characteristic.ActiveIdentifier, 1);
+ .setCharacteristic(Characteristic.ActiveIdentifier, 1)
televisionService
.getCharacteristic(Characteristic.ActiveIdentifier)!
.on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => {
- console.log("set Active Identifier => setNewValue: " + newValue);
- callback(null);
- });
+ console.log(`set Active Identifier => setNewValue: ${newValue}`)
+ callback(null)
+ })
televisionService
.getCharacteristic(Characteristic.RemoteKey)!
.on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => {
- console.log("set Remote Key => setNewValue: " + newValue);
- callback(null);
- });
+ console.log(`set Remote Key => setNewValue: ${newValue}`)
+ callback(null)
+ })
televisionService
.getCharacteristic(Characteristic.PictureMode)!
.on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => {
- console.log("set PictureMode => setNewValue: " + newValue);
- callback(null);
- });
+ console.log(`set PictureMode => setNewValue: ${newValue}`)
+ callback(null)
+ })
televisionService
.getCharacteristic(Characteristic.PowerModeSelection)!
.on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => {
- console.log("set PowerModeSelection => setNewValue: " + newValue);
- callback(null);
- });
+ console.log(`set PowerModeSelection => setNewValue: ${newValue}`)
+ callback(null)
+ })
// Speaker
-const speakerService = tv.addService(Service.TelevisionSpeaker);
+const speakerService = tv.addService(Service.TelevisionSpeaker)
speakerService
.setCharacteristic(Characteristic.Active, Characteristic.Active.ACTIVE)
- .setCharacteristic(Characteristic.VolumeControlType, Characteristic.VolumeControlType.ABSOLUTE);
+ .setCharacteristic(Characteristic.VolumeControlType, Characteristic.VolumeControlType.ABSOLUTE)
speakerService.getCharacteristic(Characteristic.VolumeSelector)!
.on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => {
- console.log("set VolumeSelector => setNewValue: " + newValue);
- callback(null);
- });
+ console.log(`set VolumeSelector => setNewValue: ${newValue}`)
+ callback(null)
+ })
// HDMI 1
-const inputHDMI1 = tv.addService(Service.InputSource, "hdmi1", "HDMI 1");
+const inputHDMI1 = tv.addService(Service.InputSource, 'hdmi1', 'HDMI 1')
inputHDMI1
.setCharacteristic(Characteristic.Identifier, 1)
- .setCharacteristic(Characteristic.ConfiguredName, "HDMI 1")
+ .setCharacteristic(Characteristic.ConfiguredName, 'HDMI 1')
.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED)
- .setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.HDMI);
+ .setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.HDMI)
// HDMI 2
-const inputHDMI2 = tv.addService(Service.InputSource, "hdmi2", "HDMI 2");
+const inputHDMI2 = tv.addService(Service.InputSource, 'hdmi2', 'HDMI 2')
inputHDMI2
.setCharacteristic(Characteristic.Identifier, 2)
- .setCharacteristic(Characteristic.ConfiguredName, "HDMI 2")
+ .setCharacteristic(Characteristic.ConfiguredName, 'HDMI 2')
.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED)
- .setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.HDMI);
+ .setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.HDMI)
// Netflix
-const inputNetflix = tv.addService(Service.InputSource, "netflix", "Netflix");
+const inputNetflix = tv.addService(Service.InputSource, 'netflix', 'Netflix')
inputNetflix
.setCharacteristic(Characteristic.Identifier, 3)
- .setCharacteristic(Characteristic.ConfiguredName, "Netflix")
+ .setCharacteristic(Characteristic.ConfiguredName, 'Netflix')
.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED)
- .setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.APPLICATION);
+ .setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.APPLICATION)
-televisionService.addLinkedService(inputHDMI1);
-televisionService.addLinkedService(inputHDMI2);
-televisionService.addLinkedService(inputNetflix);
+televisionService.addLinkedService(inputHDMI1)
+televisionService.addLinkedService(inputHDMI2)
+televisionService.addLinkedService(inputNetflix)
-const accessControl = new AccessControlManagement(true);
+const accessControl = new AccessControlManagement(true)
accessControl.on(AccessControlEvent.ACCESS_LEVEL_UPDATED, (level: AccessLevel) => {
- console.log("New access control level of " + level);
-});
+ console.log(`New access control level of ${level}`)
+})
accessControl.on(AccessControlEvent.PASSWORD_SETTING_UPDATED, (password: string | undefined, passwordRequired: boolean) => {
if (passwordRequired) {
- console.log("A required password was specified");
+ console.log('A required password was specified')
} else {
- console.log("No password set!");
+ console.log('No password set!')
}
-});
+})
-tv.addService(accessControl.getService());
+tv.addService(accessControl.getService())
diff --git a/src/accessories/TemperatureSensor_accessory.ts b/src/accessories/TemperatureSensor_accessory.ts
index 6c4cdca61..6f0876ff5 100644
--- a/src/accessories/TemperatureSensor_accessory.ts
+++ b/src/accessories/TemperatureSensor_accessory.ts
@@ -1,52 +1,51 @@
+/* eslint-disable no-console */
// here's a fake temperature sensor device that we'll expose to HomeKit
-import { Accessory, Categories, Characteristic, CharacteristicEventTypes, CharacteristicValue, NodeCallback, Service, uuid } from "..";
+import type { CharacteristicValue, NodeCallback } from '../index.js'
+
+import { Accessory, Categories, Characteristic, CharacteristicEventTypes, Service, uuid } from '../index.js'
const FAKE_SENSOR = {
currentTemperature: 50,
- getTemperature: function () {
- console.log("Getting the current temperature!");
- return FAKE_SENSOR.currentTemperature;
+ getTemperature() {
+ console.log('Getting the current temperature!')
+ return FAKE_SENSOR.currentTemperature
},
- randomizeTemperature: function () {
+ randomizeTemperature() {
// randomize temperature to a value between 0 and 100
- FAKE_SENSOR.currentTemperature = Math.round(Math.random() * 100);
+ FAKE_SENSOR.currentTemperature = Math.round(Math.random() * 100)
},
-};
-
+}
// Generate a consistent UUID for our Temperature Sensor Accessory that will remain the same
// even when restarting our server. We use the `uuid.generate` helper function to create
// a deterministic UUID based on an arbitrary "namespace" and the string "temperature-sensor".
-const sensorUUID = uuid.generate("hap-nodejs:accessories:temperature-sensor");
+const sensorUUID = uuid.generate('hap-nodejs:accessories:temperature-sensor')
// This is the Accessory that we'll return to HAP-NodeJS that represents our fake lock.
-const sensor = exports.accessory = new Accessory("Temperature Sensor", sensorUUID);
+const sensor = exports.accessory = new Accessory('Temperature Sensor', sensorUUID)
// Add properties for publishing (in case we're using Core.js and not BridgedCore.js)
// @ts-expect-error: Core/BridgeCore API
-sensor.username = "C1:5D:3A:AE:5E:FA";
+sensor.username = 'C1:5D:3A:AE:5E:FA'
// @ts-expect-error: Core/BridgeCore API
-sensor.pincode = "031-45-154";
-sensor.category = Categories.SENSOR;
+sensor.pincode = '031-45-154'
+sensor.category = Categories.SENSOR
// Add the actual TemperatureSensor Service.
sensor
.addService(Service.TemperatureSensor)!
.getCharacteristic(Characteristic.CurrentTemperature)!
.on(CharacteristicEventTypes.GET, (callback: NodeCallback) => {
-
// return our current value
- callback(null, FAKE_SENSOR.getTemperature());
- });
+ callback(null, FAKE_SENSOR.getTemperature())
+ })
// randomize our temperature reading every 3 seconds
setInterval(() => {
-
- FAKE_SENSOR.randomizeTemperature();
+ FAKE_SENSOR.randomizeTemperature()
// update the characteristic value so interested iOS devices can get notified
sensor
.getService(Service.TemperatureSensor)!
- .setCharacteristic(Characteristic.CurrentTemperature, FAKE_SENSOR.currentTemperature);
-
-}, 3000);
+ .setCharacteristic(Characteristic.CurrentTemperature, FAKE_SENSOR.currentTemperature)
+}, 3000)
diff --git a/src/accessories/Wi-FiRouter_accessory.ts b/src/accessories/Wi-FiRouter_accessory.ts
index cd8290336..cb252a963 100644
--- a/src/accessories/Wi-FiRouter_accessory.ts
+++ b/src/accessories/Wi-FiRouter_accessory.ts
@@ -1,17 +1,20 @@
-import { Accessory, AccessoryEventTypes, Categories, Service, uuid, VoidCallback } from "..";
+/* eslint-disable no-console */
+import type { VoidCallback } from '../index.js'
-const UUID = uuid.generate("hap-nodejs:accessories:wifi-router");
-export const accessory = new Accessory("Wi-Fi Router", UUID);
+import { Accessory, AccessoryEventTypes, Categories, Service, uuid } from '../index.js'
+
+const UUID = uuid.generate('hap-nodejs:accessories:wifi-router')
+export const accessory = new Accessory('Wi-Fi Router', UUID)
// @ts-expect-error: Core/BridgeCore API
-accessory.username = "FA:3C:ED:D2:1A:A2";
+accessory.username = 'FA:3C:ED:D2:1A:A2'
// @ts-expect-error: Core/BridgeCore API
-accessory.pincode = "031-45-154";
-accessory.category = Categories.ROUTER;
+accessory.pincode = '031-45-154'
+accessory.category = Categories.ROUTER
accessory.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => {
- console.log("Identify the '%s'", accessory.displayName);
- callback();
-});
+ console.log('Identify the \'%s\'', accessory.displayName)
+ callback()
+})
-accessory.addService(Service.WiFiRouter);
+accessory.addService(Service.WiFiRouter)
diff --git a/src/accessories/Wi-FiSatellite_accessory.ts b/src/accessories/Wi-FiSatellite_accessory.ts
index 7d91783ca..6a5efab16 100644
--- a/src/accessories/Wi-FiSatellite_accessory.ts
+++ b/src/accessories/Wi-FiSatellite_accessory.ts
@@ -1,20 +1,23 @@
-import { Accessory, AccessoryEventTypes, Categories, Characteristic, Service, uuid, VoidCallback } from "..";
+/* eslint-disable no-console */
+import type { VoidCallback } from '../index.js'
-const UUID = uuid.generate("hap-nodejs:accessories:wifi-satellite");
-export const accessory = new Accessory("Wi-Fi Satellite", UUID);
+import { Accessory, AccessoryEventTypes, Categories, Characteristic, Service, uuid } from '../index.js'
+
+const UUID = uuid.generate('hap-nodejs:accessories:wifi-satellite')
+export const accessory = new Accessory('Wi-Fi Satellite', UUID)
// @ts-expect-error: Core/BridgeCore API
-accessory.username = "FA:3C:ED:5A:1A:A2";
+accessory.username = 'FA:3C:ED:5A:1A:A2'
// @ts-expect-error: Core/BridgeCore API
-accessory.pincode = "031-45-154";
-accessory.category = Categories.ROUTER;
+accessory.pincode = '031-45-154'
+accessory.category = Categories.ROUTER
accessory.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => {
- console.log("Identify the '%s'", accessory.displayName);
- callback();
-});
+ console.log('Identify the \'%s\'', accessory.displayName)
+ callback()
+})
-const satellite = accessory.addService(Service.WiFiSatellite);
+const satellite = accessory.addService(Service.WiFiSatellite)
satellite.getCharacteristic(Characteristic.WiFiSatelliteStatus)!
- .updateValue(Characteristic.WiFiSatelliteStatus.CONNECTED);
+ .updateValue(Characteristic.WiFiSatelliteStatus.CONNECTED)
diff --git a/src/accessories/gstreamer-audioProducer.ts b/src/accessories/gstreamer-audioProducer.ts
index 2c5e46952..91fa1d66e 100644
--- a/src/accessories/gstreamer-audioProducer.ts
+++ b/src/accessories/gstreamer-audioProducer.ts
@@ -1,40 +1,52 @@
-import assert from "assert";
-import { ChildProcess, spawn } from "child_process";
-import createDebug from "debug";
+import type { Buffer } from 'node:buffer'
+import type { ChildProcess } from 'node:child_process'
+
+import type {
+ AudioCodecConfiguration,
+ ErrorHandler,
+ FrameHandler,
+ SiriAudioStreamProducer,
+} from '../index.js'
+
+import assert from 'node:assert'
+import { spawn } from 'node:child_process'
+import process from 'node:process'
+
+import createDebug from 'debug'
+
import {
AudioBitrate,
- AudioCodecConfiguration,
AudioCodecTypes,
AudioSamplerate,
- ErrorHandler,
- FrameHandler,
HDSProtocolSpecificErrorReason,
- SiriAudioStreamProducer,
-} from "..";
+} from '../index.js'
-const debug = createDebug("HAP-NodeJS:Remote:GStreamer");
+const debug = createDebug('HAP-NodeJS:Remote:GStreamer')
+// eslint-disable-next-line no-restricted-syntax
const enum AudioType {
- GENERIC = 2049,
- VOICE = 2048
+ GENERIC = 2049,
+ VOICE = 2048,
}
+// eslint-disable-next-line no-restricted-syntax
const enum Bandwidth {
- NARROW_BAND = 1101,
- MEDIUM_BAND = 1102,
- WIDE_BAND = 1103,
- SUPER_WIDE_BAND = 1104,
- FULL_BAND = 1105,
- AUTO = -1000
+ NARROW_BAND = 1101,
+ MEDIUM_BAND = 1102,
+ WIDE_BAND = 1103,
+ SUPER_WIDE_BAND = 1104,
+ FULL_BAND = 1105,
+ AUTO = -1000,
}
+// eslint-disable-next-line no-restricted-syntax
const enum BitrateType {
- CONSTANT = 0,
- VARIABLE = 1,
+ CONSTANT = 0,
+ VARIABLE = 1,
}
-export type GStreamerOptions = {
- alsaSrc: string,
+export interface GStreamerOptions {
+ alsaSrc: string
}
/**
@@ -48,89 +60,88 @@ export type GStreamerOptions = {
*
*/
export class GStreamerAudioProducer implements SiriAudioStreamProducer {
-
private readonly options: GStreamerOptions = {
- alsaSrc: "plughw:1",
- };
+ alsaSrc: 'plughw:1',
+ }
- private readonly frameHandler: FrameHandler;
- private readonly errorHandler: ErrorHandler;
+ private readonly frameHandler: FrameHandler
+ private readonly errorHandler: ErrorHandler
- private process?: ChildProcess;
- private running = false;
+ private process?: ChildProcess
+ private running = false
constructor(frameHandler: FrameHandler, errorHandler: ErrorHandler, options?: Partial) {
- this.frameHandler = frameHandler;
- this.errorHandler = errorHandler;
+ this.frameHandler = frameHandler
+ this.errorHandler = errorHandler
if (options) {
- for (const [ key, value ] of Object.entries(options)) {
+ for (const [key, value] of Object.entries(options)) {
// @ts-expect-error: type mismatch
- GStreamerAudioProducer.options[key] = value;
+ GStreamerAudioProducer.options[key] = value
}
}
}
startAudioProduction(selectedAudioConfiguration: AudioCodecConfiguration): void {
if (this.running) {
- throw new Error("Gstreamer already running");
+ throw new Error('Gstreamer already running')
}
- const codecParameters = selectedAudioConfiguration.parameters;
- assert(selectedAudioConfiguration.codecType === AudioCodecTypes.OPUS);
+ const codecParameters = selectedAudioConfiguration.parameters
+ assert(selectedAudioConfiguration.codecType === AudioCodecTypes.OPUS)
- let bitrateType = BitrateType.VARIABLE;
+ let bitrateType = BitrateType.VARIABLE
switch (codecParameters.bitrate) {
- case AudioBitrate.CONSTANT:
- bitrateType = BitrateType.CONSTANT;
- break;
- case AudioBitrate.VARIABLE:
- bitrateType = BitrateType.VARIABLE;
- break;
+ case AudioBitrate.CONSTANT:
+ bitrateType = BitrateType.CONSTANT
+ break
+ case AudioBitrate.VARIABLE:
+ bitrateType = BitrateType.VARIABLE
+ break
}
- let bandwidth = Bandwidth.SUPER_WIDE_BAND;
+ let bandwidth = Bandwidth.SUPER_WIDE_BAND
switch (codecParameters.samplerate) {
- case AudioSamplerate.KHZ_8:
- bandwidth = Bandwidth.NARROW_BAND;
- break;
- case AudioSamplerate.KHZ_16:
- bandwidth = Bandwidth.WIDE_BAND;
- break;
- case AudioSamplerate.KHZ_24:
- bandwidth = Bandwidth.SUPER_WIDE_BAND;
- break;
+ case AudioSamplerate.KHZ_8:
+ bandwidth = Bandwidth.NARROW_BAND
+ break
+ case AudioSamplerate.KHZ_16:
+ bandwidth = Bandwidth.WIDE_BAND
+ break
+ case AudioSamplerate.KHZ_24:
+ bandwidth = Bandwidth.SUPER_WIDE_BAND
+ break
}
- const packetTime = codecParameters.rtpTime;
-
- debug("Launching gstreamer...");
- this.running = true;
-
- const args = "-q " +
- "alsasrc device=" + this.options.alsaSrc + " ! " +
- "capsfilter caps=audio/x-raw,format=S16LE,rate=24000 ! " +
- // "level post-messages=true interval=" + packetTime + "000000 ! " + // used to capture rms
- "opusenc " +
- "bitrate-type=" + bitrateType + " " +
- "bitrate=24000 " +
- "audio-type=" + AudioType.VOICE + " " +
- "bandwidth=" + bandwidth + " " +
- "frame-size=" + packetTime + " ! " +
- "fdsink fd=1";
-
- this.process = spawn("gst-launch-1.0", args.split(" "), { env: process.env });
- this.process.on("error", error => {
+ const packetTime = codecParameters.rtpTime
+
+ debug('Launching gstreamer...')
+ this.running = true
+
+ const args = `-q `
+ + `alsasrc device=${this.options.alsaSrc} ! `
+ + `capsfilter caps=audio/x-raw,format=S16LE,rate=24000 ! `
+ // "level post-messages=true interval=" + packetTime + "000000 ! " + // used to capture rms
+ + `opusenc `
+ + `bitrate-type=${bitrateType} `
+ + `bitrate=24000 `
+ + `audio-type=${AudioType.VOICE} `
+ + `bandwidth=${bandwidth} `
+ + `frame-size=${packetTime} ! `
+ + `fdsink fd=1`
+
+ this.process = spawn('gst-launch-1.0', args.split(' '), { env: process.env })
+ this.process.on('error', (error) => {
if (this.running) {
- debug("Failed to spawn gstreamer process: " + error.message);
- this.errorHandler(HDSProtocolSpecificErrorReason.CANCELLED);
+ debug(`Failed to spawn gstreamer process: ${error.message}`)
+ this.errorHandler(HDSProtocolSpecificErrorReason.CANCELLED)
} else {
- debug("Failed to kill gstreamer process: " + error.message);
+ debug(`Failed to kill gstreamer process: ${error.message}`)
}
- });
- this.process.stdout?.on("data", (data: Buffer) => {
+ })
+ this.process.stdout?.on('data', (data: Buffer) => {
if (!this.running) { // received data after it was closed
- return;
+ return
}
/*
@@ -140,33 +151,32 @@ export class GStreamerAudioProducer implements SiriAudioStreamProducer {
Opus relies on the container format to specify the length of the frame.
Although sometimes multiple opus frames are squashed together the decoder seems to be able
to handle that as it just creates a not very noticeable distortion.
- If we wanted to make this perfect we would need to write a nodejs c++ submodule or something
+ If we wanted to make this perfect we would need to write a Node.js c++ submodule or something
to interface directly with gstreamer api.
*/
this.frameHandler({
- data: data,
- rms: 0.25, // only way currently to extract rms from gstreamer is by interfacing with the api directly (nodejs c++ submodule could be a solution)
- });
- });
- this.process.stderr?.on("data", data => {
- debug("GStreamer process reports the following error: " + String(data));
- });
- this.process.on("exit", (code, signal) => {
- if (signal !== "SIGTERM") { // if we receive SIGTERM, process exited gracefully (we stopped it)
- debug("GStreamer process unexpectedly exited with code %d (signal: %s)", code, signal);
- this.errorHandler(HDSProtocolSpecificErrorReason.UNEXPECTED_FAILURE);
+ data,
+ rms: 0.25, // only way currently to extract rms from gstreamer is by interfacing with the api directly (Node.js c++ submodule could be a solution)
+ })
+ })
+ this.process.stderr?.on('data', (data) => {
+ debug(`GStreamer process reports the following error: ${String(data)}`)
+ })
+ this.process.on('exit', (code, signal) => {
+ if (signal !== 'SIGTERM') { // if we receive SIGTERM, process exited gracefully (we stopped it)
+ debug('GStreamer process unexpectedly exited with code %d (signal: %s)', code, signal)
+ this.errorHandler(HDSProtocolSpecificErrorReason.UNEXPECTED_FAILURE)
}
- });
+ })
}
stopAudioProduction(): void {
if (this.running) {
- this.process!.kill("SIGTERM");
- this.running = false;
+ this.process!.kill('SIGTERM')
+ this.running = false
}
- this.process = undefined;
+ this.process = undefined
}
-
}
diff --git a/src/accessories/types.ts b/src/accessories/types.ts
index 91f5f7efb..71083db67 100644
--- a/src/accessories/types.ts
+++ b/src/accessories/types.ts
@@ -1,91 +1,89 @@
-//HomeKit Types UUID's
+// HomeKit Types UUID's
-const stPre = "000000";
-const stPost = "-0000-1000-8000-0026BB765291";
+const stPre = '000000'
+const stPost = '-0000-1000-8000-0026BB765291'
+// HomeKitTransportCategoryTypes
+export const OTHER_TCTYPE = 1
+export const FAN_TCTYPE = 3
+export const GARAGE_DOOR_OPENER_TCTYPE = 4
+export const LIGHTBULB_TCTYPE = 5
+export const DOOR_LOCK_TCTYPE = 6
+export const OUTLET_TCTYPE = 7
+export const SWITCH_TCTYPE = 8
+export const THERMOSTAT_TCTYPE = 9
+export const SENSOR_TCTYPE = 10
+export const ALARM_SYSTEM_TCTYPE = 11
+export const DOOR_TCTYPE = 12
+export const WINDOW_TCTYPE = 13
+export const WINDOW_COVERING_TCTYPE = 14
+export const PROGRAMMABLE_SWITCH_TCTYPE = 15
-//HomeKitTransportCategoryTypes
-export const OTHER_TCTYPE = 1;
-export const FAN_TCTYPE = 3;
-export const GARAGE_DOOR_OPENER_TCTYPE = 4;
-export const LIGHTBULB_TCTYPE = 5;
-export const DOOR_LOCK_TCTYPE = 6;
-export const OUTLET_TCTYPE = 7;
-export const SWITCH_TCTYPE = 8;
-export const THERMOSTAT_TCTYPE = 9;
-export const SENSOR_TCTYPE = 10;
-export const ALARM_SYSTEM_TCTYPE = 11;
-export const DOOR_TCTYPE = 12;
-export const WINDOW_TCTYPE = 13;
-export const WINDOW_COVERING_TCTYPE = 14;
-export const PROGRAMMABLE_SWITCH_TCTYPE = 15;
+// HomeKitServiceTypes
-//HomeKitServiceTypes
+export const LIGHTBULB_STYPE = `${stPre}43${stPost}`
+export const SWITCH_STYPE = `${stPre}49${stPost}`
+export const THERMOSTAT_STYPE = `${stPre}4A${stPost}`
+export const GARAGE_DOOR_OPENER_STYPE = `${stPre}41${stPost}`
+export const ACCESSORY_INFORMATION_STYPE = `${stPre}3E${stPost}`
+export const FAN_STYPE = `${stPre}40${stPost}`
+export const OUTLET_STYPE = `${stPre}47${stPost}`
+export const LOCK_MECHANISM_STYPE = `${stPre}45${stPost}`
+export const LOCK_MANAGEMENT_STYPE = `${stPre}44${stPost}`
+export const ALARM_STYPE = `${stPre}7E${stPost}`
+export const WINDOW_COVERING_STYPE = `${stPre}8C${stPost}`
+export const OCCUPANCY_SENSOR_STYPE = `${stPre}86${stPost}`
+export const CONTACT_SENSOR_STYPE = `${stPre}80${stPost}`
+export const MOTION_SENSOR_STYPE = `${stPre}85${stPost}`
+export const HUMIDITY_SENSOR_STYPE = `${stPre}82${stPost}`
+export const TEMPERATURE_SENSOR_STYPE = `${stPre}8A${stPost}`
-export const LIGHTBULB_STYPE = stPre + "43" + stPost;
-export const SWITCH_STYPE = stPre + "49" + stPost;
-export const THERMOSTAT_STYPE = stPre + "4A" + stPost;
-export const GARAGE_DOOR_OPENER_STYPE = stPre + "41" + stPost;
-export const ACCESSORY_INFORMATION_STYPE = stPre + "3E" + stPost;
-export const FAN_STYPE = stPre + "40" + stPost;
-export const OUTLET_STYPE = stPre + "47" + stPost;
-export const LOCK_MECHANISM_STYPE = stPre + "45" + stPost;
-export const LOCK_MANAGEMENT_STYPE = stPre + "44" + stPost;
-export const ALARM_STYPE = stPre + "7E" + stPost;
-export const WINDOW_COVERING_STYPE = stPre + "8C" + stPost;
-export const OCCUPANCY_SENSOR_STYPE = stPre + "86" + stPost;
-export const CONTACT_SENSOR_STYPE = stPre + "80" + stPost;
-export const MOTION_SENSOR_STYPE = stPre + "85" + stPost;
-export const HUMIDITY_SENSOR_STYPE = stPre + "82" + stPost;
-export const TEMPERATURE_SENSOR_STYPE = stPre + "8A" + stPost;
+// HomeKitCharacteristicsTypes
-//HomeKitCharacteristicsTypes
-
-
-export const ALARM_CURRENT_STATE_CTYPE = stPre + "66" + stPost;
-export const ALARM_TARGET_STATE_CTYPE = stPre + "67" + stPost;
-export const ADMIN_ONLY_ACCESS_CTYPE = stPre + "01" + stPost;
-export const AUDIO_FEEDBACK_CTYPE = stPre + "05" + stPost;
-export const BRIGHTNESS_CTYPE = stPre + "08" + stPost;
-export const BATTERY_LEVEL_CTYPE = stPre + "68" + stPost;
-export const COOLING_THRESHOLD_CTYPE = stPre + "0D" + stPost;
-export const CONTACT_SENSOR_STATE_CTYPE = stPre + "6A" + stPost;
-export const CURRENT_DOOR_STATE_CTYPE = stPre + "0E" + stPost;
-export const CURRENT_LOCK_MECHANISM_STATE_CTYPE = stPre + "1D" + stPost;
-export const CURRENT_RELATIVE_HUMIDITY_CTYPE = stPre + "10" + stPost;
-export const CURRENT_TEMPERATURE_CTYPE = stPre + "11" + stPost;
-export const HEATING_THRESHOLD_CTYPE = stPre + "12" + stPost;
-export const HUE_CTYPE = stPre + "13" + stPost;
-export const IDENTIFY_CTYPE = stPre + "14" + stPost;
-export const LOCK_MANAGEMENT_AUTO_SECURE_TIMEOUT_CTYPE = stPre + "1A" + stPost;
-export const LOCK_MANAGEMENT_CONTROL_POINT_CTYPE = stPre + "19" + stPost;
-export const LOCK_MECHANISM_LAST_KNOWN_ACTION_CTYPE = stPre + "1C" + stPost;
-export const LOGS_CTYPE = stPre + "1F" + stPost;
-export const MANUFACTURER_CTYPE = stPre + "20" + stPost;
-export const MODEL_CTYPE = stPre + "21" + stPost;
-export const MOTION_DETECTED_CTYPE = stPre + "22" + stPost;
-export const NAME_CTYPE = stPre + "23" + stPost;
-export const OBSTRUCTION_DETECTED_CTYPE = stPre + "24" + stPost;
-export const OUTLET_IN_USE_CTYPE = stPre + "26" + stPost;
-export const OCCUPANCY_DETECTED_CTYPE = stPre + "71" + stPost;
-export const POWER_STATE_CTYPE = stPre + "25" + stPost;
-export const PROGRAMMABLE_SWITCH_SWITCH_EVENT_CTYPE = stPre + "73" + stPost;
-export const PROGRAMMABLE_SWITCH_OUTPUT_STATE_CTYPE = stPre + "74" + stPost;
-export const ROTATION_DIRECTION_CTYPE = stPre + "28" + stPost;
-export const ROTATION_SPEED_CTYPE = stPre + "29" + stPost;
-export const SATURATION_CTYPE = stPre + "2F" + stPost;
-export const SERIAL_NUMBER_CTYPE = stPre + "30" + stPost;
-export const FIRMWARE_REVISION_CTYPE = stPre + "52" + stPost;
-export const STATUS_LOW_BATTERY_CTYPE = stPre + "79" + stPost;
-export const STATUS_FAULT_CTYPE = stPre + "77" + stPost;
-export const TARGET_DOORSTATE_CTYPE = stPre + "32" + stPost;
-export const TARGET_LOCK_MECHANISM_STATE_CTYPE = stPre + "1E" + stPost;
-export const TARGET_RELATIVE_HUMIDITY_CTYPE = stPre + "34" + stPost;
-export const TARGET_TEMPERATURE_CTYPE = stPre + "35" + stPost;
-export const TEMPERATURE_UNITS_CTYPE = stPre + "36" + stPost;
-export const VERSION_CTYPE = stPre + "37" + stPost;
-export const WINDOW_COVERING_TARGET_POSITION_CTYPE = stPre + "7C" + stPost;
-export const WINDOW_COVERING_CURRENT_POSITION_CTYPE = stPre + "6D" + stPost;
-export const WINDOW_COVERING_OPERATION_STATE_CTYPE = stPre + "72" + stPost;
-export const CURRENTHEATINGCOOLING_CTYPE = stPre + "0F" + stPost;
-export const TARGETHEATINGCOOLING_CTYPE = stPre + "33" + stPost;
+export const ALARM_CURRENT_STATE_CTYPE = `${stPre}66${stPost}`
+export const ALARM_TARGET_STATE_CTYPE = `${stPre}67${stPost}`
+export const ADMIN_ONLY_ACCESS_CTYPE = `${stPre}01${stPost}`
+export const AUDIO_FEEDBACK_CTYPE = `${stPre}05${stPost}`
+export const BRIGHTNESS_CTYPE = `${stPre}08${stPost}`
+export const BATTERY_LEVEL_CTYPE = `${stPre}68${stPost}`
+export const COOLING_THRESHOLD_CTYPE = `${stPre}0D${stPost}`
+export const CONTACT_SENSOR_STATE_CTYPE = `${stPre}6A${stPost}`
+export const CURRENT_DOOR_STATE_CTYPE = `${stPre}0E${stPost}`
+export const CURRENT_LOCK_MECHANISM_STATE_CTYPE = `${stPre}1D${stPost}`
+export const CURRENT_RELATIVE_HUMIDITY_CTYPE = `${stPre}10${stPost}`
+export const CURRENT_TEMPERATURE_CTYPE = `${stPre}11${stPost}`
+export const HEATING_THRESHOLD_CTYPE = `${stPre}12${stPost}`
+export const HUE_CTYPE = `${stPre}13${stPost}`
+export const IDENTIFY_CTYPE = `${stPre}14${stPost}`
+export const LOCK_MANAGEMENT_AUTO_SECURE_TIMEOUT_CTYPE = `${stPre}1A${stPost}`
+export const LOCK_MANAGEMENT_CONTROL_POINT_CTYPE = `${stPre}19${stPost}`
+export const LOCK_MECHANISM_LAST_KNOWN_ACTION_CTYPE = `${stPre}1C${stPost}`
+export const LOGS_CTYPE = `${stPre}1F${stPost}`
+export const MANUFACTURER_CTYPE = `${stPre}20${stPost}`
+export const MODEL_CTYPE = `${stPre}21${stPost}`
+export const MOTION_DETECTED_CTYPE = `${stPre}22${stPost}`
+export const NAME_CTYPE = `${stPre}23${stPost}`
+export const OBSTRUCTION_DETECTED_CTYPE = `${stPre}24${stPost}`
+export const OUTLET_IN_USE_CTYPE = `${stPre}26${stPost}`
+export const OCCUPANCY_DETECTED_CTYPE = `${stPre}71${stPost}`
+export const POWER_STATE_CTYPE = `${stPre}25${stPost}`
+export const PROGRAMMABLE_SWITCH_SWITCH_EVENT_CTYPE = `${stPre}73${stPost}`
+export const PROGRAMMABLE_SWITCH_OUTPUT_STATE_CTYPE = `${stPre}74${stPost}`
+export const ROTATION_DIRECTION_CTYPE = `${stPre}28${stPost}`
+export const ROTATION_SPEED_CTYPE = `${stPre}29${stPost}`
+export const SATURATION_CTYPE = `${stPre}2F${stPost}`
+export const SERIAL_NUMBER_CTYPE = `${stPre}30${stPost}`
+export const FIRMWARE_REVISION_CTYPE = `${stPre}52${stPost}`
+export const STATUS_LOW_BATTERY_CTYPE = `${stPre}79${stPost}`
+export const STATUS_FAULT_CTYPE = `${stPre}77${stPost}`
+export const TARGET_DOORSTATE_CTYPE = `${stPre}32${stPost}`
+export const TARGET_LOCK_MECHANISM_STATE_CTYPE = `${stPre}1E${stPost}`
+export const TARGET_RELATIVE_HUMIDITY_CTYPE = `${stPre}34${stPost}`
+export const TARGET_TEMPERATURE_CTYPE = `${stPre}35${stPost}`
+export const TEMPERATURE_UNITS_CTYPE = `${stPre}36${stPost}`
+export const VERSION_CTYPE = `${stPre}37${stPost}`
+export const WINDOW_COVERING_TARGET_POSITION_CTYPE = `${stPre}7C${stPost}`
+export const WINDOW_COVERING_CURRENT_POSITION_CTYPE = `${stPre}6D${stPost}`
+export const WINDOW_COVERING_OPERATION_STATE_CTYPE = `${stPre}72${stPost}`
+export const CURRENTHEATINGCOOLING_CTYPE = `${stPre}0F${stPost}`
+export const TARGETHEATINGCOOLING_CTYPE = `${stPre}33${stPost}`
diff --git a/src/index.spec.ts b/src/index.spec.ts
index 8ac789ff5..2b1b0cd86 100644
--- a/src/index.spec.ts
+++ b/src/index.spec.ts
@@ -1,8 +1,10 @@
// we just test that we can import index e.g. without any cyclic imports
-import "./index";
+import { describe, expect, it } from 'vitest'
-describe("index", () => {
- test("test index import", () => {
- expect(true).toBeTruthy();
- });
-});
+import './index.js'
+
+describe('index', () => {
+ it('test index import', () => {
+ expect(true).toBeTruthy()
+ })
+})
diff --git a/src/index.ts b/src/index.ts
index d4c453e69..43d8c04f0 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,38 +1,44 @@
-import "source-map-support/register"; // registering node-source-map-support for typescript stack traces
-import "./lib/definitions"; // must be loaded before Characteristic and Service class
-import createDebug from "debug";
+import 'source-map-support/register.js' // registering node-source-map-support for typescript stack traces
+import './lib/definitions/index.js' // must be loaded before Characteristic and Service class
+import { createRequire } from 'node:module'
+
+import createDebug from 'debug'
+
+import * as Characteristics from './lib/definitions/CharacteristicDefinitions.js'
+import * as Services from './lib/definitions/ServiceDefinitions.js'
/**
* @group Utils
*/
-export * as uuid from "./lib/util/uuid";
-export * from "./lib/model/HAPStorage";
-export * from "./lib/Accessory";
-export * from "./lib/Bridge";
-export * from "./lib/Service";
-export * from "./lib/Characteristic";
-export * from "./lib/camera";
-export * from "./lib/tv/AccessControlManagement";
-export * from "./lib/HAPServer";
-export * from "./lib/datastream";
-export * from "./lib/controller";
-export * from "./lib/model/AccessoryInfo";
+export * as LegacyTypes from './accessories/types.js'
+export * from './lib/Accessory.js'
+export * from './lib/Bridge.js'
+export * from './lib/camera/index.js'
+export * from './lib/Characteristic.js'
+export * from './lib/controller/index.js'
+export * from './lib/datastream/index.js'
+export * from './lib/HAPServer.js'
+export * from './lib/model/AccessoryInfo.js'
+export * from './lib/model/HAPStorage.js'
+export * from './lib/Service.js'
+export * from './lib/tv/AccessControlManagement.js'
-export * from "./lib/util/clone";
-export * from "./lib/util/once";
-export * from "./lib/util/tlv";
-export * from "./lib/util/hapStatusError";
-export * from "./lib/util/color-utils";
-export * from "./lib/util/time";
-export * from "./lib/util/eventedhttp";
+export * from './lib/util/clone.js'
+export * from './lib/util/color-utils.js'
+export * from './lib/util/eventedhttp.js'
+export * from './lib/util/hapStatusError.js'
+export * from './lib/util/once.js'
+export * from './lib/util/time.js'
+export * from './lib/util/tlv.js'
-export * from "./types";
/**
* @group Utils
*/
-export * as LegacyTypes from "./accessories/types";
+export * as uuid from './lib/util/uuid.js'
+export * from './types.js'
-const debug = createDebug("HAP-NodeJS:Advertiser");
+const require = createRequire(import.meta.url)
+const debug = createDebug('HAP-NodeJS:Advertiser')
/**
* This method can be used to retrieve the current running library version of the HAP-NodeJS framework.
@@ -41,18 +47,14 @@ const debug = createDebug("HAP-NodeJS:Advertiser");
* @group Utils
*/
export function HAPLibraryVersion(): string {
- // eslint-disable-next-line @typescript-eslint/no-require-imports
- const packageJson = require("../package.json");
- return packageJson.version;
+ const packageJson = require('../package.json')
+ return packageJson.version
}
function printInit() {
- debug("Initializing HAP-NodeJS v%s ...", HAPLibraryVersion());
+ debug('Initializing HAP-NodeJS v%s ...', HAPLibraryVersion())
}
-printInit();
-
-import * as Services from "./lib/definitions/ServiceDefinitions";
-import * as Characteristics from "./lib/definitions/CharacteristicDefinitions";
+printInit()
/**
* This namespace doesn't actually exist and is only used to generate documentation for all Service and Characteristic Definitions.
@@ -61,9 +63,4 @@ import * as Characteristics from "./lib/definitions/CharacteristicDefinitions";
*
* @group Utils
*/
-export declare namespace _definitions { // eslint-disable-line @typescript-eslint/no-namespace
- export {
- Services,
- Characteristics,
- };
-}
+export { Characteristics, Services }
diff --git a/src/internal-types.ts b/src/internal-types.ts
index 9670f0dc7..10bae4767 100644
--- a/src/internal-types.ts
+++ b/src/internal-types.ts
@@ -1,19 +1,19 @@
-import { CharacteristicValue, Nullable } from "./types";
+import type { CharacteristicValue, Nullable } from './types'
/**
* @group HAP Accessory Server
*/
export interface EventNotification {
- characteristics: CharacteristicEventNotification[],
+ characteristics: CharacteristicEventNotification[]
}
/**
* @group HAP Accessory Server
*/
export interface CharacteristicEventNotification {
- aid: number,
- iid: number,
- value: Nullable,
+ aid: number
+ iid: number
+ value: Nullable
}
/**
@@ -21,53 +21,57 @@ export interface CharacteristicEventNotification {
*/
export function consideredTrue(input: string | null): boolean {
if (!input) {
- return false;
+ return false
}
- return input === "true" || input === "1";
+ return input === 'true' || input === '1'
}
/**
* @group HAP Accessory Server
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum TLVValues {
- // noinspection JSUnusedGlobalSymbols
REQUEST_TYPE = 0x00,
- // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
+
+ // eslint-disable-next-line ts/no-duplicate-enum-values
METHOD = 0x00, // (match the terminology of the spec sheet but keep backwards compatibility with entry above)
USERNAME = 0x01,
- // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
+
+ // eslint-disable-next-line ts/no-duplicate-enum-values
IDENTIFIER = 0x01,
SALT = 0x02,
PUBLIC_KEY = 0x03,
PASSWORD_PROOF = 0x04,
ENCRYPTED_DATA = 0x05,
SEQUENCE_NUM = 0x06,
- // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
+
+ // eslint-disable-next-line ts/no-duplicate-enum-values
STATE = 0x06,
ERROR_CODE = 0x07,
RETRY_DELAY = 0x08,
CERTIFICATE = 0x09, // x.509 certificate
PROOF = 0x0A,
- // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
- SIGNATURE = 0x0A, // apple authentication coprocessor
+
+ // eslint-disable-next-line ts/no-duplicate-enum-values
+ SIGNATURE = 0x0A, // apple authentication coprocessor
PERMISSIONS = 0x0B, // None (0x00): regular user, 0x01: Admin (able to add/remove/list pairings)
FRAGMENT_DATA = 0x0C,
FRAGMENT_LAST = 0x0D,
- SEPARATOR = 0x0FF // Zero-length TLV that separates different TLVs in a list.
+ SEPARATOR = 0x0FF, // Zero-length TLV that separates different TLVs in a list.
}
/**
* @group HAP Accessory Server
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum PairMethods {
- // noinspection JSUnusedGlobalSymbols
PAIR_SETUP = 0x00,
PAIR_SETUP_WITH_AUTH = 0x01,
PAIR_VERIFY = 0x02,
ADD_PAIRING = 0x03,
REMOVE_PAIRING = 0x04,
- LIST_PAIRINGS = 0x05
+ LIST_PAIRINGS = 0x05,
}
/**
@@ -75,20 +79,22 @@ export const enum PairMethods {
*
* @group HAP Accessory Server
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum PairingStates {
M1 = 0x01,
M2 = 0x02,
M3 = 0x03,
M4 = 0x04,
M5 = 0x05,
- M6 = 0x06
+ M6 = 0x06,
}
/**
* @group HAP Accessory Server
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum HAPMimeTypes {
- PAIRING_TLV8 = "application/pairing+tlv8",
- HAP_JSON = "application/hap+json",
- IMAGE_JPEG = "image/jpeg",
+ PAIRING_TLV8 = 'application/pairing+tlv8',
+ HAP_JSON = 'application/hap+json',
+ IMAGE_JPEG = 'image/jpeg',
}
diff --git a/src/lib/Accessory.spec.ts b/src/lib/Accessory.spec.ts
index 814c85afb..10481363b 100644
--- a/src/lib/Accessory.spec.ts
+++ b/src/lib/Accessory.spec.ts
@@ -1,5 +1,8 @@
-import crypto from "crypto";
-import {
+import type { Buffer } from 'node:buffer'
+
+import type { Mock, MockInstance } from 'vitest'
+
+import type {
AccessoriesResponse,
CharacteristicJsonObject,
CharacteristicReadData,
@@ -12,813 +15,829 @@ import {
InterfaceName,
IPAddress,
ResourceRequest,
- ResourceRequestType,
-} from "../types";
-import { Accessory, AccessoryEventTypes, Categories, CharacteristicWarningType, MDNSAdvertiser, PublishInfo } from "./Accessory";
-import { BonjourHAPAdvertiser } from "./Advertiser";
-import { Bridge } from "./Bridge";
+} from '../types'
+import type { PublishInfo } from './Accessory'
+import type { CharacteristicGetCallback, CharacteristicSetCallback } from './Characteristic'
+import type { Controller, ControllerIdentifier, ControllerServiceMap } from './controller'
+import type { IdentifyCallback } from './HAPServer'
+import type { PairingInformation } from './model/AccessoryInfo'
+import type { HAPConnection } from './util/eventedhttp'
+
+import { randomBytes } from 'node:crypto'
+
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+
+import { ResourceRequestType } from '../types.js'
+import {
+ Accessory,
+ AccessoryEventTypes,
+ Categories,
+ CharacteristicWarningType,
+ MDNSAdvertiser,
+} from './Accessory.js'
+import { BonjourHAPAdvertiser } from './Advertiser.js'
+import { Bridge } from './Bridge.js'
import {
Access,
Characteristic,
CharacteristicEventTypes,
- CharacteristicGetCallback,
- CharacteristicSetCallback,
Formats,
Perms,
Units,
-} from "./Characteristic";
-import { CameraController, Controller, ControllerIdentifier, ControllerServiceMap } from "./controller";
-import { createCameraControllerOptions, MOCK_IMAGE } from "./controller/CameraController.spec";
-import { HAPHTTPCode, HAPStatus, IdentifyCallback, TLVErrorCode } from "./HAPServer";
-import { AccessoryInfo, PairingInformation, PermissionTypes } from "./model/AccessoryInfo";
-import { IdentifierCache } from "./model/IdentifierCache";
-import { Service } from "./Service";
-import { EventedHTTPServer, HAPConnection } from "./util/eventedhttp";
-import { HapStatusError } from "./util/hapStatusError";
-import { awaitEventOnce, PromiseTimeout } from "./util/promise-utils";
-import * as uuid from "./util/uuid";
-import { toShortForm } from "./util/uuid";
-import Mock = jest.Mock;
-
-describe("Accessory", () => {
- const TEST_DISPLAY_NAME = "Test Accessory";
- const TEST_UUID = uuid.generate("HAP-NODEJS-TEST-ACCESSORY!");
-
- let accessory: Accessory;
- let connection: HAPConnection;
-
- const serverUsername = "AB:CD:EF:00:11:22";
- const clientUsername0 = "AB:CD:EF:00:11:23";
- let clientPublicKey0: Buffer;
- const clientUsername1 = "AB:CD:EF:00:11:24";
- let clientPublicKey1: Buffer;
-
- let accessoryInfoUnpaired: AccessoryInfo;
- let accessoryInfoPaired: AccessoryInfo;
- const saveMock: Mock = jest.fn();
-
- let callback: Mock;
+} from './Characteristic.js'
+import { createCameraControllerOptions, MOCK_IMAGE } from './controller/CameraController.spec.js'
+import { CameraController } from './controller/index.js'
+import { HAPHTTPCode, HAPStatus, TLVErrorCode } from './HAPServer.js'
+import { AccessoryInfo, PermissionTypes } from './model/AccessoryInfo.js'
+import { IdentifierCache } from './model/IdentifierCache.js'
+import { Service } from './Service.js'
+import { EventedHTTPServer } from './util/eventedhttp.js'
+import { HapStatusError } from './util/hapStatusError.js'
+import { awaitEventOnce, PromiseTimeout } from './util/promise-utils.js'
+import { generate, toShortForm } from './util/uuid.js'
+
+describe('accessory', () => {
+ const TEST_DISPLAY_NAME = 'Test Accessory'
+ const TEST_UUID = generate('HAP-NODEJS-TEST-ACCESSORY!')
+
+ let accessory: Accessory
+ let connection: HAPConnection
+
+ const serverUsername = 'AB:CD:EF:00:11:22'
+ const clientUsername0 = 'AB:CD:EF:00:11:23'
+ let clientPublicKey0: Buffer
+ const clientUsername1 = 'AB:CD:EF:00:11:24'
+ let clientPublicKey1: Buffer
+
+ let accessoryInfoUnpaired: AccessoryInfo
+ let accessoryInfoPaired: AccessoryInfo
+ const saveMock: Mock = vi.fn()
+
+ let callback: Mock
// a void promise to wait for the above callback to be called!
- let callbackPromise: Promise;
+ let callbackPromise: Promise
beforeEach(() => {
- clientPublicKey0 = crypto.randomBytes(32);
- clientPublicKey1 = crypto.randomBytes(32);
+ clientPublicKey0 = randomBytes(32)
+ clientPublicKey1 = randomBytes(32)
- accessoryInfoUnpaired = AccessoryInfo.create(serverUsername);
+ accessoryInfoUnpaired = AccessoryInfo.create(serverUsername)
// @ts-expect-error: private access
- accessoryInfoUnpaired.setupID = Accessory._generateSetupID();
- accessoryInfoUnpaired.displayName = "Outlet";
- accessoryInfoUnpaired.category = 7;
- accessoryInfoUnpaired.pincode = " 031-45-154";
- accessoryInfoUnpaired.save = saveMock;
+ accessoryInfoUnpaired.setupID = Accessory._generateSetupID()
+ accessoryInfoUnpaired.displayName = 'Outlet'
+ accessoryInfoUnpaired.category = 7
+ accessoryInfoUnpaired.pincode = ' 031-45-154'
+ accessoryInfoUnpaired.save = saveMock
- accessoryInfoPaired = AccessoryInfo.create(serverUsername);
+ accessoryInfoPaired = AccessoryInfo.create(serverUsername)
// @ts-expect-error: private access
- accessoryInfoPaired.setupID = Accessory._generateSetupID();
- accessoryInfoPaired.displayName = "Outlet";
- accessoryInfoPaired.category = 7;
- accessoryInfoPaired.pincode = " 031-45-154";
- accessoryInfoPaired.addPairedClient(clientUsername0, clientPublicKey0, PermissionTypes.ADMIN);
- accessoryInfoPaired.save = saveMock;
+ accessoryInfoPaired.setupID = Accessory._generateSetupID()
+ accessoryInfoPaired.displayName = 'Outlet'
+ accessoryInfoPaired.category = 7
+ accessoryInfoPaired.pincode = ' 031-45-154'
+ accessoryInfoPaired.addPairedClient(clientUsername0, clientPublicKey0, PermissionTypes.ADMIN)
+ accessoryInfoPaired.save = saveMock
// ensure we start with a clean Accessory for every test
- Accessory.cleanupAccessoryData(serverUsername);
- accessory = new Accessory(TEST_DISPLAY_NAME, TEST_UUID);
+ Accessory.cleanupAccessoryData(serverUsername)
+ accessory = new Accessory(TEST_DISPLAY_NAME, TEST_UUID)
connection = {
username: clientUsername0,
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- } as any;
+ } as any
- callback = jest.fn();
- const originalReset = callback.mockReset;
+ callback = vi.fn()
+ const originalReset = callback.mockReset
callback.mockReset = () => {
- const resetResult = originalReset();
- callbackPromise = new Promise(resolve => {
- callback.mockImplementationOnce(() => resolve());
- });
- return resetResult;
- };
+ const resetResult = originalReset()
+ callbackPromise = new Promise((resolve) => {
+ callback.mockImplementationOnce(() => resolve())
+ })
+ return resetResult
+ }
- callback.mockReset();
+ callback.mockReset()
- saveMock.mockReset();
- });
+ saveMock.mockReset()
+ })
afterEach(async () => {
- await accessory?.unpublish();
- await accessory?.destroy();
- });
-
- describe("constructor", () => {
- test("fail to load with no display name", () => {
- expect(() => new Accessory("", ""))
- .toThrow("non-empty displayName");
- });
-
- test("fail to load with no UUID", () => {
- expect(() => new Accessory("Test", ""))
- .toThrow("valid UUID");
- });
-
- test("fail to load with an invalid UUID", () => {
- expect(() => new Accessory("Test", "test"))
- .toThrow("not a valid UUID");
- });
- });
-
- describe("handling services", () => {
- test("addService", () => {
- const existingCount = 1; // accessoryInformation service; protocolInformation is only added on publish
-
- const instance = new Service.Switch("Switch");
- expect(accessory.services.length).toEqual(existingCount);
-
- const switchService = accessory.addService(instance);
- expect(accessory.services.length).toEqual(existingCount + 1);
- expect(accessory.services.includes(instance)).toBeTruthy();
- expect(switchService).toBe(instance);
-
- const outletService = accessory.addService(Service.Outlet, "Outlet");
- expect(outletService.displayName).toEqual("Outlet");
- expect(accessory.services.length).toEqual(existingCount + 2);
- expect(accessory.services.includes(outletService)).toBeTruthy();
+ await accessory?.unpublish()
+ await accessory?.destroy()
+ })
+
+ describe('constructor', () => {
+ it('fail to load with no display name', () => {
+ expect(() => new Accessory('', ''))
+ .toThrow('non-empty displayName')
+ })
+
+ it('fail to load with no UUID', () => {
+ expect(() => new Accessory('Test', ''))
+ .toThrow('valid UUID')
+ })
+
+ it('fail to load with an invalid UUID', () => {
+ expect(() => new Accessory('Test', 'test'))
+ .toThrow('not a valid UUID')
+ })
+ })
+
+ describe('handling services', () => {
+ it('addService', () => {
+ const existingCount = 1 // accessoryInformation service; protocolInformation is only added on publish
+
+ const instance = new Service.Switch('Switch')
+ expect(accessory.services.length).toEqual(existingCount)
+
+ const switchService = accessory.addService(instance)
+ expect(accessory.services.length).toEqual(existingCount + 1)
+ expect(accessory.services.includes(instance)).toBeTruthy()
+ expect(switchService).toBe(instance)
+
+ const outletService = accessory.addService(Service.Outlet, 'Outlet')
+ expect(outletService.displayName).toEqual('Outlet')
+ expect(accessory.services.length).toEqual(existingCount + 2)
+ expect(accessory.services.includes(outletService)).toBeTruthy()
// CHECKING DUPLICATES
- const instance0 = new Service.Switch("Switch1");
- expect(() => accessory.addService(instance0)).toThrow();
+ const instance0 = new Service.Switch('Switch1')
+ expect(() => accessory.addService(instance0)).toThrow()
- const instance1 = accessory.addService(Service.Switch, "Switch2", "subtype");
- expect(instance1.displayName).toEqual("Switch2");
- expect(() => accessory.addService(Service.Switch, "Switch3", "subtype")).toThrow();
+ const instance1 = accessory.addService(Service.Switch, 'Switch2', 'subtype')
+ expect(instance1.displayName).toEqual('Switch2')
+ expect(() => accessory.addService(Service.Switch, 'Switch3', 'subtype')).toThrow()
expect(accessory.getService(Service.Switch))
- .toBe(instance);
- expect(accessory.getService("Switch"))
- .toBe(instance);
-
- expect(accessory.getServiceById(Service.Switch, "subtype"))
- .toBe(instance1);
- expect(accessory.getServiceById("Switch2", "subtype"))
- .toBe(instance1);
- expect(accessory.getServiceById("Switch3", "subtype"))
- .toBeUndefined();
- });
+ .toBe(instance)
+ expect(accessory.getService('Switch'))
+ .toBe(instance)
- test("removeService", () => {
- const existingCount = 1; // accessoryInformation service; protocolInformation is only added on publish
+ expect(accessory.getServiceById(Service.Switch, 'subtype'))
+ .toBe(instance1)
+ expect(accessory.getServiceById('Switch2', 'subtype'))
+ .toBe(instance1)
+ expect(accessory.getServiceById('Switch3', 'subtype'))
+ .toBeUndefined()
+ })
- const instance = new Service.Switch("Switch");
- instance.setPrimaryService();
- expect(accessory.services.length).toEqual(existingCount);
+ it('removeService', () => {
+ const existingCount = 1 // accessoryInformation service; protocolInformation is only added on publish
+ const instance = new Service.Switch('Switch')
+ instance.setPrimaryService()
+ expect(accessory.services.length).toEqual(existingCount)
- accessory.addService(instance);
- expect(accessory.services.length).toEqual(existingCount + 1);
+ accessory.addService(instance)
+ expect(accessory.services.length).toEqual(existingCount + 1)
- const outlet = accessory.addService(Service.Outlet);
- outlet.addLinkedService(instance);
- expect(accessory.services.length).toEqual(existingCount + 2);
+ const outlet = accessory.addService(Service.Outlet)
+ outlet.addLinkedService(instance)
+ expect(accessory.services.length).toEqual(existingCount + 2)
- accessory.removeService(instance);
- expect(accessory.services.length).toEqual(existingCount + 1);
- expect(accessory.services.includes(instance)).toBeFalsy();
+ accessory.removeService(instance)
+ expect(accessory.services.length).toEqual(existingCount + 1)
+ expect(accessory.services.includes(instance)).toBeFalsy()
// HANDLING PRIMARY AND LINKED SERVICES
// @ts-expect-error: private access
- expect(accessory.primaryService).toBe(undefined);
- expect(instance.isPrimaryService).toBeTruthy();
- expect(outlet.linkedServices.length).toEqual(0);
- });
+ expect(accessory.primaryService).toBe(undefined)
+ expect(instance.isPrimaryService).toBeTruthy()
+ expect(outlet.linkedServices.length).toEqual(0)
+ })
- test("primary service handling", () => {
- const instance = new Service.Switch("Switch");
- instance.setPrimaryService();
- accessory.addService(instance);
+ it('primary service handling', () => {
+ const instance = new Service.Switch('Switch')
+ instance.setPrimaryService()
+ accessory.addService(instance)
// @ts-expect-error: private access
- expect(accessory.primaryService).toBe(instance);
+ expect(accessory.primaryService).toBe(instance)
- const outlet = new Service.Outlet("Outlet");
- outlet.setPrimaryService();
- accessory.addService(outlet);
+ const outlet = new Service.Outlet('Outlet')
+ outlet.setPrimaryService()
+ accessory.addService(outlet)
// @ts-expect-error: private access
- expect(accessory.primaryService).toBe(outlet);
- expect(instance.isPrimaryService).toBeFalsy();
- expect(outlet.isPrimaryService).toBeTruthy();
+ expect(accessory.primaryService).toBe(outlet)
+ expect(instance.isPrimaryService).toBeFalsy()
+ expect(outlet.isPrimaryService).toBeTruthy()
- instance.setPrimaryService();
+ instance.setPrimaryService()
// @ts-expect-error: private access
- expect(accessory.primaryService).toBe(instance);
- expect(instance.isPrimaryService).toBeTruthy();
- expect(outlet.isPrimaryService).toBeFalsy();
+ expect(accessory.primaryService).toBe(instance)
+ expect(instance.isPrimaryService).toBeTruthy()
+ expect(outlet.isPrimaryService).toBeFalsy()
- instance.setPrimaryService(false);
+ instance.setPrimaryService(false)
// @ts-expect-error: private access
- expect(accessory.primaryService).toBe(undefined);
- expect(instance.isPrimaryService).toBeFalsy();
- expect(outlet.isPrimaryService).toBeFalsy();
- });
- });
+ expect(accessory.primaryService).toBe(undefined)
+ expect(instance.isPrimaryService).toBeFalsy()
+ expect(outlet.isPrimaryService).toBeFalsy()
+ })
+ })
- describe("bridged accessories", () => {
- test("addBridgedAccessory", () => {
- const bridge = new Bridge("TestBridge", uuid.generate("bridge test"));
+ describe('bridged accessories', () => {
+ it('addBridgedAccessory', () => {
+ const bridge = new Bridge('TestBridge', generate('bridge test'))
- bridge.addBridgedAccessories([ accessory ]);
- expect(bridge.bridged).toBeFalsy();
- expect(bridge.bridge).toBeUndefined();
- expect(accessory.bridged).toBeTruthy();
- expect(accessory.bridge).toBe(bridge);
+ bridge.addBridgedAccessories([accessory])
+ expect(bridge.bridged).toBeFalsy()
+ expect(bridge.bridge).toBeUndefined()
+ expect(accessory.bridged).toBeTruthy()
+ expect(accessory.bridge).toBe(bridge)
- expect(bridge.getPrimaryAccessory()).toBe(bridge);
- expect(accessory.getPrimaryAccessory()).toBe(bridge);
+ expect(bridge.getPrimaryAccessory()).toBe(bridge)
+ expect(accessory.getPrimaryAccessory()).toBe(bridge)
- expect(bridge.bridgedAccessories.includes(accessory)).toBeTruthy();
+ expect(bridge.bridgedAccessories.includes(accessory)).toBeTruthy()
- expect(() => bridge.addBridgedAccessory(accessory)).toThrow();
- expect(() => bridge.addBridgedAccessory(new Bridge("asdf", uuid.generate("asdf"))))
- .toThrow();
- });
+ expect(() => bridge.addBridgedAccessory(accessory)).toThrow()
+ expect(() => bridge.addBridgedAccessory(new Bridge('asdf', generate('asdf'))))
+ .toThrow()
+ })
- test("removeBridgedAccessory", () => {
- const bridge = new Bridge("TestBridge", uuid.generate("bridge test"));
+ it('removeBridgedAccessory', () => {
+ const bridge = new Bridge('TestBridge', generate('bridge test'))
const validate = () => {
- expect(bridge.bridged).toBeFalsy();
- expect(bridge.bridge).toBeUndefined();
- expect(accessory.bridged).toBeFalsy();
- expect(accessory.bridge).toBeUndefined();
+ expect(bridge.bridged).toBeFalsy()
+ expect(bridge.bridge).toBeUndefined()
+ expect(accessory.bridged).toBeFalsy()
+ expect(accessory.bridge).toBeUndefined()
- expect(bridge.getPrimaryAccessory()).toBe(bridge);
- expect(accessory.getPrimaryAccessory()).toBe(accessory);
+ expect(bridge.getPrimaryAccessory()).toBe(bridge)
+ expect(accessory.getPrimaryAccessory()).toBe(accessory)
- expect(bridge.bridgedAccessories.includes(accessory)).toBeFalsy();
- };
+ expect(bridge.bridgedAccessories.includes(accessory)).toBeFalsy()
+ }
- bridge.addBridgedAccessories([ accessory ]);
- bridge.removeBridgedAccessory(accessory);
- validate();
+ bridge.addBridgedAccessories([accessory])
+ bridge.removeBridgedAccessory(accessory)
+ validate()
- bridge.addBridgedAccessories([ accessory ]);
- bridge.removeBridgedAccessories([ accessory ]);
- validate();
+ bridge.addBridgedAccessories([accessory])
+ bridge.removeBridgedAccessories([accessory])
+ validate()
- bridge.addBridgedAccessories([ accessory ]);
- bridge.removeAllBridgedAccessories();
- validate();
+ bridge.addBridgedAccessories([accessory])
+ bridge.removeAllBridgedAccessories()
+ validate()
- expect(() => bridge.removeBridgedAccessory(accessory)).toThrow();
- });
+ expect(() => bridge.removeBridgedAccessory(accessory)).toThrow()
+ })
- test("getAccessoryByAID", () => {
- const bridge = new Bridge("TestBridge", uuid.generate("bridge test"));
- bridge.addBridgedAccessory(accessory);
+ it('getAccessoryByAID', () => {
+ const bridge = new Bridge('TestBridge', generate('bridge test'))
+ bridge.addBridgedAccessory(accessory)
- bridge._identifierCache = new IdentifierCache(serverUsername);
- bridge._assignIDs(bridge._identifierCache);
+ bridge._identifierCache = new IdentifierCache(serverUsername)
+ bridge._assignIDs(bridge._identifierCache)
// @ts-expect-error: private access
- expect(bridge.getAccessoryByAID(1)).toBe(bridge);
+ expect(bridge.getAccessoryByAID(1)).toBe(bridge)
// @ts-expect-error: private access
- expect(bridge.getAccessoryByAID(2)).toBe(accessory);
+ expect(bridge.getAccessoryByAID(2)).toBe(accessory)
// @ts-expect-error: private access
- expect(accessory.getAccessoryByAID(2)).toBe(accessory);
- });
- });
+ expect(accessory.getAccessoryByAID(2)).toBe(accessory)
+ })
+ })
- describe("accessory controllers", () => {
- test("configureController deserialize controllers and remove/add/replace services correctly", () => {
- accessory.configureController(new TestController());
+ describe('accessory controllers', () => {
+ it('configureController deserialize controllers and remove/add/replace services correctly', () => {
+ accessory.configureController(new TestController())
- const serialized = Accessory.serialize(accessory);
+ const serialized = Accessory.serialize(accessory)
- const restoredAccessory = Accessory.deserialize(serialized);
- restoredAccessory.configureController(new TestController()); // restore Controller;
+ const restoredAccessory = Accessory.deserialize(serialized)
+ restoredAccessory.configureController(new TestController()) // restore Controller;
- expect(restoredAccessory.services).toBeDefined();
- expect(restoredAccessory.services.length).toEqual(3); // accessory information, light sensor, outlet
+ expect(restoredAccessory.services).toBeDefined()
+ expect(restoredAccessory.services.length).toEqual(3) // accessory information, light sensor, outlet
- expect(restoredAccessory.getService(Service.Lightbulb)).toBeUndefined();
- expect(restoredAccessory.getService(Service.LightSensor)).toBeDefined();
- expect(restoredAccessory.getService(Service.Outlet)).toBeDefined();
- expect(restoredAccessory.getService(Service.Switch)).toBeUndefined();
- });
- });
+ expect(restoredAccessory.getService(Service.Lightbulb)).toBeUndefined()
+ expect(restoredAccessory.getService(Service.LightSensor)).toBeDefined()
+ expect(restoredAccessory.getService(Service.Outlet)).toBeDefined()
+ expect(restoredAccessory.getService(Service.Switch)).toBeUndefined()
+ })
+ })
- describe("publish", () => {
- test.each`
+ describe('publish', () => {
+ it.each`
advertiser | republish
${MDNSAdvertiser.BONJOUR} | ${false}
${MDNSAdvertiser.CIAO} | ${false}
${MDNSAdvertiser.BONJOUR} | ${true}
${MDNSAdvertiser.CIAO} | ${true}
- `("Clean Accessory publish and unpublish (advertiser: $advertiser; republish: $republish)", async ({ advertiser, republish }) => {
- const switchService = new Service.Switch("My Example Switch");
- accessory.addService(switchService);
+ `('clean Accessory publish and unpublish (advertiser: $advertiser; republish: $republish)', async ({ advertiser, republish }) => {
+ const switchService = new Service.Switch('My Example Switch')
+ accessory.addService(switchService)
const publishInfo: PublishInfo = {
username: serverUsername,
- pincode: "000-00-000",
+ pincode: '000-00-000',
category: Categories.SWITCH,
- advertiser: advertiser,
- };
+ advertiser,
+ }
- await accessory.publish(publishInfo);
+ await accessory.publish(publishInfo)
- expect(accessory.displayName.startsWith(TEST_DISPLAY_NAME));
- expect(accessory.displayName.length).toEqual(TEST_DISPLAY_NAME.length + 1 + 4); // added hash!
+ expect(accessory.displayName.startsWith(TEST_DISPLAY_NAME))
+ expect(accessory.displayName.length).toEqual(TEST_DISPLAY_NAME.length + 1 + 4) // added hash!
- await awaitEventOnce(accessory, AccessoryEventTypes.ADVERTISED);
+ await awaitEventOnce(accessory, AccessoryEventTypes.ADVERTISED)
- const displayNameWithIdentifyingMaterial = accessory.displayName;
+ const displayNameWithIdentifyingMaterial = accessory.displayName
- await accessory.unpublish();
+ await accessory.unpublish()
if (!republish) {
- return;
+ return
}
// This second round tests, that the Accessory is reusable after unpublished was called
- await PromiseTimeout(200);
+ await PromiseTimeout(200)
- await accessory.publish(publishInfo);
+ await accessory.publish(publishInfo)
// ensure unification isn't done twice!
- expect(accessory.displayName).toEqual(displayNameWithIdentifyingMaterial);
+ expect(accessory.displayName).toEqual(displayNameWithIdentifyingMaterial)
- await awaitEventOnce(accessory, AccessoryEventTypes.ADVERTISED);
- });
+ await awaitEventOnce(accessory, AccessoryEventTypes.ADVERTISED)
+ })
- test("Clean Accessory publish and unpublish with default advertiser selection", async () => {
- const switchService = new Service.Switch("My Example Switch");
- accessory.addService(switchService);
+ it('clean Accessory publish and unpublish with default advertiser (ciao) selection', async () => {
+ const switchService = new Service.Switch('My Example Switch')
+ accessory.addService(switchService)
const publishInfo: PublishInfo = {
username: serverUsername,
- pincode: "000-00-000",
+ pincode: '000-00-000',
category: Categories.SWITCH,
- advertiser: undefined,
- };
+ advertiser: MDNSAdvertiser.CIAO,
+ }
- await accessory.publish(publishInfo);
+ await accessory.publish(publishInfo)
- expect(accessory.displayName.startsWith(TEST_DISPLAY_NAME));
- expect(accessory.displayName.length).toEqual(TEST_DISPLAY_NAME.length + 1 + 4); // added hash!
+ expect(accessory.displayName.startsWith(TEST_DISPLAY_NAME))
+ expect(accessory.displayName.length).toEqual(TEST_DISPLAY_NAME.length + 1 + 4) // added hash!
- await awaitEventOnce(accessory, AccessoryEventTypes.ADVERTISED);
- });
- });
+ await awaitEventOnce(accessory, AccessoryEventTypes.ADVERTISED)
+ })
+ })
- describe("Accessory and Service naming checks", () => {
- let consoleWarnSpy: jest.SpyInstance;
+ describe('accessory and Service naming checks', () => {
+ const advertiser = MDNSAdvertiser.CIAO
+ let consoleWarnSpy: MockInstance
beforeEach(() => {
- consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
- });
+ consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
+ })
afterEach(() => {
- consoleWarnSpy.mockRestore();
- });
+ consoleWarnSpy.mockRestore()
+ })
- test("Accessory Name ending with !", async () => {
- const accessoryBadName = new Accessory("Bad Name!",uuid.generate("Bad Name"));
+ it('accessory Name ending with !', async () => {
+ const accessoryBadName = new Accessory('Bad Name!', generate('Bad Name'))
const publishInfo: PublishInfo = {
username: serverUsername,
- pincode: "000-00-000",
+ pincode: '000-00-000',
category: Categories.SWITCH,
- advertiser: undefined,
- };
+ advertiser,
+ }
+
+ await accessoryBadName.publish(publishInfo)
- await accessoryBadName.publish(publishInfo);
- // eslint-disable-next-line max-len
- expect(consoleWarnSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'Bad Name!' has an invalid 'Name' characteristic ('Bad Name!'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.");
+ expect(consoleWarnSpy).toHaveBeenCalledWith('HAP-NodeJS WARNING: The accessory \'Bad Name!\' has an invalid \'Name\' characteristic (\'Bad Name!\'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.')
- await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
- await accessoryBadName?.unpublish();
- await accessoryBadName?.destroy();
- });
+ await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED)
+ await accessoryBadName?.unpublish()
+ await accessoryBadName?.destroy()
+ })
- test("Accessory Name containing !", async () => {
- const accessoryBadName = new Accessory("Bad ! Name",uuid.generate("Bad Name"));
+ it('accessory Name containing !', async () => {
+ const accessoryBadName = new Accessory('Bad ! Name', generate('Bad Name'))
const publishInfo: PublishInfo = {
username: serverUsername,
- pincode: "000-00-000",
+ pincode: '000-00-000',
category: Categories.SWITCH,
- advertiser: undefined,
- };
+ advertiser,
+ }
- await accessoryBadName.publish(publishInfo);
- // eslint-disable-next-line max-len
- expect(consoleWarnSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'Bad ! Name' has an invalid 'Name' characteristic ('Bad ! Name'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.");
+ await accessoryBadName.publish(publishInfo)
- await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
- await accessoryBadName?.unpublish();
- await accessoryBadName?.destroy();
- });
+ expect(consoleWarnSpy).toHaveBeenCalledWith('HAP-NodeJS WARNING: The accessory \'Bad ! Name\' has an invalid \'Name\' characteristic (\'Bad ! Name\'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.')
- test("Accessory Name containing '", async () => {
- const accessoryBadName = new Accessory("Bad ' Name",uuid.generate("Bad ' Name"));
+ await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED)
+ await accessoryBadName?.unpublish()
+ await accessoryBadName?.destroy()
+ })
+
+ it('accessory Name containing apostrophe', async () => {
+ const accessoryBadName = new Accessory('Bad \' Name', generate('Bad \' Name'))
const publishInfo: PublishInfo = {
username: serverUsername,
- pincode: "000-00-000",
+ pincode: '000-00-000',
category: Categories.SWITCH,
- advertiser: undefined,
- };
+ advertiser,
+ }
- await accessoryBadName.publish(publishInfo);
- expect(consoleWarnSpy).toBeCalledTimes(0);
+ await accessoryBadName.publish(publishInfo)
+ expect(consoleWarnSpy).toBeCalledTimes(0)
- await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
- await accessoryBadName?.unpublish();
- await accessoryBadName?.destroy();
- });
+ await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED)
+ await accessoryBadName?.unpublish()
+ await accessoryBadName?.destroy()
+ })
- test("Accessory Name starting with '", async () => {
- const accessoryBadName = new Accessory("'Bad Name",uuid.generate("Bad Name'"));
+ it('accessory Name starting with apostrophe', async () => {
+ const accessoryBadName = new Accessory('\'Bad Name', generate('Bad Name\''))
const publishInfo: PublishInfo = {
username: serverUsername,
- pincode: "000-00-000",
+ pincode: '000-00-000',
category: Categories.SWITCH,
- advertiser: undefined,
- };
+ advertiser,
+ }
- await accessoryBadName.publish(publishInfo);
- expect(accessoryBadName.displayName.startsWith(TEST_DISPLAY_NAME));
- expect(consoleWarnSpy).toBeCalledTimes(2);
- // eslint-disable-next-line max-len
- expect(consoleWarnSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory ''Bad Name' has an invalid 'Name' characteristic (''Bad Name'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.");
+ await accessoryBadName.publish(publishInfo)
+ expect(accessoryBadName.displayName.startsWith(TEST_DISPLAY_NAME))
+ expect(consoleWarnSpy).toBeCalledTimes(2)
- await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
- await accessoryBadName?.unpublish();
- await accessoryBadName?.destroy();
- });
+ expect(consoleWarnSpy).toHaveBeenCalledWith('HAP-NodeJS WARNING: The accessory \'\'Bad Name\' has an invalid \'Name\' characteristic (\'\'Bad Name\'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.')
- test("Service Name containing !", async () => {
- const switchService = new Service.Switch("My Bad ! Switch");
- const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
- accessoryBadName.addService(switchService);
+ await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED)
+ await accessoryBadName?.unpublish()
+ await accessoryBadName?.destroy()
+ })
+
+ it('service Name containing !', async () => {
+ const switchService = new Service.Switch('My Bad ! Switch')
+ const accessoryBadName = new Accessory('Bad Name', generate('Bad Name'))
+ accessoryBadName.addService(switchService)
const publishInfo: PublishInfo = {
username: serverUsername,
- pincode: "000-00-000",
+ pincode: '000-00-000',
category: Categories.SWITCH,
- advertiser: undefined,
- };
+ advertiser,
+ }
+
+ await accessoryBadName.publish(publishInfo)
- await accessoryBadName.publish(publishInfo);
- // eslint-disable-next-line max-len
- expect(consoleWarnSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'My Bad ! Switch' has an invalid 'Name' characteristic ('My Bad ! Switch'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.");
+ expect(consoleWarnSpy).toHaveBeenCalledWith('HAP-NodeJS WARNING: The accessory \'My Bad ! Switch\' has an invalid \'Name\' characteristic (\'My Bad ! Switch\'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.')
- await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
- await accessoryBadName?.unpublish();
- await accessoryBadName?.destroy();
- });
+ await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED)
+ await accessoryBadName?.unpublish()
+ await accessoryBadName?.destroy()
+ })
- test("Service Name ending with !", async () => {
- const switchService = new Service.Switch("My Bad Switch!");
- const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
- accessoryBadName.addService(switchService);
+ it('service Name ending with !', async () => {
+ const switchService = new Service.Switch('My Bad Switch!')
+ const accessoryBadName = new Accessory('Bad Name', generate('Bad Name'))
+ accessoryBadName.addService(switchService)
const publishInfo: PublishInfo = {
username: serverUsername,
- pincode: "000-00-000",
+ pincode: '000-00-000',
category: Categories.SWITCH,
- advertiser: undefined,
- };
+ advertiser,
+ }
+
+ await accessoryBadName.publish(publishInfo)
+ expect(consoleWarnSpy).toBeCalledTimes(1)
- await accessoryBadName.publish(publishInfo);
- expect(consoleWarnSpy).toBeCalledTimes(1);
- // eslint-disable-next-line max-len
- expect(consoleWarnSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'My Bad Switch!' has an invalid 'Name' characteristic ('My Bad Switch!'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.");
+ expect(consoleWarnSpy).toHaveBeenCalledWith('HAP-NodeJS WARNING: The accessory \'My Bad Switch!\' has an invalid \'Name\' characteristic (\'My Bad Switch!\'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.')
- await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
- await accessoryBadName?.unpublish();
- await accessoryBadName?.destroy();
- });
+ await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED)
+ await accessoryBadName?.unpublish()
+ await accessoryBadName?.destroy()
+ })
- test("Service Name containing '", async () => {
- const switchService = new Service.Switch("My Bad ' Switch");
- const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
- accessoryBadName.addService(switchService);
+ it('service Name containing apostrophe', async () => {
+ const switchService = new Service.Switch('My Bad \' Switch')
+ const accessoryBadName = new Accessory('Bad Name', generate('Bad Name'))
+ accessoryBadName.addService(switchService)
const publishInfo: PublishInfo = {
username: serverUsername,
- pincode: "000-00-000",
+ pincode: '000-00-000',
category: Categories.SWITCH,
- advertiser: undefined,
- };
+ advertiser,
+ }
- await accessoryBadName.publish(publishInfo);
- expect(consoleWarnSpy).toBeCalledTimes(0);
+ await accessoryBadName.publish(publishInfo)
+ expect(consoleWarnSpy).toBeCalledTimes(0)
- await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
- await accessoryBadName?.unpublish();
- await accessoryBadName?.destroy();
- });
+ await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED)
+ await accessoryBadName?.unpublish()
+ await accessoryBadName?.destroy()
+ })
- test("Service Name ending with '", async () => {
- const switchService = new Service.Switch("My Bad Switch'");
- const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
- accessoryBadName.addService(switchService);
+ it('service Name ending with apostrophe', async () => {
+ const switchService = new Service.Switch('My Bad Switch\'')
+ const accessoryBadName = new Accessory('Bad Name', generate('Bad Name'))
+ accessoryBadName.addService(switchService)
const publishInfo: PublishInfo = {
username: serverUsername,
- pincode: "000-00-000",
+ pincode: '000-00-000',
category: Categories.SWITCH,
- advertiser: undefined,
- };
+ advertiser,
+ }
+
+ await accessoryBadName.publish(publishInfo)
+ expect(consoleWarnSpy).toBeCalledTimes(1)
- await accessoryBadName.publish(publishInfo);
- expect(consoleWarnSpy).toBeCalledTimes(1);
- // eslint-disable-next-line max-len
- expect(consoleWarnSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'My Bad Switch'' has an invalid 'Name' characteristic ('My Bad Switch''). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.");
+ expect(consoleWarnSpy).toHaveBeenCalledWith('HAP-NodeJS WARNING: The accessory \'My Bad Switch\'\' has an invalid \'Name\' characteristic (\'My Bad Switch\'\'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.')
- await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
- await accessoryBadName?.unpublish();
- await accessoryBadName?.destroy();
- });
+ await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED)
+ await accessoryBadName?.unpublish()
+ await accessoryBadName?.destroy()
+ })
- test("Service Name beginning with '", async () => {
- const switchService = new Service.Switch("'My Bad Switch");
- const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
- accessoryBadName.addService(switchService);
+ it('service Name beginning with apostrophe', async () => {
+ const switchService = new Service.Switch('\'My Bad Switch')
+ const accessoryBadName = new Accessory('Bad Name', generate('Bad Name'))
+ accessoryBadName.addService(switchService)
const publishInfo: PublishInfo = {
username: serverUsername,
- pincode: "000-00-000",
+ pincode: '000-00-000',
category: Categories.SWITCH,
- advertiser: undefined,
- };
+ advertiser,
+ }
+
+ await accessoryBadName.publish(publishInfo)
+ expect(consoleWarnSpy).toBeCalledTimes(1)
- await accessoryBadName.publish(publishInfo);
- expect(consoleWarnSpy).toBeCalledTimes(1);
- // eslint-disable-next-line max-len
- expect(consoleWarnSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory ''My Bad Switch' has an invalid 'Name' characteristic (''My Bad Switch'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.");
+ expect(consoleWarnSpy).toHaveBeenCalledWith('HAP-NodeJS WARNING: The accessory \'\'My Bad Switch\' has an invalid \'Name\' characteristic (\'\'My Bad Switch\'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.')
- await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
- await accessoryBadName?.unpublish();
- await accessoryBadName?.destroy();
- });
+ await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED)
+ await accessoryBadName?.unpublish()
+ await accessoryBadName?.destroy()
+ })
- test("Service ConfiguredName beginning with '", async () => {
- const switchService = new Service.Switch("My Bad Switch");
- const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
- switchService.addCharacteristic(Characteristic.ConfiguredName);
- accessoryBadName.addService(switchService);
+ it('service ConfiguredName beginning with apostrophe', async () => {
+ const switchService = new Service.Switch('My Bad Switch')
+ const accessoryBadName = new Accessory('Bad Name', generate('Bad Name'))
+ switchService.addCharacteristic(Characteristic.ConfiguredName)
+ accessoryBadName.addService(switchService)
const publishInfo: PublishInfo = {
username: serverUsername,
- pincode: "000-00-000",
+ pincode: '000-00-000',
category: Categories.SWITCH,
- advertiser: undefined,
- };
+ advertiser,
+ }
- await accessoryBadName.publish(publishInfo);
+ await accessoryBadName.publish(publishInfo)
- switchService.getCharacteristic(Characteristic.ConfiguredName).updateValue("'Bad Name");
+ switchService.getCharacteristic(Characteristic.ConfiguredName).updateValue('\'Bad Name')
- expect(consoleWarnSpy).toBeCalledTimes(1);
- // eslint-disable-next-line max-len
- expect(consoleWarnSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'Configured Name' has an invalid 'ConfiguredName' characteristic (''Bad Name'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.");
+ expect(consoleWarnSpy).toBeCalledTimes(1)
- await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
- await accessoryBadName?.unpublish();
- await accessoryBadName?.destroy();
- });
+ expect(consoleWarnSpy).toHaveBeenCalledWith('HAP-NodeJS WARNING: The accessory \'Configured Name\' has an invalid \'ConfiguredName\' characteristic (\'\'Bad Name\'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.')
- });
+ await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED)
+ await accessoryBadName?.unpublish()
+ await accessoryBadName?.destroy()
+ })
+ })
- describe("pairing", () => {
- let defaultPairingInfo: PairingInformation;
+ describe('pairing', () => {
+ let defaultPairingInfo: PairingInformation
beforeEach(() => {
- defaultPairingInfo = { username: clientUsername0, publicKey: clientPublicKey0, permission: PermissionTypes.ADMIN };
- });
+ defaultPairingInfo = { username: clientUsername0, publicKey: clientPublicKey0, permission: PermissionTypes.ADMIN }
+ })
- test("handleInitialPairSetupFinished", async () => {
- const advertiser = new BonjourHAPAdvertiser(accessoryInfoUnpaired);
- advertiser.updateAdvertisement = jest.fn();
- accessory._advertiser = advertiser;
+ it('handleInitialPairSetupFinished', async () => {
+ const advertiser = new BonjourHAPAdvertiser(accessoryInfoUnpaired)
+ advertiser.updateAdvertisement = vi.fn()
+ accessory._advertiser = advertiser
- accessoryInfoUnpaired.addPairedClient = jest.fn();
- accessory._accessoryInfo = accessoryInfoUnpaired;
+ accessoryInfoUnpaired.addPairedClient = vi.fn()
+ accessory._accessoryInfo = accessoryInfoUnpaired
- const publicKey = crypto.randomBytes(32);
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- const callback = jest.fn();
+ const publicKey = randomBytes(32)
+ const callback = vi.fn()
// @ts-expect-error: private access
- accessory.handleInitialPairSetupFinished(clientUsername0, publicKey, callback);
+ accessory.handleInitialPairSetupFinished(clientUsername0, publicKey, callback)
- expect(accessoryInfoUnpaired.addPairedClient).toBeCalledTimes(1);
- expect(accessoryInfoUnpaired.addPairedClient).toBeCalledWith(clientUsername0, publicKey, PermissionTypes.ADMIN);
+ expect(accessoryInfoUnpaired.addPairedClient).toBeCalledTimes(1)
+ expect(accessoryInfoUnpaired.addPairedClient).toBeCalledWith(clientUsername0, publicKey, PermissionTypes.ADMIN)
- expect(saveMock).toBeCalledTimes(1);
+ expect(saveMock).toBeCalledTimes(1)
- expect(advertiser.updateAdvertisement).toBeCalledTimes(1);
+ expect(advertiser.updateAdvertisement).toBeCalledTimes(1)
- await advertiser.destroy();
- });
+ advertiser.destroy()
+ })
- describe("handleAddPairing", () => {
- test("unavailable", () => {
+ describe('handleAddPairing', () => {
+ it('unavailable', () => {
// @ts-expect-error: private access
- accessory.handleAddPairing(connection, clientUsername1, clientPublicKey1, PermissionTypes.USER, callback);
- expect(callback).toBeCalledWith(TLVErrorCode.UNAVAILABLE);
- });
+ accessory.handleAddPairing(connection, clientUsername1, clientPublicKey1, PermissionTypes.USER, callback)
+ expect(callback).toBeCalledWith(TLVErrorCode.UNAVAILABLE)
+ })
- test("missing admin permissions", () => {
- accessory._accessoryInfo = accessoryInfoUnpaired;
+ it('missing admin permissions', () => {
+ accessory._accessoryInfo = accessoryInfoUnpaired
// @ts-expect-error: private access
- accessory.handleAddPairing(connection, clientUsername1, clientPublicKey1, PermissionTypes.USER, callback);
- expect(callback).toBeCalledWith(TLVErrorCode.AUTHENTICATION);
- });
+ accessory.handleAddPairing(connection, clientUsername1, clientPublicKey1, PermissionTypes.USER, callback)
+ expect(callback).toBeCalledWith(TLVErrorCode.AUTHENTICATION)
+ })
- test.each([PermissionTypes.USER, PermissionTypes.ADMIN])(
- "adding pairing: %p", type => {
- accessory._accessoryInfo = accessoryInfoPaired;
+ it.each([PermissionTypes.USER, PermissionTypes.ADMIN])(
+ 'adding pairing: %p',
+ (type) => {
+ accessory._accessoryInfo = accessoryInfoPaired
// @ts-expect-error: private access
- accessory.handleAddPairing(connection, clientUsername1, clientPublicKey1, type, callback);
- expect(callback).toBeCalledWith(0);
+ accessory.handleAddPairing(connection, clientUsername1, clientPublicKey1, type, callback)
+ expect(callback).toBeCalledWith(0)
const expectedPairings: PairingInformation[] = [
defaultPairingInfo,
{ username: clientUsername1, publicKey: clientPublicKey1, permission: type },
- ];
- expect(accessoryInfoPaired.listPairings()).toEqual(expectedPairings);
+ ]
+ expect(accessoryInfoPaired.listPairings()).toEqual(expectedPairings)
expect(accessoryInfoPaired.pairedAdminClients)
- .toEqual(type === PermissionTypes.ADMIN ? 2 : 1);
+ .toEqual(type === PermissionTypes.ADMIN ? 2 : 1)
- expect(saveMock).toBeCalledTimes(1);
- });
+ expect(saveMock).toBeCalledTimes(1)
+ },
+ )
- test.each([
+ it.each([
{ previous: PermissionTypes.USER, update: PermissionTypes.ADMIN },
{ previous: PermissionTypes.ADMIN, update: PermissionTypes.USER },
{ previous: PermissionTypes.USER, update: PermissionTypes.USER },
{ previous: PermissionTypes.ADMIN, update: PermissionTypes.ADMIN },
])(
- "update pairing: %p", type => {
- accessory._accessoryInfo = accessoryInfoPaired;
- accessoryInfoPaired.addPairedClient(clientUsername1, clientPublicKey1, type.previous);
+ 'update pairing: %p',
+ (type) => {
+ accessory._accessoryInfo = accessoryInfoPaired
+ accessoryInfoPaired.addPairedClient(clientUsername1, clientPublicKey1, type.previous)
expect(accessoryInfoPaired.pairedAdminClients)
- .toEqual(type.previous === PermissionTypes.ADMIN ? 2 : 1);
+ .toEqual(type.previous === PermissionTypes.ADMIN ? 2 : 1)
// @ts-expect-error: private access
- accessory.handleAddPairing(connection, clientUsername1, clientPublicKey1, type.update, callback);
- expect(callback).toBeCalledWith(0);
+ accessory.handleAddPairing(connection, clientUsername1, clientPublicKey1, type.update, callback)
+ expect(callback).toBeCalledWith(0)
const expectedPairings: PairingInformation[] = [
defaultPairingInfo,
{ username: clientUsername1, publicKey: clientPublicKey1, permission: type.update },
- ];
- expect(accessoryInfoPaired.listPairings()).toEqual(expectedPairings);
+ ]
+ expect(accessoryInfoPaired.listPairings()).toEqual(expectedPairings)
expect(accessoryInfoPaired.pairedAdminClients)
- .toEqual(type.update === PermissionTypes.ADMIN ? 2 : 1);
+ .toEqual(type.update === PermissionTypes.ADMIN ? 2 : 1)
- expect(saveMock).toBeCalledTimes(1);
- });
+ expect(saveMock).toBeCalledTimes(1)
+ },
+ )
- test("update permission with non-matching public key", () => {
- accessory._accessoryInfo = accessoryInfoPaired;
- accessoryInfoPaired.addPairedClient(clientUsername1, clientPublicKey1, PermissionTypes.USER);
+ it('update permission with non-matching public key', () => {
+ accessory._accessoryInfo = accessoryInfoPaired
+ accessoryInfoPaired.addPairedClient(clientUsername1, clientPublicKey1, PermissionTypes.USER)
// @ts-expect-error: private access
- accessory.handleAddPairing(connection, clientUsername1, crypto.randomBytes(32), PermissionTypes.ADMIN, callback);
- expect(callback).toBeCalledWith(TLVErrorCode.UNKNOWN);
- });
- });
+ accessory.handleAddPairing(connection, clientUsername1, randomBytes(32), PermissionTypes.ADMIN, callback)
+ expect(callback).toBeCalledWith(TLVErrorCode.UNKNOWN)
+ })
+ })
- describe("handleRemovePairing", () => {
- let storage: typeof EventedHTTPServer.destroyExistingConnectionsAfterUnpair;
+ describe('handleRemovePairing', () => {
+ let storage: typeof EventedHTTPServer.destroyExistingConnectionsAfterUnpair
beforeEach(() => {
- storage = EventedHTTPServer.destroyExistingConnectionsAfterUnpair;
- EventedHTTPServer.destroyExistingConnectionsAfterUnpair = jest.fn();
- });
+ storage = EventedHTTPServer.destroyExistingConnectionsAfterUnpair
+ EventedHTTPServer.destroyExistingConnectionsAfterUnpair = vi.fn()
+ })
afterEach(() => {
- EventedHTTPServer.destroyExistingConnectionsAfterUnpair = storage;
- });
+ EventedHTTPServer.destroyExistingConnectionsAfterUnpair = storage
+ })
- test("unavailable", () => {
+ it('unavailable', () => {
// @ts-expect-error: private access
- accessory.handleRemovePairing(connection, clientUsername1, callback);
- expect(callback).toBeCalledWith(TLVErrorCode.UNAVAILABLE);
- });
+ accessory.handleRemovePairing(connection, clientUsername1, callback)
+ expect(callback).toBeCalledWith(TLVErrorCode.UNAVAILABLE)
+ })
- test("missing admin permissions", () => {
- accessory._accessoryInfo = accessoryInfoUnpaired;
+ it('missing admin permissions', () => {
+ accessory._accessoryInfo = accessoryInfoUnpaired
// @ts-expect-error: private access
- accessory.handleRemovePairing(connection, clientUsername1, callback);
- expect(callback).toBeCalledWith(TLVErrorCode.AUTHENTICATION);
- });
-
- test.each([PermissionTypes.ADMIN, PermissionTypes.USER])(
- "remove pairing: %s", type => {
- const count = type === PermissionTypes.ADMIN ? 2 : 1;
- accessory._accessoryInfo = accessoryInfoPaired;
- accessoryInfoPaired.addPairedClient(clientUsername1, clientPublicKey1, type);
- expect(accessoryInfoPaired.pairedAdminClients).toEqual(count);
+ accessory.handleRemovePairing(connection, clientUsername1, callback)
+ expect(callback).toBeCalledWith(TLVErrorCode.AUTHENTICATION)
+ })
+
+ it.each([PermissionTypes.ADMIN, PermissionTypes.USER])(
+ 'remove pairing: %s',
+ (type) => {
+ const count = type === PermissionTypes.ADMIN ? 2 : 1
+ accessory._accessoryInfo = accessoryInfoPaired
+ accessoryInfoPaired.addPairedClient(clientUsername1, clientPublicKey1, type)
+ expect(accessoryInfoPaired.pairedAdminClients).toEqual(count)
// @ts-expect-error: private access
- accessory.handleRemovePairing(connection, clientUsername1, callback);
- expect(callback).toBeCalledWith(0);
+ accessory.handleRemovePairing(connection, clientUsername1, callback)
+ expect(callback).toBeCalledWith(0)
expect(accessoryInfoPaired.listPairings())
- .toEqual([ defaultPairingInfo ]);
- expect(accessoryInfoPaired.pairedAdminClients).toEqual(1);
+ .toEqual([defaultPairingInfo])
+ expect(accessoryInfoPaired.pairedAdminClients).toEqual(1)
- expect(EventedHTTPServer.destroyExistingConnectionsAfterUnpair).toBeCalledTimes(1);
+ expect(EventedHTTPServer.destroyExistingConnectionsAfterUnpair).toBeCalledTimes(1)
expect(EventedHTTPServer.destroyExistingConnectionsAfterUnpair)
- .toBeCalledWith(connection, clientUsername1);
- });
+ .toBeCalledWith(connection, clientUsername1)
+ },
+ )
- test("remove last ADMIN pairing", () => {
- accessory._accessoryInfo = accessoryInfoPaired;
- accessoryInfoPaired.addPairedClient(clientUsername1, clientPublicKey1, PermissionTypes.USER);
+ it('remove last ADMIN pairing', () => {
+ accessory._accessoryInfo = accessoryInfoPaired
+ accessoryInfoPaired.addPairedClient(clientUsername1, clientPublicKey1, PermissionTypes.USER)
// a mock which just forwards to the normal function call, so that data integrity is ensured
- const _removePairedClient0Mock = jest.fn();
+ const _removePairedClient0Mock = vi.fn()
// @ts-expect-error: private access
- _removePairedClient0Mock.mockImplementation(accessoryInfoPaired._removePairedClient0);
+ _removePairedClient0Mock.mockImplementation(accessoryInfoPaired._removePairedClient0)
// @ts-expect-error: private access
- accessoryInfoPaired._removePairedClient0 = _removePairedClient0Mock;
+ accessoryInfoPaired._removePairedClient0 = _removePairedClient0Mock
- expect(accessoryInfoPaired.pairedAdminClients).toEqual(1);
+ expect(accessoryInfoPaired.pairedAdminClients).toEqual(1)
// after we removed pairing we also expect that the accessory is advertised as unpaired
- const advertiser = new BonjourHAPAdvertiser(accessoryInfoUnpaired);
- advertiser.updateAdvertisement = jest.fn();
- accessory._advertiser = advertiser;
- const eventMock = jest.fn();
- accessory.on(AccessoryEventTypes.UNPAIRED, eventMock);
+ const advertiser = new BonjourHAPAdvertiser(accessoryInfoUnpaired)
+ advertiser.updateAdvertisement = vi.fn()
+ accessory._advertiser = advertiser
+ const eventMock = vi.fn()
+ accessory.on(AccessoryEventTypes.UNPAIRED, eventMock)
// @ts-expect-error: private access
- accessory.handleRemovePairing(connection, clientUsername0, callback);
- expect(callback).toBeCalledWith(0);
+ accessory.handleRemovePairing(connection, clientUsername0, callback)
+ expect(callback).toBeCalledWith(0)
- expect(accessoryInfoPaired.listPairings()).toEqual([]);
- expect(accessoryInfoPaired.pairedAdminClients).toEqual(0);
+ expect(accessoryInfoPaired.listPairings()).toEqual([])
+ expect(accessoryInfoPaired.pairedAdminClients).toEqual(0)
// verify calls to _removePairedClient0
- expect(_removePairedClient0Mock).toBeCalledTimes(2);
+ expect(_removePairedClient0Mock).toBeCalledTimes(2)
expect(_removePairedClient0Mock)
- .toHaveBeenNthCalledWith(1, connection, clientUsername0);
+ .toHaveBeenNthCalledWith(1, connection, clientUsername0)
expect(_removePairedClient0Mock)
- .toHaveBeenNthCalledWith(2, connection, clientUsername1); // it shall also remove the user pairing
+ .toHaveBeenNthCalledWith(2, connection, clientUsername1) // it shall also remove the user pairing
- expect(EventedHTTPServer.destroyExistingConnectionsAfterUnpair).toBeCalledTimes(2);
+ expect(EventedHTTPServer.destroyExistingConnectionsAfterUnpair).toBeCalledTimes(2)
// verify that accessory is marked as unpaired again
- expect(advertiser.updateAdvertisement).toBeCalledTimes(1);
- expect(eventMock).toBeCalledTimes(1);
- });
- });
+ expect(advertiser.updateAdvertisement).toBeCalledTimes(1)
+ expect(eventMock).toBeCalledTimes(1)
+ })
+ })
- describe("handleListPairings", () => {
- test("unavailable", () => {
+ describe('handleListPairings', () => {
+ it('unavailable', () => {
// @ts-expect-error: private access
- accessory.handleListPairings(connection, callback);
- expect(callback).toBeCalledWith(TLVErrorCode.UNAVAILABLE);
- });
+ accessory.handleListPairings(connection, callback)
+ expect(callback).toBeCalledWith(TLVErrorCode.UNAVAILABLE)
+ })
- test("missing admin permissions", () => {
- accessory._accessoryInfo = accessoryInfoUnpaired;
+ it('missing admin permissions', () => {
+ accessory._accessoryInfo = accessoryInfoUnpaired
// @ts-expect-error: private access
- accessory.handleListPairings(connection, callback);
- expect(callback).toBeCalledWith(TLVErrorCode.AUTHENTICATION);
+ accessory.handleListPairings(connection, callback)
+ expect(callback).toBeCalledWith(TLVErrorCode.AUTHENTICATION)
- accessory._accessoryInfo = accessoryInfoPaired;
- accessoryInfoPaired.addPairedClient(clientUsername1, clientPublicKey1, PermissionTypes.USER);
- connection.username = clientUsername1;
+ accessory._accessoryInfo = accessoryInfoPaired
+ accessoryInfoPaired.addPairedClient(clientUsername1, clientPublicKey1, PermissionTypes.USER)
+ connection.username = clientUsername1
// @ts-expect-error: private access
- accessory.handleListPairings(connection, callback);
- expect(callback).toBeCalledWith(TLVErrorCode.AUTHENTICATION);
- });
+ accessory.handleListPairings(connection, callback)
+ expect(callback).toBeCalledWith(TLVErrorCode.AUTHENTICATION)
+ })
- test("list pairings", () => {
- accessory._accessoryInfo = accessoryInfoPaired;
+ it('list pairings', () => {
+ accessory._accessoryInfo = accessoryInfoPaired
// @ts-expect-error: private access
- accessory.handleListPairings(connection, callback);
- expect(callback).toBeCalledWith(0, [ defaultPairingInfo ]);
- });
- });
- });
+ accessory.handleListPairings(connection, callback)
+ expect(callback).toBeCalledWith(0, [defaultPairingInfo])
+ })
+ })
+ })
- describe("published switch service", () => {
- let switchService: Service;
- let onCharacteristic: Characteristic;
+ describe('published switch service', () => {
+ let switchService: Service
+ let onCharacteristic: Characteristic
- const aid = 1;
+ const aid = 1
const iids = {
accessoryInformation: 1,
identify: 2,
@@ -834,114 +853,115 @@ describe("Accessory", () => {
protocolInformation: 11,
version: 12,
- };
+ }
beforeEach(() => {
- const loadBackup = AccessoryInfo.load;
- AccessoryInfo.load = jest.fn(() => {
+ const loadBackup = AccessoryInfo.load
+ AccessoryInfo.load = vi.fn(() => {
// inject our mocked accessoryInfo object
- return accessoryInfoPaired;
- });
+ return accessoryInfoPaired
+ })
const publishInfo: PublishInfo = {
username: serverUsername,
- pincode: "123-45-678",
+ pincode: '123-45-678',
category: Categories.SWITCH,
advertiser: MDNSAdvertiser.BONJOUR,
- };
+ }
- switchService = new Service.Switch("Switch");
- accessory.addService(switchService);
+ switchService = new Service.Switch('Switch')
+ accessory.addService(switchService)
- onCharacteristic = switchService.getCharacteristic(Characteristic.On);
+ onCharacteristic = switchService.getCharacteristic(Characteristic.On)
- accessory.publish(publishInfo);
+ accessory.publish(publishInfo)
- AccessoryInfo.load = loadBackup;
+ AccessoryInfo.load = loadBackup
// saveMock may be called in `publish`
- saveMock.mockReset();
+ saveMock.mockReset()
- expect(aid).toEqual(accessory.aid);
- });
+ expect(aid).toEqual(accessory.aid)
+ })
- test("purgeUnusedIDs", () => {
- expect(accessory.shouldPurgeUnusedIDs).toBeTruthy();
- accessory.purgeUnusedIDs();
- expect(accessory.shouldPurgeUnusedIDs).toBeTruthy();
+ it('purgeUnusedIDs', () => {
+ expect(accessory.shouldPurgeUnusedIDs).toBeTruthy()
+ accessory.purgeUnusedIDs()
+ expect(accessory.shouldPurgeUnusedIDs).toBeTruthy()
- accessory.disableUnusedIDPurge();
+ accessory.disableUnusedIDPurge()
- expect(accessory.shouldPurgeUnusedIDs).toBeFalsy();
- accessory.purgeUnusedIDs();
- expect(accessory.shouldPurgeUnusedIDs).toBeFalsy();
+ expect(accessory.shouldPurgeUnusedIDs).toBeFalsy()
+ accessory.purgeUnusedIDs()
+ expect(accessory.shouldPurgeUnusedIDs).toBeFalsy()
- accessory.enableUnusedIDPurge();
- expect(accessory.shouldPurgeUnusedIDs).toBeTruthy();
- });
+ accessory.enableUnusedIDPurge()
+ expect(accessory.shouldPurgeUnusedIDs).toBeTruthy()
+ })
- test("setupURI", () => {
- let setupURI = accessory.setupURI();
+ it('setupURI', () => {
+ let setupURI = accessory.setupURI()
- const originalSetupURI = setupURI;
+ const originalSetupURI = setupURI
- expect(setupURI.startsWith("X-HM://")).toBeTruthy();
- setupURI = setupURI.substring(7);
+ expect(setupURI.startsWith('X-HM://')).toBeTruthy()
+ setupURI = setupURI.substring(7)
- const encodedPayload = setupURI.substring(0, 9);
- const setupId = setupURI.substring(9);
+ const encodedPayload = setupURI.substring(0, 9)
+ const setupId = setupURI.substring(9)
- expect(setupId).toEqual(accessory._setupID);
+ expect(setupId).toEqual(accessory._setupID)
- const payload = parseInt(encodedPayload, 36);
- const low = payload & 0xFFFFFFFF;
- const high = (payload - low) / 0x100000000;
+ const payload = Number.parseInt(encodedPayload, 36)
+ const low = payload & 0xFFFFFFFF
+ const high = (payload - low) / 0x100000000
- const setupCode = low & 0x7FFFFFF;
- const pairedWithController = (low >> 27) & 0x01;
- const supportsIP = (low >> 28) & 0x01;
- const supportsBLE = (low >> 29) & 0x01;
- const supportsWAC = (low >> 30) & 0x01;
- const category = ((low >> 31) & 0x01) + ((high & 0x7F) << 1);
- const reserved = (high >> 8) & 0xF;
- const version = (high >> 12) & 0x7;
+ const setupCode = low & 0x7FFFFFF
+ const pairedWithController = (low >> 27) & 0x01
+ const supportsIP = (low >> 28) & 0x01
+ const supportsBLE = (low >> 29) & 0x01
+ const supportsWAC = (low >> 30) & 0x01
+ const category = ((low >> 31) & 0x01) + ((high & 0x7F) << 1)
+ const reserved = (high >> 8) & 0xF
+ const version = (high >> 12) & 0x7
- expect(setupCode).toEqual(parseInt(accessory._accessoryInfo!.pincode.replace(/-/g, ""), 10));
- expect(pairedWithController).toEqual(0);
- expect(supportsIP).toEqual(1);
- expect(supportsBLE).toEqual(0);
- expect(supportsWAC).toEqual(0);
- expect(category).toEqual(Categories.SWITCH);
- expect(reserved).toEqual(0);
- expect(version).toEqual(0);
+ expect(setupCode).toEqual(Number.parseInt(accessory._accessoryInfo!.pincode.replace(/-/g, ''), 10))
+ expect(pairedWithController).toEqual(0)
+ expect(supportsIP).toEqual(1)
+ expect(supportsBLE).toEqual(0)
+ expect(supportsWAC).toEqual(0)
+ expect(category).toEqual(Categories.SWITCH)
+ expect(reserved).toEqual(0)
+ expect(version).toEqual(0)
- expect(accessory.setupURI()).toEqual(originalSetupURI);
- });
+ expect(accessory.setupURI()).toEqual(originalSetupURI)
+ })
- describe("handleAccessories", () => {
+ describe('handleAccessories', () => {
const characteristicHAPInfo = async (
characteristicConstructor: new () => Characteristic,
iid: number,
value?: CharacteristicValue,
- ): Promise => {
- const characteristic = new characteristicConstructor();
+ ): Promise => { // eslint-disable-line unicorn/consistent-function-scoping
+ // eslint-disable-next-line new-cap
+ const characteristic = new characteristicConstructor()
if (value !== undefined) {
- characteristic.value = value;
+ characteristic.value = value
}
- characteristic.iid = iid;
- return characteristic.toHAP(connection);
- };
+ characteristic.iid = iid
+ return characteristic.toHAP(connection)
+ }
- test("test accessories database retrieval", async () => {
+ it('test accessories database retrieval', async () => {
// @ts-expect-error: private access
- accessory.handleAccessories(connection, callback);
+ accessory.handleAccessories(connection, callback)
- await callbackPromise;
+ await callbackPromise
// TODO test Service.toHAP and Characteristic.toHAP separately!
const expected: AccessoriesResponse = {
accessories: [{
- aid: aid,
+ aid,
services: [
{
iid: iids.accessoryInformation,
@@ -963,7 +983,7 @@ describe("Accessory", () => {
hidden: undefined,
primary: undefined,
characteristics: [
- await characteristicHAPInfo(Characteristic.Name, iids.switchName, "Switch"),
+ await characteristicHAPInfo(Characteristic.Name, iids.switchName, 'Switch'),
await characteristicHAPInfo(Characteristic.On, iids.on, false),
],
},
@@ -973,22 +993,22 @@ describe("Accessory", () => {
hidden: undefined,
primary: undefined,
characteristics: [
- await characteristicHAPInfo(Characteristic.Version, iids.version, "1.1.0"),
+ await characteristicHAPInfo(Characteristic.Version, iids.version, '1.1.0'),
],
},
],
}],
- };
- expect(callback).toBeCalledTimes(1);
- expect(callback).toBeCalledWith(undefined, expected);
- });
- });
+ }
+ expect(callback).toBeCalledTimes(1)
+ expect(callback).toBeCalledWith(undefined, expected)
+ })
+ })
- describe("handleGetCharacteristic", () => {
+ describe('handleGetCharacteristic', () => {
const testRequestResponse = async (
request: Partial,
...expectedReadData: CharacteristicReadData[]
- ): Promise => {
+ ): Promise => { // eslint-disable-line unicorn/consistent-function-scoping
// @ts-expect-error: private access
accessory.handleGetCharacteristics(connection, {
ids: [],
@@ -997,104 +1017,104 @@ describe("Accessory", () => {
includeType: false,
includePerms: false,
...request,
- }, callback);
+ }, callback)
- await callbackPromise;
+ await callbackPromise
const expectedResponse: CharacteristicsReadResponse = {
characteristics: expectedReadData,
- };
+ }
- expect(callback).toBeCalledTimes(1);
- expect(callback).toBeCalledWith(undefined, expectedResponse);
+ expect(callback).toBeCalledTimes(1)
+ expect(callback).toBeCalledWith(undefined, expectedResponse)
- callback.mockReset();
- };
+ callback.mockReset()
+ }
- test("read Switch.On characteristic", async () => {
+ it('read Switch.On characteristic', async () => {
await testRequestResponse({
- ids: [{ aid: aid, iid: iids.on }],
+ ids: [{ aid, iid: iids.on }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
value: 0,
- });
+ })
// testing that errors are forwarded properly!
onCharacteristic.onGet(() => {
- throw new HapStatusError(HAPStatus.OUT_OF_RESOURCE);
- });
+ throw new HapStatusError(HAPStatus.OUT_OF_RESOURCE)
+ })
await testRequestResponse({
- ids: [{ aid: aid, iid: iids.on }],
+ ids: [{ aid, iid: iids.on }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.OUT_OF_RESOURCE,
- });
- });
+ })
+ })
- test("read non-existent characteristic", async () => {
+ it('read non-existent characteristic', async () => {
await testRequestResponse({
ids: [{ aid: 2, iid: iids.on }],
}, {
aid: 2,
iid: iids.on,
status: HAPStatus.INVALID_VALUE_IN_REQUEST,
- });
+ })
await testRequestResponse({
- ids: [{ aid: aid, iid: 15 }],
+ ids: [{ aid, iid: 15 }],
}, {
- aid: aid,
+ aid,
iid: 15,
status: HAPStatus.INVALID_VALUE_IN_REQUEST,
- });
- });
+ })
+ })
- test("reading write-only characteristic", async () => {
+ it('reading write-only characteristic', async () => {
await testRequestResponse({
- ids: [{ aid: aid, iid: iids.identify }],
+ ids: [{ aid, iid: iids.identify }],
}, {
- aid: aid,
+ aid,
iid: iids.identify,
status: HAPStatus.WRITE_ONLY_CHARACTERISTIC,
- });
- });
+ })
+ })
- test("read includeMeta, includePerms, includeType, includeEvent", async () => {
- const ids = [{ aid: aid, iid: iids.on }];
+ it('read includeMeta, includePerms, includeType, includeEvent', async () => {
+ const ids = [{ aid, iid: iids.on }]
const partialExpectedResponse = {
- aid: aid,
+ aid,
iid: iids.on,
value: 0,
- };
+ }
- const customCharacteristic = new Characteristic("Custom", uuid.generate("custom"), {
+ const customCharacteristic = new Characteristic('Custom', generate('custom'), {
format: Formats.UINT64,
perms: [Perms.PAIRED_READ],
unit: Units.SECONDS,
minValue: 10,
maxValue: 100,
minStep: 2,
- });
- customCharacteristic.value = 20;
- switchService.addCharacteristic(customCharacteristic);
- accessory._assignIDs(accessory._identifierCache!);
+ })
+ customCharacteristic.value = 20
+ switchService.addCharacteristic(customCharacteristic)
+ accessory._assignIDs(accessory._identifierCache!)
await testRequestResponse({
- ids: ids,
+ ids,
includeMeta: true,
}, {
...partialExpectedResponse,
format: Formats.BOOL,
- });
+ })
await testRequestResponse({
- ids: [{ aid: aid, iid: customCharacteristic.iid! }],
+ ids: [{ aid, iid: customCharacteristic.iid! }],
includeMeta: true,
}, {
- aid: aid,
+ aid,
iid: customCharacteristic.iid!,
value: 20,
format: Formats.UINT64,
@@ -1102,1140 +1122,1147 @@ describe("Accessory", () => {
minValue: 10,
maxValue: 100,
minStep: 2,
- });
+ })
await testRequestResponse({
- ids: [{ aid: aid, iid: iids.serialNumber }],
+ ids: [{ aid, iid: iids.serialNumber }],
includeMeta: true,
}, {
- aid: aid,
+ aid,
iid: 6,
- value: "Default-SerialNumber",
+ value: 'Default-SerialNumber',
format: Formats.STRING,
maxLen: 64,
- });
+ })
await testRequestResponse({
- ids: ids,
+ ids,
includePerms: true,
}, {
...partialExpectedResponse,
perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
await testRequestResponse({
- ids: ids,
+ ids,
includeType: true,
}, {
...partialExpectedResponse,
type: toShortForm(Characteristic.On.UUID),
- });
-
+ })
- const hasEventNotificationsMock: Mock = jest.fn();
- connection.hasEventNotifications = hasEventNotificationsMock;
+ // @ts-expect-error Generic type Mock requires between 0 and 1 type arguments
+ const hasEventNotificationsMock: Mock = vi.fn()
+ connection.hasEventNotifications = hasEventNotificationsMock
- hasEventNotificationsMock.mockImplementationOnce(() => true);
+ hasEventNotificationsMock.mockImplementationOnce(() => true)
await testRequestResponse({
- ids: ids,
+ ids,
includeEvent: true,
}, {
...partialExpectedResponse,
ev: true,
- });
- expect(hasEventNotificationsMock).toHaveBeenCalledWith(aid, iids.on);
- hasEventNotificationsMock.mockReset();
+ })
+ expect(hasEventNotificationsMock).toHaveBeenCalledWith(aid, iids.on)
+ hasEventNotificationsMock.mockReset()
- hasEventNotificationsMock.mockImplementationOnce(() => false);
+ hasEventNotificationsMock.mockImplementationOnce(() => false)
await testRequestResponse({
- ids: ids,
+ ids,
includeEvent: true,
}, {
...partialExpectedResponse,
ev: false,
- });
- expect(hasEventNotificationsMock).toHaveBeenCalledWith(aid, iids.on);
- });
+ })
+ expect(hasEventNotificationsMock).toHaveBeenCalledWith(aid, iids.on)
+ })
- test("reading adminOnly", async () => {
+ it('reading adminOnly', async () => {
onCharacteristic.setProps({
adminOnlyAccess: [Access.READ],
- });
+ })
await testRequestResponse({
- ids: [{ aid: aid, iid: iids.on }],
+ ids: [{ aid, iid: iids.on }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
value: 0,
- });
+ })
- connection.username = clientUsername1;
+ connection.username = clientUsername1
await testRequestResponse({
- ids: [{ aid: aid, iid: iids.on }],
+ ids: [{ aid, iid: iids.on }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INSUFFICIENT_PRIVILEGES,
- });
+ })
- connection.username = undefined;
- accessory._accessoryInfo = undefined;
+ connection.username = undefined
+ accessory._accessoryInfo = undefined
await testRequestResponse({
- ids: [{ aid: aid, iid: iids.on }],
+ ids: [{ aid, iid: iids.on }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INSUFFICIENT_PRIVILEGES,
- });
- });
- });
+ })
+ })
+ })
- describe("handleSetCharacteristic", () => {
- let consoleWarnSpy: jest.SpyInstance;
+ describe('handleSetCharacteristic', () => {
+ let consoleWarnSpy: MockInstance
beforeEach(() => {
// Mock console.warn before each test that needs it
- consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
- });
+ consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
+ })
afterEach(() => {
// Restore console.warn after each test
- consoleWarnSpy.mockRestore();
- });
+ consoleWarnSpy.mockRestore()
+ })
const testRequestResponse = async (
request: Partial,
...expectedReadData: CharacteristicWriteData[]
- ): Promise => {
+ ): Promise => { // eslint-disable-line unicorn/consistent-function-scoping
// @ts-expect-error: private access
accessory.handleSetCharacteristics(connection, {
characteristics: [],
...request,
- }, callback);
+ }, callback)
- await callbackPromise;
+ await callbackPromise
const expectedResponse: CharacteristicsWriteResponse = {
characteristics: expectedReadData,
- };
+ }
- expect(callback).toBeCalledTimes(1);
- expect(callback).toBeCalledWith(undefined, expectedResponse);
+ expect(callback).toBeCalledTimes(1)
+ expect(callback).toBeCalledWith(undefined, expectedResponse)
- callback.mockReset();
- };
+ callback.mockReset()
+ }
- test("write Switch.On characteristic", async () => {
+ it('write Switch.On characteristic', async () => {
await testRequestResponse({
characteristics: [{
- aid: aid,
+ aid,
iid: iids.on,
value: true,
}],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
- });
+ })
- expect(onCharacteristic.value).toEqual(true);
+ expect(onCharacteristic.value).toEqual(true)
// testing that errors are forwarder properly
onCharacteristic.onSet(() => {
- throw new HapStatusError(HAPStatus.RESOURCE_BUSY);
- });
+ throw new HapStatusError(HAPStatus.RESOURCE_BUSY)
+ })
await testRequestResponse({
characteristics: [{
- aid: aid,
+ aid,
iid: iids.on,
value: true,
}],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.RESOURCE_BUSY,
- });
- });
+ })
+ })
- test.each([true, false])(
- "write-response characteristic with requesting r: %s", async rValue => {
- onCharacteristic.props.perms.push(Perms.WRITE_RESPONSE);
+ it.each([true, false])(
+ 'write-response characteristic with requesting r: %s',
+ async (rValue) => {
+ onCharacteristic.props.perms.push(Perms.WRITE_RESPONSE)
onCharacteristic.on(CharacteristicEventTypes.SET, (value, callback) => {
- expect(value).toEqual(false);
- callback(undefined, true);
- });
+ expect(value).toEqual(false)
+ callback(undefined, true)
+ })
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: false, r: rValue }],
+ characteristics: [{ aid, iid: iids.on, value: false, r: rValue }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
value: rValue ? 1 : undefined,
- });
- });
+ })
+ },
+ )
- test("requesting write-response on non-write-response characteristic", async () => {
+ it('requesting write-response on non-write-response characteristic', async () => {
onCharacteristic.onSet(() => {
- return true; // write response
- });
+ return true // write response
+ })
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: false, r: true }],
+ characteristics: [{ aid, iid: iids.on, value: false, r: true }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
- });
- });
+ })
+ })
- test("write non-existent characteristic", async () => {
+ it('write non-existent characteristic', async () => {
await testRequestResponse({
characteristics: [{ aid: 2, iid: iids.on, value: true }],
}, {
aid: 2,
iid: iids.on,
status: HAPStatus.INVALID_VALUE_IN_REQUEST,
- });
+ })
await testRequestResponse({
- characteristics: [{ aid: aid, iid: 15, value: true }],
+ characteristics: [{ aid, iid: 15, value: true }],
}, {
- aid: aid,
+ aid,
iid: 15,
status: HAPStatus.INVALID_VALUE_IN_REQUEST,
- });
- });
+ })
+ })
- test("writing read-only characteristic", async () => {
+ it('writing read-only characteristic', async () => {
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.model, value: "New Model" }],
+ characteristics: [{ aid, iid: iids.model, value: 'New Model' }],
}, {
- aid: aid,
+ aid,
iid: iids.model,
status: HAPStatus.READ_ONLY_CHARACTERISTIC,
- });
- });
+ })
+ })
- test("writing adminOnly", async () => {
+ it('writing adminOnly', async () => {
onCharacteristic.setProps({
adminOnlyAccess: [Access.WRITE],
- });
+ })
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: true }],
+ characteristics: [{ aid, iid: iids.on, value: true }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
- });
+ })
- connection.username = clientUsername1; // changing to non-adming username
+ connection.username = clientUsername1 // changing to non-adming username
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: true }],
+ characteristics: [{ aid, iid: iids.on, value: true }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INSUFFICIENT_PRIVILEGES,
- });
+ })
- connection.username = undefined;
- accessory._accessoryInfo = undefined;
+ connection.username = undefined
+ accessory._accessoryInfo = undefined
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: true }],
+ characteristics: [{ aid, iid: iids.on, value: true }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INSUFFICIENT_PRIVILEGES,
- });
- });
+ })
+ })
- test("enabling and disabling event notifications", async () => {
- const hasEventNotificationsMock: Mock = jest.fn();
- connection.hasEventNotifications = hasEventNotificationsMock;
- connection.enableEventNotifications = jest.fn();
- connection.disableEventNotifications = jest.fn();
+ it('enabling and disabling event notifications', async () => {
+ // @ts-expect-error Generic type Mock requires between 0 and 1 type arguments
+ const hasEventNotificationsMock: Mock = vi.fn()
+ connection.hasEventNotifications = hasEventNotificationsMock
+ connection.enableEventNotifications = vi.fn()
+ connection.disableEventNotifications = vi.fn()
- hasEventNotificationsMock.mockImplementationOnce(() => false);
+ hasEventNotificationsMock.mockImplementationOnce(() => false)
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, ev: false }],
+ characteristics: [{ aid, iid: iids.on, ev: false }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
- });
- expect(connection.enableEventNotifications).not.toBeCalled();
- expect(connection.disableEventNotifications).not.toBeCalled();
+ })
+ expect(connection.enableEventNotifications).not.toBeCalled()
+ expect(connection.disableEventNotifications).not.toBeCalled()
// @ts-expect-error: private access
- expect(onCharacteristic.subscriptions).toEqual(0);
+ expect(onCharacteristic.subscriptions).toEqual(0)
- hasEventNotificationsMock.mockImplementationOnce(() => false);
+ hasEventNotificationsMock.mockImplementationOnce(() => false)
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, ev: true }],
+ characteristics: [{ aid, iid: iids.on, ev: true }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
- });
- expect(connection.enableEventNotifications).toHaveBeenCalledWith(aid, iids.on);
+ })
+ expect(connection.enableEventNotifications).toHaveBeenCalledWith(aid, iids.on)
// @ts-expect-error: private access
- expect(onCharacteristic.subscriptions).toEqual(1);
+ expect(onCharacteristic.subscriptions).toEqual(1)
- hasEventNotificationsMock.mockImplementationOnce(() => true);
+ hasEventNotificationsMock.mockImplementationOnce(() => true)
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, ev: true }],
+ characteristics: [{ aid, iid: iids.on, ev: true }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
- });
- expect(connection.enableEventNotifications).toBeCalledTimes(1); // >stays< at 1 invocation
- expect(connection.disableEventNotifications).not.toBeCalled();
+ })
+ expect(connection.enableEventNotifications).toBeCalledTimes(1) // >stays< at 1 invocation
+ expect(connection.disableEventNotifications).not.toBeCalled()
// @ts-expect-error: private access
- expect(onCharacteristic.subscriptions).toEqual(1);
+ expect(onCharacteristic.subscriptions).toEqual(1)
- hasEventNotificationsMock.mockImplementationOnce(() => true);
+ hasEventNotificationsMock.mockImplementationOnce(() => true)
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, ev: false }],
+ characteristics: [{ aid, iid: iids.on, ev: false }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
- });
- expect(connection.disableEventNotifications).toHaveBeenCalledWith(aid, iids.on);
+ })
+ expect(connection.disableEventNotifications).toHaveBeenCalledWith(aid, iids.on)
// @ts-expect-error: private access
- expect(onCharacteristic.subscriptions).toEqual(0);
- });
+ expect(onCharacteristic.subscriptions).toEqual(0)
+ })
- test("unsupported event notifications", async () => {
- connection.hasEventNotifications = jest.fn(() => false);
+ it('unsupported event notifications', async () => {
+ connection.hasEventNotifications = vi.fn(() => false)
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.model, ev: true }],
+ characteristics: [{ aid, iid: iids.model, ev: true }],
}, {
- aid: aid,
+ aid,
iid: iids.model,
status: HAPStatus.NOTIFICATION_NOT_SUPPORTED,
- });
+ })
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.model, ev: false }],
+ characteristics: [{ aid, iid: iids.model, ev: false }],
}, {
- aid: aid,
+ aid,
iid: iids.model,
status: HAPStatus.NOTIFICATION_NOT_SUPPORTED,
- });
- });
+ })
+ })
- test("clearing event notifications on disconnect", async () => {
- const hasEventNotificationsMock: Mock = jest.fn();
- connection.hasEventNotifications = hasEventNotificationsMock;
- connection.enableEventNotifications = jest.fn();
+ it('clearing event notifications on disconnect', async () => {
+ // @ts-expect-error Generic type Mock requires between 0 and 1 type arguments
+ const hasEventNotificationsMock: Mock = vi.fn()
+ connection.hasEventNotifications = hasEventNotificationsMock
+ connection.enableEventNotifications = vi.fn()
- hasEventNotificationsMock.mockImplementationOnce(() => false);
+ hasEventNotificationsMock.mockImplementationOnce(() => false)
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, ev: true }],
+ characteristics: [{ aid, iid: iids.on, ev: true }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
- });
- expect(connection.enableEventNotifications).toBeCalled();
+ })
+ expect(connection.enableEventNotifications).toBeCalled()
// @ts-expect-error: private access
- expect(onCharacteristic.subscriptions).toEqual(1);
+ expect(onCharacteristic.subscriptions).toEqual(1)
- connection.getRegisteredEvents = jest.fn(() => {
- return new Set([aid + "." + iids.on]);
- });
- connection.clearRegisteredEvents = jest.fn();
+ connection.getRegisteredEvents = vi.fn(() => {
+ return new Set([`${aid}.${iids.on}`])
+ })
+ connection.clearRegisteredEvents = vi.fn()
// @ts-expect-error: private access
- const originalImplementation = accessory.findCharacteristic.bind(accessory);
+ const originalImplementation = accessory.findCharacteristic.bind(accessory)
// @ts-expect-error: private access
- accessory.findCharacteristic = jest.fn((aid, iid) => {
- return originalImplementation(aid, iid);
- });
+ accessory.findCharacteristic = vi.fn((aid, iid) => {
+ return originalImplementation(aid as number, iid as number)
+ })
// @ts-expect-error: private access
- accessory.handleHAPConnectionClosed(connection);
+ accessory.handleHAPConnectionClosed(connection)
- expect(connection.clearRegisteredEvents).toBeCalled();
+ expect(connection.clearRegisteredEvents).toBeCalled()
// @ts-expect-error: private access
- expect(accessory.findCharacteristic).toHaveBeenCalledWith(aid, iids.on);
+ expect(accessory.findCharacteristic).toHaveBeenCalledWith(aid, iids.on)
// @ts-expect-error: private access
- expect(onCharacteristic.subscriptions).toEqual(0);
- });
+ expect(onCharacteristic.subscriptions).toEqual(0)
+ })
- test("adminOnly notifications", async () => {
- connection.hasEventNotifications = jest.fn(() => false);
- connection.enableEventNotifications = jest.fn();
- connection.disableEventNotifications = jest.fn();
+ it('adminOnly notifications', async () => {
+ connection.hasEventNotifications = vi.fn(() => false)
+ connection.enableEventNotifications = vi.fn()
+ connection.disableEventNotifications = vi.fn()
onCharacteristic.setProps({
adminOnlyAccess: [Access.NOTIFY],
- });
+ })
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, ev: true }],
+ characteristics: [{ aid, iid: iids.on, ev: true }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
- });
- expect(connection.enableEventNotifications).toHaveBeenCalledTimes(1);
+ })
+ expect(connection.enableEventNotifications).toHaveBeenCalledTimes(1)
- connection.username = clientUsername1; // changing to non-admin username
+ connection.username = clientUsername1 // changing to non-admin username
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, ev: true }],
+ characteristics: [{ aid, iid: iids.on, ev: true }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INSUFFICIENT_PRIVILEGES,
- });
-
+ })
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, ev: false }],
+ characteristics: [{ aid, iid: iids.on, ev: false }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INSUFFICIENT_PRIVILEGES,
- });
+ })
- connection.username = undefined;
- accessory._accessoryInfo = undefined;
+ connection.username = undefined
+ accessory._accessoryInfo = undefined
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, ev: true }],
+ characteristics: [{ aid, iid: iids.on, ev: true }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INSUFFICIENT_PRIVILEGES,
- });
- });
+ })
+ })
- test("write with additional authorization", async () => {
- const handler: Mock = jest.fn();
+ it('write with additional authorization', async () => {
+ // @ts-expect-error Generic type Mock requires between 0 and 1 type arguments.
+ const handler: Mock = vi.fn()
// write with no handler
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: true, authData: "xyz" }],
+ characteristics: [{ aid, iid: iids.on, value: true, authData: 'xyz' }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
- });
+ })
- onCharacteristic.setupAdditionalAuthorization(handler);
+ onCharacteristic.setupAdditionalAuthorization(handler)
// allowed to write
- handler.mockImplementationOnce(() => true);
+ handler.mockImplementationOnce(() => true)
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: true, authData: "xyz" }],
+ characteristics: [{ aid, iid: iids.on, value: true, authData: 'xyz' }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
- });
- expect(handler).toHaveBeenCalledWith("xyz");
+ })
+ expect(handler).toHaveBeenCalledWith('xyz')
// rejected write
- handler.mockImplementationOnce(() => false);
+ handler.mockImplementationOnce(() => false)
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: true, authData: "xyz" }],
+ characteristics: [{ aid, iid: iids.on, value: true, authData: 'xyz' }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INSUFFICIENT_AUTHORIZATION,
- });
- expect(handler).toHaveBeenCalledWith("xyz");
+ })
+ expect(handler).toHaveBeenCalledWith('xyz')
// exception
handler.mockImplementationOnce(() => {
- throw new Error("EXPECTED TEST ERROR");
- });
+ throw new Error('EXPECTED TEST ERROR')
+ })
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: true, authData: "xyz" }],
+ characteristics: [{ aid, iid: iids.on, value: true, authData: 'xyz' }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INSUFFICIENT_AUTHORIZATION,
- });
- expect(handler).toHaveBeenCalledWith("xyz");
- });
+ })
+ expect(handler).toHaveBeenCalledWith('xyz')
+ })
- test.each([false, true])(
- "timed write with timed write being required: %s", async timedWriteRequired => {
+ it.each([false, true])(
+ 'timed write with timed write being required: %s',
+ async (timedWriteRequired) => {
if (timedWriteRequired) {
- onCharacteristic.props.perms.push(Perms.TIMED_WRITE);
+ onCharacteristic.props.perms.push(Perms.TIMED_WRITE)
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: true }],
+ characteristics: [{ aid, iid: iids.on, value: true }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INVALID_VALUE_IN_REQUEST,
- });
+ })
}
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: true }],
+ characteristics: [{ aid, iid: iids.on, value: true }],
pid: 1337, // pid already expired
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INVALID_VALUE_IN_REQUEST,
- });
+ })
- connection.timedWritePid = 1337;
+ connection.timedWritePid = 1337
connection.timedWriteTimeout = setTimeout(() => {
- fail(new Error("timed-write timeout was never cleared"));
- }, 1000);
+ throw new Error('timed-write timeout was never cleared')
+ }, 1000)
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: true }],
+ characteristics: [{ aid, iid: iids.on, value: true }],
pid: 1336, // invalid pid
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INVALID_VALUE_IN_REQUEST,
- });
+ })
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: true }],
+ characteristics: [{ aid, iid: iids.on, value: true }],
pid: 1337, // correct pid
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
- });
+ })
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on, value: true }],
+ characteristics: [{ aid, iid: iids.on, value: true }],
pid: 1337, // can't reuse pid
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INVALID_VALUE_IN_REQUEST,
- });
- });
+ })
+ },
+ )
- test("empty write", async () => {
+ it('empty write', async () => {
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.on }],
+ characteristics: [{ aid, iid: iids.on }],
}, {
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.INVALID_VALUE_IN_REQUEST,
- });
- });
+ })
+ })
- test("Identify Write", async () => {
+ it('identify Write', async () => {
// PAIRED IDENTIFY
await testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.identify, value: true }],
+ characteristics: [{ aid, iid: iids.identify, value: true }],
}, {
- aid: aid,
+ aid,
iid: iids.identify,
status: HAPStatus.SUCCESS,
- });
+ })
- const eventPromise: Promise<[boolean, IdentifyCallback]> = awaitEventOnce(accessory, AccessoryEventTypes.IDENTIFY);
+ const eventPromise: Promise<[boolean, IdentifyCallback]> = awaitEventOnce(accessory, AccessoryEventTypes.IDENTIFY)
const response = testRequestResponse({
- characteristics: [{ aid: aid, iid: iids.identify, value: true }],
+ characteristics: [{ aid, iid: iids.identify, value: true }],
}, {
- aid: aid,
+ aid,
iid: iids.identify,
status: HAPStatus.SUCCESS,
- });
+ })
- const eventResult = await eventPromise;
- expect(eventResult[0]).toEqual(true);
- eventResult[1]();
+ const eventResult = await eventPromise
+ expect(eventResult[0]).toEqual(true)
+ eventResult[1]()
- await response;
- });
- });
+ await response
+ })
+ })
- describe("handleResource", () => {
- let cameraController: CameraController;
+ describe('handleResource', () => {
+ let cameraController: CameraController
beforeEach(() => {
- cameraController = new CameraController(createCameraControllerOptions());
- accessory.configureController(cameraController);
- });
+ cameraController = new CameraController(createCameraControllerOptions())
+ accessory.configureController(cameraController)
+ })
+ // eslint-disable-next-line unicorn/consistent-function-scoping
const testRequestResponse = async (partial: Partial = {}) => {
// @ts-expect-error: private access
accessory.handleResource({
- "resource-type": ResourceRequestType.IMAGE,
- "image-width": 200,
- "image-height": 200,
+ 'resource-type': ResourceRequestType.IMAGE,
+ 'image-width': 200,
+ 'image-height': 200,
...partial,
- }, callback);
-
- await callbackPromise;
+ }, callback)
- };
+ await callbackPromise
+ }
- test("unknown resource type", () => {
+ it('unknown resource type', () => {
// @ts-expect-error: private access
- accessory.handleResource({ "resource-type": "unknown" }, callback);
+ accessory.handleResource({ 'resource-type': 'unknown' }, callback)
expect(callback).toHaveBeenCalledWith({
httpCode: HAPHTTPCode.NOT_FOUND,
status: HAPStatus.RESOURCE_DOES_NOT_EXIST,
- });
- });
+ })
+ })
- test("missing camera controller", () => {
- accessory.removeController(cameraController);
+ it('missing camera controller', () => {
+ accessory.removeController(cameraController)
// @ts-expect-error: private access
accessory.handleResource({
- "resource-type": ResourceRequestType.IMAGE,
- "image-width": 200,
- "image-height": 200,
- }, callback);
+ 'resource-type': ResourceRequestType.IMAGE,
+ 'image-width': 200,
+ 'image-height': 200,
+ }, callback)
expect(callback).toHaveBeenCalledWith({
httpCode: HAPHTTPCode.NOT_FOUND,
status: HAPStatus.RESOURCE_DOES_NOT_EXIST,
- });
- });
+ })
+ })
- test("retrieve image resource", async () => {
- await testRequestResponse();
- expect(callback).toHaveBeenCalledWith(undefined, MOCK_IMAGE);
+ it('retrieve image resource', async () => {
+ await testRequestResponse()
+ expect(callback).toHaveBeenCalledWith(undefined, MOCK_IMAGE)
- await testRequestResponse({ aid: 1 });
- expect(callback).toHaveBeenCalledWith(undefined, MOCK_IMAGE);
- });
+ await testRequestResponse({ aid: 1 })
+ expect(callback).toHaveBeenCalledWith(undefined, MOCK_IMAGE)
+ })
- test("retrieve image resource on bridge", async () => {
- accessory.addBridgedAccessory(new Accessory("Bridged accessory", uuid.generate("bridged")));
- accessory._assignIDs(accessory._identifierCache!);
+ it('retrieve image resource on bridge', async () => {
+ accessory.addBridgedAccessory(new Accessory('Bridged accessory', generate('bridged')))
+ accessory._assignIDs(accessory._identifierCache!)
- await testRequestResponse();
- expect(callback).toHaveBeenCalledWith(undefined, MOCK_IMAGE);
+ await testRequestResponse()
+ expect(callback).toHaveBeenCalledWith(undefined, MOCK_IMAGE)
- await testRequestResponse({ aid: 1 });
- expect(callback).toHaveBeenCalledWith(undefined, MOCK_IMAGE);
- });
+ await testRequestResponse({ aid: 1 })
+ expect(callback).toHaveBeenCalledWith(undefined, MOCK_IMAGE)
+ })
- test("retrieve image resource on bridged accessory", async () => {
- accessory.removeController(cameraController);
+ it('retrieve image resource on bridged accessory', async () => {
+ accessory.removeController(cameraController)
- const bridged = new Accessory("Bridged accessory", uuid.generate("bridged"));
- bridged.configureController(cameraController);
+ const bridged = new Accessory('Bridged accessory', generate('bridged'))
+ bridged.configureController(cameraController)
- accessory.addBridgedAccessory(bridged);
- accessory._assignIDs(accessory._identifierCache!);
- expect(bridged.aid).toBeDefined();
+ accessory.addBridgedAccessory(bridged)
+ accessory._assignIDs(accessory._identifierCache!)
+ expect(bridged.aid).toBeDefined()
- await testRequestResponse();
+ await testRequestResponse()
expect(callback).toHaveBeenCalledWith({
httpCode: HAPHTTPCode.NOT_FOUND,
status: HAPStatus.RESOURCE_DOES_NOT_EXIST,
- });
+ })
- await testRequestResponse({ aid: 1 });
+ await testRequestResponse({ aid: 1 })
expect(callback).toHaveBeenCalledWith({
httpCode: HAPHTTPCode.NOT_FOUND,
status: HAPStatus.RESOURCE_DOES_NOT_EXIST,
- });
+ })
- await testRequestResponse({ aid: bridged.aid! });
- expect(callback).toHaveBeenCalledWith(undefined, MOCK_IMAGE);
- });
+ await testRequestResponse({ aid: bridged.aid! })
+ expect(callback).toHaveBeenCalledWith(undefined, MOCK_IMAGE)
+ })
- test("properly forward erroneous conditions", async () => {
+ it('properly forward erroneous conditions', async () => {
cameraController.recordingManagement!.operatingModeService
.getCharacteristic(Characteristic.HomeKitCameraActive)
- .value = false;
+ .value = false
- await testRequestResponse();
- expect(callback).toHaveBeenCalledWith({ httpCode: HAPHTTPCode.MULTI_STATUS, status: HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE });
- });
- });
+ await testRequestResponse()
+ expect(callback).toHaveBeenCalledWith({ httpCode: HAPHTTPCode.MULTI_STATUS, status: HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE })
+ })
+ })
- describe("characteristic read/write characteristicWarning", () => {
+ describe('characteristic read/write characteristicWarning', () => {
// @ts-expect-error: private access
- const originalWarning = Accessory.TIMEOUT_WARNING;
+ const originalWarning = Accessory.TIMEOUT_WARNING
// @ts-expect-error: private access
- const originalTimeoutAfterWarning = Accessory.TIMEOUT_AFTER_WARNING;
+ const originalTimeoutAfterWarning = Accessory.TIMEOUT_AFTER_WARNING
- let characteristicWarningHandler: Mock;
+ // @ts-expect-error Generic type Mock requires between 0 and 1 type arguments.
+ let characteristicWarningHandler: Mock
const adjustTimeouts = (warning: number, timeout: number) => {
// @ts-expect-error: private access
- Accessory.TIMEOUT_WARNING = warning;
+ Accessory.TIMEOUT_WARNING = warning
// @ts-expect-error: private access
- Accessory.TIMEOUT_AFTER_WARNING = timeout;
- };
+ Accessory.TIMEOUT_AFTER_WARNING = timeout
+ }
beforeEach(() => {
- characteristicWarningHandler = jest.fn();
+ characteristicWarningHandler = vi.fn()
// @ts-expect-error: private access
- accessory.sendCharacteristicWarning = characteristicWarningHandler;
- });
+ accessory.sendCharacteristicWarning = characteristicWarningHandler
+ })
afterEach(() => {
- adjustTimeouts(originalWarning, originalTimeoutAfterWarning);
- });
+ adjustTimeouts(originalWarning, originalTimeoutAfterWarning)
+ })
- test("slow read notification", async () => {
- adjustTimeouts(60, 10000);
+ it('slow read notification', async () => {
+ adjustTimeouts(60, 10000)
- const getEvent: Promise<[CharacteristicGetCallback]> = awaitEventOnce(onCharacteristic, CharacteristicEventTypes.GET);
+ const getEvent: Promise<[CharacteristicGetCallback]> = awaitEventOnce(onCharacteristic, CharacteristicEventTypes.GET)
// @ts-expect-error: private access
accessory.handleGetCharacteristics(connection, {
- ids: [{ aid: aid, iid: iids.on }],
+ ids: [{ aid, iid: iids.on }],
includeMeta: false,
includeEvent: false,
includeType: false,
includePerms: false,
- }, callback);
+ }, callback)
- await PromiseTimeout(2);
- expect(callback).not.toHaveBeenCalled();
+ await PromiseTimeout(2)
+ expect(callback).not.toHaveBeenCalled()
- await PromiseTimeout(70);
+ await PromiseTimeout(70)
expect(characteristicWarningHandler)
- .toHaveBeenCalledWith(onCharacteristic, CharacteristicWarningType.SLOW_READ, expect.anything());
+ .toHaveBeenCalledWith(onCharacteristic, CharacteristicWarningType.SLOW_READ, expect.anything())
- const eventResult = await getEvent;
- eventResult[0](undefined, true);
+ const eventResult = await getEvent
+ eventResult[0](undefined, true)
- await callbackPromise;
+ await callbackPromise
expect(callback).toHaveBeenCalledWith(undefined, {
characteristics: [{
- aid: aid,
+ aid,
iid: iids.on,
value: 1,
}],
- });
- });
+ })
+ })
- test("timeout read notification", async () => {
- adjustTimeouts(10, 50);
+ it('timeout read notification', async () => {
+ adjustTimeouts(10, 50)
- const getEvent: Promise<[CharacteristicGetCallback]> = awaitEventOnce(onCharacteristic, CharacteristicEventTypes.GET);
+ const getEvent: Promise<[CharacteristicGetCallback]> = awaitEventOnce(onCharacteristic, CharacteristicEventTypes.GET)
// @ts-expect-error: private access
accessory.handleGetCharacteristics(connection, {
- ids: [{ aid: aid, iid: iids.on }],
+ ids: [{ aid, iid: iids.on }],
includeMeta: false,
includeEvent: false,
includeType: false,
includePerms: false,
- }, callback);
+ }, callback)
- await PromiseTimeout(2);
- expect(callback).not.toHaveBeenCalled();
+ await PromiseTimeout(2)
+ expect(callback).not.toHaveBeenCalled()
- await PromiseTimeout(70);
+ await PromiseTimeout(70)
expect(characteristicWarningHandler)
- .toHaveBeenCalledWith(onCharacteristic, CharacteristicWarningType.SLOW_READ, expect.anything());
+ .toHaveBeenCalledWith(onCharacteristic, CharacteristicWarningType.SLOW_READ, expect.anything())
expect(characteristicWarningHandler)
- .toHaveBeenCalledWith(onCharacteristic, CharacteristicWarningType.TIMEOUT_READ, expect.anything());
+ .toHaveBeenCalledWith(onCharacteristic, CharacteristicWarningType.TIMEOUT_READ, expect.anything())
- const eventResult = await getEvent;
- eventResult[0](undefined, true);
+ const eventResult = await getEvent
+ eventResult[0](undefined, true)
- await callbackPromise;
+ await callbackPromise
expect(callback).toHaveBeenCalledWith(undefined, {
characteristics: [{
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.OPERATION_TIMED_OUT,
}],
- });
- });
+ })
+ })
- test("slow write notification", async () => {
- adjustTimeouts(60, 10000);
+ it('slow write notification', async () => {
+ adjustTimeouts(60, 10000)
- const setEvent: Promise<[CharacteristicValue, CharacteristicSetCallback]> = awaitEventOnce(onCharacteristic, CharacteristicEventTypes.SET);
+ const setEvent: Promise<[CharacteristicValue, CharacteristicSetCallback]> = awaitEventOnce(onCharacteristic, CharacteristicEventTypes.SET)
// @ts-expect-error: private access
accessory.handleSetCharacteristics(connection, {
- characteristics: [{ aid: aid, iid: iids.on, value: true }],
- }, callback);
+ characteristics: [{ aid, iid: iids.on, value: true }],
+ }, callback)
- await PromiseTimeout(2);
- expect(callback).not.toHaveBeenCalled();
+ await PromiseTimeout(2)
+ expect(callback).not.toHaveBeenCalled()
- await PromiseTimeout(70);
+ await PromiseTimeout(70)
expect(characteristicWarningHandler)
- .toHaveBeenCalledWith(onCharacteristic, CharacteristicWarningType.SLOW_WRITE, expect.anything());
+ .toHaveBeenCalledWith(onCharacteristic, CharacteristicWarningType.SLOW_WRITE, expect.anything())
- const eventResult = await setEvent;
- expect(eventResult[0]).toBe(true);
- eventResult[1]();
+ const eventResult = await setEvent
+ expect(eventResult[0]).toBe(true)
+ eventResult[1]()
- await callbackPromise;
+ await callbackPromise
expect(callback).toHaveBeenCalledWith(undefined, {
characteristics: [{
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.SUCCESS,
}],
- });
- });
+ })
+ })
- test("timeout read notification", async () => {
- adjustTimeouts(10, 50);
+ it('timeout write notification', async () => {
+ adjustTimeouts(10, 50)
- const setEvent: Promise<[CharacteristicValue, CharacteristicSetCallback]> = awaitEventOnce(onCharacteristic, CharacteristicEventTypes.SET);
+ const setEvent: Promise<[CharacteristicValue, CharacteristicSetCallback]> = awaitEventOnce(onCharacteristic, CharacteristicEventTypes.SET)
// @ts-expect-error: private access
accessory.handleSetCharacteristics(connection, {
- characteristics: [{ aid: aid, iid: iids.on, value: true }],
- }, callback);
+ characteristics: [{ aid, iid: iids.on, value: true }],
+ }, callback)
- await PromiseTimeout(2);
- expect(callback).not.toHaveBeenCalled();
+ await PromiseTimeout(2)
+ expect(callback).not.toHaveBeenCalled()
- await PromiseTimeout(70);
+ await PromiseTimeout(70)
expect(characteristicWarningHandler)
- .toHaveBeenCalledWith(onCharacteristic, CharacteristicWarningType.SLOW_WRITE, expect.anything());
+ .toHaveBeenCalledWith(onCharacteristic, CharacteristicWarningType.SLOW_WRITE, expect.anything())
expect(characteristicWarningHandler)
- .toHaveBeenCalledWith(onCharacteristic, CharacteristicWarningType.TIMEOUT_WRITE, expect.anything());
+ .toHaveBeenCalledWith(onCharacteristic, CharacteristicWarningType.TIMEOUT_WRITE, expect.anything())
- const eventResult = await setEvent;
- expect(eventResult[0]).toBe(true);
- eventResult[1]();
+ const eventResult = await setEvent
+ expect(eventResult[0]).toBe(true)
+ eventResult[1]()
- await callbackPromise;
+ await callbackPromise
expect(callback).toHaveBeenCalledWith(undefined, {
characteristics: [{
- aid: aid,
+ aid,
iid: iids.on,
status: HAPStatus.OPERATION_TIMED_OUT,
}],
- });
- });
- });
- });
+ })
+ })
+ })
+ })
- describe("characteristicWarning", () => {
- let consoleWarnSpy: jest.SpyInstance;
+ describe('characteristicWarning', () => {
+ let consoleWarnSpy: MockInstance
beforeEach(() => {
// Mock console.warn before each test that needs it
- consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
- });
+ consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
+ })
afterEach(() => {
// Restore console.warn after each test
- consoleWarnSpy.mockRestore();
- });
+ consoleWarnSpy.mockRestore()
+ })
- test("emit characteristic warning", () => {
- accessory.on(AccessoryEventTypes.CHARACTERISTIC_WARNING, callback);
+ it('emit characteristic warning', () => {
+ accessory.on(AccessoryEventTypes.CHARACTERISTIC_WARNING, callback)
- const service = accessory.addService(Service.Lightbulb, "Light");
- const on = service.getCharacteristic(Characteristic.On);
+ const service = accessory.addService(Service.Lightbulb, 'Light')
+ const on = service.getCharacteristic(Characteristic.On)
- on.updateValue({});
- expect(callback).toHaveBeenCalledTimes(1);
- });
+ on.updateValue({})
+ expect(callback).toHaveBeenCalledTimes(1)
+ })
- test("forward characteristic on bridged accessory", () => {
- const bridge = new Bridge("Test bridge", uuid.generate("bridge test"));
+ it('forward characteristic on bridged accessory', () => {
+ const bridge = new Bridge('Test bridge', generate('bridge test'))
- bridge.addBridgedAccessory(accessory);
+ bridge.addBridgedAccessory(accessory)
- bridge.on(AccessoryEventTypes.CHARACTERISTIC_WARNING, callback);
- accessory.on(AccessoryEventTypes.CHARACTERISTIC_WARNING, callback);
+ bridge.on(AccessoryEventTypes.CHARACTERISTIC_WARNING, callback)
+ accessory.on(AccessoryEventTypes.CHARACTERISTIC_WARNING, callback)
- const service = accessory.addService(Service.Lightbulb, "Light");
- const on = service.getCharacteristic(Characteristic.On);
+ const service = accessory.addService(Service.Lightbulb, 'Light')
+ const on = service.getCharacteristic(Characteristic.On)
- on.updateValue({});
- expect(callback).toHaveBeenCalledTimes(2);
- });
+ on.updateValue({})
+ expect(callback).toHaveBeenCalledTimes(2)
+ })
- it("should run without characteristic warning handler", () => {
- const service = accessory.addService(Service.Lightbulb, "Light");
- const on = service.getCharacteristic(Characteristic.On);
+ it('should run without characteristic warning handler', () => {
+ const service = accessory.addService(Service.Lightbulb, 'Light')
+ const on = service.getCharacteristic(Characteristic.On)
- on.updateValue({});
- });
- });
+ on.updateValue({})
+ })
+ })
- describe("serialize", () => {
- test("serialize accessory", () => {
- accessory.category = Categories.LIGHTBULB;
+ describe('serialize', () => {
+ it('serialize accessory', () => {
+ accessory.category = Categories.LIGHTBULB
- const lightService = new Service.Lightbulb("TestLight", "subtype");
- const switchService = new Service.Switch("TestSwitch", "subtype");
- lightService.addLinkedService(switchService);
+ const lightService = new Service.Lightbulb('TestLight', 'subtype')
+ const switchService = new Service.Switch('TestSwitch', 'subtype')
+ lightService.addLinkedService(switchService)
- accessory.addService(lightService);
- accessory.addService(switchService);
+ accessory.addService(lightService)
+ accessory.addService(switchService)
- const json = Accessory.serialize(accessory);
- expect(json.displayName).toEqual(accessory.displayName);
- expect(json.UUID).toEqual(accessory.UUID);
- expect(json.category).toEqual(Categories.LIGHTBULB);
+ const json = Accessory.serialize(accessory)
+ expect(json.displayName).toEqual(accessory.displayName)
+ expect(json.UUID).toEqual(accessory.UUID)
+ expect(json.category).toEqual(Categories.LIGHTBULB)
- expect(json.services).toBeDefined();
- expect(json.services.length).toEqual(3); // 2 above + accessory information service
- expect(json.linkedServices).toBeDefined();
- expect(Object.keys(json.linkedServices!)).toEqual([lightService.UUID + "subtype"]);
- expect(Object.values(json.linkedServices!)).toEqual([[switchService.UUID + "subtype"]]);
- });
- });
+ expect(json.services).toBeDefined()
+ expect(json.services.length).toEqual(3) // 2 above + accessory information service
+ expect(json.linkedServices).toBeDefined()
+ expect(Object.keys(json.linkedServices!)).toEqual([`${lightService.UUID}subtype`])
+ expect(Object.values(json.linkedServices!)).toEqual([[`${switchService.UUID}subtype`]])
+ })
+ })
- describe("deserialize", () => {
- let consoleWarnSpy: jest.SpyInstance;
+ describe('deserialize', () => {
+ let consoleWarnSpy: MockInstance
beforeEach(() => {
// Mock console.warn before each test that needs it
- consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
- });
+ consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
+ })
afterEach(() => {
// Restore console.warn after each test
- consoleWarnSpy.mockRestore();
- });
-
- test("deserialize legacy json from homebridge", () => {
- const json = JSON.parse("{\"plugin\":\"homebridge-samplePlatform\",\"platform\":\"SamplePlatform\"," +
- "\"displayName\":\"2020-01-17T18:45:41.049Z\",\"UUID\":\"dc3951d8-662e-46f7-b6fe-d1b5b5e1a995\",\"category\":1," +
- "\"context\":{},\"linkedServices\":{\"0000003E-0000-1000-8000-0026BB765291\":[],\"00000043-0000-1000-8000-0026BB765291\":[]}," +
- "\"services\":[{\"UUID\":\"0000003E-0000-1000-8000-0026BB765291\",\"characteristics\":[" +
- "{\"displayName\":\"Identify\",\"UUID\":\"00000014-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"bool\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pw\"]}," +
- "\"value\":false,\"eventOnlyCharacteristic\":false},{\"displayName\":\"Manufacturer\",\"UUID\":\"00000020-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]}," +
- "\"value\":\"Default-Manufacturer\",\"eventOnlyCharacteristic\":false},{\"displayName\":\"Model\"," +
- "\"UUID\":\"00000021-0000-1000-8000-0026BB765291\",\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null," +
- "\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]},\"value\":\"Default-Model\",\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"Name\",\"UUID\":\"00000023-0000-1000-8000-0026BB765291\",\"props\":{\"format\":\"string\",\"unit\":null," +
- "\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]},\"value\":\"2020-01-17T18:45:41.049Z\"," +
- "\"eventOnlyCharacteristic\":false},{\"displayName\":\"Serial Number\",\"UUID\":\"00000030-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]}," +
- "\"value\":\"Default-SerialNumber\",\"eventOnlyCharacteristic\":false},{\"displayName\":\"Firmware Revision\"," +
- "\"UUID\":\"00000052-0000-1000-8000-0026BB765291\",\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null," +
- "\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]},\"value\":\"\",\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"Product Data\",\"UUID\":\"00000220-0000-1000-8000-0026BB765291\",\"props\":{\"format\":\"data\"," +
- "\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]},\"value\":null," +
- "\"eventOnlyCharacteristic\":false}]},{\"displayName\":\"Test Light\",\"UUID\":\"00000043-0000-1000-8000-0026BB765291\"," +
- "\"characteristics\":[{\"displayName\":\"Name\",\"UUID\":\"00000023-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]}," +
- "\"value\":\"Test Light\",\"eventOnlyCharacteristic\":false},{\"displayName\":\"On\"," +
- "\"UUID\":\"00000025-0000-1000-8000-0026BB765291\",\"props\":{\"format\":\"bool\",\"unit\":null,\"minValue\":null," +
- "\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\",\"pw\",\"ev\"]},\"value\":false,\"eventOnlyCharacteristic\":false}]}]}");
-
- const accessory = Accessory.deserialize(json);
-
- expect(accessory.displayName).toEqual(json.displayName);
- expect(accessory.UUID).toEqual(json.UUID);
- expect(accessory.category).toEqual(json.category);
-
- expect(accessory.services).toBeDefined();
- expect(accessory.services.length).toEqual(2);
- });
-
- test("deserialize complete json", () => {
+ consoleWarnSpy.mockRestore()
+ })
+
+ it('deserialize legacy json from homebridge', () => {
+ const json = JSON.parse('{"plugin":"homebridge-samplePlatform","platform":"SamplePlatform",'
+ + '"displayName":"2020-01-17T18:45:41.049Z","UUID":"dc3951d8-662e-46f7-b6fe-d1b5b5e1a995","category":1,'
+ + '"context":{},"linkedServices":{"0000003E-0000-1000-8000-0026BB765291":[],"00000043-0000-1000-8000-0026BB765291":[]},'
+ + '"services":[{"UUID":"0000003E-0000-1000-8000-0026BB765291","characteristics":['
+ + '{"displayName":"Identify","UUID":"00000014-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"bool","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pw"]},'
+ + '"value":false,"eventOnlyCharacteristic":false},{"displayName":"Manufacturer","UUID":"00000020-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"string","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},'
+ + '"value":"Default-Manufacturer","eventOnlyCharacteristic":false},{"displayName":"Model",'
+ + '"UUID":"00000021-0000-1000-8000-0026BB765291","props":{"format":"string","unit":null,"minValue":null,'
+ + '"maxValue":null,"minStep":null,"perms":["pr"]},"value":"Default-Model","eventOnlyCharacteristic":false},'
+ + '{"displayName":"Name","UUID":"00000023-0000-1000-8000-0026BB765291","props":{"format":"string","unit":null,'
+ + '"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},"value":"2020-01-17T18:45:41.049Z",'
+ + '"eventOnlyCharacteristic":false},{"displayName":"Serial Number","UUID":"00000030-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"string","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},'
+ + '"value":"Default-SerialNumber","eventOnlyCharacteristic":false},{"displayName":"Firmware Revision",'
+ + '"UUID":"00000052-0000-1000-8000-0026BB765291","props":{"format":"string","unit":null,"minValue":null,'
+ + '"maxValue":null,"minStep":null,"perms":["pr"]},"value":"","eventOnlyCharacteristic":false},'
+ + '{"displayName":"Product Data","UUID":"00000220-0000-1000-8000-0026BB765291","props":{"format":"data",'
+ + '"unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},"value":null,'
+ + '"eventOnlyCharacteristic":false}]},{"displayName":"Test Light","UUID":"00000043-0000-1000-8000-0026BB765291",'
+ + '"characteristics":[{"displayName":"Name","UUID":"00000023-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"string","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},'
+ + '"value":"Test Light","eventOnlyCharacteristic":false},{"displayName":"On",'
+ + '"UUID":"00000025-0000-1000-8000-0026BB765291","props":{"format":"bool","unit":null,"minValue":null,'
+ + '"maxValue":null,"minStep":null,"perms":["pr","pw","ev"]},"value":false,"eventOnlyCharacteristic":false}]}]}')
+
+ const accessory = Accessory.deserialize(json)
+
+ expect(accessory.displayName).toEqual(json.displayName)
+ expect(accessory.UUID).toEqual(json.UUID)
+ expect(accessory.category).toEqual(json.category)
+
+ expect(accessory.services).toBeDefined()
+ expect(accessory.services.length).toEqual(2)
+ })
+
+ it('deserialize complete json', () => {
// json for a light accessory
- const json = JSON.parse("{\"displayName\":\"TestAccessory\",\"UUID\":\"0beec7b5-ea3f-40fd-bc95-d0dd47f3c5bc\"," +
- "\"category\":5,\"services\":[{\"UUID\":\"0000003E-0000-1000-8000-0026BB765291\",\"hiddenService\":false," +
- "\"primaryService\":false,\"characteristics\":[{\"displayName\":\"Identify\",\"UUID\":\"00000014-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"bool\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pw\"]}," +
- "\"value\":false,\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"Manufacturer\",\"UUID\":\"00000020-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]}," +
- "\"value\":\"Default-Manufacturer\",\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"Model\",\"UUID\":\"00000021-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]}," +
- "\"value\":\"Default-Model\",\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"Name\",\"UUID\":\"00000023-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]}," +
- "\"value\":\"TestAccessory\",\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"Serial Number\",\"UUID\":\"00000030-0000-1000-8000-0026BB765291\",\"props\":{\"format\":\"string\"," +
- "\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]},\"value\":\"Default-SerialNumber\"," +
- "\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false},{\"displayName\":\"Firmware Revision\"," +
- "\"UUID\":\"00000052-0000-1000-8000-0026BB765291\",\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null," +
- "\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]},\"value\":\"1.0\",\"accessRestrictedToAdmins\":[]," +
- "\"eventOnlyCharacteristic\":false},{\"displayName\":\"Product Data\"," +
- "\"UUID\":\"00000220-0000-1000-8000-0026BB765291\",\"props\":{\"format\":\"data\",\"unit\":null,\"minValue\":null," +
- "\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]},\"value\":null,\"accessRestrictedToAdmins\":[]," +
- "\"eventOnlyCharacteristic\":false}],\"optionalCharacteristics\":[{\"displayName\":\"Hardware Revision\"," +
- "\"UUID\":\"00000053-0000-1000-8000-0026BB765291\",\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null," +
- "\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]},\"value\":\"\",\"accessRestrictedToAdmins\":[]," +
- "\"eventOnlyCharacteristic\":false},{\"displayName\":\"Accessory Flags\",\"UUID\":\"000000A6-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"uint32\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\",\"ev\"]}," +
- "\"value\":0,\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}]}," +
- "{\"displayName\":\"TestLight\",\"UUID\":\"00000043-0000-1000-8000-0026BB765291\"," +
- "\"subtype\":\"subtype\",\"hiddenService\":false,\"primaryService\":false," +
- "\"characteristics\":[{\"displayName\":\"Name\",\"UUID\":\"00000023-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]}," +
- "\"value\":\"TestLight\",\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"On\",\"UUID\":\"00000025-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"bool\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\",\"pw\",\"ev\"]}," +
- "\"value\":false,\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}]," +
- "\"optionalCharacteristics\":[{\"displayName\":\"Brightness\",\"UUID\":\"00000008-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"int\",\"unit\":\"percentage\",\"minValue\":0,\"maxValue\":100,\"minStep\":1," +
- "\"perms\":[\"pr\",\"pw\",\"ev\"]},\"value\":0,\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"Hue\",\"UUID\":\"00000013-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"float\",\"unit\":\"arcdegrees\",\"minValue\":0,\"maxValue\":360,\"minStep\":1,\"perms\":[\"pr\",\"pw\",\"ev\"]}," +
- "\"value\":0,\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"Saturation\",\"UUID\":\"0000002F-0000-1000-8000-0026BB765291\",\"props\":{\"format\":\"float\"," +
- "\"unit\":\"percentage\",\"minValue\":0,\"maxValue\":100,\"minStep\":1,\"perms\":[\"pr\",\"pw\",\"ev\"]},\"value\":0," +
- "\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false},{\"displayName\":\"Name\"," +
- "\"UUID\":\"00000023-0000-1000-8000-0026BB765291\",\"props\":{\"format\":\"string\",\"unit\":null," +
- "\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]},\"value\":\"\"," +
- "\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"Color Temperature\",\"UUID\":\"000000CE-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"uint32\",\"unit\":null,\"minValue\":140,\"maxValue\":500,\"minStep\":1,\"perms\":[\"pr\",\"pw\",\"ev\"]}," +
- "\"value\":140,\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}]}," +
- "{\"displayName\":\"TestSwitch\",\"UUID\":\"00000049-0000-1000-8000-0026BB765291\",\"subtype\":\"subtype\"," +
- "\"hiddenService\":false,\"primaryService\":false,\"characteristics\":[{\"displayName\":\"Name\"," +
- "\"UUID\":\"00000023-0000-1000-8000-0026BB765291\",\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null," +
- "\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]},\"value\":\"TestSwitch\",\"accessRestrictedToAdmins\":[]," +
- "\"eventOnlyCharacteristic\":false},{\"displayName\":\"On\",\"UUID\":\"00000025-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"bool\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null," +
- "\"perms\":[\"pr\",\"pw\",\"ev\"]},\"value\":false,\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}]," +
- "\"optionalCharacteristics\":[{\"displayName\":\"Name\",\"UUID\":\"00000023-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]}," +
- "\"value\":\"\",\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}]}]," +
- "\"linkedServices\":{\"00000043-0000-1000-8000-0026BB765291subtype\":[\"00000049-0000-1000-8000-0026BB765291subtype\"]}}");
-
- const accessory = Accessory.deserialize(json);
-
- expect(accessory.displayName).toEqual(json.displayName);
- expect(accessory.UUID).toEqual(json.UUID);
- expect(accessory.category).toEqual(json.category);
-
- expect(accessory.services).toBeDefined();
- expect(accessory.services.length).toEqual(3);
- expect(accessory.getService(Service.Lightbulb)).toBeDefined();
- expect(accessory.getService(Service.Lightbulb)!.linkedServices.length).toEqual(1);
- expect(accessory.getService(Service.Lightbulb)!.linkedServices[0].UUID).toEqual(Service.Switch.UUID);
- });
- });
-
- describe("parseBindOption", () => {
+ const json = JSON.parse('{"displayName":"TestAccessory","UUID":"0beec7b5-ea3f-40fd-bc95-d0dd47f3c5bc",'
+ + '"category":5,"services":[{"UUID":"0000003E-0000-1000-8000-0026BB765291","hiddenService":false,'
+ + '"primaryService":false,"characteristics":[{"displayName":"Identify","UUID":"00000014-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"bool","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pw"]},'
+ + '"value":false,"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},'
+ + '{"displayName":"Manufacturer","UUID":"00000020-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"string","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},'
+ + '"value":"Default-Manufacturer","accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},'
+ + '{"displayName":"Model","UUID":"00000021-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"string","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},'
+ + '"value":"Default-Model","accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},'
+ + '{"displayName":"Name","UUID":"00000023-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"string","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},'
+ + '"value":"TestAccessory","accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},'
+ + '{"displayName":"Serial Number","UUID":"00000030-0000-1000-8000-0026BB765291","props":{"format":"string",'
+ + '"unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},"value":"Default-SerialNumber",'
+ + '"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},{"displayName":"Firmware Revision",'
+ + '"UUID":"00000052-0000-1000-8000-0026BB765291","props":{"format":"string","unit":null,"minValue":null,'
+ + '"maxValue":null,"minStep":null,"perms":["pr"]},"value":"1.0","accessRestrictedToAdmins":[],'
+ + '"eventOnlyCharacteristic":false},{"displayName":"Product Data",'
+ + '"UUID":"00000220-0000-1000-8000-0026BB765291","props":{"format":"data","unit":null,"minValue":null,'
+ + '"maxValue":null,"minStep":null,"perms":["pr"]},"value":null,"accessRestrictedToAdmins":[],'
+ + '"eventOnlyCharacteristic":false}],"optionalCharacteristics":[{"displayName":"Hardware Revision",'
+ + '"UUID":"00000053-0000-1000-8000-0026BB765291","props":{"format":"string","unit":null,"minValue":null,'
+ + '"maxValue":null,"minStep":null,"perms":["pr"]},"value":"","accessRestrictedToAdmins":[],'
+ + '"eventOnlyCharacteristic":false},{"displayName":"Accessory Flags","UUID":"000000A6-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"uint32","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr","ev"]},'
+ + '"value":0,"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false}]},'
+ + '{"displayName":"TestLight","UUID":"00000043-0000-1000-8000-0026BB765291",'
+ + '"subtype":"subtype","hiddenService":false,"primaryService":false,'
+ + '"characteristics":[{"displayName":"Name","UUID":"00000023-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"string","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},'
+ + '"value":"TestLight","accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},'
+ + '{"displayName":"On","UUID":"00000025-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"bool","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr","pw","ev"]},'
+ + '"value":false,"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false}],'
+ + '"optionalCharacteristics":[{"displayName":"Brightness","UUID":"00000008-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"int","unit":"percentage","minValue":0,"maxValue":100,"minStep":1,'
+ + '"perms":["pr","pw","ev"]},"value":0,"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},'
+ + '{"displayName":"Hue","UUID":"00000013-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"float","unit":"arcdegrees","minValue":0,"maxValue":360,"minStep":1,"perms":["pr","pw","ev"]},'
+ + '"value":0,"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},'
+ + '{"displayName":"Saturation","UUID":"0000002F-0000-1000-8000-0026BB765291","props":{"format":"float",'
+ + '"unit":"percentage","minValue":0,"maxValue":100,"minStep":1,"perms":["pr","pw","ev"]},"value":0,'
+ + '"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},{"displayName":"Name",'
+ + '"UUID":"00000023-0000-1000-8000-0026BB765291","props":{"format":"string","unit":null,'
+ + '"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},"value":"",'
+ + '"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},'
+ + '{"displayName":"Color Temperature","UUID":"000000CE-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"uint32","unit":null,"minValue":140,"maxValue":500,"minStep":1,"perms":["pr","pw","ev"]},'
+ + '"value":140,"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false}]},'
+ + '{"displayName":"TestSwitch","UUID":"00000049-0000-1000-8000-0026BB765291","subtype":"subtype",'
+ + '"hiddenService":false,"primaryService":false,"characteristics":[{"displayName":"Name",'
+ + '"UUID":"00000023-0000-1000-8000-0026BB765291","props":{"format":"string","unit":null,"minValue":null,'
+ + '"maxValue":null,"minStep":null,"perms":["pr"]},"value":"TestSwitch","accessRestrictedToAdmins":[],'
+ + '"eventOnlyCharacteristic":false},{"displayName":"On","UUID":"00000025-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"bool","unit":null,"minValue":null,"maxValue":null,"minStep":null,'
+ + '"perms":["pr","pw","ev"]},"value":false,"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false}],'
+ + '"optionalCharacteristics":[{"displayName":"Name","UUID":"00000023-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"string","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},'
+ + '"value":"","accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false}]}],'
+ + '"linkedServices":{"00000043-0000-1000-8000-0026BB765291subtype":["00000049-0000-1000-8000-0026BB765291subtype"]}}')
+
+ const accessory = Accessory.deserialize(json)
+
+ expect(accessory.displayName).toEqual(json.displayName)
+ expect(accessory.UUID).toEqual(json.UUID)
+ expect(accessory.category).toEqual(json.category)
+
+ expect(accessory.services).toBeDefined()
+ expect(accessory.services.length).toEqual(3)
+ expect(accessory.getService(Service.Lightbulb)).toBeDefined()
+ expect(accessory.getService(Service.Lightbulb)!.linkedServices.length).toEqual(1)
+ expect(accessory.getService(Service.Lightbulb)!.linkedServices[0].UUID).toEqual(Service.Switch.UUID)
+ })
+ })
+
+ describe('parseBindOption', () => {
const basePublishInfo: PublishInfo = {
username: serverUsername,
- pincode: "000-00-000",
+ pincode: '000-00-000',
category: Categories.SWITCH,
- };
+ }
const callParseBindOption = (bind: (InterfaceName | IPAddress) | (InterfaceName | IPAddress)[]): {
- advertiserAddress?: string[],
- serviceRestrictedAddress?: string[],
- serviceDisableIpv6?: boolean,
- serverAddress?: string,
+ advertiserAddress?: string[]
+ serviceRestrictedAddress?: string[]
+ serviceDisableIpv6?: boolean
+ serverAddress?: string
} => {
const info: PublishInfo = {
...basePublishInfo,
- bind: bind,
- };
+ bind,
+ }
// @ts-expect-error: private access
- return Accessory.parseBindOption(info);
- };
-
- test("parse unspecified ipv6 address", () => {
- expect(callParseBindOption("::")).toEqual({
- serverAddress: "::",
- });
- });
-
- test("parse unspecified ipv4 address", () => {
- expect(callParseBindOption("0.0.0.0")).toEqual({
- serverAddress: "0.0.0.0",
+ return Accessory.parseBindOption(info)
+ }
+
+ it('parse unspecified ipv6 address', () => {
+ expect(callParseBindOption('::')).toEqual({
+ serverAddress: '::',
+ })
+ })
+
+ it('parse unspecified ipv4 address', () => {
+ expect(callParseBindOption('0.0.0.0')).toEqual({
+ serverAddress: '0.0.0.0',
serviceDisableIpv6: true,
- });
- });
-
- test("parse interface names", () => {
- expect(callParseBindOption(["en0", "lo0"])).toEqual({
- serverAddress: "::",
- advertiserAddress: ["en0", "lo0"],
- serviceRestrictedAddress: ["en0", "lo0"],
- });
- });
-
- test("parse interface names with explicit ipv6 support", () => {
- expect(callParseBindOption(["en0", "lo0", "::"])).toEqual({
- serverAddress: "::",
- advertiserAddress: ["en0", "lo0"],
- serviceRestrictedAddress: ["en0", "lo0"],
- });
- });
-
- test("parse interface names ipv4 only", () => {
- expect(callParseBindOption(["en0", "lo0", "0.0.0.0"])).toEqual({
- serverAddress: "0.0.0.0",
- advertiserAddress: ["en0", "lo0"],
- serviceRestrictedAddress: ["en0", "lo0"],
+ })
+ })
+
+ it('parse interface names', () => {
+ expect(callParseBindOption(['en0', 'lo0'])).toEqual({
+ serverAddress: '::',
+ advertiserAddress: ['en0', 'lo0'],
+ serviceRestrictedAddress: ['en0', 'lo0'],
+ })
+ })
+
+ it('parse interface names with explicit ipv6 support', () => {
+ expect(callParseBindOption(['en0', 'lo0', '::'])).toEqual({
+ serverAddress: '::',
+ advertiserAddress: ['en0', 'lo0'],
+ serviceRestrictedAddress: ['en0', 'lo0'],
+ })
+ })
+
+ it('parse interface names ipv4 only', () => {
+ expect(callParseBindOption(['en0', 'lo0', '0.0.0.0'])).toEqual({
+ serverAddress: '0.0.0.0',
+ advertiserAddress: ['en0', 'lo0'],
+ serviceRestrictedAddress: ['en0', 'lo0'],
serviceDisableIpv6: true,
- });
- });
-
- test("parse ipv4 address", () => {
- expect(callParseBindOption("169.254.104.90")).toEqual({
- serverAddress: "0.0.0.0",
- advertiserAddress: ["169.254.104.90"],
- serviceRestrictedAddress: ["169.254.104.90"],
- });
-
- expect(callParseBindOption(["169.254.104.90", "192.168.1.4"])).toEqual({
- serverAddress: "0.0.0.0",
- advertiserAddress: ["169.254.104.90", "192.168.1.4"],
- serviceRestrictedAddress: ["169.254.104.90", "192.168.1.4"],
- });
- });
-
- test("parse ipv6 address", () => {
- expect(callParseBindOption("2001:db8::")).toEqual({
- serverAddress: "::",
- advertiserAddress: ["2001:db8::"],
- serviceRestrictedAddress: ["2001:db8::"],
- });
-
- expect(callParseBindOption(["2001:db8::", "2001:db8::1"])).toEqual({
- serverAddress: "::",
- advertiserAddress: ["2001:db8::", "2001:db8::1"],
- serviceRestrictedAddress: ["2001:db8::", "2001:db8::1"],
- });
- });
- });
-});
+ })
+ })
+
+ it('parse ipv4 address', () => {
+ expect(callParseBindOption('169.254.104.90')).toEqual({
+ serverAddress: '0.0.0.0',
+ advertiserAddress: ['169.254.104.90'],
+ serviceRestrictedAddress: ['169.254.104.90'],
+ })
+
+ expect(callParseBindOption(['169.254.104.90', '192.168.1.4'])).toEqual({
+ serverAddress: '0.0.0.0',
+ advertiserAddress: ['169.254.104.90', '192.168.1.4'],
+ serviceRestrictedAddress: ['169.254.104.90', '192.168.1.4'],
+ })
+ })
+
+ it('parse ipv6 address', () => {
+ expect(callParseBindOption('2001:db8::')).toEqual({
+ serverAddress: '::',
+ advertiserAddress: ['2001:db8::'],
+ serviceRestrictedAddress: ['2001:db8::'],
+ })
+
+ expect(callParseBindOption(['2001:db8::', '2001:db8::1'])).toEqual({
+ serverAddress: '::',
+ advertiserAddress: ['2001:db8::', '2001:db8::1'],
+ serviceRestrictedAddress: ['2001:db8::', '2001:db8::1'],
+ })
+ })
+ })
+})
class TestController implements Controller {
controllerId(): ControllerIdentifier {
- return "test-id";
+ return 'test-id'
}
constructServices(): ControllerServiceMap {
- const lightService = new Service.Lightbulb("", "");
- const switchService = new Service.Switch("", "");
+ const lightService = new Service.Lightbulb('', '')
+ const switchService = new Service.Switch('', '')
return {
light: lightService,
switch: switchService,
- };
+ }
}
initWithServices(serviceMap: ControllerServiceMap): void | ControllerServiceMap {
// serviceMap will be altered here to test update procedure
- delete serviceMap.switch;
- serviceMap.light = new Service.LightSensor("", "");
- serviceMap.outlet = new Service.Outlet("", "");
+ delete serviceMap.switch
+ serviceMap.light = new Service.LightSensor('', '')
+ serviceMap.outlet = new Service.Outlet('', '')
- return serviceMap;
+ return serviceMap
}
configureServices(): void {
diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts
index c67b724cf..8b8b273f2 100644
--- a/src/lib/Accessory.ts
+++ b/src/lib/Accessory.ts
@@ -1,9 +1,5 @@
-import assert from "assert";
-import crypto from "crypto";
-import createDebug from "debug";
-import { EventEmitter } from "events";
-import net from "net";
-import {
+/* global NodeJS */
+import type {
AccessoryJsonObject,
CharacteristicId,
CharacteristicReadData,
@@ -23,67 +19,76 @@ import {
PartialCharacteristicReadData,
PartialCharacteristicWriteData,
ResourceRequest,
- ResourceRequestType,
VoidCallback,
WithUUID,
-} from "../types";
-import { Advertiser, AdvertiserEvent, AvahiAdvertiser, BonjourHAPAdvertiser, CiaoAdvertiser, ResolvedAdvertiser } from "./Advertiser";
-// noinspection JSDeprecatedSymbols
-import {
- Access,
- ChangeReason,
- Characteristic,
- CharacteristicEventTypes,
- CharacteristicOperationContext,
- CharacteristicSetCallback,
- Perms,
-} from "./Characteristic";
-import {
- CameraController,
+} from '../types'
+import type { Advertiser } from './Advertiser'
+import type { CharacteristicOperationContext, CharacteristicSetCallback } from './Characteristic'
+import type {
Controller,
ControllerConstructor,
ControllerIdentifier,
ControllerServiceMap,
- isSerializableController,
-} from "./controller";
-import {
+} from './controller'
+import type {
AccessoriesCallback,
AddPairingCallback,
- HAPHTTPCode,
- HAPServer,
- HAPServerEventTypes,
- HAPStatus,
IdentifyCallback,
ListPairingsCallback,
PairCallback,
ReadCharacteristicsCallback,
RemovePairingCallback,
ResourceRequestCallback,
- TLVErrorCode,
WriteCharacteristicsCallback,
-} from "./HAPServer";
-import { AccessoryInfo, PermissionTypes } from "./model/AccessoryInfo";
-import { ControllerStorage } from "./model/ControllerStorage";
-import { IdentifierCache } from "./model/IdentifierCache";
-import { SerializedService, Service, ServiceCharacteristicChange, ServiceEventTypes, ServiceId } from "./Service";
-import { clone } from "./util/clone";
-import { EventName, HAPConnection, HAPUsername } from "./util/eventedhttp";
-import { formatOutgoingCharacteristicValue } from "./util/request-util";
-import * as uuid from "./util/uuid";
-import { toShortForm } from "./util/uuid";
-import { checkName } from "./util/checkName";
-
-const debug = createDebug("HAP-NodeJS:Accessory");
-const MAX_ACCESSORIES = 149; // Maximum number of bridged accessories per bridge.
-const MAX_SERVICES = 100;
+} from './HAPServer'
+import type { SerializedService, ServiceCharacteristicChange, ServiceId } from './Service'
+import type { EventName, HAPConnection, HAPUsername } from './util/eventedhttp'
+
+import assert from 'node:assert'
+import { Buffer } from 'node:buffer'
+import { createHash } from 'node:crypto'
+import { EventEmitter } from 'node:events'
+import { isIP } from 'node:net'
+
+import createDebug from 'debug'
+
+import { ResourceRequestType } from '../types.js'
+import { AdvertiserEvent, AvahiAdvertiser, BonjourHAPAdvertiser, CiaoAdvertiser, ResolvedAdvertiser } from './Advertiser.js'
+import {
+ Access,
+ ChangeReason,
+ Characteristic,
+ CharacteristicEventTypes,
+ Perms,
+} from './Characteristic.js'
+import { CameraController, isSerializableController } from './controller/index.js'
+import {
+ HAPHTTPCode,
+ HAPServer,
+ HAPServerEventTypes,
+ HAPStatus,
+ TLVErrorCode,
+} from './HAPServer.js'
+import { AccessoryInfo, PermissionTypes } from './model/AccessoryInfo.js'
+import { ControllerStorage } from './model/ControllerStorage.js'
+import { IdentifierCache } from './model/IdentifierCache.js'
+import { Service, ServiceEventTypes } from './Service.js'
+import { checkName } from './util/checkName.js'
+import { clone } from './util/clone.js'
+import { formatOutgoingCharacteristicValue } from './util/request-util.js'
+import { isValid, toShortForm } from './util/uuid.js'
+
+const debug = createDebug('HAP-NodeJS:Accessory')
+const MAX_ACCESSORIES = 149 // Maximum number of bridged accessories per bridge.
+const MAX_SERVICES = 100
/**
* Known category values. Category is a hint to iOS clients about what "type" of Accessory this represents, for UI only.
*
* @group Accessory
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum Categories {
- // noinspection JSUnusedGlobalSymbols
OTHER = 1,
BRIDGE = 2,
FAN = 3,
@@ -95,16 +100,18 @@ export const enum Categories {
THERMOSTAT = 9,
SENSOR = 10,
ALARM_SYSTEM = 11,
- // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
- SECURITY_SYSTEM = 11, //Added to conform to HAP naming
+
+ // eslint-disable-next-line ts/no-duplicate-enum-values
+ SECURITY_SYSTEM = 11, // Added to conform to HAP naming
DOOR = 12,
WINDOW = 13,
WINDOW_COVERING = 14,
PROGRAMMABLE_SWITCH = 15,
RANGE_EXTENDER = 16,
CAMERA = 17,
- // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
- IP_CAMERA = 17, //Added to conform to HAP naming
+
+ // eslint-disable-next-line ts/no-duplicate-enum-values
+ IP_CAMERA = 17, // Added to conform to HAP naming
VIDEO_DOORBELL = 18,
AIR_PURIFIER = 19,
AIR_HEATER = 20,
@@ -130,67 +137,68 @@ export const enum Categories {
* @group Accessory
*/
export interface SerializedAccessory {
- displayName: string,
- UUID: string,
- lastKnownUsername?: MacAddress,
- category: Categories,
-
- services: SerializedService[],
- linkedServices?: Record,
- controllers?: SerializedControllerContext[],
+ displayName: string
+ UUID: string
+ lastKnownUsername?: MacAddress
+ category: Categories
+
+ services: SerializedService[]
+ linkedServices?: Record
+ controllers?: SerializedControllerContext[]
}
/**
* @group Controller API
*/
export interface SerializedControllerContext {
- type: ControllerIdentifier, // this field is called type out of history
- services: SerializedServiceMap,
+ type: ControllerIdentifier // this field is called type out of history
+ services: SerializedServiceMap
}
/**
* @group Controller API
*/
-export type SerializedServiceMap = Record; // maps controller defined name (from the ControllerServiceMap) to serviceId
+export type SerializedServiceMap = Record // maps controller defined name (from the ControllerServiceMap) to serviceId
/**
* @group Controller API
*/
export interface ControllerContext {
controller: Controller
- serviceMap: ControllerServiceMap,
+ serviceMap: ControllerServiceMap
}
/**
* @group Accessory
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum CharacteristicWarningType {
- SLOW_WRITE = "slow-write",
- TIMEOUT_WRITE = "timeout-write",
- SLOW_READ = "slow-read",
- TIMEOUT_READ = "timeout-read",
- WARN_MESSAGE = "warn-message",
- ERROR_MESSAGE = "error-message",
- DEBUG_MESSAGE = "debug-message",
+ SLOW_WRITE = 'slow-write',
+ TIMEOUT_WRITE = 'timeout-write',
+ SLOW_READ = 'slow-read',
+ TIMEOUT_READ = 'timeout-read',
+ WARN_MESSAGE = 'warn-message',
+ ERROR_MESSAGE = 'error-message',
+ DEBUG_MESSAGE = 'debug-message',
}
/**
* @group Accessory
*/
export interface CharacteristicWarning {
- characteristic: Characteristic,
- type: CharacteristicWarningType,
- message: string,
- originatorChain: string[],
- stack?: string,
+ characteristic: Characteristic
+ type: CharacteristicWarningType
+ message: string
+ originatorChain: string[]
+ stack?: string
}
/**
* @group Accessory
*/
export interface PublishInfo {
- username: MacAddress;
- pincode: HAPPincode;
+ username: MacAddress
+ pincode: HAPPincode
/**
* Specify the category for the HomeKit accessory.
* The category is used only in the mdns advertisement and specifies the devices type
@@ -199,8 +207,8 @@ export interface PublishInfo {
* For the Television and Smart Speaker service it also affects the icon shown in
* the Home app when paired.
*/
- category?: Categories;
- setupID?: string;
+ category?: Categories
+ setupID?: string
/**
* Defines the host where the HAP server will be bound to.
* When undefined the HAP server will bind to all available interfaces
@@ -252,40 +260,41 @@ export interface PublishInfo {
* So it is advised to specify an interface name instead of a specific address.
*
*/
- bind?: (InterfaceName | IPAddress) | (InterfaceName | IPAddress)[];
+ bind?: (InterfaceName | IPAddress) | (InterfaceName | IPAddress)[]
/**
* Defines the port where the HAP server will be bound to.
* When undefined port 0 will be used resulting in a random port.
*/
- port?: number;
+ port?: number
/**
* If this option is set to true, HAP-NodeJS will add identifying material (based on {@link username})
* to the end of the accessory display name (and bonjour instance name).
* Default: true
*/
- addIdentifyingMaterial?: boolean;
+ addIdentifyingMaterial?: boolean
/**
* Defines the advertiser used with the published Accessory.
*/
- advertiser?: MDNSAdvertiser;
+ advertiser?: MDNSAdvertiser
}
/**
* @group Accessory
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum MDNSAdvertiser {
/**
* Use the `@homebridge/ciao` module as advertiser.
*/
- CIAO = "ciao",
+ CIAO = 'ciao',
/**
* Use the `bonjour-hap` module as advertiser.
*/
- BONJOUR = "bonjour-hap",
+ BONJOUR = 'bonjour-hap',
/**
* Use Avahi/D-Bus as advertiser.
*/
- AVAHI = "avahi",
+ AVAHI = 'avahi',
/**
* Use systemd-resolved/D-Bus as advertiser.
*
@@ -293,32 +302,34 @@ export const enum MDNSAdvertiser {
* Therefore, we can't detect if our advertisement might be lost due to a restart of the systemd-resolved daemon restart.
* Consequentially, treat this feature as an experimental feature.
*/
- RESOLVED = "resolved",
+ RESOLVED = 'resolved',
}
/**
* @group Accessory
*/
-export type AccessoryCharacteristicChange = ServiceCharacteristicChange & {
- service: Service;
-};
+export type AccessoryCharacteristicChange = ServiceCharacteristicChange & {
+ service: Service
+}
/**
* @group Service
*/
export interface ServiceConfigurationChange {
- service: Service;
+ service: Service
}
+// eslint-disable-next-line no-restricted-syntax
const enum WriteRequestState {
REGULAR_REQUEST,
TIMED_WRITE_AUTHENTICATED,
- TIMED_WRITE_REJECTED
+ TIMED_WRITE_REJECTED,
}
/**
* @group Accessory
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum AccessoryEventTypes {
/**
* Emitted when an iOS device wishes for this Accessory to identify itself. If `paired` is false, then
@@ -328,57 +339,51 @@ export const enum AccessoryEventTypes {
* `getService(Service.AccessoryInformation).getCharacteristic(Characteristic.Identify).on('set', ...)`
* You must call the callback for identification to be successful.
*/
- IDENTIFY = "identify",
+ IDENTIFY = 'identify',
/**
* This event is emitted once the HAP TCP socket is bound.
* At this point the mdns advertisement isn't yet available. Use the {@link ADVERTISED} if you require the accessory to be discoverable.
*/
- LISTENING = "listening",
+ LISTENING = 'listening',
/**
* This event is emitted once the mDNS suite has fully advertised the presence of the accessory.
* This event is guaranteed to be called after {@link LISTENING}.
*/
- ADVERTISED = "advertised",
- SERVICE_CONFIGURATION_CHANGE = "service-configurationChange",
+ ADVERTISED = 'advertised',
+ SERVICE_CONFIGURATION_CHANGE = 'service-configurationChange',
/**
* Emitted after a change in the value of one of the provided Service's Characteristics.
*/
- SERVICE_CHARACTERISTIC_CHANGE = "service-characteristic-change",
- PAIRED = "paired",
- UNPAIRED = "unpaired",
+ SERVICE_CHARACTERISTIC_CHANGE = 'service-characteristic-change',
+ PAIRED = 'paired',
+ UNPAIRED = 'unpaired',
- CHARACTERISTIC_WARNING = "characteristic-warning",
+ CHARACTERISTIC_WARNING = 'characteristic-warning',
}
/**
* @group Accessory
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export declare interface Accessory {
- on(event: "identify", listener: (paired: boolean, callback: VoidCallback) => void): this;
- on(event: "listening", listener: (port: number, address: string) => void): this;
- on(event: "advertised", listener: () => void): this;
-
- on(event: "service-configurationChange", listener: (change: ServiceConfigurationChange) => void): this;
- on(event: "service-characteristic-change", listener: (change: AccessoryCharacteristicChange) => void): this;
-
- on(event: "paired", listener: () => void): this;
- on(event: "unpaired", listener: () => void): this;
-
- on(event: "characteristic-warning", listener: (warning: CharacteristicWarning) => void): this;
-
-
- emit(event: "identify", paired: boolean, callback: VoidCallback): boolean;
- emit(event: "listening", port: number, address: string): boolean;
- emit(event: "advertised"): boolean;
-
- emit(event: "service-configurationChange", change: ServiceConfigurationChange): boolean;
- emit(event: "service-characteristic-change", change: AccessoryCharacteristicChange): boolean;
-
- emit(event: "paired"): boolean;
- emit(event: "unpaired"): boolean;
-
- emit(event: "characteristic-warning", warning: CharacteristicWarning): boolean;
+ /* eslint-disable ts/method-signature-style */
+ on(event: 'identify', listener: (paired: boolean, callback: VoidCallback) => void): this
+ on(event: 'listening', listener: (port: number, address: string) => void): this
+ on(event: 'advertised', listener: () => void): this
+ on(event: 'service-configurationChange', listener: (change: ServiceConfigurationChange) => void): this
+ on(event: 'service-characteristic-change', listener: (change: AccessoryCharacteristicChange) => void): this
+ on(event: 'paired', listener: () => void): this
+ on(event: 'unpaired', listener: () => void): this
+ on(event: 'characteristic-warning', listener: (warning: CharacteristicWarning) => void): this
+ emit(event: 'identify', paired: boolean, callback: VoidCallback): boolean
+ emit(event: 'listening', port: number, address: string): boolean
+ emit(event: 'advertised'): boolean
+ emit(event: 'service-configurationChange', change: ServiceConfigurationChange): boolean
+ emit(event: 'service-characteristic-change', change: AccessoryCharacteristicChange): boolean
+ emit(event: 'paired'): boolean
+ emit(event: 'unpaired'): boolean
+ emit(event: 'characteristic-warning', warning: CharacteristicWarning): boolean
+ /* eslint-enable ts/method-signature-style */
}
/**
@@ -392,83 +397,83 @@ export declare interface Accessory {
*
* @group Accessory
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export class Accessory extends EventEmitter {
// Timeout in milliseconds until a characteristic warning is issue
- private static readonly TIMEOUT_WARNING = 3000;
+ private static readonly TIMEOUT_WARNING = 3000
// Timeout in milliseconds after `TIMEOUT_WARNING` until the operation on the characteristic is considered timed out.
- private static readonly TIMEOUT_AFTER_WARNING = 6000;
+ private static readonly TIMEOUT_AFTER_WARNING = 6000
// NOTICE: when adding/changing properties, remember to possibly adjust the serialize/deserialize functions
- aid: Nullable = null; // assigned by us in assignIDs() or by a Bridge
- _isBridge = false; // true if we are a Bridge (creating a new instance of the Bridge subclass sets this to true)
- bridged = false; // true if we are hosted "behind" a Bridge Accessory
- bridge?: Accessory; // if accessory is bridged, this property points to the bridge which bridges this accessory
- bridgedAccessories: Accessory[] = []; // If we are a Bridge, these are the Accessories we are bridging
- reachable = true;
- lastKnownUsername?: MacAddress;
- category: Categories = Categories.OTHER;
- services: Service[] = [];
- private primaryService?: Service;
- shouldPurgeUnusedIDs = true; // Purge unused ids by default
+ aid: Nullable = null // assigned by us in assignIDs() or by a Bridge
+ _isBridge = false // true if we are a Bridge (creating a new instance of the Bridge subclass sets this to true)
+ bridged = false // true if we are hosted "behind" a Bridge Accessory
+ bridge?: Accessory // if accessory is bridged, this property points to the bridge which bridges this accessory
+ bridgedAccessories: Accessory[] = [] // If we are a Bridge, these are the Accessories we are bridging
+ reachable = true
+ lastKnownUsername?: MacAddress
+ category: Categories = Categories.OTHER
+ services: Service[] = []
+ private primaryService?: Service
+ shouldPurgeUnusedIDs = true // Purge unused ids by default
/**
* Captures if initialization steps inside {@link publish} have been called.
* This is important when calling {@link publish} multiple times (e.g. after calling {@link unpublish}).
- * @private Private API
+ * @private
*/
- private initialized = false;
+ private initialized = false
- private controllers: Record = {};
- private serializedControllers?: Record; // store uninitialized controller data after a Accessory.deserialize call
- private activeCameraController?: CameraController;
+ private controllers: Record = {}
+ private serializedControllers?: Record // store uninitialized controller data after a Accessory.deserialize call
+ private activeCameraController?: CameraController
/**
- * @private Private API.
+ * @private
*/
- _accessoryInfo?: Nullable;
+ _accessoryInfo?: Nullable
/**
- * @private Private API.
+ * @private
*/
- _setupID: Nullable = null;
+ _setupID: Nullable = null
/**
- * @private Private API.
+ * @private
*/
- _identifierCache?: Nullable;
+ _identifierCache?: Nullable
/**
- * @private Private API.
+ * @private
*/
- controllerStorage: ControllerStorage = new ControllerStorage(this);
+ controllerStorage: ControllerStorage = new ControllerStorage(this)
/**
- * @private Private API.
+ * @private
*/
- _advertiser?: Advertiser;
+ _advertiser?: Advertiser
/**
- * @private Private API.
+ * @private
*/
- _server?: HAPServer;
+ _server?: HAPServer
/**
- * @private Private API.
+ * @private
*/
- _setupURI?: string;
+ _setupURI?: string
- private configurationChangeDebounceTimeout?: NodeJS.Timeout;
+ private configurationChangeDebounceTimeout?: NodeJS.Timeout
/**
* This property captures the time when we last served a /accessories request.
* For multiple bursts of /accessories request we don't want to always contact GET handlers
*/
- private lastAccessoriesRequest = 0;
+ private lastAccessoriesRequest = 0
constructor(public displayName: string, public UUID: string) {
- super();
- assert(displayName, "Accessories must be created with a non-empty displayName.");
- assert(UUID, "Accessories must be created with a valid UUID.");
- assert(uuid.isValid(UUID), "UUID '" + UUID + "' is not a valid UUID. Try using the provided 'generateUUID' function to create a " +
- "valid UUID from any arbitrary string, like a serial number.");
+ super()
+ assert(displayName, 'Accessories must be created with a non-empty displayName.')
+ assert(UUID, 'Accessories must be created with a valid UUID.')
+ assert(isValid(UUID), `UUID '${UUID}' is not a valid UUID. Try using the provided 'generateUUID' function to create a `
+ + `valid UUID from any arbitrary string, like a serial number.`)
// create our initial "Accessory Information" Service that all Accessories are expected to have
- checkName(this.displayName, "Name", displayName);
+ checkName(this.displayName, 'Name', displayName)
this.addService(Service.AccessoryInformation)
- .setCharacteristic(Characteristic.Name, displayName);
+ .setCharacteristic(Characteristic.Name, displayName)
// sign up for when iOS attempts to "set" the `Identify` characteristic - this means a paired device wishes
// for us to identify ourselves (as opposed to an unpaired device - that case is handled by HAPServer 'identify' event)
@@ -476,22 +481,22 @@ export class Accessory extends EventEmitter {
.getCharacteristic(Characteristic.Identify)!
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
if (value) {
- const paired = true;
- this.identificationRequest(paired, callback);
+ const paired = true
+ this.identificationRequest(paired, callback)
}
- });
+ })
}
private identificationRequest(paired: boolean, callback: IdentifyCallback) {
- debug("[%s] Identification request", this.displayName);
+ debug('[%s] Identification request', this.displayName)
if (this.listeners(AccessoryEventTypes.IDENTIFY).length > 0) {
// allow implementors to identify this Accessory in whatever way is appropriate, and pass along
// the standard callback for completion.
- this.emit(AccessoryEventTypes.IDENTIFY, paired, callback);
+ this.emit(AccessoryEventTypes.IDENTIFY, paired, callback)
} else {
- debug("[%s] Identification request ignored; no listeners to 'identify' event", this.displayName);
- callback();
+ debug('[%s] Identification request ignored; no listeners to \'identify\' event', this.displayName)
+ callback()
}
}
@@ -509,110 +514,109 @@ export class Accessory extends EventEmitter {
* @returns Returns the constructed service instance.
*/
public addService(serviceConstructor: S, ...constructorArgs: ConstructorArgs): Service
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
public addService(serviceParam: Service | typeof Service, ...constructorArgs: any[]): Service {
// service might be a constructor like `Service.AccessoryInformation` instead of an instance
// of Service. Coerce if necessary.
- const service: Service = typeof serviceParam === "function"
- ? new serviceParam(constructorArgs[0], constructorArgs[1], constructorArgs[2])
- : serviceParam;
+ const service: Service = typeof serviceParam === 'function'
+ ? new serviceParam(constructorArgs[0], constructorArgs[1], constructorArgs[2])// eslint-disable-line new-cap
+ : serviceParam
// check for UUID+subtype conflict
for (const existing of this.services) {
if (existing.UUID === service.UUID) {
// OK we have two Services with the same UUID. Check that each defines a `subtype` property and that each is unique.
if (!service.subtype) {
- throw new Error("Cannot add a Service with the same UUID '" + existing.UUID +
- "' as another Service in this Accessory without also defining a unique 'subtype' property.");
+ throw new Error(`Cannot add a Service with the same UUID '${existing.UUID
+ }' as another Service in this Accessory without also defining a unique 'subtype' property.`)
}
if (service.subtype === existing.subtype) {
- throw new Error("Cannot add a Service with the same UUID '" + existing.UUID +
- "' and subtype '" + existing.subtype + "' as another Service in this Accessory.");
+ throw new Error(`Cannot add a Service with the same UUID '${existing.UUID
+ }' and subtype '${existing.subtype}' as another Service in this Accessory.`)
}
}
}
if (this.services.length >= MAX_SERVICES) {
- throw new Error("Cannot add more than " + MAX_SERVICES + " services to a single accessory!");
+ throw new Error(`Cannot add more than ${MAX_SERVICES} services to a single accessory!`)
}
- this.services.push(service);
+ this.services.push(service)
if (service.isPrimaryService) { // check if a primary service was added
if (this.primaryService !== undefined) {
- this.primaryService.isPrimaryService = false;
+ this.primaryService.isPrimaryService = false
}
- this.primaryService = service;
+ this.primaryService = service
}
if (!this.bridged) {
- this.enqueueConfigurationUpdate();
+ this.enqueueConfigurationUpdate()
} else {
- this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service });
+ this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service })
}
- this.setupServiceEventHandlers(service);
+ this.setupServiceEventHandlers(service)
- return service;
+ return service
}
public removeService(service: Service): void {
- const index = this.services.indexOf(service);
+ const index = this.services.indexOf(service)
if (index >= 0) {
- this.services.splice(index, 1);
+ this.services.splice(index, 1)
if (this.primaryService === service) { // check if we are removing out primary service
- this.primaryService = undefined;
+ this.primaryService = undefined
}
- this.removeLinkedService(service); // remove it from linked service entries on the local accessory
+ this.removeLinkedService(service) // remove it from linked service entries on the local accessory
if (!this.bridged) {
- this.enqueueConfigurationUpdate();
+ this.enqueueConfigurationUpdate()
} else {
- this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service });
+ this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service })
}
- service.removeAllListeners();
+ service.removeAllListeners()
}
}
private removeLinkedService(removed: Service) {
for (const service of this.services) {
- service.removeLinkedService(removed);
+ service.removeLinkedService(removed)
}
}
public getService>(name: string | T): Service | undefined {
for (const service of this.services) {
- if (typeof name === "string" && (service.displayName === name || service.name === name || service.subtype === name)) {
- return service;
+ if (typeof name === 'string' && (service.displayName === name || service.name === name || service.subtype === name)) {
+ return service
} else {
// @ts-expect-error ('UUID' does not exist on type 'never')
- if (typeof name === "function" && ((service instanceof name) || (name.UUID === service.UUID))) {
- return service;
+ if (typeof name === 'function' && ((service instanceof name) || (name.UUID === service.UUID))) {
+ return service
}
}
}
- return undefined;
+ return undefined
}
public getServiceById>(uuid: string | T, subType: string): Service | undefined {
for (const service of this.services) {
- if (typeof uuid === "string" && (service.displayName === uuid || service.name === uuid) && service.subtype === subType) {
- return service;
+ if (typeof uuid === 'string' && (service.displayName === uuid || service.name === uuid) && service.subtype === subType) {
+ return service
} else {
// @ts-expect-error ('UUID' does not exist on type 'never')
- if (typeof uuid === "function" && ((service instanceof uuid) || (uuid.UUID === service.UUID)) && service.subtype === subType) {
- return service;
+ if (typeof uuid === 'function' && ((service instanceof uuid) || (uuid.UUID === service.UUID)) && service.subtype === subType) {
+ return service
}
}
}
- return undefined;
+ return undefined
}
/**
@@ -622,108 +626,108 @@ export class Accessory extends EventEmitter {
* @returns the primary accessory
*/
public getPrimaryAccessory = (): Accessory => {
- return this.bridged? this.bridge!: this;
- };
+ return this.bridged ? this.bridge! : this
+ }
public addBridgedAccessory(accessory: Accessory, deferUpdate = false): Accessory {
if (accessory._isBridge || accessory === this) {
- throw new Error("Illegal state: either trying to bridge a bridge or trying to bridge itself!");
+ throw new Error('Illegal state: either trying to bridge a bridge or trying to bridge itself!')
}
if (accessory.initialized) {
- throw new Error("Tried to bridge an accessory which was already published once!");
+ throw new Error('Tried to bridge an accessory which was already published once!')
}
if (accessory.bridge != null) {
// this also prevents that we bridge the same accessory twice!
- throw new Error("Tried to bridge " + accessory.displayName + " while it was already bridged by " + accessory.bridge.displayName);
+ throw new Error(`Tried to bridge ${accessory.displayName} while it was already bridged by ${accessory.bridge.displayName}`)
}
if (this.bridgedAccessories.length >= MAX_ACCESSORIES) {
- throw new Error("Cannot Bridge more than " + MAX_ACCESSORIES + " Accessories");
+ throw new Error(`Cannot Bridge more than ${MAX_ACCESSORIES} Accessories`)
}
// listen for changes in ANY characteristics of ANY services on this Accessory
- accessory.on(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, change => this.handleCharacteristicChangeEvent(accessory, change.service, change));
- accessory.on(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, this.enqueueConfigurationUpdate.bind(this));
- accessory.on(AccessoryEventTypes.CHARACTERISTIC_WARNING, this.handleCharacteristicWarning.bind(this));
+ accessory.on(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, change => this.handleCharacteristicChangeEvent(accessory, change.service, change))
+ accessory.on(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, this.enqueueConfigurationUpdate.bind(this))
+ accessory.on(AccessoryEventTypes.CHARACTERISTIC_WARNING, this.handleCharacteristicWarning.bind(this))
- accessory.bridged = true;
- accessory.bridge = this;
+ accessory.bridged = true
+ accessory.bridge = this
- this.bridgedAccessories.push(accessory);
+ this.bridgedAccessories.push(accessory)
- this.controllerStorage.linkAccessory(accessory); // init controllers of bridged accessory
+ this.controllerStorage.linkAccessory(accessory) // init controllers of bridged accessory
- if(!deferUpdate) {
- this.enqueueConfigurationUpdate();
+ if (!deferUpdate) {
+ this.enqueueConfigurationUpdate()
}
- return accessory;
+ return accessory
}
public addBridgedAccessories(accessories: Accessory[]): void {
for (const accessory of accessories) {
- this.addBridgedAccessory(accessory, true);
+ this.addBridgedAccessory(accessory, true)
}
- this.enqueueConfigurationUpdate();
+ this.enqueueConfigurationUpdate()
}
public removeBridgedAccessory(accessory: Accessory, deferUpdate = false): void {
// check for UUID conflict
- const accessoryIndex = this.bridgedAccessories.indexOf(accessory);
+ const accessoryIndex = this.bridgedAccessories.indexOf(accessory)
if (accessoryIndex === -1) {
- throw new Error("Cannot find the bridged Accessory to remove.");
+ throw new Error('Cannot find the bridged Accessory to remove.')
}
- this.bridgedAccessories.splice(accessoryIndex, 1);
+ this.bridgedAccessories.splice(accessoryIndex, 1)
- accessory.bridged = false;
- accessory.bridge = undefined;
- accessory.removeAllListeners();
+ accessory.bridged = false
+ accessory.bridge = undefined
+ accessory.removeAllListeners()
- if(!deferUpdate) {
- this.enqueueConfigurationUpdate();
+ if (!deferUpdate) {
+ this.enqueueConfigurationUpdate()
}
}
public removeBridgedAccessories(accessories: Accessory[]): void {
for (const accessory of accessories) {
- this.removeBridgedAccessory(accessory, true);
+ this.removeBridgedAccessory(accessory, true)
}
- this.enqueueConfigurationUpdate();
+ this.enqueueConfigurationUpdate()
}
public removeAllBridgedAccessories(): void {
- for (let i = this.bridgedAccessories.length - 1; i >= 0; i --) {
- this.removeBridgedAccessory(this.bridgedAccessories[i], true);
+ for (let i = this.bridgedAccessories.length - 1; i >= 0; i--) {
+ this.removeBridgedAccessory(this.bridgedAccessories[i], true)
}
- this.enqueueConfigurationUpdate();
+ this.enqueueConfigurationUpdate()
}
private getCharacteristicByIID(iid: number): Characteristic | undefined {
for (const service of this.services) {
- const characteristic = service.getCharacteristicByIID(iid);
+ const characteristic = service.getCharacteristicByIID(iid)
if (characteristic) {
- return characteristic;
+ return characteristic
}
}
}
protected getAccessoryByAID(aid: number): Accessory | undefined {
if (this.aid === aid) {
- return this;
+ return this
}
- return this.bridgedAccessories.find(value => value.aid === aid);
+ return this.bridgedAccessories.find(value => value.aid === aid)
}
protected findCharacteristic(aid: number, iid: number): Characteristic | undefined {
- const accessory = this.getAccessoryByAID(aid);
- return accessory && accessory.getCharacteristicByIID(iid);
+ const accessory = this.getAccessoryByAID(aid)
+ return accessory && accessory.getCharacteristicByIID(iid)
}
/**
@@ -741,61 +745,61 @@ export class Accessory extends EventEmitter {
* @param controllerConstructor - The Controller instance or constructor to the Controller with no required arguments.
*/
public configureController(controllerConstructor: Controller | ControllerConstructor): void {
- const controller = typeof controllerConstructor === "function"
- ? new controllerConstructor() // any custom constructor arguments should be passed before using .bind(...)
- : controllerConstructor;
- const id = controller.controllerId();
+ // any custom constructor arguments should be passed before using .bind(...)
+ const controller = typeof controllerConstructor === 'function'
+ ? new controllerConstructor() // eslint-disable-line new-cap
+ : controllerConstructor
+ const id = controller.controllerId()
if (this.controllers[id]) {
- throw new Error(`A Controller with the type/id '${id}' was already added to the accessory ${this.displayName}`);
+ throw new Error(`A Controller with the type/id '${id}' was already added to the accessory ${this.displayName}`)
}
- const savedServiceMap = this.serializedControllers && this.serializedControllers[id];
- let serviceMap: ControllerServiceMap;
+ const savedServiceMap = this.serializedControllers && this.serializedControllers[id]
+ let serviceMap: ControllerServiceMap
if (savedServiceMap) { // we found data to restore from
- const clonedServiceMap = clone(savedServiceMap);
- const updatedServiceMap = controller.initWithServices(savedServiceMap); // init controller with existing services
- serviceMap = updatedServiceMap || savedServiceMap; // initWithServices could return an updated serviceMap, otherwise just use the existing one
+ const clonedServiceMap = clone(savedServiceMap)
+ const updatedServiceMap = controller.initWithServices(savedServiceMap) // init controller with existing services
+ serviceMap = updatedServiceMap || savedServiceMap // initWithServices could return an updated serviceMap, otherwise just use the existing one
if (updatedServiceMap) { // controller returned a ServiceMap and thus signaled an updated set of services
// clonedServiceMap is altered by this method, should not be touched again after this call (for the future people)
- this.handleUpdatedControllerServiceMap(clonedServiceMap, updatedServiceMap);
+ this.handleUpdatedControllerServiceMap(clonedServiceMap, updatedServiceMap)
}
- controller.configureServices(); // let the controller setup all its handlers
+ controller.configureServices() // let the controller setup all its handlers
// remove serialized data from our dictionary:
- delete this.serializedControllers![id];
+ delete this.serializedControllers![id]
if (Object.entries(this.serializedControllers!).length === 0) {
- this.serializedControllers = undefined;
+ this.serializedControllers = undefined
}
} else {
- serviceMap = controller.constructServices(); // let the controller create his services
- controller.configureServices(); // let the controller setup all its handlers
+ serviceMap = controller.constructServices() // let the controller create his services
+ controller.configureServices() // let the controller setup all its handlers
- Object.values(serviceMap).forEach(service => {
+ Object.values(serviceMap).forEach((service) => {
if (service && !this.services.includes(service)) {
- this.addService(service);
+ this.addService(service)
}
- });
+ })
}
-
// --- init handlers and setup context ---
const context: ControllerContext = {
- controller: controller,
- serviceMap: serviceMap,
- };
+ controller,
+ serviceMap,
+ }
if (isSerializableController(controller)) {
- this.controllerStorage.trackController(controller);
+ this.controllerStorage.trackController(controller)
}
- this.controllers[id] = context;
+ this.controllers[id] = context
if (controller instanceof CameraController) { // save CameraController for Snapshot handling
- this.activeCameraController = controller;
+ this.activeCameraController = controller
}
}
@@ -807,119 +811,119 @@ export class Accessory extends EventEmitter {
* @param controller - The controller which should be removed from the accessory.
*/
public removeController(controller: Controller): void {
- const id = controller.controllerId();
+ const id = controller.controllerId()
- const storedController = this.controllers[id];
+ const storedController = this.controllers[id]
if (storedController) {
if (storedController.controller !== controller) {
- throw new Error("[" + this.displayName + "] tried removing a controller with the id/type '" + id +
- "' though provided controller isn't the same instance that is registered!");
+ throw new Error(`[${this.displayName}] tried removing a controller with the id/type '${id
+ }' though provided controller isn't the same instance that is registered!`)
}
if (isSerializableController(controller)) {
// this will reset the state change delegate before we call handleControllerRemoved()
- this.controllerStorage.untrackController(controller);
+ this.controllerStorage.untrackController(controller)
}
if (controller.handleFactoryReset) {
- controller.handleFactoryReset();
+ controller.handleFactoryReset()
}
- controller.handleControllerRemoved();
+ controller.handleControllerRemoved()
- delete this.controllers[id];
+ delete this.controllers[id]
if (this.activeCameraController === controller) {
- this.activeCameraController = undefined;
+ this.activeCameraController = undefined
}
- Object.values(storedController.serviceMap).forEach(service => {
+ Object.values(storedController.serviceMap).forEach((service) => {
if (service) {
- this.removeService(service);
+ this.removeService(service)
}
- });
+ })
}
if (this.serializedControllers) {
- delete this.serializedControllers[id];
+ delete this.serializedControllers[id]
}
}
private handleAccessoryUnpairedForControllers(): void {
for (const context of Object.values(this.controllers)) {
- const controller = context.controller;
+ const controller = context.controller
if (controller.handleFactoryReset) { // if the controller implements handleFactoryReset, setup event handlers for this controller
- controller.handleFactoryReset();
+ controller.handleFactoryReset()
}
if (isSerializableController(controller)) {
- this.controllerStorage.purgeControllerData(controller);
+ this.controllerStorage.purgeControllerData(controller)
}
}
}
private handleUpdatedControllerServiceMap(originalServiceMap: ControllerServiceMap, updatedServiceMap: ControllerServiceMap) {
- updatedServiceMap = clone(updatedServiceMap); // clone it so we can alter it
+ updatedServiceMap = clone(updatedServiceMap) // clone it so we can alter it
- Object.keys(originalServiceMap).forEach(name => { // this loop removed any services contained in both ServiceMaps
- const service = originalServiceMap[name];
- const updatedService = updatedServiceMap[name];
+ Object.keys(originalServiceMap).forEach((name) => { // this loop removed any services contained in both ServiceMaps
+ const service = originalServiceMap[name]
+ const updatedService = updatedServiceMap[name]
if (service && updatedService) { // we check all names contained in both ServiceMaps for changes
- delete originalServiceMap[name]; // delete from original ServiceMap, so it will only contain deleted services at the end
- delete updatedServiceMap[name]; // delete from updated ServiceMap, so it will only contain added services at the end
+ delete originalServiceMap[name] // delete from original ServiceMap, so it will only contain deleted services at the end
+ delete updatedServiceMap[name] // delete from updated ServiceMap, so it will only contain added services at the end
if (service !== updatedService) {
- this.removeService(service);
- this.addService(updatedService);
+ this.removeService(service)
+ this.addService(updatedService)
}
}
- });
+ })
// now originalServiceMap contains only deleted services and updateServiceMap only added services
- Object.values(originalServiceMap).forEach(service => {
+ Object.values(originalServiceMap).forEach((service) => {
if (service) {
- this.removeService(service);
+ this.removeService(service)
}
- });
- Object.values(updatedServiceMap).forEach(service => {
+ })
+ Object.values(updatedServiceMap).forEach((service) => {
if (service) {
- this.addService(service);
+ this.addService(service)
}
- });
+ })
}
setupURI(): string {
if (this._setupURI) {
- return this._setupURI;
+ return this._setupURI
}
- assert(!!this._accessoryInfo, "Cannot generate setupURI on an accessory that isn't published yet!");
+ assert(!!this._accessoryInfo, 'Cannot generate setupURI on an accessory that isn\'t published yet!')
- const buffer = Buffer.alloc(8);
- let value_low = parseInt(this._accessoryInfo.pincode.replace(/-/g, ""), 10);
- const value_high = this._accessoryInfo.category >> 1;
+ const buffer = Buffer.alloc(8)
+ let value_low = Number.parseInt(this._accessoryInfo.pincode.replace(/-/g, ''), 10)
+ const value_high = this._accessoryInfo.category >> 1
- value_low |= 1 << 28; // Supports IP;
+ value_low |= 1 << 28 // Supports IP;
- buffer.writeUInt32BE(value_low, 4);
+ buffer.writeUInt32BE(value_low, 4)
- if (this._accessoryInfo.category & 1) {
- buffer[4] = buffer[4] | 1 << 7;
+ if (this._accessoryInfo.category % 2 !== 0) { // check if the category is odd
+ buffer[4] += 128 // set the 7th bit by adding 128
}
- buffer.writeUInt32BE(value_high, 0);
+ buffer.writeUInt32BE(value_high, 0)
- let encodedPayload = (buffer.readUInt32BE(4) + (buffer.readUInt32BE(0) * 0x100000000)).toString(36).toUpperCase();
+ let encodedPayload = (buffer.readUInt32BE(4) + (buffer.readUInt32BE(0) * 0x100000000)).toString(36).toUpperCase()
if (encodedPayload.length !== 9) {
for (let i = 0; i <= 9 - encodedPayload.length; i++) {
- encodedPayload = "0" + encodedPayload;
+ encodedPayload = `0${encodedPayload}`
}
}
- this._setupURI = "X-HM://" + encodedPayload + this._setupID;
- return this._setupURI;
+ this._setupURI = `X-HM://${encodedPayload}${this._setupID}`
+ return this._setupURI
}
/**
@@ -928,92 +932,92 @@ export class Accessory extends EventEmitter {
* If it is called on a bridge it will call this method for all bridged accessories.
*/
private validateAccessory(mainAccessory?: boolean) {
- const service = this.getService(Service.AccessoryInformation);
+ const service = this.getService(Service.AccessoryInformation)
if (!service) {
- console.log("HAP-NodeJS WARNING: The accessory '" + this.displayName + "' is getting published without a AccessoryInformation service. " +
- "This might prevent the accessory from being added to the Home app or leading to the accessory being unresponsive!");
+ // eslint-disable-next-line no-console
+ console.log(`HAP-NodeJS WARNING: The accessory '${this.displayName}' is getting published without a AccessoryInformation service. `
+ + `This might prevent the accessory from being added to the Home app or leading to the accessory being unresponsive!`)
} else {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
const checkValue = (name: string, value?: any) => {
if (!value) {
- console.log("HAP-NodeJS WARNING: The accessory '" + this.displayName + "' is getting published with the characteristic '" + name + "'" +
- " (of the AccessoryInformation service) not having a value set. " +
- "This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
+ // eslint-disable-next-line no-console
+ console.log(`HAP-NodeJS WARNING: The accessory '${this.displayName}' is getting published with the characteristic '${name}'`
+ + ` (of the AccessoryInformation service) not having a value set. `
+ + `This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!`)
}
- };
+ }
- checkName(this.displayName, "Name", service.getCharacteristic(Characteristic.Name).value);
- checkValue("FirmwareRevision", service.getCharacteristic(Characteristic.FirmwareRevision).value);
- checkValue("Manufacturer", service.getCharacteristic(Characteristic.Manufacturer).value);
- checkValue("Model", service.getCharacteristic(Characteristic.Model).value);
- checkValue("Name", service.getCharacteristic(Characteristic.Name).value);
- checkValue("SerialNumber", service.getCharacteristic(Characteristic.SerialNumber).value);
+ checkName(this.displayName, 'Name', service.getCharacteristic(Characteristic.Name).value)
+ checkValue('FirmwareRevision', service.getCharacteristic(Characteristic.FirmwareRevision).value)
+ checkValue('Manufacturer', service.getCharacteristic(Characteristic.Manufacturer).value)
+ checkValue('Model', service.getCharacteristic(Characteristic.Model).value)
+ checkValue('Name', service.getCharacteristic(Characteristic.Name).value)
+ checkValue('SerialNumber', service.getCharacteristic(Characteristic.SerialNumber).value)
}
if (mainAccessory) {
// the main accessory which is advertised via bonjour must have a name with length <= 63 (limitation of DNS FQDN names)
- assert(Buffer.from(this.displayName, "utf8").length <= 63, "Accessory displayName cannot be longer than 63 bytes!");
+ assert(Buffer.from(this.displayName, 'utf8').length <= 63, 'Accessory displayName cannot be longer than 63 bytes!')
}
if (this.bridged) {
- this.bridgedAccessories.forEach(accessory => accessory.validateAccessory());
+ this.bridgedAccessories.forEach(accessory => accessory.validateAccessory())
}
}
/**
* Assigns aid/iid to ourselves, any Accessories we are bridging, and all associated Services+Characteristics. Uses
* the provided identifierCache to keep IDs stable.
- * @private Private API
+ * @private
*/
_assignIDs(identifierCache: IdentifierCache): void {
-
// if we are responsible for our own identifierCache, start the expiration process
// also check weather we want to have an expiration process
if (this._identifierCache && this.shouldPurgeUnusedIDs) {
- this._identifierCache.startTrackingUsage();
+ this._identifierCache.startTrackingUsage()
}
if (this.bridged) {
// This Accessory is bridged, so it must have an aid > 1. Use the provided identifierCache to
// fetch or assign one based on our UUID.
- this.aid = identifierCache.getAID(this.UUID);
+ this.aid = identifierCache.getAID(this.UUID)
} else {
// Since this Accessory is the server (as opposed to any Accessories that may be bridged behind us),
// we must have aid = 1
- this.aid = 1;
+ this.aid = 1
}
for (const service of this.services) {
if (this._isBridge) {
- service._assignIDs(identifierCache, this.UUID, 2000000000);
+ service._assignIDs(identifierCache, this.UUID, 2000000000)
} else {
- service._assignIDs(identifierCache, this.UUID);
+ service._assignIDs(identifierCache, this.UUID)
}
}
// now assign IDs for any Accessories we are bridging
for (const accessory of this.bridgedAccessories) {
- accessory._assignIDs(identifierCache);
+ accessory._assignIDs(identifierCache)
}
// expire any now-unused cache keys (for Accessories, Services, or Characteristics
// that have been removed since the last call to assignIDs())
if (this._identifierCache) {
- //Check weather we want to purge the unused ids
+ // Check weather we want to purge the unused ids
if (this.shouldPurgeUnusedIDs) {
- this._identifierCache.stopTrackingUsageAndExpireUnused();
+ this._identifierCache.stopTrackingUsageAndExpireUnused()
}
- //Save in case we have new ones
- this._identifierCache.save();
+ // Save in case we have new ones
+ this._identifierCache.save()
}
}
disableUnusedIDPurge(): void {
- this.shouldPurgeUnusedIDs = false;
+ this.shouldPurgeUnusedIDs = false
}
enableUnusedIDPurge(): void {
- this.shouldPurgeUnusedIDs = true;
+ this.shouldPurgeUnusedIDs = true
}
/**
@@ -1021,39 +1025,39 @@ export class Accessory extends EventEmitter {
* when you have disabled auto purge, so you can do it manually
*/
purgeUnusedIDs(): void {
- //Cache the state of the purge mechanism and set it to true
- const oldValue = this.shouldPurgeUnusedIDs;
- this.shouldPurgeUnusedIDs = true;
+ // Cache the state of the purge mechanism and set it to true
+ const oldValue = this.shouldPurgeUnusedIDs
+ this.shouldPurgeUnusedIDs = true
- //Reassign all ids
- this._assignIDs(this._identifierCache!);
+ // Reassign all ids
+ this._assignIDs(this._identifierCache!)
// Revert the purge mechanism state
- this.shouldPurgeUnusedIDs = oldValue;
+ this.shouldPurgeUnusedIDs = oldValue
}
/**
* Returns a JSON representation of this accessory suitable for delivering to HAP clients.
*/
private async toHAP(connection: HAPConnection, contactGetHandlers = true): Promise {
- assert(this.aid, "aid cannot be undefined for accessory '" + this.displayName + "'");
- assert(this.services.length, "accessory '" + this.displayName + "' does not have any services!");
+ assert(this.aid, `aid cannot be undefined for accessory '${this.displayName}'`)
+ assert(this.services.length, `accessory '${this.displayName}' does not have any services!`)
const accessory: AccessoryJsonObject = {
aid: this.aid!,
services: await Promise.all(this.services.map(service => service.toHAP(connection, contactGetHandlers))),
- };
+ }
- const accessories: AccessoryJsonObject[] = [accessory];
+ const accessories: AccessoryJsonObject[] = [accessory]
if (!this.bridged) {
- accessories.push(... await Promise.all(
+ accessories.push(...await Promise.all(
this.bridgedAccessories
.map(accessory => accessory.toHAP(connection, contactGetHandlers).then(value => value[0])),
- ));
+ ))
}
- return accessories;
+ return accessories
}
/**
@@ -1061,25 +1065,25 @@ export class Accessory extends EventEmitter {
*/
private internalHAPRepresentation(assignIds = true): AccessoryJsonObject[] {
if (assignIds) {
- this._assignIDs(this._identifierCache!); // make sure our aid/iid's are all assigned
+ this._assignIDs(this._identifierCache!) // make sure our aid/iid's are all assigned
}
- assert(this.aid, "aid cannot be undefined for accessory '" + this.displayName + "'");
- assert(this.services.length, "accessory '" + this.displayName + "' does not have any services!");
+ assert(this.aid, `aid cannot be undefined for accessory '${this.displayName}'`)
+ assert(this.services.length, `accessory '${this.displayName}' does not have any services!`)
const accessory: AccessoryJsonObject = {
aid: this.aid!,
services: this.services.map(service => service.internalHAPRepresentation()),
- };
+ }
- const accessories: AccessoryJsonObject[] = [accessory];
+ const accessories: AccessoryJsonObject[] = [accessory]
if (!this.bridged) {
for (const accessory of this.bridgedAccessories) {
- accessories.push(accessory.internalHAPRepresentation(false)[0]);
+ accessories.push(accessory.internalHAPRepresentation(false)[0])
}
}
- return accessories;
+ return accessories
}
/**
@@ -1101,60 +1105,60 @@ export class Accessory extends EventEmitter {
*/
public async publish(info: PublishInfo, allowInsecureRequest?: boolean): Promise {
if (this.bridged) {
- throw new Error("Can't publish in accessory which is bridged by another accessory. Bridged by " + this.bridge?.displayName);
+ throw new Error(`Can't publish in accessory which is bridged by another accessory. Bridged by ${this.bridge?.displayName}`)
}
- let service = this.getService(Service.ProtocolInformation);
+ let service = this.getService(Service.ProtocolInformation)
if (!service) {
- service = this.addService(Service.ProtocolInformation); // add the protocol information service to the primary accessory
+ service = this.addService(Service.ProtocolInformation) // add the protocol information service to the primary accessory
}
- service.setCharacteristic(Characteristic.Version, CiaoAdvertiser.protocolVersionService);
+ service.setCharacteristic(Characteristic.Version, CiaoAdvertiser.protocolVersionService)
if (this.lastKnownUsername && this.lastKnownUsername !== info.username) { // username changed since last publish
- Accessory.cleanupAccessoryData(this.lastKnownUsername); // delete old Accessory data
+ Accessory.cleanupAccessoryData(this.lastKnownUsername) // delete old Accessory data
}
if (!this.initialized && (info.addIdentifyingMaterial ?? true)) {
// adding some identifying material to our displayName if it's our first publish() call
- this.displayName = this.displayName + " " + crypto.createHash("sha512")
- .update(info.username, "utf8")
- .digest("hex").slice(0, 4).toUpperCase();
- this.getService(Service.AccessoryInformation)!.updateCharacteristic(Characteristic.Name, this.displayName);
+ this.displayName = `${this.displayName} ${createHash('sha512')
+ .update(info.username, 'utf8')
+ .digest('hex').slice(0, 4).toUpperCase()}`
+ this.getService(Service.AccessoryInformation)!.updateCharacteristic(Characteristic.Name, this.displayName)
}
// attempt to load existing AccessoryInfo from disk
- this._accessoryInfo = AccessoryInfo.load(info.username);
+ this._accessoryInfo = AccessoryInfo.load(info.username)
// if we don't have one, create a new one.
if (!this._accessoryInfo) {
- debug("[%s] Creating new AccessoryInfo for our HAP server", this.displayName);
- this._accessoryInfo = AccessoryInfo.create(info.username);
+ debug('[%s] Creating new AccessoryInfo for our HAP server', this.displayName)
+ this._accessoryInfo = AccessoryInfo.create(info.username)
}
if (info.setupID) {
- this._setupID = info.setupID;
- } else if (this._accessoryInfo.setupID === undefined || this._accessoryInfo.setupID === "") {
- this._setupID = Accessory._generateSetupID();
+ this._setupID = info.setupID
+ } else if (this._accessoryInfo.setupID === undefined || this._accessoryInfo.setupID === '') {
+ this._setupID = Accessory._generateSetupID()
} else {
- this._setupID = this._accessoryInfo.setupID;
+ this._setupID = this._accessoryInfo.setupID
}
- this._accessoryInfo.setupID = this._setupID;
+ this._accessoryInfo.setupID = this._setupID
// make sure we have up-to-date values in AccessoryInfo, then save it in case they changed (or if we just created it)
- this._accessoryInfo.displayName = this.displayName;
- this._accessoryInfo.model = this.getService(Service.AccessoryInformation)!.getCharacteristic(Characteristic.Model).value as string;
- this._accessoryInfo.category = info.category || Categories.OTHER;
- this._accessoryInfo.pincode = info.pincode;
- this._accessoryInfo.save();
+ this._accessoryInfo.displayName = this.displayName
+ this._accessoryInfo.model = this.getService(Service.AccessoryInformation)!.getCharacteristic(Characteristic.Model).value as string
+ this._accessoryInfo.category = info.category || Categories.OTHER
+ this._accessoryInfo.pincode = info.pincode
+ this._accessoryInfo.save()
// create our IdentifierCache, so we can provide clients with stable aid/iid's
- this._identifierCache = IdentifierCache.load(info.username);
+ this._identifierCache = IdentifierCache.load(info.username)
// if we don't have one, create a new one.
if (!this._identifierCache) {
- debug("[%s] Creating new IdentifierCache", this.displayName);
- this._identifierCache = new IdentifierCache(info.username);
+ debug('[%s] Creating new IdentifierCache', this.displayName)
+ this._identifierCache = new IdentifierCache(info.username)
}
// If it's bridge and there are no accessories already assigned to the bridge
@@ -1162,29 +1166,31 @@ export class Accessory extends EventEmitter {
// of accessories that might be added later. Useful when dynamically adding
// accessories.
if (this._isBridge && this.bridgedAccessories.length === 0) {
- this.disableUnusedIDPurge();
- this.controllerStorage.purgeUnidentifiedAccessoryData = false;
+ this.disableUnusedIDPurge()
+ this.controllerStorage.purgeUnidentifiedAccessoryData = false
}
if (!this.initialized) { // controller storage is only loaded from disk the first time we publish!
- this.controllerStorage.load(info.username); // initializing controller data
+ this.controllerStorage.load(info.username) // initializing controller data
}
// assign aid/iid
- this._assignIDs(this._identifierCache);
+ this._assignIDs(this._identifierCache)
// get our accessory information in HAP format and determine if our configuration (that is, our
// Accessories/Services/Characteristics) has changed since the last time we were published. make
// sure to omit actual values since these are not part of the "configuration".
- const config = this.internalHAPRepresentation(false); // TODO ensure this stuff is ordered
+ const config = this.internalHAPRepresentation(false) // TODO ensure this stuff is ordered
// TODO queue this check until about 5 seconds after startup, allowing some last changes after the publish call
// without constantly incrementing the current config number
- this._accessoryInfo.checkForCurrentConfigurationNumberIncrement(config, true);
+ this._accessoryInfo.checkForCurrentConfigurationNumberIncrement(config, true)
- this.validateAccessory(true);
+ this.validateAccessory(true)
// create our Advertiser which broadcasts our presence over mdns
- const parsed = Accessory.parseBindOption(info);
+ const parsed = Accessory.parseBindOption(info)
+
+ debug('[%s] Starting HAP server and publishing Accessory...', this.displayName)
// Select the advertiser to use based on the user's choice and availability
// 1. Check if info.advertiser is set by the user.
@@ -1198,84 +1204,88 @@ export class Accessory extends EventEmitter {
// > If not, use ciao.
if (info.advertiser) {
+ const originalAdvertiser = info.advertiser
+ debug('[%s] Advertiser set to %s', this.displayName, info.advertiser)
if (
- (info.advertiser === MDNSAdvertiser.AVAHI && await AvahiAdvertiser.isAvailable()) ||
- (info.advertiser === MDNSAdvertiser.RESOLVED && await ResolvedAdvertiser.isAvailable()) ||
- (info.advertiser === MDNSAdvertiser.BONJOUR) ||
- (info.advertiser === MDNSAdvertiser.CIAO)
+ (info.advertiser === MDNSAdvertiser.AVAHI && await AvahiAdvertiser.isAvailable())
+ || (info.advertiser === MDNSAdvertiser.RESOLVED && await ResolvedAdvertiser.isAvailable())
+ || (info.advertiser === MDNSAdvertiser.BONJOUR)
+ || (info.advertiser === MDNSAdvertiser.CIAO)
) {
// User chosen advertiser is available, use it
+ debug('[%s] Using advertiser %s', this.displayName, info.advertiser)
} else {
if (await AvahiAdvertiser.isAvailable()) {
- info.advertiser = MDNSAdvertiser.AVAHI;
+ info.advertiser = MDNSAdvertiser.AVAHI
} else {
- info.advertiser = MDNSAdvertiser.CIAO;
+ info.advertiser = MDNSAdvertiser.CIAO
}
console.error(
- `[${this.displayName}] The selected advertiser, "${info.advertiser}", isn't available on this platform. ` +
- `Reverting to ${info.advertiser === MDNSAdvertiser.AVAHI ? "avahi" : "ciao"}.`,
- );
+ `[${this.displayName}] The selected advertiser [${originalAdvertiser}] isn't available on this platform. `
+ + `Reverting to [${info.advertiser}].`,
+ )
}
} else {
if (await AvahiAdvertiser.isAvailable()) {
- info.advertiser = MDNSAdvertiser.AVAHI;
+ info.advertiser = MDNSAdvertiser.AVAHI
} else {
- info.advertiser = MDNSAdvertiser.CIAO;
+ info.advertiser = MDNSAdvertiser.CIAO
}
}
switch (info.advertiser) {
- case MDNSAdvertiser.CIAO:
- this._advertiser = new CiaoAdvertiser(this._accessoryInfo, {
- interface: parsed.advertiserAddress,
- }, {
- restrictedAddresses: parsed.serviceRestrictedAddress,
- disabledIpv6: parsed.serviceDisableIpv6,
- });
- break;
- case MDNSAdvertiser.BONJOUR:
- this._advertiser = new BonjourHAPAdvertiser(this._accessoryInfo, {
- restrictedAddresses: parsed.serviceRestrictedAddress,
- disabledIpv6: parsed.serviceDisableIpv6,
- });
- break;
- case MDNSAdvertiser.AVAHI:
- this._advertiser = new AvahiAdvertiser(this._accessoryInfo);
- break;
- case MDNSAdvertiser.RESOLVED:
- this._advertiser = new ResolvedAdvertiser(this._accessoryInfo);
- break;
- }
- this._advertiser.on(AdvertiserEvent.UPDATED_NAME, name => {
- this.displayName = name;
+ case MDNSAdvertiser.CIAO:
+ this._advertiser = new CiaoAdvertiser(this._accessoryInfo, {
+ interface: parsed.advertiserAddress,
+ }, {
+ restrictedAddresses: parsed.serviceRestrictedAddress,
+ disabledIpv6: parsed.serviceDisableIpv6,
+ })
+ break
+ case MDNSAdvertiser.BONJOUR:
+ this._advertiser = new BonjourHAPAdvertiser(this._accessoryInfo, {
+ restrictedAddresses: parsed.serviceRestrictedAddress,
+ disabledIpv6: parsed.serviceDisableIpv6,
+ })
+ break
+ case MDNSAdvertiser.AVAHI:
+ this._advertiser = new AvahiAdvertiser(this._accessoryInfo)
+ break
+ case MDNSAdvertiser.RESOLVED:
+ this._advertiser = new ResolvedAdvertiser(this._accessoryInfo)
+ break
+ }
+ debug('[%s] Advertiser created', this.displayName)
+ this._advertiser.on(AdvertiserEvent.UPDATED_NAME, (name) => {
+ this.displayName = name
if (this._accessoryInfo) {
- this._accessoryInfo.displayName = name;
- this._accessoryInfo.save();
+ this._accessoryInfo.displayName = name
+ this._accessoryInfo.save()
}
// bonjour service name MUST match the name in the accessory information service
this.getService(Service.AccessoryInformation)!
- .updateCharacteristic(Characteristic.Name, name);
- });
+ .updateCharacteristic(Characteristic.Name, name)
+ })
// create our HAP server which handles all communication between iOS devices and us
- this._server = new HAPServer(this._accessoryInfo);
- this._server.allowInsecureRequest = !!allowInsecureRequest;
- this._server.on(HAPServerEventTypes.LISTENING, this.onListening.bind(this));
- this._server.on(HAPServerEventTypes.IDENTIFY, this.identificationRequest.bind(this, false));
- this._server.on(HAPServerEventTypes.PAIR, this.handleInitialPairSetupFinished.bind(this));
- this._server.on(HAPServerEventTypes.ADD_PAIRING, this.handleAddPairing.bind(this));
- this._server.on(HAPServerEventTypes.REMOVE_PAIRING, this.handleRemovePairing.bind(this));
- this._server.on(HAPServerEventTypes.LIST_PAIRINGS, this.handleListPairings.bind(this));
- this._server.on(HAPServerEventTypes.ACCESSORIES, this.handleAccessories.bind(this));
- this._server.on(HAPServerEventTypes.GET_CHARACTERISTICS, this.handleGetCharacteristics.bind(this));
- this._server.on(HAPServerEventTypes.SET_CHARACTERISTICS, this.handleSetCharacteristics.bind(this));
- this._server.on(HAPServerEventTypes.CONNECTION_CLOSED, this.handleHAPConnectionClosed.bind(this));
- this._server.on(HAPServerEventTypes.REQUEST_RESOURCE, this.handleResource.bind(this));
-
- this._server.listen(info.port, parsed.serverAddress);
-
- this.initialized = true;
+ this._server = new HAPServer(this._accessoryInfo)
+ this._server.allowInsecureRequest = !!allowInsecureRequest
+ this._server.on(HAPServerEventTypes.LISTENING, this.onListening.bind(this))
+ this._server.on(HAPServerEventTypes.IDENTIFY, this.identificationRequest.bind(this, false))
+ this._server.on(HAPServerEventTypes.PAIR, this.handleInitialPairSetupFinished.bind(this))
+ this._server.on(HAPServerEventTypes.ADD_PAIRING, this.handleAddPairing.bind(this))
+ this._server.on(HAPServerEventTypes.REMOVE_PAIRING, this.handleRemovePairing.bind(this))
+ this._server.on(HAPServerEventTypes.LIST_PAIRINGS, this.handleListPairings.bind(this))
+ this._server.on(HAPServerEventTypes.ACCESSORIES, this.handleAccessories.bind(this))
+ this._server.on(HAPServerEventTypes.GET_CHARACTERISTICS, this.handleGetCharacteristics.bind(this))
+ this._server.on(HAPServerEventTypes.SET_CHARACTERISTICS, this.handleSetCharacteristics.bind(this))
+ this._server.on(HAPServerEventTypes.CONNECTION_CLOSED, this.handleHAPConnectionClosed.bind(this))
+ this._server.on(HAPServerEventTypes.REQUEST_RESOURCE, this.handleResource.bind(this))
+
+ this._server.listen(info.port, parsed.serverAddress)
+
+ this.initialized = true
}
/**
@@ -1284,257 +1294,256 @@ export class Accessory extends EventEmitter {
* Trying to invoke publish() on the object will result undefined behavior
*/
public destroy(): Promise {
- const promise = this.unpublish();
+ const promise = this.unpublish()
if (this._accessoryInfo) {
- Accessory.cleanupAccessoryData(this._accessoryInfo.username);
+ Accessory.cleanupAccessoryData(this._accessoryInfo.username)
- this._accessoryInfo = undefined;
- this._identifierCache = undefined;
- this.controllerStorage = new ControllerStorage(this);
+ this._accessoryInfo = undefined
+ this._identifierCache = undefined
+ this.controllerStorage = new ControllerStorage(this)
}
- this.removeAllListeners();
+ this.removeAllListeners()
- return promise;
+ return promise
}
public async unpublish(): Promise {
if (this._server) {
- this._server.destroy();
- this._server = undefined;
+ this._server.destroy()
+ this._server = undefined
}
if (this._advertiser) {
- // noinspection JSIgnoredPromiseFromCall
- await this._advertiser.destroy();
- this._advertiser = undefined;
+ this._advertiser.destroy()
+ this._advertiser = undefined
}
}
private enqueueConfigurationUpdate(): void {
if (this.configurationChangeDebounceTimeout) {
- return; // already enqueued
+ return // already enqueued
}
this.configurationChangeDebounceTimeout = setTimeout(() => {
- this.configurationChangeDebounceTimeout = undefined;
+ this.configurationChangeDebounceTimeout = undefined
if (this._advertiser && this._advertiser) {
// get our accessory information in HAP format and determine if our configuration (that is, our
// Accessories/Services/Characteristics) has changed since the last time we were published. make
// sure to omit actual values since these are not part of the "configuration".
- const config = this.internalHAPRepresentation(); // TODO ensure this stuff is ordered
+ const config = this.internalHAPRepresentation() // TODO ensure this stuff is ordered
if (this._accessoryInfo?.checkForCurrentConfigurationNumberIncrement(config)) {
- this._advertiser.updateAdvertisement();
+ this._advertiser.updateAdvertisement()
}
}
- }, 1000);
- this.configurationChangeDebounceTimeout.unref();
+ }, 1000)
+ this.configurationChangeDebounceTimeout.unref()
// 1s is fine, HomeKit is built that with configuration updates no iid or aid conflicts occur.
// Thus, the only thing happening when the txt update arrives late is already removed accessories/services
// not responding or new accessories/services not yet shown
}
private onListening(port: number, hostname: string): void {
- assert(this._advertiser, "Advertiser wasn't created at onListening!");
+ assert(this._advertiser, 'Advertiser wasn\'t created at onListening!')
// the HAP server is listening, so we can now start advertising our presence.
- this._advertiser!.initPort(port);
+ this._advertiser!.initPort(port)
this._advertiser!.startAdvertising()
.then(() => this.emit(AccessoryEventTypes.ADVERTISED))
- .catch(reason => {
- console.error("Could not create mDNS advertisement. The HAP-Server won't be discoverable: " + reason);
+ .catch((reason) => {
+ console.error(`Could not create mDNS advertisement. The HAP-Server won't be discoverable: ${reason}`)
if (reason.stack) {
- debug("Detailed error: " + reason.stack);
+ debug(`Detailed error: ${reason.stack}`)
}
- });
+ })
- this.emit(AccessoryEventTypes.LISTENING, port, hostname);
+ this.emit(AccessoryEventTypes.LISTENING, port, hostname)
}
private handleInitialPairSetupFinished(username: string, publicKey: Buffer, callback: PairCallback): void {
- debug("[%s] Paired with client %s", this.displayName, username);
+ debug('[%s] Paired with client %s', this.displayName, username)
- this._accessoryInfo?.addPairedClient(username, publicKey, PermissionTypes.ADMIN);
- this._accessoryInfo?.save();
+ this._accessoryInfo?.addPairedClient(username, publicKey, PermissionTypes.ADMIN)
+ this._accessoryInfo?.save()
// update our advertisement, so it can pick up on the paired status of AccessoryInfo
- this._advertiser?.updateAdvertisement();
+ this._advertiser?.updateAdvertisement()
- callback();
+ callback()
- this.emit(AccessoryEventTypes.PAIRED);
+ this.emit(AccessoryEventTypes.PAIRED)
}
private handleAddPairing(connection: HAPConnection, username: string, publicKey: Buffer, permission: PermissionTypes, callback: AddPairingCallback): void {
if (!this._accessoryInfo) {
- callback(TLVErrorCode.UNAVAILABLE);
- return;
+ callback(TLVErrorCode.UNAVAILABLE)
+ return
}
if (!this._accessoryInfo.hasAdminPermissions(connection.username!)) {
- callback(TLVErrorCode.AUTHENTICATION);
- return;
+ callback(TLVErrorCode.AUTHENTICATION)
+ return
}
- const existingKey = this._accessoryInfo.getClientPublicKey(username);
+ const existingKey = this._accessoryInfo.getClientPublicKey(username)
if (existingKey) {
if (existingKey.toString() !== publicKey.toString()) {
- callback(TLVErrorCode.UNKNOWN);
- return;
+ callback(TLVErrorCode.UNKNOWN)
+ return
}
- this._accessoryInfo.updatePermission(username, permission);
+ this._accessoryInfo.updatePermission(username, permission)
} else {
- this._accessoryInfo.addPairedClient(username, publicKey, permission);
+ this._accessoryInfo.addPairedClient(username, publicKey, permission)
}
- this._accessoryInfo.save();
+ this._accessoryInfo.save()
// there should be no need to update advertisement
- callback(0);
+ callback(0)
}
private handleRemovePairing(connection: HAPConnection, username: HAPUsername, callback: RemovePairingCallback): void {
if (!this._accessoryInfo) {
- callback(TLVErrorCode.UNAVAILABLE);
- return;
+ callback(TLVErrorCode.UNAVAILABLE)
+ return
}
if (!this._accessoryInfo.hasAdminPermissions(connection.username!)) {
- callback(TLVErrorCode.AUTHENTICATION);
- return;
+ callback(TLVErrorCode.AUTHENTICATION)
+ return
}
- this._accessoryInfo.removePairedClient(connection, username);
- this._accessoryInfo.save();
+ this._accessoryInfo.removePairedClient(connection, username)
+ this._accessoryInfo.save()
- callback(0); // first of all ensure the pairing is removed before we advertise availability again
+ callback(0) // first of all ensure the pairing is removed before we advertise availability again
if (!this._accessoryInfo.paired()) {
- this._advertiser?.updateAdvertisement();
- this.emit(AccessoryEventTypes.UNPAIRED);
+ this._advertiser?.updateAdvertisement()
+ this.emit(AccessoryEventTypes.UNPAIRED)
- this.handleAccessoryUnpairedForControllers();
+ this.handleAccessoryUnpairedForControllers()
for (const accessory of this.bridgedAccessories) {
- accessory.handleAccessoryUnpairedForControllers();
+ accessory.handleAccessoryUnpairedForControllers()
}
}
}
private handleListPairings(connection: HAPConnection, callback: ListPairingsCallback): void {
if (!this._accessoryInfo) {
- callback(TLVErrorCode.UNAVAILABLE);
- return;
+ callback(TLVErrorCode.UNAVAILABLE)
+ return
}
if (!this._accessoryInfo.hasAdminPermissions(connection.username!)) {
- callback(TLVErrorCode.AUTHENTICATION);
- return;
+ callback(TLVErrorCode.AUTHENTICATION)
+ return
}
- callback(0, this._accessoryInfo.listPairings());
+ callback(0, this._accessoryInfo.listPairings())
}
private handleAccessories(connection: HAPConnection, callback: AccessoriesCallback): void {
- this._assignIDs(this._identifierCache!); // make sure our aid/iid's are all assigned
+ this._assignIDs(this._identifierCache!) // make sure our aid/iid's are all assigned
- const now = Date.now();
- const contactGetHandlers = now - this.lastAccessoriesRequest > 5_000; // we query the latest value if last /accessories was more than 5s ago
- this.lastAccessoriesRequest = now;
+ const now = Date.now()
+ const contactGetHandlers = now - this.lastAccessoriesRequest > 5_000 // we query the latest value if last /accessories was more than 5s ago
+ this.lastAccessoriesRequest = now
- this.toHAP(connection, contactGetHandlers).then(value => {
+ this.toHAP(connection, contactGetHandlers).then((value) => {
callback(undefined, {
accessories: value,
- });
- }, reason => {
- console.error("[" + this.displayName + "] /accessories request error with: " + reason.stack);
- callback({ httpCode: HAPHTTPCode.INTERNAL_SERVER_ERROR, status: HAPStatus.SERVICE_COMMUNICATION_FAILURE });
- });
+ })
+ }, (reason) => {
+ console.error(`[${this.displayName}] /accessories request error with: ${reason.stack}`)
+ callback({ httpCode: HAPHTTPCode.INTERNAL_SERVER_ERROR, status: HAPStatus.SERVICE_COMMUNICATION_FAILURE })
+ })
}
private handleGetCharacteristics(connection: HAPConnection, request: CharacteristicsReadRequest, callback: ReadCharacteristicsCallback): void {
- const characteristics: CharacteristicReadData[] = [];
- const response: CharacteristicsReadResponse = { characteristics: characteristics };
+ const characteristics: CharacteristicReadData[] = []
+ const response: CharacteristicsReadResponse = { characteristics }
- const missingCharacteristics: Set = new Set(request.ids.map(id => id.aid + "." + id.iid));
+ const missingCharacteristics: Set = new Set(request.ids.map(id => `${id.aid}.${id.iid}`))
if (missingCharacteristics.size !== request.ids.length) {
// if those sizes differ, we have duplicates and can't properly handle that
- callback({ httpCode: HAPHTTPCode.UNPROCESSABLE_ENTITY, status: HAPStatus.INVALID_VALUE_IN_REQUEST });
- return;
+ callback({ httpCode: HAPHTTPCode.UNPROCESSABLE_ENTITY, status: HAPStatus.INVALID_VALUE_IN_REQUEST })
+ return
}
let timeout: NodeJS.Timeout | undefined = setTimeout(() => {
for (const id of missingCharacteristics) {
- const split = id.split(".");
- const aid = parseInt(split[0], 10);
- const iid = parseInt(split[1], 10);
-
- const accessory = this.getAccessoryByAID(aid)!;
- const characteristic = accessory.getCharacteristicByIID(iid)!;
- this.sendCharacteristicWarning(characteristic, CharacteristicWarningType.SLOW_READ, "The read handler for the characteristic '" +
- characteristic.displayName + "' on the accessory '" + accessory.displayName + "' was slow to respond!");
+ const split = id.split('.')
+ const aid = Number.parseInt(split[0], 10)
+ const iid = Number.parseInt(split[1], 10)
+
+ const accessory = this.getAccessoryByAID(aid)!
+ const characteristic = accessory.getCharacteristicByIID(iid)!
+ this.sendCharacteristicWarning(characteristic, CharacteristicWarningType.SLOW_READ, `The read handler for the characteristic '${
+ characteristic.displayName}' on the accessory '${accessory.displayName}' was slow to respond!`)
}
// after a total of 10s we do no longer wait for a request to appear and just return status code timeout
timeout = setTimeout(() => {
- timeout = undefined;
+ timeout = undefined
for (const id of missingCharacteristics) {
- const split = id.split(".");
- const aid = parseInt(split[0], 10);
- const iid = parseInt(split[1], 10);
+ const split = id.split('.')
+ const aid = Number.parseInt(split[0], 10)
+ const iid = Number.parseInt(split[1], 10)
- const accessory = this.getAccessoryByAID(aid)!;
- const characteristic = accessory.getCharacteristicByIID(iid)!;
- this.sendCharacteristicWarning(characteristic, CharacteristicWarningType.TIMEOUT_READ, "The read handler for the characteristic '" +
- characteristic.displayName + "' on the accessory '" + accessory.displayName + "' didn't respond at all!. " +
- "Please check that you properly call the callback!");
+ const accessory = this.getAccessoryByAID(aid)!
+ const characteristic = accessory.getCharacteristicByIID(iid)!
+ this.sendCharacteristicWarning(characteristic, CharacteristicWarningType.TIMEOUT_READ, `The read handler for the characteristic '${
+ characteristic.displayName}' on the accessory '${accessory.displayName}' didn't respond at all!. `
+ + `Please check that you properly call the callback!`)
characteristics.push({
- aid: aid,
- iid: iid,
+ aid,
+ iid,
status: HAPStatus.OPERATION_TIMED_OUT,
- });
+ })
}
- missingCharacteristics.clear();
+ missingCharacteristics.clear()
- callback(undefined, response);
- }, Accessory.TIMEOUT_AFTER_WARNING);
- timeout.unref();
- }, Accessory.TIMEOUT_WARNING);
- timeout.unref();
+ callback(undefined, response)
+ }, Accessory.TIMEOUT_AFTER_WARNING)
+ timeout.unref()
+ }, Accessory.TIMEOUT_WARNING)
+ timeout.unref()
for (const id of request.ids) {
- const name = id.aid + "." + id.iid;
- this.handleCharacteristicRead(connection, id, request).then(value => {
+ const name = `${id.aid}.${id.iid}`
+ this.handleCharacteristicRead(connection, id, request).then((value) => {
return {
aid: id.aid,
iid: id.iid,
...value,
- };
- }, reason => { // this error block is only called if hap-nodejs itself messed up
- console.error(`[${this.displayName}] Read request for characteristic ${name} encountered an error: ${reason.stack}`);
+ }
+ }, (reason) => { // this error block is only called if hap-nodejs itself messed up
+ console.error(`[${this.displayName}] Read request for characteristic ${name} encountered an error: ${reason.stack}`)
return {
aid: id.aid,
iid: id.iid,
status: HAPStatus.SERVICE_COMMUNICATION_FAILURE,
- };
- }).then(value => {
+ }
+ }).then((value) => {
if (!timeout) {
- return; // if timeout is undefined, response was already sent out
+ return // if timeout is undefined, response was already sent out
}
- missingCharacteristics.delete(name);
- characteristics.push(value);
+ missingCharacteristics.delete(name)
+ characteristics.push(value)
if (missingCharacteristics.size === 0) {
if (timeout) {
- clearTimeout(timeout);
- timeout = undefined;
+ clearTimeout(timeout)
+ timeout = undefined
}
- callback(undefined, response);
+ callback(undefined, response)
}
- });
+ })
}
}
@@ -1543,168 +1552,166 @@ export class Accessory extends EventEmitter {
id: CharacteristicId,
request: CharacteristicsReadRequest,
): Promise {
- const characteristic = this.findCharacteristic(id.aid, id.iid);
+ const characteristic = this.findCharacteristic(id.aid, id.iid)
if (!characteristic) {
- debug("[%s] Could not find a Characteristic with aid of %s and iid of %s", this.displayName, id.aid, id.iid);
- return { status: HAPStatus.INVALID_VALUE_IN_REQUEST };
+ debug('[%s] Could not find a Characteristic with aid of %s and iid of %s', this.displayName, id.aid, id.iid)
+ return { status: HAPStatus.INVALID_VALUE_IN_REQUEST }
}
if (!characteristic.props.perms.includes(Perms.PAIRED_READ)) { // check if read is allowed for this characteristic
- debug("[%s] Tried reading from characteristic which does not allow reading (aid of %s and iid of %s)", this.displayName, id.aid, id.iid);
- return { status: HAPStatus.WRITE_ONLY_CHARACTERISTIC };
+ debug('[%s] Tried reading from characteristic which does not allow reading (aid of %s and iid of %s)', this.displayName, id.aid, id.iid)
+ return { status: HAPStatus.WRITE_ONLY_CHARACTERISTIC }
}
if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.READ)) {
- const verifiable = this._accessoryInfo && connection.username;
+ const verifiable = this._accessoryInfo && connection.username
if (!verifiable) {
- debug("[%s] Could not verify admin permissions for Characteristic which requires admin permissions for reading (aid of %s and iid of %s)",
- this.displayName, id.aid, id.iid);
+ debug('[%s] Could not verify admin permissions for Characteristic which requires admin permissions for reading (aid of %s and iid of %s)', this.displayName, id.aid, id.iid)
}
if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) {
- return { status: HAPStatus.INSUFFICIENT_PRIVILEGES };
+ return { status: HAPStatus.INSUFFICIENT_PRIVILEGES }
}
}
- return characteristic.handleGetRequest(connection).then(value => {
- value = formatOutgoingCharacteristicValue(value, characteristic.props);
- debug("[%s] Got Characteristic \"%s\" value: \"%s\"", this.displayName, characteristic!.displayName, value);
+ return characteristic.handleGetRequest(connection).then((value) => {
+ value = formatOutgoingCharacteristicValue(value, characteristic.props)
+ debug('[%s] Got Characteristic "%s" value: "%s"', this.displayName, characteristic!.displayName, value)
const data: PartialCharacteristicReadData = {
- value: value == null ? null: value,
- };
+ value: value == null ? null : value,
+ }
if (request.includeMeta) {
- data.format = characteristic.props.format;
- data.unit = characteristic.props.unit;
- data.minValue = characteristic.props.minValue;
- data.maxValue = characteristic.props.maxValue;
- data.minStep = characteristic.props.minStep;
- data.maxLen = characteristic.props.maxLen || characteristic.props.maxDataLen;
+ data.format = characteristic.props.format
+ data.unit = characteristic.props.unit
+ data.minValue = characteristic.props.minValue
+ data.maxValue = characteristic.props.maxValue
+ data.minStep = characteristic.props.minStep
+ data.maxLen = characteristic.props.maxLen || characteristic.props.maxDataLen
}
if (request.includePerms) {
- data.perms = characteristic.props.perms;
+ data.perms = characteristic.props.perms
}
if (request.includeType) {
- data.type = toShortForm(characteristic.UUID);
+ data.type = toShortForm(characteristic.UUID)
}
if (request.includeEvent) {
- data.ev = connection.hasEventNotifications(id.aid, id.iid);
+ data.ev = connection.hasEventNotifications(id.aid, id.iid)
}
- return data;
+ return data
}, (reason: HAPStatus) => {
- // @ts-expect-error: preserveConstEnums compiler option
- debug("[%s] Error getting value for characteristic \"%s\": %s", this.displayName, characteristic.displayName, HAPStatus[reason]);
- return { status: reason };
- });
+ debug('[%s] Error getting value for characteristic "%s": %s', this.displayName, characteristic.displayName, HAPStatus[reason])
+ return { status: reason }
+ })
}
private handleSetCharacteristics(connection: HAPConnection, writeRequest: CharacteristicsWriteRequest, callback: WriteCharacteristicsCallback): void {
- debug("[%s] Processing characteristic set: %s", this.displayName, JSON.stringify(writeRequest));
+ debug('[%s] Processing characteristic set: %s', this.displayName, JSON.stringify(writeRequest))
- let writeState: WriteRequestState = WriteRequestState.REGULAR_REQUEST;
+ let writeState: WriteRequestState = WriteRequestState.REGULAR_REQUEST
if (writeRequest.pid !== undefined) { // check for timed writes
if (connection.timedWritePid === writeRequest.pid) {
- writeState = WriteRequestState.TIMED_WRITE_AUTHENTICATED;
- clearTimeout(connection.timedWriteTimeout!);
- connection.timedWritePid = undefined;
- connection.timedWriteTimeout = undefined;
+ writeState = WriteRequestState.TIMED_WRITE_AUTHENTICATED
+ clearTimeout(connection.timedWriteTimeout!)
+ connection.timedWritePid = undefined
+ connection.timedWriteTimeout = undefined
- debug("[%s] Timed write request got acknowledged for pid %d", this.displayName, writeRequest.pid);
+ debug('[%s] Timed write request got acknowledged for pid %d', this.displayName, writeRequest.pid)
} else {
- writeState = WriteRequestState.TIMED_WRITE_REJECTED;
- debug("[%s] TTL for timed write request has probably expired for pid %d", this.displayName, writeRequest.pid);
+ writeState = WriteRequestState.TIMED_WRITE_REJECTED
+ debug('[%s] TTL for timed write request has probably expired for pid %d', this.displayName, writeRequest.pid)
}
}
- const characteristics: CharacteristicWriteData[] = [];
- const response: CharacteristicsWriteResponse = { characteristics: characteristics };
+ const characteristics: CharacteristicWriteData[] = []
+ const response: CharacteristicsWriteResponse = { characteristics }
const missingCharacteristics: Set = new Set(
writeRequest.characteristics
- .map(characteristic => characteristic.aid + "." + characteristic.iid),
- );
+ .map(characteristic => `${characteristic.aid}.${characteristic.iid}`),
+ )
if (missingCharacteristics.size !== writeRequest.characteristics.length) {
// if those sizes differ, we have duplicates and can't properly handle that
- callback({ httpCode: HAPHTTPCode.UNPROCESSABLE_ENTITY, status: HAPStatus.INVALID_VALUE_IN_REQUEST });
- return;
+ callback({ httpCode: HAPHTTPCode.UNPROCESSABLE_ENTITY, status: HAPStatus.INVALID_VALUE_IN_REQUEST })
+ return
}
let timeout: NodeJS.Timeout | undefined = setTimeout(() => {
for (const id of missingCharacteristics) {
- const split = id.split(".");
- const aid = parseInt(split[0], 10);
- const iid = parseInt(split[1], 10);
-
- const accessory = this.getAccessoryByAID(aid)!;
- const characteristic = accessory.getCharacteristicByIID(iid)!;
- this.sendCharacteristicWarning(characteristic, CharacteristicWarningType.SLOW_WRITE, "The write handler for the characteristic '" +
- characteristic.displayName + "' on the accessory '" + accessory.displayName + "' was slow to respond!");
+ const split = id.split('.')
+ const aid = Number.parseInt(split[0], 10)
+ const iid = Number.parseInt(split[1], 10)
+
+ const accessory = this.getAccessoryByAID(aid)!
+ const characteristic = accessory.getCharacteristicByIID(iid)!
+ this.sendCharacteristicWarning(characteristic, CharacteristicWarningType.SLOW_WRITE, `The write handler for the characteristic '${
+ characteristic.displayName}' on the accessory '${accessory.displayName}' was slow to respond!`)
}
// after a total of 10s we do no longer wait for a request to appear and just return status code timeout
timeout = setTimeout(() => {
- timeout = undefined;
+ timeout = undefined
for (const id of missingCharacteristics) {
- const split = id.split(".");
- const aid = parseInt(split[0], 10);
- const iid = parseInt(split[1], 10);
+ const split = id.split('.')
+ const aid = Number.parseInt(split[0], 10)
+ const iid = Number.parseInt(split[1], 10)
- const accessory = this.getAccessoryByAID(aid)!;
- const characteristic = accessory.getCharacteristicByIID(iid)!;
- this.sendCharacteristicWarning(characteristic, CharacteristicWarningType.TIMEOUT_WRITE, "The write handler for the characteristic '" +
- characteristic.displayName + "' on the accessory '" + accessory.displayName + "' didn't respond at all!. " +
- "Please check that you properly call the callback!");
+ const accessory = this.getAccessoryByAID(aid)!
+ const characteristic = accessory.getCharacteristicByIID(iid)!
+ this.sendCharacteristicWarning(characteristic, CharacteristicWarningType.TIMEOUT_WRITE, `The write handler for the characteristic '${
+ characteristic.displayName}' on the accessory '${accessory.displayName}' didn't respond at all!. `
+ + `Please check that you properly call the callback!`)
characteristics.push({
- aid: aid,
- iid: iid,
+ aid,
+ iid,
status: HAPStatus.OPERATION_TIMED_OUT,
- });
+ })
}
- missingCharacteristics.clear();
+ missingCharacteristics.clear()
- callback(undefined, response);
- }, Accessory.TIMEOUT_AFTER_WARNING);
- timeout.unref();
- }, Accessory.TIMEOUT_WARNING);
- timeout.unref();
+ callback(undefined, response)
+ }, Accessory.TIMEOUT_AFTER_WARNING)
+ timeout.unref()
+ }, Accessory.TIMEOUT_WARNING)
+ timeout.unref()
for (const data of writeRequest.characteristics) {
- const name = data.aid + "." + data.iid;
- this.handleCharacteristicWrite(connection, data, writeState).then(value => {
+ const name = `${data.aid}.${data.iid}`
+ this.handleCharacteristicWrite(connection, data, writeState).then((value) => {
return {
aid: data.aid,
iid: data.iid,
...value,
- };
- }, reason => { // this error block is only called if hap-nodejs itself messed up
- console.error(`[${this.displayName}] Write request for characteristic ${name} encountered an error: ${reason.stack}`);
+ }
+ }, (reason) => { // this error block is only called if hap-nodejs itself messed up
+ console.error(`[${this.displayName}] Write request for characteristic ${name} encountered an error: ${reason.stack}`)
return {
aid: data.aid,
iid: data.iid,
status: HAPStatus.SERVICE_COMMUNICATION_FAILURE,
- };
- }).then(value => {
+ }
+ }).then((value) => {
if (!timeout) {
- return; // if timeout is undefined, response was already sent out
+ return // if timeout is undefined, response was already sent out
}
- missingCharacteristics.delete(name);
- characteristics.push(value);
+ missingCharacteristics.delete(name)
+ characteristics.push(value)
if (missingCharacteristics.size === 0) { // if everything returned send the response
if (timeout) {
- clearTimeout(timeout);
- timeout = undefined;
+ clearTimeout(timeout)
+ timeout = undefined
}
- callback(undefined, response);
+ callback(undefined, response)
}
- });
+ })
}
}
@@ -1713,49 +1720,47 @@ export class Accessory extends EventEmitter {
data: CharacteristicWrite,
writeState: WriteRequestState,
): Promise {
- const characteristic = this.findCharacteristic(data.aid, data.iid);
+ const characteristic = this.findCharacteristic(data.aid, data.iid)
if (!characteristic) {
- debug("[%s] Could not find a Characteristic with aid of %s and iid of %s", this.displayName, data.aid, data.iid);
- return { status: HAPStatus.INVALID_VALUE_IN_REQUEST };
+ debug('[%s] Could not find a Characteristic with aid of %s and iid of %s', this.displayName, data.aid, data.iid)
+ return { status: HAPStatus.INVALID_VALUE_IN_REQUEST }
}
if (writeState === WriteRequestState.TIMED_WRITE_REJECTED) {
- return { status: HAPStatus.INVALID_VALUE_IN_REQUEST };
+ return { status: HAPStatus.INVALID_VALUE_IN_REQUEST }
}
if (data.ev == null && data.value == null) {
- return { status: HAPStatus.INVALID_VALUE_IN_REQUEST };
+ return { status: HAPStatus.INVALID_VALUE_IN_REQUEST }
}
if (data.ev != null) { // register/unregister event notifications
if (!characteristic.props.perms.includes(Perms.NOTIFY)) { // check if notify is allowed for this characteristic
- debug("[%s] Tried %s notifications for Characteristic which does not allow notify (aid of %s and iid of %s)",
- this.displayName, data.ev? "enabling": "disabling", data.aid, data.iid);
- return { status: HAPStatus.NOTIFICATION_NOT_SUPPORTED };
+ debug('[%s] Tried %s notifications for Characteristic which does not allow notify (aid of %s and iid of %s)', this.displayName, data.ev ? 'enabling' : 'disabling', data.aid, data.iid)
+ return { status: HAPStatus.NOTIFICATION_NOT_SUPPORTED }
}
if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.NOTIFY)) {
- const verifiable = connection.username && this._accessoryInfo;
+ const verifiable = connection.username && this._accessoryInfo
if (!verifiable) {
- debug("[%s] Could not verify admin permissions for Characteristic which requires admin permissions for notify (aid of %s and iid of %s)",
- this.displayName, data.aid, data.iid);
+ debug('[%s] Could not verify admin permissions for Characteristic which requires admin permissions for notify (aid of %s and iid of %s)', this.displayName, data.aid, data.iid)
}
if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) {
- return { status: HAPStatus.INSUFFICIENT_PRIVILEGES };
+ return { status: HAPStatus.INSUFFICIENT_PRIVILEGES }
}
}
- const notificationsEnabled = connection.hasEventNotifications(data.aid, data.iid);
+ const notificationsEnabled = connection.hasEventNotifications(data.aid, data.iid)
if (data.ev && !notificationsEnabled) {
- connection.enableEventNotifications(data.aid, data.iid);
- characteristic.subscribe();
- debug("[%s] Registered Characteristic \"%s\" on \"%s\" for events", connection.remoteAddress, characteristic.displayName, this.displayName);
+ connection.enableEventNotifications(data.aid, data.iid)
+ characteristic.subscribe()
+ debug('[%s] Registered Characteristic "%s" on "%s" for events', connection.remoteAddress, characteristic.displayName, this.displayName)
} else if (!data.ev && notificationsEnabled) {
- characteristic.unsubscribe();
- connection.disableEventNotifications(data.aid, data.iid);
- debug("[%s] Unregistered Characteristic \"%s\" on \"%s\" for events", connection.remoteAddress, characteristic.displayName, this.displayName);
+ characteristic.unsubscribe()
+ connection.disableEventNotifications(data.aid, data.iid)
+ debug('[%s] Unregistered Characteristic "%s" on "%s" for events', connection.remoteAddress, characteristic.displayName, this.displayName)
}
// response is returned below in the else block
@@ -1763,19 +1768,18 @@ export class Accessory extends EventEmitter {
if (data.value != null) {
if (!characteristic.props.perms.includes(Perms.PAIRED_WRITE)) { // check if write is allowed for this characteristic
- debug("[%s] Tried writing to Characteristic which does not allow writing (aid of %s and iid of %s)", this.displayName, data.aid, data.iid);
- return { status: HAPStatus.READ_ONLY_CHARACTERISTIC };
+ debug('[%s] Tried writing to Characteristic which does not allow writing (aid of %s and iid of %s)', this.displayName, data.aid, data.iid)
+ return { status: HAPStatus.READ_ONLY_CHARACTERISTIC }
}
if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.WRITE)) {
- const verifiable = connection.username && this._accessoryInfo;
+ const verifiable = connection.username && this._accessoryInfo
if (!verifiable) {
- debug("[%s] Could not verify admin permissions for Characteristic which requires admin permissions for write (aid of %s and iid of %s)",
- this.displayName, data.aid, data.iid);
+ debug('[%s] Could not verify admin permissions for Characteristic which requires admin permissions for write (aid of %s and iid of %s)', this.displayName, data.aid, data.iid)
}
if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) {
- return { status: HAPStatus.INSUFFICIENT_PRIVILEGES };
+ return { status: HAPStatus.INSUFFICIENT_PRIVILEGES }
}
}
@@ -1783,131 +1787,128 @@ export class Accessory extends EventEmitter {
// if the characteristic "supports additional authorization" but doesn't define a handler for the check
// we conclude that the characteristic doesn't want to check the authData (currently) and just allows access for everybody
- let allowWrite;
+ let allowWrite
try {
- allowWrite = characteristic.additionalAuthorizationHandler(data.authData);
+ allowWrite = characteristic.additionalAuthorizationHandler(data.authData)
} catch (error) {
- console.warn("[" + this.displayName + "] Additional authorization handler has thrown an error when checking authData: " + error.stack);
- allowWrite = false;
+ console.warn(`[${this.displayName}] Additional authorization handler has thrown an error when checking authData: ${error.stack}`)
+ allowWrite = false
}
if (!allowWrite) {
- return { status: HAPStatus.INSUFFICIENT_AUTHORIZATION };
+ return { status: HAPStatus.INSUFFICIENT_AUTHORIZATION }
}
}
if (characteristic.props.perms.includes(Perms.TIMED_WRITE) && writeState !== WriteRequestState.TIMED_WRITE_AUTHENTICATED) {
- debug("[%s] Tried writing to a timed write only Characteristic without properly preparing (iid of %s and aid of %s)",
- this.displayName, data.aid, data.iid);
- return { status: HAPStatus.INVALID_VALUE_IN_REQUEST };
+ debug('[%s] Tried writing to a timed write only Characteristic without properly preparing (iid of %s and aid of %s)', this.displayName, data.aid, data.iid)
+ return { status: HAPStatus.INVALID_VALUE_IN_REQUEST }
}
- return characteristic.handleSetRequest(data.value, connection).then(value => {
- debug("[%s] Setting Characteristic \"%s\" to value %s", this.displayName, characteristic.displayName, data.value);
+ return characteristic.handleSetRequest(data.value, connection).then((value) => {
+ debug('[%s] Setting Characteristic "%s" to value %s', this.displayName, characteristic.displayName, data.value)
return {
// if write response is requests and value is provided, return that
- value: data.r && value? formatOutgoingCharacteristicValue(value, characteristic.props): undefined,
+ value: data.r && value ? formatOutgoingCharacteristicValue(value, characteristic.props) : undefined,
status: HAPStatus.SUCCESS,
- };
+ }
}, (status: HAPStatus) => {
- // @ts-expect-error: forceConsistentCasingInFileNames compiler option
- debug("[%s] Error setting Characteristic \"%s\" to value %s: ", this.displayName, characteristic.displayName, data.value, HAPStatus[status]);
+ debug('[%s] Error setting Characteristic "%s" to value %s: ', this.displayName, characteristic.displayName, data.value, HAPStatus[status])
- return { status: status };
- });
+ return { status }
+ })
}
- return { status: HAPStatus.SUCCESS };
+ return { status: HAPStatus.SUCCESS }
}
private handleResource(data: ResourceRequest, callback: ResourceRequestCallback): void {
- if (data["resource-type"] === ResourceRequestType.IMAGE) {
- const aid = data.aid; // aid is optionally supplied by HomeKit (for example when camera is bridged, multiple cams, etc)
+ if (data['resource-type'] === ResourceRequestType.IMAGE) {
+ const aid = data.aid // aid is optionally supplied by HomeKit (for example when camera is bridged, multiple cams, etc.)
- let accessory: Accessory | undefined = undefined;
- let controller: CameraController | undefined = undefined;
+ let accessory: Accessory | undefined
+ let controller: CameraController | undefined
if (aid) {
- accessory = this.getAccessoryByAID(aid);
+ accessory = this.getAccessoryByAID(aid)
if (accessory && accessory.activeCameraController) {
- controller = accessory.activeCameraController;
+ controller = accessory.activeCameraController
}
} else if (this.activeCameraController) { // aid was not supplied, check if this accessory is a camera
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- accessory = this;
- controller = this.activeCameraController;
+ accessory = this // eslint-disable-line ts/no-this-alias
+ controller = this.activeCameraController
}
if (!controller) {
- debug("[%s] received snapshot request though no camera controller was associated!");
- callback({ httpCode: HAPHTTPCode.NOT_FOUND, status: HAPStatus.RESOURCE_DOES_NOT_EXIST });
- return;
+ debug('[%s] received snapshot request though no camera controller was associated!')
+ callback({ httpCode: HAPHTTPCode.NOT_FOUND, status: HAPStatus.RESOURCE_DOES_NOT_EXIST })
+ return
}
- controller.handleSnapshotRequest(data["image-height"], data["image-width"], accessory?.displayName, data.reason)
- .then(buffer => {
- callback(undefined, buffer);
+ controller.handleSnapshotRequest(data['image-height'], data['image-width'], accessory?.displayName, data.reason)
+ .then((buffer) => {
+ callback(undefined, buffer)
}, (status: HAPStatus) => {
- callback({ httpCode: HAPHTTPCode.MULTI_STATUS, status: status });
- });
+ callback({ httpCode: HAPHTTPCode.MULTI_STATUS, status })
+ })
- return;
+ return
}
- debug("[%s] received request for unsupported image type: " + data["resource-type"], this._accessoryInfo?.username);
- callback({ httpCode: HAPHTTPCode.NOT_FOUND, status: HAPStatus.RESOURCE_DOES_NOT_EXIST });
+ debug(`[%s] received request for unsupported image type: ${data['resource-type']}`, this._accessoryInfo?.username)
+ callback({ httpCode: HAPHTTPCode.NOT_FOUND, status: HAPStatus.RESOURCE_DOES_NOT_EXIST })
}
private handleHAPConnectionClosed(connection: HAPConnection): void {
for (const event of connection.getRegisteredEvents()) {
- const ids = event.split(".");
- const aid = parseInt(ids[0], 10);
- const iid = parseInt(ids[1], 10);
+ const ids = event.split('.')
+ const aid = Number.parseInt(ids[0], 10)
+ const iid = Number.parseInt(ids[1], 10)
- const characteristic = this.findCharacteristic(aid, iid);
+ const characteristic = this.findCharacteristic(aid, iid)
if (characteristic) {
- characteristic.unsubscribe();
+ characteristic.unsubscribe()
}
}
- connection.clearRegisteredEvents();
+ connection.clearRegisteredEvents()
}
private handleServiceConfigurationChangeEvent(service: Service): void {
if (!service.isPrimaryService && service === this.primaryService) {
// service changed form primary to non-primary service
- this.primaryService = undefined;
+ this.primaryService = undefined
} else if (service.isPrimaryService && service !== this.primaryService) {
// service changed from non-primary to primary service
if (this.primaryService !== undefined) {
- this.primaryService.isPrimaryService = false;
+ this.primaryService.isPrimaryService = false
}
- this.primaryService = service;
+ this.primaryService = service
}
if (this.bridged) {
- this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service });
+ this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service })
} else {
- this.enqueueConfigurationUpdate();
+ this.enqueueConfigurationUpdate()
}
}
private handleCharacteristicChangeEvent(accessory: Accessory, service: Service, change: ServiceCharacteristicChange): void {
if (this.bridged) { // forward this to our main accessory
- this.emit(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, { ...change, service: service });
+ this.emit(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, { ...change, service })
} else {
if (!this._server) {
- return; // we're not running a HAPServer, so there's no one to notify about this event
+ return // we're not running a HAPServer, so there's no one to notify about this event
}
if (accessory.aid == null || change.characteristic.iid == null) {
- debug("[%s] Muting event notification for %s as ids aren't yet assigned!", accessory.displayName, change.characteristic.displayName);
- return;
+ debug('[%s] Muting event notification for %s as ids aren\'t yet assigned!', accessory.displayName, change.characteristic.displayName)
+ return
}
- if (change.context != null && typeof change.context === "object" && (change.context as CharacteristicOperationContext).omitEventUpdate) {
- debug("[%s] Omitting event updates for %s as specified in the context object!", accessory.displayName, change.characteristic.displayName);
- return;
+ if (change.context != null && typeof change.context === 'object' && (change.context as CharacteristicOperationContext).omitEventUpdate) {
+ debug('[%s] Omitting event updates for %s as specified in the context object!', accessory.displayName, change.characteristic.displayName)
+ return
}
if (!(change.reason === ChangeReason.EVENT || change.oldValue !== change.newValue
@@ -1917,57 +1918,57 @@ export class Accessory extends EventEmitter {
// we only emit a change event if the reason was a call to sendEventNotification, if the value changed
// as of a write request or a read request or if the change happened on dedicated event characteristics
// otherwise we ignore this change event (with the return below)
- return;
+ return
}
- const uuid = change.characteristic.UUID;
+ const uuid = change.characteristic.UUID
const immediateDelivery = uuid === Characteristic.ButtonEvent.UUID || uuid === Characteristic.ProgrammableSwitchEvent.UUID
- || uuid === Characteristic.MotionDetected.UUID || uuid === Characteristic.ContactSensorState.UUID;
+ || uuid === Characteristic.MotionDetected.UUID || uuid === Characteristic.ContactSensorState.UUID
- const value = formatOutgoingCharacteristicValue(change.newValue, change.characteristic.props);
- this._server.sendEventNotifications(accessory.aid, change.characteristic.iid, value, change.originator, immediateDelivery);
+ const value = formatOutgoingCharacteristicValue(change.newValue, change.characteristic.props)
+ this._server.sendEventNotifications(accessory.aid, change.characteristic.iid, value, change.originator, immediateDelivery)
}
}
private sendCharacteristicWarning(characteristic: Characteristic, type: CharacteristicWarningType, message: string): void {
this.handleCharacteristicWarning({
- characteristic: characteristic,
- type: type,
- message: message,
+ characteristic,
+ type,
+ message,
originatorChain: [characteristic.displayName], // we are missing the service displayName, but that's okay
- stack: new Error().stack,
- });
+ stack: new Error().stack, // eslint-disable-line unicorn/error-message
+ })
}
private handleCharacteristicWarning(warning: CharacteristicWarning): void {
- warning.originatorChain = [this.displayName, ...warning.originatorChain];
+ warning.originatorChain = [this.displayName, ...warning.originatorChain]
- const emitted = this.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, warning);
+ const emitted = this.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, warning)
if (!emitted) {
- const message = `[${warning.originatorChain.join("@")}] ${warning.message}`;
+ const message = `[${warning.originatorChain.join('@')}] ${warning.message}`
if (warning.type === CharacteristicWarningType.ERROR_MESSAGE
- || warning.type === CharacteristicWarningType.TIMEOUT_READ|| warning.type === CharacteristicWarningType.TIMEOUT_WRITE) {
- console.error(message);
+ || warning.type === CharacteristicWarningType.TIMEOUT_READ || warning.type === CharacteristicWarningType.TIMEOUT_WRITE) {
+ console.error(message)
} else {
- console.warn(message);
+ console.warn(message)
}
- debug("[%s] Above characteristic warning was thrown at: %s", this.displayName, warning.stack ?? "unknown");
+ debug('[%s] Above characteristic warning was thrown at: %s', this.displayName, warning.stack ?? 'unknown')
}
}
private setupServiceEventHandlers(service: Service): void {
- service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, this.handleServiceConfigurationChangeEvent.bind(this, service));
- service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, this.handleCharacteristicChangeEvent.bind(this, this, service));
- service.on(ServiceEventTypes.CHARACTERISTIC_WARNING, this.handleCharacteristicWarning.bind(this));
+ service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, this.handleServiceConfigurationChangeEvent.bind(this, service))
+ service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, this.handleCharacteristicChangeEvent.bind(this, this, service))
+ service.on(ServiceEventTypes.CHARACTERISTIC_WARNING, this.handleCharacteristicWarning.bind(this))
}
private _sideloadServices(targetServices: Service[]): void {
for (const service of targetServices) {
- this.setupServiceEventHandlers(service);
+ this.setupServiceEventHandlers(service)
}
- this.services = targetServices.slice();
+ this.services = targetServices.slice()
// Fix Identify
this
@@ -1975,23 +1976,23 @@ export class Accessory extends EventEmitter {
.getCharacteristic(Characteristic.Identify)!
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
if (value) {
- const paired = true;
- this.identificationRequest(paired, callback);
+ const paired = true
+ this.identificationRequest(paired, callback)
}
- });
+ })
}
private static _generateSetupID(): string {
- const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- const max = chars.length;
- let setupID = "";
+ const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ const max = chars.length
+ let setupID = ''
for (let i = 0; i < 4; i++) {
- const index = Math.floor(Math.random() * max);
- setupID += chars.charAt(index);
+ const index = Math.floor(Math.random() * max)
+ setupID += chars.charAt(index)
}
- return setupID;
+ return setupID
}
// serialization and deserialization functions, mainly designed for homebridge to create a json copy to store on disk
@@ -1999,203 +2000,202 @@ export class Accessory extends EventEmitter {
const json: SerializedAccessory = {
displayName: accessory.displayName,
UUID: accessory.UUID,
- lastKnownUsername: accessory._accessoryInfo? accessory._accessoryInfo.username: undefined,
+ lastKnownUsername: accessory._accessoryInfo ? accessory._accessoryInfo.username : undefined,
category: accessory.category,
services: [],
- };
+ }
- const linkedServices: Record = {};
- let hasLinkedServices = false;
+ const linkedServices: Record = {}
+ let hasLinkedServices = false
- accessory.services.forEach(service => {
- json.services.push(Service.serialize(service));
+ accessory.services.forEach((service) => {
+ json.services.push(Service.serialize(service))
- const linkedServicesPresentation: ServiceId[] = [];
- service.linkedServices.forEach(linkedService => {
- linkedServicesPresentation.push(linkedService.getServiceId());
- });
+ const linkedServicesPresentation: ServiceId[] = []
+ service.linkedServices.forEach((linkedService) => {
+ linkedServicesPresentation.push(linkedService.getServiceId())
+ })
if (linkedServicesPresentation.length > 0) {
- linkedServices[service.getServiceId()] = linkedServicesPresentation;
- hasLinkedServices = true;
+ linkedServices[service.getServiceId()] = linkedServicesPresentation
+ hasLinkedServices = true
}
- });
+ })
if (hasLinkedServices) {
- json.linkedServices = linkedServices;
+ json.linkedServices = linkedServices
}
- const controllers: SerializedControllerContext[] = [];
+ const controllers: SerializedControllerContext[] = []
// save controllers
- Object.values(accessory.controllers).forEach((context: ControllerContext) => {
+ Object.values(accessory.controllers).forEach((context: ControllerContext) => {
controllers.push({
type: context.controller.controllerId(),
services: Accessory.serializeServiceMap(context.serviceMap),
- });
- });
+ })
+ })
// also save controller which didn't get initialized (could lead to service duplication if we throw that data away)
Object.entries(accessory.serializedControllers || {}).forEach(([id, serviceMap]) => {
controllers.push({
type: id,
services: Accessory.serializeServiceMap(serviceMap),
- });
- });
+ })
+ })
if (controllers.length > 0) {
- json.controllers = controllers;
+ json.controllers = controllers
}
- return json;
+ return json
}
public static deserialize(json: SerializedAccessory): Accessory {
- const accessory = new Accessory(json.displayName, json.UUID);
+ const accessory = new Accessory(json.displayName, json.UUID)
- accessory.lastKnownUsername = json.lastKnownUsername;
- accessory.category = json.category;
+ accessory.lastKnownUsername = json.lastKnownUsername
+ accessory.category = json.category
- const services: Service[] = [];
- const servicesMap: Record = {};
+ const services: Service[] = []
+ const servicesMap: Record = {}
- json.services.forEach(serialized => {
- const service = Service.deserialize(serialized);
+ json.services.forEach((serialized) => {
+ const service = Service.deserialize(serialized)
- services.push(service);
- servicesMap[service.getServiceId()] = service;
- });
+ services.push(service)
+ servicesMap[service.getServiceId()] = service
+ })
if (json.linkedServices) {
- for (const [ serviceId, linkedServicesKeys ] of Object.entries(json.linkedServices)) {
- const primaryService = servicesMap[serviceId];
+ for (const [serviceId, linkedServicesKeys] of Object.entries(json.linkedServices)) {
+ const primaryService = servicesMap[serviceId]
if (!primaryService) {
- continue;
+ continue
}
- linkedServicesKeys.forEach(linkedServiceKey => {
- const linkedService = servicesMap[linkedServiceKey];
+ linkedServicesKeys.forEach((linkedServiceKey) => {
+ const linkedService = servicesMap[linkedServiceKey]
if (linkedService) {
- primaryService.addLinkedService(linkedService);
+ primaryService.addLinkedService(linkedService)
}
- });
+ })
}
}
if (json.controllers) { // just save it for later if it exists {@see configureController}
- accessory.serializedControllers = {};
+ accessory.serializedControllers = {}
- json.controllers.forEach(serializedController => {
- accessory.serializedControllers![serializedController.type] = Accessory.deserializeServiceMap(serializedController.services, servicesMap);
- });
+ json.controllers.forEach((serializedController) => {
+ accessory.serializedControllers![serializedController.type] = Accessory.deserializeServiceMap(serializedController.services, servicesMap)
+ })
}
- accessory._sideloadServices(services);
+ accessory._sideloadServices(services)
- return accessory;
+ return accessory
}
public static cleanupAccessoryData(username: MacAddress): void {
- IdentifierCache.remove(username);
- AccessoryInfo.remove(username);
- ControllerStorage.remove(username);
+ IdentifierCache.remove(username)
+ AccessoryInfo.remove(username)
+ ControllerStorage.remove(username)
}
private static serializeServiceMap(serviceMap: ControllerServiceMap): SerializedServiceMap {
- const serialized: SerializedServiceMap = {};
+ const serialized: SerializedServiceMap = {}
Object.entries(serviceMap).forEach(([name, service]) => {
if (!service) {
- return;
+ return
}
- serialized[name] = service.getServiceId();
- });
+ serialized[name] = service.getServiceId()
+ })
- return serialized;
+ return serialized
}
private static deserializeServiceMap(serializedServiceMap: SerializedServiceMap, servicesMap: Record): ControllerServiceMap {
- const controllerServiceMap: ControllerServiceMap = {};
+ const controllerServiceMap: ControllerServiceMap = {}
Object.entries(serializedServiceMap).forEach(([name, serviceId]) => {
- const service = servicesMap[serviceId];
+ const service = servicesMap[serviceId]
if (service) {
- controllerServiceMap[name] = service;
+ controllerServiceMap[name] = service
}
- });
+ })
- return controllerServiceMap;
+ return controllerServiceMap
}
private static parseBindOption(info: PublishInfo): {
- advertiserAddress?: string[],
- serviceRestrictedAddress?: string[],
- serviceDisableIpv6?: boolean,
- serverAddress?: string,
+ advertiserAddress?: string[]
+ serviceRestrictedAddress?: string[]
+ serviceDisableIpv6?: boolean
+ serverAddress?: string
} {
- let advertiserAddress: string[] | undefined = undefined;
- let disableIpv6: boolean | undefined = undefined;
- let serverAddress: string | undefined = undefined;
+ let advertiserAddress: string[] | undefined
+ let disableIpv6: boolean | undefined
+ let serverAddress: string | undefined
if (info.bind) {
- const entries: Set = new Set(Array.isArray(info.bind)? info.bind: [info.bind]);
+ const entries: Set = new Set(Array.isArray(info.bind) ? info.bind : [info.bind])
- if (entries.has("::")) {
- serverAddress = "::";
+ if (entries.has('::')) {
+ serverAddress = '::'
- entries.delete("::");
+ entries.delete('::')
if (entries.size) {
- advertiserAddress = Array.from(entries);
+ advertiserAddress = Array.from(entries)
}
- } else if (entries.has("0.0.0.0")) {
- disableIpv6 = true;
- serverAddress = "0.0.0.0";
+ } else if (entries.has('0.0.0.0')) {
+ disableIpv6 = true
+ serverAddress = '0.0.0.0'
- entries.delete("0.0.0.0");
+ entries.delete('0.0.0.0')
if (entries.size) {
- advertiserAddress = Array.from(entries);
+ advertiserAddress = Array.from(entries)
}
} else if (entries.size === 1) {
- advertiserAddress = Array.from(entries);
+ advertiserAddress = Array.from(entries)
- const entry = entries.values().next().value; // grab the first one
+ const entry = entries.values().next().value // grab the first one
- const version = net.isIP(entry); // check if ip address was specified or an interface name
+ const version = isIP(entry) // check if ip address was specified or an interface name
if (version) {
- serverAddress = version === 4? "0.0.0.0": "::"; // we currently bind to unspecified addresses so config-ui always has a connection via loopback
+ serverAddress = version === 4 ? '0.0.0.0' : '::' // we currently bind to unspecified addresses so config-ui always has a connection via loopback
} else {
- serverAddress = "::"; // the interface could have both ipv4 and ipv6 addresses
+ serverAddress = '::' // the interface could have both ipv4 and ipv6 addresses
}
} else if (entries.size > 1) {
- advertiserAddress = Array.from(entries);
+ advertiserAddress = Array.from(entries)
- let bindUnspecifiedIpv6 = false; // we bind on "::" if there are interface names, or we detect ipv6 addresses
+ let bindUnspecifiedIpv6 = false // we bind on "::" if there are interface names, or we detect ipv6 addresses
for (const entry of entries) {
- const version = net.isIP(entry);
+ const version = isIP(entry)
if (version === 0 || version === 6) {
- bindUnspecifiedIpv6 = true;
- break;
+ bindUnspecifiedIpv6 = true
+ break
}
}
if (bindUnspecifiedIpv6) {
- serverAddress = "::";
+ serverAddress = '::'
} else {
- serverAddress = "0.0.0.0";
+ serverAddress = '0.0.0.0'
}
}
}
return {
- advertiserAddress: advertiserAddress,
+ advertiserAddress,
serviceRestrictedAddress: advertiserAddress,
serviceDisableIpv6: disableIpv6,
- serverAddress: serverAddress,
- };
+ serverAddress,
+ }
}
-
}
diff --git a/src/lib/Advertiser.spec.ts b/src/lib/Advertiser.spec.ts
index 08c4546e0..c9b35ec7e 100644
--- a/src/lib/Advertiser.spec.ts
+++ b/src/lib/Advertiser.spec.ts
@@ -1,29 +1,31 @@
-import { CiaoAdvertiser, PairingFeatureFlag, StatusFlag } from "./Advertiser";
+import { describe, expect, it } from 'vitest'
+
+import { CiaoAdvertiser, PairingFeatureFlag, StatusFlag } from './Advertiser.js'
describe(CiaoAdvertiser, () => {
- describe("ff and sf", () => {
- it("should correctly format pairing feature flags", () => {
- expect(CiaoAdvertiser.ff()).toEqual(0);
- expect(CiaoAdvertiser.ff(PairingFeatureFlag.SUPPORTS_HARDWARE_AUTHENTICATION)).toEqual(1);
- expect(CiaoAdvertiser.ff(PairingFeatureFlag.SUPPORTS_SOFTWARE_AUTHENTICATION)).toEqual(2);
+ describe('ff and sf', () => {
+ it('should correctly format pairing feature flags', () => {
+ expect(CiaoAdvertiser.ff()).toEqual(0)
+ expect(CiaoAdvertiser.ff(PairingFeatureFlag.SUPPORTS_HARDWARE_AUTHENTICATION)).toEqual(1)
+ expect(CiaoAdvertiser.ff(PairingFeatureFlag.SUPPORTS_SOFTWARE_AUTHENTICATION)).toEqual(2)
expect(CiaoAdvertiser.ff(
PairingFeatureFlag.SUPPORTS_HARDWARE_AUTHENTICATION,
PairingFeatureFlag.SUPPORTS_SOFTWARE_AUTHENTICATION,
- )).toEqual(3);
- });
+ )).toEqual(3)
+ })
- it("should correctly format status flags", () => {
- expect(CiaoAdvertiser.sf()).toEqual(0);
+ it('should correctly format status flags', () => {
+ expect(CiaoAdvertiser.sf()).toEqual(0)
- expect(CiaoAdvertiser.sf(StatusFlag.NOT_PAIRED)).toEqual(1);
- expect(CiaoAdvertiser.sf(StatusFlag.NOT_JOINED_WIFI)).toEqual(2);
- expect(CiaoAdvertiser.sf(StatusFlag.PROBLEM_DETECTED)).toEqual(4);
+ expect(CiaoAdvertiser.sf(StatusFlag.NOT_PAIRED)).toEqual(1)
+ expect(CiaoAdvertiser.sf(StatusFlag.NOT_JOINED_WIFI)).toEqual(2)
+ expect(CiaoAdvertiser.sf(StatusFlag.PROBLEM_DETECTED)).toEqual(4)
- expect(CiaoAdvertiser.sf(StatusFlag.NOT_PAIRED, StatusFlag.NOT_JOINED_WIFI)).toEqual(3);
- expect(CiaoAdvertiser.sf(StatusFlag.NOT_PAIRED, StatusFlag.PROBLEM_DETECTED)).toEqual(5);
- expect(CiaoAdvertiser.sf(StatusFlag.NOT_JOINED_WIFI, StatusFlag.PROBLEM_DETECTED)).toEqual(6);
+ expect(CiaoAdvertiser.sf(StatusFlag.NOT_PAIRED, StatusFlag.NOT_JOINED_WIFI)).toEqual(3)
+ expect(CiaoAdvertiser.sf(StatusFlag.NOT_PAIRED, StatusFlag.PROBLEM_DETECTED)).toEqual(5)
+ expect(CiaoAdvertiser.sf(StatusFlag.NOT_JOINED_WIFI, StatusFlag.PROBLEM_DETECTED)).toEqual(6)
- expect(CiaoAdvertiser.sf(StatusFlag.NOT_PAIRED, StatusFlag.NOT_JOINED_WIFI, StatusFlag.PROBLEM_DETECTED)).toEqual(7);
- });
- });
-});
+ expect(CiaoAdvertiser.sf(StatusFlag.NOT_PAIRED, StatusFlag.NOT_JOINED_WIFI, StatusFlag.PROBLEM_DETECTED)).toEqual(7)
+ })
+ })
+})
diff --git a/src/lib/Advertiser.ts b/src/lib/Advertiser.ts
index 301e075c8..27fa42b45 100644
--- a/src/lib/Advertiser.ts
+++ b/src/lib/Advertiser.ts
@@ -1,17 +1,24 @@
-// eslint-disable-next-line @typescript-eslint/triple-slash-reference
///
-import ciao, { CiaoService, MDNSServerOptions, Responder, ServiceEvent, ServiceTxt, ServiceType } from "@homebridge/ciao";
-import { InterfaceName, IPAddress } from "@homebridge/ciao/lib/NetworkManager";
-import dbus, { DBusInterface, MessageBus } from "@homebridge/dbus-native";
-import assert from "assert";
-import bonjour, { BonjourHAP, BonjourHAPService } from "bonjour-hap";
-import crypto from "crypto";
-import createDebug from "debug";
-import { EventEmitter } from "events";
-import { AccessoryInfo } from "./model/AccessoryInfo";
-import { PromiseTimeout } from "./util/promise-utils";
-
-const debug = createDebug("HAP-NodeJS:Advertiser");
+import type { CiaoService, MDNSServerOptions, Responder, ServiceTxt } from '@homebridge/ciao'
+import type { InterfaceName, IPAddress } from '@homebridge/ciao/lib/NetworkManager.js'
+import type { DBusInterface, MessageBus } from '@homebridge/dbus-native'
+import type { BonjourHAP, BonjourHAPService } from 'bonjour-hap'
+
+import type { AccessoryInfo } from './model/AccessoryInfo'
+
+import assert from 'node:assert'
+import { Buffer } from 'node:buffer'
+import { createHash } from 'node:crypto'
+import { EventEmitter } from 'node:events'
+
+import { getResponder, ServiceEvent, ServiceType } from '@homebridge/ciao'
+import bonjour from 'bonjour-hap'
+import createDebug from 'debug'
+
+import { systemBus } from './dbus/index.js'
+import { PromiseTimeout } from './util/promise-utils.js'
+
+const debug = createDebug('HAP-NodeJS:Advertiser')
/**
* This enum lists all bitmasks for all known status flags.
@@ -19,6 +26,7 @@ const debug = createDebug("HAP-NodeJS:Advertiser");
*
* @group Advertiser
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum StatusFlag {
NOT_PAIRED = 0x01,
NOT_JOINED_WIFI = 0x02,
@@ -31,6 +39,7 @@ export const enum StatusFlag {
*
* @group Advertiser
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum PairingFeatureFlag {
SUPPORTS_HARDWARE_AUTHENTICATION = 0x01,
SUPPORTS_SOFTWARE_AUTHENTICATION = 0x02,
@@ -39,21 +48,23 @@ export const enum PairingFeatureFlag {
/**
* @group Advertiser
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum AdvertiserEvent {
/**
* Emitted if the underlying mDNS advertisers signals, that the service name
* was automatically changed due to some naming conflicts on the network.
*/
- UPDATED_NAME = "updated-name",
+ UPDATED_NAME = 'updated-name',
}
/**
* @group Advertiser
*/
export declare interface Advertiser {
- on(event: "updated-name", listener: (name: string) => void): this;
-
- emit(event: "updated-name", name: string): boolean;
+ /* eslint-disable ts/method-signature-style */
+ on(event: 'updated-name', listener: (name: string) => void): this
+ emit(event: 'updated-name', name: string): boolean
+ /* eslint-enable ts/method-signature-style */
}
/**
@@ -74,30 +85,30 @@ export interface ServiceNetworkOptions {
* If the service is set to advertise on a given interface, though the MDNSServer is
* configured to ignore this interface, the service won't be advertised on the interface.
*/
- restrictedAddresses?: (InterfaceName | IPAddress)[];
+ restrictedAddresses?: (InterfaceName | IPAddress)[]
/**
* The service won't advertise ipv6 address records.
* This can be used to simulate binding on 0.0.0.0.
* May be combined with {@link restrictedAddresses}.
*/
- disabledIpv6?: boolean;
+ disabledIpv6?: boolean
}
/**
* A generic Advertiser interface required for any MDNS Advertiser backend implementations.
*
- * All implementations have to extend NodeJS' {@link EventEmitter} and emit the events defined in {@link AdvertiserEvent}.
+ * All implementations have to extend Node.js {@link EventEmitter} and emit the events defined in {@link AdvertiserEvent}.
*
* @group Advertiser
*/
export interface Advertiser {
- initPort(port: number): void;
+ initPort: (port: number) => void
- startAdvertising(): Promise;
+ startAdvertising: () => Promise
- updateAdvertisement(silent?: boolean): void;
+ updateAdvertisement: (silent?: boolean) => void
- destroy(): void;
+ destroy: () => void
}
/**
@@ -111,92 +122,92 @@ export interface Advertiser {
* @group Advertiser
*/
export class CiaoAdvertiser extends EventEmitter implements Advertiser {
- static protocolVersion = "1.1";
- static protocolVersionService = "1.1.0";
+ static protocolVersion = '1.1'
+ static protocolVersionService = '1.1.0'
- private readonly accessoryInfo: AccessoryInfo;
- private readonly setupHash: string;
+ private readonly accessoryInfo: AccessoryInfo
+ private readonly setupHash: string
- private readonly responder: Responder;
- private readonly advertisedService: CiaoService;
+ private readonly responder: Responder
+ private readonly advertisedService: CiaoService
constructor(accessoryInfo: AccessoryInfo, responderOptions?: MDNSServerOptions, serviceOptions?: ServiceNetworkOptions) {
- super();
- this.accessoryInfo = accessoryInfo;
- this.setupHash = CiaoAdvertiser.computeSetupHash(accessoryInfo);
+ super()
+ this.accessoryInfo = accessoryInfo
+ this.setupHash = CiaoAdvertiser.computeSetupHash(accessoryInfo)
- this.responder = ciao.getResponder({
+ this.responder = getResponder({
...responderOptions,
- });
+ })
this.advertisedService = this.responder.createService({
name: this.accessoryInfo.displayName,
type: ServiceType.HAP,
txt: CiaoAdvertiser.createTxt(accessoryInfo, this.setupHash),
// host will default now to .local, spaces replaced with dashes
...serviceOptions,
- });
- this.advertisedService.on(ServiceEvent.NAME_CHANGED, this.emit.bind(this, AdvertiserEvent.UPDATED_NAME));
+ })
+ this.advertisedService.on(ServiceEvent.NAME_CHANGED, this.emit.bind(this, AdvertiserEvent.UPDATED_NAME))
- debug(`Preparing Advertiser for '${this.accessoryInfo.displayName}' using ciao backend!`);
+ debug(`Preparing Advertiser for '${this.accessoryInfo.displayName}' using ciao backend!`)
}
public initPort(port: number): void {
- this.advertisedService.updatePort(port);
+ this.advertisedService.updatePort(port)
}
public startAdvertising(): Promise {
- debug(`Starting to advertise '${this.accessoryInfo.displayName}' using ciao backend!`);
- return this.advertisedService!.advertise();
+ debug(`Starting to advertise '${this.accessoryInfo.displayName}' using ciao backend!`)
+ return this.advertisedService!.advertise()
}
public updateAdvertisement(silent?: boolean): void {
- const txt = CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash);
- debug("Updating txt record (txt: %o, silent: %d)", txt, silent);
- this.advertisedService!.updateTxt(txt, silent);
+ const txt = CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash)
+ debug('Updating txt record (txt: %o, silent: %d)', txt, silent)
+ this.advertisedService!.updateTxt(txt, silent)
}
public async destroy(): Promise {
// advertisedService.destroy(); is called implicitly via the shutdown call
- await this.responder.shutdown();
- this.removeAllListeners();
+ await this.responder.shutdown()
+ this.removeAllListeners()
}
static createTxt(accessoryInfo: AccessoryInfo, setupHash: string): ServiceTxt {
- const statusFlags: StatusFlag[] = [];
+ const statusFlags: StatusFlag[] = []
if (!accessoryInfo.paired()) {
- statusFlags.push(StatusFlag.NOT_PAIRED);
+ statusFlags.push(StatusFlag.NOT_PAIRED)
}
return {
- "c#": accessoryInfo.getConfigVersion(), // current configuration number
- ff: CiaoAdvertiser.ff(), // pairing feature flags
- id: accessoryInfo.username, // device id
- md: accessoryInfo.model, // model name
- pv: CiaoAdvertiser.protocolVersion, // protocol version
- "s#": 1, // current state number (must be 1)
- sf: CiaoAdvertiser.sf(...statusFlags), // status flags
- ci: accessoryInfo.category,
- sh: setupHash,
- };
+ 'c#': accessoryInfo.getConfigVersion(), // current configuration number
+ 'ff': CiaoAdvertiser.ff(), // pairing feature flags
+ 'id': accessoryInfo.username, // device id
+ 'md': accessoryInfo.model, // model name
+ 'pv': CiaoAdvertiser.protocolVersion, // protocol version
+ 's#': 1, // current state number (must be 1)
+ 'sf': CiaoAdvertiser.sf(...statusFlags), // status flags
+ 'ci': accessoryInfo.category,
+ 'sh': setupHash,
+ }
}
static computeSetupHash(accessoryInfo: AccessoryInfo): string {
- const hash = crypto.createHash("sha512");
- hash.update(accessoryInfo.setupID + accessoryInfo.username.toUpperCase());
- return hash.digest().slice(0, 4).toString("base64");
+ const hash = createHash('sha512')
+ hash.update(accessoryInfo.setupID + accessoryInfo.username.toUpperCase())
+ return hash.digest().toString('base64').substring(0, 4)
}
public static ff(...flags: PairingFeatureFlag[]): number {
- let value = 0;
- flags.forEach(flag => value |= flag);
- return value;
+ let value = 0
+ flags.forEach(flag => value |= flag)
+ return value
}
public static sf(...flags: StatusFlag[]): number {
- let value = 0;
- flags.forEach(flag => value |= flag);
- return value;
+ let value = 0
+ flags.forEach(flag => value |= flag)
+ return value
}
}
@@ -206,123 +217,124 @@ export class CiaoAdvertiser extends EventEmitter implements Advertiser {
* @group Advertiser
*/
export class BonjourHAPAdvertiser extends EventEmitter implements Advertiser {
- private readonly accessoryInfo: AccessoryInfo;
- private readonly setupHash: string;
- private readonly serviceOptions?: ServiceNetworkOptions;
+ private readonly accessoryInfo: AccessoryInfo
+ private readonly setupHash: string
+ private readonly serviceOptions?: ServiceNetworkOptions
- private bonjour: BonjourHAP;
- private advertisement?: BonjourHAPService;
+ private bonjour: BonjourHAP
+ private advertisement?: BonjourHAPService
- private port?: number;
- private destroyed = false;
+ private port?: number
+ private destroyed = false
constructor(accessoryInfo: AccessoryInfo, serviceOptions?: ServiceNetworkOptions) {
- super();
- this.accessoryInfo = accessoryInfo;
- this.setupHash = CiaoAdvertiser.computeSetupHash(accessoryInfo);
- this.serviceOptions = serviceOptions;
+ super()
+ this.accessoryInfo = accessoryInfo
+ this.setupHash = CiaoAdvertiser.computeSetupHash(accessoryInfo)
+ this.serviceOptions = serviceOptions
- this.bonjour = bonjour();
- debug(`Preparing Advertiser for '${this.accessoryInfo.displayName}' using bonjour-hap backend!`);
+ this.bonjour = bonjour()
+ debug(`Preparing Advertiser for '${this.accessoryInfo.displayName}' using bonjour-hap backend!`)
}
public initPort(port: number): void {
- this.port = port;
+ this.port = port
}
public startAdvertising(): Promise {
- assert(!this.destroyed, "Can't advertise on a destroyed bonjour instance!");
+ assert(!this.destroyed, 'Can\'t advertise on a destroyed bonjour instance!')
if (this.port == null) {
- throw new Error("Tried starting bonjour-hap advertisement without initializing port!");
+ throw new Error('Tried starting bonjour-hap advertisement without initializing port!')
}
- debug(`Starting to advertise '${this.accessoryInfo.displayName}' using bonjour-hap backend!`);
+ debug(`Starting to advertise '${this.accessoryInfo.displayName}' using bonjour-hap backend!`)
if (this.advertisement) {
- this.destroy();
+ this.destroy()
}
- const hostname = this.accessoryInfo.username.replace(/:/ig, "_") + ".local";
+ const hostname = `${this.accessoryInfo.username.replace(/:/g, '_')}.local`
this.advertisement = this.bonjour.publish({
name: this.accessoryInfo.displayName,
- type: "hap",
+ type: 'hap',
port: this.port,
txt: CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash),
host: hostname,
addUnsafeServiceEnumerationRecord: true,
...this.serviceOptions,
- });
+ })
- return PromiseTimeout(1);
+ return PromiseTimeout(1)
}
public updateAdvertisement(silent?: boolean): void {
- const txt = CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash);
- debug("Updating txt record (txt: %o, silent: %d)", txt, silent);
+ const txt = CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash)
+ debug('Updating txt record (txt: %o, silent: %d)', txt, silent)
if (this.advertisement) {
- this.advertisement.updateTxt(txt, silent);
+ this.advertisement.updateTxt(txt, silent)
}
}
public destroy(): void {
if (this.advertisement) {
this.advertisement.stop(() => {
- this.advertisement!.destroy();
- this.advertisement = undefined;
- this.bonjour.destroy();
- });
+ this.advertisement!.destroy()
+ this.advertisement = undefined
+ this.bonjour.destroy()
+ })
} else {
- this.bonjour.destroy();
+ this.bonjour.destroy()
}
}
-
}
function messageBusConnectionResult(bus: MessageBus): Promise {
return new Promise((resolve, reject) => {
+ debug('Waiting for connection to message bus...')
const errorHandler = (error: Error) => {
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
- bus.connection.removeListener("connect", connectHandler);
- reject(error);
- };
+ debug('Failed to connect to message bus: %s', error)
+ // eslint-disable-next-line ts/no-use-before-define
+ bus.connection.removeListener('connect', connectHandler)
+ reject(error)
+ }
const connectHandler = () => {
- bus.connection.removeListener("error", errorHandler);
- resolve();
- };
+ debug('Connected to message bus!')
+ bus.connection.removeListener('error', errorHandler)
+ resolve()
+ }
- bus.connection.once("connect", connectHandler);
- bus.connection.once("error", errorHandler);
- });
+ bus.connection.once('connect', connectHandler)
+ bus.connection.once('error', errorHandler)
+ debug('Listening for connection to message bus...')
+ })
}
/**
* @group Advertiser
*/
export class DBusInvokeError extends Error {
- readonly errorName: string;
+ readonly errorName: string
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(errorObject: { name: string, message: any }) {
- super();
+ super()
- Object.setPrototypeOf(this, DBusInvokeError.prototype);
+ Object.setPrototypeOf(this, DBusInvokeError.prototype)
- this.name = "DBusInvokeError";
+ this.name = 'DBusInvokeError'
- this.errorName = errorObject.name;
+ this.errorName = errorObject.name
if (Array.isArray(errorObject.message) && errorObject.message.length === 1) {
- this.message = errorObject.message[0];
+ this.message = errorObject.message[0]
} else {
- this.message = errorObject.message.toString();
+ this.message = errorObject.message.toString()
}
}
}
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function dbusInvoke( bus: MessageBus, destination: string, path: string, dbusInterface: string, member: string, others?: any): Promise {
+function dbusInvoke(bus: MessageBus, destination: string, path: string, dbusInterface: string, member: string, others?: any): Promise {
return new Promise((resolve, reject) => {
const command = {
destination,
@@ -330,20 +342,18 @@ function dbusInvoke( bus: MessageBus, destination: string, path: string, dbusInt
interface: dbusInterface,
member,
...(others || {}),
- };
+ }
bus.invoke(command, (err, result) => {
if (err) {
- reject(new DBusInvokeError(err));
+ reject(new DBusInvokeError(err))
} else {
- resolve(result);
+ resolve(result)
}
- });
-
- });
+ })
+ })
}
-
/**
* AvahiServerState.
*
@@ -351,13 +361,13 @@ function dbusInvoke( bus: MessageBus, destination: string, path: string, dbusInt
*
* @group Advertiser
*/
+// eslint-disable-next-line no-restricted-syntax
const enum AvahiServerState {
- // noinspection JSUnusedGlobalSymbols
INVALID = 0,
REGISTERING,
RUNNING,
COLLISION,
- FAILURE
+ FAILURE,
}
/**
@@ -370,77 +380,77 @@ const enum AvahiServerState {
* @group Advertiser
*/
export class AvahiAdvertiser extends EventEmitter implements Advertiser {
- private readonly accessoryInfo: AccessoryInfo;
- private readonly setupHash: string;
+ private readonly accessoryInfo: AccessoryInfo
+ private readonly setupHash: string
- private port?: number;
+ private port?: number
- private bus?: MessageBus;
- private avahiServerInterface?: DBusInterface;
- private path?: string;
+ private bus?: MessageBus
+ private avahiServerInterface?: DBusInterface
+ private path?: string
- private readonly stateChangeHandler: (state: AvahiServerState) => void;
+ private readonly stateChangeHandler: (state: AvahiServerState) => void
constructor(accessoryInfo: AccessoryInfo) {
- super();
- this.accessoryInfo = accessoryInfo;
- this.setupHash = CiaoAdvertiser.computeSetupHash(accessoryInfo);
+ super()
+ this.accessoryInfo = accessoryInfo
+ this.setupHash = CiaoAdvertiser.computeSetupHash(accessoryInfo)
- debug(`Preparing Advertiser for '${this.accessoryInfo.displayName}' using Avahi backend!`);
+ debug(`Preparing Advertiser for '${this.accessoryInfo.displayName}' using Avahi backend!`)
- this.bus = dbus.systemBus();
+ this.bus = systemBus()
- this.stateChangeHandler = this.handleStateChangedEvent.bind(this);
+ this.stateChangeHandler = this.handleStateChangedEvent.bind(this)
}
private createTxt(): Array {
return Object
.entries(CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash))
- .map((el: Array) => Buffer.from(el[0] + "=" + el[1]));
+ .map((el: Array) => Buffer.from(`${el[0]}=${el[1]}`))
}
public initPort(port: number): void {
- this.port = port;
+ this.port = port
}
public async startAdvertising(): Promise {
if (this.port == null) {
- throw new Error("Tried starting Avahi advertisement without initializing port!");
+ throw new Error('Tried starting Avahi advertisement without initializing port!')
}
if (!this.bus) {
- throw new Error("Tried to start Avahi advertisement on a destroyed advertiser!");
+ throw new Error('Tried to start Avahi advertisement on a destroyed advertiser!')
}
- debug(`Starting to advertise '${this.accessoryInfo.displayName}' using Avahi backend!`);
+ debug(`Starting to advertise '${this.accessoryInfo.displayName}' using Avahi backend!`)
- this.path = await AvahiAdvertiser.avahiInvoke(this.bus, "/", "Server", "EntryGroupNew") as string;
- await AvahiAdvertiser.avahiInvoke(this.bus, this.path, "EntryGroup", "AddService", {
+ this.path = await AvahiAdvertiser.avahiInvoke(this.bus, '/', 'Server', 'EntryGroupNew') as string
+ await AvahiAdvertiser.avahiInvoke(this.bus, this.path, 'EntryGroup', 'AddService', {
body: [
-1, // interface
-1, // protocol
0, // flags
this.accessoryInfo.displayName, // name
- "_hap._tcp", // type
- "", // domain
- "", // host
+ '_hap._tcp', // type
+ '', // domain
+ '', // host
this.port, // port
this.createTxt(), // txt
],
- signature: "iiussssqaay",
- });
- await AvahiAdvertiser.avahiInvoke(this.bus, this.path, "EntryGroup", "Commit");
+ signature: 'iiussssqaay',
+ })
+ await AvahiAdvertiser.avahiInvoke(this.bus, this.path, 'EntryGroup', 'Commit')
try {
if (!this.avahiServerInterface) {
- this.avahiServerInterface = await AvahiAdvertiser.avahiInterface(this.bus, "Server");
- this.avahiServerInterface.on("StateChanged", this.stateChangeHandler);
+ this.avahiServerInterface = await AvahiAdvertiser.avahiInterface(this.bus, 'Server')
+ this.avahiServerInterface.on('StateChanged', this.stateChangeHandler)
}
} catch (error) {
// We have some problem on Synology https://github.com/homebridge/HAP-NodeJS/issues/993
- console.warn("Failed to create listener for avahi-daemon server state. The system will not be notified about restarts of avahi-daemon " +
- "and will therefore stay undiscoverable in those instances. Error message: " + error);
+ console.warn(`Failed to create listener for avahi-daemon server state. The system will not be notified about restarts of avahi-daemon `
+ + `and will therefore stay undiscoverable in those instances. Error message: ${error}`)
if (error.stack) {
- debug("Detailed error: " + error.stack);
+ debug(`Detailed error: ${error.stack}`)
}
}
}
@@ -453,117 +463,114 @@ export class AvahiAdvertiser extends EventEmitter implements Advertiser {
*/
private handleStateChangedEvent(state: AvahiServerState): void {
if (state === AvahiServerState.RUNNING && this.path) {
- debug("Found Avahi daemon to have restarted!");
+ debug('Found Avahi daemon to have restarted!')
this.startAdvertising()
- .catch(reason => console.error("Could not (re-)create mDNS advertisement. The HAP-Server won't be discoverable: " + reason));
+ .catch(reason => console.error(`Could not (re-)create mDNS advertisement. The HAP-Server won't be discoverable: ${reason}`))
}
}
public async updateAdvertisement(silent?: boolean): Promise {
if (!this.bus) {
- throw new Error("Tried to update Avahi advertisement on a destroyed advertiser!");
+ throw new Error('Tried to update Avahi advertisement on a destroyed advertiser!')
}
if (!this.path) {
- debug("Tried to update advertisement without a valid `path`!");
- return;
+ debug('Tried to update advertisement without a valid `path`!')
+ return
}
- debug("Updating txt record (txt: %o, silent: %d)", CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash), silent);
+ debug('Updating txt record (txt: %o, silent: %d)', CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash), silent)
try {
- await AvahiAdvertiser.avahiInvoke(this.bus, this.path, "EntryGroup", "UpdateServiceTxt", {
- body: [-1, -1, 0, this.accessoryInfo.displayName, "_hap._tcp", "", this.createTxt()],
- signature: "iiusssaay",
- });
+ await AvahiAdvertiser.avahiInvoke(this.bus, this.path, 'EntryGroup', 'UpdateServiceTxt', {
+ body: [-1, -1, 0, this.accessoryInfo.displayName, '_hap._tcp', '', this.createTxt()],
+ signature: 'iiusssaay',
+ })
} catch (error) {
- console.error("Failed to update avahi advertisement: " + error);
+ console.error(`Failed to update avahi advertisement: ${error}`)
}
}
public async destroy(): Promise {
if (!this.bus) {
- throw new Error("Tried to destroy Avahi advertisement on a destroyed advertiser!");
+ throw new Error('Tried to destroy Avahi advertisement on a destroyed advertiser!')
}
if (this.path) {
try {
- await AvahiAdvertiser.avahiInvoke(this.bus, this.path, "EntryGroup", "Free");
+ await AvahiAdvertiser.avahiInvoke(this.bus, this.path, 'EntryGroup', 'Free')
} catch (error) {
// Typically, this fails if e.g. avahi service was stopped in the meantime.
- debug("Destroying Avahi advertisement failed: " + error);
+ debug(`Destroying Avahi advertisement failed: ${error}`)
}
- this.path = undefined;
+ this.path = undefined
}
if (this.avahiServerInterface) {
- this.avahiServerInterface.removeListener("StateChanged", this.stateChangeHandler);
- this.avahiServerInterface = undefined;
+ this.avahiServerInterface.removeListener('StateChanged', this.stateChangeHandler)
+ this.avahiServerInterface = undefined
}
- this.bus.connection.stream.destroy();
- this.bus = undefined;
+ this.bus.connection.stream.destroy()
+ this.bus = undefined
}
public static async isAvailable(): Promise {
- const bus = dbus.systemBus();
-
+ debug('Checking for Avahi/DBus availability...')
+ const bus = systemBus()
try {
try {
- await messageBusConnectionResult(bus);
+ await messageBusConnectionResult(bus)
} catch (error) {
- debug("Avahi/DBus classified unavailable due to missing dbus interface!");
- return false;
+ return false
}
try {
- const version = await this.avahiInvoke(bus, "/", "Server", "GetVersionString");
- debug("Detected Avahi over DBus interface running version '%s'.", version);
+ const version = await this.avahiInvoke(bus, '/', 'Server', 'GetVersionString')
+ debug('Detected Avahi over DBus interface running version \'%s\'.', version)
} catch (error) {
- debug("Avahi/DBus classified unavailable due to missing avahi interface!");
- return false;
+ debug('Avahi/DBus classified unavailable due to missing avahi interface!')
+ return false
}
- return true;
+ return true
} finally {
- bus.connection.stream.destroy();
+ bus.connection.stream.destroy()
}
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
private static avahiInvoke(bus: MessageBus, path: string, dbusInterface: string, member: string, others?: any): Promise {
return dbusInvoke(
bus,
- "org.freedesktop.Avahi",
+ 'org.freedesktop.Avahi',
path,
`org.freedesktop.Avahi.${dbusInterface}`,
member,
others,
- );
+ )
}
private static avahiInterface(bus: MessageBus, dbusInterface: string): Promise {
return new Promise((resolve, reject) => {
bus
- .getService("org.freedesktop.Avahi")
- .getInterface("/", "org.freedesktop.Avahi." + dbusInterface, (error, iface) => {
+ .getService('org.freedesktop.Avahi')
+ .getInterface('/', `org.freedesktop.Avahi.${dbusInterface}`, (error, iface) => {
if (error || !iface) {
- reject(error ?? new Error("Interface not present!"));
+ reject(error ?? new Error('Interface not present!'))
} else {
- resolve(iface);
+ resolve(iface)
}
- });
- });
+ })
+ })
}
}
-type ResolvedServiceTxt = Array>;
+type ResolvedServiceTxt = Array>
const RESOLVED_PERMISSIONS_ERRORS = [
- "org.freedesktop.DBus.Error.AccessDenied",
- "org.freedesktop.DBus.Error.AuthFailed",
- "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired",
-];
-
+ 'org.freedesktop.DBus.Error.AccessDenied',
+ 'org.freedesktop.DBus.Error.AuthFailed',
+ 'org.freedesktop.DBus.Error.InteractiveAuthorizationRequired',
+]
/**
* Advertiser based on the systemd-resolved D-Bus library.
@@ -572,176 +579,174 @@ const RESOLVED_PERMISSIONS_ERRORS = [
* @group Advertiser
*/
export class ResolvedAdvertiser extends EventEmitter implements Advertiser {
- private readonly accessoryInfo: AccessoryInfo;
- private readonly setupHash: string;
+ private readonly accessoryInfo: AccessoryInfo
+ private readonly setupHash: string
- private port?: number;
+ private port?: number
- private bus?: MessageBus;
- private path?: string;
+ private bus?: MessageBus
+ private path?: string
constructor(accessoryInfo: AccessoryInfo) {
- super();
- this.accessoryInfo = accessoryInfo;
- this.setupHash = CiaoAdvertiser.computeSetupHash(accessoryInfo);
+ super()
+ this.accessoryInfo = accessoryInfo
+ this.setupHash = CiaoAdvertiser.computeSetupHash(accessoryInfo)
- this.bus = dbus.systemBus();
+ this.bus = systemBus()
- debug(`Preparing Advertiser for '${this.accessoryInfo.displayName}' using systemd-resolved backend!`);
+ debug(`Preparing Advertiser for '${this.accessoryInfo.displayName}' using systemd-resolved backend!`)
}
private createTxt(): ResolvedServiceTxt {
return Object
.entries(CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash))
- .map((el: Array) => [el[0].toString(), Buffer.from(el[1].toString())]);
+ .map((el: Array) => [el[0].toString(), Buffer.from(el[1].toString())])
}
public initPort(port: number): void {
- this.port = port;
+ this.port = port
}
public async startAdvertising(): Promise {
if (this.port == null) {
- throw new Error("Tried starting systemd-resolved advertisement without initializing port!");
+ throw new Error('Tried starting systemd-resolved advertisement without initializing port!')
}
if (!this.bus) {
- throw new Error("Tried to start systemd-resolved advertisement on a destroyed advertiser!");
+ throw new Error('Tried to start systemd-resolved advertisement on a destroyed advertiser!')
}
- debug(`Starting to advertise '${this.accessoryInfo.displayName}' using systemd-resolved backend!`);
+ debug(`Starting to advertise '${this.accessoryInfo.displayName}' using systemd-resolved backend!`)
try {
- this.path = await ResolvedAdvertiser.managerInvoke(this.bus, "RegisterService", {
+ this.path = await ResolvedAdvertiser.managerInvoke(this.bus, 'RegisterService', {
body: [
this.accessoryInfo.displayName, // name
this.accessoryInfo.displayName, // name_template
- "_hap._tcp", // type
+ '_hap._tcp', // type
this.port, // service_port
0, // service_priority
0, // service_weight
[this.createTxt()], // txt_datas
],
- signature: "sssqqqaa{say}",
- });
+ signature: 'sssqqqaa{say}',
+ })
} catch (error) {
if (error instanceof DBusInvokeError) {
if (RESOLVED_PERMISSIONS_ERRORS.includes(error.errorName)) {
- error.message = `Permissions issue. See https://homebridge.io/w/mDNS-Options for more info. ${error.message}`;
+ error.message = `Permissions issue. See https://homebridge.io/w/mDNS-Options for more info. ${error.message}`
}
}
- throw error;
+ throw error
}
}
public async updateAdvertisement(silent?: boolean): Promise {
if (!this.bus) {
- throw new Error("Tried to update systemd-resolved advertisement on a destroyed advertiser!");
+ throw new Error('Tried to update systemd-resolved advertisement on a destroyed advertiser!')
}
- debug("Updating txt record (txt: %o, silent: %d)", CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash), silent);
+ debug('Updating txt record (txt: %o, silent: %d)', CiaoAdvertiser.createTxt(this.accessoryInfo, this.setupHash), silent)
// Currently, systemd-resolved has no way to update an existing record.
- await this.stopAdvertising();
- await this.startAdvertising();
+ await this.stopAdvertising()
+ await this.startAdvertising()
}
private async stopAdvertising(): Promise {
if (!this.bus) {
- throw new Error("Tried to destroy systemd-resolved advertisement on a destroyed advertiser!");
+ throw new Error('Tried to destroy systemd-resolved advertisement on a destroyed advertiser!')
}
if (this.path) {
try {
- await ResolvedAdvertiser.managerInvoke(this.bus, "UnregisterService", {
+ await ResolvedAdvertiser.managerInvoke(this.bus, 'UnregisterService', {
body: [this.path],
- signature: "o",
- });
+ signature: 'o',
+ })
} catch (error) {
// Typically, this fails if e.g. systemd-resolved service was stopped in the meantime.
- debug("Destroying systemd-resolved advertisement failed: " + error);
+ debug(`Destroying systemd-resolved advertisement failed: ${error}`)
}
- this.path = undefined;
+ this.path = undefined
}
}
public async destroy(): Promise {
if (!this.bus) {
- throw new Error("Tried to destroy systemd-resolved advertisement on a destroyed advertiser!");
+ throw new Error('Tried to destroy systemd-resolved advertisement on a destroyed advertiser!')
}
- await this.stopAdvertising();
+ await this.stopAdvertising()
- this.bus.connection.stream.destroy();
- this.bus = undefined;
+ this.bus.connection.stream.destroy()
+ this.bus = undefined
}
public static async isAvailable(): Promise {
- const bus = dbus.systemBus();
+ const bus = systemBus()
try {
try {
- await messageBusConnectionResult(bus);
+ await messageBusConnectionResult(bus)
} catch (error) {
- debug("systemd-resolved/DBus classified unavailable due to missing dbus interface!");
- return false;
+ debug('systemd-resolved/DBus classified unavailable due to missing dbus interface!')
+ return false
}
try {
// Ensure that systemd-resolved is accessible.
- await this.managerInvoke(bus, "ResolveHostname", {
- body: [0, "127.0.0.1", 0, 0],
- signature: "isit",
- });
- debug("Detected systemd-resolved over DBus interface running version.");
+ await this.managerInvoke(bus, 'ResolveHostname', {
+ body: [0, '127.0.0.1', 0, 0],
+ signature: 'isit',
+ })
+ debug('Detected systemd-resolved over DBus interface running version.')
} catch (error) {
- debug("systemd-resolved/DBus classified unavailable due to missing systemd-resolved interface!");
- return false;
+ debug('systemd-resolved/DBus classified unavailable due to missing systemd-resolved interface!')
+ return false
}
try {
const mdnsStatus = await this.resolvedInvoke(
bus,
- "org.freedesktop.DBus.Properties",
- "Get",
+ 'org.freedesktop.DBus.Properties',
+ 'Get',
{
- body: ["org.freedesktop.resolve1.Manager", "MulticastDNS"],
- signature: "ss",
+ body: ['org.freedesktop.resolve1.Manager', 'MulticastDNS'],
+ signature: 'ss',
},
- );
+ )
- if (mdnsStatus[0][0].type !== "s") {
- throw new Error("Invalid type for MulticastDNS");
+ if (mdnsStatus[0][0].type !== 's') {
+ throw new Error('Invalid type for MulticastDNS')
}
- if (mdnsStatus[1][0] !== "yes" ) {
- debug("systemd-resolved/DBus classified unavailable because MulticastDNS is not enabled!");
- return false;
+ if (mdnsStatus[1][0] !== 'yes') {
+ debug('systemd-resolved/DBus classified unavailable because MulticastDNS is not enabled!')
+ return false
}
} catch (error) {
- debug("systemd-resolved/DBus classified unavailable due to failure checking system status: " + error);
- return false;
+ debug(`systemd-resolved/DBus classified unavailable due to failure checking system status: ${error}`)
+ return false
}
- return true;
+ return true
} finally {
- bus.connection.stream.destroy();
+ bus.connection.stream.destroy()
}
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
private static resolvedInvoke(bus: MessageBus, dbusInterface: string, member: string, others?: any): Promise {
return dbusInvoke(
bus,
- "org.freedesktop.resolve1",
- "/org/freedesktop/resolve1",
+ 'org.freedesktop.resolve1',
+ '/org/freedesktop/resolve1',
dbusInterface,
member,
others,
- );
+ )
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
private static managerInvoke(bus: MessageBus, member: string, others?: any): Promise {
- return this.resolvedInvoke(bus, "org.freedesktop.resolve1.Manager", member, others);
+ return this.resolvedInvoke(bus, 'org.freedesktop.resolve1.Manager', member, others)
}
}
diff --git a/src/lib/Bridge.ts b/src/lib/Bridge.ts
index 5e3390006..001ca4f40 100644
--- a/src/lib/Bridge.ts
+++ b/src/lib/Bridge.ts
@@ -1,4 +1,4 @@
-import { Accessory } from "./Accessory";
+import { Accessory } from './Accessory.js'
/**
* Bridge is a special type of HomeKit Accessory that hosts other Accessories "behind" it. This way you
@@ -9,7 +9,7 @@ import { Accessory } from "./Accessory";
*/
export class Bridge extends Accessory {
constructor(displayName: string, UUID: string) {
- super(displayName, UUID);
- this._isBridge = true;
+ super(displayName, UUID)
+ this._isBridge = true
}
}
diff --git a/src/lib/Characteristic.spec.ts b/src/lib/Characteristic.spec.ts
index 5bf0e5994..a39e2cc4c 100644
--- a/src/lib/Characteristic.spec.ts
+++ b/src/lib/Characteristic.spec.ts
@@ -1,811 +1,824 @@
-import { CharacteristicWarningType } from "./Accessory";
+import type { CharacteristicChange, CharacteristicProps, SerializedCharacteristic } from './Characteristic'
+
+import { Buffer } from 'node:buffer'
+
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+
+import { CharacteristicWarningType } from './Accessory.js'
import {
Access,
Characteristic,
- CharacteristicChange,
CharacteristicEventTypes,
- CharacteristicProps,
Formats,
Perms,
- SerializedCharacteristic,
Units,
-} from "./Characteristic";
-import { SelectedRTPStreamConfiguration } from "./definitions";
-import { HAPStatus } from "./HAPServer";
-import { HapStatusError } from "./util/hapStatusError";
-import * as uuid from "./util/uuid";
+} from './Characteristic.js'
+import { SelectedRTPStreamConfiguration } from './definitions/index.js'
+import { HAPStatus } from './HAPServer.js'
+import { HapStatusError } from './util/hapStatusError.js'
+import { generate } from './util/uuid.js'
function createCharacteristic(type: Formats, customUUID?: string): Characteristic {
- return new Characteristic("Test", customUUID || uuid.generate("Foo"), { format: type, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] });
+ return new Characteristic('Test', customUUID || generate('Foo'), { format: type, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] })
}
function createCharacteristicWithProps(props: CharacteristicProps, customUUID?: string): Characteristic {
- return new Characteristic("Test", customUUID || uuid.generate("Foo"), props);
+ return new Characteristic('Test', customUUID || generate('Foo'), props)
}
-describe("Characteristic", () => {
+describe('characteristic', () => {
beforeEach(() => {
- jest.resetAllMocks();
- });
+ vi.resetAllMocks()
+ })
- describe("#setProps()", () => {
- it("should overwrite existing properties", () => {
- const characteristic = createCharacteristic(Formats.BOOL);
+ describe('#setProps()', () => {
+ it('should overwrite existing properties', () => {
+ const characteristic = createCharacteristic(Formats.BOOL)
- const NEW_PROPS = { format: Formats.STRING, perms: [Perms.NOTIFY] };
- characteristic.setProps(NEW_PROPS);
+ const NEW_PROPS = { format: Formats.STRING, perms: [Perms.NOTIFY] }
+ characteristic.setProps(NEW_PROPS)
- expect(characteristic.props).toEqual(NEW_PROPS);
- });
+ expect(characteristic.props).toEqual(NEW_PROPS)
+ })
- it("should fail when setting invalid value range", () => {
- const characteristic = createCharacteristic(Formats.INT);
+ it('should fail when setting invalid value range', () => {
+ const characteristic = createCharacteristic(Formats.INT)
const setProps = (min: number, max: number) => characteristic.setProps({
minValue: min,
maxValue: max,
- });
+ })
- expect(() => setProps(-256, -512)).toThrow(Error);
- expect(() => setProps(0, -3)).toThrow(Error);
- expect(() => setProps(6, 0)).toThrow(Error);
- expect(() => setProps(678, 234)).toThrow(Error);
+ expect(() => setProps(-256, -512)).toThrow(Error)
+ expect(() => setProps(0, -3)).toThrow(Error)
+ expect(() => setProps(6, 0)).toThrow(Error)
+ expect(() => setProps(678, 234)).toThrow(Error)
// should allow setting equal values
- setProps(0, 0);
- setProps(3, 3);
- });
+ setProps(0, 0)
+ setProps(3, 3)
+ })
- it("should reject update to minValue and maxValue when they are out of range for format type", () => {
+ it('should reject update to minValue and maxValue when they are out of range for format type', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.UINT8,
perms: [Perms.NOTIFY, Perms.PAIRED_READ],
minValue: 0,
maxValue: 255,
- });
+ })
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
- mock.mockReset();
+ mock.mockReset()
characteristic.setProps({
minValue: 700,
maxValue: 1000,
- });
+ })
- expect(characteristic.props.minValue).toEqual(0); // min for UINT8
- expect(characteristic.props.maxValue).toEqual(255); // max for UINT8
- expect(mock).toBeCalledTimes(2);
+ expect(characteristic.props.minValue).toEqual(0) // min for UINT8
+ expect(characteristic.props.maxValue).toEqual(255) // max for UINT8
+ expect(mock).toBeCalledTimes(2)
- mock.mockReset();
+ mock.mockReset()
characteristic.setProps({
minValue: -1000,
maxValue: -500,
- });
+ })
- expect(characteristic.props.minValue).toEqual(0); // min for UINT8
- expect(characteristic.props.maxValue).toEqual(255); // max for UINT8
- expect(mock).toBeCalledTimes(2);
+ expect(characteristic.props.minValue).toEqual(0) // min for UINT8
+ expect(characteristic.props.maxValue).toEqual(255) // max for UINT8
+ expect(mock).toBeCalledTimes(2)
- mock.mockReset();
+ mock.mockReset()
characteristic.setProps({
minValue: 10,
maxValue: 1000,
- });
+ })
- expect(characteristic.props.minValue).toEqual(10);
- expect(characteristic.props.maxValue).toEqual(255); // max for UINT8
- expect(mock).toBeCalledTimes(1);
- });
+ expect(characteristic.props.minValue).toEqual(10)
+ expect(characteristic.props.maxValue).toEqual(255) // max for UINT8
+ expect(mock).toBeCalledTimes(1)
+ })
- it("should reject update to minValue and maxValue when minValue is greater than maxValue", () => {
+ it('should reject update to minValue and maxValue when minValue is greater than maxValue', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.FLOAT,
perms: [Perms.NOTIFY, Perms.PAIRED_READ],
- });
+ })
expect(() => {
characteristic.setProps({
minValue: 1000,
maxValue: 500,
- });
- }).toThrowError();
+ })
+ }).toThrowError()
- expect(characteristic.props.minValue).toBeUndefined();
- expect(characteristic.props.maxValue).toBeUndefined();
- });
+ expect(characteristic.props.minValue).toBeUndefined()
+ expect(characteristic.props.maxValue).toBeUndefined()
+ })
- it("should accept update to minValue and maxValue when they are in range for format type", () => {
+ it('should accept update to minValue and maxValue when they are in range for format type', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.INT,
perms: [Perms.NOTIFY, Perms.PAIRED_READ],
minValue: 0,
maxValue: 255,
- });
+ })
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
- mock.mockReset();
+ mock.mockReset()
characteristic.setProps({
minValue: 10,
maxValue: 240,
- });
+ })
- expect(characteristic.props.minValue).toEqual(10);
- expect(characteristic.props.maxValue).toEqual(240);
- expect(mock).toBeCalledTimes(0);
+ expect(characteristic.props.minValue).toEqual(10)
+ expect(characteristic.props.maxValue).toEqual(240)
+ expect(mock).toBeCalledTimes(0)
- mock.mockReset();
+ mock.mockReset()
characteristic.setProps({
minValue: -2147483648,
maxValue: 2147483647,
- });
+ })
- expect(characteristic.props.minValue).toEqual(-2147483648);
- expect(characteristic.props.maxValue).toEqual(2147483647);
- expect(mock).toBeCalledTimes(0);
- });
+ expect(characteristic.props.minValue).toEqual(-2147483648)
+ expect(characteristic.props.maxValue).toEqual(2147483647)
+ expect(mock).toBeCalledTimes(0)
+ })
- it("should reject non-finite numbers for minValue and maxValue for numeric characteristics", () => {
+ it('should reject non-finite numbers for minValue and maxValue for numeric characteristics', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.FLOAT,
perms: [Perms.NOTIFY, Perms.PAIRED_READ],
- });
+ })
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
- mock.mockReset();
+ mock.mockReset()
characteristic.setProps({
minValue: Number.NEGATIVE_INFINITY,
- });
+ })
- expect(characteristic.props.minValue).toEqual(undefined);
- expect(mock).toBeCalledTimes(1);
- expect(mock).toBeCalledWith(expect.stringContaining("Property 'minValue' must be a finite number"), expect.anything());
+ expect(characteristic.props.minValue).toEqual(undefined)
+ expect(mock).toBeCalledTimes(1)
+ expect(mock).toBeCalledWith(expect.stringContaining('Property \'minValue\' must be a finite number'), expect.anything())
- mock.mockReset();
+ mock.mockReset()
characteristic.setProps({
maxValue: Number.POSITIVE_INFINITY,
- });
+ })
- expect(characteristic.props.maxValue).toEqual(undefined);
- expect(mock).toBeCalledTimes(1);
- expect(mock).toBeCalledWith(expect.stringContaining("Property 'maxValue' must be a finite number"), expect.anything());
- });
+ expect(characteristic.props.maxValue).toEqual(undefined)
+ expect(mock).toBeCalledTimes(1)
+ expect(mock).toBeCalledWith(expect.stringContaining('Property \'maxValue\' must be a finite number'), expect.anything())
+ })
- it("should reject NaN numbers for minValue and maxValue for numeric characteristics", () => {
+ it('should reject NaN numbers for minValue and maxValue for numeric characteristics', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.FLOAT,
perms: [Perms.NOTIFY, Perms.PAIRED_READ],
- });
+ })
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
- mock.mockReset();
+ mock.mockReset()
characteristic.setProps({
- minValue: NaN,
- });
+ minValue: Number.NaN,
+ })
- expect(characteristic.props.minValue).toEqual(undefined);
- expect(mock).toBeCalledTimes(1);
- expect(mock).toBeCalledWith(expect.stringContaining("Property 'minValue' must be a finite number"), expect.anything());
+ expect(characteristic.props.minValue).toEqual(undefined)
+ expect(mock).toBeCalledTimes(1)
+ expect(mock).toBeCalledWith(expect.stringContaining('Property \'minValue\' must be a finite number'), expect.anything())
- mock.mockReset();
+ mock.mockReset()
characteristic.setProps({
- maxValue: NaN,
- });
+ maxValue: Number.NaN,
+ })
- expect(characteristic.props.maxValue).toEqual(undefined);
- expect(mock).toBeCalledTimes(1);
- expect(mock).toBeCalledWith(expect.stringContaining("Property 'maxValue' must be a finite number"), expect.anything());
- });
+ expect(characteristic.props.maxValue).toEqual(undefined)
+ expect(mock).toBeCalledTimes(1)
+ expect(mock).toBeCalledWith(expect.stringContaining('Property \'maxValue\' must be a finite number'), expect.anything())
+ })
- test.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
- "numeric values of format %p should be corrected on setProps call with min/max restrictions", format => {
+ it.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
+ 'numeric values of format %p should be corrected on setProps call with min/max restrictions',
+ (format) => {
const characteristic = createCharacteristicWithProps({
- format: format,
+ format,
perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
- const changedMock = jest.fn();
- characteristic.on(CharacteristicEventTypes.CHANGE, changedMock);
+ const changedMock = vi.fn()
+ characteristic.on(CharacteristicEventTypes.CHANGE, changedMock)
// @ts-expect-error: spying on private property
- const warningMock = jest.spyOn(characteristic, "characteristicWarning");
+ const warningMock = vi.spyOn(characteristic, 'characteristicWarning')
const expectNoChange = () => {
- expect(changedMock).toBeCalledTimes(0);
- expect(warningMock).toBeCalledTimes(0);
- };
+ expect(changedMock).toBeCalledTimes(0)
+ expect(warningMock).toBeCalledTimes(0)
+ }
const expectChange = () => {
- expect(changedMock).toBeCalledTimes(1);
- expect(warningMock).toBeCalledTimes(1);
- expect(warningMock).toBeCalledWith(expect.anything(), CharacteristicWarningType.DEBUG_MESSAGE);
- };
+ expect(changedMock).toBeCalledTimes(1)
+ expect(warningMock).toBeCalledTimes(1)
+ expect(warningMock).toBeCalledWith(expect.anything(), CharacteristicWarningType.DEBUG_MESSAGE)
+ }
const reset = () => {
- changedMock.mockReset();
- warningMock.mockReset();
- };
+ changedMock.mockReset()
+ warningMock.mockReset()
+ }
- reset();
+ reset()
characteristic.setProps({
minValue: 0,
maxValue: 100,
- });
+ })
// a value of null must not be corrected!
- expectNoChange();
+ expectNoChange()
- characteristic.setValue(0);
+ characteristic.setValue(0)
- reset();
+ reset()
characteristic.setProps({
minValue: 10,
maxValue: 100,
- });
+ })
// value should be corrected to 10. If changing the min/max value bounds, users should change the value first!
- expectChange();
- expect(characteristic.value).toEqual(10);
+ expectChange()
+ expect(characteristic.value).toEqual(10)
- reset();
+ reset()
characteristic.setProps({
minValue: null,
maxValue: null,
- });
+ })
// unsetting min/max value restriction must not make a difference
- expectNoChange();
+ expectNoChange()
- reset();
+ reset()
characteristic.setProps({
validValueRanges: [20, 100],
- });
+ })
// value should be corrected to 20
- expectChange();
- expect(characteristic.value).toEqual(20);
+ expectChange()
+ expect(characteristic.value).toEqual(20)
- reset();
+ reset()
characteristic.setProps({
validValueRanges: null,
- });
+ })
// unsetting min/max value restriction must not make a difference
- expectNoChange();
+ expectNoChange()
- reset();
+ reset()
characteristic.setProps({
validValues: [1, 2, 3],
- });
+ })
// value should be corrected to the first valid value
- expectChange();
- expect(characteristic.value).toBe(1);
- });
+ expectChange()
+ expect(characteristic.value).toBe(1)
+ },
+ )
- test("string values should be corrected on setProps call with length restrictions", () => {
+ it('string values should be corrected on setProps call with length restrictions', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.STRING,
perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
- const changedMock = jest.fn();
- characteristic.on(CharacteristicEventTypes.CHANGE, changedMock);
+ const changedMock = vi.fn()
+ characteristic.on(CharacteristicEventTypes.CHANGE, changedMock)
// @ts-expect-error: spying on private property
- const warningMock = jest.spyOn(characteristic, "characteristicWarning");
+ const warningMock = vi.spyOn(characteristic, 'characteristicWarning')
const expectNoChange = () => {
- expect(changedMock).toBeCalledTimes(0);
- expect(warningMock).toBeCalledTimes(0);
- };
+ expect(changedMock).toBeCalledTimes(0)
+ expect(warningMock).toBeCalledTimes(0)
+ }
const expectChange = () => {
- expect(changedMock).toBeCalledTimes(1);
- expect(warningMock).toBeCalledTimes(1);
- expect(warningMock).toBeCalledWith(expect.anything(), CharacteristicWarningType.DEBUG_MESSAGE);
- };
+ expect(changedMock).toBeCalledTimes(1)
+ expect(warningMock).toBeCalledTimes(1)
+ expect(warningMock).toBeCalledWith(expect.anything(), CharacteristicWarningType.DEBUG_MESSAGE)
+ }
const reset = () => {
- changedMock.mockReset();
- warningMock.mockReset();
- };
+ changedMock.mockReset()
+ warningMock.mockReset()
+ }
- reset();
+ reset()
characteristic.setProps({
maxLen: 256,
- });
+ })
- expectNoChange();
- expect(characteristic.value).toBeNull();
+ expectNoChange()
+ expect(characteristic.value).toBeNull()
- characteristic.setValue("Hello World");
+ characteristic.setValue('Hello World')
- reset();
+ reset()
characteristic.setProps({
maxLen: 5,
- });
+ })
- expectChange();
- expect(characteristic.value).toBe("Hello"); // TODO string length cutting should happen on read only?
+ expectChange()
+ expect(characteristic.value).toBe('Hello') // TODO string length cutting should happen on read only?
- reset();
+ reset()
characteristic.setProps({
maxLen: null,
- });
+ })
- expectNoChange();
- });
+ expectNoChange()
+ })
- test("setProps call should not remove error state", () => {
+ it('setProps call should not remove error state', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.STRING,
perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
- const changedMock = jest.fn();
- characteristic.on(CharacteristicEventTypes.CHANGE, changedMock);
+ const changedMock = vi.fn()
+ characteristic.on(CharacteristicEventTypes.CHANGE, changedMock)
// @ts-expect-error: spying on private property
- const warningMock = jest.spyOn(characteristic, "characteristicWarning");
+ const warningMock = vi.spyOn(characteristic, 'characteristicWarning')
- characteristic.setValue("Hello World");
- characteristic.updateValue(new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE));
+ characteristic.setValue('Hello World')
+ characteristic.updateValue(new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE))
- changedMock.mockReset();
- warningMock.mockReset();
+ changedMock.mockReset()
+ warningMock.mockReset()
characteristic.setProps({
maxLen: 5,
- });
+ })
- expect(changedMock).toBeCalledTimes(0);
- expect(warningMock).toBeCalledTimes(0);
- expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- });
+ expect(changedMock).toBeCalledTimes(0)
+ expect(warningMock).toBeCalledTimes(0)
+ expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ })
- test("setProps should not try to correct value if impossible", () => {
+ it('setProps should not try to correct value if impossible', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.STRING,
perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
- const changedMock = jest.fn();
- characteristic.on(CharacteristicEventTypes.CHANGE, changedMock);
+ const changedMock = vi.fn()
+ characteristic.on(CharacteristicEventTypes.CHANGE, changedMock)
// @ts-expect-error: spying on private property
- const warningMock = jest.spyOn(characteristic, "characteristicWarning");
+ const warningMock = vi.spyOn(characteristic, 'characteristicWarning')
- characteristic.setValue("Hello World");
+ characteristic.setValue('Hello World')
- changedMock.mockReset();
- warningMock.mockReset();
+ changedMock.mockReset()
+ warningMock.mockReset()
characteristic.setProps({
format: Formats.INT,
- });
+ })
- expect(changedMock).toBeCalledTimes(0);
- expect(warningMock).toBeCalledTimes(0);
- expect(characteristic.value).toEqual("Hello World");
+ expect(changedMock).toBeCalledTimes(0)
+ expect(warningMock).toBeCalledTimes(0)
+ expect(characteristic.value).toEqual('Hello World')
// when changing the format, users must change the value after changing setProps!
- });
+ })
- test("setProps must not emit a change event for event-type characteristics: ProgrammableSwitchEvent", () => {
- const switchEvent = new Characteristic.ProgrammableSwitchEvent();
+ it('setProps must not emit a change event for event-type characteristics: ProgrammableSwitchEvent', () => {
+ const switchEvent = new Characteristic.ProgrammableSwitchEvent()
- const changedMock = jest.fn();
- switchEvent.on(CharacteristicEventTypes.CHANGE, changedMock);
+ const changedMock = vi.fn()
+ switchEvent.on(CharacteristicEventTypes.CHANGE, changedMock)
// @ts-expect-error: spying on private property
- const warningMock = jest.spyOn(switchEvent, "characteristicWarning");
+ const warningMock = vi.spyOn(switchEvent, 'characteristicWarning')
- switchEvent.updateValue(Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS);
- changedMock.mockReset();
- warningMock.mockReset();
+ switchEvent.updateValue(Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS)
+ changedMock.mockReset()
+ warningMock.mockReset()
switchEvent.setProps({
validValues: [0, 2],
- });
+ })
- expect(changedMock).toBeCalledTimes(0);
- expect(warningMock).toBeCalledTimes(0);
- expect(switchEvent.value).toEqual(1);
- });
+ expect(changedMock).toBeCalledTimes(0)
+ expect(warningMock).toBeCalledTimes(0)
+ expect(switchEvent.value).toEqual(1)
+ })
- test("setProps must not emit a change event for event-type characteristics: ButtonEvent", () => {
- const buttonEvent = new Characteristic.ButtonEvent();
+ it('setProps must not emit a change event for event-type characteristics: ButtonEvent', () => {
+ const buttonEvent = new Characteristic.ButtonEvent()
- const changedMock = jest.fn();
- buttonEvent.on(CharacteristicEventTypes.CHANGE, changedMock);
+ const changedMock = vi.fn()
+ buttonEvent.on(CharacteristicEventTypes.CHANGE, changedMock)
// @ts-expect-error: spying on private property
- const warningMock = jest.spyOn(buttonEvent, "characteristicWarning");
+ const warningMock = vi.spyOn(buttonEvent, 'characteristicWarning')
- buttonEvent.updateValue("0000"); // some empty tlv
- changedMock.mockReset();
- warningMock.mockReset();
+ buttonEvent.updateValue('0000') // some empty tlv
+ changedMock.mockReset()
+ warningMock.mockReset()
// we actually don't modify anything
buttonEvent.setProps({
- });
+ })
- expect(changedMock).toBeCalledTimes(0);
- expect(warningMock).toBeCalledTimes(0);
- expect(buttonEvent.value).toEqual("0000");
- });
- });
+ expect(changedMock).toBeCalledTimes(0)
+ expect(warningMock).toBeCalledTimes(0)
+ expect(buttonEvent.value).toEqual('0000')
+ })
+ })
- describe("validValuesIterator", () => {
- it ("should iterate over min/max value definition", () => {
+ describe('validValuesIterator', () => {
+ it ('should iterate over min/max value definition', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.INT,
perms: [Perms.PAIRED_READ],
minValue: 2,
maxValue: 5,
- });
+ })
- const result = Array.from(characteristic.validValuesIterator());
- expect(result).toEqual([2, 3, 4, 5]);
- });
+ const result = Array.from(characteristic.validValuesIterator())
+ expect(result).toEqual([2, 3, 4, 5])
+ })
- it ("should iterate over min/max value definition with minStep defined", () => {
+ it ('should iterate over min/max value definition with minStep defined', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.INT,
perms: [Perms.PAIRED_READ],
minValue: 2,
maxValue: 10,
minStep: 2, // can't really test with .x precision as of floating point precision
- });
+ })
- const result = Array.from(characteristic.validValuesIterator());
- expect(result).toEqual([2, 4, 6, 8, 10]);
- });
+ const result = Array.from(characteristic.validValuesIterator())
+ expect(result).toEqual([2, 4, 6, 8, 10])
+ })
- it ("should iterate over validValues array definition", () => {
- const validValues = [1, 3, 4, 5, 8];
+ it ('should iterate over validValues array definition', () => {
+ const validValues = [1, 3, 4, 5, 8]
const characteristic = createCharacteristicWithProps({
format: Formats.INT,
perms: [Perms.PAIRED_READ],
- validValues: validValues,
- });
+ validValues,
+ })
- const result = Array.from(characteristic.validValuesIterator());
- expect(result).toEqual(validValues);
- });
+ const result = Array.from(characteristic.validValuesIterator())
+ expect(result).toEqual(validValues)
+ })
- it ("should iterate over validValueRanges definition", () => {
+ it ('should iterate over validValueRanges definition', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.INT,
perms: [Perms.PAIRED_READ],
validValueRanges: [2, 5],
- });
+ })
- const result = Array.from(characteristic.validValuesIterator());
- expect(result).toEqual([2, 3, 4, 5]);
- });
+ const result = Array.from(characteristic.validValuesIterator())
+ expect(result).toEqual([2, 3, 4, 5])
+ })
- it("should iterate over UINT8 definition", () => {
- const characteristic = createCharacteristic(Formats.UINT8);
+ it('should iterate over UINT8 definition', () => {
+ const characteristic = createCharacteristic(Formats.UINT8)
- const result = Array.from(characteristic.validValuesIterator());
- expect(result).toEqual(Array.from(new Uint8Array(256).map((value, i) => i)));
- });
+ const result = Array.from(characteristic.validValuesIterator())
+ expect(result).toEqual(Array.from(new Uint8Array(256).map((value, i) => i)))
+ })
- // we could do the same for UINT16, UINT32 and UINT64 but i think thats kind of pointless and takes to long
- });
+ // We could do the same for UINT16, UINT32 and UINT64, but I think that's kind of pointless and takes to long
+ })
- describe("#subscribe()", () => {
- it("correctly adds a single subscription", () => {
- const characteristic = createCharacteristic(Formats.BOOL);
- const subscribeSpy = jest.fn();
- characteristic.on(CharacteristicEventTypes.SUBSCRIBE, subscribeSpy);
- characteristic.subscribe();
+ describe('#subscribe()', () => {
+ it('correctly adds a single subscription', () => {
+ const characteristic = createCharacteristic(Formats.BOOL)
+ const subscribeSpy = vi.fn()
+ characteristic.on(CharacteristicEventTypes.SUBSCRIBE, subscribeSpy)
+ characteristic.subscribe()
- expect(subscribeSpy).toHaveBeenCalledTimes(1);
+ expect(subscribeSpy).toHaveBeenCalledTimes(1)
// @ts-expect-error: private access
- expect(characteristic.subscriptions).toEqual(1);
- });
-
- it("correctly adds multiple subscriptions", () => {
- const characteristic = createCharacteristic(Formats.BOOL);
- const subscribeSpy = jest.fn();
- characteristic.on(CharacteristicEventTypes.SUBSCRIBE, subscribeSpy);
- characteristic.subscribe();
- characteristic.subscribe();
- characteristic.subscribe();
-
- expect(subscribeSpy).toHaveBeenCalledTimes(1);
+ expect(characteristic.subscriptions).toEqual(1)
+ })
+
+ it('correctly adds multiple subscriptions', () => {
+ const characteristic = createCharacteristic(Formats.BOOL)
+ const subscribeSpy = vi.fn()
+ characteristic.on(CharacteristicEventTypes.SUBSCRIBE, subscribeSpy)
+ characteristic.subscribe()
+ characteristic.subscribe()
+ characteristic.subscribe()
+
+ expect(subscribeSpy).toHaveBeenCalledTimes(1)
// @ts-expect-error: private access
- expect(characteristic.subscriptions).toEqual(3);
- });
- });
-
- describe("#unsubscribe()", () => {
- it("correctly removes a single subscription", () => {
- const characteristic = createCharacteristic(Formats.BOOL);
- const subscribeSpy = jest.fn();
- const unsubscribeSpy = jest.fn();
- characteristic.on(CharacteristicEventTypes.SUBSCRIBE, subscribeSpy);
- characteristic.on(CharacteristicEventTypes.UNSUBSCRIBE, unsubscribeSpy);
- characteristic.subscribe();
- characteristic.unsubscribe();
-
- expect(subscribeSpy).toHaveBeenCalledTimes(1);
- expect(unsubscribeSpy).toHaveBeenCalledTimes(1);
+ expect(characteristic.subscriptions).toEqual(3)
+ })
+ })
+
+ describe('#unsubscribe()', () => {
+ it('correctly removes a single subscription', () => {
+ const characteristic = createCharacteristic(Formats.BOOL)
+ const subscribeSpy = vi.fn()
+ const unsubscribeSpy = vi.fn()
+ characteristic.on(CharacteristicEventTypes.SUBSCRIBE, subscribeSpy)
+ characteristic.on(CharacteristicEventTypes.UNSUBSCRIBE, unsubscribeSpy)
+ characteristic.subscribe()
+ characteristic.unsubscribe()
+
+ expect(subscribeSpy).toHaveBeenCalledTimes(1)
+ expect(unsubscribeSpy).toHaveBeenCalledTimes(1)
// @ts-expect-error: private access
- expect(characteristic.subscriptions).toEqual(0);
- });
-
- it("correctly removes multiple subscriptions", () => {
- const characteristic = createCharacteristic(Formats.BOOL);
- const subscribeSpy = jest.fn();
- const unsubscribeSpy = jest.fn();
- characteristic.on(CharacteristicEventTypes.SUBSCRIBE, subscribeSpy);
- characteristic.on(CharacteristicEventTypes.UNSUBSCRIBE, unsubscribeSpy);
- characteristic.subscribe();
- characteristic.subscribe();
- characteristic.subscribe();
- characteristic.unsubscribe();
- characteristic.unsubscribe();
- characteristic.unsubscribe();
-
- expect(subscribeSpy).toHaveBeenCalledTimes(1);
- expect(unsubscribeSpy).toHaveBeenCalledTimes(1);
+ expect(characteristic.subscriptions).toEqual(0)
+ })
+
+ it('correctly removes multiple subscriptions', () => {
+ const characteristic = createCharacteristic(Formats.BOOL)
+ const subscribeSpy = vi.fn()
+ const unsubscribeSpy = vi.fn()
+ characteristic.on(CharacteristicEventTypes.SUBSCRIBE, subscribeSpy)
+ characteristic.on(CharacteristicEventTypes.UNSUBSCRIBE, unsubscribeSpy)
+ characteristic.subscribe()
+ characteristic.subscribe()
+ characteristic.subscribe()
+ characteristic.unsubscribe()
+ characteristic.unsubscribe()
+ characteristic.unsubscribe()
+
+ expect(subscribeSpy).toHaveBeenCalledTimes(1)
+ expect(unsubscribeSpy).toHaveBeenCalledTimes(1)
// @ts-expect-error: private access
- expect(characteristic.subscriptions).toEqual(0);
- });
- });
-
- describe("#handleGetRequest()", () => {
- it("should handle special event only characteristics", (callback) => {
- const characteristic = createCharacteristic(Formats.BOOL, Characteristic.ProgrammableSwitchEvent.UUID);
-
- characteristic.handleGetRequest().then(() => {
- expect(characteristic.statusCode).toEqual(HAPStatus.SUCCESS);
- expect(characteristic.value).toEqual(null);
- callback();
- });
- });
-
- it("should return cached values if no listeners are registered", (callback) => {
- const characteristic = createCharacteristic(Formats.BOOL);
-
- characteristic.handleGetRequest().then(() => {
- expect(characteristic.statusCode).toEqual(HAPStatus.SUCCESS);
- expect(characteristic.value).toEqual(null);
- callback();
- });
- });
- });
-
- describe("#validateClientSuppliedValue()", () => {
- it("rejects undefined values from client", async () => {
+ expect(characteristic.subscriptions).toEqual(0)
+ })
+ })
+
+ describe('#handleGetRequest()', () => {
+ it('should handle special event only characteristics', async () => {
+ const characteristic = createCharacteristic(Formats.BOOL, Characteristic.ProgrammableSwitchEvent.UUID)
+
+ await characteristic.handleGetRequest()
+ expect(characteristic.statusCode).toEqual(HAPStatus.SUCCESS)
+ expect(characteristic.value).toEqual(null)
+ })
+
+ it('should return cached values if no listeners are registered', async () => {
+ const characteristic = createCharacteristic(Formats.BOOL)
+
+ await characteristic.handleGetRequest()
+ expect(characteristic.statusCode).toEqual(HAPStatus.SUCCESS)
+ expect(characteristic.value).toEqual(null)
+ })
+ })
+
+ describe('#validateClientSuppliedValue()', () => {
+ it('rejects undefined values from client', async () => {
const characteristic = createCharacteristicWithProps({
format: Formats.UINT8,
maxValue: 1,
minValue: 0,
minStep: 1,
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
// set initial known good value
- characteristic.setValue(1);
+ characteristic.setValue(1)
// this should throw an error
await expect(characteristic.handleSetRequest(undefined as unknown as boolean, null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// the existing valid value should remain
- expect(characteristic.value).toEqual(1);
+ expect(characteristic.value).toEqual(1)
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalled();
- });
+ expect(validateClientSuppliedValueMock).toBeCalled()
+ })
- it("rejects invalid values for the boolean format type", async () => {
+ it('rejects invalid values for the boolean format type', async () => {
const characteristic = createCharacteristicWithProps({
format: Formats.BOOL,
maxValue: 1,
minValue: 0,
minStep: 1,
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
// set initial known good value
- characteristic.setValue(true);
+ characteristic.setValue(true)
// numbers other than 1 or 0 should throw an error
await expect(characteristic.handleSetRequest(20, null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// strings should throw an error
- await expect(characteristic.handleSetRequest("true", null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ await expect(characteristic.handleSetRequest('true', null as unknown as undefined))
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// the existing valid value should remain
- expect(characteristic.value).toEqual(true);
+ expect(characteristic.value).toEqual(true)
// 0 should set the value to false
await expect(characteristic.handleSetRequest(0, null as unknown as undefined))
- .resolves.toEqual(undefined);
- expect(characteristic.value).toEqual(false);
+ .resolves.toEqual(undefined)
+ expect(characteristic.value).toEqual(false)
// 1 should set the value to true
await expect(characteristic.handleSetRequest(1, null as unknown as undefined))
- .resolves.toEqual(undefined);
- expect(characteristic.value).toEqual(true);
+ .resolves.toEqual(undefined)
+ expect(characteristic.value).toEqual(true)
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalledTimes(4);
- });
+ expect(validateClientSuppliedValueMock).toBeCalledTimes(4)
+ })
- test.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
- "boolean types sent for %p types should be transformed from false to 0", async (intType) => {
+ it.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
+ 'boolean types sent for %p types should be transformed from false to 0',
+ async (intType) => {
const characteristic = createCharacteristicWithProps({
format: intType,
maxValue: 1,
minValue: 0,
minStep: 1,
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
- await characteristic.handleSetRequest(false, null as unknown as undefined);
- expect(characteristic.value).toEqual(0);
+ await characteristic.handleSetRequest(false, null as unknown as undefined)
+ expect(characteristic.value).toEqual(0)
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalled();
- });
-
+ expect(validateClientSuppliedValueMock).toBeCalled()
+ },
+ )
- test.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
- "boolean types sent for %p types should be transformed from true to 1", async (intType) => {
+ it.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
+ 'boolean types sent for %p types should be transformed from true to 1',
+ async (intType) => {
const characteristic = createCharacteristicWithProps({
format: intType,
maxValue: 1,
minValue: 0,
minStep: 1,
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
- await characteristic.handleSetRequest(true, null as unknown as undefined);
- expect(characteristic.value).toEqual(1);
+ await characteristic.handleSetRequest(true, null as unknown as undefined)
+ expect(characteristic.value).toEqual(1)
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalled();
- });
+ expect(validateClientSuppliedValueMock).toBeCalled()
+ },
+ )
- test.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
- "rejects string values sent for %p types sent from client", async (intType) => {
+ it.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
+ 'rejects string values sent for %p types sent from client',
+ async (intType) => {
const characteristic = createCharacteristicWithProps({
format: intType,
maxValue: 1,
minValue: 0,
minStep: 1,
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
// set initial known good value
- characteristic.setValue(1);
+ characteristic.setValue(1)
// this should throw an error
- await expect(characteristic.handleSetRequest("what is this!", null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ await expect(characteristic.handleSetRequest('what is this!', null as unknown as undefined))
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// the existing valid value should remain
- expect(characteristic.value).toEqual(1);
+ expect(characteristic.value).toEqual(1)
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalled();
- });
+ expect(validateClientSuppliedValueMock).toBeCalled()
+ },
+ )
- test.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
- "ensure maxValue is not exceeded for %p types sent from client", async (intType) => {
+ it.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
+ 'ensure maxValue is not exceeded for %p types sent from client',
+ async (intType) => {
const characteristic = createCharacteristicWithProps({
format: intType,
maxValue: 1,
minValue: 0,
minStep: 1,
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
// set initial known good value
- characteristic.setValue(1);
+ characteristic.setValue(1)
// this should throw an error
await expect(characteristic.handleSetRequest(100, null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// this should throw an error
await expect(characteristic.handleSetRequest(-100, null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// value should revert to
- expect(characteristic.value).toEqual(1);
+ expect(characteristic.value).toEqual(1)
// this should pass
await expect(characteristic.handleSetRequest(0, null as unknown as undefined))
- .resolves.toEqual(undefined);
+ .resolves.toEqual(undefined)
// value should now be 3
- expect(characteristic.value).toEqual(0);
+ expect(characteristic.value).toEqual(0)
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalledTimes(3);
- });
+ expect(validateClientSuppliedValueMock).toBeCalledTimes(3)
+ },
+ )
- test.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
- "ensure NaN is rejected for %p types sent from client", async (intType) => {
+ it.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
+ 'ensure NaN is rejected for %p types sent from client',
+ async (intType) => {
const characteristic = createCharacteristicWithProps({
format: intType,
maxValue: 1,
minValue: 0,
minStep: 1,
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
// set initial known good value
- characteristic.setValue(1);
+ characteristic.setValue(1)
// this should throw an error
- await expect(characteristic.handleSetRequest(NaN, null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ await expect(characteristic.handleSetRequest(Number.NaN, null as unknown as undefined))
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// value should revert to
- expect(characteristic.value).toEqual(1);
+ expect(characteristic.value).toEqual(1)
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalledTimes(1);
- });
+ expect(validateClientSuppliedValueMock).toBeCalledTimes(1)
+ },
+ )
- test.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
- "ensure non-finite values are rejected for %p types sent from client", async (intType) => {
+ it.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
+ 'ensure non-finite values are rejected for %p types sent from client',
+ async (intType) => {
const characteristic = createCharacteristicWithProps({
format: intType,
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
// set initial known good value
- characteristic.setValue(1);
+ characteristic.setValue(1)
// this should throw an error
await expect(characteristic.handleSetRequest(Infinity, null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// value should revert to
- expect(characteristic.value).toEqual(1);
+ expect(characteristic.value).toEqual(1)
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalledTimes(1);
- });
+ expect(validateClientSuppliedValueMock).toBeCalledTimes(1)
+ },
+ )
- test.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
- "ensure value is rejected if outside valid values for %p types sent from client", async (intType) => {
+ it.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
+ 'ensure value is rejected if outside valid values for %p types sent from client',
+ async (intType) => {
const characteristic = createCharacteristicWithProps({
format: intType,
maxValue: 10,
@@ -813,34 +826,36 @@ describe("Characteristic", () => {
minStep: 1,
validValues: [1, 3, 5, 10],
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
// set initial known good value
- characteristic.setValue(1);
+ characteristic.setValue(1)
// this should throw an error
await expect(characteristic.handleSetRequest(6, null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// value should revert to
- expect(characteristic.value).toEqual(1);
+ expect(characteristic.value).toEqual(1)
// this should pass
await expect(characteristic.handleSetRequest(3, null as unknown as undefined))
- .resolves.toEqual(undefined);
+ .resolves.toEqual(undefined)
// value should now be 3
- expect(characteristic.value).toEqual(3);
+ expect(characteristic.value).toEqual(3)
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalledTimes(2);
- });
+ expect(validateClientSuppliedValueMock).toBeCalledTimes(2)
+ },
+ )
- test.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
- "ensure value is rejected if outside valid value ranges for %p types sent from client", async (intType) => {
+ it.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
+ 'ensure value is rejected if outside valid value ranges for %p types sent from client',
+ async (intType) => {
const characteristic = createCharacteristicWithProps({
format: intType,
maxValue: 1000,
@@ -848,1207 +863,1198 @@ describe("Characteristic", () => {
minStep: 1,
validValueRanges: [50, 55],
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
// set initial known good value
- characteristic.setValue(50);
+ characteristic.setValue(50)
// this should throw an error
await expect(characteristic.handleSetRequest(100, null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// this should throw an error
await expect(characteristic.handleSetRequest(20, null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// value should still be 50
- expect(characteristic.value).toEqual(50);
+ expect(characteristic.value).toEqual(50)
// this should pass
await expect(characteristic.handleSetRequest(52, null as unknown as undefined))
- .resolves.toEqual(undefined);
+ .resolves.toEqual(undefined)
// value should now be 52
- expect(characteristic.value).toEqual(52);
+ expect(characteristic.value).toEqual(52)
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalledTimes(3);
- });
+ expect(validateClientSuppliedValueMock).toBeCalledTimes(3)
+ },
+ )
- test.each([Formats.STRING, Formats.TLV8, Formats.DATA])(
- "rejects non-string values for the %p format type from the client", async (stringType) => {
+ it.each([Formats.STRING, Formats.TLV8, Formats.DATA])(
+ 'rejects non-string values for the %p format type from the client',
+ async (stringType) => {
const characteristic = createCharacteristicWithProps({
format: stringType,
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
// set initial known good value
- characteristic.setValue("some string");
+ characteristic.setValue('some string')
// numbers should throw an error
await expect(characteristic.handleSetRequest(1234, null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// booleans should throw an error
await expect(characteristic.handleSetRequest(false, null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// the existing valid value should remain
- expect(characteristic.value).toEqual("some string");
+ expect(characteristic.value).toEqual('some string')
// strings should pass
- await expect(characteristic.handleSetRequest("some other test string", null as unknown as undefined))
- .resolves.toEqual(undefined);
+ await expect(characteristic.handleSetRequest('some other test string', null as unknown as undefined))
+ .resolves.toEqual(undefined)
// value should now be updated
- expect(characteristic.value).toEqual("some other test string");
+ expect(characteristic.value).toEqual('some other test string')
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalledTimes(3);
- });
+ expect(validateClientSuppliedValueMock).toBeCalledTimes(3)
+ },
+ )
- it("should accept Formats.FLOAT with precision provided by client", async () => {
+ it('should accept Formats.FLOAT with precision provided by client', async () => {
const characteristic = createCharacteristicWithProps({
format: Formats.FLOAT,
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
// set initial known good value
- characteristic.setValue(0.0005);
+ characteristic.setValue(0.0005)
// the existing valid value should remain
- expect(characteristic.value).toEqual(0.0005);
+ expect(characteristic.value).toEqual(0.0005)
// should allow float
await expect(characteristic.handleSetRequest(0.0001005, null as unknown as undefined))
- .resolves.toEqual(undefined);
+ .resolves.toEqual(undefined)
// value should now be updated
- expect(characteristic.value).toEqual(0.0001005);
+ expect(characteristic.value).toEqual(0.0001005)
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalledTimes(1);
- });
+ expect(validateClientSuppliedValueMock).toBeCalledTimes(1)
+ })
- it("should accept negative floats in range for Formats.FLOAT provided by the client", async () => {
+ it('should accept negative floats in range for Formats.FLOAT provided by the client', async () => {
const characteristic = createCharacteristicWithProps({
format: Formats.FLOAT,
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
minValue: -1000,
maxValue: 1000,
- });
+ })
// @ts-expect-error - spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
// should allow negative float
await expect(characteristic.handleSetRequest(-0.013, null as unknown as undefined))
- .resolves.toEqual(undefined);
+ .resolves.toEqual(undefined)
// value should now be updated
- expect(characteristic.value).toEqual(-0.013);
+ expect(characteristic.value).toEqual(-0.013)
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalledTimes(1);
- });
+ expect(validateClientSuppliedValueMock).toBeCalledTimes(1)
+ })
- it("rejects string values exceeding the max length from the client", async () => {
+ it('rejects string values exceeding the max length from the client', async () => {
const characteristic = createCharacteristicWithProps({
format: Formats.STRING,
maxLen: 5,
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
// set initial known good value
- characteristic.setValue("abcde");
+ characteristic.setValue('abcde')
- // should reject strings that are to long
- await expect(characteristic.handleSetRequest("this is to long", null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ // should reject strings that are too long
+ await expect(characteristic.handleSetRequest('this is to long', null as unknown as undefined))
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// the existing valid value should remain
- expect(characteristic.value).toEqual("abcde");
+ expect(characteristic.value).toEqual('abcde')
// strings should pass
- await expect(characteristic.handleSetRequest("abc", null as unknown as undefined))
- .resolves.toEqual(undefined);
+ await expect(characteristic.handleSetRequest('abc', null as unknown as undefined))
+ .resolves.toEqual(undefined)
// value should now be updated
- expect(characteristic.value).toEqual("abc");
+ expect(characteristic.value).toEqual('abc')
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalledTimes(2);
- });
+ expect(validateClientSuppliedValueMock).toBeCalledTimes(2)
+ })
- it("rejects data values exceeding the max length from the client", async () => {
+ it('rejects data values exceeding the max length from the client', async () => {
const characteristic = createCharacteristicWithProps({
format: Formats.DATA,
maxDataLen: 5,
perms: [Perms.EVENTS, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const validateClientSuppliedValueMock = jest.spyOn(characteristic, "validateClientSuppliedValue");
+ const validateClientSuppliedValueMock = vi.spyOn(characteristic, 'validateClientSuppliedValue')
// set initial known good value
- characteristic.setValue("abcde");
+ characteristic.setValue('abcde')
- // should reject strings that are to long
- await expect(characteristic.handleSetRequest("this is to long", null as unknown as undefined))
- .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ // should reject strings that are too long
+ await expect(characteristic.handleSetRequest('this is to long', null as unknown as undefined))
+ .rejects.toEqual(HAPStatus.INVALID_VALUE_IN_REQUEST)
// the existing valid value should remain
- expect(characteristic.value).toEqual("abcde");
+ expect(characteristic.value).toEqual('abcde')
// strings should pass
- await expect(characteristic.handleSetRequest("abc", null as unknown as undefined))
- .resolves.toEqual(undefined);
+ await expect(characteristic.handleSetRequest('abc', null as unknown as undefined))
+ .resolves.toEqual(undefined)
// value should now be updated
- expect(characteristic.value).toEqual("abc");
+ expect(characteristic.value).toEqual('abc')
// ensure validator was actually called
- expect(validateClientSuppliedValueMock).toBeCalledTimes(2);
- });
-
- });
-
- describe("#validateUserInput()", () => {
-
- it("should validate an integer property", () => {
- const VALUE = 1024;
- const characteristic = createCharacteristic(Formats.INT);
+ expect(validateClientSuppliedValueMock).toBeCalledTimes(2)
+ })
+ })
+
+ describe('#validateUserInput()', () => {
+ it('should validate an integer property', () => {
+ const VALUE = 1024
+ const characteristic = createCharacteristic(Formats.INT)
// @ts-expect-error: private access
- expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE);
- });
+ expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE)
+ })
- it("should validate a float property", () => {
- const VALUE = 1.024;
+ it('should validate a float property', () => {
+ const VALUE = 1.024
const characteristic = createCharacteristicWithProps({
format: Formats.FLOAT,
minStep: 0.001,
minValue: 0,
perms: [Perms.NOTIFY],
- });
+ })
// @ts-expect-error: private access
- expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE);
- });
+ expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE)
+ })
- it("should validate a UINT8 property", () => {
- const VALUE = 10;
- const characteristic = createCharacteristic(Formats.UINT8);
+ it('should validate a UINT8 property', () => {
+ const VALUE = 10
+ const characteristic = createCharacteristic(Formats.UINT8)
// @ts-expect-error: private access
- expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE);
- });
+ expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE)
+ })
- it("should validate a UINT16 property", () => {
- const VALUE = 10;
- const characteristic = createCharacteristic(Formats.UINT16);
+ it('should validate a UINT16 property', () => {
+ const VALUE = 10
+ const characteristic = createCharacteristic(Formats.UINT16)
// @ts-expect-error: private access
- expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE);
- });
+ expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE)
+ })
- it("should validate a UINT32 property", () => {
- const VALUE = 10;
- const characteristic = createCharacteristic(Formats.UINT32);
+ it('should validate a UINT32 property', () => {
+ const VALUE = 10
+ const characteristic = createCharacteristic(Formats.UINT32)
// @ts-expect-error: private access
- expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE);
- });
+ expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE)
+ })
- it("should validate a UINT64 property", () => {
- const VALUE = 10;
- const characteristic = createCharacteristic(Formats.UINT64);
+ it('should validate a UINT64 property', () => {
+ const VALUE = 10
+ const characteristic = createCharacteristic(Formats.UINT64)
// @ts-expect-error: private access
- expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE);
- });
+ expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE)
+ })
- it("should validate a boolean property", () => {
- const VALUE = true;
- const characteristic = createCharacteristic(Formats.BOOL);
+ it('should validate a boolean property', () => {
+ const VALUE = true
+ const characteristic = createCharacteristic(Formats.BOOL)
// @ts-expect-error: private access
- expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE);
- });
+ expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE)
+ })
- it("should validate a string property", () => {
- const VALUE = "Test";
- const characteristic = createCharacteristic(Formats.STRING);
+ it('should validate a string property', () => {
+ const VALUE = 'Test'
+ const characteristic = createCharacteristic(Formats.STRING)
// @ts-expect-error: private access
- expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE);
- });
+ expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE)
+ })
- it("should validate a data property", () => {
- const VALUE = Buffer.from("Hello my good friend. Have a nice day!", "ascii").toString("base64");
- const characteristic = createCharacteristic(Formats.DATA);
+ it('should validate a data property', () => {
+ const VALUE = Buffer.from('Hello my good friend. Have a nice day!', 'ascii').toString('base64')
+ const characteristic = createCharacteristic(Formats.DATA)
// @ts-expect-error: private access
- expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE);
- });
+ expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE)
+ })
- it("should validate a TLV8 property", () => {
- const VALUE = "";
- const characteristic = createCharacteristic(Formats.TLV8);
+ it('should validate a TLV8 property', () => {
+ const VALUE = ''
+ const characteristic = createCharacteristic(Formats.TLV8)
// @ts-expect-error: private access
- expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE);
- });
+ expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE)
+ })
- it("should validate boolean inputs", () => {
+ it('should validate boolean inputs', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.BOOL,
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
- characteristic.setValue(true);
- expect(characteristic.value).toEqual(true);
+ characteristic.setValue(true)
+ expect(characteristic.value).toEqual(true)
- characteristic.setValue(false);
- expect(characteristic.value).toEqual(false);
+ characteristic.setValue(false)
+ expect(characteristic.value).toEqual(false)
- characteristic.setValue(1);
- expect(characteristic.value).toEqual(true);
+ characteristic.setValue(1)
+ expect(characteristic.value).toEqual(true)
- characteristic.setValue(0);
- expect(characteristic.value).toEqual(false);
+ characteristic.setValue(0)
+ expect(characteristic.value).toEqual(false)
- characteristic.setValue("1");
- expect(characteristic.value).toEqual(true);
+ characteristic.setValue('1')
+ expect(characteristic.value).toEqual(true)
- characteristic.setValue("true");
- expect(characteristic.value).toEqual(true);
+ characteristic.setValue('true')
+ expect(characteristic.value).toEqual(true)
- characteristic.setValue("0");
- expect(characteristic.value).toEqual(false);
+ characteristic.setValue('0')
+ expect(characteristic.value).toEqual(false)
- characteristic.setValue("false");
- expect(characteristic.value).toEqual(false);
+ characteristic.setValue('false')
+ expect(characteristic.value).toEqual(false)
- characteristic.setValue({ some: "object" });
- expect(characteristic.value).toEqual(false);
- expect(mock).toBeCalledTimes(1);
- });
+ characteristic.setValue({ some: 'object' })
+ expect(characteristic.value).toEqual(false)
+ expect(mock).toBeCalledTimes(1)
+ })
- it("should validate boolean inputs when value is undefined", () => {
+ it('should validate boolean inputs when value is undefined', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.BOOL,
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
- characteristic.setValue(undefined as unknown as boolean);
- expect(characteristic.value).toEqual(false);
- expect(mock).toBeCalledTimes(1);
- });
+ characteristic.setValue(undefined as unknown as boolean)
+ expect(characteristic.value).toEqual(false)
+ expect(mock).toBeCalledTimes(1)
+ })
- test.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
- "should validate %p inputs", (intType) => {
+ it.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
+ 'should validate %p inputs',
+ (intType) => {
const characteristic = createCharacteristicWithProps({
format: intType,
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
minValue: 0,
maxValue: 100,
- });
+ })
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
- characteristic.setValue(1);
- expect(characteristic.value).toEqual(1);
+ characteristic.setValue(1)
+ expect(characteristic.value).toEqual(1)
// round to nearest valid value, trigger warning
- mock.mockReset();
- characteristic.setValue(-100);
- expect(characteristic.value).toEqual(0);
- expect(mock).toBeCalledTimes(1);
+ mock.mockReset()
+ characteristic.setValue(-100)
+ expect(characteristic.value).toEqual(0)
+ expect(mock).toBeCalledTimes(1)
// round to nearest valid value, trigger warning
- mock.mockReset();
- characteristic.setValue(200);
- expect(characteristic.value).toEqual(100);
- expect(mock).toBeCalledTimes(1);
+ mock.mockReset()
+ characteristic.setValue(200)
+ expect(characteristic.value).toEqual(100)
+ expect(mock).toBeCalledTimes(1)
// parse string
- mock.mockReset();
- characteristic.setValue("50");
- expect(characteristic.value).toEqual(50);
- expect(mock).toBeCalledTimes(0);
+ mock.mockReset()
+ characteristic.setValue('50')
+ expect(characteristic.value).toEqual(50)
+ expect(mock).toBeCalledTimes(0)
// handle NaN from non-numeric string, restore last known value, trigger warning
- mock.mockReset();
- characteristic.setValue(50);
- characteristic.setValue("SOME STRING");
- expect(characteristic.value).toEqual(50);
- expect(mock).toBeCalledTimes(1);
- expect(mock).toBeCalledWith("characteristic value expected valid finite number and received \"NaN\" (number)", "warn-message");
+ mock.mockReset()
+ characteristic.setValue(50)
+ characteristic.setValue('SOME STRING')
+ expect(characteristic.value).toEqual(50)
+ expect(mock).toBeCalledTimes(1)
+ expect(mock).toBeCalledWith('characteristic value expected valid finite number and received "NaN" (number)', 'warn-message')
// handle NaN: number from number value
- mock.mockReset();
- characteristic.setValue(50);
- characteristic.setValue(NaN);
- expect(characteristic.value).toEqual(50);
- expect(mock).toBeCalledTimes(1);
- expect(mock).toBeCalledWith("characteristic value expected valid finite number and received \"NaN\" (number)", "warn-message");
+ mock.mockReset()
+ characteristic.setValue(50)
+ characteristic.setValue(Number.NaN)
+ expect(characteristic.value).toEqual(50)
+ expect(mock).toBeCalledTimes(1)
+ expect(mock).toBeCalledWith('characteristic value expected valid finite number and received "NaN" (number)', 'warn-message')
// handle object, restore last known value, trigger warning
- mock.mockReset();
- characteristic.setValue(50);
- characteristic.setValue({ some: "object" });
- expect(characteristic.value).toEqual(50);
- expect(mock).toBeCalledTimes(1);
+ mock.mockReset()
+ characteristic.setValue(50)
+ characteristic.setValue({ some: 'object' })
+ expect(characteristic.value).toEqual(50)
+ expect(mock).toBeCalledTimes(1)
// handle boolean - true -> 1
- mock.mockReset();
- characteristic.setValue(true);
- expect(characteristic.value).toEqual(1);
- expect(mock).toBeCalledTimes(0);
+ mock.mockReset()
+ characteristic.setValue(true)
+ expect(characteristic.value).toEqual(1)
+ expect(mock).toBeCalledTimes(0)
// handle boolean - false -> 0
- mock.mockReset();
- characteristic.setValue(false);
- expect(characteristic.value).toEqual(0);
- expect(mock).toBeCalledTimes(0);
+ mock.mockReset()
+ characteristic.setValue(false)
+ expect(characteristic.value).toEqual(0)
+ expect(mock).toBeCalledTimes(0)
},
- );
+ )
- test.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
- "should validate %p inputs when value is undefined", (intType) => {
+ it.each([Formats.INT, Formats.FLOAT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
+ 'should validate %p inputs when value is undefined',
+ (intType) => {
const characteristic = createCharacteristicWithProps({
format: intType,
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
minValue: 0,
maxValue: 100,
- });
+ })
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
// undefined values should be set to the minValue if not yet set
- mock.mockReset();
- characteristic.setValue(undefined as unknown as boolean);
- expect(characteristic.value).toEqual(0);
- expect(mock).toBeCalledTimes(1);
+ mock.mockReset()
+ characteristic.setValue(undefined as unknown as boolean)
+ expect(characteristic.value).toEqual(0)
+ expect(mock).toBeCalledTimes(1)
// undefined values should be set to the existing value if set
- mock.mockReset();
- characteristic.setValue(50);
- characteristic.setValue(undefined as unknown as boolean);
- expect(characteristic.value).toEqual(50);
- expect(mock).toBeCalledTimes(1);
+ mock.mockReset()
+ characteristic.setValue(50)
+ characteristic.setValue(undefined as unknown as boolean)
+ expect(characteristic.value).toEqual(50)
+ expect(mock).toBeCalledTimes(1)
},
- );
+ )
- test.each([Formats.INT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
- "should round when a float is provided for %p inputs", (intType) => {
+ it.each([Formats.INT, Formats.UINT8, Formats.UINT16, Formats.UINT32, Formats.UINT64])(
+ 'should round when a float is provided for %p inputs',
+ (intType) => {
const characteristic = createCharacteristicWithProps({
format: intType,
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
minValue: 0,
maxValue: 100,
- });
+ })
- characteristic.setValue(99.5);
- expect(characteristic.value).toEqual(100);
+ characteristic.setValue(99.5)
+ expect(characteristic.value).toEqual(100)
- characteristic.setValue(0.1);
- expect(characteristic.value).toEqual(0);
+ characteristic.setValue(0.1)
+ expect(characteristic.value).toEqual(0)
},
- );
+ )
- it("should not round floats for Formats.FLOAT", () => {
+ it('should not round floats for Formats.FLOAT', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.FLOAT,
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
minValue: 0,
maxValue: 100,
- });
+ })
- characteristic.setValue(99.5);
- expect(characteristic.value).toEqual(99.5);
+ characteristic.setValue(99.5)
+ expect(characteristic.value).toEqual(99.5)
- characteristic.setValue(0.1);
- expect(characteristic.value).toEqual(0.1);
- });
+ characteristic.setValue(0.1)
+ expect(characteristic.value).toEqual(0.1)
+ })
- it("should accept Formats.FLOAT with non-defined min/max value", () => {
+ it('should accept Formats.FLOAT with non-defined min/max value', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.FLOAT,
minStep: 0.01,
perms: [Perms.PAIRED_READ, Perms.NOTIFY],
- }, uuid.generate("051"));
+ }, generate('051'))
// @ts-expect-error - spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
- mock.mockReset();
- characteristic.updateValue(0.09);
- expect(characteristic.value).toEqual(0.09);
- expect(mock).toBeCalledTimes(0);
- });
+ mock.mockReset()
+ characteristic.updateValue(0.09)
+ expect(characteristic.value).toEqual(0.09)
+ expect(mock).toBeCalledTimes(0)
+ })
- it("should validate Formats.FLOAT with precision", () => {
- const characteristic = new Characteristic.CurrentAmbientLightLevel();
+ it('should validate Formats.FLOAT with precision', () => {
+ const characteristic = new Characteristic.CurrentAmbientLightLevel()
// @ts-expect-error - spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
-
- mock.mockReset();
- characteristic.setValue(0);
- expect(characteristic.value).toEqual(0.0001);
- expect(mock).toBeCalledTimes(1);
-
- mock.mockReset();
- characteristic.setValue(0.0001);
- expect(characteristic.value).toEqual(0.0001);
- expect(mock).toBeCalledTimes(0);
-
- mock.mockReset();
- characteristic.setValue("0.0001");
- expect(characteristic.value).toEqual(0.0001);
- expect(mock).toBeCalledTimes(0);
-
- mock.mockReset();
- characteristic.setValue(100000.00000001);
- expect(characteristic.value).toEqual(100000);
- expect(mock).toBeCalledTimes(1);
-
- mock.mockReset();
- characteristic.setValue(100000);
- expect(characteristic.value).toEqual(100000);
- expect(mock).toBeCalledTimes(0);
- });
-
- it("should validate Formats.FLOAT with precision with minimum steps", () => {
- const characteristic = createCharacteristic(Formats.FLOAT);
- let minStep;
-
- minStep = 100 / 6;
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
+
+ mock.mockReset()
+ characteristic.setValue(0)
+ expect(characteristic.value).toEqual(0.0001)
+ expect(mock).toBeCalledTimes(1)
+
+ mock.mockReset()
+ characteristic.setValue(0.0001)
+ expect(characteristic.value).toEqual(0.0001)
+ expect(mock).toBeCalledTimes(0)
+
+ mock.mockReset()
+ characteristic.setValue('0.0001')
+ expect(characteristic.value).toEqual(0.0001)
+ expect(mock).toBeCalledTimes(0)
+
+ mock.mockReset()
+ characteristic.setValue(100000.00000001)
+ expect(characteristic.value).toEqual(100000)
+ expect(mock).toBeCalledTimes(1)
+
+ mock.mockReset()
+ characteristic.setValue(100000)
+ expect(characteristic.value).toEqual(100000)
+ expect(mock).toBeCalledTimes(0)
+ })
+
+ it('should validate Formats.FLOAT with precision with minimum steps', () => {
+ const characteristic = createCharacteristic(Formats.FLOAT)
+ let minStep
+
+ minStep = 100 / 6
characteristic.setProps({
minValue: 0,
maxValue: 100,
- minStep: minStep,
- });
- for(let i = 1; i <= 7; i++) {
- const desiredValue = Math.min(Math.max(i * minStep, 0), 100);
- characteristic.setValue(i * minStep);
- expect(characteristic.value).toEqual(desiredValue);
+ minStep,
+ })
+ for (let i = 1; i <= 7; i++) {
+ const desiredValue = Math.min(Math.max(i * minStep, 0), 100)
+ characteristic.setValue(i * minStep)
+ expect(characteristic.value).toEqual(desiredValue)
}
- minStep = 1;
+ minStep = 1
characteristic.setProps({
minValue: 0.5,
maxValue: 2.5,
- minStep: minStep,
- });
- for(let i = 1; i <= 4; i++) {
- const desiredValue = Math.min(Math.max(i * minStep + 0.5, 0.5), 2.5);
- characteristic.setValue(i * minStep + 0.5);
- expect(characteristic.value).toEqual(desiredValue);
+ minStep,
+ })
+ for (let i = 1; i <= 4; i++) {
+ const desiredValue = Math.min(Math.max(i * minStep + 0.5, 0.5), 2.5)
+ characteristic.setValue(i * minStep + 0.5)
+ expect(characteristic.value).toEqual(desiredValue)
}
- minStep = 100 / 3;
+ minStep = 100 / 3
characteristic.setProps({
minValue: 0,
maxValue: 100,
- minStep: minStep,
- });
- for(let i = 1; i <= 4; i++) {
- const desiredValue = Math.min(Math.max(i * minStep, 0), 100);
- characteristic.setValue(i * minStep);
- expect(characteristic.value).toEqual(desiredValue);
+ minStep,
+ })
+ for (let i = 1; i <= 4; i++) {
+ const desiredValue = Math.min(Math.max(i * minStep, 0), 100)
+ characteristic.setValue(i * minStep)
+ expect(characteristic.value).toEqual(desiredValue)
}
- });
+ })
- it("should allow negative floats in range for Formats.FLOAT", () => {
+ it('should allow negative floats in range for Formats.FLOAT', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.FLOAT,
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
minValue: -1000,
maxValue: 1000,
- });
+ })
// @ts-expect-error - spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
- mock.mockReset();
- characteristic.setValue(-0.013);
- expect(characteristic.value).toEqual(-0.013);
- expect(mock).toBeCalledTimes(0);
- });
+ mock.mockReset()
+ characteristic.setValue(-0.013)
+ expect(characteristic.value).toEqual(-0.013)
+ expect(mock).toBeCalledTimes(0)
+ })
- it("should not allow non-finite floats in range for Formats.FLOAT", () => {
+ it('should not allow non-finite floats in range for Formats.FLOAT', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.FLOAT,
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error - spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
- mock.mockReset();
- characteristic.setValue(Infinity);
- expect(characteristic.value).toEqual(0);
- expect(mock).toBeCalledTimes(1);
+ mock.mockReset()
+ characteristic.setValue(Infinity)
+ expect(characteristic.value).toEqual(0)
+ expect(mock).toBeCalledTimes(1)
- mock.mockReset();
- characteristic.setValue(Number.POSITIVE_INFINITY);
- expect(characteristic.value).toEqual(0);
- expect(mock).toBeCalledTimes(1);
+ mock.mockReset()
+ characteristic.setValue(Number.POSITIVE_INFINITY)
+ expect(characteristic.value).toEqual(0)
+ expect(mock).toBeCalledTimes(1)
- mock.mockReset();
- characteristic.setValue(Number.NEGATIVE_INFINITY);
- expect(characteristic.value).toEqual(0);
- expect(mock).toBeCalledTimes(1);
- });
+ mock.mockReset()
+ characteristic.setValue(Number.NEGATIVE_INFINITY)
+ expect(characteristic.value).toEqual(0)
+ expect(mock).toBeCalledTimes(1)
+ })
- it("should validate string inputs", () => {
+ it('should validate string inputs', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.STRING,
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
maxLen: 15,
- });
+ })
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
// valid string
- mock.mockReset();
- characteristic.setValue("ok string");
- expect(characteristic.value).toEqual("ok string");
- expect(mock).toBeCalledTimes(0);
+ mock.mockReset()
+ characteristic.setValue('ok string')
+ expect(characteristic.value).toEqual('ok string')
+ expect(mock).toBeCalledTimes(0)
// number - convert to string - trigger warning
- mock.mockReset();
- characteristic.setValue(12345);
- expect(characteristic.value).toEqual("12345");
- expect(mock).toBeCalledTimes(1);
+ mock.mockReset()
+ characteristic.setValue(12345)
+ expect(characteristic.value).toEqual('12345')
+ expect(mock).toBeCalledTimes(1)
// not a string or number, use last known good value and trigger warning
- mock.mockReset();
- characteristic.setValue("ok string");
- characteristic.setValue({ ok: "an object" });
- expect(characteristic.value).toEqual("ok string");
- expect(mock).toBeCalledTimes(1);
+ mock.mockReset()
+ characteristic.setValue('ok string')
+ characteristic.setValue({ ok: 'an object' })
+ expect(characteristic.value).toEqual('ok string')
+ expect(mock).toBeCalledTimes(1)
// max length exceeded
- mock.mockReset();
- characteristic.setValue("this string exceeds the max length allowed");
- expect(characteristic.value).toEqual("this string exc");
- expect(mock).toBeCalledTimes(1);
- });
+ mock.mockReset()
+ characteristic.setValue('this string exceeds the max length allowed')
+ expect(characteristic.value).toEqual('this string exc')
+ expect(mock).toBeCalledTimes(1)
+ })
- it("should validate string inputs when undefined", () => {
+ it('should validate string inputs when undefined', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.STRING,
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
maxLen: 15,
- });
+ })
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
// undefined values should be set to "undefined" of no valid value is set yet
- mock.mockReset();
- characteristic.setValue(undefined as unknown as boolean);
- expect(characteristic.value).toEqual("undefined");
- expect(mock).toBeCalledTimes(1);
+ mock.mockReset()
+ characteristic.setValue(undefined as unknown as boolean)
+ expect(characteristic.value).toEqual('undefined')
+ expect(mock).toBeCalledTimes(1)
// undefined values should revert back to last known good value if set
- mock.mockReset();
- characteristic.setValue("ok string");
- characteristic.setValue(undefined as unknown as boolean);
- expect(characteristic.value).toEqual("ok string");
- expect(mock).toBeCalledTimes(1);
- });
-
- it("should validate data type intputs", () => {
+ mock.mockReset()
+ characteristic.setValue('ok string')
+ characteristic.setValue(undefined as unknown as boolean)
+ expect(characteristic.value).toEqual('ok string')
+ expect(mock).toBeCalledTimes(1)
+ })
+
+ it('should validate data type inputs', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.DATA,
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
maxDataLen: 15,
- });
+ })
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
// valid data
- mock.mockReset();
- characteristic.setValue("some data");
- expect(characteristic.value).toEqual("some data");
- expect(mock).toBeCalledTimes(0);
+ mock.mockReset()
+ characteristic.setValue('some data')
+ expect(characteristic.value).toEqual('some data')
+ expect(mock).toBeCalledTimes(0)
// not valid data
- mock.mockReset();
- characteristic.setValue({ some: "data" });
- expect(mock).toBeCalledTimes(1);
+ mock.mockReset()
+ characteristic.setValue({ some: 'data' })
+ expect(mock).toBeCalledTimes(1)
// max length exceeded
- mock.mockReset();
- characteristic.setValue("this string exceeds the max length allowed");
- expect(mock).toBeCalledTimes(1);
- });
+ mock.mockReset()
+ characteristic.setValue('this string exceeds the max length allowed')
+ expect(mock).toBeCalledTimes(1)
+ })
- it("should handle null inputs correctly for scalar Apple characteristics", () => {
- const characteristic = new Characteristic("CurrentTemperature", Characteristic.CurrentTemperature.UUID, {
+ it('should handle null inputs correctly for scalar Apple characteristics', () => {
+ const characteristic = new Characteristic('CurrentTemperature', Characteristic.CurrentTemperature.UUID, {
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
format: Formats.FLOAT,
minValue: 0,
maxValue: 100,
- });
+ })
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
// if the initial value is null, validation should set a valid default
- mock.mockReset();
- characteristic.setValue(null as unknown as boolean);
- expect(characteristic.value).toEqual(0);
- expect(mock).toBeCalledTimes(2);
+ mock.mockReset()
+ characteristic.setValue(null as unknown as boolean)
+ expect(characteristic.value).toEqual(0)
+ expect(mock).toBeCalledTimes(2)
// if the value has been previously set, and null is received, the previous value should be returned,
- mock.mockReset();
- characteristic.setValue(50);
- characteristic.setValue(null as unknown as boolean);
- expect(characteristic.value).toEqual(50);
- expect(mock).toBeCalledTimes(1);
- });
-
- it("should handle null inputs correctly for scalar non-scalar Apple characteristics", () => {
- const characteristicTLV = new SelectedRTPStreamConfiguration();
- const characteristicData = new Characteristic("Data characteristic", Characteristic.SupportedRTPConfiguration.UUID, {
+ mock.mockReset()
+ characteristic.setValue(50)
+ characteristic.setValue(null as unknown as boolean)
+ expect(characteristic.value).toEqual(50)
+ expect(mock).toBeCalledTimes(1)
+ })
+
+ it('should handle null inputs correctly for scalar non-scalar Apple characteristics', () => {
+ const characteristicTLV = new SelectedRTPStreamConfiguration()
+ const characteristicData = new Characteristic('Data characteristic', Characteristic.SupportedRTPConfiguration.UUID, {
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
format: Formats.DATA,
- });
+ })
- const exampleString = "Example String"; // data and tlv8 are both string based
+ const exampleString = 'Example String' // data and tlv8 are both string based
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristicTLV, "characteristicWarning");
+ const mock = vi.spyOn(characteristicTLV, 'characteristicWarning')
// null is a valid value for tlv8 format
- mock.mockReset();
- characteristicTLV.setValue(exampleString);
- expect(characteristicTLV.value).toEqual(exampleString);
- characteristicTLV.setValue(null as unknown as string);
- expect(characteristicTLV.value).toEqual(null);
- expect(mock).toBeCalledTimes(0);
+ mock.mockReset()
+ characteristicTLV.setValue(exampleString)
+ expect(characteristicTLV.value).toEqual(exampleString)
+ characteristicTLV.setValue(null as unknown as string)
+ expect(characteristicTLV.value).toEqual(null)
+ expect(mock).toBeCalledTimes(0)
// null is a valid value for data format
- mock.mockReset();
- characteristicData.setValue(exampleString);
- expect(characteristicData.value).toEqual(exampleString);
- characteristicData.setValue(null as unknown as string);
- expect(characteristicData.value).toEqual(null);
- expect(mock).toBeCalledTimes(0);
- });
-
- it("should handle null inputs correctly for non-Apple characteristics", () => {
+ mock.mockReset()
+ characteristicData.setValue(exampleString)
+ expect(characteristicData.value).toEqual(exampleString)
+ characteristicData.setValue(null as unknown as string)
+ expect(characteristicData.value).toEqual(null)
+ expect(mock).toBeCalledTimes(0)
+ })
+
+ it('should handle null inputs correctly for non-Apple characteristics', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.INT,
perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE],
- });
+ })
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
// if the initial value is null, still allow null for non-Apple characteristics
- mock.mockReset();
- characteristic.setValue(null as unknown as boolean);
- expect(characteristic.value).toEqual(null);
- expect(mock).toBeCalledTimes(0);
+ mock.mockReset()
+ characteristic.setValue(null as unknown as boolean)
+ expect(characteristic.value).toEqual(null)
+ expect(mock).toBeCalledTimes(0)
// if the value has been previously set, and null is received, still allow null for non-Apple characteristics
- mock.mockReset();
- characteristic.setValue(50);
- characteristic.setValue(null as unknown as boolean);
- expect(characteristic.value).toEqual(null);
- expect(mock).toBeCalledTimes(0);
- });
- });
-
- describe("#getDefaultValue()", () => {
-
- it("should get the correct default value for a boolean property", () => {
- const characteristic = createCharacteristic(Formats.BOOL);
+ mock.mockReset()
+ characteristic.setValue(50)
+ characteristic.setValue(null as unknown as boolean)
+ expect(characteristic.value).toEqual(null)
+ expect(mock).toBeCalledTimes(0)
+ })
+ })
+
+ describe('#getDefaultValue()', () => {
+ it('should get the correct default value for a boolean property', () => {
+ const characteristic = createCharacteristic(Formats.BOOL)
// @ts-expect-error: private access
- expect(characteristic.getDefaultValue()).toEqual(false);
- });
+ expect(characteristic.getDefaultValue()).toEqual(false)
+ })
- it("should get the correct default value for a string property", () => {
- const characteristic = createCharacteristic(Formats.STRING);
+ it('should get the correct default value for a string property', () => {
+ const characteristic = createCharacteristic(Formats.STRING)
// @ts-expect-error: private access
- expect(characteristic.getDefaultValue()).toEqual("");
- });
+ expect(characteristic.getDefaultValue()).toEqual('')
+ })
- it("should get the correct default value for a data property", () => {
- const characteristic = createCharacteristic(Formats.DATA);
+ it('should get the correct default value for a data property', () => {
+ const characteristic = createCharacteristic(Formats.DATA)
// @ts-expect-error: private access
- expect(characteristic.getDefaultValue()).toEqual("");
- });
+ expect(characteristic.getDefaultValue()).toEqual('')
+ })
- it("should get the correct default value for a TLV8 property", () => {
- const characteristic = createCharacteristic(Formats.TLV8);
+ it('should get the correct default value for a TLV8 property', () => {
+ const characteristic = createCharacteristic(Formats.TLV8)
// @ts-expect-error: private access
- expect(characteristic.getDefaultValue()).toEqual("");
- });
+ expect(characteristic.getDefaultValue()).toEqual('')
+ })
- it("should get the correct default value a UINT8 property without minValue", () => {
+ it('should get the correct default value a UINT8 property without minValue', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.UINT8,
perms: [Perms.TIMED_WRITE, Perms.PAIRED_READ],
- });
+ })
// @ts-expect-error: private access
- expect(characteristic.getDefaultValue()).toEqual(0);
- expect(characteristic.value).toEqual(null); // null if never set
- });
+ expect(characteristic.getDefaultValue()).toEqual(0)
+ expect(characteristic.value).toEqual(null) // null if never set
+ })
- it("should get the correct default value a UINT8 property with minValue", () => {
+ it('should get the correct default value a UINT8 property with minValue', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.UINT8,
perms: [Perms.TIMED_WRITE, Perms.PAIRED_READ],
minValue: 50,
- });
+ })
// @ts-expect-error: private access
- expect(characteristic.getDefaultValue()).toEqual(50);
- expect(characteristic.value).toEqual(null); // null if never set
- });
+ expect(characteristic.getDefaultValue()).toEqual(50)
+ expect(characteristic.value).toEqual(null) // null if never set
+ })
- it("should get the correct default value a INT property without minValue", () => {
+ it('should get the correct default value a INT property without minValue', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.INT,
perms: [Perms.TIMED_WRITE, Perms.PAIRED_READ],
- });
+ })
// @ts-expect-error: private access
- expect(characteristic.getDefaultValue()).toEqual(0);
- expect(characteristic.value).toEqual(null); // null if never set
- });
+ expect(characteristic.getDefaultValue()).toEqual(0)
+ expect(characteristic.value).toEqual(null) // null if never set
+ })
- it("should get the correct default value a INT property with minValue", () => {
+ it('should get the correct default value a INT property with minValue', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.INT,
perms: [Perms.TIMED_WRITE, Perms.PAIRED_READ],
minValue: 50,
- });
+ })
// @ts-expect-error: private access
- expect(characteristic.getDefaultValue()).toEqual(50);
- expect(characteristic.value).toEqual(null); // null if never set
- });
+ expect(characteristic.getDefaultValue()).toEqual(50)
+ expect(characteristic.value).toEqual(null) // null if never set
+ })
- it("should get the correct default value for the current temperature characteristic", () => {
- const characteristic = new Characteristic.CurrentTemperature();
+ it('should get the correct default value for the current temperature characteristic', () => {
+ const characteristic = new Characteristic.CurrentTemperature()
// @ts-expect-error: private access
- expect(characteristic.getDefaultValue()).toEqual(0);
- expect(characteristic.value).toEqual(0);
- });
+ expect(characteristic.getDefaultValue()).toEqual(0)
+ expect(characteristic.value).toEqual(0)
+ })
- it("should get the default value from the first item in the validValues prop", () => {
+ it('should get the default value from the first item in the validValues prop', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.INT,
perms: [Perms.TIMED_WRITE, Perms.PAIRED_READ],
validValues: [5, 4, 3, 2],
- });
+ })
// @ts-expect-error: private access
- expect(characteristic.getDefaultValue()).toEqual(5);
- expect(characteristic.value).toEqual(null); // null if never set
- });
+ expect(characteristic.getDefaultValue()).toEqual(5)
+ expect(characteristic.value).toEqual(null) // null if never set
+ })
- it("should get the default value from minValue prop if set", () => {
+ it('should get the default value from minValue prop if set', () => {
const characteristic = createCharacteristicWithProps({
format: Formats.INT,
perms: [Perms.TIMED_WRITE, Perms.PAIRED_READ],
minValue: 100,
maxValue: 255,
- });
+ })
// @ts-expect-error: private access
- expect(characteristic.getDefaultValue()).toEqual(100);
- expect(characteristic.value).toEqual(null); // null if never set
- });
-
- });
+ expect(characteristic.getDefaultValue()).toEqual(100)
+ expect(characteristic.value).toEqual(null) // null if never set
+ })
+ })
describe(`@${CharacteristicEventTypes.GET}`, () => {
- it("should call any listeners for the event", (callback) => {
- const characteristic = createCharacteristic(Formats.STRING);
+ it('should call any listeners for the event', async () => {
+ const characteristic = createCharacteristic(Formats.STRING)
- const listenerCallback = jest.fn();
+ const listenerCallback = vi.fn()
- characteristic.handleGetRequest().then(() => {
- characteristic.on(CharacteristicEventTypes.GET, listenerCallback);
- characteristic.handleGetRequest();
- expect(listenerCallback).toHaveBeenCalledTimes(1);
- callback();
- });
- });
+ await characteristic.handleGetRequest()
+ characteristic.on(CharacteristicEventTypes.GET, listenerCallback)
+ characteristic.handleGetRequest()
+ expect(listenerCallback).toHaveBeenCalledTimes(1)
+ })
- it("should handle GET event errors gracefully when using on('get')", async () => {
- const characteristic = createCharacteristic(Formats.STRING);
+ it('should handle GET event errors gracefully when using on(\'get\')', async () => {
+ const characteristic = createCharacteristic(Formats.STRING)
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
// throw HapStatusError - should not trigger characteristic warning
- mock.mockReset();
- characteristic.removeAllListeners("get");
- characteristic.on("get", (callback) => {
- callback(new HapStatusError(HAPStatus.RESOURCE_BUSY));
- });
- await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.RESOURCE_BUSY);
- expect(characteristic.statusCode).toEqual(HAPStatus.RESOURCE_BUSY);
- expect(mock).toBeCalledTimes(0);
+ mock.mockReset()
+ characteristic.removeAllListeners('get')
+ characteristic.on('get', (callback) => {
+ callback(new HapStatusError(HAPStatus.RESOURCE_BUSY))
+ })
+ await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.RESOURCE_BUSY)
+ expect(characteristic.statusCode).toEqual(HAPStatus.RESOURCE_BUSY)
+ expect(mock).toBeCalledTimes(0)
// throw number - should not trigger characteristic warning
- mock.mockReset();
- characteristic.removeAllListeners("get");
- characteristic.on("get", (callback) => {
- callback(HAPStatus.RESOURCE_BUSY);
- });
- await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.RESOURCE_BUSY);
- expect(characteristic.statusCode).toEqual(HAPStatus.RESOURCE_BUSY);
- expect(mock).toBeCalledTimes(0);
+ mock.mockReset()
+ characteristic.removeAllListeners('get')
+ characteristic.on('get', (callback) => {
+ callback(HAPStatus.RESOURCE_BUSY)
+ })
+ await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.RESOURCE_BUSY)
+ expect(characteristic.statusCode).toEqual(HAPStatus.RESOURCE_BUSY)
+ expect(mock).toBeCalledTimes(0)
// throw out of range number - should convert status code to SERVICE_COMMUNICATION_FAILURE
- mock.mockReset();
- characteristic.removeAllListeners("get");
- characteristic.on("get", (callback) => {
- callback(234234234234 as HAPStatus);
- });
- await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(mock).toBeCalledTimes(0);
+ mock.mockReset()
+ characteristic.removeAllListeners('get')
+ characteristic.on('get', (callback) => {
+ callback(234234234234 as HAPStatus)
+ })
+ await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(mock).toBeCalledTimes(0)
// throw other error - callback style getters should still not trigger warning when error is passed in
- mock.mockReset();
- characteristic.removeAllListeners("get");
- characteristic.on("get", (callback) => {
- callback(new Error("Something else"));
- });
- await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(mock).toBeCalledTimes(0);
- });
- });
-
- describe("onGet handler", () => {
- it("should ignore GET event handler when onGet was specified", async () => {
- const characteristic = createCharacteristic(Formats.STRING);
-
- const listenerCallback = jest.fn().mockImplementation((callback) => {
- callback(undefined, "OddValue");
- });
- const handlerMock = jest.fn();
+ mock.mockReset()
+ characteristic.removeAllListeners('get')
+ characteristic.on('get', (callback) => {
+ callback(new Error('Something else'))
+ })
+ await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(mock).toBeCalledTimes(0)
+ })
+ })
+
+ describe('onGet handler', () => {
+ it('should ignore GET event handler when onGet was specified', async () => {
+ const characteristic = createCharacteristic(Formats.STRING)
+
+ const listenerCallback = vi.fn().mockImplementation((callback) => {
+ callback(undefined, 'OddValue')
+ })
+ const handlerMock = vi.fn()
characteristic.onGet(() => {
- handlerMock();
- return "CurrentValue";
- });
- characteristic.on(CharacteristicEventTypes.GET, listenerCallback);
- const value = await characteristic.handleGetRequest();
+ handlerMock()
+ return 'CurrentValue'
+ })
+ characteristic.on(CharacteristicEventTypes.GET, listenerCallback)
+ const value = await characteristic.handleGetRequest()
- expect(value).toEqual("CurrentValue");
- expect(handlerMock).toHaveBeenCalledTimes(1);
- expect(listenerCallback).toHaveBeenCalledTimes(0);
- });
+ expect(value).toEqual('CurrentValue')
+ expect(handlerMock).toHaveBeenCalledTimes(1)
+ expect(listenerCallback).toHaveBeenCalledTimes(0)
+ })
- it("should handle GET event errors gracefully when using the onGet handler", async () => {
- const characteristic = createCharacteristic(Formats.STRING);
+ it('should handle GET event errors gracefully when using the onGet handler', async () => {
+ const characteristic = createCharacteristic(Formats.STRING)
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
// throw HapStatusError - should not trigger characteristic warning
- mock.mockReset();
+ mock.mockReset()
characteristic.onGet(() => {
- throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- });
- await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(mock).toBeCalledTimes(0);
+ throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ })
+ await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(mock).toBeCalledTimes(0)
// throw number - should not trigger characteristic warning
- mock.mockReset();
+ mock.mockReset()
characteristic.onGet(() => {
- throw HAPStatus.SERVICE_COMMUNICATION_FAILURE;
- });
- await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(mock).toBeCalledTimes(0);
+ throw HAPStatus.SERVICE_COMMUNICATION_FAILURE
+ })
+ await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(mock).toBeCalledTimes(0)
// throw out of range number - should convert status code to SERVICE_COMMUNICATION_FAILURE
- mock.mockReset();
+ mock.mockReset()
characteristic.onGet(() => {
- throw 234234234234;
- });
- await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(mock).toBeCalledTimes(0);
+ throw 234234234234 // eslint-disable-line no-throw-literal
+ })
+ await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(mock).toBeCalledTimes(0)
// throw other error - should trigger characteristic warning
- mock.mockReset();
+ mock.mockReset()
characteristic.onGet(() => {
- throw new Error("A Random Error");
- });
- await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(mock).toBeCalledTimes(1);
- });
- });
+ throw new Error('A Random Error')
+ })
+ await expect(characteristic.handleGetRequest()).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(mock).toBeCalledTimes(1)
+ })
+ })
describe(`@${CharacteristicEventTypes.SET}`, () => {
- it("should call any listeners for the event", () => {
- const characteristic = createCharacteristic(Formats.STRING);
+ it('should call any listeners for the event', () => {
+ const characteristic = createCharacteristic(Formats.STRING)
- const VALUE = "NewValue";
- const listenerCallback = jest.fn();
+ const VALUE = 'NewValue'
+ const listenerCallback = vi.fn()
- characteristic.handleSetRequest(VALUE);
- characteristic.on(CharacteristicEventTypes.SET, listenerCallback);
- characteristic.handleSetRequest(VALUE);
+ characteristic.handleSetRequest(VALUE)
+ characteristic.on(CharacteristicEventTypes.SET, listenerCallback)
+ characteristic.handleSetRequest(VALUE)
- expect(listenerCallback).toHaveBeenCalledTimes(1);
- });
+ expect(listenerCallback).toHaveBeenCalledTimes(1)
+ })
- it("should handle SET event errors gracefully when using on('set')", async () => {
- const characteristic = createCharacteristic(Formats.STRING);
+ it('should handle SET event errors gracefully when using on(\'set\')', async () => {
+ const characteristic = createCharacteristic(Formats.STRING)
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
// throw HapStatusError - should not trigger characteristic warning
- mock.mockReset();
- characteristic.removeAllListeners("set");
- characteristic.on("set", (value, callback) => {
- callback(new HapStatusError(HAPStatus.RESOURCE_BUSY));
- });
- await expect(characteristic.handleSetRequest("hello")).rejects.toEqual(HAPStatus.RESOURCE_BUSY);
- expect(characteristic.statusCode).toEqual(HAPStatus.RESOURCE_BUSY);
- expect(mock).toBeCalledTimes(0);
+ mock.mockReset()
+ characteristic.removeAllListeners('set')
+ characteristic.on('set', (value, callback) => {
+ callback(new HapStatusError(HAPStatus.RESOURCE_BUSY))
+ })
+ await expect(characteristic.handleSetRequest('hello')).rejects.toEqual(HAPStatus.RESOURCE_BUSY)
+ expect(characteristic.statusCode).toEqual(HAPStatus.RESOURCE_BUSY)
+ expect(mock).toBeCalledTimes(0)
// throw number - should not trigger characteristic warning
- mock.mockReset();
- characteristic.removeAllListeners("set");
- characteristic.on("set", (value, callback) => {
- callback(HAPStatus.RESOURCE_BUSY);
- });
- await expect(characteristic.handleSetRequest("hello")).rejects.toEqual(HAPStatus.RESOURCE_BUSY);
- expect(characteristic.statusCode).toEqual(HAPStatus.RESOURCE_BUSY);
- expect(mock).toBeCalledTimes(0);
+ mock.mockReset()
+ characteristic.removeAllListeners('set')
+ characteristic.on('set', (value, callback) => {
+ callback(HAPStatus.RESOURCE_BUSY)
+ })
+ await expect(characteristic.handleSetRequest('hello')).rejects.toEqual(HAPStatus.RESOURCE_BUSY)
+ expect(characteristic.statusCode).toEqual(HAPStatus.RESOURCE_BUSY)
+ expect(mock).toBeCalledTimes(0)
// throw out of range number - should convert status code to SERVICE_COMMUNICATION_FAILURE
- mock.mockReset();
- characteristic.removeAllListeners("set");
- characteristic.on("set", (value, callback) => {
- callback(234234234234 as HAPStatus);
- });
- await expect(characteristic.handleSetRequest("hello")).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(mock).toBeCalledTimes(0);
+ mock.mockReset()
+ characteristic.removeAllListeners('set')
+ characteristic.on('set', (value, callback) => {
+ callback(234234234234 as HAPStatus)
+ })
+ await expect(characteristic.handleSetRequest('hello')).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(mock).toBeCalledTimes(0)
// throw other error - callback style setters should still not trigger warning when error is passed in
- mock.mockReset();
- characteristic.removeAllListeners("set");
- characteristic.on("set", (value, callback) => {
- callback(new Error("Something else"));
- });
- await expect(characteristic.handleSetRequest("hello")).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(mock).toBeCalledTimes(0);
- });
- });
-
- describe("onSet handler", () => {
- it("should ignore SET event handler when onSet was specified", () => {
- const characteristic = createCharacteristic(Formats.STRING);
-
- const listenerCallback = jest.fn();
- const handlerMock = jest.fn();
-
- characteristic.onSet(value => {
- handlerMock(value);
- expect(value).toEqual("NewValue");
- return;
- });
- characteristic.on(CharacteristicEventTypes.SET, listenerCallback);
- characteristic.handleSetRequest("NewValue");
-
- expect(handlerMock).toHaveBeenCalledTimes(1);
- expect(listenerCallback).toHaveBeenCalledTimes(0);
- });
-
- it("should handle SET event errors gracefully when using onSet handler", async () => {
- const characteristic = createCharacteristic(Formats.STRING);
+ mock.mockReset()
+ characteristic.removeAllListeners('set')
+ characteristic.on('set', (value, callback) => {
+ callback(new Error('Something else'))
+ })
+ await expect(characteristic.handleSetRequest('hello')).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(mock).toBeCalledTimes(0)
+ })
+ })
+
+ describe('onSet handler', () => {
+ it('should ignore SET event handler when onSet was specified', () => {
+ const characteristic = createCharacteristic(Formats.STRING)
+
+ const listenerCallback = vi.fn()
+ const handlerMock = vi.fn()
+
+ characteristic.onSet((value) => {
+ handlerMock(value)
+ expect(value).toEqual('NewValue')
+ })
+ characteristic.on(CharacteristicEventTypes.SET, listenerCallback)
+ characteristic.handleSetRequest('NewValue')
+
+ expect(handlerMock).toHaveBeenCalledTimes(1)
+ expect(listenerCallback).toHaveBeenCalledTimes(0)
+ })
+
+ it('should handle SET event errors gracefully when using onSet handler', async () => {
+ const characteristic = createCharacteristic(Formats.STRING)
// @ts-expect-error: spying on private property
- const mock = jest.spyOn(characteristic, "characteristicWarning");
+ const mock = vi.spyOn(characteristic, 'characteristicWarning')
// throw HapStatusError - should not trigger characteristic warning
- mock.mockReset();
+ mock.mockReset()
characteristic.onSet(() => {
- throw new HapStatusError(HAPStatus.RESOURCE_BUSY);
- });
- await expect(characteristic.handleSetRequest("hello")).rejects.toEqual(HAPStatus.RESOURCE_BUSY);
- expect(characteristic.statusCode).toEqual(HAPStatus.RESOURCE_BUSY);
- expect(mock).toBeCalledTimes(0);
+ throw new HapStatusError(HAPStatus.RESOURCE_BUSY)
+ })
+ await expect(characteristic.handleSetRequest('hello')).rejects.toEqual(HAPStatus.RESOURCE_BUSY)
+ expect(characteristic.statusCode).toEqual(HAPStatus.RESOURCE_BUSY)
+ expect(mock).toBeCalledTimes(0)
// throw number - should not trigger characteristic warning
- mock.mockReset();
+ mock.mockReset()
characteristic.onSet(() => {
- throw HAPStatus.RESOURCE_BUSY;
- });
- await expect(characteristic.handleSetRequest("hello")).rejects.toEqual(HAPStatus.RESOURCE_BUSY);
- expect(characteristic.statusCode).toEqual(HAPStatus.RESOURCE_BUSY);
- expect(mock).toBeCalledTimes(0);
+ throw HAPStatus.RESOURCE_BUSY
+ })
+ await expect(characteristic.handleSetRequest('hello')).rejects.toEqual(HAPStatus.RESOURCE_BUSY)
+ expect(characteristic.statusCode).toEqual(HAPStatus.RESOURCE_BUSY)
+ expect(mock).toBeCalledTimes(0)
// throw out of range number - should convert status code to SERVICE_COMMUNICATION_FAILURE
- mock.mockReset();
+ mock.mockReset()
characteristic.onSet(() => {
- throw 234234234234;
- });
- await expect(characteristic.handleSetRequest("hello")).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(mock).toBeCalledTimes(0);
+ throw 234234234234 // eslint-disable-line no-throw-literal
+ })
+ await expect(characteristic.handleSetRequest('hello')).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(mock).toBeCalledTimes(0)
// throw other error - should trigger characteristic warning
- mock.mockReset();
+ mock.mockReset()
characteristic.onSet(() => {
- throw new Error("A Random Error");
- });
- await expect(characteristic.handleSetRequest("hello")).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
- expect(mock).toBeCalledTimes(1);
- });
- });
+ throw new Error('A Random Error')
+ })
+ await expect(characteristic.handleSetRequest('hello')).rejects.toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(characteristic.statusCode).toEqual(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
+ expect(mock).toBeCalledTimes(1)
+ })
+ })
describe(`@${CharacteristicEventTypes.CHANGE}`, () => {
+ it('should call listeners for the event when the characteristic is event-only, and the value is set', async () => {
+ const characteristic = createCharacteristic(Formats.STRING, Characteristic.ProgrammableSwitchEvent.UUID)
- it("should call listeners for the event when the characteristic is event-only, and the value is set", (callback) => {
- const characteristic = createCharacteristic(Formats.STRING, Characteristic.ProgrammableSwitchEvent.UUID);
+ const VALUE = 'NewValue'
+ const listenerCallback = vi.fn()
+ const setValueCallback = vi.fn()
- const VALUE = "NewValue";
- const listenerCallback = jest.fn();
- const setValueCallback = jest.fn();
+ await characteristic.setValue(VALUE)
+ setValueCallback()
- characteristic.setValue(VALUE, () => {
- setValueCallback();
+ characteristic.on(CharacteristicEventTypes.CHANGE, listenerCallback)
+ await characteristic.setValue(VALUE)
+ setValueCallback()
- characteristic.on(CharacteristicEventTypes.CHANGE, listenerCallback);
- characteristic.setValue(VALUE, () => {
- setValueCallback();
+ expect(listenerCallback).toHaveBeenCalledTimes(1)
+ expect(setValueCallback).toHaveBeenCalledTimes(2)
+ })
- expect(listenerCallback).toHaveBeenCalledTimes(1);
- expect(setValueCallback).toHaveBeenCalledTimes(2);
- callback();
- });
- });
- });
-
- it("should call any listeners for the event when the characteristic is event-only, and the value is updated", () => {
- const characteristic = createCharacteristic(Formats.STRING);
+ it('should call any listeners for the event when the characteristic is event-only, and the value is updated', () => {
+ const characteristic = createCharacteristic(Formats.STRING)
// characteristic.eventOnlyCharacteristic = true;
- const VALUE = "NewValue";
- const listenerCallback = jest.fn();
- const updateValueCallback = jest.fn();
-
- characteristic.on(CharacteristicEventTypes.CHANGE, listenerCallback);
- // noinspection JSDeprecatedSymbols
- characteristic.updateValue(VALUE, updateValueCallback);
+ const VALUE = 'NewValue'
+ const listenerCallback = vi.fn()
+ const updateValueCallback = vi.fn()
- expect(listenerCallback).toHaveBeenCalledTimes(1);
- expect(updateValueCallback).toHaveBeenCalledTimes(1);
- });
+ characteristic.on(CharacteristicEventTypes.CHANGE, listenerCallback)
+ characteristic.updateValue(VALUE, updateValueCallback)
- it("should call the change listener with proper context when supplied as second argument to updateValue", () => {
- const characteristic = createCharacteristic(Formats.STRING);
+ expect(listenerCallback).toHaveBeenCalledTimes(1)
+ expect(updateValueCallback).toHaveBeenCalledTimes(1)
+ })
- const VALUE = "NewValue";
- const CONTEXT = "Context";
+ it('should call the change listener with proper context when supplied as second argument to updateValue', () => {
+ const characteristic = createCharacteristic(Formats.STRING)
- const listener = jest.fn().mockImplementation((change: CharacteristicChange) => {
- expect(change.newValue).toEqual(VALUE);
- expect(change.context).toEqual(CONTEXT);
- });
+ const VALUE = 'NewValue'
+ const CONTEXT = 'Context'
- characteristic.on(CharacteristicEventTypes.CHANGE, listener);
- characteristic.updateValue(VALUE, CONTEXT);
+ const listener = vi.fn().mockImplementation((change: CharacteristicChange) => {
+ expect(change.newValue).toEqual(VALUE)
+ expect(change.context).toEqual(CONTEXT)
+ })
- expect(listener).toHaveBeenCalledTimes(1);
- });
+ characteristic.on(CharacteristicEventTypes.CHANGE, listener)
+ characteristic.updateValue(VALUE, CONTEXT)
- it("should call the change listener with proper context when supplied as second argument to setValue", () => {
- const characteristic = createCharacteristic(Formats.STRING);
+ expect(listener).toHaveBeenCalledTimes(1)
+ })
- const VALUE = "NewValue";
- const CONTEXT = "Context";
+ it('should call the change listener with proper context when supplied as second argument to setValue', () => {
+ const characteristic = createCharacteristic(Formats.STRING)
- const listener = jest.fn().mockImplementation((change: CharacteristicChange) => {
- expect(change.newValue).toEqual(VALUE);
- expect(change.context).toEqual(CONTEXT);
- });
+ const VALUE = 'NewValue'
+ const CONTEXT = 'Context'
- characteristic.on(CharacteristicEventTypes.CHANGE, listener);
- characteristic.setValue(VALUE, CONTEXT);
+ const listener = vi.fn().mockImplementation((change: CharacteristicChange) => {
+ expect(change.newValue).toEqual(VALUE)
+ expect(change.context).toEqual(CONTEXT)
+ })
+ characteristic.on(CharacteristicEventTypes.CHANGE, listener)
+ characteristic.setValue(VALUE, CONTEXT)
- expect(listener).toHaveBeenCalledTimes(1);
- });
- });
+ expect(listener).toHaveBeenCalledTimes(1)
+ })
+ })
describe(`@${CharacteristicEventTypes.SUBSCRIBE}`, () => {
+ it('should call any listeners for the event', () => {
+ const characteristic = createCharacteristic(Formats.STRING)
- it("should call any listeners for the event", () => {
- const characteristic = createCharacteristic(Formats.STRING);
+ const cb = vi.fn()
- const cb = jest.fn();
+ characteristic.on(CharacteristicEventTypes.SUBSCRIBE, cb)
+ characteristic.subscribe()
- characteristic.on(CharacteristicEventTypes.SUBSCRIBE, cb);
- characteristic.subscribe();
-
- expect(cb).toHaveBeenCalledTimes(1);
- });
- });
+ expect(cb).toHaveBeenCalledTimes(1)
+ })
+ })
describe(`@${CharacteristicEventTypes.UNSUBSCRIBE}`, () => {
+ it('should call any listeners for the event', () => {
+ const characteristic = createCharacteristic(Formats.STRING)
- it("should call any listeners for the event", () => {
- const characteristic = createCharacteristic(Formats.STRING);
-
- const cb = jest.fn();
+ const cb = vi.fn()
- characteristic.subscribe();
- characteristic.on(CharacteristicEventTypes.UNSUBSCRIBE, cb);
- characteristic.unsubscribe();
+ characteristic.subscribe()
+ characteristic.on(CharacteristicEventTypes.UNSUBSCRIBE, cb)
+ characteristic.unsubscribe()
- expect(cb).toHaveBeenCalledTimes(1);
- });
+ expect(cb).toHaveBeenCalledTimes(1)
+ })
- it("should not call any listeners for the event if none are registered", () => {
- const characteristic = createCharacteristic(Formats.STRING);
+ it('should not call any listeners for the event if none are registered', () => {
+ const characteristic = createCharacteristic(Formats.STRING)
- const cb = jest.fn();
+ const cb = vi.fn()
- characteristic.on(CharacteristicEventTypes.UNSUBSCRIBE, cb);
- characteristic.unsubscribe();
+ characteristic.on(CharacteristicEventTypes.UNSUBSCRIBE, cb)
+ characteristic.unsubscribe()
- expect(cb).not.toHaveBeenCalled();
- });
- });
+ expect(cb).not.toHaveBeenCalled()
+ })
+ })
- describe("#serialize", () => {
- it("should serialize characteristic", () => {
+ describe('#serialize', () => {
+ it('should serialize characteristic', () => {
const props: CharacteristicProps = {
format: Formats.INT,
perms: [Perms.TIMED_WRITE, Perms.PAIRED_READ],
@@ -2057,54 +2063,54 @@ describe("Characteristic", () => {
minValue: 123,
validValueRanges: [123, 1234],
adminOnlyAccess: [Access.WRITE],
- };
+ }
- const characteristic = createCharacteristicWithProps(props, Characteristic.ProgrammableSwitchEvent.UUID);
- characteristic.value = "TestValue";
+ const characteristic = createCharacteristicWithProps(props, Characteristic.ProgrammableSwitchEvent.UUID)
+ characteristic.value = 'TestValue'
- const json = Characteristic.serialize(characteristic);
+ const json = Characteristic.serialize(characteristic)
expect(json).toEqual({
displayName: characteristic.displayName,
UUID: characteristic.UUID,
- props: props,
- value: "TestValue",
+ props,
+ value: 'TestValue',
eventOnlyCharacteristic: true,
- });
- });
+ })
+ })
- it("should serialize characteristic with proper constructor name", () => {
- const characteristic = new Characteristic.Name();
- characteristic.updateValue("New Name!");
+ it('should serialize characteristic with proper constructor name', () => {
+ const characteristic = new Characteristic.Name()
+ characteristic.updateValue('New Name!')
- const json = Characteristic.serialize(characteristic);
+ const json = Characteristic.serialize(characteristic)
expect(json).toEqual({
- displayName: "Name",
- UUID: "00000023-0000-1000-8000-0026BB765291",
+ displayName: 'Name',
+ UUID: '00000023-0000-1000-8000-0026BB765291',
eventOnlyCharacteristic: false,
- constructorName: "Name",
- value: "New Name!",
- props: { format: "string", perms: [ "pr" ], maxLen: 64 },
- });
- });
- });
-
- describe("#deserialize", () => {
- it("should deserialize legacy json from homebridge", () => {
- const json = JSON.parse("{\"displayName\": \"On\", \"UUID\": \"00000025-0000-1000-8000-0026BB765291\", " +
- "\"props\": {\"format\": \"int\", \"unit\": \"seconds\", \"minValue\": 4, \"maxValue\": 6, \"minStep\": 0.1, \"perms\": [\"pr\", \"pw\", \"ev\"]}, " +
- "\"value\": false, \"eventOnlyCharacteristic\": false}");
- const characteristic = Characteristic.deserialize(json);
-
- expect(characteristic.displayName).toEqual(json.displayName);
- expect(characteristic.UUID).toEqual(json.UUID);
- expect(characteristic.props).toEqual(json.props);
- expect(characteristic.value).toEqual(json.value);
- });
-
- it("should deserialize complete json", () => {
+ constructorName: 'Name',
+ value: 'New Name!',
+ props: { format: 'string', perms: ['pr'], maxLen: 64 },
+ })
+ })
+ })
+
+ describe('#deserialize', () => {
+ it('should deserialize legacy json from homebridge', () => {
+ const json = JSON.parse('{"displayName": "On", "UUID": "00000025-0000-1000-8000-0026BB765291", '
+ + '"props": {"format": "int", "unit": "seconds", "minValue": 4, "maxValue": 6, "minStep": 0.1, "perms": ["pr", "pw", "ev"]}, '
+ + '"value": false, "eventOnlyCharacteristic": false}')
+ const characteristic = Characteristic.deserialize(json)
+
+ expect(characteristic.displayName).toEqual(json.displayName)
+ expect(characteristic.UUID).toEqual(json.UUID)
+ expect(characteristic.props).toEqual(json.props)
+ expect(characteristic.value).toEqual(json.value)
+ })
+
+ it('should deserialize complete json', () => {
const json: SerializedCharacteristic = {
- displayName: "MyName",
- UUID: "00000001-0000-1000-8000-0026BB765291",
+ displayName: 'MyName',
+ UUID: '00000001-0000-1000-8000-0026BB765291',
props: {
format: Formats.INT,
perms: [Perms.TIMED_WRITE, Perms.PAIRED_READ],
@@ -2114,33 +2120,31 @@ describe("Characteristic", () => {
validValueRanges: [123, 1234],
adminOnlyAccess: [Access.NOTIFY, Access.READ],
},
- value: "testValue",
+ value: 'testValue',
eventOnlyCharacteristic: false,
- };
+ }
- const characteristic = Characteristic.deserialize(json);
+ const characteristic = Characteristic.deserialize(json)
- expect(characteristic.displayName).toEqual(json.displayName);
- expect(characteristic.UUID).toEqual(json.UUID);
- expect(characteristic.props).toEqual(json.props);
- expect(characteristic.value).toEqual(json.value);
- });
+ expect(characteristic.displayName).toEqual(json.displayName)
+ expect(characteristic.UUID).toEqual(json.UUID)
+ expect(characteristic.props).toEqual(json.props)
+ expect(characteristic.value).toEqual(json.value)
+ })
- it("should deserialize from json with constructor name", () => {
+ it('should deserialize from json with constructor name', () => {
const json: SerializedCharacteristic = {
- displayName: "Name",
- UUID: "00000023-0000-1000-8000-0026BB765291",
+ displayName: 'Name',
+ UUID: '00000023-0000-1000-8000-0026BB765291',
eventOnlyCharacteristic: false,
- constructorName: "Name",
- value: "New Name!",
- props: { format: "string", perms: [ Perms.PAIRED_READ ], maxLen: 64 },
- };
-
- const characteristic = Characteristic.deserialize(json);
-
- expect(characteristic instanceof Characteristic.Name).toBeTruthy();
- });
+ constructorName: 'Name',
+ value: 'New Name!',
+ props: { format: 'string', perms: [Perms.PAIRED_READ], maxLen: 64 },
+ }
- });
+ const characteristic = Characteristic.deserialize(json)
-});
+ expect(characteristic instanceof Characteristic.Name).toBeTruthy()
+ })
+ })
+})
diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts
index c99ea81de..b8e919d8e 100644
--- a/src/lib/Characteristic.ts
+++ b/src/lib/Characteristic.ts
@@ -1,8 +1,4 @@
-import assert from "assert";
-import createDebug from "debug";
-import { EventEmitter } from "events";
-import { CharacteristicJsonObject, CharacteristicValue, Nullable, PartialAllowingNull, VoidCallback } from "../types";
-import { CharacteristicWarningType } from "./Accessory";
+import type { CharacteristicJsonObject, CharacteristicValue, Nullable, PartialAllowingNull, VoidCallback } from '../types'
import type {
AccessCodeControlPoint,
AccessCodeSupportedConfiguration,
@@ -243,13 +239,21 @@ import type {
WiFiCapabilities,
WiFiConfigurationControl,
WiFiSatelliteStatus,
-} from "./definitions";
-import { HAPStatus, IsKnownHAPStatusError } from "./HAPServer";
-import { IdentifierCache } from "./model/IdentifierCache";
-import { clone } from "./util/clone";
-import { HAPConnection } from "./util/eventedhttp";
-import { HapStatusError } from "./util/hapStatusError";
-import { once } from "./util/once";
+} from './definitions'
+import type { IdentifierCache } from './model/IdentifierCache'
+import type { HAPConnection } from './util/eventedhttp'
+
+import assert from 'node:assert'
+import { EventEmitter } from 'node:events'
+
+import createDebug from 'debug'
+
+import { CharacteristicWarningType } from './Accessory.js'
+import { HAPStatus, isKnownHAPStatusError } from './HAPServer.js'
+import { checkName } from './util/checkName.js'
+import { clone } from './util/clone.js'
+import { HapStatusError } from './util/hapStatusError.js'
+import { once } from './util/once.js'
import {
formatOutgoingCharacteristicValue,
isIntegerNumericFormat,
@@ -257,125 +261,126 @@ import {
isUnsignedNumericFormat,
numericLowerBound,
numericUpperBound,
-} from "./util/request-util";
-import { BASE_UUID, toShortForm } from "./util/uuid";
-import { checkName } from "./util/checkName";
+} from './util/request-util.js'
+import { BASE_UUID, toShortForm } from './util/uuid.js'
-const debug = createDebug("HAP-NodeJS:Characteristic");
+const debug = createDebug('HAP-NodeJS:Characteristic')
/**
* @group Characteristic
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum Formats {
- BOOL = "bool",
+ BOOL = 'bool',
/**
* Signed 32-bit integer
*/
- INT = "int", // signed 32-bit int
+ INT = 'int', // signed 32-bit int
/**
* Signed 64-bit floating point
*/
- FLOAT = "float",
+ FLOAT = 'float',
/**
* String encoded in utf8
*/
- STRING = "string",
+ STRING = 'string',
/**
* Unsigned 8-bit integer.
*/
- UINT8 = "uint8",
+ UINT8 = 'uint8',
/**
* Unsigned 16-bit integer.
*/
- UINT16 = "uint16",
+ UINT16 = 'uint16',
/**
* Unsigned 32-bit integer.
*/
- UINT32 = "uint32",
+ UINT32 = 'uint32',
/**
* Unsigned 64-bit integer.
*/
- UINT64 = "uint64",
+ UINT64 = 'uint64',
/**
* Data is base64 encoded string.
*/
- DATA = "data",
+ DATA = 'data',
/**
* Base64 encoded tlv8 string.
*/
- TLV8 = "tlv8"
+ TLV8 = 'tlv8',
}
/**
* @group Characteristic
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum Units {
/**
* Celsius is the only temperature unit in the HomeKit Accessory Protocol.
* Unit conversion is always done on the client side e.g. on the iPhone in the Home App depending on
* the configured unit on the device itself.
*/
- CELSIUS = "celsius",
- PERCENTAGE = "percentage",
- ARC_DEGREE = "arcdegrees",
- LUX = "lux",
- SECONDS = "seconds",
+ CELSIUS = 'celsius',
+ PERCENTAGE = 'percentage',
+ ARC_DEGREE = 'arcdegrees',
+ LUX = 'lux',
+ SECONDS = 'seconds',
}
/**
* @group Characteristic
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum Perms {
- // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
- PAIRED_READ = "pr",
- // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
- PAIRED_WRITE = "pw",
- NOTIFY = "ev",
- // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
- EVENTS = "ev",
- ADDITIONAL_AUTHORIZATION = "aa",
- TIMED_WRITE = "tw",
- HIDDEN = "hd",
- WRITE_RESPONSE = "wr",
+ PAIRED_READ = 'pr',
+ PAIRED_WRITE = 'pw',
+ NOTIFY = 'ev',
+
+ // eslint-disable-next-line ts/no-duplicate-enum-values
+ EVENTS = 'ev',
+ ADDITIONAL_AUTHORIZATION = 'aa',
+ TIMED_WRITE = 'tw',
+ HIDDEN = 'hd',
+ WRITE_RESPONSE = 'wr',
}
/**
* @group Characteristic
*/
export interface CharacteristicProps {
- format: Formats | string;
- perms: Perms[];
- unit?: Units | string;
- description?: string;
+ format: Formats | string
+ perms: Perms[]
+ unit?: Units | string
+ description?: string
/**
* Defines the minimum value for a numeric characteristic
*/
- minValue?: number;
+ minValue?: number
/**
* Defines the maximum value for a numeric characteristic
*/
- maxValue?: number;
- minStep?: number;
+ maxValue?: number
+ minStep?: number
/**
* Maximum number of characters when format is {@link Formats.STRING}.
* Default is 64 characters. Maximum allowed is 256 characters.
*/
- maxLen?: number;
+ maxLen?: number
/**
* Maximum number of characters when format is {@link Formats.DATA}.
* Default is 2097152 characters.
*/
- maxDataLen?: number;
+ maxDataLen?: number
/**
* Defines an array of valid values to be used for the characteristic.
*/
- validValues?: number[];
+ validValues?: number[]
/**
* Two element array where the first value specifies the lowest valid value and
* the second element specifies the highest valid value.
*/
- validValueRanges?: [min: number, max: number];
- adminOnlyAccess?: Access[];
+ validValueRanges?: [min: number, max: number]
+ adminOnlyAccess?: Access[]
}
/**
@@ -386,43 +391,45 @@ export interface CharacteristicProps {
*
* @group Characteristic
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum Access {
READ = 0x00,
WRITE = 0x01,
- NOTIFY = 0x02
+ NOTIFY = 0x02,
}
/**
* @group Characteristic
*/
-export type CharacteristicChange = {
- originator?: HAPConnection,
- newValue: Nullable;
- oldValue: Nullable;
- reason: ChangeReason,
- context?: CharacteristicContext;
-};
+export interface CharacteristicChange {
+ originator?: HAPConnection
+ newValue: Nullable
+ oldValue: Nullable
+ reason: ChangeReason
+ context?: CharacteristicContext
+}
/**
* @group Characteristic
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum ChangeReason {
/**
* Reason used when HomeKit writes a value or the API user calls {@link Characteristic.setValue}.
*/
- WRITE = "write",
+ WRITE = 'write',
/**
* Reason used when the API user calls the method {@link Characteristic.updateValue}.
*/
- UPDATE = "update",
+ UPDATE = 'update',
/**
* Used when HomeKit reads a value or the API user calls the deprecated method `Characteristic.getValue`.
*/
- READ = "read",
+ READ = 'read',
/**
* Used when call to {@link Characteristic.sendEventNotification} was made.
*/
- EVENT = "event",
+ EVENT = 'event',
}
/**
@@ -438,25 +445,26 @@ export interface CharacteristicOperationContext {
* the Accessory won't send any event notifications to HomeKit controllers
* for that particular change.
*/
- omitEventUpdate?: boolean;
+ omitEventUpdate?: boolean
}
/**
* @group Characteristic
*/
export interface SerializedCharacteristic {
- displayName: string,
- UUID: string,
- eventOnlyCharacteristic: boolean,
- constructorName?: string,
+ displayName: string
+ UUID: string
+ eventOnlyCharacteristic: boolean
+ constructorName?: string
- value: Nullable,
- props: CharacteristicProps,
+ value: Nullable
+ props: CharacteristicProps
}
/**
* @group Characteristic
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum CharacteristicEventTypes {
/**
* This event is thrown when a HomeKit controller wants to read the current value of the characteristic.
@@ -464,191 +472,188 @@ export const enum CharacteristicEventTypes {
*
* HAP-NodeJS will complain about slow running get handlers after 3 seconds and terminate the request after 10 seconds.
*/
- GET = "get",
+ GET = 'get',
/**
* This event is thrown when a HomeKit controller wants to write a new value to the characteristic.
* The event handler should call the supplied callback as fast as possible.
*
* HAP-NodeJS will complain about slow running set handlers after 3 seconds and terminate the request after 10 seconds.
*/
- SET = "set",
+ SET = 'set',
/**
* Emitted after a new value is set for the characteristic.
* The new value can be set via a request by a HomeKit controller or via an API call.
*/
- CHANGE = "change",
+ CHANGE = 'change',
/**
* @private
*/
- SUBSCRIBE = "subscribe",
+ SUBSCRIBE = 'subscribe',
/**
* @private
*/
- UNSUBSCRIBE = "unsubscribe",
+ UNSUBSCRIBE = 'unsubscribe',
/**
* @private
*/
- CHARACTERISTIC_WARNING = "characteristic-warning",
+ CHARACTERISTIC_WARNING = 'characteristic-warning',
}
/**
* @group Characteristic
*/
-export type CharacteristicContext = any; // eslint-disable-line @typescript-eslint/no-explicit-any
+export type CharacteristicContext = any
/**
* @group Characteristic
*/
-export type CharacteristicGetCallback = (status?: HAPStatus | null | Error, value?: Nullable) => void;
+export type CharacteristicGetCallback = (status?: HAPStatus | null | Error, value?: Nullable) => void
/**
* @group Characteristic
*/
-export type CharacteristicSetCallback = (error?: HAPStatus | null | Error, writeResponse?: Nullable) => void;
+export type CharacteristicSetCallback = (error?: HAPStatus | null | Error, writeResponse?: Nullable) => void
/**
* @group Characteristic
*/
export type CharacteristicGetHandler = (context: CharacteristicContext, connection?: HAPConnection)
- => Promise> | Nullable;
+=> Promise> | Nullable
/**
* @group Characteristic
*/
export type CharacteristicSetHandler = (value: CharacteristicValue, context: CharacteristicContext, connection?: HAPConnection)
- => Promise | void> | Nullable | void;
+=> Promise | void> | Nullable | void
/**
* @group Characteristic
*/
-export type AdditionalAuthorizationHandler = (additionalAuthorizationData: string | undefined) => boolean;
+export type AdditionalAuthorizationHandler = (additionalAuthorizationData: string | undefined) => boolean
/**
* @group Characteristic
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export declare interface Characteristic {
-
- on(event: "get", listener: (callback: CharacteristicGetCallback, context: CharacteristicContext, connection?: HAPConnection) => void): this;
+ /* eslint-disable ts/method-signature-style */
+ on(event: 'get', listener: (callback: CharacteristicGetCallback, context: CharacteristicContext, connection?: HAPConnection) => void): this
on(
- event: "set",
+ event: 'set',
listener: (value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicContext, connection?: HAPConnection) => void
): this
- on(event: "change", listener: (change: CharacteristicChange) => void): this;
+ on(event: 'change', listener: (change: CharacteristicChange) => void): this
/**
* @private
*/
- on(event: "subscribe", listener: VoidCallback): this;
+ on(event: 'subscribe', listener: VoidCallback): this
/**
* @private
*/
- on(event: "unsubscribe", listener: VoidCallback): this;
+ on(event: 'unsubscribe', listener: VoidCallback): this
/**
* @private
*/
- on(event: "characteristic-warning", listener: (type: CharacteristicWarningType, message: string, stack?: string) => void): this;
-
+ on(event: 'characteristic-warning', listener: (type: CharacteristicWarningType, message: string, stack?: string) => void): this
/**
* @private
*/
- emit(event: "get", callback: CharacteristicGetCallback, context: CharacteristicContext, connection?: HAPConnection): boolean;
+ emit(event: 'get', callback: CharacteristicGetCallback, context: CharacteristicContext, connection?: HAPConnection): boolean
/**
* @private
*/
- emit(event: "set", value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicContext, connection?: HAPConnection): boolean;
+ emit(event: 'set', value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicContext, connection?: HAPConnection): boolean
/**
* @private
*/
- emit(event: "change", change: CharacteristicChange): boolean;
+ emit(event: 'change', change: CharacteristicChange): boolean
/**
* @private
*/
- emit(event: "subscribe"): boolean;
+ emit(event: 'subscribe'): boolean
/**
* @private
*/
- emit(event: "unsubscribe"): boolean;
+ emit(event: 'unsubscribe'): boolean
/**
* @private
*/
- emit(event: "characteristic-warning", type: CharacteristicWarningType, message: string, stack?: string): boolean;
-
+ emit(event: 'characteristic-warning', type: CharacteristicWarningType, message: string, stack?: string): boolean
+ /* eslint-enable ts/method-signature-style */
}
/**
* @group Characteristic
*/
class ValidValuesIterable implements Iterable {
-
- private readonly props: CharacteristicProps;
+ private readonly props: CharacteristicProps
constructor(props: CharacteristicProps) {
- assert(isNumericFormat(props.format), "Cannot instantiate valid values iterable when format is not numeric. Found " + props.format);
- this.props = props;
+ assert(isNumericFormat(props.format), `Cannot instantiate valid values iterable when format is not numeric. Found ${props.format}`)
+ this.props = props
}
*[Symbol.iterator](): Iterator {
if (this.props.validValues) {
for (const value of this.props.validValues) {
- yield value;
+ yield value
}
} else {
- let min = 0; // default is zero for all the uint types
- let max: number;
- let stepValue = 1;
+ let min = 0 // default is zero for all the uint types
+ let max: number
+ let stepValue = 1
if (this.props.validValueRanges) {
- min = this.props.validValueRanges[0];
- max = this.props.validValueRanges[1];
+ min = this.props.validValueRanges[0]
+ max = this.props.validValueRanges[1]
} else if (this.props.minValue != null && this.props.maxValue != null) {
- min = this.props.minValue;
- max = this.props.maxValue;
+ min = this.props.minValue
+ max = this.props.maxValue
if (this.props.minStep != null) {
- stepValue = this.props.minStep;
+ stepValue = this.props.minStep
}
} else if (isUnsignedNumericFormat(this.props.format)) {
- max = numericUpperBound(this.props.format);
+ max = numericUpperBound(this.props.format)
} else {
- throw new Error("Could not find valid iterator strategy for props: " + JSON.stringify(this.props));
+ throw new Error(`Could not find valid iterator strategy for props: ${JSON.stringify(this.props)}`)
}
for (let i = min; i <= max; i += stepValue) {
- yield i;
+ yield i
}
}
}
-
}
-const numberPattern = /^-?\d+$/;
+const numberPattern = /^-?\d+$/
function extractHAPStatusFromError(error: Error) {
- let errorValue = HAPStatus.SERVICE_COMMUNICATION_FAILURE;
+ let errorValue = HAPStatus.SERVICE_COMMUNICATION_FAILURE
if (numberPattern.test(error.message)) {
- const value = parseInt(error.message, 10);
+ const value = Number.parseInt(error.message, 10)
- if (IsKnownHAPStatusError(value)) {
- errorValue = value;
+ if (isKnownHAPStatusError(value)) {
+ errorValue = value
}
}
- return errorValue;
+ return errorValue
}
function maxWithUndefined(a?: number, b?: number): number | undefined {
if (a == null) {
- return b;
+ return b
} else if (b == null) {
- return a;
+ return a
} else {
- return Math.max(a, b);
+ return Math.max(a, b)
}
}
function minWithUndefined(a?: number, b?: number): number | undefined {
if (a == null) {
- return b;
+ return b
} else if (b == null) {
- return a;
+ return a
} else {
- return Math.min(a, b);
+ return Math.min(a, b)
}
}
@@ -664,1006 +669,1005 @@ function minWithUndefined(a?: number, b?: number): number | undefined {
*
* @group Characteristic
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export class Characteristic extends EventEmitter {
-
// Pattern below is for automatic detection of the section of defined characteristics. Used by the generator
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-
/**
* @group Characteristic Definitions
*/
- public static AccessCodeControlPoint: typeof AccessCodeControlPoint;
+ public static AccessCodeControlPoint: typeof AccessCodeControlPoint
/**
* @group Characteristic Definitions
*/
- public static AccessCodeSupportedConfiguration: typeof AccessCodeSupportedConfiguration;
+ public static AccessCodeSupportedConfiguration: typeof AccessCodeSupportedConfiguration
/**
* @group Characteristic Definitions
*/
- public static AccessControlLevel: typeof AccessControlLevel;
+ public static AccessControlLevel: typeof AccessControlLevel
/**
* @group Characteristic Definitions
*/
- public static AccessoryFlags: typeof AccessoryFlags;
+ public static AccessoryFlags: typeof AccessoryFlags
/**
* @group Characteristic Definitions
*/
- public static AccessoryIdentifier: typeof AccessoryIdentifier;
+ public static AccessoryIdentifier: typeof AccessoryIdentifier
/**
* @group Characteristic Definitions
*/
- public static Active: typeof Active;
+ public static Active: typeof Active
/**
* @group Characteristic Definitions
*/
- public static ActiveIdentifier: typeof ActiveIdentifier;
+ public static ActiveIdentifier: typeof ActiveIdentifier
/**
* @group Characteristic Definitions
*/
- public static ActivityInterval: typeof ActivityInterval;
+ public static ActivityInterval: typeof ActivityInterval
/**
* @group Characteristic Definitions
*/
- public static AdministratorOnlyAccess: typeof AdministratorOnlyAccess;
+ public static AdministratorOnlyAccess: typeof AdministratorOnlyAccess
/**
* @group Characteristic Definitions
*/
- public static AirParticulateDensity: typeof AirParticulateDensity;
+ public static AirParticulateDensity: typeof AirParticulateDensity
/**
* @group Characteristic Definitions
*/
- public static AirParticulateSize: typeof AirParticulateSize;
+ public static AirParticulateSize: typeof AirParticulateSize
/**
* @group Characteristic Definitions
*/
- public static AirPlayEnable: typeof AirPlayEnable;
+ public static AirPlayEnable: typeof AirPlayEnable
/**
* @group Characteristic Definitions
*/
- public static AirQuality: typeof AirQuality;
+ public static AirQuality: typeof AirQuality
/**
* @group Characteristic Definitions
*/
- public static AppMatchingIdentifier: typeof AppMatchingIdentifier;
+ public static AppMatchingIdentifier: typeof AppMatchingIdentifier
/**
* @group Characteristic Definitions
*/
- public static AssetUpdateReadiness: typeof AssetUpdateReadiness;
+ public static AssetUpdateReadiness: typeof AssetUpdateReadiness
/**
* @group Characteristic Definitions
*/
- public static AudioFeedback: typeof AudioFeedback;
+ public static AudioFeedback: typeof AudioFeedback
/**
* @group Characteristic Definitions
*/
- public static BatteryLevel: typeof BatteryLevel;
+ public static BatteryLevel: typeof BatteryLevel
/**
* @group Characteristic Definitions
*/
- public static Brightness: typeof Brightness;
+ public static Brightness: typeof Brightness
/**
* @group Characteristic Definitions
*/
- public static ButtonEvent: typeof ButtonEvent;
+ public static ButtonEvent: typeof ButtonEvent
/**
* @group Characteristic Definitions
*/
- public static CameraOperatingModeIndicator: typeof CameraOperatingModeIndicator;
+ public static CameraOperatingModeIndicator: typeof CameraOperatingModeIndicator
/**
* @group Characteristic Definitions
*/
- public static CarbonDioxideDetected: typeof CarbonDioxideDetected;
+ public static CarbonDioxideDetected: typeof CarbonDioxideDetected
/**
* @group Characteristic Definitions
*/
- public static CarbonDioxideLevel: typeof CarbonDioxideLevel;
+ public static CarbonDioxideLevel: typeof CarbonDioxideLevel
/**
* @group Characteristic Definitions
*/
- public static CarbonDioxidePeakLevel: typeof CarbonDioxidePeakLevel;
+ public static CarbonDioxidePeakLevel: typeof CarbonDioxidePeakLevel
/**
* @group Characteristic Definitions
*/
- public static CarbonMonoxideDetected: typeof CarbonMonoxideDetected;
+ public static CarbonMonoxideDetected: typeof CarbonMonoxideDetected
/**
* @group Characteristic Definitions
*/
- public static CarbonMonoxideLevel: typeof CarbonMonoxideLevel;
+ public static CarbonMonoxideLevel: typeof CarbonMonoxideLevel
/**
* @group Characteristic Definitions
*/
- public static CarbonMonoxidePeakLevel: typeof CarbonMonoxidePeakLevel;
+ public static CarbonMonoxidePeakLevel: typeof CarbonMonoxidePeakLevel
/**
* @group Characteristic Definitions
*/
- public static CCAEnergyDetectThreshold: typeof CCAEnergyDetectThreshold;
+ public static CCAEnergyDetectThreshold: typeof CCAEnergyDetectThreshold
/**
* @group Characteristic Definitions
*/
- public static CCASignalDetectThreshold: typeof CCASignalDetectThreshold;
+ public static CCASignalDetectThreshold: typeof CCASignalDetectThreshold
/**
* @group Characteristic Definitions
*/
- public static CharacteristicValueActiveTransitionCount: typeof CharacteristicValueActiveTransitionCount;
+ public static CharacteristicValueActiveTransitionCount: typeof CharacteristicValueActiveTransitionCount
/**
* @group Characteristic Definitions
*/
- public static CharacteristicValueTransitionControl: typeof CharacteristicValueTransitionControl;
+ public static CharacteristicValueTransitionControl: typeof CharacteristicValueTransitionControl
/**
* @group Characteristic Definitions
*/
- public static ChargingState: typeof ChargingState;
+ public static ChargingState: typeof ChargingState
/**
* @group Characteristic Definitions
*/
- public static ClosedCaptions: typeof ClosedCaptions;
+ public static ClosedCaptions: typeof ClosedCaptions
/**
* @group Characteristic Definitions
*/
- public static ColorTemperature: typeof ColorTemperature;
+ public static ColorTemperature: typeof ColorTemperature
/**
* @group Characteristic Definitions
*/
- public static ConfigurationState: typeof ConfigurationState;
+ public static ConfigurationState: typeof ConfigurationState
/**
* @group Characteristic Definitions
*/
- public static ConfiguredName: typeof ConfiguredName;
+ public static ConfiguredName: typeof ConfiguredName
/**
* @group Characteristic Definitions
*/
- public static ContactSensorState: typeof ContactSensorState;
+ public static ContactSensorState: typeof ContactSensorState
/**
* @group Characteristic Definitions
*/
- public static CoolingThresholdTemperature: typeof CoolingThresholdTemperature;
+ public static CoolingThresholdTemperature: typeof CoolingThresholdTemperature
/**
* @group Characteristic Definitions
*/
- public static CryptoHash: typeof CryptoHash;
+ public static CryptoHash: typeof CryptoHash
/**
* @group Characteristic Definitions
*/
- public static CurrentAirPurifierState: typeof CurrentAirPurifierState;
+ public static CurrentAirPurifierState: typeof CurrentAirPurifierState
/**
* @group Characteristic Definitions
*/
- public static CurrentAmbientLightLevel: typeof CurrentAmbientLightLevel;
+ public static CurrentAmbientLightLevel: typeof CurrentAmbientLightLevel
/**
* @group Characteristic Definitions
*/
- public static CurrentDoorState: typeof CurrentDoorState;
+ public static CurrentDoorState: typeof CurrentDoorState
/**
* @group Characteristic Definitions
*/
- public static CurrentFanState: typeof CurrentFanState;
+ public static CurrentFanState: typeof CurrentFanState
/**
* @group Characteristic Definitions
*/
- public static CurrentHeaterCoolerState: typeof CurrentHeaterCoolerState;
+ public static CurrentHeaterCoolerState: typeof CurrentHeaterCoolerState
/**
* @group Characteristic Definitions
*/
- public static CurrentHeatingCoolingState: typeof CurrentHeatingCoolingState;
+ public static CurrentHeatingCoolingState: typeof CurrentHeatingCoolingState
/**
* @group Characteristic Definitions
*/
- public static CurrentHorizontalTiltAngle: typeof CurrentHorizontalTiltAngle;
+ public static CurrentHorizontalTiltAngle: typeof CurrentHorizontalTiltAngle
/**
* @group Characteristic Definitions
*/
- public static CurrentHumidifierDehumidifierState: typeof CurrentHumidifierDehumidifierState;
+ public static CurrentHumidifierDehumidifierState: typeof CurrentHumidifierDehumidifierState
/**
* @group Characteristic Definitions
*/
- public static CurrentMediaState: typeof CurrentMediaState;
+ public static CurrentMediaState: typeof CurrentMediaState
/**
* @group Characteristic Definitions
*/
- public static CurrentPosition: typeof CurrentPosition;
+ public static CurrentPosition: typeof CurrentPosition
/**
* @group Characteristic Definitions
*/
- public static CurrentRelativeHumidity: typeof CurrentRelativeHumidity;
+ public static CurrentRelativeHumidity: typeof CurrentRelativeHumidity
/**
* @group Characteristic Definitions
*/
- public static CurrentSlatState: typeof CurrentSlatState;
+ public static CurrentSlatState: typeof CurrentSlatState
/**
* @group Characteristic Definitions
*/
- public static CurrentTemperature: typeof CurrentTemperature;
+ public static CurrentTemperature: typeof CurrentTemperature
/**
* @group Characteristic Definitions
*/
- public static CurrentTiltAngle: typeof CurrentTiltAngle;
+ public static CurrentTiltAngle: typeof CurrentTiltAngle
/**
* @group Characteristic Definitions
*/
- public static CurrentTransport: typeof CurrentTransport;
+ public static CurrentTransport: typeof CurrentTransport
/**
* @group Characteristic Definitions
*/
- public static CurrentVerticalTiltAngle: typeof CurrentVerticalTiltAngle;
+ public static CurrentVerticalTiltAngle: typeof CurrentVerticalTiltAngle
/**
* @group Characteristic Definitions
*/
- public static CurrentVisibilityState: typeof CurrentVisibilityState;
+ public static CurrentVisibilityState: typeof CurrentVisibilityState
/**
* @group Characteristic Definitions
*/
- public static DataStreamHAPTransport: typeof DataStreamHAPTransport;
+ public static DataStreamHAPTransport: typeof DataStreamHAPTransport
/**
* @group Characteristic Definitions
*/
- public static DataStreamHAPTransportInterrupt: typeof DataStreamHAPTransportInterrupt;
+ public static DataStreamHAPTransportInterrupt: typeof DataStreamHAPTransportInterrupt
/**
* @group Characteristic Definitions
*/
- public static DiagonalFieldOfView: typeof DiagonalFieldOfView;
+ public static DiagonalFieldOfView: typeof DiagonalFieldOfView
/**
* @group Characteristic Definitions
*/
- public static DigitalZoom: typeof DigitalZoom;
+ public static DigitalZoom: typeof DigitalZoom
/**
* @group Characteristic Definitions
*/
- public static DisplayOrder: typeof DisplayOrder;
+ public static DisplayOrder: typeof DisplayOrder
/**
* @group Characteristic Definitions
*/
- public static EventRetransmissionMaximum: typeof EventRetransmissionMaximum;
+ public static EventRetransmissionMaximum: typeof EventRetransmissionMaximum
/**
* @group Characteristic Definitions
*/
- public static EventSnapshotsActive: typeof EventSnapshotsActive;
+ public static EventSnapshotsActive: typeof EventSnapshotsActive
/**
* @group Characteristic Definitions
*/
- public static EventTransmissionCounters: typeof EventTransmissionCounters;
+ public static EventTransmissionCounters: typeof EventTransmissionCounters
/**
* @group Characteristic Definitions
*/
- public static FilterChangeIndication: typeof FilterChangeIndication;
+ public static FilterChangeIndication: typeof FilterChangeIndication
/**
* @group Characteristic Definitions
*/
- public static FilterLifeLevel: typeof FilterLifeLevel;
+ public static FilterLifeLevel: typeof FilterLifeLevel
/**
* @group Characteristic Definitions
*/
- public static FirmwareRevision: typeof FirmwareRevision;
+ public static FirmwareRevision: typeof FirmwareRevision
/**
* @group Characteristic Definitions
*/
- public static FirmwareUpdateReadiness: typeof FirmwareUpdateReadiness;
+ public static FirmwareUpdateReadiness: typeof FirmwareUpdateReadiness
/**
* @group Characteristic Definitions
*/
- public static FirmwareUpdateStatus: typeof FirmwareUpdateStatus;
+ public static FirmwareUpdateStatus: typeof FirmwareUpdateStatus
/**
* @group Characteristic Definitions
*/
- public static HardwareFinish: typeof HardwareFinish;
+ public static HardwareFinish: typeof HardwareFinish
/**
* @group Characteristic Definitions
*/
- public static HardwareRevision: typeof HardwareRevision;
+ public static HardwareRevision: typeof HardwareRevision
/**
* @group Characteristic Definitions
*/
- public static HeartBeat: typeof HeartBeat;
+ public static HeartBeat: typeof HeartBeat
/**
* @group Characteristic Definitions
*/
- public static HeatingThresholdTemperature: typeof HeatingThresholdTemperature;
+ public static HeatingThresholdTemperature: typeof HeatingThresholdTemperature
/**
* @group Characteristic Definitions
*/
- public static HoldPosition: typeof HoldPosition;
+ public static HoldPosition: typeof HoldPosition
/**
* @group Characteristic Definitions
*/
- public static HomeKitCameraActive: typeof HomeKitCameraActive;
+ public static HomeKitCameraActive: typeof HomeKitCameraActive
/**
* @group Characteristic Definitions
*/
- public static Hue: typeof Hue;
+ public static Hue: typeof Hue
/**
* @group Characteristic Definitions
*/
- public static Identifier: typeof Identifier;
+ public static Identifier: typeof Identifier
/**
* @group Characteristic Definitions
*/
- public static Identify: typeof Identify;
+ public static Identify: typeof Identify
/**
* @group Characteristic Definitions
*/
- public static ImageMirroring: typeof ImageMirroring;
+ public static ImageMirroring: typeof ImageMirroring
/**
* @group Characteristic Definitions
*/
- public static ImageRotation: typeof ImageRotation;
+ public static ImageRotation: typeof ImageRotation
/**
* @group Characteristic Definitions
*/
- public static InputDeviceType: typeof InputDeviceType;
+ public static InputDeviceType: typeof InputDeviceType
/**
* @group Characteristic Definitions
*/
- public static InputSourceType: typeof InputSourceType;
+ public static InputSourceType: typeof InputSourceType
/**
* @group Characteristic Definitions
*/
- public static InUse: typeof InUse;
+ public static InUse: typeof InUse
/**
* @group Characteristic Definitions
*/
- public static IsConfigured: typeof IsConfigured;
+ public static IsConfigured: typeof IsConfigured
/**
* @group Characteristic Definitions
*/
- public static LeakDetected: typeof LeakDetected;
+ public static LeakDetected: typeof LeakDetected
/**
* @group Characteristic Definitions
*/
- public static ListPairings: typeof ListPairings;
+ public static ListPairings: typeof ListPairings
/**
* @group Characteristic Definitions
*/
- public static LockControlPoint: typeof LockControlPoint;
+ public static LockControlPoint: typeof LockControlPoint
/**
* @group Characteristic Definitions
*/
- public static LockCurrentState: typeof LockCurrentState;
+ public static LockCurrentState: typeof LockCurrentState
/**
* @group Characteristic Definitions
*/
- public static LockLastKnownAction: typeof LockLastKnownAction;
+ public static LockLastKnownAction: typeof LockLastKnownAction
/**
* @group Characteristic Definitions
*/
- public static LockManagementAutoSecurityTimeout: typeof LockManagementAutoSecurityTimeout;
+ public static LockManagementAutoSecurityTimeout: typeof LockManagementAutoSecurityTimeout
/**
* @group Characteristic Definitions
*/
- public static LockPhysicalControls: typeof LockPhysicalControls;
+ public static LockPhysicalControls: typeof LockPhysicalControls
/**
* @group Characteristic Definitions
*/
- public static LockTargetState: typeof LockTargetState;
+ public static LockTargetState: typeof LockTargetState
/**
* @group Characteristic Definitions
*/
- public static Logs: typeof Logs;
+ public static Logs: typeof Logs
/**
* @group Characteristic Definitions
*/
- public static MACRetransmissionMaximum: typeof MACRetransmissionMaximum;
+ public static MACRetransmissionMaximum: typeof MACRetransmissionMaximum
/**
* @group Characteristic Definitions
*/
- public static MACTransmissionCounters: typeof MACTransmissionCounters;
+ public static MACTransmissionCounters: typeof MACTransmissionCounters
/**
* @group Characteristic Definitions
*/
- public static ManagedNetworkEnable: typeof ManagedNetworkEnable;
+ public static ManagedNetworkEnable: typeof ManagedNetworkEnable
/**
* @group Characteristic Definitions
*/
- public static ManuallyDisabled: typeof ManuallyDisabled;
+ public static ManuallyDisabled: typeof ManuallyDisabled
/**
* @group Characteristic Definitions
*/
- public static Manufacturer: typeof Manufacturer;
+ public static Manufacturer: typeof Manufacturer
/**
* @group Characteristic Definitions
*/
- public static MatterFirmwareRevisionNumber: typeof MatterFirmwareRevisionNumber;
+ public static MatterFirmwareRevisionNumber: typeof MatterFirmwareRevisionNumber
/**
* @group Characteristic Definitions
*/
- public static MatterFirmwareUpdateStatus: typeof MatterFirmwareUpdateStatus;
+ public static MatterFirmwareUpdateStatus: typeof MatterFirmwareUpdateStatus
/**
* @group Characteristic Definitions
*/
- public static MaximumTransmitPower: typeof MaximumTransmitPower;
+ public static MaximumTransmitPower: typeof MaximumTransmitPower
/**
* @group Characteristic Definitions
*/
- public static MetricsBufferFullState: typeof MetricsBufferFullState;
+ public static MetricsBufferFullState: typeof MetricsBufferFullState
/**
* @group Characteristic Definitions
*/
- public static Model: typeof Model;
+ public static Model: typeof Model
/**
* @group Characteristic Definitions
*/
- public static MotionDetected: typeof MotionDetected;
+ public static MotionDetected: typeof MotionDetected
/**
* @group Characteristic Definitions
*/
- public static MultifunctionButton: typeof MultifunctionButton;
+ public static MultifunctionButton: typeof MultifunctionButton
/**
* @group Characteristic Definitions
*/
- public static Mute: typeof Mute;
+ public static Mute: typeof Mute
/**
* @group Characteristic Definitions
*/
- public static Name: typeof Name;
+ public static Name: typeof Name
/**
* @group Characteristic Definitions
*/
- public static NetworkAccessViolationControl: typeof NetworkAccessViolationControl;
+ public static NetworkAccessViolationControl: typeof NetworkAccessViolationControl
/**
* @group Characteristic Definitions
*/
- public static NetworkClientProfileControl: typeof NetworkClientProfileControl;
+ public static NetworkClientProfileControl: typeof NetworkClientProfileControl
/**
* @group Characteristic Definitions
*/
- public static NetworkClientStatusControl: typeof NetworkClientStatusControl;
+ public static NetworkClientStatusControl: typeof NetworkClientStatusControl
/**
* @group Characteristic Definitions
*/
- public static NFCAccessControlPoint: typeof NFCAccessControlPoint;
+ public static NFCAccessControlPoint: typeof NFCAccessControlPoint
/**
* @group Characteristic Definitions
*/
- public static NFCAccessSupportedConfiguration: typeof NFCAccessSupportedConfiguration;
+ public static NFCAccessSupportedConfiguration: typeof NFCAccessSupportedConfiguration
/**
* @group Characteristic Definitions
*/
- public static NightVision: typeof NightVision;
+ public static NightVision: typeof NightVision
/**
* @group Characteristic Definitions
*/
- public static NitrogenDioxideDensity: typeof NitrogenDioxideDensity;
+ public static NitrogenDioxideDensity: typeof NitrogenDioxideDensity
/**
* @group Characteristic Definitions
*/
- public static ObstructionDetected: typeof ObstructionDetected;
+ public static ObstructionDetected: typeof ObstructionDetected
/**
* @group Characteristic Definitions
*/
- public static OccupancyDetected: typeof OccupancyDetected;
+ public static OccupancyDetected: typeof OccupancyDetected
/**
* @group Characteristic Definitions
*/
- public static On: typeof On;
+ public static On: typeof On
/**
* @group Characteristic Definitions
*/
- public static OperatingStateResponse: typeof OperatingStateResponse;
+ public static OperatingStateResponse: typeof OperatingStateResponse
/**
* @group Characteristic Definitions
*/
- public static OpticalZoom: typeof OpticalZoom;
+ public static OpticalZoom: typeof OpticalZoom
/**
* @group Characteristic Definitions
*/
- public static OutletInUse: typeof OutletInUse;
+ public static OutletInUse: typeof OutletInUse
/**
* @group Characteristic Definitions
*/
- public static OzoneDensity: typeof OzoneDensity;
+ public static OzoneDensity: typeof OzoneDensity
/**
* @group Characteristic Definitions
*/
- public static PairingFeatures: typeof PairingFeatures;
+ public static PairingFeatures: typeof PairingFeatures
/**
* @group Characteristic Definitions
*/
- public static PairSetup: typeof PairSetup;
+ public static PairSetup: typeof PairSetup
/**
* @group Characteristic Definitions
*/
- public static PairVerify: typeof PairVerify;
+ public static PairVerify: typeof PairVerify
/**
* @group Characteristic Definitions
*/
- public static PasswordSetting: typeof PasswordSetting;
+ public static PasswordSetting: typeof PasswordSetting
/**
* @group Characteristic Definitions
*/
- public static PeriodicSnapshotsActive: typeof PeriodicSnapshotsActive;
+ public static PeriodicSnapshotsActive: typeof PeriodicSnapshotsActive
/**
* @group Characteristic Definitions
*/
- public static PictureMode: typeof PictureMode;
+ public static PictureMode: typeof PictureMode
/**
* @group Characteristic Definitions
*/
- public static Ping: typeof Ping;
+ public static Ping: typeof Ping
/**
* @group Characteristic Definitions
*/
- public static PM10Density: typeof PM10Density;
+ public static PM10Density: typeof PM10Density
/**
* @group Characteristic Definitions
*/
- public static PM2_5Density: typeof PM2_5Density;
+ public static PM2_5Density: typeof PM2_5Density
/**
* @group Characteristic Definitions
*/
- public static PositionState: typeof PositionState;
+ public static PositionState: typeof PositionState
/**
* @group Characteristic Definitions
*/
- public static PowerModeSelection: typeof PowerModeSelection;
+ public static PowerModeSelection: typeof PowerModeSelection
/**
* @group Characteristic Definitions
*/
- public static ProductData: typeof ProductData;
+ public static ProductData: typeof ProductData
/**
* @group Characteristic Definitions
*/
- public static ProgrammableSwitchEvent: typeof ProgrammableSwitchEvent;
+ public static ProgrammableSwitchEvent: typeof ProgrammableSwitchEvent
/**
* @group Characteristic Definitions
*/
- public static ProgrammableSwitchOutputState: typeof ProgrammableSwitchOutputState;
+ public static ProgrammableSwitchOutputState: typeof ProgrammableSwitchOutputState
/**
* @group Characteristic Definitions
*/
- public static ProgramMode: typeof ProgramMode;
+ public static ProgramMode: typeof ProgramMode
/**
* @group Characteristic Definitions
*/
- public static ReceivedSignalStrengthIndication: typeof ReceivedSignalStrengthIndication;
+ public static ReceivedSignalStrengthIndication: typeof ReceivedSignalStrengthIndication
/**
* @group Characteristic Definitions
*/
- public static ReceiverSensitivity: typeof ReceiverSensitivity;
+ public static ReceiverSensitivity: typeof ReceiverSensitivity
/**
* @group Characteristic Definitions
*/
- public static RecordingAudioActive: typeof RecordingAudioActive;
+ public static RecordingAudioActive: typeof RecordingAudioActive
/**
* @group Characteristic Definitions
*/
- public static RelativeHumidityDehumidifierThreshold: typeof RelativeHumidityDehumidifierThreshold;
+ public static RelativeHumidityDehumidifierThreshold: typeof RelativeHumidityDehumidifierThreshold
/**
* @group Characteristic Definitions
*/
- public static RelativeHumidityHumidifierThreshold: typeof RelativeHumidityHumidifierThreshold;
+ public static RelativeHumidityHumidifierThreshold: typeof RelativeHumidityHumidifierThreshold
/**
* @group Characteristic Definitions
*/
- public static RemainingDuration: typeof RemainingDuration;
+ public static RemainingDuration: typeof RemainingDuration
/**
* @group Characteristic Definitions
*/
- public static RemoteKey: typeof RemoteKey;
+ public static RemoteKey: typeof RemoteKey
/**
* @group Characteristic Definitions
*/
- public static ResetFilterIndication: typeof ResetFilterIndication;
+ public static ResetFilterIndication: typeof ResetFilterIndication
/**
* @group Characteristic Definitions
*/
- public static RotationDirection: typeof RotationDirection;
+ public static RotationDirection: typeof RotationDirection
/**
* @group Characteristic Definitions
*/
- public static RotationSpeed: typeof RotationSpeed;
+ public static RotationSpeed: typeof RotationSpeed
/**
* @group Characteristic Definitions
*/
- public static RouterStatus: typeof RouterStatus;
+ public static RouterStatus: typeof RouterStatus
/**
* @group Characteristic Definitions
*/
- public static Saturation: typeof Saturation;
+ public static Saturation: typeof Saturation
/**
* @group Characteristic Definitions
*/
- public static SecuritySystemAlarmType: typeof SecuritySystemAlarmType;
+ public static SecuritySystemAlarmType: typeof SecuritySystemAlarmType
/**
* @group Characteristic Definitions
*/
- public static SecuritySystemCurrentState: typeof SecuritySystemCurrentState;
+ public static SecuritySystemCurrentState: typeof SecuritySystemCurrentState
/**
* @group Characteristic Definitions
*/
- public static SecuritySystemTargetState: typeof SecuritySystemTargetState;
+ public static SecuritySystemTargetState: typeof SecuritySystemTargetState
/**
* @group Characteristic Definitions
*/
- public static SelectedAudioStreamConfiguration: typeof SelectedAudioStreamConfiguration;
+ public static SelectedAudioStreamConfiguration: typeof SelectedAudioStreamConfiguration
/**
* @group Characteristic Definitions
*/
- public static SelectedCameraRecordingConfiguration: typeof SelectedCameraRecordingConfiguration;
+ public static SelectedCameraRecordingConfiguration: typeof SelectedCameraRecordingConfiguration
/**
* @group Characteristic Definitions
*/
- public static SelectedDiagnosticsModes: typeof SelectedDiagnosticsModes;
+ public static SelectedDiagnosticsModes: typeof SelectedDiagnosticsModes
/**
* @group Characteristic Definitions
*/
- public static SelectedRTPStreamConfiguration: typeof SelectedRTPStreamConfiguration;
+ public static SelectedRTPStreamConfiguration: typeof SelectedRTPStreamConfiguration
/**
* @group Characteristic Definitions
*/
- public static SelectedSleepConfiguration: typeof SelectedSleepConfiguration;
+ public static SelectedSleepConfiguration: typeof SelectedSleepConfiguration
/**
* @group Characteristic Definitions
*/
- public static SerialNumber: typeof SerialNumber;
+ public static SerialNumber: typeof SerialNumber
/**
* @group Characteristic Definitions
*/
- public static ServiceLabelIndex: typeof ServiceLabelIndex;
+ public static ServiceLabelIndex: typeof ServiceLabelIndex
/**
* @group Characteristic Definitions
*/
- public static ServiceLabelNamespace: typeof ServiceLabelNamespace;
+ public static ServiceLabelNamespace: typeof ServiceLabelNamespace
/**
* @group Characteristic Definitions
*/
- public static SetDuration: typeof SetDuration;
+ public static SetDuration: typeof SetDuration
/**
* @group Characteristic Definitions
*/
- public static SetupDataStreamTransport: typeof SetupDataStreamTransport;
+ public static SetupDataStreamTransport: typeof SetupDataStreamTransport
/**
* @group Characteristic Definitions
*/
- public static SetupEndpoints: typeof SetupEndpoints;
+ public static SetupEndpoints: typeof SetupEndpoints
/**
* @group Characteristic Definitions
*/
- public static SetupTransferTransport: typeof SetupTransferTransport;
+ public static SetupTransferTransport: typeof SetupTransferTransport
/**
* @group Characteristic Definitions
*/
- public static SignalToNoiseRatio: typeof SignalToNoiseRatio;
+ public static SignalToNoiseRatio: typeof SignalToNoiseRatio
/**
* @group Characteristic Definitions
*/
- public static SiriEnable: typeof SiriEnable;
+ public static SiriEnable: typeof SiriEnable
/**
* @group Characteristic Definitions
*/
- public static SiriEndpointSessionStatus: typeof SiriEndpointSessionStatus;
+ public static SiriEndpointSessionStatus: typeof SiriEndpointSessionStatus
/**
* @group Characteristic Definitions
*/
- public static SiriEngineVersion: typeof SiriEngineVersion;
+ public static SiriEngineVersion: typeof SiriEngineVersion
/**
* @group Characteristic Definitions
*/
- public static SiriInputType: typeof SiriInputType;
+ public static SiriInputType: typeof SiriInputType
/**
* @group Characteristic Definitions
*/
- public static SiriLightOnUse: typeof SiriLightOnUse;
+ public static SiriLightOnUse: typeof SiriLightOnUse
/**
* @group Characteristic Definitions
*/
- public static SiriListening: typeof SiriListening;
+ public static SiriListening: typeof SiriListening
/**
* @group Characteristic Definitions
*/
- public static SiriTouchToUse: typeof SiriTouchToUse;
+ public static SiriTouchToUse: typeof SiriTouchToUse
/**
* @group Characteristic Definitions
*/
- public static SlatType: typeof SlatType;
+ public static SlatType: typeof SlatType
/**
* @group Characteristic Definitions
*/
- public static SleepDiscoveryMode: typeof SleepDiscoveryMode;
+ public static SleepDiscoveryMode: typeof SleepDiscoveryMode
/**
* @group Characteristic Definitions
*/
- public static SleepInterval: typeof SleepInterval;
+ public static SleepInterval: typeof SleepInterval
/**
* @group Characteristic Definitions
*/
- public static SmokeDetected: typeof SmokeDetected;
+ public static SmokeDetected: typeof SmokeDetected
/**
* @group Characteristic Definitions
*/
- public static SoftwareRevision: typeof SoftwareRevision;
+ public static SoftwareRevision: typeof SoftwareRevision
/**
* @group Characteristic Definitions
*/
- public static StagedFirmwareVersion: typeof StagedFirmwareVersion;
+ public static StagedFirmwareVersion: typeof StagedFirmwareVersion
/**
* @group Characteristic Definitions
*/
- public static StatusActive: typeof StatusActive;
+ public static StatusActive: typeof StatusActive
/**
* @group Characteristic Definitions
*/
- public static StatusFault: typeof StatusFault;
+ public static StatusFault: typeof StatusFault
/**
* @group Characteristic Definitions
*/
- public static StatusJammed: typeof StatusJammed;
+ public static StatusJammed: typeof StatusJammed
/**
* @group Characteristic Definitions
*/
- public static StatusLowBattery: typeof StatusLowBattery;
+ public static StatusLowBattery: typeof StatusLowBattery
/**
* @group Characteristic Definitions
*/
- public static StatusTampered: typeof StatusTampered;
+ public static StatusTampered: typeof StatusTampered
/**
* @group Characteristic Definitions
*/
- public static StreamingStatus: typeof StreamingStatus;
+ public static StreamingStatus: typeof StreamingStatus
/**
* @group Characteristic Definitions
*/
- public static SulphurDioxideDensity: typeof SulphurDioxideDensity;
+ public static SulphurDioxideDensity: typeof SulphurDioxideDensity
/**
* @group Characteristic Definitions
*/
- public static SupportedAssetTypes: typeof SupportedAssetTypes;
+ public static SupportedAssetTypes: typeof SupportedAssetTypes
/**
* @group Characteristic Definitions
*/
- public static SupportedAudioRecordingConfiguration: typeof SupportedAudioRecordingConfiguration;
+ public static SupportedAudioRecordingConfiguration: typeof SupportedAudioRecordingConfiguration
/**
* @group Characteristic Definitions
*/
- public static SupportedAudioStreamConfiguration: typeof SupportedAudioStreamConfiguration;
+ public static SupportedAudioStreamConfiguration: typeof SupportedAudioStreamConfiguration
/**
* @group Characteristic Definitions
*/
- public static SupportedCameraRecordingConfiguration: typeof SupportedCameraRecordingConfiguration;
+ public static SupportedCameraRecordingConfiguration: typeof SupportedCameraRecordingConfiguration
/**
* @group Characteristic Definitions
*/
- public static SupportedCharacteristicValueTransitionConfiguration: typeof SupportedCharacteristicValueTransitionConfiguration;
+ public static SupportedCharacteristicValueTransitionConfiguration: typeof SupportedCharacteristicValueTransitionConfiguration
/**
* @group Characteristic Definitions
*/
- public static SupportedDataStreamTransportConfiguration: typeof SupportedDataStreamTransportConfiguration;
+ public static SupportedDataStreamTransportConfiguration: typeof SupportedDataStreamTransportConfiguration
/**
* @group Characteristic Definitions
*/
- public static SupportedDiagnosticsModes: typeof SupportedDiagnosticsModes;
+ public static SupportedDiagnosticsModes: typeof SupportedDiagnosticsModes
/**
* @group Characteristic Definitions
*/
- public static SupportedDiagnosticsSnapshot: typeof SupportedDiagnosticsSnapshot;
+ public static SupportedDiagnosticsSnapshot: typeof SupportedDiagnosticsSnapshot
/**
* @group Characteristic Definitions
*/
- public static SupportedFirmwareUpdateConfiguration: typeof SupportedFirmwareUpdateConfiguration;
+ public static SupportedFirmwareUpdateConfiguration: typeof SupportedFirmwareUpdateConfiguration
/**
* @group Characteristic Definitions
*/
- public static SupportedMetrics: typeof SupportedMetrics;
+ public static SupportedMetrics: typeof SupportedMetrics
/**
* @group Characteristic Definitions
*/
- public static SupportedRouterConfiguration: typeof SupportedRouterConfiguration;
+ public static SupportedRouterConfiguration: typeof SupportedRouterConfiguration
/**
* @group Characteristic Definitions
*/
- public static SupportedRTPConfiguration: typeof SupportedRTPConfiguration;
+ public static SupportedRTPConfiguration: typeof SupportedRTPConfiguration
/**
* @group Characteristic Definitions
*/
- public static SupportedSleepConfiguration: typeof SupportedSleepConfiguration;
+ public static SupportedSleepConfiguration: typeof SupportedSleepConfiguration
/**
* @group Characteristic Definitions
*/
- public static SupportedTransferTransportConfiguration: typeof SupportedTransferTransportConfiguration;
+ public static SupportedTransferTransportConfiguration: typeof SupportedTransferTransportConfiguration
/**
* @group Characteristic Definitions
*/
- public static SupportedVideoRecordingConfiguration: typeof SupportedVideoRecordingConfiguration;
+ public static SupportedVideoRecordingConfiguration: typeof SupportedVideoRecordingConfiguration
/**
* @group Characteristic Definitions
*/
- public static SupportedVideoStreamConfiguration: typeof SupportedVideoStreamConfiguration;
+ public static SupportedVideoStreamConfiguration: typeof SupportedVideoStreamConfiguration
/**
* @group Characteristic Definitions
*/
- public static SwingMode: typeof SwingMode;
+ public static SwingMode: typeof SwingMode
/**
* @group Characteristic Definitions
*/
- public static TapType: typeof TapType;
+ public static TapType: typeof TapType
/**
* @group Characteristic Definitions
*/
- public static TargetAirPurifierState: typeof TargetAirPurifierState;
+ public static TargetAirPurifierState: typeof TargetAirPurifierState
/**
* @group Characteristic Definitions
*/
- public static TargetControlList: typeof TargetControlList;
+ public static TargetControlList: typeof TargetControlList
/**
* @group Characteristic Definitions
*/
- public static TargetControlSupportedConfiguration: typeof TargetControlSupportedConfiguration;
+ public static TargetControlSupportedConfiguration: typeof TargetControlSupportedConfiguration
/**
* @group Characteristic Definitions
*/
- public static TargetDoorState: typeof TargetDoorState;
+ public static TargetDoorState: typeof TargetDoorState
/**
* @group Characteristic Definitions
*/
- public static TargetFanState: typeof TargetFanState;
+ public static TargetFanState: typeof TargetFanState
/**
* @group Characteristic Definitions
*/
- public static TargetHeaterCoolerState: typeof TargetHeaterCoolerState;
+ public static TargetHeaterCoolerState: typeof TargetHeaterCoolerState
/**
* @group Characteristic Definitions
*/
- public static TargetHeatingCoolingState: typeof TargetHeatingCoolingState;
+ public static TargetHeatingCoolingState: typeof TargetHeatingCoolingState
/**
* @group Characteristic Definitions
*/
- public static TargetHorizontalTiltAngle: typeof TargetHorizontalTiltAngle;
+ public static TargetHorizontalTiltAngle: typeof TargetHorizontalTiltAngle
/**
* @group Characteristic Definitions
*/
- public static TargetHumidifierDehumidifierState: typeof TargetHumidifierDehumidifierState;
+ public static TargetHumidifierDehumidifierState: typeof TargetHumidifierDehumidifierState
/**
* @group Characteristic Definitions
*/
- public static TargetMediaState: typeof TargetMediaState;
+ public static TargetMediaState: typeof TargetMediaState
/**
* @group Characteristic Definitions
*/
- public static TargetPosition: typeof TargetPosition;
+ public static TargetPosition: typeof TargetPosition
/**
* @group Characteristic Definitions
*/
- public static TargetRelativeHumidity: typeof TargetRelativeHumidity;
+ public static TargetRelativeHumidity: typeof TargetRelativeHumidity
/**
* @group Characteristic Definitions
*/
- public static TargetTemperature: typeof TargetTemperature;
+ public static TargetTemperature: typeof TargetTemperature
/**
* @group Characteristic Definitions
*/
- public static TargetTiltAngle: typeof TargetTiltAngle;
+ public static TargetTiltAngle: typeof TargetTiltAngle
/**
* @group Characteristic Definitions
*/
- public static TargetVerticalTiltAngle: typeof TargetVerticalTiltAngle;
+ public static TargetVerticalTiltAngle: typeof TargetVerticalTiltAngle
/**
* @group Characteristic Definitions
*/
- public static TargetVisibilityState: typeof TargetVisibilityState;
+ public static TargetVisibilityState: typeof TargetVisibilityState
/**
* @group Characteristic Definitions
*/
- public static TemperatureDisplayUnits: typeof TemperatureDisplayUnits;
+ public static TemperatureDisplayUnits: typeof TemperatureDisplayUnits
/**
* @group Characteristic Definitions
*/
- public static ThirdPartyCameraActive: typeof ThirdPartyCameraActive;
+ public static ThirdPartyCameraActive: typeof ThirdPartyCameraActive
/**
* @group Characteristic Definitions
*/
- public static ThreadControlPoint: typeof ThreadControlPoint;
+ public static ThreadControlPoint: typeof ThreadControlPoint
/**
* @group Characteristic Definitions
*/
- public static ThreadNodeCapabilities: typeof ThreadNodeCapabilities;
+ public static ThreadNodeCapabilities: typeof ThreadNodeCapabilities
/**
* @group Characteristic Definitions
*/
- public static ThreadOpenThreadVersion: typeof ThreadOpenThreadVersion;
+ public static ThreadOpenThreadVersion: typeof ThreadOpenThreadVersion
/**
* @group Characteristic Definitions
*/
- public static ThreadStatus: typeof ThreadStatus;
+ public static ThreadStatus: typeof ThreadStatus
/**
* @group Characteristic Definitions
*/
- public static Token: typeof Token;
+ public static Token: typeof Token
/**
* @group Characteristic Definitions
*/
- public static TransmitPower: typeof TransmitPower;
+ public static TransmitPower: typeof TransmitPower
/**
* @group Characteristic Definitions
*/
- public static ValveType: typeof ValveType;
+ public static ValveType: typeof ValveType
/**
* @group Characteristic Definitions
*/
- public static Version: typeof Version;
+ public static Version: typeof Version
/**
* @group Characteristic Definitions
*/
- public static VideoAnalysisActive: typeof VideoAnalysisActive;
+ public static VideoAnalysisActive: typeof VideoAnalysisActive
/**
* @group Characteristic Definitions
*/
- public static VOCDensity: typeof VOCDensity;
+ public static VOCDensity: typeof VOCDensity
/**
* @group Characteristic Definitions
*/
- public static Volume: typeof Volume;
+ public static Volume: typeof Volume
/**
* @group Characteristic Definitions
*/
- public static VolumeControlType: typeof VolumeControlType;
+ public static VolumeControlType: typeof VolumeControlType
/**
* @group Characteristic Definitions
*/
- public static VolumeSelector: typeof VolumeSelector;
+ public static VolumeSelector: typeof VolumeSelector
/**
* @group Characteristic Definitions
*/
- public static WakeConfiguration: typeof WakeConfiguration;
+ public static WakeConfiguration: typeof WakeConfiguration
/**
* @group Characteristic Definitions
*/
- public static WANConfigurationList: typeof WANConfigurationList;
+ public static WANConfigurationList: typeof WANConfigurationList
/**
* @group Characteristic Definitions
*/
- public static WANStatusList: typeof WANStatusList;
+ public static WANStatusList: typeof WANStatusList
/**
* @group Characteristic Definitions
*/
- public static WaterLevel: typeof WaterLevel;
+ public static WaterLevel: typeof WaterLevel
/**
* @group Characteristic Definitions
*/
- public static WiFiCapabilities: typeof WiFiCapabilities;
+ public static WiFiCapabilities: typeof WiFiCapabilities
/**
* @group Characteristic Definitions
*/
- public static WiFiConfigurationControl: typeof WiFiConfigurationControl;
+ public static WiFiConfigurationControl: typeof WiFiConfigurationControl
/**
* @group Characteristic Definitions
*/
- public static WiFiSatelliteStatus: typeof WiFiSatelliteStatus;
+ public static WiFiSatelliteStatus: typeof WiFiSatelliteStatus
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// NOTICE: when adding/changing properties, remember to possibly adjust the serialize/deserialize functions
- public displayName: string;
- public UUID: string;
- iid: Nullable = null;
- value: Nullable = null;
+ public displayName: string
+ public UUID: string
+ iid: Nullable = null
+ value: Nullable = null
/**
* @private
*/
- statusCode: HAPStatus = HAPStatus.SUCCESS;
- props: CharacteristicProps;
+ statusCode: HAPStatus = HAPStatus.SUCCESS
+ props: CharacteristicProps
/**
* The {@link Characteristic.onGet} handler
*/
- private getHandler?: CharacteristicGetHandler;
+ private getHandler?: CharacteristicGetHandler
/**
* The {@link Characteristic.onSet} handler
*/
- private setHandler?: CharacteristicSetHandler;
+ private setHandler?: CharacteristicSetHandler
- private subscriptions = 0;
+ private subscriptions = 0
/**
* @private
*/
- additionalAuthorizationHandler?: AdditionalAuthorizationHandler;
+ additionalAuthorizationHandler?: AdditionalAuthorizationHandler
public constructor(displayName: string, UUID: string, props: CharacteristicProps) {
- super();
- this.displayName = displayName;
- this.UUID = UUID;
+ super()
+ this.displayName = displayName
+ this.UUID = UUID
this.props = { // some weird defaults (with legacy constructor props was optional)
format: Formats.INT,
perms: [Perms.NOTIFY],
- };
+ }
- this.setProps(props || {}); // ensure sanity checks are called
+ this.setProps(props || {}) // ensure sanity checks are called
}
/**
@@ -1680,20 +1684,20 @@ export class Characteristic extends EventEmitter {
* @param handler
*/
public onGet(handler: CharacteristicGetHandler): Characteristic {
- if (typeof handler !== "function") {
- this.characteristicWarning(".onGet handler must be a function");
- return this;
+ if (typeof handler !== 'function') {
+ this.characteristicWarning('.onGet handler must be a function')
+ return this
}
- this.getHandler = handler;
- return this;
+ this.getHandler = handler
+ return this
}
/**
* Removes the {@link CharacteristicGetHandler} handler which was configured using {@link onGet}.
*/
public removeOnGet(): Characteristic {
- this.getHandler = undefined;
- return this;
+ this.getHandler = undefined
+ return this
}
/**
@@ -1711,20 +1715,20 @@ export class Characteristic extends EventEmitter {
* @param handler
*/
public onSet(handler: CharacteristicSetHandler): Characteristic {
- if (typeof handler !== "function") {
- this.characteristicWarning(".onSet handler must be a function");
- return this;
+ if (typeof handler !== 'function') {
+ this.characteristicWarning('.onSet handler must be a function')
+ return this
}
- this.setHandler = handler;
- return this;
+ this.setHandler = handler
+ return this
}
/**
* Removes the {@link CharacteristicSetHandler} which was configured using {@link onSet}.
*/
public removeOnSet(): Characteristic {
- this.setHandler = undefined;
- return this;
+ this.setHandler = undefined
+ return this
}
/**
@@ -1735,177 +1739,176 @@ export class Characteristic extends EventEmitter {
* @param props - Partial properties object with the desired updates.
*/
public setProps(props: PartialAllowingNull): Characteristic {
- assert(props, "props cannot be undefined when setting props");
+ assert(props, 'props cannot be undefined when setting props')
// TODO calling setProps after publish doesn't lead to a increment in the current configuration number
- let formatDidChange = false;
+ let formatDidChange = false
// for every value "null" can be used to reset props, except for required props
if (props.format) {
- formatDidChange = this.props.format !== props.format;
- this.props.format = props.format;
+ formatDidChange = this.props.format !== props.format
+ this.props.format = props.format
}
if (props.perms) {
- assert(props.perms.length > 0, "characteristic prop perms cannot be empty array");
- this.props.perms = props.perms;
+ assert(props.perms.length > 0, 'characteristic prop perms cannot be empty array')
+ this.props.perms = props.perms
}
if (props.unit !== undefined) {
- this.props.unit = props.unit != null? props.unit: undefined;
+ this.props.unit = props.unit != null ? props.unit : undefined
}
if (props.description !== undefined) {
- this.props.description = props.description != null? props.description: undefined;
+ this.props.description = props.description != null ? props.description : undefined
}
// check minValue is valid for the format type
if (props.minValue !== undefined) {
if (props.minValue === null) {
- props.minValue = undefined;
+ props.minValue = undefined
} else if (!isNumericFormat(this.props.format)) {
this.characteristicWarning(
- "Characteristic Property 'minValue' can only be set for characteristics with numeric format, but not for " + this.props.format,
+ `Characteristic Property 'minValue' can only be set for characteristics with numeric format, but not for ${this.props.format}`,
CharacteristicWarningType.ERROR_MESSAGE,
- );
- props.minValue = undefined;
- } else if (typeof (props.minValue as unknown) !== "number" || !Number.isFinite(props.minValue)) {
+ )
+ props.minValue = undefined
+ } else if (typeof (props.minValue as unknown) !== 'number' || !Number.isFinite(props.minValue)) {
this.characteristicWarning(
`Characteristic Property 'minValue' must be a finite number, received "${props.minValue}" (${typeof props.minValue})`,
CharacteristicWarningType.ERROR_MESSAGE,
- );
- props.minValue = undefined;
+ )
+ props.minValue = undefined
} else {
if (props.minValue < numericLowerBound(this.props.format)) {
this.characteristicWarning(
- "Characteristic Property 'minValue' was set to " + props.minValue + ", but for numeric format " +
- this.props.format + " minimum possible is " + numericLowerBound(this.props.format),
+ `Characteristic Property 'minValue' was set to ${props.minValue}, but for numeric format ${
+ this.props.format} minimum possible is ${numericLowerBound(this.props.format)}`,
CharacteristicWarningType.ERROR_MESSAGE,
- );
- props.minValue = numericLowerBound(this.props.format);
+ )
+ props.minValue = numericLowerBound(this.props.format)
} else if (props.minValue > numericUpperBound(this.props.format)) {
this.characteristicWarning(
- "Characteristic Property 'minValue' was set to " + props.minValue + ", but for numeric format " +
- this.props.format + " maximum possible is " + numericUpperBound(this.props.format),
+ `Characteristic Property 'minValue' was set to ${props.minValue}, but for numeric format ${
+ this.props.format} maximum possible is ${numericUpperBound(this.props.format)}`,
CharacteristicWarningType.ERROR_MESSAGE,
- );
- props.minValue = numericLowerBound(this.props.format);
+ )
+ props.minValue = numericLowerBound(this.props.format)
}
}
- this.props.minValue = props.minValue;
+ this.props.minValue = props.minValue
}
// check maxValue is valid for the format type
if (props.maxValue !== undefined) {
if (props.maxValue === null) {
- props.maxValue = undefined;
+ props.maxValue = undefined
} else if (!isNumericFormat(this.props.format)) {
this.characteristicWarning(
- "Characteristic Property 'maxValue' can only be set for characteristics with numeric format, but not for " + this.props.format,
+ `Characteristic Property 'maxValue' can only be set for characteristics with numeric format, but not for ${this.props.format}`,
CharacteristicWarningType.ERROR_MESSAGE,
- );
- props.maxValue = undefined;
- } else if (typeof (props.maxValue as unknown) !== "number" || !Number.isFinite(props.maxValue)) {
+ )
+ props.maxValue = undefined
+ } else if (typeof (props.maxValue as unknown) !== 'number' || !Number.isFinite(props.maxValue)) {
this.characteristicWarning(
`Characteristic Property 'maxValue' must be a finite number, received "${props.maxValue}" (${typeof props.maxValue})`,
CharacteristicWarningType.ERROR_MESSAGE,
- );
- props.maxValue = undefined;
+ )
+ props.maxValue = undefined
} else {
if (props.maxValue > numericUpperBound(this.props.format)) {
this.characteristicWarning(
- "Characteristic Property 'maxValue' was set to " + props.maxValue + ", but for numeric format " +
- this.props.format + " maximum possible is " + numericUpperBound(this.props.format),
+ `Characteristic Property 'maxValue' was set to ${props.maxValue}, but for numeric format ${
+ this.props.format} maximum possible is ${numericUpperBound(this.props.format)}`,
CharacteristicWarningType.ERROR_MESSAGE,
- );
- props.maxValue = numericUpperBound(this.props.format);
+ )
+ props.maxValue = numericUpperBound(this.props.format)
} else if (props.maxValue < numericLowerBound(this.props.format)) {
this.characteristicWarning(
- "Characteristic Property 'maxValue' was set to " + props.maxValue + ", but for numeric format " +
- this.props.format + " minimum possible is " + numericLowerBound(this.props.format),
+ `Characteristic Property 'maxValue' was set to ${props.maxValue}, but for numeric format ${
+ this.props.format} minimum possible is ${numericLowerBound(this.props.format)}`,
CharacteristicWarningType.ERROR_MESSAGE,
- );
- props.maxValue = numericUpperBound(this.props.format);
+ )
+ props.maxValue = numericUpperBound(this.props.format)
}
}
- this.props.maxValue = props.maxValue;
+ this.props.maxValue = props.maxValue
}
if (props.minStep !== undefined) {
if (props.minStep === null) {
- this.props.minStep = undefined;
+ this.props.minStep = undefined
} else if (!isNumericFormat(this.props.format)) {
this.characteristicWarning(
- "Characteristic Property `minStep` can only be set for characteristics with numeric format, but not for " + this.props.format,
+ `Characteristic Property \`minStep\` can only be set for characteristics with numeric format, but not for ${this.props.format}`,
CharacteristicWarningType.ERROR_MESSAGE,
- );
+ )
} else {
if (props.minStep < 1 && isIntegerNumericFormat(this.props.format)) {
- this.characteristicWarning("Characteristic Property `minStep` was set to a value lower than 1, " +
- "this will have no effect on format `" + this.props.format);
+ this.characteristicWarning(`Characteristic Property \`minStep\` was set to a value lower than 1, `
+ + `this will have no effect on format \`${this.props.format}`)
}
- this.props.minStep = props.minStep;
+ this.props.minStep = props.minStep
}
}
if (props.maxLen !== undefined) {
if (props.maxLen === null) {
- this.props.maxLen = undefined;
+ this.props.maxLen = undefined
} else if (this.props.format !== Formats.STRING) {
this.characteristicWarning(
- "Characteristic Property `maxLen` can only be set for characteristics with format `STRING`, but not for " + this.props.format,
+ `Characteristic Property \`maxLen\` can only be set for characteristics with format \`STRING\`, but not for ${this.props.format}`,
CharacteristicWarningType.ERROR_MESSAGE,
- );
+ )
} else {
if (props.maxLen > 256) {
- this.characteristicWarning("Characteristic Property string `maxLen` cannot be bigger than 256");
- props.maxLen = 256;
+ this.characteristicWarning('Characteristic Property string `maxLen` cannot be bigger than 256')
+ props.maxLen = 256
}
- this.props.maxLen = props.maxLen;
+ this.props.maxLen = props.maxLen
}
}
if (props.maxDataLen !== undefined) {
if (props.maxDataLen === null) {
- this.props.maxDataLen = undefined;
+ this.props.maxDataLen = undefined
} else if (this.props.format !== Formats.DATA) {
this.characteristicWarning(
- "Characteristic Property `maxDataLen` can only be set for characteristics with format `DATA`, but not for " + this.props.format,
+ `Characteristic Property \`maxDataLen\` can only be set for characteristics with format \`DATA\`, but not for ${this.props.format}`,
CharacteristicWarningType.ERROR_MESSAGE,
- );
+ )
} else {
- this.props.maxDataLen = props.maxDataLen;
+ this.props.maxDataLen = props.maxDataLen
}
}
if (props.validValues !== undefined) {
if (props.validValues === null) {
- this.props.validValues = undefined;
+ this.props.validValues = undefined
} else if (!isNumericFormat(this.props.format)) {
- this.characteristicWarning("Characteristic Property `validValues` was supplied for non numeric format " + this.props.format);
+ this.characteristicWarning(`Characteristic Property \`validValues\` was supplied for non numeric format ${this.props.format}`)
} else {
- assert(props.validValues.length, "characteristic prop validValues cannot be empty array");
- this.props.validValues = props.validValues;
+ assert(props.validValues.length, 'characteristic prop validValues cannot be empty array')
+ this.props.validValues = props.validValues
}
}
if (props.validValueRanges !== undefined) {
if (props.validValueRanges === null) {
- this.props.validValueRanges = undefined;
+ this.props.validValueRanges = undefined
} else if (!isNumericFormat(this.props.format)) {
- this.characteristicWarning("Characteristic Property `validValueRanges` was supplied for non numeric format " + this.props.format);
+ this.characteristicWarning(`Characteristic Property \`validValueRanges\` was supplied for non numeric format ${this.props.format}`)
} else {
- assert(props.validValueRanges.length === 2, "characteristic prop validValueRanges must have a length of 2");
- this.props.validValueRanges = props.validValueRanges;
+ assert(props.validValueRanges.length === 2, 'characteristic prop validValueRanges must have a length of 2')
+ this.props.validValueRanges = props.validValueRanges
}
}
if (props.adminOnlyAccess !== undefined) {
- this.props.adminOnlyAccess = props.adminOnlyAccess != null? props.adminOnlyAccess: undefined;
+ this.props.adminOnlyAccess = props.adminOnlyAccess != null ? props.adminOnlyAccess : undefined
}
-
if (this.props.minValue != null && this.props.maxValue != null) { // the eqeq instead of eqeqeq is important here
if (this.props.minValue > this.props.maxValue) { // see https://github.com/homebridge/HAP-NodeJS/issues/690
- this.props.minValue = undefined;
- this.props.maxValue = undefined;
- throw new Error("Error setting CharacteristicsProps for '" + this.displayName + "': 'minValue' cannot be greater or equal the 'maxValue'!");
+ this.props.minValue = undefined
+ this.props.maxValue = undefined
+ throw new Error(`Error setting CharacteristicsProps for '${this.displayName}': 'minValue' cannot be greater or equal the 'maxValue'!`)
}
}
@@ -1922,15 +1925,15 @@ export class Characteristic extends EventEmitter {
// - Special case for `ProgrammableSwitchEvent` where every change in value is considered an event which would result in ghost button presses
// validateUserInput when called from setProps is intended to clamp value withing allowed range. It is why warnings should not be displayed.
- const correctedValue = this.validateUserInput(this.value, CharacteristicWarningType.DEBUG_MESSAGE);
+ const correctedValue = this.validateUserInput(this.value, CharacteristicWarningType.DEBUG_MESSAGE)
if (correctedValue !== this.value) {
// we don't want to emit a CHANGE event if the value didn't change at all!
- this.updateValue(correctedValue);
+ this.updateValue(correctedValue)
}
}
- return this;
+ return this
}
/**
@@ -1938,11 +1941,11 @@ export class Characteristic extends EventEmitter {
*
* The range of valid values can be defined using three different ways via the {@link CharacteristicProps} object
* (set via the {@link setProps} method):
- * * First method is to specifically list every valid value inside {@link CharacteristicProps.validValues}
- * * Second you can specify a range via {@link CharacteristicProps.minValue} and {@link CharacteristicProps.maxValue} (with optionally defining
+ * First method is to specifically list every valid value inside {@link CharacteristicProps.validValues}
+ * Second you can specify a range via {@link CharacteristicProps.minValue} and {@link CharacteristicProps.maxValue} (with optionally defining
* {@link CharacteristicProps.minStep})
- * * And lastly you can specify a range via {@link CharacteristicProps.validValueRanges}
- * * Implicitly a valid value range is predefined for characteristics with Format {@link Formats.UINT8}, {@link Formats.UINT16},
+ * And lastly you can specify a range via {@link CharacteristicProps.validValueRanges}
+ * Implicitly a valid value range is predefined for characteristics with Format {@link Formats.UINT8}, {@link Formats.UINT16},
* {@link Formats.UINT32} and {@link Formats.UINT64}: starting by zero to their respective maximum number
*
* The method will automatically detect which type of valid values definition is used and provide
@@ -1962,10 +1965,9 @@ export class Characteristic extends EventEmitter {
* ```
*/
public validValuesIterator(): Iterable {
- return new ValidValuesIterable(this.props);
+ return new ValidValuesIterable(this.props)
}
- // noinspection JSUnusedGlobalSymbols
/**
* This method can be used to set up additional authorization for a characteristic.
* For one, it adds the {@link Perms.ADDITIONAL_AUTHORIZATION} permission to the characteristic
@@ -1984,9 +1986,9 @@ export class Characteristic extends EventEmitter {
*/
public setupAdditionalAuthorization(handler: AdditionalAuthorizationHandler): void {
if (!this.props.perms.includes(Perms.ADDITIONAL_AUTHORIZATION)) {
- this.props.perms.push(Perms.ADDITIONAL_AUTHORIZATION);
+ this.props.perms.push(Perms.ADDITIONAL_AUTHORIZATION)
}
- this.additionalAuthorizationHandler = handler;
+ this.additionalAuthorizationHandler = handler
}
/**
@@ -2022,10 +2024,10 @@ export class Characteristic extends EventEmitter {
* @param error - The error object
*
* Note: Erroneous state is never **pushed** to the client side. Only, if the HomeKit client requests the current
- * state of the Characteristic, the corresponding {@link HapStatusError} is returned. As described above,
- * any {@link onGet} or {@link CharacteristicEventTypes.GET} handlers have precedence.
+ * state of the Characteristic, the corresponding {@link HapStatusError} is returned. As described above,
+ * any {@link onGet} or {@link CharacteristicEventTypes.GET} handlers have precedence.
*/
- setValue(error: HapStatusError | Error): Characteristic;
+ setValue(error: HapStatusError | Error): Characteristic
/**
* This updates the value by calling the {@link CharacteristicEventTypes.SET} event handler associated with the characteristic.
* This acts the same way as when a HomeKit controller sends a `/characteristics` request to update the characteristic.
@@ -2040,47 +2042,47 @@ export class Characteristic extends EventEmitter {
*
* Note: If you don't want the {@link CharacteristicEventTypes.SET} to be called, refer to {@link updateValue}.
*/
- setValue(value: CharacteristicValue, context?: CharacteristicContext): Characteristic;
+ setValue(value: CharacteristicValue, context?: CharacteristicContext): Characteristic
setValue(value: CharacteristicValue | Error, callback?: CharacteristicSetCallback, context?: CharacteristicContext): Characteristic {
if (value instanceof Error) {
- this.statusCode = value instanceof HapStatusError? value.hapStatus: extractHAPStatusFromError(value);
+ this.statusCode = value instanceof HapStatusError ? value.hapStatus : extractHAPStatusFromError(value)
if (callback) {
- callback();
+ callback()
}
- return this;
+ return this
}
- if (callback && !context && typeof callback !== "function") {
- context = callback;
- callback = undefined;
+ if (callback && !context && typeof callback !== 'function') {
+ context = callback
+ callback = undefined
}
try {
- value = this.validateUserInput(value)!;
+ value = this.validateUserInput(value)!
} catch (error) {
- this.characteristicWarning(error?.message + "", CharacteristicWarningType.ERROR_MESSAGE, error?.stack);
+ this.characteristicWarning(`${error?.message}`, CharacteristicWarningType.ERROR_MESSAGE, error?.stack)
if (callback) {
- callback(error);
+ callback(error)
}
- return this;
+ return this
}
- this.handleSetRequest(value, undefined, context).then(value => {
+ this.handleSetRequest(value, undefined, context).then((value) => {
if (callback) {
if (value) { // possible write response
- callback(null, value);
+ callback(null, value)
} else {
- callback(null);
+ callback(null)
}
}
- }, reason => {
+ }, (reason) => {
if (callback) {
- callback(reason);
+ callback(reason)
}
- });
+ })
- return this;
+ return this
}
/**
@@ -2091,14 +2093,14 @@ export class Characteristic extends EventEmitter {
*
* Note: Refer to the respective overloads for {@link CharacteristicValue} or {@link HapStatusError} for respective documentation.
*/
- updateValue(value: Nullable | Error | HapStatusError): Characteristic;
+ updateValue(value: Nullable | Error | HapStatusError): Characteristic
/**
* This updates the value of the characteristic. If the value changed, an event notification will be sent to all connected
* HomeKit controllers which are registered to receive event notifications for this characteristic.
*
* @param value - The new value.
*/
- updateValue(value: Nullable): Characteristic;
+ updateValue(value: Nullable): Characteristic
/**
* Sets the state of the characteristic to an errored state.
* If a {@link onGet} or {@link CharacteristicEventTypes.GET} handler is set up,
@@ -2115,10 +2117,10 @@ export class Characteristic extends EventEmitter {
* @param error - The error object
*
* Note: Erroneous state is never **pushed** to the client side. Only, if the HomeKit client requests the current
- * state of the Characteristic, the corresponding {@link HapStatusError} is returned. As described above,
- * any {@link onGet} or {@link CharacteristicEventTypes.GET} handlers have precedence.
+ * state of the Characteristic, the corresponding {@link HapStatusError} is returned. As described above,
+ * any {@link onGet} or {@link CharacteristicEventTypes.GET} handlers have precedence.
*/
- updateValue(error: Error | HapStatusError): Characteristic;
+ updateValue(error: Error | HapStatusError): Characteristic
/**
* This updates the value of the characteristic. If the value changed, an event notification will be sent to all connected
* HomeKit controllers which are registered to receive event notifications for this characteristic.
@@ -2126,44 +2128,44 @@ export class Characteristic extends EventEmitter {
* @param value - The new value.
* @param context - Passed to the {@link CharacteristicEventTypes.CHANGE} event handler.
*/
- updateValue(value: Nullable, context?: CharacteristicContext): Characteristic;
+ updateValue(value: Nullable, context?: CharacteristicContext): Characteristic
updateValue(value: Nullable | Error | HapStatusError, callback?: () => void, context?: CharacteristicContext): Characteristic {
if (value instanceof Error) {
- this.statusCode = value instanceof HapStatusError? value.hapStatus: extractHAPStatusFromError(value);
+ this.statusCode = value instanceof HapStatusError ? value.hapStatus : extractHAPStatusFromError(value)
if (callback) {
- callback();
+ callback()
}
- return this;
+ return this
}
- if (callback && !context && typeof callback !== "function") {
- context = callback;
- callback = undefined;
+ if (callback && !context && typeof callback !== 'function') {
+ context = callback
+ callback = undefined
}
try {
- value = this.validateUserInput(value);
+ value = this.validateUserInput(value)
} catch (error) {
- this.characteristicWarning(error?.message + "", CharacteristicWarningType.ERROR_MESSAGE, error?.stack);
+ this.characteristicWarning(`${error?.message}`, CharacteristicWarningType.ERROR_MESSAGE, error?.stack)
if (callback) {
- callback();
+ callback()
}
- return this;
+ return this
}
- this.statusCode = HAPStatus.SUCCESS;
+ this.statusCode = HAPStatus.SUCCESS
- const oldValue = this.value;
- this.value = value;
+ const oldValue = this.value
+ this.value = value
if (callback) {
- callback();
+ callback()
}
- this.emit(CharacteristicEventTypes.CHANGE, { originator: undefined, oldValue: oldValue, newValue: value, reason: ChangeReason.UPDATE, context: context });
+ this.emit(CharacteristicEventTypes.CHANGE, { originator: undefined, oldValue, newValue: value, reason: ChangeReason.UPDATE, context })
- return this; // for chaining
+ return this // for chaining
}
/**
@@ -2176,89 +2178,86 @@ export class Characteristic extends EventEmitter {
* @param context - Passed to the {@link CharacteristicEventTypes.CHANGE} event handler.
*/
public sendEventNotification(value: CharacteristicValue, context?: CharacteristicContext): Characteristic {
- this.statusCode = HAPStatus.SUCCESS;
+ this.statusCode = HAPStatus.SUCCESS
- value = this.validateUserInput(value)!;
- const oldValue = this.value;
- this.value = value;
+ value = this.validateUserInput(value)!
+ const oldValue = this.value
+ this.value = value
- this.emit(CharacteristicEventTypes.CHANGE, { originator: undefined, oldValue: oldValue, newValue: value, reason: ChangeReason.EVENT, context: context });
+ this.emit(CharacteristicEventTypes.CHANGE, { originator: undefined, oldValue, newValue: value, reason: ChangeReason.EVENT, context })
- return this; // for chaining
+ return this // for chaining
}
/**
* Called when a HAP requests wants to know the current value of the characteristic.
*
* @param connection - The HAP connection from which the request originated from.
- * @param context - Deprecated parameter. There for backwards compatibility.
- * @private Used by the Accessory to load the characteristic value
+ * @param context - Deprecated parameter, is there for backwards compatibility.
+ * @private
*/
async handleGetRequest(connection?: HAPConnection, context?: CharacteristicContext): Promise> {
if (!this.props.perms.includes(Perms.PAIRED_READ)) { // check if we are allowed to read from this characteristic
- throw HAPStatus.WRITE_ONLY_CHARACTERISTIC;
+ throw HAPStatus.WRITE_ONLY_CHARACTERISTIC
}
if (this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) {
// special workaround for event only programmable switch event, which must always return null
- return null;
+ return null
}
if (this.getHandler) {
if (this.listeners(CharacteristicEventTypes.GET).length > 0) {
- this.characteristicWarning("Ignoring on('get') handler as onGet handler was defined instead");
+ this.characteristicWarning('Ignoring on(\'get\') handler as onGet handler was defined instead')
}
try {
- let value = await this.getHandler(context, connection);
- this.statusCode = HAPStatus.SUCCESS;
+ let value = await this.getHandler(context, connection)
+ this.statusCode = HAPStatus.SUCCESS
try {
- value = this.validateUserInput(value);
+ value = this.validateUserInput(value)
} catch (error) {
- this.characteristicWarning(`An illegal value was supplied by the read handler for characteristic: ${error?.message}`,
- CharacteristicWarningType.WARN_MESSAGE, error?.stack);
- this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE;
- return Promise.reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
+ this.characteristicWarning(`An illegal value was supplied by the read handler for characteristic: ${error?.message}`, CharacteristicWarningType.WARN_MESSAGE, error?.stack)
+ this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE
+ return Promise.reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
}
- const oldValue = this.value;
- this.value = value;
+ const oldValue = this.value
+ this.value = value
if (oldValue !== value) { // emit a change event if necessary
this.emit(
CharacteristicEventTypes.CHANGE,
- { originator: connection, oldValue: oldValue, newValue: value, reason: ChangeReason.READ, context: context },
- );
+ { originator: connection, oldValue, newValue: value, reason: ChangeReason.READ, context },
+ )
}
- return value;
+ return value
} catch (error) {
- if (typeof error === "number") {
- const hapStatusError = new HapStatusError(error);
- this.statusCode = hapStatusError.hapStatus;
+ if (typeof error === 'number') {
+ const hapStatusError = new HapStatusError(error)
+ this.statusCode = hapStatusError.hapStatus
} else if (error instanceof HapStatusError) {
- this.statusCode = error.hapStatus;
+ this.statusCode = error.hapStatus
} else {
- this.characteristicWarning(`Unhandled error thrown inside read handler for characteristic: ${error?.message}`,
- CharacteristicWarningType.ERROR_MESSAGE, error?.stack);
- this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE;
+ this.characteristicWarning(`Unhandled error thrown inside read handler for characteristic: ${error?.message}`, CharacteristicWarningType.ERROR_MESSAGE, error?.stack)
+ this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE
}
- throw this.statusCode;
+ throw this.statusCode
}
}
if (this.listeners(CharacteristicEventTypes.GET).length === 0) {
if (this.statusCode) {
- throw this.statusCode;
+ throw this.statusCode
}
try {
- return this.validateUserInput(this.value);
+ return this.validateUserInput(this.value)
} catch (error) {
- this.characteristicWarning(`An illegal value was supplied by setting \`value\` for characteristic: ${error?.message}`,
- CharacteristicWarningType.WARN_MESSAGE, error?.stack);
- return Promise.reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
+ this.characteristicWarning(`An illegal value was supplied by setting \`value\` for characteristic: ${error?.message}`, CharacteristicWarningType.WARN_MESSAGE, error?.stack)
+ return Promise.reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
}
}
@@ -2266,41 +2265,40 @@ export class Characteristic extends EventEmitter {
try {
this.emit(CharacteristicEventTypes.GET, once((status, value) => {
if (status) {
- if (typeof status === "number") {
- const hapStatusError = new HapStatusError(status);
- this.statusCode = hapStatusError.hapStatus;
+ if (typeof status === 'number') {
+ const hapStatusError = new HapStatusError(status)
+ this.statusCode = hapStatusError.hapStatus
} else if (status instanceof HapStatusError) {
- this.statusCode = status.hapStatus;
+ this.statusCode = status.hapStatus
} else {
- debug("[%s] Received error from get handler %s", this.displayName, status.stack);
- this.statusCode = extractHAPStatusFromError(status);
+ debug('[%s] Received error from get handler %s', this.displayName, status.stack)
+ this.statusCode = extractHAPStatusFromError(status)
}
- reject(this.statusCode);
- return;
+ reject(this.statusCode)
+ return
}
- this.statusCode = HAPStatus.SUCCESS;
+ this.statusCode = HAPStatus.SUCCESS
- value = this.validateUserInput(value);
- const oldValue = this.value;
- this.value = value;
+ value = this.validateUserInput(value)
+ const oldValue = this.value
+ this.value = value
- resolve(value);
+ resolve(value)
if (oldValue !== value) { // emit a change event if necessary
this.emit(
CharacteristicEventTypes.CHANGE,
- { originator: connection, oldValue: oldValue, newValue: value, reason: ChangeReason.READ, context: context },
- );
+ { originator: connection, oldValue, newValue: value, reason: ChangeReason.READ, context },
+ )
}
- }), context, connection);
+ }), context, connection)
} catch (error) {
- this.characteristicWarning(`Unhandled error thrown inside read handler for characteristic: ${error?.message}`,
- CharacteristicWarningType.ERROR_MESSAGE, error?.stack);
- this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE;
- reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
+ this.characteristicWarning(`Unhandled error thrown inside read handler for characteristic: ${error?.message}`, CharacteristicWarningType.ERROR_MESSAGE, error?.stack)
+ this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE
+ reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
}
- });
+ })
}
/**
@@ -2308,117 +2306,113 @@ export class Characteristic extends EventEmitter {
*
* @param value - The updated value
* @param connection - The connection from which the request originated from
- * @param context - Deprecated parameter. There for backwards compatibility.
+ * @param context - Deprecated parameter, is there for backwards compatibility.
* @returns Promise resolve to void in normal operation. When characteristic supports write-response, HAP
- * requests a write-response and the set handler returns a write-response value, the respective
- * write response value is resolved.
+ * requests a write-response and the set handler returns a write-response value, the respective
+ * write response value is resolved.
* @private
*/
async handleSetRequest(value: CharacteristicValue, connection?: HAPConnection, context?: CharacteristicContext): Promise {
- this.statusCode = HAPStatus.SUCCESS;
+ this.statusCode = HAPStatus.SUCCESS
if (connection !== undefined) {
// if connection is undefined, the set "request" comes from the setValue method.
// for setValue a value of "null" is allowed and checked via validateUserInput.
try {
- value = this.validateClientSuppliedValue(value);
+ value = this.validateClientSuppliedValue(value)
} catch (e) {
- debug(`[${this.displayName}]`, e.message);
- return Promise.reject(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ debug(`[${this.displayName}]`, e.message)
+ return Promise.reject(HAPStatus.INVALID_VALUE_IN_REQUEST)
}
}
- const oldValue = this.value;
+ const oldValue = this.value
if (this.setHandler) {
if (this.listeners(CharacteristicEventTypes.SET).length > 0) {
- this.characteristicWarning("Ignoring on('set') handler as onSet handler was defined instead");
+ this.characteristicWarning('Ignoring on(\'set\') handler as onSet handler was defined instead')
}
try {
- const writeResponse = await this.setHandler(value, context, connection);
- this.statusCode = HAPStatus.SUCCESS;
+ const writeResponse = await this.setHandler(value, context, connection)
+ this.statusCode = HAPStatus.SUCCESS
if (writeResponse != null && this.props.perms.includes(Perms.WRITE_RESPONSE)) {
- this.value = this.validateUserInput(writeResponse);
- return this.value!;
+ this.value = this.validateUserInput(writeResponse)
+ return this.value!
} else {
if (writeResponse != null) {
- this.characteristicWarning("SET handler returned write response value, though the characteristic doesn't support write response",
- CharacteristicWarningType.DEBUG_MESSAGE);
+ this.characteristicWarning('SET handler returned write response value, though the characteristic doesn\'t support write response', CharacteristicWarningType.DEBUG_MESSAGE)
}
- this.value = value;
+ this.value = value
this.emit(
CharacteristicEventTypes.CHANGE,
- { originator: connection, oldValue: oldValue, newValue: value, reason: ChangeReason.WRITE, context: context },
- );
- return;
+ { originator: connection, oldValue, newValue: value, reason: ChangeReason.WRITE, context },
+ )
+ return
}
} catch (error) {
- if (typeof error === "number") {
- const hapStatusError = new HapStatusError(error);
- this.statusCode = hapStatusError.hapStatus;
+ if (typeof error === 'number') {
+ const hapStatusError = new HapStatusError(error)
+ this.statusCode = hapStatusError.hapStatus
} else if (error instanceof HapStatusError) {
- this.statusCode = error.hapStatus;
+ this.statusCode = error.hapStatus
} else {
- this.characteristicWarning(`Unhandled error thrown inside write handler for characteristic: ${error?.message}`,
- CharacteristicWarningType.ERROR_MESSAGE, error?.stack);
- this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE;
+ this.characteristicWarning(`Unhandled error thrown inside write handler for characteristic: ${error?.message}`, CharacteristicWarningType.ERROR_MESSAGE, error?.stack)
+ this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE
}
- throw this.statusCode;
+ throw this.statusCode
}
}
if (this.listeners(CharacteristicEventTypes.SET).length === 0) {
- this.value = value;
- this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, reason: ChangeReason.WRITE, context: context });
- return Promise.resolve();
+ this.value = value
+ this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue, newValue: value, reason: ChangeReason.WRITE, context })
+ return Promise.resolve()
} else {
return new Promise((resolve, reject) => {
try {
this.emit(CharacteristicEventTypes.SET, value, once((status, writeResponse) => {
if (status) {
- if (typeof status === "number") {
- const hapStatusError = new HapStatusError(status);
- this.statusCode = hapStatusError.hapStatus;
+ if (typeof status === 'number') {
+ const hapStatusError = new HapStatusError(status)
+ this.statusCode = hapStatusError.hapStatus
} else if (status instanceof HapStatusError) {
- this.statusCode = status.hapStatus;
+ this.statusCode = status.hapStatus
} else {
- debug("[%s] Received error from set handler %s", this.displayName, status.stack);
- this.statusCode = extractHAPStatusFromError(status);
+ debug('[%s] Received error from set handler %s', this.displayName, status.stack)
+ this.statusCode = extractHAPStatusFromError(status)
}
- reject(this.statusCode);
- return;
+ reject(this.statusCode)
+ return
}
- this.statusCode = HAPStatus.SUCCESS;
+ this.statusCode = HAPStatus.SUCCESS
if (writeResponse != null && this.props.perms.includes(Perms.WRITE_RESPONSE)) {
// support write response simply by letting the implementor pass the response as second argument to the callback
- this.value = this.validateUserInput(writeResponse);
- resolve(this.value!);
+ this.value = this.validateUserInput(writeResponse)
+ resolve(this.value!)
} else {
if (writeResponse != null) {
- this.characteristicWarning("SET handler returned write response value, though the characteristic doesn't support write response",
- CharacteristicWarningType.DEBUG_MESSAGE);
+ this.characteristicWarning('SET handler returned write response value, though the characteristic doesn\'t support write response', CharacteristicWarningType.DEBUG_MESSAGE)
}
- this.value = value;
- resolve();
+ this.value = value
+ resolve()
this.emit(
CharacteristicEventTypes.CHANGE,
- { originator: connection, oldValue: oldValue, newValue: value, reason: ChangeReason.WRITE, context: context },
- );
+ { originator: connection, oldValue, newValue: value, reason: ChangeReason.WRITE, context },
+ )
}
- }), context, connection);
+ }), context, connection)
} catch (error) {
- this.characteristicWarning(`Unhandled error thrown inside write handler for characteristic: ${error?.message}`,
- CharacteristicWarningType.ERROR_MESSAGE, error?.stack);
- this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE;
- reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
+ this.characteristicWarning(`Unhandled error thrown inside write handler for characteristic: ${error?.message}`, CharacteristicWarningType.ERROR_MESSAGE, error?.stack)
+ this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE
+ reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
}
- });
+ })
}
}
@@ -2428,9 +2422,9 @@ export class Characteristic extends EventEmitter {
*/
subscribe(): void {
if (this.subscriptions === 0) {
- this.emit(CharacteristicEventTypes.SUBSCRIBE);
+ this.emit(CharacteristicEventTypes.SUBSCRIBE)
}
- this.subscriptions++;
+ this.subscriptions++
}
/**
@@ -2439,57 +2433,56 @@ export class Characteristic extends EventEmitter {
* @private
*/
unsubscribe(): void {
- const wasOne = this.subscriptions === 1;
- this.subscriptions--;
- this.subscriptions = Math.max(this.subscriptions, 0);
+ const wasOne = this.subscriptions === 1
+ this.subscriptions--
+ this.subscriptions = Math.max(this.subscriptions, 0)
if (wasOne) {
- this.emit(CharacteristicEventTypes.UNSUBSCRIBE);
+ this.emit(CharacteristicEventTypes.UNSUBSCRIBE)
}
}
protected getDefaultValue(): Nullable {
- // noinspection JSDeprecatedSymbols
switch (this.props.format) {
- case Formats.BOOL:
- return false;
- case Formats.STRING:
- switch (this.UUID) {
- case Characteristic.Manufacturer.UUID:
- return "Default-Manufacturer";
- case Characteristic.Model.UUID:
- return "Default-Model";
- case Characteristic.SerialNumber.UUID:
- return "Default-SerialNumber";
- case Characteristic.FirmwareRevision.UUID:
- return "0.0.0";
- default:
- return "";
- }
- case Formats.DATA:
- return ""; // who knows!
- case Formats.TLV8:
- return ""; // who knows!
- case Formats.INT:
- case Formats.FLOAT:
- case Formats.UINT8:
- case Formats.UINT16:
- case Formats.UINT32:
- case Formats.UINT64:
- switch(this.UUID) {
- case Characteristic.CurrentTemperature.UUID:
- return 0; // some existing integrations expect this to be 0 by default
- default: {
- if (this.props.validValues?.length && typeof this.props.validValues[0] === "number") {
- return this.props.validValues[0];
+ case Formats.BOOL:
+ return false
+ case Formats.STRING:
+ switch (this.UUID) {
+ case Characteristic.Manufacturer.UUID:
+ return 'Default-Manufacturer'
+ case Characteristic.Model.UUID:
+ return 'Default-Model'
+ case Characteristic.SerialNumber.UUID:
+ return 'Default-SerialNumber'
+ case Characteristic.FirmwareRevision.UUID:
+ return '0.0.0'
+ default:
+ return ''
}
- if (typeof this.props.minValue === "number" && Number.isFinite(this.props.minValue)) {
- return this.props.minValue;
+ case Formats.DATA:
+ return '' // who knows!
+ case Formats.TLV8:
+ return '' // who knows!
+ case Formats.INT:
+ case Formats.FLOAT:
+ case Formats.UINT8:
+ case Formats.UINT16:
+ case Formats.UINT32:
+ case Formats.UINT64:
+ switch (this.UUID) {
+ case Characteristic.CurrentTemperature.UUID:
+ return 0 // some existing integrations expect this to be 0 by default
+ default: {
+ if (this.props.validValues?.length && typeof this.props.validValues[0] === 'number') {
+ return this.props.validValues[0]
+ }
+ if (typeof this.props.minValue === 'number' && Number.isFinite(this.props.minValue)) {
+ return this.props.minValue
+ }
+ return 0
+ }
}
- return 0;
- }
- }
- default:
- return 0;
+ default:
+ return 0
}
}
@@ -2501,96 +2494,96 @@ export class Characteristic extends EventEmitter {
*/
private validateClientSuppliedValue(value?: Nullable): CharacteristicValue {
if (value == null) {
- throw new Error(`Client supplied invalid value for ${this.props.format}: ${value}`);
+ throw new Error(`Client supplied invalid value for ${this.props.format}: ${value}`)
}
switch (this.props.format) {
- case Formats.BOOL: {
- if (typeof value === "boolean") {
- return value;
- }
-
- if (typeof value === "number" && (value === 1 || value === 0)) {
- return Boolean(value);
- }
+ case Formats.BOOL: {
+ if (typeof value === 'boolean') {
+ return value
+ }
- throw new Error(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`);
- }
- case Formats.INT:
- case Formats.FLOAT:
- case Formats.UINT8:
- case Formats.UINT16:
- case Formats.UINT32:
- case Formats.UINT64: {
- if (typeof value === "boolean") {
- value = value ? 1 : 0;
- }
+ if (typeof value === 'number' && (value === 1 || value === 0)) {
+ return Boolean(value)
+ }
- if (typeof value !== "number" || !Number.isFinite(value)) {
- throw new Error(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`);
+ throw new Error(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`)
}
+ case Formats.INT:
+ case Formats.FLOAT:
+ case Formats.UINT8:
+ case Formats.UINT16:
+ case Formats.UINT32:
+ case Formats.UINT64: {
+ if (typeof value === 'boolean') {
+ value = value ? 1 : 0
+ }
- const numericMin = maxWithUndefined(this.props.minValue, numericLowerBound(this.props.format));
- const numericMax = minWithUndefined(this.props.maxValue, numericUpperBound(this.props.format));
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
+ throw new TypeError(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`)
+ }
- if (typeof numericMin === "number" && value < numericMin) {
- throw new Error(`Client supplied value of ${value} is less than the minimum allowed value of ${numericMin}`);
- }
+ const numericMin = maxWithUndefined(this.props.minValue, numericLowerBound(this.props.format))
+ const numericMax = minWithUndefined(this.props.maxValue, numericUpperBound(this.props.format))
- if (typeof numericMax === "number" && value > numericMax) {
- throw new Error(`Client supplied value of ${value} is greater than the maximum allowed value of ${numericMax}`);
- }
+ if (typeof numericMin === 'number' && value < numericMin) {
+ throw new Error(`Client supplied value of ${value} is less than the minimum allowed value of ${numericMin}`)
+ }
- if (this.props.validValues && !this.props.validValues.includes(value)) {
- throw new Error(`Client supplied value of ${value} is not in ${this.props.validValues.toString()}`);
- }
+ if (typeof numericMax === 'number' && value > numericMax) {
+ throw new Error(`Client supplied value of ${value} is greater than the maximum allowed value of ${numericMax}`)
+ }
- if (this.props.validValueRanges && this.props.validValueRanges.length === 2) {
- if (value < this.props.validValueRanges[0]) {
- throw new Error(`Client supplied value of ${value} is less than the minimum allowed value of ${this.props.validValueRanges[0]}`);
+ if (this.props.validValues && !this.props.validValues.includes(value)) {
+ throw new Error(`Client supplied value of ${value} is not in ${this.props.validValues.toString()}`)
}
- if (value > this.props.validValueRanges[1]) {
- throw new Error(`Client supplied value of ${value} is greater than the maximum allowed value of ${this.props.validValueRanges[1]}`);
+
+ if (this.props.validValueRanges && this.props.validValueRanges.length === 2) {
+ if (value < this.props.validValueRanges[0]) {
+ throw new Error(`Client supplied value of ${value} is less than the minimum allowed value of ${this.props.validValueRanges[0]}`)
+ }
+ if (value > this.props.validValueRanges[1]) {
+ throw new Error(`Client supplied value of ${value} is greater than the maximum allowed value of ${this.props.validValueRanges[1]}`)
+ }
}
- }
- return value;
- }
- case Formats.STRING: {
- if (typeof value !== "string") {
- throw new Error(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`);
+ return value
}
+ case Formats.STRING: {
+ if (typeof value !== 'string') {
+ throw new TypeError(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`)
+ }
- const maxLength = this.props.maxLen != null? this.props.maxLen: 64; // default is 64; max is 256 which is set in setProps
- if (value.length > maxLength) {
- throw new Error(`Client supplied value length of ${value.length} exceeds maximum length allowed of ${maxLength}`);
- }
+ const maxLength = this.props.maxLen != null ? this.props.maxLen : 64 // default is 64; max is 256 which is set in setProps
+ if (value.length > maxLength) {
+ throw new Error(`Client supplied value length of ${value.length} exceeds maximum length allowed of ${maxLength}`)
+ }
- return value;
- }
- case Formats.DATA: {
- if (typeof value !== "string") {
- throw new Error(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`);
+ return value
}
+ case Formats.DATA: {
+ if (typeof value !== 'string') {
+ throw new TypeError(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`)
+ }
- // we don't validate base64 here
+ // we don't validate base64 here
- const maxLength = this.props.maxDataLen != null? this.props.maxDataLen: 0x200000; // default is 0x200000
- if (value.length > maxLength) {
- throw new Error(`Client supplied value length of ${value.length} exceeds maximum length allowed of ${maxLength}`);
- }
+ const maxLength = this.props.maxDataLen != null ? this.props.maxDataLen : 0x200000 // default is 0x200000
+ if (value.length > maxLength) {
+ throw new Error(`Client supplied value length of ${value.length} exceeds maximum length allowed of ${maxLength}`)
+ }
- return value;
- }
- case Formats.TLV8:
- if (typeof value !== "string") {
- throw new Error(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`);
+ return value
}
+ case Formats.TLV8:
+ if (typeof value !== 'string') {
+ throw new TypeError(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`)
+ }
- return value;
+ return value
}
- return value;
+ return value
}
/**
@@ -2604,13 +2597,12 @@ export class Characteristic extends EventEmitter {
private validateUserInput(value?: Nullable, warningType = CharacteristicWarningType.WARN_MESSAGE): Nullable {
if (value === null) {
if (this.UUID === Characteristic.Model.UUID || this.UUID === Characteristic.SerialNumber.UUID) { // mirrors the statement in case: Formats.STRING
- this.characteristicWarning("characteristic must have a non null value otherwise HomeKit will reject this accessory, ignoring new value",
- CharacteristicWarningType.ERROR_MESSAGE);
- return this.value; // don't change the value
+ this.characteristicWarning('characteristic must have a non null value otherwise HomeKit will reject this accessory, ignoring new value', CharacteristicWarningType.ERROR_MESSAGE)
+ return this.value // don't change the value
}
if (this.props.format === Formats.DATA || this.props.format === Formats.TLV8) {
- return value; // TLV8 and DATA formats are allowed to have null as a value
+ return value // TLV8 and DATA formats are allowed to have null as a value
}
/**
@@ -2626,164 +2618,164 @@ export class Characteristic extends EventEmitter {
if (this.UUID.endsWith(BASE_UUID)) { // we have an apple defined characteristic (at least assuming nobody else uses the UUID namespace)
if (this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) {
- return value; // null is allowed as a value for ProgrammableSwitchEvent
+ return value // null is allowed as a value for ProgrammableSwitchEvent
}
- this.characteristicWarning("characteristic was supplied illegal value: null! Home App will reject null for Apple defined characteristics",
- warningType);
+ this.characteristicWarning('characteristic was supplied illegal value: null! Home App will reject null for Apple defined characteristics', warningType)
// if the value has been set previously, return it now, otherwise continue with validation to have a default value set.
if (this.value !== null) {
- return this.value;
+ return this.value
}
} else {
// we currently allow null for any non-custom defined characteristics
- return value;
+ return value
}
}
switch (this.props.format) {
- case Formats.BOOL: {
- if (typeof value === "boolean") {
- return value;
- }
- if (typeof value === "number") {
- return value === 1;
- }
- if (typeof value === "string") {
- return value === "1" || value === "true";
- }
+ case Formats.BOOL: {
+ if (typeof value === 'boolean') {
+ return value
+ }
+ if (typeof value === 'number') {
+ return value === 1
+ }
+ if (typeof value === 'string') {
+ return value === '1' || value === 'true'
+ }
- this.characteristicWarning("characteristic value expected boolean and received " + typeof value, warningType);
- return false;
- }
- case Formats.INT:
- case Formats.FLOAT:
- case Formats.UINT8:
- case Formats.UINT16:
- case Formats.UINT32:
- case Formats.UINT64: {
- if (typeof value === "boolean") {
- value = value ? 1 : 0;
- }
- if (typeof value === "string") {
- value = this.props.format === Formats.FLOAT ? parseFloat(value) : parseInt(value, 10);
- }
- if (typeof value !== "number" || !Number.isFinite(value)) {
- this.characteristicWarning(`characteristic value expected valid finite number and received "${value}" (${typeof value})`, warningType);
- value = typeof this.value === "number" ? this.value : this.props.minValue || 0;
- }
+ this.characteristicWarning(`characteristic value expected boolean and received ${typeof value}`, warningType)
+ return false
+ }
+ case Formats.INT:
+ case Formats.FLOAT:
+ case Formats.UINT8:
+ case Formats.UINT16:
+ case Formats.UINT32:
+ case Formats.UINT64: {
+ if (typeof value === 'boolean') {
+ value = value ? 1 : 0
+ }
+ if (typeof value === 'string') {
+ value = this.props.format === Formats.FLOAT ? Number.parseFloat(value) : Number.parseInt(value, 10)
+ }
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
+ this.characteristicWarning(`characteristic value expected valid finite number and received "${value}" (${typeof value})`, warningType)
+ value = typeof this.value === 'number' ? this.value : this.props.minValue || 0
+ }
- const numericMin = maxWithUndefined(this.props.minValue, numericLowerBound(this.props.format));
- const numericMax = minWithUndefined(this.props.maxValue, numericUpperBound(this.props.format));
+ const numericMin = maxWithUndefined(this.props.minValue, numericLowerBound(this.props.format))
+ const numericMax = minWithUndefined(this.props.maxValue, numericUpperBound(this.props.format))
- let stepValue: number | undefined = undefined;
- if (this.props.format === Formats.FLOAT) {
- stepValue = this.props.minStep;
- } else {
- stepValue = maxWithUndefined(this.props.minStep, 1);
- }
+ let stepValue: number | undefined
+ if (this.props.format === Formats.FLOAT) {
+ stepValue = this.props.minStep
+ } else {
+ stepValue = maxWithUndefined(this.props.minStep, 1)
+ }
- if (stepValue != null && stepValue > 0) {
- const minValue = this.props.minValue != null ? this.props.minValue : 0;
- value = stepValue * Math.round((value - minValue) / stepValue) + minValue;
- }
+ if (stepValue != null && stepValue > 0) {
+ const minValue = this.props.minValue != null ? this.props.minValue : 0
+ value = stepValue * Math.round((value - minValue) / stepValue) + minValue
+ }
- if (numericMin != null && value < numericMin) {
- this.characteristicWarning(`characteristic was supplied illegal value: number ${value} exceeded minimum of ${numericMin}`, warningType);
- value = numericMin;
- }
- if (numericMax != null && value > numericMax) {
- this.characteristicWarning(`characteristic was supplied illegal value: number ${value} exceeded maximum of ${numericMax}`, warningType);
- value = numericMax;
- }
+ if (numericMin != null && value < numericMin) {
+ this.characteristicWarning(`characteristic was supplied illegal value: number ${value} exceeded minimum of ${numericMin}`, warningType)
+ value = numericMin
+ }
+ if (numericMax != null && value > numericMax) {
+ this.characteristicWarning(`characteristic was supplied illegal value: number ${value} exceeded maximum of ${numericMax}`, warningType)
+ value = numericMax
+ }
- if (this.props.validValues && !this.props.validValues.includes(value)) {
- this.characteristicWarning(`characteristic value ${value} is not contained in valid values array`, warningType);
- return this.props.validValues.includes(this.value as number) ? this.value : (this.props.validValues[0] || 0);
- }
+ if (this.props.validValues && !this.props.validValues.includes(value)) {
+ this.characteristicWarning(`characteristic value ${value} is not contained in valid values array`, warningType)
+ return this.props.validValues.includes(this.value as number) ? this.value : (this.props.validValues[0] || 0)
+ }
- if (this.props.validValueRanges && this.props.validValueRanges.length === 2) {
- if (value < this.props.validValueRanges[0]) {
- this.characteristicWarning(`characteristic was supplied illegal value: number ${value} not contained in valid value range of `
- + `${this.props.validValueRanges}, supplying illegal values will throw errors in the future`, warningType);
- value = this.props.validValueRanges[0];
- } else if (value > this.props.validValueRanges[1]) {
- this.characteristicWarning(`characteristic was supplied illegal value: number ${value} not contained in valid value range of `
- + `${this.props.validValueRanges}, supplying illegal values will throw errors in the future`, warningType);
- value = this.props.validValueRanges[1];
+ if (this.props.validValueRanges && this.props.validValueRanges.length === 2) {
+ if (value < this.props.validValueRanges[0]) {
+ this.characteristicWarning(`characteristic was supplied illegal value: number ${value} not contained in valid value range of `
+ + `${this.props.validValueRanges}, supplying illegal values will throw errors in the future`, warningType)
+ value = this.props.validValueRanges[0]
+ } else if (value > this.props.validValueRanges[1]) {
+ this.characteristicWarning(`characteristic was supplied illegal value: number ${value} not contained in valid value range of `
+ + `${this.props.validValueRanges}, supplying illegal values will throw errors in the future`, warningType)
+ value = this.props.validValueRanges[1]
+ }
}
- }
- return value;
- }
- case Formats.STRING: {
- if (typeof value === "number") {
- this.characteristicWarning("characteristic was supplied illegal value: number instead of string, " +
- "supplying illegal values will throw errors in the future", warningType);
- value = String(value);
- }
- if (typeof value !== "string") {
- this.characteristicWarning("characteristic value expected string and received " + (typeof value), warningType);
- value = typeof this.value === "string" ? this.value : value + "";
+ return value
}
+ case Formats.STRING: {
+ if (typeof value === 'number') {
+ this.characteristicWarning('characteristic was supplied illegal value: number instead of string, '
+ + 'supplying illegal values will throw errors in the future', warningType)
+ value = String(value)
+ }
+ if (typeof value !== 'string') {
+ this.characteristicWarning(`characteristic value expected string and received ${typeof value}`, warningType)
+ value = typeof this.value === 'string' ? this.value : `${value}`
+ }
- // mirrors the case value = null at the beginning
- if (value.length <= 1 && (this.UUID === Characteristic.Model.UUID || this.UUID === Characteristic.SerialNumber.UUID)) {
- this.characteristicWarning(`[${this.displayName}] characteristic must have a length of more than 1 character otherwise`
- + ` HomeKit will reject this accessory, ignoring new value ${warningType}`);
- return this.value; // just return the current value
- }
+ // mirrors the case value = null at the beginning
+ if (value.length <= 1 && (this.UUID === Characteristic.Model.UUID || this.UUID === Characteristic.SerialNumber.UUID)) {
+ this.characteristicWarning(`[${this.displayName}] characteristic must have a length of more than 1 character otherwise`
+ + ` HomeKit will reject this accessory, ignoring new value ${warningType}`)
+ return this.value // just return the current value
+ }
- const maxLength = this.props.maxLen ?? 64; // default is 64 (max is 256 which is set in setProps)
- if (value.length > maxLength) {
- this.characteristicWarning(`characteristic was supplied illegal value: string '${value}' exceeded max length of ${maxLength}`, warningType);
- value = value.substring(0, maxLength);
- }
+ const maxLength = this.props.maxLen ?? 64 // default is 64 (max is 256 which is set in setProps)
+ if (value.length > maxLength) {
+ this.characteristicWarning(`characteristic was supplied illegal value: string '${value}' exceeded max length of ${maxLength}`, warningType)
+ value = value.substring(0, maxLength)
+ }
- if (value.length > 0 && this.UUID === Characteristic.ConfiguredName.UUID) {
- checkName(this.displayName, "ConfiguredName", value);
- }
+ if (value.length > 0 && this.UUID === Characteristic.ConfiguredName.UUID) {
+ checkName(this.displayName, 'ConfiguredName', value)
+ }
- return value;
- }
- case Formats.DATA:
- if (typeof value !== "string") {
- throw new Error("characteristic with DATA format must have string value");
+ return value
}
+ case Formats.DATA:
+ if (typeof value !== 'string') {
+ throw new TypeError('characteristic with DATA format must have string value')
+ }
- if (this.props.maxDataLen != null && value.length > this.props.maxDataLen) {
- // can't cut it as we would basically set binary rubbish afterwards
- throw new Error("characteristic with DATA format exceeds specified maxDataLen");
- }
- return value;
- case Formats.TLV8:
- if (value === undefined) {
- this.characteristicWarning("characteristic was supplied illegal value: undefined", warningType);
- return this.value;
- }
- return value; // we trust that this is valid tlv8
+ if (this.props.maxDataLen != null && value.length > this.props.maxDataLen) {
+ // can't cut it as we would basically set binary rubbish afterward
+ throw new Error('characteristic with DATA format exceeds specified maxDataLen')
+ }
+ return value
+ case Formats.TLV8:
+ if (value === undefined) {
+ this.characteristicWarning('characteristic was supplied illegal value: undefined', warningType)
+ return this.value
+ }
+ return value // we trust that this is valid tlv8
}
// hopefully it shouldn't get to this point
if (value === undefined) {
- this.characteristicWarning("characteristic was supplied illegal value: undefined", CharacteristicWarningType.ERROR_MESSAGE);
- return this.value;
+ this.characteristicWarning('characteristic was supplied illegal value: undefined', CharacteristicWarningType.ERROR_MESSAGE)
+ return this.value
}
- return value;
+ return value
}
/**
- * @private used to assign iid to characteristic
+ * @private
*/
_assignID(identifierCache: IdentifierCache, accessoryName: string, serviceUUID: string, serviceSubtype?: string): void {
// generate our IID based on our UUID
- this.iid = identifierCache.getIID(accessoryName, serviceUUID, serviceSubtype, this.UUID);
+ this.iid = identifierCache.getIID(accessoryName, serviceUUID, serviceSubtype, this.UUID)
}
+ // eslint-disable-next-line unicorn/error-message
private characteristicWarning(message: string, type = CharacteristicWarningType.WARN_MESSAGE, stack = new Error().stack): void {
- this.emit(CharacteristicEventTypes.CHARACTERISTIC_WARNING, type, message, stack);
+ this.emit(CharacteristicEventTypes.CHARACTERISTIC_WARNING, type, message, stack)
}
/**
@@ -2792,10 +2784,10 @@ export class Characteristic extends EventEmitter {
*/
removeAllListeners(event?: string | symbol): this {
if (!event) {
- this.removeOnGet();
- this.removeOnSet();
+ this.removeOnGet()
+ this.removeOnSet()
}
- return super.removeAllListeners(event);
+ return super.removeAllListeners(event)
}
/**
@@ -2803,137 +2795,138 @@ export class Characteristic extends EventEmitter {
* @private
*/
replaceBy(characteristic: Characteristic): void {
- this.props = characteristic.props;
- this.updateValue(characteristic.value);
+ this.props = characteristic.props
+ this.updateValue(characteristic.value)
- const getListeners = characteristic.listeners(CharacteristicEventTypes.GET);
+ const getListeners = characteristic.listeners(CharacteristicEventTypes.GET)
if (getListeners.length) {
// the callback can only be called once, so we remove all old listeners
- this.removeAllListeners(CharacteristicEventTypes.GET);
+ this.removeAllListeners(CharacteristicEventTypes.GET)
// @ts-expect-error: force type
- getListeners.forEach(listener => this.addListener(CharacteristicEventTypes.GET, listener));
+ getListeners.forEach(listener => this.addListener(CharacteristicEventTypes.GET, listener))
}
- this.removeOnGet();
+ this.removeOnGet()
if (characteristic.getHandler) {
- this.onGet(characteristic.getHandler);
+ this.onGet(characteristic.getHandler)
}
- const setListeners = characteristic.listeners(CharacteristicEventTypes.SET);
+ const setListeners = characteristic.listeners(CharacteristicEventTypes.SET)
if (setListeners.length) {
// the callback can only be called once, so we remove all old listeners
- this.removeAllListeners(CharacteristicEventTypes.SET);
+ this.removeAllListeners(CharacteristicEventTypes.SET)
// @ts-expect-error: force type
- setListeners.forEach(listener => this.addListener(CharacteristicEventTypes.SET, listener));
+ setListeners.forEach(listener => this.addListener(CharacteristicEventTypes.SET, listener))
}
- this.removeOnSet();
+ this.removeOnSet()
if (characteristic.setHandler) {
- this.onSet(characteristic.setHandler);
+ this.onSet(characteristic.setHandler)
}
}
/**
* Returns a JSON representation of this characteristic suitable for delivering to HAP clients.
- * @private used to generate response to /accessories query
+ * @private
*/
async toHAP(connection: HAPConnection, contactGetHandlers = true): Promise {
- const object = this.internalHAPRepresentation();
+ const object = this.internalHAPRepresentation()
if (!this.props.perms.includes(Perms.PAIRED_READ)) {
- object.value = undefined;
+ object.value = undefined
} else if (this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) {
// special workaround for event only programmable switch event, which must always return null
- object.value = null;
+ object.value = null
} else { // query the current value
const value = contactGetHandlers
? await this.handleGetRequest(connection).catch(() => {
- const value = this.getDefaultValue();
- debug("[%s] Error getting value for characteristic on /accessories request. Returning default value instead: %s", this.displayName, `${value}`);
- return value; // use default value
+ const value = this.getDefaultValue()
+ debug('[%s] Error getting value for characteristic on /accessories request. Returning default value instead: %s', this.displayName, `${value}`)
+ return value // use default value
})
- : this.value;
+ : this.value
- object.value = formatOutgoingCharacteristicValue(value, this.props);
+ object.value = formatOutgoingCharacteristicValue(value, this.props)
}
- return object;
+ return object
}
/**
* Returns a JSON representation of this characteristic without the value.
- * @private used to generate the config hash
+ * @private
*/
internalHAPRepresentation(): CharacteristicJsonObject {
- assert(this.iid,"iid cannot be undefined for characteristic '" + this.displayName + "'");
+ assert(this.iid, `iid cannot be undefined for characteristic '${this.displayName}'`)
// TODO include the value for characteristics of the AccessoryInformation service
return {
- type: toShortForm(this.UUID),
- iid: this.iid!,
- value: null,
- perms: this.props.perms,
- description: this.props.description || this.displayName,
- format: this.props.format,
- unit: this.props.unit,
- minValue: this.props.minValue,
- maxValue: this.props.maxValue,
- minStep: this.props.minStep,
- maxLen: this.props.maxLen,
- maxDataLen: this.props.maxDataLen,
- "valid-values": this.props.validValues,
- "valid-values-range": this.props.validValueRanges,
- };
+ 'type': toShortForm(this.UUID),
+ 'iid': this.iid!,
+ 'value': null,
+ 'perms': this.props.perms,
+ 'description': this.props.description || this.displayName,
+ 'format': this.props.format,
+ 'unit': this.props.unit,
+ 'minValue': this.props.minValue,
+ 'maxValue': this.props.maxValue,
+ 'minStep': this.props.minStep,
+ 'maxLen': this.props.maxLen,
+ 'maxDataLen': this.props.maxDataLen,
+ 'valid-values': this.props.validValues,
+ 'valid-values-range': this.props.validValueRanges,
+ }
}
/**
* Serialize characteristic into json string.
*
* @param characteristic - Characteristic object.
- * @private used to store characteristic on disk
+ * @private
*/
static serialize(characteristic: Characteristic): SerializedCharacteristic {
- let constructorName: string | undefined;
- if (characteristic.constructor.name !== "Characteristic") {
- constructorName = characteristic.constructor.name;
+ let constructorName: string | undefined
+ if (characteristic.constructor.name !== 'Characteristic') {
+ constructorName = characteristic.constructor.name
}
return {
displayName: characteristic.displayName,
UUID: characteristic.UUID,
eventOnlyCharacteristic: characteristic.UUID === Characteristic.ProgrammableSwitchEvent.UUID, // support downgrades for now
- constructorName: constructorName,
+ constructorName,
value: characteristic.value,
props: clone({}, characteristic.props),
- };
+ }
}
/**
* Deserialize characteristic from json string.
*
* @param json - Json string representing a characteristic.
- * @private used to recreate characteristic from disk
+ * @private
*/
static deserialize(json: SerializedCharacteristic): Characteristic {
- let characteristic: Characteristic;
+ let characteristic: Characteristic
if (json.constructorName && json.constructorName.charAt(0).toUpperCase() === json.constructorName.charAt(0)
&& Characteristic[json.constructorName as keyof (typeof Characteristic)]) { // MUST start with uppercase character and must exist on Characteristic object
- const constructor = Characteristic[json.constructorName as keyof (typeof Characteristic)] as { new(): Characteristic };
- characteristic = new constructor();
- characteristic.displayName = json.displayName;
- characteristic.setProps(json.props);
+ const constructor = Characteristic[json.constructorName as keyof (typeof Characteristic)] as { new(): Characteristic }
+ characteristic = new constructor()
+ characteristic.displayName = json.displayName
+ characteristic.setProps(json.props)
} else {
- characteristic = new Characteristic(json.displayName, json.UUID, json.props);
+ characteristic = new Characteristic(json.displayName, json.UUID, json.props)
}
- characteristic.value = json.value;
+ characteristic.value = json.value
- return characteristic;
+ return characteristic
}
-
}
// We have a cyclic dependency problem. Within this file we have the definitions of "./definitions" as
// type imports only (in order to define the static properties). Setting those properties is done outside
// this file, within the definition files. Therefore, we import it at the end of this file. Seems weird, but is important.
-import "./definitions/CharacteristicDefinitions";
+(async () => {
+ await import('./definitions/CharacteristicDefinitions.js')
+})()
diff --git a/src/lib/HAPServer.spec.ts b/src/lib/HAPServer.spec.ts
index eb0b5d65c..c6923d772 100644
--- a/src/lib/HAPServer.spec.ts
+++ b/src/lib/HAPServer.spec.ts
@@ -1,287 +1,298 @@
-import axios, { AxiosError, AxiosResponse } from "axios";
-import crypto from "crypto";
-import { Agent } from "http";
-import tweetnacl from "tweetnacl";
-import { PairingStates, TLVValues } from "../internal-types";
-import { HAPHTTPClient } from "../test-utils/HAPHTTPClient";
-import { PairSetupClient } from "../test-utils/PairSetupClient";
-import { PairVerifyClient } from "../test-utils/PairVerifyClient";
-import {
+import type { AxiosResponse } from 'axios'
+
+import type {
AccessoriesResponse,
CharacteristicId,
CharacteristicReadData,
CharacteristicsWriteRequest,
CharacteristicsWriteResponse,
ResourceRequest,
- ResourceRequestType,
-} from "../types";
-import { Accessory } from "./Accessory";
-import { Characteristic, Formats, Perms } from "./Characteristic";
+} from '../types'
+import type { IdentifyCallback } from './HAPServer'
+import type { PairingInformation } from './model/AccessoryInfo'
+import type { HAPConnection, HAPEncryption } from './util/eventedhttp'
+
+import { Buffer } from 'node:buffer'
+import { randomBytes } from 'node:crypto'
+import { Agent } from 'node:http'
+
+import axios, { AxiosError } from 'axios'
+import tweetnacl from 'tweetnacl'
+import { afterEach, beforeEach, describe, expect, it } from 'vitest'
+
+import { PairingStates, TLVValues } from '../internal-types.js'
+import { HAPHTTPClient } from '../test-utils/HAPHTTPClient.js'
+import { PairSetupClient } from '../test-utils/PairSetupClient.js'
+import { PairVerifyClient } from '../test-utils/PairVerifyClient.js'
+import { ResourceRequestType } from '../types.js'
+import { Accessory } from './Accessory.js'
+import { Characteristic, Formats, Perms } from './Characteristic.js'
import {
HAPHTTPCode,
HAPPairingHTTPCode,
HAPServer,
HAPServerEventTypes,
HAPStatus,
- IdentifyCallback,
- IsKnownHAPStatusError,
+ isKnownHAPStatusError,
TLVErrorCode,
-} from "./HAPServer";
-import { AccessoryInfo, PairingInformation, PermissionTypes } from "./model/AccessoryInfo";
-import { Service } from "./Service";
-import { HAPConnection, HAPEncryption } from "./util/eventedhttp";
-import { awaitEventOnce, PromiseTimeout } from "./util/promise-utils";
-import * as tlv from "./util/tlv";
-
-describe("HAPServer", () => {
- const serverUsername = "AA:AA:AA:AA:AA:AA";
- const clientUsername = "BB:BB:BB:BB:BB:BB";
- const thirdUsername = "CC:CC:CC:CC:CC:CC";
-
- let accessoryInfoUnpaired: AccessoryInfo;
+} from './HAPServer.js'
+import { AccessoryInfo, PermissionTypes } from './model/AccessoryInfo.js'
+import { Service } from './Service.js'
+import { awaitEventOnce, PromiseTimeout } from './util/promise-utils.js'
+import { decode } from './util/tlv.js'
+
+describe('hAPServer', () => {
+ const serverUsername = 'AA:AA:AA:AA:AA:AA'
+ const clientUsername = 'BB:BB:BB:BB:BB:BB'
+ const thirdUsername = 'CC:CC:CC:CC:CC:CC'
+
+ let accessoryInfoUnpaired: AccessoryInfo
const serverInfoUnpaired = {
username: serverUsername,
publicKey: Buffer.alloc(0),
- };
+ }
- let accessoryInfoPaired: AccessoryInfo;
+ let accessoryInfoPaired: AccessoryInfo
const serverInfoPaired = {
username: serverUsername,
publicKey: Buffer.alloc(0),
- };
+ }
const clientInfo = {
username: clientUsername,
privateKey: Buffer.alloc(0),
publicKey: Buffer.alloc(0),
- };
+ }
- let httpAgent: Agent;
- let server: HAPServer;
+ let httpAgent: Agent
+ let server: HAPServer
- async function bindServer(server: HAPServer, port = 0, host = "localhost"): Promise<[port: number, string: string]> {
- const listenPromise: Promise<[number, string]> = awaitEventOnce(server, HAPServerEventTypes.LISTENING);
- server.listen(port, host);
- return await listenPromise;
+ async function bindServer(server: HAPServer, port = 0, host = 'localhost'): Promise<[port: number, string: string]> {
+ const listenPromise: Promise<[number, string]> = awaitEventOnce(server, HAPServerEventTypes.LISTENING)
+ server.listen(port, host)
+ return await listenPromise
}
beforeEach(() => {
- accessoryInfoUnpaired = AccessoryInfo.create(serverUsername);
+ accessoryInfoUnpaired = AccessoryInfo.create(serverUsername)
// @ts-expect-error: private access
- accessoryInfoUnpaired.setupID = Accessory._generateSetupID();
- accessoryInfoUnpaired.displayName = "Outlet";
- accessoryInfoUnpaired.category = 7;
- accessoryInfoUnpaired.pincode = " 031-45-154";
- serverInfoUnpaired.publicKey = accessoryInfoUnpaired.signPk;
+ accessoryInfoUnpaired.setupID = Accessory._generateSetupID()
+ accessoryInfoUnpaired.displayName = 'Outlet'
+ accessoryInfoUnpaired.category = 7
+ accessoryInfoUnpaired.pincode = ' 031-45-154'
+ serverInfoUnpaired.publicKey = accessoryInfoUnpaired.signPk
- accessoryInfoPaired = AccessoryInfo.create(serverUsername);
+ accessoryInfoPaired = AccessoryInfo.create(serverUsername)
// @ts-expect-error: private access
- accessoryInfoPaired.setupID = Accessory._generateSetupID();
- accessoryInfoPaired.displayName = "Outlet";
- accessoryInfoPaired.category = 7;
- accessoryInfoPaired.pincode = " 031-45-154";
- serverInfoPaired.publicKey = accessoryInfoPaired.signPk;
+ accessoryInfoPaired.setupID = Accessory._generateSetupID()
+ accessoryInfoPaired.displayName = 'Outlet'
+ accessoryInfoPaired.category = 7
+ accessoryInfoPaired.pincode = ' 031-45-154'
+ serverInfoPaired.publicKey = accessoryInfoPaired.signPk
- const clientKeyPair = tweetnacl.sign.keyPair();
- clientInfo.privateKey = Buffer.from(clientKeyPair.secretKey);
- clientInfo.publicKey = Buffer.from(clientKeyPair.publicKey);
- accessoryInfoPaired.addPairedClient(clientUsername, clientInfo.publicKey, PermissionTypes.ADMIN);
+ const clientKeyPair = tweetnacl.sign.keyPair()
+ clientInfo.privateKey = Buffer.from(clientKeyPair.secretKey)
+ clientInfo.publicKey = Buffer.from(clientKeyPair.publicKey)
+ accessoryInfoPaired.addPairedClient(clientUsername, clientInfo.publicKey, PermissionTypes.ADMIN)
// used to do long living http connections without own tcp interface
httpAgent = new Agent({
keepAlive: true,
- });
- });
+ })
+ })
afterEach(() => {
- server?.stop();
- server?.destroy();
- });
+ server?.stop()
+ server?.destroy()
+ })
- test("simple unpaired identify", async () => {
- server = new HAPServer(accessoryInfoUnpaired);
- const [port] = await bindServer(server);
+ it('simple unpaired identify', async () => {
+ server = new HAPServer(accessoryInfoUnpaired)
+ const [port] = await bindServer(server)
- const promise: Promise = awaitEventOnce(server, HAPServerEventTypes.IDENTIFY);
+ const promise: Promise = awaitEventOnce(server, HAPServerEventTypes.IDENTIFY)
- const request: Promise> = axios.post(`http://localhost:${port}/identify`, { httpAgent });
+ const request: Promise> = axios.post(`http://localhost:${port}/identify`, { httpAgent })
- const callback = await promise;
- callback(); // signal successful identify!
+ const callback = await promise
+ callback() // signal successful identify!
- const response = await request;
- expect(response.data).toBeFalsy();
- });
+ const response = await request
+ expect(response.data).toBeFalsy()
+ })
- test("reject unpaired identify on paired server", async () => {
- server = new HAPServer(accessoryInfoPaired);
- const [port] = await bindServer(server);
+ it('reject unpaired identify on paired server', async () => {
+ server = new HAPServer(accessoryInfoPaired)
+ const [port] = await bindServer(server)
- try {
- const response = await axios.post(`http://localhost:${port}/identify`, { httpAgent });
- fail(`Expected erroneous response, got ${response}`);
- } catch (error) {
- expect(error).toBeInstanceOf(AxiosError);
- expect(error.response?.status).toBe(HAPPairingHTTPCode.BAD_REQUEST);
- expect(error.response?.data).toEqual({ status: HAPStatus.INSUFFICIENT_PRIVILEGES });
- }
- });
+ await expect(axios.post(`http://localhost:${port}/identify`, { httpAgent })).rejects.toThrow(AxiosError)
+ await expect(axios.post(`http://localhost:${port}/identify`, { httpAgent })).rejects.toMatchObject({
+ response: {
+ status: HAPPairingHTTPCode.BAD_REQUEST,
+ data: { status: HAPStatus.INSUFFICIENT_PRIVILEGES },
+ },
+ })
+ })
- test("test-utils successful /pair-setup", async () => {
- server = new HAPServer(accessoryInfoUnpaired);
- const [port] = await bindServer(server);
+ it('test-utils successful /pair-setup', async () => {
+ server = new HAPServer(accessoryInfoUnpaired)
+ const [port] = await bindServer(server)
server.on(HAPServerEventTypes.PAIR, (username, clientLTPK, callback) => {
- expect(username).toEqual(clientUsername);
- expect(clientLTPK).toEqual(clientInfo.publicKey);
- callback();
- });
+ expect(username).toEqual(clientUsername)
+ expect(clientLTPK).toEqual(clientInfo.publicKey)
+ callback()
+ })
- const pairSetup = new PairSetupClient(port, httpAgent);
+ const pairSetup = new PairSetupClient(port, httpAgent)
- const M6 = await pairSetup.sendPairSetup(accessoryInfoPaired.pincode, clientInfo);
+ const M6 = await pairSetup.sendPairSetup(accessoryInfoPaired.pincode, clientInfo)
- expect(M6.accessoryIdentifier).toEqual(serverUsername);
- expect(M6.accessoryLTPK).toEqual(accessoryInfoUnpaired.signPk);
- });
+ expect(M6.accessoryIdentifier).toEqual(serverUsername)
+ expect(M6.accessoryLTPK).toEqual(accessoryInfoUnpaired.signPk)
+ })
- test("reject pair-setup after already paired", async () => {
- server = new HAPServer(accessoryInfoPaired);
- const [port] = await bindServer(server);
+ it('reject pair-setup after already paired', async () => {
+ server = new HAPServer(accessoryInfoPaired)
+ const [port] = await bindServer(server)
- const pairSetup = new PairSetupClient(port, httpAgent);
+ const pairSetup = new PairSetupClient(port, httpAgent)
- const response = await pairSetup.sendM1();
- const objectsM2 = tlv.decode(response.data);
- expect(objectsM2[TLVValues.STATE].readUInt8(0)).toEqual(PairingStates.M2);
- expect(objectsM2[TLVValues.ERROR_CODE].readUInt8(0)).toEqual(TLVErrorCode.UNAVAILABLE);
- });
+ const response = await pairSetup.sendM1()
+ const objectsM2 = decode(response.data)
+ expect(objectsM2[TLVValues.STATE].readUInt8(0)).toEqual(PairingStates.M2)
+ expect(objectsM2[TLVValues.ERROR_CODE].readUInt8(0)).toEqual(TLVErrorCode.UNAVAILABLE)
+ })
- test("reject pair-setup with state M3", async () => {
- server = new HAPServer(accessoryInfoUnpaired);
- const [port] = await bindServer(server);
+ it('reject pair-setup with state M3', async () => {
+ server = new HAPServer(accessoryInfoUnpaired)
+ const [port] = await bindServer(server)
- const pairSetup = new PairSetupClient(port, httpAgent);
+ const pairSetup = new PairSetupClient(port, httpAgent)
const M3 = await pairSetup.prepareM3({
- salt: crypto.randomBytes(16),
- serverPublicKey: crypto.randomBytes(384),
- }, accessoryInfoUnpaired.pincode);
+ salt: randomBytes(16),
+ serverPublicKey: randomBytes(384),
+ }, accessoryInfoUnpaired.pincode)
+ let response
try {
- const response = await pairSetup.sendM3(M3);
- fail(`Expected erroneous response, got ${response}`);
+ response = await pairSetup.sendM3(M3)
} catch (error) {
- expect(error).toBeInstanceOf(AxiosError);
- expect(error.response?.status).toBe(HAPHTTPCode.BAD_REQUEST);
+ expect(error).toBeInstanceOf(AxiosError)
+ expect(error.response?.status).toBe(HAPHTTPCode.BAD_REQUEST)
+
+ const objectsM4 = decode(error.response?.data)
+ expect(objectsM4[TLVValues.STATE].readUInt8(0)).toEqual(PairingStates.M4)
+ expect(objectsM4[TLVValues.ERROR_CODE].readUInt8(0)).toEqual(TLVErrorCode.UNKNOWN)
+ }
- const objectsM4 = tlv.decode(error.response?.data);
- expect(objectsM4[TLVValues.STATE].readUInt8(0)).toEqual(PairingStates.M4);
- expect(objectsM4[TLVValues.ERROR_CODE].readUInt8(0)).toEqual(TLVErrorCode.UNKNOWN);
+ if (response) {
+ throw new Error(`Expected erroneous response, got ${response}`)
}
- });
+ })
- test("reject pair-setup with state M5", async () => {
- server = new HAPServer(accessoryInfoUnpaired);
- const [port] = await bindServer(server);
+ it('reject pair-setup with state M5', async () => {
+ server = new HAPServer(accessoryInfoUnpaired)
+ const [port] = await bindServer(server)
- const pairSetup = new PairSetupClient(port, httpAgent);
+ const pairSetup = new PairSetupClient(port, httpAgent)
- const M5 = await pairSetup.prepareM5({ sharedSecret: crypto.randomBytes(256) }, clientInfo);
+ const M5 = pairSetup.prepareM5({ sharedSecret: randomBytes(256) }, clientInfo)
try {
- const response = await pairSetup.sendM5(M5);
- fail(`Expected erroneous response, got ${response}`);
+ const response = await pairSetup.sendM5(M5)
+ throw new Error(`Expected erroneous response, got ${response}`)
} catch (error) {
- expect(error).toBeInstanceOf(AxiosError);
- expect(error.response?.status).toBe(HAPHTTPCode.BAD_REQUEST);
+ expect(error).toBeInstanceOf(AxiosError)
+ expect(error.response?.status).toBe(HAPHTTPCode.BAD_REQUEST)
- const objectsM4 = tlv.decode(error.response?.data);
- expect(objectsM4[TLVValues.STATE].readUInt8(0)).toEqual(PairingStates.M6);
- expect(objectsM4[TLVValues.ERROR_CODE].readUInt8(0)).toEqual(TLVErrorCode.UNKNOWN);
+ const objectsM4 = decode(error.response?.data)
+ expect(objectsM4[TLVValues.STATE].readUInt8(0)).toEqual(PairingStates.M6)
+ expect(objectsM4[TLVValues.ERROR_CODE].readUInt8(0)).toEqual(TLVErrorCode.UNKNOWN)
}
- });
+ })
- test("test successful /pair-verify", async () => {
- server = new HAPServer(accessoryInfoPaired);
- const [port] = await bindServer(server);
+ it('test successful /pair-verify', async () => {
+ server = new HAPServer(accessoryInfoPaired)
+ const [port] = await bindServer(server)
- const pairVerify = new PairVerifyClient(port, httpAgent);
- await pairVerify.sendPairVerify(serverInfoPaired, clientInfo);
- });
+ const pairVerify = new PairVerifyClient(port, httpAgent)
+ await pairVerify.sendPairVerify(serverInfoPaired, clientInfo)
+ })
- test("test /pair-verify with not being paired", async () => {
- server = new HAPServer(accessoryInfoUnpaired);
- const [port] = await bindServer(server);
+ it('test /pair-verify with not being paired', async () => {
+ server = new HAPServer(accessoryInfoUnpaired)
+ const [port] = await bindServer(server)
- const pairVerify = new PairVerifyClient(port, httpAgent);
+ const pairVerify = new PairVerifyClient(port, httpAgent)
// M1
- const responseM1 = await pairVerify.sendM1();
+ const responseM1 = await pairVerify.sendM1()
// M2
- const M2 = pairVerify.parseM2(responseM1.data, serverInfoUnpaired);
+ const M2 = pairVerify.parseM2(responseM1.data, serverInfoUnpaired)
// M3
- const M3 = pairVerify.prepareM3(M2, clientInfo);
+ const M3 = pairVerify.prepareM3(M2, clientInfo)
- const responseM3 = await pairVerify.sendM3(M3);
- const objectsM4 = tlv.decode(responseM3.data);
+ const responseM3 = await pairVerify.sendM3(M3)
+ const objectsM4 = decode(responseM3.data)
- expect(objectsM4[TLVValues.STATE].readUInt8(0)).toBe(PairingStates.M4);
- expect(objectsM4[TLVValues.ERROR_CODE].readUInt8(0)).toEqual(TLVErrorCode.AUTHENTICATION);
- });
+ expect(objectsM4[TLVValues.STATE].readUInt8(0)).toBe(PairingStates.M4)
+ expect(objectsM4[TLVValues.ERROR_CODE].readUInt8(0)).toEqual(TLVErrorCode.AUTHENTICATION)
+ })
- describe("tests with paired and pair-verified connection", () => {
- let port: number;
- let address: string;
- let encryption: HAPEncryption;
- let client: HAPHTTPClient;
+ describe('tests with paired and pair-verified connection', () => {
+ let port: number
+ let address: string
+ let encryption: HAPEncryption
+ let client: HAPHTTPClient
beforeEach(async () => {
- server = new HAPServer(accessoryInfoPaired);
- const addressInformation = await bindServer(server);
- port = addressInformation[0];
- address = addressInformation[1];
+ server = new HAPServer(accessoryInfoPaired)
+ const addressInformation = await bindServer(server)
+ port = addressInformation[0]
+ address = addressInformation[1]
- const pairVerify = new PairVerifyClient(port, httpAgent);
- encryption = await pairVerify.sendPairVerify(serverInfoPaired, clientInfo);
+ const pairVerify = new PairVerifyClient(port, httpAgent)
+ encryption = await pairVerify.sendPairVerify(serverInfoPaired, clientInfo)
- client = new HAPHTTPClient(httpAgent, address, port);
- client.attachSocket();
+ client = new HAPHTTPClient(httpAgent, address, port)
+ client.attachSocket()
- client.enableEncryption(encryption);
- });
+ client.enableEncryption(encryption)
+ })
afterEach(() => {
- client.releaseSocket();
- });
+ client.releaseSocket()
+ })
- test("test /pairings ADD_PAIRING", async () => {
- const exampleKey = crypto.randomBytes(32);
+ it('test /pairings ADD_PAIRING', async () => {
+ const exampleKey = randomBytes(32)
server.on(HAPServerEventTypes.ADD_PAIRING, (connection, username, publicKey, permission, callback) => {
- expect(connection.encryption).toBeDefined();
- expect(username).toEqual(thirdUsername);
- expect(publicKey).toEqual(exampleKey);
- expect(permission).toEqual(PermissionTypes.ADMIN);
- callback(0);
- });
+ expect(connection.encryption).toBeDefined()
+ expect(username).toEqual(thirdUsername)
+ expect(publicKey).toEqual(exampleKey)
+ expect(permission).toEqual(PermissionTypes.ADMIN)
+ callback(0)
+ })
- await client.sendAddPairingRequest(thirdUsername, exampleKey, PermissionTypes.ADMIN);
- });
+ await client.sendAddPairingRequest(thirdUsername, exampleKey, PermissionTypes.ADMIN)
+ })
- test("test /pairings REMOVE_PAIRING", async () => {
+ it('test /pairings REMOVE_PAIRING', async () => {
server.on(HAPServerEventTypes.REMOVE_PAIRING, (connection, username, callback) => {
- expect(connection.encryption).toBeDefined();
- expect(username).toEqual(thirdUsername);
- callback(0);
- });
+ expect(connection.encryption).toBeDefined()
+ expect(username).toEqual(thirdUsername)
+ callback(0)
+ })
- await client.sendRemovePairingRequest(thirdUsername);
- });
+ await client.sendRemovePairingRequest(thirdUsername)
+ })
- test("test /pairings LIST_PAIRINGS", async () => {
+ it('test /pairings LIST_PAIRINGS', async () => {
const list: PairingInformation[] = [
{
username: clientUsername,
@@ -290,21 +301,21 @@ describe("HAPServer", () => {
},
{
username: thirdUsername,
- publicKey: crypto.randomBytes(32),
+ publicKey: randomBytes(32),
permission: PermissionTypes.USER,
},
- ];
+ ]
server.on(HAPServerEventTypes.LIST_PAIRINGS, (connection, callback) => {
- expect(connection.encryption).toBeDefined();
- callback(0, list);
- });
+ expect(connection.encryption).toBeDefined()
+ callback(0, list)
+ })
- const response = await client.sendListPairingsRequest();
- expect(response).toEqual(list);
- });
+ const response = await client.sendListPairingsRequest()
+ expect(response).toEqual(list)
+ })
- test("test /accessories", async () => {
+ it('test /accessories', async () => {
const accessoryResponse: AccessoriesResponse = {
accessories: [{
aid: 1,
@@ -315,28 +326,28 @@ describe("HAPServer", () => {
iid: 3,
format: Formats.STRING,
perms: [Perms.PAIRED_READ],
- value: "Hello World",
+ value: 'Hello World',
type: Characteristic.Name.UUID,
}],
}],
}],
- };
+ }
server.on(HAPServerEventTypes.ACCESSORIES, (connection, callback) => {
- expect(connection.encryption).toBeDefined();
- callback(undefined, accessoryResponse);
- });
+ expect(connection.encryption).toBeDefined()
+ callback(undefined, accessoryResponse)
+ })
- const accessories = await client.sendAccessoriesRequest();
- expect(accessories).toEqual(accessoryResponse);
- });
+ const accessories = await client.sendAccessoriesRequest()
+ expect(accessories).toEqual(accessoryResponse)
+ })
- test("test successful GET /characteristics", async () => {
- const ids: CharacteristicId[] = [ { aid: 1, iid: 9 }, { aid: 2, iid: 14 } ];
+ it('test successful GET /characteristics', async () => {
+ const ids: CharacteristicId[] = [{ aid: 1, iid: 9 }, { aid: 2, iid: 14 }]
const readData: CharacteristicReadData[] = [
{
...ids[0],
- value: "Hello World",
+ value: 'Hello World',
type: Characteristic.Name.UUID,
ev: true,
},
@@ -346,48 +357,48 @@ describe("HAPServer", () => {
type: Characteristic.Active.UUID,
ev: false,
},
- ];
+ ]
server.on(HAPServerEventTypes.GET_CHARACTERISTICS, (connection, request, callback) => {
- expect(connection.encryption).toBeDefined();
- expect(request.ids).toEqual(ids);
- expect(request.includeMeta).toBeFalsy();
- expect(request.includePerms).toBeFalsy();
- expect(request.includeType).toBeTruthy();
- expect(request.includeEvent).toBeTruthy();
- callback(undefined, { characteristics: readData });
- });
-
- const httpResponse = await client.sendCharacteristicRead(ids, false, false, true, true);
- expect(httpResponse.statusCode).toEqual(HAPHTTPCode.OK);
- expect(httpResponse.body).toEqual({ characteristics: readData });
- });
-
- test("test GET /characteristics with errors", async () => {
- const ids: CharacteristicId[] = [ { aid: 1, iid: 9 }, { aid: 2, iid: 14 } ];
+ expect(connection.encryption).toBeDefined()
+ expect(request.ids).toEqual(ids)
+ expect(request.includeMeta).toBeFalsy()
+ expect(request.includePerms).toBeFalsy()
+ expect(request.includeType).toBeTruthy()
+ expect(request.includeEvent).toBeTruthy()
+ callback(undefined, { characteristics: readData })
+ })
+
+ const httpResponse = await client.sendCharacteristicRead(ids, false, false, true, true)
+ expect(httpResponse.statusCode).toEqual(HAPHTTPCode.OK)
+ expect(httpResponse.body).toEqual({ characteristics: readData })
+ })
+
+ it('test GET /characteristics with errors', async () => {
+ const ids: CharacteristicId[] = [{ aid: 1, iid: 9 }, { aid: 2, iid: 14 }]
const readData: CharacteristicReadData[] = [
{
...ids[0],
- value: "Hello World",
+ value: 'Hello World',
},
{
...ids[1],
status: HAPStatus.SERVICE_COMMUNICATION_FAILURE,
},
- ];
+ ]
server.on(HAPServerEventTypes.GET_CHARACTERISTICS, (connection, request, callback) => {
- expect(connection.encryption).toBeDefined();
- expect(request.ids).toEqual(ids);
- expect(request.includeMeta).toBeFalsy();
- expect(request.includePerms).toBeFalsy();
- expect(request.includeType).toBeFalsy();
- expect(request.includeEvent).toBeFalsy();
- callback(undefined, { characteristics: readData });
- });
-
- const httpResponse = await client.sendCharacteristicRead(ids);
- expect(httpResponse.statusCode).toEqual(HAPHTTPCode.MULTI_STATUS);
+ expect(connection.encryption).toBeDefined()
+ expect(request.ids).toEqual(ids)
+ expect(request.includeMeta).toBeFalsy()
+ expect(request.includePerms).toBeFalsy()
+ expect(request.includeType).toBeFalsy()
+ expect(request.includeEvent).toBeFalsy()
+ callback(undefined, { characteristics: readData })
+ })
+
+ const httpResponse = await client.sendCharacteristicRead(ids)
+ expect(httpResponse.statusCode).toEqual(HAPHTTPCode.MULTI_STATUS)
expect(httpResponse.body).toEqual({
characteristics: [
{
@@ -396,16 +407,16 @@ describe("HAPServer", () => {
},
readData[1],
],
- });
- });
+ })
+ })
- test("test successful PUT /characteristics value write NO_CONTENT", async () => {
+ it('test successful PUT /characteristics value write NO_CONTENT', async () => {
const writeRequest: CharacteristicsWriteRequest = {
characteristics: [
{
aid: 1,
iid: 9,
- value: "Hello World",
+ value: 'Hello World',
},
{
aid: 2,
@@ -413,7 +424,7 @@ describe("HAPServer", () => {
value: true,
},
],
- };
+ }
const hapResponse: CharacteristicsWriteResponse = {
characteristics: [
@@ -428,25 +439,25 @@ describe("HAPServer", () => {
status: HAPStatus.SUCCESS,
},
],
- };
+ }
server.on(HAPServerEventTypes.SET_CHARACTERISTICS, (connection, request, callback) => {
- expect(connection.encryption).toBeDefined();
- expect(request).toEqual(writeRequest);
- callback(undefined, hapResponse);
- });
+ expect(connection.encryption).toBeDefined()
+ expect(request).toEqual(writeRequest)
+ callback(undefined, hapResponse)
+ })
- const httpResponse = await client.sendCharacteristicWrite(writeRequest);
- expect(httpResponse.statusCode).toEqual(HAPHTTPCode.NO_CONTENT);
- });
+ const httpResponse = await client.sendCharacteristicWrite(writeRequest)
+ expect(httpResponse.statusCode).toEqual(HAPHTTPCode.NO_CONTENT)
+ })
- test("test PUT /characteristics value with MULTI_STATUS", async () => {
- const ids: CharacteristicId[] = [ { aid: 1, iid: 9 }, { aid: 2, iid: 14 } ];
+ it('test PUT /characteristics value with MULTI_STATUS', async () => {
+ const ids: CharacteristicId[] = [{ aid: 1, iid: 9 }, { aid: 2, iid: 14 }]
const writeRequest: CharacteristicsWriteRequest = {
characteristics: [
{
...ids[0],
- value: "Hello World",
+ value: 'Hello World',
},
{
...ids[1],
@@ -455,7 +466,7 @@ describe("HAPServer", () => {
},
],
pid: 1337,
- };
+ }
const hapResponse: CharacteristicsWriteResponse = {
characteristics: [
@@ -468,180 +479,175 @@ describe("HAPServer", () => {
status: HAPStatus.SERVICE_COMMUNICATION_FAILURE,
},
],
- };
+ }
server.on(HAPServerEventTypes.SET_CHARACTERISTICS, (connection, request, callback) => {
- expect(connection.encryption).toBeDefined();
- expect(request).toEqual(writeRequest);
- callback(undefined, hapResponse);
- });
+ expect(connection.encryption).toBeDefined()
+ expect(request).toEqual(writeRequest)
+ callback(undefined, hapResponse)
+ })
- const httpResponse = await client.sendCharacteristicWrite(writeRequest);
- expect(httpResponse.statusCode).toEqual(HAPHTTPCode.MULTI_STATUS);
- });
+ const httpResponse = await client.sendCharacteristicWrite(writeRequest)
+ expect(httpResponse.statusCode).toEqual(HAPHTTPCode.MULTI_STATUS)
+ })
- test("test PUT /characteristics value with write response", async () => {
- const ids: CharacteristicId[] = [ { aid: 1, iid: 9 } ];
+ it('test PUT /characteristics value with write response', async () => {
+ const ids: CharacteristicId[] = [{ aid: 1, iid: 9 }]
const writeRequest: CharacteristicsWriteRequest = {
characteristics: [
{
...ids[0],
- value: "Hello",
+ value: 'Hello',
},
],
- };
+ }
const hapResponse: CharacteristicsWriteResponse = {
characteristics: [
{
...ids[0],
status: HAPStatus.SUCCESS,
- value: "World",
+ value: 'World',
},
],
- };
+ }
server.on(HAPServerEventTypes.SET_CHARACTERISTICS, (connection, request, callback) => {
- expect(connection.encryption).toBeDefined();
- expect(request).toEqual(writeRequest);
- callback(undefined, hapResponse);
- });
+ expect(connection.encryption).toBeDefined()
+ expect(request).toEqual(writeRequest)
+ callback(undefined, hapResponse)
+ })
- const httpResponse = await client.sendCharacteristicWrite(writeRequest);
- expect(httpResponse.statusCode).toEqual(HAPHTTPCode.MULTI_STATUS);
- });
+ const httpResponse = await client.sendCharacteristicWrite(writeRequest)
+ expect(httpResponse.statusCode).toEqual(HAPHTTPCode.MULTI_STATUS)
+ })
- test("test prepare write request", async () => {
+ it('test prepare write request', async () => {
await client.sendPrepareWrite({
pid: 1337,
ttl: 1000,
- });
+ })
- await PromiseTimeout(50);
+ await PromiseTimeout(50)
// testing that pid is overwritten!
await client.sendPrepareWrite({
pid: 13337,
ttl: 100,
- });
+ })
// we just do a /accessories request to get hold onto the HAPConnection object
- let hapConnection!: HAPConnection;
+ let hapConnection!: HAPConnection
server.on(HAPServerEventTypes.ACCESSORIES, (connection, callback) => {
- hapConnection = connection;
- callback(undefined, { accessories:[] });
- });
- await client.sendAccessoriesRequest();
+ hapConnection = connection
+ callback(undefined, { accessories: [] })
+ })
+ await client.sendAccessoriesRequest()
- expect(hapConnection.timedWriteTimeout).toBeDefined();
- expect(hapConnection.timedWritePid).toBe(13337);
+ expect(hapConnection.timedWriteTimeout).toBeDefined()
+ expect(hapConnection.timedWritePid).toBe(13337)
- await PromiseTimeout(120);
+ await PromiseTimeout(120)
- expect(hapConnection.timedWriteTimeout).toBeUndefined();
- expect(hapConnection.timedWritePid).toBeUndefined();
- });
+ expect(hapConnection.timedWriteTimeout).toBeUndefined()
+ expect(hapConnection.timedWritePid).toBeUndefined()
+ })
- test("test resource request", async () => {
- const image = crypto.randomBytes(256);
+ it('test resource request', async () => {
+ const image = randomBytes(256)
const request: ResourceRequest = {
- "image-height": 256,
- "image-width": 512,
- "resource-type": ResourceRequestType.IMAGE,
- };
+ 'image-height': 256,
+ 'image-width': 512,
+ 'resource-type': ResourceRequestType.IMAGE,
+ }
server.on(HAPServerEventTypes.REQUEST_RESOURCE, (resource, callback) => {
- expect(resource).toEqual(request);
- callback(undefined, image);
- });
+ expect(resource).toEqual(request)
+ callback(undefined, image)
+ })
- const result = await client.sendResourceRequest(request);
- expect(result).toEqual(image);
- });
- });
+ const result = await client.sendResourceRequest(request)
+ expect(result).toEqual(image)
+ })
+ })
- test.each(["pairings", "accessories", "characteristics", "prepare", "resource"])(
- "request to \"/%s\" should be rejected in unpaired and unverified state",
+ it.each(['pairings', 'accessories', 'characteristics', 'prepare', 'resource'])(
+ 'request to "/%s" should be rejected in unpaired and unverified state',
async (route: string) => {
- server = new HAPServer(accessoryInfoUnpaired);
- const [port] = await bindServer(server);
+ server = new HAPServer(accessoryInfoUnpaired)
+ const [port] = await bindServer(server)
try {
- const response = await axios.post(`http://localhost:${port}/${route}`, { httpAgent });
- fail(`Expected erroneous response, got ${response}`);
+ const response = await axios.post(`http://localhost:${port}/${route}`, { httpAgent })
+ throw new Error(`Expected erroneous response, got ${response}`)
} catch (error) {
- expect(error).toBeInstanceOf(AxiosError);
- expect(error.response?.status).toBe(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED);
- expect(error.response?.data).toEqual({ status: HAPStatus.INSUFFICIENT_PRIVILEGES });
+ expect(error).toBeInstanceOf(AxiosError)
+ expect(error.response?.status).toBe(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED)
+ expect(error.response?.data).toEqual({ status: HAPStatus.INSUFFICIENT_PRIVILEGES })
}
},
- );
+ )
- test.each(["pairings", "accessories", "characteristics", "prepare", "resource"])(
- "request to \"/%s\" should be rejected in paired and unverified state",
+ it.each(['pairings', 'accessories', 'characteristics', 'prepare', 'resource'])(
+ 'request to "/%s" should be rejected in paired and unverified state',
async (route: string) => {
- server = new HAPServer(accessoryInfoPaired);
- const [port] = await bindServer(server);
+ server = new HAPServer(accessoryInfoPaired)
+ const [port] = await bindServer(server)
try {
- const response = await axios.post(`http://localhost:${port}/${route}`, { httpAgent });
- fail(`Expected erroneous response, got ${response}`);
+ const response = await axios.post(`http://localhost:${port}/${route}`, { httpAgent })
+ throw new Error(`Expected erroneous response, got ${response}`)
} catch (error) {
- expect(error).toBeInstanceOf(AxiosError);
- expect(error.response?.status).toBe(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED);
- expect(error.response?.data).toEqual({ status: HAPStatus.INSUFFICIENT_PRIVILEGES });
+ expect(error).toBeInstanceOf(AxiosError)
+ expect(error.response?.status).toBe(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED)
+ expect(error.response?.data).toEqual({ status: HAPStatus.INSUFFICIENT_PRIVILEGES })
}
},
- );
+ )
- test("test non-existence resource", async () => {
- server = new HAPServer(accessoryInfoUnpaired);
- const [port] = await bindServer(server);
+ it('test non-existence resource', async () => {
+ server = new HAPServer(accessoryInfoUnpaired)
+ const [port] = await bindServer(server)
try {
- const response = await axios.post(`http://localhost:${port}/non-existent`, { httpAgent });
- fail(`Expected erroneous response, got ${response}`);
+ const response = await axios.post(`http://localhost:${port}/non-existent`, { httpAgent })
+ throw new Error(`Expected erroneous response, got ${response}`)
} catch (error) {
- expect(error).toBeInstanceOf(AxiosError);
- expect(error.response?.status).toBe(HAPHTTPCode.NOT_FOUND);
- expect(error.response?.data).toEqual({ status: HAPStatus.RESOURCE_DOES_NOT_EXIST });
+ expect(error).toBeInstanceOf(AxiosError)
+ expect(error.response?.status).toBe(HAPHTTPCode.NOT_FOUND)
+ expect(error.response?.data).toEqual({ status: HAPStatus.RESOURCE_DOES_NOT_EXIST })
}
- });
+ })
+})
-});
-
-describe(IsKnownHAPStatusError, () => {
- it("should approve all defined error codes", () => {
+describe(isKnownHAPStatusError, () => {
+ it('should approve all defined error codes', () => {
// @ts-expect-error: forceConsistentCasingInFileNames compiler option
const errorValues = Object.values(HAPStatus)
- .filter(error => typeof error === "number") // .values will actually include both the enum values and enum names
- .filter(error => error !== 0); // filter out HAPStatus.SUCCESS
+ .filter(error => typeof error === 'number') // .values will actually include both the enum values and enum names
+ .filter(error => error !== 0) // filter out HAPStatus.SUCCESS
for (const error of errorValues) {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore:next-line - This was @ts-expect-error: type mismatch, but it triggered build errors
- // Summary of all failing tests src/lib/HAPServer.spec.ts:621:7 - error TS2578: Unused '@ts-expect-error' directive.
-
- const result = IsKnownHAPStatusError(error);
+ const result = isKnownHAPStatusError(error)
if (!result) {
- fail("IsKnownHAPStatusError does not return true for error code " + error);
+ throw new Error(`isKnownHAPStatusError does not return true for error code ${error}`)
}
}
- });
+ })
- it("should reject non defined error codes", () => {
- expect(IsKnownHAPStatusError(23 as HAPStatus)).toBe(false);
- expect(IsKnownHAPStatusError(-3 as HAPStatus)).toBe(false);
- expect(IsKnownHAPStatusError(-72037 as HAPStatus)).toBe(false);
- expect(IsKnownHAPStatusError(HAPStatus.SUCCESS as HAPStatus)).toBe(false);
- });
+ it('should reject non defined error codes', () => {
+ expect(isKnownHAPStatusError(23 as HAPStatus)).toBe(false)
+ expect(isKnownHAPStatusError(-3 as HAPStatus)).toBe(false)
+ expect(isKnownHAPStatusError(-72037 as HAPStatus)).toBe(false)
+ expect(isKnownHAPStatusError(HAPStatus.SUCCESS as HAPStatus)).toBe(false)
+ })
- it("should reject invalid user input", () => {
+ it('should reject invalid user input', () => {
// @ts-expect-error: deliberate illegal input
- expect(IsKnownHAPStatusError("aaaa")).toBe(false);
+ expect(isKnownHAPStatusError('aaaa')).toBe(false)
// @ts-expect-error: deliberate illegal input
- expect(IsKnownHAPStatusError({ "key": "value" })).toBe(false);
+ expect(isKnownHAPStatusError({ key: 'value' })).toBe(false)
// @ts-expect-error: deliberate illegal input
- expect(IsKnownHAPStatusError([])).toBe(false);
- });
-});
+ expect(isKnownHAPStatusError([])).toBe(false)
+ })
+})
diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts
index de3e29fa6..161333d5f 100644
--- a/src/lib/HAPServer.ts
+++ b/src/lib/HAPServer.ts
@@ -1,12 +1,6 @@
-import crypto from "crypto";
-import createDebug from "debug";
-import { EventEmitter } from "events";
-import { SRP, SrpServer } from "fast-srp-hap";
-import { IncomingMessage, ServerResponse } from "http";
-import tweetnacl from "tweetnacl";
-import { URL } from "url";
-import { consideredTrue, HAPMimeTypes, PairingStates, PairMethods, TLVValues } from "../internal-types";
-import {
+import type { IncomingMessage, ServerResponse } from 'node:http'
+
+import type {
AccessoriesResponse,
CharacteristicId,
CharacteristicsReadRequest,
@@ -18,38 +12,56 @@ import {
PrepareWriteRequest,
ResourceRequest,
VoidCallback,
-} from "../types";
-import { AccessoryInfo, PairingInformation, PermissionTypes } from "./model/AccessoryInfo";
-import { EventedHTTPServer, EventedHTTPServerEvent, HAPConnection, HAPEncryption, HAPUsername } from "./util/eventedhttp";
-import * as hapCrypto from "./util/hapCrypto";
-import { once } from "./util/once";
-import * as tlv from "./util/tlv";
+} from '../types'
+import type { AccessoryInfo, PairingInformation, PermissionTypes } from './model/AccessoryInfo'
+import type { HAPConnection, HAPUsername } from './util/eventedhttp'
+
+import { Buffer } from 'node:buffer'
+import { randomBytes } from 'node:crypto'
+import { EventEmitter } from 'node:events'
+import { URL } from 'node:url'
+
+import createDebug from 'debug'
+import { SRP, SrpServer } from 'fast-srp-hap'
+import tweetnacl from 'tweetnacl'
-const debug = createDebug("HAP-NodeJS:HAPServer");
+import { consideredTrue, HAPMimeTypes, PairingStates, PairMethods, TLVValues } from '../internal-types.js'
+import { EventedHTTPServer, EventedHTTPServerEvent, HAPEncryption } from './util/eventedhttp.js'
+import {
+ chacha20_poly1305_decryptAndVerify,
+ chacha20_poly1305_encryptAndSeal,
+ generateCurve25519KeyPair,
+ generateCurve25519SharedSecKey,
+ HKDF,
+} from './util/hapCrypto.js'
+import { once } from './util/once.js'
+import { decode, encode } from './util/tlv.js'
+
+const debug = createDebug('HAP-NodeJS:HAPServer')
/**
* TLV error codes for the `TLVValues.ERROR_CODE` field.
*
* @group HAP Accessory Server
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum TLVErrorCode {
- // noinspection JSUnusedGlobalSymbols
UNKNOWN = 0x01,
INVALID_REQUEST = 0x02,
- // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
+
+ // eslint-disable-next-line ts/no-duplicate-enum-values
AUTHENTICATION = 0x02, // setup code or signature verification failed
BACKOFF = 0x03, // // client must look at retry delay tlv item
MAX_PEERS = 0x04, // server cannot accept any more pairings
MAX_TRIES = 0x05, // server reached maximum number of authentication attempts
UNAVAILABLE = 0x06, // server pairing method is unavailable
- BUSY = 0x07 // cannot accept pairing request at this time
+ BUSY = 0x07, // cannot accept pairing request at this time
}
/**
* @group HAP Accessory Server
*/
-export const enum HAPStatus {
- // noinspection JSUnusedGlobalSymbols
+export enum HAPStatus {
/**
* Success of the request.
@@ -104,7 +116,7 @@ export const enum HAPStatus {
*/
NOT_ALLOWED_IN_CURRENT_STATE = -70412,
- // when adding new status codes, remember to update bounds in IsKnownHAPStatusError below
+ // when adding new status codes, remember to update bounds in isKnownHAPStatusError below
}
/**
@@ -112,13 +124,13 @@ export const enum HAPStatus {
*
* @group HAP Accessory Server
*/
-export function IsKnownHAPStatusError(status: HAPStatus): boolean {
+export function isKnownHAPStatusError(status: HAPStatus): boolean {
return (
// Lower bound (most negative error code)
- status >= HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE &&
+ status >= HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE
// Upper bound (negative error code closest to zero)
- status <= HAPStatus.INSUFFICIENT_PRIVILEGES
- );
+ && status <= HAPStatus.INSUFFICIENT_PRIVILEGES
+ )
}
/**
@@ -131,8 +143,8 @@ export function IsKnownHAPStatusError(status: HAPStatus): boolean {
*
* @group HAP Accessory Server
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum HAPHTTPCode {
- // noinspection JSUnusedGlobalSymbols
OK = 200,
NO_CONTENT = 204,
MULTI_STATUS = 207,
@@ -153,8 +165,8 @@ export const enum HAPHTTPCode {
*
* @group HAP Accessory Server
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum HAPPairingHTTPCode {
- // noinspection JSUnusedGlobalSymbols
OK = 200,
BAD_REQUEST = 400, // e.g. bad tlv, state errors, etc
@@ -165,153 +177,155 @@ export const enum HAPPairingHTTPCode {
INTERNAL_SERVER_ERROR = 500,
}
-type HAPRequestHandler = (connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse) => void;
+type HAPRequestHandler = (connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse) => void
/**
* @group HAP Accessory Server
*/
-export type IdentifyCallback = VoidCallback;
+export type IdentifyCallback = VoidCallback
/**
* @group HAP Accessory Server
*/
-export type HAPHttpError = { httpCode: HAPHTTPCode, status: HAPStatus};
+export interface HAPHttpError { httpCode: HAPHTTPCode, status: HAPStatus }
/**
* @group HAP Accessory Server
*/
-export type PairingsCallback = (error: TLVErrorCode | 0, data?: T) => void;
+export type PairingsCallback = (error: TLVErrorCode | 0, data?: T) => void
/**
* @group HAP Accessory Server
*/
-export type AddPairingCallback = PairingsCallback;
+export type AddPairingCallback = PairingsCallback
/**
* @group HAP Accessory Server
*/
-export type RemovePairingCallback = PairingsCallback;
+export type RemovePairingCallback = PairingsCallback
/**
* @group HAP Accessory Server
*/
-export type ListPairingsCallback = PairingsCallback;
+export type ListPairingsCallback = PairingsCallback
/**
* @group HAP Accessory Server
*/
-export type PairCallback = VoidCallback;
+export type PairCallback = VoidCallback
/**
* @group HAP Accessory Server
*/
-export type AccessoriesCallback = (error: HAPHttpError | undefined, result?: AccessoriesResponse) => void;
+export type AccessoriesCallback = (error: HAPHttpError | undefined, result?: AccessoriesResponse) => void
/**
* @group HAP Accessory Server
*/
-export type ReadCharacteristicsCallback = (error: HAPHttpError | undefined, response?: CharacteristicsReadResponse) => void;
+export type ReadCharacteristicsCallback = (error: HAPHttpError | undefined, response?: CharacteristicsReadResponse) => void
/**
* @group HAP Accessory Server
*/
-export type WriteCharacteristicsCallback = (error: HAPHttpError | undefined, response?: CharacteristicsWriteResponse) => void;
+export type WriteCharacteristicsCallback = (error: HAPHttpError | undefined, response?: CharacteristicsWriteResponse) => void
/**
* @group HAP Accessory Server
*/
-export type ResourceRequestCallback = (error: HAPHttpError | undefined, resource?: Buffer) => void;
+export type ResourceRequestCallback = (error: HAPHttpError | undefined, resource?: Buffer) => void
/**
* @group HAP Accessory Server
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum HAPServerEventTypes {
/**
* Emitted when the server is fully set up and ready to receive connections.
*/
- LISTENING = "listening",
+ LISTENING = 'listening',
/**
* Emitted when a client wishes for this server to identify itself before pairing. You must call the
* callback to respond to the client with success.
*/
- IDENTIFY = "identify",
- ADD_PAIRING = "add-pairing",
- REMOVE_PAIRING = "remove-pairing",
- LIST_PAIRINGS = "list-pairings",
+ IDENTIFY = 'identify',
+ ADD_PAIRING = 'add-pairing',
+ REMOVE_PAIRING = 'remove-pairing',
+ LIST_PAIRINGS = 'list-pairings',
/**
* This event is emitted when a client completes the "pairing" process and exchanges encryption keys.
* Note that this does not mean the "Add Accessory" process in iOS has completed.
* You must call the callback to complete the process.
*/
- PAIR = "pair",
+ PAIR = 'pair',
/**
* This event is emitted when a client requests the complete representation of Accessory data for
* this Accessory (for instance, what services, characteristics, etc. are supported) and any bridged
* Accessories in the case of a Bridge Accessory. The listener must call the provided callback function
* when the accessory data is ready. We will automatically JSON.stringify the data.
*/
- ACCESSORIES = "accessories",
+ ACCESSORIES = 'accessories',
/**
* This event is emitted when a client wishes to retrieve the current value of one or more characteristics.
* The listener must call the provided callback function when the values are ready. iOS clients can typically
* wait up to 10 seconds for this call to return. We will automatically JSON.stringify the data (which must
* be an array) and wrap it in an object with a top-level "characteristics" property.
*/
- GET_CHARACTERISTICS = "get-characteristics",
+ GET_CHARACTERISTICS = 'get-characteristics',
/**
* This event is emitted when a client wishes to set the current value of one or more characteristics and/or
* subscribe to one or more events. The 'events' param is an initially-empty object, associated with the current
* connection, on which you may store event registration keys for later processing. The listener must call
* the provided callback when the request has been processed.
*/
- SET_CHARACTERISTICS = "set-characteristics",
- REQUEST_RESOURCE = "request-resource",
- CONNECTION_CLOSED = "connection-closed",
+ SET_CHARACTERISTICS = 'set-characteristics',
+ REQUEST_RESOURCE = 'request-resource',
+ CONNECTION_CLOSED = 'connection-closed',
}
/**
* @group HAP Accessory Server
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export declare interface HAPServer {
- on(event: "listening", listener: (port: number, address: string) => void): this;
- on(event: "identify", listener: (callback: IdentifyCallback) => void): this;
+ /* eslint-disable ts/method-signature-style */
+ on(event: 'listening', listener: (port: number, address: string) => void): this
+ on(event: 'identify', listener: (callback: IdentifyCallback) => void): this
on(
- event: "add-pairing",
+ event: 'add-pairing',
listener: (connection: HAPConnection, username: HAPUsername, publicKey: Buffer, permission: PermissionTypes, callback: AddPairingCallback) => void
- ): this;
- on(event: "remove-pairing", listener: (connection: HAPConnection, username: HAPUsername, callback: RemovePairingCallback) => void): this;
- on(event: "list-pairings", listener: (connection: HAPConnection, callback: ListPairingsCallback) => void): this;
- on(event: "pair", listener: (username: HAPUsername, clientLTPK: Buffer, callback: PairCallback) => void): this;
+ ): this
+ on(event: 'remove-pairing', listener: (connection: HAPConnection, username: HAPUsername, callback: RemovePairingCallback) => void): this
+ on(event: 'list-pairings', listener: (connection: HAPConnection, callback: ListPairingsCallback) => void): this
+ on(event: 'pair', listener: (username: HAPUsername, clientLTPK: Buffer, callback: PairCallback) => void): this
- on(event: "accessories", listener: (connection: HAPConnection, callback: AccessoriesCallback) => void): this;
+ on(event: 'accessories', listener: (connection: HAPConnection, callback: AccessoriesCallback) => void): this
on(
- event: "get-characteristics",
+ event: 'get-characteristics',
listener: (connection: HAPConnection, request: CharacteristicsReadRequest, callback: ReadCharacteristicsCallback) => void
- ): this;
+ ): this
on(
- event: "set-characteristics",
+ event: 'set-characteristics',
listener: (connection: HAPConnection, request: CharacteristicsWriteRequest, callback: WriteCharacteristicsCallback) => void
- ): this;
- on(event: "request-resource", listener: (resource: ResourceRequest, callback: ResourceRequestCallback) => void): this;
-
- on(event: "connection-closed", listener: (connection: HAPConnection) => void): this;
+ ): this
+ on(event: 'request-resource', listener: (resource: ResourceRequest, callback: ResourceRequestCallback) => void): this
+ on(event: 'connection-closed', listener: (connection: HAPConnection) => void): this
- emit(event: "listening", port: number, address: string): boolean;
- emit(event: "identify", callback : IdentifyCallback): boolean;
+ emit(event: 'listening', port: number, address: string): boolean
+ emit(event: 'identify', callback: IdentifyCallback): boolean
emit(
- event: "add-pairing",
+ event: 'add-pairing',
connection: HAPConnection,
username: HAPUsername,
publicKey: Buffer,
permission: PermissionTypes,
callback: AddPairingCallback
- ): boolean;
- emit(event: "remove-pairing", connection: HAPConnection, username: HAPUsername, callback: RemovePairingCallback): boolean;
- emit(event: "list-pairings", connection: HAPConnection, callback: ListPairingsCallback): boolean;
- emit(event: "pair", username: HAPUsername, clientLTPK: Buffer, callback: PairCallback): boolean;
-
- emit(event: "accessories", connection: HAPConnection, callback : AccessoriesCallback): boolean;
- emit(event: "get-characteristics", connection: HAPConnection, request: CharacteristicsReadRequest, callback: ReadCharacteristicsCallback): boolean;
- emit(event: "set-characteristics", connection: HAPConnection, request: CharacteristicsWriteRequest, callback: WriteCharacteristicsCallback): boolean;
- emit(event: "request-resource", resource: ResourceRequest, callback: ResourceRequestCallback): boolean;
-
- emit(event: "connection-closed", connection: HAPConnection): boolean;
+ ): boolean
+ emit(event: 'remove-pairing', connection: HAPConnection, username: HAPUsername, callback: RemovePairingCallback): boolean
+ emit(event: 'list-pairings', connection: HAPConnection, callback: ListPairingsCallback): boolean
+ emit(event: 'pair', username: HAPUsername, clientLTPK: Buffer, callback: PairCallback): boolean
+
+ emit(event: 'accessories', connection: HAPConnection, callback: AccessoriesCallback): boolean
+ emit(event: 'get-characteristics', connection: HAPConnection, request: CharacteristicsReadRequest, callback: ReadCharacteristicsCallback): boolean
+ emit(event: 'set-characteristics', connection: HAPConnection, request: CharacteristicsWriteRequest, callback: WriteCharacteristicsCallback): boolean
+ emit(event: 'request-resource', resource: ResourceRequest, callback: ResourceRequestCallback): boolean
+
+ emit(event: 'connection-closed', connection: HAPConnection): boolean
+ /* eslint-enable ts/method-signature-style */
}
/**
@@ -335,43 +349,42 @@ export declare interface HAPServer {
*
* @group HAP Accessory Server
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export class HAPServer extends EventEmitter {
+ private accessoryInfo: AccessoryInfo
+ private httpServer: EventedHTTPServer
+ private unsuccessfulPairAttempts = 0 // after 100 unsuccessful attempts the server won't accept any further attempts. Will currently be reset on a reboot
- private accessoryInfo: AccessoryInfo;
- private httpServer: EventedHTTPServer;
- private unsuccessfulPairAttempts = 0; // after 100 unsuccessful attempts the server won't accept any further attempts. Will currently be reset on a reboot
-
- allowInsecureRequest: boolean;
+ allowInsecureRequest: boolean
constructor(accessoryInfo: AccessoryInfo) {
- super();
- this.accessoryInfo = accessoryInfo;
- this.allowInsecureRequest = false;
+ super()
+ this.accessoryInfo = accessoryInfo
+ this.allowInsecureRequest = false
// internal server that does all the actual communication
- this.httpServer = new EventedHTTPServer();
- this.httpServer.on(EventedHTTPServerEvent.LISTENING, this.onListening.bind(this));
- this.httpServer.on(EventedHTTPServerEvent.REQUEST, this.handleRequestOnHAPConnection.bind(this));
- this.httpServer.on(EventedHTTPServerEvent.CONNECTION_CLOSED, this.handleConnectionClosed.bind(this));
+ this.httpServer = new EventedHTTPServer()
+ this.httpServer.on(EventedHTTPServerEvent.LISTENING, this.onListening.bind(this))
+ this.httpServer.on(EventedHTTPServerEvent.REQUEST, this.handleRequestOnHAPConnection.bind(this))
+ this.httpServer.on(EventedHTTPServerEvent.CONNECTION_CLOSED, this.handleConnectionClosed.bind(this))
}
public listen(port = 0, host?: string): void {
- if (host === "::") {
+ if (host === '::') {
// this will work around "EAFNOSUPPORT: address family not supported" errors
// on systems where IPv6 is not supported/enabled, we just use the node default then by supplying undefined
- host = undefined;
+ host = undefined
}
- this.httpServer.listen(port, host);
+ this.httpServer.listen(port, host)
}
public stop(): void {
- this.httpServer.stop();
+ this.httpServer.stop()
}
public destroy(): void {
- this.stop();
- this.removeAllListeners();
+ this.stop()
+ this.removeAllListeners()
}
/**
@@ -383,72 +396,72 @@ export class HAPServer extends EventEmitter {
* @param value - The newly set value of the characteristic.
* @param originator - If specified, the connection will not get an event message.
* @param immediateDelivery - The HAP spec requires some characteristics to be delivery immediately.
- * Namely, for the {@link Characteristic.ButtonEvent} and the {@link Characteristic.ProgrammableSwitchEvent} characteristics.
+ * Namely, for the {@link Characteristic.ButtonEvent} and the {@link Characteristic.ProgrammableSwitchEvent} characteristics.
*/
public sendEventNotifications(aid: number, iid: number, value: Nullable, originator?: HAPConnection, immediateDelivery?: boolean): void {
try {
- this.httpServer.broadcastEvent(aid, iid, value, originator, immediateDelivery);
+ this.httpServer.broadcastEvent(aid, iid, value, originator, immediateDelivery)
} catch (error) {
- console.warn("[" + this.accessoryInfo.username + "] Error when sending event notifications: " + error.message);
+ console.warn(`[${this.accessoryInfo.username}] Error when sending event notifications: ${error.message}`)
}
}
private onListening(port: number, hostname: string): void {
- this.emit(HAPServerEventTypes.LISTENING, port, hostname);
+ this.emit(HAPServerEventTypes.LISTENING, port, hostname)
}
// Called when an HTTP request was detected.
private handleRequestOnHAPConnection(connection: HAPConnection, request: IncomingMessage, response: ServerResponse): void {
- debug("[%s] HAP Request: %s %s", this.accessoryInfo.username, request.method, request.url);
- const buffers: Buffer[] = [];
- request.on("data", data => buffers.push(data));
+ debug('[%s] HAP Request: %s %s', this.accessoryInfo.username, request.method, request.url)
+ const buffers: Buffer[] = []
+ request.on('data', data => buffers.push(data))
- request.on("end", () => {
- const url = new URL(request.url!, "http://hap-nodejs.local"); // parse the url (query strings etc)
+ request.on('end', () => {
+ const url = new URL(request.url!, 'http://hap-nodejs.local') // parse the url (query strings etc)
- const handler = this.getHandler(url);
+ const handler = this.getHandler(url)
if (!handler) {
- debug("[%s] WARNING: Handler for %s not implemented", this.accessoryInfo.username, request.url);
- response.writeHead(HAPHTTPCode.NOT_FOUND, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.RESOURCE_DOES_NOT_EXIST }));
+ debug('[%s] WARNING: Handler for %s not implemented', this.accessoryInfo.username, request.url)
+ response.writeHead(HAPHTTPCode.NOT_FOUND, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.RESOURCE_DOES_NOT_EXIST }))
} else {
- const data = Buffer.concat(buffers);
+ const data = Buffer.concat(buffers)
try {
- handler(connection, url, request, data, response);
+ handler(connection, url, request, data, response)
} catch (error) {
- debug("[%s] Error executing route handler: %s", this.accessoryInfo.username, error.stack);
- response.writeHead(HAPHTTPCode.INTERNAL_SERVER_ERROR, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.RESOURCE_BUSY })); // resource busy try again, does somehow fit?
+ debug('[%s] Error executing route handler: %s', this.accessoryInfo.username, error.stack)
+ response.writeHead(HAPHTTPCode.INTERNAL_SERVER_ERROR, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.RESOURCE_BUSY })) // resource busy try again, does somehow fit?
}
}
- });
+ })
}
private handleConnectionClosed(connection: HAPConnection): void {
- this.emit(HAPServerEventTypes.CONNECTION_CLOSED, connection);
+ this.emit(HAPServerEventTypes.CONNECTION_CLOSED, connection)
}
private getHandler(url: URL): HAPRequestHandler | undefined {
switch (url.pathname.toLowerCase()) {
- case "/identify":
- return this.handleIdentifyRequest.bind(this);
- case "/pair-setup":
- return this.handlePairSetup.bind(this);
- case "/pair-verify":
- return this.handlePairVerify.bind(this);
- case "/pairings":
- return this.handlePairings.bind(this);
- case "/accessories":
- return this.handleAccessories.bind(this);
- case "/characteristics":
- return this.handleCharacteristics.bind(this);
- case "/prepare":
- return this.handlePrepareWrite.bind(this);
- case "/resource":
- return this.handleResource.bind(this);
- default:
- return undefined;
+ case '/identify':
+ return this.handleIdentifyRequest.bind(this)
+ case '/pair-setup':
+ return this.handlePairSetup.bind(this)
+ case '/pair-verify':
+ return this.handlePairVerify.bind(this)
+ case '/pairings':
+ return this.handlePairings.bind(this)
+ case '/accessories':
+ return this.handleAccessories.bind(this)
+ case '/characteristics':
+ return this.handleCharacteristics.bind(this)
+ case '/prepare':
+ return this.handlePrepareWrite.bind(this)
+ case '/resource':
+ return this.handleResource.bind(this)
+ default:
+ return undefined
}
}
@@ -458,131 +471,129 @@ export class HAPServer extends EventEmitter {
private handleIdentifyRequest(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void {
// POST body is empty
if (this.accessoryInfo.paired() && !this.allowInsecureRequest) {
- response.writeHead(HAPHTTPCode.BAD_REQUEST, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }));
- return;
+ response.writeHead(HAPHTTPCode.BAD_REQUEST, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }))
+ return
}
- this.emit(HAPServerEventTypes.IDENTIFY, once(err => {
+ this.emit(HAPServerEventTypes.IDENTIFY, once((err) => {
if (!err) {
- debug("[%s] Identification success", this.accessoryInfo.username);
- response.writeHead(HAPHTTPCode.NO_CONTENT);
- response.end();
+ debug('[%s] Identification success', this.accessoryInfo.username)
+ response.writeHead(HAPHTTPCode.NO_CONTENT)
+ response.end()
} else {
- debug("[%s] Identification error: %s", this.accessoryInfo.username, err.message);
- response.writeHead(HAPHTTPCode.INTERNAL_SERVER_ERROR, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.RESOURCE_BUSY }));
+ debug('[%s] Identification error: %s', this.accessoryInfo.username, err.message)
+ response.writeHead(HAPHTTPCode.INTERNAL_SERVER_ERROR, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.RESOURCE_BUSY }))
}
- }));
+ }))
}
private handlePairSetup(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void {
// Can only be directly paired with one iOS device
if (!this.allowInsecureRequest && this.accessoryInfo.paired()) {
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, TLVErrorCode.UNAVAILABLE));
- return;
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, TLVErrorCode.UNAVAILABLE))
+ return
}
if (this.unsuccessfulPairAttempts > 100) {
- debug("[%s] Reached maximum amount of unsuccessful pair attempts!", this.accessoryInfo.username);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, TLVErrorCode.MAX_TRIES));
- return;
+ debug('[%s] Reached maximum amount of unsuccessful pair attempts!', this.accessoryInfo.username)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, TLVErrorCode.MAX_TRIES))
+ return
}
- const tlvData = tlv.decode(data);
- const sequence = tlvData[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number
+ const tlvData = decode(data)
+ const sequence = tlvData[TLVValues.SEQUENCE_NUM][0] // value is single byte with sequence number
if (sequence === PairingStates.M1) {
- this.handlePairSetupM1(connection, request, response);
+ this.handlePairSetupM1(connection, request, response)
} else if (sequence === PairingStates.M3 && connection._pairSetupState === PairingStates.M2) {
- this.handlePairSetupM3(connection, request, response, tlvData);
+ this.handlePairSetupM3(connection, request, response, tlvData)
} else if (sequence === PairingStates.M5 && connection._pairSetupState === PairingStates.M4) {
- this.handlePairSetupM5(connection, request, response, tlvData);
+ this.handlePairSetupM5(connection, request, response, tlvData)
} else {
// Invalid state/sequence number
- response.writeHead(HAPPairingHTTPCode.BAD_REQUEST, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.STATE, sequence + 1, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN));
- return;
+ response.writeHead(HAPPairingHTTPCode.BAD_REQUEST, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.STATE, sequence + 1, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN))
}
}
private handlePairSetupM1(connection: HAPConnection, request: IncomingMessage, response: ServerResponse): void {
- debug("[%s] Pair step 1/5", this.accessoryInfo.username);
- const salt = crypto.randomBytes(16 );
+ debug('[%s] Pair step 1/5', this.accessoryInfo.username)
+ const salt = randomBytes(16)
- const srpParams = SRP.params.hap;
- SRP.genKey(32).then(key => {
+ const srpParams = SRP.params.hap
+ SRP.genKey(32).then((key) => {
// create a new SRP server
- const srpServer = new SrpServer(srpParams, salt, Buffer.from("Pair-Setup"), Buffer.from(this.accessoryInfo.pincode), key);
- const srpB = srpServer.computeB();
+ const srpServer = new SrpServer(srpParams, salt, Buffer.from('Pair-Setup'), Buffer.from(this.accessoryInfo.pincode), key)
+ const srpB = srpServer.computeB()
// attach it to the current TCP session
- connection.srpServer = srpServer;
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M2, TLVValues.SALT, salt, TLVValues.PUBLIC_KEY, srpB));
- connection._pairSetupState = PairingStates.M2;
- }).catch(error => {
- debug("[%s] Error occurred when generating srp key: %s", this.accessoryInfo.username, error.message);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN));
- return;
- });
+ connection.srpServer = srpServer
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.SEQUENCE_NUM, PairingStates.M2, TLVValues.SALT, salt, TLVValues.PUBLIC_KEY, srpB))
+ connection._pairSetupState = PairingStates.M2
+ }).catch((error) => {
+ debug('[%s] Error occurred when generating srp key: %s', this.accessoryInfo.username, error.message)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN))
+ })
}
private handlePairSetupM3(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, tlvData: Record): void {
- debug("[%s] Pair step 2/5", this.accessoryInfo.username);
- const A = tlvData[TLVValues.PUBLIC_KEY]; // "A is a public key that exists only for a single login session."
- const M1 = tlvData[TLVValues.PASSWORD_PROOF]; // "M1 is the proof that you actually know your own password."
+ debug('[%s] Pair step 2/5', this.accessoryInfo.username)
+ const A = tlvData[TLVValues.PUBLIC_KEY] // "A is a public key that exists only for a single login session."
+ const M1 = tlvData[TLVValues.PASSWORD_PROOF] // "M1 is the proof that you actually know your own password."
// pull the SRP server we created in stepOne out of the current session
- const srpServer = connection.srpServer!;
- srpServer.setA(A);
+ const srpServer = connection.srpServer!
+ srpServer.setA(A)
try {
- srpServer.checkM1(M1);
+ srpServer.checkM1(M1)
} catch (err) {
// most likely the client supplied an incorrect pincode.
- this.unsuccessfulPairAttempts++;
- debug("[%s] Error while checking pincode: %s", this.accessoryInfo.username, err.message);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION));
- connection._pairSetupState = undefined;
- return;
+ this.unsuccessfulPairAttempts++
+ debug('[%s] Error while checking pincode: %s', this.accessoryInfo.username, err.message)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.SEQUENCE_NUM, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION))
+ connection._pairSetupState = undefined
+ return
}
// "M2 is the proof that the server actually knows your password."
- const M2 = srpServer.computeM2();
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M4, TLVValues.PASSWORD_PROOF, M2));
- connection._pairSetupState = PairingStates.M4;
+ const M2 = srpServer.computeM2()
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.SEQUENCE_NUM, PairingStates.M4, TLVValues.PASSWORD_PROOF, M2))
+ connection._pairSetupState = PairingStates.M4
}
private handlePairSetupM5(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, tlvData: Record): void {
- debug("[%s] Pair step 3/5", this.accessoryInfo.username);
+ debug('[%s] Pair step 3/5', this.accessoryInfo.username)
// pull the SRP server we created in stepOne out of the current session
- const srpServer = connection.srpServer!;
- const encryptedData = tlvData[TLVValues.ENCRYPTED_DATA];
- const messageData = Buffer.alloc(encryptedData.length - 16);
- const authTagData = Buffer.alloc(16);
- encryptedData.copy(messageData, 0, 0, encryptedData.length - 16);
- encryptedData.copy(authTagData, 0, encryptedData.length - 16, encryptedData.length);
- const S_private = srpServer.computeK();
- const encSalt = Buffer.from("Pair-Setup-Encrypt-Salt");
- const encInfo = Buffer.from("Pair-Setup-Encrypt-Info");
- const outputKey = hapCrypto.HKDF("sha512", encSalt, S_private, encInfo, 32);
-
- let plaintext;
+ const srpServer = connection.srpServer!
+ const encryptedData = tlvData[TLVValues.ENCRYPTED_DATA]
+ const messageData = Buffer.alloc(encryptedData.length - 16)
+ const authTagData = Buffer.alloc(16)
+ encryptedData.copy(messageData, 0, 0, encryptedData.length - 16)
+ encryptedData.copy(authTagData, 0, encryptedData.length - 16, encryptedData.length)
+ const S_private = srpServer.computeK()
+ const encSalt = Buffer.from('Pair-Setup-Encrypt-Salt')
+ const encInfo = Buffer.from('Pair-Setup-Encrypt-Info')
+ const outputKey = HKDF('sha512', encSalt, S_private, encInfo, 32)
+
+ let plaintext
try {
- plaintext = hapCrypto.chacha20_poly1305_decryptAndVerify(outputKey, Buffer.from("PS-Msg05"), null, messageData, authTagData);
+ plaintext = chacha20_poly1305_decryptAndVerify(outputKey, Buffer.from('PS-Msg05'), null, messageData, authTagData)
} catch (error) {
- debug("[%s] Error while decrypting and verifying M5 subTlv: %s", this.accessoryInfo.username);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION));
- connection._pairSetupState = undefined;
- return;
+ debug('[%s] Error while decrypting and verifying M5 subTlv: %s', this.accessoryInfo.username)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.SEQUENCE_NUM, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION))
+ connection._pairSetupState = undefined
+ return
}
// decode the client payload and pass it on to the next step
- const M5Packet = tlv.decode(plaintext);
- const clientUsername = M5Packet[TLVValues.USERNAME];
- const clientLTPK = M5Packet[TLVValues.PUBLIC_KEY];
- const clientProof = M5Packet[TLVValues.PROOF];
- this.handlePairSetupM5_2(connection, request, response, clientUsername, clientLTPK, clientProof, outputKey);
+ const M5Packet = decode(plaintext)
+ const clientUsername = M5Packet[TLVValues.USERNAME]
+ const clientLTPK = M5Packet[TLVValues.PUBLIC_KEY]
+ const clientProof = M5Packet[TLVValues.PROOF]
+ this.handlePairSetupM5_2(connection, request, response, clientUsername, clientLTPK, clientProof, outputKey)
}
// M5-2
@@ -595,20 +606,20 @@ export class HAPServer extends EventEmitter {
clientProof: Buffer,
hkdfEncKey: Buffer,
): void {
- debug("[%s] Pair step 4/5", this.accessoryInfo.username);
- const S_private = connection.srpServer!.computeK();
- const controllerSalt = Buffer.from("Pair-Setup-Controller-Sign-Salt");
- const controllerInfo = Buffer.from("Pair-Setup-Controller-Sign-Info");
- const outputKey = hapCrypto.HKDF("sha512", controllerSalt, S_private, controllerInfo, 32);
- const completeData = Buffer.concat([outputKey, clientUsername, clientLTPK]);
+ debug('[%s] Pair step 4/5', this.accessoryInfo.username)
+ const S_private = connection.srpServer!.computeK()
+ const controllerSalt = Buffer.from('Pair-Setup-Controller-Sign-Salt')
+ const controllerInfo = Buffer.from('Pair-Setup-Controller-Sign-Info')
+ const outputKey = HKDF('sha512', controllerSalt, S_private, controllerInfo, 32)
+ const completeData = Buffer.concat([outputKey, clientUsername, clientLTPK])
if (!tweetnacl.sign.detached.verify(completeData, clientProof, clientLTPK)) {
- debug("[%s] Invalid signature", this.accessoryInfo.username);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION));
- connection._pairSetupState = undefined;
- return;
+ debug('[%s] Invalid signature', this.accessoryInfo.username)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION))
+ connection._pairSetupState = undefined
+ return
}
- this.handlePairSetupM5_3(connection, request, response, clientUsername, clientLTPK, hkdfEncKey);
+ this.handlePairSetupM5_3(connection, request, response, clientUsername, clientLTPK, hkdfEncKey)
}
// M5 - F + M6
@@ -620,275 +631,279 @@ export class HAPServer extends EventEmitter {
clientLTPK: Buffer,
hkdfEncKey: Buffer,
): void {
- debug("[%s] Pair step 5/5", this.accessoryInfo.username);
- const S_private = connection.srpServer!.computeK();
- const accessorySalt = Buffer.from("Pair-Setup-Accessory-Sign-Salt");
- const accessoryInfo = Buffer.from("Pair-Setup-Accessory-Sign-Info");
- const outputKey = hapCrypto.HKDF("sha512", accessorySalt, S_private, accessoryInfo, 32);
- const serverLTPK = this.accessoryInfo.signPk;
- const usernameData = Buffer.from(this.accessoryInfo.username);
- const material = Buffer.concat([outputKey, usernameData, serverLTPK]);
- const privateKey = Buffer.from(this.accessoryInfo.signSk);
- const serverProof = tweetnacl.sign.detached(material, privateKey);
- const message = tlv.encode(TLVValues.USERNAME, usernameData, TLVValues.PUBLIC_KEY, serverLTPK, TLVValues.PROOF, serverProof);
-
- const encrypted = hapCrypto.chacha20_poly1305_encryptAndSeal(hkdfEncKey, Buffer.from("PS-Msg06"), null, message);
+ debug('[%s] Pair step 5/5', this.accessoryInfo.username)
+ const S_private = connection.srpServer!.computeK()
+ const accessorySalt = Buffer.from('Pair-Setup-Accessory-Sign-Salt')
+ const accessoryInfo = Buffer.from('Pair-Setup-Accessory-Sign-Info')
+ const outputKey = HKDF('sha512', accessorySalt, S_private, accessoryInfo, 32)
+ const serverLTPK = this.accessoryInfo.signPk
+ const usernameData = Buffer.from(this.accessoryInfo.username)
+ const material = Buffer.concat([outputKey, usernameData, serverLTPK])
+ const privateKey = Buffer.from(this.accessoryInfo.signSk)
+ const serverProof = tweetnacl.sign.detached(material, privateKey)
+ const message = encode(TLVValues.USERNAME, usernameData, TLVValues.PUBLIC_KEY, serverLTPK, TLVValues.PROOF, serverProof)
+
+ const encrypted = chacha20_poly1305_encryptAndSeal(hkdfEncKey, Buffer.from('PS-Msg06'), null, message)
// finally, notify listeners that we have been paired with a client
- this.emit(HAPServerEventTypes.PAIR, clientUsername.toString(), clientLTPK, once(err => {
+ this.emit(HAPServerEventTypes.PAIR, clientUsername.toString(), clientLTPK, once((err) => {
if (err) {
- debug("[%s] Error adding pairing info: %s", this.accessoryInfo.username, err.message);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN));
- connection._pairSetupState = undefined;
- return;
+ debug('[%s] Error adding pairing info: %s', this.accessoryInfo.username, err.message)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN))
+ connection._pairSetupState = undefined
+ return
}
// send final pairing response to client
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ENCRYPTED_DATA, Buffer.concat([encrypted.ciphertext, encrypted.authTag])));
- connection._pairSetupState = undefined;
- }));
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ENCRYPTED_DATA, Buffer.concat([encrypted.ciphertext, encrypted.authTag])))
+ connection._pairSetupState = undefined
+ }))
}
private handlePairVerify(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void {
- const tlvData = tlv.decode(data);
- const sequence = tlvData[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number
+ const tlvData = decode(data)
+ const sequence = tlvData[TLVValues.SEQUENCE_NUM][0] // value is single byte with sequence number
if (sequence === PairingStates.M1) {
- this.handlePairVerifyM1(connection, request, response, tlvData);
+ this.handlePairVerifyM1(connection, request, response, tlvData)
} else if (sequence === PairingStates.M3 && connection._pairVerifyState === PairingStates.M2) {
- this.handlePairVerifyM3(connection, request, response, tlvData);
+ this.handlePairVerifyM3(connection, request, response, tlvData)
} else {
// Invalid state/sequence number
- response.writeHead(HAPPairingHTTPCode.BAD_REQUEST, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.STATE, sequence + 1, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN));
- return;
+ response.writeHead(HAPPairingHTTPCode.BAD_REQUEST, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.STATE, sequence + 1, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN))
}
}
private handlePairVerifyM1(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, tlvData: Record): void {
- debug("[%s] Pair verify step 1/2", this.accessoryInfo.username);
- const clientPublicKey = tlvData[TLVValues.PUBLIC_KEY]; // Buffer
+ debug('[%s] Pair verify step 1/2', this.accessoryInfo.username)
+ const clientPublicKey = tlvData[TLVValues.PUBLIC_KEY] // Buffer
// generate new encryption keys for this session
- const keyPair = hapCrypto.generateCurve25519KeyPair();
- const secretKey = Buffer.from(keyPair.secretKey);
- const publicKey = Buffer.from(keyPair.publicKey);
- const sharedSec = Buffer.from(hapCrypto.generateCurve25519SharedSecKey(secretKey, clientPublicKey));
- const usernameData = Buffer.from(this.accessoryInfo.username);
- const material = Buffer.concat([publicKey, usernameData, clientPublicKey]);
- const privateKey = Buffer.from(this.accessoryInfo.signSk);
- const serverProof = tweetnacl.sign.detached(material, privateKey);
- const encSalt = Buffer.from("Pair-Verify-Encrypt-Salt");
- const encInfo = Buffer.from("Pair-Verify-Encrypt-Info");
- const outputKey = hapCrypto.HKDF("sha512", encSalt, sharedSec, encInfo, 32).slice(0, 32);
-
- connection.encryption = new HAPEncryption(clientPublicKey, secretKey, publicKey, sharedSec, outputKey);
+ const keyPair = generateCurve25519KeyPair()
+ const secretKey = Buffer.from(keyPair.secretKey)
+ const publicKey = Buffer.from(keyPair.publicKey)
+ const sharedSec = Buffer.from(generateCurve25519SharedSecKey(secretKey, clientPublicKey))
+ const usernameData = Buffer.from(this.accessoryInfo.username)
+ const material = Buffer.concat([publicKey, usernameData, clientPublicKey])
+ const privateKey = Buffer.from(this.accessoryInfo.signSk)
+ const serverProof = tweetnacl.sign.detached(material, privateKey)
+ const encSalt = Buffer.from('Pair-Verify-Encrypt-Salt')
+ const encInfo = Buffer.from('Pair-Verify-Encrypt-Info')
+ const outputKey = HKDF('sha512', encSalt, sharedSec, encInfo, 32).subarray(0, 32)
+
+ connection.encryption = new HAPEncryption(clientPublicKey, secretKey, publicKey, sharedSec, outputKey)
// compose the response data in TLV format
- const message = tlv.encode(TLVValues.USERNAME, usernameData, TLVValues.PROOF, serverProof);
-
- const encrypted = hapCrypto.chacha20_poly1305_encryptAndSeal(outputKey, Buffer.from("PV-Msg02"), null, message);
-
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(
- TLVValues.SEQUENCE_NUM, PairingStates.M2,
- TLVValues.ENCRYPTED_DATA, Buffer.concat([encrypted.ciphertext, encrypted.authTag]),
- TLVValues.PUBLIC_KEY, publicKey,
- ));
- connection._pairVerifyState = PairingStates.M2;
+ const message = encode(TLVValues.USERNAME, usernameData, TLVValues.PROOF, serverProof)
+
+ const encrypted = chacha20_poly1305_encryptAndSeal(outputKey, Buffer.from('PV-Msg02'), null, message)
+
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(
+ TLVValues.SEQUENCE_NUM,
+ PairingStates.M2,
+ TLVValues.ENCRYPTED_DATA,
+ Buffer.concat([encrypted.ciphertext, encrypted.authTag]),
+ TLVValues.PUBLIC_KEY,
+ publicKey,
+ ))
+ connection._pairVerifyState = PairingStates.M2
}
private handlePairVerifyM3(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, objects: Record): void {
- debug("[%s] Pair verify step 2/2", this.accessoryInfo.username);
- const encryptedData = objects[TLVValues.ENCRYPTED_DATA];
- const messageData = Buffer.alloc(encryptedData.length - 16);
- const authTagData = Buffer.alloc(16);
- encryptedData.copy(messageData, 0, 0, encryptedData.length - 16);
- encryptedData.copy(authTagData, 0, encryptedData.length - 16, encryptedData.length);
+ debug('[%s] Pair verify step 2/2', this.accessoryInfo.username)
+ const encryptedData = objects[TLVValues.ENCRYPTED_DATA]
+ const messageData = Buffer.alloc(encryptedData.length - 16)
+ const authTagData = Buffer.alloc(16)
+ encryptedData.copy(messageData, 0, 0, encryptedData.length - 16)
+ encryptedData.copy(authTagData, 0, encryptedData.length - 16, encryptedData.length)
// instance of HAPEncryption (created in handlePairVerifyStepOne)
- const enc = connection.encryption!;
+ const enc = connection.encryption!
- let plaintext;
+ let plaintext
try {
- plaintext = hapCrypto.chacha20_poly1305_decryptAndVerify(enc.hkdfPairEncryptionKey, Buffer.from("PV-Msg03"), null, messageData, authTagData);
+ plaintext = chacha20_poly1305_decryptAndVerify(enc.hkdfPairEncryptionKey, Buffer.from('PV-Msg03'), null, messageData, authTagData)
} catch (error) {
- debug("[%s] M3: Failed to decrypt and/or verify", this.accessoryInfo.username);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.STATE, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION));
- connection._pairVerifyState = undefined;
- return;
+ debug('[%s] M3: Failed to decrypt and/or verify', this.accessoryInfo.username)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.STATE, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION))
+ connection._pairVerifyState = undefined
+ return
}
- const decoded = tlv.decode(plaintext);
- const clientUsername = decoded[TLVValues.USERNAME];
- const proof = decoded[TLVValues.PROOF];
- const material = Buffer.concat([enc.clientPublicKey, clientUsername, enc.publicKey]);
+ const decoded = decode(plaintext)
+ const clientUsername = decoded[TLVValues.USERNAME]
+ const proof = decoded[TLVValues.PROOF]
+ const material = Buffer.concat([enc.clientPublicKey, clientUsername, enc.publicKey])
// since we're paired, we should have the public key stored for this client
- const clientPublicKey = this.accessoryInfo.getClientPublicKey(clientUsername.toString());
+ const clientPublicKey = this.accessoryInfo.getClientPublicKey(clientUsername.toString())
// if we're not actually paired, then there's nothing to verify - this client thinks it's paired with us, but we
// disagree. Respond with invalid request (seems to match HomeKit Accessory Simulator behavior)
if (!clientPublicKey) {
- debug("[%s] Client %s attempting to verify, but we are not paired; rejecting client", this.accessoryInfo.username, clientUsername);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.STATE, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION));
- connection._pairVerifyState = undefined;
- return;
+ debug('[%s] Client %s attempting to verify, but we are not paired; rejecting client', this.accessoryInfo.username, clientUsername)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.STATE, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION))
+ connection._pairVerifyState = undefined
+ return
}
if (!tweetnacl.sign.detached.verify(material, proof, clientPublicKey)) {
- debug("[%s] Client %s provided an invalid signature", this.accessoryInfo.username, clientUsername);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.STATE, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION));
- connection._pairVerifyState = undefined;
- return;
+ debug('[%s] Client %s provided an invalid signature', this.accessoryInfo.username, clientUsername)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.STATE, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION))
+ connection._pairVerifyState = undefined
+ return
}
- debug("[%s] Client %s verification complete", this.accessoryInfo.username, clientUsername);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M4));
+ debug('[%s] Client %s verification complete', this.accessoryInfo.username, clientUsername)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.SEQUENCE_NUM, PairingStates.M4))
// now that the client has been verified, we must "upgrade" our pseudo-HTTP connection to include
// TCP-level encryption. We'll do this by adding some more encryption vars to the session, and using them
// in future calls to onEncrypt, onDecrypt.
- const encSalt = Buffer.from("Control-Salt");
- const infoRead = Buffer.from("Control-Read-Encryption-Key");
- const infoWrite = Buffer.from("Control-Write-Encryption-Key");
- enc.accessoryToControllerKey = hapCrypto.HKDF("sha512", encSalt, enc.sharedSecret, infoRead, 32);
- enc.controllerToAccessoryKey = hapCrypto.HKDF("sha512", encSalt, enc.sharedSecret, infoWrite, 32);
+ const encSalt = Buffer.from('Control-Salt')
+ const infoRead = Buffer.from('Control-Read-Encryption-Key')
+ const infoWrite = Buffer.from('Control-Write-Encryption-Key')
+ enc.accessoryToControllerKey = HKDF('sha512', encSalt, enc.sharedSecret, infoRead, 32)
+ enc.controllerToAccessoryKey = HKDF('sha512', encSalt, enc.sharedSecret, infoWrite, 32)
// Our connection is now completely setup. We now want to subscribe this connection to special
- connection.connectionAuthenticated(clientUsername.toString());
- connection._pairVerifyState = undefined;
+ connection.connectionAuthenticated(clientUsername.toString())
+ connection._pairVerifyState = undefined
}
private handlePairings(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void {
// Only accept /pairing request if there is a secure session
if (!this.allowInsecureRequest && !connection.isAuthenticated()) {
- response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }));
- return;
+ response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }))
+ return
}
- const objects = tlv.decode(data);
- const method = objects[TLVValues.METHOD][0]; // value is single byte with request type
+ const objects = decode(data)
+ const method = objects[TLVValues.METHOD][0] // value is single byte with request type
- const state = objects[TLVValues.STATE][0];
+ const state = objects[TLVValues.STATE][0]
if (state !== PairingStates.M1) {
- return;
+ return
}
if (method === PairMethods.ADD_PAIRING) {
- const identifier = objects[TLVValues.IDENTIFIER].toString();
- const publicKey = objects[TLVValues.PUBLIC_KEY];
- const permissions = objects[TLVValues.PERMISSIONS][0] as PermissionTypes;
+ const identifier = objects[TLVValues.IDENTIFIER].toString()
+ const publicKey = objects[TLVValues.PUBLIC_KEY]
+ const permissions = objects[TLVValues.PERMISSIONS][0] as PermissionTypes
this.emit(HAPServerEventTypes.ADD_PAIRING, connection, identifier, publicKey, permissions, once((error: TLVErrorCode | 0) => {
if (error > 0) {
- debug("[%s] Pairings: failed ADD_PAIRING with code %d", this.accessoryInfo.username, error);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, error));
- return;
+ debug('[%s] Pairings: failed ADD_PAIRING with code %d', this.accessoryInfo.username, error)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, error))
+ return
}
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.STATE, PairingStates.M2));
- debug("[%s] Pairings: successfully executed ADD_PAIRING", this.accessoryInfo.username);
- }));
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.STATE, PairingStates.M2))
+ debug('[%s] Pairings: successfully executed ADD_PAIRING', this.accessoryInfo.username)
+ }))
} else if (method === PairMethods.REMOVE_PAIRING) {
- const identifier = objects[TLVValues.IDENTIFIER].toString();
+ const identifier = objects[TLVValues.IDENTIFIER].toString()
this.emit(HAPServerEventTypes.REMOVE_PAIRING, connection, identifier, once((error: TLVErrorCode | 0) => {
if (error > 0) {
- debug("[%s] Pairings: failed REMOVE_PAIRING with code %d", this.accessoryInfo.username, error);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, error));
- return;
+ debug('[%s] Pairings: failed REMOVE_PAIRING with code %d', this.accessoryInfo.username, error)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, error))
+ return
}
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.STATE, PairingStates.M2));
- debug("[%s] Pairings: successfully executed REMOVE_PAIRING", this.accessoryInfo.username);
- }));
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.STATE, PairingStates.M2))
+ debug('[%s] Pairings: successfully executed REMOVE_PAIRING', this.accessoryInfo.username)
+ }))
} else if (method === PairMethods.LIST_PAIRINGS) {
this.emit(HAPServerEventTypes.LIST_PAIRINGS, connection, once((error: TLVErrorCode | 0, data?: PairingInformation[]) => {
if (error > 0) {
- debug("[%s] Pairings: failed LIST_PAIRINGS with code %d", this.accessoryInfo.username, error);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" });
- response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, error));
- return;
+ debug('[%s] Pairings: failed LIST_PAIRINGS with code %d', this.accessoryInfo.username, error)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': 'application/pairing+tlv8' })
+ response.end(encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, error))
+ return
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const tlvList = [] as any[];
+ const tlvList = [] as any[]
data!.forEach((value: PairingInformation, index: number) => {
if (index > 0) {
- tlvList.push(TLVValues.SEPARATOR, Buffer.alloc(0));
+ tlvList.push(TLVValues.SEPARATOR, Buffer.alloc(0))
}
tlvList.push(
- TLVValues.IDENTIFIER, value.username,
- TLVValues.PUBLIC_KEY, value.publicKey,
- TLVValues.PERMISSIONS, value.permission,
- );
- });
-
- const list = tlv.encode(TLVValues.STATE, PairingStates.M2, ...tlvList);
- response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": HAPMimeTypes.PAIRING_TLV8 });
- response.end(list);
- debug("[%s] Pairings: successfully executed LIST_PAIRINGS", this.accessoryInfo.username);
- }));
+ TLVValues.IDENTIFIER,
+ value.username,
+ TLVValues.PUBLIC_KEY,
+ value.publicKey,
+ TLVValues.PERMISSIONS,
+ value.permission,
+ )
+ })
+
+ const list = encode(TLVValues.STATE, PairingStates.M2, ...tlvList)
+ response.writeHead(HAPPairingHTTPCode.OK, { 'Content-Type': HAPMimeTypes.PAIRING_TLV8 })
+ response.end(list)
+ debug('[%s] Pairings: successfully executed LIST_PAIRINGS', this.accessoryInfo.username)
+ }))
}
}
private handleAccessories(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void {
if (!this.allowInsecureRequest && !connection.isAuthenticated()) {
- response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }));
- return;
+ response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }))
+ return
}
// call out to listeners to retrieve the latest accessories JSON
this.emit(HAPServerEventTypes.ACCESSORIES, connection, once((error, result) => {
if (error) {
- response.writeHead(error.httpCode, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: error.status }));
+ response.writeHead(error.httpCode, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: error.status }))
} else {
- response.writeHead(HAPHTTPCode.OK, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify(result));
+ response.writeHead(HAPHTTPCode.OK, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify(result))
}
- }));
+ }))
}
private handleCharacteristics(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void {
if (!this.allowInsecureRequest && !connection.isAuthenticated()) {
- response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }));
- return;
+ response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }))
+ return
}
- if (request.method === "GET") {
- const searchParams = url.searchParams;
+ if (request.method === 'GET') {
+ const searchParams = url.searchParams
- const idParam = searchParams.get("id");
+ const idParam = searchParams.get('id')
if (!idParam) {
- response.writeHead(HAPHTTPCode.BAD_REQUEST, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }));
- return;
+ response.writeHead(HAPHTTPCode.BAD_REQUEST, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }))
+ return
}
- const ids: CharacteristicId[] = [];
- for (const entry of idParam.split(",")) { // ["1.9","2.14"]
- const split = entry.split("."); // ["1","9"]
+ const ids: CharacteristicId[] = []
+ for (const entry of idParam.split(',')) { // ["1.9","2.14"]
+ const split = entry.split('.') // ["1","9"]
ids.push({
- aid: parseInt(split[0], 10), // accessory id
- iid: parseInt(split[1], 10), // (characteristic) instance id
- });
+ aid: Number.parseInt(split[0], 10), // accessory id
+ iid: Number.parseInt(split[1], 10), // (characteristic) instance id
+ })
}
const readRequest: CharacteristicsReadRequest = {
- ids: ids,
- includeMeta: consideredTrue(searchParams.get("meta")),
- includePerms: consideredTrue(searchParams.get("perms")),
- includeType: consideredTrue(searchParams.get("type")),
- includeEvent: consideredTrue(searchParams.get("ev")),
- };
+ ids,
+ includeMeta: consideredTrue(searchParams.get('meta')),
+ includePerms: consideredTrue(searchParams.get('perms')),
+ includeType: consideredTrue(searchParams.get('type')),
+ includeEvent: consideredTrue(searchParams.get('ev')),
+ }
this.emit(
HAPServerEventTypes.GET_CHARACTERISTICS,
@@ -896,49 +911,49 @@ export class HAPServer extends EventEmitter {
readRequest,
once((error, readResponse) => {
if (error) {
- response.writeHead(error.httpCode, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: error.status }));
- return;
+ response.writeHead(error.httpCode, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: error.status }))
+ return
}
- const characteristics = readResponse!.characteristics;
+ const characteristics = readResponse!.characteristics
- let errorOccurred = false; // determine if we send a 207 Multi-Status
+ let errorOccurred = false // determine if we send a 207 Multi-Status
for (const data of characteristics) {
if (data.status) {
- errorOccurred = true;
- break;
+ errorOccurred = true
+ break
}
}
if (errorOccurred) { // on a 207 Multi-Status EVERY characteristic MUST include a status property
for (const data of characteristics) {
if (!data.status) { // a status is undefined if the request was successful
- data.status = HAPStatus.SUCCESS; // a value of zero indicates success
+ data.status = HAPStatus.SUCCESS // a value of zero indicates success
}
}
}
// 207 "multi-status" is returned when an error occurs reading a characteristic. otherwise 200 is returned
- response.writeHead(errorOccurred? HAPHTTPCode.MULTI_STATUS: HAPHTTPCode.OK, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ characteristics: characteristics }));
+ response.writeHead(errorOccurred ? HAPHTTPCode.MULTI_STATUS : HAPHTTPCode.OK, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ characteristics }))
}),
- );
- } else if (request.method === "PUT") {
+ )
+ } else if (request.method === 'PUT') {
if (!connection.isAuthenticated()) {
if (!request.headers || (request.headers && request.headers.authorization !== this.accessoryInfo.pincode)) {
- response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }));
- return;
+ response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }))
+ return
}
}
if (data.length === 0) {
- response.writeHead(HAPHTTPCode.BAD_REQUEST, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }));
- return;
+ response.writeHead(HAPHTTPCode.BAD_REQUEST, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }))
+ return
}
- const writeRequest = JSON.parse(data.toString("utf8")) as CharacteristicsWriteRequest;
+ const writeRequest = JSON.parse(data.toString('utf8')) as CharacteristicsWriteRequest
this.emit(
HAPServerEventTypes.SET_CHARACTERISTICS,
@@ -946,112 +961,110 @@ export class HAPServer extends EventEmitter {
writeRequest,
once((error, writeResponse) => {
if (error) {
- response.writeHead(error.httpCode, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: error.status }));
- return;
+ response.writeHead(error.httpCode, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: error.status }))
+ return
}
- const characteristics = writeResponse!.characteristics;
+ const characteristics = writeResponse!.characteristics
- let multiStatus = false;
+ let multiStatus = false
for (const data of characteristics) {
if (data.status || data.value !== undefined) {
// also send multiStatus on write response requests
- multiStatus = true;
- break;
+ multiStatus = true
+ break
}
}
if (multiStatus) {
// 207 is "multi-status" since HomeKit may be setting multiple things and any one can fail independently
- response.writeHead(HAPHTTPCode.MULTI_STATUS, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ characteristics: characteristics }));
+ response.writeHead(HAPHTTPCode.MULTI_STATUS, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ characteristics }))
} else {
// if everything went fine send 204 no content response
- response.writeHead(HAPHTTPCode.NO_CONTENT);
- response.end();
+ response.writeHead(HAPHTTPCode.NO_CONTENT)
+ response.end()
}
}),
- );
+ )
} else {
- response.writeHead(HAPHTTPCode.BAD_REQUEST, { "Content-Type": HAPMimeTypes.HAP_JSON }); // method not allowed
- response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }));
+ response.writeHead(HAPHTTPCode.BAD_REQUEST, { 'Content-Type': HAPMimeTypes.HAP_JSON }) // method not allowed
+ response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }))
}
}
private handlePrepareWrite(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void {
if (!this.allowInsecureRequest && !connection.isAuthenticated()) {
- response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }));
- return;
+ response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }))
+ return
}
- if (request.method === "PUT") {
+ if (request.method === 'PUT') {
if (data.length === 0) {
- response.writeHead(HAPHTTPCode.BAD_REQUEST, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }));
- return;
+ response.writeHead(HAPHTTPCode.BAD_REQUEST, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }))
+ return
}
- const prepareRequest = JSON.parse(data.toString()) as PrepareWriteRequest;
+ const prepareRequest = JSON.parse(data.toString()) as PrepareWriteRequest
if (prepareRequest.pid && prepareRequest.ttl) {
- debug("[%s] Received prepare write request with pid %d and ttl %d", this.accessoryInfo.username, prepareRequest.pid, prepareRequest.ttl);
+ debug('[%s] Received prepare write request with pid %d and ttl %d', this.accessoryInfo.username, prepareRequest.pid, prepareRequest.ttl)
if (connection.timedWriteTimeout) { // clear any currently existing timeouts
- clearTimeout(connection.timedWriteTimeout);
+ clearTimeout(connection.timedWriteTimeout)
}
- connection.timedWritePid = prepareRequest.pid;
+ connection.timedWritePid = prepareRequest.pid
connection.timedWriteTimeout = setTimeout(() => {
- debug("[%s] Timed write request timed out for pid %d", this.accessoryInfo.username, prepareRequest.pid);
- connection.timedWritePid = undefined;
- connection.timedWriteTimeout = undefined;
- }, prepareRequest.ttl);
-
- response.writeHead(HAPHTTPCode.OK, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.SUCCESS }));
- return;
+ debug('[%s] Timed write request timed out for pid %d', this.accessoryInfo.username, prepareRequest.pid)
+ connection.timedWritePid = undefined
+ connection.timedWriteTimeout = undefined
+ }, prepareRequest.ttl)
+
+ response.writeHead(HAPHTTPCode.OK, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.SUCCESS }))
} else {
- response.writeHead(HAPHTTPCode.BAD_REQUEST, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }));
+ response.writeHead(HAPHTTPCode.BAD_REQUEST, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }))
}
} else {
- response.writeHead(HAPHTTPCode.BAD_REQUEST, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }));
+ response.writeHead(HAPHTTPCode.BAD_REQUEST, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }))
}
}
private handleResource(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void {
if (!connection.isAuthenticated()) {
if (!(this.allowInsecureRequest && request.headers && request.headers.authorization === this.accessoryInfo.pincode)) {
- response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }));
- return;
+ response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES }))
+ return
}
}
- if (request.method === "POST") {
+ if (request.method === 'POST') {
if (data.length === 0) {
- response.writeHead(HAPHTTPCode.BAD_REQUEST, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }));
- return;
+ response.writeHead(HAPHTTPCode.BAD_REQUEST, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }))
+ return
}
- const resourceRequest = JSON.parse(data.toString()) as ResourceRequest;
+ const resourceRequest = JSON.parse(data.toString()) as ResourceRequest
// call out to listeners to retrieve the resource, snapshot only right now
this.emit(HAPServerEventTypes.REQUEST_RESOURCE, resourceRequest, once((error, resource) => {
if (error) {
- response.writeHead(error.httpCode, { "Content-Type": HAPMimeTypes.HAP_JSON });
- response.end(JSON.stringify({ status: error.status }));
+ response.writeHead(error.httpCode, { 'Content-Type': HAPMimeTypes.HAP_JSON })
+ response.end(JSON.stringify({ status: error.status }))
} else {
- response.writeHead(HAPHTTPCode.OK, { "Content-Type": HAPMimeTypes.IMAGE_JPEG });
- response.end(resource);
+ response.writeHead(HAPHTTPCode.OK, { 'Content-Type': HAPMimeTypes.IMAGE_JPEG })
+ response.end(resource)
}
- }));
+ }))
} else {
- response.writeHead(HAPHTTPCode.BAD_REQUEST, { "Content-Type": HAPMimeTypes.HAP_JSON }); // method not allowed
- response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }));
+ response.writeHead(HAPHTTPCode.BAD_REQUEST, { 'Content-Type': HAPMimeTypes.HAP_JSON }) // method not allowed
+ response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST }))
}
}
-
}
diff --git a/src/lib/Service.spec.ts b/src/lib/Service.spec.ts
index d1fb4cf00..a17910e83 100644
--- a/src/lib/Service.spec.ts
+++ b/src/lib/Service.spec.ts
@@ -1,136 +1,138 @@
-import { Characteristic } from "./Characteristic";
-import { SerializedService, Service } from "./Service";
-import * as uuid from "./util/uuid";
+import type { SerializedService } from './Service'
+import { describe, expect, it } from 'vitest'
-const createService = () => {
- return new Service("Test", uuid.generate("Foo"), "subtype");
-};
+import { Characteristic } from './Characteristic.js'
+import { Service } from './Service.js'
+import { generate } from './util/uuid.js'
-describe("Service", () => {
+function createService() {
+ return new Service('Test', generate('Foo'), 'subtype')
+}
- describe("#constructor()", () => {
- it("should set the name characteristic to the display name", () => {
- const service = createService();
- expect(service.getCharacteristic(Characteristic.Name)!.value).toEqual("Test");
- });
+describe('service', () => {
+ describe('#constructor()', () => {
+ it('should set the name characteristic to the display name', () => {
+ const service = createService()
+ expect(service.getCharacteristic(Characteristic.Name)!.value).toEqual('Test')
+ })
- it("should fail to load with no UUID", () => {
+ it('should fail to load with no UUID', () => {
expect(() => {
- new Service("Test", "", "subtype");
- }).toThrow("valid UUID");
- });
- });
-
- describe("#serialize", () => {
- it("should serialize service", () => {
- const service = new Service.Lightbulb("TestLight", "subTypeLight");
- service.isHiddenService = true;
- service.isPrimaryService = true;
-
- const json = Service.serialize(service);
- expect(json.displayName).toEqual(service.displayName);
- expect(json.UUID).toEqual(service.UUID);
- expect(json.subtype).toEqual(service.subtype);
- expect(json.hiddenService).toEqual(service.isHiddenService);
- expect(json.primaryService).toEqual(service.isPrimaryService);
+ new Service('Test', '', 'subtype') // eslint-disable-line no-new
+ }).toThrow('valid UUID')
+ })
+ })
+
+ describe('#serialize', () => {
+ it('should serialize service', () => {
+ const service = new Service.Lightbulb('TestLight', 'subTypeLight')
+ service.isHiddenService = true
+ service.isPrimaryService = true
+
+ const json = Service.serialize(service)
+ expect(json.displayName).toEqual(service.displayName)
+ expect(json.UUID).toEqual(service.UUID)
+ expect(json.subtype).toEqual(service.subtype)
+ expect(json.hiddenService).toEqual(service.isHiddenService)
+ expect(json.primaryService).toEqual(service.isPrimaryService)
// just count the elements. If those characteristics are serialized correctly is tested in the Characteristic.spec
- expect(service.characteristics).toBeDefined();
- expect(json.characteristics.length).toEqual(service.characteristics.length);
- expect(service.optionalCharacteristics).toBeDefined();
- expect(json.optionalCharacteristics!.length).toEqual(service.optionalCharacteristics.length);
- });
-
- it("should serialize service with proper constructor name", () => {
- const service = new Service.Speaker("Speaker Name");
-
- const json = Service.serialize(service);
- expect(json.constructorName).toBe("Speaker");
- });
- });
-
- describe("#deserialize", () => {
- it("should deserialize legacy json from homebridge", () => {
- const json = JSON.parse("{\"displayName\":\"Test Light\",\"UUID\":\"00000043-0000-1000-8000-0026BB765291\"," +
- "\"characteristics\":[{\"displayName\":\"Name\",\"UUID\":\"00000023-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]}," +
- "\"value\":\"Test Light\",\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"On\",\"UUID\":\"00000025-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"bool\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\",\"pw\",\"ev\"]}," +
- "\"value\":false,\"eventOnlyCharacteristic\":false}]}");
- const service = Service.deserialize(json);
-
- expect(service.displayName).toEqual(json.displayName);
- expect(service.UUID).toEqual(json.UUID);
- expect(service.subtype).toBeUndefined();
- expect(service.isHiddenService).toEqual(false);
- expect(service.isPrimaryService).toEqual(false);
+ expect(service.characteristics).toBeDefined()
+ expect(json.characteristics.length).toEqual(service.characteristics.length)
+ expect(service.optionalCharacteristics).toBeDefined()
+ expect(json.optionalCharacteristics!.length).toEqual(service.optionalCharacteristics.length)
+ })
+
+ it('should serialize service with proper constructor name', () => {
+ const service = new Service.Speaker('Speaker Name')
+
+ const json = Service.serialize(service)
+ expect(json.constructorName).toBe('Speaker')
+ })
+ })
+
+ describe('#deserialize', () => {
+ it('should deserialize legacy json from homebridge', () => {
+ const json = JSON.parse('{"displayName":"Test Light","UUID":"00000043-0000-1000-8000-0026BB765291",'
+ + '"characteristics":[{"displayName":"Name","UUID":"00000023-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"string","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},'
+ + '"value":"Test Light","eventOnlyCharacteristic":false},'
+ + '{"displayName":"On","UUID":"00000025-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"bool","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr","pw","ev"]},'
+ + '"value":false,"eventOnlyCharacteristic":false}]}')
+ const service = Service.deserialize(json)
+
+ expect(service.displayName).toEqual(json.displayName)
+ expect(service.UUID).toEqual(json.UUID)
+ expect(service.subtype).toBeUndefined()
+ expect(service.isHiddenService).toEqual(false)
+ expect(service.isPrimaryService).toEqual(false)
// just count the elements. If those characteristics are serialized correctly is tested in the Characteristic.spec
- expect(service.characteristics).toBeDefined();
- expect(service.characteristics.length).toEqual(2);
- expect(service.optionalCharacteristics).toBeDefined();
- expect(service.optionalCharacteristics!.length).toEqual(0); // homebridge didn't save those
- });
+ expect(service.characteristics).toBeDefined()
+ expect(service.characteristics.length).toEqual(2)
+ expect(service.optionalCharacteristics).toBeDefined()
+ expect(service.optionalCharacteristics!.length).toEqual(0) // homebridge didn't save those
+ })
- it("should deserialize complete json", () => {
+ it('should deserialize complete json', () => {
// json for a light accessory
- const json = JSON.parse("{\"displayName\":\"TestLight\",\"UUID\":\"00000043-0000-1000-8000-0026BB765291\"," +
- "\"subtype\":\"subTypeLight\",\"hiddenService\":true,\"primaryService\":true," +
- "\"characteristics\":[{\"displayName\":\"Name\",\"UUID\":\"00000023-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]}," +
- "\"value\":\"TestLight\",\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"On\",\"UUID\":\"00000025-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"bool\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\",\"pw\",\"ev\"]}," +
- "\"value\":false,\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}]," +
- "\"optionalCharacteristics\":[{\"displayName\":\"Brightness\",\"UUID\":\"00000008-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"int\",\"unit\":\"percentage\",\"minValue\":0,\"maxValue\":100,\"minStep\":1,\"perms\":[\"pr\",\"pw\",\"ev\"]}," +
- "\"value\":0,\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"Hue\",\"UUID\":\"00000013-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"float\",\"unit\":\"arcdegrees\",\"minValue\":0,\"maxValue\":360,\"minStep\":1,\"perms\":[\"pr\",\"pw\",\"ev\"]}," +
- "\"value\":0,\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"Saturation\",\"UUID\":\"0000002F-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"float\",\"unit\":\"percentage\",\"minValue\":0,\"maxValue\":100,\"minStep\":1,\"perms\":[\"pr\",\"pw\",\"ev\"]}," +
- "\"value\":0,\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"Name\",\"UUID\":\"00000023-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"string\",\"unit\":null,\"minValue\":null,\"maxValue\":null,\"minStep\":null,\"perms\":[\"pr\"]}," +
- "\"value\":\"\",\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}," +
- "{\"displayName\":\"Color Temperature\",\"UUID\":\"000000CE-0000-1000-8000-0026BB765291\"," +
- "\"props\":{\"format\":\"uint32\",\"unit\":null,\"minValue\":140,\"maxValue\":500,\"minStep\":1,\"perms\":[\"pr\",\"pw\",\"ev\"]}," +
- "\"value\":140,\"accessRestrictedToAdmins\":[],\"eventOnlyCharacteristic\":false}]}");
-
- const service = Service.deserialize(json);
-
- expect(service.displayName).toEqual(json.displayName);
- expect(service.UUID).toEqual(json.UUID);
- expect(service.subtype).toEqual(json.subtype);
- expect(service.isHiddenService).toEqual(true);
- expect(service.isPrimaryService).toEqual(true);
+ const json = JSON.parse('{"displayName":"TestLight","UUID":"00000043-0000-1000-8000-0026BB765291",'
+ + '"subtype":"subTypeLight","hiddenService":true,"primaryService":true,'
+ + '"characteristics":[{"displayName":"Name","UUID":"00000023-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"string","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},'
+ + '"value":"TestLight","accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},'
+ + '{"displayName":"On","UUID":"00000025-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"bool","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr","pw","ev"]},'
+ + '"value":false,"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false}],'
+ + '"optionalCharacteristics":[{"displayName":"Brightness","UUID":"00000008-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"int","unit":"percentage","minValue":0,"maxValue":100,"minStep":1,"perms":["pr","pw","ev"]},'
+ + '"value":0,"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},'
+ + '{"displayName":"Hue","UUID":"00000013-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"float","unit":"arcdegrees","minValue":0,"maxValue":360,"minStep":1,"perms":["pr","pw","ev"]},'
+ + '"value":0,"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},'
+ + '{"displayName":"Saturation","UUID":"0000002F-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"float","unit":"percentage","minValue":0,"maxValue":100,"minStep":1,"perms":["pr","pw","ev"]},'
+ + '"value":0,"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},'
+ + '{"displayName":"Name","UUID":"00000023-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"string","unit":null,"minValue":null,"maxValue":null,"minStep":null,"perms":["pr"]},'
+ + '"value":"","accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false},'
+ + '{"displayName":"Color Temperature","UUID":"000000CE-0000-1000-8000-0026BB765291",'
+ + '"props":{"format":"uint32","unit":null,"minValue":140,"maxValue":500,"minStep":1,"perms":["pr","pw","ev"]},'
+ + '"value":140,"accessRestrictedToAdmins":[],"eventOnlyCharacteristic":false}]}')
+
+ const service = Service.deserialize(json)
+
+ expect(service.displayName).toEqual(json.displayName)
+ expect(service.UUID).toEqual(json.UUID)
+ expect(service.subtype).toEqual(json.subtype)
+ expect(service.isHiddenService).toEqual(true)
+ expect(service.isPrimaryService).toEqual(true)
// just count the elements. If those characteristics are serialized correctly is tested in the Characteristic.spec
- expect(service.characteristics).toBeDefined();
- expect(service.characteristics.length).toEqual(2); // On, Name
- expect(service.optionalCharacteristics).toBeDefined();
- expect(service.optionalCharacteristics!.length).toEqual(5); // as defined in the Lightbulb service
- });
-
- it("should deserialize from json with constructor name", () => {
- const json: SerializedService = JSON.parse("{\"displayName\":\"Speaker Name\",\"UUID\":\"00000113-0000-1000-8000-0026BB765291\"," +
- "\"constructorName\":\"Speaker\",\"hiddenService\":false,\"primaryService\":false,\"characteristics\":" +
- "[{\"displayName\":\"Name\",\"UUID\":\"00000023-0000-1000-8000-0026BB765291\",\"eventOnlyCharacteristic\":false,\"constructorName\":\"Name\"," +
- "\"value\":\"Speaker Name\",\"props\":{\"format\":\"string\",\"perms\":[\"pr\"],\"maxLen\":64}},{\"displayName\":\"Mute\"," +
- "\"UUID\":\"0000011A-0000-1000-8000-0026BB765291\",\"eventOnlyCharacteristic\":false," +
- "\"constructorName\":\"Mute\",\"value\":false,\"props\":{\"format\":\"bool\",\"perms\":[\"ev\",\"pr\",\"pw\"]}}]," +
- "\"optionalCharacteristics\":[{\"displayName\":\"Active\",\"UUID\":\"000000B0-0000-1000-8000-0026BB765291\"," +
- "\"eventOnlyCharacteristic\":false,\"constructorName\":\"Active\",\"value\":0,\"props\":{\"format\":\"uint8\",\"perms\":[\"ev\",\"pr\",\"pw\"]," +
- "\"minValue\":0,\"maxValue\":1,\"minStep\":1}},{\"displayName\":\"Volume\",\"UUID\":\"00000119-0000-1000-8000-0026BB765291\"," +
- "\"eventOnlyCharacteristic\":false,\"constructorName\":\"Volume\",\"value\":0,\"props\":{\"format\":\"uint8\",\"perms\":[\"ev\",\"pr\",\"pw\"]," +
- "\"unit\":\"percentage\",\"minValue\":0,\"maxValue\":100,\"minStep\":1}}]}");
-
- const service = Service.deserialize(json);
-
- expect(service instanceof Service.Speaker).toBeTruthy();
- });
- });
-});
+ expect(service.characteristics).toBeDefined()
+ expect(service.characteristics.length).toEqual(2) // On, Name
+ expect(service.optionalCharacteristics).toBeDefined()
+ expect(service.optionalCharacteristics!.length).toEqual(5) // as defined in the Lightbulb service
+ })
+
+ it('should deserialize from json with constructor name', () => {
+ const json: SerializedService = JSON.parse('{"displayName":"Speaker Name","UUID":"00000113-0000-1000-8000-0026BB765291",'
+ + '"constructorName":"Speaker","hiddenService":false,"primaryService":false,"characteristics":'
+ + '[{"displayName":"Name","UUID":"00000023-0000-1000-8000-0026BB765291","eventOnlyCharacteristic":false,"constructorName":"Name",'
+ + '"value":"Speaker Name","props":{"format":"string","perms":["pr"],"maxLen":64}},{"displayName":"Mute",'
+ + '"UUID":"0000011A-0000-1000-8000-0026BB765291","eventOnlyCharacteristic":false,'
+ + '"constructorName":"Mute","value":false,"props":{"format":"bool","perms":["ev","pr","pw"]}}],'
+ + '"optionalCharacteristics":[{"displayName":"Active","UUID":"000000B0-0000-1000-8000-0026BB765291",'
+ + '"eventOnlyCharacteristic":false,"constructorName":"Active","value":0,"props":{"format":"uint8","perms":["ev","pr","pw"],'
+ + '"minValue":0,"maxValue":1,"minStep":1}},{"displayName":"Volume","UUID":"00000119-0000-1000-8000-0026BB765291",'
+ + '"eventOnlyCharacteristic":false,"constructorName":"Volume","value":0,"props":{"format":"uint8","perms":["ev","pr","pw"],'
+ + '"unit":"percentage","minValue":0,"maxValue":100,"minStep":1}}]}')
+
+ const service = Service.deserialize(json)
+
+ expect(service instanceof Service.Speaker).toBeTruthy()
+ })
+ })
+})
diff --git a/src/lib/Service.ts b/src/lib/Service.ts
index 9548883aa..f5de07878 100644
--- a/src/lib/Service.ts
+++ b/src/lib/Service.ts
@@ -1,9 +1,7 @@
-import assert from "assert";
-import createDebug from "debug";
-import { EventEmitter } from "events";
-import { CharacteristicValue, Nullable, ServiceJsonObject, WithUUID } from "../types";
-import { CharacteristicWarning, CharacteristicWarningType } from "./Accessory";
-import { Characteristic, CharacteristicChange, CharacteristicEventTypes, SerializedCharacteristic } from "./Characteristic";
+/* global NodeJS */
+import type { CharacteristicValue, Nullable, ServiceJsonObject, WithUUID } from '../types'
+import type { CharacteristicWarning } from './Accessory'
+import type { CharacteristicChange, SerializedCharacteristic } from './Characteristic'
import type {
AccessCode,
AccessControl,
@@ -76,34 +74,42 @@ import type {
WiFiTransport,
Window,
WindowCovering,
-} from "./definitions";
-import { IdentifierCache } from "./model/IdentifierCache";
-import { HAPConnection } from "./util/eventedhttp";
-import { HapStatusError } from "./util/hapStatusError";
-import { toShortForm } from "./util/uuid";
-import { checkName } from "./util/checkName";
+} from './definitions'
+import type { IdentifierCache } from './model/IdentifierCache'
+import type { HAPConnection } from './util/eventedhttp'
+import type { HapStatusError } from './util/hapStatusError'
-const debug = createDebug("HAP-NodeJS:Service");
+import assert from 'node:assert'
+import { EventEmitter } from 'node:events'
+
+import createDebug from 'debug'
+
+import { CharacteristicWarningType } from './Accessory.js'
+import { Characteristic, CharacteristicEventTypes } from './Characteristic.js'
+import { checkName } from './util/checkName.js'
+import { toShortForm } from './util/uuid.js'
+
+const debug = createDebug('HAP-NodeJS:Service')
/**
* HAP spec allows a maximum of 100 characteristics per service!
*/
-const MAX_CHARACTERISTICS = 100;
+const MAX_CHARACTERISTICS = 100
/**
* @group Service
*/
export interface SerializedService {
- displayName: string,
- UUID: string,
- subtype?: string,
- constructorName?: string,
+ displayName: string
+ UUID: string
+ subtype?: string
+ constructorName?: string
- hiddenService?: boolean,
- primaryService?: boolean,
+ hiddenService?: boolean
+ primaryService?: boolean
- characteristics: SerializedCharacteristic[],
- optionalCharacteristics?: SerializedCharacteristic[],
+ characteristics: SerializedCharacteristic[]
+ optionalCharacteristics?: SerializedCharacteristic[]
}
/**
@@ -111,34 +117,36 @@ export interface SerializedService {
*
* @group Service
*/
-export type ServiceId = string;
+export type ServiceId = string
/**
* @group Service
*/
-export type ServiceCharacteristicChange = CharacteristicChange & { characteristic: Characteristic };
+export type ServiceCharacteristicChange = CharacteristicChange & { characteristic: Characteristic }
/**
* @group Service
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum ServiceEventTypes {
- CHARACTERISTIC_CHANGE = "characteristic-change",
- SERVICE_CONFIGURATION_CHANGE = "service-configurationChange",
- CHARACTERISTIC_WARNING = "characteristic-warning",
+ CHARACTERISTIC_CHANGE = 'characteristic-change',
+ SERVICE_CONFIGURATION_CHANGE = 'service-configurationChange',
+ CHARACTERISTIC_WARNING = 'characteristic-warning',
}
/**
* @group Service
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export declare interface Service {
- on(event: "characteristic-change", listener: (change: ServiceCharacteristicChange) => void): this;
- on(event: "service-configurationChange", listener: () => void): this;
- on(event: "characteristic-warning", listener: (warning: CharacteristicWarning) => void): this;
-
- emit(event: "characteristic-change", change: ServiceCharacteristicChange): boolean;
- emit(event: "service-configurationChange"): boolean;
- emit(event: "characteristic-warning", warning: CharacteristicWarning): boolean;
+ /* eslint-disable ts/method-signature-style */
+ on(event: 'characteristic-change', listener: (change: ServiceCharacteristicChange) => void): this
+ on(event: 'service-configurationChange', listener: () => void): this
+ on(event: 'characteristic-warning', listener: (warning: CharacteristicWarning) => void): this
+ emit(event: 'characteristic-change', change: ServiceCharacteristicChange): boolean
+ emit(event: 'service-configurationChange'): boolean
+ emit(event: 'characteristic-warning', warning: CharacteristicWarning): boolean
+ /* eslint-enable ts/method-signature-style */
}
/**
@@ -163,7 +171,7 @@ export declare interface Service {
*
* @group Service
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export class Service extends EventEmitter {
// Service MUST NOT have any other static variables
@@ -172,328 +180,328 @@ export class Service extends EventEmitter {
/**
* @group Service Definitions
*/
- public static AccessCode: typeof AccessCode;
+ public static AccessCode: typeof AccessCode
/**
* @group Service Definitions
*/
- public static AccessControl: typeof AccessControl;
+ public static AccessControl: typeof AccessControl
/**
* @group Service Definitions
*/
- public static AccessoryInformation: typeof AccessoryInformation;
+ public static AccessoryInformation: typeof AccessoryInformation
/**
* @group Service Definitions
*/
- public static AccessoryMetrics: typeof AccessoryMetrics;
+ public static AccessoryMetrics: typeof AccessoryMetrics
/**
* @group Service Definitions
*/
- public static AccessoryRuntimeInformation: typeof AccessoryRuntimeInformation;
+ public static AccessoryRuntimeInformation: typeof AccessoryRuntimeInformation
/**
* @group Service Definitions
*/
- public static AirPurifier: typeof AirPurifier;
+ public static AirPurifier: typeof AirPurifier
/**
* @group Service Definitions
*/
- public static AirQualitySensor: typeof AirQualitySensor;
+ public static AirQualitySensor: typeof AirQualitySensor
/**
* @group Service Definitions
*/
- public static AssetUpdate: typeof AssetUpdate;
+ public static AssetUpdate: typeof AssetUpdate
/**
* @group Service Definitions
*/
- public static Assistant: typeof Assistant;
+ public static Assistant: typeof Assistant
/**
* @group Service Definitions
*/
- public static AudioStreamManagement: typeof AudioStreamManagement;
+ public static AudioStreamManagement: typeof AudioStreamManagement
/**
* @group Service Definitions
*/
- public static Battery: typeof Battery;
+ public static Battery: typeof Battery
/**
* @group Service Definitions
*/
- public static CameraOperatingMode: typeof CameraOperatingMode;
+ public static CameraOperatingMode: typeof CameraOperatingMode
/**
* @group Service Definitions
*/
- public static CameraRecordingManagement: typeof CameraRecordingManagement;
+ public static CameraRecordingManagement: typeof CameraRecordingManagement
/**
* @group Service Definitions
*/
- public static CameraRTPStreamManagement: typeof CameraRTPStreamManagement;
+ public static CameraRTPStreamManagement: typeof CameraRTPStreamManagement
/**
* @group Service Definitions
*/
- public static CarbonDioxideSensor: typeof CarbonDioxideSensor;
+ public static CarbonDioxideSensor: typeof CarbonDioxideSensor
/**
* @group Service Definitions
*/
- public static CarbonMonoxideSensor: typeof CarbonMonoxideSensor;
+ public static CarbonMonoxideSensor: typeof CarbonMonoxideSensor
/**
* @group Service Definitions
*/
- public static ContactSensor: typeof ContactSensor;
+ public static ContactSensor: typeof ContactSensor
/**
* @group Service Definitions
*/
- public static DataStreamTransportManagement: typeof DataStreamTransportManagement;
+ public static DataStreamTransportManagement: typeof DataStreamTransportManagement
/**
* @group Service Definitions
*/
- public static Diagnostics: typeof Diagnostics;
+ public static Diagnostics: typeof Diagnostics
/**
* @group Service Definitions
*/
- public static Door: typeof Door;
+ public static Door: typeof Door
/**
* @group Service Definitions
*/
- public static Doorbell: typeof Doorbell;
+ public static Doorbell: typeof Doorbell
/**
* @group Service Definitions
*/
- public static Fan: typeof Fan;
+ public static Fan: typeof Fan
/**
* @group Service Definitions
*/
- public static Fanv2: typeof Fanv2;
+ public static Fanv2: typeof Fanv2
/**
* @group Service Definitions
*/
- public static Faucet: typeof Faucet;
+ public static Faucet: typeof Faucet
/**
* @group Service Definitions
*/
- public static FilterMaintenance: typeof FilterMaintenance;
+ public static FilterMaintenance: typeof FilterMaintenance
/**
* @group Service Definitions
*/
- public static FirmwareUpdate: typeof FirmwareUpdate;
+ public static FirmwareUpdate: typeof FirmwareUpdate
/**
* @group Service Definitions
*/
- public static GarageDoorOpener: typeof GarageDoorOpener;
+ public static GarageDoorOpener: typeof GarageDoorOpener
/**
* @group Service Definitions
*/
- public static HeaterCooler: typeof HeaterCooler;
+ public static HeaterCooler: typeof HeaterCooler
/**
* @group Service Definitions
*/
- public static HumidifierDehumidifier: typeof HumidifierDehumidifier;
+ public static HumidifierDehumidifier: typeof HumidifierDehumidifier
/**
* @group Service Definitions
*/
- public static HumiditySensor: typeof HumiditySensor;
+ public static HumiditySensor: typeof HumiditySensor
/**
* @group Service Definitions
*/
- public static InputSource: typeof InputSource;
+ public static InputSource: typeof InputSource
/**
* @group Service Definitions
*/
- public static IrrigationSystem: typeof IrrigationSystem;
+ public static IrrigationSystem: typeof IrrigationSystem
/**
* @group Service Definitions
*/
- public static LeakSensor: typeof LeakSensor;
+ public static LeakSensor: typeof LeakSensor
/**
* @group Service Definitions
*/
- public static Lightbulb: typeof Lightbulb;
+ public static Lightbulb: typeof Lightbulb
/**
* @group Service Definitions
*/
- public static LightSensor: typeof LightSensor;
+ public static LightSensor: typeof LightSensor
/**
* @group Service Definitions
*/
- public static LockManagement: typeof LockManagement;
+ public static LockManagement: typeof LockManagement
/**
* @group Service Definitions
*/
- public static LockMechanism: typeof LockMechanism;
+ public static LockMechanism: typeof LockMechanism
/**
* @group Service Definitions
*/
- public static Microphone: typeof Microphone;
+ public static Microphone: typeof Microphone
/**
* @group Service Definitions
*/
- public static MotionSensor: typeof MotionSensor;
+ public static MotionSensor: typeof MotionSensor
/**
* @group Service Definitions
*/
- public static NFCAccess: typeof NFCAccess;
+ public static NFCAccess: typeof NFCAccess
/**
* @group Service Definitions
*/
- public static OccupancySensor: typeof OccupancySensor;
+ public static OccupancySensor: typeof OccupancySensor
/**
* @group Service Definitions
*/
- public static Outlet: typeof Outlet;
+ public static Outlet: typeof Outlet
/**
* @group Service Definitions
*/
- public static Pairing: typeof Pairing;
+ public static Pairing: typeof Pairing
/**
* @group Service Definitions
*/
- public static PowerManagement: typeof PowerManagement;
+ public static PowerManagement: typeof PowerManagement
/**
* @group Service Definitions
*/
- public static ProtocolInformation: typeof ProtocolInformation;
+ public static ProtocolInformation: typeof ProtocolInformation
/**
* @group Service Definitions
*/
- public static SecuritySystem: typeof SecuritySystem;
+ public static SecuritySystem: typeof SecuritySystem
/**
* @group Service Definitions
*/
- public static ServiceLabel: typeof ServiceLabel;
+ public static ServiceLabel: typeof ServiceLabel
/**
* @group Service Definitions
*/
- public static Siri: typeof Siri;
+ public static Siri: typeof Siri
/**
* @group Service Definitions
*/
- public static SiriEndpoint: typeof SiriEndpoint;
+ public static SiriEndpoint: typeof SiriEndpoint
/**
* @group Service Definitions
*/
- public static Slats: typeof Slats;
+ public static Slats: typeof Slats
/**
* @group Service Definitions
*/
- public static SmartSpeaker: typeof SmartSpeaker;
+ public static SmartSpeaker: typeof SmartSpeaker
/**
* @group Service Definitions
*/
- public static SmokeSensor: typeof SmokeSensor;
+ public static SmokeSensor: typeof SmokeSensor
/**
* @group Service Definitions
*/
- public static Speaker: typeof Speaker;
+ public static Speaker: typeof Speaker
/**
* @group Service Definitions
*/
- public static StatefulProgrammableSwitch: typeof StatefulProgrammableSwitch;
+ public static StatefulProgrammableSwitch: typeof StatefulProgrammableSwitch
/**
* @group Service Definitions
*/
- public static StatelessProgrammableSwitch: typeof StatelessProgrammableSwitch;
+ public static StatelessProgrammableSwitch: typeof StatelessProgrammableSwitch
/**
* @group Service Definitions
*/
- public static Switch: typeof Switch;
+ public static Switch: typeof Switch
/**
* @group Service Definitions
*/
- public static TapManagement: typeof TapManagement;
+ public static TapManagement: typeof TapManagement
/**
* @group Service Definitions
*/
- public static TargetControl: typeof TargetControl;
+ public static TargetControl: typeof TargetControl
/**
* @group Service Definitions
*/
- public static TargetControlManagement: typeof TargetControlManagement;
+ public static TargetControlManagement: typeof TargetControlManagement
/**
* @group Service Definitions
*/
- public static Television: typeof Television;
+ public static Television: typeof Television
/**
* @group Service Definitions
*/
- public static TelevisionSpeaker: typeof TelevisionSpeaker;
+ public static TelevisionSpeaker: typeof TelevisionSpeaker
/**
* @group Service Definitions
*/
- public static TemperatureSensor: typeof TemperatureSensor;
+ public static TemperatureSensor: typeof TemperatureSensor
/**
* @group Service Definitions
*/
- public static Thermostat: typeof Thermostat;
+ public static Thermostat: typeof Thermostat
/**
* @group Service Definitions
*/
- public static ThreadTransport: typeof ThreadTransport;
+ public static ThreadTransport: typeof ThreadTransport
/**
* @group Service Definitions
*/
- public static TransferTransportManagement: typeof TransferTransportManagement;
+ public static TransferTransportManagement: typeof TransferTransportManagement
/**
* @group Service Definitions
*/
- public static Valve: typeof Valve;
+ public static Valve: typeof Valve
/**
* @group Service Definitions
*/
- public static WiFiRouter: typeof WiFiRouter;
+ public static WiFiRouter: typeof WiFiRouter
/**
* @group Service Definitions
*/
- public static WiFiSatellite: typeof WiFiSatellite;
+ public static WiFiSatellite: typeof WiFiSatellite
/**
* @group Service Definitions
*/
- public static WiFiTransport: typeof WiFiTransport;
+ public static WiFiTransport: typeof WiFiTransport
/**
* @group Service Definitions
*/
- public static Window: typeof Window;
+ public static Window: typeof Window
/**
* @group Service Definitions
*/
- public static WindowCovering: typeof WindowCovering;
+ public static WindowCovering: typeof WindowCovering
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// NOTICE: when adding/changing properties, remember to possibly adjust the serialize/deserialize functions
- public displayName: string;
- public UUID: string;
- subtype?: string;
- iid: Nullable = null; // assigned later by our containing Accessory
- name: Nullable = null;
- characteristics: Characteristic[] = [];
- optionalCharacteristics: Characteristic[] = [];
+ public displayName: string
+ public UUID: string
+ subtype?: string
+ iid: Nullable = null // assigned later by our containing Accessory
+ name: Nullable = null
+ characteristics: Characteristic[] = []
+ optionalCharacteristics: Characteristic[] = []
/**
* @private
*/
- isHiddenService = false;
+ isHiddenService = false
/**
* @private
*/
- isPrimaryService = false; // do not write to this directly
+ isPrimaryService = false // do not write to this directly
/**
* @private
*/
- linkedServices: Service[] = [];
+ linkedServices: Service[] = []
- public constructor(displayName = "", UUID: string, subtype?: string) {
- super();
- assert(UUID, "Services must be created with a valid UUID.");
- this.displayName = displayName;
- this.UUID = UUID;
- this.subtype = subtype;
+ public constructor(displayName = '', UUID: string, subtype?: string) {
+ super()
+ assert(UUID, 'Services must be created with a valid UUID.')
+ this.displayName = displayName
+ this.UUID = UUID
+ this.subtype = subtype
// every service has an optional Characteristic.Name property - we'll set it to our displayName
// if one was given
// if you don't provide a display name, some HomeKit apps may choose to hide the device.
if (displayName) {
// create the characteristic if necessary
- checkName(this.displayName, "Name", displayName);
- const nameCharacteristic =
- this.getCharacteristic(Characteristic.Name) ||
- this.addCharacteristic(Characteristic.Name);
+ checkName(this.displayName, 'Name', displayName)
+ const nameCharacteristic
+ = this.getCharacteristic(Characteristic.Name)
+ || this.addCharacteristic(Characteristic.Name)
- nameCharacteristic.updateValue(displayName);
+ nameCharacteristic.updateValue(displayName)
}
}
@@ -505,40 +513,39 @@ export class Service extends EventEmitter {
* @returns the serviceId
*/
public getServiceId(): ServiceId {
- return this.UUID + (this.subtype || "");
+ return this.UUID + (this.subtype || '')
}
public addCharacteristic(input: Characteristic): Characteristic
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
public addCharacteristic(input: { new (...args: any[]): Characteristic }, ...constructorArgs: any[]): Characteristic
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- public addCharacteristic(input: Characteristic | {new (...args: any[]): Characteristic}, ...constructorArgs: any[]): Characteristic {
+ public addCharacteristic(input: Characteristic | { new (...args: any[]): Characteristic }, ...constructorArgs: any[]): Characteristic {
// characteristic might be a constructor like `Characteristic.Brightness` instead of an instance of Characteristic. Coerce if necessary.
- const characteristic = typeof input === "function"? new input(...constructorArgs): input;
+ // eslint-disable-next-line new-cap
+ const characteristic = typeof input === 'function' ? new input(...constructorArgs) : input
// check for UUID conflict
for (const existing of this.characteristics) {
if (existing.UUID === characteristic.UUID) {
- if (characteristic.UUID === "00000052-0000-1000-8000-0026BB765291") {
- //This is a special workaround for the Firmware Revision characteristic.
- return existing;
+ if (characteristic.UUID === '00000052-0000-1000-8000-0026BB765291') {
+ // This is a special workaround for the Firmware Revision characteristic.
+ return existing
}
- throw new Error("Cannot add a Characteristic with the same UUID as another Characteristic in this Service: " + existing.UUID);
+ throw new Error(`Cannot add a Characteristic with the same UUID as another Characteristic in this Service: ${existing.UUID}`)
}
}
if (this.characteristics.length >= MAX_CHARACTERISTICS) {
- throw new Error("Cannot add more than " + MAX_CHARACTERISTICS + " characteristics to a single service!");
+ throw new Error(`Cannot add more than ${MAX_CHARACTERISTICS} characteristics to a single service!`)
}
- this.setupCharacteristicEventHandlers(characteristic);
+ this.setupCharacteristicEventHandlers(characteristic)
- this.characteristics.push(characteristic);
+ this.characteristics.push(characteristic)
- this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE);
+ this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE)
- return characteristic;
+ return characteristic
}
/**
@@ -550,8 +557,8 @@ export class Service extends EventEmitter {
* @param isPrimary - optional boolean (default true) if the service should be the primary service
*/
public setPrimaryService(isPrimary = true): void {
- this.isPrimaryService = isPrimary;
- this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE);
+ this.isPrimaryService = isPrimary
+ this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE)
}
/**
@@ -560,8 +567,8 @@ export class Service extends EventEmitter {
* @param isHidden - optional boolean (default true) if the service should be marked hidden
*/
public setHiddenService(isHidden = true): void {
- this.isHiddenService = isHidden;
- this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE);
+ this.isHiddenService = isHidden
+ this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE)
}
/**
@@ -571,11 +578,11 @@ export class Service extends EventEmitter {
* @param service - The service this service should link to
*/
public addLinkedService(service: Service): void {
- //TODO: Add a check if the service is on the same accessory.
+ // TODO: Add a check if the service is on the same accessory.
if (!this.linkedServices.includes(service)) {
- this.linkedServices.push(service);
+ this.linkedServices.push(service)
}
- this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE);
+ this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE)
}
/**
@@ -584,76 +591,75 @@ export class Service extends EventEmitter {
* @param service - Previously linked service
*/
public removeLinkedService(service: Service): void {
- //TODO: Add a check if the service is on the same accessory.
- const index = this.linkedServices.indexOf(service);
+ // TODO: Add a check if the service is on the same accessory.
+ const index = this.linkedServices.indexOf(service)
if (index !== -1) {
- this.linkedServices.splice(index, 1);
+ this.linkedServices.splice(index, 1)
}
- this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE);
+ this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE)
}
public removeCharacteristic(characteristic: Characteristic): void {
- const index = this.characteristics.indexOf(characteristic);
+ const index = this.characteristics.indexOf(characteristic)
if (index !== -1) {
- this.characteristics.splice(index, 1);
- characteristic.removeAllListeners();
+ this.characteristics.splice(index, 1)
+ characteristic.removeAllListeners()
- this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE);
+ this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE)
}
}
// If a Characteristic constructor is passed a Characteristic object will always be returned
- public getCharacteristic(constructor: WithUUID<{new (): Characteristic}>): Characteristic
+ public getCharacteristic(constructor: WithUUID<{ new (): Characteristic }>): Characteristic
// Still support using a Characteristic constructor or a name so "passing though" a value still works
// https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#use-union-types
- public getCharacteristic(name: string | WithUUID<{new (): Characteristic}>): Characteristic | undefined
- public getCharacteristic(name: string | WithUUID<{new (): Characteristic}>): Characteristic | undefined {
+ public getCharacteristic(name: string | WithUUID<{ new (): Characteristic }>): Characteristic | undefined
+ public getCharacteristic(name: string | WithUUID<{ new (): Characteristic }>): Characteristic | undefined {
// returns a characteristic object from the service
// If Service.prototype.getCharacteristic(Characteristic.Type) does not find the characteristic,
// but the type is in optionalCharacteristics, it adds the characteristic.type to the service and returns it.
for (const characteristic of this.characteristics) {
- if (typeof name === "string" && characteristic.displayName === name) {
- return characteristic;
+ if (typeof name === 'string' && characteristic.displayName === name) {
+ return characteristic
} else {
// @ts-expect-error ('UUID' does not exist on type 'never')
- if (typeof name === "function" && ((characteristic instanceof name) || (name.UUID === characteristic.UUID))) {
- return characteristic;
+ if (typeof name === 'function' && ((characteristic instanceof name) || (name.UUID === characteristic.UUID))) {
+ return characteristic
}
}
}
- if (typeof name === "function") {
+ if (typeof name === 'function') {
for (const characteristic of this.optionalCharacteristics) {
// @ts-expect-error ('UUID' does not exist on type 'never')
if ((characteristic instanceof name) || (name.UUID === characteristic.UUID)) {
- return this.addCharacteristic(name);
+ return this.addCharacteristic(name)
}
}
- const instance = this.addCharacteristic(name);
+ const instance = this.addCharacteristic(name)
// Not found in optional Characteristics. Adding anyway, but warning about it if it isn't the Name.
if (name.UUID !== Characteristic.Name.UUID) {
- this.emitCharacteristicWarningEvent(instance, CharacteristicWarningType.WARN_MESSAGE,
- "Characteristic not in required or optional characteristic section for service " + this.constructor.name + ". Adding anyway.");
+ this.emitCharacteristicWarningEvent(instance, CharacteristicWarningType.WARN_MESSAGE, `Characteristic not in required or optional characteristic section for service ${this.constructor.name}. Adding anyway.`)
}
- return instance;
+ return instance
}
}
public testCharacteristic>(name: string | T): boolean {
// checks for the existence of a characteristic object in the service
for (const characteristic of this.characteristics) {
- if (typeof name === "string" && characteristic.displayName === name) {
- return true;
+ if (typeof name === 'string' && characteristic.displayName === name) {
+ return true
} else {
// @ts-expect-error ('UUID' does not exist on type 'never')
- if (typeof name === "function" && ((characteristic instanceof name) || (name.UUID === characteristic.UUID))) {
- return true;
+ if (typeof name === 'function' && ((characteristic instanceof name) || (name.UUID === characteristic.UUID))) {
+ return true
}
}
}
- return false;
+ return false
}
/**
@@ -670,7 +676,7 @@ export class Service extends EventEmitter {
*
* Note: If you don't want the {@link CharacteristicEventTypes.SET} to be called, refer to {@link updateCharacteristic}.
*/
- public setCharacteristic>(name: string | T, value: CharacteristicValue): Service;
+ public setCharacteristic>(name: string | T, value: CharacteristicValue): Service
/**
* Sets the state of the characteristic to an errored state.
*
@@ -691,17 +697,17 @@ export class Service extends EventEmitter {
* @param error - The error object
*
* Note: Erroneous state is never **pushed** to the client side. Only, if the HomeKit client requests the current
- * state of the Characteristic, the corresponding {@link HapStatusError} is returned. As described above,
- * any {@link Characteristic.onGet} or {@link CharacteristicEventTypes.GET} handlers have preference.
+ * state of the Characteristic, the corresponding {@link HapStatusError} is returned. As described above,
+ * any {@link Characteristic.onGet} or {@link CharacteristicEventTypes.GET} handlers have preference.
*/
- public setCharacteristic>(name: string | T, error: HapStatusError | Error): Service
- public setCharacteristic>(
+ public setCharacteristic>(name: string | T, error: HapStatusError | Error): Service
+ public setCharacteristic>(
name: string | T,
value: CharacteristicValue | HapStatusError | Error,
): Service {
// @ts-expect-error: We know that both overloads exists individually. There is just no publicly exposed type for that!
- this.getCharacteristic(name)!.setValue(value);
- return this; // for chaining
+ this.getCharacteristic(name)!.setValue(value)
+ return this // for chaining
}
/**
@@ -711,7 +717,7 @@ export class Service extends EventEmitter {
* @param name - The name or the constructor of the desired {@link Characteristic}.
* @param value - The new value.
*/
- public updateCharacteristic>(name: string | T, value: Nullable): Service;
+ public updateCharacteristic>(name: string | T, value: Nullable): Service
/**
* Sets the state of the characteristic to an errored state.
* If a {@link Characteristic.onGet} or {@link CharacteristicEventTypes.GET} handler is set up,
@@ -729,29 +735,28 @@ export class Service extends EventEmitter {
* guide for more information on how to present erroneous state to the user.
*
* Note: Erroneous state is never **pushed** to the client side. Only, if the HomeKit client requests the current
- * state of the Characteristic, the corresponding {@link HapStatusError} is returned. As described above,
- * any {@link Characteristic.onGet} or {@link CharacteristicEventTypes.GET} handlers have precedence.
+ * state of the Characteristic, the corresponding {@link HapStatusError} is returned. As described above,
+ * any {@link Characteristic.onGet} or {@link CharacteristicEventTypes.GET} handlers have precedence.
*/
- public updateCharacteristic>(name: string | T, error: HapStatusError | Error): Service
- public updateCharacteristic>(
+ public updateCharacteristic>(name: string | T, error: HapStatusError | Error): Service
+ public updateCharacteristic>(
name: string | T,
value: Nullable | HapStatusError | Error,
): Service {
- this.getCharacteristic(name)!.updateValue(value);
- return this;
+ this.getCharacteristic(name)!.updateValue(value)
+ return this
}
- public addOptionalCharacteristic(characteristic: Characteristic | {new (): Characteristic}): void {
+ public addOptionalCharacteristic(characteristic: Characteristic | { new (): Characteristic }): void {
// characteristic might be a constructor like `Characteristic.Brightness` instead of an instance
// of Characteristic. Coerce if necessary.
- if (typeof characteristic === "function") {
- characteristic = new characteristic() as Characteristic;
+ if (typeof characteristic === 'function') {
+ characteristic = new characteristic() as Characteristic // eslint-disable-line new-cap
}
- this.optionalCharacteristics.push(characteristic);
+ this.optionalCharacteristics.push(characteristic)
}
- // noinspection JSUnusedGlobalSymbols
/**
* This method was created to copy all characteristics from another service to this.
* It's only adopting is currently in homebridge to merge the AccessoryInformation service. So some things
@@ -760,31 +765,31 @@ export class Service extends EventEmitter {
* It will not remove characteristics which are present currently but not added on the other characteristic.
* It will not replace the characteristic if the value is falsy (except of '0' or 'false')
* @param service
- * @private used by homebridge
+ * @private
*/
replaceCharacteristicsFromService(service: Service): void {
if (this.UUID !== service.UUID) {
- throw new Error(`Incompatible services. Tried replacing characteristics of ${this.UUID} with characteristics from ${service.UUID}`);
+ throw new Error(`Incompatible services. Tried replacing characteristics of ${this.UUID} with characteristics from ${service.UUID}`)
}
- const foreignCharacteristics: Record = {}; // index foreign characteristics by UUID
- service.characteristics.forEach(characteristic => foreignCharacteristics[characteristic.UUID] = characteristic);
+ const foreignCharacteristics: Record = {} // index foreign characteristics by UUID
+ service.characteristics.forEach(characteristic => foreignCharacteristics[characteristic.UUID] = characteristic)
- this.characteristics.forEach(characteristic => {
- const foreignCharacteristic = foreignCharacteristics[characteristic.UUID];
+ this.characteristics.forEach((characteristic) => {
+ const foreignCharacteristic = foreignCharacteristics[characteristic.UUID]
if (foreignCharacteristic) {
- delete foreignCharacteristics[characteristic.UUID];
+ delete foreignCharacteristics[characteristic.UUID]
if (!foreignCharacteristic.value && foreignCharacteristic.value !== 0 && foreignCharacteristic.value !== false) {
- return; // ignore falsy values except if it's the number zero or literally false
+ return // ignore falsy values except if it's the number zero or literally false
}
- characteristic.replaceBy(foreignCharacteristic);
+ characteristic.replaceBy(foreignCharacteristic)
}
- });
+ })
// add all additional characteristics which where not present already
- Object.values(foreignCharacteristics).forEach(characteristic => this.addCharacteristic(characteristic));
+ Object.values(foreignCharacteristics).forEach(characteristic => this.addCharacteristic(characteristic))
}
/**
@@ -793,7 +798,7 @@ export class Service extends EventEmitter {
getCharacteristicByIID(iid: number): Characteristic | undefined {
for (const characteristic of this.characteristics) {
if (characteristic.iid === iid) {
- return characteristic;
+ return characteristic
}
}
}
@@ -803,38 +808,38 @@ export class Service extends EventEmitter {
*/
_assignIDs(identifierCache: IdentifierCache, accessoryName: string, baseIID = 0): void {
// the Accessory Information service must have a (reserved by IdentifierCache) ID of 1
- if (this.UUID === "0000003E-0000-1000-8000-0026BB765291") {
- this.iid = 1;
+ if (this.UUID === '0000003E-0000-1000-8000-0026BB765291') {
+ this.iid = 1
} else {
// assign our own ID based on our UUID
- this.iid = baseIID + identifierCache.getIID(accessoryName, this.UUID, this.subtype);
+ this.iid = baseIID + identifierCache.getIID(accessoryName, this.UUID, this.subtype)
}
// assign IIDs to our Characteristics
for (const characteristic of this.characteristics) {
- characteristic._assignID(identifierCache, accessoryName, this.UUID, this.subtype);
+ characteristic._assignID(identifierCache, accessoryName, this.UUID, this.subtype)
}
}
/**
* Returns a JSON representation of this service suitable for delivering to HAP clients.
- * @private used to generate response to /accessories query
+ * @private
*/
toHAP(connection: HAPConnection, contactGetHandlers = true): Promise {
- return new Promise(resolve => {
- assert(this.iid, "iid cannot be undefined for service '" + this.displayName + "'");
- assert(this.characteristics.length, "service '" + this.displayName + "' does not have any characteristics!");
+ return new Promise((resolve) => {
+ assert(this.iid, `iid cannot be undefined for service '${this.displayName}'`)
+ assert(this.characteristics.length, `service '${this.displayName}' does not have any characteristics!`)
const service: ServiceJsonObject = {
type: toShortForm(this.UUID),
iid: this.iid!,
characteristics: [],
- hidden: this.isHiddenService? true: undefined,
- primary: this.isPrimaryService? true: undefined,
- };
+ hidden: this.isHiddenService ? true : undefined,
+ primary: this.isPrimaryService ? true : undefined,
+ }
if (this.linkedServices.length) {
- service.linked = [];
+ service.linked = []
for (const linked of this.linkedServices) {
if (!linked.iid) {
// we got a linked service which is not added to the accessory
@@ -842,75 +847,73 @@ export class Service extends EventEmitter {
// we have some (at least one) plugins on homebridge which link to the AccessoryInformation service.
// homebridge always creates its own AccessoryInformation service and ignores the user supplied one
// thus the link is automatically broken.
- debug(`iid of linked service '${linked.displayName}' ${linked.UUID} is undefined on service '${this.displayName}'`);
- continue;
+ debug(`iid of linked service '${linked.displayName}' ${linked.UUID} is undefined on service '${this.displayName}'`)
+ continue
}
- service.linked.push(linked.iid!);
+ service.linked.push(linked.iid!)
}
}
- const missingCharacteristics: Set = new Set();
+ const missingCharacteristics: Set = new Set()
let timeout: NodeJS.Timeout | undefined = setTimeout(() => {
for (const characteristic of missingCharacteristics) {
- this.emitCharacteristicWarningEvent(characteristic, CharacteristicWarningType.SLOW_READ,
- `The read handler for the characteristic '${characteristic.displayName}' was slow to respond!`);
+ this.emitCharacteristicWarningEvent(characteristic, CharacteristicWarningType.SLOW_READ, `The read handler for the characteristic '${characteristic.displayName}' was slow to respond!`)
}
timeout = setTimeout(() => {
- timeout = undefined;
+ timeout = undefined
for (const characteristic of missingCharacteristics) {
- this.emitCharacteristicWarningEvent(characteristic, CharacteristicWarningType.TIMEOUT_READ,
- "The read handler for the characteristic '" + characteristic?.displayName +
- "' didn't respond at all!. Please check that you properly call the callback!");
- service.characteristics.push(characteristic.internalHAPRepresentation()); // value is set to null
+ this.emitCharacteristicWarningEvent(characteristic, CharacteristicWarningType.TIMEOUT_READ, `The read handler for the characteristic '${characteristic?.displayName
+ }' didn't respond at all!. Please check that you properly call the callback!`)
+ service.characteristics.push(characteristic.internalHAPRepresentation()) // value is set to null
}
- missingCharacteristics.clear();
- resolve(service);
- }, 6000);
- }, 3000);
+ missingCharacteristics.clear()
+ resolve(service)
+ }, 6000)
+ }, 3000)
for (const characteristic of this.characteristics) {
- missingCharacteristics.add(characteristic);
- characteristic.toHAP(connection, contactGetHandlers).then(value => {
+ missingCharacteristics.add(characteristic)
+ characteristic.toHAP(connection, contactGetHandlers).then((value) => {
if (!timeout) {
- return; // if timeout is undefined, response was already sent out
+ return // if timeout is undefined, response was already sent out
}
- missingCharacteristics.delete(characteristic);
- service.characteristics.push(value);
+ missingCharacteristics.delete(characteristic)
+ service.characteristics.push(value)
if (missingCharacteristics.size === 0) {
if (timeout) {
- clearTimeout(timeout);
- timeout = undefined;
+ clearTimeout(timeout)
+ timeout = undefined
}
- resolve(service);
+ resolve(service)
}
- });
+ })
}
- });
+ })
}
/**
* Returns a JSON representation of this service without characteristic values.
- * @private used to generate the config hash
+ * @private
*/
internalHAPRepresentation(): ServiceJsonObject {
- assert(this.iid, "iid cannot be undefined for service '" + this.displayName + "'");
- assert(this.characteristics.length, "service '" + this.displayName + "' does not have any characteristics!");
+ assert(this.iid, `iid cannot be undefined for service '${this.displayName}'`)
+ assert(this.characteristics.length, `service '${this.displayName}' does not have any characteristics!`)
const service: ServiceJsonObject = {
type: toShortForm(this.UUID),
iid: this.iid!,
characteristics: this.characteristics.map(characteristic => characteristic.internalHAPRepresentation()),
- hidden: this.isHiddenService? true: undefined,
- primary: this.isPrimaryService? true: undefined,
- };
+ hidden: this.isHiddenService ? true : undefined,
+ primary: this.isPrimaryService ? true : undefined,
+ }
if (this.linkedServices.length) {
- service.linked = [];
+ service.linked = []
for (const linked of this.linkedServices) {
if (!linked.iid) {
// we got a linked service which is not added to the accessory
@@ -918,14 +921,14 @@ export class Service extends EventEmitter {
// we have some (at least one) plugins on homebridge which link to the AccessoryInformation service.
// homebridge always creates its own AccessoryInformation service and ignores the user supplied one
// thus the link is automatically broken.
- debug(`iid of linked service '${linked.displayName}' ${linked.UUID} is undefined on service '${this.displayName}'`);
- continue;
+ debug(`iid of linked service '${linked.displayName}' ${linked.UUID} is undefined on service '${this.displayName}'`)
+ continue
}
- service.linked.push(linked.iid!);
+ service.linked.push(linked.iid!)
}
}
- return service;
+ return service
}
/**
@@ -934,10 +937,10 @@ export class Service extends EventEmitter {
private setupCharacteristicEventHandlers(characteristic: Characteristic): void {
// listen for changes in characteristics and bubble them up
characteristic.on(CharacteristicEventTypes.CHANGE, (change: CharacteristicChange) => {
- this.emit(ServiceEventTypes.CHARACTERISTIC_CHANGE, { ...change, characteristic: characteristic });
- });
+ this.emit(ServiceEventTypes.CHARACTERISTIC_CHANGE, { ...change, characteristic })
+ })
- characteristic.on(CharacteristicEventTypes.CHARACTERISTIC_WARNING, this.emitCharacteristicWarningEvent.bind(this, characteristic));
+ characteristic.on(CharacteristicEventTypes.CHARACTERISTIC_WARNING, this.emitCharacteristicWarningEvent.bind(this, characteristic))
}
/**
@@ -945,12 +948,12 @@ export class Service extends EventEmitter {
*/
private emitCharacteristicWarningEvent(characteristic: Characteristic, type: CharacteristicWarningType, message: string, stack?: string): void {
this.emit(ServiceEventTypes.CHARACTERISTIC_WARNING, {
- characteristic: characteristic,
- type: type,
- message: message,
+ characteristic,
+ type,
+ message,
originatorChain: [this.displayName, characteristic.displayName],
- stack: stack,
- });
+ stack,
+ })
}
/**
@@ -958,19 +961,19 @@ export class Service extends EventEmitter {
*/
private _sideloadCharacteristics(targetCharacteristics: Characteristic[]): void {
for (const target of targetCharacteristics) {
- this.setupCharacteristicEventHandlers(target);
+ this.setupCharacteristicEventHandlers(target)
}
- this.characteristics = targetCharacteristics.slice();
+ this.characteristics = targetCharacteristics.slice()
}
/**
* @private
*/
static serialize(service: Service): SerializedService {
- let constructorName: string | undefined;
- if (service.constructor.name !== "Service") {
- constructorName = service.constructor.name;
+ let constructorName: string | undefined
+ if (service.constructor.name !== 'Service') {
+ constructorName = service.constructor.name
}
return {
@@ -978,46 +981,47 @@ export class Service extends EventEmitter {
UUID: service.UUID,
subtype: service.subtype,
- constructorName: constructorName,
+ constructorName,
hiddenService: service.isHiddenService,
primaryService: service.isPrimaryService,
characteristics: service.characteristics.map(characteristic => Characteristic.serialize(characteristic)),
optionalCharacteristics: service.optionalCharacteristics.map(characteristic => Characteristic.serialize(characteristic)),
- };
+ }
}
/**
* @private
*/
static deserialize(json: SerializedService): Service {
- let service: Service;
+ let service: Service
if (json.constructorName && json.constructorName.charAt(0).toUpperCase() === json.constructorName.charAt(0)
&& Service[json.constructorName as keyof (typeof Service)]) { // MUST start with uppercase character and must exist on Service object
- const constructor = Service[json.constructorName as keyof (typeof Service)] as { new(displayName?: string, subtype?: string): Service };
- service = new constructor(json.displayName, json.subtype);
+ const constructor = Service[json.constructorName as keyof (typeof Service)] as { new(displayName?: string, subtype?: string): Service }
+ service = new constructor(json.displayName, json.subtype)
} else {
- service = new Service(json.displayName, json.UUID, json.subtype);
+ service = new Service(json.displayName, json.UUID, json.subtype)
}
- service.isHiddenService = !!json.hiddenService;
- service.isPrimaryService = !!json.primaryService;
+ service.isHiddenService = !!json.hiddenService
+ service.isPrimaryService = !!json.primaryService
- const characteristics = json.characteristics.map(serialized => Characteristic.deserialize(serialized));
- service._sideloadCharacteristics(characteristics);
+ const characteristics = json.characteristics.map(serialized => Characteristic.deserialize(serialized))
+ service._sideloadCharacteristics(characteristics)
if (json.optionalCharacteristics) {
- service.optionalCharacteristics = json.optionalCharacteristics.map(serialized => Characteristic.deserialize(serialized));
+ service.optionalCharacteristics = json.optionalCharacteristics.map(serialized => Characteristic.deserialize(serialized))
}
- return service;
+ return service
}
-
}
// We have a cyclic dependency problem. Within this file we have the definitions of "./definitions" as
// type imports only (in order to define the static properties). Setting those properties is done outside
// this file, within the definition files. Therefore, we import it at the end of this file. Seems weird, but is important.
-import "./definitions/ServiceDefinitions";
+(async () => {
+ await import('./definitions/ServiceDefinitions.js')
+})()
diff --git a/src/lib/camera/RTPProxy.ts b/src/lib/camera/RTPProxy.ts
index b7a5b1e77..a53b4c5d7 100644
--- a/src/lib/camera/RTPProxy.ts
+++ b/src/lib/camera/RTPProxy.ts
@@ -1,14 +1,17 @@
-import dgram, { Socket, SocketType } from "dgram";
+import type { Socket, SocketType } from 'node:dgram'
+
+import { Buffer } from 'node:buffer'
+import { createSocket } from 'node:dgram'
/**
* @group Camera
*/
export interface RTPProxyOptions {
- disabled: boolean;
- isIPV6?: boolean;
- outgoingAddress: string;
- outgoingPort: number;
- outgoingSSRC: number;
+ disabled: boolean
+ isIPV6?: boolean
+ outgoingAddress: string
+ outgoingPort: number
+ outgoingSSRC: number
}
/**
@@ -21,318 +24,296 @@ export interface RTPProxyOptions {
* @group Camera
*/
export default class RTPProxy {
- startingPort = 10000;
- type: SocketType;
- outgoingAddress: string;
- outgoingPort: number;
- incomingPayloadType: number;
- outgoingSSRC: number;
- incomingSSRC: number | null;
- outgoingPayloadType: number | null;
- disabled: boolean;
-
- incomingRTPSocket!: Socket;
- incomingRTCPSocket!: Socket;
- outgoingSocket!: Socket;
- serverAddress?: string;
- serverRTPPort?: number;
- serverRTCPPort?: number;
+ startingPort = 10000
+ type: SocketType
+ outgoingAddress: string
+ outgoingPort: number
+ incomingPayloadType: number
+ outgoingSSRC: number
+ incomingSSRC: number | null
+ outgoingPayloadType: number | null
+ disabled: boolean
+
+ incomingRTPSocket!: Socket
+ incomingRTCPSocket!: Socket
+ outgoingSocket!: Socket
+ serverAddress?: string
+ serverRTPPort?: number
+ serverRTCPPort?: number
constructor(public options: RTPProxyOptions) {
- this.type = options.isIPV6 ? "udp6" : "udp4";
+ this.type = options.isIPV6 ? 'udp6' : 'udp4'
- this.startingPort = 10000;
+ this.startingPort = 10000
- this.outgoingAddress = options.outgoingAddress;
- this.outgoingPort = options.outgoingPort;
- this.incomingPayloadType = 0;
- this.outgoingSSRC = options.outgoingSSRC;
- this.disabled = options.disabled;
- this.incomingSSRC = null;
- this.outgoingPayloadType = null;
+ this.outgoingAddress = options.outgoingAddress
+ this.outgoingPort = options.outgoingPort
+ this.incomingPayloadType = 0
+ this.outgoingSSRC = options.outgoingSSRC
+ this.disabled = options.disabled
+ this.incomingSSRC = null
+ this.outgoingPayloadType = null
}
- setup(): Promise {
- return this.createSocketPair(this.type)
- .then((sockets) => {
- this.incomingRTPSocket = sockets[0];
- this.incomingRTCPSocket = sockets[1];
-
- return this.createSocket(this.type);
- }).then((socket) => {
- this.outgoingSocket = socket;
- this.onBound();
- });
+ async setup(): Promise {
+ const sockets = await this.createSocketPair(this.type)
+ this.incomingRTPSocket = sockets[0]
+ this.incomingRTCPSocket = sockets[1]
+ this.outgoingSocket = await this.createSocket(this.type)
+ this.onBound()
}
destroy(): void {
if (this.incomingRTPSocket) {
- this.incomingRTPSocket.close();
+ this.incomingRTPSocket.close()
}
if (this.incomingRTCPSocket) {
- this.incomingRTCPSocket.close();
+ this.incomingRTCPSocket.close()
}
if (this.outgoingSocket) {
- this.outgoingSocket.close();
+ this.outgoingSocket.close()
}
}
incomingRTPPort(): number {
- const address = this.incomingRTPSocket.address();
-
- if (typeof address !== "string") {
- return address.port;
- }
-
- throw new Error("Unsupported socket!");
+ const address = this.incomingRTPSocket.address()
+ return address.port
}
incomingRTCPPort(): number {
- const address = this.incomingRTCPSocket.address();
-
- if (typeof address !== "string") {
- return address.port;
- }
-
- throw new Error("Unsupported socket!");
+ const address = this.incomingRTCPSocket.address()
+ return address.port
}
outgoingLocalPort(): number {
- const address = this.outgoingSocket.address();
-
- if (typeof address !== "string") {
- return address.port;
- }
-
- throw new Error("Unsupported socket!");
+ const address = this.outgoingSocket.address()
+ return address.port
}
setServerAddress(address: string): void {
- this.serverAddress = address;
+ this.serverAddress = address
}
setServerRTPPort(port: number): void {
- this.serverRTPPort = port;
+ this.serverRTPPort = port
}
setServerRTCPPort(port: number): void {
- this.serverRTCPPort = port;
+ this.serverRTCPPort = port
}
setIncomingPayloadType(pt: number): void {
- this.incomingPayloadType = pt;
+ this.incomingPayloadType = pt
}
setOutgoingPayloadType(pt: number): void {
- this.outgoingPayloadType = pt;
+ this.outgoingPayloadType = pt
}
sendOut(msg: Buffer): void {
// Just drop it if we're not setup yet, I guess.
- if(!this.outgoingAddress || !this.outgoingPort) {
- return;
+ if (!this.outgoingAddress || !this.outgoingPort) {
+ return
}
- this.outgoingSocket.send(msg, this.outgoingPort, this.outgoingAddress);
+ this.outgoingSocket.send(msg, this.outgoingPort, this.outgoingAddress)
}
sendBack(msg: Buffer): void {
// Just drop it if we're not setup yet, I guess.
- if(!this.serverAddress || !this.serverRTCPPort) {
- return;
+ if (!this.serverAddress || !this.serverRTCPPort) {
+ return
}
- this.outgoingSocket.send(msg, this.serverRTCPPort, this.serverAddress);
+ this.outgoingSocket.send(msg, this.serverRTCPPort, this.serverAddress)
}
onBound(): void {
- if(this.disabled) {
- return;
+ if (this.disabled) {
+ return
}
- this.incomingRTPSocket.on("message", msg => {
- this.rtpMessage(msg);
- });
+ this.incomingRTPSocket.on('message', (msg) => {
+ this.rtpMessage(msg)
+ })
- this.incomingRTCPSocket.on("message", msg => {
- this.rtcpMessage(msg);
- });
+ this.incomingRTCPSocket.on('message', (msg) => {
+ this.rtcpMessage(msg)
+ })
- this.outgoingSocket.on("message", msg => {
- this.rtcpReply(msg);
- });
+ this.outgoingSocket.on('message', (msg) => {
+ this.rtcpReply(msg)
+ })
}
rtpMessage(msg: Buffer): void {
-
- if(msg.length < 12) {
+ if (msg.length < 12) {
// Not a proper RTP packet. Just forward it.
- this.sendOut(msg);
- return;
+ this.sendOut(msg)
+ return
}
- let mpt = msg.readUInt8(1);
- const pt = mpt & 0x7F;
- if(pt === this.incomingPayloadType) {
- mpt = (mpt & 0x80) | this.outgoingPayloadType!;
- msg.writeUInt8(mpt, 1);
+ let mpt = msg.readUInt8(1)
+ const pt = mpt & 0x7F
+ if (pt === this.incomingPayloadType) {
+ mpt = (mpt & 0x80) | this.outgoingPayloadType!
+ msg.writeUInt8(mpt, 1)
}
- if(this.incomingSSRC === null) {
- this.incomingSSRC = msg.readUInt32BE(4);
+ if (this.incomingSSRC === null) {
+ this.incomingSSRC = msg.readUInt32BE(4)
}
- msg.writeUInt32BE(this.outgoingSSRC, 8);
- this.sendOut(msg);
+ msg.writeUInt32BE(this.outgoingSSRC, 8)
+ this.sendOut(msg)
}
processRTCPMessage(msg: Buffer, transform: (pt: number, packet: Buffer) => Buffer): Buffer | null {
- const rtcpPackets = [];
- let offset = 0;
- while((offset + 4) <= msg.length) {
- const pt = msg.readUInt8(offset + 1);
- const len = msg.readUInt16BE(offset + 2) * 4;
- if((offset + 4 + len) > msg.length) {
- break;
+ const rtcpPackets = []
+ let offset = 0
+ while ((offset + 4) <= msg.length) {
+ const pt = msg.readUInt8(offset + 1)
+ const len = msg.readUInt16BE(offset + 2) * 4
+ if ((offset + 4 + len) > msg.length) {
+ break
}
- let packet = msg.slice(offset, offset + 4 + len);
+ let packet = msg.subarray(offset, offset + 4 + len)
- packet = transform(pt, packet);
+ packet = transform(pt, packet)
- if(packet) {
- rtcpPackets.push(packet);
+ if (packet) {
+ rtcpPackets.push(packet)
}
- offset += 4 + len;
+ offset += 4 + len
}
- if(rtcpPackets.length > 0) {
- return Buffer.concat(rtcpPackets);
+ if (rtcpPackets.length > 0) {
+ return Buffer.concat(rtcpPackets)
}
- return null;
+ return null
}
rtcpMessage(msg: Buffer): void {
const processed = this.processRTCPMessage(msg, (pt, packet) => {
- if(pt !== 200 || packet.length < 8) {
- return packet;
+ if (pt !== 200 || packet.length < 8) {
+ return packet
}
- if(this.incomingSSRC === null) {
- this.incomingSSRC = packet.readUInt32BE(4);
+ if (this.incomingSSRC === null) {
+ this.incomingSSRC = packet.readUInt32BE(4)
}
- packet.writeUInt32BE(this.outgoingSSRC, 4);
- return packet;
- });
+ packet.writeUInt32BE(this.outgoingSSRC, 4)
+ return packet
+ })
- if(processed) {
- this.sendOut(processed);
+ if (processed) {
+ this.sendOut(processed)
}
}
rtcpReply(msg: Buffer): void {
const processed = this.processRTCPMessage(msg, (pt, packet) => {
- if(pt !== 201 || packet.length < 12) {
- return packet;
+ if (pt !== 201 || packet.length < 12) {
+ return packet
}
// Assume source 1 is the one we want to edit.
- packet.writeUInt32BE(this.incomingSSRC!, 8);
- return packet;
- });
+ packet.writeUInt32BE(this.incomingSSRC!, 8)
+ return packet
+ })
-
- if(processed) {
- this.sendOut(processed);
+ if (processed) {
+ this.sendOut(processed)
}
}
createSocket(type: SocketType): Promise {
- return new Promise(resolve => {
+ return new Promise((resolve) => {
const retry = () => {
- const socket = dgram.createSocket(type);
+ const socket = createSocket(type)
const bindErrorHandler = () => {
- if(this.startingPort === 65535) {
- this.startingPort = 10000;
+ if (this.startingPort === 65535) {
+ this.startingPort = 10000
} else {
- ++this.startingPort;
+ ++this.startingPort
}
- socket.close();
- retry();
- };
+ socket.close()
+ retry()
+ }
- socket.once("error", bindErrorHandler);
+ socket.once('error', bindErrorHandler)
- socket.on("listening", () => {
- resolve(socket);
- });
+ socket.on('listening', () => {
+ resolve(socket)
+ })
- socket.bind(this.startingPort);
- };
+ socket.bind(this.startingPort)
+ }
- retry();
- });
+ retry()
+ })
}
createSocketPair(type: SocketType): Promise {
- return new Promise(resolve => {
+ return new Promise((resolve) => {
const retry = () => {
- const socket1 = dgram.createSocket(type);
- const socket2 = dgram.createSocket(type);
- const state = { socket1: 0, socket2: 0 };
+ const socket1 = createSocket(type)
+ const socket2 = createSocket(type)
+ const state = { socket1: 0, socket2: 0 }
const recheck = () => {
- if(state.socket1 === 0 || state.socket2 === 0) {
- return;
+ if (state.socket1 === 0 || state.socket2 === 0) {
+ return
}
- if(state.socket1 === 2 && state.socket2 === 2) {
- resolve([socket1, socket2]);
- return;
+ if (state.socket1 === 2 && state.socket2 === 2) {
+ resolve([socket1, socket2])
+ return
}
- if(this.startingPort === 65534) {
- this.startingPort = 10000;
+ if (this.startingPort === 65534) {
+ this.startingPort = 10000
} else {
- ++this.startingPort;
+ ++this.startingPort
}
- socket1.close();
- socket2.close();
+ socket1.close()
+ socket2.close()
- retry();
- };
+ retry()
+ }
- socket1.once("error", () => {
- state.socket1 = 1;
- recheck();
- });
+ socket1.once('error', () => {
+ state.socket1 = 1
+ recheck()
+ })
- socket2.once("error", () => {
- state.socket2 = 1;
- recheck();
- });
+ socket2.once('error', () => {
+ state.socket2 = 1
+ recheck()
+ })
- socket1.once("listening", () => {
- state.socket1 = 2;
- recheck();
- });
+ socket1.once('listening', () => {
+ state.socket1 = 2
+ recheck()
+ })
- socket2.once("listening", () => {
- state.socket2 = 2;
- recheck();
- });
+ socket2.once('listening', () => {
+ state.socket2 = 2
+ recheck()
+ })
- socket1.bind(this.startingPort);
- socket2.bind(this.startingPort + 1);
- };
+ socket1.bind(this.startingPort)
+ socket2.bind(this.startingPort + 1)
+ }
- retry();
- });
+ retry()
+ })
}
}
diff --git a/src/lib/camera/RTPStreamManagement.ts b/src/lib/camera/RTPStreamManagement.ts
index 274679aa3..cbb2c21c9 100644
--- a/src/lib/camera/RTPStreamManagement.ts
+++ b/src/lib/camera/RTPStreamManagement.ts
@@ -1,27 +1,37 @@
-import assert from "assert";
-import crypto from "crypto";
-import createDebug from "debug";
-import net from "net";
-import { Access, Characteristic, CharacteristicEventTypes, CharacteristicSetCallback } from "../Characteristic";
-import { CameraController, CameraStreamingDelegate, ResourceRequestReason, StateChangeDelegate } from "../controller";
-import type { CameraRTPStreamManagement } from "../definitions";
-import { CharacteristicValue } from "../../types";
-import { HAPStatus } from "../HAPServer";
-import { Service } from "../Service";
-import { HAPConnection, HAPConnectionEvent } from "../util/eventedhttp";
-import { HapStatusError } from "../util/hapStatusError";
-import { once } from "../util/once";
-import * as tlv from "../util/tlv";
-import * as uuid from "../util/uuid";
-import RTPProxy from "./RTPProxy";
-
-const debug = createDebug("HAP-NodeJS:Camera:RTPStreamManagement");
+import type { CharacteristicValue } from '../../types'
+import type { CharacteristicSetCallback } from '../Characteristic'
+import type { CameraStreamingDelegate, ResourceRequestReason, StateChangeDelegate } from '../controller'
+import type { CameraRTPStreamManagement } from '../definitions'
+import type { HAPConnection } from '../util/eventedhttp'
+
+import assert from 'node:assert'
+import { Buffer } from 'node:buffer'
+import { randomBytes } from 'node:crypto'
+import { isIPv4 } from 'node:net'
+
+import createDebug from 'debug'
+
+import { Access, Characteristic, CharacteristicEventTypes } from '../Characteristic.js'
+import { CameraController } from '../controller/index.js'
+import { HAPStatus } from '../HAPServer.js'
+import { Service } from '../Service.js'
+import { HAPConnectionEvent } from '../util/eventedhttp.js'
+import { HapStatusError } from '../util/hapStatusError.js'
+import { once } from '../util/once.js'
+import { decode, encode, writeUInt16, writeUInt32 } from '../util/tlv.js'
+import { unparse, write } from '../util/uuid.js'
+import RTPProxy from './RTPProxy.js'
+
+const debug = createDebug('HAP-NodeJS:Camera:RTPStreamManagement')
+
// ---------------------------------- TLV DEFINITIONS START ----------------------------------
+// eslint-disable-next-line no-restricted-syntax
const enum StreamingStatusTypes {
STATUS = 0x01,
}
+// eslint-disable-next-line no-restricted-syntax
const enum StreamingStatus {
AVAILABLE = 0x00,
IN_USE = 0x01, // Session is marked IN_USE after the first setup request
@@ -30,16 +40,19 @@ const enum StreamingStatus {
// ----------
+// eslint-disable-next-line no-restricted-syntax
const enum SupportedVideoStreamConfigurationTypes {
VIDEO_CODEC_CONFIGURATION = 0x01,
}
+// eslint-disable-next-line no-restricted-syntax
const enum VideoCodecConfigurationTypes {
CODEC_TYPE = 0x01,
CODEC_PARAMETERS = 0x02,
ATTRIBUTES = 0x03,
}
+// eslint-disable-next-line no-restricted-syntax
const enum VideoCodecParametersTypes {
PROFILE_ID = 0x01,
LEVEL = 0x02,
@@ -48,15 +61,17 @@ const enum VideoCodecParametersTypes {
CVO_ID = 0x05, // ID for CVO RTP extension, value in range from 1 to 14
}
+// eslint-disable-next-line no-restricted-syntax
const enum VideoAttributesTypes {
IMAGE_WIDTH = 0x01,
IMAGE_HEIGHT = 0x02,
- FRAME_RATE = 0x03
+ FRAME_RATE = 0x03,
}
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum VideoCodecType {
H264 = 0x00,
// while the namespace is already reserved for H265 it isn't currently supported.
@@ -66,6 +81,7 @@ export const enum VideoCodecType {
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum H264Profile {
BASELINE = 0x00,
MAIN = 0x01,
@@ -75,6 +91,7 @@ export const enum H264Profile {
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum H264Level {
LEVEL3_1 = 0x00,
LEVEL3_2 = 0x01,
@@ -84,27 +101,32 @@ export const enum H264Level {
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum VideoCodecPacketizationMode {
- NON_INTERLEAVED = 0x00
+ NON_INTERLEAVED = 0x00,
}
+// eslint-disable-next-line no-restricted-syntax
const enum VideoCodecCVO { // Coordination of Video Orientation
UNSUPPORTED = 0x00,
- SUPPORTED = 0x01
+ SUPPORTED = 0x01,
}
// ----------
+// eslint-disable-next-line no-restricted-syntax
const enum SupportedAudioStreamConfigurationTypes {
AUDIO_CODEC_CONFIGURATION = 0x01,
COMFORT_NOISE_SUPPORT = 0x02,
}
+// eslint-disable-next-line no-restricted-syntax
const enum AudioCodecConfigurationTypes {
CODEC_TYPE = 0x01,
CODEC_PARAMETERS = 0x02,
}
+// eslint-disable-next-line no-restricted-syntax
const enum AudioCodecTypes { // only really by HAP supported codecs are AAC-ELD and OPUS
PCMU = 0x00,
PCMA = 0x01,
@@ -115,34 +137,38 @@ const enum AudioCodecTypes { // only really by HAP supported codecs are AAC-ELD
AMR_WB = 0x06,
}
+// eslint-disable-next-line no-restricted-syntax
const enum AudioCodecParametersTypes {
CHANNEL = 0x01,
BIT_RATE = 0x02,
SAMPLE_RATE = 0x03,
- PACKET_TIME = 0x04 // only present in selected audio codec parameters tlv
+ PACKET_TIME = 0x04, // only present in selected audio codec parameters tlv
}
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum AudioBitrate {
VARIABLE = 0x00,
- CONSTANT = 0x01
+ CONSTANT = 0x01,
}
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum AudioSamplerate {
KHZ_8 = 0x00,
KHZ_16 = 0x01,
- KHZ_24 = 0x02
+ KHZ_24 = 0x02,
// 3, 4, 5 are theoretically defined, but no idea to what kHz value they correspond to
// probably KHZ_32, KHZ_44_1, KHZ_48 (as supported by Secure Video recordings)
}
// ----------
+// eslint-disable-next-line no-restricted-syntax
const enum SupportedRTPConfigurationTypes {
SRTP_CRYPTO_SUITE = 0x02,
}
@@ -150,16 +176,16 @@ const enum SupportedRTPConfigurationTypes {
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum SRTPCryptoSuites { // public API
AES_CM_128_HMAC_SHA1_80 = 0x00,
AES_CM_256_HMAC_SHA1_80 = 0x01,
- NONE = 0x02
+ NONE = 0x02,
}
-
// ----------
-
+// eslint-disable-next-line no-restricted-syntax
const enum SetupEndpointsTypes {
SESSION_ID = 0x01,
CONTROLLER_ADDRESS = 0x03,
@@ -167,6 +193,7 @@ const enum SetupEndpointsTypes {
AUDIO_SRTP_PARAMETERS = 0x05,
}
+// eslint-disable-next-line no-restricted-syntax
const enum AddressTypes {
ADDRESS_VERSION = 0x01,
ADDRESS = 0x02,
@@ -174,18 +201,20 @@ const enum AddressTypes {
AUDIO_RTP_PORT = 0x04,
}
+// eslint-disable-next-line no-restricted-syntax
const enum IPAddressVersion {
IPV4 = 0x00,
- IPV6 = 0x01
+ IPV6 = 0x01,
}
-
+// eslint-disable-next-line no-restricted-syntax
const enum SRTPParametersTypes {
SRTP_CRYPTO_SUITE = 0x01,
MASTER_KEY = 0x02, // 16 bytes for AES_CM_128_HMAC_SHA1_80; 32 bytes for AES_256_CM_HMAC_SHA1_80
- MASTER_SALT = 0x03 // 14 bytes
+ MASTER_SALT = 0x03, // 14 bytes
}
+// eslint-disable-next-line no-restricted-syntax
const enum SetupEndpointsResponseTypes {
SESSION_ID = 0x01,
STATUS = 0x02,
@@ -196,22 +225,23 @@ const enum SetupEndpointsResponseTypes {
AUDIO_SSRC = 0x07,
}
+// eslint-disable-next-line no-restricted-syntax
const enum SetupEndpointsStatus {
SUCCESS = 0x00,
BUSY = 0x01,
- ERROR = 0x02
+ ERROR = 0x02,
}
-
// ----------
-
+// eslint-disable-next-line no-restricted-syntax
const enum SelectedRTPStreamConfigurationTypes {
SESSION_CONTROL = 0x01,
SELECTED_VIDEO_PARAMETERS = 0x02,
- SELECTED_AUDIO_PARAMETERS = 0x03
+ SELECTED_AUDIO_PARAMETERS = 0x03,
}
+// eslint-disable-next-line no-restricted-syntax
const enum SessionControlTypes {
SESSION_IDENTIFIER = 0x01, // uuid, 16 bytes
COMMAND = 0x02,
@@ -225,6 +255,7 @@ enum SessionControlCommand {
RECONFIGURE_SESSION = 0x04,
}
+// eslint-disable-next-line no-restricted-syntax
const enum SelectedVideoParametersTypes {
CODEC_TYPE = 0x01,
CODEC_PARAMETERS = 0x02,
@@ -232,6 +263,7 @@ const enum SelectedVideoParametersTypes {
RTP_PARAMETERS = 0x04,
}
+// eslint-disable-next-line no-restricted-syntax
const enum VideoRTPParametersTypes {
PAYLOAD_TYPE = 0x01,
SYNCHRONIZATION_SOURCE = 0x02,
@@ -240,6 +272,7 @@ const enum VideoRTPParametersTypes {
MAX_MTU = 0x05, // only there if value is not default value; default values: ipv4 1378; ipv6 1228 bytes
}
+// eslint-disable-next-line no-restricted-syntax
const enum SelectedAudioParametersTypes {
CODEC_TYPE = 0x01,
CODEC_PARAMETERS = 0x02,
@@ -247,12 +280,13 @@ const enum SelectedAudioParametersTypes {
COMFORT_NOISE = 0x04,
}
+// eslint-disable-next-line no-restricted-syntax
const enum AudioRTPParametersTypes {
PAYLOAD_TYPE = 0x01,
SYNCHRONIZATION_SOURCE = 0x02,
MAX_BIT_RATE = 0x03,
MIN_RTCP_INTERVAL = 0x04, // minimum RTCP interval in seconds
- COMFORT_NOISE_PAYLOAD_TYPE = 0x06
+ COMFORT_NOISE_PAYLOAD_TYPE = 0x06,
}
// ---------------------------------- TLV DEFINITIONS END ------------------------------------
@@ -265,112 +299,112 @@ export type CameraStreamingOptions = CameraStreamingOptionsBase & (CameraStreami
* @group Camera
*/
export interface CameraStreamingOptionsBase {
- proxy?: boolean; // default false
- disable_audio_proxy?: boolean; // default false; If proxy = true, you can opt out audio proxy via this
+ proxy?: boolean // default false
+ disable_audio_proxy?: boolean // default false; If proxy = true, you can opt out audio proxy via this
- video: VideoStreamingOptions;
+ video: VideoStreamingOptions
/**
* "audio" is optional and only needs to be declared if audio streaming is supported.
* If defined the Microphone service will be added and Microphone volume control will be made available.
* If not defined hap-nodejs will expose a default codec in order for the video stream to work
*/
- audio?: AudioStreamingOptions;
+ audio?: AudioStreamingOptions
}
/**
* @group Camera
*/
export interface CameraStreamingOptionsLegacySRTP {
- srtp: boolean; // a value of true indicates support of AES_CM_128_HMAC_SHA1_80
+ srtp: boolean // a value of true indicates support of AES_CM_128_HMAC_SHA1_80
}
/**
* @group Camera
*/
export interface CameraStreamingOptionsSupportedCryptoSuites {
- supportedCryptoSuites: SRTPCryptoSuites[], // Suite NONE should only be used for testing and will probably be never selected by iOS!
+ supportedCryptoSuites: SRTPCryptoSuites[] // Suite NONE should only be used for testing and will probably be never selected by iOS!
}
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isLegacySRTPOptions(options: any): options is CameraStreamingOptionsLegacySRTP {
- return "srtp" in options;
+ return 'srtp' in options
}
/**
* @group Camera
*/
-export type VideoStreamingOptions = {
- codec: H264CodecParameters,
- resolutions: Resolution[],
- cvoId?: number,
+export interface VideoStreamingOptions {
+ codec: H264CodecParameters
+ resolutions: Resolution[]
+ cvoId?: number
}
/**
* @group Camera
*/
export interface H264CodecParameters {
- levels: H264Level[],
- profiles: H264Profile[],
+ levels: H264Level[]
+ profiles: H264Profile[]
}
/**
* @group Camera
*/
-export type Resolution = [number, number, number]; // width, height, framerate
+export type Resolution = [number, number, number] // width, height, framerate
/**
* @group Camera
*/
-export type AudioStreamingOptions = {
- codecs: AudioStreamingCodec[],
- twoWayAudio?: boolean, // default false, indicates support of 2way audio (will add the Speaker service and Speaker volume control)
- comfort_noise?: boolean, // default false
+export interface AudioStreamingOptions {
+ codecs: AudioStreamingCodec[]
+ twoWayAudio?: boolean // default false, indicates support of 2way audio (will add the Speaker service and Speaker volume control)
+ comfort_noise?: boolean // default false
}
/**
* @group Camera
*/
-export type AudioStreamingCodec = {
- type: AudioStreamingCodecType | string, // string type for backwards compatibility
- audioChannels?: number, // default 1
- bitrate?: AudioBitrate, // default VARIABLE, AAC-ELD or OPUS MUST support VARIABLE bitrate
- samplerate: AudioStreamingSamplerate[] | AudioStreamingSamplerate, // OPUS or AAC-ELD must support samplerate at 16k and 25k
+export interface AudioStreamingCodec {
+ type: AudioStreamingCodecType | string // string type for backwards compatibility
+ audioChannels?: number // default 1
+ bitrate?: AudioBitrate // default VARIABLE, AAC-ELD or OPUS MUST support VARIABLE bitrate
+ samplerate: AudioStreamingSamplerate[] | AudioStreamingSamplerate // OPUS or AAC-ELD must support samplerate at 16k and 25k
}
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum AudioStreamingCodecType { // codecs as defined by the HAP spec; only AAC-ELD and OPUS seem to work
- PCMU = "PCMU",
- PCMA = "PCMA",
- AAC_ELD = "AAC-eld",
- OPUS = "OPUS",
- MSBC = "mSBC",
- AMR = "AMR",
- AMR_WB = "AMR-WB",
+ PCMU = 'PCMU',
+ PCMA = 'PCMA',
+ AAC_ELD = 'AAC-eld',
+ OPUS = 'OPUS',
+ MSBC = 'mSBC',
+ AMR = 'AMR',
+ AMR_WB = 'AMR-WB',
}
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum AudioStreamingSamplerate {
KHZ_8 = 8,
KHZ_16 = 16,
KHZ_24 = 24,
}
-
/**
* @group Camera
*/
-export type StreamSessionIdentifier = string; // uuid provided by HAP to identify a streaming session
+export type StreamSessionIdentifier = string // uuid provided by HAP to identify a streaming session
/**
* @group Camera
*/
-export type SnapshotRequest = {
- height: number;
- width: number;
+export interface SnapshotRequest {
+ height: number
+ width: number
/**
* An optional {@link ResourceRequestReason}. The client decides if it wants to send this value. It is typically
* only sent in the context of HomeKit Secure Video Cameras.
@@ -383,213 +417,214 @@ export type SnapshotRequest = {
/**
* @group Camera
*/
-export type PrepareStreamRequest = {
- sessionID: StreamSessionIdentifier,
- sourceAddress: string,
- targetAddress: string,
- addressVersion: "ipv4" | "ipv6",
- audio: Source,
- video: Source,
+export interface PrepareStreamRequest {
+ sessionID: StreamSessionIdentifier
+ sourceAddress: string
+ targetAddress: string
+ addressVersion: 'ipv4' | 'ipv6'
+ audio: Source
+ video: Source
}
/**
* @group Camera
*/
-export type Source = {
- port: number,
+export interface Source {
+ port: number
- srtpCryptoSuite: SRTPCryptoSuites, // if cryptoSuite is NONE, key and salt are both zero-length
- srtp_key: Buffer,
- srtp_salt: Buffer,
+ srtpCryptoSuite: SRTPCryptoSuites // if cryptoSuite is NONE, key and salt are both zero-length
+ srtp_key: Buffer
+ srtp_salt: Buffer
- proxy_rtp?: number,
- proxy_rtcp?: number,
-};
+ proxy_rtp?: number
+ proxy_rtcp?: number
+}
/**
* @group Camera
*/
-export type PrepareStreamResponse = {
+export interface PrepareStreamResponse {
/**
* Any value set to this optional property will overwrite the automatically determined local address,
* which is sent as RTP endpoint to the iOS device.
*/
- addressOverride?: string;
+ addressOverride?: string
// video should be instanceOf ProxiedSourceResponse if proxy is required
- video: SourceResponse | ProxiedSourceResponse;
+ video: SourceResponse | ProxiedSourceResponse
// needs to be only supplied if audio is required; audio should be instanceOf ProxiedSourceResponse if proxy is required and audio proxy is not disabled
- audio?: SourceResponse | ProxiedSourceResponse;
+ audio?: SourceResponse | ProxiedSourceResponse
}
/**
* @group Camera
*/
export interface SourceResponse {
- port: number, // RTP/RTCP port of streaming server
- ssrc: number, // synchronization source of the stream
+ port: number // RTP/RTCP port of streaming server
+ ssrc: number // synchronization source of the stream
- srtp_key?: Buffer, // SRTP Key. Required if SRTP is used for the current stream
- srtp_salt?: Buffer, // SRTP Salt. Required if SRTP is used for the current stream
+ srtp_key?: Buffer // SRTP Key. Required if SRTP is used for the current stream
+ srtp_salt?: Buffer // SRTP Salt. Required if SRTP is used for the current stream
}
/**
* @group Camera
*/
export interface ProxiedSourceResponse {
- proxy_pt: number, // Payload Type of input stream
- proxy_server_address: string, // IP address of RTP server
- proxy_server_rtp: number, // RTP port
- proxy_server_rtcp: number, // RTCP port
+ proxy_pt: number // Payload Type of input stream
+ proxy_server_address: string // IP address of RTP server
+ proxy_server_rtp: number // RTP port
+ proxy_server_rtcp: number // RTCP port
}
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum StreamRequestTypes {
- RECONFIGURE = "reconfigure",
- START = "start",
- STOP = "stop",
+ RECONFIGURE = 'reconfigure',
+ START = 'start',
+ STOP = 'stop',
}
/**
* @group Camera
*/
-export type StreamingRequest = StartStreamRequest | ReconfigureStreamRequest | StopStreamRequest;
+export type StreamingRequest = StartStreamRequest | ReconfigureStreamRequest | StopStreamRequest
/**
* @group Camera
*/
-export type StartStreamRequest = {
- sessionID: StreamSessionIdentifier,
- type: StreamRequestTypes.START,
- video: VideoInfo,
- audio: AudioInfo,
+export interface StartStreamRequest {
+ sessionID: StreamSessionIdentifier
+ type: StreamRequestTypes.START
+ video: VideoInfo
+ audio: AudioInfo
}
/**
* @group Camera
*/
-export type ReconfigureStreamRequest = {
- sessionID: StreamSessionIdentifier,
- type: StreamRequestTypes.RECONFIGURE,
- video: ReconfiguredVideoInfo,
+export interface ReconfigureStreamRequest {
+ sessionID: StreamSessionIdentifier
+ type: StreamRequestTypes.RECONFIGURE
+ video: ReconfiguredVideoInfo
}
/**
* @group Camera
*/
-export type StopStreamRequest = {
- sessionID: StreamSessionIdentifier,
- type: StreamRequestTypes.STOP,
+export interface StopStreamRequest {
+ sessionID: StreamSessionIdentifier
+ type: StreamRequestTypes.STOP
}
/**
* @group Camera
*/
-export type AudioInfo = {
- codec: AudioStreamingCodecType, // block size for AAC-ELD must be 480 samples
+export interface AudioInfo {
+ codec: AudioStreamingCodecType // block size for AAC-ELD must be 480 samples
- channel: number,
- bit_rate: number,
- sample_rate: AudioStreamingSamplerate, // 8, 16, 24
- packet_time: number, // rtp packet time: length of time in ms represented by the media in a packet (20ms, 30ms, 40ms, 60ms)
+ channel: number
+ bit_rate: number
+ sample_rate: AudioStreamingSamplerate // 8, 16, 24
+ packet_time: number // rtp packet time: length of time in ms represented by the media in a packet (20ms, 30ms, 40ms, 60ms)
- pt: number, // payloadType, typically 110
- ssrc: number, // synchronisation source
- max_bit_rate: number,
- rtcp_interval: number, // minimum rtcp interval in seconds (floating point number), pretty much always 0.5
- comfort_pt: number, // comfortNoise payloadType, 13
+ pt: number // payloadType, typically 110
+ ssrc: number // synchronisation source
+ max_bit_rate: number
+ rtcp_interval: number // minimum rtcp interval in seconds (floating point number), pretty much always 0.5
+ comfort_pt: number // comfortNoise payloadType, 13
- comfortNoiseEnabled: boolean,
-};
+ comfortNoiseEnabled: boolean
+}
/**
* @group Camera
*/
-export type VideoInfo = { // minimum keyframe interval is about 5 seconds
- codec: VideoCodecType;
- profile: H264Profile,
- level: H264Level,
- packetizationMode: VideoCodecPacketizationMode,
- cvoId?: number, // Coordination of Video Orientation, only supplied if enabled AND supported; ranges from 1 to 14
-
- width: number,
- height: number,
- fps: number,
-
- pt: number, // payloadType, 99 for h264
- ssrc: number, // synchronisation source
- max_bit_rate: number,
- rtcp_interval: number, // minimum rtcp interval in seconds (floating point number), pretty much always 0.5 (standard says a rang from 0.5 to 1.5)
- mtu: number, // maximum transmissions unit, default values: ipv4: 1378 bytes; ipv6: 1228 bytes
-};
+export interface VideoInfo { // minimum keyframe interval is about 5 seconds
+ codec: VideoCodecType
+ profile: H264Profile
+ level: H264Level
+ packetizationMode: VideoCodecPacketizationMode
+ cvoId?: number // Coordination of Video Orientation, only supplied if enabled AND supported; ranges from 1 to 14
+
+ width: number
+ height: number
+ fps: number
+
+ pt: number // payloadType, 99 for h264
+ ssrc: number // synchronisation source
+ max_bit_rate: number
+ rtcp_interval: number // minimum rtcp interval in seconds (floating point number), pretty much always 0.5 (standard says a rang from 0.5 to 1.5)
+ mtu: number // maximum transmissions unit, default values: ipv4: 1378 bytes; ipv6: 1228 bytes
+}
/**
* @group Camera
*/
-export type ReconfiguredVideoInfo = {
- width: number,
- height: number,
- fps: number,
+export interface ReconfiguredVideoInfo {
+ width: number
+ height: number
+ fps: number
- max_bit_rate: number,
- rtcp_interval: number, // minimum rtcp interval in seconds (floating point number)
+ max_bit_rate: number
+ rtcp_interval: number // minimum rtcp interval in seconds (floating point number)
}
/**
* @group Camera
*/
export interface RTPStreamManagementState {
- id: number;
- active: boolean;
+ id: number
+ active: boolean
}
/**
* @group Camera
*/
export class RTPStreamManagement {
- private readonly id: number;
- private readonly delegate: CameraStreamingDelegate;
- readonly service: CameraRTPStreamManagement;
+ private readonly id: number
+ private readonly delegate: CameraStreamingDelegate
+ readonly service: CameraRTPStreamManagement
- private stateChangeDelegate?: StateChangeDelegate;
+ private stateChangeDelegate?: StateChangeDelegate
- requireProxy: boolean;
- disableAudioProxy: boolean;
- supportedCryptoSuites: SRTPCryptoSuites[];
- videoOnly = false;
+ requireProxy: boolean
+ disableAudioProxy: boolean
+ supportedCryptoSuites: SRTPCryptoSuites[]
+ videoOnly = false
- readonly supportedRTPConfiguration: string;
- readonly supportedVideoStreamConfiguration: string;
- readonly supportedAudioStreamConfiguration: string;
+ readonly supportedRTPConfiguration: string
+ readonly supportedVideoStreamConfiguration: string
+ readonly supportedAudioStreamConfiguration: string
- private activeConnection?: HAPConnection;
- private readonly activeConnectionClosedListener: (callback?: CharacteristicSetCallback) => void;
- sessionIdentifier?: StreamSessionIdentifier = undefined;
+ private activeConnection?: HAPConnection
+ private readonly activeConnectionClosedListener: (callback?: CharacteristicSetCallback) => void
+ sessionIdentifier?: StreamSessionIdentifier = undefined
/**
- * @private private API
+ * @private
*/
- streamStatus: StreamingStatus = StreamingStatus.AVAILABLE; // use _updateStreamStatus to update this property
- private ipVersion?: "ipv4" | "ipv6"; // ip version for the current session
+ streamStatus: StreamingStatus = StreamingStatus.AVAILABLE // use _updateStreamStatus to update this property
+ private ipVersion?: 'ipv4' | 'ipv6' // ip version for the current session
- selectedConfiguration = ""; // base64 representation of the currently selected configuration
- setupEndpointsResponse = ""; // response of the SetupEndpoints Characteristic
+ selectedConfiguration = '' // base64 representation of the currently selected configuration
+ setupEndpointsResponse = '' // response of the SetupEndpoints Characteristic
/**
- * @private deprecated API
+ * @private
*/
- audioProxy?: RTPProxy;
+ audioProxy?: RTPProxy
/**
- * @private deprecated API
+ * @private
*/
- videoProxy?: RTPProxy;
+ videoProxy?: RTPProxy
/**
* A RTPStreamManagement is considered disabled if `HomeKitCameraActive` is set to false.
* We use a closure based approach to retrieve the value of this characteristic.
* The characteristic is managed by the RecordingManagement.
*/
- private readonly disabledThroughOperatingMode?: () => boolean;
+ private readonly disabledThroughOperatingMode?: () => boolean
constructor(
id: number,
@@ -598,218 +633,220 @@ export class RTPStreamManagement {
service?: CameraRTPStreamManagement,
disabledThroughOperatingMode?: () => boolean,
) {
- this.id = id;
- this.delegate = delegate;
+ this.id = id
+ this.delegate = delegate
- this.requireProxy = options.proxy || false;
- this.disableAudioProxy = options.disable_audio_proxy || false;
+ this.requireProxy = options.proxy || false
+ this.disableAudioProxy = options.disable_audio_proxy || false
if (isLegacySRTPOptions(options)) {
- this.supportedCryptoSuites = [options.srtp? SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80: SRTPCryptoSuites.NONE];
+ this.supportedCryptoSuites = [options.srtp ? SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80 : SRTPCryptoSuites.NONE]
} else {
- this.supportedCryptoSuites = options.supportedCryptoSuites;
+ this.supportedCryptoSuites = options.supportedCryptoSuites
}
if (this.supportedCryptoSuites.length === 0) {
- this.supportedCryptoSuites.push(SRTPCryptoSuites.NONE);
+ this.supportedCryptoSuites.push(SRTPCryptoSuites.NONE)
}
if (!options.video) {
- throw new Error("Video parameters cannot be undefined in options");
+ throw new Error('Video parameters cannot be undefined in options')
}
- this.supportedRTPConfiguration = RTPStreamManagement._supportedRTPConfiguration(this.supportedCryptoSuites);
- this.supportedVideoStreamConfiguration = RTPStreamManagement._supportedVideoStreamConfiguration(options.video);
- this.supportedAudioStreamConfiguration = this._supportedAudioStreamConfiguration(options.audio);
+ this.supportedRTPConfiguration = RTPStreamManagement._supportedRTPConfiguration(this.supportedCryptoSuites)
+ this.supportedVideoStreamConfiguration = RTPStreamManagement._supportedVideoStreamConfiguration(options.video)
+ this.supportedAudioStreamConfiguration = this._supportedAudioStreamConfiguration(options.audio)
- this.activeConnectionClosedListener = this._handleStopStream.bind(this);
+ this.activeConnectionClosedListener = this._handleStopStream.bind(this)
- this.service = service || this.constructService(id);
- this.setupServiceHandlers();
+ this.service = service || this.constructService(id)
+ this.setupServiceHandlers()
- this.resetSetupEndpointsResponse();
- this.resetSelectedStreamConfiguration();
+ this.resetSetupEndpointsResponse()
+ this.resetSelectedStreamConfiguration()
- this.disabledThroughOperatingMode = disabledThroughOperatingMode;
+ this.disabledThroughOperatingMode = disabledThroughOperatingMode
}
public forceStop(): void {
- this.handleSessionClosed();
+ this.handleSessionClosed()
}
getService(): CameraRTPStreamManagement {
- return this.service;
+ return this.service
}
handleFactoryReset(): void {
- this.resetSelectedStreamConfiguration();
- this.resetSetupEndpointsResponse();
+ this.resetSelectedStreamConfiguration()
+ this.resetSetupEndpointsResponse()
- this.service.updateCharacteristic(Characteristic.Active, true);
+ this.service.updateCharacteristic(Characteristic.Active, true)
// on a factory reset the assumption is that all connections were already terminated and thus "handleStopStream" was already called
}
public destroy(): void {
if (this.activeConnection) {
- this._handleStopStream();
+ this._handleStopStream()
}
}
private constructService(id: number): CameraRTPStreamManagement {
- const managementService = new Service.CameraRTPStreamManagement("", id.toString());
+ const managementService = new Service.CameraRTPStreamManagement('', id.toString())
// this service is required only when recording is enabled. We don't really have access to this info here,
// so we just add the characteristic. Doesn't really hurt.
- managementService.setCharacteristic(Characteristic.Active, true);
+ managementService.setCharacteristic(Characteristic.Active, true)
- return managementService;
+ return managementService
}
private setupServiceHandlers() {
if (!this.service.testCharacteristic(Characteristic.Active)) {
// the active characteristic might not be present on some older configurations.
- this.service.setCharacteristic(Characteristic.Active, true);
+ this.service.setCharacteristic(Characteristic.Active, true)
}
this.service.getCharacteristic(Characteristic.Active)
.on(CharacteristicEventTypes.CHANGE, () => this.stateChangeDelegate?.())
- .setProps({ adminOnlyAccess: [Access.WRITE] });
+ .setProps({ adminOnlyAccess: [Access.WRITE] })
// ensure that configurations are up-to-date and reflected in the characteristic values
- this.service.setCharacteristic(Characteristic.SupportedRTPConfiguration, this.supportedRTPConfiguration);
- this.service.setCharacteristic(Characteristic.SupportedVideoStreamConfiguration, this.supportedVideoStreamConfiguration);
- this.service.setCharacteristic(Characteristic.SupportedAudioStreamConfiguration, this.supportedAudioStreamConfiguration);
+ this.service.setCharacteristic(Characteristic.SupportedRTPConfiguration, this.supportedRTPConfiguration)
+ this.service.setCharacteristic(Characteristic.SupportedVideoStreamConfiguration, this.supportedVideoStreamConfiguration)
+ this.service.setCharacteristic(Characteristic.SupportedAudioStreamConfiguration, this.supportedAudioStreamConfiguration)
- this._updateStreamStatus(StreamingStatus.AVAILABLE); // reset streaming status to available
- this.service.setCharacteristic(Characteristic.SetupEndpoints, this.setupEndpointsResponse); // reset SetupEndpoints to default
+ this._updateStreamStatus(StreamingStatus.AVAILABLE) // reset streaming status to available
+ this.service.setCharacteristic(Characteristic.SetupEndpoints, this.setupEndpointsResponse) // reset SetupEndpoints to default
this.service.getCharacteristic(Characteristic.SelectedRTPStreamConfiguration)!
- .on(CharacteristicEventTypes.GET, callback => {
+ .on(CharacteristicEventTypes.GET, (callback) => {
if (this.streamingIsDisabled()) {
- callback(null, tlv.encode(
- SelectedRTPStreamConfigurationTypes.SESSION_CONTROL, tlv.encode(
- SessionControlTypes.COMMAND, SessionControlCommand.SUSPEND_SESSION,
+ callback(null, encode(
+ SelectedRTPStreamConfigurationTypes.SESSION_CONTROL,
+ encode(
+ SessionControlTypes.COMMAND,
+ SessionControlCommand.SUSPEND_SESSION,
),
- ).toString("base64"));
- return;
+ ).toString('base64'))
+ return
}
- callback(null, this.selectedConfiguration);
+ callback(null, this.selectedConfiguration)
})
- .on(CharacteristicEventTypes.SET, this._handleSelectedStreamConfigurationWrite.bind(this));
+ .on(CharacteristicEventTypes.SET, this._handleSelectedStreamConfigurationWrite.bind(this))
this.service.getCharacteristic(Characteristic.SetupEndpoints)!
- .on(CharacteristicEventTypes.GET, callback => {
+ .on(CharacteristicEventTypes.GET, (callback) => {
if (this.streamingIsDisabled()) {
- callback(null, tlv.encode(
- SetupEndpointsResponseTypes.STATUS, SetupEndpointsStatus.ERROR,
- ).toString("base64"));
- return;
+ callback(null, encode(
+ SetupEndpointsResponseTypes.STATUS,
+ SetupEndpointsStatus.ERROR,
+ ).toString('base64'))
+ return
}
- callback(null, this.setupEndpointsResponse);
+ callback(null, this.setupEndpointsResponse)
})
.on(CharacteristicEventTypes.SET, (value, callback, context, connection) => {
if (!connection) {
- debug("Set event handler for SetupEndpoints cannot be called from plugin. Connection undefined!");
- callback(HAPStatus.INVALID_VALUE_IN_REQUEST);
- return;
+ debug('Set event handler for SetupEndpoints cannot be called from plugin. Connection undefined!')
+ callback(HAPStatus.INVALID_VALUE_IN_REQUEST)
+ return
}
- this.handleSetupEndpoints(value, callback, connection);
- });
+ this.handleSetupEndpoints(value, callback, connection)
+ })
}
private handleSessionClosed(): void { // called when the streaming was ended or aborted and needs to be cleaned up
- this.resetSelectedStreamConfiguration();
- this.resetSetupEndpointsResponse();
+ this.resetSelectedStreamConfiguration()
+ this.resetSetupEndpointsResponse()
if (this.activeConnection) {
- this.activeConnection.removeListener(HAPConnectionEvent.CLOSED, this.activeConnectionClosedListener);
- this.activeConnection.setMaxListeners(this.activeConnection.getMaxListeners() - 1);
- this.activeConnection = undefined;
+ this.activeConnection.removeListener(HAPConnectionEvent.CLOSED, this.activeConnectionClosedListener)
+ this.activeConnection.setMaxListeners(this.activeConnection.getMaxListeners() - 1)
+ this.activeConnection = undefined
}
- this._updateStreamStatus(StreamingStatus.AVAILABLE);
- this.sessionIdentifier = undefined;
- this.ipVersion = undefined;
+ this._updateStreamStatus(StreamingStatus.AVAILABLE)
+ this.sessionIdentifier = undefined
+ this.ipVersion = undefined
if (this.videoProxy) {
- this.videoProxy.destroy();
- this.videoProxy = undefined;
+ this.videoProxy.destroy()
+ this.videoProxy = undefined
}
if (this.audioProxy) {
- this.audioProxy.destroy();
- this.audioProxy = undefined;
+ this.audioProxy.destroy()
+ this.audioProxy = undefined
}
}
private streamingIsDisabled(callback?: CharacteristicSetCallback): boolean {
if (!this.service.getCharacteristic(Characteristic.Active).value) {
- if (typeof callback === "function") {
- callback(new HapStatusError(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE));
+ if (typeof callback === 'function') {
+ callback(new HapStatusError(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE))
}
- return true;
+ return true
}
if (this.disabledThroughOperatingMode?.()) {
- if (typeof callback === "function") {
- callback(new HapStatusError(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE));
+ if (typeof callback === 'function') {
+ callback(new HapStatusError(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE))
}
- return true;
+ return true
}
- return false;
+ return false
}
private _handleSelectedStreamConfigurationWrite(value: CharacteristicValue, callback: CharacteristicSetCallback): void {
if (this.streamingIsDisabled(callback)) {
- return;
+ return
}
- const data = Buffer.from(value as string, "base64");
- const objects = tlv.decode(data);
+ const data = Buffer.from(value as string, 'base64')
+ const objects = decode(data)
- const sessionControl = tlv.decode(objects[SelectedRTPStreamConfigurationTypes.SESSION_CONTROL]);
- const sessionIdentifier = uuid.unparse(sessionControl[SessionControlTypes.SESSION_IDENTIFIER]);
- const requestType: SessionControlCommand = sessionControl[SessionControlTypes.COMMAND][0];
+ const sessionControl = decode(objects[SelectedRTPStreamConfigurationTypes.SESSION_CONTROL])
+ const sessionIdentifier = unparse(sessionControl[SessionControlTypes.SESSION_IDENTIFIER])
+ const requestType: SessionControlCommand = sessionControl[SessionControlTypes.COMMAND][0]
if (sessionIdentifier !== this.sessionIdentifier) {
- debug(`Received unknown session Identifier with request to ${SessionControlCommand[requestType]}`);
- callback(HAPStatus.INVALID_VALUE_IN_REQUEST);
- return;
+ debug(`Received unknown session Identifier with request to ${SessionControlCommand[requestType]}`)
+ callback(HAPStatus.INVALID_VALUE_IN_REQUEST)
+ return
}
- this.selectedConfiguration = value as string;
+ this.selectedConfiguration = value as string
// intercept the callback chain to check if an error occurred.
const streamCallback: CharacteristicSetCallback = (error, writeResponse) => {
- callback(error, writeResponse); // does not support writeResponse, but how knows what comes in the future.
+ callback(error, writeResponse) // does not support writeResponse, but how knows what comes in the future.
if (error) {
- this.handleSessionClosed();
+ this.handleSessionClosed()
}
- };
+ }
switch (requestType) {
- case SessionControlCommand.START_SESSION: {
- const selectedVideoParameters = tlv.decode(objects[SelectedRTPStreamConfigurationTypes.SELECTED_VIDEO_PARAMETERS]);
- const selectedAudioParameters = tlv.decode(objects[SelectedRTPStreamConfigurationTypes.SELECTED_AUDIO_PARAMETERS]);
+ case SessionControlCommand.START_SESSION: {
+ const selectedVideoParameters = decode(objects[SelectedRTPStreamConfigurationTypes.SELECTED_VIDEO_PARAMETERS])
+ const selectedAudioParameters = decode(objects[SelectedRTPStreamConfigurationTypes.SELECTED_AUDIO_PARAMETERS])
- this._handleStartStream(selectedVideoParameters, selectedAudioParameters, streamCallback);
- break;
- }
- case SessionControlCommand.RECONFIGURE_SESSION: {
- const reconfiguredVideoParameters = tlv.decode(objects[SelectedRTPStreamConfigurationTypes.SELECTED_VIDEO_PARAMETERS]);
+ this._handleStartStream(selectedVideoParameters, selectedAudioParameters, streamCallback)
+ break
+ }
+ case SessionControlCommand.RECONFIGURE_SESSION: {
+ const reconfiguredVideoParameters = decode(objects[SelectedRTPStreamConfigurationTypes.SELECTED_VIDEO_PARAMETERS])
- this.handleReconfigureStream(reconfiguredVideoParameters, streamCallback);
- break;
- }
- case SessionControlCommand.END_SESSION:
- this._handleStopStream(streamCallback);
- break;
- case SessionControlCommand.RESUME_SESSION:
- case SessionControlCommand.SUSPEND_SESSION:
- default:
- debug(`Unhandled request type ${SessionControlCommand[requestType]}`);
- callback(HAPStatus.INVALID_VALUE_IN_REQUEST);
- return;
+ this.handleReconfigureStream(reconfiguredVideoParameters, streamCallback)
+ break
+ }
+ case SessionControlCommand.END_SESSION:
+ this._handleStopStream(streamCallback)
+ break
+ case SessionControlCommand.RESUME_SESSION:
+ case SessionControlCommand.SUSPEND_SESSION:
+ default:
+ debug(`Unhandled request type ${SessionControlCommand[requestType]}`)
+ callback(HAPStatus.INVALID_VALUE_IN_REQUEST)
}
}
@@ -819,79 +856,76 @@ export class RTPStreamManagement {
callback: CharacteristicSetCallback,
): void {
// selected video configuration
- // noinspection JSUnusedLocalSymbols
- const videoCodec = videoConfiguration[SelectedVideoParametersTypes.CODEC_TYPE]; // always 0x00 for h264
- const videoParametersTLV = videoConfiguration[SelectedVideoParametersTypes.CODEC_PARAMETERS];
- const videoAttributesTLV = videoConfiguration[SelectedVideoParametersTypes.ATTRIBUTES];
- const videoRTPParametersTLV = videoConfiguration[SelectedVideoParametersTypes.RTP_PARAMETERS];
+ const videoCodec = videoConfiguration[SelectedVideoParametersTypes.CODEC_TYPE] // always 0x00 for h264
+ const videoParametersTLV = videoConfiguration[SelectedVideoParametersTypes.CODEC_PARAMETERS]
+ const videoAttributesTLV = videoConfiguration[SelectedVideoParametersTypes.ATTRIBUTES]
+ const videoRTPParametersTLV = videoConfiguration[SelectedVideoParametersTypes.RTP_PARAMETERS]
// video parameters
- const videoParameters = tlv.decode(videoParametersTLV);
- const h264Profile: H264Profile = videoParameters[VideoCodecParametersTypes.PROFILE_ID][0];
- const h264Level: H264Level = videoParameters[VideoCodecParametersTypes.LEVEL][0];
- const packetizationMode: VideoCodecPacketizationMode = videoParameters[VideoCodecParametersTypes.PACKETIZATION_MODE][0];
- const cvoEnabled = videoParameters[VideoCodecParametersTypes.CVO_ENABLED];
- let cvoId: number | undefined = undefined;
+ const videoParameters = decode(videoParametersTLV)
+ const h264Profile: H264Profile = videoParameters[VideoCodecParametersTypes.PROFILE_ID][0]
+ const h264Level: H264Level = videoParameters[VideoCodecParametersTypes.LEVEL][0]
+ const packetizationMode: VideoCodecPacketizationMode = videoParameters[VideoCodecParametersTypes.PACKETIZATION_MODE][0]
+ const cvoEnabled = videoParameters[VideoCodecParametersTypes.CVO_ENABLED]
+ let cvoId: number | undefined
if (cvoEnabled && cvoEnabled[0] === VideoCodecCVO.SUPPORTED) {
- cvoId = videoParameters[VideoCodecParametersTypes.CVO_ID].readUInt8(0);
+ cvoId = videoParameters[VideoCodecParametersTypes.CVO_ID].readUInt8(0)
}
// video attributes
- const videoAttributes = tlv.decode(videoAttributesTLV);
- const width = videoAttributes[VideoAttributesTypes.IMAGE_WIDTH].readUInt16LE(0);
- const height = videoAttributes[VideoAttributesTypes.IMAGE_HEIGHT].readUInt16LE(0);
- const frameRate = videoAttributes[VideoAttributesTypes.FRAME_RATE].readUInt8(0);
+ const videoAttributes = decode(videoAttributesTLV)
+ const width = videoAttributes[VideoAttributesTypes.IMAGE_WIDTH].readUInt16LE(0)
+ const height = videoAttributes[VideoAttributesTypes.IMAGE_HEIGHT].readUInt16LE(0)
+ const frameRate = videoAttributes[VideoAttributesTypes.FRAME_RATE].readUInt8(0)
// video rtp parameters
- const videoRTPParameters = tlv.decode(videoRTPParametersTLV);
- const videoPayloadType = videoRTPParameters[VideoRTPParametersTypes.PAYLOAD_TYPE].readUInt8(0); // 99
- const videoSSRC = videoRTPParameters[VideoRTPParametersTypes.SYNCHRONIZATION_SOURCE].readUInt32LE(0);
- const videoMaximumBitrate = videoRTPParameters[VideoRTPParametersTypes.MAX_BIT_RATE].readUInt16LE(0);
- const videoRTCPInterval = videoRTPParameters[VideoRTPParametersTypes.MIN_RTCP_INTERVAL].readFloatLE(0);
- let maxMTU = this.ipVersion === "ipv6"? 1228: 1378; // default values ipv4: 1378 bytes; ipv6: 1228 bytes
+ const videoRTPParameters = decode(videoRTPParametersTLV)
+ const videoPayloadType = videoRTPParameters[VideoRTPParametersTypes.PAYLOAD_TYPE].readUInt8(0) // 99
+ const videoSSRC = videoRTPParameters[VideoRTPParametersTypes.SYNCHRONIZATION_SOURCE].readUInt32LE(0)
+ const videoMaximumBitrate = videoRTPParameters[VideoRTPParametersTypes.MAX_BIT_RATE].readUInt16LE(0)
+ const videoRTCPInterval = videoRTPParameters[VideoRTPParametersTypes.MIN_RTCP_INTERVAL].readFloatLE(0)
+ let maxMTU = this.ipVersion === 'ipv6' ? 1228 : 1378 // default values ipv4: 1378 bytes; ipv6: 1228 bytes
if (videoRTPParameters[VideoRTPParametersTypes.MAX_MTU]) {
- maxMTU = videoRTPParameters[VideoRTPParametersTypes.MAX_MTU].readUInt16LE(0);
+ maxMTU = videoRTPParameters[VideoRTPParametersTypes.MAX_MTU].readUInt16LE(0)
}
-
// selected audio configuration
- const audioCodec: AudioCodecTypes = audioConfiguration[SelectedAudioParametersTypes.CODEC_TYPE][0];
- const audioParametersTLV = audioConfiguration[SelectedAudioParametersTypes.CODEC_PARAMETERS];
- const audioRTPParametersTLV = audioConfiguration[SelectedAudioParametersTypes.RTP_PARAMETERS];
- const comfortNoise = !!audioConfiguration[SelectedAudioParametersTypes.COMFORT_NOISE].readUInt8(0);
+ const audioCodec: AudioCodecTypes = audioConfiguration[SelectedAudioParametersTypes.CODEC_TYPE][0]
+ const audioParametersTLV = audioConfiguration[SelectedAudioParametersTypes.CODEC_PARAMETERS]
+ const audioRTPParametersTLV = audioConfiguration[SelectedAudioParametersTypes.RTP_PARAMETERS]
+ const comfortNoise = !!audioConfiguration[SelectedAudioParametersTypes.COMFORT_NOISE].readUInt8(0)
// audio parameters
- const audioParameters = tlv.decode(audioParametersTLV);
- const channels = audioParameters[AudioCodecParametersTypes.CHANNEL][0];
- const audioBitrate: AudioBitrate = audioParameters[AudioCodecParametersTypes.BIT_RATE][0];
- const samplerate: AudioSamplerate = audioParameters[AudioCodecParametersTypes.SAMPLE_RATE][0];
- const rtpPacketTime = audioParameters[AudioCodecParametersTypes.PACKET_TIME].readUInt8(0);
+ const audioParameters = decode(audioParametersTLV)
+ const channels = audioParameters[AudioCodecParametersTypes.CHANNEL][0]
+ const audioBitrate: AudioBitrate = audioParameters[AudioCodecParametersTypes.BIT_RATE][0]
+ const samplerate: AudioSamplerate = audioParameters[AudioCodecParametersTypes.SAMPLE_RATE][0]
+ const rtpPacketTime = audioParameters[AudioCodecParametersTypes.PACKET_TIME].readUInt8(0)
// audio rtp parameters
- const audioRTPParameters = tlv.decode(audioRTPParametersTLV);
- const audioPayloadType = audioRTPParameters[AudioRTPParametersTypes.PAYLOAD_TYPE].readUInt8(0); // 110
- const audioSSRC = audioRTPParameters[AudioRTPParametersTypes.SYNCHRONIZATION_SOURCE].readUInt32LE(0);
- const audioMaximumBitrate = audioRTPParameters[AudioRTPParametersTypes.MAX_BIT_RATE].readUInt16LE(0);
- const audioRTCPInterval = audioRTPParameters[AudioRTPParametersTypes.MIN_RTCP_INTERVAL].readFloatLE(0);
- const comfortNoisePayloadType = audioRTPParameters[AudioRTPParametersTypes.COMFORT_NOISE_PAYLOAD_TYPE].readUInt8(0); // 13
+ const audioRTPParameters = decode(audioRTPParametersTLV)
+ const audioPayloadType = audioRTPParameters[AudioRTPParametersTypes.PAYLOAD_TYPE].readUInt8(0) // 110
+ const audioSSRC = audioRTPParameters[AudioRTPParametersTypes.SYNCHRONIZATION_SOURCE].readUInt32LE(0)
+ const audioMaximumBitrate = audioRTPParameters[AudioRTPParametersTypes.MAX_BIT_RATE].readUInt16LE(0)
+ const audioRTCPInterval = audioRTPParameters[AudioRTPParametersTypes.MIN_RTCP_INTERVAL].readFloatLE(0)
+ const comfortNoisePayloadType = audioRTPParameters[AudioRTPParametersTypes.COMFORT_NOISE_PAYLOAD_TYPE].readUInt8(0) // 13
if (this.requireProxy) {
- this.videoProxy!.setOutgoingPayloadType(videoPayloadType);
+ this.videoProxy!.setOutgoingPayloadType(videoPayloadType)
if (!this.disableAudioProxy) {
- this.audioProxy!.setOutgoingPayloadType(audioPayloadType);
+ this.audioProxy!.setOutgoingPayloadType(audioPayloadType)
}
}
-
const videoInfo: VideoInfo = {
codec: videoCodec.readUInt8(0),
profile: h264Profile,
level: h264Level,
- packetizationMode: packetizationMode,
- cvoId: cvoId,
+ packetizationMode,
+ cvoId,
- width: width,
- height: height,
+ width,
+ height,
fps: frameRate,
pt: videoPayloadType,
@@ -899,49 +933,49 @@ export class RTPStreamManagement {
max_bit_rate: videoMaximumBitrate,
rtcp_interval: videoRTCPInterval,
mtu: maxMTU,
- };
+ }
- let audioCodecName: AudioStreamingCodecType;
- let samplerateNum: AudioStreamingSamplerate;
+ let audioCodecName: AudioStreamingCodecType
+ let samplerateNum: AudioStreamingSamplerate
switch (audioCodec) {
- case AudioCodecTypes.PCMU:
- audioCodecName = AudioStreamingCodecType.PCMU;
- break;
- case AudioCodecTypes.PCMA:
- audioCodecName = AudioStreamingCodecType.PCMA;
- break;
- case AudioCodecTypes.AAC_ELD:
- audioCodecName = AudioStreamingCodecType.AAC_ELD;
- break;
- case AudioCodecTypes.OPUS:
- audioCodecName = AudioStreamingCodecType.OPUS;
- break;
- case AudioCodecTypes.MSBC:
- audioCodecName = AudioStreamingCodecType.MSBC;
- break;
- case AudioCodecTypes.AMR:
- audioCodecName = AudioStreamingCodecType.AMR;
- break;
- case AudioCodecTypes.AMR_WB:
- audioCodecName = AudioStreamingCodecType.AMR_WB;
- break;
- default:
- throw new Error(`Encountered unknown selected audio codec ${audioCodec}`);
+ case AudioCodecTypes.PCMU:
+ audioCodecName = AudioStreamingCodecType.PCMU
+ break
+ case AudioCodecTypes.PCMA:
+ audioCodecName = AudioStreamingCodecType.PCMA
+ break
+ case AudioCodecTypes.AAC_ELD:
+ audioCodecName = AudioStreamingCodecType.AAC_ELD
+ break
+ case AudioCodecTypes.OPUS:
+ audioCodecName = AudioStreamingCodecType.OPUS
+ break
+ case AudioCodecTypes.MSBC:
+ audioCodecName = AudioStreamingCodecType.MSBC
+ break
+ case AudioCodecTypes.AMR:
+ audioCodecName = AudioStreamingCodecType.AMR
+ break
+ case AudioCodecTypes.AMR_WB:
+ audioCodecName = AudioStreamingCodecType.AMR_WB
+ break
+ default:
+ throw new Error(`Encountered unknown selected audio codec ${audioCodec}`)
}
switch (samplerate) {
- case AudioSamplerate.KHZ_8:
- samplerateNum = 8;
- break;
- case AudioSamplerate.KHZ_16:
- samplerateNum = 16;
- break;
- case AudioSamplerate.KHZ_24:
- samplerateNum = 24;
- break;
- default:
- throw new Error(`Encountered unknown selected audio samplerate ${samplerate}`);
+ case AudioSamplerate.KHZ_8:
+ samplerateNum = 8
+ break
+ case AudioSamplerate.KHZ_16:
+ samplerateNum = 16
+ break
+ case AudioSamplerate.KHZ_24:
+ samplerateNum = 24
+ break
+ default:
+ throw new Error(`Encountered unknown selected audio samplerate ${samplerate}`)
}
const audioInfo: AudioInfo = {
@@ -959,134 +993,144 @@ export class RTPStreamManagement {
comfort_pt: comfortNoisePayloadType,
comfortNoiseEnabled: comfortNoise,
- };
+ }
const request: StartStreamRequest = {
sessionID: this.sessionIdentifier!,
type: StreamRequestTypes.START,
video: videoInfo,
audio: audioInfo,
- };
+ }
- this.delegate.handleStreamRequest(request, error => callback(error));
+ this.delegate.handleStreamRequest(request, error => callback(error))
}
private handleReconfigureStream(videoConfiguration: Record, callback: CharacteristicSetCallback): void {
// selected video configuration
- const videoAttributesTLV = videoConfiguration[SelectedVideoParametersTypes.ATTRIBUTES];
- const videoRTPParametersTLV = videoConfiguration[SelectedVideoParametersTypes.RTP_PARAMETERS];
+ const videoAttributesTLV = videoConfiguration[SelectedVideoParametersTypes.ATTRIBUTES]
+ const videoRTPParametersTLV = videoConfiguration[SelectedVideoParametersTypes.RTP_PARAMETERS]
// video attributes
- const videoAttributes = tlv.decode(videoAttributesTLV);
- const width = videoAttributes[VideoAttributesTypes.IMAGE_WIDTH].readUInt16LE(0);
- const height = videoAttributes[VideoAttributesTypes.IMAGE_HEIGHT].readUInt16LE(0);
- const frameRate = videoAttributes[VideoAttributesTypes.FRAME_RATE].readUInt8(0);
+ const videoAttributes = decode(videoAttributesTLV)
+ const width = videoAttributes[VideoAttributesTypes.IMAGE_WIDTH].readUInt16LE(0)
+ const height = videoAttributes[VideoAttributesTypes.IMAGE_HEIGHT].readUInt16LE(0)
+ const frameRate = videoAttributes[VideoAttributesTypes.FRAME_RATE].readUInt8(0)
// video rtp parameters
- const videoRTPParameters = tlv.decode(videoRTPParametersTLV);
- const videoMaximumBitrate = videoRTPParameters[VideoRTPParametersTypes.MAX_BIT_RATE].readUInt16LE(0);
+ const videoRTPParameters = decode(videoRTPParametersTLV)
+ const videoMaximumBitrate = videoRTPParameters[VideoRTPParametersTypes.MAX_BIT_RATE].readUInt16LE(0)
// seems to be always zero, use default of 0.5
- const videoRTCPInterval = videoRTPParameters[VideoRTPParametersTypes.MIN_RTCP_INTERVAL].readFloatLE(0) || 0.5;
+ const videoRTCPInterval = videoRTPParameters[VideoRTPParametersTypes.MIN_RTCP_INTERVAL].readFloatLE(0) || 0.5
const reconfiguredVideoInfo: ReconfiguredVideoInfo = {
- width: width,
- height: height,
+ width,
+ height,
fps: frameRate,
max_bit_rate: videoMaximumBitrate,
rtcp_interval: videoRTCPInterval,
- };
+ }
const request: ReconfigureStreamRequest = {
sessionID: this.sessionIdentifier!,
type: StreamRequestTypes.RECONFIGURE,
video: reconfiguredVideoInfo,
- };
+ }
- this.delegate.handleStreamRequest(request, error => callback(error));
+ this.delegate.handleStreamRequest(request, error => callback(error))
}
private _handleStopStream(callback?: CharacteristicSetCallback): void {
const request: StopStreamRequest = {
sessionID: this.sessionIdentifier!, // save sessionIdentifier before handleSessionClosed is called
type: StreamRequestTypes.STOP,
- };
+ }
- this.handleSessionClosed();
+ this.handleSessionClosed()
- this.delegate.handleStreamRequest(request, error => callback? callback(error): undefined);
+ this.delegate.handleStreamRequest(request, error => callback ? callback(error) : undefined)
}
private handleSetupEndpoints(value: CharacteristicValue, callback: CharacteristicSetCallback, connection: HAPConnection): void {
if (this.streamingIsDisabled(callback)) {
- return;
+ return
}
- const data = Buffer.from(value as string, "base64");
- const objects = tlv.decode(data);
+ const data = Buffer.from(value as string, 'base64')
+ const objects = decode(data)
- const sessionIdentifier = uuid.unparse(objects[SetupEndpointsTypes.SESSION_ID]);
+ const sessionIdentifier = unparse(objects[SetupEndpointsTypes.SESSION_ID])
if (this.streamStatus !== StreamingStatus.AVAILABLE) {
- this.setupEndpointsResponse = tlv.encode(
- SetupEndpointsResponseTypes.SESSION_ID, uuid.write(sessionIdentifier),
- SetupEndpointsResponseTypes.STATUS, SetupEndpointsStatus.BUSY,
- ).toString("base64");
- callback();
- return;
+ this.setupEndpointsResponse = encode(
+ SetupEndpointsResponseTypes.SESSION_ID,
+ write(sessionIdentifier),
+ SetupEndpointsResponseTypes.STATUS,
+ SetupEndpointsStatus.BUSY,
+ ).toString('base64')
+ callback()
+ return
}
- assert(this.activeConnection == null,
- "Found non-nil `activeConnection` when trying to setup streaming endpoints, even though streamStatus is reported to be AVAILABLE!");
+ assert(this.activeConnection == null, 'Found non-nil `activeConnection` when trying to setup streaming endpoints, even though streamStatus is reported to be AVAILABLE!')
- this.activeConnection = connection;
- this.activeConnection.setMaxListeners(this.activeConnection.getMaxListeners() + 1);
- this.activeConnection.on(HAPConnectionEvent.CLOSED, this.activeConnectionClosedListener);
+ this.activeConnection = connection
+ this.activeConnection.setMaxListeners(this.activeConnection.getMaxListeners() + 1)
+ this.activeConnection.on(HAPConnectionEvent.CLOSED, this.activeConnectionClosedListener)
- this.sessionIdentifier = sessionIdentifier;
- this._updateStreamStatus(StreamingStatus.IN_USE);
+ this.sessionIdentifier = sessionIdentifier
+ this._updateStreamStatus(StreamingStatus.IN_USE)
// Address
- const targetAddressPayload = objects[SetupEndpointsTypes.CONTROLLER_ADDRESS];
- const processedAddressInfo = tlv.decode(targetAddressPayload);
- const addressVersion = processedAddressInfo[AddressTypes.ADDRESS_VERSION][0];
- const controllerAddress = processedAddressInfo[AddressTypes.ADDRESS].toString("utf8");
- const targetVideoPort = processedAddressInfo[AddressTypes.VIDEO_RTP_PORT].readUInt16LE(0);
- const targetAudioPort = processedAddressInfo[AddressTypes.AUDIO_RTP_PORT].readUInt16LE(0);
+ const targetAddressPayload = objects[SetupEndpointsTypes.CONTROLLER_ADDRESS]
+ const processedAddressInfo = decode(targetAddressPayload)
+ const addressVersion = processedAddressInfo[AddressTypes.ADDRESS_VERSION][0]
+ const controllerAddress = processedAddressInfo[AddressTypes.ADDRESS].toString('utf8')
+ const targetVideoPort = processedAddressInfo[AddressTypes.VIDEO_RTP_PORT].readUInt16LE(0)
+ const targetAudioPort = processedAddressInfo[AddressTypes.AUDIO_RTP_PORT].readUInt16LE(0)
// Video SRTP Params
- const videoSRTPPayload = objects[SetupEndpointsTypes.VIDEO_SRTP_PARAMETERS];
- const processedVideoInfo = tlv.decode(videoSRTPPayload);
- const videoCryptoSuite = processedVideoInfo[SRTPParametersTypes.SRTP_CRYPTO_SUITE][0];
- const videoMasterKey = processedVideoInfo[SRTPParametersTypes.MASTER_KEY];
- const videoMasterSalt = processedVideoInfo[SRTPParametersTypes.MASTER_SALT];
+ const videoSRTPPayload = objects[SetupEndpointsTypes.VIDEO_SRTP_PARAMETERS]
+ const processedVideoInfo = decode(videoSRTPPayload)
+ const videoCryptoSuite = processedVideoInfo[SRTPParametersTypes.SRTP_CRYPTO_SUITE][0]
+ const videoMasterKey = processedVideoInfo[SRTPParametersTypes.MASTER_KEY]
+ const videoMasterSalt = processedVideoInfo[SRTPParametersTypes.MASTER_SALT]
// Audio SRTP Params
- const audioSRTPPayload = objects[SetupEndpointsTypes.AUDIO_SRTP_PARAMETERS];
- const processedAudioInfo = tlv.decode(audioSRTPPayload);
- const audioCryptoSuite = processedAudioInfo[SRTPParametersTypes.SRTP_CRYPTO_SUITE][0];
- const audioMasterKey = processedAudioInfo[SRTPParametersTypes.MASTER_KEY];
- const audioMasterSalt = processedAudioInfo[SRTPParametersTypes.MASTER_SALT];
+ const audioSRTPPayload = objects[SetupEndpointsTypes.AUDIO_SRTP_PARAMETERS]
+ const processedAudioInfo = decode(audioSRTPPayload)
+ const audioCryptoSuite = processedAudioInfo[SRTPParametersTypes.SRTP_CRYPTO_SUITE][0]
+ const audioMasterKey = processedAudioInfo[SRTPParametersTypes.MASTER_KEY]
+ const audioMasterSalt = processedAudioInfo[SRTPParametersTypes.MASTER_SALT]
debug(
- "Session: ", sessionIdentifier,
- "\nControllerAddress: ", controllerAddress,
- "\nVideoPort: ", targetVideoPort,
- "\nAudioPort: ", targetAudioPort,
- "\nVideo Crypto: ", videoCryptoSuite,
- "\nVideo Master Key: ", videoMasterKey,
- "\nVideo Master Salt: ", videoMasterSalt,
- "\nAudio Crypto: ", audioCryptoSuite,
- "\nAudio Master Key: ", audioMasterKey,
- "\nAudio Master Salt: ", audioMasterSalt,
- );
-
+ 'Session: ',
+ sessionIdentifier,
+ '\nControllerAddress: ',
+ controllerAddress,
+ '\nVideoPort: ',
+ targetVideoPort,
+ '\nAudioPort: ',
+ targetAudioPort,
+ '\nVideo Crypto: ',
+ videoCryptoSuite,
+ '\nVideo Master Key: ',
+ videoMasterKey,
+ '\nVideo Master Salt: ',
+ videoMasterSalt,
+ '\nAudio Crypto: ',
+ audioCryptoSuite,
+ '\nAudio Master Key: ',
+ audioMasterKey,
+ '\nAudio Master Salt: ',
+ audioMasterSalt,
+ )
const prepareRequest: PrepareStreamRequest = {
sessionID: sessionIdentifier,
sourceAddress: connection.localAddress,
targetAddress: controllerAddress,
- addressVersion: addressVersion === IPAddressVersion.IPV6? "ipv6": "ipv4",
+ addressVersion: addressVersion === IPAddressVersion.IPV6 ? 'ipv6' : 'ipv4',
video: { // if suite is NONE, keys and salts are zero-length
port: targetVideoPort,
@@ -1102,56 +1146,58 @@ export class RTPStreamManagement {
srtp_key: audioMasterKey,
srtp_salt: audioMasterSalt,
},
- };
+ }
- const promises: Promise[] = [];
+ const promises: Promise[] = []
if (this.requireProxy) {
- prepareRequest.targetAddress = connection.getLocalAddress(addressVersion === IPAddressVersion.IPV6? "ipv6": "ipv4"); // ip versions must be the same
+ prepareRequest.targetAddress = connection.getLocalAddress(addressVersion === IPAddressVersion.IPV6 ? 'ipv6' : 'ipv4') // ip versions must be the same
this.videoProxy = new RTPProxy({
outgoingAddress: controllerAddress,
outgoingPort: targetVideoPort,
- outgoingSSRC: crypto.randomBytes(4).readUInt32LE(0), // videoSSRC
+ outgoingSSRC: randomBytes(4).readUInt32LE(0), // videoSSRC
disabled: false,
- });
+ })
promises.push(this.videoProxy.setup().then(() => {
- prepareRequest.video.proxy_rtp = this.videoProxy!.incomingRTPPort();
- prepareRequest.video.proxy_rtcp = this.videoProxy!.incomingRTCPPort();
- }));
+ prepareRequest.video.proxy_rtp = this.videoProxy!.incomingRTPPort()
+ prepareRequest.video.proxy_rtcp = this.videoProxy!.incomingRTCPPort()
+ }))
if (!this.disableAudioProxy) {
this.audioProxy = new RTPProxy({
outgoingAddress: controllerAddress,
outgoingPort: targetAudioPort,
- outgoingSSRC: crypto.randomBytes(4).readUInt32LE(0), // audioSSRC
+ outgoingSSRC: randomBytes(4).readUInt32LE(0), // audioSSRC
disabled: this.videoOnly,
- });
+ })
promises.push(this.audioProxy.setup().then(() => {
- prepareRequest.audio.proxy_rtp = this.audioProxy!.incomingRTPPort();
- prepareRequest.audio.proxy_rtcp = this.audioProxy!.incomingRTCPPort();
- }));
+ prepareRequest.audio.proxy_rtp = this.audioProxy!.incomingRTPPort()
+ prepareRequest.audio.proxy_rtcp = this.audioProxy!.incomingRTCPPort()
+ }))
}
}
Promise.all(promises).then(() => {
this.delegate.prepareStream(prepareRequest, once((error?: Error, response?: PrepareStreamResponse) => {
if (error || !response) {
- debug(`PrepareStream request encountered an error: ${error? error.message: undefined}`);
- this.setupEndpointsResponse = tlv.encode(
- SetupEndpointsResponseTypes.SESSION_ID, uuid.write(sessionIdentifier),
- SetupEndpointsResponseTypes.STATUS, SetupEndpointsStatus.ERROR,
- ).toString("base64");
-
- this.handleSessionClosed();
- callback(error);
+ debug(`PrepareStream request encountered an error: ${error ? error.message : undefined}`)
+ this.setupEndpointsResponse = encode(
+ SetupEndpointsResponseTypes.SESSION_ID,
+ write(sessionIdentifier),
+ SetupEndpointsResponseTypes.STATUS,
+ SetupEndpointsStatus.ERROR,
+ ).toString('base64')
+
+ this.handleSessionClosed()
+ callback(error)
} else {
- this.generateSetupEndpointResponse(connection, sessionIdentifier, prepareRequest, response, callback);
+ this.generateSetupEndpointResponse(connection, sessionIdentifier, prepareRequest, response, callback)
}
- }));
- });
+ }))
+ })
}
private generateSetupEndpointResponse(
@@ -1161,24 +1207,24 @@ export class RTPStreamManagement {
response: PrepareStreamResponse,
callback: CharacteristicSetCallback,
): void {
- let address: string;
- let addressVersion = request.addressVersion;
+ let address: string
+ let addressVersion = request.addressVersion
- let videoPort: number;
- let audioPort: number;
+ let videoPort: number
+ let audioPort: number
- let videoCryptoSuite: SRTPCryptoSuites;
- let videoSRTPKey: Buffer;
- let videoSRTPSalt: Buffer;
- let audioCryptoSuite: SRTPCryptoSuites;
- let audioSRTPKey: Buffer;
- let audioSRTPSalt: Buffer;
+ let videoCryptoSuite: SRTPCryptoSuites
+ let videoSRTPKey: Buffer
+ let videoSRTPSalt: Buffer
+ let audioCryptoSuite: SRTPCryptoSuites
+ let audioSRTPKey: Buffer
+ let audioSRTPSalt: Buffer
- let videoSSRC: number;
- let audioSSRC: number;
+ let videoSSRC: number
+ let audioSSRC: number
if (!this.videoOnly && !response.audio) {
- throw new Error("Audio was enabled but not supplied in PrepareStreamResponse!");
+ throw new Error('Audio was enabled but not supplied in PrepareStreamResponse!')
}
// Provide default values if audio was not supplied
@@ -1187,344 +1233,380 @@ export class RTPStreamManagement {
ssrc: CameraController.generateSynchronisationSource(),
srtp_key: request.audio.srtp_key,
srtp_salt: request.audio.srtp_salt,
- };
+ }
if (!this.requireProxy) {
- const videoInfo = response.video as SourceResponse;
- const audioInfo = audio as SourceResponse;
+ const videoInfo = response.video as SourceResponse
+ const audioInfo = audio as SourceResponse
if (response.addressOverride) {
- addressVersion = net.isIPv4(response.addressOverride)? "ipv4": "ipv6";
- address = response.addressOverride;
+ addressVersion = isIPv4(response.addressOverride) ? 'ipv4' : 'ipv6'
+ address = response.addressOverride
} else {
- address = connection.getLocalAddress(addressVersion);
+ address = connection.getLocalAddress(addressVersion)
}
if (request.addressVersion !== addressVersion) {
- throw new Error(`Incoming and outgoing ip address versions must match! Expected ${request.addressVersion} but got ${addressVersion}`);
+ throw new Error(`Incoming and outgoing ip address versions must match! Expected ${request.addressVersion} but got ${addressVersion}`)
}
- videoPort = videoInfo.port;
- audioPort = audioInfo.port;
-
+ videoPort = videoInfo.port
+ audioPort = audioInfo.port
if (request.video.srtpCryptoSuite !== SRTPCryptoSuites.NONE
- && (videoInfo.srtp_key === undefined || videoInfo.srtp_salt === undefined)) {
- throw new Error("SRTP was selected for the prepared video stream, but no 'srtp_key' or 'srtp_salt' was specified!");
+ && (videoInfo.srtp_key === undefined || videoInfo.srtp_salt === undefined)) {
+ throw new Error('SRTP was selected for the prepared video stream, but no \'srtp_key\' or \'srtp_salt\' was specified!')
}
if (request.audio.srtpCryptoSuite !== SRTPCryptoSuites.NONE
- && (audioInfo.srtp_key === undefined || audioInfo.srtp_salt === undefined)) {
- throw new Error("SRTP was selected for the prepared audio stream, but no 'srtp_key' or 'srtp_salt' was specified!");
+ && (audioInfo.srtp_key === undefined || audioInfo.srtp_salt === undefined)) {
+ throw new Error('SRTP was selected for the prepared audio stream, but no \'srtp_key\' or \'srtp_salt\' was specified!')
}
- videoCryptoSuite = request.video.srtpCryptoSuite;
- videoSRTPKey = videoInfo.srtp_key || Buffer.alloc(0); // key and salt are zero-length for cryptoSuite = NONE
- videoSRTPSalt = videoInfo.srtp_salt || Buffer.alloc(0);
+ videoCryptoSuite = request.video.srtpCryptoSuite
+ videoSRTPKey = videoInfo.srtp_key || Buffer.alloc(0) // key and salt are zero-length for cryptoSuite = NONE
+ videoSRTPSalt = videoInfo.srtp_salt || Buffer.alloc(0)
- audioCryptoSuite = request.audio.srtpCryptoSuite;
- audioSRTPKey = audioInfo.srtp_key || Buffer.alloc(0); // key and salt are zero-length for cryptoSuite = NONE
- audioSRTPSalt = audioInfo.srtp_salt || Buffer.alloc(0);
+ audioCryptoSuite = request.audio.srtpCryptoSuite
+ audioSRTPKey = audioInfo.srtp_key || Buffer.alloc(0) // key and salt are zero-length for cryptoSuite = NONE
+ audioSRTPSalt = audioInfo.srtp_salt || Buffer.alloc(0)
-
- videoSSRC = videoInfo.ssrc;
- audioSSRC = audioInfo.ssrc;
+ videoSSRC = videoInfo.ssrc
+ audioSSRC = audioInfo.ssrc
} else {
- const videoInfo = response.video as ProxiedSourceResponse;
-
- address = connection.getLocalAddress(request.addressVersion);
-
+ const videoInfo = response.video as ProxiedSourceResponse
- videoCryptoSuite = SRTPCryptoSuites.NONE;
- videoSRTPKey = Buffer.alloc(0);
- videoSRTPSalt = Buffer.alloc(0);
+ address = connection.getLocalAddress(request.addressVersion)
- audioCryptoSuite = SRTPCryptoSuites.NONE;
- audioSRTPKey = Buffer.alloc(0);
- audioSRTPSalt = Buffer.alloc(0);
+ videoCryptoSuite = SRTPCryptoSuites.NONE
+ videoSRTPKey = Buffer.alloc(0)
+ videoSRTPSalt = Buffer.alloc(0)
+ audioCryptoSuite = SRTPCryptoSuites.NONE
+ audioSRTPKey = Buffer.alloc(0)
+ audioSRTPSalt = Buffer.alloc(0)
- this.videoProxy!.setIncomingPayloadType(videoInfo.proxy_pt);
- this.videoProxy!.setServerAddress(videoInfo.proxy_server_address);
- this.videoProxy!.setServerRTPPort(videoInfo.proxy_server_rtp);
- this.videoProxy!.setServerRTCPPort(videoInfo.proxy_server_rtcp);
+ this.videoProxy!.setIncomingPayloadType(videoInfo.proxy_pt)
+ this.videoProxy!.setServerAddress(videoInfo.proxy_server_address)
+ this.videoProxy!.setServerRTPPort(videoInfo.proxy_server_rtp)
+ this.videoProxy!.setServerRTCPPort(videoInfo.proxy_server_rtcp)
- videoPort = this.videoProxy!.outgoingLocalPort();
- videoSSRC = this.videoProxy!.outgoingSSRC;
+ videoPort = this.videoProxy!.outgoingLocalPort()
+ videoSSRC = this.videoProxy!.outgoingSSRC
if (!this.disableAudioProxy) {
- const audioInfo = response.audio as ProxiedSourceResponse;
- this.audioProxy!.setIncomingPayloadType(audioInfo.proxy_pt);
- this.audioProxy!.setServerAddress(audioInfo.proxy_server_address);
- this.audioProxy!.setServerRTPPort(audioInfo.proxy_server_rtp);
- this.audioProxy!.setServerRTCPPort(audioInfo.proxy_server_rtcp);
-
- audioPort = this.audioProxy!.outgoingLocalPort();
- audioSSRC = this.audioProxy!.outgoingSSRC;
+ const audioInfo = response.audio as ProxiedSourceResponse
+ this.audioProxy!.setIncomingPayloadType(audioInfo.proxy_pt)
+ this.audioProxy!.setServerAddress(audioInfo.proxy_server_address)
+ this.audioProxy!.setServerRTPPort(audioInfo.proxy_server_rtp)
+ this.audioProxy!.setServerRTCPPort(audioInfo.proxy_server_rtcp)
+
+ audioPort = this.audioProxy!.outgoingLocalPort()
+ audioSSRC = this.audioProxy!.outgoingSSRC
} else {
- const audioInfo = response.audio as SourceResponse;
+ const audioInfo = response.audio as SourceResponse
- audioPort = audioInfo.port;
- audioSSRC = audioInfo.ssrc;
+ audioPort = audioInfo.port
+ audioSSRC = audioInfo.ssrc
}
}
- this.ipVersion = addressVersion; // we need to save this in order to calculate some default mtu values later
-
- const accessoryAddress = tlv.encode(
- AddressTypes.ADDRESS_VERSION, addressVersion === "ipv4"? IPAddressVersion.IPV4: IPAddressVersion.IPV6,
- AddressTypes.ADDRESS, address,
- AddressTypes.VIDEO_RTP_PORT, tlv.writeUInt16(videoPort),
- AddressTypes.AUDIO_RTP_PORT, tlv.writeUInt16(audioPort),
- );
-
- const videoSRTPParameters = tlv.encode(
- SRTPParametersTypes.SRTP_CRYPTO_SUITE, videoCryptoSuite,
- SRTPParametersTypes.MASTER_KEY, videoSRTPKey,
- SRTPParametersTypes.MASTER_SALT, videoSRTPSalt,
- );
-
- const audioSRTPParameters = tlv.encode(
- SRTPParametersTypes.SRTP_CRYPTO_SUITE, audioCryptoSuite,
- SRTPParametersTypes.MASTER_KEY, audioSRTPKey,
- SRTPParametersTypes.MASTER_SALT, audioSRTPSalt,
- );
-
- this.setupEndpointsResponse = tlv.encode(
- SetupEndpointsResponseTypes.SESSION_ID, uuid.write(identifier),
- SetupEndpointsResponseTypes.STATUS, SetupEndpointsStatus.SUCCESS,
- SetupEndpointsResponseTypes.ACCESSORY_ADDRESS, accessoryAddress,
- SetupEndpointsResponseTypes.VIDEO_SRTP_PARAMETERS, videoSRTPParameters,
- SetupEndpointsResponseTypes.AUDIO_SRTP_PARAMETERS, audioSRTPParameters,
- SetupEndpointsResponseTypes.VIDEO_SSRC, tlv.writeUInt32(videoSSRC),
- SetupEndpointsResponseTypes.AUDIO_SSRC, tlv.writeUInt32(audioSSRC),
- ).toString("base64");
- callback();
+ this.ipVersion = addressVersion // we need to save this in order to calculate some default mtu values later
+
+ const accessoryAddress = encode(
+ AddressTypes.ADDRESS_VERSION,
+ addressVersion === 'ipv4' ? IPAddressVersion.IPV4 : IPAddressVersion.IPV6,
+ AddressTypes.ADDRESS,
+ address,
+ AddressTypes.VIDEO_RTP_PORT,
+ writeUInt16(videoPort),
+ AddressTypes.AUDIO_RTP_PORT,
+ writeUInt16(audioPort),
+ )
+
+ const videoSRTPParameters = encode(
+ SRTPParametersTypes.SRTP_CRYPTO_SUITE,
+ videoCryptoSuite,
+ SRTPParametersTypes.MASTER_KEY,
+ videoSRTPKey,
+ SRTPParametersTypes.MASTER_SALT,
+ videoSRTPSalt,
+ )
+
+ const audioSRTPParameters = encode(
+ SRTPParametersTypes.SRTP_CRYPTO_SUITE,
+ audioCryptoSuite,
+ SRTPParametersTypes.MASTER_KEY,
+ audioSRTPKey,
+ SRTPParametersTypes.MASTER_SALT,
+ audioSRTPSalt,
+ )
+
+ this.setupEndpointsResponse = encode(
+ SetupEndpointsResponseTypes.SESSION_ID,
+ write(identifier),
+ SetupEndpointsResponseTypes.STATUS,
+ SetupEndpointsStatus.SUCCESS,
+ SetupEndpointsResponseTypes.ACCESSORY_ADDRESS,
+ accessoryAddress,
+ SetupEndpointsResponseTypes.VIDEO_SRTP_PARAMETERS,
+ videoSRTPParameters,
+ SetupEndpointsResponseTypes.AUDIO_SRTP_PARAMETERS,
+ audioSRTPParameters,
+ SetupEndpointsResponseTypes.VIDEO_SSRC,
+ writeUInt32(videoSSRC),
+ SetupEndpointsResponseTypes.AUDIO_SSRC,
+ writeUInt32(audioSSRC),
+ ).toString('base64')
+ callback()
}
private _updateStreamStatus(status: StreamingStatus): void {
- this.streamStatus = status;
+ this.streamStatus = status
- this.service.updateCharacteristic(Characteristic.StreamingStatus, tlv.encode(
- StreamingStatusTypes.STATUS, this.streamStatus,
- ).toString("base64"));
+ this.service.updateCharacteristic(Characteristic.StreamingStatus, encode(
+ StreamingStatusTypes.STATUS,
+ this.streamStatus,
+ ).toString('base64'))
}
private static _supportedRTPConfiguration(supportedCryptoSuites: SRTPCryptoSuites[]): string {
if (supportedCryptoSuites.length === 1 && supportedCryptoSuites[0] === SRTPCryptoSuites.NONE) {
- debug("Client claims it doesn't support SRTP. The stream may stops working with future iOS releases.");
+ debug('Client claims it doesn\'t support SRTP. The stream may stops working with future iOS releases.')
}
- return tlv.encode(SupportedRTPConfigurationTypes.SRTP_CRYPTO_SUITE, supportedCryptoSuites).toString("base64");
+ return encode(SupportedRTPConfigurationTypes.SRTP_CRYPTO_SUITE, supportedCryptoSuites).toString('base64')
}
private static _supportedVideoStreamConfiguration(videoOptions: VideoStreamingOptions): string {
if (!videoOptions.codec) {
- throw new Error("Video codec cannot be undefined");
+ throw new Error('Video codec cannot be undefined')
}
if (!videoOptions.resolutions) {
- throw new Error("Video resolutions cannot be undefined");
+ throw new Error('Video resolutions cannot be undefined')
}
- let codecParameters = tlv.encode(
- VideoCodecParametersTypes.PROFILE_ID, videoOptions.codec.profiles,
- VideoCodecParametersTypes.LEVEL, videoOptions.codec.levels,
- VideoCodecParametersTypes.PACKETIZATION_MODE, VideoCodecPacketizationMode.NON_INTERLEAVED,
- );
+ let codecParameters = encode(
+ VideoCodecParametersTypes.PROFILE_ID,
+ videoOptions.codec.profiles,
+ VideoCodecParametersTypes.LEVEL,
+ videoOptions.codec.levels,
+ VideoCodecParametersTypes.PACKETIZATION_MODE,
+ VideoCodecPacketizationMode.NON_INTERLEAVED,
+ )
if (videoOptions.cvoId != null) {
codecParameters = Buffer.concat([
codecParameters,
- tlv.encode(
- VideoCodecParametersTypes.CVO_ENABLED, VideoCodecCVO.SUPPORTED,
- VideoCodecParametersTypes.CVO_ID, videoOptions.cvoId,
+ encode(
+ VideoCodecParametersTypes.CVO_ENABLED,
+ VideoCodecCVO.SUPPORTED,
+ VideoCodecParametersTypes.CVO_ID,
+ videoOptions.cvoId,
),
- ]);
+ ])
}
- const videoStreamConfiguration = tlv.encode(
- VideoCodecConfigurationTypes.CODEC_TYPE, VideoCodecType.H264,
- VideoCodecConfigurationTypes.CODEC_PARAMETERS, codecParameters,
- VideoCodecConfigurationTypes.ATTRIBUTES, videoOptions.resolutions.map(resolution => {
+ const videoStreamConfiguration = encode(
+ VideoCodecConfigurationTypes.CODEC_TYPE,
+ VideoCodecType.H264,
+ VideoCodecConfigurationTypes.CODEC_PARAMETERS,
+ codecParameters,
+ VideoCodecConfigurationTypes.ATTRIBUTES,
+ videoOptions.resolutions.map((resolution) => {
if (resolution.length !== 3) {
- throw new Error("Unexpected video resolution");
+ throw new Error('Unexpected video resolution')
}
- const width = Buffer.alloc(2);
- const height = Buffer.alloc(2);
- const frameRate = Buffer.alloc(1);
-
- width.writeUInt16LE(resolution[0], 0);
- height.writeUInt16LE(resolution[1], 0);
- frameRate.writeUInt8(resolution[2], 0);
-
- return tlv.encode(
- VideoAttributesTypes.IMAGE_WIDTH, width,
- VideoAttributesTypes.IMAGE_HEIGHT, height,
- VideoAttributesTypes.FRAME_RATE, frameRate,
- );
+ const width = Buffer.alloc(2)
+ const height = Buffer.alloc(2)
+ const frameRate = Buffer.alloc(1)
+
+ width.writeUInt16LE(resolution[0], 0)
+ height.writeUInt16LE(resolution[1], 0)
+ frameRate.writeUInt8(resolution[2], 0)
+
+ return encode(
+ VideoAttributesTypes.IMAGE_WIDTH,
+ width,
+ VideoAttributesTypes.IMAGE_HEIGHT,
+ height,
+ VideoAttributesTypes.FRAME_RATE,
+ frameRate,
+ )
}),
- );
+ )
- return tlv.encode(
- SupportedVideoStreamConfigurationTypes.VIDEO_CODEC_CONFIGURATION, videoStreamConfiguration,
- ).toString("base64");
+ return encode(
+ SupportedVideoStreamConfigurationTypes.VIDEO_CODEC_CONFIGURATION,
+ videoStreamConfiguration,
+ ).toString('base64')
}
private checkForLegacyAudioCodecRepresentation(codecs: AudioStreamingCodec[]) { // we basically merge the samplerates here
- const codecMap: Record = {};
+ const codecMap: Record = {}
- codecs.slice().forEach(codec => {
- const previous = codecMap[codec.type];
+ codecs.slice().forEach((codec) => {
+ const previous = codecMap[codec.type]
if (previous) {
- if (typeof previous.samplerate === "number") {
- previous.samplerate = [previous.samplerate];
+ if (typeof previous.samplerate === 'number') {
+ previous.samplerate = [previous.samplerate]
}
- previous.samplerate = previous.samplerate.concat(codec.samplerate);
+ previous.samplerate = previous.samplerate.concat(codec.samplerate)
- const index = codecs.indexOf(codec);
+ const index = codecs.indexOf(codec)
if (index >= 0) {
- codecs.splice(index, 1);
+ codecs.splice(index, 1)
}
} else {
- codecMap[codec.type] = codec;
+ codecMap[codec.type] = codec
}
- });
+ })
}
private _supportedAudioStreamConfiguration(audioOptions?: AudioStreamingOptions): string {
// Only AAC-ELD and OPUS are accepted by iOS currently, and we need to give it something it will accept
// for it to start the video stream.
- const comfortNoise = audioOptions && !!audioOptions.comfort_noise;
- const supportedCodecs: AudioStreamingCodec[] = (audioOptions && audioOptions.codecs) || [];
- this.checkForLegacyAudioCodecRepresentation(supportedCodecs);
+ const comfortNoise = audioOptions && !!audioOptions.comfort_noise
+ const supportedCodecs: AudioStreamingCodec[] = (audioOptions && audioOptions.codecs) || []
+ this.checkForLegacyAudioCodecRepresentation(supportedCodecs)
if (supportedCodecs.length === 0) { // Fake a Codec if we haven't got anything
- debug("Client doesn't support any audio codec that HomeKit supports.");
- this.videoOnly = true;
+ debug('Client doesn\'t support any audio codec that HomeKit supports.')
+ this.videoOnly = true
supportedCodecs.push({
type: AudioStreamingCodecType.OPUS, // Opus @16K required by Apple Watch AFAIK
samplerate: [AudioStreamingSamplerate.KHZ_16, AudioStreamingSamplerate.KHZ_24], // 16 and 24 must be supported
- });
+ })
}
- const codecConfigurations: Buffer[] = supportedCodecs.map(codec => {
- let type: AudioCodecTypes;
+ const codecConfigurations: Buffer[] = supportedCodecs.map((codec) => {
+ let type: AudioCodecTypes
switch (codec.type) {
- case AudioStreamingCodecType.OPUS:
- type = AudioCodecTypes.OPUS;
- break;
- case AudioStreamingCodecType.AAC_ELD:
- type = AudioCodecTypes.AAC_ELD;
- break;
- case AudioStreamingCodecType.PCMA:
- type = AudioCodecTypes.PCMA;
- break;
- case AudioStreamingCodecType.PCMU:
- type = AudioCodecTypes.PCMU;
- break;
- case AudioStreamingCodecType.MSBC:
- type = AudioCodecTypes.MSBC;
- break;
- case AudioStreamingCodecType.AMR:
- type = AudioCodecTypes.AMR;
- break;
- case AudioStreamingCodecType.AMR_WB:
- type = AudioCodecTypes.AMR_WB;
- break;
- default:
- throw new Error("Unsupported codec: " + codec.type);
+ case AudioStreamingCodecType.OPUS:
+ type = AudioCodecTypes.OPUS
+ break
+ case AudioStreamingCodecType.AAC_ELD:
+ type = AudioCodecTypes.AAC_ELD
+ break
+ case AudioStreamingCodecType.PCMA:
+ type = AudioCodecTypes.PCMA
+ break
+ case AudioStreamingCodecType.PCMU:
+ type = AudioCodecTypes.PCMU
+ break
+ case AudioStreamingCodecType.MSBC:
+ type = AudioCodecTypes.MSBC
+ break
+ case AudioStreamingCodecType.AMR:
+ type = AudioCodecTypes.AMR
+ break
+ case AudioStreamingCodecType.AMR_WB:
+ type = AudioCodecTypes.AMR_WB
+ break
+ default:
+ throw new Error(`Unsupported codec: ${codec.type}`)
}
- const providedSamplerates = (typeof codec.samplerate === "number"? [codec.samplerate]: codec.samplerate).map(rate => {
- let samplerate;
+ const providedSamplerates = (typeof codec.samplerate === 'number' ? [codec.samplerate] : codec.samplerate).map((rate) => {
+ let samplerate
switch (rate) {
- case AudioStreamingSamplerate.KHZ_8:
- samplerate = AudioSamplerate.KHZ_8;
- break;
- case AudioStreamingSamplerate.KHZ_16:
- samplerate = AudioSamplerate.KHZ_16;
- break;
- case AudioStreamingSamplerate.KHZ_24:
- samplerate = AudioSamplerate.KHZ_24;
- break;
- default:
- console.log("Unsupported sample rate: ", codec.samplerate);
- samplerate = -1;
+ case AudioStreamingSamplerate.KHZ_8:
+ samplerate = AudioSamplerate.KHZ_8
+ break
+ case AudioStreamingSamplerate.KHZ_16:
+ samplerate = AudioSamplerate.KHZ_16
+ break
+ case AudioStreamingSamplerate.KHZ_24:
+ samplerate = AudioSamplerate.KHZ_24
+ break
+ default:
+ // eslint-disable-next-line no-console
+ console.log('Unsupported sample rate: ', codec.samplerate)
+ samplerate = -1
}
- return samplerate;
- }).filter(rate => rate !== -1);
+ return samplerate
+ }).filter(rate => rate !== -1)
if (providedSamplerates.length === 0) {
- throw new Error("Audio samplerate cannot be empty!");
+ throw new Error('Audio samplerate cannot be empty!')
}
- const audioParameters = tlv.encode(
- AudioCodecParametersTypes.CHANNEL, Math.max(1, codec.audioChannels || 1),
- AudioCodecParametersTypes.BIT_RATE, codec.bitrate || AudioBitrate.VARIABLE,
- AudioCodecParametersTypes.SAMPLE_RATE, providedSamplerates,
- );
-
- return tlv.encode(
- AudioCodecConfigurationTypes.CODEC_TYPE, type,
- AudioCodecConfigurationTypes.CODEC_PARAMETERS, audioParameters,
- );
- });
-
- return tlv.encode(
- SupportedAudioStreamConfigurationTypes.AUDIO_CODEC_CONFIGURATION, codecConfigurations,
- SupportedAudioStreamConfigurationTypes.COMFORT_NOISE_SUPPORT, comfortNoise? 1: 0,
- ).toString("base64");
+ const audioParameters = encode(
+ AudioCodecParametersTypes.CHANNEL,
+ Math.max(1, codec.audioChannels || 1),
+ AudioCodecParametersTypes.BIT_RATE,
+ codec.bitrate || AudioBitrate.VARIABLE,
+ AudioCodecParametersTypes.SAMPLE_RATE,
+ providedSamplerates,
+ )
+
+ return encode(
+ AudioCodecConfigurationTypes.CODEC_TYPE,
+ type,
+ AudioCodecConfigurationTypes.CODEC_PARAMETERS,
+ audioParameters,
+ )
+ })
+
+ return encode(
+ SupportedAudioStreamConfigurationTypes.AUDIO_CODEC_CONFIGURATION,
+ codecConfigurations,
+ SupportedAudioStreamConfigurationTypes.COMFORT_NOISE_SUPPORT,
+ comfortNoise ? 1 : 0,
+ ).toString('base64')
}
private resetSetupEndpointsResponse(): void {
- this.setupEndpointsResponse = tlv.encode(
- SetupEndpointsResponseTypes.STATUS, SetupEndpointsStatus.ERROR,
- ).toString("base64");
- this.service.updateCharacteristic(Characteristic.SetupEndpoints, this.setupEndpointsResponse);
+ this.setupEndpointsResponse = encode(
+ SetupEndpointsResponseTypes.STATUS,
+ SetupEndpointsStatus.ERROR,
+ ).toString('base64')
+ this.service.updateCharacteristic(Characteristic.SetupEndpoints, this.setupEndpointsResponse)
}
private resetSelectedStreamConfiguration(): void {
- this.selectedConfiguration = tlv.encode(
- SelectedRTPStreamConfigurationTypes.SESSION_CONTROL, tlv.encode(
- SessionControlTypes.COMMAND, SessionControlCommand.SUSPEND_SESSION,
+ this.selectedConfiguration = encode(
+ SelectedRTPStreamConfigurationTypes.SESSION_CONTROL,
+ encode(
+ SessionControlTypes.COMMAND,
+ SessionControlCommand.SUSPEND_SESSION,
),
- ).toString("base64");
- this.service.updateCharacteristic(Characteristic.SelectedRTPStreamConfiguration, this.selectedConfiguration);
+ ).toString('base64')
+ this.service.updateCharacteristic(Characteristic.SelectedRTPStreamConfiguration, this.selectedConfiguration)
}
/**
* @private
*/
serialize(): RTPStreamManagementState | undefined {
- const characteristicValue = this.service.getCharacteristic(Characteristic.Active).value;
+ const characteristicValue = this.service.getCharacteristic(Characteristic.Active).value
if (characteristicValue === true) {
- return undefined;
+ return undefined
}
return {
id: this.id,
active: !!characteristicValue,
- };
+ }
}
/**
* @private
*/
deserialize(serialized: RTPStreamManagementState): void {
- assert(serialized.id === this.id, `Tried to initialize RTPStreamManagement ${this.id} with data from management with id ${serialized.id}!`);
+ assert(serialized.id === this.id, `Tried to initialize RTPStreamManagement ${this.id} with data from management with id ${serialized.id}!`)
- this.service.updateCharacteristic(Characteristic.Active, serialized.active);
+ this.service.updateCharacteristic(Characteristic.Active, serialized.active)
}
/**
* @private
*/
setupStateChangeDelegate(delegate?: StateChangeDelegate): void {
- this.stateChangeDelegate = delegate;
+ this.stateChangeDelegate = delegate
}
}
-
diff --git a/src/lib/camera/RecordingManagement.spec.ts b/src/lib/camera/RecordingManagement.spec.ts
index 6155080ff..403cc05af 100644
--- a/src/lib/camera/RecordingManagement.spec.ts
+++ b/src/lib/camera/RecordingManagement.spec.ts
@@ -1,40 +1,43 @@
-import { Characteristic } from "../Characteristic";
-import { MockDelegate, mockRecordingOptions } from "../controller/CameraController.spec";
+import type { CameraRecordingConfiguration } from './RecordingManagement'
+
+import { describe, expect, it } from 'vitest'
+
+import { Characteristic } from '../Characteristic.js'
+import { MockDelegate, mockRecordingOptions } from '../controller/CameraController.spec.js'
import {
AudioRecordingCodecType,
AudioRecordingSamplerate,
- CameraRecordingConfiguration,
EventTriggerOption,
MediaContainerType,
RecordingManagement,
-} from "./RecordingManagement";
-import { AudioBitrate, H264Level, H264Profile, VideoCodecType } from "./RTPStreamManagement";
+} from './RecordingManagement.js'
+import { AudioBitrate, H264Level, H264Profile, VideoCodecType } from './RTPStreamManagement.js'
-describe("RecordingManagement", () => {
- test("handleSelectedCameraRecordingConfiguration", () => {
- const base64Input = "AR0BBKAPAAACCAEAAAAAAAAAAwsBAQACBgEEoA8AAAIkAQEAAhIBAQECAQIDBNAHAAAEBKAPAAADCwECsAQCAkAGAwEYAxQBAQACDwEBAQIBAAMBAwQEQAAAAA==";
+describe('recordingManagement', () => {
+ it('handleSelectedCameraRecordingConfiguration', () => {
+ const base64Input = 'AR0BBKAPAAACCAEAAAAAAAAAAwsBAQACBgEEoA8AAAIkAQEAAhIBAQECAQIDBNAHAAAEBKAPAAADCwECsAQCAkAGAwEYAxQBAQACDwEBAQIBAAMBAwQEQAAAAA=='
const management = new RecordingManagement(
mockRecordingOptions,
new MockDelegate(),
new Set([EventTriggerOption.MOTION, EventTriggerOption.DOORBELL]),
- );
+ )
// @ts-expect-error: private access
- management.handleSelectedCameraRecordingConfigurationWrite(base64Input);
+ management.handleSelectedCameraRecordingConfigurationWrite(base64Input)
// @ts-expect-error: private access
expect(management.handleSelectedCameraRecordingConfigurationRead())
- .toEqual(base64Input);
+ .toEqual(base64Input)
// @ts-expect-error: private access
- const configuration = management.selectedConfiguration;
- expect(configuration).toBeDefined();
- expect(configuration!.base64).toEqual(base64Input);
+ const configuration = management.selectedConfiguration
+ expect(configuration).toBeDefined()
+ expect(configuration!.base64).toEqual(base64Input)
const expected: CameraRecordingConfiguration = {
prebufferLength: 4000,
- eventTriggerTypes: [ EventTriggerOption.MOTION ],
+ eventTriggerTypes: [EventTriggerOption.MOTION],
mediaContainerConfiguration: { type: MediaContainerType.FRAGMENTED_MP4, fragmentLength: 4000 },
videoCodec: {
type: VideoCodecType.H264,
@@ -44,7 +47,7 @@ describe("RecordingManagement", () => {
bitRate: 2000,
iFrameInterval: 4000,
},
- resolution: [ 1200, 1600, 24 ],
+ resolution: [1200, 1600, 24],
},
audioCodec: {
type: AudioRecordingCodecType.AAC_LC,
@@ -53,11 +56,11 @@ describe("RecordingManagement", () => {
bitrateMode: AudioBitrate.VARIABLE,
bitrate: 64,
},
- };
- expect(configuration!.parsed).toEqual(expected);
- });
+ }
+ expect(configuration!.parsed).toEqual(expected)
+ })
- test("test supported configuration", () => {
+ it('test supported configuration', () => {
const management = new RecordingManagement(
{
prebufferLength: 8000,
@@ -101,19 +104,19 @@ describe("RecordingManagement", () => {
},
new MockDelegate(),
new Set([EventTriggerOption.MOTION, EventTriggerOption.DOORBELL]),
- );
+ )
const supportedRecording = management.recordingManagementService
- .getCharacteristic(Characteristic.SupportedCameraRecordingConfiguration).value;
+ .getCharacteristic(Characteristic.SupportedCameraRecordingConfiguration).value
const supportedVideo = management.recordingManagementService
- .getCharacteristic(Characteristic.SupportedVideoRecordingConfiguration).value;
+ .getCharacteristic(Characteristic.SupportedVideoRecordingConfiguration).value
const supportedAudio = management.recordingManagementService
- .getCharacteristic(Characteristic.SupportedAudioRecordingConfiguration).value;
+ .getCharacteristic(Characteristic.SupportedAudioRecordingConfiguration).value
- expect(supportedRecording).toEqual("AQRAHwAAAggDAAAAAAAAAAMLAQEAAgYBBKAPAAA=");
- expect(supportedVideo).toEqual("Af4BAQACCwEBAQIBAAAAAgECAwsBAkAGAgKwBAMBGAAAAwsBAqAFAgI4BAMBGAAAAwsBAgAFAgLAAwMBGAAAAwsBAgAEAgIAAwMBGAAAAwsBAk" +
- "AGAgKwBAMBDwAAAwsBAqAFAgI4BAMBDwAAAwsBAgAFAgLAAwMBDwAAAwsBAgAEAgIAAwMBDwAAAwsBArAEAgJABgMBGAAAAwsBAjgEAgKgBQMBGAAAAwsBAsADAgIABQMBGAAAAwsBAgADAgIABA" +
- "MBGAAAAwsBArAEAgJABgMBDwAAAwsBAjgEAgKgBQMBDwAAAwsBAsADAgIABQMBDwAAAwsBAgADAgIABAMBDw==");
- expect(supportedAudio).toEqual("AQ4BAQACCQEBAQIBAAMBAw==");
- });
-});
+ expect(supportedRecording).toEqual('AQRAHwAAAggDAAAAAAAAAAMLAQEAAgYBBKAPAAA=')
+ expect(supportedVideo).toEqual('Af4BAQACCwEBAQIBAAAAAgECAwsBAkAGAgKwBAMBGAAAAwsBAqAFAgI4BAMBGAAAAwsBAgAFAgLAAwMBGAAAAwsBAgAEAgIAAwMBGAAAAwsBAk'
+ + 'AGAgKwBAMBDwAAAwsBAqAFAgI4BAMBDwAAAwsBAgAFAgLAAwMBDwAAAwsBAgAEAgIAAwMBDwAAAwsBArAEAgJABgMBGAAAAwsBAjgEAgKgBQMBGAAAAwsBAsADAgIABQMBGAAAAwsBAgADAgIABA'
+ + 'MBGAAAAwsBArAEAgJABgMBDwAAAwsBAjgEAgKgBQMBDwAAAwsBAsADAgIABQMBDwAAAwsBAgADAgIABAMBDw==')
+ expect(supportedAudio).toEqual('AQ4BAQACCQEBAQIBAAMBAw==')
+ })
+})
diff --git a/src/lib/camera/RecordingManagement.ts b/src/lib/camera/RecordingManagement.ts
index 09889745e..a2e651882 100644
--- a/src/lib/camera/RecordingManagement.ts
+++ b/src/lib/camera/RecordingManagement.ts
@@ -1,32 +1,40 @@
-import crypto from "crypto";
-import createDebug from "debug";
-import { EventEmitter } from "events";
-import { AudioBitrate, VideoCodecType } from ".";
-import { Access, Characteristic, CharacteristicEventTypes } from "../Characteristic";
-import { CameraRecordingDelegate, StateChangeDelegate } from "../controller";
-import {
+/* global NodeJS */
+import type { VideoCodecType } from '.'
+import type { CameraRecordingDelegate, StateChangeDelegate } from '../controller'
+import type {
DataStreamConnection,
- DataStreamConnectionEvent,
- DataStreamManagement,
DataStreamProtocolHandler,
EventHandler,
+ RequestHandler,
+} from '../datastream'
+import type { CameraOperatingMode, CameraRecordingManagement } from '../definitions'
+import type { H264CodecParameters, H264Level, H264Profile, Resolution } from './RTPStreamManagement'
+
+import { Buffer } from 'node:buffer'
+import { createHash } from 'node:crypto'
+import { EventEmitter } from 'node:events'
+
+import createDebug from 'debug'
+
+import { Access, Characteristic, CharacteristicEventTypes } from '../Characteristic.js'
+import {
+ DataStreamConnectionEvent,
+ DataStreamManagement,
HDSConnectionError,
HDSConnectionErrorType,
HDSProtocolError,
HDSProtocolSpecificErrorReason,
HDSStatus,
Protocols,
- RequestHandler,
Topics,
-} from "../datastream";
-import { CameraOperatingMode, CameraRecordingManagement } from "../definitions";
-import { HAPStatus } from "../HAPServer";
-import { Service } from "../Service";
-import { HapStatusError } from "../util/hapStatusError";
-import * as tlv from "../util/tlv";
-import { H264CodecParameters, H264Level, H264Profile, Resolution } from "./RTPStreamManagement";
+} from '../datastream/index.js'
+import { HAPStatus } from '../HAPServer.js'
+import { Service } from '../Service.js'
+import { HapStatusError } from '../util/hapStatusError.js'
+import { decode, encode } from '../util/tlv.js'
+import { AudioBitrate } from './index.js'
-const debug = createDebug("HAP-NodeJS:Camera:RecordingManagement");
+const debug = createDebug('HAP-NodeJS:Camera:RecordingManagement')
/**
* Describes options passed to the {@link RecordingManagement}.
@@ -43,15 +51,15 @@ export interface CameraRecordingOptions {
* This exactly is the prebuffer. A camera will constantly store the last
* x seconds (the `prebufferLength`) to provide more context to a given event.
*/
- prebufferLength: number;
+ prebufferLength: number
/**
* This property can be used to override the automatic heuristic of the {@link CameraController}
* which derives the {@link EventTriggerOption}s from application state.
*
* {@link EventTriggerOption}s are derived automatically as follows:
- * * {@link EventTriggerOption.MOTION} is enabled when a {@link Service.MotionSensor} is configured (via {@link CameraControllerOptions.sensors}).
- * * {@link EventTriggerOption.DOORBELL} is enabled when the {@link DoorbellController} is used.
+ * {@link EventTriggerOption.MOTION} is enabled when a {@link Service.MotionSensor} is configured (via {@link CameraControllerOptions.sensors}).
+ * {@link EventTriggerOption.DOORBELL} is enabled when the {@link DoorbellController} is used.
*
* Note: This property is **ADDITIVE**. Meaning if the {@link CameraController} decides to add
* a certain {@link EventTriggerOption} it will still do so. This option can only be used to
@@ -62,10 +70,10 @@ export interface CameraRecordingOptions {
/**
* List of supported media {@link MediaContainerConfiguration}s (or a single one).
*/
- mediaContainerConfiguration: MediaContainerConfiguration | MediaContainerConfiguration[];
+ mediaContainerConfiguration: MediaContainerConfiguration | MediaContainerConfiguration[]
- video: VideoRecordingOptions,
- audio: AudioRecordingOptions,
+ video: VideoRecordingOptions
+ audio: AudioRecordingOptions
}
/**
@@ -73,6 +81,7 @@ export interface CameraRecordingOptions {
*
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum EventTriggerOption {
/**
* The Motion trigger. If enabled motion should trigger the start of a recording.
@@ -92,8 +101,9 @@ export const enum EventTriggerOption {
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum MediaContainerType {
- FRAGMENTED_MP4 = 0x00
+ FRAGMENTED_MP4 = 0x00,
}
/**
@@ -103,57 +113,57 @@ export interface MediaContainerConfiguration {
/**
* The type of media container.
*/
- type: MediaContainerType;
+ type: MediaContainerType
/**
* The length in milliseconds of every individual recording fragment.
* A typical value of HomeKit Secure Video cameras is 4000ms.
*/
- fragmentLength: number;
+ fragmentLength: number
}
/**
* @group Camera
*/
export interface VideoRecordingOptions {
- type: VideoCodecType;
- parameters: H264CodecParameters;
+ type: VideoCodecType
+ parameters: H264CodecParameters
/**
* Required resolutions to be supported are:
- * * 1920x1080
- * * 1280x720
+ * 1920x1080
+ * 1280x720
*
* The following frame rates are required to be supported:
- * * 15 fps
- * * 24fps or 30fps
+ * 15 fps
+ * 24fps or 30fps
*/
- resolutions: Resolution[];
+ resolutions: Resolution[]
}
/**
* @group Camera
*/
-export type AudioRecordingOptions = {
+export interface AudioRecordingOptions {
/**
* List (or single entry) of supported {@link AudioRecordingCodec}s.
*/
- codecs: AudioRecordingCodec | AudioRecordingCodec[],
+ codecs: AudioRecordingCodec | AudioRecordingCodec[]
}
/**
* @group Camera
*/
-export type AudioRecordingCodec = {
- type: AudioRecordingCodecType,
+export interface AudioRecordingCodec {
+ type: AudioRecordingCodecType
/**
* The count of audio channels. Must be at least `1`.
* Defaults to `1`.
*/
- audioChannels?: number,
+ audioChannels?: number
/**
* The supported bitrate mode. Defaults to {@link AudioBitrate.VARIABLE}.
*/
- bitrateMode?: AudioBitrate,
- samplerate: AudioRecordingSamplerate[] | AudioRecordingSamplerate,
+ bitrateMode?: AudioBitrate
+ samplerate: AudioRecordingSamplerate[] | AudioRecordingSamplerate
}
/**
@@ -166,54 +176,55 @@ export interface CameraRecordingConfiguration {
* The size of the prebuffer in milliseconds.
* This value is less or equal of the value advertised in the {@link Characteristic.SupportedCameraRecordingConfiguration}.
*/
- prebufferLength: number;
+ prebufferLength: number
/**
* List of the enabled {@link EventTriggerOption}s.
*/
- eventTriggerTypes: EventTriggerOption[];
+ eventTriggerTypes: EventTriggerOption[]
/**
* The selected {@link MediaContainerConfiguration}.
*/
- mediaContainerConfiguration: MediaContainerConfiguration;
+ mediaContainerConfiguration: MediaContainerConfiguration
/**
* The selected video codec configuration.
*/
videoCodec: {
- type: VideoCodecType.H264;
- parameters: SelectedH264CodecParameters,
- resolution: Resolution,
- },
+ type: VideoCodecType.H264
+ parameters: SelectedH264CodecParameters
+ resolution: Resolution
+ }
/**
* The selected audio codec configuration.
*/
audioCodec: AudioRecordingCodec & {
- bitrate: number,
- samplerate: AudioRecordingSamplerate,
- },
+ bitrate: number
+ samplerate: AudioRecordingSamplerate
+ }
}
/**
* @group Camera
*/
export interface SelectedH264CodecParameters {
- profile: H264Profile,
- level: H264Level,
- bitRate: number,
+ profile: H264Profile
+ level: H264Level
+ bitRate: number
/**
* The selected i-frame interval in milliseconds.
*/
- iFrameInterval: number,
+ iFrameInterval: number
}
-
+// eslint-disable-next-line no-restricted-syntax
const enum VideoCodecConfigurationTypes {
CODEC_TYPE = 0x01,
CODEC_PARAMETERS = 0x02,
ATTRIBUTES = 0x03,
}
+// eslint-disable-next-line no-restricted-syntax
const enum VideoCodecParametersTypes {
PROFILE_ID = 0x01,
LEVEL = 0x02,
@@ -221,12 +232,14 @@ const enum VideoCodecParametersTypes {
IFRAME_INTERVAL = 0x04,
}
+// eslint-disable-next-line no-restricted-syntax
const enum VideoAttributesTypes {
IMAGE_WIDTH = 0x01,
IMAGE_HEIGHT = 0x02,
FRAME_RATE = 0x03,
}
+// eslint-disable-next-line no-restricted-syntax
const enum SelectedCameraRecordingConfigurationTypes {
SELECTED_RECORDING_CONFIGURATION = 0x01,
SELECTED_VIDEO_CONFIGURATION = 0x02,
@@ -236,6 +249,7 @@ const enum SelectedCameraRecordingConfigurationTypes {
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum AudioRecordingCodecType {
AAC_LC = 0,
AAC_ELD = 1,
@@ -244,6 +258,7 @@ export const enum AudioRecordingCodecType {
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum AudioRecordingSamplerate {
KHZ_8 = 0,
KHZ_16 = 1,
@@ -253,37 +268,44 @@ export const enum AudioRecordingSamplerate {
KHZ_48 = 5,
}
+// eslint-disable-next-line no-restricted-syntax
const enum SupportedVideoRecordingConfigurationTypes {
VIDEO_CODEC_CONFIGURATION = 0x01,
}
+// eslint-disable-next-line no-restricted-syntax
const enum SupportedCameraRecordingConfigurationTypes {
PREBUFFER_LENGTH = 0x01,
EVENT_TRIGGER_OPTIONS = 0x02,
- MEDIA_CONTAINER_CONFIGURATIONS = 0x03
+ MEDIA_CONTAINER_CONFIGURATIONS = 0x03,
}
+// eslint-disable-next-line no-restricted-syntax
const enum MediaContainerConfigurationTypes {
MEDIA_CONTAINER_TYPE = 0x01,
MEDIA_CONTAINER_PARAMETERS = 0x02,
}
+// eslint-disable-next-line no-restricted-syntax
const enum MediaContainerParameterTypes {
FRAGMENT_LENGTH = 0x01,
}
+// eslint-disable-next-line no-restricted-syntax
const enum AudioCodecParametersTypes {
CHANNEL = 0x01,
BIT_RATE = 0x02,
SAMPLE_RATE = 0x03,
- MAX_AUDIO_BITRATE = 0x04 // only present in selected audio codec parameters tlv
+ MAX_AUDIO_BITRATE = 0x04, // only present in selected audio codec parameters tlv
}
+// eslint-disable-next-line no-restricted-syntax
const enum AudioCodecConfigurationTypes {
CODEC_TYPE = 0x01,
CODEC_PARAMETERS = 0x02,
}
+// eslint-disable-next-line no-restricted-syntax
const enum SupportedAudioRecordingConfigurationTypes {
AUDIO_CODEC_CONFIGURATION = 0x01,
}
@@ -291,30 +313,31 @@ const enum SupportedAudioRecordingConfigurationTypes {
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum PacketDataType {
/**
* mp4 moov box
*/
- MEDIA_INITIALIZATION = "mediaInitialization",
+ MEDIA_INITIALIZATION = 'mediaInitialization',
/**
* mp4 moof + mdat boxes
*/
- MEDIA_FRAGMENT = "mediaFragment",
+ MEDIA_FRAGMENT = 'mediaFragment',
}
interface DataSendDataEvent {
- streamId: number;
+ streamId: number
packets: {
- data: Buffer;
+ data: Buffer
metadata: {
- dataType: PacketDataType,
- dataSequenceNumber: number,
- isLastDataChunk: boolean,
- dataChunkSequenceNumber: number,
- dataTotalSize?: number,
+ dataType: PacketDataType
+ dataSequenceNumber: number
+ isLastDataChunk: boolean
+ dataChunkSequenceNumber: number
+ dataTotalSize?: number
}
- }[];
- endOfStream?: boolean;
+ }[]
+ endOfStream?: boolean
}
/**
@@ -324,22 +347,21 @@ export interface RecordingPacket {
/**
* The `Buffer` containing the data of the packet.
*/
- data: Buffer;
+ data: Buffer
/**
* Defines if this `RecordingPacket` is the last one in the recording stream.
* If `true` this will signal an end of stream and closes the recording stream.
*/
- isLast: boolean;
+ isLast: boolean
}
-
/**
* @group Camera
*/
export interface RecordingManagementServices {
- recordingManagement: CameraRecordingManagement;
- operatingMode: CameraOperatingMode;
- dataStreamManagement: DataStreamManagement;
+ recordingManagement: CameraRecordingManagement
+ operatingMode: CameraOperatingMode
+ dataStreamManagement: DataStreamManagement
}
/**
@@ -353,76 +375,76 @@ export interface RecordingManagementState {
* that they might reconsider their decision based on the updated configuration.
*/
configurationHash: {
- algorithm: "sha256";
- hash: string;
- };
+ algorithm: 'sha256'
+ hash: string
+ }
/**
* The base64 encoded tlv of the {@link CameraRecordingConfiguration}.
* This value MIGHT be `undefined` if no HomeKit controller has yet selected a configuration.
*/
- selectedConfiguration?: string;
+ selectedConfiguration?: string
/**
* Service `CameraRecordingManagement`; Characteristic `Active`
*/
- recordingActive: boolean;
+ recordingActive: boolean
/**
* Service `CameraRecordingManagement`; Characteristic `RecordingAudioActive`
*/
- recordingAudioActive: boolean;
+ recordingAudioActive: boolean
/**
* Service `CameraOperatingMode`; Characteristic `EventSnapshotsActive`
*/
- eventSnapshotsActive: boolean;
+ eventSnapshotsActive: boolean
/**
* Service `CameraOperatingMode`; Characteristic `HomeKitCameraActive`
*/
- homeKitCameraActive: boolean;
+ homeKitCameraActive: boolean
/**
* Service `CameraOperatingMode`; Characteristic `PeriodicSnapshotsActive`
*/
- periodicSnapshotsActive: boolean;
+ periodicSnapshotsActive: boolean
}
/**
* @group Camera
*/
export class RecordingManagement {
- readonly options: CameraRecordingOptions;
- readonly delegate: CameraRecordingDelegate;
+ readonly options: CameraRecordingOptions
+ readonly delegate: CameraRecordingDelegate
- private stateChangeDelegate?: StateChangeDelegate;
+ private stateChangeDelegate?: StateChangeDelegate
- private readonly supportedCameraRecordingConfiguration: string;
- private readonly supportedVideoRecordingConfiguration: string;
- private readonly supportedAudioRecordingConfiguration: string;
+ private readonly supportedCameraRecordingConfiguration: string
+ private readonly supportedVideoRecordingConfiguration: string
+ private readonly supportedAudioRecordingConfiguration: string
/**
* 32 bit mask of enabled {@link EventTriggerOption}s.
*/
- private readonly eventTriggerOptions: number;
+ private readonly eventTriggerOptions: number
- readonly recordingManagementService: CameraRecordingManagement;
- readonly operatingModeService: CameraOperatingMode;
- readonly dataStreamManagement: DataStreamManagement;
+ readonly recordingManagementService: CameraRecordingManagement
+ readonly operatingModeService: CameraOperatingMode
+ readonly dataStreamManagement: DataStreamManagement
/**
* The currently active recording stream.
* Any camera only supports one stream at a time.
*/
- private recordingStream?: CameraRecordingStream;
+ private recordingStream?: CameraRecordingStream
private selectedConfiguration?: {
/**
* The parsed configuration structure.
*/
- parsed: CameraRecordingConfiguration,
+ parsed: CameraRecordingConfiguration
/**
* The rawValue representation. TLV8 data encoded as base64 string.
*/
- base64: string,
- };
+ base64: string
+ }
/**
* Array of sensor services (e.g. {@link Service.MotionSensor} or {@link Service.OccupancySensor}).
@@ -430,12 +452,12 @@ export class RecordingManagement {
* The value of the {@link Characteristic.HomeKitCameraActive} is mirrored towards the {@link Characteristic.StatusActive} characteristic.
* The array is initialized my the caller shortly after calling the constructor.
*/
- sensorServices: Service[] = [];
+ sensorServices: Service[] = []
/**
* Defines if recording is enabled for this recording management.
*/
- private recordingActive = false;
+ private recordingActive = false
constructor(
options: CameraRecordingOptions,
@@ -443,232 +465,230 @@ export class RecordingManagement {
eventTriggerOptions: Set,
services?: RecordingManagementServices,
) {
- this.options = options;
- this.delegate = delegate;
+ this.options = options
+ this.delegate = delegate
- const recordingServices = services || this.constructService();
- this.recordingManagementService = recordingServices.recordingManagement;
- this.operatingModeService = recordingServices.operatingMode;
- this.dataStreamManagement = recordingServices.dataStreamManagement;
+ const recordingServices = services || this.constructService()
+ this.recordingManagementService = recordingServices.recordingManagement
+ this.operatingModeService = recordingServices.operatingMode
+ this.dataStreamManagement = recordingServices.dataStreamManagement
- this.eventTriggerOptions = 0;
+ this.eventTriggerOptions = 0
for (const option of eventTriggerOptions) {
- this.eventTriggerOptions |= option; // OR
+ this.eventTriggerOptions |= option // OR
}
- this.supportedCameraRecordingConfiguration = this._supportedCameraRecordingConfiguration(options);
- this.supportedVideoRecordingConfiguration = this._supportedVideoRecordingConfiguration(options.video);
- this.supportedAudioRecordingConfiguration = this._supportedAudioStreamConfiguration(options.audio);
+ this.supportedCameraRecordingConfiguration = this._supportedCameraRecordingConfiguration(options)
+ this.supportedVideoRecordingConfiguration = this._supportedVideoRecordingConfiguration(options.video)
+ this.supportedAudioRecordingConfiguration = this._supportedAudioStreamConfiguration(options.audio)
- this.setupServiceHandlers();
+ this.setupServiceHandlers()
}
private constructService(): RecordingManagementServices {
- const recordingManagement = new Service.CameraRecordingManagement("", "");
- recordingManagement.setCharacteristic(Characteristic.Active, false);
- recordingManagement.setCharacteristic(Characteristic.RecordingAudioActive, false);
+ const recordingManagement = new Service.CameraRecordingManagement('', '')
+ recordingManagement.setCharacteristic(Characteristic.Active, false)
+ recordingManagement.setCharacteristic(Characteristic.RecordingAudioActive, false)
- const operatingMode = new Service.CameraOperatingMode("", "");
- operatingMode.setCharacteristic(Characteristic.EventSnapshotsActive, true);
- operatingMode.setCharacteristic(Characteristic.HomeKitCameraActive, true);
- operatingMode.setCharacteristic(Characteristic.PeriodicSnapshotsActive, true);
+ const operatingMode = new Service.CameraOperatingMode('', '')
+ operatingMode.setCharacteristic(Characteristic.EventSnapshotsActive, true)
+ operatingMode.setCharacteristic(Characteristic.HomeKitCameraActive, true)
+ operatingMode.setCharacteristic(Characteristic.PeriodicSnapshotsActive, true)
- const dataStreamManagement = new DataStreamManagement();
- recordingManagement.addLinkedService(dataStreamManagement.getService());
+ const dataStreamManagement = new DataStreamManagement()
+ recordingManagement.addLinkedService(dataStreamManagement.getService())
return {
- recordingManagement: recordingManagement,
- operatingMode: operatingMode,
- dataStreamManagement: dataStreamManagement,
- };
+ recordingManagement,
+ operatingMode,
+ dataStreamManagement,
+ }
}
private setupServiceHandlers() {
// update the current configuration values to the current state.
- this.recordingManagementService.setCharacteristic(Characteristic.SupportedCameraRecordingConfiguration, this.supportedCameraRecordingConfiguration);
- this.recordingManagementService.setCharacteristic(Characteristic.SupportedVideoRecordingConfiguration, this.supportedVideoRecordingConfiguration);
- this.recordingManagementService.setCharacteristic(Characteristic.SupportedAudioRecordingConfiguration, this.supportedAudioRecordingConfiguration);
+ this.recordingManagementService.setCharacteristic(Characteristic.SupportedCameraRecordingConfiguration, this.supportedCameraRecordingConfiguration)
+ this.recordingManagementService.setCharacteristic(Characteristic.SupportedVideoRecordingConfiguration, this.supportedVideoRecordingConfiguration)
+ this.recordingManagementService.setCharacteristic(Characteristic.SupportedAudioRecordingConfiguration, this.supportedAudioRecordingConfiguration)
this.recordingManagementService.getCharacteristic(Characteristic.SelectedCameraRecordingConfiguration)
.onGet(this.handleSelectedCameraRecordingConfigurationRead.bind(this))
.onSet(this.handleSelectedCameraRecordingConfigurationWrite.bind(this))
- .setProps({ adminOnlyAccess: [Access.WRITE] });
+ .setProps({ adminOnlyAccess: [Access.WRITE] })
this.recordingManagementService.getCharacteristic(Characteristic.Active)
- .onSet(value => {
+ .onSet((value) => {
if (!!value === this.recordingActive) {
- return; // skip delegate call if state didn't change!
+ return // skip delegate call if state didn't change!
}
- this.recordingActive = !!value;
- this.delegate.updateRecordingActive(this.recordingActive);
+ this.recordingActive = !!value
+ this.delegate.updateRecordingActive(this.recordingActive)
})
.on(CharacteristicEventTypes.CHANGE, () => this.stateChangeDelegate?.())
- .setProps({ adminOnlyAccess: [Access.WRITE] });
+ .setProps({ adminOnlyAccess: [Access.WRITE] })
this.recordingManagementService.getCharacteristic(Characteristic.RecordingAudioActive)
- .on(CharacteristicEventTypes.CHANGE, () => this.stateChangeDelegate?.());
+ .on(CharacteristicEventTypes.CHANGE, () => this.stateChangeDelegate?.())
this.operatingModeService.getCharacteristic(Characteristic.HomeKitCameraActive)
- .on(CharacteristicEventTypes.CHANGE, change => {
+ .on(CharacteristicEventTypes.CHANGE, (change) => {
for (const service of this.sensorServices) {
- service.setCharacteristic(Characteristic.StatusActive, !!change.newValue);
+ service.setCharacteristic(Characteristic.StatusActive, !!change.newValue)
}
if (!change.newValue && this.recordingStream) {
- this.recordingStream.close(HDSProtocolSpecificErrorReason.NOT_ALLOWED);
+ this.recordingStream.close(HDSProtocolSpecificErrorReason.NOT_ALLOWED)
}
- this.stateChangeDelegate?.();
+ this.stateChangeDelegate?.()
})
- .setProps({ adminOnlyAccess: [Access.WRITE] });
+ .setProps({ adminOnlyAccess: [Access.WRITE] })
this.operatingModeService.getCharacteristic(Characteristic.EventSnapshotsActive)
.on(CharacteristicEventTypes.CHANGE, () => this.stateChangeDelegate?.())
- .setProps({ adminOnlyAccess: [Access.WRITE] });
+ .setProps({ adminOnlyAccess: [Access.WRITE] })
this.operatingModeService.getCharacteristic(Characteristic.PeriodicSnapshotsActive)
.on(CharacteristicEventTypes.CHANGE, () => this.stateChangeDelegate?.())
- .setProps({ adminOnlyAccess: [Access.WRITE] });
+ .setProps({ adminOnlyAccess: [Access.WRITE] })
this.dataStreamManagement
- .onRequestMessage(Protocols.DATA_SEND, Topics.OPEN, this.handleDataSendOpen.bind(this));
+ .onRequestMessage(Protocols.DATA_SEND, Topics.OPEN, this.handleDataSendOpen.bind(this))
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
private handleDataSendOpen(connection: DataStreamConnection, id: number, message: Record) {
// for message fields see https://github.com/Supereg/secure-video-specification#41-start
- const streamId: number = message.streamId;
- const type: string = message.type;
- const target: string = message.target;
- const reason: string = message.reason;
-
- if (target !== "controller" || type !== "ipcamera.recording") {
- debug("[HDS %s] Received data send with unexpected target: %s or type: %d. Rejecting...",
- connection.remoteAddress, target, type);
+ const streamId: number = message.streamId
+ const type: string = message.type
+ const target: string = message.target
+ const reason: string = message.reason
+
+ if (target !== 'controller' || type !== 'ipcamera.recording') {
+ debug('[HDS %s] Received data send with unexpected target: %s or type: %d. Rejecting...', connection.remoteAddress, target, type)
connection.sendResponse(Protocols.DATA_SEND, Topics.OPEN, id, HDSStatus.PROTOCOL_SPECIFIC_ERROR, {
status: HDSProtocolSpecificErrorReason.UNEXPECTED_FAILURE,
- });
- return;
+ })
+ return
}
if (!this.recordingActive) {
connection.sendResponse(Protocols.DATA_SEND, Topics.OPEN, id, HDSStatus.PROTOCOL_SPECIFIC_ERROR, {
status: HDSProtocolSpecificErrorReason.NOT_ALLOWED,
- });
- return;
+ })
+ return
}
if (!this.operatingModeService.getCharacteristic(Characteristic.HomeKitCameraActive).value) {
connection.sendResponse(Protocols.DATA_SEND, Topics.OPEN, id, HDSStatus.PROTOCOL_SPECIFIC_ERROR, {
status: HDSProtocolSpecificErrorReason.NOT_ALLOWED,
- });
- return;
+ })
+ return
}
if (this.recordingStream) {
- debug("[HDS %s] Rejecting DATA_SEND OPEN as another stream (%s) is already recording with streamId %d!",
- connection.remoteAddress, this.recordingStream.connection.remoteAddress, this.recordingStream.streamId);
+ debug('[HDS %s] Rejecting DATA_SEND OPEN as another stream (%s) is already recording with streamId %d!', connection.remoteAddress, this.recordingStream.connection.remoteAddress, this.recordingStream.streamId)
// there is already a recording stream running.
connection.sendResponse(Protocols.DATA_SEND, Topics.OPEN, id, HDSStatus.PROTOCOL_SPECIFIC_ERROR, {
status: HDSProtocolSpecificErrorReason.BUSY,
- });
- return;
+ })
+ return
}
if (!this.selectedConfiguration) {
connection.sendResponse(Protocols.DATA_SEND, Topics.OPEN, id, HDSStatus.PROTOCOL_SPECIFIC_ERROR, {
status: HDSProtocolSpecificErrorReason.INVALID_CONFIGURATION,
- });
- return;
+ })
+ return
}
- debug("[HDS %s] HDS DATA_SEND Open with reason '%s'.", connection.remoteAddress, reason);
+ debug('[HDS %s] HDS DATA_SEND Open with reason \'%s\'.', connection.remoteAddress, reason)
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
- this.recordingStream = new CameraRecordingStream(connection, this.delegate, id, streamId);
+ // eslint-disable-next-line ts/no-use-before-define
+ this.recordingStream = new CameraRecordingStream(connection, this.delegate, id, streamId)
+
+ // eslint-disable-next-line ts/no-use-before-define
this.recordingStream.on(CameraRecordingStreamEvents.CLOSED, () => {
- debug("[HDS %s] Removing active recoding session from recording management!", connection.remoteAddress);
- this.recordingStream = undefined;
- });
+ debug('[HDS %s] Removing active recoding session from recording management!', connection.remoteAddress)
+ this.recordingStream = undefined
+ })
- this.recordingStream.startStreaming();
+ this.recordingStream.startStreaming()
}
private handleSelectedCameraRecordingConfigurationRead(): string {
if (!this.selectedConfiguration) {
- throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
+ throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
}
- return this.selectedConfiguration.base64;
+ return this.selectedConfiguration.base64
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
private handleSelectedCameraRecordingConfigurationWrite(value: any): void {
- const configuration = this.parseSelectedConfiguration(value);
+ const configuration = this.parseSelectedConfiguration(value)
- const changed = this.selectedConfiguration?.base64 !== value;
+ const changed = this.selectedConfiguration?.base64 !== value
this.selectedConfiguration = {
parsed: configuration,
base64: value,
- };
+ }
if (changed) {
- this.delegate.updateRecordingConfiguration(this.selectedConfiguration.parsed);
+ this.delegate.updateRecordingConfiguration(this.selectedConfiguration.parsed)
// notify controller storage about updated values!
- this.stateChangeDelegate?.();
+ this.stateChangeDelegate?.()
}
}
private parseSelectedConfiguration(value: string): CameraRecordingConfiguration {
- const decoded = tlv.decode(Buffer.from(value, "base64"));
-
- const recording = tlv.decode(decoded[SelectedCameraRecordingConfigurationTypes.SELECTED_RECORDING_CONFIGURATION]);
- const video = tlv.decode(decoded[SelectedCameraRecordingConfigurationTypes.SELECTED_VIDEO_CONFIGURATION]);
- const audio = tlv.decode(decoded[SelectedCameraRecordingConfigurationTypes.SELECTED_AUDIO_CONFIGURATION]);
-
- const prebufferLength = recording[SupportedCameraRecordingConfigurationTypes.PREBUFFER_LENGTH].readInt32LE(0);
- let eventTriggerOptions = recording[SupportedCameraRecordingConfigurationTypes.EVENT_TRIGGER_OPTIONS].readInt32LE(0);
- const mediaContainerConfiguration = tlv.decode(recording[SupportedCameraRecordingConfigurationTypes.MEDIA_CONTAINER_CONFIGURATIONS]);
- const containerType = mediaContainerConfiguration[MediaContainerConfigurationTypes.MEDIA_CONTAINER_TYPE][0];
- const mediaContainerParameters = tlv.decode(mediaContainerConfiguration[MediaContainerConfigurationTypes.MEDIA_CONTAINER_PARAMETERS]);
- const fragmentLength = mediaContainerParameters[MediaContainerParameterTypes.FRAGMENT_LENGTH].readInt32LE(0);
-
- const videoCodec = video[VideoCodecConfigurationTypes.CODEC_TYPE][0];
- const videoParameters = tlv.decode(video[VideoCodecConfigurationTypes.CODEC_PARAMETERS]);
- const videoAttributes = tlv.decode(video[VideoCodecConfigurationTypes.ATTRIBUTES]);
-
- const profile = videoParameters[VideoCodecParametersTypes.PROFILE_ID][0];
- const level = videoParameters[VideoCodecParametersTypes.LEVEL][0];
- const videoBitrate = videoParameters[VideoCodecParametersTypes.BITRATE].readInt32LE(0);
- const iFrameInterval = videoParameters[VideoCodecParametersTypes.IFRAME_INTERVAL].readInt32LE(0);
-
- const width = videoAttributes[VideoAttributesTypes.IMAGE_WIDTH].readInt16LE(0);
- const height = videoAttributes[VideoAttributesTypes.IMAGE_HEIGHT].readInt16LE(0);
- const framerate = videoAttributes[VideoAttributesTypes.FRAME_RATE][0];
-
- const audioCodec = audio[AudioCodecConfigurationTypes.CODEC_TYPE][0];
- const audioParameters = tlv.decode(audio[AudioCodecConfigurationTypes.CODEC_PARAMETERS]);
-
- const audioChannels = audioParameters[AudioCodecParametersTypes.CHANNEL][0];
- const samplerate = audioParameters[AudioCodecParametersTypes.SAMPLE_RATE][0];
- const audioBitrateMode = audioParameters[AudioCodecParametersTypes.BIT_RATE][0];
- const audioBitrate = audioParameters[AudioCodecParametersTypes.MAX_AUDIO_BITRATE].readUInt32LE(0);
-
- const typedEventTriggers: EventTriggerOption[] = [];
- let bit_index = 0;
+ const decoded = decode(Buffer.from(value, 'base64'))
+
+ const recording = decode(decoded[SelectedCameraRecordingConfigurationTypes.SELECTED_RECORDING_CONFIGURATION])
+ const video = decode(decoded[SelectedCameraRecordingConfigurationTypes.SELECTED_VIDEO_CONFIGURATION])
+ const audio = decode(decoded[SelectedCameraRecordingConfigurationTypes.SELECTED_AUDIO_CONFIGURATION])
+
+ const prebufferLength = recording[SupportedCameraRecordingConfigurationTypes.PREBUFFER_LENGTH].readInt32LE(0)
+ let eventTriggerOptions = recording[SupportedCameraRecordingConfigurationTypes.EVENT_TRIGGER_OPTIONS].readInt32LE(0)
+ const mediaContainerConfiguration = decode(recording[SupportedCameraRecordingConfigurationTypes.MEDIA_CONTAINER_CONFIGURATIONS])
+ const containerType = mediaContainerConfiguration[MediaContainerConfigurationTypes.MEDIA_CONTAINER_TYPE][0]
+ const mediaContainerParameters = decode(mediaContainerConfiguration[MediaContainerConfigurationTypes.MEDIA_CONTAINER_PARAMETERS])
+ const fragmentLength = mediaContainerParameters[MediaContainerParameterTypes.FRAGMENT_LENGTH].readInt32LE(0)
+
+ const videoCodec = video[VideoCodecConfigurationTypes.CODEC_TYPE][0]
+ const videoParameters = decode(video[VideoCodecConfigurationTypes.CODEC_PARAMETERS])
+ const videoAttributes = decode(video[VideoCodecConfigurationTypes.ATTRIBUTES])
+
+ const profile = videoParameters[VideoCodecParametersTypes.PROFILE_ID][0]
+ const level = videoParameters[VideoCodecParametersTypes.LEVEL][0]
+ const videoBitrate = videoParameters[VideoCodecParametersTypes.BITRATE].readInt32LE(0)
+ const iFrameInterval = videoParameters[VideoCodecParametersTypes.IFRAME_INTERVAL].readInt32LE(0)
+
+ const width = videoAttributes[VideoAttributesTypes.IMAGE_WIDTH].readInt16LE(0)
+ const height = videoAttributes[VideoAttributesTypes.IMAGE_HEIGHT].readInt16LE(0)
+ const framerate = videoAttributes[VideoAttributesTypes.FRAME_RATE][0]
+
+ const audioCodec = audio[AudioCodecConfigurationTypes.CODEC_TYPE][0]
+ const audioParameters = decode(audio[AudioCodecConfigurationTypes.CODEC_PARAMETERS])
+
+ const audioChannels = audioParameters[AudioCodecParametersTypes.CHANNEL][0]
+ const samplerate = audioParameters[AudioCodecParametersTypes.SAMPLE_RATE][0]
+ const audioBitrateMode = audioParameters[AudioCodecParametersTypes.BIT_RATE][0]
+ const audioBitrate = audioParameters[AudioCodecParametersTypes.MAX_AUDIO_BITRATE].readUInt32LE(0)
+
+ const typedEventTriggers: EventTriggerOption[] = []
+ let bitIndex = 0
while (eventTriggerOptions > 0) {
- if (eventTriggerOptions & 0x01) { // of the lowest bit is set add the next event trigger option
- typedEventTriggers.push(1 << bit_index);
+ if (eventTriggerOptions % 2 === 1) { // check if the lowest bit is set
+ typedEventTriggers.push(1 << bitIndex)
}
- eventTriggerOptions = eventTriggerOptions >> 1; // shift to right till we reach zero.
- bit_index += 1; // count our current bit index
+ eventTriggerOptions = Math.floor(eventTriggerOptions / 2) // shift right by dividing by 2
+ bitIndex += 1
}
return {
- prebufferLength: prebufferLength,
+ prebufferLength,
eventTriggerTypes: typedEventTriggers,
mediaContainerConfiguration: {
type: containerType,
@@ -677,10 +697,10 @@ export class RecordingManagement {
videoCodec: {
type: videoCodec,
parameters: {
- profile: profile,
- level: level,
+ profile,
+ level,
bitRate: videoBitrate,
- iFrameInterval: iFrameInterval,
+ iFrameInterval,
},
resolution: [width, height, framerate],
},
@@ -691,121 +711,142 @@ export class RecordingManagement {
bitrateMode: audioBitrateMode,
bitrate: audioBitrate,
},
- };
+ }
}
private _supportedCameraRecordingConfiguration(options: CameraRecordingOptions): string {
const mediaContainers = Array.isArray(options.mediaContainerConfiguration)
? options.mediaContainerConfiguration
- : [options.mediaContainerConfiguration];
-
- const prebufferLength = Buffer.alloc(4);
- const eventTriggerOptions = Buffer.alloc(8);
-
- prebufferLength.writeInt32LE(options.prebufferLength, 0);
- eventTriggerOptions.writeInt32LE(this.eventTriggerOptions, 0);
-
- return tlv.encode(
- SupportedCameraRecordingConfigurationTypes.PREBUFFER_LENGTH, prebufferLength,
- SupportedCameraRecordingConfigurationTypes.EVENT_TRIGGER_OPTIONS, eventTriggerOptions,
- SupportedCameraRecordingConfigurationTypes.MEDIA_CONTAINER_CONFIGURATIONS, mediaContainers.map(config => {
- const fragmentLength = Buffer.alloc(4);
-
- fragmentLength.writeInt32LE(config.fragmentLength, 0);
-
- return tlv.encode(
- MediaContainerConfigurationTypes.MEDIA_CONTAINER_TYPE, config.type,
- MediaContainerConfigurationTypes.MEDIA_CONTAINER_PARAMETERS, tlv.encode(
- MediaContainerParameterTypes.FRAGMENT_LENGTH, fragmentLength,
+ : [options.mediaContainerConfiguration]
+
+ const prebufferLength = Buffer.alloc(4)
+ const eventTriggerOptions = Buffer.alloc(8)
+
+ prebufferLength.writeInt32LE(options.prebufferLength, 0)
+ eventTriggerOptions.writeInt32LE(this.eventTriggerOptions, 0)
+
+ return encode(
+ SupportedCameraRecordingConfigurationTypes.PREBUFFER_LENGTH,
+ prebufferLength,
+ SupportedCameraRecordingConfigurationTypes.EVENT_TRIGGER_OPTIONS,
+ eventTriggerOptions,
+ SupportedCameraRecordingConfigurationTypes.MEDIA_CONTAINER_CONFIGURATIONS,
+ mediaContainers.map((config) => {
+ const fragmentLength = Buffer.alloc(4)
+
+ fragmentLength.writeInt32LE(config.fragmentLength, 0)
+
+ return encode(
+ MediaContainerConfigurationTypes.MEDIA_CONTAINER_TYPE,
+ config.type,
+ MediaContainerConfigurationTypes.MEDIA_CONTAINER_PARAMETERS,
+ encode(
+ MediaContainerParameterTypes.FRAGMENT_LENGTH,
+ fragmentLength,
),
- );
+ )
}),
- ).toString("base64");
+ ).toString('base64')
}
private _supportedVideoRecordingConfiguration(videoOptions: VideoRecordingOptions): string {
if (!videoOptions.parameters) {
- throw new Error("Video parameters cannot be undefined");
+ throw new Error('Video parameters cannot be undefined')
}
if (!videoOptions.resolutions) {
- throw new Error("Video resolutions cannot be undefined");
+ throw new Error('Video resolutions cannot be undefined')
}
- const codecParameters = tlv.encode(
- VideoCodecParametersTypes.PROFILE_ID, videoOptions.parameters.profiles,
- VideoCodecParametersTypes.LEVEL, videoOptions.parameters.levels,
- );
-
- const videoStreamConfiguration = tlv.encode(
- VideoCodecConfigurationTypes.CODEC_TYPE, videoOptions.type,
- VideoCodecConfigurationTypes.CODEC_PARAMETERS, codecParameters,
- VideoCodecConfigurationTypes.ATTRIBUTES, videoOptions.resolutions.map(resolution => {
+ const codecParameters = encode(
+ VideoCodecParametersTypes.PROFILE_ID,
+ videoOptions.parameters.profiles,
+ VideoCodecParametersTypes.LEVEL,
+ videoOptions.parameters.levels,
+ )
+
+ const videoStreamConfiguration = encode(
+ VideoCodecConfigurationTypes.CODEC_TYPE,
+ videoOptions.type,
+ VideoCodecConfigurationTypes.CODEC_PARAMETERS,
+ codecParameters,
+ VideoCodecConfigurationTypes.ATTRIBUTES,
+ videoOptions.resolutions.map((resolution) => {
if (resolution.length !== 3) {
- throw new Error("Unexpected video resolution");
+ throw new Error('Unexpected video resolution')
}
- const width = Buffer.alloc(2);
- const height = Buffer.alloc(2);
- const frameRate = Buffer.alloc(1);
-
- width.writeUInt16LE(resolution[0], 0);
- height.writeUInt16LE(resolution[1], 0);
- frameRate.writeUInt8(resolution[2], 0);
-
- return tlv.encode(
- VideoAttributesTypes.IMAGE_WIDTH, width,
- VideoAttributesTypes.IMAGE_HEIGHT, height,
- VideoAttributesTypes.FRAME_RATE, frameRate,
- );
+ const width = Buffer.alloc(2)
+ const height = Buffer.alloc(2)
+ const frameRate = Buffer.alloc(1)
+
+ width.writeUInt16LE(resolution[0], 0)
+ height.writeUInt16LE(resolution[1], 0)
+ frameRate.writeUInt8(resolution[2], 0)
+
+ return encode(
+ VideoAttributesTypes.IMAGE_WIDTH,
+ width,
+ VideoAttributesTypes.IMAGE_HEIGHT,
+ height,
+ VideoAttributesTypes.FRAME_RATE,
+ frameRate,
+ )
}),
- );
+ )
- return tlv.encode(
- SupportedVideoRecordingConfigurationTypes.VIDEO_CODEC_CONFIGURATION, videoStreamConfiguration,
- ).toString("base64");
+ return encode(
+ SupportedVideoRecordingConfigurationTypes.VIDEO_CODEC_CONFIGURATION,
+ videoStreamConfiguration,
+ ).toString('base64')
}
private _supportedAudioStreamConfiguration(audioOptions: AudioRecordingOptions): string {
const audioCodecs = Array.isArray(audioOptions.codecs)
? audioOptions.codecs
- : [audioOptions.codecs];
+ : [audioOptions.codecs]
if (audioCodecs.length === 0) {
- throw Error("CameraRecordingOptions.audio: At least one audio codec configuration must be specified!");
+ throw new Error('CameraRecordingOptions.audio: At least one audio codec configuration must be specified!')
}
- const codecConfigurations: Buffer[] = audioCodecs.map(codec => {
+ const codecConfigurations: Buffer[] = audioCodecs.map((codec) => {
const providedSamplerates = Array.isArray(codec.samplerate)
? codec.samplerate
- : [codec.samplerate];
+ : [codec.samplerate]
if (providedSamplerates.length === 0) {
- throw new Error("CameraRecordingOptions.audio.codecs: Audio samplerate cannot be empty!");
+ throw new Error('CameraRecordingOptions.audio.codecs: Audio samplerate cannot be empty!')
}
- const audioParameters = tlv.encode(
- AudioCodecParametersTypes.CHANNEL, Math.max(1, codec.audioChannels || 1),
- AudioCodecParametersTypes.BIT_RATE, codec.bitrateMode || AudioBitrate.VARIABLE,
- AudioCodecParametersTypes.SAMPLE_RATE, providedSamplerates,
- );
-
- return tlv.encode(
- AudioCodecConfigurationTypes.CODEC_TYPE, codec.type,
- AudioCodecConfigurationTypes.CODEC_PARAMETERS, audioParameters,
- );
- });
-
- return tlv.encode(
- SupportedAudioRecordingConfigurationTypes.AUDIO_CODEC_CONFIGURATION, codecConfigurations,
- ).toString("base64");
+ const audioParameters = encode(
+ AudioCodecParametersTypes.CHANNEL,
+ Math.max(1, codec.audioChannels || 1),
+ AudioCodecParametersTypes.BIT_RATE,
+ codec.bitrateMode || AudioBitrate.VARIABLE,
+ AudioCodecParametersTypes.SAMPLE_RATE,
+ providedSamplerates,
+ )
+
+ return encode(
+ AudioCodecConfigurationTypes.CODEC_TYPE,
+ codec.type,
+ AudioCodecConfigurationTypes.CODEC_PARAMETERS,
+ audioParameters,
+ )
+ })
+
+ return encode(
+ SupportedAudioRecordingConfigurationTypes.AUDIO_CODEC_CONFIGURATION,
+ codecConfigurations,
+ ).toString('base64')
}
- private computeConfigurationHash(algorithm = "sha256"): string {
- const configurationHash = crypto.createHash(algorithm);
- configurationHash.update(this.supportedCameraRecordingConfiguration);
- configurationHash.update(this.supportedVideoRecordingConfiguration);
- configurationHash.update(this.supportedAudioRecordingConfiguration);
- return configurationHash.digest().toString("hex");
+ private computeConfigurationHash(algorithm = 'sha256'): string {
+ const configurationHash = createHash(algorithm)
+ configurationHash.update(this.supportedCameraRecordingConfiguration)
+ configurationHash.update(this.supportedVideoRecordingConfiguration)
+ configurationHash.update(this.supportedAudioRecordingConfiguration)
+ return configurationHash.digest().toString('hex')
}
/**
@@ -814,8 +855,8 @@ export class RecordingManagement {
serialize(): RecordingManagementState | undefined {
return {
configurationHash: {
- algorithm: "sha256",
- hash: this.computeConfigurationHash("sha256"),
+ algorithm: 'sha256',
+ hash: this.computeConfigurationHash('sha256'),
},
selectedConfiguration: this.selectedConfiguration?.base64,
@@ -825,53 +866,53 @@ export class RecordingManagement {
eventSnapshotsActive: !!this.operatingModeService.getCharacteristic(Characteristic.EventSnapshotsActive).value,
homeKitCameraActive: !!this.operatingModeService.getCharacteristic(Characteristic.HomeKitCameraActive).value,
periodicSnapshotsActive: !!this.operatingModeService.getCharacteristic(Characteristic.PeriodicSnapshotsActive).value,
- };
+ }
}
/**
* @private
*/
deserialize(serialized: RecordingManagementState): void {
- let changedState = false;
+ let changedState = false
// we only restore the `selectedConfiguration` if our supported configuration hasn't changed.
- const currentConfigurationHash = this.computeConfigurationHash(serialized.configurationHash.algorithm);
+ const currentConfigurationHash = this.computeConfigurationHash(serialized.configurationHash.algorithm)
if (serialized.selectedConfiguration) {
if (currentConfigurationHash === serialized.configurationHash.hash) {
this.selectedConfiguration = {
base64: serialized.selectedConfiguration,
parsed: this.parseSelectedConfiguration(serialized.selectedConfiguration),
- };
+ }
} else {
- changedState = true;
+ changedState = true
}
}
- this.recordingActive = serialized.recordingActive;
- this.recordingManagementService.updateCharacteristic(Characteristic.Active, serialized.recordingActive);
- this.recordingManagementService.updateCharacteristic(Characteristic.RecordingAudioActive, serialized.recordingAudioActive);
+ this.recordingActive = serialized.recordingActive
+ this.recordingManagementService.updateCharacteristic(Characteristic.Active, serialized.recordingActive)
+ this.recordingManagementService.updateCharacteristic(Characteristic.RecordingAudioActive, serialized.recordingAudioActive)
- this.operatingModeService.updateCharacteristic(Characteristic.EventSnapshotsActive, serialized.eventSnapshotsActive);
- this.operatingModeService.updateCharacteristic(Characteristic.PeriodicSnapshotsActive, serialized.periodicSnapshotsActive);
+ this.operatingModeService.updateCharacteristic(Characteristic.EventSnapshotsActive, serialized.eventSnapshotsActive)
+ this.operatingModeService.updateCharacteristic(Characteristic.PeriodicSnapshotsActive, serialized.periodicSnapshotsActive)
- this.operatingModeService.updateCharacteristic(Characteristic.HomeKitCameraActive, serialized.homeKitCameraActive);
+ this.operatingModeService.updateCharacteristic(Characteristic.HomeKitCameraActive, serialized.homeKitCameraActive)
for (const service of this.sensorServices) {
- service.setCharacteristic(Characteristic.StatusActive, serialized.homeKitCameraActive);
+ service.setCharacteristic(Characteristic.StatusActive, serialized.homeKitCameraActive)
}
try {
if (this.selectedConfiguration) {
- this.delegate.updateRecordingConfiguration(this.selectedConfiguration.parsed);
+ this.delegate.updateRecordingConfiguration(this.selectedConfiguration.parsed)
}
if (serialized.recordingActive) {
- this.delegate.updateRecordingActive(serialized.recordingActive);
+ this.delegate.updateRecordingActive(serialized.recordingActive)
}
} catch (error) {
- console.error("Failed to properly initialize CameraRecordingDelegate from persistent storage: " + error.stack);
+ console.error(`Failed to properly initialize CameraRecordingDelegate from persistent storage: ${error.stack}`)
}
if (changedState) {
- this.stateChangeDelegate?.();
+ this.stateChangeDelegate?.()
}
}
@@ -879,57 +920,58 @@ export class RecordingManagement {
* @private
*/
setupStateChangeDelegate(delegate?: StateChangeDelegate): void {
- this.stateChangeDelegate = delegate;
+ this.stateChangeDelegate = delegate
}
destroy(): void {
- this.dataStreamManagement.destroy();
+ this.dataStreamManagement.destroy()
}
handleFactoryReset(): void {
- this.selectedConfiguration = undefined;
- this.recordingManagementService.updateCharacteristic(Characteristic.Active, false);
- this.recordingManagementService.updateCharacteristic(Characteristic.RecordingAudioActive, false);
+ this.selectedConfiguration = undefined
+ this.recordingManagementService.updateCharacteristic(Characteristic.Active, false)
+ this.recordingManagementService.updateCharacteristic(Characteristic.RecordingAudioActive, false)
- this.operatingModeService.updateCharacteristic(Characteristic.EventSnapshotsActive, true);
- this.operatingModeService.updateCharacteristic(Characteristic.PeriodicSnapshotsActive, true);
+ this.operatingModeService.updateCharacteristic(Characteristic.EventSnapshotsActive, true)
+ this.operatingModeService.updateCharacteristic(Characteristic.PeriodicSnapshotsActive, true)
- this.operatingModeService.updateCharacteristic(Characteristic.HomeKitCameraActive, true);
+ this.operatingModeService.updateCharacteristic(Characteristic.HomeKitCameraActive, true)
for (const service of this.sensorServices) {
- service.setCharacteristic(Characteristic.StatusActive, true);
+ service.setCharacteristic(Characteristic.StatusActive, true)
}
try {
// notifying the delegate about the updated state
- this.delegate.updateRecordingActive(false);
- this.delegate.updateRecordingConfiguration(undefined);
+ this.delegate.updateRecordingActive(false)
+ this.delegate.updateRecordingConfiguration(undefined)
} catch (error) {
- console.error("CameraRecordingDelegate failed to update state after handleFactoryReset: " + error.stack);
+ console.error(`CameraRecordingDelegate failed to update state after handleFactoryReset: ${error.stack}`)
}
}
}
-
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
const enum CameraRecordingStreamEvents {
/**
* This event is fired when the recording stream is closed.
* Either due to a normal exit (e.g. the HomeKit Controller acknowledging the stream)
* or due to an erroneous exit (e.g. HDS connection getting closed).
*/
- CLOSED = "closed",
+ CLOSED = 'closed',
}
/**
* @group Camera
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
declare interface CameraRecordingStream {
- on(event: "closed", listener: () => void): this;
-
- emit(event: "closed"): boolean;
+ /* eslint-disable ts/method-signature-style */
+ on(event: 'closed', listener: () => void): this
+ emit(event: 'closed'): boolean
+ /* eslint-enable ts/method-signature-style */
}
/**
@@ -938,234 +980,231 @@ declare interface CameraRecordingStream {
*
* @group Camera
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
class CameraRecordingStream extends EventEmitter implements DataStreamProtocolHandler {
- readonly connection: DataStreamConnection;
- readonly delegate: CameraRecordingDelegate;
- readonly hdsRequestId: number;
- readonly streamId: number;
- private closed = false;
+ readonly connection: DataStreamConnection
+ readonly delegate: CameraRecordingDelegate
+ readonly hdsRequestId: number
+ readonly streamId: number
+ private closed = false
eventHandler?: Record = {
[Topics.CLOSE]: this.handleDataSendClose.bind(this),
[Topics.ACK]: this.handleDataSendAck.bind(this),
- };
- requestHandler?: Record = undefined;
+ }
+
+ requestHandler?: Record = undefined
- private readonly closeListener: () => void;
+ private readonly closeListener: () => void
- private generator?: AsyncGenerator;
+ private generator?: AsyncGenerator
/**
* This timeout is used to detect non-returning generators.
* When we signal the delegate that it is being closed its generator must return withing 10s.
*/
- private generatorTimeout?: NodeJS.Timeout;
+ private generatorTimeout?: NodeJS.Timeout
/**
* This timer is used to check if the stream is properly closed when we expect it to do so.
* When we expect a close signal from the remote, we wait 12s for it. Otherwise, we abort and close it ourselves.
* This ensures memory is freed, and that we recover fast from erroneous states.
*/
- private closingTimeout?: NodeJS.Timeout;
+ private closingTimeout?: NodeJS.Timeout
constructor(connection: DataStreamConnection, delegate: CameraRecordingDelegate, requestId: number, streamId: number) {
- super();
- this.connection = connection;
- this.delegate = delegate;
- this.hdsRequestId = requestId;
- this.streamId = streamId;
-
- this.connection.on(DataStreamConnectionEvent.CLOSED, this.closeListener = this.handleDataStreamConnectionClosed.bind(this));
- this.connection.addProtocolHandler(Protocols.DATA_SEND, this);
+ super()
+ this.connection = connection
+ this.delegate = delegate
+ this.hdsRequestId = requestId
+ this.streamId = streamId
+
+ this.connection.on(DataStreamConnectionEvent.CLOSED, this.closeListener = this.handleDataStreamConnectionClosed.bind(this))
+ this.connection.addProtocolHandler(Protocols.DATA_SEND, this)
}
startStreaming() {
- // noinspection JSIgnoredPromiseFromCall
- this._startStreaming();
+ this._startStreaming()
}
private async _startStreaming() {
- debug("[HDS %s] Sending DATA_SEND OPEN response for streamId %d", this.connection.remoteAddress, this.streamId);
+ debug('[HDS %s] Sending DATA_SEND OPEN response for streamId %d', this.connection.remoteAddress, this.streamId)
this.connection.sendResponse(Protocols.DATA_SEND, Topics.OPEN, this.hdsRequestId, HDSStatus.SUCCESS, {
status: HDSStatus.SUCCESS,
- });
+ })
// 256 KiB (1KiB to 900 KiB)
- const maxChunk = 0x40000;
+ const maxChunk = 0x40000
// The first buffer which we receive from the generator is always the `mediaInitialization` packet (mp4 `moov` box).
- let initialization = true;
- let dataSequenceNumber = 1;
+ let initialization = true
+ let dataSequenceNumber = 1
// tracks if the last received RecordingPacket was yielded with `isLast=true`.
- let lastFragmentWasMarkedLast = false;
+ let lastFragmentWasMarkedLast = false
try {
- this.generator = this.delegate.handleRecordingStreamRequest(this.streamId);
+ this.generator = this.delegate.handleRecordingStreamRequest(this.streamId)
for await (const packet of this.generator) {
if (this.closed) {
- console.error(`[HDS ${this.connection.remoteAddress}] Delegate yielded fragment after stream ${this.streamId} was already closed!`);
- break;
+ console.error(`[HDS ${this.connection.remoteAddress}] Delegate yielded fragment after stream ${this.streamId} was already closed!`)
+ break
}
if (lastFragmentWasMarkedLast) {
- console.error(`[HDS ${this.connection.remoteAddress}] Delegate yielded fragment for stream ${this.streamId} after already signaling end of stream!`);
- break;
+ console.error(`[HDS ${this.connection.remoteAddress}] Delegate yielded fragment for stream ${this.streamId} after already signaling end of stream!`)
+ break
}
- const fragment = packet.data;
+ const fragment = packet.data
- let offset = 0;
- let dataChunkSequenceNumber = 1;
+ let offset = 0
+ let dataChunkSequenceNumber = 1
while (offset < fragment.length) {
if (this.closed) {
- break;
+ break
}
- const data = fragment.slice(offset, offset + maxChunk);
- offset += data.length;
+ const data = fragment.subarray(offset, offset + maxChunk)
+ offset += data.length
// see https://github.com/Supereg/secure-video-specification#42-binary-data
const event: DataSendDataEvent = {
streamId: this.streamId,
packets: [{
- data: data,
+ data,
metadata: {
dataType: initialization ? PacketDataType.MEDIA_INITIALIZATION : PacketDataType.MEDIA_FRAGMENT,
- dataSequenceNumber: dataSequenceNumber,
- dataChunkSequenceNumber: dataChunkSequenceNumber,
+ dataSequenceNumber,
+ dataChunkSequenceNumber,
isLastDataChunk: offset >= fragment.length,
dataTotalSize: dataChunkSequenceNumber === 1 ? fragment.length : undefined,
},
}],
endOfStream: offset >= fragment.length ? Boolean(packet.isLast).valueOf() : undefined,
- };
+ }
- debug("[HDS %s] Sending DATA_SEND DATA for stream %d with metadata: %o and length %d; EoS: %s",
- this.connection.remoteAddress, this.streamId, event.packets[0].metadata, data.length, event.endOfStream);
- this.connection.sendEvent(Protocols.DATA_SEND, Topics.DATA, event);
+ debug('[HDS %s] Sending DATA_SEND DATA for stream %d with metadata: %o and length %d; EoS: %s', this.connection.remoteAddress, this.streamId, event.packets[0].metadata, data.length, event.endOfStream)
+ this.connection.sendEvent(Protocols.DATA_SEND, Topics.DATA, event)
- dataChunkSequenceNumber++;
- initialization = false;
+ dataChunkSequenceNumber++
+ initialization = false
}
- lastFragmentWasMarkedLast = packet.isLast;
+ lastFragmentWasMarkedLast = packet.isLast
if (packet.isLast) {
- break;
+ break
}
- dataSequenceNumber++;
+ dataSequenceNumber++
}
if (!lastFragmentWasMarkedLast && !this.closed) {
// Delegate violates the contract. Exited normally on a non-closed stream without properly setting `isLast`.
- console.warn(`[HDS ${this.connection.remoteAddress}] Delegate finished streaming for ${this.streamId} without setting RecordingPacket.isLast. ` +
- "Can't notify Controller about endOfStream!");
+ console.warn(`[HDS ${this.connection.remoteAddress}] Delegate finished streaming for ${this.streamId} without setting RecordingPacket.isLast. `
+ + 'Can\'t notify Controller about endOfStream!')
}
} catch (error) {
if (this.closed) {
- console.warn(`[HDS ${this.connection.remoteAddress}] Encountered unexpected error on already closed recording stream ${this.streamId}: ${error.stack}`);
+ console.warn(`[HDS ${this.connection.remoteAddress}] Encountered unexpected error on already closed recording stream ${this.streamId}: ${error.stack}`)
} else {
- let closeReason = HDSProtocolSpecificErrorReason.UNEXPECTED_FAILURE;
+ let closeReason = HDSProtocolSpecificErrorReason.UNEXPECTED_FAILURE
if (error instanceof HDSProtocolError) {
- closeReason = error.reason;
- debug("[HDS %s] Delegate signaled to close the recording stream %d.", this.connection.remoteAddress, this.streamId);
+ closeReason = error.reason
+ debug('[HDS %s] Delegate signaled to close the recording stream %d.', this.connection.remoteAddress, this.streamId)
} else if (error instanceof HDSConnectionError && error.type === HDSConnectionErrorType.CLOSED_SOCKET) {
// we are probably on a shutdown or just late. Connection is dead. End the stream!
- debug("[HDS %s] Exited recording stream due to closed HDS socket: stream id %d.", this.connection.remoteAddress, this.streamId);
- return; // execute finally and then exit (we want to skip the `sendEvent` below)
+ debug('[HDS %s] Exited recording stream due to closed HDS socket: stream id %d.', this.connection.remoteAddress, this.streamId)
+ return // execute finally and then exit (we want to skip the `sendEvent` below)
} else {
- console.error(`[HDS ${this.connection.remoteAddress}] Encountered unexpected error for recording stream ${this.streamId}: ${error.stack}`);
+ console.error(`[HDS ${this.connection.remoteAddress}] Encountered unexpected error for recording stream ${this.streamId}: ${error.stack}`)
}
// call close to go through standard close routine!
- this.close(closeReason);
+ this.close(closeReason)
}
- return;
+ return
} finally {
- this.generator = undefined;
+ this.generator = undefined
if (this.generatorTimeout) {
- clearTimeout(this.generatorTimeout);
+ clearTimeout(this.generatorTimeout)
}
if (!this.closed) {
// e.g. when returning with `endOfStream` we rely on the HomeHub to send an ACK event to close the recording.
// With this timer we ensure that the HomeHub has the chance to close the stream gracefully but at the same time
// ensure that if something fails the recording stream is freed nonetheless.
- this.kickOffCloseTimeout();
+ this.kickOffCloseTimeout()
}
}
- debug("[HDS %s] Finished DATA_SEND transmission for stream %d!", this.connection.remoteAddress, this.streamId);
+ debug('[HDS %s] Finished DATA_SEND transmission for stream %d!', this.connection.remoteAddress, this.streamId)
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
private handleDataSendAck(message: Record) {
- const streamId: string = message.streamId;
- const endOfStream: boolean = message.endOfStream;
+ const streamId: string = message.streamId
+ const endOfStream: boolean = message.endOfStream
// The HomeKit Controller will send a DATA_SEND ACK if we set the `endOfStream` flag in the last packet
// of our DATA_SEND DATA packet.
// To my testing the session is then considered complete and the HomeKit controller will close the HDS Connection after 5 seconds.
- debug("[HDS %s] Received DATA_SEND ACK packet for streamId %s. Acknowledged %s.", this.connection.remoteAddress, streamId, endOfStream);
+ debug('[HDS %s] Received DATA_SEND ACK packet for streamId %s. Acknowledged %s.', this.connection.remoteAddress, streamId, endOfStream)
- this.handleClosed(() => this.delegate.acknowledgeStream?.(this.streamId));
+ this.handleClosed(() => this.delegate.acknowledgeStream?.(this.streamId))
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
private handleDataSendClose(message: Record) {
// see https://github.com/Supereg/secure-video-specification#43-close
- const streamId: number = message.streamId;
- const reason: HDSProtocolSpecificErrorReason = message.reason;
+ const streamId: number = message.streamId
+ const reason: HDSProtocolSpecificErrorReason = message.reason
if (streamId !== this.streamId) {
- return;
+ return
}
- debug("[HDS %s] Received DATA_SEND CLOSE for streamId %d with reason %s",
+ debug('[HDS %s] Received DATA_SEND CLOSE for streamId %d with reason %s',
// @ts-expect-error: forceConsistentCasingInFileNames compiler option
- this.connection.remoteAddress, streamId, HDSProtocolSpecificErrorReason[reason]);
+ this.connection.remoteAddress, streamId, HDSProtocolSpecificErrorReason[reason])
- this.handleClosed(() => this.delegate.closeRecordingStream(streamId, reason));
+ this.handleClosed(() => this.delegate.closeRecordingStream(streamId, reason))
}
private handleDataStreamConnectionClosed() {
- debug("[HDS %s] The HDS connection of the stream %d closed.", this.connection.remoteAddress, this.streamId);
+ debug('[HDS %s] The HDS connection of the stream %d closed.', this.connection.remoteAddress, this.streamId)
- this.handleClosed(() => this.delegate.closeRecordingStream(this.streamId, undefined));
+ this.handleClosed(() => this.delegate.closeRecordingStream(this.streamId, undefined))
}
private handleClosed(closure: () => void): void {
- this.closed = true;
+ this.closed = true
if (this.closingTimeout) {
- clearTimeout(this.closingTimeout);
- this.closingTimeout = undefined;
+ clearTimeout(this.closingTimeout)
+ this.closingTimeout = undefined
}
- this.connection.removeProtocolHandler(Protocols.DATA_SEND, this);
- this.connection.removeListener(DataStreamConnectionEvent.CLOSED, this.closeListener);
+ this.connection.removeProtocolHandler(Protocols.DATA_SEND, this)
+ this.connection.removeListener(DataStreamConnectionEvent.CLOSED, this.closeListener)
if (this.generator) {
// when this variable is defined, the generator hasn't returned yet.
// we start a timeout to uncover potential programming mistakes where we await forever and can't free resources.
this.generatorTimeout = setTimeout(() => {
- console.error("[HDS %s] Recording download stream %d is still awaiting generator although stream was closed 10s ago! " +
- "This is a programming mistake by the camera implementation which prevents freeing up resources.", this.connection.remoteAddress, this.streamId);
- }, 10000);
+ console.error('[HDS %s] Recording download stream %d is still awaiting generator although stream was closed 10s ago! '
+ + 'This is a programming mistake by the camera implementation which prevents freeing up resources.', this.connection.remoteAddress, this.streamId)
+ }, 10000)
}
try {
- closure();
+ closure()
} catch (error) {
- console.error(`[HDS ${this.connection.remoteAddress}] CameraRecordingDelegated failed to handle closing the stream ${this.streamId}: ${error.stack}`);
+ console.error(`[HDS ${this.connection.remoteAddress}] CameraRecordingDelegated failed to handle closing the stream ${this.streamId}: ${error.stack}`)
}
- this.emit(CameraRecordingStreamEvents.CLOSED);
+ this.emit(CameraRecordingStreamEvents.CLOSED)
}
/**
@@ -1174,36 +1213,36 @@ class CameraRecordingStream extends EventEmitter implements DataStreamProtocolHa
*/
close(reason: HDSProtocolSpecificErrorReason): void {
if (this.closed) {
- return;
+ return
}
- debug("[HDS %s] Recording stream %d was closed manually with reason %s.",
+ debug('[HDS %s] Recording stream %d was closed manually with reason %s.',
// @ts-expect-error: forceConsistentCasingInFileNames compiler option
- this.connection.remoteAddress, this.streamId, reason ? HDSProtocolSpecificErrorReason[reason] : "CLOSED");
+ this.connection.remoteAddress, this.streamId, reason ? HDSProtocolSpecificErrorReason[reason] : 'CLOSED')
// the `isConsideredClosed` check just ensures that the won't ever throw here and that `handledClosed` is always executed.
if (!this.connection.isConsideredClosed()) {
this.connection.sendEvent(Protocols.DATA_SEND, Topics.CLOSE, {
streamId: this.streamId,
- reason: reason,
- });
+ reason,
+ })
}
- this.handleClosed(() => this.delegate.closeRecordingStream(this.streamId, reason));
+ this.handleClosed(() => this.delegate.closeRecordingStream(this.streamId, reason))
}
private kickOffCloseTimeout(): void {
if (this.closingTimeout) {
- clearTimeout(this.closingTimeout);
+ clearTimeout(this.closingTimeout)
}
this.closingTimeout = setTimeout(() => {
if (this.closed) {
- return;
+ return
}
- debug("[HDS %s] Recording stream %d took longer than expected to fully close. Force closing now!", this.connection.remoteAddress, this.streamId);
- this.close(HDSProtocolSpecificErrorReason.CANCELLED);
- }, 12000);
+ debug('[HDS %s] Recording stream %d took longer than expected to fully close. Force closing now!', this.connection.remoteAddress, this.streamId)
+ this.close(HDSProtocolSpecificErrorReason.CANCELLED)
+ }, 12000)
}
}
diff --git a/src/lib/camera/index.ts b/src/lib/camera/index.ts
index 341a0ce87..320a11f5b 100644
--- a/src/lib/camera/index.ts
+++ b/src/lib/camera/index.ts
@@ -1,3 +1,3 @@
-export * from "./RTPProxy";
-export * from "./RTPStreamManagement";
-export * from "./RecordingManagement";
+export * from './RecordingManagement.js'
+export * from './RTPProxy.js'
+export * from './RTPStreamManagement.js'
diff --git a/src/lib/controller/AdaptiveLightingController.ts b/src/lib/controller/AdaptiveLightingController.ts
index a9354ee9f..0227139c0 100644
--- a/src/lib/controller/AdaptiveLightingController.ts
+++ b/src/lib/controller/AdaptiveLightingController.ts
@@ -1,20 +1,7 @@
-import assert from "assert";
-import { HAPStatus } from "../HAPServer";
-import { ColorUtils } from "../util/color-utils";
-import { HapStatusError } from "../util/hapStatusError";
-import { epochMillisFromMillisSince2001_01_01Buffer } from "../util/time";
-import * as uuid from "../util/uuid";
-import createDebug from "debug";
-import { EventEmitter } from "events";
-import { CharacteristicValue } from "../../types";
-import {
- ChangeReason,
- Characteristic,
- CharacteristicChange,
- CharacteristicEventTypes,
- CharacteristicOperationContext,
-} from "../Characteristic";
-import {
+/* global NodeJS */
+import type { CharacteristicValue } from '../../types'
+import type { CharacteristicChange, CharacteristicOperationContext } from '../Characteristic'
+import type {
Brightness,
CharacteristicValueActiveTransitionCount,
CharacteristicValueTransitionControl,
@@ -23,48 +10,74 @@ import {
Lightbulb,
Saturation,
SupportedCharacteristicValueTransitionConfiguration,
-} from "../definitions";
-import * as tlv from "../util/tlv";
-import {
+} from '../definitions'
+import type {
ControllerIdentifier,
ControllerServiceMap,
- DefaultControllerType,
SerializableController,
StateChangeDelegate,
-} from "./Controller";
+} from './Controller'
+
+import assert from 'node:assert'
+import { Buffer } from 'node:buffer'
+import { EventEmitter } from 'node:events'
-const debug = createDebug("HAP-NodeJS:Controller:TransitionControl");
+import createDebug from 'debug'
+import { ChangeReason, Characteristic, CharacteristicEventTypes } from '../Characteristic.js'
+import { HAPStatus } from '../HAPServer.js'
+import { ColorUtils } from '../util/color-utils.js'
+import { HapStatusError } from '../util/hapStatusError.js'
+import { epochMillisFromMillisSince2001_01_01Buffer } from '../util/time.js'
+import {
+ decode,
+ decodeWithLists,
+ encode,
+ readVariableUIntLE,
+ writeFloat32LE,
+ writeUInt32,
+ writeVariableUIntLE,
+} from '../util/tlv.js'
+import { unparse, write } from '../util/uuid.js'
+import { DefaultControllerType } from './Controller.js'
+
+const debug = createDebug('HAP-NodeJS:Controller:TransitionControl')
+
+// eslint-disable-next-line no-restricted-syntax
const enum SupportedCharacteristicValueTransitionConfigurationsTypes {
SUPPORTED_TRANSITION_CONFIGURATION = 0x01,
}
+// eslint-disable-next-line no-restricted-syntax
const enum SupportedValueTransitionConfigurationTypes {
CHARACTERISTIC_IID = 0x01,
TRANSITION_TYPE = 0x02, // assumption
}
+// eslint-disable-next-line no-restricted-syntax
const enum TransitionType {
BRIGHTNESS = 0x01, // uncertain
COLOR_TEMPERATURE = 0x02,
}
-
+// eslint-disable-next-line no-restricted-syntax
const enum TransitionControlTypes {
READ_CURRENT_VALUE_TRANSITION_CONFIGURATION = 0x01, // could probably a list of ValueTransitionConfigurationTypes
UPDATE_VALUE_TRANSITION_CONFIGURATION = 0x02,
}
+// eslint-disable-next-line no-restricted-syntax
const enum ReadValueTransitionConfiguration {
CHARACTERISTIC_IID = 0x01,
}
+// eslint-disable-next-line no-restricted-syntax
const enum UpdateValueTransitionConfigurationsTypes {
VALUE_TRANSITION_CONFIGURATION = 0x01, // this type could be a tlv8 list
}
+// eslint-disable-next-line no-restricted-syntax
const enum ValueTransitionConfigurationTypes {
- // noinspection JSUnusedGlobalSymbols
CHARACTERISTIC_IID = 0x01, // 1 byte
TRANSITION_PARAMETERS = 0x02,
UNKNOWN_3 = 0x03, // sent with value = 1 (1 byte)
@@ -75,18 +88,21 @@ const enum ValueTransitionConfigurationTypes {
NOTIFY_INTERVAL_THRESHOLD = 0x08, // 32 bit uint
}
+// eslint-disable-next-line no-restricted-syntax
const enum ValueTransitionParametersTypes {
TRANSITION_ID = 0x01, // 16 bytes
START_TIME = 0x02, // 8 bytes the start time for the provided schedule, millis since 2001/01/01 00:00:000
UNKNOWN_3 = 0x03, // 8 bytes, id or something (same for multiple writes)
}
+// eslint-disable-next-line no-restricted-syntax
const enum TransitionCurveConfigurationTypes {
TRANSITION_ENTRY = 0x01,
ADJUSTMENT_CHARACTERISTIC_IID = 0x02,
ADJUSTMENT_MULTIPLIER_RANGE = 0x03,
}
+// eslint-disable-next-line no-restricted-syntax
const enum TransitionEntryTypes {
ADJUSTMENT_FACTOR = 0x01,
VALUE = 0x02,
@@ -94,15 +110,18 @@ const enum TransitionEntryTypes {
DURATION = 0x04, // optional, default 0, sets how long the previous value will stay the same (non interpolation time section)
}
+// eslint-disable-next-line no-restricted-syntax
const enum TransitionAdjustmentMultiplierRange {
MINIMUM_ADJUSTMENT_MULTIPLIER = 0x01, // brightness 10
MAXIMUM_ADJUSTMENT_MULTIPLIER = 0x02, // brightness 100
}
+// eslint-disable-next-line no-restricted-syntax
const enum ValueTransitionConfigurationResponseTypes { // read format for control point
VALUE_CONFIGURATION_STATUS = 0x01,
}
+// eslint-disable-next-line no-restricted-syntax
const enum ValueTransitionConfigurationStatusTypes {
CHARACTERISTIC_IID = 0x01,
TRANSITION_PARAMETERS = 0x02,
@@ -110,17 +129,16 @@ const enum ValueTransitionConfigurationStatusTypes {
}
interface AdaptiveLightingCharacteristicContext extends CharacteristicOperationContext {
- controller: AdaptiveLightingController;
+ controller: AdaptiveLightingController
}
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isAdaptiveLightingContext(context: any): context is AdaptiveLightingCharacteristicContext {
- return context && "controller" in context;
+ return context && 'controller' in context
}
interface SavedLastTransitionPointInfo {
- curveIndex: number;
- lowerBoundTimeOffset: number;
+ curveIndex: number
+ lowerBoundTimeOffset: number
}
/**
@@ -130,56 +148,56 @@ export interface ActiveAdaptiveLightingTransition {
/**
* The instance id for the characteristic for which this transition applies to (aka the ColorTemperature characteristic).
*/
- iid: number;
+ iid: number
/**
* Start of the transition in epoch time millis (as sent from the HomeKit controller).
- * Additionally see {@link timeMillisOffset}.
+ * Additionally, see {@link timeMillisOffset}.
*/
- transitionStartMillis: number;
+ transitionStartMillis: number
/**
* It is not necessarily given, that we have the same time (or rather the correct time) as the HomeKit controller
* who set up the transition schedule.
- * Thus we record the delta between our current time and the the time sent with the setup request.
+ * Thus, we record the delta between our current time and the time sent with the setup request.
* timeMillisOffset
is defined as Date.now() - transitionStartMillis;
.
* So in the case were we actually have a correct local time, it most likely will be positive (due to network latency).
* But of course it can also be negative.
*/
- timeMillisOffset: number;
+ timeMillisOffset: number
/**
* Value is the same for ALL control write requests I have seen (even on other homes).
* @private
*/
- transitionId: string;
+ transitionId: string
/**
* Start of transition in milliseconds from 2001-01-01 00:00:00; unsigned 64 bit LE integer
- * @private as it is a 64 bit integer, we just store the buffer to not have the struggle to encode/decode 64 bit int in JavaScript
+ * @private
*/
- transitionStartBuffer: string;
+ transitionStartBuffer: string
/**
* Hex string of 8 bytes. Some kind of id (?). Sometimes it isn't supplied. Don't know the use for that.
* @private
*/
- id3?: string;
+ id3?: string
- transitionCurve: AdaptiveLightingTransitionCurveEntry[];
+ transitionCurve: AdaptiveLightingTransitionCurveEntry[]
- brightnessCharacteristicIID: number;
- brightnessAdjustmentRange: BrightnessAdjustmentMultiplierRange;
+ brightnessCharacteristicIID: number
+ brightnessAdjustmentRange: BrightnessAdjustmentMultiplierRange
/**
* Interval in milliseconds specifies how often the accessory should update the color temperature (internally).
- * Typically this is 60000 aka 60 seconds aka 1 minute.
+ * Typically, this is 60000 aka 60 seconds aka 1 minute.
* Note {@link notifyIntervalThreshold}
*/
- updateInterval: number,
+ updateInterval: number
/**
* Defines the interval in milliseconds on how often the accessory may send even notifications
- * to subscribed HomeKit controllers (aka call {@link Characteristic.updateValue}.
- * Typically this is 600000 aka 600 seconds aka 10 minutes or 300000 aka 300 seconds aka 5 minutes.
+ * to subscribed HomeKit controllers (aka call {@link Characteristic.updateValue}).
+ * Typically, this is 600000 aka 600 seconds aka 10 minutes or 300000 aka 300 seconds aka 5 minutes.
*/
- notifyIntervalThreshold: number;
+ notifyIntervalThreshold: number
}
/**
@@ -189,13 +207,12 @@ export interface AdaptiveLightingTransitionPoint {
/**
* This is the time offset from the transition start to the {@link lowerBound}.
*/
- lowerBoundTimeOffset: number;
+ lowerBoundTimeOffset: number
+ transitionOffset: number
- transitionOffset: number;
-
- lowerBound: AdaptiveLightingTransitionCurveEntry;
- upperBound: AdaptiveLightingTransitionCurveEntry;
+ lowerBound: AdaptiveLightingTransitionCurveEntry
+ upperBound: AdaptiveLightingTransitionCurveEntry
}
/**
@@ -205,13 +222,13 @@ export interface AdaptiveLightingTransitionCurveEntry {
/**
* The color temperature in mired.
*/
- temperature: number,
+ temperature: number
/**
* The color temperature actually set to the color temperature characteristic is dependent
* on the current brightness value of the lightbulb.
* This means you will always need to query the current brightness when updating the color temperature
* for the next transition step.
- * Additionally you will also need to correct the color temperature when the end user changes the
+ * Additionally, you will also need to correct the color temperature when the end user changes the
* brightness of the Lightbulb.
*
* The brightnessAdjustmentFactor is always a negative floating point value.
@@ -223,21 +240,21 @@ export interface AdaptiveLightingTransitionCurveEntry {
* Complete example:
* ```js
* const temperature = ...; // next transition value, the above property
- * // below query the current brightness while staying the the min/max brightness range (typically between 10-100 percent)
+ * // below query the current brightness while staying the min/max brightness range (typically between 10-100 percent)
* const currentBrightness = Math.max(minBrightnessValue, Math.min(maxBrightnessValue, CHARACTERISTIC_BRIGHTNESS_VALUE));
*
* // as both temperature and brightnessAdjustmentFactor are floating point values it is advised to round to the next integer
* const resultTemperature = Math.round(temperature + brightnessAdjustmentFactor * currentBrightness);
* ```
*/
- brightnessAdjustmentFactor: number;
+ brightnessAdjustmentFactor: number
/**
* The duration in milliseconds this exact temperature value stays the same.
- * When we transition to to the temperature value represented by this entry, it stays for the specified
+ * When we transition to the temperature value represented by this entry, it stays for the specified
* duration on the exact same value (with respect to brightness adjustment) until we transition
* to the next entry (see {@link transitionTime}).
*/
- duration?: number;
+ duration?: number
/**
* The time in milliseconds the color temperature should transition from the previous
* entry to this one.
@@ -247,15 +264,15 @@ export interface AdaptiveLightingTransitionCurveEntry {
* If this is the first entry in the Curve (this value is probably zero) and is the offset to the transitionStartMillis
* (the Date/Time were this transition curve was set up).
*/
- transitionTime: number;
+ transitionTime: number
}
/**
* @group Adaptive Lighting
*/
export interface BrightnessAdjustmentMultiplierRange {
- minBrightnessValue: number;
- maxBrightnessValue: number;
+ minBrightnessValue: number
+ maxBrightnessValue: number
}
/**
@@ -267,7 +284,7 @@ export interface AdaptiveLightingOptions {
* You can choose between automatic and manual mode.
* See {@link AdaptiveLightingControllerMode}.
*/
- controllerMode?: AdaptiveLightingControllerMode,
+ controllerMode?: AdaptiveLightingControllerMode
/**
* Defines a custom temperature adjustment factor.
*
@@ -277,13 +294,14 @@ export interface AdaptiveLightingOptions {
* For example supplying a value of `-10` will reduce the ColorTemperature, which is
* calculated from the transition schedule, by 10 mired for every change.
*/
- customTemperatureAdjustment?: number,
+ customTemperatureAdjustment?: number
}
/**
* Defines in which mode the {@link AdaptiveLightingController} will operate in.
* @group Adaptive Lighting
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum AdaptiveLightingControllerMode {
/**
* In automatic mode pretty much everything from setup to transition scheduling is done by the controller.
@@ -299,19 +317,20 @@ export const enum AdaptiveLightingControllerMode {
/**
* @group Adaptive Lighting
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum AdaptiveLightingControllerEvents {
/**
* This event is called once a HomeKit controller enables Adaptive Lighting
* or a HomeHub sends an updated transition schedule for the next 24 hours.
* This is also called on startup when AdaptiveLighting was previously enabled.
*/
- UPDATE = "update",
+ UPDATE = 'update',
/**
* In yet unknown circumstances HomeKit may also send a dedicated disable command
* via the control point characteristic. You may want to handle that in manual mode as well.
* The current transition will still be associated with the controller object when this event is called.
*/
- DISABLED = "disable",
+ DISABLED = 'disable',
}
/**
@@ -319,19 +338,20 @@ export const enum AdaptiveLightingControllerEvents {
* see {@link ActiveAdaptiveLightingTransition}.
*/
export interface AdaptiveLightingControllerUpdate {
- transitionStartMillis: number;
- timeMillisOffset: number;
- transitionCurve: AdaptiveLightingTransitionCurveEntry[];
- brightnessAdjustmentRange: BrightnessAdjustmentMultiplierRange;
- updateInterval: number,
- notifyIntervalThreshold: number;
+ transitionStartMillis: number
+ timeMillisOffset: number
+ transitionCurve: AdaptiveLightingTransitionCurveEntry[]
+ brightnessAdjustmentRange: BrightnessAdjustmentMultiplierRange
+ updateInterval: number
+ notifyIntervalThreshold: number
}
/**
* @group Adaptive Lighting
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export declare interface AdaptiveLightingController {
+ /* eslint-disable ts/method-signature-style */
/**
* See {@link AdaptiveLightingControllerEvents.UPDATE}
* Also see {@link AdaptiveLightingControllerUpdate}
@@ -339,27 +359,29 @@ export declare interface AdaptiveLightingController {
* @param event
* @param listener
*/
- on(event: "update", listener: (update: AdaptiveLightingControllerUpdate) => void): this;
+ on(event: 'update', listener: (update: AdaptiveLightingControllerUpdate) => void): this
+
/**
* See {@link AdaptiveLightingControllerEvents.DISABLED}
*
* @param event
* @param listener
*/
- on(event: "disable", listener: () => void): this;
+ on(event: 'disable', listener: () => void): this
/**
* See {@link AdaptiveLightingControllerUpdate}
*/
- emit(event: "update", update: AdaptiveLightingControllerUpdate): boolean;
- emit(event: "disable"): boolean;
+ emit(event: 'update', update: AdaptiveLightingControllerUpdate): boolean
+ emit(event: 'disable'): boolean
+ /* eslint-enable ts/method-signature-style */
}
/**
* @group Adaptive Lighting
*/
export interface SerializedAdaptiveLightingControllerState {
- activeTransition: ActiveAdaptiveLightingTransition;
+ activeTransition: ActiveAdaptiveLightingTransition
}
/**
@@ -375,7 +397,7 @@ export interface SerializedAdaptiveLightingControllerState {
* (updating the schedule according to your current day/night situation).
* Once enabled the lightbulb will execute the provided transitions. The color temperature value set is always
* dependent on the current brightness value. Meaning brighter light will be colder and darker light will be warmer.
- * HomeKit considers Adaptive Lighting to be disabled as soon a write happens to either the
+ * HomeKit considers Adaptive Lighting to be disabled as soon a 'write' happens to either the
* Hue/Saturation or the ColorTemperature characteristics.
* The AdaptiveLighting state must persist across reboots.
*
@@ -392,10 +414,10 @@ export interface SerializedAdaptiveLightingControllerState {
*
* AUTOMATIC (Default mode):
*
- * This is the easiest mode to setup and needs less to no work form your side for AdaptiveLighting to work.
+ * This is the easiest mode to set up and needs less to no work form your side for AdaptiveLighting to work.
* The AdaptiveLightingController will go through setup procedure with HomeKit and automatically update
* the color temperature characteristic base on the current transition schedule.
- * It is also adjusting the color temperature when a write to the brightness characteristic happens.
+ * It is also adjusting the color temperature when a 'write' to the brightness characteristic happens.
* Additionally, it will also handle turning off AdaptiveLighting, when it detects a write happening to the
* ColorTemperature, Hue or Saturation characteristic (though it can only detect writes coming from HomeKit and
* can't detect changes done to the physical devices directly! See below).
@@ -417,12 +439,12 @@ export interface SerializedAdaptiveLightingControllerState {
* - When using Hue/Saturation:
* When using Hue/Saturation in combination with the ColorTemperature characteristic you need to update the
* respective other in a particular way depending on if being in "color mode" or "color temperature mode".
- * When a write happens to Hue/Saturation characteristic in is advised to set the internal value of the
+ * When a 'write' happens to Hue/Saturation characteristic in is advised to set the internal value of the
* ColorTemperature to the minimal (NOT RAISING an event).
- * When a write happens to the ColorTemperature characteristic just MUST convert to a proper representation
+ * When a 'write' happens to the ColorTemperature characteristic just MUST convert to a proper representation
* in hue and saturation values, with RAISING an event.
* As noted above you MUST NOT call the {@link Characteristic.setValue} method for this, as this will be considered
- * a write to the characteristic and will turn off AdaptiveLighting. Instead, you should use
+ * a 'write' to the characteristic and will turn off AdaptiveLighting. Instead, you should use
* {@link Characteristic.updateValue} for this.
* You can and SHOULD use the supplied utility method {@link ColorUtils.colorTemperatureToHueAndSaturation}
* for converting mired to hue and saturation values.
@@ -440,7 +462,7 @@ export interface SerializedAdaptiveLightingControllerState {
* are only sent in the defined interval threshold, adjust the color temperature when brightness is changed
* and signal that Adaptive Lighting should be disabled if ColorTemperature, Hue or Saturation is changed manually.
*
- * First step is to setup up an event handler for the {@link AdaptiveLightingControllerEvents.UPDATE}, which is called
+ * First step is to set up an event handler for the {@link AdaptiveLightingControllerEvents.UPDATE}, which is called
* when AdaptiveLighting is enabled, the HomeHub updates the schedule for the next 24 hours or AdaptiveLighting
* is restored from disk on startup.
* In the event handler you can get the current schedule via {@link AdaptiveLightingController.getAdaptiveLightingTransitionCurve},
@@ -454,44 +476,43 @@ export interface SerializedAdaptiveLightingControllerState {
* the color temperature when the brightness of the lightbulb changes (see {@link AdaptiveLightingTransitionCurveEntry.brightnessAdjustmentFactor}),
* and signal when AdaptiveLighting got disabled by calling {@link AdaptiveLightingController.disableAdaptiveLighting}
* when ColorTemperature, Hue or Saturation where changed manually.
- * Lastly you should set up a event handler for the {@link AdaptiveLightingControllerEvents.DISABLED} event.
+ * Lastly you should set up an event handler for the {@link AdaptiveLightingControllerEvents.DISABLED} event.
* In yet unknown circumstances HomeKit may also send a dedicated disable command via the control point characteristic.
* Be prepared to handle that.
*
* @group Adaptive Lighting
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export class AdaptiveLightingController
extends EventEmitter
implements SerializableController {
+ private stateChangeDelegate?: StateChangeDelegate
- private stateChangeDelegate?: StateChangeDelegate;
-
- private readonly lightbulb: Lightbulb;
- private readonly mode: AdaptiveLightingControllerMode;
- private readonly customTemperatureAdjustment: number;
+ private readonly lightbulb: Lightbulb
+ private readonly mode: AdaptiveLightingControllerMode
+ private readonly customTemperatureAdjustment: number
- private readonly adjustmentFactorChangedListener: (change: CharacteristicChange) => void;
- private readonly characteristicManualWrittenChangeListener: (change: CharacteristicChange) => void;
+ private readonly adjustmentFactorChangedListener: (change: CharacteristicChange) => void
+ private readonly characteristicManualWrittenChangeListener: (change: CharacteristicChange) => void
- private supportedTransitionConfiguration?: SupportedCharacteristicValueTransitionConfiguration;
- private transitionControl?: CharacteristicValueTransitionControl;
- private activeTransitionCount?: CharacteristicValueActiveTransitionCount;
+ private supportedTransitionConfiguration?: SupportedCharacteristicValueTransitionConfiguration
+ private transitionControl?: CharacteristicValueTransitionControl
+ private activeTransitionCount?: CharacteristicValueActiveTransitionCount
- private colorTemperatureCharacteristic?: ColorTemperature;
- private brightnessCharacteristic?: Brightness;
- private saturationCharacteristic?: Saturation;
- private hueCharacteristic?: Hue;
+ private colorTemperatureCharacteristic?: ColorTemperature
+ private brightnessCharacteristic?: Brightness
+ private saturationCharacteristic?: Saturation
+ private hueCharacteristic?: Hue
- private activeTransition?: ActiveAdaptiveLightingTransition;
- private didRunFirstInitializationStep = false;
- private updateTimeout?: NodeJS.Timeout;
+ private activeTransition?: ActiveAdaptiveLightingTransition
+ private didRunFirstInitializationStep = false
+ private updateTimeout?: NodeJS.Timeout
- private lastTransitionPointInfo?: SavedLastTransitionPointInfo;
- private lastEventNotificationSent = 0;
- private lastNotifiedTemperatureValue = 0;
- private lastNotifiedSaturationValue = 0;
- private lastNotifiedHueValue = 0;
+ private lastTransitionPointInfo?: SavedLastTransitionPointInfo
+ private lastEventNotificationSent = 0
+ private lastNotifiedTemperatureValue = 0
+ private lastNotifiedSaturationValue = 0
+ private lastNotifiedHueValue = 0
/**
* Creates a new instance of the AdaptiveLightingController.
@@ -501,32 +522,32 @@ export class AdaptiveLightingController
* @param options - Optional options to define the operating mode (automatic vs manual).
*/
constructor(service: Lightbulb, options?: AdaptiveLightingOptions) {
- super();
- this.lightbulb = service;
- this.mode = options?.controllerMode ?? AdaptiveLightingControllerMode.AUTOMATIC;
- this.customTemperatureAdjustment = options?.customTemperatureAdjustment ?? 0;
+ super()
+ this.lightbulb = service
+ this.mode = options?.controllerMode ?? AdaptiveLightingControllerMode.AUTOMATIC
+ this.customTemperatureAdjustment = options?.customTemperatureAdjustment ?? 0
- assert(this.lightbulb.testCharacteristic(Characteristic.ColorTemperature), "Lightbulb must have the ColorTemperature characteristic added!");
- assert(this.lightbulb.testCharacteristic(Characteristic.Brightness), "Lightbulb must have the Brightness characteristic added!");
+ assert(this.lightbulb.testCharacteristic(Characteristic.ColorTemperature), 'Lightbulb must have the ColorTemperature characteristic added!')
+ assert(this.lightbulb.testCharacteristic(Characteristic.Brightness), 'Lightbulb must have the Brightness characteristic added!')
- this.adjustmentFactorChangedListener = this.handleAdjustmentFactorChanged.bind(this);
- this.characteristicManualWrittenChangeListener = this.handleCharacteristicManualWritten.bind(this);
+ this.adjustmentFactorChangedListener = this.handleAdjustmentFactorChanged.bind(this)
+ this.characteristicManualWrittenChangeListener = this.handleCharacteristicManualWritten.bind(this)
}
/**
* @private
*/
controllerId(): ControllerIdentifier {
- return DefaultControllerType.CHARACTERISTIC_TRANSITION + "-" + this.lightbulb.getServiceId();
+ return `${DefaultControllerType.CHARACTERISTIC_TRANSITION}-${this.lightbulb.getServiceId()}`
}
// ----------- PUBLIC API START -----------
/**
- * Returns if a Adaptive Lighting transition is currently active.
+ * Returns if an Adaptive Lighting transition is currently active.
*/
public isAdaptiveLightingActive(): boolean {
- return !!this.activeTransition;
+ return !!this.activeTransition
}
/**
@@ -538,106 +559,106 @@ export class AdaptiveLightingController
*/
public disableAdaptiveLighting(): void {
if (this.updateTimeout) {
- clearTimeout(this.updateTimeout);
- this.updateTimeout = undefined;
+ clearTimeout(this.updateTimeout)
+ this.updateTimeout = undefined
}
if (this.activeTransition) {
- this.colorTemperatureCharacteristic?.removeListener(CharacteristicEventTypes.CHANGE, this.characteristicManualWrittenChangeListener);
- this.brightnessCharacteristic?.removeListener(CharacteristicEventTypes.CHANGE, this.adjustmentFactorChangedListener);
- this.hueCharacteristic?.removeListener(CharacteristicEventTypes.CHANGE, this.characteristicManualWrittenChangeListener);
- this.saturationCharacteristic?.removeListener(CharacteristicEventTypes.CHANGE, this.characteristicManualWrittenChangeListener);
+ this.colorTemperatureCharacteristic?.removeListener(CharacteristicEventTypes.CHANGE, this.characteristicManualWrittenChangeListener)
+ this.brightnessCharacteristic?.removeListener(CharacteristicEventTypes.CHANGE, this.adjustmentFactorChangedListener)
+ this.hueCharacteristic?.removeListener(CharacteristicEventTypes.CHANGE, this.characteristicManualWrittenChangeListener)
+ this.saturationCharacteristic?.removeListener(CharacteristicEventTypes.CHANGE, this.characteristicManualWrittenChangeListener)
- this.activeTransition = undefined;
+ this.activeTransition = undefined
- this.stateChangeDelegate?.();
+ this.stateChangeDelegate?.()
}
- this.colorTemperatureCharacteristic = undefined;
- this.brightnessCharacteristic = undefined;
- this.hueCharacteristic = undefined;
- this.saturationCharacteristic = undefined;
+ this.colorTemperatureCharacteristic = undefined
+ this.brightnessCharacteristic = undefined
+ this.hueCharacteristic = undefined
+ this.saturationCharacteristic = undefined
- this.lastTransitionPointInfo = undefined;
- this.lastEventNotificationSent = 0;
- this.lastNotifiedTemperatureValue = 0;
- this.lastNotifiedSaturationValue = 0;
- this.lastNotifiedHueValue = 0;
+ this.lastTransitionPointInfo = undefined
+ this.lastEventNotificationSent = 0
+ this.lastNotifiedTemperatureValue = 0
+ this.lastNotifiedSaturationValue = 0
+ this.lastNotifiedHueValue = 0
- this.didRunFirstInitializationStep = false;
+ this.didRunFirstInitializationStep = false
- this.activeTransitionCount?.sendEventNotification(0);
+ this.activeTransitionCount?.sendEventNotification(0)
- debug("[%s] Disabling adaptive lighting", this.lightbulb.displayName);
+ debug('[%s] Disabling adaptive lighting', this.lightbulb.displayName)
}
/**
- * Returns the time where the current transition curve was started in epoch time millis.
+ * Returns the time when the current transition curve was started in epoch time millis.
* A transition curves is active for 24 hours typically and is renewed every 24 hours by a HomeHub.
- * Additionally see {@link getAdaptiveLightingTimeOffset}.
+ * Additionally, see {@link getAdaptiveLightingTimeOffset}.
*/
public getAdaptiveLightingStartTimeOfTransition(): number {
if (!this.activeTransition) {
- throw new Error("There is no active transition!");
+ throw new Error('There is no active transition!')
}
- return this.activeTransition.transitionStartMillis;
+ return this.activeTransition.transitionStartMillis
}
/**
* It is not necessarily given, that we have the same time (or rather the correct time) as the HomeKit controller
* who set up the transition schedule.
- * Thus we record the delta between our current time and the the time send with the setup request.
+ * Thus, we record the delta between our current time and the time send with the setup request.
* timeOffset
is defined as Date.now() - getAdaptiveLightingStartTimeOfTransition();
.
* So in the case were we actually have a correct local time, it most likely will be positive (due to network latency).
* But of course it can also be negative.
*/
public getAdaptiveLightingTimeOffset(): number {
if (!this.activeTransition) {
- throw new Error("There is no active transition!");
+ throw new Error('There is no active transition!')
}
- return this.activeTransition.timeMillisOffset;
+ return this.activeTransition.timeMillisOffset
}
public getAdaptiveLightingTransitionCurve(): AdaptiveLightingTransitionCurveEntry[] {
if (!this.activeTransition) {
- throw new Error("There is no active transition!");
+ throw new Error('There is no active transition!')
}
- return this.activeTransition.transitionCurve;
+ return this.activeTransition.transitionCurve
}
public getAdaptiveLightingBrightnessMultiplierRange(): BrightnessAdjustmentMultiplierRange {
if (!this.activeTransition) {
- throw new Error("There is no active transition!");
+ throw new Error('There is no active transition!')
}
- return this.activeTransition.brightnessAdjustmentRange;
+ return this.activeTransition.brightnessAdjustmentRange
}
/**
* This method returns the interval (in milliseconds) in which the light should update its internal color temperature
* (aka changes it physical color).
- * A lightbulb should ideally change this also when turned of in oder to have a smooth transition when turning the light on.
+ * A lightbulb should ideally change this also when turned off in oder to have a smooth transition when turning the light on.
*
- * Typically this evaluates to 60000 milliseconds (60 seconds).
+ * Typically, this evaluates to 60000 milliseconds (60 seconds).
*/
public getAdaptiveLightingUpdateInterval(): number {
if (!this.activeTransition) {
- throw new Error("There is no active transition!");
+ throw new Error('There is no active transition!')
}
- return this.activeTransition.updateInterval;
+ return this.activeTransition.updateInterval
}
/**
- * Returns the minimum interval threshold (in milliseconds) a accessory may notify HomeKit controllers about a new
+ * Returns the minimum interval threshold (in milliseconds) an accessory may notify HomeKit controllers about a new
* color temperature value via event notifications (what happens when you call {@link Characteristic.updateValue}).
* Meaning the accessory should only send event notifications to subscribed HomeKit controllers at the specified interval.
*
- * Typically this evaluates to 600000 milliseconds (10 minutes).
+ * Typically, this evaluates to 600000 milliseconds (10 minutes).
*/
public getAdaptiveLightingNotifyIntervalThreshold(): number {
if (!this.activeTransition) {
- throw new Error("There is no active transition!");
+ throw new Error('There is no active transition!')
}
- return this.activeTransition.notifyIntervalThreshold;
+ return this.activeTransition.notifyIntervalThreshold
}
// ----------- PUBLIC API END -----------
@@ -645,17 +666,17 @@ export class AdaptiveLightingController
private handleActiveTransitionUpdated(calledFromDeserializer = false): void {
if (this.activeTransitionCount) {
if (!calledFromDeserializer) {
- this.activeTransitionCount.sendEventNotification(1);
+ this.activeTransitionCount.sendEventNotification(1)
} else {
- this.activeTransitionCount.value = 1;
+ this.activeTransitionCount.value = 1
}
}
if (this.mode === AdaptiveLightingControllerMode.AUTOMATIC) {
- this.scheduleNextUpdate();
+ this.scheduleNextUpdate()
} else if (this.mode === AdaptiveLightingControllerMode.MANUAL) {
if (!this.activeTransition) {
- throw new Error("There is no active transition!");
+ throw new Error('There is no active transition!')
}
const update: AdaptiveLightingControllerUpdate = {
@@ -665,56 +686,56 @@ export class AdaptiveLightingController
brightnessAdjustmentRange: this.activeTransition.brightnessAdjustmentRange,
updateInterval: this.activeTransition.updateInterval,
notifyIntervalThreshold: this.activeTransition.notifyIntervalThreshold,
- };
+ }
- this.emit(AdaptiveLightingControllerEvents.UPDATE, update);
+ this.emit(AdaptiveLightingControllerEvents.UPDATE, update)
} else {
- throw new Error("Unsupported adaptive lighting controller mode: " + this.mode);
+ throw new Error(`Unsupported adaptive lighting controller mode: ${this.mode}`)
}
if (!calledFromDeserializer) {
- this.stateChangeDelegate?.();
+ this.stateChangeDelegate?.()
}
}
private handleAdaptiveLightingEnabled(): void { // this method is run when the initial curve was sent
if (!this.activeTransition) {
- throw new Error("There is no active transition!");
+ throw new Error('There is no active transition!')
}
- this.colorTemperatureCharacteristic = this.lightbulb.getCharacteristic(Characteristic.ColorTemperature);
- this.brightnessCharacteristic = this.lightbulb.getCharacteristic(Characteristic.Brightness);
+ this.colorTemperatureCharacteristic = this.lightbulb.getCharacteristic(Characteristic.ColorTemperature)
+ this.brightnessCharacteristic = this.lightbulb.getCharacteristic(Characteristic.Brightness)
- this.colorTemperatureCharacteristic.on(CharacteristicEventTypes.CHANGE, this.characteristicManualWrittenChangeListener);
- this.brightnessCharacteristic.on(CharacteristicEventTypes.CHANGE, this.adjustmentFactorChangedListener);
+ this.colorTemperatureCharacteristic.on(CharacteristicEventTypes.CHANGE, this.characteristicManualWrittenChangeListener)
+ this.brightnessCharacteristic.on(CharacteristicEventTypes.CHANGE, this.adjustmentFactorChangedListener)
if (this.lightbulb.testCharacteristic(Characteristic.Hue)) {
this.hueCharacteristic = this.lightbulb.getCharacteristic(Characteristic.Hue)
- .on(CharacteristicEventTypes.CHANGE, this.characteristicManualWrittenChangeListener);
+ .on(CharacteristicEventTypes.CHANGE, this.characteristicManualWrittenChangeListener)
}
if (this.lightbulb.testCharacteristic(Characteristic.Saturation)) {
this.saturationCharacteristic = this.lightbulb.getCharacteristic(Characteristic.Saturation)
- .on(CharacteristicEventTypes.CHANGE, this.characteristicManualWrittenChangeListener);
+ .on(CharacteristicEventTypes.CHANGE, this.characteristicManualWrittenChangeListener)
}
}
private handleAdaptiveLightingDisabled(): void {
if (this.mode === AdaptiveLightingControllerMode.MANUAL && this.activeTransition) { // only emit the event if a transition is actually enabled
- this.emit(AdaptiveLightingControllerEvents.DISABLED);
+ this.emit(AdaptiveLightingControllerEvents.DISABLED)
}
- this.disableAdaptiveLighting();
+ this.disableAdaptiveLighting()
}
private handleAdjustmentFactorChanged(change: CharacteristicChange): void {
if (change.newValue === change.oldValue) {
- return;
+ return
}
// consider the following scenario:
// a HomeKit controller queries the light (meaning e.g. Brightness, Hue and Saturation characteristics).
// As of the implementation of the light the brightness characteristic get handler returns first
- // (and returns a value different than the cached value).
- // This change handler gets called and we will update the color temperature accordingly
+ // (and returns a value different from the cached value).
+ // This change handler gets called, and we will update the color temperature accordingly
// (which also adjusts the internal cached values for Hue and Saturation).
// After some short time the Hue or Saturation get handler return with the last known value to the plugin.
// As those values now differ from the cached values (we already updated) we get a call to handleCharacteristicManualWritten
@@ -726,18 +747,18 @@ export class AdaptiveLightingController
// It doesn't ensure that those race conditions do not happen anymore, but with a 1s delay it reduces the possibility by a bit
setTimeout(() => {
if (!this.activeTransition) {
- return; // was disabled in the mean time
+ return // was disabled in the meantime
}
- this.scheduleNextUpdate(true);
- }, 1000).unref();
+ this.scheduleNextUpdate(true)
+ }, 1000).unref()
} else {
- this.scheduleNextUpdate(true); // run a dry scheduleNextUpdate to adjust the colorTemperature using the new brightness value
+ this.scheduleNextUpdate(true) // run a dry scheduleNextUpdate to adjust the colorTemperature using the new brightness value
}
}
/**
* This method is called when a change happens to the Hue/Saturation or ColorTemperature characteristic.
- * When such a write happens (caused by the user changing the color/temperature) Adaptive Lighting must be disabled.
+ * When such a 'write' happens (caused by the user changing the color/temperature) Adaptive Lighting must be disabled.
*
* @param change
*/
@@ -747,9 +768,8 @@ export class AdaptiveLightingController
// or the result of a changed value returned by a read handler
// or the change was done by the controller itself
- debug("[%s] Received a manual write to an characteristic (newValue: %d, oldValue: %d, reason: %s). Thus disabling adaptive lighting!",
- this.lightbulb.displayName, change.newValue, change.oldValue, change.reason);
- this.disableAdaptiveLighting();
+ debug('[%s] Received a manual write to an characteristic (newValue: %d, oldValue: %d, reason: %s). Thus disabling adaptive lighting!', this.lightbulb.displayName, change.newValue, change.oldValue, change.reason)
+ this.disableAdaptiveLighting()
}
}
@@ -759,101 +779,101 @@ export class AdaptiveLightingController
*/
public getCurrentAdaptiveLightingTransitionPoint(): AdaptiveLightingTransitionPoint | undefined {
if (!this.activeTransition) {
- throw new Error("Cannot calculate current transition point if no transition is active!");
+ throw new Error('Cannot calculate current transition point if no transition is active!')
}
// adjustedNow is the now() date corrected to the time of the initiating controller
- const adjustedNow = Date.now() - this.activeTransition.timeMillisOffset;
+ const adjustedNow = Date.now() - this.activeTransition.timeMillisOffset
// "offset" since the start of the transition schedule
- const offset = adjustedNow - this.activeTransition.transitionStartMillis;
+ const offset = adjustedNow - this.activeTransition.transitionStartMillis
- let i = this.lastTransitionPointInfo?.curveIndex ?? 0;
- let lowerBoundTimeOffset = this.lastTransitionPointInfo?.lowerBoundTimeOffset ?? 0; // time offset to the lowerBound transition entry
- let lowerBound: AdaptiveLightingTransitionCurveEntry | undefined = undefined;
- let upperBound: AdaptiveLightingTransitionCurveEntry | undefined = undefined;
+ let i = this.lastTransitionPointInfo?.curveIndex ?? 0
+ let lowerBoundTimeOffset = this.lastTransitionPointInfo?.lowerBoundTimeOffset ?? 0 // time offset to the lowerBound transition entry
+ let lowerBound: AdaptiveLightingTransitionCurveEntry | undefined
+ let upperBound: AdaptiveLightingTransitionCurveEntry | undefined
for (; i + 1 < this.activeTransition.transitionCurve.length; i++) {
- const lowerBound0 = this.activeTransition.transitionCurve[i];
- const upperBound0 = this.activeTransition.transitionCurve[i + 1];
+ const lowerBound0 = this.activeTransition.transitionCurve[i]
+ const upperBound0 = this.activeTransition.transitionCurve[i + 1]
- const lowerBoundDuration = lowerBound0.duration ?? 0;
- lowerBoundTimeOffset += lowerBound0.transitionTime;
+ const lowerBoundDuration = lowerBound0.duration ?? 0
+ lowerBoundTimeOffset += lowerBound0.transitionTime
if (offset >= lowerBoundTimeOffset) {
if (offset <= lowerBoundTimeOffset + lowerBoundDuration + upperBound0.transitionTime) {
- lowerBound = lowerBound0;
- upperBound = upperBound0;
- break;
+ lowerBound = lowerBound0
+ upperBound = upperBound0
+ break
}
} else if (this.lastTransitionPointInfo) {
// if we reached here the entry in the transitionCurve we are searching for is somewhere before current i.
// This can only happen when we have a faulty lastTransitionPointInfo (otherwise we would start from i=0).
- // Thus we try again by searching from i=0
- this.lastTransitionPointInfo = undefined;
- return this.getCurrentAdaptiveLightingTransitionPoint();
+ // Thus, we try again by searching from i=0
+ this.lastTransitionPointInfo = undefined
+ return this.getCurrentAdaptiveLightingTransitionPoint()
}
- lowerBoundTimeOffset += lowerBoundDuration;
+ lowerBoundTimeOffset += lowerBoundDuration
}
if (!lowerBound || !upperBound) {
- this.lastTransitionPointInfo = undefined;
- return undefined;
+ this.lastTransitionPointInfo = undefined
+ return undefined
}
this.lastTransitionPointInfo = {
curveIndex: i,
// we need to subtract lowerBound.transitionTime. When we start the loop above
// with a saved transition point, we will always add lowerBound.transitionTime as first step.
- // Otherwise our calculations are simply wrong.
+ // Otherwise, our calculations are simply wrong.
lowerBoundTimeOffset: lowerBoundTimeOffset - lowerBound.transitionTime,
- };
+ }
return {
- lowerBoundTimeOffset: lowerBoundTimeOffset,
+ lowerBoundTimeOffset,
transitionOffset: offset - lowerBoundTimeOffset,
- lowerBound: lowerBound,
- upperBound: upperBound,
- };
+ lowerBound,
+ upperBound,
+ }
}
private scheduleNextUpdate(dryRun = false): void {
if (!this.activeTransition) {
- throw new Error("tried scheduling transition when no transition was active!");
+ throw new Error('tried scheduling transition when no transition was active!')
}
if (!dryRun) {
- this.updateTimeout = undefined;
+ this.updateTimeout = undefined
}
if (!this.didRunFirstInitializationStep) {
- this.didRunFirstInitializationStep = true;
- this.handleAdaptiveLightingEnabled();
+ this.didRunFirstInitializationStep = true
+ this.handleAdaptiveLightingEnabled()
}
- const transitionPoint = this.getCurrentAdaptiveLightingTransitionPoint();
+ const transitionPoint = this.getCurrentAdaptiveLightingTransitionPoint()
if (!transitionPoint) {
- debug("[%s] Reached end of transition curve!", this.lightbulb.displayName);
+ debug('[%s] Reached end of transition curve!', this.lightbulb.displayName)
if (!dryRun) {
// the transition schedule is only for 24 hours, we reached the end?
- this.disableAdaptiveLighting();
+ this.disableAdaptiveLighting()
}
- return;
+ return
}
- const lowerBound = transitionPoint.lowerBound;
- const upperBound = transitionPoint.upperBound;
+ const lowerBound = transitionPoint.lowerBound
+ const upperBound = transitionPoint.upperBound
- let interpolatedTemperature: number;
- let interpolatedAdjustmentFactor: number;
- if (lowerBound.duration && transitionPoint.transitionOffset <= lowerBound.duration) {
- interpolatedTemperature = lowerBound.temperature;
- interpolatedAdjustmentFactor = lowerBound.brightnessAdjustmentFactor;
+ let interpolatedTemperature: number
+ let interpolatedAdjustmentFactor: number
+ if (lowerBound.duration && transitionPoint.transitionOffset <= lowerBound.duration) {
+ interpolatedTemperature = lowerBound.temperature
+ interpolatedAdjustmentFactor = lowerBound.brightnessAdjustmentFactor
} else {
- const timePercentage = (transitionPoint.transitionOffset - (lowerBound.duration ?? 0)) / upperBound.transitionTime;
- interpolatedTemperature = lowerBound.temperature + (upperBound.temperature - lowerBound.temperature) * timePercentage;
+ const timePercentage = (transitionPoint.transitionOffset - (lowerBound.duration ?? 0)) / upperBound.transitionTime
+ interpolatedTemperature = lowerBound.temperature + (upperBound.temperature - lowerBound.temperature) * timePercentage
interpolatedAdjustmentFactor = lowerBound.brightnessAdjustmentFactor
- + (upperBound.brightnessAdjustmentFactor - lowerBound.brightnessAdjustmentFactor) * timePercentage;
+ + (upperBound.brightnessAdjustmentFactor - lowerBound.brightnessAdjustmentFactor) * timePercentage
}
const adjustmentMultiplier = Math.max(
@@ -862,31 +882,30 @@ export class AdaptiveLightingController
this.activeTransition.brightnessAdjustmentRange.maxBrightnessValue,
this.brightnessCharacteristic?.value as number, // get handler is not called for optimal performance
),
- );
+ )
- let temperature = Math.round(interpolatedTemperature + interpolatedAdjustmentFactor * adjustmentMultiplier);
+ let temperature = Math.round(interpolatedTemperature + interpolatedAdjustmentFactor * adjustmentMultiplier)
// apply any manually applied temperature adjustments
- temperature += this.customTemperatureAdjustment;
+ temperature += this.customTemperatureAdjustment
- const min = this.colorTemperatureCharacteristic?.props.minValue ?? 140;
- const max = this.colorTemperatureCharacteristic?.props.maxValue ?? 500;
- temperature = Math.max(min, Math.min(max, temperature));
- const color = ColorUtils.colorTemperatureToHueAndSaturation(temperature);
+ const min = this.colorTemperatureCharacteristic?.props.minValue ?? 140
+ const max = this.colorTemperatureCharacteristic?.props.maxValue ?? 500
+ temperature = Math.max(min, Math.min(max, temperature))
+ const color = ColorUtils.colorTemperatureToHueAndSaturation(temperature)
- debug("[%s] Next temperature value is %d (for brightness %d adj: %s)",
- this.lightbulb.displayName, temperature, adjustmentMultiplier, this.customTemperatureAdjustment);
+ debug('[%s] Next temperature value is %d (for brightness %d adj: %s)', this.lightbulb.displayName, temperature, adjustmentMultiplier, this.customTemperatureAdjustment)
const context: AdaptiveLightingCharacteristicContext = {
controller: this,
omitEventUpdate: true,
- };
+ }
/*
* We set saturation and hue values BEFORE we call the ColorTemperature SET handler (via setValue).
* First thought was so the API user could get the values in the SET handler of the color temperature characteristic.
- * Do this is probably not really elegant cause this would only work when Adaptive Lighting is turned on
- * an the accessory MUST in any case update the Hue/Saturation values on a ColorTemperature write
+ * Do this is probably not really elegant because this would only work when Adaptive Lighting is turned on
+ * and the accessory MUST in any case update the Hue/Saturation values on a ColorTemperature write
* (obviously only if Hue/Saturation characteristics are added to the service).
*
* The clever thing about this though is that, that it prevents notifications from being sent for Hue and Saturation
@@ -898,48 +917,48 @@ export class AdaptiveLightingController
* value to the hue and saturation representation.
*/
if (this.saturationCharacteristic) {
- this.saturationCharacteristic.value = color.saturation;
+ this.saturationCharacteristic.value = color.saturation
}
if (this.hueCharacteristic) {
- this.hueCharacteristic.value = color.hue;
+ this.hueCharacteristic.value = color.hue
}
- this.colorTemperatureCharacteristic?.handleSetRequest(temperature, undefined, context).catch(reason => { // reason is HAPStatus code
- debug("[%s] Failed to next adaptive lighting transition point: %d", this.lightbulb.displayName, reason);
- });
+ this.colorTemperatureCharacteristic?.handleSetRequest(temperature, undefined, context).catch((reason) => { // reason is HAPStatus code
+ debug('[%s] Failed to next adaptive lighting transition point: %d', this.lightbulb.displayName, reason)
+ })
if (!this.activeTransition) {
- console.warn("[" + this.lightbulb.displayName + "] Adaptive Lighting was probably disable my mistake by some call in " +
- "the SET handler of the ColorTemperature characteristic! " +
- "Please check that you don't call setValue/setCharacteristic on the Hue, Saturation or ColorTemperature characteristic!");
- return;
+ console.warn(`[${this.lightbulb.displayName}] Adaptive Lighting was probably disable my mistake by some call in `
+ + `the SET handler of the ColorTemperature characteristic! `
+ + `Please check that you don't call setValue/setCharacteristic on the Hue, Saturation or ColorTemperature characteristic!`)
+ return
}
- const now = Date.now();
+ const now = Date.now()
if (!dryRun && now - this.lastEventNotificationSent >= this.activeTransition.notifyIntervalThreshold) {
- debug("[%s] Sending event notifications for current transition!", this.lightbulb.displayName);
- this.lastEventNotificationSent = now;
+ debug('[%s] Sending event notifications for current transition!', this.lightbulb.displayName)
+ this.lastEventNotificationSent = now
const eventContext: AdaptiveLightingCharacteristicContext = {
controller: this,
- };
+ }
if (this.lastNotifiedTemperatureValue !== temperature) {
- this.colorTemperatureCharacteristic?.sendEventNotification(temperature, eventContext);
- this.lastNotifiedTemperatureValue = temperature;
+ this.colorTemperatureCharacteristic?.sendEventNotification(temperature, eventContext)
+ this.lastNotifiedTemperatureValue = temperature
}
if (this.saturationCharacteristic && this.lastNotifiedSaturationValue !== color.saturation) {
- this.saturationCharacteristic.sendEventNotification(color.saturation, eventContext);
- this.lastNotifiedSaturationValue = color.saturation;
+ this.saturationCharacteristic.sendEventNotification(color.saturation, eventContext)
+ this.lastNotifiedSaturationValue = color.saturation
}
if (this.hueCharacteristic && this.lastNotifiedHueValue !== color.hue) {
- this.hueCharacteristic.sendEventNotification(color.hue, eventContext);
- this.lastNotifiedHueValue = color.hue;
+ this.hueCharacteristic.sendEventNotification(color.hue, eventContext)
+ this.lastNotifiedHueValue = color.hue
}
}
if (!dryRun) {
- this.updateTimeout = setTimeout(this.scheduleNextUpdate.bind(this), this.activeTransition.updateInterval);
+ this.updateTimeout = setTimeout(this.scheduleNextUpdate.bind(this), this.activeTransition.updateInterval)
}
}
@@ -947,13 +966,13 @@ export class AdaptiveLightingController
* @private
*/
constructServices(): ControllerServiceMap {
- return {};
+ return {}
}
/**
* @private
*/
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ // eslint-disable-next-line unused-imports/no-unused-vars
initWithServices(serviceMap: ControllerServiceMap): void | ControllerServiceMap {
// do nothing
}
@@ -962,50 +981,50 @@ export class AdaptiveLightingController
* @private
*/
configureServices(): void {
- this.supportedTransitionConfiguration = this.lightbulb.getCharacteristic(Characteristic.SupportedCharacteristicValueTransitionConfiguration);
+ this.supportedTransitionConfiguration = this.lightbulb.getCharacteristic(Characteristic.SupportedCharacteristicValueTransitionConfiguration)
this.transitionControl = this.lightbulb.getCharacteristic(Characteristic.CharacteristicValueTransitionControl)
- .updateValue("");
+ .updateValue('')
this.activeTransitionCount = this.lightbulb.getCharacteristic(Characteristic.CharacteristicValueActiveTransitionCount)
- .updateValue(0);
+ .updateValue(0)
this.supportedTransitionConfiguration
- .onGet(this.handleSupportedTransitionConfigurationRead.bind(this));
+ .onGet(this.handleSupportedTransitionConfigurationRead.bind(this))
this.transitionControl
.onGet(() => {
- return this.buildTransitionControlResponseBuffer().toString("base64");
+ return this.buildTransitionControlResponseBuffer().toString('base64')
})
- .onSet(value => {
+ .onSet((value) => {
try {
- return this.handleTransitionControlWrite(value);
+ return this.handleTransitionControlWrite(value)
} catch (error) {
- console.warn(`[%s] DEBUG: '${value}'`);
- console.warn("[%s] Encountered error on CharacteristicValueTransitionControl characteristic: " + error.stack);
- this.disableAdaptiveLighting();
- throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
+ console.warn(`[%s] DEBUG: '${value}'`)
+ console.warn(`[%s] Encountered error on CharacteristicValueTransitionControl characteristic: ${error.stack}`)
+ this.disableAdaptiveLighting()
+ throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
}
- });
+ })
}
/**
* @private
*/
handleControllerRemoved(): void {
- this.lightbulb.removeCharacteristic(this.supportedTransitionConfiguration!);
- this.lightbulb.removeCharacteristic(this.transitionControl!);
- this.lightbulb.removeCharacteristic(this.activeTransitionCount!);
+ this.lightbulb.removeCharacteristic(this.supportedTransitionConfiguration!)
+ this.lightbulb.removeCharacteristic(this.transitionControl!)
+ this.lightbulb.removeCharacteristic(this.activeTransitionCount!)
- this.supportedTransitionConfiguration = undefined;
- this.transitionControl = undefined;
- this.activeTransitionCount = undefined;
+ this.supportedTransitionConfiguration = undefined
+ this.transitionControl = undefined
+ this.activeTransitionCount = undefined
- this.removeAllListeners();
+ this.removeAllListeners()
}
/**
* @private
*/
handleFactoryReset(): void {
- this.handleAdaptiveLightingDisabled();
+ this.handleAdaptiveLightingDisabled()
}
/**
@@ -1013,241 +1032,269 @@ export class AdaptiveLightingController
*/
serialize(): SerializedAdaptiveLightingControllerState | undefined {
if (!this.activeTransition) {
- return undefined;
+ return undefined
}
return {
activeTransition: this.activeTransition,
- };
+ }
}
/**
* @private
*/
deserialize(serialized: SerializedAdaptiveLightingControllerState): void {
- this.activeTransition = serialized.activeTransition;
+ this.activeTransition = serialized.activeTransition
// Data migrations from beta builds
if (!this.activeTransition.transitionId) {
// @ts-expect-error: data migration from beta builds
- this.activeTransition.transitionId = this.activeTransition.id1;
+ this.activeTransition.transitionId = this.activeTransition.id1
// @ts-expect-error: data migration from beta builds
- delete this.activeTransition.id1;
+ delete this.activeTransition.id1
}
if (!this.activeTransition.timeMillisOffset) { // compatibility to data produced by early betas
- this.activeTransition.timeMillisOffset = 0;
+ this.activeTransition.timeMillisOffset = 0
}
- this.handleActiveTransitionUpdated(true);
+ this.handleActiveTransitionUpdated(true)
}
/**
* @private
*/
setupStateChangeDelegate(delegate?: StateChangeDelegate): void {
- this.stateChangeDelegate = delegate;
+ this.stateChangeDelegate = delegate
}
private handleSupportedTransitionConfigurationRead(): string {
- const brightnessIID = this.lightbulb?.getCharacteristic(Characteristic.Brightness).iid;
- const temperatureIID = this.lightbulb?.getCharacteristic(Characteristic.ColorTemperature).iid;
- assert(brightnessIID, "iid for brightness characteristic is undefined");
- assert(temperatureIID, "iid for temperature characteristic is undefined");
-
- return tlv.encode(SupportedCharacteristicValueTransitionConfigurationsTypes.SUPPORTED_TRANSITION_CONFIGURATION, [
- tlv.encode(
- SupportedValueTransitionConfigurationTypes.CHARACTERISTIC_IID, tlv.writeVariableUIntLE(brightnessIID!),
- SupportedValueTransitionConfigurationTypes.TRANSITION_TYPE, TransitionType.BRIGHTNESS,
+ const brightnessIID = this.lightbulb?.getCharacteristic(Characteristic.Brightness).iid
+ const temperatureIID = this.lightbulb?.getCharacteristic(Characteristic.ColorTemperature).iid
+ assert(brightnessIID, 'iid for brightness characteristic is undefined')
+ assert(temperatureIID, 'iid for temperature characteristic is undefined')
+
+ return encode(SupportedCharacteristicValueTransitionConfigurationsTypes.SUPPORTED_TRANSITION_CONFIGURATION, [
+ encode(
+ SupportedValueTransitionConfigurationTypes.CHARACTERISTIC_IID,
+ writeVariableUIntLE(brightnessIID!),
+ SupportedValueTransitionConfigurationTypes.TRANSITION_TYPE,
+ TransitionType.BRIGHTNESS,
),
- tlv.encode(
- SupportedValueTransitionConfigurationTypes.CHARACTERISTIC_IID, tlv.writeVariableUIntLE(temperatureIID!),
- SupportedValueTransitionConfigurationTypes.TRANSITION_TYPE, TransitionType.COLOR_TEMPERATURE,
+ encode(
+ SupportedValueTransitionConfigurationTypes.CHARACTERISTIC_IID,
+ writeVariableUIntLE(temperatureIID!),
+ SupportedValueTransitionConfigurationTypes.TRANSITION_TYPE,
+ TransitionType.COLOR_TEMPERATURE,
),
- ]).toString("base64");
+ ]).toString('base64')
}
private buildTransitionControlResponseBuffer(time?: number): Buffer {
if (!this.activeTransition) {
- return Buffer.alloc(0);
+ return Buffer.alloc(0)
}
- const active = this.activeTransition;
+ const active = this.activeTransition
- const timeSinceStart = time ?? (Date.now() - active.timeMillisOffset - active.transitionStartMillis);
- const timeSinceStartBuffer = tlv.writeVariableUIntLE(timeSinceStart);
+ const timeSinceStart = time ?? (Date.now() - active.timeMillisOffset - active.transitionStartMillis)
+ const timeSinceStartBuffer = writeVariableUIntLE(timeSinceStart)
- let parameters = tlv.encode(
- ValueTransitionParametersTypes.TRANSITION_ID, uuid.write(active.transitionId),
- ValueTransitionParametersTypes.START_TIME, Buffer.from(active.transitionStartBuffer, "hex"),
- );
+ let parameters = encode(
+ ValueTransitionParametersTypes.TRANSITION_ID,
+ write(active.transitionId),
+ ValueTransitionParametersTypes.START_TIME,
+ Buffer.from(active.transitionStartBuffer, 'hex'),
+ )
if (active.id3) {
parameters = Buffer.concat([
parameters,
- tlv.encode(ValueTransitionParametersTypes.UNKNOWN_3, Buffer.from(active.id3, "hex")),
- ]);
+ encode(ValueTransitionParametersTypes.UNKNOWN_3, Buffer.from(active.id3, 'hex')),
+ ])
}
- const status = tlv.encode(
- ValueTransitionConfigurationStatusTypes.CHARACTERISTIC_IID, tlv.writeVariableUIntLE(active.iid!),
- ValueTransitionConfigurationStatusTypes.TRANSITION_PARAMETERS, parameters,
- ValueTransitionConfigurationStatusTypes.TIME_SINCE_START, timeSinceStartBuffer,
- );
-
- return tlv.encode(
- ValueTransitionConfigurationResponseTypes.VALUE_CONFIGURATION_STATUS, status,
- );
+ const status = encode(
+ ValueTransitionConfigurationStatusTypes.CHARACTERISTIC_IID,
+ writeVariableUIntLE(active.iid!),
+ ValueTransitionConfigurationStatusTypes.TRANSITION_PARAMETERS,
+ parameters,
+ ValueTransitionConfigurationStatusTypes.TIME_SINCE_START,
+ timeSinceStartBuffer,
+ )
+
+ return encode(
+ ValueTransitionConfigurationResponseTypes.VALUE_CONFIGURATION_STATUS,
+ status,
+ )
}
private handleTransitionControlWrite(value: CharacteristicValue): string {
- if (typeof value !== "string") {
- throw new HapStatusError(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ if (typeof value !== 'string') {
+ throw new HapStatusError(HAPStatus.INVALID_VALUE_IN_REQUEST)
}
- const tlvData = tlv.decode(Buffer.from(value, "base64"));
- const responseBuffers: Buffer[] = [];
+ const tlvData = decode(Buffer.from(value, 'base64'))
+ const responseBuffers: Buffer[] = []
- const readTransition = tlvData[TransitionControlTypes.READ_CURRENT_VALUE_TRANSITION_CONFIGURATION];
+ const readTransition = tlvData[TransitionControlTypes.READ_CURRENT_VALUE_TRANSITION_CONFIGURATION]
if (readTransition) {
- const readTransitionResponse = this.handleTransitionControlReadTransition(readTransition);
+ const readTransitionResponse = this.handleTransitionControlReadTransition(readTransition)
if (readTransitionResponse) {
- responseBuffers.push(readTransitionResponse);
+ responseBuffers.push(readTransitionResponse)
}
}
- const updateTransition = tlvData[TransitionControlTypes.UPDATE_VALUE_TRANSITION_CONFIGURATION];
+ const updateTransition = tlvData[TransitionControlTypes.UPDATE_VALUE_TRANSITION_CONFIGURATION]
if (updateTransition) {
- const updateTransitionResponse = this.handleTransitionControlUpdateTransition(updateTransition);
+ const updateTransitionResponse = this.handleTransitionControlUpdateTransition(updateTransition)
if (updateTransitionResponse) {
- responseBuffers.push(updateTransitionResponse);
+ responseBuffers.push(updateTransitionResponse)
}
}
- return Buffer.concat(responseBuffers).toString("base64");
+ return Buffer.concat(responseBuffers).toString('base64')
}
private handleTransitionControlReadTransition(buffer: Buffer): Buffer | undefined {
- const readTransition = tlv.decode(buffer);
+ const readTransition = decode(buffer)
- const iid = tlv.readVariableUIntLE(readTransition[ReadValueTransitionConfiguration.CHARACTERISTIC_IID]);
+ const iid = readVariableUIntLE(readTransition[ReadValueTransitionConfiguration.CHARACTERISTIC_IID])
if (this.activeTransition) {
if (this.activeTransition.iid !== iid) {
- console.warn("[" + this.lightbulb.displayName + "] iid of current adaptive lighting transition (" + this.activeTransition.iid
- + ") doesn't match the requested one " + iid);
- throw new HapStatusError(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ console.warn(`[${this.lightbulb.displayName}] iid of current adaptive lighting transition (${this.activeTransition.iid
+ }) doesn't match the requested one ${iid}`)
+ throw new HapStatusError(HAPStatus.INVALID_VALUE_IN_REQUEST)
}
- let parameters = tlv.encode(
- ValueTransitionParametersTypes.TRANSITION_ID, uuid.write(this.activeTransition.transitionId),
- ValueTransitionParametersTypes.START_TIME, Buffer.from(this.activeTransition.transitionStartBuffer, "hex"),
- );
+ let parameters = encode(
+ ValueTransitionParametersTypes.TRANSITION_ID,
+ write(this.activeTransition.transitionId),
+ ValueTransitionParametersTypes.START_TIME,
+ Buffer.from(this.activeTransition.transitionStartBuffer, 'hex'),
+ )
if (this.activeTransition.id3) {
parameters = Buffer.concat([
parameters,
- tlv.encode(ValueTransitionParametersTypes.UNKNOWN_3, Buffer.from(this.activeTransition.id3, "hex")),
- ]);
+ encode(ValueTransitionParametersTypes.UNKNOWN_3, Buffer.from(this.activeTransition.id3, 'hex')),
+ ])
}
- return tlv.encode(
- TransitionControlTypes.READ_CURRENT_VALUE_TRANSITION_CONFIGURATION, tlv.encode(
- ValueTransitionConfigurationTypes.CHARACTERISTIC_IID, tlv.writeVariableUIntLE(this.activeTransition.iid),
- ValueTransitionConfigurationTypes.TRANSITION_PARAMETERS, parameters,
- ValueTransitionConfigurationTypes.UNKNOWN_3, 1,
- ValueTransitionConfigurationTypes.TRANSITION_CURVE_CONFIGURATION, tlv.encode(
- TransitionCurveConfigurationTypes.TRANSITION_ENTRY, this.activeTransition.transitionCurve.map((entry, index, array) => {
- const duration = array[index - 1]?.duration ?? 0; // we store stuff differently :sweat_smile:
-
- return tlv.encode(
- TransitionEntryTypes.ADJUSTMENT_FACTOR, tlv.writeFloat32LE(entry.brightnessAdjustmentFactor),
- TransitionEntryTypes.VALUE, tlv.writeFloat32LE(entry.temperature),
- TransitionEntryTypes.TRANSITION_OFFSET, tlv.writeVariableUIntLE(entry.transitionTime),
- TransitionEntryTypes.DURATION, tlv.writeVariableUIntLE(duration),
- );
+ return encode(
+ TransitionControlTypes.READ_CURRENT_VALUE_TRANSITION_CONFIGURATION,
+ encode(
+ ValueTransitionConfigurationTypes.CHARACTERISTIC_IID,
+ writeVariableUIntLE(this.activeTransition.iid),
+ ValueTransitionConfigurationTypes.TRANSITION_PARAMETERS,
+ parameters,
+ ValueTransitionConfigurationTypes.UNKNOWN_3,
+ 1,
+ ValueTransitionConfigurationTypes.TRANSITION_CURVE_CONFIGURATION,
+ encode(
+ TransitionCurveConfigurationTypes.TRANSITION_ENTRY,
+ this.activeTransition.transitionCurve.map((entry, index, array) => {
+ const duration = array[index - 1]?.duration ?? 0 // we store stuff differently :sweat_smile:
+
+ return encode(
+ TransitionEntryTypes.ADJUSTMENT_FACTOR,
+ writeFloat32LE(entry.brightnessAdjustmentFactor),
+ TransitionEntryTypes.VALUE,
+ writeFloat32LE(entry.temperature),
+ TransitionEntryTypes.TRANSITION_OFFSET,
+ writeVariableUIntLE(entry.transitionTime),
+ TransitionEntryTypes.DURATION,
+ writeVariableUIntLE(duration),
+ )
}),
- TransitionCurveConfigurationTypes.ADJUSTMENT_CHARACTERISTIC_IID, tlv.writeVariableUIntLE(this.activeTransition.brightnessCharacteristicIID),
- TransitionCurveConfigurationTypes.ADJUSTMENT_MULTIPLIER_RANGE, tlv.encode(
- // eslint-disable-next-line max-len
- TransitionAdjustmentMultiplierRange.MINIMUM_ADJUSTMENT_MULTIPLIER, tlv.writeUInt32(this.activeTransition.brightnessAdjustmentRange.minBrightnessValue),
- // eslint-disable-next-line max-len
- TransitionAdjustmentMultiplierRange.MAXIMUM_ADJUSTMENT_MULTIPLIER, tlv.writeUInt32(this.activeTransition.brightnessAdjustmentRange.maxBrightnessValue),
+ TransitionCurveConfigurationTypes.ADJUSTMENT_CHARACTERISTIC_IID,
+ writeVariableUIntLE(this.activeTransition.brightnessCharacteristicIID),
+ TransitionCurveConfigurationTypes.ADJUSTMENT_MULTIPLIER_RANGE,
+ encode(
+
+ TransitionAdjustmentMultiplierRange.MINIMUM_ADJUSTMENT_MULTIPLIER,
+ writeUInt32(this.activeTransition.brightnessAdjustmentRange.minBrightnessValue),
+
+ TransitionAdjustmentMultiplierRange.MAXIMUM_ADJUSTMENT_MULTIPLIER,
+ writeUInt32(this.activeTransition.brightnessAdjustmentRange.maxBrightnessValue),
),
),
- ValueTransitionConfigurationTypes.UPDATE_INTERVAL, tlv.writeVariableUIntLE(this.activeTransition.updateInterval),
- ValueTransitionConfigurationTypes.NOTIFY_INTERVAL_THRESHOLD, tlv.writeVariableUIntLE(this.activeTransition.notifyIntervalThreshold),
+ ValueTransitionConfigurationTypes.UPDATE_INTERVAL,
+ writeVariableUIntLE(this.activeTransition.updateInterval),
+ ValueTransitionConfigurationTypes.NOTIFY_INTERVAL_THRESHOLD,
+ writeVariableUIntLE(this.activeTransition.notifyIntervalThreshold),
),
- );
+ )
} else {
- return undefined; // returns empty string
+ return undefined // returns empty string
}
}
private handleTransitionControlUpdateTransition(buffer: Buffer): Buffer {
- const updateTransition = tlv.decode(buffer);
- const transitionConfiguration = tlv.decode(updateTransition[UpdateValueTransitionConfigurationsTypes.VALUE_TRANSITION_CONFIGURATION]);
+ const updateTransition = decode(buffer)
+ const transitionConfiguration = decode(updateTransition[UpdateValueTransitionConfigurationsTypes.VALUE_TRANSITION_CONFIGURATION])
- const iid = tlv.readVariableUIntLE(transitionConfiguration[ValueTransitionConfigurationTypes.CHARACTERISTIC_IID]);
+ const iid = readVariableUIntLE(transitionConfiguration[ValueTransitionConfigurationTypes.CHARACTERISTIC_IID])
if (!this.lightbulb.getCharacteristicByIID(iid)) {
- throw new HapStatusError(HAPStatus.INVALID_VALUE_IN_REQUEST);
+ throw new HapStatusError(HAPStatus.INVALID_VALUE_IN_REQUEST)
}
- const param3 = transitionConfiguration[ValueTransitionConfigurationTypes.UNKNOWN_3]?.readUInt8(0); // when present it is always 1
+ const param3 = transitionConfiguration[ValueTransitionConfigurationTypes.UNKNOWN_3]?.readUInt8(0) // when present it is always 1
if (!param3) { // if HomeKit just sends the iid, we consider that as "disable adaptive lighting" (assumption)
- this.handleAdaptiveLightingDisabled();
- return tlv.encode(TransitionControlTypes.UPDATE_VALUE_TRANSITION_CONFIGURATION, Buffer.alloc(0));
+ this.handleAdaptiveLightingDisabled()
+ return encode(TransitionControlTypes.UPDATE_VALUE_TRANSITION_CONFIGURATION, Buffer.alloc(0))
}
- const parametersTLV = tlv.decode(transitionConfiguration[ValueTransitionConfigurationTypes.TRANSITION_PARAMETERS]);
- const curveConfiguration = tlv.decodeWithLists(transitionConfiguration[ValueTransitionConfigurationTypes.TRANSITION_CURVE_CONFIGURATION]);
- const updateInterval = transitionConfiguration[ValueTransitionConfigurationTypes.UPDATE_INTERVAL]?.readUInt16LE(0);
- const notifyIntervalThreshold = transitionConfiguration[ValueTransitionConfigurationTypes.NOTIFY_INTERVAL_THRESHOLD].readUInt32LE(0);
+ const parametersTLV = decode(transitionConfiguration[ValueTransitionConfigurationTypes.TRANSITION_PARAMETERS])
+ const curveConfiguration = decodeWithLists(transitionConfiguration[ValueTransitionConfigurationTypes.TRANSITION_CURVE_CONFIGURATION])
+ const updateInterval = transitionConfiguration[ValueTransitionConfigurationTypes.UPDATE_INTERVAL]?.readUInt16LE(0)
+ const notifyIntervalThreshold = transitionConfiguration[ValueTransitionConfigurationTypes.NOTIFY_INTERVAL_THRESHOLD].readUInt32LE(0)
- const transitionId = parametersTLV[ValueTransitionParametersTypes.TRANSITION_ID];
- const startTime = parametersTLV[ValueTransitionParametersTypes.START_TIME];
- const id3 = parametersTLV[ValueTransitionParametersTypes.UNKNOWN_3]; // this may be undefined
+ const transitionId = parametersTLV[ValueTransitionParametersTypes.TRANSITION_ID]
+ const startTime = parametersTLV[ValueTransitionParametersTypes.START_TIME]
+ const id3 = parametersTLV[ValueTransitionParametersTypes.UNKNOWN_3] // this may be undefined
- const startTimeMillis = epochMillisFromMillisSince2001_01_01Buffer(startTime);
- const timeMillisOffset = Date.now() - startTimeMillis;
+ const startTimeMillis = epochMillisFromMillisSince2001_01_01Buffer(startTime)
+ const timeMillisOffset = Date.now() - startTimeMillis
- const transitionCurve: AdaptiveLightingTransitionCurveEntry[] = [];
- let previous: AdaptiveLightingTransitionCurveEntry | undefined = undefined;
+ const transitionCurve: AdaptiveLightingTransitionCurveEntry[] = []
+ let previous: AdaptiveLightingTransitionCurveEntry | undefined
- const transitions = curveConfiguration[TransitionCurveConfigurationTypes.TRANSITION_ENTRY] as Buffer[];
+ const transitions = curveConfiguration[TransitionCurveConfigurationTypes.TRANSITION_ENTRY] as Buffer[]
for (const entry of transitions) {
- const tlvEntry = tlv.decode(entry);
+ const tlvEntry = decode(entry)
- const adjustmentFactor = tlvEntry[TransitionEntryTypes.ADJUSTMENT_FACTOR].readFloatLE(0);
- const value = tlvEntry[TransitionEntryTypes.VALUE].readFloatLE(0);
+ const adjustmentFactor = tlvEntry[TransitionEntryTypes.ADJUSTMENT_FACTOR].readFloatLE(0)
+ const value = tlvEntry[TransitionEntryTypes.VALUE].readFloatLE(0)
- const transitionOffset = tlv.readVariableUIntLE(tlvEntry[TransitionEntryTypes.TRANSITION_OFFSET]);
+ const transitionOffset = readVariableUIntLE(tlvEntry[TransitionEntryTypes.TRANSITION_OFFSET])
- const duration = tlvEntry[TransitionEntryTypes.DURATION]? tlv.readVariableUIntLE(tlvEntry[TransitionEntryTypes.DURATION]): undefined;
+ const duration = tlvEntry[TransitionEntryTypes.DURATION] ? readVariableUIntLE(tlvEntry[TransitionEntryTypes.DURATION]) : undefined
if (previous) {
- previous.duration = duration;
+ previous.duration = duration
}
previous = {
temperature: value,
brightnessAdjustmentFactor: adjustmentFactor,
transitionTime: transitionOffset,
- };
- transitionCurve.push(previous);
+ }
+ transitionCurve.push(previous)
}
- const adjustmentIID = tlv.readVariableUIntLE((curveConfiguration[TransitionCurveConfigurationTypes.ADJUSTMENT_CHARACTERISTIC_IID] as Buffer));
- const adjustmentMultiplierRange = tlv.decode(curveConfiguration[TransitionCurveConfigurationTypes.ADJUSTMENT_MULTIPLIER_RANGE] as Buffer);
- const minAdjustmentMultiplier = adjustmentMultiplierRange[TransitionAdjustmentMultiplierRange.MINIMUM_ADJUSTMENT_MULTIPLIER].readUInt32LE(0);
- const maxAdjustmentMultiplier = adjustmentMultiplierRange[TransitionAdjustmentMultiplierRange.MAXIMUM_ADJUSTMENT_MULTIPLIER].readUInt32LE(0);
+ const adjustmentIID = readVariableUIntLE((curveConfiguration[TransitionCurveConfigurationTypes.ADJUSTMENT_CHARACTERISTIC_IID] as Buffer))
+ const adjustmentMultiplierRange = decode(curveConfiguration[TransitionCurveConfigurationTypes.ADJUSTMENT_MULTIPLIER_RANGE] as Buffer)
+ const minAdjustmentMultiplier = adjustmentMultiplierRange[TransitionAdjustmentMultiplierRange.MINIMUM_ADJUSTMENT_MULTIPLIER].readUInt32LE(0)
+ const maxAdjustmentMultiplier = adjustmentMultiplierRange[TransitionAdjustmentMultiplierRange.MAXIMUM_ADJUSTMENT_MULTIPLIER].readUInt32LE(0)
this.activeTransition = {
- iid: iid,
+ iid,
transitionStartMillis: startTimeMillis,
- timeMillisOffset: timeMillisOffset,
+ timeMillisOffset,
- transitionId: uuid.unparse(transitionId),
- transitionStartBuffer: startTime.toString("hex"),
- id3: id3?.toString("hex"),
+ transitionId: unparse(transitionId),
+ transitionStartBuffer: startTime.toString('hex'),
+ id3: id3?.toString('hex'),
brightnessCharacteristicIID: adjustmentIID,
brightnessAdjustmentRange: {
@@ -1255,25 +1302,25 @@ export class AdaptiveLightingController
maxBrightnessValue: maxAdjustmentMultiplier,
},
- transitionCurve: transitionCurve,
+ transitionCurve,
updateInterval: updateInterval ?? 60000,
- notifyIntervalThreshold: notifyIntervalThreshold,
- };
+ notifyIntervalThreshold,
+ }
if (this.updateTimeout) {
- clearTimeout(this.updateTimeout);
- this.updateTimeout = undefined;
- debug("[%s] Adaptive lighting was renewed.", this.lightbulb.displayName);
+ clearTimeout(this.updateTimeout)
+ this.updateTimeout = undefined
+ debug('[%s] Adaptive lighting was renewed.', this.lightbulb.displayName)
} else {
- debug("[%s] Adaptive lighting was enabled.", this.lightbulb.displayName);
+ debug('[%s] Adaptive lighting was enabled.', this.lightbulb.displayName)
}
- this.handleActiveTransitionUpdated();
+ this.handleActiveTransitionUpdated()
- return tlv.encode(
- TransitionControlTypes.UPDATE_VALUE_TRANSITION_CONFIGURATION, this.buildTransitionControlResponseBuffer(0),
- );
+ return encode(
+ TransitionControlTypes.UPDATE_VALUE_TRANSITION_CONFIGURATION,
+ this.buildTransitionControlResponseBuffer(0),
+ )
}
-
}
diff --git a/src/lib/controller/CameraController.spec.ts b/src/lib/controller/CameraController.spec.ts
index b0964be85..6b6a93ca9 100644
--- a/src/lib/controller/CameraController.spec.ts
+++ b/src/lib/controller/CameraController.spec.ts
@@ -1,41 +1,46 @@
-import crypto from "crypto";
-import {
- CameraController,
+import type {
CameraControllerOptions,
CameraRecordingDelegate,
CameraStreamingDelegate,
- DoorbellController,
PrepareStreamCallback,
- ResourceRequestReason,
SnapshotRequestCallback,
StreamRequestCallback,
-} from ".";
+} from '.'
+import type {
+ CameraRecordingConfiguration,
+ CameraRecordingOptions,
+ CameraStreamingOptions,
+ PrepareStreamRequest,
+ RecordingPacket,
+ SnapshotRequest,
+ StreamingRequest,
+} from '../camera'
+import type { HDSProtocolSpecificErrorReason } from '../datastream'
+
+import { Buffer } from 'node:buffer'
+import { randomBytes } from 'node:crypto'
+
+import { beforeEach, describe, expect, it } from 'vitest'
+
import {
AudioBitrate,
AudioRecordingCodecType,
AudioRecordingSamplerate,
AudioStreamingCodecType,
AudioStreamingSamplerate,
- CameraRecordingConfiguration,
- CameraRecordingOptions,
- CameraStreamingOptions,
EventTriggerOption,
H264Level,
H264Profile,
MediaContainerType,
- PrepareStreamRequest,
- RecordingPacket,
- SnapshotRequest,
SRTPCryptoSuites,
- StreamingRequest,
VideoCodecType,
-} from "../camera";
-import { Characteristic } from "../Characteristic";
-import { HDSProtocolSpecificErrorReason } from "../datastream";
-import "../definitions";
-import { HAPStatus } from "../HAPServer";
+} from '../camera/index.js'
+import { Characteristic } from '../Characteristic.js'
+import '../definitions/index.js'
+import { HAPStatus } from '../HAPServer.js'
+import { CameraController, DoorbellController, ResourceRequestReason } from './index.js'
-export const MOCK_IMAGE = crypto.randomBytes(64);
+export const MOCK_IMAGE = randomBytes(64)
export const mockStreamingOptions: CameraStreamingOptions = {
supportedCryptoSuites: [SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80],
@@ -65,7 +70,7 @@ export const mockStreamingOptions: CameraStreamingOptions = {
samplerate: AudioStreamingSamplerate.KHZ_24,
}],
},
-};
+}
export const mockRecordingOptions: CameraRecordingOptions = {
prebufferLength: 4000,
@@ -87,39 +92,39 @@ export const mockRecordingOptions: CameraRecordingOptions = {
samplerate: AudioRecordingSamplerate.KHZ_48,
}],
},
-};
+}
export class MockDelegate implements CameraStreamingDelegate, CameraRecordingDelegate {
handleSnapshotRequest(request: SnapshotRequest, callback: SnapshotRequestCallback): void {
- callback(undefined, MOCK_IMAGE);
+ callback(undefined, MOCK_IMAGE)
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ // eslint-disable-next-line unused-imports/no-unused-vars
handleStreamRequest(request: StreamingRequest, callback: StreamRequestCallback): void {
- throw Error("Unsupported!");
+ throw new Error('Unsupported!')
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ // eslint-disable-next-line unused-imports/no-unused-vars
prepareStream(request: PrepareStreamRequest, callback: PrepareStreamCallback): void {
- throw Error("Unsupported!");
+ throw new Error('Unsupported!')
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ // eslint-disable-next-line unused-imports/no-unused-vars
async *handleRecordingStreamRequest(streamId: number): AsyncGenerator {
- yield { data: Buffer.alloc(64, 0), isLast: true };
+ yield { data: Buffer.alloc(64, 0), isLast: true }
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ // eslint-disable-next-line unused-imports/no-unused-vars
closeRecordingStream(streamId: number, reason: HDSProtocolSpecificErrorReason): void {
// do nothing
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ // eslint-disable-next-line unused-imports/no-unused-vars
updateRecordingActive(active: boolean): void {
// do nothing
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ // eslint-disable-next-line unused-imports/no-unused-vars
updateRecordingConfiguration(configuration: CameraRecordingConfiguration | undefined): void {
// do nothing
}
@@ -132,176 +137,175 @@ export function createCameraControllerOptions(
delegate: CameraStreamingDelegate & CameraRecordingDelegate = new MockDelegate(),
): CameraControllerOptions {
return {
- cameraStreamCount: cameraStreamCount,
- delegate: delegate,
- streamingOptions: streamingOptions,
+ cameraStreamCount,
+ delegate,
+ streamingOptions,
recording: {
options: recordingOptions,
- delegate: delegate,
+ delegate,
},
sensors: {
motion: true,
occupancy: true,
},
- };
+ }
}
-describe("CameraController", () => {
- let controller: CameraController;
+describe('cameraController', () => {
+ let controller: CameraController
beforeEach(() => {
- controller = new CameraController(createCameraControllerOptions());
+ controller = new CameraController(createCameraControllerOptions())
// basic controller init
- controller.constructServices();
- controller.configureServices();
- });
+ controller.constructServices()
+ controller.configureServices()
+ })
- test("init", () => {
+ it('init', () => {
expect(controller.recordingManagement?.operatingModeService.getCharacteristic(Characteristic.HomeKitCameraActive).value)
- .toBe(1);
+ .toBe(1)
expect(controller.recordingManagement?.operatingModeService.getCharacteristic(Characteristic.PeriodicSnapshotsActive).value)
- .toBe(1);
+ .toBe(1)
expect(controller.recordingManagement?.operatingModeService.getCharacteristic(Characteristic.EventSnapshotsActive).value)
- .toBe(1);
+ .toBe(1)
expect(controller.recordingManagement?.recordingManagementService.getCharacteristic(Characteristic.RecordingAudioActive).value)
- .toBe(0);
+ .toBe(0)
expect(controller.recordingManagement?.recordingManagementService.getCharacteristic(Characteristic.Active).value)
- .toBe(Characteristic.Active.INACTIVE);
+ .toBe(Characteristic.Active.INACTIVE)
for (const streamManagement of controller.streamManagements) {
expect(streamManagement.getService().getCharacteristic(Characteristic.Active).value)
- .toBe(Characteristic.Active.ACTIVE);
+ .toBe(Characteristic.Active.ACTIVE)
}
- expect(controller.motionService?.testCharacteristic(Characteristic.StatusActive)).toBeTruthy();
+ expect(controller.motionService?.testCharacteristic(Characteristic.StatusActive)).toBeTruthy()
expect(controller.motionService!.getCharacteristic(Characteristic.StatusActive).value)
- .toEqual(true);
+ .toEqual(true)
- expect(controller.occupancyService?.testCharacteristic(Characteristic.StatusActive)).toBeTruthy();
+ expect(controller.occupancyService?.testCharacteristic(Characteristic.StatusActive)).toBeTruthy()
expect(controller.occupancyService!.getCharacteristic(Characteristic.StatusActive).value)
- .toEqual(true);
- });
+ .toEqual(true)
+ })
- describe("retrieveEventTriggerOptions", () => {
- test("with motion service", () => {
+ describe('retrieveEventTriggerOptions', () => {
+ it('with motion service', () => {
// @ts-expect-error (private access)
- expect(controller.recordingManagement?.eventTriggerOptions).toBe(EventTriggerOption.MOTION);
- });
+ expect(controller.recordingManagement?.eventTriggerOptions).toBe(EventTriggerOption.MOTION)
+ })
- test("with motion service and doorbell", () => {
- const doorbell = new DoorbellController(createCameraControllerOptions());
- doorbell.constructServices();
- doorbell.configureServices();
+ it('with motion service and doorbell', () => {
+ const doorbell = new DoorbellController(createCameraControllerOptions())
+ doorbell.constructServices()
+ doorbell.configureServices()
// @ts-expect-error (private access)
- expect(doorbell.recordingManagement?.eventTriggerOptions).toBe(EventTriggerOption.MOTION | EventTriggerOption.DOORBELL);
- });
+ expect(doorbell.recordingManagement?.eventTriggerOptions).toBe(EventTriggerOption.MOTION | EventTriggerOption.DOORBELL)
+ })
- test("with motion service and doorbell and override", () => {
- const options = mockRecordingOptions;
- options.overrideEventTriggerOptions = [EventTriggerOption.MOTION];
- const doorbell = new DoorbellController(createCameraControllerOptions(options));
- doorbell.constructServices();
- doorbell.configureServices();
+ it('with motion service and doorbell and override', () => {
+ const options = mockRecordingOptions
+ options.overrideEventTriggerOptions = [EventTriggerOption.MOTION]
+ const doorbell = new DoorbellController(createCameraControllerOptions(options))
+ doorbell.constructServices()
+ doorbell.configureServices()
// @ts-expect-error (private access)
- expect(doorbell.recordingManagement?.eventTriggerOptions).toBe(EventTriggerOption.MOTION | EventTriggerOption.DOORBELL);
- });
+ expect(doorbell.recordingManagement?.eventTriggerOptions).toBe(EventTriggerOption.MOTION | EventTriggerOption.DOORBELL)
+ })
- test("with motion service and doorbell specified as override", () => {
- const options = mockRecordingOptions;
- options.overrideEventTriggerOptions = [EventTriggerOption.DOORBELL];
- const camera = new CameraController(createCameraControllerOptions(options));
- camera.constructServices();
- camera.configureServices();
+ it('with motion service and doorbell specified as override', () => {
+ const options = mockRecordingOptions
+ options.overrideEventTriggerOptions = [EventTriggerOption.DOORBELL]
+ const camera = new CameraController(createCameraControllerOptions(options))
+ camera.constructServices()
+ camera.configureServices()
// @ts-expect-error (private access)
- expect(camera.recordingManagement?.eventTriggerOptions).toBe(EventTriggerOption.MOTION | EventTriggerOption.DOORBELL);
- });
- });
+ expect(camera.recordingManagement?.eventTriggerOptions).toBe(EventTriggerOption.MOTION | EventTriggerOption.DOORBELL)
+ })
+ })
- describe("handleSnapshotRequest", () => {
+ describe('handleSnapshotRequest', () => {
beforeEach(() => {
controller.recordingManagement?.operatingModeService
- .setCharacteristic(Characteristic.PeriodicSnapshotsActive, true);
- });
+ .setCharacteristic(Characteristic.PeriodicSnapshotsActive, true)
+ })
- test("simple handleSnapshotRequest", async () => {
- const result = controller.handleSnapshotRequest(100, 100, "SomeAccessory", undefined);
- await expect(result).resolves.toEqual(MOCK_IMAGE);
- });
+ it('simple handleSnapshotRequest', async () => {
+ const result = controller.handleSnapshotRequest(100, 100, 'SomeAccessory', undefined)
+ await expect(result).resolves.toEqual(MOCK_IMAGE)
+ })
- test("handleSnapshot considering PeriodicSnapshotsActive state", async () => {
- controller.recordingManagement?.operatingModeService.setCharacteristic(Characteristic.PeriodicSnapshotsActive, false);
+ it('handleSnapshot considering PeriodicSnapshotsActive state', async () => {
+ controller.recordingManagement?.operatingModeService.setCharacteristic(Characteristic.PeriodicSnapshotsActive, false)
await expect(
- controller.handleSnapshotRequest(100, 100, "SomeAccessory", undefined),
- ).rejects.toEqual(HAPStatus.INSUFFICIENT_PRIVILEGES);
+ controller.handleSnapshotRequest(100, 100, 'SomeAccessory', undefined),
+ ).rejects.toEqual(HAPStatus.INSUFFICIENT_PRIVILEGES)
await expect(
- controller.handleSnapshotRequest(100, 100, "SomeAccessory", ResourceRequestReason.PERIODIC),
- ).rejects.toEqual(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE);
+ controller.handleSnapshotRequest(100, 100, 'SomeAccessory', ResourceRequestReason.PERIODIC),
+ ).rejects.toEqual(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE)
await expect(
- controller.handleSnapshotRequest(100, 100, "SomeAccessory", ResourceRequestReason.EVENT),
- ).resolves.toEqual(MOCK_IMAGE);
- });
+ controller.handleSnapshotRequest(100, 100, 'SomeAccessory', ResourceRequestReason.EVENT),
+ ).resolves.toEqual(MOCK_IMAGE)
+ })
- test("handleSnapshot considering EventSnapshotsActive state", async () => {
- controller.recordingManagement?.operatingModeService.setCharacteristic(Characteristic.EventSnapshotsActive, false);
+ it('handleSnapshot considering EventSnapshotsActive state', async () => {
+ controller.recordingManagement?.operatingModeService.setCharacteristic(Characteristic.EventSnapshotsActive, false)
await expect(
- controller.handleSnapshotRequest(100, 100, "SomeAccessory", undefined),
- ).rejects.toEqual(HAPStatus.INSUFFICIENT_PRIVILEGES);
+ controller.handleSnapshotRequest(100, 100, 'SomeAccessory', undefined),
+ ).rejects.toEqual(HAPStatus.INSUFFICIENT_PRIVILEGES)
await expect(
- controller.handleSnapshotRequest(100, 100, "SomeAccessory", ResourceRequestReason.PERIODIC),
- ).resolves.toEqual(MOCK_IMAGE);
+ controller.handleSnapshotRequest(100, 100, 'SomeAccessory', ResourceRequestReason.PERIODIC),
+ ).resolves.toEqual(MOCK_IMAGE)
await expect(
- controller.handleSnapshotRequest(100, 100, "SomeAccessory", ResourceRequestReason.EVENT),
- ).rejects.toEqual(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE);
- });
+ controller.handleSnapshotRequest(100, 100, 'SomeAccessory', ResourceRequestReason.EVENT),
+ ).rejects.toEqual(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE)
+ })
- test("handleSnapshot considering HomeKitCameraActive state", async () => {
- controller.recordingManagement?.operatingModeService.setCharacteristic(Characteristic.HomeKitCameraActive, false);
+ it('handleSnapshot considering HomeKitCameraActive state', async () => {
+ controller.recordingManagement?.operatingModeService.setCharacteristic(Characteristic.HomeKitCameraActive, false)
for (const reason of [undefined, ResourceRequestReason.PERIODIC, ResourceRequestReason.EVENT]) {
await expect(
- controller.handleSnapshotRequest(100, 100, "SomeAccessory", reason),
- ).rejects.toEqual(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE);
+ controller.handleSnapshotRequest(100, 100, 'SomeAccessory', reason),
+ ).rejects.toEqual(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE)
}
- });
+ })
- test("handleSnapshot considering RTPStreamManagement.Active state", async () => {
+ it('handleSnapshot considering RTPStreamManagement.Active state', async () => {
for (const management of controller.streamManagements) {
- management.service.setCharacteristic(Characteristic.Active, false);
+ management.service.setCharacteristic(Characteristic.Active, false)
}
for (const reason of [undefined, ResourceRequestReason.PERIODIC, ResourceRequestReason.EVENT]) {
await expect(
- controller.handleSnapshotRequest(100, 100, "SomeAccessory", reason),
- ).rejects.toEqual(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE);
+ controller.handleSnapshotRequest(100, 100, 'SomeAccessory', reason),
+ ).rejects.toEqual(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE)
}
- });
- });
-
- describe("serialization", () => {
- test("identity", () => {
- const serialized0 = controller.serialize()!;
- controller.deserialize(serialized0);
- const serialized1 = controller.serialize();
+ })
+ })
- expect(serialized0).toEqual(serialized1);
- });
+ describe('serialization', () => {
+ it('identity', () => {
+ const serialized0 = controller.serialize()!
+ controller.deserialize(serialized0)
+ const serialized1 = controller.serialize()
+ expect(serialized0).toEqual(serialized1)
+ })
- test("should restore existing configuration", () => {
- const selectedConfiguration = "AR0BBKAPAAACCAEAAAAAAAAAAwsBAQACBgEEoA8AAAIkAQEAAhIBAQICAQIDBNAHAAAEBKAPAAADC" +
- "wECgAcCAjgEAwEeAxQBAQECDwEBAQIBAAMBBQQEQAAAAA==";
+ it('should restore existing configuration', () => {
+ const selectedConfiguration = 'AR0BBKAPAAACCAEAAAAAAAAAAwsBAQACBgEEoA8AAAIkAQEAAhIBAQICAQIDBNAHAAAEBKAPAAADC'
+ + 'wECgAcCAjgEAwEeAxQBAQECDwEBAQIBAAMBBQQEQAAAAA=='
const data = JSON.parse(`{
"type": "camera",
"controllerData": {
@@ -324,40 +328,40 @@ describe("CameraController", () => {
}
}
}
- }`);
+ }`)
- controller.deserialize(data.controllerData.data);
+ controller.deserialize(data.controllerData.data)
expect(controller.streamManagements[0].service.getCharacteristic(Characteristic.Active).value)
- .toBe(Characteristic.Active.ACTIVE);
+ .toBe(Characteristic.Active.ACTIVE)
expect(controller.streamManagements[1].service.getCharacteristic(Characteristic.Active).value)
- .toBe(Characteristic.Active.INACTIVE);
+ .toBe(Characteristic.Active.INACTIVE)
// @ts-expect-error (private access)
expect(controller.recordingManagement!.selectedConfiguration)
- .toBeUndefined(); // the hash is from a different configuration, we expect to drop selected config when the supported configuration changes!
+ .toBeUndefined() // the hash is from a different configuration, we expect to drop selected config when the supported configuration changes!
expect(controller.recordingManagement?.recordingManagementService.getCharacteristic(Characteristic.Active).value)
- .toBe(Characteristic.Active.ACTIVE);
+ .toBe(Characteristic.Active.ACTIVE)
expect(controller.recordingManagement?.recordingManagementService.getCharacteristic(Characteristic.RecordingAudioActive).value)
- .toBe(1);
+ .toBe(1)
expect(controller.recordingManagement?.operatingModeService.getCharacteristic(Characteristic.HomeKitCameraActive).value)
- .toBe(0);
+ .toBe(0)
expect(controller.recordingManagement?.operatingModeService.getCharacteristic(Characteristic.PeriodicSnapshotsActive).value)
- .toBe(0);
+ .toBe(0)
expect(controller.recordingManagement?.operatingModeService.getCharacteristic(Characteristic.EventSnapshotsActive).value)
- .toBe(0);
- });
- });
-
- describe("handleFactoryReset", () => {
- test("default configuration", () => {
- const serialized0 = controller.serialize();
- controller.handleFactoryReset();
- const serialized1 = controller.serialize();
-
- expect(serialized0).toEqual(serialized1);
- });
- });
-});
+ .toBe(0)
+ })
+ })
+
+ describe('handleFactoryReset', () => {
+ it('default configuration', () => {
+ const serialized0 = controller.serialize()
+ controller.handleFactoryReset()
+ const serialized1 = controller.serialize()
+
+ expect(serialized0).toEqual(serialized1)
+ })
+ })
+})
diff --git a/src/lib/controller/CameraController.ts b/src/lib/controller/CameraController.ts
index f8124508e..d75355d0f 100644
--- a/src/lib/controller/CameraController.ts
+++ b/src/lib/controller/CameraController.ts
@@ -1,25 +1,22 @@
-import crypto from "crypto";
-import createDebug from "debug";
-import { EventEmitter } from "events";
-import { CharacteristicValue, SessionIdentifier } from "../../types";
-import {
+/* global NodeJS */
+import type { Buffer } from 'node:buffer'
+
+import type { CharacteristicValue, SessionIdentifier } from '../../types'
+import type {
CameraRecordingConfiguration,
CameraRecordingOptions,
CameraStreamingOptions,
- EventTriggerOption,
PrepareStreamRequest,
PrepareStreamResponse,
- RecordingManagement,
RecordingManagementState,
RecordingPacket,
- RTPStreamManagement,
RTPStreamManagementState,
SnapshotRequest,
StreamingRequest,
-} from "../camera";
-import { Characteristic, CharacteristicEventTypes, CharacteristicGetCallback, CharacteristicSetCallback } from "../Characteristic";
-import { DataStreamManagement, HDSProtocolSpecificErrorReason } from "../datastream";
-import {
+} from '../camera'
+import type { CharacteristicGetCallback, CharacteristicSetCallback } from '../Characteristic'
+import type { HDSProtocolSpecificErrorReason } from '../datastream'
+import type {
CameraOperatingMode,
CameraRecordingManagement,
DataStreamTransportManagement,
@@ -28,13 +25,23 @@ import {
MotionSensor,
OccupancySensor,
Speaker,
-} from "../definitions";
-import { HAPStatus } from "../HAPServer";
-import { Service } from "../Service";
-import { HapStatusError } from "../util/hapStatusError";
-import { ControllerIdentifier, ControllerServiceMap, DefaultControllerType, SerializableController, StateChangeDelegate } from "./Controller";
+} from '../definitions'
+import type { ControllerIdentifier, ControllerServiceMap, SerializableController, StateChangeDelegate } from './Controller'
+
+import { randomBytes } from 'node:crypto'
+import { EventEmitter } from 'node:events'
+
+import createDebug from 'debug'
-const debug = createDebug("HAP-NodeJS:Camera:Controller");
+import { EventTriggerOption, RecordingManagement, RTPStreamManagement } from '../camera/index.js'
+import { Characteristic, CharacteristicEventTypes } from '../Characteristic.js'
+import { DataStreamManagement } from '../datastream/index.js'
+import { HAPStatus } from '../HAPServer.js'
+import { Service } from '../Service.js'
+import { HapStatusError } from '../util/hapStatusError.js'
+import { DefaultControllerType } from './Controller.js'
+
+const debug = createDebug('HAP-NodeJS:Camera:Controller')
/**
* @group Camera
@@ -47,17 +54,17 @@ export interface CameraControllerOptions {
*
* Default value: 1
*/
- cameraStreamCount?: number,
+ cameraStreamCount?: number
/**
* Delegate which handles the actual RTP/RTCP video/audio streaming and Snapshot requests.
*/
- delegate: CameraStreamingDelegate,
+ delegate: CameraStreamingDelegate
/**
* Options regarding video/audio streaming
*/
- streamingOptions: CameraStreamingOptions,
+ streamingOptions: CameraStreamingOptions
/**
* When supplying this option, it will enable support for HomeKit Secure Video.
@@ -71,12 +78,12 @@ export interface CameraControllerOptions {
/**
* Options regarding Recordings (Secure Video)
*/
- options: CameraRecordingOptions,
+ options: CameraRecordingOptions
/**
- * Delegate which handles the audio/video recording data streaming on motion.
- */
- delegate: CameraRecordingDelegate,
+ * Delegate which handles the audio/video recording data streaming on motion.
+ */
+ delegate: CameraRecordingDelegate
}
/**
@@ -101,7 +108,7 @@ export interface CameraControllerOptions {
* If supplied, this sensor will be used as a {@link EventTriggerOption.MOTION} trigger.
* The characteristic {@link Characteristic.StatusActive} will be added, which is used to enable or disable the sensor.
*/
- motion?: Service | boolean;
+ motion?: Service | boolean
/**
* Define if a {@link Service.OccupancySensor} should be created/associated with the controller.
*
@@ -111,26 +118,27 @@ export interface CameraControllerOptions {
*
* The characteristic {@link Characteristic.StatusActive} will be added, which is used to enable or disable the sensor.
*/
- occupancy?: Service | boolean;
+ occupancy?: Service | boolean
}
}
/**
* @group Camera
*/
-export type SnapshotRequestCallback = (error?: Error | HAPStatus, buffer?: Buffer) => void;
+export type SnapshotRequestCallback = (error?: Error | HAPStatus, buffer?: Buffer) => void
/**
* @group Camera
*/
-export type PrepareStreamCallback = (error?: Error, response?: PrepareStreamResponse) => void;
+export type PrepareStreamCallback = (error?: Error, response?: PrepareStreamResponse) => void
/**
* @group Camera
*/
-export type StreamRequestCallback = (error?: Error) => void;
+export type StreamRequestCallback = (error?: Error) => void
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum ResourceRequestReason {
/**
* The reason describes periodic resource requests.
@@ -141,7 +149,7 @@ export const enum ResourceRequestReason {
* The resource request is the result of some event.
* In the example of camera image snapshots, requests are made due to e.g. a motion event or similar.
*/
- EVENT = 1
+ EVENT = 1,
}
/**
@@ -159,17 +167,17 @@ export interface CameraStreamingDelegate {
* @param request - Request containing image size.
* @param callback - Callback supplied with the resulting Buffer
*/
- handleSnapshotRequest(request: SnapshotRequest, callback: SnapshotRequestCallback): void;
-
- prepareStream(request: PrepareStreamRequest, callback: PrepareStreamCallback): void;
- handleStreamRequest(request: StreamingRequest, callback: StreamRequestCallback): void;
-
+ /* eslint-disable ts/method-signature-style */
+ handleSnapshotRequest(request: SnapshotRequest, callback: SnapshotRequestCallback): void
+ prepareStream(request: PrepareStreamRequest, callback: PrepareStreamCallback): void
+ handleStreamRequest(request: StreamingRequest, callback: StreamRequestCallback): void
+ /* eslint-enable ts/method-signature-style */
}
/**
* A `CameraRecordingDelegate` is responsible for handling recordings of a HomeKit Secure Video camera.
*
- * It is responsible for maintaining the prebuffer (see {@link CameraRecordingOptions.prebufferLength},
+ * It is responsible for maintaining the prebuffer (see {@link CameraRecordingOptions.prebufferLength}),
* once recording was activated (see {@link updateRecordingActive}).
*
* Before recording is considered enabled two things must happen:
@@ -219,7 +227,8 @@ export interface CameraRecordingDelegate {
*
* @param active - Specifies if recording is active or not.
*/
- updateRecordingActive(active: boolean): void;
+ // eslint-disable-next-line ts/method-signature-style
+ updateRecordingActive(active: boolean): void
/**
* A call to this method signals that the selected (by the HomeKit Controller)
@@ -234,11 +243,12 @@ export interface CameraRecordingDelegate {
* currently running stream and only apply the updated configuration to the next stream.
*
* @param configuration - The {@link CameraRecordingConfiguration}. Reconfigure your recording pipeline accordingly.
- * The parameter might be `undefined` when the selected configuration became invalid. This typically ony happens
- * e.g. due to a factory reset (when all pairings are removed). Disable the recording pipeline in such a case
- * even if recording is still enabled for the camera.
+ * The parameter might be `undefined` when the selected configuration became invalid. This typically ony happens
+ * e.g. due to a factory reset (when all pairings are removed). Disable the recording pipeline in such a case
+ * even if recording is still enabled for the camera.
*/
- updateRecordingConfiguration(configuration: CameraRecordingConfiguration | undefined): void;
+ // eslint-disable-next-line ts/method-signature-style
+ updateRecordingConfiguration(configuration: CameraRecordingConfiguration | undefined): void
/**
* This method is called to stream the next recording event.
@@ -272,7 +282,7 @@ export interface CameraRecordingDelegate {
* Once a close of stream is signaled, the `AsyncGenerator` function must return gracefully.
*
* For more information about `AsyncGenerator`s you might have a look at:
- * * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
*
* NOTE: HAP-NodeJS guarantees that this method is only called with a valid selected {@link CameraRecordingConfiguration}.
*
@@ -280,7 +290,8 @@ export interface CameraRecordingDelegate {
*
* @param streamId - The streamId of the currently ongoing stream.
*/
- handleRecordingStreamRequest(streamId: number): AsyncGenerator;
+ // eslint-disable-next-line ts/method-signature-style
+ handleRecordingStreamRequest(streamId: number): AsyncGenerator
/**
* This method is called once the HomeKit Controller acknowledges the `endOfStream`.
@@ -289,7 +300,8 @@ export interface CameraRecordingDelegate {
*
* @param streamId - The streamId of the acknowledged stream.
*/
- acknowledgeStream?(streamId: number): void;
+ // eslint-disable-next-line ts/method-signature-style
+ acknowledgeStream?(streamId: number): void
/**
* This method is called to notify the delegate that a recording stream started via {@link handleRecordingStreamRequest} was closed.
@@ -301,9 +313,10 @@ export interface CameraRecordingDelegate {
*
* @param streamId - The streamId for which the close event was sent.
* @param reason - The reason with which the stream was closed.
- * NOTE: This method is also called in case of a closed connection. This is encoded by setting the `reason` to undefined.
+ * NOTE: This method is also called in case of a closed connection. This is encoded by setting the `reason` to undefined.
*/
- closeRecordingStream(streamId: number, reason: HDSProtocolSpecificErrorReason | undefined): void;
+ // eslint-disable-next-line ts/method-signature-style
+ closeRecordingStream(streamId: number, reason: HDSProtocolSpecificErrorReason | undefined): void
}
/**
@@ -312,56 +325,58 @@ export interface CameraRecordingDelegate {
export interface CameraControllerServiceMap extends ControllerServiceMap {
// "streamManagement%d": CameraRTPStreamManagement, // format to map all stream management services; indexed by zero
- microphone?: Microphone,
- speaker?: Speaker,
+ microphone?: Microphone
+ speaker?: Speaker
- cameraEventRecordingManagement?: CameraRecordingManagement,
- cameraOperatingMode?: CameraOperatingMode,
- dataStreamTransportManagement?: DataStreamTransportManagement,
+ cameraEventRecordingManagement?: CameraRecordingManagement
+ cameraOperatingMode?: CameraOperatingMode
+ dataStreamTransportManagement?: DataStreamTransportManagement
- motionService?: MotionSensor,
- occupancyService?: OccupancySensor,
+ motionService?: MotionSensor
+ occupancyService?: OccupancySensor
// this ServiceMap is also used by the DoorbellController; there is no necessity to declare it,
// but I think its good practice to reserve the namespace
- doorbell?: Doorbell;
+ doorbell?: Doorbell
}
/**
* @group Camera
*/
export interface CameraControllerState {
- streamManagements: RTPStreamManagementState[];
- recordingManagement?: RecordingManagementState;
+ streamManagements: RTPStreamManagementState[]
+ recordingManagement?: RecordingManagementState
}
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum CameraControllerEvents {
/**
* Emitted when the mute state or the volume changed. The Apple Home App typically does not set those values
* except the mute state. When you adjust the volume in the Camera view it will reset the muted state if it was set previously.
* The value of volume has nothing to do with the volume slider in the Camera view of the Home app.
*/
- MICROPHONE_PROPERTIES_CHANGED = "microphone-change",
+ MICROPHONE_PROPERTIES_CHANGED = 'microphone-change',
/**
* Emitted when the mute state or the volume changed. The Apple Home App typically does not set those values
* except the mute state. When you unmute the device microphone it will reset the mute state if it was set previously.
*/
- SPEAKER_PROPERTIES_CHANGED = "speaker-change",
+ SPEAKER_PROPERTIES_CHANGED = 'speaker-change',
}
/**
* @group Camera
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export declare interface CameraController {
- on(event: "microphone-change", listener: (muted: boolean, volume: number) => void): this;
- on(event: "speaker-change", listener: (muted: boolean, volume: number) => void): this;
-
- emit(event: "microphone-change", muted: boolean, volume: number): boolean;
- emit(event: "speaker-change", muted: boolean, volume: number): boolean;
+ /* eslint-disable ts/method-signature-style */
+ on(event: 'microphone-change', listener: (muted: boolean, volume: number) => void): this
+ on(event: 'speaker-change', listener: (muted: boolean, volume: number) => void): this
+ emit(event: 'microphone-change', muted: boolean, volume: number): boolean
+ emit(event: 'speaker-change', muted: boolean, volume: number): boolean
+ /* eslint-enable ts/method-signature-style */
}
/**
@@ -369,72 +384,74 @@ export declare interface CameraController {
*
* @group Camera
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export class CameraController extends EventEmitter implements SerializableController {
- private static readonly STREAM_MANAGEMENT = "streamManagement"; // key to index all RTPStreamManagement services
+ private static readonly STREAM_MANAGEMENT = 'streamManagement' // key to index all RTPStreamManagement services
- private stateChangeDelegate?: StateChangeDelegate;
+ private stateChangeDelegate?: StateChangeDelegate
- private readonly streamCount: number;
- private readonly delegate: CameraStreamingDelegate;
- private readonly streamingOptions: CameraStreamingOptions;
+ private readonly streamCount: number
+ private readonly delegate: CameraStreamingDelegate
+ private readonly streamingOptions: CameraStreamingOptions
/**
* **Temporary** storage for {@link CameraRecordingOptions} and {@link CameraRecordingDelegate}.
* This property is reset to `undefined` after the CameraController was fully initialized.
* You can still access those values via the {@link CameraController.recordingManagement}.
*/
private recording?: {
- options: CameraRecordingOptions,
- delegate: CameraRecordingDelegate,
- };
+ options: CameraRecordingOptions
+ delegate: CameraRecordingDelegate
+ }
+
/**
* Temporary storage for the sensor option.
*/
private sensorOptions?: {
- motion?: Service | boolean;
- occupancy?: Service | boolean;
- };
- private readonly legacyMode: boolean = false;
+ motion?: Service | boolean
+ occupancy?: Service | boolean
+ }
+
+ private readonly legacyMode: boolean = false
/**
* @private
*/
- streamManagements: RTPStreamManagement[] = [];
+ streamManagements: RTPStreamManagement[] = []
/**
* The {@link RecordingManagement} which is responsible for handling HomeKit Secure Video.
* This property is only present if recording was configured.
*/
- recordingManagement?: RecordingManagement;
+ recordingManagement?: RecordingManagement
- private microphoneService?: Microphone;
- private speakerService?: Speaker;
+ private microphoneService?: Microphone
+ private speakerService?: Speaker
- private microphoneMuted = false;
- private microphoneVolume = 100;
- private speakerMuted = false;
- private speakerVolume = 100;
+ private microphoneMuted = false
+ private microphoneVolume = 100
+ private speakerMuted = false
+ private speakerVolume = 100
- motionService?: MotionSensor;
- private motionServiceExternallySupplied = false;
- occupancyService?: OccupancySensor;
- private occupancyServiceExternallySupplied = false;
+ motionService?: MotionSensor
+ private motionServiceExternallySupplied = false
+ occupancyService?: OccupancySensor
+ private occupancyServiceExternallySupplied = false
constructor(options: CameraControllerOptions, legacyMode = false) {
- super();
- this.streamCount = Math.max(1, options.cameraStreamCount || 1);
- this.delegate = options.delegate;
- this.streamingOptions = options.streamingOptions;
- this.recording = options.recording;
- this.sensorOptions = options.sensors;
-
- this.legacyMode = legacyMode; // legacy mode will prevent from Microphone and Speaker services to get created to avoid collisions
+ super()
+ this.streamCount = Math.max(1, options.cameraStreamCount || 1)
+ this.delegate = options.delegate
+ this.streamingOptions = options.streamingOptions
+ this.recording = options.recording
+ this.sensorOptions = options.sensors
+
+ this.legacyMode = legacyMode // legacy mode will prevent from Microphone and Speaker services to get created to avoid collisions
}
/**
* @private
*/
controllerId(): ControllerIdentifier {
- return DefaultControllerType.CAMERA;
+ return DefaultControllerType.CAMERA
}
// ----------------------------------- STREAM API ------------------------------------
@@ -446,63 +463,63 @@ export class CameraController extends EventEmitter implements SerializableContro
* @param sessionId - id of the current ongoing streaming session
*/
public forceStopStreamingSession(sessionId: SessionIdentifier): void {
- this.streamManagements.forEach(management => {
+ this.streamManagements.forEach((management) => {
if (management.sessionIdentifier === sessionId) {
- management.forceStop();
+ management.forceStop()
}
- });
+ })
}
public static generateSynchronisationSource(): number {
- const ssrc = crypto.randomBytes(4); // range [-2.14748e+09 - 2.14748e+09]
- ssrc[0] = 0;
- return ssrc.readInt32BE(0);
+ const ssrc = randomBytes(4) // range [-2.14748e+09 - 2.14748e+09]
+ ssrc[0] = 0
+ return ssrc.readInt32BE(0)
}
// ----------------------------- MICROPHONE/SPEAKER API ------------------------------
public setMicrophoneMuted(muted = true): void {
if (!this.microphoneService) {
- return;
+ return
}
- this.microphoneMuted = muted;
- this.microphoneService.updateCharacteristic(Characteristic.Mute, muted);
+ this.microphoneMuted = muted
+ this.microphoneService.updateCharacteristic(Characteristic.Mute, muted)
}
public setMicrophoneVolume(volume: number): void {
if (!this.microphoneService) {
- return;
+ return
}
- this.microphoneVolume = volume;
- this.microphoneService.updateCharacteristic(Characteristic.Volume, volume);
+ this.microphoneVolume = volume
+ this.microphoneService.updateCharacteristic(Characteristic.Volume, volume)
}
public setSpeakerMuted(muted = true): void {
if (!this.speakerService) {
- return;
+ return
}
- this.speakerMuted = muted;
- this.speakerService.updateCharacteristic(Characteristic.Mute, muted);
+ this.speakerMuted = muted
+ this.speakerService.updateCharacteristic(Characteristic.Mute, muted)
}
public setSpeakerVolume(volume: number): void {
if (!this.speakerService) {
- return;
+ return
}
- this.speakerVolume = volume;
- this.speakerService.updateCharacteristic(Characteristic.Volume, volume);
+ this.speakerVolume = volume
+ this.speakerService.updateCharacteristic(Characteristic.Volume, volume)
}
private emitMicrophoneChange() {
- this.emit(CameraControllerEvents.MICROPHONE_PROPERTIES_CHANGED, this.microphoneMuted, this.microphoneVolume);
+ this.emit(CameraControllerEvents.MICROPHONE_PROPERTIES_CHANGED, this.microphoneMuted, this.microphoneVolume)
}
private emitSpeakerChange() {
- this.emit(CameraControllerEvents.SPEAKER_PROPERTIES_CHANGED, this.speakerMuted, this.speakerVolume);
+ this.emit(CameraControllerEvents.SPEAKER_PROPERTIES_CHANGED, this.speakerMuted, this.speakerVolume)
}
// -----------------------------------------------------------------------------------
@@ -511,112 +528,109 @@ export class CameraController extends EventEmitter implements SerializableContro
*/
constructServices(): CameraControllerServiceMap {
for (let i = 0; i < this.streamCount; i++) {
- const rtp = new RTPStreamManagement(i, this.streamingOptions, this.delegate, undefined, this.rtpStreamManagementDisabledThroughOperatingMode.bind(this));
- this.streamManagements.push(rtp);
+ const rtp = new RTPStreamManagement(i, this.streamingOptions, this.delegate, undefined, this.rtpStreamManagementDisabledThroughOperatingMode.bind(this))
+ this.streamManagements.push(rtp)
}
if (!this.legacyMode && this.streamingOptions.audio) {
// In theory the Microphone Service is a necessity. In practice, it's not. lol.
// So we just add it if the user wants to support audio
- this.microphoneService = new Service.Microphone("", "");
- this.microphoneService.setCharacteristic(Characteristic.Volume, this.microphoneVolume);
+ this.microphoneService = new Service.Microphone('', '')
+ this.microphoneService.setCharacteristic(Characteristic.Volume, this.microphoneVolume)
if (this.streamingOptions.audio.twoWayAudio) {
- this.speakerService = new Service.Speaker("", "");
- this.speakerService.setCharacteristic(Characteristic.Volume, this.speakerVolume);
+ this.speakerService = new Service.Speaker('', '')
+ this.speakerService.setCharacteristic(Characteristic.Volume, this.speakerVolume)
}
}
if (this.recording) {
- this.recordingManagement = new RecordingManagement(this.recording.options, this.recording.delegate, this.retrieveEventTriggerOptions());
+ this.recordingManagement = new RecordingManagement(this.recording.options, this.recording.delegate, this.retrieveEventTriggerOptions())
}
-
if (this.sensorOptions?.motion) {
- if (typeof this.sensorOptions.motion === "boolean") {
- this.motionService = new Service.MotionSensor("", "");
+ if (typeof this.sensorOptions.motion === 'boolean') {
+ this.motionService = new Service.MotionSensor('', '')
} else {
- this.motionService = this.sensorOptions.motion;
- this.motionServiceExternallySupplied = true;
+ this.motionService = this.sensorOptions.motion
+ this.motionServiceExternallySupplied = true
}
- this.motionService.setCharacteristic(Characteristic.StatusActive, true);
- this.recordingManagement?.recordingManagementService.addLinkedService(this.motionService);
+ this.motionService.setCharacteristic(Characteristic.StatusActive, true)
+ this.recordingManagement?.recordingManagementService.addLinkedService(this.motionService)
}
if (this.sensorOptions?.occupancy) {
- if (typeof this.sensorOptions.occupancy === "boolean") {
- this.occupancyService = new Service.OccupancySensor("", "");
+ if (typeof this.sensorOptions.occupancy === 'boolean') {
+ this.occupancyService = new Service.OccupancySensor('', '')
} else {
- this.occupancyService = this.sensorOptions.occupancy;
- this.occupancyServiceExternallySupplied = true;
+ this.occupancyService = this.sensorOptions.occupancy
+ this.occupancyServiceExternallySupplied = true
}
- this.occupancyService.setCharacteristic(Characteristic.StatusActive, true);
- this.recordingManagement?.recordingManagementService.addLinkedService(this.occupancyService);
+ this.occupancyService.setCharacteristic(Characteristic.StatusActive, true)
+ this.recordingManagement?.recordingManagementService.addLinkedService(this.occupancyService)
}
-
const serviceMap: CameraControllerServiceMap = {
microphone: this.microphoneService,
speaker: this.speakerService,
motionService: !this.motionServiceExternallySupplied ? this.motionService : undefined,
occupancyService: !this.occupancyServiceExternallySupplied ? this.occupancyService : undefined,
- };
+ }
if (this.recordingManagement) {
- serviceMap.cameraEventRecordingManagement = this.recordingManagement.recordingManagementService;
- serviceMap.cameraOperatingMode = this.recordingManagement.operatingModeService;
- serviceMap.dataStreamTransportManagement = this.recordingManagement.dataStreamManagement.getService();
+ serviceMap.cameraEventRecordingManagement = this.recordingManagement.recordingManagementService
+ serviceMap.cameraOperatingMode = this.recordingManagement.operatingModeService
+ serviceMap.dataStreamTransportManagement = this.recordingManagement.dataStreamManagement.getService()
}
this.streamManagements.forEach((management, index) => {
- serviceMap[CameraController.STREAM_MANAGEMENT + index] = management.getService();
- });
+ serviceMap[CameraController.STREAM_MANAGEMENT + index] = management.getService()
+ })
- this.recording = undefined;
- this.sensorOptions = undefined;
+ this.recording = undefined
+ this.sensorOptions = undefined
- return serviceMap;
+ return serviceMap
}
/**
* @private
*/
initWithServices(serviceMap: CameraControllerServiceMap): void | CameraControllerServiceMap {
- const result = this._initWithServices(serviceMap);
+ const result = this._initWithServices(serviceMap)
if (result.updated) { // serviceMap must only be returned if anything actually changed
- return result.serviceMap;
+ return result.serviceMap
}
}
protected _initWithServices(serviceMap: CameraControllerServiceMap): { serviceMap: CameraControllerServiceMap, updated: boolean } {
- let modifiedServiceMap = false;
+ let modifiedServiceMap = false
- // eslint-disable-next-line no-constant-condition
for (let i = 0; true; i++) {
- const streamManagementService = serviceMap[CameraController.STREAM_MANAGEMENT + i];
+ const streamManagementService = serviceMap[CameraController.STREAM_MANAGEMENT + i]
if (i < this.streamCount) {
- const operatingModeClosure = this.rtpStreamManagementDisabledThroughOperatingMode.bind(this);
+ const operatingModeClosure = this.rtpStreamManagementDisabledThroughOperatingMode.bind(this)
if (streamManagementService) { // normal init
- this.streamManagements.push(new RTPStreamManagement(i, this.streamingOptions, this.delegate, streamManagementService, operatingModeClosure));
+ this.streamManagements.push(new RTPStreamManagement(i, this.streamingOptions, this.delegate, streamManagementService, operatingModeClosure))
} else { // stream count got bigger, we need to create a new service
- const management = new RTPStreamManagement(i, this.streamingOptions, this.delegate, undefined, operatingModeClosure);
+ const management = new RTPStreamManagement(i, this.streamingOptions, this.delegate, undefined, operatingModeClosure)
- this.streamManagements.push(management);
- serviceMap[CameraController.STREAM_MANAGEMENT + i] = management.getService();
+ this.streamManagements.push(management)
+ serviceMap[CameraController.STREAM_MANAGEMENT + i] = management.getService()
- modifiedServiceMap = true;
+ modifiedServiceMap = true
}
} else {
if (streamManagementService) { // stream count got reduced, we need to remove old service
- delete serviceMap[CameraController.STREAM_MANAGEMENT + i];
- modifiedServiceMap = true;
+ delete serviceMap[CameraController.STREAM_MANAGEMENT + i]
+ modifiedServiceMap = true
} else {
- break; // we finished counting, and we got no saved service; we are finished
+ break // we finished counting, and we got no saved service; we are finished
}
}
}
@@ -624,42 +638,42 @@ export class CameraController extends EventEmitter implements SerializableContro
// MICROPHONE
if (!this.legacyMode && this.streamingOptions.audio) { // microphone should be present
if (serviceMap.microphone) {
- this.microphoneService = serviceMap.microphone;
+ this.microphoneService = serviceMap.microphone
} else {
// microphone wasn't created yet => create a new one
- this.microphoneService = new Service.Microphone("", "");
- this.microphoneService.setCharacteristic(Characteristic.Volume, this.microphoneVolume);
+ this.microphoneService = new Service.Microphone('', '')
+ this.microphoneService.setCharacteristic(Characteristic.Volume, this.microphoneVolume)
- serviceMap.microphone = this.microphoneService;
- modifiedServiceMap = true;
+ serviceMap.microphone = this.microphoneService
+ modifiedServiceMap = true
}
} else if (serviceMap.microphone) { // microphone service supplied, though settings seemed to have changed
// we need to remove it
- delete serviceMap.microphone;
- modifiedServiceMap = true;
+ delete serviceMap.microphone
+ modifiedServiceMap = true
}
// SPEAKER
if (!this.legacyMode && this.streamingOptions.audio?.twoWayAudio) { // speaker should be present
if (serviceMap.speaker) {
- this.speakerService = serviceMap.speaker;
+ this.speakerService = serviceMap.speaker
} else {
// speaker wasn't created yet => create a new one
- this.speakerService = new Service.Speaker("", "");
- this.speakerService.setCharacteristic(Characteristic.Volume, this.speakerVolume);
+ this.speakerService = new Service.Speaker('', '')
+ this.speakerService.setCharacteristic(Characteristic.Volume, this.speakerVolume)
- serviceMap.speaker = this.speakerService;
- modifiedServiceMap = true;
+ serviceMap.speaker = this.speakerService
+ modifiedServiceMap = true
}
} else if (serviceMap.speaker) { // speaker service supplied, though settings seemed to have changed
// we need to remove it
- delete serviceMap.speaker;
- modifiedServiceMap = true;
+ delete serviceMap.speaker
+ modifiedServiceMap = true
}
// RECORDING
if (this.recording) {
- const eventTriggers = this.retrieveEventTriggerOptions();
+ const eventTriggers = this.retrieveEventTriggerOptions()
// RECORDING MANAGEMENT
if (serviceMap.cameraEventRecordingManagement && serviceMap.cameraOperatingMode && serviceMap.dataStreamTransportManagement) {
@@ -672,139 +686,139 @@ export class CameraController extends EventEmitter implements SerializableContro
operatingMode: serviceMap.cameraOperatingMode,
dataStreamManagement: new DataStreamManagement(serviceMap.dataStreamTransportManagement),
},
- );
+ )
} else {
this.recordingManagement = new RecordingManagement(
this.recording.options,
this.recording.delegate,
eventTriggers,
- );
+ )
- serviceMap.cameraEventRecordingManagement = this.recordingManagement.recordingManagementService;
- serviceMap.cameraOperatingMode = this.recordingManagement.operatingModeService;
- serviceMap.dataStreamTransportManagement = this.recordingManagement.dataStreamManagement.getService();
- modifiedServiceMap = true;
+ serviceMap.cameraEventRecordingManagement = this.recordingManagement.recordingManagementService
+ serviceMap.cameraOperatingMode = this.recordingManagement.operatingModeService
+ serviceMap.dataStreamTransportManagement = this.recordingManagement.dataStreamManagement.getService()
+ modifiedServiceMap = true
}
} else {
if (serviceMap.cameraEventRecordingManagement) {
- delete serviceMap.cameraEventRecordingManagement;
- modifiedServiceMap = true;
+ delete serviceMap.cameraEventRecordingManagement
+ modifiedServiceMap = true
}
if (serviceMap.cameraOperatingMode) {
- delete serviceMap.cameraOperatingMode;
- modifiedServiceMap = true;
+ delete serviceMap.cameraOperatingMode
+ modifiedServiceMap = true
}
if (serviceMap.dataStreamTransportManagement) {
- delete serviceMap.dataStreamTransportManagement;
- modifiedServiceMap = true;
+ delete serviceMap.dataStreamTransportManagement
+ modifiedServiceMap = true
}
}
// MOTION SENSOR
if (this.sensorOptions?.motion) {
- if (typeof this.sensorOptions.motion === "boolean") {
+ if (typeof this.sensorOptions.motion === 'boolean') {
if (serviceMap.motionService) {
- this.motionService = serviceMap.motionService;
+ this.motionService = serviceMap.motionService
} else {
// it could be the case that we previously had a manually supplied motion service
// at this point we can't remove the iid from the list of linked services from the recording management!
- this.motionService = new Service.MotionSensor("", "");
+ this.motionService = new Service.MotionSensor('', '')
}
} else {
- this.motionService = this.sensorOptions.motion;
- this.motionServiceExternallySupplied = true;
+ this.motionService = this.sensorOptions.motion
+ this.motionServiceExternallySupplied = true
if (serviceMap.motionService) { // motion service previously supplied as bool option
- this.recordingManagement?.recordingManagementService.removeLinkedService(serviceMap.motionService);
- delete serviceMap.motionService;
- modifiedServiceMap = true;
+ this.recordingManagement?.recordingManagementService.removeLinkedService(serviceMap.motionService)
+ delete serviceMap.motionService
+ modifiedServiceMap = true
}
}
- this.motionService.setCharacteristic(Characteristic.StatusActive, true);
- this.recordingManagement?.recordingManagementService.addLinkedService(this.motionService);
+ this.motionService.setCharacteristic(Characteristic.StatusActive, true)
+ this.recordingManagement?.recordingManagementService.addLinkedService(this.motionService)
} else {
if (serviceMap.motionService) {
- this.recordingManagement?.recordingManagementService.removeLinkedService(serviceMap.motionService);
- delete serviceMap.motionService;
- modifiedServiceMap = true;
+ this.recordingManagement?.recordingManagementService.removeLinkedService(serviceMap.motionService)
+ delete serviceMap.motionService
+ modifiedServiceMap = true
}
}
// OCCUPANCY SENSOR
if (this.sensorOptions?.occupancy) {
- if (typeof this.sensorOptions.occupancy === "boolean") {
+ if (typeof this.sensorOptions.occupancy === 'boolean') {
if (serviceMap.occupancyService) {
- this.occupancyService = serviceMap.occupancyService;
+ this.occupancyService = serviceMap.occupancyService
} else {
// it could be the case that we previously had a manually supplied occupancy service
// at this point we can't remove the iid from the list of linked services from the recording management!
- this.occupancyService = new Service.OccupancySensor("", "");
+ this.occupancyService = new Service.OccupancySensor('', '')
}
} else {
- this.occupancyService = this.sensorOptions.occupancy;
- this.occupancyServiceExternallySupplied = true;
+ this.occupancyService = this.sensorOptions.occupancy
+ this.occupancyServiceExternallySupplied = true
if (serviceMap.occupancyService) { // occupancy service previously supplied as bool option
- this.recordingManagement?.recordingManagementService.removeLinkedService(serviceMap.occupancyService);
- delete serviceMap.occupancyService;
- modifiedServiceMap = true;
+ this.recordingManagement?.recordingManagementService.removeLinkedService(serviceMap.occupancyService)
+ delete serviceMap.occupancyService
+ modifiedServiceMap = true
}
}
- this.occupancyService.setCharacteristic(Characteristic.StatusActive, true);
- this.recordingManagement?.recordingManagementService.addLinkedService(this.occupancyService);
+ this.occupancyService.setCharacteristic(Characteristic.StatusActive, true)
+ this.recordingManagement?.recordingManagementService.addLinkedService(this.occupancyService)
} else {
if (serviceMap.occupancyService) {
- this.recordingManagement?.recordingManagementService.removeLinkedService(serviceMap.occupancyService);
- delete serviceMap.occupancyService;
- modifiedServiceMap = true;
+ this.recordingManagement?.recordingManagementService.removeLinkedService(serviceMap.occupancyService)
+ delete serviceMap.occupancyService
+ modifiedServiceMap = true
}
}
if (this.migrateFromDoorbell(serviceMap)) {
- modifiedServiceMap = true;
+ modifiedServiceMap = true
}
- this.recording = undefined;
- this.sensorOptions = undefined;
+ this.recording = undefined
+ this.sensorOptions = undefined
return {
- serviceMap: serviceMap,
+ serviceMap,
updated: modifiedServiceMap,
- };
+ }
}
// overwritten in DoorbellController (to avoid cyclic dependencies, I hate typescript for that)
protected migrateFromDoorbell(serviceMap: ControllerServiceMap): boolean {
if (serviceMap.doorbell) { // See NOTICE in DoorbellController
- delete serviceMap.doorbell;
- return true;
+ delete serviceMap.doorbell
+ return true
}
- return false;
+ return false
}
protected retrieveEventTriggerOptions(): Set {
if (!this.recording) {
- return new Set();
+ return new Set()
}
- const triggerOptions = new Set();
+ const triggerOptions = new Set()
if (this.recording.options.overrideEventTriggerOptions) {
for (const option of this.recording.options.overrideEventTriggerOptions) {
- triggerOptions.add(option);
+ triggerOptions.add(option)
}
}
if (this.sensorOptions?.motion) {
- triggerOptions.add(EventTriggerOption.MOTION);
+ triggerOptions.add(EventTriggerOption.MOTION)
}
// this method is overwritten by the `DoorbellController` to automatically configure EventTriggerOption.DOORBELL
- return triggerOptions;
+ return triggerOptions
}
/**
@@ -814,111 +828,110 @@ export class CameraController extends EventEmitter implements SerializableContro
if (this.microphoneService) {
this.microphoneService.getCharacteristic(Characteristic.Mute)!
.on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
- callback(undefined, this.microphoneMuted);
+ callback(undefined, this.microphoneMuted)
})
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- this.microphoneMuted = value as boolean;
- callback();
- this.emitMicrophoneChange();
- });
+ this.microphoneMuted = value as boolean
+ callback()
+ this.emitMicrophoneChange()
+ })
this.microphoneService.getCharacteristic(Characteristic.Volume)!
.on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
- callback(undefined, this.microphoneVolume);
+ callback(undefined, this.microphoneVolume)
})
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- this.microphoneVolume = value as number;
- callback();
- this.emitMicrophoneChange();
- });
+ this.microphoneVolume = value as number
+ callback()
+ this.emitMicrophoneChange()
+ })
}
if (this.speakerService) {
this.speakerService.getCharacteristic(Characteristic.Mute)!
.on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
- callback(undefined, this.speakerMuted);
+ callback(undefined, this.speakerMuted)
})
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- this.speakerMuted = value as boolean;
- callback();
- this.emitSpeakerChange();
- });
+ this.speakerMuted = value as boolean
+ callback()
+ this.emitSpeakerChange()
+ })
this.speakerService.getCharacteristic(Characteristic.Volume)!
.on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => {
- callback(undefined, this.speakerVolume);
+ callback(undefined, this.speakerVolume)
})
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
- this.speakerVolume = value as number;
- callback();
- this.emitSpeakerChange();
- });
+ this.speakerVolume = value as number
+ callback()
+ this.emitSpeakerChange()
+ })
}
// make the sensor services available to the RecordingManagement.
if (this.motionService) {
- this.recordingManagement?.sensorServices.push(this.motionService);
+ this.recordingManagement?.sensorServices.push(this.motionService)
}
if (this.occupancyService) {
- this.recordingManagement?.sensorServices.push(this.occupancyService);
+ this.recordingManagement?.sensorServices.push(this.occupancyService)
}
}
private rtpStreamManagementDisabledThroughOperatingMode(): boolean {
return this.recordingManagement
? !this.recordingManagement.operatingModeService.getCharacteristic(Characteristic.HomeKitCameraActive).value
- : false;
+ : false
}
-
/**
* @private
*/
handleControllerRemoved(): void {
- this.handleFactoryReset();
+ this.handleFactoryReset()
for (const management of this.streamManagements) {
- management.destroy();
+ management.destroy()
}
- this.streamManagements.splice(0, this.streamManagements.length);
+ this.streamManagements.splice(0, this.streamManagements.length)
- this.microphoneService = undefined;
- this.speakerService = undefined;
+ this.microphoneService = undefined
+ this.speakerService = undefined
- this.recordingManagement?.destroy();
- this.recordingManagement = undefined;
+ this.recordingManagement?.destroy()
+ this.recordingManagement = undefined
- this.removeAllListeners();
+ this.removeAllListeners()
}
/**
* @private
*/
handleFactoryReset(): void {
- this.streamManagements.forEach(management => management.handleFactoryReset());
- this.recordingManagement?.handleFactoryReset();
+ this.streamManagements.forEach(management => management.handleFactoryReset())
+ this.recordingManagement?.handleFactoryReset()
- this.microphoneMuted = false;
- this.microphoneVolume = 100;
- this.speakerMuted = false;
- this.speakerVolume = 100;
+ this.microphoneMuted = false
+ this.microphoneVolume = 100
+ this.speakerMuted = false
+ this.speakerVolume = 100
}
/**
* @private
*/
serialize(): CameraControllerState | undefined {
- const streamManagementStates: RTPStreamManagementState[] = [];
+ const streamManagementStates: RTPStreamManagementState[] = []
for (const management of this.streamManagements) {
- const serializedState = management.serialize();
+ const serializedState = management.serialize()
if (serializedState) {
- streamManagementStates.push(serializedState);
+ streamManagementStates.push(serializedState)
}
}
return {
streamManagements: streamManagementStates,
recordingManagement: this.recordingManagement?.serialize(),
- };
+ }
}
/**
@@ -926,22 +939,22 @@ export class CameraController extends EventEmitter implements SerializableContro
*/
deserialize(serialized: CameraControllerState): void {
for (const streamManagementState of serialized.streamManagements) {
- const streamManagement = this.streamManagements[streamManagementState.id];
+ const streamManagement = this.streamManagements[streamManagementState.id]
if (streamManagement) {
- streamManagement.deserialize(streamManagementState);
+ streamManagement.deserialize(streamManagementState)
}
}
if (serialized.recordingManagement) {
if (this.recordingManagement) {
- this.recordingManagement.deserialize(serialized.recordingManagement);
+ this.recordingManagement.deserialize(serialized.recordingManagement)
} else {
// Active characteristic cannot be controlled if removing HSV, ensure they are all active!
for (const streamManagement of this.streamManagements) {
- streamManagement.service.updateCharacteristic(Characteristic.Active, true);
+ streamManagement.service.updateCharacteristic(Characteristic.Active, true)
}
- this.stateChangeDelegate?.();
+ this.stateChangeDelegate?.()
}
}
}
@@ -950,13 +963,13 @@ export class CameraController extends EventEmitter implements SerializableContro
* @private
*/
setupStateChangeDelegate(delegate?: StateChangeDelegate): void {
- this.stateChangeDelegate = delegate;
+ this.stateChangeDelegate = delegate
for (const streamManagement of this.streamManagements) {
- streamManagement.setupStateChangeDelegate(delegate);
+ streamManagement.setupStateChangeDelegate(delegate)
}
- this.recordingManagement?.setupStateChangeDelegate(delegate);
+ this.recordingManagement?.setupStateChangeDelegate(delegate)
}
/**
@@ -966,42 +979,42 @@ export class CameraController extends EventEmitter implements SerializableContro
// first step is to verify that the reason is applicable to our current policy
const streamingDisabled = this.streamManagements
.map(management => !management.getService().getCharacteristic(Characteristic.Active).value)
- .reduce((previousValue, currentValue) => previousValue && currentValue);
+ .reduce((previousValue, currentValue) => previousValue && currentValue)
if (streamingDisabled) {
- debug("[%s] Rejecting snapshot as streaming is disabled.", accessoryName);
- return Promise.reject(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE);
+ debug('[%s] Rejecting snapshot as streaming is disabled.', accessoryName)
+ return Promise.reject(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE)
}
if (this.recordingManagement) {
- const operatingModeService = this.recordingManagement.operatingModeService;
+ const operatingModeService = this.recordingManagement.operatingModeService
if (!operatingModeService.getCharacteristic(Characteristic.HomeKitCameraActive).value) {
- debug("[%s] Rejecting snapshot as HomeKit camera is disabled.", accessoryName);
- return Promise.reject(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE);
+ debug('[%s] Rejecting snapshot as HomeKit camera is disabled.', accessoryName)
+ return Promise.reject(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE)
}
const eventSnapshotsActive = operatingModeService
.getCharacteristic(Characteristic.EventSnapshotsActive)
- .value;
+ .value
if (!eventSnapshotsActive) {
if (reason == null) {
- debug("[%s] Rejecting snapshot as reason is required due to disabled event snapshots.", accessoryName);
- return Promise.reject(HAPStatus.INSUFFICIENT_PRIVILEGES);
+ debug('[%s] Rejecting snapshot as reason is required due to disabled event snapshots.', accessoryName)
+ return Promise.reject(HAPStatus.INSUFFICIENT_PRIVILEGES)
} else if (reason === ResourceRequestReason.EVENT) {
- debug("[%s] Rejecting snapshot as even snapshots are disabled.", accessoryName);
- return Promise.reject(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE);
+ debug('[%s] Rejecting snapshot as even snapshots are disabled.', accessoryName)
+ return Promise.reject(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE)
}
}
const periodicSnapshotsActive = operatingModeService
.getCharacteristic(Characteristic.PeriodicSnapshotsActive)
- .value;
+ .value
if (!periodicSnapshotsActive) {
if (reason == null) {
- debug("[%s] Rejecting snapshot as reason is required due to disabled periodic snapshots.", accessoryName);
- return Promise.reject(HAPStatus.INSUFFICIENT_PRIVILEGES);
+ debug('[%s] Rejecting snapshot as reason is required due to disabled periodic snapshots.', accessoryName)
+ return Promise.reject(HAPStatus.INSUFFICIENT_PRIVILEGES)
} else if (reason === ResourceRequestReason.PERIODIC) {
- debug("[%s] Rejecting snapshot as periodic snapshots are disabled.", accessoryName);
- return Promise.reject(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE);
+ debug('[%s] Rejecting snapshot as periodic snapshots are disabled.', accessoryName)
+ return Promise.reject(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE)
}
}
}
@@ -1012,62 +1025,62 @@ export class CameraController extends EventEmitter implements SerializableContro
let timeout: NodeJS.Timeout | undefined = setTimeout(() => {
console.warn(
`[${accessoryName}] The image snapshot handler for the given accessory is slow to respond! See https://homebridge.io/w/JtMGR for more info.`,
- );
+ )
timeout = setTimeout(() => {
- timeout = undefined;
+ timeout = undefined
console.warn(
`[${accessoryName}] The image snapshot handler for the given accessory didn't respond at all! See https://homebridge.io/w/JtMGR for more info.`,
- );
+ )
- reject(HAPStatus.OPERATION_TIMED_OUT);
- }, 17000);
- timeout.unref();
- }, 8000);
- timeout.unref();
+ reject(HAPStatus.OPERATION_TIMED_OUT)
+ }, 17000)
+ timeout.unref()
+ }, 8000)
+ timeout.unref()
try {
this.delegate.handleSnapshotRequest({
- height: height,
- width: width,
- reason: reason,
+ height,
+ width,
+ reason,
}, (error, buffer) => {
if (!timeout) {
- return;
+ return
} else {
- clearTimeout(timeout);
- timeout = undefined;
+ clearTimeout(timeout)
+ timeout = undefined
}
if (error) {
- if (typeof error === "number") {
- reject(error);
+ if (typeof error === 'number') {
+ reject(error)
} else {
- debug("[%s] Error getting snapshot: %s", accessoryName, error.stack);
- reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
+ debug('[%s] Error getting snapshot: %s', accessoryName, error.stack)
+ reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
}
- return;
+ return
}
if (!buffer || buffer.length === 0) {
- console.warn(`[${accessoryName}] Snapshot request handler provided empty image buffer!`);
- reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
+ console.warn(`[${accessoryName}] Snapshot request handler provided empty image buffer!`)
+ reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE)
} else {
- resolve(buffer);
+ resolve(buffer)
}
- });
+ })
} catch (error) {
if (!timeout) {
- return;
+ return
} else {
- clearTimeout(timeout);
- timeout = undefined;
+ clearTimeout(timeout)
+ timeout = undefined
}
- console.warn(`[${accessoryName}] Unhandled error thrown inside snapshot request handler: ${error.stack}`);
- reject(error instanceof HapStatusError ? error.hapStatus : HAPStatus.SERVICE_COMMUNICATION_FAILURE);
+ console.warn(`[${accessoryName}] Unhandled error thrown inside snapshot request handler: ${error.stack}`)
+ reject(error instanceof HapStatusError ? error.hapStatus : HAPStatus.SERVICE_COMMUNICATION_FAILURE)
}
- });
+ })
}
}
diff --git a/src/lib/controller/Controller.ts b/src/lib/controller/Controller.ts
index af186cbb6..34149536d 100644
--- a/src/lib/controller/Controller.ts
+++ b/src/lib/controller/Controller.ts
@@ -1,4 +1,4 @@
-import { Service } from "../Service";
+import type { Service } from '../Service'
/**
* A ControllerServiceMap represents all services used by a Controller.
@@ -7,7 +7,7 @@ import { Service } from "../Service";
* @group Controller API
*/
export interface ControllerServiceMap {
- [name: string]: Service | undefined,
+ [name: string]: Service | undefined
}
/**
@@ -18,39 +18,40 @@ export interface ControllerServiceMap {
* You can define custom ControllerTypes if you wish to, but be careful that it does not collide with any known definitions.
* @group Controller API
*/
-export type ControllerType = string | DefaultControllerType;
+export type ControllerType = string | DefaultControllerType
/**
* @group Controller API
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum DefaultControllerType {
- CAMERA = "camera", // or doorbell
- REMOTE = "remote",
- TV = "tv",
- ROUTER = "router",
- LOCK = "lock",
- CHARACTERISTIC_TRANSITION = "characteristic-transition",
+ CAMERA = 'camera', // or doorbell
+ REMOTE = 'remote',
+ TV = 'tv',
+ ROUTER = 'router',
+ LOCK = 'lock',
+ CHARACTERISTIC_TRANSITION = 'characteristic-transition',
}
/**
* @group Controller API
*/
-export type ControllerIdentifier = string | ControllerType;
+export type ControllerIdentifier = string | ControllerType
/**
* @group Controller API
*/
-export type StateChangeDelegate = () => void;
+export type StateChangeDelegate = () => void
/**
* @group Controller API
*/
export interface ControllerConstructor {
- new(): Controller;
+ new(): Controller
}
/**
- * A Controller represents a somewhat more complex arrangement of multiple services which together form a accessory
+ * A Controller represents a somewhat more complex arrangement of multiple services which together form an accessory
* like for example cameras, remotes, tvs or routers.
* Controllers implementing this interface are capable of being serialized and thus stored on and recreated from disk.
* Meaning services, characteristic configurations and optionally additional controller states can be persistently saved.
@@ -64,85 +65,90 @@ export interface ControllerConstructor {
* The constructor of a Controller should only initialize controller specific configuration and states
* and MUST NOT create any services or characteristics.
* Additionally, it must implement all necessary methods as noted below. Those methods will get called
- * when the accessory gets added to an Accessory or a Accessory is restored from disk.
+ * when the accessory gets added to an Accessory or an Accessory is restored from disk.
* @group Controller API
*/
export interface Controller {
- /**
- * Every instance of a Controller must define appropriate identifying material.
- * The returned identifier MUST NOT change over the lifetime of the Controller object.
- *
- * Note: The controller can choose to return the same identifier for all controllers of the same type.
- * This will result in the user only being able to add ONE instance of an Controller to an accessory.
- *
- * Some predefined identifiers can be found in {@link ControllerIdentifier}.
- */
- controllerId(): ControllerIdentifier;
-
- /**
- * This method is called by the accessory the controller is added to. This method is only called if a new controller
- * is constructed (aka the controller is not restored from disk {@link initWithServices}).
- * It MUST create all needed services and characteristics.
- * It MAY create links between services or mark them as hidden or primary.
- * It MUST NOT configure any event handlers.
- * The controller SHOULD save created services in internal properties for later access.
- *
- * The method must return all created services in a ServiceMap.
- * A {@link ControllerServiceMap} basically maps a name to every service on the controller.
- * It is used to potentially recreate a controller for a given ServiceMap using {@link initWithServices}.
- *
- * The set of services represented by the Controller MUST remain static and can only change over new version of
- * the Controller implementation (see {@link initWithServices})
- *
- * @returns a {@link ControllerServiceMap} representing all services of a controller indexed by a controller chosen name.
- */
- constructServices(): M;
-
- /**
- * This method is called to initialize the controller with already created services.
- * The controller SHOULD save the passed services in internal properties for later access.
- *
- * The controller can return a ServiceMap to signal that the set of services changed.
- * A Controller MUST modify the ServiceMap which is passed to the method and MUST NOT create a new one (to support inheritance).
- * It MUST NOT return a ServiceMap if the service configuration did not change!
- * It MUST be able to restore services using a ServiceMap from any point in time.
- *
- * @param serviceMap - A {@link ControllerServiceMap} that represents all services of a controller indexed by the controller chosen name.
- * @returns optionally a {@link ControllerServiceMap}. This can be used to alter the services configuration of a controller.
- */
- initWithServices(serviceMap: M): M | void;
-
- /**
- * This method is called to configure the services and their characteristics of the controller.
- * When this method is called, it is guaranteed that either {@link constructServices} or {@link initWithServices}
- * were called before and all services are already created.
- *
- * This method SHOULD set up all necessary event handlers for services and characteristics.
- */
- configureServices(): void;
-
- /**
- * This method is called once the Controller is removed from the accessory.
- * The controller MUST reset everything to its initial state (just as it would have been constructed freshly)
- * form the constructor.
- * Adding the Controller back to an accessory after it was removed MUST be supported!
- * If the controller is a {@link SerializableController} it MUST NOT call the {@link StateChangeDelegate}
- * as a result of a call to this method.
- *
- * All service contained in the {@link ControllerServiceMap} returned by {@link constructServices}
- * will be automatically removed from the Accessory. The Controller MUST remove any references to those services.
- */
- handleControllerRemoved(): void;
-
- /**
- * This method is called to signal a factory reset of the controller and its services and characteristics.
- * A controller MUST reset any configuration or states to default values.
- *
- * This method is called once the accessory gets unpaired or the Controller gets removed from the Accessory.
- */
- handleFactoryReset?(): void;
-
+ /**
+ * Every instance of a Controller must define appropriate identifying material.
+ * The returned identifier MUST NOT change over the lifetime of the Controller object.
+ *
+ * Note: The controller can choose to return the same identifier for all controllers of the same type.
+ * This will result in the user only being able to add ONE instance of a Controller to an accessory.
+ *
+ * Some predefined identifiers can be found in {@link ControllerIdentifier}.
+ */
+ // eslint-disable-next-line ts/method-signature-style
+ controllerId(): ControllerIdentifier
+
+ /**
+ * This method is called by the accessory the controller is added to. This method is only called if a new controller
+ * is constructed (aka the controller is not restored from disk {@link initWithServices}).
+ * It MUST create all needed services and characteristics.
+ * It MAY create links between services or mark them as hidden or primary.
+ * It MUST NOT configure any event handlers.
+ * The controller SHOULD save created services in internal properties for later access.
+ *
+ * The method must return all created services in a ServiceMap.
+ * A {@link ControllerServiceMap} basically maps a name to every service on the controller.
+ * It is used to potentially recreate a controller for a given ServiceMap using {@link initWithServices}.
+ *
+ * The set of services represented by the Controller MUST remain static and can only change over new version of
+ * the Controller implementation (see {@link initWithServices})
+ *
+ * @returns a {@link ControllerServiceMap} representing all services of a controller indexed by a controller chosen name.
+ */
+ // eslint-disable-next-line ts/method-signature-style
+ constructServices(): M
+
+ /**
+ * This method is called to initialize the controller with already created services.
+ * The controller SHOULD save the passed services in internal properties for later access.
+ *
+ * The controller can return a ServiceMap to signal that the set of services changed.
+ * A Controller MUST modify the ServiceMap which is passed to the method and MUST NOT create a new one (to support inheritance).
+ * It MUST NOT return a ServiceMap if the service configuration did not change!
+ * It MUST be able to restore services using a ServiceMap from any point in time.
+ *
+ * @param serviceMap - A {@link ControllerServiceMap} that represents all services of a controller indexed by the controller chosen name.
+ * @returns optionally a {@link ControllerServiceMap}. This can be used to alter the services configuration of a controller.
+ */
+ // eslint-disable-next-line ts/method-signature-style
+ initWithServices(serviceMap: M): M | void
+
+ /**
+ * This method is called to configure the services and their characteristics of the controller.
+ * When this method is called, it is guaranteed that either {@link constructServices} or {@link initWithServices}
+ * were called before and all services are already created.
+ *
+ * This method SHOULD set up all necessary event handlers for services and characteristics.
+ */
+ // eslint-disable-next-line ts/method-signature-style
+ configureServices(): void
+
+ /**
+ * This method is called once the Controller is removed from the accessory.
+ * The controller MUST reset everything to its initial state (just as it would have been constructed freshly)
+ * form the constructor.
+ * Adding the Controller back to an accessory after it was removed MUST be supported!
+ * If the controller is a {@link SerializableController} it MUST NOT call the {@link StateChangeDelegate}
+ * as a result of a call to this method.
+ *
+ * All service contained in the {@link ControllerServiceMap} returned by {@link constructServices}
+ * will be automatically removed from the Accessory. The Controller MUST remove any references to those services.
+ */
+ // eslint-disable-next-line ts/method-signature-style
+ handleControllerRemoved(): void
+
+ /**
+ * This method is called to signal a factory reset of the controller and its services and characteristics.
+ * A controller MUST reset any configuration or states to default values.
+ *
+ * This method is called once the accessory gets unpaired or the Controller gets removed from the Accessory.
+ */
+ // eslint-disable-next-line ts/method-signature-style
+ handleFactoryReset?(): void
}
/**
@@ -150,41 +156,44 @@ export interface Controller extends Controller {
- /**
- * This method can be used to persistently save controller related configuration across reboots.
- * It should return undefined, if the controller data was reset to default values and nothing needs to be stored anymore.
- *
- * @returns an arbitrary Controller defined object containing all necessary data
- */
- serialize(): S | undefined;
-
- /**
- * This method is called to restore the controller state from disk.
- * This is only called once, when the data was loaded from disk and the Accessory is to be published.
- * A controller MUST provide backwards compatibility for any configuration layout exposed at any time.
- * A Controller MUST NOT depend on any specific calling order.
- *
- * @param serialized
- */
- deserialize(serialized: S): void;
-
- /**
- * This method is inherited from {@link Controller.handleFactoryReset} though is required with {@link SerializableController}.
- */
- handleFactoryReset(): void;
-
- /**
- * This method is called once upon setup. It supplies a function used by the Controller to signal state changes.
- * The implementing controller SHOULD store the function and call it every time the internal controller state changes.
- * It should be expected that the {@link serialize} method will be called next and that the state will be stored
- * to disk.
- * The delegate parameter can be undefined when the controller is removed and the state change delegate is reset.
- *
- * @param delegate - The {@link StateChangeDelegate} to call when controller state has changed
- */
- setupStateChangeDelegate(delegate?: StateChangeDelegate): void;
+ /**
+ * This method can be used to persistently save controller related configuration across reboots.
+ * It should return undefined, if the controller data was reset to default values and nothing needs to be stored anymore.
+ *
+ * @returns an arbitrary Controller defined object containing all necessary data
+ */
+ // eslint-disable-next-line ts/method-signature-style
+ serialize(): S | undefined
+
+ /**
+ * This method is called to restore the controller state from disk.
+ * This is only called once, when the data was loaded from disk and the Accessory is to be published.
+ * A controller MUST provide backwards compatibility for any configuration layout exposed at any time.
+ * A Controller MUST NOT depend on any specific calling order.
+ *
+ * @param serialized
+ */
+ // eslint-disable-next-line ts/method-signature-style
+ deserialize(serialized: S): void
+
+ /**
+ * This method is inherited from {@link Controller.handleFactoryReset} though is required with {@link SerializableController}.
+ */
+ // eslint-disable-next-line ts/method-signature-style
+ handleFactoryReset(): void
+
+ /**
+ * This method is called once upon setup. It supplies a function used by the Controller to signal state changes.
+ * The implementing controller SHOULD store the function and call it every time the internal controller state changes.
+ * It should be expected that the {@link serialize} method will be called next and that the state will be stored
+ * to disk.
+ * The delegate parameter can be undefined when the controller is removed and the state change delegate is reset.
+ *
+ * @param delegate - The {@link StateChangeDelegate} to call when controller state has changed
+ */
+ // eslint-disable-next-line ts/method-signature-style
+ setupStateChangeDelegate(delegate?: StateChangeDelegate): void
}
/**
@@ -192,5 +201,5 @@ export interface SerializableController {
- test("event trigger options", () => {
- const controller = new DoorbellController(createCameraControllerOptions());
+import { EventTriggerOption } from '../camera/index.js'
+import { createCameraControllerOptions } from './CameraController.spec.js'
+import { DoorbellController } from './DoorbellController.js'
+
+describe('doorbellController', () => {
+ it('event trigger options', () => {
+ const controller = new DoorbellController(createCameraControllerOptions())
// @ts-expect-error: protected access
- const options = controller.retrieveEventTriggerOptions();
- expect(new Array(...options).sort()).toEqual([EventTriggerOption.MOTION, EventTriggerOption.DOORBELL]);
+ const options = controller.retrieveEventTriggerOptions()
+ expect([...options].sort()).toEqual([EventTriggerOption.MOTION, EventTriggerOption.DOORBELL])
- controller.constructServices();
- controller.configureServices();
+ controller.constructServices()
+ controller.configureServices()
- expect(controller.recordingManagement).toBeTruthy();
+ expect(controller.recordingManagement).toBeTruthy()
// @ts-expect-error: private access
- expect(controller.recordingManagement.eventTriggerOptions).toEqual(3);
- });
-});
+ expect(controller.recordingManagement.eventTriggerOptions).toEqual(3)
+ })
+})
diff --git a/src/lib/controller/DoorbellController.ts b/src/lib/controller/DoorbellController.ts
index e7f7a20fa..df68e5a6d 100644
--- a/src/lib/controller/DoorbellController.ts
+++ b/src/lib/controller/DoorbellController.ts
@@ -1,9 +1,11 @@
-import { EventTriggerOption } from "../camera";
-import { Characteristic } from "../Characteristic";
-import type { Doorbell } from "../definitions";
-import { Service } from "../Service";
-import { CameraController, CameraControllerOptions, CameraControllerServiceMap } from "./CameraController";
-import { ControllerServiceMap } from "./Controller";
+import type { Doorbell } from '../definitions'
+import type { CameraControllerOptions, CameraControllerServiceMap } from './CameraController'
+import type { ControllerServiceMap } from './Controller'
+
+import { EventTriggerOption } from '../camera/index.js'
+import { Characteristic } from '../Characteristic.js'
+import { Service } from '../Service.js'
+import { CameraController } from './CameraController.js'
/**
* Options which are additionally supplied for a {@link DoorbellController}.
@@ -14,7 +16,7 @@ export interface DoorbellOptions {
/**
* Name used to for the {@link Service.Doorbell} service.
*/
- name?: string;
+ name?: string
/**
* This property may be used to supply an external {@link Service.Doorbell}.
@@ -39,100 +41,100 @@ export interface DoorbellOptions {
* @group Doorbell
*/
export class DoorbellController extends CameraController {
- private doorbellService?: Doorbell;
- private doorbellServiceExternallySupplied = false;
+ private doorbellService?: Doorbell
+ private doorbellServiceExternallySupplied = false
/**
* Temporary storage. Erased after init.
*/
- private doorbellOptions?: DoorbellOptions;
+ private doorbellOptions?: DoorbellOptions
/**
* Initializes a new `DoorbellController`.
* @param options - The {@link CameraControllerOptions} and optional {@link DoorbellOptions}.
*/
constructor(options: CameraControllerOptions & DoorbellOptions) {
- super(options);
+ super(options)
this.doorbellOptions = {
name: options.name,
externalDoorbellService: options.externalDoorbellService,
- };
+ }
}
/**
* Call this method to signal a doorbell button press.
*/
public ringDoorbell(): void {
- this.doorbellService!.updateCharacteristic(Characteristic.ProgrammableSwitchEvent, Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS);
+ this.doorbellService!.updateCharacteristic(Characteristic.ProgrammableSwitchEvent, Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS)
}
constructServices(): CameraControllerServiceMap {
if (this.doorbellOptions?.externalDoorbellService) {
- this.doorbellService = this.doorbellOptions.externalDoorbellService;
- this.doorbellServiceExternallySupplied = true;
+ this.doorbellService = this.doorbellOptions.externalDoorbellService
+ this.doorbellServiceExternallySupplied = true
} else {
- this.doorbellService = new Service.Doorbell(this.doorbellOptions?.name ?? "", "");
+ this.doorbellService = new Service.Doorbell(this.doorbellOptions?.name ?? '', '')
}
- this.doorbellService.setPrimaryService();
+ this.doorbellService.setPrimaryService()
- const serviceMap = super.constructServices();
+ const serviceMap = super.constructServices()
if (!this.doorbellServiceExternallySupplied) {
- serviceMap.doorbell = this.doorbellService;
+ serviceMap.doorbell = this.doorbellService
}
- return serviceMap;
+ return serviceMap
}
initWithServices(serviceMap: CameraControllerServiceMap): void | CameraControllerServiceMap {
- const result = super._initWithServices(serviceMap);
+ const result = super._initWithServices(serviceMap)
if (this.doorbellOptions?.externalDoorbellService) {
- this.doorbellService = this.doorbellOptions.externalDoorbellService;
- this.doorbellServiceExternallySupplied = true;
+ this.doorbellService = this.doorbellOptions.externalDoorbellService
+ this.doorbellServiceExternallySupplied = true
if (result.serviceMap.doorbell) {
- delete result.serviceMap.doorbell;
- result.updated = true;
+ delete result.serviceMap.doorbell
+ result.updated = true
}
} else {
- this.doorbellService = result.serviceMap.doorbell;
+ this.doorbellService = result.serviceMap.doorbell
if (!this.doorbellService) { // see NOTICE above
- this.doorbellService = new Service.Doorbell(this.doorbellOptions?.name ?? "", "");
+ this.doorbellService = new Service.Doorbell(this.doorbellOptions?.name ?? '', '')
- result.serviceMap.doorbell = this.doorbellService;
- result.updated = true;
+ result.serviceMap.doorbell = this.doorbellService
+ result.updated = true
}
}
- this.doorbellService.setPrimaryService();
+ this.doorbellService.setPrimaryService()
if (result.updated) {
- return result.serviceMap;
+ return result.serviceMap
}
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ // eslint-disable-next-line unused-imports/no-unused-vars
protected migrateFromDoorbell(serviceMap: ControllerServiceMap): boolean {
- return false;
+ return false
}
protected retrieveEventTriggerOptions(): Set {
- const result = super.retrieveEventTriggerOptions();
- result.add(EventTriggerOption.DOORBELL);
- return result;
+ const result = super.retrieveEventTriggerOptions()
+ result.add(EventTriggerOption.DOORBELL)
+ return result
}
handleControllerRemoved(): void {
- super.handleControllerRemoved();
+ super.handleControllerRemoved()
- this.doorbellService = undefined;
+ this.doorbellService = undefined
}
configureServices(): void {
- super.configureServices();
+ super.configureServices()
this.doorbellService!.getCharacteristic(Characteristic.ProgrammableSwitchEvent)
- .onGet(() => null); // a value of null represent nothing is pressed
+ .onGet(() => null) // a value of null represent nothing is pressed
- this.doorbellOptions = undefined;
+ this.doorbellOptions = undefined
}
}
diff --git a/src/lib/controller/RemoteController.ts b/src/lib/controller/RemoteController.ts
index 5bbeb2864..0d43927b1 100644
--- a/src/lib/controller/RemoteController.ts
+++ b/src/lib/controller/RemoteController.ts
@@ -1,67 +1,82 @@
-import assert from "assert";
-import createDebug from "debug";
-import { EventEmitter } from "events";
-import { CharacteristicValue } from "../../types";
-import { AudioBitrate, AudioSamplerate } from "../camera";
-import {
- Characteristic,
- CharacteristicEventTypes,
- CharacteristicGetCallback,
- CharacteristicSetCallback,
-} from "../Characteristic";
-import {
- HDSProtocolSpecificErrorReason,
+/* global NodeJS */
+import type { CharacteristicValue } from '../../types'
+import type { CharacteristicGetCallback, CharacteristicSetCallback } from '../Characteristic'
+import type {
DataStreamConnection,
- DataStreamConnectionEvent,
- DataStreamManagement,
DataStreamProtocolHandler,
- DataStreamServerEvent,
EventHandler,
- Float32,
- HDSStatus,
- Int64,
- Protocols,
RequestHandler,
- Topics,
-} from "../datastream";
+} from '../datastream'
import type {
AudioStreamManagement,
DataStreamTransportManagement,
Siri,
TargetControl,
TargetControlManagement,
-} from "../definitions";
-import { HAPStatus } from "../HAPServer";
-import { Service } from "../Service";
-import { HAPConnection, HAPConnectionEvent } from "../util/eventedhttp";
-import * as tlv from "../util/tlv";
-import {
+} from '../definitions'
+import type { HAPConnection } from '../util/eventedhttp'
+import type {
ControllerIdentifier,
ControllerServiceMap,
- DefaultControllerType,
SerializableController,
StateChangeDelegate,
-} from "./Controller";
+} from './Controller'
+
+import assert from 'node:assert'
+import { Buffer } from 'node:buffer'
+import { EventEmitter } from 'node:events'
-const debug = createDebug("HAP-NodeJS:Remote:Controller");
+import createDebug from 'debug'
+import { AudioBitrate, AudioSamplerate } from '../camera/index.js'
+import { Characteristic, CharacteristicEventTypes } from '../Characteristic.js'
+import {
+ DataStreamConnectionEvent,
+ DataStreamManagement,
+ DataStreamServerEvent,
+ Float32,
+ HDSProtocolSpecificErrorReason,
+ HDSStatus,
+ Int64,
+ Protocols,
+ Topics,
+} from '../datastream/index.js'
+import { HAPStatus } from '../HAPServer.js'
+import { Service } from '../Service.js'
+import { HAPConnectionEvent } from '../util/eventedhttp.js'
+import {
+ decode,
+ decodeList,
+ encode,
+ readUInt16,
+ readUInt32,
+ writeUInt16,
+ writeUInt32,
+ writeVariableUIntLE,
+} from '../util/tlv.js'
+import { DefaultControllerType } from './Controller.js'
+
+const debug = createDebug('HAP-NodeJS:Remote:Controller')
+
+// eslint-disable-next-line no-restricted-syntax
const enum TargetControlCommands {
MAXIMUM_TARGETS = 0x01,
TICKS_PER_SECOND = 0x02,
SUPPORTED_BUTTON_CONFIGURATION = 0x03,
- TYPE = 0x04
+ TYPE = 0x04,
}
+// eslint-disable-next-line no-restricted-syntax
const enum SupportedButtonConfigurationTypes {
BUTTON_ID = 0x01,
- BUTTON_TYPE = 0x02
+ BUTTON_TYPE = 0x02,
}
/**
* @group Apple TV Remote
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum ButtonType {
- // noinspection JSUnusedGlobalSymbols
UNDEFINED = 0x00,
MENU = 0x01,
PLAY_PAUSE = 0x02,
@@ -75,47 +90,49 @@ export const enum ButtonType {
VOLUME_DOWN = 0x0A,
SIRI = 0x0B,
POWER = 0x0C,
- GENERIC = 0x0D
+ GENERIC = 0x0D,
}
-
+// eslint-disable-next-line no-restricted-syntax
const enum TargetControlList {
OPERATION = 0x01,
- TARGET_CONFIGURATION = 0x02
+ TARGET_CONFIGURATION = 0x02,
}
enum Operation {
- // noinspection JSUnusedGlobalSymbols
UNDEFINED = 0x00,
LIST = 0x01,
ADD = 0x02,
REMOVE = 0x03,
RESET = 0x04,
- UPDATE = 0x05
+ UPDATE = 0x05,
}
+// eslint-disable-next-line no-restricted-syntax
const enum TargetConfigurationTypes {
TARGET_IDENTIFIER = 0x01,
TARGET_NAME = 0x02,
TARGET_CATEGORY = 0x03,
- BUTTON_CONFIGURATION = 0x04
+ BUTTON_CONFIGURATION = 0x04,
}
/**
* @group Apple TV Remote
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum TargetCategory {
- // noinspection JSUnusedGlobalSymbols
UNDEFINED = 0x00,
- APPLE_TV = 0x18
+ APPLE_TV = 0x18,
}
+// eslint-disable-next-line no-restricted-syntax
const enum ButtonConfigurationTypes {
BUTTON_ID = 0x01,
BUTTON_TYPE = 0x02,
BUTTON_NAME = 0x03,
}
+// eslint-disable-next-line no-restricted-syntax
const enum ButtonEvent {
BUTTON_ID = 0x01,
BUTTON_STATE = 0x02,
@@ -126,62 +143,63 @@ const enum ButtonEvent {
/**
* @group Apple TV Remote
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum ButtonState {
UP = 0x00,
- DOWN = 0x01
+ DOWN = 0x01,
}
-
/**
* @group Apple TV Remote
*/
-export type SupportedConfiguration = {
- maximumTargets: number,
- ticksPerSecond: number,
- supportedButtonConfiguration: SupportedButtonConfiguration[],
+export interface SupportedConfiguration {
+ maximumTargets: number
+ ticksPerSecond: number
+ supportedButtonConfiguration: SupportedButtonConfiguration[]
hardwareImplemented: boolean
}
/**
* @group Apple TV Remote
*/
-export type SupportedButtonConfiguration = {
- buttonID: number,
+export interface SupportedButtonConfiguration {
+ buttonID: number
buttonType: ButtonType
}
/**
* @group Apple TV Remote
*/
-export type TargetConfiguration = {
- targetIdentifier: number,
- targetName?: string, // on Operation.UPDATE targetName is left out
- targetCategory?: TargetCategory, // on Operation.UPDATE targetCategory is left out
+export interface TargetConfiguration {
+ targetIdentifier: number
+ targetName?: string // on Operation.UPDATE targetName is left out
+ targetCategory?: TargetCategory // on Operation.UPDATE targetCategory is left out
buttonConfiguration: Record // button configurations indexed by their ID
}
/**
* @group Apple TV Remote
*/
-export type ButtonConfiguration = {
- buttonID: number,
- buttonType: ButtonType,
+export interface ButtonConfiguration {
+ buttonID: number
+ buttonType: ButtonType
buttonName?: string
}
-
+// eslint-disable-next-line no-restricted-syntax
const enum SelectedAudioInputStreamConfigurationTypes {
SELECTED_AUDIO_INPUT_STREAM_CONFIGURATION = 0x01,
}
// ----------
+// eslint-disable-next-line no-restricted-syntax
const enum SupportedAudioStreamConfigurationTypes {
- // noinspection JSUnusedGlobalSymbols
AUDIO_CODEC_CONFIGURATION = 0x01,
COMFORT_NOISE_SUPPORT = 0x02,
}
+// eslint-disable-next-line no-restricted-syntax
const enum AudioCodecConfigurationTypes {
CODEC_TYPE = 0x01,
CODEC_PARAMETERS = 0x02,
@@ -190,8 +208,8 @@ const enum AudioCodecConfigurationTypes {
/**
* @group Camera
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum AudioCodecTypes { // only really by HAP supported codecs are AAC-ELD and OPUS
- // noinspection JSUnusedGlobalSymbols
PCMU = 0x00,
PCMA = 0x01,
AAC_ELD = 0x02,
@@ -201,47 +219,48 @@ export const enum AudioCodecTypes { // only really by HAP supported codecs are A
AMR_WB = 0x06,
}
+// eslint-disable-next-line no-restricted-syntax
const enum AudioCodecParametersTypes {
CHANNEL = 0x01,
BIT_RATE = 0x02,
SAMPLE_RATE = 0x03,
- PACKET_TIME = 0x04 // only present in selected audio codec parameters tlv
+ PACKET_TIME = 0x04, // only present in selected audio codec parameters tlv
}
// ----------
-type SupportedAudioStreamConfiguration = {
- audioCodecConfiguration: AudioCodecConfiguration,
+interface SupportedAudioStreamConfiguration {
+ audioCodecConfiguration: AudioCodecConfiguration
}
-type SelectedAudioStreamConfiguration = {
- audioCodecConfiguration: AudioCodecConfiguration,
+interface SelectedAudioStreamConfiguration {
+ audioCodecConfiguration: AudioCodecConfiguration
}
/**
* @group Apple TV Remote
*/
-export type AudioCodecConfiguration = {
- codecType: AudioCodecTypes,
- parameters: AudioCodecParameters,
+export interface AudioCodecConfiguration {
+ codecType: AudioCodecTypes
+ parameters: AudioCodecParameters
}
/**
* @group Apple TV Remote
*/
-export type AudioCodecParameters = {
- channels: number, // number of audio channels, default is 1
- bitrate: AudioBitrate,
- samplerate: AudioSamplerate,
- rtpTime?: RTPTime, // only present in SelectedAudioCodecParameters TLV
+export interface AudioCodecParameters {
+ channels: number // number of audio channels, default is 1
+ bitrate: AudioBitrate
+ samplerate: AudioSamplerate
+ rtpTime?: RTPTime // only present in SelectedAudioCodecParameters TLV
}
/**
* @group Apple TV Remote
*/
-export type RTPTime = 20 | 30 | 40 | 60;
-
+export type RTPTime = 20 | 30 | 40 | 60
+// eslint-disable-next-line no-restricted-syntax
const enum SiriAudioSessionState {
STARTING = 0, // we are currently waiting for a response for the start request
SENDING = 1, // we are sending data
@@ -249,47 +268,45 @@ const enum SiriAudioSessionState {
CLOSED = 3, // the close event was sent
}
-type DataSendMessageData = {
- packets: AudioFramePacket[],
- streamId: Int64,
- endOfStream: boolean,
+interface DataSendMessageData {
+ packets: AudioFramePacket[]
+ streamId: Int64
+ endOfStream: boolean
}
/**
* @group Apple TV Remote
*/
-export type AudioFrame = {
- data: Buffer,
- rms: number, // root-mean-square
+export interface AudioFrame {
+ data: Buffer
+ rms: number // root-mean-square
}
-type AudioFramePacket = {
- data: Buffer,
+interface AudioFramePacket {
+ data: Buffer
metadata: {
- rms: Float32, // root-mean-square
- sequenceNumber: Int64,
- },
+ rms: Float32 // root-mean-square
+ sequenceNumber: Int64
+ }
}
-
/**
* @group Apple TV Remote
*/
-export type FrameHandler = (frame: AudioFrame) => void;
+export type FrameHandler = (frame: AudioFrame) => void
/**
* @group Apple TV Remote
*/
-export type ErrorHandler = (error: HDSProtocolSpecificErrorReason) => void;
+export type ErrorHandler = (error: HDSProtocolSpecificErrorReason) => void
/**
* @group Apple TV Remote
*/
export interface SiriAudioStreamProducer {
-
- startAudioProduction(selectedAudioConfiguration: AudioCodecConfiguration): void;
-
- stopAudioProduction(): void;
-
+ /* eslint-disable ts/method-signature-style */
+ startAudioProduction(selectedAudioConfiguration: AudioCodecConfiguration): void
+ stopAudioProduction(): void
+ /* eslint-enable ts/method-signature-style */
}
/**
@@ -304,14 +321,14 @@ export interface SiriAudioStreamProducerConstructor {
* @param errorHandler - should be called with an appropriate reason when the producing process errored
* @param options - optional parameter for passing any configuration related options
*/
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- new(frameHandler: FrameHandler, errorHandler: ErrorHandler, options?: any): SiriAudioStreamProducer;
+ new(frameHandler: FrameHandler, errorHandler: ErrorHandler, options?: any): SiriAudioStreamProducer
}
/**
* @group Apple TV Remote
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum TargetUpdates {
NAME,
CATEGORY,
@@ -322,73 +339,73 @@ export const enum TargetUpdates {
/**
* @group Apple TV Remote
*/
+// eslint-disable-next-line no-restricted-syntax
export const enum RemoteControllerEvents {
/**
* This event is emitted when the active state of the remote has changed.
* active = true indicates that there is currently an Apple TV listening of button presses and audio streams.
*/
- ACTIVE_CHANGE = "active-change",
+ ACTIVE_CHANGE = 'active-change',
/**
* This event is emitted when the currently selected target has changed.
* Possible reasons for a changed active identifier: manual change via api call, first target configuration
* gets added, active target gets removed, accessory gets unpaired, reset request was sent.
* An activeIdentifier of 0 indicates that no target is selected.
*/
- ACTIVE_IDENTIFIER_CHANGE = "active-identifier-change",
+ ACTIVE_IDENTIFIER_CHANGE = 'active-identifier-change',
/**
* This event is emitted when a new target configuration is received. As we currently do not persistently store
* configured targets, this will be called at every startup for every Apple TV configured in the home.
*/
- TARGET_ADDED = "target-add",
+ TARGET_ADDED = 'target-add',
/**
* This event is emitted when an existing target was updated.
* The 'updates' array indicates what exactly was changed for the target.
*/
- TARGET_UPDATED = "target-update",
+ TARGET_UPDATED = 'target-update',
/**
* This event is emitted when an existing configuration for a target was removed.
*/
- TARGET_REMOVED = "target-remove",
+ TARGET_REMOVED = 'target-remove',
/**
* This event is emitted when a reset of the target configuration is requested.
* With this event every configuration made should be reset. This event is also called
* when the accessory gets unpaired.
*/
- TARGETS_RESET = "targets-reset",
+ TARGETS_RESET = 'targets-reset',
}
/**
* @group Apple TV Remote
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export declare interface RemoteController {
- on(event: "active-change", listener: (active: boolean) => void): this;
- on(event: "active-identifier-change", listener: (activeIdentifier: number) => void): this;
-
- on(event: "target-add", listener: (targetConfiguration: TargetConfiguration) => void): this;
- on(event: "target-update", listener: (targetConfiguration: TargetConfiguration, updates: TargetUpdates[]) => void): this;
- on(event: "target-remove", listener: (targetIdentifier: number) => void): this;
- on(event: "targets-reset", listener: () => void): this;
-
- emit(event: "active-change", active: boolean): boolean;
- emit(event: "active-identifier-change", activeIdentifier: number): boolean;
-
- emit(event: "target-add", targetConfiguration: TargetConfiguration): boolean;
- emit(event: "target-update", targetConfiguration: TargetConfiguration, updates: TargetUpdates[]): boolean;
- emit(event: "target-remove", targetIdentifier: number): boolean;
- emit(event: "targets-reset"): boolean;
+ /* eslint-disable ts/method-signature-style */
+ on(event: 'active-change', listener: (active: boolean) => void): this
+ on(event: 'active-identifier-change', listener: (activeIdentifier: number) => void): this
+ on(event: 'target-add', listener: (targetConfiguration: TargetConfiguration) => void): this
+ on(event: 'target-update', listener: (targetConfiguration: TargetConfiguration, updates: TargetUpdates[]) => void): this
+ on(event: 'target-remove', listener: (targetIdentifier: number) => void): this
+ on(event: 'targets-reset', listener: () => void): this
+ emit(event: 'active-change', active: boolean): boolean
+ emit(event: 'active-identifier-change', activeIdentifier: number): boolean
+ emit(event: 'target-add', targetConfiguration: TargetConfiguration): boolean
+ emit(event: 'target-update', targetConfiguration: TargetConfiguration, updates: TargetUpdates[]): boolean
+ emit(event: 'target-remove', targetIdentifier: number): boolean
+ emit(event: 'targets-reset'): boolean
+ /* eslint-enable ts/method-signature-style */
}
/**
* @group Apple TV Remote
*/
export interface RemoteControllerServiceMap extends ControllerServiceMap {
- targetControlManagement: TargetControlManagement,
- targetControl: TargetControl,
+ targetControlManagement: TargetControlManagement
+ targetControl: TargetControl
- siri?: Siri,
- audioStreamManagement?: AudioStreamManagement,
+ siri?: Siri
+ audioStreamManagement?: AudioStreamManagement
dataStreamTransportManagement?: DataStreamTransportManagement
}
@@ -396,8 +413,8 @@ export interface RemoteControllerServiceMap extends ControllerServiceMap {
* @group Apple TV Remote
*/
export interface SerializedControllerState {
- activeIdentifier: number,
- targetConfigurations: Record;
+ activeIdentifier: number
+ targetConfigurations: Record
}
/**
@@ -405,50 +422,49 @@ export interface SerializedControllerState {
*
* @group Apple TV Remote
*/
-// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
+// eslint-disable-next-line ts/no-unsafe-declaration-merging
export class RemoteController extends EventEmitter
implements SerializableController, DataStreamProtocolHandler {
- private stateChangeDelegate?: StateChangeDelegate;
+ private stateChangeDelegate?: StateChangeDelegate
- private readonly audioSupported: boolean;
- private readonly audioProducerConstructor?: SiriAudioStreamProducerConstructor;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- private readonly audioProducerOptions?: any;
+ private readonly audioSupported: boolean
+ private readonly audioProducerConstructor?: SiriAudioStreamProducerConstructor
+ private readonly audioProducerOptions?: any
- private targetControlManagementService?: TargetControlManagement;
- private targetControlService?: TargetControl;
+ private targetControlManagementService?: TargetControlManagement
+ private targetControlService?: TargetControl
- private siriService?: Siri;
- private audioStreamManagementService?: AudioStreamManagement;
- private dataStreamManagement?: DataStreamManagement;
+ private siriService?: Siri
+ private audioStreamManagementService?: AudioStreamManagement
+ private dataStreamManagement?: DataStreamManagement
- private buttons: Record = {}; // internal mapping of buttonId to buttonType for supported buttons
- private readonly supportedConfiguration: string;
- targetConfigurations: Map = new Map();
- private targetConfigurationsString = "";
+ private buttons: Record = {} // internal mapping of buttonId to buttonType for supported buttons
+ private readonly supportedConfiguration: string
+ targetConfigurations: Map
Determines if the given status code is a known HAPStatus error code.
+- Preparing search index...
- The search index is not available
hap-nodejsFunction isKnownHAPStatusError
Determines if the given status code is a known HAPStatus error code.
Parameters
Returns boolean
Settings