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