OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview | 6 * @fileoverview |
7 * Class representing the host-list portion of the home screen UI. | 7 * Class representing the host-list portion of the home screen UI. |
8 */ | 8 */ |
9 | 9 |
10 'use strict'; | 10 'use strict'; |
11 | 11 |
12 /** @suppress {duplicate} */ | 12 /** @suppress {duplicate} */ |
13 var remoting = remoting || {}; | 13 var remoting = remoting || {}; |
14 | 14 |
15 /** | 15 /** |
16 * Create a host list consisting of the specified HTML elements, which should | 16 * Create a host list consisting of the specified HTML elements, which should |
17 * have a common parent that contains only host-list UI as it will be hidden | 17 * have a common parent that contains only host-list UI as it will be hidden |
18 * if the host-list is empty. | 18 * if the host-list is empty. |
19 * | |
19 * @constructor | 20 * @constructor |
20 * @param {Element} table The HTML <table> to contain host-list. | 21 * @param {Element} table The HTML <table> to contain host-list. |
21 * @param {Element} errorDiv The HTML <div> to display error messages. | 22 * @param {Element} errorDiv The HTML <div> to display error messages. |
22 */ | 23 */ |
23 remoting.HostList = function(table, errorDiv) { | 24 remoting.HostList = function(table, errorDiv) { |
24 /** | 25 /** |
25 * @type {Element} | 26 * @type {Element} |
26 * @private | 27 * @private |
27 */ | 28 */ |
28 this.table_ = table; | 29 this.table_ = table; |
29 /** | 30 /** |
30 * @type {Element} | 31 * @type {Element} |
31 * @private | 32 * @private |
32 */ | 33 */ |
33 this.errorDiv_ = errorDiv; | 34 this.errorDiv_ = errorDiv; |
34 /** | 35 /** |
35 * @type {Array.<remoting.HostTableEntry>} | 36 * @type {Array.<remoting.HostTableEntry>} |
36 * @private | 37 * @private |
37 */ | 38 */ |
38 this.hostTableEntries_ = null; | 39 this.hostTableEntries_ = []; |
40 /** | |
41 * @type {Array.<remoting.Host>} | |
42 * @private | |
43 */ | |
44 this.hosts_ = []; | |
45 /** | |
46 * @type {string} | |
47 * @private | |
48 */ | |
49 this.lastError_ = ''; | |
Wez
2011/12/03 01:26:33
nit: blank line here, please
Jamie
2011/12/05 20:49:08
Done.
| |
50 // Load the cache of the last host-list, if present. | |
51 var cached = /** @type {string} */ | |
52 (window.localStorage.getItem(remoting.HostList.HOSTS_KEY)); | |
Wez
2011/12/03 01:26:33
nit: indent
Jamie
2011/12/05 20:49:08
Done.
| |
53 if (cached) { | |
54 try { | |
55 this.hosts_ = /** @type {Array} */ JSON.parse(cached); | |
56 } catch (err) { | |
57 console.error('Invalid host list cache:', /** @type {*} */(err)); | |
58 } | |
59 } | |
39 }; | 60 }; |
40 | 61 |
41 /** | 62 /** |
42 * Search the host list for a host with the specified id. | 63 * Search the host list for a host with the specified id. |
64 * | |
43 * @param {string} hostId The unique id of the host. | 65 * @param {string} hostId The unique id of the host. |
44 * @return {remoting.HostTableEntry?} The host table entry, if any. | 66 * @return {remoting.Host?} The host, if any. |
45 */ | 67 */ |
46 remoting.HostList.prototype.getHostForId = function(hostId) { | 68 remoting.HostList.prototype.getHostForId = function(hostId) { |
47 for (var i = 0; i < this.hostTableEntries_.length; ++i) { | 69 for (var i = 0; i < this.hosts_.length; ++i) { |
48 if (this.hostTableEntries_[i].host.hostId == hostId) { | 70 if (this.hosts_[i].hostId == hostId) { |
49 return this.hostTableEntries_[i]; | 71 return this.hosts_[i]; |
50 } | 72 } |
51 } | 73 } |
52 return null; | 74 return null; |
53 }; | 75 }; |
54 | 76 |
55 /** | 77 /** |
56 * Query the Remoting Directory for the user's list of hosts. | 78 * Query the Remoting Directory for the user's list of hosts. |
57 * | 79 * |
80 * @param {function(boolean):void} onDone Callback invoked with true on success | |
81 * or false on failure. | |
58 * @return {void} Nothing. | 82 * @return {void} Nothing. |
59 */ | 83 */ |
60 remoting.HostList.prototype.refresh = function() { | 84 remoting.HostList.prototype.refresh = function(onDone) { |
61 /** @type {remoting.HostList} */ | 85 /** @type {remoting.HostList} */ |
62 var that = this; | 86 var that = this; |
63 /** @param {XMLHttpRequest} xhr */ | 87 /** @param {XMLHttpRequest} xhr The response from the server. */ |
64 var parseHostListResponse = function(xhr) { | 88 var parseHostListResponse = function(xhr) { |
65 that.parseHostListResponse_(xhr); | 89 that.parseHostListResponse_(xhr, onDone); |
66 } | 90 } |
67 /** @param {string} token */ | 91 /** @param {string} token The OAuth2 token. */ |
68 var getHosts = function(token) { | 92 var getHosts = function(token) { |
69 var headers = { 'Authorization': 'OAuth ' + token }; | 93 var headers = { 'Authorization': 'OAuth ' + token }; |
70 remoting.xhr.get( | 94 remoting.xhr.get( |
71 'https://www.googleapis.com/chromoting/v1/@me/hosts', | 95 'https://www.googleapis.com/chromoting/v1/@me/hosts', |
72 parseHostListResponse, '', headers); | 96 parseHostListResponse, '', headers); |
73 }; | 97 }; |
74 remoting.oauth2.callWithToken(getHosts); | 98 remoting.oauth2.callWithToken(getHosts); |
75 } | 99 }; |
76 | 100 |
77 /** | 101 /** |
78 * Handle the results of the host list request. A success response will | 102 * Handle the results of the host list request. A success response will |
79 * include a JSON-encoded list of host descriptions, which we display if we're | 103 * include a JSON-encoded list of host descriptions, which we display if we're |
80 * able to successfully parse it. | 104 * able to successfully parse it. |
81 * | 105 * |
82 * @param {XMLHttpRequest} xhr The XHR object for the host list request. | 106 * @param {XMLHttpRequest} xhr The XHR object for the host list request. |
107 * @param {function(boolean):void} onDone The callback passed to |refresh|. | |
83 * @return {void} Nothing. | 108 * @return {void} Nothing. |
109 * @private | |
84 */ | 110 */ |
85 remoting.HostList.prototype.parseHostListResponse_ = function(xhr) { | 111 remoting.HostList.prototype.parseHostListResponse_ = function(xhr, onDone) { |
86 try { | 112 try { |
87 if (xhr.status == 200) { | 113 if (xhr.status == 200) { |
88 var parsed_response = | 114 var parsed_response = |
89 /** @type {{data: {items: Array}}} */ JSON.parse(xhr.responseText); | 115 /** @type {{data: {items: Array}}} */ JSON.parse(xhr.responseText); |
90 if (parsed_response.data && parsed_response.data.items) { | 116 if (parsed_response.data && parsed_response.data.items) { |
91 this.setHosts_(parsed_response.data.items); | 117 this.hosts_ = parsed_response.data.items; |
118 this.lastError_ = ''; | |
92 } | 119 } |
93 } else { | 120 } else { |
94 // Some other error. | 121 // Some other error. |
95 console.error('Bad status on host list query: ', xhr); | 122 console.error('Bad status on host list query: ', xhr); |
96 // For most errors in the 4xx range, tell the user to re-authorize us. | |
97 if (xhr.status == 403) { | 123 if (xhr.status == 403) { |
98 // The user's account is not enabled for Me2Me, so fail silently. | 124 // The user's account is not enabled for Me2Me, so fail silently. |
99 } else if (xhr.status >= 400 && xhr.status <= 499) { | 125 this.hosts_ = []; |
100 this.showError_(remoting.Error.GENERIC); | 126 this.lastError_ = ''; |
127 } else { | |
128 // For other errors, tell the user to re-authorize us. | |
129 this.hosts_ = []; | |
130 this.lastError_ = remoting.Error.GENERIC; | |
Wez
2011/12/03 01:26:33
I specifically avoided sending them to the reautho
Jamie
2011/12/05 20:49:08
We have to show some error message in this case. I
Wez
2011/12/05 20:59:38
Suggesting courses of action that we know won't he
| |
101 } | 131 } |
102 } | 132 } |
103 } catch (er) { | 133 } catch (er) { |
104 var typed_er = /** @type {Object} */ (er); | 134 var typed_er = /** @type {Object} */ (er); |
105 console.error('Error processing response: ', xhr, typed_er); | 135 console.error('Error processing response: ', xhr, typed_er); |
136 this.hosts_ = []; | |
137 this.lastError_ = remoting.Error.GENERIC; | |
Wez
2011/12/03 01:26:33
I specifically avoided barfing them to the reautho
Jamie
2011/12/05 20:49:08
See above.
| |
106 } | 138 } |
107 } | 139 window.localStorage.setItem(remoting.HostList.HOSTS_KEY, |
140 JSON.stringify(this.hosts_)); | |
141 onDone(this.lastError_ == ''); | |
142 }; | |
108 | 143 |
109 /** | 144 /** |
110 * Refresh the host list with up-to-date details. | 145 * Display the list of hosts or error condition. |
111 * @param {Array.<remoting.Host>} hosts The new host list. | 146 * |
112 * @return {void} Nothing. | 147 * @return {void} Nothing. |
113 * @private | |
114 */ | 148 */ |
115 remoting.HostList.prototype.setHosts_ = function(hosts) { | 149 remoting.HostList.prototype.display = function() { |
116 this.table_.innerHTML = ''; | 150 this.table_.innerHTML = ''; |
117 this.showError_(null); | 151 this.errorDiv_.innerText = ''; |
118 this.hostTableEntries_ = []; | 152 this.hostTableEntries_ = []; |
119 | 153 |
120 /** @type {remoting.HostList} */ | 154 /** @type {remoting.HostList} */ |
121 var that = this; | 155 var that = this; |
122 for (var i = 0; i < hosts.length; ++i) { | 156 for (var i = 0; i < this.hosts_.length; ++i) { |
123 /** @type {remoting.Host} */ | 157 /** @type {remoting.Host} */ |
124 var host = hosts[i]; | 158 var host = this.hosts_[i]; |
125 // Validate the entry to make sure it has all the fields we expect. | 159 // Validate the entry to make sure it has all the fields we expect. |
126 if (host.hostName && host.hostId && host.status && host.jabberId && | 160 if (host.hostName && host.hostId && host.status && host.jabberId && |
127 host.publicKey) { | 161 host.publicKey) { |
128 var onRename = function() { that.renameHost_(host.hostId); } | 162 var onRename = function() { that.renameHost_(host.hostId); } |
129 var onDelete = function() { that.deleteHost_(host.hostId); } | 163 var onDelete = function() { that.deleteHost_(host.hostId); } |
130 var hostTableEntry = new remoting.HostTableEntry(); | 164 var hostTableEntry = new remoting.HostTableEntry(); |
131 hostTableEntry.init(host, onRename, onDelete); | 165 hostTableEntry.init(host, onRename, onDelete); |
132 this.hostTableEntries_[i] = hostTableEntry; | 166 this.hostTableEntries_[i] = hostTableEntry; |
133 this.table_.appendChild(hostTableEntry.tableRow); | 167 this.table_.appendChild(hostTableEntry.tableRow); |
134 } | 168 } |
135 } | 169 } |
136 | 170 |
137 this.showOrHide_(this.hostTableEntries_.length != 0); | 171 if (this.lastError_ != '') { |
138 }; | 172 l10n.localizeElementFromTag(this.errorDiv_, this.lastError_); |
173 } | |
139 | 174 |
140 /** | 175 this.showOrHide_(this.hosts_.length != 0 || this.lastError_ != ''); |
141 * Display a localized error message. | |
142 * @param {remoting.Error?} errorTag The error to display, or NULL to clear any | |
143 * previous error. | |
144 * @return {void} Nothing. | |
145 */ | |
146 remoting.HostList.prototype.showError_ = function(errorTag) { | |
147 this.table_.innerHTML = ''; | |
148 if (errorTag) { | |
149 l10n.localizeElementFromTag(this.errorDiv_, | |
150 /** @type {string} */ (errorTag)); | |
151 this.showOrHide_(true); | |
152 } else { | |
153 this.errorDiv_.innerText = ''; | |
154 } | |
155 }; | 176 }; |
156 | 177 |
157 /** | 178 /** |
158 * Show or hide the host-list UI. | 179 * Show or hide the host-list UI. |
180 * | |
159 * @param {boolean} show True to show the UI, or false to hide it. | 181 * @param {boolean} show True to show the UI, or false to hide it. |
160 * @return {void} Nothing. | 182 * @return {void} Nothing. |
161 * @private | 183 * @private |
162 */ | 184 */ |
163 remoting.HostList.prototype.showOrHide_ = function(show) { | 185 remoting.HostList.prototype.showOrHide_ = function(show) { |
164 var parent = /** @type {Element} */ (this.table_.parentNode); | 186 var parent = /** @type {Element} */ (this.table_.parentNode); |
165 parent.hidden = !show; | 187 parent.hidden = !show; |
166 if (show) { | 188 if (show) { |
167 parent.style.height = parent.scrollHeight + 'px'; | 189 parent.style.height = parent.scrollHeight + 'px'; |
168 removeClass(parent, remoting.HostList.COLLAPSED_); | 190 removeClass(parent, remoting.HostList.COLLAPSED_); |
169 } else { | 191 } else { |
170 addClass(parent, remoting.HostList.COLLAPSED_); | 192 addClass(parent, remoting.HostList.COLLAPSED_); |
171 } | 193 } |
172 }; | 194 }; |
173 | 195 |
174 /** | 196 /** |
175 * Remove a host from the list, and deregister it. | 197 * Remove a host from the list, and deregister it. |
198 * | |
176 * @param {string} hostId The id of the host to be removed. | 199 * @param {string} hostId The id of the host to be removed. |
177 * @return {void} Nothing. | 200 * @return {void} Nothing. |
178 * @private | 201 * @private |
179 */ | 202 */ |
180 remoting.HostList.prototype.deleteHost_ = function(hostId) { | 203 remoting.HostList.prototype.deleteHost_ = function(hostId) { |
181 /** @type {remoting.HostTableEntry} */ | 204 var host = this.getHostForId(hostId); |
182 var hostTableEntry = this.getHostForId(hostId); | 205 if (!host) { |
183 if (!hostTableEntry) { | |
184 console.error('No host registered for id ' + hostId); | 206 console.error('No host registered for id ' + hostId); |
185 return; | 207 return; |
186 } | 208 } |
187 | 209 |
188 this.table_.removeChild(hostTableEntry.tableRow); | 210 var index = this.hosts_.indexOf(host); |
189 var index = this.hostTableEntries_.indexOf(hostTableEntry); | |
190 if (index != -1) { // Since we've just found it, index must be >= 0 | 211 if (index != -1) { // Since we've just found it, index must be >= 0 |
212 this.hosts_.splice(index, 1); | |
213 } | |
214 var hostTableEntry = null; | |
215 for (index = 0; index < this.hostTableEntries_.length; ++index) { | |
216 if (this.hostTableEntries_[index].host.hostId == hostId) { | |
217 hostTableEntry = this.hostTableEntries_[index]; | |
218 break; | |
219 } | |
220 } | |
221 if (hostTableEntry) { | |
222 this.table_.removeChild(hostTableEntry.tableRow); | |
191 this.hostTableEntries_.splice(index, 1); | 223 this.hostTableEntries_.splice(index, 1); |
192 } | 224 } |
193 | 225 |
194 /** @param {string} token */ | 226 /** @param {string} token The OAuth2 token. */ |
195 var deleteHost = function(token) { | 227 var deleteHost = function(token) { |
196 var headers = { 'Authorization': 'OAuth ' + token }; | 228 var headers = { 'Authorization': 'OAuth ' + token }; |
197 remoting.xhr.remove( | 229 remoting.xhr.remove( |
198 'https://www.googleapis.com/chromoting/v1/@me/hosts/' + hostId, | 230 'https://www.googleapis.com/chromoting/v1/@me/hosts/' + hostId, |
199 function() {}, '', headers); | 231 function() {}, '', headers); |
200 } | 232 } |
201 remoting.oauth2.callWithToken(deleteHost); | 233 remoting.oauth2.callWithToken(deleteHost); |
202 | 234 |
203 this.showOrHide_(this.hostTableEntries_.length != 0); | 235 this.showOrHide_(this.hostTableEntries_.length != 0); |
204 }; | 236 }; |
205 | 237 |
206 /** | 238 /** |
207 * Prepare a host for renaming by replacing its name with an edit box. | 239 * Rename the specified host. |
240 * | |
208 * @param {string} hostId The id of the host to be renamed. | 241 * @param {string} hostId The id of the host to be renamed. |
209 * @return {void} Nothing. | 242 * @return {void} Nothing. |
210 * @private | 243 * @private |
211 */ | 244 */ |
212 remoting.HostList.prototype.renameHost_ = function(hostId) { | 245 remoting.HostList.prototype.renameHost_ = function(hostId) { |
213 /** @type {remoting.HostTableEntry} */ | 246 /** @type {remoting.Host} */ |
214 var hostTableEntry = this.getHostForId(hostId); | 247 var host = this.getHostForId(hostId); |
215 if (!hostTableEntry) { | 248 if (!host) { |
216 console.error('No host registered for id ' + hostId); | 249 console.error('No host registered for id ' + hostId); |
217 return; | 250 return; |
218 } | 251 } |
219 /** @param {string} token */ | 252 /** @param {string} token The OAuth2 token. */ |
220 var renameHost = function(token) { | 253 var renameHost = function(token) { |
221 var headers = { | 254 var headers = { |
222 'Authorization': 'OAuth ' + token, | 255 'Authorization': 'OAuth ' + token, |
223 'Content-type' : 'application/json; charset=UTF-8' | 256 'Content-type' : 'application/json; charset=UTF-8' |
224 }; | 257 }; |
225 var newHostDetails = { data: { | 258 var newHostDetails = { data: { |
226 hostId: hostTableEntry.host.hostId, | 259 hostId: host.hostId, |
227 hostName: hostTableEntry.host.hostName, | 260 hostName: host.hostName, |
228 publicKey: hostTableEntry.host.publicKey | 261 publicKey: host.publicKey |
229 } }; | 262 } }; |
230 remoting.xhr.put( | 263 remoting.xhr.put( |
231 'https://www.googleapis.com/chromoting/v1/@me/hosts/' + hostId, | 264 'https://www.googleapis.com/chromoting/v1/@me/hosts/' + hostId, |
232 function(xhr) {}, | 265 function(xhr) {}, |
233 JSON.stringify(newHostDetails), | 266 JSON.stringify(newHostDetails), |
234 headers); | 267 headers); |
235 } | 268 } |
236 remoting.oauth2.callWithToken(renameHost); | 269 remoting.oauth2.callWithToken(renameHost); |
237 }; | 270 }; |
238 | 271 |
239 /** | 272 /** |
240 * Class name for the host list when it is collapsed. | 273 * Class name for the host list when it is collapsed. |
241 * @private | 274 * @private |
242 */ | 275 */ |
243 remoting.HostList.COLLAPSED_ = 'collapsed'; | 276 remoting.HostList.COLLAPSED_ = 'collapsed'; |
244 | 277 |
278 /** | |
279 * Key name under which Me2Me hosts are cached. | |
280 */ | |
281 remoting.HostList.HOSTS_KEY = 'me2me-cached-hosts'; | |
282 | |
245 /** @type {remoting.HostList} */ | 283 /** @type {remoting.HostList} */ |
246 remoting.hostList = null; | 284 remoting.hostList = null; |
OLD | NEW |