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