UNPKG

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