Download src/hk_chrome_http.erl from Snapkitty/hyperkitty-chromium: direct link, hf CLI and curl.
- Browser
- Download file 3.99 kB
-
https://huggingface.co/Snapkitty/hyperkitty-chromium/resolve/main/src/hk_chrome_http.erl
- Command line
-
hf download hf://Snapkitty/hyperkitty-chromium/src/hk_chrome_http.erl
-
curl -L -o hk_chrome_http.erl https://huggingface.co/Snapkitty/hyperkitty-chromium/resolve/main/src/hk_chrome_http.erl
3.99 kB
| % SPDX-License-Identifier: LicenseRef-Sovereign-Leviathan-SL-AGPL3-001 | |
| % Licensing: see LICENSE in the repository root. | |
| %%%------------------------------------------------------------------- | |
| %%% @doc HK-BROWSER :: Chrome's `/json' HTTP control endpoint. | |
| %%% | |
| %%% Chromium, started with `--remote-debugging-port', exposes a | |
| %%% small plain-HTTP API on that port for target (tab) lifecycle -- | |
| %%% separate from the per-target CDP WebSocket that `hk_cdp_client' | |
| %%% speaks. This module wraps that HTTP surface with `httpc' (part | |
| %%% of `inets', already a runtime dependency) so `hk_browser_session' | |
| %%% doesn't need its own HTTP client for this one-off, low-volume | |
| %%% traffic. | |
| %%% @end | |
| %%%------------------------------------------------------------------- | |
| -module(hk_chrome_http). | |
| -export([wait_for_ready/2, new_target/2, close_target/2, version/1]). | |
| -define(POLL_INTERVAL_MS, 100). | |
| %% @doc Poll `/json/version' until Chrome's debug HTTP server answers | |
| %% or `TimeoutMs' elapses. Used right after launching the Chrome OS | |
| %% process, whose startup time is not instantaneous or guaranteed. | |
| -spec wait_for_ready(inet:port_number(), timeout()) -> ok | {error, timeout}. | |
| wait_for_ready(Port, TimeoutMs) -> | |
| Deadline = erlang:monotonic_time(millisecond) + TimeoutMs, | |
| wait_loop(Port, Deadline). | |
| wait_loop(Port, Deadline) -> | |
| case version(Port) of | |
| {ok, _} -> ok; | |
| {error, _} -> | |
| case erlang:monotonic_time(millisecond) >= Deadline of | |
| true -> {error, timeout}; | |
| false -> | |
| timer:sleep(?POLL_INTERVAL_MS), | |
| wait_loop(Port, Deadline) | |
| end | |
| end. | |
| -spec version(inet:port_number()) -> {ok, map()} | {error, term()}. | |
| version(Port) -> | |
| get_json("http://127.0.0.1:" ++ integer_to_list(Port) ++ "/json/version"). | |
| %% @doc Create a new target (tab) navigated to `Url'; returns its | |
| %% CDP target id and WebSocket debugger URL. | |
| -spec new_target(inet:port_number(), binary()) -> | |
| {ok, #{target_id => binary(), ws_url => binary()}} | {error, term()}. | |
| new_target(Port, Url) -> | |
| Encoded = uri_string:quote(binary_to_list(Url)), | |
| TargetUrl = "http://127.0.0.1:" ++ integer_to_list(Port) ++ "/json/new?" ++ Encoded, | |
| %% Chrome >= 111 rejects GET on /json/new with 405 Method Not | |
| %% Allowed (a CSRF-style hardening: creating a target must not be | |
| %% triggerable by a plain cross-origin GET/link) and requires PUT | |
| %% instead. `httpc:request/4' (not the shorthand `/1') is needed to | |
| %% pass a method other than the default. | |
| case httpc:request(put, {TargetUrl, [], "text/plain", <<>>}, [{timeout, 5000}], []) of | |
| {ok, {{_, 200, _}, _, Body}} -> | |
| case (catch jsx:decode(list_to_binary(Body), [return_maps])) of | |
| #{<<"id">> := TargetId, <<"webSocketDebuggerUrl">> := WsUrl} -> | |
| {ok, #{target_id => TargetId, ws_url => WsUrl}}; | |
| Other -> | |
| {error, {unexpected_response, Other}} | |
| end; | |
| {ok, {{_, Status, _}, _, _}} -> | |
| {error, {http_status, Status}}; | |
| {error, Reason} -> | |
| {error, Reason} | |
| end. | |
| -spec close_target(inet:port_number(), binary()) -> ok | {error, term()}. | |
| close_target(Port, TargetId) -> | |
| Url = "http://127.0.0.1:" ++ integer_to_list(Port) ++ "/json/close/" | |
| ++ binary_to_list(TargetId), | |
| case httpc:request(get, {Url, []}, [], []) of | |
| {ok, {{_, 200, _}, _, _}} -> ok; | |
| {ok, {{_, Status, _}, _, _}} -> {error, {http_status, Status}}; | |
| {error, Reason} -> {error, Reason} | |
| end. | |
| %%% internal | |
| get_json(Url) -> | |
| case httpc:request(get, {Url, []}, [{timeout, 5000}], []) of | |
| {ok, {{_, 200, _}, _, Body}} -> | |
| try {ok, jsx:decode(list_to_binary(Body), [return_maps])} | |
| catch _:_ -> {error, invalid_json} end; | |
| {ok, {{_, Status, _}, _, _}} -> | |
| {error, {http_status, Status}}; | |
| {error, Reason} -> | |
| {error, Reason} | |
| end. | |