From d20848ae0ee0ab9ed4284b9853ef0b989a18dbcb Mon Sep 17 00:00:00 2001 From: Andrew Dodson Date: Mon, 25 Nov 2013 20:37:00 +1100 Subject: [PATCH 001/845] logout, remove session from a remote service, issue #18 --- demos/profile.html | 2 +- demos/sdk/facebook.html | 52 +++++++++++++++++++++++++++++++ demos/sdk/google.html | 65 +++++++++++++++++++++++++++++++++++++++ demos/sdk/linkedIn.html | 49 +++++++++++++++++++++++++++++ demos/sdk/live.html | 7 +++++ demos/sdk/soundcloud.html | 23 ++++++++++++++ src/hello.js | 41 ++++++++++++++++++++---- src/modules/facebook.js | 7 +++++ src/modules/windows.js | 4 +++ 9 files changed, 243 insertions(+), 7 deletions(-) create mode 100644 demos/sdk/facebook.html create mode 100644 demos/sdk/google.html create mode 100644 demos/sdk/linkedIn.html create mode 100644 demos/sdk/soundcloud.html diff --git a/demos/profile.html b/demos/profile.html index 90f8e898..3f103c27 100644 --- a/demos/profile.html +++ b/demos/profile.html @@ -1,6 +1,6 @@ - + diff --git a/demos/sdk/facebook.html b/demos/sdk/facebook.html new file mode 100644 index 00000000..87f46bc8 --- /dev/null +++ b/demos/sdk/facebook.html @@ -0,0 +1,52 @@ + + + + +
+

Facebook proprietary SDK

+ +

SignIn

+ + +

SignOut: of this app and Facebook.com

+ + +

Include the SDK's

+ + +

FB.Event.subscribe

+ + +

FB.init

+ + +

FB.init

+ \ No newline at end of file diff --git a/demos/sdk/google.html b/demos/sdk/google.html new file mode 100644 index 00000000..565e418b --- /dev/null +++ b/demos/sdk/google.html @@ -0,0 +1,65 @@ + + + + + +Windows Live demo + + + + + +

Google SDK

+ +

Signin

+ + +

Signout: of this app only

+ diff --git a/demos/sdk/linkedIn.html b/demos/sdk/linkedIn.html new file mode 100644 index 00000000..1df5b2b0 --- /dev/null +++ b/demos/sdk/linkedIn.html @@ -0,0 +1,49 @@ + + + + + + + + +

LinkedIn SDK

+ +

SignIn

+ +
+ +

SignOut: of app and LinkedIn.com

+ + +

Status Update

+ + + + + + + +
+ + + \ No newline at end of file diff --git a/demos/sdk/live.html b/demos/sdk/live.html index f1803ca3..88fb55f3 100644 --- a/demos/sdk/live.html +++ b/demos/sdk/live.html @@ -4,10 +4,17 @@ Windows Live demo + +

Signin

+ +

Signout: of app and windows

+ + + + + + +Soundcloud demo +

Soundcloud demo

+ +

Signin

+ + +

Signout: of app and windows

