Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / print_preview / cloud_print_interface.js
1 // Copyright (c) 2012 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 cr.define('cloudprint', function() {
6   'use strict';
7
8   /**
9    * API to the Google Cloud Print service.
10    * @param {string} baseUrl Base part of the Google Cloud Print service URL
11    *     with no trailing slash. For example,
12    *     'https://www.google.com/cloudprint'.
13    * @param {!print_preview.NativeLayer} nativeLayer Native layer used to get
14    *     Auth2 tokens.
15    * @constructor
16    * @extends {cr.EventTarget}
17    */
18   function CloudPrintInterface(baseUrl, nativeLayer) {
19     /**
20      * The base URL of the Google Cloud Print API.
21      * @type {string}
22      * @private
23      */
24     this.baseUrl_ = baseUrl;
25
26     /**
27      * Used to get Auth2 tokens.
28      * @type {!print_preview.NativeLayer}
29      * @private
30      */
31     this.nativeLayer_ = nativeLayer;
32
33     /**
34      * Last received XSRF token. Sent as a parameter in every request.
35      * @type {string}
36      * @private
37      */
38     this.xsrfToken_ = '';
39
40     /**
41      * Pending requests delayed until we get access token.
42      * @type {!Array.<!CloudPrintRequest>}
43      * @private
44      */
45     this.requestQueue_ = [];
46
47     /**
48      * Number of outstanding cloud destination search requests.
49      * @type {number}
50      * @private
51      */
52     this.outstandingCloudSearchRequestCount_ = 0;
53
54     /**
55      * Event tracker used to keep track of native layer events.
56      * @type {!EventTracker}
57      * @private
58      */
59     this.tracker_ = new EventTracker();
60
61     this.addEventListeners_();
62   };
63
64   /**
65    * Event types dispatched by the interface.
66    * @enum {string}
67    */
68   CloudPrintInterface.EventType = {
69     PRINTER_DONE: 'cloudprint.CloudPrintInterface.PRINTER_DONE',
70     PRINTER_FAILED: 'cloudprint.CloudPrintInterface.PRINTER_FAILED',
71     SEARCH_DONE: 'cloudprint.CloudPrintInterface.SEARCH_DONE',
72     SEARCH_FAILED: 'cloudprint.CloudPrintInterface.SEARCH_FAILED',
73     SUBMIT_DONE: 'cloudprint.CloudPrintInterface.SUBMIT_DONE',
74     SUBMIT_FAILED: 'cloudprint.CloudPrintInterface.SUBMIT_FAILED',
75     UPDATE_PRINTER_TOS_ACCEPTANCE_FAILED:
76         'cloudprint.CloudPrintInterface.UPDATE_PRINTER_TOS_ACCEPTANCE_FAILED'
77   };
78
79   /**
80    * Content type header value for a URL encoded HTTP request.
81    * @type {string}
82    * @const
83    * @private
84    */
85   CloudPrintInterface.URL_ENCODED_CONTENT_TYPE_ =
86       'application/x-www-form-urlencoded';
87
88   /**
89    * Multi-part POST request boundary used in communication with Google
90    * Cloud Print.
91    * @type {string}
92    * @const
93    * @private
94    */
95   CloudPrintInterface.MULTIPART_BOUNDARY_ =
96       '----CloudPrintFormBoundaryjc9wuprokl8i';
97
98   /**
99    * Content type header value for a multipart HTTP request.
100    * @type {string}
101    * @const
102    * @private
103    */
104   CloudPrintInterface.MULTIPART_CONTENT_TYPE_ =
105       'multipart/form-data; boundary=' +
106       CloudPrintInterface.MULTIPART_BOUNDARY_;
107
108   /**
109    * Regex that extracts Chrome's version from the user-agent string.
110    * @type {!RegExp}
111    * @const
112    * @private
113    */
114   CloudPrintInterface.VERSION_REGEXP_ = /.*Chrome\/([\d\.]+)/i;
115
116   /**
117    * Enumeration of JSON response fields from Google Cloud Print API.
118    * @enum {string}
119    * @private
120    */
121   CloudPrintInterface.JsonFields_ = {
122     PRINTER: 'printer'
123   };
124
125   /**
126    * Could Print origins used to search printers.
127    * @type {!Array.<!print_preview.Destination.Origin>}
128    * @const
129    * @private
130    */
131   CloudPrintInterface.CLOUD_ORIGINS_ = [
132       print_preview.Destination.Origin.COOKIES,
133       print_preview.Destination.Origin.DEVICE
134       // TODO(vitalybuka): Enable when implemented.
135       // ready print_preview.Destination.Origin.PROFILE
136   ];
137
138   CloudPrintInterface.prototype = {
139     __proto__: cr.EventTarget.prototype,
140
141     /** @return {string} Base URL of the Google Cloud Print service. */
142     get baseUrl() {
143       return this.baseUrl_;
144     },
145
146     /**
147      * @return {boolean} Whether a search for cloud destinations is in progress.
148      */
149     get isCloudDestinationSearchInProgress() {
150       return this.outstandingCloudSearchRequestCount_ > 0;
151     },
152
153     /**
154      * Sends a Google Cloud Print search API request.
155      * @param {boolean} isRecent Whether to search for only recently used
156      *     printers.
157      */
158     search: function(isRecent) {
159       var params = [
160         new HttpParam('connection_status', 'ALL'),
161         new HttpParam('client', 'chrome'),
162         new HttpParam('use_cdd', 'true')
163       ];
164       if (isRecent) {
165         params.push(new HttpParam('q', '^recent'));
166       }
167       CloudPrintInterface.CLOUD_ORIGINS_.forEach(function(origin) {
168         ++this.outstandingCloudSearchRequestCount_;
169         var cpRequest =
170             this.buildRequest_('GET', 'search', params, origin,
171                                this.onSearchDone_.bind(this, isRecent));
172         this.sendOrQueueRequest_(cpRequest);
173       }, this);
174     },
175
176     /**
177      * Sends a Google Cloud Print submit API request.
178      * @param {!print_preview.Destination} destination Cloud destination to
179      *     print to.
180      * @param {!print_preview.PrintTicketStore} printTicketStore Contains the
181      *     print ticket to print.
182      * @param {!print_preview.DocumentInfo} documentInfo Document data model.
183      * @param {string} data Base64 encoded data of the document.
184      */
185     submit: function(destination, printTicketStore, documentInfo, data) {
186       var result =
187           CloudPrintInterface.VERSION_REGEXP_.exec(navigator.userAgent);
188       var chromeVersion = 'unknown';
189       if (result && result.length == 2) {
190         chromeVersion = result[1];
191       }
192       var params = [
193         new HttpParam('printerid', destination.id),
194         new HttpParam('contentType', 'dataUrl'),
195         new HttpParam('title', documentInfo.title),
196         new HttpParam('ticket',
197                       printTicketStore.createPrintTicket(destination)),
198         new HttpParam('content', 'data:application/pdf;base64,' + data),
199         new HttpParam('tag',
200                       '__google__chrome_version=' + chromeVersion),
201         new HttpParam('tag', '__google__os=' + navigator.platform)
202       ];
203       var cpRequest = this.buildRequest_('POST', 'submit', params,
204                                          destination.origin,
205                                          this.onSubmitDone_.bind(this));
206       this.sendOrQueueRequest_(cpRequest);
207     },
208
209     /**
210      * Sends a Google Cloud Print printer API request.
211      * @param {string} printerId ID of the printer to lookup.
212      * @param {!print_preview.Destination.Origin} origin Origin of the printer.
213      */
214     printer: function(printerId, origin) {
215       var params = [
216         new HttpParam('printerid', printerId),
217         new HttpParam('use_cdd', 'true'),
218         new HttpParam('printer_connection_status', 'true')
219       ];
220       var cpRequest =
221           this.buildRequest_('GET', 'printer', params, origin,
222                              this.onPrinterDone_.bind(this, printerId));
223       this.sendOrQueueRequest_(cpRequest);
224     },
225
226     /**
227      * Sends a Google Cloud Print update API request to accept (or reject) the
228      * terms-of-service of the given printer.
229      * @param {string} printerId ID of the printer to accept the
230      *     terms-of-service for.
231      * @param {!print_preview.Destination.Origin} origin Origin of the printer.
232      * @param {boolean} isAccepted Whether the user accepted the
233      *     terms-of-service.
234      */
235     updatePrinterTosAcceptance: function(printerId, origin, isAccepted) {
236       var params = [
237         new HttpParam('printerid', printerId),
238         new HttpParam('is_tos_accepted', isAccepted)
239       ];
240       var cpRequest =
241           this.buildRequest_('POST', 'update', params, origin,
242                              this.onUpdatePrinterTosAcceptanceDone_.bind(this));
243       this.sendOrQueueRequest_(cpRequest);
244     },
245
246     /**
247      * Adds event listeners to the relevant native layer events.
248      * @private
249      */
250     addEventListeners_: function() {
251       this.tracker_.add(
252           this.nativeLayer_,
253           print_preview.NativeLayer.EventType.ACCESS_TOKEN_READY,
254           this.onAccessTokenReady_.bind(this));
255     },
256
257     /**
258      * Builds request to the Google Cloud Print API.
259      * @param {string} method HTTP method of the request.
260      * @param {string} action Google Cloud Print action to perform.
261      * @param {Array.<!HttpParam>} params HTTP parameters to include in the
262      *     request.
263      * @param {!print_preview.Destination.Origin} origin Origin for destination.
264      * @param {function(number, Object, !print_preview.Destination.Origin)}
265      *     callback Callback to invoke when request completes.
266      * @return {!CloudPrintRequest} Partially prepared request.
267      * @private
268      */
269     buildRequest_: function(method, action, params, origin, callback) {
270       var url = this.baseUrl_ + '/' + action + '?xsrf=';
271       if (origin == print_preview.Destination.Origin.COOKIES) {
272         if (!this.xsrfToken_) {
273           // TODO(rltoscano): Should throw an error if not a read-only action or
274           // issue an xsrf token request.
275         } else {
276           url = url + this.xsrfToken_;
277         }
278       }
279       var body = null;
280       if (params) {
281         if (method == 'GET') {
282           url = params.reduce(function(partialUrl, param) {
283             return partialUrl + '&' + param.name + '=' +
284                 encodeURIComponent(param.value);
285           }, url);
286         } else if (method == 'POST') {
287           body = params.reduce(function(partialBody, param) {
288             return partialBody + 'Content-Disposition: form-data; name=\"' +
289                 param.name + '\"\r\n\r\n' + param.value + '\r\n--' +
290                 CloudPrintInterface.MULTIPART_BOUNDARY_ + '\r\n';
291           }, '--' + CloudPrintInterface.MULTIPART_BOUNDARY_ + '\r\n');
292         }
293       }
294
295       var headers = {};
296       headers['X-CloudPrint-Proxy'] = 'ChromePrintPreview';
297       if (method == 'GET') {
298         headers['Content-Type'] = CloudPrintInterface.URL_ENCODED_CONTENT_TYPE_;
299       } else if (method == 'POST') {
300         headers['Content-Type'] = CloudPrintInterface.MULTIPART_CONTENT_TYPE_;
301       }
302
303       var xhr = new XMLHttpRequest();
304       xhr.open(method, url, true);
305       xhr.withCredentials =
306           (origin == print_preview.Destination.Origin.COOKIES);
307       for (var header in headers) {
308         xhr.setRequestHeader(header, headers[header]);
309       }
310
311       return new CloudPrintRequest(xhr, body, origin, callback);
312     },
313
314     /**
315      * Sends a request to the Google Cloud Print API or queues if it needs to
316      *     wait OAuth2 access token.
317      * @param {!CloudPrintRequest} request Request to send or queue.
318      * @private
319      */
320     sendOrQueueRequest_: function(request) {
321       if (request.origin == print_preview.Destination.Origin.COOKIES) {
322         return this.sendRequest_(request);
323       } else {
324         this.requestQueue_.push(request);
325         this.nativeLayer_.startGetAccessToken(request.origin);
326       }
327     },
328
329     /**
330      * Sends a request to the Google Cloud Print API.
331      * @param {!CloudPrintRequest} request Request to send.
332      * @private
333      */
334     sendRequest_: function(request) {
335       request.xhr.onreadystatechange =
336           this.onReadyStateChange_.bind(this, request);
337       request.xhr.send(request.body);
338     },
339
340     /**
341      * Creates a Google Cloud Print interface error that is ready to dispatch.
342      * @param {!CloudPrintInterface.EventType} type Type of the error.
343      * @param {!CloudPrintRequest} request Request that has been completed.
344      * @return {!Event} Google Cloud Print interface error event.
345      * @private
346      */
347     createErrorEvent_: function(type, request) {
348       var errorEvent = new Event(type);
349       errorEvent.status = request.xhr.status;
350       if (request.xhr.status == 200) {
351         errorEvent.errorCode = request.result['errorCode'];
352         errorEvent.message = request.result['message'];
353       } else {
354         errorEvent.errorCode = 0;
355         errorEvent.message = '';
356       }
357       errorEvent.origin = request.origin;
358       return errorEvent;
359     },
360
361     /**
362      * Called when a native layer receives access token.
363      * @param {Event} evt Contains the authetication type and access token.
364      * @private
365      */
366     onAccessTokenReady_: function(event) {
367       // TODO(vitalybuka): remove when other Origins implemented.
368       assert(event.authType == print_preview.Destination.Origin.DEVICE);
369       this.requestQueue_ = this.requestQueue_.filter(function(request) {
370         assert(request.origin == print_preview.Destination.Origin.DEVICE);
371         if (request.origin != event.authType) {
372           return true;
373         }
374         if (event.accessToken) {
375           request.xhr.setRequestHeader('Authorization',
376                                        'Bearer ' + event.accessToken);
377           this.sendRequest_(request);
378         } else {  // No valid token.
379           // Without abort status does not exists.
380           request.xhr.abort();
381           request.callback(request);
382         }
383         return false;
384       }, this);
385     },
386
387     /**
388      * Called when the ready-state of a XML http request changes.
389      * Calls the successCallback with the result or dispatches an ERROR event.
390      * @param {!CloudPrintRequest} request Request that was changed.
391      * @private
392      */
393     onReadyStateChange_: function(request) {
394       if (request.xhr.readyState == 4) {
395         if (request.xhr.status == 200) {
396           request.result = JSON.parse(request.xhr.responseText);
397           if (request.origin == print_preview.Destination.Origin.COOKIES &&
398               request.result['success']) {
399             this.xsrfToken_ = request.result['xsrf_token'];
400           }
401         }
402         request.status = request.xhr.status;
403         request.callback(request);
404       }
405     },
406
407     /**
408      * Called when the search request completes.
409      * @param {boolean} isRecent Whether the search request was for recent
410      *     destinations.
411      * @param {!CloudPrintRequest} request Request that has been completed.
412      * @private
413      */
414     onSearchDone_: function(isRecent, request) {
415       --this.outstandingCloudSearchRequestCount_;
416       if (request.xhr.status == 200 && request.result['success']) {
417         var printerListJson = request.result['printers'] || [];
418         var printerList = [];
419         printerListJson.forEach(function(printerJson) {
420           try {
421             printerList.push(
422                 cloudprint.CloudDestinationParser.parse(printerJson,
423                                                         request.origin));
424           } catch (err) {
425             console.error('Unable to parse cloud print destination: ' + err);
426           }
427         });
428         var searchDoneEvent =
429             new Event(CloudPrintInterface.EventType.SEARCH_DONE);
430         searchDoneEvent.printers = printerList;
431         searchDoneEvent.origin = request.origin;
432         searchDoneEvent.isRecent = isRecent;
433         searchDoneEvent.email = request.result['request']['user'];
434         this.dispatchEvent(searchDoneEvent);
435       } else {
436         var errorEvent = this.createErrorEvent_(
437             CloudPrintInterface.EventType.SEARCH_FAILED, request);
438         this.dispatchEvent(errorEvent);
439       }
440     },
441
442     /**
443      * Called when the submit request completes.
444      * @param {!CloudPrintRequest} request Request that has been completed.
445      * @private
446      */
447     onSubmitDone_: function(request) {
448       if (request.xhr.status == 200 && request.result['success']) {
449         var submitDoneEvent = new Event(
450             CloudPrintInterface.EventType.SUBMIT_DONE);
451         submitDoneEvent.jobId = request.result['job']['id'];
452         this.dispatchEvent(submitDoneEvent);
453       } else {
454         var errorEvent = this.createErrorEvent_(
455             CloudPrintInterface.EventType.SUBMIT_FAILED, request);
456         this.dispatchEvent(errorEvent);
457       }
458     },
459
460     /**
461      * Called when the printer request completes.
462      * @param {string} destinationId ID of the destination that was looked up.
463      * @param {!CloudPrintRequest} request Request that has been completed.
464      * @private
465      */
466     onPrinterDone_: function(destinationId, request) {
467       if (request.xhr.status == 200 && request.result['success']) {
468         var printerJson = request.result['printers'][0];
469         var printer;
470         try {
471           printer = cloudprint.CloudDestinationParser.parse(printerJson,
472                                                             request.origin);
473         } catch (err) {
474           console.error('Failed to parse cloud print destination: ' +
475               JSON.stringify(printerJson));
476           return;
477         }
478         var printerDoneEvent =
479             new Event(CloudPrintInterface.EventType.PRINTER_DONE);
480         printerDoneEvent.printer = printer;
481         this.dispatchEvent(printerDoneEvent);
482       } else {
483         var errorEvent = this.createErrorEvent_(
484             CloudPrintInterface.EventType.PRINTER_FAILED, request);
485         errorEvent.destinationId = destinationId;
486         errorEvent.destinationOrigin = request.origin;
487         this.dispatchEvent(errorEvent, request.origin);
488       }
489     },
490
491     /**
492      * Called when the update printer TOS acceptance request completes.
493      * @param {!CloudPrintRequest} request Request that has been completed.
494      * @private
495      */
496     onUpdatePrinterTosAcceptanceDone_: function(request) {
497       if (request.xhr.status == 200 && request.result['success']) {
498         // Do nothing.
499       } else {
500         var errorEvent = this.createErrorEvent_(
501             CloudPrintInterface.EventType.SUBMIT_FAILED, request);
502         this.dispatchEvent(errorEvent);
503       }
504     }
505   };
506
507   /**
508    * Data structure that holds data for Cloud Print requests.
509    * @param {!XMLHttpRequest} xhr Partially prepared http request.
510    * @param {string} body Data to send with POST requests.
511    * @param {!print_preview.Destination.Origin} origin Origin for destination.
512    * @param {function(!CloudPrintRequest)} callback Callback to invoke when
513    *     request completes.
514    * @constructor
515    */
516   function CloudPrintRequest(xhr, body, origin, callback) {
517     /**
518      * Partially prepared http request.
519      * @type {!XMLHttpRequest}
520      */
521     this.xhr = xhr;
522
523     /**
524      * Data to send with POST requests.
525      * @type {string}
526      */
527     this.body = body;
528
529     /**
530      * Origin for destination.
531      * @type {!print_preview.Destination.Origin}
532      */
533     this.origin = origin;
534
535     /**
536      * Callback to invoke when request completes.
537      * @type {function(!CloudPrintRequest)}
538      */
539     this.callback = callback;
540
541     /**
542      * Result for requests.
543      * @type {Object} JSON response.
544      */
545     this.result = null;
546   };
547
548   /**
549    * Data structure that represents an HTTP parameter.
550    * @param {string} name Name of the parameter.
551    * @param {string} value Value of the parameter.
552    * @constructor
553    */
554   function HttpParam(name, value) {
555     /**
556      * Name of the parameter.
557      * @type {string}
558      */
559     this.name = name;
560
561     /**
562      * Name of the value.
563      * @type {string}
564      */
565     this.value = value;
566   };
567
568   // Export
569   return {
570     CloudPrintInterface: CloudPrintInterface
571   };
572 });