func (c *ChromeTarget) checkTargetDisconnected(method string) {
	switch method {
	case "Inspector.targetCrashed", "Inspector.detached":
		c.replyLock.Lock()
		for _, replyCh := range c.replyDispatcher {
			close(replyCh)
		}
		c.replyDispatcher = make(map[int64]chan *gcdmessage.Message)
		c.replyLock.Unlock()
	}
}
check target detached/crashed
close any replier channels that are open
dispatch detached / crashed event as usual
package gcd
import (
	"encoding/json"
	"io"
	"log"
	"net"
	"sync"
	"sync/atomic"
	"time"
	"github.com/wirepair/gcd/gcdapi"
	"github.com/wirepair/gcd/gcdmessage"
	"golang.org/x/net/websocket"
)
type TargetInfo struct {
	Description          string `json:"description"`
	DevtoolsFrontendUrl  string `json:"devtoolsFrontendUrl"`
	FaviconUrl           string `json:"faviconUrl"`
	Id                   string `json:"id"`
	Title                string `json:"title"`
	Type                 string `json:"type"`
	Url                  string `json:"url"`
	WebSocketDebuggerUrl string `json:"webSocketDebuggerUrl"`
}
type ChromeTarget struct {
	replyLock       sync.RWMutex                            
	replyDispatcher map[int64]chan *gcdmessage.Message      
	eventLock       sync.RWMutex                            
	eventDispatcher map[string]func(*ChromeTarget, []byte)  
	conn            *websocket.Conn                         
	Accessibility        *gcdapi.Accessibility
	Animation            *gcdapi.Animation
	ApplicationCache     *gcdapi.ApplicationCache  
	Browser              *gcdapi.Browser
	CacheStorage         *gcdapi.CacheStorage
	Console              *gcdapi.Console            
	CSS                  *gcdapi.CSS                
	Database             *gcdapi.Database           
	Debugger             *gcdapi.Debugger           
	DeviceOrientation    *gcdapi.DeviceOrientation  
	DOMDebugger          *gcdapi.DOMDebugger        
	DOM                  *gcdapi.DOM                
	DOMSnapshot          *gcdapi.DOMSnapshot
	DOMStorage           *gcdapi.DOMStorage  
	Emulation            *gcdapi.Emulation
	HeadlessExperimental *gcdapi.HeadlessExperimental
	HeapProfiler         *gcdapi.HeapProfiler  
	IndexedDB            *gcdapi.IndexedDB     
	Input                *gcdapi.Input         
	Inspector            *gcdapi.Inspector
	IO                   *gcdapi.IO
	LayerTree            *gcdapi.LayerTree
	Log                  *gcdapi.Log
	Memory               *gcdapi.Memory
	Network              *gcdapi.Network
	Overlay              *gcdapi.Overlay
	Page                 *gcdapi.Page
	Profiler             *gcdapi.Profiler
	Performance          *gcdapi.Performance  
	Runtime              *gcdapi.Runtime
	Schema               *gcdapi.Schema
	Security             *gcdapi.Security
	ServiceWorker        *gcdapi.ServiceWorker
	Storage              *gcdapi.Storage
	SystemInfo           *gcdapi.SystemInfo
	TargetApi            *gcdapi.Target  
	Tracing              *gcdapi.Tracing
	Tethering            *gcdapi.Tethering
	Testing              *gcdapi.Testing
	Fetch                *gcdapi.Fetch
	Target      *TargetInfo               
	sendCh      chan *gcdmessage.Message  
	doneCh      chan struct{}             
	apiTimeout  time.Duration             
	sendId      int64                     
	debugEvents bool                      
	debug       bool                      
	stopped     bool                      
}
func openChromeTarget(addr string, target *TargetInfo) (*ChromeTarget, error) {
	conn, err := wsConnection(addr, target.WebSocketDebuggerUrl)
	if err != nil {
		return nil, err
	}
	replier := make(map[int64]chan *gcdmessage.Message)
	eventer := make(map[string]func(*ChromeTarget, []byte))
	sendCh := make(chan *gcdmessage.Message)
	doneCh := make(chan struct{})
	chromeTarget := &ChromeTarget{conn: conn, Target: target, sendCh: sendCh, replyDispatcher: replier, eventDispatcher: eventer, doneCh: doneCh, sendId: 0}
	chromeTarget.apiTimeout = 120 * time.Second  
	chromeTarget.Init()
	chromeTarget.listen()
	return chromeTarget, nil
}
func (c *ChromeTarget) Init() {
	c.Accessibility = gcdapi.NewAccessibility(c)
	c.Animation = gcdapi.NewAnimation(c)
	c.ApplicationCache = gcdapi.NewApplicationCache(c)
	c.Browser = gcdapi.NewBrowser(c)
	c.CacheStorage = gcdapi.NewCacheStorage(c)
	c.Console = gcdapi.NewConsole(c)
	c.CSS = gcdapi.NewCSS(c)
	c.Database = gcdapi.NewDatabase(c)
	c.Debugger = gcdapi.NewDebugger(c)
	c.DeviceOrientation = gcdapi.NewDeviceOrientation(c)
	c.DOMDebugger = gcdapi.NewDOMDebugger(c)
	c.DOM = gcdapi.NewDOM(c)
	c.DOMSnapshot = gcdapi.NewDOMSnapshot(c)
	c.DOMStorage = gcdapi.NewDOMStorage(c)
	c.Emulation = gcdapi.NewEmulation(c)
	c.HeapProfiler = gcdapi.NewHeapProfiler(c)
	c.IndexedDB = gcdapi.NewIndexedDB(c)
	c.Input = gcdapi.NewInput(c)
	c.Inspector = gcdapi.NewInspector(c)
	c.IO = gcdapi.NewIO(c)
	c.LayerTree = gcdapi.NewLayerTree(c)
	c.Memory = gcdapi.NewMemory(c)
	c.Log = gcdapi.NewLog(c)
	c.Network = gcdapi.NewNetwork(c)
	c.Overlay = gcdapi.NewOverlay(c)
	c.Page = gcdapi.NewPage(c)
	c.Profiler = gcdapi.NewProfiler(c)
	c.Runtime = gcdapi.NewRuntime(c)
	c.Schema = gcdapi.NewSchema(c)
	c.Security = gcdapi.NewSecurity(c)
	c.SystemInfo = gcdapi.NewSystemInfo(c)
	c.ServiceWorker = gcdapi.NewServiceWorker(c)
	c.TargetApi = gcdapi.NewTarget(c)
	c.Tracing = gcdapi.NewTracing(c)
	c.Tethering = gcdapi.NewTethering(c)
	c.HeadlessExperimental = gcdapi.NewHeadlessExperimental(c)
	c.Performance = gcdapi.NewPerformance(c)
	c.Testing = gcdapi.NewTesting(c)
	c.Fetch = gcdapi.NewFetch(c)
}
func (c *ChromeTarget) shutdown() {
	if c.stopped == true {
		return
	}
	c.stopped = true
	close(c.doneCh)
	c.conn.Close()
}
func (c *ChromeTarget) SetApiTimeout(timeout time.Duration) {
	c.apiTimeout = timeout
}
func (c *ChromeTarget) GetApiTimeout() time.Duration {
	return c.apiTimeout
}
func (c *ChromeTarget) Subscribe(method string, callback func(*ChromeTarget, []byte)) {
	c.eventLock.Lock()
	c.eventDispatcher[method] = callback
	c.eventLock.Unlock()
}
func (c *ChromeTarget) Unsubscribe(method string) {
	c.eventLock.Lock()
	delete(c.eventDispatcher, method)
	c.eventLock.Unlock()
}
func (c *ChromeTarget) DebugEvents(debug bool) {
	c.debugEvents = debug
}
func (c *ChromeTarget) Debug(debug bool) {
	c.debug = debug
}
func (c *ChromeTarget) listen() {
	go c.listenRead()
	go c.listenWrite()
}
func (c *ChromeTarget) listenWrite() {
	for {
		select {
		case msg := <-c.sendCh:
			c.replyLock.Lock()
			c.replyDispatcher[msg.Id] = msg.ReplyCh
			c.replyLock.Unlock()
			c.debugf("%d sending to chrome. %s\n", msg.Id, msg.Data)
			err := websocket.Message.Send(c.conn, string(msg.Data))
			if err != nil {
				c.debugf("error sending message: %s\n", err)
				return
			}
		case <-c.doneCh:
			return
		}
	}
}
func (c *ChromeTarget) listenRead() {
	for {
		select {
		case <-c.doneCh:
			return
		default:
			var msg string
			err := websocket.Message.Receive(c.conn, &msg)
			if err == io.EOF {
				c.debugf("error io.EOF in websocket read")
				return
			} else if err != nil {
				c.debugf("error in ws read: %s\n", err)
			} else {
				go c.dispatchResponse([]byte(msg))
			}
		}
	}
}
type responseHeader struct {
	Id     int64  `json:"id"`
	Method string `json:"method"`
}
func (c *ChromeTarget) dispatchResponse(msg []byte) {
	f := &responseHeader{}
	err := json.Unmarshal(msg, f)
	if err != nil {
		c.debugf("error reading response data from chrome target: %v\n", err)
	}
	c.replyLock.Lock()
	if r, ok := c.replyDispatcher[f.Id]; ok {
		delete(c.replyDispatcher, f.Id)
		c.replyLock.Unlock()
		c.debugf("%d dispatching\n", f.Id)
		c.dispatchWithTimeout(r, f.Id, msg)
		return
	}
	c.replyLock.Unlock()
	c.checkTargetDisconnected(f.Method)
	c.eventLock.RLock()
	if r, ok := c.eventDispatcher[f.Method]; ok {
		c.eventLock.RUnlock()
		c.debugf("dispatching %s event: %s\n", f.Method, string(msg))
		go r(c, msg)
		return
	}
	c.eventLock.RUnlock()
	if c.debugEvents == true {
		log.Printf("\n\nno event recv bound for: %s\n", f.Method)
		log.Printf("data: %s\n\n", string(msg))
	}
}
func (c *ChromeTarget) dispatchWithTimeout(r chan<- *gcdmessage.Message, id int64, msg []byte) {
	timeout := time.NewTimer(c.GetApiTimeout())
	defer timeout.Stop()
	select {
	case r <- &gcdmessage.Message{Id: id, Data: msg}:
		timeout.Stop()
	case <-timeout.C:
		c.debugf("timed out dispatching request id: %d of msg: %s\n", id, msg)
		close(r)
		return
	}
	return
}