iobroker.eos-admin 7.9.32 → 7.9.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,7 +31,7 @@
31
31
  rel="stylesheet"
32
32
  href="css/leaflet.css"
33
33
  />
34
- <link rel="stylesheet" href="./css/eos-branding.css?v=32" />
34
+ <link rel="stylesheet" href="./css/eos-branding.css?v=34" />
35
35
  <link
36
36
  rel="manifest"
37
37
  href="manifest.json"
@@ -154,12 +154,13 @@
154
154
  <script type="module" crossorigin src="./assets/index-CQZugZ1z.js"></script>
155
155
  <link rel="modulepreload" crossorigin href="./assets/preload-helper-BDBacUwf.js">
156
156
  <link rel="modulepreload" crossorigin href="./assets/iobroker_admin__mf_v__runtimeInit__mf_v__-g2X2zhAf.js">
157
- <script defer src="./js/eos-branding.js?v=32"></script>
158
- <script defer src="./js/eos-security-ui.js?v=32"></script>
159
- <script defer src="./js/eos-assistant.js?v=32"></script>
157
+ <script defer src="./js/eos-branding.js?v=34"></script>
158
+ <script defer src="./js/eos-security-ui.js?v=34"></script>
159
+ <script defer src="./js/eos-hard-logout.js?v=34"></script>
160
+ <script defer src="./js/eos-assistant.js?v=34"></script>
160
161
  </head>
161
162
  <body>
162
163
  <noscript>You need to enable JavaScript to run this app.</noscript>
163
164
  <div id="root"></div>
164
- </body>
165
+ </body>
165
166
  </html>
@@ -2,5 +2,5 @@
2
2
  'use strict';
3
3
  // v31: EOS Assist is rendered by eos-branding.js so it can share route/security state.
4
4
  // This file stays as a lightweight compatibility hook for cache-safe deployments.
5
- window.NEXOWATT_EOS_ASSIST_VERSION = 'v31-integrated-ai-ready';
5
+ window.NEXOWATT_EOS_ASSIST_VERSION = 'v33-assist-position-fix';
6
6
  })();
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- window.NEXOWATT_EOS_UI_VERSION = 'v32-nav-toggle-tile-fix';
4
+ window.NEXOWATT_EOS_UI_VERSION = 'v33-assist-position-fix';
5
5
 
6
6
  const BRAND = 'NexoWatt EOS';
7
7
  const EOS_MEANING = 'Energy Operation System';
@@ -510,6 +510,78 @@
510
510
  window.location.assign(cleanRoot);
511
511
  };
512
512
 
