iobroker.lovelace 4.1.9 → 4.1.10

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.
package/README.md CHANGED
@@ -473,6 +473,10 @@ After that checkout modified version in `./build` folder. Then.
473
473
  PLACEHOLDER for the next version:
474
474
  ### **WORK IN PROGRESS**
475
475
  -->
476
+ ### 4.1.10 (2024-05-23)
477
+ * (Garfonso) device icons work again.
478
+ * (Garfonso) default user sometimes was not found in system.
479
+
476
480
  ### 4.1.9 (2024-04-26)
477
481
  * (Garfonso) add support for new service call structure.
478
482
  * (Garfonso) add support for delivering files from other adapters, for example, local cover images.
@@ -489,9 +493,6 @@ After that checkout modified version in `./build` folder. Then.
489
493
  ### 4.1.5 (2024-03-05)
490
494
  * (Garfonso) fixed: possible crashes during startup
491
495
 
492
- ### 4.1.4 (2024-02-10)
493
- * (Garfonso) improved fix: lamp icons now turn gray on switch off.
494
-
495
496
  ## License
496
497
 
497
498
  Copyright 2019-2024, bluefox <dogafox@gmail.com>
package/io-package.json CHANGED
@@ -1,8 +1,21 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "lovelace",
4
- "version": "4.1.9",
4
+ "version": "4.1.10",
5
5
  "news": {
6
+ "4.1.10": {
7
+ "en": "device icons work again (if authorization is required).\ndefault user sometimes was not found in system.",
8
+ "de": "gerätesymbole funktionieren wieder (wenn authorisierung notwendig).\nder eingestellte standard-benutzer wurde manchmal nicht im system gefunden.",
9
+ "ru": "иконки устройства снова работают.\nпользователь по умолчанию иногда не был найден в системе.",
10
+ "pt": "ícones do dispositivo funcionam novamente.\nusuário padrão às vezes não foi encontrado no sistema.",
11
+ "nl": "apparaatpictogrammen werken opnieuw.\nde standaardgebruiker werd soms niet gevonden in het systeem.",
12
+ "fr": "les icônes du périphérique fonctionnent à nouveau.\nl'utilisateur par défaut n'a parfois pas été trouvé dans le système.",
13
+ "it": "le icone del dispositivo funzionano di nuovo.\nutente predefinito a volte non è stato trovato nel sistema.",
14
+ "es": "los iconos del dispositivo funcionan de nuevo.\nusuario predeterminado a veces no se encontró en el sistema.",
15
+ "pl": "ikony urządzenia działają ponownie.\ndomyślny użytkownik czasami nie został znaleziony w systemie.",
16
+ "uk": "знову працюють іконки пристрою.\nне знайдено типовий користувач.",
17
+ "zh-cn": "设备图标再次工作.\n默认用户有时在系统中找不到 ."
18
+ },
6
19
  "4.1.9": {
7
20
  "en": "add support for new service call structure.\nadd support for delivering files from other adapters, for example, local cover images.\ncleaned up service descriptions.",
8
21
  "de": "unterstützung für neue service call-struktur hinzufügen.\nhinzufügen von unterstützung für die bereitstellung von dateien von anderen adaptern, zum beispiel lokale cover-bilder.\ngereinigte servicebeschreibungen.",
@@ -53,10 +66,6 @@
53
66
  "4.1.4": {
54
67
  "en": "improved fix: lamp icons now turn gray on switch off.",
55
68
  "de": "verbesserter fix: lampensymbole werden nun grau beim ausschalten."
56
- },
57
- "4.1.3": {
58
- "en": "prevent warning for browser_mod/recall_id service call\nfix: lamp icons now turn gray on switch off.\nfix: notifications via sendTo work again.",
59
- "de": "Verhindere warnung für browser_mod/recall_id service call\nfix: Lampensymbole werden nun grau beim ausschalten.\nfix: Benachrichtigung mittels sendTo funktionieren wieder."
60
69
  }
61
70
  },
62
71
  "titleLang": {
@@ -308,6 +317,18 @@
308
317
  "def": false
309
318
  }
310
319
  },
