@gooin/garmin-connect 1.7.5 → 1.8.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.
Files changed (138) hide show
  1. package/dist/common/FileMFASessionStorage.js +1 -247
  2. package/dist/common/HttpClient copy.js +1 -361
  3. package/dist/common/HttpClient.d.ts +1 -0
  4. package/dist/common/HttpClient.js +1 -613
  5. package/dist/common/HttpClient.js.map +1 -1
  6. package/dist/common/HttpClientOLD.js +1 -361
  7. package/dist/common/HttpClientOLDV1.js +1 -361
  8. package/dist/common/HttpClientV1.js +1 -577
  9. package/dist/common/MFAManager.js +1 -126
  10. package/dist/common/MFASessionStorage.js +1 -3
  11. package/dist/common/RedisMFASessionStorage.js +1 -281
  12. package/dist/common/RedisMFASessionStorage.js.map +1 -1
  13. package/dist/garmin/GarminConnect.d.ts +15 -69
  14. package/dist/garmin/GarminConnect.js +1 -509
  15. package/dist/garmin/GarminConnect.js.map +1 -1
  16. package/dist/garmin/UrlClass.d.ts +28 -0
  17. package/dist/garmin/UrlClass.js +1 -128
  18. package/dist/garmin/UrlClass.js.map +1 -1
  19. package/dist/garmin/common/DateUtils.d.ts +1 -0
  20. package/dist/garmin/common/DateUtils.js +1 -38
  21. package/dist/garmin/common/DateUtils.js.map +1 -1
  22. package/dist/garmin/common/HydrationUtils.js +1 -16
  23. package/dist/garmin/common/WeightUtils.js +1 -9
  24. package/dist/garmin/modules/activity/base.d.ts +39 -0
  25. package/dist/garmin/modules/activity/base.js +1 -0
  26. package/dist/garmin/modules/activity/base.js.map +1 -0
  27. package/dist/garmin/modules/activity/cycling.d.ts +16 -0
  28. package/dist/garmin/modules/activity/cycling.js +1 -0
  29. package/dist/garmin/modules/activity/cycling.js.map +1 -0
  30. package/dist/garmin/modules/activity/running.d.ts +15 -0
  31. package/dist/garmin/modules/activity/running.js +1 -0
  32. package/dist/garmin/modules/activity/running.js.map +1 -0
  33. package/dist/garmin/modules/activity/training-status.d.ts +13 -0
  34. package/dist/garmin/modules/activity/training-status.js +1 -0
  35. package/dist/garmin/modules/activity/training-status.js.map +1 -0
  36. package/dist/garmin/modules/coach/activity-summary.d.ts +20 -0
  37. package/dist/garmin/modules/coach/activity-summary.js +1 -0
  38. package/dist/garmin/modules/coach/activity-summary.js.map +1 -0
  39. package/dist/garmin/modules/coach/helpers.d.ts +157 -0
  40. package/dist/garmin/modules/coach/helpers.js +1 -0
  41. package/dist/garmin/modules/coach/helpers.js.map +1 -0
  42. package/dist/garmin/modules/coach/training-overview.d.ts +5 -0
  43. package/dist/garmin/modules/coach/training-overview.js +1 -0
  44. package/dist/garmin/modules/coach/training-overview.js.map +1 -0
  45. package/dist/garmin/modules/coach/wellness-overview.d.ts +5 -0
  46. package/dist/garmin/modules/coach/wellness-overview.js +1 -0
  47. package/dist/garmin/modules/coach/wellness-overview.js.map +1 -0
  48. package/dist/garmin/modules/coach/wellness-summary.d.ts +4 -0
  49. package/dist/garmin/modules/coach/wellness-summary.js +1 -0
  50. package/dist/garmin/modules/coach/wellness-summary.js.map +1 -0
  51. package/dist/garmin/modules/coach.d.ts +60 -0
  52. package/dist/garmin/modules/coach.js +1 -0
  53. package/dist/garmin/modules/coach.js.map +1 -0
  54. package/dist/garmin/modules/course.d.ts +27 -0
  55. package/dist/garmin/modules/course.js +1 -0
  56. package/dist/garmin/modules/course.js.map +1 -0
  57. package/dist/garmin/modules/device.d.ts +11 -0
  58. package/dist/garmin/modules/device.js +1 -0
  59. package/dist/garmin/modules/device.js.map +1 -0
  60. package/dist/garmin/modules/misc.d.ts +18 -0
  61. package/dist/garmin/modules/misc.js +1 -0
  62. package/dist/garmin/modules/misc.js.map +1 -0
  63. package/dist/garmin/modules/types.d.ts +10 -0
  64. package/dist/garmin/modules/types.js +1 -0
  65. package/dist/garmin/modules/types.js.map +1 -0
  66. package/dist/garmin/modules/user.d.ts +18 -0
  67. package/dist/garmin/modules/user.js +1 -0
  68. package/dist/garmin/modules/user.js.map +1 -0
  69. package/dist/garmin/modules/wellness/body-battery.d.ts +11 -0
  70. package/dist/garmin/modules/wellness/body-battery.js +1 -0
  71. package/dist/garmin/modules/wellness/body-battery.js.map +1 -0
  72. package/dist/garmin/modules/wellness/heart-rate.d.ts +11 -0
  73. package/dist/garmin/modules/wellness/heart-rate.js +1 -0
  74. package/dist/garmin/modules/wellness/heart-rate.js.map +1 -0
  75. package/dist/garmin/modules/wellness/hrv.d.ts +12 -0
  76. package/dist/garmin/modules/wellness/hrv.js +1 -0
  77. package/dist/garmin/modules/wellness/hrv.js.map +1 -0
  78. package/dist/garmin/modules/wellness/hydration.d.ts +12 -0
  79. package/dist/garmin/modules/wellness/hydration.js +1 -0
  80. package/dist/garmin/modules/wellness/hydration.js.map +1 -0
  81. package/dist/garmin/modules/wellness/sleep.d.ts +16 -0
  82. package/dist/garmin/modules/wellness/sleep.js +1 -0
  83. package/dist/garmin/modules/wellness/sleep.js.map +1 -0
  84. package/dist/garmin/modules/wellness/weight.d.ts +20 -0
  85. package/dist/garmin/modules/wellness/weight.js +1 -0
  86. package/dist/garmin/modules/wellness/weight.js.map +1 -0
  87. package/dist/garmin/modules/workout.d.ts +23 -0
  88. package/dist/garmin/modules/workout.js +1 -0
  89. package/dist/garmin/modules/workout.js.map +1 -0
  90. package/dist/garmin/types/activity-stats.d.ts +21 -0
  91. package/dist/garmin/types/activity-stats.js +1 -0
  92. package/dist/garmin/types/activity-stats.js.map +1 -0
  93. package/dist/garmin/types/activity.js +1 -27
  94. package/dist/garmin/types/body-battery.d.ts +7 -0
  95. package/dist/garmin/types/body-battery.js +1 -0
  96. package/dist/garmin/types/body-battery.js.map +1 -0
  97. package/dist/garmin/types/coach.d.ts +533 -0
  98. package/dist/garmin/types/coach.js +1 -0
  99. package/dist/garmin/types/coach.js.map +1 -0
  100. package/dist/garmin/types/course.d.ts +3 -11
  101. package/dist/garmin/types/course.js +1 -3
  102. package/dist/garmin/types/cycling.d.ts +70 -0
  103. package/dist/garmin/types/cycling.js +1 -0
  104. package/dist/garmin/types/cycling.js.map +1 -0
  105. package/dist/garmin/types/device.d.ts +32 -0
  106. package/dist/garmin/types/device.js +1 -0
  107. package/dist/garmin/types/device.js.map +1 -0
  108. package/dist/garmin/types/golf.d.ts +4 -4
  109. package/dist/garmin/types/golf.js +1 -3
  110. package/dist/garmin/types/golf.js.map +1 -1
  111. package/dist/garmin/types/heartrate.d.ts +3 -3
  112. package/dist/garmin/types/heartrate.js +1 -2
  113. package/dist/garmin/types/hrv.d.ts +47 -0
  114. package/dist/garmin/types/hrv.js +1 -0
  115. package/dist/garmin/types/hrv.js.map +1 -0
  116. package/dist/garmin/types/hydration.d.ts +3 -3
  117. package/dist/garmin/types/hydration.js +1 -2
  118. package/dist/garmin/types/index.d.ts +15 -0
  119. package/dist/garmin/types/index.js +1 -36
  120. package/dist/garmin/types/index.js.map +1 -1
  121. package/dist/garmin/types/personal-info.d.ts +29 -0
  122. package/dist/garmin/types/personal-info.js +1 -0
  123. package/dist/garmin/types/personal-info.js.map +1 -0
  124. package/dist/garmin/types/race-prediction.d.ts +50 -0
  125. package/dist/garmin/types/race-prediction.js +1 -0
  126. package/dist/garmin/types/race-prediction.js.map +1 -0
  127. package/dist/garmin/types/sleep.d.ts +46 -0
  128. package/dist/garmin/types/sleep.js +1 -3
  129. package/dist/garmin/types/training-status.d.ts +70 -0
  130. package/dist/garmin/types/training-status.js +1 -0
  131. package/dist/garmin/types/training-status.js.map +1 -0
  132. package/dist/garmin/types/weight.d.ts +46 -4
  133. package/dist/garmin/types/weight.js +1 -3
  134. package/dist/garmin/workouts/Running.js +1 -47
  135. package/dist/garmin/workouts/templates/RunningTemplate.js +1 -78
  136. package/dist/index.js +1 -11
  137. package/dist/utils.js +1 -37
  138. package/package.json +4 -3
