1/*! hellojs v1.14.1 | (c) 2012-2017 Andrew Dodson | MIT https://adodson.com/hello.js/LICENSE */ 2// ES5 Object.create 3if (!Object.create) { 4 5 // Shim, Object create 6 // A shim for Object.create(), it adds a prototype to a new object 7 Object.create = (function() { 8 9 function F() {} 10 11 return function(o) { 12 13 if (arguments.length != 1) { 14 throw new Error('Object.create implementation only accepts one parameter.'); 15 } 16 17 F.prototype = o; 18 return new F(); 19 }; 20 21 })(); 22 23} 24 25// ES5 Object.keys 26if (!Object.keys) { 27 Object.keys = function(o, k, r) { 28 r = []; 29 for (k in o) { 30 if (r.hasOwnProperty.call(o, k)) 31 r.push(k); 32 } 33 34 return r; 35 }; 36} 37 38// ES5 [].indexOf 39if (!Array.prototype.indexOf) { 40 Array.prototype.indexOf = function(s) { 41 42 for (var j = 0; j < this.length; j++) { 43 if (this[j] === s) { 44 return j; 45 } 46 } 47 48 return -1; 49 }; 50} 51 52// ES5 [].forEach 53if (!Array.prototype.forEach) { 54 Array.prototype.forEach = function(fun/*, thisArg*/) { 55 56 if (this === void 0 || this === null) { 57 throw new TypeError(); 58 } 59 60 var t = Object(this); 61 var len = t.length >>> 0; 62 if (typeof fun !== 'function') { 63 throw new TypeError(); 64 } 65 66 var thisArg = arguments.length >= 2 ? arguments[1] : void 0; 67 for (var i = 0; i < len; i++) { 68 if (i in t) { 69 fun.call(thisArg, t[i], i, t); 70 } 71 } 72 73 return this; 74 }; 75} 76
77// ES5 [].filter 78if (!Array.prototype.filter) { 79 Array.prototype.filter = function(fun, thisArg) { 80 81 var a = []; 82 this.forEach(function(val, i, t) { 83 if (fun.call(thisArg || void 0, val, i, t)) { 84 a.push(val); 85 } 86 }); 87 88 return a; 89 }; 90} 91 92// Production steps of ECMA-262, Edition 5, 15.4.4.19 93// Reference: http://es5.github.io/#x15.4.4.19 94if (!Array.prototype.map) { 95 96 Array.prototype.map = function(fun, thisArg) { 97 98 var a = []; 99 this.forEach(function(val, i, t) { 100 a.push(fun.call(thisArg || void 0, val, i, t)); 101 }); 102 103 return a; 104 }; 105} 106 107// ES5 isArray 108if (!Array.isArray) { 109 110 // Function Array.isArray 111 Array.isArray = function(o) { 112 return Object.prototype.toString.call(o) === '[object Array]'; 113 }; 114 115} 116 117// Test for location.assign 118if (typeof window === 'object' && typeof window.location === 'object' && !window.location.assign) { 119 120 window.location.assign = function(url) { 121 window.location = url; 122 }; 123 124} 125 126// Test for Function.bind 127if (!Function.prototype.bind) { 128 129 // MDN 130 // Polyfill IE8, does not support native Function.bind 131 Function.prototype.bind = function(b) { 132 133 if (typeof this !== 'function') { 134 throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); 135 } 136 137 function C() {} 138 139 var a = [].slice; 140 var f = a.call(arguments, 1); 141 var _this = this; 142 var D = function() { 143 return _this.apply(this instanceof C ? this : b || window, f.concat(a.call(arguments))); 144 }; 145 146 C.prototype = this.prototype; 147 D.prototype = new C(); 148 149 return D; 150 }; 151 152} 153 154/** 155 * @hello.js 156 * 157 * HelloJS is a client side Javascript SDK for making OAuth2 logins and subsequent REST calls. 158 * 159 * @author Andrew Dodson 160 * @website https://adodson.com/hello.js/ 161 * 162 * @copyright Andrew Dodson, 2012 - 2015 163 * @license MIT: You are free to use and modify this code for any use, on the condition that this copyright notice remains. 164 */ 165 166var hello = function(name) { 167 return hello.use(name); 168}; 169 170hello.utils = { 171 172 // Extend the first object with the properties and methods of the second 173 extend: function(r /*, a[, b[, ...]] */) { 174 175 // Get the arguments as an array but ommit the initial item 176 Array.prototype.slice.call(arguments, 1).forEach(function(a) { 177 if (Array.isArray(r) && Array.isArray(a)) { 178 Array.prototype.push.apply(r, a); 179 } 180 else if (r && (r instanceof Object || typeof r === 'object') && a && (a instanceof Object || typeof a === 'object') && r !== a) { 181 for (var x in a) { 182 r[x] = hello.utils.extend(r[x], a[x]); 183 } 184 } 185 else { 186 187 if (Array.isArray(a)) { 188 // Clone it 189 a = a.slice(0); 190 } 191 192 r = a; 193 } 194 }); 195 196 return r; 197 } 198}; 199 200// Core library 201hello.utils.extend(hello, { 202 203 settings: { 204 205 // OAuth2 authentication defaults 206 redirect_uri: window.location.href.split('#')[0], 207 response_type: 'token', 208 display: 'popup', 209 state: '', 210 211 // OAuth1 shim 212 // The path to the OAuth1 server for signing user requests 213 // Want to recreate your own? Checkout https://github.com/MrSwitch/node-oauth-shim 214 oauth_proxy: 'https://auth-server.herokuapp.com/proxy', 215 216 // API timeout in milliseconds 217 timeout: 20000, 218 219 // Popup Options 220 popup: { 221 resizable: 1, 222 scrollbars: 1, 223 width: 500, 224 height: 550 225 }, 226 227 // Default scope 228 // Many services require atleast a profile scope, 229 // HelloJS automatially includes the value of provider.scope_map.basic 230 // If that's not required it can be removed via hello.settings.scope.length = 0; 231 scope: ['basic'], 232 233 // Scope Maps 234 // This is the default module scope, these are the defaults which each service is mapped too. 235 // By including them here it prevents the scope from being applied accidentally 236 scope_map: { 237 basic: '' 238 }, 239 240 // Default service / network 241 default_service: null, 242 243 // Force authentication 244 // When hello.login is fired. 245 // (null): ignore current session expiry and continue with login 246 // (true): ignore current session expiry and continue with login, ask for user to reauthenticate 247 // (false): if the current session looks good for the request scopes return the current session. 248 force: null, 249 250 // Page URL 251 // When 'display=page' this property defines where the users page should end up after redirect_uri 252 // Ths could be problematic if the redirect_uri is indeed the final place, 253 // Typically this circumvents the problem of the redirect_url being a dumb relay page. 254 page_uri: window.location.href 255 }, 256 257 // Service configuration objects 258 services: {}, 259 260 // Use 261 // Define a new instance of the HelloJS library with a default service 262 use: function(service) { 263 264 // Create self, which inherits from its parent 265 var self = Object.create(this); 266 267 // Inherit the prototype from its parent 268 self.settings = Object.create(this.settings); 269 270 // Define the default service 271 if (service) { 272 self.settings.default_service = service; 273 } 274 275 // Create an instance of Events 276 self.utils.Event.call(self); 277 278 return self; 279 }, 280 281 // Initialize 282 // Define the client_ids for the endpoint services 283 // @param object o, contains a key value pair, service => clientId 284 // @param object opts, contains a key value pair of options used for defining the authentication defaults 285 // @param number timeout, timeout in seconds 286 init: function(services, options) { 287 288 var utils = this.utils; 289 290 if (!services) { 291 return this.services; 292 } 293 294 // Define provider credentials 295 // Reformat the ID field 296 for (var x in services) {if (services.hasOwnProperty(x)) { 297 if (typeof (services[x]) !== 'object') { 298 services[x] = {id: services[x]}; 299 } 300 }} 301 302 // Merge services if there already exists some 303 utils.extend(this.services, services); 304 305 // Update the default settings with this one. 306 if (options) { 307 utils.extend(this.settings, options); 308 309 // Do this immediatly incase the browser changes the current path. 310 if ('redirect_uri' in options) { 311 this.settings.redirect_uri = utils.url(options.redirect_uri).href; 312 } 313 } 314 315 return this; 316 }, 317
vendor: 7,304 bytes, lines 318-548
318 // Login 319 // Using the endpoint 320 // @param network stringify name to connect to 321 // @param options object (optional) {display mode, is either none|popup(default)|page, scope: email,birthday,publish, .. } 322 // @param callback function (optional) fired on signin 323 login: function() { 324 325 // Create an object which inherits its parent as the prototype and constructs a new event chain. 326 var _this = this; 327 var utils = _this.utils; 328 var error = utils.error; 329 var promise = utils.Promise(); 330 331 // Get parameters 332 var p = utils.args({network: 's', options: 'o', callback: 'f'}, arguments); 333 334 // Local vars 335 var url; 336 337 // Get all the custom options and store to be appended to the querystring 338 var qs = utils.diffKey(p.options, _this.settings); 339 340 // Merge/override options with app defaults 341 var opts = p.options = utils.merge(_this.settings, p.options || {}); 342 343 // Merge/override options with app defaults 344 opts.popup = utils.merge(_this.settings.popup, p.options.popup || {}); 345 346 // Network 347 p.network = p.network || _this.settings.default_service; 348 349 // Bind callback to both reject and fulfill states 350 promise.proxy.then(p.callback, p.callback); 351 352 // Trigger an event on the global listener 353 function emit(s, value) { 354 hello.emit(s, value); 355 } 356 357 promise.proxy.then(emit.bind(this, 'auth.login auth'), emit.bind(this, 'auth.failed auth')); 358 359 // Is our service valid? 360 if (typeof (p.network) !== 'string' || !(p.network in _this.services)) { 361 // Trigger the default login. 362 // Ahh we dont have one. 363 return promise.reject(error('invalid_network', 'The provided network was not recognized')); 364 } 365 366 var provider = _this.services[p.network]; 367 368 // Create a global listener to capture events triggered out of scope 369 var callbackId = utils.globalEvent(function(str) { 370 371 // The responseHandler returns a string, lets save this locally 372 var obj; 373 374 if (str) { 375 obj = JSON.parse(str); 376 } 377 else { 378 obj = error('cancelled', 'The authentication was not completed'); 379 } 380 381 // Handle these response using the local 382 // Trigger on the parent 383 if (!obj.error) { 384 385 // Save on the parent window the new credentials 386 // This fixes an IE10 bug i think... atleast it does for me. 387 utils.store(obj.network, obj); 388 389 // Fulfill a successful login 390 promise.fulfill({ 391 network: obj.network, 392 authResponse: obj 393 }); 394 } 395 else { 396 // Reject a successful login 397 promise.reject(obj); 398 } 399 }); 400 401 var redirectUri = utils.url(opts.redirect_uri).href; 402 403 // May be a space-delimited list of multiple, complementary types 404 var responseType = provider.oauth.response_type || opts.response_type; 405 406 // Fallback to token if the module hasn't defined a grant url 407 if (/\bcode\b/.test(responseType) && !provider.oauth.grant) { 408 responseType = responseType.replace(/\bcode\b/, 'token'); 409 } 410 411 // Query string parameters, we may pass our own arguments to form the querystring 412 p.qs = utils.merge(qs, { 413 client_id: encodeURIComponent(provider.id), 414 response_type: encodeURIComponent(responseType), 415 redirect_uri: encodeURIComponent(redirectUri), 416 state: { 417 client_id: provider.id, 418 network: p.network, 419 display: opts.display, 420 callback: callbackId, 421 state: opts.state, 422 redirect_uri: redirectUri 423 } 424 }); 425 426 // Get current session for merging scopes, and for quick auth response 427 var session = utils.store(p.network); 428 429 // Scopes (authentication permisions) 430 // Ensure this is a string - IE has a problem moving Arrays between windows 431 // Append the setup scope 432 var SCOPE_SPLIT = /[,\s]+/; 433 434 // Include default scope settings (cloned). 435 var scope = _this.settings.scope ? [_this.settings.scope.toString()] : []; 436 437 // Extend the providers scope list with the default 438 var scopeMap = utils.merge(_this.settings.scope_map, provider.scope || {}); 439 440 // Add user defined scopes... 441 if (opts.scope) { 442 scope.push(opts.scope.toString()); 443 } 444 445 // Append scopes from a previous session. 446 // This helps keep app credentials constant, 447 // Avoiding having to keep tabs on what scopes are authorized 448 if (session && 'scope' in session && session.scope instanceof String) { 449 scope.push(session.scope); 450 } 451 452 // Join and Split again 453 scope = scope.join(',').split(SCOPE_SPLIT); 454 455 // Format remove duplicates and empty values 456 scope = utils.unique(scope).filter(filterEmpty); 457 458 // Save the the scopes to the state with the names that they were requested with. 459 p.qs.state.scope = scope.join(','); 460 461 // Map scopes to the providers naming convention 462 scope = scope.map(function(item) { 463 // Does this have a mapping? 464 return (item in scopeMap) ? scopeMap[item] : item; 465 }); 466 467 // Stringify and Arrayify so that double mapped scopes are given the chance to be formatted 468 scope = scope.join(',').split(SCOPE_SPLIT); 469 470 // Again... 471 // Format remove duplicates and empty values 472 scope = utils.unique(scope).filter(filterEmpty); 473 474 // Join with the expected scope delimiter into a string 475 p.qs.scope = scope.join(provider.scope_delim || ','); 476 477 // Is the user already signed in with the appropriate scopes, valid access_token? 478 if (opts.force === false) { 479 480 if (session && 'access_token' in session && session.access_token && 'expires' in session && session.expires > ((new Date()).getTime() / 1e3)) { 481 // What is different about the scopes in the session vs the scopes in the new login? 482 var diff = utils.diff((session.scope || '').split(SCOPE_SPLIT), (p.qs.state.scope || '').split(SCOPE_SPLIT)); 483 if (diff.length === 0) { 484 485 // OK trigger the callback 486 promise.fulfill({ 487 unchanged: true, 488 network: p.network, 489 authResponse: session 490 }); 491 492 // Nothing has changed 493 return promise; 494 } 495 } 496 } 497 498 // Page URL 499 if (opts.display === 'page' && opts.page_uri) { 500 // Add a page location, place to endup after session has authenticated 501 p.qs.state.page_uri = utils.url(opts.page_uri).href; 502 } 503 504 // Bespoke 505 // Override login querystrings from auth_options 506 if ('login' in provider && typeof (provider.login) === 'function') { 507 // Format the paramaters according to the providers formatting function 508 provider.login(p); 509 } 510 511 // Add OAuth to state 512 // Where the service is going to take advantage of the oauth_proxy 513 if (!/\btoken\b/.test(responseType) || 514 parseInt(provider.oauth.version, 10) < 2 || 515 (opts.display === 'none' && provider.oauth.grant && session && session.refresh_token)) { 516 517 // Add the oauth endpoints 518 p.qs.state.oauth = provider.oauth; 519 520 // Add the proxy url 521 p.qs.state.oauth_proxy = opts.oauth_proxy; 522 523 } 524 525 // Convert state to a string 526 p.qs.state = encodeURIComponent(JSON.stringify(p.qs.state)); 527 528 // URL 529 if (parseInt(provider.oauth.version, 10) === 1) { 530 531 // Turn the request to the OAuth Proxy for 3-legged auth 532 url = utils.qs(opts.oauth_proxy, p.qs, encodeFunction); 533 } 534 535 // Refresh token 536 else if (opts.display === 'none' && provider.oauth.grant && session && session.refresh_token) { 537 538 // Add the refresh_token to the request 539 p.qs.refresh_token = session.refresh_token; 540 541 // Define the request path 542 url = utils.qs(opts.oauth_proxy, p.qs, encodeFunction); 543 } 544 else { 545 url = utils.qs(provider.oauth.auth, p.qs, encodeFunction); 546 } 547 548 // Broadcast this event as an auth:init
549 emit('auth.init', p); 550 551 // Execute 552 // Trigger how we want self displayed 553 if (opts.display === 'none') { 554 // Sign-in in the background, iframe 555 utils.iframe(url, redirectUri); 556 } 557 558 // Triggering popup? 559 else if (opts.display === 'popup') { 560 561 var popup = utils.popup(url, redirectUri, opts.popup); 562 563 var timer = setInterval(function() { 564 if (!popup || popup.closed) { 565 clearInterval(timer); 566 if (!promise.state) { 567 568 var response = error('cancelled', 'Login has been cancelled'); 569 570 if (!popup) { 571 response = error('blocked', 'Popup was blocked'); 572 } 573 574 response.network = p.network; 575 576 promise.reject(response); 577 } 578 } 579 }, 100); 580 } 581 582 else { 583 window.location = url; 584 } 585 586 return promise.proxy; 587 588 function encodeFunction(s) {return s;} 589 590 function filterEmpty(s) {return !!s;} 591 }, 592 593 // Remove any data associated with a given service 594 // @param string name of the service 595 // @param function callback 596 logout: function() { 597 598 var _this = this; 599 var utils = _this.utils; 600 var error = utils.error; 601 602 // Create a new promise 603 var promise = utils.Promise(); 604 605 var p = utils.args({name:'s', options: 'o', callback: 'f'}, arguments); 606 607 p.options = p.options || {}; 608 609 // Add callback to events 610 promise.proxy.then(p.callback, p.callback); 611 612 // Trigger an event on the global listener 613 function emit(s, value) { 614 hello.emit(s, value); 615 } 616 617 promise.proxy.then(emit.bind(this, 'auth.logout auth'), emit.bind(this, 'error')); 618 619 // Network 620 p.name = p.name || this.settings.default_service; 621 p.authResponse = utils.store(p.name); 622 623 if (p.name && !(p.name in _this.services)) { 624 625 promise.reject(error('invalid_network', 'The network was unrecognized')); 626 627 } 628 else if (p.name && p.authResponse) { 629 630 // Define the callback 631 var callback = function(opts) { 632 633 // Remove from the store 634 utils.store(p.name, null); 635 636 // Emit events by default 637 promise.fulfill(hello.utils.merge({network:p.name}, opts || {})); 638 }; 639 640 // Run an async operation to remove the users session 641 var _opts = {}; 642 if (p.options.force) { 643 var logout = _this.services[p.name].logout; 644 if (logout) { 645 // Convert logout to URL string, 646 // If no string is returned, then this function will handle the logout async style 647 if (typeof (logout) === 'function') { 648 logout = logout(callback, p); 649 } 650 651 // If logout is a string then assume URL and open in iframe. 652 if (typeof (logout) === 'string') { 653 utils.iframe(logout); 654 _opts.force = null; 655 _opts.message = 'Logout success on providers site was indeterminate'; 656 } 657 else if (logout === undefined) { 658 // The callback function will handle the response. 659 return promise.proxy; 660 } 661 } 662 } 663 664 // Remove local credentials 665 callback(_opts); 666 } 667 else { 668 promise.reject(error('invalid_session', 'There was no session to remove')); 669 } 670 671 return promise.proxy; 672 }, 673 674 // Returns all the sessions that are subscribed too 675 // @param string optional, name of the service to get information about. 676 getAuthResponse: function(service) { 677 678 // If the service doesn't exist 679 service = service || this.settings.default_service; 680 681 if (!service || !(service in this.services)) { 682 return null; 683 } 684 685 return this.utils.store(service) || null; 686 }, 687 688 // Events: placeholder for the events 689 events: {} 690}); 691 692// Core utilities 693hello.utils.extend(hello.utils, { 694 695 // Error 696 error: function(code, message) { 697 return { 698 error: { 699 code: code, 700 message: message 701 } 702 }; 703 }, 704 705 // Append the querystring to a url 706 // @param string url 707 // @param object parameters 708 qs: function(url, params, formatFunction) { 709 710 if (params) { 711 712 // Set default formatting function 713 formatFunction = formatFunction || encodeURIComponent; 714 715 // Override the items in the URL which already exist 716 for (var x in params) { 717 var str = '([\\?\\&])' + x + '=[^\\&]*'; 718 var reg = new RegExp(str); 719 if (url.match(reg)) { 720 url = url.replace(reg, '$1' + x + '=' + formatFunction(params[x])); 721 delete params[x]; 722 } 723 } 724 } 725 726 if (!this.isEmpty(params)) { 727 return url + (url.indexOf('?') > -1 ? '&' : '?') + this.param(params, formatFunction); 728 } 729 730 return url; 731 }, 732 733 // Param 734 // Explode/encode the parameters of an URL string/object 735 // @param string s, string to decode 736 param: function(s, formatFunction) { 737 var b; 738 var a = {}; 739 var m; 740 741 if (typeof (s) === 'string') { 742 743 formatFunction = formatFunction || decodeURIComponent; 744 745 m = s.replace(/^[\#\?]/, '').match(/([^=\/\&]+)=([^\&]+)/g); 746 if (m) { 747 for (var i = 0; i < m.length; i++) { 748 b = m[i].match(/([^=]+)=(.*)/); 749 a[b[1]] = formatFunction(b[2]); 750 } 751 } 752 753 return a; 754 } 755 else { 756 757 formatFunction = formatFunction || encodeURIComponent; 758 759 var o = s; 760 761 a = []; 762 763 for (var x in o) {if (o.hasOwnProperty(x)) { 764 if (o.hasOwnProperty(x)) { 765 a.push([x, o[x] === '?' ? '?' : formatFunction(o[x])].join('=')); 766 } 767 }} 768 769 return a.join('&'); 770 } 771 }, 772
vendor: 4,891 bytes, lines 773-985
773 // Local storage facade 774 store: (function() { 775 776 var a = ['localStorage', 'sessionStorage']; 777 var i = -1; 778 var prefix = 'test'; 779 780 // Set LocalStorage 781 var localStorage; 782 783 while (a[++i]) { 784 try { 785 // In Chrome with cookies blocked, calling localStorage throws an error 786 localStorage = window[a[i]]; 787 localStorage.setItem(prefix + i, i); 788 localStorage.removeItem(prefix + i); 789 break; 790 } 791 catch (e) { 792 localStorage = null; 793 } 794 } 795 796 if (!localStorage) { 797 798 var cache = null; 799 800 localStorage = { 801 getItem: function(prop) { 802 prop = prop + '='; 803 var m = document.cookie.split(';'); 804 for (var i = 0; i < m.length; i++) { 805 var _m = m[i].replace(/(^\s+|\s+$)/, ''); 806 if (_m && _m.indexOf(prop) === 0) { 807 return _m.substr(prop.length); 808 } 809 } 810 811 return cache; 812 }, 813 814 setItem: function(prop, value) { 815 cache = value; 816 document.cookie = prop + '=' + value; 817 } 818 }; 819 820 // Fill the cache up 821 cache = localStorage.getItem('hello'); 822 } 823 824 function get() { 825 var json = {}; 826 try { 827 json = JSON.parse(localStorage.getItem('hello')) || {}; 828 } 829 catch (e) {} 830 831 return json; 832 } 833 834 function set(json) { 835 localStorage.setItem('hello', JSON.stringify(json)); 836 } 837 838 // Check if the browser support local storage 839 return function(name, value, days) { 840 841 // Local storage 842 var json = get(); 843 844 if (name && value === undefined) { 845 return json[name] || null; 846 } 847 else if (name && value === null) { 848 try { 849 delete json[name]; 850 } 851 catch (e) { 852 json[name] = null; 853 } 854 } 855 else if (name) { 856 json[name] = value; 857 } 858 else { 859 return json; 860 } 861 862 set(json); 863 864 return json || null; 865 }; 866 867 })(), 868 869 // Create and Append new DOM elements 870 // @param node string 871 // @param attr object literal 872 // @param dom/string 873 append: function(node, attr, target) { 874 875 var n = typeof (node) === 'string' ? document.createElement(node) : node; 876 877 if (typeof (attr) === 'object') { 878 if ('tagName' in attr) { 879 target = attr; 880 } 881 else { 882 for (var x in attr) {if (attr.hasOwnProperty(x)) { 883 if (typeof (attr[x]) === 'object') { 884 for (var y in attr[x]) {if (attr[x].hasOwnProperty(y)) { 885 n[x][y] = attr[x][y]; 886 }} 887 } 888 else if (x === 'html') { 889 n.innerHTML = attr[x]; 890 } 891 892 // IE doesn't like us setting methods with setAttribute 893 else if (!/^on/.test(x)) { 894 n.setAttribute(x, attr[x]); 895 } 896 else { 897 n[x] = attr[x]; 898 } 899 }} 900 } 901 } 902 903 if (target === 'body') { 904 (function self() { 905 if (document.body) { 906 document.body.appendChild(n); 907 } 908 else { 909 setTimeout(self, 16); 910 } 911 })(); 912 } 913 else if (typeof (target) === 'object') { 914 target.appendChild(n); 915 } 916 else if (typeof (target) === 'string') { 917 document.getElementsByTagName(target)[0].appendChild(n); 918 } 919 920 return n; 921 }, 922 923 // An easy way to create a hidden iframe 924 // @param string src 925 iframe: function(src) { 926 this.append('iframe', {src: src, style: {position:'absolute', left: '-1000px', bottom: 0, height: '1px', width: '1px'}}, 'body'); 927 }, 928 929 // Recursive merge two objects into one, second parameter overides the first 930 // @param a array 931 merge: function(/* Args: a, b, c, .. n */) { 932 var args = Array.prototype.slice.call(arguments); 933 args.unshift({}); 934 return this.extend.apply(null, args); 935 }, 936 937 // Makes it easier to assign parameters, where some are optional 938 // @param o object 939 // @param a arguments 940 args: function(o, args) { 941 942 var p = {}; 943 var i = 0; 944 var t = null; 945 var x = null; 946 947 // 'x' is the first key in the list of object parameters 948 for (x in o) {if (o.hasOwnProperty(x)) { 949 break; 950 }} 951 952 // Passing in hash object of arguments? 953 // Where the first argument can't be an object 954 if ((args.length === 1) && (typeof (args[0]) === 'object') && o[x] != 'o!') { 955 956 // Could this object still belong to a property? 957 // Check the object keys if they match any of the property keys 958 for (x in args[0]) {if (o.hasOwnProperty(x)) { 959 // Does this key exist in the property list? 960 if (x in o) { 961 // Yes this key does exist so its most likely this function has been invoked with an object parameter 962 // Return first argument as the hash of all arguments 963 return args[0]; 964 } 965 }} 966 } 967 968 // Else loop through and account for the missing ones. 969 for (x in o) {if (o.hasOwnProperty(x)) { 970 971 t = typeof (args[i]); 972 973 if ((typeof (o[x]) === 'function' && o[x].test(args[i])) || (typeof (o[x]) === 'string' && ( 974 (o[x].indexOf('s') > -1 && t === 'string') || 975 (o[x].indexOf('o') > -1 && t === 'object') || 976 (o[x].indexOf('i') > -1 && t === 'number') || 977 (o[x].indexOf('a') > -1 && t === 'object') || 978 (o[x].indexOf('f') > -1 && t === 'function') 979 )) 980 ) { 981 p[x] = args[i++]; 982 } 983 984 else if (typeof (o[x]) === 'string' && o[x].indexOf('!') > -1) { 985 return false;
986 } 987 }} 988 989 return p; 990 }, 991 992 // Returns a URL instance 993 url: function(path) { 994 995 // If the path is empty 996 if (!path) { 997 return window.location; 998 } 999 1000 // Chrome and FireFox support new URL() to extract URL objects 1001 else if (window.URL && URL instanceof Function && URL.length !== 0) { 1002 return new URL(path, window.location); 1003 } 1004 1005 // Ugly shim, it works! 1006 else { 1007 var a = document.createElement('a'); 1008 a.href = path; 1009 return a.cloneNode(false); 1010 } 1011 }, 1012 1013 diff: function(a, b) { 1014 return b.filter(function(item) { 1015 return a.indexOf(item) === -1; 1016 }); 1017 }, 1018 1019 // Get the different hash of properties unique to `a`, and not in `b` 1020 diffKey: function(a, b) { 1021 if (a || !b) { 1022 var r = {}; 1023 for (var x in a) { 1024 // Does the property not exist? 1025 if (!(x in b)) { 1026 r[x] = a[x]; 1027 } 1028 } 1029 1030 return r; 1031 } 1032 1033 return a; 1034 }, 1035 1036 // Unique 1037 // Remove duplicate and null values from an array 1038 // @param a array 1039 unique: function(a) { 1040 if (!Array.isArray(a)) { return []; } 1041 1042 return a.filter(function(item, index) { 1043 // Is this the first location of item 1044 return a.indexOf(item) === index; 1045 }); 1046 }, 1047 1048 isEmpty: function(obj) { 1049 1050 // Scalar 1051 if (!obj) 1052 return true; 1053 1054 // Array 1055 if (Array.isArray(obj)) { 1056 return !obj.length; 1057 } 1058 else if (typeof (obj) === 'object') { 1059 // Object 1060 for (var key in obj) { 1061 if (obj.hasOwnProperty(key)) { 1062 return false; 1063 } 1064 } 1065 } 1066 1067 return true; 1068 }, 1069 1070 //jscs:disable 1071 1072 /*! 1073 ** Thenable -- Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable 1074 ** Copyright (c) 2013-2014 Ralf S. Engelschall <http://engelschall.com> 1075 ** Licensed under The MIT License <http://opensource.org/licenses/MIT> 1076 ** Source-Code distributed on <http://github.com/rse/thenable> 1077 */ 1078 Promise: (function(){ 1079 /* promise states [Promises/A+ 2.1] */ 1080 var STATE_PENDING = 0; /* [Promises/A+ 2.1.1] */ 1081 var STATE_FULFILLED = 1; /* [Promises/A+ 2.1.2] */ 1082 var STATE_REJECTED = 2; /* [Promises/A+ 2.1.3] */ 1083 1084 /* promise object constructor */ 1085 var api = function (executor) { 1086 /* optionally support non-constructor/plain-function call */ 1087 if (!(this instanceof api)) 1088 return new api(executor); 1089 1090 /* initialize object */ 1091 this.id = "Thenable/1.0.6"; 1092 this.state = STATE_PENDING; /* initial state */ 1093 this.fulfillValue = undefined; /* initial value */ /* [Promises/A+ 1.3, 2.1.2.2] */ 1094 this.rejectReason = undefined; /* initial reason */ /* [Promises/A+ 1.5, 2.1.3.2] */ 1095 this.onFulfilled = []; /* initial handlers */ 1096 this.onRejected = []; /* initial handlers */ 1097 1098 /* provide optional information-hiding proxy */ 1099 this.proxy = { 1100 then: this.then.bind(this) 1101 }; 1102 1103 /* support optional executor function */ 1104 if (typeof executor === "function") 1105 executor.call(this, this.fulfill.bind(this), this.reject.bind(this)); 1106 }; 1107 1108 /* promise API methods */ 1109 api.prototype = { 1110 /* promise resolving methods */ 1111 fulfill: function (value) { return deliver(this, STATE_FULFILLED, "fulfillValue", value); }, 1112 reject: function (value) { return deliver(this, STATE_REJECTED, "rejectReason", value); }, 1113 1114 /* "The then Method" [Promises/A+ 1.1, 1.2, 2.2] */ 1115 then: function (onFulfilled, onRejected) { 1116 var curr = this; 1117 var next = new api(); /* [Promises/A+ 2.2.7] */ 1118 curr.onFulfilled.push( 1119 resolver(onFulfilled, next, "fulfill")); /* [Promises/A+ 2.2.2/2.2.6] */ 1120 curr.onRejected.push( 1121 resolver(onRejected, next, "reject" )); /* [Promises/A+ 2.2.3/2.2.6] */ 1122 execute(curr); 1123 return next.proxy; /* [Promises/A+ 2.2.7, 3.3] */ 1124 } 1125 }; 1126 1127 /* deliver an action */ 1128 var deliver = function (curr, state, name, value) { 1129 if (curr.state === STATE_PENDING) { 1130 curr.state = state; /* [Promises/A+ 2.1.2.1, 2.1.3.1] */ 1131 curr[name] = value; /* [Promises/A+ 2.1.2.2, 2.1.3.2] */ 1132 execute(curr); 1133 } 1134 return curr; 1135 }; 1136 1137 /* execute all handlers */ 1138 var execute = function (curr) { 1139 if (curr.state === STATE_FULFILLED) 1140 execute_handlers(curr, "onFulfilled", curr.fulfillValue); 1141 else if (curr.state === STATE_REJECTED) 1142 execute_handlers(curr, "onRejected", curr.rejectReason); 1143 }; 1144 1145 /* execute particular set of handlers */ 1146 var execute_handlers = function (curr, name, value) { 1147 /* global process: true */ 1148 /* global setImmediate: true */ 1149 /* global setTimeout: true */ 1150 1151 /* short-circuit processing */ 1152 if (curr[name].length === 0) 1153 return; 1154 1155 /* iterate over all handlers, exactly once */ 1156 var handlers = curr[name]; 1157 curr[name] = []; /* [Promises/A+ 2.2.2.3, 2.2.3.3] */ 1158 var func = function () { 1159 for (var i = 0; i < handlers.length; i++) 1160 handlers[i](value); /* [Promises/A+ 2.2.5] */ 1161 }; 1162 1163 /* execute procedure asynchronously */ /* [Promises/A+ 2.2.4, 3.1] */ 1164 if (typeof process === "object" && typeof process.nextTick === "function") 1165 process.nextTick(func); 1166 else if (typeof setImmediate === "function") 1167 setImmediate(func); 1168 else 1169 setTimeout(func, 0); 1170 }; 1171 1172 /* generate a resolver function */ 1173 var resolver = function (cb, next, method) { 1174 return function (value) { 1175 if (typeof cb !== "function") /* [Promises/A+ 2.2.1, 2.2.7.3, 2.2.7.4] */ 1176 next[method].call(next, value); /* [Promises/A+ 2.2.7.3, 2.2.7.4] */ 1177 else { 1178 var result; 1179 try { result = cb(value); } /* [Promises/A+ 2.2.2.1, 2.2.3.1, 2.2.5, 3.2] */ 1180 catch (e) { 1181 next.reject(e); /* [Promises/A+ 2.2.7.2] */ 1182 return; 1183 } 1184 resolve(next, result); /* [Promises/A+ 2.2.7.1] */ 1185 } 1186 }; 1187 }; 1188 1189 /* "Promise Resolution Procedure" */ /* [Promises/A+ 2.3] */ 1190 var resolve = function (promise, x) { 1191 /* sanity check arguments */ /* [Promises/A+ 2.3.1] */ 1192 if (promise === x || promise.proxy === x) { 1193 promise.reject(new TypeError("cannot resolve promise with itself")); 1194 return; 1195 } 1196 1197 /* surgically check for a "then" method 1198 (mainly to just call the "getter" of "then" only once) */ 1199 var then; 1200 if ((typeof x === "object" && x !== null) || typeof x === "function") { 1201 try { then = x.then; } /* [Promises/A+ 2.3.3.1, 3.5] */
1202 catch (e) { 1203 promise.reject(e); /* [Promises/A+ 2.3.3.2] */ 1204 return; 1205 } 1206 } 1207 1208 /* handle own Thenables [Promises/A+ 2.3.2] 1209 and similar "thenables" [Promises/A+ 2.3.3] */ 1210 if (typeof then === "function") { 1211 var resolved = false; 1212 try { 1213 /* call retrieved "then" method */ /* [Promises/A+ 2.3.3.3] */ 1214 then.call(x, 1215 /* resolvePromise */ /* [Promises/A+ 2.3.3.3.1] */ 1216 function (y) { 1217 if (resolved) return; resolved = true; /* [Promises/A+ 2.3.3.3.3] */ 1218 if (y === x) /* [Promises/A+ 3.6] */ 1219 promise.reject(new TypeError("circular thenable chain")); 1220 else 1221 resolve(promise, y); 1222 }, 1223 1224 /* rejectPromise */ /* [Promises/A+ 2.3.3.3.2] */ 1225 function (r) { 1226 if (resolved) return; resolved = true; /* [Promises/A+ 2.3.3.3.3] */ 1227 promise.reject(r); 1228 } 1229 ); 1230 } 1231 catch (e) { 1232 if (!resolved) /* [Promises/A+ 2.3.3.3.3] */ 1233 promise.reject(e); /* [Promises/A+ 2.3.3.3.4] */ 1234 } 1235 return; 1236 } 1237 1238 /* handle other values */ 1239 promise.fulfill(x); /* [Promises/A+ 2.3.4, 2.3.3.4] */ 1240 }; 1241 1242 /* export API */ 1243 return api; 1244 })(), 1245 1246 //jscs:enable 1247 1248 // Event 1249 // A contructor superclass for adding event menthods, on, off, emit. 1250 Event: function() { 1251 1252 var separator = /[\s\,]+/; 1253 1254 // If this doesn't support getPrototype then we can't get prototype.events of the parent 1255 // So lets get the current instance events, and add those to a parent property 1256 this.parent = { 1257 events: this.events, 1258 findEvents: this.findEvents, 1259 parent: this.parent, 1260 utils: this.utils 1261 }; 1262 1263 this.events = {}; 1264 1265 // On, subscribe to events 1266 // @param evt string 1267 // @param callback function 1268 this.on = function(evt, callback) { 1269 1270 if (callback && typeof (callback) === 'function') { 1271 var a = evt.split(separator); 1272 for (var i = 0; i < a.length; i++) { 1273 1274 // Has this event already been fired on this instance? 1275 this.events[a[i]] = [callback].concat(this.events[a[i]] || []); 1276 } 1277 } 1278 1279 return this; 1280 }; 1281 1282 // Off, unsubscribe to events 1283 // @param evt string 1284 // @param callback function 1285 this.off = function(evt, callback) { 1286 1287 this.findEvents(evt, function(name, index) { 1288 if (!callback || this.events[name][index] === callback) { 1289 this.events[name][index] = null; 1290 } 1291 }); 1292 1293 return this; 1294 }; 1295 1296 // Emit 1297 // Triggers any subscribed events 1298 this.emit = function(evt /*, data, ... */) { 1299 1300 // Get arguments as an Array, knock off the first one 1301 var args = Array.prototype.slice.call(arguments, 1); 1302 args.push(evt); 1303 1304 // Handler 1305 var handler = function(name, index) { 1306 1307 // Replace the last property with the event name 1308 args[args.length - 1] = (name === '*' ? evt : name); 1309 1310 // Trigger 1311 this.events[name][index].apply(this, args); 1312 }; 1313 1314 // Find the callbacks which match the condition and call 1315 var _this = this; 1316 while (_this && _this.findEvents) { 1317 1318 // Find events which match 1319 _this.findEvents(evt + ',*', handler); 1320 _this = _this.parent; 1321 } 1322 1323 return this; 1324 }; 1325 1326 // 1327 // Easy functions 1328 this.emitAfter = function() { 1329 var _this = this; 1330 var args = arguments; 1331 setTimeout(function() { 1332 _this.emit.apply(_this, args); 1333 }, 0); 1334 1335 return this; 1336 }; 1337 1338 this.findEvents = function(evt, callback) { 1339 1340 var a = evt.split(separator); 1341 1342 for (var name in this.events) {if (this.events.hasOwnProperty(name)) { 1343 1344 if (a.indexOf(name) > -1) { 1345 1346 for (var i = 0; i < this.events[name].length; i++) { 1347 1348 // Does the event handler exist? 1349 if (this.events[name][i]) { 1350 // Emit on the local instance of this 1351 callback.call(this, name, i); 1352 } 1353 } 1354 } 1355 }} 1356 }; 1357 1358 return this; 1359 }, 1360 1361 // Global Events 1362 // Attach the callback to the window object 1363 // Return its unique reference 1364 globalEvent: function(callback, guid) { 1365 // If the guid has not been supplied then create a new one. 1366 guid = guid || '_hellojs_' + parseInt(Math.random() * 1e12, 10).toString(36); 1367 1368 // Define the callback function 1369 window[guid] = function() { 1370 // Trigger the callback 1371 try { 1372 if (callback.apply(this, arguments)) { 1373 delete window[guid]; 1374 } 1375 } 1376 catch (e) { 1377 console.error(e); 1378 } 1379 }; 1380 1381 return guid; 1382 }, 1383 1384 // Trigger a clientside popup 1385 // This has been augmented to support PhoneGap 1386 popup: function(url, redirectUri, options) { 1387 1388 var documentElement = document.documentElement; 1389 1390 // Multi Screen Popup Positioning (http://stackoverflow.com/a/16861050) 1391 // Credit: http://www.xtf.dk/2011/08/center-new-popup-window-even-on.html 1392 // Fixes dual-screen position Most browsers Firefox 1393 1394 if (options.height) { 1395 var dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top; 1396 var height = screen.height || window.innerHeight || documentElement.clientHeight; 1397 options.top = parseInt((height - options.height) / 2, 10) + dualScreenTop; 1398 } 1399 1400 if (options.width) { 1401 var dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left; 1402 var width = screen.width || window.innerWidth || documentElement.clientWidth; 1403 options.left = parseInt((width - options.width) / 2, 10) + dualScreenLeft; 1404 } 1405 1406 // Convert options into an array 1407 var optionsArray = [];
vendor: 19,515 bytes, lines 1408-2181
1408 Object.keys(options).forEach(function(name) { 1409 var value = options[name]; 1410 optionsArray.push(name + (value !== null ? '=' + value : '')); 1411 }); 1412 1413 // Call the open() function with the initial path 1414 // 1415 // OAuth redirect, fixes URI fragments from being lost in Safari 1416 // (URI Fragments within 302 Location URI are lost over HTTPS) 1417 // Loading the redirect.html before triggering the OAuth Flow seems to fix it. 1418 // 1419 // Firefox decodes URL fragments when calling location.hash. 1420 // - This is bad if the value contains break points which are escaped 1421 // - Hence the url must be encoded twice as it contains breakpoints. 1422 if (navigator.userAgent.indexOf('Safari') !== -1 && navigator.userAgent.indexOf('Chrome') === -1) { 1423 url = redirectUri + '#oauth_redirect=' + encodeURIComponent(encodeURIComponent(url)); 1424 } 1425 1426 var popup = window.open( 1427 url, 1428 '_blank', 1429 optionsArray.join(',') 1430 ); 1431 1432 if (popup && popup.focus) { 1433 popup.focus(); 1434 } 1435 1436 return popup; 1437 }, 1438 1439 // OAuth and API response handler 1440 responseHandler: function(window, parent) { 1441 1442 var _this = this; 1443 var p; 1444 var location = window.location; 1445 1446 // Is this an auth relay message which needs to call the proxy? 1447 p = _this.param(location.search); 1448 1449 // OAuth2 or OAuth1 server response? 1450 if (p && p.state && (p.code || p.oauth_token)) { 1451 1452 var state = JSON.parse(p.state); 1453 1454 // Add this path as the redirect_uri 1455 p.redirect_uri = state.redirect_uri || location.href.replace(/[\?\#].*$/, ''); 1456 1457 // Redirect to the host 1458 var path = state.oauth_proxy + '?' + _this.param(p); 1459 1460 location.assign(path); 1461 1462 return; 1463 } 1464 1465 // Save session, from redirected authentication 1466 // #access_token has come in? 1467 // 1468 // FACEBOOK is returning auth errors within as a query_string... thats a stickler for consistency. 1469 // SoundCloud is the state in the querystring and the token in the hashtag, so we'll mix the two together 1470 1471 p = _this.merge(_this.param(location.search || ''), _this.param(location.hash || '')); 1472 1473 // If p.state 1474 if (p && 'state' in p) { 1475 1476 // Remove any addition information 1477 // E.g. p.state = 'facebook.page'; 1478 try { 1479 var a = JSON.parse(p.state); 1480 _this.extend(p, a); 1481 } 1482 catch (e) { 1483 console.error('Could not decode state parameter'); 1484 } 1485 1486 // Access_token? 1487 if (('access_token' in p && p.access_token) && p.network) { 1488 1489 if (!p.expires_in || parseInt(p.expires_in, 10) === 0) { 1490 // If p.expires_in is unset, set to 0 1491 p.expires_in = 0; 1492 } 1493 1494 p.expires_in = parseInt(p.expires_in, 10); 1495 p.expires = ((new Date()).getTime() / 1e3) + (p.expires_in || (60 * 60 * 24 * 365)); 1496 1497 // Lets use the "state" to assign it to one of our networks 1498 authCallback(p, window, parent); 1499 } 1500 1501 // Error=? 1502 // &error_description=? 1503 // &state=? 1504 else if (('error' in p && p.error) && p.network) { 1505 1506 p.error = { 1507 code: p.error, 1508 message: p.error_message || p.error_description 1509 }; 1510 1511 // Let the state handler handle it 1512 authCallback(p, window, parent); 1513 } 1514 1515 // API call, or a cancelled login 1516 // Result is serialized JSON string 1517 else if (p.callback && p.callback in parent) { 1518 1519 // Trigger a function in the parent 1520 var res = 'result' in p && p.result ? JSON.parse(p.result) : false; 1521 1522 // Trigger the callback on the parent 1523 callback(parent, p.callback)(res); 1524 closeWindow(); 1525 } 1526 1527 // If this page is still open 1528 if (p.page_uri) { 1529 location.assign(p.page_uri); 1530 } 1531 } 1532 1533 // OAuth redirect, fixes URI fragments from being lost in Safari 1534 // (URI Fragments within 302 Location URI are lost over HTTPS) 1535 // Loading the redirect.html before triggering the OAuth Flow seems to fix it. 1536 else if ('oauth_redirect' in p) { 1537 1538 location.assign(decodeURIComponent(p.oauth_redirect)); 1539 return; 1540 } 1541 1542 // Trigger a callback to authenticate 1543 function authCallback(obj, window, parent) { 1544 1545 var cb = obj.callback; 1546 var network = obj.network; 1547 1548 // Trigger the callback on the parent 1549 _this.store(network, obj); 1550 1551 // If this is a page request it has no parent or opener window to handle callbacks 1552 if (('display' in obj) && obj.display === 'page') { 1553 return; 1554 } 1555 1556 // Remove from session object 1557 if (parent && cb && cb in parent) { 1558 1559 try { 1560 delete obj.callback; 1561 } 1562 catch (e) {} 1563 1564 // Update store 1565 _this.store(network, obj); 1566 1567 // Call the globalEvent function on the parent 1568 // It's safer to pass back a string to the parent, 1569 // Rather than an object/array (better for IE8) 1570 var str = JSON.stringify(obj); 1571 1572 try { 1573 callback(parent, cb)(str); 1574 } 1575 catch (e) { 1576 // Error thrown whilst executing parent callback 1577 } 1578 } 1579 1580 closeWindow(); 1581 } 1582 1583 function callback(parent, callbackID) { 1584 if (callbackID.indexOf('_hellojs_') !== 0) { 1585 return function() { 1586 throw 'Could not execute callback ' + callbackID; 1587 }; 1588 } 1589 1590 return parent[callbackID]; 1591 } 1592 1593 function closeWindow() { 1594 1595 if (window.frameElement) { 1596 // Inside an iframe, remove from parent 1597 parent.document.body.removeChild(window.frameElement); 1598 } 1599 else { 1600 // Close this current window 1601 try { 1602 window.close(); 1603 } 1604 catch (e) {} 1605 1606 // IOS bug wont let us close a popup if still loading 1607 if (window.addEventListener) { 1608 window.addEventListener('load', function() { 1609 window.close(); 1610 }); 1611 } 1612 } 1613 1614 } 1615 } 1616}); 1617 1618// Events 1619// Extend the hello object with its own event instance 1620hello.utils.Event.call(hello); 1621 1622/////////////////////////////////// 1623// Monitoring session state 1624// Check for session changes 1625/////////////////////////////////// 1626 1627(function(hello) { 1628 1629 // Monitor for a change in state and fire 1630 var oldSessions = {}; 1631 1632 // Hash of expired tokens 1633 var expired = {}; 1634 1635 // Listen to other triggers to Auth events, use these to update this 1636 hello.on('auth.login, auth.logout', function(auth) { 1637 if (auth && typeof (auth) === 'object' && auth.network) { 1638 oldSessions[auth.network] = hello.utils.store(auth.network) || {}; 1639 } 1640 }); 1641 1642 (function self() { 1643 1644 var CURRENT_TIME = ((new Date()).getTime() / 1e3); 1645 var emit = function(eventName) { 1646 hello.emit('auth.' + eventName, { 1647 network: name, 1648 authResponse: session 1649 }); 1650 }; 1651 1652 // Loop through the services 1653 for (var name in hello.services) {if (hello.services.hasOwnProperty(name)) { 1654 1655 if (!hello.services[name].id) { 1656 // We haven't attached an ID so dont listen. 1657 continue; 1658 } 1659 1660 // Get session 1661 var session = hello.utils.store(name) || {}; 1662 var provider = hello.services[name]; 1663 var oldSess = oldSessions[name] || {}; 1664 1665 // Listen for globalEvents that did not get triggered from the child 1666 if (session && 'callback' in session) { 1667 1668 // To do remove from session object... 1669 var cb = session.callback; 1670 try { 1671 delete session.callback; 1672 } 1673 catch (e) {} 1674 1675 // Update store 1676 // Removing the callback 1677 hello.utils.store(name, session); 1678 1679 // Emit global events 1680 try { 1681 window[cb](session); 1682 } 1683 catch (e) {} 1684 } 1685 1686 // Refresh token 1687 if (session && ('expires' in session) && session.expires < CURRENT_TIME) { 1688 1689 // If auto refresh is possible 1690 // Either the browser supports 1691 var refresh = provider.refresh || session.refresh_token; 1692 1693 // Has the refresh been run recently? 1694 if (refresh && (!(name in expired) || expired[name] < CURRENT_TIME)) { 1695 // Try to resignin 1696 hello.emit('notice', name + ' has expired trying to resignin'); 1697 hello.login(name, {display: 'none', force: false}); 1698 1699 // Update expired, every 10 minutes 1700 expired[name] = CURRENT_TIME + 600; 1701 } 1702 1703 // Does this provider not support refresh 1704 else if (!refresh && !(name in expired)) { 1705 // Label the event 1706 emit('expired'); 1707 expired[name] = true; 1708 } 1709 1710 // If session has expired then we dont want to store its value until it can be established that its been updated 1711 continue; 1712 } 1713 1714 // Has session changed? 1715 else if (oldSess.access_token === session.access_token && 1716 oldSess.expires === session.expires) { 1717 continue; 1718 } 1719 1720 // Access_token has been removed 1721 else if (!session.access_token && oldSess.access_token) { 1722 emit('logout'); 1723 } 1724 1725 // Access_token has been created 1726 else if (session.access_token && !oldSess.access_token) { 1727 emit('login'); 1728 } 1729 1730 // Access_token has been updated 1731 else if (session.expires !== oldSess.expires) { 1732 emit('update'); 1733 } 1734 1735 // Updated stored session 1736 oldSessions[name] = session; 1737 1738 // Remove the expired flags 1739 if (name in expired) { 1740 delete expired[name]; 1741 } 1742 }} 1743 1744 // Check error events 1745 setTimeout(self, 1000); 1746 })(); 1747 1748})(hello); 1749 1750// EOF CORE lib 1751////////////////////////////////// 1752 1753///////////////////////////////////////// 1754// API 1755// @param path string 1756// @param query object (optional) 1757// @param method string (optional) 1758// @param data object (optional) 1759// @param timeout integer (optional) 1760// @param callback function (optional) 1761 1762hello.api = function() { 1763 1764 // Shorthand 1765 var _this = this; 1766 var utils = _this.utils; 1767 var error = utils.error; 1768 1769 // Construct a new Promise object 1770 var promise = utils.Promise(); 1771 1772 // Arguments 1773 var p = utils.args({path: 's!', query: 'o', method: 's', data: 'o', timeout: 'i', callback: 'f'}, arguments); 1774 1775 // Method 1776 p.method = (p.method || 'get').toLowerCase(); 1777 1778 // Headers 1779 p.headers = p.headers || {}; 1780 1781 // Query 1782 p.query = p.query || {}; 1783 1784 // If get, put all parameters into query 1785 if (p.method === 'get' || p.method === 'delete') { 1786 utils.extend(p.query, p.data); 1787 p.data = {}; 1788 } 1789 1790 var data = p.data = p.data || {}; 1791 1792 // Completed event callback 1793 promise.then(p.callback, p.callback); 1794 1795 // Remove the network from path, e.g. facebook:/me/friends 1796 // Results in { network : facebook, path : me/friends } 1797 if (!p.path) { 1798 return promise.reject(error('invalid_path', 'Missing the path parameter from the request')); 1799 } 1800 1801 p.path = p.path.replace(/^\/+/, ''); 1802 var a = (p.path.split(/[\/\:]/, 2) || [])[0].toLowerCase(); 1803 1804 if (a in _this.services) { 1805 p.network = a; 1806 var reg = new RegExp('^' + a + ':?\/?'); 1807 p.path = p.path.replace(reg, ''); 1808 } 1809 1810 // Network & Provider 1811 // Define the network that this request is made for 1812 p.network = _this.settings.default_service = p.network || _this.settings.default_service; 1813 var o = _this.services[p.network]; 1814 1815 // INVALID 1816 // Is there no service by the given network name? 1817 if (!o) { 1818 return promise.reject(error('invalid_network', 'Could not match the service requested: ' + p.network)); 1819 } 1820 1821 // PATH 1822 // As long as the path isn't flagged as unavaiable, e.g. path == false 1823 1824 if (!(!(p.method in o) || !(p.path in o[p.method]) || o[p.method][p.path] !== false)) { 1825 return promise.reject(error('invalid_path', 'The provided path is not available on the selected network')); 1826 } 1827 1828 // PROXY 1829 // OAuth1 calls always need a proxy 1830 1831 if (!p.oauth_proxy) { 1832 p.oauth_proxy = _this.settings.oauth_proxy; 1833 } 1834 1835 if (!('proxy' in p)) { 1836 p.proxy = p.oauth_proxy && o.oauth && parseInt(o.oauth.version, 10) === 1; 1837 } 1838 1839 // TIMEOUT 1840 // Adopt timeout from global settings by default 1841 1842 if (!('timeout' in p)) { 1843 p.timeout = _this.settings.timeout; 1844 } 1845 1846 // Format response 1847 // Whether to run the raw response through post processing. 1848 if (!('formatResponse' in p)) { 1849 p.formatResponse = true; 1850 } 1851 1852 // Get the current session 1853 // Append the access_token to the query 1854 p.authResponse = _this.getAuthResponse(p.network); 1855 if (p.authResponse && p.authResponse.access_token) { 1856 p.query.access_token = p.authResponse.access_token; 1857 } 1858 1859 var url = p.path; 1860 var m; 1861 1862 // Store the query as options 1863 // This is used to populate the request object before the data is augmented by the prewrap handlers. 1864 p.options = utils.clone(p.query); 1865 1866 // Clone the data object 1867 // Prevent this script overwriting the data of the incoming object. 1868 // Ensure that everytime we run an iteration the callbacks haven't removed some data 1869 p.data = utils.clone(data); 1870 1871 // URL Mapping 1872 // Is there a map for the given URL? 1873 var actions = o[{'delete': 'del'}[p.method] || p.method] || {}; 1874 1875 // Extrapolate the QueryString 1876 // Provide a clean path 1877 // Move the querystring into the data 1878 if (p.method === 'get') { 1879 1880 var query = url.split(/[\?#]/)[1]; 1881 if (query) { 1882 utils.extend(p.query, utils.param(query)); 1883 1884 // Remove the query part from the URL 1885 url = url.replace(/\?.*?(#|$)/, '$1'); 1886 } 1887 } 1888 1889 // Is the hash fragment defined 1890 if ((m = url.match(/#(.+)/, ''))) { 1891 url = url.split('#')[0]; 1892 p.path = m[1]; 1893 } 1894 else if (url in actions) { 1895 p.path = url; 1896 url = actions[url]; 1897 } 1898 else if ('default' in actions) { 1899 url = actions['default']; 1900 } 1901 1902 // Redirect Handler 1903 // This defines for the Form+Iframe+Hash hack where to return the results too. 1904 p.redirect_uri = _this.settings.redirect_uri; 1905 1906 // Define FormatHandler 1907 // The request can be procesed in a multitude of ways 1908 // Here's the options - depending on the browser and endpoint 1909 p.xhr = o.xhr; 1910 p.jsonp = o.jsonp; 1911 p.form = o.form; 1912 1913 // Make request 1914 if (typeof (url) === 'function') { 1915 // Does self have its own callback? 1916 url(p, getPath); 1917 } 1918 else { 1919 // Else the URL is a string 1920 getPath(url); 1921 } 1922 1923 return promise.proxy; 1924 1925 // If url needs a base 1926 // Wrap everything in 1927 function getPath(url) { 1928 1929 // Format the string if it needs it 1930 url = url.replace(/\@\{([a-z\_\-]+)(\|.*?)?\}/gi, function(m, key, defaults) { 1931 var val = defaults ? defaults.replace(/^\|/, '') : ''; 1932 if (key in p.query) { 1933 val = p.query[key]; 1934 delete p.query[key]; 1935 } 1936 else if (p.data && key in p.data) { 1937 val = p.data[key]; 1938 delete p.data[key]; 1939 } 1940 else if (!defaults) { 1941 promise.reject(error('missing_attribute', 'The attribute ' + key + ' is missing from the request')); 1942 } 1943 1944 return val; 1945 }); 1946 1947 // Add base 1948 if (!url.match(/^https?:\/\//)) { 1949 url = o.base + url; 1950 } 1951 1952 // Define the request URL 1953 p.url = url; 1954 1955 // Make the HTTP request with the curated request object 1956 // CALLBACK HANDLER 1957 // @ response object 1958 // @ statusCode integer if available 1959 utils.request(p, function(r, headers) { 1960 1961 // Is this a raw response? 1962 if (!p.formatResponse) { 1963 // Bad request? error statusCode or otherwise contains an error response vis JSONP? 1964 if (typeof headers === 'object' ? (headers.statusCode >= 400) : (typeof r === 'object' && 'error' in r)) { 1965 promise.reject(r); 1966 } 1967 else { 1968 promise.fulfill(r); 1969 } 1970 1971 return; 1972 } 1973 1974 // Should this be an object 1975 if (r === true) { 1976 r = {success:true}; 1977 } 1978 else if (!r) { 1979 r = {}; 1980 } 1981 1982 // The delete callback needs a better response 1983 if (p.method === 'delete') { 1984 r = (!r || utils.isEmpty(r)) ? {success:true} : r; 1985 } 1986 1987 // FORMAT RESPONSE? 1988 // Does self request have a corresponding formatter 1989 if (o.wrap && ((p.path in o.wrap) || ('default' in o.wrap))) { 1990 var wrap = (p.path in o.wrap ? p.path : 'default'); 1991 var time = (new Date()).getTime(); 1992 1993 // FORMAT RESPONSE 1994 var b = o.wrap[wrap](r, headers, p); 1995 1996 // Has the response been utterly overwritten? 1997 // Typically self augments the existing object.. but for those rare occassions 1998 if (b) { 1999 r = b; 2000 } 2001 } 2002 2003 // Is there a next_page defined in the response? 2004 if (r && 'paging' in r && r.paging.next) { 2005 2006 // Add the relative path if it is missing from the paging/next path 2007 if (r.paging.next[0] === '?') { 2008 r.paging.next = p.path + r.paging.next; 2009 } 2010 2011 // The relative path has been defined, lets markup the handler in the HashFragment 2012 else { 2013 r.paging.next += '#' + p.path; 2014 } 2015 } 2016 2017 // Dispatch to listeners 2018 // Emit events which pertain to the formatted response 2019 if (!r || 'error' in r) { 2020 promise.reject(r); 2021 } 2022 else { 2023 promise.fulfill(r); 2024 } 2025 }); 2026 } 2027}; 2028 2029// API utilities 2030hello.utils.extend(hello.utils, { 2031 2032 // Make an HTTP request 2033 request: function(p, callback) { 2034 2035 var _this = this; 2036 var error = _this.error; 2037 2038 // This has to go through a POST request 2039 if (!_this.isEmpty(p.data) && !('FileList' in window) && _this.hasBinary(p.data)) { 2040 2041 // Disable XHR and JSONP 2042 p.xhr = false; 2043 p.jsonp = false; 2044 } 2045 2046 // Check if the browser and service support CORS 2047 var cors = this.request_cors(function() { 2048 // If it does then run this... 2049 return ((p.xhr === undefined) || (p.xhr && (typeof (p.xhr) !== 'function' || p.xhr(p, p.query)))); 2050 }); 2051 2052 if (cors) { 2053 2054 formatUrl(p, function(url) { 2055 2056 var x = _this.xhr(p.method, url, p.headers, p.data, callback); 2057 x.onprogress = p.onprogress || null; 2058 2059 // Windows Phone does not support xhr.upload, see #74 2060 // Feature detect 2061 if (x.upload && p.onuploadprogress) { 2062 x.upload.onprogress = p.onuploadprogress; 2063 } 2064 2065 }); 2066 2067 return; 2068 } 2069 2070 // Clone the query object 2071 // Each request modifies the query object and needs to be tared after each one. 2072 var _query = p.query; 2073 2074 p.query = _this.clone(p.query); 2075 2076 // Assign a new callbackID 2077 p.callbackID = _this.globalEvent(); 2078 2079 // JSONP 2080 if (p.jsonp !== false) { 2081 2082 // Clone the query object 2083 p.query.callback = p.callbackID; 2084 2085 // If the JSONP is a function then run it 2086 if (typeof (p.jsonp) === 'function') { 2087 p.jsonp(p, p.query); 2088 } 2089 2090 // Lets use JSONP if the method is 'get' 2091 if (p.method === 'get') { 2092 2093 formatUrl(p, function(url) { 2094 _this.jsonp(url, callback, p.callbackID, p.timeout); 2095 }); 2096 2097 return; 2098 } 2099 else { 2100 // It's not compatible reset query 2101 p.query = _query; 2102 } 2103 2104 } 2105 2106 // Otherwise we're on to the old school, iframe hacks and JSONP 2107 if (p.form !== false) { 2108 2109 // Add some additional query parameters to the URL 2110 // We're pretty stuffed if the endpoint doesn't like these 2111 p.query.redirect_uri = p.redirect_uri; 2112 p.query.state = JSON.stringify({callback:p.callbackID}); 2113 2114 var opts; 2115 2116 if (typeof (p.form) === 'function') { 2117 2118 // Format the request 2119 opts = p.form(p, p.query); 2120 } 2121 2122 if (p.method === 'post' && opts !== false) { 2123 2124 formatUrl(p, function(url) { 2125 _this.post(url, p.data, opts, callback, p.callbackID, p.timeout); 2126 }); 2127 2128 return; 2129 } 2130 } 2131 2132 // None of the methods were successful throw an error 2133 callback(error('invalid_request', 'There was no mechanism for handling this request')); 2134 2135 return; 2136 2137 // Format URL 2138 // Constructs the request URL, optionally wraps the URL through a call to a proxy server 2139 // Returns the formatted URL 2140 function formatUrl(p, callback) { 2141 2142 // Are we signing the request? 2143 var sign; 2144 2145 // OAuth1 2146 // Remove the token from the query before signing 2147 if (p.authResponse && p.authResponse.oauth && parseInt(p.authResponse.oauth.version, 10) === 1) { 2148 2149 // OAUTH SIGNING PROXY 2150 sign = p.query.access_token; 2151 2152 // Remove the access_token 2153 delete p.query.access_token; 2154 2155 // Enfore use of Proxy 2156 p.proxy = true; 2157 } 2158 2159 // POST body to querystring 2160 if (p.data && (p.method === 'get' || p.method === 'delete')) { 2161 // Attach the p.data to the querystring. 2162 _this.extend(p.query, p.data); 2163 p.data = null; 2164 } 2165 2166 // Construct the path 2167 var path = _this.qs(p.url, p.query); 2168 2169 // Proxy the request through a server 2170 // Used for signing OAuth1 2171 // And circumventing services without Access-Control Headers 2172 if (p.proxy) { 2173 // Use the proxy as a path 2174 path = _this.qs(p.oauth_proxy, { 2175 path: path, 2176 access_token: sign || '', 2177 2178 // This will prompt the request to be signed as though it is OAuth1 2179 then: p.proxy_response_type || (p.method.toLowerCase() === 'get' ? 'redirect' : 'proxy'), 2180 method: p.method.toLowerCase(), 2181 suppress_response_codes:
vendor: 12,259 bytes, lines 2181-2662
2181 true 2182 }); 2183 } 2184 2185 callback(path); 2186 } 2187 }, 2188 2189 // Test whether the browser supports the CORS response 2190 request_cors: function(callback) { 2191 return 'withCredentials' in new XMLHttpRequest() && callback(); 2192 }, 2193 2194 // Return the type of DOM object 2195 domInstance: function(type, data) { 2196 var test = 'HTML' + (type || '').replace( 2197 /^[a-z]/, 2198 function(m) { 2199 return m.toUpperCase(); 2200 } 2201 2202 ) + 'Element'; 2203 2204 if (!data) { 2205 return false; 2206 } 2207 2208 if (window[test]) { 2209 return data instanceof window[test]; 2210 } 2211 else if (window.Element) { 2212 return data instanceof window.Element && (!type || (data.tagName && data.tagName.toLowerCase() === type)); 2213 } 2214 else { 2215 return (!(data instanceof Object || data instanceof Array || data instanceof String || data instanceof Number) && data.tagName && data.tagName.toLowerCase() === type); 2216 } 2217 }, 2218 2219 // Create a clone of an object 2220 clone: function(obj) { 2221 // Does not clone DOM elements, nor Binary data, e.g. Blobs, Filelists 2222 if (obj === null || typeof (obj) !== 'object' || obj instanceof Date || 'nodeName' in obj || this.isBinary(obj) || (typeof FormData === 'function' && obj instanceof FormData)) { 2223 return obj; 2224 } 2225 2226 if (Array.isArray(obj)) { 2227 // Clone each item in the array 2228 return obj.map(this.clone.bind(this)); 2229 } 2230 2231 // But does clone everything else. 2232 var clone = {}; 2233 for (var x in obj) { 2234 clone[x] = this.clone(obj[x]); 2235 } 2236 2237 return clone; 2238 }, 2239 2240 // XHR: uses CORS to make requests 2241 xhr: function(method, url, headers, data, callback) { 2242 2243 var r = new XMLHttpRequest(); 2244 var error = this.error; 2245 2246 // Binary? 2247 var binary = false; 2248 if (method === 'blob') { 2249 binary = method; 2250 method = 'GET'; 2251 } 2252 2253 method = method.toUpperCase(); 2254 2255 // Xhr.responseType 'json' is not supported in any of the vendors yet. 2256 r.onload = function(e) { 2257 var json = r.response; 2258 try { 2259 json = JSON.parse(r.responseText); 2260 } 2261 catch (_e) { 2262 if (r.status === 401) { 2263 json = error('access_denied', r.statusText); 2264 } 2265 } 2266 2267 var headers = headersToJSON(r.getAllResponseHeaders()); 2268 headers.statusCode = r.status; 2269 2270 callback(json || (method === 'GET' ? error('empty_response', 'Could not get resource') : {}), headers); 2271 }; 2272 2273 r.onerror = function(e) { 2274 var json = r.responseText; 2275 try { 2276 json = JSON.parse(r.responseText); 2277 } 2278 catch (_e) {} 2279 2280 callback(json || error('access_denied', 'Could not get resource')); 2281 }; 2282 2283 var x; 2284 2285 // Should we add the query to the URL? 2286 if (method === 'GET' || method === 'DELETE') { 2287 data = null; 2288 } 2289 else if (data && typeof (data) !== 'string' && !(data instanceof FormData) && !(data instanceof File) && !(data instanceof Blob)) { 2290 // Loop through and add formData 2291 var f = new FormData(); 2292 for (x in data) if (data.hasOwnProperty(x)) { 2293 if (data[x] instanceof HTMLInputElement) { 2294 if ('files' in data[x] && data[x].files.length > 0) { 2295 f.append(x, data[x].files[0]); 2296 } 2297 } 2298 else if (data[x] instanceof Blob) { 2299 f.append(x, data[x], data.name); 2300 } 2301 else { 2302 f.append(x, data[x]); 2303 } 2304 } 2305 2306 data = f; 2307 } 2308 2309 // Open the path, async 2310 r.open(method, url, true); 2311 2312 if (binary) { 2313 if ('responseType' in r) { 2314 r.responseType = binary; 2315 } 2316 else { 2317 r.overrideMimeType('text/plain; charset=x-user-defined'); 2318 } 2319 } 2320 2321 // Set any bespoke headers 2322 if (headers) { 2323 for (x in headers) { 2324 r.setRequestHeader(x, headers[x]); 2325 } 2326 } 2327 2328 r.send(data); 2329 2330 return r; 2331 2332 // Headers are returned as a string 2333 function headersToJSON(s) { 2334 var r = {}; 2335 var reg = /([a-z\-]+):\s?(.*);?/gi; 2336 var m; 2337 while ((m = reg.exec(s))) { 2338 r[m[1]] = m[2]; 2339 } 2340 2341 return r; 2342 } 2343 }, 2344 2345 // JSONP 2346 // Injects a script tag into the DOM to be executed and appends a callback function to the window object 2347 // @param string/function pathFunc either a string of the URL or a callback function pathFunc(querystringhash, continueFunc); 2348 // @param function callback a function to call on completion; 2349 jsonp: function(url, callback, callbackID, timeout) { 2350 2351 var _this = this; 2352 var error = _this.error; 2353 2354 // Change the name of the callback 2355 var bool = 0; 2356 var head = document.getElementsByTagName('head')[0]; 2357 var operaFix; 2358 var result = error('server_error', 'server_error'); 2359 var cb = function() { 2360 if (!(bool++)) { 2361 window.setTimeout(function() { 2362 callback(result); 2363 head.removeChild(script); 2364 }, 0); 2365 } 2366 2367 }; 2368 2369 // Add callback to the window object 2370 callbackID = _this.globalEvent(function(json) { 2371 result = json; 2372 return true; 2373 2374 // Mark callback as done 2375 }, callbackID); 2376 2377 // The URL is a function for some cases and as such 2378 // Determine its value with a callback containing the new parameters of this function. 2379 url = url.replace(new RegExp('=\\?(&|$)'), '=' + callbackID + '$1'); 2380 2381 // Build script tag 2382 var script = _this.append('script', { 2383 id: callbackID, 2384 name: callbackID, 2385 src: url, 2386 async: true, 2387 onload: cb, 2388 onerror: cb, 2389 onreadystatechange: function() { 2390 if (/loaded|complete/i.test(this.readyState)) { 2391 cb(); 2392 } 2393 } 2394 }); 2395 2396 // Opera fix error 2397 // Problem: If an error occurs with script loading Opera fails to trigger the script.onerror handler we specified 2398 // 2399 // Fix: 2400 // By setting the request to synchronous we can trigger the error handler when all else fails. 2401 // This action will be ignored if we've already called the callback handler "cb" with a successful onload event 2402 if (window.navigator.userAgent.toLowerCase().indexOf('opera') > -1) { 2403 operaFix = _this.append('script', { 2404 text: 'document.getElementById(\'' + callbackID + '\').onerror();' 2405 }); 2406 script.async = false; 2407 } 2408 2409 // Add timeout 2410 if (timeout) { 2411 window.setTimeout(function() { 2412 result = error('timeout', 'timeout'); 2413 cb(); 2414 }, timeout); 2415 } 2416 2417 // TODO: add fix for IE, 2418 // However: unable recreate the bug of firing off the onreadystatechange before the script content has been executed and the value of "result" has been defined. 2419 // Inject script tag into the head element 2420 head.appendChild(script); 2421 2422 // Append Opera Fix to run after our script 2423 if (operaFix) { 2424 head.appendChild(operaFix); 2425 } 2426 }, 2427 2428 // Post 2429 // Send information to a remote location using the post mechanism 2430 // @param string uri path 2431 // @param object data, key value data to send 2432 // @param function callback, function to execute in response 2433 post: function(url, data, options, callback, callbackID, timeout) { 2434 2435 var _this = this; 2436 var error = _this.error; 2437 var doc = document; 2438 2439 // This hack needs a form 2440 var form = null; 2441 var reenableAfterSubmit = []; 2442 var newform; 2443 var i = 0; 2444 var x = null; 2445 var bool = 0; 2446 var cb = function(r) { 2447 if (!(bool++)) { 2448 callback(r); 2449 } 2450 }; 2451 2452 // What is the name of the callback to contain 2453 // We'll also use this to name the iframe 2454 _this.globalEvent(cb, callbackID); 2455 2456 // Build the iframe window 2457 var win; 2458 try { 2459 // IE7 hack, only lets us define the name here, not later. 2460 win = doc.createElement('<iframe name="' + callbackID + '">'); 2461 } 2462 catch (e) { 2463 win = doc.createElement('iframe'); 2464 } 2465 2466 win.name = callbackID; 2467 win.id = callbackID; 2468 win.style.display = 'none'; 2469 2470 // Override callback mechanism. Triggger a response onload/onerror 2471 if (options && options.callbackonload) { 2472 // Onload is being fired twice 2473 win.onload = function() { 2474 cb({ 2475 response: 'posted', 2476 message: 'Content was posted' 2477 }); 2478 }; 2479 } 2480 2481 if (timeout) { 2482 setTimeout(function() { 2483 cb(error('timeout', 'The post operation timed out')); 2484 }, timeout); 2485 } 2486 2487 doc.body.appendChild(win); 2488 2489 // If we are just posting a single item 2490 if (_this.domInstance('form', data)) { 2491 // Get the parent form 2492 form = data.form; 2493 2494 // Loop through and disable all of its siblings 2495 for (i = 0; i < form.elements.length; i++) { 2496 if (form.elements[i] !== data) { 2497 form.elements[i].setAttribute('disabled', true); 2498 } 2499 } 2500 2501 // Move the focus to the form 2502 data = form; 2503 } 2504 2505 // Posting a form 2506 if (_this.domInstance('form', data)) { 2507 // This is a form element 2508 form = data; 2509 2510 // Does this form need to be a multipart form? 2511 for (i = 0; i < form.elements.length; i++) { 2512 if (!form.elements[i].disabled && form.elements[i].type === 'file') { 2513 form.encoding = form.enctype = 'multipart/form-data'; 2514 form.elements[i].setAttribute('name', 'file'); 2515 } 2516 } 2517 } 2518 else { 2519 // Its not a form element, 2520 // Therefore it must be a JSON object of Key=>Value or Key=>Element 2521 // If anyone of those values are a input type=file we shall shall insert its siblings into the form for which it belongs. 2522 for (x in data) if (data.hasOwnProperty(x)) { 2523 // Is this an input Element? 2524 if (_this.domInstance('input', data[x]) && data[x].type === 'file') { 2525 form = data[x].form; 2526 form.encoding = form.enctype = 'multipart/form-data'; 2527 } 2528 } 2529 2530 // Do If there is no defined form element, lets create one. 2531 if (!form) { 2532 // Build form 2533 form = doc.createElement('form'); 2534 doc.body.appendChild(form); 2535 newform = form; 2536 } 2537 2538 var input; 2539 2540 // Add elements to the form if they dont exist 2541 for (x in data) if (data.hasOwnProperty(x)) { 2542 2543 // Is this an element? 2544 var el = (_this.domInstance('input', data[x]) || _this.domInstance('textArea', data[x]) || _this.domInstance('select', data[x])); 2545 2546 // Is this not an input element, or one that exists outside the form. 2547 if (!el || data[x].form !== form) { 2548 2549 // Does an element have the same name? 2550 var inputs = form.elements[x]; 2551 if (input) { 2552 // Remove it. 2553 if (!(inputs instanceof NodeList)) { 2554 inputs = [inputs]; 2555 } 2556 2557 for (i = 0; i < inputs.length; i++) { 2558 inputs[i].parentNode.removeChild(inputs[i]); 2559 } 2560 2561 } 2562 2563 // Create an input element 2564 input = doc.createElement('input'); 2565 input.setAttribute('type', 'hidden'); 2566 input.setAttribute('name', x); 2567 2568 // Does it have a value attribute? 2569 if (el) { 2570 input.value = data[x].value; 2571 } 2572 else if (_this.domInstance(null, data[x])) { 2573 input.value = data[x].innerHTML || data[x].innerText; 2574 } 2575 else { 2576 input.value = data[x]; 2577 } 2578 2579 form.appendChild(input); 2580 } 2581 2582 // It is an element, which exists within the form, but the name is wrong 2583 else if (el && data[x].name !== x) { 2584 data[x].setAttribute('name', x); 2585 data[x].name = x; 2586 } 2587 } 2588 2589 // Disable elements from within the form if they weren't specified 2590 for (i = 0; i < form.elements.length; i++) { 2591 2592 input = form.elements[i]; 2593 2594 // Does the same name and value exist in the parent 2595 if (!(input.name in data) && input.getAttribute('disabled') !== true) { 2596 // Disable 2597 input.setAttribute('disabled', true); 2598 2599 // Add re-enable to callback 2600 reenableAfterSubmit.push(input); 2601 } 2602 } 2603 } 2604 2605 // Set the target of the form 2606 form.setAttribute('method', 'POST'); 2607 form.setAttribute('target', callbackID); 2608 form.target = callbackID; 2609 2610 // Update the form URL 2611 form.setAttribute('action', url); 2612 2613 // Submit the form 2614 // Some reason this needs to be offset from the current window execution 2615 setTimeout(function() { 2616 form.submit(); 2617 2618 setTimeout(function() { 2619 try { 2620 // Remove the iframe from the page. 2621 //win.parentNode.removeChild(win); 2622 // Remove the form 2623 if (newform) { 2624 newform.parentNode.removeChild(newform); 2625 } 2626 } 2627 catch (e) { 2628 try { 2629 console.error('HelloJS: could not remove iframe'); 2630 } 2631 catch (ee) {} 2632 } 2633 2634 // Reenable the disabled form 2635 for (var i = 0; i < reenableAfterSubmit.length; i++) { 2636 if (reenableAfterSubmit[i]) { 2637 reenableAfterSubmit[i].setAttribute('disabled', false); 2638 reenableAfterSubmit[i].disabled = false; 2639 } 2640 } 2641 }, 0); 2642 }, 100); 2643 }, 2644 2645 // Some of the providers require that only multipart is used with non-binary forms. 2646 // This function checks whether the form contains binary data 2647 hasBinary: function(data) { 2648 for (var x in data) if (data.hasOwnProperty(x)) { 2649 if (this.isBinary(data[x])) { 2650 return true; 2651 } 2652 } 2653 2654 return false; 2655 }, 2656 2657 // Determines if a variable Either Is or like a FormInput has the value of a Blob 2658 2659 isBinary: function(data) { 2660 2661 return data instanceof Object && ( 2662
vendor: 36,029 bytes, lines 2662-4199
2662(this.domInstance('input', data) && data.type === 'file') || 2663 ('FileList' in window && data instanceof window.FileList) || 2664 ('File' in window && data instanceof window.File) || 2665 ('Blob' in window && data instanceof window.Blob)); 2666 2667 }, 2668 2669 // Convert Data-URI to Blob string 2670 toBlob: function(dataURI) { 2671 var reg = /^data\:([^;,]+(\;charset=[^;,]+)?)(\;base64)?,/i; 2672 var m = dataURI.match(reg); 2673 if (!m) { 2674 return dataURI; 2675 } 2676 2677 var binary = atob(dataURI.replace(reg, '')); 2678 var array = []; 2679 for (var i = 0; i < binary.length; i++) { 2680 array.push(binary.charCodeAt(i)); 2681 } 2682 2683 return new Blob([new Uint8Array(array)], {type: m[1]}); 2684 } 2685 2686}); 2687 2688// EXTRA: Convert FormElement to JSON for POSTing 2689// Wrappers to add additional functionality to existing functions 2690(function(hello) { 2691 2692 // Copy original function 2693 var api = hello.api; 2694 var utils = hello.utils; 2695 2696 utils.extend(utils, { 2697 2698 // DataToJSON 2699 // This takes a FormElement|NodeList|InputElement|MixedObjects and convers the data object to JSON. 2700 dataToJSON: function(p) { 2701 2702 var _this = this; 2703 var w = window; 2704 var data = p.data; 2705 2706 // Is data a form object 2707 if (_this.domInstance('form', data)) { 2708 data = _this.nodeListToJSON(data.elements); 2709 } 2710 else if ('NodeList' in w && data instanceof NodeList) { 2711 data = _this.nodeListToJSON(data); 2712 } 2713 else if (_this.domInstance('input', data)) { 2714 data = _this.nodeListToJSON([data]); 2715 } 2716 2717 // Is data a blob, File, FileList? 2718 if (('File' in w && data instanceof w.File) || 2719 ('Blob' in w && data instanceof w.Blob) || 2720 ('FileList' in w && data instanceof w.FileList)) { 2721 data = {file: data}; 2722 } 2723 2724 // Loop through data if it's not form data it must now be a JSON object 2725 if (!('FormData' in w && data instanceof w.FormData)) { 2726 2727 for (var x in data) if (data.hasOwnProperty(x)) { 2728 2729 if ('FileList' in w && data[x] instanceof w.FileList) { 2730 if (data[x].length === 1) { 2731 data[x] = data[x][0]; 2732 } 2733 } 2734 else if (_this.domInstance('input', data[x]) && data[x].type === 'file') { 2735 continue; 2736 } 2737 else if (_this.domInstance('input', data[x]) || 2738 _this.domInstance('select', data[x]) || 2739 _this.domInstance('textArea', data[x])) { 2740 data[x] = data[x].value; 2741 } 2742 else if (_this.domInstance(null, data[x])) { 2743 data[x] = data[x].innerHTML || data[x].innerText; 2744 } 2745 } 2746 } 2747 2748 p.data = data; 2749 return data; 2750 }, 2751 2752 // NodeListToJSON 2753 // Given a list of elements extrapolate their values and return as a json object 2754 nodeListToJSON: function(nodelist) { 2755 2756 var json = {}; 2757 2758 // Create a data string 2759 for (var i = 0; i < nodelist.length; i++) { 2760 2761 var input = nodelist[i]; 2762 2763 // If the name of the input is empty or diabled, dont add it. 2764 if (input.disabled || !input.name) { 2765 continue; 2766 } 2767 2768 // Is this a file, does the browser not support 'files' and 'FormData'? 2769 if (input.type === 'file') { 2770 json[input.name] = input; 2771 } 2772 else { 2773 json[input.name] = input.value || input.innerHTML; 2774 } 2775 } 2776 2777 return json; 2778 } 2779 }); 2780 2781 // Replace it 2782 hello.api = function() { 2783 2784 // Get arguments 2785 var p = utils.args({path: 's!', method: 's', data:'o', timeout: 'i', callback: 'f'}, arguments); 2786 2787 // Change for into a data object 2788 if (p.data) { 2789 utils.dataToJSON(p); 2790 } 2791 2792 return api.call(this, p); 2793 }; 2794 2795})(hello); 2796 2797///////////////////////////////////// 2798// 2799// Save any access token that is in the current page URL 2800// Handle any response solicited through iframe hash tag following an API request 2801// 2802///////////////////////////////////// 2803 2804hello.utils.responseHandler(window, window.opener || window.parent); 2805 2806// Script to support ChromeApps 2807// This overides the hello.utils.popup method to support chrome.identity.launchWebAuthFlow 2808// See https://developer.chrome.com/apps/app_identity#non 2809 2810// Is this a chrome app? 2811 2812if (typeof chrome === 'object' && typeof chrome.identity === 'object' && chrome.identity.launchWebAuthFlow) { 2813 2814 (function() { 2815 2816 // Swap the popup method 2817 hello.utils.popup = function(url) { 2818 2819 return _open(url, true); 2820 2821 }; 2822 2823 // Swap the hidden iframe method 2824 hello.utils.iframe = function(url) { 2825 2826 _open(url, false); 2827 2828 }; 2829 2830 // Swap the request_cors method 2831 hello.utils.request_cors = function(callback) { 2832 2833 callback(); 2834 2835 // Always run as CORS 2836 2837 return true; 2838 }; 2839 2840 // Swap the storage method 2841 var _cache = {}; 2842 chrome.storage.local.get('hello', function(r) { 2843 // Update the cache 2844 _cache = r.hello || {}; 2845 }); 2846 2847 hello.utils.store = function(name, value) { 2848 2849 // Get all 2850 if (arguments.length === 0) { 2851 return _cache; 2852 } 2853 2854 // Get 2855 if (arguments.length === 1) { 2856 return _cache[name] || null; 2857 } 2858 2859 // Set 2860 if (value) { 2861 _cache[name] = value; 2862 chrome.storage.local.set({hello: _cache}); 2863 return value; 2864 } 2865 2866 // Delete 2867 if (value === null) { 2868 delete _cache[name]; 2869 chrome.storage.local.set({hello: _cache}); 2870 return null; 2871 } 2872 }; 2873 2874 // Open function 2875 function _open(url, interactive) { 2876 2877 // Launch 2878 var ref = { 2879 closed: false 2880 }; 2881 2882 // Launch the webAuthFlow 2883 chrome.identity.launchWebAuthFlow({ 2884 url: url, 2885 interactive: interactive 2886 }, function(responseUrl) { 2887 2888 // Did the user cancel this prematurely 2889 if (responseUrl === undefined) { 2890 ref.closed = true; 2891 return; 2892 } 2893 2894 // Split appart the URL 2895 var a = hello.utils.url(responseUrl); 2896 2897 // The location can be augmented in to a location object like so... 2898 // We dont have window operations on the popup so lets create some 2899 var _popup = { 2900 location: { 2901 2902 // Change the location of the popup 2903 assign: function(url) { 2904 2905 // If there is a secondary reassign 2906 // In the case of OAuth1 2907 // Trigger this in non-interactive mode. 2908 _open(url, false); 2909 }, 2910 2911 search: a.search, 2912 hash: a.hash, 2913 href: a.href 2914 }, 2915 close: function() {} 2916 }; 2917 2918 // Then this URL contains information which HelloJS must process 2919 // URL string 2920 // Window - any action such as window relocation goes here 2921 // Opener - the parent window which opened this, aka this script 2922 2923 hello.utils.responseHandler(_popup, window); 2924 }); 2925 2926 // Return the reference 2927 return ref; 2928 } 2929 2930 })(); 2931} 2932 2933// Phonegap override for hello.phonegap.js 2934(function() { 2935 2936 // Is this a phonegap implementation? 2937 if (!(/^file:\/{3}[^\/]/.test(window.location.href) && window.cordova)) { 2938 // Cordova is not included. 2939 return; 2940 } 2941 2942 // Augment the hidden iframe method 2943 hello.utils.iframe = function(url, redirectUri) { 2944 hello.utils.popup(url, redirectUri, {hidden: 'yes'}); 2945 }; 2946 2947 // Augment the popup 2948 var utilPopup = hello.utils.popup; 2949 2950 // Replace popup 2951 hello.utils.popup = function(url, redirectUri, options) { 2952 2953 // Run the standard 2954 var popup = utilPopup.call(this, url, redirectUri, options); 2955 2956 // Create a function for reopening the popup, and assigning events to the new popup object 2957 // PhoneGap support 2958 // Add an event listener to listen to the change in the popup windows URL 2959 // This must appear before popup.focus(); 2960 try { 2961 if (popup && popup.addEventListener) { 2962 2963 // Get the origin of the redirect URI 2964 2965 var a = hello.utils.url(redirectUri); 2966 var redirectUriOrigin = a.origin || (a.protocol + '//' + a.hostname); 2967 2968 // Listen to changes in the InAppBrowser window 2969 2970 popup.addEventListener('loadstart', function(e) { 2971 2972 var url = e.url; 2973 2974 // Is this the path, as given by the redirectUri? 2975 // Check the new URL agains the redirectUriOrigin. 2976 // According to #63 a user could click 'cancel' in some dialog boxes .... 2977 // The popup redirects to another page with the same origin, yet we still wish it to close. 2978 2979 if (url.indexOf(redirectUriOrigin) !== 0) { 2980 return; 2981 } 2982 2983 // Split appart the URL 2984 var a = hello.utils.url(url); 2985 2986 // We dont have window operations on the popup so lets create some 2987 // The location can be augmented in to a location object like so... 2988 2989 var _popup = { 2990 location: { 2991 // Change the location of the popup 2992 assign: function(location) { 2993 2994 // Unfourtunatly an app is may not change the location of a InAppBrowser window. 2995 // So to shim this, just open a new one. 2996 popup.executeScript({code: 'window.location.href = "' + location + ';"'}); 2997 }, 2998 2999 search: a.search, 3000 hash: a.hash, 3001 href: a.href 3002 }, 3003 close: function() { 3004 if (popup.close) { 3005 popup.close(); 3006 try { 3007 popup.closed = true; 3008 } 3009 catch (_e) {} 3010 } 3011 } 3012 }; 3013 3014 // Then this URL contains information which HelloJS must process 3015 // URL string 3016 // Window - any action such as window relocation goes here 3017 // Opener - the parent window which opened this, aka this script 3018 3019 hello.utils.responseHandler(_popup, window); 3020 3021 }); 3022 } 3023 } 3024 catch (e) {} 3025 3026 return popup; 3027 }; 3028 3029})(); 3030 3031(function(hello) { 3032 3033 // OAuth1 3034 var OAuth1Settings = { 3035 version: '1.0', 3036 auth: 'https://www.dropbox.com/1/oauth/authorize', 3037 request: 'https://api.dropbox.com/1/oauth/request_token', 3038 token: 'https://api.dropbox.com/1/oauth/access_token' 3039 }; 3040 3041 // OAuth2 Settings 3042 var OAuth2Settings = { 3043 version: 2, 3044 auth: 'https://www.dropbox.com/1/oauth2/authorize', 3045 grant: 'https://api.dropbox.com/1/oauth2/token' 3046 }; 3047 3048 // Initiate the Dropbox module 3049 hello.init({ 3050 3051 dropbox: { 3052 3053 name: 'Dropbox', 3054 3055 oauth: OAuth2Settings, 3056 3057 login: function(p) { 3058 // OAuth2 non-standard adjustments 3059 p.qs.scope = ''; 3060 3061 // Should this be run as OAuth1? 3062 // If the redirect_uri is is HTTP (non-secure) then its required to revert to the OAuth1 endpoints 3063 var redirect = decodeURIComponent(p.qs.redirect_uri); 3064 if (redirect.indexOf('http:') === 0 && redirect.indexOf('http://localhost/') !== 0) { 3065 3066 // Override the dropbox OAuth settings. 3067 hello.services.dropbox.oauth = OAuth1Settings; 3068 } 3069 else { 3070 // Override the dropbox OAuth settings. 3071 hello.services.dropbox.oauth = OAuth2Settings; 3072 } 3073 3074 // The dropbox login window is a different size 3075 p.options.popup.width = 1000; 3076 p.options.popup.height = 1000; 3077 }, 3078 3079 /* 3080 Dropbox does not allow insecure HTTP URI's in the redirect_uri field 3081 ...otherwise I'd love to use OAuth2 3082 3083 Follow request https://forums.dropbox.com/topic.php?id=106505 3084 3085 p.qs.response_type = 'code'; 3086 oauth: { 3087 version: 2, 3088 auth: 'https://www.dropbox.com/1/oauth2/authorize', 3089 grant: 'https://api.dropbox.com/1/oauth2/token' 3090 } 3091 */ 3092 3093 // API Base URL 3094 base: 'https://api.dropbox.com/1/', 3095 3096 // Bespoke setting: this is states whether to use the custom environment of Dropbox or to use their own environment 3097 // Because it's notoriously difficult for Dropbox too provide access from other webservices, this defaults to Sandbox 3098 root: 'sandbox', 3099 3100 // Map GET requests 3101 get: { 3102 me: 'account/info', 3103 3104 // Https://www.dropbox.com/developers/core/docs#metadata 3105 'me/files': req('metadata/auto/@{parent|}'), 3106 'me/folder': req('metadata/auto/@{id}'), 3107 'me/folders': req('metadata/auto/'), 3108 3109 'default': function(p, callback) { 3110 if (p.path.match('https://api-content.dropbox.com/1/files/')) { 3111 // This is a file, return binary data 3112 p.method = 'blob'; 3113 } 3114 3115 callback(p.path); 3116 } 3117 }, 3118 3119 post: { 3120 'me/files': function(p, callback) { 3121 3122 var path = p.data.parent; 3123 var fileName = p.data.name; 3124 3125 p.data = { 3126 file: p.data.file 3127 }; 3128 3129 // Does this have a data-uri to upload as a file? 3130 if (typeof (p.data.file) === 'string') { 3131 p.data.file = hello.utils.toBlob(p.data.file); 3132 } 3133 3134 callback('https://api-content.dropbox.com/1/files_put/auto/' + path + '/' + fileName); 3135 }, 3136 3137 'me/folders': function(p, callback) { 3138 3139 var name = p.data.name; 3140 p.data = {}; 3141 3142 callback('fileops/create_folder?root=@{root|sandbox}&' + hello.utils.param({ 3143 path: name 3144 })); 3145 } 3146 }, 3147 3148 // Map DELETE requests 3149 del: { 3150 'me/files': 'fileops/delete?root=@{root|sandbox}&path=@{id}', 3151 'me/folder': 'fileops/delete?root=@{root|sandbox}&path=@{id}' 3152 }, 3153 3154 wrap: { 3155 me: function(o) { 3156 formatError(o); 3157 if (!o.uid) { 3158 return o; 3159 } 3160 3161 o.name = o.display_name; 3162 var m = o.name.split(' '); 3163 o.first_name = m.shift(); 3164 o.last_name = m.join(' '); 3165 o.id = o.uid; 3166 delete o.uid; 3167 delete o.display_name; 3168 return o; 3169 }, 3170 3171 'default': function(o, headers, req) { 3172 formatError(o); 3173 if (o.is_dir && o.contents) { 3174 o.data = o.contents; 3175 delete o.contents; 3176 3177 o.data.forEach(function(item) { 3178 item.root = o.root; 3179 formatFile(item, headers, req); 3180 }); 3181 } 3182 3183 formatFile(o, headers, req); 3184 3185 if (o.is_deleted) { 3186 o.success = true; 3187 } 3188 3189 return o; 3190 } 3191 }, 3192 3193 // Doesn't return the CORS headers 3194 xhr: function(p) { 3195 3196 // The proxy supports allow-cross-origin-resource 3197 // Alas that's the only thing we're using. 3198 if (p.data && p.data.file) { 3199 var file = p.data.file; 3200 if (file) { 3201 if (file.files) { 3202 p.data = file.files[0]; 3203 } 3204 else { 3205 p.data = file; 3206 } 3207 } 3208 } 3209 3210 if (p.method === 'delete') { 3211 p.method = 'post'; 3212 } 3213 3214 return true; 3215 }, 3216 3217 form: function(p, qs) { 3218 delete qs.state; 3219 delete qs.redirect_uri; 3220 } 3221 } 3222 }); 3223 3224 function formatError(o) { 3225 if (o && 'error' in o) { 3226 o.error = { 3227 code: 'server_error', 3228 message: o.error.message || o.error 3229 }; 3230 } 3231 } 3232 3233 function formatFile(o, headers, req) { 3234 3235 if (typeof o !== 'object' || 3236 (typeof Blob !== 'undefined' && o instanceof Blob) || 3237 (typeof ArrayBuffer !== 'undefined' && o instanceof ArrayBuffer)) { 3238 // This is a file, let it through unformatted 3239 return; 3240 } 3241 3242 if ('error' in o) { 3243 return; 3244 } 3245 3246 var path = (o.root !== 'app_folder' ? o.root : '') + o.path.replace(/\&/g, '%26'); 3247 path = path.replace(/^\//, ''); 3248 if (o.thumb_exists) { 3249 o.thumbnail = req.oauth_proxy + '?path=' + 3250 encodeURIComponent('https://api-content.dropbox.com/1/thumbnails/auto/' + path + '?format=jpeg&size=m') + '&access_token=' + req.options.access_token; 3251 } 3252 3253 o.type = (o.is_dir ? 'folder' : o.mime_type); 3254 o.name = o.path.replace(/.*\//g, ''); 3255 if (o.is_dir) { 3256 o.files = path.replace(/^\//, ''); 3257 } 3258 else { 3259 o.downloadLink = hello.settings.oauth_proxy + '?path=' + 3260 encodeURIComponent('https://api-content.dropbox.com/1/files/auto/' + path) + '&access_token=' + req.options.access_token; 3261 o.file = 'https://api-content.dropbox.com/1/files/auto/' + path; 3262 } 3263 3264 if (!o.id) { 3265 o.id = o.path.replace(/^\//, ''); 3266 } 3267 3268 // O.media = 'https://api-content.dropbox.com/1/files/' + path; 3269 } 3270 3271 function req(str) { 3272 return function(p, cb) { 3273 delete p.query.limit; 3274 cb(str); 3275 }; 3276 } 3277 3278})(hello); 3279 3280(function(hello) { 3281 3282 hello.init({ 3283 3284 facebook: { 3285 3286 name: 'Facebook', 3287 3288 // SEE https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.1 3289 oauth: { 3290 version: 2, 3291 auth: 'https://www.facebook.com/dialog/oauth/', 3292 grant: 'https://graph.facebook.com/oauth/access_token' 3293 }, 3294 3295 // Authorization scopes 3296 scope: { 3297 basic: 'public_profile', 3298 email: 'email', 3299 share: 'user_posts', 3300 birthday: 'user_birthday', 3301 events: 'user_events', 3302 photos: 'user_photos', 3303 videos: 'user_videos', 3304 friends: 'user_friends', 3305 files: 'user_photos,user_videos', 3306 publish_files: 'user_photos,user_videos,publish_actions', 3307 publish: 'publish_actions', 3308 3309 // Deprecated in v2.0 3310 // Create_event : 'create_event', 3311 3312 offline_access: '' 3313 }, 3314 3315 // Refresh the access_token 3316 refresh: false, 3317 3318 login: function(p) { 3319 3320 // Reauthenticate 3321 // https://developers.facebook.com/docs/facebook-login/reauthentication 3322 if (p.options.force) { 3323 p.qs.auth_type = 'reauthenticate'; 3324 } 3325 3326 // Set the display value 3327 p.qs.display = p.options.display || 'popup'; 3328 }, 3329 3330 logout: function(callback, options) { 3331 // Assign callback to a global handler 3332 var callbackID = hello.utils.globalEvent(callback); 3333 var redirect = encodeURIComponent(hello.settings.redirect_uri + '?' + hello.utils.param({callback:callbackID, result: JSON.stringify({force:true}), state: '{}'})); 3334 var token = (options.authResponse || {}).access_token; 3335 hello.utils.iframe('https://www.facebook.com/logout.php?next=' + redirect + '&access_token=' + token); 3336 3337 // Possible responses: 3338 // String URL - hello.logout should handle the logout 3339 // Undefined - this function will handle the callback 3340 // True - throw a success, this callback isn't handling the callback 3341 // False - throw a error 3342 if (!token) { 3343 // If there isn't a token, the above wont return a response, so lets trigger a response 3344 return false; 3345 } 3346 }, 3347 3348 // API Base URL 3349 base: 'https://graph.facebook.com/v2.7/', 3350 3351 // Map GET requests 3352 get: { 3353 me: 'me?fields=email,first_name,last_name,name,timezone,verified', 3354 'me/friends': 'me/friends', 3355 'me/following': 'me/friends', 3356 'me/followers': 'me/friends', 3357 'me/share': 'me/feed', 3358 'me/like': 'me/likes', 3359 'me/files': 'me/albums', 3360 'me/albums': 'me/albums?fields=cover_photo,name', 3361 'me/album': '@{id}/photos?fields=picture', 3362 'me/photos': 'me/photos', 3363 'me/photo': '@{id}', 3364 'friend/albums': '@{id}/albums', 3365 'friend/photos': '@{id}/photos' 3366 3367 // Pagination 3368 // Https://developers.facebook.com/docs/reference/api/pagination/ 3369 }, 3370 3371 // Map POST requests 3372 post: { 3373 'me/share': 'me/feed', 3374 'me/photo': '@{id}' 3375 3376 // Https://developers.facebook.com/docs/graph-api/reference/v2.2/object/likes/ 3377 }, 3378 3379 wrap: { 3380 me: formatUser, 3381 'me/friends': formatFriends, 3382 'me/following': formatFriends, 3383 'me/followers': formatFriends, 3384 'me/albums': format, 3385 'me/photos': format, 3386 'me/files': format, 3387 'default': format 3388 }, 3389 3390 // Special requirements for handling XHR 3391 xhr: function(p, qs) { 3392 if (p.method === 'get' || p.method === 'post') { 3393 qs.suppress_response_codes = true; 3394 } 3395 3396 // Is this a post with a data-uri? 3397 if (p.method === 'post' && p.data && typeof (p.data.file) === 'string') { 3398 // Convert the Data-URI to a Blob 3399 p.data.file = hello.utils.toBlob(p.data.file); 3400 } 3401 3402 return true; 3403 }, 3404 3405 // Special requirements for handling JSONP fallback 3406 jsonp: function(p, qs) { 3407 var m = p.method; 3408 if (m !== 'get' && !hello.utils.hasBinary(p.data)) { 3409 p.data.method = m; 3410 p.method = 'get'; 3411 } 3412 else if (p.method === 'delete') { 3413 qs.method = 'delete'; 3414 p.method = 'post'; 3415 } 3416 }, 3417 3418 // Special requirements for iframe form hack 3419 form: function(p) { 3420 return { 3421 // Fire the callback onload 3422 callbackonload: true 3423 }; 3424 } 3425 } 3426 }); 3427 3428 var base = 'https://graph.facebook.com/'; 3429 3430 function formatUser(o) { 3431 if (o.id) { 3432 o.thumbnail = o.picture = 'https://graph.facebook.com/' + o.id + '/picture'; 3433 } 3434 3435 return o; 3436 } 3437 3438 function formatFriends(o) { 3439 if ('data' in o) { 3440 o.data.forEach(formatUser); 3441 } 3442 3443 return o; 3444 } 3445 3446 function format(o, headers, req) { 3447 if (typeof o === 'boolean') { 3448 o = {success: o}; 3449 } 3450 3451 if (o && 'data' in o) { 3452 var token = req.query.access_token; 3453 3454 if (!(o.data instanceof Array)) { 3455 var data = o.data; 3456 delete o.data; 3457 o.data = [data]; 3458 } 3459 3460 o.data.forEach(function(d) { 3461 3462 if (d.picture) { 3463 d.thumbnail = d.picture; 3464 } 3465 3466 d.pictures = (d.images || []) 3467 .sort(function(a, b) { 3468 return a.width - b.width; 3469 }); 3470 3471 if (d.cover_photo && d.cover_photo.id) { 3472 d.thumbnail = base + d.cover_photo.id + '/picture?access_token=' + token; 3473 } 3474 3475 if (d.type === 'album') { 3476 d.files = d.photos = base + d.id + '/photos'; 3477 } 3478 3479 if (d.can_upload) { 3480 d.upload_location = base + d.id + '/photos'; 3481 } 3482 }); 3483 } 3484 3485 return o; 3486 } 3487 3488})(hello); 3489 3490(function(hello) { 3491 3492 hello.init({ 3493 3494 flickr: { 3495 3496 name: 'Flickr', 3497 3498 // Ensure that you define an oauth_proxy 3499 oauth: { 3500 version: '1.0a', 3501 auth: 'https://www.flickr.com/services/oauth/authorize?perms=read', 3502 request: 'https://www.flickr.com/services/oauth/request_token', 3503 token: 'https://www.flickr.com/services/oauth/access_token' 3504 }, 3505 3506 // API base URL 3507 base: 'https://api.flickr.com/services/rest', 3508 3509 // Map GET resquests 3510 get: { 3511 me: sign('flickr.people.getInfo'), 3512 'me/friends': sign('flickr.contacts.getList', {per_page:'@{limit|50}'}), 3513 'me/following': sign('flickr.contacts.getList', {per_page:'@{limit|50}'}), 3514 'me/followers': sign('flickr.contacts.getList', {per_page:'@{limit|50}'}), 3515 'me/albums': sign('flickr.photosets.getList', {per_page:'@{limit|50}'}), 3516 'me/album': sign('flickr.photosets.getPhotos', {photoset_id: '@{id}'}), 3517 'me/photos': sign('flickr.people.getPhotos', {per_page:'@{limit|50}'}) 3518 }, 3519 3520 wrap: { 3521 me: function(o) { 3522 formatError(o); 3523 o = checkResponse(o, 'person'); 3524 if (o.id) { 3525 if (o.realname) { 3526 o.name = o.realname._content; 3527 var m = o.name.split(' '); 3528 o.first_name = m.shift(); 3529 o.last_name = m.join(' '); 3530 } 3531 3532 o.thumbnail = getBuddyIcon(o, 'l'); 3533 o.picture = getBuddyIcon(o, 'l'); 3534 } 3535 3536 return o; 3537 }, 3538 3539 'me/friends': formatFriends, 3540 'me/followers': formatFriends, 3541 'me/following': formatFriends, 3542 'me/albums': function(o) { 3543 formatError(o); 3544 o = checkResponse(o, 'photosets'); 3545 paging(o); 3546 if (o.photoset) { 3547 o.data = o.photoset; 3548 o.data.forEach(function(item) { 3549 item.name = item.title._content; 3550 item.photos = 'https://api.flickr.com/services/rest' + getApiUrl('flickr.photosets.getPhotos', {photoset_id: item.id}, true); 3551 }); 3552 3553 delete o.photoset; 3554 } 3555 3556 return o; 3557 }, 3558 3559 'me/photos': function(o) { 3560 formatError(o); 3561 return formatPhotos(o); 3562 }, 3563 3564 'default': function(o) { 3565 formatError(o); 3566 return formatPhotos(o); 3567 } 3568 }, 3569 3570 xhr: false, 3571 3572 jsonp: function(p, qs) { 3573 if (p.method == 'get') { 3574 delete qs.callback; 3575 qs.jsoncallback = p.callbackID; 3576 } 3577 } 3578 } 3579 }); 3580 3581 function getApiUrl(method, extraParams, skipNetwork) { 3582 var url = ((skipNetwork) ? '' : 'flickr:') + 3583 '?method=' + method + 3584 '&api_key=' + hello.services.flickr.id + 3585 '&format=json'; 3586 for (var param in extraParams) { 3587 if (extraParams.hasOwnProperty(param)) { 3588 url += '&' + param + '=' + extraParams[param]; 3589 } 3590 } 3591 3592 return url; 3593 } 3594 3595 // This is not exactly neat but avoid to call 3596 // The method 'flickr.test.login' for each api call 3597 3598 function withUser(cb) { 3599 var auth = hello.getAuthResponse('flickr'); 3600 cb(auth && auth.user_nsid ? auth.user_nsid : null); 3601 } 3602 3603 function sign(url, params) { 3604 if (!params) { 3605 params = {}; 3606 } 3607 3608 return function(p, callback) { 3609 withUser(function(userId) { 3610 params.user_id = userId; 3611 callback(getApiUrl(url, params, true)); 3612 }); 3613 }; 3614 } 3615 3616 function getBuddyIcon(profile, size) { 3617 var url = 'https://www.flickr.com/images/buddyicon.gif'; 3618 if (profile.nsid && profile.iconserver && profile.iconfarm) { 3619 url = 'https://farm' + profile.iconfarm + '.staticflickr.com/' + 3620 profile.iconserver + '/' + 3621 'buddyicons/' + profile.nsid + 3622 ((size) ? '_' + size : '') + '.jpg'; 3623 } 3624 3625 return url; 3626 } 3627 3628 // See: https://www.flickr.com/services/api/misc.urls.html 3629 function createPhotoUrl(id, farm, server, secret, size) { 3630 size = (size) ? '_' + size : ''; 3631 return 'https://farm' + farm + '.staticflickr.com/' + server + '/' + id + '_' + secret + size + '.jpg'; 3632 } 3633 3634 function formatUser(o) { 3635 } 3636 3637 function formatError(o) { 3638 if (o && o.stat && o.stat.toLowerCase() != 'ok') { 3639 o.error = { 3640 code: 'invalid_request', 3641 message: o.message 3642 }; 3643 } 3644 } 3645 3646 function formatPhotos(o) { 3647 if (o.photoset || o.photos) { 3648 var set = ('photoset' in o) ? 'photoset' : 'photos'; 3649 o = checkResponse(o, set); 3650 paging(o); 3651 o.data = o.photo; 3652 delete o.photo; 3653 for (var i = 0; i < o.data.length; i++) { 3654 var photo = o.data[i]; 3655 photo.name = photo.title; 3656 photo.picture = createPhotoUrl(photo.id, photo.farm, photo.server, photo.secret, ''); 3657 photo.pictures = createPictures(photo.id, photo.farm, photo.server, photo.secret); 3658 photo.source = createPhotoUrl(photo.id, photo.farm, photo.server, photo.secret, 'b'); 3659 photo.thumbnail = createPhotoUrl(photo.id, photo.farm, photo.server, photo.secret, 'm'); 3660 } 3661 } 3662 3663 return o; 3664 } 3665 3666 // See: https://www.flickr.com/services/api/misc.urls.html 3667 function createPictures(id, farm, server, secret) { 3668 3669 var NO_LIMIT = 2048; 3670 var sizes = [ 3671 {id: 't', max: 100}, 3672 {id: 'm', max: 240}, 3673 {id: 'n', max: 320}, 3674 {id: '', max: 500}, 3675 {id: 'z', max: 640}, 3676 {id: 'c', max: 800}, 3677 {id: 'b', max: 1024}, 3678 {id: 'h', max: 1600}, 3679 {id: 'k', max: 2048}, 3680 {id: 'o', max: NO_LIMIT} 3681 ]; 3682 3683 return sizes.map(function(size) { 3684 return { 3685 source: createPhotoUrl(id, farm, server, secret, size.id), 3686 3687 // Note: this is a guess that's almost certain to be wrong (unless square source) 3688 width: size.max, 3689 height: size.max 3690 }; 3691 }); 3692 } 3693 3694 function checkResponse(o, key) { 3695 3696 if (key in o) { 3697 o = o[key]; 3698 } 3699 else if (!('error' in o)) { 3700 o.error = { 3701 code: 'invalid_request', 3702 message: o.message || 'Failed to get data from Flickr' 3703 }; 3704 } 3705 3706 return o; 3707 } 3708 3709 function formatFriends(o) { 3710 formatError(o); 3711 if (o.contacts) { 3712 o = checkResponse(o, 'contacts'); 3713 paging(o); 3714 o.data = o.contact; 3715 delete o.contact; 3716 for (var i = 0; i < o.data.length; i++) { 3717 var item = o.data[i]; 3718 item.id = item.nsid; 3719 item.name = item.realname || item.username; 3720 item.thumbnail = getBuddyIcon(item, 'm'); 3721 } 3722 } 3723 3724 return o; 3725 } 3726 3727 function paging(res) { 3728 if (res.page && res.pages && res.page !== res.pages) { 3729 res.paging = { 3730 next: '?page=' + (++res.page) 3731 }; 3732 } 3733 } 3734 3735})(hello); 3736 3737(function(hello) { 3738 3739 hello.init({ 3740 3741 foursquare: { 3742 3743 name: 'Foursquare', 3744 3745 oauth: { 3746 // See: https://developer.foursquare.com/overview/auth 3747 version: 2, 3748 auth: 'https://foursquare.com/oauth2/authenticate', 3749 grant: 'https://foursquare.com/oauth2/access_token' 3750 }, 3751 3752 // Refresh the access_token once expired 3753 refresh: true, 3754 3755 base: 'https://api.foursquare.com/v2/', 3756 3757 get: { 3758 me: 'users/self', 3759 'me/friends': 'users/self/friends', 3760 'me/followers': 'users/self/friends', 3761 'me/following': 'users/self/friends' 3762 }, 3763 3764 wrap: { 3765 me: function(o) { 3766 formatError(o); 3767 if (o && o.response) { 3768 o = o.response.user; 3769 formatUser(o); 3770 } 3771 3772 return o; 3773 }, 3774 3775 'default': function(o) { 3776 formatError(o); 3777 3778 // Format friends 3779 if (o && 'response' in o && 'friends' in o.response && 'items' in o.response.friends) { 3780 o.data = o.response.friends.items; 3781 o.data.forEach(formatUser); 3782 delete o.response; 3783 } 3784 3785 return o; 3786 } 3787 }, 3788 3789 xhr: formatRequest, 3790 jsonp: formatRequest 3791 } 3792 }); 3793 3794 function formatError(o) { 3795 if (o.meta && (o.meta.code === 400 || o.meta.code === 401)) { 3796 o.error = { 3797 code: 'access_denied', 3798 message: o.meta.errorDetail 3799 }; 3800 } 3801 } 3802 3803 function formatUser(o) { 3804 if (o && o.id) { 3805 o.thumbnail = o.photo.prefix + '100x100' + o.photo.suffix; 3806 o.name = o.firstName + ' ' + o.lastName; 3807 o.first_name = o.firstName; 3808 o.last_name = o.lastName; 3809 if (o.contact) { 3810 if (o.contact.email) { 3811 o.email = o.contact.email; 3812 } 3813 } 3814 } 3815 } 3816 3817 function formatRequest(p, qs) { 3818 var token = qs.access_token; 3819 delete qs.access_token; 3820 qs.oauth_token = token; 3821 qs.v = 20121125; 3822 return true; 3823 } 3824 3825})(hello); 3826 3827(function(hello) { 3828 3829 hello.init({ 3830 3831 github: { 3832 3833 name: 'GitHub', 3834 3835 oauth: { 3836 version: 2, 3837 auth: 'https://github.com/login/oauth/authorize', 3838 grant: 'https://github.com/login/oauth/access_token', 3839 response_type: 'code' 3840 }, 3841 3842 scope: { 3843 email: 'user:email' 3844 }, 3845 3846 base: 'https://api.github.com/', 3847 3848 get: { 3849 me: 'user', 3850 'me/friends': 'user/following?per_page=@{limit|100}', 3851 'me/following': 'user/following?per_page=@{limit|100}', 3852 'me/followers': 'user/followers?per_page=@{limit|100}', 3853 'me/like': 'user/starred?per_page=@{limit|100}' 3854 }, 3855 3856 wrap: { 3857 me: function(o, headers) { 3858 3859 formatError(o, headers); 3860 formatUser(o); 3861 3862 return o; 3863 }, 3864 3865 'default': function(o, headers, req) { 3866 3867 formatError(o, headers); 3868 3869 if (Array.isArray(o)) { 3870 o = {data:o}; 3871 } 3872 3873 if (o.data) { 3874 paging(o, headers, req); 3875 o.data.forEach(formatUser); 3876 } 3877 3878 return o; 3879 } 3880 }, 3881 3882 xhr: function(p) { 3883 3884 if (p.method !== 'get' && p.data) { 3885 3886 // Serialize payload as JSON 3887 p.headers = p.headers || {}; 3888 p.headers['Content-Type'] = 'application/json'; 3889 if (typeof (p.data) === 'object') { 3890 p.data = JSON.stringify(p.data); 3891 } 3892 } 3893 3894 return true; 3895 } 3896 } 3897 }); 3898 3899 function formatError(o, headers) { 3900 var code = headers ? headers.statusCode : (o && 'meta' in o && 'status' in o.meta && o.meta.status); 3901 if ((code === 401 || code === 403)) { 3902 o.error = { 3903 code: 'access_denied', 3904 message: o.message || (o.data ? o.data.message : 'Could not get response') 3905 }; 3906 delete o.message; 3907 } 3908 } 3909 3910 function formatUser(o) { 3911 if (o.id) { 3912 o.thumbnail = o.picture = o.avatar_url; 3913 o.name = o.login; 3914 } 3915 } 3916 3917 function paging(res, headers, req) { 3918 if (res.data && res.data.length && headers && headers.Link) { 3919 var next = headers.Link.match(/<(.*?)>;\s*rel=\"next\"/); 3920 if (next) { 3921 res.paging = { 3922 next: next[1] 3923 }; 3924 } 3925 } 3926 } 3927 3928})(hello); 3929 3930(function(hello) { 3931 3932 var contactsUrl = 'https://www.google.com/m8/feeds/contacts/default/full?v=3.0&alt=json&max-results=@{limit|1000}&start-index=@{start|1}'; 3933 3934 hello.init({ 3935 3936 google: { 3937 3938 name: 'Google Plus', 3939 3940 // See: http://code.google.com/apis/accounts/docs/OAuth2UserAgent.html 3941 oauth: { 3942 version: 2, 3943 auth: 'https://accounts.google.com/o/oauth2/auth', 3944 grant: 'https://accounts.google.com/o/oauth2/token' 3945 }, 3946 3947 // Authorization scopes 3948 scope: { 3949 basic: 'https://www.googleapis.com/auth/plus.me profile', 3950 email: 'email', 3951 birthday: '', 3952 events: '', 3953 photos: 'https://picasaweb.google.com/data/', 3954 videos: 'http://gdata.youtube.com', 3955 friends: 'https://www.google.com/m8/feeds, https://www.googleapis.com/auth/plus.login', 3956 files: 'https://www.googleapis.com/auth/drive.readonly', 3957 publish: '', 3958 publish_files: 'https://www.googleapis.com/auth/drive', 3959 share: '', 3960 create_event: '', 3961 offline_access: '' 3962 }, 3963 3964 scope_delim: ' ', 3965 3966 login: function(p) { 3967 3968 if (p.qs.response_type === 'code') { 3969 3970 // Let's set this to an offline access to return a refresh_token 3971 p.qs.access_type = 'offline'; 3972 } 3973 3974 // Reauthenticate 3975 // https://developers.google.com/identity/protocols/ 3976 if (p.options.force) { 3977 p.qs.approval_prompt = 'force'; 3978 } 3979 }, 3980 3981 // API base URI 3982 base: 'https://www.googleapis.com/', 3983 3984 // Map GET requests 3985 get: { 3986 me: 'plus/v1/people/me', 3987 3988 // Deprecated Sept 1, 2014 3989 //'me': 'oauth2/v1/userinfo?alt=json', 3990 3991 // See: https://developers.google.com/+/api/latest/people/list 3992 'me/friends': 'plus/v1/people/me/people/visible?maxResults=@{limit|100}', 3993 'me/following': contactsUrl, 3994 'me/followers': contactsUrl, 3995 'me/contacts': contactsUrl, 3996 'me/share': 'plus/v1/people/me/activities/public?maxResults=@{limit|100}', 3997 'me/feed': 'plus/v1/people/me/activities/public?maxResults=@{limit|100}', 3998 'me/albums': 'https://picasaweb.google.com/data/feed/api/user/default?alt=json&max-results=@{limit|100}&start-index=@{start|1}', 3999 'me/album': function(p, callback) { 4000 var key = p.query.id; 4001 delete p.query.id; 4002 callback(key.replace('/entry/', '/feed/')); 4003 }, 4004 4005 'me/photos': 'https://picasaweb.google.com/data/feed/api/user/default?alt=json&kind=photo&max-results=@{limit|100}&start-index=@{start|1}', 4006 4007 // See: https://developers.google.com/drive/v2/reference/files/list 4008 'me/file': 'drive/v2/files/@{id}', 4009 'me/files': 'drive/v2/files?q=%22@{parent|root}%22+in+parents+and+trashed=false&maxResults=@{limit|100}', 4010 4011 // See: https://developers.google.com/drive/v2/reference/files/list 4012 'me/folders': 'drive/v2/files?q=%22@{id|root}%22+in+parents+and+mimeType+=+%22application/vnd.google-apps.folder%22+and+trashed=false&maxResults=@{limit|100}', 4013 4014 // See: https://developers.google.com/drive/v2/reference/files/list 4015 'me/folder': 'drive/v2/files?q=%22@{id|root}%22+in+parents+and+trashed=false&maxResults=@{limit|100}' 4016 }, 4017 4018 // Map POST requests 4019 post: { 4020 4021 // Google Drive 4022 'me/files': uploadDrive, 4023 'me/folders': function(p, callback) { 4024 p.data = { 4025 title: p.data.name, 4026 parents: [{id: p.data.parent || 'root'}], 4027 mimeType: 'application/vnd.google-apps.folder' 4028 }; 4029 callback('drive/v2/files'); 4030 } 4031 }, 4032 4033 // Map PUT requests 4034 put: { 4035 'me/files': uploadDrive 4036 }, 4037 4038 // Map DELETE requests 4039 del: { 4040 'me/files': 'drive/v2/files/@{id}', 4041 'me/folder': 'drive/v2/files/@{id}' 4042 }, 4043 4044 // Map PATCH requests 4045 patch: { 4046 'me/file': 'drive/v2/files/@{id}' 4047 }, 4048 4049 wrap: { 4050 me: function(o) { 4051 if (o.id) { 4052 o.last_name = o.family_name || (o.name ? o.name.familyName : null); 4053 o.first_name = o.given_name || (o.name ? o.name.givenName : null); 4054 4055 if (o.emails && o.emails.length) { 4056 o.email = o.emails[0].value; 4057 } 4058 4059 formatPerson(o); 4060 } 4061 4062 return o; 4063 }, 4064 4065 'me/friends': function(o) { 4066 if (o.items) { 4067 paging(o); 4068 o.data = o.items; 4069 o.data.forEach(formatPerson); 4070 delete o.items; 4071 } 4072 4073 return o; 4074 }, 4075 4076 'me/contacts': formatFriends, 4077 'me/followers': formatFriends, 4078 'me/following': formatFriends, 4079 'me/share': formatFeed, 4080 'me/feed': formatFeed, 4081 'me/albums': gEntry, 4082 'me/photos': formatPhotos, 4083 'default': gEntry 4084 }, 4085 4086 xhr: function(p) { 4087 4088 if (p.method === 'post' || p.method === 'put') { 4089 toJSON(p); 4090 } 4091 else if (p.method === 'patch') { 4092 hello.utils.extend(p.query, p.data); 4093 p.data = null; 4094 } 4095 4096 return true; 4097 }, 4098 4099 // Don't even try submitting via form. 4100 // This means no POST operations in <=IE9 4101 form: false 4102 } 4103 }); 4104 4105 function toInt(s) { 4106 return parseInt(s, 10); 4107 } 4108 4109 function formatFeed(o) { 4110 paging(o); 4111 o.data = o.items; 4112 delete o.items; 4113 return o; 4114 } 4115 4116 // Format: ensure each record contains a name, id etc. 4117 function formatItem(o) { 4118 if (o.error) { 4119 return; 4120 } 4121 4122 if (!o.name) { 4123 o.name = o.title || o.message; 4124 } 4125 4126 if (!o.picture) { 4127 o.picture = o.thumbnailLink; 4128 } 4129 4130 if (!o.thumbnail) { 4131 o.thumbnail = o.thumbnailLink; 4132 } 4133 4134 if (o.mimeType === 'application/vnd.google-apps.folder') { 4135 o.type = 'folder'; 4136 o.files = 'https://www.googleapis.com/drive/v2/files?q=%22' + o.id + '%22+in+parents'; 4137 } 4138 4139 return o; 4140 } 4141 4142 function formatImage(image) { 4143 return { 4144 source: image.url, 4145 width: image.width, 4146 height: image.height 4147 }; 4148 } 4149 4150 function formatPhotos(o) { 4151 o.data = o.feed.entry.map(formatEntry); 4152 delete o.feed; 4153 } 4154 4155 // Google has a horrible JSON API 4156 function gEntry(o) { 4157 paging(o); 4158 4159 if ('feed' in o && 'entry' in o.feed) { 4160 o.data = o.feed.entry.map(formatEntry); 4161 delete o.feed; 4162 } 4163 4164 // Old style: Picasa, etc. 4165 else if ('entry' in o) { 4166 return formatEntry(o.entry); 4167 } 4168 4169 // New style: Google Drive & Plus 4170 else if ('items' in o) { 4171 o.data = o.items.map(formatItem); 4172 delete o.items; 4173 } 4174 else { 4175 formatItem(o); 4176 } 4177 4178 return o; 4179 } 4180 4181 function formatPerson(o) { 4182 o.name = o.displayName || o.name; 4183 o.picture = o.picture || (o.image ? o.image.url : null); 4184 o.thumbnail = o.picture; 4185 } 4186 4187 function formatFriends(o, headers, req) { 4188 paging(o); 4189 var r = []; 4190 if ('feed' in o && 'entry' in o.feed) { 4191 var token = req.query.access_token; 4192 for (var i = 0; i < o.feed.entry.length; i++) { 4193 var a = o.feed.entry[i]; 4194 4195 a.id = a.id.$t; 4196 a.name = a.title.$t; 4197 delete a.title; 4198 if (a.gd$email) { 4199 a.email
vendor: 7,124 bytes, lines 4199-4508
4199= (a.gd$email && a.gd$email.length > 0) ? a.gd$email[0].address : null; 4200 a.emails = a.gd$email; 4201 delete a.gd$email; 4202 } 4203 4204 if (a.updated) { 4205 a.updated = a.updated.$t; 4206 } 4207 4208 if (a.link) { 4209 4210 var pic = (a.link.length > 0) ? a.link[0].href : null; 4211 if (pic && a.link[0].gd$etag) { 4212 pic += (pic.indexOf('?') > -1 ? '&' : '?') + 'access_token=' + token; 4213 a.picture = pic; 4214 a.thumbnail = pic; 4215 } 4216 4217 delete a.link; 4218 } 4219 4220 if (a.category) { 4221 delete a.category; 4222 } 4223 } 4224 4225 o.data = o.feed.entry; 4226 delete o.feed; 4227 } 4228 4229 return o; 4230 } 4231 4232 function formatEntry(a) { 4233 4234 var group = a.media$group; 4235 var photo = group.media$content.length ? group.media$content[0] : {}; 4236 var mediaContent = group.media$content || []; 4237 var mediaThumbnail = group.media$thumbnail || []; 4238 4239 var pictures = mediaContent 4240 .concat(mediaThumbnail) 4241 .map(formatImage) 4242 .sort(function(a, b) { 4243 return a.width - b.width; 4244 }); 4245 4246 var i = 0; 4247 var _a; 4248 var p = { 4249 id: a.id.$t, 4250 name: a.title.$t, 4251 description: a.summary.$t, 4252 updated_time: a.updated.$t, 4253 created_time: a.published.$t, 4254 picture: photo ? photo.url : null, 4255 pictures: pictures, 4256 images: [], 4257 thumbnail: photo ? photo.url : null, 4258 width: photo.width, 4259 height: photo.height 4260 }; 4261 4262 // Get feed/children 4263 if ('link' in a) { 4264 for (i = 0; i < a.link.length; i++) { 4265 var d = a.link[i]; 4266 if (d.rel.match(/\#feed$/)) { 4267 p.upload_location = p.files = p.photos = d.href; 4268 break; 4269 } 4270 } 4271 } 4272 4273 // Get images of different scales 4274 if ('category' in a && a.category.length) { 4275 _a = a.category; 4276 for (i = 0; i < _a.length; i++) { 4277 if (_a[i].scheme && _a[i].scheme.match(/\#kind$/)) { 4278 p.type = _a[i].term.replace(/^.*?\#/, ''); 4279 } 4280 } 4281 } 4282 4283 // Get images of different scales 4284 if ('media$thumbnail' in group && group.media$thumbnail.length) { 4285 _a = group.media$thumbnail; 4286 p.thumbnail = _a[0].url; 4287 p.images = _a.map(formatImage); 4288 } 4289 4290 _a = group.media$content; 4291 4292 if (_a && _a.length) { 4293 p.images.push(formatImage(_a[0])); 4294 } 4295 4296 return p; 4297 } 4298 4299 function paging(res) { 4300 4301 // Contacts V2 4302 if ('feed' in res && res.feed.openSearch$itemsPerPage) { 4303 var limit = toInt(res.feed.openSearch$itemsPerPage.$t); 4304 var start = toInt(res.feed.openSearch$startIndex.$t); 4305 var total = toInt(res.feed.openSearch$totalResults.$t); 4306 4307 if ((start + limit) < total) { 4308 res.paging = { 4309 next: '?start=' + (start + limit) 4310 }; 4311 } 4312 } 4313 else if ('nextPageToken' in res) { 4314 res.paging = { 4315 next: '?pageToken=' + res.nextPageToken 4316 }; 4317 } 4318 } 4319 4320 // Construct a multipart message 4321 function Multipart() { 4322 4323 // Internal body 4324 var body = []; 4325 var boundary = (Math.random() * 1e10).toString(32); 4326 var counter = 0; 4327 var lineBreak = '\r\n'; 4328 var delim = lineBreak + '--' + boundary; 4329 var ready = function() {}; 4330 4331 var dataUri = /^data\:([^;,]+(\;charset=[^;,]+)?)(\;base64)?,/i; 4332 4333 // Add file 4334 function addFile(item) { 4335 var fr = new FileReader(); 4336 fr.onload = function(e) { 4337 addContent(btoa(e.target.result), item.type + lineBreak + 'Content-Transfer-Encoding: base64'); 4338 }; 4339 4340 fr.readAsBinaryString(item); 4341 } 4342 4343 // Add content 4344 function addContent(content, type) { 4345 body.push(lineBreak + 'Content-Type: ' + type + lineBreak + lineBreak + content); 4346 counter--; 4347 ready(); 4348 } 4349 4350 // Add new things to the object 4351 this.append = function(content, type) { 4352 4353 // Does the content have an array 4354 if (typeof (content) === 'string' || !('length' in Object(content))) { 4355 // Converti to multiples 4356 content = [content]; 4357 } 4358 4359 for (var i = 0; i < content.length; i++) { 4360 4361 counter++; 4362 4363 var item = content[i]; 4364 4365 // Is this a file? 4366 // Files can be either Blobs or File types 4367 if ( 4368 (typeof (File) !== 'undefined' && item instanceof File) || 4369 (typeof (Blob) !== 'undefined' && item instanceof Blob) 4370 ) { 4371 // Read the file in 4372 addFile(item); 4373 } 4374 4375 // Data-URI? 4376 // Data:[<mime type>][;charset=<charset>][;base64],<encoded data> 4377 // /^data\:([^;,]+(\;charset=[^;,]+)?)(\;base64)?,/i 4378 else if (typeof (item) === 'string' && item.match(dataUri)) { 4379 var m = item.match(dataUri); 4380 addContent(item.replace(dataUri, ''), m[1] + lineBreak + 'Content-Transfer-Encoding: base64'); 4381 } 4382 4383 // Regular string 4384 else { 4385 addContent(item, type); 4386 } 4387 } 4388 }; 4389 4390 this.onready = function(fn) { 4391 ready = function() { 4392 if (counter === 0) { 4393 // Trigger ready 4394 body.unshift(''); 4395 body.push('--'); 4396 fn(body.join(delim), boundary); 4397 body = []; 4398 } 4399 }; 4400 4401 ready(); 4402 }; 4403 } 4404 4405 // Upload to Drive 4406 // If this is PUT then only augment the file uploaded 4407 // PUT https://developers.google.com/drive/v2/reference/files/update 4408 // POST https://developers.google.com/drive/manage-uploads 4409 function uploadDrive(p, callback) { 4410 4411 var data = {}; 4412 4413 // Test for DOM element 4414 if (p.data && 4415 (typeof (HTMLInputElement) !== 'undefined' && p.data instanceof HTMLInputElement) 4416 ) { 4417 p.data = {file: p.data}; 4418 } 4419 4420 if (!p.data.name && Object(Object(p.data.file).files).length && p.method === 'post') { 4421 p.data.name = p.data.file.files[0].name; 4422 } 4423 4424 if (p.method === 'post') { 4425 p.data = { 4426 title: p.data.name, 4427 parents: [{id: p.data.parent || 'root'}], 4428 file: p.data.file 4429 }; 4430 } 4431 else { 4432 4433 // Make a reference 4434 data = p.data; 4435 p.data = {}; 4436 4437 // Add the parts to change as required 4438 if (data.parent) { 4439 p.data.parents = [{id: p.data.parent || 'root'}]; 4440 } 4441 4442 if (data.file) { 4443 p.data.file = data.file; 4444 } 4445 4446 if (data.name) { 4447 p.data.title = data.name; 4448 } 4449 } 4450 4451 // Extract the file, if it exists from the data object 4452 // If the File is an INPUT element lets just concern ourselves with the NodeList 4453 var file; 4454 if ('file' in p.data) { 4455 file = p.data.file; 4456 delete p.data.file; 4457 4458 if (typeof (file) === 'object' && 'files' in file) { 4459 // Assign the NodeList 4460 file = file.files; 4461 } 4462 4463 if (!file || !file.length) { 4464 callback({ 4465 error: { 4466 code: 'request_invalid', 4467 message: 'There were no files attached with this request to upload' 4468 } 4469 }); 4470 return; 4471 } 4472 } 4473 4474 // Set type p.data.mimeType = Object(file[0]).type || 'application/octet-stream'; 4475 4476 // Construct a multipart message 4477 var parts = new Multipart(); 4478 parts.append(JSON.stringify(p.data), 'application/json'); 4479 4480 // Read the file into a base64 string... yep a hassle, i know 4481 // FormData doesn't let us assign our own Multipart headers and HTTP Content-Type 4482 // Alas GoogleApi need these in a particular format 4483 if (file) { 4484 parts.append(file); 4485 } 4486 4487 parts.onready(function(body, boundary) { 4488 4489 p.headers['content-type'] = 'multipart/related; boundary="' + boundary + '"'; 4490 p.data = body; 4491 4492 callback('upload/drive/v2/files' + (data.id ? '/' + data.id : '') + '?uploadType=multipart'); 4493 }); 4494 4495 } 4496 4497 function toJSON(p) { 4498 if (typeof (p.data) === 'object') { 4499 // Convert the POST into a javascript object 4500 try { 4501 p.data = JSON.stringify(p.data); 4502 p.headers['content-type'] = 'application/json'; 4503 } 4504 catch (e) {} 4505 } 4506 } 4507 4508})(hello);
vendor: 27,013 bytes, lines 4509-5826
4509 4510(function(hello) { 4511 4512 hello.init({ 4513 4514 instagram: { 4515 4516 name: 'Instagram', 4517 4518 oauth: { 4519 // See: http://instagram.com/developer/authentication/ 4520 version: 2, 4521 auth: 'https://instagram.com/oauth/authorize/', 4522 grant: 'https://api.instagram.com/oauth/access_token' 4523 }, 4524 4525 // Refresh the access_token once expired 4526 refresh: true, 4527 4528 scope: { 4529 basic: 'basic', 4530 photos: '', 4531 friends: 'relationships', 4532 publish: 'likes comments', 4533 email: '', 4534 share: '', 4535 publish_files: '', 4536 files: '', 4537 videos: '', 4538 offline_access: '' 4539 }, 4540 4541 scope_delim: ' ', 4542 4543 base: 'https://api.instagram.com/v1/', 4544 4545 get: { 4546 me: 'users/self', 4547 'me/feed': 'users/self/feed?count=@{limit|100}', 4548 'me/photos': 'users/self/media/recent?min_id=0&count=@{limit|100}', 4549 'me/friends': 'users/self/follows?count=@{limit|100}', 4550 'me/following': 'users/self/follows?count=@{limit|100}', 4551 'me/followers': 'users/self/followed-by?count=@{limit|100}', 4552 'friend/photos': 'users/@{id}/media/recent?min_id=0&count=@{limit|100}' 4553 }, 4554 4555 post: { 4556 'me/like': function(p, callback) { 4557 var id = p.data.id; 4558 p.data = {}; 4559 callback('media/' + id + '/likes'); 4560 } 4561 }, 4562 4563 del: { 4564 'me/like': 'media/@{id}/likes' 4565 }, 4566 4567 wrap: { 4568 me: function(o) { 4569 4570 formatError(o); 4571 4572 if ('data' in o) { 4573 o.id = o.data.id; 4574 o.thumbnail = o.data.profile_picture; 4575 o.name = o.data.full_name || o.data.username; 4576 } 4577 4578 return o; 4579 }, 4580 4581 'me/friends': formatFriends, 4582 'me/following': formatFriends, 4583 'me/followers': formatFriends, 4584 'me/photos': function(o) { 4585 4586 formatError(o); 4587 paging(o); 4588 4589 if ('data' in o) { 4590 o.data = o.data.filter(function(d) { 4591 return d.type === 'image'; 4592 }); 4593 4594 o.data.forEach(function(d) { 4595 d.name = d.caption ? d.caption.text : null; 4596 d.thumbnail = d.images.thumbnail.url; 4597 d.picture = d.images.standard_resolution.url; 4598 d.pictures = Object.keys(d.images) 4599 .map(function(key) { 4600 var image = d.images[key]; 4601 return formatImage(image); 4602 }) 4603 .sort(function(a, b) { 4604 return a.width - b.width; 4605 }); 4606 }); 4607 } 4608 4609 return o; 4610 }, 4611 4612 'default': function(o) { 4613 o = formatError(o); 4614 paging(o); 4615 return o; 4616 } 4617 }, 4618 4619 // Instagram does not return any CORS Headers 4620 // So besides JSONP we're stuck with proxy 4621 xhr: function(p, qs) { 4622 4623 var method = p.method; 4624 var proxy = method !== 'get'; 4625 4626 if (proxy) { 4627 4628 if ((method === 'post' || method === 'put') && p.query.access_token) { 4629 p.data.access_token = p.query.access_token; 4630 delete p.query.access_token; 4631 } 4632 4633 // No access control headers 4634 // Use the proxy instead 4635 p.proxy = proxy; 4636 } 4637 4638 return proxy; 4639 }, 4640 4641 // No form 4642 form: false 4643 } 4644 }); 4645 4646 function formatImage(image) { 4647 return { 4648 source: image.url, 4649 width: image.width, 4650 height: image.height 4651 }; 4652 } 4653 4654 function formatError(o) { 4655 if (typeof o === 'string') { 4656 return { 4657 error: { 4658 code: 'invalid_request', 4659 message: o 4660 } 4661 }; 4662 } 4663 4664 if (o && 'meta' in o && 'error_type' in o.meta) { 4665 o.error = { 4666 code: o.meta.error_type, 4667 message: o.meta.error_message 4668 }; 4669 } 4670 4671 return o; 4672 } 4673 4674 function formatFriends(o) { 4675 paging(o); 4676 if (o && 'data' in o) { 4677 o.data.forEach(formatFriend); 4678 } 4679 4680 return o; 4681 } 4682 4683 function formatFriend(o) { 4684 if (o.id) { 4685 o.thumbnail = o.profile_picture; 4686 o.name = o.full_name || o.username; 4687 } 4688 } 4689 4690 // See: http://instagram.com/developer/endpoints/ 4691 function paging(res) { 4692 if ('pagination' in res) { 4693 res.paging = { 4694 next: res.pagination.next_url 4695 }; 4696 delete res.pagination; 4697 } 4698 } 4699 4700})(hello); 4701 4702(function(hello) { 4703 4704 hello.init({ 4705 4706 joinme: { 4707 4708 name: 'join.me', 4709 4710 oauth: { 4711 version: 2, 4712 auth: 'https://secure.join.me/api/public/v1/auth/oauth2', 4713 grant: 'https://secure.join.me/api/public/v1/auth/oauth2' 4714 }, 4715 4716 refresh: false, 4717 4718 scope: { 4719 basic: 'user_info', 4720 user: 'user_info', 4721 scheduler: 'scheduler', 4722 start: 'start_meeting', 4723 email: '', 4724 friends: '', 4725 share: '', 4726 publish: '', 4727 photos: '', 4728 publish_files: '', 4729 files: '', 4730 videos: '', 4731 offline_access: '' 4732 }, 4733 4734 scope_delim: ' ', 4735 4736 login: function(p) { 4737 p.options.popup.width = 400; 4738 p.options.popup.height = 700; 4739 }, 4740 4741 base: 'https://api.join.me/v1/', 4742 4743 get: { 4744 me: 'user', 4745 meetings: 'meetings', 4746 'meetings/info': 'meetings/@{id}' 4747 }, 4748 4749 post: { 4750 'meetings/start/adhoc': function(p, callback) { 4751 callback('meetings/start'); 4752 }, 4753 4754 'meetings/start/scheduled': function(p, callback) { 4755 var meetingId = p.data.meetingId; 4756 p.data = {}; 4757 callback('meetings/' + meetingId + '/start'); 4758 }, 4759 4760 'meetings/schedule': function(p, callback) { 4761 callback('meetings'); 4762 } 4763 }, 4764 4765 patch: { 4766 'meetings/update': function(p, callback) { 4767 callback('meetings/' + p.data.meetingId); 4768 } 4769 }, 4770 4771 del: { 4772 'meetings/delete': 'meetings/@{id}' 4773 }, 4774 4775 wrap: { 4776 me: function(o, headers) { 4777 formatError(o, headers); 4778 4779 if (!o.email) { 4780 return o; 4781 } 4782 4783 o.name = o.fullName; 4784 o.first_name = o.name.split(' ')[0]; 4785 o.last_name = o.name.split(' ')[1]; 4786 o.id = o.email; 4787 4788 return o; 4789 }, 4790 4791 'default': function(o, headers) { 4792 formatError(o, headers); 4793 4794 return o; 4795 } 4796 }, 4797 4798 xhr: formatRequest 4799 4800 } 4801 }); 4802 4803 function formatError(o, headers) { 4804 var errorCode; 4805 var message; 4806 var details; 4807 4808 if (o && ('Message' in o)) { 4809 message = o.Message; 4810 delete o.Message; 4811 4812 if ('ErrorCode' in o) { 4813 errorCode = o.ErrorCode; 4814 delete o.ErrorCode; 4815 } 4816 else { 4817 errorCode = getErrorCode(headers); 4818 } 4819 4820 o.error = { 4821 code: errorCode, 4822 message: message, 4823 details: o 4824 }; 4825 } 4826 4827 return o; 4828 } 4829 4830 function formatRequest(p, qs) { 4831 // Move the access token from the request body to the request header 4832 var token = qs.access_token; 4833 delete qs.access_token; 4834 p.headers.Authorization = 'Bearer ' + token; 4835 4836 // Format non-get requests to indicate json body 4837 if (p.method !== 'get' && p.data) { 4838 p.headers['Content-Type'] = 'application/json'; 4839 if (typeof (p.data) === 'object') { 4840 p.data = JSON.stringify(p.data); 4841 } 4842 } 4843 4844 if (p.method === 'put') { 4845 p.method = 'patch'; 4846 } 4847 4848 return true; 4849 } 4850 4851 function getErrorCode(headers) { 4852 switch (headers.statusCode) { 4853 case 400: 4854 return 'invalid_request'; 4855 case 403: 4856 return 'stale_token'; 4857 case 401: 4858 return 'invalid_token'; 4859 case 500: 4860 return 'server_error'; 4861 default: 4862 return 'server_error'; 4863 } 4864 } 4865 4866}(hello)); 4867 4868(function(hello) { 4869 4870 hello.init({ 4871 4872 linkedin: { 4873 4874 oauth: { 4875 version: 2, 4876 response_type: 'code', 4877 auth: 'https://www.linkedin.com/uas/oauth2/authorization', 4878 grant: 'https://www.linkedin.com/uas/oauth2/accessToken' 4879 }, 4880 4881 // Refresh the access_token once expired 4882 refresh: true, 4883 4884 scope: { 4885 basic: 'r_basicprofile', 4886 email: 'r_emailaddress', 4887 files: '', 4888 friends: '', 4889 photos: '', 4890 publish: 'w_share', 4891 publish_files: 'w_share', 4892 share: '', 4893 videos: '', 4894 offline_access: '' 4895 }, 4896 scope_delim: ' ', 4897 4898 base: 'https://api.linkedin.com/v1/', 4899 4900 get: { 4901 me: 'people/~:(picture-url,first-name,last-name,id,formatted-name,email-address)', 4902 4903 // See: http://developer.linkedin.com/documents/get-network-updates-and-statistics-api 4904 'me/share': 'people/~/network/updates?count=@{limit|250}' 4905 }, 4906 4907 post: { 4908 4909 // See: https://developer.linkedin.com/documents/api-requests-json 4910 'me/share': function(p, callback) { 4911 var data = { 4912 visibility: { 4913 code: 'anyone' 4914 } 4915 }; 4916 4917 if (p.data.id) { 4918 4919 data.attribution = { 4920 share: { 4921 id: p.data.id 4922 } 4923 }; 4924 4925 } 4926 else { 4927 data.comment = p.data.message; 4928 if (p.data.picture && p.data.link) { 4929 data.content = { 4930 'submitted-url': p.data.link, 4931 'submitted-image-url': p.data.picture 4932 }; 4933 } 4934 } 4935 4936 p.data = JSON.stringify(data); 4937 4938 callback('people/~/shares?format=json'); 4939 }, 4940 4941 'me/like': like 4942 }, 4943 4944 del:{ 4945 'me/like': like 4946 }, 4947 4948 wrap: { 4949 me: function(o) { 4950 formatError(o); 4951 formatUser(o); 4952 return o; 4953 }, 4954 4955 'me/friends': formatFriends, 4956 'me/following': formatFriends, 4957 'me/followers': formatFriends, 4958 'me/share': function(o) { 4959 formatError(o); 4960 paging(o); 4961 if (o.values) { 4962 o.data = o.values.map(formatUser); 4963 o.data.forEach(function(item) { 4964 item.message = item.headline; 4965 }); 4966 4967 delete o.values; 4968 } 4969 4970 return o; 4971 }, 4972 4973 'default': function(o, headers) { 4974 formatError(o); 4975 empty(o, headers); 4976 paging(o); 4977 } 4978 }, 4979 4980 jsonp: function(p, qs) { 4981 formatQuery(qs); 4982 if (p.method === 'get') { 4983 qs.format = 'jsonp'; 4984 qs['error-callback'] = p.callbackID; 4985 } 4986 }, 4987 4988 xhr: function(p, qs) { 4989 if (p.method !== 'get') { 4990 formatQuery(qs); 4991 p.headers['Content-Type'] = 'application/json'; 4992 4993 // Note: x-li-format ensures error responses are not returned in XML 4994 p.headers['x-li-format'] = 'json'; 4995 p.proxy = true; 4996 return true; 4997 } 4998 4999 return false; 5000 } 5001 } 5002 }); 5003 5004 function formatError(o) { 5005 if (o && 'errorCode' in o) { 5006 o.error = { 5007 code: o.status, 5008 message: o.message 5009 }; 5010 } 5011 } 5012 5013 function formatUser(o) { 5014 if (o.error) { 5015 return; 5016 } 5017 5018 o.first_name = o.firstName; 5019 o.last_name = o.lastName; 5020 o.name = o.formattedName || (o.first_name + ' ' + o.last_name); 5021 o.thumbnail = o.pictureUrl; 5022 o.email = o.emailAddress; 5023 return o; 5024 } 5025 5026 function formatFriends(o) { 5027 formatError(o); 5028 paging(o); 5029 if (o.values) { 5030 o.data = o.values.map(formatUser); 5031 delete o.values; 5032 } 5033 5034 return o; 5035 } 5036 5037 function paging(res) { 5038 if ('_count' in res && '_start' in res && (res._count + res._start) < res._total) { 5039 res.paging = { 5040 next: '?start=' + (res._start + res._count) + '&count=' + res._count 5041 }; 5042 } 5043 } 5044 5045 function empty(o, headers) { 5046 if (JSON.stringify(o) === '{}' && headers.statusCode === 200) { 5047 o.success = true; 5048 } 5049 } 5050 5051 function formatQuery(qs) { 5052 // LinkedIn signs requests with the parameter 'oauth2_access_token' 5053 // ... yeah another one who thinks they should be different! 5054 if (qs.access_token) { 5055 qs.oauth2_access_token = qs.access_token; 5056 delete qs.access_token; 5057 } 5058 } 5059 5060 function like(p, callback) { 5061 p.headers['x-li-format'] = 'json'; 5062 var id = p.data.id; 5063 p.data = (p.method !== 'delete').toString(); 5064 p.method = 'put'; 5065 callback('people/~/network/updates/key=' + id + '/is-liked'); 5066 } 5067 5068})(hello); 5069 5070// See: https://developers.soundcloud.com/docs/api/reference 5071(function(hello) { 5072 5073 hello.init({ 5074 5075 soundcloud: { 5076 name: 'SoundCloud', 5077 5078 oauth: { 5079 version: 2, 5080 auth: 'https://soundcloud.com/connect', 5081 grant: 'https://soundcloud.com/oauth2/token' 5082 }, 5083 5084 // Request path translated 5085 base: 'https://api.soundcloud.com/', 5086 get: { 5087 me: 'me.json', 5088 5089 // Http://developers.soundcloud.com/docs/api/reference#me 5090 'me/friends': 'me/followings.json', 5091 'me/followers': 'me/followers.json', 5092 'me/following': 'me/followings.json', 5093 5094 // See: http://developers.soundcloud.com/docs/api/reference#activities 5095 'default': function(p, callback) { 5096 5097 // Include '.json at the end of each request' 5098 callback(p.path + '.json'); 5099 } 5100 }, 5101 5102 // Response handlers 5103 wrap: { 5104 me: function(o) { 5105 formatUser(o); 5106 return o; 5107 }, 5108 5109 'default': function(o) { 5110 if (Array.isArray(o)) { 5111 o = { 5112 data: o.map(formatUser) 5113 }; 5114 } 5115 5116 paging(o); 5117 return o; 5118 } 5119 }, 5120 5121 xhr: formatRequest, 5122 jsonp: formatRequest 5123 } 5124 }); 5125 5126 function formatRequest(p, qs) { 5127 // Alter the querystring 5128 var token = qs.access_token; 5129 delete qs.access_token; 5130 qs.oauth_token = token; 5131 qs['_status_code_map[302]'] = 200; 5132 return true; 5133 } 5134 5135 function formatUser(o) { 5136 if (o.id) { 5137 o.picture = o.avatar_url; 5138 o.thumbnail = o.avatar_url; 5139 o.name = o.username || o.full_name; 5140 } 5141 5142 return o; 5143 } 5144 5145 // See: http://developers.soundcloud.com/docs/api/reference#activities 5146 function paging(res) { 5147 if ('next_href' in res) { 5148 res.paging = { 5149 next: res.next_href 5150 }; 5151 } 5152 } 5153 5154})(hello); 5155 5156(function(hello) { 5157 5158 var base = 'https://api.twitter.com/'; 5159 5160 hello.init({ 5161 5162 twitter: { 5163 5164 // Ensure that you define an oauth_proxy 5165 oauth: { 5166 version: '1.0a', 5167 auth: base + 'oauth/authenticate', 5168 request: base + 'oauth/request_token', 5169 token: base + 'oauth/access_token' 5170 }, 5171 5172 login: function(p) { 5173 // Reauthenticate 5174 // https://dev.twitter.com/oauth/reference/get/oauth/authenticate 5175 var prefix = '?force_login=true'; 5176 this.oauth.auth = this.oauth.auth.replace(prefix, '') + (p.options.force ? prefix : ''); 5177 }, 5178 5179 base: base + '1.1/', 5180 5181 get: { 5182 me: 'account/verify_credentials.json', 5183 'me/friends': 'friends/list.json?count=@{limit|200}', 5184 'me/following': 'friends/list.json?count=@{limit|200}', 5185 'me/followers': 'followers/list.json?count=@{limit|200}', 5186 5187 // Https://dev.twitter.com/docs/api/1.1/get/statuses/user_timeline 5188 'me/share': 'statuses/user_timeline.json?count=@{limit|200}', 5189 5190 // Https://dev.twitter.com/rest/reference/get/favorites/list 5191 'me/like': 'favorites/list.json?count=@{limit|200}' 5192 }, 5193 5194 post: { 5195 'me/share': function(p, callback) { 5196 5197 var data = p.data; 5198 p.data = null; 5199 5200 var status = []; 5201 5202 // Change message to status 5203 if (data.message) { 5204 status.push(data.message); 5205 delete data.message; 5206 } 5207 5208 // If link is given 5209 if (data.link) { 5210 status.push(data.link); 5211 delete data.link; 5212 } 5213 5214 if (data.picture) { 5215 status.push(data.picture); 5216 delete data.picture; 5217 } 5218 5219 // Compound all the components 5220 if (status.length) { 5221 data.status = status.join(' '); 5222 } 5223 5224 // Tweet media 5225 if (data.file) { 5226 data['media[]'] = data.file; 5227 delete data.file; 5228 p.data = data; 5229 callback('statuses/update_with_media.json'); 5230 } 5231 5232 // Retweet? 5233 else if ('id' in data) { 5234 callback('statuses/retweet/' + data.id + '.json'); 5235 } 5236 5237 // Tweet 5238 else { 5239 // Assign the post body to the query parameters 5240 hello.utils.extend(p.query, data); 5241 callback('statuses/update.json?include_entities=1'); 5242 } 5243 }, 5244 5245 // See: https://dev.twitter.com/rest/reference/post/favorites/create 5246 'me/like': function(p, callback) { 5247 var id = p.data.id; 5248 p.data = null; 5249 callback('favorites/create.json?id=' + id); 5250 } 5251 }, 5252 5253 del: { 5254 5255 // See: https://dev.twitter.com/rest/reference/post/favorites/destroy 5256 'me/like': function() { 5257 p.method = 'post'; 5258 var id = p.data.id; 5259 p.data = null; 5260 callback('favorites/destroy.json?id=' + id); 5261 } 5262 }, 5263 5264 wrap: { 5265 me: function(res) { 5266 formatError(res); 5267 formatUser(res); 5268 return res; 5269 }, 5270 5271 'me/friends': formatFriends, 5272 'me/followers': formatFriends, 5273 'me/following': formatFriends, 5274 5275 'me/share': function(res) { 5276 formatError(res); 5277 paging(res); 5278 if (!res.error && 'length' in res) { 5279 return {data: res}; 5280 } 5281 5282 return res; 5283 }, 5284 5285 'default': function(res) { 5286 res = arrayToDataResponse(res); 5287 paging(res); 5288 return res; 5289 } 5290 }, 5291 xhr: function(p) { 5292 5293 // Rely on the proxy for non-GET requests. 5294 return (p.method !== 'get'); 5295 } 5296 } 5297 }); 5298 5299 function formatUser(o) { 5300 if (o.id) { 5301 if (o.name) { 5302 var m = o.name.split(' '); 5303 o.first_name = m.shift(); 5304 o.last_name = m.join(' '); 5305 } 5306 5307 // See: https://dev.twitter.com/overview/general/user-profile-images-and-banners 5308 o.thumbnail = o.profile_image_url_https || o.profile_image_url; 5309 } 5310 5311 return o; 5312 } 5313 5314 function formatFriends(o) { 5315 formatError(o); 5316 paging(o); 5317 if (o.users) { 5318 o.data = o.users.map(formatUser); 5319 delete o.users; 5320 } 5321 5322 return o; 5323 } 5324 5325 function formatError(o) { 5326 if (o.errors) { 5327 var e = o.errors[0]; 5328 o.error = { 5329 code: 'request_failed', 5330 message: e.message 5331 }; 5332 } 5333 } 5334 5335 // Take a cursor and add it to the path 5336 function paging(res) { 5337 // Does the response include a 'next_cursor_string' 5338 if ('next_cursor_str' in res) { 5339 // See: https://dev.twitter.com/docs/misc/cursoring 5340 res.paging = { 5341 next: '?cursor=' + res.next_cursor_str 5342 }; 5343 } 5344 } 5345 5346 function arrayToDataResponse(res) { 5347 return Array.isArray(res) ? {data: res} : res; 5348 } 5349 5350 /** 5351 // The documentation says to define user in the request 5352 // Although its not actually required. 5353 5354 var user_id; 5355 5356 function withUserId(callback){ 5357 if(user_id){ 5358 callback(user_id); 5359 } 5360 else{ 5361 hello.api('twitter:/me', function(o){ 5362 user_id = o.id; 5363 callback(o.id); 5364 }); 5365 } 5366 } 5367 5368 function sign(url){ 5369 return function(p, callback){ 5370 withUserId(function(user_id){ 5371 callback(url+'?user_id='+user_id); 5372 }); 5373 }; 5374 } 5375 */ 5376 5377})(hello); 5378 5379// Vkontakte (vk.com) 5380(function(hello) { 5381 5382 hello.init({ 5383 5384 vk: { 5385 name: 'Vk', 5386 5387 // See https://vk.com/dev/oauth_dialog 5388 oauth: { 5389 version: 2, 5390 auth: 'https://oauth.vk.com/authorize', 5391 grant: 'https://oauth.vk.com/access_token' 5392 }, 5393 5394 // Authorization scopes 5395 // See https://vk.com/dev/permissions 5396 scope: { 5397 email: 'email', 5398 friends: 'friends', 5399 photos: 'photos', 5400 videos: 'video', 5401 share: 'share', 5402 offline_access: 'offline' 5403 }, 5404 5405 // Refresh the access_token 5406 refresh: true, 5407 5408 login: function(p) { 5409 p.qs.display = window.navigator && 5410 window.navigator.userAgent && 5411 /ipad|phone|phone|android/.test(window.navigator.userAgent.toLowerCase()) ? 'mobile' : 'popup'; 5412 }, 5413 5414 // API Base URL 5415 base: 'https://api.vk.com/method/', 5416 5417 // Map GET requests 5418 get: { 5419 me: function(p, callback) { 5420 p.query.fields = 'id,first_name,last_name,photo_max'; 5421 callback('users.get'); 5422 } 5423 }, 5424 5425 wrap: { 5426 me: function(res, headers, req) { 5427 formatError(res); 5428 return formatUser(res, req); 5429 } 5430 }, 5431 5432 // No XHR 5433 xhr: false, 5434 5435 // All requests should be JSONP as of missing CORS headers in https://api.vk.com/method/* 5436 jsonp: true, 5437 5438 // No form 5439 form: false 5440 } 5441 }); 5442 5443 function formatUser(o, req) { 5444 5445 if (o !== null && 'response' in o && o.response !== null && o.response.length) { 5446 o = o.response[0]; 5447 o.id = o.uid; 5448 o.thumbnail = o.picture = o.photo_max; 5449 o.name = o.first_name + ' ' + o.last_name; 5450 5451 if (req.authResponse && req.authResponse.email !== null) 5452 o.email = req.authResponse.email; 5453 } 5454 5455 return o; 5456 } 5457 5458 function formatError(o) { 5459 5460 if (o.error) { 5461 var e = o.error; 5462 o.error = { 5463 code: e.error_code, 5464 message: e.error_msg 5465 }; 5466 } 5467 } 5468 5469})(hello); 5470 5471(function(hello) { 5472 5473 hello.init({ 5474 windows: { 5475 name: 'Windows live', 5476 5477 // REF: http://msdn.microsoft.com/en-us/library/hh243641.aspx 5478 oauth: { 5479 version: 2, 5480 auth: 'https://login.live.com/oauth20_authorize.srf', 5481 grant: 'https://login.live.com/oauth20_token.srf' 5482 }, 5483 5484 // Refresh the access_token once expired 5485 refresh: true, 5486 5487 logout: function() { 5488 return 'http://login.live.com/oauth20_logout.srf?ts=' + (new Date()).getTime(); 5489 }, 5490 5491 // Authorization scopes 5492 scope: { 5493 basic: 'wl.signin,wl.basic', 5494 email: 'wl.emails', 5495 birthday: 'wl.birthday', 5496 events: 'wl.calendars', 5497 photos: 'wl.photos', 5498 videos: 'wl.photos', 5499 friends: 'wl.contacts_emails', 5500 files: 'wl.skydrive', 5501 publish: 'wl.share', 5502 publish_files: 'wl.skydrive_update', 5503 share: 'wl.share', 5504 create_event: 'wl.calendars_update,wl.events_create', 5505 offline_access: 'wl.offline_access' 5506 }, 5507 5508 // API base URL 5509 base: 'https://apis.live.net/v5.0/', 5510 5511 // Map GET requests 5512 get: { 5513 5514 // Friends 5515 me: 'me', 5516 'me/friends': 'me/friends', 5517 'me/following': 'me/contacts', 5518 'me/followers': 'me/friends', 5519 'me/contacts': 'me/contacts', 5520 5521 'me/albums': 'me/albums', 5522 5523 // Include the data[id] in the path 5524 'me/album': '@{id}/files', 5525 'me/photo': '@{id}', 5526 5527 // Files 5528 'me/files': '@{parent|me/skydrive}/files', 5529 'me/folders': '@{id|me/skydrive}/files', 5530 'me/folder': '@{id|me/skydrive}/files' 5531 }, 5532 5533 // Map POST requests 5534 post: { 5535 'me/albums': 'me/albums', 5536 'me/album': '@{id}/files/', 5537 5538 'me/folders': '@{id|me/skydrive/}', 5539 'me/files': '@{parent|me/skydrive}/files' 5540 }, 5541 5542 // Map DELETE requests 5543 del: { 5544 // Include the data[id] in the path 5545 'me/album': '@{id}', 5546 'me/photo': '@{id}', 5547 'me/folder': '@{id}', 5548 'me/files': '@{id}' 5549 }, 5550 5551 wrap: { 5552 me: formatUser, 5553 5554 'me/friends': formatFriends, 5555 'me/contacts': formatFriends, 5556 'me/followers': formatFriends, 5557 'me/following': formatFriends, 5558 'me/albums': formatAlbums, 5559 'me/photos': formatDefault, 5560 'default': formatDefault 5561 }, 5562 5563 xhr: function(p) { 5564 if (p.method !== 'get' && p.method !== 'delete' && !hello.utils.hasBinary(p.data)) { 5565 5566 // Does this have a data-uri to upload as a file? 5567 if (typeof (p.data.file) === 'string') { 5568 p.data.file = hello.utils.toBlob(p.data.file); 5569 } 5570 else { 5571 p.data = JSON.stringify(p.data); 5572 p.headers = { 5573 'Content-Type': 'application/json' 5574 }; 5575 } 5576 } 5577 5578 return true; 5579 }, 5580 5581 jsonp: function(p) { 5582 if (p.method !== 'get' && !hello.utils.hasBinary(p.data)) { 5583 p.data.method = p.method; 5584 p.method = 'get'; 5585 } 5586 } 5587 } 5588 }); 5589 5590 function formatDefault(o) { 5591 if ('data' in o) { 5592 o.data.forEach(function(d) { 5593 if (d.picture) { 5594 d.thumbnail = d.picture; 5595 } 5596 5597 if (d.images) { 5598 d.pictures = d.images 5599 .map(formatImage) 5600 .sort(function(a, b) { 5601 return a.width - b.width; 5602 }); 5603 } 5604 }); 5605 } 5606 5607 return o; 5608 } 5609 5610 function formatImage(image) { 5611 return { 5612 width: image.width, 5613 height: image.height, 5614 source: image.source 5615 }; 5616 } 5617 5618 function formatAlbums(o) { 5619 if ('data' in o) { 5620 o.data.forEach(function(d) { 5621 d.photos = d.files = 'https://apis.live.net/v5.0/' + d.id + '/photos'; 5622 }); 5623 } 5624 5625 return o; 5626 } 5627 5628 function formatUser(o, headers, req) { 5629 if (o.id) { 5630 var token = req.query.access_token; 5631 if (o.emails) { 5632 o.email = o.emails.preferred; 5633 } 5634 5635 // If this is not an non-network friend 5636 if (o.is_friend !== false) { 5637 // Use the id of the user_id if available 5638 var id = (o.user_id || o.id); 5639 o.thumbnail = o.picture = 'https://apis.live.net/v5.0/' + id + '/picture?access_token=' + token; 5640 } 5641 } 5642 5643 return o; 5644 } 5645 5646 function formatFriends(o, headers, req) { 5647 if ('data' in o) { 5648 o.data.forEach(function(d) { 5649 formatUser(d, headers, req); 5650 }); 5651 } 5652 5653 return o; 5654 } 5655 5656})(hello); 5657 5658(function(hello) { 5659 5660 hello.init({ 5661 5662 yahoo: { 5663 5664 // Ensure that you define an oauth_proxy 5665 oauth: { 5666 version: '1.0a', 5667 auth: 'https://api.login.yahoo.com/oauth/v2/request_auth', 5668 request: 'https://api.login.yahoo.com/oauth/v2/get_request_token', 5669 token: 'https://api.login.yahoo.com/oauth/v2/get_token' 5670 }, 5671 5672 // Login handler 5673 login: function(p) { 5674 // Change the default popup window to be at least 560 5675 // Yahoo does dynamically change it on the fly for the signin screen (only, what if your already signed in) 5676 p.options.popup.width = 560; 5677 5678 // Yahoo throws an parameter error if for whatever reason the state.scope contains a comma, so lets remove scope 5679 try {delete p.qs.state.scope;} 5680 catch (e) {} 5681 }, 5682 5683 base: 'https://social.yahooapis.com/v1/', 5684 5685 get: { 5686 me: yql('select * from social.profile(0) where guid=me'), 5687 'me/friends': yql('select * from social.contacts(0) where guid=me'), 5688 'me/following': yql('select * from social.contacts(0) where guid=me') 5689 }, 5690 wrap: { 5691 me: formatUser, 5692 5693 // Can't get IDs 5694 // It might be better to loop through the social.relationship table with has unique IDs of users. 5695 'me/friends': formatFriends, 5696 'me/following': formatFriends, 5697 'default': paging 5698 } 5699 } 5700 }); 5701 5702 /* 5703 // Auto-refresh fix: bug in Yahoo can't get this to work with node-oauth-shim 5704 login : function(o){ 5705 // Is the user already logged in 5706 var auth = hello('yahoo').getAuthResponse(); 5707 5708 // Is this a refresh token? 5709 if(o.options.display==='none'&&auth&&auth.access_token&&auth.refresh_token){ 5710 // Add the old token and the refresh token, including path to the query 5711 // See http://developer.yahoo.com/oauth/guide/oauth-refreshaccesstoken.html 5712 o.qs.access_token = auth.access_token; 5713 o.qs.refresh_token = auth.refresh_token; 5714 o.qs.token_url = 'https://api.login.yahoo.com/oauth/v2/get_token'; 5715 } 5716 }, 5717 */ 5718 5719 function formatError(o) { 5720 if (o && 'meta' in o && 'error_type' in o.meta) { 5721 o.error = { 5722 code: o.meta.error_type, 5723 message: o.meta.error_message 5724 }; 5725 } 5726 } 5727 5728 function formatUser(o) { 5729 5730 formatError(o); 5731 if (o.query && o.query.results && o.query.results.profile) { 5732 o = o.query.results.profile; 5733 o.id = o.guid; 5734 o.last_name = o.familyName; 5735 o.first_name = o.givenName || o.nickname; 5736 var a = []; 5737 if (o.first_name) { 5738 a.push(o.first_name); 5739 } 5740 5741 if (o.last_name) { 5742 a.push(o.last_name); 5743 } 5744 5745 o.name = a.join(' '); 5746 o.email = (o.emails && o.emails[0]) ? o.emails[0].handle : null; 5747 o.thumbnail = o.image ? o.image.imageUrl : null; 5748 } 5749 5750 return o; 5751 } 5752 5753 function formatFriends(o, headers, request) { 5754 formatError(o); 5755 paging(o, headers, request); 5756 var contact; 5757 var field; 5758 if (o.query && o.query.results && o.query.results.contact) { 5759 o.data = o.query.results.contact; 5760 delete o.query; 5761 5762 if (!Array.isArray(o.data)) { 5763 o.data = [o.data]; 5764 } 5765 5766 o.data.forEach(formatFriend); 5767 } 5768 5769 return o; 5770 } 5771 5772 function formatFriend(contact) { 5773 contact.id = null; 5774 5775 // #362: Reports of responses returning a single item, rather than an Array of items. 5776 // Format the contact.fields to be an array. 5777 if (contact.fields && !(contact.fields instanceof Array)) { 5778 contact.fields = [contact.fields]; 5779 } 5780 5781 (contact.fields || []).forEach(function(field) { 5782 if (field.type === 'email') { 5783 contact.email = field.value; 5784 } 5785 5786 if (field.type === 'name') { 5787 contact.first_name = field.value.givenName; 5788 contact.last_name = field.value.familyName; 5789 contact.name = field.value.givenName + ' ' + field.value.familyName; 5790 } 5791 5792 if (field.type === 'yahooid') { 5793 contact.id = field.value; 5794 } 5795 }); 5796 } 5797 5798 function paging(res, headers, request) { 5799 5800 // See: http://developer.yahoo.com/yql/guide/paging.html#local_limits 5801 if (res.query && res.query.count && request.options) { 5802 res.paging = { 5803 next: '?start=' + (res.query.count + (+request.options.start || 1)) 5804 }; 5805 } 5806 5807 return res; 5808 } 5809 5810 function yql(q) { 5811 return 'https://query.yahooapis.com/v1/yql?q=' + (q + ' limit @{limit|100} offset @{start|0}').replace(/\s/g, '%20') + '&format=json'; 5812 } 5813 5814})(hello); 5815 5816// Register as anonymous AMD module 5817if (typeof define === 'function' && define.amd) { 5818 define(function() { 5819 return hello; 5820 }); 5821} 5822 5823// CommonJS module for browserify 5824if (typeof module === 'object' && module.exports) { 5825 module.exports = hello; 5826}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.