561 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			561 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| ;(function (exports) {
 | |
|   'use strict';
 | |
| 
 | |
|   var OAUTH3 = exports.OAUTH3;
 | |
|   var OAUTH3_CORE = exports.OAUTH3_CORE;
 | |
| 
 | |
|   function getDefaultAppUrl() {
 | |
|     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(/\/?$/, '')
 | |
|       ;
 | |
|   }
 | |
| 
 | |
|   var browser = exports.OAUTH3_BROWSER = {
 | |
|     window: window
 | |
|   , clientUri: function (location) {
 | |
|       return OAUTH3_CORE.normalizeUri(location.host + location.pathname);
 | |
|     }
 | |
|   , discover: function (providerUri, opts) {
 | |
|       if (!providerUri) {
 | |
|         throw new Error('oauth3.discover(providerUri, opts) received providerUri as ' + providerUri);
 | |
|       }
 | |
|       var directives = OAUTH3.hooks.getDirectives(providerUri);
 | |
|       if (directives && directives.issuer) {
 | |
|         return OAUTH3.PromiseA.resolve(directives);
 | |
|       }
 | |
|       return browser._discoverHelper(providerUri, opts).then(function (directives) {
 | |
|         directives.issuer = directives.issuer || OAUTH3_CORE.normalizeUrl(providerUri);
 | |
|         console.log('discoverHelper', directives);
 | |
|         return OAUTH3.hooks.setDirectives(providerUri, directives);
 | |
|       });
 | |
|     }
 | |
|   , _discoverHelper: function (providerUri, opts) {
 | |
|       opts = opts || {};
 | |
|       //opts.debug = true;
 | |
|       providerUri = OAUTH3_CORE.normalizeUrl(providerUri);
 | |
|       if (window.location.hostname.match(providerUri)) {
 | |
|         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.core.normalizeUrl(providerUri) + '/.well-known/oauth3/directives.json'
 | |
|         });
 | |
|       }
 | |
| 
 | |
|       if (!window.location.hostname.match(opts.client_id || opts.client_uri)) {
 | |
|         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);
 | |
|       }
 | |
|       var discObj = OAUTH3_CORE.urls.discover(providerUri, { client_id: (opts.client_id || opts.client_uri || getDefaultAppUrl()), debug: opts.debug });
 | |
| 
 | |
|       // TODO ability to reuse iframe instead of closing
 | |
|       return browser.insertIframe(discObj.url, discObj.state, opts).then(function (params) {
 | |
|         if (params.error) {
 | |
|           return OAUTH3_CORE.formatError(providerUri, params.error);
 | |
|         }
 | |
|         var directives = JSON.parse(atob(OAUTH3_CORE.utils.urlSafeBase64ToBase64(params.result || params.directives)));
 | |
|         return directives;
 | |
|       }, function (err) {
 | |
|         return OAUTH3.PromiseA.reject(err);
 | |
|       });
 | |
|     }
 | |
| 
 | |
|   , discoverAuthorizationDialog: function(providerUri, opts) {
 | |
|       var discObj = OAUTH3.core.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
 | |
|       var discWin = OAUTH3.openWindow(discObj.url, discObj.state, { reuseWindow: 'conquerer' });
 | |
|       return discWin.then(function (params) {
 | |
|         console.log('discwin params');
 | |
|         console.log(params);
 | |
|         // discWin.child
 | |
|         // TODO params should have response_type indicating json, binary, etc
 | |
|         var directives = JSON.parse(atob(OAUTH3.core.utils.urlSafeBase64ToBase64(params.result || params.directives)));
 | |
|         console.log('directives');
 | |
|         console.log(directives);
 | |
| 
 | |
|         // Do some stuff
 | |
|         var authObj = OAUTH3.core.implicitGrant(
 | |
|           directives
 | |
|         , { redirect_uri: opts.redirect_uri
 | |
|           , debug: opts.debug
 | |
|           , client_id: opts.client_id || opts.client_uri
 | |
|           , client_uri: opts.client_uri || opts.client_id
 | |
|           }
 | |
|         );
 | |
| 
 | |
|         if (params.debug) {
 | |
|           window.alert("DEBUG MODE: Pausing so you can look at logs and whatnot :) Fire at will!");
 | |
|         }
 | |
| 
 | |
|         return new OAUTH3.PromiseA(function (resolve, reject) {
 | |
|           // TODO check if authObj.url is relative or full
 | |
|           discWin.child.location = OAUTH3.core.urls.resolve(providerUri, authObj.url);
 | |
| 
 | |
|           if (params.debug) {
 | |
|             discWin.child.focus();
 | |
|           }
 | |
| 
 | |
|           window['--oauth3-callback-' + authObj.state] = function (tokens) {
 | |
|             if (tokens.error) {
 | |
|               return reject(OAUTH3.core.formatError(tokens.error));
 | |
|             }
 | |
| 
 | |
|             if (params.debug || tokens.debug) {
 | |
|               if (window.confirm("DEBUG MODE: okay to close oauth3 window?")) {
 | |
|                 discWin.child.close();
 | |
|               }
 | |
|             }
 | |
|             else {
 | |
|               discWin.child.close();
 | |
|             }
 | |
| 
 | |
|             resolve(tokens);
 | |
|           };
 | |
|         });
 | |
|       }).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
 | |
|         );
 | |