@@ -96,6 +96,7 @@ export declare class HttpClient {
96
96
  private performLoginStep3;
97
97
  /**
98
98
  * 判断是否需要MFA验证
99
+ * 如果页面标题不包含"sign"且不包含"ticket",说明还在验证阶段,可能需要MFA
99
100
  */
100
101
  private isMFARequired;
101
102
  /**
@@ -1,613 +1 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.HttpClient = void 0;
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 MFAManager_1 = require("./MFAManager");
14
- const node_crypto_1 = __importDefault(require("node:crypto"));
15
- const tough_cookie_1 = require("tough-cookie");
16
- const axios_cookiejar_support_1 = require("axios-cookiejar-support");
17
- // 正则表达式常量
18
- const CSRF_RE = new RegExp('name="_csrf"\\s+value="(.+?)"');
19
- const TICKET_RE = new RegExp('ticket=([^"]+)"');
20
- const ACCOUNT_LOCKED_RE = new RegExp('var statuss*=s*"([^"]*)"');
21
- const PAGE_TITLE_RE = new RegExp('<title>([^<]*)</title>');
22
- // 用户代理常量
23
- const USER_AGENT_CONNECTMOBILE = 'com.garmin.android.apps.connectmobile';
24
- 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';
25
- const USER_AGENT_BROWSER_MAC = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
26
- // URL常量
27
- const OAUTH_CONSUMER_URL = 'https://thegarth.s3.amazonaws.com/oauth_consumer.json';
28
- // HTTP状态码常量
29
- const HTTP_STATUS = {
30
- UNAUTHORIZED: 401
31
- };
32
- // 全局变量
33
- let tokenRefreshPromise = null;
34
- let refreshSubscribers = [];
35
- class HttpClient {
36
- constructor(url, config) {
37
- var _a, _b;
38
- const jar = new tough_cookie_1.CookieJar();
39
- this.url = url;
40
- this.client = (0, axios_cookiejar_support_1.wrapper)(axios_1.default.create({
41
- timeout: (_a = config === null || config === void 0 ? void 0 : config.timeout) !== null && _a !== void 0 ? _a : 5000,
42
- timeoutErrorMessage: `Request Timeout: > ${(_b = config === null || config === void 0 ? void 0 : config.timeout) !== null && _b !== void 0 ? _b : 5000} ms`,
43
- maxRedirects: 10,
44
- validateStatus: function (status) {
45
- return status >= 200 && status < 400;
46
- },
47
- withCredentials: true,
48
- jar: jar
49
- }));
50
- this.config = config;
51
- // 使用新的MFA配置初始化MFAManager
52
- const mfaConfig = config.mfa || {
53
- type: 'file',
54
- dir: config.mfaStorageDir || '/tmp'
55
- };
56
- this.mfaManager = MFAManager_1.MFAManager.getInstance(mfaConfig);
57
- this.setupInterceptors();
58
- }
59
- /**
60
- * 设置请求和响应拦截器
61
- */
62
- setupInterceptors() {
63
- // 响应拦截器
64
- this.client.interceptors.response.use((response) => {
65
- // this.logResponseTracking(response);
66
- return response;
67
- }, async (error) => {
68
- return this.handleResponseError(error);
69
- });
70
- // 请求拦截器
71
- this.client.interceptors.request.use(async (config) => {
72
- if (this.oauth2Token) {
73
- config.headers.Authorization =
74
- 'Bearer ' + this.oauth2Token.access_token;
75
- }
76
- return config;
77
- });
78
- }
79
- /**
80
- * 记录响应跟踪信息
81
- */
82
- logResponseTracking(response) {
83
- var _a, _b, _c, _d;
84
- if (((_a = response.config.url) === null || _a === void 0 ? void 0 : _a.includes('signin')) ||
85
- ((_b = response.config.url) === null || _b === void 0 ? void 0 : _b.includes('verifyMFA'))) {
86
- console.log('> 响应跟踪 - URL:', response.config.url);
87
- console.log('响应跟踪 - 状态码:', response.status);
88
- console.log('响应跟踪 - 最终URL:', ((_c = response.request) === null || _c === void 0 ? void 0 : _c.responseURL) || response.config.url);
89
- console.log('响应跟踪 - 重定向次数:', ((_d = response.request) === null || _d === void 0 ? void 0 : _d.redirectCount) || 0);
90
- if (response.headers.location) {
91
- console.log('响应跟踪 - Location头:', response.headers.location);
92
- }
93
- if (response.status >= 300 && response.status < 400) {
94
- console.log('响应跟踪 - 检测到重定向状态码:', response.status);
95
- }
96
- }
97
- }
98
- /**
99
- * 处理响应错误
100
- */
101
- async handleResponseError(error) {
102
- var _a;
103
- if (axios_1.default.isAxiosError(error) && error.code === 'ECONNABORTED') {
104
- throw new Error(error.message || 'Request Timeout');
105
- }
106
- const originalRequest = error.config;
107
- if (((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) === HTTP_STATUS.UNAUTHORIZED &&
108
- !(originalRequest === null || originalRequest === void 0 ? void 0 : originalRequest._retry)) {
109
- if (!this.oauth2Token) {
110
- throw new Error('No OAuth2 token available');
111
- }
112
- originalRequest._retry = true;
113
- try {
114
- if (!tokenRefreshPromise) {
115
- tokenRefreshPromise = this.refreshOauth2Token().finally(() => {
116
- tokenRefreshPromise = null;
117
- });
118
- }
119
- await tokenRefreshPromise;
120
- originalRequest.headers.Authorization = `Bearer ${this.oauth2Token.access_token}`;
121
- return this.client(originalRequest);
122
- }
123
- catch (err) {
124
- console.error('Token refresh failed:', err);
125
- throw err;
126
- }
127
- }
128
- if (axios_1.default.isAxiosError(error) && error.response) {
129
- this.handleError(error.response);
130
- }
131
- else {
132
- throw new Error('Network error or unknown error occurred');
133
- }
134
- throw error;
135
- }
136
- /**
137
- * 获取OAuth消费者信息
138
- */
139
- async fetchOauthConsumer() {
140
- const response = await axios_1.default.get(OAUTH_CONSUMER_URL);
141
- this.OAUTH_CONSUMER = {
142
- key: response.data.consumer_key,
143
- secret: response.data.consumer_secret
144
- };
145
- }
146
- /**
147
- * 检查令牌有效性
148
- */
149
- async checkTokenVaild() {
150
- if (this.oauth2Token) {
151
- if (this.oauth2Token.expires_at < luxon_1.DateTime.now().toSeconds()) {
152
- console.error('Token expired!');
153
- await this.refreshOauth2Token();
154
- }
155
- }
156
- }
157
- /**
158
- * GET请求
159
- */
160
- async get(url, config) {
161
- const response = await this.client.get(url, config);
162
- return response === null || response === void 0 ? void 0 : response.data;
163
- }
164
- /**
165
- * POST请求
166
- */
167
- async post(url, data, config) {
168
- const response = await this.client.post(url, data, config);
169
- return response === null || response === void 0 ? void 0 : response.data;
170
- }
171
- /**
172
- * PUT请求
173
- */
174
- async put(url, data, config) {
175
- const response = await this.client.put(url, data, config);
176
- return response === null || response === void 0 ? void 0 : response.data;
177
- }
178
- /**
179
- * DELETE请求
180
- */
181
- async delete(url, config) {
182
- const response = await this.client.post(url, null, {
183
- ...config,
184
- headers: {
185
- ...config === null || config === void 0 ? void 0 : config.headers,
186
- 'X-Http-Method-Override': 'DELETE'
187
- }
188
- });
189
- return response === null || response === void 0 ? void 0 : response.data;
190
- }
191
- /**
192
- * 设置通用请求头
193
- */
194
- setCommonHeader(headers) {
195
- lodash_1.default.each(headers, (headerValue, key) => {
196
- this.client.defaults.headers.common[key] = headerValue;
197
- });
198
- }
199
- /**
200
- * 处理错误
201
- */
202
- handleError(response) {
203
- this.handleHttpError(response);
204
- }
205
- /**
206
- * 处理HTTP错误
207
- */
208
- handleHttpError(response) {
209
- const { status, statusText, data } = response;
210
- const errorMessage = {
211
- status,
212
- statusText,
213
- data: typeof data === 'object' ? JSON.stringify(data) : data
214
- };
215
- console.error('HTTP Error:', errorMessage);
216
- throw new Error(`HTTP Error (${status}): ${statusText}`);
217
- }
218
- /**
219
- * 登录到Garmin Connect
220
- * @param username 用户名
221
- * @param password 密码
222
- * @param mfaCallback MFA验证回调函数
223
- * @param sessionId 会话ID,用于分步登录
224
- * @returns Promise<HttpClient>
225
- */
226
- async login(username, password, sessionId) {
227
- try {
228
- // 准备登录
229
- await this.fetchOauthConsumer();
230
- // 获取登录票据
231
- const ticket = await this.getLoginTicket(username, password, sessionId);
232
- // 获取OAuth1令牌
233
- const oauth1 = await this.getOauth1Token(ticket);
234
- // 交换OAuth2令牌
235
- await this.exchange(oauth1);
236
- return this;
237
- }
238
- catch (error) {
239
- console.error('Login failed:', error);
240
- throw error;
241
- }
242
- }
243
- /**
244
- * 获取登录票据
245
- * @param username 用户名
246
- * @param password 密码
247
- * @param mfaCallback MFA验证回调函数
248
- * @param sessionId 会话ID,用于分步登录
249
- * @returns 登录票据
250
- */
251
- async getLoginTicket(username, password, sessionId) {
252
- // 准备登录参数
253
- const loginParams = this.prepareLoginParams();
254
- // 步骤1: 设置cookie
255
- await this.performLoginStep1(loginParams.step1Params);
256
- // 步骤2: 获取CSRF令牌
257
- const csrfToken = await this.performLoginStep2(loginParams.step2Params);
258
- // 步骤3: 提交凭据
259
- let signinResult = await this.performLoginStep3(username, password, csrfToken, loginParams.step3Params);
260
- // 检查账户锁定状态
261
- this.handleAccountLocked(signinResult);
262
- // 检查页面标题,判断是否需要MFA
263
- const pageTitle = this.handlePageTitle(signinResult);
264
- // 如果需要MFA,执行MFA验证
265
- if (this.isMFARequired(pageTitle)) {
266
- // 如果提供了sessionId,则使用分步登录模式
267
- if (sessionId) {
268
- // 等待外部提供验证码
269
- const mfaCode = await this.mfaManager.waitForMFACode(sessionId);
270
- // 使用获取到的验证码完成MFA验证
271
- signinResult = await this.handleMFAWithCode(signinResult, loginParams.step3Params, mfaCode);
272
- }
273
- else {
274
- throw new Error('需要MFA验证,但未提供验证码获取方式');
275
- }
276
- }
277
- // 提取票据
278
- const ticket = this.extractTicket(signinResult);
279
- if (!ticket) {
280
- throw new Error('登录失败(未找到票据或MFA验证失败),请检查用户名和密码');
281
- }
282
- return ticket;
283
- }
284
- /**
285
- * 准备登录参数
286
- */
287
- prepareLoginParams() {
288
- return {
289
- step1Params: {
290
- clientId: 'GarminConnect',
291
- locale: 'en',
292
- service: this.url.GC_MODERN
293
- },
294
- step2Params: {
295
- id: 'gauth-widget',
296
- embedWidget: true,
297
- locale: 'en',
298
- gauthHost: this.url.GARMIN_SSO_EMBED
299
- },
300
- step3Params: {
301
- id: 'gauth-widget',
302
- embedWidget: true,
303
- clientId: 'GarminConnect',
304
- locale: 'en',
305
- gauthHost: this.url.GARMIN_SSO_EMBED,
306
- service: this.url.GARMIN_SSO_EMBED,
307
- source: this.url.GARMIN_SSO_EMBED,
308
- redirectAfterAccountLoginUrl: this.url.GARMIN_SSO_EMBED,
309
- redirectAfterAccountCreationUrl: this.url.GARMIN_SSO_EMBED
310
- }
311
- };
312
- }
313
- /**
314
- * 执行登录步骤1:设置cookie
315
- */
316
- async performLoginStep1(step1Params) {
317
- const step1Url = `${this.url.GARMIN_SSO_EMBED}?${qs_1.default.stringify(step1Params)}`;
318
- await this.client.get(step1Url);
319
- }
320
- /**
321
- * 执行登录步骤2:获取CSRF令牌
322
- */
323
- async performLoginStep2(step2Params) {
324
- const step2Url = `${this.url.SIGNIN_URL}?${qs_1.default.stringify(step2Params)}`;
325
- const step2Result = await this.get(step2Url);
326
- const csrfToken = this.extractCsrfToken(step2Result);
327
- if (!csrfToken) {
328
- throw new Error('登录 - 未找到CSRF令牌');
329
- }
330
- return csrfToken;
331
- }
332
- /**
333
- * 执行登录步骤3:提交凭据
334
- */
335
- async performLoginStep3(username, password, csrfToken, step3Params) {
336
- const step3Url = `${this.url.SIGNIN_URL}?${qs_1.default.stringify(step3Params)}`;
337
- // console.log('🚀 - getLoginTicket - step3Url:', step3Url);
338
- const step3Form = new form_data_1.default();
339
- step3Form.append('username', username);
340
- step3Form.append('password', password);
341
- step3Form.append('embed', 'true');
342
- step3Form.append('_csrf', csrfToken);
343
- return this.post(step3Url, step3Form, {
344
- headers: {
345
- 'Content-Type': 'application/x-www-form-urlencoded',
346
- Dnt: 1,
347
- Origin: this.url.GARMIN_SSO_ORIGIN,
348
- Referer: this.url.SIGNIN_URL,
349
- 'User-Agent': USER_AGENT_CONNECTMOBILE
350
- }
351
- });
352
- }
353
- /**
354
- * 判断是否需要MFA验证
355
- */
356
- isMFARequired(pageTitle) {
357
- return pageTitle.toLowerCase().includes('mfa');
358
- }
359
- /**
360
- * 从响应中提取票据
361
- */
362
- extractTicket(signinResult) {
363
- const ticketRegResult = TICKET_RE.exec(signinResult);
364
- return ticketRegResult ? ticketRegResult[1] : null;
365
- }
366
- /**
367
- * 处理MFA验证(使用直接提供的验证码)
368
- * @param htmlStr HTML响应字符串
369
- * @param signinParams 登录参数
370
- * @param mfaCode MFA验证码
371
- * @returns MFA验证后的响应字符串
372
- */
373
- async handleMFAWithCode(htmlStr, signinParams, mfaCode) {
374
- try {
375
- // 提取CSRF令牌
376
- const csrfToken = this.extractCsrfToken(htmlStr);
377
- if (!csrfToken) {
378
- throw new Error('MFA验证 - 未找到CSRF令牌');
379
- }
380
- // 提交MFA验证码
381
- const mfaResult = await this.submitMFACode(csrfToken, mfaCode, signinParams);
382
- // 验证MFA结果
383
- return this.validateMFAResult(mfaResult);
384
- }
385
- catch (error) {
386
- console.error('MFA验证失败:', error);
387
- throw new Error(`MFA验证失败: ${error}`);
388
- }
389
- }
390
- /**
391
- * 处理MFA验证
392
- * @param htmlStr HTML响应字符串
393
- * @param signinParams 登录参数
394
- * @param mfaCallback MFA验证回调函数
395
- * @returns MFA验证后的响应字符串
396
- */
397
- async handleMFA(htmlStr, signinParams, mfaCallback) {
398
- // 验证MFA回调函数
399
- if (!mfaCallback) {
400
- throw new Error('登录失败(需要MFA验证),请提供MFA回调函数');
401
- }
402
- // 提取CSRF令牌
403
- const csrfToken = this.extractCsrfToken(htmlStr);
404
- // console.log('🚀 - handleMFA - csrfToken:', csrfToken);
405
- if (!csrfToken) {
406
- throw new Error('无法从MFA页面提取CSRF令牌');
407
- }
408
- // 获取MFA验证码
409
- const mfaCode = await mfaCallback();
410
- console.log('🚀 - handleMFA - mfaCode:', mfaCode);
411
- // 提交MFA验证
412
- const mfaResult = await this.submitMFACode(csrfToken, mfaCode, signinParams);
413
- // 验证MFA结果
414
- return this.validateMFAResult(mfaResult);
415
- }
416
- /**
417
- * 提交MFA验证码
418
- */
419
- async submitMFACode(csrfToken, mfaCode, signinParams) {
420
- const SSO = this.url.GARMIN_SSO;
421
- const mfaForm = new form_data_1.default();
422
- mfaForm.append('mfa-code', mfaCode);
423
- mfaForm.append('embed', 'true');
424
- mfaForm.append('_csrf', csrfToken);
425
- return this.post(`${SSO}/verifyMFA/loginEnterMfaCode`, mfaForm, {
426
- params: signinParams,
427
- headers: {
428
- 'Content-Type': 'application/x-www-form-urlencoded',
429
- Dnt: 1,
430
- Origin: this.url.GARMIN_SSO_ORIGIN,
431
- Referer: `${SSO}/signin`,
432
- 'User-Agent': USER_AGENT_BROWSER
433
- },
434
- maxRedirects: 10,
435
- transformResponse: [
436
- function (data, headers) {
437
- if (headers.location &&
438
- headers.location.includes('logintoken')) {
439
- console.log('检测到重定向到包含logintoken的URL:', headers.location);
440
- }
441
- return data;
442
- }
443
- ]
444
- });
445
- }
446
- /**
447
- * 验证MFA结果
448
- */
449
- validateMFAResult(mfaResult) {
450
- // console.log('MFA验证完成:', mfaResult);
451
- const pageTitle = this.handlePageTitle(mfaResult);
452
- console.log('MFA验证后的页面标题:', pageTitle);
453
- return mfaResult;
454
- }
455
- /**
456
- * 从HTML中提取CSRF令牌
457
- * @param html HTML字符串
458
- * @returns CSRF令牌或null
459
- */
460
- extractCsrfToken(html) {
461
- const match = CSRF_RE.exec(html);
462
- return match ? match[1] : null;
463
- }
464
- /**
465
- * 处理页面标题
466
- * @param htmlStr HTML字符串
467
- * @returns 页面标题
468
- */
469
- handlePageTitle(htmlStr) {
470
- const pageTitleRegResult = PAGE_TITLE_RE.exec(htmlStr);
471
- if (pageTitleRegResult) {
472
- const title = pageTitleRegResult[1];
473
- console.log('登录页面标题:', title);
474
- if (lodash_1.default.includes(title, 'Update Phone Number')) {
475
- throw new Error('登录失败(需要更新电话号码),请更新您的电话号码,参考: https://github.com/matin/garth/issues/19');
476
- }
477
- return title;
478
- }
479
- else {
480
- throw new Error('登录失败(未找到页面标题)');
481
- }
482
- }
483
- /**
484
- * 处理账户锁定状态
485
- * @param htmlStr HTML字符串
486
- */
487
- handleAccountLocked(htmlStr) {
488
- const accountLockedRegResult = ACCOUNT_LOCKED_RE.exec(htmlStr);
489
- if (accountLockedRegResult) {
490
- const msg = accountLockedRegResult[1];
491
- console.error(msg);
492
- throw new Error('登录失败(账户已锁定),请打开Connect网页解锁您的账户');
493
- }
494
- }
495
- /**
496
- * 刷新OAuth2令牌
497
- */
498
- async refreshOauth2Token() {
499
- try {
500
- if (!this.OAUTH_CONSUMER) {
501
- await this.fetchOauthConsumer();
502
- }
503
- if (!this.oauth2Token || !this.oauth1Token) {
504
- throw new Error('缺少刷新令牌所需的必要令牌');
505
- }
506
- const oauth1 = {
507
- oauth: this.getOauthClient(this.OAUTH_CONSUMER),
508
- token: this.oauth1Token
509
- };
510
- await this.exchange(oauth1);
511
- console.log(`「${this.config.username}」在「${this.url.domain}」的OAuth2令牌刷新成功`);
512
- }
513
- catch (error) {
514
- console.error('刷新OAuth2令牌失败:', error);
515
- throw error;
516
- }
517
- }
518
- /**
519
- * 获取OAuth1令牌
520
- * @param ticket 登录票据
521
- * @returns OAuth1令牌和客户端
522
- */
523
- async getOauth1Token(ticket) {
524
- if (!this.OAUTH_CONSUMER) {
525
- throw new Error('未找到OAuth消费者信息');
526
- }
527
- const params = {
528
- ticket,
529
- 'login-url': this.url.GARMIN_SSO_EMBED,
530
- 'accepts-mfa-tokens': true
531
- };
532
- const url = `${this.url.OAUTH_URL}/preauthorized?${qs_1.default.stringify(params)}`;
533
- const oauth = this.getOauthClient(this.OAUTH_CONSUMER);
534
- const requestData = {
535
- url: url,
536
- method: 'GET'
537
- };
538
- const headers = oauth.toHeader(oauth.authorize(requestData));
539
- const response = await this.get(url, {
540
- headers: {
541
- ...headers,
542
- 'User-Agent': USER_AGENT_CONNECTMOBILE
543
- }
544
- });
545
- const token = qs_1.default.parse(response);
546
- this.oauth1Token = token;
547
- return { token, oauth };
548
- }
549
- /**
550
- * 获取OAuth客户端
551
- * @param consumer OAuth消费者信息
552
- * @returns OAuth客户端
553
- */
554
- getOauthClient(consumer) {
555
- return new oauth_1_0a_1.default({
556
- consumer: consumer,
557
- signature_method: 'HMAC-SHA1',
558
- hash_function(base_string, key) {
559
- return node_crypto_1.default
560
- .createHmac('sha1', key)
561
- .update(base_string)
562
- .digest('base64');
563
- }
564
- });
565
- }
566
- /**
567
- * 交换OAuth2令牌
568
- * @param oauth1 OAuth1令牌和客户端
569
- */
570
- async exchange(oauth1) {
571
- const token = {
572
- key: oauth1.token.oauth_token,
573
- secret: oauth1.token.oauth_token_secret
574
- };
575
- const baseUrl = `${this.url.OAUTH_URL}/exchange/user/2.0`;
576
- const requestData = {
577
- url: baseUrl,
578
- method: 'POST',
579
- data: null
580
- };
581
- const authData = oauth1.oauth.authorize(requestData, token);
582
- const url = `${baseUrl}?${qs_1.default.stringify(authData)}`;
583
- this.oauth2Token = undefined;
584
- const response = await this.post(url, null, {
585
- headers: {
586
- 'User-Agent': USER_AGENT_CONNECTMOBILE,
587
- 'Content-Type': 'application/x-www-form-urlencoded'
588
- }
589
- });
590
- this.oauth2Token = this.setOauth2TokenExpiresAt(response);
591
- }
592
- /**
593
- * 设置OAuth2令牌过期时间
594
- * @param token OAuth2令牌
595
- * @returns 设置了过期时间的OAuth2令牌
596
- */
597
- setOauth2TokenExpiresAt(token) {
598
- const now = luxon_1.DateTime.now();
599
- const expiresAt = now.plus({ seconds: token.expires_in });
600
- const refreshTokenExpiresAt = now.plus({
601
- seconds: token.refresh_token_expires_in
602
- });
603
- return {
604
- ...token,
605
- last_update_date: now.toLocal().toString(),
606
- expires_date: expiresAt.toLocal().toString(),
607
- expires_at: expiresAt.toSeconds(),
608
- refresh_token_expires_at: refreshTokenExpiresAt.toSeconds()
609
- };
610
- }
611
- }
612
- exports.HttpClient = HttpClient;
613
- //# sourceMappingURL=HttpClient.js.map
1
+ 'use strict';const a2_0x54e484=a2_0xdd09;function a2_0xdd09(_0x31651d,_0x26bcda){_0x31651d=_0x31651d-0xc2;const _0x5758b1=a2_0x5758();let _0xdd09d2=_0x5758b1[_0x31651d];return _0xdd09d2;}(function(_0x72d5c7,_0x2e8ce8){const _0x4b244a=a2_0xdd09,_0xa128a1=_0x72d5c7();while(!![]){try{const _0x59ee34=-parseInt(_0x4b244a(0x11d))/0x1+-parseInt(_0x4b244a(0x106))/0x2*(-parseInt(_0x4b244a(0xdb))/0x3)+parseInt(_0x4b244a(0x146))/0x4+-parseInt(_0x4b244a(0xc6))/0x5*(parseInt(_0x4b244a(0x157))/0x6)+-parseInt(_0x4b244a(0x152))/0x7*(parseInt(_0x4b244a(0x108))/0x8)+-parseInt(_0x4b244a(0xd5))/0x9+parseInt(_0x4b244a(0xfb))/0xa;if(_0x59ee34===_0x2e8ce8)break;else _0xa128a1['push'](_0xa128a1['shift']());}catch(_0x50cdf0){_0xa128a1['push'](_0xa128a1['shift']());}}}(a2_0x5758,0x53430));var __importDefault=this&&this[a2_0x54e484(0xf6)]||function(_0x2d68d3){const _0x3a3d80=a2_0x54e484;return _0x2d68d3&&_0x2d68d3[_0x3a3d80(0x13e)]?_0x2d68d3:{'default':_0x2d68d3};};Object[a2_0x54e484(0xc3)](exports,'__esModule',{'value':!![]}),exports['HttpClient']=void 0x0;const axios_1=__importDefault(require(a2_0x54e484(0x150))),lodash_1=__importDefault(require(a2_0x54e484(0x168))),luxon_1=require(a2_0x54e484(0xd0)),oauth_1_0a_1=__importDefault(require(a2_0x54e484(0x15e))),qs_1=__importDefault(require('qs')),MFAManager_1=require(a2_0x54e484(0xf3)),node_crypto_1=__importDefault(require(a2_0x54e484(0xd2))),tough_cookie_1=require(a2_0x54e484(0x102)),axios_cookiejar_support_1=require(a2_0x54e484(0x109)),CSRF_RE=new RegExp(a2_0x54e484(0x14a)),TICKET_RE=new RegExp('ticket=([^\x22]+)\x22'),ACCOUNT_LOCKED_RE=new RegExp(a2_0x54e484(0x10e)),PAGE_TITLE_RE=new RegExp(a2_0x54e484(0x11c)),USER_AGENT_CONNECTMOBILE=a2_0x54e484(0x11e),USER_AGENT_BROWSER=a2_0x54e484(0x164),USER_AGENT_BROWSER_MAC=a2_0x54e484(0xec),OAUTH_CONSUMER_URL='https://thegarth.s3.amazonaws.com/oauth_consumer.json',HTTP_STATUS={'UNAUTHORIZED':0x191};function a2_0x5758(){const _0x488b4d=['登录失败(账户已锁定),请打开Connect网页解锁您的账户','isAxiosError','response','get','<title>([^<]*)</title>','17320BHToWh','com.garmin.android.apps.connectmobile','/tmp','url','username','checkTokenVaild','redirectCount','login','handleAccountLocked','expires_in','响应跟踪\x20-\x20最终URL:','handleResponseError','includes','HTTP\x20Error:','缺少刷新令牌所需的必要令牌','oauth1Token','fetchOauthConsumer','prepareLoginParams','exchange','oauth','getOauthClient','logintoken','config','」的OAuth2令牌刷新成功','响应跟踪\x20-\x20状态码:','getLoginTicket','logResponseTracking','use','\x20ms','timeout','true','performLoginStep2','Authorization','__esModule','检测到重定向到包含logintoken的URL:','refreshOauth2Token','submitMFACode','consumer_key','status','authorize','HMAC-SHA1','1171532OkLEbY','headers','isMFARequired','update','name=\x22_csrf\x22\x5cs+value=\x22(.+?)\x22','Request\x20Timeout:\x20>\x20','interceptors','mfaManager','toLocal','SIGNIN_URL','axios','Token\x20expired!','7Qprtim','setCommonHeader','MFA验证后的页面标题:','location','/verifyMFA/loginEnterMfaCode','168cfaugt','MFA验证失败:\x20','GarminConnect','响应跟踪\x20-\x20重定向次数:','oauth2Token','exec','响应跟踪\x20-\x20检测到重定向状态码:','oauth-1.0a','message','响应跟踪\x20-\x20Location头:','):\x20','domain','ECONNABORTED','Mozilla/5.0\x20(Windows\x20NT\x2010.0;\x20Win64;\x20x64)\x20AppleWebKit/537.36\x20(KHTML,\x20like\x20Gecko)\x20Chrome/117.0.0.0\x20Safari/537.36','sign','each','stringify','lodash','common','extractCsrfToken','HTTP\x20Error\x20(','登录失败(需要MFA验证),请提供MFA回调函数','GC_MODERN','HttpClient','defineProperty','GARMIN_SSO_ORIGIN','sha1','52665vaHVwJ','defaults','mfa','toString','/signin','_retry','🚀\x20-\x20handleMFA\x20-\x20mfaCode:','toSeconds','oauth_token_secret','access_token','luxon','log','node:crypto','responseURL','MFAManager','3739959YqfRqt','OAUTH_URL','handleError','🚀\x20-\x20getLoginTicket\x20-\x20等待验证码\x20sessionId:','handleMFA','GARMIN_SSO_EMBED','11874hYzZmD','plus','Network\x20error\x20or\x20unknown\x20error\x20occurred','需要MFA验证,但未提供验证码获取方式','Request\x20Timeout','Token\x20refresh\x20failed:','application/x-www-form-urlencoded','step3Params','DateTime','append','performLoginStep1','mfa-code','error','无法从MFA页面提取CSRF令牌','client','Login\x20failed:','No\x20OAuth2\x20token\x20available','Mozilla/5.0\x20(Macintosh;\x20Intel\x20Mac\x20OS\x20X\x2010_15_7)\x20AppleWebKit/537.36\x20(KHTML,\x20like\x20Gecko)\x20Chrome/131.0.0.0\x20Safari/537.36','_csrf','default','parse','data','setupInterceptors','token','./MFAManager','登录页面标题:','request','__importDefault','handlePageTitle','put','>\x20响应跟踪\x20-\x20URL:','登录失败(未找到票据或MFA验证失败),请检查用户名和密码','3498420VRdgzJ','getInstance','DELETE','consumer_secret','performLoginStep3','handleMFAWithCode','handleHttpError','tough-cookie','Update\x20Phone\x20Number','step1Params','Bearer\x20','336KmQGuH','gauth-widget','1910672QAoQMb','axios-cookiejar-support','登录失败(需要更新电话号码),请更新您的电话号码,参考:\x20https://github.com/matin/garth/issues/19','登录失败(未找到页面标题)','validateMFAResult','refresh_token_expires_in','var\x20statuss*=s*\x22([^\x22]*)\x22','signin','/exchange/user/2.0','post','file','extractTicket','OAUTH_CONSUMER','POST','getOauth1Token','object'];a2_0x5758=function(){return _0x488b4d;};return a2_0x5758();}let tokenRefreshPromise=null,refreshSubscribers=[];class HttpClient{constructor(_0x524b70,_0x31ce69){const _0x165f9b=a2_0x54e484;var _0x27f0e2,_0x4bcf54;const _0x207e29=new tough_cookie_1['CookieJar']();this[_0x165f9b(0x120)]=_0x524b70,this[_0x165f9b(0xe9)]=(0x0,axios_cookiejar_support_1['wrapper'])(axios_1[_0x165f9b(0xee)]['create']({'timeout':(_0x27f0e2=_0x31ce69===null||_0x31ce69===void 0x0?void 0x0:_0x31ce69[_0x165f9b(0x13a)])!==null&&_0x27f0e2!==void 0x0?_0x27f0e2:0x1388,'timeoutErrorMessage':_0x165f9b(0x14b)+((_0x4bcf54=_0x31ce69===null||_0x31ce69===void 0x0?void 0x0:_0x31ce69[_0x165f9b(0x13a)])!==null&&_0x4bcf54!==void 0x0?_0x4bcf54:0x1388)+_0x165f9b(0x139),'maxRedirects':0xa,'validateStatus':function(_0x2eace6){return _0x2eace6>=0xc8&&_0x2eace6<0x190;},'withCredentials':!![],'jar':_0x207e29})),this['config']=_0x31ce69;const _0x2c41cf=_0x31ce69[_0x165f9b(0xc8)]||{'type':_0x165f9b(0x112),'dir':_0x31ce69['mfaStorageDir']||_0x165f9b(0x11f)};this[_0x165f9b(0x14d)]=MFAManager_1[_0x165f9b(0xd4)][_0x165f9b(0xfc)](_0x2c41cf),this[_0x165f9b(0xf1)]();}['setupInterceptors'](){const _0x21b752=a2_0x54e484;this[_0x21b752(0xe9)][_0x21b752(0x14c)][_0x21b752(0x11a)][_0x21b752(0x138)](_0x555f24=>{return _0x555f24;},async _0x1810e9=>{const _0x47344b=_0x21b752;return this[_0x47344b(0x128)](_0x1810e9);}),this['client'][_0x21b752(0x14c)]['request']['use'](async _0x4cf74a=>{const _0x4e90b1=_0x21b752;return this[_0x4e90b1(0x15b)]&&(_0x4cf74a[_0x4e90b1(0x147)][_0x4e90b1(0x13d)]=_0x4e90b1(0x105)+this[_0x4e90b1(0x15b)][_0x4e90b1(0xcf)]),_0x4cf74a;});}[a2_0x54e484(0x137)](_0x5d9c68){const _0x1f3a63=a2_0x54e484;var _0x5a75b8,_0x316885,_0x22ce34,_0x3caa67;(((_0x5a75b8=_0x5d9c68[_0x1f3a63(0x133)][_0x1f3a63(0x120)])===null||_0x5a75b8===void 0x0?void 0x0:_0x5a75b8[_0x1f3a63(0x129)](_0x1f3a63(0x10f)))||((_0x316885=_0x5d9c68[_0x1f3a63(0x133)][_0x1f3a63(0x120)])===null||_0x316885===void 0x0?void 0x0:_0x316885[_0x1f3a63(0x129)]('verifyMFA')))&&(console[_0x1f3a63(0xd1)](_0x1f3a63(0xf9),_0x5d9c68[_0x1f3a63(0x133)][_0x1f3a63(0x120)]),console[_0x1f3a63(0xd1)](_0x1f3a63(0x135),_0x5d9c68[_0x1f3a63(0x143)]),console[_0x1f3a63(0xd1)](_0x1f3a63(0x127),((_0x22ce34=_0x5d9c68[_0x1f3a63(0xf5)])===null||_0x22ce34===void 0x0?void 0x0:_0x22ce34[_0x1f3a63(0xd3)])||_0x5d9c68['config'][_0x1f3a63(0x120)]),console[_0x1f3a63(0xd1)](_0x1f3a63(0x15a),((_0x3caa67=_0x5d9c68['request'])===null||_0x3caa67===void 0x0?void 0x0:_0x3caa67[_0x1f3a63(0x123)])||0x0),_0x5d9c68[_0x1f3a63(0x147)][_0x1f3a63(0x155)]&&console[_0x1f3a63(0xd1)](_0x1f3a63(0x160),_0x5d9c68[_0x1f3a63(0x147)][_0x1f3a63(0x155)]),_0x5d9c68[_0x1f3a63(0x143)]>=0x12c&&_0x5d9c68[_0x1f3a63(0x143)]<0x190&&console['log'](_0x1f3a63(0x15d),_0x5d9c68[_0x1f3a63(0x143)]));}async[a2_0x54e484(0x128)](_0x2681bd){const _0x370357=a2_0x54e484;var _0x51c384;if(axios_1['default'][_0x370357(0x119)](_0x2681bd)&&_0x2681bd['code']===_0x370357(0x163))throw new Error(_0x2681bd[_0x370357(0x15f)]||_0x370357(0xdf));const _0x161613=_0x2681bd[_0x370357(0x133)];if(((_0x51c384=_0x2681bd===null||_0x2681bd===void 0x0?void 0x0:_0x2681bd[_0x370357(0x11a)])===null||_0x51c384===void 0x0?void 0x0:_0x51c384[_0x370357(0x143)])===HTTP_STATUS['UNAUTHORIZED']&&!(_0x161613===null||_0x161613===void 0x0?void 0x0:_0x161613[_0x370357(0xcb)])){if(!this[_0x370357(0x15b)])throw new Error(_0x370357(0xeb));_0x161613['_retry']=!![];try{return!tokenRefreshPromise&&(tokenRefreshPromise=this[_0x370357(0x140)]()['finally'](()=>{tokenRefreshPromise=null;})),await tokenRefreshPromise,_0x161613[_0x370357(0x147)]['Authorization']='Bearer\x20'+this[_0x370357(0x15b)][_0x370357(0xcf)],this[_0x370357(0xe9)](_0x161613);}catch(_0x1cf124){console['error'](_0x370357(0xe0),_0x1cf124);throw _0x1cf124;}}if(axios_1['default']['isAxiosError'](_0x2681bd)&&_0x2681bd['response'])this[_0x370357(0xd7)](_0x2681bd[_0x370357(0x11a)]);else throw new Error(_0x370357(0xdd));throw _0x2681bd;}async[a2_0x54e484(0x12d)](){const _0x1a4db0=a2_0x54e484,_0x2b8bc4=await axios_1[_0x1a4db0(0xee)][_0x1a4db0(0x11b)](OAUTH_CONSUMER_URL);this[_0x1a4db0(0x114)]={'key':_0x2b8bc4[_0x1a4db0(0xf0)][_0x1a4db0(0x142)],'secret':_0x2b8bc4['data'][_0x1a4db0(0xfe)]};}async[a2_0x54e484(0x122)](){const _0x389856=a2_0x54e484;this[_0x389856(0x15b)]&&(this[_0x389856(0x15b)]['expires_at']<luxon_1['DateTime']['now']()['toSeconds']()&&(console[_0x389856(0xe7)](_0x389856(0x151)),await this['refreshOauth2Token']()));}async[a2_0x54e484(0x11b)](_0x5bc708,_0x4c8a09){const _0x1ad6de=a2_0x54e484,_0x3b5b7e=await this[_0x1ad6de(0xe9)][_0x1ad6de(0x11b)](_0x5bc708,_0x4c8a09);return _0x3b5b7e===null||_0x3b5b7e===void 0x0?void 0x0:_0x3b5b7e[_0x1ad6de(0xf0)];}async[a2_0x54e484(0x111)](_0x5f53d7,_0x104247,_0xbb2598){const _0x1ed8c0=a2_0x54e484,_0x2fe223=await this[_0x1ed8c0(0xe9)][_0x1ed8c0(0x111)](_0x5f53d7,_0x104247,_0xbb2598);return _0x2fe223===null||_0x2fe223===void 0x0?void 0x0:_0x2fe223[_0x1ed8c0(0xf0)];}async[a2_0x54e484(0xf8)](_0x4ae0a3,_0xc30386,_0x163c84){const _0xe0b32=a2_0x54e484,_0x44edd4=await this[_0xe0b32(0xe9)]['put'](_0x4ae0a3,_0xc30386,_0x163c84);return _0x44edd4===null||_0x44edd4===void 0x0?void 0x0:_0x44edd4[_0xe0b32(0xf0)];}async['delete'](_0x2ead5f,_0x523312){const _0x28cc45=a2_0x54e484,_0x5a4e30=await this['client'][_0x28cc45(0x111)](_0x2ead5f,null,{..._0x523312,'headers':{..._0x523312===null||_0x523312===void 0x0?void 0x0:_0x523312[_0x28cc45(0x147)],'X-Http-Method-Override':_0x28cc45(0xfd)}});return _0x5a4e30===null||_0x5a4e30===void 0x0?void 0x0:_0x5a4e30[_0x28cc45(0xf0)];}[a2_0x54e484(0x153)](_0x5c847e){const _0x54ffda=a2_0x54e484;lodash_1[_0x54ffda(0xee)][_0x54ffda(0x166)](_0x5c847e,(_0x4f561d,_0x21a7b4)=>{const _0x5c65f6=_0x54ffda;this[_0x5c65f6(0xe9)][_0x5c65f6(0xc7)][_0x5c65f6(0x147)][_0x5c65f6(0x169)][_0x21a7b4]=_0x4f561d;});}['handleError'](_0x5b9245){const _0xc27356=a2_0x54e484;this[_0xc27356(0x101)](_0x5b9245);}[a2_0x54e484(0x101)](_0x50da7a){const _0x21887d=a2_0x54e484,{status:_0x4d4e27,statusText:_0x170dc4,data:_0x20eaf4}=_0x50da7a,_0x447f43={'status':_0x4d4e27,'statusText':_0x170dc4,'data':typeof _0x20eaf4===_0x21887d(0x117)?JSON[_0x21887d(0x167)](_0x20eaf4):_0x20eaf4};console[_0x21887d(0xe7)](_0x21887d(0x12a),_0x447f43);throw new Error(_0x21887d(0x16b)+_0x4d4e27+_0x21887d(0x161)+_0x170dc4);}async[a2_0x54e484(0x124)](_0x37cb0b,_0x3add7a,_0x513ccf){const _0x2bf526=a2_0x54e484;try{await this[_0x2bf526(0x12d)]();const _0x2cd1f4=await this['getLoginTicket'](_0x37cb0b,_0x3add7a,_0x513ccf),_0x2416d2=await this['getOauth1Token'](_0x2cd1f4);return await this['exchange'](_0x2416d2),this;}catch(_0x3b2be1){console['error'](_0x2bf526(0xea),_0x3b2be1);throw _0x3b2be1;}}async[a2_0x54e484(0x136)](_0x38f77a,_0xf6ab28,_0x502969){const _0x6a1b39=a2_0x54e484,_0x4fda67=this[_0x6a1b39(0x12e)]();await this[_0x6a1b39(0xe5)](_0x4fda67[_0x6a1b39(0x104)]);const _0x44b85b=await this[_0x6a1b39(0x13c)](_0x4fda67['step2Params']);let _0x114a44=await this[_0x6a1b39(0xff)](_0x38f77a,_0xf6ab28,_0x44b85b,_0x4fda67[_0x6a1b39(0xe2)]);this[_0x6a1b39(0x125)](_0x114a44);const _0x2a6769=this[_0x6a1b39(0xf7)](_0x114a44);if(this['isMFARequired'](_0x2a6769)){if(_0x502969){console[_0x6a1b39(0xd1)](_0x6a1b39(0xd8),_0x502969);const _0x16f8ee=await this[_0x6a1b39(0x14d)]['waitForMFACode'](_0x502969);console[_0x6a1b39(0xd1)]('🚀\x20-\x20getLoginTicket\x20-\x20收到验证码\x20mfaCode:',_0x16f8ee),_0x114a44=await this[_0x6a1b39(0x100)](_0x114a44,_0x4fda67['step3Params'],_0x16f8ee);}else throw new Error(_0x6a1b39(0xde));}const _0xcdf524=this[_0x6a1b39(0x113)](_0x114a44);if(!_0xcdf524)throw new Error(_0x6a1b39(0xfa));return _0xcdf524;}['prepareLoginParams'](){const _0x55e7c4=a2_0x54e484;return{'step1Params':{'clientId':_0x55e7c4(0x159),'locale':'en','service':this[_0x55e7c4(0x120)][_0x55e7c4(0x16d)]},'step2Params':{'id':_0x55e7c4(0x107),'embedWidget':!![],'locale':'en','gauthHost':this[_0x55e7c4(0x120)][_0x55e7c4(0xda)]},'step3Params':{'id':_0x55e7c4(0x107),'embedWidget':!![],'clientId':_0x55e7c4(0x159),'locale':'en','gauthHost':this[_0x55e7c4(0x120)][_0x55e7c4(0xda)],'service':this[_0x55e7c4(0x120)][_0x55e7c4(0xda)],'source':this[_0x55e7c4(0x120)]['GARMIN_SSO_EMBED'],'redirectAfterAccountLoginUrl':this[_0x55e7c4(0x120)]['GARMIN_SSO_EMBED'],'redirectAfterAccountCreationUrl':this[_0x55e7c4(0x120)]['GARMIN_SSO_EMBED']}};}async[a2_0x54e484(0xe5)](_0x1d491b){const _0x45945f=a2_0x54e484,_0x16bd43=this['url'][_0x45945f(0xda)]+'?'+qs_1[_0x45945f(0xee)][_0x45945f(0x167)](_0x1d491b);await this[_0x45945f(0xe9)][_0x45945f(0x11b)](_0x16bd43);}async[a2_0x54e484(0x13c)](_0x41823e){const _0x48ef45=a2_0x54e484,_0xed7341=this[_0x48ef45(0x120)][_0x48ef45(0x14f)]+'?'+qs_1['default'][_0x48ef45(0x167)](_0x41823e),_0x3ce052=await this['get'](_0xed7341),_0x5797f=this['extractCsrfToken'](_0x3ce052);if(!_0x5797f)throw new Error('登录\x20-\x20未找到CSRF令牌');return _0x5797f;}async[a2_0x54e484(0xff)](_0x30de80,_0x1eb9ef,_0x490be1,_0x338a0c){const _0x5b0268=a2_0x54e484,_0x1760dd=this['url'][_0x5b0268(0x14f)]+'?'+qs_1[_0x5b0268(0xee)][_0x5b0268(0x167)](_0x338a0c),_0x1120e2=new URLSearchParams();return _0x1120e2['append'](_0x5b0268(0x121),_0x30de80),_0x1120e2[_0x5b0268(0xe4)]('password',_0x1eb9ef),_0x1120e2['append']('embed',_0x5b0268(0x13b)),_0x1120e2[_0x5b0268(0xe4)](_0x5b0268(0xed),_0x490be1),this[_0x5b0268(0x111)](_0x1760dd,_0x1120e2,{'headers':{'Content-Type':_0x5b0268(0xe1),'Dnt':0x1,'Origin':this[_0x5b0268(0x120)][_0x5b0268(0xc4)],'Referer':this[_0x5b0268(0x120)]['SIGNIN_URL'],'User-Agent':USER_AGENT_CONNECTMOBILE}});}[a2_0x54e484(0x148)](_0x43f112){const _0xf7d7fc=a2_0x54e484,_0x3e86cc=_0x43f112['toLowerCase']();return _0x3e86cc[_0xf7d7fc(0x129)](_0xf7d7fc(0xc8))||_0x3e86cc[_0xf7d7fc(0x129)]('authentication')&&!_0x3e86cc[_0xf7d7fc(0x129)](_0xf7d7fc(0x165));}[a2_0x54e484(0x113)](_0x204ddc){const _0x5f2f97=a2_0x54e484,_0x1075a0=TICKET_RE[_0x5f2f97(0x15c)](_0x204ddc);return _0x1075a0?_0x1075a0[0x1]:null;}async[a2_0x54e484(0x100)](_0x5cd9f3,_0x18fb6b,_0x5bf4b3){const _0x5d272d=a2_0x54e484;try{const _0x1a25ce=this[_0x5d272d(0x16a)](_0x5cd9f3);if(!_0x1a25ce)throw new Error('MFA验证\x20-\x20未找到CSRF令牌');const _0x13e229=await this[_0x5d272d(0x141)](_0x1a25ce,_0x5bf4b3,_0x18fb6b);return this['validateMFAResult'](_0x13e229);}catch(_0x11c2f8){console[_0x5d272d(0xe7)]('MFA验证失败:',_0x11c2f8);throw new Error(_0x5d272d(0x158)+_0x11c2f8);}}async[a2_0x54e484(0xd9)](_0x4728bb,_0x2c8cdd,_0x52c74f){const _0x2a4e2b=a2_0x54e484;if(!_0x52c74f)throw new Error(_0x2a4e2b(0x16c));const _0x58461b=this[_0x2a4e2b(0x16a)](_0x4728bb);if(!_0x58461b)throw new Error(_0x2a4e2b(0xe8));const _0x13637b=await _0x52c74f();console[_0x2a4e2b(0xd1)](_0x2a4e2b(0xcc),_0x13637b);const _0x53e20e=await this[_0x2a4e2b(0x141)](_0x58461b,_0x13637b,_0x2c8cdd);return this[_0x2a4e2b(0x10c)](_0x53e20e);}async[a2_0x54e484(0x141)](_0x4124c9,_0x59b770,_0x15c3bd){const _0x48de43=a2_0x54e484,_0x4f9f4a=this[_0x48de43(0x120)]['GARMIN_SSO'],_0x538eb9=new URLSearchParams();return _0x538eb9[_0x48de43(0xe4)](_0x48de43(0xe6),_0x59b770),_0x538eb9[_0x48de43(0xe4)]('embed',_0x48de43(0x13b)),_0x538eb9[_0x48de43(0xe4)]('_csrf',_0x4124c9),this['post'](_0x4f9f4a+_0x48de43(0x156),_0x538eb9,{'params':_0x15c3bd,'headers':{'Content-Type':_0x48de43(0xe1),'Dnt':0x1,'Origin':this['url'][_0x48de43(0xc4)],'Referer':_0x4f9f4a+_0x48de43(0xca),'User-Agent':USER_AGENT_BROWSER},'maxRedirects':0xa,'transformResponse':[function(_0xe25b42,_0x34b8ce){const _0x40f687=_0x48de43;return _0x34b8ce['location']&&_0x34b8ce[_0x40f687(0x155)][_0x40f687(0x129)](_0x40f687(0x132))&&console['log'](_0x40f687(0x13f),_0x34b8ce[_0x40f687(0x155)]),_0xe25b42;}]});}[a2_0x54e484(0x10c)](_0x3146f1){const _0x47de75=a2_0x54e484,_0x29bdf8=this[_0x47de75(0xf7)](_0x3146f1);return console[_0x47de75(0xd1)](_0x47de75(0x154),_0x29bdf8),_0x3146f1;}[a2_0x54e484(0x16a)](_0x50fb28){const _0x1697d9=a2_0x54e484,_0x39e484=CSRF_RE[_0x1697d9(0x15c)](_0x50fb28);return _0x39e484?_0x39e484[0x1]:null;}['handlePageTitle'](_0x53ad84){const _0x2b261c=a2_0x54e484,_0x1f0a8e=PAGE_TITLE_RE[_0x2b261c(0x15c)](_0x53ad84);if(_0x1f0a8e){const _0xe35281=_0x1f0a8e[0x1];console[_0x2b261c(0xd1)](_0x2b261c(0xf4),_0xe35281);if(lodash_1[_0x2b261c(0xee)][_0x2b261c(0x129)](_0xe35281,_0x2b261c(0x103)))throw new Error(_0x2b261c(0x10a));return _0xe35281;}else throw new Error(_0x2b261c(0x10b));}[a2_0x54e484(0x125)](_0x4e613c){const _0x13b9c3=a2_0x54e484,_0x20716c=ACCOUNT_LOCKED_RE[_0x13b9c3(0x15c)](_0x4e613c);if(_0x20716c){const _0x1c6e2e=_0x20716c[0x1];console[_0x13b9c3(0xe7)](_0x1c6e2e);throw new Error(_0x13b9c3(0x118));}}async['refreshOauth2Token'](){const _0x210355=a2_0x54e484;try{!this['OAUTH_CONSUMER']&&await this[_0x210355(0x12d)]();if(!this[_0x210355(0x15b)]||!this['oauth1Token'])throw new Error(_0x210355(0x12b));const _0x236a16={'oauth':this[_0x210355(0x131)](this[_0x210355(0x114)]),'token':this[_0x210355(0x12c)]};await this['exchange'](_0x236a16),console[_0x210355(0xd1)]('「'+this['config'][_0x210355(0x121)]+'」在「'+this[_0x210355(0x120)][_0x210355(0x162)]+_0x210355(0x134));}catch(_0x4469ae){console[_0x210355(0xe7)]('刷新OAuth2令牌失败:',_0x4469ae);throw _0x4469ae;}}async[a2_0x54e484(0x116)](_0x22abde){const _0xf73833=a2_0x54e484;if(!this['OAUTH_CONSUMER'])throw new Error('未找到OAuth消费者信息');const _0x242133={'ticket':_0x22abde,'login-url':this['url'][_0xf73833(0xda)],'accepts-mfa-tokens':!![]},_0x37f776=this['url'][_0xf73833(0xd6)]+'/preauthorized?'+qs_1[_0xf73833(0xee)][_0xf73833(0x167)](_0x242133),_0x54c4d5=this[_0xf73833(0x131)](this['OAUTH_CONSUMER']),_0x8864ef={'url':_0x37f776,'method':'GET'},_0x4d0ac1=_0x54c4d5['toHeader'](_0x54c4d5[_0xf73833(0x144)](_0x8864ef)),_0x57cb9a=await this[_0xf73833(0x11b)](_0x37f776,{'headers':{..._0x4d0ac1,'User-Agent':USER_AGENT_CONNECTMOBILE}}),_0x5d48da=qs_1[_0xf73833(0xee)][_0xf73833(0xef)](_0x57cb9a);return this[_0xf73833(0x12c)]=_0x5d48da,{'token':_0x5d48da,'oauth':_0x54c4d5};}[a2_0x54e484(0x131)](_0x5e4b88){const _0x4e8819=a2_0x54e484;return new oauth_1_0a_1[(_0x4e8819(0xee))]({'consumer':_0x5e4b88,'signature_method':_0x4e8819(0x145),'hash_function'(_0x3a3ee2,_0x22379a){const _0x2c0585=_0x4e8819;return node_crypto_1[_0x2c0585(0xee)]['createHmac'](_0x2c0585(0xc5),_0x22379a)[_0x2c0585(0x149)](_0x3a3ee2)['digest']('base64');}});}async[a2_0x54e484(0x12f)](_0x134197){const _0x4fcdbb=a2_0x54e484,_0x837956={'key':_0x134197[_0x4fcdbb(0xf2)]['oauth_token'],'secret':_0x134197[_0x4fcdbb(0xf2)][_0x4fcdbb(0xce)]},_0x4177d6=this[_0x4fcdbb(0x120)]['OAUTH_URL']+_0x4fcdbb(0x110),_0x3a7980={'url':_0x4177d6,'method':_0x4fcdbb(0x115),'data':null},_0x2bdcbd=_0x134197[_0x4fcdbb(0x130)][_0x4fcdbb(0x144)](_0x3a7980,_0x837956),_0x56cb45=_0x4177d6+'?'+qs_1['default'][_0x4fcdbb(0x167)](_0x2bdcbd);this[_0x4fcdbb(0x15b)]=undefined;const _0x231ee1=await this['post'](_0x56cb45,null,{'headers':{'User-Agent':USER_AGENT_CONNECTMOBILE,'Content-Type':'application/x-www-form-urlencoded'}});this[_0x4fcdbb(0x15b)]=this['setOauth2TokenExpiresAt'](_0x231ee1);}['setOauth2TokenExpiresAt'](_0x4437d3){const _0x14d4a4=a2_0x54e484,_0x558982=luxon_1[_0x14d4a4(0xe3)]['now'](),_0x39a6a5=_0x558982[_0x14d4a4(0xdc)]({'seconds':_0x4437d3[_0x14d4a4(0x126)]}),_0x318eb1=_0x558982['plus']({'seconds':_0x4437d3[_0x14d4a4(0x10d)]});return{..._0x4437d3,'last_update_date':_0x558982[_0x14d4a4(0x14e)]()[_0x14d4a4(0xc9)](),'expires_date':_0x39a6a5[_0x14d4a4(0x14e)]()[_0x14d4a4(0xc9)](),'expires_at':_0x39a6a5['toSeconds'](),'refresh_token_expires_at':_0x318eb1[_0x14d4a4(0xcd)]()};}}exports[a2_0x54e484(0xc2)]=HttpClient;