/* 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) { /** @private */ this.sandbox_ = sandbox; /** @type {?function(string):void} * @private */ this.onConnected_ = null; /** @type {function(remoting.Error):void} * @private */ this.onError_ = function(error) {}; /** @type {?function(string):void} * @private */ this.onIq_ = null; /** @type {Object.} * @private */ this.pendingXhrs_ = {}; /** @private */ this.localJid_ = ''; /** @private */ this.accessTokenRefreshTimerStarted_ = false; window.addEventListener('message', this.onMessage_.bind(this), false); if (remoting.isAppsV2) { var message = { 'command': 'proxyXhrs' }; this.sandbox_.postMessage(message, '*'); } }; /** * @param {function(string):void} onConnected Callback to be called when WCS is * connected. May be called synchronously if WCS is already connected. * @param {function(remoting.Error):void} onError called in case of an error. * @return {void} Nothing. */ remoting.WcsSandboxContainer.prototype.connect = function( onConnected, onError) { this.onError_ = onError; this.ensureAccessTokenRefreshTimer_(); if (this.localJid_) { onConnected(this.localJid_); } else { this.onConnected_ = onConnected; } }; /** * @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; }; /** * Refreshes access token and starts a timer to update it periodically. * * @private */ remoting.WcsSandboxContainer.prototype.ensureAccessTokenRefreshTimer_ = function() { if (this.accessTokenRefreshTimerStarted_) { return; } this.refreshAccessToken_(); setInterval(this.refreshAccessToken_.bind(this), 60 * 1000); this.accessTokenRefreshTimerStarted_ = true; } /** * @private * @return {void} Nothing. */ remoting.WcsSandboxContainer.prototype.refreshAccessToken_ = function() { remoting.identity.callWithToken( this.setAccessToken_.bind(this), this.onError_); }; /** * @private * @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 localJid = event.data['localJid']; if (localJid === undefined) { console.error('onReady: missing localJid'); break; } this.localJid_ = localJid; if (this.onConnected_) { var callback = this.onConnected_; this.onConnected_ = null; callback(localJid); } 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;