forked from GoogleChrome/chrome-extensions-samples
-
Notifications
You must be signed in to change notification settings - Fork 0
/
nodejs-net.coffee
117 lines (103 loc) · 3.56 KB
/
nodejs-net.coffee
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
# This is a clone of node.js's net.Socket API made to work using Chrome's TCP
# socket API.
# [http://nodejs.org/api/net.html]
exports = window.net = {}
# TCP socket.
# Events emitted:
# - 'connect': the connection succeeded, proceed.
# - 'data': data received. Argument is the data (array of longs, atm)
# - 'end': the other end sent a FIN packet, and won't accept any more data.
# - 'error': an error occurred. The socket is pretty much hosed now. (TODO:
# investigate how node deals with errors. The docs say 'close' gets sent right
# after 'error', so they probably destroy the socket.)
# - 'close': emitted when the socket is fully closed.
# TODO: 'drain': emitted when the write buffer becomes empty
class Socket
constructor: ->
@listeners = {}
on: (ev, cb) ->
(@listeners[ev] ?= []).push cb
removeListener: (ev, cb) ->
return unless @listeners and @listeners[ev] and cb?
@listeners[ev] = (l for l in @listeners[ev] when l != cb and l.listener != cb)
once: (ev, cb) ->
@on ev, f = (args...) =>
@removeListener ev, f
cb(args...)
f.listener = cb
emit: (ev, args...) ->
l(args...) for l in (@listeners[ev] ? [])
connect: (port, host='localhost') ->
@_active()
go = (err, addr) =>
return @emit 'error', "couldn't resolve: #{err}" if err
@_active()
chrome.experimental.socket.create 'tcp', {}, (si) =>
@socketId = si.socketId
if @socketId > 0
chrome.experimental.socket.connect @socketId, addr, port, @_onConnect
else
return @emit 'error', "couldn't create socket"
if /^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$/.test host
go null, host
else
Socket.resolve host, go
_onConnect: (rc) =>
if rc < 0
# TODO: I'm pretty sure we should never get a -1 here..
# TODO: we should destroy the socket when we get an error.
@emit 'error', rc
else
@emit 'connect'
chrome.experimental.socket.read @socketId, @_onRead
_onRead: (readInfo) =>
console.error "Bad assumption: got -1 in _onRead" if readInfo.resultCode is -1
console.log readInfo
@_active()
if readInfo.resultCode < 0
@emit 'error', readInfo.resultCode
else if readInfo.resultCode is 0
@emit 'end'
@destroy() # TODO: half-open sockets
if readInfo.data.byteLength
@emit 'data', readInfo.data
chrome.experimental.socket.read @socketId, @_onRead
write: (data) ->
@_active()
chrome.experimental.socket.write @socketId, data, (writeInfo) =>
if writeInfo.resultCode < 0
console.error "SOCKET ERROR on write: ", writeInfo.resultCode
console.log "Wrote #{writeInfo.bytesWritten} of #{data.byteLength} bytes"
if writeInfo.bytesWritten == data.byteLength
@emit 'drain' # TODO not sure if this works, don't rely on this message
else
console.error "Waaah can't handle non-complete writes"
# looks to me like there's no equivalent to node's end() in the socket API
destroy: ->
chrome.experimental.socket.disconnect @socketId
@emit 'close' # TODO: figure out whether i should emit 'end' as well?
end: ->
# TODO: only half-close the socket
chrome.experimental.socket.disconnect @socketId
@emit 'close'
_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
cb(null, res.address)
else
cb(res.resultCode)
exports.Socket = Socket