├── .babelrc ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── feature_request.md │ └── question_help.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── coverage.yml │ ├── documentation.yml │ ├── npm_upload.yml │ └── spec_update.yml ├── .gitignore ├── .gitmodules ├── .jsdoc.json ├── .nycrc.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── UPGRADING.md ├── codecov.yml ├── examples ├── README.md ├── javascript │ ├── .eslintrc │ ├── PKCE-backend │ │ ├── code_flow_example.js │ │ ├── package-lock.json │ │ └── package.json │ ├── auth │ │ └── index.html │ ├── basic │ │ └── index.html │ ├── download │ │ └── index.html │ ├── index.html │ ├── node │ │ ├── basic.js │ │ ├── download.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── team-as-user.js │ │ ├── team.js │ │ └── upload.js │ ├── pkce-browser │ │ └── index.html │ ├── server.js │ ├── simple-backend │ │ ├── code_flow_example.js │ │ ├── package-lock.json │ │ └── package.json │ ├── styles.css │ ├── team-as-user │ │ └── index.html │ ├── team │ │ └── index.html │ ├── upload │ │ └── index.html │ └── utils.js └── typescript │ ├── .eslintrc │ ├── basic │ ├── basic.js.map │ ├── basic.ts │ └── index.js │ ├── download │ ├── download.js.map │ ├── download.ts │ └── index.js │ ├── package-lock.json │ ├── package.json │ ├── team-as-user │ ├── index.js │ ├── team-as-user.js.map │ └── team-as-user.ts │ ├── team │ ├── index.js │ ├── team.js.map │ └── team.ts │ ├── tsconfig.json │ └── upload │ ├── index.js │ ├── upload.js.map │ └── upload.ts ├── generator ├── generate_routes.py └── typescript │ ├── dropbox_types.d.tstemplate │ └── index.d.tstemplate ├── index.js ├── lib ├── routes.js └── types.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── scripts ├── release_note_generator.sh └── update_version.sh ├── src ├── auth.js ├── constants.js ├── dropbox.js ├── error.js ├── response.js └── utils.js ├── test ├── .eslintrc ├── build │ ├── browser.js │ ├── browser │ │ ├── es.html │ │ ├── umd.html │ │ └── umd.min.html │ ├── node.js │ └── node │ │ ├── js_cjs │ │ ├── package-lock.json │ │ ├── package.json │ │ └── require.js │ │ ├── js_esm │ │ ├── import.js │ │ ├── namedImport.js │ │ ├── package-lock.json │ │ └── package.json │ │ ├── ts_cjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── require.js │ │ ├── require.js.map │ │ ├── require.ts │ │ └── tsconfig.json │ │ └── ts_es6 │ │ ├── import.js │ │ ├── import.js.map │ │ ├── import.ts │ │ ├── namedImport.js │ │ ├── namedImport.js.map │ │ ├── namedImport.ts │ │ ├── package-lock.json │ │ ├── package.json │ │ └── tsconfig.json ├── fixtures │ └── test.txt ├── integration │ ├── team.js │ └── user.js ├── types │ ├── .eslintrc │ ├── tsconfig.json │ ├── types_test.js │ ├── types_test.js.map │ └── types_test.ts └── unit │ ├── auth.js │ ├── dropbox.js │ ├── error.js │ ├── response.js │ └── utils.js └── types ├── .eslintrc ├── dropbox_types.d.ts └── index.d.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "env": { 4 | "coverage": { 5 | "plugins": ["istanbul"] 6 | }, 7 | "commonjs": { 8 | "presets": [[ 9 | "@babel/preset-env", { 10 | "modules": "cjs" 11 | } 12 | ]], 13 | "plugins": [ 14 | ["transform-es2015-modules-commonjs", { "noInterop": true }] 15 | ] 16 | }, 17 | "es": { 18 | "presets": [[ 19 | "@babel/preset-env", { 20 | "modules": false 21 | } 22 | ]] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib/types.js 2 | lib/routes.js 3 | types/dropbox_types.d.ts 4 | types/index.d.ts 5 | test/types/types_test.js 6 | test/types/types_test.js.map 7 | test/build/node/** 8 | docs/** 9 | dist/ 10 | es/ 11 | cjs/ 12 | examples/typescript/**/*.js 13 | examples/typescript/**/*.js.map 14 | **/node_modules/** 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb-base"], 3 | "env": { 4 | "node": true 5 | }, 6 | "rules": { 7 | "func-names": 0, 8 | "no-param-reassign": 0, 9 | "import/prefer-default-export": 0, 10 | "import/extensions": 0, 11 | "no-undef": 0 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 2017, 15 | "impliedStrict": false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a report to help us improve the SDK 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of the bug. 12 | 13 | **To Reproduce** 14 | The steps to reproduce the behavior 15 | 16 | **Expected Behavior** 17 | A clear description of what you expected to happen. 18 | 19 | **Actual Behavior** 20 | A clear description of what actually happened 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Versions** 26 | * What version of the SDK are you using? 27 | * What version of the language are you using? 28 | * Are you using Javascript or Typescript? 29 | * What platform are you using? (if applicable) 30 | 31 | **Additional context** 32 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature Request" 3 | about: Suggest an idea for this SDK 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Why is this feature valuable to you? Does it solve a problem you're having?** 11 | A clear and concise description of why this feature is valuable. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. (if applicable) 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question_help.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4AC Questions / Help" 3 | about: Get help with issues you are experiencing 4 | title: '' 5 | labels: help-wanted, question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Before you start** 11 | Have you checked StackOverflow, previous issues, and Dropbox Developer Forums for help? 12 | 13 | **What is your question?** 14 | A clear and concise description of the question. 15 | 16 | **Screenshots** 17 | If applicable, add screenshots to help explain your question. 18 | 19 | **Versions** 20 | * What version of the SDK are you using? 21 | * What version of the language are you using? 22 | * Are you using Javascript or Typescript? 23 | * What platform are you using? (if applicable) 24 | 25 | **Additional context** 26 | Add any other context about the question here. -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Updates to main package 9 | - package-ecosystem: "npm" 10 | directory: "/" 11 | schedule: 12 | interval: "monthly" 13 | # Updates to TS examples 14 | - package-ecosystem: "npm" 15 | directory: "examples/typescript" 16 | schedule: 17 | interval: "monthly" 18 | 19 | - package-ecosystem: "github-actions" 20 | directory: "/" 21 | schedule: 22 | interval: "monthly" 23 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ## **Checklist** 6 | 7 | 8 | **General Contributing** 9 | - [ ] Have you read the Code of Conduct and signed the [CLA](https://opensource.dropbox.com/cla/)? 10 | 11 | **Is This a Code Change?** 12 | - [ ] Non-code related change (markdown/git settings etc) 13 | - [ ] SDK Code Change 14 | - [ ] Example/Test Code Change 15 | 16 | **Validation** 17 | - [ ] Does `npm test` pass? 18 | - [ ] Does `npm run build` pass? 19 | - [ ] Does `npm run lint` pass? -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | CI: 8 | continue-on-error: true 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, macos-latest] 13 | node: [10, 11, 12, 13, 14] 14 | steps: 15 | - uses: actions/checkout@v2.3.4 16 | - name: Setup Node.js environment 17 | uses: actions/setup-node@v2.1.5 18 | with: 19 | node-version: ${{ matrix.node }} 20 | - name: Install SDK 21 | run: | 22 | npm install 23 | - name: Run Build 24 | run: | 25 | npm run build 26 | - name: Run Examples Builds 27 | run: | 28 | cd examples/typescript/ 29 | npm install 30 | npm run build 31 | cd ../.. 32 | - name: Run Linter 33 | run: | 34 | npm run lint 35 | - name: Run Unit Tests 36 | run: | 37 | npm run test:unit 38 | - name: Run TypeScipt Tests 39 | run: | 40 | npm run test:typescript 41 | - name: Run Build Tests 42 | if: matrix.node >= 12 # "type" = "module" was introduced in node v12 so lower versions will fail ESM tests 43 | run: | 44 | npm run clean 45 | npm run build 46 | npm run test:build 47 | Integration: 48 | runs-on: ${{ matrix.os }} 49 | strategy: 50 | matrix: 51 | os: [ubuntu-latest, macos-latest] 52 | node: [14] # Can only be ran on 14 because it relies on fs/promises 53 | steps: 54 | - uses: actions/checkout@v2.3.4 55 | - name: Setup Node.js environment 56 | uses: actions/setup-node@v2.1.5 57 | with: 58 | node-version: ${{ matrix.node }} 59 | - name: Install SDK 60 | run: | 61 | npm install 62 | - name: Run Integration Tests 63 | env: 64 | LEGACY_USER_DROPBOX_TOKEN: ${{ secrets.LEGACY_USER_DROPBOX_TOKEN }} 65 | LEGACY_USER_CLIENT_ID: ${{ secrets.LEGACY_USER_CLIENT_ID }} 66 | LEGACY_USER_CLIENT_SECRET: ${{ secrets.LEGACY_USER_CLIENT_SECRET }} 67 | LEGACY_USER_REFRESH_TOKEN: ${{ secrets.LEGACY_USER_REFRESH_TOKEN }} 68 | SCOPED_USER_DROPBOX_TOKEN: ${{ secrets.SCOPED_USER_DROPBOX_TOKEN }} 69 | SCOPED_USER_CLIENT_ID: ${{ secrets.SCOPED_USER_CLIENT_ID }} 70 | SCOPED_USER_CLIENT_SECRET: ${{ secrets.SCOPED_USER_CLIENT_SECRET }} 71 | SCOPED_USER_REFRESH_TOKEN: ${{ secrets.SCOPED_USER_REFRESH_TOKEN }} 72 | SCOPED_TEAM_DROPBOX_TOKEN: ${{ secrets.SCOPED_TEAM_DROPBOX_TOKEN }} 73 | SCOPED_TEAM_CLIENT_ID: ${{ secrets.SCOPED_TEAM_CLIENT_ID }} 74 | SCOPED_TEAM_CLIENT_SECRET: ${{ secrets.SCOPED_TEAM_CLIENT_SECRET }} 75 | SCOPED_TEAM_REFRESH_TOKEN: ${{ secrets.SCOPED_TEAM_REFRESH_TOKEN }} 76 | DROPBOX_SHARED_LINK: ${{ secrets.DROPBOX_SHARED_LINK }} 77 | run: | 78 | npm run test:integration 79 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: CodeCov 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | schedule: 8 | - cron: 0 0 * * * 9 | 10 | jobs: 11 | Unit: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2.3.4 15 | - name: Setup Node.js environment 16 | uses: actions/setup-node@v2.1.5 17 | with: 18 | node-version: '14' 19 | - name: Install SDK 20 | run: | 21 | npm install 22 | - name: Generate Unit Test Coverage 23 | run: | 24 | npm run coverage:unit 25 | - name: Publish Coverage 26 | uses: codecov/codecov-action@v1.5.0 27 | with: 28 | flags: unit 29 | fail_ci_if_error: true 30 | Integration: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v2.3.4 34 | - name: Setup Node.js environment 35 | uses: actions/setup-node@v2.1.5 36 | with: 37 | node-version: '14' 38 | - name: Install SDK 39 | run: | 40 | npm install 41 | - name: Generate Integration Test Coverage 42 | env: 43 | LEGACY_USER_DROPBOX_TOKEN: ${{ secrets.LEGACY_USER_DROPBOX_TOKEN }} 44 | LEGACY_USER_CLIENT_ID: ${{ secrets.LEGACY_USER_CLIENT_ID }} 45 | LEGACY_USER_CLIENT_SECRET: ${{ secrets.LEGACY_USER_CLIENT_SECRET }} 46 | LEGACY_USER_REFRESH_TOKEN: ${{ secrets.LEGACY_USER_REFRESH_TOKEN }} 47 | SCOPED_USER_DROPBOX_TOKEN: ${{ secrets.SCOPED_USER_DROPBOX_TOKEN }} 48 | SCOPED_USER_CLIENT_ID: ${{ secrets.SCOPED_USER_CLIENT_ID }} 49 | SCOPED_USER_CLIENT_SECRET: ${{ secrets.SCOPED_USER_CLIENT_SECRET }} 50 | SCOPED_USER_REFRESH_TOKEN: ${{ secrets.SCOPED_USER_REFRESH_TOKEN }} 51 | SCOPED_TEAM_DROPBOX_TOKEN: ${{ secrets.SCOPED_TEAM_DROPBOX_TOKEN }} 52 | SCOPED_TEAM_CLIENT_ID: ${{ secrets.SCOPED_TEAM_CLIENT_ID }} 53 | SCOPED_TEAM_CLIENT_SECRET: ${{ secrets.SCOPED_TEAM_CLIENT_SECRET }} 54 | SCOPED_TEAM_REFRESH_TOKEN: ${{ secrets.SCOPED_TEAM_REFRESH_TOKEN }} 55 | DROPBOX_SHARED_LINK: ${{ secrets.DROPBOX_SHARED_LINK }} 56 | run: | 57 | npm run coverage:integration 58 | - name: Publish Coverage 59 | uses: codecov/codecov-action@v1.5.0 60 | with: 61 | flags: integration 62 | fail_ci_if_error: true 63 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Update Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | npm: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2.3.4 13 | - uses: actions/setup-node@v2.1.5 14 | with: 15 | registry-url: https://npm.pkg.github.com/ 16 | node-version: 14 17 | - name: Build Package 18 | run: | 19 | npm install 20 | npm run build 21 | - name: Build Documentation 22 | run: npm run generate-docs 23 | - name: Push Documentation 24 | uses: JamesIves/github-pages-deploy-action@releases/v3 25 | with: 26 | ACCESS_TOKEN: ${{ secrets.GH_PAGES_PUBLISH_TOKEN }} 27 | BRANCH: gh-pages # The branch the action should deploy to. 28 | FOLDER: docs # The folder the action should deploy. -------------------------------------------------------------------------------- /.github/workflows/npm_upload.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Publish to NPM 5 | 6 | on: 7 | workflow_dispatch: 8 | release: 9 | types: [created] 10 | 11 | jobs: 12 | npm: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2.3.4 16 | - uses: actions/setup-node@v2.1.5 17 | with: 18 | node-version: 14 19 | - name: Build Package 20 | run: | 21 | npm install 22 | npm run build 23 | - name: NPM Publish 24 | uses: JS-DevTools/npm-publish@v1.4.3 25 | with: 26 | token: ${{ secrets.NPM_TOKEN }} 27 | -------------------------------------------------------------------------------- /.github/workflows/spec_update.yml: -------------------------------------------------------------------------------- 1 | name: Spec Update 2 | on: 3 | workflow_dispatch: 4 | repository_dispatch: 5 | types: [spec_update] 6 | 7 | jobs: 8 | Update: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2.3.4 12 | - name: Setup Python environment 13 | uses: actions/setup-python@v2.2.2 14 | with: 15 | python-version: 3.7 16 | - name: Setup Node.JS environment 17 | uses: actions/setup-node@v2.1.5 18 | with: 19 | node-version: 14 20 | - name: Get current time 21 | uses: 1466587594/get-current-time@v2 22 | id: current-time 23 | with: 24 | format: YYYY_MM_DD 25 | utcOffset: "-08:00" 26 | - name: Update SDK Version 27 | run: npm version --commit-hooks false --git-tag-version false minor 28 | - name: Install SDK 29 | run: | 30 | npm install 31 | - name: Update Modules 32 | run: | 33 | git submodule init 34 | git submodule update --remote --recursive 35 | - name: Generate Branch Name 36 | id: git-branch 37 | run: | 38 | echo "::set-output name=branch::spec_update_${{ steps.current-time.outputs.formattedTime }}" 39 | - name: Generate Num Diffs 40 | id: git-diff-num 41 | run: | 42 | cd generator/ 43 | diffs=$(git diff --submodule dropbox-api-spec | grep ">" | wc -l) 44 | echo "Number of Spec diffs: $diffs" 45 | echo "::set-output name=num-diff::$diffs" 46 | cd .. 47 | - name: Generate Diff 48 | id: git-diff 49 | run: | 50 | cd generator/dropbox-api-spec 51 | gitdiff=$(git log -n ${{ steps.git-diff-num.outputs.num-diff }} --pretty="format:%n %H %n%n %b") 52 | commit="Automated Spec Update $gitdiff" 53 | commit="${commit//'%'/'%25'}" 54 | commit="${commit//$'\n'/'%0A'}" 55 | commit="${commit//$'\r'/'%0D'}" 56 | echo "Commit Message: $commit" 57 | echo "::set-output name=commit::$commit" 58 | cd ../.. 59 | - name: Generate New Routes 60 | run: | 61 | cd generator/stone 62 | python setup.py install 63 | cd .. 64 | python generate_routes.py 65 | cd .. 66 | - name: Create Pull Request 67 | uses: peter-evans/create-pull-request@v3.9.1 68 | if: steps.git-diff-num.outputs.num-diff != 0 69 | with: 70 | token: ${{ secrets.SPEC_UPDATE_TOKEN }} 71 | commit-message: | 72 | ${{ steps.git-diff.outputs.commit}} 73 | branch: ${{ steps.git-branch.outputs.branch }} 74 | delete-branch: true 75 | title: 'Automated Spec Update' 76 | body: | 77 | ${{ steps.git-diff.outputs.commit}} 78 | base: 'main' 79 | team-reviewers: | 80 | owners 81 | maintainers 82 | draft: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE config folders 2 | .idea/ 3 | 4 | #Output Directories 5 | es/ 6 | cjs/ 7 | umd/ 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | lerna-debug.log* 16 | 17 | 18 | docs/ 19 | 20 | # Diagnostic reports (https://nodejs.org/api/report.html) 21 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 22 | 23 | # Runtime data 24 | pids 25 | *.pid 26 | *.seed 27 | *.pid.lock 28 | 29 | # Directory for instrumented libs generated by jscoverage/JSCover 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | coverage 34 | *.lcov 35 | 36 | # nyc test coverage 37 | .nyc_output 38 | 39 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 40 | .grunt 41 | 42 | # Bower dependency directory (https://bower.io/) 43 | bower_components 44 | 45 | # node-waf configuration 46 | .lock-wscript 47 | 48 | # Compiled binary addons (https://nodejs.org/api/addons.html) 49 | build/Release 50 | 51 | # Dependency directories 52 | node_modules/ 53 | jspm_packages/ 54 | 55 | # TypeScript v1 declaration files 56 | typings/ 57 | 58 | # TypeScript cache 59 | *.tsbuildinfo 60 | 61 | # Optional npm cache directory 62 | .npm 63 | 64 | # Optional eslint cache 65 | .eslintcache 66 | 67 | # Microbundle cache 68 | .rpt2_cache/ 69 | .rts2_cache_cjs/ 70 | .rts2_cache_es/ 71 | .rts2_cache_umd/ 72 | 73 | # Optional REPL history 74 | .node_repl_history 75 | 76 | # Output of 'npm pack' 77 | *.tgz 78 | 79 | # Yarn Integrity file 80 | .yarn-integrity 81 | 82 | # dotenv environment variables file 83 | .env 84 | .env.test 85 | 86 | # parcel-bundler cache (https://parceljs.org/) 87 | .cache 88 | 89 | # Next.js build output 90 | .next 91 | 92 | # Nuxt.js build / generate output 93 | .nuxt 94 | dist 95 | 96 | # Gatsby files 97 | .cache/ 98 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 99 | # https://nextjs.org/blog/next-9-1#public-directory-support 100 | # public 101 | 102 | # vuepress build output 103 | .vuepress/dist 104 | 105 | # Serverless directories 106 | .serverless/ 107 | 108 | # FuseBox cache 109 | .fusebox/ 110 | 111 | # DynamoDB Local files 112 | .dynamodb/ 113 | 114 | # TernJS port file 115 | .tern-port 116 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dropbox-api-spec"] 2 | path = generator/dropbox-api-spec 3 | url = https://github.com/dropbox/dropbox-api-spec.git 4 | [submodule "stone"] 5 | path = generator/stone 6 | url = https://github.com/dropbox/stone.git 7 | -------------------------------------------------------------------------------- /.jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "include": ["./src", "./lib"] 4 | }, 5 | "opts": { 6 | "template": "./node_modules/ink-docstrap/template", 7 | "destination": "docs", 8 | "readme": "./README.md", 9 | "recurse": true 10 | }, 11 | "templates": { 12 | "systemName": "Dropbox Node SDK", 13 | "theme": "cosmo" 14 | } 15 | } -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "include": [ 4 | "src/*.js", 5 | "lib/*.js" 6 | ], 7 | "require": ["@babel/register"] 8 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Dropbox Code Of Conduct 2 | 3 | *Dropbox believes that an inclusive development environment fosters greater technical achievement. To encourage a diverse group of contributors we've adopted this code of conduct.* 4 | 5 | Please read the Official Dropbox [Code of Conduct](https://opensource.dropbox.com/coc/) before contributing. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Dropbox SDK for Javascript 2 | We value and rely on the feedback from our community. This comes in the form of bug reports, feature requests, and general guidance. We welcome your issues and pull requests and try our hardest to be timely in both response and resolution. Please read through this document before submitting issues or pull requests to ensure we have the necessary information to help you resolve your issue. 3 | 4 | ## Filing Bug Reports 5 | You can file a bug report on the [GitHub Issues][issues] page. 6 | 7 | 1. Search through existing issues to ensure that your issue has not been reported. If it is a common issue, there is likely already an issue. 8 | 9 | 2. Please ensure you are using the latest version of the SDK. While this may be a valid issue, we only will fix bugs affecting the latest version and your bug may have been fixed in a newer version. 10 | 11 | 3. Provide as much information as you can regarding the language version, SDK version, and any other relevant information about your environment so we can help resolve the issue as quickly as possible. 12 | 13 | ## Submitting Pull Requests 14 | 15 | We are more than happy to recieve pull requests helping us improve the state of our SDK. You can open a new pull request on the [GitHub Pull Requests][pr] page. 16 | 17 | 1. Please ensure that you have read the [License][license], [Code of Conduct][coc] and have signed the [Contributing License Agreement (CLA)][cla]. 18 | 19 | 2. Please add tests confirming the new functionality works. Pull requests will not be merged without passing continuous integration tests unless the pull requests aims to fix existing issues with these tests. 20 | 21 | 3. If the pull request is modifying typescript definitions, please remember to change the template found under `generator/typescript` and run the generation instead of manually changing types. If there is an issue with the generation, please file an issue. 22 | 23 | ## Updating Generated Code 24 | 25 | Generated code can be updated by running the following commands: 26 | 27 | ``` 28 | $ git submodule init 29 | $ git submodule update --remote --recursive 30 | $ cd generator/stone 31 | $ python setup.py install 32 | $ cd .. 33 | $ python generate_routes.py 34 | ``` 35 | 36 | This will generate typescript definitions and route code. 37 | 38 | ## Testing the Code 39 | 40 | Tests live under the `test/` folder and are then broken down into the type of test it is. To run both the unit tests and the typescript tests, you can use: 41 | 42 | ``` 43 | $ npm test 44 | ``` 45 | 46 | If you would like to run the integration tests locally, you can run: 47 | 48 | ``` 49 | export DROPBOX_TOKEN={fill in user token} 50 | export DROPBOX_TEAM_TOKEN={fill in team token} 51 | export DROPBOX_USER_ID={fill in assume user id} 52 | export DROPBOX_SHARED_LINK={fill in shared link} 53 | $ npm run test:integration 54 | ``` 55 | 56 | Note: If you do not have all of these tokens available, we run integration tests as a part of pull request validation and you are able to rely on those if you are unable to obtain yourself. 57 | 58 | [issues]: https://github.com/dropbox/dropbox-sdk-js/issues 59 | [pr]: https://github.com/dropbox/dropbox-sdk-js/pulls 60 | [coc]: https://github.com/dropbox/dropbox-sdk-js/blob/main/CODE_OF_CONDUCT.md 61 | [license]: https://github.com/dropbox/dropbox-sdk-js/blob/main/LICENSE 62 | [cla]: https://opensource.dropbox.com/cla/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Dropbox Inc., http://www.dropbox.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Logo][logo]][repo] 2 | 3 | [![node-current](https://img.shields.io/node/v/dropbox)](https://www.npmjs.com/package/dropbox) 4 | [![npm](https://img.shields.io/npm/v/dropbox)](https://www.npmjs.com/package/dropbox) 5 | [![codecov](https://codecov.io/gh/dropbox/dropbox-sdk-js/branch/main/graph/badge.svg)](https://codecov.io/gh/dropbox/dropbox-sdk-js) 6 | 7 | The offical Dropbox SDK for Javascript. 8 | 9 | Documentation can be found on [GitHub Pages][documentation] 10 | 11 | ## Installation 12 | 13 | Create an app via the [Developer Console][devconsole] 14 | 15 | Install via [npm](https://www.npmjs.com/) 16 | 17 | ``` 18 | $ npm install --save dropbox 19 | ``` 20 | 21 | Install from source: 22 | 23 | ``` 24 | $ git clone https://github.com/dropbox/dropbox-sdk-js.git 25 | $ cd dropbox-sdk-js 26 | $ npm install 27 | ``` 28 | 29 | If you are using the repository from the browser, you can use any CDNs that hosts the Dropbox package by including a script tag with the link to the package. However, we highly recommend you do not directly import the latest version and instead choose a specific version. When we update and release a breaking change, this could break production code which we hope to avoid. Note, we follow [semver](https://semver.org/) naming conventions which means that any major version update could contain a breaking change. 30 | 31 | After installation, follow one of our [Examples][examples] or read the [Documentation][documentation]. 32 | 33 | You can also view our [OAuth guide][oauthguide]. 34 | 35 | ## Examples 36 | 37 | We provide [Examples][examples] to help get you started with a lot of the basic functionality in the SDK. We provide most examples in both Javascript and Typescript with some having a Node equivalent. 38 | 39 | - **OAuth** 40 | - Auth - [ [JS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/javascript/auth) ] - A simple auth example to get an access token and list the files in the root of your Dropbox account. 41 | - Simple Backend [ [JS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/javascript/simple-backend) ] - A simple example of a node backend doing a multi-step auth flow for Short Lived Tokens. 42 | - PKCE Backend [ [JS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/javascript/PKCE-backend) ] - A simple example of a node backend doing a multi-step auth flow using PKCE and Short Lived Tokens. 43 | - PKCE Browser [ [JS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/javascript/pkce-browser) ] - A simple example of a frontend doing a multi-step auth flow using PKCE and Short Lived Tokens. 44 | 45 | - **Other Examples** 46 | - Basic - [ [TS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/typescript/basic), [JS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/javascript/basic) ] - A simple example that takes in a token and fetches files from your Dropbox account. 47 | - Download - [ [TS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/typescript/download), [JS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/javascript/download) ] - An example showing how to download a shared file. 48 | - Team As User - [ [TS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/typescript/team-as-user), [JS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/javascript/team-as-user) ] - An example showing how to act as a user. 49 | - Team - [ [TS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/typescript/team), [JS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/javascript/team) ] - An example showing how to use the team functionality and list team devices. 50 | - Upload [ [TS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/typescript/upload), [JS](https://github.com/dropbox/dropbox-sdk-js/tree/main/examples/javascript/upload) ] - An example showing how to upload a file to Dropbox. 51 | 52 | ## Getting Help 53 | 54 | If you find a bug, please see [CONTRIBUTING.md][contributing] for information on how to report it. 55 | 56 | If you need help that is not specific to this SDK, please reach out to [Dropbox Support][support]. 57 | 58 | ## License 59 | 60 | This SDK is distributed under the MIT license, please see [LICENSE][license] for more information. 61 | 62 | [logo]: https://cfl.dropboxstatic.com/static/images/sdk/javascript_banner.png 63 | [repo]: https://github.com/dropbox/dropbox-sdk-js 64 | [documentation]: https://dropbox.github.io/dropbox-sdk-js/ 65 | [examples]: https://github.com/dropbox/dropbox-sdk-js/tree/main/examples 66 | [license]: https://github.com/dropbox/dropbox-sdk-js/blob/main/LICENSE 67 | [contributing]: https://github.com/dropbox/dropbox-sdk-js/blob/main/CONTRIBUTING.md 68 | [devconsole]: https://dropbox.com/developers/apps 69 | [oauthguide]: https://www.dropbox.com/lp/developers/reference/oauth-guide 70 | [support]: https://www.dropbox.com/developers/contact 71 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | # Upgrading the Dropbox SDK 2 | 3 | This document is designed to show you how to upgrade to the latest version of the SDK accomodating any breaking changes introduced by major version updates. If you find any issues with either this guide on upgrading or the changes introduced in the new version, please see [CONTRIBUTING.md][contributing] 4 | 5 | # Upgrading from v9.X.X to v10.0.0 6 | 7 | ## 1. Deprecating the `authenticateWithCordova` function 8 | 9 | The `authenticateWithCordova` function used an in-app browser within the Cordova framework to authenticate users via OAuth. As a part of hardening security, we are following [Google’s recommendation](https://developers.googleblog.com/2016/08/modernizing-oauth-interactions-in-native-apps.html) to remove support for authentication via a “web-view” or in-app browsers. Since the `authenticateWithCordova` function relies on running in an in-app browser, we have made the choice to deprecate this function. 10 | 11 | Instead, apps will need to implement logic to handle this use case. The high level logic would be as follows: 12 | 13 | 1. getAuthenticationUrl with your app’s parameters. For Native Apps, we highly encourage using PKCE to increase your app’s security. 14 | 2. Open the authentication URL in the default system browser 15 | 3. Redirect back into your app upon completion of the OAuth flow. 16 | 17 | We recommend using a custom URI for redirect to ensure you are redirecting directly back into your app. You can read up on this process more in detail on the [OAuth site](https://www.oauth.com/oauth2-servers/redirect-uris/redirect-uris-native-apps/). 18 | 19 | # Upgrading from v8.X.X to v9.0.0 20 | 21 | ## 1. Unblocking browser PKCE flow 22 | 23 | Previously, there was an issue in which Node and the Browser use different processes to generate the `codeVerifier` and `codeChallenge`. In order to remedy this, both `generatePKCECodes` and `getAuthenticationUrl` now return promises due to the how the browser digests hashes. 24 | 25 | Previous Implementation(synchronous): 26 | ``` 27 | var authUrl = dbxAuth.getAuthenticationUrl(redirectUri, null, 'code', 'offline', null, 'none', false) 28 | // logic for navigating to authUrl 29 | ``` 30 | New Implementation(async): 31 | ``` 32 | dbxAuth.getAuthenticationUrl(redirectUri, null, 'code', 'offline', null, 'none', false) 33 | .then((authUrl) => { 34 | // logic for navigating to authUrl 35 | }); 36 | ``` 37 | # Upgrading from v7.X.X to v8.0.0 38 | 39 | ## 1. Throwing Errors as `DropboxResponseError` rather than a literal object 40 | 41 | We have created a new Error class called `DropboxResponseError` which contains the same members as the literal that was thrown, but in a cleaner format. It also allows you to leverage the fact this class now extends the builtin `Error` class. 42 | 43 | # Upgrading from v6.X.X to v7.0.0 44 | 45 | ## 1. Fixing the Typescript argument parameter bug ([#41](https://github.com/dropbox/dropbox-sdk-js/issues/41)) 46 | 47 | We noticed a long lasting bug where the Typescript definitions of routes with no arg would require a `void` argument. This required users to make calls like this: 48 | 49 | ``` 50 | var result = dbx.usersGetCurrentAccount(null); 51 | ``` 52 | 53 | We have since fixed this to no longer require the null parameter. 54 | 55 | # Upgrading from v5.X.X to v6.0.0 56 | 57 | ## 1. Unifying Dropbox and DropboxTeam 58 | 59 | We made the decision to unify the Dropbox and DropboxTeam objects to further simplify the logic in the SDK. Migrating is very straightforward, a reference like this: 60 | 61 | ``` 62 | var dbx = new DropboxTeam({ 63 | accessToken: 'my_token' 64 | }); 65 | ``` 66 | 67 | Can be rewritten as: 68 | 69 | ``` 70 | var dbx = new Dropbox({ 71 | accessToken: 'my_token' 72 | }); 73 | ``` 74 | 75 | Additionally, when using features like assume user, select admin, or path root they are not set as a part of the constructor rather than creating a new client. Logic like this: 76 | 77 | ``` 78 | var dbx = new DropboxTeam({ 79 | accessToken: 'my_token' 80 | }); 81 | var dbx_user = dbx.actAsUser(user_id); 82 | dbx_user.usersGetCurrentAccount(); 83 | ``` 84 | 85 | Can be rewritten as: 86 | 87 | ``` 88 | var dbx = new Dropbox({ 89 | accessToken: 'my_token', 90 | selectUser: 'my_user_id' 91 | }); 92 | dbx.usersGetcurrentAccount(); 93 | ``` 94 | 95 | ## 2. Moving authentication to DropboxAuth 96 | 97 | Another change that was made was to move all auth related functionality into the DropboxAuth object. The main Dropbox object can be constructed the same way but this will internally create a DropboxAuth object. In order to access any auth functions from the main client you must change your code as such: 98 | 99 | ``` 100 | dbx.get_authentication_url(...); 101 | ``` 102 | 103 | Would become something like this: 104 | 105 | ``` 106 | dbx.auth.get_authentication_url(...); 107 | ``` 108 | 109 | However, we recommend creating a DropboxAuth object before creating a client and then constructing as such: 110 | 111 | ``` 112 | var dbxAuth = new DropboxAuth(); 113 | ... // Do auth logic 114 | var dbx = new Dropbox(dbxAuth); 115 | ``` 116 | 117 | That way if you need to create another instance of the client, you can easily plug in the same auth object. 118 | 119 | ## 3. Changing Typescript export format 120 | 121 | We have updated the Typescript definitions to be a part of `Dropbox` namespace rather than the `DropboxTypes` namespace. This would look like: 122 | 123 | ``` 124 | const result: DropboxTypes.users.FullAccount dbx.usersGetCurrentAccount(); 125 | ``` 126 | 127 | Would become: 128 | 129 | ``` 130 | const result: Dropbox.users.FullAccount dbx.usersGetCurrentAccount(); 131 | ``` 132 | 133 | ## 4. Updating the Response object 134 | 135 | We have wrapped the raw responses into the `DropboxResponse` object in order to expose more information out to users. This change looks like: 136 | 137 | ``` 138 | var response = dbx.usersGetcurrentAccount(); 139 | console.log(response.fileBlob); //or fileBinary if using workers 140 | ``` 141 | 142 | Would become: 143 | 144 | ``` 145 | var response = dbx.usersGetcurrentAccount(); 146 | console.log(response.result.fileBlob); //or fileBinary if using workers 147 | ``` 148 | 149 | This also exposes the other components of the response like the status and headers which was not previously available. 150 | 151 | ``` 152 | var response = dbx.usersGetcurrentAccount(); 153 | console.log(response.status); 154 | console.log(response.headers); 155 | ``` 156 | 157 | ## 5. Default behavior for `fetch`. 158 | 159 | Previously we have provided guidance to SDK users that they should not rely on the Dropbox SDK's global fetch and that it would be deprecated in future versions. In 6.0.0 onwards, we now include the `node-fetch` dependency as part of the NPM package. For browser environments, we fallback to `window.fetch` by default. 160 | 161 | As a result, you should not pass in your own `fetch` to the Dropbox constructor unless you have a specific reason to do so (mocking, etc). Note that if you opt to pass in fetch to support your use case, you may need to bind your fetch to the appropriate context e.g. `fetch.bind(your_context)`. 162 | 163 | [contributing]: https://github.com/dropbox/dropbox-sdk-js/blob/main/CONTRIBUTING.md 164 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: auto 6 | threshold: 0% 7 | base: auto 8 | if_not_found: error 9 | if_ci_failed: error 10 | informational: false 11 | only_pulls: true -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Dropbox JavaScript SDK Examples 2 | 3 | To run the examples in your development environment: 4 | 5 | 1. Clone this repo 6 | 2. Run `npm install` and `npm run build` 7 | 3. Start the development server with `node server.js`. 8 | 4. Point your browser to 9 | 10 | # Dropbox TypeScript SDK Examples 11 | 12 | To run the examples in your development environment: 13 | 14 | 1. Clone this repo 15 | 2. Run `npm install` and `npm run build` 16 | 3. Run the example you want (basic, download, team, team-as-user, or upload) 17 | e.g. `node basic` 18 | 19 | ## Code flow example 20 | 21 | 1. Clone this repo 22 | 2. Run `npm install` and `npm run build` in the root of the repository 23 | 3. Create an app in the [App console](https://www.dropbox.com/developers/apps). 24 | 4. Set a redirect URI "http://localhost:3000/auth" on the app's page on the [App console](https://www.dropbox.com/developers/apps). 25 | 5. Set app key and secret in `examples/javascript/simple-backend/code_flow_example.js` on lines 17 and 18. 26 | 6. Run `node examples/javascript/simple-backend/code_flow_example.js` 27 | 7. Point your browser to 28 | -------------------------------------------------------------------------------- /examples/javascript/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": 0, 4 | "import/no-extraneous-dependencies": 0 5 | } 6 | } -------------------------------------------------------------------------------- /examples/javascript/PKCE-backend/code_flow_example.js: -------------------------------------------------------------------------------- 1 | // Standalone example to demonstrate codeflow. 2 | // Start the server, hit localhost:3000 on the browser, and click through. 3 | // On the server logs, you should have the auth code, as well as the token 4 | // from exchanging it. This exchange is invisible to the app user 5 | 6 | const fetch = require('node-fetch'); 7 | const app = require('express')(); 8 | 9 | const hostname = 'localhost'; 10 | const port = 3000; 11 | 12 | const config = { 13 | fetch, 14 | clientId: '42zjexze6mfpf7x', 15 | }; 16 | 17 | const { Dropbox } = require('dropbox'); // eslint-disable-line import/no-unresolved 18 | 19 | const dbx = new Dropbox(config); 20 | 21 | const redirectUri = `http://${hostname}:${port}/auth`; 22 | 23 | app.get('/', (req, res) => { 24 | dbx.auth.getAuthenticationUrl(redirectUri, null, 'code', 'offline', null, 'none', true) 25 | .then((authUrl) => { 26 | res.writeHead(302, { Location: authUrl }); 27 | res.end(); 28 | }); 29 | }); 30 | 31 | app.get('/auth', (req, res) => { // eslint-disable-line no-unused-vars 32 | const { code } = req.query; 33 | console.log(`code:${code}`); 34 | 35 | dbx.auth.getAccessTokenFromCode(redirectUri, code) 36 | .then((token) => { 37 | console.log(`Token Result:${JSON.stringify(token)}`); 38 | dbx.auth.setRefreshToken(token.result.refresh_token); 39 | dbx.usersGetCurrentAccount() 40 | .then((response) => { 41 | console.log('response', response); 42 | }) 43 | .catch((error) => { 44 | console.error(error); 45 | }); 46 | }) 47 | .catch((error) => { 48 | console.error(error); 49 | }); 50 | res.end(); 51 | }); 52 | 53 | app.listen(port); 54 | -------------------------------------------------------------------------------- /examples/javascript/PKCE-backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PKCE-backend", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "dropbox": "file:../../../" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/javascript/auth/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dropbox JavaScript SDK 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 29 | 30 |
31 | 36 | 37 | 41 |
42 | 43 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /examples/javascript/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dropbox JavaScript SDK 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 28 | 29 | 30 |
31 |

This example fetches the contents of your root Dropbox directory. It uses the Dropbox.filesListFolder() method [docs].

32 | 33 |
34 | 35 | 36 |
37 | 38 | 39 |
    40 | 41 |

    To obtain an access token for quick testing, you can go to API Explorer click the "Get Token" button on the top right, copy the token it creates and then paste it here.

    42 |
    43 | 44 | 45 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /examples/javascript/download/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dropbox JavaScript SDK 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 28 | 29 | 30 |
    31 |

    This example shows how to use the Dropbox.sharingGetSharedLinkFile() [docs] method to download the file for the given shared link.

    32 | 33 |
    34 | 35 | 36 | 37 |
    38 | 39 | 40 |
    41 | 42 |

    To obtain an access token for quick testing, you can go to API Explorer click the "Get Token" button on the top right, copy the token it creates and then paste it here.

    43 |
    44 | 45 | 46 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /examples/javascript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dropbox JavaScript SDK Examples 6 | 7 | 8 | 9 | 10 | 11 | 26 | 27 |
    28 | 37 |
    38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/javascript/node/basic.js: -------------------------------------------------------------------------------- 1 | const prompt = require('prompt'); 2 | const { Dropbox } = require('dropbox'); // eslint-disable-line import/no-unresolved 3 | 4 | prompt.start(); 5 | 6 | prompt.get({ 7 | properties: { 8 | accessToken: { 9 | description: 'Please enter an API V2 access token', 10 | }, 11 | }, 12 | }, (error, result) => { 13 | const dbx = new Dropbox({ accessToken: result.accessToken }); 14 | dbx.filesListFolder({ path: '/Screenshots' }) 15 | .then((response) => { 16 | console.log(response); 17 | }) 18 | .catch((err) => { 19 | console.log(err); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /examples/javascript/node/download.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const prompt = require('prompt'); 3 | const { Dropbox } = require('dropbox'); // eslint-disable-line import/no-unresolved 4 | 5 | prompt.start(); 6 | 7 | prompt.get({ 8 | properties: { 9 | accessToken: { 10 | description: 'Please enter an API V2 access token', 11 | }, 12 | sharedLink: { 13 | description: 'Please enter a shared link to a file', 14 | }, 15 | }, 16 | }, (error, result) => { 17 | const dbx = new Dropbox({ accessToken: result.accessToken }); 18 | dbx.sharingGetSharedLinkFile({ url: result.sharedLink }) 19 | .then((data) => { 20 | fs.writeFile(data.result.name, data.result.fileBinary, 'binary', (err) => { 21 | if (err) { throw err; } 22 | console.log(`File: ${data.result.name} saved.`); 23 | }); 24 | }) 25 | .catch((err) => { 26 | throw err; 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /examples/javascript/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-node-examples", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "dropbox": "file:../../../" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/javascript/node/team-as-user.js: -------------------------------------------------------------------------------- 1 | const prompt = require('prompt'); 2 | const { Dropbox } = require('dropbox'); // eslint-disable-line import/no-unresolved 3 | 4 | prompt.start(); 5 | 6 | prompt.get({ 7 | properties: { 8 | accessToken: { 9 | description: 'Please enter an API V2 team access token', 10 | }, 11 | userId: { 12 | description: 'Please enter the id of the user you would like to act as', 13 | }, 14 | }, 15 | }, (error, result) => { 16 | const dbx = new Dropbox({ accessToken: result.accessToken, selectUser: result.userId }); 17 | dbx.filesListFolder({ path: '' }) 18 | .then((response) => { 19 | console.log(response); 20 | }) 21 | .catch((err) => { 22 | console.log(err); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /examples/javascript/node/team.js: -------------------------------------------------------------------------------- 1 | const prompt = require('prompt'); 2 | const { Dropbox } = require('dropbox'); // eslint-disable-line import/no-unresolved 3 | 4 | prompt.start(); 5 | 6 | prompt.get({ 7 | properties: { 8 | accessToken: { 9 | description: 'Please enter an API V2 team access token', 10 | }, 11 | }, 12 | }, (error, result) => { 13 | const dbx = new Dropbox({ accessToken: result.accessToken }); 14 | dbx.teamDevicesListTeamDevices({}) 15 | .then((response) => { 16 | console.log('Devices', response); 17 | }) 18 | .catch((err) => { 19 | console.log(err); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /examples/javascript/node/upload.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const prompt = require('prompt'); 4 | const { Dropbox } = require('dropbox'); // eslint-disable-line import/no-unresolved 5 | 6 | prompt.start(); 7 | 8 | prompt.get({ 9 | properties: { 10 | accessToken: { 11 | description: 'Please enter an API V2 access token', 12 | }, 13 | }, 14 | }, (error, result) => { 15 | const dbx = new Dropbox({ accessToken: result.accessToken }); 16 | 17 | fs.readFile(path.join(__dirname, '/basic.js'), (err, contents) => { 18 | if (err) { 19 | console.log('Error: ', err); 20 | } 21 | 22 | // This uploads basic.js to the root of your dropbox 23 | dbx.filesUpload({ path: '/basic.js', contents }) 24 | .then((response) => { 25 | console.log(response); 26 | }) 27 | .catch((uploadErr) => { 28 | console.log(uploadErr); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /examples/javascript/pkce-browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dropbox JavaScript SDK 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 31 | 32 | 33 |
    34 |

    This example shows how to use PKCE in the browser

    35 | 40 | 41 | 45 |
    46 | 47 | 48 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /examples/javascript/server.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const express = require('express'); 4 | const rewrite = require('express-urlrewrite'); 5 | const rollup = require('rollup-endpoint'); 6 | 7 | const app = express(); 8 | const port = process.env.PORT || 8080; 9 | 10 | app.get( 11 | '/__build__/Dropbox-sdk.min.js', 12 | rollup.serve({ 13 | entry: path.resolve(__dirname, '../../dist/Dropbox-sdk.js'), 14 | }), 15 | ); 16 | 17 | fs.readdirSync(__dirname).forEach((file) => { 18 | if (fs.statSync(path.join(__dirname, file)).isDirectory()) { 19 | console.log(`Adding Route: /${file}/* from file /${file}/index.html`); 20 | app.use(rewrite(`/${file}/*`, `/${file}/index.html`)); 21 | } 22 | }); 23 | 24 | app.use(express.static(__dirname)); 25 | app.use('/__dist__', express.static(path.resolve(__dirname, '../../dist'))); 26 | 27 | app.listen(port, () => { 28 | console.log(`Express server listening on port ${port}`); 29 | }); 30 | -------------------------------------------------------------------------------- /examples/javascript/simple-backend/code_flow_example.js: -------------------------------------------------------------------------------- 1 | // Standalone example to demonstrate codeflow. 2 | // Start the server, hit localhost:3000 on the browser, and click through. 3 | // On the server logs, you should have the auth code, as well as the token 4 | // from exchanging it. This exchange is invisible to the app user 5 | 6 | const fetch = require('node-fetch'); 7 | const app = require('express')(); 8 | 9 | const hostname = 'localhost'; 10 | const port = 3000; 11 | 12 | const config = { 13 | fetch, 14 | clientId: 'APP_KEY_HERE', 15 | clientSecret: 'APP_SECRET_HERE', 16 | }; 17 | 18 | const { Dropbox } = require('dropbox'); // eslint-disable-line import/no-unresolved 19 | 20 | const dbx = new Dropbox(config); 21 | 22 | const redirectUri = `http://${hostname}:${port}/auth`; 23 | app.get('/', (req, res) => { 24 | dbx.auth.getAuthenticationUrl(redirectUri, null, 'code', 'offline', null, 'none', false) 25 | .then((authUrl) => { 26 | res.writeHead(302, { Location: authUrl }); 27 | res.end(); 28 | }); 29 | }); 30 | 31 | app.get('/auth', (req, res) => { // eslint-disable-line no-unused-vars 32 | const { code } = req.query; 33 | console.log(`code:${code}`); 34 | 35 | dbx.auth.getAccessTokenFromCode(redirectUri, code) 36 | .then((token) => { 37 | console.log(`Token Result:${JSON.stringify(token)}`); 38 | dbx.auth.setRefreshToken(token.result.refresh_token); 39 | dbx.usersGetCurrentAccount() 40 | .then((response) => { 41 | console.log('response', response); 42 | }) 43 | .catch((error) => { 44 | console.error(error); 45 | }); 46 | }) 47 | .catch((error) => { 48 | console.error(error); 49 | }); 50 | res.end(); 51 | }); 52 | 53 | app.listen(port); 54 | -------------------------------------------------------------------------------- /examples/javascript/simple-backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-backend", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "dropbox": "file:../../../" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/javascript/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 4 | line-height: 1.5; 5 | } 6 | .container { 7 | display: block; 8 | width: 90%; 9 | max-width: 800px; 10 | margin-left: auto; 11 | margin-right: auto; 12 | } 13 | .container.main { 14 | padding-top: 30px; 15 | } 16 | code, .code { 17 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 18 | color: #666; 19 | } 20 | .info { 21 | font-size: 13px; 22 | font-style: italic; 23 | color: #666; 24 | margin-top: 40px; 25 | } 26 | a { 27 | color: #007ee5; 28 | } 29 | input { 30 | border: 2px solid #007ee5; 31 | border-radius: 3px; 32 | padding: 8px; 33 | font-size: 16px; 34 | } 35 | .button, button { 36 | border-radius: 3px; 37 | background-color: #007ee5; 38 | border: none; 39 | color: #fff; 40 | font-size: 16px; 41 | padding: 10px 15px; 42 | text-decoration: none; 43 | } 44 | 45 | .page-header { 46 | background-color: #007ee5; 47 | padding: 10px 0 0 0; 48 | } 49 | .page-header .container { 50 | display: flex; 51 | flex-direction: column; 52 | justify-content: space-between; 53 | height: 150px; 54 | } 55 | .page-header a { 56 | color: #fff; 57 | text-decoration: none; 58 | } 59 | .page-header nav { 60 | display: flex; 61 | justify-content: space-between; 62 | align-items: center; 63 | } 64 | .page-header h1 { 65 | display: flex; 66 | align-items: center; 67 | color: #fff; 68 | font-size: 17px; 69 | font-weight: 200; 70 | } 71 | .page-header .logo { 72 | width: 100px; 73 | margin-right: 10px; 74 | } 75 | .page-header .view-source { 76 | font-weight: 200; 77 | font-size: 12px; 78 | } 79 | .page-header h2 { 80 | color: #fff; 81 | font-size: 18px; 82 | font-weight: normal; 83 | } 84 | -------------------------------------------------------------------------------- /examples/javascript/team-as-user/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dropbox JavaScript SDK 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 28 | 29 | 30 |
    31 |

    This example shows how to use the Dropbox class and the selectUser [docs] parameter, to retreive a Dropbox class that is acting as a specific user on the team.

    32 | 33 |
    34 | 35 | 36 | 37 |
    38 | 39 | 40 |
      41 | 42 |

      This endpoint requires a Dropbox Business API app key, see here for more details.

      43 |
      44 | 45 | 46 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /examples/javascript/team/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dropbox JavaScript SDK 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 28 | 29 | 30 |
      31 |

      This example shows how to use the Dropbox class and the Dropbox.teamDevicesListTeamDevices() method [docs].

      32 | 33 |
      34 | 35 | 36 |
      37 | 38 | 39 |
        40 | 41 |

        This endpoint requires a Dropbox Business API app key, see here for more details.

        42 |
        43 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /examples/javascript/upload/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dropbox JavaScript SDK 5 | 6 | 7 | 8 | 9 | 10 | 26 | 27 | 28 |
        29 |

        This example shows how to use the Dropbox.filesUpload() [docs] method to upload a file.

        30 | 31 |
        32 | 33 | 34 | 35 |
        36 | 37 | 38 |

        39 | 40 |

        To obtain an access token for quick testing, you can go to API Explorer click the "Get Token" button on the top right, copy the token it creates and then paste it here.

        41 |
        42 | 43 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /examples/javascript/utils.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | window.utils = { 3 | parseQueryString(str) { 4 | const ret = Object.create(null); 5 | 6 | if (typeof str !== 'string') { 7 | return ret; 8 | } 9 | 10 | str = str.trim().replace(/^(\?|#|&)/, ''); 11 | 12 | if (!str) { 13 | return ret; 14 | } 15 | 16 | str.split('&').forEach((param) => { 17 | const parts = param.replace(/\+/g, ' ').split('='); 18 | // Firefox (pre 40) decodes `%3D` to `=` 19 | // https://github.com/sindresorhus/query-string/pull/37 20 | let key = parts.shift(); 21 | let val = parts.length > 0 ? parts.join('=') : undefined; 22 | 23 | key = decodeURIComponent(key); 24 | 25 | // missing `=` should be `null`: 26 | // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters 27 | val = val === undefined ? null : decodeURIComponent(val); 28 | 29 | if (ret[key] === undefined) { 30 | ret[key] = val; 31 | } else if (Array.isArray(ret[key])) { 32 | ret[key].push(val); 33 | } else { 34 | ret[key] = [ret[key], val]; 35 | } 36 | }); 37 | 38 | return ret; 39 | }, 40 | }; 41 | }(window)); 42 | -------------------------------------------------------------------------------- /examples/typescript/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb-base"], 3 | "env": { 4 | "browser": false, 5 | "node": true 6 | }, 7 | "rules": { 8 | "func-names": 0, 9 | "no-param-reassign": 0, 10 | "import/prefer-default-export": 0, 11 | "no-console": 0, 12 | "import/no-extraneous-dependencies": 0 13 | }, 14 | "plugins": ["@typescript-eslint"], 15 | "parser": "@typescript-eslint/parser", 16 | "parserOptions": { "ecmaVersion": 6 } 17 | } -------------------------------------------------------------------------------- /examples/typescript/basic/basic.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["basic.ts"],"names":[],"mappings":";AAAA,qCAAkC;AAElC,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEjC,MAAM,CAAC,KAAK,EAAE,CAAC;AAEf,MAAM,CAAC,GAAG,CAAC;IACT,UAAU,EAAE;QACV,WAAW,EAAE;YACX,WAAW,EAAE,qCAAqC;SACnD;KACF;CACF,EAAE,CAAC,KAAU,EAAE,MAAW,EAAE,EAAE;IAC7B,MAAM,GAAG,GAAG,IAAI,iBAAO,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7D,GAAG,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;SAC9B,IAAI,CAAC,CAAC,QAAa,EAAE,EAAE;QACtB,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAAQ,EAAE,EAAE;QAClB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"} -------------------------------------------------------------------------------- /examples/typescript/basic/basic.ts: -------------------------------------------------------------------------------- 1 | import { Dropbox } from 'dropbox'; 2 | 3 | const prompt = require('prompt'); 4 | 5 | prompt.start(); 6 | 7 | prompt.get({ 8 | properties: { 9 | accessToken: { 10 | description: 'Please enter an API V2 access token', 11 | }, 12 | }, 13 | }, (error: any, result: any) => { 14 | const dbx = new Dropbox({ accessToken: result.accessToken }); 15 | dbx.filesListFolder({ path: '' }) 16 | .then((response: any) => { 17 | console.log(response); 18 | }) 19 | .catch((err: any) => { 20 | console.log(err); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /examples/typescript/basic/index.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports, "__esModule", { value: true }); 2 | const dropbox_1 = require("dropbox"); 3 | const prompt = require('prompt'); 4 | prompt.start(); 5 | prompt.get({ 6 | properties: { 7 | accessToken: { 8 | description: 'Please enter an API V2 access token', 9 | }, 10 | }, 11 | }, (error, result) => { 12 | const dbx = new dropbox_1.Dropbox({ accessToken: result.accessToken }); 13 | dbx.filesListFolder({ path: '' }) 14 | .then((response) => { 15 | console.log(response); 16 | }) 17 | .catch((err) => { 18 | console.log(err); 19 | }); 20 | }); 21 | //# sourceMappingURL=basic.js.map -------------------------------------------------------------------------------- /examples/typescript/download/download.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["download.ts"],"names":[],"mappings":";AAAA,qCAAkD,CAAC,qCAAqC;AACxF,yBAA0B;AAE1B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEjC,MAAM,CAAC,KAAK,EAAE,CAAC;AAEf,MAAM,CAAC,GAAG,CAAC;IACT,UAAU,EAAE;QACV,WAAW,EAAE;YACX,WAAW,EAAE,qCAAqC;SACnD;QACD,UAAU,EAAE;YACV,WAAW,EAAE,sCAAsC;SACpD;KACF;CACF,EAAE,CAAC,KAAU,EAAE,MAAW,EAAE,EAAE;IAC7B,MAAM,GAAG,GAAG,IAAI,iBAAO,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7D,GAAG,CAAC,wBAAwB,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;SACrD,IAAI,CAAC,CAAC,IAAS,EAAE,EAAE;QAClB,4DAA4D;QAC5D,+DAA+D;QAC/D,6BAA6B;QAC7B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAS,IAAK,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE;YAC/E,IAAI,GAAG,EAAE;gBAAE,MAAM,GAAG,CAAC;aAAE;YACvB,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,IAAI,SAAS,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAA0C,EAAE,EAAE;QACpD,MAAM,GAAG,CAAC;IACZ,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"} -------------------------------------------------------------------------------- /examples/typescript/download/download.ts: -------------------------------------------------------------------------------- 1 | import { Dropbox, Error, sharing } from 'dropbox'; // eslint-disable-line no-unused-vars 2 | import fs = require('fs'); 3 | 4 | const prompt = require('prompt'); 5 | 6 | prompt.start(); 7 | 8 | prompt.get({ 9 | properties: { 10 | accessToken: { 11 | description: 'Please enter an API V2 access token', 12 | }, 13 | sharedLink: { 14 | description: 'Please enter a shared link to a file', 15 | }, 16 | }, 17 | }, (error: any, result: any) => { 18 | const dbx = new Dropbox({ accessToken: result.accessToken }); 19 | dbx.sharingGetSharedLinkFile({ url: result.sharedLink }) 20 | .then((data: any) => { 21 | // Note: The fileBinary field is not part of the Dropbox SDK 22 | // specification, so it is not included in the TypeScript type. 23 | // It is injected by the SDK. 24 | fs.writeFile(data.result.name, ( data).result.fileBinary, { encoding: 'binary' }, (err) => { 25 | if (err) { throw err; } 26 | console.log(`File: ${data.result.name} saved.`); 27 | }); 28 | }) 29 | .catch((err: Error) => { 30 | throw err; 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /examples/typescript/download/index.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports, "__esModule", { value: true }); 2 | const dropbox_1 = require("dropbox"); // eslint-disable-line no-unused-vars 3 | const fs = require("fs"); 4 | const prompt = require('prompt'); 5 | prompt.start(); 6 | prompt.get({ 7 | properties: { 8 | accessToken: { 9 | description: 'Please enter an API V2 access token', 10 | }, 11 | sharedLink: { 12 | description: 'Please enter a shared link to a file', 13 | }, 14 | }, 15 | }, (error, result) => { 16 | const dbx = new dropbox_1.Dropbox({ accessToken: result.accessToken }); 17 | dbx.sharingGetSharedLinkFile({ url: result.sharedLink }) 18 | .then((data) => { 19 | // Note: The fileBinary field is not part of the Dropbox SDK 20 | // specification, so it is not included in the TypeScript type. 21 | // It is injected by the SDK. 22 | fs.writeFile(data.result.name, data.result.fileBinary, { encoding: 'binary' }, (err) => { 23 | if (err) { 24 | throw err; 25 | } 26 | console.log(`File: ${data.result.name} saved.`); 27 | }); 28 | }) 29 | .catch((err) => { 30 | throw err; 31 | }); 32 | }); 33 | //# sourceMappingURL=download.js.map -------------------------------------------------------------------------------- /examples/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dropbox-ts-examples", 3 | "version": "1.0.0", 4 | "description": "Dropbox TypeScript SDK Examples", 5 | "scripts": { 6 | "build": "tsc -p tsconfig.json" 7 | }, 8 | "contributors": [ 9 | "Brad Rogers ", 10 | "Andrew Lawson ", 11 | "John Vilk " 12 | ], 13 | "license": "MIT", 14 | "engines": { 15 | "node": ">=0.10" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/dropbox/dropbox-sdk-js.git" 20 | }, 21 | "dependencies": { 22 | "dropbox": "file:../../" 23 | }, 24 | "devDependencies": { 25 | "@types/express": "^4.17.11", 26 | "@types/node": "^14.14.25", 27 | "typescript": "^4.1.4" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/typescript/team-as-user/index.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports, "__esModule", { value: true }); 2 | const dropbox_1 = require("dropbox"); // eslint-disable-line no-unused-vars 3 | const prompt = require('prompt'); 4 | prompt.start(); 5 | prompt.get({ 6 | properties: { 7 | accessToken: { 8 | description: 'Please enter an API V2 team access token', 9 | }, 10 | userId: { 11 | description: 'Please enter the id of the user you would like to act as', 12 | }, 13 | }, 14 | }, (error, result) => { 15 | const dbx = new dropbox_1.Dropbox({ accessToken: result.accessToken, selectUser: result.userId }); 16 | dbx.filesListFolder({ path: '' }) 17 | .then((response) => { 18 | console.log(response); 19 | }) 20 | .catch((err) => { 21 | console.log(err); 22 | }); 23 | }); 24 | //# sourceMappingURL=team-as-user.js.map -------------------------------------------------------------------------------- /examples/typescript/team-as-user/team-as-user.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["team-as-user.ts"],"names":[],"mappings":";AAAA,qCAAgD,CAAC,qCAAqC;AAEtF,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEjC,MAAM,CAAC,KAAK,EAAE,CAAC;AAEf,MAAM,CAAC,GAAG,CAAC;IACT,UAAU,EAAE;QACV,WAAW,EAAE;YACX,WAAW,EAAE,0CAA0C;SACxD;QACD,MAAM,EAAE;YACN,WAAW,EAAE,0DAA0D;SACxE;KACF;CACF,EAAE,CAAC,KAAU,EAAE,MAAW,EAAE,EAAE;IAC7B,MAAM,GAAG,GAAG,IAAI,iBAAO,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACxF,GAAG,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;SAC9B,IAAI,CAAC,CAAC,QAAa,EAAE,EAAE;QACtB,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAAiC,EAAE,EAAE;QAC3C,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"} -------------------------------------------------------------------------------- /examples/typescript/team-as-user/team-as-user.ts: -------------------------------------------------------------------------------- 1 | import { Dropbox, Error, files } from 'dropbox'; // eslint-disable-line no-unused-vars 2 | 3 | const prompt = require('prompt'); 4 | 5 | prompt.start(); 6 | 7 | prompt.get({ 8 | properties: { 9 | accessToken: { 10 | description: 'Please enter an API V2 team access token', 11 | }, 12 | userId: { 13 | description: 'Please enter the id of the user you would like to act as', 14 | }, 15 | }, 16 | }, (error: any, result: any) => { 17 | const dbx = new Dropbox({ accessToken: result.accessToken, selectUser: result.userId }); 18 | dbx.filesListFolder({ path: '' }) 19 | .then((response: any) => { 20 | console.log(response); 21 | }) 22 | .catch((err: Error) => { 23 | console.log(err); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /examples/typescript/team/index.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports, "__esModule", { value: true }); 2 | const dropbox_1 = require("dropbox"); // eslint-disable-line no-unused-vars 3 | const prompt = require('prompt'); 4 | prompt.start(); 5 | prompt.get({ 6 | properties: { 7 | accessToken: { 8 | description: 'Please enter an API V2 team access token', 9 | }, 10 | }, 11 | }, (error, result) => { 12 | const dbx = new dropbox_1.Dropbox({ accessToken: result.accessToken }); 13 | dbx.teamDevicesListTeamDevices({}) 14 | .then((response) => { 15 | console.log('Devices', response); 16 | }) 17 | .catch((err) => { 18 | console.log(err); 19 | }); 20 | }); 21 | //# sourceMappingURL=team.js.map -------------------------------------------------------------------------------- /examples/typescript/team/team.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["team.ts"],"names":[],"mappings":";AAAA,qCAA+C,CAAC,qCAAqC;AAErF,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEjC,MAAM,CAAC,KAAK,EAAE,CAAC;AAEf,MAAM,CAAC,GAAG,CAAC;IACT,UAAU,EAAE;QACV,WAAW,EAAE;YACX,WAAW,EAAE,0CAA0C;SACxD;KACF;CACF,EAAE,CAAC,KAAU,EAAE,MAAW,EAAE,EAAE;IAC7B,MAAM,GAAG,GAAG,IAAI,iBAAO,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7D,GAAG,CAAC,0BAA0B,CAAC,EAAE,CAAC;SAC/B,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;QACjB,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAAqC,EAAE,EAAE;QAC/C,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"} -------------------------------------------------------------------------------- /examples/typescript/team/team.ts: -------------------------------------------------------------------------------- 1 | import { Dropbox, Error, team } from 'dropbox'; // eslint-disable-line no-unused-vars 2 | 3 | const prompt = require('prompt'); 4 | 5 | prompt.start(); 6 | 7 | prompt.get({ 8 | properties: { 9 | accessToken: { 10 | description: 'Please enter an API V2 team access token', 11 | }, 12 | }, 13 | }, (error: any, result: any) => { 14 | const dbx = new Dropbox({ accessToken: result.accessToken }); 15 | dbx.teamDevicesListTeamDevices({}) 16 | .then((response) => { 17 | console.log('Devices', response); 18 | }) 19 | .catch((err: Error) => { 20 | console.log(err); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /examples/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2015", 5 | "lib": ["dom", "es2015", "es2016", "es2017"], 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "noFallthroughCasesInSwitch": true, 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noImplicitUseStrict": true, 14 | "noUnusedLocals": true, 15 | "outDir": "dist" 16 | }, 17 | "include": ["**/*.ts", "node/**/*.ts"], 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /examples/typescript/upload/index.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports, "__esModule", { value: true }); 2 | const dropbox_1 = require("dropbox"); // eslint-disable-line no-unused-vars 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const prompt = require('prompt'); 6 | prompt.start(); 7 | prompt.get({ 8 | properties: { 9 | accessToken: { 10 | description: 'Please enter an API V2 access token', 11 | }, 12 | }, 13 | }, (error, result) => { 14 | const dbx = new dropbox_1.Dropbox({ accessToken: result.accessToken }); 15 | fs.readFile(path.join(__dirname, '/basic.js'), 'utf8', (err, contents) => { 16 | if (err) { 17 | console.log('Error: ', err); 18 | } 19 | // This uploads basic.js to the root of your dropbox 20 | dbx.filesUpload({ path: '/basic.js', contents }) 21 | .then((response) => { 22 | console.log(response); 23 | }) 24 | .catch((uploadErr) => { 25 | console.log(uploadErr); 26 | }); 27 | }); 28 | }); 29 | //# sourceMappingURL=upload.js.map -------------------------------------------------------------------------------- /examples/typescript/upload/upload.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["upload.ts"],"names":[],"mappings":";AAAA,qCAAgD,CAAC,qCAAqC;AACtF,yBAA0B;AAC1B,6BAA8B;AAE9B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEjC,MAAM,CAAC,KAAK,EAAE,CAAC;AAEf,MAAM,CAAC,GAAG,CAAC;IACT,UAAU,EAAE;QACV,WAAW,EAAE;YACX,WAAW,EAAE,qCAAqC;SACnD;KACF;CACF,EAAE,CAAC,KAAU,EAAE,MAAW,EAAE,EAAE;IAC7B,MAAM,GAAG,GAAG,IAAI,iBAAO,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAE7D,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE;QACvE,IAAI,GAAG,EAAE;YACP,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;SAC7B;QAED,oDAAoD;QACpD,GAAG,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;aAC7C,IAAI,CAAC,CAAC,QAAa,EAAE,EAAE;YACtB,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxB,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,SAAmC,EAAE,EAAE;YAC7C,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} -------------------------------------------------------------------------------- /examples/typescript/upload/upload.ts: -------------------------------------------------------------------------------- 1 | import { Dropbox, Error, files } from 'dropbox'; // eslint-disable-line no-unused-vars 2 | import fs = require('fs'); 3 | import path = require('path'); 4 | 5 | const prompt = require('prompt'); 6 | 7 | prompt.start(); 8 | 9 | prompt.get({ 10 | properties: { 11 | accessToken: { 12 | description: 'Please enter an API V2 access token', 13 | }, 14 | }, 15 | }, (error: any, result: any) => { 16 | const dbx = new Dropbox({ accessToken: result.accessToken }); 17 | 18 | fs.readFile(path.join(__dirname, '/basic.js'), 'utf8', (err, contents) => { 19 | if (err) { 20 | console.log('Error: ', err); 21 | } 22 | 23 | // This uploads basic.js to the root of your dropbox 24 | dbx.filesUpload({ path: '/basic.js', contents }) 25 | .then((response: any) => { 26 | console.log(response); 27 | }) 28 | .catch((uploadErr: Error) => { 29 | console.log(uploadErr); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /generator/generate_routes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | 4 | import argparse 5 | import glob 6 | import json 7 | import os 8 | import subprocess 9 | import sys 10 | 11 | cmdline_desc = """\ 12 | Runs Stone to generate JS routes for the Dropbox client. 13 | """ 14 | 15 | _cmdline_parser = argparse.ArgumentParser(description=cmdline_desc) 16 | _cmdline_parser.add_argument( 17 | '-v', 18 | '--verbose', 19 | action='store_true', 20 | help='Print debugging statements.', 21 | ) 22 | _cmdline_parser.add_argument( 23 | 'spec', 24 | nargs='*', 25 | type=str, 26 | help='Path to API specifications. Each must have a .stone extension.', 27 | ) 28 | _cmdline_parser.add_argument( 29 | '-s', 30 | '--stone', 31 | type=str, 32 | help='Path to clone of stone repository.', 33 | ) 34 | 35 | 36 | def main(): 37 | """The entry point for the program.""" 38 | 39 | args = _cmdline_parser.parse_args() 40 | verbose = args.verbose 41 | 42 | if args.spec: 43 | specs = args.spec 44 | else: 45 | # If no specs were specified, default to the spec submodule. 46 | specs = glob.glob('dropbox-api-spec/*.stone') # Arbitrary sorting 47 | specs.sort() 48 | 49 | specs = [os.path.join(os.getcwd(), s) for s in specs] 50 | 51 | stone_path = os.path.abspath('stone') 52 | if args.stone: 53 | stone_path = args.stone 54 | 55 | dropbox_pkg_path = os.path.abspath( 56 | os.path.join(os.path.dirname(sys.argv[0]), '../lib')) 57 | if verbose: 58 | print('Dropbox package path: %s' % dropbox_pkg_path) 59 | 60 | typescript_template_path = os.path.abspath( 61 | os.path.join(os.path.dirname(sys.argv[0]), 'typescript')) 62 | if verbose: 63 | print('TypeScript template path: %s' % typescript_template_path) 64 | 65 | types_template_path = os.path.abspath( 66 | os.path.join(os.path.dirname(sys.argv[0]), '../types')) 67 | if verbose: 68 | print('Types template path: %s' % types_template_path) 69 | 70 | upload_arg = { 71 | "match": ["style", "upload"], 72 | "arg_name": "contents", 73 | "arg_type": "Object", 74 | "arg_docstring": "The file contents to be uploaded." 75 | } 76 | 77 | if verbose: 78 | print('Generating JS types') 79 | subprocess.check_output( 80 | (['python3', '-m', 'stone.cli', 'js_types', dropbox_pkg_path] + 81 | specs + ['-b', 'team'] + ['-a', 'host', '-a', 'style', '-a', 'auth'] + 82 | ['--', 'types.js', '-e', json.dumps(upload_arg)]), 83 | cwd=stone_path) 84 | 85 | if verbose: 86 | print('Generating JS client routes for user routes') 87 | o = subprocess.check_output( 88 | (['python3', '-m', 'stone.cli', 'js_client', dropbox_pkg_path] + 89 | specs + ['-a', 'host', '-a', 'style', '-a', 'auth', '-a', 'scope'] + 90 | ['--', 'routes.js', '-c', 'Dropbox', '--wrap-response-in', 'DropboxResponse', '--wrap-error-in', 'DropboxResponseError', '-a', 'scope']), 91 | cwd=stone_path) 92 | if verbose: 93 | print(o) 94 | 95 | if verbose: 96 | print('Generating TSD types') 97 | subprocess.check_output( 98 | (['python3', '-m', 'stone.cli', 'tsd_types', typescript_template_path] + 99 | specs + ['-b', 'team'] + ['-a', 'host', '-a', 'style'] + 100 | ['--', 'dropbox_types.d.tstemplate', 'dropbox_types.d.ts', '-e', json.dumps(upload_arg), '--export-namespaces']), 101 | cwd=stone_path) 102 | 103 | if verbose: 104 | print('Generating TSD client routes for user routes') 105 | subprocess.check_output( 106 | (['python3', '-m', 'stone.cli', 'tsd_client', typescript_template_path] + 107 | specs + ['-a', 'host', '-a', 'style', '-a', 'scope'] + 108 | ['--', 'index.d.tstemplate', 'index.d.ts', '--wrap-response-in', 'DropboxResponse', '--wrap-error-in', 'DropboxResponseError', '--import-namespaces', '--types-file', './dropbox_types', '-a', 'scope']), 109 | cwd=stone_path) 110 | 111 | typescript_generated_files = glob.glob('typescript/*.d.ts') 112 | typescript_generated_files.sort() 113 | typescript_generated_files = [os.path.join(os.getcwd(), f) for f in typescript_generated_files] 114 | if verbose: 115 | print('TypeScript generated files: %s' % typescript_generated_files) 116 | 117 | if verbose: 118 | print('Moving TSD routes and types to types/') 119 | for file in typescript_generated_files: 120 | subprocess.check_output( 121 | (['mv', file , types_template_path]), 122 | cwd=typescript_template_path 123 | ) 124 | 125 | if __name__ == '__main__': 126 | main() 127 | -------------------------------------------------------------------------------- /generator/typescript/dropbox_types.d.tstemplate: -------------------------------------------------------------------------------- 1 | // Auto-generated by Stone, do not modify. 2 | 3 | /*TYPES*/ 4 | -------------------------------------------------------------------------------- /generator/typescript/index.d.tstemplate: -------------------------------------------------------------------------------- 1 | // Auto-generated by Stone, do not modify. 2 | 3 | /*IMPORT*/ 4 | export * from './dropbox_types'; 5 | 6 | export interface DropboxAuthOptions { 7 | // An access token for making authenticated requests. 8 | accessToken?: string; 9 | // The time at which the access token expires. 10 | accessTokenExpiresAt?: Date; 11 | // A refresh token for retrieving access tokens 12 | refreshToken?: string; 13 | // The client id for your app. Used to create authentication URL. 14 | clientId?: string; 15 | // The client secret for your app. Used for refresh and token exchange. 16 | clientSecret?: string; 17 | // The fetch library for making requests. 18 | fetch?: Function; 19 | // A custom domain to use when making api requests. This should only be used for testing as scaffolding to avoid making network requests. 20 | domain?: string; 21 | // A custom delimiter to use when separating domain from subdomain. This should only be used for testing as scaffolding. 22 | domainDelimiter?: string; 23 | // An object (in the form of header: value) designed to set custom headers to use during a request. 24 | customHeaders?: object; 25 | // Whether request data is sent on body or as URL params. Defaults to false. 26 | dataOnBody?: boolean; 27 | } 28 | 29 | export class DropboxAuth { 30 | /** 31 | * The DropboxAuth class that provides methods to manage, acquire, and refresh tokens. 32 | */ 33 | constructor(); 34 | 35 | /** 36 | * The DropboxAuth class that provides methods to manage, acquire, and refresh tokens. 37 | */ 38 | constructor(options: DropboxAuthOptions); 39 | 40 | /** 41 | * Get the access token 42 | * @returns {String} Access token 43 | */ 44 | getAccessToken(): string; 45 | 46 | /** 47 | * Get an OAuth2 access token from an OAuth2 Code. 48 | * @param redirectUri A URL to redirect the user to after authenticating. 49 | * This must be added to your app through the admin interface. 50 | * @param code An OAuth2 code. 51 | * @returns {Object} An object containing the token and related info (if applicable) 52 | */ 53 | getAccessTokenFromCode(redirectUri: string, code: string): Promise>; 54 | 55 | /** 56 | * Get a URL that can be used to authenticate users for the Dropbox API. 57 | * @arg {String} redirectUri - A URL to redirect the user to after 58 | * authenticating. This must be added to your app through the admin interface. 59 | * @arg {String} [state] - State that will be returned in the redirect URL to help 60 | * prevent cross site scripting attacks. 61 | * @arg {String} [authType] - auth type, defaults to 'token', other option is 'code' 62 | * @arg {String} [tokenAccessType] - type of token to request. From the following: 63 | * null - creates a token with the app default (either legacy or online) 64 | * legacy - creates one long-lived token with no expiration 65 | * online - create one short-lived token with an expiration 66 | * offline - create one short-lived token with an expiration with a refresh token 67 | * @arg {Array} [scope] - scopes to request for the grant 68 | * @arg {String} [includeGrantedScopes] - whether or not to include previously granted scopes. 69 | * From the following: 70 | * user - include user scopes in the grant 71 | * team - include team scopes in the grant 72 | * Note: if this user has never linked the app, include_granted_scopes must be None 73 | * @arg {boolean} [usePKCE] - Whether or not to use Sha256 based PKCE. PKCE should be only use on 74 | * client apps which doesn't call your server. It is less secure than non-PKCE flow but 75 | * can be used if you are unable to safely retrieve your app secret 76 | * @returns {Promise} - Url to send user to for Dropbox API authentication 77 | * returned in a promise 78 | */ 79 | getAuthenticationUrl(redirectUri: string, state?: string, authType?: 'token' | 'code', tokenAccessType?: null | 'legacy' | 'offline' | 'online', scope?: Array, includeGrantedScopes?: 'none' | 'user' | 'team', usePKCE?: boolean): Promise; 80 | 81 | /** 82 | * Get the client id 83 | * @returns {String} Client id 84 | */ 85 | getClientId(): string; 86 | 87 | /** 88 | * Set the access token used to authenticate requests to the API. 89 | * @param accessToken An access token. 90 | */ 91 | setAccessToken(accessToken: string): void; 92 | 93 | /** 94 | * Set the client id, which is used to help gain an access token. 95 | * @param clientId Your app's client ID. 96 | */ 97 | setClientId(clientId: string): void; 98 | 99 | /** 100 | * Set the client secret 101 | * @param clientSecret Your app's client secret. 102 | */ 103 | setClientSecret(clientSecret: string): void; 104 | 105 | /** 106 | * Sets the refresh token 107 | * @param refreshToken - A refresh token 108 | */ 109 | setRefreshToken(refreshToken: string): void; 110 | 111 | /** 112 | * Gets the refresh token 113 | * @returns {String} Refresh token 114 | */ 115 | getRefreshToken(): string; 116 | 117 | /** 118 | * Sets the access token's expiration date 119 | * @param accessTokenExpiresAt - new expiration date 120 | */ 121 | setAccessTokenExpiresAt(accessTokenExpiresAt: Date): void; 122 | 123 | /** 124 | * Gets the access token's expiration date 125 | * @returns {Date} date of token expiration 126 | */ 127 | getAccessTokenExpiresAt(): Date; 128 | 129 | /** 130 | * Sets the code verifier for PKCE flow 131 | * @param {String} codeVerifier - new code verifier 132 | */ 133 | setCodeVerifier(codeVerifier: string): void; 134 | 135 | /** 136 | * Gets the code verifier for PKCE flow 137 | * @returns {String} - code verifier for PKCE 138 | */ 139 | getCodeVerifier(): string; 140 | 141 | /** 142 | * Checks if a token is needed, can be refreshed and if the token is expired. 143 | * If so, attempts to refresh access token 144 | * @returns {Promise<*>} 145 | */ 146 | checkAndRefreshAccessToken(): void; 147 | 148 | /** 149 | * Refreshes the access token using the refresh token, if available 150 | * @arg {List} scope - a subset of scopes from the original 151 | * refresh to acquire with an access token 152 | * @returns {Promise<*>} 153 | */ 154 | refreshAccessToken(scope?: Array): void; 155 | 156 | } 157 | 158 | export interface DropboxOptions { 159 | // Select user is only used for team functionality. It specifies which user the team access token should be acting as. 160 | selectUser?: string; 161 | // Select admin is only used by team functionality. It specifies which team admin the team access token should be acting as. 162 | selectAdmin?: string; 163 | // Root path to access other namespaces. Use to access team folders for example 164 | pathRoot?: string; 165 | // The DropboxAuth object used to authenticate requests. If this is set, the remaining parameters will be ignored. 166 | auth?: DropboxAuth | null; 167 | // An access token for making authenticated requests. 168 | accessToken?: string; 169 | // The time at which the access token expires. 170 | accessTokenExpiresAt?: Date; 171 | // A refresh token for retrieving access tokens 172 | refreshToken?: string; 173 | // The client id for your app. Used to create authentication URL. 174 | clientId?: string; 175 | // The client secret for your app. Used for refresh and token exchange. 176 | clientSecret?: string; 177 | // The fetch library for making requests. 178 | fetch?: Function; 179 | // A custom domain to use when making api requests. This should only be used for testing as scaffolding to avoid making network requests. 180 | domain?: string; 181 | // A custom delimiter to use when separating domain subdomain. This should only be used for testing as scaffolding. 182 | domainDelimiter?: string; 183 | // An object (in the form of header: value) designed to set custom headers to use during a request. 184 | customHeaders?: object; 185 | } 186 | 187 | export class DropboxResponseError { 188 | /** 189 | * The response class of HTTP errors from API calls using the Dropbox SDK. 190 | */ 191 | constructor(status: number, headers: any, error: T); 192 | 193 | /** 194 | * HTTP Status code of the call 195 | */ 196 | status: number; 197 | 198 | /** 199 | * Headers returned from the call. Set as any to support both node and browser. 200 | */ 201 | headers: any; 202 | 203 | /** 204 | * Serialized Error of the call 205 | */ 206 | error: T; 207 | } 208 | 209 | export class DropboxResponse { 210 | /** 211 | * The response class of all successful API calls using the Dropbox SDK. 212 | */ 213 | constructor(status: number, headers: any, result: T); 214 | 215 | /** 216 | * HTTP Status code of the call 217 | */ 218 | status: number; 219 | 220 | /** 221 | * Headers returned from the call. Set as any to support both node and browser. 222 | */ 223 | headers: any; 224 | 225 | /** 226 | * Serialized Result of the call 227 | */ 228 | result: T; 229 | } 230 | 231 | export class Dropbox { 232 | /** 233 | * The Dropbox SDK class that provides methods to read, write and 234 | * create files or folders in a user or team's Dropbox. 235 | */ 236 | constructor(); 237 | 238 | /** 239 | * The Dropbox SDK class that provides methods to read, write and 240 | * create files or folders in a user or team's Dropbox. 241 | */ 242 | constructor(options: DropboxOptions); 243 | /*ROUTES*/ 244 | } 245 | 246 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export { default as Dropbox } from './src/dropbox.js'; 2 | export { default as DropboxAuth } from './src/auth.js'; 3 | export { DropboxResponse } from './src/response.js'; 4 | export { DropboxResponseError } from './src/error.js'; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dropbox", 3 | "version": "10.34.0", 4 | "registry": "npm", 5 | "description": "The Dropbox JavaScript SDK is a lightweight, promise based interface to the Dropbox v2 API that works in both nodejs and browser environments.", 6 | "main": "cjs/index.js", 7 | "browser": "dist/Dropbox-sdk.min.js", 8 | "typings": "types/index", 9 | "types": "types/index", 10 | "module": "es/index.js", 11 | "jsnext:main": "es/index.js", 12 | "scripts": { 13 | "build:es": "cross-env BABEL_ENV=es babel src -d es/src && cross-env BABEL_ENV=es babel lib -d es/lib && cross-env BABEL_ENV=es babel index.js -d es", 14 | "build:cjs": "cross-env BABEL_ENV=commonjs babel src -d cjs/src && cross-env BABEL_ENV=commonjs babel lib -d cjs/lib && cross-env BABEL_ENV=commonjs babel index.js -d cjs", 15 | "build:umd": "cross-env BABEL_ENV=es BUNDLE_TYPE=normal rollup -c -i index.js -o dist/Dropbox-sdk.js -n Dropbox", 16 | "build:umd:min": "cross-env BABEL_ENV=es BUNDLE_TYPE=minified rollup -c -i index.js -o dist/Dropbox-sdk.min.js -n Dropbox", 17 | "build": "npm run build:es && npm run build:cjs && npm run build:umd && npm run build:umd:min", 18 | "lint": "eslint --ext .js,.jsx,.ts .", 19 | "lint-fix": "eslint --fix --ext .js,.jsx,.ts .", 20 | "test": "npm run test:typescript && npm run test:unit", 21 | "test:typescript": "tsc --build test/types", 22 | "test:integration": "mocha --timeout 10000 --require @babel/register test/integration/**/*.js", 23 | "test:unit": "mocha --require @babel/register test/unit/**/*.js", 24 | "test:build": "mocha --timeout 100000 --require @babel/register test/build/*.js", 25 | "report": "nyc report --reporter=lcov --reporter=text", 26 | "clean": "rm -rf dist es cjs", 27 | "generate-docs": "jsdoc -c ./.jsdoc.json", 28 | "coverage:unit": "cross-env BABEL_ENV=coverage nyc --reporter=lcov npm run test:unit", 29 | "coverage:integration": "cross-env BABEL_ENV=coverage nyc --reporter=lcov npm run test:integration" 30 | }, 31 | "files": [ 32 | "*.md", 33 | "LICENSE", 34 | "index.js", 35 | "src", 36 | "lib", 37 | "types", 38 | "dist", 39 | "es", 40 | "cjs" 41 | ], 42 | "keywords": [ 43 | "dropbox", 44 | "files", 45 | "sync", 46 | "sdk", 47 | "client" 48 | ], 49 | "homepage": "https://github.com/dropbox/dropbox-sdk-js#readme", 50 | "repository": { 51 | "type": "git", 52 | "url": "git+https://github.com/dropbox/dropbox-sdk-js.git" 53 | }, 54 | "bugs": { 55 | "url": "https://github.com/dropbox/dropbox-sdk-js/issues" 56 | }, 57 | "license": "MIT", 58 | "directories": { 59 | "example": "examples", 60 | "test": "test" 61 | }, 62 | "engines": { 63 | "node": ">=0.10.3" 64 | }, 65 | "contributors": [ 66 | "Brad Rogers ", 67 | "Andrew Lawson ", 68 | "James Sidhu ", 69 | "John Vilk ", 70 | "Steve Klebanoff ", 71 | "Bohdan Tereta " 72 | ], 73 | "devDependencies": { 74 | "@babel/cli": "^7.11.6", 75 | "@babel/core": "^7.11.6", 76 | "@babel/preset-env": "^7.11.5", 77 | "@babel/register": "^7.11.5", 78 | "@testing-library/dom": "^7.24.5", 79 | "@types/node": "^14.11.2", 80 | "@types/node-fetch": "^2.5.7", 81 | "@typescript-eslint/eslint-plugin": "^4.0.0", 82 | "@typescript-eslint/parser": "^3.10.1", 83 | "babel-plugin-istanbul": "^6.0.0", 84 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", 85 | "chai": "^4.2.0", 86 | "chai-as-promised": "^7.1.1", 87 | "cross-env": "^7.0.2", 88 | "eslint": "^7.9.0", 89 | "eslint-config-airbnb-base": "^14.2.0", 90 | "eslint-plugin-import": "^2.22.0", 91 | "express": "^4.17.1", 92 | "express-urlrewrite": "^1.3.0", 93 | "gh-pages": "^3.1.0", 94 | "ink-docstrap": "^1.3.2", 95 | "jsdoc": "^3.6.6", 96 | "jsdom": "^16.4.0", 97 | "mocha": "^8.1.3", 98 | "nyc": "^15.1.0", 99 | "prompt": "^1.0.0", 100 | "rollup": "^2.28.2", 101 | "rollup-endpoint": "^0.2.2", 102 | "rollup-plugin-babel": "^4.4.0", 103 | "rollup-plugin-terser": "^7.0.2", 104 | "sinon": "^9.0.3", 105 | "typescript": "^4.0.3" 106 | }, 107 | "peerDependencies": { 108 | "@types/node-fetch": "^2.5.7" 109 | }, 110 | "dependencies": { 111 | "node-fetch": "^2.6.1" 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import { terser } from 'rollup-plugin-terser'; 3 | 4 | /** 5 | * We use rollup for the UMD build. 6 | * Rollup is only needed for this because UMD is the 7 | * only build configuration that requires us to bundle 8 | * all assets into a single file. 9 | * 10 | * We also only publish a minified bundle for UMD. 11 | * We use a flag of BUNDLE_TYPE set by cross-env to control 12 | * whether or not we minify the bundle. We use terser to 13 | * do the actual minification and it is only added when the 14 | * BUNDLE_TYPE = minified (BUNDLE_TYPE=normal for basic UMD) 15 | */ 16 | 17 | const config = { 18 | output: { 19 | format: 'umd', 20 | sourcemap: (process.env.BUNDLE_TYPE !== 'minified'), 21 | globals: { 22 | crypto: 'crypto', 23 | }, 24 | }, 25 | external: ['es6-promise/auto', 'crypto'], 26 | plugins: [ 27 | babel(), 28 | ], 29 | }; 30 | 31 | if (process.env.BUNDLE_TYPE === 'minified') { 32 | config.plugins.push( 33 | terser({ 34 | compress: { 35 | pure_getters: true, 36 | unsafe: true, 37 | unsafe_comps: true, 38 | warnings: false, 39 | }, 40 | }), 41 | ); 42 | } 43 | 44 | export default config; 45 | -------------------------------------------------------------------------------- /scripts/release_note_generator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | last_version=$(git tag --sort v:refname | tail -n 2 | head -n 1) 4 | echo "Getting commit history since $last_version" 5 | num_commits=$(git rev-list --count $last_version..HEAD) 6 | echo "Found $num_commits commits since last revision" 7 | git_log=$(git log -n $num_commits --pretty="format:* %s %n") 8 | linked_log=$(echo "Release Notes: \n\n$git_log" | sed -e 's/#\([0-9]*\)/[#\1](https:\/\/github.com\/dropbox\/dropbox-sdk-js\/pull\/\1)/g') 9 | echo "\n\n$linked_log" -------------------------------------------------------------------------------- /scripts/update_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z $1 ]; then 4 | echo "error: $0 needs a version number as argument."; 5 | exit 1 6 | else 7 | set -ex 8 | NEW_VERSION=$1 9 | 10 | git checkout main 11 | git reset --hard HEAD 12 | 13 | git tag "v${NEW_VERSION}" -m "${NEW_VERSION} release" 14 | 15 | git push origin 16 | git push origin --tags 17 | fi -------------------------------------------------------------------------------- /src/auth.js: -------------------------------------------------------------------------------- 1 | import { 2 | getTokenExpiresAtDate, 3 | isBrowserEnv, 4 | createBrowserSafeString, 5 | OAuth2AuthorizationUrl, 6 | OAuth2TokenUrl, 7 | isWorkerEnv, 8 | } from './utils.js'; 9 | import { parseResponse } from './response.js'; 10 | 11 | let fetch; 12 | let crypto; 13 | let Encoder; 14 | 15 | // Expiration is 300 seconds but needs to be in milliseconds for Date object 16 | const TokenExpirationBuffer = 300 * 1000; 17 | const PKCELength = 128; 18 | const TokenAccessTypes = ['legacy', 'offline', 'online']; 19 | const GrantTypes = ['code', 'token']; 20 | const IncludeGrantedScopes = ['none', 'user', 'team']; 21 | 22 | /** 23 | * @class DropboxAuth 24 | * @classdesc The DropboxAuth class that provides methods to manage, acquire, and refresh tokens. 25 | * @arg {Object} options 26 | * @arg {Function} [options.fetch] - fetch library for making requests. 27 | * @arg {String} [options.accessToken] - An access token for making authenticated 28 | * requests. 29 | * @arg {Date} [options.AccessTokenExpiresAt] - Date of the current access token's 30 | * expiration (if available) 31 | * @arg {String} [options.refreshToken] - A refresh token for retrieving access tokens 32 | * @arg {String} [options.clientId] - The client id for your app. Used to create 33 | * authentication URL. 34 | * @arg {String} [options.clientSecret] - The client secret for your app. Used to create 35 | * authentication URL and refresh access tokens. 36 | * @arg {String} [options.domain] - A custom domain to use when making api requests. This 37 | * should only be used for testing as scaffolding to avoid making network requests. 38 | * @arg {String} [options.domainDelimiter] - A custom delimiter to use when separating domain from 39 | * subdomain. This should only be used for testing as scaffolding. 40 | * @arg {Object} [options.customHeaders] - An object (in the form of header: value) designed to set 41 | * custom headers to use during a request. 42 | * @arg {Boolean} [options.dataOnBody] - Whether request data is sent on body or as URL params. 43 | * Defaults to false. 44 | */ 45 | export default class DropboxAuth { 46 | constructor(options) { 47 | options = options || {}; 48 | 49 | if (isBrowserEnv()) { 50 | fetch = window.fetch.bind(window); 51 | crypto = window.crypto || window.msCrypto; // for IE11 52 | } else if (isWorkerEnv()) { 53 | /* eslint-disable no-restricted-globals */ 54 | fetch = self.fetch.bind(self); 55 | crypto = self.crypto; 56 | /* eslint-enable no-restricted-globals */ 57 | } else { 58 | fetch = require('node-fetch'); // eslint-disable-line global-require 59 | crypto = require('crypto'); // eslint-disable-line global-require 60 | } 61 | 62 | if (typeof TextEncoder === 'undefined') { 63 | Encoder = require('util').TextEncoder; // eslint-disable-line global-require 64 | } else { 65 | Encoder = TextEncoder; 66 | } 67 | 68 | this.fetch = options.fetch || fetch; 69 | this.accessToken = options.accessToken; 70 | this.accessTokenExpiresAt = options.accessTokenExpiresAt; 71 | this.refreshToken = options.refreshToken; 72 | this.clientId = options.clientId; 73 | this.clientSecret = options.clientSecret; 74 | 75 | this.domain = options.domain; 76 | this.domainDelimiter = options.domainDelimiter; 77 | this.customHeaders = options.customHeaders; 78 | this.dataOnBody = options.dataOnBody; 79 | } 80 | 81 | /** 82 | * Set the access token used to authenticate requests to the API. 83 | * @arg {String} accessToken - An access token 84 | * @returns {undefined} 85 | */ 86 | setAccessToken(accessToken) { 87 | this.accessToken = accessToken; 88 | } 89 | 90 | /** 91 | * Get the access token 92 | * @returns {String} Access token 93 | */ 94 | getAccessToken() { 95 | return this.accessToken; 96 | } 97 | 98 | /** 99 | * Set the client id, which is used to help gain an access token. 100 | * @arg {String} clientId - Your apps client id 101 | * @returns {undefined} 102 | */ 103 | setClientId(clientId) { 104 | this.clientId = clientId; 105 | } 106 | 107 | /** 108 | * Get the client id 109 | * @returns {String} Client id 110 | */ 111 | getClientId() { 112 | return this.clientId; 113 | } 114 | 115 | /** 116 | * Set the client secret 117 | * @arg {String} clientSecret - Your app's client secret 118 | * @returns {undefined} 119 | */ 120 | setClientSecret(clientSecret) { 121 | this.clientSecret = clientSecret; 122 | } 123 | 124 | /** 125 | * Get the client secret 126 | * @returns {String} Client secret 127 | */ 128 | getClientSecret() { 129 | return this.clientSecret; 130 | } 131 | 132 | /** 133 | * Gets the refresh token 134 | * @returns {String} Refresh token 135 | */ 136 | getRefreshToken() { 137 | return this.refreshToken; 138 | } 139 | 140 | /** 141 | * Sets the refresh token 142 | * @param refreshToken - A refresh token 143 | */ 144 | setRefreshToken(refreshToken) { 145 | this.refreshToken = refreshToken; 146 | } 147 | 148 | /** 149 | * Gets the access token's expiration date 150 | * @returns {Date} date of token expiration 151 | */ 152 | getAccessTokenExpiresAt() { 153 | return this.accessTokenExpiresAt; 154 | } 155 | 156 | /** 157 | * Sets the access token's expiration date 158 | * @param accessTokenExpiresAt - new expiration date 159 | */ 160 | setAccessTokenExpiresAt(accessTokenExpiresAt) { 161 | this.accessTokenExpiresAt = accessTokenExpiresAt; 162 | } 163 | 164 | /** 165 | * Sets the code verifier for PKCE flow 166 | * @param {String} codeVerifier - new code verifier 167 | */ 168 | setCodeVerifier(codeVerifier) { 169 | this.codeVerifier = codeVerifier; 170 | } 171 | 172 | /** 173 | * Gets the code verifier for PKCE flow 174 | * @returns {String} - code verifier for PKCE 175 | */ 176 | getCodeVerifier() { 177 | return this.codeVerifier; 178 | } 179 | 180 | generateCodeChallenge() { 181 | const encoder = new Encoder(); 182 | const codeData = encoder.encode(this.codeVerifier); 183 | let codeChallenge; 184 | if (isBrowserEnv() || isWorkerEnv()) { 185 | return crypto.subtle.digest('SHA-256', codeData) 186 | .then((digestedHash) => { 187 | const base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(digestedHash))); 188 | codeChallenge = createBrowserSafeString(base64String).substr(0, 128); 189 | this.codeChallenge = codeChallenge; 190 | }); 191 | } 192 | const digestedHash = crypto.createHash('sha256').update(codeData).digest(); 193 | codeChallenge = createBrowserSafeString(digestedHash); 194 | this.codeChallenge = codeChallenge; 195 | return Promise.resolve(); 196 | } 197 | 198 | generatePKCECodes() { 199 | let codeVerifier; 200 | if (isBrowserEnv() || isWorkerEnv()) { 201 | const array = new Uint8Array(PKCELength); 202 | const randomValueArray = crypto.getRandomValues(array); 203 | const base64String = btoa(randomValueArray); 204 | codeVerifier = createBrowserSafeString(base64String).substr(0, 128); 205 | } else { 206 | const randomBytes = crypto.randomBytes(PKCELength); 207 | codeVerifier = createBrowserSafeString(randomBytes).substr(0, 128); 208 | } 209 | this.codeVerifier = codeVerifier; 210 | 211 | return this.generateCodeChallenge(); 212 | } 213 | 214 | /** 215 | * Get a URL that can be used to authenticate users for the Dropbox API. 216 | * @arg {String} redirectUri - A URL to redirect the user to after 217 | * authenticating. This must be added to your app through the admin interface. 218 | * @arg {String} [state] - State that will be returned in the redirect URL to help 219 | * prevent cross site scripting attacks. 220 | * @arg {String} [authType] - auth type, defaults to 'token', other option is 'code' 221 | * @arg {String} [tokenAccessType] - type of token to request. From the following: 222 | * null - creates a token with the app default (either legacy or online) 223 | * legacy - creates one long-lived token with no expiration 224 | * online - create one short-lived token with an expiration 225 | * offline - create one short-lived token with an expiration with a refresh token 226 | * @arg {Array} [scope] - scopes to request for the grant 227 | * @arg {String} [includeGrantedScopes] - whether or not to include previously granted scopes. 228 | * From the following: 229 | * user - include user scopes in the grant 230 | * team - include team scopes in the grant 231 | * Note: if this user has never linked the app, include_granted_scopes must be None 232 | * @arg {boolean} [usePKCE] - Whether or not to use Sha256 based PKCE. PKCE should be only use 233 | * on client apps which doesn't call your server. It is less secure than non-PKCE flow but 234 | * can be used if you are unable to safely retrieve your app secret 235 | * @returns {Promise} - Url to send user to for Dropbox API authentication 236 | * returned in a promise 237 | */ 238 | getAuthenticationUrl(redirectUri, state, authType = 'token', tokenAccessType = null, scope = null, includeGrantedScopes = 'none', usePKCE = false) { 239 | const clientId = this.getClientId(); 240 | const baseUrl = OAuth2AuthorizationUrl(this.domain); 241 | 242 | if (!clientId) { 243 | throw new Error('A client id is required. You can set the client id using .setClientId().'); 244 | } 245 | if (authType !== 'code' && !redirectUri) { 246 | throw new Error('A redirect uri is required.'); 247 | } 248 | if (!GrantTypes.includes(authType)) { 249 | throw new Error('Authorization type must be code or token'); 250 | } 251 | if (tokenAccessType && !TokenAccessTypes.includes(tokenAccessType)) { 252 | throw new Error('Token Access Type must be legacy, offline, or online'); 253 | } 254 | if (scope && !(scope instanceof Array)) { 255 | throw new Error('Scope must be an array of strings'); 256 | } 257 | if (!IncludeGrantedScopes.includes(includeGrantedScopes)) { 258 | throw new Error('includeGrantedScopes must be none, user, or team'); 259 | } 260 | 261 | let authUrl; 262 | if (authType === 'code') { 263 | authUrl = `${baseUrl}?response_type=code&client_id=${clientId}`; 264 | } else { 265 | authUrl = `${baseUrl}?response_type=token&client_id=${clientId}`; 266 | } 267 | 268 | if (redirectUri) { 269 | authUrl += `&redirect_uri=${redirectUri}`; 270 | } 271 | if (state) { 272 | authUrl += `&state=${state}`; 273 | } 274 | if (tokenAccessType) { 275 | authUrl += `&token_access_type=${tokenAccessType}`; 276 | } 277 | if (scope) { 278 | authUrl += `&scope=${scope.join(' ')}`; 279 | } 280 | if (includeGrantedScopes !== 'none') { 281 | authUrl += `&include_granted_scopes=${includeGrantedScopes}`; 282 | } 283 | if (usePKCE) { 284 | return this.generatePKCECodes() 285 | .then(() => { 286 | authUrl += '&code_challenge_method=S256'; 287 | authUrl += `&code_challenge=${this.codeChallenge}`; 288 | return authUrl; 289 | }); 290 | } 291 | return Promise.resolve(authUrl); 292 | } 293 | 294 | /** 295 | * Get an OAuth2 access token from an OAuth2 Code. 296 | * @arg {String} redirectUri - A URL to redirect the user to after 297 | * authenticating. This must be added to your app through the admin interface. 298 | * @arg {String} code - An OAuth2 code. 299 | * @returns {Object} An object containing the token and related info (if applicable) 300 | */ 301 | getAccessTokenFromCode(redirectUri, code) { 302 | const clientId = this.getClientId(); 303 | const clientSecret = this.getClientSecret(); 304 | 305 | if (!clientId) { 306 | throw new Error('A client id is required. You can set the client id using .setClientId().'); 307 | } 308 | let path = OAuth2TokenUrl(this.domain, this.domainDelimiter); 309 | path += '?grant_type=authorization_code'; 310 | path += `&code=${code}`; 311 | path += `&client_id=${clientId}`; 312 | 313 | if (clientSecret) { 314 | path += `&client_secret=${clientSecret}`; 315 | } else { 316 | if (!this.codeVerifier) { 317 | throw new Error('You must use PKCE when generating the authorization URL to not include a client secret'); 318 | } 319 | path += `&code_verifier=${this.codeVerifier}`; 320 | } 321 | if (redirectUri) { 322 | path += `&redirect_uri=${redirectUri}`; 323 | } 324 | 325 | const fetchOptions = { 326 | method: 'POST', 327 | headers: { 328 | 'Content-Type': 'application/x-www-form-urlencoded', 329 | }, 330 | }; 331 | return this.fetch(path, fetchOptions) 332 | .then((res) => parseResponse(res)); 333 | } 334 | 335 | /** 336 | * Checks if a token is needed, can be refreshed and if the token is expired. 337 | * If so, attempts to refresh access token 338 | * @returns {Promise<*>} 339 | */ 340 | checkAndRefreshAccessToken() { 341 | const canRefresh = this.getRefreshToken() && this.getClientId(); 342 | const needsRefresh = !this.getAccessTokenExpiresAt() 343 | || (new Date(Date.now() + TokenExpirationBuffer)) >= this.getAccessTokenExpiresAt(); 344 | const needsToken = !this.getAccessToken(); 345 | if ((needsRefresh || needsToken) && canRefresh) { 346 | return this.refreshAccessToken(); 347 | } 348 | return Promise.resolve(); 349 | } 350 | 351 | /** 352 | * Refreshes the access token using the refresh token, if available 353 | * @arg {Array} scope - a subset of scopes from the original 354 | * refresh to acquire with an access token 355 | * @returns {Promise<*>} 356 | */ 357 | refreshAccessToken(scope = null) { 358 | const clientId = this.getClientId(); 359 | const clientSecret = this.getClientSecret(); 360 | 361 | if (!clientId) { 362 | throw new Error('A client id is required. You can set the client id using .setClientId().'); 363 | } 364 | if (scope && !(scope instanceof Array)) { 365 | throw new Error('Scope must be an array of strings'); 366 | } 367 | 368 | let refreshUrl = OAuth2TokenUrl(this.domain, this.domainDelimiter); 369 | const fetchOptions = { 370 | headers: { 'Content-Type': 'application/json' }, 371 | method: 'POST', 372 | }; 373 | 374 | if (this.dataOnBody) { 375 | const body = { grant_type: 'refresh_token', client_id: clientId, refresh_token: this.getRefreshToken() }; 376 | 377 | if (clientSecret) { 378 | body.client_secret = clientSecret; 379 | } 380 | if (scope) { 381 | body.scope = scope.join(' '); 382 | } 383 | 384 | fetchOptions.body = body; 385 | } else { 386 | refreshUrl += `?grant_type=refresh_token&refresh_token=${this.getRefreshToken()}`; 387 | refreshUrl += `&client_id=${clientId}`; 388 | if (clientSecret) { 389 | refreshUrl += `&client_secret=${clientSecret}`; 390 | } 391 | if (scope) { 392 | refreshUrl += `&scope=${scope.join(' ')}`; 393 | } 394 | } 395 | 396 | return this.fetch(refreshUrl, fetchOptions) 397 | .then((res) => parseResponse(res)) 398 | .then((res) => { 399 | this.setAccessToken(res.result.access_token); 400 | this.setAccessTokenExpiresAt(getTokenExpiresAtDate(res.result.expires_in)); 401 | }); 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const RPC = 'rpc'; 2 | export const UPLOAD = 'upload'; 3 | export const DOWNLOAD = 'download'; 4 | 5 | export const APP_AUTH = 'app'; 6 | export const USER_AUTH = 'user'; 7 | export const TEAM_AUTH = 'team'; 8 | export const NO_AUTH = 'noauth'; 9 | export const COOKIE = 'cookie'; 10 | 11 | export const DEFAULT_API_DOMAIN = 'dropboxapi.com'; 12 | export const DEFAULT_DOMAIN = 'dropbox.com'; 13 | 14 | export const TEST_DOMAIN_MAPPINGS = { 15 | api: 'api', 16 | notify: 'bolt', 17 | content: 'api-content', 18 | }; 19 | -------------------------------------------------------------------------------- /src/dropbox.js: -------------------------------------------------------------------------------- 1 | import { 2 | UPLOAD, 3 | DOWNLOAD, 4 | RPC, 5 | APP_AUTH, 6 | TEAM_AUTH, 7 | USER_AUTH, 8 | NO_AUTH, 9 | COOKIE, 10 | } from './constants.js'; 11 | import { routes } from '../lib/routes.js'; 12 | import DropboxAuth from './auth.js'; 13 | import { baseApiUrl, httpHeaderSafeJson } from './utils.js'; 14 | import { parseDownloadResponse, parseResponse } from './response.js'; 15 | 16 | const b64 = typeof btoa === 'undefined' 17 | ? (str) => Buffer.from(str).toString('base64') 18 | : btoa; 19 | 20 | /** 21 | * @class Dropbox 22 | * @classdesc The Dropbox SDK class that provides methods to read, write and 23 | * create files or folders in a user or team's Dropbox. 24 | * @arg {Object} options 25 | * @arg {Function} [options.fetch] - fetch library for making requests. 26 | * @arg {String} [options.selectUser] - Select user is only used for team functionality. 27 | * It specifies which user the team access token should be acting as. 28 | * @arg {String} [options.pathRoot] - root path to access other namespaces 29 | * Use to access team folders for example 30 | * @arg {String} [options.selectAdmin] - Select admin is only used by team functionality. 31 | * It specifies which team admin the team access token should be acting as. 32 | * @arg {DropboxAuth} [options.auth] - The DropboxAuth object used to authenticate requests. 33 | * If this is set, the remaining parameters will be ignored. 34 | * @arg {String} [options.accessToken] - An access token for making authenticated 35 | * requests. 36 | * @arg {Date} [options.accessTokenExpiresAt] - Date of the current access token's 37 | * expiration (if available) 38 | * @arg {String} [options.refreshToken] - A refresh token for retrieving access tokens 39 | * @arg {String} [options.clientId] - The client id for your app. Used to create 40 | * authentication URL. 41 | * @arg {String} [options.clientSecret] - The client secret for your app. Used to create 42 | * authentication URL and refresh access tokens. 43 | * @arg {String} [options.domain] - A custom domain to use when making api requests. This 44 | * should only be used for testing as scaffolding to avoid making network requests. 45 | * @arg {String} [options.domainDelimiter] - A custom delimiter to use when separating domain from 46 | * subdomain. This should only be used for testing as scaffolding. 47 | * @arg {Object} [options.customHeaders] - An object (in the form of header: value) designed to set 48 | * custom headers to use during a request. 49 | */ 50 | export default class Dropbox { 51 | constructor(options) { 52 | options = options || {}; 53 | 54 | if (options.auth) { 55 | this.auth = options.auth; 56 | } else { 57 | this.auth = new DropboxAuth(options); 58 | } 59 | 60 | this.fetch = options.fetch || this.auth.fetch; 61 | this.selectUser = options.selectUser; 62 | this.selectAdmin = options.selectAdmin; 63 | this.pathRoot = options.pathRoot; 64 | 65 | this.domain = options.domain || this.auth.domain; 66 | this.domainDelimiter = options.domainDelimiter || this.auth.domainDelimiter; 67 | this.customHeaders = options.customHeaders || this.auth.customHeaders; 68 | 69 | Object.assign(this, routes); 70 | } 71 | 72 | request(path, args, auth, host, style) { 73 | // scope is provided after "style", but unused in requests, so it's not in parameters 74 | switch (style) { 75 | case RPC: 76 | return this.rpcRequest(path, args, auth, host); 77 | case DOWNLOAD: 78 | return this.downloadRequest(path, args, auth, host); 79 | case UPLOAD: 80 | return this.uploadRequest(path, args, auth, host); 81 | default: 82 | throw new Error(`Invalid request style: ${style}`); 83 | } 84 | } 85 | 86 | rpcRequest(path, body, auth, host) { 87 | return this.auth.checkAndRefreshAccessToken() 88 | .then(() => { 89 | const fetchOptions = { 90 | method: 'POST', 91 | body: (body) ? JSON.stringify(body) : null, 92 | headers: {}, 93 | }; 94 | 95 | if (body) { 96 | fetchOptions.headers['Content-Type'] = 'application/json'; 97 | } 98 | 99 | this.setAuthHeaders(auth, fetchOptions); 100 | this.setCommonHeaders(fetchOptions); 101 | 102 | return fetchOptions; 103 | }) 104 | .then((fetchOptions) => this.fetch( 105 | baseApiUrl(host, this.domain, this.domainDelimiter) + path, 106 | fetchOptions, 107 | )) 108 | .then((res) => parseResponse(res)); 109 | } 110 | 111 | downloadRequest(path, args, auth, host) { 112 | return this.auth.checkAndRefreshAccessToken() 113 | .then(() => { 114 | const fetchOptions = { 115 | method: 'POST', 116 | headers: { 117 | 'Dropbox-API-Arg': httpHeaderSafeJson(args), 118 | }, 119 | }; 120 | 121 | this.setAuthHeaders(auth, fetchOptions); 122 | this.setCommonHeaders(fetchOptions); 123 | 124 | return fetchOptions; 125 | }) 126 | .then((fetchOptions) => this.fetch( 127 | baseApiUrl(host, this.domain, this.domainDelimiter) + path, 128 | fetchOptions, 129 | )) 130 | .then((res) => parseDownloadResponse(res)); 131 | } 132 | 133 | uploadRequest(path, args, auth, host) { 134 | return this.auth.checkAndRefreshAccessToken() 135 | .then(() => { 136 | const { contents } = args; 137 | delete args.contents; 138 | 139 | const fetchOptions = { 140 | body: contents, 141 | method: 'POST', 142 | headers: { 143 | 'Content-Type': 'application/octet-stream', 144 | 'Dropbox-API-Arg': httpHeaderSafeJson(args), 145 | }, 146 | }; 147 | 148 | this.setAuthHeaders(auth, fetchOptions); 149 | this.setCommonHeaders(fetchOptions); 150 | 151 | return fetchOptions; 152 | }) 153 | .then((fetchOptions) => this.fetch( 154 | baseApiUrl(host, this.domain, this.domainDelimiter) + path, 155 | fetchOptions, 156 | )) 157 | .then((res) => parseResponse(res)); 158 | } 159 | 160 | setAuthHeaders(auth, fetchOptions) { 161 | // checks for multiauth and assigns auth based on priority to create header in switch case 162 | if (auth.split(',').length > 1) { 163 | const authTypes = auth.replace(' ', '').split(','); 164 | if (authTypes.includes(USER_AUTH) && this.auth.getAccessToken()) { 165 | auth = USER_AUTH; 166 | } else if (authTypes.includes(TEAM_AUTH) && this.auth.getAccessToken()) { 167 | auth = TEAM_AUTH; 168 | } else if (authTypes.includes(APP_AUTH)) { 169 | auth = APP_AUTH; 170 | } 171 | } 172 | 173 | switch (auth) { 174 | case APP_AUTH: 175 | if (this.auth.clientId && this.auth.clientSecret) { 176 | const authHeader = b64(`${this.auth.clientId}:${this.auth.clientSecret}`); 177 | fetchOptions.headers.Authorization = `Basic ${authHeader}`; 178 | } 179 | break; 180 | case TEAM_AUTH: 181 | case USER_AUTH: 182 | if (this.auth.getAccessToken()) { 183 | fetchOptions.headers.Authorization = `Bearer ${this.auth.getAccessToken()}`; 184 | } 185 | break; 186 | case NO_AUTH: 187 | case COOKIE: 188 | break; 189 | default: 190 | throw new Error(`Unhandled auth type: ${auth}`); 191 | } 192 | } 193 | 194 | setCommonHeaders(options) { 195 | if (this.selectUser) { 196 | options.headers['Dropbox-API-Select-User'] = this.selectUser; 197 | } 198 | if (this.selectAdmin) { 199 | options.headers['Dropbox-API-Select-Admin'] = this.selectAdmin; 200 | } 201 | if (this.pathRoot) { 202 | options.headers['Dropbox-API-Path-Root'] = this.pathRoot; 203 | } 204 | if (this.customHeaders) { 205 | const headerKeys = Object.keys(this.customHeaders); 206 | headerKeys.forEach((header) => { 207 | options.headers[header] = this.customHeaders[header]; 208 | }); 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The response class of HTTP errors from API calls using the Dropbox SDK. 3 | * @class DropboxResponseError 4 | * @classdesc The response class of HTTP errors from API calls using the Dropbox SDK. 5 | * @arg {number} status - HTTP Status code of the call 6 | * @arg {Object} headers - Headers returned from the call 7 | * @arg {Object} error - Serialized Error of the call 8 | */ 9 | export class DropboxResponseError extends Error { 10 | constructor(status, headers, error) { 11 | super(`Response failed with a ${status} code`); 12 | this.name = 'DropboxResponseError'; 13 | this.status = status; 14 | this.headers = headers; 15 | this.error = error; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/response.js: -------------------------------------------------------------------------------- 1 | import { isWindowOrWorker } from './utils.js'; 2 | import { DropboxResponseError } from './error.js'; 3 | 4 | export class DropboxResponse { 5 | constructor(status, headers, result) { 6 | this.status = status; 7 | this.headers = headers; 8 | this.result = result; 9 | } 10 | } 11 | 12 | function throwAsError(res) { 13 | return res.text() 14 | .then((data) => { 15 | let errorObject; 16 | try { 17 | errorObject = JSON.parse(data); 18 | } catch (error) { 19 | errorObject = data; 20 | } 21 | 22 | throw new DropboxResponseError(res.status, res.headers, errorObject); 23 | }); 24 | } 25 | 26 | export function parseResponse(res) { 27 | if (!res.ok) { 28 | return throwAsError(res); 29 | } 30 | return res.text() 31 | .then((data) => { 32 | let responseObject; 33 | try { 34 | responseObject = JSON.parse(data); 35 | } catch (error) { 36 | responseObject = data; 37 | } 38 | 39 | return new DropboxResponse(res.status, res.headers, responseObject); 40 | }); 41 | } 42 | 43 | export function parseDownloadResponse(res) { 44 | if (!res.ok) { 45 | return throwAsError(res); 46 | } 47 | return new Promise((resolve) => { 48 | if (isWindowOrWorker()) { 49 | res.blob().then((data) => resolve(data)); 50 | } else { 51 | res.buffer().then((data) => resolve(data)); 52 | } 53 | }).then((data) => { 54 | const result = JSON.parse(res.headers.get('dropbox-api-result')); 55 | 56 | if (isWindowOrWorker()) { 57 | result.fileBlob = data; 58 | } else { 59 | result.fileBinary = data; 60 | } 61 | 62 | return new DropboxResponse(res.status, res.headers, result); 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import { DEFAULT_API_DOMAIN, DEFAULT_DOMAIN, TEST_DOMAIN_MAPPINGS } from './constants'; 2 | 3 | function getSafeUnicode(c) { 4 | const unicode = `000${c.charCodeAt(0).toString(16)}`.slice(-4); 5 | return `\\u${unicode}`; 6 | } 7 | 8 | export const baseApiUrl = (subdomain, domain = DEFAULT_API_DOMAIN, domainDelimiter = '.') => { 9 | if (!domainDelimiter) { 10 | return `https://${domain}/2/`; 11 | } 12 | if (domain !== DEFAULT_API_DOMAIN && TEST_DOMAIN_MAPPINGS[subdomain] !== undefined) { 13 | subdomain = TEST_DOMAIN_MAPPINGS[subdomain]; 14 | domainDelimiter = '-'; 15 | } 16 | return `https://${subdomain}${domainDelimiter}${domain}/2/`; 17 | }; 18 | export const OAuth2AuthorizationUrl = (domain = DEFAULT_DOMAIN) => { 19 | if (domain !== DEFAULT_DOMAIN) { 20 | domain = `meta-${domain}`; 21 | } 22 | return `https://${domain}/oauth2/authorize`; 23 | }; 24 | export const OAuth2TokenUrl = (domain = DEFAULT_API_DOMAIN, domainDelimiter = '.') => { 25 | let subdomain = 'api'; 26 | if (domain !== DEFAULT_API_DOMAIN) { 27 | subdomain = TEST_DOMAIN_MAPPINGS[subdomain]; 28 | domainDelimiter = '-'; 29 | } 30 | return `https://${subdomain}${domainDelimiter}${domain}/oauth2/token`; 31 | }; 32 | 33 | // source https://www.dropboxforum.com/t5/API-support/HTTP-header-quot-Dropbox-API-Arg-quot-could-not-decode-input-as/m-p/173823/highlight/true#M6786 34 | export function httpHeaderSafeJson(args) { 35 | return JSON.stringify(args).replace(/[\u007f-\uffff]/g, getSafeUnicode); 36 | } 37 | 38 | export function getTokenExpiresAtDate(expiresIn) { 39 | return new Date(Date.now() + (expiresIn * 1000)); 40 | } 41 | 42 | /* global WorkerGlobalScope */ 43 | export function isWindowOrWorker() { 44 | return ( 45 | ( 46 | typeof WorkerGlobalScope !== 'undefined' 47 | && self instanceof WorkerGlobalScope // eslint-disable-line no-restricted-globals 48 | ) 49 | || ( 50 | typeof module === 'undefined' 51 | || typeof window !== 'undefined' 52 | ) 53 | ); 54 | } 55 | 56 | export function isBrowserEnv() { 57 | return typeof window !== 'undefined'; 58 | } 59 | 60 | export function isWorkerEnv() { 61 | return typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope; // eslint-disable-line no-restricted-globals 62 | } 63 | 64 | export function createBrowserSafeString(toBeConverted) { 65 | const convertedString = toBeConverted.toString('base64') 66 | .replace(/\+/g, '-') 67 | .replace(/\//g, '_') 68 | .replace(/=/g, ''); 69 | return convertedString; 70 | } 71 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "rules": { 6 | "no-restricted-syntax": 0, 7 | "no-console": 0, 8 | "no-unused-vars": 0 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/build/browser.js: -------------------------------------------------------------------------------- 1 | import { JSDOM } from 'jsdom'; 2 | import { fireEvent } from '@testing-library/dom'; 3 | import chai from 'chai'; 4 | import fs from 'fs'; 5 | import path from 'path'; 6 | 7 | const SDK_IMPORT_HOLDER = 'sdkscript'; 8 | 9 | const setImport = (rawHTML, importPath) => rawHTML.replace(SDK_IMPORT_HOLDER, `file://${importPath}`); 10 | 11 | const executeTest = (testContainer) => { 12 | const children = testContainer.childNodes; 13 | const result = children[1]; 14 | chai.assert.equal(result.localName, 'h1', 'Test setup'); 15 | 16 | const execute = children[3]; 17 | chai.assert.equal(execute.localName, 'button', 'Test setup'); 18 | 19 | fireEvent.click(execute); 20 | return result.innerHTML; 21 | }; 22 | 23 | describe('Browser Definitions', () => { 24 | describe('ES Build', () => { 25 | let html; 26 | let dom; 27 | // let document; 28 | 29 | before((done) => { 30 | const importPath = path.resolve(__dirname, '../../es/index.js'); 31 | html = fs.readFileSync(path.resolve(__dirname, './browser/es.html'), 'utf8'); 32 | html = setImport(html, importPath); 33 | done(); 34 | }); 35 | 36 | beforeEach((done) => { 37 | // Constructing a new JSDOM with this option is the key 38 | // to getting the code in the script tag to execute. 39 | // This is indeed dangerous and should only be done with trusted content. 40 | // https://github.com/jsdom/jsdom#executing-scripts 41 | dom = new JSDOM(html, { 42 | runScripts: 'dangerously', 43 | resources: 'usable', 44 | }); 45 | dom.window.onload = () => { 46 | // document = dom.window.document; 47 | done(); 48 | }; 49 | }); 50 | 51 | // Broken until JSDOM supports 5 | 6 | 7 | 8 | 9 |
          10 |
        • 11 |

          No Execution

          12 | 13 | 22 |
        • 23 |
        24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/build/browser/umd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
          11 |
        • 12 |

          No Execution

          13 | 14 | 22 |
        • 23 |
        24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/build/browser/umd.min.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
          11 |
        • 12 |

          No Execution

          13 | 14 | 22 |
        • 23 |
        24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/build/node.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | const { exec } = require('child_process'); 4 | 5 | describe('Node Definitions', () => { 6 | describe('JS CJS Imports', () => { 7 | const dirPath = path.resolve(__dirname, 'node/js_cjs'); 8 | 9 | beforeEach((done) => { 10 | exec(`cd ${dirPath} && npm install`, (error, stdout, stderr) => { 11 | if (error) { 12 | console.log(stdout); 13 | done(error); 14 | } else { 15 | done(); 16 | } 17 | }); 18 | }); 19 | 20 | it('Require', (done) => { 21 | exec(`cd ${dirPath} && npm run test:require`, (error, stdout, stderr) => { 22 | if (error) { 23 | console.log(stdout); 24 | done(error); 25 | } else { 26 | done(); 27 | } 28 | }); 29 | }); 30 | }); 31 | 32 | describe('JS ESM Imports', () => { 33 | const dirPath = path.resolve(__dirname, 'node/js_esm'); 34 | 35 | beforeEach((done) => { 36 | exec(`cd ${dirPath} && npm install`, (error, stdout, stderr) => { 37 | if (error) { 38 | console.log(stdout); 39 | done(error); 40 | } else { 41 | done(); 42 | } 43 | }); 44 | }); 45 | 46 | it('Import', (done) => { 47 | exec(`cd ${dirPath} && npm run test:import`, (error, stdout, stderr) => { 48 | if (error) { 49 | console.log(stdout); 50 | done(error); 51 | } else { 52 | done(); 53 | } 54 | }); 55 | }); 56 | 57 | // Named imports do not currently work. 58 | // it("Named Import", (done) => { 59 | // exec(`cd ${dirPath} && npm run test:namedImport`, (error, stdout, stderr) => { 60 | // if (error) { 61 | // console.log(stdout); 62 | // done(error); 63 | // } else { 64 | // done(); 65 | // } 66 | // }); 67 | // }); 68 | }); 69 | 70 | describe('TS ES6 Imports', () => { 71 | const dirPath = path.resolve(__dirname, 'node/ts_es6'); 72 | 73 | beforeEach((done) => { 74 | exec(`cd ${dirPath} && npm install && npm run build`, (error, stdout, stderr) => { 75 | if (error) { 76 | console.log(stdout); 77 | done(error); 78 | } else { 79 | done(); 80 | } 81 | }); 82 | }); 83 | 84 | it('Import', (done) => { 85 | exec(`cd ${dirPath} && npm run test:import`, (error, stdout, stderr) => { 86 | if (error) { 87 | console.log(stdout); 88 | done(error); 89 | } else { 90 | done(); 91 | } 92 | }); 93 | }); 94 | 95 | // Named imports do not currently work 96 | // it("Named Import", (done) => { 97 | // exec(`cd ${dirPath} && npm run test:namedImport`, (error, stdout, stderr) => { 98 | // if (error) { 99 | // console.log(stdout); 100 | // done(error); 101 | // } else { 102 | // done(); 103 | // } 104 | // }); 105 | // }); 106 | }); 107 | 108 | describe('TS CJS Imports', () => { 109 | const dirPath = path.resolve(__dirname, 'node/ts_cjs'); 110 | 111 | beforeEach((done) => { 112 | exec(`cd ${dirPath} && npm install && npm run build`, (error, stdout, stderr) => { 113 | if (error) { 114 | console.log(stdout); 115 | done(error); 116 | } else { 117 | done(); 118 | } 119 | }); 120 | }); 121 | 122 | // Current Namespace definitions fail compilation 123 | // it("Require", (done) => { 124 | // exec(`cd ${dirPath} && npm run test:require`, (error, stdout, stderr) => { 125 | // if (error) { 126 | // console.log(stdout); 127 | // done(error); 128 | // } else { 129 | // done(); 130 | // } 131 | // }); 132 | // }); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /test/build/node/js_cjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cjs_build_test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test:require": "node require.js" 7 | }, 8 | "devDependencies": { 9 | "dropbox": "file:../../../../" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/build/node/js_cjs/require.js: -------------------------------------------------------------------------------- 1 | var dropbox = require('dropbox'); 2 | 3 | var dbx = new dropbox.Dropbox(); 4 | var dbxAuth = new dropbox.DropboxAuth(); 5 | 6 | var { Dropbox, DropboxAuth } = dropbox; 7 | 8 | var dbx2 = new Dropbox(); 9 | var dbxAuth2 = new DropboxAuth(); -------------------------------------------------------------------------------- /test/build/node/js_esm/import.js: -------------------------------------------------------------------------------- 1 | import dropbox from 'dropbox'; 2 | 3 | var dbx = new dropbox.Dropbox(); 4 | var dbxAuth = new dropbox.DropboxAuth(); 5 | 6 | var { Dropbox, DropboxAuth } = dropbox; 7 | 8 | var dbx2 = new Dropbox(); 9 | var dbxAuth2 = new DropboxAuth(); -------------------------------------------------------------------------------- /test/build/node/js_esm/namedImport.js: -------------------------------------------------------------------------------- 1 | import { Dropbox, DropboxAuth } from 'dropbox'; 2 | 3 | var dbx = new dropbox.Dropbox(); 4 | var dbxAuth = new dropbox.DropboxAuth(); -------------------------------------------------------------------------------- /test/build/node/js_esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cjs_build_test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "scripts": { 7 | "test:import": "node import.js", 8 | "test:namedImport": "node namedImport.js" 9 | }, 10 | "devDependencies": { 11 | "dropbox": "file:../../../../" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/build/node/ts_cjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cjs_build_test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "scripts": { 7 | "build": "tsc -p tsconfig.json", 8 | "test:require": "node require.js" 9 | }, 10 | "devDependencies": { 11 | "dropbox": "file:../../../../", 12 | "typescript": "^4.0.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/build/node/ts_cjs/require.js: -------------------------------------------------------------------------------- 1 | const dropbox = require('dropbox'); 2 | var dbx = new dropbox.Dropbox(); 3 | var dbxAuth = new dropbox.DropboxAuth(); 4 | //# sourceMappingURL=require.js.map -------------------------------------------------------------------------------- /test/build/node/ts_cjs/require.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"require.js","sourceRoot":"","sources":["require.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;AAEnC,IAAI,GAAG,GAAoB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;AACjD,IAAI,OAAO,GAAwB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC"} -------------------------------------------------------------------------------- /test/build/node/ts_cjs/require.ts: -------------------------------------------------------------------------------- 1 | const dropbox = require('dropbox'); 2 | 3 | var dbx: dropbox.Dropbox = new dropbox.Dropbox(); 4 | var dbxAuth: dropbox.DropboxAuth = new dropbox.DropboxAuth(); -------------------------------------------------------------------------------- /test/build/node/ts_cjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2015", 5 | "lib": ["dom", "es2015", "es2016", "es2017"], 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "sourceMap": true, 9 | "declaration": false, 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitAny": true, 12 | "noImplicitReturns": true, 13 | "noImplicitThis": true, 14 | "noImplicitUseStrict": true, 15 | "noUnusedLocals": false 16 | }, 17 | "include": [ 18 | "import.ts", 19 | "require.ts" 20 | ], 21 | "exclude": [ 22 | "node_modules" 23 | ] 24 | } -------------------------------------------------------------------------------- /test/build/node/ts_es6/import.js: -------------------------------------------------------------------------------- 1 | import dropbox from 'dropbox'; 2 | const dbx = new dropbox.Dropbox(); 3 | const dbxAuth = new dropbox.DropboxAuth(); 4 | const { Dropbox, DropboxAuth } = dropbox; 5 | var dbx2 = new Dropbox(); 6 | var dbxAuth2 = new DropboxAuth(); 7 | //# sourceMappingURL=import.js.map -------------------------------------------------------------------------------- /test/build/node/ts_es6/import.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"import.js","sourceRoot":"","sources":["import.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,MAAM,GAAG,GAAoB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;AACnD,MAAM,OAAO,GAAwB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;AAE/D,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;AAEzC,IAAI,IAAI,GAAoB,IAAI,OAAO,EAAE,CAAC;AAC1C,IAAI,QAAQ,GAAwB,IAAI,WAAW,EAAE,CAAC"} -------------------------------------------------------------------------------- /test/build/node/ts_es6/import.ts: -------------------------------------------------------------------------------- 1 | import dropbox from 'dropbox'; 2 | 3 | const dbx: dropbox.Dropbox = new dropbox.Dropbox(); 4 | const dbxAuth: dropbox.DropboxAuth = new dropbox.DropboxAuth(); 5 | 6 | const { Dropbox, DropboxAuth } = dropbox; 7 | 8 | var dbx2: dropbox.Dropbox = new Dropbox(); 9 | var dbxAuth2: dropbox.DropboxAuth = new DropboxAuth(); -------------------------------------------------------------------------------- /test/build/node/ts_es6/namedImport.js: -------------------------------------------------------------------------------- 1 | import { Dropbox, DropboxAuth } from 'dropbox'; 2 | var dbx = new Dropbox(); 3 | var dbxAuth = new DropboxAuth(); 4 | //# sourceMappingURL=namedImport.js.map -------------------------------------------------------------------------------- /test/build/node/ts_es6/namedImport.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"namedImport.js","sourceRoot":"","sources":["namedImport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/C,IAAI,GAAG,GAAY,IAAI,OAAO,EAAE,CAAC;AACjC,IAAI,OAAO,GAAgB,IAAI,WAAW,EAAE,CAAC"} -------------------------------------------------------------------------------- /test/build/node/ts_es6/namedImport.ts: -------------------------------------------------------------------------------- 1 | import { Dropbox, DropboxAuth } from 'dropbox'; 2 | 3 | var dbx: Dropbox = new Dropbox(); 4 | var dbxAuth: DropboxAuth = new DropboxAuth(); -------------------------------------------------------------------------------- /test/build/node/ts_es6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cjs_build_test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "scripts": { 7 | "build": "tsc -p tsconfig.json", 8 | "test:import": "node import.js", 9 | "test:namedImport": "node namedImport.js" 10 | }, 11 | "devDependencies": { 12 | "dropbox": "file:../../../../", 13 | "typescript": "^4.0.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/build/node/ts_es6/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ES6", 4 | "target": "es2015", 5 | "lib": ["dom", "es2015", "es2016", "es2017"], 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "sourceMap": true, 9 | "declaration": false, 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitAny": true, 12 | "noImplicitReturns": true, 13 | "noImplicitThis": true, 14 | "noImplicitUseStrict": true, 15 | "noUnusedLocals": false 16 | }, 17 | "include": [ 18 | "import.ts", 19 | "namedImport.ts" 20 | ], 21 | "exclude": [ 22 | "node_modules" 23 | ] 24 | } -------------------------------------------------------------------------------- /test/fixtures/test.txt: -------------------------------------------------------------------------------- 1 | Hello world! 2 | -------------------------------------------------------------------------------- /test/integration/team.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import fs from 'fs/promises'; 3 | import path from 'path'; 4 | import { Dropbox, DropboxAuth } from '../../index.js'; 5 | import { DropboxResponse } from '../../src/response.js'; 6 | 7 | const appInfo = { 8 | SCOPED: { 9 | accessToken: process.env.SCOPED_TEAM_DROPBOX_TOKEN, 10 | clientId: process.env.SCOPED_TEAM_CLIENT_ID, 11 | clientSecret: process.env.SCOPED_TEAM_CLIENT_SECRET, 12 | refreshToken: process.env.SCOPED_TEAM_REFRESH_TOKEN, 13 | }, 14 | }; 15 | 16 | for (const appType in appInfo) { 17 | if (appType) { // for linter 18 | describe(`Team ${appType}`, () => { 19 | let dbxAuth; 20 | let dbx; 21 | beforeEach(() => { 22 | dbxAuth = new DropboxAuth(appInfo[appType]); 23 | dbx = new Dropbox({ auth: dbxAuth }); 24 | }); 25 | 26 | describe('rpc', () => { 27 | it('rpc request is successful', (done) => { 28 | dbx.teamGetInfo() 29 | .then((resp) => { 30 | chai.assert.instanceOf(resp, DropboxResponse); 31 | chai.assert.equal(resp.status, 200, resp.result); 32 | chai.assert.isObject(resp.result); 33 | 34 | done(); 35 | }) 36 | .catch(done); 37 | }); 38 | }); 39 | 40 | describe('app auth', () => { 41 | it('successfully uses app auth', (done) => { 42 | dbx.checkApp({ query: 'Echo string' }) 43 | .then((resp) => { 44 | chai.assert.instanceOf(resp, DropboxResponse); 45 | chai.assert.equal(resp.status, 200, resp.result); 46 | chai.assert.isObject(resp.result); 47 | chai.assert.equal('Echo string', resp.result.result); 48 | 49 | done(); 50 | }) 51 | .catch(done); 52 | }); 53 | }); 54 | 55 | describe('token refresh', () => { 56 | it('refreshes the token when necessary', (done) => { 57 | const currentDate = new Date(); 58 | dbxAuth.setAccessTokenExpiresAt(currentDate.setHours(currentDate.getHours() - 20)); 59 | const expirationBeforeRefresh = dbxAuth.getAccessTokenExpiresAt(); 60 | const dbxWithRefreshToken = new Dropbox({ auth: dbxAuth }); 61 | 62 | dbxWithRefreshToken.teamGetInfo() 63 | .then((resp) => { 64 | chai.assert.instanceOf(resp, DropboxResponse); 65 | chai.assert.equal(resp.status, 200, resp.result); 66 | chai.assert.isObject(resp.result); 67 | // testing to make sure that the token has been refreshed 68 | chai.assert.notEqual( 69 | dbxAuth.getAccessToken(), appInfo[appType].token, 70 | ); 71 | // comparing dates to make sure new token expiration is set 72 | chai.assert.isTrue( 73 | dbxAuth.accessTokenExpiresAt > new Date(expirationBeforeRefresh), 74 | ); 75 | 76 | done(); 77 | }) 78 | .catch(done); 79 | }); 80 | }); 81 | }); 82 | } 83 | } 84 | 85 | describe('incorrect auth', () => { 86 | it('fails if there is an empty auth object', () => { 87 | const dbxAuth = new DropboxAuth(); 88 | const dbx = new Dropbox({ auth: dbxAuth }); 89 | 90 | dbx.teamGetInfo() 91 | .catch((err) => { 92 | chai.assert.instanceOf(err, DropboxResponseError); 93 | chai.assert.isObject(err); 94 | chai.assert.equal(err.error, 'Error in call to API function "users/get_current_account": The given OAuth 2 access token is malformed.'); 95 | }); 96 | }); 97 | 98 | it('fails if token is invalid', () => { 99 | const dbxAuth = new DropboxAuth({ accessToken: 'foo' }); 100 | const dbx = new Dropbox({ auth: dbxAuth }); 101 | 102 | dbx.teamGetInfo() 103 | .catch((err) => { 104 | chai.assert.instanceOf(err, DropboxResponseError); 105 | chai.assert.isObject(err); 106 | chai.assert.equal(err.error, 'Error in call to API function "users/get_current_account": The given OAuth 2 access token is malformed.'); 107 | }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /test/integration/user.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | 4 | import chai from 'chai'; 5 | 6 | import { Dropbox, DropboxAuth } from '../../index.js'; 7 | import { DropboxResponse } from '../../src/response.js'; 8 | import { DropboxResponseError } from '../../src/error.js'; 9 | 10 | const appInfo = { 11 | LEGACY: { 12 | accessToken: process.env.LEGACY_USER_DROPBOX_TOKEN, 13 | clientId: process.env.LEGACY_USER_CLIENT_ID, 14 | clientSecret: process.env.LEGACY_USER_CLIENT_SECRET, 15 | refreshToken: process.env.LEGACY_USER_REFRESH_TOKEN, 16 | }, 17 | SCOPED: { 18 | accessToken: process.env.SCOPED_USER_DROPBOX_TOKEN, 19 | clientId: process.env.SCOPED_USER_CLIENT_ID, 20 | clientSecret: process.env.SCOPED_USER_CLIENT_SECRET, 21 | refreshToken: process.env.SCOPED_USER_REFRESH_TOKEN, 22 | }, 23 | }; 24 | 25 | for (const appType in appInfo) { 26 | if (appType) { // for linter 27 | describe(`User ${appType}`, () => { 28 | let dbxAuth; 29 | let dbx; 30 | beforeEach(() => { 31 | dbxAuth = new DropboxAuth(appInfo[appType]); 32 | dbx = new Dropbox({ auth: dbxAuth }); 33 | }); 34 | 35 | describe('rpc', () => { 36 | it('rpc request is successful', (done) => { 37 | dbx.usersGetCurrentAccount() 38 | .then((resp) => { 39 | chai.assert.instanceOf(resp, DropboxResponse); 40 | chai.assert.equal(resp.status, 200, resp.result); 41 | chai.assert.isObject(resp.result); 42 | 43 | done(); 44 | }) 45 | .catch(done); 46 | }); 47 | }); 48 | 49 | describe('download', () => { 50 | it('download request is successful', (done) => { 51 | dbx.sharingGetSharedLinkFile({ url: process.env.DROPBOX_SHARED_LINK }) 52 | .then((resp) => { 53 | chai.assert.instanceOf(resp, DropboxResponse); 54 | chai.assert.equal(resp.status, 200, resp.result); 55 | chai.assert.isObject(resp.result); 56 | 57 | chai.assert.isString(resp.result.name); 58 | chai.assert.isDefined(resp.result.fileBinary); 59 | 60 | done(); 61 | }) 62 | .catch(done); 63 | }); 64 | }); 65 | 66 | describe('upload', () => { 67 | it('upload request is successful', (done) => { 68 | fs.readFile(path.resolve('test/fixtures/test.txt'), 'utf8') 69 | .then((contents) => { 70 | dbx.filesUpload({ path: '/test.txt', contents }) 71 | .then((resp) => { 72 | chai.assert.instanceOf(resp, DropboxResponse); 73 | chai.assert.equal(resp.status, 200, resp.result); 74 | chai.assert.isObject(resp.result); 75 | 76 | done(); 77 | }) 78 | .catch(done); 79 | }); 80 | }); 81 | }); 82 | 83 | describe('app auth', () => { 84 | it('successfully uses app auth', (done) => { 85 | dbx.checkApp({ query: 'Echo string' }) 86 | .then((resp) => { 87 | chai.assert.instanceOf(resp, DropboxResponse); 88 | chai.assert.equal(resp.status, 200, resp.result); 89 | chai.assert.isObject(resp.result); 90 | chai.assert.equal('Echo string', resp.result.result); 91 | 92 | done(); 93 | }) 94 | .catch(done); 95 | }); 96 | }); 97 | 98 | describe('token refresh', () => { 99 | it('refreshes the token when necessary', (done) => { 100 | const currentDate = new Date(); 101 | // setting expired refresh token 102 | dbxAuth.setAccessTokenExpiresAt(currentDate.setHours(currentDate.getHours() - 20)); 103 | 104 | const expirationBeforeRefresh = dbxAuth.getAccessTokenExpiresAt(); 105 | const dbxWithRefreshToken = new Dropbox({ auth: dbxAuth }); 106 | 107 | dbxWithRefreshToken.usersGetCurrentAccount() 108 | .then((resp) => { 109 | chai.assert.instanceOf(resp, DropboxResponse); 110 | chai.assert.equal(resp.status, 200, resp.result); 111 | chai.assert.isObject(resp.result); 112 | // testing to make sure that the token has been refreshed 113 | chai.assert.notEqual( 114 | dbxAuth.getAccessToken(), appInfo[appType].token, 115 | ); 116 | // comparing dates to make sure new token expiration is set 117 | chai.assert.isTrue( 118 | dbxAuth.accessTokenExpiresAt > new Date(expirationBeforeRefresh), 119 | ); 120 | 121 | done(); 122 | }) 123 | .catch(done); 124 | }); 125 | }); 126 | }); 127 | } 128 | } 129 | 130 | describe('incorrect auth', () => { 131 | it('fails if there is an empty auth object', () => { 132 | const dbxAuth = new DropboxAuth(); 133 | const dbx = new Dropbox({ auth: dbxAuth }); 134 | 135 | dbx.usersGetCurrentAccount() 136 | .catch((err) => { 137 | chai.assert.instanceOf(err, DropboxResponseError); 138 | chai.assert.isObject(err); 139 | chai.assert.equal(err.error, 'Error in call to API function "users/get_current_account": The given OAuth 2 access token is malformed.'); 140 | }); 141 | }); 142 | 143 | it('fails if token is invalid', () => { 144 | const dbxAuth = new DropboxAuth({ accessToken: 'foo' }); 145 | const dbx = new Dropbox({ auth: dbxAuth }); 146 | 147 | dbx.usersGetCurrentAccount() 148 | .catch((err) => { 149 | chai.assert.instanceOf(err, DropboxResponseError); 150 | chai.assert.isObject(err); 151 | chai.assert.equal(err.error, 'Error in call to API function "users/get_current_account": The given OAuth 2 access token is malformed.'); 152 | }); 153 | }); 154 | }); 155 | 156 | describe('multiauth', () => { 157 | it('mulitauth request is successful', (done) => { 158 | const dbxAuth = new DropboxAuth(appInfo.LEGACY); 159 | const dbx = new Dropbox({ auth: dbxAuth }); 160 | const arg = { 161 | resource: { 162 | '.tag': 'link', 163 | url: process.env.DROPBOX_SHARED_LINK, 164 | }, 165 | format: 'jpeg', 166 | size: 'w64h64', 167 | mode: 'strict', 168 | }; 169 | dbx.filesGetThumbnailV2(arg) 170 | .then((resp) => { 171 | chai.assert.instanceOf(resp, DropboxResponse); 172 | chai.assert.equal(resp.status, 200, resp.result); 173 | done(); 174 | }) 175 | .catch(done); 176 | }); 177 | }); 178 | -------------------------------------------------------------------------------- /test/types/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb-base"], 3 | "env": { 4 | "browser": false, 5 | "node": true 6 | }, 7 | "rules": { 8 | "func-names": 0, 9 | "no-param-reassign": 0, 10 | "import/prefer-default-export": 0, 11 | "no-unused-expressions": 0, 12 | "no-unused-vars": 0 13 | }, 14 | "plugins": ["@typescript-eslint"], 15 | "parser": "@typescript-eslint/parser", 16 | "parserOptions": { "ecmaVersion": 6 } 17 | } -------------------------------------------------------------------------------- /test/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": ["./types_test.ts"], 3 | "compilerOptions": { 4 | /* Basic Options */ 5 | "incremental": false /* Enable incremental compilation */, 6 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, 7 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 8 | "lib": ["esnext"], 9 | 10 | /* Strict Type-Checking Options */ 11 | "strict": true /* Enable all strict type-checking options. */, 12 | "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 13 | "strictNullChecks": true /* Enable strict null checks. */, 14 | "strictFunctionTypes": true /* Enable strict checking of function types. */, 15 | "strictBindCallApply": true /* Enable strict 'bind', 'call', and 'apply' methods on functions. */, 16 | "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 17 | "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 18 | "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */ 19 | }, 20 | "exclude": [ 21 | "node_modules" 22 | ] 23 | } -------------------------------------------------------------------------------- /test/types/types_test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /** 3 | * This file does not exist to be executed, just compiled, 4 | * so that we can ensure that the definition files 5 | * only reference names that exist, 6 | * and to perform a basic sanity check that types are exported as intended. 7 | */ 8 | Object.defineProperty(exports, "__esModule", { value: true }); 9 | var Dropbox = require("../../types/index"); // eslint-disable-line 10 | // Check DropboxAuth Constructor and Methods 11 | // Test default constructor 12 | var dropboxAuth = new Dropbox.DropboxAuth(); 13 | // Test config constructor 14 | dropboxAuth = new Dropbox.DropboxAuth({ 15 | accessToken: 'myToken', 16 | accessTokenExpiresAt: new Date(Date.now()), 17 | refreshToken: 'myToken', 18 | clientId: 'myClientId', 19 | clientSecret: 'myClientSecret', 20 | }); 21 | // Test getters/setters 22 | dropboxAuth.setAccessToken('myToken'); 23 | dropboxAuth.getAccessToken(); 24 | dropboxAuth.setAccessTokenExpiresAt(new Date(Date.now())); 25 | dropboxAuth.getAccessTokenExpiresAt(); 26 | dropboxAuth.setRefreshToken('myToken'); 27 | dropboxAuth.getRefreshToken(); 28 | dropboxAuth.setClientId('myClientId'); 29 | dropboxAuth.getClientId(); 30 | dropboxAuth.setClientSecret('myClientSecret'); 31 | // Test other methods 32 | dropboxAuth.getAuthenticationUrl('myRedirect'); 33 | dropboxAuth.getAuthenticationUrl('myRedirect', 'myState'); 34 | dropboxAuth.getAuthenticationUrl('myRedirect', 'myState', 'code'); 35 | dropboxAuth.getAuthenticationUrl('myRedirect', 'mystate', 'code', 'offline', ['scope', 'scope'], 'none', false); 36 | dropboxAuth.getAccessTokenFromCode('myRedirect', 'myCode'); 37 | dropboxAuth.checkAndRefreshAccessToken(); 38 | dropboxAuth.refreshAccessToken(); 39 | dropboxAuth.refreshAccessToken(['files.metadata.read', 'files.metadata.write']); 40 | // Check Dropbox Constructor or Methods 41 | // Test config constructor 42 | var dropbox = new Dropbox.Dropbox({ 43 | auth: dropboxAuth, 44 | selectUser: '', 45 | selectAdmin: '', 46 | pathRoot: '', 47 | }); 48 | var dropbox2 = new Dropbox.Dropbox({ 49 | accessToken: 'myToken', 50 | accessTokenExpiresAt: new Date(Date.now()), 51 | refreshToken: 'myToken', 52 | clientId: 'myClientId', 53 | clientSecret: 'myClientSecret', 54 | selectUser: '', 55 | selectAdmin: '', 56 | pathRoot: '', 57 | }); 58 | dropbox.usersGetCurrentAccount() 59 | .then(function (response) { 60 | var status = response.status; 61 | var result = response.result; 62 | var headers = response.headers; 63 | }) 64 | .catch(function (error) { 65 | var status = error.status; 66 | var headers = error.headers; 67 | var errorObject = error.error; 68 | }); 69 | dropbox2.usersGetCurrentAccount(); 70 | -------------------------------------------------------------------------------- /test/types/types_test.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"types_test.js","sourceRoot":"","sources":["types_test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"} -------------------------------------------------------------------------------- /test/types/types_test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file does not exist to be executed, just compiled, 3 | * so that we can ensure that the definition files 4 | * only reference names that exist, 5 | * and to perform a basic sanity check that types are exported as intended. 6 | */ 7 | 8 | import { Headers } from 'node-fetch'; 9 | import * as Dropbox from '../../types/index'; // eslint-disable-line 10 | 11 | // Check DropboxAuth Constructor and Methods 12 | // Test default constructor 13 | let dropboxAuth = new Dropbox.DropboxAuth(); 14 | 15 | // Test config constructor 16 | dropboxAuth = new Dropbox.DropboxAuth({ 17 | accessToken: 'myToken', 18 | accessTokenExpiresAt: new Date(Date.now()), 19 | refreshToken: 'myToken', 20 | clientId: 'myClientId', 21 | clientSecret: 'myClientSecret', 22 | }); 23 | 24 | // Test getters/setters 25 | dropboxAuth.setAccessToken('myToken'); 26 | dropboxAuth.getAccessToken(); 27 | dropboxAuth.setAccessTokenExpiresAt(new Date(Date.now())); 28 | dropboxAuth.getAccessTokenExpiresAt(); 29 | dropboxAuth.setRefreshToken('myToken'); 30 | dropboxAuth.getRefreshToken(); 31 | dropboxAuth.setClientId('myClientId'); 32 | dropboxAuth.getClientId(); 33 | dropboxAuth.setClientSecret('myClientSecret'); 34 | 35 | // Test other methods 36 | dropboxAuth.getAuthenticationUrl('myRedirect'); 37 | dropboxAuth.getAuthenticationUrl('myRedirect', 'myState'); 38 | dropboxAuth.getAuthenticationUrl('myRedirect', 'myState', 'code'); 39 | dropboxAuth.getAuthenticationUrl('myRedirect', 'mystate', 'code', 'offline', ['scope', 'scope'], 'none', false); 40 | dropboxAuth.getAccessTokenFromCode('myRedirect', 'myCode'); 41 | dropboxAuth.checkAndRefreshAccessToken(); 42 | dropboxAuth.refreshAccessToken(); 43 | dropboxAuth.refreshAccessToken(['files.metadata.read', 'files.metadata.write']); 44 | 45 | // Check Dropbox Constructor or Methods 46 | // Test config constructor 47 | const dropbox = new Dropbox.Dropbox({ 48 | auth: dropboxAuth, 49 | selectUser: '', 50 | selectAdmin: '', 51 | pathRoot: '', 52 | }); 53 | 54 | const dropbox2 = new Dropbox.Dropbox({ 55 | accessToken: 'myToken', 56 | accessTokenExpiresAt: new Date(Date.now()), 57 | refreshToken: 'myToken', 58 | clientId: 'myClientId', 59 | clientSecret: 'myClientSecret', 60 | selectUser: '', 61 | selectAdmin: '', 62 | pathRoot: '', 63 | }); 64 | 65 | dropbox.usersGetCurrentAccount() 66 | .then((response: Dropbox.DropboxResponse) => { 67 | const { status } = response; 68 | const { result } = response; 69 | const { headers } = response; 70 | }) 71 | .catch((error: Dropbox.DropboxResponseError) => { 72 | const { status } = error; 73 | const { headers } = error; 74 | const errorObject: Dropbox.users.GetAccountError = error.error; 75 | }); 76 | dropbox2.usersGetCurrentAccount(); 77 | -------------------------------------------------------------------------------- /test/unit/auth.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import sinon from 'sinon'; 3 | import chaiAsPromised from 'chai-as-promised'; 4 | import * as utils from '../../src/utils'; 5 | import { DropboxAuth } from '../../index.js'; 6 | 7 | chai.use(chaiAsPromised); 8 | describe('DropboxAuth', () => { 9 | describe('accessToken', () => { 10 | it('can be set in the constructor', () => { 11 | const dbx = new DropboxAuth({ accessToken: 'foo' }); 12 | chai.assert.equal(dbx.getAccessToken(), 'foo'); 13 | }); 14 | 15 | it('is undefined if not set in constructor', () => { 16 | const dbx = new DropboxAuth(); 17 | chai.assert.equal(dbx.getAccessToken(), undefined); 18 | }); 19 | 20 | it('can be set after being instantiated', () => { 21 | const dbx = new DropboxAuth(); 22 | dbx.setAccessToken('foo'); 23 | chai.assert.equal(dbx.getAccessToken(), 'foo'); 24 | }); 25 | }); 26 | 27 | describe('clientId', () => { 28 | it('can be set in the constructor', () => { 29 | const dbx = new DropboxAuth({ clientId: 'foo' }); 30 | chai.assert.equal(dbx.getClientId(), 'foo'); 31 | }); 32 | 33 | it('is undefined if not set in constructor', () => { 34 | const dbx = new DropboxAuth(); 35 | chai.assert.equal(dbx.getClientId(), undefined); 36 | }); 37 | 38 | it('can be set after being instantiated', () => { 39 | const dbx = new DropboxAuth(); 40 | dbx.setClientId('foo'); 41 | chai.assert.equal(dbx.getClientId(), 'foo'); 42 | }); 43 | }); 44 | 45 | describe('customHeaders', () => { 46 | it('can be set in the constructor', () => { 47 | const dbx = new DropboxAuth({ customHeaders: { foo: 'bar' } }); 48 | chai.assert.equal(dbx.customHeaders.foo, 'bar'); 49 | }); 50 | 51 | it('is undefined if not set in constructor', () => { 52 | const dbx = new DropboxAuth(); 53 | chai.assert.equal(dbx.customHeaders, undefined); 54 | }); 55 | }); 56 | 57 | describe('getAuthenticationUrl()', () => { 58 | it('throws an error if the client id isn\'t set', () => { 59 | const dbx = new DropboxAuth(); 60 | chai.assert.throws( 61 | DropboxAuth.prototype.getAuthenticationUrl.bind(dbx, 'https://redirecturl.com'), 62 | Error, 63 | 'A client id is required. You can set the client id using .setClientId().', 64 | ); 65 | }); 66 | 67 | it('throws an error if the redirect url isn\'t set', () => { 68 | const dbx = new DropboxAuth({ clientId: 'CLIENT_ID' }); 69 | chai.assert.throws( 70 | DropboxAuth.prototype.getAuthenticationUrl.bind(dbx), 71 | Error, 72 | 'A redirect uri is required.', 73 | ); 74 | }); 75 | 76 | it('throws an error if the redirect url isn\'t set and type is code', () => { 77 | const dbx = new DropboxAuth({ clientId: 'CLIENT_ID' }); 78 | return chai.expect( 79 | dbx.getAuthenticationUrl('', null, 'code'), 80 | ).to.eventually.deep.equal('https://dropbox.com/oauth2/authorize?response_type=code&client_id=CLIENT_ID'); 81 | }); 82 | 83 | it('changes the domain if a custom domain is set', () => { 84 | const dbx = new DropboxAuth({ 85 | clientId: 'CLIENT_ID', 86 | domain: 'mydomain.com', 87 | }); 88 | dbx.getAuthenticationUrl('localhost', null, 'code') 89 | .then((url) => { 90 | chai.assertEqual(url, 'https://mydomain.com/oauth2/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=localhost'); 91 | }); 92 | }); 93 | 94 | const dbx = new DropboxAuth({ clientId: 'CLIENT_ID' }); 95 | for (const redirectUri of ['', 'localhost']) { 96 | for (const state of ['', 'state']) { 97 | for (const tokenAccessType of [null, 'legacy', 'offline', 'online']) { 98 | for (const scope of [null, ['files.metadata.read', 'files.metadata.write']]) { 99 | for (const includeGrantedScopes of ['none', 'user', 'team']) { 100 | it(`returns correct auth url with all combinations of valid input. redirectUri: ${redirectUri}, state: ${state}, 101 | tokenAccessType: ${tokenAccessType}, scope: ${scope}, includeGrantedScopes: ${includeGrantedScopes}`, (done) => { 102 | dbx.getAuthenticationUrl(redirectUri, state, 'code', tokenAccessType, scope, includeGrantedScopes) // eslint-disable-line no-await-in-loop 103 | .then((url) => { 104 | chai.assert(url.startsWith('https://dropbox.com/oauth2/authorize?response_type=code&client_id=CLIENT_ID')); 105 | 106 | if (redirectUri) { 107 | chai.assert(url.includes(`&redirect_uri=${redirectUri}`)); 108 | } else { 109 | chai.assert(!url.includes('&redirect_uri=')); 110 | } 111 | 112 | if (state) { 113 | chai.assert(url.includes(`&state=${state}`)); 114 | } else { 115 | chai.assert(!url.includes('&state=')); 116 | } 117 | 118 | if (tokenAccessType) { 119 | chai.assert(url.includes(`&token_access_type=${tokenAccessType}`)); 120 | } else { 121 | chai.assert(!url.includes('&token_access_type=')); 122 | } 123 | 124 | if (scope) { 125 | chai.assert(url.includes(`&scope=${scope.join(' ')}`)); 126 | } else { 127 | chai.assert(!url.includes('&scope=')); 128 | } 129 | 130 | if (includeGrantedScopes !== 'none') { 131 | chai.assert(url.includes(`&include_granted_scopes=${includeGrantedScopes}`)); 132 | } else { 133 | chai.assert(!url.includes('&include_granted_scopes=')); 134 | } 135 | done(); 136 | }) 137 | .catch(done); 138 | }); 139 | } 140 | } 141 | } 142 | } 143 | } 144 | }); 145 | 146 | describe('clientSecret', () => { 147 | it('can be set in the constructor', () => { 148 | const dbx = new DropboxAuth({ clientSecret: 'foo' }); 149 | chai.assert.equal(dbx.getClientSecret(), 'foo'); 150 | }); 151 | 152 | it('is undefined if not set in constructor', () => { 153 | const dbx = new DropboxAuth(); 154 | chai.assert.equal(dbx.getClientSecret(), undefined); 155 | }); 156 | 157 | it('can be set after being instantiated', () => { 158 | const dbx = new DropboxAuth(); 159 | dbx.setClientSecret('foo'); 160 | chai.assert.equal(dbx.getClientSecret(), 'foo'); 161 | }); 162 | }); 163 | 164 | describe('dataOnBody', () => { 165 | it('can be set in the constructor', () => { 166 | const dbx = new DropboxAuth({ dataOnBody: true }); 167 | chai.assert.equal(dbx.dataOnBody, true); 168 | }); 169 | 170 | it('is undefined if not set in constructor', () => { 171 | const dbx = new DropboxAuth(); 172 | chai.assert.equal(dbx.dataOnBody, undefined); 173 | }); 174 | }); 175 | 176 | describe('refreshToken', () => { 177 | it('can be set in the constructor', () => { 178 | const dbxAuth = new DropboxAuth({ refreshToken: 'foo' }); 179 | chai.assert.equal(dbxAuth.getRefreshToken(), 'foo'); 180 | }); 181 | 182 | it('is undefined if not set in constructor', () => { 183 | const dbxAuth = new DropboxAuth(); 184 | chai.assert.equal(dbxAuth.getRefreshToken(), undefined); 185 | }); 186 | 187 | it('can be set after being instantiated', () => { 188 | const dbxAuth = new DropboxAuth(); 189 | dbxAuth.setRefreshToken('foo'); 190 | chai.assert.equal(dbxAuth.getRefreshToken(), 'foo'); 191 | }); 192 | }); 193 | 194 | describe('accessTokenExpiresAt', () => { 195 | it('can be set in the constructor', () => { 196 | const date = new Date(2020, 11, 30); 197 | const dbxAuth = new DropboxAuth({ accessTokenExpiresAt: date }); 198 | chai.assert.equal(dbxAuth.getAccessTokenExpiresAt(), date); 199 | }); 200 | 201 | it('is undefined if not set in constructor', () => { 202 | const dbxAuth = new DropboxAuth(); 203 | chai.assert.equal(dbxAuth.getAccessTokenExpiresAt(), undefined); 204 | }); 205 | 206 | it('can be set after being instantiated', () => { 207 | const dbxAuth = new DropboxAuth(); 208 | const date = new Date(2020, 11, 30); 209 | dbxAuth.setAccessTokenExpiresAt(date); 210 | chai.assert.equal(dbxAuth.getAccessTokenExpiresAt(), date); 211 | }); 212 | }); 213 | 214 | describe('codeVerifier', () => { 215 | it('is undefined when constructed', () => { 216 | const dbxAuth = new DropboxAuth(); 217 | chai.assert.equal(dbxAuth.getCodeVerifier(), undefined); 218 | }); 219 | 220 | it('can be set after being instantiated', () => { 221 | const dbxAuth = new DropboxAuth(); 222 | const verifier = 'abcdef'; 223 | dbxAuth.setCodeVerifier(verifier); 224 | chai.assert.equal(dbxAuth.getCodeVerifier(), verifier); 225 | }); 226 | }); 227 | 228 | describe('generatePKCECodes', () => { 229 | it('saves a new code verifier on Auth obj', () => { 230 | const dbxAuth = new DropboxAuth(); 231 | chai.assert.equal(dbxAuth.codeVerifier, undefined); 232 | dbxAuth.generatePKCECodes(); 233 | chai.assert.isTrue(!!dbxAuth.codeVerifier); 234 | }); 235 | 236 | it('creates a code verifier of the correct length', () => { 237 | const dbxAuth = new DropboxAuth(); 238 | dbxAuth.generatePKCECodes(); 239 | chai.assert.equal(dbxAuth.codeVerifier.length, 128); 240 | }); 241 | 242 | it('saves a new code challenge on Auth obj', () => { 243 | const dbxAuth = new DropboxAuth(); 244 | chai.assert.equal(dbxAuth.codeChallenge, undefined); 245 | dbxAuth.generatePKCECodes(); 246 | chai.assert.isTrue(!!dbxAuth.codeChallenge); 247 | }); 248 | 249 | it('gets called when using PKCE flow', (done) => { 250 | const dbxAuth = new DropboxAuth({ clientId: 'foo' }); 251 | const pkceSpy = sinon.spy(dbxAuth, 'generatePKCECodes'); 252 | dbxAuth.getAuthenticationUrl('test', null, undefined, undefined, undefined, undefined, true) 253 | .then(() => { 254 | chai.assert.isTrue(pkceSpy.calledOnce); 255 | done(); 256 | }) 257 | .catch(done); 258 | }); 259 | 260 | it('generates valid code challenge from verifier (Node)', (done) => { 261 | const dbxAuth = new DropboxAuth(); 262 | const verifier = 'NTUsMjIsMzYsMTY4LDIyLDEzNywyNDMsOTYsMTIxLDIxNSwxNDAsMTYwLDMwLDE1LDIzMSw1NiwzMCwyMTIsMTQyLDIyMywxMzMsMTIsMjI1LDIzOCwxMDcsMjQ1LDM0'; 263 | dbxAuth.setCodeVerifier(verifier); 264 | dbxAuth.generateCodeChallenge() 265 | .then(() => { 266 | chai.assert.equal(dbxAuth.codeChallenge, 'fKco8CJrMUeyji-FJ83wxnx2bqK6BOqzefPamApt3Nc'); 267 | done(); 268 | }) 269 | .catch(done); 270 | }); 271 | }); 272 | 273 | describe('getAccessTokenFromCode', () => { 274 | it('throws an error without a clientID', () => { 275 | const dbxAuth = new DropboxAuth(); 276 | chai.assert.throws( 277 | DropboxAuth.prototype.getAccessTokenFromCode.bind(dbxAuth, 'foo', 'bar'), 278 | Error, 279 | 'A client id is required. You can set the client id using .setClientId().', 280 | ); 281 | }); 282 | 283 | it('throws an error when not provided a client secret or code challenge', () => { 284 | const dbxAuth = new DropboxAuth({ clientId: 'foo' }); 285 | chai.assert.throws( 286 | DropboxAuth.prototype.getAccessTokenFromCode.bind(dbxAuth, 'foo', 'bar'), 287 | Error, 288 | 'You must use PKCE when generating the authorization URL to not include a client secret', 289 | ); 290 | }); 291 | 292 | it('sets the right path for fetch request', () => { 293 | const dbxAuth = new DropboxAuth({ 294 | clientId: 'foo', 295 | clientSecret: 'bar', 296 | }); 297 | 298 | const fetchSpy = sinon.spy(dbxAuth, 'fetch'); 299 | dbxAuth.getAccessTokenFromCode('foo', 'bar'); 300 | const path = dbxAuth.fetch.getCall(0).args[0]; 301 | chai.assert.isTrue(fetchSpy.calledOnce); 302 | chai.assert.equal('https://api.dropboxapi.com/oauth2/token?grant_type=authorization_code&code=bar&client_id=foo&client_secret=bar&redirect_uri=foo', path); 303 | }); 304 | 305 | it('sets the right path without a redirect uri', () => { 306 | const dbxAuth = new DropboxAuth({ 307 | clientId: 'foo', 308 | clientSecret: 'bar', 309 | }); 310 | 311 | const fetchSpy = sinon.spy(dbxAuth, 'fetch'); 312 | dbxAuth.getAccessTokenFromCode(undefined, 'bar'); 313 | const path = dbxAuth.fetch.getCall(0).args[0]; 314 | chai.assert.isTrue(fetchSpy.calledOnce); 315 | chai.assert.equal('https://api.dropboxapi.com/oauth2/token?grant_type=authorization_code&code=bar&client_id=foo&client_secret=bar', path); 316 | }); 317 | 318 | it('sets the correct headers for fetch request', () => { 319 | const dbxAuth = new DropboxAuth({ 320 | clientId: 'foo', 321 | clientSecret: 'bar', 322 | }); 323 | 324 | const fetchSpy = sinon.spy(dbxAuth, 'fetch'); 325 | dbxAuth.getAccessTokenFromCode('foo', 'bar'); 326 | const { headers } = dbxAuth.fetch.getCall(0).args[1]; 327 | chai.assert.isTrue(fetchSpy.calledOnce); 328 | chai.assert.equal(headers['Content-Type'], 'application/x-www-form-urlencoded'); 329 | }); 330 | }); 331 | 332 | describe('checkAndRefreshAccessToken', () => { 333 | it('does not refresh without refresh token or clientId', () => { 334 | const dbxAuth = new DropboxAuth(); 335 | const refreshSpy = sinon.spy(dbxAuth, 'refreshAccessToken'); 336 | dbxAuth.checkAndRefreshAccessToken(); 337 | chai.assert.isTrue(refreshSpy.notCalled); 338 | }); 339 | 340 | it('doesn\'t refresh token when not past expiration time', () => { 341 | const currentDate = new Date(); 342 | const dbxAuth = new DropboxAuth({ 343 | accessTokenExpiresAt: currentDate.setHours(currentDate.getHours() + 2), 344 | }); 345 | const refreshSpy = sinon.spy(dbxAuth, 'refreshAccessToken'); 346 | dbxAuth.checkAndRefreshAccessToken(); 347 | chai.assert.isTrue(refreshSpy.notCalled); 348 | }); 349 | 350 | it('refreshes token when past expiration', () => { 351 | const dbxAuth = new DropboxAuth({ 352 | accessTokenExpiresAt: new Date(2019, 11, 19), 353 | clientId: '123', 354 | refreshToken: 'foo', 355 | }); 356 | 357 | const refreshSpy = sinon.spy(dbxAuth, 'refreshAccessToken'); 358 | dbxAuth.checkAndRefreshAccessToken(); 359 | chai.assert.isTrue(refreshSpy.calledOnce); 360 | }); 361 | }); 362 | 363 | describe('refreshAccessToken', () => { 364 | it('throws an error when not provided with a clientId', () => { 365 | const dbxAuth = new DropboxAuth(); 366 | chai.assert.throws( 367 | DropboxAuth.prototype.refreshAccessToken.bind(dbxAuth), 368 | Error, 369 | 'A client id is required. You can set the client id using .setClientId().', 370 | ); 371 | }); 372 | 373 | it('throws an error when provided an argument that is not a list', () => { 374 | const dbxAuth = new DropboxAuth({ clientId: 'foo' }); 375 | chai.assert.throws( 376 | DropboxAuth.prototype.refreshAccessToken.bind(dbxAuth, 'not a list'), 377 | Error, 378 | 'Scope must be an array of strings', 379 | ); 380 | }); 381 | 382 | it('sets request content type to json', () => { 383 | const dbxAuth = new DropboxAuth({ 384 | clientId: 'foo', 385 | clientSecret: 'bar', 386 | }); 387 | 388 | const fetchSpy = sinon.spy(dbxAuth, 'fetch'); 389 | dbxAuth.refreshAccessToken(); 390 | chai.assert.isTrue(fetchSpy.calledOnce); 391 | 392 | const { headers } = dbxAuth.fetch.getCall(0).args[1]; 393 | chai.assert.equal(headers['Content-Type'], 'application/json'); 394 | }); 395 | 396 | describe('when dataOnBody flag is enabled', () => { 397 | const dataOnBody = true; 398 | 399 | it('does the request without URL parameters', () => { 400 | const dbxAuth = new DropboxAuth({ 401 | clientId: 'foo', 402 | clientSecret: 'bar', 403 | dataOnBody, 404 | }); 405 | 406 | const fetchSpy = sinon.spy(dbxAuth, 'fetch'); 407 | dbxAuth.refreshAccessToken(['files.metadata.read']); 408 | chai.assert.isTrue(fetchSpy.calledOnce); 409 | const refreshUrl = dbxAuth.fetch.getCall(0).args[0]; 410 | 411 | chai.assert.equal(refreshUrl, 'https://api.dropboxapi.com/oauth2/token'); 412 | }); 413 | 414 | it('sends the client id and secret in the body', () => { 415 | const dbxAuth = new DropboxAuth({ 416 | clientId: 'foo', 417 | clientSecret: 'bar', 418 | dataOnBody, 419 | }); 420 | 421 | const fetchSpy = sinon.spy(dbxAuth, 'fetch'); 422 | dbxAuth.refreshAccessToken(); 423 | chai.assert.isTrue(fetchSpy.calledOnce); 424 | 425 | const { body } = dbxAuth.fetch.getCall(0).args[1]; 426 | chai.assert.equal(body.client_id, 'foo'); 427 | chai.assert.equal(body.client_secret, 'bar'); 428 | }); 429 | 430 | it('sends the scope in the body when passed', () => { 431 | const dbxAuth = new DropboxAuth({ 432 | clientId: 'foo', 433 | clientSecret: 'bar', 434 | dataOnBody, 435 | }); 436 | 437 | const fetchSpy = sinon.spy(dbxAuth, 'fetch'); 438 | dbxAuth.refreshAccessToken(['files.metadata.read']); 439 | chai.assert.isTrue(fetchSpy.calledOnce); 440 | 441 | const { body } = dbxAuth.fetch.getCall(0).args[1]; 442 | chai.assert.equal(body.scope, 'files.metadata.read'); 443 | }); 444 | }); 445 | 446 | describe('when dataOnBody flag is disabled', () => { 447 | const dataOnBody = false; 448 | const testRefreshUrl = 'https://api.dropboxapi.com/oauth2/token?grant_type=refresh_token&refresh_token=undefined&client_id=foo&client_secret=bar'; 449 | 450 | it('sets the correct refresh url (no scope passed)', () => { 451 | const dbxAuth = new DropboxAuth({ 452 | clientId: 'foo', 453 | clientSecret: 'bar', 454 | dataOnBody, 455 | }); 456 | 457 | const fetchSpy = sinon.spy(dbxAuth, 'fetch'); 458 | dbxAuth.refreshAccessToken(); 459 | chai.assert.isTrue(fetchSpy.calledOnce); 460 | const refreshUrl = dbxAuth.fetch.getCall(0).args[0]; 461 | const { headers } = dbxAuth.fetch.getCall(0).args[1]; 462 | chai.assert.equal(refreshUrl, testRefreshUrl); 463 | chai.assert.equal(headers['Content-Type'], 'application/json'); 464 | }); 465 | 466 | it('sets the correct refresh url (scope passed)', () => { 467 | const dbxAuth = new DropboxAuth({ 468 | clientId: 'foo', 469 | clientSecret: 'bar', 470 | dataOnBody, 471 | }); 472 | 473 | const fetchSpy = sinon.spy(dbxAuth, 'fetch'); 474 | dbxAuth.refreshAccessToken(['files.metadata.read']); 475 | chai.assert.isTrue(fetchSpy.calledOnce); 476 | const refreshUrl = dbxAuth.fetch.getCall(0).args[0]; 477 | const { headers } = dbxAuth.fetch.getCall(0).args[1]; 478 | const testScopeUrl = `${testRefreshUrl}&scope=files.metadata.read`; 479 | chai.assert.equal(refreshUrl, testScopeUrl); 480 | chai.assert.equal(headers['Content-Type'], 'application/json'); 481 | }); 482 | }); 483 | }); 484 | describe('fetch and crypto', () => { 485 | const windowRef = global.window; 486 | const selfRef = global.self; 487 | let spyOnFetch; 488 | let spyOnDigest; 489 | let spyOnIsBrowserEnv; 490 | let spyOnIsWorkerEnv; 491 | beforeEach(() => { 492 | spyOnFetch = sinon.spy(); 493 | spyOnDigest = sinon.spy(); 494 | spyOnIsBrowserEnv = sinon.stub(utils, 'isBrowserEnv'); 495 | spyOnIsWorkerEnv = sinon.stub(utils, 'isWorkerEnv'); 496 | }); 497 | afterEach(() => { 498 | Object.defineProperty(global, 'window', { 499 | writable: true, 500 | value: windowRef, 501 | }); 502 | Object.defineProperty(global, 'self', { 503 | writable: true, 504 | value: selfRef, 505 | }); 506 | spyOnIsBrowserEnv.restore(); 507 | spyOnIsWorkerEnv.restore(); 508 | }); 509 | it('refers fetch and crypto from window object in browser env', () => { 510 | Object.defineProperty(global, 'window', { 511 | writable: true, 512 | value: { 513 | fetch: spyOnFetch, 514 | crypto: { 515 | subtle: { 516 | digest: () => ({ 517 | then: spyOnDigest, 518 | }), 519 | }, 520 | }, 521 | }, 522 | }); 523 | spyOnIsBrowserEnv.returns(true); 524 | const dbxAuth = new DropboxAuth({ 525 | clientId: 'foo', 526 | clientSecret: 'bar', 527 | }); 528 | dbxAuth.fetch(); 529 | dbxAuth.generateCodeChallenge(); 530 | chai.assert(spyOnIsBrowserEnv.called); 531 | chai.assert(!spyOnIsWorkerEnv.called); 532 | chai.assert(spyOnFetch.calledOnce); 533 | chai.assert(spyOnDigest.calledOnce); 534 | }); 535 | it('refers fetch and crypto from self object in service worker env', () => { 536 | Object.defineProperty(global, 'self', { 537 | writable: true, 538 | value: { 539 | fetch: spyOnFetch, 540 | crypto: { 541 | subtle: { 542 | digest: () => ({ 543 | then: spyOnDigest, 544 | }), 545 | }, 546 | }, 547 | }, 548 | }); 549 | spyOnIsBrowserEnv.returns(false); 550 | spyOnIsWorkerEnv.returns(true); 551 | const dbxAuth = new DropboxAuth({ 552 | clientId: 'foo', 553 | clientSecret: 'bar', 554 | }); 555 | dbxAuth.fetch(); 556 | dbxAuth.generateCodeChallenge(); 557 | chai.assert(spyOnIsBrowserEnv.called); 558 | chai.assert(spyOnIsWorkerEnv.called); 559 | chai.assert(spyOnFetch.calledOnce); 560 | chai.assert(spyOnDigest.calledOnce); 561 | }); 562 | }); 563 | }); 564 | -------------------------------------------------------------------------------- /test/unit/dropbox.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import sinon from 'sinon'; 4 | 5 | import { fail } from 'assert'; 6 | import { 7 | RPC, 8 | USER_AUTH, 9 | DOWNLOAD, 10 | UPLOAD, 11 | TEAM_AUTH, 12 | APP_AUTH, 13 | NO_AUTH, 14 | COOKIE, 15 | } from '../../src/constants.js'; 16 | import { Dropbox, DropboxAuth } from '../../index.js'; 17 | 18 | chai.use(chaiAsPromised); 19 | describe('Dropbox', () => { 20 | describe('selectUser', () => { 21 | it('can be set in the constructor', () => { 22 | const dbx = new Dropbox({ selectUser: 'foo' }); 23 | chai.assert.equal(dbx.selectUser, 'foo'); 24 | }); 25 | 26 | it('is undefined if not set in constructor', () => { 27 | const dbx = new Dropbox(); 28 | chai.assert.equal(dbx.selectUser, undefined); 29 | }); 30 | }); 31 | 32 | describe('domain', () => { 33 | it('can be set in the constructor', () => { 34 | const dbx = new Dropbox({ domain: 'mydomain.com' }); 35 | chai.assert.equal(dbx.domain, 'mydomain.com'); 36 | }); 37 | 38 | it('is undefined if not set in constructor', () => { 39 | const dbx = new Dropbox(); 40 | chai.assert.equal(dbx.domain, undefined); 41 | }); 42 | 43 | it('is set by auth.domain if not set in constructor', () => { 44 | const auth = new DropboxAuth({ domain: 'mydomain.com' }); 45 | const dbx = new Dropbox({ auth }); 46 | chai.assert.equal(dbx.domain, 'mydomain.com'); 47 | }); 48 | }); 49 | 50 | describe('domainDelimiter', () => { 51 | it('can be set in the constructor', () => { 52 | const dbx = new Dropbox({ domainDelimiter: '-' }); 53 | chai.assert.equal(dbx.domainDelimiter, '-'); 54 | }); 55 | 56 | it('is undefined if not set in constructor', () => { 57 | const dbx = new Dropbox(); 58 | chai.assert.equal(dbx.domainDelimiter, undefined); 59 | }); 60 | 61 | it('is set by auth.domainDelimiter if not set in constructor', () => { 62 | const auth = new DropboxAuth({ domainDelimiter: '-' }); 63 | const dbx = new Dropbox({ auth }); 64 | chai.assert.equal(dbx.domainDelimiter, '-'); 65 | }); 66 | }); 67 | 68 | describe('customHeaders', () => { 69 | it('can be set in the constructor', () => { 70 | const dbx = new Dropbox({ customHeaders: { foo: 'bar' } }); 71 | chai.assert.equal(dbx.customHeaders.foo, 'bar'); 72 | }); 73 | 74 | it('is undefined if not set in constructor', () => { 75 | const dbx = new Dropbox(); 76 | chai.assert.equal(dbx.customHeaders, undefined); 77 | }); 78 | 79 | it('is set by auth.customHeaders if not set in constructor', () => { 80 | const auth = new DropboxAuth({ customHeaders: { foo: 'bar' } }); 81 | const dbx = new Dropbox({ auth }); 82 | chai.assert.equal(dbx.customHeaders.foo, 'bar'); 83 | }); 84 | }); 85 | 86 | describe('RPC requests', () => { 87 | it('request() calls the correct request method', () => { 88 | const dbx = new Dropbox(); 89 | const rpcSpy = sinon.spy(dbx, 'rpcRequest'); 90 | dbx.request('path', {}, USER_AUTH, 'api', RPC) 91 | .catch((error) => { 92 | fail(error); 93 | }); 94 | chai.assert.isTrue(rpcSpy.calledOnce); 95 | chai.assert.equal('path', dbx.rpcRequest.getCall(0).args[0]); 96 | chai.assert.deepEqual({}, dbx.rpcRequest.getCall(0).args[1]); 97 | }); 98 | 99 | it('completes a cookie auth RPC request', () => { 100 | const dbxAuth = new DropboxAuth(); 101 | const dbx = new Dropbox({ auth: dbxAuth }); 102 | const rpcSpy = sinon.spy(dbx, 'rpcRequest'); 103 | dbx.request('path', {}, COOKIE, 'api', RPC) 104 | .catch((error) => { 105 | fail(error); 106 | }); 107 | chai.assert.isTrue(rpcSpy.calledOnce); 108 | chai.assert.equal('path', dbx.rpcRequest.getCall(0).args[0]); 109 | chai.assert.deepEqual({}, dbx.rpcRequest.getCall(0).args[1]); 110 | chai.assert.equal(COOKIE, dbx.rpcRequest.getCall(0).args[2]); 111 | }); 112 | 113 | it('throws an error for invalid request styles', () => { 114 | chai.assert.throws( 115 | Dropbox.prototype.request.bind(Dropbox, '', {}, 'user', 'api', 'BADTYPE'), 116 | Error, 117 | 'Invalid request style', 118 | ); 119 | }); 120 | }); 121 | 122 | describe('Upload Requests', () => { 123 | it('request() calls the correct request method', () => { 124 | const dbx = new Dropbox(); 125 | const uploadSpy = sinon.spy(dbx, 'uploadRequest'); 126 | dbx.request('path', {}, USER_AUTH, 'api', UPLOAD) 127 | .catch((error) => { 128 | fail(error); 129 | }); 130 | chai.assert.isTrue(uploadSpy.calledOnce); 131 | chai.assert.equal('path', dbx.uploadRequest.getCall(0).args[0]); 132 | chai.assert.deepEqual({}, dbx.uploadRequest.getCall(0).args[1]); 133 | }); 134 | }); 135 | 136 | describe('Download Requests', () => { 137 | it('calls the correct request method', () => { 138 | const dbx = new Dropbox(); 139 | const downloadSpy = sinon.spy(dbx, 'downloadRequest'); 140 | dbx.request('path', {}, USER_AUTH, 'api', DOWNLOAD) 141 | .catch((error) => { 142 | fail(error); 143 | }); 144 | chai.assert.isTrue(downloadSpy.calledOnce); 145 | chai.assert.equal('path', dbx.downloadRequest.getCall(0).args[0]); 146 | chai.assert.deepEqual({}, dbx.downloadRequest.getCall(0).args[1]); 147 | }); 148 | }); 149 | 150 | describe('pathRoot', () => { 151 | it('can be set in the constructor', () => { 152 | const dbx = new Dropbox({ pathRoot: 'foo' }); 153 | chai.assert.equal(dbx.pathRoot, 'foo'); 154 | }); 155 | 156 | it('is undefined if not set in constructor', () => { 157 | const dbx = new Dropbox(); 158 | chai.assert.equal(dbx.pathRoot, undefined); 159 | }); 160 | }); 161 | 162 | describe('setCommonHeaders', () => { 163 | it('creates the correct headers when constructed with select user/admin and/or path root', () => { 164 | for (const selectUser of [undefined, 'foo']) { 165 | for (const selectAdmin of [undefined, 'bar']) { 166 | for (const pathRoot of [undefined, 'test']) { 167 | const dbx = new Dropbox({ 168 | selectUser, 169 | selectAdmin, 170 | pathRoot, 171 | }); 172 | 173 | const fetchOptions = { 174 | headers: {}, 175 | }; 176 | dbx.setCommonHeaders(fetchOptions); 177 | const { headers } = fetchOptions; 178 | chai.assert.equal(headers['Dropbox-API-Select-User'], selectUser); 179 | chai.assert.equal(headers['Dropbox-API-Select-Admin'], selectAdmin); 180 | chai.assert.equal(headers['Dropbox-API-Path-Root'], pathRoot); 181 | } 182 | } 183 | } 184 | }); 185 | 186 | it('sets custom headers correctly', () => { 187 | const dbx = new Dropbox({ 188 | customHeaders: { 189 | foo: 'bar', 190 | milk: 'shake', 191 | cookie: 'hash', 192 | }, 193 | }); 194 | 195 | const fetchOptions = { 196 | headers: {}, 197 | }; 198 | 199 | dbx.setCommonHeaders(fetchOptions); 200 | const { headers } = fetchOptions; 201 | chai.assert.equal(headers.foo, 'bar'); 202 | chai.assert.equal(headers.milk, 'shake'); 203 | chai.assert.equal(headers.cookie, 'hash'); 204 | }); 205 | }); 206 | 207 | describe('setAuthHeaders', () => { 208 | const authTypes = ['user', 'app', 'team', 'noauth', 'user, app', 'team, app', 'cookie']; 209 | for (const auth of authTypes) { 210 | for (const hasAccessToken of [true, false]) { 211 | for (const hasAppKeys of [true, false]) { 212 | it(`correctly sets auth headers given '${auth}' auth and ${hasAccessToken ? 'has' : 'does not have'} an access token`, () => { 213 | const dbx = new Dropbox({ 214 | accessToken: hasAccessToken ? 'token' : undefined, 215 | clientId: hasAppKeys ? 'app_key' : undefined, 216 | clientSecret: hasAppKeys ? 'app_secret' : undefined, 217 | }); 218 | 219 | const fetchOptions = { 220 | headers: {}, 221 | }; 222 | 223 | const isExpectedToHaveTokenHeader = hasAccessToken && (auth.includes('user') || auth.includes('team')); 224 | const isExpectedToHaveAppHeader = ((auth === 'app') || (auth.includes('app') && !hasAccessToken)) && hasAppKeys; 225 | 226 | dbx.setAuthHeaders(auth, fetchOptions); 227 | 228 | const { headers } = fetchOptions; 229 | if (isExpectedToHaveAppHeader) { 230 | chai.assert.isTrue(headers.Authorization.includes('Basic')); 231 | } else if (isExpectedToHaveTokenHeader) { 232 | chai.assert.isTrue(headers.Authorization.includes('Bearer')); 233 | } else { 234 | chai.assert.deepEqual(headers, {}); 235 | } 236 | }); 237 | } 238 | } 239 | } 240 | 241 | it('throws an error on an invalid auth type', () => { 242 | const dbx = new Dropbox(); 243 | 244 | const fetchOptions = { 245 | headers: {}, 246 | }; 247 | 248 | chai.assert.throws( 249 | Dropbox.prototype.setAuthHeaders.bind(Dropbox, 'bad auth type', fetchOptions), 250 | Error, 251 | ); 252 | }); 253 | }); 254 | }); 255 | -------------------------------------------------------------------------------- /test/unit/error.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | 3 | import { DropboxResponseError } from '../../src/error.js'; 4 | 5 | describe('DropboxResponseError', () => { 6 | describe('Error throwing', () => { 7 | it('can be caught with a try/catch', () => { 8 | try { 9 | throw new DropboxResponseError(400, {}, {}); 10 | } catch (error) { 11 | chai.assert.isTrue(error instanceof DropboxResponseError); 12 | chai.assert.equal(error.status, 400); 13 | chai.assert.deepEqual(error.headers, {}); 14 | chai.assert.deepEqual(error.error, {}); 15 | } 16 | }); 17 | 18 | it('can be caught in a promise reject', () => { 19 | new Promise((resolve, reject) => { 20 | reject(new DropboxResponseError(400, {}, {})); 21 | }).catch((error) => { 22 | chai.assert.isTrue(error instanceof DropboxResponseError); 23 | chai.assert.equal(error.status, 400); 24 | chai.assert.deepEqual(error.headers, {}); 25 | chai.assert.deepEqual(error.error, {}); 26 | }); 27 | }); 28 | 29 | it('can be caught if thrown in promise', () => { 30 | new Promise(() => { 31 | throw new DropboxResponseError(400, {}, {}); 32 | }).catch((error) => { 33 | chai.assert.isTrue(error instanceof DropboxResponseError); 34 | chai.assert.equal(error.status, 400); 35 | chai.assert.deepEqual(error.headers, {}); 36 | chai.assert.deepEqual(error.error, {}); 37 | }); 38 | }); 39 | }); 40 | 41 | describe('Message', () => { 42 | it('correctly formats message with response code', () => { 43 | const error = new DropboxResponseError(400, {}, {}); 44 | chai.assert.equal(error.message, 'Response failed with a 400 code'); 45 | }); 46 | }); 47 | 48 | describe('Name', () => { 49 | it('correctly sets name', () => { 50 | const error = new DropboxResponseError(400, {}, {}); 51 | chai.assert.equal(error.name, 'DropboxResponseError'); 52 | }); 53 | }); 54 | 55 | describe('Stack Trace', () => { 56 | it('is set in super', () => { 57 | const error = new DropboxResponseError(400, {}, {}); 58 | chai.assert.exists(error.stack); 59 | }); 60 | }); 61 | 62 | describe('Status', () => { 63 | it('can be set in the constructor', () => { 64 | const error = new DropboxResponseError(400, {}, {}); 65 | chai.assert.equal(error.status, 400); 66 | }); 67 | }); 68 | 69 | describe('Headers', () => { 70 | it('can be set in the constructor', () => { 71 | const error = new DropboxResponseError(400, {}, {}); 72 | chai.assert.deepEqual(error.headers, {}); 73 | }); 74 | }); 75 | 76 | describe('Error', () => { 77 | it('can be set in the constructor', () => { 78 | const error = new DropboxResponseError(400, {}, {}); 79 | chai.assert.deepEqual(error.error, {}); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/unit/response.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import { Response } from 'node-fetch'; 4 | import { httpHeaderSafeJson } from '../../src/utils'; 5 | import { 6 | DropboxResponse, 7 | parseResponse, 8 | parseDownloadResponse, 9 | } from '../../src/response.js'; 10 | import { DropboxResponseError } from '../../src/error.js'; 11 | 12 | chai.use(chaiAsPromised); 13 | describe('DropboxResponse', () => { 14 | describe('Status', () => { 15 | it('can be set in the constructor', () => { 16 | const response = new DropboxResponse(200, {}, {}); 17 | chai.assert.equal(response.status, 200); 18 | }); 19 | }); 20 | 21 | describe('Headers', () => { 22 | it('can be set in the constructor', () => { 23 | const response = new DropboxResponse(200, {}, {}); 24 | chai.assert.deepEqual(response.headers, {}); 25 | }); 26 | }); 27 | 28 | describe('Result', () => { 29 | it('can be set in the constructor', () => { 30 | const response = new DropboxResponse(200, {}, {}); 31 | chai.assert.deepEqual(response.result, {}); 32 | }); 33 | }); 34 | 35 | describe('parseResponse', () => { 36 | it('correctly parses the response', () => { 37 | const init = { 38 | status: 200, 39 | }; 40 | const response = new Response(undefined, init); 41 | return chai.assert.isFulfilled(parseResponse(response)); 42 | }); 43 | 44 | it('throws an error when not a 200 status code', () => { 45 | const statusArray = [300, 400, 500]; 46 | for (const status of statusArray) { 47 | const init = { 48 | status, 49 | }; 50 | const response = new Response(undefined, init); 51 | chai.assert.isRejected(parseResponse(response), DropboxResponseError); 52 | } 53 | }); 54 | }); 55 | 56 | describe('parseDownloadResponse', () => { 57 | it('correctly parses the response', () => { 58 | const init = { 59 | status: 200, 60 | headers: { 61 | 'dropbox-api-result': httpHeaderSafeJson({ fileBinary: 'test' }), 62 | }, 63 | }; 64 | const response = new Response(undefined, init); 65 | return chai.assert.isFulfilled(parseDownloadResponse(response)); 66 | }); 67 | 68 | it('throws an error when not a 200 status code', () => { 69 | const statusArray = [300, 400, 500]; 70 | for (const status of statusArray) { 71 | const init = { 72 | status, 73 | }; 74 | const response = new Response(undefined, init); 75 | chai.assert.isRejected(parseDownloadResponse(response), DropboxResponseError); 76 | } 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/unit/utils.js: -------------------------------------------------------------------------------- 1 | import chai from 'chai'; 2 | import { 3 | baseApiUrl, 4 | getTokenExpiresAtDate, 5 | isWindowOrWorker, 6 | OAuth2AuthorizationUrl, 7 | OAuth2TokenUrl, 8 | isWorkerEnv, 9 | } from '../../src/utils.js'; 10 | 11 | describe('Dropbox utils', () => { 12 | describe('baseApiUrl', () => { 13 | it('correctly sets base url when domainDelimiter is empty', () => { 14 | const host = 'api'; 15 | const domain = 'mydomain.com'; 16 | const testUrl = baseApiUrl(host, domain, ''); 17 | chai.assert.equal(testUrl, 'https://mydomain.com/2/'); 18 | }); 19 | 20 | it('correctly sets base url when provided a subdomain', () => { 21 | const host = 'test'; 22 | const testUrl = baseApiUrl(host); 23 | chai.assert.equal(testUrl, 'https://test.dropboxapi.com/2/'); 24 | }); 25 | 26 | it('correctly sets base url when provided a subdomain and domain', () => { 27 | const host = 'test'; 28 | const domain = 'mydomain.com'; 29 | const testUrl = baseApiUrl(host, domain); 30 | chai.assert.equal(testUrl, 'https://test.mydomain.com/2/'); 31 | }); 32 | }); 33 | 34 | it('correctly sets base url when provided a normal subdomain and domain', () => { 35 | const host = 'api'; 36 | const domain = 'mydomain.com'; 37 | const testUrl = baseApiUrl(host, domain); 38 | chai.assert.equal(testUrl, 'https://api-mydomain.com/2/'); 39 | }); 40 | }); 41 | 42 | describe('OAuth2AuthorizationUrl', () => { 43 | it('correctly returns the authorization url when not provided an override', () => { 44 | const testUrl = OAuth2AuthorizationUrl(); 45 | chai.assert.equal(testUrl, 'https://dropbox.com/oauth2/authorize'); 46 | }); 47 | 48 | it('correctly returns the authorization url when provided an override', () => { 49 | const domain = 'mydomain.com'; 50 | const testUrl = OAuth2AuthorizationUrl(domain); 51 | chai.assert.equal(testUrl, 'https://meta-mydomain.com/oauth2/authorize'); 52 | }); 53 | }); 54 | 55 | describe('OAuth2TokenUrl', () => { 56 | it('correctly returns the authorization url when not provided an override', () => { 57 | const testUrl = OAuth2TokenUrl(); 58 | chai.assert.equal(testUrl, 'https://api.dropboxapi.com/oauth2/token'); 59 | }); 60 | 61 | it('correctly returns the authorization url when provided an override', () => { 62 | const domain = 'mydomain.com'; 63 | const testUrl = OAuth2TokenUrl(domain); 64 | chai.assert.equal(testUrl, 'https://api-mydomain.com/oauth2/token'); 65 | }); 66 | }); 67 | 68 | describe('getTokenExpiresAtDate', () => { 69 | it('correctly sets when the token will expire', () => { 70 | const expirationTime = 600; 71 | const currentDate = new Date(Date.now()); 72 | const expiresDate = getTokenExpiresAtDate(expirationTime); 73 | // adding a buffer of 100ms to expiration time 74 | chai.assert.isTrue((expiresDate - currentDate) / 1000 <= (expirationTime + 100)); 75 | }); 76 | }); 77 | 78 | describe('isWindowOrWorker', () => { 79 | it('returns false when not window or of type WorkerGlobalScope', () => { 80 | const testEnv = isWindowOrWorker(); 81 | chai.assert.isFalse(testEnv); 82 | }); 83 | }); 84 | 85 | describe('isWorkerEnv', () => { 86 | it('returns false when not running in a service worker env', () => { 87 | chai.assert.isFalse(isWorkerEnv()); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /types/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb-base"], 3 | "env": { 4 | "browser": false, 5 | "node": true 6 | }, 7 | "rules": { 8 | "func-names": 0, 9 | "no-param-reassign": 0, 10 | "import/prefer-default-export": 0 11 | }, 12 | "plugins": ["@typescript-eslint"], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { "ecmaVersion": 6 } 15 | } --------------------------------------------------------------------------------