iobroker.google-sharedlocations2 0.3.2 → 0.3.4

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
@@ -27,6 +27,12 @@ Copyright and trademark of Google are property of Google.
27
27
  Placeholder for the next version (at the beginning of the line):
28
28
  ### **WORK IN PROGRESS**
29
29
  -->
30
+ ### 0.3.4 (2026-04-22)
31
+ * (Garfonso) replaced axios dependency. Tried to make login more robust.
32
+
33
+ ### 0.3.3 (2026-02-17)
34
+ * (Garfonso) if deleting cookies, also delete cookies in Browser to force login with username & password.
35
+
30
36
  ### 0.3.2 (2026-02-09)
31
37
  * (Garfonso) refresh with browser ignores cookies from adapter
32
38
 
Binary file
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "google-sharedlocations2",
4
- "version": "0.3.2",
4
+ "version": "0.3.4",
5
5
  "news": {
6
+ "0.3.4": {
7
+ "en": "replaced axios dependency. Tried to make login more robust.",
8
+ "de": "ersetzt axios Abhängigkeit. Versuchen Sie, die Anmeldung robuster zu machen.",
9
+ "ru": "замена аксиозависимости. Пытался сделать логин более надежным.",
10
+ "pt": "substituiu a dependência axios. Tentou tornar o login mais robusto.",
11
+ "nl": "axios afhankelijkheid vervangen. Probeerde om login robuuster te maken.",
12
+ "fr": "a remplacé la dépendance aux axios. J'ai essayé de rendre la connexion plus robuste.",
13
+ "it": "ha sostituito la dipendenza dagli assi. Ho cercato di rendere il login più robusto.",
14
+ "es": "sustituyó la dependencia de axios. Traté de hacer que el login sea más robusto.",
15
+ "pl": "zastąpił zależność aksjońską. Próbował uczynić logowanie bardziej solidnym.",
16
+ "uk": "замінена аксіос залежність. Виконується, щоб зробити логін більш надійним.",
17
+ "zh-cn": "取代对轴的依赖。 试图使登录更坚固."
18
+ },
19
+ "0.3.3": {
20
+ "en": "if deleting cookies, also delete cookies in Browser to force login with username & password.",
21
+ "de": "wenn Sie Cookies löschen, löschen Sie auch Cookies im Browser, um die Anmeldung mit Benutzername & Passwort zu zwingen.",
22
+ "ru": "при удалении файлов cookie также удаляйте файлы cookie в браузере, чтобы заставить войти в систему с именем пользователя и паролем.",
23
+ "pt": "se excluir cookies, também excluir cookies no Navegador para forçar login com nome de usuário e senha.",
24
+ "nl": "als u cookies verwijdert, verwijdert u ook cookies in de browser om inloggen met gebruikersnaam en wachtwoord te forceren.",
25
+ "fr": "si vous supprimez les cookies, supprimez également les cookies dans le navigateur pour forcer la connexion avec le nom d'utilisateur et le mot de passe.",
26
+ "it": "se la cancellazione dei cookie, anche eliminare i cookie in Browser per forzare il login con nome utente e password.",
27
+ "es": "si elimina las cookies, también eliminar las cookies en Browser para forzar el login con el nombre de usuario & contraseña.",
28
+ "pl": "jeśli usuniesz pliki cookie, usuń je również w przeglądarce, aby wymusić logowanie z nazwą użytkownika i hasłem.",
29
+ "uk": "якщо видаліть файли cookie, також видаліть файли cookie у браузері, щоб захистити логін з ім'ям користувача та паролем.",
30
+ "zh-cn": "如果删除 cookie, 也可以在浏览器中删除 cookie, 以强制用户名和密码登录 ."
31
+ },
6
32
  "0.3.2": {
7
33
  "en": "refresh with browser ignores cookies from adapter",
8
34
  "de": "mit browser aktualisieren ignoriert cookies vom adapter",
@@ -67,32 +93,6 @@
67
93
  "pl": "poprawa odzyskiwania z błędów logowania",
68
94
  "uk": "поліпшення відновлення від помилок логіна",
69
95
  "zh-cn": "改进登录错误的恢复"
70
- },
71
- "0.1.0": {
72
- "en": "added: support for places\nadded: support for fences\ntry to prevent login as much as possible.",
73
- "de": "hinzugefügt: unterstützung für places\nhinzugefügt: unterstützung für fences\nversuche, die anmeldung so weit wie möglich zu verhindern.",
74
- "ru": "добавлено: поддержка мест\nдобавлено: поддержка заборов\nстарайтесь максимально предотвратить вход.",
75
- "pt": "adicionado: suporte para locais\nadicionado: suporte para cercas\ntentar evitar o login o máximo possível.",
76
- "nl": "toegevoegd: ondersteuning voor plaatsen\ntoegevoegd: steun voor hekken\nproberen zo veel mogelijk inloggen te voorkomen.",
77
- "fr": "ajouté: soutien aux places\najouté: soutien aux clôtures\nessayer d'éviter la connexion autant que possible.",
78
- "it": "aggiunto: supporto per i luoghi\naggiunto: supporto per recinzioni\ncercare di impedire il login il più possibile.",
79
- "es": "añadido: apoyo a los lugares\nañadido: soporte para vallas\ntratar de prevenir la entrada tanto como sea posible.",
80
- "pl": "dodane: wsparcie dla miejsc\ndodane: wsparcie dla ogrodzeń\nspróbuj zapobiec logowaniu jak najwięcej.",
81
- "uk": "додано: підтримка місць\nдоданий: підтримка парканів\nнамагатися попередити логін якомога простіше.",
82
- "zh-cn": "添加:对位置的支持\n添加:支持围栏\n尽量防止登录."
83
- },
84
- "0.0.3": {
85
- "en": "prevent login if no username and password is set\nfix tests",
86
- "de": "verhindern login, wenn kein benutzername und passwort eingestellt ist\ntests repariert",
87
- "ru": "предотвратить вход в систему, если имя пользователя и пароль не установлены\nисправление",
88
- "pt": "impedir o login se nenhum nome de usuário e senha estiver definido\ncorrigir os testes",
89
- "nl": "login voorkomen als er geen gebruikersnaam en wachtwoord is ingesteld\nvastleggen van tests",
90
- "fr": "empêcher le login si aucun nom d'utilisateur et mot de passe n'est défini\nessais de correction",
91
- "it": "impedire il login se non viene impostato nessun nome utente e password\ntest di correzione",
92
- "es": "previene el inicio de sesión si no se establece el nombre de usuario y la contraseña\npruebas de reparación",
93
- "pl": "zapobiec logowaniu, jeśli nie jest ustawiona nazwa użytkownika i hasło\nbadania naprawcze",
94
- "uk": "заборонити логін, якщо не встановлено ім’я користувача та пароль\nфіксувати тести",
95
- "zh-cn": "如果没有设置用户名和密码, 请防止登录\n固定测试"
96
96
  }
97
97
  },
