§
    X›j¼­  ã                   óR  — U d Z ddlZddlZddlZddlZddlZddlZddlZddlm	Z	 ddl
mZ ddlZ	 ddlmZ dZn# e$ r dZY nw xY w	 ddlmZ dZn# e$ r dZY nw xY wd	d
lmZmZmZ  ej        e¦  «        ZdZdZdZdZdZ dZ!dZ"dZ#dZ$dZ%dZ&dZ'dZ(dZ)e*e+df         e,d<   dZ-e*e+df         e,d<    ej.        d¦  «        Z/ ej.        d¦  «        Z0de1e+e+f         fd „Z2d!Z3d"Z4d#Z5d$Z6d%Z7 G d&„ d'e8¦  «        Z9d(e+de+fd)„Z:d.d*e1e+e+f         dz  de1e+e+f         fd+„Z; G d,„ d-¦  «        Z<dS )/aÂ  Authentication engine for Garmin Connect.

Strategy chain (each strategy is tried in order; only auth errors stop the chain):
1. Mobile iOS + curl_cffi  (TLS fingerprint rotation, no delay needed)
2. Mobile iOS + requests   (plain HTTP fallback)
3. SSO embed widget + cffi (HTML form flow, bypasses clientId rate limits)
4. Portal web + curl_cffi  (TLS fingerprint rotation, 10-20s anti-WAF delay)
5. Portal web + requests   (plain HTTP last resort)
é    N)ÚPath)ÚAny)ÚrequestsTF)Úgenerateé   )Ú GarminConnectAuthenticationErrorÚGarminConnectConnectionErrorÚ!GarminConnectTooManyRequestsErrorÚGCM_IOS_DARKz-https://mobile.integration.garmin.com/gcm/ioszmMozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148ÚGCM_ANDROID_DARKz1https://mobile.integration.garmin.com/gcm/androidzyMozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36ÚGarminConnectzhttps://connect.garmin.com/appzuMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36g      $@g      4@g      @g       @)Ú
safari_iosÚsafariÚ	chrome120.ÚMOBILE_IMPERSONATIONS)r   r   r   Úedge101ÚchromeÚPORTAL_IMPERSONATIONSzname="_csrf"\s+value="(.+?)"z<title>(.+?)</title>Úreturnc                  óŠ   — t           r4t          ¦   «         } t          | j                             ¦   «         ¦  «        S dt
          iS )zŽGenerate a random browser User-Agent + sec-ch-ua headers.

    Falls back to a static desktop Chrome UA if ua_generator is not installed.
    ú
User-Agent)Ú
HAS_UA_GENÚ_generate_uaÚdictÚheadersÚgetÚDESKTOP_USER_AGENT)Úuas    úQ/home/lighthouse/health/venv/lib/python3.11/site-packages/garminconnect/client.pyÚ_random_browser_headersr    ^   s;   € õ
 ð &Ý‰^Œ^ˆÝ�B”J—N’NÑ$Ô$Ñ%Ô%Ð%ØÕ,Ð-Ð-ó    zGCM-Android-5.23zhcom.garmin.android.apps.connectmobile/5.23; ; Google/sdk_gphone64_arm64/google; Android/33; Dalvik/2.1.0z7https://diauth.garmin.com/di-oauth2-service/oauth/tokenzJhttps://connectapi.garmin.com/di-oauth2-service/oauth/grant/service_ticket)Ú'GARMIN_CONNECT_MOBILE_ANDROID_DI_2025Q2Ú'GARMIN_CONNECT_MOBILE_ANDROID_DI_2024Q4Ú GARMIN_CONNECT_MOBILE_ANDROID_DIÚGARMIN_CONNECT_MOBILE_IOS_DIc                   ó   — e Zd ZdZdS )Ú_MFARequireduÒ   Internal sentinel â€” raised by login strategies when MFA is needed.

    Stops the strategy chain immediately (like a credential error).
    The caller (login()) handles it via prompt_mfa / return_on_mfa.
    N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__© r!   r   r'   r'   z   s   € € € € € ðð ð ð r!   r'   Ú	client_idc                 ó~   — dt          j        | › d�                     ¦   «         ¦  «                             ¦   «         z   S )NzBasic ú:)Úbase64Ú	b64encodeÚencodeÚdecode)r-   s    r   Ú_build_basic_authr4   ‚   s6   € Ø•fÔ&¨)   ×'=Ò'=Ñ'?Ô'?Ñ@Ô@×GÒGÑIÔIÑIÐIr!   Úextrac           	      ó^   — t           t          dddddddœ}| r|                     | ¦  «         |S )NÚ10861ÚAndroidÚenÚGC5úen-US,en;q=0.9)r   zX-Garmin-User-AgentzX-Garmin-Paired-App-VersionzX-Garmin-Client-Platformz	X-App-VerzX-LangzX-GCExperienceúAccept-Language)ÚNATIVE_API_USER_AGENTÚNATIVE_X_GARMIN_USER_AGENTÚupdate)r5   r   s     r   Ú_native_headersr@   †   sG   € å+Ý9Ø'.Ø$-ØØØØ+ð	ð 	€Gð ð Ø�Š�uÑÔÐØ€Nr!   c                   óò  — e Zd ZdZd8dededdfd„Zedefd„¦   «         Z	de
