blob: 6eaac662e67746fa7e9c4a888b22b0f018a547de [file] [log] [blame]
/* Copyright 2013 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/**
* @fileoverview
* The application side of the application/sandbox WCS interface, used by the
* application to exchange messages with the sandbox.
*/
'use strict';
/** @suppress {duplicate} */
var remoting = remoting || {};
/**
* @param {Window} sandbox The Javascript Window object representing the
* sandboxed WCS driver.
* @constructor
*/
remoting.WcsSandboxContainer = function(sandbox) {
this.sandbox_ = sandbox;
/** @type {?function(string):void} */
this.onLocalJid_ = null;
/** @type {?function(remoting.Error):void} */
this.onError_ = null;
/** @type {?function(string):void} */
this.onIq_ = null;
/** @type {Object.<number, XMLHttpRequest>} */
this.pendingXhrs_ = {};
window.addEventListener('message', this.onMessage_.bind(this), false);
if (remoting.isAppsV2) {
var message = {
'command': 'proxyXhrs'
};
this.sandbox_.postMessage(message, '*');
}
};
/**
* @param {?function(string):void} onLocalJid Callback invoked with the client
* JID when the WCS code has loaded. Note that this may be called more than
* once (potentially with a different JID) if the WCS node is reloaded for
* any reason.
* @return {void} Nothing.
*/
remoting.WcsSandboxContainer.prototype.setOnLocalJid = function(onLocalJid) {
this.onLocalJid_ = onLocalJid;
};
/**
* @param {?function(remoting.Error):void} onError Callback invoked if the WCS
* code cannot be loaded.
* @return {void} Nothing.
*/
remoting.WcsSandboxContainer.prototype.setOnError = function(onError) {
this.onError_ = onError;
};
/**
* @param {?function(string):void} onIq Callback invoked when an IQ stanza is
* received.
* @return {void} Nothing.
*/
remoting.WcsSandboxContainer.prototype.setOnIq = function(onIq) {
this.onIq_ = onIq;
};
/**
* @param {string} token The access token.
* @return {void}
*/
remoting.WcsSandboxContainer.prototype.setAccessToken = function(token) {
var message = {
'command': 'setAccessToken',
'token': token
};
this.sandbox_.postMessage(message, '*');
};
/**
* @param {string} stanza The IQ stanza to send.
* @return {void}
*/
remoting.WcsSandboxContainer.prototype.sendIq = function(stanza) {
var message = {
'command': 'sendIq',
'stanza': stanza
};
this.sandbox_.postMessage(message, '*');
};
/**
* Event handler to process messages from the sandbox.
*
* @param {Event} event
*/
remoting.WcsSandboxContainer.prototype.onMessage_ = function(event) {
switch (event.data['command']) {
case 'onLocalJid':
/** @type {string} */
var clientJid = event.data['clientJid'];
if (clientJid === undefined) {
console.error('onReady: missing client JID');
break;
}
if (this.onLocalJid_) {
this.onLocalJid_(clientJid);
}
break;
case 'onError':
/** @type {remoting.Error} */
var error = event.data['error'];
if (error === undefined) {
console.error('onError: missing error code');
break;
}
this.onError_(error);
break;
case 'onIq':
/** @type {string} */
var stanza = event.data['stanza'];
if (stanza === undefined) {
console.error('onIq: missing IQ stanza');
break;
}
if (this.onIq_) {
this.onIq_(stanza);
}
break;
case 'sendXhr':
/** @type {number} */
var id = event.data['id'];
if (id === undefined) {
console.error('sendXhr: missing id');
break;
}
/** @type {Object} */
var parameters = event.data['parameters'];
if (parameters === undefined) {
console.error('sendXhr: missing parameters');
break;
}
/** @type {string} */
var method = parameters['method'];
if (method === undefined) {
console.error('sendXhr: missing method');
break;
}
/** @type {string} */
var url = parameters['url'];
if (url === undefined) {
console.error('sendXhr: missing url');
break;
}
/** @type {string} */
var data = parameters['data'];
if (data === undefined) {
console.error('sendXhr: missing data');
break;
}
/** @type {string|undefined}*/
var user = parameters['user'];
/** @type {string|undefined}*/
var password = parameters['password'];
var xhr = new XMLHttpRequest;
this.pendingXhrs_[id] = xhr;
xhr.open(method, url, true, user, password);
/** @type {Object} */
var headers = parameters['headers'];
if (headers) {
for (var header in headers) {
xhr.setRequestHeader(header, headers[header]);
}
}
xhr.onreadystatechange = this.onReadyStateChange_.bind(this, id);
xhr.send(data);
break;
case 'abortXhr':
var id = event.data['id'];
if (id === undefined) {
console.error('abortXhr: missing id');
break;
}
var xhr = this.pendingXhrs_[id]
if (!xhr) {
// It's possible for an abort and a reply to cross each other on the
// IPC channel. In that case, we silently ignore the abort.
break;
}
xhr.abort();
break;
default:
console.error('Unexpected message:', event.data['command'], event.data);
}
};
/**
* Return a "copy" of an XHR object suitable for postMessage. Specifically,
* remove all non-serializable members such as functions.
*
* @param {XMLHttpRequest} xhr The XHR to serialize.
* @return {Object} A serializable version of the input.
*/
function sanitizeXhr_(xhr) {
/** @type {Object} */
var result = {
readyState: xhr.readyState,
response: xhr.response,
responseText: xhr.responseText,
responseType: xhr.responseType,
responseXML: xhr.responseXML,
status: xhr.status,
statusText: xhr.statusText,
withCredentials: xhr.withCredentials
};
return result;
}
/**
* @param {number} id The unique ID of the XHR for which the state has changed.
* @private
*/
remoting.WcsSandboxContainer.prototype.onReadyStateChange_ = function(id) {
var xhr = this.pendingXhrs_[id];
if (!xhr) {
// XHRs are only removed when they have completed, in which case no
// further callbacks should be received.
console.error('Unexpected callback for xhr', id);
return;
}
var message = {
'command': 'xhrStateChange',
'id': id,
'xhr': sanitizeXhr_(xhr)
};
this.sandbox_.postMessage(message, '*');
if (xhr.readyState == 4) {
delete this.pendingXhrs_[id];
}
}
/** @type {remoting.WcsSandboxContainer} */
remoting.wcsSandbox = null;