§
    ¬	Êjº�  ã                  ó
  — d Z ddlm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
Z
ddlZddlZddlmZ ddlZddlZddlZddlmZmZ ddlmZmZmZmZmZmZmZmZmZmZm Z m!Z!m"Z" dRd
„Z#dSd„Z$dTd„Z%dUdVd„Z&dUdWd„Z'dXd„Z(dZ)dYd„Z*dZ+dZ,dZd„Z-d Z.d!Z/ e0h d"£¦  «        Z1d#Z2ej3        Z4d[d&„Z5d\d*„Z6d]d,„Z7e.fd^d0„Z8d1de.fd_d4„Z9d`dad5„Z:dbd7„Z;dcd:„Z<ddd<„Z=dedB„Z>dfdgdE„Z?dhddFœdidG„Z@dUdjdI„ZAdJ„ ZBdkdL„ZCdUdMdNœdldP„ZDddQlEmFZFmGZG dS )mzCDownload and cache the patched Firefox binary from GitHub Releases.é    )ÚannotationsN)ÚPathé   )ÚBINARY_ENTRY_RELÚRELEASE_URL_TEMPLATE)ÚAssetÚEngineMismatchÚSUPPORTED_LEGSÚSUPPORTED_PLATFORMSÚSealÚ	SealErrorÚSealMismatchÚactive_sealÚread_engine_identityÚ
read_stampÚresource_rootÚverify_engineÚwrite_stampÚreturnú
str | Nonec                 ó~   — t           j                             d¦  «        pt           j                             d¦  «        S )NÚSTEALTHFOX_GITHUB_TOKENÚGITHUB_TOKEN)ÚosÚenvironÚget© ó    úGC:\Projects\Muse2API\.venv\Lib\site-packages\invisible_core/download.pyÚ_github_tokenr    *   s*   € ÝŒ:�>Š>Ð3Ñ4Ô4ÐV½¼
¿ºÀ~Ñ8VÔ8VÐVr   ÚtemplateÚstrútuple[str, str]c                ó¨   — t          j        d| ¦  «        }|st          d| ›�¦  «        ‚|                     d¦  «        |                     d¦  «        fS )z0Extract (owner, repo) from RELEASE_URL_TEMPLATE.z-https://github\.com/([^/]+)/([^/]+)/releases/zcannot parse owner/repo from r   é   )ÚreÚmatchÚRuntimeErrorÚgroup)r!   Úms     r   Ú_parse_owner_repor+   .   sS   € å
ŒÐAÀ8ÑLÔL€AØð IÝÐG¸8ÐGÐGÑHÔHÐHØ�7Š7�1‰:Œ:�q—w’w˜q‘z”zÐ!Ð!r   r   c                 ó¤   — t           j                             d¦  «        } | rt          | ¦  «        S t          t	          j        d¦  «        ¦  «        S )a  Directory where all cached binaries live.

    INVISIBLE_PLAYWRIGHT_CACHE_DIR overrides it. It was set by the test suite
    and read nowhere, so "isolated" cache tests were operating on the
    developer's real cache; honouring it makes them hermetic on every OS.
    ÚINVISIBLE_PLAYWRIGHT_CACHE_DIRzinvisible-playwright)r   r   r   r   ÚplatformdirsÚuser_cache_dir)Úoverrides    r   Ú
cache_rootr1   6   sF   € õ Œz�~Š~Ð>Ñ?Ô?€HØð Ý�H‰~Œ~ÐÝ•Ô+Ð,BÑCÔCÑDÔDÐDr   ÚsealúSeal | Nonec                ór   — | pt          ¦   «         }t          ¦   «         |j        › d|j        › d|j        › �z  S )zÛThe cache directory IS the identity: tag, base version and BuildID.

    A different seal names a different directory, so a warm tree belonging to a
    different expectation is not rejected, it is never looked at.
    Ú_)r   r1   ÚtagÚupstream_versionÚbuild_id)r2   Úss     r   Úcache_dir_for_sealr:   C   s>   € ð 	Ð•‘”€AÝ‰<Œ<˜QœUÐFÐF QÔ%7ÐFÐF¸!¼*ÐFÐFÑFÐFr   Úversionc                óx   — t          ¦   «         }| �| |j        k    rt          |¦  «        S t          ¦   «         | z  S )z”Back-compat shim. The sealed tag resolves to the content-keyed dir; any
    other tag resolves to its legacy flat name (enumeration / cleanup only).)r   r6   r:   r1   )r;   r9   s     r   Úcache_dir_for_versionr=   M   s;   € õ 	‰Œ€AØ€˜' Q¤UÒ*Ð*Ý! !Ñ$Ô$Ð$Ý‰<Œ<˜'Ñ!Ð!r   r6   Ú
asset_namec                óŽ  ‡‡— t          ¦   «         Š‰st          j        | |¬¦  «        S t          t          ¦  «        \  }}d|› d|› d| › �Šˆˆfd„}t	          |d| › �¦  «        }|                     ¦   «                              dg ¦  «        D ]%}|                     d¦  «        |k    r
|d	         c S Œ&t          d
|›d| ›�¦  «        ‚)a¤  Return a downloadable URL for the asset.

    For private repos the direct `releases/download/<tag>/<asset>` URL returns
    404 even with a token, so we resolve via the API: list assets for the
    release tag, find the one matching `asset_name`, and use its API URL with
    `Accept: application/octet-stream` (which 302-redirects to a signed URL).
    For public repos the direct URL still works without a token.
    )r6   Úassetzhttps://api.github.com/repos/ú/z/releases/tags/c                 óh   •— t          j        ‰dd‰› �id¬¦  «        } |                      ¦   «          | S )NÚAuthorizationútoken é   )ÚheadersÚtimeout)Úrequestsr   Úraise_for_status)ÚrÚapiÚtokens    €€r   Ú_askz _resolve_asset_url.<locals>._askh   s?   ø€ ÝŒL˜ Ð8HÀÐ8HÐ8HÐ&IÐSUÐVÑVÔVˆØ	×ÒÑÔÐØˆr   zthe release index for ÚassetsÚnameÚurlzasset z not found in release )r    r   Úformatr+   Ú_with_retriesÚjsonr   r(   )	r6   r>   ÚownerÚreporM   rJ   ÚarK   rL   s	          @@r   Ú_resolve_asset_urlrW   V   s   øø€ õ ‰OŒO€EØð FÝ#Ô*¨s¸*ÐEÑEÔEÐEÝ#Õ$8Ñ9Ô9�K€Eˆ4Ø