513
+
514
+ let hardLogoutTimer = 0;
515
+ let hardLogoutPollTimer = 0;
516
+ let hardLogoutInstalled = false;
517
+
518
+ const clearAuthStorage = () => safe(() => {
519
+ const storageKeys = [
520
+ 'App.refreshToken', 'App.accessToken', 'App.token', 'tokens', 'iobroker.admin.token',
521
+ 'access_token', 'refresh_token', 'oidc_id_token', 'oidc_access_token', 'oidc_refresh_token'
522
+ ];
523
+ storageKeys.forEach(key => {
524
+ window.localStorage?.removeItem(key);
525
+ window.sessionStorage?.removeItem(key);
526
+ window._localStorage?.removeItem?.(key);
527
+ });
528
+ ['access_token', 'refresh_token', 'connect.sid', 'io', 'sid'].forEach(name => {
529
+ document.cookie = `${name}=; Max-Age=0; path=/; SameSite=Lax`;
530
+ document.cookie = `${name}=; Max-Age=0; path=${new URL(ASSET_BASE).pathname || '/'}; SameSite=Lax`;
531
+ });
532
+ });
533
+
534
+ const hardLogout = () => {
535
+ clearAuthStorage();
536
+ const loginUrl = new URL('index.html?login', ASSET_BASE);
537
+ loginUrl.searchParams.set('eosLogout', 'timeout');
538
+ window.location.replace(loginUrl.href);
539
+ };
540
+
541
+ const scheduleHardLogoutCheck = delay => {
542
+ if (hardLogoutPollTimer) window.clearTimeout(hardLogoutPollTimer);
543
+ hardLogoutPollTimer = window.setTimeout(checkHardLogoutSession, Math.max(1000, delay || 15000));
544
+ };
545
+
546
+ async function checkHardLogoutSession() {
547
+ if (isLoginView() || !document.getElementById('app-paper')) {
548
+ scheduleHardLogoutCheck(10000);
549
+ return;
550
+ }
551
+ try {
552
+ const response = await fetch(new URL('session', ASSET_BASE).href, {
553
+ credentials: 'same-origin',
554
+ cache: 'no-store',
555
+ headers: { accept: 'application/json' },
556
+ });
557
+ const data = await response.json().catch(() => ({}));
558
+ const expireInSec = Number(data?.expireInSec);
559
+ if (Number.isFinite(expireInSec) && expireInSec <= 0) {
560
+ hardLogout();
561
+ return;
562
+ }
563
+ if (Number.isFinite(expireInSec) && expireInSec > 0) {
564
+ if (hardLogoutTimer) window.clearTimeout(hardLogoutTimer);
565
+ hardLogoutTimer = window.setTimeout(hardLogout, Math.max(1200, expireInSec * 1000 + 500));
566
+ scheduleHardLogoutCheck(Math.min(Math.max(5000, expireInSec * 500), 30000));
567
+ return;
568
+ }
569
+ } catch (_) {
570
+ // Network reconnects happen during updates/restarts. Do not log out on a transient request failure.
571
+ }
572
+ scheduleHardLogoutCheck(30000);
573
+ }
574
+
575
+ const installHardLogoutWatchdog = () => {
576
+ if (hardLogoutInstalled) return;
577
+ hardLogoutInstalled = true;
578
+ window.addEventListener('focus', () => checkHardLogoutSession(), { passive: true });
579
+ document.addEventListener('visibilitychange', () => {
580
+ if (!document.hidden) checkHardLogoutSession();
581
+ }, { passive: true });
582
+ scheduleHardLogoutCheck(2500);
583
+ };
584
+
513
585
  const ensureBrandBadge = toolbar => {
514
586
  if (!toolbar) return;
515
587
  document.querySelectorAll('.eos-brand-badge').forEach(existing => {
@@ -1104,6 +1176,7 @@
1104
1176
  ensureStandaloneNavToggle();
1105
1177
  installAssistDelegatedClick();
1106
1178
  ensureEosAssist();
1179
+ installHardLogoutWatchdog();
1107
1180
  ensureRightsHelper();
1108
1181
  ensurePermissionPresets();
1109
1182
  ensureSettingsDialogClasses();
@@ -1125,6 +1198,7 @@
1125
1198
  ensureStandaloneNavToggle();
1126
1199
  installAssistDelegatedClick();
1127
1200
  ensureEosAssist();
1201
+ installHardLogoutWatchdog();
1128
1202
  ensureRightsHelper();
1129
1203
  ensurePermissionPresets();
1130
1204
  ensureSettingsDialogClasses();
@@ -0,0 +1,163 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ const LOG_PREFIX = '[NexoWatt EOS hard logout]';
5
+ const VERSION = '34';
6
+ const MIN_POLL_MS = 15_000;
7
+ const MAX_TIMER_MS = 2_147_000_000;
8
+ const MIN_TTL_SEC = 5;
9
+ const STORAGE_KEY = `nexowatt:eos:hardLogoutAt:${location.host}:${location.pathname.split('/')[1] || 'root'}`;
10
+ let logoutTimer = null;
11
+ let pollTimer = null;
12
+ let logoutStarted = false;
13
+
14
+ function isLoginPage() {
15
+ const path = String(window.location.pathname || '').toLowerCase();
16
+ const search = String(window.location.search || '').toLowerCase();
17
+ return path.endsWith('/login') || path.includes('/login/') || search.includes('login');
18
+ }
19
+
20
+ function clearStoredTokens() {
21
+ const storages = [window.localStorage, window.sessionStorage, window._localStorage, window._sessionStorage]
22
+ .filter(Boolean)
23
+ .filter((storage, index, array) => array.indexOf(storage) === index);
24
+ const tokenKeyPattern = /(access[_-]?token|refresh[_-]?token|token[_-]?expires|expires[_-]?in|oauth|bearer|auth|connection)/i;
25
+ for (const storage of storages) {
26
+ try {
27
+ const keys = [];
28
+ for (let i = 0; i < storage.length; i++) {
29
+ const key = storage.key(i);
30
+ if (key && (tokenKeyPattern.test(key) || key === STORAGE_KEY)) {
31
+ keys.push(key);
32
+ }
33
+ }
34
+ keys.forEach(key => storage.removeItem(key));
35
+ } catch (e) {
36
+ // ignore blocked storage
37
+ }
38
+ }
39
+ }
40
+
41
+ function clearAuthCookies() {
42
+ const cookieNames = ['access_token', 'refresh_token', 'connect.sid', 'ioBroker.sid'];
43
+ const paths = ['/', window.location.pathname.replace(/\/[^/]*$/, '/') || '/'];
44
+ for (const name of cookieNames) {
45
+ for (const path of paths) {
46
+ document.cookie = `${name}=; Max-Age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}; SameSite=Lax`;
47
+ }
48
+ }
49
+ }
50
+
51
+ function readDeadline() {
52
+ try {
53
+ const value = Number(window.localStorage.getItem(STORAGE_KEY));
54
+ return Number.isFinite(value) && value > 0 ? value : 0;
55
+ } catch (e) {
56
+ return 0;
57
+ }
58
+ }
59
+
60
+ function writeDeadline(deadline) {
61
+ try {
62
+ window.localStorage.setItem(STORAGE_KEY, String(deadline));
63
+ } catch (e) {
64
+ // ignore blocked storage
65
+ }
66
+ }
67
+
68
+ function hardLogout(reason) {
69
+ if (logoutStarted || isLoginPage()) return;
70
+ logoutStarted = true;
71
+ try { console.warn(LOG_PREFIX, reason || 'session expired'); } catch (e) {}
72
+ if (logoutTimer) {
73
+ clearTimeout(logoutTimer);
74
+ logoutTimer = null;
75
+ }
76
+ if (pollTimer) {
77
+ clearInterval(pollTimer);
78
+ pollTimer = null;
79
+ }
80
+ clearStoredTokens();
81
+ clearAuthCookies();
82
+ const origin = encodeURIComponent((window.location.pathname || '/') + (window.location.search || '') + (window.location.hash || ''));
83
+ window.location.href = './logout?origin=' + origin + '&hard=1';
84
+ }
85
+
86
+ function scheduleHardLogout(deadline) {
87
+ const ms = Math.min(Math.max(deadline - Date.now() + 250, 250), MAX_TIMER_MS);
88
+ if (logoutTimer) clearTimeout(logoutTimer);
89
+ logoutTimer = setTimeout(() => hardLogout('configured login timeout reached'), ms);
90
+ }
91
+
92
+ function applyServerExpiration(expireInSec) {
93
+ const seconds = Number(expireInSec);
94
+ if (!Number.isFinite(seconds)) return;
95
+ if (seconds <= 0) {
96
+ hardLogout('server reported expired session');
97
+ return;
98
+ }
99
+ if (seconds < MIN_TTL_SEC) return;
100
+
101
+ const now = Date.now();
102
+ const candidateDeadline = now + seconds * 1000;
103
+ const storedDeadline = readDeadline();
104
+
105
+ // Set the hard deadline once. If the server reports an earlier expiration later,
106
+ // tighten the deadline. Never extend it through refresh-token based renewal.
107
+ const deadline = !storedDeadline || candidateDeadline < storedDeadline - 5000 ? candidateDeadline : storedDeadline;
108
+ if (deadline !== storedDeadline) writeDeadline(deadline);
109
+
110
+ if (now >= deadline) {
111
+ hardLogout('stored hard deadline reached');
112
+ } else {
113
+ scheduleHardLogout(deadline);
114
+ }
115
+ }
116
+
117
+ async function checkSession() {
118
+ if (logoutStarted) return;
119
+ if (isLoginPage()) {
120
+ clearStoredTokens();
121
+ clearAuthCookies();
122
+ return;
123
+ }
124
+
125
+ const storedDeadline = readDeadline();
126
+ if (storedDeadline && Date.now() >= storedDeadline) {
127
+ hardLogout('stored hard deadline reached');
128
+ return;
129
+ }
130
+
131
+ try {
132
+ const response = await fetch('./session?hard=1&ts=' + Date.now(), {
133
+ credentials: 'include',
134
+ cache: 'no-store',
135
+ headers: { Accept: 'application/json' },
136
+ });
137
+ if (!response.ok) return;
138
+ const session = await response.json();
139
+ if (typeof session.expireInSec === 'number') {
140
+ applyServerExpiration(session.expireInSec);
141
+ }
142
+ } catch (e) {
143
+ // During update/restart the endpoint can be unavailable. Do not logout just because of a network error.
144
+ }
145
+ }
146
+
147
+ function start() {
148
+ checkSession();
149
+ pollTimer = setInterval(checkSession, MIN_POLL_MS);
150
+ window.addEventListener('focus', checkSession, { passive: true });
151
+ document.addEventListener('visibilitychange', () => {
152
+ if (!document.hidden) checkSession();
153
+ });
154
+ }
155
+
156
+ window.NEXOWATT_EOS_HARD_LOGOUT = { version: VERSION, checkSession, hardLogout, clearStoredTokens, clearAuthCookies };
157
+
158
+ if (document.readyState === 'loading') {
159
+ document.addEventListener('DOMContentLoaded', start, { once: true });
160
+ } else {
161
+ start();
162
+ }
163
+ })();
package/build/lib/web.js CHANGED
@@ -450,12 +450,18 @@ class Web {
450
450
  if (!token && req.query?.token) token = req.query.token;
451
451
  if (!token) return null;
452
452
  try {
453
- return decodeURIComponent(token);
453
+ token = decodeURIComponent(token);
454
454
  }
455
455
  catch {
456
- return token;
456
+ // keep raw token
457
457
  }
458
+ token = token.trim();
459
+ if (token.startsWith('Bearer ')) token = token.substring('Bearer '.length).trim();
460
+ if (token.startsWith('s:')) token = token.substring(2);
461
+ if (token.includes('.') && !token.startsWith('eyJ')) token = token.substring(0, token.indexOf('.'));
462
+ return token || null;
458
463
  }
464
+
459
465
  readSession(id) {
460
466
  return new Promise(resolve => this.adapter.getSession(id, token => resolve(token)));
461
467
  }
@@ -576,11 +582,38 @@ class Web {
576
582
  this.server.app.use(cookieParser());
577
583
  this.server.app.use(bodyParser.urlencoded({ extended: true }));
578
584
  this.server.app.use(bodyParser.json());
585
+ // NexoWatt EOS: enforce the configured login timeout as a hard logout.
586
+ // We remove refresh tokens from OAuth responses so the admin client cannot silently extend sessions.
587
+ const eosHardLogout = this.adapter.config.nexowattHardLogout !== false;
588
+ if (eosHardLogout) {
589
+ this.server.app.use('/oauth/token', (req, res, next) => {
590
+ const grantType = String(req.body?.grant_type || '');
591
+ if (req.method === 'POST' && grantType === 'refresh_token') {
592
+ res.status(401).json({
593
+ error: 'invalid_grant',
594
+ error_description: 'EOS session expired. Please log in again.',
595
+ eosHardLogout: true,
596
+ });
597
+ return;
598
+ }
599
+ const originalJson = res.json.bind(res);
600
+ res.json = body => {
601
+ if (body && typeof body === 'object' && body.access_token && body.expires_in) {
602
+ const expiresIn = Math.max(1, Math.round(Number(body.expires_in) || Number(this.settings.ttl) || 3600));
603
+ body.refresh_token = '';
604
+ body.refresh_token_expires_in = expiresIn;
605
+ body.eosHardLogout = true;
606
+ }
607
+ return originalJson(body);
608
+ };
609
+ next();
610
+ });
611
+ }
579
612
  this.oauth2Model = (0, webserver_1.createOAuth2Server)(this.adapter, {
580
613
  app: this.server.app,
581
614
  secure: this.settings.secure,
582
615
  accessLifetime: this.settings.ttl,
583
- refreshLifetime: 60 * 60 * 24 * 7, // 1 week (Maybe adjustable?)
616
+ refreshLifetime: this.settings.ttl,
584
617
  noBasicAuth: this.settings.noBasicAuth,
585
618
  loginPage: (req) => {
586
619
  const isDev = req.url.includes('?dev');
@@ -598,28 +631,44 @@ class Web {
598
631
  },
599
632
  });
600
633
  this.server.app.get('/session', (req, res) => {
601
- if (req.headers.cookie) {
602
- const cookies = req.headers.cookie.split(';').find(c => c.trim().startsWith('access_token='));
603
- let tokenCookie = cookies?.split('=')[1];
604
- if (!tokenCookie && req.headers.authorization?.startsWith('Bearer ')) {
605
- tokenCookie = req.headers.authorization.split(' ')[1];
606
- }
607
- else if (!tokenCookie && req.query?.token) {
608
- tokenCookie = req.query.token;
609
- }
610
- if (tokenCookie) {
611
- void this.adapter.getSession(`a:${tokenCookie[1]}`, (token) => {
612
- if (!token?.user) {
613
- res.json({ expireInSec: 0 });
614
- }
615
- else {
616
- res.json({ expireInSec: Math.round((token.aExp - Date.now()) / 1000) });
617
- }
618
- });
634
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
635
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
636
+ if (!this.settings.auth) {
637
+ res.json({ expireInSec: Math.max(120, this.settings.ttl || 3600), hardLogout: true, auth: false });
638
+ return;
639
+ }
640
+ const tokenValue = this.readAccessTokenFromRequest(req);
641
+ if (!tokenValue) {
642
+ res.json({ expireInSec: 0, hardLogout: true, error: 'Cannot find session token' });
643
+ return;
644
+ }
645
+ const candidates = new Set();
646
+ candidates.add(tokenValue);
647
+ candidates.add(tokenValue.startsWith('a:') ? tokenValue : `a:${tokenValue}`);
648
+ if (tokenValue.length > 1) candidates.add(`a:${tokenValue[1]}`);
649
+ const ids = Array.from(candidates);
650
+ const readNext = (index) => {
651
+ const id = ids[index];
652
+ if (!id) {
653
+ res.json({ expireInSec: 0, hardLogout: true, error: 'Cannot find session' });
619
654
  return;
620
655
  }
621
- }
622
- res.json({ error: 'Cannot find session' });
656
+ void this.adapter.getSession(id, (token) => {
657
+ if (!token?.user) {
658
+ readNext(index + 1);
659
+ return;
660
+ }
661
+ const now = Date.now();
662
+ const expirations = [Number(token.aExp), Number(token.rExp), Number(token.expire), Number(token.expires), Number(token.expiresAt)].filter(value => Number.isFinite(value) && value > 0);
663
+ const expiresAt = expirations.length ? Math.min(...expirations) : now + (this.settings.ttl || 3600) * 1000;
664
+ res.json({
665
+ expireInSec: Math.max(0, Math.floor((expiresAt - now) / 1000)),
666
+ hardLogout: true,
667
+ user: token.user,
668
+ });
669
+ });
670
+ };
671
+ readNext(0);
623
672
  });
624
673
  this.server.app.get(/.*\/nexowatt\/security\/(?:context|session)$/, (req, res) => {
625
674
  void this.sendEosSecuritySession(req, res).catch(e => {
@@ -648,6 +697,9 @@ class Web {
648
697
  origin = origin.substring(0, pos);
649
698
  }
650
699
  }
700
+ for (const cookieName of ['access_token', 'refresh_token', 'connect.sid', 'io', 'ioBroker.sid', 'eos-admin.sid']) {
701
+ res.clearCookie(cookieName, { path: '/' });
702
+ }
651
703
  if (isDev) {
652
704
  res.redirect('http://127.0.0.1:3000/index.html?login');
653
705
  }
@@ -0,0 +1,11 @@
1
+ # NexoWatt EOS Admin v33 – EOS Assist Position
2
+
3
+ Diese Version verschiebt den EOS Assist Launcher weiter nach links, damit der Bereich unten rechts für den Seiten-Einstellbutton, Floating-Action-Buttons und Slider frei bleibt.
4
+
5
+ ## Änderungen
6
+
7
+ - Version auf `7.9.33` erhöht.
8
+ - EOS Assist bleibt unten rechts sichtbar, sitzt aber mit Sicherheitsabstand zum rechten Rand.
9
+ - Der native Seiten-Einstellbutton und andere Floating-Action-Buttons erhalten eine höhere Bedienpriorität.
10
+ - Mobile Ansicht berücksichtigt kleinere Abstände.
11
+ - Keine Backend-, MCP- oder Server-Abhängigkeiten geändert.
@@ -0,0 +1,23 @@
1
+ # NexoWatt EOS Admin v34
2
+
3
+ ## Zweck
4
+
5
+ Version 7.9.34 korrigiert zwei produktive Punkte:
6
+
7
+ 1. Repository-/Update-Metadaten zeigen vollständig auf die aktuelle npm-Version.
8
+ 2. Die konfigurierte Abmeldezeit wird als harte Sitzungslaufzeit erzwungen.
9
+
10
+ ## Update-Fix
11
+
12
+ In vorherigen Paketen konnten `common.meta`, `common.extIcon` und `common.readme` noch auf ältere unpkg-Versionen zeigen. Das konnte dazu führen, dass das Repository zwar eine neue Version anzeigt, der Admin beim Aktualisieren aber alte Metadaten nachlädt.
13
+
14
+ v34 setzt diese Felder synchron auf `7.9.34`.
15
+
16
+ ## Harte Abmeldung
17
+
18
+ Der Upstream-Admin-Client verlängert OAuth-Tokens normalerweise automatisch. Für EOS wird das deaktiviert:
19
+
20
+ - erfolgreiche OAuth-Responses enthalten keinen nutzbaren Refresh-Token mehr,
21
+ - alte Refresh-Token-Versuche werden mit `401 invalid_grant` beantwortet,
22
+ - der Client muss nach Ablauf der eingestellten Abmeldezeit neu anmelden.
23
+
package/io-package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "eos-admin",
4
- "version": "7.9.32",
4
+ "version": "7.9.34",
5
5
  "titleLang": {
6
6
  "en": "NexoWatt EOS Admin",
7
7
  "de": "NexoWatt EOS Admin",
@@ -18,6 +18,10 @@
18
18
  "connectionType": "local",
19
19
  "dataSource": "push",
20
20
  "news": {
21
+ "7.9.34": {
22
+ "en": "Fixed repository metadata for reliable EOS Admin updates and enforced hard logout after the configured login timeout.",
23
+ "de": "Repository-Metadaten für zuverlässige EOS-Admin-Updates korrigiert und harte Abmeldung nach der eingestellten Abmeldezeit erzwungen."
24
+ },
21
25
  "7.9.31": {
22
26
  "en": "UI cleanup: fixes EOS Security German text encoding, removes the logout tile next to the compact navigation arrow, and expands EOS Assist with AI-ready multi-path setup guidance.",
23
27
  "de": "UI-Feinschliff: korrigiert deutsche EOS-Sicherheitsbeschriftungen, entfernt die Abmelden-Kachel neben dem Kompaktpfeil und erweitert EOS Assist um KI-bereite Mehrwege-Einrichtungshilfe."
@@ -161,10 +165,6 @@
161
165
  "pl": "Rozszerzony plik konfiguracyjny JSON do adaptera fregata",
162
166
  "uk": "Розширений налаштування JSON для адаптера фригату",
163
167
  "zh-cn": "JSON 护卫舰适配器扩展配置"
164
- },
165
- "7.9.32": {
166
- "en": "Fixed the EOS navigation compact toggle: the decorative left tile was removed while the arrow button remains visible and usable.",
167
- "de": "EOS Navigationsumschalter korrigiert: Die dekorative linke Kachel wurde entfernt, der Pfeilbutton bleibt sichtbar und nutzbar."
168
168
  }
169
169
  },
170
170
  "desc": {
@@ -202,7 +202,7 @@
202
202
  "icon": "admin.svg",
203
203
  "messagebox": true,
204
204
  "enabled": true,
205
- "extIcon": "https://unpkg.com/iobroker.eos-admin@7.9.30/admin/admin.png",
205
+ "extIcon": "https://unpkg.com/iobroker.eos-admin@7.9.34/admin/admin.svg",
206
206
  "keywords": [
207
207
  "NexoWatt",
208
208
  "EOS",
@@ -213,7 +213,7 @@
213
213
  "licensed"
214
214
  ],
215
215
  "compact": true,
216
- "readme": "https://unpkg.com/iobroker.eos-admin@7.9.30/README.md",
216
+ "readme": "https://unpkg.com/iobroker.eos-admin@7.9.34/README.md",
217
217
  "authors": [
218
218
  "bluefox <bluefox@ccu.io>",
219
219
  "hobbyquaker <hq@ccu.io>"
@@ -282,7 +282,7 @@
282
282
  "nondeletable": false,
283
283
  "allowAdapterUpdate": true,
284
284
  "allowAdapterDelete": false,
285
- "meta": "https://unpkg.com/iobroker.eos-admin@7.9.30/io-package.json",
285
+ "meta": "https://unpkg.com/iobroker.eos-admin@7.9.34/io-package.json",
286
286
  "npmPackage": "iobroker.eos-admin"
287
287
  },
288
288
  "native": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "iobroker.eos-admin",
3
3
  "description": "NexoWatt EOS Admin standalone interface for Energy Operation System",
4
- "version": "7.9.32",
4
+ "version": "7.9.34",
5
5
  "contributors": [
6
6
  "bluefox <dogafox@gmail.com>",
7
7
  "apollon77",
@@ -22,6 +22,15 @@ if (io.common.stopBeforeUpdate !== false) fail('io-package common.stopBeforeUpda
22
22
  if (io.common.dontDelete === true) fail('io-package common.dontDelete must not be true because it blocks clean updates');
23
23
  if (io.common.nondeletable === true) fail('io-package common.nondeletable must not be true because it blocks updates');
24
24
 
25
+ const expectedBase = `https://unpkg.com/iobroker.eos-admin@${pkg.version}`;
26
+ for (const [field, expected] of Object.entries({
27
+ extIcon: `${expectedBase}/admin/admin.svg`,
28
+ readme: `${expectedBase}/README.md`,
29
+ meta: `${expectedBase}/io-package.json`,
30
+ })) {
31
+ if (io.common[field] !== expected) fail(`io-package common.${field} must be ${expected}, got ${io.common[field]}`);
32
+ }
33
+
25
34
  for (const file of [
26
35
  'adminWww/index.html',
27
36
  'adminWww/js/eos-branding.js',
@@ -46,4 +55,9 @@ const bootstrap = bootstrapFiles.map(f => fs.readFileSync(path.join(root, 'admin
46
55
  if (!bootstrap.includes('window.adapterName="eos-admin"')) fail('frontend bootstrap does not set window.adapterName="eos-admin"');
47
56
  if (bootstrap.includes('window.adapterName="admin"')) fail('frontend bootstrap still contains window.adapterName="admin"');
48
57
 
58
+ const webBuild = fs.readFileSync(path.join(root, 'build/lib/web.js'), 'utf8');
59
+ if (!webBuild.includes("body.refresh_token = '';")) fail('build/lib/web.js does not remove OAuth refresh tokens for hard logout');
60
+ if (!webBuild.includes("grantType === 'refresh_token'")) fail('build/lib/web.js does not reject refresh_token grants');
61
+ if (!webBuild.includes('refreshLifetime: this.settings.ttl')) fail('build/lib/web.js does not bind refresh lifetime to ttl');
62
+
49
63
  console.log('[NexoWatt EOS package validation] OK');