1<!-- 2Copyright 2014 Google Inc 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 https://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15--> 16 17<link rel="import" href="../polymer/polymer.html"> 18<link rel="import" href="../google-apis/google-js-api.html"> 19 20<script> 21 (function() { 22 23 /** 24 * Enum of attributes to be passed through to the login API call. 25 * @readonly 26 * @enum {string} 27 */ 28 var ProxyLoginAttributes = { 29 'appPackageName': 'apppackagename', 30 'clientId': 'clientid', 31 'cookiePolicy': 'cookiepolicy', 32 'hostedDomain': 'hostedDomain', 33 'openidPrompt': 'prompt', 34 'requestVisibleActions': 'requestvisibleactions' 35 }; 36 37 /** 38 * AuthEngine does all interactions with gapi.auth2 39 * 40 * It is tightly coupled with <google-signin-aware> element 41 * The elements configure AuthEngine. 42 * AuthEngine propagates all authentication events to all google-signin-aware elements 43 * 44 * API used: https://developers.google.com/identity/sign-in/web/reference 45 * 46 */ 47 var AuthEngine = { 48 49 /** 50 * oauth2 argument, set by google-signin-aware 51 */ 52 _clientId: null, 53 54 get clientId() { 55 return this._clientId; 56 }, 57 58 set clientId(val) { 59 if (this._clientId && val && val != this._clientId) { 60 throw new Error('clientId cannot change. Values do not match. New: ' + val + ' Old:' + this._clientId); 61 } 62 if (val && val != this._clientId) { 63 this._clientId = val; 64 this.initAuth2(); 65 } 66 }, 67 68 /** 69 * oauth2 argument, set by google-signin-aware 70 */ 71 _cookiePolicy: 'single_host_origin', 72 73 get cookiePolicy() { 74 return this._cookiePolicy; 75 }, 76 77 set cookiePolicy(val) { 78 if (val) { 79 this._cookiePolicy = val; 80 } 81 }, 82 83 /** 84 * oauth2 argument, set by google-signin-aware 85 */ 86 _appPackageName: '', 87 88 get appPackageName() { 89 return this._appPackageName; 90 }, 91 92 set appPackageName(val) { 93 if (this._appPackageName && val && val != this._appPackageName) { 94 throw new Error('appPackageName cannot change. Values do not match. New: ' + val + ' Old: ' + this._appPackageName); 95 } 96 if (val) { 97 this._appPackageName = val; 98 } 99 }, 100 101 /** 102 * oauth2 argument, set by google-signin-aware 103 */ 104 _requestVisibleActions: '', 105 106 get requestVisibleactions() { 107 return this._requestVisibleActions; 108 }, 109 110 set requestVisibleactions(val) { 111 if (this._requestVisibleActions && val && val != this._requestVisibleActions) { 112 throw new Error('requestVisibleactions cannot change. Values do not match. New: ' + val + ' Old: ' + this._requestVisibleActions); 113 } 114 if (val) 115 this._requestVisibleActions = val; 116 }, 117 118 /** 119 * oauth2 argument, set by google-signin-aware 120 */ 121 _hostedDomain: '', 122 123 get hostedDomain() { 124 return this._hostedDomain; 125 }, 126 127 set hostedDomain(val) { 128 if (this._hostedDomain && val && val != this._hostedDomain) { 129 throw new Error('hostedDomain cannot change. Values do not match. New: ' + val + ' Old: ' + this._hostedDomain); 130 } 131 if (val) 132 this._hostedDomain = val; 133 }, 134 135 /** 136 * oauth2 argument, set by google-signin-aware 137 */ 138 _openidPrompt: '', 139 140 get openidPrompt() { 141 return this._openidPrompt; 142 }, 143 144 set openidPrompt(val) { 145 if (typeof val !== 'string') { 146 throw new Error( 147 'openidPrompt must be a string. Received ' + typeof val); 148 } 149 if (val) { 150 var values = val.split(' '); 151 values = values.map(function(v) { 152 return v.trim(); 153 }); 154 values = values.filter(function(v) { 155 return v; 156 }); 157 var validValues = {none: 0, login: 0, consent: 0, select_account: 0}; 158 values.forEach(function(v) { 159 if (v == 'none' && values.length > 1) { 160 throw new Error( 161 'none cannot be combined with other openidPrompt values'); 162 } 163 if (!(v in validValues)) { 164 throw new Error( 165 'invalid openidPrompt value ' + v + 166 '. Valid values: ' + Object.keys(validValues).join(', ')); 167 } 168 }); 169 } 170 this._openidPrompt = val; 171 }, 172 173 /** Is offline access currently enabled in the google-signin-aware element? */ 174 _offline: false, 175 176 get offline() { 177 return this._offline; 178 }, 179 180 set offline(val) { 181 this._offline = val; 182 this.updateAdditionalAuth(); 183 }, 184 185 /** Should we force a re-prompt for offline access? */ 186 _offlineAlwaysPrompt: false, 187 188 get offlineAlwaysPrompt() { 189 return this._offlineAlwaysPrompt; 190 }, 191 192 set offlineAlwaysPrompt(val) { 193 this._offlineAlwaysPrompt = val; 194 this.updateAdditionalAuth(); 195 }, 196 197 /** Have we already gotten offline access from Google during this session? */ 198 offlineGranted: false, 199 200 /** <google-js-api> */ 201 _apiLoader: null, 202 203 /** an array of wanted scopes. oauth2 argument */ 204 _requestedScopeArray: [], 205 206 /** _requestedScopeArray as string */ 207 get requestedScopes() { 208 return this._requestedScopeArray.join(' '); 209 }, 210 211 /** Is auth library initalized? */ 212 _initialized: false, 213 214 /** Is user signed in? */ 215 _signedIn: false, 216 217 /** Currently granted scopes */ 218 _grantedScopeArray: [], 219 220 /** True if additional authorization is required */ 221 _needAdditionalAuth: true, 222 223 /** True if have google+ scopes */ 224 _hasPlusScopes: false, 225 226 /** 227 * array of <google-signin-aware> 228 * state changes are broadcast to them 229 */ 230 signinAwares: [], 231 232 init: function() { 233 this._apiLoader = document.createElement('google-js-api'); 234 this._apiLoader.addEventListener('js-api-load', this.loadAuth2.bind(this)); 235 if (Polymer.Element) { 236 document.body.appendChild(this._apiLoader); 237 } 238 }, 239 240 loadAuth2: function() { 241 gapi.load('auth2', this.initAuth2.bind(this)); 242 }, 243 244 initAuth2: function() { 245 if (!('gapi' in window) || !('auth2' in window.gapi) || !this.clientId) { 246 return; 247 } 248 var auth = gapi.auth2.init({ 249 'client_id': this.clientId, 250 'cookie_policy': this.cookiePolicy, 251 'scope': this.requestedScopes, 252 'hosted_domain': this.hostedDomain 253 }); 254 255 auth['currentUser'].listen(this.handleUserUpdate.bind(this)); 256 257 auth.then( 258 function onFulfilled() { 259 // Let the current user listener trigger the changes. 260 }, 261 function onRejected(error) { 262 console.error(error); 263 } 264 ); 265 }, 266 267 handleUserUpdate: function(newPrimaryUser) { 268 // update and broadcast currentUser 269 var isSignedIn = newPrimaryUser.isSignedIn(); 270 if (isSignedIn != this._signedIn) { 271 this._signedIn = isSignedIn; 272 for (var i=0; i<this.signinAwares.length; i++) { 273 this.signinAwares[i]._setSignedIn(isSignedIn); 274 } 275 } 276 // update and broadcast initialized property the first time the isSignedIn property is set. 277 if(!this._initialized) { 278 for (var i=0; i<this.signinAwares.length; i++) { 279 this.signinAwares[i]._setInitialized(true); 280 } 281 this._initialized = true; 282 } 283 284 285 // update granted scopes 286 this._grantedScopeArray = this.strToScopeArray( 287 newPrimaryUser.getGrantedScopes()); 288 // console.log(this._grantedScopeArray); 289 this.updateAdditionalAuth(); 290 291 var response = newPrimaryUser.getAuthResponse(); 292 for (var i=0; i<this.signinAwares.length; i++) { 293 this.signinAwares[i]._updateScopeStatus(response); 294 } 295 }, 296 297 setOfflineCode: function(code) { 298 for (var i=0; i<this.signinAwares.length; i++) { 299 this.signinAwares[i]._updateOfflineCode(code); 300 } 301 }, 302 303 /** convert scope string to scope array */ 304 strToScopeArray: function(str) { 305 if (!str) { 306 return []; 307 } 308 // remove extra spaces, then split 309 var scopes = str.replace(/\ +/g, ' ').trim().split(' '); 310 for (var i=0; i<scopes.length; i++) { 311 scopes[i] = scopes[i].toLowerCase(); 312 // Handle scopes that will be deprecated but are still returned with their old value 313 if (scopes[i] === 'https://www.googleapis.com/auth/userinfo.profile') { 314 scopes[i] = 'profile'; 315 } 316 if (scopes[i] === 'https://www.googleapis.com/auth/userinfo.email') { 317 scopes[i] = 'email'; 318 } 319 } 320 // return with duplicates filtered out 321 return scopes.filter( function(value, index, self) { 322 return self.indexOf(value) === index; 323 }); 324 }, 325 326 /** true if scopes have google+ scopes */ 327 isPlusScope: function(scope) { 328 return (scope.indexOf('/auth/games') > -1) 329 || (scope.indexOf('auth/plus.') > -1 && scope.indexOf('auth/plus.me') < 0); 330 }, 331 332 /** true if scopes have been granted */ 333 hasGrantedScopes: function(scopeStr) { 334 var scopes = this.strToScopeArray(scopeStr); 335 for (var i=0; i< scopes.length; i++) { 336 if (this._grantedScopeArray.indexOf(scopes[i]) === -1) 337 return false; 338 } 339 return true; 340 }, 341 342 /** request additional scopes */ 343 requestScopes: function(newScopeStr) { 344 var newScopes = this.strToScopeArray(newScopeStr); 345 var scopesUpdated = false; 346 for (var i=0; i<newScopes.length; i++) { 347 if (this._requestedScopeArray.indexOf(newScopes[i]) === -1) { 348 this._requestedScopeArray.push(newScopes[i]); 349 scopesUpdated = true; 350 } 351 } 352 if (scopesUpdated) { 353 this.updateAdditionalAuth(); 354 this.updatePlusScopes(); 355 } 356 }, 357 358 /** update status of _needAdditionalAuth */ 359 updateAdditionalAuth: function() { 360 var needMoreAuth = false; 361 if ((this.offlineAlwaysPrompt || this.offline ) && !this.offlineGranted) { 362 needMoreAuth = true; 363 } else { 364 for (var i=0; i<this._requestedScopeArray.length; i++) { 365 if (this._grantedScopeArray.indexOf(this._requestedScopeArray[i]) === -1) { 366 needMoreAuth = true; 367 break; 368 } 369 } 370 } 371 if (this._needAdditionalAuth != needMoreAuth) { 372 this._needAdditionalAuth = needMoreAuth; 373 // broadcast new value 374 for (var i=0; i<this.signinAwares.length; i++) { 375 this.signinAwares[i]._setNeedAdditionalAuth(needMoreAuth); 376 } 377 } 378 }, 379 380 updatePlusScopes: function() { 381 var hasPlusScopes = false; 382 for (var i = 0; i < this._requestedScopeArray.length; i++) { 383 if (this.isPlusScope(this._requestedScopeArray[i])) { 384 hasPlusScopes = true; 385 break; 386 } 387 } 388 if (this._hasPlusScopes != hasPlusScopes) { 389 this._hasPlusScopes = hasPlusScopes; 390 for (var i=0; i<this.signinAwares.length; i++) { 391 this.signinAwares[i]._setHasPlusScopes(hasPlusScopes); 392 } 393 } 394 }, 395 /** 396 * attached <google-signin-aware> 397 * @param {!GoogleSigninAwareElement} aware element to add 398 */ 399 attachSigninAware: function(aware) { 400 if (this.signinAwares.indexOf(aware) == -1) { 401 this.signinAwares.push(aware); 402 // Initialize aware properties 403 aware._setNeedAdditionalAuth(this._needAdditionalAuth); 404 aware._setInitialized(this._initialized); 405 aware._setSignedIn(this._signedIn); 406 aware._setHasPlusScopes(this._hasPlusScopes); 407 } else { 408 console.warn('signinAware attached more than once', aware); 409 } 410 }, 411 412 detachSigninAware: function(aware) { 413 var index = this.signinAwares.indexOf(aware); 414 if (index != -1) { 415 this.signinAwares.splice(index, 1); 416 } else { 417 console.warn('Trying to detach unattached signin-aware'); 418 } 419 }, 420 421 /** returns scopes not granted */ 422 getMissingScopes: function() { 423 return this._requestedScopeArray.filter( function(scope) { 424 return this._grantedScopeArray.indexOf(scope) === -1; 425 }.bind(this)).join(' '); 426 }, 427 428 assertAuthInitialized: function() { 429 if (!this.clientId) { 430 throw new Error("AuthEngine not initialized. clientId has not been configured."); 431 } 432 if (!('gapi' in window)) { 433 throw new Error("AuthEngine not initialized. gapi has not loaded."); 434 } 435 if (!('auth2' in window.gapi)) { 436 throw new Error("AuthEngine not initialized. auth2 not loaded."); 437 } 438 }, 439 440 /** pops up sign-in dialog */ 441 signIn: function() { 442 this.assertAuthInitialized(); 443 var params = { 444 'scope': this.getMissingScopes() 445 }; 446 447 // Proxy specific attributes through to the signIn options. 448 Object.keys(ProxyLoginAttributes).forEach(function(key) { 449 if (this[key] && this[key] !== '') { 450 params[ProxyLoginAttributes[key]] = this[key]; 451 } 452 }, this); 453 454 var promise; 455 var user = gapi.auth2.getAuthInstance()['currentUser'].get(); 456 if (!(this.offline || this.offlineAlwaysPrompt)) { 457 if (user.getGrantedScopes()) { 458 // additional auth, skip multiple account dialog 459 promise = user.grant(params); 460 } else { 461 // initial signin 462 promise = gapi.auth2.getAuthInstance().signIn(params); 463 } 464 } else { 465 params.redirect_uri = 'postmessage'; 466 if (this.offlineAlwaysPrompt) { 467 params.approval_prompt = 'force'; 468 } 469 470 // Despite being documented at https://goo.gl/tiO0Bk 471 // It doesn't seem like user.grantOfflineAccess() actually exists in 472 // the current version of the Google Sign-In JS client we're using 473 // through GoogleWebComponents. So in the offline case, we will not 474 // distinguish between a first auth and an additional one. 475 promise = gapi.auth2.getAuthInstance().grantOfflineAccess(params); 476 } 477 promise.then( 478 function onFulfilled(response) { 479 // If login was offline, response contains one string "code" 480 // Otherwise it contains the user object already 481 var newUser; 482 if (response.code) { 483 AuthEngine.offlineGranted = true; 484 newUser = gapi.auth2.getAuthInstance()['currentUser'].get(); 485 AuthEngine.setOfflineCode(response.code); 486 } else { 487 newUser = response; 488 } 489 490 var authResponse = newUser.getAuthResponse(); 491 // Let the current user listener trigger the changes. 492 }, 493 function onRejected(error) { 494 // Access denied is not an error, user hit cancel 495 if ("Access denied." !== error.reason) { 496 this.signinAwares.forEach(function(awareInstance) { 497 awareInstance.errorNotify(error); 498 }); 499 } 500 }.bind(this) 501 ); 502 }, 503 504 /** signs user out */ 505 signOut: function() { 506 this.assertAuthInitialized(); 507 gapi.auth2.getAuthInstance().signOut().then( 508 function onFulfilled() { 509 // Let the current user listener trigger the changes. 510 }, 511 function onRejected(error) { 512 console.error(error); 513 } 514 ); 515 } 516 }; 517 518 AuthEngine.init(); 519 520/** 521`google-signin-aware` is used to enable authentication in custom elements by 522interacting with a google-signin element that needs to be present somewhere 523on the page. 524 525The `scopes` attribute allows you to specify which scope permissions are required 526(e.g do you want to allow interaction with the Google Drive API). 527 528The `google-signin-aware-success` event is triggered when a user successfully 529authenticates. If either `offline` or `offlineAlwaysPrompt` is set to true, successful 530authentication will also trigger the `google-signin-offline-success`event. 531The `google-signin-aware-signed-out` event is triggered when a user explicitly 532signs out via the google-signin element. 533 534You can bind to `isAuthorized` property to monitor authorization state. 535##### Example 536 537 <google-signin-aware scopes="https://www.googleapis.com/auth/drive"></google-signin-aware> 538 539 540##### Example with offline 541 <template id="awareness" is="dom-bind"> 542 <google-signin-aware 543 scopes="https://www.googleapis.com/auth/drive" 544 offline 545 on-google-signin-aware-success="handleSignin" 546 on-google-signin-offline-success="handleOffline"></google-signin-aware> 547 <\/template> 548 <script> 549 var aware = document.querySelector('#awareness'); 550 aware.handleSignin = function(response) { 551 var user = gapi.auth2.getAuthInstance()['currentUser'].get(); 552 console.log('User name: ' + user.getBasicProfile().getName()); 553 }; 554 aware.handleOffline = function(response) { 555 console.log('Offline code received: ' + response.detail.code); 556 // Here you would POST response.detail.code to your webserver, which can 557 // exchange the authorization code for an access token. More info at: 558 // https://developers.google.com/identity/protocols/OAuth2WebServer 559 }; 560 <\/script> 561*/ 562 Polymer({ 563 564 is: 'google-signin-aware', 565 566 /** 567 * Fired when this scope has been authorized 568 * @param {Object} result Authorization result. 569 * @event google-signin-aware-success 570 */ 571 572 /** 573 * Fired when an offline authorization is successful. 574 * @param {{code: string}} detail - 575 * code: The one-time authorization code from Google. 576 * Your application can exchange this for an `access_token` and `refresh_token` 577 * @event google-signin-offline-success 578 */ 579 580 /** 581 * Fired when this scope is not authorized 582 * @event google-signin-aware-signed-out 583 */ 584 585 /** 586 * Fired when there is an error during the signin flow. 587 * @param {Object} detail The error object returned from the OAuth 2 flow. 588 * @event google-signin-aware-error 589 */ 590 591 /** 592 * This block is needed so the previous @param is not assigned to the next property. 593 */ 594 595 properties: { 596 /** 597 * App package name for android over-the-air installs. 598 * See the relevant [docs](https://developers.google.com/+/web/signin/android-app-installs) 599 */ 600 appPackageName: { 601 type: String, 602 observer: '_appPackageNameChanged' 603 }, 604 605 /** 606 * a Google Developers clientId reference 607 */ 608 clientId: { 609 type: String, 610 observer: '_clientIdChanged' 611 }, 612 613 /** 614 * The cookie policy defines what URIs have access to the session cookie 615 * remembering the user's sign-in state. 616 * See the relevant [docs](https://developers.google.com/+/web/signin/reference#determining_a_value_for_cookie_policy) for more information. 617 * @default 'single_host_origin' 618 */ 619 cookiePolicy: { 620 type: String, 621 observer: '_cookiePolicyChanged' 622 }, 623 624 /** 625 * The app activity types you want to write on behalf of the user 626 * (e.g http://schemas.google.com/AddActivity) 627 * 628 */ 629 requestVisibleActions: { 630 type: String, 631 observer: '_requestVisibleActionsChanged' 632 }, 633 634 /** 635 * The Google Apps domain to which users must belong to sign in. 636 * See the relevant [docs](https://developers.google.com/identity/sign-in/web/reference) for more information. 637 */ 638 hostedDomain: { 639 type: String, 640 observer: '_hostedDomainChanged' 641 }, 642 643 /** 644 * Allows for offline `access_token` retrieval during the signin process. 645 * See also `offlineAlwaysPrompt`. You only need to set one of the two; if both 646 * are set, the behavior of `offlineAlwaysPrompt` will override `offline`. 647 */ 648 offline: { 649 type: Boolean, 650 value: false, 651 observer: '_offlineChanged' 652 }, 653 654 /** 655 * Works the same as `offline` with the addition that it will always 656 * force a re-prompt to the user, guaranteeing that you will get a 657 * refresh_token even if the user has already granted offline access to 658 * this application. You only need to set one of `offline` or 659 * `offlineAlwaysPrompt`, not both. 660 */ 661 offlineAlwaysPrompt: { 662 type: Boolean, 663 value: false, 664 observer: '_offlineAlwaysPromptChanged' 665 }, 666 667 /** 668 * The scopes to provide access to (e.g https://www.googleapis.com/auth/drive) 669 * and should be space-delimited. 670 */ 671 scopes: { 672 type: String, 673 value: 'profile', 674 observer: '_scopesChanged' 675 }, 676 677 /** 678 * Space-delimited, case-sensitive list of strings that 679 * specifies whether the the user is prompted for reauthentication 680 * and/or consent. The defined values are: 681 * none: do not display authentication or consent pages. 682 * This value is mutually exclusive with the rest. 683 * login: always prompt the user for reauthentication. 684 * consent: always show consent screen. 685 * select_account: always show account selection page. 686 * This enables a user who has multiple accounts to select amongst 687 * the multiple accounts that they might have current sessions for. 688 * For more information, see "prompt" parameter description in 689 * https://openid.net/specs/openid-connect-basic-1_0.html#RequestParameters 690 */ 691 openidPrompt: { 692 type: String, 693 value: '', 694 observer: '_openidPromptChanged' 695 }, 696 697 /** 698 * True when the auth library has been initialized, and signedIn property value is set from the first api response. 699 */ 700 initialized: { 701 type: Boolean, 702 notify: true, 703 readOnly: true 704 }, 705 706 /** 707 * True if user is signed in 708 */ 709 signedIn: { 710 type: Boolean, 711 notify: true, 712 readOnly: true 713 }, 714 715 /** 716 * True if authorizations for *this* element have been granted 717 */ 718 isAuthorized: { 719 type: Boolean, 720 notify: true, 721 readOnly: true, 722 value: false 723 }, 724 725 /** 726 * True if additional authorizations for *any* element are required 727 */ 728 needAdditionalAuth: { 729 type: Boolean, 730 notify: true, 731 readOnly: true 732 }, 733 734 /** 735 * True if *any* element has google+ scopes 736 */ 737 hasPlusScopes: { 738 type: Boolean, 739 value: false, 740 notify: true, 741 readOnly: true 742 } 743 }, 744 745 attached: function() { 746 AuthEngine.attachSigninAware(this); 747 }, 748 749 detached: function() { 750 AuthEngine.detachSigninAware(this); 751 }, 752 753 /** pops up the authorization dialog */ 754 signIn: function() { 755 AuthEngine.signIn(); 756 }, 757 758 /** signs user out */ 759 signOut: function() { 760 AuthEngine.signOut(); 761 }, 762 763 errorNotify: function(error) { 764 this.fire('google-signin-aware-error', error); 765 }, 766 767 _appPackageNameChanged: function(newName, oldName) { 768 AuthEngine.appPackageName = newName; 769 }, 770 771 _clientIdChanged: function(newId, oldId) { 772 AuthEngine.clientId = newId; 773 }, 774 775 _cookiePolicyChanged: function(newPolicy, oldPolicy) { 776 AuthEngine.cookiePolicy = newPolicy; 777 }, 778 779 _requestVisibleActionsChanged: function(newVal, oldVal) { 780 AuthEngine.requestVisibleActions = newVal; 781 }, 782 783 _hostedDomainChanged: function(newVal, oldVal) { 784 AuthEngine.hostedDomain = newVal; 785 }, 786 787 _offlineChanged: function(newVal, oldVal) { 788 AuthEngine.offline = newVal; 789 }, 790 791 _offlineAlwaysPromptChanged: function(newVal, oldVal) { 792 AuthEngine.offlineAlwaysPrompt = newVal; 793 }, 794 795 _scopesChanged: function(newVal, oldVal) { 796 AuthEngine.requestScopes(newVal); 797 this._updateScopeStatus(undefined); 798 }, 799 800 _openidPromptChanged: function(newVal, oldVal) { 801 AuthEngine.openidPrompt = newVal; 802 }, 803 804 _updateScopeStatus: function(user) { 805 var newAuthorized = this.signedIn && AuthEngine.hasGrantedScopes(this.scopes); 806 if (newAuthorized !== this.isAuthorized) { 807 this._setIsAuthorized(newAuthorized); 808 if (newAuthorized) { 809 this.fire('google-signin-aware-success', user); 810 } 811 else { 812 this.fire('google-signin-aware-signed-out', user); 813 } 814 } 815 }, 816 817 _updateOfflineCode: function(code) { 818 if (code) { 819 this.fire('google-signin-offline-success', {code: code}); 820 } 821 } 822 }); 823 })(); 824</script> 825