Chromium Code Reviews| 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 |