WIP framing refactor
This commit is contained in:
		
							parent
							
								
									bbc557c349
								
							
						
					
					
						commit
						4acad44297
					
				| @ -4,10 +4,13 @@ | |||||||
| 
 | 
 | ||||||
|   var OAUTH3 = exports.OAUTH3 = { |   var OAUTH3 = exports.OAUTH3 = { | ||||||
|     utils: { |     utils: { | ||||||
|       atob: function (base64) { |       clientUri: function (location) { | ||||||
|  |         return OAUTH3.utils.uri.normalize(location.host + location.pathname); | ||||||
|  |       } | ||||||
|  |     , atob: function (base64) { | ||||||
|         return (exports.atob || require('atob'))(base64); |         return (exports.atob || require('atob'))(base64); | ||||||
|       } |       } | ||||||
|     , urlSafeBase64ToBase64: function (b64) { |     , _urlSafeBase64ToBase64: function (b64) { | ||||||
|         // URL-safe Base64 to Base64
 |         // URL-safe Base64 to Base64
 | ||||||
|         // https://en.wikipedia.org/wiki/Base64
 |         // https://en.wikipedia.org/wiki/Base64
 | ||||||
|         // https://gist.github.com/catwell/3046205
 |         // https://gist.github.com/catwell/3046205
 | ||||||
| @ -43,13 +46,6 @@ | |||||||
|             ; |             ; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       , getDefaultAppUrl: function () { |  | ||||||
|         console.warn('[deprecated] using window.location.{protocol, host, pathname} when opts.client_id should be used'); |  | ||||||
|         return window.location.protocol |  | ||||||
|           + '//' + window.location.host |  | ||||||
|           + (window.location.pathname).replace(/\/?$/, '') |  | ||||||
|           ; |  | ||||||
|       } |  | ||||||
|     , query: { |     , query: { | ||||||
|         stringify: function (params) { |         stringify: function (params) { | ||||||
|           var qs = []; |           var qs = []; | ||||||
| @ -163,11 +159,10 @@ | |||||||
| 
 | 
 | ||||||
|       return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives.get(providerUri)).then(function (directives) { |       return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives.get(providerUri)).then(function (directives) { | ||||||
|         if (directives && directives.issuer) { |         if (directives && directives.issuer) { | ||||||
|           return OAUTH3.PromiseA.resolve(directives); |           return directives; | ||||||
|         } |         } | ||||||
|         return OAUTH3._discoverHelper(providerUri, opts).then(function (directives) { |         return OAUTH3._discoverHelper(providerUri, opts).then(function (directives) { | ||||||
|           directives.issuer = directives.issuer || OAUTH3.utils.url.normalize(providerUri); |           directives.issuer = directives.issuer || OAUTH3.utils.url.normalize(providerUri); | ||||||
|           console.log('discoverHelper', directives); |  | ||||||
|           // OAUTH3.PromiseA.resolve() is taken care of because this is wrapped
 |           // OAUTH3.PromiseA.resolve() is taken care of because this is wrapped
 | ||||||
|           return OAUTH3.hooks.directives.set(providerUri, directives); |           return OAUTH3.hooks.directives.set(providerUri, directives); | ||||||
|         }); |         }); | ||||||
| @ -177,12 +172,181 @@ | |||||||
|   , _discoverHelper: function (providerUri, opts) { |   , _discoverHelper: function (providerUri, opts) { | ||||||
|       return OAUTH3._browser.discover(providerUri, opts); |       return OAUTH3._browser.discover(providerUri, opts); | ||||||
|     } |     } | ||||||
|  |   , request: function (preq) { | ||||||
|  |       return OAUTH3._browser.request(preq); | ||||||
|  |     } | ||||||
|  |   , implicitGrant: function(providerUri, opts) { | ||||||
|  |       var promise; | ||||||
|  | 
 | ||||||
|  |       if (opts.broker) { | ||||||
|  |         promise = OAUTH3._discoverThenImplicitGrant(providerUri, opts); | ||||||
|  |       } | ||||||
|  |       else { | ||||||
|  |         promise = OAUTH3._implicitGrant(providerUri, opts); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return promise.then(function (tokens) { | ||||||
|  |         return OAUTH3.hooks.refreshSession( | ||||||
|  |           opts.session || { | ||||||
|  |             provider_uri: providerUri | ||||||
|  |           , client_id: opts.client_id | ||||||
|  |           , client_uri: opts.client_uri || opts.clientUri | ||||||
|  |           } | ||||||
|  |         , tokens | ||||||
|  |         ); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   , _discoverThenImplicitGrant: function(providerUri, opts) { | ||||||
|  |       opts.windowType = opts.windowType || 'popup'; | ||||||
|  |       return OAUTH3._discover(providerUri, opts).then(function (directives) { | ||||||
|  |         return OAUTH3._implicitGrant(directives, opts).then(function (tokens) { | ||||||
|  |           OAUTH3._browser.closeFrame(tokens.state || opts._state); | ||||||
|  |           opts._state = undefined; | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   , _discover: function(providerUri, opts) { | ||||||
|  |       providerUri = OAUTH3.utils.url.normalize(providerUri); | ||||||
|  | 
 | ||||||
|  |       if (providerUri.match(OAUTH3._browser.window.location.hostname)) { | ||||||
|  |         console.warn("It looks like you're a provider checking for your own directive," | ||||||
|  |           + " so we we're just gonna use" | ||||||
|  |           + " OAUTH3.request({ method: 'GET', url: '.well-known/oauth3/directive.json' })"); | ||||||
|  |         return OAUTH3.request({ | ||||||
|  |           method: 'GET' | ||||||
|  |         , url: OAUTH3.utils.url.normalize(providerUri) + '/.well-known/oauth3/directives.json' | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (!(opts.client_id || opts.client_uri).match(OAUTH3._browser.window.location.hostname)) { | ||||||
|  |         console.warn("It looks like your client_id doesn't match your current window..." | ||||||
|  |           + " this probably won't end well"); | ||||||
|  |         console.warn(opts.client_id || opts.client_uri, OAUTH3._browser.window.location.hostname); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       var discReq = OAUTH3.urls.discover( | ||||||
|  |         providerUri | ||||||
|  |       , { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location)) | ||||||
|  |         , windowType: opts.broker && opts.windowType || 'background' | ||||||
|  |         , broker: opts.broker | ||||||
|  |         , debug: opts.debug | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |       opts._state = discReq.state; | ||||||
|  |       //var discReq = OAUTH3.urls.discover(providerUri, opts);
 | ||||||
|  | 
 | ||||||
|  |       // hmm... we're gonna need a broker for this since switching windows is distracting,
 | ||||||
|  |       // popups are obnoxious, iframes are sometimes blocked, and most servers don't implement CORS
 | ||||||
|  |       // eventually it should be the browser (and postMessage may be a viable option now), but whatever...
 | ||||||
|  | 
 | ||||||
|  |       // TODO allow postMessage from providerUri in addition to callback
 | ||||||
|  |       // TODO allow node to open a desktop browser window
 | ||||||
|  |       return OAUTH3._browser.frameRequest( | ||||||
|  |         discReq.url | ||||||
|  |       , discReq.state | ||||||
|  |       , { windowType: opts.windowType | ||||||
|  |         , reuseWindow: opts.broker && '-broker' | ||||||
|  |         , debug: opts.debug | ||||||
|  |         } | ||||||
|  |       ).then(function (params) { | ||||||
|  |         // discWin.child.close()
 | ||||||
|  |         // TODO params should have response_type indicating json, binary, etc
 | ||||||
|  |         var directives = JSON.parse(OAUTH3.utils.atob(OAUTH3.utils.urlSafeBase64ToBase64(params.result || params.directives))); | ||||||
|  |         return OAUTH3.hooks.directives.set(providerUri, directives); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   , _implicitGrant: function(providerUri, opts) { | ||||||
|  |       // TODO this may need to be synchronous for browser security policy
 | ||||||
|  |       return OAUTH3.PromiseA.resolve(OAUTH3.hooks.directives.get(providerUri)).then(function (directives) { | ||||||
|  |         // Do some stuff
 | ||||||
|  |         var authReq = OAUTH3.urls.implicitGrant( | ||||||
|  |           directives | ||||||
|  |         , { redirect_uri: opts.redirect_uri | ||||||
|  |           , client_id: opts.client_id || opts.client_uri | ||||||
|  |           , client_uri: opts.client_uri || opts.client_id | ||||||
|  |           , state: opts._state | ||||||
|  |           , debug: opts.debug | ||||||
|  |           } | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         if (opts.debug) { | ||||||
|  |           window.alert("DEBUG MODE: Pausing so you can look at logs and whatnot :) Fire at will!"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return new OAUTH3.PromiseA(function (resolve, reject) { | ||||||
|  |           return OAUTH3._browser.frameRequest( | ||||||
|  |             authReq.url | ||||||
|  |           , authReq.state // state should recycle params
 | ||||||
|  |           , { windowType: opts.windowType | ||||||
|  |             , reuseWindow: opts.broker && '-broker' | ||||||
|  |             , debug: opts.debug | ||||||
|  |             } | ||||||
|  |           ).then(function (tokens) { | ||||||
|  |             if (tokens.error) { | ||||||
|  |               return reject(OAUTH3.utils._formatError(tokens.error)); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             OAUTH3._browser.closeFrame(authReq.state, { debug: opts.debug || tokens.debug }); | ||||||
|  | 
 | ||||||
|  |             return tokens; | ||||||
|  |           }); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     //
 | ||||||
|  |     // Let the Code Waste begin!!
 | ||||||
|  |     //
 | ||||||
|   , _browser: { |   , _browser: { | ||||||
|       discover: function (providerUri, opts) { |       window: window | ||||||
|  |       // TODO we don't need to include this if we're using jQuery or angular
 | ||||||
|  |     , request: function (preq, _sys) { | ||||||
|  |         return new OAUTH3.PromiseA(function (resolve, reject) { | ||||||
|  |           var xhr; | ||||||
|  |           try { | ||||||
|  |             xhr = new XMLHttpRequest(_sys); | ||||||
|  |           } catch(e) { | ||||||
|  |             xhr = new XMLHttpRequest(); | ||||||
|  |           } | ||||||
|  |           xhr.onreadystatechange = function () { | ||||||
|  |             console.error('state change'); | ||||||
|  |             var data; | ||||||
|  |             if (xhr.readyState !== XMLHttpRequest.DONE) { | ||||||
|  |               // nothing to do here
 | ||||||
|  |               return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (xhr.status !== 200) { | ||||||
|  |               reject(new Error('bad status code: ' + xhr.status)); | ||||||
|  |               return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             try { | ||||||
|  |               data = JSON.parse(xhr.responseText); | ||||||
|  |             } catch(e) { | ||||||
|  |               data = xhr.responseText; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             resolve({ | ||||||
|  |               request: xhr | ||||||
|  |             , data: data | ||||||
|  |             , status: xhr.status | ||||||
|  |             }); | ||||||
|  |           }; | ||||||
|  |           xhr.open(preq.method, preq.url, true); | ||||||
|  |           var headers = preq.headers || {}; | ||||||
|  |           Object.keys(headers).forEach(function (key) { | ||||||
|  |             xhr.setRequestHeader(key, headers[key]); | ||||||
|  |           }); | ||||||
|  |           xhr.send(); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     , discover: function (providerUri, opts) { | ||||||
|         opts = opts || {}; |         opts = opts || {}; | ||||||
|         //opts.debug = true;
 |         //opts.debug = true;
 | ||||||
|         providerUri = OAUTH3.utils.url.normalize(providerUri); |         providerUri = OAUTH3.utils.url.normalize(providerUri); | ||||||
|         if (window.location.hostname.match(providerUri)) { |         if (providerUri.match(OAUTH3._browser.window.location.hostname)) { | ||||||
|           console.warn("It looks like you're a provider checking for your own directive," |           console.warn("It looks like you're a provider checking for your own directive," | ||||||
|             + " so we we're just gonna use OAUTH3.request({ method: 'GET', url: '.well-known/oauth3/directive.json' })"); |             + " so we we're just gonna use OAUTH3.request({ method: 'GET', url: '.well-known/oauth3/directive.json' })"); | ||||||
|           return OAUTH3.request({ |           return OAUTH3.request({ | ||||||
| @ -191,36 +355,41 @@ | |||||||
|           }); |           }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!window.location.hostname.match(opts.client_id || opts.client_uri)) { |         if (!(opts.client_id || opts.client_uri).match(OAUTH3._browser.window.location.hostname)) { | ||||||
|           console.warn("It looks like your client_id doesn't match your current window... this probably won't end well"); |           console.warn("It looks like your client_id doesn't match your current window... this probably won't end well"); | ||||||
|           console.warn(opts.client_id || opts.client_uri, window.location.hostname); |           console.warn(opts.client_id || opts.client_uri, OAUTH3._browser.window.location.hostname); | ||||||
|         } |         } | ||||||
|         var discObj = OAUTH3.urls.discover( |         var discObj = OAUTH3.urls.discover( | ||||||
|           providerUri |           providerUri | ||||||
|         , { client_id: (opts.client_id || opts.client_uri || OAUTH3.utils.getDefaultAppUrl()), debug: opts.debug } |         , { client_id: (opts.client_id || opts.client_uri || OAUTH3.clientUri(OAUTH3._browser.window.location)), debug: opts.debug } | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         // TODO ability to reuse iframe instead of closing
 |         // TODO ability to reuse iframe instead of closing
 | ||||||
|         return OAUTH3._browser.iframe.insert(discObj.url, discObj.state, opts).then(function (params) { |         return OAUTH3._browser._iframe.insert(discObj.url, discObj.state, opts).then(function (params) { | ||||||
|           OAUTH3._browser.iframe.remove(discObj.state); |           OAUTH3._browser.closeFrame(discObj.state, { debug: opts.debug || params.debug }); | ||||||
|           if (params.error) { |           if (params.error) { | ||||||
|             return OAUTH3.utils._formatError(providerUri, params.error); |             return OAUTH3.utils._formatError(providerUri, params.error); | ||||||
|           } |           } | ||||||
|           var directives = JSON.parse(OAUTH3.utils.atob(OAUTH3.utils.urlSafeBase64ToBase64(params.result || params.directives))); |           var directives = JSON.parse(OAUTH3.utils.atob(OAUTH3.utils._urlSafeBase64ToBase64(params.result || params.directives))); | ||||||
|           return directives; |           return directives; | ||||||
|         }, function (err) { |         }, function (err) { | ||||||
|           OAUTH3._browser.iframe.remove(discObj.state); |           OAUTH3._browser.closeFrame(discObj.state, { debug: opts.debug || err.debug }); | ||||||
|           return OAUTH3.PromiseA.reject(err); |           return OAUTH3.PromiseA.reject(err); | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|     , iframe: { |     , frameRequest: function (url, state, opts) { | ||||||
|         _frames: {} |         var previousFrame = OAUTH3._browser._frames[state]; | ||||||
|       , insert: function (url, state, opts) { | 
 | ||||||
|  |         if (!opts.windowType) { | ||||||
|  |           opts.windowType = 'popup'; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         opts = opts || {}; |         opts = opts || {}; | ||||||
|         if (opts.debug) { |         if (opts.debug) { | ||||||
|           opts.timeout = opts.timeout || 15 * 60 * 1000; |           opts.timeout = opts.timeout || 15 * 60 * 1000; | ||||||
|         } |         } | ||||||
|           var promise = new OAUTH3.PromiseA(function (resolve, reject) { | 
 | ||||||
|  |         return new OAUTH3.PromiseA(function (resolve, reject) { | ||||||
|           var tok; |           var tok; | ||||||
| 
 | 
 | ||||||
|           function cleanup() { |           function cleanup() { | ||||||
| @ -241,6 +410,76 @@ | |||||||
|             cleanup(); |             cleanup(); | ||||||
|           }, opts.timeout || 15 * 1000); |           }, opts.timeout || 15 * 1000); | ||||||
| 
 | 
 | ||||||
|  |           if ('background' === opts.windowType) { | ||||||
|  |             if (previousFrame) { | ||||||
|  |               previousFrame.location = url; | ||||||
|  |               //promise = previousFrame.promise;
 | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |               OAUTH3._browser._iframe.insert(url, state, opts); | ||||||
|  |             } | ||||||
|  |           } else if ('popup' === opts.windowType) { | ||||||
|  |             if (previousFrame) { | ||||||
|  |               previousFrame.location = url; | ||||||
|  |               if (opts.debug) { | ||||||
|  |                 previousFrame.focus(); | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |               OAUTH3._browser.frame.open(url, state, opts); | ||||||
|  |             } | ||||||
|  |           } else if ('inline' === opts.windowType) { | ||||||
|  |             // callback function will never execute and would need to redirect back to current page
 | ||||||
|  |             // rather than the callback.html
 | ||||||
|  |             url += '&original_url=' + OAUTH3._browser.window.location.href; | ||||||
|  |             OAUTH3._browser.window.location = url; | ||||||
|  |             //promise = OAUTH3.PromiseA.resolve({ url: url });
 | ||||||
|  |             return; | ||||||
|  |           } else { | ||||||
|  |             throw new Error("login framing method options.windowType=" | ||||||
|  |               + opts.windowType + " not type yet implemented"); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |         }).then(function (params) { | ||||||
|  |           var err; | ||||||
|  | 
 | ||||||
|  |           if (params.error || params.error_description) { | ||||||
|  |             err = new Error(params.error_description || "Unknown response error"); | ||||||
|  |             err.code = params.error || "E_UKNOWN_ERROR"; | ||||||
|  |             err.params = params; | ||||||
|  |             //_formatError
 | ||||||
|  |             return OAUTH3.PromiseA.reject(err); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           return params; | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     , closeFrame: function (state, opts) { | ||||||
|  |         function close() { | ||||||
|  |           try { | ||||||
|  |             OAUTH3._browser._frames[state].close(); | ||||||
|  |           } catch(e) { | ||||||
|  |             try { | ||||||
|  |               OAUTH3._browser._frames[state].remove(); | ||||||
|  |             } catch(e) { | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           delete OAUTH3._browser._frames[state]; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (opts.debug) { | ||||||
|  |           if (window.confirm("DEBUG MODE: okay to close oauth3 window?")) { | ||||||
|  |             close(); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |           close(); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     , _frames: {} | ||||||
|  |     , iframe: { | ||||||
|  |         insert: function (url, state, opts) { | ||||||
|           // TODO hidden / non-hidden (via directive even)
 |           // TODO hidden / non-hidden (via directive even)
 | ||||||
|           var framesrc = '<iframe class="js-oauth3-iframe" src="' + url + '" '; |           var framesrc = '<iframe class="js-oauth3-iframe" src="' + url + '" '; | ||||||
|           if (opts.debug) { |           if (opts.debug) { | ||||||
| @ -251,21 +490,60 @@ | |||||||
|           } |           } | ||||||
|           framesrc += '></iframe>'; |           framesrc += '></iframe>'; | ||||||
| 
 | 
 | ||||||
|             OAUTH3._browser.iframe._frames[state] = window.document.createElement('div'); |           var frame = OAUTH3._browser._frames[state] = OAUTH3._browser.window.document.createElement('div'); | ||||||
|             OAUTH3._browser.iframe._frames[state].innerHTML = framesrc; |           OAUTH3._browser._frames[state].innerHTML = framesrc; | ||||||
|  |           OAUTH3._browser.window.document.body.appendChild(OAUTH3._browser._frames[state]); | ||||||
| 
 | 
 | ||||||
|             window.document.body.appendChild(OAUTH3._browser.iframe._frames[state]); |           return frame; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     , frame: { | ||||||
|  |         open: function (url, state, opts) { | ||||||
|  |           if (opts.debug) { | ||||||
|  |             opts.timeout = opts.timeout || 15 * 60 * 1000; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           var promise = new OAUTH3.PromiseA(function (resolve, reject) { | ||||||
|  |             var tok; | ||||||
|  | 
 | ||||||
|  |             function cleanup() { | ||||||
|  |               clearTimeout(tok); | ||||||
|  |               tok = null; | ||||||
|  |               delete window['--oauth3-callback-' + state]; | ||||||
|  |               // close is done later in case the window is reused or self-closes synchronously itself / by parent
 | ||||||
|  |               // (probably won't ever happen, but that's a negotiable implementation detail)
 | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             window['--oauth3-callback-' + state] = function (params) { | ||||||
|  |               console.log('YOLO!!'); | ||||||
|  |               resolve(params); | ||||||
|  |               cleanup(); | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             tok = setTimeout(function () { | ||||||
|  |               var err = new Error("the windowed request did not complete within 3 minutes"); | ||||||
|  |               err.code = "E_TIMEOUT"; | ||||||
|  |               reject(err); | ||||||
|  |               cleanup(); | ||||||
|  |             }, opts.timeout || 3 * 60 * 1000); | ||||||
|  | 
 | ||||||
|  |             setTimeout(function () { | ||||||
|  |               if (!promise.child) { | ||||||
|  |                 reject("TODO: open the iframe first and discover oauth3 directives before popup"); | ||||||
|  |                 cleanup(); | ||||||
|  |               } | ||||||
|  |             }, 0); | ||||||
|           }); |           }); | ||||||
| 
 | 
 | ||||||
|  |           // TODO allow size changes (via directive even)
 | ||||||
|  |           OAUTH3._browser._frames[state] = window.open( | ||||||
|  |             url | ||||||
|  |           , 'oauth3-login-' + (opts.reuseWindow || state) | ||||||
|  |           , 'height=' + (opts.height || 720) + ',width=' + (opts.width || 620) | ||||||
|  |           ); | ||||||
|           // TODO periodically garbage collect expired handlers from window object
 |           // TODO periodically garbage collect expired handlers from window object
 | ||||||
|           return promise; |           return promise; | ||||||
|         } |         } | ||||||
|       , remove: function (state) { |  | ||||||
|           if (OAUTH3._browser.iframe._frames[state]) { |  | ||||||
|             OAUTH3._browser.iframe._frames[state].remove(); |  | ||||||
|           } |  | ||||||
|           delete OAUTH3._browser.iframe._frames[state]; |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user