Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(463)

Unified Diff: remoting/webapp/xmpp_connection.js

Issue 514343002: XMPP implementation in JavaScript. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: remoting/webapp/xmpp_connection.js
diff --git a/remoting/webapp/xmpp_connection.js b/remoting/webapp/xmpp_connection.js
new file mode 100644
index 0000000000000000000000000000000000000000..d3f445b77cec149e6c0fedc221d0ac105caa2b10
--- /dev/null
+++ b/remoting/webapp/xmpp_connection.js
@@ -0,0 +1,275 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/**
+ * Object used to connect to XMPP server.
Jamie 2014/08/29 02:14:08 I think it's implicit that it's an object. How abo
Sergey Ulanov 2014/08/29 23:40:29 Done.
+ *
+ * TODO(sergeyu): Chrome provides two APIs for TCP sockets: chrome.socket and
+ * chrome.sockets.tcp . chrome.socket is deprecated but it's still used here
+ * because TLS support in chrome.sockets.tcp is currently broken, see
+ * crbug.com/403076 .
+ *
+ * @param {function(remoting.XmppConnection.State):void} onStateChangedCallback
+ * Callback to call on state change.
+ * @param {function(Element):void} onIncomingStanzaCallback Callback to call to
+ * handle incoming messages.
+ * @constructor
+ */
+remoting.XmppConnection =
+ function(onStateChangedCallback, onIncomingStanzaCallback) {
+ this.server_ = '';
Jamie 2014/08/29 02:14:08 These should be declared as @private. I'm also a b
Sergey Ulanov 2014/08/29 23:40:28 Yes. jscompiler can infer type from assignment her
+ this.onStateChangedCallback_ = onStateChangedCallback;
+ this.onIncomingStanzaCallback_ = onIncomingStanzaCallback;
+ this.socketId_ = -1;
+ this.state_ = remoting.XmppConnection.State.NOT_CONNECTED;
+ this.readPending_ = false;
+ this.sendPending_ = false;
+ this.starttlsPending_ = false;
Jamie 2014/08/29 02:14:08 s/tls/Tls/ for consistency with the method name.
Sergey Ulanov 2014/08/29 23:40:28 Done.
+ /** @type {Array.<ArrayBuffer>} */
+ this.sendQueue_ = [];
+ this.jid_ = '';
+ this.error_ = remoting.Error.NONE;
+}
Jamie 2014/08/29 02:14:09 Semi-colons after functions declared by assignment
Sergey Ulanov 2014/08/29 23:40:28 Done. Can emacs be integrated with clang-format fo
+
+/**
+ * @enum {number} XmppConnection states. Possible state transitions:
+ * NOT_CONNECTED -> CONNECTING (connect() called).
+ * CONNECTING -> HANDSHAKE (connected successfully).
+ * HANDSHAKE -> CONNECTED (connected successfully).
+ * CONNECTING -> FAILED (connection failed).
+ * HANDSHAKE -> FAILED (authentication failed).
+ * * -> CLOSED (dispose() called).
+ */
+remoting.XmppConnection.State = {
+ NOT_CONNECTED: 0,
+ CONNECTING: 1,
+ HANDSHAKE: 2,
+ CONNECTED: 3,
+ FAILED: 4,
+ CLOSED: 5
+};
+
+/**
+ * @param {string} server
+ * @param {string} username
+ * @param {string} authToken
+ */
+remoting.XmppConnection.prototype.connect =
+ function(server, username, authToken) {
+ if (this.state_ != remoting.XmppConnection.State.NOT_CONNECTED) {
+ console.error(
+ 'remoting.XmppConnection.prototype.connect() can only be called once.');
Jamie 2014/08/29 02:14:08 The log message will include the file name, so I t
Sergey Ulanov 2014/08/29 23:40:29 Replaced with assert()
+ return;
+ }
+
+ this.error_ = remoting.Error.NONE;
+ this.server_ = server;
+ /** @type {remoting.XmppLoginHandler} */
+ this.loginHandler_ =
+ new remoting.XmppLoginHandler(username, authToken,
+ this.sendInternal_.bind(this),
+ this.startTls_.bind(this),
+ this.onHandshakeDone_.bind(this),
+ this.onIncomingStanzaCallback_,
+ this.onError_.bind(this));
+ chrome.socket.create("tcp", {}, this.onSocketCreated_.bind(this));
+ this.setState_(remoting.XmppConnection.State.CONNECTING);
+}
+
+/** @param {string} message */
+remoting.XmppConnection.prototype.sendMessage = function(message) {
+ if (this.state_ != remoting.XmppConnection.State.CONNECTED) {
+ console.error(
Jamie 2014/08/29 02:14:08 As above, the caller should have some indication t
Sergey Ulanov 2014/08/29 23:40:29 Done.
+ 'remoting.XmppConnection.prototype.sendMessage() is called when not ' +
+ 'connected.');
+ return;
+ }
+
+ this.sendInternal_(message);
+}
+
+/** @return {remoting.XmppConnection.State} Current state */
+remoting.XmppConnection.prototype.getState = function() {
+ return this.state_;
+}
+
+/** @return {remoting.Error} Error when in FAILED state. */
+remoting.XmppConnection.prototype.getError = function() {
+ return this.error_;
+}
+
+/** @return {string} Current JID when in CONNECTED state. */
+remoting.XmppConnection.prototype.getJid = function() {
+ return this.jid_;
+}
+
+remoting.XmppConnection.prototype.dispose = function() {
Jamie 2014/08/29 02:14:09 This might be better named disconnect() for symmet
Sergey Ulanov 2014/08/29 23:40:29 Yes, the intent here is to follow the Disposable p
+ this.closeSocket_();
+ this.setState_(remoting.XmppConnection.State.CLOSED);
+}
+
+/** @param {chrome.socket.CreateInfo} createInfo */
Jamie 2014/08/29 02:14:08 @private for this and other methods ending in an u
Sergey Ulanov 2014/08/29 23:40:28 Done.
+remoting.XmppConnection.prototype.onSocketCreated_ = function(createInfo) {
+ // Check if connection was terminated.
Jamie 2014/08/29 02:14:08 I think this can only happen if the caller calls d
Sergey Ulanov 2014/08/29 23:40:28 Replaced with "Check if connection was destroyed"
+ if (this.state_ != remoting.XmppConnection.State.CONNECTING) {
+ chrome.socket.destroy(createInfo.socketId);
+ return;
+ }
+
+ this.socketId_ = createInfo.socketId;
+
+ var hostnameAndPort = this.server_.split(':', 2);
+ var hostname = hostnameAndPort[0];
+ var port =
+ (hostnameAndPort.length == 2) ? parseInt(hostnameAndPort[1], 10) : 5222;
+ chrome.socket.connect(
+ this.socketId_, hostname, port, this.onSocketConnected_.bind(this));
+}
+
+/** @param {number} result */
+remoting.XmppConnection.prototype.onSocketConnected_ = function(result) {
+ // Check if connection was terminated.
+ if (this.state_ != remoting.XmppConnection.State.CONNECTING)
+ return;
Jamie 2014/08/29 02:14:09 Braces for single-line ifs for consistency, please
Sergey Ulanov 2014/08/29 23:40:29 No. It should have been destroyed when dispose() w
+
+ if (result != 0) {
+ this.onError_(remoting.Error.NETWORK_FAILURE,
+ 'Failed to connect to ' + this.server_ + ': ' + result);
+ return;
+ }
+
+ this.setState_(remoting.XmppConnection.State.HANDSHAKE);
+
+ this.tryRead_();
+ this.loginHandler_.start();
+}
+
+remoting.XmppConnection.prototype.tryRead_ = function() {
+ base.debug.assert(!this.readPending_);
+ if ((this.state_ != remoting.XmppConnection.State.HANDSHAKE &&
+ this.state_ != remoting.XmppConnection.State.CONNECTED) ||
+ this.starttlsPending_) {
+ return;
+ }
Jamie 2014/08/29 02:14:08 Why an assert for readPending, but a conditional f
Sergey Ulanov 2014/08/29 23:40:29 Done.
+
+ this.readPending_ = true;
+ chrome.socket.read(this.socketId_, this.onRead_.bind(this));
+}
+
+/** @param {chrome.socket.ReadInfo} readInfo */
+remoting.XmppConnection.prototype.onRead_ = function(readInfo) {
+ base.debug.assert(this.readPending_);
+ this.readPending_ = false;
+
+ if (readInfo.resultCode < 0) {
+ this.onError_(remoting.Error.NETWORK_FAILURE,
+ 'Failed to receive from XMPP socket: ' + readInfo.resultCode);
+ return;
+ }
+
+ this.loginHandler_.onDataReceived(readInfo.data);
Jamie 2014/08/29 02:14:09 The name of this class implies that it just handle
Sergey Ulanov 2014/08/29 23:40:29 Refactored this code so that LoginHandler is used
+ this.tryRead_();
+}
+
+/** @param {string} text */
+remoting.XmppConnection.prototype.sendInternal_ = function(text) {
+ this.sendQueue_.push(base.encodeUtf8(text));
+ this.doSend_();
+}
+
+remoting.XmppConnection.prototype.doSend_ = function() {
+ if (this.sendPending_)
+ return;
+ if (this.sendQueue_.length == 0)
+ return;
+
+ var data = this.sendQueue_[0]
+ this.sendPending_ = true;
+ chrome.socket.write(this.socketId_, data, this.onWrite_.bind(this));
+}
+
+/** @param {chrome.socket.WriteInfo} writeInfo */
+remoting.XmppConnection.prototype.onWrite_ = function(writeInfo) {
+ base.debug.assert(this.sendPending_);
+ this.sendPending_ = false;
+
+ if (writeInfo.bytesWritten < 0) {
+ console.error('TCP write failed with error ' + writeInfo.bytesWritten);
Jamie 2014/08/29 02:14:08 You should call onError here so that this.error is
Sergey Ulanov 2014/08/29 23:40:29 Done.
+ this.setState_(remoting.XmppConnection.State.FAILED);
+ return;
+ }
+
+ base.debug.assert(this.sendQueue_.length > 0);
+
+ var data = this.sendQueue_[0]
+ base.debug.assert(writeInfo.bytesWritten <= data.byteLength);
Jamie 2014/08/29 02:14:09 What is this assert guarding against? You're alrea
Sergey Ulanov 2014/08/29 23:40:29 Its to detect bugs that cause head of sendQueue_ t
+ if (writeInfo.bytesWritten >= data.byteLength) {
+ this.sendQueue_.shift();
+ } else {
+ this.sendQueue_[0] = data.slice(data.byteLength - writeInfo.bytesWritten);
+ }
+
+ this.doSend_();
+}
+
+remoting.XmppConnection.prototype.startTls_ = function() {
+ base.debug.assert(!this.readPending_);
+ base.debug.assert(!this.starttlsPending_);
+
+ this.starttlsPending_ = true;
+ chrome.socket.secure(
+ this.socketId_, {}, this.onTlsStarted_.bind(this));
+}
+
+/** @param {number} resultCode */
+remoting.XmppConnection.prototype.onTlsStarted_ = function(resultCode) {
+ base.debug.assert(this.starttlsPending_);
+ this.starttlsPending_ = false;
+
+ if (resultCode < 0) {
+ this.onError_(remoting.Error.NETWORK_FAILURE,
+ 'Failed to start TLS: ' + resultCode);
+ return;
+ }
+
+ this.tryRead_();
+ this.loginHandler_.onTlsStarted();
+}
+
+/** @param {string} jid */
+remoting.XmppConnection.prototype.onHandshakeDone_ = function(jid) {
+ this.jid_ = jid;
+ this.setState_(remoting.XmppConnection.State.CONNECTED);
+}
+
+/**
+ * @param {remoting.Error} error
+ * @param {string} text
+ */
+remoting.XmppConnection.prototype.onError_ = function(error, text) {
+ console.error(text);
+ this.error_ = error;
+ this.closeSocket_();
+ this.setState_(remoting.XmppConnection.State.FAILED);
+}
+
+remoting.XmppConnection.prototype.closeSocket_ = function() {
+ if (this.socketId_ != -1) {
+ chrome.socket.destroy(this.socketId_);
+ this.socketId_ = -1;
+ }
+}
+
+/** @param {remoting.XmppConnection.State} newState */
+remoting.XmppConnection.prototype.setState_ = function(newState) {
+ if (this.state_ != newState) {
+ this.state_ = newState;
+ this.onStateChangedCallback_(this.state_);
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698