|       });
 | |
|     }
 | |
| 
 | |
|   , frameRequest: function (url, state, opts) {
 | |
|       var promise;
 | |
| 
 | |
|       if (!opts.windowType) {
 | |
|         opts.windowType = 'popup';
 | |
|       }
 | |
| 
 | |
|       if ('background' === opts.windowType) {
 | |
|         promise = browser.insertIframe(url, state, opts);
 | |
|       } else if ('popup' === opts.windowType) {
 | |
|         promise = browser.openWindow(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=' + browser.window.location.href;
 | |
|         promise = browser.window.location = url;
 | |
|       } else {
 | |
|         throw new Error("login framing method options.windowType not specified or not type yet implemented");
 | |
|       }
 | |
| 
 | |
|       return promise.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;
 | |
|           return OAUTH3.PromiseA.reject(err);
 | |
|         }
 | |
| 
 | |
|         return params;
 | |
|       });
 | |
|     }
 | |
| 
 | |
|   , insertIframe: function (url, state, opts) {
 | |
|       opts = opts || {};
 | |
|       if (opts.debug) {
 | |
|         opts.timeout = opts.timeout || 15 * 60 * 1000;
 | |
|       }
 | |
|       var promise = new OAUTH3.PromiseA(function (resolve, reject) {
 | |
|         var tok;
 | |
|         var iframeDiv;
 | |
| 
 | |
|         function cleanup() {
 | |
|           delete window['--oauth3-callback-' + state];
 | |
|           iframeDiv.remove();
 | |
|           clearTimeout(tok);
 | |
|           tok = null;
 | |
|         }
 | |
| 
 | |
|         window['--oauth3-callback-' + state] = function (params) {
 | |
|           resolve(params);
 | |
|           cleanup();
 | |
|         };
 | |
| 
 | |
|         tok = setTimeout(function () {
 | |
|           var err = new Error("the iframe request did not complete within 15 seconds");
 | |
|           err.code = "E_TIMEOUT";
 | |
|           reject(err);
 | |
|           cleanup();
 | |
|         }, opts.timeout || 15 * 1000);
 | |
| 
 | |
|         // TODO hidden / non-hidden (via directive even)
 | |
|         var framesrc = '<iframe class="js-oauth3-iframe" src="' + url + '" ';
 | |
|         if (opts.debug) {
 | |
|           framesrc += ' width="800px" height="800px" style="opacity: 0.8;" frameborder="1"';
 | |
|         }
 | |
|         else {
 | |
|           framesrc += ' width="1px" height="1px" frameborder="0"';
 | |
|         }
 | |
|         framesrc += '></iframe>';
 | |
| 
 | |
|         iframeDiv = window.document.createElement('div');
 | |
|         iframeDiv.innerHTML = framesrc;
 | |
|         window.document.body.appendChild(iframeDiv);
 | |
|       });
 | |
| 
 | |
|       // TODO periodically garbage collect expired handlers from window object
 | |
|       return promise;
 | |
|     }
 | |
| 
 | |
|   , openWindow: 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];
 | |
|           // this is last in case the window self-closes synchronously
 | |
|           // (should never happen, but that's a negotiable implementation detail)
 | |
