UNPKG

5.41 kBJavaScriptView Raw
1
2/**
3 * Module dependencies.
4 */
5
6var parser = require('socket.io-parser');
7var debug = require('debug')('socket.io:client');
8var url = require('url');
9
10/**
11 * Module exports.
12 */
13
14module.exports = Client;
15
16/**
17 * Client constructor.
18 *
19 * @param {Server} server instance
20 * @param {Socket} conn
21 * @api private
22 */
23
24function Client(server, conn){
25 this.server = server;
26 this.conn = conn;
27 this.encoder = new parser.Encoder();
28 this.decoder = new parser.Decoder();
29 this.id = conn.id;
30 this.request = conn.request;
31 this.setup();
32 this.sockets = {};
33 this.nsps = {};
34 this.connectBuffer = [];
35}
36
37/**
38 * Sets up event listeners.
39 *
40 * @api private
41 */
42
43Client.prototype.setup = function(){
44 this.onclose = this.onclose.bind(this);
45 this.ondata = this.ondata.bind(this);
46 this.onerror = this.onerror.bind(this);
47 this.ondecoded = this.ondecoded.bind(this);
48
49 this.decoder.on('decoded', this.ondecoded);
50 this.conn.on('data', this.ondata);
51 this.conn.on('error', this.onerror);
52 this.conn.on('close', this.onclose);
53};
54
55/**
56 * Connects a client to a namespace.
57 *
58 * @param {String} name namespace
59 * @api private
60 */
61
62Client.prototype.connect = function(name, query){
63 debug('connecting to namespace %s', name);
64 var nsp = this.server.nsps[name];
65 if (!nsp) {
66 this.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'});
67 return;
68 }
69
70 if ('/' != name && !this.nsps['/']) {
71 this.connectBuffer.push(name);
72 return;
73 }
74
75 var self = this;
76 var socket = nsp.add(this, query, function(){
77 self.sockets[socket.id] = socket;
78 self.nsps[nsp.name] = socket;
79
80 if ('/' == nsp.name && self.connectBuffer.length > 0) {
81 self.connectBuffer.forEach(self.connect, self);
82 self.connectBuffer = [];
83 }
84 });
85};
86
87/**
88 * Disconnects from all namespaces and closes transport.
89 *
90 * @api private
91 */
92
93Client.prototype.disconnect = function(){
94 for (var id in this.sockets) {
95 if (this.sockets.hasOwnProperty(id)) {
96 this.sockets[id].disconnect();
97 }
98 }
99 this.sockets = {};
100 this.close();
101};
102
103/**
104 * Removes a socket. Called by each `Socket`.
105 *
106 * @api private
107 */
108
109Client.prototype.remove = function(socket){
110 if (this.sockets.hasOwnProperty(socket.id)) {
111 var nsp = this.sockets[socket.id].nsp.name;
112 delete this.sockets[socket.id];
113 delete this.nsps[nsp];
114 } else {
115 debug('ignoring remove for %s', socket.id);
116 }
117};
118
119/**
120 * Closes the underlying connection.
121 *
122 * @api private
123 */
124
125Client.prototype.close = function(){
126 if ('open' == this.conn.readyState) {
127 debug('forcing transport close');
128 this.conn.close();
129 this.onclose('forced server close');
130 }
131};
132
133/**
134 * Writes a packet to the transport.
135 *
136 * @param {Object} packet object
137 * @param {Object} opts
138 * @api private
139 */
140
141Client.prototype.packet = function(packet, opts){
142 opts = opts || {};
143 var self = this;
144
145 // this writes to the actual connection
146 function writeToEngine(encodedPackets) {
147 if (opts.volatile && !self.conn.transport.writable) return;
148 for (var i = 0; i < encodedPackets.length; i++) {
149 self.conn.write(encodedPackets[i], { compress: opts.compress });
150 }
151 }
152
153 if ('open' == this.conn.readyState) {
154 debug('writing packet %j', packet);
155 if (!opts.preEncoded) { // not broadcasting, need to encode
156 this.encoder.encode(packet, function (encodedPackets) { // encode, then write results to engine
157 writeToEngine(encodedPackets);
158 });
159 } else { // a broadcast pre-encodes a packet
160 writeToEngine(packet);
161 }
162 } else {
163 debug('ignoring packet write %j', packet);
164 }
165};
166
167/**
168 * Called with incoming transport data.
169 *
170 * @api private
171 */
172
173Client.prototype.ondata = function(data){
174 // try/catch is needed for protocol violations (GH-1880)
175 try {
176 this.decoder.add(data);
177 } catch(e) {
178 this.onerror(e);
179 }
180};
181
182/**
183 * Called when parser fully decodes a packet.
184 *
185 * @api private
186 */
187
188Client.prototype.ondecoded = function(packet) {
189 if (parser.CONNECT == packet.type) {
190 this.connect(url.parse(packet.nsp).pathname, url.parse(packet.nsp, true).query);
191 } else {
192 var socket = this.nsps[packet.nsp];
193 if (socket) {
194 process.nextTick(function() {
195 socket.onpacket(packet);
196 });
197 } else {
198 debug('no socket for namespace %s', packet.nsp);
199 }
200 }
201};
202
203/**
204 * Handles an error.
205 *
206 * @param {Object} err object
207 * @api private
208 */
209
210Client.prototype.onerror = function(err){
211 for (var id in this.sockets) {
212 if (this.sockets.hasOwnProperty(id)) {
213 this.sockets[id].onerror(err);
214 }
215 }
216 this.onclose('client error');
217};
218
219/**
220 * Called upon transport close.
221 *
222 * @param {String} reason
223 * @api private
224 */
225
226Client.prototype.onclose = function(reason){
227 debug('client close with reason %s', reason);
228
229 // ignore a potential subsequent `close` event
230 this.destroy();
231
232 // `nsps` and `sockets` are cleaned up seamlessly
233 for (var id in this.sockets) {
234 if (this.sockets.hasOwnProperty(id)) {
235 this.sockets[id].onclose(reason);
236 }
237 }
238 this.sockets = {};
239
240 this.decoder.destroy(); // clean up decoder
241};
242
243/**
244 * Cleans up event listeners.
245 *
246 * @api private
247 */
248
249Client.prototype.destroy = function(){
250 this.conn.removeListener('data', this.ondata);
251 this.conn.removeListener('error', this.onerror);
252 this.conn.removeListener('close', this.onclose);
253 this.decoder.removeListener('decoded', this.ondecoded);
254};