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.
7 /** @suppress {duplicate} */
8 var remoting
= remoting
|| {};
11 * A connection to an XMPP server.
14 * @implements {remoting.SignalStrategy}
16 remoting
.XmppConnection = function() {
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 */
28 this.state_
= remoting
.SignalStrategy
.State
.NOT_CONNECTED
;
30 this.sendPending_
= false;
32 this.startTlsPending_
= false;
33 /** @private {Array<ArrayBuffer>} */
35 /** @private {remoting.XmppLoginHandler} */
36 this.loginHandler_
= null;
37 /** @private {remoting.XmppStreamParser} */
38 this.streamParser_
= null;
42 this.error_
= remoting
.Error
.none();
46 * @param {function(remoting.SignalStrategy.State):void} onStateChangedCallback
48 remoting
.XmppConnection
.prototype.setStateChangedCallback = function(
49 onStateChangedCallback
) {
50 this.onStateChangedCallback_
= onStateChangedCallback
;
53 remoting
.XmppConnection
.prototype.setSocketForTests = function(
54 /** remoting.TcpSocket */ socket
) {
55 this.socket_
= socket
;
59 * @param {?function(Element):void} onIncomingStanzaCallback Callback to call on
62 remoting
.XmppConnection
.prototype.setIncomingStanzaCallback
=
63 function(onIncomingStanzaCallback
) {
64 this.onIncomingStanzaCallback_
= onIncomingStanzaCallback
;
68 * @param {string} server
69 * @param {string} username
70 * @param {string} authToken
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);
77 this.error_
= remoting
.Error
.none();
78 var hostnameAndPort
= server
.split(':', 2);
79 this.server_
= hostnameAndPort
[0];
81 (hostnameAndPort
.length
== 2) ? parseInt(hostnameAndPort
[1], 10) : 5222;
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';
96 // <starttls> handshake before starting TLS is not needed when connecting on
98 var needHandshakeBeforeTls
= this.port_
!= 443;
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
);
108 this.socket_
= new remoting
.TcpSocket();
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
);
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
);
126 * @param {remoting.LogToServer} logToServer The LogToServer instance for the
129 remoting
.XmppConnection
.prototype.sendConnectionSetupResults
=
130 function(logToServer
) {
133 /** @return {remoting.SignalStrategy.State} Current state */
134 remoting
.XmppConnection
.prototype.getState = function() {
138 /** @return {!remoting.Error} Error when in FAILED state. */
139 remoting
.XmppConnection
.prototype.getError = function() {
143 /** @return {string} Current JID when in CONNECTED state. */
144 remoting
.XmppConnection
.prototype.getJid = function() {
148 /** @return {remoting.SignalStrategy.Type} The signal strategy type. */
149 remoting
.XmppConnection
.prototype.getType = function() {
150 return remoting
.SignalStrategy
.Type
.XMPP
;
153 remoting
.XmppConnection
.prototype.dispose = function() {
154 base
.dispose(this.socket_
);
156 this.setState_(remoting
.SignalStrategy
.State
.CLOSED
);
160 remoting
.XmppConnection
.prototype.onSocketConnected_ = function() {
161 // Check if connection was destroyed.
162 if (this.state_
!= remoting
.SignalStrategy
.State
.CONNECTING
) {
166 this.setState_(remoting
.SignalStrategy
.State
.HANDSHAKE
);
167 this.loginHandler_
.start();
169 if (!this.startTlsPending_
) {
170 this.socket_
.startReceiving(this.onReceive_
.bind(this),
171 this.onReceiveError_
.bind(this));
176 * @param {ArrayBuffer} data
179 remoting
.XmppConnection
.prototype.onReceive_ = function(data
) {
180 base
.debug
.assert(this.state_
== remoting
.SignalStrategy
.State
.HANDSHAKE
||
181 this.state_
== remoting
.SignalStrategy
.State
.CONNECTED
);
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
);
191 * @param {number} errorCode
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
);
200 * @param {string} text
203 remoting
.XmppConnection
.prototype.sendString_ = function(text
) {
204 this.sendBuffer_(base
.encodeUtf8(text
));
208 * @param {ArrayBuffer} data
211 remoting
.XmppConnection
.prototype.sendBuffer_ = function(data
) {
212 this.sendQueue_
.push(data
);
213 this.flushSendQueue_();
219 remoting
.XmppConnection
.prototype.flushSendQueue_ = function() {
220 if (this.sendPending_
|| this.sendQueue_
.length
== 0) {
226 this.sendPending_
= true;
227 this.socket_
.send(this.sendQueue_
[0])
228 .then(function(/** number */ bytesSent
) {
229 that
.sendPending_
= false;
230 that
.onSent_(bytesSent
);
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
);
240 * @param {number} bytesSent
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
) {
250 base
.debug
.assert(this.sendQueue_
.length
> 0);
252 var data
= this.sendQueue_
[0];
253 base
.debug
.assert(bytesSent
<= data
.byteLength
);
254 if (bytesSent
== data
.byteLength
) {
255 this.sendQueue_
.shift();
257 this.sendQueue_
[0] = data
.slice(data
.byteLength
- bytesSent
);
260 this.flushSendQueue_();
266 remoting
.XmppConnection
.prototype.startTls_ = function() {
267 base
.debug
.assert(!this.startTlsPending_
);
271 this.startTlsPending_
= true;
272 this.socket_
.startTls()
274 that
.startTlsPending_
= false;
275 that
.socket_
.startReceiving(that
.onReceive_
.bind(that
),
276 that
.onReceiveError_
.bind(that
));
278 that
.loginHandler_
.onTlsStarted();
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
);
288 * @param {string} jid
289 * @param {remoting.XmppStreamParser} streamParser
292 remoting
.XmppConnection
.prototype.onHandshakeDone_
=
293 function(jid
, streamParser
) {
295 this.streamParser_
= streamParser
;
296 this.streamParser_
.setCallbacks(this.onIncomingStanza_
.bind(this),
297 this.onParserError_
.bind(this));
298 this.setState_(remoting
.SignalStrategy
.State
.CONNECTED
);
302 * @param {Element} stanza
305 remoting
.XmppConnection
.prototype.onIncomingStanza_ = function(stanza
) {
306 if (this.onIncomingStanzaCallback_
) {
307 this.onIncomingStanzaCallback_(stanza
);
312 * @param {string} text
315 remoting
.XmppConnection
.prototype.onParserError_ = function(text
) {
316 this.onError_(remoting
.Error
.unexpected(), text
);
320 * @param {!remoting.Error} error
321 * @param {string} text
324 remoting
.XmppConnection
.prototype.onError_ = function(error
, text
) {
327 base
.dispose(this.socket_
);
329 this.setState_(remoting
.SignalStrategy
.State
.FAILED
);
333 * @param {remoting.SignalStrategy.State} newState
336 remoting
.XmppConnection
.prototype.setState_ = function(newState
) {
337 if (this.state_
!= newState
) {
338 this.state_
= newState
;
339 this.onStateChangedCallback_(this.state_
);