eef         fd	„Z	 	 d9dededededeedz  ef         f
d„Zdededdfd„Zdededdfd„Zdedededdfd„Zdededdfd„Zdeddfd„Zdededdfd„Zdededdfd„Zdedededdfd„Zdeddfd„Z	 d:dedededz  ddfd„Zdededefd„Z	 d;dededz  ddfd „Zd<d!„Zd"ededz  fd#„Zdefd$„Zd<d%„Zdefd&„Zd'eddfd(„Zd'eddfd)„Z d*eddfd+„Z!d'ededefd,„Z"d-ed.ed'ededef
d/„Z#d.ed'ededefd0„Z$d.ed'ededefd1„Z%d.ed'ededefd2„Z&d3ededeedz  ef         fd4„Z'd'edede(fd5„Z)de*j+        fd6„Z,d-ed'ededefd7„Z-dS )=ÚClientz,A client to communicate with Garmin Connect.ú
garmin.comÚdomainÚkwargsr   Nc                 ó²  — || _         d|› �| _        d|› �| _        d|› �| _        d|› d�| _        d | _        d | _        d | _        d | _        d | _	        t          j        ¦   «         | _        t          j                             |                     dd¦  «        |                     dd¦  «        ¬¦  «        }| j                             d	|¦  «         d | _        d S )
Nzhttps://sso.zhttps://connect.zhttps://connectapi.z/appÚpool_connectionsé   Úpool_maxsize©rG   rI   úhttps://)rD   Ú_ssoÚ_connectÚ_connectapiÚ_portal_service_urlÚdi_tokenÚdi_refresh_tokenÚdi_client_idÚjwt_webÚ
csrf_tokenr   ÚSessionÚcsÚadaptersÚHTTPAdapterr   ÚmountÚ_tokenstore_path)ÚselfrD   rE   Úadapters       r   Ú__init__zClient.__init__™   së   € ØˆŒØ+ 6Ð+Ð+ˆŒ	Ø3¨6Ð3Ð3ˆŒØ9°Ð9Ð9ˆÔà#B°fÐ#BÐ#BÐ#BˆÔ ð %)ˆŒØ,0ˆÔØ(,ˆÔð $(ˆŒØ&*ˆŒõ  Ô'Ñ)Ô)ˆŒÝÔ#×/Ò/Ø#ŸZšZÐ(:¸BÑ?Ô?ØŸš N°BÑ7Ô7ð 0ñ 
ô 
ˆð 	Œ�Š�j 'Ñ*Ô*Ð*à,0ˆÔÐÐr!   c                 ó8   — t          | j        p| j        ¦  «        S ©N)ÚboolrP   rS   ©r[   s    r   Úis_authenticatedzClient.is_authenticated´   s   € å�D”MÐ1 T¤\Ñ2Ô2Ð2r!   c                 óü   — | j         st          d¦  «        ‚| j        rt          d| j        › �ddœ¦  «        S dd| j        | j        › d�d| j        › �d| j        › �d	œ}| j        rt          | j        ¦  «        |d
<   |S )NzNot authenticatedzBearer úapplication/json)ÚAuthorizationÚAcceptÚNTú/modern/zconnectapi.zJWT_WEB=)rf   ÚNKÚOriginÚRefererz
DI-BackendÚCookiezconnect-csrf-token)	rb   r   rP   r@   rM   rD   rS   rT   Ústr)r[   r   s     r   Úget_api_headerszClient.get_api_headers¸   s»   € ØÔ$ð 	HÝ2Ð3FÑGÔGÐGØŒ=ð 	Ý"à%>¨t¬}Ð%>Ð%>Ø0ðð ñô ð ð )ØØ”mØœ-Ð1Ð1Ð1Ø5¨¬Ð5Ð5Ø/ ¤Ð/Ð/ð#
ð #
ˆð Œ?ð 	AÝ,/°´Ñ,@Ô,@ˆGÐ(Ñ)Øˆr!   FÚemailÚpasswordÚ
prompt_mfaÚreturn_on_mfac                 óš  ‡ ‡‡— dˆˆˆ fd„fdˆˆˆ fd„fdˆˆˆ fd„fdˆˆˆ fd„fd	ˆˆˆ fd
„fg}d}d}|D ]ä\  }}		 t                                d|¦  «          |	¦   «           dS # t          $ r ‚ t          $ r; |rY  dS |r# |¦   «         }
‰                      |
¦  «         Y  dS t          d¦  «        ‚t
          $ r-}t                                d||¦  «         |dz  }|}Y d}~Œ­d}~wt          $ r(}t                                d||¦  «         |}Y d}~ŒÝd}~ww xY w|t          |¦  «        k    rt          d¦  «        ‚t          d|› �¦  «        ‚)u  Log in using a cascading 5-strategy chain (ha-garmin order).

        Tries each strategy in order.  Only credential errors (GarminConnectAuthenticationError)
        and MFA requirements stop the chain immediately â€” all other failures
        (429 rate limits, transport errors, HTML challenges) fall through to
        the next strategy.

        Args:
            email: Garmin account email.
            password: Garmin account password.
            prompt_mfa: Callable that returns an MFA code string when invoked.
            return_on_mfa: When True, return ("needs_mfa", None) instead of
                           calling prompt_mfa; caller must call resume_login().

        Returns:
            (None, None) on success; ("needs_mfa", None) when return_on_mfa=True.

        zmobile+cffic                  ó0   •— ‰                      ‰ ‰¦  «        S r_   )Ú_mobile_login_cffi©ro   rp   r[   s   €€€r   ú<lambda>zClient.login.<locals>.<lambda>é   s   ø€  D×$;Ò$;¸EÀ8Ñ$LÔ$L€ r!   zmobile+requestsc                  ó0   •— ‰                      ‰ ‰¦  «        S r_   )Ú_mobile_login_requestsrv   s   €€€r   rw   zClient.login.<locals>.<lambda>ê   s   ø€ ¨×(CÒ(CÀEÈ8Ñ(TÔ(T€ r!   zwidget+cffic                  ó0   •— ‰                      ‰ ‰¦  «        S r_   )Ú_widget_web_loginrv   s   €€€r   rw   zClient.login.<locals>.<lambda>ë   s   ø€  D×$:Ò$:¸5À(Ñ$KÔ$K€ r!   zportal+cffic                  ó0   •— ‰                      ‰ ‰¦  «        S r_   )Ú_portal_web_login_cffirv   s   €€€r   rw   zClient.login.<locals>.<lambda>ì   s   ø€  D×$?Ò$?ÀÀxÑ$PÔ$P€ r!   zportal+requestsc                  ó0   •— ‰                      ‰ ‰¦  «        S r_   )Ú_portal_web_login_requestsrv   s   €€€r   rw   zClient.login.<locals>.<lambda>ï   s   ø€ ˜×7Ò7¸¸xÑHÔH€ r!   Nr   zTrying login strategy: %s©NN)Ú	needs_mfaNz1MFA Required but no prompt_mfa mechanism suppliedz%s returned 429: %sr   z%s failed: %szRAll login strategies rate limited (429). Try again later or check your IP/network.z All login strategies exhausted: )