|           if (!opts.reuseWindow) {
 | |
|             promise.child.close();
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         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)
 | |
|       promise.child = 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
 | |
|       return promise;
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // Logins
 | |
|     //
 | |
|   , authn: {
 | |
|       authorizationRedirect: function (providerUri, opts) {
 | |
|         // TODO get own directives
 | |
| 
 | |
|         return OAUTH3.discover(providerUri, opts).then(function (directive) {
 | |
|           var prequest = OAUTH3_CORE.urls.authorizationRedirect(
 | |
|             directive
 | |
|           , opts
 | |
|           );
 | |
| 
 | |
|           if (!prequest.state) {
 | |
|             throw new Error("[Devolper Error] [authorization redirect] prequest.state is empty");
 | |
|           }
 | |
| 
 | |
|           return browser.frameRequest(prequest.url, prequest.state, opts);
 | |
|         }).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
 | |
|           );
 | |
|         });
 | |
|       }
 | |
|     , implicitGrant: function (providerUri, opts) {
 | |
|         // TODO let broker=true change behavior to open discover inline with frameRequest
 | |
|         // TODO OAuth3 provider should use the redirect URI as the appId?
 | |
|         return OAUTH3.discover(providerUri, opts).then(function (directive) {
 | |
|           var prequest = OAUTH3_CORE.urls.implicitGrant(
 | |
|             directive
 | |
|             // TODO OAuth3 provider should referrer / referer / origin as the appId?
 | |
|           , opts
 | |
|           );
 | |
| 
 | |
|           if (!prequest.state) {
 | |
|             throw new Error("[Devolper Error] [implicit grant] prequest.state is empty");
 | |
|           }
 | |
| 
 | |
|           return browser.frameRequest(prequest.url, prequest.state, opts);
 | |
|         }).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
 | |
|           );
 | |
|         });
 | |
|       }
 | |
|     , logout: function (providerUri, opts) {
 | |
|         opts = opts || {};
 | |
| 
 | |
|         return OAUTH3.discover(providerUri, opts).then(function (directive) {
 | |
|           var prequest = OAUTH3_CORE.urls.logout(
 | |
|             directive
 | |
|           , opts
 | |
|           );
 | |
|           // Oauth3.init({ logout: function () {} });
 | |
|           //return Oauth3.logout();
 | |
| 
 | |
|           var redirectUri = opts.redirect_uri || opts.redirectUri
 | |
|             || (window.location.protocol + '//' + (window.location.host + window.location.pathname) + 'oauth3.html')
 | |
|             ;
 | |
|           var params = {
 | |
|             // logout=true for all logins/accounts
 | |
|             // logout=app-scoped-login-id for a single login
 | |
|             action: 'logout'
 | |
|             // TODO specify specific accounts / logins to delete from session
 | |
|           , accounts: true
 | |
|           , logins: true
 | |
|           , redirect_uri: redirectUri
 | |
|           , state: prequest.state
 | |
|           , debug: opts.debug
 | |
|           };
 | |
| 
 | |
|           if (prequest.url === params.redirect_uri) {
 | |
|             return OAUTH3.PromiseA.resolve();
 | |
|           }
 | |
| 
 | |
|           prequest.url += '#' + OAUTH3_CORE.querystringify(params);
 | |
| 
 | |
|           return OAUTH3.insertIframe(prequest.url, prequest.state, opts);
 | |
|         });
 | |
|       }
 | |
|     }
 | |
