hyperkitty-chromium / src /hk_api_browser_h.erl
SNAPKITTYWEST's picture
Add src/hk_api_browser_h.erl
e18b22f verified
Raw
History Blame Contribute Delete
7.54 kB
%%%-------------------------------------------------------------------
%%% @doc `/browser' API domain -- a direct, session/tab-scoped path
%%% to the same typed operations `hk_agent_toolbox' calls for an
%%% agent. This is for operator/manual use (opening a session from
%%% the frontend's "browser" view without an agent involved) and for
%%% tests; it goes through the *same* `hk_browser_session' module
%%% and therefore the *same* structured events as agent-driven
%%% browser use -- there is exactly one code path that talks to
%%% Chromium, regardless of who asked.
%%%
%%% POST /api/browser/sessions create
%%% GET /api/browser/sessions list
%%% GET /api/browser/sessions/:id describe
%%% POST /api/browser/sessions/:id/close close
%%% POST /api/browser/sessions/:id/tabs open a tab
%%% POST /api/browser/sessions/:id/tabs/:tab_id/navigate navigate
%%% POST /api/browser/sessions/:id/tabs/:tab_id/actions {op, args}:
%%% op in back|forward|reload|read_page|query_element|click|type|
%%% scroll|screenshot|extract_links|close
%%% @end
%%%-------------------------------------------------------------------
-module(hk_api_browser_h).
-export([init/2]).
init(Req0 = #{method := <<"POST">>}, collection) ->
RequestId = hk_api_util:request_id(Req0),
{ok, Body, Req1} = hk_api_util:read_json_body(Req0),
OwnerAgentId = maps:get(<<"owner_agent_id">>, Body, undefined),
Profile = maps:get(<<"profile">>, Body, <<"manual">>),
Req = hk_api_util:run_operation(browser_create_session, RequestId, fun() ->
SessionId = hk_id:new(<<"sess">>),
case hk_browser_session_sup:start_session(#{session_id => SessionId,
owner_agent_id => OwnerAgentId, profile => Profile}) of
{ok, _Pid} -> {ok, {browser_session, SessionId}, #{session_id => SessionId}};
{error, Reason} -> {error, Reason}
end
end, Req1),
{ok, Req, collection};
init(Req0 = #{method := <<"GET">>}, collection) ->
Sessions = [describe_or_skip(Id) || {Id, _Pid} <- hk_browser_registry:list_sessions()],
Req = hk_api_util:reply_json(200, #{sessions => [M || {true, M} <- Sessions]}, Req0),
{ok, Req, collection};
init(Req0 = #{method := <<"GET">>}, item) ->
SessionId = cowboy_req:binding(id, Req0),
Req = case hk_browser_registry:lookup(SessionId) of
{ok, Pid} ->
{ok, Session} = hk_browser_session:describe(Pid),
hk_api_util:reply_json(200, hk_schema:to_map(Session), Req0);
{error, not_found} ->
hk_api_util:reply_error(404, session_not_found, Req0)
end,
{ok, Req, item};
init(Req0 = #{method := <<"POST">>}, close) ->
SessionId = cowboy_req:binding(id, Req0),
RequestId = hk_api_util:request_id(Req0),
Req = hk_api_util:run_operation(browser_close_session, RequestId, fun() ->
with_session(SessionId, fun(Pid) ->
hk_browser_session:close_session(Pid),
{ok, {browser_session, SessionId}, #{}}
end)
end, Req0),
{ok, Req, close};
init(Req0 = #{method := <<"POST">>}, tabs) ->
SessionId = cowboy_req:binding(id, Req0),
RequestId = hk_api_util:request_id(Req0),
{ok, Body, Req1} = hk_api_util:read_json_body(Req0),
Url = maps:get(<<"url">>, Body, <<"about:blank">>),
Req = hk_api_util:run_operation(browser_open_tab, RequestId, fun() ->
with_session(SessionId, fun(Pid) ->
case hk_browser_session:open_tab(Pid, Url) of
{ok, TabId} -> {ok, {browser_tab, TabId}, #{tab_id => TabId}};
{error, Reason} -> {error, Reason}
end
end)
end, Req1),
{ok, Req, tabs};
init(Req0 = #{method := <<"POST">>}, navigate) ->
SessionId = cowboy_req:binding(id, Req0),
TabId = cowboy_req:binding(tab_id, Req0),
RequestId = hk_api_util:request_id(Req0),
{ok, Body, Req1} = hk_api_util:read_json_body(Req0),
Url = maps:get(<<"url">>, Body, <<>>),
Req = hk_api_util:run_operation(browser_navigate, RequestId, fun() ->
with_session(SessionId, fun(Pid) ->
case hk_browser_session:navigate(Pid, TabId, Url) of
ok -> {ok, {browser_tab, TabId}, #{}};
{error, Reason} -> {error, Reason}
end
end)
end, Req1),
{ok, Req, navigate};
init(Req0 = #{method := <<"POST">>}, actions) ->
SessionId = cowboy_req:binding(id, Req0),
TabId = cowboy_req:binding(tab_id, Req0),
RequestId = hk_api_util:request_id(Req0),
{ok, Body, Req1} = hk_api_util:read_json_body(Req0),
Op = binary_to_atom(maps:get(<<"op">>, Body, <<"read_page">>), utf8),
Args = maps:get(<<"args">>, Body, #{}),
Req = hk_api_util:run_operation(browser_tab_action, RequestId, fun() ->
with_session(SessionId, fun(Pid) -> dispatch(Pid, Op, TabId, Args) end)
end, Req1),
{ok, Req, actions};
init(Req0, State) ->
Req = hk_api_util:reply_error(405, method_not_allowed, Req0),
{ok, Req, State}.
%%% internal
with_session(undefined, _Fun) -> {error, missing_session_id};
with_session(SessionId, Fun) ->
case hk_browser_registry:lookup(SessionId) of
{ok, Pid} -> Fun(Pid);
{error, not_found} -> {error, session_not_found}
end.
dispatch(Pid, back, TabId, _Args) -> as_op(hk_browser_session:back(Pid, TabId), TabId);
dispatch(Pid, forward, TabId, _Args) -> as_op(hk_browser_session:forward(Pid, TabId), TabId);
dispatch(Pid, reload, TabId, _Args) -> as_op(hk_browser_session:reload(Pid, TabId), TabId);
dispatch(Pid, close, TabId, _Args) -> as_op(hk_browser_session:close_tab(Pid, TabId), TabId);
dispatch(Pid, read_page, TabId, _Args) ->
as_result(hk_browser_session:read_page(Pid, TabId), TabId, text);
dispatch(Pid, query_element, TabId, Args) ->
Selector = maps:get(<<"selector">>, Args, <<>>),
as_result(hk_browser_session:query_element(Pid, TabId, Selector), TabId, element);
dispatch(Pid, click, TabId, Args) ->
Selector = maps:get(<<"selector">>, Args, <<>>),
as_result(hk_browser_session:click(Pid, TabId, Selector), TabId, click);
dispatch(Pid, type, TabId, Args) ->
Selector = maps:get(<<"selector">>, Args, <<>>),
Text = maps:get(<<"text">>, Args, <<>>),
as_result(hk_browser_session:type(Pid, TabId, Selector, Text), TabId, type);
dispatch(Pid, scroll, TabId, Args) ->
Dy = maps:get(<<"delta_y">>, Args, 0),
as_result(hk_browser_session:scroll(Pid, TabId, Dy), TabId, scroll);
dispatch(Pid, screenshot, TabId, _Args) ->
as_result(hk_browser_session:screenshot(Pid, TabId), TabId, screenshot_base64);
dispatch(Pid, extract_links, TabId, _Args) ->
as_result(hk_browser_session:extract_links(Pid, TabId), TabId, links);
dispatch(_Pid, Op, _TabId, _Args) ->
{error, {unsupported_op, Op}}.
as_op(ok, TabId) -> {ok, {browser_tab, TabId}, #{}};
as_op({error, Reason}, _TabId) -> {error, Reason}.
as_result({ok, Value}, TabId, Key) -> {ok, {browser_tab, TabId}, #{Key => Value}};
as_result({error, Reason}, _TabId, _Key) -> {error, Reason}.
describe_or_skip(SessionId) ->
case hk_browser_registry:lookup(SessionId) of
{ok, Pid} ->
case catch hk_browser_session:describe(Pid) of
{ok, Session} -> {true, hk_schema:to_map(Session)};
_ -> false
end;
_ -> false
end.