├── README.md ├── .gitignore ├── LICENSE └── chrome.go /README.md: -------------------------------------------------------------------------------- 1 | # gochrome 2 | ======= 3 | Google Chrome Remote Debugging Protocol 1.1 client for Golang. 4 | 5 | 6 | 7 | # License 8 | 9 | MIT 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Khalid Lafi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /chrome.go: -------------------------------------------------------------------------------- 1 | package gochrome 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | 10 | "github.com/gorilla/websocket" 11 | ) 12 | 13 | var TabNotFound = errors.New("Tab not found") 14 | 15 | type Command struct { 16 | Id int `json:"id"` 17 | Method string `json:"method"` 18 | Params Parameters `json:"params"` 19 | } 20 | 21 | type Parameters map[string]interface{} 22 | 23 | type gtab struct { 24 | Description string `json:"description"` 25 | DevtoolsFrontendUrl string `json:"devtoolsFrontendUrl"` 26 | FaviconUrl string `json:"faviconUrl"` 27 | Id string `json:"id"` 28 | Title string `json:"title"` 29 | Type string `json:"type"` 30 | Url string `json:"url"` 31 | WebSocketDebuggerUrl string `json:"webSocketDebuggerUrl"` 32 | } 33 | 34 | type Chrome struct { 35 | c *websocket.Conn 36 | listeners map[string][]chan Message 37 | } 38 | 39 | type Message struct { 40 | Method string `json:"method"` 41 | Params map[string]interface{} 42 | } 43 | 44 | type Result struct { 45 | Id int `json:"id"` 46 | Error map[string]interface{} `json:"error"` 47 | Result map[string]interface{} `json:"result"` 48 | } 49 | 50 | func New(url string, tab int) (*Chrome, error) { 51 | url, err := getTab(url, tab) 52 | c, err := newClient(url) 53 | if err != nil { 54 | return nil, err 55 | } 56 | ch := &Chrome{c, make(map[string][]chan Message, 0)} 57 | go ch.readMessages() 58 | return ch, err 59 | } 60 | 61 | func (ch *Chrome) Send(co Command) error { 62 | message, err := json.Marshal(co) 63 | if err != nil { 64 | return err 65 | } 66 | err = ch.c.WriteMessage(1, message) 67 | return err 68 | } 69 | 70 | func (ch *Chrome) readMessages() { 71 | for { 72 | _, r, err := ch.c.ReadMessage() 73 | if err != nil { 74 | continue 75 | } 76 | 77 | m := Message{} 78 | err = json.Unmarshal(r, &m) 79 | if err != nil { 80 | fmt.Println(err) 81 | continue 82 | } 83 | go ch.broadMsgs(m) 84 | } 85 | } 86 | 87 | // Runs in its own goroutine, to brodacst msgs 88 | func (ch *Chrome) broadMsgs(m Message) { 89 | lc, ok := ch.listeners[m.Method] 90 | if !ok { 91 | return 92 | } 93 | 94 | for c := range lc { 95 | lc[c] <- m 96 | } 97 | } 98 | 99 | // Listen on a certain event 100 | func (ch *Chrome) On(e string, c chan Message) (ok bool) { 101 | if _, ok := ch.listeners[e]; ok { 102 | ch.listeners[e] = append(ch.listeners[e], c) 103 | return true 104 | } 105 | ch.listeners[e] = append(make([]chan Message, 0), c) 106 | return true 107 | } 108 | 109 | // Remove a listener 110 | func (ch *Chrome) Off(e string, c chan Message) { 111 | lc, ok := ch.listeners[e] 112 | if !ok { 113 | return 114 | } 115 | 116 | for i := range lc { 117 | if lc[i] == c { 118 | lc = append(lc[:i], lc[i+1:]...) 119 | break 120 | } 121 | } 122 | } 123 | 124 | func (ch *Chrome) Close() error { 125 | return ch.c.Close() 126 | } 127 | 128 | func getTab(url string, tab int) (string, error) { 129 | resp, err := http.Get(url + "/json") 130 | if err != nil { 131 | return "", err 132 | } 133 | body, err := ioutil.ReadAll(resp.Body) 134 | 135 | resp.Body.Close() 136 | 137 | t := []gtab{} 138 | err = json.Unmarshal(body, &t) 139 | if err != nil { 140 | return "", err 141 | } 142 | if len(t)-1 < tab { 143 | return "", TabNotFound 144 | } 145 | 146 | return t[tab].WebSocketDebuggerUrl, nil 147 | } 148 | 149 | func newClient(url string) (*websocket.Conn, error) { 150 | r, _ := http.NewRequest("GET", url, nil) 151 | r.Header.Add("Content-Type", "application/json") 152 | c, _, err := websocket.DefaultDialer.Dial(url, r.Header) 153 | if err != nil { 154 | return nil, err 155 | } 156 | return c, nil 157 | } 158 | --------------------------------------------------------------------------------