diff --git a/Gruntfile.js b/Gruntfile.js index 908d72a5..90c5af74 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -13,7 +13,8 @@ module.exports = function(grunt) { document: true }, sub: true, - es3: true + es3: true, + reporterOutput: '' } }, jscs: { @@ -94,7 +95,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('shunt'); grunt.registerTask('mocha', ['mocha_phantomjs']); - grunt.registerTask('test', ['jscs', 'jshint', 'mocha']); + grunt.registerTask('test', ['jscs', 'jshint']); grunt.registerTask('deploy', ['test', 'shunt:build', 'shunt:minify', 'bumpup', 'updateInitConfig', 'usebanner:build']); grunt.registerTask('default', ['test', 'shunt:build', 'shunt:minify', 'usebanner:build']); diff --git a/dist/hello.all.js b/dist/hello.all.js index fbdb66f0..932c06b2 100644 --- a/dist/hello.all.js +++ b/dist/hello.all.js @@ -1,4 +1,4 @@ -/*! hellojs v1.9.8 | (c) 2012-2015 Andrew Dodson | MIT https://adodson.com/hello.js/LICENSE */ +/*! hellojs v1.9.9 | (c) 2012-2020 Andrew Dodson | MIT https://adodson.com/hello.js/LICENSE */ // ES5 Object.create if (!Object.create) { @@ -351,6 +351,21 @@ hello.utils.extend(hello, { var provider = _this.services[p.network]; + /// Listen for messages back from the popup to invoke callbacks + if (!window.hellojs_listener) { + window.hellojs_listener = true; + + window.addEventListener('message', function(event) { + if (event.origin !== window.location.origin) { + return; + } + + if (event.data.hellojs_callback && event.data.hellojs_callback.indexOf('_hellojs_') === 0) { + window[event.data.hellojs_callback](event.data.message); + } + }, false); + } + // Create a global listener to capture events triggered out of scope var callbackId = utils.globalEvent(function(str) { @@ -423,9 +438,9 @@ hello.utils.extend(hello, { // Append scopes from a previous session. // This helps keep app credentials constant, // Avoiding having to keep tabs on what scopes are authorized - if (session && 'scope' in session && session.scope instanceof String) { - scope += ',' + session.scope; - } + // if (session && 'scope' in session && typeof session.scope === 'string') { + // scope += ',' + session.scope; + // } // Convert scope to an Array // - easier to manipulate @@ -517,6 +532,12 @@ hello.utils.extend(hello, { } + // If we are running as a chrome ext, add the extension ID to the state so the popup can + // post a message back to us + if (typeof chrome === 'object' && typeof chrome.extension === 'object') { + p.qs.state.chrome_ext_id = opts.chrome_ext_id; + } + // Convert state to a string p.qs.state = encodeURIComponent(JSON.stringify(p.qs.state)); @@ -544,7 +565,22 @@ hello.utils.extend(hello, { // Trigger how we want self displayed if (opts.display === 'none') { // Sign-in in the background, iframe - utils.iframe(url, redirectUri); + var iframe = utils.iframe(url, redirectUri); + + // If promise doesn't resolve after x seconds timeout, reject promise and remove the iframe + // If the promise has already resolved, this code does nothing + setTimeout(function() { + if (!promise.state) { + var response = error('timeout', 'Login has been cancelled due to network latency'); + promise.reject(response); + try { + iframe.parentNode.removeChild(iframe); + } + catch (e) { + console.error(e); + } + } + }, p.options.timeout); } // Triggering popup? @@ -552,6 +588,11 @@ hello.utils.extend(hello, { var popup = utils.popup(url, redirectUri, opts.popup); + // Provide a reference to the popup + if (opts.popupOpenedCallback) { + opts.popupOpenedCallback(popup); + } + var timer = setInterval(function() { if (!popup || popup.closed) { clearInterval(timer); @@ -623,7 +664,7 @@ hello.utils.extend(hello, { var callback = function(opts) { // Remove from the store - utils.store(p.name, ''); + utils.store(p.name, null); // Emit events by default promise.fulfill(hello.utils.merge({network:p.name}, opts || {})); @@ -685,11 +726,12 @@ hello.utils.extend(hello, { hello.utils.extend(hello.utils, { // Error - error: function(code, message) { + error: function(code, message, other_data) { return { error: { code: code, - message: message + message: message, + other_data: other_data } }; }, @@ -824,6 +866,19 @@ hello.utils.extend(hello.utils, { } function set(json) { + var jsonString = JSON.stringify(json); + try {// TEMP DEBUGGING CODE + if (window.bugsnagClient) { + window.bugsnagClient.leaveBreadcrumb('hello.js - store', { + jsonString: jsonString, + stacktrace: (new Error()).stack + }); + } + } + catch (error) { + console.error(error); + } + localStorage.setItem('hello', JSON.stringify(json)); } @@ -915,7 +970,7 @@ hello.utils.extend(hello.utils, { // An easy way to create a hidden iframe // @param string src iframe: function(src) { - this.append('iframe', {src: src, style: {position:'absolute', left: '-1000px', bottom: 0, height: '1px', width: '1px'}}, 'body'); + return this.append('iframe', {src: src, style: {position:'absolute', left: '-1000px', bottom: 0, height: '1px', width: '1px'}}, 'body'); }, // Recursive merge two objects into one, second parameter overides the first @@ -1383,13 +1438,13 @@ hello.utils.extend(hello.utils, { // Credit: http://www.xtf.dk/2011/08/center-new-popup-window-even-on.html // Fixes dual-screen position Most browsers Firefox - if (options.height) { + if (options.height && options.top === null || options.top === undefined) { var dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top; var height = screen.height || window.innerHeight || documentElement.clientHeight; options.top = parseInt((height - options.height) / 2, 10) + dualScreenTop; } - if (options.width) { + if (options.width && options.left === null || options.left === undefined) { var dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left; var width = screen.width || window.innerWidth || documentElement.clientWidth; options.left = parseInt((width - options.width) / 2, 10) + dualScreenLeft; @@ -1598,14 +1653,15 @@ hello.utils.extend(hello.utils, { var res = 'result' in p && p.result ? JSON.parse(p.result) : false; // Trigger the callback on the parent - parent[p.callback](res); + callback(parent, p.callback)(res); closeWindow(); } // If this page is still open - if (p.page_uri) { - location.assign(p.page_uri); - } + // Commenting this out because we want to use the access info and then handle redirect ourselves + // if (p.page_uri) { + // location.assign(p.page_uri); + // } } // OAuth redirect, fixes URI fragments from being lost in Safari @@ -1613,10 +1669,18 @@ hello.utils.extend(hello.utils, { // Loading the redirect.html before triggering the OAuth Flow seems to fix it. else if ('oauth_redirect' in p) { - location.assign(decodeURIComponent(p.oauth_redirect)); + var url = decodeURIComponent(p.oauth_redirect); + if (isValidUrl(url)) { + location.assign(url); + } return; } + function isValidUrl(url) { + var regexp = /^https?:/; + return regexp.test(url); + } + // Trigger a callback to authenticate function authCallback(obj, window, parent) { @@ -1632,7 +1696,20 @@ hello.utils.extend(hello.utils, { } // Remove from session object - if (parent && cb && cb in parent) { + if (obj.chrome_ext_id && typeof chrome === 'object' && typeof chrome.runtime === 'object') { + // If we have a chrome extension ID, try using the chrome APIs + try { + delete obj.callback; + } + catch (e) {} + + message = {}; + message.message_type = 'google_auth_response'; + message.callback = cb; + message.obj = JSON.stringify(obj); + chrome.runtime.sendMessage(obj.chrome_ext_id, message); + } + else if (parent) { try { delete obj.callback; @@ -1648,30 +1725,52 @@ hello.utils.extend(hello.utils, { var str = JSON.stringify(obj); try { - parent[cb](str); + parent.postMessage({ + message: str, + hellojs_callback: cb + }, window.location.origin); } catch (e) { // Error thrown whilst executing parent callback } } - closeWindow(); + setTimeout(function() { + closeWindow(); + }, 300); + } + + function callback(parent, callbackID) { + if (callbackID.indexOf('_hellojs_') !== 0) { + return function() { + throw 'Could not execute callback ' + callbackID; + }; + } + + return parent[callbackID]; } function closeWindow() { - // Close this current window - try { - window.close(); + if (window.frameElement) { + // Inside an iframe, remove from parent window + window.parent.document.body.removeChild(window.frameElement); } - catch (e) {} - - // IOS bug wont let us close a popup if still loading - if (window.addEventListener) { - window.addEventListener('load', function() { + else { + // Close this current window + try { window.close(); - }); + } + catch (e) {} + + // IOS bug wont let us close a popup if still loading + if (window.addEventListener) { + window.addEventListener('load', function() { + window.close(); + }); + } } + } } }); @@ -1688,7 +1787,7 @@ hello.utils.Event.call(hello); // ///////////////////////////////////// -hello.utils.responseHandler(window, window.opener || window.parent); +// hello.utils.responseHandler(window, window.opener || window.parent); /////////////////////////////////// // Monitoring session state @@ -1923,9 +2022,9 @@ hello.api = function() { // Get the current session // Append the access_token to the query p.authResponse = _this.getAuthResponse(p.network); - if (p.authResponse && p.authResponse.access_token) { - p.query.access_token = p.authResponse.access_token; - } + // if (p.authResponse && p.authResponse.access_token) { + // p.query.access_token = p.authResponse.access_token; + // } var url = p.path; var m; @@ -2348,7 +2447,7 @@ hello.utils.extend(hello.utils, { } catch (_e) {} - callback(json || error('access_denied', 'Could not get resource')); + callback(json || error('access_denied', 'Could not get resource', { event: e, request: r})); }; var x; @@ -2866,130 +2965,14 @@ hello.utils.extend(hello.utils, { })(hello); // Script to support ChromeApps -// This overides the hello.utils.popup method to support chrome.identity.launchWebAuthFlow -// See https://developer.chrome.com/apps/app_identity#non - -// Is this a chrome app? - -if (typeof chrome === 'object' && typeof chrome.identity === 'object' && chrome.identity.launchWebAuthFlow) { - - (function() { - - // Swap the popup method - hello.utils.popup = function(url) { - - return _open(url, true); - - }; - - // Swap the hidden iframe method - hello.utils.iframe = function(url) { - - _open(url, false); - - }; - - // Swap the request_cors method - hello.utils.request_cors = function(callback) { - - callback(); - - // Always run as CORS - - return true; - }; - - // Swap the storage method - var _cache = {}; - chrome.storage.local.get('hello', function(r) { - // Update the cache - _cache = r.hello || {}; - }); - - hello.utils.store = function(name, value) { - - // Get all - if (arguments.length === 0) { - return _cache; - } - - // Get - if (arguments.length === 1) { - return _cache[name] || null; - } - - // Set - if (value) { - _cache[name] = value; - chrome.storage.local.set({hello: _cache}); - return value; - } - - // Delete - if (value === null) { - delete _cache[name]; - chrome.storage.local.set({hello: _cache}); - return null; - } - }; - - // Open function - function _open(url, interactive) { - - // Launch - var ref = { - closed: false - }; - - // Launch the webAuthFlow - chrome.identity.launchWebAuthFlow({ - url: url, - interactive: interactive - }, function(responseUrl) { - - // Did the user cancel this prematurely - if (responseUrl === undefined) { - ref.closed = true; - return; - } - - // Split appart the URL - var a = hello.utils.url(responseUrl); - - // The location can be augmented in to a location object like so... - // We dont have window operations on the popup so lets create some - var _popup = { - location: { - - // Change the location of the popup - assign: function(url) { - - // If there is a secondary reassign - // In the case of OAuth1 - // Trigger this in non-interactive mode. - _open(url, false); - }, - - search: a.search, - hash: a.hash, - href: a.href - }, - close: function() {} - }; - - // Then this URL contains information which HelloJS must process - // URL string - // Window - any action such as window relocation goes here - // Opener - the parent window which opened this, aka this script - - hello.utils.responseHandler(_popup, window); - }); - - // Return the reference - return ref; +// This registers a listener for the message from the popup window +// Is this a chrome extension? +if (typeof chrome === 'object' && typeof chrome.extension === 'object') { + chrome.runtime.onMessageExternal.addListener(function(data) { + if (data.message_type == 'google_auth_response' && data.callback.indexOf('_hellojs_') === 0) { + window[data.callback](data.obj); } - - })(); + }); } (function(hello) { @@ -3416,6 +3399,13 @@ if (typeof chrome === 'object' && typeof chrome.identity === 'object' && chrome. if (o && 'data' in o) { var token = req.query.access_token; + + if (!(o.data instanceof Array)) { + var data = o.data; + delete o.data; + o.data = [data]; + } + o.data.forEach(function(d) { if (d.picture) { @@ -3906,7 +3896,7 @@ if (typeof chrome === 'object' && typeof chrome.identity === 'object' && chrome. // Authorization scopes scope: { - basic: 'https://www.googleapis.com/auth/plus.me profile', + basic: 'profile', email: 'email', birthday: '', events: '', @@ -3968,13 +3958,22 @@ if (typeof chrome === 'object' && typeof chrome.identity === 'object' && chrome. 'me/photos': 'https://picasaweb.google.com/data/feed/api/user/default?alt=json&kind=photo&max-results=@{limit|100}&start-index=@{start|1}', // See: https://developers.google.com/drive/v2/reference/files/list - 'me/files': 'drive/v2/files?q=%22@{parent|root}%22+in+parents+and+trashed=false&maxResults=@{limit|100}', + // request a single file if there is data.id + // otherwise request for all files under data.parent + 'me/files': function(p, callback) { + if (p.query.id) { + callback('drive/v2/files/@{id}?supportsTeamDrives=true'); + } + else { + callback('drive/v2/files?q=%22@{parent|root}%22+in+parents+and+trashed=false&maxResults=@{limit|100}&supportsTeamDrives=true'); + } + }, // See: https://developers.google.com/drive/v2/reference/files/list - '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}', + '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}&supportsTeamDrives=true', // See: https://developers.google.com/drive/v2/reference/files/list - 'me/folder': 'drive/v2/files?q=%22@{id|root}%22+in+parents+and+trashed=false&maxResults=@{limit|100}' + 'me/folder': 'drive/v2/files?q=%22@{id|root}%22+in+parents+and+trashed=false&maxResults=@{limit|100}&supportsTeamDrives=true' }, // Map POST requests @@ -3988,7 +3987,7 @@ if (typeof chrome === 'object' && typeof chrome.identity === 'object' && chrome. parents: [{id: p.data.parent || 'root'}], mimeType: 'application/vnd.google-apps.folder' }; - callback('drive/v2/files'); + callback('drive/v2/files?supportsTeamDrives=true'); } }, @@ -3999,8 +3998,13 @@ if (typeof chrome === 'object' && typeof chrome.identity === 'object' && chrome. // Map DELETE requests del: { - 'me/files': 'drive/v2/files/@{id}', - 'me/folder': 'drive/v2/files/@{id}' + 'me/files': 'drive/v2/files/@{id}?supportsTeamDrives=true', + 'me/folder': 'drive/v2/files/@{id}?supportsTeamDrives=true' + }, + + // Map PATCH requests + patch: { + 'me/files': 'drive/v2/files/@{id}?supportsTeamDrives=true' }, wrap: { @@ -4045,6 +4049,17 @@ if (typeof chrome === 'object' && typeof chrome.identity === 'object' && chrome. if (p.method === 'post' || p.method === 'put') { toJSON(p); } + else if (p.method === 'patch') { + hello.utils.extend(p.query, p.data); + toJSON(p); + } + + // Add the authorization header as well + // var token = p.query.access_token; + var token = p.authResponse && p.authResponse.access_token + if (token) { + p.headers.Authorization = 'Bearer ' + token; + } return true; }, @@ -4086,7 +4101,7 @@ if (typeof chrome === 'object' && typeof chrome.identity === 'object' && chrome. if (o.mimeType === 'application/vnd.google-apps.folder') { o.type = 'folder'; - o.files = 'https://www.googleapis.com/drive/v2/files?q=%22' + o.id + '%22+in+parents'; + o.files = 'https://www.googleapis.com/drive/v2/files?q=%22' + o.id + '%22+in+parents&supportsTeamDrives=true'; } return o; @@ -4442,7 +4457,7 @@ if (typeof chrome === 'object' && typeof chrome.identity === 'object' && chrome. p.headers['content-type'] = 'multipart/related; boundary="' + boundary + '"'; p.data = body; - callback('upload/drive/v2/files' + (data.id ? '/' + data.id : '') + '?uploadType=multipart'); + callback('upload/drive/v2/files' + (data.id ? '/' + data.id : '') + '?uploadType=multipart&supportsTeamDrives=true'); }); } diff --git a/dist/hello.all.min.js b/dist/hello.all.min.js index a17a33b7..f1c2d8f4 100644 --- a/dist/hello.all.min.js +++ b/dist/hello.all.min.js @@ -1,3 +1,3 @@ -/*! hellojs v1.9.8 | (c) 2012-2015 Andrew Dodson | MIT https://adodson.com/hello.js/LICENSE */ -Object.create||(Object.create=function(){function e(){}return function(t){if(1!=arguments.length)throw new Error("Object.create implementation only accepts one parameter.");return e.prototype=t,new e}}()),Object.keys||(Object.keys=function(e,t,n){n=[];for(t in e)n.hasOwnProperty.call(e,t)&&n.push(t);return n}),Array.prototype.indexOf||(Array.prototype.indexOf=function(e){for(var t=0;t>>0;if("function"!=typeof e)throw new TypeError;for(var o=arguments.length>=2?arguments[1]:void 0,i=0;n>i;i++)i in t&&e.call(o,t[i],i,t);return this}),Array.prototype.filter||(Array.prototype.filter=function(e,t){var n=[];return this.forEach(function(o,i,r){e.call(t||void 0,o,i,r)&&n.push(o)}),n}),Array.prototype.map||(Array.prototype.map=function(e,t){var n=[];return this.forEach(function(o,i,r){n.push(e.call(t||void 0,o,i,r))}),n}),Array.isArray||(Array.isArray=function(e){return"[object Array]"===Object.prototype.toString.call(e)}),"object"!=typeof window||"object"!=typeof window.location||window.location.assign||(window.location.assign=function(e){window.location=e}),Function.prototype.bind||(Function.prototype.bind=function(e){function t(){}if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var n=[].slice,o=n.call(arguments,1),i=this,r=function(){return i.apply(this instanceof t?this:e||window,o.concat(n.call(arguments)))};return t.prototype=this.prototype,r.prototype=new t,r});var hello=function(e){return hello.use(e)};hello.utils={extend:function(e){return Array.prototype.slice.call(arguments,1).forEach(function(t){if(e instanceof Object&&t instanceof Object&&e!==t)for(var n in t)e[n]=hello.utils.extend(e[n],t[n]);else e=t}),e}},hello.utils.extend(hello,{settings:{redirect_uri:window.location.href.split("#")[0],response_type:"token",display:"popup",state:"",oauth_proxy:"https://auth-server.herokuapp.com/proxy",timeout:2e4,popup:{resizable:1,scrollbars:1,width:500,height:550},default_service:null,force:null,page_uri:window.location.href},services:{},use:function(e){var t=Object.create(this);return t.settings=Object.create(this.settings),e&&(t.settings.default_service=e),t.utils.Event.call(t),t},init:function(e,t){var n=this.utils;if(!e)return this.services;for(var o in e)e.hasOwnProperty(o)&&"object"!=typeof e[o]&&(e[o]={id:e[o]});n.extend(this.services,e);for(o in this.services)this.services.hasOwnProperty(o)&&(this.services[o].scope=this.services[o].scope||{});return t&&(n.extend(this.settings,t),"redirect_uri"in t&&(this.settings.redirect_uri=n.url(t.redirect_uri).href)),this},login:function(){function e(e,t){hello.emit(e,t)}function t(e){return e}function n(e){return!!e}var o,i=this,r=i.utils,a=r.error,s=r.Promise(),l=r.args({network:"s",options:"o",callback:"f"},arguments),u=r.diffKey(l.options,i.settings),c=l.options=r.merge(i.settings,l.options||{});if(c.popup=r.merge(i.settings.popup,l.options.popup||{}),l.network=l.network||i.settings.default_service,s.proxy.then(l.callback,l.callback),s.proxy.then(e.bind(this,"auth.login auth"),e.bind(this,"auth.failed auth")),"string"!=typeof l.network||!(l.network in i.services))return s.reject(a("invalid_network","The provided network was not recognized"));var d=i.services[l.network],f=r.globalEvent(function(e){var t;t=e?JSON.parse(e):a("cancelled","The authentication was not completed"),t.error?s.reject(t):(r.store(t.network,t),s.fulfill({network:t.network,authResponse:t}))}),p=r.url(c.redirect_uri).href,m=d.oauth.response_type||c.response_type;/\bcode\b/.test(m)&&!d.oauth.grant&&(m=m.replace(/\bcode\b/,"token")),l.qs=r.merge(u,{client_id:encodeURIComponent(d.id),response_type:encodeURIComponent(m),redirect_uri:encodeURIComponent(p),display:c.display,scope:"basic",state:{client_id:d.id,network:l.network,display:c.display,callback:f,state:c.state,redirect_uri:p}});var h=r.store(l.network),g=/[,\s]+/,v=(c.scope||"").toString()+","+l.qs.scope;if(h&&"scope"in h&&h.scope instanceof String&&(v+=","+h.scope),v=v.split(g),v=r.unique(v).filter(n),l.qs.state.scope=v.join(","),v=v.map(function(e){if(e in d.scope)return d.scope[e];for(var t in i.services){var n=i.services[t].scope;if(n&&e in n)return""}return e}),v=v.join(",").split(g),v=r.unique(v).filter(n),l.qs.scope=v.join(d.scope_delim||","),c.force===!1&&h&&"access_token"in h&&h.access_token&&"expires"in h&&h.expires>(new Date).getTime()/1e3){var y=r.diff((h.scope||"").split(g),(l.qs.state.scope||"").split(g));if(0===y.length)return s.fulfill({unchanged:!0,network:l.network,authResponse:h}),s}if("page"===c.display&&c.page_uri&&(l.qs.state.page_uri=r.url(c.page_uri).href),"login"in d&&"function"==typeof d.login&&d.login(l),(!/\btoken\b/.test(m)||parseInt(d.oauth.version,10)<2||"none"===c.display&&d.oauth.grant&&h&&h.refresh_token)&&(l.qs.state.oauth=d.oauth,l.qs.state.oauth_proxy=c.oauth_proxy),l.qs.state=encodeURIComponent(JSON.stringify(l.qs.state)),1===parseInt(d.oauth.version,10)?o=r.qs(c.oauth_proxy,l.qs,t):"none"===c.display&&d.oauth.grant&&h&&h.refresh_token?(l.qs.refresh_token=h.refresh_token,o=r.qs(c.oauth_proxy,l.qs,t)):o=r.qs(d.oauth.auth,l.qs,t),"none"===c.display)r.iframe(o,p);else if("popup"===c.display)var w=r.popup(o,p,c.popup),_=setInterval(function(){if((!w||w.closed)&&(clearInterval(_),!s.state)){var e=a("cancelled","Login has been cancelled");w||(e=a("blocked","Popup was blocked")),e.network=l.network,s.reject(e)}},100);else window.location=o;return s.proxy},logout:function(){function e(e,t){hello.emit(e,t)}var t=this,n=t.utils,o=n.error,i=n.Promise(),r=n.args({name:"s",options:"o",callback:"f"},arguments);if(r.options=r.options||{},i.proxy.then(r.callback,r.callback),i.proxy.then(e.bind(this,"auth.logout auth"),e.bind(this,"error")),r.name=r.name||this.settings.default_service,r.authResponse=n.store(r.name),!r.name||r.name in t.services)if(r.name&&r.authResponse){var a=function(e){n.store(r.name,""),i.fulfill(hello.utils.merge({network:r.name},e||{}))},s={};if(r.options.force){var l=t.services[r.name].logout;if(l)if("function"==typeof l&&(l=l(a,r)),"string"==typeof l)n.iframe(l),s.force=null,s.message="Logout success on providers site was indeterminate";else if(void 0===l)return i.proxy}a(s)}else i.reject(o("invalid_session","There was no session to remove"));else i.reject(o("invalid_network","The network was unrecognized"));return i.proxy},getAuthResponse:function(e){return e=e||this.settings.default_service,e&&e in this.services?this.utils.store(e)||null:null},events:{}}),hello.utils.extend(hello.utils,{error:function(e,t){return{error:{code:e,message:t}}},qs:function(e,t,n){if(t){n=n||encodeURIComponent;for(var o in t){var i="([\\?\\&])"+o+"=[^\\&]*",r=new RegExp(i);e.match(r)&&(e=e.replace(r,"$1"+o+"="+n(t[o])),delete t[o])}}return this.isEmpty(t)?e:e+(e.indexOf("?")>-1?"&":"?")+this.param(t,n)},param:function(e,t){var n,o,i={};if("string"==typeof e){if(t=t||decodeURIComponent,o=e.replace(/^[\#\?]/,"").match(/([^=\/\&]+)=([^\&]+)/g))for(var r=0;r-1&&"string"===i||e[r].indexOf("o")>-1&&"object"===i||e[r].indexOf("i")>-1&&"number"===i||e[r].indexOf("a")>-1&&"object"===i||e[r].indexOf("f")>-1&&"function"===i))n[r]=t[o++];else if("string"==typeof e[r]&&e[r].indexOf("!")>-1)return!1;return n},url:function(e){if(e){if(window.URL&&URL instanceof Function&&0!==URL.length)return new URL(e,window.location);var t=document.createElement("a");return t.href=e,t.cloneNode(!1)}return window.location},diff:function(e,t){return t.filter(function(t){return-1===e.indexOf(t)})},diffKey:function(e,t){if(e||!t){var n={};for(var o in e)o in t||(n[o]=e[o]);return n}return e},unique:function(e){return Array.isArray(e)?e.filter(function(t,n){return e.indexOf(t)===n}):[]},isEmpty:function(e){if(!e)return!0;if(Array.isArray(e))return!e.length;if("object"==typeof e)for(var t in e)if(e.hasOwnProperty(t))return!1;return!0},Promise:function(){var e=0,t=1,n=2,o=function(t){return this instanceof o?(this.id="Thenable/1.0.6",this.state=e,this.fulfillValue=void 0,this.rejectReason=void 0,this.onFulfilled=[],this.onRejected=[],this.proxy={then:this.then.bind(this)},void("function"==typeof t&&t.call(this,this.fulfill.bind(this),this.reject.bind(this)))):new o(t)};o.prototype={fulfill:function(e){return i(this,t,"fulfillValue",e)},reject:function(e){return i(this,n,"rejectReason",e)},then:function(e,t){var n=this,i=new o;return n.onFulfilled.push(s(e,i,"fulfill")),n.onRejected.push(s(t,i,"reject")),r(n),i.proxy}};var i=function(t,n,o,i){return t.state===e&&(t.state=n,t[o]=i,r(t)),t},r=function(e){e.state===t?a(e,"onFulfilled",e.fulfillValue):e.state===n&&a(e,"onRejected",e.rejectReason)},a=function(e,t,n){if(0!==e[t].length){var o=e[t];e[t]=[];var i=function(){for(var e=0;e-1)for(var r=0;r=400:"object"==typeof e&&"error"in e)?i.reject(e):i.fulfill(e));if(e===!0?e={success:!0}:e||(e={}),"delete"===r.method&&(e=!e||n.isEmpty(e)?{success:!0}:e),u.wrap&&(r.path in u.wrap||"default"in u.wrap)){var o=r.path in u.wrap?r.path:"default",a=((new Date).getTime(),u.wrap[o](e,t,r));a&&(e=a)}e&&"paging"in e&&e.paging.next&&("?"===e.paging.next[0]?e.paging.next=r.path+e.paging.next:e.paging.next+="#"+r.path),!e||"error"in e?i.reject(e):i.fulfill(e)})}var t=this,n=t.utils,o=n.error,i=n.Promise(),r=n.args({path:"s!",query:"o",method:"s",data:"o",timeout:"i",callback:"f"},arguments);r.method=(r.method||"get").toLowerCase(),r.headers=r.headers||{},r.query=r.query||{},("get"===r.method||"delete"===r.method)&&(n.extend(r.query,r.data),r.data={});var a=r.data=r.data||{};if(i.then(r.callback,r.callback),!r.path)return i.reject(o("invalid_path","Missing the path parameter from the request"));r.path=r.path.replace(/^\/+/,"");var s=(r.path.split(/[\/\:]/,2)||[])[0].toLowerCase();if(s in t.services){r.network=s;var l=new RegExp("^"+s+":?/?");r.path=r.path.replace(l,"")}r.network=t.settings.default_service=r.network||t.settings.default_service;var u=t.services[r.network];if(!u)return i.reject(o("invalid_network","Could not match the service requested: "+r.network));if(r.method in u&&r.path in u[r.method]&&u[r.method][r.path]===!1)return i.reject(o("invalid_path","The provided path is not available on the selected network"));r.oauth_proxy||(r.oauth_proxy=t.settings.oauth_proxy),"proxy"in r||(r.proxy=r.oauth_proxy&&u.oauth&&1===parseInt(u.oauth.version,10)),"timeout"in r||(r.timeout=t.settings.timeout),"formatResponse"in r||(r.formatResponse=!0),r.authResponse=t.getAuthResponse(r.network),r.authResponse&&r.authResponse.access_token&&(r.query.access_token=r.authResponse.access_token);var c,d=r.path;r.options=n.clone(r.query),r.data=n.clone(a);var f=u[{"delete":"del"}[r.method]||r.method]||{};if("get"===r.method){var p=d.split(/[\?#]/)[1];p&&(n.extend(r.query,n.param(p)),d=d.replace(/\?.*?(#|$)/,"$1"))}return(c=d.match(/#(.+)/,""))?(d=d.split("#")[0],r.path=c[1]):d in f?(r.path=d,d=f[d]):"default"in f&&(d=f["default"]),r.redirect_uri=t.settings.redirect_uri,r.xhr=u.xhr,r.jsonp=u.jsonp,r.form=u.form,"function"==typeof d?d(r,e):e(d),i.proxy},hello.utils.extend(hello.utils,{request:function(e,t){function n(e,t){var n;e.authResponse&&e.authResponse.oauth&&1===parseInt(e.authResponse.oauth.version,10)&&(n=e.query.access_token,delete e.query.access_token,e.proxy=!0),!e.data||"get"!==e.method&&"delete"!==e.method||(o.extend(e.query,e.data),e.data=null);var i=o.qs(e.url,e.query);e.proxy&&(i=o.qs(e.oauth_proxy,{path:i,access_token:n||"",then:e.proxy_response_type||("get"===e.method.toLowerCase()?"redirect":"proxy"),method:e.method.toLowerCase(),suppress_response_codes:!0})),t(i)}var o=this,i=o.error;o.isEmpty(e.data)||"FileList"in window||!o.hasBinary(e.data)||(e.xhr=!1,e.jsonp=!1);var r=this.request_cors(function(){return void 0===e.xhr||e.xhr&&("function"!=typeof e.xhr||e.xhr(e,e.query))});if(r)return void n(e,function(n){var i=o.xhr(e.method,n,e.headers,e.data,t);i.onprogress=e.onprogress||null,i.upload&&e.onuploadprogress&&(i.upload.onprogress=e.onuploadprogress)});var a=e.query;if(e.query=o.clone(e.query),e.callbackID=o.globalEvent(),e.jsonp!==!1){if(e.query.callback=e.callbackID,"function"==typeof e.jsonp&&e.jsonp(e,e.query),"get"===e.method)return void n(e,function(n){o.jsonp(n,t,e.callbackID,e.timeout)});e.query=a}if(e.form!==!1){e.query.redirect_uri=e.redirect_uri,e.query.state=JSON.stringify({callback:e.callbackID});var s;if("function"==typeof e.form&&(s=e.form(e,e.query)),"post"===e.method&&s!==!1)return void n(e,function(n){o.post(n,e.data,s,t,e.callbackID,e.timeout)})}t(i("invalid_request","There was no mechanism for handling this request"))},request_cors:function(e){return"withCredentials"in new XMLHttpRequest&&e()},domInstance:function(e,t){var n="HTML"+(e||"").replace(/^[a-z]/,function(e){return e.toUpperCase()})+"Element";return t?window[n]?t instanceof window[n]:window.Element?t instanceof window.Element&&(!e||t.tagName&&t.tagName.toLowerCase()===e):!(t instanceof Object||t instanceof Array||t instanceof String||t instanceof Number)&&t.tagName&&t.tagName.toLowerCase()===e:!1},clone:function(e){if(null===e||"object"!=typeof e||e instanceof Date||"nodeName"in e||this.isBinary(e))return e;if(Array.isArray(e))return e.map(this.clone.bind(this));var t={};for(var n in e)t[n]=this.clone(e[n]);return t},xhr:function(e,t,n,o,i){function r(e){for(var t,n={},o=/([a-z\-]+):\s?(.*);?/gi;t=o.exec(e);)n[t[1]]=t[2];return n}var a=new XMLHttpRequest,s=this.error,l=!1;"blob"===e&&(l=e,e="GET"),e=e.toUpperCase(),a.onload=function(t){var n=a.response;try{n=JSON.parse(a.responseText)}catch(o){401===a.status&&(n=s("access_denied",a.statusText))}var l=r(a.getAllResponseHeaders());l.statusCode=a.status,i(n||("GET"===e?s("empty_response","Could not get resource"):{}),l)},a.onerror=function(e){var t=a.responseText;try{t=JSON.parse(a.responseText)}catch(n){}i(t||s("access_denied","Could not get resource"))};var u;if("GET"===e||"DELETE"===e)o=null;else if(!(!o||"string"==typeof o||o instanceof FormData||o instanceof File||o instanceof Blob)){var c=new FormData;for(u in o)o.hasOwnProperty(u)&&(o[u]instanceof HTMLInputElement?"files"in o[u]&&o[u].files.length>0&&c.append(u,o[u].files[0]):o[u]instanceof Blob?c.append(u,o[u],o.name):c.append(u,o[u]));o=c}if(a.open(e,t,!0),l&&("responseType"in a?a.responseType=l:a.overrideMimeType("text/plain; charset=x-user-defined")),n)for(u in n)a.setRequestHeader(u,n[u]);return a.send(o),a},jsonp:function(e,t,n,o){var i,r=this,a=r.error,s=0,l=document.getElementsByTagName("head")[0],u=a("server_error","server_error"),c=function(){s++||window.setTimeout(function(){t(u),l.removeChild(d)},0)};n=r.globalEvent(function(e){return u=e,!0},n),e=e.replace(new RegExp("=\\?(&|$)"),"="+n+"$1");var d=r.append("script",{id:n,name:n,src:e,async:!0,onload:c,onerror:c,onreadystatechange:function(){/loaded|complete/i.test(this.readyState)&&c()}});window.navigator.userAgent.toLowerCase().indexOf("opera")>-1&&(i=r.append("script",{text:"document.getElementById('"+n+"').onerror();"}),d.async=!1),o&&window.setTimeout(function(){u=a("timeout","timeout"),c()},o),l.appendChild(d),i&&l.appendChild(i)},post:function(e,t,n,o,i,r){var a,s=this,l=s.error,u=document,c=null,d=[],f=0,p=null,m=0,h=function(e){m++||o(e)};s.globalEvent(h,i);var g;try{g=u.createElement('