nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

commit 8096817534ad78e7cdd9655748b36332a0dc2f1d
parent 02d59edb888c3771be6e9d19f8b621c254bf18e2
Author: Sergej Orlov <wladimirych@gmail.com>
Date:   Fri, 21 Nov 2025 21:35:33 +0100

tracks-list: Update import of Garmin data for new API

Diffstat:
Meslint_rules/legacy_files_list.js | 1-
Msrc/lib/leaflet.control.track-list/lib/services/baseService.js | 8++++++++
Msrc/lib/leaflet.control.track-list/lib/services/garmin.js | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mtest/track_load_data/testcases/garmin_connect_activity_with_title.json | 2+-
Mtest/track_load_data/testcases/garmin_connect_activity_without_title.json | 2+-
5 files changed, 94 insertions(+), 39 deletions(-)

diff --git a/eslint_rules/legacy_files_list.js b/eslint_rules/legacy_files_list.js @@ -74,7 +74,6 @@ module.exports = [ 'src/lib/leaflet.control.track-list/lib/services/strava.js', 'src/lib/leaflet.control.track-list/lib/services/endomondo.js', 'src/lib/leaflet.control.track-list/lib/services/index.js', - 'src/lib/leaflet.control.track-list/lib/services/garmin.js', 'src/lib/leaflet.control.track-list/lib/services/tracedetrail.js', 'src/lib/leaflet.control.track-list/lib/services/gpslib.js', 'src/lib/leaflet.control.track-list/lib/services/openstreetmapRu.js', diff --git a/src/lib/leaflet.control.track-list/lib/services/baseService.js b/src/lib/leaflet.control.track-list/lib/services/baseService.js @@ -17,10 +17,18 @@ class BaseService { throw new Error('Method not implemented'); } + async prepare() { + return null; + } + async geoData() { if (!this.isOurUrl()) { throw new Error('Unsupported url'); } + const error = await this.prepare(); + if (error) { + return [{name: this.origUrl, error: error}]; + } const requests = this.requestOptions().map((it) => fetch(it.url, it.options)); let responses; try { diff --git a/src/lib/leaflet.control.track-list/lib/services/garmin.js b/src/lib/leaflet.control.track-list/lib/services/garmin.js @@ -1,10 +1,41 @@ -import BaseService from './baseService'; import {urlViaCorsProxy} from '~/lib/CORSProxy'; +import {fetch} from '~/lib/xhr-promise'; + +import BaseService from './baseService'; class GarminBase extends BaseService { + urlRe = /NOT IMPLEMENTED/u; + isOurUrl() { return this.urlRe.test(this.origUrl); } + + async prepare() { + let response; + try { + response = await fetch(urlViaCorsProxy(this.origUrl + '?' + Date.now()), { + isResponseSuccess: (xhr) => xhr.status === 200, + }); + } catch { + return 'NETWORK'; + } + let dom; + try { + dom = new DOMParser().parseFromString(response.response, 'text/html'); + } catch { + return 'NETWORK'; + } + const token = dom.querySelector('meta[name="csrf-token"]')?.content; + if (!token) { + return 'NETWORK'; + } + this.token = token; + return null; + } +} + +function isResponseSuccess(xhr) { + return xhr.status === 200 || xhr.status === 403 || xhr.status === 404; } class GarminRoute extends GarminBase { @@ -12,14 +43,18 @@ class GarminRoute extends GarminBase { requestOptions() { const m = this.urlRe.exec(this.origUrl); - const trackId = this.trackId = m[1]; - return [{ - url: urlViaCorsProxy(`https://connect.garmin.com/course-service/course/${trackId}`), - options: { - responseType: 'json', - isResponseSuccess: (xhr) => xhr.status === 200 || xhr.status === 403 || xhr.status === 404 + const trackId = m[1]; + this.trackId = trackId; + return [ + { + url: urlViaCorsProxy(`https://connect.garmin.com/gc-api/course-service/course/${trackId}`), + options: { + responseType: 'json', + headers: [['connect-csrf-token', this.token]], + isResponseSuccess, + }, }, - }]; + ]; } parseResponse(responses) { @@ -30,10 +65,10 @@ class GarminRoute extends GarminBase { if (response.status === 404) { return [{error: 'Garmin Connect route does not exist'}]; } - let name = `Garmin Connect route ${this.trackId}`; + let trackName = `Garmin Connect route ${this.trackId}`; const data = response.responseJSON; if (!data) { - return [{name, error: 'UNSUPPORTED'}]; + return [{name: trackName, error: 'UNSUPPORTED'}]; } let points = null; let tracks = []; @@ -45,14 +80,16 @@ class GarminRoute extends GarminBase { tracks = [data.geoPoints.map((obj) => ({lat: obj.latitude, lng: obj.longitude}))]; } } catch { - return [{name, error: 'UNSUPPORTED'}]; + return [{name: trackName, error: 'UNSUPPORTED'}]; } - name = data.courseName ? data.courseName : name; - return [{ - name, - points, - tracks: tracks, - }]; + trackName = data.courseName ? data.courseName : trackName; + return [ + { + name: trackName, + points, + tracks, + }, + ]; } } @@ -61,43 +98,54 @@ class GarminActivity extends GarminBase { requestOptions() { const m = this.urlRe.exec(this.origUrl); - const trackId = this.trackId = m[1]; + const trackId = m[1]; + this.trackId = trackId; return [ { - url: urlViaCorsProxy( - `https://connect.garmin.com/activity-service/activity/${trackId}/details`), + url: urlViaCorsProxy(`https://connect.garmin.com/gc-api/activity-service/activity/${trackId}`), options: { responseType: 'json', - isResponseSuccess: (xhr) => xhr.status === 200 || xhr.status === 403 || xhr.status === 404 - } - } + headers: [['connect-csrf-token', this.token]], + isResponseSuccess, + }, + }, + { + url: urlViaCorsProxy(`https://connect.garmin.com/gc-api/activity-service/activity/${trackId}/details`), + options: { + responseType: 'json', + headers: [['connect-csrf-token', this.token]], + isResponseSuccess, + }, + }, ]; } parseResponse(responses) { - const response = responses[0]; - if (response.status === 403) { + const [infoResponse, detailsResponse] = responses; + if (infoResponse.status === 403) { return [{error: 'Garmin Connect user disabled viewing this activity'}]; } - if (response.status === 404) { + if (infoResponse.status === 404) { return [{error: 'Garmin Connect activity does not exist'}]; } - let name = `Garmin Connect activity ${this.trackId}`; - const data = response.responseJSON; - if (!data) { - return [{name, error: 'UNSUPPORTED'}]; + let trackName = `Garmin Connect activity ${this.trackId}`; + if (!infoResponse.responseJSON) { + return [{name: trackName, error: 'UNSUPPORTED'}]; } + trackName = infoResponse.responseJSON.activityName || trackName; let track; try { - track = data.geoPolylineDTO.polyline.map((obj) => ({lat: obj.lat, lng: obj.lon})); + track = detailsResponse.responseJSON.geoPolylineDTO.polyline.map((obj) => ({lat: obj.lat, lng: obj.lon})); } catch { - return [{name, error: 'UNSUPPORTED'}]; + return [{name: trackName, error: 'UNSUPPORTED'}]; } - return [{ - name, - tracks: [track] - }]; + return [ + { + name: trackName, + tracks: [track], + }, + ]; } } diff --git a/test/track_load_data/testcases/garmin_connect_activity_with_title.json b/test/track_load_data/testcases/garmin_connect_activity_with_title.json @@ -7,7 +7,7 @@ ], "geodata": [ { - "name": "Garmin Connect activity 5346900838", + "name": "Test - Тест - Zkouška", "tracks": [ [ {"lat": 56.52694562450051, "lng": 28.96248271688819}, diff --git a/test/track_load_data/testcases/garmin_connect_activity_without_title.json b/test/track_load_data/testcases/garmin_connect_activity_without_title.json @@ -2,7 +2,7 @@ "query": ["https://connect.garmin.com/modern/activity/5346914204"], "geodata": [ { - "name": "Garmin Connect activity 5346914204", + "name": "Uncategorized", "tracks": [ [ {"lat": 39.148948872461915, "lng": 68.29878690652549},