§
    ®	Êj°i  ã                  óÎ  — d Z ddlmZ ddlZddlmZmZmZm	Z
 ddlmZ ddlmZmZ ddlmZmZmZ dd	lmZmZmZ dd
lmZmZmZmZ g d¢ZdZdCdDd„Z	 ddlm Z!m"Z" nA# e#$ r9Z$ddl%m&Z' 	  e'¦   «         pdZ(n# e)$ r dZ(Y nw xY w e#de(› de$› d�¦  «        e$‚dZ$[$ww xY wdddddœdEd#„Z*ddd$œdFd1„Z+ G d2„ d3e,¦  «        Z- G d4„ d5e,¦  «        Z.d6Z/d7Z0d8Z1d9d:œdGd@„Z2 G dA„ dB¦  «        Z3dS )HaG  Session logic shared by the sync and async entry points.

WHY THIS FILE EXISTS. There are two `InvisiblePlaywright` classes, and the only
thing keeping them equal was somebody remembering. Measured 2026-07-27: 203 of
252 normalised lines of `async_api.py` also appear in `launcher.py` - 80.6% -
and `__init__`, `_teardown`, `_build_env` and `_default_context_kwargs` are 100%
identical once the word `await` is deleted. So the duplication is not the price
of async; async was the excuse for it.

That is a defect GENERATOR, and it has already produced three:

  * the 0.4.0 process-leak fix reached the sync class only, and shipped to the
    other half of the users under a release note saying it was fixed;
  * `INVPW_TRUE_HEADLESS`, a documented env var, worked on the async API alone;
  * `_build_prefs` was a method on one side and twenty inline lines on the other.

