iobroker.google-sharedlocations2 0.1.1 → 0.3.0

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,16 @@ 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.0 (2026-02-09)
31
+ * (Garfonso) do not update states if no new position is available.
32
+ * (Garfonso) fixed: refresh via browser
33
+ * (Garfonso) added: force refresh via browser by setting "forceRefresh" state to true
34
+ * (Garfonso) changed: now store complete cookies array
35
+
36
+ ### 0.2.0 (2026-02-03)
37
+ * (Garfonso) now using data directory to store chrome data
38
+ * (Garfonso) try to use existing cookie in browser to refresh cookie without login.
39
+
30
40
  ### 0.1.1 (2026-02-02)
31
41
  * (Garfonso) improved recovery from login errors
32
42
 
@@ -1,5 +1,17 @@
1
1
  {
2
+ "Fences, will create boolean states for each fence to indicate if user is within the fence": "Fences erstellen boolesche Zustände für jeden Fence, um anzuzeigen, ob sich der Benutzer innerhalb des Bereichs befindet",
3
+ "Geofencing Settings": "Geofencing-Einstellungen",
4
+ "Google Settings": "Google-Einstellungen",
5
+ "ID of state": "ID des Staates",
6
+ "Instance of iobroker.places to use": "Zu verwendende Instanz von iobroker.places",
7
+ "Latitude": "Breitengrad",
8
+ "Longitude": "Längengrad",
9
+ "Name of fence": "Name des Bereichs",
10
+ "Password of ioBroker Google Account": "Passwort des ioBroker-Google-Kontos",
11
+ "Polling Interval (in seconds, at least 60 seconds)": "Abfrageintervall (in Sekunden, mindestens 60 Sekunden)",
12
+ "Radius (m)": "Radius (m)",
13
+ "User": "Benutzer",
14
+ "Username of ioBroker Google Account": "Benutzername des ioBroker-Google-Kontos",
2
15
  "google-sharedlocations2 adapter settings": "Adaptereinstellungen für google-sharedlocations2",
3
- "option1": "Option1",
4
- "option2": "Option2"
5
- }
16
+ "labelAccountHint": "Bitte geben Sie die Anmeldeinformationen eines ioBroker-Google-Kontos an, mit dem Sie Ihren Standort über die Google Maps-App teilen. Es wird dringend empfohlen, zu diesem Zweck ein dediziertes Google-Konto zu erstellen."
17
+ }
@@ -1,5 +1,17 @@
1
1
  {
2
2
  "google-sharedlocations2 adapter settings": "Adapter settings for google-sharedlocations2",
3
- "option1": "option1",
4
- "option2": "option2"
3
+ "Google Settings": "Google Settings",
4
+ "labelAccountHint": "Please provide the credentials of an ioBroker Google Account which you share your location with using the Google Maps App. It is strongly recommended to create a dedicated Google Account for this purpose.",
5
+ "Username of ioBroker Google Account": "Username of ioBroker Google Account",
6
+ "Password of ioBroker Google Account": "Password of ioBroker Google Account",
7
+ "Polling Interval (in seconds, at least 60 seconds)": "Polling Interval (in seconds, at least 60 seconds)",
8
+ "Geofencing Settings": "Geofencing Settings",
9
+ "Instance of iobroker.places to use": "Instance of iobroker.places to use",
10
+ "Fences, will create boolean states for each fence to indicate if user is within the fence": "Fences, will create boolean states for each fence to indicate if user is within the fence",
11
+ "Name of fence": "Name of fence",
12
+ "Latitude": "Latitude",
13
+ "Longitude": "Longitude",
14
+ "Radius (m)": "Radius (m)",
15
+ "User": "User",
16
+ "ID of state": "ID of state"
5
17
  }