L¨%Ð
LÐ
L°$Ð
LÐ
LÀsÐ
LÐ
L€Cð
ð ð ð ð ð õ
 	�dÐ:°SÐ:Ð:Ñ;Ô;€AØ�VŠV‰XŒX�\Š\˜( BÑ'Ô'ð ð ˆØ�5Š5�‰=Œ=˜JÒ&Ð&Ø�U”8ˆOˆOˆOð 'å
ÐK 
ÐKÐKÀCÐKÐKÑ
LÔ
LÐLr   é   rP   c           	     ó�  — d}|                       d¦  «        rW|                      dd¦  «        d                              ¦   «         r)t          |                      dd¦  «        d         ¦  «        }d| › d|› d�d|› �d	g}|�|t          k     r|d
| › d�d
dd
dd
dgz  }n
|d
| › d�gz  }t          d¦  «                             |¦  «        S )aˆ  What to say when the engine archive is not there.

    WHY THIS EXISTS. A 404 surfaced as `requests.exceptions.HTTPError: 404 Client
    Error` and nothing else - no tag, no cause, no remedy. Reported as issue #51
    on 2026-08-01 by somebody whose install came from a git pin predating
    firefox-14, when the binaries moved to the source repo. They did the work
    themselves: found that the tags existed, that no release did, and that the URL
    template named a repo hosting nothing. That investigation is the message's
    job, not the reporter's.

    AND IT STOPS AT A MESSAGE, DELIBERATELY. 18.10.0 shipped a version of this
    that ran `pip install --upgrade` on the caller's environment and then told
    them to re-run. It worked, and it is the wrong shape: a library that installs
    things while it is running mutates an environment nobody asked it to touch,
    ignores whatever lockfile put that version there, and inside a container
    rewrites an image layer at runtime. Removed in 18.11.0. Detecting the state
    and saying what to run is the whole job; running it is the caller's.

    The two cases are genuinely different and the message says which one it is.
    An OLD tag cannot be fixed by waiting: those releases were removed on purpose,
    so the pin has to move. A CURRENT tag missing its asset is something else -
    a re-cut release, a pruned asset - and worth reporting.
    Nzfirefox-ú-r   zthe engine archive for z is not published: z -> HTTP 404z  tried z^  the tag comes from the release seal inside invisible-core, not from anything you configured.Ú zÁ is one of the engine releases that were REMOVED on purpose, so that everybody runs one engine rather than a long tail of old ones. It is not coming back and no amount of retrying will find it.zÒYou are running a version of this package that predates the move. That usually means an install pinned to a git ref rather than to a release - including a pin inside some other project that depends on this one.zG  pip install --upgrade invisible-playwright     # or invisible-firefoxzŒIf the pin is not yours to move, the project that owns it has to move it: a git ref from before the move can never download an engine again.zµ is a current release, so this is not the retired-engine case. Either the asset was pruned from the release or the release was re-cut without it. Worth reporting, with this message.é
   )Ú
startswithÚsplitÚisdigitÚintÚOLDEST_PUBLISHED_TAG_NUMBERÚchrÚjoin)r6   r>   rP   ÚnumberÚliness        r   Ú_missing_release_messagerf   {   s.  € ð0 €FØ
‡~‚~�jÑ!Ô!ð + c§i¢i°°QÑ&7Ô&7¸Ô&:×&BÒ&BÑ&DÔ&Dð +Ý�S—Y’Y˜s AÑ&Ô& qÔ)Ñ*Ô*ˆð 	S #ÐRÐR¸*ÐRÐRÐRØ�3ÐÐð	#ð€Eð Ð˜fÕ'BÒBÐBØØØð Mð Mð Mð ðRð ØUØðUð
ñ 	
ˆˆð  	ØØð Fð Fð Fð
ñ 	
ˆõ ˆr‰7Œ7�<Š<˜ÑÔÐr   ÚINVISIBLE_DOWNLOAD_DEADLINEg      œ@Úfloatc            	     ó  — t           j                             t          ¦  «        } | �|                      ¦   «         st
          S 	 t          | ¦  «        S # t          $ r% t          t          › d| ›dt
          d›d�¦  «        d‚w xY w)zESeconds allowed for one download. Zero or negative removes the bound.Nú=z9 is not a number of seconds. Unset it for the default of ú.0fz/s, or set it to 0 to remove the bound entirely.)	r   r   r   ÚDOWNLOAD_DEADLINE_ENVÚstripÚDOWNLOAD_DEADLINE_DEFAULTrh   Ú
ValueErrorr(   )Úraws    r   Ú_download_deadlinerq   Ã   s    € å
Œ*�.Š.Õ.Ñ
/Ô
/€CØ
€{˜#Ÿ)š)™+œ+€{Ý(Ð(ð	Ý�S‰zŒzÐøÝð ð ð õ Ý$ð <ð < sð <ð <Ý+DÐMð<ð <ð <ñ
ô 
ð ð		ðøøøs   ÁA Á/Bé   )g       @g       @>   é˜  é­  éô  éö  é÷  éø  g      N@ÚmessageÚNonec                ó>   — t          | t          j        d¬¦  «         d S )NT)ÚfileÚflush)ÚprintÚsysÚstderr)ry   s    r   Ú_noter�   ú   s   € Ý	ˆ'�œ
¨$Ð/Ñ/Ô/Ð/Ð/Ð/r   ÚexcÚBaseExceptionÚboolc                óà   — t          | t          j        ¦  «        rt          | j        dd¦  «        }|t
          v S t          | t          j        t          j        t          j        j	        f¦  «        S )aR  Does this failure say "ask again", or does it say something?

    Pure: no clock, no network, no state, so the answer can be checked against
    known-bad inputs without a server. Everything it does not recognise is
    permanent, which is the safe direction: an unknown failure is reported once
    rather than repeated three times.
    Ústatus_codeN)
