PageRenderTime 45ms CodeModel.GetById 33ms app.highlight 9ms RepoModel.GetById 1ms app.codeStats 0ms

/src/socket.js

https://github.com/astro/bitford
JavaScript | 223 lines | 189 code | 30 blank | 4 comment | 41 complexity | 70a05776bbe88849adfaddb8eb2cc2de MD5 | raw file
  1var Socket = chrome.socket || chrome.experimental.socket;
  2
  3function BaseSocket(sockId) {
  4    this.sockId = sockId;
  5
  6    this.paused = true;
  7    this.readPending = false;
  8}
  9
 10BaseSocket.prototype = {
 11    getInfo: function(cb) {
 12	chrome.socket.getInfo(this.sockId, cb);
 13    },
 14
 15    end: function() {
 16	if (!this.sockId)
 17	    return;
 18
 19	if (this.onEnd)
 20	    this.onEnd();
 21	Socket.destroy(this.sockId);
 22
 23	delete this.sockId;
 24    },
 25
 26    pause: function() {
 27	this.paused = true;
 28    },
 29
 30    resume: function() {
 31	this.paused = false;
 32	this.read();
 33    },
 34
 35    connect: function(host, port, cb) {
 36	chrome.socket.connect(this.sockId, host, port, function(res) {
 37	    if (res === 0)
 38		cb(null);
 39	    else
 40		cb(new Error("Connect: " + res));
 41	});
 42    },
 43
 44    read: function() {
 45	if (this.paused || this.readPending)
 46	    return;
 47	this.readPending = true;
 48	Socket.read(this.sockId, this.readLength, function(readInfo) {
 49	    this.readPending = false;
 50	    this.readLength = undefined;
 51	    if (readInfo.resultCode < 0)
 52		return this.end();
 53	    if (readInfo.data && this.onData) {
 54		try {
 55		    this.onData(readInfo.data);
 56		    /* onData() could have closed it */
 57		    if (this.sockId)
 58			this.read();
 59		} catch (e) {
 60		    console.error(e.stack || e.message || e);
 61		    this.end();
 62		}
 63	    }
 64	}.bind(this));
 65    }
 66};
 67
 68/* Creates paused sockets */
 69function createTCPServer(host, port, acceptCb, listenCb) {
 70    var backlog = 1;
 71
 72    Socket.create('tcp', {}, function(createInfo) {
 73	var sockId = createInfo.socketId;
 74	Socket.listen(sockId, host, port, backlog, function(res) {
 75	    function loop() {
 76		Socket.accept(sockId, function(acceptInfo) {
 77		    var sockId = acceptInfo.socketId;
 78		    if (sockId) {
 79			var sock = new TCPSocket(sockId);
 80			acceptCb(sock);
 81		    }
 82		    loop();
 83		});
 84	    }
 85	    if (res >= 0) {
 86		if (listenCb)
 87		    listenCb();
 88		loop();
 89	    } else {
 90		console.error("Cannot listen on", host, ":", port, ":", res);
 91		if (listenCb)
 92		    listenCb(new Error("Listen: " + res));
 93	    }
 94	});
 95    });
 96}
 97
 98function tryCreateTCPServer(port, acceptCb, listenCb) {
 99    var attempt = 0;
100    function doTry() {
101	attempt++;
102	createTCPServer("::", port, acceptCb, function(err) {
103	    if (err) {
104		if (attempt < 100) {
105		    port += 1 + Math.floor(7 * Math.random());
106		    doTry();
107		} else {
108		    listenCb(err);
109		}
110	    } else {
111		listenCb(null, port);
112	    }
113	});
114    }
115    doTry();
116}
117
118/* Creates paused sockets */
119function connectTCP(host, port, cb) {
120    Socket.create('tcp', {}, function(createInfo) {
121	var sock = new TCPSocket(createInfo.socketId);
122	sock.connect(host, port, function(err) {
123	    cb(err, err ? null : sock);
124	});
125    });
126}
127
128/* TODO: use an event emitter */
129function TCPSocket(sockId) {
130    BaseSocket.call(this, sockId);
131
132    this.writesPending = 0;
133    this.drained = true;
134}
135
136TCPSocket.prototype = Object.create(BaseSocket.prototype);
137TCPSocket.prototype.constructor = TCPSocket;
138
139TCPSocket.prototype.write = function(data) {
140	if (!this.sockId)
141	    return;
142
143	if (typeof data === 'string')
144	    data = strToUTF8Arr(data);
145	if (data.buffer)
146	    data = data.buffer;
147
148	Socket.write(this.sockId, data, function(writeInfo) {
149	    if (writeInfo.bytesWritten < 0) {
150		console.warn("Write to socket", this.sockId, ":", writeInfo.bytesWritten);
151		return this.end();
152	    }
153	    this.writesPending--;
154
155	    if (this.writesPending < 1 && this.sockId) {
156		this.drained = true;
157		if (this.onDrain)
158		    this.onDrain();
159	    }
160	}.bind(this));
161	this.writesPending++;
162	this.drained = false;
163};
164
165TCPSocket.prototype.end = function() {
166	if (this.sockId) {
167	    Socket.disconnect(this.sockId);
168	    BaseSocket.prototype.end.call(this, arguments);
169	}
170};
171
172
173function connectUDP(host, port, cb) {
174    Socket.create('udp', {}, function(createInfo) {
175	var sock = new UDPSocket(createInfo.socketId);
176	sock.connect(host, port, function(err) {
177	    try {
178		cb(err, err ? null : sock);
179	    } catch (e) {
180		console.warn(e.stack || e.message || e);
181	    }
182	});
183    });
184}
185
186function UDPSocket(sockId) {
187    BaseSocket.call(this, sockId);
188}
189
190UDPSocket.prototype = Object.create(BaseSocket.prototype);
191UDPSocket.prototype.constructor = UDPSocket;
192
193UDPSocket.prototype.write = function(data) {
194	if (!this.sockId)
195	    return;
196
197	if (typeof data === 'string')
198	    data = strToUTF8Arr(data);
199	else if (data.buffer)
200		data = data.buffer;
201
202	Socket.write(this.sockId, data, function(writeInfo) {
203	    if (writeInfo.bytesWritten < 0) {
204		console.warn("Write to socket", this.sockId, ":", writeInfo.bytesWritten);
205		return this.end();
206	    }
207	}.bind(this));
208};
209
210UDPSocket.prototype.recvLoop = function() {
211    chrome.socket.recvFrom(this.sockId, function(recvFromInfo) {
212	if (recvFromInfo.resultCode > 0 && this.onData) {
213	    try {
214		this.onData(recvFromInfo.data, recvFromInfo.address, recvFromInfo.port);
215	    } catch (e) {
216		console.error(e.stack || e.message || e);
217	    }
218	    this.recvLoop();
219	} else {
220	    console.warn("UDPSocket", this.sockId, "recvFrom", recvFromInfo);
221	}
222    }.bind(this));
223};