garmin-connect
garmin-connect copied to clipboard
About the userprofile interface
Hi, thanks for your masterpiece.
I found on connect.garmin.com the userprofile interface is like https://connect.garmin.com/modern/proxy/userprofile-service/userprofile/personal-information/userhash
rather than that in this project /currentuser-service/user/info
. Could you tell me why?
Looking forward for your reply
Hi, thanks!
The /currentuser-service/
is providing the userhash during login and is then stored within the client itself for future use. The /personal-information/
endpoint seems to be providing personal information only and not the user hash that the client is looking for.
The reason that your code is failing to run might be that you've made too many requests and/or login attempts. Garmin seems to have added some very strict rules for their users lately. Wait a bit, try to run it again and let me know if it still doesn't work.
Hi, thanks! The
/currentuser-service/
is providing the userhash during login and is then stored within the client itself for future use. The/personal-information/
endpoint seems to be providing personal information only and not the user hash that the client is looking for. The reason that your code is failing to run might be that you've made too many requests and/or login attempts. Garmin seems to have added some very strict rules for their users lately. Wait a bit, try to run it again and let me know if it still doesn't work.
Thanks for your reply. Recently, I was making an application for desktop aiming to sync my activity data between global server and that in China mainland. Your code is clean and elegant. I would contribute some features later , is it ok
I will always get an error after running it in my test file.
Unhandled rejection StatusCodeError: 500 - "<!DOCTYPE html>\n<html\n\t\txmlns=\"http://www.w3.org/1999/xhtml\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>Garmin</title>\n\t\t<meta name=\"description\" content=\"\" />\n\t\t<meta name=\"keywords\" content=\"\" />\n\t\t<meta name=\"format-detection\" content=\"telephone=no\" />\n\t\t\n\t\t<link rel=\"stylesheet\" type=\"text/css\" href=\"/sso/css/error/error.css?20170505\" />\n\t</head>\n\t<body>\n\t\t<div>\n\t\t\t<h2>500 Server error</h2>\n\t\t</div>\n\t</body>\n</html>\n\n"
at new StatusCodeError (/Users/xxxxxx/WebstormProjects/garmin-connect/node_modules/request-promise-core/lib/errors.js:32:15)
at Request.plumbing.callback (/Users/xxxxxx/WebstormProjects/garmin-connect/node_modules/request-promise-core/lib/plumbing.js:104:33)
at Request.RP$callback [as _callback] (/Users/xxxxxx/WebstormProjects/garmin-connect/node_modules/request-promise-core/lib/plumbing.js:46:31)
at self.callback (/Users/xxxxxx/WebstormProjects/garmin-connect/node_modules/request/request.js:185:22)
at onRequestComplete (/Users/xxxxxx/WebstormProjects/garmin-connect/node_modules/cloudscraper/index.js:629:3)
at onCloudflareResponse (/Users/xxxxxx/WebstormProjects/garmin-connect/node_modules/cloudscraper/index.js:251:3)
at onRequestResponse (/Users/xxxxxx/WebstormProjects/garmin-connect/node_modules/cloudscraper/index.js:205:5)
at Request.<anonymous> (/Users/xxxxxx/WebstormProjects/garmin-connect/node_modules/cloudscraper/index.js:149:7)
at Object.onceWrapper (events.js:520:26)
at Request.emit (events.js:400:28)
at Request.<anonymous> (/Users/xxxxxx/WebstormProjects/garmin-connect/node_modules/request/request.js:1154:10)
at Request.emit (events.js:400:28)
at IncomingMessage.<anonymous> (/Users/xxxxxx/WebstormProjects/garmin-connect/node_modules/request/request.js:1076:12)
at Object.onceWrapper (events.js:519:28)
at IncomingMessage.emit (events.js:412:35)
at endReadableNT (internal/streams/readable.js:1334:12)
at processTicksAndRejections (internal/process/task_queues.js:82:21)
(node:31668) UnhandledPromiseRejectionWarning: TypeError: Cannot destructure property 'body' of '(intermediate value)' as it is undefined.
at CFClient.get (/Users/xxxxxx/WebstormProjects/garmin-connect/src/common/CFClient.js:46:17)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at async GarminConnect.login (/Users/xxxxxx/WebstormProjects/garmin-connect/src/garmin/GarminConnect.js:60:33)
at async main (/Users/xxxxxx/WebstormProjects/garmin-connect/src/lab.js:10:5)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:31668) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:31668) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Unhandled rejection RequestError: Error: write EPROTO 4376102272:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../deps/openssl/openssl/ssl/record/rec_layer_s3.c:1544:SSL alert number 40
at onRequestResponse (/Users/xxxxxx/WebstormProjects/garmin-connect/node_modules/cloudscraper/index.js:165:21)
at Request.<anonymous> (/Users/xxxxxx/WebstormProjects/garmin-connect/node_modules/cloudscraper/index.js:144:7)
at Object.onceWrapper (events.js:520:26)
at Request.emit (events.js:400:28)
at Request.onRequestError (/Users/xxxxxx/WebstormProjects/garmin-connect/node_modules/request/request.js:877:8)
at ClientRequest.emit (events.js:400:28)
at TLSSocket.socketErrorListener (_http_client.js:475:9)
at TLSSocket.emit (events.js:400:28)
at emitErrorNT (internal/streams/destroy.js:106:8)
at emitErrorCloseNT (internal/streams/destroy.js:74:3)
at processTicksAndRejections (internal/process/task_queues.js:82:21)
Maybe this site is not used any more
Are u using this project currently?
I am using this library on a daily basis, and it seems to work for me. Interesting. Are you a newly registered user? Just thinking that there exists an 'old backend' that did handle (and still does) 'old users' but new users are required to go through another endpoint. You're clearly getting a 500, which should indicate that something is up on Garmin's side. Usually you'll end up with 403 or some garbage plain text/html if authentication fails.
You can record a network log of all requests in a browser when you're signing in to your account and then backtrack and compare those to whats happening within the library.
Also found this https://github.com/axios/axios/issues/658 That might be related to the issues you're having.
I am using this library on a daily basis, and it seems to work for me. Interesting. Are you a newly registered user? Just thinking that there exists an 'old backend' that did handle (and still does) 'old users' but new users are required to go through another endpoint. You're clearly getting a 500, which should indicate that something is up on Garmin's side. Usually you'll end up with 403 or some garbage plain text/html if authentication fails.
You can record a network log of all requests in a browser when you're signing in to your account and then backtrack and compare those to whats happening within the library.
Thanks for your reply. I forked your project and rewrote the login method with the procedure from another repo garminexport. Thanks for your work.
Are you a newly registered user?
I have two accounts. One is under garmin.cn, registered in April,2019. And the other is under garmin.com, registered in Dec,2021. For Chinese mainland domain, I replaced all garmin.com
with garmin.cn
when access GC_CONNECT_MODERN etc. I don't know where was wrong.
I have tried the repo garminexport, and it worked for me. I found the procedure was a little different from that you used. That repo's login authentication will do like below.
- Get sso.garmin.com/sso/signin as a html text
- Use a regular expression to search the csrf token from text in 1 and add the csrf token as _csrf kv in data posted later
- Post and retrieve the response as html
- Use a regular expression to search the auth_ticket from 3
- Get the auth_ticket url(claim the ticket)
- Get connect.garmin.com/modern to finish the login process.
Hi, I have rewritten some scrapper to axios. But I always got 403 Forbidden and 1020 Cloudfare. After searching , I found some guys who used python ecountered the same issue. Someone say don't use tls1.3. But how can I ? If you have any idea , please tell me. Thanks. The code resides here https://github.com/Likenttt/garmin-connect
/Users/kent/.nvm/versions/node/v14.18.2/bin/node /Users/kent/WebstormProjects/garmin-connect/src/example.js
Error: Request failed with status code 403
at createError (/Users/kent/WebstormProjects/garmin-connect/node_modules/axios/lib/core/createError.js:16:15)
at settle (/Users/kent/WebstormProjects/garmin-connect/node_modules/axios/lib/core/settle.js:17:12)
at IncomingMessage.handleStreamEnd (/Users/kent/WebstormProjects/garmin-connect/node_modules/axios/lib/adapters/http.js:293:11)
at IncomingMessage.emit (events.js:412:35)
at endReadableNT (internal/streams/readable.js:1334:12)
at processTicksAndRejections (internal/process/task_queues.js:82:21) {
config: {
transitional: {
silentJSONParsing: true,
forcedJSONParsing: true,
clarifyTimeoutError: false
},
adapter: [Function: httpAdapter],
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
validateStatus: [Function: validateStatus],
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36',
origin: 'https://sso.garmin.com',
nk: 'NT',
Cookie: [RequestJar],
'Content-Length': 200
},
params: {
service: 'https://connect.garmin.com/modern',
gauthHost: 'https://sso.garmin.com/sso'
},
method: 'post',
url: 'https://sso.garmin.com/sso/signin',
data: '{"username":"[email protected]","password":"xxxxx","embed":"false","_csrf":"9E0C98E516A1346DB7239FF382C3028824D5B6CC1B476EF268E2464AC5DC8947DCD73491E3E05C4A3FC5F282319B07AD2B90","rememberme":"on"}'
},
request: <ref *1> ClientRequest {
_events: [Object: null prototype] {
abort: [Function (anonymous)],
aborted: [Function (anonymous)],
connect: [Function (anonymous)],
error: [Function (anonymous)],
socket: [Function (anonymous)],
timeout: [Function (anonymous)],
prefinish: [Function: requestOnPrefinish]
},
_eventsCount: 7,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: TLSSocket {
_tlsOptions: [Object],
_secureEstablished: true,
_securePending: false,
_newSessionPending: false,
_controlReleased: true,
secureConnecting: false,
_SNICallback: null,
servername: 'sso.garmin.com',
alpnProtocol: false,
authorized: true,
authorizationError: null,
encrypted: true,
_events: [Object: null prototype],
_eventsCount: 10,
connecting: false,
_hadError: false,
_parent: null,
_host: 'sso.garmin.com',
_readableState: [ReadableState],
_maxListeners: undefined,
_writableState: [WritableState],
allowHalfOpen: false,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: undefined,
_server: null,
ssl: [TLSWrap],
_requestCert: true,
_rejectUnauthorized: true,
parser: null,
_httpMessage: [Circular *1],
[Symbol(res)]: [TLSWrap],
[Symbol(verified)]: true,
[Symbol(pendingSession)]: null,
[Symbol(async_id_symbol)]: 75,
[Symbol(kHandle)]: [TLSWrap],
[Symbol(kSetNoDelay)]: false,
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: null,
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0,
[Symbol(connect-options)]: [Object],
[Symbol(RequestTimeout)]: undefined
},
_header: 'POST /sso/signin?service=https:%2F%2Fconnect.garmin.com%2Fmodern&gauthHost=https:%2F%2Fsso.garmin.com%2Fsso HTTP/1.1\r\n' +
'Accept: application/json, text/plain, */*\r\n' +
'Content-Type: application/json\r\n' +
'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36\r\n' +
'origin: https://sso.garmin.com\r\n' +
'nk: NT\r\n' +
'Cookie: [object Object]\r\n' +
'Content-Length: 200\r\n' +
'Host: sso.garmin.com\r\n' +
'Connection: close\r\n' +
'\r\n',
_keepAliveTimeout: 0,
_onPendingData: [Function: noopPendingOutput],
agent: Agent {
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
defaultPort: 443,
protocol: 'https:',
options: [Object],
requests: {},
sockets: [Object],
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256,
scheduling: 'lifo',
maxTotalSockets: Infinity,
totalSocketCount: 1,
maxCachedSessions: 100,
_sessionCache: [Object],
[Symbol(kCapture)]: false
},
socketPath: undefined,
method: 'POST',
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
path: '/sso/signin?service=https:%2F%2Fconnect.garmin.com%2Fmodern&gauthHost=https:%2F%2Fsso.garmin.com%2Fsso',
_ended: true,
res: IncomingMessage {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 3,
_maxListeners: undefined,
socket: [TLSSocket],
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
headers: [Object],
rawHeaders: [Array],
trailers: {},
rawTrailers: [],
aborted: false,
upgrade: false,
url: '',
method: null,
statusCode: 403,
statusMessage: 'Forbidden',
client: [TLSSocket],
_consuming: false,
_dumped: false,
req: [Circular *1],
responseUrl: 'https://sso.garmin.com/sso/signin?service=https:%2F%2Fconnect.garmin.com%2Fmodern&gauthHost=https:%2F%2Fsso.garmin.com%2Fsso',
redirects: [],
[Symbol(kCapture)]: false,
[Symbol(RequestTimeout)]: undefined
},
aborted: false,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
reusedSocket: false,
host: 'sso.garmin.com',
protocol: 'https:',
_redirectable: Writable {
_writableState: [WritableState],
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_options: [Object],
_ended: true,
_ending: true,
_redirectCount: 0,
_redirects: [],
_requestBodyLength: 200,
_requestBodyBuffers: [],
_onNativeResponse: [Function (anonymous)],
_currentRequest: [Circular *1],
_currentUrl: 'https://sso.garmin.com/sso/signin?service=https:%2F%2Fconnect.garmin.com%2Fmodern&gauthHost=https:%2F%2Fsso.garmin.com%2Fsso',
[Symbol(kCapture)]: false
},
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype] {
accept: [Array],
'content-type': [Array],
'user-agent': [Array],
origin: [Array],
nk: [Array],
cookie: [Array],
'content-length': [Array],
host: [Array]
}
},
response: {
status: 403,
statusText: 'Forbidden',
headers: {
date: 'Fri, 07 Jan 2022 16:00:17 GMT',
'content-type': 'text/plain; charset=UTF-8',
'content-length': '16',
connection: 'close',
'x-frame-options': 'SAMEORIGIN',
'referrer-policy': 'same-origin',
'cache-control': 'private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0',
expires: 'Thu, 01 Jan 1970 00:00:01 GMT',
'expect-ct': 'max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"',
'report-to': '{"endpoints":[{"url":"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=1i3E%2FkD9M6vCSNS2HwIQdFxF%2BFr%2BuUKAMcBfnvMP8PmaXrrV6ZoT1QfBdGKPSgZ46ZSEVAuMJKkYHBxoX4MK7EHkQTl2eHpCCvqjv3eWyGQJPSQPG7LHrpKzIpv0xbAS"}],"group":"cf-nel","max_age":604800}',
nel: '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}',
server: 'cloudflare',
'cf-ray': '6c9e65f03f266453-SJC'
},
config: {
transitional: [Object],
adapter: [Function: httpAdapter],
transformRequest: [Array],
transformResponse: [Array],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
validateStatus: [Function: validateStatus],
headers: [Object],
params: [Object],
method: 'post',
url: 'https://sso.garmin.com/sso/signin',
data: '{"username":"[email protected]","password":"xxxxxx","embed":"false","_csrf":"9E0C98C16A1346DB7239FF382C3028824D5B6CC1B476EF268E2464AC5DC8947DCD73491E3E05C4A3FC5F282319B07AD2B90","rememberme":"on"}'
},
request: <ref *1> ClientRequest {
_events: [Object: null prototype],
_eventsCount: 7,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: [TLSSocket],
_header: 'POST /sso/signin?service=https:%2F%2Fconnect.garmin.com%2Fmodern&gauthHost=https:%2F%2Fsso.garmin.com%2Fsso HTTP/1.1\r\n' +
'Accept: application/json, text/plain, */*\r\n' +
'Content-Type: application/json\r\n' +
'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36\r\n' +
'origin: https://sso.garmin.com\r\n' +
'nk: NT\r\n' +
'Cookie: [object Object]\r\n' +
'Content-Length: 200\r\n' +
'Host: sso.garmin.com\r\n' +
'Connection: close\r\n' +
'\r\n',
_keepAliveTimeout: 0,
_onPendingData: [Function: noopPendingOutput],
agent: [Agent],
socketPath: undefined,
method: 'POST',
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
path: '/sso/signin?service=https:%2F%2Fconnect.garmin.com%2Fmodern&gauthHost=https:%2F%2Fsso.garmin.com%2Fsso',
_ended: true,
res: [IncomingMessage],
aborted: false,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
reusedSocket: false,
host: 'sso.garmin.com',
protocol: 'https:',
_redirectable: [Writable],
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype]
},
data: 'error code: 1020'
},
isAxiosError: true,
toJSON: [Function: toJSON]
}
(node:84480) UnhandledPromiseRejectionWarning: Error: Auth failure: failed to post https://sso.garmin.com/sso/signin
at /Users/kent/WebstormProjects/garmin-connect/src/common/CFClient.js:121:23
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at async CFClient.postJson (/Users/kent/WebstormProjects/garmin-connect/src/common/CFClient.js:115:13)
at async GarminConnect.login (/Users/kent/WebstormProjects/garmin-connect/src/garmin/GarminConnect.js:90:26)
at async main (/Users/kent/WebstormProjects/garmin-connect/src/example.js:9:5)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:84480) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:84480) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Process finished with exit code 0