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 5cr.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 * @param {!print_preview.UserInfo} userInfo User information repository. 16 * @param {boolean} isInAppKioskMode Whether the print preview is in App 17 * Kiosk mode. 18 * @constructor 19 * @extends {cr.EventTarget} 20 */ 21 function CloudPrintInterface( 22 baseUrl, nativeLayer, userInfo, isInAppKioskMode) { 23 /** 24 * The base URL of the Google Cloud Print API. 25 * @type {string} 26 * @private 27 */ 28 this.baseUrl_ = baseUrl; 29 30 /** 31 * Used to get Auth2 tokens. 32 * @type {!print_preview.NativeLayer} 33 * @private 34 */ 35 this.nativeLayer_ = nativeLayer; 36 37 /** 38 * User information repository. 39 * @type {!print_preview.UserInfo} 40 * @private 41 */ 42 this.userInfo_ = userInfo; 43 44 /** 45 * Whether Print Preview is in App Kiosk mode, basically, use only printers 46 * available for the device. 47 * @type {boolean} 48 * @private 49 */ 50 this.isInAppKioskMode_ = isInAppKioskMode; 51 52 /** 53 * Currently logged in users (identified by email) mapped to the Google 54 * session index. 55 * @type {!Object.<string, number>} 56 * @private 57 */ 58 this.userSessionIndex_ = {}; 59 60 /** 61 * Stores last received XSRF tokens for each user account. Sent as 62 * a parameter with every request. 63 * @type {!Object.<string, string>} 64 * @private 65 */ 66 this.xsrfTokens_ = {}; 67 68 /** 69 * Pending requests delayed until we get access token. 70 * @type {!Array.<!CloudPrintRequest>} 71 * @private 72 */ 73 this.requestQueue_ = []; 74 75 /** 76 * Outstanding cloud destination search requests. 77 * @type {!Array.<!CloudPrintRequest>} 78 * @private 79 */ 80 this.outstandingCloudSearchRequests_ = []; 81 82 /** 83 * Event tracker used to keep track of native layer events. 84 * @type {!EventTracker} 85 * @private 86 */ 87 this.tracker_ = new EventTracker(); 88 89 this.addEventListeners_(); 90 }; 91 92 /** 93 * Event types dispatched by the interface. 94 * @enum {string} 95 */ 96 CloudPrintInterface.EventType = { 97 INVITES_DONE: 'cloudprint.CloudPrintInterface.INVITES_DONE', 98 INVITES_FAILED: 'cloudprint.CloudPrintInterface.INVITES_FAILED', 99 PRINTER_DONE: 'cloudprint.CloudPrintInterface.PRINTER_DONE', 100 PRINTER_FAILED: 'cloudprint.CloudPrintInterface.PRINTER_FAILED', 101 PROCESS_INVITE_DONE: 'cloudprint.CloudPrintInterface.PROCESS_INVITE_DONE', 102 PROCESS_INVITE_FAILED: 103 'cloudprint.CloudPrintInterface.PROCESS_INVITE_FAILED', 104 SEARCH_DONE: 'cloudprint.CloudPrintInterface.SEARCH_DONE', 105 SEARCH_FAILED: 'cloudprint.CloudPrintInterface.SEARCH_FAILED', 106 SUBMIT_DONE: 'cloudprint.CloudPrintInterface.SUBMIT_DONE', 107 SUBMIT_FAILED: 'cloudprint.CloudPrintInterface.SUBMIT_FAILED', 108 UPDATE_PRINTER_TOS_ACCEPTANCE_FAILED: 109 'cloudprint.CloudPrintInterface.UPDATE_PRINTER_TOS_ACCEPTANCE_FAILED' 110 }; 111 112 /** 113 * Content type header value for a URL encoded HTTP request. 114 * @type {string} 115 * @const 116 * @private 117 */ 118 CloudPrintInterface.URL_ENCODED_CONTENT_TYPE_ = 119 'application/x-www-form-urlencoded'; 120 121 /** 122 * Multi-part POST request boundary used in communication with Google 123 * Cloud Print. 124 * @type {string} 125 * @const 126 * @private 127 */ 128 CloudPrintInterface.MULTIPART_BOUNDARY_ = 129 '----CloudPrintFormBoundaryjc9wuprokl8i'; 130 131 /** 132 * Content type header value for a multipart HTTP request. 133 * @type {string} 134 * @const 135 * @private 136 */ 137 CloudPrintInterface.MULTIPART_CONTENT_TYPE_ = 138 'multipart/form-data; boundary=' + 139 CloudPrintInterface.MULTIPART_BOUNDARY_; 140 141 /** 142 * Regex that extracts Chrome's version from the user-agent string. 143 * @type {!RegExp} 144 * @const 145 * @private 146 */ 147 CloudPrintInterface.VERSION_REGEXP_ = /.*Chrome\/([\d\.]+)/i; 148 149 /** 150 * Enumeration of JSON response fields from Google Cloud Print API. 151 * @enum {string} 152 * @private 153 */ 154 CloudPrintInterface.JsonFields_ = { 155 PRINTER: 'printer' 156 }; 157 158 /** 159 * Could Print origins used to search printers. 160 * @type {!Array.<!print_preview.Destination.Origin>} 161 * @const 162 * @private 163 */ 164 CloudPrintInterface.CLOUD_ORIGINS_ = [ 165 print_preview.Destination.Origin.COOKIES, 166 print_preview.Destination.Origin.DEVICE 167 // TODO(vitalybuka): Enable when implemented. 168 // ready print_preview.Destination.Origin.PROFILE 169 ]; 170 171 CloudPrintInterface.prototype = { 172 __proto__: cr.EventTarget.prototype, 173 174 /** @return {string} Base URL of the Google Cloud Print service. */ 175 get baseUrl() { 176 return this.baseUrl_; 177 }, 178 179 /** 180 * @return {boolean} Whether a search for cloud destinations is in progress. 181 */ 182 get isCloudDestinationSearchInProgress() { 183 return this.outstandingCloudSearchRequests_.length > 0; 184 }, 185 186 /** 187 * Sends Google Cloud Print search API request. 188 * @param {string=} opt_account Account the search is sent for. When 189 * omitted, the search is done on behalf of the primary user. 190 * @param {print_preview.Destination.Origin=} opt_origin When specified, 191 * searches destinations for {@code opt_origin} only, otherwise starts 192 * searches for all origins. 193 */ 194 search: function(opt_account, opt_origin) { 195 var account = opt_account || ''; 196 var origins = 197 opt_origin && [opt_origin] || CloudPrintInterface.CLOUD_ORIGINS_; 198 if (this.isInAppKioskMode_) { 199 origins = origins.filter(function(origin) { 200 return origin != print_preview.Destination.Origin.COOKIES; 201 }); 202 } 203 this.abortSearchRequests_(origins); 204 this.search_(true, account, origins); 205 this.search_(false, account, origins); 206 }, 207 208 /** 209 * Sends Google Cloud Print search API requests. 210 * @param {boolean} isRecent Whether to search for only recently used 211 * printers. 212 * @param {string} account Account the search is sent for. It matters for 213 * COOKIES origin only, and can be empty (sent on behalf of the primary 214 * user in this case). 215 * @param {!Array.<!print_preview.Destination.Origin>} origins Origins to 216 * search printers for. 217 * @private 218 */ 219 search_: function(isRecent, account, origins) { 220 var params = [ 221 new HttpParam('connection_status', 'ALL'), 222 new HttpParam('client', 'chrome'), 223 new HttpParam('use_cdd', 'true') 224 ]; 225 if (isRecent) { 226 params.push(new HttpParam('q', '^recent')); 227 } 228 origins.forEach(function(origin) { 229 var cpRequest = this.buildRequest_( 230 'GET', 231 'search', 232 params, 233 origin, 234 account, 235 this.onSearchDone_.bind(this, isRecent)); 236 this.outstandingCloudSearchRequests_.push(cpRequest); 237 this.sendOrQueueRequest_(cpRequest); 238 }, this); 239 }, 240 241 /** 242 * Sends Google Cloud Print printer sharing invitations API requests. 243 * @param {string} account Account the request is sent for. 244 */ 245 invites: function(account) { 246 var params = [ 247 new HttpParam('client', 'chrome'), 248 ]; 249 this.sendOrQueueRequest_(this.buildRequest_( 250 'GET', 251 'invites', 252 params, 253 print_preview.Destination.Origin.COOKIES, 254 account, 255 this.onInvitesDone_.bind(this))); 256 }, 257 258 /** 259 * Accepts or rejects printer sharing invitation. 260 * @param {!print_preview.Invitation} invitation Invitation to process. 261 * @param {boolean} accept Whether to accept this invitation. 262 */ 263 processInvite: function(invitation, accept) { 264 var params = [ 265 new HttpParam('printerid', invitation.destination.id), 266 new HttpParam('email', invitation.scopeId), 267 new HttpParam('accept', accept), 268 new HttpParam('use_cdd', true), 269 ]; 270 this.sendOrQueueRequest_(this.buildRequest_( 271 'POST', 272 'processinvite', 273 params, 274 invitation.destination.origin, 275 invitation.destination.account, 276 this.onProcessInviteDone_.bind(this, invitation, accept))); 277 }, 278 279 /** 280 * Sends a Google Cloud Print submit API request. 281 * @param {!print_preview.Destination} destination Cloud destination to 282 * print to. 283 * @param {!print_preview.PrintTicketStore} printTicketStore Contains the 284 * print ticket to print. 285 * @param {!print_preview.DocumentInfo} documentInfo Document data model. 286 * @param {string} data Base64 encoded data of the document. 287 */ 288 submit: function(destination, printTicketStore, documentInfo, data) { 289 var result = 290 CloudPrintInterface.VERSION_REGEXP_.exec(navigator.userAgent); 291 var chromeVersion = 'unknown'; 292 if (result && result.length == 2) { 293 chromeVersion = result[1]; 294 } 295 var params = [ 296 new HttpParam('printerid', destination.id), 297 new HttpParam('contentType', 'dataUrl'), 298 new HttpParam('title', documentInfo.title), 299 new HttpParam('ticket', 300 printTicketStore.createPrintTicket(destination)), 301 new HttpParam('content', 'data:application/pdf;base64,' + data), 302 new HttpParam('tag', 303 '__google__chrome_version=' + chromeVersion), 304 new HttpParam('tag', '__google__os=' + navigator.platform) 305 ]; 306 var cpRequest = this.buildRequest_( 307 'POST', 308 'submit', 309 params, 310 destination.origin, 311 destination.account, 312 this.onSubmitDone_.bind(this)); 313 this.sendOrQueueRequest_(cpRequest); 314 }, 315 316 /** 317 * Sends a Google Cloud Print printer API request. 318 * @param {string} printerId ID of the printer to lookup. 319 * @param {!print_preview.Destination.Origin} origin Origin of the printer. 320 * @param {string=} account Account this printer is registered for. When 321 * provided for COOKIES {@code origin}, and users sessions are still not 322 * known, will be checked against the response (both success and failure 323 * to get printer) and, if the active user account is not the one 324 * requested, {@code account} is activated and printer request reissued. 325 */ 326 printer: function(printerId, origin, account) { 327 var params = [ 328 new HttpParam('printerid', printerId), 329 new HttpParam('use_cdd', 'true'), 330 new HttpParam('printer_connection_status', 'true') 331 ]; 332 this.sendOrQueueRequest_(this.buildRequest_( 333 'GET', 334 'printer', 335 params, 336 origin, 337 account, 338 this.onPrinterDone_.bind(this, printerId))); 339 }, 340 341 /** 342 * Sends a Google Cloud Print update API request to accept (or reject) the 343 * terms-of-service of the given printer. 344 * @param {!print_preview.Destination} destination Destination to accept ToS 345 * for. 346 * @param {boolean} isAccepted Whether the user accepted ToS or not. 347 */ 348 updatePrinterTosAcceptance: function(destination, isAccepted) { 349 var params = [ 350 new HttpParam('printerid', destination.id), 351 new HttpParam('is_tos_accepted', isAccepted) 352 ]; 353 this.sendOrQueueRequest_(this.buildRequest_( 354 'POST', 355 'update', 356 params, 357 destination.origin, 358 destination.account, 359 this.onUpdatePrinterTosAcceptanceDone_.bind(this))); 360 }, 361 362 /** 363 * Adds event listeners to relevant events. 364 * @private 365 */ 366 addEventListeners_: function() { 367 this.tracker_.add( 368 this.nativeLayer_, 369 print_preview.NativeLayer.EventType.ACCESS_TOKEN_READY, 370 this.onAccessTokenReady_.bind(this)); 371 }, 372 373 /** 374 * Builds request to the Google Cloud Print API. 375 * @param {string} method HTTP method of the request. 376 * @param {string} action Google Cloud Print action to perform. 377 * @param {Array.<!HttpParam>} params HTTP parameters to include in the 378 * request. 379 * @param {!print_preview.Destination.Origin} origin Origin for destination. 380 * @param {?string} account Account the request is sent for. Can be 381 * {@code null} or empty string if the request is not cookie bound or 382 * is sent on behalf of the primary user. 383 * @param {function(number, Object, !print_preview.Destination.Origin)} 384 * callback Callback to invoke when request completes. 385 * @return {!CloudPrintRequest} Partially prepared request. 386 * @private 387 */ 388 buildRequest_: function(method, action, params, origin, account, callback) { 389 var url = this.baseUrl_ + '/' + action + '?xsrf='; 390 if (origin == print_preview.Destination.Origin.COOKIES) { 391 var xsrfToken = this.xsrfTokens_[account]; 392 if (!xsrfToken) { 393 // TODO(rltoscano): Should throw an error if not a read-only action or 394 // issue an xsrf token request. 395 } else { 396 url = url + xsrfToken; 397 } 398 if (account) { 399 var index = this.userSessionIndex_[account] || 0; 400 if (index > 0) { 401 url += '&user=' + index; 402 } 403 } 404 } 405 var body = null; 406 if (params) { 407 if (method == 'GET') { 408 url = params.reduce(function(partialUrl, param) { 409 return partialUrl + '&' + param.name + '=' + 410 encodeURIComponent(param.value); 411 }, url); 412 } else if (method == 'POST') { 413 body = params.reduce(function(partialBody, param) { 414 return partialBody + 'Content-Disposition: form-data; name=\"' + 415 param.name + '\"\r\n\r\n' + param.value + '\r\n--' + 416 CloudPrintInterface.MULTIPART_BOUNDARY_ + '\r\n'; 417 }, '--' + CloudPrintInterface.MULTIPART_BOUNDARY_ + '\r\n'); 418 } 419 } 420 421 var headers = {}; 422 headers['X-CloudPrint-Proxy'] = 'ChromePrintPreview'; 423 if (method == 'GET') { 424 headers['Content-Type'] = CloudPrintInterface.URL_ENCODED_CONTENT_TYPE_; 425 } else if (method == 'POST') { 426 headers['Content-Type'] = CloudPrintInterface.MULTIPART_CONTENT_TYPE_; 427 } 428 429 var xhr = new XMLHttpRequest(); 430 xhr.open(method, url, true); 431 xhr.withCredentials = 432 (origin == print_preview.Destination.Origin.COOKIES); 433 for (var header in headers) { 434 xhr.setRequestHeader(header, headers[header]); 435 } 436 437 return new CloudPrintRequest(xhr, body, origin, account, callback); 438 }, 439 440 /** 441 * Sends a request to the Google Cloud Print API or queues if it needs to 442 * wait OAuth2 access token. 443 * @param {!CloudPrintRequest} request Request to send or queue. 444 * @private 445 */ 446 sendOrQueueRequest_: function(request) { 447 if (request.origin == print_preview.Destination.Origin.COOKIES) { 448 return this.sendRequest_(request); 449 } else { 450 this.requestQueue_.push(request); 451 this.nativeLayer_.startGetAccessToken(request.origin); 452 } 453 }, 454 455 /** 456 * Sends a request to the Google Cloud Print API. 457 * @param {!CloudPrintRequest} request Request to send. 458 * @private 459 */ 460 sendRequest_: function(request) { 461 request.xhr.onreadystatechange = 462 this.onReadyStateChange_.bind(this, request); 463 request.xhr.send(request.body); 464 }, 465 466 /** 467 * Creates a Google Cloud Print interface error that is ready to dispatch. 468 * @param {!CloudPrintInterface.EventType} type Type of the error. 469 * @param {!CloudPrintRequest} request Request that has been completed. 470 * @return {!Event} Google Cloud Print interface error event. 471 * @private 472 */ 473 createErrorEvent_: function(type, request) { 474 var errorEvent = new Event(type); 475 errorEvent.status = request.xhr.status; 476 if (request.xhr.status == 200) { 477 errorEvent.errorCode = request.result['errorCode']; 478 errorEvent.message = request.result['message']; 479 } else { 480 errorEvent.errorCode = 0; 481 errorEvent.message = ''; 482 } 483 errorEvent.origin = request.origin; 484 return errorEvent; 485 }, 486 487 /** 488 * Updates user info and session index from the {@code request} response. 489 * @param {!CloudPrintRequest} request Request to extract user info from. 490 * @private 491 */ 492 setUsers_: function(request) { 493 if (request.origin == print_preview.Destination.Origin.COOKIES) { 494 var users = request.result['request']['users'] || []; 495 this.userSessionIndex_ = {}; 496 for (var i = 0; i < users.length; i++) { 497 this.userSessionIndex_[users[i]] = i; 498 } 499 this.userInfo_.setUsers(request.result['request']['user'], users); 500 } 501 }, 502 503 /** 504 * Terminates search requests for requested {@code origins}. 505 * @param {!Array.<print_preview.Destination.Origin>} origins Origins 506 * to terminate search requests for. 507 * @private 508 */ 509 abortSearchRequests_: function(origins) { 510 this.outstandingCloudSearchRequests_ = 511 this.outstandingCloudSearchRequests_.filter(function(request) { 512 if (origins.indexOf(request.origin) >= 0) { 513 request.xhr.abort(); 514 return false; 515 } 516 return true; 517 }); 518 }, 519 520 /** 521 * Called when a native layer receives access token. 522 * @param {Event} event Contains the authentication type and access token. 523 * @private 524 */ 525 onAccessTokenReady_: function(event) { 526 // TODO(vitalybuka): remove when other Origins implemented. 527 assert(event.authType == print_preview.Destination.Origin.DEVICE); 528 this.requestQueue_ = this.requestQueue_.filter(function(request) { 529 assert(request.origin == print_preview.Destination.Origin.DEVICE); 530 if (request.origin != event.authType) { 531 return true; 532 } 533 if (event.accessToken) { 534 request.xhr.setRequestHeader('Authorization', 535 'Bearer ' + event.accessToken); 536 this.sendRequest_(request); 537 } else { // No valid token. 538 // Without abort status does not exist. 539 request.xhr.abort(); 540 request.callback(request); 541 } 542 return false; 543 }, this); 544 }, 545 546 /** 547 * Called when the ready-state of a XML http request changes. 548 * Calls the successCallback with the result or dispatches an ERROR event. 549 * @param {!CloudPrintRequest} request Request that was changed. 550 * @private 551 */ 552 onReadyStateChange_: function(request) { 553 if (request.xhr.readyState == 4) { 554 if (request.xhr.status == 200) { 555 request.result = JSON.parse(request.xhr.responseText); 556 if (request.origin == print_preview.Destination.Origin.COOKIES && 557 request.result['success']) { 558 this.xsrfTokens_[request.result['request']['user']] = 559 request.result['xsrf_token']; 560 } 561 } 562 request.status = request.xhr.status; 563 request.callback(request); 564 } 565 }, 566 567 /** 568 * Called when the search request completes. 569 * @param {boolean} isRecent Whether the search request was for recent 570 * destinations. 571 * @param {!CloudPrintRequest} request Request that has been completed. 572 * @private 573 */ 574 onSearchDone_: function(isRecent, request) { 575 var lastRequestForThisOrigin = true; 576 this.outstandingCloudSearchRequests_ = 577 this.outstandingCloudSearchRequests_.filter(function(item) { 578 if (item != request && item.origin == request.origin) { 579 lastRequestForThisOrigin = false; 580 } 581 return item != request; 582 }); 583 var activeUser = ''; 584 if (request.origin == print_preview.Destination.Origin.COOKIES) { 585 activeUser = 586 request.result && 587 request.result['request'] && 588 request.result['request']['user']; 589 } 590 var event = null; 591 if (request.xhr.status == 200 && request.result['success']) { 592 // Extract printers. 593 var printerListJson = request.result['printers'] || []; 594 var printerList = []; 595 printerListJson.forEach(function(printerJson) { 596 try { 597 printerList.push(cloudprint.CloudDestinationParser.parse( 598 printerJson, request.origin, activeUser)); 599 } catch (err) { 600 console.error('Unable to parse cloud print destination: ' + err); 601 } 602 }); 603 // Extract and store users. 604 this.setUsers_(request); 605 // Dispatch SEARCH_DONE event. 606 event = new Event(CloudPrintInterface.EventType.SEARCH_DONE); 607 event.origin = request.origin; 608 event.printers = printerList; 609 event.isRecent = isRecent; 610 } else { 611 event = this.createErrorEvent_( 612 CloudPrintInterface.EventType.SEARCH_FAILED, 613 request); 614 } 615 event.user = activeUser; 616 event.searchDone = lastRequestForThisOrigin; 617 this.dispatchEvent(event); 618 }, 619 620 /** 621 * Called when invitations search request completes. 622 * @param {!CloudPrintRequest} request Request that has been completed. 623 * @private 624 */ 625 onInvitesDone_: function(request) { 626 var event = null; 627 var activeUser = 628 (request.result && 629 request.result['request'] && 630 request.result['request']['user']) || ''; 631 if (request.xhr.status == 200 && request.result['success']) { 632 // Extract invitations. 633 var invitationListJson = request.result['invites'] || []; 634 var invitationList = []; 635 invitationListJson.forEach(function(invitationJson) { 636 try { 637 invitationList.push(cloudprint.InvitationParser.parse( 638 invitationJson, activeUser)); 639 } catch (e) { 640 console.error('Unable to parse invitation: ' + e); 641 } 642 }); 643 // Dispatch INVITES_DONE event. 644 event = new Event(CloudPrintInterface.EventType.INVITES_DONE); 645 event.invitations = invitationList; 646 } else { 647 event = this.createErrorEvent_( 648 CloudPrintInterface.EventType.INVITES_FAILED, request); 649 } 650 event.user = activeUser; 651 this.dispatchEvent(event); 652 }, 653 654 /** 655 * Called when invitation processing request completes. 656 * @param {!print_preview.Invitation} invitation Processed invitation. 657 * @param {boolean} accept Whether this invitation was accepted or rejected. 658 * @param {!CloudPrintRequest} request Request that has been completed. 659 * @private 660 */ 661 onProcessInviteDone_: function(invitation, accept, request) { 662 var event = null; 663 var activeUser = 664 (request.result && 665 request.result['request'] && 666 request.result['request']['user']) || ''; 667 if (request.xhr.status == 200 && request.result['success']) { 668 event = new Event(CloudPrintInterface.EventType.PROCESS_INVITE_DONE); 669 if (accept) { 670 try { 671 event.printer = cloudprint.CloudDestinationParser.parse( 672 request.result['printer'], request.origin, activeUser); 673 } catch (e) { 674 console.error('Failed to parse cloud print destination: ' + e); 675 } 676 } 677 } else { 678 event = this.createErrorEvent_( 679 CloudPrintInterface.EventType.PROCESS_INVITE_FAILED, request); 680 } 681 event.invitation = invitation; 682 event.accept = accept; 683 event.user = activeUser; 684 this.dispatchEvent(event); 685 }, 686 687 /** 688 * Called when the submit request completes. 689 * @param {!CloudPrintRequest} request Request that has been completed. 690 * @private 691 */ 692 onSubmitDone_: function(request) { 693 if (request.xhr.status == 200 && request.result['success']) { 694 var submitDoneEvent = new Event( 695 CloudPrintInterface.EventType.SUBMIT_DONE); 696 submitDoneEvent.jobId = request.result['job']['id']; 697 this.dispatchEvent(submitDoneEvent); 698 } else { 699 var errorEvent = this.createErrorEvent_( 700 CloudPrintInterface.EventType.SUBMIT_FAILED, request); 701 this.dispatchEvent(errorEvent); 702 } 703 }, 704 705 /** 706 * Called when the printer request completes. 707 * @param {string} destinationId ID of the destination that was looked up. 708 * @param {!CloudPrintRequest} request Request that has been completed. 709 * @private 710 */ 711 onPrinterDone_: function(destinationId, request) { 712 // Special handling of the first printer request. It does not matter at 713 // this point, whether printer was found or not. 714 if (request.origin == print_preview.Destination.Origin.COOKIES && 715 request.result && 716 request.account && 717 request.result['request']['user'] && 718 request.result['request']['users'] && 719 request.account != request.result['request']['user']) { 720 this.setUsers_(request); 721 // In case the user account is known, but not the primary one, 722 // activate it. 723 if (this.userSessionIndex_[request.account] > 0) { 724 this.userInfo_.activeUser = request.account; 725 // Repeat the request for the newly activated account. 726 this.printer( 727 request.result['request']['params']['printerid'], 728 request.origin, 729 request.account); 730 // Stop processing this request, wait for the new response. 731 return; 732 } 733 } 734 // Process response. 735 if (request.xhr.status == 200 && request.result['success']) { 736 var activeUser = ''; 737 if (request.origin == print_preview.Destination.Origin.COOKIES) { 738 activeUser = request.result['request']['user']; 739 } 740 var printerJson = request.result['printers'][0]; 741 var printer; 742 try { 743 printer = cloudprint.CloudDestinationParser.parse( 744 printerJson, request.origin, activeUser); 745 } catch (err) { 746 console.error('Failed to parse cloud print destination: ' + 747 JSON.stringify(printerJson)); 748 return; 749 } 750 var printerDoneEvent = 751 new Event(CloudPrintInterface.EventType.PRINTER_DONE); 752 printerDoneEvent.printer = printer; 753 this.dispatchEvent(printerDoneEvent); 754 } else { 755 var errorEvent = this.createErrorEvent_( 756 CloudPrintInterface.EventType.PRINTER_FAILED, request); 757 errorEvent.destinationId = destinationId; 758 errorEvent.destinationOrigin = request.origin; 759 this.dispatchEvent(errorEvent, request.origin); 760 } 761 }, 762 763 /** 764 * Called when the update printer TOS acceptance request completes. 765 * @param {!CloudPrintRequest} request Request that has been completed. 766 * @private 767 */ 768 onUpdatePrinterTosAcceptanceDone_: function(request) { 769 if (request.xhr.status == 200 && request.result['success']) { 770 // Do nothing. 771 } else { 772 var errorEvent = this.createErrorEvent_( 773 CloudPrintInterface.EventType.SUBMIT_FAILED, request); 774 this.dispatchEvent(errorEvent); 775 } 776 } 777 }; 778 779 /** 780 * Data structure that holds data for Cloud Print requests. 781 * @param {!XMLHttpRequest} xhr Partially prepared http request. 782 * @param {string} body Data to send with POST requests. 783 * @param {!print_preview.Destination.Origin} origin Origin for destination. 784 * @param {?string} account Account the request is sent for. Can be 785 * {@code null} or empty string if the request is not cookie bound or 786 * is sent on behalf of the primary user. 787 * @param {function(!CloudPrintRequest)} callback Callback to invoke when 788 * request completes. 789 * @constructor 790 */ 791 function CloudPrintRequest(xhr, body, origin, account, callback) { 792 /** 793 * Partially prepared http request. 794 * @type {!XMLHttpRequest} 795 */ 796 this.xhr = xhr; 797 798 /** 799 * Data to send with POST requests. 800 * @type {string} 801 */ 802 this.body = body; 803 804 /** 805 * Origin for destination. 806 * @type {!print_preview.Destination.Origin} 807 */ 808 this.origin = origin; 809 810 /** 811 * User account this request is expected to be executed for. 812 * @type {?string} 813 */ 814 this.account = account; 815 816 /** 817 * Callback to invoke when request completes. 818 * @type {function(!CloudPrintRequest)} 819 */ 820 this.callback = callback; 821 822 /** 823 * Result for requests. 824 * @type {Object} JSON response. 825 */ 826 this.result = null; 827 }; 828 829 /** 830 * Data structure that represents an HTTP parameter. 831 * @param {string} name Name of the parameter. 832 * @param {string} value Value of the parameter. 833 * @constructor 834 */ 835 function HttpParam(name, value) { 836 /** 837 * Name of the parameter. 838 * @type {string} 839 */ 840 this.name = name; 841 842 /** 843 * Name of the value. 844 * @type {string} 845 */ 846 this.value = value; 847 }; 848 849 // Export 850 return { 851 CloudPrintInterface: CloudPrintInterface 852 }; 853}); 854