 		@listeners = {}
 	on: (ev, cb) ->
 		(@listeners[ev] ?= []).push cb
+	removeListener: (ev, cb) ->
+		return unless @listeners and @listeners[ev]
+		@listeners[ev] = (l for l in @listeners[ev] when l != cb)
+	once: (ev, cb) ->
+		@on ev, f = (args...) =>
+			@removeListener ev, f
+			cb(args...)
 	emit: (ev, args...) ->
 		l(args...) for l in (@listeners[ev] ? [])
 
 	connect: (port, host='localhost') ->
+		@_active()
 		go = (err, addr) =>
 			throw new Error("couldn't resolve: " + err) if err
 			console.log "Resolved #{host} to #{addr}"
+			@_active()
 			options = onEvent: @_onEvent
 			chrome.experimental.socket.create 'tcp', options, (si) =>
 				@socketId = si.socketId


	_onRead: (readInfo) =>
		console.log "onRead", readInfo
		if readInfo.resultCode < -1 # -1 is EWOULDBLOCK
			@emit 'error', readInfo.resultCode
		else if readInfo.resultCode is 0


			chrome.experimental.socket.read @socketId, @_onRead

	_active: ->
		if @timeout
			clearTimeout @timeout
			@timeout = setTimeout (=> @emit 'timeout'), @timeout_ms

	setTimeout: (ms, cb) ->
		if ms > 0
			@timeout = setTimeout (=> @emit 'timeout'), ms
			@timeout_ms = ms
			@once 'timeout', cb if cb
		else if ms == 0
			clearTimeout @timeout
			@removeListener 'timeout', cb if cb
			@timeout = null
			@timeout_ms = 0

	@resolve: (host, cb) ->
		chrome.experimental.dns.resolve host, (res) ->
			if res.resultCode is 0
