Revert of Enabling audio quality test on mac. (patchset #1 id:1 of https://codereview...
[chromium-blink-merge.git] / remoting / webapp / xmpp_connection.js
blobadf911a8fef2f8f610d43d95c1ed56b3a0e45a89
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.
5 'use strict';
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
10 /**
11 * A connection to an XMPP server.
13 * TODO(sergeyu): Chrome provides two APIs for TCP sockets: chrome.socket and
14 * chrome.sockets.tcp . chrome.socket is deprecated but it's still used here
15 * because TLS support in chrome.sockets.tcp is currently broken, see
16 * crbug.com/403076 .
18 * @param {function(remoting.SignalStrategy.State):void} onStateChangedCallback
19 * Callback to call on state change.
20 * @constructor
21 * @implements {remoting.SignalStrategy}
23 remoting.XmppConnection = function(onStateChangedCallback) {
24 /** @private */
25 this.server_ = '';
26 /** @private */
27 this.port_ = 0;
28 /** @private */
29 this.onStateChangedCallback_ = onStateChangedCallback;
30 /** @type {?function(Element):void} @private */
31 this.onIncomingStanzaCallback_ = null;
32 /** @private */
33 this.socketId_ = -1;
34 /** @private */
35 this.state_ = remoting.SignalStrategy.State.NOT_CONNECTED;
36 /** @private */
37 this.readPending_ = false;
38 /** @private */
39 this.sendPending_ = false;
40 /** @private */
41 this.startTlsPending_ = false;
42 /** @type {Array.<ArrayBuffer>} @private */
43 this.sendQueue_ = [];
44 /** @type {remoting.XmppLoginHandler} @private*/
45 this.loginHandler_ = null;
46 /** @type {remoting.XmppStreamParser} @private*/
47 this.streamParser_ = null;
48 /** @private */
49 this.jid_ = '';
50 /** @private */
51 this.error_ = remoting.Error.NONE;
54 /**
55 * @param {?function(Element):void} onIncomingStanzaCallback Callback to call on
56 * incoming messages.
58 remoting.XmppConnection.prototype.setIncomingStanzaCallback =
59 function(onIncomingStanzaCallback) {
60 this.onIncomingStanzaCallback_ = onIncomingStanzaCallback;
63 /**
64 * @param {string} server
65 * @param {string} username
66 * @param {string} authToken
68 remoting.XmppConnection.prototype.connect =
69 function(server, username, authToken) {
70 base.debug.assert(this.state_ == remoting.SignalStrategy.State.NOT_CONNECTED);
72 this.error_ = remoting.Error.NONE;
73 var hostnameAndPort = server.split(':', 2);
74 this.server_ = hostnameAndPort[0];
75 this.port_ =
76 (hostnameAndPort.length == 2) ? parseInt(hostnameAndPort[1], 10) : 5222;
78 // The server name is passed as to attribute in the <stream>. When connecting
79 // to talk.google.com it affects the certificate the server will use for TLS:
80 // talk.google.com uses gmail certificate when specified server is gmail.com
81 // or googlemail.com and google.com cert otherwise. In the same time it
82 // doesn't accept talk.google.com as target server. Here we use google.com
83 // server name when authenticating to talk.google.com. This ensures that the
84 // server will use google.com cert which will be accepted by the TLS
85 // implementation in Chrome (TLS API doesn't allow specifying domain other
86 // than the one that was passed to connect()).
87 var xmppServer = this.server_;
88 if (xmppServer == 'talk.google.com')
89 xmppServer = 'google.com';
91 /** @type {remoting.XmppLoginHandler} */
92 this.loginHandler_ =
93 new remoting.XmppLoginHandler(xmppServer, username, authToken,
94 this.sendInternal_.bind(this),
95 this.startTls_.bind(this),
96 this.onHandshakeDone_.bind(this),
97 this.onError_.bind(this));
98 chrome.socket.create("tcp", {}, this.onSocketCreated_.bind(this));
99 this.setState_(remoting.SignalStrategy.State.CONNECTING);
102 /** @param {string} message */
103 remoting.XmppConnection.prototype.sendMessage = function(message) {
104 base.debug.assert(this.state_ == remoting.SignalStrategy.State.CONNECTED);
105 this.sendInternal_(message);
108 /** @return {remoting.SignalStrategy.State} Current state */
109 remoting.XmppConnection.prototype.getState = function() {
110 return this.state_;
113 /** @return {remoting.Error} Error when in FAILED state. */
114 remoting.XmppConnection.prototype.getError = function() {
115 return this.error_;
118 /** @return {string} Current JID when in CONNECTED state. */
119 remoting.XmppConnection.prototype.getJid = function() {
120 return this.jid_;
123 remoting.XmppConnection.prototype.dispose = function() {
124 this.closeSocket_();
125 this.setState_(remoting.SignalStrategy.State.CLOSED);
129 * @param {chrome.socket.CreateInfo} createInfo
130 * @private
132 remoting.XmppConnection.prototype.onSocketCreated_ = function(createInfo) {
133 // Check if connection was destroyed.
134 if (this.state_ != remoting.SignalStrategy.State.CONNECTING) {
135 chrome.socket.destroy(createInfo.socketId);
136 return;
139 this.socketId_ = createInfo.socketId;
141 chrome.socket.connect(this.socketId_,
142 this.server_,
143 this.port_,
144 this.onSocketConnected_.bind(this));
148 * @param {number} result
149 * @private
151 remoting.XmppConnection.prototype.onSocketConnected_ = function(result) {
152 // Check if connection was destroyed.
153 if (this.state_ != remoting.SignalStrategy.State.CONNECTING) {
154 return;
157 if (result != 0) {
158 this.onError_(remoting.Error.NETWORK_FAILURE,
159 'Failed to connect to ' + this.server_ + ': ' + result);
160 return;
163 this.setState_(remoting.SignalStrategy.State.HANDSHAKE);
165 this.tryRead_();
166 this.loginHandler_.start();
170 * @private
172 remoting.XmppConnection.prototype.tryRead_ = function() {
173 base.debug.assert(!this.readPending_);
174 base.debug.assert(this.state_ == remoting.SignalStrategy.State.HANDSHAKE ||
175 this.state_ == remoting.SignalStrategy.State.CONNECTED);
176 base.debug.assert(!this.startTlsPending_);
178 this.readPending_ = true;
179 chrome.socket.read(this.socketId_, this.onRead_.bind(this));
183 * @param {chrome.socket.ReadInfo} readInfo
184 * @private
186 remoting.XmppConnection.prototype.onRead_ = function(readInfo) {
187 base.debug.assert(this.readPending_);
188 this.readPending_ = false;
190 // Check if the socket was closed while reading.
191 if (this.state_ != remoting.SignalStrategy.State.HANDSHAKE &&
192 this.state_ != remoting.SignalStrategy.State.CONNECTED) {
193 return;
197 if (readInfo.resultCode < 0) {
198 this.onError_(remoting.Error.NETWORK_FAILURE,
199 'Failed to receive from XMPP socket: ' + readInfo.resultCode);
200 return;
203 if (this.state_ == remoting.SignalStrategy.State.HANDSHAKE) {
204 this.loginHandler_.onDataReceived(readInfo.data);
205 } else if (this.state_ == remoting.SignalStrategy.State.CONNECTED) {
206 this.streamParser_.appendData(readInfo.data);
209 if (!this.startTlsPending_) {
210 this.tryRead_();
215 * @param {string} text
216 * @private
218 remoting.XmppConnection.prototype.sendInternal_ = function(text) {
219 this.sendQueue_.push(base.encodeUtf8(text));
220 this.doSend_();
224 * @private
226 remoting.XmppConnection.prototype.doSend_ = function() {
227 if (this.sendPending_ || this.sendQueue_.length == 0) {
228 return;
231 var data = this.sendQueue_[0]
232 this.sendPending_ = true;
233 chrome.socket.write(this.socketId_, data, this.onWrite_.bind(this));
237 * @param {chrome.socket.WriteInfo} writeInfo
238 * @private
240 remoting.XmppConnection.prototype.onWrite_ = function(writeInfo) {
241 base.debug.assert(this.sendPending_);
242 this.sendPending_ = false;
244 // Ignore write() result if the socket was closed.
245 if (this.state_ != remoting.SignalStrategy.State.HANDSHAKE &&
246 this.state_ != remoting.SignalStrategy.State.CONNECTED) {
247 return;
250 if (writeInfo.bytesWritten < 0) {
251 this.onError_(remoting.Error.NETWORK_FAILURE,
252 'TCP write failed with error ' + writeInfo.bytesWritten);
253 return;
256 base.debug.assert(this.sendQueue_.length > 0);
258 var data = this.sendQueue_[0]
259 base.debug.assert(writeInfo.bytesWritten <= data.byteLength);
260 if (writeInfo.bytesWritten == data.byteLength) {
261 this.sendQueue_.shift();
262 } else {
263 this.sendQueue_[0] = data.slice(data.byteLength - writeInfo.bytesWritten);
266 this.doSend_();
270 * @private
272 remoting.XmppConnection.prototype.startTls_ = function() {
273 base.debug.assert(!this.readPending_);
274 base.debug.assert(!this.startTlsPending_);
276 this.startTlsPending_ = true;
277 chrome.socket.secure(
278 this.socketId_, {}, this.onTlsStarted_.bind(this));
282 * @param {number} resultCode
283 * @private
285 remoting.XmppConnection.prototype.onTlsStarted_ = function(resultCode) {
286 base.debug.assert(this.startTlsPending_);
287 this.startTlsPending_ = false;
289 if (resultCode < 0) {
290 this.onError_(remoting.Error.NETWORK_FAILURE,
291 'Failed to start TLS: ' + resultCode);
292 return;
295 this.tryRead_();
296 this.loginHandler_.onTlsStarted();
300 * @param {string} jid
301 * @param {remoting.XmppStreamParser} streamParser
302 * @private
304 remoting.XmppConnection.prototype.onHandshakeDone_ =
305 function(jid, streamParser) {
306 this.jid_ = jid;
307 this.streamParser_ = streamParser;
308 this.streamParser_.setCallbacks(this.onIncomingStanza_.bind(this),
309 this.onParserError_.bind(this));
310 this.setState_(remoting.SignalStrategy.State.CONNECTED);
314 * @param {Element} stanza
315 * @private
317 remoting.XmppConnection.prototype.onIncomingStanza_ = function(stanza) {
318 if (this.onIncomingStanzaCallback_) {
319 this.onIncomingStanzaCallback_(stanza);
324 * @param {string} text
325 * @private
327 remoting.XmppConnection.prototype.onParserError_ = function(text) {
328 this.onError_(remoting.Error.UNEXPECTED, text);
332 * @param {remoting.Error} error
333 * @param {string} text
334 * @private
336 remoting.XmppConnection.prototype.onError_ = function(error, text) {
337 console.error(text);
338 this.error_ = error;
339 this.closeSocket_();
340 this.setState_(remoting.SignalStrategy.State.FAILED);
344 * @private
346 remoting.XmppConnection.prototype.closeSocket_ = function() {
347 if (this.socketId_ != -1) {
348 chrome.socket.destroy(this.socketId_);
349 this.socketId_ = -1;
354 * @param {remoting.SignalStrategy.State} newState
355 * @private
357 remoting.XmppConnection.prototype.setState_ = function(newState) {
358 if (this.state_ != newState) {
359 this.state_ = newState;
360 this.onStateChangedCallback_(this.state_);