Ú
isinstancerH   Ú	HTTPErrorÚgetattrÚresponseÚTRANSIENT_STATUSÚConnectionErrorÚTimeoutÚ
exceptionsÚChunkedEncodingError)r‚   Ústatuss     r   Ú_is_transientr‘   þ   sj   € õ �#•xÔ)Ñ*Ô*ð *Ý˜œ }°dÑ;Ô;ˆØÕ)Ð)Ð)õ �c�HÔ4µhÔ6FÝ$Ô/ÔDðFñ Gô Gð Gr   úfloat | Nonec                ó@  — t          t          | dd¦  «        di ¦  «        pi }|                     d¦  «        }|€dS 	 t          t          |¦  «                             ¦   «         ¦  «        }n# t
          $ r Y dS w xY wt          dt          |t          ¦  «        ¦  «        S )z>The server's own answer to "when should I ask again", or None.rŠ   NrF   zRetry-Afterg        )	r‰   r   rh   r"   rm   ro   ÚmaxÚminÚRETRY_AFTER_CAP_S)r‚   rp   ÚvalueÚsecondss       r   Ú_retry_afterr™     s¦   € å
•'˜#˜z¨4Ñ0Ô0°)¸RÑ
@Ô
@Ð
FÀB€CØ�GŠG�MÑ"Ô"€EØ€}ØˆtðÝ�˜E™
œ
×(Ò(Ñ*Ô*Ñ+Ô+ˆˆøÝð ð ð ð ˆtˆtð	øøøõ
 ˆs•C˜Õ!2Ñ3Ô3Ñ4Ô4Ð4s   ½.A, Á,
A:Á9A:ÚwhatÚattemptsr`   c                óž  — t          d|dz   ¦  «        D ]¸}	  | ¦   «         c S # t          $ r�}||k    st          |¦  «        s.|dk    r't          |¦  «        rt          |› d|› d|› �¦  «        |‚‚ t	          |¦  «        }|€t
          |dz
           }t          d|› d|› d|› d|› d	|d
›d�¦  «         t          |¦  «         Y d}~Œ±d}~ww xY wdS )aä  Run ``call()``, and run it again while the failure says to ask again.

    ``what`` names the thing being fetched, for the note and for the failure.
    A permanent failure is re-raised untouched on the first attempt, so callers
    that read a status code off it - the 404 branch in ``ensure_binary`` - still
    see the exception they expect.

    ``attempts`` is the CALLER's, and one attempt is a legitimate answer. Asking
    again is only worth its wall clock to someone who has nothing better to do
    with the failure; a caller holding a good enough copy already has something
    better, and waiting ten seconds to reach a fallback it was going to take
    anyway is a cost with no buyer. See ``_geoip_db.ensure_geoip_mmdb``.
    r   z failed z times; the last attempt said: Nz  z
: attempt ú of z	 failed (z); asking again in rk   r9   )ÚrangeÚ	Exceptionr‘   r(   r™   ÚDOWNLOAD_BACKOFF_Sr�   Ú_SLEEP)Úcallrš   r›   Úattemptr‚   Úwaits         r   rR   rR      s]  € õ( ˜˜H q™LÑ)Ô)ð ð ˆð	Ø�4‘6”6ˆMˆMˆMøÝð 	ð 	ð 	Ø˜(Ò"Ð"­-¸Ñ*<Ô*<Ð"Ø˜Q’;�;¥=°Ñ#5Ô#5�;Ý&Øð 'ð '¨ð 'ð 'Ø!$ð'ð 'ñ(ô (à-0ð1ð Ý Ñ$Ô$ˆDØˆ|Ý)¨'°A©+Ô6�Ýð :�tð :ð : wð :ð :°Hð :ð :Øð:ð :Ø.2Ð9ð:ð :ð :ñ ;ô ;ð ;å�4‰LŒLˆLˆLˆLˆLˆLˆLøøøøð	øøøðð s   —	#£
C
­BCÃC
é   ÚdstÚ
chunk_sizec                ól   ‡ ‡‡‡— t          ˆˆˆˆ fd„‰                      dd¦  «        d         |¦  «        S )a½  Download ``url`` to ``dst``, asking again when the failure is transient.

    ``dst`` is truncated at the start of every attempt, so a retry starts from
    byte zero: there is no resume here, and the failure that wants one - a
    socket dropping most of the way through a 262 MB transfer - is not the
    failure this was written for. Callers download into a temporary directory
    and only move the tree into the cache after the sha256 matches, so a
    half-written file from a dead attempt is never adopted.

    ``attempts`` defaults to the engine's policy because that is the caller with
    nothing to fall back on. It is a parameter and not a constant read inside
    because this function is shared, and a shared primitive that carries one
    caller's policy imposes it on the other: ``_geoip_db`` had already written
    down the opposite answer and would have inherited a ten-second wait on the
    way to a fallback it was taking anyway.
    c                 ó(   •— t          ‰‰‰ ‰¦  «        S ©N)Ú_download_once)r§   r¦   ÚprogressrP   s   €€€€r   ú<lambda>z _download_file.<locals>.<lambda>X  s   ø€ ¥°°S¸*ÀhÑ!OÔ!O€ r   rA   r   éÿÿÿÿ)rR   Úrsplit)rP   r¦   r§   r¬   r›   s   ```` r   Ú_download_filer°   F  sG   øøøø€ õ$ ÐOÐOÐOÐOÐOÐOÐOØŸš C¨Ñ+Ô+¨BÔ/°ñ;ô ;ð ;r   c                óþ  — |j                              dd¬¦  «         i }t          ¦   «         }|r"|                      d¦  «        rd|› �|d<   d|d<   t	          ¦   «         }t          j        ¦   «         }t          j        | dd|¬	¦  «        5 }| 	                    ¦   «          t          |j                             d
¦  «        pd¦  «        }	d}
t          |d¦  «        5 }|                     |¦  «        D ]Ü}|rG|                     |¦  «         |
t          |¦  «        z  }
|�	  ||
|	¦  «         n# t           $ r Y nw xY wt          j        ¦   «         |z
  }|dk    ru||k    ro|                     ¦   «          |                     d¬¦  «         t'          d|                      dd¦  «        d         › d|d›d|
› d|	pd› d|d›dt*          › d�¦  «        ‚ŒÝ	 ddd¦  «         n# 1 swxY w Y   ddd¦  «         dS # 1 swxY w Y   dS )aH  One attempt. If ``progress`` is given it is called with
    ``(bytes_done, total_bytes)`` as the download proceeds (total is 0 when the
    server sends no Content-Length).

    Bounded by ``DOWNLOAD_DEADLINE_ENV`` across the whole transfer; see that
    constant for why the per-read timeout below cannot do it. That bound is per
    attempt, and the RuntimeError it raises is deliberately NOT transient: a
    link slow enough to run out the deadline is not a link that a second full
    download would beat, and retrying it would multiply the very wait the bound
    exists to cap.T©ÚparentsÚexist_okzhttps://api.github.com/rD   rC   zapplication/octet-streamÚAccepté<   )ÚstreamrG   rF   zContent-Lengthr   ÚwbN)Ú
missing_okzthe download of rA   r   r®   z passed its rk   zs deadline: r�   zan unknown number ofz bytes after z^s. The per-read timeout cannot catch this - a connection that trickles never trips one. Raise zE if the link really is this slow, or set it to 0 to remove the bound.)ÚparentÚmkdirr    r]   rq   ÚtimeÚ	monotonicrH   r   rI   r`   rF   ÚopenÚiter_contentÚwriteÚlenrŸ   ÚcloseÚunlinkr(   r¯   rl   )rP   r¦   r§   r¬   rF   rL   ÚlimitÚstartedrJ   ÚtotalÚdoneÚfÚchunkÚelapseds                 r   r«   r«   \  s   € ð „J×Ò˜T¨DÐÑ1Ô1Ð1Ø €GÝ‰OŒO€EØð 7�—’Ð 9Ñ:Ô:ð 7Ø#3¨EÐ#3Ð#3ˆ�Ñ Ø6ˆ�ÑÝÑ Ô €EÝŒnÑÔ€GÝ	Œ�c $°¸GÐ	DÑ	DÔ	Dð KÈØ	×ÒÑÔÐÝ�A”I—M’MÐ"2Ñ3Ô3Ð8°qÑ9Ô9ˆØˆÝ�#�t‰_Œ_ð 	K ØŸš¨
Ñ3Ô3ð Kð K�Øð !Ø—G’G˜E‘N”N�NØ�C ™JœJÑ&�DØÐ+ð!Ø$˜H T¨5Ñ1Ô1Ð1Ð1øÝ(ð !ð !ð !Ø ˜Dð!øøøõ
 œ.Ñ*Ô*¨WÑ4�Ø˜1’9�9 ¨5¢ Ø—G’G‘I”I�IØ—J’J¨$�JÑ/Ô/Ð/Ý&ðJ¨3¯:ª:°c¸1Ñ+=Ô+=¸bÔ+Að Jð JØ Ð<ðJð JØ26ðJð Jà Ð:Ð$:ðJð Jð #ÐMðJð Jõ
 "7ðJð Jð JñKô Kð Køð!Kð	Kð 	Kð 	Kñ 	Kô 	Kð 	Kð 	Kð 	Kð 	Kð 	Kð 	Køøøð 	Kð 	Kð 	Kð 	Kð	Kð Kð Kñ Kô Kð Kð Kð Kð Kð Kð Kð Køøøð Kð Kð Kð Kð Kð Ks]   Â
AG2ÃAGÄD+Ä*GÄ+
D8	Ä5GÄ7D8	Ä8BGÇG2ÇG	ÇG2Ç!G	Ç"G2Ç2G6Ç9G6Úpathc                óú   ‡— t          j        ¦   «         }t          | d¦  «        5 Št          ˆfd„d¦  «        D ]}|                     |¦  «         Œ	 d d d ¦  «         n# 1 swxY w Y   |                     ¦   «         S )NÚrbc                 ó.   •— ‰                       d¦  «        S )Nr¥   )Úread)rÈ   s   €r   r­   z_sha256_file.<locals>.<lambda>‘  s   ø€  !§&¢&¨¡/¤/€ r   r   )ÚhashlibÚsha256r¾   ÚiterÚupdateÚ	hexdigest)rË   ÚhrÉ   rÈ   s      @r   Ú_sha256_filerÖ   Ž  s»   ø€ ÝŒÑÔ€AÝ	ˆd�DÑ	Ô	ð ˜QÝÐ1Ð1Ð1Ð1°3Ñ7Ô7ð 	ð 	ˆEØ�HŠH�U‰OŒOˆOˆOð	ðð ð ñ ô ð ð ð ð ð ð øøøð ð ð ð ð �;Š;‰=Œ=Ðs   ¥,AÁA"Á%A"Útextúdict[str, str]c                ó*  — i }|                       ¦   «         D ]{}|                     ¦   «         }|r|                     d¦  «        rŒ.|                     ¦   «         }t	          |¦  «        dk    r&|d                              d¦  «        }|d         ||<   Œ||S )Nú#r%   r®   Ú*r   )Ú
splitlinesrm   r]   r^   rÁ   Úlstrip)r×   ÚoutÚlineÚpartsÚkeys        r   Ú_parse_checksumsrâ   –  s‘   € Ø€CØ—’Ñ!Ô!ð  ð  ˆØ�zŠz‰|Œ|ˆØð 	�t—’ sÑ+Ô+ð 	ØØ—
’
‘”ˆÝˆu‰:Œ:˜Š?ˆ?à˜”)×"Ò" 3Ñ'Ô'ˆCØ˜Q”xˆC�‰HøØ€Jr   Úarchivec                óÂ  — |                      dd¬¦  «         | j        dk    rDt          j        | ¦  «        5 }|                     |¦  «         d d d ¦  «         d S # 1 swxY w Y   d S | j                             d¦  «        s	| j        dv rEt          j        | d¦  «        5 }|                     |¦  «         d d d ¦  «         d S # 1 swxY w Y   d S t          d| › �¦  «        ‚)NTr²   z.zipz.tar.gz>   ú.gzú.tgzzr:gzzunknown archive format: )
r»   ÚsuffixÚzipfileÚZipFileÚ
extractallrO   ÚendswithÚtarfiler¾   r(   )rã   r¦   ÚzfÚtfs       r   Ú_extractrï   ¤  sk  € Ø‡I‚I�d T€IÑ*Ô*Ð*Ø„~˜ÒÐÝŒ_˜WÑ%Ô%ð 	¨Ø�MŠM˜#ÑÔÐð	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	øøøð 	ð 	ð 	ð 	ð 	ð 	à	Œ×	Ò	˜yÑ	)Ô	)ð A¨W¬^¸Ð-NÐ-NÝŒ\˜' 6Ñ*Ô*ð 	¨bØ�MŠM˜#ÑÔÐð	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	øøøð 	ð 	ð 	ð 	ð 	ð 	õ Ð?°gÐ?Ð?Ñ@Ô@Ð@s#   ·AÁAÁ!AÂCÃCÃ	Cr   r@   r   Úversion_dirúPath | Nonec                ó†  — g }|                      ¦   «         r|                     |¦  «         t          ¦   «         | j        z  }||k    r)|                      ¦   «         r|                     |¦  «         |D �]Ì}||j        z  }|                      ¦   «         sŒ"	 t          || d|j        › �|¬¦  «         n=# t          $ r0}t          d|› d|j	        › �t          j        ¬¦  «         Y d}~Œtd}~ww xY w|j        r„t          |¦  «        dz  }|                      ¦   «         r]t          |¦  «                             ¦   «         |j                             ¦   «         k    r!t          d|› d�t          j        ¬¦  «         �Œn$t          d	|j        › d
�t          j        ¬¦  «         ||k    r3	 t!          j        ||¦  «         |||j        z  }}n# t$          $ r Y nw xY wt'          || |j        dd¬¦  «         t          d|› d|                      ¦   «         › d�t          j        ¬¦  «         t          || d|j        › �|¬¦  «        c S dS )av  Use a tree that is already on disk if it IS the sealed build.

    Covers every warm cache in the field (flat cache_root()/<tag> layout) and a
    content-keyed tree whose stamp was lost. Verified -> moved onto the
    content-keyed name and stamped, no download. Not verified -> left exactly
    where it is (it may belong to the other product) and the cold path runs.
    zexisting cache ©Úsourcer@   zinvisible-core: not adopting z: )r|   Nzomni.jazQ: omni.ja content does not match the sealed payload (superseded or modified tree)zinvisible-core: z¬: this platform's asset has no sealed omni.ja digest, so the payload bytes are not content-verified; adoption rests on application.ini, platform.ini and the juggler markersT©r@   Úasset_sha256Úadoptedz5invisible-core: adopted the engine already cached at z (z); no download neededzadopted )ÚexistsÚappendr1   r6   Ú	entry_relr   rO   r	   r~   Úsummaryr   r€   Úomni_sha256r   rÖ   Úlowerr   ÚreplaceÚOSErrorr   Údescribe)	r2   r@   rð   Ú