Ú_LOGGERÚdebugr   r'   Ú_complete_mfar
   ÚwarningÚ	ExceptionÚlenr	   )r[   ro   rp   rq   rr   Ú
strategiesÚlast_errÚrate_limited_countÚnameÚrunÚmfa_codeÚes   ```         r   ÚloginzClient.loginÏ   s,  øøø€ ð4 ÐLÐLÐLÐLÐLÐLÐMØÐ TÐ TÐ TÐ TÐ TÐ TÐUØÐKÐKÐKÐKÐKÐKÐLØÐPÐPÐPÐPÐPÐPÐQà!ØHÐHÐHÐHÐHÐHðð	-
ˆ
ð &*ˆØÐà#ð 	ð 	‰IˆD�#ðÝ—’Ð9¸4Ñ@Ô@Ð@Ø�‘”�à!�z�zøÝ3ð ð ð àÝð 
ð 
ð 
à ð -Ø,Ð,Ð,Ð,Øð &Ø)˜z™|œ|�HØ×&Ò& xÑ0Ô0Ð0Ø%˜:˜:˜:Ý6ØGñô ð õ 5ð ð ð Ý—’Ð 5°t¸QÑ?Ô?Ð?Ø" aÑ'Ð"Ø�Ø����øøøøÝð ð ð Ý—’ °°qÑ9Ô9Ð9Ø�Ø����øøøøðøøøð
 ¥ Z¡¤Ò0Ð0Ý3ð<ñô ð õ +Ø9¨xÐ9Ð9ñ
ô 
ð 	
s/   º%A"Á"DÁ<!DÂ!DÂ9#C!Ã!DÃ.DÄDc                 óì  — t           st          d¦  «        ‚d}t          D ]Â}	 t                               d|¦  «         t          j        |¬¦  «        }|                      |||¦  «          dS # t          t          f$ r ‚ t          $ r(}t                               d||¦  «         |}Y d}~Œ‹d}~wt          $ r(}t                               d||¦  «         |}Y d}~Œ»d}~ww xY w|r|‚t          d¦  «        ‚)zÊMobile login with curl_cffi TLS fingerprint rotation.

        Different TLS fingerprints land in different Cloudflare rate-limit
        buckets, so rotating through them gives multiple shots.
        úcurl_cffi not availableNz#mobile+cffi trying impersonation=%s©Úimpersonatezmobile+cffi(%s) 429: %szmobile+cffi(%s) failed: %sz(mobile+cffi: no impersonations available)ÚHAS_CFFIr	   r   r‚   rƒ   Úcffi_requestsrU   Ú_do_mobile_loginr   r'   r
   r†   ©r[   ro   rp   r‰   ÚimpÚsessrŽ   s          r   ru   zClient._mobile_login_cffi!  s7  € õ ð 	JÝ.Ð/HÑIÔIÐIØ%)ˆÝ(ð 	ð 	ˆCðÝ—’ÐCÀSÑIÔIÐIÝ)Ô1¸cÐBÑBÔB�Ø×%Ò% d¨E°8Ñ<Ô<Ð<Ø��øÝ4µlÐCð ð ð ØÝ4ð ð ð Ý—’Ð7¸¸aÑ@Ô@Ð@Ø�Ø����øøøøÝð ð ð Ý—’Ð:¸CÀÑCÔCÐCØ�Ø����øøøøðøøøð ð 	ØˆNÝ*Ð+UÑVÔVÐVó$   £AA-Á-C ÂB+Â+C Â8CÃC c                 óZ   — t          j        ¦   «         }|                      |||¦  «         dS )z9Mobile login with plain requests (no TLS fingerprinting).N)r   rU   r–   ©r[   ro   rp   r™   s       r   ry   zClient._mobile_login_requestsB  s.   € åÔÑ!Ô!ˆØ×Ò˜d E¨8Ñ4Ô4Ð4Ð4Ð4r!   r™   c           
      ó’  — | j         › d�}t          dt          dœ}t          dd| j         dœ}|                     |||||ddd	œd
¬¦  «        }|j        dk    rt          d¦  «        ‚	 |                     ¦   «         }n*# t          $ r}	t          d|j        › �¦  «        |	‚d}	~	ww xY w| 
                    di ¦  «         
                    d¦  «        }
|
dk    re| 
                    di ¦  «         
                    dd¦  «        | _        || _        || _        || _        t          | _        d| _        t#          ¦   «         ‚|
dk    r'|d         }|                      ||t          ¬¦  «         dS |
dk    rt'          d¦  «        ‚| 
                    di ¦  «         
                    d¦  «        dk    rt          d¦  «        ‚t          d |› �¦  «        ‚)!z9Login via sso.garmin.com/mobile/api/login (iOS app flow).z/mobile/api/loginúen-US©ÚclientIdÚlocaleÚserviceú!application/json, text/plain, */*rd   )r   rf   úContent-Typerj   TÚ ©Úusernamerp   Ú
rememberMeÚcaptchaTokené   ©Úparamsr   ÚjsonÚtimeouté­  u7   Mobile login returned 429 â€” IP rate limited by Garminz%Mobile login failed (non-JSON): HTTP NÚresponseStatusÚtypeÚMFA_REQUIREDÚcustomerMfaInfoÚmfaLastMethodUsedro   ÚiosÚ
SUCCESSFULÚserviceTicketId©r™   Úservice_urlÚINVALID_USERNAME_PASSWORDú/401 Unauthorized (Invalid Username or Password)Úerrorústatus-codeÚ429zMobile login: 429 in JSON bodyzMobile login failed: )rL   ÚIOS_SSO_CLIENT_IDÚIOS_SERVICE_URLÚIOS_LOGIN_UAÚpostÚstatus_coder
   r­   r†   r	   r   Ú_mfa_methodÚ_mfa_sessionÚ_mfa_login_paramsÚ_mfa_post_headersÚ_mfa_service_urlÚ	_mfa_flowr'   Ú_establish_sessionr   )r[   r™   ro   rp   Ú	login_urlÚlogin_paramsÚlogin_headersÚrÚresÚerrÚ	resp_typeÚtickets               r   r–   zClient._do_mobile_loginK  s.  € à”yÐ3Ð3Ð3ˆ	å)ØÝ&ð
ð 
ˆõ 'Ø9Ø.Ø”ið	
ð 
ˆð �IŠIØØØ!à!Ø$Ø"Ø "ð	ð ð ð ñ 
ô 
ˆð Œ=˜CÒÐÝ3ØIñô ð ð	Ø—&’&‘(”(ˆCˆCøÝð 	ð 	ð 	Ý.ØG¸¼ÐGÐGñô àðøøøøð	øøøð
 —G’GÐ,¨bÑ1Ô1×5Ò5°fÑ=Ô=ˆ	à˜Ò&Ð&Ø"ŸwšwÐ'8¸"Ñ=Ô=×AÒAØ# Wñ ô  ˆDÔð !%ˆDÔØ%1ˆDÔ"Ø%2ˆDÔ"Ý$3ˆDÔ!Ø"ˆDŒNÝ‘.”.Ð à˜Ò$Ð$ØÐ*Ô+ˆFØ×#Ò# F°Å?Ð#ÑSÔSÐSØˆFàÐ3Ò3Ð3Ý2ØAñô ð ð
 �7Š7�7˜BÑÔ×#Ò# MÑ2Ô2°eÒ;Ð;Ý3Ð4TÑUÔUÐUå*Ð+HÀ3Ð+HÐ+HÑIÔIÐIs   Á&A; Á;
B"ÂBÂB"c                 óü  ‡— t           st          d¦  «        ‚t          j        dd¬¦  «        }| j        › d�}|› d�}dd|d	œ}i |¥|||||d
œ¥}|                     ||¬¦  «        }|j        dk    rt          d¦  «        ‚|j        st          d|j        › �¦  «        ‚|                     |› d�|d|i¬¦  «        }|j        dk    rt          d¦  «        ‚t           
                    |j        ¦  «        }	|	st          d¦  «        ‚t          j        t          t          ¦  «        }
t                                d|
¦  «         t%          j        |
¦  «         |                     |› d�|d|j        i||d|	                     d¦  «        dœd¬¦  «        }|j        dk    rt          d¦  «        ‚t.           
                    |j        ¦  «        }|r|                     d¦  «        nd}|                     ¦   «         Št3          ˆfd„dD ¦   «         ¦  «        rt          d|› d�¦  «        ‚t3          ˆfd„dD ¦   «         ¦  «        rt5          d |› d�¦  «        ‚d!|v r8|| _        || _        d|j        i| _        d"| _        || _        tA          ¦   «         ‚|d#k    rt          d$|› d�¦  «        ‚tC          j
        d%|j        ¦  «        }|st          d&¦  «        ‚|  "                    |                     d¦  «        ||¬'¦  «         d(S ))z¤Login via the SSO embed HTML widget.

        Uses HTML form flow which bypasses clientId-based rate limits.
        Uses curl_cffi for TLS fingerprinting.
        r‘   r   rª   )r“   r®   z/ssoz/embedzgauth-widgetÚtrue)ÚidÚembedWidgetÚ	gauthHost)r×   r¢   ÚsourceÚredirectAfterAccountLoginUrlÚredirectAfterAccountCreationUrl)r¬   r¯   zWidget embed GET returned 429zWidget embed returned z/signinrk   )r¬   r   zWidget signin GET returned 429z Widget login: missing CSRF tokenz-Widget login: waiting %.0fs anti-WAF delay...r   )r§   rp   ÚembedÚ_csrf©r¬   r   Údatar®   zWidget signin POST returned 429r¥   c              3   ó    •K  — | ]}|‰v V — Œ	d S r_   r,   ©Ú.0ÚhintÚtitle_lowers     €r   ú	<genexpr>z+Client._widget_web_login.<locals>.<genexpr>Ü  s<   øè è € ð 	
ð 	
àð �KÐð	
ð 	
ð 	
ð 	
ð 	
ð 	
r!   )zbad gatewayzservice unavailableÚ
cloudflareÚ502Ú503zWidget login: server error 'ú'c              3   ó    •K  — | ]}|‰v V — Œ	d S r_   r,   rà   s     €r   rä   z+Client._widget_web_login.<locals>.<genexpr>é  s<   øè è € ð 
ð 
àð �KÐð
ð 
ð 
ð 
ð 
ð 
r!   )ÚlockedÚinvalidÚ	incorrectzaccount errorzWidget authentication failed: 'ÚMFAÚwidgetÚSuccessz Widget login: unexpected title 'úembed\?ticket=([^"]+)"z$Widget login: missing service ticketr¸   N)#r”   r	   r•   rU   rL   r   rÃ   r
   ÚokÚ_CSRF_REÚsearchÚtextÚrandomÚuniformÚWIDGET_DELAY_MIN_SÚWIDGET_DELAY_MAX_Sr‚   rƒ   ÚtimeÚsleeprÂ   ÚurlÚgroupÚ	_TITLE_REÚlowerÚanyr   rÅ   rÆ   rÇ   rÉ   Ú_widget_last_respr'   ÚrerÊ   )r[   ro   rp   r™   Ússo_baseÚ	sso_embedÚembed_paramsÚsignin_paramsrÎ   Ú
csrf_matchÚdelay_sÚtitle_matchÚtitleÚticket_matchrã   s                 @r   r{   zClient._widget_web_login”  sÖ  ø€ õ ð 	JÝ.Ð/HÑIÔIÐIÝ!Ô)°hÈÐKÑKÔKˆØ”iÐ%Ð%Ð%ˆØÐ'Ð'Ð'ˆ	à Ø!Ø!ð
ð 
ˆð

Øð
à"Ø ØØ,5Ø/8ð
ð 
ð 
ˆð �HŠH�Y |ˆHÑ4Ô4ˆØŒ=˜CÒÐÝ3Ð4SÑTÔTÐTØŒtð 	YÝ.Ð/WÈÌÐ/WÐ/WÑXÔXÐXð �HŠHØÐ Ð Ð Ø Ø 	Ð*ð ñ 
ô 
ˆð
 Œ=˜CÒÐÝ3Ð4TÑUÔUÐUå—_’_ Q¤VÑ,Ô,ˆ
Øð 	SÝ.Ð/QÑRÔRÐRõ ”.Õ!3Õ5GÑHÔHˆÝ�ŠÐEÀwÑOÔOÐOÝŒ
�7ÑÔÐð �IŠIØÐ Ð Ð Ø Ø ¤Ð&à!Ø$ØØ#×)Ò)¨!Ñ,Ô,ð	ð ð ð ñ 
ô 
ˆð Œ=˜CÒÐÝ3Ð4UÑVÔVÐVå×&Ò& q¤vÑ.Ô.ˆØ(3Ð;�×!Ò! !Ñ$Ô$Ð$¸ˆð —k’k‘m”mˆÝð 	
ð 	
ð 	
ð 	
ðð	
ñ 	
ô 	
ñ 	
ô 	
ð 
	Xõ /Ð/VÈeÐ/VÐ/VÐ/VÑWÔWÐWõ ð 
ð 
ð 
ð 
àKð
ñ 
ô 
ñ 
ô 
ð 	õ 3Ø:°%Ð:Ð:Ð:ñô ð ð �Eˆ>ˆ>Ø $ˆDÔØ%2ˆDÔ"Ø&/°´Ð%7ˆDÔ"Ø%ˆDŒNØ%&ˆDÔ"Ý‘.”.Ð à�IÒÐÝ.Ø;°5Ð;Ð;Ð;ñô ð õ
 ”yÐ!:¸A¼FÑCÔCˆØð 	WÝ.Ð/UÑVÔVÐVà×Ò × 2Ò 2°1Ñ 5Ô 5¸DÈiÐÑXÔXÐXÐXÐXr!   r�   c           
      ó  — t          | dd¦  «        }t          | dd¦  «        }|r|st          d¦  «        ‚t                               |j        ¦  «        }|st          d¦  «        ‚|                     | j        › d�t          | di ¦  «        t          | di ¦  «        |d	|                     d
¦  «        ddœd¬¦  «        }|j        dk    rt          d¦  «        ‚t                               |j        ¦  «        }|r|                     d
¦  «        nd}|dk    rt          d|› �¦  «        ‚t          j        d|j        ¦  «        }|st          d¦  «        ‚|                      |                     d
¦  «        || j        › d�¬¦  «         dS )zComplete MFA for widget flow.rÅ   Nr   zMissing widget MFA contextzWidget MFA: missing CSRF tokenz /sso/verifyMFA/loginEnterMfaCoderÆ   rÇ   rÔ   r   ÚsetupEnterMfaCode)zmfa-coderÛ   rÜ   ÚfromPagerª   rÝ   r¯   zWidget MFA verify returned 429r¥   rï   zWidget MFA failed: rð   z"Widget MFA: missing service ticketz
/sso/embedr¸   )Úgetattrr   rò   ró   rô   rÂ   rL   rü   rÃ   r
   rý   r  rÊ   )r[   r�   r™   rÎ   r  r  r	  r
  s           r   Ú_complete_mfa_widgetzClient._complete_mfa_widget  sÆ  € å�t˜^¨TÑ2Ô2ˆÝ�DÐ-¨tÑ4Ô4ˆØð 	Q˜1ð 	QÝ2Ð3OÑPÔPÐPå—_’_ Q¤VÑ,Ô,ˆ
Øð 	UÝ2Ð3SÑTÔTÐTà�IŠIØŒyÐ:Ð:Ð:Ý˜4Ð!4°bÑ9Ô9Ý˜DÐ"5°rÑ:Ô:à$ØØ#×)Ò)¨!Ñ,Ô,Ø/ð	ð ð ð ñ 
ô 
ˆð Œ=˜CÒÐÝ3Ð4TÑUÔUÐUå×&Ò& q¤vÑ.Ô.ˆØ(3Ð;�×!Ò! !Ñ$Ô$Ð$¸ˆà�IÒÐÝ2Ð3PÈÐ3PÐ3PÑQÔQÐQå”yÐ!:¸A¼FÑCÔCˆØð 	YÝ2Ð3WÑXÔXÐXà×ÒØ×Ò˜qÑ!Ô!ØØœ9Ð0Ð0Ð0ð 	 ñ 	
ô 	
ð 	
ð 	
ð 	
r!   c                 óì  — t           st          d¦  «        ‚d}t          D ]Â}	 t                               d|¦  «         t          j        |¬¦  «        }|                      |||¦  «          dS # t          t          f$ r ‚ t          $ r(}t                               d||¦  «         |}Y d}~Œ‹d}~wt          $ r(}t                               d||¦  «         |}Y d}~Œ»d}~ww xY w|r|‚t          d¦  «        ‚)zÊPortal login with curl_cffi TLS fingerprint rotation.

        Different TLS fingerprints land in different Cloudflare rate-limit
        buckets, so rotating through them gives multiple shots.
        r‘   Nz#portal+cffi trying impersonation=%sr’   zportal+cffi(%s) 429: %szportal+cffi(%s) failed: %sz(portal+cffi: no impersonations available)r”   r	   r   r‚   rƒ   r•   rU   Ú_do_portal_web_loginr   r'   r
   r†   r—   s          r   r}   zClient._portal_web_login_cffi4  s7  € õ ð 	JÝ.Ð/HÑIÔIÐIØ%)ˆÝ(ð 	ð 	ˆCðÝ—’ÐCÀSÑIÔIÐIÝ)Ô1¸cÐBÑBÔB�Ø×)Ò)¨$°°xÑ@Ô@Ð@Ø��øÝ4µlÐCð ð ð ØÝ4ð ð ð Ý—’Ð7¸¸aÑ@Ô@Ð@Ø�Ø����øøøøÝð ð ð Ý—’Ð:¸CÀÑCÔCÐCØ�Ø����øøøøðøøøð ð 	ØˆNÝ*Ð+UÑVÔVÐVrš   c                 óZ   — t          j        ¦   «         }|                      |||¦  «         dS )z9Portal login with plain requests (no TLS fingerprinting).N)r   rU   r  rœ   s       r   r   z!Client._portal_web_login_requestsU  s.   € åÔÑ!Ô!ˆØ×!Ò! $¨¨xÑ8Ô8Ð8Ð8Ð8r!   c           
      ó  — | j         › d�}t          ¦   «         }|                     |t          | j        dœi |¥dddœ¥d¬¦  «        }|j        dk    rt          d	¦  «        ‚t          j        t          t          ¦  «        }t                               d
|¦  «         t          j        |¦  «         t          d| j        dœ}i |¥ddd| j         |› dt          › d| j        › �dœ¥}	|                     | j         › d�||	||dddœd¬¦  «        }
|
j        dk    rt          d¦  «        ‚	 |
                     ¦   «         }n*# t"          $ r}t%          d|
j        › �¦  «        |‚d}~ww xY w|                     di ¦  «                             d¦  «        }|dk    re|                     di ¦  «                             dd¦  «        | _        || _        || _        |	| _        | j        | _        d | _        t3          ¦   «         ‚|d!k    r'|d"         }|                      ||| j        ¬#¦  «         dS |d$k    rt7          d%¦  «        ‚|                     d&i ¦  «                             d'¦  «        d(k    rt          d)¦  «        ‚t%          d*|› �¦  «        ‚)+u5   Login via /portal/api/login â€” desktop browser flow.z/portal/sso/en-US/sign-in©r    r¢   z?text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8r;   )rf   r<   rª   )r¬   r   r®   r¯   uC   Portal login GET returned 429 â€” Cloudflare blocking this request.z@Portal login: waiting %.0fs to avoid Cloudflare rate limiting...rž   rŸ   r£   rd   z
?clientId=z	&service=)rf   r<   r¤   rj   rk   z/portal/api/loginTr¥   r¦   r«   uD   Portal login POST returned 429 â€” Cloudflare blocking this request.z%Portal login failed (non-JSON): HTTP Nr°   r±   r²   r³   r´   ro   Úportalr¶   r·   r¸   rº   r»   r¼   r½   r¾   zPortal login: 429 in JSON bodyzPortal web login failed: )rL   r    r   ÚPORTAL_SSO_CLIENT_IDrO   rÃ   r
   rõ   rö   ÚLOGIN_DELAY_MIN_SÚLOGIN_DELAY_MAX_Sr‚   Úinforù   rú   rÂ   r­   r†   r	   rÄ   rÅ   rÆ   rÇ   rÈ   rÉ   r'   rÊ   r   )r[   r™   ro   rp   Ú
signin_urlÚbrowser_hdrsÚget_respr  rÌ   Úpost_headersrÎ   rÏ   rÐ   rÑ   rÒ   s                  r   r  zClient._do_portal_web_login^  sV  € àœ	Ð<Ð<Ð<ˆ
Ý.Ñ0Ô0ˆð —8’8Øå0ØÔ3ðð ðØðð Và#3ðð ð ð ð ñ 
ô 
ˆð  Ô 3Ò&Ð&Ý3ØUñô ð õ
 ”.Õ!2Õ4EÑFÔFˆÝ�ŠØNØñ	
ô 	
ð 	
õ 	Œ
�7ÑÔÐõ -ØØÔ/ð
ð 
ˆð


Øð

à9Ø/Ø.Ø”iàð 7ð 7Õ)=ð 7ð 7Ø Ô4ð7ð 7ð

ð 

ð 

ˆð �IŠIØŒyÐ+Ð+Ð+ØØ à!Ø$Ø"Ø "ð	ð ð ð ñ 
ô 
ˆð Œ=˜CÒÐÝ3ØVñô ð ð	Ø—&’&‘(”(ˆCˆCøÝð 	ð 	ð 	Ý.ØG¸¼ÐGÐGñô àðøøøøð	øøøð
 —G’GÐ,¨bÑ1Ô1×5Ò5°fÑ=Ô=ˆ	à˜Ò&Ð&Ø"ŸwšwÐ'8¸"Ñ=Ô=×AÒAØ# Wñ ô  ˆDÔð !%ˆDÔØ%1ˆDÔ"Ø%1ˆDÔ"Ø$(Ô$<ˆDÔ!Ø%ˆDŒNÝ‘.”.Ð à˜Ò$Ð$ØÐ*Ô+ˆFØ×#Ò#Ø˜T¨tÔ/Gð $ñ ô ð ð ˆFàÐ3Ò3Ð3Ý2ØAñô ð ð
 �7Š7�7˜BÑÔ×#Ò# MÑ2Ô2°eÒ;Ð;Ý3Ð4TÑUÔUÐUå*Ð+LÀsÐ+LÐ+LÑMÔMÐMs   Ä#D8 Ä8
EÅEÅEc                 óJ  — t          | dd¦  «        }|dk    r|                      |¦  «         dS | j        }t          | dd¦  «        |dg dd	œ}|d
k    rdn|}| j        › d|› d�| j        | j        fg}|dk    r| j        › d�}t          d| j        dœ}n| j        › d�}t          dt          dœ}| 
                    ||| j        f¦  «         g }	d}
|D �]œ\  }}}	 |                     ||||d¬¦  «        }n1# t          $ r$}|	 
                    |› d|› �¦  «         Y d}~ŒLd}~ww xY w|j        dk    r|	 
                    |› d�¦  «         |
dz  }
Œ}	 |                     ¦   «         }n0# t          $ r# |	 
                    |› d|j        › d�¦  «         Y Œ¿w xY w|                     di ¦  «                             d¦  «        dk    r|	 
                    |› d�¦  «         |
dz  }
�Œ|                     di ¦  «                             d ¦  «        d!k    rF|d"         }|d
k    rt          nt          | d#| j        ¦  «        }|                      |||¬$¦  «          dS |	 
                    |› d%|› �¦  «         �Œž|
t#          |¦  «        k    rt%          d&|	› �¦  «        ‚t'          d'|	› �¦  «        ‚)(uÓ   Complete MFA â€” routes to the handler matching the login flow.

        For portal/ios flows, tries both /portal and /mobile MFA verify
        endpoints as they may be on different rate-limit buckets.
        rÉ   r  rî   NrÄ   ro   TF)Ú	mfaMethodÚmfaVerificationCodeÚrememberMyBrowserÚreconsentListÚmfaSetuprµ   Úmobileú/z/api/mfa/verifyCodez/portal/api/mfa/verifyCoderž   rŸ   z/mobile/api/mfa/verifyCoder   rª   r«   z: connection error r¯   z
: HTTP 429r   z: HTTP z	 non-JSONr¼   r½   r¾   z: 429 in JSON bodyr°   r±   r¶   r·   rÈ   r¸   z: z0MFA verification rate limited on all endpoints: zMFA verification failed: )r  r  rÅ   rL   rÆ   rÇ   r  rO   r¿   rÀ   ÚappendrÂ   r†   rÃ   r­   r   rÊ   r‡   r
   r   )r[   r�   Úflowr™   Úmfa_jsonÚ	flow_pathÚmfa_endpointsÚalt_endpointÚ
alt_paramsÚfailuresrŠ   Úmfa_urlr¬   r   rÎ   rŽ   rÏ   rÒ   Úsvc_urls                      r   r„   zClient._complete_mfaÏ  s›  € õ �t˜[¨(Ñ3Ô3ˆØ�8ÒÐØ×%Ò% hÑ/Ô/Ð/ØˆFàÔ ˆõ !  }°gÑ>Ô>Ø#+Ø!%ØØð$
ð $
ˆð !%¨¢ �H�H°4ˆ	ð ”9Ð=Ð=˜yÐ=Ð=Ð=ØÔ&ØÔ&ððK
ˆð ˜Ò Ð Ø"œiÐCÐCÐCˆLå0Ø!ØÔ3ð*ð *ˆJˆJð #œiÐCÐCÐCˆLå-Ø!Ý*ðð ˆJð
 	×Ò˜l¨J¸Ô8NÐOÑPÔPÐPà ˆØÐà(5ð )	1ñ )	1Ñ$ˆG�V˜Wð
Ø—I’IØØ!Ø#Ø!Øð ñ ô ��øõ ð ð ð Ø—’ 7Ð BÐ B¸qÐ BÐ BÑCÔCÐCØ����øøøøðøøøð Œ} Ò#Ð#Ø—’ 7Ð 6Ð 6Ð 6Ñ7Ô7Ð7Ø" aÑ'Ð"ØðØ—f’f‘h”h��øÝð ð ð à—’ 7Ð KÐ K°1´=Ð KÐ KÐ KÑLÔLÐLØ�ðøøøð
 �wŠw�w Ñ#Ô#×'Ò'¨Ñ6Ô6¸%Ò?Ð?Ø—’ 7Ð >Ð >Ð >Ñ?Ô?Ð?Ø" aÑ'Ð"Ùà�wŠwÐ'¨Ñ,Ô,×0Ò0°Ñ8Ô8¸LÒHÐHØÐ.Ô/�ð ˜u’}�}õ $�Oå  Ð'9¸4Ô;SÑTÔTð ð
 ×'Ò'¨°TÀwÐ'ÑOÔOÐOØ��ð �OŠO˜wÐ/Ð/¨#Ð/Ð/Ñ0Ô0Ð0Ñ0ð ¥ ]Ñ!3Ô!3Ò3Ð3Ý3ØMÀ8ÐMÐMñô ð õ /Ð/UÈ8Ð/UÐ/UÑVÔVÐVs*   ÃC3Ã3
D!Ã=DÄD!ÅE#Å#*FÆFrÒ   r¹   c                 ó†  — 	 |                       ||¬¦  «         dS # t          $ r%}t                               d|¦  «         Y d}~nd}~ww xY w|�|| _        |pt
          }| j                             |d|idd¬¦  «         d}| j        j        j        D ]}|j	        dk    r	|j
        } nŒ|st          d	¦  «        ‚|| _        dS )
ul   Consume a CAS service ticket â€” DI token exchange first,
        fall back to JWT_WEB cookie auth.
        )r¹   Nz6DI token exchange failed (%s), falling back to JWT_WEBrÒ   Trª   ©r¬   Úallow_redirectsr®   ÚJWT_WEBz/JWT_WEB cookie not set after ticket consumption)Ú_exchange_service_ticketr†   r‚   r…   rV   rÀ   r   ÚcookiesÚjarr‹   Úvaluer   rS   )r[   rÒ   r™   r¹   rŽ   ÚsvcrS   Úcs           r   rÊ   zClient._establish_session:  s  € ð	YØ×)Ò)¨&¸kÐ)ÑJÔJÐJØˆFøÝð 	Yð 	Yð 	YÝ�OŠOÐTÐVWÑXÔXÐXÐXÐXÐXÐXÐXøøøøð	Yøøøð ÐØˆDŒGàÐ,�_ˆØŒ�ŠØØ˜fÐ%Ø Øð	 	ñ 	
ô 	
ð 	
ð ˆØ””Ô$ð 	ð 	ˆAØŒv˜Ò"Ð"Øœ'�Ø�ð #ð ð 	Ý2ØAñô ð ð ˆŒˆˆs   ‚ ›
A
¥AÁA
rû   c                 ó\   — t           rt          j        |fddi|¤ŽS t          j        |fi |¤ŽS )z7POST using curl_cffi if available, else plain requests.r“   r   )r”   r•   rÂ   r   )r[   rû   rE   s      r   Ú
_http_postzClient._http_post^  sA   € åð 	KÝ Ô% cÐJÐJ°xÐJÀ6ÐJÐJÐJÝŒ}˜SÐ+Ð+ FÐ+Ð+Ð+r!   c                 ó²  — |pt           }d}d}d}t          D �]}|                      t          t	          t          |¦  «        ddddœ¦  «        ||t          |dœd¬¦  «        }|j        d	k    rt          d
¦  «        ‚|j	        s0t                               d||j        |j        dd…         ¦  «         Œ›	 |                     ¦   «         }	|	d         }|	                     d¦  «        }|                      |¦  «        p|} n4# t           $ r'}
t                               d||
¦  «         Y d}
~
�Œd}
~
ww xY w|st#          d¦  «        ‚|| _        || _        || _        dS )zÄExchange a CAS service ticket for native DI + IT Bearer tokens.

        POST to diauth.garmin.com to get a DI OAuth2 token, then exchange
        for an IT token via services.garmin.com.
        Nz*application/json,text/html;q=0.9,*/*;q=0.8ú!application/x-www-form-urlencodedúno-cache©re   rf   r¤   zCache-Control)r-   Úservice_ticketÚ
grant_typer¹   rª   ©r   rÞ   r®   r¯   zDI token exchange rate limitedz DI exchange failed for %s: %s %séÈ   Úaccess_tokenÚrefresh_tokenz DI token parse failed for %s: %sz+DI token exchange failed for all client IDs)ÚMOBILE_SSO_SERVICE_URLÚDI_CLIENT_IDSr;  ÚDI_TOKEN_URLr@   r4   ÚDI_GRANT_TYPErÃ   r
   rñ   r‚   rƒ   rô   r­   r   Ú_extract_client_id_from_jwtr†   r   rP   rQ   rR   )r[   rÒ   r¹   r/  rP   Ú
di_refreshrR   r-   rÎ   rÞ   rŽ   s              r   r4  zClient._exchange_service_ticketd  s¼  € ð Ð7Õ!7ˆàˆØˆ
Øˆå&ð '	ñ '	ˆIØ—’ÝÝ'å):¸9Ñ)EÔ)EØ"NØ(KØ)3ð	ð ñô ð "+Ø&,Ý"/Ø#*ð	ð ð ð!  ñ ô ˆAð$ Œ} Ò#Ð#Ý7Ø4ñô ð ð ”4ð Ý—’Ø6ØØ”MØ”F˜4˜C˜4”Lñ	ô ð ð ðØ—v’v‘x”x�Ø Ô/�Ø!ŸXšX oÑ6Ô6�
Ø#×?Ò?ÀÑIÔIÐVÈY�Ø�øÝð ð ð Ý—’Ð@À)ÈQÑOÔOÐOØ���‘øøøøðøøøð ð 	Ý2Ø=ñô ð ð !ˆŒØ *ˆÔØ(ˆÔÐÐs   Â3AC=Ã=
D.ÄD)Ä)D.c           
      ó
  — | j         r| j        st          d¦  «        ‚|                      t          t          t          | j        ¦  «        ddddœ¦  «        d| j        | j         dœd¬	¦  «        }|j        s't          d
|j        › d|j	        dd…         › �¦  «        ‚| 
                    ¦   «         }|d         | _        |                     d| j         ¦  «        | _         |                      | j        ¦  «        p| j        | _        dS )z;Refresh the DI Bearer token using the stored refresh token.zNo DI refresh token availablerd   r=  r>  r?  rE  )rA  r-   rE  rª   rB  zDI token refresh failed: ú NrC  rD  )rQ   rR   r   r;  rH  r@   r4   rñ   rÃ   rô   r­   rP   r   rJ  )r[   rÎ   rÞ   s      r   Ú_refresh_di_tokenzClient._refresh_di_token¥  s+  € àÔ$ð 	T¨DÔ,=ð 	TÝ2Ð3RÑSÔSÐSØ�OŠOÝÝ#å%6°tÔ7HÑ%IÔ%IØ0Ø$GØ%/ð	ð ñô ð .Ø!Ô.Ø!%Ô!6ðð ð
 ð ñ 
ô 
ˆð" Œtð 	Ý2ØJ¨A¬MÐJÐJ¸A¼FÀ4ÀCÀ4¼LÐJÐJñô ð ð �vŠv‰xŒxˆØ˜^Ô,ˆŒØ $§¢¨¸$Ô:OÑ PÔ PˆÔà×,Ò,¨T¬]Ñ;Ô;ÐP¸tÔ?Pð 	ÔÐÐr!   Útokenc                 ó„  — 	 |                      d¦  «        }t          |¦  «        dk     rd S |d         dt          |d         ¦  «         dz  z  z   }t          j        t	          j        |¦  «                             ¦   «         ¦  «        }|                     d¦  «        }|rt          |¦  «        nd S # t          $ r Y d S w xY w)Nú.é   r   ú=é   r-   )
Úsplitr‡   r­   Úloadsr0   Úurlsafe_b64decoder3   r   rm   r†   )r[   rO  ÚpartsÚpayload_b64Úpayloadr7  s         r   rJ  z"Client._extract_client_id_from_jwtÅ  s¾   € ð		Ø—K’K Ñ$Ô$ˆEÝ�5‰zŒz˜AŠ~ˆ~Ø�tØ œ( S­S°°q´©]¬]¨N¸QÑ,>Ñ%?Ñ?ˆKÝ”j¥Ô!9¸+Ñ!FÔ!F×!MÒ!MÑ!OÔ!OÑPÔPˆGØ—K’K Ñ,Ô,ˆEØ!&Ð0•3�u‘:”:�:¨DÐ0øÝð 	ð 	ð 	Ø�4�4ð	øøøs   ‚(B1 ¬BB1 Â1
B?Â>B?c                 óT  — | j         p| j        }|sdS 	 dd l}t          |¦  «                             d¦  «        }t          |¦  «        dk    r°|d         dt          |d         ¦  «         dz  z  z   }t          j        t          j	        | 
                    ¦   «         ¦  «                             ¦   «         ¦  «        }|                     d¦  «        }|r*|                     ¦   «         t          |¦  «        d	z
  k    rd
S n*# t          $ r t                               d¦  «         Y nw xY wdS )NFr   rQ  rR  r   rS  rT  Úexpi„  TzFailed to check token expiry)rP   rS   rù   rm   rU  r‡   r­   rV  r0   rW  r2   r3   r   Úintr†   r‚   rƒ   )r[   rO  Ú_timerX  rY  rZ  r\  s          r   Ú_token_expires_soonzClient._token_expires_soonÑ  s'  € Ø”Ð- ¤ˆØð 	Ø�5ð	:Ø Ð Ð Ð å˜‘J”J×$Ò$ SÑ)Ô)ˆEÝ�5‰zŒz˜QŠˆØ# Aœh¨µ°U¸1´X±´°ÀÑ0BÑ)CÑC�Ýœ*ÝÔ,¨[×-?Ò-?Ñ-AÔ-AÑBÔB×IÒIÑKÔKñô �ð —k’k %Ñ(Ô(�Øð  ˜5Ÿ:š:™<œ<­3¨s©8¬8°c©>Ò:Ð:Ø˜4øøÝð 	:ð 	:ð 	:Ý�MŠMÐ8Ñ9Ô9Ð9Ð9Ð9ð	:øøøàˆus   ”C'C> Ã>$D%Ä$D%c                 ó€  — | j         rœ	 |                      ¦   «          | j        rKt          j        t
          ¦  «        5  |                      | j        ¦  «         ddd¦  «         n# 1 swxY w Y   n2# t
          $ r%}t                               d|¦  «         Y d}~nd}~ww xY wdS | j	        sdS 	 | j
                             | j        › d�t          t          dœdd¬¦  «         | j
        j        j        D ]ˆ}|j        dk    r{|j        | _        t                               d	¦  «         | j        rKt          j        t
          ¦  «        5  |                      | j        ¦  «         ddd¦  «         n# 1 swxY w Y    dS Œ‰t          j        t
          ¦  «        5  | j
                             | j        › d
�dd| j        › d�dœd¬¦  «         ddd¦  «         n# 1 swxY w Y   | j
        j        j        D ]}|j        dk    r|j        | _         dS ŒdS # t
          $ r&}t                               d|¦  «         Y d}~dS d}~ww xY w)u@   Refresh auth â€” DI token refresh or legacy JWT_WEB CAS refresh.NzDI token refresh failed: %sz/mobile/sso/en_US/sign-inr  Té   r1  r3  zSession refreshed via CAS TGTz%/services/auth/token/di-oauth/refreshrd   rg   rh   )rf   ri   rk   é
   )r   r®   zRefresh failed: %s)rP   rN  rZ   Ú
contextlibÚsuppressr†   Údumpr‚   rƒ   rb   rV   r   rL   ÚMOBILE_SSO_CLIENT_IDrF  r5  r6  r‹   r7  rS   rÂ   rM   )r[   rÐ   r9  s      r   Ú_refresh_sessionzClient._refresh_sessionå  sf  € àŒ=ð 	ðBØ×&Ò&Ñ(Ô(Ð(ØÔ(ð 9Ý#Ô,­YÑ7Ô7ð 9ð 9ØŸ	š	 $Ô"7Ñ8Ô8Ð8ð9ð 9ð 9ñ 9ô 9ð 9ð 9ð 9ð 9ð 9ð 9øøøð 9ð 9ð 9ð 9øøåð Bð Bð BÝ—’Ð;¸SÑAÔAÐAÐAÐAÐAÐAÐAøøøøðBøøøàˆFð Ô$ð 	ØˆFð"	5ØŒG�KŠKØ”9Ð7Ð7Ð7å 4Ý5ðð ð !%Øð ñ ô ð ð ”W”_Ô(ð ð �Ø”6˜YÒ&Ð&Ø#$¤7�D”LÝ—M’MÐ"AÑBÔBÐBØÔ,ð =Ý'Ô0µÑ;Ô;ð =ð =Ø ŸIšI dÔ&;Ñ<Ô<Ð<ð=ð =ð =ñ =ô =ð =ð =ð =ð =ð =ð =øøøð =ð =ð =ð =à�F�Fð 'õ Ô$¥YÑ/Ô/ð 	ð 	Ø”—’Ø”}ÐKÐKÐKà"4Ø"Ø&*¤mÐ#=Ð#=Ð#=ðð ð
 ð ñ ô ð ð	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	øøøð 	ð 	ð 	ð 	ð ”W”_Ô(ð ð �Ø”6˜YÒ&Ð&Ø#$¤7�D”LØ�E�Eð 'ðð øõ ð 	5ð 	5ð 	5Ý�MŠMÐ.°Ñ4Ô4Ð4Ð4Ð4Ð4Ð4Ð4Ð4øøøøð	5øøøs§   ‰4A0 ½A$ÁA0 Á$A(Á(A0 Á+A(Á,A0 Á0
BÁ:BÂBÂ.BH ÅE,Å H Å,E0	Å0H Å3E0	Å4H Å:H Æ2GÇH ÇGÇH ÇGÇ.H È
H È
H=ÈH8È8H=c                 óT   — | j         | j        | j        dœ}t          j        |¦  «        S )z'Serialize session state to JSON string.)rP   rQ   rR   )rP   rQ   rR   r­   Údumps)r[   rÞ   s     r   ri  zClient.dumps  s3   € ð œØ $Ô 5Ø Ô-ð 
ð  
ˆõ
 Œz˜$ÑÔÐr!   Úpathc                 ó4  — t          |¦  «                             ¦   «         }|                     ¦   «         s|j                             d¦  «        s|dz  }|j                             dd¬¦  «         |                     |                      ¦   «         ¦  «         dS )zWrite tokens safely to disk.ú.jsonúgarmin_tokens.jsonT)ÚparentsÚexist_okN)	r   Ú
expanduserÚis_dirr‹   ÚendswithÚparentÚmkdirÚ
write_textri  )r[   rj  Úps      r   re  zClient.dump!  s�   € å�‰JŒJ×!Ò!Ñ#Ô#ˆØ�8Š8‰:Œ:ð 	)˜QœVŸ_š_¨WÑ5Ô5ð 	)ØÐ(Ñ(ˆAØ	Œ�Š˜t¨dˆÑ3Ô3Ð3Ø	�Š�T—Z’Z‘\”\Ñ"Ô"Ð"Ð"Ð"r!   c                 óV  — 	 || _         t          |¦  «                             ¦   «         }|                     ¦   «         s|j                             d¦  «        s|dz  }|                      |                     ¦   «         ¦  «         d S # t          $ r}t          d|› �¦  «        |‚d }~ww xY w)Nrl  rm  z Token path not loading cleanly: )
rZ   r   rp  rq  r‹   rr  rV  Ú	read_textr†   r	   )r[   rj  rv  rŽ   s       r   ÚloadzClient.load)  sµ   € ð		Ø$(ˆDÔ!Ý�T‘
”
×%Ò%Ñ'Ô'ˆAØ�xŠx‰zŒzð - ¤§¢°Ñ!9Ô!9ð -ØÐ,Ñ,�Ø�JŠJ�q—{’{‘}”}Ñ%Ô%Ð%Ð%Ð%øÝð 	ð 	ð 	Ý.Ø6°1Ð6Ð6ñô àðøøøøð	øøøs   ‚BB Â
B(ÂB#Â#B(Ú
tokenstorec                 óB  — 	 t          j        |¦  «        }|                     d¦  «        | _        |                     d¦  «        | _        |                     d¦  «        | _        | j        st          d¦  «        ‚d S # t          $ r}t          d|› �¦  «        |‚d }~ww xY w)NrP   rQ   rR   zMissing tokens from dict loadz.Token extraction loads() structurally failed: )
r­   rV  r   rP   rQ   rR   rb   r   r†   r	   )r[   rz  rÞ   rŽ   s       r   rV  zClient.loads5  s¸   € ð
	Ý”:˜jÑ)Ô)ˆDØ ŸHšH ZÑ0Ô0ˆDŒMØ$(§H¢HÐ-?Ñ$@Ô$@ˆDÔ!Ø $§¢¨Ñ 8Ô 8ˆDÔØÔ(ð XÝ6Ð7VÑWÔWÐWðXð Xøåð 	ð 	ð 	Ý.ØDÀÐDÐDñô àðøøøøð	øøøs   ‚A8A< Á<
BÂBÂBc                 óD   —  | j         d|fi |¤Ž                     ¦   «         S )NÚGET)Ú_run_requestr­   ©r[   rj  rE   s      r   Ú
connectapizClient.connectapiB  s+   € Ø ˆtÔ  ¨Ð7Ð7°Ð7Ð7×<Ò<Ñ>Ô>Ð>r!   ÚmethodÚ_domainc                 óL   — |                      dd ¦  «          | j        ||fi |¤ŽS )NÚapi)Úpopr~  )r[   r�  r‚  rj  rE   s        r   ÚrequestzClient.requestE  s3   € Ø�
Š
�5˜$ÑÔÐØ ˆtÔ  ¨Ð8Ð8°Ð8Ð8Ð8r!   c                 ó    — |                      dd¦  «        } | j        d|fi |¤Ž}|r&t          |d¦  «        r|                     ¦   «         nd S |S )Nr„  FÚPOSTr­   ©r…  r~  Úhasattrr­   ©r[   r‚  rj  rE   r„  Úresps         r   rÂ   zClient.postI  s`   € Ø�jŠj˜ Ñ&Ô&ˆØ ˆtÔ  ¨Ð8Ð8°Ð8Ð8ˆØð 	BÝ")¨$°Ñ"7Ô"7ÐA�4—9’9‘;”;�;¸TÐAØˆr!   c                 ó    — |                      dd¦  «        } | j        d|fi |¤Ž}|r&t          |d¦  «        r|                     ¦   «         nd S |S )Nr„  FÚPUTr­   r‰  r‹  s         r   Úputz
Client.putP  s`   € Ø�jŠj˜ Ñ&Ô&ˆØ ˆtÔ  ¨Ð7Ð7°Ð7Ð7ˆØð 	BÝ")¨$°Ñ"7Ô"7ÐA�4—9’9‘;”;�;¸TÐAØˆr!   c                 ó    — |                      dd¦  «        } | j        d|fi |¤Ž}|r&t          |d¦  «        r|                     ¦   «         nd S |S )Nr„  FÚDELETEr­   r‰  r‹  s         r   ÚdeletezClient.deleteW  s`   € Ø�jŠj˜ Ñ&Ô&ˆØ ˆtÔ  ¨4Ð:Ð:°6Ð:Ð:ˆØð 	BÝ")¨$°Ñ"7Ô"7ÐA�4—9’9‘;”;�;¸TÐAØˆr!   Ú_client_statec                 ó0   — |                       |¦  «         dS )z*Complete a previously initiated MFA login.r€   )r„   )r[   r“  r�   s      r   Úresume_loginzClient.resume_login^  s   € à×Ò˜8Ñ$Ô$Ð$Øˆzr!   c                 óv   — d|vri |d<   |d                               ddi¦  «          | j        d|fi |¤Žj        S )Nr   rf   z*/*r}  )r?   r~  Úcontentr  s      r   ÚdownloadzClient.downloadc  sU   € Ø˜FÐ"Ð"Ø "ˆF�9ÑØˆyÔ× Ò  (¨EÐ!2Ñ3Ô3Ð3Ø ˆtÔ  ¨Ð7Ð7°Ð7Ð7Ô?Ð?r!   c                 óš   — t          j        ¦   «         }t           j                             dd¬¦  «        }|                     d|¦  «         |S )z8Create a fresh plain requests.Session for each API call.rH   rJ   rK   )r   rU   rW   rX   rY   )r[   r™   r\   s      r   Ú_fresh_api_sessionzClient._fresh_api_sessioni  sE   € åÔÑ!Ô!ˆÝÔ#×/Ò/ÀÐRTÐ/ÑUÔUˆØ�
Š
�:˜wÑ'Ô'Ð'Øˆr!   c                 ó`  — | j         r(|                      ¦   «         r|                      ¦   «          | j        › d|                     d¦  «        › �}d|vrd|d<   |                      ¦   «         }|                     di ¦  «        }|                     |¦  «         |                      ¦   «         } |j	        ||fd|i|¤Ž}|j
        dk    r7|                      ¦   «           |j	        ||fd|                      ¦   «         i|¤Ž}|j
        dk    r G d„ d¦  «        }	 |	¦   «         S |j
        d	k    �rd
