| OLD | NEW |
| (Empty) |
| 1 /* Copyright 2013 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 | |
| 6 /** | |
| 7 * @fileoverview | |
| 8 * The application side of the application/sandbox WCS interface, used by the | |
| 9 * application to exchange messages with the sandbox. | |
| 10 */ | |
| 11 | |
| 12 'use strict'; | |
| 13 | |
| 14 /** @suppress {duplicate} */ | |
| 15 var remoting = remoting || {}; | |
| 16 | |
| 17 /** | |
| 18 * @param {Window} sandbox The Javascript Window object representing the | |
| 19 * sandboxed WCS driver. | |
| 20 * @constructor | |
| 21 */ | |
| 22 remoting.WcsSandboxContainer = function(sandbox) { | |
| 23 /** @private */ | |
| 24 this.sandbox_ = sandbox; | |
| 25 /** @private {?function(string):void} */ | |
| 26 this.onConnected_ = null; | |
| 27 /** @private {function(!remoting.Error):void} */ | |
| 28 this.onError_ = function(error) {}; | |
| 29 /** @private {?function(string):void} */ | |
| 30 this.onIq_ = null; | |
| 31 /** @private {Object<number, XMLHttpRequest>} */ | |
| 32 this.pendingXhrs_ = {}; | |
| 33 /** @private */ | |
| 34 this.localJid_ = ''; | |
| 35 | |
| 36 /** @private */ | |
| 37 this.accessTokenRefreshTimerStarted_ = false; | |
| 38 | |
| 39 window.addEventListener('message', this.onMessage_.bind(this), false); | |
| 40 | |
| 41 if (base.isAppsV2()) { | |
| 42 var message = { | |
| 43 'command': 'proxyXhrs' | |
| 44 }; | |
| 45 this.sandbox_.postMessage(message, '*'); | |
| 46 } | |
| 47 }; | |
| 48 | |
| 49 /** | |
| 50 * @param {function(string):void} onConnected Callback to be called when WCS is | |
| 51 * connected. May be called synchronously if WCS is already connected. | |
| 52 * @param {function(!remoting.Error):void} onError called in case of an error. | |
| 53 * @return {void} Nothing. | |
| 54 */ | |
| 55 remoting.WcsSandboxContainer.prototype.connect = function( | |
| 56 onConnected, onError) { | |
| 57 this.onError_ = onError; | |
| 58 this.ensureAccessTokenRefreshTimer_(); | |
| 59 if (this.localJid_) { | |
| 60 onConnected(this.localJid_); | |
| 61 } else { | |
| 62 this.onConnected_ = onConnected; | |
| 63 } | |
| 64 }; | |
| 65 | |
| 66 /** | |
| 67 * @param {?function(string):void} onIq Callback invoked when an IQ stanza is | |
| 68 * received. | |
| 69 * @return {void} Nothing. | |
| 70 */ | |
| 71 remoting.WcsSandboxContainer.prototype.setOnIq = function(onIq) { | |
| 72 this.onIq_ = onIq; | |
| 73 }; | |
| 74 | |
| 75 /** | |
| 76 * Refreshes access token and starts a timer to update it periodically. | |
| 77 * | |
| 78 * @private | |
| 79 */ | |
| 80 remoting.WcsSandboxContainer.prototype.ensureAccessTokenRefreshTimer_ = | |
| 81 function() { | |
| 82 if (this.accessTokenRefreshTimerStarted_) { | |
| 83 return; | |
| 84 } | |
| 85 | |
| 86 this.refreshAccessToken_(); | |
| 87 setInterval(this.refreshAccessToken_.bind(this), 60 * 1000); | |
| 88 this.accessTokenRefreshTimerStarted_ = true; | |
| 89 } | |
| 90 | |
| 91 /** | |
| 92 * @private | |
| 93 * @return {void} Nothing. | |
| 94 */ | |
| 95 remoting.WcsSandboxContainer.prototype.refreshAccessToken_ = function() { | |
| 96 remoting.identity.getToken().then( | |
| 97 this.setAccessToken_.bind(this), | |
| 98 remoting.Error.handler(this.onError_)); | |
| 99 }; | |
| 100 | |
| 101 /** | |
| 102 * @private | |
| 103 * @param {string} token The access token. | |
| 104 * @return {void} | |
| 105 */ | |
| 106 remoting.WcsSandboxContainer.prototype.setAccessToken_ = function(token) { | |
| 107 var message = { | |
| 108 'command': 'setAccessToken', | |
| 109 'token': token | |
| 110 }; | |
| 111 this.sandbox_.postMessage(message, '*'); | |
| 112 }; | |
| 113 | |
| 114 /** | |
| 115 * @param {string} stanza The IQ stanza to send. | |
| 116 * @return {void} | |
| 117 */ | |
| 118 remoting.WcsSandboxContainer.prototype.sendIq = function(stanza) { | |
| 119 var message = { | |
| 120 'command': 'sendIq', | |
| 121 'stanza': stanza | |
| 122 }; | |
| 123 this.sandbox_.postMessage(message, '*'); | |
| 124 }; | |
| 125 | |
| 126 /** | |
| 127 * Event handler to process messages from the sandbox. | |
| 128 * | |
| 129 * @param {Event} event | |
| 130 */ | |
| 131 remoting.WcsSandboxContainer.prototype.onMessage_ = function(event) { | |
| 132 switch (event.data['command']) { | |
| 133 | |
| 134 case 'onLocalJid': | |
| 135 /** @type {string} */ | |
| 136 var localJid = event.data['localJid']; | |
| 137 if (localJid === undefined) { | |
| 138 console.error('onReady: missing localJid'); | |
| 139 break; | |
| 140 } | |
| 141 this.localJid_ = localJid; | |
| 142 if (this.onConnected_) { | |
| 143 var callback = this.onConnected_; | |
| 144 this.onConnected_ = null; | |
| 145 callback(localJid); | |
| 146 } | |
| 147 break; | |
| 148 | |
| 149 case 'onError': | |
| 150 /** @type {!remoting.Error} */ | |
| 151 var error = event.data['error']; | |
| 152 if (error === undefined) { | |
| 153 console.error('onError: missing error code'); | |
| 154 break; | |
| 155 } | |
| 156 this.onError_(error); | |
| 157 break; | |
| 158 | |
| 159 case 'onIq': | |
| 160 /** @type {string} */ | |
| 161 var stanza = event.data['stanza']; | |
| 162 if (stanza === undefined) { | |
| 163 console.error('onIq: missing IQ stanza'); | |
| 164 break; | |
| 165 } | |
| 166 if (this.onIq_) { | |
| 167 this.onIq_(stanza); | |
| 168 } | |
| 169 break; | |
| 170 | |
| 171 case 'sendXhr': | |
| 172 /** @type {number} */ | |
| 173 var id = event.data['id']; | |
| 174 if (id === undefined) { | |
| 175 console.error('sendXhr: missing id'); | |
| 176 break; | |
| 177 } | |
| 178 /** @type {Object} */ | |
| 179 var parameters = event.data['parameters']; | |
| 180 if (parameters === undefined) { | |
| 181 console.error('sendXhr: missing parameters'); | |
| 182 break; | |
| 183 } | |
| 184 /** @type {string} */ | |
| 185 var method = parameters['method']; | |
| 186 if (method === undefined) { | |
| 187 console.error('sendXhr: missing method'); | |
| 188 break; | |
| 189 } | |
| 190 /** @type {string} */ | |
| 191 var url = parameters['url']; | |
| 192 if (url === undefined) { | |
| 193 console.error('sendXhr: missing url'); | |
| 194 break; | |
| 195 } | |
| 196 /** @type {string} */ | |
| 197 var data = parameters['data']; | |
| 198 if (data === undefined) { | |
| 199 console.error('sendXhr: missing data'); | |
| 200 break; | |
| 201 } | |
| 202 /** @type {string|undefined}*/ | |
| 203 var user = parameters['user']; | |
| 204 /** @type {string|undefined}*/ | |
| 205 var password = parameters['password']; | |
| 206 var xhr = new XMLHttpRequest; | |
| 207 this.pendingXhrs_[id] = xhr; | |
| 208 xhr.open(method, url, true, user, password); | |
| 209 /** @type {Object} */ | |
| 210 var headers = parameters['headers']; | |
| 211 if (headers) { | |
| 212 for (var header in headers) { | |
| 213 xhr.setRequestHeader(header, headers[header]); | |
| 214 } | |
| 215 } | |
| 216 xhr.onreadystatechange = this.onReadyStateChange_.bind(this, id); | |
| 217 xhr.send(data); | |
| 218 break; | |
| 219 | |
| 220 case 'abortXhr': | |
| 221 var id = event.data['id']; | |
| 222 if (id === undefined) { | |
| 223 console.error('abortXhr: missing id'); | |
| 224 break; | |
| 225 } | |
| 226 var xhr = this.pendingXhrs_[id] | |
| 227 if (!xhr) { | |
| 228 // It's possible for an abort and a reply to cross each other on the | |
| 229 // IPC channel. In that case, we silently ignore the abort. | |
| 230 break; | |
| 231 } | |
| 232 xhr.abort(); | |
| 233 break; | |
| 234 | |
| 235 default: | |
| 236 console.error('Unexpected message:', event.data['command'], event.data); | |
| 237 } | |
| 238 }; | |
| 239 | |
| 240 /** | |
| 241 * Return a "copy" of an XHR object suitable for postMessage. Specifically, | |
| 242 * remove all non-serializable members such as functions. | |
| 243 * | |
| 244 * @param {XMLHttpRequest} xhr The XHR to serialize. | |
| 245 * @return {Object} A serializable version of the input. | |
| 246 */ | |
| 247 function sanitizeXhr_(xhr) { | |
| 248 /** @type {Object} */ | |
| 249 var result = { | |
| 250 readyState: xhr.readyState, | |
| 251 response: xhr.response, | |
| 252 responseText: xhr.responseText, | |
| 253 responseType: xhr.responseType, | |
| 254 responseXML: xhr.responseXML, | |
| 255 status: xhr.status, | |
| 256 statusText: xhr.statusText, | |
| 257 withCredentials: xhr.withCredentials | |
| 258 }; | |
| 259 return result; | |
| 260 } | |
| 261 | |
| 262 /** | |
| 263 * @param {number} id The unique ID of the XHR for which the state has changed. | |
| 264 * @private | |
| 265 */ | |
| 266 remoting.WcsSandboxContainer.prototype.onReadyStateChange_ = function(id) { | |
| 267 var xhr = this.pendingXhrs_[id]; | |
| 268 if (!xhr) { | |
| 269 // XHRs are only removed when they have completed, in which case no | |
| 270 // further callbacks should be received. | |
| 271 console.error('Unexpected callback for xhr', id); | |
| 272 return; | |
| 273 } | |
| 274 var message = { | |
| 275 'command': 'xhrStateChange', | |
| 276 'id': id, | |
| 277 'xhr': sanitizeXhr_(xhr) | |
| 278 }; | |
| 279 this.sandbox_.postMessage(message, '*'); | |
| 280 if (xhr.readyState == 4) { | |
| 281 delete this.pendingXhrs_[id]; | |
| 282 } | |
| 283 } | |
| 284 | |
| 285 /** @type {remoting.WcsSandboxContainer} */ | |
| 286 remoting.wcsSandbox = null; | |
| OLD | NEW |