@@ -1,5 +1,17 @@
1
1
  {
2
+ "Fences, will create boolean states for each fence to indicate if user is within the fence": "Cercas, creará estados booleanos para cada cerca para indicar si el usuario está dentro de la cerca.",
3
+ "Geofencing Settings": "Configuración de geocercas",
4
+ "Google Settings": "Configuración de Google",
5
+ "ID of state": "identificación del estado",
6
+ "Instance of iobroker.places to use": "Instancia de iobroker.lugares para usar",
7
+ "Latitude": "Latitud",
8
+ "Longitude": "Longitud",
9
+ "Name of fence": "nombre de la valla",
10
+ "Password of ioBroker Google Account": "Contraseña de la cuenta de Google de ioBroker",
11
+ "Polling Interval (in seconds, at least 60 seconds)": "Intervalo de sondeo (en segundos, al menos 60 segundos)",
12
+ "Radius (m)": "Radio (m)",
13
+ "User": "Usuario",
14
+ "Username of ioBroker Google Account": "Nombre de usuario de la cuenta de Google de ioBroker",
2
15
  "google-sharedlocations2 adapter settings": "Ajustes del adaptador para google-sharedlocations2",
3
- "option1": "opción1",
4
- "option2": "opción2"
5
- }
16
+ "labelAccountHint": "Proporcione las credenciales de una cuenta de Google de ioBroker con la que comparte su ubicación mediante la aplicación Google Maps. Se recomienda encarecidamente crear una cuenta de Google dedicada para este fin."
17
+ }
@@ -1,5 +1,17 @@
1
1
  {
2
+ "Fences, will create boolean states for each fence to indicate if user is within the fence": "Clôtures, créera des états booléens pour chaque clôture pour indiquer si l'utilisateur se trouve à l'intérieur de la clôture",
3
+ "Geofencing Settings": "Paramètres de géolocalisation",
4
+ "Google Settings": "Paramètres Google",
5
+ "ID of state": "ID de l'état",
6
+ "Instance of iobroker.places to use": "Instance de iobroker.places à utiliser",
7
+ "Latitude": "Latitude",
8
+ "Longitude": "Longitude",
9
+ "Name of fence": "Nom de la clôture",
10
+ "Password of ioBroker Google Account": "Mot de passe du compte Google ioBroker",
11
+ "Polling Interval (in seconds, at least 60 seconds)": "Intervalle d'interrogation (en secondes, au moins 60 secondes)",
12
+ "Radius (m)": "Rayon (m)",
13
+ "User": "Utilisateur",
14
+ "Username of ioBroker Google Account": "Nom d'utilisateur du compte Google ioBroker",
2
15
  "google-sharedlocations2 adapter settings": "Paramètres d'adaptateur pour google-sharedlocations2",
3
- "option1": "option1",
4
- "option2": "option2"
5
- }
16
+ "labelAccountHint": "Veuillez fournir les informations d'identification d'un compte Google ioBroker avec lequel vous partagez votre position à l'aide de l'application Google Maps. Il est fortement recommandé de créer un compte Google dédié à cet effet."
17
+ }
@@ -1,5 +1,17 @@
1
1
  {
2
+ "Fences, will create boolean states for each fence to indicate if user is within the fence": "Recinti: creerà stati booleani per ciascun recinto per indicare se l'utente si trova all'interno del recinto",
3
+ "Geofencing Settings": "Impostazioni del geofencing",
4
+ "Google Settings": "Impostazioni di Google",
5
+ "ID of state": "ID dello stato",
6
+ "Instance of iobroker.places to use": "Istanza di iobroker.places da utilizzare",
7
+ "Latitude": "Latitudine",
8
+ "Longitude": "Longitudine",
9
+ "Name of fence": "Nome della recinzione",
10
+ "Password of ioBroker Google Account": "Password dell'account Google ioBroker",
11
+ "Polling Interval (in seconds, at least 60 seconds)": "Intervallo di polling (in secondi, almeno 60 secondi)",
12
+ "Radius (m)": "Raggio (m)",
13
+ "User": "Utente",
14
+ "Username of ioBroker Google Account": "Nome utente dell'account Google ioBroker",
2
15
  "google-sharedlocations2 adapter settings": "Impostazioni dell'adattatore per google-sharedlocations2",
3
- "option1": "opzione1",
4
- "option2": "opzione2"
5
- }
16
+ "labelAccountHint": "Fornisci le credenziali di un account Google ioBroker con cui condividi la tua posizione utilizzando l'app Google Maps. Si consiglia vivamente di creare un account Google dedicato a questo scopo."
17
+ }
@@ -1,5 +1,17 @@
1
1
  {
2
+ "Fences, will create boolean states for each fence to indicate if user is within the fence": "Hekken, creëert booleaanse toestanden voor elk hek om aan te geven of de gebruiker zich binnen het hek bevindt",
3
+ "Geofencing Settings": "Geofencing-instellingen",
4
+ "Google Settings": "Google-instellingen",
5
+ "ID of state": "Identiteitskaart van de staat",
6
+ "Instance of iobroker.places to use": "Instantie van iobroker.places om te gebruiken",
7
+ "Latitude": "Breedte",
8
+ "Longitude": "Lengte",
9
+ "Name of fence": "Naam hek",
10
+ "Password of ioBroker Google Account": "Wachtwoord van ioBroker Google-account",
11
+ "Polling Interval (in seconds, at least 60 seconds)": "Polling-interval (in seconden, minimaal 60 seconden)",
12
+ "Radius (m)": "Straal (m)",
13
+ "User": "Gebruiker",
14
+ "Username of ioBroker Google Account": "Gebruikersnaam van ioBroker Google-account",
2
15
  "google-sharedlocations2 adapter settings": "Adapterinstellingen voor google-sharedlocations2",
3
- "option1": "optie1",
4
- "option2": "optie2"
5
- }
16
+ "labelAccountHint": "Geef de inloggegevens op van een ioBroker Google-account waarmee u uw locatie deelt via de Google Maps-app. Het wordt sterk aanbevolen om hiervoor een speciaal Google-account aan te maken."
17
+ }
@@ -1,5 +1,17 @@
1
1
  {
2
+ "Fences, will create boolean states for each fence to indicate if user is within the fence": "Ogrodzenia utworzy stany logiczne dla każdego ogrodzenia, aby wskazać, czy użytkownik znajduje się w obrębie ogrodzenia",
3
+ "Geofencing Settings": "Ustawienia geofencingu",
4
+ "Google Settings": "Ustawienia Google",
5
+ "ID of state": "Identyfikator stanu",
6
+ "Instance of iobroker.places to use": "Instancja iobroker.places do użycia",
7
+ "Latitude": "Szerokość",
8
+ "Longitude": "Długość geograficzna",
9
+ "Name of fence": "Nazwa ogrodzenia",
10
+ "Password of ioBroker Google Account": "Hasło do konta Google ioBroker",
11
+ "Polling Interval (in seconds, at least 60 seconds)": "Interwał odpytywania (w sekundach, co najmniej 60 sekund)",
12
+ "Radius (m)": "Promień (m)",
13
+ "User": "Użytkownik",
14
+ "Username of ioBroker Google Account": "Nazwa użytkownika konta Google ioBroker",
2
15
  "google-sharedlocations2 adapter settings": "Ustawienia adaptera dla google-sharedlocations2",
3
- "option1": "opcja 1",
4
- "option2": "opcja 2"
5
- }
16
+ "labelAccountHint": "Podaj dane uwierzytelniające konta Google ioBroker, któremu udostępniasz swoją lokalizację za pomocą aplikacji Google Maps. Zdecydowanie zaleca się utworzenie w tym celu dedykowanego konta Google."
17
+ }
@@ -1,5 +1,17 @@
1
1
  {
2
+ "Fences, will create boolean states for each fence to indicate if user is within the fence": "Cercas, criará estados booleanos para cada cerca para indicar se o usuário está dentro da cerca",
3
+ "Geofencing Settings": "Configurações de cerca geográfica",
4
+ "Google Settings": "Configurações do Google",
5
+ "ID of state": "ID do estado",
6
+ "Instance of iobroker.places to use": "Instância de iobroker.places para usar",
7
+ "Latitude": "Latitude",
8
+ "Longitude": "Longitude",
9
+ "Name of fence": "Nome da cerca",
10
+ "Password of ioBroker Google Account": "Senha da conta do Google ioBroker",
11
+ "Polling Interval (in seconds, at least 60 seconds)": "Intervalo de pesquisa (em segundos, pelo menos 60 segundos)",
12
+ "Radius (m)": "Raio (m)",
13
+ "User": "Usuário",
14
+ "Username of ioBroker Google Account": "Nome de usuário da conta do Google ioBroker",
2
15
  "google-sharedlocations2 adapter settings": "Configurações do adaptador para google-sharedlocations2",
3
- "option1": "opção1",
4
- "option2": "opção2"
5
- }
16
+ "labelAccountHint": "Forneça as credenciais de uma conta do Google ioBroker com a qual você compartilha sua localização usando o aplicativo Google Maps. É altamente recomendável criar uma Conta do Google dedicada para essa finalidade."
17
+ }
@@ -1,5 +1,17 @@
1
1
  {
2
+ "Fences, will create boolean states for each fence to indicate if user is within the fence": "Заборы: будут создавать логические состояния для каждого забора, чтобы указать, находится ли пользователь внутри забора.",
3
+ "Geofencing Settings": "Настройки геозон",
4
+ "Google Settings": "Настройки Google",
5
+ "ID of state": "идентификатор штата",
6
+ "Instance of iobroker.places to use": "Экземпляр iobroker.places для использования",
7
+ "Latitude": "Широта",
8
+ "Longitude": "Долгота",
9
+ "Name of fence": "Название забора",
10
+ "Password of ioBroker Google Account": "Пароль учетной записи Google ioBroker",
11
+ "Polling Interval (in seconds, at least 60 seconds)": "Интервал опроса (в секундах, не менее 60 секунд)",
12
+ "Radius (m)": "Радиус (м)",
13
+ "User": "Пользователь",
14
+ "Username of ioBroker Google Account": "Имя пользователя учетной записи Google ioBroker",
2
15
  "google-sharedlocations2 adapter settings": "Настройки адаптера для google-sharedlocations2",
3
- "option1": "вариант 1",
4
- "option2": "вариант 2"
5
- }
16
+ "labelAccountHint": "Укажите учетные данные учетной записи Google ioBroker, с которой вы делитесь своим местоположением с помощью приложения Google Maps. Для этой цели настоятельно рекомендуется создать специальную учетную запись Google."
17
+ }
@@ -1,5 +1,17 @@
1
1
  {
2
+ "Fences, will create boolean states for each fence to indicate if user is within the fence": "Fences створить логічні стани для кожної огорожі, щоб вказати, чи перебуває користувач у межах огорожі",
3
+ "Geofencing Settings": "Налаштування геозон",
4
+ "Google Settings": "Налаштування Google",
5
+ "ID of state": "ID держ",
6
+ "Instance of iobroker.places to use": "Примірник iobroker.places для використання",
7
+ "Latitude": "Широта",
8
+ "Longitude": "Довгота",
9
+ "Name of fence": "Назва огорожі",
10
+ "Password of ioBroker Google Account": "Пароль облікового запису Google ioBroker",
11
+ "Polling Interval (in seconds, at least 60 seconds)": "Інтервал опитування (у секундах, не менше 60 секунд)",
12
+ "Radius (m)": "Радіус (м)",
13
+ "User": "Користувач",
14
+ "Username of ioBroker Google Account": "Ім'я користувача облікового запису Google ioBroker",
2
15
  "google-sharedlocations2 adapter settings": "Налаштування адаптера для google-sharedlocations2",
3
- "option1": "варіант1",
4
- "option2": "варіант2"
5
- }
16
+ "labelAccountHint": "Надайте облікові дані облікового запису Google ioBroker, якому ви надаєте доступ до свого місцезнаходження за допомогою програми Google Maps. Для цього наполегливо рекомендується створити спеціальний обліковий запис Google."
17
+ }
@@ -1,5 +1,17 @@
1
1
  {
2
+ "Fences, will create boolean states for each fence to indicate if user is within the fence": "栅栏,将为每个栅栏创建布尔状态以指示用户是否在栅栏内",
3
+ "Geofencing Settings": "地理围栏设置",
4
+ "Google Settings": "谷歌设置",
5
+ "ID of state": "州ID",
6
+ "Instance of iobroker.places to use": "要使用的 iobroker.places 实例",
7
+ "Latitude": "纬度",
8
+ "Longitude": "经度",
9
+ "Name of fence": "围栏名称",
10
+ "Password of ioBroker Google Account": "ioBroker Google 账户密码",
11
+ "Polling Interval (in seconds, at least 60 seconds)": "轮询间隔(以秒为单位,至少 60 秒)",
12
+ "Radius (m)": "半径(米)",
13
+ "User": "用户",
14
+ "Username of ioBroker Google Account": "ioBroker Google 帐户的用户名",
2
15
  "google-sharedlocations2 adapter settings": "google-sharedlocations2的适配器设置",
3
- "option1": "选项1",
4
- "option2": "选项2"
5
- }
16
+ "labelAccountHint": "请提供您使用 Google 地图应用程序共享您的位置的 ioBroker Google 帐户的凭据。强烈建议为此目的创建一个专用的 Google 帐户。"
17
+ }
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "i18n": true,
3
3
  "type": "tabs",
