-
Notifications
You must be signed in to change notification settings - Fork 20
/
connection.clj
148 lines (127 loc) · 5.1 KB
/
connection.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
(ns clj-chrome-devtools.impl.connection
"The remote debugging WebSocket connection"
(:require [gniazdo.core :as ws]
[org.httpkit.client :as http]
[cheshire.core :as cheshire]
[clj-chrome-devtools.impl.util :refer [camel->clojure]]
[clojure.core.async :as async]
[clojure.string :as str]
[taoensso.timbre :as log]))
(defrecord Connection [ws-connection requests event-chan event-pub])
(defn connection? [c]
(instance? Connection c))
(defonce current-connection (atom nil))
(defn get-current-connection []
(let [c @current-connection]
(assert c "No current Chrome Devtools connection!")
c))
(defn- parse-json [string]
(cheshire/parse-string string (comp keyword camel->clojure)))
(defn- publish-event [event-chan msg]
(let [[domain event] (map (comp keyword camel->clojure)
(str/split (:method msg) #"\."))
event {:domain domain
:event event
:params (:params msg)}]
(async/go
(async/>! event-chan event))))
(defn listen [{event-pub :event-pub} domain event]
(let [ch (async/chan)]
(async/sub event-pub [domain event] ch)
ch))
(defn unlisten [{event-pub :event-pub} domain event listening-ch]
(async/unsub event-pub [domain event] listening-ch))
(defn- on-receive [requests event-chan msg]
(try
(let [{id :id :as json-msg} (parse-json msg)]
(if id
;; Has id: this is a response to our previously sent command
(let [callback (@requests id)]
(swap! requests dissoc id)
(async/thread (callback json-msg)))
;; This is an event
(publish-event event-chan json-msg)))
(catch Throwable t
(log/error "Exception in devtools WebSocket receive, msg: " msg
", throwable: " t))))
(defn- wait-for-remote-debugging-port [host port max-wait-time-ms]
(let [wait-until (+ (System/currentTimeMillis) max-wait-time-ms)
url (str "http://" host ":" port "/json/version")]
(loop [response @(http/head url)]
(cond
(= (:status response) 200)
:ok
(> (System/currentTimeMillis) wait-until)
(throw (ex-info "Chrome remote debugging port not up"
{:host host :port port
:max-wait-time-ms max-wait-time-ms}))
:default
(do (Thread/sleep 100)
(recur @(http/head url)))))))
(defn inspectable-pages
"Collect the list of inspectable pages returned by the DevTools protocol."
([host port]
(inspectable-pages host port 1000))
([host port max-wait-time-ms]
(wait-for-remote-debugging-port host port max-wait-time-ms)
(let [response @(http/get (str "http://" host ":" port "/json/list"))]
(some->> response
:body
parse-json))))
(defn- first-inspectable-page [pages]
(or (some->> pages
(filter (comp #{"page"} :type))
(keep :web-socket-debugger-url)
first)
(throw (ex-info "No debuggable pages found"
{:pages pages}))))
(defn make-ws-client
"Constructs ws client. Idle timeout defaults to 0, which means keep it
alive for the session. The `max-msg-size-mb` defaults to 1MB."
[ & [{:keys [idle-timeout max-msg-size-mb]
:or {idle-timeout 0
max-msg-size-mb (* 1024 1024)}}]]
(let [client (ws/client)]
(doto (.getPolicy client)
(.setIdleTimeout idle-timeout)
(.setMaxTextMessageSize max-msg-size-mb))
client))
(defn connect-url
"Establish a websocket connection to web-socket-debugger-url, as given by
inspectable-pages."
[web-socket-debugger-url & [ws-client]]
(assert web-socket-debugger-url)
(let [client (or ws-client
(make-ws-client))
;; Request id to callback
requests (atom {})
;; Event pub/sub channel
event-chan (async/chan)
event-pub (async/pub event-chan (juxt :domain :event))]
(.start client)
(->Connection (ws/connect web-socket-debugger-url
:on-receive (partial on-receive requests event-chan)
:client client)
requests
event-chan
event-pub)))
(defn connect
"Establish a websocket connection to the DevTools protocol at host:port.
Selects the first inspectable page returned; to attach to a specific page,
see connect-url. Initial connection will be retried for up to
max-wait-time-ms milliseconds (default 1000) before giving up."
([]
(connect "localhost" 9222))
([host port]
(connect host port 1000))
([host port max-wait-time-ms]
(connect host port max-wait-time-ms (make-ws-client)))
([host port max-wait-time-ms ws-client]
(when max-wait-time-ms
(wait-for-remote-debugging-port host port max-wait-time-ms))
(let [url (-> (inspectable-pages host port)
(first-inspectable-page))]
(connect-url url ws-client))))
(defn send-command [{requests :requests con :ws-connection} payload id callback]
(swap! requests assoc id callback)
(ws/send-msg con (cheshire/encode payload)))