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