├── .editorconfig ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── VERSION ├── filepicker_debug.js ├── index.js ├── karma.conf.js ├── package.json ├── src ├── communication │ ├── comm.js │ ├── comm_fallback.js │ ├── cookies.js │ └── handlers.js ├── dialog │ ├── exporter.js │ ├── modal.js │ ├── picker.js │ └── window.js ├── finalize.js ├── library │ ├── conversions.js │ ├── errors.js │ ├── lib.js │ ├── mimetypes.js │ ├── services.js │ └── urls.js ├── server │ ├── ajax.js │ ├── files.js │ └── iframeAjax.js ├── setup-spec.js ├── setup.js ├── utils │ ├── base64.js │ ├── browser.js │ ├── conversions_util.js │ ├── json.js │ ├── strutil.js │ ├── util.js │ └── window_utils.js └── widgets │ ├── dragdrop.js │ ├── responsive_images.js │ └── widgets.js └── tests └── unit ├── communication ├── comm-spec.js ├── cookies-spec.js └── handlers-spec.js ├── dialog ├── exporter-spec.js ├── modal-spec.js ├── picker-spec.js └── window-spec.js ├── images ├── a_test.png ├── b_test.png └── c_test.png ├── init-spec.js ├── library ├── conversions-spec.js ├── errors-spec.js ├── lib-spec.js ├── mimetypes-spec.js ├── services-spec.js └── urls-spec.js ├── responses ├── ajax_responses-spec.js ├── conversion_responses-spec.js ├── files_read_responses-spec.js ├── files_write_responses-spec.js ├── modal_responses-spec.js └── responses_init-spec.js ├── server ├── ajax-spec.js ├── files-spec.js └── iframeAjax-spec.js ├── utils ├── base64-spec.js ├── browser-spec.js ├── conversions_util-spec.js ├── json-spec.js ├── strutil-spec.js └── util-spec.js └── widgets ├── dragdrop-spec.js ├── responsive_images-spec.js └── widgets-spec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.js] 13 | indent_size = 4 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tmp 3 | npm-debug.log 4 | dist.tar 5 | dist 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": false, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": false, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": false, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "evil": true, 21 | "white": true, 22 | "validthis": true, 23 | "globals": { 24 | /* MOCHA */ 25 | "describe" : false, 26 | "it" : false, 27 | "before" : false, 28 | "beforeEach" : false, 29 | "after" : false, 30 | "afterEach" : false, 31 | "expect" : false, 32 | "window": false, 33 | "navigator":false, 34 | "File": false, 35 | "document": false, 36 | "Image": false, 37 | "FileReader": false, 38 | "Blob": false, 39 | "filepicker": false 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tmp 3 | npm-debug.log 4 | dist.tar 5 | src 6 | .jshintrc 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.12.0' 4 | cache: 5 | directories: 6 | - node_modules 7 | before_install: 8 | - npm i -g npm@^2.0.0 9 | install: 10 | - sudo apt-get update 11 | - sudo apt-get install xvfb 12 | - sudo apt-get install firefox 13 | - npm install 14 | script: 15 | - xvfb-run npm run test 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | ### 2.4.20 (12.03.2018) 3 | - Bugfix for loading picker with video in iframe (chrome browser) 4 | 5 | ### 2.4.19 (23.02.2017) 6 | - Add changes for Cloudinary upload presets and resource type [#57](https://github.com/filepicker/filepicker-js/pull/57) 7 | 8 | ### 2.4.18 (03.10.2016) 9 | - Make try/catch block less aggressive [#46](https://github.com/filepicker/filepicker-js/pull/46) 10 | 11 | ### 2.4.17 (17.08.2016) 12 | - Fix storeUrl regex [#43](https://github.com/filepicker/filepicker-js/pull/43) 13 | - Guard for malformed messages [#44](https://github.com/filepicker/filepicker-js/pull/44) 14 | 15 | ### 2.4.16 (29.07.2016) 16 | - Bugfix for `logout()` method 17 | 18 | ### 2.4.15 (28.07.2016) 19 | - Added `logout()` method [#41](https://github.com/filepicker/filepicker-js/pull/41) 20 | 21 | ### 2.4.13 & 2.4.14 (30.06.2016) 22 | - Fixed issue [#21](https://github.com/filepicker/filepicker-js/issues/21) 23 | 24 | ### 2.4.12 (24.06.2016) 25 | - [Blobs](https://developer.mozilla.org/en-US/docs/Web/API/Blob) support in `filepicker.store()` method. [#37](https://github.com/filepicker/filepicker-js/pull/37) 26 | 27 | ### 2.4.11 (19.05.2016) 28 | - Pass `converted` property to blob in onSuccess callback 29 | 30 | ### 2.4.10 (05.05.2016) 31 | - Replace cdn urls for preview widget [#34](https://github.com/filepicker/filepicker-js/pull/34) 32 | 33 | ### 2.4.9 (04.05.2016) 34 | - Passing crop and rotation data to blob [#33](https://github.com/filepicker/filepicker-js/pull/33) 35 | 36 | ### 2.4.8 (25.04.2016) 37 | - Handle HTTP server errors [#32](https://github.com/filepicker/filepicker-js/pull/32) 38 | 39 | ### 2.4.7 (07.04.2016) 40 | - Extensions are case insensitive now [#31](https://github.com/filepicker/filepicker-js/pull/31) 41 | 42 | ### 2.4.5 (25.02.2016) 43 | - Widget button rebranded to FileStack [#29](https://github.com/filepicker/filepicker-js/pull/29) 44 | 45 | ### 2.4.4 (16.02.2016) 46 | - Add filtering support to audio client [#28](https://github.com/filepicker/filepicker-js/pull/28) 47 | 48 | ### 2.4.3 (09.02.2016) 49 | - Add storeRegion option to the picker. [#19](https://github.com/filepicker/filepicker-js/pull/19) 50 | - Added option for setting video & webcam resolution [#20](https://github.com/filepicker/filepicker-js/pull/20) 51 | 52 | ### 2.4.2 (08.02.2016) 53 | - Responsive images lookup can be triggered at any time. [#23](https://github.com/filepicker/filepicker-js/pull/23) 54 | - Fix typo in ```data-fp-custom-source-conatiner``` attribute name. [#27](https://github.com/filepicker/filepicker-js/pull/27) 55 | 56 | ### 2.4.1 (05.02.2016) 57 | - Allow every image to be processed. [#25](https://github.com/filepicker/filepicker-js/pull/25) 58 | 59 | ### 2.3.8 (22.01.2016) 60 | - Handle safari standalone mode [#11](https://github.com/filepicker/filepicker-js/pull/11) 61 | 62 | ### 2.3.7 (20.01.2016) 63 | - Parse options.noFileReader parameter for server vars [#17](https://github.com/filepicker/filepicker-js/pull/17) 64 | 65 | ### 2.3.6 (15.01.2016) 66 | - Bugfix for dialog close method. 67 | 68 | ### 2.3.5 (15.01.2016) 69 | - Add ability to close opened dialog from JavaScript. [#15](https://github.com/filepicker/filepicker-js/pull/15) 70 | - Reckognize .vob file extension. [#16](https://github.com/filepicker/filepicker-js/pull/16) 71 | 72 | ### 2.3.4 (12.01.2016) 73 | - Add custom source container and path options for html widgets attributes list. [#13](https://github.com/filepicker/filepicker-js/pull/13) 74 | 75 | ### 2.3.3 (16.12.2015) 76 | - Hotfix. Missing slash in processing base url. 77 | 78 | ### 2.3.2 (16.12.2015) 79 | - Set dynamicly domain for processing. 80 | 81 | ### 2.3.1 (23.11.2015) 82 | - Add custom source path as a picker option [#7](https://github.com/filepicker/filepicker-js/pull/7) 83 | 84 | ### 2.3.0 (18.11.2015) 85 | - Add custom source bucket name as a picker option [#5](https://github.com/filepicker/filepicker-js/pull/5) 86 | - Add custom css url option for viewer [#6](https://github.com/filepicker/filepicker-js/pull/6) 87 | 88 | ### 2.2.1 (5.11.2015) 89 | - Add custom text option to the widget options. [#4](https://github.com/filepicker/filepicker-js/pull/4) 90 | 91 | ### 2.2.0 (3.11.2015) 92 | - Add responsive images feature. [#1](https://github.com/filepicker/filepicker-js/pull/1) 93 | 94 | ### 2.1.3 (15.10.2015) 95 | - Add prepublish action. Make sure to build dist version before publishing to npm. 96 | 97 | ### 2.1.2 (15.10.2015) 98 | - Fix. Filtering allowed conversions. Use hasOwnProperty method. 99 | 100 | ### 2.1.1 (09.10.2015) 101 | - Do not use Array.filter and Array.indexOf methods. Support IE8. 102 | 103 | ### 2.1.0 (06.10.2015) 104 | - Add filepicker debug script. 105 | - Add client value to response object. 106 | 107 | ### 2.0.0 (28.09.2015) 108 | - init filepicker-js repository as separate module 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # filepicker-js 2 | Filepicker javascript client library. 3 | 4 | ## Usage 5 | 6 | In order to use filepicker javascript library in your project, you need to include the following script in your HTML: 7 | 8 | ``` 9 | 10 | ``` 11 | 12 | If you want to load the javascript in a non-blocking fashion, you can use this instead: 13 | ``` 14 | 17 | ``` 18 | Script above use latest library release. Assets are compressed (gzipped) and served via CDN. 19 | You can also link to specific version. 20 | 21 | [https://api.filepicker.io/v2/filepicker-2.1.3.js](https://api.filepicker.io/v2/filepicker-2.1.3.js) 22 | [https://api.filepicker.io/v2/filepicker-2.1.3.min.js](https://api.filepicker.io/v2/filepicker-2.1.3.min.js) 23 | 24 | See [Changelog](CHANGELOG.md) 25 | 26 | Filepicker library is avaliable via bower [Bower friendly repositorium](https://github.com/krystiangw/filepicker-js-bower) 27 | 28 | ``` 29 | $ bower install filepicker-js --save 30 | ``` 31 | 32 | And via npm + browserify 33 | ``` 34 | $ npm install filepicker-js --save 35 | ``` 36 | 37 | To use it with browseify place in your code: 38 | ``` 39 | var filepickerLibrary = require('filepicker-js'); 40 | ``` 41 | 42 | Library provide ```window.filepicker``` with methods: 43 | ```setKey, pick, pickFolder, pickMultiple, pickAndStore, read, write, writeUrl, export, processImage, store, storeUrl, stat, metadata, remove, convert, constructWidget, makeDropPane```. See detailed [docs](https://www.filepicker.com/documentation/file_ingestion/javascript_api/pick?v=v2). 44 | 45 | Next thing to do is setting apikey. If you dont have one - register free account [here](https://www.filepicker.com/register/free). Setting key is possible in 2 ways: 46 | 47 | * use ```filepicker.setKey('yourApiKey')``` method. 48 | * as widget attribute ```data-fp-apikey="yourApiKey"``` 49 | 50 | 51 | ## Contributing 52 | Contributing welcomed. First install npm dependencies. 53 | ``` 54 | npm install 55 | ``` 56 | To watch changes and build script run: 57 | ``` 58 | npm run watch 59 | ``` 60 | With jshint: 61 | ``` 62 | npm run watch-linter 63 | ``` 64 | 65 | 66 | ## Releasing 67 | 1. When updating version be sure to update it in all files: 68 | 69 | ``` 70 | ./VERSION 71 | ./package.json 72 | ./src/library/lib.js 73 | ``` 74 | 75 | 2. Set git tag with current version. 76 | 77 | 3. Be sure to update [npm package version](https://www.npmjs.com/package/filepicker-js) : 78 | ``` 79 | npm publish 80 | ``` 81 | 82 | 4. And [Bower-friendly version of filepicker-js](https://github.com/filepicker/filepicker-js-bower) 83 | 84 | 85 | ## Deployment 86 | ### Filepicker 87 | 88 | Use ansible script to deploy current version for filepicker. 89 | 90 | ``` 91 | source ../vagrant/aws/new && ansible-playbook -i env/production/inventory filepicker_api/deploy_js_library_v2.yml 92 | ``` 93 | 94 | * optionally to deploy from branch othter than master 95 | ``` 96 | -e emergency_deploy="yes" 97 | ``` 98 | 99 | * optionally not to overwrite edge version 100 | ``` 101 | -e edge_version="no" 102 | ``` 103 | 104 | It overwrites [filepicker.js](https://api.filepicker.io/v2/filepicker.js) with current version. It creates versioned files, eg for v2.4.0: 105 | 106 | * [filepicker-2.4.0.js](https://api.filepicker.io/v2/filepicker-2.4.0.js) 107 | * [filepicker-2.4.0.min.js](https://api.filepicker.io/v2/filepicker-2.4.0.min.js) 108 | * [filepicker_debug-2.4.0.js](https://api.filepicker.io/v2/filepicker_debug-2.4.0.js) 109 | 110 | ### Filestack 111 | 112 | ``` 113 | source ~/.filepicker/aws_new && ansible-playbook -i env/production filestack_api/build_js.yml 114 | ``` 115 | 116 | Its working basically the same. The only diffrents is domain and file name it creates. 117 | [https://api.filestackapi.com/filestack.js](https://api.filestackapi.com/filestack.js) 118 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.4.20 2 | -------------------------------------------------------------------------------- /filepicker_debug.js: -------------------------------------------------------------------------------- 1 | //Include this script to help debug 2 | 3 | filepicker.debug = true; 4 | 5 | filepicker.error_map = { 6 | /*General*/ 7 | 400: { 8 | message: "Invalid request to the server - do you need to pass a security policy and signature?", 9 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/400' 10 | }, 11 | 403: { 12 | message: "Not authorized to make this request", 13 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/403' 14 | }, 15 | 101: { 16 | message: 'The user closed the picker without choosing a file', 17 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/101' 18 | }, 19 | 111: { 20 | message: "Your browser doesn't support reading from DOM File objects", 21 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/111' 22 | }, 23 | 112: { 24 | message: "Your browser doesn't support reading from different domains", 25 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/112' 26 | }, 27 | 113: { 28 | message: "The website of the URL you provided does not allow other domains to read data", 29 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/113' 30 | }, 31 | 114: { 32 | message: "The website of the URL you provided had an error", 33 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/114' 34 | }, 35 | 115: { 36 | message: "File not found", 37 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/115' 38 | }, 39 | 118: { 40 | message: "Unknown read error", 41 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/118' 42 | }, 43 | 121: { 44 | message: "The FPFile to write to cannot be found", 45 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/121' 46 | }, 47 | 122: { 48 | message: "The Remote URL could not be reached", 49 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/122' 50 | }, 51 | 123: { 52 | message: "Unknown write error", 53 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/123' 54 | }, 55 | 131: { 56 | message: 'The user closed the dialog without exporting a file', 57 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/131' 58 | }, 59 | 141: { 60 | message: "The FPFile to convert could not be found", 61 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/141' 62 | }, 63 | 142: { 64 | message: "The FPFile could not be converted with the requested parameters", 65 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/142' 66 | }, 67 | 143: { 68 | message: "Unknown error when converting the file", 69 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/143' 70 | }, 71 | 151: { 72 | message: "The file store could not be reached", 73 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/151' 74 | }, 75 | 152: { 76 | message: "The Remote URL could not be reached", 77 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/152' 78 | }, 79 | 153: { 80 | message: "Unknown error when storing", 81 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/153' 82 | }, 83 | 161: { 84 | message: "The file cannot be found", 85 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/161' 86 | }, 87 | 162: { 88 | message: "Error fetching metadata", 89 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/162' 90 | }, 91 | 163: { 92 | message: "Unknown error when fetching metadata", 93 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/163' 94 | }, 95 | 171: { 96 | message: "The file cannot be found, and may have already been deleted", 97 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/171' 98 | }, 99 | 172: { 100 | message: "The underlying content store could not be reached", 101 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/172' 102 | }, 103 | 173: { 104 | message: "Unknown issue when removing", 105 | moreInfo: 'https://developers.filepicker.io/answers/jsErrors/173' 106 | } 107 | }; 108 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Module definition for browserify 3 | */ 4 | require('./dist/filepicker'); 5 | module.exports = filepicker; 6 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu Oct 08 2015 11:32:53 GMT+0200 (CEST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine-ajax', 'jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'dist/filepicker-spec.js', 19 | 'tests/unit/init-spec.js', 20 | 'tests/unit/responses/*.js', 21 | 'tests/unit/**/*spec.js', 22 | ], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | ], 28 | 29 | 30 | // preprocess matching files before serving them to the browser 31 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 32 | preprocessors: { 33 | }, 34 | 35 | 36 | // test results reporter to use 37 | // possible values: 'dots', 'progress' 38 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 39 | reporters: ['progress'], 40 | 41 | 42 | // web server port 43 | port: 9876, 44 | 45 | 46 | // enable / disable colors in the output (reporters and logs) 47 | colors: true, 48 | 49 | 50 | // level of logging 51 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 52 | logLevel: config.LOG_INFO, 53 | 54 | 55 | // enable / disable watching file and executing tests whenever any file changes 56 | autoWatch: true, 57 | 58 | 59 | // start these browsers 60 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 61 | browsers: ['Firefox'], 62 | 63 | 64 | // Continuous Integration mode 65 | // if true, Karma captures browsers, runs the tests and exits 66 | singleRun: false 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "filepicker-js", 3 | "version": "2.4.20", 4 | "description": "Filepicker client javascript library", 5 | "main": "index.js", 6 | "scripts": { 7 | "prepublish": "npm run build", 8 | "pretest": "npm run prebuild && uglifyjs ./src/setup-spec.js ./src/**/*.js ./src/finalize.js -o ./dist/filepicker-spec.js -b ", 9 | "test": "./node_modules/.bin/karma start --single-run", 10 | "prebuild": "rm -rf dist && mkdir dist", 11 | "postbuild": "uglifyjs dist/filepicker.js -o ./dist/filepicker.min.js --compress --mangle && cp filepicker_debug.js dist/", 12 | "build": "uglifyjs ./src/setup.js ./src/**/*.js ./src/finalize.js -o ./dist/filepicker.js -b ", 13 | "watch": "onchange ./src/*.js ./src/**/*.js -- npm run build", 14 | "watch-test": "onchange ./tests/unit/*.js ./tests/unit/**/*.js ./src/*.js ./src/**/*.js -- npm run test", 15 | "linter": "jshint ./src/*.js ./src/**/*.js", 16 | "watch-linter": "onchange ./src/*.js ./src/**/*.js -- npm run linter" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/filepicker/filepicker-js.git" 21 | }, 22 | "keywords": [ 23 | "filepicker", 24 | "file", 25 | "upload", 26 | "file", 27 | "processing", 28 | "store", 29 | "widget" 30 | ], 31 | "author": "krystiangw", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/filepicker/filepicker-js/issues" 35 | }, 36 | "homepage": "https://github.com/filepicker/filepicker-js", 37 | "devDependencies": { 38 | "jasmine-core": "2.3.4", 39 | "jshint": "2.8.0", 40 | "karma": "0.13.19", 41 | "karma-firefox-launcher": "0.1.6", 42 | "karma-jasmine": "0.3.6", 43 | "karma-jasmine-ajax": "0.1.13", 44 | "onchange": "2.0.0", 45 | "uglify-js": "2.6.0" 46 | }, 47 | "dependencies": {} 48 | } 49 | -------------------------------------------------------------------------------- /src/communication/comm.js: -------------------------------------------------------------------------------- 1 | //comm.js 2 | 'use strict'; 3 | 4 | filepicker.extend('comm', function(){ 5 | var fp = this; 6 | 7 | var COMM_IFRAME_NAME = 'filepicker_comm_iframe'; 8 | var API_IFRAME_NAME = 'fpapi_comm_iframe'; 9 | 10 | /* 11 | * Opens the IFrame if there isn't one 12 | */ 13 | var openCommIframe = function(){ 14 | if (window.frames[COMM_IFRAME_NAME] === undefined) { 15 | //Attach a event handler 16 | openCommunicationsChannel(); 17 | 18 | //Opening an iframe to send events 19 | var commIFrame; 20 | commIFrame = document.createElement('iframe'); 21 | commIFrame.id = commIFrame.name = COMM_IFRAME_NAME; 22 | commIFrame.src = fp.urls.COMM; 23 | commIFrame.style.display = 'none'; 24 | document.body.appendChild(commIFrame); 25 | } 26 | if (window.frames[API_IFRAME_NAME] === undefined) { 27 | //Attach a event handler 28 | openCommunicationsChannel(); 29 | 30 | //Opening an iframe to send events 31 | var apiIFrame; 32 | apiIFrame = document.createElement('iframe'); 33 | apiIFrame.id = apiIFrame.name = API_IFRAME_NAME; 34 | apiIFrame.src = fp.urls.API_COMM; 35 | apiIFrame.style.display = 'none'; 36 | document.body.appendChild(apiIFrame); 37 | } 38 | }; 39 | 40 | var communicationsHandler = function(event){ 41 | if (event.origin !== fp.urls.BASE && event.origin !== fp.urls.DIALOG_BASE) { 42 | return; 43 | } 44 | try { 45 | var data = fp.json.parse(event.data); 46 | } catch(err) { 47 | console.log('[Filepicker] Failed processing message:', event.data); 48 | } 49 | if (data) { 50 | fp.handlers.run(data); 51 | } 52 | }; 53 | 54 | /* 55 | * 1. Creates the general communcation handler 56 | * 2. Set to listen 57 | * ONLY RUN ONCE 58 | */ 59 | var isOpen = false; 60 | 61 | var openCommunicationsChannel = function(){ 62 | if (isOpen){ 63 | return; 64 | } else { 65 | isOpen = true; 66 | } 67 | 68 | 69 | //Modern 70 | if (window.addEventListener) { 71 | window.addEventListener('message', communicationsHandler, false); 72 | //IE8, FF3 73 | } else if (window.attachEvent) { 74 | window.attachEvent('onmessage', communicationsHandler); 75 | //No hope 76 | } else { 77 | throw new fp.FilepickerException('Unsupported browser'); 78 | } 79 | }; 80 | 81 | var destroyCommIframe = function(){ 82 | //Modern 83 | if (window.removeEventListener) { 84 | window.removeEventListener('message', communicationsHandler, false); 85 | //IE8, FF3 86 | } else if (window.attachEvent) { 87 | window.detachEvent('onmessage', communicationsHandler); 88 | //No hope 89 | } else { 90 | throw new fp.FilepickerException('Unsupported browser'); 91 | } 92 | 93 | if (!isOpen){ 94 | return; 95 | } else { 96 | isOpen = false; 97 | } 98 | //Also removing iframe 99 | var iframes = document.getElementsByName(COMM_IFRAME_NAME); 100 | for (var i = 0; i < iframes.length; i++){ 101 | iframes[i].parentNode.removeChild(iframes[i]); 102 | } 103 | try{delete window.frames[COMM_IFRAME_NAME];}catch(e){} 104 | var api_iframes = document.getElementsByName(API_IFRAME_NAME); 105 | for (var j = 0; j < api_iframes.length; j++){ 106 | api_iframes[j].parentNode.removeChild(api_iframes[j]); 107 | } 108 | try{delete window.frames[API_IFRAME_NAME];}catch(e){} 109 | }; 110 | 111 | return { 112 | openChannel: openCommIframe, 113 | closeChannel: destroyCommIframe 114 | }; 115 | }); 116 | -------------------------------------------------------------------------------- /src/communication/comm_fallback.js: -------------------------------------------------------------------------------- 1 | //comm_fallback.js 2 | 'use strict'; 3 | 4 | filepicker.extend('comm_fallback', function(){ 5 | var fp = this; 6 | 7 | var FP_COMM_IFRAME_NAME = 'filepicker_comm_iframe'; 8 | var HOST_COMM_IFRAME_NAME = 'host_comm_iframe'; 9 | var base_host_location = ''; 10 | var hash_check_interval = 200; 11 | 12 | /* 13 | * Opens the IFrame if there isn't one 14 | */ 15 | var openCommIframe = function(){ 16 | openHostCommIframe(); 17 | }; 18 | 19 | //First we open a host comm iframe to test what the url is we'll be working with on the host 20 | //to make sure we don't run into redirect issues 21 | var openHostCommIframe = function(){ 22 | if (window.frames[HOST_COMM_IFRAME_NAME] === undefined) { 23 | //Opening an iframe to send events 24 | var hostCommIFrame; 25 | hostCommIFrame = document.createElement('iframe'); 26 | hostCommIFrame.id = hostCommIFrame.name = HOST_COMM_IFRAME_NAME; 27 | base_host_location = hostCommIFrame.src = fp.urls.constructHostCommFallback(); 28 | hostCommIFrame.style.display = 'none'; 29 | var onload = function(){ 30 | base_host_location = hostCommIFrame.contentWindow.location.href; 31 | //Then we open the filepicker comm iframe 32 | openFPCommIframe(); 33 | }; 34 | if (hostCommIFrame.attachEvent) { 35 | hostCommIFrame.attachEvent('onload', onload); 36 | } else { 37 | hostCommIFrame.onload = onload; 38 | } 39 | document.body.appendChild(hostCommIFrame); 40 | } 41 | }; 42 | 43 | var openFPCommIframe = function(){ 44 | if (window.frames[FP_COMM_IFRAME_NAME] === undefined) { 45 | //Opening an iframe to send events 46 | var fpCommIFrame; 47 | fpCommIFrame = document.createElement('iframe'); 48 | fpCommIFrame.id = fpCommIFrame.name = FP_COMM_IFRAME_NAME; 49 | fpCommIFrame.src = fp.urls.FP_COMM_FALLBACK + '?host_url=' + encodeURIComponent(base_host_location); 50 | fpCommIFrame.style.display = 'none'; 51 | document.body.appendChild(fpCommIFrame); 52 | } 53 | openCommunicationsChannel(); 54 | }; 55 | 56 | /* 57 | * 1. Creates the general communcation handler 58 | * 2. Set to listen 59 | * ONLY RUN ONCE 60 | */ 61 | var isOpen = false; 62 | var timer; 63 | var lastHash = ''; 64 | var checkHash = function(){ 65 | var comm_iframe = window.frames[FP_COMM_IFRAME_NAME]; 66 | if (!comm_iframe) {return;} 67 | var host_iframe = comm_iframe.frames[HOST_COMM_IFRAME_NAME]; 68 | if (!host_iframe) {return;} 69 | 70 | var hash = host_iframe.location.hash; 71 | //sanitization 72 | if (hash && hash.charAt(0) === '#') { 73 | hash = hash.substr(1); 74 | } 75 | if (hash === lastHash) {return;} 76 | lastHash = hash; 77 | if (!lastHash) {return;} 78 | 79 | var data; 80 | try{ 81 | data = fp.json.parse(hash); 82 | } catch (e){} 83 | 84 | if (data) { 85 | fp.handlers.run(data); 86 | } 87 | }; 88 | 89 | var openCommunicationsChannel = function(){ 90 | if (isOpen){ 91 | return; 92 | } else { 93 | isOpen = true; 94 | } 95 | 96 | timer = window.setInterval(checkHash, hash_check_interval); 97 | }; 98 | 99 | var destroyCommIframe = function(){ 100 | window.clearInterval(timer); 101 | 102 | if (!isOpen){ 103 | return; 104 | } else { 105 | isOpen = false; 106 | } 107 | //Also removing iframe 108 | var iframes = document.getElementsByName(FP_COMM_IFRAME_NAME); 109 | for (var i = 0; i < iframes.length; i++){ 110 | iframes[i].parentNode.removeChild(iframes[i]); 111 | } 112 | try{delete window.frames[FP_COMM_IFRAME_NAME];}catch(e){} 113 | 114 | iframes = document.getElementsByName(HOST_COMM_IFRAME_NAME); 115 | for (i = 0; i < iframes.length; i++){ 116 | iframes[i].parentNode.removeChild(iframes[i]); 117 | } 118 | try{delete window.frames[HOST_COMM_IFRAME_NAME];}catch(e){} 119 | }; 120 | 121 | var isEnabled = !('postMessage' in window); 122 | var setEnabled = function(enabled) { 123 | if (enabled !== isEnabled) { 124 | isEnabled = !!enabled; 125 | if (isEnabled) { 126 | activate(); 127 | } else { 128 | deactivate(); 129 | } 130 | } 131 | }; 132 | 133 | var old_comm; 134 | var activate = function(){ 135 | old_comm = fp.comm; 136 | fp.comm = { 137 | openChannel: openCommIframe, 138 | closeChannel: destroyCommIframe 139 | }; 140 | }; 141 | 142 | var deactivate = function(){ 143 | fp.comm = old_comm; 144 | old_comm = undefined; 145 | }; 146 | 147 | if (isEnabled) { 148 | activate(); 149 | } 150 | 151 | return { 152 | openChannel: openCommIframe, 153 | closeChannel: destroyCommIframe, 154 | isEnabled: isEnabled 155 | }; 156 | }); 157 | -------------------------------------------------------------------------------- /src/communication/cookies.js: -------------------------------------------------------------------------------- 1 | //cookies.js 2 | 'use strict'; 3 | 4 | filepicker.extend('cookies', function(){ 5 | var fp = this; 6 | 7 | var getReceiveCookiesMessage = function(callback) { 8 | var handler = function(data) { 9 | if (data.type !== 'ThirdPartyCookies'){ 10 | return; 11 | } 12 | fp.cookies.THIRD_PARTY_COOKIES = !!data.payload; 13 | if (callback && typeof callback === 'function'){ callback(!!data.payload);} 14 | }; 15 | return handler; 16 | }; 17 | 18 | var checkThirdParty = function(callback) { 19 | var handler = getReceiveCookiesMessage(callback); 20 | fp.handlers.attach('cookies', handler); 21 | 22 | fp.comm.openChannel(); 23 | }; 24 | 25 | return { 26 | checkThirdParty: checkThirdParty 27 | }; 28 | }); 29 | -------------------------------------------------------------------------------- /src/communication/handlers.js: -------------------------------------------------------------------------------- 1 | //handlers.js 2 | 'use strict'; 3 | 4 | filepicker.extend('handlers', function(){ 5 | var fp = this; 6 | var storage = {}; 7 | 8 | var attachHandler = function(id, handler){ 9 | if (storage.hasOwnProperty(id)){ 10 | storage[id].push(handler); 11 | } else { 12 | storage[id] = [handler]; 13 | } 14 | return handler; 15 | }; 16 | 17 | var detachHandler = function(id, fn){ 18 | var handlers = storage[id]; 19 | if (!handlers) { 20 | return; 21 | } 22 | 23 | if (fn) { 24 | for (var i = 0; i < handlers.length; i++) { 25 | if (handlers[i] === fn) { 26 | handlers.splice(i,1); 27 | break; 28 | } 29 | } 30 | if (handlers.length === 0) { 31 | delete(storage[id]); 32 | } 33 | } else { 34 | delete(storage[id]); 35 | } 36 | }; 37 | 38 | var run = function(data){ 39 | if (data == null || data.id == null) { 40 | return false; 41 | } 42 | var callerId = data.id; 43 | if (storage.hasOwnProperty(callerId)){ 44 | //have to grab first in case someone removes mid-go 45 | var handlers = storage[callerId]; 46 | for (var i = 0; i < handlers.length; i++) { 47 | handlers[i](data); 48 | } 49 | return true; 50 | } 51 | return false; 52 | }; 53 | 54 | return { 55 | attach: attachHandler, 56 | detach: detachHandler, 57 | run: run 58 | }; 59 | }); 60 | -------------------------------------------------------------------------------- /src/dialog/exporter.js: -------------------------------------------------------------------------------- 1 | //exporter.js 2 | 'use strict'; 3 | 4 | filepicker.extend('exporter', function(){ 5 | var fp = this; 6 | 7 | var normalizeOptions = function(options) { 8 | var normalize = function(singular, plural, def){ 9 | if (options[plural] && !fp.util.isArray(options[plural])) { 10 | options[plural] = [options[plural]]; 11 | } else if (options[singular]) { 12 | options[plural] = [options[singular]]; 13 | } else if (def) { 14 | options[plural] = def; 15 | } 16 | }; 17 | 18 | if (options.mimetype && options.extension) { 19 | throw fp.FilepickerException('Error: Cannot pass in both mimetype and extension parameters to the export function'); 20 | } 21 | normalize('service', 'services'); 22 | if (options.services) { 23 | for (var i = 0; i < options.services.length; i++) { 24 | var service = (''+options.services[i]).replace(' ',''); 25 | var sid = fp.services[service]; 26 | options.services[i] = (sid === undefined ? service : sid); 27 | } 28 | } 29 | if (options.openTo) { 30 | options.openTo = fp.services[options.openTo] || options.openTo; 31 | } 32 | 33 | fp.util.setDefault(options, 'container', fp.browser.openInModal() ? 'modal' : 'window'); 34 | }; 35 | 36 | var getExportHandler = function(onSuccess, onError) { 37 | var handler = function(data) { 38 | if (data.type !== 'filepickerUrl'){ 39 | return; 40 | } 41 | 42 | if (data.error) { 43 | fp.util.console.error(data.error); 44 | onError(fp.errors.FPError(132)); 45 | } else { 46 | var fpfile = {}; 47 | //TODO: change payload to not require parsing 48 | fpfile.url = data.payload.url; 49 | fpfile.filename = data.payload.data.filename; 50 | fpfile.mimetype = data.payload.data.type; 51 | fpfile.size = data.payload.data.size; 52 | fpfile.client = data.payload.data.client; 53 | //TODO: get writeable 54 | fpfile.isWriteable = true; 55 | onSuccess(fpfile); 56 | } 57 | 58 | //Try to close a modal if it exists. 59 | fp.modal.close(); 60 | }; 61 | return handler; 62 | }; 63 | 64 | var createExporter = function(input, options, onSuccess, onError) { 65 | normalizeOptions(options); 66 | 67 | var api = { 68 | close: function () { 69 | fp.modal.close(); 70 | } 71 | }; 72 | 73 | if (options.debug) { 74 | //return immediately, but still async 75 | setTimeout(function(){ 76 | onSuccess({ 77 | id:1, 78 | url: 'https://www.filepicker.io/api/file/-nBq2onTSemLBxlcBWn1', 79 | filename: 'test.png', 80 | mimetype: 'image/png', 81 | size:58979, 82 | client: 'computer' 83 | }); 84 | }, 1); 85 | return api; 86 | } 87 | 88 | if (fp.cookies.THIRD_PARTY_COOKIES === undefined) { 89 | //if you want a modal, then we need to wait until we know if 3rd party cookies allowed. 90 | var alreadyHandled = false; 91 | fp.cookies.checkThirdParty(function(){ 92 | if (!alreadyHandled) { 93 | createExporter(input, options, onSuccess, onError); 94 | alreadyHandled = true; 95 | } 96 | }); 97 | return api; 98 | } 99 | 100 | var id = fp.util.getId(); 101 | 102 | //Wrapper around on success to make sure we don't also fire on close 103 | var finished = false; 104 | var onSuccessMark = function(fpfile){ 105 | finished = true; 106 | onSuccess(fpfile); 107 | }; 108 | var onErrorMark = function(fperror){ 109 | finished = true; 110 | onError(fperror); 111 | }; 112 | 113 | var onClose = function(){ 114 | if (!finished) { 115 | finished = true; 116 | onError(fp.errors.FPError(131)); 117 | } 118 | }; 119 | 120 | fp.window.open(options.container, fp.urls.constructExportUrl(input, options, id), onClose); 121 | fp.handlers.attach(id, getExportHandler(onSuccessMark, onErrorMark)); 122 | 123 | return api; 124 | }; 125 | 126 | return { 127 | createExporter: createExporter 128 | }; 129 | }); 130 | -------------------------------------------------------------------------------- /src/dialog/modal.js: -------------------------------------------------------------------------------- 1 | //modal.js 2 | 'use strict'; 3 | 4 | filepicker.extend('modal', function(){ 5 | var fp = this, 6 | SHADE_NAME = 'filepicker_shade', 7 | WINDOW_CONTAINER_NAME = 'filepicker_dialog_container'; 8 | 9 | 10 | var originalBody = getHtmlTag(); 11 | if (originalBody) { 12 | var originalOverflow = originalBody.style.overflow; 13 | } 14 | 15 | 16 | /* 17 | * Make the code for the modal 18 | */ 19 | var generateModal = function(modalUrl, onClose){ 20 | appendStyle(); 21 | var shade = createModalShade(onClose), 22 | container = createModalContainer(), 23 | close = createModalClose(onClose), 24 | modal = document.createElement('iframe'); 25 | 26 | modal.name = fp.window.WINDOW_NAME; 27 | modal.id = fp.window.WINDOW_NAME; 28 | modal.style.width = '100%'; 29 | modal.style.height = '100%'; 30 | modal.style.border = 'none'; 31 | modal.style.position = 'relative'; 32 | 33 | //IE... 34 | modal.setAttribute('border',0); 35 | modal.setAttribute('frameborder',0); 36 | modal.setAttribute('frameBorder',0); 37 | modal.setAttribute('marginwidth',0); 38 | modal.setAttribute('marginheight',0); 39 | modal.setAttribute('allow', 'microphone; camera'); 40 | 41 | modal.src = modalUrl; 42 | container.appendChild(modal); 43 | 44 | shade.appendChild(close); 45 | shade.appendChild(container); 46 | document.body.appendChild(shade); 47 | // So user can't scrol page under modal 48 | var body = getHtmlTag(); 49 | if (body) { 50 | body.style.overflow = 'hidden'; 51 | } 52 | return modal; 53 | }; 54 | 55 | var createModalShade = function(onClose) { 56 | var shade = document.createElement('div'); 57 | shade.id = SHADE_NAME; 58 | shade.className = 'fp__overlay'; 59 | shade.onclick = getCloseModal(onClose); 60 | return shade; 61 | }; 62 | 63 | var createModalContainer = function() { 64 | var modalcontainer = document.createElement('div'); 65 | modalcontainer.id = WINDOW_CONTAINER_NAME; 66 | modalcontainer.className = 'fp__container'; 67 | // modalcontainer.style.background = '#ecedec url("https://www.filepicker.io/static/img/spinner.gif") no-repeat 50% 50%'; 68 | return modalcontainer; 69 | }; 70 | 71 | var createModalClose = function(onClose) { 72 | var close = document.createElement('div'); 73 | close.className = 'fp__close'; 74 | var closeAnchor = document.createElement('a'); 75 | closeAnchor.appendChild(document.createTextNode('X')); 76 | close.appendChild(closeAnchor); 77 | closeAnchor.onclick = getCloseModal(onClose); 78 | // Close modal on esc 79 | document.onkeydown = function(evt) { 80 | evt = evt || window.event; 81 | if (evt.keyCode === 27) { 82 | getCloseModal(onClose)(); 83 | } 84 | }; 85 | return close; 86 | }; 87 | 88 | var getCloseModal = function(onClose, force){ 89 | force = !!force; 90 | return function(){ 91 | if (fp.uploading && !force) { 92 | if (!window.confirm('You are currently uploading. If you choose "OK", the window will close and your upload will not finish. Do you want to stop uploading and close the window?')) { 93 | return; 94 | } 95 | } 96 | fp.uploading = false; 97 | document.onkeydown = null; 98 | 99 | setOriginalOverflow(); 100 | 101 | var shade = document.getElementById(SHADE_NAME); 102 | if (shade) { 103 | document.body.removeChild(shade); 104 | } 105 | var container = document.getElementById(WINDOW_CONTAINER_NAME); 106 | if (container) { 107 | document.body.removeChild(container); 108 | } 109 | try{delete window.frames[fp.window.WINDOW_NAME];}catch(e){} 110 | if (onClose){onClose();} 111 | }; 112 | }; 113 | 114 | function hide() { 115 | var shade = document.getElementById(SHADE_NAME); 116 | if (shade) { 117 | shade.hidden = true; 118 | } 119 | var container = document.getElementById(WINDOW_CONTAINER_NAME); 120 | if (container) { 121 | container.hidden = true; 122 | } 123 | setOriginalOverflow(); 124 | } 125 | 126 | function setOriginalOverflow(){ 127 | var body = getHtmlTag(); 128 | if (body) { 129 | body.style.overflow = originalOverflow; 130 | } 131 | } 132 | 133 | function appendStyle(){ 134 | var css = '.fp__overlay {top: 0;right: 0;bottom: 0;left: 0;z-index: 1000;background: rgba(0, 0, 0, 0.8);}' + 135 | 136 | '.fp__close {top: 104px; right: 108px; width: 35px; height: 35px; z-index: 20; cursor: pointer}' + 137 | '@media screen and (max-width: 768px), screen and (max-height: 500px) {.fp__close {top: 15px; right: 12px;}}' + 138 | '.fp__close a {text-indent: -9999px; overflow: hidden; display: block; width: 100%; height: 100%; background: url(https://d1zyh3sbxittvg.cloudfront.net/close.png) 50% 50% no-repeat;}' + 139 | '.fp__close a:hover {background-color: rgba(0,0,0, .02); opacity: .8;}' + 140 | '@media screen and (max-width: 768px), screen and (max-height: 500px) {top: 14px; right: 14px;}' + 141 | '.fp__copy {display: none;}' + 142 | 143 | '.fp__container {-webkit-overflow-scrolling: touch; overflow: hidden; min-height: 300px; top: 100px;right: 100px;bottom: 100px;left: 100px;background: #eee; box-sizing:content-box; -webkit-box-sizing:content-box; -moz-box-sizing:content-box;}' + 144 | 145 | '@media screen and (max-width: 768px), screen and (max-height: 500px) {.fp__copy {bottom: 0; left: 0; right: 0; height: 20px; background: #333;}}' + 146 | '@media screen and (max-width: 768px), screen and (max-height: 500px) {.fp__copy a {margin-left: 5px;}}' + 147 | '@media screen and (max-width: 768px), screen and (max-height: 500px) {.fp__container {top: 0;right: 0;bottom: 0;left: 0;}}' + 148 | 149 | '.fp__overlay, .fp__close, .fp__copy, .fp__container {position: fixed;}'; 150 | 151 | var head = document.head || document.getElementsByTagName('head')[0], 152 | style = document.createElement('style'); 153 | 154 | style.type = 'text/css'; 155 | if (style.styleSheet){ 156 | style.styleSheet.cssText = css; 157 | } else { 158 | style.appendChild(document.createTextNode(css)); 159 | } 160 | 161 | head.appendChild(style); 162 | } 163 | 164 | function getHtmlTag(){ 165 | try { 166 | return document.getElementsByTagName('html')[0]; 167 | } catch(err) { 168 | return null; 169 | } 170 | } 171 | 172 | var closeModal = getCloseModal(function(){}); 173 | 174 | return { 175 | generate: generateModal, 176 | close: closeModal, 177 | hide: hide 178 | }; 179 | }); 180 | -------------------------------------------------------------------------------- /src/dialog/picker.js: -------------------------------------------------------------------------------- 1 | //picker.js 2 | 'use strict'; 3 | 4 | filepicker.extend('picker', function(){ 5 | var fp = this; 6 | 7 | var normalizeOptions = function(options) { 8 | var normalize = function(singular, plural, def){ 9 | if (options[plural]) { 10 | if (!fp.util.isArray(options[plural])) { 11 | options[plural] = [options[plural]]; 12 | } 13 | } else if (options[singular]) { 14 | options[plural] = [options[singular]]; 15 | } else if (def) { 16 | options[plural] = def; 17 | } 18 | }; 19 | 20 | normalize('service', 'services'); 21 | normalize('mimetype', 'mimetypes'); 22 | normalize('extension', 'extensions'); 23 | 24 | if (options.services) { 25 | for (var i = 0; i < options.services.length; i++) { 26 | var service = (''+options.services[i]).replace(' ',''); 27 | 28 | if (fp.services[service] !== undefined) {//we use 0, so can't use ! 29 | service = fp.services[service]; 30 | } 31 | 32 | options.services[i] = service; 33 | } 34 | } 35 | 36 | if (options.mimetypes && options.extensions) { 37 | throw fp.FilepickerException('Error: Cannot pass in both mimetype and extension parameters to the pick function'); 38 | } 39 | if (!options.mimetypes && !options.extensions){ 40 | options.mimetypes = ['*/*']; 41 | } 42 | 43 | if (options.openTo) { 44 | options.openTo = fp.services[options.openTo] || options.openTo; 45 | } 46 | fp.util.setDefault(options, 'container', fp.browser.openInModal() ? 'modal' : 'window'); 47 | }; 48 | 49 | var getPickHandler = function(onSuccess, onError, onProgress) { 50 | var handler = function(data) { 51 | if (filterDataType(data, onProgress)) { 52 | return; 53 | } 54 | 55 | fp.uploading = false; 56 | 57 | if (data.error) { 58 | fp.util.console.error(data.error); 59 | if (data.error.code) { 60 | onError(fp.errors.FPError(data.error.code)); 61 | } else { 62 | onError(fp.errors.FPError(102)); 63 | //Try to close a modal if it exists. 64 | fp.modal.close(); 65 | } 66 | } else { 67 | var fpfile = fpfileFromPayload(data.payload); 68 | //TODO: change payload to not require parsing 69 | onSuccess(fpfile); 70 | //Try to close a modal if it exists. 71 | fp.modal.close(); 72 | } 73 | }; 74 | return handler; 75 | }; 76 | 77 | var getPickFolderHandler = function(onSuccess, onError, onProgress) { 78 | var handler = function(data) { 79 | if (filterDataType(data, onProgress)) { 80 | return; 81 | } 82 | fp.uploading = false; 83 | 84 | if (data.error) { 85 | fp.util.console.error(data.error); 86 | onError(fp.errors.FPError(102)); 87 | } else { 88 | data.payload.data.url = data.payload.url; 89 | onSuccess(data.payload.data); 90 | } 91 | 92 | //Try to close a modal if it exists. 93 | fp.modal.close(); 94 | }; 95 | return handler; 96 | }; 97 | 98 | var getUploadingHandler = function(onUploading) { 99 | onUploading = onUploading || function(){}; 100 | var handler = function(data) { 101 | if (data.type !== 'uploading') { 102 | return; 103 | } 104 | fp.uploading = !!data.payload; 105 | onUploading(fp.uploading); 106 | }; 107 | return handler; 108 | }; 109 | 110 | var addIfExist = function(data, fpfile, key) { 111 | if (data[key]) { 112 | fpfile[key] = data[key]; 113 | } 114 | }; 115 | 116 | var fpfileFromPayload = function(payload) { 117 | var fpfile = {}; 118 | var url = payload.url; 119 | if (url && url.url) { 120 | url = url.url; 121 | } 122 | fpfile.url = url; 123 | var data = payload.url.data || payload.data; 124 | fpfile.filename = data.filename; 125 | fpfile.mimetype = data.type; 126 | fpfile.size = data.size; 127 | if (data.cropped !== undefined) { 128 | fpfile.cropped = data.cropped; 129 | } 130 | if (data.rotated !== undefined) { 131 | fpfile.rotated = data.rotated; 132 | } 133 | if (data.converted !== undefined) { 134 | fpfile.converted = data.converted; 135 | } 136 | 137 | addIfExist(data, fpfile, 'id'); 138 | addIfExist(data, fpfile, 'key'); 139 | addIfExist(data, fpfile, 'container'); 140 | addIfExist(data, fpfile, 'path'); 141 | addIfExist(data, fpfile, 'client'); 142 | addIfExist(data, fpfile, 'cloudinary_resource_type'); 143 | 144 | //TODO: get writeable 145 | fpfile.isWriteable = true; 146 | 147 | return fpfile; 148 | }; 149 | 150 | var getPickMultipleHandler = function(onSuccess, onError, onProgress) { 151 | var handler = function(data) { 152 | if (filterDataType(data, onProgress)) { 153 | return; 154 | } 155 | fp.uploading = false; 156 | 157 | if (data.error) { 158 | fp.util.console.error(data.error); 159 | onError(fp.errors.FPError(102)); 160 | } else { 161 | var fpfiles = []; 162 | 163 | 164 | //TODO: change payload to not require parsing 165 | if (!fp.util.isArray(data.payload)) { 166 | data.payload = [data.payload]; 167 | } 168 | for (var i = 0; i < data.payload.length; i++) { 169 | var fpfile = fpfileFromPayload(data.payload[i]); 170 | fpfiles.push(fpfile); 171 | } 172 | onSuccess(fpfiles); 173 | } 174 | 175 | //Try to close a modal if it exists. 176 | fp.modal.close(); 177 | }; 178 | return handler; 179 | }; 180 | 181 | var createPicker = function(options, onSuccess, onError, multiple, folder, onProgress, convertFile) { 182 | normalizeOptions(options); 183 | 184 | var api = { 185 | close: function () { 186 | fp.modal.close(); 187 | } 188 | }; 189 | 190 | if (options.debug) { 191 | 192 | var dumy_data = { 193 | id:1, 194 | url: 'https://www.filepicker.io/api/file/-nBq2onTSemLBxlcBWn1', 195 | filename:'test.png', 196 | mimetype: 'image/png', 197 | size:58979, 198 | client:'computer' 199 | }; 200 | 201 | var dumy_callback; 202 | 203 | if (multiple || options.storeLocation) { 204 | dumy_callback = [dumy_data, dumy_data, dumy_data]; 205 | } else { 206 | dumy_callback = dumy_data; 207 | } 208 | 209 | //return immediately, but still async 210 | setTimeout(function(){ 211 | onSuccess(dumy_callback); 212 | }, 1); 213 | return api; 214 | } 215 | 216 | if (fp.cookies.THIRD_PARTY_COOKIES === undefined) { 217 | //if you want a modal, then we need to wait until we know if 3rd party cookies allowed. 218 | var alreadyHandled = false; 219 | fp.cookies.checkThirdParty(function(){ 220 | if (!alreadyHandled) { 221 | createPicker(options, onSuccess, onError, !!multiple, folder, onProgress); 222 | alreadyHandled = true; 223 | } 224 | }); 225 | return api; 226 | } 227 | 228 | var id = fp.util.getId(); 229 | 230 | //Wrapper around on success to make sure we don't also fire on close 231 | var finished = false; 232 | var onSuccessMark = function(fpfile){ 233 | if (options.container === 'window') { 234 | window.onbeforeunload = null; 235 | } 236 | finished = true; 237 | onSuccess(fpfile); 238 | }; 239 | var onErrorMark = function(fperror){ 240 | finished = true; 241 | onError(fperror); 242 | }; 243 | 244 | var onClose = function(){ 245 | if (!finished) { 246 | finished = true; 247 | onError(fp.errors.FPError(101)); 248 | } 249 | }; 250 | 251 | var url; 252 | var handler; 253 | if (convertFile) { 254 | url = fp.urls.constructConvertUrl(options, id); 255 | handler = getPickHandler(onSuccessMark, onErrorMark, onProgress); 256 | } else if (multiple) { 257 | url = fp.urls.constructPickUrl(options, id, true); 258 | handler = getPickMultipleHandler(onSuccessMark, onErrorMark, onProgress); 259 | } else if (folder) { 260 | url = fp.urls.constructPickFolderUrl(options, id); 261 | handler = getPickFolderHandler(onSuccessMark, onErrorMark, onProgress); 262 | } else { 263 | url = fp.urls.constructPickUrl(options, id, false); 264 | handler = getPickHandler(onSuccessMark, onErrorMark, onProgress); 265 | } 266 | 267 | fp.window.open(options.container, url, onClose); 268 | fp.handlers.attach(id, handler); 269 | 270 | var key = id+'-upload'; 271 | fp.handlers.attach(key, getUploadingHandler(function(){ 272 | fp.handlers.detach(key); 273 | })); 274 | 275 | return api; 276 | }; 277 | 278 | function filterDataType(data, onProgress){ 279 | if (data.type === 'filepickerProgress'){ 280 | fp.uploading = true; 281 | if (onProgress) { 282 | onProgress(data.payload.data); 283 | } 284 | } else if (data.type === 'notUploading') { 285 | fp.uploading = false; 286 | } else if (data.type === 'closeModal') { 287 | fp.modal.close(); 288 | } else if (data.type === 'hideModal') { 289 | fp.modal.hide(); 290 | } else if (data.type === 'filepickerUrl' || data.type === 'serverHttpError') { 291 | return false; 292 | } 293 | return true; 294 | } 295 | 296 | return { 297 | createPicker: createPicker 298 | }; 299 | }); 300 | -------------------------------------------------------------------------------- /src/dialog/window.js: -------------------------------------------------------------------------------- 1 | //windows.js 2 | 'use strict'; 3 | 4 | filepicker.extend('window', function(){ 5 | var fp = this; 6 | 7 | var DIALOG_TYPES = { 8 | OPEN:'/dialog/open/', 9 | SAVEAS:'/dialog/save/' 10 | }; 11 | 12 | var WINDOW_NAME = 'filepicker_dialog'; 13 | var WINDOW_PROPERTIES = 'left=100,top=100,height=600,width=800,menubar=no,toolbar=no,location=no,personalbar=no,status=no,resizable=yes,scrollbars=yes,dependent=yes,dialog=yes'; 14 | var CLOSE_CHECK_INTERVAL = 100; 15 | 16 | var openWindow = function(container, src, onClose) { 17 | onClose = onClose || function(){}; 18 | if (!container && fp.browser.openInModal()){ 19 | container = 'modal'; 20 | } else if (!container) { 21 | container = 'window'; 22 | } 23 | 24 | if (container === 'window') { 25 | var name = WINDOW_NAME + fp.util.getId(); 26 | window.onbeforeunload = function confirmExit() { 27 | return 'Filepicker upload does not complete.'; 28 | }; 29 | var win = window.open(src, name, WINDOW_PROPERTIES); 30 | if (!win) { 31 | window.onbeforeunload = null; 32 | window.alert('Please disable your popup blocker to upload files.'); 33 | } 34 | 35 | var closeCheck = window.setInterval(function(){ 36 | if (!win || win.closed) { 37 | window.onbeforeunload = null; 38 | window.clearInterval(closeCheck); 39 | onClose(); 40 | } 41 | }, CLOSE_CHECK_INTERVAL); 42 | } else if (container === 'modal') { 43 | fp.modal.generate(src, onClose); 44 | } else { 45 | var container_iframe = document.getElementById(container); 46 | if (!container_iframe) { 47 | throw new fp.FilepickerException('Container "'+container+'" not found. This should either be set to "window","modal", or the ID of an iframe that is currently in the document.'); 48 | } 49 | container_iframe.src = src; 50 | } 51 | }; 52 | 53 | return { 54 | open: openWindow, 55 | WINDOW_NAME: WINDOW_NAME 56 | }; 57 | }); 58 | -------------------------------------------------------------------------------- /src/finalize.js: -------------------------------------------------------------------------------- 1 | // finalize.js 2 | 'use strict'; 3 | 4 | (function(){ 5 | //setup functions 6 | filepicker.internal(function(){ 7 | var fp = this; 8 | fp.util.addOnLoad(fp.cookies.checkThirdParty); 9 | fp.util.addOnLoad(fp.widgets.buildWidgets); 10 | fp.util.addOnLoad(fp.responsiveImages.activate); 11 | }); 12 | 13 | //Now we wipe our superpowers 14 | delete filepicker.internal; 15 | delete filepicker.extend; 16 | 17 | //process the queue 18 | var queue = filepicker._queue || []; 19 | var args; 20 | var len = queue.length; 21 | if (len) { 22 | for (var i = 0; i < len; i++) { 23 | args = queue[i]; 24 | filepicker[args[0]].apply(filepicker, args[1]); 25 | } 26 | } 27 | 28 | //remove the queue 29 | if (filepicker._queue) { 30 | delete filepicker._queue; 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /src/library/conversions.js: -------------------------------------------------------------------------------- 1 | //conversions.js 2 | 'use strict'; 3 | 4 | filepicker.extend('conversions', function(){ 5 | var fp = this; 6 | 7 | var valid_parameters = { 8 | align:'string', 9 | blurAmount:'number', 10 | crop:'string or array', 11 | crop_first:'boolean', 12 | compress:'boolean', 13 | exif:'string or boolean', 14 | filter:'string', 15 | fit:'string', 16 | format:'string', 17 | height:'number', 18 | policy:'string', 19 | quality:'number', 20 | page:'number', 21 | rotate:'string or number', 22 | secure:'boolean', 23 | sharpenAmount:'number', 24 | signature:'string', 25 | storeAccess:'string', 26 | storeContainer:'string', 27 | storeRegion:'string', 28 | storeLocation:'string', 29 | storePath:'string', 30 | text:'string', 31 | cloudinaryUploadPreset: 'string', 32 | text_align:'string', 33 | text_color:'string', 34 | text_font:'string', 35 | text_padding:'number', 36 | text_size:'number', 37 | watermark:'string', 38 | watermark_position:'string', 39 | watermark_size:'number', 40 | width:'number' 41 | }; 42 | 43 | // Only for params that differ 44 | var rest_map = { 45 | w: 'width', 46 | h: 'height' 47 | }; 48 | 49 | var mapRestParams = function(options){ 50 | var obj = {}; 51 | for (var key in options) { 52 | obj[rest_map[key] || key] = options[key]; 53 | // need to convert string to number 54 | if (valid_parameters[rest_map[key] || key] === 'number') { 55 | obj[rest_map[key] || key] = Number(options[key]); 56 | } 57 | } 58 | return obj; 59 | }; 60 | 61 | var checkParameters = function(options) { 62 | var found; 63 | for (var key in options) { 64 | found = false; 65 | for (var test in valid_parameters) { 66 | if (key === test) { 67 | found = true; 68 | if (valid_parameters[test].indexOf(fp.util.typeOf(options[key])) === -1) { 69 | throw new fp.FilepickerException('Conversion parameter '+key+' is not the right type: '+options[key]+'. Should be a '+valid_parameters[test]); 70 | } 71 | } 72 | } 73 | if (!found) { 74 | throw new fp.FilepickerException('Conversion parameter '+key+' is not a valid parameter.'); 75 | } 76 | } 77 | }; 78 | 79 | var convert = function(fp_url, options, onSuccess, onError, onProgress){ 80 | checkParameters(options); 81 | 82 | if (options.crop && fp.util.isArray(options.crop)) { 83 | options.crop = options.crop.join(','); 84 | } 85 | fp.ajax.post(fp_url+'/convert', { 86 | data: options, 87 | json: true, 88 | success: function(fpfile) { 89 | onSuccess(fp.util.standardizeFPFile(fpfile)); 90 | }, 91 | error: function(msg, status, xhr) { 92 | if (msg === 'not_found') { 93 | onError(new fp.errors.FPError(141)); 94 | } else if (msg === 'bad_params') { 95 | onError(new fp.errors.FPError(142)); 96 | } else if (msg === 'not_authorized') { 97 | onError(new fp.errors.FPError(403)); 98 | } else { 99 | onError(new fp.errors.FPError(143)); 100 | } 101 | }, 102 | progress: onProgress 103 | }); 104 | }; 105 | 106 | return { 107 | convert: convert, 108 | mapRestParams: mapRestParams 109 | }; 110 | }); 111 | -------------------------------------------------------------------------------- /src/library/errors.js: -------------------------------------------------------------------------------- 1 | //errors.js 2 | 'use strict'; 3 | 4 | filepicker.extend('errors', function(){ 5 | var fp = this; 6 | 7 | var FPError = function(code) { 8 | if (this === window) { return new FPError(code);} 9 | 10 | this.code = code; 11 | if (filepicker.debug) { 12 | var info = filepicker.error_map[this.code]; 13 | this.message = info.message; 14 | this.moreInfo = info.moreInfo; 15 | this.toString = function(){ 16 | return 'FPError '+this.code+': '+this.message+'. For help, see '+this.moreInfo; 17 | }; 18 | } else { 19 | this.toString = function(){return 'FPError '+this.code+'. Include filepicker_debug.js for more info';}; 20 | } 21 | return this; 22 | }; 23 | //Telling router how to call us 24 | FPError.isClass = true; 25 | 26 | //The defualt error handler 27 | var handleError = function(fperror) { 28 | if (filepicker.debug) { 29 | fp.util.console.error(fperror.toString()); 30 | } 31 | }; 32 | 33 | return { 34 | FPError: FPError, 35 | handleError: handleError 36 | }; 37 | }, true); 38 | -------------------------------------------------------------------------------- /src/library/mimetypes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | filepicker.extend('mimetypes', function(){ 4 | var fp = this; 5 | 6 | /*We have a mimetype map to make up for the fact that browsers 7 | * don't yet recognize all the mimetypes we need to support*/ 8 | var mimetype_extension_map = { 9 | '.stl':'application/sla', 10 | '.hbs':'text/html', 11 | '.pdf':'application/pdf', 12 | '.jpg':'image/jpeg', 13 | '.jpeg':'image/jpeg', 14 | '.jpe':'image/jpeg', 15 | '.imp':'application/x-impressionist', 16 | '.vob': 'video/dvd' 17 | }; 18 | 19 | var mimetype_bad_array = [ 'application/octet-stream', 20 | 'application/download', 21 | 'application/force-download', 22 | 'octet/stream', 23 | 'application/unknown', 24 | 'application/x-download', 25 | 'application/x-msdownload', 26 | 'application/x-secure-download']; 27 | 28 | var getMimetype = function(file) { 29 | if (file.type) { 30 | var type = file.type; 31 | type = type.toLowerCase(); 32 | var bad_type = false; 33 | for (var n = 0; n < mimetype_bad_array.length; n++){ 34 | bad_type = bad_type || type === mimetype_bad_array[n]; 35 | } 36 | if (!bad_type){ 37 | return file.type; 38 | } 39 | } 40 | var filename = file.name || file.fileName; 41 | var extension = filename.match(/\.\w*$/); 42 | if (extension) { 43 | return mimetype_extension_map[extension[0].toLowerCase()] || ''; 44 | } else { 45 | if (file.type){ 46 | //Might be a bad type, but better then nothing 47 | return file.type; 48 | } else { 49 | return ''; 50 | } 51 | } 52 | }; 53 | 54 | var matchesMimetype = function(test, against) { 55 | if (!test) {return against === '*/*';} 56 | 57 | test = fp.util.trim(test).toLowerCase(); 58 | against = fp.util.trim(against).toLowerCase(); 59 | 60 | // Firefox has some oddities as it allows the user to overwrite mimetypes. 61 | // These are some of the silly mimetypes that have no meaning at all 62 | for (var n = 0; n < mimetype_bad_array.length; n++){ 63 | if (test === mimetype_bad_array[n]){ 64 | return true; 65 | } 66 | } 67 | 68 | var test_parts = test.split('/'), 69 | against_parts = against.split('/'); 70 | 71 | //comparing types 72 | if (against_parts[0] === '*') {return true;} 73 | if (against_parts[0] !== test_parts[0]) {return false;} 74 | //comparing subtypes 75 | if (against_parts[1] === '*') {return true;} 76 | return against_parts[1] === test_parts[1]; 77 | }; 78 | 79 | return { 80 | getMimetype: getMimetype, 81 | matchesMimetype: matchesMimetype 82 | }; 83 | }); 84 | -------------------------------------------------------------------------------- /src/library/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | filepicker.extend('services', function(){ 4 | /** 5 | * @ServiceEnum: the services we support 6 | * Don't use 0 as it might be confused with false 7 | */ 8 | return { 9 | COMPUTER: 1, 10 | DROPBOX: 2, 11 | FACEBOOK: 3, 12 | GITHUB: 4, 13 | GMAIL: 5, 14 | IMAGE_SEARCH: 6, 15 | URL: 7, 16 | WEBCAM: 8, 17 | GOOGLE_DRIVE: 9, 18 | SEND_EMAIL: 10, 19 | INSTAGRAM: 11, 20 | FLICKR: 12, 21 | VIDEO: 13, 22 | EVERNOTE: 14, 23 | PICASA: 15, 24 | WEBDAV: 16, 25 | FTP: 17, 26 | ALFRESCO: 18, 27 | BOX: 19, 28 | SKYDRIVE: 20, 29 | GDRIVE: 21, 30 | CUSTOMSOURCE: 22, 31 | CLOUDDRIVE: 23, 32 | GENERIC: 24, 33 | CONVERT: 25, 34 | AUDIO: 26 35 | }; 36 | }, true); 37 | -------------------------------------------------------------------------------- /src/library/urls.js: -------------------------------------------------------------------------------- 1 | //urls.js 2 | 'use strict'; 3 | 4 | filepicker.extend('urls', function(){ 5 | var fp = this; 6 | 7 | var base = 'https://www.filepicker.io'; 8 | if (window.filepicker.hostname) { 9 | base = window.filepicker.hostname; 10 | } 11 | var dialog_base = base.replace('www', 'dialog'), 12 | pick_url = dialog_base + '/dialog/open/', 13 | export_url = dialog_base + '/dialog/save/', 14 | convert_url = dialog_base + '/dialog/process/', 15 | pick_folder_url = dialog_base + '/dialog/folder/', 16 | store_url = base + '/api/store/'; 17 | 18 | var allowedConversions = ['crop', 'rotate', 'filter']; 19 | 20 | var constructPickUrl = function(options, id, multiple) { 21 | return pick_url+ constructModalQuery(options, id)+ 22 | (multiple ? '&multi='+!!multiple : '')+ 23 | (options.mimetypes !== undefined ? '&m='+options.mimetypes.join(',') : '')+ 24 | (options.extensions !== undefined ? '&ext='+options.extensions.join(',') : '')+ 25 | (options.maxSize ? '&maxSize='+options.maxSize: '')+ 26 | (options.customSourceContainer ? '&customSourceContainer='+options.customSourceContainer: '')+ 27 | (options.customSourcePath ? '&customSourcePath='+options.customSourcePath: '')+ 28 | (options.maxFiles ? '&maxFiles='+options.maxFiles: '')+ 29 | (options.folders !== undefined ? '&folders='+options.folders : '')+ 30 | (options.storeLocation ? '&storeLocation='+options.storeLocation : '')+ 31 | (options.storePath ? '&storePath='+options.storePath : '')+ 32 | (options.storeContainer ? '&storeContainer='+options.storeContainer : '')+ 33 | (options.storeRegion ? '&storeRegion='+options.storeRegion : '')+ 34 | (options.cloudinaryUploadPreset ? '&cloudinaryUploadPreset='+options.cloudinaryUploadPreset: '')+ 35 | (options.storeAccess ? '&storeAccess='+options.storeAccess : '')+ 36 | ((options.webcam && options.webcam.webcamDim) ? '&wdim='+options.webcam.webcamDim.join(',') : '')+ 37 | (options.webcamDim ? '&wdim='+options.webcamDim.join(',') : '')+ 38 | ((options.webcam && options.webcam.videoRes) ? '&videoRes='+options.webcam.videoRes: '')+ 39 | ((options.webcam && options.webcam.videoLen) ? '&videoLen='+options.webcam.videoLen: '')+ 40 | ((options.webcam && options.webcam.audioLen) ? '&audioLen='+options.webcam.audioLen: '')+ 41 | constructConversionsQuery(options.conversions); 42 | }; 43 | 44 | var constructConvertUrl = function(options, id) { 45 | var url = options.convertUrl; 46 | if (url.indexOf('&') >= 0 || url.indexOf('?') >= 0) { 47 | url = encodeURIComponent(url); 48 | } 49 | return convert_url+ constructModalQuery(options, id)+ 50 | '&curl='+url+ 51 | constructConversionsQuery(options.conversions); 52 | }; 53 | 54 | 55 | 56 | var constructPickFolderUrl = function(options, id) { 57 | return pick_folder_url + constructModalQuery(options, id); 58 | }; 59 | 60 | var constructExportUrl = function(url, options, id) { 61 | if (url.indexOf('&') >= 0 || url.indexOf('?') >= 0) { 62 | url = encodeURIComponent(url); 63 | } 64 | return export_url+ constructModalQuery(options, id)+ 65 | '&url='+url+ 66 | (options.mimetype !== undefined ? '&m='+options.mimetype : '')+ 67 | (options.extension !== undefined ? '&ext='+options.extension : '')+ 68 | (options.suggestedFilename ? '&defaultSaveasName='+options.suggestedFilename : ''); 69 | }; 70 | 71 | var constructStoreUrl = function(options) { 72 | return store_url + options.location + 73 | '?key='+fp.apikey+ 74 | (options.base64decode ? '&base64decode=true' : '')+ 75 | (options.mimetype ? '&mimetype='+options.mimetype : '')+ 76 | (options.filename ? '&filename='+encodeURIComponent(options.filename) : '')+ 77 | (options.path ? '&path='+options.path : '')+ 78 | (options.container ? '&container='+options.container : '')+ 79 | (options.access ? '&access='+options.access : '')+ 80 | constructSecurityQuery(options)+ 81 | '&plugin='+getPlugin(); 82 | }; 83 | 84 | var constructWriteUrl = function(fp_url, options) { 85 | //to make sure that fp_url already has a ? 86 | return fp_url + 87 | '?nonce=fp'+ 88 | (!!options.base64decode ? '&base64decode=true' : '')+ 89 | (options.mimetype ? '&mimetype='+options.mimetype : '')+ 90 | constructSecurityQuery(options)+ 91 | '&plugin='+getPlugin(); 92 | }; 93 | 94 | var constructHostCommFallback = function(){ 95 | var parts = fp.util.parseUrl(window.location.href); 96 | return parts.origin+'/404'; 97 | }; 98 | 99 | function constructModalQuery(options, id) { 100 | return '?key='+fp.apikey+ 101 | '&id='+id+ 102 | '&referrer='+window.location.hostname+ 103 | '&iframe='+(options.container !== 'window')+ 104 | '&version='+fp.API_VERSION+ 105 | (options.services ? '&s='+options.services.join(',') : '')+ 106 | (options.container !== undefined ? '&container='+ options.container : 'modal')+ 107 | (options.openTo ? '&loc='+options.openTo : '')+ 108 | '&language='+(options.language || fp.browser.getLanguage())+ 109 | (options.mobile !== undefined ? '&mobile='+options.mobile : '')+ 110 | // v2 111 | (options.backgroundUpload !== undefined ? '&bu='+options.backgroundUpload : '')+ 112 | (options.cropRatio ? '&cratio='+options.cropRatio : '')+ 113 | (options.cropDim ? '&cdim='+options.cropDim.join(',') : '')+ 114 | (options.cropMax ? '&cmax='+options.cropMax.join(',') : '')+ 115 | (options.cropMin ? '&cmin='+options.cropMin.join(',') : '')+ 116 | (options.cropForce !== undefined ? '&cforce='+options.cropForce : '')+ 117 | (options.hide !== undefined ? '&hide='+options.hide : '')+ 118 | (options.customCss ? '&css='+encodeURIComponent(options.customCss) : '')+ 119 | (options.customText ? '&text='+encodeURIComponent(options.customText) : '')+ 120 | (options.imageMin ? '&imin='+options.imageMin.join(',') : '')+ 121 | (options.imageMax ? '&imax='+options.imageMax.join(',') : '')+ 122 | (options.imageDim ? '&idim='+options.imageDim.join(',') : '')+ 123 | (options.imageQuality ? '&iq='+options.imageQuality : '')+ 124 | (options.noFileReader ? '&nfl='+options.noFileReader : '')+ 125 | (fp.util.isCanvasSupported() ? '' : '&canvas=false')+ 126 | (options.redirectUrl ? '&redirect_url='+options.redirectUrl : '')+ 127 | /* 128 | prevent from showing close button twice 129 | */ 130 | ((options.showClose && options.container !== 'modal') ? '&showClose='+options.showClose : '')+ 131 | constructSecurityQuery(options)+ 132 | '&plugin='+getPlugin(); 133 | } 134 | 135 | function constructSecurityQuery(options) { 136 | return (options.signature ? '&signature='+options.signature : '')+ 137 | (options.policy ? '&policy='+options.policy : ''); 138 | } 139 | 140 | function getPlugin(){ 141 | return filepicker.plugin || 'js_lib'; 142 | } 143 | 144 | 145 | function constructConversionsQuery(conversions){ 146 | conversions = conversions || []; 147 | var allowed = [], 148 | i , 149 | j; 150 | 151 | /* 152 | Use for in loop. 153 | Array.filter && Array.indexOf not supported in IE8 154 | */ 155 | 156 | for (i in conversions) { 157 | for (j in allowedConversions) { 158 | if (conversions[i] === allowedConversions[j] && conversions.hasOwnProperty(i)) { 159 | allowed.push(conversions[i]); 160 | } 161 | } 162 | } 163 | 164 | /* 165 | Only crop by default 166 | */ 167 | if (!allowed.length) { 168 | allowed.push('crop'); 169 | } 170 | return '&co='+allowed.join(','); 171 | } 172 | 173 | 174 | return { 175 | BASE: base, 176 | DIALOG_BASE: dialog_base, 177 | API_COMM: base + '/dialog/comm_iframe/', 178 | COMM: dialog_base + '/dialog/comm_iframe/', 179 | FP_COMM_FALLBACK: dialog_base + '/dialog/comm_hash_iframe/', 180 | STORE: store_url, 181 | PICK: pick_url, 182 | EXPORT: export_url, 183 | LOGOUT: base + '/api/clients/unauth', 184 | constructPickUrl: constructPickUrl, 185 | constructConvertUrl: constructConvertUrl, 186 | constructPickFolderUrl: constructPickFolderUrl, 187 | constructExportUrl: constructExportUrl, 188 | constructWriteUrl: constructWriteUrl, 189 | constructStoreUrl: constructStoreUrl, 190 | constructHostCommFallback: constructHostCommFallback, 191 | getPlugin: getPlugin 192 | }; 193 | }); 194 | -------------------------------------------------------------------------------- /src/server/ajax.js: -------------------------------------------------------------------------------- 1 | //ajax.js 2 | 'use strict'; 3 | /*jshint eqeqeq:false */ 4 | 5 | filepicker.extend('ajax', function(){ 6 | var fp = this; 7 | 8 | var get_request = function(url, options) { 9 | options.method = 'GET'; 10 | make_request(url, options); 11 | }; 12 | 13 | var post_request = function(url, options) { 14 | options.method = 'POST'; 15 | url += (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheBust='+fp.util.getId(); 16 | make_request(url, options); 17 | }; 18 | 19 | var toQueryString = function(object, base) { 20 | var queryString = []; 21 | for (var key in object) { 22 | var value = object[key]; 23 | if (base){ 24 | key = base + '. + key + '; 25 | } 26 | var result; 27 | switch (fp.util.typeOf(value)){ 28 | case 'object': result = toQueryString(value, key); break; 29 | case 'array': 30 | var qs = {}; 31 | for (var i = 0; i < value.length; i++) { 32 | qs[i] = value[i]; 33 | } 34 | result = toQueryString(qs, key); 35 | break; 36 | default: result = key + '=' + encodeURIComponent(value); break; 37 | } 38 | if (value !== null){ 39 | queryString.push(result); 40 | } 41 | } 42 | 43 | return queryString.join('&'); 44 | }; 45 | 46 | var getXhr = function() { 47 | try{ 48 | // Modern browsers 49 | return new window.XMLHttpRequest(); 50 | } catch (e){ 51 | // IE 52 | try{ 53 | return new window.ActiveXObject('Msxml2.XMLHTTP'); 54 | } catch (e) { 55 | try{ 56 | return new window.ActiveXObject('Microsoft.XMLHTTP'); 57 | } catch (e){ 58 | // Something went wrong 59 | return null; 60 | } 61 | } 62 | } 63 | }; 64 | 65 | var make_request = function(url, options) { 66 | //setting defaults 67 | url = url || ''; 68 | var method = options.method ? options.method.toUpperCase() : 'POST'; 69 | var success = options.success || function(){}; 70 | var error = options.error || function(){}; 71 | var async = options.async === undefined ? true : options.async; 72 | var data = options.data || null; 73 | var processData = options.processData === undefined ? true : options.processData; 74 | var headers = options.headers || {}; 75 | 76 | var urlParts = fp.util.parseUrl(url); 77 | var origin = window.location.protocol + '//' + window.location.host; 78 | var crossdomain = origin !== urlParts.origin; 79 | var finished = false; 80 | url += (url.indexOf('?') >= 0 ? '&' : '?') + 'plugin=' + fp.urls.getPlugin(); 81 | //var crossdomain = window.location 82 | if (data && processData) { 83 | data = toQueryString(options.data); 84 | } 85 | 86 | //creating the request 87 | var xhr; 88 | if (options.xhr) { 89 | xhr = options.xhr; 90 | } else { 91 | xhr = getXhr(); 92 | if (!xhr) { 93 | options.error('Ajax not allowed'); 94 | return xhr; 95 | } 96 | } 97 | 98 | if (crossdomain && window.XDomainRequest && !('withCredentials' in xhr)) { 99 | return new XDomainAjax(url, options); 100 | } 101 | 102 | if (options.progress && xhr.upload) { 103 | xhr.upload.addEventListener('progress', function(e){ 104 | if (e.lengthComputable) { 105 | options.progress(Math.round((e.loaded * 95) / e.total)); 106 | } 107 | }, false); 108 | } 109 | 110 | //Handlers 111 | var onStateChange = function(){ 112 | if(xhr.readyState == 4 && !finished){ 113 | if (options.progress) {options.progress(100);} 114 | if (xhr.status >= 200 && xhr.status < 300) { 115 | //TODO - look into using xhr.responseType and xhr.response for binary blobs. Not sure what to return 116 | var resp = xhr.responseText; 117 | if (options.json) { 118 | try { 119 | resp = fp.json.decode(resp); 120 | } catch (e) { 121 | onerror.call(xhr, 'Invalid json: '+resp); 122 | return; 123 | } 124 | } 125 | success(resp, xhr.status, xhr); 126 | finished = true; 127 | } else { 128 | onerror.call(xhr, xhr.responseText); 129 | finished = true; 130 | } 131 | } 132 | }; 133 | xhr.onreadystatechange = onStateChange; 134 | 135 | var onerror = function(err) { 136 | //already handled 137 | if (finished) {return;} 138 | 139 | if (options.progress) {options.progress(100);} 140 | 141 | finished = true; 142 | if (this.status == 400) { 143 | error('bad_params', this.status, this); 144 | return; 145 | } else if (this.status == 403) { 146 | error('not_authorized', this.status, this); 147 | return; 148 | } else if (this.status == 404) { 149 | error('not_found', this.status, this); 150 | return; 151 | } 152 | if (crossdomain) { 153 | if (this.readyState == 4 && this.status === 0) { 154 | error('CORS_not_allowed', this.status, this); 155 | return; 156 | } else { 157 | error('CORS_error', this.status, this); 158 | return; 159 | } 160 | } 161 | 162 | //if we're here, we don't know what happened 163 | error(err, this.status, this); 164 | }; 165 | 166 | xhr.onerror = onerror; 167 | 168 | //Executing the request 169 | if (data && method == 'GET') { 170 | url += (url.indexOf('?') !== -1 ? '&' : '?') + data; 171 | data = null; 172 | } 173 | 174 | if (options.withCredentials) { 175 | xhr.withCredentials = true; 176 | } 177 | 178 | xhr.open(method, url, async); 179 | if (options.json) { 180 | xhr.setRequestHeader('Accept', 'application/json, text/javascript'); 181 | } else { 182 | xhr.setRequestHeader('Accept', 'text/javascript, text/html, application/xml, text/xml, */*'); 183 | } 184 | 185 | var contentType = headers['Content-Type'] || headers['content-type']; 186 | if (data && processData && (method == 'POST' || method == 'PUT') && contentType === undefined) { 187 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8'); 188 | } 189 | 190 | if (headers) { 191 | for (var key in headers) { 192 | xhr.setRequestHeader(key, headers[key]); 193 | } 194 | } 195 | 196 | xhr.send(data); 197 | 198 | return xhr; 199 | }; 200 | 201 | //Ajax using XDomainRequest - different enough from normal xhr that we do it separately 202 | var XDomainAjax = function(url, options) { 203 | if (!window.XDomainRequest) {return null;} 204 | 205 | var method = options.method ? options.method.toUpperCase() : 'POST'; 206 | var success = options.success || function(){}; 207 | var error = options.error || function(){}; 208 | var data = options.data || {}; 209 | 210 | //protocol of the url must match our protocol 211 | if (window.location.protocol == 'http:') { 212 | url = url.replace('https:','http:'); 213 | } else if (window.location.protocol == 'https:') { 214 | url = url.replace('http:','https:'); 215 | } 216 | 217 | /* 218 | if (options.headers['Content-Type']) { 219 | //custom content type, so we smush the data into a {data: data} 220 | data = {'data': data}; 221 | data.mimetype = options.headers['Content-Type']; 222 | } 223 | */ 224 | 225 | if (options.async) { 226 | throw new fp.FilepickerException('Asyncronous Cross-domain requests are not supported'); 227 | } 228 | 229 | //Only supports get and post 230 | if (method !== 'GET' && method !== 'POST') { 231 | data._method = method; 232 | method = 'POST'; 233 | } 234 | 235 | if (options.processData !== false) { 236 | data = data ? toQueryString(data) : null; 237 | } 238 | 239 | //Executing the request 240 | if (data && method == 'GET') { 241 | url += (url.indexOf('?') >= 0 ? '&' : '?') + data; 242 | data = null; 243 | } 244 | 245 | //so we know it's an xdr and can handle appropriately 246 | url += (url.indexOf('?') >= 0 ? '&' : '?') + '_xdr=true&_cacheBust='+fp.util.getId(); 247 | 248 | var xdr = new window.XDomainRequest(); 249 | xdr.onload = function() { 250 | var resp = xdr.responseText; 251 | if (options.progress) {options.progress(100);} 252 | if (options.json) { 253 | try { 254 | resp = fp.json.decode(resp); 255 | } catch (e) { 256 | error('Invalid json: ' + resp, 200, xdr); 257 | return; 258 | } 259 | } 260 | //assume status == 200, since we can't get it for real 261 | success(resp, 200, xdr); 262 | }; 263 | xdr.onerror = function() { 264 | if (options.progress) {options.progress(100);} 265 | error(xdr.responseText || 'CORS_error', this.status || 500, this); 266 | }; 267 | //Must have an onprogress or ie will abort 268 | xdr.onprogress = function(){}; 269 | xdr.ontimeout = function(){}; 270 | xdr.timeout = 30000; 271 | //we can't set any headers 272 | xdr.open(method, url, true); 273 | xdr.send(data); 274 | return xdr; 275 | }; 276 | 277 | return { 278 | get: get_request, 279 | post: post_request, 280 | request: make_request 281 | }; 282 | }); 283 | -------------------------------------------------------------------------------- /src/server/iframeAjax.js: -------------------------------------------------------------------------------- 1 | //iframeAjax.js 2 | 'use strict'; 3 | 4 | filepicker.extend('iframeAjax', function(){ 5 | var fp = this; 6 | 7 | var IFRAME_ID = 'ajax_iframe'; 8 | 9 | //we can only have one out at a time 10 | var queue = []; 11 | var running = false; 12 | 13 | var get_request = function(url, options) { 14 | options.method = 'GET'; 15 | make_request(url, options); 16 | }; 17 | 18 | var post_request = function(url, options) { 19 | options.method = 'POST'; 20 | url += (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheBust='+fp.util.getId(); 21 | make_request(url, options); 22 | }; 23 | 24 | var runQueue = function(){ 25 | if (queue.length > 0) { 26 | var next = queue.shift(); 27 | make_request(next.url, next.options); 28 | } 29 | }; 30 | 31 | //Take the data, wrap it in an input and a form, submit that into an iframe, and get the response 32 | var make_request = function(url, options) { 33 | if (running) { 34 | queue.push({url: url, options: options}); 35 | return; 36 | } 37 | 38 | url += (url.indexOf('?') >= 0 ? '&' : '?') + 'plugin=' + fp.urls.getPlugin() + '&_cacheBust=' + fp.util.getId(); 39 | url += '&Content-Type=text%2Fhtml'; 40 | 41 | fp.comm.openChannel(); 42 | 43 | //Opening an iframe to make the request to 44 | var uploadIFrame; 45 | //IE makes us do rediculous things - 46 | //http://terminalapp.net/submitting-a-form-with-target-set-to-a-script-generated-iframe-on-ie/ 47 | try { 48 | uploadIFrame = document.createElement('