|j
        › �}
	 |                     ¦   «         }t          |t          ¦  «        r‰|                     d¦  «        p`|                     d¦  «        pK|                     di ¦  «                             di g¦  «        d                              ddg¦  «        d         }|r	|
d|› �z  }
n|
d|› �z  }
n5# t          $ r( t!          |j        ¦  «        dk     r|
d|j        › �z  }
Y nw xY wt%          |
¦  «        ‚|S )Nr%  r®   ra  r   i‘  éÌ   c                   ó:   — e Zd ZdZdZdefd„Zdefd„Zdefd„Z	dS )ú*Client._run_request.<locals>.EmptyJSONResprœ  r!   r   c                 ó   — i S r_   r,   ra   s    r   r­   z/Client._run_request.<locals>.EmptyJSONResp.jsonŠ  s   € Ø�Ir!   c                 ó   — dS ©Nz{}r,   ra   s    r   Ú__repr__z3Client._run_request.<locals>.EmptyJSONResp.__repr__�  ó   € Ø˜4r!   c                 ó   — dS r¡  r,   ra   s    r   Ú__str__z2Client._run_request.<locals>.EmptyJSONResp.__str__�  r£  r!   N)
r(   r)   r*   rÃ   r—  r   r­   rm   r¢  r¥  r,   r!   r   ÚEmptyJSONResprž  †  so   € € € € € Ø!�Ø�ð #ð ð ð ð ð  cð  ð  ð  ð  ð  Sð  ð  ð  ð  ð  ð  r!   r¦  i�  z
API Error Úmessager—  ÚdetailedImportResultr-  r   Úmessagesr¥   z - iô  )rb   r_  rg  rN   Úlstriprn   r…  r?   rš  r†  rÃ   r­   Ú
isinstancer   r   r†   r‡   rô   r	   )r[   r�  rj  rE   rû   r   Úcustom_headersr™   rŒ  r¦  Ú	error_msgÚ
error_dataÚmsgs                r   r~  zClient._run_requestp  s›  € ØÔ ð 	$ T×%=Ò%=Ñ%?Ô%?ð 	$Ø×!Ò!Ñ#Ô#Ð#àÔ!Ð6Ð6 D§K¢K°Ñ$4Ô$4Ð6Ð6ˆà˜FÐ"Ð"Ø "ˆF�9Ñà×&Ò&Ñ(Ô(ˆØŸš I¨rÑ2Ô2ˆØ�Š�~Ñ&Ô&Ð&à×&Ò&Ñ(Ô(ˆØˆtŒ|˜F CÐCÐC°ÐC¸FÐCÐCˆàÔ˜sÒ"Ð"Ø×!Ò!Ñ#Ô#Ð#Ø�4”< ¨ÐVÐV°T×5IÒ5IÑ5KÔ5KÐVÈvÐVÐVˆDàÔ˜sÒ"Ð"ð ð  ð  ð  ð  ñ  ô  ð  ð !�=‘?”?Ð"àÔ˜sÒ"Ñ"Ø7 TÔ%5Ð7Ð7ˆIð3Ø!ŸYšY™[œ[�
Ý˜j­$Ñ/Ô/ð 8à"Ÿš yÑ1Ô1ð 2Ø%Ÿ>š>¨)Ñ4Ô4ð2à%Ÿ>š>Ð*@À"ÑEÔEßš˜Z¨"¨Ñ.Ô.¨qô2çš˜Z¨"¨Ñ.Ô.¨qô2ð ð ð 8Ø! [¨3 [ [Ñ0˜	˜	à!Ð%7¨:Ð%7Ð%7Ñ7˜	øøÝð 3ð 3ð 3Ý�t”y‘>”> CÒ'Ð'ØÐ!2 t¤yÐ!2Ð!2Ñ2�Iøøð3øøøõ /¨yÑ9Ô9Ð9àˆs   Ä7B2G* Ç*/HÈH)rC   )NFr€   r_   )r   N).r(   r)   r*   r+   rm   r   r]   Úpropertyr`   rb   r   rn   Útupler�   ru   ry   r–   r{   r  r}   r   r  r„   rÊ   r;  r4  rN  rJ  r_  rg  ri  re  ry  rV  r€  r†  rÂ   r�  r’  r•  Úbytesr˜  r   rU   rš  r~  r,   r!   r   rB   rB   –   sõ  € € € € € Ø6Ð6ð1ð 1˜sð 1¸Sð 1ÀTð 1ð 1ð 1ð 1ð6 ð3 $ð 3ð 3ð 3ñ „Xð3ð  c¨3 h¤ð ð ð ð ð6 Ø#ðL
ð L
àðL
ð ðL
ð ð	L
ð
 ðL
