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