98
98
  "titleLang": {
@@ -158,7 +158,7 @@
158
158
  ],
159
159
  "globalDependencies": [
160
160
  {
161
- "admin": ">=7.0.23"
161
+ "admin": ">=7.6.17"
162
162
  }
163
163
  ],
164
164
  "osDependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.google-sharedlocations2",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Share your location with iobroker via google maps.",
5
5
  "author": {
6
6
  "name": "Garfonso",
@@ -26,21 +26,19 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@iobroker/adapter-core": "^3.3.2",
29
- "axios": "^1.13.4",
30
- "puppeteer": "^24.36.1"
29
+ "puppeteer": "^24.42.0"
31
30
  },
32
31
  "devDependencies": {
33
- "@alcalzone/release-script": "^5.0.0",
34
- "@alcalzone/release-script-plugin-iobroker": "^4.0.0",
35
- "@alcalzone/release-script-plugin-license": "^4.0.0",
36
- "@alcalzone/release-script-plugin-manual-review": "^4.0.0",
32
+ "@alcalzone/release-script": "^5.1.1",
33
+ "@alcalzone/release-script-plugin-iobroker": "^5.1.2",
34
+ "@alcalzone/release-script-plugin-license": "^5.1.1",
35
+ "@alcalzone/release-script-plugin-manual-review": "^5.1.1",
37
36
  "@iobroker/adapter-dev": "^1.5.0",
38
- "@iobroker/dev-server": "^0.8.0",
39
37
  "@iobroker/eslint-config": "^2.2.0",
40
38
  "@iobroker/testing": "^5.2.2",
41
- "@iobroker/types": "^7.1.0",
39
+ "@iobroker/types": "^7.1.1",
42
40
  "@tsconfig/node22": "^22.0.5",
43
- "@types/node": "^22.19.7",
41
+ "@types/node": "^22.19.17 < 23",
44
42
  "source-map-support": "^0.5.21",
45
43
  "ts-node": "^10.9.2",
46
44
  "typescript": "~5.9.3"
package/src/lib/Cookie.ts CHANGED
@@ -1,8 +1,8 @@
1
- import axios from 'axios';
2
1
  import type { GoogleSharedlocations2 } from '../main';
3
2
  import puppeteer from 'puppeteer';
4
- import type { Browser, Page, CookieData } from 'puppeteer';
3
+ import type { Browser, Page, CookieData, CookiePriority, CookieSameSite } from 'puppeteer';
5
4
  import { mkdir } from 'fs/promises';
5
+ import type { RequestCredentials } from 'undici-types/fetch';
6
6
 
7
7
  /**
8
8
  * Helper class to manage Google cookies.
@@ -114,18 +114,24 @@ export class Cookie {
114
114
  /**
115
115
  * Augment the current cookie with data from the 'set-cookie' header.
116
116
  *
117
- * @param headers - HTTP headers of axios response
117
+ * @param headersObj - HTTP headers of response
118
118
  */
119
- async augmentCookieFromHeader(headers: Record<string, any>): Promise<void> {
120
- if (headers['set-cookie'] && headers['set-cookie'].length) {
119
+ async augmentCookieFromHeader(headersObj: Headers): Promise<void> {
120
+ const headers = headersObj.getSetCookie();
121
+ if (headers.length > 0) {
121
122
  this.log?.debug('New header received.');
122
123
  const oldLength = this.cookies.length;
123
124
 
124
125
  //split old cookie and new cookie. Update single values.
125
- for (const header of headers['set-cookie']) {
126
+ for (const header of headers) {
126
127
  //console.log('Processing header cookie:', header);
127
128
  const keyValues = header.split('; ');
128
- const [name, value] = keyValues.shift().split('='); // first part is cookie, rest are attributes like path, secure etc.
129
+ const firstCookie = keyValues.shift();
130
+ if (firstCookie === undefined) {
131
+ this.log.debug(`Invalid cookie header: ${header}`);
132
+ continue;
133
+ }
134
+ const [name, value] = firstCookie.split('='); // first part is cookie, rest are attributes like path, secure etc.
129
135
  const cookie = {
130
136
  name: name.trim(),
131
137
  value: value.trim(),
@@ -147,13 +153,13 @@ export class Cookie {
147
153
  cookie.httpOnly = true;
148
154
  break;
149
155
  case 'samesite':
150
- cookie.sameSite = v ? v.trim() : 'Lax';
156
+ cookie.sameSite = v ? (v.trim() as CookieSameSite) : 'Lax';
151
157
  break;
152
158
  case 'expires':
153
159
  cookie.expires = new Date(v).getTime() / 1000; //puppeteer expects expires in seconds, not milliseconds
154
160
  break;
155
161
  case 'priority':
156
- cookie.priority = v ? v.trim() : 'Medium';
162
+ cookie.priority = v ? (v.trim() as CookiePriority) : 'Medium';
157
163
  break;
158
164
  default:
159
165
  this.log.debug(`Unknown cookie attribute: ${k}=${v}`);
@@ -189,8 +195,9 @@ export class Cookie {
189
195
  */
190
196
  async improveCookie(): Promise<boolean> {
191
197
  //see https://github.com/costastf/locationsharinglib/blob/master/locationsharinglib/locationsharinglib.py#L105
198
+ const url = 'https://myaccount.google.com/?hl=en';
192
199
  const options = {
193
- url: 'https://myaccount.google.com/?hl=en',
200
+ credentials: 'same-origin' as RequestCredentials, //or do we need 'include' here?
194
201
  headers: {
195
202
  Cookie: this.cookies.map(c => `${c.name}=${c.value}`).join('; '),
196
203
  },
@@ -198,7 +205,7 @@ export class Cookie {
198
205
  };
199
206
 
200
207
  try {
201
- const response = await axios(options);
208
+ const response = await fetch(url, options);
202
209
 
203
210
  if (response.status !== 200) {
204
211
  this.log?.error(`Failed improving cookie: ${response.status}`);
@@ -257,30 +264,34 @@ export class Cookie {
257
264
 
258
265
  //send request with current cookies
259
266
  this.log.debug('Sending request with current cookies');
267
+ const url =
268
+ 'https://www.google.com/maps/rpc/locationsharing/read?authuser=2&hl=en&gl=us&pb=!1m7!8m6!1m3!1i14!2i8413!3i5385!2i6!3x4095!2m3!1e0!2sm!3i407105169!3m7!2sen!5e1105!12m4!1e68!2m2!1sset!2sRoadmap!4e1!5m4!1e4!8m2!1e0!1e1!6m9!1e12!2i2!26m1!4b1!30m1!1f1.3953487873077393!39b1!44e1!50e0!23i4111425';
260
269
  const options = {
261
270
  method: 'GET',
262
- url: 'https://www.google.com/maps/rpc/locationsharing/read',
271
+ credentials: 'same-origin' as RequestCredentials, //or do we need 'include' here?
263
272
  headers: {
264
273
  Cookie: this.cookies.map(c => `${c.name}=${c.value}`).join('; '),
265
274
  },
266
- params: {
275
+ /*params: {
267
276
  authuser: 2,
268
277
  hl: 'en',
269
278
  gl: 'us',
270
279
  //pb is place on map. Is irrelevant, set to google head quarters here.
271
280
  pb: '!1m7!8m6!1m3!1i14!2i8413!3i5385!2i6!3x4095!2m3!1e0!2sm!3i407105169!3m7!2sen!5e1105!12m4!1e68!2m2!1sset!2sRoadmap!4e1!5m4!1e4!8m2!1e0!1e1!6m9!1e12!2i2!26m1!4b1!30m1!1f1.3953487873077393!39b1!44e1!50e0!23i4111425',
272
- },
281
+ },*/
273
282
  };
274
-
275
283
  try {
276
- const response = await axios.request(options);
284
+ const response = await fetch(url, options);
277
285
  this.log.debug(`Request successful, response code: ${response.status}`);
278
- const data = response.data.split('\n').slice(1).join('\n');
279
- const locationData = JSON.parse(data);
280
- const locations = locationData[0];
281
- if (locations && locations.length > 0) {
282
- await this.augmentCookieFromHeader(response.headers);
283
- return locations;
286
+ if (response.ok) {
287
+ const dataBuffer = await response.text();
288
+ const data = dataBuffer.split('\n').slice(1).join('\n');
289
+ const locationData = JSON.parse(data);
290
+ const locations = locationData[0];
291
+ if (locations && locations.length > 0) {
292
+ await this.augmentCookieFromHeader(response.headers);
293
+ return locations;
294
+ }
284
295
  }
285
296
  this.log.info('No shared locations found in the response, probably not logged in.');
286
297
  } catch (e) {
@@ -314,6 +325,7 @@ export class Cookie {
314
325
  /**
315
326
  * Refresh the current cookie by using puppeteer to load Google Maps with existing cookie.
316
327
  *
328
+ * @param withCookies - if true, will try to set existing cookies in browser before loading page, default is false. This can help if cookies are still valid but not complete enough to work without browser session.
317
329
  * @returns true if refresh was successful
318
330
  */
319
331
  async refreshCookieWithBrowser(withCookies: boolean = false): Promise<boolean> {
@@ -376,21 +388,25 @@ export class Cookie {
376
388
 
377
389
  /**
378
390
  * Login to Google using puppeteer to get new cookies.
391
+ *
392
+ * @param forceLogin - if true, will try to login even if current cookie seems valid, default is false
379
393
  */
380
- async loginToGetNewCookies(): Promise<boolean> {
394
+ async loginToGetNewCookies(forceLogin: boolean = false): Promise<boolean> {
381
395
  let currentStep;
382
396
  try {
383
397
  // try to refresh cookie from browser session first:
384
- let result = await this.refreshCookieWithBrowser();
385
- if (result) {
386
- this.log.info('Cookie refresh successful, no need to login again.');
387
- return true;
388
- } else if (this.isValid()) {
389
- this.log.info('Current cookie seems valid, trying refresh.');
390
- result = await this.refreshCookieWithBrowser(true);
398
+ if (!forceLogin) {
399
+ let result = await this.refreshCookieWithBrowser();
391
400
  if (result) {
392
- this.log.info('Cookie refresh with existing cookies successful, no need to login again.');
401
+ this.log.info('Cookie refresh successful, no need to login again.');
393
402
  return true;
403
+ } else if (this.isValid()) {
404
+ this.log.info('Current cookie seems valid, trying refresh.');
405
+ result = await this.refreshCookieWithBrowser(true);
406
+ if (result) {
407
+ this.log.info('Cookie refresh with existing cookies successful, no need to login again.');
408
+ return true;
409
+ }
394
410
  }
395
411
  }
396
412
 
@@ -416,6 +432,13 @@ export class Cookie {
416
432
  currentStep = msg;
417
433
  this.log.debug(msg);
418
434
  };
435
+
436
+ if (forceLogin) {
437
+ logDebug('Force login enabled, clearing cookies and local storage.');
438
+ const cookies = await this.browser!.cookies();
439
+ await this.browser!.deleteCookie(...cookies);
440
+ }
441
+
419
442
  logDebug('going to google login page.');
420
443
  await page.goto(
421
444
  'https://accounts.google.com/ServiceLogin?hl=de&continue=https://www.google.com/maps&gae=cb-eomtm',
@@ -439,8 +462,21 @@ export class Cookie {
439
462
  return false;
440
463
  }
441
464
 
442
- logDebug('filling in username.');
443
- await page.locator('#identifierId').fill(this.username);
465
+ try {
466
+ logDebug('Trying to click on username, if user was logged in before.');
467
+ const userElement = await page.$(`[data-email="${this.username}"]`);
468
+ if (userElement) {
469
+ await userElement.click();
470
+ } else {
471
+ logDebug('No user element found, filling in username.');
472
+ await page.locator('#identifierId').fill(this.username);
473
+ }
474
+ } catch (e: any) {
475
+ logDebug(`Ok, no user it seems (${e}). Let's fill in useranme`);
476
+ logDebug('filling in username.');
477
+ await page.locator('#identifierId').fill(this.username);
478
+ }
479
+
444
480
  //is this enough, or do we need to search button in this div?
445
481
  logDebug('clicking user next button.');
446
482
  await page.locator('#identifierNext').click();
package/src/main.ts CHANGED
@@ -141,6 +141,9 @@ export class GoogleSharedlocations2 extends utils.Adapter {
141
141
  await this.setState('info.connection', false, true);
142
142
  if (this._successFullPolls > 0) {
143
143
  //try to get new cookie:
144
+ this.log.debug(
145
+ `Polling failed, trying to obtain new cookies, because got ${this._successFullPolls} valid results before.`,
146
+ );
144
147
  this._successFullPolls = 0;
145
148
  await this.cookie.loginToGetNewCookies();
146
149
  }
@@ -365,7 +368,7 @@ export class GoogleSharedlocations2 extends utils.Adapter {
365
368
  this.log.info('Current cookies state was cleared, trying to obtain new cookies.');
366
369
  this._successFullPolls = 0;
367
370
  this.cookie.readCookieFromString(''); //clear old cookie
368
- await this.cookie.loginToGetNewCookies();
371
+ await this.cookie.loginToGetNewCookies(true);
369
372
  } else {
370
373
  this.log.info(
371
374
  'Current cookies state was changed from outside the adapter, updating internal cookie store.',