SNAPKITTYWEST commited on
Commit
28d87f8
·
verified ·
1 Parent(s): f21408d

Add src/hk_api_events_ws_h.erl

Browse files
Files changed (1) hide show
  1. src/hk_api_events_ws_h.erl +64 -0
src/hk_api_events_ws_h.erl ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ %%%-------------------------------------------------------------------
2
+ %%% @doc `/events' API domain, real-time half: `GET /api/events/stream'
3
+ %%% upgrades to a WebSocket and pushes every `hk_event' published
4
+ %%% system-wide, in publish order, for as long as the connection is
5
+ %%% open. This is what "support streaming events to the frontend
6
+ %%% using an appropriate real-time transport" means concretely --
7
+ %%% WebSocket over the same Cowboy listener as the REST API, backed
8
+ %%% directly by `hk_event_bus:subscribe/1'.
9
+ %%%
10
+ %%% An optional `?category_prefix=agent.' or `?subject_type=agent&
11
+ %%% subject_id=...' query string scopes the subscription (see
12
+ %%% `hk_event_bus:matches/2') -- used by e.g. the agent-console view
13
+ %%% to watch one agent without receiving the whole system's traffic.
14
+ %%% @end
15
+ %%%-------------------------------------------------------------------
16
+ -module(hk_api_events_ws_h).
17
+ -behaviour(cowboy_websocket).
18
+
19
+ -export([init/2, websocket_init/1, websocket_handle/2, websocket_info/2, terminate/3]).
20
+
21
+ init(Req, _State) ->
22
+ Qs = cowboy_req:parse_qs(Req),
23
+ Filter = build_filter(Qs),
24
+ {cowboy_websocket, Req, #{filter => Filter}}.
25
+
26
+ websocket_init(State = #{filter := Filter}) ->
27
+ ok = hk_event_bus:subscribe(Filter),
28
+ ok = hk_frontend_client_registry:client_connected(self()),
29
+ {[{text, jsx:encode(#{type => <<"connected">>})}], State}.
30
+
31
+ websocket_handle({text, <<"ping">>}, State) ->
32
+ {[{text, <<"pong">>}], State};
33
+ websocket_handle(_Frame, State) ->
34
+ {[], State}.
35
+
36
+ websocket_info({hk_event, EventMap}, State) ->
37
+ {[{text, jsx:encode(#{type => <<"event">>, event => EventMap})}], State};
38
+ websocket_info(_Info, State) ->
39
+ {[], State}.
40
+
41
+ terminate(_Reason, _Req, _State) ->
42
+ catch hk_event_bus:unsubscribe(),
43
+ catch hk_frontend_client_registry:client_disconnected(self()),
44
+ ok.
45
+
46
+ %%% internal
47
+
48
+ build_filter(Qs) ->
49
+ Raw = lists:foldl(
50
+ fun ({<<"category_prefix">>, V}, Acc) -> Acc#{category_prefix => V};
51
+ ({<<"subject_type">>, V}, Acc) -> Acc#{subject_type => binary_to_atom(V, utf8)};
52
+ ({<<"subject_id">>, V}, Acc) -> Acc#{subject_id => V};
53
+ (_, Acc) -> Acc
54
+ end, #{}, Qs),
55
+ finalize_subject_filter(Raw).
56
+
57
+ finalize_subject_filter(Filter) ->
58
+ case {maps:take(subject_type, Filter), maps:is_key(subject_id, Filter)} of
59
+ {{Type, Filter2}, true} ->
60
+ {SubjectId, Filter3} = maps:take(subject_id, Filter2),
61
+ Filter3#{subject => {Type, SubjectId}};
62
+ _ ->
63
+ Filter
64
+ end.