UNPKG

ares-ide

Version:

A browser-based code editor and UI designer for Enyo 2 projects

389 lines (351 loc) 15.4 kB
# Base class for drivers that run in the browser. # # Inheriting from this class makes a driver use HTML5 localStorage to preserve # OAuth tokens across page reloads. class Dropbox.Drivers.BrowserBase # Sets up the OAuth driver. # # Subclasses should pass the options object they receive to the superclass # constructor. # # @param {?Object} options the advanced settings below # @option options {Boolean} useQuery if true, the page will receive OAuth # data as query parameters; by default, the page receives OAuth data in # the fragment part of the URL (the string following the #, # available as document.location.hash), to avoid confusing the server # generating the page # @option options {Boolean} rememberUser if true, the user's OAuth tokens are # saved in localStorage; if you use this, you MUST provide a UI item that # calls signOut() on Dropbox.Client, to let the user "log out" of the # application # @option options {String} scope embedded in the localStorage key that holds # the authentication data; useful for having multiple OAuth tokens in a # single application constructor: (options) -> @rememberUser = options?.rememberUser or false @useQuery = options?.useQuery or false @scope = options?.scope or 'default' @storageKey = null @dbTokenRe = new RegExp "(#|\\?|&)dboauth_token=([^&#]+)(&|#|$)" @rejectedRe = new RegExp "(#|\\?|&)not_approved=true(&|#|$)" @tokenRe = new RegExp "(#|\\?|&)oauth_token=([^&#]+)(&|#|$)" # The magic happens here. onAuthStateChange: (client, callback) -> @setStorageKey client switch client.authState when DropboxClient.RESET @loadCredentials (credentials) => return callback() unless credentials if credentials.authState # Incomplete authentication. client.setCredentials credentials return callback() # There is an old access token. Only use it if the app supports # logout. unless @rememberUser return @forgetCredentials(callback) # Verify that the old access token still works. client.setCredentials credentials client.getUserInfo (error) => if error client.reset() @forgetCredentials callback else callback() when DropboxClient.REQUEST @storeCredentials client.credentials(), callback when DropboxClient.DONE if @rememberUser return @storeCredentials(client.credentials(), callback) @forgetCredentials callback when DropboxClient.SIGNED_OFF @forgetCredentials callback when DropboxClient.ERROR @forgetCredentials callback else callback() @ # Computes the @storageKey used by loadCredentials and forgetCredentials. # # @private # This is called by onAuthStateChange. # # @param {Dropbox.Client} client the client instance that is running the # authorization process # @return {Dropbox.Driver} this, for easy call chaining setStorageKey: (client) -> # NOTE: the storage key is dependent on the app hash so that multiple apps # hosted off the same server don't step on eachother's toes @storageKey = "dropbox-auth:#{@scope}:#{client.appHash()}" @ # Stores a Dropbox.Client's credentials to localStorage. # # @private # onAuthStateChange calls this method during the authentication flow. # # @param {Object} credentials the result of a Drobpox.Client#credentials call # @param {function()} callback called when the storing operation is complete # @return {Dropbox.Drivers.BrowserBase} this, for easy call chaining storeCredentials: (credentials, callback) -> localStorage.setItem @storageKey, JSON.stringify(credentials) callback() @ # Retrieves a token and secret from localStorage. # # @private # onAuthStateChange calls this method during the authentication flow. # # @param {function(?Object)} callback supplied with the credentials object # stored by a previous call to # Dropbox.Drivers.BrowserBase#storeCredentials; null if no credentials were # stored, or if the previously stored credentials were deleted # @return {Dropbox.Drivers.BrowserBase} this, for easy call chaining loadCredentials: (callback) -> jsonString = localStorage.getItem @storageKey unless jsonString callback null return @ try callback JSON.parse(jsonString) catch jsonError # Parse errors. callback null @ # Deletes information previously stored by a call to storeCredentials. # # @private # onAuthStateChange calls this method during the authentication flow. # # @param {function()} callback called after the credentials are deleted # @return {Dropbox.Drivers.BrowserBase} this, for easy call chaining forgetCredentials: (callback) -> localStorage.removeItem @storageKey callback() @ # Pre-computes the static parts of url()'s return value. # # @param {String} baseUrl # @return {[String, String]} the static prefix and suffix in url()'s return # value; the changing part is the URL-encoded OAuth token computeUrl: (baseUrl) -> querySuffix = "_dropboxjs_scope=#{encodeURIComponent(@scope)}&dboauth_token=" location = baseUrl if location.indexOf('#') is -1 fragment = null else locationPair = location.split '#', 2 location = locationPair[0] fragment = locationPair[1] if @useQuery if location.indexOf('?') is -1 location += "?#{querySuffix}" # No query string in the URL. else location += "&#{querySuffix}" # The URL already has a query string. if fragment [location, '#' + fragment] else [location, ''] else [location + '#?' + querySuffix, ''] # Figures out if the user completed the OAuth flow based on the current URL. # # @param {?String} the URL to check; if not given, the current location's URL # is checked # @return {?String} the OAuth token that the user just authorized, or null if # the user accessed this directly, without having authorized a token locationToken: (url) -> location = url or Dropbox.Drivers.BrowserBase.currentLocation() # Check for the scope. scopePattern = "_dropboxjs_scope=#{encodeURIComponent @scope}&" return null if location.indexOf?(scopePattern) is -1 # Check for not_approved=true if @rejectedRe.test(location) match = @dbTokenRe.exec location if match # TODO(pwnall): indicate that the user did not approve the app. return decodeURIComponent(match[2]) else return null # Extract the oauth_token. match = @tokenRe.exec location return decodeURIComponent(match[2]) if match null # Wrapper for window.location, for testing purposes. # # @return {String} the current page's URL @currentLocation: -> window.location.href # OAuth driver that uses a redirect and localStorage to complete the flow. class Dropbox.Drivers.Redirect extends Dropbox.Drivers.BrowserBase # Sets up the redirect-based OAuth driver. # # @param {?Object} options the advanced settings below # @option options {Boolean} useQuery if true, the page will receive OAuth # data as query parameters; by default, the page receives OAuth data in # the fragment part of the URL (the string following the #, # available as document.location.hash), to avoid confusing the server # generating the page # @option options {Boolean} rememberUser if true, the user's OAuth tokens are # saved in localStorage; if you use this, you MUST provide a UI item that # calls signOut() on Dropbox.Client, to let the user "log out" of the # application # @option options {String} scope embedded in the localStorage key that holds # the authentication data; useful for having multiple OAuth tokens in a # single application constructor: (options) -> super options [@receiverUrl1, @receiverUrl2] = @computeUrl Dropbox.Drivers.BrowserBase.currentLocation() # Forwards the authentication process from REQUEST to AUTHORIZED on redirect. onAuthStateChange: (client, callback) -> superCall = do => => super client, callback @setStorageKey client if client.authState is DropboxClient.RESET @loadCredentials (credentials) => if credentials and credentials.authState # Incomplete authentication. if credentials.token is @locationToken() and credentials.authState is DropboxClient.REQUEST # locationToken matched, so the redirect happened credentials.authState = DropboxClient.AUTHORIZED return @storeCredentials credentials, superCall else # The authentication process broke down, start over. return @forgetCredentials superCall superCall() else superCall() # URL of the current page, since the user will be sent right back. url: (token) -> @receiverUrl1 + encodeURIComponent(token) + @receiverUrl2 # Redirects to the authorize page. doAuthorize: (authUrl) -> window.location.assign authUrl # OAuth driver that uses a popup window and postMessage to complete the flow. class Dropbox.Drivers.Popup extends Dropbox.Drivers.BrowserBase # Sets up a popup-based OAuth driver. # # @param {?Object} options one of the settings below; leave out the argument # to use the current location for redirecting # @option options {Boolean} rememberUser if true, the user's OAuth tokens are # saved in localStorage; if you use this, you MUST provide a UI item that # calls signOut() on Dropbox.Client, to let the user "log out" of the # application # @option options {String} scope embedded in the localStorage key that holds # the authentication data; useful for having multiple OAuth tokens in a # single application # @option options {String} receiverUrl URL to the page that receives the # /authorize redirect and performs the postMessage # @option options {Boolean} useQuery if true, the receiverUrl will be # modified by adding query arguments; by default, a hash "#" is appended to # URLs that don't have one, so the OAuth token information is contained in # the URL fragment and does not hit the file server # @option options {String} receiverFile the URL to the receiver page will be # computed by replacing the file name (everything after the last /) of # the current location with this parameter's value constructor: (options) -> super options [@receiverUrl1, @receiverUrl2] = @computeUrl @baseUrl(options) # Removes credentials stuck in the REQUEST stage. onAuthStateChange: (client, callback) -> superCall = do => => super client, callback @setStorageKey client if client.authState is DropboxClient.RESET @loadCredentials (credentials) => if credentials and credentials.authState # Incomplete authentication. # The authentication process broke down, start over. return @forgetCredentials superCall superCall() else superCall() # Shows the authorization URL in a pop-up, waits for it to send a message. doAuthorize: (authUrl, token, tokenSecret, callback) -> @listenForMessage token, callback @openWindow authUrl # URL of the redirect receiver page, which posts a message back to this page. url: (token) -> @receiverUrl1 + encodeURIComponent(token) + @receiverUrl2 # The URL of the page that will receive the OAuth callback. # # @param {Object} options the options passed to the constructor # @option options {String} receiverUrl URL to the page that receives the # /authorize redirect and performs the postMessage # @option options {String} receiverFile the URL to the receiver page will be # computed by replacing the file name (everything after the last /) of # the current location with this parameter's value # @return {String} absolute URL of the receiver page baseUrl: (options) -> if options if options.receiverUrl return options.receiverUrl else if options.receiverFile fragments = Dropbox.Drivers.BrowserBase.currentLocation().split '/' fragments[fragments.length - 1] = options.receiverFile return fragments.join('/') Dropbox.Drivers.BrowserBase.currentLocation() # Creates a popup window. # # @param {String} url the URL that will be loaded in the popup window # @return {?DOMRef} reference to the opened window, or null if the call # failed openWindow: (url) -> window.open url, '_dropboxOauthSigninWindow', @popupWindowSpec(980, 700) # Spec string for window.open to create a nice popup. # # @param {Number} popupWidth the desired width of the popup window # @param {Number} popupHeight the desired height of the popup window # @return {String} spec string for the popup window popupWindowSpec: (popupWidth, popupHeight) -> # Metrics for the current browser window. x0 = window.screenX ? window.screenLeft y0 = window.screenY ? window.screenTop width = window.outerWidth ? document.documentElement.clientWidth height = window.outerHeight ? document.documentElement.clientHeight # Computed popup window metrics. popupLeft = Math.round x0 + (width - popupWidth) / 2 popupTop = Math.round y0 + (height - popupHeight) / 2.5 popupLeft = x0 if popupLeft < x0 popupTop = y0 if popupTop < y0 # The specification string. "width=#{popupWidth},height=#{popupHeight}," + "left=#{popupLeft},top=#{popupTop}" + 'dialog=yes,dependent=yes,scrollbars=yes,location=yes' # Listens for a postMessage from a previously opened popup window. # # @param {String} token the token string that must be received from the popup # window # @param {function()} called when the received message matches the token listenForMessage: (token, callback) -> listener = (event) => if event.data # Message coming from postMessage. data = event.data else # Message coming from Dropbox.EventSource. data = event if @locationToken(data) is token token = null # Avoid having this matched in the future. window.removeEventListener 'message', listener Dropbox.Drivers.Popup.onMessage.removeListener listener callback() window.addEventListener 'message', listener, false Dropbox.Drivers.Popup.onMessage.addListener listener # Communicates with the driver from the OAuth receiver page. @oauthReceiver: -> window.addEventListener 'load', -> opener = window.opener if window.parent isnt window.top opener or= window.parent if opener try opener.postMessage window.location.href, '*' catch ieError # IE 9 doesn't support opener.postMessage for popup windows. try # postMessage doesn't work in IE, but direct object access does. opener.Dropbox.Drivers.Popup.onMessage.dispatch( window.location.href) catch frameError # Hopefully postMessage worked. window.close() # Works around postMessage failures on Internet Explorer. @onMessage = new Dropbox.EventSource