diff --git a/ChangeLog b/ChangeLog index 5fb1db6c..124eca45 100644 --- a/ChangeLog +++ b/ChangeLog @@ -73,6 +73,8 @@ keyStop: function(e, opt) { * Fixed bug with setting curent cursor when clicked on menu item. +* Updated dropbox library to v.0.8.1. + 2012.12.12, Version 0.1.8 diff --git a/lib/client/storage/_dropbox.js b/lib/client/storage/_dropbox.js index 899b4b2e..9d3ba79d 100644 --- a/lib/client/storage/_dropbox.js +++ b/lib/client/storage/_dropbox.js @@ -17,8 +17,7 @@ var CloudCommander, Util, DOM, Dropbox, cb, Client; function load(pCallBack){ console.time('dropbox load'); - var lSrc = '//cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.7.1/dropbox.min.js', - //var lSrc = CloudCmd.LIBDIRCLIENT + 'storage/dropbox/lib/dropbox.js', + var lSrc = 'http://cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.8.1/dropbox.min.js', lLocal = CloudCmd.LIBDIRCLIENT + 'storage/dropbox/lib/dropbox.min.js', lOnload = function(){ console.timeEnd('dropbox load'); diff --git a/lib/client/storage/dropbox/.gitignore b/lib/client/storage/dropbox/.gitignore new file mode 100644 index 00000000..49398f0b --- /dev/null +++ b/lib/client/storage/dropbox/.gitignore @@ -0,0 +1,37 @@ +# Vim. +*.swp + +# OSX +.DS_Store + +# Npm modules. +node_modules + +# Vendored javascript modules. +test/vendor + +# Build output. +lib/dropbox.js +lib/dropbox.min.js +test/chrome_app/lib +test/chrome_app/manifest.json +test/chrome_app/node_modules +test/chrome_app/test +test/chrome_extension/*.js +test/chrome_profile +test/js +tmp/*.js + +# Documentation output. +doc/*.html +doc/assets +doc/classes +doc/files + +# Node packaging output. +dropbox-*.tgz + +# Potentially sensitive credentials and keys used during testing. +test/.token +test/ssl + diff --git a/lib/client/storage/dropbox/.npmignore b/lib/client/storage/dropbox/.npmignore new file mode 100644 index 00000000..86414dae --- /dev/null +++ b/lib/client/storage/dropbox/.npmignore @@ -0,0 +1,45 @@ +# Vim. +*.swp +.vimrc + +# OSX +.DS_Store + +# Git. +.git + +# Npm modules. +node_modules + +# Vendored javascript modules. +test/vendor + +# Minified library. +lib/dropbox.min.js + +# Sample apps. +samples + +# Test build output. +test/chrome_profile +test/js +tmp/*.js + +# Test code that is not related to node.js. +test/chrome_app + +# Documentation output. +doc/*.html +doc/assets +doc/classes +doc/files + +# Test app logos. +test/app_icon +# Test automating Chrome extension. +test/chrome_extension + +# Potentially sensitive credentials and keys used during testing. +test/.token +test/ssl + diff --git a/lib/client/storage/dropbox/txt.vimrc.txt b/lib/client/storage/dropbox/.vimrc similarity index 100% rename from lib/client/storage/dropbox/txt.vimrc.txt rename to lib/client/storage/dropbox/.vimrc diff --git a/lib/client/storage/dropbox/Cakefile b/lib/client/storage/dropbox/Cakefile index 4a530770..17fd7a55 100644 --- a/lib/client/storage/dropbox/Cakefile +++ b/lib/client/storage/dropbox/Cakefile @@ -1,131 +1,215 @@ -async = require 'async' -{spawn, exec} = require 'child_process' -fs = require 'fs' -log = console.log -remove = require 'remove' - -# Node 0.6 compatibility hack. -unless fs.existsSync - path = require 'path' - fs.existsSync = (filePath) -> path.existsSync filePath - - -task 'build', -> - build() - -task 'test', -> - vendor -> - build -> - ssl_cert -> - tokens -> - run 'node_modules/mocha/bin/mocha --colors --slow 200 ' + - '--timeout 10000 --require test/js/helper.js test/js/*test.js' - -task 'webtest', -> - vendor -> - build -> - ssl_cert -> - tokens -> - webFileServer = require './test/js/web_file_server.js' - webFileServer.openBrowser() - -task 'cert', -> - remove.removeSync 'test/ssl', ignoreMissing: true - ssl_cert() - -task 'vendor', -> - remove.removeSync './test/vendor', ignoreMissing: true - vendor() - -task 'tokens', -> - remove.removeSync './test/.token', ignoreMissing: true - build -> - tokens -> - process.exit 0 - -task 'doc', -> - run 'node_modules/codo/bin/codo src' - -task 'extension', -> - run 'node_modules/coffee-script/bin/coffee ' + - '--compile test/chrome_extension/*.coffee' - -build = (callback) -> - commands = [] - # Compile without --join for decent error messages. - commands.push 'node_modules/coffee-script/bin/coffee --output tmp ' + - '--compile src/*.coffee' - commands.push 'node_modules/coffee-script/bin/coffee --output lib ' + - '--compile --join dropbox.js src/*.coffee' - # Minify the javascript, for browser distribution. - commands.push 'node_modules/uglify-js/bin/uglifyjs --compress --mangle ' + - '--output lib/dropbox.min.js lib/dropbox.js' - commands.push 'node_modules/coffee-script/bin/coffee --output test/js ' + - '--compile test/src/*.coffee' - async.forEachSeries commands, run, -> - callback() if callback - -ssl_cert = (callback) -> - fs.mkdirSync 'test/ssl' unless fs.existsSync 'test/ssl' - if fs.existsSync 'test/ssl/cert.pem' - callback() if callback? - return - - run 'openssl req -new -x509 -days 365 -nodes -batch ' + - '-out test/ssl/cert.pem -keyout test/ssl/cert.pem ' + - '-subj /O=dropbox.js/OU=Testing/CN=localhost ', callback - -vendor = (callback) -> - # All the files will be dumped here. - fs.mkdirSync 'test/vendor' unless fs.existsSync 'test/vendor' - - # Embed the binary test image into a 7-bit ASCII JavaScript. - bytes = fs.readFileSync 'test/binary/dropbox.png' - fragments = [] - for i in [0...bytes.length] - fragment = bytes.readUInt8(i).toString 16 - while fragment.length < 4 - fragment = '0' + fragment - fragments.push "\\u#{fragment}" - js = "window.testImageBytes = \"#{fragments.join('')}\";" - fs.writeFileSync 'test/vendor/favicon.js', js - - downloads = [ - # chai.js ships different builds for browsers vs node.js - ['http://chaijs.com/chai.js', 'test/vendor/chai.js'], - # sinon.js also ships special builds for browsers - ['http://sinonjs.org/releases/sinon.js', 'test/vendor/sinon.js'], - # ... and sinon.js ships an IE-only module - ['http://sinonjs.org/releases/sinon-ie.js', 'test/vendor/sinon-ie.js'] - ] - async.forEachSeries downloads, download, -> - callback() if callback - -tokens = (callback) -> - TokenStash = require './test/js/token_stash.js' - tokenStash = new TokenStash - (new TokenStash()).get -> - callback() if callback? - -run = (args...) -> - for a in args - switch typeof a - when 'string' then command = a - when 'object' - if a instanceof Array then params = a - else options = a - when 'function' then callback = a - - command += ' ' + params.join ' ' if params? - cmd = spawn '/bin/sh', ['-c', command], options - cmd.stdout.on 'data', (data) -> process.stdout.write data - cmd.stderr.on 'data', (data) -> process.stderr.write data - process.on 'SIGHUP', -> cmd.kill() - cmd.on 'exit', (code) -> callback() if callback? and code is 0 - -download = ([url, file], callback) -> - if fs.existsSync file - callback() if callback? - return - - run "curl -o #{file} #{url}", callback \ No newline at end of file +async = require 'async' +{spawn, exec} = require 'child_process' +fs = require 'fs' +glob = require 'glob' +log = console.log +path = require 'path' +remove = require 'remove' + +# Node 0.6 compatibility hack. +unless fs.existsSync + fs.existsSync = (filePath) -> path.existsSync filePath + + +task 'build', -> + build() + +task 'test', -> + vendor -> + build -> + ssl_cert -> + tokens -> + run 'node_modules/mocha/bin/mocha --colors --slow 200 ' + + '--timeout 10000 --require test/js/helper.js test/js/*test.js' + +task 'webtest', -> + vendor -> + build -> + ssl_cert -> + tokens -> + webtest() + +task 'cert', -> + remove.removeSync 'test/ssl', ignoreMissing: true + ssl_cert() + +task 'vendor', -> + remove.removeSync './test/vendor', ignoreMissing: true + vendor() + +task 'tokens', -> + remove.removeSync './test/.token', ignoreMissing: true + build -> + tokens -> + process.exit 0 + +task 'doc', -> + run 'node_modules/codo/bin/codo src' + +task 'extension', -> + run 'node_modules/coffee-script/bin/coffee ' + + '--compile test/chrome_extension/*.coffee' + +task 'chrome', -> + vendor -> + build -> + # The v2 Chrome App API isn't supported yet. + buildChromeApp 'app_v1' + +task 'chrometest', -> + vendor -> + build -> + # The v2 Chrome App API isn't supported yet. + buildChromeApp 'app_v1', -> + testChromeApp() + +build = (callback) -> + commands = [] + + # Ignoring ".coffee" when sorting. + # We want "driver.coffee" to sort before "driver-browser.coffee" + source_files = glob.sync 'src/*.coffee' + source_files.sort (a, b) -> + a.replace(/\.coffee$/, '').localeCompare b.replace(/\.coffee$/, '') + + # Compile without --join for decent error messages. + commands.push 'node_modules/coffee-script/bin/coffee --output tmp ' + + "--compile #{source_files.join(' ')}" + commands.push 'node_modules/coffee-script/bin/coffee --output lib ' + + "--compile --join dropbox.js #{source_files.join(' ')}" + # Minify the javascript, for browser distribution. + commands.push 'node_modules/uglify-js/bin/uglifyjs --compress --mangle ' + + '--output lib/dropbox.min.js lib/dropbox.js' + commands.push 'node_modules/coffee-script/bin/coffee --output test/js ' + + '--compile test/src/*.coffee' + async.forEachSeries commands, run, -> + callback() if callback + +webtest = (callback) -> + webFileServer = require './test/js/web_file_server.js' + if 'BROWSER' of process.env + if process.env['BROWSER'] is 'false' + url = webFileServer.testUrl() + console.log "Please open the URL below in your browser:\n #{url}" + else + webFileServer.openBrowser process.env['BROWSER'] + else + webFileServer.openBrowser() + callback() if callback? + +ssl_cert = (callback) -> + fs.mkdirSync 'test/ssl' unless fs.existsSync 'test/ssl' + if fs.existsSync 'test/ssl/cert.pem' + callback() if callback? + return + + run 'openssl req -new -x509 -days 365 -nodes -batch ' + + '-out test/ssl/cert.pem -keyout test/ssl/cert.pem ' + + '-subj /O=dropbox.js/OU=Testing/CN=localhost ', callback + +vendor = (callback) -> + # All the files will be dumped here. + fs.mkdirSync 'test/vendor' unless fs.existsSync 'test/vendor' + + # Embed the binary test image into a 7-bit ASCII JavaScript. + bytes = fs.readFileSync 'test/binary/dropbox.png' + fragments = [] + for i in [0...bytes.length] + fragment = bytes.readUInt8(i).toString 16 + while fragment.length < 4 + fragment = '0' + fragment + fragments.push "\\u#{fragment}" + js = "window.testImageBytes = \"#{fragments.join('')}\";" + fs.writeFileSync 'test/vendor/favicon.js', js + + downloads = [ + # chai.js ships different builds for browsers vs node.js + ['http://chaijs.com/chai.js', 'test/vendor/chai.js'], + # sinon.js also ships special builds for browsers + ['http://sinonjs.org/releases/sinon.js', 'test/vendor/sinon.js'], + # ... and sinon.js ships an IE-only module + ['http://sinonjs.org/releases/sinon-ie.js', 'test/vendor/sinon-ie.js'] + ] + async.forEachSeries downloads, download, -> + callback() if callback + +testChromeApp = (callback) -> + # Clean up the profile. + fs.mkdirSync 'test/chrome_profile' unless fs.existsSync 'test/chrome_profile' + + command = "\"#{chromeCommand()}\" --load-extension=test/chrome_app " + + '--user-data-dir=test/chrome_profile --no-default-browser-check ' + + '--no-first-run --no-service-autorun --disable-default-apps ' + + '--homepage=about:blank --v=-1' + + run command, -> + callback() if callback + +buildChromeApp = (manifestFile, callback) -> + unless fs.existsSync 'test/chrome_app/test' + fs.mkdirSync 'test/chrome_app/test' + unless fs.existsSync 'test/chrome_app/node_modules' + fs.mkdirSync 'test/chrome_app/node_modules' + + links = [ + ['lib', 'test/chrome_app/lib'], + ['node_modules/mocha', 'test/chrome_app/node_modules/mocha'], + ['node_modules/sinon-chai', 'test/chrome_app/node_modules/sinon-chai'], + ['test/.token', 'test/chrome_app/test/.token'], + ['test/binary', 'test/chrome_app/test/binary'], + ['test/html', 'test/chrome_app/test/html'], + ['test/js', 'test/chrome_app/test/js'], + ['test/vendor', 'test/chrome_app/test/vendor'], + ] + commands = [ + "cp test/chrome_app/manifests/#{manifestFile}.json " + + 'test/chrome_app/manifest.json' + ] + for link in links + # fs.symlinkSync(path.resolve(link[0]), link[1]) unless fs.existsSync link[1] + commands.push "cp -r #{link[0]} #{path.dirname(link[1])}" + async.forEachSeries commands, run, -> + callback() if callback + +chromeCommand = -> + paths = [ + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary', + '/Applications/Chromium.app/MacOS/Contents/Chromium', + ] + for path in paths + return path if fs.existsSync path + + if 'process.platform' is 'win32' + 'chrome' + else + 'google-chrome' + +tokens = (callback) -> + TokenStash = require './test/js/token_stash.js' + tokenStash = new TokenStash + (new TokenStash()).get -> + callback() if callback? + +run = (args...) -> + for a in args + switch typeof a + when 'string' then command = a + when 'object' + if a instanceof Array then params = a + else options = a + when 'function' then callback = a + + command += ' ' + params.join ' ' if params? + cmd = spawn '/bin/sh', ['-c', command], options + cmd.stdout.on 'data', (data) -> process.stdout.write data + cmd.stderr.on 'data', (data) -> process.stderr.write data + process.on 'SIGHUP', -> cmd.kill() + cmd.on 'exit', (code) -> callback() if callback? and code is 0 + +download = ([url, file], callback) -> + if fs.existsSync file + callback() if callback? + return + + run "curl -o #{file} #{url}", callback diff --git a/lib/client/storage/dropbox/doc/auth_drivers.md b/lib/client/storage/dropbox/doc/auth_drivers.md index 5507f058..14971649 100644 --- a/lib/client/storage/dropbox/doc/auth_drivers.md +++ b/lib/client/storage/dropbox/doc/auth_drivers.md @@ -100,7 +100,8 @@ their Dropbox. This behavior is unintuitive to users. A reasonable compromise for apps that use `rememberUser` is to provide a `Sign out` button that calls the `signOut` method on the app's `Dropbox.Client` instance. -The +The driver's constructor takes a `receiverPath` option t + [checkbox.js](https://github.com/dropbox/dropbox-js/tree/master/samples/checkbox.js) sample application uses `rememberUser`, and implements signing off as described above. @@ -123,10 +124,12 @@ Chrome, Firefox and IE10+. If the drawbacks above are more acceptable than restructuring your application to handle redirects, create a page on your site that contains the [receiver code](https://github.com/dropbox/dropbox-js/blob/master/test/html/oauth_receiver.html), -and point the `Dropbox.Drivers.Popup` constructor to it. +change the code to reflect the location of `dropbox.js` on your site, and point +the `Dropbox.Drivers.Popup` constructor to it. ```javascript -client.authDriver(new Dropbox.Drivers.Popup({receiverUrl: "https://url.to/receiver.html"})); +client.authDriver(new Dropbox.Drivers.Popup({ + receiverUrl: "https://url.to/oauth_receiver.html"})); ``` The popup driver adds a `#` (fragment hash) to the receiver URL if necessary, @@ -138,9 +141,42 @@ If you have a good reason to disable the behavior above, set the `noFragment` option to true. ```javascript -client.authDriver(new Dropbox.Drivers.Popup({receiverUrl: "https://url.to/receiver.html", noFragment: true})); +client.authDriver(new Dropbox.Drivers.Popup({ + receiverUrl: "https://url.to/receiver.html", noFragment: true})); ``` +The popup driver implements the `rememberUser` option with the same semantics +and caveats as the redirecting driver. + + +### Dropbox.Drivers.Chrome + +Google Chrome [extensions](http://developer.chrome.com/extensions/) and +[applications](developer.chrome.com/apps/) are supported by a driver that opens +a new browser tab (in the case of extensions and legacy applications) or +an application window (for new applications) to complete the OAuth +authorization. + +To use this driver, first add the following files to your extension. + +* the [receiver script](https://github.com/dropbox/dropbox-js/blob/master/test/src/chrome_oauth_receiver.coffee); +the file is both valid JavaScript and valid CoffeeScript +* the [receiver page](https://github.com/dropbox/dropbox-js/blob/master/test/html/chrome_oauth_receiver.html); +change the page to reflect the paths to `dropbox.js` and to the receiver script +file + +Point the driver constructor to the receiver page: + +```javascript +client.authDriver(new Dropbox.Drivers.Chrome({ + receiverPath: "path/to/chrome_oauth_receiver.html"})); +``` + +This driver caches the user's credentials so that users don't have to authorize +applications / extensions on every browser launch. Applications and extensions' +UI should include a method for the user to sign out of Dropbox, which can be +implemented by calling the `signOut` instance method of `Dropbox.Client`. + ### Dropbox.Drivers.NodeServer diff --git a/lib/client/storage/dropbox/doc/development.md b/lib/client/storage/dropbox/doc/development.md index 1429d64b..7600a196 100644 --- a/lib/client/storage/dropbox/doc/development.md +++ b/lib/client/storage/dropbox/doc/development.md @@ -42,6 +42,13 @@ version, optimized for browser apps. ## Test +Install the CoffeeScript npm package globally, so you can type `cake` instead +of `node_modules/coffee-script/bin/cake`. + +```bash +npm install -g coffee-script +``` + First, you will need to obtain a couple of Dropbox tokens that will be used by the automated tests. @@ -51,12 +58,13 @@ cake tokens Re-run the command above if the tests fail due to authentication errors. -Once you have Dropbox tokens, you can run the test suite in node.js or in your -default browser. +Once you have Dropbox tokens, you can run the test suite in node.js, in your +default browser, or as a Chrome application. ```bash cake test cake webtest +cake chrometest ``` The library is automatically re-built when running tests, so you don't need to @@ -68,11 +76,46 @@ The tests store all their data in folders named along the lines of folders yourself. -## Testing Chrome Extension +### Solving Browser Issues -The test suite opens up a couple of Dropbox authorization pages, and a page -that cannot close itself. dropbox.js ships with a Google Chrome extension that -can fully automate the testing process on Chrome. +An easy method to test a browser in a virtual machine is to skip the automated +browser opening. + +```bash +BROWSER=false cake webtest +``` + +A similar method can be used to launch a specific browser. + +```bash +BROWSER=firefox cake webtest +``` + +When fighting a bug, it can be useful to keep the server process running after +the test suite completes, so tests can be re-started with a browser refresh. + +```bash +BROWSER=false NO_EXIT=1 cake webtest +``` + +[Mocha's exclusive tests](http://visionmedia.github.com/mocha/#exclusive-tests) +(`it.only` and `describe.only`) are very useful for quickly iterating while +figuring out a bug. + + +### Chrome Application / Extension Testing + +The tests for Chrome apps / extensions require manual intervention right now. + +The `cake chrometest` command will open a Google Chrome instance. The +`dropbox.js Test Suite` application must be clicked. + + +### Fully Automated Tests + +The test suite opens up the Dropbox authorization page a few times, and also +pops up a page that cannot close itself. dropbox.js ships with a Google Chrome +extension that can fully automate the testing process on Chrome / Chromium. The extension is written in CoffeeScript, so you will have to compile it. @@ -84,6 +127,69 @@ After compilation, have Chrome load the unpacked extension at `test/chrome_extension` and click on the scary-looking toolbar icon to activate the extension. The icon's color should turn red, to indicate that it is active. -The extension performs some checks to prevent against attacks. However, for -best results, you should disable the automation (by clicking on the extension -icon) when you're not testing dropbox.js. +The extension performs some checks to prevent against attacks. However, you +should still disable the automation (by clicking on the extension icon) when +you're not testing dropbox.js, just in case the extension code has bugs. + + +## Release Process + +1. At the very least, test in node.js and in a browser before releasing. + +```bash +cake test +cake webtest +``` + +1. Bump the version in `package.json`. + +1. Publish a new npm package. + +```bash +npm publish +``` + +1. Commit and tag the version bump on GitHub. + +```bash +git add package.json +git commit -m "Release X.Y.Z." +git tag -a -m "Release X.Y.Z" vX.Y.Z +git push +git push --tags +``` + +1. If you haven't already, go to the + [cdnjs GitHub page](https://github.com/cdnjs/cdnjs) and fork it. + +1. If you haven't already, set up cdnjs on your machine. + +```bash +cd .. +git clone git@github.com:you/cdnjs.git +cd cdnjs +git remote add up git://github.com/cdnjs/cdnjs.git +cd ../dropbox-js +``` + +1. Add the new release to your cdnjs fork. + +```bash +cd ../cdnjs +git checkout master +git pull up master +npm install +git checkout -b dbXYZ +mkdir ajax/libs/dropbox.js/X.Y.Z +cp ../dropbox-js/lib/dropbox.min.js ajax/libs/dropbox.js/X.Y.Z/ +vim ajax/libs/dropbox.js/package.json # Replace "version"'s value with "X.Y.Z" +npm test +git add -A +git commit -m "Added dropbox.js X.Y.Z" +git push origin dbXYZ +``` + +1. Go to your cdnjs for on GitHub and open a pull request. Use these examples +of accepted +[major release pull request](https://github.com/cdnjs/cdnjs/pull/735) and +[minor release pull request](https://github.com/cdnjs/cdnjs/pull/753). diff --git a/lib/client/storage/dropbox/doc/getting_started.md b/lib/client/storage/dropbox/doc/getting_started.md index 8fdea598..b58c0caa 100644 --- a/lib/client/storage/dropbox/doc/getting_started.md +++ b/lib/client/storage/dropbox/doc/getting_started.md @@ -12,7 +12,7 @@ This section describes how to get the library hooked up into your application. To get started right away, place this snippet in your page's ``. ```html - ``` @@ -152,24 +152,20 @@ client.authenticate(function(error, client) { ## Error Handlng -When Dropbox API calls fail, dropbox.js methods pass a `Dropbox.Error` instance -as the first parameter in their callbacks. This parameter is named `error` in -all the code snippets on this page. +When Dropbox API calls fail, dropbox.js methods pass a `Dropbox.ApiError` +instance as the first parameter in their callbacks. This parameter is named +`error` in all the code snippets on this page. If `error` is a truthy value, you should either recover from the error, or notify the user that an error occurred. The `status` field in the -`Dropbox.Error` instance contains the HTTP error code, which should be one of -the +`Dropbox.ApiError` instance contains the HTTP error code, which should be one +of the [error codes in the REST API](https://www.dropbox.com/developers/reference/api#error-handling). The snippet below is a template for an extensive error handler. ```javascript var showError = function(error) { - if (window.console) { // Skip the "if" in node.js code. - console.error(error); - } - switch (error.status) { case 401: // If you're using dropbox.js, the only cause behind this error is that @@ -202,6 +198,18 @@ var showError = function(error) { }; ``` +`Dropbox.Client` also supports a DOM event-like API for receiving all errors. +This can be used to log API errors, or to upload them to your server for +further analysis. + +```javascript +client.onError.addListener(function(error) { + if (window.console) { // Skip the "if" in node.js code. + console.error(error); + } +}); +``` + ## The Fun Part diff --git a/lib/client/storage/dropbox/lib/dropbox.js b/lib/client/storage/dropbox/lib/dropbox.js index baa1119c..05a073aa 100644 --- a/lib/client/storage/dropbox/lib/dropbox.js +++ b/lib/client/storage/dropbox/lib/dropbox.js @@ -1,8 +1,9 @@ // Generated by CoffeeScript 1.4.0 (function() { - var Dropbox, DropboxClient, DropboxXhrArrayBufferView, DropboxXhrCanSendForms, DropboxXhrDoesPreflight, DropboxXhrIeMode, DropboxXhrRequest, add32, arrayToBase64, atob, atobNibble, base64Digits, base64HmacSha1, base64Sha1, btoa, btoaNibble, crypto, dropboxEncodeKey, hmacSha1, rotateLeft32, sha1, stringToArray, _base64Digits, + var Dropbox, DropboxChromeOnMessage, DropboxChromeSendMessage, DropboxClient, DropboxXhrArrayBufferView, DropboxXhrCanSendForms, DropboxXhrDoesPreflight, DropboxXhrIeMode, DropboxXhrRequest, DropboxXhrSendArrayBufferView, add32, arrayToBase64, atob, atobNibble, base64Digits, base64HmacSha1, base64Sha1, btoa, btoaNibble, crypto, dropboxEncodeKey, hmacSha1, rotateLeft32, sha1, stringToArray, _base64Digits, __hasProp = {}.hasOwnProperty, - __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Dropbox = (function() { @@ -32,9 +33,21 @@ this.url = url; this.status = xhr.status; if (xhr.responseType) { - text = xhr.response || xhr.responseText; + try { + text = xhr.response || xhr.responseText; + } catch (e) { + try { + text = xhr.responseText; + } catch (e) { + text = null; + } + } } else { - text = xhr.responseText; + try { + text = xhr.responseText; + } catch (e) { + text = null; + } } if (text) { try { @@ -174,6 +187,11 @@ this.authServer = options.authServer || this.defaultAuthServer(); this.fileServer = options.fileServer || this.defaultFileServer(); this.downloadServer = options.downloadServer || this.defaultDownloadServer(); + this.onXhr = new Dropbox.EventSource({ + cancelable: true + }); + this.onError = new Dropbox.EventSource; + this.onAuthStateChange = new Dropbox.EventSource; this.oauth = new Dropbox.Oauth(options); this.driver = null; this.filter = null; @@ -190,10 +208,11 @@ return this; }; - Client.prototype.xhrFilter = function(filter) { - this.filter = filter; - return this; - }; + Client.prototype.onXhr = null; + + Client.prototype.onError = null; + + Client.prototype.onAuthStateChange = null; Client.prototype.dropboxUid = function() { return this.uid; @@ -210,11 +229,17 @@ var oldAuthState, _fsmStep, _this = this; oldAuthState = null; + if (!(this.driver || this.authState === DropboxClient.DONE)) { + throw new Error("Call authDriver to set an authentication driver"); + } _fsmStep = function() { var authUrl; if (oldAuthState !== _this.authState) { + if (oldAuthState !== null) { + _this.onAuthStateChange.dispatch(_this); + } oldAuthState = _this.authState; - if (_this.driver.onAuthStateChange) { + if (_this.driver && _this.driver.onAuthStateChange) { return _this.driver.onAuthStateChange(_this, _fsmStep); } } @@ -256,7 +281,8 @@ }); case DropboxClient.DONE: return callback(null, _this); - case Dropbox.SIGNED_OFF: + case DropboxClient.SIGNED_OFF: + _this.authState = DropboxClient.RESET; _this.reset(); return _fsmStep(); case DropboxClient.ERROR: @@ -267,6 +293,10 @@ return this; }; + Client.prototype.isAuthenticated = function() { + return this.authState === DropboxClient.DONE; + }; + Client.prototype.signOut = function(callback) { var xhr, _this = this; @@ -276,8 +306,10 @@ if (error) { return callback(error); } + _this.authState = DropboxClient.RESET; _this.reset(); _this.authState = DropboxClient.SIGNED_OFF; + _this.onAuthStateChange.dispatch(_this); if (_this.driver.onAuthStateChange) { return _this.driver.onAuthStateChange(_this, function() { return callback(error); @@ -292,17 +324,25 @@ return this.signOut(callback); }; - Client.prototype.getUserInfo = function(callback) { - var xhr; + Client.prototype.getUserInfo = function(options, callback) { + var httpCache, xhr; + if ((!callback) && (typeof options === 'function')) { + callback = options; + options = null; + } + httpCache = false; + if (options && options.httpCache) { + httpCache = true; + } xhr = new Dropbox.Xhr('GET', this.urls.accountInfo); - xhr.signWithOauth(this.oauth); + xhr.signWithOauth(this.oauth, httpCache); return this.dispatchXhr(xhr, function(error, userData) { return callback(error, Dropbox.UserInfo.parse(userData), userData); }); }; Client.prototype.readFile = function(path, options, callback) { - var params, rangeEnd, rangeHeader, rangeStart, responseType, xhr; + var httpCache, params, rangeEnd, rangeHeader, rangeStart, responseType, xhr; if ((!callback) && (typeof options === 'function')) { callback = options; options = null; @@ -310,6 +350,7 @@ params = {}; responseType = null; rangeHeader = null; + httpCache = false; if (options) { if (options.versionTag) { params.rev = options.versionTag; @@ -335,9 +376,13 @@ } else if (options.start != null) { rangeHeader = "bytes=" + options.start + "-"; } + if (options.httpCache) { + httpCache = true; + } } xhr = new Dropbox.Xhr('GET', "" + this.urls.getFile + "/" + (this.urlEncodePath(path))); - xhr.setParams(params).signWithOauth(this.oauth).setResponseType(responseType); + xhr.setParams(params).signWithOauth(this.oauth, httpCache); + xhr.setResponseType(responseType); if (rangeHeader) { xhr.setHeader('Range', rangeHeader); } @@ -411,13 +456,65 @@ }); }; - Client.prototype.stat = function(path, options, callback) { + Client.prototype.resumableUploadStep = function(data, cursor, callback) { + var params, xhr; + if (cursor) { + params = { + offset: cursor.offset + }; + if (cursor.tag) { + params.upload_id = cursor.tag; + } + } else { + params = { + offset: 0 + }; + } + xhr = new Dropbox.Xhr('POST', this.urls.chunkedUpload); + xhr.setBody(data).setParams(params).signWithOauth(this.oauth); + return this.dispatchXhr(xhr, function(error, cursor) { + if (error && error.status === 400 && error.response.upload_id && error.response.offset) { + return callback(null, Dropbox.UploadCursor.parse(error.response)); + } else { + return callback(error, Dropbox.UploadCursor.parse(cursor)); + } + }); + }; + + Client.prototype.resumableUploadFinish = function(path, cursor, options, callback) { var params, xhr; if ((!callback) && (typeof options === 'function')) { callback = options; options = null; } + params = { + upload_id: cursor.tag + }; + if (options) { + if (options.lastVersionTag) { + params.parent_rev = options.lastVersionTag; + } else if (options.parentRev || options.parent_rev) { + params.parent_rev = options.parentRev || options.parent_rev; + } + if (options.noOverwrite) { + params.autorename = true; + } + } + xhr = new Dropbox.Xhr('POST', "" + this.urls.commitChunkedUpload + "/" + (this.urlEncodePath(path))); + xhr.setParams(params).signWithOauth(this.oauth); + return this.dispatchXhr(xhr, function(error, metadata) { + return callback(error, Dropbox.Stat.parse(metadata)); + }); + }; + + Client.prototype.stat = function(path, options, callback) { + var httpCache, params, xhr; + if ((!callback) && (typeof options === 'function')) { + callback = options; + options = null; + } params = {}; + httpCache = false; if (options) { if (options.version != null) { params.rev = options.version; @@ -434,11 +531,14 @@ if (options.cacheHash) { params.hash = options.cacheHash; } + if (options.httpCache) { + httpCache = true; + } } params.include_deleted || (params.include_deleted = 'false'); params.list || (params.list = 'false'); xhr = new Dropbox.Xhr('GET', "" + this.urls.metadata + "/" + (this.urlEncodePath(path))); - xhr.setParams(params).signWithOauth(this.oauth); + xhr.setParams(params).signWithOauth(this.oauth, httpCache); return this.dispatchXhr(xhr, function(error, metadata) { var entries, entry, stat; stat = Dropbox.Stat.parse(metadata); @@ -476,6 +576,12 @@ if (options.versionTag) { statOptions.versionTag = options.versionTag; } + if (options.removed || options.deleted) { + statOptions.removed = options.removed || options.deleted; + } + if (options.httpCache) { + statOptions.httpCache = options.httpCache; + } } return this.stat(path, statOptions, function(error, stat, entry_stats) { var entries, entry_stat; @@ -536,17 +642,23 @@ }; Client.prototype.history = function(path, options, callback) { - var params, xhr; + var httpCache, params, xhr; if ((!callback) && (typeof options === 'function')) { callback = options; options = null; } params = {}; - if (options && (options.limit != null)) { - params.rev_limit = options.limit; + httpCache = false; + if (options) { + if (options.limit != null) { + params.rev_limit = options.limit; + } + if (options.httpCache) { + httpCache = true; + } } xhr = new Dropbox.Xhr('GET', "" + this.urls.revisions + "/" + (this.urlEncodePath(path))); - xhr.setParams(params).signWithOauth(this.oauth); + xhr.setParams(params).signWithOauth(this.oauth, httpCache); return this.dispatchXhr(xhr, function(error, versions) { var metadata, stats; if (versions) { @@ -628,7 +740,7 @@ }; Client.prototype.findByName = function(path, namePattern, options, callback) { - var params, xhr; + var httpCache, params, xhr; if ((!callback) && (typeof options === 'function')) { callback = options; options = null; @@ -636,6 +748,7 @@ params = { query: namePattern }; + httpCache = false; if (options) { if (options.limit != null) { params.file_limit = options.limit; @@ -643,9 +756,12 @@ if (options.removed || options.deleted) { params.include_deleted = true; } + if (options.httpCache) { + httpCache = true; + } } xhr = new Dropbox.Xhr('GET', "" + this.urls.search + "/" + (this.urlEncodePath(path))); - xhr.setParams(params).signWithOauth(this.oauth); + xhr.setParams(params).signWithOauth(this.oauth, httpCache); return this.dispatchXhr(xhr, function(error, results) { var metadata, stats; if (results) { @@ -784,15 +900,22 @@ }; Client.prototype.reset = function() { + var oldAuthState; this.uid = null; this.oauth.setToken(null, ''); + oldAuthState = this.authState; this.authState = DropboxClient.RESET; + if (oldAuthState !== this.authState) { + this.onAuthStateChange.dispatch(this); + } this.authError = null; this._credentials = null; return this; }; Client.prototype.setCredentials = function(credentials) { + var oldAuthState; + oldAuthState = this.authState; this.oauth.reset(credentials); this.uid = credentials.uid || null; if (credentials.authState) { @@ -806,6 +929,9 @@ } this.authError = null; this._credentials = null; + if (oldAuthState !== this.authState) { + this.onAuthStateChange.dispatch(this); + } return this; }; @@ -833,6 +959,8 @@ media: "" + this.apiServer + "/1/media/" + this.fileRoot, copyRef: "" + this.apiServer + "/1/copy_ref/" + this.fileRoot, thumbnails: "" + this.fileServer + "/1/thumbnails/" + this.fileRoot, + chunkedUpload: "" + this.fileServer + "/1/chunked_upload", + commitChunkedUpload: "" + this.fileServer + "/1/commit_chunked_upload/" + this.fileRoot, fileopsCopy: "" + this.apiServer + "/1/fileops/copy", fileopsCreateFolder: "" + this.apiServer + "/1/fileops/create_folder", fileopsDelete: "" + this.apiServer + "/1/fileops/delete", @@ -840,6 +968,8 @@ }; }; + Client.prototype.authState = null; + Client.ERROR = 0; Client.RESET = 1; @@ -893,14 +1023,12 @@ Client.prototype.dispatchXhr = function(xhr, callback) { var nativeXhr; xhr.setCallback(callback); + xhr.onError = this.onError; xhr.prepare(); nativeXhr = xhr.xhr; - if (this.filter) { - if (!this.filter(nativeXhr, xhr)) { - return nativeXhr; - } + if (this.onXhr.dispatch(xhr)) { + xhr.send(); } - xhr.send(); return nativeXhr; }; @@ -987,6 +1115,7 @@ function BrowserBase(options) { this.rememberUser = (options != null ? options.rememberUser : void 0) || false; this.scope = (options != null ? options.scope : void 0) || 'default'; + this.storageKey = null; } BrowserBase.prototype.onAuthStateChange = function(client, callback) { @@ -1115,9 +1244,7 @@ }; Redirect.prototype.doAuthorize = function(authUrl) { - /* */ - return DOM.openWindow(authUrl); - /* return window.location.assign(authUrl); */ + return window.location.assign(authUrl); }; Redirect.prototype.computeUrl = function() { @@ -1188,7 +1315,7 @@ if (client.authState === DropboxClient.RESET) { return this.loadCredentials(function(credentials) { if (credentials && credentials.authState) { - return this.forgetCredentials(superCall); + return _this.forgetCredentials(superCall); } return superCall(); }); @@ -1263,10 +1390,241 @@ return window.addEventListener('message', listener, false); }; + Popup.oauthReceiver = function() { + return window.addEventListener('load', function() { + var opener; + opener = window.opener; + if (window.parent !== window.top) { + opener || (opener = window.parent); + } + if (opener) { + try { + opener.postMessage(window.location.href, '*'); + } catch (e) { + + } + return window.close(); + } + }); + }; + return Popup; })(Dropbox.Drivers.BrowserBase); + DropboxChromeOnMessage = null; + + DropboxChromeSendMessage = null; + + if (typeof chrome !== "undefined" && chrome !== null) { + if (chrome.runtime) { + if (chrome.runtime.onMessage) { + DropboxChromeOnMessage = chrome.runtime.onMessage; + } + if (chrome.runtime.sendMessage) { + DropboxChromeSendMessage = function(m) { + return chrome.runtime.sendMessage(m); + }; + } + } + if (chrome.extension) { + if (chrome.extension.onMessage) { + DropboxChromeOnMessage || (DropboxChromeOnMessage = chrome.extension.onMessage); + } + if (chrome.extension.sendMessage) { + DropboxChromeSendMessage || (DropboxChromeSendMessage = function(m) { + return chrome.extension.sendMessage(m); + }); + } + } + if (!DropboxChromeOnMessage) { + (function() { + var page, pageHack; + pageHack = function(page) { + if (page.Dropbox) { + Dropbox.Drivers.Chrome.prototype.onMessage = page.Dropbox.Drivers.Chrome.onMessage; + return Dropbox.Drivers.Chrome.prototype.sendMessage = page.Dropbox.Drivers.Chrome.sendMessage; + } else { + page.Dropbox = Dropbox; + Dropbox.Drivers.Chrome.prototype.onMessage = new Dropbox.EventSource; + return Dropbox.Drivers.Chrome.prototype.sendMessage = function(m) { + return Dropbox.Drivers.Chrome.prototype.onMessage.dispatch(m); + }; + } + }; + if (chrome.extension && chrome.extension.getBackgroundPage) { + if (page = chrome.extension.getBackgroundPage()) { + return pageHack(page); + } + } + if (chrome.runtime && chrome.runtime.getBackgroundPage) { + return chrome.runtime.getBackgroundPage(function(page) { + return pageHack(page); + }); + } + })(); + } + } + + Dropbox.Drivers.Chrome = (function() { + + Chrome.prototype.onMessage = DropboxChromeOnMessage; + + Chrome.prototype.sendMessage = DropboxChromeSendMessage; + + Chrome.prototype.expandUrl = function(url) { + if (chrome.runtime && chrome.runtime.getURL) { + return chrome.runtime.getURL(url); + } + if (chrome.extension && chrome.extension.getURL) { + return chrome.extension.getURL(url); + } + return url; + }; + + function Chrome(options) { + var receiverPath, scope; + receiverPath = (options && options.receiverPath) || 'chrome_oauth_receiver.html'; + this.receiverUrl = this.expandUrl(receiverPath); + this.tokenRe = new RegExp("(#|\\?|&)oauth_token=([^&#]+)(&|#|$)"); + scope = (options && options.scope) || 'default'; + this.storageKey = "dropbox_js_" + scope + "_credentials"; + } + + Chrome.prototype.onAuthStateChange = function(client, callback) { + var _this = this; + switch (client.authState) { + case Dropbox.Client.RESET: + return this.loadCredentials(function(credentials) { + if (credentials) { + if (credentials.authState) { + return _this.forgetCredentials(callback); + } + client.setCredentials(credentials); + } + return callback(); + }); + case Dropbox.Client.DONE: + return this.storeCredentials(client.credentials(), callback); + case Dropbox.Client.SIGNED_OFF: + return this.forgetCredentials(callback); + case Dropbox.Client.ERROR: + return this.forgetCredentials(callback); + default: + return callback(); + } + }; + + Chrome.prototype.doAuthorize = function(authUrl, token, tokenSecret, callback) { + var window; + window = { + handle: null + }; + this.listenForMessage(token, window, callback); + return this.openWindow(authUrl, function(handle) { + return window.handle = handle; + }); + }; + + Chrome.prototype.openWindow = function(url, callback) { + if (chrome.tabs && chrome.tabs.create) { + chrome.tabs.create({ + url: url, + active: true, + pinned: false + }, function(tab) { + return callback(tab); + }); + return this; + } + if (chrome.app && chrome.app.window && chrome.app.window.create) { + chrome.app.window.create(url, { + frame: 'none', + id: 'dropbox-auth' + }, function(window) { + return callback(window); + }); + return this; + } + return this; + }; + + Chrome.prototype.closeWindow = function(handle) { + if (chrome.tabs && chrome.tabs.remove && handle.id) { + chrome.tabs.remove(handle.id); + return this; + } + if (chrome.app && chrome.app.window && handle.close) { + handle.close(); + return this; + } + return this; + }; + + Chrome.prototype.url = function() { + return this.receiverUrl; + }; + + Chrome.prototype.listenForMessage = function(token, window, callback) { + var listener, + _this = this; + listener = function(message, sender) { + var match; + if (sender && sender.tab) { + if (sender.tab.url.substring(0, _this.receiverUrl.length) !== _this.receiverUrl) { + return; + } + } + match = _this.tokenRe.exec(message.dropbox_oauth_receiver_href || ''); + if (match && decodeURIComponent(match[2]) === token) { + if (window.handle) { + _this.closeWindow(window.handle); + } + _this.onMessage.removeListener(listener); + return callback(); + } + }; + return this.onMessage.addListener(listener); + }; + + Chrome.prototype.storeCredentials = function(credentials, callback) { + var items; + items = {}; + items[this.storageKey] = credentials; + chrome.storage.local.set(items, callback); + return this; + }; + + Chrome.prototype.loadCredentials = function(callback) { + var _this = this; + chrome.storage.local.get(this.storageKey, function(items) { + return callback(items[_this.storageKey] || null); + }); + return this; + }; + + Chrome.prototype.forgetCredentials = function(callback) { + chrome.storage.local.remove(this.storageKey, callback); + return this; + }; + + Chrome.oauthReceiver = function() { + return window.addEventListener('load', function() { + var driver; + driver = new Dropbox.Drivers.Chrome(); + driver.sendMessage({ + dropbox_oauth_receiver_href: window.location.href + }); + if (window.close) { + return window.close(); + } + }); + }; + + return Chrome; + + })(); + Dropbox.Drivers.NodeServer = (function() { function NodeServer(options) { @@ -1361,6 +1719,60 @@ })(); + Dropbox.EventSource = (function() { + + function EventSource(options) { + this._cancelable = options && options.cancelable; + this._listeners = []; + } + + EventSource.prototype.addListener = function(listener) { + if (typeof listener !== 'function') { + throw new TypeError('Invalid listener type; expected function'); + } + if (__indexOf.call(this._listeners, listener) < 0) { + this._listeners.push(listener); + } + return this; + }; + + EventSource.prototype.removeListener = function(listener) { + var i, index, subscriber, _i, _len, _ref; + if (this._listeners.indexOf) { + index = this._listeners.indexOf(listener); + if (index !== -1) { + this._listeners.splice(index, 1); + } + } else { + _ref = this._listeners; + for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { + subscriber = _ref[i]; + if (subscriber === listener) { + this._listeners.splice(i, 1); + break; + } + } + } + return this; + }; + + EventSource.prototype.dispatch = function(event) { + var listener, returnValue, _i, _len, _ref; + _ref = this._listeners; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + listener = _ref[_i]; + returnValue = listener(event); + if (this._cancelable && returnValue === false) { + return false; + } + } + return true; + }; + + return EventSource; + + })(); + base64HmacSha1 = function(string, key) { return arrayToBase64(hmacSha1(stringToArray(string), stringToArray(key), string.length, key.length)); }; @@ -1720,6 +2132,10 @@ PulledChanges.prototype.shouldBackOff = void 0; + PulledChanges.prototype.cursor = function() { + return this.cursorTag; + }; + function PulledChanges(deltaInfo) { var entry; this.blankSlate = deltaInfo.reset || false; @@ -1787,13 +2203,21 @@ } }; - PublicUrl.prototype.url = void 0; + PublicUrl.prototype.url = null; - PublicUrl.prototype.expiresAt = void 0; + PublicUrl.prototype.expiresAt = null; - PublicUrl.prototype.isDirect = void 0; + PublicUrl.prototype.isDirect = null; - PublicUrl.prototype.isPreview = void 0; + PublicUrl.prototype.isPreview = null; + + PublicUrl.prototype.json = function() { + return this._json || (this._json = { + url: this.url, + expires: this.expiresAt.toString(), + direct: this.isDirect + }); + }; function PublicUrl(urlData, isDirect) { this.url = urlData.url; @@ -1803,9 +2227,14 @@ } else if (isDirect === false) { this.isDirect = false; } else { - this.isDirect = Date.now() - this.expiresAt <= 86400000; + if ('direct' in urlData) { + this.isDirect = urlData.direct; + } else { + this.isDirect = Date.now() - this.expiresAt <= 86400000; + } } this.isPreview = !this.isDirect; + this._json = null; } return PublicUrl; @@ -1822,17 +2251,26 @@ } }; - CopyReference.prototype.tag = void 0; + CopyReference.prototype.tag = null; - CopyReference.prototype.expiresAt = void 0; + CopyReference.prototype.expiresAt = null; + + CopyReference.prototype.json = function() { + return this._json || (this._json = { + copy_ref: this.tag, + expires: this.expiresAt.toString() + }); + }; function CopyReference(refData) { if (typeof refData === 'object') { this.tag = refData.copy_ref; this.expiresAt = new Date(Date.parse(refData.expires)); + this._json = refData; } else { this.tag = refData; - this.expiresAt = new Date(); + this.expiresAt = new Date(Math.ceil(Date.now() / 1000) * 1000); + this._json = null; } } @@ -1878,8 +2316,13 @@ Stat.prototype.clientModifiedAt = null; + Stat.prototype.json = function() { + return this._json; + }; + function Stat(metadata) { var lastIndex, nameSlash, _ref, _ref1; + this._json = metadata; this.path = metadata.path; if (this.path.substring(0, 1) !== '/') { this.path = '/' + this.path; @@ -1930,6 +2373,53 @@ })(); + Dropbox.UploadCursor = (function() { + + UploadCursor.parse = function(cursorData) { + if (cursorData && (typeof cursorData === 'object' || typeof cursorData === 'string')) { + return new Dropbox.UploadCursor(cursorData); + } else { + return cursorData; + } + }; + + UploadCursor.prototype.tag = null; + + UploadCursor.prototype.offset = null; + + UploadCursor.prototype.expiresAt = null; + + UploadCursor.prototype.json = function() { + return this._json || (this._json = { + upload_id: this.tag, + offset: this.offset, + expires: this.expiresAt.toString() + }); + }; + + function UploadCursor(cursorData) { + this.replace(cursorData); + } + + UploadCursor.prototype.replace = function(cursorData) { + if (typeof cursorData === 'object') { + this.tag = cursorData.upload_id || null; + this.offset = cursorData.offset || 0; + this.expiresAt = new Date(Date.parse(cursorData.expires) || Date.now()); + this._json = cursorData; + } else { + this.tag = cursorData || null; + this.offset = 0; + this.expiresAt = new Date(Math.floor(Date.now() / 1000) * 1000); + this._json = null; + } + return this; + }; + + return UploadCursor; + + })(); + Dropbox.UserInfo = (function() { UserInfo.parse = function(userInfo) { @@ -1960,8 +2450,13 @@ UserInfo.prototype.sharedBytes = null; + UserInfo.prototype.json = function() { + return this._json; + }; + function UserInfo(userInfo) { var lastIndex; + this._json = userInfo; this.name = userInfo.display_name; this.email = userInfo.email; this.countryCode = userInfo.country || null; @@ -2006,15 +2501,21 @@ if (typeof Uint8Array === 'undefined') { DropboxXhrArrayBufferView = null; + DropboxXhrSendArrayBufferView = false; } else { - DropboxXhrArrayBufferView = (new Uint8Array(0)).__proto__.__proto__.constructor; + if (Object.getPrototypeOf) { + DropboxXhrArrayBufferView = Object.getPrototypeOf(Object.getPrototypeOf(new Uint8Array(0))).constructor; + } else if (Object.__proto__) { + DropboxXhrArrayBufferView = (new Uint8Array(0)).__proto__.__proto__.constructor; + } + DropboxXhrSendArrayBufferView = DropboxXhrArrayBufferView !== Object; } Dropbox.Xhr = (function() { Xhr.Request = DropboxXhrRequest; - Xhr.ieMode = DropboxXhrIeMode; + Xhr.ieXdr = DropboxXhrIeMode; Xhr.canSendForms = DropboxXhrCanSendForms; @@ -2022,6 +2523,8 @@ Xhr.ArrayBufferView = DropboxXhrArrayBufferView; + Xhr.sendArrayBufferView = DropboxXhrSendArrayBufferView; + function Xhr(method, baseUrl) { this.method = method; this.isGet = this.method === 'GET'; @@ -2034,8 +2537,13 @@ this.responseType = null; this.callback = null; this.xhr = null; + this.onError = null; } + Xhr.prototype.xhr = null; + + Xhr.prototype.onError = null; + Xhr.prototype.setParams = function(params) { if (this.signed) { throw new Error('setParams called after addOauthParams or addOauthHeader'); @@ -2052,11 +2560,17 @@ return this; }; - Xhr.prototype.signWithOauth = function(oauth) { - if (Dropbox.Xhr.ieMode || (Dropbox.Xhr.doesPreflight && (!this.preflight))) { + Xhr.prototype.signWithOauth = function(oauth, cacheFriendly) { + if (Dropbox.Xhr.ieXdr) { return this.addOauthParams(oauth); - } else { + } else if (this.preflight || !Dropbox.Xhr.doesPreflight) { return this.addOauthHeader(oauth); + } else { + if (this.isGet && cacheFriendly) { + return this.addOauthHeader(oauth); + } else { + return this.addOauthParams(oauth); + } } }; @@ -2086,10 +2600,13 @@ if (this.body !== null) { throw new Error('Request already has a body'); } - if (!this.preflight) { - if (!((typeof FormData !== 'undefined') && (body instanceof FormData))) { - this.preflight = true; - } + if (typeof body === 'string') { + + } else if ((typeof FormData !== 'undefined') && (body instanceof FormData)) { + + } else { + this.headers['Content-Type'] = 'application/octet-stream'; + this.preflight = true; } this.body = body; return this; @@ -2120,18 +2637,24 @@ throw new Error('Request already has a body'); } if (this.isGet) { - throw new Error('paramsToBody cannot be called on GET requests'); + throw new Error('setFileField cannot be called on GET requests'); } if (typeof fileData === 'object' && typeof Blob !== 'undefined') { - if ((typeof ArrayBuffer !== "undefined" && ArrayBuffer !== null) && fileData instanceof ArrayBuffer) { - fileData = new Uint8Array(fileData); - } - if (Dropbox.Xhr.ArrayBufferView && fileData instanceof Dropbox.Xhr.ArrayBufferView) { - contentType || (contentType = 'application/octet-stream'); - fileData = new Blob([fileData], { - type: contentType - }); + if (typeof ArrayBuffer !== 'undefined') { + if (fileData instanceof ArrayBuffer) { + if (Dropbox.Xhr.sendArrayBufferView) { + fileData = new Uint8Array(fileData); + } + } else { + if (!Dropbox.Xhr.sendArrayBufferView && fileData.byteOffset === 0 && fileData.buffer instanceof ArrayBuffer) { + fileData = fileData.buffer; + } + } } + contentType || (contentType = 'application/octet-stream'); + fileData = new Blob([fileData], { + type: contentType + }); if (typeof File !== 'undefined' && fileData instanceof File) { fileData = new Blob([fileData], { type: fileData.type @@ -2184,10 +2707,10 @@ }; Xhr.prototype.prepare = function() { - var header, ieMode, value, _ref, + var header, ieXdr, value, _ref, _this = this; - ieMode = Dropbox.Xhr.ieMode; - if (this.isGet || this.body !== null || ieMode) { + ieXdr = Dropbox.Xhr.ieXdr; + if (this.isGet || this.body !== null || ieXdr) { this.paramsToUrl(); if (this.body !== null && typeof this.body === 'string') { this.headers['Content-Type'] = 'text/plain; charset=utf8'; @@ -2196,20 +2719,24 @@ this.paramsToBody(); } this.xhr = new Dropbox.Xhr.Request(); - if (ieMode) { + if (ieXdr) { this.xhr.onload = function() { - return _this.onLoad(); + return _this.onXdrLoad(); }; this.xhr.onerror = function() { - return _this.onError(); + return _this.onXdrError(); }; + this.xhr.ontimeout = function() { + return _this.onXdrError(); + }; + this.xhr.onprogress = function() {}; } else { this.xhr.onreadystatechange = function() { return _this.onReadyStateChange(); }; } this.xhr.open(this.method, this.url, true); - if (!ieMode) { + if (!ieXdr) { _ref = this.headers; for (header in _ref) { if (!__hasProp.call(_ref, header)) continue; @@ -2234,13 +2761,13 @@ this.callback = callback || this.callback; if (this.body !== null) { body = this.body; - if (Dropbox.Xhr.ArrayBufferView && body instanceof ArrayBuffer) { + if (Dropbox.Xhr.sendArrayBufferView && body instanceof ArrayBuffer) { body = new Uint8Array(body); } try { this.xhr.send(body); } catch (e) { - if (typeof Blob !== 'undefined' && Dropbox.Xhr.ArrayBufferView && body instanceof Dropbox.Xhr.ArrayBufferView) { + if (!Dropbox.Xhr.sendArrayBufferView && typeof Blob !== 'undefined') { body = new Blob([body], { type: 'application/octet-stream' }); @@ -2288,6 +2815,9 @@ } if (this.xhr.status < 200 || this.xhr.status >= 300) { apiError = new Dropbox.ApiError(this.xhr, this.method, this.url); + if (this.onError) { + this.onError.dispatch(apiError); + } this.callback(apiError); return true; } @@ -2343,7 +2873,7 @@ return true; }; - Xhr.prototype.onLoad = function() { + Xhr.prototype.onXdrLoad = function() { var text; text = this.xhr.responseText; switch (this.xhr.contentType) { @@ -2360,9 +2890,12 @@ return true; }; - Xhr.prototype.onError = function() { + Xhr.prototype.onXdrError = function() { var apiError; apiError = new Dropbox.ApiError(this.xhr, this.method, this.url); + if (this.onError) { + this.onError.dispatch(apiError); + } this.callback(apiError); return true; }; diff --git a/lib/client/storage/dropbox/lib/dropbox.min.js b/lib/client/storage/dropbox/lib/dropbox.min.js index bb51c44f..f14c0b0b 100644 --- a/lib/client/storage/dropbox/lib/dropbox.min.js +++ b/lib/client/storage/dropbox/lib/dropbox.min.js @@ -1,2 +1,2 @@ -(function(){var t,e,r,n,o,i,s,a,h,u,l,p,c,d,f,y,v,m,g,w,S,b,T,_={}.hasOwnProperty,E=function(t,e){function r(){this.constructor=t}for(var n in e)_.call(e,n)&&(t[n]=e[n]);return r.prototype=e.prototype,t.prototype=new r,t.__super__=e.prototype,t};if(t=function(){function t(t){this.client=new e(t)}return t}(),t.ApiError=function(){function t(t,e,r){var n;if(this.method=e,this.url=r,this.status=t.status,n=t.responseType?t.response||t.responseText:t.responseText)try{this.responseText=""+n,this.response=JSON.parse(n)}catch(o){this.response=null}else this.responseText="(no response)",this.response=null}return t.prototype.status=void 0,t.prototype.method=void 0,t.prototype.url=void 0,t.prototype.responseText=void 0,t.prototype.response=void 0,t.prototype.toString=function(){return"Dropbox API error "+this.status+" from "+this.method+" "+this.url+" :: "+this.responseText},t.prototype.inspect=function(){return""+this},t}(),"undefined"!=typeof window&&null!==window?window.atob&&window.btoa?(u=function(t){return window.atob(t)},f=function(t){return window.btoa(t)}):(p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",y=function(t,e,r){var n,o;for(o=3-e,t<<=8*o,n=3;n>=o;)r.push(p.charAt(63&t>>6*n)),n-=1;for(n=e;3>n;)r.push("="),n+=1;return null},l=function(t,e,r){var n,o;for(o=4-e,t<<=6*o,n=2;n>=o;)r.push(String.fromCharCode(255&t>>8*n)),n-=1;return null},f=function(t){var e,r,n,o,i,s;for(o=[],e=0,r=0,n=i=0,s=t.length;s>=0?s>i:i>s;n=s>=0?++i:--i)e=e<<8|t.charCodeAt(n),r+=1,3===r&&(y(e,r,o),e=r=0);return r>0&&y(e,r,o),o.join("")},u=function(t){var e,r,n,o,i,s,a;for(i=[],e=0,n=0,o=s=0,a=t.length;(a>=0?a>s:s>a)&&(r=t.charAt(o),"="!==r);o=a>=0?++s:--s)e=e<<6|p.indexOf(r),n+=1,4===n&&(l(e,n,i),e=n=0);return n>0&&l(e,n,i),i.join("")}):(u=function(t){var e,r;return e=new Buffer(t,"base64"),function(){var t,n,o;for(o=[],r=t=0,n=e.length;n>=0?n>t:t>n;r=n>=0?++t:--t)o.push(String.fromCharCode(e[r]));return o}().join("")},f=function(t){var e,r;return e=new Buffer(function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(t.charCodeAt(r));return o}()),e.toString("base64")}),t.Client=function(){function r(e){this.sandbox=e.sandbox||!1,this.apiServer=e.server||this.defaultApiServer(),this.authServer=e.authServer||this.defaultAuthServer(),this.fileServer=e.fileServer||this.defaultFileServer(),this.downloadServer=e.downloadServer||this.defaultDownloadServer(),this.oauth=new t.Oauth(e),this.driver=null,this.filter=null,this.uid=null,this.authState=null,this.authError=null,this._credentials=null,this.setCredentials(e),this.setupUrls()}return r.prototype.authDriver=function(t){return this.driver=t,this},r.prototype.xhrFilter=function(t){return this.filter=t,this},r.prototype.dropboxUid=function(){return this.uid},r.prototype.credentials=function(){return this._credentials||this.computeCredentials(),this._credentials},r.prototype.authenticate=function(r){var n,o,i=this;return n=null,o=function(){var s;if(n!==i.authState&&(n=i.authState,i.driver.onAuthStateChange))return i.driver.onAuthStateChange(i,o);switch(i.authState){case e.RESET:return i.requestToken(function(t,r){var n,s;return t?(i.authError=t,i.authState=e.ERROR):(n=r.oauth_token,s=r.oauth_token_secret,i.oauth.setToken(n,s),i.authState=e.REQUEST),i._credentials=null,o()});case e.REQUEST:return s=i.authorizeUrl(i.oauth.token),i.driver.doAuthorize(s,i.oauth.token,i.oauth.tokenSecret,function(){return i.authState=e.AUTHORIZED,i._credentials=null,o()});case e.AUTHORIZED:return i.getAccessToken(function(t,r){return t?(i.authError=t,i.authState=e.ERROR):(i.oauth.setToken(r.oauth_token,r.oauth_token_secret),i.uid=r.uid,i.authState=e.DONE),i._credentials=null,o()});case e.DONE:return r(null,i);case t.SIGNED_OFF:return i.reset(),o();case e.ERROR:return r(i.authError)}},o(),this},r.prototype.signOut=function(r){var n,o=this;return n=new t.Xhr("POST",this.urls.signOut),n.signWithOauth(this.oauth),this.dispatchXhr(n,function(t){return t?r(t):(o.reset(),o.authState=e.SIGNED_OFF,o.driver.onAuthStateChange?o.driver.onAuthStateChange(o,function(){return r(t)}):r(t))})},r.prototype.signOff=function(t){return this.signOut(t)},r.prototype.getUserInfo=function(e){var r;return r=new t.Xhr("GET",this.urls.accountInfo),r.signWithOauth(this.oauth),this.dispatchXhr(r,function(r,n){return e(r,t.UserInfo.parse(n),n)})},r.prototype.readFile=function(e,r,n){var o,i,s,a,h,u;return n||"function"!=typeof r||(n=r,r=null),o={},h=null,s=null,r&&(r.versionTag?o.rev=r.versionTag:r.rev&&(o.rev=r.rev),r.arrayBuffer?h="arraybuffer":r.blob?h="blob":r.binary&&(h="b"),r.length?(null!=r.start?(a=r.start,i=r.start+r.length-1):(a="",i=r.length),s="bytes="+a+"-"+i):null!=r.start&&(s="bytes="+r.start+"-")),u=new t.Xhr("GET",""+this.urls.getFile+"/"+this.urlEncodePath(e)),u.setParams(o).signWithOauth(this.oauth).setResponseType(h),s&&u.setHeader("Range",s),this.dispatchXhr(u,function(e,r,o){return n(e,r,t.Stat.parse(o))})},r.prototype.writeFile=function(e,r,n,o){var i;return o||"function"!=typeof n||(o=n,n=null),i=t.Xhr.canSendForms&&"object"==typeof r,i?this.writeFileUsingForm(e,r,n,o):this.writeFileUsingPut(e,r,n,o)},r.prototype.writeFileUsingForm=function(e,r,n,o){var i,s,a,h;return a=e.lastIndexOf("/"),-1===a?(i=e,e=""):(i=e.substring(a),e=e.substring(0,a)),s={file:i},n&&(n.noOverwrite&&(s.overwrite="false"),n.lastVersionTag?s.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(s.parent_rev=n.parentRev||n.parent_rev)),h=new t.Xhr("POST",""+this.urls.postFile+"/"+this.urlEncodePath(e)),h.setParams(s).signWithOauth(this.oauth).setFileField("file",i,r,"application/octet-stream"),delete s.file,this.dispatchXhr(h,function(e,r){return o(e,t.Stat.parse(r))})},r.prototype.writeFileUsingPut=function(e,r,n,o){var i,s;return i={},n&&(n.noOverwrite&&(i.overwrite="false"),n.lastVersionTag?i.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(i.parent_rev=n.parentRev||n.parent_rev)),s=new t.Xhr("POST",""+this.urls.putFile+"/"+this.urlEncodePath(e)),s.setBody(r).setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return o(e,t.Stat.parse(r))})},r.prototype.stat=function(e,r,n){var o,i;return n||"function"!=typeof r||(n=r,r=null),o={},r&&(null!=r.version&&(o.rev=r.version),(r.removed||r.deleted)&&(o.include_deleted="true"),r.readDir&&(o.list="true",r.readDir!==!0&&(o.file_limit=""+r.readDir)),r.cacheHash&&(o.hash=r.cacheHash)),o.include_deleted||(o.include_deleted="false"),o.list||(o.list="false"),i=new t.Xhr("GET",""+this.urls.metadata+"/"+this.urlEncodePath(e)),i.setParams(o).signWithOauth(this.oauth),this.dispatchXhr(i,function(e,r){var o,i,s;return s=t.Stat.parse(r),o=(null!=r?r.contents:void 0)?function(){var e,n,o,s;for(o=r.contents,s=[],e=0,n=o.length;n>e;e++)i=o[e],s.push(t.Stat.parse(i));return s}():void 0,n(e,s,o)})},r.prototype.readdir=function(t,e,r){var n;return r||"function"!=typeof e||(r=e,e=null),n={readDir:!0},e&&(null!=e.limit&&(n.readDir=e.limit),e.versionTag&&(n.versionTag=e.versionTag)),this.stat(t,n,function(t,e,n){var o,i;return o=n?function(){var t,e,r;for(r=[],t=0,e=n.length;e>t;t++)i=n[t],r.push(i.name);return r}():null,r(t,o,e,n)})},r.prototype.metadata=function(t,e,r){return this.stat(t,e,r)},r.prototype.makeUrl=function(e,r,n){var o,i,s,a,h;return n||"function"!=typeof r||(n=r,r=null),i=r&&(r["long"]||r.longUrl||r.downloadHack)?{short_url:"false"}:{},e=this.urlEncodePath(e),s=""+this.urls.shares+"/"+e,o=!1,a=!1,r&&(r.downloadHack?(o=!0,a=!0):r.download&&(o=!0,s=""+this.urls.media+"/"+e)),h=new t.Xhr("POST",s).setParams(i).signWithOauth(this.oauth),this.dispatchXhr(h,function(e,r){return a&&r&&r.url&&(r.url=r.url.replace(this.authServer,this.downloadServer)),n(e,t.PublicUrl.parse(r,o))})},r.prototype.history=function(e,r,n){var o,i;return n||"function"!=typeof r||(n=r,r=null),o={},r&&null!=r.limit&&(o.rev_limit=r.limit),i=new t.Xhr("GET",""+this.urls.revisions+"/"+this.urlEncodePath(e)),i.setParams(o).signWithOauth(this.oauth),this.dispatchXhr(i,function(e,r){var o,i;return i=r?function(){var e,n,i;for(i=[],e=0,n=r.length;n>e;e++)o=r[e],i.push(t.Stat.parse(o));return i}():void 0,n(e,i)})},r.prototype.revisions=function(t,e,r){return this.history(t,e,r)},r.prototype.thumbnailUrl=function(t,e){var r;return r=this.thumbnailXhr(t,e),r.paramsToUrl().url},r.prototype.readThumbnail=function(e,r,n){var o,i;return n||"function"!=typeof r||(n=r,r=null),o="b",r&&r.blob&&(o="blob"),i=this.thumbnailXhr(e,r),i.setResponseType(o),this.dispatchXhr(i,function(e,r,o){return n(e,r,t.Stat.parse(o))})},r.prototype.thumbnailXhr=function(e,r){var n,o;return n={},r&&(r.format?n.format=r.format:r.png&&(n.format="png"),r.size&&(n.size=r.size)),o=new t.Xhr("GET",""+this.urls.thumbnails+"/"+this.urlEncodePath(e)),o.setParams(n).signWithOauth(this.oauth)},r.prototype.revertFile=function(e,r,n){var o;return o=new t.Xhr("POST",""+this.urls.restore+"/"+this.urlEncodePath(e)),o.setParams({rev:r}).signWithOauth(this.oauth),this.dispatchXhr(o,function(e,r){return n(e,t.Stat.parse(r))})},r.prototype.restore=function(t,e,r){return this.revertFile(t,e,r)},r.prototype.findByName=function(e,r,n,o){var i,s;return o||"function"!=typeof n||(o=n,n=null),i={query:r},n&&(null!=n.limit&&(i.file_limit=n.limit),(n.removed||n.deleted)&&(i.include_deleted=!0)),s=new t.Xhr("GET",""+this.urls.search+"/"+this.urlEncodePath(e)),s.setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){var n,i;return i=r?function(){var e,o,i;for(i=[],e=0,o=r.length;o>e;e++)n=r[e],i.push(t.Stat.parse(n));return i}():void 0,o(e,i)})},r.prototype.search=function(t,e,r,n){return this.findByName(t,e,r,n)},r.prototype.makeCopyReference=function(e,r){var n;return n=new t.Xhr("GET",""+this.urls.copyRef+"/"+this.urlEncodePath(e)),n.signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r(e,t.CopyReference.parse(n))})},r.prototype.copyRef=function(t,e){return this.makeCopyReference(t,e)},r.prototype.pullChanges=function(e,r){var n,o;return r||"function"!=typeof e||(r=e,e=null),n=e?e.cursorTag?{cursor:e.cursorTag}:{cursor:e}:{},o=new t.Xhr("POST",this.urls.delta),o.setParams(n).signWithOauth(this.oauth),this.dispatchXhr(o,function(e,n){return r(e,t.PulledChanges.parse(n))})},r.prototype.delta=function(t,e){return this.pullChanges(t,e)},r.prototype.mkdir=function(e,r){var n;return n=new t.Xhr("POST",this.urls.fileopsCreateFolder),n.setParams({root:this.fileRoot,path:this.normalizePath(e)}).signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r(e,t.Stat.parse(n))})},r.prototype.remove=function(e,r){var n;return n=new t.Xhr("POST",this.urls.fileopsDelete),n.setParams({root:this.fileRoot,path:this.normalizePath(e)}).signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r(e,t.Stat.parse(n))})},r.prototype.unlink=function(t,e){return this.remove(t,e)},r.prototype["delete"]=function(t,e){return this.remove(t,e)},r.prototype.copy=function(e,r,n){var o,i,s;return n||"function"!=typeof o||(n=o,o=null),i={root:this.fileRoot,to_path:this.normalizePath(r)},e instanceof t.CopyReference?i.from_copy_ref=e.tag:i.from_path=this.normalizePath(e),s=new t.Xhr("POST",this.urls.fileopsCopy),s.setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return n(e,t.Stat.parse(r))})},r.prototype.move=function(e,r,n){var o,i;return n||"function"!=typeof o||(n=o,o=null),i=new t.Xhr("POST",this.urls.fileopsMove),i.setParams({root:this.fileRoot,from_path:this.normalizePath(e),to_path:this.normalizePath(r)}).signWithOauth(this.oauth),this.dispatchXhr(i,function(e,r){return n(e,t.Stat.parse(r))})},r.prototype.reset=function(){return this.uid=null,this.oauth.setToken(null,""),this.authState=e.RESET,this.authError=null,this._credentials=null,this},r.prototype.setCredentials=function(t){return this.oauth.reset(t),this.uid=t.uid||null,this.authState=t.authState?t.authState:t.token?e.DONE:e.RESET,this.authError=null,this._credentials=null,this},r.prototype.appHash=function(){return this.oauth.appHash()},r.prototype.setupUrls=function(){return this.fileRoot=this.sandbox?"sandbox":"dropbox",this.urls={requestToken:""+this.apiServer+"/1/oauth/request_token",authorize:""+this.authServer+"/1/oauth/authorize",accessToken:""+this.apiServer+"/1/oauth/access_token",signOut:""+this.apiServer+"/1/unlink_access_token",accountInfo:""+this.apiServer+"/1/account/info",getFile:""+this.fileServer+"/1/files/"+this.fileRoot,postFile:""+this.fileServer+"/1/files/"+this.fileRoot,putFile:""+this.fileServer+"/1/files_put/"+this.fileRoot,metadata:""+this.apiServer+"/1/metadata/"+this.fileRoot,delta:""+this.apiServer+"/1/delta",revisions:""+this.apiServer+"/1/revisions/"+this.fileRoot,restore:""+this.apiServer+"/1/restore/"+this.fileRoot,search:""+this.apiServer+"/1/search/"+this.fileRoot,shares:""+this.apiServer+"/1/shares/"+this.fileRoot,media:""+this.apiServer+"/1/media/"+this.fileRoot,copyRef:""+this.apiServer+"/1/copy_ref/"+this.fileRoot,thumbnails:""+this.fileServer+"/1/thumbnails/"+this.fileRoot,fileopsCopy:""+this.apiServer+"/1/fileops/copy",fileopsCreateFolder:""+this.apiServer+"/1/fileops/create_folder",fileopsDelete:""+this.apiServer+"/1/fileops/delete",fileopsMove:""+this.apiServer+"/1/fileops/move"}},r.ERROR=0,r.RESET=1,r.REQUEST=2,r.AUTHORIZED=3,r.DONE=4,r.SIGNED_OFF=5,r.prototype.urlEncodePath=function(e){return t.Xhr.urlEncodeValue(this.normalizePath(e)).replace(/%2F/gi,"/")},r.prototype.normalizePath=function(t){var e;if("/"===t.substring(0,1)){for(e=1;"/"===t.substring(e,e+1);)e+=1;return t.substring(e)}return t},r.prototype.requestToken=function(e){var r;return r=new t.Xhr("POST",this.urls.requestToken).signWithOauth(this.oauth),this.dispatchXhr(r,e)},r.prototype.authorizeUrl=function(e){var r;return r={oauth_token:e,oauth_callback:this.driver.url()},""+this.urls.authorize+"?"+t.Xhr.urlEncode(r)},r.prototype.getAccessToken=function(e){var r;return r=new t.Xhr("POST",this.urls.accessToken).signWithOauth(this.oauth),this.dispatchXhr(r,e)},r.prototype.dispatchXhr=function(t,e){var r;return t.setCallback(e),t.prepare(),r=t.xhr,this.filter&&!this.filter(r,t)?r:(t.send(),r)},r.prototype.defaultApiServer=function(){return"https://api.dropbox.com"},r.prototype.defaultAuthServer=function(){return this.apiServer.replace("api.","www.")},r.prototype.defaultFileServer=function(){return this.apiServer.replace("api.","api-content.")},r.prototype.defaultDownloadServer=function(){return this.apiServer.replace("api.","dl.")},r.prototype.computeCredentials=function(){var t;return t={key:this.oauth.key,sandbox:this.sandbox},this.oauth.secret&&(t.secret=this.oauth.secret),this.oauth.token&&(t.token=this.oauth.token,t.tokenSecret=this.oauth.tokenSecret),this.uid&&(t.uid=this.uid),this.authState!==e.ERROR&&this.authState!==e.RESET&&this.authState!==e.DONE&&this.authState!==e.SIGNED_OFF&&(t.authState=this.authState),this.apiServer!==this.defaultApiServer()&&(t.server=this.apiServer),this.authServer!==this.defaultAuthServer()&&(t.authServer=this.authServer),this.fileServer!==this.defaultFileServer()&&(t.fileServer=this.fileServer),this.downloadServer!==this.defaultDownloadServer()&&(t.downloadServer=this.downloadServer),this._credentials=t},r}(),e=t.Client,t.AuthDriver=function(){function t(){}return t.prototype.url=function(){return"https://some.url"},t.prototype.doAuthorize=function(t,e,r,n){return n("oauth-token")},t.prototype.onAuthStateChange=function(t,e){return e()},t}(),t.Drivers={},t.Drivers.BrowserBase=function(){function t(t){this.rememberUser=(null!=t?t.rememberUser:void 0)||!1,this.scope=(null!=t?t.scope:void 0)||"default"}return t.prototype.onAuthStateChange=function(t,r){var n=this;switch(this.setStorageKey(t),t.authState){case e.RESET:return this.loadCredentials(function(e){return e?e.authState?(t.setCredentials(e),r()):n.rememberUser?(t.setCredentials(e),t.getUserInfo(function(e){return e?(t.reset(),n.forgetCredentials(r)):r()})):(n.forgetCredentials(),r()):r()});case e.REQUEST:return this.storeCredentials(t.credentials(),r);case e.DONE:return this.rememberUser?this.storeCredentials(t.credentials(),r):this.forgetCredentials(r);case e.SIGNED_OFF:return this.forgetCredentials(r);case e.ERROR:return this.forgetCredentials(r);default:return r(),this}},t.prototype.setStorageKey=function(t){return this.storageKey="dropbox-auth:"+this.scope+":"+t.appHash(),this},t.prototype.storeCredentials=function(t,e){return localStorage.setItem(this.storageKey,JSON.stringify(t)),e(),this},t.prototype.loadCredentials=function(t){var e;if(e=localStorage.getItem(this.storageKey),!e)return t(null),this;try{t(JSON.parse(e))}catch(r){t(null)}return this},t.prototype.forgetCredentials=function(t){return localStorage.removeItem(this.storageKey),t(),this},t.currentLocation=function(){return window.location.href},t}(),t.Drivers.Redirect=function(r){function n(t){n.__super__.constructor.call(this,t),this.useQuery=(null!=t?t.useQuery:void 0)||!1,this.receiverUrl=this.computeUrl(t),this.tokenRe=RegExp("(#|\\?|&)oauth_token=([^&#]+)(&|#|$)")}return E(n,r),n.prototype.onAuthStateChange=function(t,r){var o,i=this;return o=function(){return function(){return n.__super__.onAuthStateChange.call(i,t,r)}}(),this.setStorageKey(t),t.authState===e.RESET?this.loadCredentials(function(t){return t&&t.authState?t.token===i.locationToken()&&t.authState===e.REQUEST?(t.authState=e.AUTHORIZED,i.storeCredentials(t,o)):i.forgetCredentials(o):o()}):o()},n.prototype.url=function(){return this.receiverUrl},n.prototype.doAuthorize=function(t){return window.location.assign(t)},n.prototype.computeUrl=function(){var e,r,n,o;return o="_dropboxjs_scope="+encodeURIComponent(this.scope),r=t.Drivers.BrowserBase.currentLocation(),-1===r.indexOf("#")?e=null:(n=r.split("#",2),r=n[0],e=n[1]),this.useQuery?r+=-1===r.indexOf("?")?"?"+o:"&"+o:e="?"+o,e?r+"#"+e:r},n.prototype.locationToken=function(){var e,r,n;return e=t.Drivers.BrowserBase.currentLocation(),n="_dropboxjs_scope="+encodeURIComponent(this.scope)+"&",-1===("function"==typeof e.indexOf?e.indexOf(n):void 0)?null:(r=this.tokenRe.exec(e),r?decodeURIComponent(r[2]):null)},n}(t.Drivers.BrowserBase),t.Drivers.Popup=function(r){function n(t){n.__super__.constructor.call(this,t),this.receiverUrl=this.computeUrl(t),this.tokenRe=RegExp("(#|\\?|&)oauth_token=([^&#]+)(&|#|$)")}return E(n,r),n.prototype.onAuthStateChange=function(t,r){var o,i=this;return o=function(){return function(){return n.__super__.onAuthStateChange.call(i,t,r)}}(),this.setStorageKey(t),t.authState===e.RESET?this.loadCredentials(function(t){return t&&t.authState?this.forgetCredentials(o):o()}):o()},n.prototype.doAuthorize=function(t,e,r,n){return this.listenForMessage(e,n),this.openWindow(t)},n.prototype.url=function(){return this.receiverUrl},n.prototype.computeUrl=function(e){var r;if(e){if(e.receiverUrl)return e.noFragment||-1!==e.receiverUrl.indexOf("#")?e.receiverUrl:e.receiverUrl+"#";if(e.receiverFile)return r=t.Drivers.BrowserBase.currentLocation().split("/"),r[r.length-1]=e.receiverFile,e.noFragment?r.join("/"):r.join("/")+"#"}return t.Drivers.BrowserBase.currentLocation()},n.prototype.openWindow=function(t){return window.open(t,"_dropboxOauthSigninWindow",this.popupWindowSpec(980,700))},n.prototype.popupWindowSpec=function(t,e){var r,n,o,i,s,a,h,u,l,p;return s=null!=(h=window.screenX)?h:window.screenLeft,a=null!=(u=window.screenY)?u:window.screenTop,i=null!=(l=window.outerWidth)?l:document.documentElement.clientWidth,r=null!=(p=window.outerHeight)?p:document.documentElement.clientHeight,n=Math.round(s+(i-t)/2),o=Math.round(a+(r-e)/2.5),s>n&&(n=s),a>o&&(o=a),"width="+t+",height="+e+","+("left="+n+",top="+o)+"dialog=yes,dependent=yes,scrollbars=yes,location=yes"},n.prototype.listenForMessage=function(t,e){var r,n=this;return r=function(o){var i;return i=n.tokenRe.exec(""+o.data),i&&decodeURIComponent(i[2])===t?(window.removeEventListener("message",r),e()):void 0},window.addEventListener("message",r,!1)},n}(t.Drivers.BrowserBase),t.Drivers.NodeServer=function(){function t(t){this.port=(null!=t?t.port:void 0)||8912,this.faviconFile=(null!=t?t.favicon:void 0)||null,this.fs=require("fs"),this.http=require("http"),this.open=require("open"),this.callbacks={},this.urlRe=RegExp("^/oauth_callback\\?"),this.tokenRe=RegExp("(\\?|&)oauth_token=([^&]+)(&|$)"),this.createApp()}return t.prototype.url=function(){return"http://localhost:"+this.port+"/oauth_callback"},t.prototype.doAuthorize=function(t,e,r,n){return this.callbacks[e]=n,this.openBrowser(t)},t.prototype.openBrowser=function(t){if(!t.match(/^https?:\/\//))throw Error("Not a http/https URL: "+t);return this.open(t)},t.prototype.createApp=function(){var t=this;return this.app=this.http.createServer(function(e,r){return t.doRequest(e,r)}),this.app.listen(this.port)},t.prototype.closeServer=function(){return this.app.close()},t.prototype.doRequest=function(t,e){var r,n,o,i=this;return this.urlRe.exec(t.url)&&(n=this.tokenRe.exec(t.url),n&&(o=decodeURIComponent(n[2]),this.callbacks[o]&&(this.callbacks[o](),delete this.callbacks[o]))),r="",t.on("data",function(t){return r+=t}),t.on("end",function(){return i.faviconFile&&"/favicon.ico"===t.url?i.sendFavicon(e):i.closeBrowser(e)})},t.prototype.closeBrowser=function(t){var e;return e='\n\n

Please close this window.

',t.writeHead(200,{"Content-Length":e.length,"Content-Type":"text/html"}),t.write(e),t.end},t.prototype.sendFavicon=function(t){return this.fs.readFile(this.faviconFile,function(e,r){return t.writeHead(200,{"Content-Length":r.length,"Content-Type":"image/x-icon"}),t.write(r),t.end})},t}(),c=function(t,e){return h(g(b(t),b(e),t.length,e.length))},d=function(t){return h(S(b(t),t.length))},("undefined"==typeof window||null===window)&&(v=require("crypto"),c=function(t,e){var r;return r=v.createHmac("sha1",e),r.update(t),r.digest("base64")},d=function(t){var e;return e=v.createHash("sha1"),e.update(t),e.digest("base64")}),g=function(t,e,r,n){var o,i,s,a;return e.length>16&&(e=S(e,n)),s=function(){var t,r;for(r=[],i=t=0;16>t;i=++t)r.push(909522486^e[i]);return r}(),a=function(){var t,r;for(r=[],i=t=0;16>t;i=++t)r.push(1549556828^e[i]);return r}(),o=S(s.concat(t),64+r),S(a.concat(o),84)},S=function(t,e){var r,n,o,i,s,h,u,l,p,c,d,f,y,v,m,g,S,b;for(t[e>>2]|=1<<31-((3&e)<<3),t[(e+8>>6<<4)+15]=e<<3,g=Array(80),r=1732584193,o=-271733879,s=-1732584194,u=271733878,p=-1009589776,f=0,m=t.length;m>f;){for(n=r,i=o,h=s,l=u,c=p,y=b=0;80>b;y=++b)g[y]=16>y?t[f+y]:w(g[y-3]^g[y-8]^g[y-14]^g[y-16],1),20>y?(d=o&s|~o&u,v=1518500249):40>y?(d=o^s^u,v=1859775393):60>y?(d=o&s|o&u|s&u,v=-1894007588):(d=o^s^u,v=-899497514),S=a(a(w(r,5),d),a(a(p,g[y]),v)),p=u,u=s,s=w(o,30),o=r,r=S;r=a(r,n),o=a(o,i),s=a(s,h),u=a(u,l),p=a(p,c),f+=16}return[r,o,s,u,p]},w=function(t,e){return t<>>32-e},a=function(t,e){var r,n;return n=(65535&t)+(65535&e),r=(t>>16)+(e>>16)+(n>>16),r<<16|65535&n},h=function(t){var e,r,n,o,i;for(o="",e=0,n=4*t.length;n>e;)r=e,i=(255&t[r>>2]>>(3-(3&r)<<3))<<16,r+=1,i|=(255&t[r>>2]>>(3-(3&r)<<3))<<8,r+=1,i|=255&t[r>>2]>>(3-(3&r)<<3),o+=T[63&i>>18],o+=T[63&i>>12],e+=1,o+=e>=n?"=":T[63&i>>6],e+=1,o+=e>=n?"=":T[63&i],e+=1;return o},T="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",b=function(t){var e,r,n,o,i;for(e=[],n=255,r=o=0,i=t.length;i>=0?i>o:o>i;r=i>=0?++o:--o)e[r>>2]|=(t.charCodeAt(r)&n)<<(3-(3&r)<<3);return e},t.Oauth=function(){function e(t){this.key=this.k=null,this.secret=this.s=null,this.token=null,this.tokenSecret=null,this._appHash=null,this.reset(t)}return e.prototype.reset=function(t){var e,r,n,o;if(t.secret)this.k=this.key=t.key,this.s=this.secret=t.secret,this._appHash=null;else if(t.key)this.key=t.key,this.secret=null,n=u(m(this.key).split("|",2)[1]),o=n.split("?",2),e=o[0],r=o[1],this.k=decodeURIComponent(e),this.s=decodeURIComponent(r),this._appHash=null;else if(!this.k)throw Error("No API key supplied");return t.token?this.setToken(t.token,t.tokenSecret):this.setToken(null,"")},e.prototype.setToken=function(e,r){if(e&&!r)throw Error("No secret supplied with the user token");return this.token=e,this.tokenSecret=r||"",this.hmacKey=t.Xhr.urlEncodeValue(this.s)+"&"+t.Xhr.urlEncodeValue(r),null},e.prototype.authHeader=function(e,r,n){var o,i,s,a,h,u;this.addAuthParams(e,r,n),i=[];for(s in n)a=n[s],"oauth_"===s.substring(0,6)&&i.push(s);for(i.sort(),o=[],h=0,u=i.length;u>h;h++)s=i[h],o.push(t.Xhr.urlEncodeValue(s)+'="'+t.Xhr.urlEncodeValue(n[s])+'"'),delete n[s];return"OAuth "+o.join(",")},e.prototype.addAuthParams=function(t,e,r){return this.boilerplateParams(r),r.oauth_signature=this.signature(t,e,r),r},e.prototype.boilerplateParams=function(t){return t.oauth_consumer_key=this.k,t.oauth_nonce=this.nonce(),t.oauth_signature_method="HMAC-SHA1",this.token&&(t.oauth_token=this.token),t.oauth_timestamp=Math.floor(Date.now()/1e3),t.oauth_version="1.0",t},e.prototype.nonce=function(){return Date.now().toString(36)+Math.random().toString(36)},e.prototype.signature=function(e,r,n){var o;return o=e.toUpperCase()+"&"+t.Xhr.urlEncodeValue(r)+"&"+t.Xhr.urlEncodeValue(t.Xhr.urlEncode(n)),c(o,this.hmacKey)},e.prototype.appHash=function(){return this._appHash?this._appHash:this._appHash=d(this.k).replace(/\=/g,"")},e}(),null==Date.now&&(Date.now=function(){return(new Date).getTime()}),m=function(t,e){var r,n,o,i,s,a,h,l,p,c,d,y;for(e?(e=[encodeURIComponent(t),encodeURIComponent(e)].join("?"),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length/2;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(16*(15&t.charCodeAt(2*r))+(15&t.charCodeAt(2*r+1)));return o}()):(c=t.split("|",2),t=c[0],e=c[1],t=u(t),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(t.charCodeAt(r));return o}(),e=u(e)),i=function(){for(y=[],l=0;256>l;l++)y.push(l);return y}.apply(this),a=0,s=p=0;256>p;s=++p)a=(a+i[r]+t[s%t.length])%256,d=[i[a],i[s]],i[s]=d[0],i[a]=d[1];return s=a=0,o=function(){var t,r,o,u;for(u=[],h=t=0,r=e.length;r>=0?r>t:t>r;h=r>=0?++t:--t)s=(s+1)%256,a=(a+i[s])%256,o=[i[a],i[s]],i[s]=o[0],i[a]=o[1],n=i[(i[s]+i[a])%256],u.push(String.fromCharCode((n^e.charCodeAt(h))%256));return u}(),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(String.fromCharCode(t[r]));return o}(),[f(t.join("")),f(o.join(""))].join("|")},t.PulledChanges=function(){function e(e){var r;this.blankSlate=e.reset||!1,this.cursorTag=e.cursor,this.shouldPullAgain=e.has_more,this.shouldBackOff=!this.shouldPullAgain,this.changes=e.cursor&&e.cursor.length?function(){var n,o,i,s;for(i=e.entries,s=[],n=0,o=i.length;o>n;n++)r=i[n],s.push(t.PullChange.parse(r));return s}():[]}return e.parse=function(e){return e&&"object"==typeof e?new t.PulledChanges(e):e},e.prototype.blankSlate=void 0,e.prototype.cursorTag=void 0,e.prototype.changes=void 0,e.prototype.shouldPullAgain=void 0,e.prototype.shouldBackOff=void 0,e}(),t.PullChange=function(){function e(e){this.path=e[0],this.stat=t.Stat.parse(e[1]),this.stat?this.wasRemoved=!1:(this.stat=null,this.wasRemoved=!0)}return e.parse=function(e){return e&&"object"==typeof e?new t.PullChange(e):e},e.prototype.path=void 0,e.prototype.wasRemoved=void 0,e.prototype.stat=void 0,e}(),t.PublicUrl=function(){function e(t,e){this.url=t.url,this.expiresAt=new Date(Date.parse(t.expires)),this.isDirect=e===!0?!0:e===!1?!1:864e5>=Date.now()-this.expiresAt,this.isPreview=!this.isDirect}return e.parse=function(e,r){return e&&"object"==typeof e?new t.PublicUrl(e,r):e},e.prototype.url=void 0,e.prototype.expiresAt=void 0,e.prototype.isDirect=void 0,e.prototype.isPreview=void 0,e}(),t.CopyReference=function(){function e(t){"object"==typeof t?(this.tag=t.copy_ref,this.expiresAt=new Date(Date.parse(t.expires))):(this.tag=t,this.expiresAt=new Date)}return e.parse=function(e){return!e||"object"!=typeof e&&"string"!=typeof e?e:new t.CopyReference(e)},e.prototype.tag=void 0,e.prototype.expiresAt=void 0,e}(),t.Stat=function(){function e(t){var e,r,n,o;switch(this.path=t.path,"/"!==this.path.substring(0,1)&&(this.path="/"+this.path),e=this.path.length-1,e>=0&&"/"===this.path.substring(e)&&(this.path=this.path.substring(0,e)),r=this.path.lastIndexOf("/"),this.name=this.path.substring(r+1),this.isFolder=t.is_dir||!1,this.isFile=!this.isFolder,this.isRemoved=t.is_deleted||!1,this.typeIcon=t.icon,this.modifiedAt=(null!=(n=t.modified)?n.length:void 0)?new Date(Date.parse(t.modified)):null,this.clientModifiedAt=(null!=(o=t.client_mtime)?o.length:void 0)?new Date(Date.parse(t.client_mtime)):null,t.root){case"dropbox":this.inAppFolder=!1;break;case"app_folder":this.inAppFolder=!0;break;default:this.inAppFolder=null}this.size=t.bytes||0,this.humanSize=t.size||"",this.hasThumbnail=t.thumb_exists||!1,this.isFolder?(this.versionTag=t.hash,this.mimeType=t.mime_type||"inode/directory"):(this.versionTag=t.rev,this.mimeType=t.mime_type||"application/octet-stream")}return e.parse=function(e){return e&&"object"==typeof e?new t.Stat(e):e},e.prototype.path=null,e.prototype.name=null,e.prototype.inAppFolder=null,e.prototype.isFolder=null,e.prototype.isFile=null,e.prototype.isRemoved=null,e.prototype.typeIcon=null,e.prototype.versionTag=null,e.prototype.mimeType=null,e.prototype.size=null,e.prototype.humanSize=null,e.prototype.hasThumbnail=null,e.prototype.modifiedAt=null,e.prototype.clientModifiedAt=null,e}(),t.UserInfo=function(){function e(t){var e;this.name=t.display_name,this.email=t.email,this.countryCode=t.country||null,this.uid=""+t.uid,t.public_app_url?(this.publicAppUrl=t.public_app_url,e=this.publicAppUrl.length-1,e>=0&&"/"===this.publicAppUrl.substring(e)&&(this.publicAppUrl=this.publicAppUrl.substring(0,e))):this.publicAppUrl=null,this.referralUrl=t.referral_link,this.quota=t.quota_info.quota,this.privateBytes=t.quota_info.normal||0,this.sharedBytes=t.quota_info.shared||0,this.usedQuota=this.privateBytes+this.sharedBytes}return e.parse=function(e){return e&&"object"==typeof e?new t.UserInfo(e):e},e.prototype.name=null,e.prototype.email=null,e.prototype.countryCode=null,e.prototype.uid=null,e.prototype.referralUrl=null,e.prototype.publicAppUrl=null,e.prototype.quota=null,e.prototype.usedQuota=null,e.prototype.privateBytes=null,e.prototype.sharedBytes=null,e}(),"undefined"!=typeof window&&null!==window?(!window.XDomainRequest||"withCredentials"in new XMLHttpRequest?(s=window.XMLHttpRequest,i=!1,n=-1===window.navigator.userAgent.indexOf("Firefox")):(s=window.XDomainRequest,i=!0,n=!1),o=!0):(s=require("xmlhttprequest").XMLHttpRequest,i=!1,n=!1,o=!1),r="undefined"==typeof Uint8Array?null:new Uint8Array(0).__proto__.__proto__.constructor,t.Xhr=function(){function e(t,e){this.method=t,this.isGet="GET"===this.method,this.url=e,this.headers={},this.params=null,this.body=null,this.preflight=!(this.isGet||"POST"===this.method),this.signed=!1,this.responseType=null,this.callback=null,this.xhr=null}return e.Request=s,e.ieMode=i,e.canSendForms=n,e.doesPreflight=o,e.ArrayBufferView=r,e.prototype.setParams=function(t){if(this.signed)throw Error("setParams called after addOauthParams or addOauthHeader");if(this.params)throw Error("setParams cannot be called twice");return this.params=t,this},e.prototype.setCallback=function(t){return this.callback=t,this},e.prototype.signWithOauth=function(e){return t.Xhr.ieMode||t.Xhr.doesPreflight&&!this.preflight?this.addOauthParams(e):this.addOauthHeader(e)},e.prototype.addOauthParams=function(t){if(this.signed)throw Error("Request already has an OAuth signature");return this.params||(this.params={}),t.addAuthParams(this.method,this.url,this.params),this.signed=!0,this},e.prototype.addOauthHeader=function(t){if(this.signed)throw Error("Request already has an OAuth signature");return this.params||(this.params={}),this.signed=!0,this.setHeader("Authorization",t.authHeader(this.method,this.url,this.params))},e.prototype.setBody=function(t){if(this.isGet)throw Error("setBody cannot be called on GET requests");if(null!==this.body)throw Error("Request already has a body");return this.preflight||"undefined"!=typeof FormData&&t instanceof FormData||(this.preflight=!0),this.body=t,this},e.prototype.setResponseType=function(t){return this.responseType=t,this -},e.prototype.setHeader=function(t,e){var r;if(this.headers[t])throw r=this.headers[t],Error("HTTP header "+t+" already set to "+r);if("Content-Type"===t)throw Error("Content-Type is automatically computed based on setBody");return this.preflight=!0,this.headers[t]=e,this},e.prototype.setFileField=function(e,r,n,o){var i,s;if(null!==this.body)throw Error("Request already has a body");if(this.isGet)throw Error("paramsToBody cannot be called on GET requests");return"object"==typeof n&&"undefined"!=typeof Blob?("undefined"!=typeof ArrayBuffer&&null!==ArrayBuffer&&n instanceof ArrayBuffer&&(n=new Uint8Array(n)),t.Xhr.ArrayBufferView&&n instanceof t.Xhr.ArrayBufferView&&(o||(o="application/octet-stream"),n=new Blob([n],{type:o})),"undefined"!=typeof File&&n instanceof File&&(n=new Blob([n],{type:n.type})),s=n instanceof Blob):s=!1,s?(this.body=new FormData,this.body.append(e,n,r)):(o||(o="application/octet-stream"),i=this.multipartBoundary(),this.headers["Content-Type"]="multipart/form-data; boundary="+i,this.body=["--",i,"\r\n",'Content-Disposition: form-data; name="',e,'"; filename="',r,'"\r\n',"Content-Type: ",o,"\r\n","Content-Transfer-Encoding: binary\r\n\r\n",n,"\r\n","--",i,"--","\r\n"].join(""))},e.prototype.multipartBoundary=function(){return[Date.now().toString(36),Math.random().toString(36)].join("----")},e.prototype.paramsToUrl=function(){var e;return this.params&&(e=t.Xhr.urlEncode(this.params),0!==e.length&&(this.url=[this.url,"?",e].join("")),this.params=null),this},e.prototype.paramsToBody=function(){if(this.params){if(null!==this.body)throw Error("Request already has a body");if(this.isGet)throw Error("paramsToBody cannot be called on GET requests");this.headers["Content-Type"]="application/x-www-form-urlencoded",this.body=t.Xhr.urlEncode(this.params),this.params=null}return this},e.prototype.prepare=function(){var e,r,n,o,i=this;if(r=t.Xhr.ieMode,this.isGet||null!==this.body||r?(this.paramsToUrl(),null!==this.body&&"string"==typeof this.body&&(this.headers["Content-Type"]="text/plain; charset=utf8")):this.paramsToBody(),this.xhr=new t.Xhr.Request,r?(this.xhr.onload=function(){return i.onLoad()},this.xhr.onerror=function(){return i.onError()}):this.xhr.onreadystatechange=function(){return i.onReadyStateChange()},this.xhr.open(this.method,this.url,!0),!r){o=this.headers;for(e in o)_.call(o,e)&&(n=o[e],this.xhr.setRequestHeader(e,n))}return this.responseType&&("b"===this.responseType?this.xhr.overrideMimeType&&this.xhr.overrideMimeType("text/plain; charset=x-user-defined"):this.xhr.responseType=this.responseType),this},e.prototype.send=function(e){var r;if(this.callback=e||this.callback,null!==this.body){r=this.body,t.Xhr.ArrayBufferView&&r instanceof ArrayBuffer&&(r=new Uint8Array(r));try{this.xhr.send(r)}catch(n){if(!("undefined"!=typeof Blob&&t.Xhr.ArrayBufferView&&r instanceof t.Xhr.ArrayBufferView))throw n;r=new Blob([r],{type:"application/octet-stream"}),this.xhr.send(r)}}else this.xhr.send();return this},e.urlEncode=function(t){var e,r,n;e=[];for(r in t)n=t[r],e.push(this.urlEncodeValue(r)+"="+this.urlEncodeValue(n));return e.sort().join("&")},e.urlEncodeValue=function(t){return encodeURIComponent(""+t).replace(/\!/g,"%21").replace(/'/g,"%27").replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/\*/g,"%2A")},e.urlDecode=function(t){var e,r,n,o,i,s;for(r={},s=t.split("&"),o=0,i=s.length;i>o;o++)n=s[o],e=n.split("="),r[decodeURIComponent(e[0])]=decodeURIComponent(e[1]);return r},e.prototype.onReadyStateChange=function(){var e,r,n,o,i,s,a,h,u;if(4!==this.xhr.readyState)return!0;if(200>this.xhr.status||this.xhr.status>=300)return e=new t.ApiError(this.xhr,this.method,this.url),this.callback(e),!0;if(s=this.xhr.getResponseHeader("x-dropbox-metadata"),null!=s?s.length:void 0)try{i=JSON.parse(s)}catch(l){i=void 0}else i=void 0;if(this.responseType){if("b"===this.responseType){for(n=null!=this.xhr.responseText?this.xhr.responseText:this.xhr.response,r=[],o=h=0,u=n.length;u>=0?u>h:h>u;o=u>=0?++h:--h)r.push(String.fromCharCode(255&n.charCodeAt(o)));a=r.join(""),this.callback(null,a,i)}else this.callback(null,this.xhr.response,i);return!0}switch(a=null!=this.xhr.responseText?this.xhr.responseText:this.xhr.response,this.xhr.getResponseHeader("Content-Type")){case"application/x-www-form-urlencoded":this.callback(null,t.Xhr.urlDecode(a),i);break;case"application/json":case"text/javascript":this.callback(null,JSON.parse(a),i);break;default:this.callback(null,a,i)}return!0},e.prototype.onLoad=function(){var e;switch(e=this.xhr.responseText,this.xhr.contentType){case"application/x-www-form-urlencoded":this.callback(null,t.Xhr.urlDecode(e),void 0);break;case"application/json":case"text/javascript":this.callback(null,JSON.parse(e),void 0);break;default:this.callback(null,e,void 0)}return!0},e.prototype.onError=function(){var e;return e=new t.ApiError(this.xhr,this.method,this.url),this.callback(e),!0},e}(),null!=("undefined"!=typeof module&&null!==module?module.exports:void 0))module.exports=t;else{if("undefined"==typeof window||null===window)throw Error("This library only supports node.js and modern browsers.");window.Dropbox=t}t.atob=u,t.btoa=f,t.hmac=c,t.sha1=d,t.encodeKey=m}).call(this); \ No newline at end of file +(function(){var t,e,r,n,o,i,s,a,h,u,l,p,c,d,f,y,v,m,g,w,S,b,_,E,C,x,R={}.hasOwnProperty,T=function(t,e){function r(){this.constructor=t}for(var n in e)R.call(e,n)&&(t[n]=e[n]);return r.prototype=e.prototype,t.prototype=new r,t.__super__=e.prototype,t},k=[].indexOf||function(t){for(var e=0,r=this.length;r>e;e++)if(e in this&&this[e]===t)return e;return-1};if(t=function(){function t(t){this.client=new n(t)}return t}(),t.ApiError=function(){function t(t,e,r){var n;if(this.method=e,this.url=r,this.status=t.status,t.responseType)try{n=t.response||t.responseText}catch(o){try{n=t.responseText}catch(o){n=null}}else try{n=t.responseText}catch(o){n=null}if(n)try{this.responseText=""+n,this.response=JSON.parse(n)}catch(o){this.response=null}else this.responseText="(no response)",this.response=null}return t.prototype.status=void 0,t.prototype.method=void 0,t.prototype.url=void 0,t.prototype.responseText=void 0,t.prototype.response=void 0,t.prototype.toString=function(){return"Dropbox API error "+this.status+" from "+this.method+" "+this.url+" :: "+this.responseText},t.prototype.inspect=function(){return""+this},t}(),"undefined"!=typeof window&&null!==window?window.atob&&window.btoa?(c=function(t){return window.atob(t)},m=function(t){return window.btoa(t)}):(f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",g=function(t,e,r){var n,o;for(o=3-e,t<<=8*o,n=3;n>=o;)r.push(f.charAt(63&t>>6*n)),n-=1;for(n=e;3>n;)r.push("="),n+=1;return null},d=function(t,e,r){var n,o;for(o=4-e,t<<=6*o,n=2;n>=o;)r.push(String.fromCharCode(255&t>>8*n)),n-=1;return null},m=function(t){var e,r,n,o,i,s;for(o=[],e=0,r=0,n=i=0,s=t.length;s>=0?s>i:i>s;n=s>=0?++i:--i)e=e<<8|t.charCodeAt(n),r+=1,3===r&&(g(e,r,o),e=r=0);return r>0&&g(e,r,o),o.join("")},c=function(t){var e,r,n,o,i,s,a;for(i=[],e=0,n=0,o=s=0,a=t.length;(a>=0?a>s:s>a)&&(r=t.charAt(o),"="!==r);o=a>=0?++s:--s)e=e<<6|f.indexOf(r),n+=1,4===n&&(d(e,n,i),e=n=0);return n>0&&d(e,n,i),i.join("")}):(c=function(t){var e,r;return e=new Buffer(t,"base64"),function(){var t,n,o;for(o=[],r=t=0,n=e.length;n>=0?n>t:t>n;r=n>=0?++t:--t)o.push(String.fromCharCode(e[r]));return o}().join("")},m=function(t){var e,r;return e=new Buffer(function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(t.charCodeAt(r));return o}()),e.toString("base64")}),t.Client=function(){function e(e){this.sandbox=e.sandbox||!1,this.apiServer=e.server||this.defaultApiServer(),this.authServer=e.authServer||this.defaultAuthServer(),this.fileServer=e.fileServer||this.defaultFileServer(),this.downloadServer=e.downloadServer||this.defaultDownloadServer(),this.onXhr=new t.EventSource({cancelable:!0}),this.onError=new t.EventSource,this.onAuthStateChange=new t.EventSource,this.oauth=new t.Oauth(e),this.driver=null,this.filter=null,this.uid=null,this.authState=null,this.authError=null,this._credentials=null,this.setCredentials(e),this.setupUrls()}return e.prototype.authDriver=function(t){return this.driver=t,this},e.prototype.onXhr=null,e.prototype.onError=null,e.prototype.onAuthStateChange=null,e.prototype.dropboxUid=function(){return this.uid},e.prototype.credentials=function(){return this._credentials||this.computeCredentials(),this._credentials},e.prototype.authenticate=function(t){var e,r,o=this;if(e=null,!this.driver&&this.authState!==n.DONE)throw Error("Call authDriver to set an authentication driver");return r=function(){var i;if(e!==o.authState&&(null!==e&&o.onAuthStateChange.dispatch(o),e=o.authState,o.driver&&o.driver.onAuthStateChange))return o.driver.onAuthStateChange(o,r);switch(o.authState){case n.RESET:return o.requestToken(function(t,e){var i,s;return t?(o.authError=t,o.authState=n.ERROR):(i=e.oauth_token,s=e.oauth_token_secret,o.oauth.setToken(i,s),o.authState=n.REQUEST),o._credentials=null,r()});case n.REQUEST:return i=o.authorizeUrl(o.oauth.token),o.driver.doAuthorize(i,o.oauth.token,o.oauth.tokenSecret,function(){return o.authState=n.AUTHORIZED,o._credentials=null,r()});case n.AUTHORIZED:return o.getAccessToken(function(t,e){return t?(o.authError=t,o.authState=n.ERROR):(o.oauth.setToken(e.oauth_token,e.oauth_token_secret),o.uid=e.uid,o.authState=n.DONE),o._credentials=null,r()});case n.DONE:return t(null,o);case n.SIGNED_OFF:return o.authState=n.RESET,o.reset(),r();case n.ERROR:return t(o.authError)}},r(),this},e.prototype.isAuthenticated=function(){return this.authState===n.DONE},e.prototype.signOut=function(e){var r,o=this;return r=new t.Xhr("POST",this.urls.signOut),r.signWithOauth(this.oauth),this.dispatchXhr(r,function(t){return t?e(t):(o.authState=n.RESET,o.reset(),o.authState=n.SIGNED_OFF,o.onAuthStateChange.dispatch(o),o.driver.onAuthStateChange?o.driver.onAuthStateChange(o,function(){return e(t)}):e(t))})},e.prototype.signOff=function(t){return this.signOut(t)},e.prototype.getUserInfo=function(e,r){var n,o;return r||"function"!=typeof e||(r=e,e=null),n=!1,e&&e.httpCache&&(n=!0),o=new t.Xhr("GET",this.urls.accountInfo),o.signWithOauth(this.oauth,n),this.dispatchXhr(o,function(e,n){return r(e,t.UserInfo.parse(n),n)})},e.prototype.readFile=function(e,r,n){var o,i,s,a,h,u,l;return n||"function"!=typeof r||(n=r,r=null),i={},u=null,a=null,o=!1,r&&(r.versionTag?i.rev=r.versionTag:r.rev&&(i.rev=r.rev),r.arrayBuffer?u="arraybuffer":r.blob?u="blob":r.binary&&(u="b"),r.length?(null!=r.start?(h=r.start,s=r.start+r.length-1):(h="",s=r.length),a="bytes="+h+"-"+s):null!=r.start&&(a="bytes="+r.start+"-"),r.httpCache&&(o=!0)),l=new t.Xhr("GET",""+this.urls.getFile+"/"+this.urlEncodePath(e)),l.setParams(i).signWithOauth(this.oauth,o),l.setResponseType(u),a&&l.setHeader("Range",a),this.dispatchXhr(l,function(e,r,o){return n(e,r,t.Stat.parse(o))})},e.prototype.writeFile=function(e,r,n,o){var i;return o||"function"!=typeof n||(o=n,n=null),i=t.Xhr.canSendForms&&"object"==typeof r,i?this.writeFileUsingForm(e,r,n,o):this.writeFileUsingPut(e,r,n,o)},e.prototype.writeFileUsingForm=function(e,r,n,o){var i,s,a,h;return a=e.lastIndexOf("/"),-1===a?(i=e,e=""):(i=e.substring(a),e=e.substring(0,a)),s={file:i},n&&(n.noOverwrite&&(s.overwrite="false"),n.lastVersionTag?s.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(s.parent_rev=n.parentRev||n.parent_rev)),h=new t.Xhr("POST",""+this.urls.postFile+"/"+this.urlEncodePath(e)),h.setParams(s).signWithOauth(this.oauth).setFileField("file",i,r,"application/octet-stream"),delete s.file,this.dispatchXhr(h,function(e,r){return o(e,t.Stat.parse(r))})},e.prototype.writeFileUsingPut=function(e,r,n,o){var i,s;return i={},n&&(n.noOverwrite&&(i.overwrite="false"),n.lastVersionTag?i.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(i.parent_rev=n.parentRev||n.parent_rev)),s=new t.Xhr("POST",""+this.urls.putFile+"/"+this.urlEncodePath(e)),s.setBody(r).setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return o(e,t.Stat.parse(r))})},e.prototype.resumableUploadStep=function(e,r,n){var o,i;return r?(o={offset:r.offset},r.tag&&(o.upload_id=r.tag)):o={offset:0},i=new t.Xhr("POST",this.urls.chunkedUpload),i.setBody(e).setParams(o).signWithOauth(this.oauth),this.dispatchXhr(i,function(e,r){return e&&400===e.status&&e.response.upload_id&&e.response.offset?n(null,t.UploadCursor.parse(e.response)):n(e,t.UploadCursor.parse(r))})},e.prototype.resumableUploadFinish=function(e,r,n,o){var i,s;return o||"function"!=typeof n||(o=n,n=null),i={upload_id:r.tag},n&&(n.lastVersionTag?i.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(i.parent_rev=n.parentRev||n.parent_rev),n.noOverwrite&&(i.autorename=!0)),s=new t.Xhr("POST",""+this.urls.commitChunkedUpload+"/"+this.urlEncodePath(e)),s.setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return o(e,t.Stat.parse(r))})},e.prototype.stat=function(e,r,n){var o,i,s;return n||"function"!=typeof r||(n=r,r=null),i={},o=!1,r&&(null!=r.version&&(i.rev=r.version),(r.removed||r.deleted)&&(i.include_deleted="true"),r.readDir&&(i.list="true",r.readDir!==!0&&(i.file_limit=""+r.readDir)),r.cacheHash&&(i.hash=r.cacheHash),r.httpCache&&(o=!0)),i.include_deleted||(i.include_deleted="false"),i.list||(i.list="false"),s=new t.Xhr("GET",""+this.urls.metadata+"/"+this.urlEncodePath(e)),s.setParams(i).signWithOauth(this.oauth,o),this.dispatchXhr(s,function(e,r){var o,i,s;return s=t.Stat.parse(r),o=(null!=r?r.contents:void 0)?function(){var e,n,o,s;for(o=r.contents,s=[],e=0,n=o.length;n>e;e++)i=o[e],s.push(t.Stat.parse(i));return s}():void 0,n(e,s,o)})},e.prototype.readdir=function(t,e,r){var n;return r||"function"!=typeof e||(r=e,e=null),n={readDir:!0},e&&(null!=e.limit&&(n.readDir=e.limit),e.versionTag&&(n.versionTag=e.versionTag),(e.removed||e.deleted)&&(n.removed=e.removed||e.deleted),e.httpCache&&(n.httpCache=e.httpCache)),this.stat(t,n,function(t,e,n){var o,i;return o=n?function(){var t,e,r;for(r=[],t=0,e=n.length;e>t;t++)i=n[t],r.push(i.name);return r}():null,r(t,o,e,n)})},e.prototype.metadata=function(t,e,r){return this.stat(t,e,r)},e.prototype.makeUrl=function(e,r,n){var o,i,s,a,h;return n||"function"!=typeof r||(n=r,r=null),i=r&&(r["long"]||r.longUrl||r.downloadHack)?{short_url:"false"}:{},e=this.urlEncodePath(e),s=""+this.urls.shares+"/"+e,o=!1,a=!1,r&&(r.downloadHack?(o=!0,a=!0):r.download&&(o=!0,s=""+this.urls.media+"/"+e)),h=new t.Xhr("POST",s).setParams(i).signWithOauth(this.oauth),this.dispatchXhr(h,function(e,r){return a&&r&&r.url&&(r.url=r.url.replace(this.authServer,this.downloadServer)),n(e,t.PublicUrl.parse(r,o))})},e.prototype.history=function(e,r,n){var o,i,s;return n||"function"!=typeof r||(n=r,r=null),i={},o=!1,r&&(null!=r.limit&&(i.rev_limit=r.limit),r.httpCache&&(o=!0)),s=new t.Xhr("GET",""+this.urls.revisions+"/"+this.urlEncodePath(e)),s.setParams(i).signWithOauth(this.oauth,o),this.dispatchXhr(s,function(e,r){var o,i;return i=r?function(){var e,n,i;for(i=[],e=0,n=r.length;n>e;e++)o=r[e],i.push(t.Stat.parse(o));return i}():void 0,n(e,i)})},e.prototype.revisions=function(t,e,r){return this.history(t,e,r)},e.prototype.thumbnailUrl=function(t,e){var r;return r=this.thumbnailXhr(t,e),r.paramsToUrl().url},e.prototype.readThumbnail=function(e,r,n){var o,i;return n||"function"!=typeof r||(n=r,r=null),o="b",r&&r.blob&&(o="blob"),i=this.thumbnailXhr(e,r),i.setResponseType(o),this.dispatchXhr(i,function(e,r,o){return n(e,r,t.Stat.parse(o))})},e.prototype.thumbnailXhr=function(e,r){var n,o;return n={},r&&(r.format?n.format=r.format:r.png&&(n.format="png"),r.size&&(n.size=r.size)),o=new t.Xhr("GET",""+this.urls.thumbnails+"/"+this.urlEncodePath(e)),o.setParams(n).signWithOauth(this.oauth)},e.prototype.revertFile=function(e,r,n){var o;return o=new t.Xhr("POST",""+this.urls.restore+"/"+this.urlEncodePath(e)),o.setParams({rev:r}).signWithOauth(this.oauth),this.dispatchXhr(o,function(e,r){return n(e,t.Stat.parse(r))})},e.prototype.restore=function(t,e,r){return this.revertFile(t,e,r)},e.prototype.findByName=function(e,r,n,o){var i,s,a;return o||"function"!=typeof n||(o=n,n=null),s={query:r},i=!1,n&&(null!=n.limit&&(s.file_limit=n.limit),(n.removed||n.deleted)&&(s.include_deleted=!0),n.httpCache&&(i=!0)),a=new t.Xhr("GET",""+this.urls.search+"/"+this.urlEncodePath(e)),a.setParams(s).signWithOauth(this.oauth,i),this.dispatchXhr(a,function(e,r){var n,i;return i=r?function(){var e,o,i;for(i=[],e=0,o=r.length;o>e;e++)n=r[e],i.push(t.Stat.parse(n));return i}():void 0,o(e,i)})},e.prototype.search=function(t,e,r,n){return this.findByName(t,e,r,n)},e.prototype.makeCopyReference=function(e,r){var n;return n=new t.Xhr("GET",""+this.urls.copyRef+"/"+this.urlEncodePath(e)),n.signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r(e,t.CopyReference.parse(n))})},e.prototype.copyRef=function(t,e){return this.makeCopyReference(t,e)},e.prototype.pullChanges=function(e,r){var n,o;return r||"function"!=typeof e||(r=e,e=null),n=e?e.cursorTag?{cursor:e.cursorTag}:{cursor:e}:{},o=new t.Xhr("POST",this.urls.delta),o.setParams(n).signWithOauth(this.oauth),this.dispatchXhr(o,function(e,n){return r(e,t.PulledChanges.parse(n))})},e.prototype.delta=function(t,e){return this.pullChanges(t,e)},e.prototype.mkdir=function(e,r){var n;return n=new t.Xhr("POST",this.urls.fileopsCreateFolder),n.setParams({root:this.fileRoot,path:this.normalizePath(e)}).signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r(e,t.Stat.parse(n))})},e.prototype.remove=function(e,r){var n;return n=new t.Xhr("POST",this.urls.fileopsDelete),n.setParams({root:this.fileRoot,path:this.normalizePath(e)}).signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r(e,t.Stat.parse(n))})},e.prototype.unlink=function(t,e){return this.remove(t,e)},e.prototype["delete"]=function(t,e){return this.remove(t,e)},e.prototype.copy=function(e,r,n){var o,i,s;return n||"function"!=typeof o||(n=o,o=null),i={root:this.fileRoot,to_path:this.normalizePath(r)},e instanceof t.CopyReference?i.from_copy_ref=e.tag:i.from_path=this.normalizePath(e),s=new t.Xhr("POST",this.urls.fileopsCopy),s.setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return n(e,t.Stat.parse(r))})},e.prototype.move=function(e,r,n){var o,i;return n||"function"!=typeof o||(n=o,o=null),i=new t.Xhr("POST",this.urls.fileopsMove),i.setParams({root:this.fileRoot,from_path:this.normalizePath(e),to_path:this.normalizePath(r)}).signWithOauth(this.oauth),this.dispatchXhr(i,function(e,r){return n(e,t.Stat.parse(r))})},e.prototype.reset=function(){var t;return this.uid=null,this.oauth.setToken(null,""),t=this.authState,this.authState=n.RESET,t!==this.authState&&this.onAuthStateChange.dispatch(this),this.authError=null,this._credentials=null,this},e.prototype.setCredentials=function(t){var e;return e=this.authState,this.oauth.reset(t),this.uid=t.uid||null,this.authState=t.authState?t.authState:t.token?n.DONE:n.RESET,this.authError=null,this._credentials=null,e!==this.authState&&this.onAuthStateChange.dispatch(this),this},e.prototype.appHash=function(){return this.oauth.appHash()},e.prototype.setupUrls=function(){return this.fileRoot=this.sandbox?"sandbox":"dropbox",this.urls={requestToken:""+this.apiServer+"/1/oauth/request_token",authorize:""+this.authServer+"/1/oauth/authorize",accessToken:""+this.apiServer+"/1/oauth/access_token",signOut:""+this.apiServer+"/1/unlink_access_token",accountInfo:""+this.apiServer+"/1/account/info",getFile:""+this.fileServer+"/1/files/"+this.fileRoot,postFile:""+this.fileServer+"/1/files/"+this.fileRoot,putFile:""+this.fileServer+"/1/files_put/"+this.fileRoot,metadata:""+this.apiServer+"/1/metadata/"+this.fileRoot,delta:""+this.apiServer+"/1/delta",revisions:""+this.apiServer+"/1/revisions/"+this.fileRoot,restore:""+this.apiServer+"/1/restore/"+this.fileRoot,search:""+this.apiServer+"/1/search/"+this.fileRoot,shares:""+this.apiServer+"/1/shares/"+this.fileRoot,media:""+this.apiServer+"/1/media/"+this.fileRoot,copyRef:""+this.apiServer+"/1/copy_ref/"+this.fileRoot,thumbnails:""+this.fileServer+"/1/thumbnails/"+this.fileRoot,chunkedUpload:""+this.fileServer+"/1/chunked_upload",commitChunkedUpload:""+this.fileServer+"/1/commit_chunked_upload/"+this.fileRoot,fileopsCopy:""+this.apiServer+"/1/fileops/copy",fileopsCreateFolder:""+this.apiServer+"/1/fileops/create_folder",fileopsDelete:""+this.apiServer+"/1/fileops/delete",fileopsMove:""+this.apiServer+"/1/fileops/move"}},e.prototype.authState=null,e.ERROR=0,e.RESET=1,e.REQUEST=2,e.AUTHORIZED=3,e.DONE=4,e.SIGNED_OFF=5,e.prototype.urlEncodePath=function(e){return t.Xhr.urlEncodeValue(this.normalizePath(e)).replace(/%2F/gi,"/")},e.prototype.normalizePath=function(t){var e;if("/"===t.substring(0,1)){for(e=1;"/"===t.substring(e,e+1);)e+=1;return t.substring(e)}return t},e.prototype.requestToken=function(e){var r;return r=new t.Xhr("POST",this.urls.requestToken).signWithOauth(this.oauth),this.dispatchXhr(r,e)},e.prototype.authorizeUrl=function(e){var r;return r={oauth_token:e,oauth_callback:this.driver.url()},""+this.urls.authorize+"?"+t.Xhr.urlEncode(r)},e.prototype.getAccessToken=function(e){var r;return r=new t.Xhr("POST",this.urls.accessToken).signWithOauth(this.oauth),this.dispatchXhr(r,e)},e.prototype.dispatchXhr=function(t,e){var r;return t.setCallback(e),t.onError=this.onError,t.prepare(),r=t.xhr,this.onXhr.dispatch(t)&&t.send(),r},e.prototype.defaultApiServer=function(){return"https://api.dropbox.com"},e.prototype.defaultAuthServer=function(){return this.apiServer.replace("api.","www.")},e.prototype.defaultFileServer=function(){return this.apiServer.replace("api.","api-content.")},e.prototype.defaultDownloadServer=function(){return this.apiServer.replace("api.","dl.")},e.prototype.computeCredentials=function(){var t;return t={key:this.oauth.key,sandbox:this.sandbox},this.oauth.secret&&(t.secret=this.oauth.secret),this.oauth.token&&(t.token=this.oauth.token,t.tokenSecret=this.oauth.tokenSecret),this.uid&&(t.uid=this.uid),this.authState!==n.ERROR&&this.authState!==n.RESET&&this.authState!==n.DONE&&this.authState!==n.SIGNED_OFF&&(t.authState=this.authState),this.apiServer!==this.defaultApiServer()&&(t.server=this.apiServer),this.authServer!==this.defaultAuthServer()&&(t.authServer=this.authServer),this.fileServer!==this.defaultFileServer()&&(t.fileServer=this.fileServer),this.downloadServer!==this.defaultDownloadServer()&&(t.downloadServer=this.downloadServer),this._credentials=t},e}(),n=t.Client,t.AuthDriver=function(){function t(){}return t.prototype.url=function(){return"https://some.url"},t.prototype.doAuthorize=function(t,e,r,n){return n("oauth-token")},t.prototype.onAuthStateChange=function(t,e){return e()},t}(),t.Drivers={},t.Drivers.BrowserBase=function(){function t(t){this.rememberUser=(null!=t?t.rememberUser:void 0)||!1,this.scope=(null!=t?t.scope:void 0)||"default",this.storageKey=null}return t.prototype.onAuthStateChange=function(t,e){var r=this;switch(this.setStorageKey(t),t.authState){case n.RESET:return this.loadCredentials(function(n){return n?n.authState?(t.setCredentials(n),e()):r.rememberUser?(t.setCredentials(n),t.getUserInfo(function(n){return n?(t.reset(),r.forgetCredentials(e)):e()})):(r.forgetCredentials(),e()):e()});case n.REQUEST:return this.storeCredentials(t.credentials(),e);case n.DONE:return this.rememberUser?this.storeCredentials(t.credentials(),e):this.forgetCredentials(e);case n.SIGNED_OFF:return this.forgetCredentials(e);case n.ERROR:return this.forgetCredentials(e);default:return e(),this}},t.prototype.setStorageKey=function(t){return this.storageKey="dropbox-auth:"+this.scope+":"+t.appHash(),this},t.prototype.storeCredentials=function(t,e){return localStorage.setItem(this.storageKey,JSON.stringify(t)),e(),this},t.prototype.loadCredentials=function(t){var e;if(e=localStorage.getItem(this.storageKey),!e)return t(null),this;try{t(JSON.parse(e))}catch(r){t(null)}return this},t.prototype.forgetCredentials=function(t){return localStorage.removeItem(this.storageKey),t(),this},t.currentLocation=function(){return window.location.href},t}(),t.Drivers.Redirect=function(e){function r(t){r.__super__.constructor.call(this,t),this.useQuery=(null!=t?t.useQuery:void 0)||!1,this.receiverUrl=this.computeUrl(t),this.tokenRe=RegExp("(#|\\?|&)oauth_token=([^&#]+)(&|#|$)")}return T(r,e),r.prototype.onAuthStateChange=function(t,e){var o,i=this;return o=function(){return function(){return r.__super__.onAuthStateChange.call(i,t,e)}}(),this.setStorageKey(t),t.authState===n.RESET?this.loadCredentials(function(t){return t&&t.authState?t.token===i.locationToken()&&t.authState===n.REQUEST?(t.authState=n.AUTHORIZED,i.storeCredentials(t,o)):i.forgetCredentials(o):o()}):o()},r.prototype.url=function(){return this.receiverUrl},r.prototype.doAuthorize=function(t){return window.location.assign(t)},r.prototype.computeUrl=function(){var e,r,n,o;return o="_dropboxjs_scope="+encodeURIComponent(this.scope),r=t.Drivers.BrowserBase.currentLocation(),-1===r.indexOf("#")?e=null:(n=r.split("#",2),r=n[0],e=n[1]),this.useQuery?r+=-1===r.indexOf("?")?"?"+o:"&"+o:e="?"+o,e?r+"#"+e:r},r.prototype.locationToken=function(){var e,r,n;return e=t.Drivers.BrowserBase.currentLocation(),n="_dropboxjs_scope="+encodeURIComponent(this.scope)+"&",-1===("function"==typeof e.indexOf?e.indexOf(n):void 0)?null:(r=this.tokenRe.exec(e),r?decodeURIComponent(r[2]):null)},r}(t.Drivers.BrowserBase),t.Drivers.Popup=function(e){function r(t){r.__super__.constructor.call(this,t),this.receiverUrl=this.computeUrl(t),this.tokenRe=RegExp("(#|\\?|&)oauth_token=([^&#]+)(&|#|$)")}return T(r,e),r.prototype.onAuthStateChange=function(t,e){var o,i=this;return o=function(){return function(){return r.__super__.onAuthStateChange.call(i,t,e)}}(),this.setStorageKey(t),t.authState===n.RESET?this.loadCredentials(function(t){return t&&t.authState?i.forgetCredentials(o):o()}):o()},r.prototype.doAuthorize=function(t,e,r,n){return this.listenForMessage(e,n),this.openWindow(t)},r.prototype.url=function(){return this.receiverUrl},r.prototype.computeUrl=function(e){var r;if(e){if(e.receiverUrl)return e.noFragment||-1!==e.receiverUrl.indexOf("#")?e.receiverUrl:e.receiverUrl+"#";if(e.receiverFile)return r=t.Drivers.BrowserBase.currentLocation().split("/"),r[r.length-1]=e.receiverFile,e.noFragment?r.join("/"):r.join("/")+"#"}return t.Drivers.BrowserBase.currentLocation()},r.prototype.openWindow=function(t){return window.open(t,"_dropboxOauthSigninWindow",this.popupWindowSpec(980,700))},r.prototype.popupWindowSpec=function(t,e){var r,n,o,i,s,a,h,u,l,p;return s=null!=(h=window.screenX)?h:window.screenLeft,a=null!=(u=window.screenY)?u:window.screenTop,i=null!=(l=window.outerWidth)?l:document.documentElement.clientWidth,r=null!=(p=window.outerHeight)?p:document.documentElement.clientHeight,n=Math.round(s+(i-t)/2),o=Math.round(a+(r-e)/2.5),s>n&&(n=s),a>o&&(o=a),"width="+t+",height="+e+","+("left="+n+",top="+o)+"dialog=yes,dependent=yes,scrollbars=yes,location=yes"},r.prototype.listenForMessage=function(t,e){var r,n=this;return r=function(o){var i;return i=n.tokenRe.exec(""+o.data),i&&decodeURIComponent(i[2])===t?(window.removeEventListener("message",r),e()):void 0},window.addEventListener("message",r,!1)},r.oauthReceiver=function(){return window.addEventListener("load",function(){var t;if(t=window.opener,window.parent!==window.top&&(t||(t=window.parent)),t){try{t.postMessage(window.location.href,"*")}catch(e){}return window.close()}})},r}(t.Drivers.BrowserBase),e=null,r=null,"undefined"!=typeof chrome&&null!==chrome&&(chrome.runtime&&(chrome.runtime.onMessage&&(e=chrome.runtime.onMessage),chrome.runtime.sendMessage&&(r=function(t){return chrome.runtime.sendMessage(t)})),chrome.extension&&(chrome.extension.onMessage&&(e||(e=chrome.extension.onMessage)),chrome.extension.sendMessage&&(r||(r=function(t){return chrome.extension.sendMessage(t)}))),e||function(){var e,r;return r=function(e){return e.Dropbox?(t.Drivers.Chrome.prototype.onMessage=e.Dropbox.Drivers.Chrome.onMessage,t.Drivers.Chrome.prototype.sendMessage=e.Dropbox.Drivers.Chrome.sendMessage):(e.Dropbox=t,t.Drivers.Chrome.prototype.onMessage=new t.EventSource,t.Drivers.Chrome.prototype.sendMessage=function(e){return t.Drivers.Chrome.prototype.onMessage.dispatch(e)})},chrome.extension&&chrome.extension.getBackgroundPage&&(e=chrome.extension.getBackgroundPage())?r(e):chrome.runtime&&chrome.runtime.getBackgroundPage?chrome.runtime.getBackgroundPage(function(t){return r(t)}):void 0}()),t.Drivers.Chrome=function(){function n(t){var e,r;e=t&&t.receiverPath||"chrome_oauth_receiver.html",this.receiverUrl=this.expandUrl(e),this.tokenRe=RegExp("(#|\\?|&)oauth_token=([^&#]+)(&|#|$)"),r=t&&t.scope||"default",this.storageKey="dropbox_js_"+r+"_credentials"}return n.prototype.onMessage=e,n.prototype.sendMessage=r,n.prototype.expandUrl=function(t){return chrome.runtime&&chrome.runtime.getURL?chrome.runtime.getURL(t):chrome.extension&&chrome.extension.getURL?chrome.extension.getURL(t):t},n.prototype.onAuthStateChange=function(e,r){var n=this;switch(e.authState){case t.Client.RESET:return this.loadCredentials(function(t){if(t){if(t.authState)return n.forgetCredentials(r);e.setCredentials(t)}return r()});case t.Client.DONE:return this.storeCredentials(e.credentials(),r);case t.Client.SIGNED_OFF:return this.forgetCredentials(r);case t.Client.ERROR:return this.forgetCredentials(r);default:return r()}},n.prototype.doAuthorize=function(t,e,r,n){var o;return o={handle:null},this.listenForMessage(e,o,n),this.openWindow(t,function(t){return o.handle=t})},n.prototype.openWindow=function(t,e){return chrome.tabs&&chrome.tabs.create?(chrome.tabs.create({url:t,active:!0,pinned:!1},function(t){return e(t)}),this):chrome.app&&chrome.app.window&&chrome.app.window.create?(chrome.app.window.create(t,{frame:"none",id:"dropbox-auth"},function(t){return e(t)}),this):this},n.prototype.closeWindow=function(t){return chrome.tabs&&chrome.tabs.remove&&t.id?(chrome.tabs.remove(t.id),this):chrome.app&&chrome.app.window&&t.close?(t.close(),this):this},n.prototype.url=function(){return this.receiverUrl},n.prototype.listenForMessage=function(t,e,r){var n,o=this;return n=function(i,s){var a;if(!s||!s.tab||s.tab.url.substring(0,o.receiverUrl.length)===o.receiverUrl)return a=o.tokenRe.exec(i.dropbox_oauth_receiver_href||""),a&&decodeURIComponent(a[2])===t?(e.handle&&o.closeWindow(e.handle),o.onMessage.removeListener(n),r()):void 0},this.onMessage.addListener(n)},n.prototype.storeCredentials=function(t,e){var r;return r={},r[this.storageKey]=t,chrome.storage.local.set(r,e),this},n.prototype.loadCredentials=function(t){var e=this;return chrome.storage.local.get(this.storageKey,function(r){return t(r[e.storageKey]||null)}),this},n.prototype.forgetCredentials=function(t){return chrome.storage.local.remove(this.storageKey,t),this},n.oauthReceiver=function(){return window.addEventListener("load",function(){var e;return e=new t.Drivers.Chrome,e.sendMessage({dropbox_oauth_receiver_href:window.location.href}),window.close?window.close():void 0})},n}(),t.Drivers.NodeServer=function(){function t(t){this.port=(null!=t?t.port:void 0)||8912,this.faviconFile=(null!=t?t.favicon:void 0)||null,this.fs=require("fs"),this.http=require("http"),this.open=require("open"),this.callbacks={},this.urlRe=RegExp("^/oauth_callback\\?"),this.tokenRe=RegExp("(\\?|&)oauth_token=([^&]+)(&|$)"),this.createApp()}return t.prototype.url=function(){return"http://localhost:"+this.port+"/oauth_callback"},t.prototype.doAuthorize=function(t,e,r,n){return this.callbacks[e]=n,this.openBrowser(t)},t.prototype.openBrowser=function(t){if(!t.match(/^https?:\/\//))throw Error("Not a http/https URL: "+t);return this.open(t)},t.prototype.createApp=function(){var t=this;return this.app=this.http.createServer(function(e,r){return t.doRequest(e,r)}),this.app.listen(this.port)},t.prototype.closeServer=function(){return this.app.close()},t.prototype.doRequest=function(t,e){var r,n,o,i=this;return this.urlRe.exec(t.url)&&(n=this.tokenRe.exec(t.url),n&&(o=decodeURIComponent(n[2]),this.callbacks[o]&&(this.callbacks[o](),delete this.callbacks[o]))),r="",t.on("data",function(t){return r+=t}),t.on("end",function(){return i.faviconFile&&"/favicon.ico"===t.url?i.sendFavicon(e):i.closeBrowser(e)})},t.prototype.closeBrowser=function(t){var e;return e='\n\n

Please close this window.

',t.writeHead(200,{"Content-Length":e.length,"Content-Type":"text/html"}),t.write(e),t.end},t.prototype.sendFavicon=function(t){return this.fs.readFile(this.faviconFile,function(e,r){return t.writeHead(200,{"Content-Length":r.length,"Content-Type":"image/x-icon"}),t.write(r),t.end})},t}(),t.EventSource=function(){function t(t){this._cancelable=t&&t.cancelable,this._listeners=[]}return t.prototype.addListener=function(t){if("function"!=typeof t)throw new TypeError("Invalid listener type; expected function");return 0>k.call(this._listeners,t)&&this._listeners.push(t),this},t.prototype.removeListener=function(t){var e,r,n,o,i,s;if(this._listeners.indexOf)r=this._listeners.indexOf(t),-1!==r&&this._listeners.splice(r,1);else for(s=this._listeners,e=o=0,i=s.length;i>o;e=++o)if(n=s[e],n===t){this._listeners.splice(e,1);break}return this},t.prototype.dispatch=function(t){var e,r,n,o,i;for(i=this._listeners,n=0,o=i.length;o>n;n++)if(e=i[n],r=e(t),this._cancelable&&r===!1)return!1;return!0},t}(),y=function(t,e){return p(b(C(t),C(e),t.length,e.length))},v=function(t){return p(E(C(t),t.length))},("undefined"==typeof window||null===window)&&(w=require("crypto"),y=function(t,e){var r;return r=w.createHmac("sha1",e),r.update(t),r.digest("base64")},v=function(t){var e;return e=w.createHash("sha1"),e.update(t),e.digest("base64")}),b=function(t,e,r,n){var o,i,s,a;return e.length>16&&(e=E(e,n)),s=function(){var t,r;for(r=[],i=t=0;16>t;i=++t)r.push(909522486^e[i]);return r}(),a=function(){var t,r;for(r=[],i=t=0;16>t;i=++t)r.push(1549556828^e[i]);return r}(),o=E(s.concat(t),64+r),E(a.concat(o),84)},E=function(t,e){var r,n,o,i,s,a,h,u,p,c,d,f,y,v,m,g,w,S;for(t[e>>2]|=1<<31-((3&e)<<3),t[(e+8>>6<<4)+15]=e<<3,g=Array(80),r=1732584193,o=-271733879,s=-1732584194,h=271733878,p=-1009589776,f=0,m=t.length;m>f;){for(n=r,i=o,a=s,u=h,c=p,y=S=0;80>S;y=++S)g[y]=16>y?t[f+y]:_(g[y-3]^g[y-8]^g[y-14]^g[y-16],1),20>y?(d=o&s|~o&h,v=1518500249):40>y?(d=o^s^h,v=1859775393):60>y?(d=o&s|o&h|s&h,v=-1894007588):(d=o^s^h,v=-899497514),w=l(l(_(r,5),d),l(l(p,g[y]),v)),p=h,h=s,s=_(o,30),o=r,r=w;r=l(r,n),o=l(o,i),s=l(s,a),h=l(h,u),p=l(p,c),f+=16}return[r,o,s,h,p]},_=function(t,e){return t<>>32-e},l=function(t,e){var r,n;return n=(65535&t)+(65535&e),r=(t>>16)+(e>>16)+(n>>16),r<<16|65535&n},p=function(t){var e,r,n,o,i;for(o="",e=0,n=4*t.length;n>e;)r=e,i=(255&t[r>>2]>>(3-(3&r)<<3))<<16,r+=1,i|=(255&t[r>>2]>>(3-(3&r)<<3))<<8,r+=1,i|=255&t[r>>2]>>(3-(3&r)<<3),o+=x[63&i>>18],o+=x[63&i>>12],e+=1,o+=e>=n?"=":x[63&i>>6],e+=1,o+=e>=n?"=":x[63&i],e+=1;return o},x="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",C=function(t){var e,r,n,o,i;for(e=[],n=255,r=o=0,i=t.length;i>=0?i>o:o>i;r=i>=0?++o:--o)e[r>>2]|=(t.charCodeAt(r)&n)<<(3-(3&r)<<3);return e},t.Oauth=function(){function e(t){this.key=this.k=null,this.secret=this.s=null,this.token=null,this.tokenSecret=null,this._appHash=null,this.reset(t)}return e.prototype.reset=function(t){var e,r,n,o;if(t.secret)this.k=this.key=t.key,this.s=this.secret=t.secret,this._appHash=null;else if(t.key)this.key=t.key,this.secret=null,n=c(S(this.key).split("|",2)[1]),o=n.split("?",2),e=o[0],r=o[1],this.k=decodeURIComponent(e),this.s=decodeURIComponent(r),this._appHash=null;else if(!this.k)throw Error("No API key supplied");return t.token?this.setToken(t.token,t.tokenSecret):this.setToken(null,"")},e.prototype.setToken=function(e,r){if(e&&!r)throw Error("No secret supplied with the user token");return this.token=e,this.tokenSecret=r||"",this.hmacKey=t.Xhr.urlEncodeValue(this.s)+"&"+t.Xhr.urlEncodeValue(r),null},e.prototype.authHeader=function(e,r,n){var o,i,s,a,h,u;this.addAuthParams(e,r,n),i=[];for(s in n)a=n[s],"oauth_"===s.substring(0,6)&&i.push(s);for(i.sort(),o=[],h=0,u=i.length;u>h;h++)s=i[h],o.push(t.Xhr.urlEncodeValue(s)+'="'+t.Xhr.urlEncodeValue(n[s])+'"'),delete n[s];return"OAuth "+o.join(",")},e.prototype.addAuthParams=function(t,e,r){return this.boilerplateParams(r),r.oauth_signature=this.signature(t,e,r),r},e.prototype.boilerplateParams=function(t){return t.oauth_consumer_key=this.k,t.oauth_nonce=this.nonce(),t.oauth_signature_method="HMAC-SHA1",this.token&&(t.oauth_token=this.token),t.oauth_timestamp=Math.floor(Date.now()/1e3),t.oauth_version="1.0",t},e.prototype.nonce=function(){return Date.now().toString(36)+Math.random().toString(36)},e.prototype.signature=function(e,r,n){var o;return o=e.toUpperCase()+"&"+t.Xhr.urlEncodeValue(r)+"&"+t.Xhr.urlEncodeValue(t.Xhr.urlEncode(n)),y(o,this.hmacKey)},e.prototype.appHash=function(){return this._appHash?this._appHash:this._appHash=v(this.k).replace(/\=/g,"")},e}(),null==Date.now&&(Date.now=function(){return(new Date).getTime()}),S=function(t,e){var r,n,o,i,s,a,h,u,l,p,d,f;for(e?(e=[encodeURIComponent(t),encodeURIComponent(e)].join("?"),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length/2;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(16*(15&t.charCodeAt(2*r))+(15&t.charCodeAt(2*r+1))); +return o}()):(p=t.split("|",2),t=p[0],e=p[1],t=c(t),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(t.charCodeAt(r));return o}(),e=c(e)),i=function(){for(f=[],u=0;256>u;u++)f.push(u);return f}.apply(this),a=0,s=l=0;256>l;s=++l)a=(a+i[r]+t[s%t.length])%256,d=[i[a],i[s]],i[s]=d[0],i[a]=d[1];return s=a=0,o=function(){var t,r,o,u;for(u=[],h=t=0,r=e.length;r>=0?r>t:t>r;h=r>=0?++t:--t)s=(s+1)%256,a=(a+i[s])%256,o=[i[a],i[s]],i[s]=o[0],i[a]=o[1],n=i[(i[s]+i[a])%256],u.push(String.fromCharCode((n^e.charCodeAt(h))%256));return u}(),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(String.fromCharCode(t[r]));return o}(),[m(t.join("")),m(o.join(""))].join("|")},t.PulledChanges=function(){function e(e){var r;this.blankSlate=e.reset||!1,this.cursorTag=e.cursor,this.shouldPullAgain=e.has_more,this.shouldBackOff=!this.shouldPullAgain,this.changes=e.cursor&&e.cursor.length?function(){var n,o,i,s;for(i=e.entries,s=[],n=0,o=i.length;o>n;n++)r=i[n],s.push(t.PullChange.parse(r));return s}():[]}return e.parse=function(e){return e&&"object"==typeof e?new t.PulledChanges(e):e},e.prototype.blankSlate=void 0,e.prototype.cursorTag=void 0,e.prototype.changes=void 0,e.prototype.shouldPullAgain=void 0,e.prototype.shouldBackOff=void 0,e.prototype.cursor=function(){return this.cursorTag},e}(),t.PullChange=function(){function e(e){this.path=e[0],this.stat=t.Stat.parse(e[1]),this.stat?this.wasRemoved=!1:(this.stat=null,this.wasRemoved=!0)}return e.parse=function(e){return e&&"object"==typeof e?new t.PullChange(e):e},e.prototype.path=void 0,e.prototype.wasRemoved=void 0,e.prototype.stat=void 0,e}(),t.PublicUrl=function(){function e(t,e){this.url=t.url,this.expiresAt=new Date(Date.parse(t.expires)),this.isDirect=e===!0?!0:e===!1?!1:"direct"in t?t.direct:864e5>=Date.now()-this.expiresAt,this.isPreview=!this.isDirect,this._json=null}return e.parse=function(e,r){return e&&"object"==typeof e?new t.PublicUrl(e,r):e},e.prototype.url=null,e.prototype.expiresAt=null,e.prototype.isDirect=null,e.prototype.isPreview=null,e.prototype.json=function(){return this._json||(this._json={url:this.url,expires:""+this.expiresAt,direct:this.isDirect})},e}(),t.CopyReference=function(){function e(t){"object"==typeof t?(this.tag=t.copy_ref,this.expiresAt=new Date(Date.parse(t.expires)),this._json=t):(this.tag=t,this.expiresAt=new Date(1e3*Math.ceil(Date.now()/1e3)),this._json=null)}return e.parse=function(e){return!e||"object"!=typeof e&&"string"!=typeof e?e:new t.CopyReference(e)},e.prototype.tag=null,e.prototype.expiresAt=null,e.prototype.json=function(){return this._json||(this._json={copy_ref:this.tag,expires:""+this.expiresAt})},e}(),t.Stat=function(){function e(t){var e,r,n,o;switch(this._json=t,this.path=t.path,"/"!==this.path.substring(0,1)&&(this.path="/"+this.path),e=this.path.length-1,e>=0&&"/"===this.path.substring(e)&&(this.path=this.path.substring(0,e)),r=this.path.lastIndexOf("/"),this.name=this.path.substring(r+1),this.isFolder=t.is_dir||!1,this.isFile=!this.isFolder,this.isRemoved=t.is_deleted||!1,this.typeIcon=t.icon,this.modifiedAt=(null!=(n=t.modified)?n.length:void 0)?new Date(Date.parse(t.modified)):null,this.clientModifiedAt=(null!=(o=t.client_mtime)?o.length:void 0)?new Date(Date.parse(t.client_mtime)):null,t.root){case"dropbox":this.inAppFolder=!1;break;case"app_folder":this.inAppFolder=!0;break;default:this.inAppFolder=null}this.size=t.bytes||0,this.humanSize=t.size||"",this.hasThumbnail=t.thumb_exists||!1,this.isFolder?(this.versionTag=t.hash,this.mimeType=t.mime_type||"inode/directory"):(this.versionTag=t.rev,this.mimeType=t.mime_type||"application/octet-stream")}return e.parse=function(e){return e&&"object"==typeof e?new t.Stat(e):e},e.prototype.path=null,e.prototype.name=null,e.prototype.inAppFolder=null,e.prototype.isFolder=null,e.prototype.isFile=null,e.prototype.isRemoved=null,e.prototype.typeIcon=null,e.prototype.versionTag=null,e.prototype.mimeType=null,e.prototype.size=null,e.prototype.humanSize=null,e.prototype.hasThumbnail=null,e.prototype.modifiedAt=null,e.prototype.clientModifiedAt=null,e.prototype.json=function(){return this._json},e}(),t.UploadCursor=function(){function e(t){this.replace(t)}return e.parse=function(e){return!e||"object"!=typeof e&&"string"!=typeof e?e:new t.UploadCursor(e)},e.prototype.tag=null,e.prototype.offset=null,e.prototype.expiresAt=null,e.prototype.json=function(){return this._json||(this._json={upload_id:this.tag,offset:this.offset,expires:""+this.expiresAt})},e.prototype.replace=function(t){return"object"==typeof t?(this.tag=t.upload_id||null,this.offset=t.offset||0,this.expiresAt=new Date(Date.parse(t.expires)||Date.now()),this._json=t):(this.tag=t||null,this.offset=0,this.expiresAt=new Date(1e3*Math.floor(Date.now()/1e3)),this._json=null),this},e}(),t.UserInfo=function(){function e(t){var e;this._json=t,this.name=t.display_name,this.email=t.email,this.countryCode=t.country||null,this.uid=""+t.uid,t.public_app_url?(this.publicAppUrl=t.public_app_url,e=this.publicAppUrl.length-1,e>=0&&"/"===this.publicAppUrl.substring(e)&&(this.publicAppUrl=this.publicAppUrl.substring(0,e))):this.publicAppUrl=null,this.referralUrl=t.referral_link,this.quota=t.quota_info.quota,this.privateBytes=t.quota_info.normal||0,this.sharedBytes=t.quota_info.shared||0,this.usedQuota=this.privateBytes+this.sharedBytes}return e.parse=function(e){return e&&"object"==typeof e?new t.UserInfo(e):e},e.prototype.name=null,e.prototype.email=null,e.prototype.countryCode=null,e.prototype.uid=null,e.prototype.referralUrl=null,e.prototype.publicAppUrl=null,e.prototype.quota=null,e.prototype.usedQuota=null,e.prototype.privateBytes=null,e.prototype.sharedBytes=null,e.prototype.json=function(){return this._json},e}(),"undefined"!=typeof window&&null!==window?(!window.XDomainRequest||"withCredentials"in new XMLHttpRequest?(h=window.XMLHttpRequest,a=!1,i=-1===window.navigator.userAgent.indexOf("Firefox")):(h=window.XDomainRequest,a=!0,i=!1),s=!0):(h=require("xmlhttprequest").XMLHttpRequest,a=!1,i=!1,s=!1),"undefined"==typeof Uint8Array?(o=null,u=!1):(Object.getPrototypeOf?o=Object.getPrototypeOf(Object.getPrototypeOf(new Uint8Array(0))).constructor:Object.__proto__&&(o=new Uint8Array(0).__proto__.__proto__.constructor),u=o!==Object),t.Xhr=function(){function e(t,e){this.method=t,this.isGet="GET"===this.method,this.url=e,this.headers={},this.params=null,this.body=null,this.preflight=!(this.isGet||"POST"===this.method),this.signed=!1,this.responseType=null,this.callback=null,this.xhr=null,this.onError=null}return e.Request=h,e.ieXdr=a,e.canSendForms=i,e.doesPreflight=s,e.ArrayBufferView=o,e.sendArrayBufferView=u,e.prototype.xhr=null,e.prototype.onError=null,e.prototype.setParams=function(t){if(this.signed)throw Error("setParams called after addOauthParams or addOauthHeader");if(this.params)throw Error("setParams cannot be called twice");return this.params=t,this},e.prototype.setCallback=function(t){return this.callback=t,this},e.prototype.signWithOauth=function(e,r){return t.Xhr.ieXdr?this.addOauthParams(e):this.preflight||!t.Xhr.doesPreflight?this.addOauthHeader(e):this.isGet&&r?this.addOauthHeader(e):this.addOauthParams(e)},e.prototype.addOauthParams=function(t){if(this.signed)throw Error("Request already has an OAuth signature");return this.params||(this.params={}),t.addAuthParams(this.method,this.url,this.params),this.signed=!0,this},e.prototype.addOauthHeader=function(t){if(this.signed)throw Error("Request already has an OAuth signature");return this.params||(this.params={}),this.signed=!0,this.setHeader("Authorization",t.authHeader(this.method,this.url,this.params))},e.prototype.setBody=function(t){if(this.isGet)throw Error("setBody cannot be called on GET requests");if(null!==this.body)throw Error("Request already has a body");return"string"==typeof t||"undefined"!=typeof FormData&&t instanceof FormData||(this.headers["Content-Type"]="application/octet-stream",this.preflight=!0),this.body=t,this},e.prototype.setResponseType=function(t){return this.responseType=t,this},e.prototype.setHeader=function(t,e){var r;if(this.headers[t])throw r=this.headers[t],Error("HTTP header "+t+" already set to "+r);if("Content-Type"===t)throw Error("Content-Type is automatically computed based on setBody");return this.preflight=!0,this.headers[t]=e,this},e.prototype.setFileField=function(e,r,n,o){var i,s;if(null!==this.body)throw Error("Request already has a body");if(this.isGet)throw Error("setFileField cannot be called on GET requests");return"object"==typeof n&&"undefined"!=typeof Blob?("undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer?t.Xhr.sendArrayBufferView&&(n=new Uint8Array(n)):!t.Xhr.sendArrayBufferView&&0===n.byteOffset&&n.buffer instanceof ArrayBuffer&&(n=n.buffer)),o||(o="application/octet-stream"),n=new Blob([n],{type:o}),"undefined"!=typeof File&&n instanceof File&&(n=new Blob([n],{type:n.type})),s=n instanceof Blob):s=!1,s?(this.body=new FormData,this.body.append(e,n,r)):(o||(o="application/octet-stream"),i=this.multipartBoundary(),this.headers["Content-Type"]="multipart/form-data; boundary="+i,this.body=["--",i,"\r\n",'Content-Disposition: form-data; name="',e,'"; filename="',r,'"\r\n',"Content-Type: ",o,"\r\n","Content-Transfer-Encoding: binary\r\n\r\n",n,"\r\n","--",i,"--","\r\n"].join(""))},e.prototype.multipartBoundary=function(){return[Date.now().toString(36),Math.random().toString(36)].join("----")},e.prototype.paramsToUrl=function(){var e;return this.params&&(e=t.Xhr.urlEncode(this.params),0!==e.length&&(this.url=[this.url,"?",e].join("")),this.params=null),this},e.prototype.paramsToBody=function(){if(this.params){if(null!==this.body)throw Error("Request already has a body");if(this.isGet)throw Error("paramsToBody cannot be called on GET requests");this.headers["Content-Type"]="application/x-www-form-urlencoded",this.body=t.Xhr.urlEncode(this.params),this.params=null}return this},e.prototype.prepare=function(){var e,r,n,o,i=this;if(r=t.Xhr.ieXdr,this.isGet||null!==this.body||r?(this.paramsToUrl(),null!==this.body&&"string"==typeof this.body&&(this.headers["Content-Type"]="text/plain; charset=utf8")):this.paramsToBody(),this.xhr=new t.Xhr.Request,r?(this.xhr.onload=function(){return i.onXdrLoad()},this.xhr.onerror=function(){return i.onXdrError()},this.xhr.ontimeout=function(){return i.onXdrError()},this.xhr.onprogress=function(){}):this.xhr.onreadystatechange=function(){return i.onReadyStateChange()},this.xhr.open(this.method,this.url,!0),!r){o=this.headers;for(e in o)R.call(o,e)&&(n=o[e],this.xhr.setRequestHeader(e,n))}return this.responseType&&("b"===this.responseType?this.xhr.overrideMimeType&&this.xhr.overrideMimeType("text/plain; charset=x-user-defined"):this.xhr.responseType=this.responseType),this},e.prototype.send=function(e){var r;if(this.callback=e||this.callback,null!==this.body){r=this.body,t.Xhr.sendArrayBufferView&&r instanceof ArrayBuffer&&(r=new Uint8Array(r));try{this.xhr.send(r)}catch(n){if(t.Xhr.sendArrayBufferView||"undefined"==typeof Blob)throw n;r=new Blob([r],{type:"application/octet-stream"}),this.xhr.send(r)}}else this.xhr.send();return this},e.urlEncode=function(t){var e,r,n;e=[];for(r in t)n=t[r],e.push(this.urlEncodeValue(r)+"="+this.urlEncodeValue(n));return e.sort().join("&")},e.urlEncodeValue=function(t){return encodeURIComponent(""+t).replace(/\!/g,"%21").replace(/'/g,"%27").replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/\*/g,"%2A")},e.urlDecode=function(t){var e,r,n,o,i,s;for(r={},s=t.split("&"),o=0,i=s.length;i>o;o++)n=s[o],e=n.split("="),r[decodeURIComponent(e[0])]=decodeURIComponent(e[1]);return r},e.prototype.onReadyStateChange=function(){var e,r,n,o,i,s,a,h,u;if(4!==this.xhr.readyState)return!0;if(200>this.xhr.status||this.xhr.status>=300)return e=new t.ApiError(this.xhr,this.method,this.url),this.onError&&this.onError.dispatch(e),this.callback(e),!0;if(s=this.xhr.getResponseHeader("x-dropbox-metadata"),null!=s?s.length:void 0)try{i=JSON.parse(s)}catch(l){i=void 0}else i=void 0;if(this.responseType){if("b"===this.responseType){for(n=null!=this.xhr.responseText?this.xhr.responseText:this.xhr.response,r=[],o=h=0,u=n.length;u>=0?u>h:h>u;o=u>=0?++h:--h)r.push(String.fromCharCode(255&n.charCodeAt(o)));a=r.join(""),this.callback(null,a,i)}else this.callback(null,this.xhr.response,i);return!0}switch(a=null!=this.xhr.responseText?this.xhr.responseText:this.xhr.response,this.xhr.getResponseHeader("Content-Type")){case"application/x-www-form-urlencoded":this.callback(null,t.Xhr.urlDecode(a),i);break;case"application/json":case"text/javascript":this.callback(null,JSON.parse(a),i);break;default:this.callback(null,a,i)}return!0},e.prototype.onXdrLoad=function(){var e;switch(e=this.xhr.responseText,this.xhr.contentType){case"application/x-www-form-urlencoded":this.callback(null,t.Xhr.urlDecode(e),void 0);break;case"application/json":case"text/javascript":this.callback(null,JSON.parse(e),void 0);break;default:this.callback(null,e,void 0)}return!0},e.prototype.onXdrError=function(){var e;return e=new t.ApiError(this.xhr,this.method,this.url),this.onError&&this.onError.dispatch(e),this.callback(e),!0},e}(),null!=("undefined"!=typeof module&&null!==module?module.exports:void 0))module.exports=t;else{if("undefined"==typeof window||null===window)throw Error("This library only supports node.js and modern browsers.");window.Dropbox=t}t.atob=c,t.btoa=m,t.hmac=y,t.sha1=v,t.encodeKey=S}).call(this); \ No newline at end of file diff --git a/lib/client/storage/dropbox/package.json b/lib/client/storage/dropbox/package.json index dfa72764..553f5fa7 100644 --- a/lib/client/storage/dropbox/package.json +++ b/lib/client/storage/dropbox/package.json @@ -1,6 +1,6 @@ { "name": "dropbox", - "version": "0.7.2", + "version": "0.8.1", "description": "Client library for the Dropbox API", "keywords": ["dropbox", "filesystem", "storage"], "homepage": "http://github.com/dropbox/dropbox-js", @@ -23,14 +23,16 @@ "devDependencies": { "async": ">= 0.1.22", "chai": ">= 1.4.0", - "codo": ">= 1.5.2", + "codo": ">= 1.5.4", "coffee-script": ">= 1.4.0", - "express": ">= 3.0.4", + "express": ">= 3.0.6", + "glob": ">= 3.1.14", "mocha": ">= 1.7.4", + "open": "https://github.com/pwnall/node-open/tarball/master", "remove": ">= 0.1.5", "sinon": ">= 1.5.2", - "sinon-chai": ">= 2.2.0", - "uglify-js": ">= 2.2.2" + "sinon-chai": ">= 2.3.0", + "uglify-js": ">= 2.2.3" }, "main": "lib/dropbox.js", "directories": { @@ -40,7 +42,7 @@ "test": "test" }, "scripts": { - "prepublish": "cake build", - "test": "cake test" + "prepublish": "node_modules/coffee-script/bin/cake build", + "test": "node_modules/coffee-script/bin/cake test" } } diff --git a/lib/client/storage/dropbox/samples/checkbox.js/README.md b/lib/client/storage/dropbox/samples/checkbox.js/README.md new file mode 100644 index 00000000..dc593a05 --- /dev/null +++ b/lib/client/storage/dropbox/samples/checkbox.js/README.md @@ -0,0 +1,67 @@ +# Checkbox, a dropbox.js Sample Application + +This application demonstrates the use of the JavaScript client library for the +Dropbox API to implement a Dropbox-backed To Do list application. + +In 70 lines of HTML, and 250 lines of commented CoffeeScript, Checkbox lets you +store your To Do list in your Dropbox! Just don't expect award winning design +or usability from a sample application. + +See this sample in action +[here](https://dl-web.dropbox.com/spa/pjlfdak1tmznswp/checkbox.js/public/index.html). + + +## Dropbox Integration + +This proof-of-concept application uses the "App folder" Dropbox access level, +so Dropbox automatically creates a directory for its app data in the users' +Dropboxes. The data model optimizes for ease of development and debugging. +Each task is stored as a file whose name is the task’s description. Tasks are +grouped under two folders, active and done. + +The main advantage of this data model is that operations on tasks cleanly map +to file operations in Dropbox. At initialization time, the application creates +its two folders, active and done. A task is created by writing an empty string +to a file in the active folder, marked as completed by moving the file to the +done folder, and removed by deleting the associated file. + +The lists of tasks are obtained by listing the contents of the active and done +folders. The data model can be easily extended, by storing JSON-encoded +information, such as deadlines, in the task files. + +This sample uses the following `Dropbox.Client` methods: + +* authenticate +* signOff +* getUserInfo +* mkdir +* readdir +* writeFile +* move +* remove + + +## Building + +This sample does not require building. Follow the steps below to get your own +copy of the sample that you can hack on. + +1. [Create a powered_by.js app in your Dropbox](https://dl-web.dropbox.com/spa/pjlfdak1tmznswp/powered_by.js/public/index.html). +1. [Get your own API key](https://www.dropbox.com/developers/apps). +1. [Encode your API key](https://dl-web.dropbox.com/spa/pjlfdak1tmznswp/api_keys.js/public/index.html). +1. Copy the source code to `/Apps/Static Web Apps/powered_by.js` in your Dropbox + +## Dependencies + +The application uses the following JavaScript libraries. + +* [dropbox.js](https://github.com/dropbox/dropbox-js) for Dropbox integration +* [less](http://lesscss.org/) for CSS conciseness +* [CoffeeScript](http://coffeescript.org/) for JavaScript conciseness +* [jQuery](http://jquery.com/) for cross-browser compatibitility + +The icons used in the application are all from +[the noun project](http://thenounproject.com/). + +The application follows a good practice of packaging its dependencies, and not +hot-linking them. diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/checkbox.coffee b/lib/client/storage/dropbox/samples/checkbox.js/public/checkbox.coffee new file mode 100644 index 00000000..5d68085b --- /dev/null +++ b/lib/client/storage/dropbox/samples/checkbox.js/public/checkbox.coffee @@ -0,0 +1,249 @@ +# vim: set tabstop=2 shiftwidth=2 softtabstop=2 expandtab : + +# Controller/View for the application. +class Checkbox + # @param {Dropbox.Client} dbClient a non-authenticated Dropbox client + # @param {DOMElement} root the app's main UI element + constructor: (@dbClient, root) -> + @$root = $ root + @taskTemplate = $('#task-template').text() + @$activeList = $('#active-task-list') + @$doneList = $('#done-task-list') + $('#signout-button').click (event) => @onSignOut event + + @dbClient.authenticate (error, data) => + return @showError(error) if error + @dbClient.getUserInfo (error, userInfo) => + return @showError(error) if error + $('#user-name').text userInfo.name + @tasks = new Tasks @, @dbClient + @tasks.load => + @wire() + @render() + @$root.removeClass 'hidden' + + # Re-renders all the data. + render: -> + @$activeList.empty() + @$doneList.empty() + @renderTask(task) for task in @tasks.active + @renderTask(task) for task in @tasks.done + + # Renders a task into the + renderTask: (task) -> + $list = if task.done then @$doneList else @$activeList + $list.append @$taskDom(task) + + # Renders the list element representing a task. + # + # @param {Task} task the task to be rendered + # @return {jQuery
  • } jQuery wrapper for the DOM representing the task + $taskDom: (task) -> + $task = $ @taskTemplate + $('.task-name', $task).text task.name + $('.task-remove-button', $task).click (event) => @onRemoveTask event, task + if task.done + $('.task-done-button', $task).addClass 'hidden' + $('.task-active-button', $task).click (event) => + @onActiveTask event, task + else + $('.task-active-button', $task).addClass 'hidden' + $('.task-done-button', $task).click (event) => @onDoneTask event, task + $task + + # Called when the user wants to create a new task. + onNewTask: (event) -> + event.preventDefault() + name = $('#new-task-name').val() + if @tasks.findByName name + alert "You already have this task on your list!" + else + $('#new-task-button').attr 'disabled', 'disabled' + $('#new-task-name').attr 'disabled', 'disabled' + task = new Task() + task.name = name + @tasks.addTask task, => + $('#new-task-name').removeAttr('disabled').val '' + $('#new-task-button').removeAttr 'disabled' + @renderTask task + + # Called when the user wants to mark a task as done. + onDoneTask: (event, task) -> + $task = @$taskElement event.target + $('button', $task).attr 'disabled', 'disabled' + @tasks.setTaskDone task, true, => + $task.remove() + @renderTask task + + # Called when the user wants to mark a task as active. + onActiveTask: (event, task) -> + $task = @$taskElement event.target + $('button', $task).attr 'disabled', 'disabled' + @tasks.setTaskDone task, false, => + $task.remove() + @renderTask task + + # Called when the user wants to permanently remove a task. + onRemoveTask: (event, task) -> + $task = @$taskElement event.target + $('button', $task).attr 'disabled', 'disabled' + @tasks.removeTask task, -> + $task.remove() + + # Called when the user wants to sign out of the application. + onSignOut: (event, task) -> + @dbClient.signOut (error) => + return @showError(error) if error + window.location.reload() + + # Finds the DOM element representing a task. + # + # @param {DOMElement} element any element inside the task element + # @return {jQuery} a jQuery wrapper around the DOM element + # representing a task + $taskElement: (element) -> + $(element).closest 'li.task' + + # Sets up listeners for the relevant DOM events. + wire: -> + $('#new-task-form').submit (event) => @onNewTask event + + # Updates the UI to show that an error has occurred. + showError: (error) -> + $('#error-notice').removeClass 'hidden' + console.log error if window.console + +# Model that wraps all a user's tasks. +class Tasks + # @param {Checkbox} controller the application controller + constructor: (@controller) -> + @dbClient = @controller.dbClient + [@active, @done] = [[], []] + + # Reads all the from a user's Dropbox. + # + # @param {function()} done called when all the tasks are read from the user's + # Dropbox, and the active and done properties are set + load: (done) -> + # We read the done tasks and the active tasks in parallel. The variables + # below tell us when we're done with both. + readActive = readDone = false + + @dbClient.mkdir '/active', (error, stat) => + # Issued mkdir so we always have a directory to read from. + # In most cases, this will fail, so don't bother checking for errors. + @dbClient.readdir '/active', (error, entries, dir_stat, entry_stats) => + return @showError(error) if error + @active = ((new Task()).fromStat(stat) for stat in entry_stats) + readActive = true + done() if readActive and readDone + @dbClient.mkdir '/done', (error, stat) => + @dbClient.readdir '/done', (error, entries, dir_stat, entry_stats) => + return @showError(error) if error + @done = ((new Task()).fromStat(stat) for stat in entry_stats) + readDone = true + done() if readActive and readDone + @ + + # Adds a new task to the user's set of tasks. + # + # @param {Task} task the task to be added + # @param {function()} done called when the task is saved to the user's + # Dropbox + addTask: (task, done) -> + task.cleanupName() + @dbClient.writeFile task.path(), '', (error, stat) => + return @showError(error) if error + @addTaskToModel task + done() + + # Returns a task with the given name, if it exists. + # + # @param {String} name the name to search for + # @return {?Task} task the task with the given name, or null if no such task + # exists + findByName: (name) -> + for tasks in [@active, @done] + for task in tasks + return task if task.name is name + null + + # Removes a task from the list of tasks. + # + # @param {Task} task the task to be removed + # @param {function()} done called when the task is removed from the user's + # Dropbox + removeTask: (task, done) -> + @dbClient.remove task.path(), (error, stat) => + return @showError(error) if error + @removeTaskFromModel task + done() + + # Marks a active task as done, or a done task as active. + # + # @param {Task} the task to be changed + setTaskDone: (task, newDoneValue, done) -> + [oldDoneValue, task.done] = [task.done, newDoneValue] + newPath = task.path() + task.done = oldDoneValue + + @dbClient.move task.path(), newPath, (error, stat) => + return @showError(error) if error + @removeTaskFromModel task + task.done = newDoneValue + @addTaskToModel task + done() + + # Adds a task to the in-memory model. Should not be called directly. + addTaskToModel: (task) -> + @taskArray(task).push task + + # Remove a task from the in-memory model. Should not be called directly. + removeTaskFromModel: (task) -> + taskArray = @taskArray task + for _task, index in taskArray + if _task is task + taskArray.splice index, 1 + break + + # @param {Task} the task whose containing array should be returned + # @return {Array} the array that should contain the given task + taskArray: (task) -> + if task.done then @done else @active + + # Updates the UI to show that an error has occurred. + showError: (error) -> + @controller.showError error + +# Model for a single user task. +class Task + # Creates a task with default values. + constructor: -> + @name = null + @done = false + + # Reads data about a task from the stat of is file in a user's Dropbox. + # + # @param {Dropbox.Stat} entry the directory entry representing the task + fromStat: (entry) -> + @name = entry.name + @done = entry.path.split('/', 3)[1] is 'done' + @ + + # Cleans up the task name so that it's valid Dropbox file name. + cleanupName: (name) -> + # English-only hack that removes slashes from the task name. + @name = @name.replace(/\ \/\ /g, ' or ').replace(/\//g, ' or ') + @ + + # Path to the file representing the task in the user's Dropbox. + # @return {String} fully-qualified path + path: -> + (if @done then '/done/' else '/active/') + @name + +# Start up the code when the DOM is fully loaded. +$ -> + client = new Dropbox.Client( + key: '/Fahm0FLioA|ZxKxLxy5irfHqsCRs+Ceo8bwJjVPu8xZlfjgGzeCjQ', sandbox: true) + client.authDriver new Dropbox.Drivers.Redirect(rememberUser: true) + new Checkbox client, '#app-ui' diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/checkbox.less b/lib/client/storage/dropbox/samples/checkbox.js/public/checkbox.less new file mode 100644 index 00000000..8f562f97 --- /dev/null +++ b/lib/client/storage/dropbox/samples/checkbox.js/public/checkbox.less @@ -0,0 +1,298 @@ +// vim: set tabstop=2 shiftwidth=2 softtabstop=2 expandtab : + +.linear-gradient (@top: #ffffff, @bottom: #000000) { + background: @top; + filter: ~"progid:DXImageTransform.Microsoft.gradient(startColorstr='@{top}', endColorstr='@{bottom}')"; + background: -webkit-linear-gradient(top, @top 0%, @bottom 100%); + background: -moz-linear-gradient(top, @top 0%, @bottom 100%); + background: -ms-linear-gradient(top, @top 0%, @bottom 100%); + background: linear-gradient(to bottom, @top 0%, @bottom 100%); +} +.border-radius (@radius) { + -webkit-border-radius: @radius; + -moz-border-radius: @radius; + border-radius: @radius; +} +.box-sizing (@sizing) { + -webkit-box-sizing: @sizing; + -moz-box-sizing: @sizing; + box-sizing: @sizing; +} +.box-shadow (@offset-x, @offset-y, @blur, @spread, @color) { + -webkit-box-shadow: @offset-x @offset-y @blur @spread @color; + -moz-box-shadow: @offset-x @offset-y @blur @spread @color; + box-shadow: @offset-x @offset-y @blur @spread @color; +} + +.font-verdana () { + font-family: Verdana, Geneva, sans-serif; +} +.font-helvetica () { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +html, body { + .linear-gradient (#ffffff, #f2f7fc); + background-attachment: fixed; + + height: 100%; + margin: 0; + padding: 0; +} + +#error-notice { + display: block; + position: absolute; + width: 100%; + .box-sizing(border-box); + + color: hsl(0, 75%, 33%); + font-size: 12pt; + .font-verdana(); + line-height: 1.5; + text-align: center; + margin: 0; + padding: 0.2em 0 0.1em 0; + + #error-refresh-button { + margin: 0 1em; + padding: 0 1em; + } + + &.hidden { + display: none; + } +} + +#app-ui { + width: 700px; + margin: 0 auto; + + h1 { + .font-verdana(); + font-size: 24px; + font-weight: 300; + line-height: 2; + text-transform: lowercase; + color: #bfbfbf; + margin: 0; + padding: 2em 0 0.25em 0; + + small { + .font-verdana(); + font-size: 10px; + font-weight: 400; + text-transform: uppercase; + color: #888888; + + a { + font-style: normal; + color: #649cd1; + text-decoration: none; + } + } + } + + #notebook-page { + border: 1px solid #aaaaaa; + -webkit-border-top-left-radius: 8px; + -webkit-border-top-right-radius: 8px; + -moz-border-radius-topleft: 8px; + -moz-border-radius-topright: 8px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + + .box-shadow(0, 0, 10px, 5px, rgba(0, 0, 0, 0.05)); + + background-color: #fffddb; + margin: 0; + padding: 24px 0 60px 0; + } + #notebook-page, .task { + background-image: -webkit-linear-gradient(top, #f3aaaa 0%, #f3aaaa 100%), + -webkit-linear-gradient(top, #f3aaaa 0%, #f3aaaa 100%); + background-image: -moz-linear-gradient(top, #f3aaaa 0%, #f3aaaa 100%), + -moz-linear-gradient(top, #f3aaaa 0%, #f3aaaa 100%); + background-image: -ms-linear-gradient(top, #f3aaaa 0%, #f3aaaa 100%), + -ms-linear-gradient(top, #f3aaaa 0%, #f3aaaa 100%); + background-image: linear-gradient(to bottom, #f3aaaa 0%, #f3aaaa 100%), + linear-gradient(to bottom, #f3aaaa 0%, #f3aaaa 100%); + background-position: 70px 0px, 76px 0px; + background-size: 1px 100%, 1px 100%; + background-repeat: no-repeat, no-repeat; + } + + #user-info { + margin: 0; + padding: 0 16px 8px 88px; + + color: #555555; + .font-helvetica(); + font-size: 16px; + line-height: 32px; + font-weight: 200; + text-align: right; + + #user-name { + display: inline-block; + padding: 0 0 0 8px; + } + + #signout-button { + visibility: hidden; + } + &:hover { + color: #000000; + + #signout-button { + visibility: visible; + } + } + } + + #new-task-form, h2, .task, .empty-task { + border-bottom: 1px solid #c9e4f2; + .font-helvetica(); + font-size: 18px; + font-weight: 300; + line-height: 36px; + color: #555555; + + margin: 0; + padding: 6px 10px 6px 88px; + } + + #new-task-form { + margin: 0; + + #new-task-name { + font-family: inherit; + font-size: inherit; + + background: rgb(255, 254, 236); + } + .task-actions { + visibility: visible; + } + } + h2 { + font-size: 20px; + font-weight: 400; + color: #000000; + } + .task-list { + list-style: none; + margin: 0; + padding: 0; + } + .task { + span.task-name { + display: inline-block; + } + &:hover { + background-color: #fffcaf; + .task-actions { + visibility: visible; + } + } + } + .task-name { + width: 373px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + vertical-align: middle; + } + .task-actions { + width: 220px; + display: inline-block; + vertical-align: middle; + line-height: 1; + visibility: hidden; + } +} + +input[type=text] { + display: inline-block; + margin: 0; + padding: 0 0.25em 0 0.25em; + line-height: 30px; + + .box-sizing(border-box); + position: relative; + left: -4px; + height: 32px; + + border: 1px solid #d2cd70; + .border-radius(4px); + -webkit-box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.1); + -moz-box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.1); + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.1); +} + +button { + display: inline-block; + vertical-align: middle; + height: 32px; + margin: 0; + padding: 0; + min-width: 104px; + cursor: pointer; + + border: 1px solid; + .border-radius(4px); + .box-shadow(0, 2px, -1px, 0, rgba(0, 0, 0, 0.1)); + + .font-verdana(); + font-size: 13px; + font-weight: 400; + text-align: center; + text-transform: uppercase; + + color: #ffffff; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); + filter: dropshadow(color=rgba(0, 0, 0, 0.5), offx=1, offy=1); + + &:hover { + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), + inset 0 0 4px rgba(255, 255, 255, 0.6); + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), + inset 0 0 4px rgba(255, 255, 255, 0.6); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), + inset 0 0 4px rgba(255, 255, 255, 0.6); + } + &:focus { + .box-shadow(0, 0, 3px, 1px, #33a0e8); + } + &:active { + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + } + + img { + display: inline-block; + vertical-align: -4px; + } + + &.task-done-button, &#new-task-button, &.task-active-button { + border-color: #448c42; + .linear-gradient(#8ed66b, #58ba6d); + + &:active { + .linear-gradient(#58ba6d, #8ed66b); + } + } + + &.task-remove-button, &#error-refresh-button, &#signout-button { + border-color: #a73030; + .linear-gradient(#f67f73, #bb5757); + &:active { + .linear-gradient(#bb5757, #f67f73); + } + } +} + +.hidden { + display: none; +} diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/images/add.png b/lib/client/storage/dropbox/samples/checkbox.js/public/images/add.png new file mode 100644 index 00000000..4566ad47 Binary files /dev/null and b/lib/client/storage/dropbox/samples/checkbox.js/public/images/add.png differ diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/images/done.png b/lib/client/storage/dropbox/samples/checkbox.js/public/images/done.png new file mode 100644 index 00000000..69e0921b Binary files /dev/null and b/lib/client/storage/dropbox/samples/checkbox.js/public/images/done.png differ diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/images/icon.svg b/lib/client/storage/dropbox/samples/checkbox.js/public/images/icon.svg new file mode 100644 index 00000000..46b4e9bd --- /dev/null +++ b/lib/client/storage/dropbox/samples/checkbox.js/public/images/icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/images/icon128.png b/lib/client/storage/dropbox/samples/checkbox.js/public/images/icon128.png new file mode 100644 index 00000000..f62d073f Binary files /dev/null and b/lib/client/storage/dropbox/samples/checkbox.js/public/images/icon128.png differ diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/images/icon16.png b/lib/client/storage/dropbox/samples/checkbox.js/public/images/icon16.png new file mode 100644 index 00000000..09c902f4 Binary files /dev/null and b/lib/client/storage/dropbox/samples/checkbox.js/public/images/icon16.png differ diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/images/icon64.png b/lib/client/storage/dropbox/samples/checkbox.js/public/images/icon64.png new file mode 100644 index 00000000..f6f2c837 Binary files /dev/null and b/lib/client/storage/dropbox/samples/checkbox.js/public/images/icon64.png differ diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/images/not_done.png b/lib/client/storage/dropbox/samples/checkbox.js/public/images/not_done.png new file mode 100644 index 00000000..83c7ebdd Binary files /dev/null and b/lib/client/storage/dropbox/samples/checkbox.js/public/images/not_done.png differ diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/images/remove.png b/lib/client/storage/dropbox/samples/checkbox.js/public/images/remove.png new file mode 100644 index 00000000..908b0d36 Binary files /dev/null and b/lib/client/storage/dropbox/samples/checkbox.js/public/images/remove.png differ diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/index.html b/lib/client/storage/dropbox/samples/checkbox.js/public/index.html new file mode 100644 index 00000000..4f86ceea --- /dev/null +++ b/lib/client/storage/dropbox/samples/checkbox.js/public/index.html @@ -0,0 +1,68 @@ + + + + + Checkbox - dropbox.js Sample Application + + + + + + + + + + + + + + diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/lib/coffee-script.js b/lib/client/storage/dropbox/samples/checkbox.js/public/lib/coffee-script.js new file mode 100644 index 00000000..66c387f8 --- /dev/null +++ b/lib/client/storage/dropbox/samples/checkbox.js/public/lib/coffee-script.js @@ -0,0 +1,8 @@ +/** + * CoffeeScript Compiler v1.4.0 + * http://coffeescript.org + * + * Copyright 2011, Jeremy Ashkenas + * Released under the MIT License + */ +(function(root){var CoffeeScript=function(){function require(a){return require[a]}return require["./helpers"]=new function(){var a=this;((function(){var b,c,d;a.starts=function(a,b,c){return b===a.substr(c,b.length)},a.ends=function(a,b,c){var d;return d=b.length,b===a.substr(a.length-d-(c||0),d)},a.compact=function(a){var b,c,d,e;e=[];for(c=0,d=a.length;c=0)f+=1;else if(j=g[0],t.call(d,j)>=0)f-=1;a+=1}return a-1},a.prototype.removeLeadingNewlines=function(){var a,b,c,d,e;e=this.tokens;for(a=c=0,d=e.length;c=0)?(d.splice(b,1),0):1})},a.prototype.closeOpenCalls=function(){var a,b;return b=function(a,b){var c;return(c=a[0])===")"||c==="CALL_END"||a[0]==="OUTDENT"&&this.tag(b-1)===")"},a=function(a,b){return this.tokens[a[0]==="OUTDENT"?b-1:b][0]="CALL_END"},this.scanTokens(function(c,d){return c[0]==="CALL_START"&&this.detectEnd(d+1,b,a),1})},a.prototype.closeOpenIndexes=function(){var a,b;return b=function(a,b){var c;return(c=a[0])==="]"||c==="INDEX_END"},a=function(a,b){return a[0]="INDEX_END"},this.scanTokens(function(c,d){return c[0]==="INDEX_START"&&this.detectEnd(d+1,b,a),1})},a.prototype.addImplicitBraces=function(){var a,b,c,f,g,i,j,k;return f=[],g=null,k=null,c=!0,i=0,j=0,b=function(a,b){var d,e,f,g,i,m;return i=this.tokens.slice(b+1,+(b+3)+1||9e9),d=i[0],g=i[1],f=i[2],"HERECOMMENT"===(d!=null?d[0]:void 0)?!1:(e=a[0],t.call(l,e)>=0&&(c=!1),(e==="TERMINATOR"||e==="OUTDENT"||t.call(h,e)>=0&&c&&b-j!==1)&&(!k&&this.tag(b-1)!==","||(g!=null?g[0]:void 0)!==":"&&((d!=null?d[0]:void 0)!=="@"||(f!=null?f[0]:void 0)!==":"))||e===","&&d&&(m=d[0])!=="IDENTIFIER"&&m!=="NUMBER"&&m!=="STRING"&&m!=="@"&&m!=="TERMINATOR"&&m!=="OUTDENT")},a=function(a,b){var c;return c=this.generate("}","}",a[2]),this.tokens.splice(b,0,c)},this.scanTokens(function(h,i,m){var n,o,p,q,r,s,u,v;if(u=q=h[0],t.call(e,u)>=0)return f.push([q==="INDENT"&&this.tag(i-1)==="{"?"{":q,i]),1;if(t.call(d,q)>=0)return g=f.pop(),1;if(q!==":"||(n=this.tag(i-2))!==":"&&((v=f[f.length-1])!=null?v[0]:void 0)==="{")return 1;c=!0,j=i+1,f.push(["{"]),o=n==="@"?i-2:i-1;while(this.tag(o-2)==="HERECOMMENT")o-=2;return p=this.tag(o-1),k=!p||t.call(l,p)>=0,s=new String("{"),s.generated=!0,r=this.generate("{",s,h[2]),m.splice(o,0,r),this.detectEnd(i+2,b,a),2})},a.prototype.addImplicitParentheses=function(){var a,b,c,d,e;return c=e=d=!1,b=function(a,b){var c,g,i,j;g=a[0];if(!e&&a.fromThen)return!0;if(g==="IF"||g==="ELSE"||g==="CATCH"||g==="->"||g==="=>"||g==="CLASS")e=!0;if(g==="IF"||g==="ELSE"||g==="SWITCH"||g==="TRY"||g==="=")d=!0;return g!=="."&&g!=="?."&&g!=="::"||this.tag(b-1)!=="OUTDENT"?!a.generated&&this.tag(b-1)!==","&&(t.call(h,g)>=0||g==="INDENT"&&!d)&&(g!=="INDENT"||(i=this.tag(b-2))!=="CLASS"&&i!=="EXTENDS"&&(j=this.tag(b-1),t.call(f,j)<0)&&(!(c=this.tokens[b+1])||!c.generated||c[0]!=="{")):!0},a=function(a,b){return this.tokens.splice(b,0,this.generate("CALL_END",")",a[2]))},this.scanTokens(function(f,h,k){var m,n,o,p,q,r,s,u;q=f[0];if(q==="CLASS"||q==="IF"||q==="FOR"||q==="WHILE")c=!0;return r=k.slice(h-1,+(h+1)+1||9e9),p=r[0],n=r[1],o=r[2],m=!c&&q==="INDENT"&&o&&o.generated&&o[0]==="{"&&p&&(s=p[0],t.call(i,s)>=0),e=!1,d=!1,t.call(l,q)>=0&&(c=!1),p&&!p.spaced&&q==="?"&&(f.call=!0),f.fromThen?1:m||(p!=null?p.spaced:void 0)&&(p.call||(u=p[0],t.call(i,u)>=0))&&(t.call(g,q)>=0||!f.spaced&&!f.newLine&&t.call(j,q)>=0)?(k.splice(h,0,this.generate("CALL_START","(",f[2])),this.detectEnd(h+1,b,a),p[0]==="?"&&(p[0]="FUNC_EXIST"),2):1})},a.prototype.addImplicitIndentation=function(){var a,b,c,d,e;return e=c=d=null,b=function(a,b){var c;return a[1]!==";"&&(c=a[0],t.call(m,c)>=0)&&(a[0]!=="ELSE"||e==="IF"||e==="THEN")},a=function(a,b){return this.tokens.splice(this.tag(b-1)===","?b-1:b,0,d)},this.scanTokens(function(f,g,h){var i,j,k;return i=f[0],i==="TERMINATOR"&&this.tag(g+1)==="THEN"?(h.splice(g,1),0):i==="ELSE"&&this.tag(g-1)!=="OUTDENT"?(h.splice.apply(h,[g,0].concat(u.call(this.indentation(f)))),2):i!=="CATCH"||(j=this.tag(g+2))!=="OUTDENT"&&j!=="TERMINATOR"&&j!=="FINALLY"?t.call(n,i)>=0&&this.tag(g+1)!=="INDENT"&&(i!=="ELSE"||this.tag(g+1)!=="IF")?(e=i,k=this.indentation(f,!0),c=k[0],d=k[1],e==="THEN"&&(c.fromThen=!0),h.splice(g+1,0,c),this.detectEnd(g+2,b,a),i==="THEN"&&h.splice(g,1),1):1:(h.splice.apply(h,[g+2,0].concat(u.call(this.indentation(f)))),4)})},a.prototype.tagPostfixConditionals=function(){var a,b,c;return c=null,b=function(a,b){var c;return(c=a[0])==="TERMINATOR"||c==="INDENT"},a=function(a,b){if(a[0]!=="INDENT"||a.generated&&!a.fromThen)return c[0]="POST_"+c[0]},this.scanTokens(function(d,e){return d[0]!=="IF"?1:(c=d,this.detectEnd(e+1,b,a),1)})},a.prototype.indentation=function(a,b){var c,d;return b==null&&(b=!1),c=["INDENT",2,a[2]],d=["OUTDENT",2,a[2]],b&&(c.generated=d.generated=!0),[c,d]},a.prototype.generate=function(a,b,c){var d;return d=[a,b,c],d.generated=!0,d},a.prototype.tag=function(a){var b;return(b=this.tokens[a])!=null?b[0]:void 0},a}(),b=[["(",")"],["[","]"],["{","}"],["INDENT","OUTDENT"],["CALL_START","CALL_END"],["PARAM_START","PARAM_END"],["INDEX_START","INDEX_END"]],a.INVERSES=k={},e=[],d=[];for(q=0,r=b.length;q","=>","[","(","{","--","++"],j=["+","-"],f=["->","=>","{","[",","],h=["POST_IF","FOR","WHILE","UNTIL","WHEN","BY","LOOP","TERMINATOR"],n=["ELSE","->","=>","TRY","FINALLY","THEN"],m=["TERMINATOR","CATCH","FINALLY","ELSE","OUTDENT","LEADING_WHEN"],l=["TERMINATOR","INDENT","OUTDENT"]})).call(this)},require["./lexer"]=new function(){var a=this;((function(){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X=[].indexOf||function(a){for(var b=0,c=this.length;b=0||X.call(g,c)>=0)&&(j=c.toUpperCase(),j==="WHEN"&&(l=this.tag(),X.call(v,l)>=0)?j="LEADING_WHEN":j==="FOR"?this.seenFor=!0:j==="UNLESS"?j="IF":X.call(O,j)>=0?j="UNARY":X.call(H,j)>=0&&(j!=="INSTANCEOF"&&this.seenFor?(j="FOR"+j,this.seenFor=!1):(j="RELATION",this.value()==="!"&&(this.tokens.pop(),c="!"+c)))),X.call(t,c)>=0&&(b?(j="IDENTIFIER",c=new String(c),c.reserved=!0):X.call(I,c)>=0&&this.error('reserved word "'+c+'"')),b||(X.call(e,c)>=0&&(c=f[c]),j=function(){switch(c){case"!":return"UNARY";case"==":case"!=":return"COMPARE";case"&&":case"||":return"LOGIC";case"true":case"false":return"BOOL";case"break":case"continue":return"STATEMENT";default:return j}}()),this.token(j,c),a&&this.token(":",":"),d.length)):0},a.prototype.numberToken=function(){var a,b,c,d,e;if(!(c=E.exec(this.chunk)))return 0;d=c[0],/^0[BOX]/.test(d)?this.error("radix prefix '"+d+"' must be lowercase"):/E/.test(d)&&!/^0x/.test(d)?this.error("exponential notation '"+d+"' must be indicated with a lowercase 'e'"):/^0\d*[89]/.test(d)?this.error("decimal literal '"+d+"' must not be prefixed with '0'"):/^0\d+/.test(d)&&this.error("octal literal '"+d+"' must be prefixed with '0o'"),b=d.length;if(e=/^0o([0-7]+)/.exec(d))d="0x"+parseInt(e[1],8).toString(16);if(a=/^0b([01]+)/.exec(d))d="0x"+parseInt(a[1],2).toString(16);return this.token("NUMBER",d),b},a.prototype.stringToken=function(){var a,b,c;switch(this.chunk.charAt(0)){case"'":if(!(a=L.exec(this.chunk)))return 0;this.token("STRING",(c=a[0]).replace(A,"\\\n"));break;case'"':if(!(c=this.balancedString(this.chunk,'"')))return 0;0=0)?0:(c=G.exec(this.chunk))?(g=c,c=g[0],e=g[1],a=g[2],e.slice(0,2)==="/*"&&this.error("regular expressions cannot begin with `*`"),e==="//"&&(e="/(?:)/"),this.token("REGEX",""+e+a),c.length):0)},a.prototype.heregexToken=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n;d=a[0],b=a[1],c=a[2];if(0>b.indexOf("#{"))return e=b.replace(o,"").replace(/\//g,"\\/"),e.match(/^\*/)&&this.error("regular expressions cannot begin with `*`"),this.token("REGEX","/"+(e||"(?:)")+"/"+c),d.length;this.token("IDENTIFIER","RegExp"),this.tokens.push(["CALL_START","("]),g=[],k=this.interpolateString(b,{regex:!0});for(i=0,j=k.length;ithis.indent){if(d)return this.indebt=e-this.indent,this.suppressNewlines(),b.length;a=e-this.indent+this.outdebt,this.token("INDENT",a),this.indents.push(a),this.ends.push("OUTDENT"),this.outdebt=this.indebt=0}else this.indebt=0,this.outdentToken(this.indent-e,d);return this.indent=e,b.length},a.prototype.outdentToken=function(a,b){var c,d;while(a>0)d=this.indents.length-1,this.indents[d]===void 0?a=0:this.indents[d]===this.outdebt?(a-=this.outdebt,this.outdebt=0):this.indents[d]=0)&&this.error('reserved word "'+this.value()+"\" can't be assigned");if((h=b[1])==="||"||h==="&&")return b[0]="COMPOUND_ASSIGN",b[1]+="=",f.length}if(f===";")this.seenFor=!1,e="TERMINATOR";else if(X.call(z,f)>=0)e="MATH";else if(X.call(i,f)>=0)e="COMPARE";else if(X.call(j,f)>=0)e="COMPOUND_ASSIGN";else if(X.call(O,f)>=0)e="UNARY";else if(X.call(K,f)>=0)e="SHIFT";else if(X.call(x,f)>=0||f==="?"&&(b!=null?b.spaced:void 0))e="LOGIC";else if(b&&!b.spaced)if(f==="("&&(k=b[0],X.call(c,k)>=0))b[0]==="?"&&(b[0]="FUNC_EXIST"),e="CALL_START";else if(f==="["&&(l=b[0],X.call(q,l)>=0)){e="INDEX_START";switch(b[0]){case"?":b[0]="INDEX_SOAK"}}switch(f){case"(":case"{":case"[":this.ends.push(r[f]);break;case")":case"}":case"]":this.pair(f)}return this.token(e,f),f.length},a.prototype.sanitizeHeredoc=function(a,b){var c,d,e,f,g;e=b.indent,d=b.herecomment;if(d){l.test(a)&&this.error('block comment cannot contain "*/", starting');if(a.indexOf("\n")<=0)return a}else while(f=m.exec(a)){c=f[1];if(e===null||0<(g=c.length)&&gj;d=1<=j?++i:--i){if(c){--c;continue}switch(e=a.charAt(d)){case"\\":++c;continue;case b:h.pop();if(!h.length)return a.slice(0,+d+1||9e9);b=h[h.length-1];continue}b!=="}"||e!=='"'&&e!=="'"?b==="}"&&e==="/"&&(f=n.exec(a.slice(d))||G.exec(a.slice(d)))?c+=f[0].length-1:b==="}"&&e==="{"?h.push(b="}"):b==='"'&&g==="#"&&e==="{"&&h.push(b="}"):h.push(b=e),g=e}return this.error("missing "+h.pop()+", starting")},a.prototype.interpolateString=function(b,c){var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;c==null&&(c={}),e=c.heredoc,m=c.regex,o=[],l=0,f=-1;while(j=b.charAt(f+=1)){if(j==="\\"){f+=1;continue}if(j!=="#"||b.charAt(f+1)!=="{"||!(d=this.balancedString(b.slice(f+1),"}")))continue;l1&&(k.unshift(["(","(",this.line]),k.push([")",")",this.line])),o.push(["TOKENS",k])}f+=d.length,l=f+1}f>l&&l1)&&this.token("(","(");for(f=q=0,r=o.length;q|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>])\2=?|\?\.|\.{2,3})/,P=/^[^\n\S]+/,h=/^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)?$)|^(?:\s*#(?!##[^#]).*)+/,d=/^[-=]>/,B=/^(?:\n[^\n\S]*)+/,L=/^'[^\\']*(?:\\.[^\\']*)*'/,s=/^`[^\\`]*(?:\\.[^\\`]*)*`/,G=/^(\/(?![\s=])[^[\/\n\\]*(?:(?:\\[\s\S]|\[[^\]\n\\]*(?:\\[\s\S][^\]\n\\]*)*])[^[\/\n\\]*)*\/)([imgy]{0,4})(?!\w)/,n=/^\/{3}([\s\S]+?)\/{3}([imgy]{0,4})(?!\w)/,o=/\s+(?:#.*)?/g,A=/\n/g,m=/\n+([^\n\S]*)/g,l=/\*\//,w=/^\s*(?:,|\??\.(?![.\d])|::)/,N=/\s+$/,j=["-=","+=","/=","*=","%=","||=","&&=","?=","<<=",">>=",">>>=","&=","^=","|="],O=["!","~","NEW","TYPEOF","DELETE","DO"],x=["&&","||","&","|","^"],K=["<<",">>",">>>"],i=["==","!=","<",">","<=",">="],z=["*","/","%"],H=["IN","OF","INSTANCEOF"],b=["TRUE","FALSE"],C=["NUMBER","REGEX","BOOL","NULL","UNDEFINED","++","--","]"],D=C.concat(")","}","THIS","IDENTIFIER","STRING"),c=["IDENTIFIER","STRING","REGEX",")","]","}","?","::","@","THIS","SUPER"],q=c.concat("NUMBER","BOOL","NULL","UNDEFINED"),v=["INDENT","OUTDENT","TERMINATOR"]})).call(this)},require["./parser"]=new function(){var a=this,b=function(){var a={trace:function(){},yy:{},symbols_:{error:2,Root:3,Body:4,Block:5,TERMINATOR:6,Line:7,Expression:8,Statement:9,Return:10,Comment:11,STATEMENT:12,Value:13,Invocation:14,Code:15,Operation:16,Assign:17,If:18,Try:19,While:20,For:21,Switch:22,Class:23,Throw:24,INDENT:25,OUTDENT:26,Identifier:27,IDENTIFIER:28,AlphaNumeric:29,NUMBER:30,STRING:31,Literal:32,JS:33,REGEX:34,DEBUGGER:35,UNDEFINED:36,NULL:37,BOOL:38,Assignable:39,"=":40,AssignObj:41,ObjAssignable:42,":":43,ThisProperty:44,RETURN:45,HERECOMMENT:46,PARAM_START:47,ParamList:48,PARAM_END:49,FuncGlyph:50,"->":51,"=>":52,OptComma:53,",":54,Param:55,ParamVar:56,"...":57,Array:58,Object:59,Splat:60,SimpleAssignable:61,Accessor:62,Parenthetical:63,Range:64,This:65,".":66,"?.":67,"::":68,Index:69,INDEX_START:70,IndexValue:71,INDEX_END:72,INDEX_SOAK:73,Slice:74,"{":75,AssignList:76,"}":77,CLASS:78,EXTENDS:79,OptFuncExist:80,Arguments:81,SUPER:82,FUNC_EXIST:83,CALL_START:84,CALL_END:85,ArgList:86,THIS:87,"@":88,"[":89,"]":90,RangeDots:91,"..":92,Arg:93,SimpleArgs:94,TRY:95,Catch:96,FINALLY:97,CATCH:98,THROW:99,"(":100,")":101,WhileSource:102,WHILE:103,WHEN:104,UNTIL:105,Loop:106,LOOP:107,ForBody:108,FOR:109,ForStart:110,ForSource:111,ForVariables:112,OWN:113,ForValue:114,FORIN:115,FOROF:116,BY:117,SWITCH:118,Whens:119,ELSE:120,When:121,LEADING_WHEN:122,IfBlock:123,IF:124,POST_IF:125,UNARY:126,"-":127,"+":128,"--":129,"++":130,"?":131,MATH:132,SHIFT:133,COMPARE:134,LOGIC:135,RELATION:136,COMPOUND_ASSIGN:137,$accept:0,$end:1},terminals_:{2:"error",6:"TERMINATOR",12:"STATEMENT",25:"INDENT",26:"OUTDENT",28:"IDENTIFIER",30:"NUMBER",31:"STRING",33:"JS",34:"REGEX",35:"DEBUGGER",36:"UNDEFINED",37:"NULL",38:"BOOL",40:"=",43:":",45:"RETURN",46:"HERECOMMENT",47:"PARAM_START",49:"PARAM_END",51:"->",52:"=>",54:",",57:"...",66:".",67:"?.",68:"::",70:"INDEX_START",72:"INDEX_END",73:"INDEX_SOAK",75:"{",77:"}",78:"CLASS",79:"EXTENDS",82:"SUPER",83:"FUNC_EXIST",84:"CALL_START",85:"CALL_END",87:"THIS",88:"@",89:"[",90:"]",92:"..",95:"TRY",97:"FINALLY",98:"CATCH",99:"THROW",100:"(",101:")",103:"WHILE",104:"WHEN",105:"UNTIL",107:"LOOP",109:"FOR",113:"OWN",115:"FORIN",116:"FOROF",117:"BY",118:"SWITCH",120:"ELSE",122:"LEADING_WHEN",124:"IF",125:"POST_IF",126:"UNARY",127:"-",128:"+",129:"--",130:"++",131:"?",132:"MATH",133:"SHIFT",134:"COMPARE",135:"LOGIC",136:"RELATION",137:"COMPOUND_ASSIGN"},productions_:[0,[3,0],[3,1],[3,2],[4,1],[4,3],[4,2],[7,1],[7,1],[9,1],[9,1],[9,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[5,2],[5,3],[27,1],[29,1],[29,1],[32,1],[32,1],[32,1],[32,1],[32,1],[32,1],[32,1],[17,3],[17,4],[17,5],[41,1],[41,3],[41,5],[41,1],[42,1],[42,1],[42,1],[10,2],[10,1],[11,1],[15,5],[15,2],[50,1],[50,1],[53,0],[53,1],[48,0],[48,1],[48,3],[48,4],[48,6],[55,1],[55,2],[55,3],[56,1],[56,1],[56,1],[56,1],[60,2],[61,1],[61,2],[61,2],[61,1],[39,1],[39,1],[39,1],[13,1],[13,1],[13,1],[13,1],[13,1],[62,2],[62,2],[62,2],[62,1],[62,1],[69,3],[69,2],[71,1],[71,1],[59,4],[76,0],[76,1],[76,3],[76,4],[76,6],[23,1],[23,2],[23,3],[23,4],[23,2],[23,3],[23,4],[23,5],[14,3],[14,3],[14,1],[14,2],[80,0],[80,1],[81,2],[81,4],[65,1],[65,1],[44,2],[58,2],[58,4],[91,1],[91,1],[64,5],[74,3],[74,2],[74,2],[74,1],[86,1],[86,3],[86,4],[86,4],[86,6],[93,1],[93,1],[94,1],[94,3],[19,2],[19,3],[19,4],[19,5],[96,3],[24,2],[63,3],[63,5],[102,2],[102,4],[102,2],[102,4],[20,2],[20,2],[20,2],[20,1],[106,2],[106,2],[21,2],[21,2],[21,2],[108,2],[108,2],[110,2],[110,3],[114,1],[114,1],[114,1],[114,1],[112,1],[112,3],[111,2],[111,2],[111,4],[111,4],[111,4],[111,6],[111,6],[22,5],[22,7],[22,4],[22,6],[119,1],[119,2],[121,3],[121,4],[123,3],[123,5],[18,1],[18,3],[18,3],[18,3],[16,2],[16,2],[16,2],[16,2],[16,2],[16,2],[16,2],[16,2],[16,3],[16,3],[16,3],[16,3],[16,3],[16,3],[16,3],[16,3],[16,5],[16,3]],performAction:function(b,c,d,e,f,g,h){var i=g.length-1;switch(f){case 1:return this.$=new e.Block;case 2:return this.$=g[i];case 3:return this.$=g[i-1];case 4:this.$=e.Block.wrap([g[i]]);break;case 5:this.$=g[i-2].push(g[i]);break;case 6:this.$=g[i-1];break;case 7:this.$=g[i];break;case 8:this.$=g[i];break;case 9:this.$=g[i];break;case 10:this.$=g[i];break;case 11:this.$=new e.Literal(g[i]);break;case 12:this.$=g[i];break;case 13:this.$=g[i];break;case 14:this.$=g[i];break;case 15:this.$=g[i];break;case 16:this.$=g[i];break;case 17:this.$=g[i];break;case 18:this.$=g[i];break;case 19:this.$=g[i];break;case 20:this.$=g[i];break;case 21:this.$=g[i];break;case 22:this.$=g[i];break;case 23:this.$=g[i];break;case 24:this.$=new e.Block;break;case 25:this.$=g[i-1];break;case 26:this.$=new e.Literal(g[i]);break;case 27:this.$=new e.Literal(g[i]);break;case 28:this.$=new e.Literal(g[i]);break;case 29:this.$=g[i];break;case 30:this.$=new e.Literal(g[i]);break;case 31:this.$=new e.Literal(g[i]);break;case 32:this.$=new e.Literal(g[i]);break;case 33:this.$=new e.Undefined;break;case 34:this.$=new e.Null;break;case 35:this.$=new e.Bool(g[i]);break;case 36:this.$=new e.Assign(g[i-2],g[i]);break;case 37:this.$=new e.Assign(g[i-3],g[i]);break;case 38:this.$=new e.Assign(g[i-4],g[i-1]);break;case 39:this.$=new e.Value(g[i]);break;case 40:this.$=new e.Assign(new e.Value(g[i-2]),g[i],"object");break;case 41:this.$=new e.Assign(new e.Value(g[i-4]),g[i-1],"object");break;case 42:this.$=g[i];break;case 43:this.$=g[i];break;case 44:this.$=g[i];break;case 45:this.$=g[i];break;case 46:this.$=new e.Return(g[i]);break;case 47:this.$=new e.Return;break;case 48:this.$=new e.Comment(g[i]);break;case 49:this.$=new e.Code(g[i-3],g[i],g[i-1]);break;case 50:this.$=new e.Code([],g[i],g[i-1]);break;case 51:this.$="func";break;case 52:this.$="boundfunc";break;case 53:this.$=g[i];break;case 54:this.$=g[i];break;case 55:this.$=[];break;case 56:this.$=[g[i]];break;case 57:this.$=g[i-2].concat(g[i]);break;case 58:this.$=g[i-3].concat(g[i]);break;case 59:this.$=g[i-5].concat(g[i-2]);break;case 60:this.$=new e.Param(g[i]);break;case 61:this.$=new e.Param(g[i-1],null,!0);break;case 62:this.$=new e.Param(g[i-2],g[i]);break;case 63:this.$=g[i];break;case 64:this.$=g[i];break;case 65:this.$=g[i];break;case 66:this.$=g[i];break;case 67:this.$=new e.Splat(g[i-1]);break;case 68:this.$=new e.Value(g[i]);break;case 69:this.$=g[i-1].add(g[i]);break;case 70:this.$=new e.Value(g[i-1],[].concat(g[i]));break;case 71:this.$=g[i];break;case 72:this.$=g[i];break;case 73:this.$=new e.Value(g[i]);break;case 74:this.$=new e.Value(g[i]);break;case 75:this.$=g[i];break;case 76:this.$=new e.Value(g[i]);break;case 77:this.$=new e.Value(g[i]);break;case 78:this.$=new e.Value(g[i]);break;case 79:this.$=g[i];break;case 80:this.$=new e.Access(g[i]);break;case 81:this.$=new e.Access(g[i],"soak");break;case 82:this.$=[new e.Access(new e.Literal("prototype")),new e.Access(g[i])];break;case 83:this.$=new e.Access(new e.Literal("prototype"));break;case 84:this.$=g[i];break;case 85:this.$=g[i-1];break;case 86:this.$=e.extend(g[i],{soak:!0});break;case 87:this.$=new e.Index(g[i]);break;case 88:this.$=new e.Slice(g[i]);break;case 89:this.$=new e.Obj(g[i-2],g[i-3].generated);break;case 90:this.$=[];break;case 91:this.$=[g[i]];break;case 92:this.$=g[i-2].concat(g[i]);break;case 93:this.$=g[i-3].concat(g[i]);break;case 94:this.$=g[i-5].concat(g[i-2]);break;case 95:this.$=new e.Class;break;case 96:this.$=new e.Class(null,null,g[i]);break;case 97:this.$=new e.Class(null,g[i]);break;case 98:this.$=new e.Class(null,g[i-1],g[i]);break;case 99:this.$=new e.Class(g[i]);break;case 100:this.$=new e.Class(g[i-1],null,g[i]);break;case 101:this.$=new e.Class(g[i-2],g[i]);break;case 102:this.$=new e.Class(g[i-3],g[i-1],g[i]);break;case 103:this.$=new e.Call(g[i-2],g[i],g[i-1]);break;case 104:this.$=new e.Call(g[i-2],g[i],g[i-1]);break;case 105:this.$=new e.Call("super",[new e.Splat(new e.Literal("arguments"))]);break;case 106:this.$=new e.Call("super",g[i]);break;case 107:this.$=!1;break;case 108:this.$=!0;break;case 109:this.$=[];break;case 110:this.$=g[i-2];break;case 111:this.$=new e.Value(new e.Literal("this"));break;case 112:this.$=new e.Value(new e.Literal("this"));break;case 113:this.$=new e.Value(new e.Literal("this"),[new e.Access(g[i])],"this");break;case 114:this.$=new e.Arr([]);break;case 115:this.$=new e.Arr(g[i-2]);break;case 116:this.$="inclusive";break;case 117:this.$="exclusive";break;case 118:this.$=new e.Range(g[i-3],g[i-1],g[i-2]);break;case 119:this.$=new e.Range(g[i-2],g[i],g[i-1]);break;case 120:this.$=new e.Range(g[i-1],null,g[i]);break;case 121:this.$=new e.Range(null,g[i],g[i-1]);break;case 122:this.$=new e.Range(null,null,g[i]);break;case 123:this.$=[g[i]];break;case 124:this.$=g[i-2].concat(g[i]);break;case 125:this.$=g[i-3].concat(g[i]);break;case 126:this.$=g[i-2];break;case 127:this.$=g[i-5].concat(g[i-2]);break;case 128:this.$=g[i];break;case 129:this.$=g[i];break;case 130:this.$=g[i];break;case 131:this.$=[].concat(g[i-2],g[i]);break;case 132:this.$=new e.Try(g[i]);break;case 133:this.$=new e.Try(g[i-1],g[i][0],g[i][1]);break;case 134:this.$=new e.Try(g[i-2],null,null,g[i]);break;case 135:this.$=new e.Try(g[i-3],g[i-2][0],g[i-2][1],g[i]);break;case 136:this.$=[g[i-1],g[i]];break;case 137:this.$=new e.Throw(g[i]);break;case 138:this.$=new e.Parens(g[i-1]);break;case 139:this.$=new e.Parens(g[i-2]);break;case 140:this.$=new e.While(g[i]);break;case 141:this.$=new e.While(g[i-2],{guard:g[i]});break;case 142:this.$=new e.While(g[i],{invert:!0});break;case 143:this.$=new e.While(g[i-2],{invert:!0,guard:g[i]});break;case 144:this.$=g[i-1].addBody(g[i]);break;case 145:this.$=g[i].addBody(e.Block.wrap([g[i-1]]));break;case 146:this.$=g[i].addBody(e.Block.wrap([g[i-1]]));break;case 147:this.$=g[i];break;case 148:this.$=(new e.While(new e.Literal("true"))).addBody(g[i]);break;case 149:this.$=(new e.While(new e.Literal("true"))).addBody(e.Block.wrap([g[i]]));break;case 150:this.$=new e.For(g[i-1],g[i]);break;case 151:this.$=new e.For(g[i-1],g[i]);break;case 152:this.$=new e.For(g[i],g[i-1]);break;case 153:this.$={source:new e.Value(g[i])};break;case 154:this.$=function(){return g[i].own=g[i-1].own,g[i].name=g[i-1][0],g[i].index=g[i-1][1],g[i]}();break;case 155:this.$=g[i];break;case 156:this.$=function(){return g[i].own=!0,g[i]}();break;case 157:this.$=g[i];break;case 158:this.$=g[i];break;case 159:this.$=new e.Value(g[i]);break;case 160:this.$=new e.Value(g[i]);break;case 161:this.$=[g[i]];break;case 162:this.$=[g[i-2],g[i]];break;case 163:this.$={source:g[i]};break;case 164:this.$={source:g[i],object:!0};break;case 165:this.$={source:g[i-2],guard:g[i]};break;case 166:this.$={source:g[i-2],guard:g[i],object:!0};break;case 167:this.$={source:g[i-2],step:g[i]};break;case 168:this.$={source:g[i-4],guard:g[i-2],step:g[i]};break;case 169:this.$={source:g[i-4],step:g[i-2],guard:g[i]};break;case 170:this.$=new e.Switch(g[i-3],g[i-1]);break;case 171:this.$=new e.Switch(g[i-5],g[i-3],g[i-1]);break;case 172:this.$=new e.Switch(null,g[i-1]);break;case 173:this.$=new e.Switch(null,g[i-3],g[i-1]);break;case 174:this.$=g[i];break;case 175:this.$=g[i-1].concat(g[i]);break;case 176:this.$=[[g[i-1],g[i]]];break;case 177:this.$=[[g[i-2],g[i-1]]];break;case 178:this.$=new e.If(g[i-1],g[i],{type:g[i-2]});break;case 179:this.$=g[i-4].addElse(new e.If(g[i-1],g[i],{type:g[i-2]}));break;case 180:this.$=g[i];break;case 181:this.$=g[i-2].addElse(g[i]);break;case 182:this.$=new e.If(g[i],e.Block.wrap([g[i-2]]),{type:g[i-1],statement:!0});break;case 183:this.$=new e.If(g[i],e.Block.wrap([g[i-2]]),{type:g[i-1],statement:!0});break;case 184:this.$=new e.Op(g[i-1],g[i]);break;case 185:this.$=new e.Op("-",g[i]);break;case 186:this.$=new e.Op("+",g[i]);break;case 187:this.$=new e.Op("--",g[i]);break;case 188:this.$=new e.Op("++",g[i]);break;case 189:this.$=new e.Op("--",g[i-1],null,!0);break;case 190:this.$=new e.Op("++",g[i-1],null,!0);break;case 191:this.$=new e.Existence(g[i-1]);break;case 192:this.$=new e.Op("+",g[i-2],g[i]);break;case 193:this.$=new e.Op("-",g[i-2],g[i]);break;case 194:this.$=new e.Op(g[i-1],g[i-2],g[i]);break;case 195:this.$=new e.Op(g[i-1],g[i-2],g[i]);break;case 196:this.$=new e.Op(g[i-1],g[i-2],g[i]);break;case 197:this.$=new e.Op(g[i-1],g[i-2],g[i]);break;case 198:this.$=function(){return g[i-1].charAt(0)==="!"?(new e.Op(g[i-1].slice(1),g[i-2],g[i])).invert():new e.Op(g[i-1],g[i-2],g[i])}();break;case 199:this.$=new e.Assign(g[i-2],g[i],g[i-1]);break;case 200:this.$=new e.Assign(g[i-4],g[i-1],g[i-3]);break;case 201:this.$=new e.Extends(g[i-2],g[i])}},table:[{1:[2,1],3:1,4:2,5:3,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,5],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[3]},{1:[2,2],6:[1,74]},{6:[1,75]},{1:[2,4],6:[2,4],26:[2,4],101:[2,4]},{4:77,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,26:[1,76],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,7],6:[2,7],26:[2,7],101:[2,7],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,8],6:[2,8],26:[2,8],101:[2,8],102:90,103:[1,65],105:[1,66],108:91,109:[1,68],110:69,125:[1,89]},{1:[2,12],6:[2,12],25:[2,12],26:[2,12],49:[2,12],54:[2,12],57:[2,12],62:93,66:[1,95],67:[1,96],68:[1,97],69:98,70:[1,99],72:[2,12],73:[1,100],77:[2,12],80:92,83:[1,94],84:[2,107],85:[2,12],90:[2,12],92:[2,12],101:[2,12],103:[2,12],104:[2,12],105:[2,12],109:[2,12],117:[2,12],125:[2,12],127:[2,12],128:[2,12],131:[2,12],132:[2,12],133:[2,12],134:[2,12],135:[2,12],136:[2,12]},{1:[2,13],6:[2,13],25:[2,13],26:[2,13],49:[2,13],54:[2,13],57:[2,13],62:102,66:[1,95],67:[1,96],68:[1,97],69:98,70:[1,99],72:[2,13],73:[1,100],77:[2,13],80:101,83:[1,94],84:[2,107],85:[2,13],90:[2,13],92:[2,13],101:[2,13],103:[2,13],104:[2,13],105:[2,13],109:[2,13],117:[2,13],125:[2,13],127:[2,13],128:[2,13],131:[2,13],132:[2,13],133:[2,13],134:[2,13],135:[2,13],136:[2,13]},{1:[2,14],6:[2,14],25:[2,14],26:[2,14],49:[2,14],54:[2,14],57:[2,14],72:[2,14],77:[2,14],85:[2,14],90:[2,14],92:[2,14],101:[2,14],103:[2,14],104:[2,14],105:[2,14],109:[2,14],117:[2,14],125:[2,14],127:[2,14],128:[2,14],131:[2,14],132:[2,14],133:[2,14],134:[2,14],135:[2,14],136:[2,14]},{1:[2,15],6:[2,15],25:[2,15],26:[2,15],49:[2,15],54:[2,15],57:[2,15],72:[2,15],77:[2,15],85:[2,15],90:[2,15],92:[2,15],101:[2,15],103:[2,15],104:[2,15],105:[2,15],109:[2,15],117:[2,15],125:[2,15],127:[2,15],128:[2,15],131:[2,15],132:[2,15],133:[2,15],134:[2,15],135:[2,15],136:[2,15]},{1:[2,16],6:[2,16],25:[2,16],26:[2,16],49:[2,16],54:[2,16],57:[2,16],72:[2,16],77:[2,16],85:[2,16],90:[2,16],92:[2,16],101:[2,16],103:[2,16],104:[2,16],105:[2,16],109:[2,16],117:[2,16],125:[2,16],127:[2,16],128:[2,16],131:[2,16],132:[2,16],133:[2,16],134:[2,16],135:[2,16],136:[2,16]},{1:[2,17],6:[2,17],25:[2,17],26:[2,17],49:[2,17],54:[2,17],57:[2,17],72:[2,17],77:[2,17],85:[2,17],90:[2,17],92:[2,17],101:[2,17],103:[2,17],104:[2,17],105:[2,17],109:[2,17],117:[2,17],125:[2,17],127:[2,17],128:[2,17],131:[2,17],132:[2,17],133:[2,17],134:[2,17],135:[2,17],136:[2,17]},{1:[2,18],6:[2,18],25:[2,18],26:[2,18],49:[2,18],54:[2,18],57:[2,18],72:[2,18],77:[2,18],85:[2,18],90:[2,18],92:[2,18],101:[2,18],103:[2,18],104:[2,18],105:[2,18],109:[2,18],117:[2,18],125:[2,18],127:[2,18],128:[2,18],131:[2,18],132:[2,18],133:[2,18],134:[2,18],135:[2,18],136:[2,18]},{1:[2,19],6:[2,19],25:[2,19],26:[2,19],49:[2,19],54:[2,19],57:[2,19],72:[2,19],77:[2,19],85:[2,19],90:[2,19],92:[2,19],101:[2,19],103:[2,19],104:[2,19],105:[2,19],109:[2,19],117:[2,19],125:[2,19],127:[2,19],128:[2,19],131:[2,19],132:[2,19],133:[2,19],134:[2,19],135:[2,19],136:[2,19]},{1:[2,20],6:[2,20],25:[2,20],26:[2,20],49:[2,20],54:[2,20],57:[2,20],72:[2,20],77:[2,20],85:[2,20],90:[2,20],92:[2,20],101:[2,20],103:[2,20],104:[2,20],105:[2,20],109:[2,20],117:[2,20],125:[2,20],127:[2,20],128:[2,20],131:[2,20],132:[2,20],133:[2,20],134:[2,20],135:[2,20],136:[2,20]},{1:[2,21],6:[2,21],25:[2,21],26:[2,21],49:[2,21],54:[2,21],57:[2,21],72:[2,21],77:[2,21],85:[2,21],90:[2,21],92:[2,21],101:[2,21],103:[2,21],104:[2,21],105:[2,21],109:[2,21],117:[2,21],125:[2,21],127:[2,21],128:[2,21],131:[2,21],132:[2,21],133:[2,21],134:[2,21],135:[2,21],136:[2,21]},{1:[2,22],6:[2,22],25:[2,22],26:[2,22],49:[2,22],54:[2,22],57:[2,22],72:[2,22],77:[2,22],85:[2,22],90:[2,22],92:[2,22],101:[2,22],103:[2,22],104:[2,22],105:[2,22],109:[2,22],117:[2,22],125:[2,22],127:[2,22],128:[2,22],131:[2,22],132:[2,22],133:[2,22],134:[2,22],135:[2,22],136:[2,22]},{1:[2,23],6:[2,23],25:[2,23],26:[2,23],49:[2,23],54:[2,23],57:[2,23],72:[2,23],77:[2,23],85:[2,23],90:[2,23],92:[2,23],101:[2,23],103:[2,23],104:[2,23],105:[2,23],109:[2,23],117:[2,23],125:[2,23],127:[2,23],128:[2,23],131:[2,23],132:[2,23],133:[2,23],134:[2,23],135:[2,23],136:[2,23]},{1:[2,9],6:[2,9],26:[2,9],101:[2,9],103:[2,9],105:[2,9],109:[2,9],125:[2,9]},{1:[2,10],6:[2,10],26:[2,10],101:[2,10],103:[2,10],105:[2,10],109:[2,10],125:[2,10]},{1:[2,11],6:[2,11],26:[2,11],101:[2,11],103:[2,11],105:[2,11],109:[2,11],125:[2,11]},{1:[2,75],6:[2,75],25:[2,75],26:[2,75],40:[1,103],49:[2,75],54:[2,75],57:[2,75],66:[2,75],67:[2,75],68:[2,75],70:[2,75],72:[2,75],73:[2,75],77:[2,75],83:[2,75],84:[2,75],85:[2,75],90:[2,75],92:[2,75],101:[2,75],103:[2,75],104:[2,75],105:[2,75],109:[2,75],117:[2,75],125:[2,75],127:[2,75],128:[2,75],131:[2,75],132:[2,75],133:[2,75],134:[2,75],135:[2,75],136:[2,75]},{1:[2,76],6:[2,76],25:[2,76],26:[2,76],49:[2,76],54:[2,76],57:[2,76],66:[2,76],67:[2,76],68:[2,76],70:[2,76],72:[2,76],73:[2,76],77:[2,76],83:[2,76],84:[2,76],85:[2,76],90:[2,76],92:[2,76],101:[2,76],103:[2,76],104:[2,76],105:[2,76],109:[2,76],117:[2,76],125:[2,76],127:[2,76],128:[2,76],131:[2,76],132:[2,76],133:[2,76],134:[2,76],135:[2,76],136:[2,76]},{1:[2,77],6:[2,77],25:[2,77],26:[2,77],49:[2,77],54:[2,77],57:[2,77],66:[2,77],67:[2,77],68:[2,77],70:[2,77],72:[2,77],73:[2,77],77:[2,77],83:[2,77],84:[2,77],85:[2,77],90:[2,77],92:[2,77],101:[2,77],103:[2,77],104:[2,77],105:[2,77],109:[2,77],117:[2,77],125:[2,77],127:[2,77],128:[2,77],131:[2,77],132:[2,77],133:[2,77],134:[2,77],135:[2,77],136:[2,77]},{1:[2,78],6:[2,78],25:[2,78],26:[2,78],49:[2,78],54:[2,78],57:[2,78],66:[2,78],67:[2,78],68:[2,78],70:[2,78],72:[2,78],73:[2,78],77:[2,78],83:[2,78],84:[2,78],85:[2,78],90:[2,78],92:[2,78],101:[2,78],103:[2,78],104:[2,78],105:[2,78],109:[2,78],117:[2,78],125:[2,78],127:[2,78],128:[2,78],131:[2,78],132:[2,78],133:[2,78],134:[2,78],135:[2,78],136:[2,78]},{1:[2,79],6:[2,79],25:[2,79],26:[2,79],49:[2,79],54:[2,79],57:[2,79],66:[2,79],67:[2,79],68:[2,79],70:[2,79],72:[2,79],73:[2,79],77:[2,79],83:[2,79],84:[2,79],85:[2,79],90:[2,79],92:[2,79],101:[2,79],103:[2,79],104:[2,79],105:[2,79],109:[2,79],117:[2,79],125:[2,79],127:[2,79],128:[2,79],131:[2,79],132:[2,79],133:[2,79],134:[2,79],135:[2,79],136:[2,79]},{1:[2,105],6:[2,105],25:[2,105],26:[2,105],49:[2,105],54:[2,105],57:[2,105],66:[2,105],67:[2,105],68:[2,105],70:[2,105],72:[2,105],73:[2,105],77:[2,105],81:104,83:[2,105],84:[1,105],85:[2,105],90:[2,105],92:[2,105],101:[2,105],103:[2,105],104:[2,105],105:[2,105],109:[2,105],117:[2,105],125:[2,105],127:[2,105],128:[2,105],131:[2,105],132:[2,105],133:[2,105],134:[2,105],135:[2,105],136:[2,105]},{6:[2,55],25:[2,55],27:109,28:[1,73],44:110,48:106,49:[2,55],54:[2,55],55:107,56:108,58:111,59:112,75:[1,70],88:[1,113],89:[1,114]},{5:115,25:[1,5]},{8:116,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:118,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:119,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{13:121,14:122,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:123,44:63,58:47,59:48,61:120,63:25,64:26,65:27,75:[1,70],82:[1,28],87:[1,58],88:[1,59],89:[1,57],100:[1,56]},{13:121,14:122,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:123,44:63,58:47,59:48,61:124,63:25,64:26,65:27,75:[1,70],82:[1,28],87:[1,58],88:[1,59],89:[1,57],100:[1,56]},{1:[2,72],6:[2,72],25:[2,72],26:[2,72],40:[2,72],49:[2,72],54:[2,72],57:[2,72],66:[2,72],67:[2,72],68:[2,72],70:[2,72],72:[2,72],73:[2,72],77:[2,72],79:[1,128],83:[2,72],84:[2,72],85:[2,72],90:[2,72],92:[2,72],101:[2,72],103:[2,72],104:[2,72],105:[2,72],109:[2,72],117:[2,72],125:[2,72],127:[2,72],128:[2,72],129:[1,125],130:[1,126],131:[2,72],132:[2,72],133:[2,72],134:[2,72],135:[2,72],136:[2,72],137:[1,127]},{1:[2,180],6:[2,180],25:[2,180],26:[2,180],49:[2,180],54:[2,180],57:[2,180],72:[2,180],77:[2,180],85:[2,180],90:[2,180],92:[2,180],101:[2,180],103:[2,180],104:[2,180],105:[2,180],109:[2,180],117:[2,180],120:[1,129],125:[2,180],127:[2,180],128:[2,180],131:[2,180],132:[2,180],133:[2,180],134:[2,180],135:[2,180],136:[2,180]},{5:130,25:[1,5]},{5:131,25:[1,5]},{1:[2,147],6:[2,147],25:[2,147],26:[2,147],49:[2,147],54:[2,147],57:[2,147],72:[2,147],77:[2,147],85:[2,147],90:[2,147],92:[2,147],101:[2,147],103:[2,147],104:[2,147],105:[2,147],109:[2,147],117:[2,147],125:[2,147],127:[2,147],128:[2,147],131:[2,147],132:[2,147],133:[2,147],134:[2,147],135:[2,147],136:[2,147]},{5:132,25:[1,5]},{8:133,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,134],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,95],5:135,6:[2,95],13:121,14:122,25:[1,5],26:[2,95],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:123,44:63,49:[2,95],54:[2,95],57:[2,95],58:47,59:48,61:137,63:25,64:26,65:27,72:[2,95],75:[1,70],77:[2,95],79:[1,136],82:[1,28],85:[2,95],87:[1,58],88:[1,59],89:[1,57],90:[2,95],92:[2,95],100:[1,56],101:[2,95],103:[2,95],104:[2,95],105:[2,95],109:[2,95],117:[2,95],125:[2,95],127:[2,95],128:[2,95],131:[2,95],132:[2,95],133:[2,95],134:[2,95],135:[2,95],136:[2,95]},{8:138,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,47],6:[2,47],8:139,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,26:[2,47],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],101:[2,47],102:39,103:[2,47],105:[2,47],106:40,107:[1,67],108:41,109:[2,47],110:69,118:[1,42],123:37,124:[1,64],125:[2,47],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,48],6:[2,48],25:[2,48],26:[2,48],54:[2,48],77:[2,48],101:[2,48],103:[2,48],105:[2,48],109:[2,48],125:[2,48]},{1:[2,73],6:[2,73],25:[2,73],26:[2,73],40:[2,73],49:[2,73],54:[2,73],57:[2,73],66:[2,73],67:[2,73],68:[2,73],70:[2,73],72:[2,73],73:[2,73],77:[2,73],83:[2,73],84:[2,73],85:[2,73],90:[2,73],92:[2,73],101:[2,73],103:[2,73],104:[2,73],105:[2,73],109:[2,73],117:[2,73],125:[2,73],127:[2,73],128:[2,73],131:[2,73],132:[2,73],133:[2,73],134:[2,73],135:[2,73],136:[2,73]},{1:[2,74],6:[2,74],25:[2,74],26:[2,74],40:[2,74],49:[2,74],54:[2,74],57:[2,74],66:[2,74],67:[2,74],68:[2,74],70:[2,74],72:[2,74],73:[2,74],77:[2,74],83:[2,74],84:[2,74],85:[2,74],90:[2,74],92:[2,74],101:[2,74],103:[2,74],104:[2,74],105:[2,74],109:[2,74],117:[2,74],125:[2,74],127:[2,74],128:[2,74],131:[2,74],132:[2,74],133:[2,74],134:[2,74],135:[2,74],136:[2,74]},{1:[2,29],6:[2,29],25:[2,29],26:[2,29],49:[2,29],54:[2,29],57:[2,29],66:[2,29],67:[2,29],68:[2,29],70:[2,29],72:[2,29],73:[2,29],77:[2,29],83:[2,29],84:[2,29],85:[2,29],90:[2,29],92:[2,29],101:[2,29],103:[2,29],104:[2,29],105:[2,29],109:[2,29],117:[2,29],125:[2,29],127:[2,29],128:[2,29],131:[2,29],132:[2,29],133:[2,29],134:[2,29],135:[2,29],136:[2,29]},{1:[2,30],6:[2,30],25:[2,30],26:[2,30],49:[2,30],54:[2,30],57:[2,30],66:[2,30],67:[2,30],68:[2,30],70:[2,30],72:[2,30],73:[2,30],77:[2,30],83:[2,30],84:[2,30],85:[2,30],90:[2,30],92:[2,30],101:[2,30],103:[2,30],104:[2,30],105:[2,30],109:[2,30],117:[2,30],125:[2,30],127:[2,30],128:[2,30],131:[2,30],132:[2,30],133:[2,30],134:[2,30],135:[2,30],136:[2,30]},{1:[2,31],6:[2,31],25:[2,31],26:[2,31],49:[2,31],54:[2,31],57:[2,31],66:[2,31],67:[2,31],68:[2,31],70:[2,31],72:[2,31],73:[2,31],77:[2,31],83:[2,31],84:[2,31],85:[2,31],90:[2,31],92:[2,31],101:[2,31],103:[2,31],104:[2,31],105:[2,31],109:[2,31],117:[2,31],125:[2,31],127:[2,31],128:[2,31],131:[2,31],132:[2,31],133:[2,31],134:[2,31],135:[2,31],136:[2,31]},{1:[2,32],6:[2,32],25:[2,32],26:[2,32],49:[2,32],54:[2,32],57:[2,32],66:[2,32],67:[2,32],68:[2,32],70:[2,32],72:[2,32],73:[2,32],77:[2,32],83:[2,32],84:[2,32],85:[2,32],90:[2,32],92:[2,32],101:[2,32],103:[2,32],104:[2,32],105:[2,32],109:[2,32],117:[2,32],125:[2,32],127:[2,32],128:[2,32],131:[2,32],132:[2,32],133:[2,32],134:[2,32],135:[2,32],136:[2,32]},{1:[2,33],6:[2,33],25:[2,33],26:[2,33],49:[2,33],54:[2,33],57:[2,33],66:[2,33],67:[2,33],68:[2,33],70:[2,33],72:[2,33],73:[2,33],77:[2,33],83:[2,33],84:[2,33],85:[2,33],90:[2,33],92:[2,33],101:[2,33],103:[2,33],104:[2,33],105:[2,33],109:[2,33],117:[2,33],125:[2,33],127:[2,33],128:[2,33],131:[2,33],132:[2,33],133:[2,33],134:[2,33],135:[2,33],136:[2,33]},{1:[2,34],6:[2,34],25:[2,34],26:[2,34],49:[2,34],54:[2,34],57:[2,34],66:[2,34],67:[2,34],68:[2,34],70:[2,34],72:[2,34],73:[2,34],77:[2,34],83:[2,34],84:[2,34],85:[2,34],90:[2,34],92:[2,34],101:[2,34],103:[2,34],104:[2,34],105:[2,34],109:[2,34],117:[2,34],125:[2,34],127:[2,34],128:[2,34],131:[2,34],132:[2,34],133:[2,34],134:[2,34],135:[2,34],136:[2,34]},{1:[2,35],6:[2,35],25:[2,35],26:[2,35],49:[2,35],54:[2,35],57:[2,35],66:[2,35],67:[2,35],68:[2,35],70:[2,35],72:[2,35],73:[2,35],77:[2,35],83:[2,35],84:[2,35],85:[2,35],90:[2,35],92:[2,35],101:[2,35],103:[2,35],104:[2,35],105:[2,35],109:[2,35],117:[2,35],125:[2,35],127:[2,35],128:[2,35],131:[2,35],132:[2,35],133:[2,35],134:[2,35],135:[2,35],136:[2,35]},{4:140,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,141],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:142,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,146],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],86:144,87:[1,58],88:[1,59],89:[1,57],90:[1,143],93:145,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,111],6:[2,111],25:[2,111],26:[2,111],49:[2,111],54:[2,111],57:[2,111],66:[2,111],67:[2,111],68:[2,111],70:[2,111],72:[2,111],73:[2,111],77:[2,111],83:[2,111],84:[2,111],85:[2,111],90:[2,111],92:[2,111],101:[2,111],103:[2,111],104:[2,111],105:[2,111],109:[2,111],117:[2,111],125:[2,111],127:[2,111],128:[2,111],131:[2,111],132:[2,111],133:[2,111],134:[2,111],135:[2,111],136:[2,111]},{1:[2,112],6:[2,112],25:[2,112],26:[2,112],27:148,28:[1,73],49:[2,112],54:[2,112],57:[2,112],66:[2,112],67:[2,112],68:[2,112],70:[2,112],72:[2,112],73:[2,112],77:[2,112],83:[2,112],84:[2,112],85:[2,112],90:[2,112],92:[2,112],101:[2,112],103:[2,112],104:[2,112],105:[2,112],109:[2,112],117:[2,112],125:[2,112],127:[2,112],128:[2,112],131:[2,112],132:[2,112],133:[2,112],134:[2,112],135:[2,112],136:[2,112]},{25:[2,51]},{25:[2,52]},{1:[2,68],6:[2,68],25:[2,68],26:[2,68],40:[2,68],49:[2,68],54:[2,68],57:[2,68],66:[2,68],67:[2,68],68:[2,68],70:[2,68],72:[2,68],73:[2,68],77:[2,68],79:[2,68],83:[2,68],84:[2,68],85:[2,68],90:[2,68],92:[2,68],101:[2,68],103:[2,68],104:[2,68],105:[2,68],109:[2,68],117:[2,68],125:[2,68],127:[2,68],128:[2,68],129:[2,68],130:[2,68],131:[2,68],132:[2,68],133:[2,68],134:[2,68],135:[2,68],136:[2,68],137:[2,68]},{1:[2,71],6:[2,71],25:[2,71],26:[2,71],40:[2,71],49:[2,71],54:[2,71],57:[2,71],66:[2,71],67:[2,71],68:[2,71],70:[2,71],72:[2,71],73:[2,71],77:[2,71],79:[2,71],83:[2,71],84:[2,71],85:[2,71],90:[2,71],92:[2,71],101:[2,71],103:[2,71],104:[2,71],105:[2,71],109:[2,71],117:[2,71],125:[2,71],127:[2,71],128:[2,71],129:[2,71],130:[2,71],131:[2,71],132:[2,71],133:[2,71],134:[2,71],135:[2,71],136:[2,71],137:[2,71]},{8:149,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:150,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:151,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{5:152,8:153,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,5],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{27:158,28:[1,73],44:159,58:160,59:161,64:154,75:[1,70],88:[1,113],89:[1,57],112:155,113:[1,156],114:157},{111:162,115:[1,163],116:[1,164]},{6:[2,90],11:168,25:[2,90],27:169,28:[1,73],29:170,30:[1,71],31:[1,72],41:166,42:167,44:171,46:[1,46],54:[2,90],76:165,77:[2,90],88:[1,113]},{1:[2,27],6:[2,27],25:[2,27],26:[2,27],43:[2,27],49:[2,27],54:[2,27],57:[2,27],66:[2,27],67:[2,27],68:[2,27],70:[2,27],72:[2,27],73:[2,27],77:[2,27],83:[2,27],84:[2,27],85:[2,27],90:[2,27],92:[2,27],101:[2,27],103:[2,27],104:[2,27],105:[2,27],109:[2,27],117:[2,27],125:[2,27],127:[2,27],128:[2,27],131:[2,27],132:[2,27],133:[2,27],134:[2,27],135:[2,27],136:[2,27]},{1:[2,28],6:[2,28],25:[2,28],26:[2,28],43:[2,28],49:[2,28],54:[2,28],57:[2,28],66:[2,28],67:[2,28],68:[2,28],70:[2,28],72:[2,28],73:[2,28],77:[2,28],83:[2,28],84:[2,28],85:[2,28],90:[2,28],92:[2,28],101:[2,28],103:[2,28],104:[2,28],105:[2,28],109:[2,28],117:[2,28],125:[2,28],127:[2,28],128:[2,28],131:[2,28],132:[2,28],133:[2,28],134:[2,28],135:[2,28],136:[2,28]},{1:[2,26],6:[2,26],25:[2,26],26:[2,26],40:[2,26],43:[2,26],49:[2,26],54:[2,26],57:[2,26],66:[2,26],67:[2,26],68:[2,26],70:[2,26],72:[2,26],73:[2,26],77:[2,26],79:[2,26],83:[2,26],84:[2,26],85:[2,26],90:[2,26],92:[2,26],101:[2,26],103:[2,26],104:[2,26],105:[2,26],109:[2,26],115:[2,26],116:[2,26],117:[2,26],125:[2,26],127:[2,26],128:[2,26],129:[2,26],130:[2,26],131:[2,26],132:[2,26],133:[2,26],134:[2,26],135:[2,26],136:[2,26],137:[2,26]},{1:[2,6],6:[2,6],7:172,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,26:[2,6],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],101:[2,6],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,3]},{1:[2,24],6:[2,24],25:[2,24],26:[2,24],49:[2,24],54:[2,24],57:[2,24],72:[2,24],77:[2,24],85:[2,24],90:[2,24],92:[2,24],97:[2,24],98:[2,24],101:[2,24],103:[2,24],104:[2,24],105:[2,24],109:[2,24],117:[2,24],120:[2,24],122:[2,24],125:[2,24],127:[2,24],128:[2,24],131:[2,24],132:[2,24],133:[2,24],134:[2,24],135:[2,24],136:[2,24]},{6:[1,74],26:[1,173]},{1:[2,191],6:[2,191],25:[2,191],26:[2,191],49:[2,191],54:[2,191],57:[2,191],72:[2,191],77:[2,191],85:[2,191],90:[2,191],92:[2,191],101:[2,191],103:[2,191],104:[2,191],105:[2,191],109:[2,191],117:[2,191],125:[2,191],127:[2,191],128:[2,191],131:[2,191],132:[2,191],133:[2,191],134:[2,191],135:[2,191],136:[2,191]},{8:174,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:175,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:176,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:177,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:178,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:179,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:180,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:181,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,146],6:[2,146],25:[2,146],26:[2,146],49:[2,146],54:[2,146],57:[2,146],72:[2,146],77:[2,146],85:[2,146],90:[2,146],92:[2,146],101:[2,146],103:[2,146],104:[2,146],105:[2,146],109:[2,146],117:[2,146],125:[2,146],127:[2,146],128:[2,146],131:[2,146],132:[2,146],133:[2,146],134:[2,146],135:[2,146],136:[2,146]},{1:[2,151],6:[2,151],25:[2,151],26:[2,151],49:[2,151],54:[2,151],57:[2,151],72:[2,151],77:[2,151],85:[2,151],90:[2,151],92:[2,151],101:[2,151],103:[2,151],104:[2,151],105:[2,151],109:[2,151],117:[2,151],125:[2,151],127:[2,151],128:[2,151],131:[2,151],132:[2,151],133:[2,151],134:[2,151],135:[2,151],136:[2,151]},{8:182,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,145],6:[2,145],25:[2,145],26:[2,145],49:[2,145],54:[2,145],57:[2,145],72:[2,145],77:[2,145],85:[2,145],90:[2,145],92:[2,145],101:[2,145],103:[2,145],104:[2,145],105:[2,145],109:[2,145],117:[2,145],125:[2,145],127:[2,145],128:[2,145],131:[2,145],132:[2,145],133:[2,145],134:[2,145],135:[2,145],136:[2,145]},{1:[2,150],6:[2,150],25:[2,150],26:[2,150],49:[2,150],54:[2,150],57:[2,150],72:[2,150],77:[2,150],85:[2,150],90:[2,150],92:[2,150],101:[2,150],103:[2,150],104:[2,150],105:[2,150],109:[2,150],117:[2,150],125:[2,150],127:[2,150],128:[2,150],131:[2,150],132:[2,150],133:[2,150],134:[2,150],135:[2,150],136:[2,150]},{81:183,84:[1,105]},{1:[2,69],6:[2,69],25:[2,69],26:[2,69],40:[2,69],49:[2,69],54:[2,69],57:[2,69],66:[2,69],67:[2,69],68:[2,69],70:[2,69],72:[2,69],73:[2,69],77:[2,69],79:[2,69],83:[2,69],84:[2,69],85:[2,69],90:[2,69],92:[2,69],101:[2,69],103:[2,69],104:[2,69],105:[2,69],109:[2,69],117:[2,69],125:[2,69],127:[2,69],128:[2,69],129:[2,69],130:[2,69],131:[2,69],132:[2,69],133:[2,69],134:[2,69],135:[2,69],136:[2,69],137:[2,69]},{84:[2,108]},{27:184,28:[1,73]},{27:185,28:[1,73]},{1:[2,83],6:[2,83],25:[2,83],26:[2,83],27:186,28:[1,73],40:[2,83],49:[2,83],54:[2,83],57:[2,83],66:[2,83],67:[2,83],68:[2,83],70:[2,83],72:[2,83],73:[2,83],77:[2,83],79:[2,83],83:[2,83],84:[2,83],85:[2,83],90:[2,83],92:[2,83],101:[2,83],103:[2,83],104:[2,83],105:[2,83],109:[2,83],117:[2,83],125:[2,83],127:[2,83],128:[2,83],129:[2,83],130:[2,83],131:[2,83],132:[2,83],133:[2,83],134:[2,83],135:[2,83],136:[2,83],137:[2,83]},{1:[2,84],6:[2,84],25:[2,84],26:[2,84],40:[2,84],49:[2,84],54:[2,84],57:[2,84],66:[2,84],67:[2,84],68:[2,84],70:[2,84],72:[2,84],73:[2,84],77:[2,84],79:[2,84],83:[2,84],84:[2,84],85:[2,84],90:[2,84],92:[2,84],101:[2,84],103:[2,84],104:[2,84],105:[2,84],109:[2,84],117:[2,84],125:[2,84],127:[2,84],128:[2,84],129:[2,84],130:[2,84],131:[2,84],132:[2,84],133:[2,84],134:[2,84],135:[2,84],136:[2,84],137:[2,84]},{8:188,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],57:[1,192],58:47,59:48,61:36,63:25,64:26,65:27,71:187,74:189,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],91:190,92:[1,191],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{69:193,70:[1,99],73:[1,100]},{81:194,84:[1,105]},{1:[2,70],6:[2,70],25:[2,70],26:[2,70],40:[2,70],49:[2,70],54:[2,70],57:[2,70],66:[2,70],67:[2,70],68:[2,70],70:[2,70],72:[2,70],73:[2,70],77:[2,70],79:[2,70],83:[2,70],84:[2,70],85:[2,70],90:[2,70],92:[2,70],101:[2,70],103:[2,70],104:[2,70],105:[2,70],109:[2,70],117:[2,70],125:[2,70],127:[2,70],128:[2,70],129:[2,70],130:[2,70],131:[2,70],132:[2,70],133:[2,70],134:[2,70],135:[2,70],136:[2,70],137:[2,70]},{6:[1,196],8:195,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,197],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,106],6:[2,106],25:[2,106],26:[2,106],49:[2,106],54:[2,106],57:[2,106],66:[2,106],67:[2,106],68:[2,106],70:[2,106],72:[2,106],73:[2,106],77:[2,106],83:[2,106],84:[2,106],85:[2,106],90:[2,106],92:[2,106],101:[2,106],103:[2,106],104:[2,106],105:[2,106],109:[2,106],117:[2,106],125:[2,106],127:[2,106],128:[2,106],131:[2,106],132:[2,106],133:[2,106],134:[2,106],135:[2,106],136:[2,106]},{8:200,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,146],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],85:[1,198],86:199,87:[1,58],88:[1,59],89:[1,57],93:145,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,53],25:[2,53],49:[1,201],53:203,54:[1,202]},{6:[2,56],25:[2,56],26:[2,56],49:[2,56],54:[2,56]},{6:[2,60],25:[2,60],26:[2,60],40:[1,205],49:[2,60],54:[2,60],57:[1,204]},{6:[2,63],25:[2,63],26:[2,63],40:[2,63],49:[2,63],54:[2,63],57:[2,63]},{6:[2,64],25:[2,64],26:[2,64],40:[2,64],49:[2,64],54:[2,64],57:[2,64]},{6:[2,65],25:[2,65],26:[2,65],40:[2,65],49:[2,65],54:[2,65],57:[2,65]},{6:[2,66],25:[2,66],26:[2,66],40:[2,66],49:[2,66],54:[2,66],57:[2,66]},{27:148,28:[1,73]},{8:200,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,146],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],86:144,87:[1,58],88:[1,59],89:[1,57],90:[1,143],93:145,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,50],6:[2,50],25:[2,50],26:[2,50],49:[2,50],54:[2,50],57:[2,50],72:[2,50],77:[2,50],85:[2,50],90:[2,50],92:[2,50],101:[2,50],103:[2,50],104:[2,50],105:[2,50],109:[2,50],117:[2,50],125:[2,50],127:[2,50],128:[2,50],131:[2,50],132:[2,50],133:[2,50],134:[2,50],135:[2,50],136:[2,50]},{1:[2,184],6:[2,184],25:[2,184],26:[2,184],49:[2,184],54:[2,184],57:[2,184],72:[2,184],77:[2,184],85:[2,184],90:[2,184],92:[2,184],101:[2,184],102:87,103:[2,184],104:[2,184],105:[2,184],108:88,109:[2,184],110:69,117:[2,184],125:[2,184],127:[2,184],128:[2,184],131:[1,78],132:[2,184],133:[2,184],134:[2,184],135:[2,184],136:[2,184]},{102:90,103:[1,65],105:[1,66],108:91,109:[1,68],110:69,125:[1,89]},{1:[2,185],6:[2,185],25:[2,185],26:[2,185],49:[2,185],54:[2,185],57:[2,185],72:[2,185],77:[2,185],85:[2,185],90:[2,185],92:[2,185],101:[2,185],102:87,103:[2,185],104:[2,185],105:[2,185],108:88,109:[2,185],110:69,117:[2,185],125:[2,185],127:[2,185],128:[2,185],131:[1,78],132:[2,185],133:[2,185],134:[2,185],135:[2,185],136:[2,185]},{1:[2,186],6:[2,186],25:[2,186],26:[2,186],49:[2,186],54:[2,186],57:[2,186],72:[2,186],77:[2,186],85:[2,186],90:[2,186],92:[2,186],101:[2,186],102:87,103:[2,186],104:[2,186],105:[2,186],108:88,109:[2,186],110:69,117:[2,186],125:[2,186],127:[2,186],128:[2,186],131:[1,78],132:[2,186],133:[2,186],134:[2,186],135:[2,186],136:[2,186]},{1:[2,187],6:[2,187],25:[2,187],26:[2,187],49:[2,187],54:[2,187],57:[2,187],66:[2,72],67:[2,72],68:[2,72],70:[2,72],72:[2,187],73:[2,72],77:[2,187],83:[2,72],84:[2,72],85:[2,187],90:[2,187],92:[2,187],101:[2,187],103:[2,187],104:[2,187],105:[2,187],109:[2,187],117:[2,187],125:[2,187],127:[2,187],128:[2,187],131:[2,187],132:[2,187],133:[2,187],134:[2,187],135:[2,187],136:[2,187]},{62:93,66:[1,95],67:[1,96],68:[1,97],69:98,70:[1,99],73:[1,100],80:92,83:[1,94],84:[2,107]},{62:102,66:[1,95],67:[1,96],68:[1,97],69:98,70:[1,99],73:[1,100],80:101,83:[1,94],84:[2,107]},{66:[2,75],67:[2,75],68:[2,75],70:[2,75],73:[2,75],83:[2,75],84:[2,75]},{1:[2,188],6:[2,188],25:[2,188],26:[2,188],49:[2,188],54:[2,188],57:[2,188],66:[2,72],67:[2,72],68:[2,72],70:[2,72],72:[2,188],73:[2,72],77:[2,188],83:[2,72],84:[2,72],85:[2,188],90:[2,188],92:[2,188],101:[2,188],103:[2,188],104:[2,188],105:[2,188],109:[2,188],117:[2,188],125:[2,188],127:[2,188],128:[2,188],131:[2,188],132:[2,188],133:[2,188],134:[2,188],135:[2,188],136:[2,188]},{1:[2,189],6:[2,189],25:[2,189],26:[2,189],49:[2,189],54:[2,189],57:[2,189],72:[2,189],77:[2,189],85:[2,189],90:[2,189],92:[2,189],101:[2,189],103:[2,189],104:[2,189],105:[2,189],109:[2,189],117:[2,189],125:[2,189],127:[2,189],128:[2,189],131:[2,189],132:[2,189],133:[2,189],134:[2,189],135:[2,189],136:[2,189]},{1:[2,190],6:[2,190],25:[2,190],26:[2,190],49:[2,190],54:[2,190],57:[2,190],72:[2,190],77:[2,190],85:[2,190],90:[2,190],92:[2,190],101:[2,190],103:[2,190],104:[2,190],105:[2,190],109:[2,190],117:[2,190],125:[2,190],127:[2,190],128:[2,190],131:[2,190],132:[2,190],133:[2,190],134:[2,190],135:[2,190],136:[2,190]},{8:206,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,207],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:208,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{5:209,25:[1,5],124:[1,210]},{1:[2,132],6:[2,132],25:[2,132],26:[2,132],49:[2,132],54:[2,132],57:[2,132],72:[2,132],77:[2,132],85:[2,132],90:[2,132],92:[2,132],96:211,97:[1,212],98:[1,213],101:[2,132],103:[2,132],104:[2,132],105:[2,132],109:[2,132],117:[2,132],125:[2,132],127:[2,132],128:[2,132],131:[2,132],132:[2,132],133:[2,132],134:[2,132],135:[2,132],136:[2,132]},{1:[2,144],6:[2,144],25:[2,144],26:[2,144],49:[2,144],54:[2,144],57:[2,144],72:[2,144],77:[2,144],85:[2,144],90:[2,144],92:[2,144],101:[2,144],103:[2,144],104:[2,144],105:[2,144],109:[2,144],117:[2,144],125:[2,144],127:[2,144],128:[2,144],131:[2,144],132:[2,144],133:[2,144],134:[2,144],135:[2,144],136:[2,144]},{1:[2,152],6:[2,152],25:[2,152],26:[2,152],49:[2,152],54:[2,152],57:[2,152],72:[2,152],77:[2,152],85:[2,152],90:[2,152],92:[2,152],101:[2,152],103:[2,152],104:[2,152],105:[2,152],109:[2,152],117:[2,152],125:[2,152],127:[2,152],128:[2,152],131:[2,152],132:[2,152],133:[2,152],134:[2,152],135:[2,152],136:[2,152]},{25:[1,214],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{119:215,121:216,122:[1,217]},{1:[2,96],6:[2,96],25:[2,96],26:[2,96],49:[2,96],54:[2,96],57:[2,96],72:[2,96],77:[2,96],85:[2,96],90:[2,96],92:[2,96],101:[2,96],103:[2,96],104:[2,96],105:[2,96],109:[2,96],117:[2,96],125:[2,96],127:[2,96],128:[2,96],131:[2,96],132:[2,96],133:[2,96],134:[2,96],135:[2,96],136:[2,96]},{8:218,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,99],5:219,6:[2,99],25:[1,5],26:[2,99],49:[2,99],54:[2,99],57:[2,99],66:[2,72],67:[2,72],68:[2,72],70:[2,72],72:[2,99],73:[2,72],77:[2,99],79:[1,220],83:[2,72],84:[2,72],85:[2,99],90:[2,99],92:[2,99],101:[2,99],103:[2,99],104:[2,99],105:[2,99],109:[2,99],117:[2,99],125:[2,99],127:[2,99],128:[2,99],131:[2,99],132:[2,99],133:[2,99],134:[2,99],135:[2,99],136:[2,99]},{1:[2,137],6:[2,137],25:[2,137],26:[2,137],49:[2,137],54:[2,137],57:[2,137],72:[2,137],77:[2,137],85:[2,137],90:[2,137],92:[2,137],101:[2,137],102:87,103:[2,137],104:[2,137],105:[2,137],108:88,109:[2,137],110:69,117:[2,137],125:[2,137],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,46],6:[2,46],26:[2,46],101:[2,46],102:87,103:[2,46],105:[2,46],108:88,109:[2,46],110:69,125:[2,46],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[1,74],101:[1,221]},{4:222,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,128],25:[2,128],54:[2,128],57:[1,224],90:[2,128],91:223,92:[1,191],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,114],6:[2,114],25:[2,114],26:[2,114],40:[2,114],49:[2,114],54:[2,114],57:[2,114],66:[2,114],67:[2,114],68:[2,114],70:[2,114],72:[2,114],73:[2,114],77:[2,114],83:[2,114],84:[2,114],85:[2,114],90:[2,114],92:[2,114],101:[2,114],103:[2,114],104:[2,114],105:[2,114],109:[2,114],115:[2,114],116:[2,114],117:[2,114],125:[2,114],127:[2,114],128:[2,114],131:[2,114],132:[2,114],133:[2,114],134:[2,114],135:[2,114],136:[2,114]},{6:[2,53],25:[2,53],53:225,54:[1,226],90:[2,53]},{6:[2,123],25:[2,123],26:[2,123],54:[2,123],85:[2,123],90:[2,123]},{8:200,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,146],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],86:227,87:[1,58],88:[1,59],89:[1,57],93:145,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,129],25:[2,129],26:[2,129],54:[2,129],85:[2,129],90:[2,129]},{1:[2,113],6:[2,113],25:[2,113],26:[2,113],40:[2,113],43:[2,113],49:[2,113],54:[2,113],57:[2,113],66:[2,113],67:[2,113],68:[2,113],70:[2,113],72:[2,113],73:[2,113],77:[2,113],79:[2,113],83:[2,113],84:[2,113],85:[2,113],90:[2,113],92:[2,113],101:[2,113],103:[2,113],104:[2,113],105:[2,113],109:[2,113],115:[2,113],116:[2,113],117:[2,113],125:[2,113],127:[2,113],128:[2,113],129:[2,113],130:[2,113],131:[2,113],132:[2,113],133:[2,113],134:[2,113],135:[2,113],136:[2,113],137:[2,113]},{5:228,25:[1,5],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,140],6:[2,140],25:[2,140],26:[2,140],49:[2,140],54:[2,140],57:[2,140],72:[2,140],77:[2,140],85:[2,140],90:[2,140],92:[2,140],101:[2,140],102:87,103:[1,65],104:[1,229],105:[1,66],108:88,109:[1,68],110:69,117:[2,140],125:[2,140],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,142],6:[2,142],25:[2,142],26:[2,142],49:[2,142],54:[2,142],57:[2,142],72:[2,142],77:[2,142],85:[2,142],90:[2,142],92:[2,142],101:[2,142],102:87,103:[1,65],104:[1,230],105:[1,66],108:88,109:[1,68],110:69,117:[2,142],125:[2,142],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,148],6:[2,148],25:[2,148],26:[2,148],49:[2,148],54:[2,148],57:[2,148],72:[2,148],77:[2,148],85:[2,148],90:[2,148],92:[2,148],101:[2,148],103:[2,148],104:[2,148],105:[2,148],109:[2,148],117:[2,148],125:[2,148],127:[2,148],128:[2,148],131:[2,148],132:[2,148],133:[2,148],134:[2,148],135:[2,148],136:[2,148]},{1:[2,149],6:[2,149],25:[2,149],26:[2,149],49:[2,149],54:[2,149],57:[2,149],72:[2,149],77:[2,149],85:[2,149],90:[2,149],92:[2,149],101:[2,149],102:87,103:[1,65],104:[2,149],105:[1,66],108:88,109:[1,68],110:69,117:[2,149],125:[2,149],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,153],6:[2,153],25:[2,153],26:[2,153],49:[2,153],54:[2,153],57:[2,153],72:[2,153],77:[2,153],85:[2,153],90:[2,153],92:[2,153],101:[2,153],103:[2,153],104:[2,153],105:[2,153],109:[2,153],117:[2,153],125:[2,153],127:[2,153],128:[2,153],131:[2,153],132:[2,153],133:[2,153],134:[2,153],135:[2,153],136:[2,153]},{115:[2,155],116:[2,155]},{27:158,28:[1,73],44:159,58:160,59:161,75:[1,70],88:[1,113],89:[1,114],112:231,114:157},{54:[1,232],115:[2,161],116:[2,161]},{54:[2,157],115:[2,157],116:[2,157]},{54:[2,158],115:[2,158],116:[2,158]},{54:[2,159],115:[2,159],116:[2,159]},{54:[2,160],115:[2,160],116:[2,160]},{1:[2,154],6:[2,154],25:[2,154],26:[2,154],49:[2,154],54:[2,154],57:[2,154],72:[2,154],77:[2,154],85:[2,154],90:[2,154],92:[2,154],101:[2,154],103:[2,154],104:[2,154],105:[2,154],109:[2,154],117:[2,154],125:[2,154],127:[2,154],128:[2,154],131:[2,154],132:[2,154],133:[2,154],134:[2,154],135:[2,154],136:[2,154]},{8:233,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:234,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,53],25:[2,53],53:235,54:[1,236],77:[2,53]},{6:[2,91],25:[2,91],26:[2,91],54:[2,91],77:[2,91]},{6:[2,39],25:[2,39],26:[2,39],43:[1,237],54:[2,39],77:[2,39]},{6:[2,42],25:[2,42],26:[2,42],54:[2,42],77:[2,42]},{6:[2,43],25:[2,43],26:[2,43],43:[2,43],54:[2,43],77:[2,43]},{6:[2,44],25:[2,44],26:[2,44],43:[2,44],54:[2,44],77:[2,44]},{6:[2,45],25:[2,45],26:[2,45],43:[2,45],54:[2,45],77:[2,45]},{1:[2,5],6:[2,5],26:[2,5],101:[2,5]},{1:[2,25],6:[2,25],25:[2,25],26:[2,25],49:[2,25],54:[2,25],57:[2,25],72:[2,25],77:[2,25],85:[2,25],90:[2,25],92:[2,25],97:[2,25],98:[2,25],101:[2,25],103:[2,25],104:[2,25],105:[2,25],109:[2,25],117:[2,25],120:[2,25],122:[2,25],125:[2,25],127:[2,25],128:[2,25],131:[2,25],132:[2,25],133:[2,25],134:[2,25],135:[2,25],136:[2,25]},{1:[2,192],6:[2,192],25:[2,192],26:[2,192],49:[2,192],54:[2,192],57:[2,192],72:[2,192],77:[2,192],85:[2,192],90:[2,192],92:[2,192],101:[2,192],102:87,103:[2,192],104:[2,192],105:[2,192],108:88,109:[2,192],110:69,117:[2,192],125:[2,192],127:[2,192],128:[2,192],131:[1,78],132:[1,81],133:[2,192],134:[2,192],135:[2,192],136:[2,192]},{1:[2,193],6:[2,193],25:[2,193],26:[2,193],49:[2,193],54:[2,193],57:[2,193],72:[2,193],77:[2,193],85:[2,193],90:[2,193],92:[2,193],101:[2,193],102:87,103:[2,193],104:[2,193],105:[2,193],108:88,109:[2,193],110:69,117:[2,193],125:[2,193],127:[2,193],128:[2,193],131:[1,78],132:[1,81],133:[2,193],134:[2,193],135:[2,193],136:[2,193]},{1:[2,194],6:[2,194],25:[2,194],26:[2,194],49:[2,194],54:[2,194],57:[2,194],72:[2,194],77:[2,194],85:[2,194],90:[2,194],92:[2,194],101:[2,194],102:87,103:[2,194],104:[2,194],105:[2,194],108:88,109:[2,194],110:69,117:[2,194],125:[2,194],127:[2,194],128:[2,194],131:[1,78],132:[2,194],133:[2,194],134:[2,194],135:[2,194],136:[2,194]},{1:[2,195],6:[2,195],25:[2,195],26:[2,195],49:[2,195],54:[2,195],57:[2,195],72:[2,195],77:[2,195],85:[2,195],90:[2,195],92:[2,195],101:[2,195],102:87,103:[2,195],104:[2,195],105:[2,195],108:88,109:[2,195],110:69,117:[2,195],125:[2,195],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[2,195],134:[2,195],135:[2,195],136:[2,195]},{1:[2,196],6:[2,196],25:[2,196],26:[2,196],49:[2,196],54:[2,196],57:[2,196],72:[2,196],77:[2,196],85:[2,196],90:[2,196],92:[2,196],101:[2,196],102:87,103:[2,196],104:[2,196],105:[2,196],108:88,109:[2,196],110:69,117:[2,196],125:[2,196],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[2,196],135:[2,196],136:[1,85]},{1:[2,197],6:[2,197],25:[2,197],26:[2,197],49:[2,197],54:[2,197],57:[2,197],72:[2,197],77:[2,197],85:[2,197],90:[2,197],92:[2,197],101:[2,197],102:87,103:[2,197],104:[2,197],105:[2,197],108:88,109:[2,197],110:69,117:[2,197],125:[2,197],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[2,197],136:[1,85]},{1:[2,198],6:[2,198],25:[2,198],26:[2,198],49:[2,198],54:[2,198],57:[2,198],72:[2,198],77:[2,198],85:[2,198],90:[2,198],92:[2,198],101:[2,198],102:87,103:[2,198],104:[2,198],105:[2,198],108:88,109:[2,198],110:69,117:[2,198],125:[2,198],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[2,198],135:[2,198],136:[2,198]},{1:[2,183],6:[2,183],25:[2,183],26:[2,183],49:[2,183],54:[2,183],57:[2,183],72:[2,183],77:[2,183],85:[2,183],90:[2,183],92:[2,183],101:[2,183],102:87,103:[1,65],104:[2,183],105:[1,66],108:88,109:[1,68],110:69,117:[2,183],125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,182],6:[2,182],25:[2,182],26:[2,182],49:[2,182],54:[2,182],57:[2,182],72:[2,182],77:[2,182],85:[2,182],90:[2,182],92:[2,182],101:[2,182],102:87,103:[1,65],104:[2,182],105:[1,66],108:88,109:[1,68],110:69,117:[2,182],125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,103],6:[2,103],25:[2,103],26:[2,103],49:[2,103],54:[2,103],57:[2,103],66:[2,103],67:[2,103],68:[2,103],70:[2,103],72:[2,103],73:[2,103],77:[2,103],83:[2,103],84:[2,103],85:[2,103],90:[2,103],92:[2,103],101:[2,103],103:[2,103],104:[2,103],105:[2,103],109:[2,103],117:[2,103],125:[2,103],127:[2,103],128:[2,103],131:[2,103],132:[2,103],133:[2,103],134:[2,103],135:[2,103],136:[2,103]},{1:[2,80],6:[2,80],25:[2,80],26:[2,80],40:[2,80],49:[2,80],54:[2,80],57:[2,80],66:[2,80],67:[2,80],68:[2,80],70:[2,80],72:[2,80],73:[2,80],77:[2,80],79:[2,80],83:[2,80],84:[2,80],85:[2,80],90:[2,80],92:[2,80],101:[2,80],103:[2,80],104:[2,80],105:[2,80],109:[2,80],117:[2,80],125:[2,80],127:[2,80],128:[2,80],129:[2,80],130:[2,80],131:[2,80],132:[2,80],133:[2,80],134:[2,80],135:[2,80],136:[2,80],137:[2,80]},{1:[2,81],6:[2,81],25:[2,81],26:[2,81],40:[2,81],49:[2,81],54:[2,81],57:[2,81],66:[2,81],67:[2,81],68:[2,81],70:[2,81],72:[2,81],73:[2,81],77:[2,81],79:[2,81],83:[2,81],84:[2,81],85:[2,81],90:[2,81],92:[2,81],101:[2,81],103:[2,81],104:[2,81],105:[2,81],109:[2,81],117:[2,81],125:[2,81],127:[2,81],128:[2,81],129:[2,81],130:[2,81],131:[2,81],132:[2,81],133:[2,81],134:[2,81],135:[2,81],136:[2,81],137:[2,81]},{1:[2,82],6:[2,82],25:[2,82],26:[2,82],40:[2,82],49:[2,82],54:[2,82],57:[2,82],66:[2,82],67:[2,82],68:[2,82],70:[2,82],72:[2,82],73:[2,82],77:[2,82],79:[2,82],83:[2,82],84:[2,82],85:[2,82],90:[2,82],92:[2,82],101:[2,82],103:[2,82],104:[2,82],105:[2,82],109:[2,82],117:[2,82],125:[2,82],127:[2,82],128:[2,82],129:[2,82],130:[2,82],131:[2,82],132:[2,82],133:[2,82],134:[2,82],135:[2,82],136:[2,82],137:[2,82]},{72:[1,238]},{57:[1,192],72:[2,87],91:239,92:[1,191],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{72:[2,88]},{8:240,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,72:[2,122],75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{12:[2,116],28:[2,116],30:[2,116],31:[2,116],33:[2,116],34:[2,116],35:[2,116],36:[2,116],37:[2,116],38:[2,116],45:[2,116],46:[2,116],47:[2,116],51:[2,116],52:[2,116],72:[2,116],75:[2,116],78:[2,116],82:[2,116],87:[2,116],88:[2,116],89:[2,116],95:[2,116],99:[2,116],100:[2,116],103:[2,116],105:[2,116],107:[2,116],109:[2,116],118:[2,116],124:[2,116],126:[2,116],127:[2,116],128:[2,116],129:[2,116],130:[2,116]},{12:[2,117],28:[2,117],30:[2,117],31:[2,117],33:[2,117],34:[2,117],35:[2,117],36:[2,117],37:[2,117],38:[2,117],45:[2,117],46:[2,117],47:[2,117],51:[2,117],52:[2,117],72:[2,117],75:[2,117],78:[2,117],82:[2,117],87:[2,117],88:[2,117],89:[2,117],95:[2,117],99:[2,117],100:[2,117],103:[2,117],105:[2,117],107:[2,117],109:[2,117],118:[2,117],124:[2,117],126:[2,117],127:[2,117],128:[2,117],129:[2,117],130:[2,117]},{1:[2,86],6:[2,86],25:[2,86],26:[2,86],40:[2,86],49:[2,86],54:[2,86],57:[2,86],66:[2,86],67:[2,86],68:[2,86],70:[2,86],72:[2,86],73:[2,86],77:[2,86],79:[2,86],83:[2,86],84:[2,86],85:[2,86],90:[2,86],92:[2,86],101:[2,86],103:[2,86],104:[2,86],105:[2,86],109:[2,86],117:[2,86],125:[2,86],127:[2,86],128:[2,86],129:[2,86],130:[2,86],131:[2,86],132:[2,86],133:[2,86],134:[2,86],135:[2,86],136:[2,86],137:[2,86]},{1:[2,104],6:[2,104],25:[2,104],26:[2,104],49:[2,104],54:[2,104],57:[2,104],66:[2,104],67:[2,104],68:[2,104],70:[2,104],72:[2,104],73:[2,104],77:[2,104],83:[2,104],84:[2,104],85:[2,104],90:[2,104],92:[2,104],101:[2,104],103:[2,104],104:[2,104],105:[2,104],109:[2,104],117:[2,104],125:[2,104],127:[2,104],128:[2,104],131:[2,104],132:[2,104],133:[2,104],134:[2,104],135:[2,104],136:[2,104]},{1:[2,36],6:[2,36],25:[2,36],26:[2,36],49:[2,36],54:[2,36],57:[2,36],72:[2,36],77:[2,36],85:[2,36],90:[2,36],92:[2,36],101:[2,36],102:87,103:[2,36],104:[2,36],105:[2,36],108:88,109:[2,36],110:69,117:[2,36],125:[2,36],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{8:241,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:242,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,109],6:[2,109],25:[2,109],26:[2,109],49:[2,109],54:[2,109],57:[2,109],66:[2,109],67:[2,109],68:[2,109],70:[2,109],72:[2,109],73:[2,109],77:[2,109],83:[2,109],84:[2,109],85:[2,109],90:[2,109],92:[2,109],101:[2,109],103:[2,109],104:[2,109],105:[2,109],109:[2,109],117:[2,109],125:[2,109],127:[2,109],128:[2,109],131:[2,109],132:[2,109],133:[2,109],134:[2,109],135:[2,109],136:[2,109]},{6:[2,53],25:[2,53],53:243,54:[1,226],85:[2,53]},{6:[2,128],25:[2,128],26:[2,128],54:[2,128],57:[1,244],85:[2,128],90:[2,128],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{50:245,51:[1,60],52:[1,61]},{6:[2,54],25:[2,54],26:[2,54],27:109,28:[1,73],44:110,55:246,56:108,58:111,59:112,75:[1,70],88:[1,113],89:[1,114]},{6:[1,247],25:[1,248]},{6:[2,61],25:[2,61],26:[2,61],49:[2,61],54:[2,61]},{8:249,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,199],6:[2,199],25:[2,199],26:[2,199],49:[2,199],54:[2,199],57:[2,199],72:[2,199],77:[2,199],85:[2,199],90:[2,199],92:[2,199],101:[2,199],102:87,103:[2,199],104:[2,199],105:[2,199],108:88,109:[2,199],110:69,117:[2,199],125:[2,199],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{8:250,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,201],6:[2,201],25:[2,201],26:[2,201],49:[2,201],54:[2,201],57:[2,201],72:[2,201],77:[2,201],85:[2,201],90:[2,201],92:[2,201],101:[2,201],102:87,103:[2,201],104:[2,201],105:[2,201],108:88,109:[2,201],110:69,117:[2,201],125:[2,201],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,181],6:[2,181],25:[2,181],26:[2,181],49:[2,181],54:[2,181],57:[2,181],72:[2,181],77:[2,181],85:[2,181],90:[2,181],92:[2,181],101:[2,181],103:[2,181],104:[2,181],105:[2,181],109:[2,181],117:[2,181],125:[2,181],127:[2,181],128:[2,181],131:[2,181],132:[2,181],133:[2,181],134:[2,181],135:[2,181],136:[2,181]},{8:251,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,133],6:[2,133],25:[2,133],26:[2,133],49:[2,133],54:[2,133],57:[2,133],72:[2,133],77:[2,133],85:[2,133],90:[2,133],92:[2,133],97:[1,252],101:[2,133],103:[2,133],104:[2,133],105:[2,133],109:[2,133],117:[2,133],125:[2,133],127:[2,133],128:[2,133],131:[2,133],132:[2,133],133:[2,133],134:[2,133],135:[2,133],136:[2,133]},{5:253,25:[1,5]},{27:254,28:[1,73]},{119:255,121:216,122:[1,217]},{26:[1,256],120:[1,257],121:258,122:[1,217]},{26:[2,174],120:[2,174],122:[2,174]},{8:260,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],94:259,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,97],5:261,6:[2,97],25:[1,5],26:[2,97],49:[2,97],54:[2,97],57:[2,97],72:[2,97],77:[2,97],85:[2,97],90:[2,97],92:[2,97],101:[2,97],102:87,103:[1,65],104:[2,97],105:[1,66],108:88,109:[1,68],110:69,117:[2,97],125:[2,97],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,100],6:[2,100],25:[2,100],26:[2,100],49:[2,100],54:[2,100],57:[2,100],72:[2,100],77:[2,100],85:[2,100],90:[2,100],92:[2,100],101:[2,100],103:[2,100],104:[2,100],105:[2,100],109:[2,100],117:[2,100],125:[2,100],127:[2,100],128:[2,100],131:[2,100],132:[2,100],133:[2,100],134:[2,100],135:[2,100],136:[2,100]},{8:262,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,138],6:[2,138],25:[2,138],26:[2,138],49:[2,138],54:[2,138],57:[2,138],66:[2,138],67:[2,138],68:[2,138],70:[2,138],72:[2,138],73:[2,138],77:[2,138],83:[2,138],84:[2,138],85:[2,138],90:[2,138],92:[2,138],101:[2,138],103:[2,138],104:[2,138],105:[2,138],109:[2,138],117:[2,138],125:[2,138],127:[2,138],128:[2,138],131:[2,138],132:[2,138],133:[2,138],134:[2,138],135:[2,138],136:[2,138]},{6:[1,74],26:[1,263]},{8:264,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,67],12:[2,117],25:[2,67],28:[2,117],30:[2,117],31:[2,117],33:[2,117],34:[2,117],35:[2,117],36:[2,117],37:[2,117],38:[2,117],45:[2,117],46:[2,117],47:[2,117],51:[2,117],52:[2,117],54:[2,67],75:[2,117],78:[2,117],82:[2,117],87:[2,117],88:[2,117],89:[2,117],90:[2,67],95:[2,117],99:[2,117],100:[2,117],103:[2,117],105:[2,117],107:[2,117],109:[2,117],118:[2,117],124:[2,117],126:[2,117],127:[2,117],128:[2,117],129:[2,117],130:[2,117]},{6:[1,266],25:[1,267],90:[1,265]},{6:[2,54],8:200,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[2,54],26:[2,54],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],85:[2,54],87:[1,58],88:[1,59],89:[1,57],90:[2,54],93:268,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,53],25:[2,53],26:[2,53],53:269,54:[1,226]},{1:[2,178],6:[2,178],25:[2,178],26:[2,178],49:[2,178],54:[2,178],57:[2,178],72:[2,178],77:[2,178],85:[2,178],90:[2,178],92:[2,178],101:[2,178],103:[2,178],104:[2,178],105:[2,178],109:[2,178],117:[2,178],120:[2,178],125:[2,178],127:[2,178],128:[2,178],131:[2,178],132:[2,178],133:[2,178],134:[2,178],135:[2,178],136:[2,178]},{8:270,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:271,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{115:[2,156],116:[2,156]},{27:158,28:[1,73],44:159,58:160,59:161,75:[1,70],88:[1,113],89:[1,114],114:272},{1:[2,163],6:[2,163],25:[2,163],26:[2,163],49:[2,163],54:[2,163],57:[2,163],72:[2,163],77:[2,163],85:[2,163],90:[2,163],92:[2,163],101:[2,163],102:87,103:[2,163],104:[1,273],105:[2,163],108:88,109:[2,163],110:69,117:[1,274],125:[2,163],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,164],6:[2,164],25:[2,164],26:[2,164],49:[2,164],54:[2,164],57:[2,164],72:[2,164],77:[2,164],85:[2,164],90:[2,164],92:[2,164],101:[2,164],102:87,103:[2,164],104:[1,275],105:[2,164],108:88,109:[2,164],110:69,117:[2,164],125:[2,164],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[1,277],25:[1,278],77:[1,276]},{6:[2,54],11:168,25:[2,54],26:[2,54],27:169,28:[1,73],29:170,30:[1,71],31:[1,72],41:279,42:167,44:171,46:[1,46],77:[2,54],88:[1,113]},{8:280,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,281],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,85],6:[2,85],25:[2,85],26:[2,85],40:[2,85],49:[2,85],54:[2,85],57:[2,85],66:[2,85],67:[2,85],68:[2,85],70:[2,85],72:[2,85],73:[2,85],77:[2,85],79:[2,85],83:[2,85],84:[2,85],85:[2,85],90:[2,85],92:[2,85],101:[2,85],103:[2,85],104:[2,85],105:[2,85],109:[2,85],117:[2,85],125:[2,85],127:[2,85],128:[2,85],129:[2,85],130:[2,85],131:[2,85],132:[2,85],133:[2,85],134:[2,85],135:[2,85],136:[2,85],137:[2,85]},{8:282,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,72:[2,120],75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{72:[2,121],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,37],6:[2,37],25:[2,37],26:[2,37],49:[2,37],54:[2,37],57:[2,37],72:[2,37],77:[2,37],85:[2,37],90:[2,37],92:[2,37],101:[2,37],102:87,103:[2,37],104:[2,37],105:[2,37],108:88,109:[2,37],110:69,117:[2,37],125:[2,37],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{26:[1,283],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[1,266],25:[1,267],85:[1,284]},{6:[2,67],25:[2,67],26:[2,67],54:[2,67],85:[2,67],90:[2,67]},{5:285,25:[1,5]},{6:[2,57],25:[2,57],26:[2,57],49:[2,57],54:[2,57]},{27:109,28:[1,73],44:110,55:286,56:108,58:111,59:112,75:[1,70],88:[1,113],89:[1,114]},{6:[2,55],25:[2,55],26:[2,55],27:109,28:[1,73],44:110,48:287,54:[2,55],55:107,56:108,58:111,59:112,75:[1,70],88:[1,113],89:[1,114]},{6:[2,62],25:[2,62],26:[2,62],49:[2,62],54:[2,62],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{26:[1,288],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{5:289,25:[1,5],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{5:290,25:[1,5]},{1:[2,134],6:[2,134],25:[2,134],26:[2,134],49:[2,134],54:[2,134],57:[2,134],72:[2,134],77:[2,134],85:[2,134],90:[2,134],92:[2,134],101:[2,134],103:[2,134],104:[2,134],105:[2,134],109:[2,134],117:[2,134],125:[2,134],127:[2,134],128:[2,134],131:[2,134],132:[2,134],133:[2,134],134:[2,134],135:[2,134],136:[2,134]},{5:291,25:[1,5]},{26:[1,292],120:[1,293],121:258,122:[1,217]},{1:[2,172],6:[2,172],25:[2,172],26:[2,172],49:[2,172],54:[2,172],57:[2,172],72:[2,172],77:[2,172],85:[2,172],90:[2,172],92:[2,172],101:[2,172],103:[2,172],104:[2,172],105:[2,172],109:[2,172],117:[2,172],125:[2,172],127:[2,172],128:[2,172],131:[2,172],132:[2,172],133:[2,172],134:[2,172],135:[2,172],136:[2,172]},{5:294,25:[1,5]},{26:[2,175],120:[2,175],122:[2,175]},{5:295,25:[1,5],54:[1,296]},{25:[2,130],54:[2,130],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,98],6:[2,98],25:[2,98],26:[2,98],49:[2,98],54:[2,98],57:[2,98],72:[2,98],77:[2,98],85:[2,98],90:[2,98],92:[2,98],101:[2,98],103:[2,98],104:[2,98],105:[2,98],109:[2,98],117:[2,98],125:[2,98],127:[2,98],128:[2,98],131:[2,98],132:[2,98],133:[2,98],134:[2,98],135:[2,98],136:[2,98]},{1:[2,101],5:297,6:[2,101],25:[1,5],26:[2,101],49:[2,101],54:[2,101],57:[2,101],72:[2,101],77:[2,101],85:[2,101],90:[2,101],92:[2,101],101:[2,101],102:87,103:[1,65],104:[2,101],105:[1,66],108:88,109:[1,68],110:69,117:[2,101],125:[2,101],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{101:[1,298]},{90:[1,299],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,115],6:[2,115],25:[2,115],26:[2,115],40:[2,115],49:[2,115],54:[2,115],57:[2,115],66:[2,115],67:[2,115],68:[2,115],70:[2,115],72:[2,115],73:[2,115],77:[2,115],83:[2,115],84:[2,115],85:[2,115],90:[2,115],92:[2,115],101:[2,115],103:[2,115],104:[2,115],105:[2,115],109:[2,115],115:[2,115],116:[2,115],117:[2,115],125:[2,115],127:[2,115],128:[2,115],131:[2,115],132:[2,115],133:[2,115],134:[2,115],135:[2,115],136:[2,115]},{8:200,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],93:300,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:200,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,146],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:147,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],86:301,87:[1,58],88:[1,59],89:[1,57],93:145,95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[2,124],25:[2,124],26:[2,124],54:[2,124],85:[2,124],90:[2,124]},{6:[1,266],25:[1,267],26:[1,302]},{1:[2,141],6:[2,141],25:[2,141],26:[2,141],49:[2,141],54:[2,141],57:[2,141],72:[2,141],77:[2,141],85:[2,141],90:[2,141],92:[2,141],101:[2,141],102:87,103:[1,65],104:[2,141],105:[1,66],108:88,109:[1,68],110:69,117:[2,141],125:[2,141],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,143],6:[2,143],25:[2,143],26:[2,143],49:[2,143],54:[2,143],57:[2,143],72:[2,143],77:[2,143],85:[2,143],90:[2,143],92:[2,143],101:[2,143],102:87,103:[1,65],104:[2,143],105:[1,66],108:88,109:[1,68],110:69,117:[2,143],125:[2,143],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{115:[2,162],116:[2,162]},{8:303,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:304,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:305,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,89],6:[2,89],25:[2,89],26:[2,89],40:[2,89],49:[2,89],54:[2,89],57:[2,89],66:[2,89],67:[2,89],68:[2,89],70:[2,89],72:[2,89],73:[2,89],77:[2,89],83:[2,89],84:[2,89],85:[2,89],90:[2,89],92:[2,89],101:[2,89],103:[2,89],104:[2,89],105:[2,89],109:[2,89],115:[2,89],116:[2,89],117:[2,89],125:[2,89],127:[2,89],128:[2,89],131:[2,89],132:[2,89],133:[2,89],134:[2,89],135:[2,89],136:[2,89]},{11:168,27:169,28:[1,73],29:170,30:[1,71],31:[1,72],41:306,42:167,44:171,46:[1,46],88:[1,113]},{6:[2,90],11:168,25:[2,90],26:[2,90],27:169,28:[1,73],29:170,30:[1,71],31:[1,72],41:166,42:167,44:171,46:[1,46],54:[2,90],76:307,88:[1,113]},{6:[2,92],25:[2,92],26:[2,92],54:[2,92],77:[2,92]},{6:[2,40],25:[2,40],26:[2,40],54:[2,40],77:[2,40],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{8:308,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{72:[2,119],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,38],6:[2,38],25:[2,38],26:[2,38],49:[2,38],54:[2,38],57:[2,38],72:[2,38],77:[2,38],85:[2,38],90:[2,38],92:[2,38],101:[2,38],103:[2,38],104:[2,38],105:[2,38],109:[2,38],117:[2,38],125:[2,38],127:[2,38],128:[2,38],131:[2,38],132:[2,38],133:[2,38],134:[2,38],135:[2,38],136:[2,38]},{1:[2,110],6:[2,110],25:[2,110],26:[2,110],49:[2,110],54:[2,110],57:[2,110],66:[2,110],67:[2,110],68:[2,110],70:[2,110],72:[2,110],73:[2,110],77:[2,110],83:[2,110],84:[2,110],85:[2,110],90:[2,110],92:[2,110],101:[2,110],103:[2,110],104:[2,110],105:[2,110],109:[2,110],117:[2,110],125:[2,110],127:[2,110],128:[2,110],131:[2,110],132:[2,110],133:[2,110],134:[2,110],135:[2,110],136:[2,110]},{1:[2,49],6:[2,49],25:[2,49],26:[2,49],49:[2,49],54:[2,49],57:[2,49],72:[2,49],77:[2,49],85:[2,49],90:[2,49],92:[2,49],101:[2,49],103:[2,49],104:[2,49],105:[2,49],109:[2,49],117:[2,49],125:[2,49],127:[2,49],128:[2,49],131:[2,49],132:[2,49],133:[2,49],134:[2,49],135:[2,49],136:[2,49]},{6:[2,58],25:[2,58],26:[2,58],49:[2,58],54:[2,58]},{6:[2,53],25:[2,53],26:[2,53],53:309,54:[1,202]},{1:[2,200],6:[2,200],25:[2,200],26:[2,200],49:[2,200],54:[2,200],57:[2,200],72:[2,200],77:[2,200],85:[2,200],90:[2,200],92:[2,200],101:[2,200],103:[2,200],104:[2,200],105:[2,200],109:[2,200],117:[2,200],125:[2,200],127:[2,200],128:[2,200],131:[2,200],132:[2,200],133:[2,200],134:[2,200],135:[2,200],136:[2,200]},{1:[2,179],6:[2,179],25:[2,179],26:[2,179],49:[2,179],54:[2,179],57:[2,179],72:[2,179],77:[2,179],85:[2,179],90:[2,179],92:[2,179],101:[2,179],103:[2,179],104:[2,179],105:[2,179],109:[2,179],117:[2,179],120:[2,179],125:[2,179],127:[2,179],128:[2,179],131:[2,179],132:[2,179],133:[2,179],134:[2,179],135:[2,179],136:[2,179]},{1:[2,135],6:[2,135],25:[2,135],26:[2,135],49:[2,135],54:[2,135],57:[2,135],72:[2,135],77:[2,135],85:[2,135],90:[2,135],92:[2,135],101:[2,135],103:[2,135],104:[2,135],105:[2,135],109:[2,135],117:[2,135],125:[2,135],127:[2,135],128:[2,135],131:[2,135],132:[2,135],133:[2,135],134:[2,135],135:[2,135],136:[2,135]},{1:[2,136],6:[2,136],25:[2,136],26:[2,136],49:[2,136],54:[2,136],57:[2,136],72:[2,136],77:[2,136],85:[2,136],90:[2,136],92:[2,136],97:[2,136],101:[2,136],103:[2,136],104:[2,136],105:[2,136],109:[2,136],117:[2,136],125:[2,136],127:[2,136],128:[2,136],131:[2,136],132:[2,136],133:[2,136],134:[2,136],135:[2,136],136:[2,136]},{1:[2,170],6:[2,170],25:[2,170],26:[2,170],49:[2,170],54:[2,170],57:[2,170],72:[2,170],77:[2,170],85:[2,170],90:[2,170],92:[2,170],101:[2,170],103:[2,170],104:[2,170],105:[2,170],109:[2,170],117:[2,170],125:[2,170],127:[2,170],128:[2,170],131:[2,170],132:[2,170],133:[2,170],134:[2,170],135:[2,170],136:[2,170]},{5:310,25:[1,5]},{26:[1,311]},{6:[1,312],26:[2,176],120:[2,176],122:[2,176]},{8:313,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{1:[2,102],6:[2,102],25:[2,102],26:[2,102],49:[2,102],54:[2,102],57:[2,102],72:[2,102],77:[2,102],85:[2,102],90:[2,102],92:[2,102],101:[2,102],103:[2,102],104:[2,102],105:[2,102],109:[2,102],117:[2,102],125:[2,102],127:[2,102],128:[2,102],131:[2,102],132:[2,102],133:[2,102],134:[2,102],135:[2,102],136:[2,102]},{1:[2,139],6:[2,139],25:[2,139],26:[2,139],49:[2,139],54:[2,139],57:[2,139],66:[2,139],67:[2,139],68:[2,139],70:[2,139],72:[2,139],73:[2,139],77:[2,139],83:[2,139],84:[2,139],85:[2,139],90:[2,139],92:[2,139],101:[2,139],103:[2,139],104:[2,139],105:[2,139],109:[2,139],117:[2,139],125:[2,139],127:[2,139],128:[2,139],131:[2,139],132:[2,139],133:[2,139],134:[2,139],135:[2,139],136:[2,139]},{1:[2,118],6:[2,118],25:[2,118],26:[2,118],49:[2,118],54:[2,118],57:[2,118],66:[2,118],67:[2,118],68:[2,118],70:[2,118],72:[2,118],73:[2,118],77:[2,118],83:[2,118],84:[2,118],85:[2,118],90:[2,118],92:[2,118],101:[2,118],103:[2,118],104:[2,118],105:[2,118],109:[2,118],117:[2,118],125:[2,118],127:[2,118],128:[2,118],131:[2,118],132:[2,118],133:[2,118],134:[2,118],135:[2,118],136:[2,118]},{6:[2,125],25:[2,125],26:[2,125],54:[2,125],85:[2,125],90:[2,125]},{6:[2,53],25:[2,53],26:[2,53],53:314,54:[1,226]},{6:[2,126],25:[2,126],26:[2,126],54:[2,126],85:[2,126],90:[2,126]},{1:[2,165],6:[2,165],25:[2,165],26:[2,165],49:[2,165],54:[2,165],57:[2,165],72:[2,165],77:[2,165],85:[2,165],90:[2,165],92:[2,165],101:[2,165],102:87,103:[2,165],104:[2,165],105:[2,165],108:88,109:[2,165],110:69,117:[1,315],125:[2,165],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,167],6:[2,167],25:[2,167],26:[2,167],49:[2,167],54:[2,167],57:[2,167],72:[2,167],77:[2,167],85:[2,167],90:[2,167],92:[2,167],101:[2,167],102:87,103:[2,167],104:[1,316],105:[2,167],108:88,109:[2,167],110:69,117:[2,167],125:[2,167],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,166],6:[2,166],25:[2,166],26:[2,166],49:[2,166],54:[2,166],57:[2,166],72:[2,166],77:[2,166],85:[2,166],90:[2,166],92:[2,166],101:[2,166],102:87,103:[2,166],104:[2,166],105:[2,166],108:88,109:[2,166],110:69,117:[2,166],125:[2,166],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[2,93],25:[2,93],26:[2,93],54:[2,93],77:[2,93]},{6:[2,53],25:[2,53],26:[2,53],53:317,54:[1,236]},{26:[1,318],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[1,247],25:[1,248],26:[1,319]},{26:[1,320]},{1:[2,173],6:[2,173],25:[2,173],26:[2,173],49:[2,173],54:[2,173],57:[2,173],72:[2,173],77:[2,173],85:[2,173],90:[2,173],92:[2,173],101:[2,173],103:[2,173],104:[2,173],105:[2,173],109:[2,173],117:[2,173],125:[2,173],127:[2,173],128:[2,173],131:[2,173],132:[2,173],133:[2,173],134:[2,173],135:[2,173],136:[2,173]},{26:[2,177],120:[2,177],122:[2,177]},{25:[2,131],54:[2,131],102:87,103:[1,65],105:[1,66],108:88,109:[1,68],110:69,125:[1,86],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[1,266],25:[1,267],26:[1,321]},{8:322,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{8:323,9:117,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,75:[1,70],78:[1,43],82:[1,28],87:[1,58],88:[1,59],89:[1,57],95:[1,38],99:[1,44],100:[1,56],102:39,103:[1,65],105:[1,66],106:40,107:[1,67],108:41,109:[1,68],110:69,118:[1,42],123:37,124:[1,64],126:[1,31],127:[1,32],128:[1,33],129:[1,34],130:[1,35]},{6:[1,277],25:[1,278],26:[1,324]},{6:[2,41],25:[2,41],26:[2,41],54:[2,41],77:[2,41]},{6:[2,59],25:[2,59],26:[2,59],49:[2,59],54:[2,59]},{1:[2,171],6:[2,171],25:[2,171],26:[2,171],49:[2,171],54:[2,171],57:[2,171],72:[2,171],77:[2,171],85:[2,171],90:[2,171],92:[2,171],101:[2,171],103:[2,171],104:[2,171],105:[2,171],109:[2,171],117:[2,171],125:[2,171],127:[2,171],128:[2,171],131:[2,171],132:[2,171],133:[2,171],134:[2,171],135:[2,171],136:[2,171]},{6:[2,127],25:[2,127],26:[2,127],54:[2,127],85:[2,127],90:[2,127]},{1:[2,168],6:[2,168],25:[2,168],26:[2,168],49:[2,168],54:[2,168],57:[2,168],72:[2,168],77:[2,168],85:[2,168],90:[2,168],92:[2,168],101:[2,168],102:87,103:[2,168],104:[2,168],105:[2,168],108:88,109:[2,168],110:69,117:[2,168],125:[2,168],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{1:[2,169],6:[2,169],25:[2,169],26:[2,169],49:[2,169],54:[2,169],57:[2,169],72:[2,169],77:[2,169],85:[2,169],90:[2,169],92:[2,169],101:[2,169],102:87,103:[2,169],104:[2,169],105:[2,169],108:88,109:[2,169],110:69,117:[2,169],125:[2,169],127:[1,80],128:[1,79],131:[1,78],132:[1,81],133:[1,82],134:[1,83],135:[1,84],136:[1,85]},{6:[2,94],25:[2,94],26:[2,94],54:[2,94],77:[2,94]}],defaultActions:{60:[2,51],61:[2,52],75:[2,3],94:[2,108],189:[2,88]},parseError:function(b,c){throw new Error(b)},parse:function(b){function o(a){d.length=d.length-2*a,e.length=e.length-a,f.length=f.length-a}function p(){var a;return a=c.lexer.lex()||1,typeof a!="number"&&(a=c.symbols_[a]||a),a}var c=this,d=[0],e=[null],f=[],g=this.table,h="",i=0,j=0,k=0,l=2,m=1;this.lexer.setInput(b),this.lexer.yy=this.yy,this.yy.lexer=this.lexer,typeof this.lexer.yylloc=="undefined"&&(this.lexer.yylloc={});var n=this.lexer.yylloc;f.push(n),typeof this.yy.parseError=="function"&&(this.parseError=this.yy.parseError);var q,r,s,t,u,v,w={},x,y,z,A;for(;;){s=d[d.length-1],this.defaultActions[s]?t=this.defaultActions[s]:(q==null&&(q=p()),t=g[s]&&g[s][q]);if(typeof t=="undefined"||!t.length||!t[0]){if(!k){A=[];for(x in g[s])this.terminals_[x]&&x>2&&A.push("'"+this.terminals_[x]+"'");var B="";this.lexer.showPosition?B="Parse error on line "+(i+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+A.join(", ")+", got '"+this.terminals_[q]+"'":B="Parse error on line "+(i+1)+": Unexpected "+(q==1?"end of input":"'"+(this.terminals_[q]||q)+"'"),this.parseError(B,{text:this.lexer.match,token:this.terminals_[q]||q,line:this.lexer.yylineno,loc:n,expected:A})}if(k==3){if(q==m)throw new Error(B||"Parsing halted.");j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,n=this.lexer.yylloc,q=p()}for(;;){if(l.toString()in g[s])break;if(s==0)throw new Error(B||"Parsing halted.");o(1),s=d[d.length-1]}r=q,q=l,s=d[d.length-1],t=g[s]&&g[s][l],k=3}if(t[0]instanceof Array&&t.length>1)throw new Error("Parse Error: multiple actions possible at state: "+s+", token: "+q);switch(t[0]){case 1:d.push(q),e.push(this.lexer.yytext),f.push(this.lexer.yylloc),d.push(t[1]),q=null,r?(q=r,r=null):(j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,n=this.lexer.yylloc,k>0&&k--);break;case 2:y=this.productions_[t[1]][1],w.$=e[e.length-y],w._$={first_line:f[f.length-(y||1)].first_line,last_line:f[f.length-1].last_line,first_column:f[f.length-(y||1)].first_column,last_column:f[f.length-1].last_column},v=this.performAction.call(w,h,j,i,this.yy,t[1],e,f);if(typeof v!="undefined")return v;y&&(d=d.slice(0,-1*y*2),e=e.slice(0,-1*y),f=f.slice(0,-1*y)),d.push(this.productions_[t[1]][0]),e.push(w.$),f.push(w._$),z=g[d[d.length-2]][d[d.length-1]],d.push(z);break;case 3:return!0}}return!0}};return undefined,a}();typeof require!="undefined"&&typeof a!="undefined"&&(a.parser=b,a.parse=function(){return b.parse.apply(b,arguments)},a.main=function(c){if(!c[1])throw new Error("Usage: "+c[0]+" FILE");if(typeof process!="undefined")var d=require("fs").readFileSync(require("path").join(process.cwd(),c[1]),"utf8");else var e=require("file").path(require("file").cwd()),d=e.join(c[1]).read({charset:"utf-8"});return a.parser.parse(d)},typeof module!="undefined"&&require.main===module&&a.main(typeof process!="undefined"?process.argv.slice(1):require("system").args))},require["./scope"]=new function(){var a=this;((function(){var b,c,d,e;e=require("./helpers"),c=e.extend,d=e.last,a.Scope=b=function(){function a(b,c,d){this.parent=b,this.expressions=c,this.method=d,this.variables=[{name:"arguments",type:"arguments"}],this.positions={},this.parent||(a.root=this)}return a.root=null,a.prototype.add=function(a,b,c){return this.shared&&!c?this.parent.add(a,b,c):Object.prototype.hasOwnProperty.call(this.positions,a)?this.variables[this.positions[a]].type=b:this.positions[a]=this.variables.push({name:a,type:b})-1},a.prototype.namedMethod=function(){return this.method.name||!this.parent?this.method:this.parent.namedMethod()},a.prototype.find=function(a){return this.check(a)?!0:(this.add(a,"var"),!1)},a.prototype.parameter=function(a){if(this.shared&&this.parent.check(a,!0))return;return this.add(a,"param")},a.prototype.check=function(a){var b;return!!(this.type(a)||((b=this.parent)!=null?b.check(a):void 0))},a.prototype.temporary=function(a,b){return a.length>1?"_"+a+(b>1?b-1:""):"_"+(b+parseInt(a,36)).toString(36).replace(/\d/g,"a")},a.prototype.type=function(a){var b,c,d,e;e=this.variables;for(c=0,d=e.length;c1&&a.level>=w?"("+c+")":c)},b.prototype.compileRoot=function(a){var b,c,d,e,f,g;return a.indent=a.bare?"":R,a.scope=new N(null,this,null),a.level=z,this.spaced=!0,e="",a.bare||(f=function(){var a,b,e,f;e=this.expressions,f=[];for(d=a=0,b=e.length;a=u?"(void 0)":"void 0"},b}(e),a.Null=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}return bm(b,a),b.prototype.isAssignable=D,b.prototype.isComplex=D,b.prototype.compileNode=function(){return"null"},b}(e),a.Bool=function(a){function b(a){this.val=a}return bm(b,a),b.prototype.isAssignable=D,b.prototype.isComplex=D,b.prototype.compileNode=function(){return this.val},b}(e),a.Return=K=function(a){function b(a){a&&!a.unwrap().isUndefined&&(this.expression=a)}return bm(b,a),b.prototype.children=["expression"],b.prototype.isStatement=Y,b.prototype.makeReturn=S,b.prototype.jumps=S,b.prototype.compile=function(a,c){var d,e;return d=(e=this.expression)!=null?e.makeReturn():void 0,!d||d instanceof b?b.__super__.compile.call(this,a,c):d.compile(a,c)},b.prototype.compileNode=function(a){return this.tab+("return"+[this.expression?" "+this.expression.compile(a,y):void 0]+";")},b}(e),a.Value=W=function(a){function b(a,c,d){return!c&&a instanceof b?a:(this.base=a,this.properties=c||[],d&&(this[d]=!0),this)}return bm(b,a),b.prototype.children=["base","properties"],b.prototype.add=function(a){return this.properties=this.properties.concat(a),this},b.prototype.hasProperties=function(){return!!this.properties.length},b.prototype.isArray=function(){return!this.properties.length&&this.base instanceof c},b.prototype.isComplex=function(){return this.hasProperties()||this.base.isComplex()},b.prototype.isAssignable=function(){return this.hasProperties()||this.base.isAssignable()},b.prototype.isSimpleNumber=function(){return this.base instanceof A&&L.test(this.base.value)},b.prototype.isString=function(){return this.base instanceof A&&q.test(this.base.value)},b.prototype.isAtomic=function(){var a,b,c,d;d=this.properties.concat(this.base);for(b=0,c=d.length;b"+this.equals],i=n[0],e=n[1],c=this.stepNum?+this.stepNum>0?""+i+" "+this.toVar:""+e+" "+this.toVar:h?(o=[+this.fromNum,+this.toNum],d=o[0],l=o[1],o,d<=l?""+i+" "+l:""+e+" "+l):(b=""+this.fromVar+" <= "+this.toVar,""+b+" ? "+i+" "+this.toVar+" : "+e+" "+this.toVar),k=this.stepVar?""+f+" += "+this.stepVar:h?j?d<=l?"++"+f:"--"+f:d<=l?""+f+"++":""+f+"--":j?""+b+" ? ++"+f+" : --"+f:""+b+" ? "+f+"++ : "+f+"--",j&&(m=""+g+" = "+m),j&&(k=""+g+" = "+k),""+m+"; "+c+"; "+k):this.compileArray(a)},b.prototype.compileArray=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p;if(this.fromNum&&this.toNum&&Math.abs(this.fromNum-this.toNum)<=20)return j=function(){p=[];for(var a=n=+this.fromNum,b=+this.toNum;n<=b?a<=b:a>=b;n<=b?a++:a--)p.push(a);return p}.apply(this),this.exclusive&&j.pop(),"["+j.join(", ")+"]";g=this.tab+R,f=a.scope.freeVariable("i"),k=a.scope.freeVariable("results"),i="\n"+g+k+" = [];",this.fromNum&&this.toNum?(a.index=f,c=this.compileNode(a)):(l=""+f+" = "+this.fromC+(this.toC!==this.toVar?", "+this.toC:""),d=""+this.fromVar+" <= "+this.toVar,c="var "+l+"; "+d+" ? "+f+" <"+this.equals+" "+this.toVar+" : "+f+" >"+this.equals+" "+this.toVar+"; "+d+" ? "+f+"++ : "+f+"--"),h="{ "+k+".push("+f+"); }\n"+g+"return "+k+";\n"+a.indent,e=function(a){return a!=null?a.contains(function(a){return a instanceof A&&a.value==="arguments"&&!a.asKey}):void 0};if(e(this.from)||e(this.to))b=", arguments";return"(function() {"+i+"\n"+g+"for ("+c+")"+h+"}).apply(this"+(b!=null?b:"")+")"},b}(e),a.Slice=O=function(a){function b(a){this.range=a,b.__super__.constructor.call(this)}return bm(b,a),b.prototype.children=["range"],b.prototype.compileNode=function(a){var b,c,d,e,f,g;return g=this.range,e=g.to,c=g.from,d=c&&c.compile(a,y)||"0",b=e&&e.compile(a,y),e&&(!!this.range.exclusive||+b!==-1)&&(f=", "+(this.range.exclusive?b:L.test(b)?""+(+b+1):(b=e.compile(a,u),"+"+b+" + 1 || 9e9"))),".slice("+d+(f||"")+")"},b}(e),a.Obj=E=function(a){function b(a,b){this.generated=b!=null?b:!1,this.objects=this.properties=a||[]}return bm(b,a),b.prototype.children=["properties"],b.prototype.compileNode=function(a){var b,c,e,f,g,h,i,j,l,m,n;l=this.properties;if(!l.length)return this.front?"({})":"{}";if(this.generated)for(m=0,n=l.length;m=0?"[\n"+a.indent+b+"\n"+this.tab+"]":"["+b+"]")):"[]"},b.prototype.assigns=function(a){var b,c,d,e;e=this.objects;for(c=0,d=e.length;c=0)throw SyntaxError("variable name may not be "+a);return a&&(a=o.test(a)&&a)},c.prototype.setContext=function(a){return this.body.traverseChildren(!1,function(b){if(b.classBody)return!1;if(b instanceof A&&b.value==="this")return b.value=a;if(b instanceof j){b.klass=a;if(b.bound)return b.context=a}})},c.prototype.addBoundFunctions=function(a){var c,d,e,f,g,h;if(this.boundFuncs.length){g=this.boundFuncs,h=[];for(e=0,f=g.length;e=0);if(e&&this.context!=="object")throw SyntaxError('variable name may not be "'+f+'"')}return bm(c,a),c.prototype.children=["variable","value"],c.prototype.isStatement=function(a){return(a!=null?a.level:void 0)===z&&this.context!=null&&bn.call(this.context,"?")>=0},c.prototype.assigns=function(a){return this[this.context==="object"?"value":"variable"].assigns(a)},c.prototype.unfoldSoak=function(a){return bh(a,this,"variable")},c.prototype.compileNode=function(a){var b,c,d,e,f,g,h,i,k;if(b=this.variable instanceof W){if(this.variable.isArray()||this.variable.isObject())return this.compilePatternMatch(a);if(this.variable.isSplice())return this.compileSplice(a);if((g=this.context)==="||="||g==="&&="||g==="?=")return this.compileConditional(a)}d=this.variable.compile(a,w);if(!this.context){if(!(f=this.variable.unwrapAll()).isAssignable())throw SyntaxError('"'+this.variable.compile(a)+'" cannot be assigned.');if(typeof f.hasProperties=="function"?!f.hasProperties():!void 0)this.param?a.scope.add(d,"var"):a.scope.find(d)}return this.value instanceof j&&(c=B.exec(d))&&(c[1]&&(this.value.klass=c[1]),this.value.name=(h=(i=(k=c[2])!=null?k:c[3])!=null?i:c[4])!=null?h:c[5]),e=this.value.compile(a,w),this.context==="object"?""+d+": "+e:(e=d+(" "+(this.context||"=")+" ")+e,a.level<=w?e:"("+e+")")},c.prototype.compilePatternMatch=function(a){var d,e,f,g,h,i,j,k,l,m,n,p,q,r,s,u,v,y,B,C,D,E,F,G,J,K,L;s=a.level===z,v=this.value,m=this.variable.base.objects;if(!(n=m.length))return f=v.compile(a),a.level>=x?"("+f+")":f;i=this.variable.isObject();if(s&&n===1&&!((l=m[0])instanceof P)){l instanceof c?(D=l,E=D.variable,h=E.base,l=D.value):l.base instanceof H?(F=(new W(l.unwrapAll())).cacheReference(a),l=F[0],h=F[1]):h=i?l["this"]?l.properties[0].name:l:new A(0),d=o.test(h.unwrap().value||0),v=new W(v),v.properties.push(new(d?b:t)(h));if(G=l.unwrap().value,bn.call(I,G)>=0)throw new SyntaxError("assignment to a reserved word: "+l.compile(a)+" = "+v.compile(a));return(new c(l,v,null,{param:this.param})).compile(a,z)}y=v.compile(a,w),e=[],r=!1;if(!o.test(y)||this.variable.assigns(y))e.push(""+(p=a.scope.freeVariable("ref"))+" = "+y),y=p;for(g=B=0,C=m.length;B=0)throw new SyntaxError("assignment to a reserved word: "+l.compile(a)+" = "+u.compile(a));e.push((new c(l,u,null,{param:this.param,subpattern:!0})).compile(a,w))}return!s&&!this.subpattern&&e.push(y),f=e.join(", "),a.level=0&&(a.isExistentialEquals=!0),(new F(this.context.slice(0,-1),b,new c(d,this.value,"="))).compile(a)},c.prototype.compileSplice=function(a){var b,c,d,e,f,g,h,i,j,k,l,m;return k=this.variable.properties.pop().range,d=k.from,h=k.to,c=k.exclusive,g=this.variable.compile(a),l=(d!=null?d.cache(a,x):void 0)||["0","0"],e=l[0],f=l[1],h?(d!=null?d.isSimpleNumber():void 0)&&h.isSimpleNumber()?(h=+h.compile(a)- +f,c||(h+=1)):(h=h.compile(a,u)+" - "+f,c||(h+=" + 1")):h="9e9",m=this.value.cache(a,w),i=m[0],j=m[1],b="[].splice.apply("+g+", ["+e+", "+h+"].concat("+i+")), "+j,a.level>z?"("+b+")":b},c}(e),a.Code=j=function(a){function b(a,b,c){this.params=a||[],this.body=b||new f,this.bound=c==="boundfunc",this.bound&&(this.context="_this")}return bm(b,a),b.prototype.children=["params","body"],b.prototype.isStatement=function(){return!!this.ctor},b.prototype.jumps=D,b.prototype.compileNode=function(a){var b,e,f,g,h,i,j,k,l,m,n,o,p,q,s,t,v,w,x,y,z,B,C,D,E,G,H,I,J,K,L,M,O;a.scope=new N(a.scope,this.body,this),a.scope.shared=$(a,"sharedScope"),a.indent+=R,delete a.bare,delete a.isExistentialEquals,l=[],e=[],H=this.paramNames();for(s=0,x=H.length;s=u?"("+b+")":b},b.prototype.paramNames=function(){var a,b,c,d,e;a=[],e=this.params;for(c=0,d=e.length;c=0)throw SyntaxError('parameter name "'+a+'" is not allowed')}return bm(b,a),b.prototype.children=["name","value"],b.prototype.compile=function(a){return this.name.compile(a,w)},b.prototype.asReference=function(a){var b;return this.reference?this.reference:(b=this.name,b["this"]?(b=b.properties[0].name,b.value.reserved&&(b=new A(a.scope.freeVariable(b.value)))):b.isComplex()&&(b=new A(a.scope.freeVariable("arg"))),b=new W(b),this.splat&&(b=new P(b)),this.reference=b)},b.prototype.isComplex=function(){return this.name.isComplex()},b.prototype.names=function(a){var b,c,e,f,g,h;a==null&&(a=this.name),b=function(a){var b;return b=a.properties[0].name.value,b.reserved?[]:[b]};if(a instanceof A)return[a.value];if(a instanceof W)return b(a);c=[],h=a.objects;for(f=0,g=h.length;f=c.length)return"";if(c.length===1)return g=c[0].compile(a,w),d?g:""+bi("slice")+".call("+g+")";e=c.slice(i);for(h=k=0,l=e.length;k1?b.expressions.unshift(new r((new H(this.guard)).invert(),new A("continue"))):this.guard&&(b=f.wrap([new r(this.guard,b)]))),b="\n"+b.compile(a,z)+"\n"+this.tab),c=e+this.tab+("while ("+this.condition.compile(a,y)+") {"+b+"}"),this.returns&&(c+="\n"+this.tab+"return "+d+";"),c},b}(e),a.Op=F=function(a){function e(a,c,d,e){if(a==="in")return new s(c,d);if(a==="do")return this.generateDo(c);if(a==="new"){if(c instanceof g&&!c["do"]&&!c.isNew)return c.newInstance();if(c instanceof j&&c.bound||c["do"])c=new H(c)}return this.operator=b[a]||a,this.first=c,this.second=d,this.flip=!!e,this}var b,c;return bm(e,a),b={"==":"===","!=":"!==",of:"in"},c={"!==":"===","===":"!=="},e.prototype.children=["first","second"],e.prototype.isSimpleNumber=D,e.prototype.isUnary=function(){return!this.second},e.prototype.isComplex=function(){var a;return!this.isUnary()||(a=this.operator)!=="+"&&a!=="-"||this.first.isComplex()},e.prototype.isChainable=function(){var a;return(a=this.operator)==="<"||a===">"||a===">="||a==="<="||a==="==="||a==="!=="},e.prototype.invert=function(){var a,b,d,f,g;if(this.isChainable()&&this.first.isChainable()){a=!0,b=this;while(b&&b.operator)a&&(a=b.operator in c),b=b.first;if(!a)return(new H(this)).invert();b=this;while(b&&b.operator)b.invert=!b.invert,b.operator=c[b.operator],b=b.first;return this}return(f=c[this.operator])?(this.operator=f,this.first.unwrap()instanceof e&&this.first.invert(),this):this.second?(new H(this)).invert():this.operator==="!"&&(d=this.first.unwrap())instanceof e&&((g=d.operator)==="!"||g==="in"||g==="instanceof")?d:new e("!",this)},e.prototype.unfoldSoak=function(a){var b;return((b=this.operator)==="++"||b==="--"||b==="delete")&&bh(a,this,"first")},e.prototype.generateDo=function(a){var b,c,e,f,h,i,k,l;f=[],c=a instanceof d&&(h=a.value.unwrap())instanceof j?h:a,l=c.params||[];for(i=0,k=l.length;i=0))throw SyntaxError("prefix increment/decrement may not have eval or arguments operand");return this.isUnary()?this.compileUnary(a):c?this.compileChain(a):this.operator==="?"?this.compileExistence(a):(b=this.first.compile(a,x)+" "+this.operator+" "+this.second.compile(a,x),a.level<=x?b:"("+b+")")},e.prototype.compileChain=function(a){var b,c,d,e;return e=this.first.second.cache(a),this.first.second=e[0],d=e[1],c=this.first.compile(a,x),b=""+c+" "+(this.invert?"&&":"||")+" "+d.compile(a)+" "+this.operator+" "+this.second.compile(a,x),"("+b+")"},e.prototype.compileExistence=function(a){var b,c;return this.first.isComplex()?(c=new A(a.scope.freeVariable("ref")),b=new H(new d(c,this.first))):(b=this.first,c=b),(new r(new l(b),c,{type:"if"})).addElse(this.second).compile(a)},e.prototype.compileUnary=function(a){var b,c,d;if(a.level>=u)return(new H(this)).compile(a);c=[b=this.operator],d=b==="+"||b==="-",(b==="new"||b==="typeof"||b==="delete"||d&&this.first instanceof e&&this.first.operator===b)&&c.push(" ");if(d&&this.first instanceof e||b==="new"&&this.first.isStatement(a))this.first=new H(this.first);return c.push(this.first.compile(a,x)),this.flip&&c.reverse(),c.join("")},e.prototype.toString=function(a){return e.__super__.toString.call(this,a,this.constructor.name+" "+this.operator)},e}(e),a.In=s=function(a){function b(a,b){this.object=a,this.array=b}return bm(b,a),b.prototype.children=["object","array"],b.prototype.invert=C,b.prototype.compileNode=function(a){var b,c,d,e,f;if(this.array instanceof W&&this.array.isArray()){f=this.array.base.objects;for(d=0,e=f.length;d= 0"),d===c?b:(b=d+", "+b,a.level=0)throw SyntaxError('catch variable may not be "'+this.error.value+'"');return a.scope.check(this.error.value)||a.scope.add(this.error.value,"param")," catch"+d+"{\n"+this.recovery.compile(a,z)+"\n"+this.tab+"}"}if(!this.ensure&&!this.recovery)return" catch (_error) {}"}.call(this),c=this.ensure?" finally {\n"+this.ensure.compile(a,z)+"\n"+this.tab+"}":"",""+this.tab+"try {\n"+e+"\n"+this.tab+"}"+(b||"")+c},b}(e),a.Throw=T=function(a){function b(a){this.expression=a}return bm(b,a),b.prototype.children=["expression"],b.prototype.isStatement=Y,b.prototype.jumps=D,b.prototype.makeReturn=S,b.prototype.compileNode=function(a){return this.tab+("throw "+this.expression.compile(a)+";")},b}(e),a.Existence=l=function(a){function b(a){this.expression=a}return bm(b,a),b.prototype.children=["expression"],b.prototype.invert=C,b.prototype.compileNode=function(a){var b,c,d,e;return this.expression.front=this.front,d=this.expression.compile(a,x),o.test(d)&&!a.scope.check(d)?(e=this.negated?["===","||"]:["!==","&&"],b=e[0],c=e[1],d="typeof "+d+" "+b+' "undefined" '+c+" "+d+" "+b+" null"):d=""+d+" "+(this.negated?"==":"!=")+" null",a.level<=v?d:"("+d+")"},b}(e),a.Parens=H=function(a){function b(a){this.body=a}return bm(b,a),b.prototype.children=["body"],b.prototype.unwrap=function(){return this.body},b.prototype.isComplex=function(){return this.body.isComplex()},b.prototype.compileNode=function(a){var b,c,d;return d=this.body.unwrap(),d instanceof W&&d.isAtomic()?(d.front=this.front,d.compile(a)):(c=d.compile(a,y),b=a.level1?b.expressions.unshift(new r((new H(this.guard)).invert(),new A("continue"))):this.guard&&(b=f.wrap([new r(this.guard,b)]))),this.pattern&&b.expressions.unshift(new d(this.name,new A(""+F+"["+l+"]"))),c+=this.pluckDirectCall(a,b),s&&(G="\n"+i+s+";"),this.object&&(e=""+l+" in "+F,this.own&&(h="\n"+i+"if (!"+bi("hasProp")+".call("+F+", "+l+")) continue;")),b=b.compile(bd(a,{indent:i}),z),b&&(b="\n"+b+"\n"),""+c+(u||"")+this.tab+"for ("+e+") {"+h+G+b+this.tab+"}"+(v||"")},b.prototype.pluckDirectCall=function(a,b){var c,e,f,h,i,k,l,m,n,o,p,q,r,s,t;e="",o=b.expressions;for(i=m=0,n=o.length;m=v?"("+d+")":d},b.prototype.unfoldSoak=function(){return this.soak&&this},b}(e),i={wrap:function(a,c,d){var e,h,i,k,l;if(a.jumps())return a;i=new j([],f.wrap([a])),e=[];if((k=a.contains(this.literalArgs))||a.contains(this.literalThis))l=new A(k?"apply":"call"),e=[new A("this")],k&&e.push(new A("arguments")),i=new W(i,[new b(l)]);return i.noReturn=d,h=new g(i,e),c?f.wrap([h]):h},literalArgs:function(a){return a instanceof A&&a.value==="arguments"&&!a.asKey},literalThis:function(a){return a instanceof A&&a.value==="this"&&!a.asKey||a instanceof j&&a.bound||a instanceof g&&a.isSuper}},bh=function(a,b,c){var d;if(!(d=b[c].unfoldSoak(a)))return;return b[c]=d.body,d.body=new W(b),d},V={"extends":function(){return"function(child, parent) { for (var key in parent) { if ("+bi("hasProp")+".call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }"},bind:function(){return"function(fn, me){ return function(){ return fn.apply(me, arguments); }; }"},indexOf:function(){return"[].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }"},hasProp:function(){return"{}.hasOwnProperty"},slice:function(){return"[].slice"}},z=1,y=2,w=3,v=4,x=5,u=6,R=" ",p="[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*",o=RegExp("^"+p+"$"),L=/^[+-]?\d+$/,B=RegExp("^(?:("+p+")\\.prototype(?:\\.("+p+")|\\[(\"(?:[^\\\\\"\\r\\n]|\\\\.)*\"|'(?:[^\\\\'\\r\\n]|\\\\.)*')\\]|\\[(0x[\\da-fA-F]+|\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\]))|("+p+")$"),q=/^['"]/,bi=function(a){var b;return b="__"+a,N.root.assign(b,V[a]()),b},be=function(a,b){return a=a.replace(/\n/g,"$&"+b),a.replace(/\s+$/,"")}})).call(this)},require["./coffee-script"]=new function(){var a=this;((function(){var b,c,d,e,f,g,h,i,j,k,l={}.hasOwnProperty;e=require("fs"),h=require("path"),k=require("./lexer"),b=k.Lexer,c=k.RESERVED,g=require("./parser").parser,j=require("vm"),i=function(a){return a.charCodeAt(0)===65279?a.substring(1):a},require.extensions&&(require.extensions[".coffee"]=function(a,b){var c;return c=d(i(e.readFileSync(b,"utf8")),{filename:b}),a._compile(c,b)}),a.VERSION="1.4.0",a.RESERVED=c,a.helpers=require("./helpers"),a.compile=d=function(b,c){var d,e,h;c==null&&(c={}),h=a.helpers.merge;try{e=g.parse(f.tokenize(b)).compile(c);if(!c.header)return e}catch(i){throw c.filename&&(i.message="In "+c.filename+", "+i.message),i}return d="Generated by CoffeeScript "+this.VERSION,"// "+d+"\n"+e},a.tokens=function(a,b){return f.tokenize(a,b)},a.nodes=function(a,b){return typeof a=="string"?g.parse(f.tokenize(a,b)):g.parse(a)},a.run=function(a,b){var c;return b==null&&(b={}),c=require.main,c.filename=process.argv[1]=b.filename?e.realpathSync(b.filename):".",c.moduleCache&&(c.moduleCache={}),c.paths=require("module")._nodeModulePaths(h.dirname(e.realpathSync(b.filename))),h.extname(c.filename)!==".coffee"||require.extensions?c._compile(d(a,b),c.filename):c._compile(a,c.filename)},a.eval=function(a,b){var c,e,f,g,i,k,m,n,o,p,q,r,s,t;b==null&&(b={});if(!(a=a.trim()))return;e=j.Script;if(e){if(b.sandbox!=null){if(b.sandbox instanceof e.createContext().constructor)m=b.sandbox;else{m=e.createContext(),r=b.sandbox;for(g in r){if(!l.call(r,g))continue;n=r[g],m[g]=n}}m.global=m.root=m.GLOBAL=m}else m=global;m.__filename=b.filename||"eval",m.__dirname=h.dirname(m.__filename);if(m===global&&!m.module&&!m.require){c=require("module"),m.module=q=new c(b.modulename||"eval"),m.require=t=function(a){return c._load(a,q,!0)},q.filename=m.__filename,s=Object.getOwnPropertyNames(require);for(o=0,p=s.length;oe;e++)if(e in this&&this[e]===t)return e;return-1};if(t=function(){function t(t){this.client=new e(t)}return t}(),t.ApiError=function(){function t(t,e,r){var n;if(this.method=e,this.url=r,this.status=t.status,n=t.responseType?t.response||t.responseText:t.responseText)try{this.responseText=""+n,this.response=JSON.parse(n)}catch(o){this.response=null}else this.responseText="(no response)",this.response=null}return t.prototype.status=void 0,t.prototype.method=void 0,t.prototype.url=void 0,t.prototype.responseText=void 0,t.prototype.response=void 0,t.prototype.toString=function(){return"Dropbox API error "+this.status+" from "+this.method+" "+this.url+" :: "+this.responseText},t.prototype.inspect=function(){return""+this},t}(),"undefined"!=typeof window&&null!==window?window.atob&&window.btoa?(u=function(t){return window.atob(t)},f=function(t){return window.btoa(t)}):(p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",y=function(t,e,r){var n,o;for(o=3-e,t<<=8*o,n=3;n>=o;)r.push(p.charAt(63&t>>6*n)),n-=1;for(n=e;3>n;)r.push("="),n+=1;return null},l=function(t,e,r){var n,o;for(o=4-e,t<<=6*o,n=2;n>=o;)r.push(String.fromCharCode(255&t>>8*n)),n-=1;return null},f=function(t){var e,r,n,o,i,s;for(o=[],e=0,r=0,n=i=0,s=t.length;s>=0?s>i:i>s;n=s>=0?++i:--i)e=e<<8|t.charCodeAt(n),r+=1,3===r&&(y(e,r,o),e=r=0);return r>0&&y(e,r,o),o.join("")},u=function(t){var e,r,n,o,i,s,a;for(i=[],e=0,n=0,o=s=0,a=t.length;(a>=0?a>s:s>a)&&(r=t.charAt(o),"="!==r);o=a>=0?++s:--s)e=e<<6|p.indexOf(r),n+=1,4===n&&(l(e,n,i),e=n=0);return n>0&&l(e,n,i),i.join("")}):(u=function(t){var e,r;return e=new Buffer(t,"base64"),function(){var t,n,o;for(o=[],r=t=0,n=e.length;n>=0?n>t:t>n;r=n>=0?++t:--t)o.push(String.fromCharCode(e[r]));return o}().join("")},f=function(t){var e,r;return e=new Buffer(function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(t.charCodeAt(r));return o}()),e.toString("base64")}),t.Client=function(){function r(e){this.sandbox=e.sandbox||!1,this.apiServer=e.server||this.defaultApiServer(),this.authServer=e.authServer||this.defaultAuthServer(),this.fileServer=e.fileServer||this.defaultFileServer(),this.downloadServer=e.downloadServer||this.defaultDownloadServer(),this.onXhr=new t.EventSource({cancelable:!0}),this.onError=new t.EventSource,this.onAuthStateChange=new t.EventSource,this.oauth=new t.Oauth(e),this.driver=null,this.filter=null,this.uid=null,this.authState=null,this.authError=null,this._credentials=null,this.setCredentials(e),this.setupUrls()}return r.prototype.authDriver=function(t){return this.driver=t,this},r.prototype.onXhr=null,r.prototype.onError=null,r.prototype.onAuthStateChange=null,r.prototype.dropboxUid=function(){return this.uid},r.prototype.credentials=function(){return this._credentials||this.computeCredentials(),this._credentials},r.prototype.authenticate=function(r){var n,o,i=this;if(n=null,!this.driver&&this.authState!==e.DONE)throw Error("Call authDriver to set an authentication driver");return o=function(){var s;if(n!==i.authState&&(null!==n&&i.onAuthStateChange.dispatch(i),n=i.authState,i.driver&&i.driver.onAuthStateChange))return i.driver.onAuthStateChange(i,o);switch(i.authState){case e.RESET:return i.requestToken(function(t,r){var n,s;return t?(i.authError=t,i.authState=e.ERROR):(n=r.oauth_token,s=r.oauth_token_secret,i.oauth.setToken(n,s),i.authState=e.REQUEST),i._credentials=null,o()});case e.REQUEST:return s=i.authorizeUrl(i.oauth.token),i.driver.doAuthorize(s,i.oauth.token,i.oauth.tokenSecret,function(){return i.authState=e.AUTHORIZED,i._credentials=null,o()});case e.AUTHORIZED:return i.getAccessToken(function(t,r){return t?(i.authError=t,i.authState=e.ERROR):(i.oauth.setToken(r.oauth_token,r.oauth_token_secret),i.uid=r.uid,i.authState=e.DONE),i._credentials=null,o()});case e.DONE:return r(null,i);case t.SIGNED_OFF:return i.authState=e.RESET,i.reset(),o();case e.ERROR:return r(i.authError)}},o(),this},r.prototype.isAuthenticated=function(){return this.authState===e.DONE},r.prototype.signOut=function(r){var n,o=this;return n=new t.Xhr("POST",this.urls.signOut),n.signWithOauth(this.oauth),this.dispatchXhr(n,function(t){return t?r(t):(o.authState=e.RESET,o.reset(),o.authState=e.SIGNED_OFF,o.onAuthStateChange.dispatch(o),o.driver.onAuthStateChange?o.driver.onAuthStateChange(o,function(){return r(t)}):r(t))})},r.prototype.signOff=function(t){return this.signOut(t)},r.prototype.getUserInfo=function(e){var r;return r=new t.Xhr("GET",this.urls.accountInfo),r.signWithOauth(this.oauth),this.dispatchXhr(r,function(r,n){return e(r,t.UserInfo.parse(n),n)})},r.prototype.readFile=function(e,r,n){var o,i,s,a,h,u;return n||"function"!=typeof r||(n=r,r=null),o={},h=null,s=null,r&&(r.versionTag?o.rev=r.versionTag:r.rev&&(o.rev=r.rev),r.arrayBuffer?h="arraybuffer":r.blob?h="blob":r.binary&&(h="b"),r.length?(null!=r.start?(a=r.start,i=r.start+r.length-1):(a="",i=r.length),s="bytes="+a+"-"+i):null!=r.start&&(s="bytes="+r.start+"-")),u=new t.Xhr("GET",""+this.urls.getFile+"/"+this.urlEncodePath(e)),u.setParams(o).signWithOauth(this.oauth).setResponseType(h),s&&u.setHeader("Range",s),this.dispatchXhr(u,function(e,r,o){return n(e,r,t.Stat.parse(o))})},r.prototype.writeFile=function(e,r,n,o){var i;return o||"function"!=typeof n||(o=n,n=null),i=t.Xhr.canSendForms&&"object"==typeof r,i?this.writeFileUsingForm(e,r,n,o):this.writeFileUsingPut(e,r,n,o)},r.prototype.writeFileUsingForm=function(e,r,n,o){var i,s,a,h;return a=e.lastIndexOf("/"),-1===a?(i=e,e=""):(i=e.substring(a),e=e.substring(0,a)),s={file:i},n&&(n.noOverwrite&&(s.overwrite="false"),n.lastVersionTag?s.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(s.parent_rev=n.parentRev||n.parent_rev)),h=new t.Xhr("POST",""+this.urls.postFile+"/"+this.urlEncodePath(e)),h.setParams(s).signWithOauth(this.oauth).setFileField("file",i,r,"application/octet-stream"),delete s.file,this.dispatchXhr(h,function(e,r){return o(e,t.Stat.parse(r))})},r.prototype.writeFileUsingPut=function(e,r,n,o){var i,s;return i={},n&&(n.noOverwrite&&(i.overwrite="false"),n.lastVersionTag?i.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(i.parent_rev=n.parentRev||n.parent_rev)),s=new t.Xhr("POST",""+this.urls.putFile+"/"+this.urlEncodePath(e)),s.setBody(r).setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return o(e,t.Stat.parse(r))})},r.prototype.resumableUploadStep=function(e,r,n){var o,i;return r?(o={offset:r.offset},r.tag&&(o.upload_id=r.tag)):o={offset:0},i=new t.Xhr("POST",this.urls.chunkedUpload),i.setBody(e).setParams(o).signWithOauth(this.oauth),this.dispatchXhr(i,function(e,r){return e&&400===e.status&&e.response.upload_id&&e.response.offset?n(null,t.UploadCursor.parse(e.response)):n(e,t.UploadCursor.parse(r))})},r.prototype.resumableUploadFinish=function(e,r,n,o){var i,s;return o||"function"!=typeof n||(o=n,n=null),i={upload_id:r.tag},n&&(n.lastVersionTag?i.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(i.parent_rev=n.parentRev||n.parent_rev),n.noOverwrite&&(i.autorename=!0)),s=new t.Xhr("POST",""+this.urls.commitChunkedUpload+"/"+this.urlEncodePath(e)),s.setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return o(e,t.Stat.parse(r))})},r.prototype.stat=function(e,r,n){var o,i;return n||"function"!=typeof r||(n=r,r=null),o={},r&&(null!=r.version&&(o.rev=r.version),(r.removed||r.deleted)&&(o.include_deleted="true"),r.readDir&&(o.list="true",r.readDir!==!0&&(o.file_limit=""+r.readDir)),r.cacheHash&&(o.hash=r.cacheHash)),o.include_deleted||(o.include_deleted="false"),o.list||(o.list="false"),i=new t.Xhr("GET",""+this.urls.metadata+"/"+this.urlEncodePath(e)),i.setParams(o).signWithOauth(this.oauth),this.dispatchXhr(i,function(e,r){var o,i,s;return s=t.Stat.parse(r),o=(null!=r?r.contents:void 0)?function(){var e,n,o,s;for(o=r.contents,s=[],e=0,n=o.length;n>e;e++)i=o[e],s.push(t.Stat.parse(i));return s}():void 0,n(e,s,o)})},r.prototype.readdir=function(t,e,r){var n;return r||"function"!=typeof e||(r=e,e=null),n={readDir:!0},e&&(null!=e.limit&&(n.readDir=e.limit),e.versionTag&&(n.versionTag=e.versionTag)),this.stat(t,n,function(t,e,n){var o,i;return o=n?function(){var t,e,r;for(r=[],t=0,e=n.length;e>t;t++)i=n[t],r.push(i.name);return r}():null,r(t,o,e,n)})},r.prototype.metadata=function(t,e,r){return this.stat(t,e,r)},r.prototype.makeUrl=function(e,r,n){var o,i,s,a,h;return n||"function"!=typeof r||(n=r,r=null),i=r&&(r["long"]||r.longUrl||r.downloadHack)?{short_url:"false"}:{},e=this.urlEncodePath(e),s=""+this.urls.shares+"/"+e,o=!1,a=!1,r&&(r.downloadHack?(o=!0,a=!0):r.download&&(o=!0,s=""+this.urls.media+"/"+e)),h=new t.Xhr("POST",s).setParams(i).signWithOauth(this.oauth),this.dispatchXhr(h,function(e,r){return a&&r&&r.url&&(r.url=r.url.replace(this.authServer,this.downloadServer)),n(e,t.PublicUrl.parse(r,o))})},r.prototype.history=function(e,r,n){var o,i;return n||"function"!=typeof r||(n=r,r=null),o={},r&&null!=r.limit&&(o.rev_limit=r.limit),i=new t.Xhr("GET",""+this.urls.revisions+"/"+this.urlEncodePath(e)),i.setParams(o).signWithOauth(this.oauth),this.dispatchXhr(i,function(e,r){var o,i;return i=r?function(){var e,n,i;for(i=[],e=0,n=r.length;n>e;e++)o=r[e],i.push(t.Stat.parse(o));return i}():void 0,n(e,i)})},r.prototype.revisions=function(t,e,r){return this.history(t,e,r)},r.prototype.thumbnailUrl=function(t,e){var r;return r=this.thumbnailXhr(t,e),r.paramsToUrl().url},r.prototype.readThumbnail=function(e,r,n){var o,i;return n||"function"!=typeof r||(n=r,r=null),o="b",r&&r.blob&&(o="blob"),i=this.thumbnailXhr(e,r),i.setResponseType(o),this.dispatchXhr(i,function(e,r,o){return n(e,r,t.Stat.parse(o))})},r.prototype.thumbnailXhr=function(e,r){var n,o;return n={},r&&(r.format?n.format=r.format:r.png&&(n.format="png"),r.size&&(n.size=r.size)),o=new t.Xhr("GET",""+this.urls.thumbnails+"/"+this.urlEncodePath(e)),o.setParams(n).signWithOauth(this.oauth)},r.prototype.revertFile=function(e,r,n){var o;return o=new t.Xhr("POST",""+this.urls.restore+"/"+this.urlEncodePath(e)),o.setParams({rev:r}).signWithOauth(this.oauth),this.dispatchXhr(o,function(e,r){return n(e,t.Stat.parse(r))})},r.prototype.restore=function(t,e,r){return this.revertFile(t,e,r)},r.prototype.findByName=function(e,r,n,o){var i,s;return o||"function"!=typeof n||(o=n,n=null),i={query:r},n&&(null!=n.limit&&(i.file_limit=n.limit),(n.removed||n.deleted)&&(i.include_deleted=!0)),s=new t.Xhr("GET",""+this.urls.search+"/"+this.urlEncodePath(e)),s.setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){var n,i;return i=r?function(){var e,o,i;for(i=[],e=0,o=r.length;o>e;e++)n=r[e],i.push(t.Stat.parse(n));return i}():void 0,o(e,i)})},r.prototype.search=function(t,e,r,n){return this.findByName(t,e,r,n)},r.prototype.makeCopyReference=function(e,r){var n;return n=new t.Xhr("GET",""+this.urls.copyRef+"/"+this.urlEncodePath(e)),n.signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r(e,t.CopyReference.parse(n))})},r.prototype.copyRef=function(t,e){return this.makeCopyReference(t,e)},r.prototype.pullChanges=function(e,r){var n,o;return r||"function"!=typeof e||(r=e,e=null),n=e?e.cursorTag?{cursor:e.cursorTag}:{cursor:e}:{},o=new t.Xhr("POST",this.urls.delta),o.setParams(n).signWithOauth(this.oauth),this.dispatchXhr(o,function(e,n){return r(e,t.PulledChanges.parse(n))})},r.prototype.delta=function(t,e){return this.pullChanges(t,e)},r.prototype.mkdir=function(e,r){var n;return n=new t.Xhr("POST",this.urls.fileopsCreateFolder),n.setParams({root:this.fileRoot,path:this.normalizePath(e)}).signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r(e,t.Stat.parse(n))})},r.prototype.remove=function(e,r){var n;return n=new t.Xhr("POST",this.urls.fileopsDelete),n.setParams({root:this.fileRoot,path:this.normalizePath(e)}).signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r(e,t.Stat.parse(n))})},r.prototype.unlink=function(t,e){return this.remove(t,e)},r.prototype["delete"]=function(t,e){return this.remove(t,e)},r.prototype.copy=function(e,r,n){var o,i,s;return n||"function"!=typeof o||(n=o,o=null),i={root:this.fileRoot,to_path:this.normalizePath(r)},e instanceof t.CopyReference?i.from_copy_ref=e.tag:i.from_path=this.normalizePath(e),s=new t.Xhr("POST",this.urls.fileopsCopy),s.setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return n(e,t.Stat.parse(r))})},r.prototype.move=function(e,r,n){var o,i;return n||"function"!=typeof o||(n=o,o=null),i=new t.Xhr("POST",this.urls.fileopsMove),i.setParams({root:this.fileRoot,from_path:this.normalizePath(e),to_path:this.normalizePath(r)}).signWithOauth(this.oauth),this.dispatchXhr(i,function(e,r){return n(e,t.Stat.parse(r))})},r.prototype.reset=function(){var t;return this.uid=null,this.oauth.setToken(null,""),t=this.authState,this.authState=e.RESET,t!==this.authState&&this.onAuthStateChange.dispatch(this),this.authError=null,this._credentials=null,this},r.prototype.setCredentials=function(t){var r;return r=this.authState,this.oauth.reset(t),this.uid=t.uid||null,this.authState=t.authState?t.authState:t.token?e.DONE:e.RESET,this.authError=null,this._credentials=null,r!==this.authState&&this.onAuthStateChange.dispatch(this),this},r.prototype.appHash=function(){return this.oauth.appHash()},r.prototype.setupUrls=function(){return this.fileRoot=this.sandbox?"sandbox":"dropbox",this.urls={requestToken:""+this.apiServer+"/1/oauth/request_token",authorize:""+this.authServer+"/1/oauth/authorize",accessToken:""+this.apiServer+"/1/oauth/access_token",signOut:""+this.apiServer+"/1/unlink_access_token",accountInfo:""+this.apiServer+"/1/account/info",getFile:""+this.fileServer+"/1/files/"+this.fileRoot,postFile:""+this.fileServer+"/1/files/"+this.fileRoot,putFile:""+this.fileServer+"/1/files_put/"+this.fileRoot,metadata:""+this.apiServer+"/1/metadata/"+this.fileRoot,delta:""+this.apiServer+"/1/delta",revisions:""+this.apiServer+"/1/revisions/"+this.fileRoot,restore:""+this.apiServer+"/1/restore/"+this.fileRoot,search:""+this.apiServer+"/1/search/"+this.fileRoot,shares:""+this.apiServer+"/1/shares/"+this.fileRoot,media:""+this.apiServer+"/1/media/"+this.fileRoot,copyRef:""+this.apiServer+"/1/copy_ref/"+this.fileRoot,thumbnails:""+this.fileServer+"/1/thumbnails/"+this.fileRoot,chunkedUpload:""+this.fileServer+"/1/chunked_upload",commitChunkedUpload:""+this.fileServer+"/1/commit_chunked_upload/"+this.fileRoot,fileopsCopy:""+this.apiServer+"/1/fileops/copy",fileopsCreateFolder:""+this.apiServer+"/1/fileops/create_folder",fileopsDelete:""+this.apiServer+"/1/fileops/delete",fileopsMove:""+this.apiServer+"/1/fileops/move"}},r.prototype.authState=null,r.ERROR=0,r.RESET=1,r.REQUEST=2,r.AUTHORIZED=3,r.DONE=4,r.SIGNED_OFF=5,r.prototype.urlEncodePath=function(e){return t.Xhr.urlEncodeValue(this.normalizePath(e)).replace(/%2F/gi,"/")},r.prototype.normalizePath=function(t){var e;if("/"===t.substring(0,1)){for(e=1;"/"===t.substring(e,e+1);)e+=1;return t.substring(e)}return t},r.prototype.requestToken=function(e){var r;return r=new t.Xhr("POST",this.urls.requestToken).signWithOauth(this.oauth),this.dispatchXhr(r,e)},r.prototype.authorizeUrl=function(e){var r;return r={oauth_token:e,oauth_callback:this.driver.url()},""+this.urls.authorize+"?"+t.Xhr.urlEncode(r)},r.prototype.getAccessToken=function(e){var r;return r=new t.Xhr("POST",this.urls.accessToken).signWithOauth(this.oauth),this.dispatchXhr(r,e)},r.prototype.dispatchXhr=function(t,e){var r;return t.setCallback(e),t.onError=this.onError,t.prepare(),r=t.xhr,this.onXhr.dispatch(t)&&t.send(),r},r.prototype.defaultApiServer=function(){return"https://api.dropbox.com"},r.prototype.defaultAuthServer=function(){return this.apiServer.replace("api.","www.")},r.prototype.defaultFileServer=function(){return this.apiServer.replace("api.","api-content.")},r.prototype.defaultDownloadServer=function(){return this.apiServer.replace("api.","dl.")},r.prototype.computeCredentials=function(){var t;return t={key:this.oauth.key,sandbox:this.sandbox},this.oauth.secret&&(t.secret=this.oauth.secret),this.oauth.token&&(t.token=this.oauth.token,t.tokenSecret=this.oauth.tokenSecret),this.uid&&(t.uid=this.uid),this.authState!==e.ERROR&&this.authState!==e.RESET&&this.authState!==e.DONE&&this.authState!==e.SIGNED_OFF&&(t.authState=this.authState),this.apiServer!==this.defaultApiServer()&&(t.server=this.apiServer),this.authServer!==this.defaultAuthServer()&&(t.authServer=this.authServer),this.fileServer!==this.defaultFileServer()&&(t.fileServer=this.fileServer),this.downloadServer!==this.defaultDownloadServer()&&(t.downloadServer=this.downloadServer),this._credentials=t},r}(),e=t.Client,t.AuthDriver=function(){function t(){}return t.prototype.url=function(){return"https://some.url"},t.prototype.doAuthorize=function(t,e,r,n){return n("oauth-token")},t.prototype.onAuthStateChange=function(t,e){return e()},t}(),t.Drivers={},t.Drivers.BrowserBase=function(){function t(t){this.rememberUser=(null!=t?t.rememberUser:void 0)||!1,this.scope=(null!=t?t.scope:void 0)||"default"}return t.prototype.onAuthStateChange=function(t,r){var n=this;switch(this.setStorageKey(t),t.authState){case e.RESET:return this.loadCredentials(function(e){return e?e.authState?(t.setCredentials(e),r()):n.rememberUser?(t.setCredentials(e),t.getUserInfo(function(e){return e?(t.reset(),n.forgetCredentials(r)):r()})):(n.forgetCredentials(),r()):r()});case e.REQUEST:return this.storeCredentials(t.credentials(),r);case e.DONE:return this.rememberUser?this.storeCredentials(t.credentials(),r):this.forgetCredentials(r);case e.SIGNED_OFF:return this.forgetCredentials(r);case e.ERROR:return this.forgetCredentials(r);default:return r(),this}},t.prototype.setStorageKey=function(t){return this.storageKey="dropbox-auth:"+this.scope+":"+t.appHash(),this},t.prototype.storeCredentials=function(t,e){return localStorage.setItem(this.storageKey,JSON.stringify(t)),e(),this},t.prototype.loadCredentials=function(t){var e;if(e=localStorage.getItem(this.storageKey),!e)return t(null),this;try{t(JSON.parse(e))}catch(r){t(null)}return this},t.prototype.forgetCredentials=function(t){return localStorage.removeItem(this.storageKey),t(),this},t.currentLocation=function(){return window.location.href},t}(),t.Drivers.Redirect=function(r){function n(t){n.__super__.constructor.call(this,t),this.useQuery=(null!=t?t.useQuery:void 0)||!1,this.receiverUrl=this.computeUrl(t),this.tokenRe=RegExp("(#|\\?|&)oauth_token=([^&#]+)(&|#|$)")}return T(n,r),n.prototype.onAuthStateChange=function(t,r){var o,i=this;return o=function(){return function(){return n.__super__.onAuthStateChange.call(i,t,r)}}(),this.setStorageKey(t),t.authState===e.RESET?this.loadCredentials(function(t){return t&&t.authState?t.token===i.locationToken()&&t.authState===e.REQUEST?(t.authState=e.AUTHORIZED,i.storeCredentials(t,o)):i.forgetCredentials(o):o()}):o()},n.prototype.url=function(){return this.receiverUrl},n.prototype.doAuthorize=function(t){return window.location.assign(t)},n.prototype.computeUrl=function(){var e,r,n,o;return o="_dropboxjs_scope="+encodeURIComponent(this.scope),r=t.Drivers.BrowserBase.currentLocation(),-1===r.indexOf("#")?e=null:(n=r.split("#",2),r=n[0],e=n[1]),this.useQuery?r+=-1===r.indexOf("?")?"?"+o:"&"+o:e="?"+o,e?r+"#"+e:r},n.prototype.locationToken=function(){var e,r,n;return e=t.Drivers.BrowserBase.currentLocation(),n="_dropboxjs_scope="+encodeURIComponent(this.scope)+"&",-1===("function"==typeof e.indexOf?e.indexOf(n):void 0)?null:(r=this.tokenRe.exec(e),r?decodeURIComponent(r[2]):null)},n}(t.Drivers.BrowserBase),t.Drivers.Popup=function(r){function n(t){n.__super__.constructor.call(this,t),this.receiverUrl=this.computeUrl(t),this.tokenRe=RegExp("(#|\\?|&)oauth_token=([^&#]+)(&|#|$)")}return T(n,r),n.prototype.onAuthStateChange=function(t,r){var o,i=this;return o=function(){return function(){return n.__super__.onAuthStateChange.call(i,t,r)}}(),this.setStorageKey(t),t.authState===e.RESET?this.loadCredentials(function(t){return t&&t.authState?i.forgetCredentials(o):o()}):o()},n.prototype.doAuthorize=function(t,e,r,n){return this.listenForMessage(e,n),this.openWindow(t)},n.prototype.url=function(){return this.receiverUrl},n.prototype.computeUrl=function(e){var r;if(e){if(e.receiverUrl)return e.noFragment||-1!==e.receiverUrl.indexOf("#")?e.receiverUrl:e.receiverUrl+"#";if(e.receiverFile)return r=t.Drivers.BrowserBase.currentLocation().split("/"),r[r.length-1]=e.receiverFile,e.noFragment?r.join("/"):r.join("/")+"#"}return t.Drivers.BrowserBase.currentLocation()},n.prototype.openWindow=function(t){return window.open(t,"_dropboxOauthSigninWindow",this.popupWindowSpec(980,700))},n.prototype.popupWindowSpec=function(t,e){var r,n,o,i,s,a,h,u,l,p;return s=null!=(h=window.screenX)?h:window.screenLeft,a=null!=(u=window.screenY)?u:window.screenTop,i=null!=(l=window.outerWidth)?l:document.documentElement.clientWidth,r=null!=(p=window.outerHeight)?p:document.documentElement.clientHeight,n=Math.round(s+(i-t)/2),o=Math.round(a+(r-e)/2.5),s>n&&(n=s),a>o&&(o=a),"width="+t+",height="+e+","+("left="+n+",top="+o)+"dialog=yes,dependent=yes,scrollbars=yes,location=yes"},n.prototype.listenForMessage=function(t,e){var r,n=this;return r=function(o){var i;return i=n.tokenRe.exec(""+o.data),i&&decodeURIComponent(i[2])===t?(window.removeEventListener("message",r),e()):void 0},window.addEventListener("message",r,!1)},n}(t.Drivers.BrowserBase),t.Drivers.NodeServer=function(){function t(t){this.port=(null!=t?t.port:void 0)||8912,this.faviconFile=(null!=t?t.favicon:void 0)||null,this.fs=require("fs"),this.http=require("http"),this.open=require("open"),this.callbacks={},this.urlRe=RegExp("^/oauth_callback\\?"),this.tokenRe=RegExp("(\\?|&)oauth_token=([^&]+)(&|$)"),this.createApp()}return t.prototype.url=function(){return"http://localhost:"+this.port+"/oauth_callback"},t.prototype.doAuthorize=function(t,e,r,n){return this.callbacks[e]=n,this.openBrowser(t)},t.prototype.openBrowser=function(t){if(!t.match(/^https?:\/\//))throw Error("Not a http/https URL: "+t);return this.open(t)},t.prototype.createApp=function(){var t=this;return this.app=this.http.createServer(function(e,r){return t.doRequest(e,r)}),this.app.listen(this.port)},t.prototype.closeServer=function(){return this.app.close()},t.prototype.doRequest=function(t,e){var r,n,o,i=this;return this.urlRe.exec(t.url)&&(n=this.tokenRe.exec(t.url),n&&(o=decodeURIComponent(n[2]),this.callbacks[o]&&(this.callbacks[o](),delete this.callbacks[o]))),r="",t.on("data",function(t){return r+=t}),t.on("end",function(){return i.faviconFile&&"/favicon.ico"===t.url?i.sendFavicon(e):i.closeBrowser(e)})},t.prototype.closeBrowser=function(t){var e;return e='\n\n

    Please close this window.

    ',t.writeHead(200,{"Content-Length":e.length,"Content-Type":"text/html"}),t.write(e),t.end},t.prototype.sendFavicon=function(t){return this.fs.readFile(this.faviconFile,function(e,r){return t.writeHead(200,{"Content-Length":r.length,"Content-Type":"image/x-icon"}),t.write(r),t.end})},t}(),t.EventSource=function(){function t(t){this._cancelable=t&&t.cancelable,this._listeners=[]}return t.prototype.addListener=function(t){if("function"!=typeof t)throw new TypeError("Invalid listener type; expected function");return 0>R.call(this._listeners,t)&&this._listeners.push(t),this},t.prototype.removeListener=function(t){var e,r,n,o,i,s;if(this._listeners.indexOf)r=this._listeners.indexOf(t),-1!==r&&this._listeners.splice(r,1);else for(s=this._listeners,e=o=0,i=s.length;i>o;e=++o)if(n=s[e],n===t){this._listeners.splice(e,1);break}return this},t.prototype.dispatch=function(t){var e,r,n,o,i;for(i=this._listeners,n=0,o=i.length;o>n;n++)if(e=i[n],r=e(t),this._cancelable&&r===!1)return!1;return!0},t}(),c=function(t,e){return h(g(b(t),b(e),t.length,e.length))},d=function(t){return h(S(b(t),t.length))},("undefined"==typeof window||null===window)&&(v=require("crypto"),c=function(t,e){var r;return r=v.createHmac("sha1",e),r.update(t),r.digest("base64")},d=function(t){var e;return e=v.createHash("sha1"),e.update(t),e.digest("base64")}),g=function(t,e,r,n){var o,i,s,a;return e.length>16&&(e=S(e,n)),s=function(){var t,r;for(r=[],i=t=0;16>t;i=++t)r.push(909522486^e[i]);return r}(),a=function(){var t,r;for(r=[],i=t=0;16>t;i=++t)r.push(1549556828^e[i]);return r}(),o=S(s.concat(t),64+r),S(a.concat(o),84)},S=function(t,e){var r,n,o,i,s,h,u,l,p,c,d,f,y,v,m,g,S,b;for(t[e>>2]|=1<<31-((3&e)<<3),t[(e+8>>6<<4)+15]=e<<3,g=Array(80),r=1732584193,o=-271733879,s=-1732584194,u=271733878,p=-1009589776,f=0,m=t.length;m>f;){for(n=r,i=o,h=s,l=u,c=p,y=b=0;80>b;y=++b)g[y]=16>y?t[f+y]:w(g[y-3]^g[y-8]^g[y-14]^g[y-16],1),20>y?(d=o&s|~o&u,v=1518500249):40>y?(d=o^s^u,v=1859775393):60>y?(d=o&s|o&u|s&u,v=-1894007588):(d=o^s^u,v=-899497514),S=a(a(w(r,5),d),a(a(p,g[y]),v)),p=u,u=s,s=w(o,30),o=r,r=S;r=a(r,n),o=a(o,i),s=a(s,h),u=a(u,l),p=a(p,c),f+=16}return[r,o,s,u,p]},w=function(t,e){return t<>>32-e},a=function(t,e){var r,n;return n=(65535&t)+(65535&e),r=(t>>16)+(e>>16)+(n>>16),r<<16|65535&n},h=function(t){var e,r,n,o,i;for(o="",e=0,n=4*t.length;n>e;)r=e,i=(255&t[r>>2]>>(3-(3&r)<<3))<<16,r+=1,i|=(255&t[r>>2]>>(3-(3&r)<<3))<<8,r+=1,i|=255&t[r>>2]>>(3-(3&r)<<3),o+=_[63&i>>18],o+=_[63&i>>12],e+=1,o+=e>=n?"=":_[63&i>>6],e+=1,o+=e>=n?"=":_[63&i],e+=1;return o},_="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",b=function(t){var e,r,n,o,i;for(e=[],n=255,r=o=0,i=t.length;i>=0?i>o:o>i;r=i>=0?++o:--o)e[r>>2]|=(t.charCodeAt(r)&n)<<(3-(3&r)<<3);return e},t.Oauth=function(){function e(t){this.key=this.k=null,this.secret=this.s=null,this.token=null,this.tokenSecret=null,this._appHash=null,this.reset(t)}return e.prototype.reset=function(t){var e,r,n,o;if(t.secret)this.k=this.key=t.key,this.s=this.secret=t.secret,this._appHash=null;else if(t.key)this.key=t.key,this.secret=null,n=u(m(this.key).split("|",2)[1]),o=n.split("?",2),e=o[0],r=o[1],this.k=decodeURIComponent(e),this.s=decodeURIComponent(r),this._appHash=null;else if(!this.k)throw Error("No API key supplied");return t.token?this.setToken(t.token,t.tokenSecret):this.setToken(null,"")},e.prototype.setToken=function(e,r){if(e&&!r)throw Error("No secret supplied with the user token");return this.token=e,this.tokenSecret=r||"",this.hmacKey=t.Xhr.urlEncodeValue(this.s)+"&"+t.Xhr.urlEncodeValue(r),null},e.prototype.authHeader=function(e,r,n){var o,i,s,a,h,u;this.addAuthParams(e,r,n),i=[];for(s in n)a=n[s],"oauth_"===s.substring(0,6)&&i.push(s);for(i.sort(),o=[],h=0,u=i.length;u>h;h++)s=i[h],o.push(t.Xhr.urlEncodeValue(s)+'="'+t.Xhr.urlEncodeValue(n[s])+'"'),delete n[s];return"OAuth "+o.join(",")},e.prototype.addAuthParams=function(t,e,r){return this.boilerplateParams(r),r.oauth_signature=this.signature(t,e,r),r},e.prototype.boilerplateParams=function(t){return t.oauth_consumer_key=this.k,t.oauth_nonce=this.nonce(),t.oauth_signature_method="HMAC-SHA1",this.token&&(t.oauth_token=this.token),t.oauth_timestamp=Math.floor(Date.now()/1e3),t.oauth_version="1.0",t},e.prototype.nonce=function(){return Date.now().toString(36)+Math.random().toString(36)},e.prototype.signature=function(e,r,n){var o;return o=e.toUpperCase()+"&"+t.Xhr.urlEncodeValue(r)+"&"+t.Xhr.urlEncodeValue(t.Xhr.urlEncode(n)),c(o,this.hmacKey)},e.prototype.appHash=function(){return this._appHash?this._appHash:this._appHash=d(this.k).replace(/\=/g,"")},e}(),null==Date.now&&(Date.now=function(){return(new Date).getTime()}),m=function(t,e){var r,n,o,i,s,a,h,l,p,c,d,y;for(e?(e=[encodeURIComponent(t),encodeURIComponent(e)].join("?"),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length/2;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(16*(15&t.charCodeAt(2*r))+(15&t.charCodeAt(2*r+1)));return o}()):(c=t.split("|",2),t=c[0],e=c[1],t=u(t),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(t.charCodeAt(r));return o}(),e=u(e)),i=function(){for(y=[],l=0;256>l;l++)y.push(l);return y}.apply(this),a=0,s=p=0;256>p;s=++p)a=(a+i[r]+t[s%t.length])%256,d=[i[a],i[s]],i[s]=d[0],i[a]=d[1];return s=a=0,o=function(){var t,r,o,u;for(u=[],h=t=0,r=e.length;r>=0?r>t:t>r;h=r>=0?++t:--t)s=(s+1)%256,a=(a+i[s])%256,o=[i[a],i[s]],i[s]=o[0],i[a]=o[1],n=i[(i[s]+i[a])%256],u.push(String.fromCharCode((n^e.charCodeAt(h))%256));return u}(),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(String.fromCharCode(t[r]));return o}(),[f(t.join("")),f(o.join(""))].join("|")},t.PulledChanges=function(){function e(e){var r;this.blankSlate=e.reset||!1,this.cursorTag=e.cursor,this.shouldPullAgain=e.has_more,this.shouldBackOff=!this.shouldPullAgain,this.changes=e.cursor&&e.cursor.length?function(){var n,o,i,s;for(i=e.entries,s=[],n=0,o=i.length;o>n;n++)r=i[n],s.push(t.PullChange.parse(r));return s}():[]}return e.parse=function(e){return e&&"object"==typeof e?new t.PulledChanges(e):e},e.prototype.blankSlate=void 0,e.prototype.cursorTag=void 0,e.prototype.changes=void 0,e.prototype.shouldPullAgain=void 0,e.prototype.shouldBackOff=void 0,e.prototype.cursor=function(){return this.cursorTag},e}(),t.PullChange=function(){function e(e){this.path=e[0],this.stat=t.Stat.parse(e[1]),this.stat?this.wasRemoved=!1:(this.stat=null,this.wasRemoved=!0)}return e.parse=function(e){return e&&"object"==typeof e?new t.PullChange(e):e},e.prototype.path=void 0,e.prototype.wasRemoved=void 0,e.prototype.stat=void 0,e}(),t.PublicUrl=function(){function e(t,e){this.url=t.url,this.expiresAt=new Date(Date.parse(t.expires)),this.isDirect=e===!0?!0:e===!1?!1:"direct"in t?t.direct:864e5>=Date.now()-this.expiresAt,this.isPreview=!this.isDirect,this._json=null}return e.parse=function(e,r){return e&&"object"==typeof e?new t.PublicUrl(e,r):e},e.prototype.url=null,e.prototype.expiresAt=null,e.prototype.isDirect=null,e.prototype.isPreview=null,e.prototype.json=function(){return this._json||(this._json={url:this.url,expires:""+this.expiresAt,direct:this.isDirect})},e}(),t.CopyReference=function(){function e(t){"object"==typeof t?(this.tag=t.copy_ref,this.expiresAt=new Date(Date.parse(t.expires)),this._json=t):(this.tag=t,this.expiresAt=new Date(1e3*Math.ceil(Date.now()/1e3)),this._json=null)}return e.parse=function(e){return!e||"object"!=typeof e&&"string"!=typeof e?e:new t.CopyReference(e)},e.prototype.tag=null,e.prototype.expiresAt=null,e.prototype.json=function(){return this._json||(this._json={copy_ref:this.tag,expires:""+this.expiresAt})},e}(),t.Stat=function(){function e(t){var e,r,n,o;switch(this._json=t,this.path=t.path,"/"!==this.path.substring(0,1)&&(this.path="/"+this.path),e=this.path.length-1,e>=0&&"/"===this.path.substring(e)&&(this.path=this.path.substring(0,e)),r=this.path.lastIndexOf("/"),this.name=this.path.substring(r+1),this.isFolder=t.is_dir||!1,this.isFile=!this.isFolder,this.isRemoved=t.is_deleted||!1,this.typeIcon=t.icon,this.modifiedAt=(null!=(n=t.modified)?n.length:void 0)?new Date(Date.parse(t.modified)):null,this.clientModifiedAt=(null!=(o=t.client_mtime)?o.length:void 0)?new Date(Date.parse(t.client_mtime)):null,t.root){case"dropbox":this.inAppFolder=!1;break;case"app_folder":this.inAppFolder=!0;break;default:this.inAppFolder=null}this.size=t.bytes||0,this.humanSize=t.size||"",this.hasThumbnail=t.thumb_exists||!1,this.isFolder?(this.versionTag=t.hash,this.mimeType=t.mime_type||"inode/directory"):(this.versionTag=t.rev,this.mimeType=t.mime_type||"application/octet-stream")}return e.parse=function(e){return e&&"object"==typeof e?new t.Stat(e):e},e.prototype.path=null,e.prototype.name=null,e.prototype.inAppFolder=null,e.prototype.isFolder=null,e.prototype.isFile=null,e.prototype.isRemoved=null,e.prototype.typeIcon=null,e.prototype.versionTag=null,e.prototype.mimeType=null,e.prototype.size=null,e.prototype.humanSize=null,e.prototype.hasThumbnail=null,e.prototype.modifiedAt=null,e.prototype.clientModifiedAt=null,e.prototype.json=function(){return this._json +},e}(),t.UploadCursor=function(){function e(t){this.replace(t)}return e.parse=function(e){return!e||"object"!=typeof e&&"string"!=typeof e?e:new t.UploadCursor(e)},e.prototype.tag=null,e.prototype.offset=null,e.prototype.expiresAt=null,e.prototype.json=function(){return this._json||(this._json={upload_id:this.tag,offset:this.offset,expires:""+this.expiresAt})},e.prototype.replace=function(t){return"object"==typeof t?(this.tag=t.upload_id||null,this.offset=t.offset||0,this.expiresAt=new Date(Date.parse(t.expires)||Date.now()),this._json=t):(this.tag=t||null,this.offset=0,this.expiresAt=new Date(1e3*Math.floor(Date.now()/1e3)),this._json=null),this},e}(),t.UserInfo=function(){function e(t){var e;this._json=t,this.name=t.display_name,this.email=t.email,this.countryCode=t.country||null,this.uid=""+t.uid,t.public_app_url?(this.publicAppUrl=t.public_app_url,e=this.publicAppUrl.length-1,e>=0&&"/"===this.publicAppUrl.substring(e)&&(this.publicAppUrl=this.publicAppUrl.substring(0,e))):this.publicAppUrl=null,this.referralUrl=t.referral_link,this.quota=t.quota_info.quota,this.privateBytes=t.quota_info.normal||0,this.sharedBytes=t.quota_info.shared||0,this.usedQuota=this.privateBytes+this.sharedBytes}return e.parse=function(e){return e&&"object"==typeof e?new t.UserInfo(e):e},e.prototype.name=null,e.prototype.email=null,e.prototype.countryCode=null,e.prototype.uid=null,e.prototype.referralUrl=null,e.prototype.publicAppUrl=null,e.prototype.quota=null,e.prototype.usedQuota=null,e.prototype.privateBytes=null,e.prototype.sharedBytes=null,e.prototype.json=function(){return this._json},e}(),"undefined"!=typeof window&&null!==window?(!window.XDomainRequest||"withCredentials"in new XMLHttpRequest?(s=window.XMLHttpRequest,i=!1,n=-1===window.navigator.userAgent.indexOf("Firefox")):(s=window.XDomainRequest,i=!0,n=!1),o=!0):(s=require("xmlhttprequest").XMLHttpRequest,i=!1,n=!1,o=!1),"undefined"==typeof Uint8Array?r=null:Object.getPrototypeOf?r=Object.getPrototypeOf(Object.getPrototypeOf(new Uint8Array(0))).constructor:Object.__proto__&&(r=new Uint8Array(0).__proto__.__proto__.constructor),t.Xhr=function(){function e(t,e){this.method=t,this.isGet="GET"===this.method,this.url=e,this.headers={},this.params=null,this.body=null,this.preflight=!(this.isGet||"POST"===this.method),this.signed=!1,this.responseType=null,this.callback=null,this.xhr=null,this.onError=null}return e.Request=s,e.ieXdr=i,e.canSendForms=n,e.doesPreflight=o,e.ArrayBufferView=r,e.prototype.xhr=null,e.prototype.onError=null,e.prototype.setParams=function(t){if(this.signed)throw Error("setParams called after addOauthParams or addOauthHeader");if(this.params)throw Error("setParams cannot be called twice");return this.params=t,this},e.prototype.setCallback=function(t){return this.callback=t,this},e.prototype.signWithOauth=function(e){return t.Xhr.ieXdr||t.Xhr.doesPreflight&&!this.preflight?this.addOauthParams(e):this.addOauthHeader(e)},e.prototype.addOauthParams=function(t){if(this.signed)throw Error("Request already has an OAuth signature");return this.params||(this.params={}),t.addAuthParams(this.method,this.url,this.params),this.signed=!0,this},e.prototype.addOauthHeader=function(t){if(this.signed)throw Error("Request already has an OAuth signature");return this.params||(this.params={}),this.signed=!0,this.setHeader("Authorization",t.authHeader(this.method,this.url,this.params))},e.prototype.setBody=function(t){if(this.isGet)throw Error("setBody cannot be called on GET requests");if(null!==this.body)throw Error("Request already has a body");return this.preflight||"undefined"!=typeof FormData&&t instanceof FormData||(this.preflight=!0),this.body=t,this},e.prototype.setResponseType=function(t){return this.responseType=t,this},e.prototype.setHeader=function(t,e){var r;if(this.headers[t])throw r=this.headers[t],Error("HTTP header "+t+" already set to "+r);if("Content-Type"===t)throw Error("Content-Type is automatically computed based on setBody");return this.preflight=!0,this.headers[t]=e,this},e.prototype.setFileField=function(e,r,n,o){var i,s;if(null!==this.body)throw Error("Request already has a body");if(this.isGet)throw Error("setFileField cannot be called on GET requests");return"object"==typeof n&&"undefined"!=typeof Blob?("undefined"!=typeof ArrayBuffer&&null!==ArrayBuffer&&n instanceof ArrayBuffer&&(n=new Uint8Array(n)),t.Xhr.ArrayBufferView&&n instanceof t.Xhr.ArrayBufferView&&(o||(o="application/octet-stream"),n=new Blob([n],{type:o})),"undefined"!=typeof File&&n instanceof File&&(n=new Blob([n],{type:n.type})),s=n instanceof Blob):s=!1,s?(this.body=new FormData,this.body.append(e,n,r)):(o||(o="application/octet-stream"),i=this.multipartBoundary(),this.headers["Content-Type"]="multipart/form-data; boundary="+i,this.body=["--",i,"\r\n",'Content-Disposition: form-data; name="',e,'"; filename="',r,'"\r\n',"Content-Type: ",o,"\r\n","Content-Transfer-Encoding: binary\r\n\r\n",n,"\r\n","--",i,"--","\r\n"].join(""))},e.prototype.multipartBoundary=function(){return[Date.now().toString(36),Math.random().toString(36)].join("----")},e.prototype.paramsToUrl=function(){var e;return this.params&&(e=t.Xhr.urlEncode(this.params),0!==e.length&&(this.url=[this.url,"?",e].join("")),this.params=null),this},e.prototype.paramsToBody=function(){if(this.params){if(null!==this.body)throw Error("Request already has a body");if(this.isGet)throw Error("paramsToBody cannot be called on GET requests");this.headers["Content-Type"]="application/x-www-form-urlencoded",this.body=t.Xhr.urlEncode(this.params),this.params=null}return this},e.prototype.prepare=function(){var e,r,n,o,i=this;if(r=t.Xhr.ieXdr,this.isGet||null!==this.body||r?(this.paramsToUrl(),null!==this.body&&"string"==typeof this.body&&(this.headers["Content-Type"]="text/plain; charset=utf8")):this.paramsToBody(),this.xhr=new t.Xhr.Request,r?(this.xhr.onload=function(){return i.onXdrLoad()},this.xhr.onerror=function(){return i.onXdrError()},this.xhr.ontimeout=function(){return i.onXdrError()},this.xhr.onprogress=function(){}):this.xhr.onreadystatechange=function(){return i.onReadyStateChange()},this.xhr.open(this.method,this.url,!0),!r){o=this.headers;for(e in o)E.call(o,e)&&(n=o[e],this.xhr.setRequestHeader(e,n))}return this.responseType&&("b"===this.responseType?this.xhr.overrideMimeType&&this.xhr.overrideMimeType("text/plain; charset=x-user-defined"):this.xhr.responseType=this.responseType),this},e.prototype.send=function(e){var r;if(this.callback=e||this.callback,null!==this.body){r=this.body,t.Xhr.ArrayBufferView&&r instanceof ArrayBuffer&&(r=new Uint8Array(r));try{this.xhr.send(r)}catch(n){if(!("undefined"!=typeof Blob&&t.Xhr.ArrayBufferView&&r instanceof t.Xhr.ArrayBufferView))throw n;r=new Blob([r],{type:"application/octet-stream"}),this.xhr.send(r)}}else this.xhr.send();return this},e.urlEncode=function(t){var e,r,n;e=[];for(r in t)n=t[r],e.push(this.urlEncodeValue(r)+"="+this.urlEncodeValue(n));return e.sort().join("&")},e.urlEncodeValue=function(t){return encodeURIComponent(""+t).replace(/\!/g,"%21").replace(/'/g,"%27").replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/\*/g,"%2A")},e.urlDecode=function(t){var e,r,n,o,i,s;for(r={},s=t.split("&"),o=0,i=s.length;i>o;o++)n=s[o],e=n.split("="),r[decodeURIComponent(e[0])]=decodeURIComponent(e[1]);return r},e.prototype.onReadyStateChange=function(){var e,r,n,o,i,s,a,h,u;if(4!==this.xhr.readyState)return!0;if(200>this.xhr.status||this.xhr.status>=300)return e=new t.ApiError(this.xhr,this.method,this.url),this.onError&&this.onError.dispatch(e),this.callback(e),!0;if(s=this.xhr.getResponseHeader("x-dropbox-metadata"),null!=s?s.length:void 0)try{i=JSON.parse(s)}catch(l){i=void 0}else i=void 0;if(this.responseType){if("b"===this.responseType){for(n=null!=this.xhr.responseText?this.xhr.responseText:this.xhr.response,r=[],o=h=0,u=n.length;u>=0?u>h:h>u;o=u>=0?++h:--h)r.push(String.fromCharCode(255&n.charCodeAt(o)));a=r.join(""),this.callback(null,a,i)}else this.callback(null,this.xhr.response,i);return!0}switch(a=null!=this.xhr.responseText?this.xhr.responseText:this.xhr.response,this.xhr.getResponseHeader("Content-Type")){case"application/x-www-form-urlencoded":this.callback(null,t.Xhr.urlDecode(a),i);break;case"application/json":case"text/javascript":this.callback(null,JSON.parse(a),i);break;default:this.callback(null,a,i)}return!0},e.prototype.onXdrLoad=function(){var e;switch(e=this.xhr.responseText,this.xhr.contentType){case"application/x-www-form-urlencoded":this.callback(null,t.Xhr.urlDecode(e),void 0);break;case"application/json":case"text/javascript":this.callback(null,JSON.parse(e),void 0);break;default:this.callback(null,e,void 0)}return!0},e.prototype.onXdrError=function(){var e;return e=new t.ApiError(this.xhr,this.method,this.url),this.onError&&this.onError.dispatch(e),this.callback(e),!0},e}(),null!=("undefined"!=typeof module&&null!==module?module.exports:void 0))module.exports=t;else{if("undefined"==typeof window||null===window)throw Error("This library only supports node.js and modern browsers.");window.Dropbox=t}t.atob=u,t.btoa=f,t.hmac=c,t.sha1=d,t.encodeKey=m}).call(this); \ No newline at end of file diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/lib/jquery.js b/lib/client/storage/dropbox/samples/checkbox.js/public/lib/jquery.js new file mode 100644 index 00000000..83589daa --- /dev/null +++ b/lib/client/storage/dropbox/samples/checkbox.js/public/lib/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
    a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
    t
    ",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
    ",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
    ",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

    ",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
    ","
    "]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
    ").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); \ No newline at end of file diff --git a/lib/client/storage/dropbox/samples/checkbox.js/public/lib/less.js b/lib/client/storage/dropbox/samples/checkbox.js/public/lib/less.js new file mode 100644 index 00000000..9b0fa6ba --- /dev/null +++ b/lib/client/storage/dropbox/samples/checkbox.js/public/lib/less.js @@ -0,0 +1,9 @@ +// +// LESS - Leaner CSS v1.3.3 +// http://lesscss.org +// +// Copyright (c) 2009-2013, Alexis Sellier +// Licensed under the Apache 2.0 License. +// +(function(e,t){function n(t){return e.less[t.split("/")[1]]}function f(){r.env==="development"?(r.optimization=0,r.watchTimer=setInterval(function(){r.watchMode&&g(function(e,t,n,r,i){t&&S(t.toCSS(),r,i.lastModified)})},r.poll)):r.optimization=3}function m(){var e=document.getElementsByTagName("style");for(var t=0;t0&&(s.splice(o-1,2),o-=2)}return i.hostPart=r[1],i.directories=s,i.path=r[1]+s.join("/"),i.fileUrl=i.path+(r[4]||""),i.url=i.fileUrl+(r[5]||""),i}function w(t,n,i,s){var o=t.contents||{},u=t.files||{},a=b(t.href,e.location.href),f=a.url,c=l&&l.getItem(f),h=l&&l.getItem(f+":timestamp"),p={css:c,timestamp:h},d;r.relativeUrls?r.rootpath?t.entryPath?d=b(r.rootpath+y(a.path,t.entryPath)).path:d=r.rootpath:d=a.path:r.rootpath?d=r.rootpath:t.entryPath?d=t.entryPath:d=a.path,x(f,t.type,function(e,l){v+=e.replace(/@import .+?;/ig,"");if(!i&&p&&l&&(new Date(l)).valueOf()===(new Date(p.timestamp)).valueOf())S(p.css,t),n(null,null,e,t,{local:!0,remaining:s},f);else try{o[f]=e,(new r.Parser({optimization:r.optimization,paths:[a.path],entryPath:t.entryPath||a.path,mime:t.type,filename:f,rootpath:d,relativeUrls:t.relativeUrls,contents:o,files:u,dumpLineNumbers:r.dumpLineNumbers})).parse(e,function(r,i){if(r)return k(r,f);try{n(r,i,e,t,{local:!1,lastModified:l,remaining:s},f),N(document.getElementById("less-error-message:"+E(f)))}catch(r){k(r,f)}})}catch(c){k(c,f)}},function(e,t){throw new Error("Couldn't load "+t+" ("+e+")")})}function E(e){return e.replace(/^[a-z]+:\/\/?[^\/]+/,"").replace(/^\//,"").replace(/\.[a-zA-Z]+$/,"").replace(/[^\.\w-]+/g,"-").replace(/\./g,":")}function S(e,t,n){var r,i=t.href||"",s="less:"+(t.title||E(i));if((r=document.getElementById(s))===null){r=document.createElement("style"),r.type="text/css",t.media&&(r.media=t.media),r.id=s;var o=t&&t.nextSibling||null;(o||document.getElementsByTagName("head")[0]).parentNode.insertBefore(r,o)}if(r.styleSheet)try{r.styleSheet.cssText=e}catch(u){throw new Error("Couldn't reassign styleSheet.cssText.")}else(function(e){r.childNodes.length>0?r.firstChild.nodeValue!==e.nodeValue&&r.replaceChild(e,r.firstChild):r.appendChild(e)})(document.createTextNode(e));if(n&&l){C("saving "+i+" to cache.");try{l.setItem(i,e),l.setItem(i+":timestamp",n)}catch(u){C("failed to save")}}}function x(e,t,n,i){function a(t,n,r){t.status>=200&&t.status<300?n(t.responseText,t.getResponseHeader("Last-Modified")):typeof r=="function"&&r(t.status,e)}var s=T(),u=o?r.fileAsync:r.async;typeof s.overrideMimeType=="function"&&s.overrideMimeType("text/css"),s.open("GET",e,u),s.setRequestHeader("Accept",t||"text/x-less, text/css; q=0.9, */*; q=0.5"),s.send(null),o&&!r.fileAsync?s.status===0||s.status>=200&&s.status<300?n(s.responseText):i(s.status,e):u?s.onreadystatechange=function(){s.readyState==4&&a(s,n,i)}:a(s,n,i)}function T(){if(e.XMLHttpRequest)return new XMLHttpRequest;try{return new ActiveXObject("MSXML2.XMLHTTP.3.0")}catch(t){return C("browser doesn't support AJAX."),null}}function N(e){return e&&e.parentNode.removeChild(e)}function C(e){r.env=="development"&&typeof console!="undefined"&&console.log("less: "+e)}function k(e,t){var n="less-error-message:"+E(t),i='
  • {content}
  • ',s=document.createElement("div"),o,u,a=[],f=e.filename||t,l=f.match(/([^\/]+(\?.*)?)$/)[1];s.id=n,s.className="less-error-message",u="

    "+(e.message||"There is an error in your .less file")+"

    "+'

    in '+l+" ";var c=function(e,t,n){e.extract[t]&&a.push(i.replace(/\{line\}/,parseInt(e.line)+(t-1)).replace(/\{class\}/,n).replace(/\{content\}/,e.extract[t]))};e.stack?u+="
    "+e.stack.split("\n").slice(1).join("
    "):e.extract&&(c(e,0,""),c(e,1,"line"),c(e,2,""),u+="on line "+e.line+", column "+(e.column+1)+":

    "+"
      "+a.join("")+"
    "),s.innerHTML=u,S([".less-error-message ul, .less-error-message li {","list-style-type: none;","margin-right: 15px;","padding: 4px 0;","margin: 0;","}",".less-error-message label {","font-size: 12px;","margin-right: 15px;","padding: 4px 0;","color: #cc7777;","}",".less-error-message pre {","color: #dd6666;","padding: 4px 0;","margin: 0;","display: inline-block;","}",".less-error-message pre.line {","color: #ff0000;","}",".less-error-message h3 {","font-size: 20px;","font-weight: bold;","padding: 15px 0 5px 0;","margin: 0;","}",".less-error-message a {","color: #10a","}",".less-error-message .error {","color: red;","font-weight: bold;","padding-bottom: 2px;","border-bottom: 1px dashed red;","}"].join("\n"),{title:"error-message"}),s.style.cssText=["font-family: Arial, sans-serif","border: 1px solid #e00","background-color: #eee","border-radius: 5px","-webkit-border-radius: 5px","-moz-border-radius: 5px","color: #e00","padding: 15px","margin-bottom: 15px"].join(";"),r.env=="development"&&(o=setInterval(function(){document.body&&(document.getElementById(n)?document.body.replaceChild(s,document.getElementById(n)):document.body.insertBefore(s,document.body.firstChild),clearInterval(o))},10))}Array.isArray||(Array.isArray=function(e){return Object.prototype.toString.call(e)==="[object Array]"||e instanceof Array}),Array.prototype.forEach||(Array.prototype.forEach=function(e,t){var n=this.length>>>0;for(var r=0;r>>0,n=new Array(t),r=arguments[1];for(var i=0;i>>0,n=0;if(t===0&&arguments.length===1)throw new TypeError;if(arguments.length>=2)var r=arguments[1];else do{if(n in this){r=this[n++];break}if(++n>=t)throw new TypeError}while(!0);for(;n=t)return-1;n<0&&(n+=t);for(;nh&&(c[u]=c[u].slice(o-h),h=o)}function w(e){var t=e.charCodeAt(0);return t===32||t===10||t===9}function E(e){var t,n,r,i,a;if(e instanceof Function)return e.call(p.parsers);if(typeof e=="string")t=s.charAt(o)===e?e:null,r=1,b();else{b();if(!(t=e.exec(c[u])))return null;r=t[0].length}if(t)return S(r),typeof t=="string"?t:t.length===1?t[0]:t}function S(e){var t=o,n=u,r=o+c[u].length,i=o+=e;while(o=0&&t.charAt(n)!=="\n";n--)r++;return{line:typeof e=="number"?(t.slice(0,e).match(/\n/g)||"").length:null,column:r}}function L(e){return r.mode==="browser"||r.mode==="rhino"?e.filename:n("path").resolve(e.filename)}function A(e,t,n){return{lineNumber:k(e,t).line+1,fileName:L(n)}}function O(e,t){var n=C(e,t),r=k(e.index,n),i=r.line,s=r.column,o=n.split("\n");this.type=e.type||"Syntax",this.message=e.message,this.filename=e.filename||t.filename,this.index=e.index,this.line=typeof i=="number"?i+1:null,this.callLine=e.call&&k(e.call,n).line+1,this.callExtract=o[k(e.call,n).line],this.stack=e.stack,this.column=s,this.extract=[o[i-1],o[i],o[i+1]]}var s,o,u,a,f,l,c,h,p,d=this,t=t||{};t.contents||(t.contents={}),t.rootpath=t.rootpath||"",t.files||(t.files={});var v=function(){},m=this.imports={paths:t.paths||[],queue:[],files:t.files,contents:t.contents,mime:t.mime,error:null,push:function(e,n){var i=this;this.queue.push(e),r.Parser.importer(e,this.paths,function(t,r,s){i.queue.splice(i.queue.indexOf(e),1);var o=s in i.files;i.files[s]=r,t&&!i.error&&(i.error=t),n(t,r,o),i.queue.length===0&&v(i.error)},t)}};return this.env=t=t||{},this.optimization="optimization"in this.env?this.env.optimization:1,this.env.filename=this.env.filename||null,p={imports:m,parse:function(e,a){var f,d,m,g,y,b,w=[],S,x=null;o=u=h=l=0,s=e.replace(/\r\n/g,"\n"),s=s.replace(/^\uFEFF/,""),c=function(e){var n=0,r=/(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,i=/\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,o=/"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,u=0,a,f=e[0],l;for(var c=0,h,p;c0?"missing closing `}`":"missing opening `{`",filename:t.filename},t)),e.map(function(e){return e.join("")})}([[]]);if(x)return a(x,t);try{f=new i.Ruleset([],E(this.parsers.primary)),f.root=!0}catch(T){return a(new O(T,t))}f.toCSS=function(e){var s,o,u;return function(s,o){var u=[],a;s=s||{},typeof o=="object"&&!Array.isArray(o)&&(o=Object.keys(o).map(function(e){var t=o[e];return t instanceof i.Value||(t instanceof i.Expression||(t=new i.Expression([t])),t=new i.Value([t])),new i.Rule("@"+e,t,!1,0)}),u=[new i.Ruleset(null,o)]);try{var f=e.call(this,{frames:u}).toCSS([],{compress:s.compress||!1,dumpLineNumbers:t.dumpLineNumbers})}catch(l){throw new O(l,t)}if(a=p.imports.error)throw a instanceof O?a:new O(a,t);return s.yuicompress&&r.mode==="node"?n("ycssmin").cssmin(f):s.compress?f.replace(/(\s)+/g,"$1"):f}}(f.eval);if(o=0&&s.charAt(N)!=="\n";N--)C++;x={type:"Parse",message:"Syntax Error on line "+y,index:o,filename:t.filename,line:y,column:C,extract:[b[y-2],b[y-1],b[y]]}}this.imports.queue.length>0?v=function(e){e=x||e,e?a(e):a(null,f)}:a(x,f)},parsers:{primary:function(){var e,t=[];while((e=E(this.mixin.definition)||E(this.rule)||E(this.ruleset)||E(this.mixin.call)||E(this.comment)||E(this.directive))||E(/^[\s\n]+/)||E(/^;+/))e&&t.push(e);return t},comment:function(){var e;if(s.charAt(o)!=="/")return;if(s.charAt(o+1)==="/")return new i.Comment(E(/^\/\/.*/),!0);if(e=E(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/))return new i.Comment(e)},entities:{quoted:function(){var e,t=o,n;s.charAt(t)==="~"&&(t++,n=!0);if(s.charAt(t)!=='"'&&s.charAt(t)!=="'")return;n&&E("~");if(e=E(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/))return new i.Quoted(e[0],e[1]||e[2],n)},keyword:function(){var e;if(e=E(/^[_A-Za-z-][_A-Za-z0-9-]*/))return i.colors.hasOwnProperty(e)?new i.Color(i.colors[e].slice(1)):new i.Keyword(e)},call:function(){var e,n,r,s,a=o;if(!(e=/^([\w-]+|%|progid:[\w\.]+)\(/.exec(c[u])))return;e=e[1],n=e.toLowerCase();if(n==="url")return null;o+=e.length;if(n==="alpha"){s=E(this.alpha);if(typeof s!="undefined")return s}E("("),r=E(this.entities.arguments);if(!E(")"))return;if(e)return new i.Call(e,r,a,t.filename)},arguments:function(){var e=[],t;while(t=E(this.entities.assignment)||E(this.expression)){e.push(t);if(!E(","))break}return e},literal:function(){return E(this.entities.ratio)||E(this.entities.dimension)||E(this.entities.color)||E(this.entities.quoted)||E(this.entities.unicodeDescriptor)},assignment:function(){var e,t;if((e=E(/^\w+(?=\s?=)/i))&&E("=")&&(t=E(this.entity)))return new i.Assignment(e,t)},url:function(){var e;if(s.charAt(o)!=="u"||!E(/^url\(/))return;return e=E(this.entities.quoted)||E(this.entities.variable)||E(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/)||"",x(")"),new i.URL(e.value!=null||e instanceof i.Variable?e:new i.Anonymous(e),t.rootpath)},variable:function(){var e,n=o;if(s.charAt(o)==="@"&&(e=E(/^@@?[\w-]+/)))return new i.Variable(e,n,t.filename)},variableCurly:function(){var e,n,r=o;if(s.charAt(o)==="@"&&(n=E(/^@\{([\w-]+)\}/)))return new i.Variable("@"+n[1],r,t.filename)},color:function(){var e;if(s.charAt(o)==="#"&&(e=E(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/)))return new i.Color(e[1])},dimension:function(){var e,t=s.charCodeAt(o);if(t>57||t<43||t===47||t==44)return;if(e=E(/^([+-]?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn|dpi|dpcm|dppx|rem|vw|vh|vmin|vm|ch)?/))return new i.Dimension(e[1],e[2])},ratio:function(){var e,t=s.charCodeAt(o);if(t>57||t<48)return;if(e=E(/^(\d+\/\d+)/))return new i.Ratio(e[1])},unicodeDescriptor:function(){var e;if(e=E(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/))return new i.UnicodeDescriptor(e[0])},javascript:function(){var e,t=o,n;s.charAt(t)==="~"&&(t++,n=!0);if(s.charAt(t)!=="`")return;n&&E("~");if(e=E(/^`([^`]*)`/))return new i.JavaScript(e[1],o,n)}},variable:function(){var e;if(s.charAt(o)==="@"&&(e=E(/^(@[\w-]+)\s*:/)))return e[1]},shorthand:function(){var e,t;if(!N(/^[@\w.%-]+\/[@\w.-]+/))return;g();if((e=E(this.entity))&&E("/")&&(t=E(this.entity)))return new i.Shorthand(e,t);y()},mixin:{call:function(){var e=[],n,r,u=[],a=[],f,l,c,h,p,d,v,m=o,b=s.charAt(o),w,S,C=!1;if(b!=="."&&b!=="#")return;g();while(n=E(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/))e.push(new i.Element(r,n,o)),r=E(">");if(E("(")){p=[];while(c=E(this.expression)){h=null,S=c;if(c.value.length==1){var k=c.value[0];k instanceof i.Variable&&E(":")&&(p.length>0&&(d&&T("Cannot mix ; and , as delimiter types"),v=!0),S=x(this.expression),h=w=k.name)}p.push(S),a.push({name:h,value:S});if(E(","))continue;if(E(";")||d)v&&T("Cannot mix ; and , as delimiter types"),d=!0,p.length>1&&(S=new i.Value(p)),u.push({name:w,value:S}),w=null,p=[],v=!1}x(")")}f=d?u:a,E(this.important)&&(C=!0);if(e.length>0&&(E(";")||N("}")))return new i.mixin.Call(e,f,m,t.filename,C);y()},definition:function(){var e,t=[],n,r,u,a,f,c=!1;if(s.charAt(o)!=="."&&s.charAt(o)!=="#"||N(/^[^{]*\}/))return;g();if(n=E(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)){e=n[1];do{E(this.comment);if(s.charAt(o)==="."&&E(/^\.{3}/)){c=!0,t.push({variadic:!0});break}if(!(u=E(this.entities.variable)||E(this.entities.literal)||E(this.entities.keyword)))break;if(u instanceof i.Variable)if(E(":"))a=x(this.expression,"expected expression"),t.push({name:u.name,value:a});else{if(E(/^\.{3}/)){t.push({name:u.name,variadic:!0}),c=!0;break}t.push({name:u.name})}else t.push({value:u})}while(E(",")||E(";"));E(")")||(l=o,y()),E(this.comment),E(/^when/)&&(f=x(this.conditions,"expected condition")),r=E(this.block);if(r)return new i.mixin.Definition(e,t,r,f,c);y()}}},entity:function(){return E(this.entities.literal)||E(this.entities.variable)||E(this.entities.url)||E(this.entities.call)||E(this.entities.keyword)||E(this.entities.javascript)||E(this.comment)},end:function(){return E(";")||N("}")},alpha:function(){var e;if(!E(/^\(opacity=/i))return;if(e=E(/^\d+/)||E(this.entities.variable))return x(")"),new i.Alpha(e)},element:function(){var e,t,n,r;n=E(this.combinator),e=E(/^(?:\d+\.\d+|\d+)%/)||E(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)||E("*")||E("&")||E(this.attribute)||E(/^\([^()@]+\)/)||E(/^[\.#](?=@)/)||E(this.entities.variableCurly),e||E("(")&&(r=E(this.entities.variableCurly)||E(this.entities.variable)||E(this.selector))&&E(")")&&(e=new i.Paren(r));if(e)return new i.Element(n,e,o)},combinator:function(){var e,t=s.charAt(o);if(t===">"||t==="+"||t==="~"||t==="|"){o++;while(s.charAt(o).match(/\s/))o++;return new i.Combinator(t)}return s.charAt(o-1).match(/\s/)?new i.Combinator(" "):new i.Combinator(null)},selector:function(){var e,t,n=[],r,u;if(E("("))return e=E(this.entity),E(")")?new i.Selector([new i.Element("",e,o)]):null;while(t=E(this.element)){r=s.charAt(o),n.push(t);if(r==="{"||r==="}"||r===";"||r===","||r===")")break}if(n.length>0)return new i.Selector(n)},attribute:function(){var e="",t,n,r;if(!E("["))return;if(t=E(/^(?:[_A-Za-z0-9-]|\\.)+/)||E(this.entities.quoted))(r=E(/^[|~*$^]?=/))&&(n=E(this.entities.quoted)||E(/^[\w-]+/))?e=[t,r,n.toCSS?n.toCSS():n].join(""):e=t;if(!E("]"))return;if(e)return"["+e+"]"},block:function(){var e;if(E("{")&&(e=E(this.primary))&&E("}"))return e},ruleset:function(){var e=[],n,r,u,a;g(),t.dumpLineNumbers&&(a=A(o,s,t));while(n=E(this.selector)){e.push(n),E(this.comment);if(!E(","))break;E(this.comment)}if(e.length>0&&(r=E(this.block))){var f=new i.Ruleset(e,r,t.strictImports);return t.dumpLineNumbers&&(f.debugInfo=a),f}l=o,y()},rule:function(){var e,t,n=s.charAt(o),r,a;g();if(n==="."||n==="#"||n==="&")return;if(e=E(this.variable)||E(this.property)){e.charAt(0)!="@"&&(a=/^([^@+\/'"*`(;{}-]*);/.exec(c[u]))?(o+=a[0].length-1,t=new i.Anonymous(a[1])):e==="font"?t=E(this.font):t=E(this.value),r=E(this.important);if(t&&E(this.end))return new i.Rule(e,t,r,f);l=o,y()}},"import":function(){var e,n,r=o;g();var s=E(/^@import(?:-(once))?\s+/);if(s&&(e=E(this.entities.quoted)||E(this.entities.url))){n=E(this.mediaFeatures);if(E(";"))return new i.Import(e,m,n,s[1]==="once",r,t.rootpath)}y()},mediaFeature:function(){var e,t,n=[];do if(e=E(this.entities.keyword))n.push(e);else if(E("(")){t=E(this.property),e=E(this.entity);if(!E(")"))return null;if(t&&e)n.push(new i.Paren(new i.Rule(t,e,null,o,!0)));else{if(!e)return null;n.push(new i.Paren(e))}}while(e);if(n.length>0)return new i.Expression(n)},mediaFeatures:function(){var e,t=[];do if(e=E(this.mediaFeature)){t.push(e);if(!E(","))break}else if(e=E(this.entities.variable)){t.push(e);if(!E(","))break}while(e);return t.length>0?t:null},media:function(){var e,n,r,u;t.dumpLineNumbers&&(u=A(o,s,t));if(E(/^@media/)){e=E(this.mediaFeatures);if(n=E(this.block))return r=new i.Media(n,e),t.dumpLineNumbers&&(r.debugInfo=u),r}},directive:function(){var e,n,r,u,a,f,l,c,h,p;if(s.charAt(o)!=="@")return;if(n=E(this["import"])||E(this.media))return n;g(),e=E(/^@[a-z-]+/);if(!e)return;l=e,e.charAt(1)=="-"&&e.indexOf("-",2)>0&&(l="@"+e.slice(e.indexOf("-",2)+1));switch(l){case"@font-face":c=!0;break;case"@viewport":case"@top-left":case"@top-left-corner":case"@top-center":case"@top-right":case"@top-right-corner":case"@bottom-left":case"@bottom-left-corner":case"@bottom-center":case"@bottom-right":case"@bottom-right-corner":case"@left-top":case"@left-middle":case"@left-bottom":case"@right-top":case"@right-middle":case"@right-bottom":c=!0;break;case"@page":case"@document":case"@supports":case"@keyframes":c=!0,h=!0;break;case"@namespace":p=!0}h&&(e+=" "+(E(/^[^{]+/)||"").trim());if(c){if(r=E(this.block))return new i.Directive(e,r)}else if((n=p?E(this.expression):E(this.entity))&&E(";")){var d=new i.Directive(e,n);return t.dumpLineNumbers&&(d.debugInfo=A(o,s,t)),d}y()},font:function(){var e=[],t=[],n,r,s,o;while(o=E(this.shorthand)||E(this.entity))t.push(o);e.push(new i.Expression(t));if(E(","))while(o=E(this.expression)){e.push(o);if(!E(","))break}return new i.Value(e)},value:function(){var e,t=[],n;while(e=E(this.expression)){t.push(e);if(!E(","))break}if(t.length>0)return new i.Value(t)},important:function(){if(s.charAt(o)==="!")return E(/^! *important/)},sub:function(){var e;if(E("(")&&(e=E(this.expression))&&E(")"))return e},multiplication:function(){var e,t,n,r;if(e=E(this.operand)){while(!N(/^\/[*\/]/)&&(n=E("/")||E("*"))&&(t=E(this.operand)))r=new i.Operation(n,[r||e,t]);return r||e}},addition:function(){var e,t,n,r;if(e=E(this.multiplication)){while((n=E(/^[-+]\s+/)||!w(s.charAt(o-1))&&(E("+")||E("-")))&&(t=E(this.multiplication)))r=new i.Operation(n,[r||e,t]);return r||e}},conditions:function(){var e,t,n=o,r;if(e=E(this.condition)){while(E(",")&&(t=E(this.condition)))r=new i.Condition("or",r||e,t,n);return r||e}},condition:function(){var e,t,n,r,s=o,u=!1;E(/^not/)&&(u=!0),x("(");if(e=E(this.addition)||E(this.entities.keyword)||E(this.entities.quoted))return(r=E(/^(?:>=|=<|[<=>])/))?(t=E(this.addition)||E(this.entities.keyword)||E(this.entities.quoted))?n=new i.Condition(r,e,t,s,u):T("expected expression"):n=new i.Condition("=",e,new i.Keyword("true"),s,u),x(")"),E(/^and/)?new i.Condition("and",n,E(this.condition)):n},operand:function(){var e,t=s.charAt(o+1);s.charAt(o)==="-"&&(t==="@"||t==="(")&&(e=E("-"));var n=E(this.sub)||E(this.entities.dimension)||E(this.entities.color)||E(this.entities.variable)||E(this.entities.call);return e?new i.Operation("*",[new i.Dimension(-1),n]):n},expression:function(){var e,t,n=[],r;while(e=E(this.addition)||E(this.entity))n.push(e);if(n.length>0)return new i.Expression(n)},property:function(){var e;if(e=E(/^(\*?-?[_a-z0-9-]+)\s*:/))return e[1]}}}};if(r.mode==="browser"||r.mode==="rhino")r.Parser.importer=function(e,t,n,r){!/^([a-z-]+:)?\//.test(e)&&t.length>0&&(e=t[0]+e),w({href:e,title:e,type:r.mime,contents:r.contents,files:r.files,rootpath:r.rootpath,entryPath:r.entryPath,relativeUrls:r.relativeUrls},function(e,i,s,o,u,a){e&&typeof r.errback=="function"?r.errback.call(null,a,t,n,r):n.call(null,e,i,a)},!0)};(function(e){function t(t){return e.functions.hsla(t.h,t.s,t.l,t.a)}function n(t,n){return t instanceof e.Dimension&&t.unit=="%"?parseFloat(t.value*n/100):r(t)}function r(t){if(t instanceof e.Dimension)return parseFloat(t.unit=="%"?t.value/100:t.value);if(typeof t=="number")return t;throw{error:"RuntimeError",message:"color functions take numbers as parameters"}}function i(e){return Math.min(1,Math.max(0,e))}e.functions={rgb:function(e,t,n){return this.rgba(e,t,n,1)},rgba:function(t,i,s,o){var u=[t,i,s].map(function(e){return n(e,256)});return o=r(o),new e.Color(u,o)},hsl:function(e,t,n){return this.hsla(e,t,n,1)},hsla:function(e,t,n,i){function u(e){return e=e<0?e+1:e>1?e-1:e,e*6<1?o+(s-o)*e*6:e*2<1?s:e*3<2?o+(s-o)*(2/3-e)*6:o}e=r(e)%360/360,t=r(t),n=r(n),i=r(i);var s=n<=.5?n*(t+1):n+t-n*t,o=n*2-s;return this.rgba(u(e+1/3)*255,u(e)*255,u(e-1/3)*255,i)},hsv:function(e,t,n){return this.hsva(e,t,n,1)},hsva:function(e,t,n,i){e=r(e)%360/360*360,t=r(t),n=r(n),i=r(i);var s,o;s=Math.floor(e/60%6),o=e/60-s;var u=[n,n*(1-t),n*(1-o*t),n*(1-(1-o)*t)],a=[[0,3,1],[2,0,1],[1,0,3],[1,2,0],[3,1,0],[0,1,2]];return this.rgba(u[a[s][0]]*255,u[a[s][1]]*255,u[a[s][2]]*255,i)},hue:function(t){return new e.Dimension(Math.round(t.toHSL().h))},saturation:function(t){return new e.Dimension(Math.round(t.toHSL().s*100),"%")},lightness:function(t){return new e.Dimension(Math.round(t.toHSL().l*100),"%")},red:function(t){return new e.Dimension(t.rgb[0])},green:function(t){return new e.Dimension(t.rgb[1])},blue:function(t){return new e.Dimension(t.rgb[2])},alpha:function(t){return new e.Dimension(t.toHSL().a)},luma:function(t){return new e.Dimension(Math.round((.2126*(t.rgb[0]/255)+.7152*(t.rgb[1]/255)+.0722*(t.rgb[2]/255))*t.alpha*100),"%")},saturate:function(e,n){var r=e.toHSL();return r.s+=n.value/100,r.s=i(r.s),t(r)},desaturate:function(e,n){var r=e.toHSL();return r.s-=n.value/100,r.s=i(r.s),t(r)},lighten:function(e,n){var r=e.toHSL();return r.l+=n.value/100,r.l=i(r.l),t(r)},darken:function(e,n){var r=e.toHSL();return r.l-=n.value/100,r.l=i(r.l),t(r)},fadein:function(e,n){var r=e.toHSL();return r.a+=n.value/100,r.a=i(r.a),t(r)},fadeout:function(e,n){var r=e.toHSL();return r.a-=n.value/100,r.a=i(r.a),t(r)},fade:function(e,n){var r=e.toHSL();return r.a=n.value/100,r.a=i(r.a),t(r)},spin:function(e,n){var r=e.toHSL(),i=(r.h+n.value)%360;return r.h=i<0?360+i:i,t(r)},mix:function(t,n,r){r||(r=new e.Dimension(50));var i=r.value/100,s=i*2-1,o=t.toHSL().a-n.toHSL().a,u=((s*o==-1?s:(s+o)/(1+s*o))+1)/2,a=1-u,f=[t.rgb[0]*u+n.rgb[0]*a,t.rgb[1]*u+n.rgb[1]*a,t.rgb[2]*u+n.rgb[2]*a],l=t.alpha*i+n.alpha*(1-i);return new e.Color(f,l)},greyscale:function(t){return this.desaturate(t,new e.Dimension(100))},contrast:function(e,t,n,r){return e.rgb?(typeof n=="undefined"&&(n=this.rgba(255,255,255,1)),typeof t=="undefined"&&(t=this.rgba(0,0,0,1)),typeof r=="undefined"?r=.43:r=r.value,(.2126*(e.rgb[0]/255)+.7152*(e.rgb[1]/255)+.0722*(e.rgb[2]/255))*e.alpha255?255:e<0?0:e).toString(16),e.length===1?"0"+e:e}).join("")},operate:function(t,n){var r=[];n instanceof e.Color||(n=n.toColor());for(var i=0;i<3;i++)r[i]=e.operate(t,this.rgb[i],n.rgb[i]);return new e.Color(r,this.alpha+n.alpha)},toHSL:function(){var e=this.rgb[0]/255,t=this.rgb[1]/255,n=this.rgb[2]/255,r=this.alpha,i=Math.max(e,t,n),s=Math.min(e,t,n),o,u,a=(i+s)/2,f=i-s;if(i===s)o=u=0;else{u=a>.5?f/(2-i-s):f/(i+s);switch(i){case e:o=(t-n)/f+(t255?255:e<0?0:e).toString(16),e.length===1?"0"+e:e}).join("")},compare:function(e){return e.rgb?e.rgb[0]===this.rgb[0]&&e.rgb[1]===this.rgb[1]&&e.rgb[2]===this.rgb[2]&&e.alpha===this.alpha?0:-1:-1}}}(n("../tree")),function(e){e.Comment=function(e,t){this.value=e,this.silent=!!t},e.Comment.prototype={toCSS:function(e){return e.compress?"":this.value},eval:function(){return this}}}(n("../tree")),function(e){e.Condition=function(e,t,n,r,i){this.op=e.trim(),this.lvalue=t,this.rvalue=n,this.index=r,this.negate=i},e.Condition.prototype.eval=function(e){var t=this.lvalue.eval(e),n=this.rvalue.eval(e),r=this.index,i,i=function(e){switch(e){case"and":return t&&n;case"or":return t||n;default:if(t.compare)i=t.compare(n);else{if(!n.compare)throw{type:"Type",message:"Unable to perform comparison",index:r};i=n.compare(t)}switch(i){case-1:return e==="<"||e==="=<";case 0:return e==="="||e===">="||e==="=<";case 1:return e===">"||e===">="}}}(this.op);return this.negate?!i:i}}(n("../tree")),function(e){e.Dimension=function(e,t){this.value=parseFloat(e),this.unit=t||null},e.Dimension.prototype={eval:function(){return this},toColor:function(){return new e.Color([this.value,this.value,this.value])},toCSS:function(){var e=this.value+this.unit;return e},operate:function(t,n){return new e.Dimension(e.operate(t,this.value,n.value),this.unit||n.unit)},compare:function(t){return t instanceof e.Dimension?t.value>this.value?-1:t.value":e.compress?">":" > ","|":e.compress?"|":" | "}[this.value]}}(n("../tree")),function(e){e.Expression=function(e){this.value=e},e.Expression.prototype={eval:function(t){return this.value.length>1?new e.Expression(this.value.map(function(e){return e.eval(t)})):this.value.length===1?this.value[0].eval(t):this},toCSS:function(e){return this.value.map(function(t){return t.toCSS?t.toCSS(e):""}).join(" ")}}}(n("../tree")),function(e){e.Import=function(t,n,r,i,s,o){var u=this;this.once=i,this.index=s,this._path=t,this.features=r&&new e.Value(r),this.rootpath=o,t instanceof e.Quoted?this.path=/(\.[a-z]*$)|([\?;].*)$/.test(t.value)?t.value:t.value+".less":this.path=t.value.value||t.value,this.css=/css([\?;].*)?$/.test(this.path),this.css||n.push(this.path,function(t,n,r){t&&(t.index=s),r&&u.once&&(u.skip=r),u.root=n||new e.Ruleset([],[])})},e.Import.prototype={toCSS:function(e){var t=this.features?" "+this.features.toCSS(e):"";return this.css?(typeof this._path.value=="string"&&!/^(?:[a-z-]+:|\/)/.test(this._path.value)&&(this._path.value=this.rootpath+this._path.value),"@import "+this._path.toCSS()+t+";\n"):""},eval:function(t){var n,r=this.features&&this.features.eval(t);return this.skip?[]:this.css?this:(n=new e.Ruleset([],this.root.rules.slice(0)),n.evalImports(t),this.features?new e.Media(n.rules,this.features.value):n.rules)}}}(n("../tree")),function(e){e.JavaScript=function(e,t,n){this.escaped=n,this.expression=e,this.index=t},e.JavaScript.prototype={eval:function(t){var n,r=this,i={},s=this.expression.replace(/@\{([\w-]+)\}/g,function(n,i){return e.jsify((new e.Variable("@"+i,r.index)).eval(t))});try{s=new Function("return ("+s+")")}catch(o){throw{message:"JavaScript evaluation error: `"+s+"`",index:this.index}}for(var u in t.frames[0].variables())i[u.slice(1)]={value:t.frames[0].variables()[u].value,toJS:function(){return this.value.eval(t).toCSS()}};try{n=s.call(i)}catch(o){throw{message:"JavaScript evaluation error: '"+o.name+": "+o.message+"'",index:this.index}}return typeof n=="string"?new e.Quoted('"'+n+'"',n,this.escaped,this.index):Array.isArray(n)?new e.Anonymous(n.join(", ")):new e.Anonymous(n)}}}(n("../tree")),function(e){e.Keyword=function(e){this.value=e},e.Keyword.prototype={eval:function(){return this},toCSS:function(){return this.value},compare:function(t){return t instanceof e.Keyword?t.value===this.value?0:1:-1}},e.True=new e.Keyword("true"),e.False=new e.Keyword("false")}(n("../tree")),function(e){e.Media=function(t,n){var r=this.emptySelectors();this.features=new e.Value(n),this.ruleset=new e.Ruleset(r,t),this.ruleset.allowImports=!0},e.Media.prototype={toCSS:function(e,t){var n=this.features.toCSS(t);return this.ruleset.root=e.length===0||e[0].multiMedia,"@media "+n+(t.compress?"{":" {\n ")+this.ruleset.toCSS(e,t).trim().replace(/\n/g,"\n ")+(t.compress?"}":"\n}\n")},eval:function(t){t.mediaBlocks||(t.mediaBlocks=[],t.mediaPath=[]);var n=new e.Media([],[]);return this.debugInfo&&(this.ruleset.debugInfo=this.debugInfo,n.debugInfo=this.debugInfo),n.features=this.features.eval(t),t.mediaPath.push(n),t.mediaBlocks.push(n),t.frames.unshift(this.ruleset),n.ruleset=this.ruleset.eval(t),t.frames.shift(),t.mediaPath.pop(),t.mediaPath.length===0?n.evalTop(t):n.evalNested(t)},variable:function(t){return e.Ruleset.prototype.variable.call(this.ruleset,t)},find:function(){return e.Ruleset.prototype.find.apply(this.ruleset,arguments)},rulesets:function(){return e.Ruleset.prototype.rulesets.apply(this.ruleset)},emptySelectors:function(){var t=new e.Element("","&",0);return[new e.Selector([t])]},evalTop:function(t){var n=this;if(t.mediaBlocks.length>1){var r=this.emptySelectors();n=new e.Ruleset(r,t.mediaBlocks),n.multiMedia=!0}return delete t.mediaBlocks,delete t.mediaPath,n},evalNested:function(t){var n,r,i=t.mediaPath.concat([this]);for(n=0;n0;n--)t.splice(n,0,new e.Anonymous("and"));return new e.Expression(t)})),new e.Ruleset([],[])},permute:function(e){if(e.length===0)return[];if(e.length===1)return e[0];var t=[],n=this.permute(e.slice(1));for(var r=0;r0){c=!0;for(a=0;athis.params.length)return!1;if(this.required>0&&n>this.params.length)return!1}r=Math.min(n,this.arity);for(var s=0;si.selectors[o].elements.length?Array.prototype.push.apply(r,i.find(new e.Selector(t.elements.slice(1)),n)):r.push(i);break}}),this._lookups[o]=r)},toCSS:function(t,n){var r=[],i=[],s=[],o=[],u=[],a,f,l;this.root||this.joinSelectors(u,t,this.selectors);for(var c=0;c0){f=e.debugInfo(n,this),a=u.map(function(e){return e.map(function(e){return e.toCSS(n)}).join("").trim()}).join(n.compress?",":",\n");for(var c=i.length-1;c>=0;c--)s.indexOf(i[c])===-1&&s.unshift(i[c]);i=s,r.push(f+a+(n.compress?"{":" {\n ")+i.join(n.compress?"":"\n ")+(n.compress?"}":"\n}\n"))}return r.push(o),r.join("")+(n.compress?"\n":"")},joinSelectors:function(e,t,n){for(var r=0;r0)for(i=0;i0&&this.mergeElementsOnToSelectors(g,a);for(s=0;s0&&(l[0].elements=l[0].elements.slice(0),l[0].elements.push(new e.Element(f.combinator,"",0))),y.push(l);else for(o=0;o0?(h=l.slice(0),m=h.pop(),d=new e.Selector(m.elements.slice(0)),v=!1):d=new e.Selector([]),c.length>1&&(p=p.concat(c.slice(1))),c.length>0&&(v=!1,d.elements.push(new e.Element(f.combinator,c[0].elements[0].value,0)),d.elements=d.elements.concat(c[0].elements.slice(1))),v||h.push(d),h=h.concat(p),y.push(h)}a=y,g=[]}}g.length>0&&this.mergeElementsOnToSelectors(g,a);for(i=0;i0?i[i.length-1]=new e.Selector(i[i.length-1].elements.concat(t)):i.push(new e.Selector(t))}}}(n("../tree")),function(e){e.Selector=function(e){this.elements=e},e.Selector.prototype.match=function(e){var t=this.elements,n=t.length,r,i,s,o;r=e.elements.slice(e.elements.length&&e.elements[0].value==="&"?1:0),i=r.length,s=Math.min(n,i);if(i===0||n1?"["+e.value.map(function(e){return e.toCSS(!1)}).join(", ")+"]":e.toCSS(!1)}}(n("./tree"));var o=/^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol);r.env=r.env||(location.hostname=="127.0.0.1"||location.hostname=="0.0.0.0"||location.hostname=="localhost"||location.port.length>0||o?"development":"production"),r.async=r.async||!1,r.fileAsync=r.fileAsync||!1,r.poll=r.poll||(o?1e3:1500);if(r.functions)for(var u in r.functions)r.tree.functions[u]=r.functions[u];var a=/!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);a&&(r.dumpLineNumbers=a[1]),r.watch=function(){return r.watchMode||(r.env="development",f()),this.watchMode=!0},r.unwatch=function(){return clearInterval(r.watchTimer),this.watchMode=!1},/!watch/.test(location.hash)&&r.watch();var l=null;if(r.env!="development")try{l=typeof e.localStorage=="undefined"?null:e.localStorage}catch(c){}var h=document.getElementsByTagName("link"),p=/^text\/(x-)?less$/;r.sheets=[];for(var d=0;d @status = xhr.status if xhr.responseType - text = xhr.response or xhr.responseText + try + text = xhr.response or xhr.responseText + catch e + try + text = xhr.responseText + catch e + text = null else - text = xhr.responseText + try + text = xhr.responseText + catch e + text = null + if text try @responseText = text.toString() diff --git a/lib/client/storage/dropbox/src/client.coffee b/lib/client/storage/dropbox/src/client.coffee index c465e4bc..abbd3810 100644 --- a/lib/client/storage/dropbox/src/client.coffee +++ b/lib/client/storage/dropbox/src/client.coffee @@ -29,6 +29,10 @@ class Dropbox.Client @fileServer = options.fileServer or @defaultFileServer() @downloadServer = options.downloadServer or @defaultDownloadServer() + @onXhr = new Dropbox.EventSource cancelable: true + @onError = new Dropbox.EventSource + @onAuthStateChange = new Dropbox.EventSource + @oauth = new Dropbox.Oauth options @driver = null @filter = null @@ -51,20 +55,24 @@ class Dropbox.Client @driver = driver @ - # Plugs in a filter for all XMLHttpRequests issued by this client. - # - # Whenever possible, filter implementations should only use the native - # XMHttpRequest object received as the first argument. The Dropbox.Xhr API - # implemented by the second argument is not yet stabilized. - # - # @param {function(XMLHttpRequest, Dropbox.Xhr): boolean} filter called every - # time the client is about to send a network request; the filter can - # inspect and modify the XMLHttpRequest; if the filter returns a falsey - # value, the XMLHttpRequest will not be sent - # @return {Drobpox.Client} this, for easy call chaining - xhrFilter: (filter) -> - @filter = filter - @ + # @property {Dropbox.EventSource} cancelable event fired every + # time when a network request to the Dropbox API server is about to be + # sent; if the event is canceled by returning a falsey value from a + # listener, the network request is silently discarded; whenever possible, + # listeners should restrict themselves to using the xhr property of the + # Dropbox.Xhr instance passed to them; everything else in the Dropbox.Xhr + # API is in flux + onXhr: null + + # @property {Dropbox.EventSource} non-cancelable event + # fired every time when a network request to the Dropbox API server results + # in an error + onError: null + + # @property {Dropbox.EventSource} non-cancelable event fired + # every time the authState property changes; this can be used to update UI + # state + onAuthStateChange: null # The authenticated user's Dropbx user ID. # @@ -94,11 +102,16 @@ class Dropbox.Client authenticate: (callback) -> oldAuthState = null + unless @driver or @authState is DropboxClient.DONE + throw new Error "Call authDriver to set an authentication driver" + # Advances the authentication FSM by one step. _fsmStep = => if oldAuthState isnt @authState + if oldAuthState isnt null + @onAuthStateChange.dispatch @ oldAuthState = @authState - if @driver.onAuthStateChange + if @driver and @driver.onAuthStateChange return @driver.onAuthStateChange(@, _fsmStep) switch @authState @@ -138,7 +151,9 @@ class Dropbox.Client when DropboxClient.DONE # We have an access token. callback null, @ - when Dropbox.SIGNED_OFF # The user signed off, restart the flow. + when DropboxClient.SIGNED_OFF # The user signed off, restart the flow. + # The authState change makes reset() not trigger onAuthStateChange. + @authState = DropboxClient.RESET @reset() _fsmStep() @@ -148,6 +163,10 @@ class Dropbox.Client _fsmStep() # Start up the state machine. @ + # @return {Boolean} true if this client is authenticated, false otherwise + isAuthenticated: -> + @authState is DropboxClient.DONE + # Revokes the user's Dropbox credentials. # # This should be called when the user explictly signs off from your @@ -164,8 +183,11 @@ class Dropbox.Client @dispatchXhr xhr, (error) => return callback(error) if error + # The authState change makes reset() not trigger onAuthStateChange. + @authState = DropboxClient.RESET @reset() @authState = DropboxClient.SIGNED_OFF + @onAuthStateChange.dispatch @ if @driver.onAuthStateChange @driver.onAuthStateChange @, -> callback error @@ -178,15 +200,29 @@ class Dropbox.Client # Retrieves information about the logged in user. # + # @param {?Object} options the advanced settings below; for the default + # settings, skip the argument or pass null + # @option options {Boolean} httpCache if true, the API request will be set to + # allow HTTP caching to work; by default, requests are set up to avoid + # CORS preflights; setting this option can make sense when making the same + # request repeatedly (polling?) # @param {function(?Dropbox.ApiError, ?Dropbox.UserInfo, ?Object)} callback # called with the result of the /account/info HTTP request; if the call # succeeds, the second parameter is a Dropbox.UserInfo instance, the # third parameter is the parsed JSON data behind the Dropbox.UserInfo # instance, and the first parameter is null # @return {XMLHttpRequest} the XHR object used for this API call - getUserInfo: (callback) -> + getUserInfo: (options, callback) -> + if (not callback) and (typeof options is 'function') + callback = options + options = null + + httpCache = false + if options and options.httpCache + httpCache = true + xhr = new Dropbox.Xhr 'GET', @urls.accountInfo - xhr.signWithOauth @oauth + xhr.signWithOauth @oauth, httpCache @dispatchXhr xhr, (error, userData) -> callback error, Dropbox.UserInfo.parse(userData), userData @@ -205,17 +241,17 @@ class Dropbox.Client # @option options {String} rev alias for "versionTag" that matches the HTTP # API # @option options {Boolean} arrayBuffer if true, the file's contents will be - # passed to the callback in an ArrayBuffer; this is a good method of - # reading non-UTF8 data, such as images; requires XHR Level 2 support, - # which is not available in IE <= 9 + # passed to the callback in an ArrayBuffer; this is the recommended method + # of reading non-UTF8 data such as images, as it is well supported across + # modern browsers; requires XHR Level 2 support, which is not available in + # IE <= 9 # @option options {Boolean} blob if true, the file's contents will be # passed to the callback in a Blob; this is a good method of reading # non-UTF8 data, such as images; requires XHR Level 2 support, which is not # available in IE <= 9 # @option options {Boolean} binary if true, the file will be retrieved as a # binary string; the default is an UTF-8 encoded string; this relies on - # browser hacks and should not be used if the environment supports the Blob - # API + # hacks and should not be used if the environment supports XHR Level 2 API # @option options {Number} length the number of bytes to be retrieved from # the file; if the start option is not present, the last "length" bytes # will be read (after issue #30 is closed); by default, the entire file is @@ -224,6 +260,10 @@ class Dropbox.Client # retrieved; if the length option is not present, the bytes between # "start" and the file's end will be read; by default, the entire # file is read + # @option options {Boolean} httpCache if true, the API request will be set to + # allow HTTP caching to work; by default, requests are set up to avoid + # CORS preflights; setting this option can make sense when making the same + # request repeatedly (polling?) # @param {function(?Dropbox.ApiError, ?String, ?Dropbox.Stat)} callback # called with the result of the /files (GET) HTTP request; the second # parameter is the contents of the file, the third parameter is a @@ -238,6 +278,7 @@ class Dropbox.Client params = {} responseType = null rangeHeader = null + httpCache = false if options if options.versionTag params.rev = options.versionTag @@ -262,8 +303,11 @@ class Dropbox.Client else if options.start? rangeHeader = "bytes=#{options.start}-" + httpCache = true if options.httpCache + xhr = new Dropbox.Xhr 'GET', "#{@urls.getFile}/#{@urlEncodePath(path)}" - xhr.setParams(params).signWithOauth(@oauth).setResponseType(responseType) + xhr.setParams(params).signWithOauth(@oauth, httpCache) + xhr.setResponseType(responseType) xhr.setHeader 'Range', rangeHeader if rangeHeader @dispatchXhr xhr, (error, data, metadata) -> callback error, data, Dropbox.Stat.parse(metadata) @@ -359,6 +403,78 @@ class Dropbox.Client @dispatchXhr xhr, (error, metadata) -> callback error, Dropbox.Stat.parse(metadata) + # Atomic step in a resumable file upload. + # + # @param {String, ArrayBuffer, ArrayBufferView, Blob, File} data the file + # contents fragment to be uploaded; if a File is passed, its name is + # ignored + # @param {?Dropbox.UploadCursor} cursor the cursor that tracks the state of + # the resumable file upload; the cursor information will not be updated + # when the API call completes + # @param {function(?Dropbox.ApiError, ?Dropbox.UploadCursor)} callback called + # with the result of the /chunked_upload HTTP request; the second paramter + # is a Dropbox.UploadCursor instance describing the progress of the upload + # operation, and the first parameter is null if things go well + # @return {XMLHttpRequest} the XHR object used for this API call + resumableUploadStep: (data, cursor, callback) -> + if cursor + params = { offset: cursor.offset } + params.upload_id = cursor.tag if cursor.tag + else + params = { offset: 0 } + + xhr = new Dropbox.Xhr 'POST', @urls.chunkedUpload + xhr.setBody(data).setParams(params).signWithOauth(@oauth) + @dispatchXhr xhr, (error, cursor) -> + if error and error.status is 400 and + error.response.upload_id and error.response.offset + callback null, Dropbox.UploadCursor.parse(error.response) + else + callback error, Dropbox.UploadCursor.parse(cursor) + + # Finishes a resumable file upload. + # + # @param {String} path the path of the file to be created, relative to the + # user's Dropbox or to the application's folder + # @param {?Object} options the advanced settings below; for the default + # settings, skip the argument or pass null + # @option options {String} lastVersionTag the identifier string for the + # version of the file's contents that was last read by this program, used + # for conflict resolution; for best results, use the versionTag attribute + # value from the Dropbox.Stat instance provided by readFile + # @option options {String} parentRev alias for "lastVersionTag" that matches + # the HTTP API + # @option options {Boolean} noOverwrite if set, the write will not overwrite + # a file with the same name that already exsits; instead the contents + # will be written to a similarly named file (e.g. "notes (1).txt" + # instead of "notes.txt") + # @param {function(?Dropbox.ApiError, ?Dropbox.Stat)} callback called with + # the result of the /files (POST) HTTP request; the second paramter is a + # Dropbox.Stat instance describing the newly created file, and the first + # parameter is null + # @return {XMLHttpRequest} the XHR object used for this API call + resumableUploadFinish: (path, cursor, options, callback) -> + if (not callback) and (typeof options is 'function') + callback = options + options = null + + params = { upload_id: cursor.tag } + + if options + if options.lastVersionTag + params.parent_rev = options.lastVersionTag + else if options.parentRev or options.parent_rev + params.parent_rev = options.parentRev or options.parent_rev + if options.noOverwrite + params.autorename = true + + # TODO: locale support would edit the params here + xhr = new Dropbox.Xhr 'POST', + "#{@urls.commitChunkedUpload}/#{@urlEncodePath(path)}" + xhr.setParams(params).signWithOauth(@oauth) + @dispatchXhr xhr, (error, metadata) -> + callback error, Dropbox.Stat.parse(metadata) + # Reads the metadata of a file or folder in a user's Dropbox. # # @param {String} path the path to the file or folder whose metadata will be @@ -384,6 +500,10 @@ class Dropbox.Client # folder's contents, the call will fail with a 304 (Contents not changed) # error code; a folder's version identifier can be obtained from the # versionTag attribute of a Dropbox.Stat instance describing it + # @option options {Boolean} httpCache if true, the API request will be set to + # allow HTTP caching to work; by default, requests are set up to avoid + # CORS preflights; setting this option can make sense when making the same + # request repeatedly (polling?) # @param {function(?Dropbox.ApiError, ?Dropbox.Stat, ?Array)} # callback called with the result of the /metadata HTTP request; if the # call succeeds, the second parameter is a Dropbox.Stat instance @@ -398,6 +518,7 @@ class Dropbox.Client options = null params = {} + httpCache = false if options if options.version? params.rev = options.version @@ -409,11 +530,13 @@ class Dropbox.Client params.file_limit = options.readDir.toString() if options.cacheHash params.hash = options.cacheHash + if options.httpCache + httpCache = true params.include_deleted ||= 'false' params.list ||= 'false' # TODO: locale support would edit the params here xhr = new Dropbox.Xhr 'GET', "#{@urls.metadata}/#{@urlEncodePath(path)}" - xhr.setParams(params).signWithOauth @oauth + xhr.setParams(params).signWithOauth @oauth, httpCache @dispatchXhr xhr, (error, metadata) -> stat = Dropbox.Stat.parse metadata if metadata?.contents @@ -443,6 +566,10 @@ class Dropbox.Client # instead of returning the contents; a folder's version identifier can be # obtained from the versionTag attribute of a Dropbox.Stat instance # describing it + # @option options {Boolean} httpCache if true, the API request will be set to + # allow HTTP caching to work; by default, requests are set up to avoid + # CORS preflights; setting this option can make sense when making the same + # request repeatedly (polling?) # @param {function(?Dropbox.ApiError, ?Array, ?Dropbox.Stat, # ?Array)} callback called with the result of the /metadata # HTTP request; if the call succeeds, the second parameter is an array @@ -462,6 +589,10 @@ class Dropbox.Client statOptions.readDir = options.limit if options.versionTag statOptions.versionTag = options.versionTag + if options.removed or options.deleted + statOptions.removed = options.removed or options.deleted + if options.httpCache + statOptions.httpCache = options.httpCache @stat path, statOptions, (error, stat, entry_stats) -> if entry_stats entries = (entry_stat.name for entry_stat in entry_stats) @@ -541,6 +672,10 @@ class Dropbox.Client # settings, skip the argument or pass null # @option options {Number} limit if specified, the call will return at most # this many versions + # @option options {Boolean} httpCache if true, the API request will be set to + # allow HTTP caching to work; by default, requests are set up to avoid + # CORS preflights; setting this option can make sense when making the same + # request repeatedly (polling?) # @param {function(?Dropbox.ApiError, ?Array)} callback called # with the result of the /revisions HTTP request; if the call succeeds, # the second parameter is an array with one Dropbox.Stat instance per @@ -552,11 +687,15 @@ class Dropbox.Client options = null params = {} - if options and options.limit? - params.rev_limit = options.limit + httpCache = false + if options + if options.limit? + params.rev_limit = options.limit + if options.httpCache + httpCache = true xhr = new Dropbox.Xhr 'GET', "#{@urls.revisions}/#{@urlEncodePath(path)}" - xhr.setParams(params).signWithOauth(@oauth) + xhr.setParams(params).signWithOauth @oauth, httpCache @dispatchXhr xhr, (error, versions) -> if versions stats = (Dropbox.Stat.parse(metadata) for metadata in versions) @@ -696,6 +835,10 @@ class Dropbox.Client # @option options {Boolean} deleted alias for "removed" that matches the HTTP # API; using this alias is not recommended, because it may cause confusion # with JavaScript's delete operation + # @option options {Boolean} httpCache if true, the API request will be set to + # allow HTTP caching to work; by default, requests are set up to avoid + # CORS preflights; setting this option can make sense when making the same + # request repeatedly (polling?) # @param {function(?Dropbox.ApiError, ?Array)} callback called # with the result of the /search HTTP request; if the call succeeds, the # second parameter is an array with one Dropbox.Stat instance per search @@ -707,14 +850,17 @@ class Dropbox.Client options = null params = { query: namePattern } + httpCache = false if options if options.limit? params.file_limit = options.limit if options.removed or options.deleted params.include_deleted = true + if options.httpCache + httpCache = true xhr = new Dropbox.Xhr 'GET', "#{@urls.search}/#{@urlEncodePath(path)}" - xhr.setParams(params).signWithOauth(@oauth) + xhr.setParams(params).signWithOauth @oauth, httpCache @dispatchXhr xhr, (error, results) -> if results stats = (Dropbox.Stat.parse(metadata) for metadata in results) @@ -753,10 +899,11 @@ class Dropbox.Client # to obtain all the changes that happened in the user's Dropbox (or # application directory) between the two calls. # - # @param {Dropbox.PulledChanges, String} cursorTag the result of a previous + # @param {Dropbox.PulledChanges, String} cursor the result of a previous # call to pullChanges, or a string containing a tag representing the # Dropbox state that is used as the baseline for the change list; this - # should be obtained from a previous call to pullChanges, or be set to null + # should either be the Dropbox.PulledChanges obtained from a previous call + # to pullChanges, the return value of Dropbox.PulledChanges#cursor, or null # / ommitted on the first call to pullChanges # @param {function(?Dropbox.ApiError, ?Dropbox.PulledChanges)} callback # called with the result of the /delta HTTP request; if the call @@ -896,7 +1043,10 @@ class Dropbox.Client reset: -> @uid = null @oauth.setToken null, '' + oldAuthState = @authState @authState = DropboxClient.RESET + if oldAuthState isnt @authState + @onAuthStateChange.dispatch @ @authError = null @_credentials = null @ @@ -906,6 +1056,7 @@ class Dropbox.Client # @param {?Object} the result of a prior call to credentials() # @return {Dropbox.Client} this, for easy call chaining setCredentials: (credentials) -> + oldAuthState = @authState @oauth.reset credentials @uid = credentials.uid or null if credentials.authState @@ -917,6 +1068,8 @@ class Dropbox.Client @authState = DropboxClient.RESET @authError = null @_credentials = null + if oldAuthState isnt @authState + @onAuthStateChange.dispatch @ @ # @return {String} a string that uniquely identifies the Dropbox application @@ -955,6 +1108,9 @@ class Dropbox.Client media: "#{@apiServer}/1/media/#{@fileRoot}" copyRef: "#{@apiServer}/1/copy_ref/#{@fileRoot}" thumbnails: "#{@fileServer}/1/thumbnails/#{@fileRoot}" + chunkedUpload: "#{@fileServer}/1/chunked_upload" + commitChunkedUpload: + "#{@fileServer}/1/commit_chunked_upload/#{@fileRoot}" # File operations. fileopsCopy: "#{@apiServer}/1/fileops/copy" @@ -962,22 +1118,27 @@ class Dropbox.Client fileopsDelete: "#{@apiServer}/1/fileops/delete" fileopsMove: "#{@apiServer}/1/fileops/move" - # authState value for a client that experienced an authentication error. + # @property {Number} the client's progress in the authentication process; + # Dropbox.Client#isAuthenticated should be called instead whenever + # possible; this attribute was intended to be used by OAuth drivers + authState: null + + # authState value for a client that experienced an authentication error @ERROR: 0 - # authState value for a properly initialized client with no user credentials. + # authState value for a properly initialized client with no user credentials @RESET: 1 - # authState value for a client with a request token that must be authorized. + # authState value for a client with a request token that must be authorized @REQUEST: 2 - # authState value for a client whose request token was authorized. + # authState value for a client whose request token was authorized @AUTHORIZED: 3 - # authState value for a client that has an access token. + # authState value for a client that has an access token @DONE: 4 - # authState value for a client that voluntarily invalidated its access token. + # authState value for a client that voluntarily invalidated its access token @SIGNED_OFF: 5 # Normalizes a Dropobx path and encodes it for inclusion in a request URL. @@ -1039,17 +1200,22 @@ class Dropbox.Client xhr = new Dropbox.Xhr('POST', @urls.accessToken).signWithOauth(@oauth) @dispatchXhr xhr, callback - # Prepares an XHR before it is sent to the server. + # Prepares and sends an XHR to the Dropbox API server. # # @private # This is a low-level method called by other client methods. + # + # @param {Dropbox.Xhr} xhr wrapper for the XHR to be sent + # @param {function(?Dropbox.ApiError, ?Object)} callback called with the + # outcome of the XHR + # @return {XMLHttpRequest} the native XHR object used to make the request dispatchXhr: (xhr, callback) -> xhr.setCallback callback + xhr.onError = @onError xhr.prepare() nativeXhr = xhr.xhr - if @filter - return nativeXhr unless @filter(nativeXhr, xhr) - xhr.send() + if @onXhr.dispatch xhr + xhr.send() nativeXhr # @private diff --git a/lib/client/storage/dropbox/src/drivers-browser.coffee b/lib/client/storage/dropbox/src/drivers-browser.coffee new file mode 100644 index 00000000..13306168 --- /dev/null +++ b/lib/client/storage/dropbox/src/drivers-browser.coffee @@ -0,0 +1,342 @@ +# 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} 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 + @scope = options?.scope or 'default' + @storageKey = null + + # 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 + @forgetCredentials() + return 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 e + # 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() + @ + + # 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 + @useQuery = options?.useQuery or false + @receiverUrl = @computeUrl options + @tokenRe = new RegExp "(#|\\?|&)oauth_token=([^&#]+)(&|#|$)" + + # 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: -> + @receiverUrl + + # Redirects to the authorize page. + doAuthorize: (authUrl) -> + window.location.assign authUrl + + # Pre-computes the return value of url. + computeUrl: -> + querySuffix = "_dropboxjs_scope=#{encodeURIComponent @scope}" + location = Dropbox.Drivers.BrowserBase.currentLocation() + 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. + else + fragment = "?#{querySuffix}" + + if fragment + location + '#' + fragment + else + location + + # Figures out if the user completed the OAuth flow based on the current URL. + # + # @return {?String} the OAuth token that the user just authorized, or null if + # the user accessed this directly, without having authorized a token + locationToken: -> + location = Dropbox.Drivers.BrowserBase.currentLocation() + + # Check for the scope. + scopePattern = "_dropboxjs_scope=#{encodeURIComponent @scope}&" + return null if location.indexOf?(scopePattern) is -1 + + # Extract the token. + match = @tokenRe.exec location + if match then decodeURIComponent(match[2]) else null + +# 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} noFragment if true, the receiverUrl will be used + # as given; by default, a hash "#" is appended to URLs that don't have + # one, so the OAuth token is received as a 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 + @receiverUrl = @computeUrl options + @tokenRe = new RegExp "(#|\\?|&)oauth_token=([^&#]+)(&|#|$)" + + # 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: -> + @receiverUrl + + # Pre-computes the return value of url. + computeUrl: (options) -> + if options + if options.receiverUrl + if options.noFragment or options.receiverUrl.indexOf('#') isnt -1 + return options.receiverUrl + else + return options.receiverUrl + '#' + else if options.receiverFile + fragments = Dropbox.Drivers.BrowserBase.currentLocation().split '/' + fragments[fragments.length - 1] = options.receiverFile + if options.noFragment + return fragments.join('/') + else + 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) => + match = @tokenRe.exec event.data.toString() + if match and decodeURIComponent(match[2]) is token + window.removeEventListener 'message', listener + callback() + window.addEventListener 'message', listener, false + + # 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 e + # IE 9 doesn't support opener.postMessage for popup windows. + window.close() diff --git a/lib/client/storage/dropbox/src/drivers-chrome.coffee b/lib/client/storage/dropbox/src/drivers-chrome.coffee new file mode 100644 index 00000000..2ead8094 --- /dev/null +++ b/lib/client/storage/dropbox/src/drivers-chrome.coffee @@ -0,0 +1,202 @@ +DropboxChromeOnMessage = null +DropboxChromeSendMessage = null + +if chrome? + # v2 manifest APIs. + if chrome.runtime + if chrome.runtime.onMessage + DropboxChromeOnMessage = chrome.runtime.onMessage + if chrome.runtime.sendMessage + DropboxChromeSendMessage = (m) -> chrome.runtime.sendMessage m + + # v1 manifest APIs. + if chrome.extension + if chrome.extension.onMessage + DropboxChromeOnMessage or= chrome.extension.onMessage + if chrome.extension.sendMessage + DropboxChromeSendMessage or= (m) -> chrome.extension.sendMessage m + + # Apps that use the v2 manifest don't get messenging in Chrome 25. + unless DropboxChromeOnMessage + do -> + pageHack = (page) -> + if page.Dropbox + Dropbox.Drivers.Chrome::onMessage = + page.Dropbox.Drivers.Chrome.onMessage + Dropbox.Drivers.Chrome::sendMessage = + page.Dropbox.Drivers.Chrome.sendMessage + else + page.Dropbox = Dropbox + Dropbox.Drivers.Chrome::onMessage = new Dropbox.EventSource + Dropbox.Drivers.Chrome::sendMessage = + (m) -> Dropbox.Drivers.Chrome::onMessage.dispatch m + + if chrome.extension and chrome.extension.getBackgroundPage + if page = chrome.extension.getBackgroundPage() + return pageHack(page) + + if chrome.runtime and chrome.runtime.getBackgroundPage + return chrome.runtime.getBackgroundPage (page) -> pageHack page + +# OAuth driver specialized for Chrome apps and extensions. +class Dropbox.Drivers.Chrome + # @property {Chrome.Event<>, Dropbox.EventSource<>} fires non-cancelable + # events when Dropbox.Drivers.Chrome#sendMessage is called + onMessage: DropboxChromeOnMessage + + # Sends a message across the Chrome extension / application. + # + # When a message is sent, the listeners registered to + # + # @param {Object} message the message to be sent + sendMessage: DropboxChromeSendMessage + + # Expans an URL relative to the Chrome extension / application root. + # + # @param {String} url a resource URL relative to the extension root + # @return {String} the absolute resource URL + expandUrl: (url) -> + if chrome.runtime and chrome.runtime.getURL + return chrome.runtime.getURL(url) + if chrome.extension and chrome.extension.getURL + return chrome.extension.getURL(url) + url + + # @param {?Object} options the settings below + # @option {String} receiverPath the path of page that receives the /authorize + # redirect and performs the postMessage; the path should be relative to the + # extension folder; by default, is 'chrome_oauth_receiver.html' + constructor: (options) -> + receiverPath = (options and options.receiverPath) or + 'chrome_oauth_receiver.html' + @receiverUrl = @expandUrl receiverPath + @tokenRe = new RegExp "(#|\\?|&)oauth_token=([^&#]+)(&|#|$)" + scope = (options and options.scope) or 'default' + @storageKey = "dropbox_js_#{scope}_credentials" + + # Saves token information when appropriate. + onAuthStateChange: (client, callback) -> + switch client.authState + when Dropbox.Client.RESET + @loadCredentials (credentials) => + if credentials + if credentials.authState + # Stuck authentication process, reset. + return @forgetCredentials(callback) + client.setCredentials credentials + callback() + when Dropbox.Client.DONE + @storeCredentials client.credentials(), callback + when Dropbox.Client.SIGNED_OFF + @forgetCredentials callback + when Dropbox.Client.ERROR + @forgetCredentials callback + else + callback() + + # Shows the authorization URL in a pop-up, waits for it to send a message. + doAuthorize: (authUrl, token, tokenSecret, callback) -> + window = handle: null + @listenForMessage token, window, callback + @openWindow authUrl, (handle) -> window.handle = handle + + # Creates a popup window. + # + # @param {String} url the URL that will be loaded in the popup window + # @param {function(Object)} callback called with a handle that can be passed + # to Dropbox.Driver.Chrome#closeWindow + # @return {Dropbox.Driver.Chrome} this + openWindow: (url, callback) -> + if chrome.tabs and chrome.tabs.create + chrome.tabs.create url: url, active: true, pinned: false, (tab) -> + callback tab + return @ + if chrome.app and chrome.app.window and chrome.app.window.create + chrome.app.window.create url, frame: 'none', id: 'dropbox-auth', + (window) -> callback window + return @ + @ + + # Closes a window that was previously opened with openWindow. + # + # @param {Object} handle the object passed to an openWindow callback + closeWindow: (handle) -> + if chrome.tabs and chrome.tabs.remove and handle.id + chrome.tabs.remove handle.id + return @ + if chrome.app and chrome.app.window and handle.close + handle.close() + return @ + @ + + # URL of the redirect receiver page that messages the app / extension. + url: -> + @receiverUrl + + # Listens for a postMessage from a previously opened tab. + # + # @param {String} token the token string that must be received from the tab + # @param {Object} window a JavaScript object whose "handle" property is a + # window handle passed to the callback of a + # Dropbox.Driver.Chrome#openWindow call + # @param {function()} called when the received message matches the token + listenForMessage: (token, window, callback) -> + listener = (message, sender) => + # Reject messages not coming from the OAuth receiver window. + if sender and sender.tab + unless sender.tab.url.substring(0, @receiverUrl.length) is @receiverUrl + return + + match = @tokenRe.exec message.dropbox_oauth_receiver_href or '' + if match and decodeURIComponent(match[2]) is token + @closeWindow window.handle if window.handle + @onMessage.removeListener listener + callback() + @onMessage.addListener listener + + # Stores a Dropbox.Client's credentials to local storage. + # + # @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) -> + items= {} + items[@storageKey] = credentials + chrome.storage.local.set items, 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) -> + chrome.storage.local.get @storageKey, (items) => + callback items[@storageKey] or 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) -> + chrome.storage.local.remove @storageKey, callback + @ + + # Communicates with the driver from the OAuth receiver page. + @oauthReceiver: -> + window.addEventListener 'load', -> + driver = new Dropbox.Drivers.Chrome() + driver.sendMessage dropbox_oauth_receiver_href: window.location.href + window.close() if window.close diff --git a/lib/client/storage/dropbox/src/drivers-node.coffee b/lib/client/storage/dropbox/src/drivers-node.coffee new file mode 100644 index 00000000..acba25af --- /dev/null +++ b/lib/client/storage/dropbox/src/drivers-node.coffee @@ -0,0 +1,87 @@ +# OAuth driver that redirects the browser to a node app to complete the flow. +# +# This is useful for testing node.js libraries and applications. +class Dropbox.Drivers.NodeServer + # Starts up the node app that intercepts the browser redirect. + # + # @param {?Object} options one or more of the options below + # @option options {Number} port the number of the TCP port that will receive + # HTTP requests + # @param {String} faviconFile the path to a file that will be served at + # /favicon.ico + constructor: (options) -> + @port = options?.port or 8912 + @faviconFile = options?.favicon or null + # Calling require in the constructor because this doesn't work in browsers. + @fs = require 'fs' + @http = require 'http' + @open = require 'open' + + @callbacks = {} + @urlRe = new RegExp "^/oauth_callback\\?" + @tokenRe = new RegExp "(\\?|&)oauth_token=([^&]+)(&|$)" + @createApp() + + # URL to the node.js OAuth callback handler. + url: -> + "http://localhost:#{@port}/oauth_callback" + + # Opens the token + doAuthorize: (authUrl, token, tokenSecret, callback) -> + @callbacks[token] = callback + @openBrowser authUrl + + # Opens the given URL in a browser. + openBrowser: (url) -> + unless url.match /^https?:\/\// + throw new Error("Not a http/https URL: #{url}") + @open url + + # Creates and starts up an HTTP server that will intercept the redirect. + createApp: -> + @app = @http.createServer (request, response) => + @doRequest request, response + @app.listen @port + + # Shuts down the HTTP server. + # + # The driver will become unusable after this call. + closeServer: -> + @app.close() + + # Reads out an /authorize callback. + doRequest: (request, response) -> + if @urlRe.exec request.url + match = @tokenRe.exec request.url + if match + token = decodeURIComponent match[2] + if @callbacks[token] + @callbacks[token]() + delete @callbacks[token] + data = '' + request.on 'data', (dataFragment) -> data += dataFragment + request.on 'end', => + if @faviconFile and (request.url is '/favicon.ico') + @sendFavicon response + else + @closeBrowser response + + # Renders a response that will close the browser window used for OAuth. + closeBrowser: (response) -> + closeHtml = """ + + +

    Please close this window.

    + """ + response.writeHead(200, + {'Content-Length': closeHtml.length, 'Content-Type': 'text/html' }) + response.write closeHtml + response.end + + # Renders the favicon file. + sendFavicon: (response) -> + @fs.readFile @faviconFile, (error, data) -> + response.writeHead(200, + { 'Content-Length': data.length, 'Content-Type': 'image/x-icon' }) + response.write data + response.end diff --git a/lib/client/storage/dropbox/src/drivers.coffee b/lib/client/storage/dropbox/src/drivers.coffee index d48184e4..de555551 100644 --- a/lib/client/storage/dropbox/src/drivers.coffee +++ b/lib/client/storage/dropbox/src/drivers.coffee @@ -57,421 +57,3 @@ class Dropbox.AuthDriver # Namespace for authentication drivers. Dropbox.Drivers = {} - -# 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} 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 - @scope = options?.scope or 'default' - - # 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 - @forgetCredentials() - return 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 e - # Parse errors. - callback null - @ - - # Deletes information previously stored by a call to storeToken. - # - # @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() - @ - - # 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 - @useQuery = options?.useQuery or false - @receiverUrl = @computeUrl options - @tokenRe = new RegExp "(#|\\?|&)oauth_token=([^&#]+)(&|#|$)" - - # 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: -> - @receiverUrl - - # Redirects to the authorize page. - doAuthorize: (authUrl) -> - window.location.assign authUrl - - # Pre-computes the return value of url. - computeUrl: -> - querySuffix = "_dropboxjs_scope=#{encodeURIComponent @scope}" - location = Dropbox.Drivers.BrowserBase.currentLocation() - 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. - else - fragment = "?#{querySuffix}" - - if fragment - location + '#' + fragment - else - location - - # Figures out if the user completed the OAuth flow based on the current URL. - # - # @return {?String} the OAuth token that the user just authorized, or null if - # the user accessed this directly, without having authorized a token - locationToken: -> - location = Dropbox.Drivers.BrowserBase.currentLocation() - - # Check for the scope. - scopePattern = "_dropboxjs_scope=#{encodeURIComponent @scope}&" - return null if location.indexOf?(scopePattern) is -1 - - # Extract the token. - match = @tokenRe.exec location - if match then decodeURIComponent(match[2]) else null - -# 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} noFragment if true, the receiverUrl will be used - # as given; by default, a hash "#" is appended to URLs that don't have - # one, so the OAuth token is received as a 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 - @receiverUrl = @computeUrl options - @tokenRe = new RegExp "(#|\\?|&)oauth_token=([^&#]+)(&|#|$)" - - # 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: -> - @receiverUrl - - # Pre-computes the return value of url. - computeUrl: (options) -> - if options - if options.receiverUrl - if options.noFragment or options.receiverUrl.indexOf('#') isnt -1 - return options.receiverUrl - else - return options.receiverUrl + '#' - else if options.receiverFile - fragments = Dropbox.Drivers.BrowserBase.currentLocation().split '/' - fragments[fragments.length - 1] = options.receiverFile - if options.noFragment - return fragments.join('/') - else - 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) => - match = @tokenRe.exec event.data.toString() - if match and decodeURIComponent(match[2]) is token - window.removeEventListener 'message', listener - callback() - window.addEventListener 'message', listener, false - - -# OAuth driver that redirects the browser to a node app to complete the flow. -# -# This is useful for testing node.js libraries and applications. -class Dropbox.Drivers.NodeServer - # Starts up the node app that intercepts the browser redirect. - # - # @param {?Object} options one or more of the options below - # @option options {Number} port the number of the TCP port that will receive - # HTTP requests - # @param {String} faviconFile the path to a file that will be served at - # /favicon.ico - constructor: (options) -> - @port = options?.port or 8912 - @faviconFile = options?.favicon or null - # Calling require in the constructor because this doesn't work in browsers. - @fs = require 'fs' - @http = require 'http' - @open = require 'open' - - @callbacks = {} - @urlRe = new RegExp "^/oauth_callback\\?" - @tokenRe = new RegExp "(\\?|&)oauth_token=([^&]+)(&|$)" - @createApp() - - # URL to the node.js OAuth callback handler. - url: -> - "http://localhost:#{@port}/oauth_callback" - - # Opens the token - doAuthorize: (authUrl, token, tokenSecret, callback) -> - @callbacks[token] = callback - @openBrowser authUrl - - # Opens the given URL in a browser. - openBrowser: (url) -> - unless url.match /^https?:\/\// - throw new Error("Not a http/https URL: #{url}") - @open url - - # Creates and starts up an HTTP server that will intercept the redirect. - createApp: -> - @app = @http.createServer (request, response) => - @doRequest request, response - @app.listen @port - - # Shuts down the HTTP server. - # - # The driver will become unusable after this call. - closeServer: -> - @app.close() - - # Reads out an /authorize callback. - doRequest: (request, response) -> - if @urlRe.exec request.url - match = @tokenRe.exec request.url - if match - token = decodeURIComponent match[2] - if @callbacks[token] - @callbacks[token]() - delete @callbacks[token] - data = '' - request.on 'data', (dataFragment) -> data += dataFragment - request.on 'end', => - if @faviconFile and (request.url is '/favicon.ico') - @sendFavicon response - else - @closeBrowser response - - # Renders a response that will close the browser window used for OAuth. - closeBrowser: (response) -> - closeHtml = """ - - -

    Please close this window.

    - """ - response.writeHead(200, - {'Content-Length': closeHtml.length, 'Content-Type': 'text/html' }) - response.write closeHtml - response.end - - # Renders the favicon file. - sendFavicon: (response) -> - @fs.readFile @faviconFile, (error, data) -> - response.writeHead(200, - { 'Content-Length': data.length, 'Content-Type': 'image/x-icon' }) - response.write data - response.end diff --git a/lib/client/storage/dropbox/src/event_source.coffee b/lib/client/storage/dropbox/src/event_source.coffee new file mode 100644 index 00000000..b87a3d3c --- /dev/null +++ b/lib/client/storage/dropbox/src/event_source.coffee @@ -0,0 +1,71 @@ +# Event dispatch following a publisher-subscriber (PubSub) model. +class Dropbox.EventSource + # Sets up an event source (publisher). + # + # @param {?Object} options one or more of the options below + # @option options {Boolean} cancelable if true, + constructor: (options) -> + @_cancelable = options and options.cancelable + @_listeners = [] + + # Registers a listener (subscriber) to events coming from this source. + # + # This is a simplified version of the addEventListener DOM API. Listeners + # must be functions, and they can be removed by calling removeListener. + # + # This method is idempotent, so a function will not be added to the list of + # listeners if was previously added. + # + # @param {function(Object)} listener called every time an event is fired; if + # the event is cancelable, the function can return false to cancel the + # event, or any other value to allow it to propagate; the return value is + # ignored for non-cancelable events + # @return {Dropbox.EventSource} this, for easy call chaining + addListener: (listener) -> + unless typeof listener is 'function' + throw new TypeError 'Invalid listener type; expected function' + unless listener in @_listeners + @_listeners.push listener + @ + + # Un-registers a listener (subscriber) previously added by addListener. + # + # This is a simplified version of the removeEventListener DOM API. The + # listener must be exactly the same object supplied to addListener. + # + # This method is idempotent, so it will fail silently if the given listener + # is not registered as a subscriber. + # + # @param {function(Object)} listener function that was previously passed in + # an addListener call + # @return {Dropbox.EventSource} this, for easy call chaining + removeListener: (listener) -> + if @_listeners.indexOf + # IE9+ + index = @_listeners.indexOf listener + @_listeners.splice index, 1 if index isnt -1 + else + # IE8 doesn't implement Array#indexOf in ES5. + for subscriber, i in @_listeners + if subscriber is listener + @_listeners.splice i, 1 + break + @ + + + # Informs the listeners (subscribers) that an event occurred. + # + # Event sources configured for non-cancelable events call all listeners in an + # unspecified order. Sources configured for cancelable events stop calling + # listeners as soon as one listener returns false value. + # + # @param {Object} event passed to all the registered listeners + # @return {Boolean} sources of cancelable events return false if the event + # was canceled and true otherwise; sources of non-cancelable events always + # return true + dispatch: (event) -> + for listener in @_listeners + returnValue = listener event + if @_cancelable and returnValue is false + return false + true diff --git a/lib/client/storage/dropbox/src/pulled_changes.coffee b/lib/client/storage/dropbox/src/pulled_changes.coffee index 0a324d24..6bf7ebcd 100644 --- a/lib/client/storage/dropbox/src/pulled_changes.coffee +++ b/lib/client/storage/dropbox/src/pulled_changes.coffee @@ -37,13 +37,19 @@ class Dropbox.PulledChanges # least 5 miuntes before issuing another pullChanges request shouldBackOff: undefined + # Serializable representation of the pull cursor inside this object. + # + # @return {String} an ASCII string that can be passed to pullChanges instead + # of this PulledChanges instance + cursor: -> @cursorTag + # Creates a new Dropbox.PulledChanges instance from a /delta API call result. # # @private # This constructor is used by Dropbox.PulledChanges, and should not be called # directly. # - # @param {?Object} deltaInfo the parsed JSON of a /delta API call result + # @param {Object} deltaInfo the parsed JSON of a /delta API call result constructor: (deltaInfo) -> @blankSlate = deltaInfo.reset or false @cursorTag = deltaInfo.cursor @@ -88,7 +94,7 @@ class Dropbox.PullChange # This constructor is used by Dropbox.PullChange.parse, and should not be # called directly. # - # @param {?Object} entry the parsed JSON of a single entry in a /delta API + # @param {Object} entry the parsed JSON of a single entry in a /delta API # call result constructor: (entry) -> @path = entry[0] diff --git a/lib/client/storage/dropbox/src/references.coffee b/lib/client/storage/dropbox/src/references.coffee index f5e62437..d7602c5f 100644 --- a/lib/client/storage/dropbox/src/references.coffee +++ b/lib/client/storage/dropbox/src/references.coffee @@ -2,8 +2,8 @@ class Dropbox.PublicUrl # Creates a PublicUrl instance from a raw API response. # - # @param {?Object} urlData the parsed JSON describing a public URL - # @param {Boolean} isDirect true if this is a direct download link, false if + # @param {?Object, ?String} urlData the parsed JSON describing a public URL + # @param {?Boolean} isDirect true if this is a direct download link, false if # is a file / folder preview link # @return {?Dropbox.PublicUrl} a PublicUrl instance wrapping the given public # link info; parameters that don't look like parsed JSON are returned as @@ -15,19 +15,28 @@ class Dropbox.PublicUrl urlData # @property {String} the public URL - url: undefined + url: null # @property {Date} after this time, the URL is not usable - expiresAt: undefined + expiresAt: null # @property {Boolean} true if this is a direct download URL, false for URLs to # preview pages in the Dropbox web app; folders do not have direct link # - isDirect: undefined + isDirect: null # @property {Boolean} true if this is URL points to a file's preview page in # Dropbox, false for direct links - isPreview: undefined + isPreview: null + + # JSON representation of this file / folder's metadata + # + # @return {Object} conforms to the JSON restrictions; can be passed to + # Dropbox.PublicUrl#parse to obtain an identical PublicUrl instance + json: -> + # HACK: this can break if the Dropbox API ever decides to use 'direct' in + # its link info + @_json ||= url: @url, expires: @expiresAt.toString(), direct: @isDirect # Creates a PublicUrl instance from a raw API response. # @@ -47,14 +56,25 @@ class Dropbox.PublicUrl else if isDirect is false @isDirect = false else - @isDirect = Date.now() - @expiresAt <= 86400000 # 1 day + # HACK: this can break if the Dropbox API ever decides to use 'direct' in + # its link info; unfortunately, there's no elegant way to guess + # between direct download URLs and preview URLs + if 'direct' of urlData + @isDirect = urlData.direct + else + @isDirect = Date.now() - @expiresAt <= 86400000 # 1 day @isPreview = !@isDirect + # The JSON representation is created on-demand, to avoid unnecessary object + # creation. + # We can't use the original JSON object because we add a 'direct' field. + @_json = null + # Reference to a file that can be used to make a copy across users' Dropboxes. class Dropbox.CopyReference # Creates a CopyReference instance from a raw reference or API response. # - # @param {?Object, ?String} refData the parsed JSON descring a copy + # @param {?Object, ?String} refData the parsed JSON describing a copy # reference, or the reference string @parse: (refData) -> if refData and (typeof refData is 'object' or typeof refData is 'string') @@ -63,10 +83,20 @@ class Dropbox.CopyReference refData # @property {String} the raw reference, for use with Dropbox APIs - tag: undefined + tag: null # @property {Date} deadline for using the reference in a copy operation - expiresAt: undefined + expiresAt: null + + # JSON representation of this file / folder's metadata + # + # @return {Object} conforms to the JSON restrictions; can be passed to + # Dropbox.CopyReference#parse to obtain an identical CopyReference instance + json: -> + # NOTE: the assignment only occurs if the CopyReference was built around a + # string; CopyReferences parsed from API responses hold onto the + # original JSON + @_json ||= copy_ref: @tag, expires: @expiresAt.toString() # Creates a CopyReference instance from a raw reference or API response. # @@ -74,13 +104,16 @@ class Dropbox.CopyReference # This constructor is used by Dropbox.CopyReference.parse, and should not be # called directly. # - # @param {?Object, ?String} refData the parsed JSON descring a copy + # @param {Object, String} refData the parsed JSON describing a copy # reference, or the reference string constructor: (refData) -> if typeof refData is 'object' @tag = refData.copy_ref @expiresAt = new Date Date.parse(refData.expires) + @_json = refData else @tag = refData - @expiresAt = new Date() - + @expiresAt = new Date Math.ceil(Date.now() / 1000) * 1000 + # The JSON representation is created on-demand, to avoid unnecessary + # object creation. + @_json = null diff --git a/lib/client/storage/dropbox/src/stat.coffee b/lib/client/storage/dropbox/src/stat.coffee index 6fe45c44..ea3db318 100644 --- a/lib/client/storage/dropbox/src/stat.coffee +++ b/lib/client/storage/dropbox/src/stat.coffee @@ -48,15 +48,15 @@ class Dropbox.Stat # folder contents twice versionTag: null - # @property {String} a guess of the MIME type representing the file or folder's - # contents + # @property {String} a guess of the MIME type representing the file or + # folder's contents mimeType: null # @property {Number} the size of the file, in bytes; null for folders size: null - # @property {String} the size of the file, in a human-readable format, such as - # "225.4KB"; the format of this string is influenced by the API client's + # @property {String} the size of the file, in a human-readable format, such + # as "225.4KB"; the format of this string is influenced by the API client's # locale humanSize: null @@ -73,6 +73,13 @@ class Dropbox.Stat # does not report any time clientModifiedAt: null + # JSON representation of this file / folder's metadata + # + # @return {Object} conforms to the JSON restrictions; can be passed to + # Dropbox.Stat#parse to obtain an identical Stat instance + json: -> + @_json + # Creates a Stat instance from a raw "metadata" response. # # @private @@ -82,6 +89,7 @@ class Dropbox.Stat # @param {Object} metadata the result of parsing JSON API responses that are # called "metadata" in the API documentation constructor: (metadata) -> + @_json = metadata @path = metadata.path # Ensure there is a trailing /, to make path processing reliable. @path = '/' + @path if @path.substring(0, 1) isnt '/' diff --git a/lib/client/storage/dropbox/src/upload_cursor.coffee b/lib/client/storage/dropbox/src/upload_cursor.coffee new file mode 100644 index 00000000..5e660102 --- /dev/null +++ b/lib/client/storage/dropbox/src/upload_cursor.coffee @@ -0,0 +1,59 @@ +# Tracks the progress of a resumable upload. +class Dropbox.UploadCursor + # Creates an UploadCursor instance from an API response. + # + # @param {?Object, ?String} cursorData the parsed JSON describing the status + # of a partial upload, or the upload ID string + @parse: (cursorData) -> + if cursorData and (typeof cursorData is 'object' or + typeof cursorData is 'string') + new Dropbox.UploadCursor cursorData + else + cursorData + + # @property {String} the server-generated ID for this upload + tag: null + + # @property {Number} number of bytes that have already been uploaded + offset: null + + # @property {Date} deadline for finishing the upload + expiresAt: null + + # JSON representation of this cursor. + # + # @return {Object} conforms to the JSON restrictions; can be passed to + # Dropbox.UploadCursor#parse to obtain an identical UploadCursor instance + json: -> + # NOTE: the assignment only occurs if + @_json ||= upload_id: @tag, offset: @offset, expires: @expiresAt.toString() + + # Creates an UploadCursor instance from a raw reference or API response. + # + # This constructor should only be called directly to obtain a cursor for a + # new file upload. Dropbox.UploadCursor#parse should be called instead + # + # @param {?Object, ?String} cursorData the parsed JSON describing a copy + # reference, or the reference string + constructor: (cursorData) -> + @replace cursorData + + # Replaces the current + # + # @private Called by Dropbox.Client#resumableUploadStep. + # + # @param {?Object, ?String} cursorData the parsed JSON describing a copy + # reference, or the reference string + # @return {Dropbox.UploadCursor} this + replace: (cursorData) -> + if typeof cursorData is 'object' + @tag = cursorData.upload_id or null + @offset = cursorData.offset or 0 + @expiresAt = new Date(Date.parse(cursorData.expires) or Date.now()) + @_json = cursorData + else + @tag = cursorData or null + @offset = 0 + @expiresAt = new Date Math.floor(Date.now() / 1000) * 1000 + @_json = null + @ diff --git a/lib/client/storage/dropbox/src/user_info.coffee b/lib/client/storage/dropbox/src/user_info.coffee index fa61f212..ba6dbea2 100644 --- a/lib/client/storage/dropbox/src/user_info.coffee +++ b/lib/client/storage/dropbox/src/user_info.coffee @@ -27,7 +27,8 @@ class Dropbox.UserInfo # returned by the authentication process uid: null - # @property {String} + # @property {String} the user's referral link; the user might benefit if + # others use the link to sign up for Dropbox referralUrl: null # Specific to applications whose access type is "public app folder". @@ -49,15 +50,23 @@ class Dropbox.UserInfo # shared with other users sharedBytes: null + # JSON representation of this user's information. + # + # @return {Object} conforms to the JSON restrictions; can be passed to + # Dropbox.UserInfo#parse to obtain an identical UserInfo instance + json: -> + @_json + # Creates a UserInfo instance from a raw API response. # # @private # This constructor is used by Dropbox.UserInfo.parse, and should not be # called directly. # - # @param {?Object} userInfo the result of parsing a JSON API response that + # @param {Object} userInfo the result of parsing a JSON API response that # describes a user constructor: (userInfo) -> + @_json = userInfo @name = userInfo.display_name @email = userInfo.email @countryCode = userInfo.country or null diff --git a/lib/client/storage/dropbox/src/xhr.coffee b/lib/client/storage/dropbox/src/xhr.coffee index ef630f67..63051828 100644 --- a/lib/client/storage/dropbox/src/xhr.coffee +++ b/lib/client/storage/dropbox/src/xhr.coffee @@ -30,28 +30,41 @@ else # https://code.google.com/p/chromium/issues/detail?id=60449 if typeof Uint8Array is 'undefined' DropboxXhrArrayBufferView = null + DropboxXhrSendArrayBufferView = false else - DropboxXhrArrayBufferView = - (new Uint8Array(0)).__proto__.__proto__.constructor + if Object.getPrototypeOf + DropboxXhrArrayBufferView = Object.getPrototypeOf( + Object.getPrototypeOf(new Uint8Array(0))).constructor + else if Object.__proto__ + DropboxXhrArrayBufferView = + (new Uint8Array(0)).__proto__.__proto__.constructor + + # Browsers that haven't implemented XHR#send(ArrayBufferView) also don't + # have a real ArrayBufferView prototype. (Safari, Firefox) + DropboxXhrSendArrayBufferView = DropboxXhrArrayBufferView isnt Object # Dispatches low-level AJAX calls (XMLHttpRequests). class Dropbox.Xhr # The object used to perform AJAX requests (XMLHttpRequest). @Request = DropboxXhrRequest # Set to true when using the XDomainRequest API. - @ieMode = DropboxXhrIeMode + @ieXdr = DropboxXhrIeMode # Set to true if the platform has proper support for FormData. @canSendForms = DropboxXhrCanSendForms # Set to true if the platform performs CORS preflight checks. @doesPreflight = DropboxXhrDoesPreflight + # Superclass for all ArrayBufferView objects. @ArrayBufferView = DropboxXhrArrayBufferView + # Set to true if we think we can send ArrayBufferView objects via XHR. + @sendArrayBufferView = DropboxXhrSendArrayBufferView + # Sets up an AJAX request. # # @param {String} method the HTTP method used to make the request ('GET', - # 'POST', 'PUT', etc.) + # 'POST', 'PUT', etc.) # @param {String} baseUrl the URL that receives the request; this URL might - # be modified, e.g. by appending parameters for GET requests + # be modified, e.g. by appending parameters for GET requests constructor: (@method, baseUrl) -> @isGet = @method is 'GET' @url = baseUrl @@ -63,6 +76,17 @@ class Dropbox.Xhr @responseType = null @callback = null @xhr = null + @onError = null + + # @property {?XMLHttpRequest} the raw XMLHttpRequest object used to make the + # request; null until Dropbox.Xhr#prepare is called + xhr: null + + # @property {?Dropbox.EventSource} if the XHR fails and + # this property is set, the Dropbox.ApiError instance that will be passed + # to the callback will be dispatched through the Dropbox.EventSource; the + # EventSource should be configured for non-cancelable events + onError: null # Sets the parameters (form field values) that will be sent with the request. # @@ -100,11 +124,22 @@ class Dropbox.Xhr # headers) might result in a valid signature that is applied in a sub-optimal # fashion. For best results, call this right before Dropbox.Xhr#prepare. # - signWithOauth: (oauth) -> - if Dropbox.Xhr.ieMode or (Dropbox.Xhr.doesPreflight and (not @preflight)) + # @param {Dropbox.Oauth} oauth OAuth instance whose key and secret will be + # used to sign the request + # @param {Boolean} cacheFriendly if true, the signing process choice will be + # biased towards allowing the HTTP cache to work; by default, the choice + # attempts to avoid the CORS preflight request whenever possible + # @return {Dropbox.Xhr} this, for easy call chaining + signWithOauth: (oauth, cacheFriendly) -> + if Dropbox.Xhr.ieXdr @addOauthParams oauth - else + else if @preflight or !Dropbox.Xhr.doesPreflight @addOauthHeader oauth + else + if @isGet and cacheFriendly + @addOauthHeader oauth + else + @addOauthParams oauth # Ammends the request parameters to include an OAuth signature. # @@ -112,7 +147,7 @@ class Dropbox.Xhr # the signing process. # # @param {Dropbox.Oauth} oauth OAuth instance whose key and secret will be - # used to sign the request + # used to sign the request # @return {Dropbox.Xhr} this, for easy call chaining addOauthParams: (oauth) -> if @signed @@ -129,7 +164,7 @@ class Dropbox.Xhr # the signing process. # # @param {Dropbox.Oauth} oauth OAuth instance whose key and secret will be - # used to sign the request + # used to sign the request # @return {Dropbox.Xhr} this, for easy call chaining addOauthHeader: (oauth) -> if @signed @@ -142,7 +177,7 @@ class Dropbox.Xhr # Sets the body (piece of data) that will be sent with the request. # # @param {String, Blob, ArrayBuffer} body the body to be sent in a request; - # GET requests cannot have a body + # GET requests cannot have a body # @return {Dropbox.Xhr} this, for easy call chaining setBody: (body) -> if @isGet @@ -150,9 +185,13 @@ class Dropbox.Xhr if @body isnt null throw new Error 'Request already has a body' - unless @preflight - unless (typeof FormData isnt 'undefined') and (body instanceof FormData) - @preflight = true + if typeof body is 'string' + # Content-Type will be set automatically. + else if (typeof FormData isnt 'undefined') and (body instanceof FormData) + # Content-Type will be set automatically. + else + @headers['Content-Type'] = 'application/octet-stream' + @preflight = true @body = body @ @@ -191,26 +230,37 @@ class Dropbox.Xhr # Simulates having an being sent with the request. # # @param {String} fieldName the name of the form field / parameter (not of - # the uploaded file) + # the uploaded file) # @param {String} fileName the name of the uploaded file (not the name of the - # form field / parameter) + # form field / parameter) # @param {String, Blob, File} fileData contents of the file to be uploaded # @param {?String} contentType the MIME type of the file to be uploaded; if - # fileData is a Blob or File, its MIME type is used instead + # fileData is a Blob or File, its MIME type is used instead setFileField: (fieldName, fileName, fileData, contentType) -> if @body isnt null throw new Error 'Request already has a body' if @isGet - throw new Error 'paramsToBody cannot be called on GET requests' + throw new Error 'setFileField cannot be called on GET requests' if typeof(fileData) is 'object' and typeof Blob isnt 'undefined' - if ArrayBuffer? and fileData instanceof ArrayBuffer - fileData = new Uint8Array fileData - if Dropbox.Xhr.ArrayBufferView and - fileData instanceof Dropbox.Xhr.ArrayBufferView - contentType or= 'application/octet-stream' - fileData = new Blob [fileData], type: contentType + if typeof ArrayBuffer isnt 'undefined' + if fileData instanceof ArrayBuffer + # Convert ArrayBuffer -> ArrayBufferView on standard-compliant + # browsers, to avoid warnings from the Blob constructor. + if Dropbox.Xhr.sendArrayBufferView + fileData = new Uint8Array fileData + else + # Convert ArrayBufferView -> ArrayBuffer on older browsers, to avoid + # having a Blob that contains "[object Uint8Array]" instead of the + # actual data. + if !Dropbox.Xhr.sendArrayBufferView and fileData.byteOffset is 0 and + fileData.buffer instanceof ArrayBuffer + fileData = fileData.buffer + + contentType or= 'application/octet-stream' + fileData = new Blob [fileData], type: contentType + # Workaround for http://crbug.com/165095 if typeof File isnt 'undefined' and fileData instanceof File fileData = new Blob [fileData], type: fileData.type @@ -236,7 +286,7 @@ class Dropbox.Xhr # @private # @return {String} a nonce suitable for use as a part boundary in a multipart - # MIME message + # MIME message multipartBoundary: -> [Date.now().toString(36), Math.random().toString(36)].join '----' @@ -276,8 +326,8 @@ class Dropbox.Xhr # # @return {Dropbox.Xhr} this, for easy call chaining prepare: -> - ieMode = Dropbox.Xhr.ieMode - if @isGet or @body isnt null or ieMode + ieXdr = Dropbox.Xhr.ieXdr + if @isGet or @body isnt null or ieXdr @paramsToUrl() if @body isnt null and typeof @body is 'string' @headers['Content-Type'] = 'text/plain; charset=utf8' @@ -285,14 +335,18 @@ class Dropbox.Xhr @paramsToBody() @xhr = new Dropbox.Xhr.Request() - if ieMode - @xhr.onload = => @onLoad() - @xhr.onerror = => @onError() + if ieXdr + @xhr.onload = => @onXdrLoad() + @xhr.onerror = => @onXdrError() + @xhr.ontimeout = => @onXdrError() + # NOTE: there are reports that XHR somtimes fails if onprogress doesn't + # have any handler + @xhr.onprogress = -> else @xhr.onreadystatechange = => @onReadyStateChange() @xhr.open @method, @url, true - unless ieMode + unless ieXdr for own header, value of @headers @xhr.setRequestHeader header, value @@ -321,16 +375,15 @@ class Dropbox.Xhr if @body isnt null body = @body # send() in XHR doesn't like naked ArrayBuffers - if Dropbox.Xhr.ArrayBufferView and body instanceof ArrayBuffer + if Dropbox.Xhr.sendArrayBufferView and body instanceof ArrayBuffer body = new Uint8Array body try @xhr.send body catch e - # Firefox doesn't support sending ArrayBufferViews. # Node.js doesn't implement Blob. - if typeof Blob isnt 'undefined' and Dropbox.Xhr.ArrayBufferView and - body instanceof Dropbox.Xhr.ArrayBufferView + if !Dropbox.Xhr.sendArrayBufferView and typeof Blob isnt 'undefined' + # Firefox doesn't support sending ArrayBufferViews. body = new Blob [body], type: 'application/octet-stream' @xhr.send body else @@ -381,6 +434,7 @@ class Dropbox.Xhr if @xhr.status < 200 or @xhr.status >= 300 apiError = new Dropbox.ApiError @xhr, @method, @url + @onError.dispatch apiError if @onError @callback apiError return true @@ -432,7 +486,7 @@ class Dropbox.Xhr true # Handles the XDomainRequest onload event. (IE 8, 9) - onLoad: -> + onXdrLoad: -> text = @xhr.responseText switch @xhr.contentType when 'application/x-www-form-urlencoded' @@ -444,7 +498,8 @@ class Dropbox.Xhr true # Handles the XDomainRequest onload event. (IE 8, 9) - onError: -> + onXdrError: -> apiError = new Dropbox.ApiError @xhr, @method, @url + @onError.dispatch apiError if @onError @callback apiError return true diff --git a/lib/client/storage/dropbox/test/app_icon/hazard.svg b/lib/client/storage/dropbox/test/app_icon/hazard.svg new file mode 100644 index 00000000..1c9542c1 --- /dev/null +++ b/lib/client/storage/dropbox/test/app_icon/hazard.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/lib/client/storage/dropbox/test/app_icon/hazard128.png b/lib/client/storage/dropbox/test/app_icon/hazard128.png new file mode 100644 index 00000000..92e78693 Binary files /dev/null and b/lib/client/storage/dropbox/test/app_icon/hazard128.png differ diff --git a/lib/client/storage/dropbox/test/app_icon/hazard16.png b/lib/client/storage/dropbox/test/app_icon/hazard16.png new file mode 100644 index 00000000..847d67dc Binary files /dev/null and b/lib/client/storage/dropbox/test/app_icon/hazard16.png differ diff --git a/lib/client/storage/dropbox/test/app_icon/hazard64.png b/lib/client/storage/dropbox/test/app_icon/hazard64.png new file mode 100644 index 00000000..afdd91e7 Binary files /dev/null and b/lib/client/storage/dropbox/test/app_icon/hazard64.png differ diff --git a/lib/client/storage/dropbox/test/app_icon/radiation.svg b/lib/client/storage/dropbox/test/app_icon/radiation.svg new file mode 100644 index 00000000..f21963e2 --- /dev/null +++ b/lib/client/storage/dropbox/test/app_icon/radiation.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/lib/client/storage/dropbox/test/app_icon/radiation128.png b/lib/client/storage/dropbox/test/app_icon/radiation128.png new file mode 100644 index 00000000..eefcc148 Binary files /dev/null and b/lib/client/storage/dropbox/test/app_icon/radiation128.png differ diff --git a/lib/client/storage/dropbox/test/app_icon/radiation16.png b/lib/client/storage/dropbox/test/app_icon/radiation16.png new file mode 100644 index 00000000..4626e76c Binary files /dev/null and b/lib/client/storage/dropbox/test/app_icon/radiation16.png differ diff --git a/lib/client/storage/dropbox/test/app_icon/radiation64.png b/lib/client/storage/dropbox/test/app_icon/radiation64.png new file mode 100644 index 00000000..8abe6bc4 Binary files /dev/null and b/lib/client/storage/dropbox/test/app_icon/radiation64.png differ diff --git a/lib/client/storage/dropbox/test/binary/dropbox.png b/lib/client/storage/dropbox/test/binary/dropbox.png new file mode 100644 index 00000000..1442d90c Binary files /dev/null and b/lib/client/storage/dropbox/test/binary/dropbox.png differ diff --git a/lib/client/storage/dropbox/test/chrome_app/images/icon.svg b/lib/client/storage/dropbox/test/chrome_app/images/icon.svg new file mode 100644 index 00000000..b074aab1 --- /dev/null +++ b/lib/client/storage/dropbox/test/chrome_app/images/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/client/storage/dropbox/test/chrome_app/images/icon128.png b/lib/client/storage/dropbox/test/chrome_app/images/icon128.png new file mode 100644 index 00000000..82d61dbc Binary files /dev/null and b/lib/client/storage/dropbox/test/chrome_app/images/icon128.png differ diff --git a/lib/client/storage/dropbox/test/chrome_app/images/icon16.png b/lib/client/storage/dropbox/test/chrome_app/images/icon16.png new file mode 100644 index 00000000..86c7e8b7 Binary files /dev/null and b/lib/client/storage/dropbox/test/chrome_app/images/icon16.png differ diff --git a/lib/client/storage/dropbox/test/chrome_app/images/icon48.png b/lib/client/storage/dropbox/test/chrome_app/images/icon48.png new file mode 100644 index 00000000..529cf14f Binary files /dev/null and b/lib/client/storage/dropbox/test/chrome_app/images/icon48.png differ diff --git a/lib/client/storage/dropbox/test/chrome_app/manifests/app_v1.json b/lib/client/storage/dropbox/test/chrome_app/manifests/app_v1.json new file mode 100644 index 00000000..5e369ae0 --- /dev/null +++ b/lib/client/storage/dropbox/test/chrome_app/manifests/app_v1.json @@ -0,0 +1,20 @@ +{ + "name": "dropbox.js Test Suite", + "version": "1.0", + "manifest_version": 2, + "description": "Test suite for Chrome applications and extensions.", + "icons": { + "16": "images/icon16.png", + "48": "images/icon48.png", + "128": "images/icon128.png" + }, + "permissions": [ + "storage", + "unlimitedStorage" + ], + "app": { + "launch": { + "local_path": "test/html/browser_test.html" + } + } +} diff --git a/lib/client/storage/dropbox/test/chrome_app/manifests/app_v2.json b/lib/client/storage/dropbox/test/chrome_app/manifests/app_v2.json new file mode 100644 index 00000000..a41b4611 --- /dev/null +++ b/lib/client/storage/dropbox/test/chrome_app/manifests/app_v2.json @@ -0,0 +1,22 @@ +{ + "name": "dropbox.js Test Suite", + "version": "1.0", + "manifest_version": 2, + "description": "Test suite for Chrome applications and extensions.", + "icons": { + "16": "images/icon16.png", + "48": "images/icon48.png", + "128": "images/icon128.png" + }, + "permissions": [ + "storage", + "unlimitedStorage" + ], + "app": { + "background": { + "scripts": [ + "test/js/chrome_app_background.js" + ] + } + } +} diff --git a/lib/client/storage/dropbox/test/chrome_extension/README.md b/lib/client/storage/dropbox/test/chrome_extension/README.md new file mode 100644 index 00000000..34c15033 --- /dev/null +++ b/lib/client/storage/dropbox/test/chrome_extension/README.md @@ -0,0 +1,10 @@ +# dropbox.js Test Automator + +This is a Google Chrome extension that fully automates the dropbox.js test +suite. Read the +[dropbox.js development guide](https://github.com/dropbox/dropbox-js/tree/master/doc) +to learn how the extension fits into the testing process. + +You're welcome to reuse the code to automate the testing of your own +application's integration with Dropbox. + diff --git a/lib/client/storage/dropbox/test/chrome_extension/background.coffee b/lib/client/storage/dropbox/test/chrome_extension/background.coffee new file mode 100644 index 00000000..b29393ba --- /dev/null +++ b/lib/client/storage/dropbox/test/chrome_extension/background.coffee @@ -0,0 +1,84 @@ +# Background script orchestrating the dropbox.js testing automation. + +class Automator + constructor: -> + @wired = false + chrome.storage.sync.get 'enabled', (values) => + @lifeSwitch values.enabled is 'true' + + # Activates or deactivates the extension. + # @param {Boolean} enabled if true, the extension's functionality is enabled + lifeSwitch: (enabled) -> + if enabled + chrome.browserAction.setIcon + path: + 19: 'images/action_on19.png' + 38: 'images/action_on38.png' + chrome.browserAction.setTitle title: '(on) dropbox.js Test Automator' + @wire() + else + chrome.browserAction.setIcon + path: + 19: 'images/action_off19.png', + 38: 'images/action_off38.png' + chrome.browserAction.setTitle title: '(off) dropbox.js Test Automator' + @unwire() + + # Checks if Dropbox's authorization dialog should be auto-clicked. + # @param {String} url the URL of the Dropbox authorization dialog + # @return {Boolean} true if the "Authorize" button should be auto-clicked + shouldAutomateAuth: (url) -> + return false unless @wired + !!(/(\?|&)oauth_callback=https?%3A%2F%2Flocalhost%3A891[12]%2F/.exec(url)) + + # Checks if an OAuth receiver window should be auto-closed. + # @param {String} url the URL of the OAuth receiver window + # @return {Boolean} true if the "Authorize" button should be auto-clicked + shouldAutomateClose: (url) -> + return false unless @wired + !!(/^https?:\/\/localhost:8912\/oauth_callback\?/.exec(url)) + + # Sets up all the features that make dropbox.js testing easier. + wire: -> + return if @wired + chrome.contentSettings.popups.set( + primaryPattern: 'http://localhost:8911/*', setting: 'allow') + @wired = true + @ + + # Disables the features that automate dropbox.js testing. + unwire: -> + return unless @wired + chrome.contentSettings.popups.clear({}) + @wired = false + @ + +# Global Automator instance. +automator = new Automator() + +# Current extension id, used to validate incoming messages. +extensionId = chrome.i18n.getMessage "@@extension_id" + +# Communicates with content scripts. +chrome.extension.onMessage.addListener (message, sender, sendResponse) -> + return unless sender.id is extensionId + switch message.type + when 'auth' + sendResponse automate: automator.shouldAutomateAuth(message.url) + when 'close' + if automator.shouldAutomateClose(message.url) and sender.tab + chrome.tabs.remove sender.tab.id + +# Listen to pref changes and activate / deactivate the extension. +chrome.storage.onChanged.addListener (changes, namespace) -> + return unless namespace is 'sync' + for name, change of changes + continue unless name is 'enabled' + automator.lifeSwitch change.newValue is 'true' + +# The browser action item flips the switch that activates the extension. +chrome.browserAction.onClicked.addListener -> + chrome.storage.sync.get 'enabled', (values) -> + enabled = values.enabled is 'true' + chrome.storage.sync.set enabled: (!enabled).toString() + diff --git a/lib/client/storage/dropbox/test/chrome_extension/content_auth.coffee b/lib/client/storage/dropbox/test/chrome_extension/content_auth.coffee new file mode 100644 index 00000000..1fb6ca89 --- /dev/null +++ b/lib/client/storage/dropbox/test/chrome_extension/content_auth.coffee @@ -0,0 +1,18 @@ +# Content script for Dropbox authorization pages. + +message = type: 'auth', url: window.location.href +chrome.extension.sendMessage message, (response) -> + return unless response.automate + + button = document.querySelector('[name=allow_access]') or + document.querySelector '.freshbutton-blue' + event = document.createEvent 'MouseEvents' + + clientX = button.clientWidth / 2 + clientY = button.clientHeight / 2 + screenX = window.screenX + button.offsetLeft + clientX + screenY = window.screenY + button.offsetTop + clientY + event.initMouseEvent 'click', true, true, window, 1, + screenX, screenY, clientX, clientY, false, false, false, false, 0, null + button.dispatchEvent event + diff --git a/lib/client/storage/dropbox/test/chrome_extension/content_close.coffee b/lib/client/storage/dropbox/test/chrome_extension/content_close.coffee new file mode 100644 index 00000000..0ca6febd --- /dev/null +++ b/lib/client/storage/dropbox/test/chrome_extension/content_close.coffee @@ -0,0 +1,4 @@ +# Content script for Dropbox OAuth receiver pages. + +message = type: 'close', url: window.location.href +chrome.extension.sendMessage message diff --git a/lib/client/storage/dropbox/test/chrome_extension/images/action_off19.png b/lib/client/storage/dropbox/test/chrome_extension/images/action_off19.png new file mode 100644 index 00000000..734c470d Binary files /dev/null and b/lib/client/storage/dropbox/test/chrome_extension/images/action_off19.png differ diff --git a/lib/client/storage/dropbox/test/chrome_extension/images/action_off38.png b/lib/client/storage/dropbox/test/chrome_extension/images/action_off38.png new file mode 100644 index 00000000..c6e96cd1 Binary files /dev/null and b/lib/client/storage/dropbox/test/chrome_extension/images/action_off38.png differ diff --git a/lib/client/storage/dropbox/test/chrome_extension/images/action_on19.png b/lib/client/storage/dropbox/test/chrome_extension/images/action_on19.png new file mode 100644 index 00000000..290b36b6 Binary files /dev/null and b/lib/client/storage/dropbox/test/chrome_extension/images/action_on19.png differ diff --git a/lib/client/storage/dropbox/test/chrome_extension/images/action_on38.png b/lib/client/storage/dropbox/test/chrome_extension/images/action_on38.png new file mode 100644 index 00000000..86dfb268 Binary files /dev/null and b/lib/client/storage/dropbox/test/chrome_extension/images/action_on38.png differ diff --git a/lib/client/storage/dropbox/test/chrome_extension/images/icon.svg b/lib/client/storage/dropbox/test/chrome_extension/images/icon.svg new file mode 100644 index 00000000..4a99e835 --- /dev/null +++ b/lib/client/storage/dropbox/test/chrome_extension/images/icon.svg @@ -0,0 +1,69 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/lib/client/storage/dropbox/test/chrome_extension/images/icon128.png b/lib/client/storage/dropbox/test/chrome_extension/images/icon128.png new file mode 100644 index 00000000..6a6c2e11 Binary files /dev/null and b/lib/client/storage/dropbox/test/chrome_extension/images/icon128.png differ diff --git a/lib/client/storage/dropbox/test/chrome_extension/images/icon16.png b/lib/client/storage/dropbox/test/chrome_extension/images/icon16.png new file mode 100644 index 00000000..3374631a Binary files /dev/null and b/lib/client/storage/dropbox/test/chrome_extension/images/icon16.png differ diff --git a/lib/client/storage/dropbox/test/chrome_extension/images/icon48.png b/lib/client/storage/dropbox/test/chrome_extension/images/icon48.png new file mode 100644 index 00000000..360607b2 Binary files /dev/null and b/lib/client/storage/dropbox/test/chrome_extension/images/icon48.png differ diff --git a/lib/client/storage/dropbox/test/chrome_extension/manifest.json b/lib/client/storage/dropbox/test/chrome_extension/manifest.json new file mode 100644 index 00000000..f6fe0e57 --- /dev/null +++ b/lib/client/storage/dropbox/test/chrome_extension/manifest.json @@ -0,0 +1,44 @@ +{ + "name": "dropbox.js Test Automator", + "version": "1.0", + "manifest_version": 2, + "description": "Automatically clicks buttons and closes windows, so you can spend more time coding.", + "icons": { + "16": "images/icon16.png", + "48": "images/icon48.png", + "128": "images/icon128.png" + }, + "permissions": [ + "http://localhost/*", + "https://localhost/*", + "https://www.dropbox.com/1/oauth/authorize*", + "contentSettings", + "storage" + ], + "browser_action": { + "default_icon": { + "19": "images/action_off19.png", + "38": "images/action_off38.png" + }, + "default_title": "dropbox.js Test Automator" + }, + "background": { + "scripts": ["background.js"] + }, + "content_scripts": [ + { + "matches": ["https://www.dropbox.com/1/oauth/authorize*"], + "js": ["content_auth.js"], + "run_at": "document_idle" + }, + { + "matches": [ + "http://localhost/*", + "https://localhost/*" + ], + "include_globs": ["http*://localhost:8912/oauth_callback*"], + "js": ["content_close.js"], + "run_at": "document_idle" + } + ] +} diff --git a/lib/client/storage/dropbox/test/html/browser_test.html b/lib/client/storage/dropbox/test/html/browser_test.html new file mode 100644 index 00000000..21bb0cec --- /dev/null +++ b/lib/client/storage/dropbox/test/html/browser_test.html @@ -0,0 +1,40 @@ + + + + + + dropbox.js browser tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + diff --git a/lib/client/storage/dropbox/test/html/chrome_oauth_receiver.html b/lib/client/storage/dropbox/test/html/chrome_oauth_receiver.html new file mode 100644 index 00000000..0f0db938 --- /dev/null +++ b/lib/client/storage/dropbox/test/html/chrome_oauth_receiver.html @@ -0,0 +1,13 @@ + + + + + + + +

    Dropbox sign-in successful

    + +

    Please close this window.

    + + diff --git a/lib/client/storage/dropbox/test/html/oauth_receiver.html b/lib/client/storage/dropbox/test/html/oauth_receiver.html new file mode 100644 index 00000000..54a70562 --- /dev/null +++ b/lib/client/storage/dropbox/test/html/oauth_receiver.html @@ -0,0 +1,14 @@ + + + + + + + +

    Dropbox sign-in successful

    + +

    Please close this window.

    + + diff --git a/lib/client/storage/dropbox/test/html/redirect_driver_test.html b/lib/client/storage/dropbox/test/html/redirect_driver_test.html new file mode 100644 index 00000000..8e5e2410 --- /dev/null +++ b/lib/client/storage/dropbox/test/html/redirect_driver_test.html @@ -0,0 +1,34 @@ + + + + + + + + +

    Please close this window.

    + + diff --git a/lib/client/storage/dropbox/test/src/base64_test.coffee b/lib/client/storage/dropbox/test/src/base64_test.coffee new file mode 100644 index 00000000..975169f2 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/base64_test.coffee @@ -0,0 +1,11 @@ +describe 'Dropbox.atob', -> + it 'decodes an ASCII string', -> + expect(Dropbox.atob('YTFiMmMz')).to.equal 'a1b2c3' + it 'decodes a non-ASCII character', -> + expect(Dropbox.atob('/A==')).to.equal String.fromCharCode(252) + +describe 'Dropbox.btoa', -> + it 'encodes an ASCII string', -> + expect(Dropbox.btoa('a1b2c3')).to.equal 'YTFiMmMz' + it 'encodes a non-ASCII character', -> + expect(Dropbox.btoa(String.fromCharCode(252))).to.equal '/A==' diff --git a/lib/client/storage/dropbox/test/src/browser_mocha_runner.coffee b/lib/client/storage/dropbox/test/src/browser_mocha_runner.coffee new file mode 100644 index 00000000..e538dce8 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/browser_mocha_runner.coffee @@ -0,0 +1,9 @@ +window.addEventListener 'load', -> + runner = mocha.run -> + failures = runner.failures || 0 + total = runner.total || 0 + image = new Image() + image.src = "/diediedie?failed=#{failures}&total=#{total}"; + image.onload = -> + null + diff --git a/lib/client/storage/dropbox/test/src/browser_mocha_setup.coffee b/lib/client/storage/dropbox/test/src/browser_mocha_setup.coffee new file mode 100644 index 00000000..70a2f3b7 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/browser_mocha_setup.coffee @@ -0,0 +1 @@ +mocha.setup ui: 'bdd', slow: 150, timeout: 10000 diff --git a/lib/client/storage/dropbox/test/src/chrome_app_background.coffee b/lib/client/storage/dropbox/test/src/chrome_app_background.coffee new file mode 100644 index 00000000..634d0199 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/chrome_app_background.coffee @@ -0,0 +1,3 @@ +chrome.app.runtime.onLaunched.addListener -> + chrome.app.window.create 'test/html/browser_test.html', + type: 'shell', frame: 'chrome', id: 'test_suite' diff --git a/lib/client/storage/dropbox/test/src/chrome_oauth_receiver.coffee b/lib/client/storage/dropbox/test/src/chrome_oauth_receiver.coffee new file mode 100644 index 00000000..de601b08 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/chrome_oauth_receiver.coffee @@ -0,0 +1 @@ +Dropbox.Drivers.Chrome.oauthReceiver() diff --git a/lib/client/storage/dropbox/test/src/client_test.coffee b/lib/client/storage/dropbox/test/src/client_test.coffee new file mode 100644 index 00000000..47d57185 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/client_test.coffee @@ -0,0 +1,1492 @@ +buildClientTests = (clientKeys) -> + # Creates the global client. + setupClient = (test, done) -> + # Should only be used for fixture teardown. + test.__client = new Dropbox.Client clientKeys + done() + + # Creates the test directory. + setupDirectory = (test, done) -> + # True if running on node.js + test.node_js = module? and module?.exports? and require? + + # All test data should go here. + test.testFolder = '/js tests.' + Math.random().toString(36) + test.__client.mkdir test.testFolder, (error, stat) -> + expect(error).to.equal null + done() + + # Creates the binary image file in the test directory. + setupImageFile = (test, done) -> + test.imageFile = "#{test.testFolder}/test-binary-image.png" + test.imageFileData = testImageBytes + + setupImageFileUsingArrayBuffer test, (success) -> + if success + return done() + setupImageFileUsingBlob test, (success) -> + if success + return done() + setupImageFileUsingString test, done + + # Standard-compliant browsers write via XHR#send(ArrayBufferView). + setupImageFileUsingArrayBuffer = (test, done) -> + if Uint8Array? and (not test.node_js) + testImageServerOn() + xhr = new Dropbox.Xhr 'GET', testImageUrl + xhr.setResponseType('arraybuffer').prepare().send (error, buffer) => + testImageServerOff() + expect(error).to.equal null + test.__client.writeFile test.imageFile, buffer, (error, stat) -> + expect(error).to.equal null + # Some browsers will send the '[object Uint8Array]' string instead of + # the ArrayBufferView. + if stat.size is buffer.byteLength + test.imageFileTag = stat.versionTag + done true + else + done false + else + done false + + # Fallback to XHR#send(Blob). + setupImageFileUsingBlob = (test, done) -> + if Blob? and (not test.node_js) + testImageServerOn() + xhr = new Dropbox.Xhr 'GET', testImageUrl + xhr.setResponseType('arraybuffer').prepare().send (error, buffer) => + testImageServerOff() + expect(error).to.equal null + blob = new Blob [buffer], type: 'image/png' + test.__client.writeFile test.imageFile, blob, (error, stat) -> + expect(error).to.equal null + if stat.size is blob.size + test.imageFileTag = stat.versionTag + done true + else + done false + else + done false + + # Last resort: send a string that will get crushed by encoding errors. + setupImageFileUsingString = (test, done) -> + test.__client.writeFile(test.imageFile, test.imageFileData, + { binary: true }, + (error, stat) -> + expect(error).to.equal null + test.imageFileTag = stat.versionTag + done() + ) + + # Creates the plaintext file in the test directory. + setupTextFile = (test, done) -> + test.textFile = "#{test.testFolder}/test-file.txt" + test.textFileData = "Plaintext test file #{Math.random().toString(36)}.\n" + test.__client.writeFile(test.textFile, test.textFileData, + (error, stat) -> + expect(error).to.equal null + test.textFileTag = stat.versionTag + done() + ) + + # Global (expensive) fixtures. + before (done) -> + @timeout 10 * 1000 + setupClient this, => + setupDirectory this, => + setupImageFile this, => + setupTextFile this, -> + done() + + # Teardown for global fixtures. + after (done) -> + @__client.remove @testFolder, (error, stat) => + @test.error(new Error(error)) if error + done() + + # Per-test (cheap) fixtures. + beforeEach -> + @client = new Dropbox.Client clientKeys + + describe 'URLs for custom API server', -> + it 'computes the other URLs correctly', -> + client = new Dropbox.Client + key: clientKeys.key, + secret: clientKeys.secret, + server: 'https://api.sandbox.dropbox-proxy.com' + + expect(client.apiServer).to.equal( + 'https://api.sandbox.dropbox-proxy.com') + expect(client.authServer).to.equal( + 'https://www.sandbox.dropbox-proxy.com') + expect(client.fileServer).to.equal( + 'https://api-content.sandbox.dropbox-proxy.com') + + describe '#normalizePath', -> + it "doesn't touch relative paths", -> + expect(@client.normalizePath('aa/b/cc/dd')).to.equal 'aa/b/cc/dd' + + it 'removes the leading / from absolute paths', -> + expect(@client.normalizePath('/aaa/b/cc/dd')).to.equal 'aaa/b/cc/dd' + + it 'removes multiple leading /s from absolute paths', -> + expect(@client.normalizePath('///aa/b/ccc/dd')).to.equal 'aa/b/ccc/dd' + + describe '#urlEncodePath', -> + it 'encodes each segment separately', -> + expect(@client.urlEncodePath('a b+c/d?e"f/g&h')).to. + equal "a%20b%2Bc/d%3Fe%22f/g%26h" + it 'normalizes paths', -> + expect(@client.urlEncodePath('///a b+c/g&h')).to. + equal "a%20b%2Bc/g%26h" + + describe '#dropboxUid', -> + it 'matches the uid in the credentials', -> + expect(@client.dropboxUid()).to.equal clientKeys.uid + + describe '#getUserInfo', -> + it 'returns reasonable information', (done) -> + @client.getUserInfo (error, userInfo, rawUserInfo) -> + expect(error).to.equal null + expect(userInfo).to.be.instanceOf Dropbox.UserInfo + expect(userInfo.uid).to.equal clientKeys.uid + expect(rawUserInfo).not.to.be.instanceOf Dropbox.UserInfo + expect(rawUserInfo).to.have.property 'uid' + done() + + describe 'with httpCache', -> + beforeEach -> + @xhr = null + @client.onXhr.addListener (xhr) => + @xhr = xhr + + it 'uses Authorization headers', (done) -> + @client.getUserInfo httpCache: true, (error, userInfo, rawUserInfo) => + if Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers + expect(@xhr.url).to.contain 'oauth_nonce' + else + expect(@xhr.headers).to.have.key 'Authorization' + + expect(error).to.equal null + expect(userInfo).to.be.instanceOf Dropbox.UserInfo + expect(userInfo.uid).to.equal clientKeys.uid + expect(rawUserInfo).not.to.be.instanceOf Dropbox.UserInfo + expect(rawUserInfo).to.have.property 'uid' + done() + + describe '#mkdir', -> + afterEach (done) -> + return done() unless @newFolder + @client.remove @newFolder, (error, stat) -> done() + + it 'creates a folder in the test folder', (done) -> + @newFolder = "#{@testFolder}/test'folder" + @client.mkdir @newFolder, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFolder + expect(stat.isFolder).to.equal true + @client.stat @newFolder, (error, stat) => + expect(error).to.equal null + expect(stat.isFolder).to.equal true + done() + + describe '#readFile', -> + it 'reads a text file', (done) -> + @client.readFile @textFile, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal @textFileData + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @textFile + expect(stat.isFile).to.equal true + done() + + it 'reads the beginning of a text file', (done) -> + return done() if Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + + @client.readFile @textFile, start: 0, length: 10, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal @textFileData.substring(0, 10) + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @textFile + expect(stat.isFile).to.equal true + done() + + it 'reads the middle of a text file', (done) -> + return done() if Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + + @client.readFile @textFile, start: 8, length: 10, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal @textFileData.substring(8, 18) + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @textFile + expect(stat.isFile).to.equal true + done() + + it 'reads the end of a text file via the start: option', (done) -> + return done() if Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + + @client.readFile @textFile, start: 10, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal @textFileData.substring(10) + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @textFile + expect(stat.isFile).to.equal true + done() + + it 'reads the end of a text file via the length: option', (done) -> + return done() if Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + + @client.readFile @textFile, length: 10, (error, data, stat) => + expect(error).to.equal null + expect(data).to. + equal @textFileData.substring(@textFileData.length - 10) + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @textFile + expect(stat.isFile).to.equal true + done() + + it 'reads a binary file into a string', (done) -> + @client.readFile @imageFile, binary: true, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal @imageFileData + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @imageFile + expect(stat.isFile).to.equal true + done() + + it 'reads a binary file into a Blob', (done) -> + return done() unless Blob? + @client.readFile @imageFile, blob: true, (error, blob, stat) => + expect(error).to.equal null + expect(blob).to.be.instanceOf Blob + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @imageFile + expect(stat.isFile).to.equal true + reader = new FileReader + reader.onloadend = => + return unless reader.readyState == FileReader.DONE + buffer = reader.result + view = new Uint8Array buffer + length = buffer.byteLength + bytes = (String.fromCharCode view[i] for i in [0...length]). + join('') + expect(bytes).to.equal @imageFileData + done() + reader.readAsArrayBuffer blob + + it 'reads a binary file into an ArrayBuffer', (done) -> + return done() unless ArrayBuffer? + @client.readFile @imageFile, arrayBuffer: true, (error, buffer, stat) => + expect(error).to.equal null + expect(buffer).to.be.instanceOf ArrayBuffer + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @imageFile + expect(stat.isFile).to.equal true + view = new Uint8Array buffer + length = buffer.byteLength + bytes = (String.fromCharCode view[i] for i in [0...length]). + join('') + expect(bytes).to.equal @imageFileData + done() + + describe 'with an onXhr listener', -> + beforeEach -> + @listenerXhr = null + @callbackCalled = false + + it 'calls the listener with a Dropbox.Xhr argument', (done) -> + @client.onXhr.addListener (xhr) => + expect(xhr).to.be.instanceOf Dropbox.Xhr + @listenerXhr = xhr + true + + @client.readFile @textFile, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal @textFileData + done() if @listenerXhr + + it 'calls the listener before firing the XHR', (done) -> + @client.onXhr.addListener (xhr) => + unless Dropbox.Xhr.ieXdr # IE's XHR doesn't have readyState + expect(xhr.xhr.readyState).to.equal 1 + expect(@callbackCalled).to.equal false + @listenerXhr = xhr + true + + @client.readFile @textFile, (error, data, stat) => + @callbackCalled = true + expect(@listenerXhr).to.be.instanceOf Dropbox.Xhr + expect(error).to.equal null + expect(data).to.equal @textFileData + done() if @listenerXhr + + it 'does not send the XHR if the listener cancels the event', (done) -> + @client.onXhr.addListener (xhr) => + expect(@callbackCalled).to.equal false + @listenerXhr = xhr + # NOTE: if the client calls send(), a DOM error will fail the test + xhr.send() + false + + @client.readFile @textFile, (error, data, stat) => + @callbackCalled = true + expect(@listenerXhr).to.be.instanceOf Dropbox.Xhr + done() if @listenerXhr + + describe 'with httpCache', -> + beforeEach -> + @xhr = null + @client.onXhr.addListener (xhr) => + @xhr = xhr + + it 'reads a text file using Authorization headers', (done) -> + @client.readFile @textFile, httpCache: true, (error, data, stat) => + if Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers + expect(@xhr.url).to.contain 'oauth_nonce' + else + expect(@xhr.headers).to.have.key 'Authorization' + + expect(error).to.equal null + expect(data).to.equal @textFileData + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @textFile + expect(stat.isFile).to.equal true + done() + + describe '#writeFile', -> + afterEach (done) -> + return done() unless @newFile + @client.remove @newFile, (error, stat) -> done() + + it 'writes a new text file', (done) -> + @newFile = "#{@testFolder}/another text file.txt" + @newFileData = "Another plaintext file #{Math.random().toString(36)}." + @client.writeFile @newFile, @newFileData, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + @client.readFile @newFile, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal @newFileData + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + done() + + it 'writes a new empty file', (done) -> + @newFile = "#{@testFolder}/another text file.txt" + @newFileData = '' + @client.writeFile @newFile, @newFileData, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + @client.readFile @newFile, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal @newFileData + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + done() + + it 'writes a Blob to a binary file', (done) -> + return done() unless Blob? and ArrayBuffer? + @newFile = "#{@testFolder}/test image from blob.png" + newBuffer = new ArrayBuffer @imageFileData.length + newBytes = new Uint8Array newBuffer + for i in [0...@imageFileData.length] + newBytes[i] = @imageFileData.charCodeAt i + @newBlob = new Blob [newBytes], type: 'image/png' + if @newBlob.size isnt newBuffer.byteLength + @newBlob = new Blob [newBuffer], type: 'image/png' + @client.writeFile @newFile, @newBlob, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + + @client.readFile @newFile, arrayBuffer: true, + (error, buffer, stat) => + expect(error).to.equal null + expect(buffer).to.be.instanceOf ArrayBuffer + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + view = new Uint8Array buffer + length = buffer.byteLength + bytes = (String.fromCharCode view[i] for i in [0...length]). + join('') + expect(bytes).to.equal @imageFileData + done() + + it 'writes a File to a binary file', (done) -> + return done() unless File? and Blob? and ArrayBuffer? + @newFile = "#{@testFolder}/test image from blob.png" + newBuffer = new ArrayBuffer @imageFileData.length + newBytes = new Uint8Array newBuffer + for i in [0...@imageFileData.length] + newBytes[i] = @imageFileData.charCodeAt i + newBlob = new Blob [newBytes], type: 'image/png' + + # Called when we have a File wrapping newBlob. + actualTestCase = (file) => + @newFileObject = file + @client.writeFile @newFile, @newFileObject, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + + @client.readFile @newFile, arrayBuffer: true, + (error, buffer, stat) => + expect(error).to.equal null + expect(buffer).to.be.instanceOf ArrayBuffer + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + view = new Uint8Array buffer + length = buffer.byteLength + bytes = (String.fromCharCode view[i] for i in [0...length]). + join('') + expect(bytes).to.equal @imageFileData + done() + + # TODO(pwnall): use lighter method of constructing a File, when available + # http://crbug.com/164933 + return done() if typeof webkitRequestFileSystem is 'undefined' + webkitRequestFileSystem window.TEMPORARY, 1024 * 1024, (fileSystem) -> + # NOTE: the File name is different from the uploaded file name, to + # catch bugs such as http://crbug.com/165095 + fileSystem.root.getFile 'test image file.png', + create: true, exclusive: false, (fileEntry) -> + fileEntry.createWriter (fileWriter) -> + fileWriter.onwriteend = -> + fileEntry.file (file) -> + actualTestCase file + fileWriter.write newBlob + + it 'writes an ArrayBuffer to a binary file', (done) -> + return done() unless ArrayBuffer? + @newFile = "#{@testFolder}/test image from arraybuffer.png" + @newBuffer = new ArrayBuffer @imageFileData.length + newBytes = new Uint8Array @newBuffer + for i in [0...@imageFileData.length] + newBytes[i] = @imageFileData.charCodeAt i + @client.writeFile @newFile, @newBuffer, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + + @client.readFile @newFile, arrayBuffer: true, + (error, buffer, stat) => + expect(error).to.equal null + expect(buffer).to.be.instanceOf ArrayBuffer + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + view = new Uint8Array buffer + length = buffer.byteLength + bytes = (String.fromCharCode view[i] for i in [0...length]). + join('') + expect(bytes).to.equal @imageFileData + done() + + it 'writes an ArrayBufferView to a binary file', (done) -> + return done() unless ArrayBuffer? + @newFile = "#{@testFolder}/test image from arraybufferview.png" + @newBytes = new Uint8Array @imageFileData.length + for i in [0...@imageFileData.length] + @newBytes[i] = @imageFileData.charCodeAt i + @client.writeFile @newFile, @newBytes, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + + @client.readFile @newFile, arrayBuffer: true, + (error, buffer, stat) => + expect(error).to.equal null + expect(buffer).to.be.instanceOf ArrayBuffer + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + view = new Uint8Array buffer + length = buffer.byteLength + bytes = (String.fromCharCode view[i] for i in [0...length]). + join('') + expect(bytes).to.equal @imageFileData + done() + + describe '#resumableUploadStep + #resumableUploadFinish', -> + beforeEach -> + if ArrayBuffer? # IE9 and below doesn't have ArrayBuffer + @length1 = Math.ceil @imageFileData.length / 3 + @length2 = @imageFileData.length - @length1 + @buffer1 = new ArrayBuffer @length1 + + @view1 = new Uint8Array @buffer1 + for i in [0...@length1] + @view1[i] = @imageFileData.charCodeAt i + @buffer2 = new ArrayBuffer @length2 + @view2 = new Uint8Array @buffer2 + for i in [0...@length2] + @view2[i] = @imageFileData.charCodeAt @length1 + i + + if Blob? # node.js and IE9 and below don't have Blob + @blob1 = new Blob [@view1], type: 'image/png' + if @blob1.size isnt @buffer1.byteLength + @blob1 = new Blob [@buffer1], type: 'image/png' + @blob2 = new Blob [@view2], type: 'image/png' + if @blob2.size isnt @buffer2.byteLength + @blob2 = new Blob [@buffer2], type: 'image/png' + + afterEach (done) -> + @timeout 20 * 1000 # This sequence is slow on the current API server. + return done() unless @newFile + @client.remove @newFile, (error, stat) -> done() + + it 'writes a text file in two stages', (done) -> + @timeout 20 * 1000 # This sequence is slow on the current API server. + + @newFile = "#{@testFolder}/test resumable upload.txt" + line1 = "This is the first fragment\n" + line2 = "This is the second fragment\n" + @client.resumableUploadStep line1, null, (error, cursor1) => + expect(error).to.equal null + expect(cursor1).to.be.instanceOf Dropbox.UploadCursor + expect(cursor1.offset).to.equal line1.length + @client.resumableUploadStep line2, cursor1, (error, cursor2) => + expect(error).to.equal null + expect(cursor2).to.be.instanceOf Dropbox.UploadCursor + expect(cursor2.offset).to.equal line1.length + line2.length + expect(cursor2.tag).to.equal cursor1.tag + @client.resumableUploadFinish @newFile, cursor2, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + @client.readFile @newFile, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal line1 + line2 + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + done() + + it 'writes a binary file using two ArrayBuffers', (done) -> + return done() unless @buffer1 + @timeout 20 * 1000 # This sequence is slow on the current API server. + + @newFile = "#{@testFolder}/test resumable arraybuffer upload.png" + @client.resumableUploadStep @buffer1, null, (error, cursor1) => + expect(error).to.equal null + expect(cursor1).to.be.instanceOf Dropbox.UploadCursor + expect(cursor1.offset).to.equal @length1 + @client.resumableUploadStep @buffer2, cursor1, (error, cursor2) => + expect(error).to.equal null + expect(cursor2).to.be.instanceOf Dropbox.UploadCursor + expect(cursor2.offset).to.equal @length1 + @length2 + expect(cursor2.tag).to.equal cursor1.tag + @client.resumableUploadFinish @newFile, cursor2, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + @client.readFile @newFile, arrayBuffer: true, + (error, buffer, stat) => + expect(error).to.equal null + expect(buffer).to.be.instanceOf ArrayBuffer + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + view = new Uint8Array buffer + length = buffer.byteLength + bytes = (String.fromCharCode view[i] for i in [0...length]). + join('') + expect(bytes).to.equal @imageFileData + done() + + it 'writes a binary file using two ArrayBufferViews', (done) -> + return done() unless @view1 + @timeout 20 * 1000 # This sequence is slow on the current API server. + + @newFile = "#{@testFolder}/test resumable arraybuffer upload.png" + @client.resumableUploadStep @buffer1, null, (error, cursor1) => + expect(error).to.equal null + expect(cursor1).to.be.instanceOf Dropbox.UploadCursor + expect(cursor1.offset).to.equal @length1 + @client.resumableUploadStep @buffer2, cursor1, (error, cursor2) => + expect(error).to.equal null + expect(cursor2).to.be.instanceOf Dropbox.UploadCursor + expect(cursor2.offset).to.equal @length1 + @length2 + expect(cursor2.tag).to.equal cursor1.tag + @client.resumableUploadFinish @newFile, cursor2, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + @client.readFile @newFile, arrayBuffer: true, + (error, buffer, stat) => + expect(error).to.equal null + expect(buffer).to.be.instanceOf ArrayBuffer + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + view = new Uint8Array buffer + length = buffer.byteLength + bytes = (String.fromCharCode view[i] for i in [0...length]). + join('') + expect(bytes).to.equal @imageFileData + done() + + + it 'writes a binary file using two Blobs', (done) -> + return done() unless @blob1 + @timeout 20 * 1000 # This sequence is slow on the current API server. + + @newFile = "#{@testFolder}/test resumable blob upload.png" + @client.resumableUploadStep @blob1, null, (error, cursor1) => + expect(error).to.equal null + expect(cursor1).to.be.instanceOf Dropbox.UploadCursor + expect(cursor1.offset).to.equal @length1 + @client.resumableUploadStep @blob2, cursor1, (error, cursor2) => + expect(error).to.equal null + expect(cursor2).to.be.instanceOf Dropbox.UploadCursor + expect(cursor2.offset).to.equal @length1 + @length2 + expect(cursor2.tag).to.equal cursor1.tag + @client.resumableUploadFinish @newFile, cursor2, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + @client.readFile @newFile, arrayBuffer: true, + (error, buffer, stat) => + expect(error).to.equal null + expect(buffer).to.be.instanceOf ArrayBuffer + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + view = new Uint8Array buffer + length = buffer.byteLength + bytes = (String.fromCharCode view[i] for i in [0...length]). + join('') + expect(bytes).to.equal @imageFileData + done() + + it 'recovers from out-of-sync correctly', (done) -> + # IE's XDR doesn't return anything on errors, so we can't do recovery. + return done() if Dropbox.Xhr.ieXdr + @timeout 20 * 1000 # This sequence is slow on the current API server. + + @newFile = "#{@testFolder}/test resumable upload out of sync.txt" + line1 = "This is the first fragment\n" + line2 = "This is the second fragment\n" + @client.resumableUploadStep line1, null, (error, cursor1) => + expect(error).to.equal null + expect(cursor1).to.be.instanceOf Dropbox.UploadCursor + expect(cursor1.offset).to.equal line1.length + cursor1.offset += 10 + @client.resumableUploadStep line2, cursor1, (error, cursor2) => + expect(error).to.equal null + expect(cursor2).to.be.instanceOf Dropbox.UploadCursor + expect(cursor2.offset).to.equal line1.length + expect(cursor2.tag).to.equal cursor1.tag + @client.resumableUploadStep line2, cursor2, (error, cursor3) => + expect(error).to.equal null + expect(cursor3).to.be.instanceOf Dropbox.UploadCursor + expect(cursor3.offset).to.equal line1.length + line2.length + expect(cursor3.tag).to.equal cursor1.tag + @client.resumableUploadFinish @newFile, cursor3, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + @client.readFile @newFile, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal line1 + line2 + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + done() + + it 'reports errors correctly', (done) -> + @newFile = "#{@testFolder}/test resumable upload error.txt" + badCursor = new Dropbox.UploadCursor 'trollcursor' + badCursor.offset = 42 + @client.resumableUploadStep @textFileData, badCursor, (error, cursor) => + expect(cursor).to.equal undefined + expect(error).to.be.instanceOf Dropbox.ApiError + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do status codes. + expect(error.status).to.equal 404 + done() + + describe '#stat', -> + it 'retrieves a Stat for a file', (done) -> + @client.stat @textFile, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @textFile + expect(stat.isFile).to.equal true + expect(stat.versionTag).to.equal @textFileTag + expect(stat.size).to.equal @textFileData.length + if clientKeys.sandbox + expect(stat.inAppFolder).to.equal true + else + expect(stat.inAppFolder).to.equal false + done() + + it 'retrieves a Stat for a folder', (done) -> + @client.stat @testFolder, (error, stat, entries) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @testFolder + expect(stat.isFolder).to.equal true + expect(stat.size).to.equal 0 + if clientKeys.sandbox + expect(stat.inAppFolder).to.equal true + else + expect(stat.inAppFolder).to.equal false + expect(entries).to.equal undefined + done() + + it 'retrieves a Stat and entries for a folder', (done) -> + @client.stat @testFolder, { readDir: true }, (error, stat, entries) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @testFolder + expect(stat.isFolder).to.equal true + expect(entries).to.be.ok + expect(entries).to.have.length 2 + expect(entries[0]).to.be.instanceOf Dropbox.Stat + expect(entries[0].path).not.to.equal @testFolder + expect(entries[0].path).to.have.string @testFolder + done() + + it 'fails cleanly for a non-existing path', (done) -> + listenerError = null + @client.onError.addListener (error) -> listenerError = error + + @client.stat @testFolder + '/should_404.txt', (error, stat, entries) => + expect(stat).to.equal undefined + expect(entries).to.equal.null + expect(error).to.be.instanceOf Dropbox.ApiError + expect(listenerError).to.equal error + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do status codes. + expect(error).to.have.property 'status' + expect(error.status).to.equal 404 + done() + + describe 'with httpCache', -> + beforeEach -> + @xhr = null + @client.onXhr.addListener (xhr) => + @xhr = xhr + + it 'retrieves a Stat for a file using Authorization headers', (done) -> + @client.stat @textFile, httpCache: true, (error, stat) => + if Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers + expect(@xhr.url).to.contain 'oauth_nonce' + else + expect(@xhr.headers).to.have.key 'Authorization' + + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @textFile + expect(stat.isFile).to.equal true + expect(stat.versionTag).to.equal @textFileTag + expect(stat.size).to.equal @textFileData.length + if clientKeys.sandbox + expect(stat.inAppFolder).to.equal true + else + expect(stat.inAppFolder).to.equal false + done() + + describe '#readdir', -> + it 'retrieves a Stat and entries for a folder', (done) -> + @client.readdir @testFolder, (error, entries, dir_stat, entry_stats) => + expect(error).to.equal null + expect(entries).to.be.ok + expect(entries).to.have.length 2 + expect(entries[0]).to.be.a 'string' + expect(entries[0]).not.to.have.string '/' + expect(entries[0]).to.match /^(test-binary-image.png)|(test-file.txt)$/ + expect(dir_stat).to.be.instanceOf Dropbox.Stat + expect(dir_stat.path).to.equal @testFolder + expect(dir_stat.isFolder).to.equal true + expect(entry_stats).to.be.ok + expect(entry_stats).to.have.length 2 + expect(entry_stats[0]).to.be.instanceOf Dropbox.Stat + expect(entry_stats[0].path).not.to.equal @testFolder + expect(entry_stats[0].path).to.have.string @testFolder + done() + + describe 'with httpCache', -> + beforeEach -> + @xhr = null + @client.onXhr.addListener (xhr) => + @xhr = xhr + + it 'retrieves a folder Stat and entries using Authorization', (done) -> + @client.readdir @testFolder, httpCache: true, + (error, entries, dir_stat, entry_stats) => + if Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers + expect(@xhr.url).to.contain 'oauth_nonce' + else + expect(@xhr.headers).to.have.key 'Authorization' + + expect(error).to.equal null + expect(entries).to.be.ok + expect(entries).to.have.length 2 + expect(entries[0]).to.be.a 'string' + expect(entries[0]).not.to.have.string '/' + expect(entries[0]).to.match( + /^(test-binary-image.png)|(test-file.txt)$/) + expect(dir_stat).to.be.instanceOf Dropbox.Stat + expect(dir_stat.path).to.equal @testFolder + expect(dir_stat.isFolder).to.equal true + expect(entry_stats).to.be.ok + expect(entry_stats).to.have.length 2 + expect(entry_stats[0]).to.be.instanceOf Dropbox.Stat + expect(entry_stats[0].path).not.to.equal @testFolder + expect(entry_stats[0].path).to.have.string @testFolder + done() + + describe '#history', -> + it 'gets a list of revisions', (done) -> + @client.history @textFile, (error, versions) => + expect(error).to.equal null + expect(versions).to.have.length 1 + expect(versions[0]).to.be.instanceOf Dropbox.Stat + expect(versions[0].path).to.equal @textFile + expect(versions[0].size).to.equal @textFileData.length + expect(versions[0].versionTag).to.equal @textFileTag + done() + + it 'returns 40x if the limit is set to 0', (done) -> + listenerError = null + @client.onError.addListener (error) -> listenerError = error + + @client.history @textFile, limit: 0, (error, versions) => + expect(error).to.be.instanceOf Dropbox.ApiError + expect(listenerError).to.equal error + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do status codes. + expect(error.status).to.be.within 400, 499 + expect(versions).not.to.be.ok + done() + + describe 'with httpCache', -> + beforeEach -> + @xhr = null + @client.onXhr.addListener (xhr) => + @xhr = xhr + + it 'gets a list of revisions using Authorization headers', (done) -> + @client.history @textFile, httpCache: true, (error, versions) => + if Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers + expect(@xhr.url).to.contain 'oauth_nonce' + else + expect(@xhr.headers).to.have.key 'Authorization' + + expect(error).to.equal null + expect(versions).to.have.length 1 + expect(versions[0]).to.be.instanceOf Dropbox.Stat + expect(versions[0].path).to.equal @textFile + expect(versions[0].size).to.equal @textFileData.length + expect(versions[0].versionTag).to.equal @textFileTag + done() + + describe '#copy', -> + afterEach (done) -> + return done() unless @newFile + @client.remove @newFile, (error, stat) -> done() + + it 'copies a file given by path', (done) -> + @timeout 12 * 1000 # This sequence is slow on the current API server. + + @newFile = "#{@testFolder}/copy of test-file.txt" + @client.copy @textFile, @newFile, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + @client.readFile @newFile, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal @textFileData + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + @client.readFile @textFile, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal @textFileData + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @textFile + expect(stat.versionTag).to.equal @textFileTag + done() + + describe '#makeCopyReference', -> + afterEach (done) -> + return done() unless @newFile + @client.remove @newFile, (error, stat) -> done() + + it 'creates a Dropbox.CopyReference that copies the file', (done) -> + @timeout 12 * 1000 # This sequence is slow on the current API server. + @newFile = "#{@testFolder}/ref copy of test-file.txt" + + @client.makeCopyReference @textFile, (error, copyRef) => + expect(error).to.equal null + expect(copyRef).to.be.instanceOf Dropbox.CopyReference + @client.copy copyRef, @newFile, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isFile).to.equal true + @client.readFile @newFile, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal @textFileData + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + done() + + describe '#move', -> + beforeEach (done) -> + @timeout 10 * 1000 # This sequence is slow on the current API server. + @moveFrom = "#{@testFolder}/move source of test-file.txt" + @client.copy @textFile, @moveFrom, (error, stat) -> + expect(error).to.equal null + done() + + afterEach (done) -> + @client.remove @moveFrom, (error, stat) => + return done() unless @moveTo + @client.remove @moveTo, (error, stat) -> done() + + it 'moves a file', (done) -> + @timeout 15 * 1000 # This sequence is slow on the current API server. + @moveTo = "#{@testFolder}/moved test-file.txt" + @client.move @moveFrom, @moveTo, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @moveTo + expect(stat.isFile).to.equal true + @client.readFile @moveTo, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal @textFileData + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @moveTo + @client.readFile @moveFrom, (error, data, stat) -> + expect(error).to.be.ok + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do status codes. + expect(error).to.have.property 'status' + expect(error.status).to.equal 404 + expect(data).to.equal undefined + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.equal undefined + done() + + describe '#remove', -> + beforeEach (done) -> + @newFolder = "#{@testFolder}/folder delete test" + @client.mkdir @newFolder, (error, stat) => + expect(error).to.equal null + done() + + afterEach (done) -> + return done() unless @newFolder + @client.remove @newFolder, (error, stat) -> done() + + it 'deletes a folder', (done) -> + @client.remove @newFolder, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFolder + @client.stat @newFolder, { removed: true }, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.isRemoved).to.equal true + done() + + it 'deletes a folder when called as unlink', (done) -> + @client.unlink @newFolder, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFolder + @client.stat @newFolder, { removed: true }, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.isRemoved).to.equal true + done() + + describe '#revertFile', -> + describe 'on a removed file', -> + beforeEach (done) -> + @timeout 12 * 1000 # This sequence seems to be quite slow. + + @newFile = "#{@testFolder}/file revert test.txt" + @client.copy @textFile, @newFile, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + @versionTag = stat.versionTag + @client.remove @newFile, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + done() + + afterEach (done) -> + return done() unless @newFile + @client.remove @newFile, (error, stat) -> done() + + it 'reverts the file to a previous version', (done) -> + @timeout 12 * 1000 # This sequence seems to be quite slow. + + @client.revertFile @newFile, @versionTag, (error, stat) => + expect(error).to.equal null + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isRemoved).to.equal false + @client.readFile @newFile, (error, data, stat) => + expect(error).to.equal null + expect(data).to.equal @textFileData + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @newFile + expect(stat.isRemoved).to.equal false + done() + + describe '#findByName', -> + it 'locates the test folder given a partial name', (done) -> + namePattern = @testFolder.substring 5 + @client.search '/', namePattern, (error, matches) => + expect(error).to.equal null + expect(matches).to.have.length 1 + expect(matches[0]).to.be.instanceOf Dropbox.Stat + expect(matches[0].path).to.equal @testFolder + expect(matches[0].isFolder).to.equal true + done() + + it 'lists the test folder files given the "test" pattern', (done) -> + @client.search @testFolder, 'test', (error, matches) => + expect(error).to.equal null + expect(matches).to.have.length 2 + done() + + it 'only lists one match when given limit 1', (done) -> + @client.search @testFolder, 'test', limit: 1, (error, matches) => + expect(error).to.equal null + expect(matches).to.have.length 1 + done() + + describe 'with httpCache', -> + beforeEach -> + @xhr = null + @client.onXhr.addListener (xhr) => + @xhr = xhr + + it 'locates the test folder using Authorize headers', (done) -> + namePattern = @testFolder.substring 5 + @client.search '/', namePattern, httpCache: true, (error, matches) => + if Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers + expect(@xhr.url).to.contain 'oauth_nonce' + else + expect(@xhr.headers).to.have.key 'Authorization' + + expect(error).to.equal null + expect(matches).to.have.length 1 + expect(matches[0]).to.be.instanceOf Dropbox.Stat + expect(matches[0].path).to.equal @testFolder + expect(matches[0].isFolder).to.equal true + done() + + describe '#makeUrl', -> + describe 'for a short Web URL', -> + it 'returns a shortened Dropbox URL', (done) -> + @client.makeUrl @textFile, (error, publicUrl) -> + expect(error).to.equal null + expect(publicUrl).to.be.instanceOf Dropbox.PublicUrl + expect(publicUrl.isDirect).to.equal false + expect(publicUrl.url).to.contain '//db.tt/' + done() + + describe 'for a Web URL created with long: true', -> + it 'returns an URL to a preview page', (done) -> + @client.makeUrl @textFile, { long: true }, (error, publicUrl) => + expect(error).to.equal null + expect(publicUrl).to.be.instanceOf Dropbox.PublicUrl + expect(publicUrl.isDirect).to.equal false + expect(publicUrl.url).not.to.contain '//db.tt/' + + # The cont/ents server does not return CORS headers. + return done() unless @nodejs + Dropbox.Xhr.request 'GET', publicUrl.url, {}, null, (error, data) -> + expect(error).to.equal null + expect(data).to.contain '' + done() + + describe 'for a Web URL created with longUrl: true', -> + it 'returns an URL to a preview page', (done) -> + @client.makeUrl @textFile, { longUrl: true }, (error, publicUrl) => + expect(error).to.equal null + expect(publicUrl).to.be.instanceOf Dropbox.PublicUrl + expect(publicUrl.isDirect).to.equal false + expect(publicUrl.url).not.to.contain '//db.tt/' + done() + + describe 'for a direct download URL', -> + it 'gets a direct download URL', (done) -> + @client.makeUrl @textFile, { download: true }, (error, publicUrl) => + expect(error).to.equal null + expect(publicUrl).to.be.instanceOf Dropbox.PublicUrl + expect(publicUrl.isDirect).to.equal true + expect(publicUrl.url).not.to.contain '//db.tt/' + + # The contents server does not return CORS headers. + return done() unless @nodejs + Dropbox.Xhr.request 'GET', publicUrl.url, {}, null, (error, data) => + expect(error).to.equal null + expect(data).to.equal @textFileData + done() + + describe 'for a direct download URL created with downloadHack: true', -> + it 'gets a direct long-lived download URL', (done) -> + @client.makeUrl @textFile, { downloadHack: true }, (error, publicUrl) => + expect(error).to.equal null + expect(publicUrl).to.be.instanceOf Dropbox.PublicUrl + expect(publicUrl.isDirect).to.equal true + expect(publicUrl.url).not.to.contain '//db.tt/' + expect(publicUrl.expiresAt - Date.now()).to.be.above 86400000 + + # The download server does not return CORS headers. + return done() unless @nodejs + Dropbox.Xhr.request 'GET', publicUrl.url, {}, null, (error, data) => + expect(error).to.equal null + expect(data).to.equal @textFileData + done() + + describe '#pullChanges', -> + beforeEach -> + # Pulling an entire Dropbox can take a lot of time, so we need fancy + # logic here. + @timeoutValue = 60 * 1000 + @timeout @timeoutValue + + afterEach (done) -> + @timeoutValue += 10 * 1000 + @timeout @timeoutValue + return done() unless @newFile + @client.remove @newFile, (error, stat) -> done() + + it 'gets a cursor, then it gets relevant changes', (done) -> + @timeout @timeoutValue + + @client.pullChanges (error, changes) => + expect(error).to.equal null + expect(changes).to.be.instanceOf Dropbox.PulledChanges + expect(changes.blankSlate).to.equal true + + # Calls pullChanges until it's done listing the user's Dropbox. + drainEntries = (client, callback) => + return callback() unless changes.shouldPullAgain + @timeoutValue += 10 * 1000 # 10 extra seconds per call + @timeout @timeoutValue + client.pullChanges changes, (error, _changes) -> + expect(error).to.equal null + changes = _changes + drainEntries client, callback + drainEntries @client, => + + @newFile = "#{@testFolder}/delta-test.txt" + newFileData = "This file is used to test the pullChanges method.\n" + @client.writeFile @newFile, newFileData, (error, stat) => + expect(error).to.equal null + expect(stat).to.have.property 'path' + expect(stat.path).to.equal @newFile + + @client.pullChanges changes, (error, changes) => + expect(error).to.equal null + expect(changes).to.be.instanceof Dropbox.PulledChanges + expect(changes.blankSlate).to.equal false + expect(changes.changes).to.have.length.greaterThan 0 + change = changes.changes[changes.changes.length - 1] + expect(change).to.be.instanceOf Dropbox.PullChange + expect(change.path).to.equal @newFile + expect(change.wasRemoved).to.equal false + expect(change.stat.path).to.equal @newFile + done() + + describe '#thumbnailUrl', -> + it 'produces an URL that contains the file name', -> + url = @client.thumbnailUrl @imageFile, { png: true, size: 'medium' } + expect(url).to.contain 'tests' # Fragment of the file name. + expect(url).to.contain 'png' + expect(url).to.contain 'medium' + + describe '#readThumbnail', -> + it 'reads the image into a string', (done) -> + @timeout 12 * 1000 # Thumbnail generation is slow. + @client.readThumbnail @imageFile, { png: true }, (error, data, stat) => + expect(error).to.equal null + expect(data).to.be.a 'string' + expect(data).to.contain 'PNG' + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @imageFile + expect(stat.isFile).to.equal true + done() + + it 'reads the image into a Blob', (done) -> + return done() unless Blob? + @timeout 12 * 1000 # Thumbnail generation is slow. + options = { png: true, blob: true } + @client.readThumbnail @imageFile, options, (error, blob, stat) => + expect(error).to.equal null + expect(blob).to.be.instanceOf Blob + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do headers. + expect(stat).to.be.instanceOf Dropbox.Stat + expect(stat.path).to.equal @imageFile + expect(stat.isFile).to.equal true + reader = new FileReader + reader.onloadend = => + return unless reader.readyState == FileReader.DONE + buffer = reader.result + view = new Uint8Array buffer + length = buffer.byteLength + bytes = (String.fromCharCode view[i] for i in [0...length]). + join('') + expect(bytes).to.contain 'PNG' + done() + reader.readAsArrayBuffer blob + + describe '#reset', -> + beforeEach -> + @authStates = [] + @client.onAuthStateChange.addListener (client) => + @authStates.push client.authState + @client.reset() + + it 'gets the client into the RESET state', -> + expect(@client.authState).to.equal Dropbox.Client.RESET + + it 'removes token and uid information', -> + credentials = @client.credentials() + expect(credentials).not.to.have.property 'token' + expect(credentials).not.to.have.property 'tokenSecret' + expect(credentials).not.to.have.property 'uid' + + it 'triggers onAuthStateChange', -> + expect(@authStates).to.deep.equal [Dropbox.Client.RESET] + + it 'does not trigger onAuthState if already reset', -> + @authStates = [] + @client.reset() + expect(@authStates).to.deep.equal [] + + describe '#credentials', -> + it 'contains all the expected keys when DONE', -> + credentials = @client.credentials() + expect(credentials).to.have.property 'key' + expect(credentials).to.have.property 'sandbox' + expect(credentials).to.have.property 'token' + expect(credentials).to.have.property 'tokenSecret' + expect(credentials).to.have.property 'uid' + + it 'does not return an authState when DONE', -> + credentials = @client.credentials() + expect(credentials).not.to.have.property 'authState' + expect(credentials).not.to.have.property 'secret' + + it 'contains all the expected keys when RESET', -> + @client.reset() + credentials = @client.credentials() + expect(credentials).to.have.property 'key' + expect(credentials).to.have.property 'sandbox' + + it 'does not return an authState when RESET', -> + @client.reset() + credentials = @client.credentials() + expect(credentials).not.to.have.property 'authState' + expect(credentials).not.to.have.property 'secret' + + describe 'for a client with raw keys', -> + beforeEach -> + @client.setCredentials( + key: 'dpf43f3p2l4k3l03', secret: 'kd94hf93k423kf44', + token: 'user-token', tokenSecret: 'user-secret', uid: '1234567') + + it 'contains all the expected keys when DONE', -> + credentials = @client.credentials() + expect(credentials).to.have.property 'key' + expect(credentials).to.have.property 'secret' + expect(credentials).to.have.property 'token' + expect(credentials).to.have.property 'tokenSecret' + expect(credentials).to.have.property 'uid' + + it 'contains all the expected keys when RESET', -> + @client.reset() + credentials = @client.credentials() + expect(credentials).to.have.property 'key' + expect(credentials).to.have.property 'sandbox' + expect(credentials).to.have.property 'secret' + + + describe '#setCredentials', -> + it 'gets the client into the RESET state', -> + @client.setCredentials key: 'app-key', secret: 'app-secret' + expect(@client.authState).to.equal Dropbox.Client.RESET + credentials = @client.credentials() + expect(credentials.key).to.equal 'app-key' + expect(credentials.secret).to.equal 'app-secret' + + it 'gets the client into the REQUEST state', -> + @client.setCredentials( + key: 'app-key', secret: 'app-secret', token: 'user-token', + tokenSecret: 'user-secret', authState: Dropbox.Client.REQUEST) + expect(@client.authState).to.equal Dropbox.Client.REQUEST + credentials = @client.credentials() + expect(credentials.key).to.equal 'app-key' + expect(credentials.secret).to.equal 'app-secret' + expect(credentials.token).to.equal 'user-token' + expect(credentials.tokenSecret).to.equal 'user-secret' + + it 'gets the client into the DONE state', -> + @client.setCredentials( + key: 'app-key', secret: 'app-secret', token: 'user-token', + tokenSecret: 'user-secret', uid: '3141592') + expect(@client.authState).to.equal Dropbox.Client.DONE + credentials = @client.credentials() + expect(credentials.key).to.equal 'app-key' + expect(credentials.secret).to.equal 'app-secret' + expect(credentials.token).to.equal 'user-token' + expect(credentials.tokenSecret).to.equal 'user-secret' + expect(credentials.uid).to.equal '3141592' + + beforeEach -> + @authStates = [] + @client.onAuthStateChange.addListener (client) => + @authStates.push client.authState + + it 'triggers onAuthStateChange when switching from DONE to RESET', -> + @client.setCredentials key: 'app-key', secret: 'app-secret' + expect(@authStates).to.deep.equal [Dropbox.Client.RESET] + + it 'does not trigger onAuthStateChange when not switching', -> + @client.setCredentials key: 'app-key', secret: 'app-secret' + @authStates = [] + @client.setCredentials key: 'app-key', secret: 'app-secret' + expect(@authStates).to.deep.equal [] + + describe '#appHash', -> + it 'is consistent', -> + client = new Dropbox.Client clientKeys + expect(client.appHash()).to.equal @client.appHash() + + it 'is a non-trivial string', -> + expect(@client.appHash()).to.be.a 'string' + expect(@client.appHash().length).to.be.greaterThan 4 + + describe '#isAuthenticated', -> + it 'is true for a client with full tokens', -> + expect(@client.isAuthenticated()).to.equal true + + it 'is false for a freshly reset client', -> + @client.reset() + expect(@client.isAuthenticated()).to.equal false + + describe '#authenticate', -> + it 'fails to move from RESET without an OAuth driver', -> + @client.reset() + @client.authDriver null + expect(=> @client.authenticate(-> + expect('authentication completed').to.equal false + )).to.throw(Error, /authDriver/) + + it 'completes without an OAuth driver if already in DONE', (done) -> + @client.authDriver null + @client.authenticate (error, client) => + expect(error).to.equal null + expect(client).to.equal @client + done() + +describe 'Dropbox.Client', -> + describe 'with full Dropbox access', -> + buildClientTests testFullDropboxKeys + + describe 'with Folder access', -> + buildClientTests testKeys + + describe '#authenticate + #signOut', -> + # NOTE: we're not duplicating this test in the full Dropbox acess suite, + # because it's annoying to the tester + it 'completes the authenticate flow', (done) -> + @timeout 45 * 1000 # Time-consuming because the user must click. + @client.reset() + @client.authDriver authDriver + authStateChanges = ['authorize'] + @client.onAuthStateChange.addListener (client) -> + authStateChanges.push client.authState + @client.authenticate (error, client) => + expect(error).to.equal null + expect(client).to.equal @client + expect(client.authState).to.equal Dropbox.Client.DONE + expect(client.isAuthenticated()).to.equal true + expect(authStateChanges).to.deep.equal(['authorize', + Dropbox.Client.REQUEST, Dropbox.Client.AUTHORIZED, + Dropbox.Client.DONE]) + # Verify that we can do API calls. + client.getUserInfo (error, userInfo) -> + expect(error).to.equal null + expect(userInfo).to.be.instanceOf Dropbox.UserInfo + invalidCredentials = client.credentials() + authStateChanges = ['signOff'] + client.signOut (error) -> + expect(error).to.equal null + expect(client.authState).to.equal Dropbox.Client.SIGNED_OFF + expect(client.isAuthenticated()).to.equal false + expect(authStateChanges).to.deep.equal(['signOff', + Dropbox.Client.SIGNED_OFF]) + # Verify that we can't use the old token in API calls. + invalidClient = new Dropbox.Client invalidCredentials + invalidClient.getUserInfo (error, userInfo) -> + expect(error).to.be.ok + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do error codes. + expect(error.status).to.equal 401 + # Verify that the same client can be used for a 2nd signin. + authStateChanges = ['authorize2'] + client.authenticate (error, client) -> + expect(error).to.equal null + expect(client.authState).to.equal Dropbox.Client.DONE + expect(client.isAuthenticated()).to.equal true + expect(authStateChanges).to.deep.equal(['authorize2', + Dropbox.Client.RESET, Dropbox.Client.REQUEST, + Dropbox.Client.AUTHORIZED, Dropbox.Client.DONE]) + # Verify that we can do API calls after the 2nd signin. + client.getUserInfo (error, userInfo) -> + expect(error).to.equal null + expect(userInfo).to.be.instanceOf Dropbox.UserInfo + done() + + + describe '#appHash', -> + it 'depends on the app key', -> + client = new Dropbox.Client testFullDropboxKeys + expect(client.appHash()).not.to.equal @client.appHash() + + describe '#constructor', -> + it 'raises an Error if initialized without an API key / secret', -> + expect(-> new Dropbox.Client(token: '123', tokenSecret: '456')).to. + throw(Error, /no api key/i) + diff --git a/lib/client/storage/dropbox/test/src/drivers_test.coffee b/lib/client/storage/dropbox/test/src/drivers_test.coffee new file mode 100644 index 00000000..d35e8485 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/drivers_test.coffee @@ -0,0 +1,446 @@ +describe 'Dropbox.Drivers.BrowserBase', -> + beforeEach -> + @node_js = module? and module?.exports? and require? + @chrome_app = chrome? and (chrome.extension or chrome.app) + @client = new Dropbox.Client testKeys + + describe 'with rememberUser: false', -> + beforeEach (done) -> + return done() if @node_js or @chrome_app + @driver = new Dropbox.Drivers.BrowserBase + @driver.setStorageKey @client + @driver.forgetCredentials done + + afterEach (done) -> + return done() if @node_js or @chrome_app + @driver.forgetCredentials done + + describe '#loadCredentials', -> + it 'produces the credentials passed to storeCredentials', (done) -> + return done() if @node_js or @chrome_app + goldCredentials = @client.credentials() + @driver.storeCredentials goldCredentials, => + @driver.loadCredentials (credentials) -> + expect(credentials).to.deep.equal goldCredentials + done() + + it 'produces null after forgetCredentials was called', (done) -> + return done() if @node_js or @chrome_app + @driver.storeCredentials @client.credentials(), => + @driver.forgetCredentials => + @driver.loadCredentials (credentials) -> + expect(credentials).to.equal null + done() + + it 'produces null if a different scope is provided', (done) -> + return done() if @node_js or @chrome_app + @driver.setStorageKey @client + @driver.storeCredentials @client.credentials(), => + @driver.forgetCredentials => + @driver.loadCredentials (credentials) -> + expect(credentials).to.equal null + done() + + +describe 'Dropbox.Drivers.Redirect', -> + describe '#url', -> + beforeEach -> + @stub = sinon.stub Dropbox.Drivers.BrowserBase, 'currentLocation' + afterEach -> + @stub.restore() + + it 'adds a query string to a static URL', -> + @stub.returns 'http://test/file' + driver = new Dropbox.Drivers.Redirect useQuery: true + expect(driver.url()).to. + equal 'http://test/file?_dropboxjs_scope=default' + + it 'adds a fragment to a static URL', -> + @stub.returns 'http://test/file' + driver = new Dropbox.Drivers.Redirect + expect(driver.url()).to. + equal 'http://test/file#?_dropboxjs_scope=default' + + it 'adds a query param to a URL with a query string', -> + @stub.returns 'http://test/file?a=true' + driver = new Dropbox.Drivers.Redirect useQuery: true + expect(driver.url()).to. + equal 'http://test/file?a=true&_dropboxjs_scope=default' + + it 'adds a fragment to a URL with a query string', -> + @stub.returns 'http://test/file?a=true' + driver = new Dropbox.Drivers.Redirect + expect(driver.url()).to. + equal 'http://test/file?a=true#?_dropboxjs_scope=default' + + it 'adds a query string to a static URL with a fragment', -> + @stub.returns 'http://test/file#frag' + driver = new Dropbox.Drivers.Redirect useQuery: true + expect(driver.url()).to. + equal 'http://test/file?_dropboxjs_scope=default#frag' + + it 'replaces the fragment in a static URL with a fragment', -> + @stub.returns 'http://test/file#frag' + driver = new Dropbox.Drivers.Redirect + expect(driver.url()).to. + equal 'http://test/file#?_dropboxjs_scope=default' + + it 'adds a query param to a URL with a query string and fragment', -> + @stub.returns 'http://test/file?a=true#frag' + driver = new Dropbox.Drivers.Redirect useQuery: true + expect(driver.url()).to. + equal 'http://test/file?a=true&_dropboxjs_scope=default#frag' + + it 'replaces the fragment in a URL with a query string and fragment', -> + @stub.returns 'http://test/file?a=true#frag' + driver = new Dropbox.Drivers.Redirect + expect(driver.url()).to. + equal 'http://test/file?a=true#?_dropboxjs_scope=default' + + it 'obeys the scope option', -> + @stub.returns 'http://test/file' + driver = new Dropbox.Drivers.Redirect( + scope: 'not default', useQuery: true) + expect(driver.url()).to. + equal 'http://test/file?_dropboxjs_scope=not%20default' + + it 'obeys the scope option when adding a fragment', -> + @stub.returns 'http://test/file' + driver = new Dropbox.Drivers.Redirect scope: 'not default' + expect(driver.url()).to. + equal 'http://test/file#?_dropboxjs_scope=not%20default' + + describe '#locationToken', -> + beforeEach -> + @stub = sinon.stub Dropbox.Drivers.BrowserBase, 'currentLocation' + afterEach -> + @stub.restore() + + it 'returns null if the location does not contain the arg', -> + @stub.returns 'http://test/file?_dropboxjs_scope=default& ' + + 'another_token=ab%20cd&oauth_tok=en' + driver = new Dropbox.Drivers.Redirect + expect(driver.locationToken()).to.equal null + + it 'returns null if the location fragment does not contain the arg', -> + @stub.returns 'http://test/file#?_dropboxjs_scope=default& ' + + 'another_token=ab%20cd&oauth_tok=en' + driver = new Dropbox.Drivers.Redirect + expect(driver.locationToken()).to.equal null + + it "extracts the token successfully with default scope", -> + @stub.returns 'http://test/file?_dropboxjs_scope=default&' + + 'oauth_token=ab%20cd&other_param=true' + driver = new Dropbox.Drivers.Redirect + expect(driver.locationToken()).to.equal 'ab cd' + + it "extracts the token successfully with set scope", -> + @stub.returns 'http://test/file?_dropboxjs_scope=not%20default&' + + 'oauth_token=ab%20cd' + driver = new Dropbox.Drivers.Redirect scope: 'not default' + expect(driver.locationToken()).to.equal 'ab cd' + + it "extracts the token from fragment with default scope", -> + @stub.returns 'http://test/file#?_dropboxjs_scope=default&' + + 'oauth_token=ab%20cd&other_param=true' + driver = new Dropbox.Drivers.Redirect + expect(driver.locationToken()).to.equal 'ab cd' + + it "extracts the token from fragment with set scope", -> + @stub.returns 'http://test/file#?_dropboxjs_scope=not%20default&' + + 'oauth_token=ab%20cd' + driver = new Dropbox.Drivers.Redirect scope: 'not default' + expect(driver.locationToken()).to.equal 'ab cd' + + it "returns null if the location scope doesn't match", -> + @stub.returns 'http://test/file?_dropboxjs_scope=defaultx&oauth_token=ab' + driver = new Dropbox.Drivers.Redirect + expect(driver.locationToken()).to.equal null + + it "returns null if the location fragment scope doesn't match", -> + @stub.returns 'http://test/file#?_dropboxjs_scope=defaultx&oauth_token=a' + driver = new Dropbox.Drivers.Redirect + expect(driver.locationToken()).to.equal null + + describe '#loadCredentials', -> + beforeEach -> + @node_js = module? and module.exports? and require? + @chrome_app = chrome? and (chrome.extension or chrome.app?.runtime) + return if @node_js or @chrome_app + @client = new Dropbox.Client testKeys + @driver = new Dropbox.Drivers.Redirect scope: 'some_scope' + @driver.setStorageKey @client + + it 'produces the credentials passed to storeCredentials', (done) -> + return done() if @node_js or @chrome_app + goldCredentials = @client.credentials() + @driver.storeCredentials goldCredentials, => + @driver = new Dropbox.Drivers.Redirect scope: 'some_scope' + @driver.setStorageKey @client + @driver.loadCredentials (credentials) -> + expect(credentials).to.deep.equal goldCredentials + done() + + it 'produces null after forgetCredentials was called', (done) -> + return done() if @node_js or @chrome_app + @driver.storeCredentials @client.credentials(), => + @driver.forgetCredentials => + @driver = new Dropbox.Drivers.Redirect scope: 'some_scope' + @driver.setStorageKey @client + @driver.loadCredentials (credentials) -> + expect(credentials).to.equal null + done() + + it 'produces null if a different scope is provided', (done) -> + return done() if @node_js or @chrome_app + @driver.setStorageKey @client + @driver.storeCredentials @client.credentials(), => + @driver = new Dropbox.Drivers.Redirect scope: 'other_scope' + @driver.setStorageKey @client + @driver.loadCredentials (credentials) -> + expect(credentials).to.equal null + done() + + describe 'integration', -> + beforeEach -> + @node_js = module? and module.exports? and require? + @chrome_app = chrome? and (chrome.extension or chrome.app?.runtime) + + it 'should work', (done) -> + return done() if @node_js or @chrome_app + @timeout 30 * 1000 # Time-consuming because the user must click. + + listener = (event) -> + expect(event.data).to.match(/^\[.*\]$/) + [error, credentials] = JSON.parse event.data + expect(error).to.equal null + expect(credentials).to.have.property 'uid' + expect(credentials.uid).to.be.a 'string' + window.removeEventListener 'message', listener + done() + + window.addEventListener 'message', listener + (new Dropbox.Drivers.Popup()).openWindow( + '/test/html/redirect_driver_test.html') + +describe 'Dropbox.Drivers.Popup', -> + describe '#url', -> + beforeEach -> + @stub = sinon.stub Dropbox.Drivers.BrowserBase, 'currentLocation' + @stub.returns 'http://test:123/a/path/file.htmx' + + afterEach -> + @stub.restore() + + it 'reflects the current page when there are no options', -> + driver = new Dropbox.Drivers.Popup + expect(driver.url()).to.equal 'http://test:123/a/path/file.htmx' + + it 'replaces the current file correctly', -> + driver = new Dropbox.Drivers.Popup receiverFile: 'another.file' + expect(driver.url()).to.equal 'http://test:123/a/path/another.file#' + + it 'replaces the current file without a fragment correctly', -> + driver = new Dropbox.Drivers.Popup + receiverFile: 'another.file', noFragment: true + expect(driver.url()).to.equal 'http://test:123/a/path/another.file' + + it 'replaces an entire URL without a fragment correctly', -> + driver = new Dropbox.Drivers.Popup + receiverUrl: 'https://something.com/filez' + expect(driver.url()).to.equal 'https://something.com/filez#' + + it 'replaces an entire URL with a fragment correctly', -> + driver = new Dropbox.Drivers.Popup + receiverUrl: 'https://something.com/filez#frag' + expect(driver.url()).to.equal 'https://something.com/filez#frag' + + it 'replaces an entire URL without a fragment and useQuery correctly', -> + driver = new Dropbox.Drivers.Popup + receiverUrl: 'https://something.com/filez', noFragment: true + expect(driver.url()).to.equal 'https://something.com/filez' + + describe '#loadCredentials', -> + beforeEach -> + @node_js = module? and module.exports? and require? + @chrome_app = chrome? and (chrome.extension or chrome.app?.runtime) + return if @node_js or @chrome_app + @client = new Dropbox.Client testKeys + @driver = new Dropbox.Drivers.Popup scope: 'some_scope' + @driver.setStorageKey @client + + it 'produces the credentials passed to storeCredentials', (done) -> + return done() if @node_js or @chrome_app + goldCredentials = @client.credentials() + @driver.storeCredentials goldCredentials, => + @driver = new Dropbox.Drivers.Popup scope: 'some_scope' + @driver.setStorageKey @client + @driver.loadCredentials (credentials) -> + expect(credentials).to.deep.equal goldCredentials + done() + + it 'produces null after forgetCredentials was called', (done) -> + return done() if @node_js or @chrome_app + @driver.storeCredentials @client.credentials(), => + @driver.forgetCredentials => + @driver = new Dropbox.Drivers.Popup scope: 'some_scope' + @driver.setStorageKey @client + @driver.loadCredentials (credentials) -> + expect(credentials).to.equal null + done() + + it 'produces null if a different scope is provided', (done) -> + return done() if @node_js or @chrome_app + @driver.setStorageKey @client + @driver.storeCredentials @client.credentials(), => + @driver = new Dropbox.Drivers.Popup scope: 'other_scope' + @driver.setStorageKey @client + @driver.loadCredentials (credentials) -> + expect(credentials).to.equal null + done() + + describe 'integration', -> + beforeEach -> + @node_js = module? and module.exports? and require? + @chrome_app = chrome? and (chrome.extension or chrome.app?.runtime) + + it 'should work with a query string', (done) -> + return done() if @node_js or @chrome_app + @timeout 45 * 1000 # Time-consuming because the user must click. + + client = new Dropbox.Client testKeys + client.reset() + authDriver = new Dropbox.Drivers.Popup + receiverFile: 'oauth_receiver.html', noFragment: true, + scope: 'popup-integration', rememberUser: false + client.authDriver authDriver + client.authenticate (error, client) => + expect(error).to.equal null + expect(client.authState).to.equal Dropbox.Client.DONE + # Verify that we can do API calls. + client.getUserInfo (error, userInfo) -> + expect(error).to.equal null + expect(userInfo).to.be.instanceOf Dropbox.UserInfo + + # Follow-up authenticate() should restart the process. + client.reset() + authDriver.doAuthorize = (authUrl, token, tokenSecret, callback) -> + client.reset() + done() + client.authenticate -> + assert false, 'The second authenticate() should not complete.' + + it 'should work with a URL fragment and rememberUser: true', (done) -> + return done() if @node_js or @chrome_app + @timeout 45 * 1000 # Time-consuming because the user must click. + + client = new Dropbox.Client testKeys + client.reset() + authDriver = new Dropbox.Drivers.Popup + receiverFile: 'oauth_receiver.html', noFragment: false, + scope: 'popup-integration', rememberUser: true + client.authDriver authDriver + authDriver.setStorageKey client + authDriver.forgetCredentials -> + client.authenticate (error, client) -> + expect(error).to.equal null + expect(client.authState).to.equal Dropbox.Client.DONE + # Verify that we can do API calls. + client.getUserInfo (error, userInfo) -> + expect(error).to.equal null + expect(userInfo).to.be.instanceOf Dropbox.UserInfo + + # Follow-up authenticate() should use stored credentials. + client.reset() + authDriver.doAuthorize = (authUrl, token, tokenSecret, callback) -> + assert false, + 'Stored credentials not used in second authenticate()' + client.authenticate (error, client) -> + # Verify that we can do API calls. + client.getUserInfo (error, userInfo) -> + expect(error).to.equal null + expect(userInfo).to.be.instanceOf Dropbox.UserInfo + done() + +describe 'Dropbox.Drivers.Chrome', -> + beforeEach -> + @chrome_app = chrome? and (chrome.extension or chrome.app?.runtime) + @client = new Dropbox.Client testKeys + + describe '#url', -> + beforeEach -> + return unless @chrome_app + @path = 'test/html/redirect_driver_test.html' + @driver = new Dropbox.Drivers.Chrome receiverPath: @path + + it 'produces a chrome-extension:// url', -> + return unless @chrome_app + expect(@driver.url()).to.match(/^chrome-extension:\/\//) + + it 'produces an URL ending in redirectPath', -> + return unless @chrome_app + url = @driver.url() + expect(url.substring(url.length - @path.length)).to.equal @path + + describe '#loadCredentials', -> + beforeEach -> + return unless @chrome_app + @client = new Dropbox.Client testKeys + @driver = new Dropbox.Drivers.Chrome scope: 'some_scope' + + it 'produces the credentials passed to storeCredentials', (done) -> + return done() unless @chrome_app + goldCredentials = @client.credentials() + @driver.storeCredentials goldCredentials, => + @driver = new Dropbox.Drivers.Chrome scope: 'some_scope' + @driver.loadCredentials (credentials) -> + expect(credentials).to.deep.equal goldCredentials + done() + + it 'produces null after forgetCredentials was called', (done) -> + return done() unless @chrome_app + @driver.storeCredentials @client.credentials(), => + @driver.forgetCredentials => + @driver = new Dropbox.Drivers.Chrome scope: 'some_scope' + @driver.loadCredentials (credentials) -> + expect(credentials).to.equal null + done() + + it 'produces null if a different scope is provided', (done) -> + return done() unless @chrome_app + @driver.storeCredentials @client.credentials(), => + @driver = new Dropbox.Drivers.Chrome scope: 'other_scope' + @driver.loadCredentials (credentials) -> + expect(credentials).to.equal null + done() + + describe 'integration', -> + it 'should work', (done) -> + return done() unless @chrome_app + @timeout 45 * 1000 # Time-consuming because the user must click. + + client = new Dropbox.Client testKeys + client.reset() + authDriver = new Dropbox.Drivers.Chrome( + receiverPath: 'test/html/chrome_oauth_receiver.html', + scope: 'chrome_integration') + client.authDriver authDriver + authDriver.forgetCredentials -> + client.authenticate (error, client) -> + expect(error).to.equal null + expect(client.authState).to.equal Dropbox.Client.DONE + # Verify that we can do API calls. + client.getUserInfo (error, userInfo) -> + expect(error).to.equal null + expect(userInfo).to.be.instanceOf Dropbox.UserInfo + # Follow-up authenticate() should use stored credentials. + client.reset() + authDriver.doAuthorize = (authUrl, token, tokenSecret, callback) -> + assert false, + 'Stored credentials not used in second authenticate()' + client.authenticate (error, client) -> + # Verify that we can do API calls. + client.getUserInfo (error, userInfo) -> + expect(error).to.equal null + expect(userInfo).to.be.instanceOf Dropbox.UserInfo + done() diff --git a/lib/client/storage/dropbox/test/src/event_source_test.coffee b/lib/client/storage/dropbox/test/src/event_source_test.coffee new file mode 100644 index 00000000..e7440b0f --- /dev/null +++ b/lib/client/storage/dropbox/test/src/event_source_test.coffee @@ -0,0 +1,144 @@ +describe 'Dropbox.EventSource', -> + beforeEach -> + @source = new Dropbox.EventSource + @cancelable = new Dropbox.EventSource cancelable: true + + # 3 listeners, 1 and 2 are already hooked up + @event1 = null + @return1 = true + @listener1 = (event) => + @event1 = event + @return1 + @source.addListener @listener1 + @cancelable.addListener @listener1 + @event2 = null + @return2 = false + @listener2 = (event) => + @event2 = event + @return2 + @source.addListener @listener2 + @cancelable.addListener @listener2 + @event3 = null + @return3 = true + @listener3 = (event) => + @event3 = event + @return3 + + describe '#addListener', -> + it 'adds a new listener', -> + @source.addListener @listener3 + expect(@source._listeners).to.deep. + equal [@listener1, @listener2, @listener3] + + it 'does not add an existing listener', -> + @source.addListener @listener2 + expect(@source._listeners).to.deep.equal [@listener1, @listener2] + + it 'is idempotent', -> + @source.addListener @listener3 + @source.addListener @listener3 + expect(@source._listeners).to.deep. + equal [@listener1, @listener2, @listener3] + + it 'refuses to add non-functions', -> + expect(=> @source.addListener 42).to.throw(TypeError, /listener type/) + + describe '#removeListener', -> + it 'does nothing for a non-existing listener', -> + @source.removeListener @listener3 + expect(@source._listeners).to.deep.equal [@listener1, @listener2] + + it 'removes a listener at the end of the queue', -> + @source.removeListener @listener2 + expect(@source._listeners).to.deep.equal [@listener1] + + it 'removes a listener at the beginning of the queue', -> + @source.removeListener @listener1 + expect(@source._listeners).to.deep.equal [@listener2] + + it 'removes a listener at the middle of the queue', -> + @source.addListener @listener3 + @source.removeListener @listener2 + expect(@source._listeners).to.deep.equal [@listener1, @listener3] + + it 'removes all the listeners', -> + @source.removeListener @listener1 + @source.removeListener @listener2 + expect(@source._listeners).to.deep.equal [] + + describe 'without ES5 Array#indexOf', -> + beforeEach -> + @source._listeners.indexOf = null + + afterEach -> + delete @source._listeners.indexOf + + assertArraysEqual = (array1, array2) -> + expect(array1.length).to.equal(array2.length) + for i in [0...array1.length] + expect(array1[i]).to.equal(array2[i]) + + it 'does nothing for a non-existing listener', -> + @source.removeListener @listener3 + assertArraysEqual @source._listeners, [@listener1, @listener2] + + it 'removes a listener at the end of the queue', -> + @source.removeListener @listener2 + assertArraysEqual @source._listeners, [@listener1] + + it 'removes a listener at the beginning of the queue', -> + @source.removeListener @listener1 + assertArraysEqual @source._listeners, [@listener2] + + it 'removes a listener at the middle of the queue', -> + @source.addListener @listener3 + @source.removeListener @listener2 + assertArraysEqual @source._listeners, [@listener1, @listener3] + + it 'removes all the listeners', -> + @source.removeListener @listener1 + @source.removeListener @listener2 + assertArraysEqual @source._listeners, [] + + describe '#dispatch', -> + beforeEach -> + @event = { answer: 42 } + + it 'passes event to all listeners', -> + @source.dispatch @event + expect(@event1).to.equal @event + expect(@event2).to.equal @event + expect(@event3).to.equal null + + describe 'on non-cancelable events', -> + beforeEach -> + @source.addListener @listener3 + @returnValue = @source.dispatch @event + + it 'calls all the listeners', -> + expect(@event1).to.equal @event + expect(@event2).to.equal @event + expect(@event3).to.equal @event + + it 'ignores the listener return values', -> + expect(@returnValue).to.equal true + + describe 'on cancelable events', -> + beforeEach -> + @cancelable.addListener @listener3 + @returnValue = @cancelable.dispatch @event + + it 'stops calling listeners after cancelation', -> + expect(@event1).to.equal @event + expect(@event2).to.equal @event + expect(@event3).to.equal null + + it 'reports cancelation', -> + expect(@returnValue).to.equal false + + it 'calls all listeners if no cancelation occurs', -> + @return2 = true + @returnValue = @cancelable.dispatch @event + + expect(@returnValue).to.equal true + expect(@event3).to.equal @event diff --git a/lib/client/storage/dropbox/test/src/helper.coffee b/lib/client/storage/dropbox/test/src/helper.coffee new file mode 100644 index 00000000..2b42b86a --- /dev/null +++ b/lib/client/storage/dropbox/test/src/helper.coffee @@ -0,0 +1,55 @@ +if global? and require? and module? + # Node.JS + exports = global + + exports.Dropbox = require '../../lib/dropbox' + exports.chai = require 'chai' + exports.sinon = require 'sinon' + exports.sinonChai = require 'sinon-chai' + + exports.authDriver = new Dropbox.Drivers.NodeServer port: 8912 + + TokenStash = require './token_stash.js' + (new TokenStash()).get (credentials) -> + exports.testKeys = credentials + (new TokenStash(fullDropbox: true)).get (credentials) -> + exports.testFullDropboxKeys = credentials + + testIconPath = './test/binary/dropbox.png' + fs = require 'fs' + buffer = fs.readFileSync testIconPath + bytes = [] + for i in [0...buffer.length] + bytes.push String.fromCharCode(buffer.readUInt8(i)) + exports.testImageBytes = bytes.join '' + exports.testImageUrl = 'http://localhost:8913/favicon.ico' + imageServer = null + exports.testImageServerOn = -> + imageServer = + new Dropbox.Drivers.NodeServer port: 8913, favicon: testIconPath + exports.testImageServerOff = -> + imageServer.closeServer() + imageServer = null +else + if chrome? and chrome.runtime + # Chrome app + exports = window + exports.authDriver = new Dropbox.Drivers.Chrome( + receiverPath: 'test/html/chrome_oauth_receiver.html', + scope: 'helper-chrome') + # Hack-implement "rememberUser: false" in the Chrome driver. + exports.authDriver.storeCredentials = (credentials, callback) -> callback() + exports.authDriver.loadCredentials = (callback) -> callback null + else + # Browser + exports = window + exports.authDriver = new Dropbox.Drivers.Popup( + receiverFile: 'oauth_receiver.html', scope: 'helper-popup') + + exports.testImageUrl = '/test/binary/dropbox.png' + exports.testImageServerOn = -> null + exports.testImageServerOff = -> null + +# Shared setup. +exports.assert = exports.chai.assert +exports.expect = exports.chai.expect diff --git a/lib/client/storage/dropbox/test/src/hmac_test.coffee b/lib/client/storage/dropbox/test/src/hmac_test.coffee new file mode 100644 index 00000000..b8fbafce --- /dev/null +++ b/lib/client/storage/dropbox/test/src/hmac_test.coffee @@ -0,0 +1,22 @@ +describe 'Dropbox.hmac', -> + it 'works for an empty message with an empty key', -> + expect(Dropbox.hmac('', '')).to.equal '+9sdGxiqbAgyS31ktx+3Y3BpDh0=' + + it 'works for the non-empty Wikipedia example', -> + expect(Dropbox.hmac('The quick brown fox jumps over the lazy dog', 'key')). + to.equal '3nybhbi3iqa8ino29wqQcBydtNk=' + + it 'works for the Oauth example', -> + key = 'kd94hf93k423kf44&pfkkdhi9sl3r4s00' + string = 'GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal' + expect(Dropbox.hmac(string, key)).to.equal 'tR3+Ty81lMeYAr/Fid0kMTYa/WM=' + +describe 'Dropbox.sha1', -> + it 'works for an empty message', -> + expect(Dropbox.sha1('')).to.equal '2jmj7l5rSw0yVb/vlWAYkK/YBwk=' + it 'works for the FIPS-180 Appendix A sample', -> + expect(Dropbox.sha1('abc')).to.equal 'qZk+NkcGgWq6PiVxeFDCbJzQ2J0=' + it 'works for the FIPS-180 Appendix B sample', -> + string = 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq' + expect(Dropbox.sha1(string)).to.equal 'hJg+RBw70m66rkqh+VEp5eVGcPE=' + diff --git a/lib/client/storage/dropbox/test/src/oauth_test.coffee b/lib/client/storage/dropbox/test/src/oauth_test.coffee new file mode 100644 index 00000000..25dbc562 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/oauth_test.coffee @@ -0,0 +1,124 @@ +describe 'Dropbox.Oauth', -> + beforeEach -> + @oauth = new Dropbox.Oauth + key: 'dpf43f3p2l4k3l03', + secret: 'kd94hf93k423kf44' + @oauth.setToken 'nnch734d00sl2jdk', 'pfkkdhi9sl3r4s00' + + # The example in OAuth 1.0a Appendix A. + @request = + method: 'GET', + url: 'http://photos.example.net/photos' + params: + file: 'vacation.jpg', + size: 'original' + @dateStub = sinon.stub Date, 'now' + @dateStub.returns 1191242096999 + + afterEach -> + @dateStub.restore() + + describe '#boilerplateParams', -> + it 'issues unique nonces', -> + nonces = {} + for i in [1..100] + nonce = @oauth.boilerplateParams({}).oauth_nonce + expect(nonces).not.to.have.property nonce + nonces[nonce] = true + + it 'fills all the arguments', -> + params = @oauth.boilerplateParams(@request.params) + properties = ['oauth_consumer_key', 'oauth_nonce', + 'oauth_signature_method', 'oauth_timestamp', + 'oauth_version'] + for property in properties + expect(params).to.have.property property + + describe '#signature', -> + it 'works for the OAuth 1.0a example', -> + @nonceStub = sinon.stub @oauth, 'nonce' + @nonceStub.returns 'kllo9940pd9333jh' + + @oauth.boilerplateParams(@request.params) + expect(@oauth.signature(@request.method, @request.url, @request.params)). + to.equal 'tR3+Ty81lMeYAr/Fid0kMTYa/WM=' + + @nonceStub.restore() + + it 'works with an encoded key', -> + @oauth = new Dropbox.Oauth + key: Dropbox.encodeKey(@oauth.key, @oauth.secret), + token: @oauth.token, tokenSecret: @oauth.tokenSecret + + @nonceStub = sinon.stub @oauth, 'nonce' + @nonceStub.returns 'kllo9940pd9333jh' + + @oauth.boilerplateParams(@request.params) + expect(@oauth.signature(@request.method, @request.url, @request.params)). + to.equal 'tR3+Ty81lMeYAr/Fid0kMTYa/WM=' + + @nonceStub.restore() + + describe '#addAuthParams', -> + it 'matches the OAuth 1.0a example', -> + @nonceStub = sinon.stub @oauth, 'nonce' + @nonceStub.returns 'kllo9940pd9333jh' + + goldenParams = + file: 'vacation.jpg' + oauth_consumer_key: 'dpf43f3p2l4k3l03' + oauth_nonce: 'kllo9940pd9333jh' + oauth_signature: 'tR3+Ty81lMeYAr/Fid0kMTYa/WM=' + oauth_signature_method: 'HMAC-SHA1' + oauth_timestamp: '1191242096' + oauth_token: 'nnch734d00sl2jdk' + oauth_version: '1.0' + size: 'original' + + @oauth.addAuthParams @request.method, @request.url, @request.params + expect(Dropbox.Xhr.urlEncode(@request.params)).to. + eql Dropbox.Xhr.urlEncode(goldenParams) + + @nonceStub.restore() + + it "doesn't leave any OAuth-related value in params", -> + @oauth.authHeader(@request.method, @request.url, @request.params) + expect(Dropbox.Xhr.urlEncode(@request.params)).to. + equal "file=vacation.jpg&size=original" + + describe '#authHeader', -> + it 'matches the OAuth 1.0a example', -> + @nonceStub = sinon.stub @oauth, 'nonce' + @nonceStub.returns 'kllo9940pd9333jh' + + goldenHeader = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03",oauth_nonce="kllo9940pd9333jh",oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1191242096",oauth_token="nnch734d00sl2jdk",oauth_version="1.0"' + header = @oauth.authHeader @request.method, @request.url, @request.params + expect(header).to.equal goldenHeader + + @nonceStub.restore() + + it "doesn't leave any OAuth-related value in params", -> + @oauth.authHeader(@request.method, @request.url, @request.params) + expect(Dropbox.Xhr.urlEncode(@request.params)).to. + equal "file=vacation.jpg&size=original" + + describe '#appHash', -> + it 'is a non-trivial string', -> + expect(@oauth.appHash()).to.be.a 'string' + expect(@oauth.appHash().length).to.be.greaterThan 4 + + it 'is consistent', -> + oauth = new Dropbox.Oauth key: @oauth.key, secret: @oauth.secret + expect(oauth.appHash()).to.equal @oauth.appHash() + + it 'depends on the app key', -> + oauth = new Dropbox.Oauth key: @oauth.key + '0', secret: @oauth.secret + expect(oauth.appHash()).not.to.equal @oauth.appHash() + expect(oauth.appHash()).to.be.a 'string' + expect(oauth.appHash().length).to.be.greaterThan 4 + + describe '#constructor', -> + it 'raises an Error if initialized without an API key / secret', -> + expect(-> new Dropbox.Oauth(token: '123', tokenSecret: '456')).to. + throw(Error, /no api key/i) + diff --git a/lib/client/storage/dropbox/test/src/pulled_changes_test.coffee b/lib/client/storage/dropbox/test/src/pulled_changes_test.coffee new file mode 100644 index 00000000..4d2fe67d --- /dev/null +++ b/lib/client/storage/dropbox/test/src/pulled_changes_test.coffee @@ -0,0 +1,128 @@ +describe 'Dropbox.PulledChanges', -> + describe '.parse', -> + describe 'on a sample response', -> + beforeEach -> + deltaInfo = { + "reset": false, + "cursor": "nTZYLOcTQnyB7-Wc72M-kEAcBQdk2EjLaJIRupQWgDXmRwKWzuG5V4se2mvU7yzXn4cZSJltoW4tpbqgy0Ezxh1b1p3ygp7wy-vdaYJusujnLAyEsKdYCHPZYZdZt7sQG0BopF2ufAuD56ijYbdX5DhMKe85MFqncnFDvNxSjsodEw-IkCfNZmagDmpOZCxmLqu71hLTApwhqO9-dhm-fk6KSYs-OZwRmVwOE2JAnJbWuifNiM8KwMz5sRBZ5FMJPDqXpOW5PqPCwbkAmKQACbNXFi0k1JuxulpDlQh3zMr3lyLMs-fmaDTTU355mY5xSAXK05Zgs5rPJ6lcaBOUmEBSXcPhxFDHk5NmAdA03Shq04t2_4bupzWX-txT84FmOLNncchl7ZDBCMwyrAzD2kCYOTu1_lhui0C-fiCZgZBKU4OyP6qrkdo4gZu3", + "has_more": true, + "entries": [ + [ + "/Getting_Started.pdf", + { + "size": "225.4KB", + "rev": "35e97029684fe", + "thumb_exists": true, # Changed to test hasThumbnail=true code. + "bytes": 230783, + "modified": "Tue, 19 Jul 2011 21:55:38 +0000", + "client_mtime": "Mon, 18 Jul 2011 18:04:35 +0000", + "path": "/Getting_Started.pdf", + "is_dir": false, + "icon": "page_white_acrobat", + "root": "app_folder", # Changed to test app_folder code path. + "mime_type": "application/pdf", + "revision": 220823 + } + ], + [ + "/Public", + null + ] + ] + } + @changes = Dropbox.PulledChanges.parse deltaInfo + + it 'parses blankSlate correctly', -> + expect(@changes).to.have.property 'blankSlate' + expect(@changes.blankSlate).to.equal false + + it 'parses cursorTag correctly', -> + expect(@changes).to.have.property 'cursorTag' + expect(@changes.cursorTag).to.equal 'nTZYLOcTQnyB7-Wc72M-kEAcBQdk2EjLaJIRupQWgDXmRwKWzuG5V4se2mvU7yzXn4cZSJltoW4tpbqgy0Ezxh1b1p3ygp7wy-vdaYJusujnLAyEsKdYCHPZYZdZt7sQG0BopF2ufAuD56ijYbdX5DhMKe85MFqncnFDvNxSjsodEw-IkCfNZmagDmpOZCxmLqu71hLTApwhqO9-dhm-fk6KSYs-OZwRmVwOE2JAnJbWuifNiM8KwMz5sRBZ5FMJPDqXpOW5PqPCwbkAmKQACbNXFi0k1JuxulpDlQh3zMr3lyLMs-fmaDTTU355mY5xSAXK05Zgs5rPJ6lcaBOUmEBSXcPhxFDHk5NmAdA03Shq04t2_4bupzWX-txT84FmOLNncchl7ZDBCMwyrAzD2kCYOTu1_lhui0C-fiCZgZBKU4OyP6qrkdo4gZu3' + + it 'parses shouldPullAgain correctly', -> + expect(@changes).to.have.property 'shouldPullAgain' + expect(@changes.shouldPullAgain).to.equal true + + it 'parses shouldBackOff correctly', -> + expect(@changes).to.have.property 'shouldBackOff' + expect(@changes.shouldBackOff).to.equal false + + it 'parses changes correctly', -> + expect(@changes).to.have.property 'changes' + expect(@changes.changes).to.have.length 2 + expect(@changes.changes[0]).to.be.instanceOf Dropbox.PullChange + expect(@changes.changes[0].path).to.equal '/Getting_Started.pdf' + expect(@changes.changes[1]).to.be.instanceOf Dropbox.PullChange + expect(@changes.changes[1].path).to.equal '/Public' + + it 'passes null through', -> + expect(Dropbox.PulledChanges.parse(null)).to.equal null + + it 'passes undefined through', -> + expect(Dropbox.PulledChanges.parse(undefined)).to.equal undefined + + +describe 'Dropbox.PullChange', -> + describe '.parse', -> + describe 'on a modification change', -> + beforeEach -> + entry = [ + "/Getting_Started.pdf", + { + "size": "225.4KB", + "rev": "35e97029684fe", + "thumb_exists": true, # Changed to test hasThumbnail=true code. + "bytes": 230783, + "modified": "Tue, 19 Jul 2011 21:55:38 +0000", + "client_mtime": "Mon, 18 Jul 2011 18:04:35 +0000", + "path": "/Getting_Started.pdf", + "is_dir": false, + "icon": "page_white_acrobat", + "root": "app_folder", # Changed to test app_folder code path. + "mime_type": "application/pdf", + "revision": 220823 + } + ] + @changes = Dropbox.PullChange.parse entry + + it 'parses path correctly', -> + expect(@changes).to.have.property 'path' + expect(@changes.path).to.equal '/Getting_Started.pdf' + + it 'parses wasRemoved correctly', -> + expect(@changes).to.have.property 'wasRemoved' + expect(@changes.wasRemoved).to.equal false + + it 'parses stat correctly', -> + expect(@changes).to.have.property 'stat' + expect(@changes.stat).to.be.instanceOf Dropbox.Stat + expect(@changes.stat.path).to.equal @changes.path + + describe 'on a deletion change', -> + beforeEach -> + entry = [ + "/Public", + null + ] + @changes = Dropbox.PullChange.parse entry + + it 'parses path correctly', -> + expect(@changes).to.have.property 'path' + expect(@changes.path).to.equal '/Public' + + it 'parses wasRemoved correctly', -> + expect(@changes).to.have.property 'wasRemoved' + expect(@changes.wasRemoved).to.equal true + + it 'parses stat correctly', -> + expect(@changes).to.have.property 'stat' + expect(@changes.stat).to.equal null + + it 'passes null through', -> + expect(Dropbox.PullChange.parse(null)).to.equal null + + it 'passes undefined through', -> + expect(Dropbox.PullChange.parse(undefined)).to.equal undefined + + diff --git a/lib/client/storage/dropbox/test/src/references_test.coffee b/lib/client/storage/dropbox/test/src/references_test.coffee new file mode 100644 index 00000000..ac03c567 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/references_test.coffee @@ -0,0 +1,92 @@ +describe 'Dropbox.PublicUrl', -> + describe '.parse', -> + describe 'on the /shares API example', -> + beforeEach -> + urlData = { + "url": "http://db.tt/APqhX1", + "expires": "Tue, 01 Jan 2030 00:00:00 +0000" + } + @url = Dropbox.PublicUrl.parse urlData, false + + it 'parses url correctly', -> + expect(@url).to.have.property 'url' + expect(@url.url).to.equal 'http://db.tt/APqhX1' + + it 'parses expiresAt correctly', -> + expect(@url).to.have.property 'expiresAt' + expect(@url.expiresAt).to.be.instanceOf Date + expect([ + 'Tue, 01 Jan 2030 00:00:00 GMT', # every sane JS platform + 'Tue, 1 Jan 2030 00:00:00 UTC' # Internet Explorer + ]).to.contain(@url.expiresAt.toUTCString()) + + it 'parses isDirect correctly', -> + expect(@url).to.have.property 'isDirect' + expect(@url.isDirect).to.equal false + + it 'parses isPreview correctly', -> + expect(@url).to.have.property 'isPreview' + expect(@url.isPreview).to.equal true + + it 'round-trips through json / parse correctly', -> + newUrl = Dropbox.PublicUrl.parse @url.json() + newUrl.json() # Get _json populated for newUrl. + expect(newUrl).to.deep.equal @url + + it 'passes null through', -> + expect(Dropbox.PublicUrl.parse(null)).to.equal null + + it 'passes undefined through', -> + expect(Dropbox.PublicUrl.parse(undefined)).to.equal undefined + + +describe 'Dropbox.CopyReference', -> + describe '.parse', -> + describe 'on the API example', -> + beforeEach -> + refData = { + "copy_ref": "z1X6ATl6aWtzOGq0c3g5Ng", + "expires": "Fri, 31 Jan 2042 21:01:05 +0000" + } + @ref = Dropbox.CopyReference.parse refData + + it 'parses tag correctly', -> + expect(@ref).to.have.property 'tag' + expect(@ref.tag).to.equal 'z1X6ATl6aWtzOGq0c3g5Ng' + + it 'parses expiresAt correctly', -> + expect(@ref).to.have.property 'expiresAt' + expect(@ref.expiresAt).to.be.instanceOf Date + expect([ + 'Fri, 31 Jan 2042 21:01:05 GMT', # every sane JS platform + 'Fri, 31 Jan 2042 21:01:05 UTC' # Internet Explorer + ]).to.contain(@ref.expiresAt.toUTCString()) + + it 'round-trips through json / parse correctly', -> + newRef = Dropbox.CopyReference.parse @ref.json() + expect(newRef).to.deep.equal @ref + + describe 'on a reference string', -> + beforeEach -> + rawRef = 'z1X6ATl6aWtzOGq0c3g5Ng' + @ref = Dropbox.CopyReference.parse rawRef + + it 'parses tag correctly', -> + expect(@ref).to.have.property 'tag' + expect(@ref.tag).to.equal 'z1X6ATl6aWtzOGq0c3g5Ng' + + it 'parses expiresAt correctly', -> + expect(@ref).to.have.property 'expiresAt' + expect(@ref.expiresAt).to.be.instanceOf Date + expect(@ref.expiresAt - (new Date())).to.be.below 1000 + + it 'round-trips through json / parse correctly', -> + newRef = Dropbox.CopyReference.parse @ref.json() + expect(newRef).to.deep.equal @ref + + it 'passes null through', -> + expect(Dropbox.CopyReference.parse(null)).to.equal null + + it 'passes undefined through', -> + expect(Dropbox.CopyReference.parse(undefined)).to.equal undefined + diff --git a/lib/client/storage/dropbox/test/src/stat_test.coffee b/lib/client/storage/dropbox/test/src/stat_test.coffee new file mode 100644 index 00000000..2fecf635 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/stat_test.coffee @@ -0,0 +1,204 @@ +describe 'Dropbox.Stat', -> + describe '.parse', -> + describe 'on the API file example', -> + beforeEach -> + # File example at + # https://www.dropbox.com/developers/reference/api#metadata + metadata = { + "size": "225.4KB", + "rev": "35e97029684fe", + "thumb_exists": true, # Changed to test hasThumbnail=true code. + "bytes": 230783, + "modified": "Tue, 19 Jul 2011 21:55:38 +0000", + "client_mtime": "Mon, 18 Jul 2011 18:04:35 +0000", + "path": "/Getting_Started.pdf", + "is_dir": false, + "icon": "page_white_acrobat", + "root": "app_folder", # Changed to test app_folder code path. + "mime_type": "application/pdf", + "revision": 220823 + } + @stat = Dropbox.Stat.parse metadata + + it 'parses the path correctly', -> + expect(@stat).to.have.property 'path' + expect(@stat.path).to.equal '/Getting_Started.pdf' + + it 'parses name correctly', -> + expect(@stat).to.have.property 'name' + expect(@stat.name).to.equal 'Getting_Started.pdf' + + it 'parses inAppFolder corectly', -> + expect(@stat).to.have.property 'inAppFolder' + expect(@stat.inAppFolder).to.equal true + + it 'parses isFolder correctly', -> + expect(@stat).to.have.property 'isFolder' + expect(@stat.isFolder).to.equal false + expect(@stat).to.have.property 'isFile' + expect(@stat.isFile).to.equal true + + it 'parses isRemoved correctly', -> + expect(@stat).to.have.property 'isRemoved' + expect(@stat.isRemoved).to.equal false + + it 'parses typeIcon correctly', -> + expect(@stat).to.have.property 'typeIcon' + expect(@stat.typeIcon).to.equal 'page_white_acrobat' + + it 'parses versionTag correctly', -> + expect(@stat).to.have.property 'versionTag' + expect(@stat.versionTag).to.equal '35e97029684fe' + + it 'parses mimeType correctly', -> + expect(@stat).to.have.property 'mimeType' + expect(@stat.mimeType).to.equal 'application/pdf' + + it 'parses size correctly', -> + expect(@stat).to.have.property 'size' + expect(@stat.size).to.equal 230783 + + it 'parses humanSize correctly', -> + expect(@stat).to.have.property 'humanSize' + expect(@stat.humanSize).to.equal "225.4KB" + + it 'parses hasThumbnail correctly', -> + expect(@stat).to.have.property 'hasThumbnail' + expect(@stat.hasThumbnail).to.equal true + + it 'parses modifiedAt correctly', -> + expect(@stat).to.have.property 'modifiedAt' + expect(@stat.modifiedAt).to.be.instanceOf Date + expect([ + 'Tue, 19 Jul 2011 21:55:38 GMT', # every sane JS platform + 'Tue, 19 Jul 2011 21:55:38 UTC' # Internet Explorer + ]).to.contain(@stat.modifiedAt.toUTCString()) + + it 'parses clientModifiedAt correctly', -> + expect(@stat).to.have.property 'clientModifiedAt' + expect(@stat.clientModifiedAt).to.be.instanceOf Date + expect([ + 'Mon, 18 Jul 2011 18:04:35 GMT', # every sane JS platform + 'Mon, 18 Jul 2011 18:04:35 UTC' # Internet Explorer + ]).to.contain(@stat.clientModifiedAt.toUTCString()) + + it 'round-trips through json / parse correctly', -> + newStat = Dropbox.Stat.parse @stat.json() + expect(newStat).to.deep.equal @stat + + + describe 'on the API directory example', -> + beforeEach -> + # Folder example at + # https://www.dropbox.com/developers/reference/api#metadata + metadata = { + "size": "0 bytes", + "hash": "37eb1ba1849d4b0fb0b28caf7ef3af52", + "bytes": 0, + "thumb_exists": false, + "rev": "714f029684fe", + "modified": "Wed, 27 Apr 2011 22:18:51 +0000", + "path": "/Public", + "is_dir": true, + "is_deleted": true, # Added to test isRemoved=true code path. + "icon": "folder_public", + "root": "dropbox", + "revision": 29007 + } + @stat = Dropbox.Stat.parse metadata + + it 'parses path correctly', -> + expect(@stat).to.have.property 'path' + expect(@stat.path).to.equal '/Public' + + it 'parses name correctly', -> + expect(@stat).to.have.property 'name' + expect(@stat.name).to.equal 'Public' + + it 'parses inAppFolder corectly', -> + expect(@stat).to.have.property 'inAppFolder' + expect(@stat.inAppFolder).to.equal false + + it 'parses isFolder correctly', -> + expect(@stat).to.have.property 'isFolder' + expect(@stat.isFolder).to.equal true + expect(@stat).to.have.property 'isFile' + expect(@stat.isFile).to.equal false + + it 'parses isRemoved correctly', -> + expect(@stat).to.have.property 'isRemoved' + expect(@stat.isRemoved).to.equal true + + it 'parses typeIcon correctly', -> + expect(@stat).to.have.property 'typeIcon' + expect(@stat.typeIcon).to.equal 'folder_public' + + it 'parses versionTag correctly', -> + expect(@stat).to.have.property 'versionTag' + expect(@stat.versionTag).to.equal '37eb1ba1849d4b0fb0b28caf7ef3af52' + + it 'parses mimeType correctly', -> + expect(@stat).to.have.property 'mimeType' + expect(@stat.mimeType).to.equal 'inode/directory' + + it 'parses size correctly', -> + expect(@stat).to.have.property 'size' + expect(@stat.size).to.equal 0 + + it 'parses humanSize correctly', -> + expect(@stat).to.have.property 'humanSize' + expect(@stat.humanSize).to.equal '0 bytes' + + it 'parses hasThumbnail correctly', -> + expect(@stat).to.have.property 'hasThumbnail' + expect(@stat.hasThumbnail).to.equal false + + it 'parses modifiedAt correctly', -> + expect(@stat).to.have.property 'modifiedAt' + expect(@stat.modifiedAt).to.be.instanceOf Date + expect([ + 'Wed, 27 Apr 2011 22:18:51 GMT', # every sane JS platform + 'Wed, 27 Apr 2011 22:18:51 UTC' # Internet Explorer + ]).to.contain(@stat.modifiedAt.toUTCString()) + + it 'parses missing clientModifiedAt correctly', -> + expect(@stat).to.have.property 'clientModifiedAt' + expect(@stat.clientModifiedAt).to.equal null + + it 'round-trips through json / parse correctly', -> + newStat = Dropbox.Stat.parse @stat.json() + expect(newStat).to.deep.equal @stat + + it 'passes null through', -> + expect(Dropbox.Stat.parse(null)).to.equal null + + it 'passes undefined through', -> + expect(Dropbox.Stat.parse(undefined)).to.equal undefined + + describe 'on a contrived file/path example', -> + beforeEach -> + metadata = { + "size": "225.4KB", + "rev": "35e97029684fe", + "thumb_exists": true, # Changed to test hasThumbnail=true code. + "bytes": 230783, + "modified": "Tue, 19 Jul 2011 21:55:38 +0000", + "client_mtime": "Mon, 18 Jul 2011 18:04:35 +0000", + "path": "path/to/a/file/named/Getting_Started.pdf/", + "is_dir": false, + "icon": "page_white_acrobat", + "root": "app_folder", # Changed to test app_folder code path. + "mime_type": "application/pdf", + "revision": 220823 + } + @stat = Dropbox.Stat.parse metadata + + it 'parses the path correctly', -> + expect(@stat).to.have.property 'path' + expect(@stat.path).to.equal '/path/to/a/file/named/Getting_Started.pdf' + + it 'parses name correctly', -> + expect(@stat).to.have.property 'name' + expect(@stat.name).to.equal 'Getting_Started.pdf' + + diff --git a/lib/client/storage/dropbox/test/src/token_stash.coffee b/lib/client/storage/dropbox/test/src/token_stash.coffee new file mode 100644 index 00000000..8dec2440 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/token_stash.coffee @@ -0,0 +1,105 @@ +# Stashes Dropbox access credentials. +class TokenStash + # @param {Object} options the advanced options below + # @option options {Boolean} fullDropbox if true, the returned credentials + # will be good for full Dropbox access; otherwise, the credentials will + # work for Folder access + constructor: (options) -> + @fs = require 'fs' + # Node 0.6 hack. + unless @fs.existsSync + path = require 'path' + @fs.existsSync = (filePath) -> path.existsSync filePath + @getCache = null + @sandbox = !options?.fullDropbox + @setupFs() + + # Calls the supplied method with the Dropbox access credentials. + get: (callback) -> + @getCache or= @readStash() + if @getCache + callback @getCache + return null + + @liveLogin (fullCredentials, sandboxCredentials) => + unless fullCredentials and sandboxCredentials + throw new Error('Dropbox API authorization failed') + + @writeStash fullCredentials, sandboxCredentials + @getCache = @readStash() + callback @getCache + + # Obtains credentials by doing a login on the live site. + liveLogin: (callback) -> + Dropbox = require '../../lib/dropbox' + sandboxClient = new Dropbox.Client @clientOptions().sandbox + fullClient = new Dropbox.Client @clientOptions().full + @setupAuth() + sandboxClient.authDriver @authDriver + sandboxClient.authenticate (error, data) => + if error + @killAuth() + callback null + return + fullClient.authDriver @authDriver + fullClient.authenticate (error, data) => + @killAuth() + if error + callback null + return + credentials = @clientOptions() + callback fullClient.credentials(), sandboxClient.credentials() + + # Returns the options used to create a Dropbox Client. + clientOptions: -> + { + sandbox: + sandbox: true + key: 'gWJAiHNbmDA=|MJ3xAk3nLeByuOckISnHib+h+1zTCG3TKTOEFvAAZw==' + full: + key: 'OYaTsqx6IXA=|z8mRqmTRoSdCqvkTvHTZq4ZZNxa7I5wM8X5E33IwCA==' + } + + # Reads the file containing the access credentials, if it is available. + # + # @return {Object?} parsed access credentials, or null if they haven't been + # stashed + readStash: -> + unless @fs.existsSync @jsonPath + return null + stash = JSON.parse @fs.readFileSync @jsonPath + if @sandbox then stash.sandbox else stash.full + + # Stashes the access credentials for future test use. + writeStash: (fullCredentials, sandboxCredentials) -> + json = JSON.stringify full: fullCredentials, sandbox: sandboxCredentials + @fs.writeFileSync @jsonPath, json + + js = "window.testKeys = #{JSON.stringify sandboxCredentials};" + + "window.testFullDropboxKeys = #{JSON.stringify fullCredentials};" + @fs.writeFileSync @jsPath, js + + # Sets up a node.js server-based authentication driver. + setupAuth: -> + return if @authDriver + + Dropbox = require '../../lib/dropbox' + @authDriver = new Dropbox.Drivers.NodeServer + + # Shuts down the node.js server behind the authentication server. + killAuth: -> + return unless @authDriver + + @authDriver.closeServer() + @authDriver = null + + # Sets up the directory structure for the credential stash. + setupFs: -> + @dirPath = 'test/.token' + @jsonPath = 'test/.token/token.json' + @jsPath = 'test/.token/token.js' + + unless @fs.existsSync @dirPath + @fs.mkdirSync @dirPath + +module.exports = TokenStash diff --git a/lib/client/storage/dropbox/test/src/upload_cursor_test.coffee b/lib/client/storage/dropbox/test/src/upload_cursor_test.coffee new file mode 100644 index 00000000..6997bda5 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/upload_cursor_test.coffee @@ -0,0 +1,114 @@ +describe 'Dropbox.UploadCursor', -> + describe '.parse', -> + describe 'on the API example', -> + beforeEach -> + cursorData = { + "upload_id": "v0k84B0AT9fYkfMUp0sBTA", + "offset": 31337, + "expires": "Tue, 19 Jul 2011 21:55:38 +0000" + } + @cursor = Dropbox.UploadCursor.parse cursorData + + it 'parses tag correctly', -> + expect(@cursor).to.have.property 'tag' + expect(@cursor.tag).to.equal 'v0k84B0AT9fYkfMUp0sBTA' + + it 'parses offset correctly', -> + expect(@cursor).to.have.property 'offset' + expect(@cursor.offset).to.equal 31337 + + it 'parses expiresAt correctly', -> + expect(@cursor).to.have.property 'expiresAt' + expect(@cursor.expiresAt).to.be.instanceOf Date + expect([ + 'Tue, 19 Jul 2011 21:55:38 GMT', # every sane JS platform + 'Tue, 19 Jul 2011 21:55:38 UTC' # Internet Explorer + ]).to.contain(@cursor.expiresAt.toUTCString()) + + it 'round-trips through json / parse correctly', -> + newCursor = Dropbox.UploadCursor.parse @cursor.json() + expect(newCursor).to.deep.equal @cursor + + describe 'on a reference string', -> + beforeEach -> + rawRef = 'v0k84B0AT9fYkfMUp0sBTA' + @cursor = Dropbox.UploadCursor.parse rawRef + + it 'parses tag correctly', -> + expect(@cursor).to.have.property 'tag' + expect(@cursor.tag).to.equal 'v0k84B0AT9fYkfMUp0sBTA' + + it 'parses offset correctly', -> + expect(@cursor).to.have.property 'offset' + expect(@cursor.offset).to.equal 0 + + it 'parses expiresAt correctly', -> + expect(@cursor).to.have.property 'expiresAt' + expect(@cursor.expiresAt).to.be.instanceOf Date + expect(@cursor.expiresAt - (new Date())).to.be.below 1000 + + it 'round-trips through json / parse correctly', -> + newCursor = Dropbox.UploadCursor.parse @cursor.json() + newCursor.json() # Get _json populated for newCursor. + expect(newCursor).to.deep.equal @cursor + + it 'passes null through', -> + expect(Dropbox.CopyReference.parse(null)).to.equal null + + it 'passes undefined through', -> + expect(Dropbox.CopyReference.parse(undefined)).to.equal undefined + + describe '.constructor', -> + describe 'with no arguments', -> + beforeEach -> + @cursor = new Dropbox.UploadCursor + + it 'sets up tag correctly', -> + expect(@cursor).to.have.property 'tag' + expect(@cursor.tag).to.equal null + + it 'parses offset correctly', -> + expect(@cursor).to.have.property 'offset' + expect(@cursor.offset).to.equal 0 + + it 'parses expiresAt correctly', -> + expect(@cursor).to.have.property 'expiresAt' + expect(@cursor.expiresAt - (new Date())).to.be.below 1000 + + it 'round-trips through json / parse correctly', -> + newCursor = Dropbox.UploadCursor.parse @cursor.json() + newCursor.json() # Get _json populated for newCursor. + expect(newCursor).to.deep.equal @cursor + + describe '.replace', -> + beforeEach -> + @cursor = new Dropbox.UploadCursor + + describe 'on the API example', -> + beforeEach -> + cursorData = { + "upload_id": "v0k84B0AT9fYkfMUp0sBTA", + "offset": 31337, + "expires": "Tue, 19 Jul 2011 21:55:38 +0000" + } + @cursor.replace cursorData + + it 'parses tag correctly', -> + expect(@cursor).to.have.property 'tag' + expect(@cursor.tag).to.equal 'v0k84B0AT9fYkfMUp0sBTA' + + it 'parses offset correctly', -> + expect(@cursor).to.have.property 'offset' + expect(@cursor.offset).to.equal 31337 + + it 'parses expiresAt correctly', -> + expect(@cursor).to.have.property 'expiresAt' + expect(@cursor.expiresAt).to.be.instanceOf Date + expect([ + 'Tue, 19 Jul 2011 21:55:38 GMT', # every sane JS platform + 'Tue, 19 Jul 2011 21:55:38 UTC' # Internet Explorer + ]).to.contain(@cursor.expiresAt.toUTCString()) + + it 'round-trips through json / parse correctly', -> + newCursor = Dropbox.UploadCursor.parse @cursor.json() + expect(newCursor).to.deep.equal @cursor diff --git a/lib/client/storage/dropbox/test/src/user_info_test.coffee b/lib/client/storage/dropbox/test/src/user_info_test.coffee new file mode 100644 index 00000000..65e09476 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/user_info_test.coffee @@ -0,0 +1,95 @@ +describe 'Dropbox.UserInfo', -> + describe '.parse', -> + describe 'on the API example', -> + beforeEach -> + userData = { + "referral_link": "https://www.dropbox.com/referrals/r1a2n3d4m5s6t7", + "display_name": "John P. User", + "uid": 12345678, + "country": "US", + "quota_info": { + "shared": 253738410565, + "quota": 107374182400000, + "normal": 680031877871 + }, + "email": "johnpuser@company.com" # Added to reflect real responses. + } + @userInfo = Dropbox.UserInfo.parse userData + + it 'parses name correctly', -> + expect(@userInfo).to.have.property 'name' + expect(@userInfo.name).to.equal 'John P. User' + + it 'parses email correctly', -> + expect(@userInfo).to.have.property 'email' + expect(@userInfo.email).to.equal 'johnpuser@company.com' + + it 'parses countryCode correctly', -> + expect(@userInfo).to.have.property 'countryCode' + expect(@userInfo.countryCode).to.equal 'US' + + it 'parses uid correctly', -> + expect(@userInfo).to.have.property 'uid' + expect(@userInfo.uid).to.equal '12345678' + + it 'parses referralUrl correctly', -> + expect(@userInfo).to.have.property 'referralUrl' + expect(@userInfo.referralUrl).to. + equal 'https://www.dropbox.com/referrals/r1a2n3d4m5s6t7' + + it 'parses quota correctly', -> + expect(@userInfo).to.have.property 'quota' + expect(@userInfo.quota).to.equal 107374182400000 + + it 'parses usedQuota correctly', -> + expect(@userInfo).to.have.property 'usedQuota' + expect(@userInfo.usedQuota).to.equal 933770288436 + + it 'parses privateBytes correctly', -> + expect(@userInfo).to.have.property 'privateBytes' + expect(@userInfo.privateBytes).to.equal 680031877871 + + it 'parses sharedBytes correctly', -> + expect(@userInfo).to.have.property 'usedQuota' + expect(@userInfo.sharedBytes).to.equal 253738410565 + + it 'parses publicAppUrl correctly', -> + expect(@userInfo.publicAppUrl).to.equal null + + it 'round-trips through json / parse correctly', -> + newInfo = Dropbox.UserInfo.parse @userInfo.json() + expect(newInfo).to.deep.equal @userInfo + + it 'passes null through', -> + expect(Dropbox.UserInfo.parse(null)).to.equal null + + it 'passes undefined through', -> + expect(Dropbox.UserInfo.parse(undefined)).to.equal undefined + + + describe 'on real data from a "public app folder" application', -> + beforeEach -> + userData = { + "referral_link": "https://www.dropbox.com/referrals/NTM1OTg4MTA5", + "display_name": "Victor Costan", + "uid": 87654321, # Anonymized. + "public_app_url": "https://dl-web.dropbox.com/spa/90vw6zlu4268jh4/", + "country": "US", + "quota_info": { + "shared": 6074393565, + "quota": 73201090560, + "normal": 4684642723 + }, + "email": "spam@gmail.com" # Anonymized. + } + @userInfo = Dropbox.UserInfo.parse userData + + it 'parses publicAppUrl correctly', -> + expect(@userInfo.publicAppUrl).to. + equal 'https://dl-web.dropbox.com/spa/90vw6zlu4268jh4' + + it 'round-trips through json / parse correctly', -> + newInfo = Dropbox.UserInfo.parse @userInfo.json() + expect(newInfo).to.deep.equal @userInfo + + diff --git a/lib/client/storage/dropbox/test/src/web_file_server.coffee b/lib/client/storage/dropbox/test/src/web_file_server.coffee new file mode 100644 index 00000000..9c9dfd24 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/web_file_server.coffee @@ -0,0 +1,54 @@ +express = require 'express' +fs = require 'fs' +https = require 'https' +open = require 'open' + +# Tiny express.js server for the Web files. +class WebFileServer + # Starts up a HTTP server. + constructor: (@port = 8911) -> + @createApp() + + # Opens the test URL in a browser. + openBrowser: (appName) -> + open @testUrl(), appName + + # The URL that should be used to start the tests. + testUrl: -> + "https://localhost:#{@port}/test/html/browser_test.html" + + # The server code. + createApp: -> + @app = express() + @app.get '/diediedie', (request, response) => + if 'failed' of request.query + failed = parseInt request.query['failed'] + else + failed = 1 + total = parseInt request.query['total'] || 0 + passed = total - failed + exitCode = if failed == 0 then 0 else 1 + console.log "#{passed} passed, #{failed} failed" + + response.header 'Content-Type', 'image/png' + response.header 'Content-Length', '0' + response.end '' + unless 'NO_EXIT' of process.env + @server.close() + process.exit exitCode + + @app.use (request, response, next) -> + response.header 'Access-Control-Allow-Origin', '*' + response.header 'Access-Control-Allow-Methods', 'DELETE,GET,POST,PUT' + response.header 'Access-Control-Allow-Headers', + 'Content-Type, Authorization' + next() + + @app.use express.static(fs.realpathSync(__dirname + '/../../'), + { hidden: true }) + options = key: fs.readFileSync 'test/ssl/cert.pem' + options.cert = options.key + @server = https.createServer(options, @app) + @server.listen @port + +module.exports = new WebFileServer diff --git a/lib/client/storage/dropbox/test/src/xhr_test.coffee b/lib/client/storage/dropbox/test/src/xhr_test.coffee new file mode 100644 index 00000000..afe84a69 --- /dev/null +++ b/lib/client/storage/dropbox/test/src/xhr_test.coffee @@ -0,0 +1,644 @@ +describe 'Dropbox.Xhr', -> + beforeEach -> + @node_js = module? and module?.exports? and require? + @oauth = new Dropbox.Oauth testKeys + + describe 'with a GET', -> + beforeEach -> + @xhr = new Dropbox.Xhr 'GET', 'https://request.url' + + it 'initializes correctly', -> + expect(@xhr.isGet).to.equal true + expect(@xhr.method).to.equal 'GET' + expect(@xhr.url).to.equal 'https://request.url' + expect(@xhr.preflight).to.equal false + + describe '#setHeader', -> + beforeEach -> + @xhr.setHeader 'Range', 'bytes=0-1000' + + it 'adds a HTTP header header', -> + expect(@xhr.headers).to.have.property 'Range' + expect(@xhr.headers['Range']).to.equal 'bytes=0-1000' + + it 'does not work twice for the same header', -> + expect(=> @xhr.setHeader('Range', 'bytes=0-1000')).to.throw Error + + it 'flags the Xhr as needing preflight', -> + expect(@xhr.preflight).to.equal true + + it 'rejects Content-Type', -> + expect(=> @xhr.setHeader('Content-Type', 'text/plain')).to.throw Error + + describe '#setParams', -> + beforeEach -> + @xhr.setParams 'param 1': true, 'answer': 42 + + it 'does not flag the XHR as needing preflight', -> + expect(@xhr.preflight).to.equal false + + it 'does not work twice', -> + expect(=> @xhr.setParams 'answer': 43).to.throw Error + + describe '#paramsToUrl', -> + beforeEach -> + @xhr.paramsToUrl() + + it 'changes the url', -> + expect(@xhr.url).to. + equal 'https://request.url?answer=42¶m%201=true' + + it 'sets params to null', -> + expect(@xhr.params).to.equal null + + describe '#paramsToBody', -> + it 'throws an error', -> + expect(=> @xhr.paramsToBody()).to.throw Error + + describe '#addOauthParams', -> + beforeEach -> + @xhr.addOauthParams @oauth + + it 'keeps existing params', -> + expect(@xhr.params).to.have.property 'answer' + expect(@xhr.params.answer).to.equal 42 + + it 'adds an oauth_signature param', -> + expect(@xhr.params).to.have.property 'oauth_signature' + + it 'does not add an Authorization header', -> + expect(@xhr.headers).not.to.have.property 'Authorization' + + it 'does not work twice', -> + expect(=> @xhr.addOauthParams()).to.throw Error + + describe '#addOauthHeader', -> + beforeEach -> + @xhr.addOauthHeader @oauth + + it 'keeps existing params', -> + expect(@xhr.params).to.have.property 'answer' + expect(@xhr.params.answer).to.equal 42 + + it 'does not add an oauth_signature param', -> + expect(@xhr.params).not.to.have.property 'oauth_signature' + + it 'adds an Authorization header', -> + expect(@xhr.headers).to.have.property 'Authorization' + + describe '#addOauthParams without params', -> + beforeEach -> + @xhr.addOauthParams @oauth + + it 'adds an oauth_signature param', -> + expect(@xhr.params).to.have.property 'oauth_signature' + + describe '#addOauthHeader without params', -> + beforeEach -> + @xhr.addOauthHeader @oauth + + it 'adds an Authorization header', -> + expect(@xhr.headers).to.have.property 'Authorization' + + describe '#signWithOauth', -> + describe 'for a request that does not need preflight', -> + beforeEach -> + @xhr.signWithOauth @oauth + + if Dropbox.Xhr.doesPreflight + it 'uses addOauthParams', -> + expect(@xhr.params).to.have.property 'oauth_signature' + else + it 'uses addOauthHeader in node.js', -> + expect(@xhr.headers).to.have.property 'Authorization' + + describe 'for a request that needs preflight', -> + beforeEach -> + @xhr.setHeader 'Range', 'bytes=0-1000' + @xhr.signWithOauth @oauth + + if Dropbox.Xhr.ieXdr # IE's XDR doesn't do HTTP headers. + it 'uses addOauthParams in IE', -> + expect(@xhr.params).to.have.property 'oauth_signature' + else + it 'uses addOauthHeader', -> + expect(@xhr.headers).to.have.property 'Authorization' + + describe 'with cacheFriendly: true', -> + describe 'for a request that does not need preflight', -> + beforeEach -> + @xhr.signWithOauth @oauth, true + + if Dropbox.Xhr.ieXdr + it 'uses addOauthParams in IE', -> + expect(@xhr.params).to.have.property 'oauth_signature' + else + it 'uses addOauthHeader', -> + expect(@xhr.headers).to.have.property 'Authorization' + + describe 'for a request that needs preflight', -> + beforeEach -> + @xhr.setHeader 'Range', 'bytes=0-1000' + @xhr.signWithOauth @oauth, true + + if Dropbox.Xhr.ieXdr # IE's XDR doesn't do HTTP headers. + it 'uses addOauthParams in IE', -> + expect(@xhr.params).to.have.property 'oauth_signature' + else + it 'uses addOauthHeader', -> + expect(@xhr.headers).to.have.property 'Authorization' + + describe '#setFileField', -> + it 'throws an error', -> + expect(=> @xhr.setFileField('file', 'filename.bin', '

    File Data

    ', + 'text/html')).to.throw Error + + describe '#setBody', -> + it 'throws an error', -> + expect(=> @xhr.setBody('body data')).to.throw Error + + it 'does not flag the XHR as needing preflight', -> + expect(@xhr.preflight).to.equal false + + describe '#setResponseType', -> + beforeEach -> + @xhr.setResponseType 'b' + + it 'changes responseType', -> + expect(@xhr.responseType).to.equal 'b' + + it 'does not flag the XHR as needing preflight', -> + expect(@xhr.preflight).to.equal false + + describe '#prepare with params', -> + beforeEach -> + @xhr.setParams answer: 42 + @xhr.prepare() + + it 'creates the native xhr', -> + expect(typeof @xhr.xhr).to.equal 'object' + + it 'opens the native xhr', -> + return if Dropbox.Xhr.ieXdr # IE's XDR doesn't do readyState. + expect(@xhr.xhr.readyState).to.equal 1 + + it 'pushes the params in the url', -> + expect(@xhr.url).to.equal 'https://request.url?answer=42' + + describe 'with a POST', -> + beforeEach -> + @xhr = new Dropbox.Xhr 'POST', 'https://request.url' + + it 'initializes correctly', -> + expect(@xhr.isGet).to.equal false + expect(@xhr.method).to.equal 'POST' + expect(@xhr.url).to.equal 'https://request.url' + expect(@xhr.preflight).to.equal false + + describe '#setHeader', -> + beforeEach -> + @xhr.setHeader 'Range', 'bytes=0-1000' + + it 'adds a HTTP header header', -> + expect(@xhr.headers).to.have.property 'Range' + expect(@xhr.headers['Range']).to.equal 'bytes=0-1000' + + it 'does not work twice for the same header', -> + expect(=> @xhr.setHeader('Range', 'bytes=0-1000')).to.throw Error + + it 'flags the Xhr as needing preflight', -> + expect(@xhr.preflight).to.equal true + + it 'rejects Content-Type', -> + expect(=> @xhr.setHeader('Content-Type', 'text/plain')).to.throw Error + + describe '#setParams', -> + beforeEach -> + @xhr.setParams 'param 1': true, 'answer': 42 + + it 'does not work twice', -> + expect(=> @xhr.setParams 'answer': 43).to.throw Error + + it 'does not flag the XHR as needing preflight', -> + expect(@xhr.preflight).to.equal false + + describe '#paramsToUrl', -> + beforeEach -> + @xhr.paramsToUrl() + + it 'changes the url', -> + expect(@xhr.url).to. + equal 'https://request.url?answer=42¶m%201=true' + + it 'sets params to null', -> + expect(@xhr.params).to.equal null + + it 'does not set the body', -> + expect(@xhr.body).to.equal null + + describe '#paramsToBody', -> + beforeEach -> + @xhr.paramsToBody() + + it 'url-encodes the params', -> + expect(@xhr.body).to.equal 'answer=42¶m%201=true' + + it 'sets the Content-Type header', -> + expect(@xhr.headers).to.have.property 'Content-Type' + expect(@xhr.headers['Content-Type']).to. + equal 'application/x-www-form-urlencoded' + + it 'does not change the url', -> + expect(@xhr.url).to.equal 'https://request.url' + + it 'does not work twice', -> + @xhr.setParams answer: 43 + expect(=> @xhr.paramsToBody()).to.throw Error + + describe '#addOauthParams', -> + beforeEach -> + @xhr.addOauthParams @oauth + + it 'keeps existing params', -> + expect(@xhr.params).to.have.property 'answer' + expect(@xhr.params.answer).to.equal 42 + + it 'adds an oauth_signature param', -> + expect(@xhr.params).to.have.property 'oauth_signature' + + it 'does not add an Authorization header', -> + expect(@xhr.headers).not.to.have.property 'Authorization' + + it 'does not work twice', -> + expect(=> @xhr.addOauthParams()).to.throw Error + + describe '#addOauthHeader', -> + beforeEach -> + @xhr.addOauthHeader @oauth + + it 'keeps existing params', -> + expect(@xhr.params).to.have.property 'answer' + expect(@xhr.params.answer).to.equal 42 + + it 'does not add an oauth_signature param', -> + expect(@xhr.params).not.to.have.property 'oauth_signature' + + it 'adds an Authorization header', -> + expect(@xhr.headers).to.have.property 'Authorization' + + describe '#addOauthParams without params', -> + beforeEach -> + @xhr.addOauthParams @oauth + + it 'adds an oauth_signature param', -> + expect(@xhr.params).to.have.property 'oauth_signature' + + describe '#addOauthHeader without params', -> + beforeEach -> + @xhr.addOauthHeader @oauth + + it 'adds an Authorization header', -> + expect(@xhr.headers).to.have.property 'Authorization' + + describe '#signWithOauth', -> + describe 'for a request that does not need preflight', -> + beforeEach -> + @xhr.signWithOauth @oauth + + if Dropbox.Xhr.doesPreflight + it 'uses addOauthParams', -> + expect(@xhr.params).to.have.property 'oauth_signature' + else + it 'uses addOauthHeader in node.js', -> + expect(@xhr.headers).to.have.property 'Authorization' + + describe 'for a request that needs preflight', -> + beforeEach -> + @xhr.setHeader 'Range', 'bytes=0-1000' + @xhr.signWithOauth @oauth + + if Dropbox.Xhr.ieXdr # IE's XDR doesn't do HTTP headers. + it 'uses addOauthParams in IE', -> + expect(@xhr.params).to.have.property 'oauth_signature' + else + it 'uses addOauthHeader', -> + expect(@xhr.headers).to.have.property 'Authorization' + + describe 'with cacheFriendly: true', -> + describe 'for a request that does not need preflight', -> + beforeEach -> + @xhr.signWithOauth @oauth, true + + if Dropbox.Xhr.doesPreflight + it 'uses addOauthParams', -> + expect(@xhr.params).to.have.property 'oauth_signature' + else + it 'uses addOauthHeader in node.js', -> + expect(@xhr.headers).to.have.property 'Authorization' + + describe 'for a request that needs preflight', -> + beforeEach -> + @xhr.setHeader 'Range', 'bytes=0-1000' + @xhr.signWithOauth @oauth, true + + if Dropbox.Xhr.ieXdr # IE's XDR doesn't do HTTP headers. + it 'uses addOauthParams in IE', -> + expect(@xhr.params).to.have.property 'oauth_signature' + else + it 'uses addOauthHeader', -> + expect(@xhr.headers).to.have.property 'Authorization' + + describe '#setFileField with a String', -> + beforeEach -> + @nonceStub = sinon.stub @xhr, 'multipartBoundary' + @nonceStub.returns 'multipart----boundary' + @xhr.setFileField 'file', 'filename.bin', '

    File Data

    ', + 'text/html' + + afterEach -> + @nonceStub.restore() + + it 'sets the Content-Type header', -> + expect(@xhr.headers).to.have.property 'Content-Type' + expect(@xhr.headers['Content-Type']).to. + equal 'multipart/form-data; boundary=multipart----boundary' + + it 'sets the body', -> + expect(@xhr.body).to.equal("""--multipart----boundary\r +Content-Disposition: form-data; name="file"; filename="filename.bin"\r +Content-Type: text/html\r +Content-Transfer-Encoding: binary\r +\r +

    File Data

    \r +--multipart----boundary--\r\n +""") + + it 'does not work twice', -> + expect(=> @xhr.setFileField('file', 'filename.bin', '

    File Data

    ', + 'text/html')).to.throw Error + + it 'does not flag the XHR as needing preflight', -> + expect(@xhr.preflight).to.equal false + + describe '#setBody with a string', -> + beforeEach -> + @xhr.setBody 'body data' + + it 'sets the request body', -> + expect(@xhr.body).to.equal 'body data' + + it 'does not work twice', -> + expect(=> @xhr.setBody('body data')).to.throw Error + + it 'does not flag the XHR as needing preflight', -> + expect(@xhr.preflight).to.equal false + + describe '#setBody with FormData', -> + beforeEach -> + if FormData? + formData = new FormData() + formData.append 'name', 'value' + @xhr.setBody formData + + it 'does not flag the XHR as needing preflight', -> + return unless FormData? + expect(@xhr.preflight).to.equal false + + describe '#setBody with Blob', -> + beforeEach -> + if Blob? + blob = new Blob ["abcdef"], type: 'image/png' + @xhr.setBody blob + + it 'flags the XHR as needing preflight', -> + return unless Blob? + expect(@xhr.preflight).to.equal true + + it 'sets the Content-Type header', -> + return unless Blob? + expect(@xhr.headers).to.have.property 'Content-Type' + expect(@xhr.headers['Content-Type']).to. + equal 'application/octet-stream' + + describe '#setBody with ArrayBuffer', -> + beforeEach -> + if ArrayBuffer? + buffer = new ArrayBuffer 5 + @xhr.setBody buffer + + it 'flags the XHR as needing preflight', -> + return unless ArrayBuffer? + expect(@xhr.preflight).to.equal true + + it 'sets the Content-Type header', -> + return unless ArrayBuffer? + expect(@xhr.headers).to.have.property 'Content-Type' + expect(@xhr.headers['Content-Type']).to. + equal 'application/octet-stream' + + describe '#setBody with ArrayBufferView', -> + beforeEach -> + if Uint8Array? + view = new Uint8Array 5 + @xhr.setBody view + + it 'flags the XHR as needing preflight', -> + return unless Uint8Array? + expect(@xhr.preflight).to.equal true + + it 'sets the Content-Type header', -> + return unless Uint8Array? + expect(@xhr.headers).to.have.property 'Content-Type' + expect(@xhr.headers['Content-Type']).to. + equal 'application/octet-stream' + + describe '#setResponseType', -> + beforeEach -> + @xhr.setResponseType 'b' + + it 'changes responseType', -> + expect(@xhr.responseType).to.equal 'b' + + it 'does not flag the XHR as needing preflight', -> + expect(@xhr.preflight).to.equal false + + describe '#prepare with params', -> + beforeEach -> + @xhr.setParams answer: 42 + @xhr.prepare() + + it 'creates the native xhr', -> + expect(typeof @xhr.xhr).to.equal 'object' + + it 'opens the native xhr', -> + return if Dropbox.Xhr.ieXdr # IE's XDR doesn't do readyState. + expect(@xhr.xhr.readyState).to.equal 1 + + if Dropbox.Xhr.ieXdr + it 'keeps the params in the URL in IE', -> + expect(@xhr.url).to.equal 'https://request.url?answer=42' + expect(@xhr.body).to.equal null + else + it 'pushes the params in the body', -> + expect(@xhr.body).to.equal 'answer=42' + + describe 'with a PUT', -> + beforeEach -> + @xhr = new Dropbox.Xhr 'PUT', 'https://request.url' + + it 'initializes correctly', -> + expect(@xhr.isGet).to.equal false + expect(@xhr.method).to.equal 'PUT' + expect(@xhr.url).to.equal 'https://request.url' + expect(@xhr.preflight).to.equal true + + describe '#send', -> + it 'reports errors correctly', (done) -> + @url = 'https://api.dropbox.com/1/oauth/request_token' + @xhr = new Dropbox.Xhr 'POST', @url + @xhr.prepare().send (error, data) => + expect(data).to.equal undefined + expect(error).to.be.instanceOf Dropbox.ApiError + expect(error).to.have.property 'url' + expect(error.url).to.equal @url + expect(error).to.have.property 'method' + expect(error.method).to.equal 'POST' + unless Dropbox.Xhr.ieXdr # IE's XDR doesn't do HTTP status codes. + expect(error).to.have.property 'status' + expect(error.status).to.equal 401 # Bad OAuth request. + expect(error).to.have.property 'responseText' + expect(error.responseText).to.be.a 'string' + unless Dropbox.Xhr.ieXdr # IE's XDR hides the HTTP body on error. + expect(error).to.have.property 'response' + expect(error.response).to.be.an 'object' + expect(error.toString()).to.match /^Dropbox API error/ + expect(error.toString()).to.contain 'POST' + expect(error.toString()).to.contain @url + done() + + it 'reports errors correctly when onError is set', (done) -> + @url = 'https://api.dropbox.com/1/oauth/request_token' + @xhr = new Dropbox.Xhr 'POST', @url + @xhr.onError = new Dropbox.EventSource + listenerError = null + @xhr.onError.addListener (error) -> listenerError = error + @xhr.prepare().send (error, data) => + expect(data).to.equal undefined + expect(error).to.be.instanceOf Dropbox.ApiError + expect(error).to.have.property 'url' + expect(error.url).to.equal @url + expect(error).to.have.property 'method' + expect(error.method).to.equal 'POST' + expect(listenerError).to.equal error + done() + + it 'processes data correctly', (done) -> + xhr = new Dropbox.Xhr 'POST', + 'https://api.dropbox.com/1/oauth/request_token', + xhr.addOauthParams @oauth + xhr.prepare().send (error, data) -> + expect(error).to.not.be.ok + expect(data).to.have.property 'oauth_token' + expect(data).to.have.property 'oauth_token_secret' + done() + + it 'processes data correctly when using setCallback', (done) -> + xhr = new Dropbox.Xhr 'POST', + 'https://api.dropbox.com/1/oauth/request_token', + xhr.addOauthParams @oauth + xhr.setCallback (error, data) -> + expect(error).to.not.be.ok + expect(data).to.have.property 'oauth_token' + expect(data).to.have.property 'oauth_token_secret' + done() + xhr.prepare().send() + + it 'sends Authorize headers correctly', (done) -> + return done() if Dropbox.Xhr.ieXdr # IE's XDR doesn't set headers. + + xhr = new Dropbox.Xhr 'POST', + 'https://api.dropbox.com/1/oauth/request_token', + xhr.addOauthHeader @oauth + xhr.prepare().send (error, data) -> + expect(error).to.equal null + expect(data).to.have.property 'oauth_token' + expect(data).to.have.property 'oauth_token_secret' + done() + + describe 'with a binary response', -> + beforeEach -> + testImageServerOn() + @xhr = new Dropbox.Xhr 'GET', testImageUrl + + afterEach -> + testImageServerOff() + + describe 'with responseType b', -> + beforeEach -> + @xhr.setResponseType 'b' + + it 'retrieves a string where each character is a byte', (done) -> + @xhr.prepare().send (error, data) -> + expect(error).to.not.be.ok + expect(data).to.be.a 'string' + expect(data).to.equal testImageBytes + done() + + describe 'with responseType arraybuffer', -> + beforeEach -> + @xhr.setResponseType 'arraybuffer' + + it 'retrieves a well-formed ArrayBuffer', (done) -> + # Skip this test on node.js and IE 9 and below + return done() unless ArrayBuffer? + + @xhr.prepare().send (error, buffer) -> + expect(error).to.not.be.ok + expect(buffer).to.be.instanceOf ArrayBuffer + view = new Uint8Array buffer + length = buffer.byteLength + bytes = (String.fromCharCode view[i] for i in [0...length]). + join('') + expect(bytes).to.equal testImageBytes + done() + + describe 'with responseType blob', -> + beforeEach -> + @xhr.setResponseType 'blob' + + it 'retrieves a well-formed Blob', (done) -> + # Skip this test on node.js and IE 9 and below + return done() unless Blob? + + @xhr.prepare().send (error, blob) -> + expect(error).to.not.be.ok + expect(blob).to.be.instanceOf Blob + reader = new FileReader + reader.onloadend = -> + return unless reader.readyState == FileReader.DONE + buffer = reader.result + view = new Uint8Array buffer + length = buffer.byteLength + bytes = (String.fromCharCode view[i] for i in [0...length]). + join('') + expect(bytes).to.equal testImageBytes + done() + reader.readAsArrayBuffer blob + + describe '#urlEncode', -> + it 'iterates properly', -> + expect(Dropbox.Xhr.urlEncode({foo: 'bar', baz: 5})).to. + equal 'baz=5&foo=bar' + it 'percent-encodes properly', -> + expect(Dropbox.Xhr.urlEncode({'a +x()': "*b'"})).to. + equal 'a%20%2Bx%28%29=%2Ab%27' + + describe '#urlDecode', -> + it 'iterates properly', -> + decoded = Dropbox.Xhr.urlDecode('baz=5&foo=bar') + expect(decoded['baz']).to.equal '5' + expect(decoded['foo']).to.equal 'bar' + it 'percent-decodes properly', -> + decoded = Dropbox.Xhr.urlDecode('a%20%2Bx%28%29=%2Ab%27') + expect(decoded['a +x()']).to.equal "*b'" +