candidatesÚlegacyÚdÚentryÚeÚomnis	            r   Ú_adopt_existing_cacher  °  sÞ  € ð €JØ×ÒÑÔð 'Ø×Ò˜+Ñ&Ô&Ð&Ý‰\Œ\˜DœHÑ$€FØ�ÒÐ §¢¡¤ÐØ×Ò˜&Ñ!Ô!Ð!àð 'Sñ 'SˆØ�E”OÑ#ˆØ�|Š|‰~Œ~ð 	Øð	Ý˜% Ð.HÀÄÐ.HÐ.HÐPUÐVÑVÔVÐVÐVøÝð 	ð 	ð 	õ ÐB°!ÐBÐB°q´yÐBÐBÍÌÐTÑTÔTÐTØˆHˆHˆHˆHøøøøð	øøøð Ôð 	#Ý  Ñ'Ô'¨)Ñ3ˆDØ�{Š{‰}Œ}ð ¥¨dÑ!3Ô!3×!9Ò!9Ñ!;Ô!;¸uÔ?P×?VÒ?VÑ?XÔ?XÒ!XÐ!XÝð J°að Jð Jð JÝPSÔPZð\ñ \ô \ð \áøõ
 ð N Q¤Vð Nð Nð Nõ ”zð#ñ #ô #ð #ð �ÒÐðÝ”
