Home | History | Annotate | Download | only in webapp
      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   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  */
     50 remoting.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  */
     59 remoting.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  */
     68 remoting.WcsSandboxContainer.prototype.setOnIq = function(onIq) {
     69   this.onIq_ = onIq;
     70 };
     71 
     72 /**
     73  * @param {string} token The access token.
     74  * @return {void}
     75  */
     76 remoting.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  */
     88 remoting.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  */
    101 remoting.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  */
    214 function 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  */
    233 remoting.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} */
    253 remoting.wcsSandbox = null;