mirror of
https://github.com/coderaiser/cloudcmd.git
synced 2026-01-23 10:45:47 +00:00
added ability to upload files to dropbox
This commit is contained in:
parent
fff8f7cff2
commit
3e9ee9cfc2
31 changed files with 4034 additions and 129 deletions
|
|
@ -26,6 +26,9 @@ jquery loaded after ie.js, should be before.
|
|||
inside event function varible called event do not exist
|
||||
(everithing ok on webkit).
|
||||
|
||||
* Added ability to upload files to dropbox.
|
||||
|
||||
|
||||
2012.12.12, Version 0.1.8
|
||||
|
||||
* Added ability to shutdown Cloud Commander
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
"github_key" : "891c251b925e4e967fa9",
|
||||
"github_secret" : "afe9bed1e810c5dc44c4c2a953fc6efb1e5b0545",
|
||||
"dropbox_key" : "0nd3ssnp5fp7tqs",
|
||||
"dropbox_secret" : "r61lxpchmk8l06o",
|
||||
"dropbox_encoded_key" : "DkMz4FYHQTA=|GW6pf2dONkrGvckMwBsl1V1vysrCPktPiUWN7UpDjw==",
|
||||
"dropbox_chooser_key" : "o7d6llji052vijk",
|
||||
"logs" : false,
|
||||
"show_keys_panel" : true,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,24 @@ var CloudCommander, Util, DOM, $;
|
|||
}
|
||||
}
|
||||
|
||||
function getContent(pCallBack){
|
||||
return DOM.getCurrentFileContent(function(pData){
|
||||
var lName = DOM.getCurrentName();
|
||||
if( Util.isObject(pData) ){
|
||||
pData = JSON.stringify(pData, null, 4);
|
||||
|
||||
var lExt = '.json';
|
||||
if( !Util.checkExtension(lName, lExt) )
|
||||
lName += lExt;
|
||||
}
|
||||
|
||||
Util.exec(pCallBack, {
|
||||
data: pData,
|
||||
name: lName
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** function return configureation for menu */
|
||||
function getConfig (){
|
||||
return{
|
||||
|
|
@ -76,24 +94,17 @@ var CloudCommander, Util, DOM, $;
|
|||
'gist': {
|
||||
name: 'Gist',
|
||||
callback: function(key, opt){
|
||||
DOM.getCurrentFileContent(function(pData){
|
||||
var lName = DOM.getCurrentName();
|
||||
if( Util.isObject(pData) ){
|
||||
pData = JSON.stringify(pData, null, 4);
|
||||
|
||||
var lExt = '.json';
|
||||
if( !Util.checkExtension(lName, lExt) )
|
||||
lName += lExt;
|
||||
}
|
||||
|
||||
var lGitHub = cloudcmd.GitHub;
|
||||
getContent(function(pParams){
|
||||
var lGitHub = cloudcmd.GitHub,
|
||||
lData = pParams.data,
|
||||
lName = pParams.name;
|
||||
|
||||
if('init' in lGitHub)
|
||||
lGitHub.createGist(pData, lName);
|
||||
lGitHub.createGist(lData, lName);
|
||||
else
|
||||
Util.exec(cloudcmd.GitHub,
|
||||
function(){
|
||||
lGitHub.createGist(pData, lName);
|
||||
lGitHub.createGist(lData, lName);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -103,28 +114,37 @@ var CloudCommander, Util, DOM, $;
|
|||
|
||||
'gdrive': {
|
||||
name: 'GDrive',
|
||||
|
||||
callback: function(key, opt){
|
||||
DOM.getCurrentFileContent(function(data){
|
||||
var lName = DOM.getCurrentName();
|
||||
|
||||
if( Util.isObject(data) )
|
||||
data = JSON.stringify(data, null, 4);
|
||||
var lData = {
|
||||
data: data,
|
||||
name: lName
|
||||
};
|
||||
|
||||
getContent(function(pParams){
|
||||
var lGDrive = cloudcmd.GDrive;
|
||||
|
||||
if('init' in lGDrive)
|
||||
lGDrive.init(lData);
|
||||
lGDrive.init(pParams);
|
||||
else
|
||||
Util.exec(cloudcmd.GDrive, lData);
|
||||
Util.exec(cloudcmd.GDrive, pParams);
|
||||
});
|
||||
|
||||
Util.log('Uploading to gdrive...');
|
||||
}
|
||||
},
|
||||
'dropbox':{
|
||||
name: 'DropBox',
|
||||
callback: function(key, opt){
|
||||
getContent(function(pParams){
|
||||
var lDropBox = cloudcmd.DropBox,
|
||||
lData = pParams.data,
|
||||
lName = pParams.name;
|
||||
|
||||
if('init' in lDropBox)
|
||||
lDropBox.uploadFile(lData, lName);
|
||||
else
|
||||
Util.exec(lDropBox, function(){
|
||||
cloudcmd.DropBox.uploadFile(lData, lName);
|
||||
});
|
||||
});
|
||||
|
||||
Util.log('Uploading to dropbox...');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,113 +1,102 @@
|
|||
var CloudCommander, Util, DOM, gapi;
|
||||
var CloudCommander, Util, DOM, CloudFunc, Dropbox, cb, Client;
|
||||
/* module for work with github */
|
||||
|
||||
(function(){
|
||||
"use strict";
|
||||
|
||||
var cloudcmd = CloudCommander,
|
||||
GDrive = {};
|
||||
|
||||
var cloudcmd = CloudCommander,
|
||||
//Client,
|
||||
DropBoxStore = {};
|
||||
|
||||
function authorize(pData){
|
||||
/* https://developers.google.com/drive/credentials */
|
||||
Util.setTimeout({
|
||||
func : function(pCallBack){
|
||||
var lCLIENT_ID = '255175681917.apps.googleusercontent.com',
|
||||
lSCOPES = 'https://www.googleapis.com/auth/drive',
|
||||
lParams = {
|
||||
'client_id' : lCLIENT_ID,
|
||||
'scope' : lSCOPES,
|
||||
'immediate' : true
|
||||
};
|
||||
|
||||
gapi.auth.authorize(lParams, pCallBack);
|
||||
},
|
||||
|
||||
callback : function(pAuthResult){
|
||||
var lRet;
|
||||
if (pAuthResult && !pAuthResult.error){
|
||||
uploadFile(pData);
|
||||
|
||||
lRet = true;
|
||||
}
|
||||
return lRet;
|
||||
}
|
||||
});
|
||||
}
|
||||
/* temporary callback function for work with github */
|
||||
cb = function (err, data){ console.log(err || data);};
|
||||
|
||||
function load(pData){
|
||||
var lUrl = 'https://apis.google.com/js/client.js';
|
||||
|
||||
DOM.jsload(lUrl, function(){
|
||||
authorize(pData);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the file upload.
|
||||
*
|
||||
* @param {Object} evt Arguments from the file selector.
|
||||
*/
|
||||
function uploadFile(pData) {
|
||||
gapi.client.load('drive', 'v2', function() {
|
||||
GDrive.uploadFile(pData);
|
||||
});
|
||||
}
|
||||
/* PRIVATE FUNCTIONS */
|
||||
|
||||
/**
|
||||
* Insert new file.
|
||||
*
|
||||
* @param {File} fileData {name, data} File object to read data from.
|
||||
* @param {Function} callback Function to call when the request is complete.
|
||||
* function loads dropbox.js
|
||||
*/
|
||||
GDrive.uploadFile = function(pData, callback) {
|
||||
var lData = pData.data,
|
||||
lName = pData.name,
|
||||
boundary = '-------314159265358979323846',
|
||||
delimiter = "\r\n--" + boundary + "\r\n",
|
||||
close_delim = "\r\n--" + boundary + "--",
|
||||
|
||||
contentType = pData.type || 'application/octet-stream',
|
||||
metadata = {
|
||||
'title' : lName,
|
||||
'mimeType' : contentType
|
||||
},
|
||||
|
||||
base64Data = btoa(lData),
|
||||
|
||||
multipartRequestBody =
|
||||
delimiter +
|
||||
'Content-Type: application/json\r\n\r\n' +
|
||||
JSON.stringify(metadata) +
|
||||
delimiter +
|
||||
'Content-Type: ' + contentType + '\r\n' +
|
||||
'Content-Transfer-Encoding: base64\r\n' +
|
||||
'\r\n' +
|
||||
base64Data +
|
||||
close_delim;
|
||||
|
||||
var request = gapi.client.request({
|
||||
'path': '/upload/drive/v2/files',
|
||||
'method': 'POST',
|
||||
'params': {'uploadType': 'multipart'},
|
||||
'headers': {
|
||||
'Content-Type': 'multipart/mixed; boundary="' + boundary + '"'
|
||||
},
|
||||
|
||||
'body': multipartRequestBody
|
||||
function load(pCallBack){
|
||||
console.time('dropbox load');
|
||||
|
||||
var lSrc = '//cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.7.1/dropbox.min.js',
|
||||
lLocal = CloudFunc.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)
|
||||
});
|
||||
|
||||
if (!callback)
|
||||
callback = function(file) {
|
||||
console.log(file);
|
||||
}
|
||||
|
||||
function getUserData(pCallBack){
|
||||
var lName,
|
||||
lShowUserInfo = function(pError, pData){
|
||||
if(!pError){
|
||||
lName = pData.name;
|
||||
console.log('Hello ' + lName + ' :)!');
|
||||
}
|
||||
};
|
||||
|
||||
request.execute(callback);
|
||||
Client.getUserInfo(lShowUserInfo);
|
||||
|
||||
Util.exec(pCallBack);
|
||||
}
|
||||
/**
|
||||
* function logins on dropbox
|
||||
*
|
||||
* @param pData = {key, secret}
|
||||
*/
|
||||
DropBoxStore.login = function(pCallBack){
|
||||
cloudcmd.getConfig(function(pConfig){
|
||||
Client = new Dropbox.Client({
|
||||
key: pConfig.dropbox_encoded_key
|
||||
});
|
||||
Client.authDriver(new Dropbox.Drivers.Redirect({rememberUser: true}));
|
||||
|
||||
Client.authenticate(function(pError, pClient) {
|
||||
Util.log(pError);
|
||||
Client = pClient;
|
||||
Util.exec(pCallBack);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
/**
|
||||
* upload file to DropBox
|
||||
*/
|
||||
DropBoxStore.uploadFile = function(pContent, pFileName){
|
||||
if(pContent){
|
||||
DOM.Images.showLoad();
|
||||
if(!pFileName)
|
||||
pFileName = Util.getDate();
|
||||
|
||||
Client.writeFile(pFileName, pContent, function(pError, pData){
|
||||
DOM.Images.hideLoad();
|
||||
console.log(pError || pData);
|
||||
});
|
||||
}
|
||||
|
||||
return pContent;
|
||||
};
|
||||
|
||||
|
||||
GDrive.init = function(pData){
|
||||
load(pData);
|
||||
DropBoxStore.init = function(pCallBack){
|
||||
Util.loadOnLoad([
|
||||
Util.retExec(pCallBack),
|
||||
getUserData,
|
||||
DropBoxStore.login,
|
||||
load
|
||||
]);
|
||||
|
||||
cloudcmd.DropBox.init = null;
|
||||
};
|
||||
|
||||
cloudcmd.GDrive = GDrive;
|
||||
})();
|
||||
cloudcmd.DropBox = DropBoxStore;
|
||||
})();
|
||||
|
|
|
|||
55
lib/client/storage/_dropbox_chooser.js
Normal file
55
lib/client/storage/_dropbox_chooser.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
var CloudCommander, DOM, Dropbox;
|
||||
/* module for work with github */
|
||||
|
||||
(function(){
|
||||
"use strict";
|
||||
|
||||
var cloudcmd = CloudCommander,
|
||||
CHOOSER_API = 'https://www.dropbox.com/static/api/1/dropbox.js',
|
||||
CLIENT_ID,
|
||||
DropBoxStore = {},
|
||||
options = {
|
||||
linkType: "direct",
|
||||
success: function(files) {
|
||||
console.log("Here's the file link:" + files[0].link);
|
||||
},
|
||||
cancel: function() {
|
||||
console.log('Chose something');
|
||||
}
|
||||
};
|
||||
|
||||
/* PRIVATE FUNCTIONS */
|
||||
|
||||
/**
|
||||
* function loads dropbox.js
|
||||
*/
|
||||
function load(){
|
||||
console.time('dropbox load');
|
||||
|
||||
cloudcmd.getConfig(function(pConfig){
|
||||
var lElement = DOM.anyload({
|
||||
src : CHOOSER_API,
|
||||
not_append : true,
|
||||
id : 'dropboxjs',
|
||||
func : DropBoxStore.choose
|
||||
|
||||
});
|
||||
|
||||
var lDropBoxId = pConfig.dropbox_chooser_key;
|
||||
lElement.setAttribute('data-app-key', lDropBoxId);
|
||||
document.body.appendChild(lElement);
|
||||
|
||||
console.timeEnd('dropbox load');
|
||||
});
|
||||
}
|
||||
|
||||
DropBoxStore.choose = function(){
|
||||
Dropbox.choose(options);
|
||||
};
|
||||
|
||||
DropBoxStore.init = function(){
|
||||
load();
|
||||
};
|
||||
|
||||
cloudcmd.DropBox = DropBoxStore;
|
||||
})();
|
||||
113
lib/client/storage/_gdrive.js
Normal file
113
lib/client/storage/_gdrive.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
var CloudCommander, Util, DOM, gapi;
|
||||
|
||||
(function(){
|
||||
"use strict";
|
||||
|
||||
var cloudcmd = CloudCommander,
|
||||
GDrive = {};
|
||||
|
||||
|
||||
function authorize(pData){
|
||||
/* https://developers.google.com/drive/credentials */
|
||||
Util.setTimeout({
|
||||
func : function(pCallBack){
|
||||
var lCLIENT_ID = '255175681917.apps.googleusercontent.com',
|
||||
lSCOPES = 'https://www.googleapis.com/auth/drive',
|
||||
lParams = {
|
||||
'client_id' : lCLIENT_ID,
|
||||
'scope' : lSCOPES,
|
||||
'immediate' : true
|
||||
};
|
||||
|
||||
gapi.auth.authorize(lParams, pCallBack);
|
||||
},
|
||||
|
||||
callback : function(pAuthResult){
|
||||
var lRet;
|
||||
if (pAuthResult && !pAuthResult.error){
|
||||
uploadFile(pData);
|
||||
|
||||
lRet = true;
|
||||
}
|
||||
return lRet;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function load(pData){
|
||||
var lUrl = 'https://apis.google.com/js/client.js';
|
||||
|
||||
DOM.jsload(lUrl, function(){
|
||||
authorize(pData);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the file upload.
|
||||
*
|
||||
* @param {Object} evt Arguments from the file selector.
|
||||
*/
|
||||
function uploadFile(pData) {
|
||||
gapi.client.load('drive', 'v2', function() {
|
||||
GDrive.uploadFile(pData);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert new file.
|
||||
*
|
||||
* @param {File} fileData {name, data} File object to read data from.
|
||||
* @param {Function} callback Function to call when the request is complete.
|
||||
*/
|
||||
GDrive.uploadFile = function(pData, callback) {
|
||||
var lData = pData.data,
|
||||
lName = pData.name,
|
||||
boundary = '-------314159265358979323846',
|
||||
delimiter = "\r\n--" + boundary + "\r\n",
|
||||
close_delim = "\r\n--" + boundary + "--",
|
||||
|
||||
contentType = pData.type || 'application/octet-stream',
|
||||
metadata = {
|
||||
'title' : lName,
|
||||
'mimeType' : contentType
|
||||
},
|
||||
|
||||
base64Data = btoa(lData),
|
||||
|
||||
multipartRequestBody =
|
||||
delimiter +
|
||||
'Content-Type: application/json\r\n\r\n' +
|
||||
JSON.stringify(metadata) +
|
||||
delimiter +
|
||||
'Content-Type: ' + contentType + '\r\n' +
|
||||
'Content-Transfer-Encoding: base64\r\n' +
|
||||
'\r\n' +
|
||||
base64Data +
|
||||
close_delim;
|
||||
|
||||
var request = gapi.client.request({
|
||||
'path': '/upload/drive/v2/files',
|
||||
'method': 'POST',
|
||||
'params': {'uploadType': 'multipart'},
|
||||
'headers': {
|
||||
'Content-Type': 'multipart/mixed; boundary="' + boundary + '"'
|
||||
},
|
||||
|
||||
'body': multipartRequestBody
|
||||
});
|
||||
|
||||
if (!callback)
|
||||
callback = function(file) {
|
||||
console.log(file);
|
||||
};
|
||||
|
||||
request.execute(callback);
|
||||
};
|
||||
|
||||
|
||||
GDrive.init = function(pData){
|
||||
load(pData);
|
||||
};
|
||||
|
||||
cloudcmd.GDrive = GDrive;
|
||||
})();
|
||||
|
|
@ -62,11 +62,7 @@ var CloudCommander, Util, DOM, $, Github, cb;
|
|||
if ( Util.isContainStr(lCode, '?code=') ){
|
||||
lCode = lCode.replace('?code=','');
|
||||
|
||||
DOM.ajax({
|
||||
type : 'put',
|
||||
url : AuthURL,
|
||||
data: lCode,
|
||||
success: function(pData){
|
||||
var lSuccess = function(pData){
|
||||
if(pData && pData.token){
|
||||
lToken = pData.token;
|
||||
|
||||
|
|
@ -76,8 +72,15 @@ var CloudCommander, Util, DOM, $, Github, cb;
|
|||
}
|
||||
else
|
||||
Util.log("Worning: token not getted...");
|
||||
}
|
||||
});
|
||||
},
|
||||
lData = {
|
||||
type : 'put',
|
||||
url : AuthURL,
|
||||
data : lCode,
|
||||
success : lSuccess
|
||||
};
|
||||
|
||||
DOM.ajax(lData);
|
||||
}
|
||||
else
|
||||
//window.open('welcome.html', 'welcome','width=300,height=200,menubar=yes,status=yes')">
|
||||
|
|
|
|||
19
lib/client/storage/dropbox/LICENSE.txt
Normal file
19
lib/client/storage/dropbox/LICENSE.txt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
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.
|
||||
73
lib/client/storage/dropbox/README.md
Normal file
73
lib/client/storage/dropbox/README.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# 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.
|
||||
167
lib/client/storage/dropbox/doc/auth_drivers.md
Normal file
167
lib/client/storage/dropbox/doc/auth_drivers.md
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
# 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
|
||||
[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),
|
||||
and point the `Dropbox.Drivers.Popup` constructor to it.
|
||||
|
||||
```javascript
|
||||
client.authDriver(new Dropbox.Drivers.Popup({receiverUrl: "https://url.to/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}));
|
||||
```
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
84
lib/client/storage/dropbox/doc/coffee_faq.md
Normal file
84
lib/client/storage/dropbox/doc/coffee_faq.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# 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.
|
||||
89
lib/client/storage/dropbox/doc/development.md
Normal file
89
lib/client/storage/dropbox/doc/development.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# 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
|
||||
|
||||
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 or in your
|
||||
default browser.
|
||||
|
||||
```bash
|
||||
cake test
|
||||
cake webtest
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## Testing Chrome Extension
|
||||
|
||||
The test suite opens up a couple of Dropbox authorization pages, and a page
|
||||
that cannot close itself. dropbox.js ships with a Google Chrome extension that
|
||||
can fully automate the testing process on Chrome.
|
||||
|
||||
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, for
|
||||
best results, you should disable the automation (by clicking on the extension
|
||||
icon) when you're not testing dropbox.js.
|
||||
272
lib/client/storage/dropbox/doc/getting_started.md
Normal file
272
lib/client/storage/dropbox/doc/getting_started.md
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
# 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.7.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.Error` instance
|
||||
as the first parameter in their callbacks. This parameter is named `error` in
|
||||
all the code snippets on this page.
|
||||
|
||||
If `error` is a truthy value, you should either recover from the error, or
|
||||
notify the user that an error occurred. The `status` field in the
|
||||
`Dropbox.Error` instance contains the HTTP error code, which should be one of
|
||||
the
|
||||
[error codes in the REST API](https://www.dropbox.com/developers/reference/api#error-handling).
|
||||
|
||||
The snippet below is a template for an extensive error handler.
|
||||
|
||||
```javascript
|
||||
var showError = function(error) {
|
||||
if (window.console) { // Skip the "if" in node.js code.
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
switch (error.status) {
|
||||
case 401:
|
||||
// If you're using dropbox.js, the only cause behind this error is that
|
||||
// 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.
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
10
lib/client/storage/dropbox/lib/README.md
Normal file
10
lib/client/storage/dropbox/lib/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# 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.
|
||||
5
lib/client/storage/dropbox/lib/dropbox.min.js
vendored
Normal file
5
lib/client/storage/dropbox/lib/dropbox.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
46
lib/client/storage/dropbox/package.json
Normal file
46
lib/client/storage/dropbox/package.json
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "dropbox",
|
||||
"version": "0.7.2",
|
||||
"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.2",
|
||||
"coffee-script": ">= 1.4.0",
|
||||
"express": ">= 3.0.4",
|
||||
"mocha": ">= 1.7.4",
|
||||
"remove": ">= 0.1.5",
|
||||
"sinon": ">= 1.5.2",
|
||||
"sinon-chai": ">= 2.2.0",
|
||||
"uglify-js": ">= 2.2.2"
|
||||
},
|
||||
"main": "lib/dropbox.js",
|
||||
"directories": {
|
||||
"doc": "doc",
|
||||
"lib": "lib",
|
||||
"src": "src",
|
||||
"test": "test"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublish": "cake build",
|
||||
"test": "cake test"
|
||||
}
|
||||
}
|
||||
6
lib/client/storage/dropbox/src/000-dropbox.coffee
Normal file
6
lib/client/storage/dropbox/src/000-dropbox.coffee
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Main entry point to the Dropbox API.
|
||||
class Dropbox
|
||||
constructor: (options) ->
|
||||
@client = new DropboxClient options
|
||||
|
||||
# NOTE: this is not yet implemented.
|
||||
49
lib/client/storage/dropbox/src/api_error.coffee
Normal file
49
lib/client/storage/dropbox/src/api_error.coffee
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# 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
|
||||
text = xhr.response or xhr.responseText
|
||||
else
|
||||
text = xhr.responseText
|
||||
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()
|
||||
72
lib/client/storage/dropbox/src/base64.coffee
Normal file
72
lib/client/storage/dropbox/src/base64.coffee
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# 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'
|
||||
1104
lib/client/storage/dropbox/src/client.coffee
Normal file
1104
lib/client/storage/dropbox/src/client.coffee
Normal file
File diff suppressed because it is too large
Load diff
477
lib/client/storage/dropbox/src/drivers.coffee
Normal file
477
lib/client/storage/dropbox/src/drivers.coffee
Normal file
|
|
@ -0,0 +1,477 @@
|
|||
# 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 = {}
|
||||
|
||||
# Base class for drivers that run in the browser.
|
||||
#
|
||||
# Inheriting from this class makes a driver use HTML5 localStorage to preserve
|
||||
# OAuth tokens across page reloads.
|
||||
class Dropbox.Drivers.BrowserBase
|
||||
# Sets up the OAuth driver.
|
||||
#
|
||||
# Subclasses should pass the options object they receive to the superclass
|
||||
# constructor.
|
||||
#
|
||||
# @param {?Object} options the advanced settings below
|
||||
# @option options {Boolean} rememberUser if true, the user's OAuth tokens are
|
||||
# saved in localStorage; if you use this, you MUST provide a UI item that
|
||||
# calls signOut() on Dropbox.Client, to let the user "log out" of the
|
||||
# application
|
||||
# @option options {String} scope embedded in the localStorage key that holds
|
||||
# the authentication data; useful for having multiple OAuth tokens in a
|
||||
# single application
|
||||
constructor: (options) ->
|
||||
@rememberUser = options?.rememberUser or false
|
||||
@scope = options?.scope or 'default'
|
||||
|
||||
# The magic happens here.
|
||||
onAuthStateChange: (client, callback) ->
|
||||
@setStorageKey client
|
||||
|
||||
switch client.authState
|
||||
when DropboxClient.RESET
|
||||
@loadCredentials (credentials) =>
|
||||
return callback() unless credentials
|
||||
|
||||
if credentials.authState # Incomplete authentication.
|
||||
client.setCredentials credentials
|
||||
return callback()
|
||||
|
||||
# There is an old access token. Only use it if the app supports
|
||||
# logout.
|
||||
unless @rememberUser
|
||||
@forgetCredentials()
|
||||
return callback()
|
||||
|
||||
# Verify that the old access token still works.
|
||||
client.setCredentials credentials
|
||||
client.getUserInfo (error) =>
|
||||
if error
|
||||
client.reset()
|
||||
@forgetCredentials callback
|
||||
else
|
||||
callback()
|
||||
when DropboxClient.REQUEST
|
||||
@storeCredentials client.credentials(), callback
|
||||
when DropboxClient.DONE
|
||||
if @rememberUser
|
||||
return @storeCredentials(client.credentials(), callback)
|
||||
@forgetCredentials callback
|
||||
when DropboxClient.SIGNED_OFF
|
||||
@forgetCredentials callback
|
||||
when DropboxClient.ERROR
|
||||
@forgetCredentials callback
|
||||
else
|
||||
callback()
|
||||
@
|
||||
|
||||
# Computes the @storageKey used by loadCredentials and forgetCredentials.
|
||||
#
|
||||
# @private
|
||||
# This is called by onAuthStateChange.
|
||||
#
|
||||
# @param {Dropbox.Client} client the client instance that is running the
|
||||
# authorization process
|
||||
# @return {Dropbox.Driver} this, for easy call chaining
|
||||
setStorageKey: (client) ->
|
||||
# NOTE: the storage key is dependent on the app hash so that multiple apps
|
||||
# hosted off the same server don't step on eachother's toes
|
||||
@storageKey = "dropbox-auth:#{@scope}:#{client.appHash()}"
|
||||
@
|
||||
|
||||
# Stores a Dropbox.Client's credentials to localStorage.
|
||||
#
|
||||
# @private
|
||||
# onAuthStateChange calls this method during the authentication flow.
|
||||
#
|
||||
# @param {Object} credentials the result of a Drobpox.Client#credentials call
|
||||
# @param {function()} callback called when the storing operation is complete
|
||||
# @return {Dropbox.Drivers.BrowserBase} this, for easy call chaining
|
||||
storeCredentials: (credentials, callback) ->
|
||||
localStorage.setItem @storageKey, JSON.stringify(credentials)
|
||||
callback()
|
||||
@
|
||||
|
||||
# Retrieves a token and secret from localStorage.
|
||||
#
|
||||
# @private
|
||||
# onAuthStateChange calls this method during the authentication flow.
|
||||
#
|
||||
# @param {function(?Object)} callback supplied with the credentials object
|
||||
# stored by a previous call to
|
||||
# Dropbox.Drivers.BrowserBase#storeCredentials; null if no credentials were
|
||||
# stored, or if the previously stored credentials were deleted
|
||||
# @return {Dropbox.Drivers.BrowserBase} this, for easy call chaining
|
||||
loadCredentials: (callback) ->
|
||||
jsonString = localStorage.getItem @storageKey
|
||||
unless jsonString
|
||||
callback null
|
||||
return @
|
||||
|
||||
try
|
||||
callback JSON.parse(jsonString)
|
||||
catch e
|
||||
# Parse errors.
|
||||
callback null
|
||||
@
|
||||
|
||||
# Deletes information previously stored by a call to storeToken.
|
||||
#
|
||||
# @private
|
||||
# onAuthStateChange calls this method during the authentication flow.
|
||||
#
|
||||
# @param {function()} callback called after the credentials are deleted
|
||||
# @return {Dropbox.Drivers.BrowserBase} this, for easy call chaining
|
||||
forgetCredentials: (callback) ->
|
||||
localStorage.removeItem @storageKey
|
||||
callback()
|
||||
@
|
||||
|
||||
# Wrapper for window.location, for testing purposes.
|
||||
#
|
||||
# @return {String} the current page's URL
|
||||
@currentLocation: ->
|
||||
window.location.href
|
||||
|
||||
# OAuth driver that uses a redirect and localStorage to complete the flow.
|
||||
class Dropbox.Drivers.Redirect extends Dropbox.Drivers.BrowserBase
|
||||
# Sets up the redirect-based OAuth driver.
|
||||
#
|
||||
# @param {?Object} options the advanced settings below
|
||||
# @option options {Boolean} useQuery if true, the page will receive OAuth
|
||||
# data as query parameters; by default, the page receives OAuth data in
|
||||
# the fragment part of the URL (the string following the #,
|
||||
# available as document.location.hash), to avoid confusing the server
|
||||
# generating the page
|
||||
# @option options {Boolean} rememberUser if true, the user's OAuth tokens are
|
||||
# saved in localStorage; if you use this, you MUST provide a UI item that
|
||||
# calls signOut() on Dropbox.Client, to let the user "log out" of the
|
||||
# application
|
||||
# @option options {String} scope embedded in the localStorage key that holds
|
||||
# the authentication data; useful for having multiple OAuth tokens in a
|
||||
# single application
|
||||
constructor: (options) ->
|
||||
super options
|
||||
@useQuery = options?.useQuery or false
|
||||
@receiverUrl = @computeUrl options
|
||||
@tokenRe = new RegExp "(#|\\?|&)oauth_token=([^&#]+)(&|#|$)"
|
||||
|
||||
# Forwards the authentication process from REQUEST to AUTHORIZED on redirect.
|
||||
onAuthStateChange: (client, callback) ->
|
||||
superCall = do => => super client, callback
|
||||
@setStorageKey client
|
||||
if client.authState is DropboxClient.RESET
|
||||
@loadCredentials (credentials) =>
|
||||
if credentials and credentials.authState # Incomplete authentication.
|
||||
if credentials.token is @locationToken() and
|
||||
credentials.authState is DropboxClient.REQUEST
|
||||
# locationToken matched, so the redirect happened
|
||||
credentials.authState = DropboxClient.AUTHORIZED
|
||||
return @storeCredentials credentials, superCall
|
||||
else
|
||||
# The authentication process broke down, start over.
|
||||
return @forgetCredentials superCall
|
||||
superCall()
|
||||
else
|
||||
superCall()
|
||||
|
||||
# URL of the current page, since the user will be sent right back.
|
||||
url: ->
|
||||
@receiverUrl
|
||||
|
||||
# Redirects to the authorize page.
|
||||
doAuthorize: (authUrl) ->
|
||||
window.location.assign authUrl
|
||||
|
||||
# Pre-computes the return value of url.
|
||||
computeUrl: ->
|
||||
querySuffix = "_dropboxjs_scope=#{encodeURIComponent @scope}"
|
||||
location = Dropbox.Drivers.BrowserBase.currentLocation()
|
||||
if location.indexOf('#') is -1
|
||||
fragment = null
|
||||
else
|
||||
locationPair = location.split '#', 2
|
||||
location = locationPair[0]
|
||||
fragment = locationPair[1]
|
||||
if @useQuery
|
||||
if location.indexOf('?') is -1
|
||||
location += "?#{querySuffix}" # No query string in the URL.
|
||||
else
|
||||
location += "&#{querySuffix}" # The URL already has a query string.
|
||||
else
|
||||
fragment = "?#{querySuffix}"
|
||||
|
||||
if fragment
|
||||
location + '#' + fragment
|
||||
else
|
||||
location
|
||||
|
||||
# Figures out if the user completed the OAuth flow based on the current URL.
|
||||
#
|
||||
# @return {?String} the OAuth token that the user just authorized, or null if
|
||||
# the user accessed this directly, without having authorized a token
|
||||
locationToken: ->
|
||||
location = Dropbox.Drivers.BrowserBase.currentLocation()
|
||||
|
||||
# Check for the scope.
|
||||
scopePattern = "_dropboxjs_scope=#{encodeURIComponent @scope}&"
|
||||
return null if location.indexOf?(scopePattern) is -1
|
||||
|
||||
# Extract the token.
|
||||
match = @tokenRe.exec location
|
||||
if match then decodeURIComponent(match[2]) else null
|
||||
|
||||
# OAuth driver that uses a popup window and postMessage to complete the flow.
|
||||
class Dropbox.Drivers.Popup extends Dropbox.Drivers.BrowserBase
|
||||
# Sets up a popup-based OAuth driver.
|
||||
#
|
||||
# @param {?Object} options one of the settings below; leave out the argument
|
||||
# to use the current location for redirecting
|
||||
# @option options {Boolean} rememberUser if true, the user's OAuth tokens are
|
||||
# saved in localStorage; if you use this, you MUST provide a UI item that
|
||||
# calls signOut() on Dropbox.Client, to let the user "log out" of the
|
||||
# application
|
||||
# @option options {String} scope embedded in the localStorage key that holds
|
||||
# the authentication data; useful for having multiple OAuth tokens in a
|
||||
# single application
|
||||
# @option options {String} receiverUrl URL to the page that receives the
|
||||
# /authorize redirect and performs the postMessage
|
||||
# @option options {Boolean} noFragment if true, the receiverUrl will be used
|
||||
# as given; by default, a hash "#" is appended to URLs that don't have
|
||||
# one, so the OAuth token is received as a URL fragment and does not hit
|
||||
# the file server
|
||||
# @option options {String} receiverFile the URL to the receiver page will be
|
||||
# computed by replacing the file name (everything after the last /) of
|
||||
# the current location with this parameter's value
|
||||
constructor: (options) ->
|
||||
super options
|
||||
@receiverUrl = @computeUrl options
|
||||
@tokenRe = new RegExp "(#|\\?|&)oauth_token=([^&#]+)(&|#|$)"
|
||||
|
||||
# Removes credentials stuck in the REQUEST stage.
|
||||
onAuthStateChange: (client, callback) ->
|
||||
superCall = do => => super client, callback
|
||||
@setStorageKey client
|
||||
if client.authState is DropboxClient.RESET
|
||||
@loadCredentials (credentials) ->
|
||||
if credentials and credentials.authState # Incomplete authentication.
|
||||
# The authentication process broke down, start over.
|
||||
return @forgetCredentials superCall
|
||||
superCall()
|
||||
else
|
||||
superCall()
|
||||
|
||||
# Shows the authorization URL in a pop-up, waits for it to send a message.
|
||||
doAuthorize: (authUrl, token, tokenSecret, callback) ->
|
||||
@listenForMessage token, callback
|
||||
@openWindow authUrl
|
||||
|
||||
# URL of the redirect receiver page, which posts a message back to this page.
|
||||
url: ->
|
||||
@receiverUrl
|
||||
|
||||
# Pre-computes the return value of url.
|
||||
computeUrl: (options) ->
|
||||
if options
|
||||
if options.receiverUrl
|
||||
if options.noFragment or options.receiverUrl.indexOf('#') isnt -1
|
||||
return options.receiverUrl
|
||||
else
|
||||
return options.receiverUrl + '#'
|
||||
else if options.receiverFile
|
||||
fragments = Dropbox.Drivers.BrowserBase.currentLocation().split '/'
|
||||
fragments[fragments.length - 1] = options.receiverFile
|
||||
if options.noFragment
|
||||
return fragments.join('/')
|
||||
else
|
||||
return fragments.join('/') + '#'
|
||||
Dropbox.Drivers.BrowserBase.currentLocation()
|
||||
|
||||
# Creates a popup window.
|
||||
#
|
||||
# @param {String} url the URL that will be loaded in the popup window
|
||||
# @return {?DOMRef} reference to the opened window, or null if the call
|
||||
# failed
|
||||
openWindow: (url) ->
|
||||
window.open url, '_dropboxOauthSigninWindow', @popupWindowSpec(980, 700)
|
||||
|
||||
# Spec string for window.open to create a nice popup.
|
||||
#
|
||||
# @param {Number} popupWidth the desired width of the popup window
|
||||
# @param {Number} popupHeight the desired height of the popup window
|
||||
# @return {String} spec string for the popup window
|
||||
popupWindowSpec: (popupWidth, popupHeight) ->
|
||||
# Metrics for the current browser window.
|
||||
x0 = window.screenX ? window.screenLeft
|
||||
y0 = window.screenY ? window.screenTop
|
||||
width = window.outerWidth ? document.documentElement.clientWidth
|
||||
height = window.outerHeight ? document.documentElement.clientHeight
|
||||
|
||||
# Computed popup window metrics.
|
||||
popupLeft = Math.round x0 + (width - popupWidth) / 2
|
||||
popupTop = Math.round y0 + (height - popupHeight) / 2.5
|
||||
popupLeft = x0 if popupLeft < x0
|
||||
popupTop = y0 if popupTop < y0
|
||||
|
||||
# The specification string.
|
||||
"width=#{popupWidth},height=#{popupHeight}," +
|
||||
"left=#{popupLeft},top=#{popupTop}" +
|
||||
'dialog=yes,dependent=yes,scrollbars=yes,location=yes'
|
||||
|
||||
# Listens for a postMessage from a previously opened popup window.
|
||||
#
|
||||
# @param {String} token the token string that must be received from the popup
|
||||
# window
|
||||
# @param {function()} called when the received message matches the token
|
||||
listenForMessage: (token, callback) ->
|
||||
listener = (event) =>
|
||||
match = @tokenRe.exec event.data.toString()
|
||||
if match and decodeURIComponent(match[2]) is token
|
||||
window.removeEventListener 'message', listener
|
||||
callback()
|
||||
window.addEventListener 'message', listener, false
|
||||
|
||||
|
||||
# OAuth driver that redirects the browser to a node app to complete the flow.
|
||||
#
|
||||
# This is useful for testing node.js libraries and applications.
|
||||
class Dropbox.Drivers.NodeServer
|
||||
# Starts up the node app that intercepts the browser redirect.
|
||||
#
|
||||
# @param {?Object} options one or more of the options below
|
||||
# @option options {Number} port the number of the TCP port that will receive
|
||||
# HTTP requests
|
||||
# @param {String} faviconFile the path to a file that will be served at
|
||||
# /favicon.ico
|
||||
constructor: (options) ->
|
||||
@port = options?.port or 8912
|
||||
@faviconFile = options?.favicon or null
|
||||
# Calling require in the constructor because this doesn't work in browsers.
|
||||
@fs = require 'fs'
|
||||
@http = require 'http'
|
||||
@open = require 'open'
|
||||
|
||||
@callbacks = {}
|
||||
@urlRe = new RegExp "^/oauth_callback\\?"
|
||||
@tokenRe = new RegExp "(\\?|&)oauth_token=([^&]+)(&|$)"
|
||||
@createApp()
|
||||
|
||||
# URL to the node.js OAuth callback handler.
|
||||
url: ->
|
||||
"http://localhost:#{@port}/oauth_callback"
|
||||
|
||||
# Opens the token
|
||||
doAuthorize: (authUrl, token, tokenSecret, callback) ->
|
||||
@callbacks[token] = callback
|
||||
@openBrowser authUrl
|
||||
|
||||
# Opens the given URL in a browser.
|
||||
openBrowser: (url) ->
|
||||
unless url.match /^https?:\/\//
|
||||
throw new Error("Not a http/https URL: #{url}")
|
||||
@open url
|
||||
|
||||
# Creates and starts up an HTTP server that will intercept the redirect.
|
||||
createApp: ->
|
||||
@app = @http.createServer (request, response) =>
|
||||
@doRequest request, response
|
||||
@app.listen @port
|
||||
|
||||
# Shuts down the HTTP server.
|
||||
#
|
||||
# The driver will become unusable after this call.
|
||||
closeServer: ->
|
||||
@app.close()
|
||||
|
||||
# Reads out an /authorize callback.
|
||||
doRequest: (request, response) ->
|
||||
if @urlRe.exec request.url
|
||||
match = @tokenRe.exec request.url
|
||||
if match
|
||||
token = decodeURIComponent match[2]
|
||||
if @callbacks[token]
|
||||
@callbacks[token]()
|
||||
delete @callbacks[token]
|
||||
data = ''
|
||||
request.on 'data', (dataFragment) -> data += dataFragment
|
||||
request.on 'end', =>
|
||||
if @faviconFile and (request.url is '/favicon.ico')
|
||||
@sendFavicon response
|
||||
else
|
||||
@closeBrowser response
|
||||
|
||||
# Renders a response that will close the browser window used for OAuth.
|
||||
closeBrowser: (response) ->
|
||||
closeHtml = """
|
||||
<!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
|
||||
180
lib/client/storage/dropbox/src/hmac.coffee
Normal file
180
lib/client/storage/dropbox/src/hmac.coffee
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
# 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
|
||||
|
||||
162
lib/client/storage/dropbox/src/oauth.coffee
Normal file
162
lib/client/storage/dropbox/src/oauth.coffee
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
# 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()
|
||||
36
lib/client/storage/dropbox/src/prod.coffee
Normal file
36
lib/client/storage/dropbox/src/prod.coffee
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# 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 '|'
|
||||
|
||||
100
lib/client/storage/dropbox/src/pulled_changes.coffee
Normal file
100
lib/client/storage/dropbox/src/pulled_changes.coffee
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# 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
|
||||
|
||||
# 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
|
||||
86
lib/client/storage/dropbox/src/references.coffee
Normal file
86
lib/client/storage/dropbox/src/references.coffee
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# 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} 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: undefined
|
||||
|
||||
# @property {Date} after this time, the URL is not usable
|
||||
expiresAt: undefined
|
||||
|
||||
# @property {Boolean} true if this is a direct download URL, false for URLs to
|
||||
# preview pages in the Dropbox web app; folders do not have direct link
|
||||
#
|
||||
isDirect: undefined
|
||||
|
||||
# @property {Boolean} true if this is URL points to a file's preview page in
|
||||
# Dropbox, false for direct links
|
||||
isPreview: undefined
|
||||
|
||||
# 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
|
||||
@isDirect = Date.now() - @expiresAt <= 86400000 # 1 day
|
||||
@isPreview = !@isDirect
|
||||
|
||||
# Reference to a file that can be used to make a copy across users' Dropboxes.
|
||||
class Dropbox.CopyReference
|
||||
# Creates a CopyReference instance from a raw reference or API response.
|
||||
#
|
||||
# @param {?Object, ?String} refData the parsed JSON descring a copy
|
||||
# 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: undefined
|
||||
|
||||
# @property {Date} deadline for using the reference in a copy operation
|
||||
expiresAt: undefined
|
||||
|
||||
# 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 descring 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)
|
||||
else
|
||||
@tag = refData
|
||||
@expiresAt = new Date()
|
||||
|
||||
127
lib/client/storage/dropbox/src/stat.coffee
Normal file
127
lib/client/storage/dropbox/src/stat.coffee
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
# 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
|
||||
|
||||
# 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) ->
|
||||
@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'
|
||||
79
lib/client/storage/dropbox/src/user_info.coffee
Normal file
79
lib/client/storage/dropbox/src/user_info.coffee
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# 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}
|
||||
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
|
||||
|
||||
# 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) ->
|
||||
@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
|
||||
|
||||
450
lib/client/storage/dropbox/src/xhr.coffee
Normal file
450
lib/client/storage/dropbox/src/xhr.coffee
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
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
|
||||
else
|
||||
DropboxXhrArrayBufferView =
|
||||
(new Uint8Array(0)).__proto__.__proto__.constructor
|
||||
|
||||
# Dispatches low-level AJAX calls (XMLHttpRequests).
|
||||
class Dropbox.Xhr
|
||||
# The object used to perform AJAX requests (XMLHttpRequest).
|
||||
@Request = DropboxXhrRequest
|
||||
# Set to true when using the XDomainRequest API.
|
||||
@ieMode = DropboxXhrIeMode
|
||||
# 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
|
||||
@ArrayBufferView = DropboxXhrArrayBufferView
|
||||
|
||||
# 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
|
||||
|
||||
# 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.
|
||||
#
|
||||
signWithOauth: (oauth) ->
|
||||
if Dropbox.Xhr.ieMode or (Dropbox.Xhr.doesPreflight and (not @preflight))
|
||||
@addOauthParams oauth
|
||||
else
|
||||
@addOauthHeader 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'
|
||||
|
||||
unless @preflight
|
||||
unless (typeof FormData isnt 'undefined') and (body instanceof FormData)
|
||||
@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 'paramsToBody cannot be called on GET requests'
|
||||
|
||||
if typeof(fileData) is 'object' and typeof Blob isnt 'undefined'
|
||||
if ArrayBuffer? and fileData instanceof ArrayBuffer
|
||||
fileData = new Uint8Array fileData
|
||||
if Dropbox.Xhr.ArrayBufferView and
|
||||
fileData instanceof Dropbox.Xhr.ArrayBufferView
|
||||
contentType or= 'application/octet-stream'
|
||||
fileData = new Blob [fileData], type: contentType
|
||||
# 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: ->
|
||||
ieMode = Dropbox.Xhr.ieMode
|
||||
if @isGet or @body isnt null or ieMode
|
||||
@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 ieMode
|
||||
@xhr.onload = => @onLoad()
|
||||
@xhr.onerror = => @onError()
|
||||
else
|
||||
@xhr.onreadystatechange = => @onReadyStateChange()
|
||||
@xhr.open @method, @url, true
|
||||
|
||||
unless ieMode
|
||||
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.ArrayBufferView and body instanceof ArrayBuffer
|
||||
body = new Uint8Array body
|
||||
|
||||
try
|
||||
@xhr.send body
|
||||
catch e
|
||||
# Firefox doesn't support sending ArrayBufferViews.
|
||||
# Node.js doesn't implement Blob.
|
||||
if typeof Blob isnt 'undefined' and Dropbox.Xhr.ArrayBufferView and
|
||||
body instanceof Dropbox.Xhr.ArrayBufferView
|
||||
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
|
||||
@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)
|
||||
onLoad: ->
|
||||
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)
|
||||
onError: ->
|
||||
apiError = new Dropbox.ApiError @xhr, @method, @url
|
||||
@callback apiError
|
||||
return true
|
||||
21
lib/client/storage/dropbox/src/zzz-export.coffee
Normal file
21
lib/client/storage/dropbox/src/zzz-export.coffee
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# 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
|
||||
|
||||
6
lib/client/storage/dropbox/txt.vimrc.txt
Normal file
6
lib/client/storage/dropbox/txt.vimrc.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
" Indentation settings for the project: 2-space indentation, no tabs.
|
||||
set tabstop=2
|
||||
set softtabstop=2
|
||||
set shiftwidth=2
|
||||
set expandtab
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue