OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 'use strict'; | |
6 | |
7 /** @suppress {duplicate} */ | |
8 var remoting = remoting || {}; | |
9 | |
10 /** | |
11 * A connection to an XMPP server. | |
12 * | |
13 * @constructor | |
14 * @implements {remoting.SignalStrategy} | |
15 */ | |
16 remoting.XmppConnection = function() { | |
17 /** @private */ | |
18 this.server_ = ''; | |
19 /** @private */ | |
20 this.port_ = 0; | |
21 /** @private {?function(remoting.SignalStrategy.State):void} */ | |
22 this.onStateChangedCallback_ = null; | |
23 /** @private {?function(Element):void} */ | |
24 this.onIncomingStanzaCallback_ = null; | |
25 /** @type {?remoting.TcpSocket} @private */ | |
26 this.socket_ = null; | |
27 /** @private */ | |
28 this.state_ = remoting.SignalStrategy.State.NOT_CONNECTED; | |
29 /** @private */ | |
30 this.sendPending_ = false; | |
31 /** @private */ | |
32 this.startTlsPending_ = false; | |
33 /** @private {Array<ArrayBuffer>} */ | |
34 this.sendQueue_ = []; | |
35 /** @private {remoting.XmppLoginHandler} */ | |
36 this.loginHandler_ = null; | |
37 /** @private {remoting.XmppStreamParser} */ | |
38 this.streamParser_ = null; | |
39 /** @private */ | |
40 this.jid_ = ''; | |
41 /** @private */ | |
42 this.error_ = remoting.Error.none(); | |
43 }; | |
44 | |
45 /** | |
46 * @param {function(remoting.SignalStrategy.State):void} onStateChangedCallback | |
47 */ | |
48 remoting.XmppConnection.prototype.setStateChangedCallback = function( | |
49 onStateChangedCallback) { | |
50 this.onStateChangedCallback_ = onStateChangedCallback; | |
51 }; | |
52 | |
53 remoting.XmppConnection.prototype.setSocketForTests = function( | |
54 /** remoting.TcpSocket */ socket) { | |
55 this.socket_ = socket; | |
56 }; | |
57 | |
58 /** | |
59 * @param {?function(Element):void} onIncomingStanzaCallback Callback to call on | |
60 * incoming messages. | |
61 */ | |
62 remoting.XmppConnection.prototype.setIncomingStanzaCallback = | |
63 function(onIncomingStanzaCallback) { | |
64 this.onIncomingStanzaCallback_ = onIncomingStanzaCallback; | |
65 }; | |
66 | |
67 /** | |
68 * @param {string} server | |
69 * @param {string} username | |
70 * @param {string} authToken | |
71 */ | |
72 remoting.XmppConnection.prototype.connect = | |
73 function(server, username, authToken) { | |
74 base.debug.assert(this.state_ == remoting.SignalStrategy.State.NOT_CONNECTED); | |
75 base.debug.assert(this.onStateChangedCallback_ != null); | |
76 | |
77 this.error_ = remoting.Error.none(); | |
78 var hostnameAndPort = server.split(':', 2); | |
79 this.server_ = hostnameAndPort[0]; | |
80 this.port_ = | |
81 (hostnameAndPort.length == 2) ? parseInt(hostnameAndPort[1], 10) : 5222; | |
82 | |
83 // The server name is passed as to attribute in the <stream>. When connecting | |
84 // to talk.google.com it affects the certificate the server will use for TLS: | |
85 // talk.google.com uses gmail certificate when specified server is gmail.com | |
86 // or googlemail.com and google.com cert otherwise. In the same time it | |
87 // doesn't accept talk.google.com as target server. Here we use google.com | |
88 // server name when authenticating to talk.google.com. This ensures that the | |
89 // server will use google.com cert which will be accepted by the TLS | |
90 // implementation in Chrome (TLS API doesn't allow specifying domain other | |
91 // than the one that was passed to connect()). | |
92 var xmppServer = this.server_; | |
93 if (xmppServer == 'talk.google.com') | |
94 xmppServer = 'google.com'; | |
95 | |
96 // <starttls> handshake before starting TLS is not needed when connecting on | |
97 // the HTTPS port. | |
98 var needHandshakeBeforeTls = this.port_ != 443; | |
99 | |
100 /** @type {remoting.XmppLoginHandler} */ | |
101 this.loginHandler_ = new remoting.XmppLoginHandler( | |
102 xmppServer, username, authToken, needHandshakeBeforeTls, | |
103 this.sendString_.bind(this), this.startTls_.bind(this), | |
104 this.onHandshakeDone_.bind(this), this.onError_.bind(this)); | |
105 this.setState_(remoting.SignalStrategy.State.CONNECTING); | |
106 | |
107 if (!this.socket_) { | |
108 this.socket_ = new remoting.TcpSocket(); | |
109 } | |
110 var that = this; | |
111 this.socket_.connect(this.server_, this.port_) | |
112 .then(this.onSocketConnected_.bind(this)) | |
113 .catch(function(error) { | |
114 that.onError_(new remoting.Error(remoting.Error.Tag.NETWORK_FAILURE), | |
115 'Failed to connect to ' + that.server_ + ': ' + error); | |
116 }); | |
117 }; | |
118 | |
119 /** @param {string} message */ | |
120 remoting.XmppConnection.prototype.sendMessage = function(message) { | |
121 base.debug.assert(this.state_ == remoting.SignalStrategy.State.CONNECTED); | |
122 this.sendString_(message); | |
123 }; | |
124 | |
125 /** | |
126 * @param {remoting.LogToServer} logToServer The LogToServer instance for the | |
127 * connection. | |
128 */ | |
129 remoting.XmppConnection.prototype.sendConnectionSetupResults = | |
130 function(logToServer) { | |
131 }; | |
132 | |
133 /** @return {remoting.SignalStrategy.State} Current state */ | |
134 remoting.XmppConnection.prototype.getState = function() { | |
135 return this.state_; | |
136 }; | |
137 | |
138 /** @return {!remoting.Error} Error when in FAILED state. */ | |
139 remoting.XmppConnection.prototype.getError = function() { | |
140 return this.error_; | |
141 }; | |
142 | |
143 /** @return {string} Current JID when in CONNECTED state. */ | |
144 remoting.XmppConnection.prototype.getJid = function() { | |
145 return this.jid_; | |
146 }; | |
147 | |
148 /** @return {remoting.SignalStrategy.Type} The signal strategy type. */ | |
149 remoting.XmppConnection.prototype.getType = function() { | |
150 return remoting.SignalStrategy.Type.XMPP; | |
151 }; | |
152 | |
153 remoting.XmppConnection.prototype.dispose = function() { | |
154 base.dispose(this.socket_); | |
155 this.socket_ = null; | |
156 this.setState_(remoting.SignalStrategy.State.CLOSED); | |
157 }; | |
158 | |
159 /** @private */ | |
160 remoting.XmppConnection.prototype.onSocketConnected_ = function() { | |
161 // Check if connection was destroyed. | |
162 if (this.state_ != remoting.SignalStrategy.State.CONNECTING) { | |
163 return; | |
164 } | |
165 | |
166 this.setState_(remoting.SignalStrategy.State.HANDSHAKE); | |
167 this.loginHandler_.start(); | |
168 | |
169 if (!this.startTlsPending_) { | |
170 this.socket_.startReceiving(this.onReceive_.bind(this), | |
171 this.onReceiveError_.bind(this)); | |
172 } | |
173 }; | |
174 | |
175 /** | |
176 * @param {ArrayBuffer} data | |
177 * @private | |
178 */ | |
179 remoting.XmppConnection.prototype.onReceive_ = function(data) { | |
180 base.debug.assert(this.state_ == remoting.SignalStrategy.State.HANDSHAKE || | |
181 this.state_ == remoting.SignalStrategy.State.CONNECTED); | |
182 | |
183 if (this.state_ == remoting.SignalStrategy.State.HANDSHAKE) { | |
184 this.loginHandler_.onDataReceived(data); | |
185 } else if (this.state_ == remoting.SignalStrategy.State.CONNECTED) { | |
186 this.streamParser_.appendData(data); | |
187 } | |
188 }; | |
189 | |
190 /** | |
191 * @param {number} errorCode | |
192 * @private | |
193 */ | |
194 remoting.XmppConnection.prototype.onReceiveError_ = function(errorCode) { | |
195 this.onError_(new remoting.Error(remoting.Error.Tag.NETWORK_FAILURE), | |
196 'Failed to receive from XMPP socket: ' + errorCode); | |
197 }; | |
198 | |
199 /** | |
200 * @param {string} text | |
201 * @private | |
202 */ | |
203 remoting.XmppConnection.prototype.sendString_ = function(text) { | |
204 this.sendBuffer_(base.encodeUtf8(text)); | |
205 }; | |
206 | |
207 /** | |
208 * @param {ArrayBuffer} data | |
209 * @private | |
210 */ | |
211 remoting.XmppConnection.prototype.sendBuffer_ = function(data) { | |
212 this.sendQueue_.push(data); | |
213 this.flushSendQueue_(); | |
214 }; | |
215 | |
216 /** | |
217 * @private | |
218 */ | |
219 remoting.XmppConnection.prototype.flushSendQueue_ = function() { | |
220 if (this.sendPending_ || this.sendQueue_.length == 0) { | |
221 return; | |
222 } | |
223 | |
224 var that = this; | |
225 | |
226 this.sendPending_ = true; | |
227 this.socket_.send(this.sendQueue_[0]) | |
228 .then(function(/** number */ bytesSent) { | |
229 that.sendPending_ = false; | |
230 that.onSent_(bytesSent); | |
231 }) | |
232 .catch(function(/** number */ error) { | |
233 that.sendPending_ = false; | |
234 that.onError_(new remoting.Error(remoting.Error.Tag.NETWORK_FAILURE), | |
235 'TCP write failed with error ' + error); | |
236 }); | |
237 }; | |
238 | |
239 /** | |
240 * @param {number} bytesSent | |
241 * @private | |
242 */ | |
243 remoting.XmppConnection.prototype.onSent_ = function(bytesSent) { | |
244 // Ignore send() result if the socket was closed. | |
245 if (this.state_ != remoting.SignalStrategy.State.HANDSHAKE && | |
246 this.state_ != remoting.SignalStrategy.State.CONNECTED) { | |
247 return; | |
248 } | |
249 | |
250 base.debug.assert(this.sendQueue_.length > 0); | |
251 | |
252 var data = this.sendQueue_[0]; | |
253 base.debug.assert(bytesSent <= data.byteLength); | |
254 if (bytesSent == data.byteLength) { | |
255 this.sendQueue_.shift(); | |
256 } else { | |
257 this.sendQueue_[0] = data.slice(data.byteLength - bytesSent); | |
258 } | |
259 | |
260 this.flushSendQueue_(); | |
261 }; | |
262 | |
263 /** | |
264 * @private | |
265 */ | |
266 remoting.XmppConnection.prototype.startTls_ = function() { | |
267 base.debug.assert(!this.startTlsPending_); | |
268 | |
269 var that = this; | |
270 | |
271 this.startTlsPending_ = true; | |
272 this.socket_.startTls() | |
273 .then(function() { | |
274 that.startTlsPending_ = false; | |
275 that.socket_.startReceiving(that.onReceive_.bind(that), | |
276 that.onReceiveError_.bind(that)); | |
277 | |
278 that.loginHandler_.onTlsStarted(); | |
279 }) | |
280 .catch(function(/** number */ error) { | |
281 that.startTlsPending_ = false; | |
282 that.onError_(new remoting.Error(remoting.Error.Tag.NETWORK_FAILURE), | |
283 'Failed to start TLS: ' + error); | |
284 }); | |
285 } | |
286 | |
287 /** | |
288 * @param {string} jid | |
289 * @param {remoting.XmppStreamParser} streamParser | |
290 * @private | |
291 */ | |
292 remoting.XmppConnection.prototype.onHandshakeDone_ = | |
293 function(jid, streamParser) { | |
294 this.jid_ = jid; | |
295 this.streamParser_ = streamParser; | |
296 this.streamParser_.setCallbacks(this.onIncomingStanza_.bind(this), | |
297 this.onParserError_.bind(this)); | |
298 this.setState_(remoting.SignalStrategy.State.CONNECTED); | |
299 }; | |
300 | |
301 /** | |
302 * @param {Element} stanza | |
303 * @private | |
304 */ | |
305 remoting.XmppConnection.prototype.onIncomingStanza_ = function(stanza) { | |
306 if (this.onIncomingStanzaCallback_) { | |
307 this.onIncomingStanzaCallback_(stanza); | |
308 } | |
309 }; | |
310 | |
311 /** | |
312 * @param {string} text | |
313 * @private | |
314 */ | |
315 remoting.XmppConnection.prototype.onParserError_ = function(text) { | |
316 this.onError_(remoting.Error.unexpected(), text); | |
317 }; | |
318 | |
319 /** | |
320 * @param {!remoting.Error} error | |
321 * @param {string} text | |
322 * @private | |
323 */ | |
324 remoting.XmppConnection.prototype.onError_ = function(error, text) { | |
325 console.error(text); | |
326 this.error_ = error; | |
327 base.dispose(this.socket_); | |
328 this.socket_ = null; | |
329 this.setState_(remoting.SignalStrategy.State.FAILED); | |
330 }; | |
331 | |
332 /** | |
333 * @param {remoting.SignalStrategy.State} newState | |
334 * @private | |
335 */ | |
336 remoting.XmppConnection.prototype.setState_ = function(newState) { | |
337 if (this.state_ != newState) { | |
338 this.state_ = newState; | |
339 this.onStateChangedCallback_(this.state_); | |
340 } | |
341 }; | |
OLD | NEW |