˜1˜kÑ*Ô*Ð*Ø&¨°e´oÑ(E�5��øÝð ð ð Ø�ðøøøå�A�t 5¤:¸DÈ$ÐOÑOÔOÐOÝð 9Àað 9ð 9Ø—-’-‘/”/ð9ð 9ð 9Ý?B¼zð	Kñ 	Kô 	Kð 	Kõ ˜U DÐ1D¸A¼FÐ1DÐ1DÈEÐRÑRÔRÐRÐRÐRØˆ4s*   ÂB2Â2
C,Â<&C'Ã'C,Æ%!GÇ
GÇGÚrootÚlistc                óL  — | pt          ¦   «         } |                      ¦   «         sg S |pt          j        }g }|                      ¦   «         D ]Ø}|                     ¦   «         r|j                             d¦  «        sŒ1|j                             dd¦  «        d         }|                     ¦   «         sŒgt          |¦  «        }|t          j        ¦   «         k    s ||¦  «        rŒ™t          j        |d¬¦  «         |                     ¦   «         s|                     |¦  «         ŒÙ|S )añ  Remove the `.tmp-<tag>-<pid>` trees left by downloads whose process is gone.

    A download extracts into a directory named after its pid and, when it
    starts again, removes only that one. A process killed during `verifying`
    or `extracting` therefore leaves up to the whole tree behind, and until
    this existed nothing ever looked at it again. The MCP server's prefetch
    (0.13.0) made the case ordinary: a client that closes the server in the
    first minute of its first session kills a download in flight.

    A tree whose pid is alive belongs to a download in flight, in this process
    or another, and is left alone. A reused pid keeps an orphan for one more
    round, which is the conservative side. Returns what was removed.
    ú.tmp-rZ   r   r®   T©Úignore_errors)r1   Úis_dirÚpsutilÚ
