OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 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 * Interface abstracting the Application functionality. | 7 * Interface abstracting the Application functionality. |
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 * @param {Array<string>} appCapabilities Array of application capabilities. | 16 * @param {Array<string>} appCapabilities Array of application capabilities. |
17 * @constructor | 17 * @constructor |
18 * @implements {remoting.ApplicationInterface} | |
18 */ | 19 */ |
19 remoting.Application = function(appCapabilities) { | 20 remoting.Application = function(appCapabilities) { |
20 /** @private {remoting.Application.Delegate} */ | 21 // Create global factories. |
21 this.delegate_ = null; | 22 remoting.ClientPlugin.factory = new remoting.DefaultClientPluginFactory(); |
23 remoting.SessionConnector.factory = | |
24 new remoting.DefaultSessionConnectorFactory(); | |
22 | 25 |
23 /** @private {Array<string>} */ | 26 /** @private {Array<string>} */ |
24 this.appCapabilities_ = [ | 27 this.appCapabilities_ = [ |
25 remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION, | 28 remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION, |
26 remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS, | 29 remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS, |
27 remoting.ClientSession.Capability.VIDEO_RECORDER | 30 remoting.ClientSession.Capability.VIDEO_RECORDER |
28 ]; | 31 ]; |
29 // Append the app-specific capabilities. | 32 // Append the app-specific capabilities. |
30 this.appCapabilities_.push.apply(this.appCapabilities_, appCapabilities); | 33 this.appCapabilities_.push.apply(this.appCapabilities_, appCapabilities); |
31 | 34 |
32 /** @private {remoting.SessionConnector} */ | 35 /** @protected {remoting.SessionConnector} */ |
33 this.sessionConnector_ = null; | 36 this.sessionConnector_ = remoting.SessionConnector.factory.createConnector( |
37 document.getElementById('client-container'), | |
38 this.onConnected_.bind(this), | |
39 this.onError_.bind(this), | |
40 this.onConnectionFailed_.bind(this), | |
41 this.appCapabilities_); | |
34 | 42 |
35 /** @private {base.Disposable} */ | 43 /** @private {base.Disposable} */ |
36 this.sessionConnectedHooks_ = null; | 44 this.sessionConnectedHooks_ = null; |
37 }; | 45 }; |
38 | 46 |
39 /** | 47 /** |
40 * @param {remoting.Application.Delegate} appDelegate The delegate that | 48 * @return {remoting.SessionConnector} The session connector. |
41 * contains the app-specific functionality. | |
42 */ | 49 */ |
43 remoting.Application.prototype.setDelegate = function(appDelegate) { | 50 remoting.Application.prototype.getSessionConnector = function() { |
44 this.delegate_ = appDelegate; | 51 return this.sessionConnector_; |
45 }; | 52 }; |
46 | 53 |
47 /** | 54 /** |
48 * @return {string} Application product name to be used in UI. | |
49 */ | |
50 remoting.Application.prototype.getApplicationName = function() { | |
51 return this.delegate_.getApplicationName(); | |
52 }; | |
53 | |
54 /** | |
55 * @param {remoting.ClientSession.Capability} capability | 55 * @param {remoting.ClientSession.Capability} capability |
56 * @return {boolean} | 56 * @return {boolean} |
57 */ | 57 */ |
58 remoting.Application.prototype.hasCapability = function(capability) { | 58 remoting.Application.prototype.hasCapability = function(capability) { |
59 var capabilities = this.appCapabilities_; | 59 var capabilities = this.appCapabilities_; |
60 return capabilities.indexOf(capability) != -1; | 60 return capabilities.indexOf(capability) != -1; |
61 }; | 61 }; |
62 | 62 |
63 /* Disconnect the remoting client. */ | |
64 remoting.Application.prototype.disconnect = function() { | |
65 if (remoting.clientSession) { | |
66 remoting.clientSession.disconnect(remoting.Error.none()); | |
67 console.log('Disconnected.'); | |
68 } | |
69 }; | |
70 | |
71 /* Public method to exit the application. */ | |
72 remoting.Application.prototype.quit = function() { | |
73 this.exitApplication_(); | |
74 }; | |
75 | |
76 /** | |
77 * Close the main window when quitting the application. This should be called | |
78 * by exitApplication() in the subclass. | |
79 * @protected | |
80 */ | |
81 remoting.Application.prototype.exit_ = function() { | |
Jamie
2015/03/26 01:54:30
Having both exit_ and quit is confusing. Perhaps e
garykac
2015/03/26 16:38:11
Changed name to closeMainWindow_ since I like havi
| |
82 chrome.app.window.current().close(); | |
83 }; | |
84 | |
63 /** | 85 /** |
64 * Initialize the application and register all event handlers. After this | 86 * Initialize the application and register all event handlers. After this |
65 * is called, the app is running and waiting for user events. | 87 * is called, the app is running and waiting for user events. |
66 * | |
67 * @return {void} Nothing. | |
68 */ | 88 */ |
69 remoting.Application.prototype.start = function() { | 89 remoting.Application.prototype.start = function() { |
70 // Create global objects. | |
71 remoting.ClientPlugin.factory = new remoting.DefaultClientPluginFactory(); | |
72 remoting.SessionConnector.factory = | |
73 new remoting.DefaultSessionConnectorFactory(); | |
74 | |
75 // TODO(garykac): This should be owned properly rather than living in the | 90 // TODO(garykac): This should be owned properly rather than living in the |
76 // global 'remoting' namespace. | 91 // global 'remoting' namespace. |
77 remoting.settings = new remoting.Settings(); | 92 remoting.settings = new remoting.Settings(); |
78 | 93 |
79 remoting.initGlobalObjects(); | 94 remoting.initGlobalObjects(); |
80 remoting.initIdentity(); | 95 remoting.initIdentity(); |
81 | 96 |
82 this.delegate_.init(); | 97 this.initApplication_(); |
83 | 98 |
84 var that = this; | 99 var that = this; |
85 remoting.identity.getToken().then( | 100 remoting.identity.getToken(). |
86 this.delegate_.start.bind(this.delegate_, this.getSessionConnector()) | 101 then(this.startApplication_.bind(this)). |
87 ).catch(remoting.Error.handler( | 102 catch(remoting.Error.handler( |
88 function(/** !remoting.Error */ error) { | 103 function(/** !remoting.Error */ error) { |
89 if (error.hasTag(remoting.Error.Tag.CANCELLED)) { | 104 if (error.hasTag(remoting.Error.Tag.CANCELLED)) { |
90 that.exit(); | 105 that.exitApplication_(); |
91 } else { | 106 } else { |
92 that.delegate_.signInFailed(error); | 107 that.signInFailed_(error); |
93 } | 108 } |
94 } | 109 } |
95 ) | 110 ) |
96 ); | 111 ); |
97 }; | 112 }; |
98 | 113 |
99 /** | 114 /** |
100 * Quit the application. | |
101 */ | |
102 remoting.Application.prototype.exit = function() { | |
103 this.delegate_.handleExit(); | |
104 chrome.app.window.current().close(); | |
105 }; | |
106 | |
107 /** Disconnect the remoting client. */ | |
108 remoting.Application.prototype.disconnect = function() { | |
109 if (remoting.clientSession) { | |
110 remoting.clientSession.disconnect(remoting.Error.none()); | |
111 console.log('Disconnected.'); | |
112 } | |
113 }; | |
114 | |
115 /** | |
116 * Called when a new session has been connected. | 115 * Called when a new session has been connected. |
117 * | 116 * |
118 * @param {remoting.ConnectionInfo} connectionInfo | 117 * @param {remoting.ConnectionInfo} connectionInfo |
119 * @return {void} Nothing. | 118 * @return {void} Nothing. |
119 * @protected | |
120 */ | 120 */ |
121 remoting.Application.prototype.onConnected = function(connectionInfo) { | 121 remoting.Application.prototype.initSession_ = function(connectionInfo) { |
122 this.sessionConnectedHooks_ = new base.Disposables( | 122 this.sessionConnectedHooks_ = new base.Disposables( |
123 new base.EventHook(connectionInfo.session(), 'stateChanged', | 123 new base.EventHook(connectionInfo.session(), 'stateChanged', |
124 this.onSessionFinished_.bind(this)), | 124 this.onSessionFinished_.bind(this)), |
125 new base.RepeatingTimer(this.updateStatistics_.bind(this), 1000) | 125 new base.RepeatingTimer(this.updateStatistics_.bind(this), 1000) |
126 ); | 126 ); |
127 remoting.clipboard.startSession(); | 127 remoting.clipboard.startSession(); |
128 | |
129 this.delegate_.handleConnected(connectionInfo); | |
130 }; | 128 }; |
131 | 129 |
132 /** | 130 /** |
133 * Called when the current session has been disconnected. | |
134 * | |
135 * @return {void} Nothing. | |
136 */ | |
137 remoting.Application.prototype.onDisconnected = function() { | |
138 this.delegate_.handleDisconnected(); | |
139 }; | |
140 | |
141 /** | |
142 * Called when the current session's connection has failed. | |
143 * | |
144 * @param {!remoting.Error} error | |
145 * @return {void} Nothing. | |
146 */ | |
147 remoting.Application.prototype.onConnectionFailed = function(error) { | |
148 this.delegate_.handleConnectionFailed(this.sessionConnector_, error); | |
149 }; | |
150 | |
151 /** | |
152 * Called when an error needs to be displayed to the user. | |
153 * | |
154 * @param {!remoting.Error} errorTag The error to be localized and displayed. | |
155 * @return {void} Nothing. | |
156 */ | |
157 remoting.Application.prototype.onError = function(errorTag) { | |
158 this.delegate_.handleError(errorTag); | |
159 }; | |
160 | |
161 /** | |
162 * @return {remoting.SessionConnector} A session connector, creating a new one | |
163 * if necessary. | |
164 */ | |
165 remoting.Application.prototype.getSessionConnector = function() { | |
166 // TODO(garykac): Check if this can be initialized in the ctor. | |
167 if (!this.sessionConnector_) { | |
168 this.sessionConnector_ = remoting.SessionConnector.factory.createConnector( | |
169 document.getElementById('client-container'), | |
170 this.onConnected.bind(this), | |
171 this.onError.bind(this), | |
172 this.onConnectionFailed.bind(this), | |
173 this.appCapabilities_); | |
174 } | |
175 return this.sessionConnector_; | |
176 }; | |
177 | |
178 /** | |
179 * Callback function called when the state of the client plugin changes. The | 131 * Callback function called when the state of the client plugin changes. The |
180 * current and previous states are available via the |state| member variable. | 132 * current and previous states are available via the |state| member variable. |
181 * | 133 * |
182 * @param {remoting.ClientSession.StateEvent=} state | 134 * @param {remoting.ClientSession.StateEvent=} state |
183 * @private | 135 * @private |
184 */ | 136 */ |
185 remoting.Application.prototype.onSessionFinished_ = function(state) { | 137 remoting.Application.prototype.onSessionFinished_ = function(state) { |
186 switch (state.current) { | 138 switch (state.current) { |
187 case remoting.ClientSession.State.CLOSED: | 139 case remoting.ClientSession.State.CLOSED: |
188 console.log('Connection closed by host'); | 140 console.log('Connection closed by host'); |
189 this.onDisconnected(); | 141 this.onDisconnected_(); |
190 break; | 142 break; |
191 case remoting.ClientSession.State.FAILED: | 143 case remoting.ClientSession.State.FAILED: |
192 var error = remoting.clientSession.getError(); | 144 var error = remoting.clientSession.getError(); |
193 console.error('Client plugin reported connection failed: ' + | 145 console.error('Client plugin reported connection failed: ' + |
194 error.toString()); | 146 error.toString()); |
195 if (error === null) { | 147 if (error === null) { |
196 error = remoting.Error.unexpected(); | 148 error = remoting.Error.unexpected(); |
197 } | 149 } |
198 this.onError(error); | 150 this.onError_(error); |
199 break; | 151 break; |
200 | 152 |
201 default: | 153 default: |
202 console.error('Unexpected client plugin state: ' + state.current); | 154 console.error('Unexpected client plugin state: ' + state.current); |
203 // This should only happen if the web-app and client plugin get out of | 155 // This should only happen if the web-app and client plugin get out of |
204 // sync, so MISSING_PLUGIN is a suitable error. | 156 // sync, so MISSING_PLUGIN is a suitable error. |
205 this.onError(new remoting.Error(remoting.Error.Tag.MISSING_PLUGIN)); | 157 this.onError_(new remoting.Error(remoting.Error.Tag.MISSING_PLUGIN)); |
206 break; | 158 break; |
207 } | 159 } |
208 | 160 |
209 base.dispose(this.sessionConnectedHooks_); | 161 base.dispose(this.sessionConnectedHooks_); |
210 this.sessionConnectedHooks_= null; | 162 this.sessionConnectedHooks_= null; |
211 this.sessionConnector_.closeSession(); | 163 this.sessionConnector_.closeSession(); |
212 }; | 164 }; |
213 | 165 |
214 /** @private */ | 166 /** @private */ |
215 remoting.Application.prototype.updateStatistics_ = function() { | 167 remoting.Application.prototype.updateStatistics_ = function() { |
216 var perfstats = remoting.clientSession.getPerfStats(); | 168 var perfstats = remoting.clientSession.getPerfStats(); |
217 remoting.stats.update(perfstats); | 169 remoting.stats.update(perfstats); |
218 remoting.clientSession.logStatistics(perfstats); | 170 remoting.clientSession.logStatistics(perfstats); |
219 }; | 171 }; |
220 | 172 |
221 | 173 |
174 /* | |
175 * remoting.ApplicationInterface | |
176 * These functions must be overridden in the subclass. | |
177 */ | |
178 | |
179 /** @return {string} */ | |
180 remoting.Application.prototype.getApplicationName = function() { | |
181 base.debug.assert(false, "Subclass must override"); | |
182 }; | |
183 | |
184 /** | |
185 * @param {!remoting.Error} error | |
186 * @protected | |
187 */ | |
188 remoting.Application.prototype.signInFailed_ = function(error) { | |
189 base.debug.assert(false, "Subclass must override"); | |
190 }; | |
191 | |
192 /** @protected */ | |
193 remoting.Application.prototype.initApplication_ = function() { | |
194 base.debug.assert(false, "Subclass must override"); | |
195 }; | |
196 | |
197 /** | |
198 * @param {string} token | |
199 * @protected | |
200 */ | |
201 remoting.Application.prototype.startApplication_ = function(token) { | |
202 base.debug.assert(false, "Subclass must override"); | |
203 }; | |
204 | |
205 remoting.Application.prototype.exitApplication_ = function() { | |
206 base.debug.assert(false, "Subclass must override"); | |
207 }; | |
208 | |
209 /** | |
210 * @param {remoting.ConnectionInfo} connectionInfo | |
211 * @protected | |
212 */ | |
213 remoting.Application.prototype.onConnected_ = function(connectionInfo) { | |
214 base.debug.assert(false, "Subclass must override"); | |
215 }; | |
216 | |
217 /** @protected */ | |
218 remoting.Application.prototype.onDisconnected_ = function() { | |
219 base.debug.assert(false, "Subclass must override"); | |
220 }; | |
221 | |
222 /** | |
223 * @param {!remoting.Error} error | |
224 * @protected | |
225 */ | |
226 remoting.Application.prototype.onConnectionFailed_ = function(error) { | |
227 base.debug.assert(false, "Subclass must override"); | |
228 }; | |
229 | |
230 /** | |
231 * @param {!remoting.Error} error The error to be localized and displayed. | |
232 * @protected | |
233 */ | |
234 remoting.Application.prototype.onError_ = function(error) { | |
235 base.debug.assert(false, "Subclass must override"); | |
236 }; | |
237 | |
238 | |
222 /** | 239 /** |
240 * The interface specifies the methods that a subclass of remoting.Application | |
241 * is required implement to override the default behavior. | |
242 * | |
223 * @interface | 243 * @interface |
224 */ | 244 */ |
225 remoting.Application.Delegate = function() {}; | 245 remoting.ApplicationInterface = function() {}; |
246 | |
247 /** | |
248 * @return {string} Application product name to be used in UI. | |
249 */ | |
250 remoting.ApplicationInterface.prototype.getApplicationName = function() {}; | |
251 | |
252 /** | |
253 * Report an authentication error to the user. This is called in lieu of | |
254 * startApplication() if the user cannot be authenticated or if they decline | |
255 * the app permissions. | |
256 * | |
257 * @param {!remoting.Error} error The failure reason. | |
258 */ | |
259 remoting.ApplicationInterface.prototype.signInFailed_ = function(error) {}; | |
226 | 260 |
227 /** | 261 /** |
228 * Initialize the application. This is called before an OAuth token is requested | 262 * Initialize the application. This is called before an OAuth token is requested |
229 * and should be used for tasks such as initializing the DOM, registering event | 263 * and should be used for tasks such as initializing the DOM, registering event |
230 * handlers, etc. | 264 * handlers, etc. After this is called, the app is running and waiting for |
265 * user events. | |
231 */ | 266 */ |
232 remoting.Application.Delegate.prototype.init = function() {}; | 267 remoting.ApplicationInterface.prototype.initApplication_ = function() {}; |
233 | 268 |
234 /** | 269 /** |
235 * Start the application. Once start() is called, the delegate can assume that | 270 * Start the application. Once startApplication() is called, we can assume that |
236 * the user has consented to all permissions specified in the manifest. | 271 * the user has consented to all permissions specified in the manifest. |
237 * | 272 * |
238 * @param {remoting.SessionConnector} connector | 273 * @param {string} token An OAuth access token. The app should not cache |
239 * @param {string} token An OAuth access token. The delegate should not cache | |
240 * this token, but can assume that it will remain valid during application | 274 * this token, but can assume that it will remain valid during application |
241 * start-up. | 275 * start-up. |
242 */ | 276 */ |
243 remoting.Application.Delegate.prototype.start = function(connector, token) {}; | 277 remoting.ApplicationInterface.prototype.startApplication_ = function(token) {}; |
244 | 278 |
245 /** | 279 /** |
246 * Report an authentication error to the user. This is called in lieu of start() | 280 * Close down the application before exiting. |
247 * if the user cannot be authenticated. | |
248 * | |
249 * @param {!remoting.Error} error The failure reason. | |
250 */ | 281 */ |
251 remoting.Application.Delegate.prototype.signInFailed = function(error) {}; | 282 remoting.ApplicationInterface.prototype.exitApplication_ = function() {}; |
252 | |
253 /** | |
254 * @return {string} Application product name to be used in UI. | |
255 */ | |
256 remoting.Application.Delegate.prototype.getApplicationName = function() {}; | |
257 | 283 |
258 /** | 284 /** |
259 * Called when a new session has been connected. | 285 * Called when a new session has been connected. |
260 * | 286 * |
261 * @param {remoting.ConnectionInfo} connectionInfo | 287 * @param {remoting.ConnectionInfo} connectionInfo |
262 * @return {void} Nothing. | |
263 */ | 288 */ |
264 remoting.Application.Delegate.prototype.handleConnected = function( | 289 remoting.ApplicationInterface.prototype.onConnected_ = |
265 connectionInfo) {}; | 290 function(connectionInfo) {}; |
266 | 291 |
267 /** | 292 /** |
268 * Called when the current session has been disconnected. | 293 * Called when the current session has been disconnected. |
269 * | |
270 * @return {void} Nothing. | |
271 */ | 294 */ |
272 remoting.Application.Delegate.prototype.handleDisconnected = function() {}; | 295 remoting.ApplicationInterface.prototype.onDisconnected_ = function() {}; |
273 | 296 |
274 /** | 297 /** |
275 * Called when the current session's connection has failed. | 298 * Called when the current session's connection has failed. |
276 * | 299 * |
277 * @param {remoting.SessionConnector} connector | |
278 * @param {!remoting.Error} error | 300 * @param {!remoting.Error} error |
279 * @return {void} Nothing. | |
280 */ | 301 */ |
281 remoting.Application.Delegate.prototype.handleConnectionFailed = | 302 remoting.ApplicationInterface.prototype.onConnectionFailed_ = |
282 function(connector, error) {}; | 303 function(error) {}; |
283 | 304 |
284 /** | 305 /** |
285 * Called when an error needs to be displayed to the user. | 306 * Called when an error needs to be displayed to the user. |
286 * | 307 * |
287 * @param {!remoting.Error} errorTag The error to be localized and displayed. | 308 * @param {!remoting.Error} errorTag The error to be localized and displayed. |
288 * @return {void} Nothing. | |
289 */ | 309 */ |
290 remoting.Application.Delegate.prototype.handleError = function(errorTag) {}; | 310 remoting.ApplicationInterface.prototype.onError_ = function(errorTag) {}; |
291 | |
292 /** | |
293 * Perform any application-specific cleanup before exiting. This is called in | |
294 * lieu of start() if the user declines the app permissions, and will usually | |
295 * be called immediately prior to exiting, although delegates should not rely | |
296 * on this. | |
297 */ | |
298 remoting.Application.Delegate.prototype.handleExit = function() {}; | |
299 | 311 |
300 | 312 |
301 /** @type {remoting.Application} */ | 313 /** @type {remoting.Application} */ |
302 remoting.app = null; | 314 remoting.app = null; |
OLD | NEW |