garmin.js (4822B)
1 import {urlViaCorsProxy} from '~/lib/CORSProxy'; 2 import {fetch} from '~/lib/xhr-promise'; 3 4 import BaseService from './baseService'; 5 6 class GarminBase extends BaseService { 7 urlRe = /NOT IMPLEMENTED/u; 8 9 isOurUrl() { 10 return this.urlRe.test(this.origUrl); 11 } 12 13 async prepare() { 14 let response; 15 try { 16 response = await fetch(urlViaCorsProxy(this.origUrl + '?' + Date.now()), { 17 isResponseSuccess: (xhr) => xhr.status === 200, 18 }); 19 } catch { 20 return 'NETWORK'; 21 } 22 let dom; 23 try { 24 dom = new DOMParser().parseFromString(response.response, 'text/html'); 25 } catch { 26 return 'NETWORK'; 27 } 28 const token = dom.querySelector('meta[name="csrf-token"]')?.content; 29 if (!token) { 30 return 'NETWORK'; 31 } 32 this.token = token; 33 return null; 34 } 35 } 36 37 function isResponseSuccess(xhr) { 38 return xhr.status === 200 || xhr.status === 403 || xhr.status === 404; 39 } 40 41 class GarminRoute extends GarminBase { 42 urlRe = /^https?:\/\/connect\.garmin\.com\/modern\/course\/(\d+)/u; 43 44 requestOptions() { 45 const m = this.urlRe.exec(this.origUrl); 46 const trackId = m[1]; 47 this.trackId = trackId; 48 return [ 49 { 50 url: urlViaCorsProxy(`https://connect.garmin.com/gc-api/course-service/course/${trackId}`), 51 options: { 52 responseType: 'json', 53 headers: [['connect-csrf-token', this.token]], 54 isResponseSuccess, 55 }, 56 }, 57 ]; 58 } 59 60 parseResponse(responses) { 61 const response = responses[0]; 62 if (response.status === 403) { 63 return [{error: 'Garmin Connect user disabled viewing this route'}]; 64 } 65 if (response.status === 404) { 66 return [{error: 'Garmin Connect route does not exist'}]; 67 } 68 let trackName = `Garmin Connect route ${this.trackId}`; 69 const data = response.responseJSON; 70 if (!data) { 71 return [{name: trackName, error: 'UNSUPPORTED'}]; 72 } 73 let points = null; 74 let tracks = []; 75 try { 76 if (data.coursePoints) { 77 points = data.coursePoints.map((pt) => ({name: pt.name, lat: pt.lat, lng: pt.lon})); 78 } 79 if (data.geoPoints) { 80 tracks = [data.geoPoints.map((obj) => ({lat: obj.latitude, lng: obj.longitude}))]; 81 } 82 } catch { 83 return [{name: trackName, error: 'UNSUPPORTED'}]; 84 } 85 trackName = data.courseName ? data.courseName : trackName; 86 return [ 87 { 88 name: trackName, 89 points, 90 tracks, 91 }, 92 ]; 93 } 94 } 95 96 class GarminActivity extends GarminBase { 97 urlRe = /^https?:\/\/connect\.garmin\.com\/modern\/activity\/(\d+)/u; 98 99 requestOptions() { 100 const m = this.urlRe.exec(this.origUrl); 101 const trackId = m[1]; 102 this.trackId = trackId; 103 return [ 104 { 105 url: urlViaCorsProxy(`https://connect.garmin.com/gc-api/activity-service/activity/${trackId}`), 106 options: { 107 responseType: 'json', 108 headers: [['connect-csrf-token', this.token]], 109 isResponseSuccess, 110 }, 111 }, 112 { 113 url: urlViaCorsProxy(`https://connect.garmin.com/gc-api/activity-service/activity/${trackId}/details`), 114 options: { 115 responseType: 'json', 116 headers: [['connect-csrf-token', this.token]], 117 isResponseSuccess, 118 }, 119 }, 120 ]; 121 } 122 123 parseResponse(responses) { 124 const [infoResponse, detailsResponse] = responses; 125 if (infoResponse.status === 403) { 126 return [{error: 'Garmin Connect user disabled viewing this activity'}]; 127 } 128 if (infoResponse.status === 404) { 129 return [{error: 'Garmin Connect activity does not exist'}]; 130 } 131 let trackName = `Garmin Connect activity ${this.trackId}`; 132 if (!infoResponse.responseJSON) { 133 return [{name: trackName, error: 'UNSUPPORTED'}]; 134 } 135 trackName = infoResponse.responseJSON.activityName || trackName; 136 let track; 137 try { 138 track = detailsResponse.responseJSON.geoPolylineDTO.polyline.map((obj) => ({lat: obj.lat, lng: obj.lon})); 139 } catch { 140 return [{name: trackName, error: 'UNSUPPORTED'}]; 141 } 142 143 return [ 144 { 145 name: trackName, 146 tracks: [track], 147 }, 148 ]; 149 } 150 } 151 152 export {GarminRoute, GarminActivity};