320
+ {
321
+ "_id": "info.configUpdateProcessed",
322
+ "type": "state",
323
+ "common": {
324
+ "name": "If a config update was processed",
325
+ "type": "boolean",
326
+ "read": true,
327
+ "write": false,
328
+ "role": "state",
329
+ "def": false
330
+ }
331
+ },
311
332
  {
312
333
  "_id": "info.readyForClients",
313
334
  "type": "state",
@@ -221,6 +221,7 @@ class HistoryModule {
221
221
  constructor(options) {
222
222
  this.adapter = options.adapter;
223
223
  this.entityData = options.entityData;
224
+ this.personModule = options.personModule;
224
225
  }
225
226
 
226
227
  async processRequest(req, res) {
@@ -233,7 +234,7 @@ class HistoryModule {
233
234
  entities.push(entity || id);
234
235
  }
235
236
 
236
- const newResult = await getHistory(this.adapter, entities, new Date(req.params.start).getTime(), new Date(req.query.end_time).getTime(), req.query.noAttributes, req._user || this.adapter.config.defaultUser);
237
+ const newResult = await getHistory(this.adapter, entities, new Date(req.params.start).getTime(), new Date(req.query.end_time).getTime(), req.query.noAttributes, this.personModule.getUserIDFromName(req._user));
237
238
  const oldResult = [];
238
239
  for (const [entity_id, states] of Object.entries(newResult)) {
239
240
  const entityResult = [];
@@ -307,7 +308,7 @@ class HistoryModule {
307
308
  entities.push(entity || id);
308
309
  }
309
310
 
310
- const historyData = await getHistory(this.adapter, entities, parameters.startTime, Date.now(), parameters.noAttributes, this.adapter.config.defaultUser);
311
+ const historyData = await getHistory(this.adapter, entities, parameters.startTime, Date.now(), parameters.noAttributes, this.personModule.getUserIDFromName(ws.__auth?.username));
311
312
  sendHistoryResponse(ws, message.id, historyData, parameters);
312
313
  return true;
313
314
  }
@@ -28,24 +28,36 @@ class PersonModule {
28
28
  * @returns {string}
29
29
  */
30
30
  getUserIDFromName(name) {
31
+ if (!this.adapter.config.auth || name === this.adapter.config.defaultUser) {
32
+ return this.adapter.config.defaultUser;
33
+ }
34
+
35
+ if (typeof name !== 'string') { //prevent error if no user supplied.
36
+ throw new Error('Username supplied is not a string, can not find id: ' + name);
37
+ }
38
+
31
39
  for (const userObj of Object.values(this.usersCache)) {
32
40
  if (userObj.name === name) {
33
41
  return userObj.iobId;
34
42
  }
35
43
  }
36
44
 
37
- //fallback, default user "admin" is misleading, if user renamed admin.. hm. :-/
38
- if (this.adapter.config.auth && name === 'admin') {
39
- return 'system.user.admin';
40
- }
41
-
42
45
  this.adapter.log.warn(`Could not get user id for ${name} - Trying with username` + JSON.stringify(this.usersCache['system.user.' + name.toLowerCase()]));
43
46
  return 'system.user.' + name.toLowerCase(); //hack and not correct since js-controller 3.2
44
47
  }
45
48
 
49
+ /**
50
+ * Get username for id. Needed for default user -> name conversion, for example.
51
+ * @param {string} id
52
+ * @returns {string | undefined}
53
+ */
54
+ getUserNameFromID(id) {
55
+ return this.usersCache[id]?.name;
56
+ }
57
+
46
58
  onObjectChange(id, obj) {
47
59
  if (id.startsWith('system.user.')) {
48
- if (obj && obj.common && obj.common.enabled()) {
60
+ if (obj && obj.common && obj.common.enabled) {
49
61
  this.usersCache[id] = {
50
62
  iobId: obj._id,
51
63
  name: obj.common.name || '',
@@ -62,6 +74,9 @@ class PersonModule {
62
74
 
63
75
  async init() {
64
76
  const userObjects = await this.adapter.getForeignObjectsAsync('system.user.*', 'user');
77
+ if (!this.adapter.config.defaultUser.startsWith('system.user.')) { //augment default user to be full id here once.
78
+ this.adapter.config.defaultUser = `system.user.${this.adapter.config.defaultUser}`;
79
+ }
65
80
  let defaultUserObject = null;
66
81
  for (const [id, obj] of Object.entries(userObjects)) {
67
82
  if (obj.common && obj.common.enabled) { //only show enabled persons?
@@ -72,7 +87,7 @@ class PersonModule {
72
87
  picture: obj.common.icon,
73
88
  description: obj.common.desc
74
89
  };
75
- if (obj.common.name === this.adapter.config.defaultUser) {
90
+ if (id === this.adapter.config.defaultUser) {
76
91
  defaultUserObject = obj;
77
92
  }
78
93
  }
@@ -80,16 +95,14 @@ class PersonModule {
80
95
 
81
96
  //default user only relevant for !auth.
82
97
  if (!defaultUserObject) {
83
- //Ok, we did not find defaultUser object. Might be renamed admin?
84
- defaultUserObject = userObjects['system.user.admin'];
85
- this.adapter.config.defaultUser = defaultUserObject.common.name;
86
- if (this.adapter.config.defaultUser !== 'admin' && !this.adapter.config.auth) {
87
- //Activating auth will hide this setting. Not sure what to do then... In general: all users will be able to read everything in this case.
88
- this.adapter.log.warn(`Could not find default user ${this.adapter.config.defaultUser} - Using admin - Please update your configuration.`);
98
+ if (!defaultUserObject && !this.adapter.config.auth) {
99
+ const configuredUserNotFound = `Could not find default user ${this.adapter.config.defaultUser}. Please update your configuration.`;
100
+ this.adapter.log.error(configuredUserNotFound);
101
+
102
+ throw new Error(configuredUserNotFound);
89
103
  }
90
104
  }
91
105
 
92
-
93
106
  await this.adapter.subscribeObjectsAsync('system.user.*');
94
107
  }
95
108
  }
package/lib/server.js CHANGED
@@ -165,10 +165,6 @@ class WebServer {
165
165
  adapter: this.adapter,
166
166
  objects: this._objectData.objects
167
167
  }),
168
- history: new HistoryModule({
169
- adapter: this.adapter,
170
- entityData: entityData
171
- }),
172
168
  conversation: new ConversationModule({
173
169
  adapter: this.adapter,
174
170
  sendResponse: this._sendResponse,
@@ -198,6 +194,11 @@ class WebServer {
198
194
  adapter: this.adapter
199
195
  })
200
196
  };
197
+ this._modules.history = new HistoryModule({
198
+ adapter: this.adapter,
199
+ entityData: entityData,
200
+ personModule: this._modules.person
201
+ });
201
202
 
202
203
  this.converter = {
203
204
  [Types.socket]: converterSwitch.processSocket,
@@ -276,6 +277,13 @@ class WebServer {
276
277
  }
277
278
  this.adapter.setState('info.readyForClients', true, true);
278
279
  this.log.debug('Initialization done.');
280
+ }).catch((err) => {
281
+ this.log.error(`Initialization error: ${err}`);
282
+ if (typeof this.adapter.terminate === 'function') {
283
+ this.adapter.terminate(utils.EXIT_CODES.INVALID_ADAPTER_CONFIG);
284
+ } else {
285
+ process.exit(utils.EXIT_CODES.INVALID_ADAPTER_CONFIG);
286
+ }
279
287
  });
280
288
  }
281
289
 
@@ -297,6 +305,7 @@ class WebServer {
297
305
  }
298
306
  await this._getAllStates();
299
307
  await this._manageSubscribesFromConfig();
308
+ this.log.debug('entitiesUpdated for startup.');
300
309
  await this.adapter.setStateAsync('info.entitiesUpdated', true, true);
301
310
  }
302
311
 
@@ -525,7 +534,7 @@ class WebServer {
525
534
  }
526
535
 
527
536
  async _processSingleCall(ws, data, entity_id) {
528
- const user = this._modules.person.getUserIDFromName(ws.__auth.username || this.config.defaultUser);
537
+ const user = this._modules.person.getUserIDFromName(ws.__auth?.username);
529
538
 
530
539
  const entity = entityData.entityId2Entity[entity_id];
531
540
  const id = entity.context.STATE.setId;
@@ -696,7 +705,7 @@ class WebServer {
696
705
  entity.state = 'unknown';
697
706
  if (entity.context.STATE && entity.context.STATE.getId) {
698
707
  try {
699
- const user = this._modules.person.getUserIDFromName(this.config.defaultUser); // TODO: why is this always defaultUser?
708
+ const user = this.config.defaultUser;
700
709
  const state = await this.adapter.getForeignStateAsync(entity.context.STATE.getId, {user});
701
710
  if (state) {
702
711
  await this.onStateChange(entity.context.STATE.getId, state);
@@ -1495,7 +1504,7 @@ class WebServer {
1495
1504
  file = file.substring(0, pos);
1496
1505
  }
1497
1506
  try {
1498
- const user = this._modules.person.getUserIDFromName(this.config.defaultUser); // TODO: why is this always default user?
1507
+ const user = this._modules.person.getUserIDFromName(req._user);
1499
1508
  let data;
1500
1509
  if (file.startsWith('/lovelace/')) {
1501
1510
  file = file.replace('/lovelace/', '');
@@ -1519,7 +1528,7 @@ class WebServer {
1519
1528
  file = file.substring(0, pos);
1520
1529
  }
1521
1530
  try {
1522
- const user = this._modules.person.getUserIDFromName(this.config.defaultUser); // TODO: why is this always default user?
1531
+ const user = this._modules.person.getUserIDFromName(req._user);
1523
1532
  const data = await this.adapter.readFileAsync(this.adapter.namespace, file.replace('/local/custom_ui/', '/cards/'), {user});
1524
1533
  const pos = req.url.lastIndexOf('.');
1525
1534
  res.setHeader('content-type', (mime.getType || mime.lookup).call(data.mimeType, file.substring(pos + 1).toLowerCase()));
@@ -1864,15 +1873,17 @@ class WebServer {
1864
1873
  }
1865
1874
  }
1866
1875
  }
1867
- } else {
1868
- req._user = this.config.defaultUser;
1869
1876
  }
1870
1877
 
1878
+ //if no auth or page does not require auth, use default user:
1879
+ //invalid token run into return above and close connection.
1880
+ req._user = req._user || this.config.defaultUser;
1881
+
1871
1882
  next();
1872
1883
  }
1873
1884
  });
1874
1885
 
1875
- //handle local images that are content of some states:
1886
+ //handle local images that are content of some states:
1876
1887
  this._app.use(async (req, res, next) => {
1877
1888
  if (this._requestableFiles.includes(req.url)) {
1878
1889
  if (!req._user) { //sadly frontend does not send auth info with most request.... :-/
@@ -1925,7 +1936,7 @@ class WebServer {
1925
1936
  if (obj && obj.common.type === 'file') {
1926
1937
  contentType = (mime.getType || mime.lookup).call(mime, fileName[0]);
1927
1938
  }
1928
- const user = this._modules.person.getUserIDFromName(this.config.defaultUser);
1939
+ const user = this._modules.person.getUserIDFromName(req._user);
1929
1940
  const data = await this.adapter.getBinaryStateAsync(fileName[0], {user});
1930
1941
  if (data !== null && obj !== undefined) {
1931
1942
  if (data && typeof data === 'object' && data.val !== undefined && data.ack !== undefined) {
@@ -1994,7 +2005,7 @@ class WebServer {
1994
2005
  return res.status(404).json({error: 'Start or end misformated'});
1995
2006
  }
1996
2007
 
1997
- const user = this._modules.person.getUserIDFromName(this.config.defaultUser);
2008
+ const user = this._modules.person.getUserIDFromName(req._user);
1998
2009
  try {
1999
2010
  const state = await this.adapter.getForeignStateAsync(entity.context.STATE.getId, {user});
2000
2011
  if (state && state.val) {
@@ -2076,10 +2087,10 @@ class WebServer {
2076
2087
  }
2077
2088
 
2078
2089
  async _getCurrentUser(ws) {
2079
- const user = this._modules.person.getUserIDFromName(ws.__auth.username || this.config.defaultUser);
2090
+ const user = this._modules.person.getUserIDFromName(ws.__auth.username);
2080
2091
  const userObj = {
2081
2092
  id: user,
2082
- name: ws.__auth.username || this.config.defaultUser,
2093
+ name: ws.__auth.username || this._modules.person.getUserNameFromID(this.config.defaultUser),
2083
2094
  is_owner: user === 'system.user.admin',
2084
2095
  is_admin: user === 'system.user.admin',
2085
2096
  credentials: [{auth_provider_type: 'iobroker', auth_provider_id: null}],
@@ -2165,7 +2176,7 @@ class WebServer {
2165
2176
  throw new Error('no entity found');
2166
2177
  } else {
2167
2178
  let id;
2168
- let userName = this.config.defaultUser;
2179
+ let userName; // will be ignored in case of no authentication enabled.
2169
2180
  if (this.config.auth !== false && (token || access_token)) {
2170
2181
  if (access_token) {
2171
2182
  const now = Date.now();
@@ -2669,7 +2680,8 @@ class WebServer {
2669
2680
  if (id === `${this.adapter.namespace}.configuration`) {
2670
2681
  this._lovelaceConfig = obj.native;
2671
2682
  await this._manageSubscribesFromConfig();
2672
- await this.adapter.setStateAsync('info.entitiesUpdated', true, true);
2683
+ this.log.debug(`configUpdateProcessed for config.`);
2684
+ await this.adapter.setStateAsync('info.configUpdateProcessed', true, true);
2673
2685
  } else if (id === 'system.config') {
2674
2686
  if (obj && obj.common) {
2675
2687
  this.lang = obj.common.language || this.lang || 'en';
@@ -2677,6 +2689,7 @@ class WebServer {
2677
2689
  this.systemConfig = obj.common;
2678
2690
  this._updateConstantEntities();
2679
2691
  this.log.debug(`${id} -> config updated, constant entities updated.`);
2692
+ this.log.debug('entitiesUpdated for system.config.');
2680
2693
  await this.adapter.setStateAsync('info.entitiesUpdated', true, true);
2681
2694
  }
2682
2695
  } else {
@@ -2753,6 +2766,7 @@ class WebServer {
2753
2766
  for (const id of idsWithUpdate) {
2754
2767
  await this.onStateChange(id, null, true);
2755
2768
  }
2769
+ this.log.debug('entitiesUpdated for object changes.');
2756
2770
  await this.adapter.setStateAsync('info.entitiesUpdated', true, true);
2757
2771
  this.log.debug('Had changes, updated states and notified entitiesUpdated state.');
2758
2772
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.lovelace",
3
- "version": "4.1.9",
3
+ "version": "4.1.10",
4
4
  "description": "With this adapter you can build visualization for ioBroker with Home Assistant Lovelace UI",
5
5
  "author": {
6
6
  "name": "bluefox",