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 * 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.
| |
12 * | |
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 . | |
17 * | |
18 * @param {function(remoting.XmppConnection.State):void} onStateChangedCallback | |
19 * Callback to call on state change. | |
20 * @param {function(Element):void} onIncomingStanzaCallback Callback to call to | |
21 * handle incoming messages. | |
22 * @constructor | |
23 */ | |
24 remoting.XmppConnection = | |
25 function(onStateChangedCallback, onIncomingStanzaCallback) { | |
26 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
| |
27 this.onStateChangedCallback_ = onStateChangedCallback; | |
28 this.onIncomingStanzaCallback_ = onIncomingStanzaCallback; | |
29 this.socketId_ = -1; | |
30 this.state_ = remoting.XmppConnection.State.NOT_CONNECTED; | |
31 this.readPending_ = false; | |
32 this.sendPending_ = false; | |
33 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.
| |
34 /** @type {Array.<ArrayBuffer>} */ | |
35 this.sendQueue_ = []; | |
36 this.jid_ = ''; | |
37 this.error_ = remoting.Error.NONE; | |
38 } | |
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
| |
39 | |
40 /** | |
41 * @enum {number} XmppConnection states. Possible state transitions: | |
42 * NOT_CONNECTED -> CONNECTING (connect() called). | |
43 * CONNECTING -> HANDSHAKE (connected successfully). | |
44 * HANDSHAKE -> CONNECTED (connected successfully). | |
45 * CONNECTING -> FAILED (connection failed). | |
46 * HANDSHAKE -> FAILED (authentication failed). | |
47 * * -> CLOSED (dispose() called). | |
48 */ | |
49 remoting.XmppConnection.State = { | |
50 NOT_CONNECTED: 0, | |
51 CONNECTING: 1, | |
52 HANDSHAKE: 2, | |
53 CONNECTED: 3, | |
54 FAILED: 4, | |
55 CLOSED: 5 | |
56 }; | |
57 | |
58 /** | |
59 * @param {string} server | |
60 * @param {string} username | |
61 * @param {string} authToken | |
62 */ | |
63 remoting.XmppConnection.prototype.connect = | |
64 function(server, username, authToken) { | |
65 if (this.state_ != remoting.XmppConnection.State.NOT_CONNECTED) { | |
66 console.error( | |
67 '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()
| |
68 return; | |
69 } | |
70 | |
71 this.error_ = remoting.Error.NONE; | |
72 this.server_ = server; | |
73 /** @type {remoting.XmppLoginHandler} */ | |
74 this.loginHandler_ = | |
75 new remoting.XmppLoginHandler(username, authToken, | |
76 this.sendInternal_.bind(this), | |
77 this.startTls_.bind(this), | |
78 this.onHandshakeDone_.bind(this), | |
79 this.onIncomingStanzaCallback_, | |
80 this.onError_.bind(this)); | |
81 chrome.socket.create("tcp", {}, this.onSocketCreated_.bind(this)); | |
82 this.setState_(remoting.XmppConnection.State.CONNECTING); | |
83 } | |
84 | |
85 /** @param {string} message */ | |
86 remoting.XmppConnection.prototype.sendMessage = function(message) { | |
87 if (this.state_ != remoting.XmppConnection.State.CONNECTED) { | |
88 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.
| |
89 'remoting.XmppConnection.prototype.sendMessage() is called when not ' + | |
90 'connected.'); | |
91 return; | |
92 } | |
93 | |
94 this.sendInternal_(message); | |
95 } | |
96 | |
97 /** @return {remoting.XmppConnection.State} Current state */ | |
98 remoting.XmppConnection.prototype.getState = function() { | |
99 return this.state_; | |
100 } | |
101 | |
102 /** @return {remoting.Error} Error when in FAILED state. */ | |
103 remoting.XmppConnection.prototype.getError = function() { | |
104 return this.error_; | |
105 } | |
106 | |
107 /** @return {string} Current JID when in CONNECTED state. */ | |
108 remoting.XmppConnection.prototype.getJid = function() { | |
109 return this.jid_; | |
110 } | |
111 | |
112 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
| |
113 this.closeSocket_(); | |
114 this.setState_(remoting.XmppConnection.State.CLOSED); | |
115 } | |
116 | |
117 /** @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.
| |
118 remoting.XmppConnection.prototype.onSocketCreated_ = function(createInfo) { | |
119 // 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"
| |
120 if (this.state_ != remoting.XmppConnection.State.CONNECTING) { | |
121 chrome.socket.destroy(createInfo.socketId); | |
122 return; | |
123 } | |
124 | |
125 this.socketId_ = createInfo.socketId; | |
126 | |
127 var hostnameAndPort = this.server_.split(':', 2); | |
128 var hostname = hostnameAndPort[0]; | |
129 var port = | |
130 (hostnameAndPort.length == 2) ? parseInt(hostnameAndPort[1], 10) : 5222; | |
131 chrome.socket.connect( | |
132 this.socketId_, hostname, port, this.onSocketConnected_.bind(this)); | |
133 } | |
134 | |
135 /** @param {number} result */ | |
136 remoting.XmppConnection.prototype.onSocketConnected_ = function(result) { | |
137 // Check if connection was terminated. | |
138 if (this.state_ != remoting.XmppConnection.State.CONNECTING) | |
139 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
| |
140 | |
141 if (result != 0) { | |
142 this.onError_(remoting.Error.NETWORK_FAILURE, | |
143 'Failed to connect to ' + this.server_ + ': ' + result); | |
144 return; | |
145 } | |
146 | |
147 this.setState_(remoting.XmppConnection.State.HANDSHAKE); | |
148 | |
149 this.tryRead_(); | |
150 this.loginHandler_.start(); | |
151 } | |
152 | |
153 remoting.XmppConnection.prototype.tryRead_ = function() { | |
154 base.debug.assert(!this.readPending_); | |
155 if ((this.state_ != remoting.XmppConnection.State.HANDSHAKE && | |
156 this.state_ != remoting.XmppConnection.State.CONNECTED) || | |
157 this.starttlsPending_) { | |
158 return; | |
159 } | |
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.
| |
160 | |
161 this.readPending_ = true; | |
162 chrome.socket.read(this.socketId_, this.onRead_.bind(this)); | |
163 } | |
164 | |
165 /** @param {chrome.socket.ReadInfo} readInfo */ | |
166 remoting.XmppConnection.prototype.onRead_ = function(readInfo) { | |
167 base.debug.assert(this.readPending_); | |
168 this.readPending_ = false; | |
169 | |
170 if (readInfo.resultCode < 0) { | |
171 this.onError_(remoting.Error.NETWORK_FAILURE, | |
172 'Failed to receive from XMPP socket: ' + readInfo.resultCode); | |
173 return; | |
174 } | |
175 | |
176 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
| |
177 this.tryRead_(); | |
178 } | |
179 | |
180 /** @param {string} text */ | |
181 remoting.XmppConnection.prototype.sendInternal_ = function(text) { | |
182 this.sendQueue_.push(base.encodeUtf8(text)); | |
183 this.doSend_(); | |
184 } | |
185 | |
186 remoting.XmppConnection.prototype.doSend_ = function() { | |
187 if (this.sendPending_) | |
188 return; | |
189 if (this.sendQueue_.length == 0) | |
190 return; | |
191 | |
192 var data = this.sendQueue_[0] | |
193 this.sendPending_ = true; | |
194 chrome.socket.write(this.socketId_, data, this.onWrite_.bind(this)); | |
195 } | |
196 | |
197 /** @param {chrome.socket.WriteInfo} writeInfo */ | |
198 remoting.XmppConnection.prototype.onWrite_ = function(writeInfo) { | |
199 base.debug.assert(this.sendPending_); | |
200 this.sendPending_ = false; | |
201 | |
202 if (writeInfo.bytesWritten < 0) { | |
203 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.
| |
204 this.setState_(remoting.XmppConnection.State.FAILED); | |
205 return; | |
206 } | |
207 | |
208 base.debug.assert(this.sendQueue_.length > 0); | |
209 | |
210 var data = this.sendQueue_[0] | |
211 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
| |
212 if (writeInfo.bytesWritten >= data.byteLength) { | |
213 this.sendQueue_.shift(); | |
214 } else { | |
215 this.sendQueue_[0] = data.slice(data.byteLength - writeInfo.bytesWritten); | |
216 } | |
217 | |
218 this.doSend_(); | |
219 } | |
220 | |
221 remoting.XmppConnection.prototype.startTls_ = function() { | |
222 base.debug.assert(!this.readPending_); | |
223 base.debug.assert(!this.starttlsPending_); | |
224 | |
225 this.starttlsPending_ = true; | |
226 chrome.socket.secure( | |
227 this.socketId_, {}, this.onTlsStarted_.bind(this)); | |
228 } | |
229 | |
230 /** @param {number} resultCode */ | |
231 remoting.XmppConnection.prototype.onTlsStarted_ = function(resultCode) { | |
232 base.debug.assert(this.starttlsPending_); | |
233 this.starttlsPending_ = false; | |
234 | |
235 if (resultCode < 0) { | |
236 this.onError_(remoting.Error.NETWORK_FAILURE, | |
237 'Failed to start TLS: ' + resultCode); | |
238 return; | |
239 } | |
240 | |
241 this.tryRead_(); | |
242 this.loginHandler_.onTlsStarted(); | |
243 } | |
244 | |
245 /** @param {string} jid */ | |
246 remoting.XmppConnection.prototype.onHandshakeDone_ = function(jid) { | |
247 this.jid_ = jid; | |
248 this.setState_(remoting.XmppConnection.State.CONNECTED); | |
249 } | |
250 | |
251 /** | |
252 * @param {remoting.Error} error | |
253 * @param {string} text | |
254 */ | |
255 remoting.XmppConnection.prototype.onError_ = function(error, text) { | |
256 console.error(text); | |
257 this.error_ = error; | |
258 this.closeSocket_(); | |
259 this.setState_(remoting.XmppConnection.State.FAILED); | |
260 } | |
261 | |
262 remoting.XmppConnection.prototype.closeSocket_ = function() { | |
263 if (this.socketId_ != -1) { | |
264 chrome.socket.destroy(this.socketId_); | |
265 this.socketId_ = -1; | |
266 } | |
267 } | |
268 | |
269 /** @param {remoting.XmppConnection.State} newState */ | |
270 remoting.XmppConnection.prototype.setState_ = function(newState) { | |
271 if (this.state_ != newState) { | |
272 this.state_ = newState; | |
273 this.onStateChangedCallback_(this.state_); | |
274 } | |
275 } | |
OLD | NEW |