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. | |
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_ = ''; | |
kelvinp
2014/08/29 01:37:09
@private. Same for other private members and func
Sergey Ulanov
2014/08/29 23:40:30
Done.
| |
27 this.port_ = 0; | |
28 this.onStateChangedCallback_ = onStateChangedCallback; | |
29 this.onIncomingStanzaCallback_ = onIncomingStanzaCallback; | |
30 this.socketId_ = -1; | |
31 this.state_ = remoting.XmppConnection.State.NOT_CONNECTED; | |
32 this.readPending_ = false; | |
33 this.sendPending_ = false; | |
34 this.starttlsPending_ = false; | |
35 /** @type {Array.<ArrayBuffer>} */ | |
36 this.sendQueue_ = []; | |
37 this.jid_ = ''; | |
38 this.error_ = remoting.Error.NONE; | |
39 } | |
40 | |
41 /** | |
42 * @enum {number} XmppConnection states. Possible state transitions: | |
43 * NOT_CONNECTED -> CONNECTING (connect() called). | |
44 * CONNECTING -> HANDSHAKE (connected successfully). | |
45 * HANDSHAKE -> CONNECTED (connected successfully). | |
kelvinp
2014/08/29 01:37:09
Nit: HANDSHAKE -> CONNECTED (Authenticated success
Sergey Ulanov
2014/08/29 23:40:29
Done.
| |
46 * CONNECTING -> FAILED (connection failed). | |
47 * HANDSHAKE -> FAILED (authentication failed). | |
48 * * -> CLOSED (dispose() called). | |
49 */ | |
50 remoting.XmppConnection.State = { | |
51 NOT_CONNECTED: 0, | |
52 CONNECTING: 1, | |
53 HANDSHAKE: 2, | |
54 CONNECTED: 3, | |
55 FAILED: 4, | |
56 CLOSED: 5 | |
57 }; | |
58 | |
59 /** | |
60 * @param {string} server | |
61 * @param {string} username | |
62 * @param {string} authToken | |
63 */ | |
64 remoting.XmppConnection.prototype.connect = | |
65 function(server, username, authToken) { | |
66 if (this.state_ != remoting.XmppConnection.State.NOT_CONNECTED) { | |
67 console.error( | |
68 'remoting.XmppConnection.prototype.connect() can only be called once.'); | |
69 return; | |
70 } | |
71 | |
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; | |
77 | |
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'; | |
90 | |
91 /** @type {remoting.XmppLoginHandler} */ | |
92 this.loginHandler_ = | |
93 new remoting.XmppLoginHandler(xmppServer, username, authToken, | |
kelvinp
2014/08/29 01:37:10
Instead of binding private functions and handing t
Sergey Ulanov
2014/08/29 23:40:30
This would create circular dependency between Xmpp
| |
94 this.sendInternal_.bind(this), | |
95 this.startTls_.bind(this), | |
96 this.onHandshakeDone_.bind(this), | |
97 this.onIncomingStanzaCallback_, | |
98 this.onError_.bind(this)); | |
99 chrome.socket.create("tcp", {}, this.onSocketCreated_.bind(this)); | |
100 this.setState_(remoting.XmppConnection.State.CONNECTING); | |
101 } | |
102 | |
103 /** @param {string} message */ | |
104 remoting.XmppConnection.prototype.sendMessage = function(message) { | |
105 if (this.state_ != remoting.XmppConnection.State.CONNECTED) { | |
106 console.error( | |
107 'remoting.XmppConnection.prototype.sendMessage() is called when not ' + | |
108 'connected.'); | |
109 return; | |
110 } | |
111 | |
112 this.sendInternal_(message); | |
113 } | |
114 | |
115 /** @return {remoting.XmppConnection.State} Current state */ | |
116 remoting.XmppConnection.prototype.getState = function() { | |
117 return this.state_; | |
118 } | |
119 | |
120 /** @return {remoting.Error} Error when in FAILED state. */ | |
121 remoting.XmppConnection.prototype.getError = function() { | |
122 return this.error_; | |
123 } | |
124 | |
125 /** @return {string} Current JID when in CONNECTED state. */ | |
126 remoting.XmppConnection.prototype.getJid = function() { | |
127 return this.jid_; | |
128 } | |
129 | |
130 remoting.XmppConnection.prototype.dispose = function() { | |
131 this.closeSocket_(); | |
132 this.setState_(remoting.XmppConnection.State.CLOSED); | |
133 } | |
134 | |
135 /** @param {chrome.socket.CreateInfo} createInfo */ | |
136 remoting.XmppConnection.prototype.onSocketCreated_ = function(createInfo) { | |
137 // Check if connection was terminated. | |
138 if (this.state_ != remoting.XmppConnection.State.CONNECTING) { | |
139 chrome.socket.destroy(createInfo.socketId); | |
140 return; | |
141 } | |
142 | |
143 this.socketId_ = createInfo.socketId; | |
144 | |
145 chrome.socket.connect(this.socketId_, | |
146 this.server_, | |
147 this.port_, | |
148 this.onSocketConnected_.bind(this)); | |
149 } | |
150 | |
151 /** @param {number} result */ | |
152 remoting.XmppConnection.prototype.onSocketConnected_ = function(result) { | |
153 // Check if connection was terminated. | |
154 if (this.state_ != remoting.XmppConnection.State.CONNECTING) | |
155 return; | |
156 | |
157 if (result != 0) { | |
158 this.onError_(remoting.Error.NETWORK_FAILURE, | |
159 'Failed to connect to ' + this.server_ + ': ' + result); | |
160 return; | |
161 } | |
162 | |
163 this.setState_(remoting.XmppConnection.State.HANDSHAKE); | |
164 | |
165 this.tryRead_(); | |
166 this.loginHandler_.start(); | |
167 } | |
168 | |
169 remoting.XmppConnection.prototype.tryRead_ = function() { | |
kelvinp
2014/08/29 01:37:10
tryRead will cause onRead to call, which will call
Sergey Ulanov
2014/08/29 23:40:30
chrome.socket.read() never calls the callback sync
| |
170 base.debug.assert(!this.readPending_); | |
171 if ((this.state_ != remoting.XmppConnection.State.HANDSHAKE && | |
172 this.state_ != remoting.XmppConnection.State.CONNECTED) || | |
173 this.starttlsPending_) { | |
174 return; | |
175 } | |
176 | |
177 this.readPending_ = true; | |
178 chrome.socket.read(this.socketId_, this.onRead_.bind(this)); | |
179 } | |
180 | |
181 /** @param {chrome.socket.ReadInfo} readInfo */ | |
182 remoting.XmppConnection.prototype.onRead_ = function(readInfo) { | |
183 base.debug.assert(this.readPending_); | |
184 this.readPending_ = false; | |
185 | |
186 if (readInfo.resultCode < 0) { | |
187 this.onError_(remoting.Error.NETWORK_FAILURE, | |
188 'Failed to receive from XMPP socket: ' + readInfo.resultCode); | |
189 return; | |
190 } | |
191 | |
192 this.loginHandler_.onDataReceived(readInfo.data); | |
193 this.tryRead_(); | |
194 } | |
195 | |
196 /** @param {string} text */ | |
197 remoting.XmppConnection.prototype.sendInternal_ = function(text) { | |
198 this.sendQueue_.push(base.encodeUtf8(text)); | |
199 this.doSend_(); | |
200 } | |
201 | |
202 remoting.XmppConnection.prototype.doSend_ = function() { | |
203 if (this.sendPending_) | |
204 return; | |
205 if (this.sendQueue_.length == 0) | |
206 return; | |
207 | |
208 var data = this.sendQueue_[0] | |
209 this.sendPending_ = true; | |
210 chrome.socket.write(this.socketId_, data, this.onWrite_.bind(this)); | |
211 } | |
212 | |
213 /** @param {chrome.socket.WriteInfo} writeInfo */ | |
214 remoting.XmppConnection.prototype.onWrite_ = function(writeInfo) { | |
215 base.debug.assert(this.sendPending_); | |
216 this.sendPending_ = false; | |
217 | |
218 if (writeInfo.bytesWritten < 0) { | |
219 console.error('TCP write failed with error ' + writeInfo.bytesWritten); | |
220 this.setState_(remoting.XmppConnection.State.FAILED); | |
221 return; | |
222 } | |
223 | |
224 base.debug.assert(this.sendQueue_.length > 0); | |
225 | |
226 var data = this.sendQueue_[0] | |
227 base.debug.assert(writeInfo.bytesWritten <= data.byteLength); | |
kelvinp
2014/08/29 01:37:10
It is strange that we assert that bytesWritten <=
Sergey Ulanov
2014/08/29 23:40:29
replaced >= with == in the line below.
| |
228 if (writeInfo.bytesWritten >= data.byteLength) { | |
229 this.sendQueue_.shift(); | |
230 } else { | |
231 this.sendQueue_[0] = data.slice(data.byteLength - writeInfo.bytesWritten); | |
232 } | |
233 | |
234 this.doSend_(); | |
235 } | |
236 | |
237 remoting.XmppConnection.prototype.startTls_ = function() { | |
kelvinp
2014/08/29 01:37:10
This function is marked as private but it is not c
Sergey Ulanov
2014/08/29 23:40:30
No. It's internal detail of XmppConnection() and i
| |
238 base.debug.assert(!this.readPending_); | |
239 base.debug.assert(!this.starttlsPending_); | |
240 | |
241 this.starttlsPending_ = true; | |
242 chrome.socket.secure( | |
243 this.socketId_, {}, this.onTlsStarted_.bind(this)); | |
244 } | |
245 | |
246 /** @param {number} resultCode */ | |
247 remoting.XmppConnection.prototype.onTlsStarted_ = function(resultCode) { | |
248 base.debug.assert(this.starttlsPending_); | |
249 this.starttlsPending_ = false; | |
250 | |
251 if (resultCode < 0) { | |
252 this.onError_(remoting.Error.NETWORK_FAILURE, | |
253 'Failed to start TLS: ' + resultCode); | |
254 return; | |
255 } | |
256 | |
257 this.tryRead_(); | |
258 this.loginHandler_.onTlsStarted(); | |
259 } | |
260 | |
261 /** @param {string} jid */ | |
262 remoting.XmppConnection.prototype.onHandshakeDone_ = function(jid) { | |
263 this.jid_ = jid; | |
264 this.setState_(remoting.XmppConnection.State.CONNECTED); | |
265 } | |
266 | |
267 /** | |
268 * @param {remoting.Error} error | |
269 * @param {string} text | |
270 */ | |
271 remoting.XmppConnection.prototype.onError_ = function(error, text) { | |
272 console.error(text); | |
273 this.error_ = error; | |
274 this.closeSocket_(); | |
275 this.setState_(remoting.XmppConnection.State.FAILED); | |
276 } | |
277 | |
278 remoting.XmppConnection.prototype.closeSocket_ = function() { | |
279 if (this.socketId_ != -1) { | |
280 chrome.socket.destroy(this.socketId_); | |
281 this.socketId_ = -1; | |
282 } | |
283 } | |
284 | |
285 /** @param {remoting.XmppConnection.State} newState */ | |
286 remoting.XmppConnection.prototype.setState_ = function(newState) { | |
287 if (this.state_ != newState) { | |
288 this.state_ = newState; | |
289 this.onStateChangedCallback_(this.state_); | |
290 } | |
291 } | |
OLD | NEW |