+ + + + + \ No newline at end of file diff --git a/src/hello.js b/src/hello.js index 1b5b9167..924ae55e 100644 --- a/src/hello.js +++ b/src/hello.js @@ -384,7 +384,7 @@ hello.utils.extend( hello, { // if( opts.display === 'none' ){ // signin in the background, iframe - utils.append('iframe', { src : url, style : {position:'absolute',left:"-1000px",bottom:0,height:'1px',width:'1px'} }, 'body'); + utils.iframe(url); } @@ -428,9 +428,11 @@ hello.utils.extend( hello, { // @param string name of the service // @param function callback // - logout : function(s, callback){ + logout : function(){ - var p = this.utils.args({name:'s', callback:"f" }, arguments); + var utils = self.utils; + + var p = utils.args({name:'s', callback:"f" }, arguments); // Create self // An object which inherits its parent as the prototype. @@ -450,7 +452,7 @@ hello.utils.extend( hello, { }}); return self; } - if(p.name && self.utils.store(p.name)){ + if(p.name && utils.store(p.name)){ // Trigger a logout callback on the provider if(typeof(self.services[p.name].logout) === 'function'){ @@ -476,8 +478,26 @@ hello.utils.extend( hello, { return self; } - // Emit events by default - self.emitAfter("complete logout success auth.logout auth", true); + // Define the callback + var callback = function(){ + // Emit events by default + self.emitAfter("complete logout success auth.logout auth", true); + }; + + // Does this endpoint + var logout = self.service[p.name]['logout']; + if( logout ){ + // Convert logout to string + if(typeof(logout) === 'function' && (logout = logout(callback)) ){ + return self; + } + // If logout is a string then assume URL and open in iframe. + if(logout){ + utils.iframe( logout ); + } + } + + callback(); return self; }, @@ -664,6 +684,15 @@ hello.utils.extend( hello.utils, { return n; }, + // + // create IFRAME + // 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'); + }, + // // merge // recursive merge two objects into one, second parameter overides the first diff --git a/src/modules/facebook.js b/src/modules/facebook.js index cda789a4..e9bf9f09 100644 --- a/src/modules/facebook.js +++ b/src/modules/facebook.js @@ -53,6 +53,13 @@ hello.init({ auth : 'http://www.facebook.com/dialog/oauth/' }, + logout : function(){ + var domain = window.location.hostname; + var token = (hello.utils.store('facebook')||{}).access_token; + var id = hello.services.facebook.id; + return "https://www.facebook.com/logout.php?access_token="+token+"&app_id="+id+"&channel_url=http%3A%2F%2Fstatic.ak.facebook.com%2Fconnect%2Fxd_arbiter.php%3Fversion%3D28%23cb%3Df1f0c8f8b8%26domain%3D"+domain+"%26origin%3Dhttp%253A%252F%252F"+domain+"%252Ff2931a264%26relation%3Dparent.parent&display=hidden&e2e=%7B%7D&locale=en_US&next=http%3A%2F%2Fstatic.ak.facebook.com%2Fconnect%2Fxd_arbiter.php%3Fversion%3D28%23cb%3Df1b925c374%26domain%3D"+domain+"%26origin%3Dhttp%253A%252F%252F"+domain+"%252Ff2931a264%26relation%3Dparent%26frame%3Df2b703c1d&sdk=joey"; + }, + // Authorization scopes scope : { basic : '', diff --git a/src/modules/windows.js b/src/modules/windows.js index 95229299..4cd7b14a 100644 --- a/src/modules/windows.js +++ b/src/modules/windows.js @@ -31,6 +31,10 @@ hello.init({ auth : 'https://login.live.com/oauth20_authorize.srf' }, + logout : function(){ + return 'http://login.live.com/oauth20_logout.srf?ts='+(new Date()).getTime(); + }, + // Authorization scopes scope : { basic : 'wl.signin,wl.basic', From 77e8d2e00bd4344703012b4d672ed0d9a306b46d Mon Sep 17 00:00:00 2001 From: Andrew Dodson Date: Fri, 6 Dec 2013 00:17:45 +1100 Subject: [PATCH 002/845] Include, DELETE me/photos --- assets/index.js | 41 ++++++++++++++++++++++++++++++++++++----- src/modules/facebook.js | 17 +++++++++++------ src/modules/windows.js | 1 + 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/assets/index.js b/assets/index.js index a6108a23..09f3a4fd 100644 --- a/assets/index.js +++ b/assets/index.js @@ -255,6 +255,20 @@ var tests = [ id : reg.string } }, + { + title : "Remove an album", + api : "api", + method : 'delete', + path : 'me/album', + scope : ["publish_files"], + data : { + id : '[ALBUM_ID]' + }, + setup : before_photo_post, + expected : { + success : true + } + }, { title : "Upload image to Album", api : "api", @@ -287,15 +301,15 @@ var tests = [ } }, { - title : "Remove an album", + title : "Remove a Photo from an Album", api : "api", method : 'delete', - path : 'me/album', + path : 'me/photo', scope : ["publish_files"], data : { - id : '[ALBUM_ID]' + id : '[PHOTO_ID]' }, - setup : before_photo_post, + setup : get_test_photo, expected : { success : true } @@ -434,8 +448,25 @@ function before_photo_post(test, callback){ }); } +function get_test_photo(test, callback){ - + before_photo_post(test, function(s){ + if(s){ + callback(s); + return; + } + hello(test.network).api( "me/album", { id : test.data.id } ).success(function(r){ + for(var i=0;i Date: Sat, 7 Dec 2013 00:35:41 +1100 Subject: [PATCH 003/845] Adding delete mapping for files and folders --- assets/index.js | 14 +++++++++++ src/modules/dropbox.js | 57 ++++++++++++++++++++++++++++++++++++------ src/modules/google.js | 3 ++- src/modules/windows.js | 1 + 4 files changed, 66 insertions(+), 9 deletions(-) diff --git a/assets/index.js b/assets/index.js index 09f3a4fd..7c033abb 100644 --- a/assets/index.js +++ b/assets/index.js @@ -375,6 +375,20 @@ var tests = [ }] } }, + { + title : "Delete a folder", + api : "api", + method : 'delete', + path : 'me/folder', + scope : ["publish_files"], + data : { + id : '[FOLDER_ID]' + }, + setup : get_test_folder, + expected : { + success : true + } + }, { title : "Upload my file", api : "api", diff --git a/src/modules/dropbox.js b/src/modules/dropbox.js index 31457517..68f0cd9e 100644 --- a/src/modules/dropbox.js +++ b/src/modules/dropbox.js @@ -40,7 +40,7 @@ function format_file(o){ o.file = 'https://api-content.dropbox.com/1/files/'+ path; } if(!o.id){ - o.id = o.name; + o.id = o.path.replace(/^\//,''); } // o.media = "https://api-content.dropbox.com/1/files/" + path; } @@ -53,6 +53,18 @@ function req(str){ }; } +function dataURItoBlob(dataURI) { + var reg = /^data\:([^;,]+(\;charset=[^;,]+)?)(\;base64)?,/i; + var m = dataURI.match(reg); + var binary = atob(dataURI.replace(reg,'')); + var array = []; + for(var i = 0; i < binary.length; i++) { + array.push(binary.charCodeAt(i)); + } + return new Blob([new Uint8Array(array)], {type: m[1]}); +} + + hello.init({ 'dropbox' : { @@ -99,7 +111,7 @@ hello.init({ "me" : 'account/info', // https://www.dropbox.com/developers/core/docs#metadata - "me/files" : req("metadata/@{root|sandbox}/"), + "me/files" : req("metadata/@{root|sandbox}/@{id}"), "me/folder" : req("metadata/@{root|sandbox}/@{id}"), "me/folders" : req('metadata/@{root|sandbox}/'), @@ -121,6 +133,11 @@ hello.init({ file : p.data.file }; + // Does this have a data-uri to upload as a file? + if( typeof( p.data.file ) === 'string' ){ + p.data.file = dataURItoBlob(p.data.file); + } + callback('https://api-content.dropbox.com/1/files_put/@{root|sandbox}/'+path+"/"+file_name); }, "me/folders" : function(p, callback){ @@ -133,6 +150,14 @@ hello.init({ })); } }, + + // Map DELETE requests + del : { + "me/files" : "fileops/delete?root=@{root|sandbox}&path=@{id}", + "me/folder" : "fileops/delete?root=@{root|sandbox}&path=@{id}" + }, + + wrap : { me : function(o){ formatError(o); @@ -161,18 +186,34 @@ hello.init({ format_file(o); + if(o.is_deleted){ + o.success = true; + } + return o; } }, + // doesn't return the CORS headers xhr : function(p){ - // forgetting content DropBox supports the allow-cross-origin-resource - if(p.path.match("https://api-content.dropbox.com/")){ - //p.data = p.data.file.files[0]; - return false; + + // the proxy supports allow-cross-origin-resource + // alas that's the only thing we're using. + if( p.data && p.data.file ){ + var file = p.data.file; + if( file ){ + if(file.files){ + p.data = file.files[0]; + } + else{ + p.data = file; + } + } } - else if(p.path.match("me/files")&&p.method==='post'){ - return true; + if(p.method==='delete'){ + // Post delete operations + p.method = 'post'; + } return true; } diff --git a/src/modules/google.js b/src/modules/google.js index 20c08b30..dba5ed13 100644 --- a/src/modules/google.js +++ b/src/modules/google.js @@ -532,7 +532,8 @@ // Map DELETE requests del : { - 'me/files' : 'https://www.googleapis.com/drive/v2/files/@{id}' + 'me/files' : 'drive/v2/files/@{id}', + 'me/folder' : 'drive/v2/files/@{id}' }, wrap : { diff --git a/src/modules/windows.js b/src/modules/windows.js index 17f66a68..0d644fee 100644 --- a/src/modules/windows.js +++ b/src/modules/windows.js @@ -98,6 +98,7 @@ hello.init({ // Include the data[id] in the path "me/album" : '@{id}', "me/photo" : '@{id}', + "me/folder" : '@{id}', "me/files" : '@{id}' }, From 3b85f3ada5bb1e22cc94f53794d28dae7acdee4c Mon Sep 17 00:00:00 2001 From: Andrew Dodson Date: Tue, 10 Dec 2013 21:25:03 +1100 Subject: [PATCH 004/845] Move proprietary SDK implementations into their own folder --- demos/{ => sdk}/linkedIn-sdk.html | 0 demos/{ => sdk}/profile-sdks.html | 0 demos/{ => sdk}/upload_sdks.html | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename demos/{ => sdk}/linkedIn-sdk.html (100%) rename demos/{ => sdk}/profile-sdks.html (100%) rename demos/{ => sdk}/upload_sdks.html (100%) diff --git a/demos/linkedIn-sdk.html b/demos/sdk/linkedIn-sdk.html similarity index 100% rename from demos/linkedIn-sdk.html rename to demos/sdk/linkedIn-sdk.html diff --git a/demos/profile-sdks.html b/demos/sdk/profile-sdks.html similarity index 100% rename from demos/profile-sdks.html rename to demos/sdk/profile-sdks.html diff --git a/demos/upload_sdks.html b/demos/sdk/upload_sdks.html similarity index 100% rename from demos/upload_sdks.html rename to demos/sdk/upload_sdks.html From ea0947e24639fa769b374884829c76e4129d7268 Mon Sep 17 00:00:00 2001 From: Andrew Dodson Date: Sat, 14 Dec 2013 13:08:49 +1100 Subject: [PATCH 005/845] Add Google Drive PUT me/files, Change GET me/files to use parent property instead of id, #20 --- assets/index.js | 65 ++++++++++++++++++++++++++++++++++++---- src/modules/dropbox.js | 4 +-- src/modules/google.js | 68 ++++++++++++++++++++++++++++++++---------- src/modules/windows.js | 4 +-- 4 files changed, 116 insertions(+), 25 deletions(-) diff --git a/assets/index.js b/assets/index.js index 7c033abb..203b3681 100644 --- a/assets/index.js +++ b/assets/index.js @@ -389,6 +389,23 @@ var tests = [ success : true } }, + { + title : "Get files in folder", + api : "api", + method : 'get', + path : 'me/files', + scope : ["files"], + data : { + parent : '[FOLDER_ID]' + }, + setup : get_test_folder, + expected : { + data : [{ + id : reg.string, + name : reg.name + }] + } + }, { title : "Upload my file", api : "api", @@ -396,7 +413,7 @@ var tests = [ path : 'me/files', scope : ["publish_files"], data : { - id : '[FOLDER_ID]', + parent : '[FOLDER_ID]', file : INPUT_FILE, name : "TestFile.png" }, @@ -413,7 +430,7 @@ var tests = [ path : 'me/files', scope : ["publish_files"], data : { - id : '[FOLDER_ID]', + parent : '[FOLDER_ID]', file : DATA_URL, name : "TestFile.png" }, @@ -423,6 +440,39 @@ var tests = [ name : reg.name } }, + { + title : "Update the file contents", + api : "api", + method : 'put', + path : 'me/files', + scope : ["publish_files"], + data : { + id : '[FILE_ID]', + file : DATA_URL, + name : "TestFile.png" + }, + setup : get_test_file, + expected : { + id : reg.string, + name : reg.name + } + }, + { + title : "Move the file location", + api : "api", + method : 'put', + path : 'me/files', + scope : ["publish_files"], + data : { + id : '[FILE_ID]', + parent : '[NEW_PARENT_ID]' + }, + setup : get_test_file, + expected : { + id : reg.string, + name : reg.name + } + }, { title : "Delete my file", api : "api", @@ -484,7 +534,7 @@ function get_test_photo(test, callback){ function get_test_folder(test, callback){ - if(!("id" in test.data)){ + if(!("id" in test.data)&&!("parent" in test.data)){ callback(); return; } @@ -494,7 +544,12 @@ function get_test_folder(test, callback){ for(var i=0;i Date: Sat, 14 Dec 2013 13:18:58 +1100 Subject: [PATCH 006/845] Release 0.1.2, fix #20 - Add Google Drive Support --- bower.json | 2 +- dist/hello.all.js | 569 +++++++++++++++++++++++++++++++++++++++--- dist/hello.all.min.js | 16 +- dist/hello.js | 25 +- dist/hello.min.js | 2 +- package.json | 2 +- 6 files changed, 565 insertions(+), 51 deletions(-) diff --git a/bower.json b/bower.json index 84e213ad..95b7382b 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "hello", - "version": "0.1.1", + "version": "0.1.2", "homepage": "http://adodson.com/hello.js/", "license" : "https://github.com/MrSwitch/hello.js/blob/master/LICENSE", "repository": { diff --git a/dist/hello.all.js b/dist/hello.all.js index ac8e4468..d3aa92ca 100644 --- a/dist/hello.all.js +++ b/dist/hello.all.js @@ -1338,6 +1338,7 @@ hello.api = function(){ var self = this.use(), utils = self.utils; + // Reference arguments self.args = p; @@ -1451,6 +1452,11 @@ hello.api = function(){ }}); } + // + // Get the current session + var session = self.getAuthResponse(p.network); + + // // Given the path trigger the fix processPath(p.path); @@ -1573,7 +1579,12 @@ hello.api = function(){ o.jsonp(p,qs); } - // Is self still a post? + // Does this provider have a custom method? + if("api" in o && o.api( url, p, {access_token:session.access_token}, callback ) ){ + return; + } + + // Is method still a post? if( p.method === 'post' ){ // Add some additional query parameters to the URL @@ -1616,8 +1627,7 @@ hello.api = function(){ function _sign(network, path, method, data, modifyQueryString, callback){ // OAUTH SIGNING PROXY - var session = self.getAuthResponse(network), - service = self.services[network], + var service = self.services[network], token = (session ? session.access_token : null); // Is self an OAuth1 endpoint @@ -1686,7 +1696,7 @@ hello.utils.extend( hello.utils, { // Create a clone of an object clone : function(obj){ if("nodeName" in obj){ - return obj[x]; + return obj; } var clone = {}, x; for(x in obj){ @@ -1764,7 +1774,7 @@ hello.utils.extend( hello.utils, { } data = null; } - else if( data && typeof(data) !== 'string' && !(data instanceof FormData)){ + else if( data && typeof(data) !== 'string' && !(data instanceof FormData) && !(data instanceof File) && !(data instanceof Blob)){ // Loop through and add formData var f = new FormData(); for( x in data )if(data.hasOwnProperty(x)){ @@ -1773,6 +1783,9 @@ hello.utils.extend( hello.utils, { f.append(x, data[x].files[0]); } } + else if(data[x] instanceof Blob){ + f.append(x, data[x], data.name); + } else{ f.append(x, data[x]); } @@ -2183,8 +2196,6 @@ hello.utils.extend( hello.utils, { } return false; } - - }); @@ -2361,7 +2372,7 @@ function format_file(o){ o.file = 'https://api-content.dropbox.com/1/files/'+ path; } if(!o.id){ - o.id = o.name; + o.id = o.path.replace(/^\//,''); } // o.media = "https://api-content.dropbox.com/1/files/" + path; } @@ -2374,6 +2385,18 @@ function req(str){ }; } +function dataURItoBlob(dataURI) { + var reg = /^data\:([^;,]+(\;charset=[^;,]+)?)(\;base64)?,/i; + var m = dataURI.match(reg); + var binary = atob(dataURI.replace(reg,'')); + var array = []; + for(var i = 0; i < binary.length; i++) { + array.push(binary.charCodeAt(i)); + } + return new Blob([new Uint8Array(array)], {type: m[1]}); +} + + hello.init({ 'dropbox' : { @@ -2420,7 +2443,7 @@ hello.init({ "me" : 'account/info', // https://www.dropbox.com/developers/core/docs#metadata - "me/files" : req("metadata/@{root|sandbox}/"), + "me/files" : req("metadata/@{root|sandbox}/@{parent}"), "me/folder" : req("metadata/@{root|sandbox}/@{id}"), "me/folders" : req('metadata/@{root|sandbox}/'), @@ -2435,13 +2458,18 @@ hello.init({ post : { "me/files" : function(p,callback){ - var path = p.data.id, + var path = p.data.parent, file_name = p.data.name; p.data = { file : p.data.file }; + // Does this have a data-uri to upload as a file? + if( typeof( p.data.file ) === 'string' ){ + p.data.file = dataURItoBlob(p.data.file); + } + callback('https://api-content.dropbox.com/1/files_put/@{root|sandbox}/'+path+"/"+file_name); }, "me/folders" : function(p, callback){ @@ -2454,6 +2482,14 @@ hello.init({ })); } }, + + // Map DELETE requests + del : { + "me/files" : "fileops/delete?root=@{root|sandbox}&path=@{id}", + "me/folder" : "fileops/delete?root=@{root|sandbox}&path=@{id}" + }, + + wrap : { me : function(o){ formatError(o); @@ -2482,18 +2518,34 @@ hello.init({ format_file(o); + if(o.is_deleted){ + o.success = true; + } + return o; } }, + // doesn't return the CORS headers xhr : function(p){ - // forgetting content DropBox supports the allow-cross-origin-resource - if(p.path.match("https://api-content.dropbox.com/")){ - //p.data = p.data.file.files[0]; - return false; + + // the proxy supports allow-cross-origin-resource + // alas that's the only thing we're using. + if( p.data && p.data.file ){ + var file = p.data.file; + if( file ){ + if(file.files){ + p.data = file.files[0]; + } + else{ + p.data = file; + } + } } - else if(p.path.match("me/files")&&p.method==='post'){ - return true; + if(p.method==='delete'){ + // Post delete operations + p.method = 'post'; + } return true; } @@ -2604,7 +2656,12 @@ hello.init({ // Map DELETE requests del : { - //'me/album' : '@{id}' + /* + // Can't delete an album + // http://stackoverflow.com/questions/8747181/how-to-delete-an-album + 'me/album' : '@{id}' + */ + 'me/photo' : '@{id}' }, wrap : { @@ -2622,20 +2679,20 @@ hello.init({ if(p.method==='get'||p.method==='post'){ qs.suppress_response_codes = true; } - else if(p.method === "delete"){ - qs.method = 'delete'; - p.method = "post"; - } return true; }, // Special requirements for handling JSONP fallback - jsonp : function(p){ + jsonp : function(p,qs){ var m = p.method.toLowerCase(); if( m !== 'get' && !hello.utils.hasBinary(p.data) ){ p.data.method = m; p.method = 'get'; } + else if(p.method === "delete"){ + qs.method = 'delete'; + p.method = "post"; + } }, // Special requirements for iframe form hack @@ -3042,7 +3099,7 @@ hello.init({ // // GOOGLE API // -(function(hello){ +(function(hello, window){ "use strict"; @@ -3211,6 +3268,295 @@ hello.init({ } } + // + // Misc + var utils = hello.utils; + + + // Multipart + // Construct a multipart message + + function Multipart(){ + // Internal body + var body = [], + boundary = (Math.random()*1e10).toString(32), + counter = 0, + line_break = "\r\n", + delim = line_break + "--" + boundary, + ready = function(){}, + data_uri = /^data\:([^;,]+(\;charset=[^;,]+)?)(\;base64)?,/i; + + // Add File + function addFile(item){ + var fr = new FileReader(); + fr.onload = function(e){ + //addContent( e.target.result, item.type ); + addContent( btoa(e.target.result), item.type + line_break + "Content-Transfer-Encoding: base64"); + }; + fr.readAsBinaryString(item); + } + + // Add content + function addContent(content, type){ + body.push(line_break + 'Content-Type: ' + type + line_break + line_break + content); + counter--; + ready(); + } + + // Add new things to the object + this.append = function(content, type){ + + // Does the content have an array + if(typeof(content) === "string" || !('length' in Object(content)) ){ + // converti to multiples + content = [content]; + } + + for(var i=0;i][;charset=][;base64], + // /^data\:([^;,]+(\;charset=[^;,]+)?)(\;base64)?,/i + else if( typeof( item ) === 'string' && item.match(data_uri) ){ + var m = item.match(data_uri); + addContent(item.replace(data_uri,''), m[1] + line_break + "Content-Transfer-Encoding: base64"); + } + + // Regular string + else{ + addContent(item, type); + } + } + }; + + this.onready = function(fn){ + ready = function(){ + if( counter===0 ){ + // trigger ready + body.unshift(''); + body.push('--'); + fn( body.join(delim), boundary); + body = []; + } + }; + ready(); + }; + } + + + // + // Events + // + var addEvent, removeEvent; + + if(document.removeEventListener){ + addEvent = function(elm, event_name, callback){ + elm.addEventListener(event_name, callback); + }; + removeEvent = function(elm, event_name, callback){ + elm.removeEventListener(event_name, callback); + }; + } + else if(document.detachEvent){ + removeEvent = function (elm, event_name, callback){ + elm.detachEvent("on"+event_name, callback); + }; + addEvent = function (elm, event_name, callback){ + elm.attachEvent("on"+event_name, callback); + }; + } + + // + // postMessage + // This is used whereby the browser does not support CORS + // + var xd_iframe, xd_ready, xd_id, xd_counter, xd_queue=[]; + function xd(method, url, headers, body, callback){ + + // This is the origin of the Domain we're opening + var origin = 'https://content.googleapis.com'; + + // Is this the first time? + if(!xd_iframe){ + + // ID + xd_id = String(parseInt(Math.random()*1e8,10)); + + // Create the proxy window + xd_iframe = utils.append('iframe', { src : origin + "/static/proxy.html?jsh=m%3B%2F_%2Fscs%2Fapps-static%2F_%2Fjs%2Fk%3Doz.gapi.en.mMZgig4ibk0.O%2Fm%3D__features__%2Fam%3DEQ%2Frt%3Dj%2Fd%3D1%2Frs%3DAItRSTNZBJcXGialq7mfSUkqsE3kvYwkpQ#parent="+window.location.origin+"&rpctoken="+xd_id, + style : {position:'absolute',left:"-1000px",bottom:0,height:'1px',width:'1px'} }, 'body'); + + // Listen for on ready events + // Set the window listener to handle responses from this + addEvent( window, "message", function CB(e){ + + // Try a callback + if(e.origin !== origin){ + return; + } + + var json; + + try{ + json = JSON.parse(e.data); + } + catch(ee){ + // This wasn't meant to be + return; + } + + // Is this the right implementation? + if(json && json.s && json.s === "ready:"+xd_id){ + // Yes, it is. + // Lets trigger the pending operations + xd_ready = true; + xd_counter = 0; + + for(var i=0;i(new Date).getTime()/1e3){var u=t.diff(c.scope||[],n.qs.state.scope||[]);if(0===u.length)return e.emit("notice","User already has a valid access_token"),e.emitAfter("complete success login",{network:n.network,authResponse:c}),e}if(n.qs.redirect_uri=t.realPath(n.qs.redirect_uri),o.oauth&&(n.qs.state.oauth=o.oauth),n.qs.state=JSON.stringify(n.qs.state),"login"in o&&"function"==typeof o.login&&o.login(n),r=1===parseInt(o.oauth.version,10)?t.qs(i.oauth_proxy,n.qs):t.qs(o.oauth.auth,n.qs),e.emit("notice","Authorization URL "+r),"none"===i.display)t.append("iframe",{src:r,style:{position:"absolute",left:"-1000px",bottom:0,height:"1px",width:"1px"}},"body");else if("popup"===i.display){var p=i.window_height||550,f=i.window_width||500,d=window.open(r,"Authentication","resizeable=true,height="+p+",width="+f+",left="+(window.innerWidth-f)/2+",top="+(window.innerHeight-p)/2);d.focus();var h=setInterval(function(){d.closed&&(clearInterval(h),s||e.emit("complete failed error",{error:{code:"cancelled",message:"Login has been cancelled"},network:n.network}))},100)}else window.location=r;return e},logout:function(){var e=this.utils.args({name:"s",callback:"f"},arguments),t=this.use();if(t.on("complete",e.callback),e.name=e.name||t.settings.default_service,e.name&&!(e.name in t.services))return t.emitAfter("complete error",{error:{code:"invalid_network",message:"The network was unrecognized"}}),t;if(e.name&&t.utils.store(e.name))"function"==typeof t.services[e.name].logout&&t.services[e.name].logout(e),t.utils.store(e.name,"");else{if(e.name)return t.emitAfter("complete error",{error:{code:"invalid_session",message:"There was no session to remove"}}),t;for(var n in t.services)t.services.hasOwnProperty(n)&&t.logout(n);t.service(!1)}return t.emitAfter("complete logout success auth.logout auth",!0),t},getAuthResponse:function(e){return e=e||this.settings.default_service,e&&e in this.services?this.utils.store(e)||null:(this.emit("complete error",{error:{code:"invalid_network",message:"The network was unrecognized"}}),null)},events:{}}),hello.utils.extend(hello.utils,{qs:function(e,t){if(t){var n;for(var r in t)if(e.indexOf(r)>-1){var i="[\\?\\&]"+r+"=[^\\&]*";n=new RegExp(i),e=e.replace(n,"")}}return e+(this.isEmpty(t)?"":(e.indexOf("?")>-1?"&":"?")+this.param(t))},param:function(e){var t,n,r={};if("string"==typeof e){if(n=e.replace(/^[\#\?]/,"").match(/([^=\/\&]+)=([^\&]+)/g))for(var i=0;i-1&&"string"===i||e[o].indexOf("o")>-1&&"object"===i||e[o].indexOf("i")>-1&&"number"===i||e[o].indexOf("a")>-1&&"object"===i||e[o].indexOf("f")>-1&&"function"===i))n[o]=t[r++];else if("string"==typeof e[o]&&e[o].indexOf("!")>-1)return!1;return n},realPath:function(e){var t=window.location;for(0===e.indexOf("/")?e=t.protocol+"//"+t.host+e:e.match(/^https?\:\/\//)||(e=(t.href.replace(/#.*/,"").replace(/\/[^\/]+$/,"/")+e).replace(/\/\.\//g,"/"));/\/[^\/]+\/\.\.\//g.test(e);)e=e.replace(/\/[^\/]+\/\.\.\//g,"/");return e},diff:function(e,t){for(var n=[],r=0;r0)return!1;if(e&&0===e.length)return!0;for(var t in e)if(e.hasOwnProperty(t))return!1;return!0},objectCreate:function(){function e(){}return Object.create?Object.create:function(t){if(1!=arguments.length)throw new Error("Object.create implementation only accepts one parameter.");return e.prototype=t,new e}}(),Event:function(){this.parent={events:this.events,findEvents:this.findEvents,parent:this.parent,utils:this.utils},this.events={},this.on=function(e,t){if(t&&"function"==typeof t)for(var n=e.split(/[\s\,]+/),r=0;r-1)for(var i=0;i0&&f.append(u,r[u].files[0]):f.append(u,r[u]));r=f}return t(p,function(t){if(c.open(e,t,!0),l&&("responseType"in c?c.responseType=l:c.overrideMimeType("text/plain; charset=x-user-defined")),n)for(var i in n)c.setRequestHeader(i,n[i]);c.send(r)}),c},jsonp:function(e,t,n,r){var i,o,s=this,a=0,c=document.getElementsByTagName("head")[0],l={error:{message:"server_error",code:"server_error"}},u=function(){a++||window.setTimeout(function(){t(l),c.removeChild(o)},0)},p=s.globalEvent(function(e){return l=e,!0},n);if("function"!=typeof e){var f=e;f=f.replace(new RegExp("=\\?(&|$)"),"="+p+"$1"),e=function(e,t){t(s.qs(f,e))}}e(function(e){for(var t in e)e.hasOwnProperty(t)&&"?"===e[t]&&(e[t]=p)},function(e){o=s.append("script",{id:p,name:p,src:e,async:!0,onload:u,onerror:u,onreadystatechange:function(){/loaded|complete/i.test(this.readyState)&&u()}}),window.navigator.userAgent.toLowerCase().indexOf("opera")>-1&&(i=s.append("script",{text:"document.getElementById('"+p+"').onerror();"}),o.async=!1),r&&window.setTimeout(function(){l={error:{message:"timeout",code:"timeout"}},u()},r),c.appendChild(o),i&&c.appendChild(i)})},post:function(e,t,n,r,i,o){var s=this,a=document;if("function"!=typeof e){var c=e;e=function(e,t){t(s.qs(c,e))}}var l,u=null,p=[],f=0,d=null,h=0,m=function(e){h++||r(e)};s.globalEvent(m,i);var g;try{g=a.createElement('