Please close this window.
19 | 20 | -------------------------------------------------------------------------------- /dropbox.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | angular.module('dropbox', []) 5 | 6 | 7 | .provider('Dropbox', function DropboxProvider () { 8 | 9 | 10 | var clientId, redirectUri; 11 | 12 | 13 | this.config = function (id, uri) { 14 | this.clientId = clientId = id; 15 | this.redirectUri = redirectUri = uri; 16 | }; 17 | 18 | 19 | this.$get = [ 20 | '$q', 21 | '$http', 22 | '$window', 23 | function ($q, $http, $window) { 24 | 25 | 26 | /** 27 | * Credentials 28 | */ 29 | 30 | var oauth = {}; 31 | 32 | 33 | /** 34 | * Dropbox API Servers 35 | */ 36 | 37 | var authServer = 'https://www.dropbox.com' 38 | , apiServer = 'https://api.dropbox.com' 39 | , fileServer = 'https://api-content.dropbox.com'; 40 | 41 | 42 | /** 43 | * API Method URLs 44 | */ 45 | 46 | var urls = { 47 | // Authentication. 48 | authorize: authServer + '/1/oauth2/authorize', 49 | token: apiServer + '/1/oauth2/token', 50 | signOut: apiServer + '/1/unlink_access_token', 51 | 52 | // Accounts. 53 | accountInfo: apiServer + '/1/account/info', 54 | 55 | // Files and metadata. 56 | getFile: fileServer + '/1/files/auto/', 57 | postFile: fileServer + '/1/files/auto/', 58 | putFile: fileServer + '/1/files_put/auto/', 59 | metadata: apiServer + '/1/metadata/auto/', 60 | delta: apiServer + '/1/delta', 61 | revisions: apiServer + '/1/revisions/auto/', 62 | restore: apiServer + '/1/restore/auto/', 63 | search: apiServer + '/1/search/auto/', 64 | shares: apiServer + '/1/shares/auto', 65 | media: apiServer + '/1/media/auto', 66 | copyRef: apiServer + '/1/copy_ref/auto', 67 | thumbnails: fileServer + '/1/thumbnails/auto', 68 | chunkedUpload: fileServer + '/1/chunked_upload', 69 | commitChunkedUpload: fileServer + '/1/commit_chunked_upload/auto', 70 | 71 | // File operations. 72 | fileopsCopy: apiServer + '/1/fileops/copy', 73 | fileopsCreateFolder: apiServer + '/1/fileops/create_folder', 74 | fileopsDelete: apiServer + '/1/fileops/delete', 75 | fileopsMove: apiServer + '/1/fileops/move' 76 | }; 77 | 78 | 79 | /** 80 | * OAuth 2.0 Signatures 81 | */ 82 | 83 | function oauthHeader(options) { 84 | if (!options.headers) { options.headers = {}; } 85 | options.headers['Authorization'] = 'Bearer ' + oauth.access_token; 86 | } 87 | 88 | function oauthParams(options) { 89 | if (!options.params) { options.params = {}; } 90 | options.params.access_token = oauth.access_token; 91 | } 92 | 93 | 94 | /** 95 | * HTTP Request Helper 96 | */ 97 | 98 | function request(config) { 99 | var deferred = $q.defer(); 100 | 101 | oauthHeader(config); 102 | 103 | function success(response) { 104 | console.log(config, response.data); 105 | deferred.resolve(response.data); 106 | } 107 | 108 | function failure(fault) { 109 | console.log(config, fault); 110 | deferred.reject(fault); 111 | } 112 | 113 | $http(config).then(success, failure); 114 | return deferred.promise; 115 | } 116 | 117 | 118 | /** 119 | * HTTP GET Helper 120 | */ 121 | 122 | function GET(url, params) { 123 | var responseType = 'text'; 124 | if (params) { 125 | if (params.arrayBuffer) { 126 | responseType = 'arraybuffer'; 127 | } else if (params.blob) { 128 | responseType = 'blob'; 129 | } else if (params.buffer) { 130 | responseType = 'buffer'; 131 | } else if (params.binary) { 132 | responseType = 'b'; // See the Dropbox.Util.Xhr.setResponseType docs 133 | } 134 | } 135 | 136 | return request({ 137 | responseType: responseType, 138 | method: 'GET', 139 | url: url, 140 | params: params 141 | }); 142 | } 143 | 144 | 145 | /** 146 | * HTTP POST Helper 147 | */ 148 | 149 | function POST(url, data, params) { 150 | return request({ 151 | method: 'POST', 152 | url: url, 153 | data: data, 154 | params: params 155 | }); 156 | } 157 | 158 | 159 | /** 160 | * Configure the authorize popup window 161 | * Adapted from dropbox-js 162 | */ 163 | 164 | function popupSize(popupWidth, popupHeight) { 165 | var x0, y0, width, height, popupLeft, popupTop; 166 | 167 | // Metrics for the current browser window. 168 | x0 = $window.screenX || $window.screenLeft 169 | y0 = $window.screenY || $window.screenTop 170 | width = $window.outerWidth || $document.documentElement.clientWidth 171 | height = $window.outerHeight || $document.documentElement.clientHeight 172 | 173 | // Computed popup window metrics. 174 | popupLeft = Math.round(x0) + (width - popupWidth) / 2 175 | popupTop = Math.round(y0) + (height - popupHeight) / 2.5 176 | if (popupLeft < x0) { popupLeft = x0 } 177 | if (popupTop < y0) { popupTop = y0 } 178 | 179 | return 'width=' + popupWidth + ',height=' + popupHeight + ',' + 180 | 'left=' + popupLeft + ',top=' + popupTop + ',' + 181 | 'dialog=yes,dependent=yes,scrollbars=yes,location=yes'; 182 | } 183 | 184 | 185 | /** 186 | * Parse credentials from Dropbox authorize callback 187 | * Adapted from dropbox-js 188 | */ 189 | 190 | function queryParamsFromUrl(url) { 191 | var match = /^[^?#]+(\?([^\#]*))?(\#(.*))?$/.exec(url); 192 | if (!match) { return {}; } 193 | 194 | var query = match[2] || '' 195 | , fragment = match[4] || '' 196 | , fragmentOffset = fragment.indexOf('?') 197 | , params = {} 198 | ; 199 | 200 | if (fragmentOffset !== -1) { 201 | fragment = fragment.substring(fragmentOffset + 1); 202 | } 203 | 204 | var kvp = query.split('&').concat(fragment.split('&')); 205 | kvp.forEach(function (kv) { 206 | var offset = kv.indexOf('='); 207 | if (offset === -1) { return; } 208 | params[decodeURIComponent(kv.substring(0, offset))] = 209 | decodeURIComponent(kv.substring(offset + 1)); 210 | }); 211 | 212 | return params; 213 | } 214 | 215 | 216 | /** 217 | * Dropbox Service 218 | */ 219 | 220 | return { 221 | 222 | 223 | urls: urls, // exposed for testing 224 | 225 | 226 | credentials: function () { 227 | return oauth; 228 | }, 229 | 230 | 231 | authenticate: function () { 232 | var self = this 233 | , deferred = $q.defer() 234 | , authUrl = urls.authorize 235 | + '?client_id=' + clientId 236 | // + '&state=' + 237 | + '&response_type=token' 238 | + '&redirect_uri=' + redirectUri 239 | 240 | function listener(event) { 241 | var response = queryParamsFromUrl(event.data); 242 | 243 | if (response.access_denied) { 244 | deferred.reject(response); 245 | } 246 | 247 | else if (response.access_token) { 248 | oauth = self.oauth = response; 249 | deferred.resolve(oauth); 250 | } 251 | 252 | $window.removeEventListener('message', listener, false); 253 | } 254 | 255 | $window.addEventListener('message', listener, false); 256 | $window.open(authUrl,'_dropboxOauthSigninWindow', popupSize(700, 500)); 257 | 258 | return deferred.promise; 259 | }, 260 | 261 | 262 | isAuthenticated: function () { 263 | return (oauth.access_token) ? true : false 264 | }, 265 | 266 | 267 | // signOut 268 | 269 | 270 | // signOff 271 | 272 | 273 | accountInfo: function () { 274 | return GET(urls.accountInfo); 275 | }, 276 | 277 | 278 | userInfo: function () { 279 | return this.accountInfo(); 280 | }, 281 | 282 | 283 | readFile: function (path, params) { 284 | return GET(urls.getFile + path, params); 285 | }, 286 | 287 | 288 | writeFile: function (path, content, params) { 289 | return request({ 290 | method: 'POST', 291 | url: urls.putFile + path, 292 | data: content, 293 | headers: { 'Content-Type': undefined }, 294 | transformRequest: angular.identity, 295 | params: params 296 | }); 297 | }, 298 | 299 | 300 | stat: function (path, params) { 301 | return GET(urls.metadata + path, params); 302 | }, 303 | 304 | 305 | readdir: function (path, params) { 306 | var deferred = $q.defer(); 307 | 308 | function success(stat) { 309 | var entries = stat.contents.map(function (entry) { 310 | return entry.path; 311 | }); 312 | 313 | console.log('readdir of ' + path, entries); 314 | deferred.resolve(entries); 315 | } 316 | 317 | function failure(fault) { deferred.reject(fault); } 318 | 319 | this.stat(path, params).then(success, failure); 320 | return deferred.promise; 321 | }, 322 | 323 | 324 | metadata: function (path, params) { 325 | return this.stat(path, params); 326 | }, 327 | 328 | 329 | // makeUrl 330 | 331 | 332 | history: function (path, params) { 333 | return GET(urls.revisions + path, params); 334 | }, 335 | 336 | 337 | revisions: function (path, params) { 338 | return this.history(path, params); 339 | }, 340 | 341 | 342 | thumbnailUrl: function (path, params) { 343 | return urls.thumbnails 344 | + path 345 | + '?format=jpeg&size=m&access_token=' 346 | + oauth.access_token; 347 | }, 348 | 349 | 350 | // readThumbnail 351 | 352 | 353 | revertFile: function (path, rev) { 354 | return POST(urls.restore + path, null, { rev: rev }); 355 | }, 356 | 357 | 358 | restore: function (path, rev) { 359 | return this.revertFile(path, rev); 360 | }, 361 | 362 | 363 | findByName: function (path, pattern, params) { 364 | var params = params || {}; 365 | params.query = pattern; 366 | 367 | return GET(urls.search + path, params); 368 | }, 369 | 370 | 371 | search: function (path, pattern, params) { 372 | return this.findByName(path, pattern, params); 373 | }, 374 | 375 | 376 | // makeCopyReference 377 | 378 | 379 | // copyRef 380 | 381 | 382 | // pullChanges 383 | 384 | 385 | // delta 386 | 387 | 388 | mkdir: function (path) { 389 | return POST(urls.fileopsCreateFolder, null, { 390 | root: 'auto', 391 | path: path 392 | }); 393 | }, 394 | 395 | 396 | remove: function (path) { 397 | return POST(urls.fileopsDelete, null, { 398 | root: 'auto', 399 | path: path 400 | }); 401 | }, 402 | 403 | 404 | unlink: function (path) { 405 | return this.remove(path); 406 | }, 407 | 408 | 409 | delete: function (path) { 410 | return this.remove(path); 411 | }, 412 | 413 | 414 | copy: function (from, to) { 415 | return POST(urls.fileopsCopy, null, { 416 | root: 'auto', 417 | to_path: to, 418 | from_path: from 419 | }); 420 | }, 421 | 422 | 423 | move: function (from, to) { 424 | return POST(urls.fileopsMove, null, { 425 | root: 'auto', 426 | to_path: to, 427 | from_path: from 428 | }); 429 | }, 430 | 431 | 432 | reset: function () { 433 | oauth = {}; 434 | }, 435 | 436 | 437 | setCredentials: function (credentials) { 438 | oauth = credentials; 439 | }, 440 | 441 | 442 | // appHash 443 | 444 | 445 | }; 446 | 447 | 448 | }]; 449 | 450 | 451 | }) 452 | 453 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | // your config 4 | frameworks: ["jasmine"], 5 | 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | 10 | // list of files / patterns to load in the browser 11 | files: [ 12 | 'bower_components/angular/angular.js', 13 | 'bower_components/angular-mocks/angular-mocks.js', 14 | 'dropbox.js', 15 | 'test/**/*Spec.coffee' 16 | ], 17 | 18 | 19 | // list of files to exclude 20 | exclude: [], 21 | 22 | 23 | // test results reporter to use 24 | // possible values: 'dots', 'progress', 'junit' 25 | reporters: ['progress'], 26 | 27 | 28 | // web server port 29 | port: 9876, 30 | 31 | 32 | // cli runner port 33 | runnerPort: 9100, 34 | 35 | 36 | // enable / disable colors in the output (reporters and logs) 37 | colors: true, 38 | 39 | 40 | // level of logging 41 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 42 | logLevel: config.LOG_INFO, 43 | 44 | 45 | // enable / disable watching file and executing tests whenever any file changes 46 | autoWatch: true, 47 | 48 | 49 | // Start these browsers, currently available: 50 | // - Chrome 51 | // - ChromeCanary 52 | // - Firefox 53 | // - Opera 54 | // - Safari (only Mac) 55 | // - PhantomJS 56 | // - IE (only Windows) 57 | browsers: ['Chrome'], 58 | 59 | 60 | // If browser does not capture in given timeout [ms], kill it 61 | captureTimeout: 60000, 62 | 63 | preprocessors: { 64 | '**/*.coffee': 'coffee' 65 | }, 66 | 67 | // Continuous Integration mode 68 | // if true, it capture browsers, run tests and exit 69 | singleRun: false 70 | }); 71 | }; 72 | 73 | 74 | -------------------------------------------------------------------------------- /test/spec/DropboxProviderSpec.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | describe 'DropboxProvider', -> 4 | 5 | 6 | {DropboxProvider} = {} 7 | 8 | 9 | beforeEach module 'dropbox' 10 | 11 | 12 | beforeEach module ($injector) -> 13 | DropboxProvider = $injector.get 'DropboxProvider' 14 | DropboxProvider.config 'CLIENT_ID', 'https://HOST/callback.html' 15 | 16 | 17 | beforeEach inject ($injector) -> 18 | Dropbox = $injector.get 'Dropbox' 19 | 20 | 21 | describe 'configure', -> 22 | 23 | it 'should set the client id', -> 24 | expect(DropboxProvider.clientId).toBe 'CLIENT_ID' 25 | 26 | it 'should set the redirect uri', -> 27 | expect(DropboxProvider.redirectUri).toBe 'https://HOST/callback.html' 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/spec/DropboxSpec.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | 4 | describe 'Dropbox', -> 5 | 6 | 7 | {DropboxProvider,Dropbox,$httpBackend,credentials} = {} 8 | 9 | 10 | headers = 11 | 'Accept': 'application/json, text/plain, */*' 12 | 'Authorization': 'Bearer g1bb3r1sh' 13 | 14 | 15 | beforeEach module 'dropbox' 16 | 17 | 18 | beforeEach inject ($injector) -> 19 | Dropbox = $injector.get 'Dropbox' 20 | $httpBackend = $injector.get '$httpBackend' 21 | credentials = access_token: 'g1bb3r1sh' 22 | Dropbox.setCredentials credentials 23 | 24 | 25 | afterEach -> 26 | $httpBackend.verifyNoOutstandingExpectation() 27 | $httpBackend.verifyNoOutstandingRequest() 28 | 29 | 30 | describe 'authorize', -> 31 | # TEST THE MESSAGE EVENT ON WINDOW 32 | 33 | 34 | describe 'credentials', -> 35 | 36 | it 'should return OAuth credentials', -> 37 | expect(Dropbox.credentials()).toEqual(credentials) 38 | 39 | 40 | describe 'authenticate', -> 41 | 42 | it 'should return a promise', -> 43 | expect(typeof Dropbox.authenticate().then).toEqual 'function' 44 | 45 | 46 | describe 'isAuthenticated', -> 47 | 48 | it 'should ...', -> 49 | expect(Dropbox.isAuthenticated()).toEqual true 50 | 51 | 52 | describe 'signOut', -> 53 | 54 | 55 | describe 'signOff', -> 56 | 57 | 58 | describe 'accountInfo', -> 59 | 60 | it 'should get account info', -> 61 | $httpBackend.expectGET("#{Dropbox.urls.accountInfo}").respond(null) 62 | Dropbox.accountInfo() 63 | $httpBackend.flush() 64 | 65 | 66 | describe 'readFile', -> 67 | 68 | it 'should get the contents of a file', -> 69 | url = "#{Dropbox.urls.getFile}directory/name.ext" 70 | $httpBackend.expectGET(url, headers).respond null 71 | Dropbox.readFile 'directory/name.ext' 72 | $httpBackend.flush() 73 | 74 | it 'should get the contents of a file as a blob', -> 75 | url = "#{Dropbox.urls.getFile}directory/name.ext?blob=true" 76 | $httpBackend.expectGET(url, headers).respond null 77 | Dropbox.readFile 'directory/name.ext', 'blob' : true 78 | $httpBackend.flush() 79 | 80 | describe 'writeFile', -> 81 | 82 | it 'should write string data', -> 83 | url = "#{Dropbox.urls.putFile}directory/file.txt" 84 | content = "contents of file" 85 | $httpBackend.expectPOST(url, content, headers).respond null 86 | Dropbox.writeFile 'directory/file.txt', content 87 | $httpBackend.flush() 88 | 89 | it 'should write ArrayBuffer data' 90 | it 'should write ArrayBuffer view data' 91 | it 'should write Blob data' 92 | it 'should write File data' 93 | it 'should write Buffer data' 94 | 95 | 96 | describe 'resumableUploadStep', -> 97 | 98 | 99 | describe 'resumableUploadFinish', -> 100 | 101 | 102 | describe 'stat', -> 103 | 104 | it 'should get the stat for a path', -> 105 | url = "#{Dropbox.urls.metadata}directory/name.ext" 106 | $httpBackend.expectGET(url, headers).respond null 107 | Dropbox.stat 'directory/name.ext' 108 | $httpBackend.flush() 109 | 110 | 111 | describe 'readdir', -> 112 | # HOW TO TEST THIS? 113 | 114 | 115 | describe 'metadata', -> 116 | 117 | it 'should get the metadata for a path', -> 118 | url = "#{Dropbox.urls.metadata}directory/name.ext" 119 | $httpBackend.expectGET(url, headers).respond null 120 | Dropbox.metadata 'directory/name.ext' 121 | $httpBackend.flush() 122 | 123 | 124 | describe 'makeUrl', -> 125 | 126 | 127 | describe 'history', -> 128 | 129 | it 'should get the history for a path', -> 130 | url = "#{Dropbox.urls.revisions}directory/name.ext" 131 | $httpBackend.expectGET(url, headers).respond null 132 | Dropbox.history 'directory/name.ext' 133 | $httpBackend.flush() 134 | 135 | 136 | describe 'revisions', -> 137 | 138 | it 'should get the revisions for a path', -> 139 | url = "#{Dropbox.urls.revisions}directory/name.ext" 140 | $httpBackend.expectGET(url, headers).respond null 141 | Dropbox.revisions 'directory/name.ext' 142 | $httpBackend.flush() 143 | 144 | 145 | describe 'thumbnailUrl', -> 146 | 147 | it 'should make a signed url for a thumbnail', -> 148 | url = "#{Dropbox.urls.thumbnails}directory/image.jpeg?format=jpeg&size=m&access_token=#{credentials.access_token}" 149 | expect(Dropbox.thumbnailUrl('directory/image.jpeg')).toEqual(url) 150 | 151 | 152 | describe 'readThumbnail', -> 153 | 154 | 155 | describe 'revertFile', -> 156 | 157 | 158 | it 'should restore a previous version', -> 159 | url = "#{Dropbox.urls.restore}file1.txt?rev=1234" 160 | $httpBackend.expectPOST(url, undefined).respond null 161 | Dropbox.revertFile 'file1.txt', '1234' 162 | $httpBackend.flush() 163 | 164 | 165 | describe 'restore', -> 166 | 167 | it 'should restore a previous version', -> 168 | url = "#{Dropbox.urls.restore}file1.txt?rev=1234" 169 | $httpBackend.expectPOST(url, undefined).respond null 170 | Dropbox.restore 'file1.txt', '1234' 171 | $httpBackend.flush() 172 | 173 | 174 | describe 'findByName', -> 175 | 176 | it 'should get query a path', -> 177 | url = "#{Dropbox.urls.search}directory/name.ext?query=terms" 178 | $httpBackend.expectGET(url, headers).respond null 179 | Dropbox.findByName 'directory/name.ext', 'terms' 180 | $httpBackend.flush() 181 | 182 | 183 | describe 'search', -> 184 | 185 | it 'should get query a path', -> 186 | url = "#{Dropbox.urls.search}directory/name.ext?query=terms" 187 | $httpBackend.expectGET(url, headers).respond null 188 | Dropbox.search 'directory/name.ext', 'terms' 189 | $httpBackend.flush() 190 | 191 | 192 | describe 'makeCopyReference', -> 193 | 194 | 195 | describe 'copyRef', -> 196 | 197 | 198 | describe 'pullChanges', -> 199 | 200 | 201 | describe 'delta', -> 202 | 203 | 204 | describe 'mkdir', -> 205 | 206 | it 'should create a folder', -> 207 | url = "#{Dropbox.urls.fileopsCreateFolder}?path=folder1&root=auto" 208 | $httpBackend.expectPOST(url, undefined).respond null 209 | Dropbox.mkdir 'folder1' 210 | $httpBackend.flush() 211 | 212 | 213 | describe 'remove', -> 214 | 215 | it 'should remove a file', -> 216 | url = "#{Dropbox.urls.fileopsDelete}?path=dir%2Ffile.ext&root=auto" 217 | $httpBackend.expectPOST(url, undefined).respond null 218 | Dropbox.remove 'dir/file.ext' 219 | $httpBackend.flush() 220 | 221 | 222 | describe 'unlink', -> 223 | 224 | it 'should remove a file', -> 225 | url = "#{Dropbox.urls.fileopsDelete}?path=dir%2Ffile.ext&root=auto" 226 | $httpBackend.expectPOST(url, undefined).respond null 227 | Dropbox.unlink 'dir/file.ext' 228 | $httpBackend.flush() 229 | 230 | 231 | describe 'delete', -> 232 | 233 | it 'should delete a file', -> 234 | url = "#{Dropbox.urls.fileopsDelete}?path=dir%2Ffile.ext&root=auto" 235 | $httpBackend.expectPOST(url, undefined).respond null 236 | Dropbox.delete 'dir/file.ext' 237 | $httpBackend.flush() 238 | 239 | 240 | describe 'copy', -> 241 | 242 | it 'should copy a file from one path to another', -> 243 | url = "#{Dropbox.urls.fileopsCopy}?from_path=dir%2Ffile1.ext&root=auto&to_path=dir%2Ffile2.ext" 244 | $httpBackend.expectPOST(url, undefined).respond null 245 | Dropbox.copy 'dir/file1.ext', 'dir/file2.ext' 246 | $httpBackend.flush() 247 | 248 | 249 | describe 'move', -> 250 | 251 | it 'should move a file', -> 252 | url = "#{Dropbox.urls.fileopsMove}?from_path=dir%2Ffile1.ext&root=auto&to_path=dir%2Ffile2.ext" 253 | $httpBackend.expectPOST(url, undefined).respond null 254 | Dropbox.move 'dir/file1.ext', 'dir/file2.ext' 255 | $httpBackend.flush() 256 | 257 | 258 | describe 'reset', -> 259 | 260 | 261 | describe 'setCredentials', -> 262 | 263 | 264 | describe 'appHash', -> 265 | 266 | --------------------------------------------------------------------------------