pid_existsÚiterdirrO   r]   r¯   r_   r`   r   ÚgetpidÚshutilÚrmtreerø   rù   )r  ÚaliveÚremovedÚ	candidateÚpid_textÚpids         r   Úsweep_orphaned_tmpr  ê  s  € ð Ð•:‘<”<€DØ�;Š;‰=Œ=ð Øˆ	ØÐ&•VÔ&€EØ€GØ—\’\‘^”^ð &ð &ˆ	Ø×ÒÑ!Ô!ð 	¨¬×)BÒ)BÀ7Ñ)KÔ)Kð 	ØØ”>×(Ò(¨¨aÑ0Ô0°Ô4ˆØ×ÒÑ!Ô!ð 	ØÝ�(‰mŒmˆØ•"”)‘+”+ÒÐ   s¡¤ÐØÝŒ�i¨tÐ4Ñ4Ô4Ð4Ø×ÒÑ!Ô!ð 	&Ø�NŠN˜9Ñ%Ô%Ð%øØ€Nr   )r2   c               ó¼  ‡— d2ˆfd„}|pt          ¦   «         }| �9| |j        k    r.t          d|                     ¦   «         › d| ›d	| ›d
| ›d�	¦  «        ‚|j        rt          d|j        › d�¦  «        ‚t          j        }|t          vrM|dk    rt          d¦  «        ‚t          d|›dd                     d„ t          D ¦   «         ¦  «        ›d�¦  «        ‚|                     |t          j        ¦   «         ¦  «        }t          |¦  «        }||j        z  }t#          t%          ¦   «         ¦  «         t'          |¦  «        }	|	rM|	                     d¦  «        |j        k    r/|                     ¦   «         rt/          ||d|j        › �|¬¦  «        S t3          |||¦  «        }
|
�|
S t5          |j        |j        ¦  «        }t%          ¦   «         d|j        › dt7          j        ¦   «         › �z  }t;          j        |d¬¦  «         t?          j         ¦   «         5 }tC          |¦  «        |j        z  } |d¦  «         	 tE          |||¬¦  «         n[# tF          j$        $ rI}tK          |j&        dd¦  «        dk    r‚ tO          tQ          |j        |j        |¦  «        ¦  «        |‚d}~ww xY w |d ¦  «         tS          |¦  «        }| *                    ¦   «         |j+         *                    ¦   «         k    r+tO          d!|j        › d"|j        › d#|› d$|j+        › d%�	¦  «        ‚ |d&¦  «         tY          ||¦  «         ddd¦  «         n# 1 swxY w Y   ||j        z  }|                     ¦   «         s(t;          j        |d¬¦  «         tO          d'|› �¦  «        ‚t;          j        |d¬¦  «         	 t7          j-        ||¦  «         nh# t\          $ r[}|                     ¦   «         rt;          j        |d¬¦  «         n&tO          d(|› d)|› d*|› d+|j/        d,z  d-›d.�	¦  «        |‚Y d}~nd}~ww xY wta          |||j        |j+        d/¬0¦  «         t/          ||d1|j        › �|¬¦  «        S )3a¨  Return a verified path to the sealed Firefox executable. Download if needed.

    `version`, when given, must be the sealed tag: this core is paired with
    exactly one build and cannot coherently install another.

    ``progress``, if given, is called with ``(bytes_done, total_bytes)`` while the
    (large) archive downloads, for a UI progress bar. ``status``, if given, is
    called with a phase string ("downloading" | "verifying" | "extracting") so a
    UI can show what the post-100% (silent, no byte-progress) steps are doing -
    the SHA256 check + the archive extraction can take tens of seconds with no
    download progress, and otherwise look frozen at 100%.
    Úpr"   r   rz   c                óJ   •— ‰�	  ‰| ¦  «         d S # t           $ r Y d S w xY wd S rª   )rŸ   )r  r�   s    €r   Ú_phasezensure_binary.<locals>._phase  sI   ø€ ØÐðØ��q‘	”	�	�	�	øÝð ð ð Ø��ðøøøð Ðs   … ’
 Ÿ Nz!this invisible-core is sealed to z; it cannot install zÚ.
The prefs, the spoofed User-Agent and the protocol expectations in this package were generated for the sealed build, so pairing them with another engine would ship a browser whose claim contradicts itself.

To drive a7   anyway, generate a seal for that build and point this package at it:
  python -m invisible_core seal --binary <path to that firefox> -o my.seal.json
  set INVISIBLE_SEAL_FILE=my.seal.json
The User-Agent and the prefs then move with the seal, so the pair stays coherent.

(A release of invisible-core sealed to z£ would also work, if one exists. It does not for tags that predate the seal, which is why the route above is given first: it works for any build you have on disk.)zthe active seal (aé  ) is a LOCAL seal with no published assets, so there is nothing to download. Pass binary_path= pointing at the build this seal was generated from. (INVPW_BINARY_PATH is NOT read by this library - the test scripts and run_e2e.py translate it into binary_path=, so it only works under them. Corrected 2026-08-19; the same false promise was removed from invisible_playwright/_engine.py on 2026-08-14, and naming an env var the code never reads sends the reader hunting in the wrong function.)ÚdarwinaY  macOS is no longer a supported platform: no Mac binaries have been published since firefox-21, and this package downloads none.
Seals from earlier releases still carry the macOS assets - they stay readable as history - but a new start on a Mac stops here instead of attempting a download that does not exist.
Nothing changes on Windows or Linux.z3this platform is not one we publish an engine for: z. The declared legs are z, c              3  ó    K  — | ]	}d |z  V — Œ
dS )z%s/%sNr   )Ú.0Úgs     r   ú	<genexpr>z ensure_binary.<locals>.<genexpr>R  s&   è è € ÐCÐC¨q˜w¨™{ÐCÐCÐCÐCÐCÐCr   ú.Úseal_digestz
cache hit ró   r  rZ   Tr  Údownloading)r¬   r†   i”  Ú	verifyingzpayload mismatch for z under tag z:
  got      z
  expected zÊ  (from the seal shipped inside invisible-core)
The asset published under this tag is not the payload this core was sealed against: the tag was re-cut, or the download was corrupted. Refusing to use it.Ú
extractingz#binary not found after extraction: z1could not move the verified download into place: z
  from z
  to   zá
The download is COMPLETE and its sha256 matches the seal, so it is kept where it is: move that directory onto the destination by hand, or re-run once whatever holds the destination has exited. Deleting it would cost another i   rk   z MB for nothing.Frõ   zfresh download )r  r"   r   rz   )1r   r6   r   r   Úis_localr   Úoriginr   Úplatformr   ÚNotImplementedErrorrc   r
   Ú	asset_forÚmachiner:   rú   r  r1   r   r   Údigestrø   r   rO   r  rW   r   r  r  r  ÚtempfileÚTemporaryDirectoryr   r°   rH   rˆ   r‰   rŠ   r(   rf   rÖ   rý   rÑ   rï   rþ   rÿ   Úsizer   )r;   r¬   r�   r2   r  Úplatr@   rð   r  Ústampr÷   Úurl_archiveÚtmp_dirÚtdÚarchive_pathr‚   ÚactualÚ	tmp_entryr  s     `                r   Úensure_binaryr;    sÒ  ø€ ðð ð ð ð ð ð Ð •;‘=”=€DØÐ˜w¨$¬(Ò2Ð2ÝðO°·²±´ð Oð OØðOð Oð  ðOð Oð 7>ðOð Oð Oñ
ô 
ð 	
ð$ „}ð 	VÝðU ¤ð Uð Uð UñVô Vð 	Võ Œ<€DØÕ&Ð&Ð&ð
 �8ÒÐÝ%ð7ñô ð õ "Ð!ð ˆtˆt�T—Y’YÐCÐCµNÐCÑCÔCÑCÔCÐCÐCðEñ