ð 
ˆs�T‰z˜3ˆÔ	ðL
ð L
ð L
ð L
ðdW¨ð W°sð W¸tð Wð Wð Wð WðB5¨Cð 5¸3ð 5À4ð 5ð 5ð 5ð 5ðCJ Sð CJ°ð CJÀð CJÈð CJð CJð CJð CJðRoY sð oY°cð oY¸dð oYð oYð oYð oYðb)
¨Sð )
°Tð )
ð )
ð )
ð )
ð^W¨Cð W¸3ð WÀ4ð Wð Wð Wð WðB9°ð 9¸sð 9Àtð 9ð 9ð 9ð 9ðkN¨ð kN°Sð kNÀCð kNÈDð kNð kNð kNð kNðbeW cð eW¨dð eWð eWð eWð eWðX HLð"ð "Øð"Ø!$ð"Ø:=À¹*ð"à	ð"ð "ð "ð "ðH,˜cð ,¨Sð ,°Sð ,ð ,ð ,ð ,ð 6:ð?)ð ?)Øð?)Ø(+¨d©
ð?)à	ð?)ð ?)ð ?)ð ?)ðB
ð 
ð 
ð 
ð@
°ð 
¸¸t¹ð 
ð 
ð 
ð 
ð Tð ð ð ð ð(15ð 15ð 15ð 15ðf �sð  ð  ð  ð  ð#˜ð # ð #ð #ð #ð #ð
˜ð 
 ð 
ð 
ð 
ð 
ð ð ¨ð ð ð ð ð?˜sð ?¨cð ?°cð ?ð ?ð ?ð ?ð9˜cð 9¨Cð 9°sð 9Àcð 9Ècð 9ð 9ð 9ð 9ð˜Cð  sð °cð ¸cð ð ð ð ð˜3ð  cð °Sð ¸Sð ð ð ð ð˜cð ¨ð ¸ð Àð ð ð ð ð¨#ð ¸ð ÀÀsÈTÁzÐSVÀÔAWð ð ð ð ð
@˜Sð @¨Cð @°Eð @ð @ð @ð @ð HÔ$4ð ð ð ð ð: 3ð :¨cð :¸Sð :ÀSð :ð :ð :ð :ð :ð :r!   rB   r_   )=r+   r0   rc  r­   Úloggingrõ   r  rù   Úpathlibr   Útypingr   r   Ú	curl_cffir•   r”   ÚImportErrorÚua_generatorr   r   r   Ú
exceptionsr   r	   r
   Ú	getLoggerr(   r‚   r¿   rÀ   rÁ   rf  rF  ÚMOBILE_SSO_USER_AGENTr  ÚPORTAL_SSO_SERVICE_URLr   r  r  r÷   rø   r   r±  rm   Ú__annotations__r   Úcompilerò   rý   r   r    r=   r>   rH  rI  rG  r†   r'   r4   r@   rB   r,   r!   r   ú<module>r¿     s)  ððð ð ð €€€Ø Ð Ð Ð Ø €€€Ø €€€Ø €€€Ø 	€	€	€	Ø €€€Ø Ð Ð Ð Ð Ð Ø Ð Ð Ð Ð Ð à €€€ðØ3Ð3Ð3Ð3Ð3Ð3à€H€HøØð ð ð Ø€H€H€HðøøøðØ5Ð5Ð5Ð5Ð5Ð5à€J€JøØð ð ð Ø€J€J€Jðøøøðð ð ð ð ð ð ð ð ð ð ˆ'Ô
˜HÑ
%Ô
%€ð #Ð ØA€ð=ð ð *Ð ØLÐ ð,ð ð 'Ð Ø9Ð ð%ð ð Ð ØÐ àÐ ØÐ ð *OÐ �u˜S #˜X”Ð NÐ NÑ Nð*Ð �u˜S #˜X”ð ð ñ ð ˆ2Œ:Ð5Ñ6Ô6€ØˆBŒJÐ.Ñ/Ô/€	ð.  c¨3 h¤ð .ð .ð .ð .ð +Ð ðð ð I€àPð ð€ðð ð ð ð �9ñ ô ð ðJ ð J¨ð Jð Jð Jð Jðð ˜4  S œ>¨DÑ0ð ¸DÀÀcÀ¼Nð ð ð ð ð Tð Tð Tð Tð Tñ Tô Tð Tð Tð Ts!   ±: ºAÁAÁA ÁAÁA