├── .github └── workflows │ ├── codeql-analysis.yml │ └── runtest.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── SECURITY.md ├── jasmine.json ├── package-lock.json ├── package.json ├── src ├── class │ ├── AuthPin.ts │ └── PlexOauth.ts ├── helpers │ ├── LinkHelper.ts │ ├── RequestHelper.ts │ ├── Util.ts │ └── Validators.ts ├── index.ts └── models │ └── PlexCodeModels.ts ├── test └── index.spec.ts └── tsconfig.json /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '40 0 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.github/workflows/runtest.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: testing 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | test: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v2 24 | 25 | # Runs a set of commands using the runners shell 26 | - name: Run a multi-line script 27 | run: | 28 | npm install 29 | npm test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | node_modules/ 3 | build/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Bob Henley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![testing](https://github.com/Dmbob/plex-oauth/workflows/testing/badge.svg) 2 | ![codeql](https://github.com/Dmbob/plex-oauth/workflows/CodeQL/badge.svg) 3 | 4 | ## Overview 5 | This library is used to work with Plex OAuth to get an auth token used for making requests to a Plex server's API 6 | 7 | As a note, these auth tokens should **not** be public. If you are making an app using a Plex auth token, it is recommended to not use the auth token on a frontend. If you need to authenticate a frontend system, then you should use the Plex auth token on the backend to make requests to the Plex server and use a different type of authentication for the frontend that will work with the Plex auth token on the backend. 8 | 9 | ## Installation 10 | You can install this package through npm 11 | ``` 12 | npm install plex-oauth 13 | ``` 14 | 15 | ## Usage 16 | There are only 2 main functions to use in the `PlexOauth` class (`requestHostedLoginURL` & `checkForAuthToken`). These will both return promises. If either of these functions throw an error, it needs to be explicitly caught with a `try/catch` or using `.catch(err)` on the promise itself. 17 | 18 | Please look at the following example of how to get the Plex hosted login UI URL and how to query for the auth token: 19 | ```typescript 20 | import { PlexOauth, IPlexClientDetails } from "plex-oauth" 21 | 22 | let clientInformation: IPlexClientDetails = { 23 | clientIdentifier: "", // This is a unique identifier used to identify your app with Plex. 24 | product: "", // Name of your application 25 | device: "", // The type of device your application is running on 26 | version: "1", // Version of your application 27 | forwardUrl: "https://localhost:3000", // Optional - Url to forward back to after signing in. 28 | platform: "Web", // Optional - Platform your application runs on - Defaults to 'Web' 29 | urlencode: true // Optional - If set to true, the output URL is url encoded, otherwise if not specified or 'false', the output URL will return as-is 30 | } 31 | 32 | let plexOauth = new PlexOauth(clientInformation); 33 | 34 | // Get hosted UI URL and Pin Id 35 | plexOauth.requestHostedLoginURL().then(data => { 36 | let [hostedUILink, pinId] = data; 37 | 38 | console.log(hostedUILink); // UI URL used to log into Plex 39 | 40 | /* 41 | * You can now navigate the user's browser to the 'hostedUILink'. This will include the forward URL 42 | * for your application, so when they have finished signing into Plex, they will be redirected back 43 | * to the specified URL. From there, you just need to perform a query to check for the auth token. 44 | * (See Below) 45 | */ 46 | 47 | // Check for the auth token, once returning to the application 48 | plexOauth.checkForAuthToken(pinId).then(authToken => { 49 | console.log(authToken); // Returns the auth token if set, otherwise returns null 50 | 51 | // An auth token will only be null if the user never signs into the hosted UI, or you stop checking for a new one before they can log in 52 | }).catch(err => { 53 | throw err; 54 | }); 55 | 56 | }).catch(err => { 57 | throw err; 58 | }); 59 | ``` 60 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | > 2.0 | :white_check_mark: | 8 | | < 2.0 | :x: | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | Please open an issue if a vulnarability is found. A response will be made as soon as possible 13 | -------------------------------------------------------------------------------- /jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporters": [ 3 | { 4 | "name": "jasmine-spec-reporter#SpecReporter", 5 | "options": { 6 | "displayStacktrace": "all" 7 | } 8 | } 9 | ], 10 | "spec_dir": "test", 11 | "spec_files": ["**/*[sS]pec.ts"] 12 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plex-oauth", 3 | "version": "2.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/jasmine": { 8 | "version": "3.6.10", 9 | "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.6.10.tgz", 10 | "integrity": "sha512-yfCl7JGtIc5LjScFpeIGBBNhJFkJdAAcsAnAd9ZRHwzh+sR2zkt257BKkTCF5VpJ8wMPnzzZ8QatRdXM8tqpKA==", 11 | "dev": true 12 | }, 13 | "@types/node": { 14 | "version": "14.14.43", 15 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.43.tgz", 16 | "integrity": "sha512-3pwDJjp1PWacPTpH0LcfhgjvurQvrZFBrC6xxjaUEZ7ifUtT32jtjPxEMMblpqd2Mvx+k8haqQJLQxolyGN/cQ==", 17 | "dev": true 18 | }, 19 | "ansi-regex": { 20 | "version": "5.0.1", 21 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 22 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 23 | "dev": true 24 | }, 25 | "ansi-styles": { 26 | "version": "4.3.0", 27 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 28 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 29 | "dev": true, 30 | "requires": { 31 | "color-convert": "^2.0.1" 32 | } 33 | }, 34 | "arg": { 35 | "version": "4.1.3", 36 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 37 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 38 | "dev": true 39 | }, 40 | "axios": { 41 | "version": "0.26.1", 42 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", 43 | "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", 44 | "requires": { 45 | "follow-redirects": "^1.14.8" 46 | } 47 | }, 48 | "balanced-match": { 49 | "version": "1.0.2", 50 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 51 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 52 | "dev": true 53 | }, 54 | "brace-expansion": { 55 | "version": "1.1.11", 56 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 57 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 58 | "dev": true, 59 | "requires": { 60 | "balanced-match": "^1.0.0", 61 | "concat-map": "0.0.1" 62 | } 63 | }, 64 | "buffer-from": { 65 | "version": "1.1.1", 66 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 67 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 68 | "dev": true 69 | }, 70 | "cliui": { 71 | "version": "7.0.4", 72 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 73 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 74 | "dev": true, 75 | "requires": { 76 | "string-width": "^4.2.0", 77 | "strip-ansi": "^6.0.0", 78 | "wrap-ansi": "^7.0.0" 79 | } 80 | }, 81 | "color-convert": { 82 | "version": "2.0.1", 83 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 84 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 85 | "dev": true, 86 | "requires": { 87 | "color-name": "~1.1.4" 88 | } 89 | }, 90 | "color-name": { 91 | "version": "1.1.4", 92 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 93 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 94 | "dev": true 95 | }, 96 | "colors": { 97 | "version": "1.4.0", 98 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 99 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", 100 | "dev": true 101 | }, 102 | "concat-map": { 103 | "version": "0.0.1", 104 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 105 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 106 | "dev": true 107 | }, 108 | "diff": { 109 | "version": "4.0.2", 110 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 111 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 112 | "dev": true 113 | }, 114 | "emoji-regex": { 115 | "version": "8.0.0", 116 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 117 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 118 | "dev": true 119 | }, 120 | "escalade": { 121 | "version": "3.1.1", 122 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 123 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 124 | "dev": true 125 | }, 126 | "follow-redirects": { 127 | "version": "1.15.1", 128 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", 129 | "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" 130 | }, 131 | "fs.realpath": { 132 | "version": "1.0.0", 133 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 134 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 135 | "dev": true 136 | }, 137 | "get-caller-file": { 138 | "version": "2.0.5", 139 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 140 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 141 | "dev": true 142 | }, 143 | "glob": { 144 | "version": "7.1.6", 145 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 146 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 147 | "dev": true, 148 | "requires": { 149 | "fs.realpath": "^1.0.0", 150 | "inflight": "^1.0.4", 151 | "inherits": "2", 152 | "minimatch": "^3.0.4", 153 | "once": "^1.3.0", 154 | "path-is-absolute": "^1.0.0" 155 | } 156 | }, 157 | "inflight": { 158 | "version": "1.0.6", 159 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 160 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 161 | "dev": true, 162 | "requires": { 163 | "once": "^1.3.0", 164 | "wrappy": "1" 165 | } 166 | }, 167 | "inherits": { 168 | "version": "2.0.4", 169 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 170 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 171 | "dev": true 172 | }, 173 | "is-fullwidth-code-point": { 174 | "version": "3.0.0", 175 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 176 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 177 | "dev": true 178 | }, 179 | "jasmine": { 180 | "version": "3.7.0", 181 | "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.7.0.tgz", 182 | "integrity": "sha512-wlzGQ+cIFzMEsI+wDqmOwvnjTvolLFwlcpYLCqSPPH0prOQaW3P+IzMhHYn934l1imNvw07oCyX+vGUv3wmtSQ==", 183 | "dev": true, 184 | "requires": { 185 | "glob": "^7.1.6", 186 | "jasmine-core": "~3.7.0" 187 | } 188 | }, 189 | "jasmine-core": { 190 | "version": "3.7.1", 191 | "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.7.1.tgz", 192 | "integrity": "sha512-DH3oYDS/AUvvr22+xUBW62m1Xoy7tUlY1tsxKEJvl5JeJ7q8zd1K5bUwiOxdH+erj6l2vAMM3hV25Xs9/WrmuQ==", 193 | "dev": true 194 | }, 195 | "jasmine-spec-reporter": { 196 | "version": "5.0.2", 197 | "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-5.0.2.tgz", 198 | "integrity": "sha512-6gP1LbVgJ+d7PKksQBc2H0oDGNRQI3gKUsWlswKaQ2fif9X5gzhQcgM5+kiJGCQVurOG09jqNhk7payggyp5+g==", 199 | "dev": true, 200 | "requires": { 201 | "colors": "1.4.0" 202 | } 203 | }, 204 | "jasmine-ts": { 205 | "version": "0.3.3", 206 | "resolved": "https://registry.npmjs.org/jasmine-ts/-/jasmine-ts-0.3.3.tgz", 207 | "integrity": "sha512-ISBmhSe1imdT2V3u681YgIIlpnt68dhdu8l94JeWfL4sYE67bfoHj2Coc1k1ND3u+DxQcHMTn8nCZ7Pr3dV6TQ==", 208 | "dev": true, 209 | "requires": { 210 | "yargs": "^16.2.0" 211 | } 212 | }, 213 | "make-error": { 214 | "version": "1.3.6", 215 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 216 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 217 | "dev": true 218 | }, 219 | "minimatch": { 220 | "version": "3.0.4", 221 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 222 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 223 | "dev": true, 224 | "requires": { 225 | "brace-expansion": "^1.1.7" 226 | } 227 | }, 228 | "once": { 229 | "version": "1.4.0", 230 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 231 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 232 | "dev": true, 233 | "requires": { 234 | "wrappy": "1" 235 | } 236 | }, 237 | "path-is-absolute": { 238 | "version": "1.0.1", 239 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 240 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 241 | "dev": true 242 | }, 243 | "require-directory": { 244 | "version": "2.1.1", 245 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 246 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 247 | "dev": true 248 | }, 249 | "source-map": { 250 | "version": "0.6.1", 251 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 252 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 253 | "dev": true 254 | }, 255 | "source-map-support": { 256 | "version": "0.5.19", 257 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", 258 | "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", 259 | "dev": true, 260 | "requires": { 261 | "buffer-from": "^1.0.0", 262 | "source-map": "^0.6.0" 263 | } 264 | }, 265 | "string-width": { 266 | "version": "4.2.3", 267 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 268 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 269 | "dev": true, 270 | "requires": { 271 | "emoji-regex": "^8.0.0", 272 | "is-fullwidth-code-point": "^3.0.0", 273 | "strip-ansi": "^6.0.1" 274 | } 275 | }, 276 | "strip-ansi": { 277 | "version": "6.0.1", 278 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 279 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 280 | "dev": true, 281 | "requires": { 282 | "ansi-regex": "^5.0.1" 283 | } 284 | }, 285 | "ts-node": { 286 | "version": "8.10.2", 287 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", 288 | "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", 289 | "dev": true, 290 | "requires": { 291 | "arg": "^4.1.0", 292 | "diff": "^4.0.1", 293 | "make-error": "^1.1.1", 294 | "source-map-support": "^0.5.17", 295 | "yn": "3.1.1" 296 | } 297 | }, 298 | "typescript": { 299 | "version": "3.9.9", 300 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", 301 | "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", 302 | "dev": true 303 | }, 304 | "wrap-ansi": { 305 | "version": "7.0.0", 306 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 307 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 308 | "dev": true, 309 | "requires": { 310 | "ansi-styles": "^4.0.0", 311 | "string-width": "^4.1.0", 312 | "strip-ansi": "^6.0.0" 313 | } 314 | }, 315 | "wrappy": { 316 | "version": "1.0.2", 317 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 318 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 319 | "dev": true 320 | }, 321 | "y18n": { 322 | "version": "5.0.8", 323 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 324 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 325 | "dev": true 326 | }, 327 | "yargs": { 328 | "version": "16.2.0", 329 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 330 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 331 | "dev": true, 332 | "requires": { 333 | "cliui": "^7.0.2", 334 | "escalade": "^3.1.1", 335 | "get-caller-file": "^2.0.5", 336 | "require-directory": "^2.1.1", 337 | "string-width": "^4.2.0", 338 | "y18n": "^5.0.5", 339 | "yargs-parser": "^20.2.2" 340 | } 341 | }, 342 | "yargs-parser": { 343 | "version": "20.2.9", 344 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", 345 | "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", 346 | "dev": true 347 | }, 348 | "yn": { 349 | "version": "3.1.1", 350 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 351 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 352 | "dev": true 353 | } 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plex-oauth", 3 | "version": "2.1.0", 4 | "description": "Small library to handle simple OAuth with Plex", 5 | "repository": { 6 | "url": "https://github.com/Dmbob/plex-oauth" 7 | }, 8 | "main": "build/src/index.js", 9 | "scripts": { 10 | "prepublishOnly": "npm run build && npm test", 11 | "test": "jasmine-ts --config=jasmine.json", 12 | "build": "tsc" 13 | }, 14 | "keywords": [ 15 | "plex", 16 | "oauth" 17 | ], 18 | "author": "Bob Henley", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "@types/jasmine": "^3.6.10", 22 | "@types/node": "^14.14.43", 23 | "jasmine": "^3.7.0", 24 | "jasmine-spec-reporter": "^5.0.2", 25 | "jasmine-ts": "^0.3.3", 26 | "ts-node": "^8.10.2", 27 | "typescript": "^3.9.9" 28 | }, 29 | "files": [ 30 | "build/**/*" 31 | ], 32 | "dependencies": { 33 | "axios": "^0.26.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/class/AuthPin.ts: -------------------------------------------------------------------------------- 1 | import { IPlexCodeResponse, IPlexClientDetails } from "../models/PlexCodeModels"; 2 | import { RequestHelper } from "../helpers/RequestHelper"; 3 | import { LinkHelper } from "../helpers/LinkHelper"; 4 | import { Util } from "../helpers/Util"; 5 | 6 | export class AuthPin { 7 | constructor() {} 8 | 9 | /** 10 | * Request's an OAuth Pin from the Plex API, which is used to get the Auth Token 11 | * @param {IPlexClientDetails} clientInfo Client information to send to the Plex API 12 | * 13 | * @returns {Promise} The hosted ui URL 14 | */ 15 | public getPin(clientInfo: IPlexClientDetails): Promise { 16 | return RequestHelper.post(`${LinkHelper.PLEX_PIN_BASE_PATH}/pins?strong=true`, "", {...LinkHelper.getHeaders(clientInfo)}).then(response => { 17 | return response as IPlexCodeResponse; 18 | }).catch(err => { 19 | throw err; 20 | }); 21 | } 22 | 23 | /** 24 | * Sends repeated requests to the Plex Pin API with the provided pin id. It will then return 25 | * the auth token if it gets one, or null if it runs out of retries. 26 | * @param {IPlexClientDetails} clientInfo Client information to send to the Plex API 27 | * @param {number} pinId The id of the Plex pin to query for 28 | * @param {number} requestDelay The delay in seconds to wait between each poll 29 | * @param {number} maxRetries The number of retries before returning null 30 | * 31 | * @returns {Promise} The auth token if found or null 32 | */ 33 | public pollForAuthToken(clientInfo: IPlexClientDetails, pinId: number, requestDelay: number, maxRetries: number): Promise { 34 | if(requestDelay < 1000) { 35 | requestDelay = 1000; 36 | } 37 | 38 | return RequestHelper.get(`${LinkHelper.PLEX_PIN_BASE_PATH}/pins/${pinId}`, {...LinkHelper.getHeaders(clientInfo)}).then(response => { 39 | if(response) { 40 | let pinData = response as IPlexCodeResponse; 41 | 42 | if(pinData.authToken) { 43 | return pinData.authToken; 44 | }else { 45 | if(maxRetries <= 0) { return null } 46 | return Util.wait(() => this.pollForAuthToken(clientInfo, pinId, requestDelay, maxRetries-=1), requestDelay); 47 | } 48 | }else { 49 | return null; 50 | } 51 | }).catch(err => { 52 | throw err; 53 | }); 54 | } 55 | } -------------------------------------------------------------------------------- /src/class/PlexOauth.ts: -------------------------------------------------------------------------------- 1 | import { IPlexClientDetails } from "../models/PlexCodeModels"; 2 | import { AuthPin } from "./AuthPin"; 3 | import { LinkHelper } from "../helpers/LinkHelper"; 4 | import { Validators } from "../helpers/Validators"; 5 | 6 | export class PlexOauth { 7 | private authPin: AuthPin; 8 | 9 | /** 10 | * Create an instance of the 'PlexOauth' class 11 | * @param {IPlexClientDetails} clientIdentifier Unique identifier for your client (Should be different for every client) 12 | */ 13 | constructor (private clientInfo: IPlexClientDetails) { 14 | for (const validator of Validators.clientDetailsValidators) { 15 | validator(clientInfo); 16 | } 17 | 18 | this.authPin = new AuthPin(); 19 | } 20 | 21 | /** 22 | * Request the hosted UI link for your app. A user will use this link to sign in and authenticate with Plex. 23 | * This gets returned with the pin id needed to query the Plex Pin API for the auth token 24 | * @returns {Promise<[string, number]} [hostedUIUrl, pinId] - Returns a promise of the hosted login URL and the pin Id as a tuple 25 | */ 26 | public requestHostedLoginURL(): Promise<[string, number]> { 27 | return this.authPin.getPin(this.clientInfo).then(codeResponse => { 28 | let link = `${ 29 | LinkHelper.PLEX_AUTH_BASE_PATH 30 | }#?code=${ 31 | codeResponse.code 32 | }&context[device][product]=${ 33 | this.clientInfo.product 34 | }&context[device][device]=${ 35 | this.clientInfo.device 36 | }&clientID=${ 37 | codeResponse.clientIdentifier 38 | }`; 39 | 40 | if (this.clientInfo.forwardUrl) { 41 | link += `&forwardUrl=${this.clientInfo.forwardUrl || ""}`; 42 | } 43 | 44 | return [ 45 | this.clientInfo.urlencode ? encodeURI(link) : link, 46 | codeResponse.id 47 | ] as [string, number]; 48 | }).catch(err => { 49 | throw err; 50 | }); 51 | } 52 | 53 | /** 54 | * After a user signs in with the hosted UI, we need to check the Plex API for the auth token. 55 | * This function will poll their API looking for the auth token and returning it if found. 56 | * If the auth token is not found, this function will return null 57 | * @param {number} pinId The pinId to query for 58 | * @param {number} requestDelay The amount of delay in milliseconds. Can not go below 1000 (1 second) 59 | * @param {number} maxRetries The maximum number of retries until an auth token is received 60 | * 61 | * @returns {Promise} The authtoken if found or null 62 | */ 63 | public checkForAuthToken(pinId: number, requestDelay?: number, maxRetries?: number): Promise { 64 | if(!pinId) { throw new Error ("Pin Id is not set - Unable to poll for auth token without id"); } 65 | 66 | // If 'requestDelay' or 'maxRetries' is not set, then we will treat this 67 | // as a single request, so we only request the auth token from the api once 68 | requestDelay = requestDelay || 1000; 69 | maxRetries = maxRetries || 0; 70 | 71 | return this.authPin.pollForAuthToken(this.clientInfo, pinId, requestDelay, maxRetries).then(authToken => { 72 | return authToken; 73 | }).catch(err => { 74 | throw err; 75 | }); 76 | } 77 | } -------------------------------------------------------------------------------- /src/helpers/LinkHelper.ts: -------------------------------------------------------------------------------- 1 | import { IPlexPinHeaders, IPlexClientDetails, IPlexCodeResponse } from "../models/PlexCodeModels"; 2 | 3 | /** 4 | * Contains a series of helper functions and values for getting and building the url's needed to 5 | * request a Plex pin and token 6 | */ 7 | export class LinkHelper { 8 | public static readonly PLEX_PIN_BASE_PATH = "https://plex.tv/api/v2"; 9 | public static readonly PLEX_AUTH_BASE_PATH = "https://app.plex.tv/auth"; 10 | 11 | public static readonly PLEX_DEFAULT_PLATFORM = "Web"; 12 | 13 | /** 14 | * Returns the headers needed to make requests to the Plex API using the client info 15 | * @param {IPlexClientDetails} clientInfo Client info to build headers from 16 | * 17 | * @returns {IPlexPinHeaders} The headers needed to make the requests to the Plex API 18 | */ 19 | public static getHeaders(clientInfo: IPlexClientDetails): IPlexPinHeaders { 20 | return { 21 | "X-Plex-Client-Identifier": clientInfo.clientIdentifier, 22 | "X-Plex-Device": clientInfo.device, 23 | "X-Plex-Platform": clientInfo.platform || this.PLEX_DEFAULT_PLATFORM, 24 | "X-Plex-Product": clientInfo.product, 25 | "X-Plex-Version": clientInfo.version 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/helpers/RequestHelper.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig } from "axios"; 2 | 3 | /** 4 | * Class to facilitate the different web requests needed to query the Plex OAuth API 5 | */ 6 | export class RequestHelper { 7 | /** 8 | * Make a GET request to the specified endpoint 9 | * @param {string} url Request URL 10 | * @param {OutgoingHttpHeaders} headers Additional headers to be passed to the request 11 | * 12 | * @returns {Promise} A promise containing the result of the GET request 13 | */ 14 | public static get(url: string, headers: AxiosRequestConfig["headers"]): Promise { 15 | return axios.get(url, { 16 | headers: { 17 | "Content-Type": "application/json", 18 | "Accept": "application/json", 19 | ...headers 20 | } 21 | }).then(response => { 22 | return response.data; 23 | }); 24 | } 25 | 26 | /** 27 | * Make a POST request to the specified endpoint 28 | * @param {string} url Request URL 29 | * @param {string} body Body as a JSON String 30 | * @param {OutgoingHttpHeaders} headers Additional headers to be passed to the request 31 | * 32 | * @returns {Promise} A promise containing the result of the POST request 33 | */ 34 | public static post(url: string, body: string, headers: AxiosRequestConfig["headers"]): Promise { 35 | return axios.post(url, body, { 36 | headers: { 37 | "Content-Type": "application/json", 38 | "Accept": "application/json", 39 | ...headers 40 | } 41 | }).then(response => { 42 | return response.data; 43 | }); 44 | } 45 | } -------------------------------------------------------------------------------- /src/helpers/Util.ts: -------------------------------------------------------------------------------- 1 | export class Util { 2 | /** 3 | * Returns the input function after waiting the specified amount of time 4 | * @param {Function} func The input function 5 | * @param {number} waitTime The amount of time in milliseconds to wait 6 | * 7 | * @returns {Promise} A promise containing the value of the input function 8 | */ 9 | public static wait(func: Function, waitTime: number): Promise { 10 | return new Promise((resolve, reject) => { 11 | setTimeout(() => { 12 | resolve(func() as unknown as T); 13 | }, waitTime) 14 | }); 15 | } 16 | 17 | /** 18 | * Converts input URL string into a 'URL' object and passes it to the specified validator function 19 | * @param {string} url The URL to validate 20 | * @param {(url: URL) => boolean} validator The validation logic to apply to the URL 21 | * @returns {boolean} True if the validation passes or false if it does not 22 | */ 23 | public static validateUrl(url: string, validator: (url: URL) => boolean) { 24 | const u = new URL(url); 25 | return validator(u); 26 | } 27 | } -------------------------------------------------------------------------------- /src/helpers/Validators.ts: -------------------------------------------------------------------------------- 1 | import { IPlexClientDetails } from "../models/PlexCodeModels"; 2 | import { Util } from "./Util"; 3 | 4 | /** 5 | * This namespace contains validators used for various purposes 6 | */ 7 | export namespace Validators { 8 | /** 9 | * Validators specific to validating the Plex Client Details 10 | */ 11 | export const clientDetailsValidators: Array<(clientInfo: IPlexClientDetails) => void> = [ 12 | /** 13 | * Validate that the forward URL only uses http/https protocols 14 | */ 15 | (clientInfo) => { 16 | if (clientInfo.forwardUrl && (!Util.validateUrl(clientInfo.forwardUrl, forwardUrl => { 17 | const regex = new RegExp(/http[s]?/); 18 | return regex.test(forwardUrl.protocol); 19 | }))) { 20 | console.warn("Validation: The forwardUrl must have a protocol of http/https"); 21 | } 22 | } 23 | ]; 24 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./class/PlexOauth"; 2 | export { IPlexClientDetails } from "./models/PlexCodeModels"; -------------------------------------------------------------------------------- /src/models/PlexCodeModels.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the location of the user requesting the pin code 3 | */ 4 | interface IPlexCodeLocation { 5 | city: string, 6 | code: string, 7 | coordinates: string, 8 | country: string, 9 | postal_code: string, 10 | subdivisions: string, 11 | time_zone: string 12 | } 13 | 14 | /** 15 | * Represents the response from the Plex Pins API 16 | */ 17 | export interface IPlexCodeResponse { 18 | authToken: string | null, 19 | clientIdentifier: string, 20 | code: string, 21 | createdAt: string, 22 | expiresAt: string, 23 | expiresIn: number, 24 | id: number, 25 | location: IPlexCodeLocation, 26 | newRegistration:boolean | null, 27 | product: string, 28 | trusted: boolean 29 | } 30 | 31 | /** 32 | * Represents the headers needed when sending requests to the Plex Pins API 33 | */ 34 | export interface IPlexPinHeaders { 35 | "X-Plex-Client-Identifier": string, 36 | "X-Plex-Device": string, 37 | "X-Plex-Platform": string, 38 | "X-Plex-Product": string, 39 | "X-Plex-Version": string 40 | } 41 | 42 | /** 43 | * Represents info about your Plex application 44 | */ 45 | export interface IPlexClientDetails { 46 | /** 47 | * Unique Id that identifies your client 48 | */ 49 | clientIdentifier: string, 50 | 51 | /** 52 | * Name of your application 53 | */ 54 | product: string, 55 | 56 | /** 57 | * The type of device your application is running on 58 | */ 59 | device: string, 60 | 61 | /** 62 | * Version of your application 63 | */ 64 | version: string, 65 | 66 | /** 67 | * Optional - Url to forward back to your application 68 | */ 69 | forwardUrl?: string 70 | 71 | /** 72 | * Optional - Defaults to 'Web' 73 | */ 74 | platform?: string, 75 | 76 | /** 77 | * Optional - Whether or not the output url should be url encoded 78 | */ 79 | urlencode?: boolean 80 | } -------------------------------------------------------------------------------- /test/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlexOauth } from "../src"; 2 | 3 | describe("Module", () => { 4 | let plexOauth = new PlexOauth({ 5 | clientIdentifier: "dhjoasoidjapoiwdmamlw", 6 | device: "TEST DEVICE", 7 | product: "TEST DEVICE", 8 | version: "1", 9 | forwardUrl: "https://localhost/" 10 | }); 11 | 12 | beforeEach(() => { 13 | spyOn(console, "warn"); 14 | }) 15 | 16 | it("loads and initializes", () => { 17 | expect(plexOauth).toBeDefined(); 18 | }); 19 | 20 | it("returns a hosted ui link and poll for an auth token", () => { 21 | plexOauth.requestHostedLoginURL().then(response => { 22 | let [hostedUiLink, pinId] = response; 23 | 24 | expect(hostedUiLink).toBeDefined(); 25 | expect(hostedUiLink).toContain("https://app.plex.tv/auth"); 26 | expect(hostedUiLink).not.toContain("undefined"); 27 | expect(hostedUiLink).not.toContain("null"); 28 | 29 | plexOauth.checkForAuthToken(pinId, 1000, 1).then(pinResponse => { 30 | let authToken = pinResponse; 31 | 32 | expect(authToken).toBeNull(); 33 | }); 34 | }); 35 | }); 36 | 37 | it("logs a warning if the forwardUrl does not use http/https protocol", () => { 38 | const testError = new PlexOauth({ 39 | clientIdentifier: "dhjoasoidjapoiwdmamlw", 40 | device: "TEST DEVICE", 41 | product: "TEST DEVICE", 42 | version: "1", 43 | forwardUrl: "notreal://localhost/" 44 | }); 45 | 46 | expect(console.warn).toHaveBeenCalledWith("Validation: The forwardUrl must have a protocol of http/https"); 47 | }); 48 | 49 | it("returns a URL with no forwardUrl if the input forwardUrl is not specified", async () => { 50 | const details = await new PlexOauth({ 51 | clientIdentifier: "dhjoasoidjapoiwdmamlw", 52 | device: "TEST DEVICE", 53 | product: "TEST DEVICE", 54 | version: "1" 55 | }).requestHostedLoginURL(); 56 | 57 | const url = new URL(details[0]); 58 | expect(url.hash).not.toMatch(/\&forwardUrl=(.*)/) 59 | }); 60 | 61 | it("returns a URL with forwardUrl if the input forwardUrl is specified", async () => { 62 | const details = await new PlexOauth({ 63 | clientIdentifier: "dhjoasoidjapoiwdmamlw", 64 | device: "TEST DEVICE", 65 | product: "TEST DEVICE", 66 | version: "1", 67 | forwardUrl: "http://example.com" 68 | }).requestHostedLoginURL(); 69 | 70 | const url = new URL(details[0]); 71 | expect(url.hash).toMatch(/\&forwardUrl=http:\/\/example.com/) 72 | }); 73 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./build", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | "typeRoots": [ 49 | "node_modules/@types" 50 | ], /* List of folders to include type definitions from. */ 51 | "types": [ 52 | "node", 53 | "jasmine" 54 | ], /* Type declaration files to be included in compilation. */ 55 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 56 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 57 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 58 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 59 | 60 | /* Source Map Options */ 61 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 62 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 63 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 64 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 65 | 66 | /* Experimental Options */ 67 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 68 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 69 | 70 | /* Advanced Options */ 71 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 72 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 73 | } 74 | } 75 | --------------------------------------------------------------------------------