Anything here must stay free of Playwright imports: it is the part that has
nothing to do with which API a caller picked. What legitimately differs between
the two classes is `await`, and nothing else belongs in this file.
é    )ÚannotationsNé   )ÚENGINE_BINARYÚENGINE_PYTHONÚ
enable_forÚmax_seconds_for)Úresolve_executable)ÚMOTION_BUDGET_PREFÚSESSION_SEED_PREF)ÚAnyÚDictÚOptional)Úcompose_session_prefsÚcontext_geometryÚmake_virtual_display)ÚFontManifestMismatchÚbuild_launch_envÚcached_font_manifest_pathÚverify_font_manifest)Úbuild_prefsÚtrue_headless_requestedÚTRUE_HEADLESS_ENVÚProxyEgressDriftedÚegress_ancora_validoÚINVPW_TRUE_HEADLESSÚenvúOptional[Dict[str, str]]ÚreturnÚboolc                óZ   — | �| nt           j                             t          ¦  «        dk    S )NÚ1)ÚosÚenvironÚgetr   )r   s    úMC:\Projects\Muse2API\.venv\Lib\site-packages\invisible_playwright/_session.pyr   r   D   s&   € Ø�?ˆCˆC­¬
×7Ò7Õ8IÑJÔJÈcÒQÐQó    )ÚIANA_TO_POSIX_TZÚtz_env)Údeclared_core_pinza newer versionz*invisible-playwright needs invisible-core zS: the installed one does not provide the timezone conversion this version imports (za). Upgrade with `pip install -U invisible-playwright`, which pulls the core it was built against.)ÚprofileÚ
executableÚbase_envÚdisplay_envÚtimezoneúOptional[str]Úsrflx_declaredr*   r   r+   r,   r-   ú"Optional[Dict[str, Optional[str]]]úDict[str, str]c           	     ó  — d}t          t          |dd¦  «        dd¦  «        }|rQ|r@t          ||¦  «        }|r.t          dt          |¦  «        › d|dd…         › d|› d	�¦  «        ‚t	          |¦  «        }t          i | ||||¬
¦  «        S )aÌ  The environment the Firefox subprocess is launched with, minus the token.

    The session token is stamped by the caller, because it is the one part that
    is genuinely per-session; everything here was written twice, identically,
    in `launcher._build_env` and `async_api._build_env`.

    Fonts DO need an env, and only an env will do. The engine builds its font
    list during app startup, inside InitSharedFontListForPlatform, and on this
    path the prefs never reach the profile at all - Playwright sends them over
    the wire and JS applies them once the browser is already running. Measured
    2026-08-08: the pref of the same name always read empty there, so the
    engine's packaged copy won and the manifest this package declares was
    inert. An environment variable is set at process creation and inherited by
    every content process.

    `profile=None` sets nothing, which leaves the engine on its own copy - the
    floor that keeps a browser launched without this package rendering.

    What is composed here is only what needs the EXECUTABLE: the manifest is
    verified against the engine's own fonts before its path is handed over.
    The environment itself is composed by the core's `build_launch_env`, the
    one place that knows TZ, the WebRTC declaration, the manifest variable and
    the hidden surface's `launch_env()`; until 0.25.2 this function was a twin
    of that body, and the hidden-surface half of the contract lived only here.
    NÚfontÚmanifestÚ z.the font manifest this package declares names z. face file(s) the engine does not carry (e.g. é   z). Engine: z6. The metrics would describe fonts that are not there.)r.   r0   Úmanifest_pathr,   r-   )Úgetattrr   r   Úlenr   r   )	r.   r0   r*   r+   r,   r-   r8   r5   Úmissings	            r%   Ú	build_envr<   l   sä   € ð` €MÝ•w˜w¨°Ñ5Ô5°zÀ2ÑFÔF€HØð <ð ð 	AÝ*¨8°ZÑ@Ô@ˆGð ð AÝ*ð@Ý˜7‘|”|ð@ð @à$ R a Rœ[ð@ð @à5?ð@ð @ð @ñAô Að Aõ
 2°(Ñ;Ô;ˆÝ˜B¨À.Ø*7À(Ø(3ð5ñ 5ô 5ð 5r&   )Úshow_cursorÚsession_seedÚlocaleÚextra_prefsúOptional[Dict[str, Any]]Úvirtual_displayÚcursor_engineÚstrÚhumanizer=   úOptional[bool]r>   úOptional[int]úDict[str, Any]c        	   
     ó  — t          | |||||t          k    rt          |¦  «        nd|¬¦  «        j        }	|�M|rKt	          |¦  «        |	t
          <   t	          t          t          |¦  «        dz  ¦  «        ¦  «        |	t          <   |	S )a&  Fingerprint prefs plus the humanize toggle, which is always set explicitly.

    Takes values rather than a session object so it stays callable from a test
    without constructing either class - and so that adding a field to one class
    cannot silently change what the other one builds.
    F)r?   r.   r@   rB   rE   r=   Ng     @�@)r   r   Ú_cursor_max_secondsÚprefsÚintr   Úroundr
   )
r*   r?   r.   r@   rB   rC   rE   r=   r>   rK   s
             r%   r   r   ·   sª   € õX "ØØØØð (à"¥mÒ3Ð3õ & hÑ/Ô/Ð/Ø9>Øðñ ô ô ð 
ðT Ð HÐÝ#& |Ñ#4Ô#4ˆÕÑ Ý$'­Ý Ñ)Ô)¨FÑ2ñ)4ô )4ñ %5ô %5ˆÕ Ñ!à€Lr&   c                  ó   — e Zd ZdZdS )r   af  The egress IP changed MID-SESSION.

    This is not a condition the product can recover from, and it is deliberate
    that it is an error instead of a silent update.

    The IP we declare to the engine for the WebRTC srflx candidate is
    discovered ONCE, at launch. If the egress changes afterwards, the page
    exits from one address and WebRTC announces another: it is exactly the
    comparison detectors make ("WebRTC IP doesn't match your Remote IP"), and
    no Firefox on a real connection produces it.

    Updating the value on the fly would be worse, not better: the site would
    see the WebRTC IP change before its own eyes mid-session, which is just as
    unnatural a signal. If the egress does not hold for the duration of the
    session, that proxy is not sticky and is not usable for this purpose.

    Measured on 2026-08-25: the two providers tried both declare sessions
    sticky by TIME (60 minutes at most for one, a sliding timeout for the
    other, which decays even earlier if the residential peer disconnects), so
    on a long enough session the drift is not a risk: it is a certainty.
    N©Ú__name__Ú
__module__Ú__qualname__Ú__doc__© r&   r%   r   r     s   € € € € € ðð ð ð r&   r   c                  ó   — e Zd ZdZdS )ÚProxyEgressNonVerificabilean  The egress could not be MEASURED, repeatedly.

    This is not drift and it is not parity: it is absence of measurement. A
    proxy that does not answer the probe for several checks in a row is not a
    proxy that holds, it is a proxy we are flying blind on while the engine
    keeps declaring to every page an address that nobody is confirming any
    more.
    NrO   rT   r&   r%   rV   rV   ,  s   € € € € € ðð ð ð r&   rV   ÚreggeÚderivataÚnon_misurabileé   ©ÚtimeoutÚproxyÚattesor\   rL   ú'tuple[str, Optional[str]]'c               ó¸   — | r|s	t           dfS ddlm} 	 |                     | |¬¦  «        }n# t          $ r t
          dfcY S w xY w||k    rt           nt          |fS )uÁ  (outcome, current_ip), with outcome among the three `USCITA_*`.

    â›” THERE ARE THREE OUTCOMES BECAUSE TWO LIE. Until 2026-08-25 this function
    returned `(True, None)` when the probe FAILED, with a comment that argued
    the right half of the thing well - "a failed discovery is not a drift",
    and that is true, a network problem is not turned into an accusation
    against the proxy. But the returned value said `regge` (holds), i.e. it
    **asserted parity on the basis of a measurement that had not taken
    place**.

    It is the same class of defect this project has already paid for twice
    and fixed twice:

    - `fppro_consistency.py` printed CONSISTENCY PASS when `visitor_id` was
      mute in BOTH runs, because two `None`s come out identical. It has had a
      third outcome since 2026-08-15, exit code 2 = NOT INTERPRETABLE.
    - `repair_core` set `verdict = None` under a handler that said "a broken
      probe is not a licence", above a test `verdict is not None` that then
      let the reinstall proceed.

    The caller now distinguishes: on `USCITA_DERIVATA` it refuses right away,
    on `USCITA_NON_MISURABILE` it counts and refuses only if it repeats,
    because a probe that fails once is the network and one that always fails
    is blindness.

    The case with no proxy or no expected value stays `USCITA_REGGE`, and it
    is different from the other two: there is nothing to betray there, no
    measurement was missed.
    Nr   )Ú_geor[   )ÚUSCITA_REGGEÚinvisible_corera   Údiscover_egress_ipÚ	ExceptionÚUSCITA_NON_MISURABILEÚUSCITA_DERIVATA)r]   r^   r\   ra   Úattuales        r%   r   r   >  s•   € ð@ ð "˜ð "Ý˜TÐ!Ð!Ø#Ð#Ð#Ð#Ð#Ð#ð+Ø×)Ò)¨%¸Ð)ÑAÔAˆˆøÝð +ð +ð +Ý$ dÐ*Ð*Ð*Ð*ð+øøøà# vÒ-Ð-�LˆLµ?ÀWÐLÐLs   •- ­AÁAc                  óB   — e Zd ZdZdd„Zdd„Zdd	„Zdd
„Zdd„Zdd„Z	dS )ÚCommonLaunchuÐ  The six methods both entry points had, written once.

    â›” THE DUPLICATION THIS CLOSES IS THE ONE THIS FILE WAS CREATED FOR, and it
    survived the file's own creation. `_session` was written on 2026-07-27 to
    hold what the sync and async classes share, and it did extract the
    functions - `build_env`, `build_prefs`, `true_headless_requested`. What it
    left behind were the METHODS that call them: six of them, in both classes,
    with bodies that are identical byte for byte once the docstrings are
    removed. 222 lines saying the same thing twice.

    The measurement that says it is the same thing: the two classes use the
    SAME FOURTEEN attributes of `self` across those six methods, and the ASTs
    of the bodies compare equal. What made them look different - similarity
    ratios between 32% and 69% - was entirely comments worded differently.

    â›” AND THE COST OF THE SPLIT IS NOT HYPOTHETICAL. The three defects listed
    at the top of this file all have the same shape, and one of them is in the
    code that moved here: `INVPW_TRUE_HEADLESS` was honoured by the async class
    alone, so a documented environment variable worked or not depending on
    which entry point the caller had picked.

    â›” WHAT THIS CLASS EXPECTS, said out loud because a mixin's contract is
    otherwise invisible: both subclasses set `seed`, `_binary_path`,
    `_cursor_engine`, `_extra_prefs`, `_headless`, `_humanize`,
    `_lifetime_guard`, `_locale` (the core's SessionLocale, None until
    `__enter__` decides it), `_profile`, `_session_token`, `_show_cursor`,
    `_srflx_declared`, `_timezone` and `_virtual_display` in their own
    `__init__`. They already did, identically, which is why this works at all.
    r   r   c                óŽ   — | j         sdS t          ¦   «         rdS t          ¦   «         }|�|                     ¦   «          || _        dS )uf  Translate the user's ``headless`` flag.

        When ``True``, Firefox stays in headed mode (real rendering pipeline â†’
        coherent fingerprint) and the window is hidden by drawing on a screen
        nobody looks at: on Linux a fresh Xvfb spawned here; on Windows a
        fresh Win32 desktop the spawner creates the browser on
        (``STARTUPINFO.lpDesktop``, read from ``INVPW_DESKTOP``). The engine
        is stock on this surface: no pref hides anything.

        ``self._virtual_display`` is the fact ``_build_prefs`` reads to decide
        whether the desktop workarounds apply (B172).
        FT)Ú	_headlessr   r   ÚstartÚ_virtual_display)ÚselfÚvds     r%   Ú_resolve_headlesszCommonLaunch._resolve_headless‡  sR   € ð Œ~ð 	Ø�5õ
 #Ñ$Ô$ð 	Ø�4Ý!Ñ#Ô#ˆØˆ>Ø�HŠH‰JŒJˆJØ$&ˆDÔ!Øˆur&   rH   c                ó€   — i t          | j        ¦  «        ¥}| j        r
| j        |d<   | j        �| j        j        |d<   |S )NÚtimezone_idr?   )r   Ú_profileÚ	_timezoneÚ_localeÚprimary)ro   Úkwargss     r%   Ú_default_context_kwargsz$CommonLaunch._default_context_kwargs¢  sQ   € ð
"
Ý˜tœ}Ñ-Ô-ð"
ˆð Œ>ð 	3Ø$(¤NˆF�=Ñ!ð Œ<Ð#Ø#œ|Ô3ˆF�8ÑØˆr&   rK   r2   c           
     óÖ   — | j         }| j                             t          | j        | j        | j        t          | j        ¦  «        |�| 	                    ¦   «         nd¬¦  «        ¦  «        S )a  Env for the Firefox subprocess, then stamped with this session's token.

        The body is `build_env`, shared with the async class - it was
        written twice, identically, and the WebRTC pair is a contract with the
        binary, so two landing sites meant two chances to miss a change.

        The token stamp stays here because it is the only genuinely per-session
        part: children inherit the environment, so every process in the tree
        carries it and teardown can find its own tree and only its own.
        N)r.   r0   r*   r+   r-   )
rn   Ú_session_tokenÚstampr<   ru   Ú_srflx_declaredrt   r	   Ú_binary_pathÚ
launch_env)ro   rK   rp   s      r%   Ú
_build_envzCommonLaunch._build_envÀ  so   € ð Ô"ˆØÔ"×(Ò(Ý˜tœ~Ø%)Ô%9Ø"œmÝ!3°DÔ4EÑ!FÔ!FØ57°^ "§-¢-¡/¤/ /Èð	Oñ Oô OñPô Pð 	Pr&   c                ó�   — t          | j        | j        | j        | j        | j        du| j        | j        | j        | j	        ¬¦	  «	        S )at  Fingerprint prefs plus humanize toggle (always set explicitly).

        The body lives in `build_prefs`, which the async class calls
        too. It used to be twenty lines here and the SAME twenty inlined into
        `async_api.__aenter__` - identical calls in identical order, differing
        only in their comments, which is how the two entry points drift.
        N)	r*   r?   r.   r@   rB   rC   rE   r=   r>   )
r   rt   rv   ru   Ú_extra_prefsrn   Ú_cursor_engineÚ	_humanizeÚ_show_cursorÚseed©ro   s    r%   Ú_build_prefszCommonLaunch._build_prefsÓ  sQ   € õ Ø”MØ”<Ø”^ØÔ)Ø Ô1¸Ð=ØÔ-Ø”^ØÔ)Øœð

ñ 

ô 

ð 
	
r&   Úownerr   ÚNonec                ó|   — | j         t          k    rdS t          || j        t	          | j        ¦  «        ¬¦  «         dS )a=  Register this session so its pages move through the Python generator.

        Registered on the browser (or on the persistent context, which is all
        there is in that mode) rather than on each page: pages appear by
        several routes we do not control - ``browser.new_page()`` builds its
        context inside the driver, and a site can open a popup on its own - and
        every one of them can find its way back to this owner. The seed is the
        session seed, so a replayed seed replays the cursor exactly as it
        replays the fingerprint.
        N)r†   Úmax_seconds)rƒ   r   Ú_enable_cursor_enginer†   rJ   r„   )ro   r‰   s     r%   Ú_arm_cursor_enginezCommonLaunch._arm_cursor_engineç  sL   € ð Ô¥-Ò/Ð/ØˆFÝØ˜œ	Õ/BÀ4Ä>Ñ/RÔ/Rð	
ñ 	
ô 	
ð 	
ð 	
ð 	
r&   c                óh   — 	 | j                              | j        ¦  «         dS # t          $ r Y dS w xY w)ao  Tie the browser tree to this process's lifetime, at the OS level.

        MEASURED before being written, because the first attempt at this fixed
        a path that was not broken: an exception out of the `with` block does
        NOT leak - __exit__ runs and Playwright cleans up, zero survivors over
        an interleaved A/B. The leak is the killed-runner path, where __exit__
        never executes at all: launch, kill the runner, and eight processes
        were still alive; twelve on the second attempt. Nothing written inside
        _teardown can reach that, so the guarantee comes from a Windows job
        object that the kernel empties when this process's handle closes,
        however this process ends.

        Best-effort by construction: a failure here leaves the pre-existing
        behaviour rather than breaking a launch that is otherwise fine.
        N)Ú_lifetime_guardÚbindr{   re   r‡   s    r%   Ú_bind_process_treezCommonLaunch._bind_process_treeø  sI   € ð 	ØÔ ×%Ò% dÔ&9Ñ:Ô:Ð:Ð:Ð:øÝð 	ð 	ð 	ØˆDˆDð	øøøs   ‚# £
1°1N)r   r   )r   rH   )rK   rH   r   r2   )r‰   r   r   rŠ   )r   rŠ   )
rP   rQ   rR   rS   rq   ry   r€   rˆ   rŽ   r’   rT   r&   r%   rj   rj   h  sš   € € € € € ðð ð<ð ð ð ð6ð ð ð ð<Pð Pð Pð Pð&
ð 
ð 
ð 
ð(
ð 
ð 
ð 
ð"ð ð ð ð ð r&   rj   )N)r   r   r   r   )r.   r/   r0   r/   r*   r   r+   r/   r,   r   r-   r1   r   r2   )r*   r   r?   r   r.   r/   r@   rA   rB   r   rC   rD   rE   r   r=   rF   r>   rG   r   rH   )r]   r   r^   r/   r\   rL   r   r_   )4rS   Ú
__future__r   r"   Ú_cursorr   r   r   r�   r   rJ   Ú_enginer	   Ú_juggler.serverr
   r   Útypingr   r   r   rc   r   r   r   Úinvisible_core.launchr   r   r   r   Ú__all__r   r   r'   Ú_IANA_TO_POSIX_TZr(   ÚImportErrorÚ_excÚ_pinr)   Ú_declared_core_pinÚ_wantre   r<   r   ÚRuntimeErrorr   rV   rb   rg   rf   r   rj   rT   r&   r%   ú<module>r¡      sQ  ððð ð( #Ð "Ð "Ð "Ð "Ð "à 	€	€	€	ð>ð >ð >ð >ð >ð >ð >ð >ð >ð >ð >ð >ð (Ð 'Ð 'Ð 'Ð 'Ð 'Ø BÐ BÐ BÐ BÐ BÐ BÐ BÐ BØ &Ð &Ð &Ð &Ð &Ð &Ð &Ð &Ð &Ð &à XÐ XÐ XÐ XÐ XÐ XÐ XÐ XÐ XÐ Xð9ð 9ð 9ð 9ð 9ð 9ð 9ð 9ð 9ð 9ð 9ð 9ð
9ð 9ð 9€ð6 *Ð ðRð Rð Rð Rð Rð*ðð ð ð ð ð ð ð ð øð ð ð ð Ø=Ð=Ð=Ð=Ð=Ð=ð"Ø"Ð"Ñ$Ô$Ð9Ð(9ˆˆøØð "ð "ð "Ø!ˆˆˆð"øøøà
ˆ+ð	0°Uð 	0ð 	0àð	0ð 	0ð 	0ñô ð
 ðøøøøðøøøð0 Ø $Ø)-ð 7;ð+H5ð H5ð H5ð H5ð H5ð H5ðh #'Ø"&ðZð Zð Zð Zð Zð Zðzð ð ð ð ˜ñ ô ð ð0ð ð ð ð  ñ ô ð ð €Ø€Ø(Ð ð
 ,.ð'Mð 'Mð 'Mð 'Mð 'Mð 'MðTcð cð cð cð cñ cô cð cð cð cs<   ÁA ÁBÁ!BÁ(A5Á4BÁ5A?Á<BÁ>A?Á?BÂB