ô 
ð 	
ð
 �NŠN˜4¥Ô!1Ñ!3Ô!3Ñ4Ô4€EÝ$ TÑ*Ô*€KØ˜%œ/Ñ)€Eõ •z‘|”|Ñ$Ô$Ð$å�{Ñ#Ô#€EØð _�—’˜=Ñ)Ô)¨T¬[Ò8Ð8¸U¿\º\¹^¼^Ð8Ý˜U DÐ1P¸kÔ>NÐ1PÐ1PÐX]Ð^Ñ^Ô^Ð^å# D¨%°Ñ=Ô=€GØÐØˆå$ T¤X¨u¬zÑ:Ô:€KÝ‰lŒlÐ= T¤XÐ=Ð=µ´	±´Ð=Ð=Ñ=€GÝ
„M�'¨Ð.Ñ.Ô.Ð.Ý	Ô	$Ñ	&Ô	&ð (¨"Ý˜B‘x”x %¤*Ñ,ˆØˆˆ}ÑÔÐð	NÝ˜;¨¸xÐHÑHÔHÐHÐHøÝÔ!ð 	Nð 	Nð 	NÝ�s”| ]°DÑ9Ô9¸SÒ@Ð@ØÝÕ7¸¼À%Ä*Ø7Bñ Dô  Dñ Eô EØJMðNøøøøð	Nøøøð
 	ˆˆ{ÑÔÐÝ˜lÑ+Ô+ˆØ�<Š<‰>Œ>˜Uœ\×/Ò/Ñ1Ô1Ò1Ð1Ýð'¨¬
ð 'ð '¸t¼xð 'ð 'Ø$ð'ð 'à#œlð'ð 'ð 'ñô ð ð 	ˆˆ|ÑÔÐÝ�˜wÑ'Ô'Ð'ð-(ð (ð (ñ (ô (ð (ð (ð (ð (ð (ð (øøøð (ð (ð (ð (ð0 ˜%œ/Ñ)€Ið ×ÒÑÔð NÝŒ�g¨TÐ2Ñ2Ô2Ð2ÝÐLÀÐLÐLÑMÔMÐMå
„M�+¨TÐ2Ñ2Ô2Ð2ðÝ
Œ
�7˜KÑ(Ô(Ð(Ð(øÝð ð ð ð �<Š<‰>Œ>ð 	õ ŒM˜'°Ð6Ñ6Ô6Ð6Ð6åðSÀAð Sð SØ!ðSð Sà%ðSð Sð ',¤j°GÑ&<ÐRðSð Sð Sñô ð ðð 7Ð6Ð6Ð6Ð6øøøøðøøøõ( �˜T¨¬À%Ä,ÐX]Ð^Ñ^Ô^Ð^Ý˜ Ð-Q¸{Ô?OÐ-QÐ-QÐY^Ð_Ñ_Ô_Ð_sQ   È#L?È6I	ÉL?É	J!ÉAJÊJ!Ê!BL?Ì?MÍMÎ'N= Î=
P"ÏAPÐP"Útuplec                ó¸  — | pt          ¦   «         }	 |                     t          j        t          j        ¦   «         ¦  «        }t          |¦  «        |j        z  }|                     ¦   «         sdS t          ||d|¬¦  «         d| 	                    ¦   «         fS # t          $ r}d|j        fcY d}~S d}~wt          $ r}dt          |¦  «        fcY d}~S d}~ww xY w)zB(ok, detail) for the sealed engine's cache. Never raises. For UIs.)Fznot downloadedzstatus checkró   TFN)r   r-  r   r+  r.  r:   rú   rø   r   r   r	   rû   rŸ   r"   )r2   r9   r@   r  r  s        r   Úengine_statusr>  ¢  sù   € àÐ•‘”€AðØ—’�CœL­(Ô*:Ñ*<Ô*<Ñ=Ô=ˆÝ" 1Ñ%Ô%¨¬Ñ7ˆØ�|Š|‰~Œ~ð 	-Ø,Ð,Ý�e˜Q ~¸UÐCÑCÔCÐCØ�a—j’j‘l”lÐ#Ð#øÝð "ð "ð "ð
 �q”yÐ!Ð!Ð!Ð!Ð!Ð!Ð!øøøøÝð ð ð Ø•s˜1‘v”vˆÐÐÐÐÐÐøøøøðøøøs6   ’AB Á0(B Â
CÂ#B1Â+CÂ1CÂ>CÃCÃCc               #  óÞ  K  — t          ¦   «         } |                      ¦   «         sdS t          |                      ¦   «         ¦  «        D ]¤}|                     ¦   «         r#|j        dv s|j                             d¦  «        rŒ:d}t          t          j	        ¦   «         ¦  «        D ]A}||z                       ¦   «         r(	 t          ||z  ¦  «        }n# t          $ r d}Y nw xY w nŒB||fV — Œ¥dS )zFYield (dir, identity_or_None) for every engine tree in the cache root.N>   Úgeoipr  )r1   rø   Úsortedr  r  rO   r]   Úsetr   Úvaluesr   rŸ   )r  r  ÚidentÚrels       r   Úiter_cached_enginesrF  ¶  s  è è € å‰<Œ<€DØ�;Š;‰=Œ=ð ØˆÝ�D—L’L‘N”NÑ#Ô#ð ð ˆØ�xŠx‰zŒzð 	˜QœV yÐ0Ð0°A´F×4EÒ4EÀgÑ4NÔ4NÐ0ØØˆÝÕ'Ô.Ñ0Ô0Ñ1Ô1ð 	ð 	ˆCØ�C‘×ÒÑ!Ô!ð ð!Ý0°°S±Ñ9Ô9�E�EøÝ ð !ð !ð !Ø �E�E�Eð!øøøà�ðð �ˆhˆˆˆˆðð s   Â>CÃC ÃC rO   c                ó>   — | |k    p|                       |dz   ¦  «        S )a^  Does cache directory `name` hold the engine of release `tag`?

    Two layouts, one predicate: the legacy flat `<tag>` and the content-keyed
    `<tag>_<version>_<buildid>`. The separator is REQUIRED, otherwise a bare
    startswith makes `firefox-180_...` a match for a seal on `firefox-18` and
    clear_cache deletes another release's engine.
    r5   )r]   )rO   r6   s     r   Ú_tree_belongs_to_tagrH  É  s#   € ð �3Š;Ð4˜$Ÿ/š/¨#°©)Ñ4Ô4Ð4r   F)Ú
everythingrI  c               óì   — |rdn| �| nt          ¦   «         j        }g }t          ¦   «         D ]G\  }}|st          |j        |¦  «        r+t          j        |d¬¦  «         |                     |¦  «         ŒH|S )z=Remove engine trees only. Never the cache root, never geoip/.NTr  )r   r6   rF  rH  rO   r  r  rù   )r6   rI  Úwantr  r  Ú_idents         r   Úclear_cacherM  Ô  s†   € àÐRˆ4ˆ4¨3¨? C CÅÁÄÔ@Q€DØ€GÝ(Ñ*Ô*ð ð ‰	ˆˆ6Øð 	Õ-¨a¬f°dÑ;Ô;ð 	ÝŒM˜!¨4Ð0Ñ0Ô0Ð0Ø�NŠN˜1ÑÔÐøØ€Nr   )Úensure_geoip_mmdbÚgeoip_mmdb_path)r   r   )r!   r"   r   r#   )r   r   rª   )r2   r3   r   r   )r;   r   r   r   )r6   r"   r>   r"   r   r"   )r6   r"   r>   r"   rP   r"   r   r"   )r   rh   )ry   r"   r   rz   )r‚   rƒ   r   r„   )r‚   rƒ   r   r’   )rš   r"   r›   r`   )
rP   r"   r¦   r   r§   r`   r›   r`   r   rz   )r¥   N)rP   r"   r¦   r   r§   r`   r   rz   )rË   r   r   r"   )r×   r"   r   rØ   )rã   r   r¦   r   r   rz   )r2   r   r@   r   rð   r   r   rñ   )NN)r  rñ   r   r	  )NNN)r;   r   r2   r3   r   r   )r2   r3   r   r<  )rO   r"   r6   r"   r   r„   )r6   r   rI  r„   r   r	  )HÚ__doc__Ú
__future__r   rÐ   r   r+  r&   r  r   rì   r0  r¼   rè   Úpathlibr   r.   r  rH   Ú	constantsr   r   r2   r   r	   r
   r   r   r   r   r   r   r   r   r   r   r    r+   r1   r:   r=   rW   ra   rf   rl   rn   rq   ÚDOWNLOAD_ATTEMPTSr    Ú	frozensetr‹   r–   Úsleepr¡   r�   r‘   r™   rR   r°   r«   rÖ   râ   rï   r  r  r;  r>  rF  rH  rM  Ú	_geoip_dbrN  rO  r   r   r   ú<module>rX     s.  ðØ IÐ IØ "Ð "Ð "Ð "Ð "Ð "à €€€Ø 	€	€	€	Ø €€€Ø 	€	€	€	Ø €€€Ø 