4
- "items": [
5
- {
4
+ "items": {
5
+ "_AccountSettings": {
6
6
  "type": "panel",
7
7
  "label": "Google Settings",
8
8
  "items": {
9
9
  "_explanation": {
10
10
  "type": "staticText",
11
- "label": "Please provide the credentials of an ioBroker Google Account which you share your location with using the Google Maps App. It is strongly recommended to create a dedicated Google Account for this purpose.",
11
+ "label": "labelAccountHint",
12
12
  "newLine": true,
13
13
  "xs": 12,
14
14
  "sm": 12,
@@ -53,13 +53,13 @@
53
53
  }
54
54
  }
55
55
  },
56
- {
56
+ "_GeofencingSettings": {
57
57
  "type": "panel",
58
58
  "label": "Geofencing Settings",
59
59
  "items": {
60
60
  "placesInstance": {
61
61
  "type": "instance",
62
- "label": "Instance of iobroker.places to use (set to -1 to auto-detect)",
62
+ "label": "Instance of iobroker.places to use",
63
63
  "adapter": "places",
64
64
  "allowDeactivate": true,
65
65
  "newLine": true,
@@ -112,24 +112,19 @@
112
112
  "command": "getUsers",
113
113
  "attr": "user",
114
114
  "multiple": false,
115
- "title": "Users",
115
+ "title": "User",
116
116
  "width": "20%",
117
117
  "default": ""
118
118
  },
119
119
  {
120
120
  "type": "text",
121
- "title": "Fence ID",
121
+ "title": "ID of state",
122
122
  "width": "15%",
123
- "attr": "fenceId",
124
- "defaultFunc": "data.name + data.user",
125
- "alsoDependsOn": [
126
- "name",
127
- "user"
128
- ]
123
+ "attr": "fenceId"
129
124
  }
130
125
  ]
131
126
  }
132
127
  }
133
128
  }
134
- ]
129
+ }
135
130
  }
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "google-sharedlocations2",
4
- "version": "0.1.1",
4
+ "version": "0.3.0",
5
5
  "news": {
6
+ "0.3.0": {
7
+ "en": "do not update states if no new position is available.\nfixed: refresh via browser\nadded: force refresh via browser by setting \"forceRefresh\" state to true\nchanged: now store complete cookies array",
8
+ "de": "aktualisiere zustände nicht, wenn keine neue position verfügbar ist.\nrepariert: cookie refresh über browser\nneu: Refresh über Browser durch \"forceRefresh\" Zustand auf true setzen erzwingen\ngeändert: jetzt komplette cookies-array speichern",
9
+ "ru": "не обновляйте состояния, если нет новой позиции.\nобновление через браузер\nдобавлено: принудительное обновление через браузер, настроив состояние «forceRefresh» на истинное\nизменено: теперь храните полный массив cookie",
10
+ "pt": "não atualizar estados se nenhuma nova posição estiver disponível.\ncorrigido: atualizar via navegador\nadded: forçar a atualização via navegador definindo o estado \"forceRefresh\" para true\nalterado: agora armazenar o array completo de cookies",
11
+ "nl": "niet bijwerken indien er geen nieuwe positie beschikbaar is.\nvast: vernieuwen via browser\ntoegevoegd: force refresh via browser door \"forceRefresh\" in te stellen\ngewijzigd: nu volledige cookies-array opslaan",
12
+ "fr": "ne mettez pas à jour les états si aucun nouveau poste n'est disponible.\ncorrigé: rafraîchir via le navigateur\najouté: force rafraîchir via le navigateur en définissant l'état \"forceRefresh\" à true\nmodifié : stockez maintenant le tableau complet des cookies",
13
+ "it": "non aggiornare gli stati se non è disponibile nessuna nuova posizione.\nfisso: rinfrescare tramite browser\naggiunto: forza rinfresco tramite browser impostando lo stato \"forceRefresh\" a true\ncambiato: ora memorizzare l'array completo dei cookie",
14
+ "es": "no actualizar estados si no hay nueva posición disponible.\nfijo: refrescante a través del navegador\nañadido: actualización de fuerza a través del navegador mediante el establecimiento del estado \"forceRefresh\" a la verdad\ncambiado: ahora almacena la gama completa de cookies",
15
+ "pl": "nie aktualizować stanów, jeśli nie jest dostępna nowa pozycja.\nstałe: odświeżenie przez przeglądarkę\ndodane: siła odświeżania przez przeglądarkę poprzez ustawienie stanu \"forceRefresh\" na true\nzmienione: teraz przechowywać pełną tablicę cookies",
16
+ "uk": "не оновлювати стани, якщо немає нової позиції.\nвиправлено: оновлення через браузер\nдодано: сила освіження через браузер за допомогою налаштування \"forceRefresh\" стану true\nзмінено: тепер зберігає повний масив файлів cookie",
17
+ "zh-cn": "如果没有新职位,则不更新状态.\n固定: 通过浏览器刷新\n添加: 通过浏览器将“ ForceRefresh” 状态设置为真以强制刷新\n更改: 现在存储完整的 cookie 阵列"
18
+ },
19
+ "0.2.0": {
20
+ "en": "now using data directory to store chrome data\ntry to use existing cookie in browser to refresh cookie without login.",
21
+ "de": "jetzt mit data directory, um chromdaten zu speichern\nversuchen, existierenden cookie im browser zu verwenden, um cookie ohne anmeldung zu aktualisieren.",
22
+ "ru": "теперь используя директор данных для хранения хромированных данных\nпопробуйте использовать существующий cookie в браузере, чтобы обновить cookie без входа в систему.",
23
+ "pt": "agora usando o diretor de dados para armazenar dados de cromo\ntentar usar cookie existente no navegador para atualizar cookie sem login.",
24
+ "nl": "nu met behulp van data director om chroom gegevens op te slaan\nprobeer bestaande cookie in de browser te gebruiken om cookie te vernieuwen zonder in te loggen.",
25
+ "fr": "maintenant en utilisant le directeur de données pour stocker des données chrome\nessayez d'utiliser le cookie existentint dans le navigateur pour rafraîchir le cookie sans vous connecter.",
26
+ "it": "ora utilizzando il data director per memorizzare i dati cromati\nprovare a utilizzare cookie esisteint nel browser per aggiornare i cookie senza effettuare il login.",
27
+ "es": "ahora utilizando el director de datos para almacenar datos de cromo\ntratar de utilizar la cookie existente en el navegador para refrescar la cookie sin login.",
28
+ "pl": "teraz za pomocą dyrektora danych do przechowywania danych chromowych\nspróbuj użyć plików cookie existint w przeglądarce, aby odświeżyć pliki cookie bez logowania.",
29
+ "uk": "тепер використовуючи каталог даних для зберігання хромованих даних\nспробуйте використовувати наявний cookie-файли в браузері, щоб оновити cookie без реєстрації.",
30
+ "zh-cn": "现在使用数据总监存储铬数据\n尝试在浏览器中使用存在 cookie来刷新 cookie而不登录."
31
+ },
6
32
  "0.1.1": {
7
33
  "en": "improved recovery from login errors",
8
34
  "de": "verbesserte wiederherstellung von login-fehlern",
@@ -218,14 +244,40 @@
218
244
  "type": "state",
219
245
  "common": {
220
246
  "role": "state",
221
- "name": "Current authentication cookies",
247
+ "name": "Current authentication cookies (write only)",
222
248
  "desc": "Overwrite to set cookies manually. Set empty to login again.",
223
249
  "type": "string",
224
- "read": true,
250
+ "read": false,
225
251
  "write": true,
226
252
  "def": ""
227
253
  }
228
254
  },
255
+ {
256
+ "_id": "info.cookieStore",
257
+ "type": "state",
258
+ "common": {
259
+ "role": "state",
260
+ "name": "JSON cookie store with all data",
261
+ "desc": "please don't mess up",
262
+ "type": "object",
263
+ "read": true,
264
+ "write": false,
265
+ "def": "[]"
266
+ }
267
+ },
268
+ {
269
+ "_id": "info.forceRefreshWithBrowser",
270
+ "type": "state",
271
+ "common": {
272
+ "role": "button",
273
+ "name": "Start Browser to refresh cookies",
274
+ "desc": "Set to true to start the browser and refresh cookies using existing login in browser. Will reset to false automatically after starting the browser.",
275
+ "type": "boolean",
276
+ "read": false,
277
+ "write": true,
278
+ "def": false
279
+ }
280
+ },
229
281
  {
230
282
  "_id": "fences",
231
283
  "type": "folder",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.google-sharedlocations2",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Share your location with iobroker via google maps.",
5
5
  "author": {
6
6
  "name": "Garfonso",
package/src/lib/Cookie.ts CHANGED
@@ -1,30 +1,33 @@
1
1
  import axios from 'axios';
2
2
  import type { GoogleSharedlocations2 } from '../main';
3
3
  import puppeteer from 'puppeteer';
4
- import type { Browser } from 'puppeteer';
4
+ import type { Browser, Page, CookieData } from 'puppeteer';
5
+ import { mkdir } from 'fs/promises';
5
6
 
6
7
  /**
7
8
  * Helper class to manage Google cookies.
8
9
  */
9
10
  export class Cookie {
10
- currentCookie: string;
11
+ cookies: CookieData[] = [];
11
12
  username?: string;
12
13
  password?: string;
13
14
  adapter: GoogleSharedlocations2;
14
15
  log;
15
16
  private browser: Browser | null = null;
17
+ dataDir: string;
16
18
 
17
19
  /**
18
20
  * Construct cookie helper
19
21
  *
20
22
  * @param adapter - adapter instance
23
+ * @param dataDir - data directory of the instance to store browser data to.
21
24
  */
22
- constructor(adapter: GoogleSharedlocations2) {
23
- this.currentCookie = '';
25
+ constructor(adapter: GoogleSharedlocations2, dataDir: string) {
24
26
  this.username = '';
25
27
  this.password = '';
26
28
  this.adapter = adapter;
27
29
  this.log = adapter.log;
30
+ this.dataDir = dataDir;
28
31
  }
29
32
 
30
33
  /**
@@ -33,16 +36,36 @@ export class Cookie {
33
36
  async init(): Promise<void> {
34
37
  this.username = this.adapter.config.googleUsername;
35
38
  this.password = this.adapter.config.googlePassword;
39
+ this.log = this.adapter.log; // does not exist during construction...
36
40
  try {
37
- const state = await this.adapter.getStateAsync('info.currentCookies');
41
+ //ensure data dir exists
42
+ await mkdir(this.dataDir, { recursive: true }); //recursive true should prevent error if already exists.
43
+ const state = await this.adapter.getStateAsync('info.cookieStore');
44
+ const stringState = await this.adapter.getStateAsync('info.currentCookies');
38
45
  if (state && state.val && typeof state.val === 'string') {
39
- this.currentCookie = state.val;
40
- this.log?.debug('Loaded cookie from state.');
41
- } else {
42
- this.currentCookie = '';
43
- this.log?.debug('No cookie found in state, trying to log in to get new one.');
44
- await this.loginToGetNewCookies();
46
+ try {
47
+ this.cookies = JSON.parse(state.val);
48
+ this.log?.debug(`Loaded ${this.cookies.length} cookies from state.`);
49
+ if (this.isValid()) {
50
+ return;
51
+ }
52
+ } catch (e) {
53
+ this.log?.error(
54
+ `Error parsing cookies from state: ${(e as Error).message}, using string as cookie.`,
55
+ );
56
+ }
57
+ }
58
+
59
+ if (stringState && stringState.val && typeof stringState.val === 'string') {
60
+ this.log?.debug('Loaded cookie string from state, trying to convert to new format.');
61
+ this.readCookieFromString(stringState.val);
62
+ if (this.isValid()) {
63
+ return;
64
+ }
45
65
  }
66
+
67
+ this.log?.debug('No cookie found in states, trying to log in to get new one.');
68
+ await this.loginToGetNewCookies();
46
69
  } catch (err: any) {
47
70
  this.log?.error(`Error loading cookie from state: ${err}`);
48
71
  }
@@ -53,12 +76,35 @@ export class Cookie {
53
76
  */
54
77
  async storeCookie(): Promise<void> {
55
78
  try {
56
- await this.adapter.setStateAsync('info.currentCookies', this.currentCookie, true);
79
+ await this.adapter.setState('info.cookieStore', JSON.stringify(this.cookies), true);
57
80
  } catch (err: any) {
58
81
  this.log?.error(`Error storing cookie: ${err}`);
59
82
  }
60
83
  }
61
84
 
85
+ /**
86
+ * Read cookie from a string in the format "name=value; name2=value2" and store it in the cookies array.
87
+ *
88
+ * @param cookieString - cookie string to read from
89
+ */
90
+ readCookieFromString(cookieString: string): void {
91
+ this.cookies = cookieString
92
+ .split(';')
93
+ .map(pair => {
94
+ const parts = pair.trim().split('=');
95
+ return parts.length >= 2
96
+ ? {
97
+ name: parts[0].trim(),
98
+ value: parts.slice(1).join('=').trim(),
99
+ domain: '.google.com',
100
+ path: '/',
101
+ secure: true,
102
+ }
103
+ : null;
104
+ })
105
+ .filter(c => c !== null);
106
+ }
107
+
62
108
  /**
63
109
  * Augment the current cookie with data from the 'set-cookie' header.
64
110
  *
@@ -67,25 +113,64 @@ export class Cookie {
67
113
  async augmentCookieFromHeader(headers: Record<string, any>): Promise<void> {
68
114
  if (headers['set-cookie'] && headers['set-cookie'].length) {
69
115
  this.log?.debug('New header received.');
70
- const oldLength = this.currentCookie.length;
71
- const cookies = this.currentCookie.split('; ').map(c => c.split('='));
116
+ const oldLength = this.cookies.length;
72
117
 
73
118
  //split old cookie and new cookie. Update single values.
74
119
  for (const header of headers['set-cookie']) {
75
- const incomingCookies = header.split('; ');
76
- for (const cookie of incomingCookies) {
77
- const [name, value] = cookie.split('=');
78
- const cIndex = cookies.findIndex(c => c[0] === name);
79
- if (cIndex < 0) {
80
- cookies.push([name, value]); //add
81
- } else {
82
- cookies[cIndex][1] = value; //update
120
+ //console.log('Processing header cookie:', header);
121
+ const keyValues = header.split('; ');
122
+ const [name, value] = keyValues.shift().split('='); // first part is cookie, rest are attributes like path, secure etc.
123
+ const cookie = {
124
+ name: name.trim(),
125
+ value: value.trim(),
126
+ domain: '.google.com',
127
+ } as CookieData;
128
+ for (const kv of keyValues) {
129
+ const [k, v] = kv.split('=');
130
+ switch (k.toLowerCase()) {
131
+ case 'domain':
132
+ cookie.domain = v ? v.trim() : '.google.com';
133
+ break;
134
+ case 'path':
135
+ cookie.path = v ? v.trim() : '/';
136
+ break;
137
+ case 'secure':
138
+ cookie.secure = true;
139
+ break;
140
+ case 'httponly':
141
+ cookie.httpOnly = true;
142
+ break;
143
+ case 'samesite':
144
+ cookie.sameSite = v ? v.trim() : 'Lax';
145
+ break;
146
+ case 'expires':
147
+ cookie.expires = new Date(v).getTime() / 1000; //puppeteer expects expires in seconds, not milliseconds
148
+ break;
149
+ case 'priority':
150
+ cookie.priority = v ? v.trim() : 'Medium';
151
+ break;
152
+ default:
153
+ this.log.debug(`Unknown cookie attribute: ${k}=${v}`);
83
154
  }
84
155
  }
156
+ const cIndex = this.cookies.findIndex(c => c.name === name);
157
+ if (cIndex < 0) {
158
+ this.log.debug(`Adding new cookie from header: ${cookie.name}=${cookie.value}`);
159
+ this.cookies.push(cookie); //add
160
+ } else {
161
+ this.log.debug(`Updating cookie from header: ${cookie.name}=${cookie.value}`);
162
+ this.cookies[cIndex] = cookie; //update
163
+ }
85
164
  }
86
165
 
87
- this.currentCookie = cookies.map(cv => cv.join('=')).join('; ');
88
- this.log?.debug(`Cookie updated. Length: ${oldLength} -> ${this.currentCookie.length}`);
166
+ this.cookies
167
+ .filter(c => c.expires && c.expires < Date.now() / 1000)
168
+ .forEach(c =>
169
+ this.log.debug(`Cookie ${c.name} expired at ${new Date(c.expires! * 1000).toISOString()}`),
170
+ );
171
+ this.cookies = this.cookies.filter(c => !c.expires || c.expires > Date.now() / 1000); //remove expired cookies
172
+
173
+ this.log?.debug(`Cookie updated. Length: ${oldLength} -> ${this.cookies.length}`);
89
174
  return this.storeCookie();
90
175
  }
91
176
  }
@@ -93,12 +178,12 @@ export class Cookie {
93
178
  /**
94
179
  * Improve the current cookie by making a request to Google My Account page.
95
180
  */
96
- async improveCookie(): Promise<void> {
181
+ async improveCookie(): Promise<boolean> {
97
182
  //see https://github.com/costastf/locationsharinglib/blob/master/locationsharinglib/locationsharinglib.py#L105
98
183
  const options = {
99
184
  url: 'https://myaccount.google.com/?hl=en',
100
185
  headers: {
101
- Cookie: this.currentCookie,
186
+ Cookie: this.cookies.map(c => `${c.name}=${c.value}`).join('; '),
102
187
  },
103
188
  method: 'get',
104
189
  };
@@ -108,12 +193,159 @@ export class Cookie {
108
193
 
109
194
  if (response.status !== 200) {
110
195
  this.log?.error(`Failed improving cookie: ${response.status}`);
111
- } else {
112
- await this.augmentCookieFromHeader(response.headers);
196
+ return false;
113
197
  }
198
+ await this.augmentCookieFromHeader(response.headers);
199
+ return true;
114
200
  } catch (err: any) {
115
201
  this.log?.error(err);
116
202
  this.log?.info('Connection to google maps failure.');
203
+ return false;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Start a puppeteer browser instance and return a new page. Sets up user agent and hides automation flag.
209
+ *
210
+ * @returns puppeteer page or undefined if browser could not be started
211
+ */
212
+ private async startBrowser(): Promise<Page | undefined> {
213
+ if (this.browser) {
214
+ this.log.info('Seems we are already trying to log in. Aborting new login attempt.');
215
+ return;
216
+ }
217
+ this.log.debug('Starting browser.');
218
+ this.browser = await puppeteer.launch({
219
+ headless: true,
220
+ args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-blink-features=AutomationControlled'],
221
+ ignoreDefaultArgs: ['--enable-automation'], //h// ide automation flag, did not help.
222
+ userDataDir: this.dataDir,
223
+ });
224
+ this.log.debug('browser started, opening new page.');
225
+ const page = await this.browser.newPage();
226
+ //hide puppeteer automation flag
227
+ await page.evaluateOnNewDocument(() => {
228
+ Object.defineProperty(navigator, 'webdriver', { get: () => false });
229
+ });
230
+ await page.setUserAgent({
231
+ userAgent:
232
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
233
+ });
234
+
235
+ return page;
236
+ }
237
+
238
+ /**
239
+ * Request location Data from Google Maps
240
+ *
241
+ * @returns Array of location data or undefined if request failed
242
+ */
243
+ async sendRequest(): Promise<Array<any> | undefined> {
244
+ if (!this.isValid()) {
245
+ this.log.error('Cannot send request, no cookies available!');
246
+ return;
247
+ }
248
+
249
+ //send request with current cookies
250
+ this.log.debug('Sending request with current cookies');
251
+ const options = {
252
+ method: 'GET',
253
+ url: 'https://www.google.com/maps/rpc/locationsharing/read',
254
+ headers: {
255
+ Cookie: this.cookies.map(c => `${c.name}=${c.value}`).join('; '),
256
+ },
257
+ params: {
258
+ authuser: 2,
259
+ hl: 'en',
260
+ gl: 'us',
261
+ //pb is place on map. Is irrelevant, set to google head quarters here.
262
+ 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',
263
+ },
264
+ };
265
+
266
+ try {
267
+ const response = await axios.request(options);
268
+ this.log.debug(`Request successful, response code: ${response.status}`);
269
+ const data = response.data.split('\n').slice(1).join('\n');
270
+ const locationData = JSON.parse(data);
271
+ const locations = locationData[0];
272
+ if (locations && locations.length > 0) {
273
+ await this.augmentCookieFromHeader(response.headers);
274
+ return locations;
275
+ }
276
+ this.log.info('No shared locations found in the response, probably not logged in.');
277
+ } catch (e) {
278
+ this.log.error(`Error during request: ${(e as Error).message}`);
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Get cookies from the given page and store them. Also closes Browser.
284
+ *
285
+ * @param page - puppeteer page
286
+ */
287
+ private async getCookiesFromPage(page: Page): Promise<void> {
288
+ //using deprecated function, but browser.cookies just does not work...???
289
+ const cookies = await page.cookies();
290
+ const browserCookies = await this.browser!.cookies();
291
+ this.log.debug(`Got ${cookies.length} cookies from page, ${browserCookies.length} from browser.cookies().`);
292
+
293
+ this.cookies = cookies.filter(c => c.domain.includes('google')); //only keep google cookies, maybe some other cookies are set during login which we do not want to store.
294
+ await this.browser!.close();
295
+ if (this.isValid()) {
296
+ this.log.warn('Cookie string seems too short, login probably failed!');
297
+ } else {
298
+ this.log.info(`Obtained new cookies from Google login with length ${this.cookies.length}.`);
299
+ this.cookies = cookies;
300
+ await this.storeCookie();
301
+ }
302
+ this.browser = null;
303
+ }
304
+
305
+ /**
306
+ * Refresh the current cookie by using puppeteer to load Google Maps with existing cookie.
307
+ *
308
+ * @returns true if refresh was successful
309
+ */
310
+ async refreshCookieWithBrowser(): Promise<boolean> {
311
+ if (this.browser) {
312
+ this.log.info('Seems we are already trying to log in. Aborting new login attempt.');
313
+ return false;
314
+ }
315
+
316
+ this.log.debug('Trying to refresh cookie by loading Google Maps with existing cookie in Browser.');
317
+ const page = await this.startBrowser();
318
+ if (!page) {
319
+ this.log.error('Could not start browser for cookie refresh.');
320
+ return false;
321
+ }
322
+
323
+ const cookieArray = [...this.cookies];
324
+ // somehow we stored wrong cookies... :-/ Try to clean up here.
325
+ while (cookieArray.length > 0) {
326
+ try {
327
+ //await page.setCookie(...cookieArray);
328
+ await this.browser!.setCookie(...cookieArray);
329
+ break;
330
+ } catch (e) {
331
+ this.log.error(`Error setting cookies in browser: ${(e as Error).message}, trying again...`);
332
+ const cookie = cookieArray.pop(); //remove last cookie and try again, maybe some cookies are not valid for puppeteer or something.
333
+ console.log('Removed cookie:', cookie);
334
+ }
335
+ }
336
+
337
+ try {
338
+ await page.goto('https://www.google.com/maps', { waitUntil: 'networkidle2', timeout: 60000 });
339
+ await new Promise(r => setTimeout(r, 5000));
340
+
341
+ await this.sendRequest();
342
+ await this.getCookiesFromPage(page);
343
+ return true;
344
+ } catch (e) {
345
+ this.log.error(
346
+ `Error during cookie refresh: ${(e as Error).message}, ${e instanceof Error ? e.stack : ''}`,
347
+ );
348
+ return false;
117
349
  }
118
350
  }
119
351
 
@@ -122,6 +354,15 @@ export class Cookie {
122
354
  */
123
355
  async loginToGetNewCookies(): Promise<boolean> {
124
356
  try {
357
+ if (this.isValid()) {
358
+ this.log.info('Current cookie seems valid, trying refresh.');
359
+ await this.refreshCookieWithBrowser();
360
+ if (this.isValid()) {
361
+ this.log.info('Cookie refresh successful, no need to login again.');
362
+ return true;
363
+ }
364
+ }
365
+
125
366
  if (this.browser) {
126
367
  this.log.info('Seems we are already trying to log in. Aborting new login attempt.');
127
368
  return false;
@@ -133,23 +374,11 @@ export class Cookie {
133
374
 
134
375
  this.log.info('Trying to login to Google to get new cookies.');
135
376
  //testing puppeteer:
136
- this.log.debug('Starting browser.');
137
- this.browser = await puppeteer.launch({
138
- headless: true,
139
- args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-blink-features=AutomationControlled'],
140
- ignoreDefaultArgs: ['--enable-automation'], //hide automation flag, did not help.
141
- });
142
- this.log.debug('browser started, opening new page.');
143
- const page = await this.browser.newPage();
144
-
145
- //hide puppeteer automation flag
146
- await page.evaluateOnNewDocument(() => {
147
- Object.defineProperty(navigator, 'webdriver', { get: () => false });
148
- });
149
- await page.setUserAgent({
150
- userAgent:
151
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
152
- });
377
+ const page = await this.startBrowser();
378
+ if (!page) {
379
+ this.log.error('Could not start browser for login.');
380
+ return false;
381
+ }
153
382
 
154
383
  this.log.debug('going to google login page.');
155
384
  await page.goto(
@@ -177,23 +406,7 @@ export class Cookie {
177
406
 
178
407
  await page.goto('https://www.google.com/maps');
179
408
  this.log.debug('getting cookies.');
180
- //using deprecated function, but browser.cookies just does not work...???
181
- const cookies = await page.cookies();
182
-
183
- this.currentCookie = cookies
184
- .filter(c => c.domain.includes('google'))
185
- .map(c => `${c.name}=${c.value}`)
186
- .join('; ');
187
- //this.log.debug(this._cookies);
188
- //console.log(this._cookies);
189
- await this.browser.close();
190
- if (this.currentCookie.length < 50) {
191
- this.log.warn('Cookie string seems too short, login probably failed!');
192
- } else {
193
- this.log.info('Obtained new cookies from Google login.');
194
- await this.storeCookie();
195
- }
196
- this.browser = null;
409
+ await this.getCookiesFromPage(page);
197
410
  return true;
198
411
  } catch (e) {
199
412
  this.log.error(`Error in puppeteer: ${(e as Error).message}`);
@@ -224,6 +437,8 @@ export class Cookie {
224
437
  * Check if the current cookie is valid.
225
438
  */
226
439
  isValid(): boolean {
227
- return this.currentCookie.length > 50;
440
+ // we can not really check if cookie is valid without sending a request, but if the cookie string is very short, it is probably not valid.
441
+ // maybe change that to some check against the array length in future?
442
+ return this.cookies.map(c => `${c.name}=${c.value}`).join('; ').length > 50;
228
443
  }
229
444
  }
package/src/main.ts CHANGED
@@ -9,7 +9,6 @@ import * as utils from '@iobroker/adapter-core';
9
9
  import { User } from './lib/User';
10
10
  import { Fence } from './lib/Fence';
11
11
  import { Cookie } from './lib/Cookie';
12
- import axios from 'axios';
13
12
 
14
13
  //used to test timeout against
15
14
  const MAX_INT32 = 2 ** 31 - 1; // 2147483647 (hex 0x7FFFFFFF)
@@ -43,7 +42,7 @@ export class GoogleSharedlocations2 extends utils.Adapter {
43
42
  // this.on('objectChange', this.onObjectChange.bind(this));
44
43
  this.on('message', this.onMessage.bind(this));
45
44
  this.on('unload', this.onUnload.bind(this));
46
- this.cookie = new Cookie(this);
45
+ this.cookie = new Cookie(this, utils.getAbsoluteInstanceDataDir(this));
47
46
  }
48
47
 
49
48
  /**
@@ -55,7 +54,7 @@ export class GoogleSharedlocations2 extends utils.Adapter {
55
54
  // Reset the connection indicator during startup
56
55
  await this.setState('info.connection', false, true);
57
56
  await this.cookie.init();
58
- await this.subscribeStatesAsync('info.currentCookies');
57
+ await this.subscribeStatesAsync('info.*');
59
58
 
60
59
  //sanitize polling interval:
61
60
  this._pollInterval = this.config.pollInterval;
@@ -137,62 +136,31 @@ export class GoogleSharedlocations2 extends utils.Adapter {
137
136
  }
138
137
 
139
138
  private async sendRequest(): Promise<void> {
140
- if (!this.cookie.isValid()) {
141
- this.log.error('Cannot send request, no cookies available!');
139
+ const results = await this.cookie.sendRequest();
140
+ if (!results) {
142
141
  await this.setState('info.connection', false, true);
143
- return;
144
- }
145
-
146
- //send request with current cookies
147
- this.log.debug('Sending request with current cookies');
148
- const options = {
149
- method: 'GET',
150
- url: 'https://www.google.com/maps/rpc/locationsharing/read',
151
- headers: {
152
- Cookie: this.cookie.currentCookie,
153
- },
154
- params: {
155
- authuser: 2,
156
- hl: 'en',
157
- gl: 'us',
158
- //pb is place on map. Is irrelevant, set to google head quarters here.
159
- 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',
160
- },
161
- };
162
-
163
- try {
164
- const response = await axios.request(options);
165
- this.log.debug(`Request successful, response code: ${response.status}`);
166
- const data = response.data.split('\n').slice(1).join('\n');
167
- const locationData = JSON.parse(data);
168
- const locations = locationData[0];
169
- if (locations && locations.length > 0) {
170
- this._successFullPolls += 1;
171
- await this.setState('info.connection', true, true);
172
- for (const location of locations) {
173
- const user = new User(location);
174
- if (user.id) {
175
- this._users[user.id] = user;
176
- await this.fillIntoObjects(user);
177
- await this.notifyPlaces(user);
178
- await this.checkFences(user);
179
- }
180
- }
181
- } else {
182
- this.log.info('No shared locations found in the response, probably not logged in.');
183
- if (this._successFullPolls > 0) {
184
- //try to get new cookie:
185
- this._successFullPolls = 0;
186
- await this.cookie.loginToGetNewCookies();
187
- }
188
- }
189
- } catch (e) {
190
- this.log.error(`Error during request: ${(e as Error).message}`);
191
142
  if (this._successFullPolls > 0) {
192
143
  //try to get new cookie:
193
144
  this._successFullPolls = 0;
194
145
  await this.cookie.loginToGetNewCookies();
195
146
  }
147
+ } else {
148
+ this._successFullPolls += 1;
149
+ await this.setState('info.connection', true, true);
150
+ for (const location of results) {
151
+ const user = new User(location);
152
+ if (user.id) {
153
+ const oldTS = this._users[user.id]?.timestamp || 0;
154
+ this._users[user.id] = user; // should I try to merge stuff here? Or is it always completely filled?
155
+ if (user.timestamp && user.timestamp <= oldTS) {
156
+ this.log.debug(`Ignoring older or same location data for user ${user.id}`);
157
+ continue;
158
+ }
159
+ await this.fillIntoObjects(user);
160
+ await this.notifyPlaces(user);
161
+ await this.checkFences(user);
162
+ }
163
+ }
196
164
  }
197
165
  }
198
166
 
@@ -396,16 +364,24 @@ export class GoogleSharedlocations2 extends utils.Adapter {
396
364
  if (state.val === '') {
397
365
  this.log.info('Current cookies state was cleared, trying to obtain new cookies.');
398
366
  this._successFullPolls = 0;
367
+ this.cookie.readCookieFromString(''); //clear old cookie
399
368
  await this.cookie.loginToGetNewCookies();
400
- if (this.cookie.isValid()) {
401
- await this.sendRequest();
402
- }
403
369
  } else {
404
370
  this.log.info(
405
371
  'Current cookies state was changed from outside the adapter, updating internal cookie store.',
406
372
  );
407
- this.cookie.currentCookie = state.val as string;
373
+ this.cookie.readCookieFromString(state.val as string);
374
+ }
375
+ if (this.cookie.isValid()) {
376
+ await this.sendRequest();
377
+ }
378
+ } else if (id.endsWith('info.forceRefreshWithBrowser') && state && !state.ack) {
379
+ this.log.info('Force refresh in browser state was triggered, trying to obtain new cookies.');
380
+ await this.cookie.refreshCookieWithBrowser();
381
+ if (this.cookie.isValid()) {
382
+ await this.sendRequest();
408
383
  }
384
+ await this.setState('info.forceRefreshWithBrowser', false, true);
409
385
  }
410
386
  }
411
387