@gooin/garmin-connect 1.6.1 → 1.6.3
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 +1 -1
- package/dist/common/HttpClient.d.ts +2 -0
- package/dist/common/HttpClient.js +430 -249
- package/dist/common/HttpClient.js.map +1 -1
- package/dist/garmin/GarminConnect.d.ts +11 -1
- package/dist/garmin/GarminConnect.js +293 -105
- package/dist/garmin/GarminConnect.js.map +1 -1
- package/dist/garmin/UrlClass.d.ts +3 -1
- package/dist/garmin/UrlClass.js +141 -58
- package/dist/garmin/UrlClass.js.map +1 -1
- package/dist/garmin/types.d.ts +110 -0
- package/dist/garmin/workouts/Running.js +53 -36
- package/dist/garmin/workouts/Running.js.map +1 -1
- package/dist/utils.js +5 -5
- package/dist/utils.js.map +1 -1
- package/package.json +2 -4
|
@@ -1,254 +1,426 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
16
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
17
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
18
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
19
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
24
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
|
+
function step(op) {
|
|
27
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
28
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
29
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
30
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
31
|
+
switch (op[0]) {
|
|
32
|
+
case 0: case 1: t = op; break;
|
|
33
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
34
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
35
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
36
|
+
default:
|
|
37
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
38
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
39
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
40
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
41
|
+
if (t[2]) _.ops.pop();
|
|
42
|
+
_.trys.pop(); continue;
|
|
43
|
+
}
|
|
44
|
+
op = body.call(thisArg, _);
|
|
45
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
46
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
2
49
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
50
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
51
|
};
|
|
5
52
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
53
|
exports.HttpClient = void 0;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
54
|
+
var axios_1 = __importDefault(require("axios"));
|
|
55
|
+
var form_data_1 = __importDefault(require("form-data"));
|
|
56
|
+
var lodash_1 = __importDefault(require("lodash"));
|
|
57
|
+
var luxon_1 = require("luxon");
|
|
58
|
+
var oauth_1_0a_1 = __importDefault(require("oauth-1.0a"));
|
|
59
|
+
var qs_1 = __importDefault(require("qs"));
|
|
60
|
+
var crypto = require('crypto');
|
|
61
|
+
var CSRF_RE = new RegExp('name="_csrf"\\s+value="(.+?)"');
|
|
62
|
+
var TICKET_RE = new RegExp('ticket=([^"]+)"');
|
|
63
|
+
var ACCOUNT_LOCKED_RE = new RegExp('var statuss*=s*"([^"]*)"');
|
|
64
|
+
var PAGE_TITLE_RE = new RegExp('<title>([^<]*)</title>');
|
|
65
|
+
var USER_AGENT_CONNECTMOBILE = 'com.garmin.android.apps.connectmobile';
|
|
66
|
+
var USER_AGENT_BROWSER = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36';
|
|
67
|
+
var OAUTH_CONSUMER_URL = 'https://thegarth.s3.amazonaws.com/oauth_consumer.json';
|
|
20
68
|
// refresh token
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
69
|
+
var isRefreshing = false;
|
|
70
|
+
var refreshSubscribers = [];
|
|
71
|
+
var HttpClient = /** @class */ (function () {
|
|
72
|
+
function HttpClient(url) {
|
|
73
|
+
var _this = this;
|
|
25
74
|
this.url = url;
|
|
26
75
|
this.client = axios_1.default.create();
|
|
27
|
-
this.client.interceptors.response.use((response)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
76
|
+
this.client.interceptors.response.use(function (response) { return response; }, function (error) { return __awaiter(_this, void 0, void 0, function () {
|
|
77
|
+
var originalRequest, token, err_1;
|
|
78
|
+
var _this = this;
|
|
79
|
+
var _a;
|
|
80
|
+
return __generator(this, function (_b) {
|
|
81
|
+
switch (_b.label) {
|
|
82
|
+
case 0:
|
|
83
|
+
originalRequest = error.config;
|
|
84
|
+
if (!(((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) === 401 &&
|
|
85
|
+
!(originalRequest === null || originalRequest === void 0 ? void 0 : originalRequest._retry))) return [3 /*break*/, 6];
|
|
86
|
+
if (!this.oauth2Token) {
|
|
87
|
+
return [2 /*return*/];
|
|
88
|
+
}
|
|
89
|
+
if (!isRefreshing) return [3 /*break*/, 4];
|
|
90
|
+
_b.label = 1;
|
|
91
|
+
case 1:
|
|
92
|
+
_b.trys.push([1, 3, , 4]);
|
|
93
|
+
return [4 /*yield*/, new Promise(function (resolve) {
|
|
94
|
+
refreshSubscribers.push(function (token) {
|
|
95
|
+
resolve(token);
|
|
96
|
+
});
|
|
97
|
+
})];
|
|
98
|
+
case 2:
|
|
99
|
+
token = _b.sent();
|
|
100
|
+
originalRequest.headers.Authorization = "Bearer ".concat(token);
|
|
101
|
+
return [2 /*return*/, this.client(originalRequest)];
|
|
102
|
+
case 3:
|
|
103
|
+
err_1 = _b.sent();
|
|
104
|
+
console.log('err:', err_1);
|
|
105
|
+
return [2 /*return*/, Promise.reject(err_1)];
|
|
106
|
+
case 4:
|
|
107
|
+
originalRequest._retry = true;
|
|
108
|
+
isRefreshing = true;
|
|
109
|
+
console.log('interceptors: refreshOauth2Token start');
|
|
110
|
+
return [4 /*yield*/, this.refreshOauth2Token()];
|
|
111
|
+
case 5:
|
|
112
|
+
_b.sent();
|
|
113
|
+
console.log('interceptors: refreshOauth2Token end');
|
|
114
|
+
isRefreshing = false;
|
|
115
|
+
refreshSubscribers.forEach(function (subscriber) {
|
|
116
|
+
return subscriber(_this.oauth2Token.access_token);
|
|
41
117
|
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
118
|
+
refreshSubscribers = [];
|
|
119
|
+
originalRequest.headers.Authorization = "Bearer ".concat(this.oauth2Token.access_token);
|
|
120
|
+
return [2 /*return*/, this.client(originalRequest)];
|
|
121
|
+
case 6:
|
|
122
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
123
|
+
if (error === null || error === void 0 ? void 0 : error.response)
|
|
124
|
+
this.handleError(error === null || error === void 0 ? void 0 : error.response);
|
|
125
|
+
}
|
|
126
|
+
throw error;
|
|
49
127
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
128
|
+
});
|
|
129
|
+
}); });
|
|
130
|
+
this.client.interceptors.request.use(function (config) { return __awaiter(_this, void 0, void 0, function () {
|
|
131
|
+
return __generator(this, function (_a) {
|
|
132
|
+
if (this.oauth2Token) {
|
|
133
|
+
config.headers.Authorization =
|
|
134
|
+
'Bearer ' + this.oauth2Token.access_token;
|
|
135
|
+
}
|
|
136
|
+
return [2 /*return*/, config];
|
|
137
|
+
});
|
|
138
|
+
}); });
|
|
139
|
+
}
|
|
140
|
+
HttpClient.prototype.fetchOauthConsumer = function () {
|
|
141
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
142
|
+
var response;
|
|
143
|
+
return __generator(this, function (_a) {
|
|
144
|
+
switch (_a.label) {
|
|
145
|
+
case 0: return [4 /*yield*/, axios_1.default.get(OAUTH_CONSUMER_URL)];
|
|
146
|
+
case 1:
|
|
147
|
+
response = _a.sent();
|
|
148
|
+
this.OAUTH_CONSUMER = {
|
|
149
|
+
key: response.data.consumer_key,
|
|
150
|
+
secret: response.data.consumer_secret
|
|
151
|
+
};
|
|
152
|
+
return [2 /*return*/];
|
|
153
|
+
}
|
|
154
|
+
});
|
|
66
155
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
156
|
+
};
|
|
157
|
+
HttpClient.prototype.checkTokenVaild = function () {
|
|
158
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
159
|
+
return __generator(this, function (_a) {
|
|
160
|
+
switch (_a.label) {
|
|
161
|
+
case 0:
|
|
162
|
+
if (!this.oauth2Token) return [3 /*break*/, 2];
|
|
163
|
+
if (!(this.oauth2Token.expires_at < luxon_1.DateTime.now().toSeconds())) return [3 /*break*/, 2];
|
|
164
|
+
console.error('Token expired!');
|
|
165
|
+
return [4 /*yield*/, this.refreshOauth2Token()];
|
|
166
|
+
case 1:
|
|
167
|
+
_a.sent();
|
|
168
|
+
_a.label = 2;
|
|
169
|
+
case 2: return [2 /*return*/];
|
|
170
|
+
}
|
|
171
|
+
});
|
|
73
172
|
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
await this.refreshOauth2Token();
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
async get(url, config) {
|
|
91
|
-
const response = await this.client.get(url, config);
|
|
92
|
-
return response === null || response === void 0 ? void 0 : response.data;
|
|
93
|
-
}
|
|
94
|
-
async post(url, data, config) {
|
|
95
|
-
const response = await this.client.post(url, data, config);
|
|
96
|
-
return response === null || response === void 0 ? void 0 : response.data;
|
|
97
|
-
}
|
|
98
|
-
setCommonHeader(headers) {
|
|
99
|
-
lodash_1.default.each(headers, (headerValue, key) => {
|
|
100
|
-
this.client.defaults.headers.common[key] = headerValue;
|
|
173
|
+
};
|
|
174
|
+
HttpClient.prototype.get = function (url, config) {
|
|
175
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
176
|
+
var response;
|
|
177
|
+
return __generator(this, function (_a) {
|
|
178
|
+
switch (_a.label) {
|
|
179
|
+
case 0: return [4 /*yield*/, this.client.get(url, config)];
|
|
180
|
+
case 1:
|
|
181
|
+
response = _a.sent();
|
|
182
|
+
return [2 /*return*/, response === null || response === void 0 ? void 0 : response.data];
|
|
183
|
+
}
|
|
184
|
+
});
|
|
101
185
|
});
|
|
102
|
-
}
|
|
103
|
-
|
|
186
|
+
};
|
|
187
|
+
HttpClient.prototype.post = function (url, data, config) {
|
|
188
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
189
|
+
var response;
|
|
190
|
+
return __generator(this, function (_a) {
|
|
191
|
+
switch (_a.label) {
|
|
192
|
+
case 0: return [4 /*yield*/, this.client.post(url, data, config)];
|
|
193
|
+
case 1:
|
|
194
|
+
response = _a.sent();
|
|
195
|
+
return [2 /*return*/, response === null || response === void 0 ? void 0 : response.data];
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
HttpClient.prototype.delete = function (url, config) {
|
|
201
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
202
|
+
var response;
|
|
203
|
+
return __generator(this, function (_a) {
|
|
204
|
+
switch (_a.label) {
|
|
205
|
+
case 0: return [4 /*yield*/, this.client.post(url, null, __assign(__assign({}, config), { headers: __assign(__assign({}, config === null || config === void 0 ? void 0 : config.headers), { 'X-Http-Method-Override': 'DELETE' }) }))];
|
|
206
|
+
case 1:
|
|
207
|
+
response = _a.sent();
|
|
208
|
+
return [2 /*return*/, response === null || response === void 0 ? void 0 : response.data];
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
HttpClient.prototype.setCommonHeader = function (headers) {
|
|
214
|
+
var _this = this;
|
|
215
|
+
lodash_1.default.each(headers, function (headerValue, key) {
|
|
216
|
+
_this.client.defaults.headers.common[key] = headerValue;
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
HttpClient.prototype.handleError = function (response) {
|
|
104
220
|
this.handleHttpError(response);
|
|
105
|
-
}
|
|
106
|
-
handleHttpError(response) {
|
|
107
|
-
|
|
108
|
-
|
|
221
|
+
};
|
|
222
|
+
HttpClient.prototype.handleHttpError = function (response) {
|
|
223
|
+
var status = response.status, statusText = response.statusText, data = response.data;
|
|
224
|
+
var msg = "ERROR: (".concat(status, "), ").concat(statusText, ", ").concat(JSON.stringify(data));
|
|
109
225
|
console.error(msg);
|
|
110
226
|
throw new Error(msg);
|
|
111
|
-
}
|
|
227
|
+
};
|
|
112
228
|
/**
|
|
113
229
|
* Login to Garmin Connect
|
|
114
230
|
* @param username
|
|
115
231
|
* @param password
|
|
116
232
|
* @returns {Promise<HttpClient>}
|
|
117
233
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
embedWidget: true,
|
|
143
|
-
locale: 'en',
|
|
144
|
-
gauthHost: this.url.GARMIN_SSO_EMBED
|
|
145
|
-
};
|
|
146
|
-
const step2Url = `${this.url.SIGNIN_URL}?${qs_1.default.stringify(step2Params)}`;
|
|
147
|
-
// console.log('login - step2Url:', step2Url);
|
|
148
|
-
const step2Result = await this.get(step2Url);
|
|
149
|
-
// console.log('login - step2Result:', step2Result)
|
|
150
|
-
const csrfRegResult = CSRF_RE.exec(step2Result);
|
|
151
|
-
if (!csrfRegResult) {
|
|
152
|
-
throw new Error('login - csrf not found');
|
|
153
|
-
}
|
|
154
|
-
const csrf_token = csrfRegResult[1];
|
|
155
|
-
// console.log('login - csrf:', csrf_token);
|
|
156
|
-
// Step3 Get ticket
|
|
157
|
-
const signinParams = {
|
|
158
|
-
id: 'gauth-widget',
|
|
159
|
-
embedWidget: true,
|
|
160
|
-
clientId: 'GarminConnect',
|
|
161
|
-
locale: 'en',
|
|
162
|
-
gauthHost: this.url.GARMIN_SSO_EMBED,
|
|
163
|
-
service: this.url.GARMIN_SSO_EMBED,
|
|
164
|
-
source: this.url.GARMIN_SSO_EMBED,
|
|
165
|
-
redirectAfterAccountLoginUrl: this.url.GARMIN_SSO_EMBED,
|
|
166
|
-
redirectAfterAccountCreationUrl: this.url.GARMIN_SSO_EMBED
|
|
167
|
-
};
|
|
168
|
-
const step3Url = `${this.url.SIGNIN_URL}?${qs_1.default.stringify(signinParams)}`;
|
|
169
|
-
// console.log('login - step3Url:', step3Url);
|
|
170
|
-
const step3Form = new form_data_1.default();
|
|
171
|
-
step3Form.append('username', username);
|
|
172
|
-
step3Form.append('password', password);
|
|
173
|
-
step3Form.append('embed', 'true');
|
|
174
|
-
step3Form.append('_csrf', csrf_token);
|
|
175
|
-
const step3Result = await this.post(step3Url, step3Form, {
|
|
176
|
-
headers: {
|
|
177
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
178
|
-
Dnt: 1,
|
|
179
|
-
Origin: this.url.GARMIN_SSO_ORIGIN,
|
|
180
|
-
Referer: this.url.SIGNIN_URL,
|
|
181
|
-
'User-Agent': USER_AGENT_BROWSER
|
|
182
|
-
}
|
|
234
|
+
HttpClient.prototype.login = function (username, password) {
|
|
235
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
236
|
+
var ticket, oauth1;
|
|
237
|
+
return __generator(this, function (_a) {
|
|
238
|
+
switch (_a.label) {
|
|
239
|
+
case 0: return [4 /*yield*/, this.fetchOauthConsumer()];
|
|
240
|
+
case 1:
|
|
241
|
+
_a.sent();
|
|
242
|
+
return [4 /*yield*/, this.getLoginTicket(username, password)];
|
|
243
|
+
case 2:
|
|
244
|
+
ticket = _a.sent();
|
|
245
|
+
return [4 /*yield*/, this.getOauth1Token(ticket)];
|
|
246
|
+
case 3:
|
|
247
|
+
oauth1 = _a.sent();
|
|
248
|
+
// TODO: Handle MFA
|
|
249
|
+
// Step 5: Oauth2
|
|
250
|
+
return [4 /*yield*/, this.exchange(oauth1)];
|
|
251
|
+
case 4:
|
|
252
|
+
// TODO: Handle MFA
|
|
253
|
+
// Step 5: Oauth2
|
|
254
|
+
_a.sent();
|
|
255
|
+
return [2 /*return*/, this];
|
|
256
|
+
}
|
|
257
|
+
});
|
|
183
258
|
});
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
this
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
259
|
+
};
|
|
260
|
+
HttpClient.prototype.getLoginTicket = function (username, password) {
|
|
261
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
262
|
+
var step1Params, step1Url, step2Params, step2Url, step2Result, csrfRegResult, csrf_token, signinParams, step3Url, step3Form, step3Result, ticketRegResult, ticket;
|
|
263
|
+
return __generator(this, function (_a) {
|
|
264
|
+
switch (_a.label) {
|
|
265
|
+
case 0:
|
|
266
|
+
step1Params = {
|
|
267
|
+
clientId: 'GarminConnect',
|
|
268
|
+
locale: 'en',
|
|
269
|
+
service: this.url.GC_MODERN
|
|
270
|
+
};
|
|
271
|
+
step1Url = "".concat(this.url.GARMIN_SSO_EMBED, "?").concat(qs_1.default.stringify(step1Params));
|
|
272
|
+
// console.log('login - step1Url:', step1Url);
|
|
273
|
+
return [4 /*yield*/, this.client.get(step1Url)];
|
|
274
|
+
case 1:
|
|
275
|
+
// console.log('login - step1Url:', step1Url);
|
|
276
|
+
_a.sent();
|
|
277
|
+
step2Params = {
|
|
278
|
+
id: 'gauth-widget',
|
|
279
|
+
embedWidget: true,
|
|
280
|
+
locale: 'en',
|
|
281
|
+
gauthHost: this.url.GARMIN_SSO_EMBED
|
|
282
|
+
};
|
|
283
|
+
step2Url = "".concat(this.url.SIGNIN_URL, "?").concat(qs_1.default.stringify(step2Params));
|
|
284
|
+
return [4 /*yield*/, this.get(step2Url)];
|
|
285
|
+
case 2:
|
|
286
|
+
step2Result = _a.sent();
|
|
287
|
+
csrfRegResult = CSRF_RE.exec(step2Result);
|
|
288
|
+
if (!csrfRegResult) {
|
|
289
|
+
throw new Error('login - csrf not found');
|
|
290
|
+
}
|
|
291
|
+
csrf_token = csrfRegResult[1];
|
|
292
|
+
signinParams = {
|
|
293
|
+
id: 'gauth-widget',
|
|
294
|
+
embedWidget: true,
|
|
295
|
+
clientId: 'GarminConnect',
|
|
296
|
+
locale: 'en',
|
|
297
|
+
gauthHost: this.url.GARMIN_SSO_EMBED,
|
|
298
|
+
service: this.url.GARMIN_SSO_EMBED,
|
|
299
|
+
source: this.url.GARMIN_SSO_EMBED,
|
|
300
|
+
redirectAfterAccountLoginUrl: this.url.GARMIN_SSO_EMBED,
|
|
301
|
+
redirectAfterAccountCreationUrl: this.url.GARMIN_SSO_EMBED
|
|
302
|
+
};
|
|
303
|
+
step3Url = "".concat(this.url.SIGNIN_URL, "?").concat(qs_1.default.stringify(signinParams));
|
|
304
|
+
step3Form = new form_data_1.default();
|
|
305
|
+
step3Form.append('username', username);
|
|
306
|
+
step3Form.append('password', password);
|
|
307
|
+
step3Form.append('embed', 'true');
|
|
308
|
+
step3Form.append('_csrf', csrf_token);
|
|
309
|
+
return [4 /*yield*/, this.post(step3Url, step3Form, {
|
|
310
|
+
headers: {
|
|
311
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
312
|
+
Dnt: 1,
|
|
313
|
+
Origin: this.url.GARMIN_SSO_ORIGIN,
|
|
314
|
+
Referer: this.url.SIGNIN_URL,
|
|
315
|
+
'User-Agent': USER_AGENT_BROWSER
|
|
316
|
+
}
|
|
317
|
+
})];
|
|
318
|
+
case 3:
|
|
319
|
+
step3Result = _a.sent();
|
|
320
|
+
// console.log('step3Result:', step3Result)
|
|
321
|
+
this.handleAccountLocked(step3Result);
|
|
322
|
+
this.handlePageTitle(step3Result);
|
|
323
|
+
this.handleMFA(step3Result);
|
|
324
|
+
ticketRegResult = TICKET_RE.exec(step3Result);
|
|
325
|
+
if (!ticketRegResult) {
|
|
326
|
+
throw new Error('login failed (Ticket not found or MFA), please check username and password');
|
|
327
|
+
}
|
|
328
|
+
ticket = ticketRegResult[1];
|
|
329
|
+
return [2 /*return*/, ticket];
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
};
|
|
194
334
|
// TODO: Handle MFA
|
|
195
|
-
handleMFA(htmlStr) { }
|
|
196
|
-
|
|
197
|
-
|
|
335
|
+
HttpClient.prototype.handleMFA = function (htmlStr) { };
|
|
336
|
+
// TODO: Handle Phone number
|
|
337
|
+
HttpClient.prototype.handlePageTitle = function (htmlStr) {
|
|
338
|
+
var pageTitileRegResult = PAGE_TITLE_RE.exec(htmlStr);
|
|
339
|
+
if (pageTitileRegResult) {
|
|
340
|
+
var title = pageTitileRegResult[1];
|
|
341
|
+
console.log('login page title:', title);
|
|
342
|
+
if (lodash_1.default.includes(title, 'Update Phone Number')) {
|
|
343
|
+
// current I don't know where to update it
|
|
344
|
+
// See: https://github.com/matin/garth/issues/19
|
|
345
|
+
throw new Error("login failed (Update Phone number), please update your phone number, currently I don't know where to update it");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
HttpClient.prototype.handleAccountLocked = function (htmlStr) {
|
|
350
|
+
var accountLockedRegResult = ACCOUNT_LOCKED_RE.exec(htmlStr);
|
|
198
351
|
if (accountLockedRegResult) {
|
|
199
|
-
|
|
352
|
+
var msg = accountLockedRegResult[1];
|
|
200
353
|
console.error(msg);
|
|
201
354
|
throw new Error('login failed (AccountLocked), please open connect web page to unlock your account');
|
|
202
355
|
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
url: url,
|
|
231
|
-
method: 'GET'
|
|
232
|
-
};
|
|
233
|
-
const headers = oauth.toHeader(oauth.authorize(step4RequestData));
|
|
234
|
-
// console.log('getOauth1Token - headers:', headers);
|
|
235
|
-
const response = await this.get(url, {
|
|
236
|
-
headers: {
|
|
237
|
-
...headers,
|
|
238
|
-
'User-Agent': USER_AGENT_CONNECTMOBILE
|
|
239
|
-
}
|
|
356
|
+
};
|
|
357
|
+
HttpClient.prototype.refreshOauth2Token = function () {
|
|
358
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
359
|
+
var oauth1;
|
|
360
|
+
return __generator(this, function (_a) {
|
|
361
|
+
switch (_a.label) {
|
|
362
|
+
case 0:
|
|
363
|
+
if (!!this.OAUTH_CONSUMER) return [3 /*break*/, 2];
|
|
364
|
+
return [4 /*yield*/, this.fetchOauthConsumer()];
|
|
365
|
+
case 1:
|
|
366
|
+
_a.sent();
|
|
367
|
+
_a.label = 2;
|
|
368
|
+
case 2:
|
|
369
|
+
if (!this.oauth2Token || !this.oauth1Token) {
|
|
370
|
+
throw new Error('No Oauth2Token or Oauth1Token');
|
|
371
|
+
}
|
|
372
|
+
oauth1 = {
|
|
373
|
+
oauth: this.getOauthClient(this.OAUTH_CONSUMER),
|
|
374
|
+
token: this.oauth1Token
|
|
375
|
+
};
|
|
376
|
+
return [4 /*yield*/, this.exchange(oauth1)];
|
|
377
|
+
case 3:
|
|
378
|
+
_a.sent();
|
|
379
|
+
console.log('Oauth2 token refreshed!');
|
|
380
|
+
return [2 /*return*/];
|
|
381
|
+
}
|
|
382
|
+
});
|
|
240
383
|
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
384
|
+
};
|
|
385
|
+
HttpClient.prototype.getOauth1Token = function (ticket) {
|
|
386
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
387
|
+
var params, url, oauth, step4RequestData, headers, response, token;
|
|
388
|
+
return __generator(this, function (_a) {
|
|
389
|
+
switch (_a.label) {
|
|
390
|
+
case 0:
|
|
391
|
+
if (!this.OAUTH_CONSUMER) {
|
|
392
|
+
throw new Error('No OAUTH_CONSUMER');
|
|
393
|
+
}
|
|
394
|
+
params = {
|
|
395
|
+
ticket: ticket,
|
|
396
|
+
'login-url': this.url.GARMIN_SSO_EMBED,
|
|
397
|
+
'accepts-mfa-tokens': true
|
|
398
|
+
};
|
|
399
|
+
url = "".concat(this.url.OAUTH_URL, "/preauthorized?").concat(qs_1.default.stringify(params));
|
|
400
|
+
oauth = this.getOauthClient(this.OAUTH_CONSUMER);
|
|
401
|
+
step4RequestData = {
|
|
402
|
+
url: url,
|
|
403
|
+
method: 'GET'
|
|
404
|
+
};
|
|
405
|
+
headers = oauth.toHeader(oauth.authorize(step4RequestData));
|
|
406
|
+
return [4 /*yield*/, this.get(url, {
|
|
407
|
+
headers: __assign(__assign({}, headers), { 'User-Agent': USER_AGENT_CONNECTMOBILE })
|
|
408
|
+
})];
|
|
409
|
+
case 1:
|
|
410
|
+
response = _a.sent();
|
|
411
|
+
token = qs_1.default.parse(response);
|
|
412
|
+
// console.log('getOauth1Token - token:', token);
|
|
413
|
+
this.oauth1Token = token;
|
|
414
|
+
return [2 /*return*/, { token: token, oauth: oauth }];
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
};
|
|
419
|
+
HttpClient.prototype.getOauthClient = function (consumer) {
|
|
420
|
+
var oauth = new oauth_1_0a_1.default({
|
|
249
421
|
consumer: consumer,
|
|
250
422
|
signature_method: 'HMAC-SHA1',
|
|
251
|
-
hash_function(base_string, key) {
|
|
423
|
+
hash_function: function (base_string, key) {
|
|
252
424
|
return crypto
|
|
253
425
|
.createHmac('sha1', key)
|
|
254
426
|
.update(base_string)
|
|
@@ -256,36 +428,44 @@ class HttpClient {
|
|
|
256
428
|
}
|
|
257
429
|
});
|
|
258
430
|
return oauth;
|
|
259
|
-
}
|
|
431
|
+
};
|
|
260
432
|
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
433
|
+
HttpClient.prototype.exchange = function (oauth1) {
|
|
434
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
435
|
+
var token, baseUrl, requestData, step5AuthData, url, response;
|
|
436
|
+
return __generator(this, function (_a) {
|
|
437
|
+
switch (_a.label) {
|
|
438
|
+
case 0:
|
|
439
|
+
token = {
|
|
440
|
+
key: oauth1.token.oauth_token,
|
|
441
|
+
secret: oauth1.token.oauth_token_secret
|
|
442
|
+
};
|
|
443
|
+
baseUrl = "".concat(this.url.OAUTH_URL, "/exchange/user/2.0");
|
|
444
|
+
requestData = {
|
|
445
|
+
url: baseUrl,
|
|
446
|
+
method: 'POST',
|
|
447
|
+
data: null
|
|
448
|
+
};
|
|
449
|
+
step5AuthData = oauth1.oauth.authorize(requestData, token);
|
|
450
|
+
url = "".concat(baseUrl, "?").concat(qs_1.default.stringify(step5AuthData));
|
|
451
|
+
// console.log('exchange - url:', url);
|
|
452
|
+
this.oauth2Token = undefined;
|
|
453
|
+
return [4 /*yield*/, this.post(url, null, {
|
|
454
|
+
headers: {
|
|
455
|
+
'User-Agent': USER_AGENT_CONNECTMOBILE,
|
|
456
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
457
|
+
}
|
|
458
|
+
})];
|
|
459
|
+
case 1:
|
|
460
|
+
response = _a.sent();
|
|
461
|
+
// console.log('exchange - response:', response);
|
|
462
|
+
this.oauth2Token = this.setOauth2TokenExpiresAt(response);
|
|
463
|
+
return [2 /*return*/];
|
|
464
|
+
}
|
|
465
|
+
});
|
|
283
466
|
});
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// console.log('exchange - oauth2Token:', this.oauth2Token);
|
|
287
|
-
}
|
|
288
|
-
setOauth2TokenExpiresAt(token) {
|
|
467
|
+
};
|
|
468
|
+
HttpClient.prototype.setOauth2TokenExpiresAt = function (token) {
|
|
289
469
|
// human readable date
|
|
290
470
|
token['last_update_date'] = luxon_1.DateTime.now().toLocal().toString();
|
|
291
471
|
token['expires_date'] = luxon_1.DateTime.fromSeconds(luxon_1.DateTime.now().toSeconds() + token['expires_in'])
|
|
@@ -296,7 +476,8 @@ class HttpClient {
|
|
|
296
476
|
token['refresh_token_expires_at'] =
|
|
297
477
|
luxon_1.DateTime.now().toSeconds() + token['refresh_token_expires_in'];
|
|
298
478
|
return token;
|
|
299
|
-
}
|
|
300
|
-
|
|
479
|
+
};
|
|
480
|
+
return HttpClient;
|
|
481
|
+
}());
|
|
301
482
|
exports.HttpClient = HttpClient;
|
|
302
483
|
//# sourceMappingURL=HttpClient.js.map
|