€
€
€
Ø €€€Ø €€€Ø €€€Ø €€€Ø Ð Ð Ð Ð Ð à Ð Ð Ð Ø €€€Ø €€€ð >Ð =Ð =Ð =Ð =Ð =Ð =Ð =ðð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð"Wð Wð Wð Wð"ð "ð "ð "ð
Eð 
Eð 
Eð 
EðGð Gð Gð Gð Gð"ð "ð "ð "ð "ðMð Mð Mð MðD !Ð ð9ð 9ð 9ð 9ðH 6Ð Ø"Ð ðð ð ð ð@ Ð ð  Ð ð
 �9Ð;Ð;Ð;Ñ<Ô<Ð ð
 Ð ð 
Œ€ð0ð 0ð 0ð 0ðGð Gð Gð Gð$5ð 5ð 5ð 5ð  4Eð #ð #ð #ð #ð #ðL ;BÈDØ#4ð;ð ;ð ;ð ;ð ;ð,/Kð /Kð /Kð /Kð /Kðdð ð ð ðð ð ð ð	Að 	Að 	Að 	Að7ð 7ð 7ð 7ðtð ð ð ð ðDS`Ø)-ðS`ð S`ð S`ð S`ð S`ð S`ðlð ð ð ð ð(ð ð ð&5ð 5ð 5ð 5ð¸eð ð ð ð ð ð ðH :Ð 9Ð 9Ð 9Ð 9Ð 9Ð 9Ð 9Ð 9Ð 9r   