|   , isIframe: function isIframe () {
 | |
|       try {
 | |
|         return window.self !== window.top;
 | |
|       } catch (e) {
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|   , parseUrl: function (url) {
 | |
|       var parser = document.createElement('a');
 | |
|       parser.href = url;
 | |
|       return parser;
 | |
|     }
 | |
|   , isRedirectHostSafe: function (referrerUrl, redirectUrl) {
 | |
|       var src = browser.parseUrl(referrerUrl);
 | |
|       var dst = browser.parseUrl(redirectUrl);
 | |
| 
 | |
|       // TODO how should we handle subdomains?
 | |
|       // It should be safe for api.example.com to redirect to example.com
 | |
|       // But it may not be safe for to example.com to redirect to aj.example.com
 | |
|       // It is also probably not safe for sally.example.com to redirect to john.example.com
 | |
|       // The client should have a list of allowed URLs to choose from and perhaps a wildcard will do
 | |
|       //
 | |
|       // api.example.com.evil.com SHOULD NOT match example.com
 | |
|       return dst.hostname === src.hostname;
 | |
|     }
 | |
|   , checkRedirect: function (client, query) {
 | |
|       console.warn("[security] URL path checking not yet implemented");
 | |
| 
 | |
|       var clientUrl = OAUTH3.core.normalizeUrl(client.url);
 | |
|       var redirectUrl = OAUTH3.core.normalizeUrl(query.redirect_uri);
 | |
| 
 | |
|       // General rule:
 | |
|       // I can callback to a shorter domain (fewer subs) or a shorter path (on the same domain)
 | |
|       // but not a longer (more subs) or different domain or a longer path (on the same domain)
 | |
| 
 | |
| 
 | |
|       // We can callback to an explicitly listed domain (TODO and path)
 | |
|       if (browser.isRedirectHostSafe(clientUrl, redirectUrl)) {
 | |
|         return true;
 | |
|       }
 | |
| 
 | |
|       return false;
 | |
|     }
 | |
|   /*
 | |
|   , redirect: function (redirect) {
 | |
|       if (parser.search) {
 | |
|         parser.search += '&';
 | |
|       } else {
 | |
|         parser.search += '?';
 | |
|       }
 | |
| 
 | |
|       parser.search += 'error=E_NO_SESSION';
 | |
|       redirectUri = parser.href;
 | |
| 
 | |
|       window.location.href = redirectUri;
 | |
|     }
 | |
|   */
 | |
| 
 | |
|   , hackFormSubmit: function (opts) {
 | |
|       opts = opts || {};
 | |
|       scope.authorizationDecisionUri = DaplieApiConfig.providerUri + '/api/org.oauth3.provider/authorization_decision';
 | |
|       scope.updateScope();
 | |
| 
 | |
|       var redirectUri = scope.appQuery.redirect_uri.replace(/^https?:\/\//i, 'https://');
 | |
|       var separator;
 | |
| 
 | |
|       // TODO check that we appropriately use '#' for implicit and '?' for code
 | |
|       // (server-side) in an OAuth2 backwards-compatible way
 | |
|       if ('token' === scope.appQuery.response_type) {
 | |
|         separator = '#';
 | |
|       }
 | |
|       else if ('code' === scope.appQuery.response_type) {
 | |
|         separator = '?';
 | |
|       }
 | |
|       else {
 | |
|         separator = '#';
 | |
|       }
 | |
| 
 | |
|       if (scope.pendingScope.length && !opts.allow) {
 | |
|         redirectUri += separator + Oauth3.querystringify({
 | |
|           error: 'access_denied'
 | |
|           , error_description: 'None of the permissions were accepted'
 | |
|           , error_uri: 'https://oauth3.org/docs/errors#access_denied'
 | |
|           , state: scope.appQuery.state
 | |
|         });
 | |
|         window.location.href = redirectUri;
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       // TODO move to Oauth3? or not?
 | |
|       // this could be implementation-specific,
 | |
|       // but it may still be nice to provide it as de-facto
 | |
|       var url = DaplieApiConfig.apiBaseUri + '/api/org.oauth3.provider/grants/:client_id/:account_id'
 | |
|         .replace(/:client_id/g, scope.appQuery.client_id || scope.appQuery.client_uri)
 | |
|         .replace(/:account_id/g, scope.selectedAccountId)
 | |
|         ;
 | |
| 
 | |
|       var account = scope.sessionAccount;
 | |
|       var session = { accessToken: account.token, refreshToken: account.refreshToken };
 | |
|       var preq = {
 | |
|         url: url
 | |
|       , method: 'POST'
 | |
|       , data: {
 | |
|           scope: updateAccepted()
 | |
|         , response_type: scope.appQuery.response_type
 | |
|         , referrer: document.referrer || document.referer || ''
 | |
|         , referer: document.referrer || document.referer || ''
 | |
|         , tenant_id: scope.appQuery.tenant_id
 | |
|         , client_id: scope.appQuery.client_id
 | |
|         , client_uri: scope.appQuery.client_uri
 | |
|         }
 | |
|       , session: session
 | |
|       };
 | |
|       preq.clientId = preq.appId = DaplieApiConfig.appId || DaplieApiConfig.clientId;
 | |
|       preq.clientUri = preq.appUri = DaplieApiConfig.appUri || DaplieApiConfig.clientUri;
 | |
|       // TODO need a way to have middleware in Oauth3.request for TherapySession et al
 | |
| 
 | |
|       return Oauth3.request(preq).then(function (resp) {
 | |
|         var err;
 | |
|         var data = resp.data || {};
 | |
| 
 | |
|         if (data.error) {
 | |
|           err = new Error(data.error.message || data.errorDescription);
 | |
|           err.message = data.error.message || data.errorDescription;
 | |
|           err.code = resp.data.error.code || resp.data.error;
 | |
|           err.uri = 'https://oauth3.org/docs/errors#' + (resp.data.error.code || resp.data.error);
 | |
|           return $q.reject(err);
 | |
|         }
 | |
| 
 | |
|         if (!(data.code || data.accessToken)) {
 | |
|           err = new Error("No grant code");
 | |
|           return $q.reject(err);
 | |
|         }
 | |
| 
 | |
|         return data;
 | |
|       }).then(function (data) {
 | |
|         redirectUri += separator + Oauth3.querystringify({
 | |
|           state: scope.appQuery.state
 | |
| 
 | |
|         , code: data.code
 | |
| 
 | |
|         , access_token: data.access_token
 | |
|         , expires_at: data.expires_at
 | |
|         , expires_in: data.expires_in
 | |
|         , scope: data.scope
 | |
| 
 | |
|         , refresh_token: data.refresh_token
 | |
|         , refresh_expires_at: data.refresh_expires_at
 | |
|         , refresh_expires_in: data.refresh_expires_in
 | |
|         });
 | |
| 
 | |
|         if ('token' === scope.appQuery.response_type) {
 | |
|           window.location.href = redirectUri;
 | |
|           return;
 | |
|         }
 | |
|         else if ('code' === scope.appQuery.response_type) {
 | |
|           scope.hackFormSubmitHelper(redirectUri);
 | |
|           return;
 | |
|         }
 | |
|         else {
 | |
|           console.warn("Grant Code NOT IMPLEMENTED for '" + scope.appQuery.response_type + "'");
 | |
|           console.warn(redirectUri);
 | |
|           throw new Error("Grant Code NOT IMPLEMENTED for '" + scope.appQuery.response_type + "'");
 | |
|         }
 | |
|       }, function (err) {
 | |
|         redirectUri += separator + Oauth3.querystringify({
 | |
|           error: err.code || 'server_error'
 | |
|         , error_description: err.message || "Server Error: It's not your fault"
 | |
|         , error_uri: err.uri || 'https://oauth3.org/docs/errors#server_error'
 | |
|         , state: scope.appQuery.state
 | |
|         });
 | |
| 
 | |
|         console.error('Grant Code Error NOT IMPLEMENTED');
 | |
|         console.error(err);
 | |
|         console.error(redirectUri);
 | |
|         //window.location.href = redirectUri;
 | |
|       });
 | |
|     }
 | |
| 
 | |
|   , hackFormSubmitHelper: function (uri) {
 | |
|       // TODO de-jQuerify
 | |
|       //window.location.href = redirectUri;
 | |
|       //return;
 | |
| 
 | |
|       // the only way to do a POST that redirects the current window
 | |
|       window.jQuery('form.js-hack-hidden-form').attr('action', uri);
 | |
| 
 | |
|       // give time for the apply to take place
 | |
|       window.setTimeout(function () {
 | |
|         window.jQuery('form.js-hack-hidden-form').submit();
 | |
|       }, 50);
 | |
|     }
 | |
|   };
 | |
|   browser.requests = browser.authn;
 | |
| 
 | |
|   Object.keys(browser).forEach(function (key) {
 | |
|     if ('requests' === key) {
 | |
|       Object.keys(browser.requests).forEach(function (key) {
 | |
|         OAUTH3.requests[key] = browser.requests[key];
 | |
|       });
 | |
|       return;
 | |
|     }
 | |
|     OAUTH3[key] = browser[key];
 | |
|   });
 | |
| 
 | |
| }('undefined' !== typeof exports ? exports : window));
 |