text: (function(global)
  {
      Gitana.OAuth2Http = Gitana.Http.extend(
      /** @lends Gitana.OAuth2Http.prototype */
      {
          /**
           * @constructs
           *
           * @class Gitana.OAuth2Http
           */
          constructor: function(options, storage)
          {
              var self = this;
  
              // storage for OAuth credentials
              // this can either be a string ("local", "session", "memory") or a storage instance or empty
              // if empty, memory-based storage is assumed
              if (storage === null || typeof(storage) === "string")
              {
                  storage = new Gitana.OAuth2Http.Storage(storage);
              }
  
              // cookie mode
              this.cookieMode = null;
  
              // ticket mode
              this.ticketMode = null;
  
              // preset the error state
              this.error = null;
              this.errorDescription = null;
              this.errorUri = null;
  
              // gitana urls
              var tokenURL = "/oauth/token";
              if (options.tokenURL)
              {
                  tokenURL = options.tokenURL;
              }
  
              // base URL?
              var baseURL = null;
              if (options.baseURL)
              {
                  baseURL = options.baseURL;
              }
  
              // client
              var clientKey = options.clientKey;
              var clientSecret = options.clientSecret;
  
              // authorization flow
              // if none specified, assume AUTHORIZATION CODE
              this.authorizationFlow = options.authorizationFlow || Gitana.OAuth2Http.AUTHORIZATION_CODE;
  
              // optional
              if (options.requestedScope)
              {
                  this.requestedScope = options.requestedScope;
              }
  
              if (this.authorizationFlow == Gitana.OAuth2Http.AUTHORIZATION_CODE)
              {
                  this.code = options.code;
                  this.redirectUri = options.redirectUri;
              }
  
              if (this.authorizationFlow == Gitana.OAuth2Http.PASSWORD)
              {
                  this.username = options.username;
  
                  if (options.password)
                  {
                      this.password = options.password;
                  }
                  else
                  {
                      this.password = "";
                  }
              }
  
              if (this.authorizationFlow == Gitana.OAuth2Http.COOKIE)
              {
                  this.cookieMode = true;
              }
  
              if (this.authorizationFlow == Gitana.OAuth2Http.TICKET)
              {
                  this.ticketMode = options.ticket;
              }
  
              this.ticketMaxAge = options.ticketMaxAge;
  
  
              ////////////////////////////////////////////////////////////////////////////////////////////////
              //
              // ACCESSORS
              //
              ////////////////////////////////////////////////////////////////////////////////////////////////
  
              /**
               * Clears persisted storage of auth data
               */
              this.clearStorage = function()
              {
                  storage.clear();
              };
  
              /**
               * Gets or saves the access token
               *
               * @param value [String] optional value
               */
              this.accessToken = function(value)
              {
                  return storage.poke("accessToken", value);
              };
  
              /**
               * Gets or saves the refresh token
               *
               * @param value [String] optional value
               */
              this.refreshToken = function(value)
              {
                  return storage.poke("refreshToken", value);
              };
  
              /**
               * Gets or saves the granted scope
               *
               * @param value [String] optional value
               */
              this.grantedScope = function(value)
              {
                  return storage.poke("grantedScope", value);
              };
  
              /**
               * Gets or saves the expires in value
               *
               * @param value [String] optional value
               */
              this.expiresIn = function(value)
              {
                  return storage.poke("expiresIn", value);
              };
  
              /**
               * Gets or saves the grant time
               *
               * @param value [String] optional value
               */
              this.grantTime = function(value)
              {
                  return storage.poke("grantTime", value);
              };
  
              this.getClientAuthorizationHeader = function() {
  
                  var basicString = clientKey + ":";
                  if (clientSecret)
                  {
                      basicString += clientSecret;
                  }
                  return "Basic " + Gitana.btoa(basicString);
              };
  
              this.getBearerAuthorizationHeader = function()
              {
                  return "Bearer " + self.accessToken();
              };
  
              this.getPrefixedTokenURL = function()
              {
                  return this.getPrefixedURL(tokenURL);
              };
  
              this.getPrefixedURL = function(url)
              {
                  var rebasedURL = url;
                  if (baseURL && Gitana.startsWith(url, "/"))
                  {
                      rebasedURL = baseURL + url;
                  }
  
                  return rebasedURL;
              };
  
  
              // if they initiatialized with an access token, clear and write into persisted state
              // unless they're continuing an existing token
              if (this.authorizationFlow == Gitana.OAuth2Http.TOKEN)
              {
                  var existingAccessToken = this.accessToken();
                  if (existingAccessToken !== options.accessToken)
                  {
                      storage.clear();
                  }
  
                  this.accessToken(options.accessToken);
              }
  
              this.base();
          },
  
          /**
           * Performs an HTTP call using OAuth2.
           *
           * @param options
           */
          request: function(options)
          {
              var self = this;
  
              /**
               * Call over to Gitana and acquires an access token using flow authorization.
               *
               * @param success
               * @param failure
               */
              var doGetAccessToken = function(success, failure)
              {
                  var onSuccess = function(response, xhr)
                  {
                      var object = JSON.parse(response.text);
                      if (object["error"])
                      {
                          self.error = object["error"];
                          self.errorDescription = object["error_description"];
                          self.errorUri = object["error_uri"];
  
                          return failure(response, xhr);
                      }
  
                      var _accessToken = object["access_token"];
                      var _refreshToken = object["refresh_token"];
                      var _expiresIn = object["expires_in"];
                      var _grantedScope = object["scope"];
                      var _grantTime = new Date().getTime();
  
                      // store into persistent storage
                      self.clearStorage();
                      self.accessToken(_accessToken);
                      self.refreshToken(_refreshToken);
                      self.expiresIn(_expiresIn);
                      self.grantedScope(_grantedScope);
                      self.grantTime(_grantTime);
  
                      // console.log("doGetAccessToken -> " + JSON.stringify(object));
  
                      success();
                  };
  
                  var onFailure = function(http, xhr) {
                      failure(http, xhr);
                  };
  
                  var o = {
                      success: onSuccess,
                      failure: onFailure,
                      headers: {},
                      url: self.getPrefixedTokenURL(),
                      method: Gitana.OAuth2Http.TOKEN_METHOD
                  };
  
                  if (Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
                  {
                      for (var k in Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
                      {
                          o.headers[k] = Gitana.OAUTH2_TOKEN_REQUEST_HEADERS[k];
                      }
                  }
  
                  o.headers["Authorization"] = self.getClientAuthorizationHeader();
  
                  // query string
                  var qs = {};
  
                  // ticket max age
                  if (self.ticketMaxAge)
                  {
                      qs["ticketMaxAge"] = self.ticketMaxAge;
                  }
  
                  // if we're POSTing, do so as application/x-www-form-urlencoded to make secure over the wire
                  if ("post" === Gitana.OAuth2Http.TOKEN_METHOD.toLowerCase())
                  {
                      o.headers["Content-Type"] = "application/x-www-form-urlencoded";
  
                      // url encoded payload
                      var urlEncodedTokens = {};
                      urlEncodedTokens["grant_type"] = self.authorizationFlow;
                      if (self.requestedScope) {
                          urlEncodedTokens["scope"] = self.requestedScope;
                      }
                      if (self.authorizationFlow === Gitana.OAuth2Http.AUTHORIZATION_CODE)
                      {
                          urlEncodedTokens["code"] = self.code;
                          if (self.redirectUri) {
                              urlEncodedTokens["redirect_uri"] = self.redirectUri;
                          }
                      }
                      else if (self.authorizationFlow === Gitana.OAuth2Http.PASSWORD)
                      {
                          urlEncodedTokens["username"] = self.username;
                          urlEncodedTokens["password"] = self.password;
                      }
                      o.data = "" + Gitana.Http.toQueryString(urlEncodedTokens);
                  }
                  else
                  {
                      qs["grant_type"] = self.authorizationFlow;
                      if (self.requestedScope) {
                          qs["scope"] = self.requestedScope;
                      }
                      if (self.authorizationFlow === Gitana.OAuth2Http.AUTHORIZATION_CODE)
                      {
                          qs["code"] = self.code;
                          if (self.redirectUri) {
                              qs["redirect_uri"] = self.redirectUri;
                          }
                      }
                      else if (self.authorizationFlow === Gitana.OAuth2Http.PASSWORD)
                      {
                          qs["username"] = self.username;
                          qs["password"] = self.password;
                      }
                  }
  
                  // append into query string
                  var queryString = Gitana.Http.toQueryString(qs);
                  if (queryString)
                  {
                      if (o.url.indexOf("?") > -1)
                      {
                          o.url = o.url + "&" + queryString;
                      }
                      else
                      {
                          o.url = o.url + "?" + queryString;
                      }
                  }
  
                  self.invoke(o);
              };
  
              if (typeof(Gitana.REFRESH_TOKEN_LOCKS) === "undefined")
              {
                  Gitana.REFRESH_TOKEN_LOCKS = {};
              }
              if (typeof(Gitana.REFRESH_TOKEN_LOCK_REATTEMPT_MS) === "undefined")
              {
                  Gitana.REFRESH_TOKEN_LOCK_REATTEMPT_MS = 75;
              }
  
              var waitForPendingRefresh = function(key, oldAccessToken)
              {
                  setTimeout(function() {
  
                      // if another "thread" is still refreshing, keep on waiting
                      if (Gitana.REFRESH_TOKEN_LOCKS[key]) {
                          return waitForPendingRefresh();
                      }
  
                      // if we get this far, we take advantage of the new access key
                      // first check to make sure that it is a different access key
                      var newAccessToken = self.accessToken();
  
                      // we try the call again under the assumption that the access token is valid
                      // if the access token is different, we allow for another attempted refresh
                      // otherwise we do not to avoid spinning around forever
                      var autoAttemptRefresh = (newAccessToken === oldAccessToken);
  
                      // fire the call
                      doCall(autoAttemptRefresh);
  
                  }, Gitana.REFRESH_TOKEN_LOCK_REATTEMPT_MS)
              };
  
              /**
               * Calls over to Gitana and acquires an access token using an existing refresh token.
               *
               * We use a refresh token lock here (scoped to the module) so that only one event loop per refresh token
               * is allowed to perform the refresh at a time.  This is to avoid making excessive network calls and also
               * helps to avoid race/bounce conditions when multiple refresh tokens come back, spinning things out of
               * control.  Eventually it settles down but better to avoid altogether.
               *
               * @param success
               * @param failure
               */
              var doRefreshAccessToken = function(success, failure) {
  
                  var key = self.refreshToken();
                  var oldAccessToken = self.accessToken();
  
                  // if another "thread" is refreshing for this refresh key, then we wait until it finishes
                  // when it finishes, we either use the acquired access token or make another attempt
                  if (Gitana.REFRESH_TOKEN_LOCKS[key]) {
                      return waitForPendingRefresh(key, oldAccessToken);
                  }
  
                  // claim that we are the "thread" doing the refresh
                  Gitana.REFRESH_TOKEN_LOCKS[key] = true;
  
                  // make the http call for the refresh
                  _doRefreshAccessToken(function(response) {
  
                      // all done, delete the lock
                      delete Gitana.REFRESH_TOKEN_LOCKS[key];
  
                      // callback
                      success(response);
  
                  }, function(http, xhr) {
  
                      // didn't work, release the lock
                      delete Gitana.REFRESH_TOKEN_LOCKS[key];
  
                      // callback
                      failure(http, xhr);
                  });
              };
  
              var _doRefreshAccessToken = function(success, failure) {
  
                  var onSuccess = function(response)
                  {
                      var object = JSON.parse(response.text);
                      if (response["error"])
                      {
                          self.error = object["error"];
                          self.errorDescription = object["error_description"];
                          self.errorUri = object["error_uri"];
                      }
                      else
                      {
                          var _accessToken = object["access_token"];
                          var _refreshToken = object["refresh_token"];
                          var _expiresIn = object["expires_in"];
                          //self.grantedScope = object["scope"]; // this doesn't come back on refresh, assumed the same
                          var _grantTime = new Date().getTime();
                          var _grantedScope = self.grantedScope();
  
                          // store into persistent storage
                          self.clearStorage();
                          self.accessToken(_accessToken);
                          self.refreshToken(_refreshToken);
                          self.expiresIn(_expiresIn);
                          self.grantedScope(_grantedScope);
                          self.grantTime(_grantTime);
                      }
  
                      success(response);
                  };
  
                  var onFailure = function(http, xhr) {
  
                      Gitana.REFRESH_TOKEN_FAILURE_FN(self, http, xhr);
  
                      failure(http, xhr);
                  };
  
                  var o = {
                      success: onSuccess,
                      failure: onFailure,
                      headers: {},
                      url: self.getPrefixedTokenURL(),
                      method: Gitana.OAuth2Http.TOKEN_METHOD
                  };
  
                  if (Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
                  {
                      for (var k in Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
                      {
                          o.headers[k] = Gitana.OAUTH2_TOKEN_REQUEST_HEADERS[k];
                      }
                  }
  
                  o.headers["Authorization"] = self.getClientAuthorizationHeader();
  
                  // query string
                  var qs = {};
  
                  // ticket max age
                  if (self.ticketMaxAge)
                  {
                      qs["ticketMaxAge"] = self.ticketMaxAge;
                  }
  
                  // if we're POSTing, do so as application/x-www-form-urlencoded to make secure over the wire
                  if ("post" === Gitana.OAuth2Http.TOKEN_METHOD.toLowerCase())
                  {
                      o.headers["Content-Type"] = "application/x-www-form-urlencoded";
  
                      // url encoded payload
                      var urlEncodedTokens = {};
                      urlEncodedTokens["grant_type"] = "refresh_token";
                      urlEncodedTokens["refresh_token"] = self.refreshToken();
                      if (self.requestedScope)
                      {
                          urlEncodedTokens["scope"] = self.requestedScope;
                      }
                      o.data = "" + Gitana.Http.toQueryString(urlEncodedTokens);
                  }
                  else
                  {
                      qs["grant_type"] = "refresh_token";
                      qs["refresh_token"] = self.refreshToken();
                      if (self.requestedScope)
                      {
                          qs["scope"] = self.requestedScope;
                      }
                  }
  
                  // append into query string
                  var queryString = Gitana.Http.toQueryString(qs);
                  if (queryString)
                  {
                      if (o.url.indexOf("?") > -1)
                      {
                          o.url = o.url + "&" + queryString;
                      }
                      else
                      {
                          o.url = o.url + "?" + queryString;
                      }
                  }
  
                  self.invoke(o);
              };
  
              var doCall = function(autoAttemptRefresh)
              {
                  var successHandler = function(response)
                  {
                      options.success(response);
                  };
  
                  var failureHandler = function(http, xhr)
                  {
                      if (autoAttemptRefresh)
                      {
                          // there are a few good reasons why this might naturally fail
                          //
                          // 1.  our access token is invalid, has expired or has been forcefully invalidated on the Cloud CMS server
                          //     in this case, we get back a 200 and something like http.text =
                          //     {"error":"invalid_token","error_description":"Invalid access token: blahblahblah"}
                          //
                          // 2.  the access token no longer permits access to the resource
                          //     in this case, we get back a 401
                          //     it might not make much sense to re-request a new access token, but we do just in case.
  
                          var notJson = false;
                          var isInvalidToken = false;
                          if (http.text)
                          {
                              var responseData = {};
  
                              // catch if http.text is not JSON
                              try
                              {
                                  responseData = JSON.parse(http.text);
                              }
                              catch (e)
                              {
                                  console.log("Error response is not json");
                                  console.log(e);
                                  console.log(http.text);
                                  notJson = true;
                              }
  
                              if (responseData.error)
                              {
                                  if (responseData.error === "invalid_token")
                                  {
                                      isInvalidToken = true;
                                  }
                              }
                          }
                          var is401 = (http && http.code == 401) || (xhr && xhr.status == 401);
                          var is400 = (http && http.code == 400) || (xhr && xhr.status == 400);
                          var is403 = (http && http.code == 403) || (xhr && xhr.status == 403);
                          var isTimeout = (http && http.timeout) || (xhr && xhr.timeout);
  
                          // handle both cases
                          if (is401 || is400 || is403 || isInvalidToken || (notJson && !isTimeout))
                          {
                              if (self.refreshToken() || self.cookieMode)
                              {
                                  // use the refresh token to acquire a new access token
                                  doRefreshAccessToken(function() {
  
                                      // success, got a new access token
  
                                      doCall(false);
  
                                  }, function() {
  
                                      // failure, nothing else we can do
                                      // call into intended failure handler with the original failure http object
                                      options.failure(http, xhr);
                                  });
                              }
                              else
                              {
                                  // fail case - nothing we can do
                                  options.failure(http, xhr);
                              }
                          }
                          else
                          {
                              // some other kind of error
                              options.failure(http, xhr);
                          }
                      }
                      else
                      {
                          // we aren't allowed to automatically attempt to get a new token via refresh token
                          options.failure(http, xhr);
                      }
                  };
  
                  // call through to the protected resource (with custom success/failure handling)
                  var o = {};
                  Gitana.copyInto(o, options);
                  o.success = successHandler;
                  o.failure = failureHandler;
                  if (!o.headers)
                  {
                      o.headers = {};
                  }
                  if (Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
                  {
                      for (var k in Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
                      {
                          o.headers[k] = Gitana.OAUTH2_TOKEN_REQUEST_HEADERS[k];
                      }
                  }
                  if (!self.cookieMode && !self.ticketMode)
                  {
                      o.headers["Authorization"] = self.getBearerAuthorizationHeader();
                  }
                  if (self.ticketMode)
                  {
                      o.headers["GITANA_TICKET"] = encodeURIComponent(self.ticketMode);
                  }
                  o.url = self.getPrefixedURL(o.url);
  
                  // make the call
                  self.invoke(o);
              };
  
              // if we have an access token and it's about to expire (within 20 seconds of it's expiry),
              // we force an early refresh of the access token so that concurrent requests don't get access problems
              // this is important for any browser-originated requests that rely on a persisted cookie (GITANA_TICKET)
              //
              // also provide some debugging if needed
              var forceRefresh = false;
              if (self.accessToken())
              {
                  var grantTime = self.grantTime();
                  if (grantTime)
                  {
                      var expiresIn = self.expiresIn();
                      if (expiresIn)
                      {
                          // NOTE: expiresIn is in seconds
                          var expirationTimeMs = self.grantTime() + (self.expiresIn() * 1000);
                          var nowTimeMs = new Date().getTime();
  
                          var timeRemainingMs = expirationTimeMs - nowTimeMs;
                          if (timeRemainingMs <= 0)
                          {
                              // console.log("Access Token is expired, refresh will be attempted!");
                          }
                          else
                          {
                              // console.log("Access Token Time Remaining: " + timeRemainingMs);
                          }
  
                          if (timeRemainingMs <= 20000)
                          {
                              // console.log("Access Token only has 20 seconds left, forcing early refresh");
                              forceRefresh = true;
                          }
                      }
                  }
              }
  
              // if no access token, request one
              if ((!self.accessToken() || forceRefresh) && !this.cookieMode && !this.ticketMode)
              {
                  if (!self.refreshToken())
                  {
                      // no refresh token, do an authorization call
                      doGetAccessToken(function() {
  
                          // got an access token, so proceed
                          doCall(true);
  
                      }, function(http, xhr) {
  
                          // access denied
                          options.failure(http, xhr);
  
                      });
                  }
                  else
                  {
                      // we have a refresh token, so do a refresh call
                      doRefreshAccessToken(function() {
  
                          // got an access token, so proceed
                          doCall(true);
  
                      }, function(http, xhr) {
  
                          // unable to get an access token
                          options.failure(http, xhr);
  
                      });
                  }
              }
              else
              {
                  // we already have an access token
                  doCall(true);
              }
          },
  
          /**
           * Refreshes the OAuth2 access token.
           */
          refresh: function(callback)
          {
              var self = this;
  
              var currentAccessToken = self.accessToken();
              var currentRefreshToken = self.refreshToken();
              if (!currentRefreshToken)
              {
                  return callback({
                      "message": "The driver does not have a refresh token, cannot refresh"
                  });
              }
  
              var onSuccess = function(response)
              {
                  var object = JSON.parse(response.text);
                  if (object["error"])
                  {
                      self.error = object["error"];
                      self.errorDescription = object["error_description"];
                      self.errorUri = object["error_uri"];
  
                      return callback({
                          "error": self.error,
                          "message": self.errorDescription
                      });
                  }
                  else
                  {
                      var _accessToken = object["access_token"];
                      var _refreshToken = object["refresh_token"];
                      var _expiresIn = object["expires_in"];
                      //self.grantedScope = object["scope"]; // this doesn't come back on refresh, assumed the same
                      var _grantTime = new Date().getTime();
                      var _grantedScope = self.grantedScope();
  
                      // store into persistent storage
                      self.clearStorage();
                      self.accessToken(_accessToken);
                      self.refreshToken(_refreshToken);
                      self.expiresIn(_expiresIn);
                      self.grantedScope(_grantedScope);
                      self.grantTime(_grantTime);
  
                      callback();
                  }
              };
  
              var onFailure = function(http, xhr)
              {
                  if (Gitana.REFRESH_TOKEN_FAILURE_FN)
                  {
                      Gitana.REFRESH_TOKEN_FAILURE_FN(self, http, xhr);
                  }
  
                  // clear storage
                  self.clearStorage();
  
                  callback({
                      "message": "Unable to refresh access token"
                  });
              };
  
              var o = {
                  success: onSuccess,
                  failure: onFailure,
                  headers: {},
                  url: self.getPrefixedTokenURL(),
                  method: Gitana.OAuth2Http.TOKEN_METHOD
              };
  
              if (Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
              {
                  for (var k in Gitana.OAUTH2_TOKEN_REQUEST_HEADERS)
                  {
                      o.headers[k] = Gitana.OAUTH2_TOKEN_REQUEST_HEADERS[k];
                  }
              }
  
              o.headers["Authorization"] = self.getClientAuthorizationHeader();
  
              // query string
              var qs = {};
  
              // ticket max age
              if (self.ticketMaxAge)
              {
                  qs["ticketMaxAge"] = self.ticketMaxAge;
              }
  
              // if we're POSTing, do so as application/x-www-form-urlencoded to make secure over the wire
              if ("post" === Gitana.OAuth2Http.TOKEN_METHOD.toLowerCase())
              {
                  o.headers["Content-Type"] = "application/x-www-form-urlencoded";
  
                  // url encoded data
                  var urlEncodedTokens = {};
                  urlEncodedTokens["grant_type"] = "refresh_token";
                  urlEncodedTokens["refresh_token"] = self.refreshToken();
                  if (self.requestedScope)
                  {
                      urlEncodedTokens["scope"] = self.requestedScope;
                  }
                  o.data = "" + Gitana.Http.toQueryString(urlEncodedTokens);
              }
              else
              {
                  qs["grant_type"] = "refresh_token";
                  qs["refresh_token"] = self.refreshToken();
                  if (self.requestedScope)
                  {
                      qs["scope"] = self.requestedScope;
                  }
              }
  
              // append into query string
              var queryString = Gitana.Http.toQueryString(qs);
              if (queryString)
              {
                  if (o.url.indexOf("?") > -1)
                  {
                      o.url = o.url + "&" + queryString;
                  }
                  else
                  {
                      o.url = o.url + "?" + queryString;
                  }
              }
  
              self.invoke(o);
          }
  
      });
  
      /**
       * Provides a storage location for OAuth2 credentials
       *
       * @param scope
       *
       * @return storage instance
       * @constructor
       */
      Gitana.OAuth2Http.Storage = function(scope)
      {
          // in-memory implementation of HTML5 storage interface
          var memoryStorage = function() {
  
              var memory = {};
  
              var m = {};
              m.removeItem = function(key)
              {
                  delete memory[key];
              };
  
              m.getItem = function(key)
              {
                  return memory[key];
              };
  
              m.setItem = function(key, value)
              {
                  memory[key] = value;
              };
  
              return m;
          }();
  
          /**
           * Determines whether the current runtime environment supports HTML5 local storage
           *
           * @return {Boolean}
           */
          var supportsLocalStorage = function()
          {
              try {
                  return 'localStorage' in window && window['localStorage'] !== null;
              } catch (e) {
                  return false;
              }
          };
  
          /**
           * Determines whether the current runtime environment supports HTML5 session storage.
           *
           * @return {Boolean}
           */
          var supportsSessionStorage = function()
          {
              try {
                  return 'sessionStorage' in window && window['sessionStorage'] !== null;
              } catch (e) {
                  return false;
              }
          };
  
          var acquireStorage = function()
          {
              var storage = null;
  
              // store
              if (scope == "session" && supportsSessionStorage())
              {
                  storage = sessionStorage;
              }
              else if (scope == "local" && supportsLocalStorage())
              {
                  storage = localStorage;
              }
              else
              {
                  // fall back to memory-based storage
                  storage = memoryStorage;
              }
  
              return storage;
          };
  
          // result object
          var r = {};
  
          /**
           * Clears state.
           */
          r.clear = function()
          {
              // we first set to empty to account for a bug in Chrome
              // this bug is with the removeItem method where it sometimes doesn't work, so force to empty to handle worst case
              // https://bugs.chromium.org/p/chromium/issues/detail?id=765524
              acquireStorage().setItem("gitanaAuthState", "");
  
              // now do the actual remove
              acquireStorage().removeItem("gitanaAuthState");
          };
  
          /**
           * Pokes and peeks the value of a key in the state.
           *
           * @param key
           * @param value
           *
           * @return {*}
           */
          r.poke = function(key, value)
          {
              var state = {};
  
              var stateString = acquireStorage().getItem("gitanaAuthState");
              if (stateString && stateString !== "") {
                  state = JSON.parse(stateString);
              }
  
              var touch = false;
              if (typeof(value) !== "undefined" && value !== null)
              {
                  state[key] = value;
                  touch = true;
              }
              else if (value === null)
              {
                  delete state[key];
                  touch = true;
              }
  
              if (touch) {
                  acquireStorage().setItem("gitanaAuthState", JSON.stringify(state));
              }
  
              return state[key];
          };
  
          return r;
      };
  
  }(this));
  
  // statics
  Gitana.OAuth2Http.PASSWORD = "password";
  Gitana.OAuth2Http.AUTHORIZATION_CODE = "authorization_code";
  Gitana.OAuth2Http.TOKEN = "token";
  Gitana.OAuth2Http.COOKIE = "cookie";
  Gitana.OAuth2Http.TICKET = "ticket";
  
  // method to use for retrieving access and refresh tokens
  //Gitana.OAuth2Http.TOKEN_METHOD = "GET";
  Gitana.OAuth2Http.TOKEN_METHOD = "POST";
  
  
  
  