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