From 6207bc4de6fcb12af2aed69518db84950240d11e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Tue, 1 Sep 2026 11:36:11 +0200 Subject: [PATCH] v0.8.22 --- Cargo.lock | 2 +- Cargo.toml | 2 +- FILE_MANIFEST.sha256 | 16 ++++++++-------- README.md | 2 +- docs/API.md | 6 +++--- .../gree_controller/manifest.json | 2 +- web/js/core.js | 11 +++++++++-- web/styles.css | 2 +- web/sw.js | 6 ++++-- 9 files changed, 29 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60039df..6dacdd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,7 +633,7 @@ dependencies = [ [[package]] name = "gree-controller" -version = "0.8.21" +version = "0.8.22" dependencies = [ "aes", "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index d58646b..47e5024 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gree-controller" -version = "0.8.21" +version = "0.8.22" edition = "2021" authors = ["GREE Controller contributors"] description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support" diff --git a/FILE_MANIFEST.sha256 b/FILE_MANIFEST.sha256 index c2e25d7..cf9fef4 100644 --- a/FILE_MANIFEST.sha256 +++ b/FILE_MANIFEST.sha256 @@ -1,11 +1,11 @@ d67af429e4da9ce08e9d2f2a8472849ffbd70d135b1c5da535a076026794d04c ./.env.example a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore -630d1a19741908e389efefb4b2db81c089c84203024c7baf0ea94754a7da2a2c ./Cargo.lock -96e7395b59b0e6e8e7b0fd4311d33dcbb2910c756a7f3301299e92fa155b20dc ./Cargo.toml +0571baaa38d6affcd32de5842628a53e0207795a1bb11abceded67ddaf4fbc89 ./Cargo.lock +a746338d1f28b0b6ca4118cdf0bedf5d6e68a5539d2e52b48387763899d96118 ./Cargo.toml 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE -1af97e3a6eec1efa2c91f147dee689beeb33cc7efab0703c91753e056441f1aa ./README.md +f07790d52e9eb7cc9148093c44b3755341dcdfadfe2552457f0fb902ea226f5e ./README.md ec0f495ee6a618bdec10b260ee7838f7a872d8c5b0aa91c546226c1ad818f601 ./build.rs -0c47e3dbaf24ab2d7c05a55f22ab2e3c38146acf9864083ffa5b92f453f5735e ./docs/API.md +4f0cd0c69d168e0c17ec85c9326bf2b105cb82b7fc1dfd20c5f27de9f9e7b6a9 ./docs/API.md 95e3c8c99ba439a86a2fb05cfd8ef00c75cb959a024a349a082da935b726f500 ./home-assistant/README.md f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./home-assistant/custom_components/gree_controller/__init__.py 6910589f27960a28d4de9735884a7e5376e455cd885f19ce2b55947fcd135114 ./home-assistant/custom_components/gree_controller/api.py @@ -16,7 +16,7 @@ ab08fbe40e9bb48ebcbfff98760aaa0c9434b61b705aaaadea0c09c255d99b0e ./home-assista 5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./home-assistant/custom_components/gree_controller/entity_map.py c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/icon.png c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/logo.png -99d34f75475a352410525dd395cc563160d8f8d2f85e22f8287a5c2f5b36062d ./home-assistant/custom_components/gree_controller/manifest.json +d55689391ec02289dfffb622c18d8bc6423df192c267959395751d634ebe3358 ./home-assistant/custom_components/gree_controller/manifest.json 38d659d0273c0d1428679e44574a2666c605d1b3796d323b626baa40560788cf ./home-assistant/custom_components/gree_controller/number.py 39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./home-assistant/custom_components/gree_controller/select.py 1cae12876eb81085910907cf1fcf340a18dcba4f8c967ef222c9496325057849 ./home-assistant/custom_components/gree_controller/sensor.py @@ -131,7 +131,7 @@ e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon. 9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js/README.md b3d5cb473bf9771de78e14c7de730cb86c73f8cdbb6344da67ba7d6abac879ea ./web/js/bootstrap.js 64a43e2142a69ebddf4160c0d0d3b573fa379c650431090fd15c9ba3b62bc4e7 ./web/js/charts.js -82da7a69028e159ef9b72ba32b111916e9dc21618f8c7c0e410e3492773e7c12 ./web/js/core.js +cf8d97770985b8cf920382725fa33d5f61497a639544bfc528af72b276de0b6a ./web/js/core.js 6e421388c7f6deb2aa42f1205dfdc52839f9f178fbd672129972528b8517a870 ./web/js/dashboard.js 7ed64ca0ef62c4cd3d9c314adfcf1c3874a0c49139379dd6f175710786a2ced9 ./web/js/entities.js 8c94a3b4aa5455638332362379b09dc151cdf47ab24774c9fd52100ecad689c5 ./web/js/events.js @@ -144,6 +144,6 @@ d375868ded6ffcd6b18d4c661bb2051e7cc5b136f95d4d68355cdd6e0caaf278 ./web/js/realt 28ff1324f4495b7bcd431eb94f51012c1b1e558f8acb931ac35b71b48374c200 ./web/js/settings-ui.js 9d44596929f67400f5f09d82330af4452914f2b4729eadcd4f695672d2cd849c ./web/js/settings.js fd26156e9f1d6713d3def564ad000553d9a16a24376059701db2ee762c99ee6c ./web/manifest.webmanifest -e97553a992815ba49830514b0f1eb4d686f2097889574e9da6741af90b7ab5eb ./web/styles.css -03ac3a5918539904ae829ee24999d8d59efb41bdf6c0e30f76bab4cee98c3130 ./web/sw.js +32898e4562f8e7e70a20f95d2eecde235f4ecb1799b515f57e55579b0d4fd604 ./web/styles.css +bc95830ec8273818ffde0a6b50eb3cc9ecbeee65a7a1d3aeff08cde86d05dc7b ./web/sw.js d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js diff --git a/README.md b/README.md index 8b7506e..78b53bd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Self-hosted controller for GREE-compatible air conditioners with a local Web UI, thermostat zones, schedules, Home Assistant integration, history, notifications and a documented HTTP/WebSocket API. -**Current release: 0.8.21** +**Current release: 0.8.22** > [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples. diff --git a/docs/API.md b/docs/API.md index b3927f5..8527ba5 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,6 +1,6 @@ # GREE Controller API reference -HTTP and WebSocket API for GREE Controller **0.8.21**. +HTTP and WebSocket API for GREE Controller **0.8.22**. [← Main documentation](../README.md) @@ -212,7 +212,7 @@ Response: { "status": "ok", "name": "gree-controller", - "version": "0.8.21", + "version": "0.8.22", "uptime_seconds": 1234, "control_ready": true, "time": "2026-08-30T06:54:00Z" @@ -236,7 +236,7 @@ Returns the initial Web UI snapshot: "settings": {}, "outdoor_temperature": null, "system": { - "version": "0.8.21", + "version": "0.8.22", "uptime_seconds": 1234, "auth_required": false, "control_ready": true, diff --git a/home-assistant/custom_components/gree_controller/manifest.json b/home-assistant/custom_components/gree_controller/manifest.json index 2a50be0..a57ce66 100644 --- a/home-assistant/custom_components/gree_controller/manifest.json +++ b/home-assistant/custom_components/gree_controller/manifest.json @@ -1,7 +1,7 @@ { "domain": "gree_controller", "name": "GREE Controller", - "version": "0.8.21", + "version": "0.8.22", "config_flow": true, "integration_type": "hub", "iot_class": "local_polling", diff --git a/web/js/core.js b/web/js/core.js index e5531bf..a9a5565 100644 --- a/web/js/core.js +++ b/web/js/core.js @@ -262,14 +262,21 @@ function renderLanguageOptions() { async function loadLanguages() { try { - const response = await fetch(withBase('/lang/index.json'), {cache: 'no-cache'}); + // Language packs must never come from a stale Service Worker/browser cache. + // A cache-busting nonce is intentional: translations can change without the JS asset hash changing. + const languageNonce = Date.now().toString(36); + const languageUrl = path => { + const separator = String(path).includes('?') ? '&' : '?'; + return `${withBase(path)}${separator}v=${languageNonce}`; + }; + const response = await fetch(languageUrl('/lang/index.json'), {cache: 'no-store'}); if (!response.ok) throw new Error(`Language index HTTP ${response.status}`); const manifest = await response.json(); const languages = Array.isArray(manifest.languages) ? manifest.languages : []; if (!languages.some(item => item.code === DEFAULT_LANGUAGE)) throw new Error('Default English language pack is missing'); const loaded = await Promise.all(languages.map(async item => { - const packResponse = await fetch(withBase(item.path || `/lang/${encodeURIComponent(item.code)}.json`), {cache: 'no-cache'}); + const packResponse = await fetch(languageUrl(item.path || `/lang/${encodeURIComponent(item.code)}.json`), {cache: 'no-store'}); if (!packResponse.ok) throw new Error(`Language ${item.code} HTTP ${packResponse.status}`); const pack = await packResponse.json(); return {item, pack}; diff --git a/web/styles.css b/web/styles.css index 6b84a61..75db6fa 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1386,7 +1386,7 @@ textarea[aria-invalid="true"] { .group-custom-temperature-row button { grid-column:auto; } } -/* v0.8.21: make group gates visually unambiguous. */ +/* v0.8.22: make group gates visually unambiguous. */ .list-card.group-card.group-linked { border-color:color-mix(in srgb,var(--group-control-color) 42%,var(--line)); } diff --git a/web/sw.js b/web/sw.js index 76a8ac6..023efa8 100644 --- a/web/sw.js +++ b/web/sw.js @@ -1,12 +1,14 @@ const CACHE = 'gree-controller-__GREE_ASSET_CACHE__'; const SCOPE = new URL(self.registration.scope).pathname.replace(/\/$/, ''); const path = value => `${SCOPE}${value.startsWith('/') ? value : `/${value}`}` || '/'; -const ASSETS = [path('__GREE_STYLES_ASSET__'), path('__GREE_APP_ASSET__'), path('__GREE_THEME_INIT_ASSET__'), path('/favicon.svg'), path('/manifest.webmanifest'), path('/lang/index.json'), path('/lang/en.json'), path('/lang/pl.json')]; +const ASSETS = [path('__GREE_STYLES_ASSET__'), path('__GREE_APP_ASSET__'), path('__GREE_THEME_INIT_ASSET__'), path('/favicon.svg'), path('/manifest.webmanifest')]; self.addEventListener('install', event => event.waitUntil(caches.open(CACHE).then(cache => cache.addAll(ASSETS)).then(() => self.skipWaiting()))); self.addEventListener('activate', event => event.waitUntil(caches.keys().then(keys => Promise.all(keys.filter(key => key !== CACHE).map(key => caches.delete(key)))).then(() => self.clients.claim()))); self.addEventListener('fetch', event => { const url = new URL(event.request.url); - if (event.request.method !== 'GET' || url.pathname.startsWith(path('/api/')) || event.request.mode === 'navigate') return; + // API and language packs stay network-owned. Language JSON is deliberately not + // cache-first so a newly deployed translation can never be masked by an old pack. + if (event.request.method !== 'GET' || url.pathname.startsWith(path('/api/')) || url.pathname.startsWith(path('/lang/')) || event.request.mode === 'navigate') return; event.respondWith(caches.match(event.request).then(cached => cached || fetch(event.request).then(response => { const copy = response.clone(); caches.open(CACHE).then(cache => cache.put(event.request, copy)); return response; })));