From 51d90095aab7571268d773bba3031e53b36aec1b Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Sat, 4 Apr 2026 11:29:02 +0200 Subject: [PATCH] https client for scheduler/shuntingYard --- interface/package.json | 8 +- interface/pnpm-lock.yaml | 156 ++++++++++++------------- src/core/shuntingYard.cpp | 194 ++++++++++++++++++++++++-------- src/core/system.cpp | 28 +++-- src/web/WebSchedulerService.cpp | 148 ++++++++++++++++++++---- 5 files changed, 368 insertions(+), 166 deletions(-) diff --git a/interface/package.json b/interface/package.json index 0234290c6..73372e725 100644 --- a/interface/package.json +++ b/interface/package.json @@ -37,11 +37,11 @@ "jwt-decode": "^4.0.0", "magic-string": "^0.30.21", "mime-types": "^3.0.2", - "preact": "^10.29.0", + "preact": "^10.29.1", "react": "^19.2.4", "react-dom": "^19.2.4", "react-icons": "^5.6.0", - "react-router": "^7.13.2", + "react-router": "^7.14.0", "react-toastify": "^11.0.5", "typesafe-i18n": "^5.27.1", "typescript": "^6.0.2" @@ -52,10 +52,10 @@ "@preact/compat": "^18.3.2", "@preact/preset-vite": "^2.10.5", "@trivago/prettier-plugin-sort-imports": "^6.0.2", - "@types/node": "^25.5.0", + "@types/node": "^25.5.2", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "axe-core": "^4.11.1", + "axe-core": "^4.11.2", "concurrently": "^9.2.1", "eslint": "^10.1.0", "eslint-config-prettier": "^10.1.8", diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml index a40414691..f773ee0e2 100644 --- a/interface/pnpm-lock.yaml +++ b/interface/pnpm-lock.yaml @@ -25,7 +25,7 @@ importers: version: 7.3.9(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@preact/compat': specifier: ^18.3.2 - version: 18.3.2(preact@10.29.0) + version: 18.3.2(preact@10.29.1) '@table-library/react-table-library': specifier: 4.1.15 version: 4.1.15(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -51,8 +51,8 @@ importers: specifier: ^3.0.2 version: 3.0.2 preact: - specifier: ^10.29.0 - version: 10.29.0 + specifier: ^10.29.1 + version: 10.29.1 react: specifier: ^19.2.4 version: 19.2.4 @@ -63,8 +63,8 @@ importers: specifier: ^5.6.0 version: 5.6.0(react@19.2.4) react-router: - specifier: ^7.13.2 - version: 7.13.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: ^7.14.0 + version: 7.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react-toastify: specifier: ^11.0.5 version: 11.0.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -83,13 +83,13 @@ importers: version: 10.0.1(eslint@10.1.0) '@preact/preset-vite': specifier: ^2.10.5 - version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.59.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1)) + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.59.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1)) '@trivago/prettier-plugin-sort-imports': specifier: ^6.0.2 version: 6.0.2(prettier@3.8.1) '@types/node': - specifier: ^25.5.0 - version: 25.5.0 + specifier: ^25.5.2 + version: 25.5.2 '@types/react': specifier: ^19.2.14 version: 19.2.14 @@ -97,8 +97,8 @@ importers: specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.14) axe-core: - specifier: ^4.11.1 - version: 4.11.1 + specifier: ^4.11.2 + version: 4.11.2 concurrently: specifier: ^9.2.1 version: 9.2.1 @@ -122,10 +122,10 @@ importers: version: 8.58.0(eslint@10.1.0)(typescript@6.0.2) vite: specifier: ^8.0.3 - version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1) + version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1) vite-plugin-imagemin: specifier: ^0.6.1 - version: 0.6.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1)) + version: 0.6.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1)) packages: @@ -1005,8 +1005,8 @@ packages: resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==} deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed. - '@types/node@25.5.0': - resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + '@types/node@25.5.2': + resolution: {integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -1158,8 +1158,8 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axe-core@4.11.1: - resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==} + axe-core@4.11.2: + resolution: {integrity: sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw==} engines: {node: '>=4'} babel-plugin-macros@3.1.0: @@ -1181,8 +1181,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.12: - resolution: {integrity: sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==} + baseline-browser-mapping@2.10.14: + resolution: {integrity: sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA==} engines: {node: '>=6.0.0'} hasBin: true @@ -1226,8 +1226,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1280,8 +1280,8 @@ packages: resolution: {integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==} engines: {node: '>=0.10.0'} - caniuse-lite@1.0.30001782: - resolution: {integrity: sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==} + caniuse-lite@1.0.30001784: + resolution: {integrity: sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==} caw@2.0.1: resolution: {integrity: sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==} @@ -1516,8 +1516,8 @@ packages: duplexer3@0.1.5: resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} - electron-to-chromium@1.5.328: - resolution: {integrity: sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==} + electron-to-chromium@1.5.331: + resolution: {integrity: sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -1792,8 +1792,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-xml-parser@4.5.5: - resolution: {integrity: sha512-cK9c5I/DwIOI7/Q7AlGN3DuTdwN61gwSfL8rvuVPK+0mcCNHHGxRrpiFtaZZRfRMJL3Gl8B2AFlBG6qXf03w9A==} + fast-xml-parser@4.5.6: + resolution: {integrity: sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A==} hasBin: true fastq@1.20.1: @@ -2377,8 +2377,8 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash-es@4.17.23: - resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} logalot@2.1.0: resolution: {integrity: sha512-Ah4CgdSRfeCJagxQhcVNMi9BfGYyEKLa6d7OA6xSbld/Hg3Cf2QiOa1mDpmG7Ve8LOH6DN3mdttzjQAvWTyVkw==} @@ -2507,8 +2507,8 @@ packages: node-html-parser@6.1.13: resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} - node-releases@2.0.36: - resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + node-releases@2.0.37: + resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -2719,8 +2719,8 @@ packages: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} - preact@10.29.0: - resolution: {integrity: sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg==} + preact@10.29.1: + resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} @@ -2784,8 +2784,8 @@ packages: react-is@19.2.4: resolution: {integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==} - react-router@7.13.2: - resolution: {integrity: sha512-tX1Aee+ArlKQP+NIUd7SE6Li+CiGKwQtbS+FfRxPX6Pe4vHOo6nr9d++u5cwg+Z8K/x8tP+7qLmujDtfrAoUJA==} + react-router@7.14.0: + resolution: {integrity: sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -3426,7 +3426,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.0 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 + browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 @@ -3878,23 +3878,23 @@ snapshots: '@popperjs/core@2.11.8': {} - '@preact/compat@18.3.2(preact@10.29.0)': + '@preact/compat@18.3.2(preact@10.29.1)': dependencies: - preact: 10.29.0 + preact: 10.29.1 - '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.59.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1))': + '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.59.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) - '@prefresh/vite': 2.4.12(preact@10.29.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1)) + '@prefresh/vite': 2.4.12(preact@10.29.1)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1)) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.0) debug: 4.4.3 magic-string: 0.30.21 picocolors: 1.1.1 - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1) - vite-prerender-plugin: 0.5.13(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1)) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1) + vite-prerender-plugin: 0.5.13(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1)) zimmerframe: 1.1.4 transitivePeerDependencies: - preact @@ -3903,21 +3903,21 @@ snapshots: '@prefresh/babel-plugin@0.5.3': {} - '@prefresh/core@1.5.9(preact@10.29.0)': + '@prefresh/core@1.5.9(preact@10.29.1)': dependencies: - preact: 10.29.0 + preact: 10.29.1 '@prefresh/utils@1.2.1': {} - '@prefresh/vite@2.4.12(preact@10.29.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1))': + '@prefresh/vite@2.4.12(preact@10.29.1)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1))': dependencies: '@babel/core': 7.29.0 '@prefresh/babel-plugin': 0.5.3 - '@prefresh/core': 1.5.9(preact@10.29.0) + '@prefresh/core': 1.5.9(preact@10.29.1) '@prefresh/utils': 1.2.1 '@rollup/pluginutils': 4.2.1 - preact: 10.29.0 - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1) + preact: 10.29.1 + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1) transitivePeerDependencies: - supports-color @@ -4079,7 +4079,7 @@ snapshots: '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 javascript-natural-sort: 0.7.1 - lodash-es: 4.17.23 + lodash-es: 4.18.1 minimatch: 9.0.9 parse-imports-exports: 0.2.4 prettier: 3.8.1 @@ -4098,7 +4098,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 6.0.0 - '@types/node': 25.5.0 + '@types/node': 25.5.2 '@types/imagemin-gifsicle@7.0.4': dependencies: @@ -4127,19 +4127,19 @@ snapshots: '@types/imagemin@7.0.1': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 '@types/json-schema@7.0.15': {} '@types/keyv@3.1.4': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 '@types/minimatch@6.0.0': dependencies: minimatch: 10.2.5 - '@types/node@25.5.0': + '@types/node@25.5.2': dependencies: undici-types: 7.18.2 @@ -4161,11 +4161,11 @@ snapshots: '@types/responselike@1.0.3': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 '@types/svgo@2.6.4': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.1.0)(typescript@6.0.2))(eslint@10.1.0)(typescript@6.0.2)': dependencies: @@ -4308,7 +4308,7 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - axe-core@4.11.1: {} + axe-core@4.11.2: {} babel-plugin-macros@3.1.0: dependencies: @@ -4326,7 +4326,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.10.12: {} + baseline-browser-mapping@2.10.14: {} bin-build@3.0.0: dependencies: @@ -4385,13 +4385,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.1: + browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.12 - caniuse-lite: 1.0.30001782 - electron-to-chromium: 1.5.328 - node-releases: 2.0.36 - update-browserslist-db: 1.2.3(browserslist@4.28.1) + baseline-browser-mapping: 2.10.14 + caniuse-lite: 1.0.30001784 + electron-to-chromium: 1.5.331 + node-releases: 2.0.37 + update-browserslist-db: 1.2.3(browserslist@4.28.2) buffer-alloc-unsafe@1.1.0: {} @@ -4451,7 +4451,7 @@ snapshots: camelcase@2.1.1: {} - caniuse-lite@1.0.30001782: {} + caniuse-lite@1.0.30001784: {} caw@2.0.1: dependencies: @@ -4753,7 +4753,7 @@ snapshots: duplexer3@0.1.5: {} - electron-to-chromium@1.5.328: {} + electron-to-chromium@1.5.331: {} emoji-regex@10.6.0: {} @@ -5050,7 +5050,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-xml-parser@4.5.5: + fast-xml-parser@4.5.6: dependencies: strnum: 1.1.2 @@ -5461,7 +5461,7 @@ snapshots: is-svg@4.4.0: dependencies: - fast-xml-parser: 4.5.5 + fast-xml-parser: 4.5.6 is-typed-array@1.1.15: dependencies: @@ -5601,7 +5601,7 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash-es@4.17.23: {} + lodash-es@4.18.1: {} logalot@2.1.0: dependencies: @@ -5723,7 +5723,7 @@ snapshots: css-select: 5.2.2 he: 1.2.0 - node-releases@2.0.36: {} + node-releases@2.0.37: {} normalize-package-data@2.5.0: dependencies: @@ -5917,7 +5917,7 @@ snapshots: powershell-utils@0.1.0: {} - preact@10.29.0: {} + preact@10.29.1: {} prelude-ls@1.2.1: {} @@ -5969,7 +5969,7 @@ snapshots: react-is@19.2.4: {} - react-router@7.13.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-router@7.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: cookie: 1.1.1 react: 19.2.4 @@ -6415,9 +6415,9 @@ snapshots: universalify@2.0.1: {} - update-browserslist-db@1.2.3(browserslist@4.28.1): + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 escalade: 3.2.0 picocolors: 1.1.1 @@ -6444,7 +6444,7 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - vite-plugin-imagemin@0.6.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1)): + vite-plugin-imagemin@0.6.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1)): dependencies: '@types/imagemin': 7.0.1 '@types/imagemin-gifsicle': 7.0.4 @@ -6469,11 +6469,11 @@ snapshots: imagemin-webp: 6.1.0 jpegtran-bin: 6.0.1 pathe: 0.2.0 - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1) transitivePeerDependencies: - supports-color - vite-prerender-plugin@0.5.13(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1)): + vite-prerender-plugin@0.5.13(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1)): dependencies: kolorist: 1.8.0 magic-string: 0.30.21 @@ -6481,9 +6481,9 @@ snapshots: simple-code-frame: 1.3.0 source-map: 0.7.6 stack-trace: 1.0.0-pre2 - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1) - vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1): + vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.2)(esbuild@0.27.4)(terser@5.46.1): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -6491,7 +6491,7 @@ snapshots: rolldown: 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.5.2 esbuild: 0.27.4 fsevents: 2.3.3 terser: 5.46.1 diff --git a/src/core/shuntingYard.cpp b/src/core/shuntingYard.cpp index d90b0a9c7..da3e6df10 100644 --- a/src/core/shuntingYard.cpp +++ b/src/core/shuntingYard.cpp @@ -380,11 +380,15 @@ std::string commands(std::string & expr, bool quotes) { if (return_code != CommandRet::OK && return_code != CommandRet::NO_VALUE) { return expr = ""; } - - std::string data = output["api_data"] | ""; - if (!isnum(data) && quotes) { - data.insert(data.begin(), '"'); - data.insert(data.end(), '"'); + std::string data; + if (output["api_data"].is()) { + data = output["api_data"].as(); + if (!isnum(data) && quotes) { + data.insert(data.begin(), '"'); + data.insert(data.end(), '"'); + } + } else { + serializeJson(output, data); } expr.replace(f, l, data); e = f + data.length(); @@ -700,7 +704,6 @@ std::string compute(const std::string & expr) { std::string cmd = expr_new.substr(f, e - f).c_str(); JsonDocument doc; if (DeserializationError::Ok == deserializeJson(doc, cmd)) { - HTTPClient http; std::string url, header_s, value_s, method_s, key_s, keys_s; // search keys lower case for (JsonPair p : doc.as()) { @@ -720,56 +723,149 @@ std::string compute(const std::string & expr) { keys_s = p.key().c_str(); } } - if (http.begin(url.c_str())) { - int httpResult = 0; - for (JsonPair p : doc[header_s].as()) { - http.addHeader(p.key().c_str(), p.value().as().c_str()); + bool content_set = false; + std::string value = doc[value_s] | ""; + std::string method = doc[method_s] | "GET"; + if (value.length()) { + method = "POST"; + } + std::string result; + int httpResult = 0; +#ifndef NO_TLS_SUPPORT + if (Helpers::toLower(url.c_str()).starts_with("https://")) { + WiFiClient * basic_client = new WiFiClient; + ESP_SSLClient * ssl_client = new ESP_SSLClient; + ssl_client->setInsecure(); // with root CA we should set here: ssl_client->setCACert(rootCACert); + ssl_client->setBufferSizes(1024, 1024); + ssl_client->setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds) + url.replace(0, 8, ""); + std::string host = url; + auto index = url.find_first_of('/'); + if (index != std::string::npos) { + host = url.substr(0, index); + url.replace(0, index, ""); } - std::string value = doc[value_s] | ""; - std::string method = doc[method_s] | "get"; - - // if there is data, force a POST - if (value.length() || Helpers::toLower(method) == "post") { - if (value.find_first_of('{') != std::string::npos) { - http.addHeader(asyncsrv::T_Content_Type, asyncsrv::T_application_json, false); // auto-set to JSON - } - httpResult = http.POST(value.c_str()); - } else { - httpResult = http.GET(); // normal GET + /* + index = host.find_first_of('@'); + std::string auth; + if (index != std::string::npos) { + auth = base64::encode(host.substr(0, index)); + host.replace(0, index, ""); } - - if (httpResult > 0) { - std::string result = http.getString().c_str(); - std::string key = doc[key_s] | ""; - JsonDocument keys_doc; // JsonDocument to hold "keys" after doc is parsed with HTTP body - if (doc[keys_s].is()) { - keys_doc.set(doc[keys_s].as()); - } - JsonArray keys = keys_doc.as(); - - if (key.length() || !keys.isNull()) { - doc.clear(); - if (DeserializationError::Ok == deserializeJson(doc, result)) { - if (key.length()) { - result = doc[key.c_str()].as(); + */ + ssl_client->setClient(basic_client); + if (ssl_client->connect(host.c_str(), 443)) { + if (value.length() || Helpers::toLower(method) == "post") { + ssl_client->print("POST "); + ssl_client->print(url.c_str()); + ssl_client->println(" HTTP/1.1"); + ssl_client->print("Host: "); + ssl_client->println(host.c_str()); + for (JsonPair p : doc[header_s].as()) { + content_set |= (emsesp::Helpers::toLower(p.key().c_str()) == "content-type"); + ssl_client->print(p.key().c_str()); + ssl_client->print(": "); + ssl_client->println(p.value().as().c_str()); + } + if (!content_set) { + ssl_client->print("Content-Type: "); + if (value.starts_with('{')) { + ssl_client->println(asyncsrv::T_application_json); } else { - JsonVariant json = doc.as(); - for (JsonVariant keys_key : keys) { - if (keys_key.is() && json.is()) { - json = json[keys_key.as()].as(); - } else if (keys_key.is() && json.is()) { - json = json[keys_key.as()].as(); - } else { - break; // type mismatch - } - } - result = json.as(); + ssl_client->println(asyncsrv::T_text_plain); } } + ssl_client->print("Content-Length: "); + ssl_client->println(value.length()); + ssl_client->println("Connection: close"); + ssl_client->print("\r\n"); + ssl_client->print(value.c_str()); + } else { + ssl_client->print("GET "); + ssl_client->print(url.c_str()); + ssl_client->println(" HTTP/1.1"); + ssl_client->print("Host: "); + ssl_client->println(host.c_str()); + for (JsonPair p : doc[header_s].as()) { + ssl_client->print(p.key().c_str()); + ssl_client->print(": "); + ssl_client->println(p.value().as().c_str()); + } + ssl_client->println("Connection: close"); + } + auto ms = millis(); + while (!ssl_client->available() && millis() - ms < 3000) { + delay(0); + } + while (ssl_client->available()) { + result += (char)ssl_client->read(); + } + ssl_client->stop(); + index = result.find_first_of(' '); + if (index != std::string::npos) { + httpResult = stoi(result.substr(index + 1, 3)); + } + index = result.find("\r\n\r\n"); + if (index != std::string::npos) { + result.replace(0, index + 4, ""); } - expr_new.replace(f, e - f, result.c_str()); } - http.end(); + delete ssl_client; + delete basic_client; + } else +#endif + if (Helpers::toLower(url.c_str()).starts_with("http://")) { + HTTPClient * http = new HTTPClient; + if (http->begin(url.c_str())) { + for (JsonPair p : doc[header_s].as()) { + http->addHeader(p.key().c_str(), p.value().as().c_str()); + content_set |= (emsesp::Helpers::toLower(p.key().c_str()) == "content-type"); + } + if (value.length() || Helpers::toLower(method) == "post") { + if (!content_set) { + http->addHeader("Content-Type", value.starts_with('{') ? asyncsrv::T_application_json : asyncsrv::T_text_plain); + } + httpResult = http->POST(value.c_str()); + } else { + httpResult = http->GET(); // normal GET + } + if (httpResult > 0) { + result = http->getString().c_str(); + } + } + http->end(); + delete http; + } + if (httpResult == 200) { + std::string key = doc[key_s] | ""; + JsonDocument keys_doc; // JsonDocument to hold "keys" after doc is parsed with HTTP body + if (doc[keys_s].is()) { + keys_doc.set(doc[keys_s].as()); + } + JsonArray keys = keys_doc.as(); + if (key.length() || !keys.isNull()) { + doc.clear(); + if (DeserializationError::Ok == deserializeJson(doc, result)) { + if (key.length()) { + result = doc[key.c_str()].as(); + } else { + JsonVariant json = doc.as(); + for (JsonVariant keys_key : keys) { + if (keys_key.is() && json.is()) { + json = json[keys_key.as()].as(); + } else if (keys_key.is() && json.is()) { + json = json[keys_key.as()].as(); + } else { + break; // type mismatch + } + } + result = json.as(); + } + } + } + expr_new.replace(f, e - f, result); + } else if (httpResult != 0) { + EMSESP::logger().warning("URL command failed with https code: %d, response: %s", httpResult, result.c_str()); } } f = expr_new.find_first_of('{', e); diff --git a/src/core/system.cpp b/src/core/system.cpp index e9bbe3927..2acd6e622 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -140,14 +140,10 @@ bool System::command_sendmail(const char * value, const int8_t id) { bool success = false; #ifndef NO_TLS_SUPPORT - WiFiClient * basic_client; - ESP_SSLClient * ssl_client; - ReadyClient * r_client; // rClient(ssl_client); - SMTPClient * smtp; // smtp(rClient); - basic_client = new WiFiClient; - ssl_client = new ESP_SSLClient; - r_client = new ReadyClient(*ssl_client); - smtp = new SMTPClient(*r_client); + WiFiClient * basic_client = new WiFiClient; + ESP_SSLClient * ssl_client = new ESP_SSLClient; + ReadyClient * r_client = new ReadyClient(*ssl_client); + SMTPClient * smtp = new SMTPClient(*r_client); ssl_client->setClient(basic_client); ssl_client->setInsecure(); @@ -196,7 +192,16 @@ bool System::command_sendmail(const char * value, const int8_t id) { // msg.headers.addCustom("Importance", PRIORITY); // msg.headers.addCustom("X-MSMail-Priority", PRIORITY); // msg.headers.addCustom("X-Priority", PRIORITY_NUM); - + EMSESP::webSchedulerService.computed_value.clear(); + EMSESP::webSchedulerService.raw_value = body.c_str(); + for (uint16_t wait = 0; wait < 2000 && !EMSESP::webSchedulerService.raw_value.empty(); wait++) { + delay(1); + } + if (!EMSESP::webSchedulerService.computed_value.empty()) { + body = EMSESP::webSchedulerService.computed_value.c_str(); + EMSESP::webSchedulerService.computed_value.clear(); + EMSESP::webSchedulerService.computed_value.shrink_to_fit(); // free allocated memory + } msg.text.body(body); // bodyText.replace("\r\n", "
\r\n"); @@ -351,7 +356,8 @@ bool System::command_message(const char * value, const int8_t id, JsonObject out LOG_INFO("Message: %s", EMSESP::webSchedulerService.computed_value.c_str()); // send to log Mqtt::queue_publish(F_(message), EMSESP::webSchedulerService.computed_value); // send to MQTT if enabled output["api_data"] = EMSESP::webSchedulerService.computed_value; // send to API - + EMSESP::webSchedulerService.computed_value.clear(); + EMSESP::webSchedulerService.computed_value.shrink_to_fit(); return true; } @@ -1780,7 +1786,7 @@ void System::exportSystemBackup(JsonObject output) { output["version"] = EMSESP_APP_VERSION; // add the version to the output #ifndef EMSESP_STANDALONE - // add date/time if NTP enabled and active + // add date/time if NTP enabled and active if ((esp_sntp_enabled()) && (EMSESP::system_.ntp_connected())) { time_t now = time(nullptr); if (now > 1500000000L) { diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index e708c94e7..db3293fbe 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -363,39 +363,139 @@ bool WebSchedulerService::command(const char * name, const std::string & command commands(s, false); url.replace(q + 1, l, s); } - if (http.begin(url.c_str())) { - // add any given headers - for (JsonPair p : doc["header"].as()) { - http.addHeader(p.key().c_str(), p.value().as().c_str()); + std::string value = doc["value"] | data; // extract value if its in the command, or take the data + std::string method = doc["method"] | "GET"; // default GET + commands(value, false); + if (value.length()) { + method = "POST"; + } + std::string result; + int httpResult = 0; +#ifndef NO_TLS_SUPPORT + if (Helpers::toLower(url.c_str()).starts_with("https://")) { + WiFiClient * basic_client = new WiFiClient; + ESP_SSLClient * ssl_client = new ESP_SSLClient; + ssl_client->setInsecure(); // with root CA we should set here: ssl_client->setCACert(rootCACert); + ssl_client->setBufferSizes(1024, 1024); + ssl_client->setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds) + url.replace(0, 8, ""); + std::string host = url; + auto index = url.find_first_of('/'); + if (index != std::string::npos) { + host = url.substr(0, index); + url.replace(0, index, ""); } - std::string value = doc["value"] | data.c_str(); // extract value if its in the command, or take the data - std::string method = doc["method"] | "GET"; // default GET - - commands(value, false); - // if there is data, force a POST - int httpResult = 0; - if (value.length() || method == "post") { // we have all lowercase - if (value.find_first_of('{') != std::string::npos) { - http.addHeader(asyncsrv::T_Content_Type, asyncsrv::T_application_json, false); // auto-set to JSON + // EMSESP::logger().debug("Host: %s, URL: %s", host.c_str(), url.c_str()); + ssl_client->setClient(basic_client); + if (ssl_client->connect(host.c_str(), 443)) { + if (value.length() || Helpers::toLower(method) == "post") { + // EMSESP::logger().debug("POST %s HTTP/1.1", url.c_str()); + ssl_client->print("POST "); + ssl_client->print(url.c_str()); + ssl_client->println(" HTTP/1.1"); + ssl_client->print("Host: "); + ssl_client->println(host.c_str()); + bool content_set = false; + for (JsonPair p : doc["header"].as()) { + content_set |= (emsesp::Helpers::toLower(p.key().c_str()) == "content-type"); + ssl_client->print(p.key().c_str()); + ssl_client->print(": "); + ssl_client->println(p.value().as().c_str()); + } + if (!content_set) { + ssl_client->print("Content-Type: "); + if (value.starts_with('{')) { + ssl_client->println(asyncsrv::T_application_json); + } else { + ssl_client->println(asyncsrv::T_text_plain); + } + } + ssl_client->print("Content-Length: "); + ssl_client->println(value.length()); + ssl_client->println("Connection: close"); + ssl_client->print("\r\n"); + ssl_client->print(value.c_str()); + } else { + // EMSESP::logger().debug("GET %s HTTP/1.1", url.c_str()); + ssl_client->print("GET "); + ssl_client->print(url.c_str()); + ssl_client->println(" HTTP/1.1"); + ssl_client->print("Host: "); + ssl_client->println(host.c_str()); + for (JsonPair p : doc["header"].as()) { + ssl_client->print(p.key().c_str()); + ssl_client->print(": "); + ssl_client->println(p.value().as().c_str()); + } + ssl_client->println("Connection: close"); + } + auto ms = millis(); + while (ssl_client->connected() && !ssl_client->available() && millis() - ms < 3000) { + delay(0); + } + while (ssl_client->available()) { + result += (char)ssl_client->read(); + } + ssl_client->stop(); + // EMSESP::logger().debug("HTTPS response: %s", result.c_str()); + index = result.find_first_of(' '); + if (index != std::string::npos) { + httpResult = stoi(result.substr(index + 1, 3)); + // EMSESP::logger().debug("HTTPS code: %i", httpResult); + } + index = result.find("\r\n\r\n"); + if (index != std::string::npos) { + result.replace(0, index + 4, ""); + // EMSESP::logger().debug("HTTPS response: %s", result.c_str()); } - httpResult = http.POST(value.c_str()); } else { - httpResult = http.GET(); // normal GET + EMSESP::logger().warning("HTTPS connection failed"); } - - http.end(); - + delete ssl_client; + delete basic_client; // check HTTP return code if (httpResult != 200) { - char error[100]; - snprintf(error, sizeof(error), "Schedule %s: URL command failed with http code %d", name, httpResult); - EMSESP::logger().warning(error); + EMSESP::logger().warning("Schedule '%s': URL command failed with http code %d", name, httpResult); + return false; + } + return true; + } else +#endif + if (Helpers::toLower(url.c_str()).starts_with("http://")) { + HTTPClient * http = new HTTPClient; + if (http->begin(url.c_str())) { + bool content_set = false; + for (JsonPair p : doc["header"].as()) { + http->addHeader(p.key().c_str(), p.value().as().c_str()); + content_set |= p.key() == "content-type"; + } + // if there is data, force a POST + if (Helpers::toLower(method) == "post") { // we have all lowercase + if (!content_set) { + // http->addHeader("Content-Type", value.find_first_of('{') != std::string::npos ? "application/json" : "text/plain"); + if (value.starts_with('{')) { + http->addHeader(asyncsrv::T_Content_Type, asyncsrv::T_application_json, false); // auto-set to JSON + } else { + http->addHeader(asyncsrv::T_Content_Type, asyncsrv::T_text_plain, false); // auto-set to JSON + } + } + httpResult = http->POST(value.c_str()); + } else { + httpResult = http->GET(); // normal GET + if (httpResult > 0) { + result = http->getString().c_str(); + } + } + } + http->end(); + delete http; + // check HTTP return code + if (httpResult != 200) { + EMSESP::logger().warning("Schedule '%s': URL command failed with http code %d", name, httpResult); return false; } #if defined(EMSESP_DEBUG) - char msg[100]; - snprintf(msg, sizeof(msg), "Schedule %s: URL command successful with http code %d", name, httpResult); - EMSESP::logger().debug(msg); + EMSESP::logger().debug("Schedule %s: URL '%s' command successful with http code %d", name, url.c_str(), httpResult); #endif return true; }