updated dropbox to v0.9.2 and moved to packege.json from storage folder
|
|
@ -43,6 +43,9 @@ writed root directory.
|
|||
|
||||
* Changed sync reading of certs to async.
|
||||
|
||||
* Updated dropbox to v0.9.2 and moved to packege.json
|
||||
from storage folder.
|
||||
|
||||
|
||||
2012.03.01, Version 0.1.9
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src=http://cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.8.1/dropbox.min.js></script>
|
||||
<script src=//cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.9.2/dropbox.min.js></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -1,111 +1,111 @@
|
|||
var CloudCommander, Util, DOM, Dropbox, cb, Client;
|
||||
/* module for work with github */
|
||||
|
||||
(function(CloudCmd, Util, DOM){
|
||||
'use strict';
|
||||
|
||||
var DropBoxStore = {};
|
||||
|
||||
/* temporary callback function for work with github */
|
||||
cb = function (err, data){ Util.log(err || data);};
|
||||
|
||||
/* PRIVATE FUNCTIONS */
|
||||
|
||||
/**
|
||||
* function loads dropbox.js
|
||||
*/
|
||||
function load(pCallBack){
|
||||
console.time('dropbox load');
|
||||
|
||||
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');
|
||||
DOM.Images.hideLoad();
|
||||
|
||||
Util.exec(pCallBack);
|
||||
};
|
||||
|
||||
DOM.jsload(lSrc, {
|
||||
onload : lOnload,
|
||||
error : DOM.retJSLoad(lLocal, lOnload)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function getUserData(pCallBack){
|
||||
Client.getUserInfo(function(pError, pData){
|
||||
var lHello = 'Hello ' + pData.name + ' :)!',
|
||||
lMsg = pError ? pError : lHello;
|
||||
|
||||
Util.log(lMsg);
|
||||
});
|
||||
|
||||
Util.exec(pCallBack);
|
||||
}
|
||||
/**
|
||||
* function logins on dropbox
|
||||
*
|
||||
* @param pData = {key, secret}
|
||||
*/
|
||||
DropBoxStore.login = function(pCallBack){
|
||||
CloudCmd.getModules(function(pModules){
|
||||
var lStorage = Util.findObjByNameInArr(pModules, 'storage'),
|
||||
lDropBox = Util.findObjByNameInArr(lStorage, 'DropBox'),
|
||||
lDropBoxKey = lDropBox && lDropBox.encodedKey;
|
||||
|
||||
Client = new Dropbox.Client({
|
||||
key: lDropBoxKey
|
||||
});
|
||||
|
||||
//Client.authDriver(new Dropbox.Drivers.Redirect({rememberUser: true}));
|
||||
|
||||
var lURL = CloudCmd.HOST + '/html/auth/dropbox.html';
|
||||
Client.authDriver(new Dropbox.Drivers.Popup({
|
||||
receiverUrl: lURL, noFragment: true
|
||||
}));
|
||||
|
||||
Client.authenticate(function(pError, pClient) {
|
||||
Util.log(pError);
|
||||
Client = pClient;
|
||||
Util.exec(pCallBack);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
/**
|
||||
* upload file to DropBox
|
||||
*/
|
||||
DropBoxStore.uploadFile = function(pParams, pCallBack){
|
||||
var lContent = pParams.data,
|
||||
lName = pParams.name;
|
||||
|
||||
if(lContent){
|
||||
DOM.Images.showLoad();
|
||||
if(!lName)
|
||||
lName = Util.getDate();
|
||||
|
||||
Client.writeFile(lName, lContent, function(pError, pData){
|
||||
DOM.Images.hideLoad();
|
||||
Util.log(pError || pData);
|
||||
Util.exec(pCallBack);
|
||||
});
|
||||
}
|
||||
|
||||
return lContent;
|
||||
};
|
||||
|
||||
DropBoxStore.init = function(pCallBack){
|
||||
Util.loadOnLoad([
|
||||
Util.retExec(pCallBack),
|
||||
getUserData,
|
||||
DropBoxStore.login,
|
||||
load
|
||||
]);
|
||||
|
||||
CloudCmd.DropBox.init = null;
|
||||
};
|
||||
|
||||
CloudCmd.DropBox = DropBoxStore;
|
||||
})(CloudCommander, Util, DOM);
|
||||
var CloudCommander, Util, DOM, Dropbox, cb, Client;
|
||||
/* module for work with github */
|
||||
|
||||
(function(CloudCmd, Util, DOM){
|
||||
'use strict';
|
||||
|
||||
var DropBoxStore = {};
|
||||
|
||||
/* temporary callback function for work with github */
|
||||
cb = function (err, data){ Util.log(err || data);};
|
||||
|
||||
/* PRIVATE FUNCTIONS */
|
||||
|
||||
/**
|
||||
* function loads dropbox.js
|
||||
*/
|
||||
function load(pCallBack){
|
||||
console.time('dropbox load');
|
||||
|
||||
var lSrc = '//cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.9.2/dropbox.min.js',
|
||||
lLocal = '/node_modules/dropbox/lib/dropbox.js',
|
||||
lOnload = function(){
|
||||
console.timeEnd('dropbox load');
|
||||
DOM.Images.hideLoad();
|
||||
|
||||
Util.exec(pCallBack);
|
||||
};
|
||||
|
||||
DOM.jsload(lSrc, {
|
||||
onload : lOnload,
|
||||
error : DOM.retJSLoad(lLocal, lOnload)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function getUserData(pCallBack){
|
||||
Client.getUserInfo(function(pError, pData){
|
||||
var lHello = 'Hello ' + pData.name + ' :)!',
|
||||
lMsg = pError ? pError : lHello;
|
||||
|
||||
Util.log(lMsg);
|
||||
});
|
||||
|
||||
Util.exec(pCallBack);
|
||||
}
|
||||
/**
|
||||
* function logins on dropbox
|
||||
*
|
||||
* @param pData = {key, secret}
|
||||
*/
|
||||
DropBoxStore.login = function(pCallBack){
|
||||
CloudCmd.getModules(function(pModules){
|
||||
var lStorage = Util.findObjByNameInArr(pModules, 'storage'),
|
||||
lDropBox = Util.findObjByNameInArr(lStorage, 'DropBox'),
|
||||
lDropBoxKey = lDropBox && lDropBox.encodedKey;
|
||||
|
||||
Client = new Dropbox.Client({
|
||||
key: lDropBoxKey
|
||||
});
|
||||
|
||||
//Client.authDriver(new Dropbox.Drivers.Redirect({rememberUser: true}));
|
||||
|
||||
var lURL = CloudCmd.HOST + '/html/auth/dropbox.html';
|
||||
Client.authDriver(new Dropbox.Drivers.Popup({
|
||||
receiverUrl: lURL, noFragment: true
|
||||
}));
|
||||
|
||||
Client.authenticate(function(pError, pClient) {
|
||||
Util.log(pError);
|
||||
Client = pClient;
|
||||
Util.exec(pCallBack);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
/**
|
||||
* upload file to DropBox
|
||||
*/
|
||||
DropBoxStore.uploadFile = function(pParams, pCallBack){
|
||||
var lContent = pParams.data,
|
||||
lName = pParams.name;
|
||||
|
||||
if(lContent){
|
||||
DOM.Images.showLoad();
|
||||
if(!lName)
|
||||
lName = Util.getDate();
|
||||
|
||||
Client.writeFile(lName, lContent, function(pError, pData){
|
||||
DOM.Images.hideLoad();
|
||||
Util.log(pError || pData);
|
||||
Util.exec(pCallBack);
|
||||
});
|
||||
}
|
||||
|
||||
return lContent;
|
||||
};
|
||||
|
||||
DropBoxStore.init = function(pCallBack){
|
||||
Util.loadOnLoad([
|
||||
Util.retExec(pCallBack),
|
||||
getUserData,
|
||||
DropBoxStore.login,
|
||||
load
|
||||
]);
|
||||
|
||||
CloudCmd.DropBox.init = null;
|
||||
};
|
||||
|
||||
CloudCmd.DropBox = DropBoxStore;
|
||||
})(CloudCommander, Util, DOM);
|
||||
|
|
|
|||
37
lib/client/storage/dropbox/.gitignore
vendored
|
|
@ -1,37 +0,0 @@
|
|||
# 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
|
||||
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
# 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
|
||||
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
" Indentation settings for the project: 2-space indentation, no tabs.
|
||||
set tabstop=2
|
||||
set softtabstop=2
|
||||
set shiftwidth=2
|
||||
set expandtab
|
||||
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
Copyright (c) 2012 Dropbox, Inc., http://www.dropbox.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
# Client Library for the Dropbox API
|
||||
|
||||
This is a JavaScript client library for the Dropbox API,
|
||||
[written in CoffeeScript](https://github.com/dropbox/dropbox-js/blob/master/doc/coffee_faq.md),
|
||||
suitable for use in both modern browsers and in server-side code running under
|
||||
[node.js](http://nodejs.org/).
|
||||
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
This library is tested against the following JavaScript platforms
|
||||
|
||||
* [node.js](http://nodejs.org/) 0.6 and 0.8
|
||||
* [Chrome](https://www.google.com/chrome) 23
|
||||
* [Firefox](www.mozilla.org/firefox) 17
|
||||
* Internet Explorer 9
|
||||
|
||||
Keep in mind that the versions above are not hard requirements.
|
||||
|
||||
|
||||
## Installation and Usage
|
||||
|
||||
The
|
||||
[getting started guide](https://github.com/dropbox/dropbox-js/blob/master/doc/getting_started.md)
|
||||
will help you get your first dropbox.js application up and running.
|
||||
|
||||
Peruse the source code of the
|
||||
[sample apps](https://github.com/dropbox/dropbox-js/tree/master/samples),
|
||||
and borrow as much as you need.
|
||||
|
||||
The
|
||||
[dropbox.js API reference](http://coffeedoc.info/github/dropbox/dropbox-js/master/class_index.html)
|
||||
can be a good place to bookmark while building your application.
|
||||
|
||||
If you run into a problem, take a look at
|
||||
[the dropbox.js GitHub issue list](https://github.com/dropbox/dropbox-js/issues).
|
||||
Please open a new issue if your problem wasn't already reported.
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
This library is written in CoffeeScript.
|
||||
[This document](https://github.com/dropbox/dropbox-js/blob/master/doc/coffee_faq.md)
|
||||
can help you understand if that matters to you.
|
||||
|
||||
The
|
||||
[development guide](https://github.com/dropbox/dropbox-js/blob/master/doc/development.md)
|
||||
will make your life easier if you need to change the source code.
|
||||
|
||||
|
||||
## Platform-Specific Issues
|
||||
|
||||
This lists the most serious problems that you might run into while using
|
||||
`dropbox.js`. See
|
||||
[the GitHub issue list](https://github.com/dropbox/dropbox-js/issues) for a
|
||||
full list of outstanding problems.
|
||||
|
||||
### node.js
|
||||
|
||||
Reading and writing binary files is currently broken.
|
||||
|
||||
### Internet Explorer 9
|
||||
|
||||
The library only works when used from `https://` pages, due to
|
||||
[these issues](http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx).
|
||||
|
||||
Reading and writing binary files is unsupported.
|
||||
|
||||
|
||||
## Copyright and License
|
||||
|
||||
The library is Copyright (c) 2012 Dropbox Inc., and distributed under the MIT
|
||||
License.
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
# Authentication Drivers
|
||||
|
||||
This document explains the structure and functionality of a `dropbox.js` OAuth
|
||||
driver, and describes the drivers that ship with the library.
|
||||
|
||||
## The OAuth Driver Interface
|
||||
|
||||
An OAuth driver is a JavaScript object that implements the methods documented
|
||||
in the
|
||||
[Dropbox.AuthDriver class](http://coffeedoc.info/github/dropbox/dropbox-js/master/classes/Dropbox/AuthDriver.html).
|
||||
This class exists solely for the purpose of documenting these methods.
|
||||
|
||||
A simple driver can get away with implementing `url` and `doAuthorize`. The
|
||||
following example shows an awfully unusable node.js driver that asks the user
|
||||
to visit the authorization URL in a browser.
|
||||
|
||||
```javascript
|
||||
var util = require("util");
|
||||
var simpleDriver = {
|
||||
url: function() { return ""; },
|
||||
doAuthorize: function(authUrl, token, tokenSecret, callback) {
|
||||
util.print("Visit the following in a browser, then press Enter\n" +
|
||||
authUrl + "\n");
|
||||
var onEnterKey = function() {
|
||||
process.stdin.removeListener("data", onEnterKey);
|
||||
callback(token);
|
||||
}
|
||||
process.stdin.addListener("data", onEnterKey);
|
||||
process.stdin.resume();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Complex drivers can take control of the OAuth process by implementing
|
||||
`onAuthStateChange`. Implementations of this method should read the `authState`
|
||||
field of the `Dropbox.Client` instance they are given to make decisions.
|
||||
Implementations should call the `credentials` and `setCredentials` methods on
|
||||
the client to control the OAuth process.
|
||||
|
||||
See the
|
||||
[Dropbox.Drivers.Redirect source](https://github.com/dropbox/dropbox-js/blob/master/src/drivers.coffee)
|
||||
for a sample implementation of `onAuthStateChange`.
|
||||
|
||||
|
||||
### The OAuth Process Steps
|
||||
|
||||
The `authenticate` method in `Dropbox.Client` implements the OAuth process as a
|
||||
finite state machine (FSM). The current state is available in the `authState`
|
||||
field.
|
||||
|
||||
The authentication FSM has the following states.
|
||||
|
||||
* `Dropbox.Client.RESET` is the initial state, where the client has no OAuth
|
||||
tokens; after `onAuthStateChange` is triggered, the client will attempt to
|
||||
obtain an OAuth request token
|
||||
* `Dropbox.Client.REQUEST` indicates that the client has obtained an OAuth
|
||||
request token; after `onAuthStateChange` is triggered, the client will call
|
||||
`doAuthorize` on the OAuth driver, to get the OAuth request token authorized by
|
||||
the user
|
||||
* `Dropbox.Client.AUTHORIZED` is reached after the `doAuthorize` calls its
|
||||
callback, indicating that the user has authorized the OAuth request token;
|
||||
after `onAuthStateChange` is triggered, the client will attempt to exchange the
|
||||
request token for an OAuth access token
|
||||
* `Dropbox.Client.DONE` indicates that the OAuth process has completed, and the
|
||||
client has an OAuth access token that can be used in API calls; after
|
||||
`onAuthStateChange` is triggered, `authorize` will call its callback function,
|
||||
and report success
|
||||
* `Dropbox.Client.SIGNED_OFF` is reached when the client's `signOut` method is
|
||||
called, after the API call succeeds; after `onAuthStateChange` is triggered,
|
||||
`signOut` will call its callback function, and report success
|
||||
* `Dropbox.Client.ERROR` is reached if any of the Dropbox API calls used by
|
||||
`authorize` or `signOut` results in an error; after `onAuthStateChange` is
|
||||
triggered, `authorize` or `signOut` will call its callback function and report
|
||||
the error
|
||||
|
||||
|
||||
## Built-in OAuth Drivers
|
||||
|
||||
`dropbox.js` ships with the OAuth drivers below.
|
||||
|
||||
### Dropbox.Drivers.Redirect
|
||||
|
||||
The recommended built-in driver for browser applications completes the OAuth
|
||||
token authorization step by redirecting the browser to the Dropbox page that
|
||||
performs the authorization and having that page redirect back to the
|
||||
application page.
|
||||
|
||||
This driver's constructor takes the following options.
|
||||
|
||||
* `useQuery` should be set to true for applications that use the URL fragment
|
||||
(the part after `#`) to store state information
|
||||
* `rememberUser` can be set to true to have the driver store the user's OAuth
|
||||
token in `localStorage`, so the user doesn't have to authorize the application
|
||||
on every request
|
||||
|
||||
Although it seems that `rememberUser` should be true by default, it brings a
|
||||
couple of drawbacks. The user's token will still be valid after signing off the
|
||||
Dropbox web site, so your application will still recognize the user and access
|
||||
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 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.
|
||||
|
||||
|
||||
### Dropbox.Drivers.Popup
|
||||
|
||||
This driver may be useful for browser applications that can't handle the
|
||||
redirections peformed by `Dropbox.Drivers.Redirect`. This driver avoids
|
||||
changing the location of the application's browser window by popping up a
|
||||
separate window, and loading the Dropbox authorization page in that window.
|
||||
|
||||
The popup method has a couple of serious drawbacks. Most browsers will not
|
||||
display the popup window by default, and instead will show a hard-to-notice
|
||||
warning that the user must interact with to display the popup. The driver's
|
||||
code for communicating between the popup and the main application window does
|
||||
not work on IE9 and below, so applications that use it will only work on
|
||||
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),
|
||||
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/oauth_receiver.html"}));
|
||||
```
|
||||
|
||||
The popup driver adds a `#` (fragment hash) to the receiver URL if necessary,
|
||||
to ensure that the user's Dropbox uid and OAuth token are passed to the
|
||||
receiver in a URL fragment. This measure may improve your users' privacy, as it
|
||||
reduces the chance that their uid or token ends up in a server log.
|
||||
|
||||
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}));
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
This driver is designed for use in the automated test suites of node.js
|
||||
applications. It completes the OAuth token authorization step by opening the
|
||||
Dropbox authorization page in a new browser window, and "catches" the OAuth
|
||||
redirection by setting up a small server using the `https` built-in node.js
|
||||
library.
|
||||
|
||||
The driver's constructor takes the following options.
|
||||
|
||||
* `port` is the HTTP port number; the default is 8192, and works well with the
|
||||
Chrome extension described below
|
||||
* `favicon` is a path to a file that will be served in response to requests to
|
||||
`/favicon.ico`; setting this to a proper image will avoid some warnings in the
|
||||
browsers' consoles
|
||||
|
||||
To fully automate your test suite, you need to load up the Chrome extension
|
||||
bundled in the `dropbox.js` source tree. The extension automatically clicks on
|
||||
the "Authorize" button in the Dropbox token authorization page, and closes the
|
||||
page after the token authorization process completes. Follow the steps in the
|
||||
[development guide](https://github.com/dropbox/dropbox-js/blob/master/doc/development.md)
|
||||
to build and install the extension.
|
||||
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
# dropbox.js and CoffeeScript FAQ
|
||||
|
||||
dropbox.js is written in [CoffeeScript](http://coffeescript.org/), which
|
||||
compiles into very readable JavaScript. This document addresses the concerns
|
||||
that are commonly raised by JavaScript developers that do not use or wish to
|
||||
learn CoffeeScript.
|
||||
|
||||
|
||||
## Do I need to learn CoffeeScript to use the library?
|
||||
|
||||
**No.**
|
||||
|
||||
The examples in the
|
||||
[getting started guide](https://github.com/dropbox/dropbox-js/blob/master/doc/getting_started.md)
|
||||
are all written in JavaScript. The
|
||||
[dropbox.js API reference](http://coffeedoc.info/github/dropbox/dropbox-js/master/class_index.html)
|
||||
covers the entire library, so you should not need to read the library source
|
||||
code to understand how to use it.
|
||||
|
||||
_Please open an issue if the documentation is unclear!_
|
||||
|
||||
The
|
||||
[sample apps](https://github.com/dropbox/dropbox-js/tree/master/samples),
|
||||
are written in CoffeeScript. Please use the `Try CoffeeScript` button on the
|
||||
[CoffeeScript](http://coffeescript.org/) home page to quickly compile the
|
||||
sample CoffeeScript into very readable JavaScript.
|
||||
|
||||
|
||||
## Do I need to learn CoffeeScript to know how dropbox.js works?
|
||||
|
||||
**No.**
|
||||
|
||||
You can follow the
|
||||
[development guide](https://github.com/dropbox/dropbox-js/blob/master/doc/development.md)
|
||||
to build the un-minified JavaScript library in `lib/dropbox.js` and then use
|
||||
your editor's find feature to get to the source code for the methods that you
|
||||
are interested in.
|
||||
|
||||
The building instructions in the development guide do not require familiarity
|
||||
with CoffeeScript.
|
||||
|
||||
|
||||
## Do I need to learn CoffeeScript to modify dropbox.js?
|
||||
|
||||
**Yes, but you might not need to modify the library.**
|
||||
|
||||
You do need to learn CoffeeScript to change the `dropbox.js` source code. At
|
||||
the same time, you can take advantage of the library hooks and the dynamic
|
||||
nature of the JavaScript language to change the behavior of `dropbox.js`
|
||||
without touching the source code.
|
||||
|
||||
* You can implement your OAuth strategy.
|
||||
* You can add methods to the prototype classes such as `Dropbox.Client` to
|
||||
implement custom operations. _Please open an issue if you think your addition
|
||||
is generally useful!_
|
||||
* You can replace internal classes such as `Dropbox.Xhr` (or selectively
|
||||
replace methods) with wrappers that tweak the original behavior
|
||||
|
||||
|
||||
## Can I contribute to dropbox.js without learning CoffeeScript?
|
||||
|
||||
**Yes.**
|
||||
|
||||
Most of the development time is spent on API design, developing tests,
|
||||
documentation and sample code. Contributing a good testing strategy with a bug
|
||||
report can save us 90% of the development time. A feature request that also
|
||||
includes a well thought-out API change proposal and testing strategy can also
|
||||
save us 90-95% of the implementation time.
|
||||
|
||||
At the same time, _please open issues for bugs and feature requests even if you
|
||||
don't have time to include any of the above_. Knowing of a problem is the first
|
||||
step towards fixing it.
|
||||
|
||||
Last, please share your constructive suggestions on how to make `dropbox.js`
|
||||
easier to use for JavaScript developers that don't speak CoffeeScript.
|
||||
|
||||
|
||||
## Can I complain to get dropbox.js to switch away from CoffeeScript?
|
||||
|
||||
**No.**
|
||||
|
||||
At the moment, 100% of the library's development comes from unpaid, voluntary
|
||||
efforts. Switching to JavaScript would reduce the efficiency of these efforts,
|
||||
and it would kill developer motivation.
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
# dropbox.js Development
|
||||
|
||||
Read this document if you want to build `dropbox.js` or modify its source code.
|
||||
If you want to write applications using dropbox.js, check out the
|
||||
[Getting Started](getting_started.md).
|
||||
|
||||
The library is written using [CoffeeScript](http://coffeescript.org/), built
|
||||
using [cake](http://coffeescript.org/documentation/docs/cake.html), minified
|
||||
using [uglify.js](https://github.com/mishoo/UglifyJS/), tested using
|
||||
[mocha](http://visionmedia.github.com/mocha/) and
|
||||
[chai.js](http://chaijs.com/), and packaged using [npm](https://npmjs.org/).
|
||||
|
||||
If you don't "speak" CoffeeScript,
|
||||
[this document](https://github.com/dropbox/dropbox-js/blob/master/doc/coffee_faq.md)
|
||||
might address some of your concerns.
|
||||
|
||||
|
||||
## Dev Environment Setup
|
||||
|
||||
Install [node.js](http://nodejs.org/#download) to get `npm` (the node
|
||||
package manager), then use it to install the libraries required by the test
|
||||
suite.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/dropbox/dropbox-js.git
|
||||
cd dropbox-js
|
||||
npm install
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
Run `npm pack` and ignore any deprecation warnings that might come up.
|
||||
|
||||
```bash
|
||||
npm pack
|
||||
```
|
||||
|
||||
The build output is in the `lib/` directory. `dropbox.js` is the compiled
|
||||
library that ships in the npm package, and `dropbox.min.js` is a minified
|
||||
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.
|
||||
|
||||
```bash
|
||||
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, 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
|
||||
run `npm pack`. Please run the tests in both node.js and a browser before
|
||||
submitting pull requests.
|
||||
|
||||
The tests store all their data in folders named along the lines of
|
||||
`js tests.0.ac1n6lgs0e3lerk9`. If tests fail, you might have to clean up these
|
||||
folders yourself.
|
||||
|
||||
|
||||
### Solving Browser Issues
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
cake extension
|
||||
```
|
||||
|
||||
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, 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).
|
||||
|
|
@ -1,280 +0,0 @@
|
|||
# Getting Started
|
||||
|
||||
This is a guide to writing your first dropbox.js application.
|
||||
|
||||
|
||||
## Library Setup
|
||||
|
||||
This section describes how to get the library hooked up into your application.
|
||||
|
||||
### Browser Applications
|
||||
|
||||
To get started right away, place this snippet in your page's `<head>`.
|
||||
|
||||
```html
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.8.1/dropbox.min.js">
|
||||
</script>
|
||||
```
|
||||
|
||||
The snippet is not a typo. [cdnjs](https://cdnjs.com) recommends using
|
||||
[protocol-relative URLs](http://paulirish.com/2010/the-protocol-relative-url/).
|
||||
|
||||
To get the latest development build of dropbox.js, follow the steps in the
|
||||
[development guide](https://github.com/dropbox/dropbox-js/blob/master/doc/development.md).
|
||||
|
||||
|
||||
#### "Powered by Dropbox" Static Web Apps
|
||||
|
||||
Before writing any source code, use the
|
||||
[console app](https://dl-web.dropbox.com/spa/pjlfdak1tmznswp/powered_by.js/public/index.html)
|
||||
to set up your Dropbox. After adding an application, place the source code at
|
||||
`/Apps/Static Web Apps/my_awesome_app/public`. You should find a pre-generated
|
||||
`index.html` file in there.
|
||||
|
||||
### node.js Applications
|
||||
|
||||
First, install the `dropbox` [npm](https://npmjs.org/) package.
|
||||
|
||||
```bash
|
||||
npm install dropbox
|
||||
```
|
||||
|
||||
Once the npm package is installed, the following `require` statement lets you
|
||||
access the same API as browser applications
|
||||
|
||||
```javascript
|
||||
var Dropbox = require("dropbox");
|
||||
```
|
||||
|
||||
|
||||
## Initialization
|
||||
|
||||
[Register your application](https://www.dropbox.com/developers/apps) to obtain
|
||||
an API key. Read the brief
|
||||
[API core concepts intro](https://www.dropbox.com/developers/start/core).
|
||||
|
||||
Once you have an API key, use it to create a `Dropbox.Client`.
|
||||
|
||||
```javascript
|
||||
var client = new Dropbox.Client({
|
||||
key: "your-key-here", secret: "your-secret-here", sandbox: true
|
||||
});
|
||||
```
|
||||
|
||||
If your application requires full Dropbox access, leave out the `sandbox: true`
|
||||
parameter.
|
||||
|
||||
|
||||
### Browser and Open-Source Applications
|
||||
|
||||
The Dropbox API guidelines ask that the API key and secret is never exposed in
|
||||
cleartext. This is an issue for browser-side and open-source applications.
|
||||
|
||||
To meet this requirement,
|
||||
[encode your API key](https://dl-web.dropbox.com/spa/pjlfdak1tmznswp/api_keys.js/public/index.html)
|
||||
and pass the encoded key string to the `Dropbox.Client` constructor.
|
||||
|
||||
```javascript
|
||||
var client = new Dropbox.Client({
|
||||
key: "encoded-key-string|it-is-really-really-long", sandbox: true
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## Authentication
|
||||
|
||||
Before you can make any API calls, you need to authenticate your application's
|
||||
user with Dropbox, and have them authorize your app's to access their Dropbox.
|
||||
|
||||
This process follows the [OAuth 1.0](http://tools.ietf.org/html/rfc5849)
|
||||
protocol, which entails sending the user to a Web page on `www.dropbox.com`,
|
||||
and then having them redirected back to your application. Each Web application
|
||||
has its requirements, so `dropbox.js` lets you customize the authentication
|
||||
process by implementing an
|
||||
[OAuth driver](https://github.com/dropbox/dropbox-js/blob/master/src/drivers.coffee).
|
||||
|
||||
At the same time, dropbox.js ships with a couple of OAuth drivers, and you
|
||||
should take advantage of them as you prototype your application.
|
||||
|
||||
Read the
|
||||
[authentication doc](https://github.com/dropbox/dropbox-js/blob/master/doc/auth_drivers.md)
|
||||
for further information about writing an OAuth driver, and to learn about all
|
||||
the drivers that ship with `dropbox.js`.
|
||||
|
||||
### Browser Setup
|
||||
|
||||
The following snippet will set up the recommended driver.
|
||||
|
||||
```javascript
|
||||
client.authDriver(new Dropbox.Drivers.Redirect());
|
||||
```
|
||||
|
||||
The
|
||||
[authentication doc](https://github.com/dropbox/dropbox-js/blob/master/doc/auth_drivers.md)
|
||||
describes some useful options that you can pass to the
|
||||
`Dropbox.Drivers.Redirect` constructor.
|
||||
|
||||
### node.js Setup
|
||||
|
||||
Single-process node.js applications should create one driver to authenticate
|
||||
all the clients.
|
||||
|
||||
```javascript
|
||||
client.authDriver(new Dropbox.Drivers.NodeServer(8191));
|
||||
```
|
||||
|
||||
The
|
||||
[authentication doc](https://github.com/dropbox/dropbox-js/blob/master/doc/auth_drivers.md)
|
||||
has useful tips on using the `NodeServer` driver.
|
||||
|
||||
### Shared Code
|
||||
|
||||
After setting up an OAuth driver, authenticating the user is one method call
|
||||
away.
|
||||
|
||||
```javascript
|
||||
client.authenticate(function(error, client) {
|
||||
if (error) {
|
||||
// Replace with a call to your own error-handling code.
|
||||
//
|
||||
// Don't forget to return from the callback, so you don't execute the code
|
||||
// that assumes everything went well.
|
||||
return showError(error);
|
||||
}
|
||||
|
||||
// Replace with a call to your own application code.
|
||||
//
|
||||
// The user authorized your app, and everything went well.
|
||||
// client is a Dropbox.Client instance that you can use to make API calls.
|
||||
doSomethingCool(client);
|
||||
});
|
||||
```
|
||||
|
||||
## Error Handlng
|
||||
|
||||
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.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) {
|
||||
switch (error.status) {
|
||||
case 401:
|
||||
// If you're using dropbox.js, the only cause behind this error is that
|
||||
// the user token expired.
|
||||
// Get the user through the authentication flow again.
|
||||
break;
|
||||
|
||||
case 404:
|
||||
// The file or folder you tried to access is not in the user's Dropbox.
|
||||
// Handling this error is specific to your application.
|
||||
break;
|
||||
|
||||
case 507:
|
||||
// The user is over their Dropbox quota.
|
||||
// Tell them their Dropbox is full. Refreshing the page won't help.
|
||||
break;
|
||||
|
||||
case 503:
|
||||
// Too many API requests. Tell the user to try again later.
|
||||
// Long-term, optimize your code to use fewer API calls.
|
||||
break;
|
||||
|
||||
case 400: // Bad input parameter
|
||||
case 403: // Bad OAuth request.
|
||||
case 405: // Request method not expected
|
||||
default:
|
||||
// Caused by a bug in dropbox.js, in your application, or in Dropbox.
|
||||
// Tell the user an error occurred, ask them to refresh the page.
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
`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
|
||||
|
||||
Authentication was the hard part of the API integration, and error handling was
|
||||
the most boring part. Now that these are both behind us, you can interact
|
||||
with the user's Dropbox and focus on coding up your application!
|
||||
|
||||
The following sections have some commonly used code snippets. The
|
||||
[Dropbox.Client API reference](http://coffeedoc.info/github/dropbox/dropbox-js/master/classes/Dropbox/Client.html)
|
||||
will help you navigate less common scenarios, and the
|
||||
[Dropbox REST API reference](https://www.dropbox.com/developers/reference/api)
|
||||
describes the underlying HTTP protocol, and can come in handy when debugging
|
||||
your application, or if you want to extend dropbox.js.
|
||||
|
||||
### User Info
|
||||
|
||||
```javascript
|
||||
client.getUserInfo(function(error, userInfo) {
|
||||
if (error) {
|
||||
return showError(error); // Something went wrong.
|
||||
}
|
||||
|
||||
alert("Hello, " + userInfo.name + "!");
|
||||
});
|
||||
```
|
||||
|
||||
### Write a File
|
||||
|
||||
```javascript
|
||||
client.writeFile("hello_world.txt", "Hello, world!\n", function(error, stat) {
|
||||
if (error) {
|
||||
return showError(error); // Something went wrong.
|
||||
}
|
||||
|
||||
alert("File saved as revision " + stat.revisionTag);
|
||||
});
|
||||
```
|
||||
|
||||
### Read a File
|
||||
|
||||
```javascript
|
||||
client.readFile("hello_world.txt", function(error, data) {
|
||||
if (error) {
|
||||
return showError(error); // Something went wrong.
|
||||
}
|
||||
|
||||
alert(data); // data has the file's contents
|
||||
});
|
||||
```
|
||||
|
||||
### List a Directory's Contents
|
||||
|
||||
```javascript
|
||||
client.readdir("/", function(error, entries) {
|
||||
if (error) {
|
||||
return showError(error); // Something went wrong.
|
||||
}
|
||||
|
||||
alert("Your Dropbox contains " + entries.join(", "));
|
||||
});
|
||||
```
|
||||
|
||||
### Sample Applications
|
||||
|
||||
Check out the
|
||||
[sample apps](https://github.com/dropbox/dropbox-js/tree/master/samples)
|
||||
to see how all these concepts play out together.
|
||||
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# dropbox.js Build Directory
|
||||
|
||||
dropbox.js is written in [CoffeeScript](http://coffeescript.org/), and its
|
||||
source code is in the `src/` directory. The `lib/` directory contains the
|
||||
compiled JavaScript produced by the build process.
|
||||
|
||||
The
|
||||
[development guide](https://github.com/dropbox/dropbox-js/blob/master/doc/development.md)
|
||||
contains a step-by-step guide to the library's build process, and will help you
|
||||
populate this directory.
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
{
|
||||
"name": "dropbox",
|
||||
"version": "0.8.1",
|
||||
"description": "Client library for the Dropbox API",
|
||||
"keywords": ["dropbox", "filesystem", "storage"],
|
||||
"homepage": "http://github.com/dropbox/dropbox-js",
|
||||
"author": "Victor Costan <victor@costan.us> (http://www.costan.us)",
|
||||
"license": "MIT",
|
||||
"contributors": [
|
||||
"Aakanksha Sarda <aaki@mit.edu>"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dropbox/dropbox-js.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"open": ">= 0.0.2",
|
||||
"xmlhttprequest": ">= 1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": ">= 0.1.22",
|
||||
"chai": ">= 1.4.0",
|
||||
"codo": ">= 1.5.4",
|
||||
"coffee-script": ">= 1.4.0",
|
||||
"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.3.0",
|
||||
"uglify-js": ">= 2.2.3"
|
||||
},
|
||||
"main": "lib/dropbox.js",
|
||||
"directories": {
|
||||
"doc": "doc",
|
||||
"lib": "lib",
|
||||
"src": "src",
|
||||
"test": "test"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublish": "node_modules/coffee-script/bin/cake build",
|
||||
"test": "node_modules/coffee-script/bin/cake test"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
# 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.
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
# 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<li>} 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<DOMElement>} 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<Task>} 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'
|
||||
|
|
@ -1,298 +0,0 @@
|
|||
// 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;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
|
@ -1,10 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" width="100px" height="87.5px" viewBox="0 0 100 87.5" style="enable-background:new 0 0 100 87.5;" xml:space="preserve">
|
||||
<path style="fill:#010101;" d="M12.5,6.25c0,3.455-2.795,6.25-6.25,6.25C2.795,12.5,0,9.705,0,6.25C0,2.795,2.795,0,6.25,0 C9.705,0,12.5,2.795,12.5,6.25z"/>
|
||||
<rect x="25" style="fill:#010101;" width="75" height="12.5"/>
|
||||
<path style="fill:#010101;" d="M12.5,56.25c0,3.455-2.795,6.25-6.25,6.25C2.795,62.5,0,59.705,0,56.25S2.795,50,6.25,50 C9.705,50,12.5,52.795,12.5,56.25z"/>
|
||||
<rect x="25" y="50" style="fill:#010101;" width="75" height="12.5"/>
|
||||
<path style="fill:#010101;" d="M12.5,31.25c0,3.455-2.795,6.25-6.25,6.25C2.795,37.5,0,34.705,0,31.25S2.795,25,6.25,25 C9.705,25,12.5,27.795,12.5,31.25z"/>
|
||||
<path style="fill:#010101;" d="M12.5,81.25c0,3.455-2.795,6.25-6.25,6.25C2.795,87.5,0,84.705,0,81.25S2.795,75,6.25,75 C9.705,75,12.5,77.795,12.5,81.25z"/>
|
||||
<rect x="25" y="25" style="fill:#010101;" width="75" height="12.5"/>
|
||||
<rect x="25" y="75" style="fill:#010101;" width="75" height="12.5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 597 B |
|
Before Width: | Height: | Size: 124 B |
|
Before Width: | Height: | Size: 326 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
|
@ -1,68 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- vim: set tabstop=2 shiftwidth=2 softtabstop=2 expandtab : -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Checkbox - dropbox.js Sample Application</title>
|
||||
<link rel="icon" type="image/png" href="images/icon16.png" />
|
||||
<link rel="stylesheet/less" type="text/css" href="./checkbox.less" />
|
||||
<script type="text/javascript" src="lib/coffee-script.js"></script>
|
||||
<script type="text/javascript" src="lib/dropbox.js"></script>
|
||||
<script type="text/javascript" src="lib/jquery.js"></script>
|
||||
<script type="text/javascript" src="lib/less.js"></script>
|
||||
<script type="text/coffeescript" src="./checkbox.coffee"></script>
|
||||
</head>
|
||||
<body>
|
||||
<aside id="error-notice" class="hidden">
|
||||
<form action="#" method="GET">
|
||||
Something went wrong :(
|
||||
<button type="submit" id="error-refresh-button">
|
||||
<img src="images/not_done.png" alt="" /> reload the app
|
||||
</button>
|
||||
</form>
|
||||
</aside>
|
||||
<article id="app-ui" class="hidden">
|
||||
<h1>
|
||||
checkbox
|
||||
<small>powered by
|
||||
<a href="https://www.dropbox.com/developers">dropbox</a>
|
||||
</small>
|
||||
</h1>
|
||||
<div id="notebook-page">
|
||||
<aside id="user-info">
|
||||
<button type="button" id="signout-button">
|
||||
<img src="images/remove.png" alt="" /> Sign out
|
||||
</button>
|
||||
<span id="user-name" />
|
||||
</aside>
|
||||
<h2 id="active-task-heading">Active</h2>
|
||||
<ol class="task-list" id="active-task-list"></ol>
|
||||
<form action="" method="GET" id="new-task-form">
|
||||
<input type="text" id="new-task-name" class="task-name"
|
||||
required="required" placeholder="e.g., buy milk" />
|
||||
<button type="submit" id="new-task-button">
|
||||
<img src="images/add.png" alt="" /> Add
|
||||
</button>
|
||||
</form>
|
||||
<div class="empty-task"> </div>
|
||||
<h2 id="done-task-heading">Done</h2>
|
||||
<ol class="task-list" id="done-task-list"></ol>
|
||||
</div>
|
||||
</article>
|
||||
<script type="text/html" id="task-template">
|
||||
<li class="task">
|
||||
<span class="task-name" />
|
||||
<span class="task-actions">
|
||||
<button type="button" class="task-done-button">
|
||||
<img src="images/done.png" alt="" /> Done
|
||||
</button>
|
||||
<button type="button" class="task-active-button">
|
||||
<img src="images/not_done.png" alt="" /> Undo
|
||||
</button>
|
||||
<button type="button" class="task-remove-button">
|
||||
<img src="images/remove.png" alt="" /> Delete
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
# Main entry point to the Dropbox API.
|
||||
class Dropbox
|
||||
constructor: (options) ->
|
||||
@client = new DropboxClient options
|
||||
|
||||
# NOTE: this is not yet implemented.
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
# Information about a failed call to the Dropbox API.
|
||||
class Dropbox.ApiError
|
||||
# @property {Number} the HTTP error code (e.g., 403)
|
||||
status: undefined
|
||||
|
||||
# @property {String} the HTTP method of the failed request (e.g., 'GET')
|
||||
method: undefined
|
||||
|
||||
# @property {String} the URL of the failed request
|
||||
url: undefined
|
||||
|
||||
# @property {?String} the body of the HTTP error response; can be null if
|
||||
# the error was caused by a network failure or by a security issue
|
||||
responseText: undefined
|
||||
|
||||
# @property {?Object} the result of parsing the JSON in the HTTP error
|
||||
# response; can be null if the API server didn't return JSON, or if the
|
||||
# HTTP response body is unavailable
|
||||
response: undefined
|
||||
|
||||
# Wraps a failed XHR call to the Dropbox API.
|
||||
#
|
||||
# @param {String} method the HTTP verb of the API request (e.g., 'GET')
|
||||
# @param {String} url the URL of the API request
|
||||
# @param {XMLHttpRequest} xhr the XMLHttpRequest instance of the failed
|
||||
# request
|
||||
constructor: (xhr, @method, @url) ->
|
||||
@status = xhr.status
|
||||
if xhr.responseType
|
||||
try
|
||||
text = xhr.response or xhr.responseText
|
||||
catch e
|
||||
try
|
||||
text = xhr.responseText
|
||||
catch e
|
||||
text = null
|
||||
else
|
||||
try
|
||||
text = xhr.responseText
|
||||
catch e
|
||||
text = null
|
||||
|
||||
if text
|
||||
try
|
||||
@responseText = text.toString()
|
||||
@response = JSON.parse text
|
||||
catch e
|
||||
@response = null
|
||||
else
|
||||
@responseText = '(no response)'
|
||||
@response = null
|
||||
|
||||
# Used when the error is printed out by developers.
|
||||
toString: ->
|
||||
"Dropbox API error #{@status} from #{@method} #{@url} :: #{@responseText}"
|
||||
|
||||
# Used by some testing frameworks.
|
||||
inspect: ->
|
||||
@toString()
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
# node.js implementation of atob and btoa
|
||||
|
||||
if window?
|
||||
if window.atob and window.btoa
|
||||
atob = (string) -> window.atob string
|
||||
btoa = (base64) -> window.btoa base64
|
||||
else
|
||||
# IE < 10 doesn't implement the standard atob / btoa functions.
|
||||
base64Digits =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
|
||||
btoaNibble = (accumulator, bytes, result) ->
|
||||
limit = 3 - bytes
|
||||
accumulator <<= limit * 8
|
||||
i = 3
|
||||
while i >= limit
|
||||
result.push base64Digits.charAt((accumulator >> (i * 6)) & 0x3F)
|
||||
i -= 1
|
||||
i = bytes
|
||||
while i < 3
|
||||
result.push '='
|
||||
i += 1
|
||||
null
|
||||
atobNibble = (accumulator, digits, result) ->
|
||||
limit = 4 - digits
|
||||
accumulator <<= limit * 6
|
||||
i = 2
|
||||
while i >= limit
|
||||
result.push String.fromCharCode((accumulator >> (8 * i)) & 0xFF)
|
||||
i -= 1
|
||||
null
|
||||
|
||||
btoa = (string) ->
|
||||
result = []
|
||||
accumulator = 0
|
||||
bytes = 0
|
||||
for i in [0...string.length]
|
||||
accumulator = (accumulator << 8) | string.charCodeAt(i)
|
||||
bytes += 1
|
||||
if bytes is 3
|
||||
btoaNibble accumulator, bytes, result
|
||||
accumulator = bytes = 0
|
||||
|
||||
if bytes > 0
|
||||
btoaNibble accumulator, bytes, result
|
||||
result.join ''
|
||||
|
||||
atob = (base64) ->
|
||||
result = []
|
||||
accumulator = 0
|
||||
digits = 0
|
||||
for i in [0...base64.length]
|
||||
digit = base64.charAt i
|
||||
break if digit is '='
|
||||
accumulator = (accumulator << 6) | base64Digits.indexOf(digit)
|
||||
digits += 1
|
||||
if digits is 4
|
||||
atobNibble accumulator, digits, result
|
||||
accumulator = digits = 0
|
||||
|
||||
if digits > 0
|
||||
atobNibble accumulator, digits, result
|
||||
result.join ''
|
||||
|
||||
else
|
||||
# NOTE: the npm packages atob and btoa don't do base64-encoding correctly.
|
||||
atob = (arg) ->
|
||||
buffer = new Buffer arg, 'base64'
|
||||
(String.fromCharCode(buffer[i]) for i in [0...buffer.length]).join ''
|
||||
btoa = (arg) ->
|
||||
buffer = new Buffer(arg.charCodeAt(i) for i in [0...arg.length])
|
||||
buffer.toString 'base64'
|
||||
|
|
@ -1,342 +0,0 @@
|
|||
# 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()
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
# 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 = """
|
||||
<!doctype html>
|
||||
<script type="text/javascript">window.close();</script>
|
||||
<p>Please close this window.</p>
|
||||
"""
|
||||
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
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
# Documentation for the interface to a Dropbox OAuth driver.
|
||||
class Dropbox.AuthDriver
|
||||
# The callback URL that should be supplied to the OAuth /authorize call.
|
||||
#
|
||||
# The driver must be able to intercept redirects to the returned URL, in
|
||||
# order to know when a user has completed the authorization flow.
|
||||
#
|
||||
# @return {String} an absolute URL
|
||||
url: ->
|
||||
'https://some.url'
|
||||
|
||||
# Redirects users to /authorize and waits for them to complete the flow.
|
||||
#
|
||||
# This method is called when the OAuth process reaches the REQUEST state,
|
||||
# meaning the client has a request token that must be authorized by the user.
|
||||
#
|
||||
# @param {String} authUrl the URL that users should be sent to in order to
|
||||
# authorize the application's token; this points to a Web page on
|
||||
# Dropbox' servers
|
||||
# @param {String} token the OAuth token that the user is authorizing; this
|
||||
# will be provided by the Dropbox servers as a query parameter when the
|
||||
# user is redirected to the URL returned by the driver's url() method
|
||||
# @param {String} tokenSecret the secret associated with the given OAuth
|
||||
# token; the driver may store this together with the token
|
||||
# @param {function()} callback called when users have completed the
|
||||
# authorization flow; the driver should call this when Dropbox redirects
|
||||
# users to the URL returned by the url() method, and the 'token' query
|
||||
# parameter matches the value of the token parameter
|
||||
doAuthorize: (authUrl, token, tokenSecret, callback) ->
|
||||
callback 'oauth-token'
|
||||
|
||||
# Called when there is some progress in the OAuth process.
|
||||
#
|
||||
# The OAuth process goes through the following states:
|
||||
#
|
||||
# * Dropbox.Client.RESET - the client has no OAuth token, and is about to
|
||||
# ask for a request token
|
||||
# * Dropbox.Client.REQUEST - the client has a request OAuth token, and the
|
||||
# user must go to an URL on the Dropbox servers to authorize the token
|
||||
# * Dropbox.Client.AUTHORIZED - the client has a request OAuth token that
|
||||
# was authorized by the user, and is about to exchange it for an access
|
||||
# token
|
||||
# * Dropbox.Client.DONE - the client has an access OAuth token that can be
|
||||
# used for all API calls; the OAuth process is complete, and the callback
|
||||
# passed to authorize is about to be called
|
||||
# * Dropbox.Client.SIGNED_OFF - the client's Dropbox.Client#signOut() was
|
||||
# called, and the client's OAuth token was invalidated
|
||||
# * Dropbox.Client.ERROR - the client encounered an error during the OAuth
|
||||
# process; the callback passed to authorize is about to be called with the
|
||||
# error information
|
||||
#
|
||||
# @param {Dropbox.Client} client the client performing the OAuth process
|
||||
# @param {function()} callback called when onAuthStateChange acknowledges the
|
||||
# state change
|
||||
onAuthStateChange: (client, callback) ->
|
||||
callback()
|
||||
|
||||
# Namespace for authentication drivers.
|
||||
Dropbox.Drivers = {}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
# 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
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
# HMAC-SHA1 implementation heavily inspired from
|
||||
# http://pajhome.org.uk/crypt/md5/sha1.js
|
||||
|
||||
# Base64-encoded HMAC-SHA1.
|
||||
#
|
||||
# @param {String} string the ASCII string to be signed
|
||||
# @param {String} key the HMAC key
|
||||
# @return {String} a base64-encoded HMAC of the given string and key
|
||||
base64HmacSha1 = (string, key) ->
|
||||
arrayToBase64 hmacSha1(stringToArray(string), stringToArray(key),
|
||||
string.length, key.length)
|
||||
|
||||
# Base64-encoded SHA1.
|
||||
#
|
||||
# @param {String} string the ASCII string to be hashed
|
||||
# @return {String} a base64-encoded SHA1 hash of the given string
|
||||
base64Sha1 = (string) ->
|
||||
arrayToBase64 sha1(stringToArray(string), string.length)
|
||||
|
||||
# SHA1 and HMAC-SHA1 versions that use the node.js builtin crypto.
|
||||
unless window?
|
||||
crypto = require 'crypto'
|
||||
base64HmacSha1 = (string, key) ->
|
||||
hmac = crypto.createHmac 'sha1', key
|
||||
hmac.update string
|
||||
hmac.digest 'base64'
|
||||
base64Sha1 = (string) ->
|
||||
hash = crypto.createHash 'sha1'
|
||||
hash.update string
|
||||
hash.digest 'base64'
|
||||
|
||||
# HMAC-SHA1 implementation.
|
||||
#
|
||||
# @param {Array} string the HMAC input, as an array of 32-bit numbers
|
||||
# @param {Array} key the HMAC input, as an array of 32-bit numbers
|
||||
# @param {Number} length the length of the HMAC input, in bytes
|
||||
# @return {Array} the HMAC output, as an array of 32-bit numbers
|
||||
hmacSha1 = (string, key, length, keyLength) ->
|
||||
if key.length > 16
|
||||
key = sha1 key, keyLength
|
||||
|
||||
ipad = (key[i] ^ 0x36363636 for i in [0...16])
|
||||
opad = (key[i] ^ 0x5C5C5C5C for i in [0...16])
|
||||
|
||||
hash1 = sha1 ipad.concat(string), 64 + length
|
||||
sha1 opad.concat(hash1), 64 + 20
|
||||
|
||||
# SHA1 implementation.
|
||||
#
|
||||
# @param {Array} string the SHA1 input, as an array of 32-bit numbers; the
|
||||
# computation trashes the array
|
||||
# @param {Number} length the number of bytes in the SHA1 input; used in the
|
||||
# SHA1 padding algorithm
|
||||
# @return {Array<Number>} the SHA1 output, as an array of 32-bit numbers
|
||||
sha1 = (string, length) ->
|
||||
string[length >> 2] |= 1 << (31 - ((length & 0x03) << 3))
|
||||
string[(((length + 8) >> 6) << 4) + 15] = length << 3
|
||||
|
||||
state = Array 80
|
||||
a = 1732584193 # 0x67452301
|
||||
b = -271733879 # 0xefcdab89
|
||||
c = -1732584194 # 0x98badcfe
|
||||
d = 271733878 # 0x10325476
|
||||
e = -1009589776 # 0xc3d2e1f0
|
||||
|
||||
i = 0
|
||||
limit = string.length
|
||||
# Uncomment the line below to debug packing.
|
||||
# console.log string.map(xxx)
|
||||
while i < limit
|
||||
a0 = a
|
||||
b0 = b
|
||||
c0 = c
|
||||
d0 = d
|
||||
e0 = e
|
||||
|
||||
for j in [0...80]
|
||||
if j < 16
|
||||
state[j] = string[i + j]
|
||||
else
|
||||
state[j] = rotateLeft32 state[j - 3] ^ state[j - 8] ^ state[j - 14] ^
|
||||
state[j - 16], 1
|
||||
if j < 20
|
||||
ft = (b & c) | ((~b) & d)
|
||||
kt = 1518500249 # 0x5a827999
|
||||
else if j < 40
|
||||
ft = b ^ c ^ d
|
||||
kt = 1859775393 # 0x6ed9eba1
|
||||
else if j < 60
|
||||
ft = (b & c) | (b & d) | (c & d)
|
||||
kt = -1894007588 # 0x8f1bbcdc
|
||||
else
|
||||
ft = b ^ c ^ d
|
||||
kt = -899497514 # 0xca62c1d6
|
||||
t = add32 add32(rotateLeft32(a, 5), ft), add32(add32(e, state[j]), kt)
|
||||
e = d
|
||||
d = c
|
||||
c = rotateLeft32 b, 30
|
||||
b = a
|
||||
a = t
|
||||
# Uncomment the line below to debug block computation.
|
||||
# console.log [xxx(a), xxx(b), xxx(c), xxx(d), xxx(e)]
|
||||
a = add32 a, a0
|
||||
b = add32 b, b0
|
||||
c = add32 c, c0
|
||||
d = add32 d, d0
|
||||
e = add32 e, e0
|
||||
i += 16
|
||||
# Uncomment the line below to see the input to the base64 encoder.
|
||||
# console.log [xxx(a), xxx(b), xxx(c), xxx(d), xxx(e)]
|
||||
[a, b, c, d, e]
|
||||
|
||||
###
|
||||
# Uncomment the definition below for debugging.
|
||||
#
|
||||
# Returns the hexadecimal representation of a 32-bit number.
|
||||
xxx = (n) ->
|
||||
if n < 0
|
||||
n = (1 << 30) * 4 + n
|
||||
n.toString 16
|
||||
###
|
||||
|
||||
# Rotates a 32-bit word.
|
||||
#
|
||||
# @param {Number} value the 32-bit number to be rotated
|
||||
# @param {Number} count the number of bits (0..31) to rotate by
|
||||
# @return {Number} the rotated value
|
||||
rotateLeft32 = (value, count) ->
|
||||
(value << count) | (value >>> (32 - count))
|
||||
|
||||
# 32-bit unsigned addition.
|
||||
#
|
||||
# @param {Number} a, b the 32-bit numbers to be added modulo 2^32
|
||||
# @return {Number} the 32-bit representation of a + b
|
||||
add32 = (a, b) ->
|
||||
low = (a & 0xFFFF) + (b & 0xFFFF)
|
||||
high = (a >> 16) + (b >> 16) + (low >> 16)
|
||||
(high << 16) | (low & 0xFFFF)
|
||||
|
||||
# Converts a 32-bit number array into a base64-encoded string.
|
||||
#
|
||||
# @param {Array} an array of big-endian 32-bit numbers
|
||||
# @return {String} base64 encoding of the given array of numbers
|
||||
arrayToBase64 = (array) ->
|
||||
string = ""
|
||||
i = 0
|
||||
limit = array.length * 4
|
||||
while i < limit
|
||||
i2 = i
|
||||
trit = ((array[i2 >> 2] >> ((3 - (i2 & 3)) << 3)) & 0xFF) << 16
|
||||
i2 += 1
|
||||
trit |= ((array[i2 >> 2] >> ((3 - (i2 & 3)) << 3)) & 0xFF) << 8
|
||||
i2 += 1
|
||||
trit |= (array[i2 >> 2] >> ((3 - (i2 & 3)) << 3)) & 0xFF
|
||||
|
||||
string += _base64Digits[(trit >> 18) & 0x3F]
|
||||
string += _base64Digits[(trit >> 12) & 0x3F]
|
||||
i += 1
|
||||
if i >= limit
|
||||
string += '='
|
||||
else
|
||||
string += _base64Digits[(trit >> 6) & 0x3F]
|
||||
i += 1
|
||||
if i >= limit
|
||||
string += '='
|
||||
else
|
||||
string += _base64Digits[trit & 0x3F]
|
||||
i += 1
|
||||
string
|
||||
|
||||
_base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
# Converts an ASCII string into array of 32-bit numbers.
|
||||
stringToArray = (string) ->
|
||||
array = []
|
||||
mask = 0xFF
|
||||
for i in [0...string.length]
|
||||
array[i >> 2] |= (string.charCodeAt(i) & mask) << ((3 - (i & 3)) << 3)
|
||||
array
|
||||
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
# Stripped-down OAuth implementation that works with the Dropbox API server.
|
||||
class Dropbox.Oauth
|
||||
# Creates an Oauth instance that manages an application's keys and token.
|
||||
#
|
||||
# @param {Object} options the following properties
|
||||
# @option options {String} key the Dropbox application's key (consumer key,
|
||||
# in OAuth vocabulary); browser-side applications should use
|
||||
# Dropbox.encodeKey to obtain an encoded key string, and pass it as the
|
||||
# key option
|
||||
# @option options {String} secret the Dropbox application's secret (consumer
|
||||
# secret, in OAuth vocabulary); browser-side applications should not use
|
||||
# the secret option; instead, they should pass the result of
|
||||
# Dropbox.encodeKey as the key option
|
||||
constructor: (options) ->
|
||||
@key = @k = null
|
||||
@secret = @s = null
|
||||
@token = null
|
||||
@tokenSecret = null
|
||||
@_appHash = null
|
||||
@reset options
|
||||
|
||||
# Creates an Oauth instance that manages an application's keys and token.
|
||||
#
|
||||
# @see Dropbox.Oauth#constructor for options
|
||||
reset: (options) ->
|
||||
if options.secret
|
||||
@k = @key = options.key
|
||||
@s = @secret = options.secret
|
||||
@_appHash = null
|
||||
else if options.key
|
||||
@key = options.key
|
||||
@secret = null
|
||||
secret = atob dropboxEncodeKey(@key).split('|', 2)[1]
|
||||
[k, s] = secret.split '?', 2
|
||||
@k = decodeURIComponent k
|
||||
@s = decodeURIComponent s
|
||||
@_appHash = null
|
||||
else
|
||||
unless @k
|
||||
throw new Error('No API key supplied')
|
||||
|
||||
if options.token
|
||||
@setToken options.token, options.tokenSecret
|
||||
else
|
||||
@setToken null, ''
|
||||
|
||||
# Sets the OAuth token to be used for future requests.
|
||||
setToken: (token, tokenSecret) ->
|
||||
if token and (not tokenSecret)
|
||||
throw new Error('No secret supplied with the user token')
|
||||
|
||||
@token = token
|
||||
@tokenSecret = tokenSecret || ''
|
||||
|
||||
# This is part of signing, but it's set here so it can be cached.
|
||||
@hmacKey = Dropbox.Xhr.urlEncodeValue(@s) + '&' +
|
||||
Dropbox.Xhr.urlEncodeValue(tokenSecret)
|
||||
null
|
||||
|
||||
# Computes the value of the Authorization HTTP header.
|
||||
#
|
||||
# This method mutates the params object, and removes all the OAuth-related
|
||||
# parameters from it.
|
||||
#
|
||||
# @param {String} method the HTTP method used to make the request ('GET',
|
||||
# 'POST', etc)
|
||||
# @param {String} url the HTTP URL (e.g. "http://www.example.com/photos")
|
||||
# that receives the request
|
||||
# @param {Object} params an associative array (hash) containing the HTTP
|
||||
# request parameters; the parameters should include the oauth_
|
||||
# parameters generated by calling {Dropbox.Oauth#boilerplateParams}
|
||||
# @return {String} the value to be used for the Authorization HTTP header
|
||||
authHeader: (method, url, params) ->
|
||||
@addAuthParams method, url, params
|
||||
|
||||
# Collect all the OAuth parameters.
|
||||
oauth_params = []
|
||||
for param, value of params
|
||||
if param.substring(0, 6) == 'oauth_'
|
||||
oauth_params.push param
|
||||
oauth_params.sort()
|
||||
|
||||
# Remove the parameters from the params hash and add them to the header.
|
||||
header = []
|
||||
for param in oauth_params
|
||||
header.push Dropbox.Xhr.urlEncodeValue(param) + '="' +
|
||||
Dropbox.Xhr.urlEncodeValue(params[param]) + '"'
|
||||
delete params[param]
|
||||
|
||||
# NOTE: the space after the comma is optional in the OAuth spec, so we'll
|
||||
# skip it to save some bandwidth
|
||||
'OAuth ' + header.join(',')
|
||||
|
||||
# Generates OAuth-required HTTP parameters.
|
||||
#
|
||||
# This method mutates the params object, and adds the OAuth-related
|
||||
# parameters to it.
|
||||
#
|
||||
# @param {String} method the HTTP method used to make the request ('GET',
|
||||
# 'POST', etc)
|
||||
# @param {String} url the HTTP URL (e.g. "http://www.example.com/photos")
|
||||
# that receives the request
|
||||
# @param {Object} params an associative array (hash) containing the HTTP
|
||||
# request parameters; the parameters should include the oauth_
|
||||
# parameters generated by calling {Dropbox.Oauth#boilerplateParams}
|
||||
# @return {String} the value to be used for the Authorization HTTP header
|
||||
addAuthParams: (method, url, params) ->
|
||||
# Augment params with OAuth parameters.
|
||||
@boilerplateParams params
|
||||
params.oauth_signature = @signature method, url, params
|
||||
params
|
||||
|
||||
# Adds boilerplate OAuth parameters to a request's parameter list.
|
||||
#
|
||||
# This should be called right before signing a request, to maximize the
|
||||
# chances that the OAuth timestamp will be fresh.
|
||||
#
|
||||
# @param {Object} params an associative array (hash) containing the
|
||||
# parameters for an OAuth request; the boilerplate parameters will be
|
||||
# added to this hash
|
||||
# @return {Object} params
|
||||
boilerplateParams: (params) ->
|
||||
params.oauth_consumer_key = @k
|
||||
params.oauth_nonce = @nonce()
|
||||
params.oauth_signature_method = 'HMAC-SHA1'
|
||||
params.oauth_token = @token if @token
|
||||
params.oauth_timestamp = Math.floor(Date.now() / 1000)
|
||||
params.oauth_version = '1.0'
|
||||
params
|
||||
|
||||
# Generates a nonce for an OAuth request.
|
||||
#
|
||||
# @return {String} the nonce to be used as the oauth_nonce parameter
|
||||
nonce: ->
|
||||
Date.now().toString(36) + Math.random().toString(36)
|
||||
|
||||
# Computes the signature for an OAuth request.
|
||||
#
|
||||
# @param {String} method the HTTP method used to make the request ('GET',
|
||||
# 'POST', etc)
|
||||
# @param {String} url the HTTP URL (e.g. "http://www.example.com/photos")
|
||||
# that receives the request
|
||||
# @param {Object} params an associative array (hash) containing the HTTP
|
||||
# request parameters; the parameters should include the oauth_
|
||||
# parameters generated by calling {Dropbox.Oauth#boilerplateParams}
|
||||
# @return {String} the signature, ready to be used as the oauth_signature
|
||||
# OAuth parameter
|
||||
signature: (method, url, params) ->
|
||||
string = method.toUpperCase() + '&' + Dropbox.Xhr.urlEncodeValue(url) +
|
||||
'&' + Dropbox.Xhr.urlEncodeValue(Dropbox.Xhr.urlEncode(params))
|
||||
base64HmacSha1 string, @hmacKey
|
||||
|
||||
# @return {String} a string that uniquely identifies the OAuth application
|
||||
appHash: ->
|
||||
return @_appHash if @_appHash
|
||||
@_appHash = base64Sha1(@k).replace(/\=/g, '')
|
||||
|
||||
|
||||
# Polyfill for Internet Explorer 8.
|
||||
unless Date.now?
|
||||
Date.now = () ->
|
||||
(new Date()).getTime()
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
# Necessary bits to get a browser-side app in production.
|
||||
|
||||
# Packs up a key and secret into a string, to bring script kiddies some pain.
|
||||
#
|
||||
# @param {String} key the application's API key
|
||||
# @param {String} secret the application's API secret
|
||||
# @return {String} encoded key string that can be passed as the key option to
|
||||
# the Dropbox.Client constructor
|
||||
dropboxEncodeKey = (key, secret) ->
|
||||
if secret
|
||||
secret = [encodeURIComponent(key), encodeURIComponent(secret)].join('?')
|
||||
key = for i in [0...(key.length / 2)]
|
||||
((key.charCodeAt(i * 2) & 15) * 16) + (key.charCodeAt(i * 2 + 1) & 15)
|
||||
else
|
||||
[key, secret] = key.split '|', 2
|
||||
key = atob key
|
||||
key = (key.charCodeAt(i) for i in [0...key.length])
|
||||
secret = atob secret
|
||||
|
||||
s = [0...256]
|
||||
y = 0
|
||||
for x in [0...256]
|
||||
y = (y + s[i] + key[x % key.length]) % 256
|
||||
[s[x], s[y]] = [s[y], s[x]]
|
||||
|
||||
x = y = 0
|
||||
result = for z in [0...secret.length]
|
||||
x = (x + 1) % 256
|
||||
y = (y + s[x]) % 256
|
||||
[s[x], s[y]] = [s[y], s[x]]
|
||||
k = s[(s[x] + s[y]) % 256]
|
||||
String.fromCharCode((k ^ secret.charCodeAt(z)) % 256)
|
||||
|
||||
key = (String.fromCharCode(key[i]) for i in [0...key.length])
|
||||
[btoa(key.join('')), btoa(result.join(''))].join '|'
|
||||
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
# Wraps the result of pullChanges, describing the changes in a user's Dropbox.
|
||||
class Dropbox.PulledChanges
|
||||
# Creates a new Dropbox.PulledChanges instance from a /delta API call result.
|
||||
#
|
||||
# @param {?Object} deltaInfo the parsed JSON of a /delta API call result
|
||||
# @return {?Dropbox.PulledChanges} a Dropbox.PulledChanges instance wrapping
|
||||
# the given information; if the parameter does not look like parsed JSON,
|
||||
# it is returned as is
|
||||
@parse: (deltaInfo) ->
|
||||
if deltaInfo and typeof deltaInfo is 'object'
|
||||
new Dropbox.PulledChanges deltaInfo
|
||||
else
|
||||
deltaInfo
|
||||
|
||||
# @property {Boolean} if true, the application should reset its copy of the
|
||||
# user's Dropbox before applying the changes described by this instance
|
||||
blankSlate: undefined
|
||||
|
||||
# @property {String} encodes a cursor in the list of changes to a user's
|
||||
# Dropbox; a pullChanges call returns some changes at the cursor, and then
|
||||
# advance the cursor to account for the returned changes; the new cursor is
|
||||
# returned by pullChanges, and meant to be used by a subsequent pullChanges
|
||||
# call
|
||||
cursorTag: undefined
|
||||
|
||||
# @property {Array<Dropbox.PullChange> an array with one entry for each
|
||||
# change to the user's Dropbox returned by a pullChanges call.
|
||||
changes: undefined
|
||||
|
||||
# @property {Boolean} if true, the pullChanges call returned a subset of the
|
||||
# available changes, and the application should repeat the call
|
||||
# immediately to get more changes
|
||||
shouldPullAgain: undefined
|
||||
|
||||
# @property {Boolean} if true, the API call will not have any more changes
|
||||
# available in the nearby future, so the application should wait for at
|
||||
# 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
|
||||
constructor: (deltaInfo) ->
|
||||
@blankSlate = deltaInfo.reset or false
|
||||
@cursorTag = deltaInfo.cursor
|
||||
@shouldPullAgain = deltaInfo.has_more
|
||||
@shouldBackOff = not @shouldPullAgain
|
||||
if deltaInfo.cursor and deltaInfo.cursor.length
|
||||
@changes = (Dropbox.PullChange.parse entry for entry in deltaInfo.entries)
|
||||
else
|
||||
@changes = []
|
||||
|
||||
# Wraps a single change in a pullChanges result.
|
||||
class Dropbox.PullChange
|
||||
# Creates a Dropbox.PullChange instance wrapping an entry in a /delta result.
|
||||
#
|
||||
# @param {?Object} entry the parsed JSON of a single entry in a /delta API
|
||||
# call result
|
||||
# @return {?Dropbox.PullChange} a Dropbox.PullChange instance wrapping the
|
||||
# given entry of a /delta API call; if the parameter does not look like
|
||||
# parsed JSON, it is returned as is
|
||||
@parse: (entry) ->
|
||||
if entry and typeof entry is 'object'
|
||||
new Dropbox.PullChange entry
|
||||
else
|
||||
entry
|
||||
|
||||
# @property {String} the path of the changed file or folder
|
||||
path: undefined
|
||||
|
||||
# @property {Boolean} if true, this change is a deletion of the file or folder
|
||||
# at the change's path; if a folder is deleted, all its contents (files
|
||||
# and sub-folders) were also be deleted; pullChanges might not return
|
||||
# separate changes expressing for the files or sub-folders
|
||||
wasRemoved: undefined
|
||||
|
||||
# @property {?Dropbox.Stat} a Stat instance containing updated information for
|
||||
# the file or folder; this is null if the change is a deletion
|
||||
stat: undefined
|
||||
|
||||
# Creates a Dropbox.PullChange instance wrapping an entry in a /delta result.
|
||||
#
|
||||
# @private
|
||||
# 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
|
||||
# call result
|
||||
constructor: (entry) ->
|
||||
@path = entry[0]
|
||||
@stat = Dropbox.Stat.parse entry[1]
|
||||
if @stat
|
||||
@wasRemoved = false
|
||||
else
|
||||
@stat = null
|
||||
@wasRemoved = true
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
# Wraps an URL to a Dropbox file or folder that can be publicly shared.
|
||||
class Dropbox.PublicUrl
|
||||
# Creates a PublicUrl instance from a raw API response.
|
||||
#
|
||||
# @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
|
||||
# they are
|
||||
@parse: (urlData, isDirect) ->
|
||||
if urlData and typeof urlData is 'object'
|
||||
new Dropbox.PublicUrl urlData, isDirect
|
||||
else
|
||||
urlData
|
||||
|
||||
# @property {String} the public URL
|
||||
url: null
|
||||
|
||||
# @property {Date} after this time, the URL is not usable
|
||||
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: null
|
||||
|
||||
# @property {Boolean} true if this is URL points to a file's preview page in
|
||||
# Dropbox, false for direct links
|
||||
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.
|
||||
#
|
||||
# @private
|
||||
# This constructor is used by Dropbox.PublicUrl.parse, and should not be
|
||||
# called directly.
|
||||
#
|
||||
# @param {?Object} 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
|
||||
constructor: (urlData, isDirect) ->
|
||||
@url = urlData.url
|
||||
@expiresAt = new Date Date.parse(urlData.expires)
|
||||
|
||||
if isDirect is true
|
||||
@isDirect = true
|
||||
else if isDirect is false
|
||||
@isDirect = false
|
||||
else
|
||||
# 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 describing a copy
|
||||
# reference, or the reference string
|
||||
@parse: (refData) ->
|
||||
if refData and (typeof refData is 'object' or typeof refData is 'string')
|
||||
new Dropbox.CopyReference refData
|
||||
else
|
||||
refData
|
||||
|
||||
# @property {String} the raw reference, for use with Dropbox APIs
|
||||
tag: null
|
||||
|
||||
# @property {Date} deadline for using the reference in a copy operation
|
||||
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.
|
||||
#
|
||||
# @private
|
||||
# This constructor is used by Dropbox.CopyReference.parse, and should not be
|
||||
# called directly.
|
||||
#
|
||||
# @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 Math.ceil(Date.now() / 1000) * 1000
|
||||
# The JSON representation is created on-demand, to avoid unnecessary
|
||||
# object creation.
|
||||
@_json = null
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
# The result of stat-ing a file or directory in a user's Dropbox.
|
||||
class Dropbox.Stat
|
||||
# Creates a Stat instance from a raw "metadata" response.
|
||||
#
|
||||
# @param {?Object} metadata the result of parsing JSON API responses that are
|
||||
# called "metadata" in the API documentation
|
||||
# @return {?Dropbox.Stat} a Stat instance wrapping the given API response;
|
||||
# parameters that aren't parsed JSON objects are returned as they are
|
||||
@parse: (metadata) ->
|
||||
if metadata and typeof metadata is 'object'
|
||||
new Dropbox.Stat metadata
|
||||
else
|
||||
metadata
|
||||
|
||||
# @property {String} the path of this file or folder, relative to the user's
|
||||
# Dropbox or to the application's folder
|
||||
path: null
|
||||
|
||||
# @property {String} the name of this file or folder
|
||||
name: null
|
||||
|
||||
# @property {Boolean} if true, the file or folder's path is relative to the
|
||||
# application's folder; otherwise, the path is relative to the user's
|
||||
# Dropbox
|
||||
inAppFolder: null
|
||||
|
||||
# @property {Boolean} if true, this Stat instance describes a folder
|
||||
isFolder: null
|
||||
|
||||
# @property {Boolean} if true, this Stat instance describes a file
|
||||
isFile: null
|
||||
|
||||
# @property {Boolean} if true, the file or folder described by this Stat
|
||||
# instance was from the user's Dropbox, and was obtained by an API call
|
||||
# that returns deleted items
|
||||
isRemoved: null
|
||||
|
||||
# @property {String} name of an icon in Dropbox's icon library that most
|
||||
# accurately represents this file or folder
|
||||
#
|
||||
# See the Dropbox API documentation to obtain the Dropbox icon library.
|
||||
# https://www.dropbox.com/developers/reference/api#metadata
|
||||
typeIcon: null
|
||||
|
||||
# @property {String} an identifier for the contents of the described file or
|
||||
# directories; this can used to be restored a file's contents to a
|
||||
# previous version, or to save bandwidth by not retrieving the same
|
||||
# folder contents twice
|
||||
versionTag: null
|
||||
|
||||
# @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
|
||||
# locale
|
||||
humanSize: null
|
||||
|
||||
# @property {Boolean} if false, the URL generated by thumbnailUrl does not
|
||||
# point to a valid image, and should not be used
|
||||
hasThumbnail: null
|
||||
|
||||
# @property {Date} the file or folder's last modification time
|
||||
modifiedAt: null
|
||||
|
||||
# @property {?Date} the file or folder's last modification time, as reported
|
||||
# by the Dropbox client that uploaded the file; this time should not be
|
||||
# trusted, but can be used for UI (display, sorting); null if the server
|
||||
# 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
|
||||
# This constructor is used by Dropbox.Stat.parse, and should not be called
|
||||
# directly.
|
||||
#
|
||||
# @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 '/'
|
||||
# Strip any trailing /, to make path joining predictable.
|
||||
lastIndex = @path.length - 1
|
||||
if lastIndex >= 0 and @path.substring(lastIndex) is '/'
|
||||
@path = @path.substring 0, lastIndex
|
||||
|
||||
nameSlash = @path.lastIndexOf '/'
|
||||
@name = @path.substring nameSlash + 1
|
||||
|
||||
@isFolder = metadata.is_dir || false
|
||||
@isFile = !@isFolder
|
||||
@isRemoved = metadata.is_deleted || false
|
||||
@typeIcon = metadata.icon
|
||||
if metadata.modified?.length
|
||||
@modifiedAt = new Date Date.parse(metadata.modified)
|
||||
else
|
||||
@modifiedAt = null
|
||||
if metadata.client_mtime?.length
|
||||
@clientModifiedAt = new Date Date.parse(metadata.client_mtime)
|
||||
else
|
||||
@clientModifiedAt = null
|
||||
|
||||
switch metadata.root
|
||||
when 'dropbox'
|
||||
@inAppFolder = false
|
||||
when 'app_folder'
|
||||
@inAppFolder = true
|
||||
else
|
||||
# New "root" value that we're not aware of.
|
||||
@inAppFolder = null
|
||||
|
||||
@size = metadata.bytes or 0
|
||||
@humanSize = metadata.size or ''
|
||||
@hasThumbnail = metadata.thumb_exists or false
|
||||
|
||||
if @isFolder
|
||||
@versionTag = metadata.hash
|
||||
@mimeType = metadata.mime_type || 'inode/directory'
|
||||
else
|
||||
@versionTag = metadata.rev
|
||||
@mimeType = metadata.mime_type || 'application/octet-stream'
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
# 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
|
||||
@
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
# Information about a Dropbox user.
|
||||
class Dropbox.UserInfo
|
||||
# Creates a UserInfo instance from a raw API response.
|
||||
#
|
||||
# @param {?Object} userInfo the result of parsing a JSON API response that
|
||||
# describes a user
|
||||
# @return {Dropbox.UserInfo} a UserInfo instance wrapping the given API
|
||||
# response; parameters that aren't parsed JSON objects are returned as
|
||||
# the are
|
||||
@parse: (userInfo) ->
|
||||
if userInfo and typeof userInfo is 'object'
|
||||
new Dropbox.UserInfo userInfo
|
||||
else
|
||||
userInfo
|
||||
|
||||
# @property {String} the user's name, in a form that is fit for display
|
||||
name: null
|
||||
|
||||
# @property {?String} the user's email; this is not in the official API
|
||||
# documentation, so it might not be supported
|
||||
email: null
|
||||
|
||||
# @property {?String} two-letter country code, or null if unavailable
|
||||
countryCode: null
|
||||
|
||||
# @property {String} unique ID for the user; this ID matches the unique ID
|
||||
# returned by the authentication process
|
||||
uid: null
|
||||
|
||||
# @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".
|
||||
#
|
||||
# @property {String} prefix for URLs to the application's files
|
||||
publicAppUrl: null
|
||||
|
||||
# @property {Number} the maximum amount of bytes that the user can store
|
||||
quota: null
|
||||
|
||||
# @property {Number} the number of bytes taken up by the user's data
|
||||
usedQuota: null
|
||||
|
||||
# @property {Number} the number of bytes taken up by the user's data that is
|
||||
# not shared with other users
|
||||
privateBytes: null
|
||||
|
||||
# @property {Number} the number of bytes taken up by the user's data that is
|
||||
# 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
|
||||
# describes a user
|
||||
constructor: (userInfo) ->
|
||||
@_json = userInfo
|
||||
@name = userInfo.display_name
|
||||
@email = userInfo.email
|
||||
@countryCode = userInfo.country or null
|
||||
@uid = userInfo.uid.toString()
|
||||
if userInfo.public_app_url
|
||||
@publicAppUrl = userInfo.public_app_url
|
||||
lastIndex = @publicAppUrl.length - 1
|
||||
# Strip any trailing /, to make path joining predictable.
|
||||
if lastIndex >= 0 and @publicAppUrl.substring(lastIndex) is '/'
|
||||
@publicAppUrl = @publicAppUrl.substring 0, lastIndex
|
||||
else
|
||||
@publicAppUrl = null
|
||||
|
||||
@referralUrl = userInfo.referral_link
|
||||
@quota = userInfo.quota_info.quota
|
||||
@privateBytes = userInfo.quota_info.normal or 0
|
||||
@sharedBytes = userInfo.quota_info.shared or 0
|
||||
@usedQuota = @privateBytes + @sharedBytes
|
||||
|
||||
|
|
@ -1,505 +0,0 @@
|
|||
if window?
|
||||
if window.XDomainRequest and not ('withCredentials' of new XMLHttpRequest())
|
||||
DropboxXhrRequest = window.XDomainRequest
|
||||
DropboxXhrIeMode = true
|
||||
# IE's XDR doesn't allow setting requests' Content-Type to anything other
|
||||
# than text/plain, so it can't send _any_ forms.
|
||||
DropboxXhrCanSendForms = false
|
||||
else
|
||||
DropboxXhrRequest = window.XMLHttpRequest
|
||||
DropboxXhrIeMode = false
|
||||
# Firefox doesn't support adding named files to FormData.
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=690659
|
||||
DropboxXhrCanSendForms =
|
||||
window.navigator.userAgent.indexOf('Firefox') is -1
|
||||
DropboxXhrDoesPreflight = true
|
||||
else
|
||||
# Node.js needs an adapter for the XHR API.
|
||||
DropboxXhrRequest = require('xmlhttprequest').XMLHttpRequest
|
||||
DropboxXhrIeMode = false
|
||||
# Node.js doesn't have FormData. We wouldn't want to bother putting together
|
||||
# upload forms in node.js anyway, because it doesn't do CORS preflight
|
||||
# checks, so we can use PUT requests without a performance hit.
|
||||
DropboxXhrCanSendForms = false
|
||||
# Node.js is a server so it doesn't do annoying browser checks.
|
||||
DropboxXhrDoesPreflight = false
|
||||
|
||||
# ArrayBufferView isn't available in the global namespce.
|
||||
#
|
||||
# Using the hack suggested in
|
||||
# https://code.google.com/p/chromium/issues/detail?id=60449
|
||||
if typeof Uint8Array is 'undefined'
|
||||
DropboxXhrArrayBufferView = null
|
||||
DropboxXhrSendArrayBufferView = false
|
||||
else
|
||||
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.
|
||||
@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.)
|
||||
# @param {String} baseUrl the URL that receives the request; this URL might
|
||||
# be modified, e.g. by appending parameters for GET requests
|
||||
constructor: (@method, baseUrl) ->
|
||||
@isGet = @method is 'GET'
|
||||
@url = baseUrl
|
||||
@headers = {}
|
||||
@params = null
|
||||
@body = null
|
||||
@preflight = not (@isGet or (@method is 'POST'))
|
||||
@signed = false
|
||||
@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<Dropbox.ApiError>} 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.
|
||||
#
|
||||
# @param {?Object} params an associative array (hash) containing the HTTP
|
||||
# request parameters
|
||||
# @return {Dropbox.Xhr} this, for easy call chaining
|
||||
setParams: (params) ->
|
||||
if @signed
|
||||
throw new Error 'setParams called after addOauthParams or addOauthHeader'
|
||||
if @params
|
||||
throw new Error 'setParams cannot be called twice'
|
||||
@params = params
|
||||
@
|
||||
|
||||
# Sets the function called when the XHR completes.
|
||||
#
|
||||
# This function can also be set when calling Dropbox.Xhr#send.
|
||||
#
|
||||
# @param {function(?Dropbox.ApiError, ?Object, ?Object)} callback called when
|
||||
# the XHR completes; if an error occurs, the first parameter will be a
|
||||
# Dropbox.ApiError instance; otherwise, the second parameter will be an
|
||||
# instance of the required response type (e.g., String, Blob), and the
|
||||
# third parameter will be the JSON-parsed 'x-dropbox-metadata' header
|
||||
# @return {Dropbox.Xhr} this, for easy call chaining
|
||||
setCallback: (@callback) ->
|
||||
@
|
||||
|
||||
# Ammends the request parameters to include an OAuth signature.
|
||||
#
|
||||
# The OAuth signature will become invalid if the parameters are changed after
|
||||
# the signing process.
|
||||
#
|
||||
# This method automatically decides the best way to add the OAuth signature
|
||||
# to the current request. Modifying the request in any way (e.g., by adding
|
||||
# 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.
|
||||
#
|
||||
# @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 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.
|
||||
#
|
||||
# The OAuth signature will become invalid if the parameters are changed after
|
||||
# the signing process.
|
||||
#
|
||||
# @param {Dropbox.Oauth} oauth OAuth instance whose key and secret will be
|
||||
# used to sign the request
|
||||
# @return {Dropbox.Xhr} this, for easy call chaining
|
||||
addOauthParams: (oauth) ->
|
||||
if @signed
|
||||
throw new Error 'Request already has an OAuth signature'
|
||||
|
||||
@params or= {}
|
||||
oauth.addAuthParams @method, @url, @params
|
||||
@signed = true
|
||||
@
|
||||
|
||||
# Adds an Authorize header containing an OAuth signature.
|
||||
#
|
||||
# The OAuth signature will become invalid if the parameters are changed after
|
||||
# the signing process.
|
||||
#
|
||||
# @param {Dropbox.Oauth} oauth OAuth instance whose key and secret will be
|
||||
# used to sign the request
|
||||
# @return {Dropbox.Xhr} this, for easy call chaining
|
||||
addOauthHeader: (oauth) ->
|
||||
if @signed
|
||||
throw new Error 'Request already has an OAuth signature'
|
||||
|
||||
@params or= {}
|
||||
@signed = true
|
||||
@setHeader 'Authorization', oauth.authHeader(@method, @url, @params)
|
||||
|
||||
# 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
|
||||
# @return {Dropbox.Xhr} this, for easy call chaining
|
||||
setBody: (body) ->
|
||||
if @isGet
|
||||
throw new Error 'setBody cannot be called on GET requests'
|
||||
if @body isnt null
|
||||
throw new Error 'Request already has a body'
|
||||
|
||||
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
|
||||
@
|
||||
|
||||
# Sends off an AJAX request and requests a custom response type.
|
||||
#
|
||||
# This method requires XHR Level 2 support, which is not available in IE
|
||||
# versions <= 9. If these browsers must be supported, it is recommended to
|
||||
# check whether window.Blob is truthy.
|
||||
#
|
||||
# @param {String} responseType the value that will be assigned to the XHR's
|
||||
# responseType property
|
||||
# @return {Dropbox.Xhr} this, for easy call chaining
|
||||
setResponseType: (@responseType) ->
|
||||
@
|
||||
|
||||
# Sets the value of a custom HTTP header.
|
||||
#
|
||||
# Custom HTTP headers require a CORS preflight in browsers, so requests that
|
||||
# use them will take more time to complete, especially on high-latency mobile
|
||||
# connections.
|
||||
#
|
||||
# @param {String} headerName the name of the HTTP header
|
||||
# @param {String} value the value that the header will be set to
|
||||
# @return {Dropbox.Xhr} this, for easy call chaining
|
||||
setHeader: (headerName, value) ->
|
||||
if @headers[headerName]
|
||||
oldValue = @headers[headerName]
|
||||
throw new Error "HTTP header #{headerName} already set to #{oldValue}"
|
||||
if headerName is 'Content-Type'
|
||||
throw new Error 'Content-Type is automatically computed based on setBody'
|
||||
@preflight = true
|
||||
@headers[headerName] = value
|
||||
@
|
||||
|
||||
# Simulates having an <input type="file"> being sent with the request.
|
||||
#
|
||||
# @param {String} fieldName the name of the form field / parameter (not of
|
||||
# the uploaded file)
|
||||
# @param {String} fileName the name of the uploaded file (not the name of the
|
||||
# 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
|
||||
setFileField: (fieldName, fileName, fileData, contentType) ->
|
||||
if @body isnt null
|
||||
throw new Error 'Request already has a body'
|
||||
|
||||
if @isGet
|
||||
throw new Error 'setFileField cannot be called on GET requests'
|
||||
|
||||
if typeof(fileData) is 'object' and typeof Blob isnt 'undefined'
|
||||
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
|
||||
#fileData = fileData
|
||||
useFormData = fileData instanceof Blob
|
||||
else
|
||||
useFormData = false
|
||||
|
||||
if useFormData
|
||||
@body = new FormData()
|
||||
@body.append fieldName, fileData, fileName
|
||||
else
|
||||
contentType or= 'application/octet-stream'
|
||||
boundary = @multipartBoundary()
|
||||
@headers['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
|
||||
@body = ['--', boundary, "\r\n",
|
||||
'Content-Disposition: form-data; name="', fieldName,
|
||||
'"; filename="', fileName, "\"\r\n",
|
||||
'Content-Type: ', contentType, "\r\n",
|
||||
"Content-Transfer-Encoding: binary\r\n\r\n",
|
||||
fileData,
|
||||
"\r\n", '--', boundary, '--', "\r\n"].join ''
|
||||
|
||||
# @private
|
||||
# @return {String} a nonce suitable for use as a part boundary in a multipart
|
||||
# MIME message
|
||||
multipartBoundary: ->
|
||||
[Date.now().toString(36), Math.random().toString(36)].join '----'
|
||||
|
||||
# Moves this request's parameters to its URL.
|
||||
#
|
||||
# @private
|
||||
# @return {Dropbox.Xhr} this, for easy call chaining
|
||||
paramsToUrl: ->
|
||||
if @params
|
||||
queryString = Dropbox.Xhr.urlEncode @params
|
||||
if queryString.length isnt 0
|
||||
@url = [@url, '?', queryString].join ''
|
||||
@params = null
|
||||
@
|
||||
|
||||
# Moves this request's parameters to its body.
|
||||
#
|
||||
# @private
|
||||
# @return {Dropbox.Xhr} this, for easy call chaining
|
||||
paramsToBody: ->
|
||||
if @params
|
||||
if @body isnt null
|
||||
throw new Error 'Request already has a body'
|
||||
if @isGet
|
||||
throw new Error 'paramsToBody cannot be called on GET requests'
|
||||
@headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
@body = Dropbox.Xhr.urlEncode @params
|
||||
@params = null
|
||||
@
|
||||
|
||||
# Sets up an XHR request.
|
||||
#
|
||||
# This method completely sets up a native XHR object and stops short of
|
||||
# calling its send() method, so the API client has a chance of customizing
|
||||
# the XHR. After customizing the XHR, Dropbox.Xhr#send should be called.
|
||||
#
|
||||
#
|
||||
# @return {Dropbox.Xhr} this, for easy call chaining
|
||||
prepare: ->
|
||||
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'
|
||||
else
|
||||
@paramsToBody()
|
||||
|
||||
@xhr = new Dropbox.Xhr.Request()
|
||||
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 ieXdr
|
||||
for own header, value of @headers
|
||||
@xhr.setRequestHeader header, value
|
||||
|
||||
if @responseType
|
||||
if @responseType is 'b'
|
||||
if @xhr.overrideMimeType
|
||||
@xhr.overrideMimeType 'text/plain; charset=x-user-defined'
|
||||
else
|
||||
@xhr.responseType = @responseType
|
||||
|
||||
@
|
||||
|
||||
# Fires off the prepared XHR request.
|
||||
#
|
||||
# Dropbox.Xhr#prepare should be called exactly once before this method.
|
||||
#
|
||||
# @param {function(?Dropbox.ApiError, ?Object, ?Object)} callback called when
|
||||
# the XHR completes; if an error occurs, the first parameter will be a
|
||||
# Dropbox.ApiError instance; otherwise, the second parameter will be an
|
||||
# instance of the required response type (e.g., String, Blob), and the
|
||||
# third parameter will be the JSON-parsed 'x-dropbox-metadata' header
|
||||
# @return {Dropbox.Xhr} this, for easy call chaining
|
||||
send: (callback) ->
|
||||
@callback = callback or @callback
|
||||
|
||||
if @body isnt null
|
||||
body = @body
|
||||
# send() in XHR doesn't like naked ArrayBuffers
|
||||
if Dropbox.Xhr.sendArrayBufferView and body instanceof ArrayBuffer
|
||||
body = new Uint8Array body
|
||||
|
||||
try
|
||||
@xhr.send body
|
||||
catch e
|
||||
# Node.js doesn't implement Blob.
|
||||
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
|
||||
throw e
|
||||
else
|
||||
@xhr.send()
|
||||
@
|
||||
|
||||
# Encodes an associative array (hash) into a x-www-form-urlencoded String.
|
||||
#
|
||||
# For consistency, the keys are sorted in alphabetical order in the encoded
|
||||
# output.
|
||||
#
|
||||
# @param {Object} object the JavaScript object whose keys will be encoded
|
||||
# @return {String} the object's keys and values, encoded using
|
||||
# x-www-form-urlencoded
|
||||
@urlEncode: (object) ->
|
||||
chunks = []
|
||||
for key, value of object
|
||||
chunks.push @urlEncodeValue(key) + '=' + @urlEncodeValue(value)
|
||||
chunks.sort().join '&'
|
||||
|
||||
# Encodes an object into a x-www-form-urlencoded key or value.
|
||||
#
|
||||
# @param {Object} object the object to be encoded; the encoding calls
|
||||
# toString() on the object to obtain its string representation
|
||||
# @return {String} encoded string, suitable for use as a key or value in an
|
||||
# x-www-form-urlencoded string
|
||||
@urlEncodeValue: (object) ->
|
||||
encodeURIComponent(object.toString()).replace(/\!/g, '%21').
|
||||
replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').
|
||||
replace(/\*/g, '%2A')
|
||||
|
||||
# Decodes an x-www-form-urlencoded String into an associative array (hash).
|
||||
#
|
||||
# @param {String} string the x-www-form-urlencoded String to be decoded
|
||||
# @return {Object} an associative array whose keys and values are all strings
|
||||
@urlDecode: (string) ->
|
||||
result = {}
|
||||
for token in string.split '&'
|
||||
kvp = token.split '='
|
||||
result[decodeURIComponent(kvp[0])] = decodeURIComponent kvp[1]
|
||||
result
|
||||
|
||||
# Handles the XHR readystate event.
|
||||
onReadyStateChange: ->
|
||||
return true if @xhr.readyState isnt 4 # XMLHttpRequest.DONE is 4
|
||||
|
||||
if @xhr.status < 200 or @xhr.status >= 300
|
||||
apiError = new Dropbox.ApiError @xhr, @method, @url
|
||||
@onError.dispatch apiError if @onError
|
||||
@callback apiError
|
||||
return true
|
||||
|
||||
metadataJson = @xhr.getResponseHeader 'x-dropbox-metadata'
|
||||
if metadataJson?.length
|
||||
try
|
||||
metadata = JSON.parse metadataJson
|
||||
catch e
|
||||
# Make sure the app doesn't crash if the server goes crazy.
|
||||
metadata = undefined
|
||||
else
|
||||
metadata = undefined
|
||||
|
||||
if @responseType
|
||||
if @responseType is 'b'
|
||||
dirtyText = if @xhr.responseText?
|
||||
@xhr.responseText
|
||||
else
|
||||
@xhr.response
|
||||
###
|
||||
jsString = ['["']
|
||||
for i in [0...dirtyText.length]
|
||||
hexByte = (dirtyText.charCodeAt(i) & 0xFF).toString(16)
|
||||
if hexByte.length is 2
|
||||
jsString.push "\\u00#{hexByte}"
|
||||
else
|
||||
jsString.push "\\u000#{hexByte}"
|
||||
jsString.push '"]'
|
||||
console.log jsString
|
||||
text = JSON.parse(jsString.join(''))[0]
|
||||
###
|
||||
bytes = []
|
||||
for i in [0...dirtyText.length]
|
||||
bytes.push String.fromCharCode(dirtyText.charCodeAt(i) & 0xFF)
|
||||
text = bytes.join ''
|
||||
@callback null, text, metadata
|
||||
else
|
||||
@callback null, @xhr.response, metadata
|
||||
return true
|
||||
|
||||
text = if @xhr.responseText? then @xhr.responseText else @xhr.response
|
||||
switch @xhr.getResponseHeader('Content-Type')
|
||||
when 'application/x-www-form-urlencoded'
|
||||
@callback null, Dropbox.Xhr.urlDecode(text), metadata
|
||||
when 'application/json', 'text/javascript'
|
||||
@callback null, JSON.parse(text), metadata
|
||||
else
|
||||
@callback null, text, metadata
|
||||
true
|
||||
|
||||
# Handles the XDomainRequest onload event. (IE 8, 9)
|
||||
onXdrLoad: ->
|
||||
text = @xhr.responseText
|
||||
switch @xhr.contentType
|
||||
when 'application/x-www-form-urlencoded'
|
||||
@callback null, Dropbox.Xhr.urlDecode(text), undefined
|
||||
when 'application/json', 'text/javascript'
|
||||
@callback null, JSON.parse(text), undefined
|
||||
else
|
||||
@callback null, text, undefined
|
||||
true
|
||||
|
||||
# Handles the XDomainRequest onload event. (IE 8, 9)
|
||||
onXdrError: ->
|
||||
apiError = new Dropbox.ApiError @xhr, @method, @url
|
||||
@onError.dispatch apiError if @onError
|
||||
@callback apiError
|
||||
return true
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
# All changes to the global namespace happen here.
|
||||
|
||||
# This file's name is set up in such a way that it will always show up last in
|
||||
# the source directory. This makes coffee --join work as intended.
|
||||
|
||||
if module?.exports?
|
||||
# We're a node.js module, so export the Dropbox class.
|
||||
module.exports = Dropbox
|
||||
else if window?
|
||||
# We're in a browser, so add Dropbox to the global namespace.
|
||||
window.Dropbox = Dropbox
|
||||
else
|
||||
throw new Error('This library only supports node.js and modern browsers.')
|
||||
|
||||
# These are mostly useful for testing. Clients shouldn't use internal stuff.
|
||||
Dropbox.atob = atob
|
||||
Dropbox.btoa = btoa
|
||||
Dropbox.hmac = base64HmacSha1
|
||||
Dropbox.sha1 = base64Sha1
|
||||
Dropbox.encodeKey = dropboxEncodeKey
|
||||
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100px" height="94.209px" viewBox="0 0 100 94.209" enable-background="new 0 0 100 94.209" xml:space="preserve">
|
||||
<path d="M51.62,0c10.969,0.871,19.611,10.069,19.611,21.262c0,11.397-8.966,20.693-20.221,21.272v4.367
|
||||
c3.071,0.485,5.426,3.154,5.426,6.362c0,0.784-0.145,1.536-0.4,2.232l3.992,2.305c6.17-9.318,18.615-12.359,28.422-6.696
|
||||
c9.786,5.651,13.4,17.907,8.467,27.893c6.771-13.177,2.062-29.512-10.911-37.001c-3.243-1.873-6.7-3.014-10.2-3.486
|
||||
c1.339-3.264,2.081-6.836,2.081-10.583C77.887,13.059,66.268,0.886,51.62,0L51.62,0z"/>
|
||||
<path d="M48.044,0.016c-14.575,0.963-26.119,13.095-26.119,27.91c0,3.641,0.696,7.119,1.962,10.306
|
||||
c-3.393,0.497-6.754,1.627-9.905,3.446C1.099,49.117-3.622,65.28,2.952,78.412c-4.747-9.94-1.106-22.032,8.594-27.634
|
||||
c9.87-5.698,22.402-2.581,28.53,6.876l3.853-2.223c-0.242-0.677-0.366-1.409-0.366-2.168c0-3.071,2.155-5.643,5.032-6.281v-4.457
|
||||
c-11.153-0.684-20.006-9.938-20.006-21.264C28.589,10.119,37.144,0.959,48.044,0.016L48.044,0.016z"/>
|
||||
<path d="M49.29,26.803c-5.63,0.129-10.819,2.046-15.079,5.165c0.34,0.503,0.706,0.991,1.091,1.458
|
||||
c0.386,0.468,0.794,0.916,1.222,1.344s0.876,0.836,1.344,1.222c0.343,0.284,0.712,0.539,1.075,0.799
|
||||
c3.144-2.066,6.91-3.275,10.966-3.275c4.058,0,7.813,1.209,10.957,3.275c0.363-0.26,0.731-0.515,1.075-0.799
|
||||
c0.468-0.386,0.916-0.794,1.344-1.222s0.836-0.876,1.222-1.344c0.386-0.468,0.752-0.956,1.093-1.458
|
||||
c-4.415-3.23-9.826-5.165-15.69-5.165c-0.14,0-0.278-0.002-0.418,0C49.428,26.804,49.357,26.8,49.29,26.803L49.29,26.803z"/>
|
||||
<path d="M23.628,50.297C23.035,55.735,24.068,61.39,27,66.47c2.933,5.08,7.314,8.809,12.316,11.014
|
||||
c0.269-0.548,0.508-1.11,0.717-1.678c0.214-0.57,0.398-1.143,0.555-1.728s0.292-1.18,0.391-1.777
|
||||
c0.075-0.438,0.115-0.883,0.154-1.329c-3.357-1.689-6.288-4.346-8.317-7.86C30.79,59.6,29.954,55.74,30.17,51.984
|
||||
c-0.407-0.185-0.812-0.373-1.23-0.53c-0.568-0.211-1.142-0.396-1.728-0.553c-0.585-0.158-1.177-0.282-1.776-0.383
|
||||
C24.839,50.417,24.234,50.34,23.628,50.297L23.628,50.297z"/>
|
||||
<path d="M76.368,50.615c-0.604,0.043-1.21,0.121-1.807,0.221c-0.598,0.101-1.19,0.227-1.777,0.383
|
||||
c-0.582,0.157-1.165,0.343-1.732,0.554c-0.418,0.156-0.816,0.354-1.225,0.538c0.217,3.753-0.617,7.616-2.648,11.128
|
||||
c-2.025,3.512-4.958,6.163-8.318,7.854c0.045,0.445,0.082,0.888,0.157,1.328c0.102,0.598,0.227,1.19,0.383,1.775
|
||||
c0.157,0.585,0.342,1.167,0.556,1.734c0.209,0.57,0.448,1.125,0.717,1.673c5.003-2.208,9.384-5.929,12.315-11.007
|
||||
C75.92,61.716,76.961,56.053,76.368,50.615L76.368,50.615z"/>
|
||||
<path d="M45.151,57.492l-3.878,2.248c4.985,10.003,1.401,22.296-8.408,27.959c-9.759,5.635-22.142,2.671-28.342-6.541
|
||||
c8.046,12.395,24.497,16.459,37.442,8.985c3.136-1.81,5.782-4.145,7.903-6.817c2.163,2.81,4.9,5.251,8.164,7.136
|
||||
c12.96,7.481,29.432,3.402,37.467-9.025c-6.188,9.249-18.588,12.243-28.367,6.596c-9.869-5.697-13.438-18.109-8.311-28.146
|
||||
l-4.032-2.329C53.611,58.869,51.901,59.7,50,59.7C48.068,59.7,46.331,58.841,45.151,57.492L45.151,57.492z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 428 B |
|
Before Width: | Height: | Size: 2 KiB |
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100px" height="94.113px" viewBox="0 0 100 94.113" enable-background="new 0 0 100 94.113" xml:space="preserve">
|
||||
<path d="M74.868,86.582l-16.531-28.37c-3.038,1.997-8.752,2.129-8.752,2.129s-5.779-0.132-8.817-2.129l-16.58,28.37
|
||||
c0,0,9.743,7.531,25.612,7.531C65.619,94.113,74.868,86.582,74.868,86.582L74.868,86.582L74.868,86.582z"/>
|
||||
<path d="M49.881,53.471c5.417,0,9.745-4.408,9.745-9.792c0-5.417-4.328-9.827-9.745-9.827s-9.791,4.41-9.791,9.827
|
||||
C40.09,49.063,44.464,53.471,49.881,53.471L49.881,53.471L49.881,53.471z"/>
|
||||
<path d="M74.539,0.578L58.254,29.114c3.237,1.618,6.16,6.49,6.16,6.49s2.823,5.201,2.575,8.868l32.864-0.033
|
||||
c0,0,1.666-12.121-6.244-25.877C85.651,4.822,74.539,0.578,74.539,0.578L74.539,0.578L74.539,0.578z"/>
|
||||
<path d="M0.191,43.845h32.897c-0.247-3.667,2.51-8.737,2.51-8.737s2.988-4.953,6.29-6.588L25.557,0c0,0-11.378,4.658-19.32,18.397
|
||||
C-1.674,32.103,0.191,43.845,0.191,43.845L0.191,43.845L0.191,43.845z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 324 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 854 B |
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100" height="100" viewBox="0 0 100 100">
|
||||
<path d="M 66.177,0 H 50 33.823 v 3.724 h 4.445 V 88.047 C 38.269,94.638 43.531,100 50,100 56.469,100 61.731,94.638 61.731,88.047 V 3.724 h 4.445 V 0 z M 50,96.28 c -4.416,0 -8.012,-3.695 -8.012,-8.233 V 36.559 H 58.011 V 88.047 C 58.012,92.585 54.416,96.28 50,96.28 z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 439 B |
|
Before Width: | Height: | Size: 1,017 B |
|
Before Width: | Height: | Size: 240 B |
|
Before Width: | Height: | Size: 421 B |
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# 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.
|
||||
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
# 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()
|
||||
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# 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
|
||||
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
# Content script for Dropbox OAuth receiver pages.
|
||||
|
||||
message = type: 'close', url: window.location.href
|
||||
chrome.extension.sendMessage message
|
||||
|
Before Width: | Height: | Size: 611 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 745 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
|
@ -1,69 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 -2.896 128 128"
|
||||
enable-background="new 0 -2.896 100 100"
|
||||
xml:space="preserve"
|
||||
inkscape:version="0.48.3.1 r9886"
|
||||
sodipodi:docname="icon.svg"><metadata
|
||||
id="metadata19"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs17" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1564"
|
||||
inkscape:window-height="1237"
|
||||
id="namedview15"
|
||||
showgrid="true"
|
||||
inkscape:zoom="6.675088"
|
||||
inkscape:cx="69.437347"
|
||||
inkscape:cy="69.196458"
|
||||
inkscape:window-x="2702"
|
||||
inkscape:window-y="34"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Layer_1"><inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2996"
|
||||
empspacing="4"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true" /></sodipodi:namedview><path
|
||||
d="M 66.073434,0.91625655 C 80.113721,2.0311339 91.174173,13.804545 91.174173,28.131549 c 0,14.588124 -11.475171,26.486975 -25.882817,27.228093 v 5.589746 c 3.930871,0.620799 6.945263,4.03839 6.945263,8.14334 0,1.003517 -0.185599,1.966075 -0.510719,2.856953 l 5.108468,2.950393 c 7.898861,-11.927011 23.827142,-15.819481 36.381352,-8.569579 12.52605,7.231982 17.15068,22.919623 10.83773,35.702935 8.66686,-16.867782 2.63935,-37.77525 -13.96604,-47.361146 -4.14975,-2.397434 -8.5747,-3.857911 -13.054691,-4.462069 1.712636,-4.17791 2.662393,-8.750059 2.662393,-13.546207 0,-19.032273 -14.872283,-34.6136742 -33.621678,-35.74775145 l 0,0 z"
|
||||
id="path3"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="M 61.496166,0.93673655 C 42.840212,2.1693735 28.063929,17.698295 28.063929,36.661448 c 0,4.660469 0.890877,9.112298 2.511353,13.191648 -4.343029,0.636158 -8.645098,2.082554 -12.678368,4.410869 C 1.4067145,63.785861 -4.6361505,84.47445 3.7785486,101.28335 -2.2975963,88.5602 2.3628722,73.082478 14.778841,65.911936 27.41241,58.618514 43.45333,62.608264 51.297151,74.711914 l 4.931828,-2.845433 C 55.91922,70.999923 55.7605,70.064245 55.7605,69.091448 c 0,-3.92959 2.758393,-7.223022 6.440944,-8.03966 V 55.346842 C 47.925639,54.471324 36.593827,42.626233 36.593827,28.128989 36.593827,13.868545 47.5442,2.1437736 61.496166,0.93673655 l 0,0 z"
|
||||
id="path5"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 63.091042,35.224012 c -7.206382,0.165119 -13.848286,2.618873 -19.301072,6.611183 0.435199,0.643839 0.903677,1.268477 1.396476,1.866236 0.494079,0.599038 1.016318,1.172477 1.564156,1.720315 0.547839,0.547839 1.121278,1.070078 1.720316,1.564157 0.439039,0.363519 0.911358,0.689918 1.375997,1.022717 4.02431,-2.644473 8.844778,-4.19199 14.036445,-4.19199 5.194227,0 10.000615,1.547517 14.024925,4.19199 0.463359,-0.332799 0.935679,-0.659198 1.375998,-1.022717 0.599038,-0.494079 1.172477,-1.016318 1.720315,-1.564157 0.547839,-0.547838 1.070078,-1.121277 1.564157,-1.720315 0.494078,-0.599039 0.962557,-1.223677 1.399036,-1.866236 -5.649907,-4.134389 -12.57725,-6.611183 -20.081871,-6.611183 -0.1792,0 -0.355839,-0.0026 -0.535039,0 -0.0832,0.0013 -0.174079,-0.0038 -0.259839,0 l 0,0 z"
|
||||
id="path7"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 30.243763,65.296257 c -0.759038,6.960623 0.563199,14.199005 4.316149,20.701389 3.754231,6.502384 9.361897,11.275492 15.764441,14.097884 0.34432,-0.700155 0.650239,-1.419515 0.917758,-2.147834 0.273919,-0.729598 0.509439,-1.463036 0.710398,-2.211834 0.20096,-0.748798 0.373759,-1.511676 0.500479,-2.274554 0.096,-0.560639 0.1472,-1.130238 0.19712,-1.701116 -4.29695,-2.160635 -8.04862,-5.562866 -10.645734,-10.059495 -2.593274,-4.495349 -3.663351,-9.436137 -3.386872,-14.245085 -0.520958,-0.236799 -1.039357,-0.477439 -1.574396,-0.677118 -0.727038,-0.27008 -1.461756,-0.506879 -2.211834,-0.707839 -0.748798,-0.202239 -1.506556,-0.360959 -2.273275,-0.490238 -0.764158,-0.13056 -1.538556,-0.22912 -2.314234,-0.28416 l 0,0 z"
|
||||
id="path9"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 97.750797,65.703296 c -0.773118,0.05504 -1.548796,0.1536 -2.314234,0.28288 -0.765438,0.129279 -1.521916,0.291839 -2.273275,0.490238 -0.744958,0.20096 -1.491196,0.439039 -2.216954,0.709119 -0.535039,0.199679 -1.043198,0.453118 -1.567996,0.689918 0.277759,4.802548 -0.789758,9.747176 -3.388152,14.242525 -2.591993,4.496629 -6.347504,7.8899 -10.647014,10.053095 0.0576,0.568318 0.104959,1.136637 0.199679,1.699836 0.13184,0.765438 0.291839,1.523196 0.490239,2.270714 0.202239,0.750078 0.439039,1.495036 0.712958,2.219514 0.267519,0.729599 0.572159,1.44 0.917758,2.141425 6.403825,-2.826223 12.012771,-7.589091 15.761882,-14.088915 3.75167,-6.501104 5.084147,-13.749726 4.325109,-20.710349 l 0,0 z"
|
||||
id="path11"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 57.793135,74.505834 -4.963828,2.877433 c 6.380784,12.803809 1.793276,28.538793 -10.762213,35.787413 -12.491489,7.21279 -28.34169,3.41888 -36.2776703,-8.37246 10.2988543,15.86557 31.3560823,21.06747 47.9256413,11.50078 4.01407,-2.31808 7.400942,-5.30687 10.115815,-8.72574 2.768633,3.59679 6.270704,6.72126 10.449894,9.13406 16.58876,9.57437 37.672866,4.35454 47.957646,-11.55198 -7.92062,11.83997 -23.792585,15.67228 -36.309674,8.44414 C 73.297737,106.30734 68.728148,90.420035 75.289412,77.572707 l -5.159668,-2.982393 c -1.507836,1.678076 -3.69791,2.741753 -6.129904,2.741753 -2.472954,0 -4.696309,-1.099517 -6.206705,-2.826233 l 0,0 z"
|
||||
id="path13"
|
||||
inkscape:connector-curvature="0" /></svg>
|
||||
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 5 KiB |
|
Before Width: | Height: | Size: 513 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>dropbox.js browser tests</title>
|
||||
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
|
||||
<script src="/lib/dropbox.js"></script>
|
||||
|
||||
<script src="/test/vendor/sinon.js"></script>
|
||||
<!--[if IE]>
|
||||
<script src="/test/vendor/sinon-ie.js"></script>
|
||||
<![endif]-->
|
||||
<script src="/test/vendor/chai.js"></script>
|
||||
<script src="/node_modules/sinon-chai/lib/sinon-chai.js"></script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script src="/test/js/browser_mocha_setup.js"></script>
|
||||
|
||||
<script src="/test/.token/token.js"></script>
|
||||
<script src="/test/vendor/favicon.js"></script>
|
||||
<script src="/test/js/helper.js"></script>
|
||||
|
||||
<script src="/test/js/base64_test.js"></script>
|
||||
<script src="/test/js/client_test.js"></script>
|
||||
<script src="/test/js/drivers_test.js"></script>
|
||||
<script src="/test/js/event_source_test.js"></script>
|
||||
<script src="/test/js/hmac_test.js"></script>
|
||||
<script src="/test/js/oauth_test.js"></script>
|
||||
<script src="/test/js/pulled_changes_test.js"></script>
|
||||
<script src="/test/js/references_test.js"></script>
|
||||
<script src="/test/js/stat_test.js"></script>
|
||||
<script src="/test/js/upload_cursor_test.js"></script>
|
||||
<script src="/test/js/user_info_test.js"></script>
|
||||
<script src="/test/js/xhr_test.js"></script>
|
||||
<script src="/test/js/browser_mocha_runner.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="/lib/dropbox.js" type="text/javascript"></script>
|
||||
<script src="/test/js/chrome_oauth_receiver.js" type="text/javascript">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Dropbox sign-in successful</h1>
|
||||
|
||||
<p>Please close this window.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="/lib/dropbox.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
Dropbox.Drivers.Popup.oauthReceiver();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Dropbox sign-in successful</h1>
|
||||
|
||||
<p>Please close this window.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="/lib/dropbox.js"></script>
|
||||
<script src="/test/.token/token.js"></script>
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var opener = window.opener;
|
||||
if (!opener && window.parent != window.top) {
|
||||
opener = window.parent;
|
||||
}
|
||||
if (opener) {
|
||||
var client = new Dropbox.Client(window.testFullDropboxKeys);
|
||||
client.reset();
|
||||
client.authDriver(new Dropbox.Drivers.Redirect(
|
||||
{scope: "redirect-integration"}));
|
||||
client.authenticate(function(error, _client) {
|
||||
var message = [error || null, _client && _client.credentials()];
|
||||
var json = JSON.stringify(message);
|
||||
try {
|
||||
opener.postMessage(json, '*');
|
||||
} catch(e) {
|
||||
// IE doesn't support opener.postMessage for popups.
|
||||
}
|
||||
window.close();
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Please close this window.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
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=='
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
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
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
mocha.setup ui: 'bdd', slow: 150, timeout: 10000
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
chrome.app.runtime.onLaunched.addListener ->
|
||||
chrome.app.window.create 'test/html/browser_test.html',
|
||||
type: 'shell', frame: 'chrome', id: 'test_suite'
|
||||
|
|
@ -1 +0,0 @@
|
|||
Dropbox.Drivers.Chrome.oauthReceiver()
|
||||
|
|
@ -1,446 +0,0 @@
|
|||
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()
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
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='
|
||||
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
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)
|
||||
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
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
|
||||
|
||||
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
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
|
||||
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
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'
|
||||
|
||||
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
# 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
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
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
|
||||