1 |
|
-module(chrme_network). |
2 |
|
|
3 |
|
-export_type([request_id/0]). |
4 |
|
|
5 |
|
-type request_id() :: klsn:binstr(). |
6 |
|
-export([enable/1, disable/1, set_request_interception/2, |
7 |
|
continue_intercepted_request/2, emulate_network_conditions/2, |
8 |
|
register_request_will_be_sent_handler/2, unregister_request_will_be_sent_handler/2, |
9 |
|
register_event_handler/2, unregister_event_handler/2, |
10 |
|
register_response_received_handler/2, unregister_response_received_handler/2, |
11 |
|
register_loading_finished_handler/2, unregister_loading_finished_handler/2, |
12 |
|
get_response_body/2, await_response_body/3, await_response_body/2]). |
13 |
|
|
14 |
|
%% Enable network tracking |
15 |
|
-spec enable(Name :: chrme_session:name()) -> {ok, map()} | {error, term()}. |
16 |
|
enable(Name) -> |
17 |
:-( |
chrme_cdp:call(Name, <<"Network.enable">>, #{}). |
18 |
|
|
19 |
|
%% Disable network tracking |
20 |
|
-spec disable(Name :: chrme_session:name()) -> {ok, map()} | {error, term()}. |
21 |
|
disable(Name) -> |
22 |
:-( |
chrme_cdp:call(Name, <<"Network.disable">>, #{}). |
23 |
|
|
24 |
|
%% Set patterns for request interception |
25 |
|
-spec set_request_interception(Name :: chrme_session:name(), Patterns :: term()) -> {ok, map()} | {error, term()}. |
26 |
|
set_request_interception(Name, Patterns) -> |
27 |
:-( |
chrme_cdp:call(Name, <<"Network.setRequestInterception">>, #{patterns => Patterns}). |
28 |
|
|
29 |
|
%% Continue an intercepted request |
30 |
|
-spec continue_intercepted_request(Name :: chrme_session:name(), RequestId :: request_id()) -> {ok, map()} | {error, term()}. |
31 |
|
continue_intercepted_request(Name, RequestId) -> |
32 |
:-( |
chrme_cdp:call(Name, <<"Network.continueInterceptedRequest">>, #{requestId => RequestId}). |
33 |
|
|
34 |
|
%% Emulate network conditions (offline, latency, throughput) |
35 |
|
-spec emulate_network_conditions(Name :: chrme_session:name(), Conditions :: term()) -> {ok, map()} | {error, term()}. |
36 |
|
emulate_network_conditions(Name, Conditions) -> |
37 |
:-( |
chrme_cdp:call(Name, <<"Network.emulateNetworkConditions">>, Conditions). |
38 |
|
|
39 |
|
%% Subscribe to requestWillBeSent events |
40 |
|
-spec register_request_will_be_sent_handler(Name :: chrme_session:name(), Fun :: fun((map()) -> any())) -> reference(). |
41 |
|
register_request_will_be_sent_handler(Name, Fun) -> |
42 |
:-( |
Ref = make_ref(), |
43 |
:-( |
CallbackName = {chrme_network, register_request_will_be_sent_handler, Name, Ref}, |
44 |
:-( |
CallbackFun = fun |
45 |
:-( |
(stop) -> false; |
46 |
|
(_Msg = #{<<"method">> := <<"Network.requestWillBeSent">>, <<"params">> := Params}) -> |
47 |
:-( |
Fun(Params), |
48 |
:-( |
true; |
49 |
:-( |
(_) -> false |
50 |
|
end, |
51 |
:-( |
chrme_ws_apic:add_callback(Name, {CallbackName, CallbackFun}), |
52 |
:-( |
Ref. |
53 |
|
|
54 |
|
%% Unsubscribe from requestWillBeSent |
55 |
|
-spec unregister_request_will_be_sent_handler(Name :: chrme_session:name(), Ref :: reference()) -> ok. |
56 |
|
unregister_request_will_be_sent_handler(Name, Ref) -> |
57 |
:-( |
CallbackName = {chrme_network, register_request_will_be_sent_handler, Name, Ref}, |
58 |
:-( |
chrme_ws_apic:remove_callback(Name, CallbackName), |
59 |
:-( |
ok. |
60 |
|
|
61 |
|
%% ------------------------------------------------------------------ |
62 |
|
%% Await response body helper |
63 |
|
%% ------------------------------------------------------------------ |
64 |
|
|
65 |
|
-spec await_response_body(chrme_session:name(), request_id(), infinity | non_neg_integer()) -> |
66 |
|
{ok, binary(), boolean()} | {error, timeout | term()}. |
67 |
|
await_response_body(Name, ReqId, infinity) -> |
68 |
|
%% Infinite wait: keep polling until success. |
69 |
:-( |
case get_response_body(Name, ReqId) of |
70 |
|
{ok, _Body, _Enc} = Success -> |
71 |
:-( |
Success; |
72 |
|
_ -> |
73 |
:-( |
timer:sleep(100), |
74 |
:-( |
await_response_body(Name, ReqId, infinity) |
75 |
|
end; |
76 |
|
|
77 |
|
await_response_body(Name, ReqId, Timeout) when is_integer(Timeout), Timeout >= 0 -> |
78 |
:-( |
Start = erlang:monotonic_time(millisecond), |
79 |
:-( |
Poll = fun This() -> |
80 |
:-( |
case get_response_body(Name, ReqId) of |
81 |
|
{ok, _Body, _Enc} = Success -> |
82 |
:-( |
Success; |
83 |
|
_ -> |
84 |
:-( |
Now = erlang:monotonic_time(millisecond), |
85 |
:-( |
case Now - Start >= Timeout of |
86 |
:-( |
true -> {error, timeout}; |
87 |
|
false -> |
88 |
:-( |
timer:sleep(100), |
89 |
:-( |
This() |
90 |
|
end |
91 |
|
end |
92 |
|
end, |
93 |
:-( |
Poll(). |
94 |
|
|
95 |
|
-spec await_response_body(chrme_session:name(), request_id()) -> |
96 |
|
{ok, binary(), boolean()} | {error, timeout | term()}. |
97 |
|
await_response_body(Name, ReqId) -> |
98 |
:-( |
await_response_body(Name, ReqId, infinity). |
99 |
|
|
100 |
|
%% ------------------------------------------------------------------ |
101 |
|
%% Convenience: fetch response body for a completed request. |
102 |
|
%% Wraps the CDP method Network.getResponseBody |
103 |
|
%% ------------------------------------------------------------------ |
104 |
|
|
105 |
|
%% Return the response body (potentially base64-encoded) for the given |
106 |
|
%% requestId. The tuple is {ok, BodyBin, IsBase64Encoded} so the caller |
107 |
|
%% can decide whether to decode. |
108 |
|
|
109 |
|
-spec get_response_body(Name :: chrme_session:name(), |
110 |
|
RequestId :: request_id()) -> |
111 |
|
{ok, binary(), boolean()} | {error, term()}. |
112 |
|
get_response_body(Name, RequestId) -> |
113 |
:-( |
case chrme_cdp:call(Name, <<"Network.getResponseBody">>, #{requestId => RequestId}) of |
114 |
|
{ok, #{<<"body">> := Body, <<"base64Encoded">> := Encoded}} -> |
115 |
:-( |
{ok, Body, Encoded}; |
116 |
|
Other -> |
117 |
:-( |
Other |
118 |
|
end. |
119 |
|
|
120 |
|
%% ------------------------------------------------------------------ |
121 |
|
%% New convenience: listen to *all* Network domain events. |
122 |
|
%% ------------------------------------------------------------------ |
123 |
|
|
124 |
|
%% Subscribe to every "Network.*" event. |
125 |
|
-spec register_event_handler(Name :: chrme_session:name(), |
126 |
|
Fun :: fun((map()) -> any())) -> reference(). |
127 |
|
register_event_handler(Name, Fun) -> |
128 |
:-( |
Ref = make_ref(), |
129 |
:-( |
CallbackName = {chrme_network, register_event_handler, Name, Ref}, |
130 |
:-( |
CallbackFun = fun |
131 |
|
(stop) -> |
132 |
:-( |
false; |
133 |
|
(Msg = #{<<"method">> := Method}) -> |
134 |
:-( |
case Method of |
135 |
|
<<"Network.", _/binary>> -> |
136 |
:-( |
Fun(Msg), |
137 |
:-( |
true; |
138 |
|
_ -> |
139 |
:-( |
false |
140 |
|
end; |
141 |
|
(_) -> |
142 |
:-( |
false |
143 |
|
end, |
144 |
:-( |
chrme_ws_apic:add_callback(Name, {CallbackName, CallbackFun}), |
145 |
:-( |
Ref. |
146 |
|
|
147 |
|
%% Unsubscribe from all Network.* events. |
148 |
|
-spec unregister_event_handler(Name :: chrme_session:name(), Ref :: reference()) -> ok. |
149 |
|
unregister_event_handler(Name, Ref) -> |
150 |
:-( |
CallbackName = {chrme_network, register_event_handler, Name, Ref}, |
151 |
:-( |
chrme_ws_apic:remove_callback(Name, CallbackName), |
152 |
:-( |
ok. |
153 |
|
|
154 |
|
%% ------------------------------------------------------------------ |
155 |
|
%% One-off helpers for other common Network events |
156 |
|
%% ------------------------------------------------------------------ |
157 |
|
|
158 |
|
%% responseReceived -------------------------------------------------- |
159 |
|
|
160 |
|
-spec register_response_received_handler(chrme_session:name(), fun((map()) -> any())) -> reference(). |
161 |
|
register_response_received_handler(Name, Fun) -> |
162 |
:-( |
Ref = make_ref(), |
163 |
:-( |
CbName = {chrme_network, response_received, Name, Ref}, |
164 |
:-( |
CbFun = fun |
165 |
:-( |
(stop) -> false; |
166 |
|
(#{<<"method">> := <<"Network.responseReceived">>, <<"params">> := Params}) -> |
167 |
:-( |
Fun(Params), |
168 |
:-( |
true; |
169 |
:-( |
(_) -> false |
170 |
|
end, |
171 |
:-( |
chrme_ws_apic:add_callback(Name, {CbName, CbFun}), |
172 |
:-( |
Ref. |
173 |
|
|
174 |
|
-spec unregister_response_received_handler(chrme_session:name(), reference()) -> ok. |
175 |
|
unregister_response_received_handler(Name, Ref) -> |
176 |
:-( |
CbName = {chrme_network, response_received, Name, Ref}, |
177 |
:-( |
chrme_ws_apic:remove_callback(Name, CbName), |
178 |
:-( |
ok. |
179 |
|
|
180 |
|
%% loadingFinished --------------------------------------------------- |
181 |
|
|
182 |
|
-spec register_loading_finished_handler(chrme_session:name(), fun((map()) -> any())) -> reference(). |
183 |
|
register_loading_finished_handler(Name, Fun) -> |
184 |
:-( |
Ref = make_ref(), |
185 |
:-( |
CbName = {chrme_network, loading_finished, Name, Ref}, |
186 |
:-( |
CbFun = fun |
187 |
:-( |
(stop) -> false; |
188 |
|
(#{<<"method">> := <<"Network.loadingFinished">>, <<"params">> := Params}) -> |
189 |
:-( |
Fun(Params), |
190 |
:-( |
true; |
191 |
:-( |
(_) -> false |
192 |
|
end, |
193 |
:-( |
chrme_ws_apic:add_callback(Name, {CbName, CbFun}), |
194 |
:-( |
Ref. |
195 |
|
|
196 |
|
-spec unregister_loading_finished_handler(chrme_session:name(), reference()) -> ok. |
197 |
|
unregister_loading_finished_handler(Name, Ref) -> |
198 |
:-( |
CbName = {chrme_network, loading_finished, Name, Ref}, |
199 |
:-( |
chrme_ws_apic:remove_callback(Name, CbName), |
200 |
:-( |
ok. |