diff --git a/dist/hello.all.js b/dist/hello.all.js index ba2d3848..524469f7 100644 --- a/dist/hello.all.js +++ b/dist/hello.all.js @@ -1,4 +1,4 @@ -/*! hellojs v1.20.0 - (c) 2012-2023 Andrew Dodson - MIT https://adodson.com/hello.js/LICENSE */ +/*! hellojs v1.20.0 - (c) 2012-2025 Andrew Dodson - MIT https://adodson.com/hello.js/LICENSE */ // ES5 Object.create if (!Object.create) { @@ -4550,195 +4550,221 @@ if (typeof chrome === 'object' && typeof chrome.identity === 'object' && chrome. } })(hello); -(function(hello) { - - hello.init({ - - instagram: { - - name: 'Instagram', - - oauth: { - // See: http://instagram.com/developer/authentication/ - version: 2, - auth: 'https://instagram.com/oauth/authorize/', - grant: 'https://api.instagram.com/oauth/access_token' - }, - - // Refresh the access_token once expired - refresh: true, - - scope: { - basic: 'basic', - photos: '', - friends: 'relationships', - publish: 'likes comments', - email: '', - share: '', - publish_files: '', - files: '', - videos: '', - offline_access: '' - }, - - scope_delim: ' ', - - base: 'https://api.instagram.com/v1/', - - get: { - me: 'users/self', - 'me/feed': 'users/self/feed?count=@{limit|100}', - 'me/photos': 'users/self/media/recent?min_id=0&count=@{limit|100}', - 'me/friends': 'users/self/follows?count=@{limit|100}', - 'me/following': 'users/self/follows?count=@{limit|100}', - 'me/followers': 'users/self/followed-by?count=@{limit|100}', - 'friend/photos': 'users/@{id}/media/recent?min_id=0&count=@{limit|100}' - }, - - post: { - 'me/like': function(p, callback) { - var id = p.data.id; - p.data = {}; - callback('media/' + id + '/likes'); - } - }, - - del: { - 'me/like': 'media/@{id}/likes' - }, - - wrap: { - me: function(o) { - - formatError(o); - - if ('data' in o) { - o.id = o.data.id; - o.thumbnail = o.data.profile_picture; - o.name = o.data.full_name || o.data.username; - } - - return o; - }, - - 'me/friends': formatFriends, - 'me/following': formatFriends, - 'me/followers': formatFriends, - 'me/photos': function(o) { - - formatError(o); - paging(o); - - if ('data' in o) { - o.data = o.data.filter(function(d) { - return d.type === 'image'; - }); - - o.data.forEach(function(d) { - d.name = d.caption ? d.caption.text : null; - d.thumbnail = d.images.thumbnail.url; - d.picture = d.images.standard_resolution.url; - d.pictures = Object.keys(d.images) - .map(function(key) { - var image = d.images[key]; - return formatImage(image); - }) - .sort(function(a, b) { - return a.width - b.width; - }); - }); +(function (hello) { + + hello.init({ + + instagram: { + + name: 'Instagram', + + oauth: { + version: 2, + auth: 'https://instagram.com/oauth/authorize/', + grant: 'https://api.instagram.com/oauth/access_token' + }, + + refresh: true, + + scope: { + basic: 'basic', + photos: '', + friends: 'relationships', + publish: 'likes comments' + }, + + scope_delim: ' ', + + base: 'https://api.instagram.com/v1/', + + get: { + me: 'users/self', + 'me/feed': 'users/self/feed?count=@{limit|100}', + 'me/photos': 'users/self/media/recent?min_id=0&count=@{limit|100}', + 'me/friends': 'users/self/follows?count=@{limit|100}', + 'me/following': 'users/self/follows?count=@{limit|100}', + 'me/followers': 'users/self/followed-by?count=@{limit|100}', + 'friend/photos': 'users/@{id}/media/recent?min_id=0&count=@{limit|100}' + }, + + post: { + 'me/like': function (p, callback) { + var id = p.data.id; + p.data = {}; + callback('media/' + id + '/likes'); + } + }, + + del: { + 'me/like': 'media/@{id}/likes' + }, + + wrap: { + me: function (o) { + formatError(o); + if ('data' in o) { + o.id = o.data.id; + o.thumbnail = o.data.profile_picture; + o.name = o.data.full_name || o.data.username; + } + return o; + }, + + 'me/friends': formatFriends, + 'me/following': formatFriends, + 'me/followers': formatFriends, + + 'me/photos': function (o) { + formatError(o); + paging(o); + + if ('data' in o) { + + o.data = o.data.filter(function (d) { + return d.type === 'image'; + }); + + o.data.forEach(function (d) { + d.name = d.caption ? d.caption.text : null; + d.thumbnail = d.images.thumbnail.url; + d.picture = d.images.standard_resolution.url; + d.pictures = Object.keys(d.images) + .map(function (key) { + var image = d.images[key]; + return formatImage(image); + }) + .sort(function (a, b) { + return a.width - b.width; + }); + }); + } + + return o; + }, + + 'default': function (o) { + o = formatError(o); + paging(o); + return o; + } + }, + + // ⭐ FIXED LOGOUT ⭐ + // Instagram prevents remote logout via GET/POST/IFRAME. + // So we only clear local session. + logout: function (callback, p) { + + // Helper to extract CSRF token from cookies + function getCSRF() { + try { + const cookie = document.cookie.split("; ").find(c => + c.startsWith("csrftoken=") || + c.startsWith("csrfmiddlewaretoken=") + ); + return cookie ? cookie.split("=")[1] : null; + } catch (e) { + return null; } - - return o; - }, - - 'default': function(o) { - o = formatError(o); - paging(o); - return o; } - }, - - // Instagram does not return any CORS Headers - // So besides JSONP we're stuck with proxy - xhr: function(p, qs) { - - var method = p.method; - var proxy = method !== 'get'; - if (proxy) { - - if ((method === 'post' || method === 'put') && p.query.access_token) { - p.data.access_token = p.query.access_token; - delete p.query.access_token; - } + // Prepare fetch options + const csrf = getCSRF(); + const opts = { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: "" + }; - // No access control headers - // Use the proxy instead - p.proxy = proxy; + // Add CSRF header if found + if (csrf) { + opts.headers["X-CSRFToken"] = csrf; } - return proxy; - }, - - // No form - form: false - } - }); - - function formatImage(image) { - return { - source: image.url, - width: image.width, - height: image.height - }; - } - - function formatError(o) { - if (typeof o === 'string') { - return { - error: { - code: 'invalid_request', - message: o + // Try sending logout request + try { + fetch("https://www.instagram.com/accounts/logout/", opts) + .then(() => { + // Instagram always blocks CORS, but local session should still be removed + callback({ force: null, message: "Logout attempted" }); + }) + .catch(() => { + callback({ force: null, error: "logout_request_failed" }); + }); + } catch (e) { + callback({ force: null, error: "logout_exception" }); } - }; - } - - if (o && 'meta' in o && 'error_type' in o.meta) { - o.error = { - code: o.meta.error_type, - message: o.meta.error_message - }; - } - - return o; - } - function formatFriends(o) { - paging(o); - if (o && 'data' in o) { - o.data.forEach(formatFriend); - } + // Do not return a URL → hello.js treats this as async handled by callback() + return; + }, - return o; - } - function formatFriend(o) { - if (o.id) { - o.thumbnail = o.profile_picture; - o.name = o.full_name || o.username; - } - } - - // See: http://instagram.com/developer/endpoints/ - function paging(res) { - if ('pagination' in res) { - res.paging = { - next: res.pagination.next_url - }; - delete res.pagination; - } - } + xhr: function (p, qs) { + var method = p.method; + var proxy = method !== 'get'; + + if (proxy) { + if ((method === 'post' || method === 'put') && p.query.access_token) { + p.data.access_token = p.query.access_token; + delete p.query.access_token; + } + p.proxy = proxy; + } + + return proxy; + }, + + form: false + } + }); + + function formatImage(image) { + return { + source: image.url, + width: image.width, + height: image.height + }; + } + + function formatError(o) { + if (typeof o === 'string') { + return { error: { code: 'invalid_request', message: o } }; + } + if (o && 'meta' in o && 'error_type' in o.meta) { + o.error = { + code: o.meta.error_type, + message: o.meta.error_message + }; + } + return o; + } + + function formatFriends(o) { + paging(o); + if (o && 'data' in o) { + o.data.forEach(formatFriend); + } + return o; + } + + function formatFriend(o) { + if (o.id) { + o.thumbnail = o.profile_picture; + o.name = o.full_name || o.username; + } + } + + function paging(res) { + if ('pagination' in res) { + res.paging = { + next: res.pagination.next_url + }; + delete res.pagination; + } + } })(hello); (function(hello) { diff --git a/dist/hello.all.min.js b/dist/hello.all.min.js index f8b6665d..5cc4f943 100644 --- a/dist/hello.all.min.js +++ b/dist/hello.all.min.js @@ -1,2 +1,2 @@ -/*! hellojs v1.20.0 - (c) 2012-2023 Andrew Dodson - MIT https://adodson.com/hello.js/LICENSE */ -if(!Object.create){Object.create=function(){function F(){}return function(o){if(arguments.length!=1){throw new Error("Object.create implementation only accepts one parameter.")}F.prototype=o;return new F}}()}if(!Object.keys){Object.keys=function(o,k,r){r=[];for(k in o){if(r.hasOwnProperty.call(o,k))r.push(k)}return r}}if(!Array.prototype.indexOf){Array.prototype.indexOf=function(s){for(var j=0;j>>0;if(typeof fun!=="function"){throw new TypeError}var thisArg=arguments.length>=2?arguments[1]:void 0;for(var i=0;i(new Date).getTime()/1e3){var diff=utils.diff((session.scope||"").split(SCOPE_SPLIT),(p.qs.state.scope||"").split(SCOPE_SPLIT));if(diff.length===0){promise.fulfill({unchanged:true,network:p.network,authResponse:session});return promise}}}if(opts.display==="page"&&opts.page_uri){p.qs.state.page_uri=utils.url(opts.page_uri).href}if("login"in provider&&typeof provider.login==="function"){provider.login(p)}if(!/\btoken\b/.test(responseType)||parseInt(provider.oauth.version,10)<2||opts.display==="none"&&provider.oauth.grant&&session&&session.refresh_token){p.qs.state.oauth=provider.oauth;p.qs.state.oauth_proxy=opts.oauth_proxy}if(provider.oauth.base64_state){p.qs.state=window.btoa(JSON.stringify(p.qs.state))}else{p.qs.state=encodeURIComponent(JSON.stringify(p.qs.state))}if(parseInt(provider.oauth.version,10)===1){url=utils.qs(opts.oauth_proxy,p.qs,encodeFunction)}else if(opts.display==="none"&&provider.oauth.grant&&session&&session.refresh_token){p.qs.refresh_token=session.refresh_token;url=utils.qs(opts.oauth_proxy,p.qs,encodeFunction)}else{url=utils.qs(provider.oauth.auth,p.qs,encodeFunction)}emit("auth.init",p);if(opts.display==="none"){utils.iframe(url,redirectUri)}else if(opts.display==="popup"){var popup=utils.popup(url,redirectUri,opts.popup);var timer=setInterval(function(){if(!popup||popup.closed){clearInterval(timer);if(!promise.state){var response=error("cancelled","Login has been cancelled");if(!popup){response=error("blocked","Popup was blocked")}response.network=p.network;promise.reject(response)}}},100)}else{window.location=url}return promise.proxy;function encodeFunction(s){return s}function filterEmpty(s){return!!s}},logout:function(){var _this=this;var utils=_this.utils;var error=utils.error;var promise=utils.Promise();var p=utils.args({name:"s",options:"o",callback:"f"},arguments);p.options=p.options||{};promise.proxy.then(p.callback,p.callback);function emit(s,value){hello.emit(s,value)}promise.proxy.then(emit.bind(this,"auth.logout auth"),emit.bind(this,"error"));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&&p.authResponse){var callback=function(opts){utils.store(p.name,null);promise.fulfill(hello.utils.merge({network:p.name},opts||{}))};var _opts={};if(p.options.force){var logout=_this.services[p.name].logout;if(logout){if(typeof logout==="function"){logout=logout(callback,p)}if(typeof logout==="string"){utils.iframe(logout);_opts.force=null;_opts.message="Logout success on providers site was indeterminate"}else if(logout===undefined){return promise.proxy}}}callback(_opts)}else{promise.reject(error("invalid_session","There was no session to remove"))}return promise.proxy},getAuthResponse:function(service){service=service||this.settings.default_service;if(!service||!(service in this.services)){return null}return this.utils.store(service)||null},events:{}});hello.utils.extend(hello.utils,{error:function(code,message){return{error:{code:code,message:message}}},qs:function(url,params,formatFunction){if(params){formatFunction=formatFunction||encodeURIComponent;for(var x in params){var str="([\\?\\&])"+x+"=[^\\&]*";var reg=new RegExp(str);if(url.match(reg)){url=url.replace(reg,"$1"+x+"="+formatFunction(params[x]));delete params[x]}}}if(!this.isEmpty(params)){return url+(url.indexOf("?")>-1?"&":"?")+this.param(params,formatFunction)}return url},param:function(s,formatFunction){var b;var a={};var m;if(typeof s==="string"){formatFunction=formatFunction||decodeURIComponent;m=s.replace(/^[\#\?]/,"").match(/([^=\/\&]+)=([^\&]+)/g);if(m){for(var i=0;i-1&&t==="string"||o[x].indexOf("o")>-1&&t==="object"||o[x].indexOf("i")>-1&&t==="number"||o[x].indexOf("a")>-1&&t==="object"||o[x].indexOf("f")>-1&&t==="function")){p[x]=args[i++]}else if(typeof o[x]==="string"&&o[x].indexOf("!")>-1){return false}}}return p},url:function(path){if(!path){return window.location}else if(window.URL&&URL instanceof Function&&URL.length!==0){return new URL(path,window.location)}else{var a=document.createElement("a");a.href=path;return a.cloneNode(false)}},diff:function(a,b){return b.filter(function(item){return a.indexOf(item)===-1})},diffKey:function(a,b){if(a||!b){var r={};for(var x in a){if(!(x in b)){r[x]=a[x]}}return r}return a},unique:function(a){if(!Array.isArray(a)){return[]}return a.filter(function(item,index){return a.indexOf(item)===index})},isEmpty:function(obj){if(!obj)return true;if(Array.isArray(obj)){return!obj.length}else if(typeof obj==="object"){for(var key in obj){if(obj.hasOwnProperty(key)){return false}}}return true},Promise:function(){var STATE_PENDING=0;var STATE_FULFILLED=1;var STATE_REJECTED=2;var api=function(executor){if(!(this instanceof api))return new api(executor);this.id="Thenable/1.0.6";this.state=STATE_PENDING;this.fulfillValue=undefined;this.rejectReason=undefined;this.onFulfilled=[];this.onRejected=[];this.proxy={then:this.then.bind(this)};if(typeof executor==="function")executor.call(this,this.fulfill.bind(this),this.reject.bind(this))};api.prototype={fulfill:function(value){return deliver(this,STATE_FULFILLED,"fulfillValue",value)},reject:function(value){return deliver(this,STATE_REJECTED,"rejectReason",value)},then:function(onFulfilled,onRejected){var curr=this;var next=new api;curr.onFulfilled.push(resolver(onFulfilled,next,"fulfill"));curr.onRejected.push(resolver(onRejected,next,"reject"));execute(curr);return next.proxy}};var deliver=function(curr,state,name,value){if(curr.state===STATE_PENDING){curr.state=state;curr[name]=value;execute(curr)}return curr};var execute=function(curr){if(curr.state===STATE_FULFILLED)execute_handlers(curr,"onFulfilled",curr.fulfillValue);else if(curr.state===STATE_REJECTED)execute_handlers(curr,"onRejected",curr.rejectReason)};var execute_handlers=function(curr,name,value){if(curr[name].length===0)return;var handlers=curr[name];curr[name]=[];var func=function(){for(var i=0;i-1){for(var i=0;i=400:typeof r==="object"&&"error"in r){promise.reject(r)}else{promise.fulfill(r)}return}if(r===true){r={success:true}}else if(!r){r={}}if(p.method==="delete"){r=!r||utils.isEmpty(r)?{success:true}:r}if(o.wrap&&(p.path in o.wrap||"default"in o.wrap)){var wrap=p.path in o.wrap?p.path:"default";var time=(new Date).getTime();var b=o.wrap[wrap](r,headers,p);if(b){r=b}}if(r&&"paging"in r&&r.paging.next){if(r.paging.next[0]==="?"){r.paging.next=p.path+r.paging.next}else{r.paging.next+="#"+p.path}}if(!r||"error"in r){promise.reject(r)}else{promise.fulfill(r)}})}};hello.utils.extend(hello.utils,{request:function(p,callback){var _this=this;var error=_this.error;if(!_this.isEmpty(p.data)&&!("FileList"in window)&&_this.hasBinary(p.data)){p.xhr=false;p.jsonp=false}var cors=this.request_cors(function(){return p.xhr===undefined||p.xhr&&(typeof p.xhr!=="function"||p.xhr(p,p.query))});if(cors){formatUrl(p,function(url){var x=_this.xhr(p.method,url,p.headers,p.data,callback);x.onprogress=p.onprogress||null;if(x.upload&&p.onuploadprogress){x.upload.onprogress=p.onuploadprogress}});return}var _query=p.query;p.query=_this.clone(p.query);p.callbackID=_this.globalEvent();if(p.jsonp!==false){p.query.callback=p.callbackID;if(typeof p.jsonp==="function"){p.jsonp(p,p.query)}if(p.method==="get"){formatUrl(p,function(url){_this.jsonp(url,callback,p.callbackID,p.timeout)});return}else{p.query=_query}}if(p.form!==false){p.query.redirect_uri=p.redirect_uri;p.query.state=JSON.stringify({callback:p.callbackID});var opts;if(typeof p.form==="function"){opts=p.form(p,p.query)}if(p.method==="post"&&opts!==false){formatUrl(p,function(url){_this.post(url,p.data,opts,callback,p.callbackID,p.timeout)});return}}callback(error("invalid_request","There was no mechanism for handling this request"));return;function formatUrl(p,callback){var sign;if(p.authResponse&&p.authResponse.oauth&&parseInt(p.authResponse.oauth.version,10)===1){sign=p.query.access_token;delete p.query.access_token;p.proxy=true}if(p.data&&(p.method==="get"||p.method==="delete")){_this.extend(p.query,p.data);p.data=null}var path=_this.qs(p.url,p.query);if(p.proxy){path=_this.qs(p.oauth_proxy,{path:path,access_token:sign||"",then:p.proxy_response_type||(p.method.toLowerCase()==="get"?"redirect":"proxy"),method:p.method.toLowerCase(),suppress_response_codes:p.suppress_response_codes||true})}callback(path)}},request_cors:function(callback){return"withCredentials"in new XMLHttpRequest&&callback()},domInstance:function(type,data){var test="HTML"+(type||"").replace(/^[a-z]/,function(m){return m.toUpperCase()})+"Element";if(!data){return false}if(window[test]){return data instanceof window[test]}else if(window.Element){return data instanceof window.Element&&(!type||data.tagName&&data.tagName.toLowerCase()===type)}else{return!(data instanceof Object||data instanceof Array||data instanceof String||data instanceof Number)&&data.tagName&&data.tagName.toLowerCase()===type}},clone:function(obj){if(obj===null||typeof obj!=="object"||obj instanceof Date||"nodeName"in obj||this.isBinary(obj)||typeof FormData==="function"&&obj instanceof FormData){return obj}if(Array.isArray(obj)){return obj.map(this.clone.bind(this))}var clone={};for(var x in obj){clone[x]=this.clone(obj[x])}return clone},xhr:function(method,url,headers,data,callback){var r=new XMLHttpRequest;var error=this.error;var binary=false;if(method==="blob"){binary=method;method="GET"}method=method.toUpperCase();r.onload=function(e){var json=r.response;try{json=JSON.parse(r.responseText)}catch(_e){if(r.status===401){json=error("access_denied",r.statusText)}}var headers=headersToJSON(r.getAllResponseHeaders());headers.statusCode=r.status;callback(json||(method==="GET"?error("empty_response","Could not get resource"):{}),headers)};r.onerror=function(e){var json=r.responseText;try{json=JSON.parse(r.responseText)}catch(_e){}callback(json||error("access_denied","Could not get resource"))};var x;if(method==="GET"||method==="DELETE"){data=null}else if(data&&typeof data!=="string"&&!(data instanceof FormData)&&!(data instanceof File)&&!(data instanceof Blob)){var f=new FormData;for(x in data)if(data.hasOwnProperty(x)){if(data[x]instanceof HTMLInputElement){if("files"in data[x]&&data[x].files.length>0){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])}}data=f}r.open(method,url,true);if(binary){if("responseType"in r){r.responseType=binary}else{r.overrideMimeType("text/plain; charset=x-user-defined")}}if(headers){for(x in headers){r.setRequestHeader(x,headers[x])}}r.send(data);return r;function headersToJSON(s){var r={};var reg=/([a-z\-]+):\s?(.*);?/gi;var m;while(m=reg.exec(s)){r[m[1]]=m[2]}return r}},jsonp:function(url,callback,callbackID,timeout){var _this=this;var error=_this.error;var bool=0;var head=document.getElementsByTagName("head")[0];var operaFix;var result=error("server_error","server_error");var cb=function(){if(!bool++){window.setTimeout(function(){callback(result);head.removeChild(script)},0)}};callbackID=_this.globalEvent(function(json){result=json;return true},callbackID);url=url.replace(new RegExp("=\\?(&|$)"),"="+callbackID+"$1");var script=_this.append("script",{id:callbackID,name:callbackID,src:url,async:true,onload:cb,onerror:cb,onreadystatechange:function(){if(/loaded|complete/i.test(this.readyState)){cb()}}});if(window.navigator.userAgent.toLowerCase().indexOf("opera")>-1){operaFix=_this.append("script",{text:"document.getElementById('"+callbackID+"').onerror();"});script.async=false}if(timeout){window.setTimeout(function(){result=error("timeout","timeout");cb()},timeout)}head.appendChild(script);if(operaFix){head.appendChild(operaFix)}},post:function(url,data,options,callback,callbackID,timeout){var _this=this;var error=_this.error;var doc=document;var form=null;var reenableAfterSubmit=[];var newform;var i=0;var x=null;var bool=0;var cb=function(r){if(!bool++){callback(r)}};_this.globalEvent(cb,callbackID);var win;try{win=doc.createElement('