From 61df75f87a81ef7fa78f073cb2a1d712e540bf16 Mon Sep 17 00:00:00 2001 From: Andrew Dodson Date: Wed, 29 Jul 2015 17:19:25 +0100 Subject: [PATCH] Release v1.8.0, ChromeApp support #233, VK.com module #289 --- Gruntfile.js | 3 + dist/hello.all.js | 204 +++++++++++++++++++++++++++++++++++++++--- dist/hello.all.min.js | 6 +- dist/hello.js | 111 ++++++++++++++++++++--- dist/hello.min.js | 4 +- package.json | 2 +- 6 files changed, 300 insertions(+), 30 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index d41ff778..72ce4f37 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -34,12 +34,14 @@ module.exports = function(grunt) { 'dist/hello.js': [ 'src/hello.polyfill.js', 'src/hello.js', + 'src/hello.chromeapp.js', 'src/hello.amd.js', 'src/hello.commonjs.js' ], 'dist/hello.all.js': [ 'src/hello.polyfill.js', 'src/hello.js', + 'src/hello.chromeapp.js', 'src/modules/dropbox.js', 'src/modules/facebook.js', 'src/modules/flickr.js', @@ -50,6 +52,7 @@ module.exports = function(grunt) { 'src/modules/linkedin.js', 'src/modules/soundcloud.js', 'src/modules/twitter.js', + 'src/modules/vk.js', 'src/modules/windows.js', 'src/modules/yahoo.js', 'src/hello.amd.js', diff --git a/dist/hello.all.js b/dist/hello.all.js index db454f2b..4c492d32 100644 --- a/dist/hello.all.js +++ b/dist/hello.all.js @@ -1,4 +1,4 @@ -/*! hellojs v1.7.5 | (c) 2012-2015 Andrew Dodson | MIT https://adodson.com/hello.js/LICENSE */ +/*! hellojs v1.8.0 | (c) 2012-2015 Andrew Dodson | MIT https://adodson.com/hello.js/LICENSE */ // ES5 Object.create if (!Object.create) { @@ -607,13 +607,14 @@ hello.utils.extend(hello, { // Network p.name = p.name || this.settings.default_service; + p.authResponse = utils.store(p.name); if (p.name && !(p.name in _this.services)) { promise.reject(error('invalid_network', 'The network was unrecognized')); } - else if (p.name && utils.store(p.name)) { + else if (p.name && p.authResponse) { // Define the callback var callback = function(opts) { @@ -633,7 +634,7 @@ hello.utils.extend(hello, { // Convert logout to URL string, // If no string is returned, then this function will handle the logout async style if (typeof (logout) === 'function') { - logout = logout(callback); + logout = logout(callback, p); } // If logout is a string then assume URL and open in iframe. @@ -1899,12 +1900,11 @@ hello.api = function() { p.timeout = _this.settings.timeout; } - // // Get the current session // Append the access_token to the query - var session = _this.getAuthResponse(p.network); - if (session && session.access_token) { - p.query.access_token = session.access_token; + p.authResponse = _this.getAuthResponse(p.network); + if (p.authResponse && p.authResponse.access_token) { + p.query.access_token = p.authResponse.access_token; } var url = p.path; @@ -2081,10 +2081,12 @@ hello.utils.extend(hello.utils, { } // Check if the browser and service support CORS - if ( - 'withCredentials' in new XMLHttpRequest() && - (!('xhr' in p) || (p.xhr && (typeof (p.xhr) !== 'function' || p.xhr(p, p.query)))) - ) { + var cors = this.request_cors(function() { + // If it does then run this... + return (!('xhr' in p) || (p.xhr && (typeof (p.xhr) !== 'function' || p.xhr(p, p.query)))); + }); + + if (cors) { formatUrl(p, function(url) { @@ -2221,6 +2223,11 @@ hello.utils.extend(hello.utils, { } }, + // Test whether the browser supports the CORS response + request_cors: function(callback) { + return 'withCredentials' in new XMLHttpRequest() && callback(); + }, + // Return the type of DOM object domInstance: function(type, data) { var test = 'HTML' + (type || '').replace( @@ -2824,6 +2831,88 @@ 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) { + + _open(url, true); + + return { + closed: false + }; + }; + + // Swap the request_cors method + + hello.utils.request_cors = function(callback) { + + callback(); + + // Always run as CORS + + return true; + }; + + // Open function + function _open(url, interactive) { + + // Launch + + chrome.identity.launchWebAuthFlow({ + url: url, + interactive: interactive + }, function(responseUrl) { + + // 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); + }); + } + + })(); +} + (function(hello) { hello.init({ @@ -3065,6 +3154,7 @@ hello.utils.extend(hello.utils, { scope: { basic: 'public_profile', email: 'email', + share: 'user_posts', birthday: 'user_birthday', events: 'user_events', photos: 'user_photos,user_videos', @@ -3101,11 +3191,11 @@ hello.utils.extend(hello.utils, { p.options.popup.height = 400; }, - logout: function(callback) { + logout: function(callback, options) { // Assign callback to a global handler var callbackID = hello.utils.globalEvent(callback); var redirect = encodeURIComponent(hello.settings.redirect_uri + '?' + hello.utils.param({callback:callbackID, result: JSON.stringify({force:true}), state: '{}'})); - var token = (hello.utils.store('facebook') || {}).access_token; + var token = (options.authResponse || {}).access_token; hello.utils.iframe('https://www.facebook.com/logout.php?next=' + redirect + '&access_token=' + token); // Possible responses: @@ -4930,6 +5020,94 @@ hello.utils.extend(hello.utils, { })(hello); +// Vkontakte (vk.com) +(function(hello) { + + hello.init({ + + vk: { + name: 'Vk', + + // See https://vk.com/dev/oauth_dialog + oauth: { + version: 2, + auth: 'https://oauth.vk.com/authorize', + grant: 'https://oauth.vk.com/access_token' + }, + + // Authorization scopes + scope: { + basic: '', + email: 'email', + offline_access: 'offline' + }, + + // Refresh the access_token + refresh: true, + + login: function(p) { + p.qs.display = window.navigator && + window.navigator.userAgent && + /ipad|phone|phone|android/.test(window.navigator.userAgent.toLowerCase()) ? 'mobile' : 'popup'; + }, + + // API Base URL + base: 'https://api.vk.com/method/', + + // Map GET requests + get: { + me: function(p, callback) { + p.query.fields = 'id,first_name,last_name,photo_max'; + callback('users.get'); + } + }, + + wrap: { + me: function(res, headers, req) { + formatError(res); + return formatUser(res, req); + } + }, + + // No XHR + xhr: false, + + // All requests should be JSONP as of missing CORS headers in https://api.vk.com/method/* + jsonp: true, + + // No form + form: false + } + }); + + function formatUser(o, req) { + + if (o !== null && 'response' in o && o.response !== null && o.response.length) { + o = o.response[0]; + o.id = o.uid; + o.thumbnail = o.picture = o.photo_max; + o.name = o.first_name + ' ' + o.last_name; + + if (req.authResponse && req.authResponse.email !== null) + o.email = req.authResponse.email; + } + + return o; + } + + function formatError(o) { + + if (o.error) { + var e = o.error; + o.error = { + code: e.error_code, + message: e.error_msg + }; + } + } + +})(hello); + (function(hello) { hello.init({ diff --git a/dist/hello.all.min.js b/dist/hello.all.min.js index 7bf1dbac..0f9b8c83 100644 --- a/dist/hello.all.min.js +++ b/dist/hello.all.min.js @@ -1,3 +1,3 @@ -/*! hellojs v1.7.5 | (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=l.options=r.merge(i.settings,l.options||{});if(u.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 c=i.services[l.network],d=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}))}),f=r.url(u.redirect_uri).href,p=c.oauth.response_type||u.response_type;/\bcode\b/.test(p)&&!c.oauth.grant&&(p=p.replace(/\bcode\b/,"token")),l.qs={client_id:encodeURIComponent(c.id),response_type:encodeURIComponent(p),redirect_uri:encodeURIComponent(f),display:u.display,scope:"basic",state:{client_id:c.id,network:l.network,display:u.display,callback:d,state:u.state,redirect_uri:f}};var m=r.store(l.network),h=/[,\s]+/,g=(u.scope||"").toString()+","+l.qs.scope;if(m&&"scope"in m&&m.scope instanceof String&&(g+=","+m.scope),g=g.split(h),g=r.unique(g).filter(n),l.qs.state.scope=g.join(","),g=g.map(function(e){if(e in c.scope)return c.scope[e];for(var t in i.services){var n=i.services[t].scope;if(n&&e in n)return""}return e}),g=g.join(",").split(h),g=r.unique(g).filter(n),l.qs.scope=g.join(c.scope_delim||","),u.force===!1&&m&&"access_token"in m&&m.access_token&&"expires"in m&&m.expires>(new Date).getTime()/1e3){var v=r.diff((m.scope||"").split(h),(l.qs.state.scope||"").split(h));if(0===v.length)return s.fulfill({unchanged:!0,network:l.network,authResponse:m}),s}if("page"===u.display&&u.page_uri&&(l.qs.state.page_uri=r.url(u.page_uri).href),"login"in c&&"function"==typeof c.login&&c.login(l),(!/\btoken\b/.test(p)||parseInt(c.oauth.version,10)<2||"none"===u.display&&c.oauth.grant&&m&&m.refresh_token)&&(l.qs.state.oauth=c.oauth,l.qs.state.oauth_proxy=u.oauth_proxy),l.qs.state=encodeURIComponent(JSON.stringify(l.qs.state)),1===parseInt(c.oauth.version,10)?o=r.qs(u.oauth_proxy,l.qs,t):"none"===u.display&&c.oauth.grant&&m&&m.refresh_token?(l.qs.refresh_token=m.refresh_token,o=r.qs(u.oauth_proxy,l.qs,t)):o=r.qs(c.oauth.auth,l.qs,t),"none"===u.display)r.iframe(o);else if("popup"===u.display)var y=r.popup(o,f,u.popup),w=setInterval(function(){if((!y||y.closed)&&(clearInterval(w),!s.state)){var e=a("cancelled","Login has been cancelled");y||(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.name||r.name in t.services)if(r.name&&n.store(r.name)){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)),"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+"=[^\\&]*";reg=new RegExp(i),e.match(o)&&(e=e.replace(reg,"$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}return window.location},diff:function(e,t){return t.filter(function(t){return-1===e.indexOf(t)})},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;r0&&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('"+callbackId+"').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('